@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,34 @@
1
+ import type { AgentEvent } from "./types.js";
2
+ /**
3
+ * Plain `Omit<AgentEvent, K>` does NOT distribute over the `AgentEvent`
4
+ * union — `Omit` is `Pick<T, Exclude<keyof T, K>>`, and `keyof` of a union
5
+ * only yields the keys common to every member, collapsing every variant-
6
+ * specific field (`stepId`, `tool`, `envelope`, ...) down to nothing.
7
+ * Distributing over each member first (via the naked `T extends any`
8
+ * conditional) preserves each variant's own fields and its `kind` literal.
9
+ */
10
+ export type DistributiveOmit<T, K extends keyof any> = T extends unknown ? Omit<T, K> : never;
11
+ /**
12
+ * What an emission site (`runAgent`, `executeAgent`) actually knows: an
13
+ * `AgentEvent` minus the `runId`/`seq` bookkeeping that only the emitter
14
+ * that owns the run's counter can stamp.
15
+ */
16
+ export type AgentEventInput = DistributiveOmit<AgentEvent, "runId" | "seq">;
17
+ export interface AgentEventEmitter {
18
+ readonly runId: string;
19
+ /** Stamps `runId`/`seq` onto `event` and forwards it to the sink. No-op (and never throws) when no sink was given. */
20
+ emit(event: AgentEventInput): void;
21
+ }
22
+ /**
23
+ * Owns one run's `runId` + monotonic `seq` counter, so two independent
24
+ * emission sites (`runAgent` for run/answer/refusal/run.finish/error;
25
+ * `executeAgent` for step/tool/artifact) can each describe *what happened*
26
+ * without needing to coordinate numbering themselves. `runAgent` is the
27
+ * natural sole owner: it creates one `AgentEventEmitter` per call and passes
28
+ * `emit` down as `ExecuteAgentContext.onEvent`.
29
+ *
30
+ * `sink` is the outward-facing seam (`RunAgentContext.onEvent`/
31
+ * `RouteOptions.onEvent`), typed `(e: AgentEvent) => void` exactly as the
32
+ * contract specifies — callers never see the bookkeeping split.
33
+ */
34
+ export declare function createAgentEventEmitter(sink: ((event: AgentEvent) => void) | undefined, runId?: string): AgentEventEmitter;
@@ -0,0 +1,25 @@
1
+ import { randomUUID } from "node:crypto";
2
+ /**
3
+ * Owns one run's `runId` + monotonic `seq` counter, so two independent
4
+ * emission sites (`runAgent` for run/answer/refusal/run.finish/error;
5
+ * `executeAgent` for step/tool/artifact) can each describe *what happened*
6
+ * without needing to coordinate numbering themselves. `runAgent` is the
7
+ * natural sole owner: it creates one `AgentEventEmitter` per call and passes
8
+ * `emit` down as `ExecuteAgentContext.onEvent`.
9
+ *
10
+ * `sink` is the outward-facing seam (`RunAgentContext.onEvent`/
11
+ * `RouteOptions.onEvent`), typed `(e: AgentEvent) => void` exactly as the
12
+ * contract specifies — callers never see the bookkeeping split.
13
+ */
14
+ export function createAgentEventEmitter(sink, runId = randomUUID()) {
15
+ let seq = 0;
16
+ return {
17
+ runId,
18
+ emit(event) {
19
+ if (!sink)
20
+ return;
21
+ seq += 1;
22
+ sink({ ...event, runId, seq });
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,4 @@
1
+ export { createAgentEventEmitter } from "./emitter.js";
2
+ export type { AgentEventEmitter, AgentEventInput } from "./emitter.js";
3
+ export { MalformedSseFrameError, parseAgentEvent, serializeAgentEvent } from "./sse.js";
4
+ export type { AgentEvent, AgentEventBase, AgentEventKind, AnswerAgentEvent, ArtifactAgentEvent, ArtifactKind, ErrorAgentEvent, RefusalAgentEvent, RunFinishEvent, RunStartEvent, StepFinishEvent, StepStartEvent, StepTrace, TokenEvent, ToolCallEvent, ToolResultEvent, TraceStep, } from "./types.js";
@@ -0,0 +1,2 @@
1
+ export { createAgentEventEmitter } from "./emitter.js";
2
+ export { MalformedSseFrameError, parseAgentEvent, serializeAgentEvent } from "./sse.js";
@@ -0,0 +1,20 @@
1
+ import type { AgentEvent } from "./types.js";
2
+ /**
3
+ * Serializes one `AgentEvent` as a single `text/event-stream` frame per the
4
+ * harness→BFF wire format: `event: <kind>\ndata:
5
+ * <json>\n\n`. The harness itself never opens a socket — the BFF runs it
6
+ * In-process and consumes `AgentEvent`s directly via `onEvent` — this is
7
+ * only for the documented "if a future out-of-process split is wanted"
8
+ * fallback, defined + unit-tested here so it exists when needed.
9
+ */
10
+ export declare function serializeAgentEvent(event: AgentEvent): string;
11
+ export declare class MalformedSseFrameError extends Error {
12
+ constructor(reason: string, frame: string);
13
+ }
14
+ /**
15
+ * The round-trip counterpart to `serializeAgentEvent`. Not a general SSE-
16
+ * stream parser (no multi-frame buffering, no comment/retry-field support)
17
+ * — the wire format here is always exactly one `event:` line + one `data:`
18
+ * line per frame, so this only ever needs to split a single frame in two.
19
+ */
20
+ export declare function parseAgentEvent(frame: string): AgentEvent;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Serializes one `AgentEvent` as a single `text/event-stream` frame per the
3
+ * harness→BFF wire format: `event: <kind>\ndata:
4
+ * <json>\n\n`. The harness itself never opens a socket — the BFF runs it
5
+ * In-process and consumes `AgentEvent`s directly via `onEvent` — this is
6
+ * only for the documented "if a future out-of-process split is wanted"
7
+ * fallback, defined + unit-tested here so it exists when needed.
8
+ */
9
+ export function serializeAgentEvent(event) {
10
+ return `event: ${event.kind}\ndata: ${JSON.stringify(event)}\n\n`;
11
+ }
12
+ export class MalformedSseFrameError extends Error {
13
+ constructor(reason, frame) {
14
+ super(`malformed SSE AgentEvent frame (${reason}): ${JSON.stringify(frame)}`);
15
+ this.name = "MalformedSseFrameError";
16
+ }
17
+ }
18
+ /**
19
+ * The round-trip counterpart to `serializeAgentEvent`. Not a general SSE-
20
+ * stream parser (no multi-frame buffering, no comment/retry-field support)
21
+ * — the wire format here is always exactly one `event:` line + one `data:`
22
+ * line per frame, so this only ever needs to split a single frame in two.
23
+ */
24
+ export function parseAgentEvent(frame) {
25
+ const trimmed = frame.endsWith("\n\n") ? frame.slice(0, -2) : frame;
26
+ const lines = trimmed.split("\n");
27
+ const eventLine = lines.find((line) => line.startsWith("event: "));
28
+ const dataLine = lines.find((line) => line.startsWith("data: "));
29
+ if (!eventLine || !dataLine) {
30
+ throw new MalformedSseFrameError("missing event: or data: line", frame);
31
+ }
32
+ const kind = eventLine.slice("event: ".length);
33
+ let parsed;
34
+ try {
35
+ parsed = JSON.parse(dataLine.slice("data: ".length));
36
+ }
37
+ catch {
38
+ throw new MalformedSseFrameError("data: line is not valid JSON", frame);
39
+ }
40
+ if (typeof parsed !== "object" || parsed === null || !("kind" in parsed)) {
41
+ throw new MalformedSseFrameError("data payload has no kind field", frame);
42
+ }
43
+ const data = parsed;
44
+ if (data.kind !== kind) {
45
+ throw new MalformedSseFrameError(`event: "${kind}" does not match data.kind "${data.kind}"`, frame);
46
+ }
47
+ return data;
48
+ }
@@ -0,0 +1,131 @@
1
+ import type { RenderEnvelope } from "../render/index.js";
2
+ /**
3
+ * The mode-agnostic, back-end-emitted event union. Both in-process (the
4
+ * Vercel AI SDK) and dispatched (shelled `warble-agent-sdk chat`) emit this same
5
+ * shape; the BFF (out of scope here) folds it into the UI's own SSE wire
6
+ * frames.
7
+ *
8
+ * `parent`/`depth` (on `step.*`/`tool.*`) model sub-agent nesting. In-process has
9
+ * no Task mechanism — `executeAgent` runs a flat in-process DAG — so its
10
+ * events always carry `depth: 0` and never set `parent`. Only dispatched can
11
+ * populate them (this harness does not currently parse dispatched's own nesting;
12
+ * see `route/dispatched.ts`'s doc comment for the gap).
13
+ */
14
+ export type AgentEventKind = "run.start" | "step.start" | "step.finish" | "tool.call" | "tool.result" | "token" | "answer" | "refusal" | "artifact" | "run.finish" | "error";
15
+ /** Every `AgentEvent` carries these — stamped by `createAgentEventEmitter`, never by an emission site itself. */
16
+ export interface AgentEventBase {
17
+ readonly runId: string;
18
+ readonly seq: number;
19
+ }
20
+ export interface RunStartEvent extends AgentEventBase {
21
+ readonly kind: "run.start";
22
+ readonly mode: "A" | "B";
23
+ readonly agentId: string;
24
+ }
25
+ export interface StepStartEvent extends AgentEventBase {
26
+ readonly kind: "step.start";
27
+ readonly stepId: string;
28
+ readonly name: string;
29
+ readonly tier: string;
30
+ readonly parent?: string;
31
+ readonly depth: number;
32
+ }
33
+ export interface StepFinishEvent extends AgentEventBase {
34
+ readonly kind: "step.finish";
35
+ readonly stepId: string;
36
+ readonly name: string;
37
+ readonly status: "ok" | "error";
38
+ /**
39
+ * The step's own reasoning/output: on `"ok"` this is
40
+ * `summarizeToolOutput(text)` (the step's produced artifact text, bounded);
41
+ * on `"error"` it's the best available description of what went wrong.
42
+ * Optional/additive — a step.finish with no `detail` behaves exactly as it
43
+ * did before this field existed.
44
+ */
45
+ readonly detail?: string;
46
+ }
47
+ export interface ToolCallEvent extends AgentEventBase {
48
+ readonly kind: "tool.call";
49
+ readonly stepId: string;
50
+ readonly callId: string;
51
+ readonly tool: string;
52
+ readonly input?: unknown;
53
+ readonly parent?: string;
54
+ readonly depth: number;
55
+ readonly status: "running";
56
+ }
57
+ export interface ToolResultEvent extends AgentEventBase {
58
+ readonly kind: "tool.result";
59
+ readonly stepId: string;
60
+ readonly callId: string;
61
+ readonly tool: string;
62
+ readonly status: "success" | "error";
63
+ /**
64
+ * Optional late-bound input for back-ends that only receive a safe tool
65
+ * payload when the call completes. LiveWorkLog merges this onto the
66
+ * matching tool.call entry before persisting the result.
67
+ */
68
+ readonly input?: unknown;
69
+ readonly summary?: string;
70
+ readonly error?: string;
71
+ }
72
+ export interface TokenEvent extends AgentEventBase {
73
+ readonly kind: "token";
74
+ readonly text: string;
75
+ }
76
+ /** `envelope` is in-process's shape (`RenderEnvelope`); `text` is dispatched's (`finalText`). Never both. */
77
+ export interface AnswerAgentEvent extends AgentEventBase {
78
+ readonly kind: "answer";
79
+ readonly envelope?: RenderEnvelope;
80
+ readonly text?: string;
81
+ }
82
+ export interface RefusalAgentEvent extends AgentEventBase {
83
+ readonly kind: "refusal";
84
+ readonly reason: string;
85
+ readonly envelope?: RenderEnvelope;
86
+ }
87
+ export type ArtifactKind = "dashboard" | "report" | "chart";
88
+ export interface ArtifactAgentEvent extends AgentEventBase {
89
+ readonly kind: "artifact";
90
+ readonly name: string;
91
+ readonly artifactKind: ArtifactKind;
92
+ readonly location: string;
93
+ }
94
+ export interface RunFinishEvent extends AgentEventBase {
95
+ readonly kind: "run.finish";
96
+ readonly status: "answer" | "refusal" | "error";
97
+ }
98
+ export interface ErrorAgentEvent extends AgentEventBase {
99
+ readonly kind: "error";
100
+ readonly message: string;
101
+ }
102
+ export type AgentEvent = RunStartEvent | StepStartEvent | StepFinishEvent | ToolCallEvent | ToolResultEvent | TokenEvent | AnswerAgentEvent | RefusalAgentEvent | ArtifactAgentEvent | RunFinishEvent | ErrorAgentEvent;
103
+ /**
104
+ * The minimal, deliberately-scoped trace floor. Built from the existing
105
+ * `onToolCallOutcome` seam with zero new emission infrastructure:
106
+ * terminal-only (no `running` state), flat (no `parent`/`depth`), ordered by
107
+ * fire order. See `runAgent`'s doc comment.
108
+ */
109
+ export interface TraceStep {
110
+ readonly id: string;
111
+ readonly tool: string;
112
+ readonly outcome: "success" | "error";
113
+ readonly ordinal: number;
114
+ /**
115
+ * The tool call's input (e.g. the `query` tool's `{sql}`), so the UI's
116
+ * work-log can expand a step to show what it ran. Sourced straight from
117
+ * `ToolCallOutcome.input`; `unknown` because tool inputs are arbitrary
118
+ * per-tool shapes.
119
+ */
120
+ readonly input?: unknown;
121
+ /**
122
+ * A compact, bounded human summary of what happened: on `"success"` this
123
+ * is `summarizeToolOutput(outcome.output)`; on `"error"` it's
124
+ * `outcome.error`. Never the full result payload — always short enough to
125
+ * render inline in a collapsed work-log row.
126
+ */
127
+ readonly detail?: string;
128
+ }
129
+ export interface StepTrace {
130
+ readonly steps: readonly TraceStep[];
131
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ /** Thrown when a requested path resolves outside its granted scope (directory traversal). */
2
+ export declare class PathTraversalError extends Error {
3
+ constructor(requestedPath: string, scope: string);
4
+ }
5
+ /** Thrown by `writeFile` when the policy grants no `artifactWriteScope` at all. */
6
+ export declare class WriteScopeNotGrantedError extends Error {
7
+ constructor(requestedPath: string);
8
+ }
9
+ /** Thrown by `exec` when a `mode: "write"` command runs under a read-only policy. */
10
+ export declare class ReadOnlyViolationError extends Error {
11
+ constructor(command: string);
12
+ }
13
+ /** Thrown by `fetch` when the target host isn't in the policy's egress allowlist. */
14
+ export declare class EgressNotAllowedError extends Error {
15
+ constructor(host: string);
16
+ }
@@ -0,0 +1,29 @@
1
+ /** Thrown when a requested path resolves outside its granted scope (directory traversal). */
2
+ export class PathTraversalError extends Error {
3
+ constructor(requestedPath, scope) {
4
+ super(`path "${requestedPath}" escapes the scoped workspace "${scope}"`);
5
+ this.name = "PathTraversalError";
6
+ }
7
+ }
8
+ /** Thrown by `writeFile` when the policy grants no `artifactWriteScope` at all. */
9
+ export class WriteScopeNotGrantedError extends Error {
10
+ constructor(requestedPath) {
11
+ super(`write to "${requestedPath}" rejected: the enforcement policy grants no artifactWriteScope ` +
12
+ `(the agent has no locked scoped_write guardrail)`);
13
+ this.name = "WriteScopeNotGrantedError";
14
+ }
15
+ }
16
+ /** Thrown by `exec` when a `mode: "write"` command runs under a read-only policy. */
17
+ export class ReadOnlyViolationError extends Error {
18
+ constructor(command) {
19
+ super(`command "${command}" declares mode: "write" but the enforcement policy is read-only`);
20
+ this.name = "ReadOnlyViolationError";
21
+ }
22
+ }
23
+ /** Thrown by `fetch` when the target host isn't in the policy's egress allowlist. */
24
+ export class EgressNotAllowedError extends Error {
25
+ constructor(host) {
26
+ super(`fetch to host "${host}" is not in the egress allowlist`);
27
+ this.name = "EgressNotAllowedError";
28
+ }
29
+ }
@@ -0,0 +1,5 @@
1
+ export type { ExecCommand, ExecMode, ExecResult, ExecutionEnv, FetchRequest, FetchResponse } from "./types.js";
2
+ export type { ExecutionPolicy } from "./policy.js";
3
+ export { EgressNotAllowedError, PathTraversalError, ReadOnlyViolationError, WriteScopeNotGrantedError, } from "./errors.js";
4
+ export { createLocalExecutionEnv } from "./local.js";
5
+ export type { FetchImpl, LocalExecutionEnvOptions, RawFetchResponse } from "./local.js";
@@ -0,0 +1,2 @@
1
+ export { EgressNotAllowedError, PathTraversalError, ReadOnlyViolationError, WriteScopeNotGrantedError, } from "./errors.js";
2
+ export { createLocalExecutionEnv } from "./local.js";
@@ -0,0 +1,32 @@
1
+ import type { ExecCommand, ExecResult, ExecutionEnv } from "./types.js";
2
+ /** The minimal shape `local.ts` needs from a fetch response — matches the global `Response`. */
3
+ export interface RawFetchResponse {
4
+ readonly status: number;
5
+ text(): Promise<string>;
6
+ }
7
+ export type FetchImpl = (url: string, init: {
8
+ readonly method: string;
9
+ readonly headers?: Readonly<Record<string, string>>;
10
+ readonly body?: string;
11
+ }) => Promise<RawFetchResponse>;
12
+ export interface LocalExecutionEnvOptions {
13
+ /** Base directory `artifactWriteScope` (and any relative path) resolves against. Defaults to `process.cwd()`. */
14
+ readonly rootDir?: string;
15
+ /** Overridable for hermetic tests; defaults to the global `fetch`. */
16
+ readonly fetchImpl?: FetchImpl;
17
+ /** Overridable for hermetic tests; defaults to spawning `cmd.command` with `cmd.args` (no shell). */
18
+ readonly execImpl?: (cmd: ExecCommand) => Promise<ExecResult>;
19
+ /** Overridable for hermetic tests; defaults to 32 MiB (matches compile/pipeline.ts + route/dispatched.ts). */
20
+ readonly maxBufferBytes?: number;
21
+ }
22
+ /**
23
+ * The v1 `local` `ExecutionEnv` backend: in-process file I/O confined
24
+ * to a scoped workspace, a no-shell subprocess runner gated by
25
+ * `policy.readOnly`, and a basic host allowlist for `fetch`. This is not a
26
+ * sandbox — it adds exactly the checks the guardrail mapping
27
+ * (`harness/guardrails/`) requires so a locked `read_only_execution` or
28
+ * `scoped_write` guardrail has real teeth here, not just prose in a prompt.
29
+ * No microVM/delegated/egress-proxy/credential-broker backend exists yet
30
+ * (out of scope for this milestone).
31
+ */
32
+ export declare function createLocalExecutionEnv(options?: LocalExecutionEnvOptions): ExecutionEnv;
@@ -0,0 +1,134 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdir, readFile as readFileAsync, writeFile as writeFileAsync } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { EgressNotAllowedError, PathTraversalError, ReadOnlyViolationError, WriteScopeNotGrantedError } from "./errors.js";
5
+ const DEFAULT_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
6
+ /**
7
+ * The v1 `local` `ExecutionEnv` backend: in-process file I/O confined
8
+ * to a scoped workspace, a no-shell subprocess runner gated by
9
+ * `policy.readOnly`, and a basic host allowlist for `fetch`. This is not a
10
+ * sandbox — it adds exactly the checks the guardrail mapping
11
+ * (`harness/guardrails/`) requires so a locked `read_only_execution` or
12
+ * `scoped_write` guardrail has real teeth here, not just prose in a prompt.
13
+ * No microVM/delegated/egress-proxy/credential-broker backend exists yet
14
+ * (out of scope for this milestone).
15
+ */
16
+ export function createLocalExecutionEnv(options = {}) {
17
+ const rootDir = options.rootDir ?? process.cwd();
18
+ const fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
19
+ const maxBufferBytes = options.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
20
+ const execImpl = options.execImpl ?? ((cmd) => runSubprocess(cmd, maxBufferBytes));
21
+ return {
22
+ async exec(cmd, policy) {
23
+ if (cmd.mode === "write" && policy.readOnly) {
24
+ throw new ReadOnlyViolationError(cmd.command);
25
+ }
26
+ return execImpl(cmd);
27
+ },
28
+ async readFile(filePath, policy) {
29
+ const resolved = resolveWithinScope(rootDir, policy.artifactWriteScope, filePath);
30
+ return readFileAsync(resolved, "utf-8");
31
+ },
32
+ async writeFile(filePath, data, policy) {
33
+ // read_only_execution and a locked scoped_write guardrail coexist by
34
+ // design (see EnforcementPolicy's doc comment) — writing the output
35
+ // artifact is gated purely on artifactWriteScope, never on readOnly.
36
+ if (policy.artifactWriteScope === undefined) {
37
+ throw new WriteScopeNotGrantedError(filePath);
38
+ }
39
+ const resolved = resolveWithinScope(rootDir, policy.artifactWriteScope, filePath);
40
+ await mkdir(path.dirname(resolved), { recursive: true });
41
+ await writeFileAsync(resolved, data, "utf-8");
42
+ },
43
+ async fetch(req, policy) {
44
+ const host = new URL(req.url).host;
45
+ const allowed = policy.allowedHosts ?? [];
46
+ if (!allowed.includes(host)) {
47
+ throw new EgressNotAllowedError(host);
48
+ }
49
+ const response = await fetchImpl(req.url, {
50
+ method: req.method ?? "GET",
51
+ ...(req.headers ? { headers: req.headers } : {}),
52
+ ...(req.body !== undefined ? { body: req.body } : {}),
53
+ });
54
+ const body = await response.text();
55
+ return { status: response.status, body };
56
+ },
57
+ };
58
+ }
59
+ /**
60
+ * Resolves `requestedPath` against `scope` (itself resolved against
61
+ * `rootDir` when relative) and rejects anything that escapes it — the
62
+ * directory-traversal check `writeFile`/`readFile` both rely on. A
63
+ * `scope` of `undefined` confines reads to `rootDir` directly; `writeFile`
64
+ * rejects that case itself before ever reaching here.
65
+ */
66
+ function resolveWithinScope(rootDir, scope, requestedPath) {
67
+ const scopeAbs = path.resolve(rootDir, scope ?? ".");
68
+ const targetAbs = path.resolve(scopeAbs, requestedPath);
69
+ const relative = path.relative(scopeAbs, targetAbs);
70
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
71
+ throw new PathTraversalError(requestedPath, scopeAbs);
72
+ }
73
+ return targetAbs;
74
+ }
75
+ function runSubprocess(cmd, maxBufferBytes) {
76
+ return new Promise((resolve) => {
77
+ const options = {
78
+ maxBuffer: maxBufferBytes,
79
+ ...(cmd.cwd !== undefined ? { cwd: cmd.cwd } : {}),
80
+ ...(cmd.timeoutMs !== undefined ? { timeout: cmd.timeoutMs } : {}),
81
+ };
82
+ const child = execFile(cmd.command, cmd.args ? [...cmd.args] : [], options, (error, stdout, stderr) => {
83
+ // A spawn failure (the binary isn't on PATH/doesn't exist)
84
+ // reports its error as `error.code === "ENOENT"` — a STRING. The old
85
+ // `typeof error.code === "number"` check treated that as "not a real
86
+ // exit code" and fell through to the generic `error ? 1 : 0`,
87
+ // flattening a missing-binary condition into an indistinguishable
88
+ // `exitCode: 1` with empty `stderr` (the process never ran, so it
89
+ // never wrote anything) — laundering a clear "not found" into what
90
+ // looks like an ordinary command failure.
91
+ if (error && error.code === "ENOENT") {
92
+ resolve({ stdout, stderr, exitCode: 1, notFound: true });
93
+ return;
94
+ }
95
+ // guardrail-enforcement fix: `statementTimeoutSec` is only real
96
+ // enforcement if a hung `wren`/`warble` subprocess actually gets killed
97
+ // and reported distinguishably, rather than left to run forever. Node
98
+ // sets `error.killed === true` for its own `timeout` option; `stderr`
99
+ // is often empty in this case (the process was killed mid-run, not
100
+ // given a chance to explain itself), so a fallback message is supplied.
101
+ if (error && error.killed === true) {
102
+ resolve({
103
+ stdout,
104
+ stderr: stderr.length > 0 ? stderr : `subprocess "${cmd.command}" timed out after ${cmd.timeoutMs}ms`,
105
+ exitCode: 1,
106
+ timedOut: true,
107
+ });
108
+ return;
109
+ }
110
+ // Same rationale as above, for the other silent-killer: an oversized
111
+ // result set previously died on execFile's default 1 MiB `maxBuffer`
112
+ // as an opaque exit-1 with empty stderr, indistinguishable from any
113
+ // other failure.
114
+ if (error && error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
115
+ resolve({
116
+ stdout,
117
+ stderr: stderr.length > 0 ? stderr : `subprocess "${cmd.command}" exceeded the ${maxBufferBytes}-byte output buffer`,
118
+ exitCode: 1,
119
+ maxBufferExceeded: true,
120
+ });
121
+ return;
122
+ }
123
+ const exitCode = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
124
+ resolve({ stdout, stderr, exitCode });
125
+ });
126
+ // ExecCommand has no stdin/input field: every command routed through this
127
+ // backend is deliberately non-interactive. Leaving execFile's writable
128
+ // stdin pipe open falsely promises that input may arrive later, so a bare
129
+ // interpreter or any other stdin reader waits forever. End it immediately
130
+ // to deliver EOF; if a future caller needs input, that must become an
131
+ // explicit ExecCommand contract which writes the payload and then ends.
132
+ child.stdin?.end();
133
+ });
134
+ }
@@ -0,0 +1,12 @@
1
+ import type { EnforcementPolicy } from "../guardrails/index.js";
2
+ /**
3
+ * The policy every `ExecutionEnv` method receives: the guardrail-derived
4
+ * `EnforcementPolicy` (`harness/guardrails/`) plus an egress allowlist. The
5
+ * allowlist has no bundle representation in v1 — the golden bundle never
6
+ * declares a host/dataset egress guardrail — so callers supply it directly
7
+ * (see `RunAgentContext.allowedHosts` in `harness/session/run.ts`). Omitting it
8
+ * means "no hosts allowed", the safe default.
9
+ */
10
+ export interface ExecutionPolicy extends EnforcementPolicy {
11
+ readonly allowedHosts?: readonly string[];
12
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ import type { ExecutionPolicy } from "./policy.js";
2
+ /**
3
+ * A command's declared read/write intent. The harness does not parse or
4
+ * sniff shell commands to infer this — the caller (a native tool) states it
5
+ * up front, and a read-only policy rejects `"write"` commands outright.
6
+ */
7
+ export type ExecMode = "read" | "write";
8
+ export interface ExecCommand {
9
+ readonly mode: ExecMode;
10
+ readonly command: string;
11
+ readonly args?: readonly string[];
12
+ /** Working directory the command runs in. Defaults to the process's cwd when omitted. */
13
+ readonly cwd?: string;
14
+ /** Maximum time (ms) the subprocess may run before being killed. Omitted means no timeout. */
15
+ readonly timeoutMs?: number;
16
+ }
17
+ export interface ExecResult {
18
+ readonly stdout: string;
19
+ readonly stderr: string;
20
+ readonly exitCode: number;
21
+ /**
22
+ * Set (`true`) when `cmd.command` itself could not be launched — a spawn
23
+ * `ENOENT` (the binary isn't on `PATH`/doesn't exist), as opposed to the
24
+ * command running and exiting non-zero on its own. Node's spawn error
25
+ * carries this as the *string* `"ENOENT"` on `error.code`, not a number,
26
+ * so callers must check `notFound` rather than inferring it from
27
+ * `exitCode` — `exitCode` is set to `1` in this case only as a
28
+ * conservative "not success" fallback for code that ignores `notFound`,
29
+ * never as the real signal. Omitted (not just `false`) on an ordinary
30
+ * exit so existing structural equality checks on `ExecResult` are
31
+ * unaffected. See `createLocalExecutionEnv`'s `runSubprocess`.
32
+ */
33
+ readonly notFound?: boolean;
34
+ /** Set (`true`) when the subprocess was killed because it exceeded `ExecCommand.timeoutMs`. Node's `execFile` sets `error.killed === true` for its own `timeout` option (and NOT for maxBuffer-exceeded — verified: `error.code` is `null` for timeout, not a number, not `"ENOENT"`). Omitted (not `false`) on an ordinary exit, same convention as `notFound`. */
35
+ readonly timedOut?: boolean;
36
+ /** Set (`true`) when the subprocess's combined stdout/stderr exceeded the exec backend's `maxBuffer` and was killed. Node reports this as `error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"` (verified on Node 23), distinct from both `notFound` and `timedOut`. Omitted (not `false`) on an ordinary exit. */
37
+ readonly maxBufferExceeded?: boolean;
38
+ }
39
+ export interface FetchRequest {
40
+ readonly url: string;
41
+ readonly method?: string;
42
+ readonly headers?: Readonly<Record<string, string>>;
43
+ readonly body?: string;
44
+ }
45
+ export interface FetchResponse {
46
+ readonly status: number;
47
+ readonly body: string;
48
+ }
49
+ /**
50
+ * The pluggable seam side-effecting native tools execute through instead of
51
+ * touching the filesystem/network/subprocess directly — mirroring the
52
+ * "injectable, defaulted" shape of `ProviderRegistry` and
53
+ * `NativeToolRegistry`. v1 ships exactly one backend
54
+ * ({@link createLocalExecutionEnv} in `./local.js`); no microVM / delegated /
55
+ * egress-proxy / credential-broker backend exists yet (out of scope for
56
+ * this milestone — tracked separately as hosted-runtime hardening).
57
+ */
58
+ export interface ExecutionEnv {
59
+ exec(cmd: ExecCommand, policy: ExecutionPolicy): Promise<ExecResult>;
60
+ readFile(path: string, policy: ExecutionPolicy): Promise<string>;
61
+ writeFile(path: string, data: string, policy: ExecutionPolicy): Promise<void>;
62
+ fetch(req: FetchRequest, policy: ExecutionPolicy): Promise<FetchResponse>;
63
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export { deriveEnforcement } from "./policy.js";
2
+ export type { EnforcementPolicy } from "./policy.js";
@@ -0,0 +1 @@
1
+ export { deriveEnforcement } from "./policy.js";
@@ -0,0 +1,45 @@
1
+ import type { Agent } from "../bundle/schema.js";
2
+ /**
3
+ * The enforcement policy derived from an agent's `guardrails` map: a
4
+ * machine-readable summary an `ExecutionEnv`
5
+ * (`harness/exec/`) can act on directly. Deliberately excludes `gated_check`
6
+ * (`deterministic_gate` / `additivity_guard`) — `harness/session/gate.ts`
7
+ * already owns that guardrail's enforcement against the rendered envelope,
8
+ * and duplicating it here would just be two places to keep in sync.
9
+ *
10
+ * For `mcp:<server>/<name>` tools (e.g. `query`), the harness never executes
11
+ * the tool in-process — the MCP server does — so `rowLimit` /
12
+ * `statementTimeoutSec` (and `readOnly` for SQL access) are *not* enforced
13
+ * by this policy directly. They must be passed to the MCP server via its
14
+ * connection config (`McpServerConfig`, `harness/tools/mcp.ts`) or honored by
15
+ * the server's own semantics (e.g. a read-only DB role, a statement
16
+ * timeout on the connection). This module only derives the values; wiring
17
+ * them into a real server's connection is a caller/deployment concern this
18
+ * harness does not — and should not pretend to — enforce on the server's
19
+ * behalf.
20
+ */
21
+ export interface EnforcementPolicy {
22
+ readonly readOnly: boolean;
23
+ readonly artifactWriteScope?: string;
24
+ readonly rowLimit?: number;
25
+ readonly statementTimeoutSec?: number;
26
+ readonly drillDepthLimit?: number;
27
+ }
28
+ /**
29
+ * Maps an agent's `guardrails` to the harness's enforcement seams: a locked
30
+ * `read_only` guardrail sets `readOnly`; a locked `scoped_write` guardrail
31
+ * (with a `scope`) sets `artifactWriteScope`; `threshold_limit` / `generic`
32
+ * guardrails are matched by guardrail *key* (not just `enforcement`, since
33
+ * `row_limit` and `drill_depth_limit` share the `threshold_limit`
34
+ * enforcement kind) onto named numeric limits.
35
+ *
36
+ * `read_only_execution` and a locked `scoped_write` guardrail coexisting
37
+ * (`generate_dashboard`, `explain_change` in the golden bundle) is not a
38
+ * contradiction: the former restricts *data access* (no write-capable
39
+ * SQL/tool becomes active), the latter grants a narrow, separate permission
40
+ * to write the rendered *output artifact* inside `scope`. This is exactly
41
+ * why the two fields are independent here rather than one overriding the
42
+ * other — `LocalExecutionEnv.writeFile` (`harness/exec/local.ts`) reflects this
43
+ * directly, consulting only `artifactWriteScope`, never `readOnly`.
44
+ */
45
+ export declare function deriveEnforcement(agent: Agent): EnforcementPolicy;