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
parent 302b0d4ae7
commit dd63ecd1f7
3 changed files with 16 additions and 13 deletions

View file

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

View file

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

View file

@ -440,10 +440,11 @@ export function parseSessionCompactionPolicy(agent: typeof agents.$inferSelect):
export function resolveRuntimeSessionParamsForWorkspace(input: { export function resolveRuntimeSessionParamsForWorkspace(input: {
agentId: string; agentId: string;
agentName?: string | null; // [nexus] added for slug workspace dirs
previousSessionParams: Record<string, unknown> | null; previousSessionParams: Record<string, unknown> | null;
resolvedWorkspace: ResolvedWorkspaceForRun; resolvedWorkspace: ResolvedWorkspaceForRun;
}) { }) {
const { agentId, previousSessionParams, resolvedWorkspace } = input; const { agentId, agentName, previousSessionParams, resolvedWorkspace } = input;
const previousSessionId = readNonEmptyString(previousSessionParams?.sessionId); const previousSessionId = readNonEmptyString(previousSessionParams?.sessionId);
const previousCwd = readNonEmptyString(previousSessionParams?.cwd); const previousCwd = readNonEmptyString(previousSessionParams?.cwd);
if (!previousSessionId || !previousCwd) { if (!previousSessionId || !previousCwd) {
@ -465,7 +466,7 @@ export function resolveRuntimeSessionParamsForWorkspace(input: {
warning: null as string | null, warning: null as string | null,
}; };
} }
const fallbackAgentHomeCwd = resolveDefaultAgentWorkspaceDir(agentId); const fallbackAgentHomeCwd = resolveDefaultAgentWorkspaceDir({ id: agentId, name: agentName });
if (path.resolve(previousCwd) !== path.resolve(fallbackAgentHomeCwd)) { if (path.resolve(previousCwd) !== path.resolve(fallbackAgentHomeCwd)) {
return { return {
sessionParams: previousSessionParams, sessionParams: previousSessionParams,
@ -1180,7 +1181,7 @@ export function heartbeatService(db: Db) {
missingProjectCwds.push(projectCwd); missingProjectCwds.push(projectCwd);
} }
const fallbackCwd = resolveDefaultAgentWorkspaceDir(agent.id); const fallbackCwd = resolveDefaultAgentWorkspaceDir({ id: agent.id, name: agent.name });
await fs.mkdir(fallbackCwd, { recursive: true }); await fs.mkdir(fallbackCwd, { recursive: true });
const warnings: string[] = []; const warnings: string[] = [];
if (preferredWorkspaceWarning) { 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 }); await fs.mkdir(cwd, { recursive: true });
const warnings: string[] = []; const warnings: string[] = [];
if (sessionCwd) { if (sessionCwd) {
@ -2240,6 +2241,7 @@ export function heartbeatService(db: Db) {
} }
const runtimeSessionResolution = resolveRuntimeSessionParamsForWorkspace({ const runtimeSessionResolution = resolveRuntimeSessionParamsForWorkspace({
agentId: agent.id, agentId: agent.id,
agentName: agent.name, // [nexus] pass agent name for slug workspace dirs
previousSessionParams, previousSessionParams,
resolvedWorkspace: { resolvedWorkspace: {
...resolvedWorkspace, ...resolvedWorkspace,
@ -2271,7 +2273,7 @@ export function heartbeatService(db: Db) {
branchName: executionWorkspace.branchName, branchName: executionWorkspace.branchName,
worktreePath: executionWorkspace.worktreePath, worktreePath: executionWorkspace.worktreePath,
agentHome: await (async () => { agentHome: await (async () => {
const home = resolveDefaultAgentWorkspaceDir(agent.id); const home = resolveDefaultAgentWorkspaceDir({ id: agent.id, name: agent.name });
await fs.mkdir(home, { recursive: true }); await fs.mkdir(home, { recursive: true });
return home; return home;
})(), })(),