@wrenai/genbi 0.0.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 (286) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +141 -0
  3. package/bin/genbi.mjs +31 -0
  4. package/dist/assets/echarts-LTFC-ysd.js +40 -0
  5. package/dist/assets/index-Bo--QMUN.js +441 -0
  6. package/dist/assets/index-Xs4q7pNo.css +1 -0
  7. package/dist/index.html +14 -0
  8. package/dist/wrenai_logo.png +0 -0
  9. package/dist/wrenai_logo_white.png +0 -0
  10. package/dist-server/harness/auth/detect.d.ts +18 -0
  11. package/dist-server/harness/auth/detect.js +37 -0
  12. package/dist-server/harness/auth/index.d.ts +4 -0
  13. package/dist-server/harness/auth/index.js +2 -0
  14. package/dist-server/harness/auth/probe.d.ts +18 -0
  15. package/dist-server/harness/auth/probe.js +52 -0
  16. package/dist-server/harness/auth/types.d.ts +42 -0
  17. package/dist-server/harness/auth/types.js +9 -0
  18. package/dist-server/harness/bundle/loader.d.ts +32 -0
  19. package/dist-server/harness/bundle/loader.js +43 -0
  20. package/dist-server/harness/bundle/schema.d.ts +199 -0
  21. package/dist-server/harness/bundle/schema.js +127 -0
  22. package/dist-server/harness/bundle/version.d.ts +10 -0
  23. package/dist-server/harness/bundle/version.js +43 -0
  24. package/dist-server/harness/capability/gate.d.ts +6 -0
  25. package/dist-server/harness/capability/gate.js +29 -0
  26. package/dist-server/harness/capability/registry.d.ts +6 -0
  27. package/dist-server/harness/capability/registry.js +17 -0
  28. package/dist-server/harness/cli-args.d.ts +123 -0
  29. package/dist-server/harness/cli-args.js +247 -0
  30. package/dist-server/harness/cli.d.ts +2 -0
  31. package/dist-server/harness/cli.js +151 -0
  32. package/dist-server/harness/compile/cache.d.ts +23 -0
  33. package/dist-server/harness/compile/cache.js +109 -0
  34. package/dist-server/harness/compile/compose-profile.d.ts +34 -0
  35. package/dist-server/harness/compile/compose-profile.js +95 -0
  36. package/dist-server/harness/compile/errors.d.ts +22 -0
  37. package/dist-server/harness/compile/errors.js +41 -0
  38. package/dist-server/harness/compile/fingerprint.d.ts +22 -0
  39. package/dist-server/harness/compile/fingerprint.js +84 -0
  40. package/dist-server/harness/compile/index.d.ts +8 -0
  41. package/dist-server/harness/compile/index.js +7 -0
  42. package/dist-server/harness/compile/pipeline.d.ts +66 -0
  43. package/dist-server/harness/compile/pipeline.js +176 -0
  44. package/dist-server/harness/compile/resolve-binary.d.ts +55 -0
  45. package/dist-server/harness/compile/resolve-binary.js +133 -0
  46. package/dist-server/harness/compile/types.d.ts +107 -0
  47. package/dist-server/harness/compile/types.js +1 -0
  48. package/dist-server/harness/compile/warble-identity.d.ts +12 -0
  49. package/dist-server/harness/compile/warble-identity.js +33 -0
  50. package/dist-server/harness/compliance/gate.d.ts +35 -0
  51. package/dist-server/harness/compliance/gate.js +48 -0
  52. package/dist-server/harness/compliance/index.d.ts +2 -0
  53. package/dist-server/harness/compliance/index.js +1 -0
  54. package/dist-server/harness/compliance/types.d.ts +16 -0
  55. package/dist-server/harness/compliance/types.js +1 -0
  56. package/dist-server/harness/events/emitter.d.ts +34 -0
  57. package/dist-server/harness/events/emitter.js +25 -0
  58. package/dist-server/harness/events/index.d.ts +4 -0
  59. package/dist-server/harness/events/index.js +2 -0
  60. package/dist-server/harness/events/sse.d.ts +20 -0
  61. package/dist-server/harness/events/sse.js +48 -0
  62. package/dist-server/harness/events/types.d.ts +131 -0
  63. package/dist-server/harness/events/types.js +1 -0
  64. package/dist-server/harness/exec/errors.d.ts +16 -0
  65. package/dist-server/harness/exec/errors.js +29 -0
  66. package/dist-server/harness/exec/index.d.ts +5 -0
  67. package/dist-server/harness/exec/index.js +2 -0
  68. package/dist-server/harness/exec/local.d.ts +32 -0
  69. package/dist-server/harness/exec/local.js +134 -0
  70. package/dist-server/harness/exec/policy.d.ts +12 -0
  71. package/dist-server/harness/exec/policy.js +1 -0
  72. package/dist-server/harness/exec/types.d.ts +63 -0
  73. package/dist-server/harness/exec/types.js +1 -0
  74. package/dist-server/harness/guardrails/index.d.ts +2 -0
  75. package/dist-server/harness/guardrails/index.js +1 -0
  76. package/dist-server/harness/guardrails/policy.d.ts +45 -0
  77. package/dist-server/harness/guardrails/policy.js +57 -0
  78. package/dist-server/harness/index.d.ts +37 -0
  79. package/dist-server/harness/index.js +19 -0
  80. package/dist-server/harness/loop/errors.d.ts +53 -0
  81. package/dist-server/harness/loop/errors.js +72 -0
  82. package/dist-server/harness/loop/executor.d.ts +123 -0
  83. package/dist-server/harness/loop/executor.js +380 -0
  84. package/dist-server/harness/loop/guard.d.ts +16 -0
  85. package/dist-server/harness/loop/guard.js +1 -0
  86. package/dist-server/harness/loop/index.d.ts +5 -0
  87. package/dist-server/harness/loop/index.js +3 -0
  88. package/dist-server/harness/native-producer/index.d.ts +109 -0
  89. package/dist-server/harness/native-producer/index.js +302 -0
  90. package/dist-server/harness/npm-package-resolve.d.ts +28 -0
  91. package/dist-server/harness/npm-package-resolve.js +47 -0
  92. package/dist-server/harness/providers/adapters/anthropic.d.ts +7 -0
  93. package/dist-server/harness/providers/adapters/anthropic.js +6 -0
  94. package/dist-server/harness/providers/adapters/mock.d.ts +4 -0
  95. package/dist-server/harness/providers/adapters/mock.js +5 -0
  96. package/dist-server/harness/providers/adapters/openai-compatible.d.ts +10 -0
  97. package/dist-server/harness/providers/adapters/openai-compatible.js +10 -0
  98. package/dist-server/harness/providers/binding.d.ts +23 -0
  99. package/dist-server/harness/providers/binding.js +13 -0
  100. package/dist-server/harness/providers/errors.d.ts +6 -0
  101. package/dist-server/harness/providers/errors.js +12 -0
  102. package/dist-server/harness/providers/index.d.ts +18 -0
  103. package/dist-server/harness/providers/index.js +22 -0
  104. package/dist-server/harness/providers/registry.d.ts +16 -0
  105. package/dist-server/harness/providers/registry.js +24 -0
  106. package/dist-server/harness/render/envelope.d.ts +145 -0
  107. package/dist-server/harness/render/envelope.js +428 -0
  108. package/dist-server/harness/render/errors.d.ts +33 -0
  109. package/dist-server/harness/render/errors.js +46 -0
  110. package/dist-server/harness/render/index.d.ts +5 -0
  111. package/dist-server/harness/render/index.js +3 -0
  112. package/dist-server/harness/render/validate.d.ts +13 -0
  113. package/dist-server/harness/render/validate.js +88 -0
  114. package/dist-server/harness/route/adapter-spec.d.ts +40 -0
  115. package/dist-server/harness/route/adapter-spec.js +61 -0
  116. package/dist-server/harness/route/agent-sdk-cli.d.ts +30 -0
  117. package/dist-server/harness/route/agent-sdk-cli.js +86 -0
  118. package/dist-server/harness/route/agent-sdk-manifest.d.ts +31 -0
  119. package/dist-server/harness/route/agent-sdk-manifest.js +56 -0
  120. package/dist-server/harness/route/artifact-content.d.ts +53 -0
  121. package/dist-server/harness/route/artifact-content.js +83 -0
  122. package/dist-server/harness/route/chat-event-mapper.d.ts +95 -0
  123. package/dist-server/harness/route/chat-event-mapper.js +164 -0
  124. package/dist-server/harness/route/codex-ask-events.d.ts +25 -0
  125. package/dist-server/harness/route/codex-ask-events.js +395 -0
  126. package/dist-server/harness/route/codex-ask.d.ts +15 -0
  127. package/dist-server/harness/route/codex-ask.js +242 -0
  128. package/dist-server/harness/route/codex-local-cli.d.ts +20 -0
  129. package/dist-server/harness/route/codex-local-cli.js +78 -0
  130. package/dist-server/harness/route/codex-local-manifest.d.ts +22 -0
  131. package/dist-server/harness/route/codex-local-manifest.js +180 -0
  132. package/dist-server/harness/route/describe.d.ts +54 -0
  133. package/dist-server/harness/route/describe.js +84 -0
  134. package/dist-server/harness/route/dispatched.d.ts +138 -0
  135. package/dist-server/harness/route/dispatched.js +339 -0
  136. package/dist-server/harness/route/in-process.d.ts +43 -0
  137. package/dist-server/harness/route/in-process.js +106 -0
  138. package/dist-server/harness/route/index.d.ts +19 -0
  139. package/dist-server/harness/route/index.js +12 -0
  140. package/dist-server/harness/route/profile-source.d.ts +37 -0
  141. package/dist-server/harness/route/profile-source.js +76 -0
  142. package/dist-server/harness/route/route.d.ts +33 -0
  143. package/dist-server/harness/route/route.js +114 -0
  144. package/dist-server/harness/route/tier-binding.d.ts +36 -0
  145. package/dist-server/harness/route/tier-binding.js +63 -0
  146. package/dist-server/harness/route/types.d.ts +286 -0
  147. package/dist-server/harness/route/types.js +1 -0
  148. package/dist-server/harness/session/errors.d.ts +4 -0
  149. package/dist-server/harness/session/errors.js +7 -0
  150. package/dist-server/harness/session/gate.d.ts +11 -0
  151. package/dist-server/harness/session/gate.js +13 -0
  152. package/dist-server/harness/session/index.d.ts +5 -0
  153. package/dist-server/harness/session/index.js +3 -0
  154. package/dist-server/harness/session/run.d.ts +60 -0
  155. package/dist-server/harness/session/run.js +333 -0
  156. package/dist-server/harness/session/types.d.ts +29 -0
  157. package/dist-server/harness/session/types.js +1 -0
  158. package/dist-server/harness/setup/codex-events.d.ts +12 -0
  159. package/dist-server/harness/setup/codex-events.js +108 -0
  160. package/dist-server/harness/setup/runner.d.ts +497 -0
  161. package/dist-server/harness/setup/runner.js +1314 -0
  162. package/dist-server/harness/tools/errors.d.ts +107 -0
  163. package/dist-server/harness/tools/errors.js +141 -0
  164. package/dist-server/harness/tools/index.d.ts +13 -0
  165. package/dist-server/harness/tools/index.js +8 -0
  166. package/dist-server/harness/tools/mcp.d.ts +26 -0
  167. package/dist-server/harness/tools/mcp.js +15 -0
  168. package/dist-server/harness/tools/native.d.ts +179 -0
  169. package/dist-server/harness/tools/native.js +322 -0
  170. package/dist-server/harness/tools/parse.d.ts +14 -0
  171. package/dist-server/harness/tools/parse.js +18 -0
  172. package/dist-server/harness/tools/resolve-wren-binary.d.ts +12 -0
  173. package/dist-server/harness/tools/resolve-wren-binary.js +21 -0
  174. package/dist-server/harness/tools/resolve.d.ts +51 -0
  175. package/dist-server/harness/tools/resolve.js +79 -0
  176. package/dist-server/harness/tools/setup-native.d.ts +84 -0
  177. package/dist-server/harness/tools/setup-native.js +353 -0
  178. package/dist-server/harness/tools/wiring.d.ts +10 -0
  179. package/dist-server/harness/tools/wiring.js +16 -0
  180. package/dist-server/providers/setup.provider.yaml +31 -0
  181. package/dist-server/providers/wren.provider.yaml +33 -0
  182. package/dist-server/server/adopt.d.ts +87 -0
  183. package/dist-server/server/adopt.js +263 -0
  184. package/dist-server/server/app.d.ts +6 -0
  185. package/dist-server/server/app.js +2521 -0
  186. package/dist-server/server/auth-choice.d.ts +16 -0
  187. package/dist-server/server/auth-choice.js +36 -0
  188. package/dist-server/server/bin.d.ts +2 -0
  189. package/dist-server/server/bin.js +601 -0
  190. package/dist-server/server/bootstrap-project-binding.d.ts +31 -0
  191. package/dist-server/server/bootstrap-project-binding.js +71 -0
  192. package/dist-server/server/clarify.d.ts +10 -0
  193. package/dist-server/server/clarify.js +17 -0
  194. package/dist-server/server/codex-setup-mcp.d.ts +2 -0
  195. package/dist-server/server/codex-setup-mcp.js +147 -0
  196. package/dist-server/server/compose.d.ts +89 -0
  197. package/dist-server/server/compose.js +271 -0
  198. package/dist-server/server/conn-config.d.ts +30 -0
  199. package/dist-server/server/conn-config.js +253 -0
  200. package/dist-server/server/context-files.d.ts +12 -0
  201. package/dist-server/server/context-files.js +117 -0
  202. package/dist-server/server/context-map.d.ts +30 -0
  203. package/dist-server/server/context-map.js +95 -0
  204. package/dist-server/server/context-source.d.ts +68 -0
  205. package/dist-server/server/context-source.js +85 -0
  206. package/dist-server/server/db.d.ts +862 -0
  207. package/dist-server/server/db.js +1839 -0
  208. package/dist-server/server/enrichment-runner.d.ts +184 -0
  209. package/dist-server/server/enrichment-runner.js +213 -0
  210. package/dist-server/server/enrichment-verify.d.ts +73 -0
  211. package/dist-server/server/enrichment-verify.js +290 -0
  212. package/dist-server/server/enrichment.d.ts +232 -0
  213. package/dist-server/server/enrichment.js +199 -0
  214. package/dist-server/server/env-detect.d.ts +8 -0
  215. package/dist-server/server/env-detect.js +17 -0
  216. package/dist-server/server/env-field-hints.d.ts +46 -0
  217. package/dist-server/server/env-field-hints.js +48 -0
  218. package/dist-server/server/fold.d.ts +133 -0
  219. package/dist-server/server/fold.js +407 -0
  220. package/dist-server/server/harness-profile-sources.d.ts +3 -0
  221. package/dist-server/server/harness-profile-sources.js +24 -0
  222. package/dist-server/server/harness.d.ts +19 -0
  223. package/dist-server/server/harness.js +439 -0
  224. package/dist-server/server/impact.d.ts +18 -0
  225. package/dist-server/server/impact.js +85 -0
  226. package/dist-server/server/interactive-terminal.d.ts +175 -0
  227. package/dist-server/server/interactive-terminal.js +336 -0
  228. package/dist-server/server/native-artifacts.d.ts +1004 -0
  229. package/dist-server/server/native-artifacts.js +564 -0
  230. package/dist-server/server/native-dispatch-registry.d.ts +42 -0
  231. package/dist-server/server/native-dispatch-registry.js +45 -0
  232. package/dist-server/server/native-producer-fence.d.ts +14 -0
  233. package/dist-server/server/native-producer-fence.js +48 -0
  234. package/dist-server/server/native-session-resume.d.ts +6 -0
  235. package/dist-server/server/native-session-resume.js +103 -0
  236. package/dist-server/server/native-session-workspace.d.ts +40 -0
  237. package/dist-server/server/native-session-workspace.js +175 -0
  238. package/dist-server/server/native-sessions.d.ts +374 -0
  239. package/dist-server/server/native-sessions.js +1517 -0
  240. package/dist-server/server/native-wren-runtime.d.ts +20 -0
  241. package/dist-server/server/native-wren-runtime.js +259 -0
  242. package/dist-server/server/node-pty-host.d.ts +31 -0
  243. package/dist-server/server/node-pty-host.js +100 -0
  244. package/dist-server/server/route-intent.d.ts +40 -0
  245. package/dist-server/server/route-intent.js +23 -0
  246. package/dist-server/server/runtime-binding.d.ts +52 -0
  247. package/dist-server/server/runtime-binding.js +258 -0
  248. package/dist-server/server/source-catalog.d.ts +84 -0
  249. package/dist-server/server/source-catalog.js +221 -0
  250. package/dist-server/server/source-driver.d.ts +35 -0
  251. package/dist-server/server/source-driver.js +124 -0
  252. package/dist-server/server/spa.d.ts +2 -0
  253. package/dist-server/server/spa.js +51 -0
  254. package/dist-server/server/subscription-model-catalog.d.ts +27 -0
  255. package/dist-server/server/subscription-model-catalog.js +203 -0
  256. package/dist-server/server/turn.d.ts +249 -0
  257. package/dist-server/server/turn.js +1074 -0
  258. package/dist-server/server/wire-types.d.ts +799 -0
  259. package/dist-server/server/wire-types.js +1 -0
  260. package/dist-server/server/wren-home.d.ts +19 -0
  261. package/dist-server/server/wren-home.js +33 -0
  262. package/dist-server/server/wren-profiles.d.ts +43 -0
  263. package/dist-server/server/wren-profiles.js +123 -0
  264. package/package.json +93 -0
  265. package/profiles/genbi-default/context/binding.yml +9 -0
  266. package/profiles/genbi-default/ir.golden.json +506 -0
  267. package/profiles/genbi-default/profile.yml +16 -0
  268. package/profiles/genbi-enrich-context/components/apply_enrichment/component.yml +42 -0
  269. package/profiles/genbi-enrich-context/components/draft_enrichment/component.yml +30 -0
  270. package/profiles/genbi-enrich-context/components/draft_enrichment/steps/draft.md +34 -0
  271. package/profiles/genbi-enrich-context/components/inspect_context/component.yml +32 -0
  272. package/profiles/genbi-enrich-context/components/inspect_context/steps/inspect.md +13 -0
  273. package/profiles/genbi-enrich-context/context/binding.yml +4 -0
  274. package/profiles/genbi-enrich-context/ir.golden.json +302 -0
  275. package/profiles/genbi-enrich-context/profile.yml +15 -0
  276. package/profiles/genbi-monitor/context/binding.yml +7 -0
  277. package/profiles/genbi-monitor/ir.golden.json +188 -0
  278. package/profiles/genbi-monitor/profile.yml +27 -0
  279. package/profiles/genbi-setup/components/build_context/component.yml +44 -0
  280. package/profiles/genbi-setup/components/build_context/steps/build.md +14 -0
  281. package/profiles/genbi-setup/components/connect_source/component.yml +51 -0
  282. package/profiles/genbi-setup/components/connect_source/steps/connect.md +23 -0
  283. package/profiles/genbi-setup/context/binding.yml +8 -0
  284. package/profiles/genbi-setup/ir.golden.json +140 -0
  285. package/profiles/genbi-setup/profile.yml +18 -0
  286. package/profiles/genbi-setup/raw/schema.json +5 -0
