mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix: code/cli acpx reliability 20260304 (#34020)
* agents: switch claude-cli defaults to bypassPermissions * agents: add claude-cli default args coverage * agents: emit watchdog stall system event for cli runs * agents: test cli watchdog stall system event * acpx: fallback to sessions new when ensure returns no ids * acpx tests: mock sessions new fallback path * acpx tests: cover ensure-empty fallback flow * skills: clarify claude print mode without pty * docs: update cli-backends claude default args * docs: refresh cli live test default args * gateway tests: align live claude args defaults * changelog: credit claude/acpx reliability fixes * Agents: normalize legacy Claude permission flag overrides * Tests: cover legacy Claude permission override normalization * Changelog: note legacy Claude permission flag auto-normalization * ACPX: fail fast when ensure/new return no session IDs * ACPX tests: support empty sessions new fixture output * ACPX tests: assert ensureSession failure when IDs missing * CLI runner: scope watchdog heartbeat wake to session * CLI runner tests: assert session-scoped watchdog wake * Update CHANGELOG.md
This commit is contained in:
@@ -75,14 +75,35 @@ const setValue = command === "set" ? String(args[commandIndex + 2] || "") : "";
|
||||
|
||||
if (command === "sessions" && args[commandIndex + 1] === "ensure") {
|
||||
writeLog({ kind: "ensure", agent, args, sessionName: ensureName });
|
||||
emitJson({
|
||||
action: "session_ensured",
|
||||
acpxRecordId: "rec-" + ensureName,
|
||||
acpxSessionId: "sid-" + ensureName,
|
||||
agentSessionId: "inner-" + ensureName,
|
||||
name: ensureName,
|
||||
created: true,
|
||||
});
|
||||
if (process.env.MOCK_ACPX_ENSURE_EMPTY === "1") {
|
||||
emitJson({ action: "session_ensured", name: ensureName });
|
||||
} else {
|
||||
emitJson({
|
||||
action: "session_ensured",
|
||||
acpxRecordId: "rec-" + ensureName,
|
||||
acpxSessionId: "sid-" + ensureName,
|
||||
agentSessionId: "inner-" + ensureName,
|
||||
name: ensureName,
|
||||
created: true,
|
||||
});
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (command === "sessions" && args[commandIndex + 1] === "new") {
|
||||
writeLog({ kind: "new", agent, args, sessionName: ensureName });
|
||||
if (process.env.MOCK_ACPX_NEW_EMPTY === "1") {
|
||||
emitJson({ action: "session_created", name: ensureName });
|
||||
} else {
|
||||
emitJson({
|
||||
action: "session_created",
|
||||
acpxRecordId: "rec-" + ensureName,
|
||||
acpxSessionId: "sid-" + ensureName,
|
||||
agentSessionId: "inner-" + ensureName,
|
||||
name: ensureName,
|
||||
created: true,
|
||||
});
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -377,4 +377,51 @@ describe("AcpxRuntime", () => {
|
||||
expect(report.code).toBe("ACP_BACKEND_UNAVAILABLE");
|
||||
expect(report.installCommand).toContain("acpx");
|
||||
});
|
||||
|
||||
it("falls back to 'sessions new' when 'sessions ensure' returns no session IDs", async () => {
|
||||
process.env.MOCK_ACPX_ENSURE_EMPTY = "1";
|
||||
try {
|
||||
const { runtime, logPath } = await createMockRuntimeFixture();
|
||||
const handle = await runtime.ensureSession({
|
||||
sessionKey: "agent:claude:acp:fallback-test",
|
||||
agent: "claude",
|
||||
mode: "persistent",
|
||||
});
|
||||
expect(handle.backend).toBe("acpx");
|
||||
expect(handle.acpxRecordId).toBe("rec-agent:claude:acp:fallback-test");
|
||||
expect(handle.agentSessionId).toBe("inner-agent:claude:acp:fallback-test");
|
||||
|
||||
const logs = await readMockRuntimeLogEntries(logPath);
|
||||
expect(logs.some((entry) => entry.kind === "ensure")).toBe(true);
|
||||
expect(logs.some((entry) => entry.kind === "new")).toBe(true);
|
||||
} finally {
|
||||
delete process.env.MOCK_ACPX_ENSURE_EMPTY;
|
||||
}
|
||||
});
|
||||
|
||||
it("fails with ACP_SESSION_INIT_FAILED when both ensure and new omit session IDs", async () => {
|
||||
process.env.MOCK_ACPX_ENSURE_EMPTY = "1";
|
||||
process.env.MOCK_ACPX_NEW_EMPTY = "1";
|
||||
try {
|
||||
const { runtime, logPath } = await createMockRuntimeFixture();
|
||||
|
||||
await expect(
|
||||
runtime.ensureSession({
|
||||
sessionKey: "agent:claude:acp:fallback-fail",
|
||||
agent: "claude",
|
||||
mode: "persistent",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "ACP_SESSION_INIT_FAILED",
|
||||
message: expect.stringContaining("neither 'sessions ensure' nor 'sessions new'"),
|
||||
});
|
||||
|
||||
const logs = await readMockRuntimeLogEntries(logPath);
|
||||
expect(logs.some((entry) => entry.kind === "ensure")).toBe(true);
|
||||
expect(logs.some((entry) => entry.kind === "new")).toBe(true);
|
||||
} finally {
|
||||
delete process.env.MOCK_ACPX_ENSURE_EMPTY;
|
||||
delete process.env.MOCK_ACPX_NEW_EMPTY;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -179,7 +179,7 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
const cwd = asTrimmedString(input.cwd) || this.config.cwd;
|
||||
const mode = input.mode;
|
||||
|
||||
const events = await this.runControlCommand({
|
||||
let events = await this.runControlCommand({
|
||||
args: this.buildControlArgs({
|
||||
cwd,
|
||||
command: [agent, "sessions", "ensure", "--name", sessionName],
|
||||
@@ -187,12 +187,36 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
cwd,
|
||||
fallbackCode: "ACP_SESSION_INIT_FAILED",
|
||||
});
|
||||
const ensuredEvent = events.find(
|
||||
let ensuredEvent = events.find(
|
||||
(event) =>
|
||||
asOptionalString(event.agentSessionId) ||
|
||||
asOptionalString(event.acpxSessionId) ||
|
||||
asOptionalString(event.acpxRecordId),
|
||||
);
|
||||
|
||||
if (!ensuredEvent) {
|
||||
events = await this.runControlCommand({
|
||||
args: this.buildControlArgs({
|
||||
cwd,
|
||||
command: [agent, "sessions", "new", "--name", sessionName],
|
||||
}),
|
||||
cwd,
|
||||
fallbackCode: "ACP_SESSION_INIT_FAILED",
|
||||
});
|
||||
ensuredEvent = events.find(
|
||||
(event) =>
|
||||
asOptionalString(event.agentSessionId) ||
|
||||
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}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const acpxRecordId = ensuredEvent ? asOptionalString(ensuredEvent.acpxRecordId) : undefined;
|
||||
const agentSessionId = ensuredEvent ? asOptionalString(ensuredEvent.agentSessionId) : undefined;
|
||||
const backendSessionId = ensuredEvent
|
||||
|
||||
Reference in New Issue
Block a user