From a93bc61a845eb44a31d51b4e7b3dad2e2aac5178 Mon Sep 17 00:00:00 2001 From: Shakker Date: Tue, 9 Jun 2026 18:50:55 +0100 Subject: [PATCH] fix: read canonical ACP metadata for deleted-agent guard --- src/gateway/session-utils.test.ts | 2 +- src/gateway/session-utils.ts | 18 +++++++++++------- src/gateway/sessions-resolve-store.test.ts | 21 +++++++++++++-------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/gateway/session-utils.test.ts b/src/gateway/session-utils.test.ts index d61bc5b34060..ac6fa8af3f86 100644 --- a/src/gateway/session-utils.test.ts +++ b/src/gateway/session-utils.test.ts @@ -842,7 +842,7 @@ describe("gateway session utils", () => { state: "idle", lastActivityAt: 1, }, - }) as Pick; + }) as SessionEntry; const claudeKey = "agent:claude:acp:11111111-1111-4111-8111-111111111111"; const cursorKey = "agent:cursor:acp:22222222-2222-4222-8222-222222222222"; expect( diff --git a/src/gateway/session-utils.ts b/src/gateway/session-utils.ts index 5acbfc5ec8dc..4dbd5bab5075 100644 --- a/src/gateway/session-utils.ts +++ b/src/gateway/session-utils.ts @@ -9,7 +9,7 @@ import { } from "@openclaw/normalization-core/string-coerce"; import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import type { SessionsListParams } from "../../packages/gateway-protocol/src/index.js"; -import { readAcpSessionMeta } from "../acp/runtime/session-meta.js"; +import { readAcpSessionMeta, readAcpSessionMetaForEntry } from "../acp/runtime/session-meta.js"; import { resolveModelAgentRuntimeMetadata } from "../agents/agent-runtime-metadata.js"; import { listAgentIds, @@ -922,17 +922,21 @@ function resolveTranscriptUsageFallback(params: { export function resolveDeletedAgentIdFromSessionKey( cfg: OpenClawConfig, sessionKey: string, - entry?: Pick | null, + entry?: SessionEntry | null, ): string | null { const parsed = parseAgentSessionKey(sessionKey); if (!parsed) { return null; } - // Free ACP runtime keys use agent::acp:, but key shape is - // not proof: ACP bridge sessions can use ACP-shaped keys without SessionAcpMeta. - // Configured acp:binding keys stay owner-scoped even when ACP metadata exists. - if (isAcpSessionKey(sessionKey) && !parsed.rest.startsWith("acp:binding:") && entry?.acp) { - return null; + if (isAcpSessionKey(sessionKey) && !parsed.rest.startsWith("acp:binding:")) { + // Free ACP runtime keys use agent::acp:, but key shape is + // not proof: ACP bridge sessions can use ACP-shaped keys without SessionAcpMeta. + // Configured acp:binding keys stay owner-scoped even when ACP metadata exists. + const acpMeta = + entry?.acp ?? readAcpSessionMetaForEntry({ sessionKey, entry: entry ?? undefined }); + if (acpMeta) { + return null; + } } const agentId = normalizeAgentId(parsed.agentId); if (listAgentIds(cfg).includes(agentId)) { diff --git a/src/gateway/sessions-resolve-store.test.ts b/src/gateway/sessions-resolve-store.test.ts index 50cc74e4fc85..ca8f878e97b8 100644 --- a/src/gateway/sessions-resolve-store.test.ts +++ b/src/gateway/sessions-resolve-store.test.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { ErrorCodes } from "../../packages/gateway-protocol/src/index.js"; +import { writeAcpSessionMetaForMigration } from "../acp/runtime/session-meta.js"; import { resolveStorePath, saveSessionStore } from "../config/sessions.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { withStateDirEnv } from "../test-helpers/state-dir-env.js"; @@ -222,14 +223,18 @@ describe("resolveSessionKeyFromResolveParams store canonicalization", () => { sessionId: "sess-acp-harness", label: "claude-delegate", updatedAt: freshUpdatedAt(), - acp: { - backend: "acpx", - agent: "claude", - runtimeSessionName: acpKey, - mode: "oneshot", - state: "idle", - lastActivityAt: freshUpdatedAt(), - }, + }, + }); + writeAcpSessionMetaForMigration({ + sessionKey: acpKey, + sessionId: "sess-acp-harness", + meta: { + backend: "acpx", + agent: "claude", + runtimeSessionName: acpKey, + mode: "oneshot", + state: "idle", + lastActivityAt: freshUpdatedAt(), }, });