@wrenai/genbi 0.0.4 → 0.0.6

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 (234) hide show
  1. package/README.md +65 -3
  2. package/dist/assets/{echarts-LTFC-ysd.js → echarts-DYM4hjvk.js} +2 -2
  3. package/dist/assets/index-A_Wrq7Ey.css +1 -0
  4. package/dist/assets/{index-Bo--QMUN.js → index-DsH129tV.js} +18 -17
  5. package/dist/index.html +2 -2
  6. package/dist-server/harness/bundle/schema.d.ts +38 -0
  7. package/dist-server/harness/bundle/schema.js +18 -10
  8. package/dist-server/harness/bundle/version.js +1 -1
  9. package/dist-server/harness/cli-args.d.ts +10 -2
  10. package/dist-server/harness/cli-args.js +24 -8
  11. package/dist-server/harness/cli.js +75 -9
  12. package/dist-server/harness/compile/cache.js +4 -4
  13. package/dist-server/harness/compile/compose-profile.d.ts +51 -14
  14. package/dist-server/harness/compile/compose-profile.js +85 -19
  15. package/dist-server/harness/compile/context-loader.d.ts +56 -0
  16. package/dist-server/harness/compile/context-loader.js +237 -0
  17. package/dist-server/harness/compile/errors.d.ts +19 -0
  18. package/dist-server/harness/compile/errors.js +35 -0
  19. package/dist-server/harness/compile/index.d.ts +4 -3
  20. package/dist-server/harness/compile/index.js +4 -3
  21. package/dist-server/harness/compile/pipeline.d.ts +15 -7
  22. package/dist-server/harness/compile/pipeline.js +62 -14
  23. package/dist-server/harness/compile/resolve-binary.d.ts +2 -2
  24. package/dist-server/harness/compile/resolve-binary.js +3 -3
  25. package/dist-server/harness/compile/types.d.ts +29 -0
  26. package/dist-server/harness/compile/warble-identity.d.ts +11 -0
  27. package/dist-server/harness/compile/warble-identity.js +13 -0
  28. package/dist-server/harness/components/ai-step.d.ts +4 -0
  29. package/dist-server/harness/components/ai-step.js +40 -0
  30. package/dist-server/harness/components/broker.d.ts +60 -0
  31. package/dist-server/harness/components/broker.js +117 -0
  32. package/dist-server/harness/components/capability-card.d.ts +98 -0
  33. package/dist-server/harness/components/capability-card.js +169 -0
  34. package/dist-server/harness/components/context.d.ts +2 -0
  35. package/dist-server/harness/components/context.js +30 -0
  36. package/dist-server/harness/components/display.d.ts +6 -0
  37. package/dist-server/harness/components/display.js +29 -0
  38. package/dist-server/harness/components/egress-judge.d.ts +12 -0
  39. package/dist-server/harness/components/egress-judge.js +27 -0
  40. package/dist-server/harness/components/egress.d.ts +142 -0
  41. package/dist-server/harness/components/egress.js +469 -0
  42. package/dist-server/harness/components/in-process.d.ts +7 -0
  43. package/dist-server/harness/components/in-process.js +245 -0
  44. package/dist-server/harness/components/normalize.d.ts +35 -0
  45. package/dist-server/harness/components/normalize.js +245 -0
  46. package/dist-server/harness/components/plan.d.ts +31 -0
  47. package/dist-server/harness/components/plan.js +184 -0
  48. package/dist-server/harness/components/report.d.ts +207 -0
  49. package/dist-server/harness/components/report.js +309 -0
  50. package/dist-server/harness/components/runner.d.ts +193 -0
  51. package/dist-server/harness/components/runner.js +492 -0
  52. package/dist-server/harness/components/semantic-guards.d.ts +20 -0
  53. package/dist-server/harness/components/semantic-guards.js +42 -0
  54. package/dist-server/harness/components/terminal-json.d.ts +18 -0
  55. package/dist-server/harness/components/terminal-json.js +82 -0
  56. package/dist-server/harness/components/usage.d.ts +14 -0
  57. package/dist-server/harness/components/usage.js +19 -0
  58. package/dist-server/harness/components/wren-access.d.ts +29 -0
  59. package/dist-server/harness/components/wren-access.js +233 -0
  60. package/dist-server/harness/components/zone-dry-run.d.ts +43 -0
  61. package/dist-server/harness/components/zone-dry-run.js +107 -0
  62. package/dist-server/harness/components/zone-gate.d.ts +76 -0
  63. package/dist-server/harness/components/zone-gate.js +144 -0
  64. package/dist-server/harness/events/index.d.ts +1 -1
  65. package/dist-server/harness/events/types.d.ts +52 -0
  66. package/dist-server/harness/index.d.ts +9 -2
  67. package/dist-server/harness/index.js +5 -1
  68. package/dist-server/harness/loop/executor.d.ts +1 -0
  69. package/dist-server/harness/loop/executor.js +37 -15
  70. package/dist-server/harness/npm-package-resolve.d.ts +2 -0
  71. package/dist-server/harness/npm-package-resolve.js +16 -4
  72. package/dist-server/harness/providers/adapters/openai-compatible.d.ts +13 -0
  73. package/dist-server/harness/providers/adapters/openai-compatible.js +17 -0
  74. package/dist-server/harness/providers/adapters/openai.d.ts +7 -0
  75. package/dist-server/harness/providers/adapters/openai.js +6 -0
  76. package/dist-server/harness/providers/adapters/pi-ai.d.ts +48 -0
  77. package/dist-server/harness/providers/adapters/pi-ai.js +518 -0
  78. package/dist-server/harness/providers/binding.d.ts +45 -4
  79. package/dist-server/harness/providers/binding.js +44 -8
  80. package/dist-server/harness/providers/index.d.ts +9 -3
  81. package/dist-server/harness/providers/index.js +10 -3
  82. package/dist-server/harness/providers/zone.d.ts +96 -0
  83. package/dist-server/harness/providers/zone.js +112 -0
  84. package/dist-server/harness/render/answering-query.d.ts +29 -0
  85. package/dist-server/harness/render/answering-query.js +62 -0
  86. package/dist-server/harness/render/envelope.d.ts +6 -0
  87. package/dist-server/harness/render/envelope.js +10 -3
  88. package/dist-server/harness/route/adapter-spec.d.ts +8 -9
  89. package/dist-server/harness/route/adapter-spec.js +13 -14
  90. package/dist-server/harness/route/codex-ask-events.js +2 -1
  91. package/dist-server/harness/route/codex-ask.d.ts +4 -0
  92. package/dist-server/harness/route/codex-ask.js +43 -45
  93. package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
  94. package/dist-server/harness/route/codex-local-manifest.js +96 -15
  95. package/dist-server/harness/route/describe.js +45 -6
  96. package/dist-server/harness/route/dispatched.d.ts +20 -0
  97. package/dist-server/harness/route/dispatched.js +122 -24
  98. package/dist-server/harness/route/host-query.d.ts +93 -0
  99. package/dist-server/harness/route/host-query.js +257 -0
  100. package/dist-server/harness/route/in-process.d.ts +22 -1
  101. package/dist-server/harness/route/in-process.js +54 -4
  102. package/dist-server/harness/route/route.js +8 -2
  103. package/dist-server/harness/route/types.d.ts +47 -1
  104. package/dist-server/harness/route/wren-host-proxy.d.ts +33 -0
  105. package/dist-server/harness/route/wren-host-proxy.js +228 -0
  106. package/dist-server/harness/session/run.js +30 -10
  107. package/dist-server/harness/setup/runner.d.ts +17 -3
  108. package/dist-server/harness/setup/runner.js +272 -89
  109. package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
  110. package/dist-server/harness/setup/yaml-scalar.js +62 -0
  111. package/dist-server/harness/tools/setup-native.d.ts +7 -0
  112. package/dist-server/harness/tools/setup-native.js +7 -4
  113. package/dist-server/server/adopt.js +1 -17
  114. package/dist-server/server/app.d.ts +1 -0
  115. package/dist-server/server/app.js +175 -17
  116. package/dist-server/server/auth-choice.d.ts +13 -1
  117. package/dist-server/server/auth-choice.js +66 -1
  118. package/dist-server/server/bin.js +185 -25
  119. package/dist-server/server/codex-native-session.d.ts +25 -0
  120. package/dist-server/server/codex-native-session.js +1 -0
  121. package/dist-server/server/codex-runtime-composition.d.ts +34 -0
  122. package/dist-server/server/codex-runtime-composition.js +250 -0
  123. package/dist-server/server/codex-structured-ask.d.ts +23 -0
  124. package/dist-server/server/codex-structured-ask.js +102 -0
  125. package/dist-server/server/conn-config.js +4 -16
  126. package/dist-server/server/conversation-socket.d.ts +16 -0
  127. package/dist-server/server/conversation-socket.js +65 -0
  128. package/dist-server/server/db.d.ts +46 -0
  129. package/dist-server/server/db.js +88 -9
  130. package/dist-server/server/env-detect.d.ts +6 -0
  131. package/dist-server/server/env-detect.js +7 -0
  132. package/dist-server/server/fold.d.ts +23 -4
  133. package/dist-server/server/fold.js +42 -5
  134. package/dist-server/server/harness.d.ts +2 -2
  135. package/dist-server/server/harness.js +33 -7
  136. package/dist-server/server/host-evidence-grounding.d.ts +21 -0
  137. package/dist-server/server/host-evidence-grounding.js +93 -0
  138. package/dist-server/server/interactive-terminal.d.ts +10 -6
  139. package/dist-server/server/interactive-terminal.js +33 -8
  140. package/dist-server/server/managed-wren-runtime.d.ts +116 -0
  141. package/dist-server/server/managed-wren-runtime.js +670 -0
  142. package/dist-server/server/native-artifacts.d.ts +112 -7
  143. package/dist-server/server/native-artifacts.js +194 -14
  144. package/dist-server/server/native-component-context.d.ts +19 -0
  145. package/dist-server/server/native-component-context.js +76 -0
  146. package/dist-server/server/native-component-preparation.d.ts +20 -0
  147. package/dist-server/server/native-component-preparation.js +129 -0
  148. package/dist-server/server/native-component-runtime.d.ts +36 -0
  149. package/dist-server/server/native-component-runtime.js +92 -0
  150. package/dist-server/server/native-components.d.ts +84 -0
  151. package/dist-server/server/native-components.js +160 -0
  152. package/dist-server/server/native-dispatch-process.d.ts +8 -0
  153. package/dist-server/server/native-dispatch-process.js +65 -0
  154. package/dist-server/server/native-runtime-spec.d.ts +89 -0
  155. package/dist-server/server/native-runtime-spec.js +169 -0
  156. package/dist-server/server/native-sessions.d.ts +172 -19
  157. package/dist-server/server/native-sessions.js +839 -244
  158. package/dist-server/server/native-shutdown.d.ts +6 -0
  159. package/dist-server/server/native-shutdown.js +28 -0
  160. package/dist-server/server/native-wren-home.d.ts +18 -0
  161. package/dist-server/server/native-wren-home.js +262 -0
  162. package/dist-server/server/node-pty-host.d.ts +2 -1
  163. package/dist-server/server/node-pty-host.js +3 -2
  164. package/dist-server/server/profile-registry.d.ts +139 -0
  165. package/dist-server/server/profile-registry.js +620 -0
  166. package/dist-server/server/runtime-binding.d.ts +4 -1
  167. package/dist-server/server/runtime-binding.js +50 -2
  168. package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
  169. package/dist-server/server/runtime-host/claude-component-step.js +186 -0
  170. package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
  171. package/dist-server/server/runtime-host/codex-app-server.js +239 -0
  172. package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
  173. package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
  174. package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
  175. package/dist-server/server/runtime-host/codex-component-step.js +308 -0
  176. package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
  177. package/dist-server/server/runtime-host/codex-conversation.js +295 -0
  178. package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
  179. package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
  180. package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
  181. package/dist-server/server/runtime-host/codex-events.js +66 -0
  182. package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
  183. package/dist-server/server/runtime-host/codex-policy.js +151 -0
  184. package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
  185. package/dist-server/server/runtime-host/codex-process.js +96 -0
  186. package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
  187. package/dist-server/server/runtime-host/codex-rpc.js +182 -0
  188. package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
  189. package/dist-server/server/runtime-host/codex-session.js +492 -0
  190. package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
  191. package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
  192. package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
  193. package/dist-server/server/runtime-host/codex-terminal.js +120 -0
  194. package/dist-server/server/runtime-host/index.d.ts +5 -0
  195. package/dist-server/server/runtime-host/index.js +3 -0
  196. package/dist-server/server/runtime-host/local.d.ts +29 -0
  197. package/dist-server/server/runtime-host/local.js +90 -0
  198. package/dist-server/server/runtime-host/policy.d.ts +8 -0
  199. package/dist-server/server/runtime-host/policy.js +78 -0
  200. package/dist-server/server/runtime-host/types.d.ts +52 -0
  201. package/dist-server/server/runtime-host/types.js +62 -0
  202. package/dist-server/server/structured-query-evidence.d.ts +7 -0
  203. package/dist-server/server/structured-query-evidence.js +47 -0
  204. package/dist-server/server/structured-runtime.d.ts +52 -0
  205. package/dist-server/server/structured-runtime.js +130 -0
  206. package/dist-server/server/structured-scope.d.ts +4 -0
  207. package/dist-server/server/structured-scope.js +12 -0
  208. package/dist-server/server/turn.d.ts +6 -1
  209. package/dist-server/server/turn.js +86 -26
  210. package/dist-server/server/wire-types.d.ts +67 -3
  211. package/dist-server/server/wren-profiles.js +1 -1
  212. package/managed-wren/README.md +60 -0
  213. package/managed-wren/dependency-wheels.json +282 -0
  214. package/managed-wren/manifest.json +373 -0
  215. package/managed-wren/release-inputs.json +45 -0
  216. package/managed-wren/runtime-tree.cjs +115 -0
  217. package/managed-wren/source-inputs.json +40 -0
  218. package/package.json +15 -6
  219. package/profiles/genbi-default/context/binding.yml +17 -7
  220. package/profiles/genbi-default/context/context.json +146 -0
  221. package/profiles/genbi-default/ir.golden.json +39 -43
  222. package/profiles/genbi-default/profile.yml +31 -0
  223. package/profiles/genbi-enrich-context/context/binding.yml +14 -3
  224. package/profiles/genbi-enrich-context/context/context.json +146 -0
  225. package/profiles/genbi-enrich-context/ir.golden.json +4 -5
  226. package/profiles/genbi-monitor/context/binding.yml +13 -5
  227. package/profiles/genbi-monitor/context/context.json +146 -0
  228. package/profiles/genbi-monitor/ir.golden.json +13 -7
  229. package/profiles/genbi-report/context/binding.yml +19 -0
  230. package/profiles/genbi-report/context/context.json +223 -0
  231. package/profiles/genbi-report/ir.golden.json +328 -0
  232. package/profiles/genbi-report/profile.yml +38 -0
  233. package/profiles/genbi-setup/ir.golden.json +3 -1
  234. package/dist/assets/index-Xs4q7pNo.css +0 -1
