From 425bd89b48dd4c01966b1633943717b2d9896a7f Mon Sep 17 00:00:00 2001 From: xaeon2026 Date: Mon, 9 Mar 2026 12:08:11 -0400 Subject: [PATCH] Allow ACP sessions.patch lineage fields on ACP session keys (#40995) Merged via squash. Prepared head SHA: c1191edc08618dec1826c57b75556c4e35ccccaf Co-authored-by: xaeon2026 <264572156+xaeon2026@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky --- CHANGELOG.md | 1 + src/gateway/sessions-patch.test.ts | 23 +++++++++++++++++++++++ src/gateway/sessions-patch.ts | 13 +++++++++---- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa48053c..25398c660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai - Models/Kimi Coding: send `anthropic-messages` tools in native Anthropic format again so `kimi-coding` stops degrading tool calls into XML/plain-text pseudo invocations instead of real `tool_use` blocks. (#38669, #39907, #40552) Thanks @opriz. - Context engine/tests: add bundled-registry regression coverage for cross-chunk resolution, plugin-sdk re-exports, and concurrent chunk registration. (#40460) thanks @dsantoreis. - Agents/embedded runner: bound compaction retry waiting and drain embedded runs during SIGUSR1 restart so session lanes recover instead of staying blocked behind compaction. (#40324) thanks @cgdusek. +- ACP/sessions.patch: allow `spawnedBy` and `spawnDepth` lineage fields on ACP session keys so `sessions_spawn` with `runtime: "acp"` no longer fails during child-session setup. Fixes #40971. (#40995) thanks @xaeon2026. ## 2026.3.8 diff --git a/src/gateway/sessions-patch.test.ts b/src/gateway/sessions-patch.test.ts index 78d8a71ae..2249c7f5c 100644 --- a/src/gateway/sessions-patch.test.ts +++ b/src/gateway/sessions-patch.test.ts @@ -252,6 +252,29 @@ describe("gateway sessions patch", () => { expect(entry.spawnDepth).toBe(2); }); + test("sets spawnedBy for ACP sessions", async () => { + const entry = expectPatchOk( + await runPatch({ + storeKey: "agent:main:acp:child", + patch: { + key: "agent:main:acp:child", + spawnedBy: "agent:main:main", + }, + }), + ); + expect(entry.spawnedBy).toBe("agent:main:main"); + }); + + test("sets spawnDepth for ACP sessions", async () => { + const entry = expectPatchOk( + await runPatch({ + storeKey: "agent:main:acp:child", + patch: { key: "agent:main:acp:child", spawnDepth: 2 }, + }), + ); + expect(entry.spawnDepth).toBe(2); + }); + test("rejects spawnDepth on non-subagent sessions", async () => { const result = await runPatch({ patch: { key: MAIN_SESSION_KEY, spawnDepth: 1 }, diff --git a/src/gateway/sessions-patch.ts b/src/gateway/sessions-patch.ts index d55cf2cf1..b4e5ce6e0 100644 --- a/src/gateway/sessions-patch.ts +++ b/src/gateway/sessions-patch.ts @@ -19,6 +19,7 @@ import { import type { OpenClawConfig } from "../config/config.js"; import type { SessionEntry } from "../config/sessions.js"; import { + isAcpSessionKey, isSubagentSessionKey, normalizeAgentId, parseAgentSessionKey, @@ -62,6 +63,10 @@ function normalizeExecAsk(raw: string): "off" | "on-miss" | "always" | undefined return undefined; } +function supportsSpawnLineage(storeKey: string): boolean { + return isSubagentSessionKey(storeKey) || isAcpSessionKey(storeKey); +} + export async function applySessionsPatchToStore(params: { cfg: OpenClawConfig; store: Record; @@ -97,8 +102,8 @@ export async function applySessionsPatchToStore(params: { if (!trimmed) { return invalid("invalid spawnedBy: empty"); } - if (!isSubagentSessionKey(storeKey)) { - return invalid("spawnedBy is only supported for subagent:* sessions"); + if (!supportsSpawnLineage(storeKey)) { + return invalid("spawnedBy is only supported for subagent:* or acp:* sessions"); } if (existing?.spawnedBy && existing.spawnedBy !== trimmed) { return invalid("spawnedBy cannot be changed once set"); @@ -114,8 +119,8 @@ export async function applySessionsPatchToStore(params: { return invalid("spawnDepth cannot be cleared once set"); } } else if (raw !== undefined) { - if (!isSubagentSessionKey(storeKey)) { - return invalid("spawnDepth is only supported for subagent:* sessions"); + if (!supportsSpawnLineage(storeKey)) { + return invalid("spawnDepth is only supported for subagent:* or acp:* sessions"); } const numeric = Number(raw); if (!Number.isInteger(numeric) || numeric < 0) {