@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,124 @@
1
+ /**
2
+ * Makes the selected data source's Python driver available before the connect
3
+ * turn starts, rather than letting the agent discover it is missing.
4
+ *
5
+ * wren ships connectors for twenty-one sources but installs almost none of
6
+ * their drivers: each lives behind an optional extra. Picking a source whose
7
+ * extra is absent used to fail deep inside the turn with whatever the connector
8
+ * happened to raise — a bare `ModuleNotFoundError` for most, and for Oracle an
9
+ * `AttributeError: 'NoneType' object has no attribute 'connect'`, since that
10
+ * module binds its driver to `None` on ImportError.
11
+ *
12
+ * Provisioning happens here, in the BFF, and not in the agent's turn: the
13
+ * agent-run `pip install` that the onboarding skill's Preflight used to perform
14
+ * was removed precisely because it could consume the whole turn. A deterministic
15
+ * step costs no turn budget, is testable, and does not depend on the model
16
+ * choosing to run it.
17
+ */
18
+ import { execFile } from "node:child_process";
19
+ import path from "node:path";
20
+ import { realpathSync, statSync } from "node:fs";
21
+ /**
22
+ * Sources that need no extra: their dependencies are wren's core ones. The four
23
+ * file sources and `duckdb` all route to wren's duckdb connector, whose driver
24
+ * is a core dependency; `datafusion` runs in-process on wren-core. Note wren's
25
+ * own `_INSTALL_EXTRA` maps the file sources to a `duckdb` extra that does not
26
+ * exist in its pyproject — harmless there because nothing needs installing,
27
+ * but a reason not to derive the extra name blindly.
28
+ */
29
+ const NO_EXTRA_SOURCES = new Set(["duckdb", "local_file", "s3_file", "minio_file", "gcs_file", "datafusion", "connection_url"]);
30
+ /** Sources whose driver ships under a differently-named extra (wren's `_INSTALL_EXTRA`). */
31
+ const EXTRA_ALIASES = { doris: "mysql", canner: "postgres" };
32
+ /** The driver extras wren's pyproject actually declares. */
33
+ const DRIVER_EXTRAS = new Set([
34
+ "postgres", "mysql", "bigquery", "snowflake", "clickhouse", "trino",
35
+ "mssql", "databricks", "redshift", "spark", "athena", "oracle",
36
+ ]);
37
+ /** The extra a source's driver ships under, or undefined when it needs none. */
38
+ export function driverExtraFor(sourceKey) {
39
+ if (NO_EXTRA_SOURCES.has(sourceKey))
40
+ return undefined;
41
+ const extra = EXTRA_ALIASES[sourceKey] ?? sourceKey;
42
+ return DRIVER_EXTRAS.has(extra) ? extra : undefined;
43
+ }
44
+ /**
45
+ * The interpreter of the wren install on PATH. `wren` is a console script inside
46
+ * its environment's `bin/`, so its realpath's sibling `python` is that
47
+ * environment — the one whose site-packages a connector import will search.
48
+ */
49
+ export function resolveWrenVenvPython() {
50
+ const found = process.env["PATH"]
51
+ ?.split(path.delimiter)
52
+ .map((dir) => path.join(dir, "wren"))
53
+ .find((candidate) => {
54
+ try {
55
+ return statSync(candidate).isFile();
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ });
61
+ if (found === undefined)
62
+ return undefined;
63
+ try {
64
+ const interpreter = path.join(path.dirname(realpathSync(found)), "python");
65
+ const stat = statSync(interpreter);
66
+ return stat.isFile() && (stat.mode & 0o111) !== 0 ? interpreter : undefined;
67
+ }
68
+ catch {
69
+ return undefined;
70
+ }
71
+ }
72
+ function runUv(args) {
73
+ return new Promise((resolve) => {
74
+ execFile("uv", args, { maxBuffer: 32 * 1024 * 1024 }, (error, stdout, stderr) => {
75
+ resolve({ stdout, stderr, error: error });
76
+ });
77
+ });
78
+ }
79
+ /**
80
+ * Installs the source's driver extra into the wren environment. Idempotent:
81
+ * `uv pip install` on an already-satisfied extra makes no changes, so this is
82
+ * safe to run before every connect turn without probing first.
83
+ *
84
+ * Installs the extra (`wrenai[postgres]`) rather than the driver package
85
+ * directly, so the pins come from wren rather than from a second list here.
86
+ * uv resolves the already-installed editable `wrenai` as satisfying itself, so
87
+ * this adds the driver without disturbing a local editable checkout.
88
+ */
89
+ export async function provisionSourceDriver(sourceKey, options = {}) {
90
+ const extra = driverExtraFor(sourceKey);
91
+ if (extra === undefined)
92
+ return { status: "not_needed" };
93
+ const interpreter = resolveWrenVenvPython();
94
+ if (interpreter === undefined) {
95
+ return { status: "unavailable", extra, detail: "could not locate the wren environment's interpreter from PATH" };
96
+ }
97
+ const run = options.run ?? runUv;
98
+ const { stdout, stderr, error } = await run(["pip", "install", "--python", interpreter, `wrenai[${extra}]`]);
99
+ if (error !== null) {
100
+ const detail = error.code === "ENOENT" ? "uv is not on PATH" : (stderr.trim() || error.message);
101
+ return { status: error.code === "ENOENT" ? "unavailable" : "failed", extra, detail };
102
+ }
103
+ // uv prints "Installed N packages" only when it changed something; an
104
+ // already-satisfied extra makes no changes.
105
+ const changed = /^\s*(Installed|Prepared)\s+\d+\s+package/m.test(stdout) || /^\s*\+ /m.test(stdout);
106
+ return changed ? { status: "installed", extra } : { status: "ready", extra };
107
+ }
108
+ /** One line describing the outcome for the composed prompt — verifiable, never a promise. */
109
+ export function driverProvisionNote(sourceKey, result) {
110
+ switch (result.status) {
111
+ case "not_needed":
112
+ return `The "${sourceKey}" connector needs no optional driver: its dependencies ship with wren.`;
113
+ case "installed":
114
+ return `The "${sourceKey}" driver (wren's "${result.extra}" extra) was just installed into the wren environment.`;
115
+ case "ready":
116
+ return `The "${sourceKey}" driver (wren's "${result.extra}" extra) is present in the wren environment.`;
117
+ default:
118
+ // Say what is actually known. The agent can check this in one command, so
119
+ // claiming the driver is available when provisioning did not succeed would
120
+ // be disproved immediately.
121
+ return (`The "${sourceKey}" driver (wren's "${result.extra}" extra) could NOT be provisioned: ${result.detail ?? "unknown reason"}. ` +
122
+ `Do not attempt to install it yourself. If a wren command fails because the driver is missing, stop and report exactly that — do not work around it.`);
123
+ }
124
+ }
@@ -0,0 +1,2 @@
1
+ import type { Hono } from "hono";
2
+ export declare function mountSpaFallback(app: Hono, staticDir: string): void;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Serves the built SPA (`vite build`'s `dist/`) from the same Hono app as
3
+ * the BFF's own `/api/*` routes, so a published build can run as one
4
+ * process on one port instead of a separate static host in front of the
5
+ * BFF. Mounting is opt-in: `server/app.ts`'s `createApp` only calls
6
+ * `mountSpaFallback` when `TurnDeps.staticDir` is wired, and `server/bin.ts`
7
+ * (the only place that wires real production values) only wires it when
8
+ * `<staticDir>/index.html` actually exists — a dev boot with no `dist/`
9
+ * never touches this module, so the vite-proxied dev flow is unaffected.
10
+ *
11
+ * MUST be mounted last, after every `/api/*` route `createApp` registers.
12
+ * Hono resolves an overlapping match by registration order — whichever
13
+ * matching, non-`next()`-calling handler was registered first wins — so a
14
+ * catch-all mounted any earlier would shadow real routes (worst case: an
15
+ * SSE route that still "connects" but silently never streams an event).
16
+ * Both middlewares below additionally check the path themselves rather
17
+ * than relying on mount order alone, so an `/api/*` request that somehow
18
+ * reaches this module (no earlier route matched it) still 404s instead of
19
+ * getting HTML.
20
+ */
21
+ import { existsSync, readFileSync } from "node:fs";
22
+ import path from "node:path";
23
+ import { serveStatic } from "@hono/node-server/serve-static";
24
+ function isApiPath(pathname) {
25
+ return pathname === "/api" || pathname.startsWith("/api/");
26
+ }
27
+ export function mountSpaFallback(app, staticDir) {
28
+ // Real files (including `/` -> `index.html`, handled internally by
29
+ // `serveStatic`) are served as-is. `/api/*` is excluded up front so a
30
+ // request under it is never resolved against `staticDir` at all.
31
+ app.use("*", async (c, next) => {
32
+ if (isApiPath(c.req.path))
33
+ return next();
34
+ return serveStatic({ root: staticDir })(c, next);
35
+ });
36
+ // Anything serveStatic didn't find. Three cases:
37
+ // - `/api/*` with no matching route: a real 404, never HTML.
38
+ // - `/assets/...` (Vite's own hashed-bundle convention) with no matching
39
+ // file: a real 404 — a missing/renamed build asset must not silently
40
+ // resolve to `index.html`.
41
+ // - anything else: a client-side route; serve `index.html` so the SPA's
42
+ // own router can take over.
43
+ app.get("*", (c) => {
44
+ if (isApiPath(c.req.path) || c.req.path.startsWith("/assets/"))
45
+ return c.notFound();
46
+ const indexPath = path.join(staticDir, "index.html");
47
+ if (!existsSync(indexPath))
48
+ return c.notFound();
49
+ return c.html(readFileSync(indexPath, "utf8"));
50
+ });
51
+ }
@@ -0,0 +1,27 @@
1
+ import type { SubscriptionModelCatalog, SubscriptionProvider } from "./wire-types.js";
2
+ export interface SubscriptionModelCatalogOptions {
3
+ readonly agentSdkBin?: string;
4
+ readonly codexLocalBin?: string;
5
+ readonly codexHome?: string;
6
+ readonly codexBin?: string;
7
+ /** The live project identity used by the runtime, when one is bound. */
8
+ readonly getUserProject: () => string | undefined;
9
+ /** Hermetic test seam. It receives no credentials and returns parsed JSON only. */
10
+ readonly execute?: (provider: SubscriptionProvider) => Promise<unknown>;
11
+ readonly now?: () => number;
12
+ }
13
+ /** Parses a provider payload into the public allowlist, intentionally discarding every other field. */
14
+ export declare function sanitizeSubscriptionModelCatalog(provider: SubscriptionProvider, payload: unknown): SubscriptionModelCatalog;
15
+ /**
16
+ * Runs a provider-owned command with bounded output and process-group cleanup.
17
+ * The test-only `onSpawn` hook observes only a PID; it never crosses the BFF
18
+ * wire boundary and lets the regression suite prove a TERM-ignoring child is
19
+ * eventually removed by the group KILL fallback.
20
+ */
21
+ export declare function spawnJson(command: string, args: readonly string[], options: {
22
+ readonly env: NodeJS.ProcessEnv;
23
+ readonly cwd?: string;
24
+ readonly onSpawn?: (pid: number) => void;
25
+ }): Promise<unknown>;
26
+ /** Builds the BFF seam and owns a small provider-keyed process-memory TTL cache. */
27
+ export declare function createSubscriptionModelCatalog(options: SubscriptionModelCatalogOptions): (provider: SubscriptionProvider, refresh: boolean) => Promise<SubscriptionModelCatalog>;
@@ -0,0 +1,203 @@
1
+ import { spawn } from "node:child_process";
2
+ import { resolveAgentSdkCli } from "../harness/index.js";
3
+ import { resolveCodexLocalCli } from "../harness/route/codex-local-cli.js";
4
+ const CATALOG_TIMEOUT_MS = 15_000;
5
+ const CATALOG_CACHE_MS = 30_000;
6
+ const MAX_OUTPUT_BYTES = 1024 * 1024;
7
+ const TERMINATION_GRACE_MS = 1_000;
8
+ function unavailable(provider, code, retryable = true) {
9
+ return { version: 1, status: "unavailable", provider, code, retryable };
10
+ }
11
+ function record(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
13
+ }
14
+ function nonEmptyString(value) {
15
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
16
+ }
17
+ function entry(value) {
18
+ const candidate = record(value);
19
+ const model = candidate && nonEmptyString(candidate.model);
20
+ const displayName = candidate && nonEmptyString(candidate.displayName);
21
+ if (!candidate || !model || !displayName)
22
+ return undefined;
23
+ const description = typeof candidate.description === "string" ? candidate.description : undefined;
24
+ const reasoningEfforts = Array.isArray(candidate.reasoningEfforts)
25
+ ? candidate.reasoningEfforts.map((effort) => {
26
+ const item = record(effort);
27
+ const value = item && nonEmptyString(item.value);
28
+ const effortDisplayName = item && nonEmptyString(item.displayName);
29
+ if (!item || !value || !effortDisplayName)
30
+ return undefined;
31
+ return {
32
+ value,
33
+ displayName: effortDisplayName,
34
+ ...(typeof item.description === "string" ? { description: item.description } : {}),
35
+ };
36
+ }).filter((item) => item !== undefined)
37
+ : undefined;
38
+ return {
39
+ model,
40
+ displayName,
41
+ ...(description !== undefined ? { description } : {}),
42
+ ...(typeof candidate.isDefault === "boolean" ? { isDefault: candidate.isDefault } : {}),
43
+ ...(reasoningEfforts && reasoningEfforts.length > 0 ? { reasoningEfforts } : {}),
44
+ };
45
+ }
46
+ /** Parses a provider payload into the public allowlist, intentionally discarding every other field. */
47
+ export function sanitizeSubscriptionModelCatalog(provider, payload) {
48
+ const result = record(payload);
49
+ if (!result || result.version !== 1 || result.provider !== provider)
50
+ return unavailable(provider, "protocol_error", false);
51
+ if (result.status === "unavailable") {
52
+ const code = result.code;
53
+ if (code !== "not_authenticated" && code !== "runtime_unavailable" && code !== "timeout" && code !== "protocol_error") {
54
+ return unavailable(provider, "protocol_error", false);
55
+ }
56
+ return unavailable(provider, code, typeof result.retryable === "boolean" ? result.retryable : true);
57
+ }
58
+ if (result.status !== "ready" || !Array.isArray(result.models))
59
+ return unavailable(provider, "protocol_error", false);
60
+ const models = result.models.map(entry);
61
+ if (models.some((model) => model === undefined))
62
+ return unavailable(provider, "protocol_error", false);
63
+ return { version: 1, status: "ready", provider, models: models };
64
+ }
65
+ async function runProviderCommand(provider, options) {
66
+ const cli = provider === "claude"
67
+ ? await resolveAgentSdkCli(options.agentSdkBin)
68
+ : await resolveCodexLocalCli(options.codexLocalBin);
69
+ // `list-models` is intentionally a provider-owned, no-turn command. Its
70
+ // documented flags mirror the active runtime's project/Codex identity; the
71
+ // process output still crosses this module only after allowlist validation.
72
+ const project = options.getUserProject();
73
+ const args = [
74
+ ...cli.prefixArgs,
75
+ "list-models",
76
+ ...(project ? ["--project", project] : []),
77
+ ...(provider === "codex" && options.codexHome ? ["--codex-home", options.codexHome] : []),
78
+ ...(provider === "codex" && options.codexBin ? ["--codex-bin", options.codexBin] : []),
79
+ "--timeout",
80
+ String(CATALOG_TIMEOUT_MS),
81
+ ];
82
+ return spawnJson(cli.command, args, { env: process.env });
83
+ }
84
+ /**
85
+ * Runs a provider-owned command with bounded output and process-group cleanup.
86
+ * The test-only `onSpawn` hook observes only a PID; it never crosses the BFF
87
+ * wire boundary and lets the regression suite prove a TERM-ignoring child is
88
+ * eventually removed by the group KILL fallback.
89
+ */
90
+ export function spawnJson(command, args, options) {
91
+ return new Promise((resolve, reject) => {
92
+ const child = spawn(command, [...args], {
93
+ env: options.env,
94
+ ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
95
+ stdio: ["ignore", "pipe", "pipe"],
96
+ detached: process.platform !== "win32",
97
+ });
98
+ let stdout = "";
99
+ let bytes = 0;
100
+ let settled = false;
101
+ let timedOut = false;
102
+ let cleanupStarted = false;
103
+ const signalProcessGroup = (signal) => {
104
+ if (child.pid !== undefined && process.platform !== "win32") {
105
+ try {
106
+ process.kill(-child.pid, signal);
107
+ return;
108
+ }
109
+ catch { /* direct-child fallback */ }
110
+ }
111
+ child.kill(signal);
112
+ };
113
+ const cleanup = () => {
114
+ if (cleanupStarted)
115
+ return;
116
+ cleanupStarted = true;
117
+ signalProcessGroup("SIGTERM");
118
+ // Never clear this fallback on an early caller settlement: the command
119
+ // may ignore TERM (or have descendants) after the BFF has returned its
120
+ // sanitized failure. KILLing the detached group prevents a leak.
121
+ setTimeout(() => signalProcessGroup("SIGKILL"), TERMINATION_GRACE_MS).unref();
122
+ };
123
+ const settle = (action) => {
124
+ if (settled)
125
+ return;
126
+ settled = true;
127
+ clearTimeout(timer);
128
+ action();
129
+ };
130
+ const abort = (error) => {
131
+ cleanup();
132
+ settle(() => reject(new Error(error)));
133
+ };
134
+ if (child.pid !== undefined)
135
+ options.onSpawn?.(child.pid);
136
+ const timer = setTimeout(() => {
137
+ timedOut = true;
138
+ abort("timeout");
139
+ }, CATALOG_TIMEOUT_MS);
140
+ child.stdout.on("data", (chunk) => {
141
+ if (settled)
142
+ return;
143
+ bytes += chunk.length;
144
+ if (bytes > MAX_OUTPUT_BYTES) {
145
+ abort("protocol_error");
146
+ return;
147
+ }
148
+ stdout += chunk.toString("utf8");
149
+ });
150
+ // Drain stderr without retaining it: provider details must never cross the BFF boundary.
151
+ child.stderr.resume();
152
+ child.on("error", () => settle(() => reject(new Error("runtime_unavailable"))));
153
+ child.on("close", (code) => settle(() => {
154
+ if (timedOut) {
155
+ reject(new Error("timeout"));
156
+ return;
157
+ }
158
+ if (code !== 0) {
159
+ reject(new Error("runtime_unavailable"));
160
+ return;
161
+ }
162
+ try {
163
+ resolve(JSON.parse(stdout));
164
+ }
165
+ catch {
166
+ reject(new Error("protocol_error"));
167
+ }
168
+ }));
169
+ });
170
+ }
171
+ /** Builds the BFF seam and owns a small provider-keyed process-memory TTL cache. */
172
+ export function createSubscriptionModelCatalog(options) {
173
+ const cache = new Map();
174
+ const latestRequest = new Map();
175
+ const now = options.now ?? Date.now;
176
+ return async (provider, refresh) => {
177
+ const cached = cache.get(provider);
178
+ if (!refresh && cached && cached.expiresAt > now())
179
+ return cached.result;
180
+ const requestId = (latestRequest.get(provider) ?? 0) + 1;
181
+ latestRequest.set(provider, requestId);
182
+ const writeCacheIfCurrent = (result) => {
183
+ // A refresh is authoritative over every request that preceded it. An
184
+ // older promise may still resolve for its original caller, but cannot
185
+ // replace the provider cache used by subsequent reads.
186
+ if (latestRequest.get(provider) === requestId) {
187
+ cache.set(provider, { expiresAt: now() + CATALOG_CACHE_MS, result });
188
+ }
189
+ return result;
190
+ };
191
+ try {
192
+ const raw = await (options.execute ? options.execute(provider) : runProviderCommand(provider, options));
193
+ const result = sanitizeSubscriptionModelCatalog(provider, raw);
194
+ return writeCacheIfCurrent(result);
195
+ }
196
+ catch (error) {
197
+ const message = error instanceof Error ? error.message : "runtime_unavailable";
198
+ const code = message === "timeout" ? "timeout" : message === "protocol_error" ? "protocol_error" : "runtime_unavailable";
199
+ const result = unavailable(provider, code, code !== "protocol_error");
200
+ return writeCacheIfCurrent(result);
201
+ }
202
+ };
203
+ }
@@ -0,0 +1,249 @@
1
+ import type { AuthChoice, Bundle, LoginProbe, RouteOptions, RouteResult, SetupStepRunner } from "../harness/index.js";
2
+ import type { SubscriptionModelCatalog, SubscriptionProvider } from "./wire-types.js";
3
+ import { type Store } from "./db.js";
4
+ import type { EnrichmentApprovalProvider, EnrichmentApplyRunner, EnrichmentRunner } from "./enrichment.js";
5
+ import type { verifyProposal } from "./enrichment-verify.js";
6
+ import type { EnrichmentBinding } from "./enrichment.js";
7
+ import type { InteractiveTarget, InteractiveTargetReadiness, InteractiveTerminalSession, PreparedInteractiveHandoff } from "./interactive-terminal.js";
8
+ import type { NativeSessionService } from "./native-sessions.js";
9
+ import type { NativeArtifactService } from "./native-artifacts.js";
10
+ import type { NativePurpose } from "./native-dispatch-registry.js";
11
+ import type { AskSessionData, ClarifyEvent, PublishedEvent, PublishScope, SavedEvent, SetupMode, SseFrame, UnsavedEvent } from "./wire-types.js";
12
+ export declare class SessionNotFoundError extends Error {
13
+ readonly sessionId: string;
14
+ constructor(sessionId: string);
15
+ }
16
+ export declare class TurnNotFoundError extends Error {
17
+ readonly turnId: string;
18
+ constructor(turnId: string);
19
+ }
20
+ export declare class ArtifactNotFoundError extends Error {
21
+ readonly artifactId: string;
22
+ constructor(artifactId: string);
23
+ }
24
+ export interface TurnDeps {
25
+ readonly store: Store;
26
+ readonly route: (options: RouteOptions) => Promise<RouteResult>;
27
+ /** A native vendor TUI host, deliberately independent from Ask/SSE. */
28
+ readonly startInteractiveTerminal?: (input: {
29
+ readonly target: InteractiveTarget;
30
+ readonly binding: EnrichmentBinding;
31
+ }) => Promise<InteractiveTerminalSession>;
32
+ readonly getInteractiveTerminal?: (id: string) => InteractiveTerminalSession | undefined;
33
+ /** Revokes compatibility terminals outside the durable NativeSessionService. */
34
+ readonly revokeInteractiveTerminals?: () => void;
35
+ readonly interactiveTerminalReadiness?: () => Promise<Record<InteractiveTarget, InteractiveTargetReadiness>>;
36
+ readonly prepareInteractiveTerminal?: (input: {
37
+ readonly target: InteractiveTarget;
38
+ readonly binding: EnrichmentBinding;
39
+ }) => Promise<PreparedInteractiveHandoff>;
40
+ /** Durable native Sessions control plane; intentionally distinct from Ask. */
41
+ /**
42
+ * Refutes a drafted enrichment proposal before it is offered for approval.
43
+ * Injected rather than imported so a test can exercise the surrounding flow
44
+ * without a real project on disk — and, more importantly, so the production
45
+ * default is the real ladder rather than something a caller can weaken.
46
+ */
47
+ readonly verifyEnrichmentProposal?: typeof verifyProposal;
48
+ readonly nativeSessions?: NativeSessionService;
49
+ /** Session-scoped MCP persistence service; never used by Ask publish routes. */
50
+ readonly nativeArtifacts?: NativeArtifactService;
51
+ readonly baseRouteOptions: Omit<RouteOptions, "question" | "onEvent">;
52
+ /**
53
+ * Ask/compile-bind only: compiles+loads the analysis profile's bundle.
54
+ * Harness purpose introspection uses `describeHarnessBundle` below. Optional so
55
+ * every other route and every existing test's `TurnDeps` literal is
56
+ * unaffected — `server/bin.ts` wires the real `describeBundle` (from
57
+ * `../harness/index.js`); a `TurnDeps` without it simply can't serve
58
+ * `/api/harness` (that route reports a 500 instead of throwing).
59
+ */
60
+ readonly describeBundle?: (options: Omit<RouteOptions, "question" | "onEvent">) => Promise<Bundle>;
61
+ /**
62
+ * Harness-only bundle description for one closed native purpose. The
63
+ * purpose is parsed by the BFF from its read-only query vocabulary; this
64
+ * dependency resolves the corresponding server-owned profile source. It is
65
+ * intentionally distinct from native-session IR/launch wiring.
66
+ */
67
+ readonly describeHarnessBundle?: (purpose: NativePurpose, options: Omit<RouteOptions, "question" | "onEvent">) => Promise<Bundle>;
68
+ /**
69
+ * Compiles the canonical post-bind profile without rebinding its context and
70
+ * returns its declared tier names. Unlike `describeBundle`, this seam is
71
+ * intentionally auth-independent and works before a user project is bound.
72
+ */
73
+ readonly getRuntimeTierNames?: () => Promise<readonly string[]>;
74
+ /**
75
+ * Dispatches a setup-wizard turn (`connect` / `connect_resume` — see
76
+ * `TurnRow.setupStepKey`) via `harness/setup/runner.ts`'s `SetupStepRunner`
77
+ * seam, instead of the normal `route()` path. Optional so every existing
78
+ * test's `TurnDeps` literal is unaffected; a setup turn dispatched against
79
+ * a `TurnDeps` without this wired resolves as an error turn with a clear
80
+ * "not configured" message rather than throwing.
81
+ */
82
+ readonly setupRunner?: SetupStepRunner;
83
+ /**
84
+ * Resolves which `SetupStepRunner` a setup turn should dispatch through,
85
+ * given the auth choice actually in effect (Claude subscription, Codex
86
+ * subscription, or non-subscription — the same selection `server/bin.ts`
87
+ * uses at boot). Optional so every existing test's `TurnDeps` literal is
88
+ * unaffected: when absent, `resolveSetupRunner` falls back to the fixed
89
+ * `setupRunner` above (the pre-live-auth-choice behavior). Wired by
90
+ * `server/bin.ts` so a setup turn dispatches through the runner matching
91
+ * whatever auth mode is ACTUALLY bound at run time, not the one resolved at
92
+ * boot.
93
+ */
94
+ setupRunnerFor?(choice: AuthChoice): SetupStepRunner;
95
+ /**
96
+ * The workspace root new wren projects are scaffolded under
97
+ * (`WREN_HARNESS_WORKSPACE_ROOT`) — the `root` half of
98
+ * `parseSetupTerminal`'s `{root, name}` context, and `SetupStepRunOptions.workspaceRoot`.
99
+ * Optional for the same reason as `setupRunner` above.
100
+ */
101
+ readonly workspaceRoot?: string;
102
+ /**
103
+ * Binds (or rebinds) the process's single active wren project — the
104
+ * bootstrap mode's mutable project binding (see `server/bin.ts`). Optional:
105
+ * a `TurnDeps` built with a fixed `baseRouteOptions.userProject` (every
106
+ * existing test, and any non-bootstrap boot) simply has no rebinding
107
+ * capability, which is fine — `resolveUserProject` falls back to the fixed
108
+ * `baseRouteOptions.userProject` when this isn't wired.
109
+ */
110
+ bindProject?(dir: string): void;
111
+ /**
112
+ * Reads the process's currently bound project, if any. Optional; when
113
+ * absent, `resolveUserProject` reads `baseRouteOptions.userProject` instead
114
+ * (the pre-bootstrap-mode, single-fixed-project behavior).
115
+ */
116
+ getUserProject?(): string | undefined;
117
+ /**
118
+ * Clears the bootstrap project binding (back to unbound), for the setup
119
+ * wizard's "Reset setup" action. Optional and symmetric with `bindProject`:
120
+ * a fixed-project (non-bootstrap) `TurnDeps` has no binding to clear.
121
+ */
122
+ unbindProject?(): void;
123
+ /**
124
+ * Reads the process's currently bound auth choice, if a live binding is
125
+ * wired — the auth-choice mirror of `getUserProject`. Optional; when
126
+ * absent, `resolveAuthChoice` reads `baseRouteOptions.authChoice` instead
127
+ * (the pre-live-auth-choice, boot-fixed behavior every existing test's
128
+ * `TurnDeps` literal still gets).
129
+ */
130
+ getAuthChoice?(): AuthChoice;
131
+ /**
132
+ * Rebinds the process's live auth choice — the auth-choice mirror of
133
+ * `bindProject`. Wired by `server/bin.ts`; `PUT /api/config/runtime`
134
+ * (`server/app.ts`) calls this once a candidate `AuthChoice` has passed the
135
+ * compliance gate, so every later `route()`/setup dispatch reads the live
136
+ * binding through `resolveAuthChoice`/`effectiveRouteOptions` instead of the
137
+ * boot-fixed `baseRouteOptions.authChoice`.
138
+ */
139
+ setAuthChoice?(choice: AuthChoice): void;
140
+ /** Injectable, boolean-only subscription login probe used by config UI/API. */
141
+ readonly loginProbe?: LoginProbe;
142
+ /** Provider-owned, already-sanitized model discovery for the Setup form. */
143
+ readonly listSubscriptionModels?: (provider: SubscriptionProvider, refresh: boolean) => Promise<SubscriptionModelCatalog>;
144
+ /** Optional post-bind enrichment seams; absence is an intentional fail-closed runtime wall. */
145
+ readonly enrichmentRunner?: EnrichmentRunner;
146
+ readonly enrichmentApplyRunner?: EnrichmentApplyRunner;
147
+ readonly enrichmentApprovalProvider?: EnrichmentApprovalProvider;
148
+ /**
149
+ * Points every subsequent `wren` CLI subprocess (via `harness/exec/local.ts`'s
150
+ * `execFile` and `server/adopt.ts`'s own `execWren`, both of which inherit
151
+ * `process.env` verbatim) at the right `WREN_HOME` for the wizard's chosen
152
+ * entry path: `"create"` anchors it to `<workspaceRoot>/.wren` so a
153
+ * scaffolded project's `wren profile add` writes into a fresh,
154
+ * workspace-scoped `profiles.yml` instead of the operator's real
155
+ * `~/.wren/profiles.yml`; `"adopt"` (or `undefined`, e.g.
156
+ * after "Reset setup") restores the real baseline `WREN_HOME` the process
157
+ * booted with, since adopt is strictly read-only against global state and
158
+ * must keep resolving the operator's own profiles. Optional so every
159
+ * existing test's `TurnDeps` literal is unaffected — a `TurnDeps` without
160
+ * this wired just leaves `process.env.WREN_HOME` untouched (the
161
+ * pre-isolation behavior). Wired by `server/bin.ts`; called from
162
+ * `server/app.ts`'s `dispatchConnectTurn` (create) and `POST
163
+ * /api/setup/adopt` (adopt) — the two single choke points every actual
164
+ * `wren` invocation for each mode passes through — plus `POST
165
+ * /api/setup/reset` (restores baseline) so a mode change mid-boot (pick
166
+ * create, reset, pick adopt, or the reverse) can never leave a stale
167
+ * `WREN_HOME` from the previous choice in effect.
168
+ */
169
+ setWrenHomeForSetupMode?(mode: SetupMode | undefined): void;
170
+ /**
171
+ * Absolute path to a built SPA (`vite build`'s `dist/`, containing
172
+ * `index.html`) to serve from this same process, same origin as every
173
+ * `/api/*` route below. Optional so every existing test's `TurnDeps`
174
+ * literal is unaffected: when absent, `server/app.ts`'s `createApp` never
175
+ * mounts the SPA fallback and the BFF behaves exactly as it does today
176
+ * (no static serving at all — the dev flow's vite proxy on a separate
177
+ * port is the only way the UI is served). `server/bin.ts` wires this only
178
+ * when `<staticDir>/index.html` actually exists, so a dev boot with no
179
+ * `dist/` present leaves this unset.
180
+ */
181
+ readonly staticDir?: string;
182
+ }
183
+ /**
184
+ * Thrown by `effectiveRouteOptions` when no wren project is bound yet
185
+ * (bootstrap-mode, before the setup wizard's bind step). Routes that require
186
+ * a bound project (`server/app.ts`'s 409 guard) check `isProjectBound` first
187
+ * and never actually let this escape in normal operation — it exists mainly
188
+ * so a caller that forgets the guard fails loudly instead of silently
189
+ * compiling against `undefined`.
190
+ */
191
+ /** Shared 409 message for every route that requires a bound project (`server/app.ts`'s guard) — single source of truth so the guard and `ProjectNotBoundError` never drift apart. */
192
+ export declare const PROJECT_NOT_BOUND_MESSAGE = "no wren project is bound yet \u2014 complete the setup wizard's connect/bind steps first";
193
+ export declare class ProjectNotBoundError extends Error {
194
+ constructor();
195
+ }
196
+ /** Whether a wren project is currently bound (always true for a `TurnDeps` without `getUserProject` wired — the pre-bootstrap-mode case). */
197
+ export declare function isProjectBound(deps: TurnDeps): boolean;
198
+ /** Resolves the currently-bound project directory, preferring the mutable `getUserProject` binding over the fixed `baseRouteOptions.userProject`. */
199
+ export declare function resolveUserProject(deps: TurnDeps): string | undefined;
200
+ /** Resolves the auth choice actually in effect, preferring the mutable `getAuthChoice` binding over the fixed `baseRouteOptions.authChoice` — the auth-choice mirror of `resolveUserProject`. */
201
+ export declare function resolveAuthChoice(deps: TurnDeps): AuthChoice;
202
+ /** Resolves which `SetupStepRunner` a setup turn should dispatch through, given the auth choice actually in effect. Falls back to the fixed `deps.setupRunner` when `setupRunnerFor` isn't wired (pre-live-auth-choice behavior). */
203
+ export declare function resolveSetupRunner(deps: TurnDeps): SetupStepRunner | undefined;
204
+ /**
205
+ * `deps.baseRouteOptions` with `userProject`/`authChoice` re-resolved through
206
+ * their mutable bindings at call time. By default this throws while unbound;
207
+ * the read-only bootstrap Harness descriptor is the one deliberate exception
208
+ * and receives an empty project value that its raw-profile path never reads.
209
+ */
210
+ export declare function effectiveRouteOptions(deps: TurnDeps, options?: {
211
+ readonly allowUnbound?: boolean;
212
+ }): Omit<RouteOptions, "question" | "onEvent">;
213
+ /** Evicts `deps`'s memoized compiled-bundle-agent-ids entry (see `bundleAgentInfoCache` below) — called by `bindProject` so a turn after a (re)bind recompiles against the newly-bound project instead of replaying a stale/empty cache entry. */
214
+ export declare function invalidateBundleAgentIdsCache(deps: TurnDeps): void;
215
+ export interface PostTurnResult {
216
+ readonly turnId: string;
217
+ readonly clarify?: ClarifyEvent;
218
+ }
219
+ /** POST /api/sessions/:id/turns — persists the user event, runs the D1 clarify pre-flight, and either short-circuits with a clarify prompt or prepares a turn for streaming. */
220
+ export declare function postTurn(deps: TurnDeps, sessionId: string, question: string): Promise<PostTurnResult>;
221
+ /** GET (SSE) /api/sessions/:id/stream?turn=:turnId — replays a resolved turn, or executes+streams a pending one. */
222
+ export declare function streamTurn(deps: TurnDeps, sessionId: string, turnId: string, emit: (frame: SseFrame) => Promise<void>, signal?: AbortSignal): Promise<void>;
223
+ /** GET /api/sessions/:id — the Ask page's session snapshot: full event log + the latest turn's work log. */
224
+ export declare function getAskSessionData(store: Store, sessionId: string): AskSessionData;
225
+ /** POST /api/sessions/:id/artifacts/:artifactId/publish */
226
+ export declare function publishArtifactForSession(store: Store, sessionId: string, artifactId: string, scope: PublishScope): PublishedEvent;
227
+ /**
228
+ * POST /api/sessions/:id/artifacts/:artifactId/save — promotes an
229
+ * auto-created artifact onto the Artifacts page. Idempotent: a repeat call
230
+ * on an already-saved artifact returns the same `savedAt` (via
231
+ * `store.saveArtifact`'s own idempotency) and does NOT insert a second
232
+ * `SavedEvent` into the session's event log — otherwise every no-op re-save
233
+ * would grow the persisted history with duplicate events.
234
+ */
235
+ export declare function saveArtifactForSession(store: Store, sessionId: string, artifactId: string): SavedEvent;
236
+ /**
237
+ * POST /api/sessions/:id/artifacts/:artifactId/unsave — the
238
+ * mirror of `saveArtifactForSession`: unpins an artifact from the Artifacts
239
+ * page. The artifact row and its envelope file are left exactly as they are
240
+ * — only `saved_at` clears — so the artifact remains re-saveable. Idempotent:
241
+ * a repeat call on an already-unsaved artifact does NOT append a second
242
+ * `UnsavedEvent` into the session's event log, for the same reason
243
+ * `saveArtifactForSession` guards its own repeat calls.
244
+ *
245
+ * Appends an `UnsavedEvent` rather than deleting the earlier `SavedEvent` —
246
+ * the event log is append-only/replayed (see `getAskSessionData`), so
247
+ * "unsaved" is recorded as a newer fact, never as erasing an older one.
248
+ */
249
+ export declare function unsaveArtifactForSession(store: Store, sessionId: string, artifactId: string): UnsavedEvent;