@rarebit-one/harness-kernel 0.5.1

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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +286 -0
  3. package/dist/agent.d.ts +71 -0
  4. package/dist/agent.d.ts.map +1 -0
  5. package/dist/agent.js +258 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/connectors/mcpClient.d.ts +22 -0
  8. package/dist/connectors/mcpClient.d.ts.map +1 -0
  9. package/dist/connectors/mcpClient.js +63 -0
  10. package/dist/connectors/mcpClient.js.map +1 -0
  11. package/dist/context/types.d.ts +68 -0
  12. package/dist/context/types.d.ts.map +1 -0
  13. package/dist/context/types.js +43 -0
  14. package/dist/context/types.js.map +1 -0
  15. package/dist/engines/capability.d.ts +67 -0
  16. package/dist/engines/capability.d.ts.map +1 -0
  17. package/dist/engines/capability.js +73 -0
  18. package/dist/engines/capability.js.map +1 -0
  19. package/dist/engines/capabilityEmissions.d.ts +27 -0
  20. package/dist/engines/capabilityEmissions.d.ts.map +1 -0
  21. package/dist/engines/capabilityEmissions.js +37 -0
  22. package/dist/engines/capabilityEmissions.js.map +1 -0
  23. package/dist/engines/capabilityMcp.d.ts +23 -0
  24. package/dist/engines/capabilityMcp.d.ts.map +1 -0
  25. package/dist/engines/capabilityMcp.js +36 -0
  26. package/dist/engines/capabilityMcp.js.map +1 -0
  27. package/dist/engines/capabilityStdio.d.ts +46 -0
  28. package/dist/engines/capabilityStdio.d.ts.map +1 -0
  29. package/dist/engines/capabilityStdio.js +39 -0
  30. package/dist/engines/capabilityStdio.js.map +1 -0
  31. package/dist/engines/claudeCode.d.ts +73 -0
  32. package/dist/engines/claudeCode.d.ts.map +1 -0
  33. package/dist/engines/claudeCode.js +100 -0
  34. package/dist/engines/claudeCode.js.map +1 -0
  35. package/dist/engines/claudeCodeDriver.d.ts +14 -0
  36. package/dist/engines/claudeCodeDriver.d.ts.map +1 -0
  37. package/dist/engines/claudeCodeDriver.js +90 -0
  38. package/dist/engines/claudeCodeDriver.js.map +1 -0
  39. package/dist/engines/codex.d.ts +72 -0
  40. package/dist/engines/codex.d.ts.map +1 -0
  41. package/dist/engines/codex.js +157 -0
  42. package/dist/engines/codex.js.map +1 -0
  43. package/dist/engines/codexDriver.d.ts +43 -0
  44. package/dist/engines/codexDriver.d.ts.map +1 -0
  45. package/dist/engines/codexDriver.js +110 -0
  46. package/dist/engines/codexDriver.js.map +1 -0
  47. package/dist/engines/connectorMcp.d.ts +60 -0
  48. package/dist/engines/connectorMcp.d.ts.map +1 -0
  49. package/dist/engines/connectorMcp.js +137 -0
  50. package/dist/engines/connectorMcp.js.map +1 -0
  51. package/dist/engines/index.d.ts +33 -0
  52. package/dist/engines/index.d.ts.map +1 -0
  53. package/dist/engines/index.js +29 -0
  54. package/dist/engines/index.js.map +1 -0
  55. package/dist/engines/native.d.ts +55 -0
  56. package/dist/engines/native.d.ts.map +1 -0
  57. package/dist/engines/native.js +117 -0
  58. package/dist/engines/native.js.map +1 -0
  59. package/dist/engines/types.d.ts +107 -0
  60. package/dist/engines/types.d.ts.map +1 -0
  61. package/dist/engines/types.js +2 -0
  62. package/dist/engines/types.js.map +1 -0
  63. package/dist/events.d.ts +211 -0
  64. package/dist/events.d.ts.map +1 -0
  65. package/dist/events.js +69 -0
  66. package/dist/events.js.map +1 -0
  67. package/dist/index.d.ts +63 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +82 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/loop.d.ts +132 -0
  72. package/dist/loop.d.ts.map +1 -0
  73. package/dist/loop.js +77 -0
  74. package/dist/loop.js.map +1 -0
  75. package/dist/models/chat.d.ts +65 -0
  76. package/dist/models/chat.d.ts.map +1 -0
  77. package/dist/models/chat.js +66 -0
  78. package/dist/models/chat.js.map +1 -0
  79. package/dist/models/middleware.d.ts +63 -0
  80. package/dist/models/middleware.d.ts.map +1 -0
  81. package/dist/models/middleware.js +139 -0
  82. package/dist/models/middleware.js.map +1 -0
  83. package/dist/models/registry.d.ts +78 -0
  84. package/dist/models/registry.d.ts.map +1 -0
  85. package/dist/models/registry.js +0 -0
  86. package/dist/models/registry.js.map +1 -0
  87. package/dist/models/types.d.ts +149 -0
  88. package/dist/models/types.d.ts.map +1 -0
  89. package/dist/models/types.js +64 -0
  90. package/dist/models/types.js.map +1 -0
  91. package/dist/primitives/codeExec.d.ts +25 -0
  92. package/dist/primitives/codeExec.d.ts.map +1 -0
  93. package/dist/primitives/codeExec.js +69 -0
  94. package/dist/primitives/codeExec.js.map +1 -0
  95. package/dist/primitives/download.d.ts +19 -0
  96. package/dist/primitives/download.d.ts.map +1 -0
  97. package/dist/primitives/download.js +113 -0
  98. package/dist/primitives/download.js.map +1 -0
  99. package/dist/primitives/fs.d.ts +5 -0
  100. package/dist/primitives/fs.d.ts.map +1 -0
  101. package/dist/primitives/fs.js +59 -0
  102. package/dist/primitives/fs.js.map +1 -0
  103. package/dist/primitives/http.d.ts +100 -0
  104. package/dist/primitives/http.d.ts.map +1 -0
  105. package/dist/primitives/http.js +257 -0
  106. package/dist/primitives/http.js.map +1 -0
  107. package/dist/providers/anthropic.d.ts +15 -0
  108. package/dist/providers/anthropic.d.ts.map +1 -0
  109. package/dist/providers/anthropic.js +90 -0
  110. package/dist/providers/anthropic.js.map +1 -0
  111. package/dist/providers/clientOptions.d.ts +14 -0
  112. package/dist/providers/clientOptions.d.ts.map +1 -0
  113. package/dist/providers/clientOptions.js +26 -0
  114. package/dist/providers/clientOptions.js.map +1 -0
  115. package/dist/providers/index.d.ts +24 -0
  116. package/dist/providers/index.d.ts.map +1 -0
  117. package/dist/providers/index.js +51 -0
  118. package/dist/providers/index.js.map +1 -0
  119. package/dist/providers/mock.d.ts +25 -0
  120. package/dist/providers/mock.d.ts.map +1 -0
  121. package/dist/providers/mock.js +85 -0
  122. package/dist/providers/mock.js.map +1 -0
  123. package/dist/providers/openai.d.ts +32 -0
  124. package/dist/providers/openai.d.ts.map +1 -0
  125. package/dist/providers/openai.js +118 -0
  126. package/dist/providers/openai.js.map +1 -0
  127. package/dist/providers/openrouter.d.ts +29 -0
  128. package/dist/providers/openrouter.d.ts.map +1 -0
  129. package/dist/providers/openrouter.js +35 -0
  130. package/dist/providers/openrouter.js.map +1 -0
  131. package/dist/providers/types.d.ts +60 -0
  132. package/dist/providers/types.d.ts.map +1 -0
  133. package/dist/providers/types.js +2 -0
  134. package/dist/providers/types.js.map +1 -0
  135. package/dist/routing/staticResolver.d.ts +41 -0
  136. package/dist/routing/staticResolver.d.ts.map +1 -0
  137. package/dist/routing/staticResolver.js +67 -0
  138. package/dist/routing/staticResolver.js.map +1 -0
  139. package/dist/routing/types.d.ts +72 -0
  140. package/dist/routing/types.d.ts.map +1 -0
  141. package/dist/routing/types.js +11 -0
  142. package/dist/routing/types.js.map +1 -0
  143. package/dist/secrets.d.ts +8 -0
  144. package/dist/secrets.d.ts.map +1 -0
  145. package/dist/secrets.js +14 -0
  146. package/dist/secrets.js.map +1 -0
  147. package/dist/tools/metadata.d.ts +93 -0
  148. package/dist/tools/metadata.d.ts.map +1 -0
  149. package/dist/tools/metadata.js +49 -0
  150. package/dist/tools/metadata.js.map +1 -0
  151. package/dist/tools/modelTool.d.ts +35 -0
  152. package/dist/tools/modelTool.d.ts.map +1 -0
  153. package/dist/tools/modelTool.js +37 -0
  154. package/dist/tools/modelTool.js.map +1 -0
  155. package/dist/tools/registry.d.ts +65 -0
  156. package/dist/tools/registry.d.ts.map +1 -0
  157. package/dist/tools/registry.js +166 -0
  158. package/dist/tools/registry.js.map +1 -0
  159. package/dist/types.d.ts +71 -0
  160. package/dist/types.d.ts.map +1 -0
  161. package/dist/types.js +10 -0
  162. package/dist/types.js.map +1 -0
  163. package/package.json +72 -0
