@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
@@ -0,0 +1,257 @@
1
+ import { randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { accessSync, existsSync, constants as fsConstants } from "node:fs";
3
+ import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
4
+ import { createServer } from "node:net";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { COMPONENT_LIMITS } from "../components/runner.js";
9
+ import { GovernedWrenError } from "../components/wren-access.js";
10
+ /**
11
+ * Per-scope bounds (one Ask turn, or one native session). Count, request and per-result bytes
12
+ * reuse the component runner's limits; the total caps what the host keeps in memory for one scope
13
+ * until its answer is grounded. Rows default to 1000 like `run_sql`. Like `run_sql`, the host
14
+ * probes one row past the limit to report truncation; the cap is 9999 so the probe stays within
15
+ * governed-stdio's 10000-row bound.
16
+ */
17
+ export const HOST_QUERY_LIMITS = Object.freeze({
18
+ queries: COMPONENT_LIMITS.calls,
19
+ requestBytes: COMPONENT_LIMITS.requestBytes,
20
+ resultBytes: COMPONENT_LIMITS.resultBytes,
21
+ totalResultBytes: 8 * COMPONENT_LIMITS.resultBytes,
22
+ defaultRows: 1000,
23
+ maxRows: 9_999,
24
+ timeoutMs: COMPONENT_LIMITS.timeoutMs,
25
+ });
26
+ /**
27
+ * Every query runs through governed access (`wren governed-stdio`), gets a scope-unique host
28
+ * `query_id` (`q<N>-<tag>`), and is recorded as an observation of this scope only.
29
+ */
30
+ export function createHostQueryRecorder(options) {
31
+ const tag = randomBytes(4).toString("hex");
32
+ const controller = new AbortController();
33
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
34
+ const recorded = [];
35
+ let attempts = 0;
36
+ let recordedBytes = 0;
37
+ let closed = false;
38
+ let access;
39
+ const execute = async (sql, limit) => {
40
+ if (closed || signal.aborted)
41
+ return { error: "transport" };
42
+ if (typeof sql !== "string" || !sql.trim() || Buffer.byteLength(sql) > HOST_QUERY_LIMITS.requestBytes)
43
+ return { error: "invalid_request" };
44
+ if (limit !== undefined && limit !== null && (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1))
45
+ return { error: "invalid_request" };
46
+ if (attempts >= HOST_QUERY_LIMITS.queries)
47
+ return { error: "budget_exhausted" };
48
+ attempts += 1;
49
+ const rows = Math.min(HOST_QUERY_LIMITS.maxRows, typeof limit === "number" ? limit : HOST_QUERY_LIMITS.defaultRows);
50
+ let result;
51
+ try {
52
+ access ??= options.openAccess(signal);
53
+ const governed = await access;
54
+ result = await governed.query({ sql, limit: rows + 1 }, AbortSignal.any([signal, AbortSignal.timeout(HOST_QUERY_LIMITS.timeoutMs)]));
55
+ }
56
+ catch (error) {
57
+ return { error: error instanceof GovernedWrenError ? error.errorClass : "transport" };
58
+ }
59
+ if (closed)
60
+ return { error: "transport" };
61
+ if (typeof result !== "object" || result === null || Array.isArray(result))
62
+ return { error: "internal" };
63
+ const probed = result.rows;
64
+ if (!Array.isArray(probed))
65
+ return { error: "internal" };
66
+ // The N+1 probe, as plain `run_sql` reports it: more rows than asked means the shown rows
67
+ // are not the whole result. Only `rows` rows are returned and recorded.
68
+ const truncated = probed.length > rows;
69
+ const shown = truncated ? probed.slice(0, rows) : probed;
70
+ // The id is assigned here, once, so the observation and the model's copy carry the same id. The
71
+ // tag makes ids unique per scope: an id copied from another turn or session never names this one's query.
72
+ const output = { ...result, rows: shown, row_count: shown.length, truncated, query_id: `q${attempts}-${tag}` };
73
+ const bytes = Buffer.byteLength(JSON.stringify(output));
74
+ if (bytes > HOST_QUERY_LIMITS.resultBytes)
75
+ return { error: "result_too_large" };
76
+ if (recordedBytes + bytes > HOST_QUERY_LIMITS.totalResultBytes)
77
+ return { error: "budget_exhausted" };
78
+ recordedBytes += bytes;
79
+ recorded.push(Object.freeze({ tool: "run_sql", input: Object.freeze({ sql, limit: rows }), output: structuredClone(output) }));
80
+ return { result: output };
81
+ };
82
+ let closing;
83
+ const close = () => {
84
+ closing ??= (async () => {
85
+ closed = true;
86
+ recorded.length = 0;
87
+ controller.abort();
88
+ try {
89
+ if (access)
90
+ await (await access).close();
91
+ }
92
+ catch {
93
+ // Access that never opened, or already closed, has nothing left to release.
94
+ }
95
+ })();
96
+ return closing;
97
+ };
98
+ options.signal?.addEventListener("abort", () => { void close(); }, { once: true });
99
+ return { execute, observations: () => Object.freeze([...recorded]), close };
100
+ }
101
+ function requestFrame(value) {
102
+ if (typeof value !== "object" || value === null || Array.isArray(value))
103
+ return undefined;
104
+ const record = value;
105
+ if (typeof record.id !== "number" || !Number.isSafeInteger(record.id))
106
+ return undefined;
107
+ return { token: record.token, id: record.id, sql: record.sql, limit: record.limit };
108
+ }
109
+ /**
110
+ * The host side of a subscription turn's `run_sql` (codex:local and the Claude agent-sdk Ask):
111
+ * a per-turn unix socket in a fresh 0700 directory that answers only requests carrying this
112
+ * turn's random token, in front of one {@link createHostQueryRecorder} for the turn.
113
+ */
114
+ export async function openHostQueryService(options) {
115
+ if (process.platform === "win32")
116
+ throw new Error("the host query service requires unix sockets");
117
+ const directory = await mkdtemp(path.join(os.tmpdir(), "genbi-hq-"));
118
+ const token = Buffer.from(randomBytes(32).toString("base64url"), "utf8");
119
+ const socketPath = path.join(directory, "s");
120
+ const credentialFile = path.join(directory, "credential.json");
121
+ const recorder = createHostQueryRecorder(options);
122
+ const sockets = new Set();
123
+ let closed = false;
124
+ const authorized = (candidate) => {
125
+ if (typeof candidate !== "string")
126
+ return false;
127
+ const presented = Buffer.from(candidate, "utf8");
128
+ return presented.length === token.length && timingSafeEqual(presented, token);
129
+ };
130
+ const serve = (socket) => {
131
+ if (closed) {
132
+ socket.destroy();
133
+ return;
134
+ }
135
+ sockets.add(socket);
136
+ socket.on("close", () => sockets.delete(socket));
137
+ socket.on("error", () => socket.destroy());
138
+ let buffer = "";
139
+ let tail = Promise.resolve();
140
+ socket.setEncoding("utf8");
141
+ socket.on("data", (chunk) => {
142
+ buffer += chunk;
143
+ if (buffer.length > 2 * HOST_QUERY_LIMITS.requestBytes) {
144
+ socket.destroy();
145
+ return;
146
+ }
147
+ let newline = buffer.indexOf("\n");
148
+ while (newline >= 0) {
149
+ const line = buffer.slice(0, newline);
150
+ buffer = buffer.slice(newline + 1);
151
+ newline = buffer.indexOf("\n");
152
+ let frame;
153
+ try {
154
+ frame = requestFrame(JSON.parse(line));
155
+ }
156
+ catch {
157
+ frame = undefined;
158
+ }
159
+ if (!frame) {
160
+ socket.destroy();
161
+ return;
162
+ }
163
+ const { id, sql, limit } = frame;
164
+ if (!authorized(frame.token)) {
165
+ socket.end(`${JSON.stringify({ id, error: { class: "unauthorized" } })}\n`);
166
+ return;
167
+ }
168
+ tail = tail.then(async () => {
169
+ const outcome = await recorder.execute(sql, limit);
170
+ if (socket.destroyed)
171
+ return;
172
+ socket.write(`${JSON.stringify("result" in outcome ? { id, result: outcome.result } : { id, error: { class: outcome.error } })}\n`);
173
+ }).catch(() => { socket.destroy(); });
174
+ }
175
+ });
176
+ };
177
+ const server = createServer(serve);
178
+ try {
179
+ await new Promise((resolve, reject) => {
180
+ server.once("error", reject);
181
+ server.listen(socketPath, () => { server.off("error", reject); resolve(); });
182
+ });
183
+ await chmod(socketPath, 0o600);
184
+ await writeFile(credentialFile, JSON.stringify({ socket: socketPath, token: token.toString("utf8") }), { encoding: "utf8", mode: 0o600, flag: "wx" });
185
+ }
186
+ catch (error) {
187
+ server.close();
188
+ await recorder.close();
189
+ await rm(directory, { recursive: true, force: true });
190
+ throw error;
191
+ }
192
+ let closing;
193
+ const close = () => {
194
+ closing ??= (async () => {
195
+ closed = true;
196
+ for (const socket of sockets)
197
+ socket.destroy();
198
+ await new Promise((resolve) => server.close(() => resolve()));
199
+ try {
200
+ await recorder.close();
201
+ }
202
+ finally {
203
+ await rm(directory, { recursive: true, force: true });
204
+ }
205
+ })();
206
+ return closing;
207
+ };
208
+ options.signal?.addEventListener("abort", () => { void close(); }, { once: true });
209
+ return { directory, credentialFile, observations: recorder.observations, close };
210
+ }
211
+ /**
212
+ * The argv after the proxy command: the proxy script, this turn's credential file, then the
213
+ * `wren serve mcp` command it fronts. Shared by codex:local's `--server-arg`s and the agent-sdk
214
+ * host MCP config so the two cannot drift.
215
+ */
216
+ export function wrenHostProxyArgs(options) {
217
+ return [
218
+ ...options.proxy.prefixArgs,
219
+ "--credential-file",
220
+ options.credentialFile,
221
+ "--",
222
+ options.mcpServer.command,
223
+ ...options.mcpServer.prefixArgs,
224
+ "serve",
225
+ "mcp",
226
+ "--project",
227
+ options.userProject,
228
+ "--quiet",
229
+ ];
230
+ }
231
+ /** The built proxy next to this module, else its source (Node strips its erasable types). */
232
+ export function defaultWrenHostProxy() {
233
+ const built = fileURLToPath(new URL("./wren-host-proxy.js", import.meta.url));
234
+ const script = existsSync(built) ? built : fileURLToPath(new URL("./wren-host-proxy.ts", import.meta.url));
235
+ return { command: process.execPath, prefixArgs: [script] };
236
+ }
237
+ /** Governed access spawns one executable with fixed arguments; a prefixed command cannot be bound. */
238
+ export function singleWrenExecutable(cli) {
239
+ if (cli.prefixArgs.length > 0)
240
+ throw new Error("host queries need a standalone wren executable");
241
+ return cli.command;
242
+ }
243
+ export function resolveExecutableOnPath(name) {
244
+ for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
245
+ if (!directory)
246
+ continue;
247
+ const candidate = path.join(directory, name);
248
+ try {
249
+ accessSync(candidate, fsConstants.X_OK);
250
+ return candidate;
251
+ }
252
+ catch {
253
+ // Keep searching.
254
+ }
255
+ }
256
+ throw new Error(`could not find the "${name}" executable on PATH`);
257
+ }
@@ -1,11 +1,32 @@
1
+ import { type TierBinding } from "../providers/index.js";
1
2
  import type { RunAgentResult } from "../session/index.js";
