@wrenai/genbi 0.0.4 → 0.0.5

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 (230) hide show
  1. package/README.md +64 -2
  2. package/dist/assets/{echarts-LTFC-ysd.js → echarts-DYM4hjvk.js} +2 -2
  3. package/dist/assets/index-A_Wrq7Ey.css +1 -0
  4. package/dist/assets/{index-Bo--QMUN.js → index-DsH129tV.js} +18 -17
  5. package/dist/index.html +2 -2
  6. package/dist-server/harness/bundle/schema.d.ts +38 -0
  7. package/dist-server/harness/bundle/schema.js +18 -10
  8. package/dist-server/harness/bundle/version.js +1 -1
  9. package/dist-server/harness/cli-args.d.ts +10 -2
  10. package/dist-server/harness/cli-args.js +24 -8
  11. package/dist-server/harness/cli.js +75 -9
  12. package/dist-server/harness/compile/cache.js +4 -4
  13. package/dist-server/harness/compile/compose-profile.d.ts +51 -14
  14. package/dist-server/harness/compile/compose-profile.js +85 -19
  15. package/dist-server/harness/compile/context-loader.d.ts +56 -0
  16. package/dist-server/harness/compile/context-loader.js +237 -0
  17. package/dist-server/harness/compile/errors.d.ts +19 -0
  18. package/dist-server/harness/compile/errors.js +35 -0
  19. package/dist-server/harness/compile/index.d.ts +4 -3
  20. package/dist-server/harness/compile/index.js +4 -3
  21. package/dist-server/harness/compile/pipeline.d.ts +15 -7
  22. package/dist-server/harness/compile/pipeline.js +62 -14
  23. package/dist-server/harness/compile/resolve-binary.d.ts +2 -2
  24. package/dist-server/harness/compile/resolve-binary.js +3 -3
  25. package/dist-server/harness/compile/types.d.ts +29 -0
  26. package/dist-server/harness/compile/warble-identity.d.ts +11 -0
  27. package/dist-server/harness/compile/warble-identity.js +13 -0
  28. package/dist-server/harness/components/ai-step.d.ts +4 -0
  29. package/dist-server/harness/components/ai-step.js +40 -0
  30. package/dist-server/harness/components/broker.d.ts +60 -0
  31. package/dist-server/harness/components/broker.js +117 -0
  32. package/dist-server/harness/components/capability-card.d.ts +98 -0
  33. package/dist-server/harness/components/capability-card.js +169 -0
  34. package/dist-server/harness/components/context.d.ts +2 -0
  35. package/dist-server/harness/components/context.js +30 -0
  36. package/dist-server/harness/components/display.d.ts +6 -0
  37. package/dist-server/harness/components/display.js +29 -0
  38. package/dist-server/harness/components/egress-judge.d.ts +12 -0
  39. package/dist-server/harness/components/egress-judge.js +27 -0
  40. package/dist-server/harness/components/egress.d.ts +142 -0
  41. package/dist-server/harness/components/egress.js +469 -0
  42. package/dist-server/harness/components/in-process.d.ts +7 -0
  43. package/dist-server/harness/components/in-process.js +245 -0
  44. package/dist-server/harness/components/normalize.d.ts +35 -0
  45. package/dist-server/harness/components/normalize.js +245 -0
  46. package/dist-server/harness/components/plan.d.ts +31 -0
  47. package/dist-server/harness/components/plan.js +184 -0
  48. package/dist-server/harness/components/report.d.ts +207 -0
  49. package/dist-server/harness/components/report.js +309 -0
  50. package/dist-server/harness/components/runner.d.ts +193 -0
  51. package/dist-server/harness/components/runner.js +492 -0
  52. package/dist-server/harness/components/semantic-guards.d.ts +20 -0
  53. package/dist-server/harness/components/semantic-guards.js +42 -0
  54. package/dist-server/harness/components/terminal-json.d.ts +18 -0
  55. package/dist-server/harness/components/terminal-json.js +82 -0
  56. package/dist-server/harness/components/usage.d.ts +14 -0
  57. package/dist-server/harness/components/usage.js +19 -0
  58. package/dist-server/harness/components/wren-access.d.ts +29 -0
  59. package/dist-server/harness/components/wren-access.js +233 -0
  60. package/dist-server/harness/components/zone-dry-run.d.ts +43 -0
  61. package/dist-server/harness/components/zone-dry-run.js +107 -0
  62. package/dist-server/harness/components/zone-gate.d.ts +76 -0
  63. package/dist-server/harness/components/zone-gate.js +144 -0
  64. package/dist-server/harness/events/index.d.ts +1 -1
  65. package/dist-server/harness/events/types.d.ts +52 -0
  66. package/dist-server/harness/index.d.ts +9 -2
  67. package/dist-server/harness/index.js +5 -1
  68. package/dist-server/harness/loop/executor.d.ts +1 -0
  69. package/dist-server/harness/loop/executor.js +37 -15
  70. package/dist-server/harness/npm-package-resolve.d.ts +2 -0
  71. package/dist-server/harness/npm-package-resolve.js +16 -4
  72. package/dist-server/harness/providers/adapters/openai-compatible.d.ts +13 -0
  73. package/dist-server/harness/providers/adapters/openai-compatible.js +17 -0
  74. package/dist-server/harness/providers/adapters/openai.d.ts +7 -0
  75. package/dist-server/harness/providers/adapters/openai.js +6 -0
  76. package/dist-server/harness/providers/adapters/pi-ai.d.ts +48 -0
  77. package/dist-server/harness/providers/adapters/pi-ai.js +518 -0
  78. package/dist-server/harness/providers/binding.d.ts +45 -4
  79. package/dist-server/harness/providers/binding.js +44 -8
  80. package/dist-server/harness/providers/index.d.ts +9 -3
  81. package/dist-server/harness/providers/index.js +10 -3
  82. package/dist-server/harness/providers/zone.d.ts +96 -0
  83. package/dist-server/harness/providers/zone.js +112 -0
  84. package/dist-server/harness/render/answering-query.d.ts +29 -0
  85. package/dist-server/harness/render/answering-query.js +62 -0
  86. package/dist-server/harness/render/envelope.d.ts +6 -0
  87. package/dist-server/harness/render/envelope.js +10 -3
  88. package/dist-server/harness/route/adapter-spec.d.ts +8 -9
  89. package/dist-server/harness/route/adapter-spec.js +13 -14
  90. package/dist-server/harness/route/codex-ask-events.js +2 -1
  91. package/dist-server/harness/route/codex-ask.d.ts +4 -0
  92. package/dist-server/harness/route/codex-ask.js +69 -30
  93. package/dist-server/harness/route/codex-host-query.d.ts +52 -0
  94. package/dist-server/harness/route/codex-host-query.js +191 -0
  95. package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
  96. package/dist-server/harness/route/codex-local-manifest.js +96 -15
  97. package/dist-server/harness/route/codex-wren-proxy.d.ts +33 -0
  98. package/dist-server/harness/route/codex-wren-proxy.js +227 -0
  99. package/dist-server/harness/route/describe.js +45 -6
  100. package/dist-server/harness/route/in-process.d.ts +22 -1
  101. package/dist-server/harness/route/in-process.js +54 -4
  102. package/dist-server/harness/route/route.js +5 -2
  103. package/dist-server/harness/route/types.d.ts +30 -1
  104. package/dist-server/harness/session/run.js +30 -10
  105. package/dist-server/harness/setup/runner.d.ts +2 -0
  106. package/dist-server/harness/setup/runner.js +38 -17
  107. package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
  108. package/dist-server/harness/setup/yaml-scalar.js +62 -0
  109. package/dist-server/server/adopt.js +1 -17
  110. package/dist-server/server/app.d.ts +1 -0
  111. package/dist-server/server/app.js +163 -14
  112. package/dist-server/server/auth-choice.d.ts +13 -1
  113. package/dist-server/server/auth-choice.js +66 -1
  114. package/dist-server/server/bin.js +177 -25
  115. package/dist-server/server/codex-local-grounding.d.ts +18 -0
  116. package/dist-server/server/codex-local-grounding.js +92 -0
  117. package/dist-server/server/codex-native-session.d.ts +25 -0
  118. package/dist-server/server/codex-native-session.js +1 -0
  119. package/dist-server/server/codex-runtime-composition.d.ts +34 -0
  120. package/dist-server/server/codex-runtime-composition.js +250 -0
  121. package/dist-server/server/codex-structured-ask.d.ts +23 -0
  122. package/dist-server/server/codex-structured-ask.js +102 -0
  123. package/dist-server/server/conn-config.js +4 -16
  124. package/dist-server/server/conversation-socket.d.ts +16 -0
  125. package/dist-server/server/conversation-socket.js +65 -0
  126. package/dist-server/server/db.d.ts +46 -0
  127. package/dist-server/server/db.js +88 -9
  128. package/dist-server/server/env-detect.d.ts +6 -0
  129. package/dist-server/server/env-detect.js +7 -0
  130. package/dist-server/server/fold.d.ts +22 -4
  131. package/dist-server/server/fold.js +37 -5
  132. package/dist-server/server/harness.d.ts +2 -2
  133. package/dist-server/server/harness.js +33 -7
  134. package/dist-server/server/interactive-terminal.d.ts +10 -6
  135. package/dist-server/server/interactive-terminal.js +33 -8
  136. package/dist-server/server/managed-wren-runtime.d.ts +116 -0
  137. package/dist-server/server/managed-wren-runtime.js +670 -0
  138. package/dist-server/server/native-artifacts.d.ts +23 -7
  139. package/dist-server/server/native-artifacts.js +43 -12
  140. package/dist-server/server/native-component-context.d.ts +19 -0
  141. package/dist-server/server/native-component-context.js +76 -0
  142. package/dist-server/server/native-component-preparation.d.ts +20 -0
  143. package/dist-server/server/native-component-preparation.js +129 -0
  144. package/dist-server/server/native-component-runtime.d.ts +36 -0
  145. package/dist-server/server/native-component-runtime.js +92 -0
  146. package/dist-server/server/native-components.d.ts +84 -0
  147. package/dist-server/server/native-components.js +160 -0
  148. package/dist-server/server/native-dispatch-process.d.ts +8 -0
  149. package/dist-server/server/native-dispatch-process.js +65 -0
  150. package/dist-server/server/native-runtime-spec.d.ts +89 -0
  151. package/dist-server/server/native-runtime-spec.js +169 -0
  152. package/dist-server/server/native-sessions.d.ts +172 -19
  153. package/dist-server/server/native-sessions.js +839 -244
  154. package/dist-server/server/native-shutdown.d.ts +6 -0
  155. package/dist-server/server/native-shutdown.js +28 -0
  156. package/dist-server/server/native-wren-home.d.ts +18 -0
  157. package/dist-server/server/native-wren-home.js +262 -0
  158. package/dist-server/server/node-pty-host.d.ts +2 -1
  159. package/dist-server/server/node-pty-host.js +3 -2
  160. package/dist-server/server/profile-registry.d.ts +139 -0
  161. package/dist-server/server/profile-registry.js +620 -0
  162. package/dist-server/server/runtime-binding.d.ts +4 -1
  163. package/dist-server/server/runtime-binding.js +50 -2
  164. package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
  165. package/dist-server/server/runtime-host/claude-component-step.js +186 -0
  166. package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
  167. package/dist-server/server/runtime-host/codex-app-server.js +239 -0
  168. package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
  169. package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
  170. package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
  171. package/dist-server/server/runtime-host/codex-component-step.js +308 -0
  172. package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
  173. package/dist-server/server/runtime-host/codex-conversation.js +295 -0
  174. package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
  175. package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
  176. package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
  177. package/dist-server/server/runtime-host/codex-events.js +66 -0
  178. package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
  179. package/dist-server/server/runtime-host/codex-policy.js +151 -0
  180. package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
  181. package/dist-server/server/runtime-host/codex-process.js +96 -0
  182. package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
  183. package/dist-server/server/runtime-host/codex-rpc.js +182 -0
  184. package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
  185. package/dist-server/server/runtime-host/codex-session.js +492 -0
  186. package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
  187. package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
  188. package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
  189. package/dist-server/server/runtime-host/codex-terminal.js +120 -0
  190. package/dist-server/server/runtime-host/index.d.ts +5 -0
  191. package/dist-server/server/runtime-host/index.js +3 -0
  192. package/dist-server/server/runtime-host/local.d.ts +29 -0
  193. package/dist-server/server/runtime-host/local.js +90 -0
  194. package/dist-server/server/runtime-host/policy.d.ts +8 -0
  195. package/dist-server/server/runtime-host/policy.js +78 -0
  196. package/dist-server/server/runtime-host/types.d.ts +52 -0
  197. package/dist-server/server/runtime-host/types.js +62 -0
  198. package/dist-server/server/structured-query-evidence.d.ts +7 -0
  199. package/dist-server/server/structured-query-evidence.js +47 -0
  200. package/dist-server/server/structured-runtime.d.ts +52 -0
  201. package/dist-server/server/structured-runtime.js +130 -0
  202. package/dist-server/server/structured-scope.d.ts +4 -0
  203. package/dist-server/server/structured-scope.js +12 -0
  204. package/dist-server/server/turn.d.ts +6 -1
  205. package/dist-server/server/turn.js +82 -25
  206. package/dist-server/server/wire-types.d.ts +67 -3
  207. package/dist-server/server/wren-profiles.js +1 -1
  208. package/managed-wren/README.md +60 -0
  209. package/managed-wren/dependency-wheels.json +282 -0
  210. package/managed-wren/manifest.json +373 -0
  211. package/managed-wren/release-inputs.json +45 -0
  212. package/managed-wren/runtime-tree.cjs +115 -0
  213. package/managed-wren/source-inputs.json +40 -0
  214. package/package.json +15 -6
  215. package/profiles/genbi-default/context/binding.yml +17 -7
  216. package/profiles/genbi-default/context/context.json +146 -0
  217. package/profiles/genbi-default/ir.golden.json +39 -43
  218. package/profiles/genbi-default/profile.yml +31 -0
  219. package/profiles/genbi-enrich-context/context/binding.yml +14 -3
  220. package/profiles/genbi-enrich-context/context/context.json +146 -0
  221. package/profiles/genbi-enrich-context/ir.golden.json +4 -5
  222. package/profiles/genbi-monitor/context/binding.yml +13 -5
  223. package/profiles/genbi-monitor/context/context.json +146 -0
  224. package/profiles/genbi-monitor/ir.golden.json +13 -7
  225. package/profiles/genbi-report/context/binding.yml +19 -0
  226. package/profiles/genbi-report/context/context.json +223 -0
  227. package/profiles/genbi-report/ir.golden.json +328 -0
  228. package/profiles/genbi-report/profile.yml +38 -0
  229. package/profiles/genbi-setup/ir.golden.json +3 -1
  230. package/dist/assets/index-Xs4q7pNo.css +0 -1
