@rarebit-one/harness-kernel 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +65 -7
  2. package/dist/agent.d.ts +12 -3
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +191 -94
  5. package/dist/agent.js.map +1 -1
  6. package/dist/connectors/mcpClient.d.ts +6 -1
  7. package/dist/connectors/mcpClient.d.ts.map +1 -1
  8. package/dist/connectors/mcpClient.js +1 -1
  9. package/dist/connectors/mcpClient.js.map +1 -1
  10. package/dist/engines/acp.d.ts +121 -0
  11. package/dist/engines/acp.d.ts.map +1 -0
  12. package/dist/engines/acp.js +219 -0
  13. package/dist/engines/acp.js.map +1 -0
  14. package/dist/engines/acpDriver.d.ts +33 -0
  15. package/dist/engines/acpDriver.d.ts.map +1 -0
  16. package/dist/engines/acpDriver.js +150 -0
  17. package/dist/engines/acpDriver.js.map +1 -0
  18. package/dist/engines/claudeCode.d.ts +6 -0
  19. package/dist/engines/claudeCode.d.ts.map +1 -1
  20. package/dist/engines/claudeCode.js +32 -10
  21. package/dist/engines/claudeCode.js.map +1 -1
  22. package/dist/engines/claudeCodeDriver.d.ts +11 -0
  23. package/dist/engines/claudeCodeDriver.d.ts.map +1 -1
  24. package/dist/engines/claudeCodeDriver.js +35 -5
  25. package/dist/engines/claudeCodeDriver.js.map +1 -1
  26. package/dist/engines/codex.d.ts +12 -0
  27. package/dist/engines/codex.d.ts.map +1 -1
  28. package/dist/engines/codex.js +15 -1
  29. package/dist/engines/codex.js.map +1 -1
  30. package/dist/engines/codexDriver.d.ts.map +1 -1
  31. package/dist/engines/codexDriver.js +51 -12
  32. package/dist/engines/codexDriver.js.map +1 -1
  33. package/dist/engines/index.d.ts.map +1 -1
  34. package/dist/engines/index.js +8 -0
  35. package/dist/engines/index.js.map +1 -1
  36. package/dist/engines/native.d.ts.map +1 -1
  37. package/dist/engines/native.js +59 -4
  38. package/dist/engines/native.js.map +1 -1
  39. package/dist/engines/types.d.ts +16 -1
  40. package/dist/engines/types.d.ts.map +1 -1
  41. package/dist/events.d.ts +55 -10
  42. package/dist/events.d.ts.map +1 -1
  43. package/dist/events.js.map +1 -1
  44. package/dist/index.d.ts +5 -5
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/loop.d.ts +21 -2
  49. package/dist/loop.d.ts.map +1 -1
  50. package/dist/loop.js +7 -2
  51. package/dist/loop.js.map +1 -1
  52. package/dist/models/chat.d.ts +6 -2
  53. package/dist/models/chat.d.ts.map +1 -1
  54. package/dist/models/chat.js +15 -9
  55. package/dist/models/chat.js.map +1 -1
  56. package/dist/models/types.d.ts +15 -1
  57. package/dist/models/types.d.ts.map +1 -1
  58. package/dist/models/types.js.map +1 -1
  59. package/dist/primitives/codeExec.d.ts +7 -0
  60. package/dist/primitives/codeExec.d.ts.map +1 -1
  61. package/dist/primitives/codeExec.js +32 -4
  62. package/dist/primitives/codeExec.js.map +1 -1
  63. package/dist/primitives/http.d.ts +6 -0
  64. package/dist/primitives/http.d.ts.map +1 -1
  65. package/dist/primitives/http.js +28 -11
  66. package/dist/primitives/http.js.map +1 -1
  67. package/dist/processGroup.d.ts +16 -0
  68. package/dist/processGroup.d.ts.map +1 -0
  69. package/dist/processGroup.js +39 -0
  70. package/dist/processGroup.js.map +1 -0
  71. package/dist/providers/anthropic.d.ts +12 -2
  72. package/dist/providers/anthropic.d.ts.map +1 -1
  73. package/dist/providers/anthropic.js +30 -3
  74. package/dist/providers/anthropic.js.map +1 -1
  75. package/dist/providers/mock.d.ts +2 -2
  76. package/dist/providers/mock.d.ts.map +1 -1
  77. package/dist/providers/mock.js +5 -1
  78. package/dist/providers/mock.js.map +1 -1
  79. package/dist/providers/openai.d.ts +11 -2
  80. package/dist/providers/openai.d.ts.map +1 -1
  81. package/dist/providers/openai.js +25 -5
  82. package/dist/providers/openai.js.map +1 -1
  83. package/dist/providers/types.d.ts +21 -1
  84. package/dist/providers/types.d.ts.map +1 -1
  85. package/dist/signals.d.ts +46 -0
  86. package/dist/signals.d.ts.map +1 -0
  87. package/dist/signals.js +72 -0
  88. package/dist/signals.js.map +1 -0
  89. package/dist/tools/metadata.d.ts +18 -2
  90. package/dist/tools/metadata.d.ts.map +1 -1
  91. package/dist/tools/metadata.js +7 -0
  92. package/dist/tools/metadata.js.map +1 -1
  93. package/dist/tools/modelTool.d.ts.map +1 -1
  94. package/dist/tools/modelTool.js +7 -3
  95. package/dist/tools/modelTool.js.map +1 -1
  96. package/dist/tools/registry.d.ts +24 -5
  97. package/dist/tools/registry.d.ts.map +1 -1
  98. package/dist/tools/registry.js +43 -12
  99. package/dist/tools/registry.js.map +1 -1
  100. package/package.json +4 -3