2
3
  import type { InProcessOptions } from "./types.js";
3
- /** Projects a bundle-wide validated map onto the component selected for one turn. */
4
+ /**
5
+ * Projects a bundle-wide validated map onto the component selected for one
6
+ * turn, keyed on (mount, tier): an `<agent.id>/<tier>` entry wins over the
7
+ * bare tier entry, so the same tier name can bind a different model per mount.
8
+ */
4
9
  export declare function filterTierBindingForAgent(agent: {
10
+ readonly id?: string;
5
11
  readonly steps: readonly {
6
12
  readonly tier: string;
7
13
  }[];
8
14
  }, tierBinding: Readonly<Record<string, import("../providers/index.js").AdapterSpec>>): Readonly<Record<string, import("../providers/index.js").AdapterSpec>>;
15
+ /**
16
+ * The legacy (single-agent, IR 0.6) path has no caller/callee split: every
17
+ * step, and the render stage, sees rows. So a zone-aware binding on this path
18
+ * must be all-private; a `public` or unset zone on any tier the agent (or its
19
+ * render stage) uses is a loud failure, the same posture as the composed gate.
20
+ */
21
+ export declare function assertLegacyZoneBinding(agent: {
22
+ readonly id: string;
23
+ readonly steps: readonly {
24
+ readonly tier: string;
25
+ readonly realization: {
26
+ readonly kind: string;
27
+ };
28
+ }[];
29
+ }, binding: TierBinding, renderTier: string | undefined): void;
9
30
  /**
10
31
  * Resolves the `rootDir` the native `write_artifact` tool's
11
32
  * `createLocalExecutionEnv` scope is rooted at. Precedence: an explicit
@@ -3,20 +3,57 @@ import path from "node:path";
3
3
  import { createDefaultCapabilityRegistry } from "../capability/registry.js";
4
4
  import { createLocalExecutionEnv } from "../exec/index.js";
5
5
  import { deriveEnforcement } from "../guardrails/index.js";
6
- import { createDefaultProviderRegistry } from "../providers/index.js";
6
+ import { createDefaultProviderRegistry, isZoneAwareBinding, resolveTierSpec } from "../providers/index.js";
7
+ import { ZoneGateError } from "../components/zone-gate.js";
7
8
  import { runAgent } from "../session/index.js";
8
9
  import { createWrenNativeToolRegistry, resolveWrenBinary } from "../tools/index.js";
9
10
  import { deriveAdapterSpec } from "./adapter-spec.js";
10
11
  import { describeBundle } from "./describe.js";
11
12
  import { buildHybridTierBinding, buildUniformTierBinding } from "./tier-binding.js";
13
+ import { executionPlanFor } from "../components/display.js";
14
+ import { runInProcessComponents } from "../components/in-process.js";
12
15
  const ANSWER_QUERY_AGENT_ID = "answer_query";
13
16
  /** Operator-wide override for `resolveArtifactsDir`'s default, e.g. for a long-running BFF process. */