@@ -9,6 +9,7 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
9
9
  import { tmpdir } from "node:os";
10
10
  import path from "node:path";
11
11
  import { resolveHubDir, resolveWarbleBinary, runWarble } from "../harness/index.js";
12
+ import { GATEWAY_API_KEY_ENV } from "./env-detect.js";
12
13
  export class RuntimeBindingError extends Error {
13
14
  }
14
15
  /**
@@ -43,9 +44,13 @@ function bindingByTier(settings) {
43
44
  }
44
45
  return bindings;
45
46
  }
46
- /** The old single Model field is the default for every tier without a model override. */
47
+ /**
48
+ * The old single Model field is the default for every tier without a model
49
+ * override; in gateway mode the gateway's own default model comes first.
50
+ */
47
51
  export function effectiveTierModel(binding, settings) {
48
- return nonEmpty(binding.model) ?? nonEmpty(settings.apiKeyModel);
52
+ const gatewayDefault = settings.authMode === "gateway" ? nonEmpty(settings.gatewayModel) : undefined;
53
+ return nonEmpty(binding.model) ?? gatewayDefault ?? nonEmpty(settings.apiKeyModel);
49
54
  }
50
55
  /**
51
56
  * Returns a browser-safe correction for an already persisted Runtime. This is
@@ -96,8 +101,24 @@ function effectiveAdapter(binding, settings) {
96
101
  return binding.adapter;
97
102
  if (settings.authMode === "local")
98
103
  return "local";
104
+ if (settings.authMode === "gateway")
105
+ return "pi-ai";
99
106
  return settings.apiKeyAdapter ?? "anthropic";
100
107
  }
108
+ function requireGatewayProvider(tier, settings) {
109
+ const provider = nonEmpty(settings.gatewayProvider);
110
+ if (!provider)
111
+ throw new RuntimeBindingError(`A gateway provider is required for the pi-ai adapter on tier "${tier}".`);
112
+ return provider;
113
+ }
114
+ /** The pi-ai base URL is optional: without one the provider's own catalogued endpoint is used. */
115
+ function optionalGatewayBaseUrl(tier, binding, settings) {
116
+ const baseURL = nonEmpty(binding.baseURL) ?? nonEmpty(settings.gatewayBaseURL);
117
+ if (baseURL !== undefined && !absoluteHttpUrl(baseURL)) {
118
+ throw new RuntimeBindingError(`Tier "${tier}": gateway Base URL must be an absolute http(s) URL — got "${baseURL}".`);
119
+ }
120
+ return baseURL;
121
+ }
101
122
  /** Credential env vars used by the materialized in-process rows (local rows need none). */