@@ -0,0 +1,121 @@
1
+ import type { McpServer } from "@zed-industries/agent-client-protocol";
2
+ import type { RunOutcome } from "../events.js";
3
+ import type { StoppedBy } from "../signals.js";
4
+ import type { AgentEngine, EngineContext, EngineResult, EngineSupport, RunSpec } from "./types.js";
5
+ /**
6
+ * A named external agent this engine can drive over the Agent Client Protocol.
7
+ * ACP is a JSON-RPC-over-stdio protocol between a *client* (this engine) and a
8
+ * coding *agent* running as a separate process, so every ACP agent reduces to
9
+ * "which subprocess to spawn, with what argv and env". The capability MCP server
10
+ * and the run's connectors are handed to the agent through `session/new`, so a
11
+ * spec never has to describe those — only its own launch.
12
+ *
13
+ * External agent product names appearing here follow the existing `claude-code`
14
+ * / `codex` precedent: an adapter's honest identity is the tool it drives, not a
15
+ * consumer's brand.
16
+ */
17
+ export interface AcpAgentSpec {
18
+ /** Engine name; also the `provider.engine` value that selects it. */
19
+ readonly name: string;
20
+ /** Env var that must equal `"1"` for {@link AcpEngine.supports} to allow this agent. */
21
+ readonly enableEnv: string;
22
+ /** The executable to spawn — an ACP server over stdio. */
23
+ readonly command: string;
24
+ /** The process argv for a run (e.g. the ACP subcommand). */
25
+ args(spec: RunSpec): string[];
26
+ /** Extra process env — e.g. the provider credentials the agent reads for itself. */
27
+ env(spec: RunSpec): Record<string, string>;
28
+ }
29
+ /**
30
+ * Driver-neutral options the engine derives from a {@link RunSpec} and hands to
31
+ * the ACP driver. The default driver spawns `command`+`args` and speaks ACP over
32
+ * its stdio; tests inject a fake driver and assert this mapping.
33
+ */
34
+ export interface AcpOptions {
35
+ command: string;
36
+ args: string[];
37
+ env: Record<string, string>;
38
+ /** The prepared sandbox working tree — the agent's cwd and ACP `session/new` cwd. */
39
+ cwd: string;
40
+ /** The run's MCP servers (connectors + the capability server) for `session/new`. */
41
+ mcpServers: McpServer[];
42
+ /** The task prompt (workflow prompt + inputs). */
43
+ prompt: string;
44
+ /** Wall-clock budget in ms; undefined means no engine-imposed limit. */
45
+ maxDurationMs?: number;
46
+ /** Append a line to the run log (the driver surfaces the agent's stderr here). */
47
+ log: (line: string) => void;
48
+ /** The caller's cancel; the driver merges it with its own `maxDurationMs` timer. */
49
+ signal?: AbortSignal;
50
+ /**
51
+ * How long the agent gets to answer `session/cancel` after a stop before its
52
+ * process is killed. Defaults to 5s. Bounds every stop, so a hung agent
53
+ * cannot keep a canceled or timed-out run alive.
54
+ */
55
+ cancelGraceMs?: number;
56
+ }
57
+ /** Streaming callbacks the driver invokes as ACP `session/update`s arrive. */
58
+ export interface AcpClientEvents {
59
+ /** An `agent_message_chunk` — accumulated into the run's prose result. */
60
+ onAssistantChunk(text: string): void;
61
+ /** A human-readable line for the run log (thoughts, tool-call titles). */
62
+ onLog(line: string): void;
63
+ }
64
+ /** What one ACP prompt turn resolved to. */
65
+ export interface AcpOutcome {
66
+ /** The ACP `stopReason` (`end_turn`, `cancelled`, `refusal`, …). */
67
+ stopReason: string;
68
+ /** Set when the driver stopped the turn itself: its own timer
69
+ * (`"deadline"`) or the caller's signal (`"signal"`). */
70
+ stoppedBy?: StoppedBy;
71
+ }
72
+ /** The injectable boundary to the ACP transport. Tests supply a fake. */
73
+ export type AcpDriver = (opts: AcpOptions, events: AcpClientEvents) => Promise<AcpOutcome>;
74
+ /**
75
+ * Drives a run with any ACP-speaking coding agent (Claude Code, Codex, eve, …)
76
+ * against the sandbox working tree. One engine, one transport: where the codex
77
+ * engine wrote a `config.toml` and the Claude Code engine mounted an in-process
78
+ * SDK MCP server, this hands the run's connectors + the runner-hosted capability
79
+ * server to the agent through the ACP `session/new` `mcpServers` field, and reads
80
+ * back the capability server's emissions file — so an ACP agent reaches emit
81
+ * parity with native.
82
+ *
83
+ * Because an ACP agent runs arbitrary code, `supports()` refuses unless the
84
+ * operator enabled this specific agent AND the run is on the ephemeral runner
85
+ * profile (one throwaway container per run is the safety boundary). File edits
86
+ * are out-of-band: the agent edits `cwd` directly and the caller's change-set
87
+ * diff captures them, so this engine advertises no client filesystem capability.
88
+ */
89
+ export declare class AcpEngine implements AgentEngine {
90
+ readonly name: string;
91
+ private readonly agent;
92
+ private readonly capabilityServerScript;
93
+ private readonly drive;
94
+ /**
95
+ * `capabilityServerScript` is REQUIRED, exactly as for codex: the capability
96
+ * surface is injected, and a child process cannot be handed a closure, so the
97
+ * application must supply the entrypoint that serves its own tools over stdio.
98
+ */
99
+ constructor(agent: AcpAgentSpec, capabilityServerScript: string, drive?: AcpDriver);
100
+ supports(spec: RunSpec): EngineSupport;
101
+ run(spec: RunSpec, ctx: EngineContext): Promise<EngineResult>;
102
+ }
103
+ /**
104
+ * Map an ACP turn's ending onto the run outcome vocabulary. Who stopped it
105
+ * wins when the driver knows (`stoppedBy`); otherwise the ACP `stopReason`
106
+ * decides. `max_turn_requests` is the agent's own step budget; `max_tokens`
107
+ * still produced an answer (truncated), so it stays `completed`; a `refusal`
108
+ * did not do the work, so it is `failed`. An agent-side `cancelled` with no
109
+ * known stopper is reported as `canceled`.
110
+ */
111
+ export declare function acpRunOutcome(outcome: AcpOutcome): RunOutcome;
112
+ /**
113
+ * The eve agent (Vercel's open-source framework), driven over `eve acp` — its
114
+ * stdio ACP server surface. eve reads its own model/provider from the authored
115
+ * eve application in the workdir; we pass through whatever provider credentials
116
+ * the run carries so a BYO key reaches it. (Open: an eve "agent" is an authored
117
+ * app, so the workdir must contain one — a workflow-declared app path with an
118
+ * `agent/`-root default is the follow-up; today it runs the app at the workdir root.)
119
+ */
120
+ export declare const EVE_AGENT: AcpAgentSpec;
121
+ //# sourceMappingURL=acp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acp.d.ts","sourceRoot":"","sources":["../../src/engines/acp.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAQlG;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wFAAwF;IACxF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,4DAA4D;IAC5D,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,CAAA;IAC7B,oFAAoF;IACpF,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC3C;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3B,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAA;IACX,oFAAoF;IACpF,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kFAAkF;IAClF,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,oFAAoF;IACpF,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,0EAA0E;IAC1E,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,4CAA4C;AAC5C,MAAM,WAAW,UAAU;IACzB,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAA;IAClB;8DAC0D;IAC1D,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB;AAED,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;AAE1F;;;;;;;;;;;;;;GAcG;AACH,qBAAa,SAAU,YAAW,WAAW;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IAEjC;;;;OAIG;gBAED,KAAK,EAAE,YAAY,EACnB,sBAAsB,EAAE,MAAM,EAC9B,KAAK,GAAE,SAA4B;IAQrC,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa;IAgBhC,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;CAgFpE;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU,CAa7D;AA8CD;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,YAavB,CAAA"}
@@ -0,0 +1,219 @@
1
+ import { mkdtemp, rm, stat } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { CAPABILITY_SERVER_NAME } from "./capabilityMcp.js";
5
+ import { connectorServers } from "./connectorMcp.js";
6
+ import { readEmissions } from "./capabilityEmissions.js";
7
+ import { defaultAcpDriver } from "./acpDriver.js";
8
+ const LOG_TRUNCATE = 2000;
9
+ /**
10
+ * Drives a run with any ACP-speaking coding agent (Claude Code, Codex, eve, …)
11
+ * against the sandbox working tree. One engine, one transport: where the codex
12
+ * engine wrote a `config.toml` and the Claude Code engine mounted an in-process
13
+ * SDK MCP server, this hands the run's connectors + the runner-hosted capability
14
+ * server to the agent through the ACP `session/new` `mcpServers` field, and reads
15
+ * back the capability server's emissions file — so an ACP agent reaches emit
16
+ * parity with native.
17
+ *
18
+ * Because an ACP agent runs arbitrary code, `supports()` refuses unless the
19
+ * operator enabled this specific agent AND the run is on the ephemeral runner
20
+ * profile (one throwaway container per run is the safety boundary). File edits
21
+ * are out-of-band: the agent edits `cwd` directly and the caller's change-set
22
+ * diff captures them, so this engine advertises no client filesystem capability.
23
+ */
24
+ export class AcpEngine {
25
+ name;
26
+ agent;
27
+ capabilityServerScript;
28
+ drive;
29
+ /**
30
+ * `capabilityServerScript` is REQUIRED, exactly as for codex: the capability
31
+ * surface is injected, and a child process cannot be handed a closure, so the
32
+ * application must supply the entrypoint that serves its own tools over stdio.
33
+ */
34
+ constructor(agent, capabilityServerScript, drive = defaultAcpDriver) {
35
+ this.agent = agent;
36
+ this.name = agent.name;
37
+ this.capabilityServerScript = capabilityServerScript;
38
+ this.drive = drive;
39
+ }
40
+ supports(spec) {
41
+ if (process.env[this.agent.enableEnv] !== "1") {
42
+ return {
43
+ ok: false,
44
+ reason: `${this.name} engine is disabled (set ${this.agent.enableEnv}=1)`,
45
+ };
46
+ }
47
+ if (spec.workflow.runner?.profile !== "ephemeral") {
48
+ return {
49
+ ok: false,
50
+ reason: `${this.name} engine requires the ephemeral runner profile (per-run container isolation)`,
51
+ };
52
+ }
53
+ return { ok: true };
54
+ }
55
+ async run(spec, ctx) {
56
+ // Emissions land OUTSIDE the sandbox so the bookkeeping file is never captured
57
+ // by the change set; the capability server (an external process) writes here and
58
+ // we read it back after the run.
59
+ const emissionsDir = await mkdtemp(path.join(tmpdir(), "harness-emit-"));
60
+ const emissionsFile = path.join(emissionsDir, "emissions.json");
61
+ const capabilityEnv = {
62
+ HARNESS_WORKSPACE_ID: spec.workspaceId,
63
+ HARNESS_WORKDIR: spec.workdir,
64
+ HARNESS_EMISSIONS_FILE: emissionsFile,
65
+ };
66
+ const servers = {
67
+ ...connectorServers(spec.connectors),
68
+ [CAPABILITY_SERVER_NAME]: {
69
+ type: "stdio",
70
+ command: process.execPath,
71
+ args: [this.capabilityServerScript],
72
+ env: capabilityEnv,
73
+ },
74
+ };
75
+ const options = {
76
+ command: this.agent.command,
77
+ args: this.agent.args(spec),
78
+ // The capability env also rides on the process env so the stdio capability
79
+ // server inherits it regardless of how the agent forwards `session/new` env.
80
+ env: { ...this.agent.env(spec), ...capabilityEnv },
81
+ cwd: spec.workdir,
82
+ mcpServers: toAcpMcpServers(servers),
83
+ prompt: buildPrompt(spec),
84
+ ...(spec.limits?.maxDurationMs !== undefined
85
+ ? { maxDurationMs: spec.limits.maxDurationMs }
86
+ : {}),
87
+ log: ctx.log,
88
+ ...(ctx.signal ? { signal: ctx.signal } : {}),
89
+ };
90
+ ctx.log(`${this.name}: driving ACP agent in ${spec.workdir}`);
91
+ const chunks = [];
92
+ let outcome;
93
+ try {
94
+ outcome = await this.drive(options, {
95
+ onAssistantChunk: (text) => {
96
+ if (text)
97
+ chunks.push(text);
98
+ },
99
+ onLog: (line) => {
100
+ if (line)
101
+ ctx.log(`${this.name}: ${truncate(line, LOG_TRUNCATE)}`);
102
+ },
103
+ });
104
+ }
105
+ catch (err) {
106
+ // A driver that throws on the caller's abort was still canceled, not broken.
107
+ if (!ctx.signal?.aborted)
108
+ throw err;
109
+ outcome = { stopReason: "cancelled", stoppedBy: "signal" };
110
+ }
111
+ if (outcome.stopReason && outcome.stopReason !== "end_turn") {
112
+ ctx.log(`${this.name}: stopped (${outcome.stopReason})`);
113
+ }
114
+ // Report whether the emissions round trip HAPPENED, not what it carried —
115
+ // presence plus size separates "the server never wrote" from "it wrote and the
116
+ // run emitted nothing"; the byte count comes from the file (see codex engine).
117
+ const emissionsStat = await stat(emissionsFile).then((st) => st, () => undefined);
118
+ const emissions = await readEmissions(emissionsFile);
119
+ ctx.log(`${this.name}: capability emissions — file ${emissionsStat ? `present (${emissionsStat.size} bytes)` : "absent"}` +
120
+ `${emissionsStat && emissions === undefined ? ", unreadable" : ""}`);
121
+ await rm(emissionsDir, { recursive: true, force: true });
122
+ return {
123
+ text: chunks.join("").trim() || "(no output)",
124
+ ...(emissions !== undefined ? { emissions } : {}),
125
+ outcome: acpRunOutcome(outcome),
126
+ };
127
+ }
128
+ }
129
+ /**
130
+ * Map an ACP turn's ending onto the run outcome vocabulary. Who stopped it
131
+ * wins when the driver knows (`stoppedBy`); otherwise the ACP `stopReason`
132
+ * decides. `max_turn_requests` is the agent's own step budget; `max_tokens`
133
+ * still produced an answer (truncated), so it stays `completed`; a `refusal`
134
+ * did not do the work, so it is `failed`. An agent-side `cancelled` with no
135
+ * known stopper is reported as `canceled`.
136
+ */
137
+ export function acpRunOutcome(outcome) {
138
+ if (outcome.stoppedBy === "deadline")
139
+ return "timed_out";
140
+ if (outcome.stoppedBy === "signal")
141
+ return "canceled";
142
+ switch (outcome.stopReason) {
143
+ case "cancelled":
144
+ return "canceled";
145
+ case "max_turn_requests":
146
+ return "steps_exhausted";
147
+ case "refusal":
148
+ return "failed";
149
+ default:
150
+ return "completed";
151
+ }
152
+ }
153
+ /**
154
+ * Translate the run's serializable MCP servers into ACP's `McpServer[]`. Same
155
+ * source data every other engine consumes (`connectorServers`), reshaped to the
156
+ * ACP wire form: env and headers become `{ name, value }` arrays.
157
+ */
158
+ function toAcpMcpServers(servers) {
159
+ return Object.entries(servers).map(([name, server]) => {
160
+ if (server.type === "stdio") {
161
+ return {
162
+ name,
163
+ command: server.command,
164
+ args: server.args ?? [],
165
+ env: Object.entries(server.env ?? {}).map(([key, value]) => ({ name: key, value })),
166
+ };
167
+ }
168
+ return {
169
+ name,
170
+ type: server.type,
171
+ url: server.url,
172
+ headers: Object.entries(server.headers ?? {}).map(([key, value]) => ({ name: key, value })),
173
+ };
174
+ });
175
+ }
176
+ function buildPrompt(spec) {
177
+ const inputs = JSON.stringify(spec.inputs ?? {}, null, 2);
178
+ return [
179
+ spec.workflow.prompt ?? `Execute the "${spec.workflow.name ?? spec.workflowPath}" workflow.`,
180
+ "",
181
+ "## Inputs",
182
+ "```json",
183
+ inputs,
184
+ "```",
185
+ "",
186
+ "You are running as an autonomous workflow. The current working directory is the",
187
+ "workspace — inspect and edit its files directly with your tools. When finished,",
188
+ "reply with the workflow's output as Markdown.",
189
+ ].join("\n");
190
+ }
191
+ function truncate(text, max) {
192
+ return text.length <= max ? text : `${text.slice(0, max)}…`;
193
+ }
194
+ /**
195
+ * The eve agent (Vercel's open-source framework), driven over `eve acp` — its
196
+ * stdio ACP server surface. eve reads its own model/provider from the authored
197
+ * eve application in the workdir; we pass through whatever provider credentials
198
+ * the run carries so a BYO key reaches it. (Open: an eve "agent" is an authored
199
+ * app, so the workdir must contain one — a workflow-declared app path with an
200
+ * `agent/`-root default is the follow-up; today it runs the app at the workdir root.)
201
+ */
202
+ export const EVE_AGENT = {
203
+ name: "eve",
204
+ enableEnv: "RUNNER_ENABLE_EVE",
205
+ command: "eve",
206
+ args: () => ["acp"],
207
+ env: (spec) => {
208
+ const env = {};
209
+ const creds = spec.provider.credentials;
210
+ if (creds?.anthropic)
211
+ env.ANTHROPIC_API_KEY = creds.anthropic;
212
+ if (creds?.openai)
213
+ env.OPENAI_API_KEY = creds.openai;
214
+ if (creds?.openrouter)
215
+ env.OPENROUTER_API_KEY = creds.openrouter;
216
+ return env;
217
+ },
218
+ };
219
+ //# sourceMappingURL=acp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acp.js","sourceRoot":"","sources":["../../src/engines/acp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type StoppedBy } from "../signals.js";
2
+ import type { AcpClientEvents, AcpOptions, AcpOutcome } from "./acp.js";
3
+ /**
4
+ * The outcome of a prompt turn that returned. A stop is credited only when the
5
+ * agent actually answered `cancelled`: a stop that lands just after a clean
6
+ * `end_turn` must not relabel a finished run as canceled or timed out.
7
+ */
8
+ export declare function promptOutcome(stopReason: string, stoppedBy: StoppedBy | undefined): AcpOutcome;
9
+ /**
10
+ * The default ACP driver: the single integration boundary to an ACP agent
11
+ * subprocess, mirroring `defaultCodexDriver`'s injectable shape. It spawns the
12
+ * agent, wires an {@link ClientSideConnection} over its stdio via
13
+ * {@link ndJsonStream}, and runs one ACP turn: `initialize` → `session/new`
14
+ * (with the run's cwd + MCP servers) → `session/prompt`.
15
+ *
16
+ * The host `Client` it implements is deliberately minimal:
17
+ * - `sessionUpdate` accumulates `agent_message_chunk`s (the run's prose) and
18
+ * surfaces thoughts / tool-call titles to the log.
19
+ * - `requestPermission` AUTO-APPROVES (selects an `allow_*` option). This is
20
+ * the ACP equivalent of the codex driver's `--dangerously-bypass-approvals`
21
+ * and the Claude Code driver's `bypassPermissions`, justified the same way:
22
+ * an ACP agent only runs inside a throwaway ephemeral container, so the
23
+ * container — not an approval prompt — is the safety boundary.
24
+ * - No `readTextFile`/`writeTextFile`/terminal capability is advertised, so the
25
+ * agent edits `cwd` directly and the caller's change-set diff is the sole
26
+ * edit path (no second, protocol-level way for a file to change).
27
+ *
28
+ * Exercised only when the operator enables the agent (per-agent env flag) on the
29
+ * ephemeral profile; every test injects a fake driver, so no binary/network is
30
+ * touched in CI.
31
+ */
32
+ export declare function defaultAcpDriver(opts: AcpOptions, events: AcpClientEvents): Promise<AcpOutcome>;
33
+ //# sourceMappingURL=acpDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpDriver.d.ts","sourceRoot":"","sources":["../../src/engines/acpDriver.ts"],"names":[],"mappings":"AAQA,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,eAAe,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAEvE;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAE9F;AAKD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,UAAU,CAAC,CA6GrB"}
@@ -0,0 +1,150 @@
1
+ import { spawn } from "node:child_process";
2
+ import { Readable, Writable } from "node:stream";
3
+ import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION, } from "@zed-industries/agent-client-protocol";
4
+ import { runStop } from "../signals.js";
5
+ /**
6
+ * The outcome of a prompt turn that returned. A stop is credited only when the
7
+ * agent actually answered `cancelled`: a stop that lands just after a clean
8
+ * `end_turn` must not relabel a finished run as canceled or timed out.
9
+ */
10
+ export function promptOutcome(stopReason, stoppedBy) {
11
+ return stopReason === "cancelled" && stoppedBy ? { stopReason, stoppedBy } : { stopReason };
12
+ }
13
+ /** How long an agent gets to honour `session/cancel` before it is killed. */
14
+ const DEFAULT_CANCEL_GRACE_MS = 5_000;
15
+ /**
16
+ * The default ACP driver: the single integration boundary to an ACP agent
17
+ * subprocess, mirroring `defaultCodexDriver`'s injectable shape. It spawns the
18
+ * agent, wires an {@link ClientSideConnection} over its stdio via
19
+ * {@link ndJsonStream}, and runs one ACP turn: `initialize` → `session/new`
20
+ * (with the run's cwd + MCP servers) → `session/prompt`.
21
+ *
22
+ * The host `Client` it implements is deliberately minimal:
23
+ * - `sessionUpdate` accumulates `agent_message_chunk`s (the run's prose) and
24
+ * surfaces thoughts / tool-call titles to the log.
25
+ * - `requestPermission` AUTO-APPROVES (selects an `allow_*` option). This is
26
+ * the ACP equivalent of the codex driver's `--dangerously-bypass-approvals`
27
+ * and the Claude Code driver's `bypassPermissions`, justified the same way:
28
+ * an ACP agent only runs inside a throwaway ephemeral container, so the
29
+ * container — not an approval prompt — is the safety boundary.
30
+ * - No `readTextFile`/`writeTextFile`/terminal capability is advertised, so the
31
+ * agent edits `cwd` directly and the caller's change-set diff is the sole
32
+ * edit path (no second, protocol-level way for a file to change).
33
+ *
34
+ * Exercised only when the operator enables the agent (per-agent env flag) on the
35
+ * ephemeral profile; every test injects a fake driver, so no binary/network is
36
+ * touched in CI.
37
+ */
38
+ export async function defaultAcpDriver(opts, events) {
39
+ const child = spawn(opts.command, opts.args, {
40
+ cwd: opts.cwd,
41
+ env: { ...process.env, ...opts.env },
42
+ stdio: ["pipe", "pipe", "pipe"],
43
+ });
44
+ let stderr = "";
45
+ child.stderr?.setEncoding("utf8");
46
+ child.stderr?.on("data", (chunk) => {
47
+ stderr += chunk;
48
+ });
49
+ // A spawn failure (e.g. the agent binary is absent) surfaces here; the awaited
50
+ // protocol calls below then reject and we rethrow with the stderr tail.
51
+ child.on("error", (err) => {
52
+ stderr += `\n${String(err)}`;
53
+ });
54
+ const client = {
55
+ // Not `async`: these return a resolved promise directly (the ACP `Client`
56
+ // methods are `Promise`-typed, but there is nothing to await).
57
+ sessionUpdate({ update }) {
58
+ switch (update.sessionUpdate) {
59
+ case "agent_message_chunk":
60
+ if (update.content.type === "text")
61
+ events.onAssistantChunk(update.content.text);
62
+ break;
63
+ case "agent_thought_chunk":
64
+ if (update.content.type === "text")
65
+ events.onLog(`thought: ${update.content.text}`);
66
+ break;
67
+ case "tool_call":
68
+ events.onLog(`tool: ${update.title}`);
69
+ break;
70
+ default:
71
+ break;
72
+ }
73
+ return Promise.resolve();
74
+ },
75
+ requestPermission({ options }) {
76
+ const allow = options.find((o) => o.kind === "allow_always") ??
77
+ options.find((o) => o.kind === "allow_once");
78
+ return Promise.resolve(allow
79
+ ? { outcome: { outcome: "selected", optionId: allow.optionId } }
80
+ : { outcome: { outcome: "cancelled" } });
81
+ },
82
+ };
83
+ const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
84
+ const conn = new ClientSideConnection(() => client, stream);
85
+ // The run's own timer merged with the caller's cancel. Once a session exists
86
+ // the stop is a protocol-level `session/cancel` (the agent winds the turn
87
+ // down and answers `cancelled`), with a bounded grace period: an agent that
88
+ // is hung or ignores the cancel is killed, so a stop can never hang the run.
89
+ // Before a session exists there is nothing to cancel, so it is killed at once.
90
+ //
91
+ // Every protocol await races `forced`, which rejects on a force-stop: a
92
+ // killed agent never answers, and its pending requests would otherwise wait
93
+ // forever.
94
+ let sessionId = "";
95
+ let forceTimer;
96
+ let rejectForced = () => { };
97
+ const forced = new Promise((_, reject) => {
98
+ rejectForced = reject;
99
+ });
100
+ forced.catch(() => { }); // observed via the races below; never unhandled
101
+ const guarded = (call) => Promise.race([call, forced]);
102
+ const forceStop = () => {
103
+ if (child.exitCode === null && child.signalCode === null)
104
+ child.kill("SIGKILL");
105
+ rejectForced(new Error(`${opts.command}: agent force-stopped`));
106
+ };
107
+ const stop = runStop(opts.maxDurationMs, opts.signal);
108
+ const onStop = () => {
109
+ if (!sessionId)
110
+ return forceStop();
111
+ void conn.cancel({ sessionId }).catch(() => { });
112
+ forceTimer = setTimeout(forceStop, opts.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS);
113
+ };
114
+ if (stop.signal?.aborted)
115
+ onStop();
116
+ else
117
+ stop.signal?.addEventListener("abort", onStop, { once: true });
118
+ try {
119
+ await guarded(conn.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }));
120
+ const session = await guarded(conn.newSession({ cwd: opts.cwd, mcpServers: opts.mcpServers }));
121
+ sessionId = session.sessionId;
122
+ // A stop that landed while the session was being created killed nothing
123
+ // yet (there was no session): honour it now rather than run the turn.
124
+ const early = stop.stoppedBy();
125
+ if (early)
126
+ return { stopReason: "cancelled", stoppedBy: early };
127
+ const res = await guarded(conn.prompt({ sessionId, prompt: [{ type: "text", text: opts.prompt }] }));
128
+ return promptOutcome(res.stopReason, stop.stoppedBy());
129
+ }
130
+ catch (err) {
131
+ // A force-stop (or the kill-before-session path) rejects the pending
132
+ // protocol call; that is the stop, not an agent failure.
133
+ const stoppedBy = stop.stoppedBy();
134
+ if (stoppedBy)
135
+ return { stopReason: "cancelled", stoppedBy };
136
+ if (stderr.trim()) {
137
+ const tail = stderr.length > 4000 ? `…${stderr.slice(-4000)}` : stderr;
138
+ opts.log(`${opts.command} stderr:\n${tail}`);
139
+ }
140
+ throw err;
141
+ }
142
+ finally {
143
+ if (forceTimer)
144
+ clearTimeout(forceTimer);
145
+ stop.signal?.removeEventListener("abort", onStop);
146
+ if (child.exitCode === null && child.signalCode === null)
147
+ child.kill();
148
+ }
149
+ }
150
+ //# sourceMappingURL=acpDriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpDriver.js","sourceRoot":"","sources":["../../src/engines/acpDriver.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { type StoppedBy } from "../signals.js";
1
2
  import type { ConnectorConfig } from "../types.js";
