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 1afc6f427d
commit 4b670431f3
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

@ -523,10 +523,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) {
@ -548,7 +549,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,
@ -1293,7 +1294,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) {
@ -1362,7 +1363,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) {
@ -2382,6 +2383,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,
@ -2413,7 +2415,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;
})(),