102
123
  export function requiredInProcessCredentialEnvVars(settings) {
103
124
  const required = new Set();
@@ -107,6 +128,8 @@ export function requiredInProcessCredentialEnvVars(settings) {
107
128
  required.add("ANTHROPIC_API_KEY");
108
129
  if (adapter === "openai-compatible")
109
130
  required.add("OPENAI_API_KEY");
131
+ if (adapter === "pi-ai")
132
+ required.add(GATEWAY_API_KEY_ENV);
110
133
  }
111
134
  return [...required];
112
135
  }
@@ -131,6 +154,14 @@ export function validateRuntimeTierBindings(settings, bundleTiers) {
131
154
  const adapter = effectiveAdapter(binding, settings);
132
155
  if (adapter === "openai-compatible" || adapter === "local")
133
156
  requireBaseUrl(tier, binding, settings);
157
+ if (adapter === "pi-ai") {
158
+ // The subscription dispatchers have no pi-ai backend; gateway rows run in-process only.
159
+ if (settings.authMode === "subscription") {
160
+ throw new RuntimeBindingError(`Tier "${tier}": the pi-ai adapter runs in-process only and cannot be used with a subscription dispatcher.`);
161
+ }
162
+ requireGatewayProvider(tier, settings);
163
+ optionalGatewayBaseUrl(tier, binding, settings);
164
+ }
134
165
  }