2
3
  import type { AgentEngine, EngineContext, EngineResult, EngineSupport, RunSpec } from "./types.js";
3
4
  import { type CapabilityContext, type CapabilityTool } from "./capability.js";
@@ -25,6 +26,8 @@ export interface ClaudeCodeOptions {
25
26
  /** The run's external MCP connectors; the driver mounts them as SDK mcpServers so
26
27
  * they become tools inside Claude Code (parity with the native engine). */
27
28
  connectors?: ConnectorConfig[];
29
+ /** The caller's cancel; the driver merges it with its own `maxDurationMs` timer. */
30
+ signal?: AbortSignal;
28
31
  }
29
32
  /** A normalized message from the harness (driver-agnostic). */
30
33
  export type ClaudeCodeMessage = {
@@ -34,6 +37,9 @@ export type ClaudeCodeMessage = {
34
37
  kind: "result";
35
38
  text: string;
36
39
  isError: boolean;
40
+ /** Set when the driver stopped the harness early: its own timer
41
+ * (`"deadline"`) or the caller's signal (`"signal"`). */
42
+ stoppedBy?: StoppedBy;
37
43
  };
38
44
  /**
39
45
  * The injectable boundary to the Claude Code harness: given mapped options, yield
@@ -1 +1 @@
1
- {"version":3,"file":"claudeCode.d.ts","sourceRoot":"","sources":["../../src/engines/claudeCode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAElG,OAAO,EAAuB,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAElG;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAA;IACX,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;sFACkF;IAClF,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;IAC3B;gFAC4E;IAC5E,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;CAC/B;AAED,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAC3B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAA;AAE1F;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,iBAAiB,KAAK,aAAa,CAAC,iBAAiB,CAAC,CAAA;AAK5F;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,iBAAiB,KAAK,cAAc,EAAE,CAAA;AAEhF,0FAA0F;AAC1F,eAAO,MAAM,sBAAsB,EAAE,qBAA2D,CAAA;AAEhG;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,YAAW,WAAW;IAClD,QAAQ,CAAC,IAAI,iBAAgB;IAE7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuB;gBAGrD,KAAK,GAAE,gBAA0C,EACjD,eAAe,GAAE,qBAA8C;IAMjE,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa;IAiBhC,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;CA4CpE"}
1
+ {"version":3,"file":"claudeCode.d.ts","sourceRoot":"","sources":["../../src/engines/claudeCode.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAElG,OAAO,EAAuB,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAElG;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAA;IACX,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;sFACkF;IAClF,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;IAC3B;gFAC4E;IAC5E,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,oFAAoF;IACpF,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IACE,IAAI,EAAE,QAAQ,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;IAChB;8DAC0D;IAC1D,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAEL;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,iBAAiB,KAAK,aAAa,CAAC,iBAAiB,CAAC,CAAA;AAK5F;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,iBAAiB,KAAK,cAAc,EAAE,CAAA;AAEhF,0FAA0F;AAC1F,eAAO,MAAM,sBAAsB,EAAE,qBAA2D,CAAA;AAEhG;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,YAAW,WAAW;IAClD,QAAQ,CAAC,IAAI,iBAAgB;IAE7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuB;gBAGrD,KAAK,GAAE,gBAA0C,EACjD,eAAe,GAAE,qBAA8C;IAMjE,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa;IAiBhC,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;CA+DpE"}
@@ -1,3 +1,4 @@
1
+ import { harnessOutcome } from "../signals.js";
1
2
  import { defaultClaudeCodeDriver } from "./claudeCodeDriver.js";
2
3
  import { writeFileCapability } from "./capability.js";
3
4
  const DEFAULT_MODEL = "claude-opus-4-8";
@@ -62,21 +63,42 @@ export class ClaudeCodeEngine {
62
63
  ...(spec.limits?.maxDurationMs !== undefined
63
64
  ? { maxDurationMs: spec.limits.maxDurationMs }
64
65
  : {}),
66
+ ...(ctx.signal ? { signal: ctx.signal } : {}),
65
67
  };
66
68
  ctx.log(`claude-code: driving model ${model} in ${spec.workdir}`);
67
69
  let finalText = "";
68
- for await (const message of this.drive(options)) {
69
- if (message.kind === "assistant") {
70
- if (message.text)
71
- ctx.log(`claude-code: ${truncate(message.text, LOG_TRUNCATE)}`);
72
- }
73
- else {
74
- finalText = message.text;
75
- if (message.isError)
76
- ctx.log("claude-code: harness reported an error");
70
+ let lastAssistantText = "";
71
+ let outcome = "completed";
72
+ try {
73
+ for await (const message of this.drive(options)) {
74
+ if (message.kind === "assistant") {
75
+ if (message.text) {
76
+ lastAssistantText = message.text;
77
+ ctx.log(`claude-code: ${truncate(message.text, LOG_TRUNCATE)}`);
78
+ }
79
+ }
80
+ else {
81
+ finalText = message.text;
82
+ outcome = harnessOutcome(message.isError, message.stoppedBy);
83
+ if (message.stoppedBy)
84
+ ctx.log(`claude-code: stopped (${outcome})`);
85
+ else if (message.isError)
86
+ ctx.log("claude-code: harness reported an error");
87
+ }
77
88
  }
78
89
  }
79
- return { text: finalText || "(no output)" };
90
+ catch (err) {
91
+ // A driver that throws on the caller's abort was still canceled, not broken.
92
+ if (!ctx.signal?.aborted)
93
+ throw err;
94
+ outcome = "canceled";
95
+ ctx.log("claude-code: stopped (canceled)");
96
+ }
97
+ const stopped = outcome === "canceled" || outcome === "timed_out";
98
+ return {
99
+ text: finalText || (stopped ? lastAssistantText : "") || "(no output)",
100
+ outcome,
101
+ };
80
102
  }
81
103
  }
82
104
  function buildPrompt(spec) {