@@ -0,0 +1,84 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { loadBundleWithProvenance } from "../bundle/loader.js";
3
+ import { compileProfile, compileRawProfile } from "../compile/pipeline.js";
4
+ import { buildAgentSdkManifestArgs, runAgentSdkManifest } from "./agent-sdk-manifest.js";
5
+ import { resolveAgentSdkCli } from "./agent-sdk-cli.js";
6
+ import { resolveCodexLocalCli } from "./codex-local-cli.js";
7
+ import { describeCodexAskManifest, describeCodexBootstrapManifest, describeCodexEnrichmentManifest } from "./codex-local-manifest.js";
8
+ /**
9
+ * Describes whichever back-end will ACTUALLY run a turn for
10
+ * `options.authChoice` — the invariant this maintains is: Harness-shown
11
+ * target == `runtimeDispatcher`'s prediction (`server/harness.ts`)
12
+ * == what `route()` really dispatches to. The IR itself stays target-neutral
13
+ * either way; only the DISPLAY artifact this function loads differs:
14
+ *
15
+ * - `authChoice.mode === "subscription"` (dispatched): compiles IR only
16
+ * (`mode: "native"`, reusing `compileProfile`'s cache) — no vercel bundle
17
+ * is produced on this path at all — then sources the display from the
18
+ * claude-agent-sdk dispatcher's OWN `manifest` subcommand, which reads
19
+ * that same IR and emits a structurally-identical-to-the-vercel-bundle
20
+ * JSON (`target: "claude-agent-sdk:local"`) to stdout. This is genuinely
21
+ * what dispatched runs — `runDispatchedDefault` (`./dispatched.js`) shells this same
22
+ * dispatcher's `chat` subcommand against the same IR.
23
+ * - every other `authChoice.mode` (in-process: api-key/local/gateway): compiles
24
+ * to a vercel bundle (`mode: "agnostic"`, `warble dispatch --target
25
+ * vercel`) — unchanged pre-existing behavior; `runInProcessDefault`
26
+ * (`./in-process.js`) calls this too rather than keeping its own copy.
27
+ */
28
+ export async function describeBundle(options) {
29
+ if (options.authChoice.mode === "subscription") {
30
+ if (options.authChoice.provider === "codex")
31
+ return describeCodexManifest(options);
32
+ return describeAgentSdkManifest(options);
33
+ }
34
+ return describeVercelBundle(options);
35
+ }
36
+ async function describeCodexManifest(options) {
37
+ const compiled = await compileForDescription(options, "native");
38
+ const purpose = options.codexManifestPurpose ?? (options.context === "bootstrap" ? "setup" : "analysis");
39
+ if (purpose === "setup") {
40
+ const cli = await resolveCodexLocalCli(options.codexLocalBin);
41
+ return describeCodexBootstrapManifest(cli, compiled.irPath);
42
+ }
43
+ const models = options.codexModels;
44
+ if (!models || !models.orchestrator.trim() || !models.cheap.trim() || !models.strong.trim()) {
45
+ throw new Error("Codex manifest requires orchestrator, cheap, and strong model bindings");
46
+ }
47
+ const cli = await resolveCodexLocalCli(options.codexLocalBin);
48
+ return purpose === "context_enrichment"
49
+ ? describeCodexEnrichmentManifest(cli, compiled.irPath, models)
50
+ : describeCodexAskManifest(cli, compiled.irPath, models);
51
+ }
52
+ async function describeVercelBundle(options) {
53
+ const compiled = await compileForDescription(options, "agnostic");
54
+ // `mode: "agnostic"` always produces a bundle — see `compileProfile`'s doc comment.
55
+ const bundleJson = JSON.parse(await readFile(compiled.bundlePath, "utf-8"));
56
+ return loadBundleWithProvenance(bundleJson, {
57
+ ...(compiled.warbleBin !== undefined ? { warbleBin: compiled.warbleBin } : {}),
58
+ profileSource: options.profileSource,
59
+ });
60
+ }
61
+ async function describeAgentSdkManifest(options) {
62
+ const compiled = await compileForDescription(options, "native");
63
+ const cli = await resolveAgentSdkCli(options.agentSdkBin);
64
+ const { command, args } = buildAgentSdkManifestArgs(cli, {
65
+ irPath: compiled.irPath,
66
+ ...(options.context !== "bootstrap" ? { userProject: options.userProject } : {}),
67
+ });
68
+ const stdout = await runAgentSdkManifest(command, args);
69
+ return loadBundleWithProvenance(JSON.parse(stdout), {
70
+ ...(compiled.warbleBin !== undefined ? { warbleBin: compiled.warbleBin } : {}),
71
+ profileSource: options.profileSource,
72
+ });
73
+ }
74
+ function compileForDescription(options, mode) {
75
+ const shared = {
76
+ profileSource: options.profileSource,
77
+ mode,
78
+ ...(options.warbleBin !== undefined ? { warbleBin: options.warbleBin } : {}),
79
+ ...(options.workDir !== undefined ? { workDir: options.workDir } : {}),
80
+ };
81
+ return options.context === "bootstrap"
82
+ ? compileRawProfile(shared)
83
+ : compileProfile({ ...shared, userProject: options.userProject });
84
+ }
@@ -0,0 +1,138 @@
1
+ import type { ResolvedCli } from "./agent-sdk-cli.js";
2
+ import type { DispatchedOptions, DispatchedResult } from "./types.js";
3
+ export interface AgentSdkChatCommand {
4
+ readonly command: string;
5
+ readonly args: readonly string[];
6
+ /** Fed to the child process over stdin, never interpolated into `args`. */
7
+ readonly input: string;
8
+ }
9
+ export interface BuildAgentSdkChatArgsOptions {
10
+ readonly irPath: string;
11
+ readonly userProject: string;
12
+ readonly question: string;
13
+ readonly outDir: string;
14
+ readonly warbleBin: string;
15
+ /**
16
+ * Which warble component `chat` dispatches, forwarded
17
+ * verbatim as `--component <agentId>`. Defaults to `MODE_B_AGENT_ID`
18
+ * (`"answer_query"`) when unset — the original default from before this
19
+ * option existed. Named `agentId`
20
+ * to match `InProcessOptions.agentId` (intent routing), even though
21
+ * Dispatched has no per-agent bundle concept of its own — it's the same
22
+ * "which compiled thing to run" knob, just a component id on the warble
23
+ * IR side rather than an agent id on a compiled vercel bundle.
24
+ */
25
+ readonly agentId?: string;
26
+ /**
27
+ * The "hybrid" passthrough: forwarded verbatim as `--models-config
28
+ * <modelsConfig>` when set, omitted entirely otherwise (existing callers
29
+ * that don't hybrid-route are byte-for-byte unaffected). This lets the
30
+ * subscription main loop (dispatched) route one step onto a non-Anthropic
31
+ * model per warble's own `ModelConfig.fromYaml` mechanism — see
32
+ * `runDispatchedDefault`'s doc comment for the known limitations this inherits.
33
+ */
34
+ readonly modelsConfig?: string;
35
+ /** Forwarded as `--max-turns <n>` when set; omitted otherwise so warble's default applies. */
36
+ readonly maxTurns?: number;
37
+ /**
38
+ * Session resume: forwarded verbatim as `--resume <id>` when set, so the dispatcher resumes
39
+ * the SAME agent-sdk conversation instead of starting a fresh one. See
40
+ * `DispatchedOptions.resumeSessionId`.
41
+ */
42
+ readonly resume?: string;
43
+ }
44
+ /**
45
+ * Builds the argv (and stdin input) for a `warble-agent-sdk chat` invocation
46
+ * without spawning anything — the validated shape from the earlier spike:
47
+ * `echo "<question>" | <agent-sdk-cli> chat <ir.json> --project <userProject>
48
+ * --component <agentId, default "answer_query"> --out <dir> --warble-bin
49
+ * <warble> [--models-config <path>] --stream-json`. `--stream-json` is always
50
+ * passed so `spawnChat` can incrementally parse per-step/per-tool NDJSON
51
+ * events off stdout instead of only getting the plain final-answer line. The
52
+ * question is always routed through `input` (stdin), never interpolated into
53
+ * `args`, so it can never break argv parsing — there is no shell involved
54
+ * either (`spawn`, not `exec`).
55
+ *
56
+ * `warble-agent-sdk chat` is a line-per-turn REPL over stdin — one
57
+ * line is one turn. A question containing a newline (e.g. the BFF's
58
+ * multi-turn Ask composing the last ~5 turns of prior context ahead of the
59
+ * follow-up) would silently become *multiple* turns if sent as-is — only the
60
+ * last turn's answer would ever be returned. The dispatcher can't be changed
61
+ * (it's a separate, soon-public repo), so instead of rejecting a multi-line
62
+ * question, `encodeQuestionForStdin` collapses its interior newlines into a
63
+ * literal `\n` escape so the whole thing still crosses stdin as exactly one
64
+ * line/turn; an LLM reads a literal `\n` in prose as a line break just fine.
65
+ * A single-line question has nothing to collapse and passes through
66
+ * unchanged. An empty/whitespace-only question is never a meaningful turn at
67
+ * all and is still rejected here, before anything is spawned.
68
+ */
69
+ export declare function buildAgentSdkChatArgs(cli: ResolvedCli, options: BuildAgentSdkChatArgsOptions): AgentSdkChatCommand;
70
+ /**
71
+ * Dispatched ("subscription"): compile the profile to IR only (`mode: "native"` —
72
+ * no vercel bundle; the claude-agent-sdk dispatcher reads IR directly and
73
+ * drives its own Claude Agent SDK `query()` loop at runtime, so no adapter/
74
+ * tier binding is assembled here), then shell the warble-agent-sdk CLI's
75
+ * `chat` subcommand, feeding the question over stdin and returning its
76
+ * finishing text.
77
+ *
78
+ * The "hybrid" passthrough: `options.modelsConfig`, when set, becomes
79
+ * `--models-config <path>` on the `chat` invocation (see
80
+ * `buildAgentSdkChatArgs`), letting the subscription main loop route one
81
+ * step (e.g. `resolve_intent`'s `cheap` tier) onto a non-Anthropic model via
82
+ * warble's own `ModelConfig.fromYaml` binding
83
+ * (`dispatcher/claude-agent-sdk/src/models.ts`). This harness only forwards
84
+ * the path — it never parses the YAML or the resulting per-tier routing
85
+ * itself, and inherits warble's own hybrid-mode holes as of this writing
86
+ * (tracked upstream, not fixed here): the render stage wall-hits on any
87
+ * non-`render: none` component under a non-Anthropic tier, the
88
+ * `openai_compat` local client has no streaming/retry, and net cost savings
89
+ * are unproven. Weak local models are also known to call tools unreliably —
90
+ * a separate, model-quality issue rather than a wiring bug. See the opt-in
91
+ * live hybrid tests (`test/hybrid-live.test.ts`) for how to exercise this
92
+ * against a real endpoint.
93
+ *
94
+ * As a belt-and-suspenders measure, `route()` already runs the compliance gate
95
+ * (`enforceCompliance`) before ever calling this function, but
96
+ * `runDispatchedDefault` is also exported from the package root — an embedder
97
+ * that imports it directly bypasses `route()`, and with it that gate,
98
+ * entirely. Re-running `enforceCompliance` here makes the executor safe
99
+ * regardless of entry point: `route()`'s call and this one are the same
100
+ * pure, idempotent decision (subscription + hosted always throws,
101
+ * subscription + personal always allows), so double-gating through
102
+ * `route()` costs nothing and closes the direct-import hole.
103
+ *
104
+ * Session resume (Plan A): `options.resumeSessionId`, when set, is forwarded as `--resume <id>` so
105
+ * the dispatcher resumes the SAME agent-sdk conversation for this turn instead of starting a fresh
106
+ * one — the agent keeps whatever it already read/listed/fetched, rather than re-orienting from
107
+ * scratch on every turn. The turn's own session id (for resuming a LATER turn) comes back on
108
+ * `DispatchedResult.sessionId` on success, and — since a resumable session id from a failed turn is just
109
+ * as useful as one from a successful turn (an `error_max_turns` failure is exactly the case this
110
+ * exists for) — is still recoverable from a thrown `DispatchedSessionError`'s `sessionId` field when this
111
+ * function rejects. This is deliberately independent of `irPath`/`resumeFromDisk`-style setup-state
112
+ * reconstruction elsewhere in the harness (`server/compose.ts`): this option resumes the actual SDK
113
+ * conversation, not a disk-state prompt rebuild.
114
+ */
115
+ export declare function runDispatchedDefault(options: DispatchedOptions): Promise<DispatchedResult>;
116
+ /**
117
+ * A `spawnChat` turn that failed (non-zero exit, timeout, or spawn error) but still surfaced a
118
+ * resumable SDK session id along the way (the dispatcher's `{t:"session",id}` NDJSON line — see
119
+ * `chat-event-mapper.ts` and warble's own `DispatchSessionError`, which is what makes this possible
120
+ * on a failed turn like `error_max_turns` in the first place, not just a successful one).
121
+ * `sessionId` is `null` when the dispatcher emitted the session line but the SDK itself never
122
+ * produced an id; it is simply absent as a property when `error` is a plain `Error` (e.g. a spawn
123
+ * failure before any NDJSON was ever read) — callers should treat "not a `DispatchedSessionError`" the
124
+ * same as "no session id available", never as a hard failure of their own.
125
+ */
126
+ export declare class DispatchedSessionError extends Error {
127
+ readonly sessionId: string | null;
128
+ constructor(message: string, sessionId: string | null);
129
+ }
130
+ export interface SpawnChatResult {
131
+ readonly finalText: string;
132
+ /**
133
+ * The SDK session id this turn ran under (the dispatcher's `{t:"session",id}` line), or
134
+ * `undefined` if no such line was ever read (e.g. an older dispatcher build, or the process
135
+ * died before emitting one).
136
+ */
137
+ readonly sessionId: string | null | undefined;
138
+ }
@@ -0,0 +1,339 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdtemp } from "node:fs/promises";
3
+ import { createInterface } from "node:readline";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { enforceCompliance } from "../compliance/index.js";
7
+ import { compileProfile } from "../compile/pipeline.js";
8
+ import { resolveWarbleBinary } from "../compile/resolve-binary.js";
9
+ import { createAgentEventEmitter } from "../events/index.js";
10
+ import { createChatEventMapperState, mapChatEventToAgentEvent, parseWarbleChatEventLine, } from "./chat-event-mapper.js";
11
+ import { resolveAgentSdkCli } from "./agent-sdk-cli.js";
12
+ const MODE_B_AGENT_ID = "answer_query";
13
+ /**
14
+ * Builds the argv (and stdin input) for a `warble-agent-sdk chat` invocation
15
+ * without spawning anything — the validated shape from the earlier spike:
16
+ * `echo "<question>" | <agent-sdk-cli> chat <ir.json> --project <userProject>
17
+ * --component <agentId, default "answer_query"> --out <dir> --warble-bin
18
+ * <warble> [--models-config <path>] --stream-json`. `--stream-json` is always
19
+ * passed so `spawnChat` can incrementally parse per-step/per-tool NDJSON
20
+ * events off stdout instead of only getting the plain final-answer line. The
21
+ * question is always routed through `input` (stdin), never interpolated into
22
+ * `args`, so it can never break argv parsing — there is no shell involved
23
+ * either (`spawn`, not `exec`).
24
+ *
25
+ * `warble-agent-sdk chat` is a line-per-turn REPL over stdin — one
26
+ * line is one turn. A question containing a newline (e.g. the BFF's
27
+ * multi-turn Ask composing the last ~5 turns of prior context ahead of the
28
+ * follow-up) would silently become *multiple* turns if sent as-is — only the
29
+ * last turn's answer would ever be returned. The dispatcher can't be changed
30
+ * (it's a separate, soon-public repo), so instead of rejecting a multi-line
31
+ * question, `encodeQuestionForStdin` collapses its interior newlines into a
32
+ * literal `\n` escape so the whole thing still crosses stdin as exactly one
33
+ * line/turn; an LLM reads a literal `\n` in prose as a line break just fine.
34
+ * A single-line question has nothing to collapse and passes through
35
+ * unchanged. An empty/whitespace-only question is never a meaningful turn at
36
+ * all and is still rejected here, before anything is spawned.
37
+ */
38
+ export function buildAgentSdkChatArgs(cli, options) {
39
+ if (options.question.trim().length === 0) {
40
+ throw new Error("dispatched question must not be empty or whitespace-only");
41
+ }
42
+ return {
43
+ command: cli.command,
44
+ args: [
45
+ ...cli.prefixArgs,
46
+ "chat",
47
+ options.irPath,
48
+ "--project",
49
+ options.userProject,
50
+ "--component",
51
+ options.agentId ?? MODE_B_AGENT_ID,
52
+ "--out",
53
+ options.outDir,
54
+ "--warble-bin",
55
+ options.warbleBin,
56
+ ...(options.modelsConfig !== undefined ? ["--models-config", options.modelsConfig] : []),
57
+ ...(options.maxTurns !== undefined ? ["--max-turns", String(options.maxTurns)] : []),
58
+ ...(options.resume !== undefined ? ["--resume", options.resume] : []),
59
+ "--stream-json",
60
+ ],
61
+ input: `${encodeQuestionForStdin(options.question)}\n`,
62
+ };
63
+ }
64
+ /**
65
+ * Collapses every newline variant inside `question` into a literal
66
+ * two-character `\n` escape so the result always fits on a single stdin
67
+ * line/turn, no matter how many lines the caller composed into it. CRLF and
68
+ * lone CR are normalized to LF first so every newline style escapes
69
+ * uniformly. A question with no interior newline has nothing to normalize or
70
+ * escape and is returned byte-for-byte unchanged.
71
+ */
72
+ function encodeQuestionForStdin(question) {
73
+ return question.replace(/\r\n|\r/g, "\n").replace(/\n/g, "\\n");
74
+ }
75
+ /**
76
+ * Dispatched ("subscription"): compile the profile to IR only (`mode: "native"` —
77
+ * no vercel bundle; the claude-agent-sdk dispatcher reads IR directly and
78
+ * drives its own Claude Agent SDK `query()` loop at runtime, so no adapter/
79
+ * tier binding is assembled here), then shell the warble-agent-sdk CLI's
80
+ * `chat` subcommand, feeding the question over stdin and returning its
81
+ * finishing text.
82
+ *
83
+ * The "hybrid" passthrough: `options.modelsConfig`, when set, becomes
84
+ * `--models-config <path>` on the `chat` invocation (see
85
+ * `buildAgentSdkChatArgs`), letting the subscription main loop route one
86
+ * step (e.g. `resolve_intent`'s `cheap` tier) onto a non-Anthropic model via
87
+ * warble's own `ModelConfig.fromYaml` binding
88
+ * (`dispatcher/claude-agent-sdk/src/models.ts`). This harness only forwards
89
+ * the path — it never parses the YAML or the resulting per-tier routing
90
+ * itself, and inherits warble's own hybrid-mode holes as of this writing
91
+ * (tracked upstream, not fixed here): the render stage wall-hits on any
92
+ * non-`render: none` component under a non-Anthropic tier, the
93
+ * `openai_compat` local client has no streaming/retry, and net cost savings
94
+ * are unproven. Weak local models are also known to call tools unreliably —
95
+ * a separate, model-quality issue rather than a wiring bug. See the opt-in
96
+ * live hybrid tests (`test/hybrid-live.test.ts`) for how to exercise this
97
+ * against a real endpoint.
98
+ *
99
+ * As a belt-and-suspenders measure, `route()` already runs the compliance gate
100
+ * (`enforceCompliance`) before ever calling this function, but
101
+ * `runDispatchedDefault` is also exported from the package root — an embedder
102
+ * that imports it directly bypasses `route()`, and with it that gate,
103
+ * entirely. Re-running `enforceCompliance` here makes the executor safe
104
+ * regardless of entry point: `route()`'s call and this one are the same
105
+ * pure, idempotent decision (subscription + hosted always throws,
106
+ * subscription + personal always allows), so double-gating through
107
+ * `route()` costs nothing and closes the direct-import hole.
108
+ *
109
+ * Session resume (Plan A): `options.resumeSessionId`, when set, is forwarded as `--resume <id>` so
110
+ * the dispatcher resumes the SAME agent-sdk conversation for this turn instead of starting a fresh
111
+ * one — the agent keeps whatever it already read/listed/fetched, rather than re-orienting from
112
+ * scratch on every turn. The turn's own session id (for resuming a LATER turn) comes back on
113
+ * `DispatchedResult.sessionId` on success, and — since a resumable session id from a failed turn is just
114
+ * as useful as one from a successful turn (an `error_max_turns` failure is exactly the case this
115
+ * exists for) — is still recoverable from a thrown `DispatchedSessionError`'s `sessionId` field when this
116
+ * function rejects. This is deliberately independent of `irPath`/`resumeFromDisk`-style setup-state
117
+ * reconstruction elsewhere in the harness (`server/compose.ts`): this option resumes the actual SDK
118
+ * conversation, not a disk-state prompt rebuild.
119
+ */
120
+ export async function runDispatchedDefault(options) {
121
+ // Live-event layer: emits run.start/answer/run.finish/error on
122
+ // options.onEvent directly, plus — now that `chat --stream-json` streams
123
+ // per-step/per-tool NDJSON over stdout — step.*/tool.* events mapped in
124
+ // from that stream via `chat-event-mapper.ts` as `spawnChat` reads each
125
+ // line (see there for the mapping). Mapped events are best-effort: a
126
+ // malformed/unrecognized NDJSON line is dropped, not fatal to the turn.
127
+ enforceCompliance(options.authChoice, { deployment: options.deployment ?? "personal" });
128
+ // `SubscriptionAuthChoice.provider` is "claude" | "codex", but the
129
+ // only Ask back-end this function drives is the Claude Agent SDK
130
+ // dispatcher (`warble-agent-sdk chat`). Codex is selected by route() and
131
+ // runs through the separate codex:local adapter.
132
+ // Without this check, a `provider: "codex"` choice would silently run the
133
+ // Claude dispatcher anyway (misrouting the request to the wrong model
134
+ // family) instead of failing loudly. Fail before compiling or spawning
135
+ // anything.
136
+ if (options.authChoice.provider !== "claude") {
137
+ throw new Error(`runDispatchedDefault only supports the Claude subscription provider. ` +
138
+ `Codex Ask must be dispatched through the codex:local route and will not fall back.`);
139
+ }
140
+ if (options.signal?.aborted)
141
+ throw new Error("warble-agent-sdk chat was cancelled before start");
142
+ // Which warble component `chat` dispatches; defaults to
143
+ // MODE_B_AGENT_ID ("answer_query") — the original default from before this
144
+ // option existed. See `DispatchedOptions.agentId`.
145
+ const agentId = options.agentId ?? MODE_B_AGENT_ID;
146
+ const emitter = createAgentEventEmitter(options.onEvent);
147
+ emitter.emit({ kind: "run.start", mode: "B", agentId });
148
+ try {
149
+ // Setup-flow bypass (`DispatchedOptions.irPath`): skip compileProfile entirely
150
+ // and dispatch the caller-supplied IR directly. Used by the setup
151
+ // wizard's `DispatchedSetupRunner`, which has no bound wren project to
152
+ // compile a profile against — it dispatches the fixed, warble-committed
153
+ // genbi-setup IR instead.
154
+ const irPath = options.irPath ?? (await compileProfile({
155
+ profileSource: options.profileSource,
156
+ userProject: options.userProject,
157
+ mode: "native",
158
+ ...(options.warbleBin !== undefined ? { warbleBin: options.warbleBin } : {}),
159
+ ...(options.workDir !== undefined ? { workDir: options.workDir } : {}),
160
+ })).irPath;
161
+ if (options.signal?.aborted)
162
+ throw new Error("warble-agent-sdk chat was cancelled during preparation");
163
+ const warbleBin = options.warbleBin ?? (await resolveWarbleBinary());
164
+ if (options.signal?.aborted)
165
+ throw new Error("warble-agent-sdk chat was cancelled during preparation");
166
+ const cli = await resolveAgentSdkCli(options.agentSdkBin);
167
+ if (options.signal?.aborted)
168
+ throw new Error("warble-agent-sdk chat was cancelled during preparation");
169
+ const outDir = options.outDir ?? (await mkdtemp(path.join(os.tmpdir(), "wren-harness-agent-sdk-")));
170
+ if (options.signal?.aborted)
171
+ throw new Error("warble-agent-sdk chat was cancelled during preparation");
172
+ const command = buildAgentSdkChatArgs(cli, {
173
+ irPath,
174
+ userProject: options.userProject,
175
+ question: options.question,
176
+ outDir,
177
+ warbleBin,
178
+ agentId,
179
+ ...(options.modelsConfig !== undefined ? { modelsConfig: options.modelsConfig } : {}),
180
+ ...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),
181
+ ...(options.resumeSessionId !== undefined ? { resume: options.resumeSessionId } : {}),
182
+ });
183
+ const { finalText, sessionId } = await spawnChat(command, (event) => emitter.emit(event), options.chatTimeoutMs ?? CHAT_TIMEOUT_MS, options.signal);
184
+ // `spawnChat` resolves with whatever trimmed final-answer text
185
+ // the process produced, even if that's empty — e.g. the CLI exits 0 but
186
+ // never printed a terminal answer line (a dispatcher bug, an unexpected
187
+ // output-format change, or a turn that produced no text). Treat that as
188
+ // a failure rather than a silent empty "answer".
189
+ if (finalText.length === 0) {
190
+ throw new Error("warble-agent-sdk chat exited successfully but produced no stdout — expected the turn's " +
191
+ "final answer text; got empty output");
192
+ }
193
+ emitter.emit({ kind: "answer", text: finalText });
194
+ emitter.emit({ kind: "run.finish", status: "answer" });
195
+ return { finalText, ...(sessionId !== undefined ? { sessionId } : {}) };
196
+ }
197
+ catch (error) {
198
+ emitter.emit({ kind: "error", message: describeDispatchedError(error) });
199
+ emitter.emit({ kind: "run.finish", status: "error" });
200
+ throw error;
201
+ }
202
+ }
203
+ function describeDispatchedError(error) {
204
+ if (error instanceof Error)
205
+ return error.message;
206
+ return typeof error === "string" ? error : JSON.stringify(error);
207
+ }
208
+ /**
209
+ * A `spawnChat` turn that failed (non-zero exit, timeout, or spawn error) but still surfaced a
210
+ * resumable SDK session id along the way (the dispatcher's `{t:"session",id}` NDJSON line — see
211
+ * `chat-event-mapper.ts` and warble's own `DispatchSessionError`, which is what makes this possible
212
+ * on a failed turn like `error_max_turns` in the first place, not just a successful one).
213
+ * `sessionId` is `null` when the dispatcher emitted the session line but the SDK itself never
214
+ * produced an id; it is simply absent as a property when `error` is a plain `Error` (e.g. a spawn
215
+ * failure before any NDJSON was ever read) — callers should treat "not a `DispatchedSessionError`" the
216
+ * same as "no session id available", never as a hard failure of their own.
217
+ */
218
+ export class DispatchedSessionError extends Error {
219
+ sessionId;
220
+ constructor(message, sessionId) {
221
+ super(message);
222
+ this.sessionId = sessionId;
223
+ this.name = "DispatchedSessionError";
224
+ }
225
+ }
226
+ // guardrail-enforcement fix: a hung `warble-agent-sdk chat` invocation
227
+ // previously blocked forever. This is a hang-guard CEILING (turns finish when
228
+ // done; it only fires on a genuine hang), so it's set generously: a cold Ask
229
+ // right after setup — a fresh, un-warmed project where answer_query explores
230
+ // before it queries — was observed to legitimately need >5 min, so 10 minutes
231
+ // is the default. Override via `DispatchedOptions.chatTimeoutMs`
232
+ // (`--chat-timeout-ms` / `WREN_HARNESS_CHAT_TIMEOUT_MS`).
233
+ const CHAT_TIMEOUT_MS = 10 * 60 * 1000;
234
+ /**
235
+ * Runs one `warble-agent-sdk chat --stream-json` turn, reading stdout INCREMENTALLY
236
+ * (line-by-line, via `readline`) rather than buffering the whole process output like the
237
+ * previous `execFile`-based version did — that's what lets `onEvent` fire live, per line, as the
238
+ * turn runs, instead of only after the process exits. Each line is parsed with
239
+ * `parseWarbleChatEventLine` and either captured as the turn's final answer (`t: "answer"`), its
240
+ * resumable session id (`t: "session"` — tracked so it can be surfaced on BOTH a successful
241
+ * resolve and a failed reject, since a resumable session id is just as useful after an
242
+ * `error_max_turns` failure as after a success; see `DispatchedSessionError`), or mapped to this
243
+ * harness's `AgentEvent` vocabulary via `mapChatEventToAgentEvent` and forwarded to `onEvent`. A
244
+ * line that fails to parse is silently dropped (never fatal to the turn — see that module's doc
245
+ * comment). `onEvent` is always called unconditionally here; it is `runDispatchedDefault`'s
246
+ * `emitter.emit`, which is already a documented no-op when the caller supplied no sink.
247
+ */
248
+ function spawnChat(command, onEvent, timeoutMs = CHAT_TIMEOUT_MS, signal) {
249
+ return new Promise((resolve, reject) => {
250
+ if (signal?.aborted) {
251
+ reject(new DispatchedSessionError("warble-agent-sdk chat was cancelled before spawn", null));
252
+ return;
253
+ }
254
+ const child = spawn(command.command, [...command.args], {
255
+ detached: process.platform !== "win32",
256
+ });
257
+ let settled = false;
258
+ let stopReason;
259
+ let killTimer;
260
+ let answerText;
261
+ let sessionId;
262
+ let stderrText = "";
263
+ const mapperState = createChatEventMapperState();
264
+ const terminate = (childSignal) => {
265
+ if (child.pid !== undefined && process.platform !== "win32") {
266
+ try {
267
+ process.kill(-child.pid, childSignal);
268
+ return;
269
+ }
270
+ catch {
271
+ // Fall through to the direct child when it did not form a group.
272
+ }
273
+ }
274
+ child.kill(childSignal);
275
+ };
276
+ const stop = (reason) => {
277
+ if (settled || stopReason !== undefined)
278
+ return;
279
+ stopReason = reason;
280
+ terminate("SIGTERM");
281
+ killTimer = setTimeout(() => terminate("SIGKILL"), 1_000);
282
+ };
283
+ const timer = setTimeout(() => stop("timeout"), timeoutMs);
284
+ const cancel = () => stop("cancelled");
285
+ signal?.addEventListener("abort", cancel, { once: true });
286
+ const settle = (fn) => {
287
+ if (settled)
288
+ return;
289
+ settled = true;
290
+ clearTimeout(timer);
291
+ if (killTimer)
292
+ clearTimeout(killTimer);
293
+ signal?.removeEventListener("abort", cancel);
294
+ fn();
295
+ };
296
+ const rl = createInterface({ input: child.stdout, crlfDelay: Infinity });
297
+ rl.on("line", (line) => {
298
+ const raw = parseWarbleChatEventLine(line);
299
+ if (raw === undefined)
300
+ return;
301
+ if (raw.t === "answer") {
302
+ answerText = raw.text;
303
+ return;
304
+ }
305
+ if (raw.t === "session") {
306
+ sessionId = raw.id;
307
+ return;
308
+ }
309
+ const mapped = mapChatEventToAgentEvent(raw, mapperState);
310
+ if (mapped !== undefined)
311
+ onEvent(mapped);
312
+ });
313
+ child.stderr.on("data", (chunk) => {
314
+ stderrText += chunk.toString("utf8");
315
+ });
316
+ child.on("error", (error) => {
317
+ settle(() => reject(new DispatchedSessionError(`warble-agent-sdk chat failed to start: ${error.message}`, sessionId ?? null)));
318
+ });
319
+ child.on("close", (code, signal) => {
320
+ settle(() => {
321
+ if (stopReason === "timeout") {
322
+ reject(new DispatchedSessionError(`warble-agent-sdk chat failed (timed out after ${timeoutMs}ms): ${stderrText}`, sessionId ?? null));
323
+ return;
324
+ }
325
+ if (stopReason === "cancelled") {
326
+ reject(new DispatchedSessionError("warble-agent-sdk chat was cancelled", sessionId ?? null));
327
+ return;
328
+ }
329
+ if (code !== 0) {
330
+ const exitCode = code ?? signal ?? "unknown";
331
+ reject(new DispatchedSessionError(`warble-agent-sdk chat failed (exit ${exitCode}): ${stderrText || "(no stderr output)"}`, sessionId ?? null));
332
+ return;
333
+ }
334
+ resolve({ finalText: (answerText ?? "").trim(), sessionId });
335
+ });
336
+ });
337
+ child.stdin?.end(command.input);
338
+ });
339
+ }
@@ -0,0 +1,43 @@
1
+ import type { RunAgentResult } from "../session/index.js";
2
+ import type { InProcessOptions } from "./types.js";
3
+ /** Projects a bundle-wide validated map onto the component selected for one turn. */
4
+ export declare function filterTierBindingForAgent(agent: {
5
+ readonly steps: readonly {
6
+ readonly tier: string;
7
+ }[];
8
+ }, tierBinding: Readonly<Record<string, import("../providers/index.js").AdapterSpec>>): Readonly<Record<string, import("../providers/index.js").AdapterSpec>>;
9
+ /**
10
+ * Resolves the `rootDir` the native `write_artifact` tool's
11
+ * `createLocalExecutionEnv` scope is rooted at. Precedence: an explicit
12
+ * `outDir` (per-call override, e.g. `InProcessOptions.outDir` /
13
+ * `WREN_HARNESS_OUT`) > the `WREN_HARNESS_ARTIFACTS_DIR` env var
14
+ * (operator-wide override) > a fixed `os.tmpdir()` subdirectory.
15
+ * Deliberately NEVER `process.cwd()` — that was the bug this fixes:
16
+ * `runInProcessDefault` used to call `createLocalExecutionEnv()` with no
17
+ * `rootDir` at all, which defaults to `process.cwd()` (`harness/exec/local.ts`)
18
+ * — for a live BFF process that's wherever this package's server process
19
+ * happens to be launched from, so every `write_artifact` call (from
20
+ * `explain_change`/`generate_dashboard`, both scoped-write-gated) dropped its
21
+ * output straight into whatever directory started the process.
22
+ *
23
+ * This is entirely independent of `projectDir` (`options.userProject`),
24
+ * which is the ONLY thing that governs the `query` tool's exec cwd (see
25
+ * `createWrenQueryTool` in `harness/tools/native.ts` — it takes `projectDir` as
26
+ * its `cwd` directly, never touching `env`'s own `rootDir`). Changing this
27
+ * default never changes where `query` runs.
28
+ */
29
+ export declare function resolveArtifactsDir(outDir: string | undefined): string;
30
+ /**
31
+ * In-process ("api-key" | "local" | "gateway"): compile the profile to a vercel
32
+ * bundle (unless `options.bundle` overrides it, for tests), resolve which
33
+ * compiled agent to run (`options.agentId`, defaulting to
34
+ * `ANSWER_QUERY_AGENT_ID` — intent routing picks this upstream in
35
+ * `server/turn.ts`, not here), assemble a `RunAgentContext` bound either to a
36
+ * single adapter covering every tier that agent uses (the default), or —
37
+ * when `options.tierBinding` is set ("hybrid" mode) — to that non-uniform
38
+ * per-tier map, validated via `buildHybridTierBinding`; wire its `query` tool
39
+ * to the real `wren` CLI against `options.userProject` (unless
40
+ * `options.mcpServers` overrides it, for tests), and run it in-process via
41
+ * `runAgent`.
42
+ */
43
+ export declare function runInProcessDefault(options: InProcessOptions): Promise<RunAgentResult>;