14
17
  const ARTIFACTS_DIR_ENV_VAR = "WREN_HARNESS_ARTIFACTS_DIR";
15
- /** Projects a bundle-wide validated map onto the component selected for one turn. */
18
+ /**
19
+ * Projects a bundle-wide validated map onto the component selected for one
20
+ * turn, keyed on (mount, tier): an `<agent.id>/<tier>` entry wins over the
21
+ * bare tier entry, so the same tier name can bind a different model per mount.
22
+ */
16
23
  export function filterTierBindingForAgent(agent, tierBinding) {
24
+ const binding = { tiers: tierBinding };
17
25
  return Object.fromEntries([...new Set(agent.steps.map((step) => step.tier))]
18
- .filter((tier) => tierBinding[tier] !== undefined)
19
- .map((tier) => [tier, tierBinding[tier]]));
26
+ .map((tier) => [tier, resolveTierSpec(binding, tier, agent.id)?.spec])
27
+ .filter((entry) => entry[1] !== undefined));
28
+ }
29
+ /**
30
+ * The legacy (single-agent, IR 0.6) path has no caller/callee split: every
31
+ * step, and the render stage, sees rows. So a zone-aware binding on this path
32
+ * must be all-private; a `public` or unset zone on any tier the agent (or its
33
+ * render stage) uses is a loud failure, the same posture as the composed gate.
34
+ */
35
+ export function assertLegacyZoneBinding(agent, binding, renderTier) {
36
+ if (!isZoneAwareBinding(binding))
37
+ return;
38
+ const violations = [];
39
+ const check = (tier, what) => {
40
+ const resolved = resolveTierSpec(binding, tier, agent.id);
41
+ const zone = resolved?.spec.zone ?? "unbound";
42
+ if (!resolved)
43
+ violations.push({ code: "unbound_tier", component: agent.id, tier, zone, message: `${what} (tier ${tier}) has no binding entry` });
44
+ else if (zone === "unbound")
45
+ violations.push({ code: "missing_zone", component: agent.id, tier, zone, message: `${what} (tier ${tier}) sees project data but binding entry "${resolved.key}" declares no zone` });
46
+ else if (zone !== "private")
47
+ violations.push({ code: "data_step_public", component: agent.id, tier, zone, message: `${what} (tier ${tier}) sees project data but binding entry "${resolved.key}" is zone: ${zone}; it must be private` });
48
+ };
49
+ for (const step of agent.steps)
50
+ check(step.tier, `step ${agent.id}.${step.tier}`);
51
+ const independent = agent.steps.filter((step) => step.realization.kind === "independent");
52
+ const render = renderTier ?? independent[independent.length - 1]?.tier;
53
+ if (render !== undefined)
54
+ check(render, `render stage of ${agent.id}`);
55
+ if (violations.length > 0)
56
+ throw new ZoneGateError(violations);
20
57
  }
