diff --git a/CHANGELOG.md b/CHANGELOG.md index d3baa9cfa..23c77de8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Gateway/Agents: canonicalize main session aliases for store writes and add regression coverage. (#709) — thanks @xMikeMickelson. - Agents: reset sessions and retry when auto-compaction overflows instead of crashing the gateway. - Sandbox: fix non-main mode incorrectly sandboxing the main DM session and align `/status` runtime reporting with effective sandbox state. +- Sandbox/Gateway: treat `agent::main` as a main-session alias when `session.mainKey` is customized (backwards compatible). ## 2026.1.10 diff --git a/src/agents/sandbox.resolveSandboxContext.test.ts b/src/agents/sandbox.resolveSandboxContext.test.ts index 9b555800c..7228f177d 100644 --- a/src/agents/sandbox.resolveSandboxContext.test.ts +++ b/src/agents/sandbox.resolveSandboxContext.test.ts @@ -105,6 +105,14 @@ describe("resolveSandboxContext", () => { }), ).toBeNull(); + expect( + await resolveSandboxContext({ + config: cfg, + sessionKey: "agent:main:main", + workspaceDir: "/tmp/clawdbot-test", + }), + ).toBeNull(); + expect( await ensureSandboxWorkspaceForSession({ config: cfg, @@ -113,6 +121,14 @@ describe("resolveSandboxContext", () => { }), ).toBeNull(); + expect( + await ensureSandboxWorkspaceForSession({ + config: cfg, + sessionKey: "agent:main:main", + workspaceDir: "/tmp/clawdbot-test", + }), + ).toBeNull(); + expect(spawn).not.toHaveBeenCalled(); vi.doUnmock("node:child_process"); diff --git a/src/agents/sandbox.ts b/src/agents/sandbox.ts index 0c2204f3b..e796e5039 100644 --- a/src/agents/sandbox.ts +++ b/src/agents/sandbox.ts @@ -19,9 +19,12 @@ import { loadConfig, STATE_DIR_CLAWDBOT, } from "../config/config.js"; -import { resolveAgentMainSessionKey } from "../config/sessions.js"; +import { + canonicalizeMainSessionAlias, + resolveAgentMainSessionKey, +} from "../config/sessions.js"; import { PROVIDER_IDS } from "../providers/registry.js"; -import { normalizeAgentId, normalizeMainKey } from "../routing/session-key.js"; +import { normalizeAgentId } from "../routing/session-key.js"; import { defaultRuntime } from "../runtime.js"; import { resolveUserPath } from "../utils.js"; import { @@ -566,22 +569,11 @@ function resolveComparableSessionKeyForSandbox(params: { agentId: string; sessionKey: string; }): string { - const trimmed = params.sessionKey.trim(); - if (!trimmed) return trimmed; - - const mainKey = normalizeMainKey(params.cfg?.session?.mainKey); - const agentMainSessionKey = resolveAgentMainSessionKey({ + return canonicalizeMainSessionAlias({ cfg: params.cfg, agentId: params.agentId, + sessionKey: params.sessionKey, }); - const isMainAlias = - trimmed === "main" || - trimmed === mainKey || - trimmed === agentMainSessionKey; - - if (params.cfg?.session?.scope === "global" && isMainAlias) return "global"; - if (isMainAlias) return agentMainSessionKey; - return trimmed; } export function resolveSandboxRuntimeStatus(params: { diff --git a/src/config/sessions.ts b/src/config/sessions.ts index 53d7dcc62..074e7f983 100644 --- a/src/config/sessions.ts +++ b/src/config/sessions.ts @@ -250,6 +250,33 @@ export function resolveAgentMainSessionKey(params: { return buildAgentMainSessionKey({ agentId: params.agentId, mainKey }); } +export function canonicalizeMainSessionAlias(params: { + cfg?: { session?: { scope?: SessionScope; mainKey?: string } }; + agentId: string; + sessionKey: string; +}): string { + const raw = params.sessionKey.trim(); + if (!raw) return raw; + + const agentId = normalizeAgentId(params.agentId); + const mainKey = normalizeMainKey(params.cfg?.session?.mainKey); + const agentMainSessionKey = buildAgentMainSessionKey({ agentId, mainKey }); + const agentMainAliasKey = buildAgentMainSessionKey({ + agentId, + mainKey: "main", + }); + + const isMainAlias = + raw === "main" || + raw === mainKey || + raw === agentMainSessionKey || + raw === agentMainAliasKey; + + if (params.cfg?.session?.scope === "global" && isMainAlias) return "global"; + if (isMainAlias) return agentMainSessionKey; + return raw; +} + function normalizeGroupLabel(raw?: string) { const trimmed = raw?.trim().toLowerCase() ?? ""; if (!trimmed) return ""; diff --git a/src/gateway/session-utils.test.ts b/src/gateway/session-utils.test.ts index ce1afe8a6..a46caa858 100644 --- a/src/gateway/session-utils.test.ts +++ b/src/gateway/session-utils.test.ts @@ -48,6 +48,9 @@ describe("gateway session utils", () => { expect(resolveSessionStoreKey({ cfg, sessionKey: "work" })).toBe( "agent:ops:work", ); + expect(resolveSessionStoreKey({ cfg, sessionKey: "agent:ops:main" })).toBe( + "agent:ops:work", + ); }); test("resolveSessionStoreKey canonicalizes bare keys to default agent", () => { diff --git a/src/gateway/session-utils.ts b/src/gateway/session-utils.ts index 960ac9021..207e60c20 100644 --- a/src/gateway/session-utils.ts +++ b/src/gateway/session-utils.ts @@ -13,6 +13,7 @@ import { type ClawdbotConfig, loadConfig } from "../config/config.js"; import { resolveStateDir } from "../config/paths.js"; import { buildGroupDisplayName, + canonicalizeMainSessionAlias, loadSessionStore, resolveMainSessionKey, resolveSessionTranscriptPath, @@ -299,11 +300,23 @@ export function resolveSessionStoreKey(params: { const raw = params.sessionKey.trim(); if (!raw) return raw; if (raw === "global" || raw === "unknown") return raw; + + const parsed = parseAgentSessionKey(raw); + if (parsed) { + const agentId = normalizeAgentId(parsed.agentId); + const canonical = canonicalizeMainSessionAlias({ + cfg: params.cfg, + agentId, + sessionKey: raw, + }); + if (canonical !== raw) return canonical; + return raw; + } + const rawMainKey = normalizeMainKey(params.cfg.session?.mainKey); if (raw === "main" || raw === rawMainKey) { return resolveMainSessionKey(params.cfg); } - if (raw.startsWith("agent:")) return raw; const agentId = resolveDefaultStoreAgentId(params.cfg); return canonicalizeSessionKeyForAgent(agentId, raw); }