@@ -0,0 +1,73 @@
1
+ import type { ConnectorConfig } from "../types.js";
2
+ import type { AgentEngine, EngineContext, EngineResult, EngineSupport, RunSpec } from "./types.js";
3
+ import { type CapabilityContext, type CapabilityTool } from "./capability.js";
4
+ /**
5
+ * Driver-neutral options the engine derives from a {@link RunSpec} and hands to
6
+ * the Claude Code harness. The default driver maps these onto the Anthropic Agent
7
+ * SDK; tests inject a fake driver and assert this mapping.
8
+ */
9
+ export interface ClaudeCodeOptions {
10
+ /** The task prompt (workflow prompt + inputs). */
11
+ prompt: string;
12
+ /** The prepared sandbox working tree — the harness's cwd; edits land here. */
13
+ cwd: string;
14
+ /** Model id (resolved: per-run model, else the engine default). */
15
+ model: string;
16
+ /** API key (per-org BYO, else the runner env key). */
17
+ apiKey?: string;
18
+ /** Tool allowlist; undefined means the harness's defaults. */
19
+ allowedTools?: string[];
20
+ /** Wall-clock budget in ms; undefined means no engine-imposed limit. */
21
+ maxDurationMs?: number;
22
+ /** Workspace-scoped capabilities the driver mounts as an in-process MCP server.
23
+ * Which ones is the application's choice — see {@link CapabilityToolFactory}. */
24
+ mcpTools?: CapabilityTool[];
25
+ /** The run's external MCP connectors; the driver mounts them as SDK mcpServers so
26
+ * they become tools inside Claude Code (parity with the native engine). */
27
+ connectors?: ConnectorConfig[];
28
+ }
29
+ /** A normalized message from the harness (driver-agnostic). */
30
+ export type ClaudeCodeMessage = {
31
+ kind: "assistant";
32
+ text: string;
33
+ } | {
34
+ kind: "result";
35
+ text: string;
36
+ isError: boolean;
37
+ };
38
+ /**
39
+ * The injectable boundary to the Claude Code harness: given mapped options, yield
40
+ * a normalized message stream. The default implementation drives the Agent SDK;
41
+ * tests inject a fake so no CLI/network runs.
42
+ */
43
+ export type ClaudeCodeDriver = (opts: ClaudeCodeOptions) => AsyncIterable<ClaudeCodeMessage>;
44
+ /**
45
+ * Builds the capability surface a run gets, bound to that run's workspace scope
46
+ * and working tree. The kernel supplies only the generic
47
+ * {@link writeFileCapability}; an application injects a factory that composes
48
+ * its own capabilities alongside it, closing over whatever sinks it wants the
49
+ * emissions to land in.
50
+ */
51
+ export type CapabilityToolFactory = (ctx: CapabilityContext) => CapabilityTool[];
52
+ /** The kernel's default surface: the one capability that carries no product semantics. */
53
+ export declare const defaultCapabilityTools: CapabilityToolFactory;
54
+ /**
55
+ * Drives a run with the Claude Code harness (its own grep/glob/edit/bash tools +
56
+ * tuned loop) against the sandbox working tree. File edits land on disk and are
57
+ * captured by the caller's change-set diff — the engine returns prose only.
58
+ *
59
+ * Claude Code runs arbitrary bash, so it must not run on the warm shared runner.
60
+ * `supports()` refuses unless (a) the operator has explicitly enabled it and (b)
61
+ * the run is on the `ephemeral` runner profile (one throwaway container per run —
62
+ * the container, not a human, is the safety boundary). The SDK is loaded lazily by
63
+ * the driver, so this module stays cheap to import on the warm runner.
64
+ */
65
+ export declare class ClaudeCodeEngine implements AgentEngine {
66
+ readonly name = "claude-code";
67
+ private readonly drive;
68
+ private readonly capabilityTools;
69
+ constructor(drive?: ClaudeCodeDriver, capabilityTools?: CapabilityToolFactory);
70
+ supports(spec: RunSpec): EngineSupport;
71
+ run(spec: RunSpec, ctx: EngineContext): Promise<EngineResult>;
72
+ }
73
+ //# sourceMappingURL=claudeCode.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,100 @@
1
+ import { defaultClaudeCodeDriver } from "./claudeCodeDriver.js";
2
+ import { writeFileCapability } from "./capability.js";
3
+ const DEFAULT_MODEL = "claude-opus-4-8";
4
+ const LOG_TRUNCATE = 2000;
5
+ /** The kernel's default surface: the one capability that carries no product semantics. */
6
+ export const defaultCapabilityTools = (ctx) => [writeFileCapability(ctx)];
7
+ /**
8
+ * Drives a run with the Claude Code harness (its own grep/glob/edit/bash tools +
9
+ * tuned loop) against the sandbox working tree. File edits land on disk and are
10
+ * captured by the caller's change-set diff — the engine returns prose only.
11
+ *
12
+ * Claude Code runs arbitrary bash, so it must not run on the warm shared runner.
13
+ * `supports()` refuses unless (a) the operator has explicitly enabled it and (b)
14
+ * the run is on the `ephemeral` runner profile (one throwaway container per run —
15
+ * the container, not a human, is the safety boundary). The SDK is loaded lazily by
16
+ * the driver, so this module stays cheap to import on the warm runner.
17
+ */
18
+ export class ClaudeCodeEngine {
19
+ name = "claude-code";
20
+ drive;
21
+ capabilityTools;
22
+ constructor(drive = defaultClaudeCodeDriver, capabilityTools = defaultCapabilityTools) {
23
+ this.drive = drive;
24
+ this.capabilityTools = capabilityTools;
25
+ }
26
+ supports(spec) {
27
+ if (process.env.RUNNER_ENABLE_CLAUDE_CODE !== "1") {
28
+ return {
29
+ ok: false,
30
+ reason: "claude-code engine is disabled (set RUNNER_ENABLE_CLAUDE_CODE=1)",
31
+ };
32
+ }
33
+ if (spec.workflow.runner?.profile !== "ephemeral") {
34
+ return {
35
+ ok: false,
36
+ reason: "claude-code engine requires the ephemeral runner profile (per-run container isolation)",
37
+ };
38
+ }
39
+ return { ok: true };
40
+ }
41
+ async run(spec, ctx) {
42
+ const apiKey = spec.provider.credentials?.anthropic ?? process.env.ANTHROPIC_API_KEY;
43
+ const model = spec.provider.model || DEFAULT_MODEL;
44
+ const allowedTools = Array.isArray(spec.permissions.tools) && spec.permissions.tools.length > 0
45
+ ? spec.permissions.tools
46
+ : undefined;
47
+ // No emissions from here: injected capabilities close over sinks the
48
+ // APPLICATION owns, so it reads what a run emitted from those directly.
49
+ // Omitted rather than returned empty — see EngineResult.
50
+ const mcpTools = this.capabilityTools({
51
+ workspaceId: spec.workspaceId,
52
+ workdir: spec.workdir,
53
+ });
54
+ const options = {
55
+ prompt: buildPrompt(spec),
56
+ cwd: spec.workdir,
57
+ model,
58
+ mcpTools,
59
+ ...(spec.connectors.length > 0 ? { connectors: spec.connectors } : {}),
60
+ ...(apiKey ? { apiKey } : {}),
61
+ ...(allowedTools ? { allowedTools } : {}),
62
+ ...(spec.limits?.maxDurationMs !== undefined
63
+ ? { maxDurationMs: spec.limits.maxDurationMs }
64
+ : {}),
65
+ };
66
+ ctx.log(`claude-code: driving model ${model} in ${spec.workdir}`);
67
+ 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");
77
+ }
78
+ }
79
+ return { text: finalText || "(no output)" };
80
+ }
81
+ }
82
+ function buildPrompt(spec) {
83
+ const inputs = JSON.stringify(spec.inputs ?? {}, null, 2);
84
+ return [
85
+ spec.workflow.prompt ?? `Execute the "${spec.workflow.name ?? spec.workflowPath}" workflow.`,
86
+ "",
87
+ "## Inputs",
88
+ "```json",
89
+ inputs,
90
+ "```",
91
+ "",
92
+ "You are running as an autonomous workflow. The current working directory is the",
93
+ "workspace — inspect and edit its files directly with your tools. When finished,",
94
+ "reply with the workflow's output as Markdown.",
95
+ ].join("\n");
96
+ }
97
+ function truncate(text, max) {
98
+ return text.length <= max ? text : `${text.slice(0, max)}…`;
99
+ }
100
+ //# sourceMappingURL=claudeCode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeCode.js","sourceRoot":"","sources":["../../src/engines/claudeCode.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAA+C,MAAM,iBAAiB,CAAA;AAuClG,MAAM,aAAa,GAAG,iBAAiB,CAAA;AACvC,MAAM,YAAY,GAAG,IAAI,CAAA;AAWzB,0FAA0F;AAC1F,MAAM,CAAC,MAAM,sBAAsB,GAA0B,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAA;AAEhG;;;;;;;;;;GAUG;AACH,MAAM,OAAO,gBAAgB;IAClB,IAAI,GAAG,aAAa,CAAA;IAEZ,KAAK,CAAkB;IACvB,eAAe,CAAuB;IAEvD,YACE,QAA0B,uBAAuB,EACjD,kBAAyC,sBAAsB;QAE/D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,eAAe,GAAG,eAAe,CAAA;IACxC,CAAC;IAED,QAAQ,CAAC,IAAa;QACpB,IAAI,OAAO,CAAC,GAAG,CAAC,yBAAyB,KAAK,GAAG,EAAE,CAAC;YAClD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,kEAAkE;aAC3E,CAAA;QACH,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,KAAK,WAAW,EAAE,CAAC;YAClD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EACJ,wFAAwF;aAC3F,CAAA;QACH,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;IACrB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAa,EAAE,GAAkB;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAA;QACpF,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,aAAa,CAAA;QAClD,MAAM,YAAY,GAChB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;YACxE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK;YACxB,CAAC,CAAC,SAAS,CAAA;QAEf,qEAAqE;QACrE,wEAAwE;QACxE,yDAAyD;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC;YACpC,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC,CAAA;QAEF,MAAM,OAAO,GAAsB;YACjC,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC;YACzB,GAAG,EAAE,IAAI,CAAC,OAAO;YACjB,KAAK;YACL,QAAQ;YACR,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7B,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,KAAK,SAAS;gBAC1C,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE;gBAC9C,CAAC,CAAC,EAAE,CAAC;SACR,CAAA;QAED,GAAG,CAAC,GAAG,CAAC,8BAA8B,KAAK,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;QAEjE,IAAI,SAAS,GAAG,EAAE,CAAA;QAElB,IAAI,KAAK,EAAE,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YAChD,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACjC,IAAI,OAAO,CAAC,IAAI;oBAAE,GAAG,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC,CAAA;YACnF,CAAC;iBAAM,CAAC;gBACN,SAAS,GAAG,OAAO,CAAC,IAAI,CAAA;gBACxB,IAAI,OAAO,CAAC,OAAO;oBAAE,GAAG,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAA;YACxE,CAAC;QACH,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,SAAS,IAAI,aAAa,EAAE,CAAA;IAC7C,CAAC;CACF;AAED,SAAS,WAAW,CAAC,IAAa;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IACzD,OAAO;QACL,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,gBAAgB,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,aAAa;QAC5F,EAAE;QACF,WAAW;QACX,SAAS;QACT,MAAM;QACN,KAAK;QACL,EAAE;QACF,iFAAiF;QACjF,iFAAiF;QACjF,+CAA+C;KAChD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACd,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,OAAO,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAA;AAC7D,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { ClaudeCodeMessage, ClaudeCodeOptions } from "./claudeCode.js";
2
+ /**
3
+ * The default Claude Code driver: runs the Anthropic Agent SDK's `query()` (which
4
+ * spawns the Claude Code CLI) against the prepared workdir and normalizes its
5
+ * message stream. This is the ONLY module that touches the SDK, and it loads it
6
+ * lazily (dynamic import) so the warm runner — where this engine never runs —
7
+ * doesn't pay to import it.
8
+ *
9
+ * Permission mode is `bypassPermissions`: this engine only runs inside a throwaway
10
+ * ephemeral container (enforced by ClaudeCodeEngine.supports), so the container,
11
+ * not an interactive human, is the safety boundary.
12
+ */
13
+ export declare function defaultClaudeCodeDriver(opts: ClaudeCodeOptions): AsyncIterable<ClaudeCodeMessage>;
14
+ //# sourceMappingURL=claudeCodeDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeCodeDriver.d.ts","sourceRoot":"","sources":["../../src/engines/claudeCodeDriver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAI3E;;;;;;;;;;GAUG;AACH,wBAAuB,uBAAuB,CAC5C,IAAI,EAAE,iBAAiB,GACtB,aAAa,CAAC,iBAAiB,CAAC,CAqDlC"}
@@ -0,0 +1,90 @@
1
+ import { CAPABILITY_SERVER_NAME, capabilitySdkServer, capabilityToolIds } from "./capabilityMcp.js";
2
+ import { connectorServers } from "./connectorMcp.js";
3
+ /**
4
+ * The default Claude Code driver: runs the Anthropic Agent SDK's `query()` (which
5
+ * spawns the Claude Code CLI) against the prepared workdir and normalizes its
6
+ * message stream. This is the ONLY module that touches the SDK, and it loads it
7
+ * lazily (dynamic import) so the warm runner — where this engine never runs —
8
+ * doesn't pay to import it.
9
+ *
10
+ * Permission mode is `bypassPermissions`: this engine only runs inside a throwaway
11
+ * ephemeral container (enforced by ClaudeCodeEngine.supports), so the container,
12
+ * not an interactive human, is the safety boundary.
13
+ */
14
+ export async function* defaultClaudeCodeDriver(opts) {
15
+ const { query } = await import("@anthropic-ai/claude-agent-sdk");
16
+ const controller = new AbortController();
17
+ const timer = opts.maxDurationMs && opts.maxDurationMs > 0
18
+ ? setTimeout(() => controller.abort(), opts.maxDurationMs)
19
+ : undefined;
20
+ // Mount the run's MCP servers: the runner-hosted, workspace-scoped capability
21
+ // surface (in-process) so Claude Code emits issues/knowledge/files uniformly with
22
+ // native, plus the run's external connectors (Slack, an Airwallex MCP, …) so they
23
+ // become tools inside Claude Code too. Same connector source the native engine uses.
24
+ const capabilities = opts.mcpTools ?? [];
25
+ const connectors = connectorServers(opts.connectors ?? []);
26
+ const mcpServers = { ...connectors };
27
+ if (capabilities.length > 0) {
28
+ mcpServers[CAPABILITY_SERVER_NAME] = await capabilitySdkServer(capabilities);
29
+ }
30
+ const hasMcpServers = Object.keys(mcpServers).length > 0;
31
+ // When the workflow narrows tools (allowedTools set), the mounted MCP tools must
32
+ // still be permitted; append the capability tool ids and a server-level allow for
33
+ // each connector (whose tool names are only known at connect time). With no
34
+ // allowlist, bypassPermissions already permits every tool.
35
+ const allowedTools = opts.allowedTools && opts.allowedTools.length > 0
36
+ ? [
37
+ ...opts.allowedTools,
38
+ ...capabilityToolIds(capabilities),
39
+ ...Object.keys(connectors).map((name) => `mcp__${name}`),
40
+ ]
41
+ : opts.allowedTools;
42
+ const options = {
43
+ cwd: opts.cwd,
44
+ model: opts.model,
45
+ permissionMode: "bypassPermissions",
46
+ allowDangerouslySkipPermissions: true,
47
+ abortController: controller,
48
+ env: { ...process.env, ...(opts.apiKey ? { ANTHROPIC_API_KEY: opts.apiKey } : {}) },
49
+ ...(hasMcpServers ? { mcpServers } : {}),
50
+ ...(allowedTools ? { allowedTools } : {}),
51
+ };
52
+ try {
53
+ for await (const message of query({ prompt: opts.prompt, options })) {
54
+ const normalized = normalize(message);
55
+ if (normalized)
56
+ yield normalized;
57
+ }
58
+ }
59
+ finally {
60
+ if (timer)
61
+ clearTimeout(timer);
62
+ }
63
+ }
64
+ function normalize(message) {
65
+ if (message.type === "assistant") {
66
+ const text = extractText(message.message.content);
67
+ return text ? { kind: "assistant", text } : undefined;
68
+ }
69
+ if (message.type === "result") {
70
+ const text = message.subtype === "success" ? message.result : "";
71
+ return { kind: "result", text, isError: message.is_error };
72
+ }
73
+ return undefined;
74
+ }
75
+ /** Concatenate the text blocks of an assistant message's content. */
76
+ function extractText(content) {
77
+ if (!Array.isArray(content))
78
+ return "";
79
+ const texts = [];
80
+ for (const block of content) {
81
+ if (typeof block === "object" &&
82
+ block !== null &&
83
+ block.type === "text" &&
84
+ typeof block.text === "string") {
85
+ texts.push(block.text);
86
+ }
87
+ }
88
+ return texts.join("\n");
89
+ }
90
+ //# sourceMappingURL=claudeCodeDriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeCodeDriver.js","sourceRoot":"","sources":["../../src/engines/claudeCodeDriver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import type { AgentEngine, EngineContext, EngineResult, EngineSupport, RunSpec } from "./types.js";
2
+ /**
3
+ * Driver-neutral options the engine derives from a {@link RunSpec} and hands to
4
+ * the codex harness. The default driver maps these onto the OpenAI Codex CLI;
5
+ * tests inject a fake driver and assert this mapping.
6
+ */
7
+ export interface CodexOptions {
8
+ /** The task prompt (workflow prompt + inputs). */
9
+ prompt: string;
10
+ /** The prepared sandbox working tree — the harness's cwd; edits land here. */
11
+ cwd: string;
12
+ /** Model id (resolved: per-run model, else the engine default). */
13
+ model: string;
14
+ /** API key (per-org BYO, else the runner env key). */
15
+ apiKey?: string;
16
+ /**
17
+ * The transient `CODEX_HOME` for this run — a temp dir OUTSIDE the sandbox that
18
+ * holds the `config.toml` the engine wrote (the run's MCP servers). The driver
19
+ * points the codex process at it via the `CODEX_HOME` env var.
20
+ */
21
+ codexHome: string;
22
+ /**
23
+ * Env the runner-hosted capability stdio MCP server needs (workspace id, workdir,
24
+ * emissions-file path). These are ALSO declared in the server's `config.toml` env
25
+ * table, but the driver sets them on the codex process env too so the spawned
26
+ * server inherits them even if codex doesn't forward the config env table.
27
+ */
28
+ capabilityEnv: Record<string, string>;
29
+ /** Wall-clock budget in ms; undefined means no engine-imposed limit. */
30
+ maxDurationMs?: number;
31
+ }
32
+ /** A normalized message from the harness (driver-agnostic). */
33
+ export type CodexMessage = {
34
+ kind: "assistant";
35
+ text: string;
36
+ } | {
37
+ kind: "result";
38
+ text: string;
39
+ isError: boolean;
40
+ };
41
+ /** The injectable boundary to the codex harness. Tests supply a fake. */
42
+ export type CodexDriver = (opts: CodexOptions) => AsyncIterable<CodexMessage>;
43
+ /** The runner-hosted capability MCP server the codex `config.toml` points at. */
44
+ /**
45
+ * Drives a run with the OpenAI Codex CLI against the sandbox working tree,
46
+ * mirroring the Claude Code engine. Because codex runs as an EXTERNAL process, it
47
+ * reaches the run's tools through a `config.toml` the engine writes into a transient
48
+ * `CODEX_HOME` (codex's REAL MCP discovery mechanism — it ignores `.mcp.json`):
49
+ * the run's connectors (Pillar 4) plus the runner-hosted, workspace-scoped
50
+ * capability server (Pillar 3) as a stdio MCP server. The capability server
51
+ * persists issue/knowledge emits to a file outside the tree, which this engine
52
+ * reads back — so codex reaches emit parity with native and Claude Code.
53
+ *
54
+ * Codex runs arbitrary code, so — like Claude Code — `supports()` refuses unless
55
+ * the operator enabled it (RUNNER_ENABLE_CODEX=1) AND the run is on the ephemeral
56
+ * runner profile (one throwaway container per run is the safety boundary).
57
+ */
58
+ export declare class CodexEngine implements AgentEngine {
59
+ readonly name = "codex";
60
+ private readonly drive;
61
+ private readonly capabilityServerScript;
62
+ /**
63
+ * `capabilityServerScript` is REQUIRED: the capability surface is injected,
64
+ * and a child process cannot be handed a closure, so the application must
65
+ * supply the entrypoint that builds and serves its own tools. There is no
66
+ * sensible kernel default — one would silently serve an empty surface.
67
+ */
68
+ constructor(drive: CodexDriver | undefined, capabilityServerScript: string);
69
+ supports(spec: RunSpec): EngineSupport;
70
+ run(spec: RunSpec, ctx: EngineContext): Promise<EngineResult>;
71
+ }
72
+ //# sourceMappingURL=codex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/engines/codex.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAMlG;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,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;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,+DAA+D;AAC/D,MAAM,MAAM,YAAY,GACtB;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,yEAAyE;AACzE,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,YAAY,KAAK,aAAa,CAAC,YAAY,CAAC,CAAA;AAK7E,iFAAiF;AAEjF;;;;;;;;;;;;;GAaG;AACH,qBAAa,WAAY,YAAW,WAAW;IAC7C,QAAQ,CAAC,IAAI,WAAU;IAEvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;IAE/C;;;;;OAKG;gBACS,KAAK,EAAE,WAAW,YAAqB,EAAE,sBAAsB,EAAE,MAAM;IAKnF,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa;IAahC,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;CA+FpE"}
@@ -0,0 +1,157 @@
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, writeCodexConfig } from "./connectorMcp.js";
6
+ import { readEmissions } from "./capabilityEmissions.js";
7
+ import { defaultCodexDriver } from "./codexDriver.js";
8
+ const DEFAULT_MODEL = "gpt-5-codex";
9
+ const LOG_TRUNCATE = 2000;
10
+ /** The runner-hosted capability MCP server the codex `config.toml` points at. */
11
+ /**
12
+ * Drives a run with the OpenAI Codex CLI against the sandbox working tree,
13
+ * mirroring the Claude Code engine. Because codex runs as an EXTERNAL process, it
14
+ * reaches the run's tools through a `config.toml` the engine writes into a transient
15
+ * `CODEX_HOME` (codex's REAL MCP discovery mechanism — it ignores `.mcp.json`):
16
+ * the run's connectors (Pillar 4) plus the runner-hosted, workspace-scoped
17
+ * capability server (Pillar 3) as a stdio MCP server. The capability server
18
+ * persists issue/knowledge emits to a file outside the tree, which this engine
19
+ * reads back — so codex reaches emit parity with native and Claude Code.
20
+ *
21
+ * Codex runs arbitrary code, so — like Claude Code — `supports()` refuses unless
22
+ * the operator enabled it (RUNNER_ENABLE_CODEX=1) AND the run is on the ephemeral
23
+ * runner profile (one throwaway container per run is the safety boundary).
24
+ */
25
+ export class CodexEngine {
26
+ name = "codex";
27
+ drive;
28
+ capabilityServerScript;
29
+ /**
30
+ * `capabilityServerScript` is REQUIRED: the capability surface is injected,
31
+ * and a child process cannot be handed a closure, so the application must
32
+ * supply the entrypoint that builds and serves its own tools. There is no
33
+ * sensible kernel default — one would silently serve an empty surface.
34
+ */
35
+ constructor(drive = defaultCodexDriver, capabilityServerScript) {
36
+ this.drive = drive;
37
+ this.capabilityServerScript = capabilityServerScript;
38
+ }
39
+ supports(spec) {
40
+ if (process.env.RUNNER_ENABLE_CODEX !== "1") {
41
+ return { ok: false, reason: "codex engine is disabled (set RUNNER_ENABLE_CODEX=1)" };
42
+ }
43
+ if (spec.workflow.runner?.profile !== "ephemeral") {
44
+ return {
45
+ ok: false,
46
+ reason: "codex engine requires the ephemeral runner profile (per-run container isolation)",
47
+ };
48
+ }
49
+ return { ok: true };
50
+ }
51
+ async run(spec, ctx) {
52
+ const apiKey = spec.provider.credentials?.openai ?? process.env.OPENAI_API_KEY;
53
+ const model = spec.provider.model || DEFAULT_MODEL;
54
+ // Emissions land OUTSIDE the sandbox so the bookkeeping file is never captured
55
+ // by the change set; the capability server (an external process) writes here and
56
+ // we read it back after the run.
57
+ const emissionsDir = await mkdtemp(path.join(tmpdir(), "harness-emit-"));
58
+ const emissionsFile = path.join(emissionsDir, "emissions.json");
59
+ // The codex `config.toml` carries resolved connector auth tokens, so it lives in
60
+ // a transient CODEX_HOME OUTSIDE the sandbox — it can never reach the change set —
61
+ // and is deleted in `finally` regardless.
62
+ //
63
+ // On /tmp, codex logs one harmless warning per run ("Refusing to create helper
64
+ // binaries under temporary dir /tmp") and PROCEEDS: it only skips creating the
65
+ // PATH-alias helper binaries, which we never use. Kept on /tmp deliberately —
66
+ // relocating off /tmp (e.g. to the `node` user's home in the container) would
67
+ // silence the log but flip codex into actually writing those unneeded helpers
68
+ // every run, and couple this to assumptions about the runtime image's writable
69
+ // dirs. The warning is purely cosmetic; not worth that trade.
70
+ const codexHome = await mkdtemp(path.join(tmpdir(), "harness-codex-home-"));
71
+ // The env the capability stdio server needs — set both in its config.toml env
72
+ // table AND on the codex process env (see CodexOptions.capabilityEnv) so it
73
+ // arrives regardless of whether codex forwards the config env table.
74
+ const capabilityEnv = {
75
+ HARNESS_WORKSPACE_ID: spec.workspaceId,
76
+ HARNESS_WORKDIR: spec.workdir,
77
+ HARNESS_EMISSIONS_FILE: emissionsFile,
78
+ };
79
+ const servers = {
80
+ ...connectorServers(spec.connectors),
81
+ [CAPABILITY_SERVER_NAME]: {
82
+ type: "stdio",
83
+ command: process.execPath,
84
+ args: [this.capabilityServerScript],
85
+ env: capabilityEnv,
86
+ },
87
+ };
88
+ await writeCodexConfig(codexHome, servers);
89
+ const options = {
90
+ prompt: buildPrompt(spec),
91
+ cwd: spec.workdir,
92
+ model,
93
+ codexHome,
94
+ capabilityEnv,
95
+ ...(apiKey ? { apiKey } : {}),
96
+ ...(spec.limits?.maxDurationMs !== undefined
97
+ ? { maxDurationMs: spec.limits.maxDurationMs }
98
+ : {}),
99
+ };
100
+ ctx.log(`codex: driving model ${model} in ${spec.workdir}`);
101
+ let finalText = "";
102
+ try {
103
+ for await (const message of this.drive(options)) {
104
+ if (message.kind === "assistant") {
105
+ if (message.text)
106
+ ctx.log(`codex: ${truncate(message.text, LOG_TRUNCATE)}`);
107
+ }
108
+ else {
109
+ finalText = message.text;
110
+ if (message.isError)
111
+ ctx.log("codex: harness reported an error");
112
+ }
113
+ }
114
+ }
115
+ finally {
116
+ // The token-bearing config is transient — never let it linger.
117
+ await rm(codexHome, { recursive: true, force: true });
118
+ }
119
+ // The stat's SIZE is the log's byte count, for two reasons. It is the only
120
+ // honest one — `JSON.stringify(x).length` counts UTF-16 code units, not
121
+ // bytes, and under-reports anything outside the BMP. And measuring by
122
+ // re-serializing allocates a second copy of a payload that may be large,
123
+ // to produce a number we already have on disk.
124
+ const emissionsStat = await stat(emissionsFile).then((st) => st, () => undefined);
125
+ const emissions = await readEmissions(emissionsFile);
126
+ // The engine reports whether the round trip HAPPENED, not what it carried:
127
+ // counting entries would mean knowing the shape, which is the coupling this
128
+ // is rid of. Presence plus size separates "the server never wrote" from "it
129
+ // wrote and the run emitted nothing".
130
+ ctx.log(`codex: capability emissions — file ${emissionsStat ? `present (${emissionsStat.size} bytes)` : "absent"}` +
131
+ `${emissionsStat && emissions === undefined ? ", unreadable" : ""}`);
132
+ await rm(emissionsDir, { recursive: true, force: true });
133
+ return {
134
+ text: finalText || "(no output)",
135
+ ...(emissions !== undefined ? { emissions } : {}),
136
+ };
137
+ }
138
+ }
139
+ function buildPrompt(spec) {
140
+ const inputs = JSON.stringify(spec.inputs ?? {}, null, 2);
141
+ return [
142
+ spec.workflow.prompt ?? `Execute the "${spec.workflow.name ?? spec.workflowPath}" workflow.`,
143
+ "",
144
+ "## Inputs",
145
+ "```json",
146
+ inputs,
147
+ "```",
148
+ "",
149
+ "You are running as an autonomous workflow. The current working directory is the",
150
+ "workspace — inspect and edit its files directly with your tools. When finished,",
151
+ "reply with the workflow's output as Markdown.",
152
+ ].join("\n");
153
+ }
154
+ function truncate(text, max) {
155
+ return text.length <= max ? text : `${text.slice(0, max)}…`;
156
+ }
157
+ //# sourceMappingURL=codex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/engines/codex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { CodexMessage, CodexOptions } from "./codex.js";
2
+ /**
3
+ * The default codex driver: the single integration boundary to the OpenAI Codex
4
+ * CLI, mirroring claudeCodeDriver's injectable shape. It runs codex
5
+ * non-interactively (`codex exec`) against the prepared sandbox. The engine has
6
+ * already written the run's MCP servers (connectors + the runner-hosted capability
7
+ * server) into `config.toml` inside `opts.codexHome`; pointing the child at it via
8
+ * the `CODEX_HOME` env var is how codex discovers them.
9
+ *
10
+ * Invocation (verified against the OpenAI Codex CLI reference, `codex exec`):
11
+ * - `-m <model>` — model override
12
+ * - `-C <cwd>` — workspace root
13
+ * - `--skip-git-repo-check` — the tarball sandbox is not a git repo; without this
14
+ * `codex exec` refuses to run ("not inside a trusted
15
+ * directory")
16
+ * - `-o <file>` — write only the assistant's final message to a file,
17
+ * which we read back as the result (cleaner than the
18
+ * formatted stdout transcript)
19
+ * - `--dangerously-bypass-approvals-and-sandbox`
20
+ * — auto-approve every action (incl. MCP tool calls such
21
+ * as the runner-hosted open_issue) and disable codex's
22
+ * local sandbox. Under the default `approval: never` +
23
+ * `sandbox: read-only`, codex auto-CANCELS tool calls
24
+ * it would otherwise need approval for, so the
25
+ * capability tools never fire. This is codex's
26
+ * equivalent of the Claude Code driver's
27
+ * `bypassPermissions` and is justified the same way:
28
+ * this engine only runs inside a throwaway ephemeral
29
+ * container, so the container — not codex's sandbox —
30
+ * is the safety boundary.
31
+ * - `-` — read the prompt from stdin
32
+ * The API key rides on `CODEX_API_KEY` (codex exec's dedicated, preferred key,
33
+ * which also avoids reading `~/.codex/auth.json`); `OPENAI_API_KEY` is set too for
34
+ * compatibility (codex deprioritizes it but still accepts it).
35
+ *
36
+ * Like the Claude Code driver this only runs inside a throwaway ephemeral
37
+ * container (enforced by CodexEngine.supports) — the container, not a human, is
38
+ * the safety boundary. It is exercised only when the operator sets
39
+ * RUNNER_ENABLE_CODEX=1; every test injects a fake driver, so no binary/network
40
+ * is touched in CI.
41
+ */
42
+ export declare function defaultCodexDriver(opts: CodexOptions): AsyncIterable<CodexMessage>;
43
+ //# sourceMappingURL=codexDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codexDriver.d.ts","sourceRoot":"","sources":["../../src/engines/codexDriver.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAuB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC,CAyEzF"}