fix: preserve configured ACP deleted-agent guard

This commit is contained in:
scotthuang
2026-06-09 18:12:55 +01:00
committed by Shakker
parent 3853eb15af
commit 52154eda0d
3 changed files with 66 additions and 4 deletions
+16
View File
@@ -846,6 +846,22 @@ describe("gateway session utils", () => {
).toBeNull();
});
test("resolveDeletedAgentIdFromSessionKey rejects deleted configured ACP binding owners", () => {
const cfg = {
agents: { list: [{ id: "main", default: true }] },
} as OpenClawConfig;
expect(
resolveDeletedAgentIdFromSessionKey(
cfg,
"agent:deleted-agent:acp:binding:discord:default:feedface",
),
).toBe("deleted-agent");
expect(
resolveDeletedAgentIdFromSessionKey(cfg, "agent:main:acp:binding:discord:default:feedface"),
).toBeNull();
});
test("resolveSessionStoreKey canonicalizes bare keys to default agent", () => {
const cfg = {
session: { mainKey: "main" },
+5 -4
View File
@@ -922,14 +922,15 @@ export function resolveDeletedAgentIdFromSessionKey(
cfg: OpenClawConfig,
sessionKey: string,
): string | null {
// ACP keys use agent:<harnessId>:acp:<uuid>; harness ids are not agents.list entries.
if (isAcpSessionKey(sessionKey)) {
return null;
}
const parsed = parseAgentSessionKey(sessionKey);
if (!parsed) {
return null;
}
// Free ACP spawn keys use agent:<harnessId>:acp:<uuid>, but configured ACP
// bindings use agent:<agentId>:acp:binding:* where agentId remains the owner.
if (isAcpSessionKey(sessionKey) && !parsed.rest.startsWith("acp:binding:")) {
return null;
}
const agentId = normalizeAgentId(parsed.agentId);
if (listAgentIds(cfg).includes(agentId)) {
return null;
@@ -248,6 +248,51 @@ describe("resolveSessionKeyFromResolveParams store canonicalization", () => {
});
});
it("rejects configured ACP binding sessions when their owning agent is deleted", async () => {
await withStateDirEnv("openclaw-sessions-resolve-acp-binding-deleted-", async () => {
const cfg: OpenClawConfig = {
agents: { list: [{ id: "main", default: true }] },
};
const acpBindingKey = "agent:deleted-agent:acp:binding:discord:default:feedface";
const deletedStorePath = resolveStorePath(cfg.session?.store, { agentId: "deleted-agent" });
await saveSessionStore(deletedStorePath, {
[acpBindingKey]: {
sessionId: "sess-acp-binding-deleted",
label: "deleted-binding",
updatedAt: freshUpdatedAt(),
},
});
const expected = {
ok: false,
error: {
code: ErrorCodes.INVALID_REQUEST,
message: 'Agent "deleted-agent" no longer exists in configuration',
},
};
await expect(
resolveSessionKeyFromResolveParams({
cfg,
p: { key: acpBindingKey },
}),
).resolves.toEqual(expected);
await expect(
resolveSessionKeyFromResolveParams({
cfg,
p: { sessionId: "sess-acp-binding-deleted" },
}),
).resolves.toEqual(expected);
await expect(
resolveSessionKeyFromResolveParams({
cfg,
p: { label: "deleted-binding" },
}),
).resolves.toEqual(expected);
});
});
it("rejects an explicit listed deleted main key instead of remapping to the live default main", async () => {
await withStateDirEnv("openclaw-sessions-resolve-key-deleted-main-", async () => {
const cfg: OpenClawConfig = {