@@ -1,8 +1,21 @@
1
1
  import { execFile } from "node:child_process";
2
- import { readFile } from "node:fs/promises";
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { planDigest } from "../components/plan.js";
3
6
  import { loadBundle } from "../bundle/loader.js";
4
7
  export const CODEX_ASK_COMPONENTS = ["answer_query", "generate_dashboard"];
5
8
  export const CODEX_ENRICH_COMPONENTS = ["inspect_context", "draft_enrichment"];
9
+ export function codexAnalysisToolArgs(component) {
10
+ const bindings = component === "answer_query"
11
+ ? ["generate_sql=run_sql", "repair_sql=run_sql"]
12
+ : ["plan_dashboard=get_context", "compose_layout=run_sql"];
13
+ const required = component === "answer_query"
14
+ ? ["generate_sql", "repair_sql"]
15
+ : ["plan_dashboard", "compose_layout"];
16
+ return ["--transport", "orchestrate", ...bindings.flatMap((binding) => ["--step-tool", binding]),
17
+ ...required.flatMap((step) => ["--require-tool", step])];
18
+ }
6
19
  export function buildCodexAskManifestArgs(cli, irPath, models, component = "answer_query") {
7
20
  return [
8
21
  ...cli.prefixArgs,
@@ -20,10 +33,7 @@ export function buildCodexAskManifestArgs(cli, irPath, models, component = "answ
20
33
  // spawn it; the runtime binds the real Wren MCP command separately.
21
34
  "--server-command",
22
35
  process.execPath,
23
- "--inspect-tool",
24
- "get_context",
25
- "--query-tool",
26
- "run_sql",
36
+ ...codexAnalysisToolArgs(component),
27
37
  ];
28
38
  }
29
39
  export function buildCodexBootstrapManifestArgs(cli, irPath) {
@@ -33,10 +43,11 @@ export function buildCodexBootstrapManifestArgs(cli, irPath) {
33
43
  irPath,
34
44
  "--server-command",
35
45
  process.execPath,
36
- "--source-tool",
37
- "setup_execution",
38
- "--context-tool",
39
- "setup_execution",
46
+ "--transport", "exec",
47
+ "--step-tool", "connect=setup_execution",
48
+ "--step-tool", "build=setup_execution",
49
+ "--require-tool", "connect",
50
+ "--require-tool", "build",
40
51
  ];
41
52
  }
42
53
  export function buildCodexEnrichmentManifestArgs(cli, irPath, models, component) {
@@ -50,10 +61,9 @@ export function buildCodexEnrichmentManifestArgs(cli, irPath, models, component)
50
61
  component === "inspect_context" ? models.cheap : models.strong,
51
62
  "--server-command",
52
63
  process.execPath,
53
- "--semantic-tool",
54
- "get_context",
55
- "--raw-material-tool",
56
- "get_context",
64
+ "--transport", "turn",
65
+ "--step-tool", `${component === "inspect_context" ? "inspect" : "draft"}=get_context`,
66
+ "--require-tool", component === "inspect_context" ? "inspect" : "draft",
57
67
  ];
58
68
  }
