From d06138ac3de897384801ac683ba9de56cf23ff50 Mon Sep 17 00:00:00 2001 From: Mariano Belinky Date: Mon, 9 Mar 2026 21:32:55 +0100 Subject: [PATCH] acp: add regression coverage and smoke-test docs --- docs/tools/acp-agents.md | 40 +++++++++++++++++++ extensions/acpx/src/runtime.test.ts | 34 ++++++++++++++++ ...sessions.gateway-server-sessions-a.test.ts | 12 ++++++ 3 files changed, 86 insertions(+) diff --git a/docs/tools/acp-agents.md b/docs/tools/acp-agents.md index 74ed73248f1..e41a96248ae 100644 --- a/docs/tools/acp-agents.md +++ b/docs/tools/acp-agents.md @@ -246,6 +246,46 @@ Interface details: - `streamTo` (optional): `"parent"` streams initial ACP run progress summaries back to the requester session as system events. - When available, accepted responses include `streamLogPath` pointing to a session-scoped JSONL log (`.acp-stream.jsonl`) you can tail for full relay history. +### Operator smoke test + +Use this after a gateway deploy when you want a quick live check that ACP spawn +is actually working end-to-end, not just passing unit tests. + +Recommended gate: + +1. Verify the deployed gateway version/commit on the target host. +2. Confirm the deployed source includes the ACP lineage acceptance in + `src/gateway/sessions-patch.ts` (`subagent:* or acp:* sessions`). +3. Open a temporary ACPX bridge session to a live agent (for example + `razor(main)` on `jpclawhq`). +4. Ask that agent to call `sessions_spawn` with: + - `runtime: "acp"` + - `agentId: "codex"` + - `mode: "run"` + - task: `Reply with exactly LIVE-ACP-SPAWN-OK` +5. Verify the agent reports: + - `accepted=yes` + - a real `childSessionKey` + - no validator error +6. Clean up the temporary ACPX bridge session. + +Example prompt to the live agent: + +```text +Use the sessions_spawn tool now with runtime: "acp", agentId: "codex", and mode: "run". +Set the task to: "Reply with exactly LIVE-ACP-SPAWN-OK". +Then report only: accepted=; childSessionKey=; error=. +``` + +Notes: + +- Keep this smoke test on `mode: "run"` unless you are intentionally testing + thread-bound persistent ACP sessions. +- Do not require `streamTo: "parent"` for the basic gate. That path depends on + requester/session capabilities and is a separate integration check. +- Treat thread-bound `mode: "session"` testing as a second, richer integration + pass from a real Discord thread or Telegram topic. + ## Sandbox compatibility ACP sessions currently run on the host runtime, not inside the OpenClaw sandbox. diff --git a/extensions/acpx/src/runtime.test.ts b/extensions/acpx/src/runtime.test.ts index bb3b94cec9e..79cd4af3231 100644 --- a/extensions/acpx/src/runtime.test.ts +++ b/extensions/acpx/src/runtime.test.ts @@ -127,6 +127,40 @@ describe("AcpxRuntime", () => { expect(promptArgs).toContain("--approve-all"); }); + it("serializes text plus image attachments into ACP prompt blocks", async () => { + const { runtime, logPath } = await createMockRuntimeFixture(); + + const handle = await runtime.ensureSession({ + sessionKey: "agent:codex:acp:with-image", + agent: "codex", + mode: "persistent", + }); + + for await (const _event of runtime.runTurn({ + handle, + text: "describe this image", + attachments: [{ mediaType: "image/png", data: "aW1hZ2UtYnl0ZXM=" }], + mode: "prompt", + requestId: "req-image", + })) { + // Consume stream to completion so prompt logging is finalized. + } + + const logs = await readMockRuntimeLogEntries(logPath); + const prompt = logs.find( + (entry) => + entry.kind === "prompt" && + String(entry.sessionName ?? "") === "agent:codex:acp:with-image", + ); + expect(prompt).toBeDefined(); + + const stdinBlocks = JSON.parse(String(prompt?.stdinText ?? "")); + expect(stdinBlocks).toEqual([ + { type: "text", text: "describe this image" }, + { type: "image", mimeType: "image/png", data: "aW1hZ2UtYnl0ZXM=" }, + ]); + }); + it("preserves leading spaces across streamed text deltas", async () => { const runtime = sharedFixture?.runtime; expect(runtime).toBeDefined(); diff --git a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts index 3837247c9bc..f986d49c648 100644 --- a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts +++ b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts @@ -463,6 +463,18 @@ describe("gateway server sessions", () => { expect(spawnedPatched.ok).toBe(true); expect(spawnedPatched.payload?.entry.spawnedBy).toBe("agent:main:main"); + const acpPatched = await rpcReq<{ + ok: true; + entry: { spawnedBy?: string; spawnDepth?: number }; + }>(ws, "sessions.patch", { + key: "agent:main:acp:child", + spawnedBy: "agent:main:main", + spawnDepth: 1, + }); + expect(acpPatched.ok).toBe(true); + expect(acpPatched.payload?.entry.spawnedBy).toBe("agent:main:main"); + expect(acpPatched.payload?.entry.spawnDepth).toBe(1); + const spawnedPatchedInvalidKey = await rpcReq(ws, "sessions.patch", { key: "agent:main:main", spawnedBy: "agent:main:main",