@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,439 @@
1
+ /**
2
+ * GET /api/harness — read-only introspection of how the CURRENTLY compiled
3
+ * profile is realized. `buildHarnessDto` is a pure mapping from an
4
+ * already-loaded `Bundle` + the store's runtime settings/context + the
5
+ * harness's own base route options onto the BFF's `HarnessDto` wire
6
+ * contract — it does no compiling and touches no turn/answer logic.
7
+ *
8
+ * Each component's `model`/`tiers` are derived from the REAL
9
+ * tier -> model binding `route()`/`runInProcessDefault` would actually apply for
10
+ * `baseRouteOptions` (see `buildRealTierResolver`), not from the Setup
11
+ * page's editable seeded `store.getRuntimeSettings()`. Those two can
12
+ * legitimately differ (e.g. a seeded `claude-haiku`/`claude-sonnet` display
13
+ * while `WREN_HARNESS_MODEL`/a gateway override actually routes every tier
14
+ * to `gpt-4o`), and per-component introspection's whole job is to report
15
+ * what that component would really run.
16
+ *
17
+ * `runtime.tierModels` (the top-level runtime summary) shares that same
18
+ * effective resolver, so it cannot disagree with the per-component rows.
19
+ * Explicit subscription settings are knowable because this process generated
20
+ * the dispatcher models config from them; an unsaved boot-time subscription
21
+ * config remains opaque and is labeled as such.
22
+ *
23
+ * Naming note — bundle "components" (`HarnessDto.components`,
24
+ * `bundle.agents`) are presented in the frontend as **Components** — not
25
+ * "sub-agents" (that framing is reserved for the profile level). This
26
+ * harness runtime routes exactly ONE component per turn from the user's
27
+ * intent (for example `answer_query` or `generate_dashboard`) — the other
28
+ * bundle agents surfaced here are read-only declared components of the
29
+ * compiled bundle, not concurrently-running sub-agents. This comment plus the one on
30
+ * `HarnessComponent`/`HarnessDto` in `wire-types.ts` document that framing.
31
+ */
32
+ import { createHash } from "node:crypto";
33
+ import path from "node:path";
34
+ import { describeConnection, resolveConnectionSource } from "./conn-config.js";
35
+ import { bundleFormatVersion, deriveAdapterSpec, findLockedGatedCheck } from "../harness/index.js";
36
+ import { assertHarnessPurposeProfile, NATIVE_DISPATCH_REGISTRY } from "./native-dispatch-registry.js";
37
+ import { collectBundleTierNames as collectCompiledBundleTierNames, effectiveTierModel } from "./runtime-binding.js";
38
+ /** snake_case/kebab-case id -> Title Case display name, e.g. "answer_query" -> "Answer Query". */
39
+ function humanize(id) {
40
+ return id
41
+ .split(/[_-]+/)
42
+ .filter((word) => word.length > 0)
43
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
44
+ .join(" ");
45
+ }
46
+ function isNonEmptyString(value) {
47
+ return typeof value === "string" && value.trim().length > 0;
48
+ }
49
+ /**
50
+ * Resolves an `AdapterSpec` to the display model string the runtime will
51
+ * ACTUALLY realize — i.e. the same `config.model` field the `anthropic`/
52
+ * `openai-compatible` adapters consult (`harness/providers/adapters/*.ts`). An
53
+ * adapter with no concrete `model` string in its config (e.g. `mock`, which
54
+ * only exists for tests/synthetic bundles and takes a scriptable generator
55
+ * instead of a model name) has no real model to show — label it honestly
56
+ * rather than fabricate a model name.
57
+ */
58
+ function adapterDisplayModel(spec) {
59
+ const config = spec.config;
60
+ const model = config && isNonEmptyString(config["model"]) ? config["model"] : undefined;
61
+ return model ?? `${spec.adapter} (no fixed model)`;
62
+ }
63
+ /**
64
+ * Store's Setup-editable seeded tier -> model map, consulted ONLY as the
65
+ * last-resort fallback when the real binding genuinely can't resolve a tier
66
+ * (see `buildRealTierResolver`) — e.g. a hybrid `tierBinding` that doesn't
67
+ * cover a tier some other bundle component uses, or a `gateway` auth choice
68
+ * missing its required config. Always labeled so it's never mistaken for a
69
+ * measured/real value.
70
+ */
71
+ function configuredFallbackModel(tier, store) {
72
+ const configured = store.getRuntimeSettings().tierModels.find((binding) => binding.tier === tier)?.model;
73
+ return configured !== undefined ? `${configured} (configured)` : `${tier} (unbound)`;
74
+ }
75
+ /**
76
+ * Explicit persisted model source. It is authoritative only after a validated
77
+ * save; seeded Setup defaults never use this path.
78
+ */
79
+ function storeTierModel(tier, store) {
80
+ const settings = store.getRuntimeSettings();
81
+ const configured = settings.tierModels.find((binding) => binding.tier === tier);
82
+ return configured ? effectiveTierModel(configured, settings) ?? `${tier} (unbound)` : `${tier} (unbound)`;
83
+ }
84
+ /**
85
+ * Builds a resolver mirroring the tier -> model binding `route()`/
86
+ * `runInProcessDefault` (`harness/route/in-process.ts`) actually apply for
87
+ * `baseRouteOptions`, so `runtime.tierModels` and each component's `model`
88
+ * reflect what really runs rather than the Setup page's seeded settings:
89
+ *
90
+ * - `subscription` (dispatched): there is no per-tier `AdapterSpec` at all — the
91
+ * provider-specific subscription dispatcher owns model routing internally, and any
92
+ * `--models-config` override is opaque YAML this harness never parses (see
93
+ * `DispatchedOptions.modelsConfig`'s doc comment) — so every tier honestly
94
+ * reports the same subscription label (matching `runtimeModeAndLabel`'s
95
+ * own `"Subscription (<provider>)"` wording).
96
+ * - a `tierBinding` override present (hybrid mode, in-process only): resolves
97
+ * per-tier via the caller-supplied `AdapterSpec` map, exactly like
98
+ * `buildHybridTierBinding` does for the `answer_query` agent at run time.
99
+ * A tier this map doesn't cover (e.g. a different bundle component uses a
100
+ * tier name `answer_query` never uses, so the hybrid map was never
101
+ * required to cover it) is genuinely unresolvable from the real binding —
102
+ * falls back to the Setup-configured value, visibly labeled as such.
103
+ * - otherwise (uniform, in-process's default path): derives ONE `AdapterSpec` via
104
+ * `deriveAdapterSpec(authChoice, model)` — the exact call `runInProcessDefault`
105
+ * itself makes — which realizes every tier alike. If that derivation
106
+ * itself throws (e.g. `gateway` mode missing `baseURL`/`model`), the same
107
+ * failure would occur at turn-execution time too; report every tier via
108
+ * the configured fallback rather than 500ing `GET /api/harness`.
109
+ */
110
+ function buildRealTierResolver(baseRouteOptions, store) {
111
+ const { authChoice } = baseRouteOptions;
112
+ if (authChoice.mode === "subscription") {
113
+ if (store.hasExplicitRuntimeSettings()) {
114
+ return { resolve: (tier) => storeTierModel(tier, store) };
115
+ }
116
+ const label = `Subscription (${authChoice.provider})`;
117
+ return { resolve: () => label };
118
+ }
119
+ if (baseRouteOptions.tierBinding !== undefined) {
120
+ const hybrid = baseRouteOptions.tierBinding;
121
+ return {
122
+ resolve: (tier) => {
123
+ const spec = hybrid[tier];
124
+ return spec ? adapterDisplayModel(spec) : configuredFallbackModel(tier, store);
125
+ },
126
+ };
127
+ }
128
+ try {
129
+ const uniformModel = adapterDisplayModel(deriveAdapterSpec(authChoice, baseRouteOptions.model !== undefined ? { model: baseRouteOptions.model } : {}));
130
+ return { resolve: () => uniformModel };
131
+ }
132
+ catch {
133
+ return { resolve: (tier) => configuredFallbackModel(tier, store) };
134
+ }
135
+ }
136
+ /**
137
+ * `backend` mirrors `authChoice.mode` verbatim — a real, already-
138
+ * meaningful auth-strategy name — rather than the internal `inProcess`/`dispatched`
139
+ * framework-dispatch bucket the DTO used to leak.
140
+ */
141
+ function runtimeBackendAndLabel(authChoice) {
142
+ switch (authChoice.mode) {
143
+ case "subscription":
144
+ return { backend: "subscription", label: `Subscription (${authChoice.provider})` };
145
+ case "api-key":
146
+ return { backend: "api-key", label: `API key (${authChoice.adapter})` };
147
+ case "local":
148
+ return { backend: "local", label: authChoice.endpoint ? `Local (${authChoice.endpoint})` : "Local" };
149
+ case "gateway":
150
+ return { backend: "gateway", label: "Gateway" };
151
+ }
152
+ }
153
+ /**
154
+ * Reports the provider-specific dispatcher that owns the configured product
155
+ * path: Claude subscription uses `claude-agent-sdk`, Codex subscription uses
156
+ * `codex-local`, and non-subscription modes run in-process.
157
+ */
158
+ function runtimeDispatcher(authChoice) {
159
+ if (authChoice.mode !== "subscription")
160
+ return "in-process";
161
+ return authChoice.provider === "codex" ? "codex-local" : "claude-agent-sdk";
162
+ }
163
+ function buildCapability(capability) {
164
+ const outcome = capability.outcome === "native" ? "native" : "realize-via";
165
+ return {
166
+ capability: capability.capability,
167
+ outcome,
168
+ providedBy: capability.provided_by,
169
+ ...(capability.criticality !== undefined ? { criticality: capability.criticality } : {}),
170
+ };
171
+ }
172
+ function buildGuardrails(guardrails) {
173
+ return Object.entries(guardrails).map(([name, guardrail]) => ({
174
+ name,
175
+ enforcement: guardrail.enforcement,
176
+ locked: guardrail.locked,
177
+ ...(guardrail.threshold !== undefined ? { threshold: guardrail.threshold } : {}),
178
+ }));
179
+ }
180
+ function isRecord(value) {
181
+ return typeof value === "object" && value !== null && !Array.isArray(value);
182
+ }
183
+ /**
184
+ * Extracts the declared render-block `type` consts from an agent's
185
+ * `output_schema` — `blocks.items.anyOf[].properties.type.const` when the
186
+ * schema unions multiple block shapes (e.g. `answer_query`'s
187
+ * table/definition), or the single-item form (`blocks.items.properties.type.const`
188
+ * directly, no `anyOf`) when it only ever emits one block shape (e.g.
189
+ * `explain_change`'s `narrative`). Deliberately defensive — `output_schema`
190
+ * is stored as an untyped JSON Schema document (see `OutputSchema` in
191
+ * `harness/bundle/schema.ts`), so this tolerates a missing `blocks`/`items`, no
192
+ * `anyOf`, or a variant with no `const`, yielding `[]` rather than throwing.
193
+ */
194
+ function extractOutputBlocks(outputSchema) {
195
+ const properties = isRecord(outputSchema) ? outputSchema["properties"] : undefined;
196
+ const blocks = isRecord(properties) ? properties["blocks"] : undefined;
197
+ const items = isRecord(blocks) ? blocks["items"] : undefined;
198
+ if (!isRecord(items))
199
+ return [];
200
+ const variants = Array.isArray(items["anyOf"]) ? items["anyOf"] : [items];
201
+ const consts = [];
202
+ for (const variant of variants) {
203
+ if (!isRecord(variant))
204
+ continue;
205
+ const variantProperties = isRecord(variant["properties"]) ? variant["properties"] : undefined;
206
+ const typeProperty = variantProperties && isRecord(variantProperties["type"]) ? variantProperties["type"] : undefined;
207
+ const constValue = typeProperty?.["const"];
208
+ if (isNonEmptyString(constValue))
209
+ consts.push(constValue);
210
+ }
211
+ return consts;
212
+ }
213
+ function buildStep(step) {
214
+ return {
215
+ name: step.name,
216
+ tier: step.tier,
217
+ consumes: step.consumes,
218
+ produces: step.produces,
219
+ realization: step.realization.kind,
220
+ ...(step.when?.guard !== undefined ? { guard: step.when.guard } : {}),
221
+ ...(step.realization.fold_into !== undefined ? { foldInto: step.realization.fold_into } : {}),
222
+ ...(step.realization.max_attempts !== undefined ? { maxAttempts: step.realization.max_attempts } : {}),
223
+ };
224
+ }
225
+ // The agent's distinct step tiers, each resolved to its REAL model via `resolver` (not the
226
+ // Setup-editable store) — mirrors what `runInProcessDefault` would actually bind for this agent's
227
+ // steps. The runtime routes one component per turn; each component's tiers/model report the
228
+ // real binding that applies when that component is selected.
229
+ /** Public, purpose-level reason used only when native readiness cannot execute the selected purpose. */
230
+ const NATIVE_PURPOSE_UNAVAILABLE_REASON = "The selected native session is unavailable.";
231
+ /**
232
+ * A component can be unavailable for two independent reasons, and only one
233
+ * of them is ever safe to promote:
234
+ *
235
+ * - "bundle-level" (`"availability" in agent`): Warble's compiled dispatch
236
+ * target declared this component unavailable on the PROGRAMMATIC path
237
+ * (e.g. `apply_enrichment`'s `human_approval: fail`) — but the currently
238
+ * selected purpose's native CLI session may still be able to run it. When
239
+ * it can, this promotes the row to `"ready"`, qualified by the native
240
+ * target label, and moves the programmatic limitation into
241
+ * `nativeAvailability` for the expanded row rather than dropping it.
242
+ * - "purpose-level" (native session readiness for the selected purpose):
243
+ * there is no execution path at all right now — never promoted.
244
+ *
245
+ * The branch taken here (not any comparison against Warble's reason string)
246
+ * is what decides promotion, per the DTO's typed `nativeAvailability` field.
247
+ */
248
+ function buildComponent(agent, resolver, dispatchTarget, purposeInfo) {
249
+ if ("availability" in agent) {
250
+ const viaLabel = purposeInfo.executionKind === "native_session" && purposeInfo.available
251
+ ? purposeInfo.targetLabel
252
+ : undefined;
253
+ return {
254
+ id: agent.id,
255
+ name: humanize(agent.id),
256
+ componentType: agent.component_type,
257
+ realizationKind: agent.realization_kind,
258
+ trigger: agent.trigger,
259
+ outcome: agent.outcome,
260
+ callableAs: agent.verb ?? agent.id,
261
+ model: "—",
262
+ tiers: [],
263
+ capabilities: [],
264
+ guardrails: [],
265
+ tools: [],
266
+ outputBlocks: [],
267
+ steps: [],
268
+ ...(viaLabel !== undefined
269
+ ? {
270
+ status: "ready",
271
+ nativeAvailability: {
272
+ viaLabel,
273
+ compiledDispatchTarget: dispatchTarget,
274
+ compiledUnavailableReason: agent.availability.reason,
275
+ },
276
+ }
277
+ : { status: "unavailable", unavailableReason: agent.availability.reason }),
278
+ };
279
+ }
280
+ const distinctTiers = [...new Set(agent.steps.map((step) => step.tier))];
281
+ const tiers = distinctTiers.map((tier) => ({ tier, model: resolver.resolve(tier) }));
282
+ const lastStep = agent.steps[agent.steps.length - 1];
283
+ const model = lastStep ? resolver.resolve(lastStep.tier) : "—";
284
+ return {
285
+ id: agent.id,
286
+ name: humanize(agent.id),
287
+ componentType: agent.component_type,
288
+ realizationKind: agent.realization_kind,
289
+ trigger: agent.trigger,
290
+ outcome: agent.outcome,
291
+ callableAs: agent.verb ?? agent.id,
292
+ model,
293
+ tiers,
294
+ capabilities: agent.capabilities.map(buildCapability),
295
+ guardrails: buildGuardrails(agent.guardrails),
296
+ tools: agent.tools.map((tool) => ({ name: tool.name, source: tool.source })),
297
+ outputBlocks: extractOutputBlocks(agent.output_schema),
298
+ steps: agent.steps.map(buildStep),
299
+ ...(purposeInfo.available
300
+ ? { status: "ready" }
301
+ : { status: "unavailable", unavailableReason: purposeInfo.reason ?? NATIVE_PURPOSE_UNAVAILABLE_REASON }),
302
+ };
303
+ }
304
+ /**
305
+ * `"Bound"` when the user has walked the Setup wizard's Compile & Bind step
306
+ * (`POST /api/setup/compile-bind`, the only writer of the "bind" step's
307
+ * `state: "done"`) — the most honest available signal, since `GET
308
+ * /api/harness` itself can independently compile the same bundle (via its
309
+ * own `describeBundle` call) without the user ever having completed Setup.
310
+ * Anything else reports the honest, non-hardcoded alternative rather than a
311
+ * literal `"Bound"` the frontend previously had no real state to key off of.
312
+ */
313
+ function deriveProfileStatus(store) {
314
+ const bindStep = store.getSetupSteps().find((step) => step.key === "bind");
315
+ return bindStep?.state === "done" ? "Bound" : "Not bound yet";
316
+ }
317
+ function deriveIrVersion(compat) {
318
+ return compat.min_ir_version === compat.max_ir_version ? compat.min_ir_version : `${compat.min_ir_version}–${compat.max_ir_version}`;
319
+ }
320
+ /** Deterministic JSON serialization (recursively sorted object keys) so `computeBundleHash` doesn't depend on property insertion order. */
321
+ function canonicalJson(value) {
322
+ if (Array.isArray(value))
323
+ return `[${value.map(canonicalJson).join(",")}]`;
324
+ if (isRecord(value)) {
325
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
326
+ return `{${entries.map(([key, val]) => `${JSON.stringify(key)}:${canonicalJson(val)}`).join(",")}}`;
327
+ }
328
+ return JSON.stringify(value);
329
+ }
330
+ /** A short, stable, content-derived id for the compiled bundle — NOT a "last compiled" timestamp, which the store has none of. */
331
+ function computeBundleHash(bundle) {
332
+ return createHash("sha256").update(canonicalJson(bundle)).digest("hex").slice(0, 7);
333
+ }
334
+ function buildProfile(bundle, baseRouteOptions, store, purpose) {
335
+ const verifyGate = bundle.agents.some((agent) => findLockedGatedCheck(agent) !== undefined);
336
+ return {
337
+ id: bundle.profile,
338
+ name: humanize(bundle.profile),
339
+ boundContext: purpose === "setup" ? "Bootstrap workspace (no project bound)" : path.basename(baseRouteOptions.userProject),
340
+ verifyGate,
341
+ bundleId: `${bundle.profile}@${bundle.target}`,
342
+ bundleVersion: bundleFormatVersion(bundle),
343
+ irVersion: deriveIrVersion(bundle.compat),
344
+ dispatchTarget: bundle.target,
345
+ bundleHash: computeBundleHash(bundle),
346
+ status: purpose === "setup" ? "Bootstrap" : deriveProfileStatus(store),
347
+ };
348
+ }
349
+ /** Exported bundle-derived source used by runtime configuration validation. */
350
+ export function collectBundleTierNames(bundle) {
351
+ return collectCompiledBundleTierNames(bundle);
352
+ }
353
+ /**
354
+ * Keep the compiled bundle's declared profile tied to the BFF-selected
355
+ * purpose before exposing it through the read-only Harness DTO.
356
+ */
357
+ export function assertHarnessBundlePurpose(bundle, purpose) {
358
+ assertHarnessPurposeProfile(purpose, bundle.profile);
359
+ }
360
+ function buildRuntime(bundle, baseRouteOptions, resolver) {
361
+ const { backend, label } = runtimeBackendAndLabel(baseRouteOptions.authChoice);
362
+ const dispatcher = runtimeDispatcher(baseRouteOptions.authChoice);
363
+ const tierModels = collectBundleTierNames(bundle).map((tier) => ({ tier, model: resolver.resolve(tier) }));
364
+ return { backend, label, dispatcher, tierModels };
365
+ }
366
+ // `type`/`location` are resolved via `server/conn-config.ts`'s
367
+ // `resolveConnectionSource`: the project's persistent `wren_project.yml`
368
+ // `data_source:` (always present) is `type`, and its pinned `profile:`
369
+ // resolved against `~/.wren/profiles.yml` supplies the fields
370
+ // `describeConnection` turns into `location` (a duckdb path, or an
371
+ // allowlisted host/database for DB-type sources — never a credential). A
372
+ // project with no profile pin honestly reports `type` with location "—"
373
+ // rather than fabricating one from the profiles store's global `active`
374
+ // profile. There is still no real "last synced at"/"via which mechanism"
375
+ // signal available from this harness, so `via`/`lastSync` stay honest "—"
376
+ // too. `tablesSynced` comes from real store data (the context model count).
377
+ function buildConnection(baseRouteOptions, store, purpose) {
378
+ if (purpose === "setup") {
379
+ return { type: "—", location: "—", via: "Bootstrap workspace", tablesSynced: 0, lastSync: "—", health: "degraded" };
380
+ }
381
+ const tablesSynced = store.getContextModels().length;
382
+ const source = resolveConnectionSource(baseRouteOptions.userProject);
383
+ const { type, location } = describeConnection(source.datasource, source.fields);
384
+ return {
385
+ type,
386
+ location,
387
+ via: "—",
388
+ tablesSynced,
389
+ lastSync: "—",
390
+ health: tablesSynced > 0 ? "healthy" : "degraded",
391
+ };
392
+ }
393
+ function setupRunnerTarget(authChoice) {
394
+ switch (runtimeDispatcher(authChoice)) {
395
+ case "claude-agent-sdk": return { target: "claude-agent-sdk:setup", targetLabel: "Claude Setup runner" };
396
+ case "codex-local": return { target: "codex-local:setup", targetLabel: "Codex Setup runner" };
397
+ case "in-process": return { target: "in-process:setup", targetLabel: "In-process Setup runner" };
398
+ }
399
+ }
400
+ function buildHarnessPurpose(purpose, baseRouteOptions, nativeReadiness, setupReadiness) {
401
+ const definition = NATIVE_DISPATCH_REGISTRY[purpose];
402
+ if (purpose === "setup") {
403
+ const available = setupReadiness?.available ?? false;
404
+ return {
405
+ ...definition,
406
+ executionKind: "setup_runner",
407
+ ...setupRunnerTarget(baseRouteOptions.authChoice),
408
+ available,
409
+ ...(!available ? { reason: setupReadiness?.reason ?? "The selected Setup runner is unavailable." } : {}),
410
+ };
411
+ }
412
+ const readiness = nativeReadiness?.purposes[purpose];
413
+ const available = readiness?.available ?? false;
414
+ return {
415
+ ...definition,
416
+ executionKind: "native_session",
417
+ ...(readiness?.target ? { target: readiness.target, targetLabel: readiness.targetLabel } : {}),
418
+ available,
419
+ ...(!available ? { reason: readiness?.reason ?? NATIVE_PURPOSE_UNAVAILABLE_REASON } : {}),
420
+ };
421
+ }
422
+ export function buildHarnessDto(bundle, store, baseRouteOptions, purpose = "analysis", nativeReadiness, setupReadiness) {
423
+ const resolver = buildRealTierResolver(baseRouteOptions, store);
424
+ const purposeInfo = buildHarnessPurpose(purpose, baseRouteOptions, nativeReadiness, setupReadiness);
425
+ return {
426
+ purpose: purposeInfo,
427
+ profile: buildProfile(bundle, baseRouteOptions, store, purpose),
428
+ runtime: buildRuntime(bundle, baseRouteOptions, resolver),
429
+ connection: buildConnection(baseRouteOptions, store, purpose),
430
+ components: bundle.agents.map((agent) => buildComponent(agent, resolver, bundle.target, purposeInfo)),
431
+ nativeSessions: {
432
+ binding: store.getNativeRuntimeBinding(),
433
+ dispatches: Object.values(NATIVE_DISPATCH_REGISTRY).map((definition) => {
434
+ const readiness = nativeReadiness?.purposes[definition.purpose];
435
+ return { purpose: definition.purpose, profile: definition.profile, scopeKind: definition.scopeKind, ...(readiness?.target ? { target: readiness.target, targetLabel: readiness.targetLabel } : {}), available: readiness?.available ?? false, ...(readiness?.reason ? { reason: readiness.reason } : {}) };
436
+ }),
437
+ },
438
+ };
439
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Blast-radius/impact computed live from the REAL bound
3
+ * project's relationship + cube graph (`wren context show`, see
4
+ * `server/context-source.ts`), replacing the earlier seeded-fixture join
5
+ * (`Store.getBlastRadius`/`getVerifiedPairs`).
6
+ *
7
+ * There is no live verified-pair (Q-SQL) source over a real project yet, so
8
+ * `brokenPairs` is always empty here — an honest gap, not a silently-dropped
9
+ * feature (see `KnowledgeStatus.verifiedPairCount`, also forced to 0 for the
10
+ * same reason).
11
+ */
12
+ import type { WrenContextShow } from "./context-source.js";
13
+ import type { ImpactResponse } from "./wire-types.js";
14
+ export declare class EntityKeyNotFoundError extends Error {
15
+ readonly entityKey: string;
16
+ constructor(entityKey: string);
17
+ }
18
+ export declare function computeImpact(contextShow: WrenContextShow, entityKey: string): ImpactResponse;
@@ -0,0 +1,85 @@
1
+ export class EntityKeyNotFoundError extends Error {
2
+ entityKey;
3
+ constructor(entityKey) {
4
+ super(`no such entity in the bound project's semantic layer: ${entityKey}`);
5
+ this.entityKey = entityKey;
6
+ }
7
+ }
8
+ /** A measure's wire key is `"<cube>.<measure>"` (see `mapCubesToMeasures`, `server/context-map.ts`) — look up the owning cube by splitting on the first `.`. */
9
+ function findSeed(contextShow, entityKey) {
10
+ const model = contextShow.models.find((m) => m.name === entityKey);
11
+ if (model)
12
+ return { key: model.name, name: model.name, kind: "model" };
13
+ const relationship = contextShow.relationships.find((r) => r.name === entityKey);
14
+ if (relationship)
15
+ return { key: relationship.name, name: relationship.name, kind: "relationship" };
16
+ for (const cube of contextShow.cubes) {
17
+ const measure = cube.measures.find((m) => `${cube.name}.${m.name}` === entityKey);
18
+ if (measure)
19
+ return { key: entityKey, name: measure.name, kind: "measure" };
20
+ }
21
+ return undefined;
22
+ }
23
+ /**
24
+ * One hop of dependents from the seed: a model's downstream is every
25
+ * relationship touching it plus the model on the other side of each, and
26
+ * every cube whose `baseObject` is this model (as its measures); a
27
+ * relationship's downstream is the two models it joins; a measure's
28
+ * downstream is its cube's base model.
29
+ */
30
+ function computeDownstream(contextShow, seed) {
31
+ const downstream = [];
32
+ const seen = new Set([seed.key]);
33
+ const add = (node) => {
34
+ if (seen.has(node.key))
35
+ return;
36
+ seen.add(node.key);
37
+ downstream.push(node);
38
+ };
39
+ if (seed.kind === "model") {
40
+ for (const rel of contextShow.relationships) {
41
+ if (!rel.models.includes(seed.key))
42
+ continue;
43
+ add({ key: rel.name, name: rel.name, kind: "relationship" });
44
+ const otherModel = rel.models[0] === seed.key ? rel.models[1] : rel.models[0];
45
+ add({ key: otherModel, name: otherModel, kind: "model" });
46
+ }
47
+ for (const cube of contextShow.cubes) {
48
+ if (cube.baseObject !== seed.key)
49
+ continue;
50
+ for (const measure of cube.measures) {
51
+ add({ key: `${cube.name}.${measure.name}`, name: measure.name, kind: "measure" });
52
+ }
53
+ }
54
+ }
55
+ else if (seed.kind === "relationship") {
56
+ const relationship = contextShow.relationships.find((r) => r.name === seed.key);
57
+ for (const modelName of relationship?.models ?? []) {
58
+ add({ key: modelName, name: modelName, kind: "model" });
59
+ }
60
+ }
61
+ else if (seed.kind === "measure") {
62
+ const cubeName = seed.key.slice(0, seed.key.lastIndexOf("."));
63
+ const cube = contextShow.cubes.find((c) => c.name === cubeName);
64
+ if (cube)
65
+ add({ key: cube.baseObject, name: cube.baseObject, kind: "model" });
66
+ }
67
+ return downstream;
68
+ }
69
+ /** Any relationship/model downstream means a shape change would ripple structurally; downstream limited to measures is a narrower compatibility risk; no downstream at all is "none". Heuristic, not a computed compatibility analysis. */
70
+ function computeSeverity(downstream) {
71
+ if (downstream.some((n) => n.kind === "model" || n.kind === "relationship"))
72
+ return "structural";
73
+ if (downstream.some((n) => n.kind === "measure"))
74
+ return "compatibility";
75
+ return "none";
76
+ }
77
+ export function computeImpact(contextShow, entityKey) {
78
+ const seed = findSeed(contextShow, entityKey);
79
+ if (!seed)
80
+ throw new EntityKeyNotFoundError(entityKey);
81
+ const downstream = computeDownstream(contextShow, seed);
82
+ const severity = computeSeverity(downstream);
83
+ const brokenPairs = [];
84
+ return { blastRadius: { seed, downstream, severity }, brokenPairs };
85
+ }