diff --git a/server/src/__tests__/heartbeat-workspace-session.test.ts b/server/src/__tests__/heartbeat-workspace-session.test.ts index 7fab2b42..0fece280 100644 --- a/server/src/__tests__/heartbeat-workspace-session.test.ts +++ b/server/src/__tests__/heartbeat-workspace-session.test.ts @@ -55,7 +55,7 @@ function buildAgent(adapterType: string, runtimeConfig: Record 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, @@ -96,7 +96,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, diff --git a/server/src/home-paths.ts b/server/src/home-paths.ts index 714f7513..fbba15bb 100644 --- a/server/src/home-paths.ts +++ b/server/src/home-paths.ts @@ -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 { diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index c909b9b7..06ad9f2a 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -440,10 +440,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 | 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) { @@ -465,7 +466,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, @@ -1180,7 +1181,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) { @@ -1249,7 +1250,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) { @@ -2240,6 +2241,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, @@ -2271,7 +2273,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; })(),