feat(02-02): update resolveDefaultAgentWorkspaceDir to use slugified agent names

- Change signature from (agentId: string) to (agent: { id: string; name?: string | null })
- Use sanitizeFriendlyPathSegment(name) for human-readable workspace dirs
- Fall back to sanitized id when name is empty/null
- Update all 4 call sites in heartbeat.ts with { id, name } objects
- Add agentName field to resolveRuntimeSessionParamsForWorkspace input type
- Update both test call sites in heartbeat-workspace-session.test.ts
This commit is contained in:
Mikkel Georgsen 2026-03-30 23:08:44 +02:00 committed by Nexus Dev
parent e803935b33
commit 09343ad034
3 changed files with 16 additions and 13 deletions

View file

@ -59,7 +59,7 @@ function buildAgent(adapterType: string, runtimeConfig: Record<string, unknown>
describe("resolveRuntimeSessionParamsForWorkspace", () => {
it("migrates fallback workspace sessions to project workspace when project cwd becomes available", () => {
const agentId = "agent-123";
const fallbackCwd = resolveDefaultAgentWorkspaceDir(agentId);
const fallbackCwd = resolveDefaultAgentWorkspaceDir({ id: agentId });
const result = resolveRuntimeSessionParamsForWorkspace({
agentId,
@ -100,7 +100,7 @@ describe("resolveRuntimeSessionParamsForWorkspace", () => {
it("does not migrate when resolved workspace id differs from previous session workspace id", () => {
const agentId = "agent-123";
const fallbackCwd = resolveDefaultAgentWorkspaceDir(agentId);
const fallbackCwd = resolveDefaultAgentWorkspaceDir({ id: agentId });
const result = resolveRuntimeSessionParamsForWorkspace({
agentId,

View file

@ -73,12 +73,13 @@ export function resolveDefaultBackupDir(): string {
return path.resolve(resolvePaperclipInstanceRoot(), "data", "backups");
}
export function resolveDefaultAgentWorkspaceDir(agentId: string): string {
const trimmed = agentId.trim();
if (!PATH_SEGMENT_RE.test(trimmed)) {
throw new Error(`Invalid agent id for workspace path '${agentId}'.`);
}
return path.resolve(resolvePaperclipInstanceRoot(), "workspaces", trimmed);
// [nexus] Accept agent object for human-readable slugified workspace dirs
export function resolveDefaultAgentWorkspaceDir(agent: { id: string; name?: string | null }): string {
// Use slugified name for human-readable dirs; fall back to sanitized id
const segment = agent.name?.trim()
? sanitizeFriendlyPathSegment(agent.name, agent.id)
: sanitizeFriendlyPathSegment(agent.id, agent.id);
return path.resolve(resolvePaperclipInstanceRoot(), "workspaces", segment);
}
function sanitizeFriendlyPathSegment(value: string | null | undefined, fallback = "_default"): string {

View file

@ -525,10 +525,11 @@ export function parseSessionCompactionPolicy(agent: typeof agents.$inferSelect):
export function resolveRuntimeSessionParamsForWorkspace(input: {
agentId: string;
agentName?: string | null; // [nexus] added for slug workspace dirs
previousSessionParams: Record<string, unknown> | null;
resolvedWorkspace: ResolvedWorkspaceForRun;
}) {
const { agentId, previousSessionParams, resolvedWorkspace } = input;
const { agentId, agentName, previousSessionParams, resolvedWorkspace } = input;
const previousSessionId = readNonEmptyString(previousSessionParams?.sessionId);
const previousCwd = readNonEmptyString(previousSessionParams?.cwd);
if (!previousSessionId || !previousCwd) {
@ -550,7 +551,7 @@ export function resolveRuntimeSessionParamsForWorkspace(input: {
warning: null as string | null,
};
}
const fallbackAgentHomeCwd = resolveDefaultAgentWorkspaceDir(agentId);
const fallbackAgentHomeCwd = resolveDefaultAgentWorkspaceDir({ id: agentId, name: agentName });
if (path.resolve(previousCwd) !== path.resolve(fallbackAgentHomeCwd)) {
return {
sessionParams: previousSessionParams,
@ -1295,7 +1296,7 @@ export function heartbeatService(db: Db) {
missingProjectCwds.push(projectCwd);
}
const fallbackCwd = resolveDefaultAgentWorkspaceDir(agent.id);
const fallbackCwd = resolveDefaultAgentWorkspaceDir({ id: agent.id, name: agent.name });
await fs.mkdir(fallbackCwd, { recursive: true });
const warnings: string[] = [];
if (preferredWorkspaceWarning) {
@ -1364,7 +1365,7 @@ export function heartbeatService(db: Db) {
}
}
const cwd = resolveDefaultAgentWorkspaceDir(agent.id);
const cwd = resolveDefaultAgentWorkspaceDir({ id: agent.id, name: agent.name });
await fs.mkdir(cwd, { recursive: true });
const warnings: string[] = [];
if (sessionCwd) {
@ -2391,6 +2392,7 @@ export function heartbeatService(db: Db) {
}
const runtimeSessionResolution = resolveRuntimeSessionParamsForWorkspace({
agentId: agent.id,
agentName: agent.name, // [nexus] pass agent name for slug workspace dirs
previousSessionParams,
resolvedWorkspace: {
...resolvedWorkspace,
@ -2422,7 +2424,7 @@ export function heartbeatService(db: Db) {
branchName: executionWorkspace.branchName,
worktreePath: executionWorkspace.worktreePath,
agentHome: await (async () => {
const home = resolveDefaultAgentWorkspaceDir(agent.id);
const home = resolveDefaultAgentWorkspaceDir({ id: agent.id, name: agent.name });
await fs.mkdir(home, { recursive: true });
return home;
})(),