fix(sandbox): canonicalize agent main alias

This commit is contained in:
Peter Steinberger
2026-01-12 02:22:56 +00:00
parent 828d9955f2
commit 76c8fc8697
6 changed files with 68 additions and 16 deletions

View File

@@ -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:<id>:main` as a main-session alias when `session.mainKey` is customized (backwards compatible).
## 2026.1.10

View File

@@ -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");

View File

@@ -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: {

View File

@@ -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 "";

View File

@@ -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", () => {

View File

@@ -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);
}