59
69
  async function describeCodexComponentManifests(cli, irPath, components, argsFor) {
@@ -73,9 +83,80 @@ async function describeCodexComponentManifests(cli, irPath, components, argsFor)
73
83
  }
74
84
  return loadBundle({ ...primary, agents: bundles.flatMap((bundle) => bundle.agents) });
75
85
  }
76
- export async function describeCodexAskManifest(cli, irPath, models) {
86
+ export async function describeCodexAskManifest(cli, irPath, models, context) {
87
+ const ir = JSON.parse(await readFile(irPath, "utf8"));
88
+ if (records(ir.components).some((node) => records(node.llm_calls).some((step) => records(step.component_calls).length))) {
89
+ if (!context)
90
+ throw new Error("Codex composed manifest requires component-owned prepared context");
91
+ return describeComposedCodexManifest(cli, irPath, ir, models, context);
92
+ }
77
93
  return describeCodexComponentManifests(cli, irPath, CODEX_ASK_COMPONENTS, (component) => buildCodexAskManifestArgs(cli, irPath, models, component));
78
94
  }
95
+ async function describeComposedCodexManifest(cli, irPath, ir, models, context) {
96
+ const dir = await mkdtemp(path.join(os.tmpdir(), "genbi-codex-manifest-"));
97
+ try {
98
+ const bindings = Object.fromEntries(CODEX_ASK_COMPONENTS.map((id) => {
99
+ const node = records(ir.components).find((component) => component.id === id);
100
+ const prepared = context.contexts[id];
101
+ if (!node || !prepared || planDigest(prepared.binding) !== planDigest(node.context_binding)) {
102
+ throw new Error(`Codex manifest context binding mismatch for ${id}`);
103
+ }
104
+ // Display preparation never launches this MCP command. Tools remain owned
105
+ // by the answer component; the dashboard only receives its call alias.
106
+ return [id, { transport: "orchestrate", models, context: JSON.stringify(prepared.snapshot), mcp: {
107
+ name: "wren", command: process.execPath,
108
+ toolsByStep: id === "answer_query" ? { resolve_intent: [], generate_sql: ["run_sql"], repair_sql: ["run_sql"] }
109
+ : { plan_dashboard: [], compose_layout: [] },
110
+ requireTool: id === "answer_query" ? ["generate_sql", "repair_sql"] : [],
111
+ } }];
112
+ }));
113
+ const file = path.join(dir, "bindings.json");
114
+ await writeFile(file, JSON.stringify({ components: bindings }), { mode: 0o600 });
115
+ const bundles = [];
116
+ for (const id of CODEX_ASK_COMPONENTS) {
117
+ const manifest = JSON.parse(await execCodexManifest(cli.command, [...cli.prefixArgs, "manifest", irPath,
118
+ "--component", id, "--transport", "orchestrate", "--component-bindings", file, "--warble-bin", context.warbleBin]));
119
+ if (manifest.version !== "0.3" || manifest.target !== "codex:local" || manifest.profile !== ir.profile || manifest.entry !== id || manifest.transport !== "orchestrate") {
120
+ throw new Error("Unexpected composed Codex manifest identity");
121
+ }
122
+ const nodes = records(manifest.components);
123
+ const expectedIds = id === "answer_query" ? [id] : [...CODEX_ASK_COMPONENTS];
124
+ if (nodes.length !== expectedIds.length || new Set(nodes.map((node) => node.id)).size !== nodes.length || nodes.some((node) => !expectedIds.includes(String(node.id)))) {
125
+ throw new Error("Unexpected composed Codex manifest closure");
126
+ }
127
+ for (const node of nodes) {
128
+ const source = records(ir.components).find((candidate) => candidate.id === node.id);
129
+ const emitted = records(node.steps);
130
+ const declared = records(source.llm_calls);
131
+ if (emitted.length !== declared.length)
132
+ throw new Error("Unexpected composed Codex manifest steps");
133
+ for (let index = 0; index < declared.length; index++) {
134
+ const step = declared[index];
135
+ const actual = emitted[index];
136
+ const aliases = Object.fromEntries(records(step.component_calls).map((edge) => [String(edge.alias), edge.component]));
137
+ const tools = bindings[String(node.id)].mcp.toolsByStep[String(step.name)] ?? [];
138
+ if (actual.name !== step.name || planDigest(actual.componentCalls) !== planDigest(aliases) || planDigest(actual.enabledTools) !== planDigest(tools)) {
139
+ throw new Error("Unexpected composed Codex manifest tool authority");
140
+ }
141
+ }
142
+ }
143
+ const entries = records(manifest.components).filter((node) => node.role === "entry");
144
+ if (entries.length !== 1 || entries[0].id !== id)
145
+ throw new Error("Unexpected composed Codex manifest entry");
146
+ const entry = entries[0];
147
+ const steps = records(entry.steps);
148
+ bundles.push(normalizeCodexManifest({ manifest_version: "0.1", target: manifest.target, profile: manifest.profile,
149
+ compat: { min_ir_version: ir.warble_ir_version, max_ir_version: ir.warble_ir_version },
150
+ agents: [{ ...entry, steps: steps.map((step) => ({ name: step.name })),
151
+ tools: [...new Set(steps.flatMap((step) => Array.isArray(step.enabledTools) ? step.enabledTools.map(String) : []))].map((name) => ({ name, source: "mcp:wren" })) }],
152
+ }, ir));
153
+ }
154
+ return loadBundle({ ...bundles[0], agents: bundles.flatMap((bundle) => bundle.agents) });
155
+ }
156
+ finally {
157
+ await rm(dir, { recursive: true, force: true });
158
+ }
159
+ }
79
160
  /**
80
161
  * Setup is a raw/bootstrap profile, not an Ask profile. Its dispatcher has a
81
162
  * dedicated whole-profile `manifest` command that neither needs nor accepts a
@@ -130,7 +211,7 @@ function normalizeCodexManifest(manifest, ir) {
130
211
  return {
131
212
  name,
132
213
  tier: String(step["tier"] ?? source["tier"] ?? "strong"),
133
- consumes: Array.isArray(step["consumes"]) ? step["consumes"].map(String) : [],
214
+ consumes: Array.isArray(step["consumes"]) ? step["consumes"].map(String) : Array.isArray(source["consumes"]) ? source["consumes"].map(String) : [],
134
215
  produces: String(step["produces"] ?? source["produces"] ?? "result"),
135
216
  prompt: String(source["prompt"] ?? ""),
136
217
  realization: when
@@ -1,6 +1,12 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { mkdtemp, readFile, realpath, rm } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { generatePreparedContext, resolveContextLoaderBinary } from "../compile/context-loader.js";
5
+ import { resolveWarbleBinary } from "../compile/resolve-binary.js";
2
6
  import { loadBundleWithProvenance } from "../bundle/loader.js";
3
7
  import { compileProfile, compileRawProfile } from "../compile/pipeline.js";
8
+ import { planDigest, readExecutionPlan } from "../components/plan.js";
9
+ import { describeComponentPlan } from "../components/display.js";
4
10
  import { buildAgentSdkManifestArgs, runAgentSdkManifest } from "./agent-sdk-manifest.js";
5
11
  import { resolveAgentSdkCli } from "./agent-sdk-cli.js";
6
12
  import { resolveCodexLocalCli } from "./codex-local-cli.js";
@@ -45,14 +51,44 @@ async function describeCodexManifest(options) {
45
51
  throw new Error("Codex manifest requires orchestrator, cheap, and strong model bindings");
46
52
  }
47
53
  const cli = await resolveCodexLocalCli(options.codexLocalBin);
48
- return purpose === "context_enrichment"
49
- ? describeCodexEnrichmentManifest(cli, compiled.irPath, models)
50
- : describeCodexAskManifest(cli, compiled.irPath, models);
54
+ if (purpose === "context_enrichment")
55
+ return describeCodexEnrichmentManifest(cli, compiled.irPath, models);
56
+ const ir = JSON.parse(await readFile(compiled.irPath, "utf8"));
57
+ if (!ir.components.some((node) => node.llm_calls.some((step) => step.component_calls?.length))) {
58
+ return describeCodexAskManifest(cli, compiled.irPath, models);
59
+ }
60
+ const dir = await mkdtemp(path.join(os.tmpdir(), "genbi-manifest-context-"));
61
+ try {
62
+ if (options.context === "bootstrap")
63
+ throw new Error("Composed analysis requires a bound project");
64
+ const project = await realpath(options.userProject);
65
+ for (const id of ["answer_query", "generate_dashboard"]) {
66
+ const binding = ir.components.find((node) => node.id === id)?.context_binding;
67
+ if (!binding || typeof binding.project !== "string" || !path.isAbsolute(binding.project) || await realpath(binding.project) !== project) {
68
+ throw new Error(`Codex manifest cannot capture context for ${id} from a different project`);
69
+ }
70
+ }
71
+ const file = path.join(dir, "context.json");
72
+ await generatePreparedContext(resolveContextLoaderBinary(), project, file);
73
+ const snapshot = JSON.parse(await readFile(file, "utf8"));
74
+ return await describeCodexAskManifest(cli, compiled.irPath, models, {
75
+ warbleBin: await resolveWarbleBinary(options.warbleBin),
76
+ contexts: Object.fromEntries(ir.components.map((node) => [node.id, { binding: node.context_binding, snapshot }])),
77
+ });
78
+ }
79
+ finally {
80
+ await rm(dir, { recursive: true, force: true });
81
+ }
51
82
  }
52
83
  async function describeVercelBundle(options) {
53
84
  const compiled = await compileForDescription(options, "agnostic");
54
85
  // `mode: "agnostic"` always produces a bundle — see `compileProfile`'s doc comment.
55
86
  const bundleJson = JSON.parse(await readFile(compiled.bundlePath, "utf-8"));
87
+ if (bundleJson.vercel_bundle_version === "0.2") {
88
+ const ir = JSON.parse(await readFile(compiled.irPath, "utf8"));
89
+ const plan = readExecutionPlan(JSON.stringify(bundleJson), { digest: bundleJson.bundle_sha256, inputIrDigest: planDigest(ir), declarations: Object.fromEntries(ir.components.map((node) => [node.id, node])), contextBinding: ir.context_binding });
90
+ return describeComponentPlan(plan, bundleJson.profile);
91
+ }
56
92
  return loadBundleWithProvenance(bundleJson, {
57
93
  ...(compiled.warbleBin !== undefined ? { warbleBin: compiled.warbleBin } : {}),
58
94
  profileSource: options.profileSource,
@@ -66,10 +102,13 @@ async function describeAgentSdkManifest(options) {
66
102
  ...(options.context !== "bootstrap" ? { userProject: options.userProject } : {}),
67
103
  });
68
104
  const stdout = await runAgentSdkManifest(command, args);
69
- return loadBundleWithProvenance(JSON.parse(stdout), {
105
+ const manifest = JSON.parse(stdout);
106
+ if (manifest.target !== "claude-agent-sdk:local")
107
+ throw new Error("Unexpected SDK manifest target");
108
+ return loadBundleWithProvenance(manifest, {
70
109
  ...(compiled.warbleBin !== undefined ? { warbleBin: compiled.warbleBin } : {}),
71
110
  profileSource: options.profileSource,
72
- });
111
+ }, { irVersion: "0.9", bundleVersions: ["0.1", "0.3"] });
73
112
  }
74
113
  function compileForDescription(options, mode) {
75
114
  const shared = {
@@ -1,5 +1,11 @@
1
1
  import type { ResolvedCli } from "./agent-sdk-cli.js";
2
+ import { type HostQueryService } from "./host-query.js";
2
3
  import type { DispatchedOptions, DispatchedResult } from "./types.js";
4
+ /** The host MCP server's name; warble allows exactly `mcp__wren_host__run_sql` from it. */
5
+ export declare const HOST_MCP_SERVER_NAME = "wren_host";
6
+ export declare const HOST_MCP_TOOLS: readonly ["run_sql"];
7
+ /** Appended once to the dispatched system prompt by warble (single line, at most 300 characters). */
8
+ export declare const HOST_MCP_INSTRUCTION: string;
3
9
  export interface AgentSdkChatCommand {
4
10
  readonly command: string;
5
11
  readonly args: readonly string[];
@@ -40,6 +46,8 @@ export interface BuildAgentSdkChatArgsOptions {
40
46
  * `DispatchedOptions.resumeSessionId`.
41
47
  */
42
48
  readonly resume?: string;
49
+ /** This turn's host MCP config file (see `writeHostMcpConfig`), forwarded as `--host-mcp-config <path>`. */
50
+ readonly hostMcpConfig?: string;
43
51
  }
44
52
  /**
45
53
  * Builds the argv (and stdin input) for a `warble-agent-sdk chat` invocation
@@ -113,6 +121,18 @@ export declare function buildAgentSdkChatArgs(cli: ResolvedCli, options: BuildAg
113
121
  * conversation, not a disk-state prompt rebuild.
114
122
  */
115
123
  export declare function runDispatchedDefault(options: DispatchedOptions): Promise<DispatchedResult>;
124
+ /**
125
+ * Writes the turn's `--host-mcp-config` file into the host query service's private 0700
126
+ * directory, as warble 0.16 reads it: a 0600 file owned by this user holding exactly
127
+ * `{name, command, args, tools, instruction}`, `command` absolute. The server is the Wren host
128
+ * proxy in front of `wren serve mcp`; the file names the credential file by path only, never its
129
+ * token. It is removed with the directory when the service closes.
130
+ */
131
+ export declare function writeHostMcpConfig(service: Pick<HostQueryService, "directory" | "credentialFile">, options: {
132
+ readonly proxy: ResolvedCli;
133
+ readonly mcpServer: ResolvedCli;
134
+ readonly userProject: string;
135
+ }): Promise<string>;
116
136
  /**
117
137
  * A `spawnChat` turn that failed (non-zero exit, timeout, or spawn error) but still surfaced a
118
138
  * resumable SDK session id along the way (the dispatcher's `{t:"session",id}` NDJSON line — see
@@ -1,14 +1,24 @@
1
1
  import { spawn } from "node:child_process";
2
- import { mkdtemp } from "node:fs/promises";
2
+ import { mkdtemp, readFile, writeFile } from "node:fs/promises";
3
3
  import { createInterface } from "node:readline";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
+ import { ModelConfig } from "@warble/claude-agent-sdk";
6
7
  import { enforceCompliance } from "../compliance/index.js";
8
+ import { hashDirectory } from "../compile/fingerprint.js";
7
9
  import { compileProfile } from "../compile/pipeline.js";
10
+ import { openWrenComponentAccess } from "../components/wren-access.js";
8
11
  import { resolveWarbleBinary } from "../compile/resolve-binary.js";
9
12
  import { createAgentEventEmitter } from "../events/index.js";
10
13
  import { createChatEventMapperState, mapChatEventToAgentEvent, parseWarbleChatEventLine, } from "./chat-event-mapper.js";
11
14
  import { resolveAgentSdkCli } from "./agent-sdk-cli.js";
15
+ import { defaultWrenHostProxy, openHostQueryService, resolveExecutableOnPath, wrenHostProxyArgs, } from "./host-query.js";
16
+ /** The host MCP server's name; warble allows exactly `mcp__wren_host__run_sql` from it. */
17
+ export const HOST_MCP_SERVER_NAME = "wren_host";
18
+ export const HOST_MCP_TOOLS = Object.freeze(["run_sql"]);
19
+ /** Appended once to the dispatched system prompt by warble (single line, at most 300 characters). */
20
+ export const HOST_MCP_INSTRUCTION = "Run data queries with mcp__wren_host__run_sql (sql, optional limit): it returns the rows plus a query_id; " +
21
+ "cite that query_id in your answer's definition.";
12
22
  const MODE_B_AGENT_ID = "answer_query";
13
23
  /**
14
24
  * Builds the argv (and stdin input) for a `warble-agent-sdk chat` invocation
@@ -56,6 +66,7 @@ export function buildAgentSdkChatArgs(cli, options) {
56
66
  ...(options.modelsConfig !== undefined ? ["--models-config", options.modelsConfig] : []),
57
67
  ...(options.maxTurns !== undefined ? ["--max-turns", String(options.maxTurns)] : []),
58
68
  ...(options.resume !== undefined ? ["--resume", options.resume] : []),
69
+ ...(options.hostMcpConfig !== undefined ? ["--host-mcp-config", options.hostMcpConfig] : []),
59
70
  "--stream-json",
60
71
  ],
61
72
  input: `${encodeQuestionForStdin(options.question)}\n`,
@@ -169,30 +180,45 @@ export async function runDispatchedDefault(options) {
169
180
  const outDir = options.outDir ?? (await mkdtemp(path.join(os.tmpdir(), "wren-harness-agent-sdk-")));
170
181
  if (options.signal?.aborted)
171
182
  throw new Error("warble-agent-sdk chat was cancelled during preparation");
172
- const command = buildAgentSdkChatArgs(cli, {
173
- irPath,
174
- userProject: options.userProject,
175
- question: options.question,
176
- outDir,
177
- warbleBin,
178
- agentId,
179
- ...(options.modelsConfig !== undefined ? { modelsConfig: options.modelsConfig } : {}),
180
- ...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),
181
- ...(options.resumeSessionId !== undefined ? { resume: options.resumeSessionId } : {}),
182
- });
183
- const { finalText, sessionId } = await spawnChat(command, (event) => emitter.emit(event), options.chatTimeoutMs ?? CHAT_TIMEOUT_MS, options.signal);
184
- // `spawnChat` resolves with whatever trimmed final-answer text
185
- // the process produced, even if that's empty — e.g. the CLI exits 0 but
186
- // never printed a terminal answer line (a dispatcher bug, an unexpected
187
- // output-format change, or a turn that produced no text). Treat that as
188
- // a failure rather than a silent empty "answer".
189
- if (finalText.length === 0) {
190
- throw new Error("warble-agent-sdk chat exited successfully but produced no stdout — expected the turn's " +
191
- "final answer text; got empty output");
183
+ // This turn's host query service, when the caller asked for one: the only place the host MCP's
184
+ // run_sql executes and the only evidence the answer can be grounded on. Closed (and its
185
+ // observations discarded) when the turn ends, whatever the outcome.
186
+ const host = await openAskHostQueries(options, irPath);
187
+ try {
188
+ const command = buildAgentSdkChatArgs(cli, {
189
+ irPath,
190
+ userProject: options.userProject,
191
+ question: options.question,
192
+ outDir,
193
+ warbleBin,
194
+ agentId,
195
+ ...(options.modelsConfig !== undefined ? { modelsConfig: options.modelsConfig } : {}),
196
+ ...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),
197
+ ...(options.resumeSessionId !== undefined ? { resume: options.resumeSessionId } : {}),
198
+ ...(host !== undefined ? { hostMcpConfig: host.configFile } : {}),
199
+ });
200
+ const { finalText, sessionId } = await spawnChat(command, (event) => emitter.emit(event), options.chatTimeoutMs ?? CHAT_TIMEOUT_MS, options.signal);
201
+ // `spawnChat` resolves with whatever trimmed final-answer text
202
+ // the process produced, even if that's empty — e.g. the CLI exits 0 but
203
+ // never printed a terminal answer line (a dispatcher bug, an unexpected
204
+ // output-format change, or a turn that produced no text). Treat that as
205
+ // a failure rather than a silent empty "answer".
206
+ if (finalText.length === 0) {
207
+ throw new Error("warble-agent-sdk chat exited successfully but produced no stdout — expected the turn's " +
208
+ "final answer text; got empty output");
209
+ }
210
+ const hostObservations = host?.service.observations();
211
+ emitter.emit({ kind: "answer", text: finalText });
212
+ emitter.emit({ kind: "run.finish", status: "answer" });
213
+ return {
214
+ finalText,
215
+ ...(sessionId !== undefined ? { sessionId } : {}),
216
+ ...(hostObservations !== undefined ? { hostObservations } : {}),
217
+ };
218
+ }
219
+ finally {
220
+ await host?.service.close();
192
221
  }
193
- emitter.emit({ kind: "answer", text: finalText });
194
- emitter.emit({ kind: "run.finish", status: "answer" });
195
- return { finalText, ...(sessionId !== undefined ? { sessionId } : {}) };
196
222
  }
197
223
  catch (error) {
198
224
  emitter.emit({ kind: "error", message: describeDispatchedError(error) });
@@ -200,6 +226,78 @@ export async function runDispatchedDefault(options) {
200
226
  throw error;
201
227
  }
202
228
  }
229
+ /**
230
+ * Opens an Ask turn's host query service and writes its host MCP config, or returns `undefined`
231
+ * when the turn runs without one (not requested, a hybrid models config, or no `wren` on PATH).
232
+ */
233
+ async function openAskHostQueries(options, irPath) {
234
+ if (options.hostQueries === undefined)
235
+ return undefined;
236
+ if (options.modelsConfig !== undefined && await bindsNonAnthropicTier(irPath, options.modelsConfig))
237
+ return undefined;
238
+ let wren;
239
+ try {
240
+ wren = options.hostQueries.wren ?? resolveExecutableOnPath("wren");
241
+ }
242
+ catch {
243
+ return undefined;
244
+ }
245
+ const userProject = options.userProject;
246
+ const service = await openHostQueryService({
247
+ openAccess: async (signal) => openWrenComponentAccess({
248
+ executable: wren,
249
+ project: userProject,
250
+ fingerprint: await hashDirectory(userProject),
251
+ signal,
252
+ }),
253
+ ...(options.signal !== undefined ? { signal: options.signal } : {}),
254
+ });
255
+ try {
256
+ const configFile = await writeHostMcpConfig(service, {
257
+ proxy: defaultWrenHostProxy(),
258
+ mcpServer: { command: wren, prefixArgs: [] },
259
+ userProject,
260
+ });
261
+ return { service, configFile };
262
+ }
263
+ catch (error) {
264
+ await service.close();
265
+ throw error;
266
+ }
267
+ }
268
+ /**
269
+ * True when the models config binds a tier this IR's steps use to a non-Anthropic provider. Warble
270
+ * runs such a plan hybrid-staged and refuses `--host-mcp-config` for it; an all-Anthropic config (the
271
+ * only kind Setup writes for Claude subscription) keeps the host MCP. Every IR tier is checked, not
272
+ * just the dispatched component's, so a hybrid tier anywhere in the profile also skips it.
273
+ */
274
+ async function bindsNonAnthropicTier(irPath, modelsConfig) {
275
+ const ir = JSON.parse(await readFile(irPath, "utf8"));
276
+ const models = ModelConfig.fromYaml(await readFile(modelsConfig, "utf8"));
277
+ const tiers = new Set(ir.components.flatMap((component) => component.llm_calls.map((step) => step.tier)));
278
+ return [...tiers].some((tier) => models.binding(tier).provider !== "anthropic");
279
+ }
280
+ /**
281
+ * Writes the turn's `--host-mcp-config` file into the host query service's private 0700
282
+ * directory, as warble 0.16 reads it: a 0600 file owned by this user holding exactly
283
+ * `{name, command, args, tools, instruction}`, `command` absolute. The server is the Wren host
284
+ * proxy in front of `wren serve mcp`; the file names the credential file by path only, never its
285
+ * token. It is removed with the directory when the service closes.
286
+ */
287
+ export async function writeHostMcpConfig(service, options) {
288
+ if (!path.isAbsolute(options.proxy.command))
289
+ throw new Error("the host MCP command must be an absolute path");
290
+ const file = path.join(service.directory, "host-mcp.json");
291
+ const config = {
292
+ name: HOST_MCP_SERVER_NAME,
293
+ command: options.proxy.command,
294
+ args: wrenHostProxyArgs({ ...options, credentialFile: service.credentialFile }),
295
+ tools: [...HOST_MCP_TOOLS],
296
+ instruction: HOST_MCP_INSTRUCTION,
297
+ };
298
+ await writeFile(file, JSON.stringify(config), { encoding: "utf8", mode: 0o600, flag: "wx" });
299
+ return file;
300
+ }
203
301
  function describeDispatchedError(error) {
204
302
  if (error instanceof Error)
205
303
  return error.message;
@@ -0,0 +1,93 @@
1
+ import type { ComponentAccess } from "../components/broker.js";
2
+ import { type GovernedErrorClass } from "../components/wren-access.js";
3
+ import type { ResolvedCli } from "./agent-sdk-cli.js";
4
+ /**
5
+ * One query the host executed for a subscription turn or a native session, in the component
6
+ * runner's observation shape (`input.sql`, `output` carrying a host `query_id`), so the shared
7
+ * grounding predicate reads it unchanged.
8
+ */
9
+ export interface HostQueryObservation {
10
+ readonly tool: "run_sql";
11
+ readonly input: {
12
+ readonly sql: string;
13
+ readonly limit: number;
14
+ };
15
+ readonly output: unknown;
16
+ }
17
+ /**
18
+ * Per-scope bounds (one Ask turn, or one native session). Count, request and per-result bytes
19
+ * reuse the component runner's limits; the total caps what the host keeps in memory for one scope
20
+ * until its answer is grounded. Rows default to 1000 like `run_sql`. Like `run_sql`, the host
21
+ * probes one row past the limit to report truncation; the cap is 9999 so the probe stays within
22
+ * governed-stdio's 10000-row bound.
23
+ */
24
+ export declare const HOST_QUERY_LIMITS: Readonly<{
25
+ queries: 32;
26
+ requestBytes: 65536;
27
+ resultBytes: 1048576;
28
+ totalResultBytes: number;
29
+ defaultRows: 1000;
30
+ maxRows: 9999;
31
+ timeoutMs: 120000;
32
+ }>;
33
+ /** Error classes a host query may return; each transport owns the sentence the model sees. */
34
+ export type HostQueryErrorClass = GovernedErrorClass | "unauthorized" | "budget_exhausted";
35
+ export type HostQueryOutcome = {
36
+ readonly result: unknown;
37
+ } | {
38
+ readonly error: HostQueryErrorClass;
39
+ };
40
+ export interface HostQueryRecorderOptions {
41
+ /** Opens governed access for this scope; called at most once, on the first query. */
42
+ readonly openAccess: (signal: AbortSignal) => Promise<ComponentAccess>;
43
+ readonly signal?: AbortSignal;
44
+ }
45
+ /**
46
+ * Executes and records one scope's queries in process. The scope is the recorder: its
47
+ * observations are never shared, and its `query_id`s carry a tag no other recorder has.
48
+ */
49
+ export interface HostQueryRecorder {
50
+ execute(sql: unknown, limit: unknown): Promise<HostQueryOutcome>;
51
+ /** This scope's recorded observations, in execution order. Empty after `close()`. */
52
+ observations(): readonly HostQueryObservation[];
53
+ /** Stops accepting queries, discards observations, closes governed access. Idempotent. */
54
+ close(): Promise<void>;
55
+ }
56
+ /**
57
+ * Every query runs through governed access (`wren governed-stdio`), gets a scope-unique host
58
+ * `query_id` (`q<N>-<tag>`), and is recorded as an observation of this scope only.
59
+ */
60
+ export declare function createHostQueryRecorder(options: HostQueryRecorderOptions): HostQueryRecorder;
61
+ export interface HostQueryService {
62
+ /** This service's private 0700 directory; it is removed on `close()`. */
63
+ readonly directory: string;
64
+ /** A 0600 file holding this turn's socket path and token. Its own path carries no secret. */
65
+ readonly credentialFile: string;
66
+ /** This turn's recorded observations, in execution order. Empty after `close()`. */
67
+ observations(): readonly HostQueryObservation[];
68
+ /** Stops accepting queries, discards observations, closes governed access, removes the directory. */
69
+ close(): Promise<void>;
70
+ }
71
+ export type HostQueryServiceOptions = HostQueryRecorderOptions;
72
+ /**
73
+ * The host side of a subscription turn's `run_sql` (codex:local and the Claude agent-sdk Ask):
74
+ * a per-turn unix socket in a fresh 0700 directory that answers only requests carrying this
75
+ * turn's random token, in front of one {@link createHostQueryRecorder} for the turn.
76
+ */
77
+ export declare function openHostQueryService(options: HostQueryServiceOptions): Promise<HostQueryService>;
78
+ /**
79
+ * The argv after the proxy command: the proxy script, this turn's credential file, then the
80
+ * `wren serve mcp` command it fronts. Shared by codex:local's `--server-arg`s and the agent-sdk
81
+ * host MCP config so the two cannot drift.
82
+ */
83
+ export declare function wrenHostProxyArgs(options: {
84
+ readonly proxy: ResolvedCli;
85
+ readonly credentialFile: string;
86
+ readonly mcpServer: ResolvedCli;
87
+ readonly userProject: string;
88
+ }): string[];
89
+ /** The built proxy next to this module, else its source (Node strips its erasable types). */
90
+ export declare function defaultWrenHostProxy(): ResolvedCli;
91
+ /** Governed access spawns one executable with fixed arguments; a prefixed command cannot be bound. */
92
+ export declare function singleWrenExecutable(cli: ResolvedCli): string;
93
+ export declare function resolveExecutableOnPath(name: string): string;