fix: preserve ACP metadata key during deleted-agent checks

This commit is contained in:
Shakker
2026-06-09 18:57:57 +01:00
committed by Shakker
parent a82abc771a
commit 784e86433c
5 changed files with 58 additions and 6 deletions
+4 -2
View File
@@ -3068,7 +3068,7 @@ export const chatHandlers: GatewayRequestHandlers = {
},
);
const sessionLoadMs = roundedChatSendTimingMs(performance.now() - sessionLoadStartedAtMs);
const { cfg, entry, canonicalKey: sessionKey } = sessionLoadResult;
const { cfg, entry, canonicalKey: sessionKey, legacyKey } = sessionLoadResult;
const selectedAgent = validateChatSelectedAgent({
cfg,
requestedSessionKey: rawSessionKey,
@@ -3080,7 +3080,9 @@ export const chatHandlers: GatewayRequestHandlers = {
}
const requestedSessionId = normalizeOptionalText(p.sessionId);
const backingSessionId = entry?.sessionId ?? requestedSessionId;
const deletedAgentId = resolveDeletedAgentIdFromSessionKey(cfg, sessionKey, entry);
const deletedAgentId = resolveDeletedAgentIdFromSessionKey(cfg, sessionKey, entry, {
acpMetadataSessionKey: legacyKey ?? sessionKey,
});
if (deletedAgentId !== null) {
respond(
false,
+4 -2
View File
@@ -785,9 +785,11 @@ async function handleSessionSend(params: {
}
const requestedAgentId = requestedAgent.agentId;
const loaded = loadSessionEntry(key, { agentId: requestedAgentId });
let { entry, canonicalKey, storePath } = loaded;
let { entry, canonicalKey, storePath, legacyKey } = loaded;
// Reject sends/steers targeting sessions whose owning agent was deleted (#65524).
const deletedAgentId = resolveDeletedAgentIdFromSessionKey(cfg, canonicalKey, entry);
const deletedAgentId = resolveDeletedAgentIdFromSessionKey(cfg, canonicalKey, entry, {
acpMetadataSessionKey: legacyKey ?? canonicalKey,
});
if (deletedAgentId !== null) {
params.respond(
false,
+9 -1
View File
@@ -923,6 +923,7 @@ export function resolveDeletedAgentIdFromSessionKey(
cfg: OpenClawConfig,
sessionKey: string,
entry?: SessionEntry | null,
options?: { acpMetadataSessionKey?: string | null },
): string | null {
const parsed = parseAgentSessionKey(sessionKey);
if (!parsed) {
@@ -932,8 +933,15 @@ export function resolveDeletedAgentIdFromSessionKey(
// Free ACP runtime keys use agent:<harnessId>:acp:<uuid>, 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 acpMetadataSessionKey = normalizeOptionalString(options?.acpMetadataSessionKey);
const acpMeta =
entry?.acp ?? readAcpSessionMetaForEntry({ sessionKey, entry: entry ?? undefined });
entry?.acp ??
(acpMetadataSessionKey
? readAcpSessionMetaForEntry({
sessionKey: acpMetadataSessionKey,
entry: entry ?? undefined,
})
: readAcpSessionMeta({ sessionKey, cfg }));
if (acpMeta) {
return null;
}
@@ -261,6 +261,43 @@ describe("resolveSessionKeyFromResolveParams store canonicalization", () => {
});
});
it("resolves migrated ACP harness keys when metadata remains under the matched store key", async () => {
await withStateDirEnv("openclaw-sessions-resolve-acp-harness-legacy-", async () => {
const cfg: OpenClawConfig = {
agents: { list: [{ id: "main", default: true }] },
};
const acpKey = "agent:claude:acp:33333333-3333-4333-8333-333333333333";
const legacyAcpKey = "agent:CLAUDE:acp:33333333-3333-4333-8333-333333333333";
const claudeStorePath = resolveStorePath(cfg.session?.store, { agentId: "claude" });
await saveSessionStore(claudeStorePath, {
[legacyAcpKey]: {
sessionId: "sess-acp-harness-legacy",
label: "claude-delegate-legacy",
updatedAt: freshUpdatedAt(),
},
});
writeAcpSessionMetaForMigration({
sessionKey: legacyAcpKey,
sessionId: "sess-acp-harness-legacy",
meta: {
backend: "acpx",
agent: "claude",
runtimeSessionName: legacyAcpKey,
mode: "oneshot",
state: "idle",
lastActivityAt: freshUpdatedAt(),
},
});
await expect(
resolveSessionKeyFromResolveParams({
cfg,
p: { key: acpKey },
}),
).resolves.toEqual({ ok: true, key: acpKey });
});
});
it("rejects ACP-shaped bridge sessions without ACP runtime metadata under deleted agents", async () => {
await withStateDirEnv("openclaw-sessions-resolve-acp-bridge-deleted-", async () => {
const cfg: OpenClawConfig = {
+4 -1
View File
@@ -43,8 +43,9 @@ function validateSessionAgentExists(
cfg: OpenClawConfig,
key: string,
entry?: SessionEntry | null,
options?: { acpMetadataSessionKey?: string | null },
): SessionsResolveResult | null {
const deletedAgentId = resolveDeletedAgentIdFromSessionKey(cfg, key, entry);
const deletedAgentId = resolveDeletedAgentIdFromSessionKey(cfg, key, entry, options);
if (deletedAgentId === null) {
return null;
}
@@ -142,6 +143,7 @@ export async function resolveSessionKeyFromResolveParams(params: {
cfg,
target.canonicalKey,
store[target.canonicalKey],
{ acpMetadataSessionKey: target.canonicalKey },
);
if (agentCheck) {
return agentCheck;
@@ -174,6 +176,7 @@ export async function resolveSessionKeyFromResolveParams(params: {
cfg,
target.canonicalKey,
migratedStore[target.canonicalKey],
{ acpMetadataSessionKey: legacyKey },
);
if (agentCheckLegacy) {
return agentCheckLegacy;