135
166
  if (settings.authMode === "subscription" && !nonEmpty(settings.subscriptionDriverModel)) {
136
167
  throw new RuntimeBindingError("A subscription dispatcher driver model is required.");
@@ -149,12 +180,29 @@ function inProcessSpec(tier, binding, settings) {
149
180
  return { adapter: "openai-compatible", config: { model, baseURL: requireBaseUrl(tier, binding, settings), apiKey: process.env["OPENAI_API_KEY"] ?? "" } };
150
181
  case "local":
151
182
  return { adapter: "openai-compatible", config: { model, baseURL: requireBaseUrl(tier, binding, settings) } };
183
+ case "pi-ai": {
184
+ // Like openai-compatible: the env-backed key lives only in this
185
+ // transient spec, never in the persisted settings.
186
+ const baseUrl = optionalGatewayBaseUrl(tier, binding, settings);
187
+ const apiKey = nonEmpty(process.env[GATEWAY_API_KEY_ENV]);
188
+ return {
189
+ adapter: "pi-ai",
190
+ config: {
191
+ provider: requireGatewayProvider(tier, settings),
192
+ model,
193
+ ...(baseUrl !== undefined ? { baseUrl } : {}),
194
+ ...(apiKey !== undefined ? { apiKey } : {}),
195
+ },
196
+ };
197
+ }
152
198
  }
153
199
  }
