feat(acp): add resumeSessionId to sessions_spawn for ACP session resume (#41847)

* feat(acp): add resumeSessionId to sessions_spawn for ACP session resume

Thread resumeSessionId through the ACP session spawn pipeline so agents
can resume existing sessions (e.g. a prior Codex conversation) instead
of starting fresh.

Flow: sessions_spawn tool → spawnAcpDirect → initializeSession →
ensureSession → acpx --resume-session flag → agent session/load

- Add resumeSessionId param to sessions-spawn-tool schema with
  description so agents can discover and use it
- Thread through SpawnAcpParams → AcpInitializeSessionInput →
  AcpRuntimeEnsureInput → acpx extension runtime
- Pass as --resume-session flag to acpx CLI
- Error hard (exit 4) on non-existent session, no silent fallback
- All new fields optional for backward compatibility

Depends on acpx >= 0.1.16 (openclaw/acpx#85, merged, pending release).

Tests: 26/26 pass (runtime + tool schema)
Verified e2e: Discord → sessions_spawn(resumeSessionId) → Codex
resumed session and recalled stored secret.

🤖 AI-assisted

* fix: guard resumeSessionId against non-ACP runtime

Add early-return error when resumeSessionId is passed without
runtime="acp" (mirrors existing streamTo guard). Without this,
the parameter is silently ignored and the agent gets a fresh
session instead of resuming.

Also update schema description to note the runtime=acp requirement.

Addresses Greptile review feedback.

* ACP: add changelog entry for session resume (#41847) (thanks @pejmanjohn)

---------

Co-authored-by: Pejman Pour-Moezzi <481729+pejmanjohn@users.noreply.github.com>
Co-authored-by: Onur <onur@textcortex.com>
This commit is contained in:
Pejman Pour-Moezzi
2026-03-10 02:36:13 -07:00
committed by GitHub
parent c2eb12bbc5
commit aca216bfcf
9 changed files with 98 additions and 8 deletions

View File

@@ -127,6 +127,32 @@ describe("AcpxRuntime", () => {
expect(promptArgs).toContain("--approve-all");
});
it("uses sessions new with --resume-session when resumeSessionId is provided", async () => {
const { runtime, logPath } = await createMockRuntimeFixture();
const resumeSessionId = "sid-resume-123";
const sessionKey = "agent:codex:acp:resume";
const handle = await runtime.ensureSession({
sessionKey,
agent: "codex",
mode: "persistent",
resumeSessionId,
});
expect(handle.backend).toBe("acpx");
expect(handle.acpxRecordId).toBe("rec-" + sessionKey);
const logs = await readMockRuntimeLogEntries(logPath);
expect(logs.some((entry) => entry.kind === "ensure")).toBe(false);
const resumeEntry = logs.find(
(entry) => entry.kind === "new" && String(entry.sessionName ?? "") === sessionKey,
);
expect(resumeEntry).toBeDefined();
const resumeArgs = (resumeEntry?.args as string[]) ?? [];
const resumeFlagIndex = resumeArgs.indexOf("--resume-session");
expect(resumeFlagIndex).toBeGreaterThanOrEqual(0);
expect(resumeArgs[resumeFlagIndex + 1]).toBe(resumeSessionId);
});
it("serializes text plus image attachments into ACP prompt blocks", async () => {
const { runtime, logPath } = await createMockRuntimeFixture();

View File

@@ -203,10 +203,14 @@ export class AcpxRuntime implements AcpRuntime {
}
const cwd = asTrimmedString(input.cwd) || this.config.cwd;
const mode = input.mode;
const resumeSessionId = asTrimmedString(input.resumeSessionId);
const ensureSubcommand = resumeSessionId
? ["sessions", "new", "--name", sessionName, "--resume-session", resumeSessionId]
: ["sessions", "ensure", "--name", sessionName];
const ensureCommand = await this.buildVerbArgs({
agent,
cwd,
command: ["sessions", "ensure", "--name", sessionName],
command: ensureSubcommand,
});
let events = await this.runControlCommand({
@@ -221,7 +225,7 @@ export class AcpxRuntime implements AcpRuntime {
asOptionalString(event.acpxRecordId),
);
if (!ensuredEvent) {
if (!ensuredEvent && !resumeSessionId) {
const newCommand = await this.buildVerbArgs({
agent,
cwd,
@@ -238,12 +242,14 @@ export class AcpxRuntime implements AcpRuntime {
asOptionalString(event.acpxSessionId) ||
asOptionalString(event.acpxRecordId),
);
if (!ensuredEvent) {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
`ACP session init failed: neither 'sessions ensure' nor 'sessions new' returned valid session identifiers for ${sessionName}.`,
);
}
}
if (!ensuredEvent) {
throw new AcpRuntimeError(
"ACP_SESSION_INIT_FAILED",
resumeSessionId
? `ACP session init failed: 'sessions new --resume-session' returned no session identifiers for ${sessionName}.`
: `ACP session init failed: neither 'sessions ensure' nor 'sessions new' returned valid session identifiers for ${sessionName}.`,
);
}
const acpxRecordId = ensuredEvent ? asOptionalString(ensuredEvent.acpxRecordId) : undefined;