Skip to content

Commit 52154ed

Browse files
scotthuangshakkernerd
authored andcommitted
fix: preserve configured ACP deleted-agent guard
1 parent 3853eb1 commit 52154ed

3 files changed

Lines changed: 66 additions & 4 deletions

File tree

src/gateway/session-utils.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,22 @@ describe("gateway session utils", () => {
846846
).toBeNull();
847847
});
848848

849+
test("resolveDeletedAgentIdFromSessionKey rejects deleted configured ACP binding owners", () => {
850+
const cfg = {
851+
agents: { list: [{ id: "main", default: true }] },
852+
} as OpenClawConfig;
853+
854+
expect(
855+
resolveDeletedAgentIdFromSessionKey(
856+
cfg,
857+
"agent:deleted-agent:acp:binding:discord:default:feedface",
858+
),
859+
).toBe("deleted-agent");
860+
expect(
861+
resolveDeletedAgentIdFromSessionKey(cfg, "agent:main:acp:binding:discord:default:feedface"),
862+
).toBeNull();
863+
});
864+
849865
test("resolveSessionStoreKey canonicalizes bare keys to default agent", () => {
850866
const cfg = {
851867
session: { mainKey: "main" },

src/gateway/session-utils.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -922,14 +922,15 @@ export function resolveDeletedAgentIdFromSessionKey(
922922
cfg: OpenClawConfig,
923923
sessionKey: string,
924924
): string | null {
925-
// ACP keys use agent:<harnessId>:acp:<uuid>; harness ids are not agents.list entries.
926-
if (isAcpSessionKey(sessionKey)) {
927-
return null;
928-
}
929925
const parsed = parseAgentSessionKey(sessionKey);
930926
if (!parsed) {
931927
return null;
932928
}
929+
// Free ACP spawn keys use agent:<harnessId>:acp:<uuid>, but configured ACP
930+
// bindings use agent:<agentId>:acp:binding:* where agentId remains the owner.
931+
if (isAcpSessionKey(sessionKey) && !parsed.rest.startsWith("acp:binding:")) {
932+
return null;
933+
}
933934
const agentId = normalizeAgentId(parsed.agentId);
934935
if (listAgentIds(cfg).includes(agentId)) {
935936
return null;

src/gateway/sessions-resolve-store.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,51 @@ describe("resolveSessionKeyFromResolveParams store canonicalization", () => {
248248
});
249249
});
250250

251+
it("rejects configured ACP binding sessions when their owning agent is deleted", async () => {
252+
await withStateDirEnv("openclaw-sessions-resolve-acp-binding-deleted-", async () => {
253+
const cfg: OpenClawConfig = {
254+
agents: { list: [{ id: "main", default: true }] },
255+
};
256+
const acpBindingKey = "agent:deleted-agent:acp:binding:discord:default:feedface";
257+
const deletedStorePath = resolveStorePath(cfg.session?.store, { agentId: "deleted-agent" });
258+
await saveSessionStore(deletedStorePath, {
259+
[acpBindingKey]: {
260+
sessionId: "sess-acp-binding-deleted",
261+
label: "deleted-binding",
262+
updatedAt: freshUpdatedAt(),
263+
},
264+
});
265+
const expected = {
266+
ok: false,
267+
error: {
268+
code: ErrorCodes.INVALID_REQUEST,
269+
message: 'Agent "deleted-agent" no longer exists in configuration',
270+
},
271+
};
272+
273+
await expect(
274+
resolveSessionKeyFromResolveParams({
275+
cfg,
276+
p: { key: acpBindingKey },
277+
}),
278+
).resolves.toEqual(expected);
279+
280+
await expect(
281+
resolveSessionKeyFromResolveParams({
282+
cfg,
283+
p: { sessionId: "sess-acp-binding-deleted" },
284+
}),
285+
).resolves.toEqual(expected);
286+
287+
await expect(
288+
resolveSessionKeyFromResolveParams({
289+
cfg,
290+
p: { label: "deleted-binding" },
291+
}),
292+
).resolves.toEqual(expected);
293+
});
294+
});
295+
251296
it("rejects an explicit listed deleted main key instead of remapping to the live default main", async () => {
252297
await withStateDirEnv("openclaw-sessions-resolve-key-deleted-main-", async () => {
253298
const cfg: OpenClawConfig = {

0 commit comments

Comments
 (0)