154
200
  function dispatchedTierYaml(tier, binding, settings) {
155
201
  const model = requireModel(tier, binding, settings);
156
202
  const adapter = effectiveAdapter(binding, settings);
157
203
  const key = JSON.stringify(tier);
204
+ if (adapter === "pi-ai")
205
+ throw new RuntimeBindingError(`Tier "${tier}": the pi-ai adapter has no subscription dispatcher equivalent.`);
158
206
  if (adapter === "anthropic")
159
207
  return [` ${key}: ${JSON.stringify(model)}`];
160
208
  return [
@@ -0,0 +1,25 @@
1
+ import { type Options, type Query, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { StepRun, StepResponse } from "../../harness/components/runner.js";
3
+ /** Supplied only by the certified same-account process provisioner. No default spawn. */
4
+ export interface ClaudeComponentRuntime {
5
+ readonly cwd: string;
6
+ readonly executable: string;
7
+ readonly model: string;
8
+ readonly account: {
9
+ readonly email: string;
10
+ readonly tokenSource: string;
11
+ readonly subscriptionType: string;
12
+ };
13
+ readonly environment: Readonly<Record<string, string>>;
14
+ readonly spawn: NonNullable<Options["spawnClaudeCodeProcess"]>;
15
+ readonly query: (input: {
16
+ prompt: AsyncIterable<SDKUserMessage>;
17
+ options: Options;
18
+ }) => Query;
19
+ /** Revalidates approved login, isolated home, SDK/CLI/SRT and runtime generation. */
20
+ assertCurrent(): void;
21
+ /** Resolves only after all owned vendor processes and descendants are gone. */
22
+ close(): Promise<void>;
23
+ }
24
+ /** One fresh SDK query and SDK MCP server per step; never reuse the outer PTY. */
25
+ export declare function runClaudeComponentStep(runtime: ClaudeComponentRuntime, run: StepRun): Promise<StepResponse>;
@@ -0,0 +1,186 @@
1
+ import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
2
+ import { z } from "zod";
3
+ class ClaudeComponentError extends Error {
4
+ reason;
5
+ constructor(reason) {
6
+ super(`Claude component ${reason}`);
7
+ this.reason = reason;
8
+ }
9
+ }
10
+ function deny() { throw new ClaudeComponentError("protocol"); }
11
+ const record = z.record(z.string(), z.unknown());
12
+ const messageIdentity = z.object({ session_id: z.string().min(1), uuid: z.string().min(1) });
13
+ const allowedEnvironment = new Set(["PATH", "HOME", "CLAUDE_CONFIG_DIR", "TMPDIR", "LANG", "LC_ALL"]);
14
+ /** One fresh SDK query and SDK MCP server per step; never reuse the outer PTY. */
15
+ export async function runClaudeComponentStep(runtime, run) {
16
+ const { cwd, executable, model } = runtime;
17
+ const account = structuredClone(runtime.account);
18
+ const environment = Object.freeze({ ...runtime.environment });
19
+ const assertCurrent = runtime.assertCurrent.bind(runtime);
20
+ const query = runtime.query.bind(runtime);
21
+ const spawn = runtime.spawn;
22
+ const close = runtime.close.bind(runtime);
23
+ const controller = new AbortController();
24
+ const signal = AbortSignal.any([run.signal, controller.signal]);
25
+ let active = true;
26
+ let initialized = false;
27
+ let sessionId;
28
+ let pending = 0;
29
+ let calls = 0;
30
+ let failed = false;
31
+ let toolQueue = Promise.resolve();
32
+ let releaseInput;
33
+ const inputReady = new Promise((resolve) => { releaseInput = resolve; });
34
+ const check = () => { if (!active || failed)
35
+ deny(); if (signal.aborted)
36
+ throw new ClaudeComponentError("cancelled"); assertCurrent(); };
37
+ let session;
38
+ const timeout = setTimeout(() => controller.abort(), 120_000);
39
+ const abort = new Promise((_, reject) => {
40
+ signal.addEventListener("abort", () => reject(new ClaudeComponentError("cancelled")), { once: true });
41
+ });
42
+ void abort.catch(() => { });
43
+ const wait = (value) => Promise.race([value, abort]);
44
+ const tools = Object.keys(run.tools).map((name) => ({ name, wire: `mcp__component__${name}` }));
45
+ try {
46
+ check();
47
+ if (!cwd.startsWith("/") || !executable.startsWith("/") || !model || !account.email || !account.tokenSource || !account.subscriptionType
48
+ || !environment.HOME || !environment.CLAUDE_CONFIG_DIR || Object.keys(environment).some((name) => !allowedEnvironment.has(name)))
49
+ deny();
50
+ const sdkTools = tools.map(({ name }) => {
51
+ const schema = z.fromJSONSchema(run.toolSchemas[name] ?? deny());
52
+ if (!(schema instanceof z.ZodObject))
53
+ deny();
54
+ return tool(name, run.toolDescriptions[name] ?? name, schema.shape, async (input) => {
55
+ check();
56
+ if (!initialized || ++calls > 32)
57
+ deny();
58
+ pending++;
59
+ const result = toolQueue.then(async () => {
60
+ check();
61
+ const parsed = schema.parse(input);
62
+ if (Buffer.byteLength(JSON.stringify(parsed)) > 65_536)
63
+ deny();
64
+ const output = await wait(run.tools[name](parsed));
65
+ check();
66
+ const text = JSON.stringify(output);
67
+ if (text === undefined || Buffer.byteLength(text) > 1_048_000)
68
+ deny();
69
+ return { content: [{ type: "text", text }] };
70
+ });
71
+ toolQueue = result.catch(() => { failed = true; controller.abort(); });
72
+ try {
73
+ return await result;
74
+ }
75
+ finally {
76
+ pending--;
77
+ }
78
+ });
79
+ });
80
+ async function* prompt() {
81
+ try {
82
+ await wait(inputReady);
83
+ check();
84
+ yield { type: "user", session_id: "", parent_tool_use_id: null,
85
+ message: { role: "user", content: JSON.stringify({ request: run.request, input: run.input, consumes: run.consumes }) } };
86
+ // Keep the SDK input channel open while governed tool exchanges run.
87
+ await wait(new Promise(() => { }));
88
+ }
89
+ catch {
90
+ if (!signal.aborted) {
91
+ failed = true;
92
+ controller.abort();
93
+ }
94
+ }
95
+ }
96
+ session = query({ prompt: prompt(), options: {
97
+ abortController: controller, cwd, model, pathToClaudeCodeExecutable: executable,
98
+ spawnClaudeCodeProcess: spawn, env: { ...environment },
99
+ tools: [], allowedTools: tools.map(({ wire }) => wire),
100
+ mcpServers: { component: createSdkMcpServer({ name: "component", tools: sdkTools }) },
101
+ canUseTool: async () => { failed = true; controller.abort(); return { behavior: "deny", message: "Undeclared tool is unavailable" }; },
102
+ permissionMode: "default", settingSources: [], plugins: [], agents: {}, additionalDirectories: [],
103
+ persistSession: false, enableFileCheckpointing: false, maxTurns: 12,
104
+ systemPrompt: [run.brief, run.prompt].filter(Boolean).join("\n\n"),
105
+ extraArgs: { "strict-mcp-config": null, "disable-slash-commands": null },
106
+ } });
107
+ const observed = z.object({ email: z.literal(account.email), tokenSource: z.literal(account.tokenSource), subscriptionType: z.literal(account.subscriptionType), apiKeySource: z.literal("none") }).parse(await wait(session.accountInfo()));
108
+ if (!observed.email)
109
+ deny();
110
+ check();
111
+ releaseInput();
112
+ const seen = new Set();
113
+ while (true) {
114
+ const next = await wait(session.next());
115
+ check();
116
+ if (next.done)
117
+ deny();
118
+ const message = record.parse(next.value);
119
+ if (Buffer.byteLength(JSON.stringify(message)) > 1_048_576)
120
+ deny();
121
+ const identity = messageIdentity.parse(message);
122
+ if (seen.has(identity.uuid) || seen.size >= 512)
123
+ deny();
124
+ seen.add(identity.uuid);
125
+ if (message.type === "system" && message.subtype === "init") {
126
+ if (initialized)
127
+ deny();
128
+ const init = z.object({ apiKeySource: z.literal("none"), claude_code_version: z.literal("2.1.259"), cwd: z.literal(cwd), model: z.literal(model),
129
+ tools: z.array(z.string()), mcp_servers: z.array(z.object({ name: z.literal("component"), status: z.literal("connected") })),
130
+ skills: z.array(z.never()), plugins: z.array(z.never()), agents: z.array(z.never()).optional(),
131
+ slash_commands: z.array(z.never()), permissionMode: z.literal("default"),
132
+ }).parse(message);
133
+ if (init.tools.length !== tools.length || new Set(init.tools).size !== tools.length || init.tools.some((name) => !tools.some(({ wire }) => wire === name)))
134
+ deny();
135
+ if (init.mcp_servers.length !== 1)
136
+ deny();
137
+ initialized = true;
138
+ sessionId = identity.session_id;
139
+ continue;
140
+ }
141
+ if (!initialized || identity.session_id !== sessionId)
142
+ deny();
143
+ if (message.type === "result") {
144
+ const result = z.object({ subtype: z.literal("success"), is_error: z.literal(false), result: z.string().max(1_048_576),
145
+ permission_denials: z.array(z.never()), modelUsage: z.record(z.string(), z.object({ webSearchRequests: z.literal(0), inputTokens: z.number().int().nonnegative(), outputTokens: z.number().int().nonnegative(), cacheReadInputTokens: z.number().int().nonnegative(), cacheCreationInputTokens: z.number().int().nonnegative() })), usage: z.object({ input_tokens: z.number().int().nonnegative(), output_tokens: z.number().int().nonnegative() }),
146
+ }).parse(message);
147
+ if (pending || failed || Object.keys(result.modelUsage).length !== 1 || !Object.hasOwn(result.modelUsage, model))
148
+ deny();
149
+ await wait(session.accountInfo()).then((value) => z.object({ email: z.literal(account.email), tokenSource: z.literal(account.tokenSource), subscriptionType: z.literal(account.subscriptionType), apiKeySource: z.literal("none") }).parse(value));
150
+ check();
151
+ const totals = result.modelUsage[model];
152
+ return { value: result.result, usage: { inputTokens: totals.inputTokens + totals.cacheReadInputTokens + totals.cacheCreationInputTokens, outputTokens: totals.outputTokens } };
153
+ }
154
+ if (message.type !== "assistant" && message.type !== "user")
155
+ deny();
156
+ if (message.parent_tool_use_id !== null)
157
+ deny();
158
+ const content = z.object({ content: z.union([z.string(), z.array(record)]) }).parse(message.message).content;
159
+ if (Array.isArray(content))
160
+ for (const block of content) {
161
+ if (block.type === "tool_use") {
162
+ if (!tools.some(({ wire }) => wire === block.name))
163
+ deny();
164
+ }
165
+ else if (!["text", "thinking", "redacted_thinking", "tool_result"].includes(String(block.type)))
166
+ deny();
167
+ }
168
+ }
169
+ }
170
+ catch (error) {
171
+ throw new ClaudeComponentError(error instanceof ClaudeComponentError ? error.reason : "protocol");
172
+ }
173
+ finally {
174
+ active = false;
175
+ clearTimeout(timeout);
176
+ controller.abort();
177
+ releaseInput();
178
+ // The provisioner owns process-tree cleanup; iterator cleanup alone is insufficient.
179
+ try {
180
+ await close();
181
+ }
182
+ catch {
183
+ throw new ClaudeComponentError("cleanup");
184
+ }
185
+ }
186
+ }
@@ -0,0 +1,75 @@
1
+ import { type ManagedWrenRuntimeRecord } from "../managed-wren-runtime.js";
2
+ import { type NativeRuntimeSpec } from "../native-runtime-spec.js";
3
+ import type { CodexWrenHome } from "../native-wren-home.js";
4
+ import { type CodexIdentityDiagnostic } from "./codex-compatibility.js";
5
+ import { CodexSession } from "./codex-session.js";
6
+ import type { CodexComponentPolicy } from "./codex-component-step.js";
7
+ import type { RpcTransport } from "./codex-rpc.js";
8
+ import type { CodexEvent } from "./codex-events.js";
9
+ import type { RuntimeBackendProbeResult, RuntimeBackendReasonCode } from "./types.js";
10
+ import type { CodexSessionTools } from "./codex-direct-tools.js";
11
+ type Reason = RuntimeBackendReasonCode<"codex-app-server">;
12
+ export declare class CodexBackendError extends Error {
13
+ readonly code: Reason;
14
+ /** Host-only drift detail for `codex_dependencies_drifted`; never part of the readiness text. */
15
+ readonly drift?: readonly string[];
16
+ constructor(code: Reason, drift?: readonly string[]);
17
+ }
18
+ export interface CodexBackendOptions {
19
+ /** Captured at BFF composition, not discovered from command/session input. */
20
+ readonly executable: string;
21
+ readonly source: string;
22
+ readonly packageRoot?: string;
23
+ readonly runtimeRoot?: string;
24
+ }
25
+ export interface CodexLaunchPermit {
26
+ readonly runtime: ManagedWrenRuntimeRecord;
27
+ /** Recheck immediately before each host-side materialization/persistence step. */
28
+ assertActive(): void;
29
+ /** Abandon before materialization or open; never deletes any runtime bytes. */
30
+ release(): void;
31
+ }
32
+ /** Disabled unless the package contains an approved runtime AND certified row. */
33
+ export declare class CodexAppServerBackend {
34
+ private permits;
35
+ private leases;
36
+ private sessions;
37
+ private steps;
38
+ private stopped;
39
+ private shutdownPromise?;
40
+ private readonly options;
41
+ constructor(options: CodexBackendOptions);
42
+ private resolve;
43
+ private inspect;
44
+ probe(): Promise<RuntimeBackendProbeResult<"codex-app-server"> & {
45
+ readonly diagnostic: CodexIdentityDiagnostic;
46
+ }>;
47
+ /** Call BEFORE durable rows or workspace materialization in Phase 5. */
48
+ prepareLaunch(): CodexLaunchPermit;
49
+ /** Include these generations in the provisioner's cleanup retain set. */
50
+ retainedManifestDigests(): readonly string[];
51
+ private assertRecord;
52
+ open(permit: CodexLaunchPermit, input: {
53
+ readonly model?: string;
54
+ readonly tools?: CodexSessionTools;
55
+ readonly spec: NativeRuntimeSpec;
56
+ readonly wrenHome: CodexWrenHome;
57
+ /** Existing host-owned binding/generation guard, never a browser callback. */
58
+ readonly assertScopeActive: () => void;
59
+ readonly onEvent: (event: CodexEvent) => void;
60
+ }): Promise<CodexSession>;
61
+ /** A fresh, governed transport per component step; same certification gate as the outer session. */
62
+ openStep(permit: CodexLaunchPermit, input: {
63
+ readonly spec: NativeRuntimeSpec;
64
+ readonly wrenHome: CodexWrenHome;
65
+ readonly model: string;
66
+ readonly accountEmail: string;
67
+ readonly signal: AbortSignal;
68
+ readonly assertScopeActive: () => void;
69
+ }): {
70
+ transport: RpcTransport;
71
+ policy: CodexComponentPolicy;
72
+ };
73
+ shutdown(): Promise<void>;
74
+ }
75
+ export {};
@@ -0,0 +1,239 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { arch, platform } from "node:os";
3
+ import { ManagedWrenRuntimeError, resolveManagedWrenRuntime } from "../managed-wren-runtime.js";
4
+ import { attestNativeExecutable, assertNativeExecutableIdentity } from "../native-runtime-spec.js";
5
+ import { CODEX_CERTIFIED_ROWS, codexCertificationSchema, evaluateCodexIdentity } from "./codex-compatibility.js";
6
+ import { buildCodexSessionPolicy } from "./codex-policy.js";
7
+ import { spawnCodexTransport } from "./codex-process.js";
8
+ import { CodexSession } from "./codex-session.js";
9
+ import { CodexRpcError } from "./codex-rpc.js";
10
+ import { runtimeNotReady } from "./policy.js";
11
+ export class CodexBackendError extends Error {
12
+ code;
13
+ /** Host-only drift detail for `codex_dependencies_drifted`; never part of the readiness text. */
14
+ drift;
15
+ constructor(code, drift) {
16
+ super(runtimeNotReady("codex-app-server", "unavailable", code).message);
17
+ this.code = code;
18
+ if (drift)
19
+ this.drift = drift;
20
+ }
21
+ }
22
+ /** Disabled unless the package contains an approved runtime AND certified row. */
23
+ export class CodexAppServerBackend {
24
+ permits = new WeakMap();
25
+ leases = new Set();
26
+ sessions = new Set();
27
+ steps = new Set();
28
+ stopped = false;
29
+ shutdownPromise;
30
+ options;
31
+ constructor(options) { this.options = Object.freeze({ ...options }); }
32
+ resolve() {
33
+ try {
34
+ return resolveManagedWrenRuntime(this.options);
35
+ }
36
+ catch (error) {
37
+ throw new CodexBackendError(error instanceof ManagedWrenRuntimeError ? error.code : "codex_wren_runtime_unprovisioned");
38
+ }
39
+ }
40
+ inspect() {
41
+ if (this.stopped)
42
+ throw new CodexBackendError("codex_app_server_unreachable");
43
+ if (platform() !== "darwin" || arch() !== "arm64")
44
+ throw new CodexBackendError("runtime_platform_unsupported");
45
+ const runtime = this.resolve(); // no fetch, install, mkdir, workspace or session side effects
46
+ let vendor;
47
+ try {
48
+ vendor = attestNativeExecutable("vendor", this.options.executable);
49
+ }
50
+ catch {
51
+ throw new CodexBackendError("codex_cli_missing");
52
+ }
53
+ // Hash before executing --version. An unknown executable never gets a probe
54
+ // process merely because the caller labels it Codex.
55
+ const row = CODEX_CERTIFIED_ROWS.map((value) => codexCertificationSchema.safeParse(value))
56
+ .find((value) => value.success && value.data.executableSha256 === vendor.digest.slice(7) && value.data.source === this.options.source);
57
+ if (!row?.success)
58
+ throw new CodexBackendError("codex_identity_uncertified");
59
+ let versionOutput;
60
+ try {
61
+ versionOutput = execFileSync(vendor.executable, ["--version"], { encoding: "utf8", timeout: 5_000, maxBuffer: 1024,
62
+ env: { PATH: "/usr/bin:/bin" }, stdio: ["ignore", "pipe", "ignore"] });
63
+ assertNativeExecutableIdentity(vendor);
64
+ }
65
+ catch {
66
+ throw new CodexBackendError("codex_app_server_unreachable");
67
+ }
68
+ // The exact executable digest binds the generated protocol schema and
69
+ // behavior evidence in its reviewed row; never generate schema files during
70
+ // readiness or equate an initialize acknowledgement with certification.
71
+ const result = evaluateCodexIdentity({ platform: "darwin-arm64", versionOutput,
72
+ executableSha256: vendor.digest.slice(7), source: this.options.source,
73
+ protocolSha256: row.data.protocolSha256, contracts: row.data.contracts }, CODEX_CERTIFIED_ROWS);
74
+ if (result.readiness.state !== "ready")
75
+ throw new CodexBackendError(result.readiness.code, result.diagnostic.drift);
76
+ return { runtime, vendor, result };
77
+ }
78
+ async probe() {
79
+ try {
80
+ return this.inspect().result;
81
+ }
82
+ catch (error) {
83
+ const code = error instanceof CodexBackendError ? error.code : "runtime_probe_failed";
84
+ const drift = error instanceof CodexBackendError ? error.drift : undefined;
85
+ return { readiness: runtimeNotReady("codex-app-server", code.startsWith("codex_wren_") ? "unprovisioned" : "incompatible", code),
86
+ diagnostic: { phase: code.startsWith("codex_wren_") ? "provisioning" : "identity", ...(drift ? { drift } : {}) } };
87
+ }
88
+ }
89
+ /** Call BEFORE durable rows or workspace materialization in Phase 5. */
90
+ prepareLaunch() {
91
+ const { runtime, vendor } = this.inspect();
92
+ const state = { runtime, vendor, consumed: false, released: false };
93
+ const permit = Object.freeze({ runtime, assertActive: () => {
94
+ if (state.released || state.consumed || this.stopped)
95
+ throw new CodexBackendError("runtime_policy_unavailable");
96
+ this.assertRecord(state);
97
+ }, release: () => {
98
+ if (state.consumed)
99
+ return;
100
+ state.released = true;
101
+ this.leases.delete(state);
102
+ } });
103
+ this.permits.set(permit, state);
104
+ this.leases.add(state);
105
+ return permit;
106
+ }
107
+ /** Include these generations in the provisioner's cleanup retain set. */
108
+ retainedManifestDigests() { return Object.freeze([...new Set([...this.leases].map((state) => state.runtime.manifest_digest))]); }
109
+ assertRecord(state) {
110
+ const current = this.resolve();
111
+ if (JSON.stringify(current) !== JSON.stringify(state.runtime))
112
+ throw new CodexBackendError("codex_wren_closure_mismatch");
113
+ try {
114
+ assertNativeExecutableIdentity(state.vendor);
115
+ }
116
+ catch {
117
+ throw new CodexBackendError("codex_identity_uncertified");
118
+ }
119
+ return current;
120
+ }
121
+ async open(permit, input) {
122
+ const state = this.permits.get(permit);
123
+ if (!state || state.consumed || state.released || this.stopped)
124
+ throw new CodexBackendError("runtime_policy_unavailable");
125
+ state.consumed = true;
126
+ const revalidate = () => {
127
+ const current = this.assertRecord(state);
128
+ if (input.spec.executables.vendor?.digest !== state.vendor.digest || input.spec.executables.vendor?.executable !== state.vendor.executable)
129
+ throw new CodexBackendError("codex_identity_uncertified");
130
+ try {
131
+ input.assertScopeActive();
132
+ }
133
+ catch {
134
+ throw new CodexBackendError("runtime_policy_unavailable");
135
+ }
136
+ try {
137
+ return buildCodexSessionPolicy(input.spec, current, input.wrenHome, input.tools !== undefined, input.model);
138
+ }
139
+ catch {
140
+ throw new CodexBackendError("codex_wren_child_env_invalid");
141
+ }
142
+ };
143
+ let spawned = false;
144
+ try {
145
+ const policy = revalidate();
146
+ const transport = spawnCodexTransport({ executable: state.vendor.executable, args: policy.args, cwd: policy.cwd, env: { ...policy.environment } });
147
+ spawned = true;
148
+ const session = await CodexSession.connect(transport, policy, () => {
149
+ if (this.stopped || JSON.stringify(revalidate()) !== JSON.stringify(policy))
150
+ throw new CodexBackendError("runtime_policy_unavailable");
151
+ }, input.onEvent, (opening) => {
152
+ // Own the connection before initialization: shutdown must also cancel
153
+ // a peer that never acknowledges the handshake.
154
+ this.sessions.add(opening);
155
+ const originalClose = opening.close.bind(opening);
156
+ opening.close = async () => { await originalClose(); this.sessions.delete(opening); this.leases.delete(state); };
157
+ }, input.tools);
158
+ if (this.stopped) {
159
+ await session.close();
160
+ this.leases.delete(state);
161
+ throw new CodexBackendError("codex_app_server_unreachable");
162
+ }
163
+ return session;
164
+ }
165
+ catch (error) {
166
+ // Pre-spawn failures have no process to retain. A post-spawn cleanup
167
+ // failure conservatively pins the generation for operator reconciliation.
168
+ if (!spawned || !(error instanceof CodexRpcError && error.reason === "cleanup"))
169
+ this.leases.delete(state);
170
+ if (error instanceof CodexBackendError)
171
+ throw error;
172
+ if (error instanceof CodexRpcError && error.reason === "cleanup")
173
+ throw new CodexBackendError("codex_app_server_cleanup_failed");
174
+ if (error instanceof CodexRpcError && error.reason === "permission")
175
+ throw new CodexBackendError("codex_permission_profile_unavailable");
176
+ throw new CodexBackendError("codex_app_server_protocol_incompatible");
177
+ }
178
+ }
179
+ /** A fresh, governed transport per component step; same certification gate as the outer session. */
180
+ openStep(permit, input) {
181
+ const state = this.permits.get(permit);
182
+ if (!state || state.consumed || state.released || this.stopped)
183
+ throw new CodexBackendError("runtime_policy_unavailable");
184
+ const checkLive = () => {
185
+ input.signal.throwIfAborted();
186
+ input.assertScopeActive();
187
+ if (this.stopped)
188
+ throw new CodexBackendError("runtime_policy_unavailable");
189
+ };
190
+ const check = () => {
191
+ checkLive();
192
+ const runtime = this.assertRecord(state);
193
+ if (input.spec.executables.vendor?.digest !== state.vendor.digest || input.spec.executables.vendor?.executable !== state.vendor.executable)
194
+ throw new CodexBackendError("codex_identity_uncertified");
195
+ return buildCodexSessionPolicy(input.spec, runtime, input.wrenHome, true, input.model);
196
+ };
197
+ const launch = check();
198
+ state.consumed = true;
199
+ let raw;
200
+ try {
201
+ raw = spawnCodexTransport({ executable: state.vendor.executable, args: launch.args, cwd: launch.cwd, env: { ...launch.environment } });
202
+ }
203
+ catch (error) {
204
+ this.leases.delete(state);
205
+ throw error;
206
+ }
207
+ let closing;
208
+ const transport = { listen: raw.listen.bind(raw), write: raw.write.bind(raw), close: () => closing ??= (async () => {
209
+ await raw.close();
210
+ this.steps.delete(transport);
211
+ this.leases.delete(state);
212
+ input.signal.removeEventListener("abort", abort);
213
+ })() };
214
+ const abort = () => { void transport.close().catch(() => { }); };
215
+ this.steps.add(transport);
216
+ input.signal.addEventListener("abort", abort, { once: true });
217
+ if (input.signal.aborted)
218
+ abort();
219
+ return { transport, policy: { cwd: launch.cwd, codexHome: launch.codexHome, permissionProfile: launch.profile,
220
+ model: input.model, accountEmail: input.accountEmail, configuration: launch.configuration,
221
+ assertLive: checkLive,
222
+ assertCurrent() { if (JSON.stringify(check()) !== JSON.stringify(launch))
223
+ throw new CodexBackendError("runtime_policy_unavailable"); } } };
224
+ }
225
+ shutdown() {
226
+ this.stopped = true;
227
+ this.shutdownPromise ??= (async () => {
228
+ const results = await Promise.allSettled([...this.sessions, ...this.steps].map((session) => session.close()));
229
+ for (const state of this.leases)
230
+ if (!state.consumed) {
231
+ state.released = true;
232
+ this.leases.delete(state);
233
+ }
234
+ if (results.some((result) => result.status === "rejected"))
235
+ throw new CodexBackendError("codex_app_server_cleanup_failed");
236
+ })();
237
+ return this.shutdownPromise;
238
+ }
239
+ }