


























@@ -475,7 +475,14 @@ describe("AcpxRuntime fresh reset wrapper", () => {
475475expect(setConfigOption).not.toHaveBeenCalled();
476476});
477477478-it("forwards timeout config controls for non-Codex ACP agents", async () => {
478+it("ignores unsupported claude-agent-acp timeout config controls", async () => {
479+// Regression for openclaw/openclaw#81127: claude-agent-acp rejects any
480+// `session/set_config_option` whose configId it does not advertise
481+// (`Unknown config option: timeout`), which previously surfaced to callers
482+// as ACP_TURN_FAILED whenever `sessions_spawn` was invoked with
483+// `timeoutSeconds`. OpenClaw still enforces the per-turn timeout
484+// in-process via runTimeoutSeconds; the wire option just must not leave
485+// the wrapper.
479486const baseStore: TestSessionStore = {
480487load: vi.fn(async () => ({
481488acpxRecordId: "agent:claude:acp:test",
@@ -497,15 +504,76 @@ describe("AcpxRuntime fresh reset wrapper", () => {
497504key: "timeout",
498505value: "60",
499506});
507+await runtime.setConfigOption({
508+ handle,
509+key: "Timeout_Seconds",
510+value: "60",
511+});
512+513+expect(setConfigOption).not.toHaveBeenCalled();
514+});
515+516+it("still forwards non-timeout config controls for claude-agent-acp", async () => {
517+const baseStore: TestSessionStore = {
518+load: vi.fn(async () => ({
519+acpxRecordId: "agent:claude:acp:test",
520+agentCommand: "npx @agentclientprotocol/claude-agent-acp",
521+})),
522+save: vi.fn(async () => {}),
523+};
524+const { runtime, delegate } = makeRuntime(baseStore);
525+const setConfigOption = vi.spyOn(delegate, "setConfigOption").mockResolvedValue(undefined);
526+const handle: Parameters<NonNullable<AcpRuntime["setConfigOption"]>>[0]["handle"] = {
527+sessionKey: "agent:claude:acp:test",
528+backend: "acpx",
529+runtimeSessionName: "agent:claude:acp:test",
530+acpxRecordId: "agent:claude:acp:test",
531+};
532+533+await runtime.setConfigOption({
534+ handle,
535+key: "model",
536+value: "claude-sonnet-4.6",
537+});
500538501539expect(setConfigOption).toHaveBeenCalledOnce();
502540expect(setConfigOption).toHaveBeenCalledWith({
503541 handle,
504-key: "timeout",
505-value: "60",
542+key: "model",
543+value: "claude-sonnet-4.6",
506544});
507545});
508546547+it("recognizes claude-agent-acp commands", () => {
548+expect(__testing.isClaudeAcpCommand("npx @agentclientprotocol/claude-agent-acp")).toBe(true);
549+expect(
550+__testing.isClaudeAcpCommand("npx -y @agentclientprotocol/claude-agent-acp@0.33.1"),
551+).toBe(true);
552+expect(__testing.isClaudeAcpCommand("claude-agent-acp")).toBe(true);
553+expect(__testing.isClaudeAcpCommand("claude-agent-acp.exe")).toBe(true);
554+expect(
555+__testing.isClaudeAcpCommand(`node "/tmp/openclaw/acpx/claude-agent-acp-wrapper.mjs"`),
556+).toBe(true);
557+// Generated ACP wrapper commands are built from `process.execPath`, which
558+// is `node.exe` on Windows. The detector must accept that launcher so the
559+// bundled Claude ACP wrapper path does not slip past the timeout
560+// suppression on Windows hosts. Generated commands use forward-slash
561+// paths (Node accepts those on Windows) — matching the same pattern as
562+// the existing CODEX_ACP_WRAPPER_COMMAND fixture.
563+expect(
564+__testing.isClaudeAcpCommand(
565+`node.exe "C:/Users/runner/AppData/Local/Temp/openclaw/acpx/claude-agent-acp-wrapper.mjs"`,
566+),
567+).toBe(true);
568+expect(
569+__testing.isClaudeAcpCommand(
570+`Node.EXE "C:/Users/runner/AppData/Local/Temp/openclaw/acpx/claude-agent-acp-wrapper.mjs"`,
571+),
572+).toBe(true);
573+expect(__testing.isClaudeAcpCommand("openclaw acp")).toBe(false);
574+expect(__testing.isClaudeAcpCommand("npx @zed-industries/codex-acp")).toBe(false);
575+});
576+509577it("keeps stale persistent loads hidden until a fresh record is saved", async () => {
510578const baseStore: TestSessionStore = {
511579load: vi.fn(async () => ({ acpxRecordId: "stale" }) as never),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。