21
58
  /**
22
59
  * Resolves the `rootDir` the native `write_artifact` tool's
@@ -56,6 +93,12 @@ export function resolveArtifactsDir(outDir) {
56
93
  */
57
94
  export async function runInProcessDefault(options) {
58
95
  const bundle = options.bundle ?? (await describeBundle(options));
96
+ if (bundle.vercel_bundle_version === "0.2") {
97
+ const plan = executionPlanFor(bundle);
98
+ if (!plan)
99
+ throw new Error("Composed display has no trusted execution plan");
100
+ return runInProcessComponents(plan, options);
101
+ }
59
102
  // Intent routing (server/turn.ts) picks the agent id when present; falls back to answer_query,
60
103
  // the original default from before intent routing existed.
61
104
  const agentId = options.agentId ?? ANSWER_QUERY_AGENT_ID;
@@ -69,6 +112,13 @@ export async function runInProcessDefault(options) {
69
112
  // here; `buildHybridTierBinding` intentionally rejects tiers used solely by
70
113
  // another component.
71
114
  const agentTierBinding = options.tierBinding === undefined ? undefined : filterTierBindingForAgent(agent, options.tierBinding);
115
+ if (options.tierBinding !== undefined || options.disclosurePolicy !== undefined || options.zoneRoles !== undefined) {
116
+ assertLegacyZoneBinding(agent, {
117
+ tiers: options.tierBinding ?? {},
118
+ ...(options.disclosurePolicy !== undefined ? { disclosurePolicy: options.disclosurePolicy } : {}),
119
+ ...(options.zoneRoles !== undefined ? { roles: options.zoneRoles } : {}),
120
+ }, undefined);
121
+ }
72
122
  const binding = options.tierBinding !== undefined
73
123
  ? buildHybridTierBinding(agent, agentTierBinding)
74
124
  : buildUniformTierBinding(agent, deriveAdapterSpec(options.authChoice, options.model !== undefined ? { model: options.model } : {}));
@@ -41,8 +41,8 @@ export async function route(options) {
41
41
  const deployment = options.deployment ?? "personal";
42
42
  const { warnings } = enforceCompliance(authChoice, { deployment });
43
43
  if (authChoice.mode === "subscription") {
44
- if (options.tierBinding !== undefined) {
45
- throw new Error("tierBinding (hybrid in-process per-tier routing) has no effect under a subscription " +
44
+ if (options.tierBinding !== undefined || options.disclosurePolicy !== undefined || options.zoneRoles !== undefined) {
45
+ throw new Error("tierBinding (with disclosurePolicy/zoneRoles: hybrid in-process per-tier routing) has no effect under a subscription " +
46
46
  "authChoice — dispatched has no adapter/tier binding of its own; use modelsConfig instead " +
47
47
  "(warble-agent-sdk's own --models-config per-step routing)");
48
48
  }
@@ -86,6 +86,9 @@ export async function route(options) {
86
86
  ...(options.agentId !== undefined ? { agentId: options.agentId } : {}),
87
87
  ...(options.chatTimeoutMs !== undefined ? { chatTimeoutMs: options.chatTimeoutMs } : {}),
88
88
  ...(options.signal !== undefined ? { signal: options.signal } : {}),
89
+ // An Ask turn: its data queries can run through a per-turn host query service, so the
90
+ // answer can be grounded on host evidence (see `withHostVerification`).
91
+ hostQueries: {},
89
92
  });
90
93
  return { backend: "agent-sdk", warnings, ...result };
91
94
  }
@@ -96,6 +99,7 @@ export async function route(options) {
96
99
  "per-tier adapter map)");
97
100
  }
98
101
  const result = await runInProcess({
102
+ ...(options.signal !== undefined ? { signal: options.signal } : {}),
99
103
  authChoice,
100
104
  profileSource: options.profileSource,
101
105
  userProject: options.userProject,
@@ -107,6 +111,8 @@ export async function route(options) {
107
111
  ...(options.bundle !== undefined ? { bundle: options.bundle } : {}),
108
112
  ...(options.mcpServers !== undefined ? { mcpServers: options.mcpServers } : {}),
109
113
  ...(options.tierBinding !== undefined ? { tierBinding: options.tierBinding } : {}),
114
+ ...(options.disclosurePolicy !== undefined ? { disclosurePolicy: options.disclosurePolicy } : {}),
115
+ ...(options.zoneRoles !== undefined ? { zoneRoles: options.zoneRoles } : {}),
110
116
  ...(options.onEvent !== undefined ? { onEvent: options.onEvent } : {}),
111
117
  ...(options.agentId !== undefined ? { agentId: options.agentId } : {}),
112
118
  });
@@ -2,7 +2,7 @@ import type { AuthChoice } from "../auth/index.js";
2
2
  import type { Bundle } from "../bundle/schema.js";
3
3
  import type { Deployment } from "../compliance/index.js";
4
4
  import type { AgentEvent } from "../events/index.js";
5
- import type { AdapterSpec } from "../providers/index.js";
5
+ import type { AdapterSpec, DisclosurePolicy, ZoneRoles } from "../providers/index.js";
6
6
  import type { RunAgentResult } from "../session/index.js";
7
7
  import type { McpServerConfigMap } from "../tools/index.js";
8
8
  import type { ResolvedCli } from "./agent-sdk-cli.js";
@@ -15,6 +15,7 @@ import type { CodexManifestModels } from "./codex-local-manifest.js";
15
15
  * tool through the real `wren` CLI (native tools) against `userProject`.
16
16
  */
17
17
  export interface InProcessOptions {
18
+ readonly signal?: AbortSignal;
18
19
  readonly authChoice: Extract<AuthChoice, {
19
20
  mode: "api-key" | "local" | "gateway";
20
21
  }>;
@@ -55,6 +56,19 @@ export interface InProcessOptions {
55
56
  * coverage is total-or-loud-fail.
56
57
  */
57
58
  readonly tierBinding?: Readonly<Record<string, AdapterSpec>>;
59
+ /**
60
+ * Hybrid privacy split: the egress disclosure policy the verification step
61
+ * enforces on every callee result before a caller sees it. Its presence
62
+ * arms the zone gate (see `components/zone-gate.ts`) and requires
63
+ * `zoneRoles.judge`.
64
+ */
65
+ readonly disclosurePolicy?: DisclosurePolicy;
66
+ /** Hybrid privacy split: binding keys of the judge / render tiers the zone gate checks by role. */
67
+ readonly zoneRoles?: ZoneRoles;
68
+ /** Hybrid privacy split: size bound for the capability card handed to public-zone steps (default 32 KiB). */
69
+ readonly capabilityCard?: {
70
+ readonly maxBytes?: number;
71
+ };
58
72
  /** Live-event layer: forwarded to `RunAgentContext.onEvent`. See that field's doc comment. */
59
73
  readonly onEvent?: (event: AgentEvent) => void;
60
74
  /**
@@ -163,6 +177,18 @@ export interface DispatchedOptions {
163
177
  * flag) when unset, which is the pre-existing behavior.
164
178
  */
165
179
  readonly resumeSessionId?: string;
180
+ /**
181
+ * Ask turns only (`route()` sets it; the setup and enrichment runners do not): open a per-turn
182
+ * host query service and hand the dispatcher a host MCP server (`--host-mcp-config`) whose
183
+ * `run_sql` executes through it, so the answer can be grounded on what the host ran. `wren` is
184
+ * the standalone Wren executable for both governed access and `wren serve mcp`; when unset it
185
+ * is resolved on PATH, and when none is found the turn runs without host queries (and its
186
+ * answer stays unverified). Ignored under `modelsConfig`: warble refuses a host MCP for a
187
+ * hybrid-staged plan, which a models config can produce.
188
+ */
189
+ readonly hostQueries?: {
190
+ readonly wren?: string;
191
+ };
166
192
  /** Cancels the owned dispatcher process without falling back to another vendor. */
167
193
  readonly signal?: AbortSignal;
168
194
  }
@@ -177,6 +203,11 @@ export interface DispatchedResult {
177
203
  * the SDK itself never produced a session id to report.
178
204
  */
179
205
  readonly sessionId?: string | null;
206
+ /**
207
+ * Ask turns with host queries only: the queries this turn's host query service executed for
208
+ * the model's host `run_sql`, the only evidence its answer may be grounded on. Never persisted.
209
+ */
210
+ readonly hostObservations?: readonly import("./host-query.js").HostQueryObservation[];
180
211
  }
181
212
  export type DispatchedExecutor = (options: DispatchedOptions) => Promise<DispatchedResult>;
182
213
  /** Codex subscription Ask runs through warble-codex-local, never the Claude dispatched dispatcher. */
@@ -209,6 +240,11 @@ export interface CodexAskOptions {
209
240
  }
210
241
  export interface CodexAskResult {
211
242
  readonly finalText: string;
243
+ /**
244
+ * codex:local only: the queries this turn's host query service executed for the model's
245
+ * `run_sql`, the only evidence its answer may be grounded on. Never persisted.
246
+ */
247
+ readonly hostObservations?: readonly import("./host-query.js").HostQueryObservation[];
212
248
  }
213
249
  export type CodexAskExecutor = (options: CodexAskOptions) => Promise<CodexAskResult>;
214
250
  /**
@@ -243,6 +279,10 @@ export interface RouteOptions {
243
279
  readonly mcpServers?: McpServerConfigMap;
244
280
  /** In-process only (hybrid mode); see `InProcessOptions.tierBinding`. `route()` rejects this alongside a `subscription` authChoice. */
245
281
  readonly tierBinding?: Readonly<Record<string, AdapterSpec>>;
282
+ /** In-process only; see `InProcessOptions.disclosurePolicy`. */
283
+ readonly disclosurePolicy?: DisclosurePolicy;
284
+ /** In-process only; see `InProcessOptions.zoneRoles`. */
285
+ readonly zoneRoles?: ZoneRoles;
246
286
  /** Dispatched only (hybrid mode); see `DispatchedOptions.modelsConfig`. `route()` rejects this alongside a non-`subscription` authChoice. */
247
287
  readonly modelsConfig?: string;
248
288
  /** Live-event layer: forwarded to whichever mode executor runs. See `InProcessOptions.onEvent`/`DispatchedOptions.onEvent`. */
@@ -283,4 +323,10 @@ export type RouteResult = ({
283
323
  } & DispatchedResult) | ({
284
324
  readonly backend: "codex-local";
285
325
  readonly warnings: readonly string[];
326
+ } & CodexAskResult) | ({
327
+ readonly backend: "codex-app-server";
328
+ readonly warnings: readonly string[];
329
+ /** Host-captured normalized component evidence; never recovered from model prose. */
330
+ readonly envelope?: import("../render/envelope.js").RenderEnvelope;
331
+ readonly dataAttempted?: boolean;
286
332
  } & CodexAskResult);
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { type Socket } from "node:net";
3
+ export interface Credential {
4
+ readonly socket: string;
5
+ readonly token: string;
6
+ }
7
+ export interface HostOutcome {
8
+ readonly result?: unknown;
9
+ readonly error?: string;
10
+ }
11
+ /**
12
+ * Accepts only an absolute, regular, user-owned file no one else can read or write. The file is
13
+ * opened without following a symlink and checked and read through that one descriptor, so what
14
+ * is checked is what is read.
15
+ */
16
+ export declare function readCredential(file: string | undefined): Credential | undefined;
17
+ /**
18
+ * One connection at a time to the turn's host query service, reconnecting after a drop. Only
19
+ * the current socket's failure fails the pending requests (all of which were sent on it): a
20
+ * stale socket closing late never fails a request pending on its replacement. Each socket has
21
+ * its own read buffer.
22
+ */
23
+ export declare class HostConnection {
24
+ private readonly credential;
25
+ private readonly open;
26
+ private readonly pending;
27
+ private socket;
28
+ private nextId;
29
+ constructor(credential: Credential, open?: (socketPath: string) => Socket);
30
+ request(sql: string, limit: number | undefined): Promise<HostOutcome>;
31
+ close(): void;
32
+ private current;
33
+ }