@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,601 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The BFF's process entrypoint. This is the ONLY file
4
+ * in `server/` that wires real production values (env vars, a real `Store`
5
+ * file, the real `route()` from `../harness/index.js`); every other `server/*`
6
+ * module takes its dependencies injected (`TurnDeps`) so tests never import
7
+ * this file.
8
+ *
9
+ * Env vars:
10
+ * PORT HTTP port to listen on (default 4787)
11
+ * WREN_BFF_DB_PATH SQLite file path (default "./wren-harness-bff.sqlite")
12
+ * WREN_HARNESS_WORKSPACE_ROOT REQUIRED. Dir the agentic setup wizard scaffolds NEW wren
13
+ * projects into (`<root>/<name>/`). The BFF always boots
14
+ * "bootstrap-pending": no project is bound yet, Ask-turn routes
15
+ * 409 until the setup wizard binds one via TurnDeps.bindProject
16
+ * (see server/turn.ts's resolveUserProject /
17
+ * effectiveRouteOptions). `/api/setup/*` and
18
+ * `/api/config/runtime` work unbound. An EXISTING project is
19
+ * taken through the wizard's adopt flow (POST /api/setup/adopt),
20
+ * which accepts any path on disk and survives a restart via
21
+ * recoverBootstrapProjectBinding — there is no boot-time
22
+ * binding env var.
23
+ * WREN_HARNESS_SETUP_IR path to the compiled genbi-setup IR (the profile's
24
+ * `connect_source` component) the setup wizard dispatches via
25
+ * dispatched's DispatchedOptions.irPath bypass. Defaults to
26
+ * resolveDefaultSetupIrPath()'s walk for this package's own
27
+ * profiles/genbi-setup/ir.golden.json; unlike WREN_HARNESS_PROFILE
28
+ * this does NOT hard-fail at boot if unresolved — only
29
+ * POST /api/setup/connect(/resume) needs it, and those return a
30
+ * clear 500 at dispatch time instead.
31
+ * WREN_HARNESS_PROFILE profileSource; default resolveDefaultProfileSource()
32
+ * WREN_HARNESS_MODE subscription|api-key|local|gateway (auth mode)
33
+ * WREN_HARNESS_PROVIDER subscription provider: claude|codex
34
+ * WREN_HARNESS_ADAPTER api-key adapter name
35
+ * WREN_HARNESS_API_KEY api-key credential
36
+ * WREN_HARNESS_MODEL model override
37
+ * WREN_HARNESS_ENDPOINT local/gateway endpoint URL
38
+ * WREN_HARNESS_WARBLE_BIN path to the warble CLI binary
39
+ * WREN_HARNESS_WREN_SHIM optional absolute server-owned Wren shim for
40
+ * native Codex runtime permissions; defaults to
41
+ * the fixed local installation when unset
42
+ * WREN_HARNESS_AGENT_SDK_BIN path to the agent-sdk CLI binary (dispatched)
43
+ * WREN_HARNESS_CODEX_LOCAL_BIN path to the warble-codex-local dispatcher CLI
44
+ * WREN_HARNESS_CODEX_BIN optional path to the Codex CLI used by that dispatcher
45
+ * WREN_HARNESS_CODEX_HOME absolute dedicated, externally authenticated CODEX_HOME used
46
+ * by persistent codex:local Ask sessions; must not be the default
47
+ * Codex home and must not contain config.toml
48
+ * WREN_HARNESS_OUT output dir forwarded to route() (dispatched run dir; also in-process's
49
+ * native write_artifact scope root, unless WREN_HARNESS_ARTIFACTS_DIR
50
+ * is set — see harness/route/in-process.ts's resolveArtifactsDir)
51
+ * WREN_HARNESS_ARTIFACTS_DIR in-process only: overrides resolveArtifactsDir's default
52
+ * (an os.tmpdir() path) when WREN_HARNESS_OUT isn't set — read
53
+ * directly by resolveArtifactsDir, not plumbed through CliFlags
54
+ * WREN_HARNESS_DEPLOYMENT personal|hosted (default personal)
55
+ * WREN_HARNESS_TIER_ADAPTER repeatable via commas: "<tier>=<mode>[:<field>=<value>,...]"
56
+ * (see harness/cli-args.ts's parseTierAdapterFlag), e.g.
57
+ * "cheap=local:endpoint=http://localhost:11434/v1,model=llama3.1"
58
+ * WREN_HARNESS_MODELS_CONFIG path forwarded verbatim to warble-agent-sdk chat (dispatched)
59
+ * WREN_HARNESS_CHAT_TIMEOUT_MS dispatched only: overrides spawnChat's default 10-minute hang
60
+ * guard on a "warble-agent-sdk chat" invocation. Raise this for a
61
+ * legitimately slower cold-start Ask turn (e.g. right after
62
+ * onboarding a project, before any compile cache is warm) instead
63
+ * of the turn being killed and reported as a timeout. Must be a
64
+ * positive integer (milliseconds); an invalid value fails fast at
65
+ * boot via the same parseChatTimeoutMs used by harness/cli.ts.
66
+ * WREN_HARNESS_SETUP_MAX_TURNS agentic setup only: caps the agent loop per setup turn,
67
+ * forwarded as "warble-agent-sdk chat --max-turns". Defaults to
68
+ * DEFAULT_SETUP_MAX_TURNS (120) — well above the dispatcher's own
69
+ * 40 — because build_context (generating an MDL) needs many turns
70
+ * and otherwise dies with error_max_turns. Positive integer.
71
+ * WREN_HARNESS_ENRICH_IR a PREBUILT genbi-enrich-context IR — in practice the profile's
72
+ * eval golden, i.e. that profile compiled against this repo's own
73
+ * example project with that project's schema baked into
74
+ * `context_binding.resolved`. It is NOT a per-user artifact and is
75
+ * no longer what a draft or a native session dispatches: both now
76
+ * compile against the bound project (see `resolveDispatchIr` and
77
+ * the enrichment runner's `profileSource`). What it still feeds is
78
+ * the producer contract probe and readiness, which ask whether this
79
+ * Warble can dispatch this profile SHAPE at all and have no user
80
+ * project in view — a fixture is the correct input there.
81
+ * Defaults to
82
+ * resolveDefaultEnrichIrPath()'s walk for this package's own
83
+ * profiles/genbi-enrich-context/ir.golden.json; like
84
+ * WREN_HARNESS_SETUP_IR this does NOT hard-fail at boot if
85
+ * unresolved — GET /api/context/enrichment reports the draft
86
+ * capability unavailable until one is configured, and
87
+ * POST /api/context/enrichment/start 503s. Claude subscription
88
+ * auth only; apply/reconcile/approval remain unwired (see the
89
+ * ticket).
90
+ *
91
+ * These mirror harness/cli.ts's `--project`/`--profile`/`--mode`/... flags
92
+ * one-to-one (env vars instead of flags, since this is a long-running server
93
+ * rather than a one-shot CLI invocation) and reuse the same cli-args.ts
94
+ * helpers harness/cli.ts itself uses for auth/deployment/tier-binding resolution.
95
+ */
96
+ import { serve } from "@hono/node-server";
97
+ import { createRequire } from "node:module";
98
+ import { existsSync } from "node:fs";
99
+ import path from "node:path";
100
+ import { fileURLToPath } from "node:url";
101
+ import { WebSocketServer } from "ws";
102
+ import { createDefaultLoginProbe, CodexSetupRunner, describeBundle, deriveAdapterSpec, enforceCompliance, InProcessSetupRunner, DispatchedSetupRunner, resolveDefaultEnrichIrPath, resolveDefaultProfileSource, resolveDefaultSetupIrPath, route, resolveArtifactsDir, selectSetupRunnerForAuth, } from "../harness/index.js";
103
+ import { buildTierBindingFromFlags, parseChatTimeoutMs, parseSetupMaxTurns, resolveAuthChoice, resolveDeployment, } from "../harness/cli-args.js";
104
+ import { createApp } from "./app.js";
105
+ import { recoverBootstrapProjectBinding } from "./bootstrap-project-binding.js";
106
+ import { createHarnessProfileSources } from "./harness-profile-sources.js";
107
+ import { compileProfile, compileRawProfile } from "../harness/compile/index.js";
108
+ import { toAuthChoiceFromRuntimeSettings } from "./auth-choice.js";
109
+ import { Store } from "./db.js";
110
+ import { resolveEnrichmentBinding, resolveProjectIdentity } from "./enrichment.js";
111
+ import { createDispatchedEnrichmentDraftRunner } from "./enrichment-runner.js";
112
+ import { dispatchInteractiveArtifacts, getInteractiveTerminalReadiness, InteractiveLaunchError, InteractiveTerminalManager, prepareInteractiveHandoff, unavailableInteractiveReadiness } from "./interactive-terminal.js";
113
+ import { createProbedPtyFactory, ensureDarwinNodePtySpawnHelper } from "./node-pty-host.js";
114
+ import { NativeSessionService } from "./native-sessions.js";
115
+ import { NativeArtifactService } from "./native-artifacts.js";
116
+ import { initializeNativeSessionStateBase, legacyInteractiveWorkspace, validateLegacyInteractiveWorkspace } from "./native-session-workspace.js";
117
+ import { codexModelsForRuntime, compileUnboundProfileTierNames, effectiveTierModel, materializeRuntimeRouteOptions, } from "./runtime-binding.js";
118
+ import { invalidateBundleAgentIdsCache } from "./turn.js";
119
+ import { createSetWrenHomeForSetupMode } from "./wren-home.js";
120
+ import { createSubscriptionModelCatalog } from "./subscription-model-catalog.js";
121
+ function envFlags() {
122
+ const env = process.env;
123
+ const tierAdapters = env["WREN_HARNESS_TIER_ADAPTER"];
124
+ const chatTimeoutMs = parseChatTimeoutMs(env["WREN_HARNESS_CHAT_TIMEOUT_MS"]);
125
+ return {
126
+ ...(env["WREN_HARNESS_PROFILE"] !== undefined ? { profile: env["WREN_HARNESS_PROFILE"] } : {}),
127
+ ...(env["WREN_HARNESS_MODE"] !== undefined ? { mode: env["WREN_HARNESS_MODE"] } : {}),
128
+ ...(env["WREN_HARNESS_PROVIDER"] !== undefined ? { provider: env["WREN_HARNESS_PROVIDER"] } : {}),
129
+ ...(env["WREN_HARNESS_ADAPTER"] !== undefined ? { adapter: env["WREN_HARNESS_ADAPTER"] } : {}),
130
+ ...(env["WREN_HARNESS_API_KEY"] !== undefined ? { apiKey: env["WREN_HARNESS_API_KEY"] } : {}),
131
+ ...(env["WREN_HARNESS_MODEL"] !== undefined ? { model: env["WREN_HARNESS_MODEL"] } : {}),
132
+ ...(env["WREN_HARNESS_ENDPOINT"] !== undefined ? { endpoint: env["WREN_HARNESS_ENDPOINT"] } : {}),
133
+ ...(env["WREN_HARNESS_WARBLE_BIN"] !== undefined ? { warbleBin: env["WREN_HARNESS_WARBLE_BIN"] } : {}),
134
+ ...(env["WREN_HARNESS_AGENT_SDK_BIN"] !== undefined ? { agentSdkBin: env["WREN_HARNESS_AGENT_SDK_BIN"] } : {}),
135
+ ...(env["WREN_HARNESS_OUT"] !== undefined ? { out: env["WREN_HARNESS_OUT"] } : {}),
136
+ ...(env["WREN_HARNESS_DEPLOYMENT"] !== undefined ? { deployment: env["WREN_HARNESS_DEPLOYMENT"] } : {}),
137
+ ...(tierAdapters !== undefined ? { tierAdapters: tierAdapters.split(",").filter((s) => s.length > 0) } : {}),
138
+ ...(env["WREN_HARNESS_MODELS_CONFIG"] !== undefined ? { modelsConfig: env["WREN_HARNESS_MODELS_CONFIG"] } : {}),
139
+ ...(chatTimeoutMs !== undefined ? { chatTimeoutMs } : {}),
140
+ };
141
+ }
142
+ async function main() {
143
+ const flags = envFlags();
144
+ const port = Number.parseInt(process.env["PORT"] ?? "4787", 10);
145
+ const workspaceRoot = process.env["WREN_HARNESS_WORKSPACE_ROOT"];
146
+ // Loud-fail rather than ignore: a caller still exporting the removed bound-mode variable
147
+ // believes it is booting against that project, and silently starting unbound would hand
148
+ // them a BFF pointed somewhere else entirely.
149
+ if (process.env["WREN_HARNESS_PROJECT"] !== undefined) {
150
+ process.stderr.write("error: WREN_HARNESS_PROJECT is no longer supported — the BFF has a single boot mode. " +
151
+ "Set WREN_HARNESS_WORKSPACE_ROOT and bring an existing project in through the setup " +
152
+ "wizard's adopt flow, which now survives a restart.\n");
153
+ process.exitCode = 1;
154
+ return;
155
+ }
156
+ if (workspaceRoot === undefined || workspaceRoot.trim().length === 0) {
157
+ process.stderr.write("error: WREN_HARNESS_WORKSPACE_ROOT is required\n");
158
+ process.exitCode = 1;
159
+ return;
160
+ }
161
+ const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
162
+ const packageRoot = path.basename(path.dirname(moduleDirectory)) === "dist-server"
163
+ ? path.resolve(moduleDirectory, "../..")
164
+ : path.resolve(moduleDirectory, "..");
165
+ // Opt-in same-process SPA serving: only wired when a build actually
166
+ // exists next to this package (`vite build`'s `dist/index.html`). A dev
167
+ // boot (no `dist/`) leaves `staticDir` unset, so `server/app.ts` never
168
+ // mounts the fallback and behavior is unchanged from today (UI served
169
+ // separately by vite's dev proxy). See `TurnDeps.staticDir`'s doc comment
170
+ // (server/turn.ts) and `server/spa.ts`.
171
+ const candidateStaticDir = path.join(packageRoot, "dist");
172
+ const staticDir = existsSync(path.join(candidateStaticDir, "index.html")) ? candidateStaticDir : undefined;
173
+ const profileSource = flags.profile ?? resolveDefaultProfileSource();
174
+ // Frozen at boot: GET /api/harness accepts only a purpose and must never
175
+ // inherit a caller-supplied RouteOptions.profileSource.
176
+ const harnessProfileSources = createHarnessProfileSources(profileSource);
177
+ const loginProbe = createDefaultLoginProbe();
178
+ const authChoice = await resolveAuthChoice(flags, loginProbe);
179
+ const deployment = resolveDeployment(flags);
180
+ const { warnings } = enforceCompliance(authChoice, { deployment });
181
+ for (const warning of warnings)
182
+ process.stderr.write(`warning: ${warning}\n`);
183
+ const tierBinding = flags.tierAdapters !== undefined ? buildTierBindingFromFlags(flags.tierAdapters) : undefined;
184
+ const dbPath = process.env["WREN_BFF_DB_PATH"] ?? "./wren-harness-bff.sqlite";
185
+ // BFF state, never a bound project, owns all non-Setup native artifacts.
186
+ // Initialization creates the fixed private namespaces once at startup.
187
+ const nativeMaterializationState = initializeNativeSessionStateBase(dbPath);
188
+ const store = new Store(dbPath);
189
+ const getCodexModels = () => codexModelsForRuntime(store.getRuntimeSettings());
190
+ const getRuntimeTierNames = () => compileUnboundProfileTierNames({
191
+ profileSource,
192
+ ...(flags.warbleBin !== undefined ? { warbleBin: flags.warbleBin } : {}),
193
+ });
194
+ // userProject is always a placeholder here: the BFF boots unbound.
195
+ // RouteOptions.userProject is a required string, but every real reader resolves the LIVE
196
+ // value through TurnDeps.getUserProject() (see server/turn.ts's resolveUserProject /
197
+ // effectiveRouteOptions), which takes precedence over this fixed field once wired below.
198
+ // This field is only ever read as a fallback for TurnDeps literals that don't wire
199
+ // getUserProject at all (e.g. existing tests' plain-object TurnDeps).
200
+ const baseRouteOptions = {
201
+ authChoice,
202
+ profileSource,
203
+ userProject: "",
204
+ deployment,
205
+ ...(flags.model !== undefined ? { model: flags.model } : {}),
206
+ ...(flags.warbleBin !== undefined ? { warbleBin: flags.warbleBin } : {}),
207
+ ...(flags.agentSdkBin !== undefined ? { agentSdkBin: flags.agentSdkBin } : {}),
208
+ ...(flags.out !== undefined ? { outDir: flags.out } : {}),
209
+ ...(tierBinding !== undefined ? { tierBinding } : {}),
210
+ ...(flags.modelsConfig !== undefined ? { modelsConfig: flags.modelsConfig } : {}),
211
+ ...(flags.chatTimeoutMs !== undefined ? { chatTimeoutMs: flags.chatTimeoutMs } : {}),
212
+ codexModels: getCodexModels,
213
+ ...(process.env["WREN_HARNESS_CODEX_HOME"] !== undefined
214
+ ? { codexHome: process.env["WREN_HARNESS_CODEX_HOME"] }
215
+ : {}),
216
+ ...(process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] !== undefined
217
+ ? { codexLocalBin: process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] }
218
+ : {}),
219
+ ...(process.env["WREN_HARNESS_CODEX_BIN"] !== undefined
220
+ ? { codexBin: process.env["WREN_HARNESS_CODEX_BIN"] }
221
+ : {}),
222
+ };
223
+ // Mutable project binding: the BFF always boots unbound, recovering a previously bound
224
+ // project from durable state when there is one, and is otherwise bound by the setup
225
+ // wizard's connect/adopt flows calling deps.bindProject(dir) — see server/turn.ts.
226
+ let boundProject = recoverBootstrapProjectBinding(store, workspaceRoot);
227
+ // eslint-disable-next-line prefer-const -- assigned once below, referenced by the closures first
228
+ let deps;
229
+ function getUserProject() {
230
+ return boundProject;
231
+ }
232
+ function bindProject(dir) {
233
+ // Canonicalize before exposing the binding. A different symlink spelling
234
+ // therefore cannot make an old enrichment run appear current, and every
235
+ // call (even to the same directory) advances its generation. Binding is
236
+ // a foundation operation (Setup connect, adopt, the context step's
237
+ // healthcheck bind) that must succeed for a project that has never been
238
+ // built, so it resolves identity only — never a revision, which would
239
+ // require `target/mdl.json` to exist. Enrichment call sites resolve the
240
+ // revision lazily, on demand, and treat an unbuilt project as a
241
+ // legitimate refusal there instead.
242
+ const identity = resolveProjectIdentity(dir);
243
+ boundProject = identity.path;
244
+ const { revokedNativeSessionIds } = store.activateEnrichmentBindingAndRevokeBoundNativeSessions(identity);
245
+ nativeSessions.revokeBindingCapabilities(revokedNativeSessionIds);
246
+ invalidateBundleAgentIdsCache(deps);
247
+ }
248
+ function unbindProject() {
249
+ boundProject = undefined;
250
+ invalidateBundleAgentIdsCache(deps);
251
+ }
252
+ // Discovery shares the same configured binaries and Codex identity as the
253
+ // runtime. Its implementation owns subprocess cleanup and payload
254
+ // sanitization; the app route only sees the public wire contract.
255
+ const listSubscriptionModels = createSubscriptionModelCatalog({
256
+ ...(flags.agentSdkBin !== undefined ? { agentSdkBin: flags.agentSdkBin } : {}),
257
+ ...(process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] !== undefined
258
+ ? { codexLocalBin: process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] }
259
+ : {}),
260
+ ...(baseRouteOptions.codexHome !== undefined ? { codexHome: baseRouteOptions.codexHome } : {}),
261
+ ...(baseRouteOptions.codexBin !== undefined ? { codexBin: baseRouteOptions.codexBin } : {}),
262
+ getUserProject,
263
+ });
264
+ // Baseline WREN_HOME this process actually booted with (possibly undefined — an operator may
265
+ // legitimately not have the var set at all, in which case the wren CLI's own default of
266
+ // `~/.wren` applies). Captured once, before anything below ever mutates
267
+ // `process.env["WREN_HOME"]`, so `setWrenHomeForSetupMode` can restore exactly this rather than
268
+ // guessing or force-deleting a value the operator's own shell environment set. See
269
+ // `TurnDeps.setWrenHomeForSetupMode`'s doc comment (server/turn.ts) for why
270
+ // this is toggled per setup-mode-action rather than once at boot: the wizard's create/adopt
271
+ // choice is a runtime, resettable decision (POST /api/setup/mode, POST /api/setup/reset), not
272
+ // something fixed at process start, so a single boot-time assignment would apply create's
273
+ // workspace-anchored WREN_HOME to adopt turns too whenever adopt is chosen (or re-chosen after
274
+ // a reset) later in the same boot. The actual logic lives in `./wren-home.js` so it's directly
275
+ // unit-testable without going through a mocked `TurnDeps`.
276
+ const originalWrenHome = process.env["WREN_HOME"];
277
+ const setWrenHomeForSetupMode = createSetWrenHomeForSetupMode(workspaceRoot, originalWrenHome);
278
+ // Mutable auth-choice binding — the auth-choice mirror of boundProject above. Starts at the
279
+ // boot-resolved `authChoice` and can be rebound later by PUT /api/config/runtime
280
+ // (server/app.ts) once a candidate AuthChoice has passed the compliance gate there.
281
+ let boundAuthChoice = store.hasExplicitRuntimeSettings()
282
+ ? toAuthChoiceFromRuntimeSettings(store.getRuntimeSettings())
283
+ : authChoice;
284
+ function getAuthChoice() {
285
+ return boundAuthChoice;
286
+ }
287
+ function setAuthChoice(choice) {
288
+ boundAuthChoice = choice;
289
+ }
290
+ const setupIrPath = process.env["WREN_HARNESS_SETUP_IR"] ?? resolveDefaultSetupIrPath();
291
+ const setupMaxTurns = parseSetupMaxTurns(process.env["WREN_HARNESS_SETUP_MAX_TURNS"]);
292
+ // Provider-aware setup selection: Claude subscription dispatches through
293
+ // DispatchedSetupRunner, Codex subscription through CodexSetupRunner, and
294
+ // api-key/local/gateway through
295
+ // the in-process vercel loop (InProcessSetupRunner). `agentSdkBin` still only
296
+ // applies to dispatched — it has no subprocess to pass a binary path to — but
297
+ // `setupMaxTurns` now applies to both: in-process enforces it as an in-process
298
+ // tool-loop step budget (`ExecuteAgentContext.maxSteps`) rather than a
299
+ // subprocess CLI flag, and `effectiveMaxTurns` reports it accordingly (see
300
+ // that class's doc comment in harness/setup/runner.ts).
301
+ //
302
+ // Both runners are constructed unconditionally (guarded only by setupIrPath being
303
+ // resolvable) rather than picking just one at boot, so a live auth-choice rebind (via
304
+ // setAuthChoice above) can dispatch a LATER setup turn through the OTHER runner without
305
+ // a process restart — see setupRunnerFor below and resolveSetupRunner in server/turn.ts.
306
+ let setupRunner;
307
+ let setupRunnerFor;
308
+ if (setupIrPath !== undefined) {
309
+ const dispatchedSetupRunner = new DispatchedSetupRunner({
310
+ irPath: setupIrPath,
311
+ getModelsConfig: () => {
312
+ if (!store.hasExplicitRuntimeSettings())
313
+ return flags.modelsConfig;
314
+ const modelsConfig = materializeRuntimeRouteOptions(store.getRuntimeSettings(), {
315
+ mode: "subscription",
316
+ provider: "claude",
317
+ }).modelsConfig;
318
+ if (!modelsConfig)
319
+ throw new Error("Claude setup requires a generated models config");
320
+ return modelsConfig;
321
+ },
322
+ ...(flags.warbleBin !== undefined ? { warbleBin: flags.warbleBin } : {}),
323
+ ...(flags.agentSdkBin !== undefined ? { agentSdkBin: flags.agentSdkBin } : {}),
324
+ ...(flags.out !== undefined ? { outDir: flags.out } : {}),
325
+ ...(setupMaxTurns !== undefined ? { maxTurns: setupMaxTurns } : {}),
326
+ });
327
+ const inProcessSetupRunner = new InProcessSetupRunner({
328
+ irPath: setupIrPath,
329
+ getStrongAdapterSpec: (choice) => {
330
+ if (!store.hasExplicitRuntimeSettings()) {
331
+ if (choice.mode === "subscription")
332
+ throw new Error("in-process setup requires non-subscription auth");
333
+ return deriveAdapterSpec(choice, flags.model !== undefined ? { model: flags.model } : {});
334
+ }
335
+ const strong = materializeRuntimeRouteOptions(store.getRuntimeSettings(), choice).tierBinding?.["strong"];
336
+ if (!strong)
337
+ throw new Error("in-process setup requires a configured strong-tier adapter");
338
+ return strong;
339
+ },
340
+ ...(flags.warbleBin !== undefined ? { warbleBin: flags.warbleBin } : {}),
341
+ ...(flags.out !== undefined ? { outDir: flags.out } : {}),
342
+ ...(flags.model !== undefined ? { model: flags.model } : {}),
343
+ ...(setupMaxTurns !== undefined ? { maxTurns: setupMaxTurns } : {}),
344
+ });
345
+ const codexSetupRunner = new CodexSetupRunner({
346
+ irPath: setupIrPath,
347
+ getStrongModel: () => store.hasExplicitRuntimeSettings()
348
+ ? effectiveTierModel(store.getRuntimeSettings().tierModels.find((binding) => binding.tier === "strong") ?? { tier: "strong" }, store.getRuntimeSettings()) ?? ""
349
+ : flags.model ?? "",
350
+ ...(process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] !== undefined
351
+ ? { codexLocalBin: process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] }
352
+ : {}),
353
+ ...(process.env["WREN_HARNESS_CODEX_BIN"] !== undefined
354
+ ? { codexBin: process.env["WREN_HARNESS_CODEX_BIN"] }
355
+ : {}),
356
+ });
357
+ const runnerSet = {
358
+ claudeSubscription: dispatchedSetupRunner,
359
+ codexSubscription: codexSetupRunner,
360
+ nonSubscription: inProcessSetupRunner,
361
+ };
362
+ const runnerFor = (choice) => selectSetupRunnerForAuth(choice, runnerSet);
363
+ setupRunner = runnerFor(authChoice);
364
+ setupRunnerFor = runnerFor;
365
+ }
366
+ else {
367
+ process.stderr.write("warning: no genbi-setup IR found (set WREN_HARNESS_SETUP_IR or restore this package's " +
368
+ "profiles/genbi-setup/ir.golden.json) — POST /api/setup/connect will 500 until one is " +
369
+ "configured\n");
370
+ }
371
+ // Post-bind enrichment draft callback. Guarded only by enrichIrPath being resolvable, mirroring
372
+ // setupRunner above — the runner itself reads the LIVE auth choice per-call (getAuthChoice)
373
+ // rather than freezing the boot-time one, and refuses (EnrichmentContractError, surfaced as a
374
+ // 500 by the route) any call made while the live choice isn't Claude subscription. Apply,
375
+ // reconcile and approval stay unwired: deps.enrichmentApplyRunner / .enrichmentApprovalProvider
376
+ // are intentionally left unset, so GET /api/context/enrichment keeps reporting those three
377
+ // "callback_unavailable" and POST /api/context/enrichment/.../apply keeps 503ing, exactly as
378
+ // before this change — only the draft capability goes live.
379
+ const enrichIrPath = process.env["WREN_HARNESS_ENRICH_IR"] ?? resolveDefaultEnrichIrPath();
380
+ // The profile's committed IR beside profile.yml — its eval golden, carrying
381
+ // the example project's schema. It is a producer-contract and readiness
382
+ // input only: what a session actually dispatches is compiled per binding by
383
+ // `resolveDispatchIr` below.
384
+ const analysisIrCandidate = process.env["WREN_HARNESS_ANALYSIS_IR"] ?? path.join(profileSource, "ir.golden.json");
385
+ const analysisIrPath = existsSync(analysisIrCandidate) ? analysisIrCandidate : undefined;
386
+ let enrichmentRunner;
387
+ if (enrichIrPath !== undefined) {
388
+ enrichmentRunner = createDispatchedEnrichmentDraftRunner({
389
+ irPath: enrichIrPath,
390
+ // Compile against the bound project rather than dispatching the prebuilt
391
+ // IR, which is the profile's eval golden and carries this repo's example
392
+ // project's schema.
393
+ profileSource: harnessProfileSources.context_enrichment,
394
+ getAuthChoice,
395
+ // Mirrors DispatchedSetupRunner's getModelsConfig above: enrichment draft dispatch is Claude
396
+ // subscription only, so this always materializes the Claude models config regardless of
397
+ // which auth choice is currently live — getAuthChoice's own check rejects the call before
398
+ // dispatch if the live choice isn't actually Claude subscription.
399
+ getModelsConfig: () => {
400
+ if (!store.hasExplicitRuntimeSettings())
401
+ return flags.modelsConfig;
402
+ return materializeRuntimeRouteOptions(store.getRuntimeSettings(), { mode: "subscription", provider: "claude" }).modelsConfig;
403
+ },
404
+ ...(flags.warbleBin !== undefined ? { warbleBin: flags.warbleBin } : {}),
405
+ ...(flags.agentSdkBin !== undefined ? { agentSdkBin: flags.agentSdkBin } : {}),
406
+ ...(flags.out !== undefined ? { outDir: flags.out } : {}),
407
+ ...(flags.chatTimeoutMs !== undefined ? { chatTimeoutMs: flags.chatTimeoutMs } : {}),
408
+ });
409
+ }
410
+ else {
411
+ process.stderr.write("warning: no genbi-enrich-context IR found (set WREN_HARNESS_ENRICH_IR or restore this " +
412
+ "package's profiles/genbi-enrich-context/ir.golden.json) — GET /api/context/enrichment " +
413
+ "will report draft unavailable until one is configured\n");
414
+ }
415
+ // Lazy-load the native add-on: a BFF serving ordinary Ask/Context must not
416
+ // fail at boot merely because the optional local terminal host is absent.
417
+ let interactiveTerminal;
418
+ const currentInteractiveBinding = () => {
419
+ const project = getUserProject();
420
+ const stored = store.getEnrichmentBinding();
421
+ if (!project || !stored)
422
+ return undefined;
423
+ try {
424
+ const resolved = resolveEnrichmentBinding(project);
425
+ return resolved.path === stored.path && resolved.identity === stored.identity
426
+ ? { ...resolved, generation: stored.generation }
427
+ : undefined;
428
+ }
429
+ catch {
430
+ return undefined;
431
+ }
432
+ };
433
+ let ptyFactory;
434
+ async function loadPty() {
435
+ ptyFactory ??= (async () => {
436
+ try {
437
+ const require = createRequire(import.meta.url);
438
+ ensureDarwinNodePtySpawnHelper(require.resolve("node-pty"));
439
+ return await createProbedPtyFactory(await import("node-pty"));
440
+ }
441
+ catch {
442
+ throw new InteractiveLaunchError("interactive terminal host cannot spawn local processes on this machine");
443
+ }
444
+ })();
445
+ return ptyFactory;
446
+ }
447
+ async function terminalManager() {
448
+ if (interactiveTerminal)
449
+ return interactiveTerminal;
450
+ interactiveTerminal = new InteractiveTerminalManager(await loadPty());
451
+ return interactiveTerminal;
452
+ }
453
+ async function prepareInteractiveTerminal(target, binding) {
454
+ if (enrichIrPath === undefined)
455
+ throw new InteractiveLaunchError("interactive enrichment artifacts are not configured");
456
+ const artifactRoot = legacyInteractiveWorkspace(nativeMaterializationState, binding.path, target);
457
+ return prepareInteractiveHandoff({
458
+ target,
459
+ binding,
460
+ artifactRoot,
461
+ materializationState: nativeMaterializationState,
462
+ materialize: () => dispatchInteractiveArtifacts({ warbleBin: flags.warbleBin ?? "warble", irPath: enrichIrPath, target, cwd: artifactRoot, boundProject: binding.path, materializationState: nativeMaterializationState }),
463
+ }, { getCurrentBinding: currentInteractiveBinding });
464
+ }
465
+ const nativeMcpUrl = `http://127.0.0.1:${port}/api/native-sessions/mcp`;
466
+ const nativeArtifacts = new NativeArtifactService({
467
+ store,
468
+ artifactsRoot: resolveArtifactsDir(flags.out),
469
+ expectedMcpUrl: nativeMcpUrl,
470
+ mcpUrl: process.env["WREN_HARNESS_NATIVE_MCP_URL"] ?? nativeMcpUrl,
471
+ getBinding: currentInteractiveBinding,
472
+ });
473
+ const nativeSessions = new NativeSessionService({
474
+ store,
475
+ terminalManager,
476
+ getBinding: currentInteractiveBinding,
477
+ workspaceRoot,
478
+ materializationState: nativeMaterializationState,
479
+ irPaths: { analysis: analysisIrPath, setup: setupIrPath, context_enrichment: enrichIrPath },
480
+ // A session dispatches an IR compiled for the project it is bound to. The
481
+ // paths above are the profiles' eval goldens — this profile compiled against
482
+ // warble's own example project, that project's schema baked into
483
+ // `context_binding.resolved` — which is the right artifact for the producer
484
+ // contract probe and the wrong one for a session that has a binding.
485
+ //
486
+ // Setup has no binding by definition, so it compiles the profile's authored
487
+ // context with no user project composed in; its golden resolves nothing
488
+ // either, which is why Setup never showed this symptom.
489
+ resolveDispatchIr: async (purpose, binding) => {
490
+ const profileSource = harnessProfileSources[purpose];
491
+ const compiled = purpose === "setup" || binding === undefined
492
+ ? await compileRawProfile({ profileSource, mode: "native", warbleBin: flags.warbleBin ?? "warble" })
493
+ : await compileProfile({ profileSource, userProject: binding.path, mode: "native", warbleBin: flags.warbleBin ?? "warble" });
494
+ return compiled.irPath;
495
+ },
496
+ warbleBin: flags.warbleBin ?? "warble",
497
+ ...(process.env["WREN_HARNESS_WREN_SHIM"] !== undefined ? { wrenShim: process.env["WREN_HARNESS_WREN_SHIM"] } : {}),
498
+ terminalHostAvailable: async () => {
499
+ try {
500
+ await loadPty();
501
+ return true;
502
+ }
503
+ catch {
504
+ return false;
505
+ }
506
+ },
507
+ artifactService: nativeArtifacts,
508
+ });
509
+ deps = {
510
+ store,
511
+ nativeSessions,
512
+ nativeArtifacts,
513
+ route,
514
+ baseRouteOptions,
515
+ describeBundle: (options) => {
516
+ return describeBundle({
517
+ ...options,
518
+ codexModels: typeof options.codexModels === "function" ? options.codexModels() : options.codexModels ?? getCodexModels(),
519
+ ...(process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] !== undefined
520
+ ? { codexLocalBin: process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] }
521
+ : {}),
522
+ });
523
+ },
524
+ describeHarnessBundle: (purpose, options) => {
525
+ const profileSource = harnessProfileSources[purpose];
526
+ const common = {
527
+ ...options,
528
+ profileSource,
529
+ codexManifestPurpose: purpose,
530
+ codexModels: typeof options.codexModels === "function" ? options.codexModels() : options.codexModels ?? getCodexModels(),
531
+ ...(process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] !== undefined
532
+ ? { codexLocalBin: process.env["WREN_HARNESS_CODEX_LOCAL_BIN"] }
533
+ : {}),
534
+ };
535
+ return purpose === "setup"
536
+ ? describeBundle({ ...common, context: "bootstrap" })
537
+ : describeBundle({ ...common, context: "bound_project", userProject: options.userProject });
538
+ },
539
+ getRuntimeTierNames,
540
+ getUserProject,
541
+ bindProject,
542
+ unbindProject,
543
+ getAuthChoice,
544
+ setAuthChoice,
545
+ loginProbe,
546
+ listSubscriptionModels,
547
+ ...(setupRunner !== undefined ? { setupRunner } : {}),
548
+ ...(setupRunnerFor !== undefined ? { setupRunnerFor } : {}),
549
+ ...(enrichmentRunner !== undefined ? { enrichmentRunner } : {}),
550
+ startInteractiveTerminal: async ({ target, binding }) => {
551
+ const { spec } = await prepareInteractiveTerminal(target, binding);
552
+ const manager = await terminalManager();
553
+ validateLegacyInteractiveWorkspace(nativeMaterializationState, binding.path, spec.cwd);
554
+ return manager.start(spec, undefined, { ...process.env, WREN_PROJECT_HOME: binding.path });
555
+ },
556
+ prepareInteractiveTerminal: async ({ target, binding }) => {
557
+ return (await prepareInteractiveTerminal(target, binding)).handoff;
558
+ },
559
+ getInteractiveTerminal: (id) => interactiveTerminal?.get(id),
560
+ revokeInteractiveTerminals: () => interactiveTerminal?.closeAll(),
561
+ interactiveTerminalReadiness: async () => {
562
+ if (enrichIrPath === undefined)
563
+ return unavailableInteractiveReadiness("interactive enrichment artifacts are not configured");
564
+ return getInteractiveTerminalReadiness({
565
+ getCurrentBinding: currentInteractiveBinding,
566
+ materializationState: nativeMaterializationState,
567
+ terminalHostAvailable: async () => {
568
+ try {
569
+ await loadPty();
570
+ return true;
571
+ }
572
+ catch {
573
+ return false;
574
+ }
575
+ },
576
+ });
577
+ },
578
+ workspaceRoot,
579
+ setWrenHomeForSetupMode,
580
+ ...(staticDir !== undefined ? { staticDir } : {}),
581
+ };
582
+ const app = createApp(deps);
583
+ const websocket = new WebSocketServer({ noServer: true });
584
+ // ws' `noServer` runtime shape is the one documented by @hono/node-server;
585
+ // its package types disagree under exactOptionalPropertyTypes.
586
+ const server = serve({ fetch: app.fetch, websocket: { server: websocket }, hostname: "127.0.0.1", port }, (info) => {
587
+ process.stdout.write(`wren-harness BFF listening on http://127.0.0.1:${info.port} (db: ${dbPath})\n`);
588
+ });
589
+ // Without this, a port that is already taken surfaces as an unhandled 'error' event and a raw
590
+ // Node stack trace -- the first thing a new user is likely to hit, and the least readable.
591
+ server.on("error", (error) => {
592
+ process.stderr.write(error.code === "EADDRINUSE"
593
+ ? `error: port ${port} is already in use — stop whatever is using it, or set PORT to a free one\n`
594
+ : `error: could not start the server on port ${port}: ${error.message}\n`);
595
+ process.exit(1);
596
+ });
597
+ }
598
+ main().catch((error) => {
599
+ process.stderr.write(`fatal: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
600
+ process.exitCode = 1;
601
+ });
@@ -0,0 +1,31 @@
1
+ import type { Store } from "./db.js";
2
+ /**
3
+ * Recover the in-memory project binding after a BFF restart.
4
+ *
5
+ * `bindProject` already persists an identity-fenced enrichment binding when
6
+ * Setup accepts a project. The process-local `boundProject` pointer used by the
7
+ * remaining Setup routes must be reconstructed from that durable proof, not
8
+ * from the setup form alone: the form is user input and a stale or replaced
9
+ * directory must remain unbound.
10
+ *
11
+ * The wizard's two entry paths leave different durable evidence, so recovery
12
+ * branches on the persisted `setup.mode` rather than applying one set of
13
+ * checks to both:
14
+ *
15
+ * - `"create"` scaffolds INTO the workspace root, and its connect turn writes
16
+ * the `.wren-validated` sentinel only after `wren profile add` genuinely
17
+ * validates the connection. Both facts are load-bearing evidence there, so
18
+ * that path keeps the workspace fence and the sentinel check.
19
+ * - `"adopt"` takes a project the user already has, anywhere on disk. Neither
20
+ * piece of evidence exists for it — containment is false by construction and
21
+ * the sentinel belongs to a flow that never ran. What stands in for them is
22
+ * that `bindProject` is reached only after adopt verification succeeds
23
+ * (`POST /api/setup/adopt`), so a *persisted binding at all* is that flow's
24
+ * equivalent proof.
25
+ *
26
+ * Identity equality is required on both paths and is what actually fences the
27
+ * adopt case: `resolveProjectIdentity` resolves device+inode, so a symlink
28
+ * repointed at another directory — or a different directory moved into the
29
+ * recorded path — no longer matches and stays unbound.
30
+ */
31
+ export declare function recoverBootstrapProjectBinding(store: Store, workspaceRoot: string): string | undefined;