@wrenai/genbi 0.0.4 → 0.0.5

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 (230) hide show
  1. package/README.md +64 -2
  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 +69 -30
  93. package/dist-server/harness/route/codex-host-query.d.ts +52 -0
  94. package/dist-server/harness/route/codex-host-query.js +191 -0
  95. package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
  96. package/dist-server/harness/route/codex-local-manifest.js +96 -15
  97. package/dist-server/harness/route/codex-wren-proxy.d.ts +33 -0
  98. package/dist-server/harness/route/codex-wren-proxy.js +227 -0
  99. package/dist-server/harness/route/describe.js +45 -6
  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 +5 -2
  103. package/dist-server/harness/route/types.d.ts +30 -1
  104. package/dist-server/harness/session/run.js +30 -10
  105. package/dist-server/harness/setup/runner.d.ts +2 -0
  106. package/dist-server/harness/setup/runner.js +38 -17
  107. package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
  108. package/dist-server/harness/setup/yaml-scalar.js +62 -0
  109. package/dist-server/server/adopt.js +1 -17
  110. package/dist-server/server/app.d.ts +1 -0
  111. package/dist-server/server/app.js +163 -14
  112. package/dist-server/server/auth-choice.d.ts +13 -1
  113. package/dist-server/server/auth-choice.js +66 -1
  114. package/dist-server/server/bin.js +177 -25
  115. package/dist-server/server/codex-local-grounding.d.ts +18 -0
  116. package/dist-server/server/codex-local-grounding.js +92 -0
  117. package/dist-server/server/codex-native-session.d.ts +25 -0
  118. package/dist-server/server/codex-native-session.js +1 -0
  119. package/dist-server/server/codex-runtime-composition.d.ts +34 -0
  120. package/dist-server/server/codex-runtime-composition.js +250 -0
  121. package/dist-server/server/codex-structured-ask.d.ts +23 -0
  122. package/dist-server/server/codex-structured-ask.js +102 -0
  123. package/dist-server/server/conn-config.js +4 -16
  124. package/dist-server/server/conversation-socket.d.ts +16 -0
  125. package/dist-server/server/conversation-socket.js +65 -0
  126. package/dist-server/server/db.d.ts +46 -0
  127. package/dist-server/server/db.js +88 -9
  128. package/dist-server/server/env-detect.d.ts +6 -0
  129. package/dist-server/server/env-detect.js +7 -0
  130. package/dist-server/server/fold.d.ts +22 -4
  131. package/dist-server/server/fold.js +37 -5
  132. package/dist-server/server/harness.d.ts +2 -2
  133. package/dist-server/server/harness.js +33 -7
  134. package/dist-server/server/interactive-terminal.d.ts +10 -6
  135. package/dist-server/server/interactive-terminal.js +33 -8
  136. package/dist-server/server/managed-wren-runtime.d.ts +116 -0
  137. package/dist-server/server/managed-wren-runtime.js +670 -0
  138. package/dist-server/server/native-artifacts.d.ts +23 -7
  139. package/dist-server/server/native-artifacts.js +43 -12
  140. package/dist-server/server/native-component-context.d.ts +19 -0
  141. package/dist-server/server/native-component-context.js +76 -0
  142. package/dist-server/server/native-component-preparation.d.ts +20 -0
  143. package/dist-server/server/native-component-preparation.js +129 -0
  144. package/dist-server/server/native-component-runtime.d.ts +36 -0
  145. package/dist-server/server/native-component-runtime.js +92 -0
  146. package/dist-server/server/native-components.d.ts +84 -0
  147. package/dist-server/server/native-components.js +160 -0
  148. package/dist-server/server/native-dispatch-process.d.ts +8 -0
  149. package/dist-server/server/native-dispatch-process.js +65 -0
  150. package/dist-server/server/native-runtime-spec.d.ts +89 -0
  151. package/dist-server/server/native-runtime-spec.js +169 -0
  152. package/dist-server/server/native-sessions.d.ts +172 -19
  153. package/dist-server/server/native-sessions.js +839 -244
  154. package/dist-server/server/native-shutdown.d.ts +6 -0
  155. package/dist-server/server/native-shutdown.js +28 -0
  156. package/dist-server/server/native-wren-home.d.ts +18 -0
  157. package/dist-server/server/native-wren-home.js +262 -0
  158. package/dist-server/server/node-pty-host.d.ts +2 -1
  159. package/dist-server/server/node-pty-host.js +3 -2
  160. package/dist-server/server/profile-registry.d.ts +139 -0
  161. package/dist-server/server/profile-registry.js +620 -0
  162. package/dist-server/server/runtime-binding.d.ts +4 -1
  163. package/dist-server/server/runtime-binding.js +50 -2
  164. package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
  165. package/dist-server/server/runtime-host/claude-component-step.js +186 -0
  166. package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
  167. package/dist-server/server/runtime-host/codex-app-server.js +239 -0
  168. package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
  169. package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
  170. package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
  171. package/dist-server/server/runtime-host/codex-component-step.js +308 -0
  172. package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
  173. package/dist-server/server/runtime-host/codex-conversation.js +295 -0
  174. package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
  175. package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
  176. package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
  177. package/dist-server/server/runtime-host/codex-events.js +66 -0
  178. package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
  179. package/dist-server/server/runtime-host/codex-policy.js +151 -0
  180. package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
  181. package/dist-server/server/runtime-host/codex-process.js +96 -0
  182. package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
  183. package/dist-server/server/runtime-host/codex-rpc.js +182 -0
  184. package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
  185. package/dist-server/server/runtime-host/codex-session.js +492 -0
  186. package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
  187. package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
  188. package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
  189. package/dist-server/server/runtime-host/codex-terminal.js +120 -0
  190. package/dist-server/server/runtime-host/index.d.ts +5 -0
  191. package/dist-server/server/runtime-host/index.js +3 -0
  192. package/dist-server/server/runtime-host/local.d.ts +29 -0
  193. package/dist-server/server/runtime-host/local.js +90 -0
  194. package/dist-server/server/runtime-host/policy.d.ts +8 -0
  195. package/dist-server/server/runtime-host/policy.js +78 -0
  196. package/dist-server/server/runtime-host/types.d.ts +52 -0
  197. package/dist-server/server/runtime-host/types.js +62 -0
  198. package/dist-server/server/structured-query-evidence.d.ts +7 -0
  199. package/dist-server/server/structured-query-evidence.js +47 -0
  200. package/dist-server/server/structured-runtime.d.ts +52 -0
  201. package/dist-server/server/structured-runtime.js +130 -0
  202. package/dist-server/server/structured-scope.d.ts +4 -0
  203. package/dist-server/server/structured-scope.js +12 -0
  204. package/dist-server/server/turn.d.ts +6 -1
  205. package/dist-server/server/turn.js +82 -25
  206. package/dist-server/server/wire-types.d.ts +67 -3
  207. package/dist-server/server/wren-profiles.js +1 -1
  208. package/managed-wren/README.md +60 -0
  209. package/managed-wren/dependency-wheels.json +282 -0
  210. package/managed-wren/manifest.json +373 -0
  211. package/managed-wren/release-inputs.json +45 -0
  212. package/managed-wren/runtime-tree.cjs +115 -0
  213. package/managed-wren/source-inputs.json +40 -0
  214. package/package.json +15 -6
  215. package/profiles/genbi-default/context/binding.yml +17 -7
  216. package/profiles/genbi-default/context/context.json +146 -0
  217. package/profiles/genbi-default/ir.golden.json +39 -43
  218. package/profiles/genbi-default/profile.yml +31 -0
  219. package/profiles/genbi-enrich-context/context/binding.yml +14 -3
  220. package/profiles/genbi-enrich-context/context/context.json +146 -0
  221. package/profiles/genbi-enrich-context/ir.golden.json +4 -5
  222. package/profiles/genbi-monitor/context/binding.yml +13 -5
  223. package/profiles/genbi-monitor/context/context.json +146 -0
  224. package/profiles/genbi-monitor/ir.golden.json +13 -7
  225. package/profiles/genbi-report/context/binding.yml +19 -0
  226. package/profiles/genbi-report/context/context.json +223 -0
  227. package/profiles/genbi-report/ir.golden.json +328 -0
  228. package/profiles/genbi-report/profile.yml +38 -0
  229. package/profiles/genbi-setup/ir.golden.json +3 -1
  230. package/dist/assets/index-Xs4q7pNo.css +0 -1
@@ -153,10 +153,21 @@ CREATE TABLE IF NOT EXISTS native_structured_answers (
153
153
  envelope_json TEXT NOT NULL,
154
154
  digest TEXT NOT NULL,
155
155
  created_at TEXT NOT NULL,
156
+ grounded INTEGER NOT NULL DEFAULT 0,
156
157
  UNIQUE(native_session_id, idempotency_key)
157
158
  );
158
159
  CREATE INDEX IF NOT EXISTS native_structured_answers_session_created_at
159
160
  ON native_structured_answers(native_session_id, created_at DESC);
161
+ -- Warble profile registry: the shipped profiles plus any the operator added. A row's admission
162
+ -- verdict is recomputed when the profile bytes or the warble binary change; rows are never
163
+ -- deleted by admission, only marked unavailable.
164
+ CREATE TABLE IF NOT EXISTS warble_profiles (
165
+ id TEXT PRIMARY KEY, kind TEXT NOT NULL, role TEXT NOT NULL, source_dir TEXT NOT NULL,
166
+ profile_hash TEXT NOT NULL, warble_identity TEXT NOT NULL,
167
+ admission_status TEXT NOT NULL, admission_reason TEXT, entry_kind TEXT, entry_verb TEXT,
168
+ ir_version TEXT, components_json TEXT NOT NULL DEFAULT '[]', rules_version INTEGER NOT NULL DEFAULT 0,
169
+ created_at TEXT NOT NULL, updated_at TEXT NOT NULL, admitted_at TEXT
170
+ );
160
171
  `;
161
172
  function boolToInt(value) {
162
173
  return value ? 1 : 0;
@@ -245,6 +256,7 @@ function coerceOrphanedTodoSteps(steps) {
245
256
  }
246
257
  export class Store {
247
258
  db;
259
+ closed = false;
248
260
  onContextSuccessWrite;
249
261
  onEnrichmentTransitionWrite;
250
262
  onEnrichmentCreationWrite;
@@ -362,6 +374,9 @@ export class Store {
362
374
  this.addColumnIfMissing("artifacts", "native_agent", "TEXT");
363
375
  this.addColumnIfMissing("artifacts", "content_digest", "TEXT");
364
376
  this.addColumnIfMissing("artifacts", "idempotency_key", "TEXT");
377
+ // Pre-existing retained answers carry no provenance, so they default to
378
+ // ungrounded: a later save by reference to one of them is Unverified.
379
+ this.addColumnIfMissing("native_structured_answers", "grounded", "INTEGER NOT NULL DEFAULT 0");
365
380
  this.db.exec("CREATE UNIQUE INDEX IF NOT EXISTS artifacts_native_idempotency_unique ON artifacts(native_session_id, idempotency_key) WHERE native_session_id IS NOT NULL AND idempotency_key IS NOT NULL");
366
381
  this.addColumnIfMissing("enrichment_operations", "draft", "TEXT NOT NULL DEFAULT ''");
367
382
  this.addColumnIfMissing("enrichment_operations", "change_kind", "TEXT NOT NULL DEFAULT 'knowledge_append'");
@@ -383,10 +398,14 @@ export class Store {
383
398
  this.addColumnIfMissing("enrichment_approvals", "evidence_ref", "TEXT NOT NULL DEFAULT ''");
384
399
  this.addColumnIfMissing("enrichment_approvals", "nonce", "TEXT NOT NULL DEFAULT ''");
385
400
  this.addColumnIfMissing("enrichment_approvals", "expires_at", "TEXT NOT NULL DEFAULT ''");
401
+ this.addColumnIfMissing("native_sessions", "entry_verb", "TEXT");
402
+ this.addColumnIfMissing("native_sessions", "transport", "TEXT");
386
403
  this.addColumnIfMissing("native_sessions", "dispatch_profile", "TEXT");
387
404
  this.addColumnIfMissing("native_sessions", "dispatch_target", "TEXT");
388
405
  this.addColumnIfMissing("native_sessions", "runtime_generation", "INTEGER");
389
406
  this.addColumnIfMissing("artifacts", "source_answer_id", "TEXT");
407
+ // A database written before admission carried a rule-set version reads 0 and re-admits.
408
+ this.addColumnIfMissing("warble_profiles", "rules_version", "INTEGER NOT NULL DEFAULT 0");
390
409
  this.db.exec("CREATE UNIQUE INDEX IF NOT EXISTS enrichment_approvals_nonce_unique ON enrichment_approvals(nonce) WHERE nonce <> ''");
391
410
  }
392
411
  addColumnIfMissing(table, column, type) {
@@ -398,7 +417,10 @@ export class Store {
398
417
  }
399
418
  close() {
400
419
  this.db.close();
420
+ this.closed = true;
401
421
  }
422
+ /** Lets delayed process-local cleanup avoid touching SQLite after shutdown. */
423
+ isClosed() { return this.closed; }
402
424
  // ---------------------------------------------------------------------
403
425
  // sessions
404
426
  // ---------------------------------------------------------------------
@@ -415,8 +437,8 @@ export class Store {
415
437
  // ---------------------------------------------------------------------
416
438
  createNativeSession(params) {
417
439
  const now = this.now().toISOString();
418
- this.db.prepare(`INSERT INTO native_sessions (id, purpose, vendor, agent, scope_kind, scope_id, project_identity, binding_generation, project_revision, dispatch_profile, dispatch_target, runtime_generation, status, created_at, updated_at, started_at, ended_at, exit_code, failure)
419
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'creating', ?, ?, NULL, NULL, NULL, NULL)`).run(params.id, params.purpose, params.vendor, params.agent, params.scopeKind, params.scopeId, params.projectIdentity ?? null, params.bindingGeneration ?? null, params.projectRevision ?? null, params.dispatchProfile ?? null, params.dispatchTarget ?? null, params.runtimeGeneration ?? null, now, now);
440
+ this.db.prepare(`INSERT INTO native_sessions (id, purpose, vendor, agent, entry_verb, scope_kind, scope_id, project_identity, binding_generation, project_revision, dispatch_profile, dispatch_target, runtime_generation, status, created_at, updated_at, started_at, ended_at, exit_code, failure, transport)
441
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'creating', ?, ?, NULL, NULL, NULL, NULL, ?)`).run(params.id, params.purpose, params.vendor, params.agent, params.entryVerb ?? null, params.scopeKind, params.scopeId, params.projectIdentity ?? null, params.bindingGeneration ?? null, params.projectRevision ?? null, params.dispatchProfile ?? null, params.dispatchTarget ?? null, params.runtimeGeneration ?? null, now, now, params.transport ?? null);
420
442
  return this.getNativeSession(params.id);
421
443
  }
422
444
  /** Stores a sealed provider handle only; callers must never pass plaintext. */
@@ -478,8 +500,8 @@ export class Store {
478
500
  }
479
501
  const now = this.now().toISOString();
480
502
  const child = params.child;
481
- this.db.prepare(`INSERT INTO native_sessions (id, purpose, vendor, agent, scope_kind, scope_id, project_identity, binding_generation, project_revision, dispatch_profile, dispatch_target, runtime_generation, status, created_at, updated_at, started_at, ended_at, exit_code, failure)
482
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'creating', ?, ?, NULL, NULL, NULL, NULL)`).run(child.id, child.purpose, child.vendor, child.agent, child.scopeKind, child.scopeId, child.projectIdentity ?? null, child.bindingGeneration ?? null, child.projectRevision ?? null, child.dispatchProfile ?? null, child.dispatchTarget ?? null, child.runtimeGeneration ?? null, now, now);
503
+ this.db.prepare(`INSERT INTO native_sessions (id, purpose, vendor, agent, entry_verb, scope_kind, scope_id, project_identity, binding_generation, project_revision, dispatch_profile, dispatch_target, runtime_generation, status, created_at, updated_at, started_at, ended_at, exit_code, failure)
504
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'creating', ?, ?, NULL, NULL, NULL, NULL)`).run(child.id, child.purpose, child.vendor, child.agent, child.entryVerb ?? null, child.scopeKind, child.scopeId, child.projectIdentity ?? null, child.bindingGeneration ?? null, child.projectRevision ?? null, child.dispatchProfile ?? null, child.dispatchTarget ?? null, child.runtimeGeneration ?? null, now, now);
483
505
  this.db.prepare(`INSERT INTO native_session_resume_actions (session_id, idempotency_key, scope_fingerprint, resumed_session_id, created_at) VALUES (?, ?, ?, ?, ?)`).run(params.sourceSessionId, params.idempotencyKey, params.scopeFingerprint, child.id, now);
484
506
  this.db.prepare(`UPDATE native_session_resume_handles SET consumed_at = ? WHERE session_id = ? AND consumed_at IS NULL`).run(now, params.sourceSessionId);
485
507
  this.saveNativeSessionResumeHandle(child.id, child.vendor, params.sealedHandle);
@@ -503,8 +525,8 @@ export class Store {
503
525
  const createdAt = this.now().toISOString();
504
526
  this.db.exec("BEGIN IMMEDIATE");
505
527
  try {
506
- this.db.prepare(`INSERT INTO native_structured_answers (id, native_session_id, idempotency_key, envelope_json, digest, created_at)
507
- VALUES (?, ?, ?, ?, ?, ?)`).run(params.id, params.nativeSessionId, params.idempotencyKey, params.envelopeJson, params.digest, createdAt);
528
+ this.db.prepare(`INSERT INTO native_structured_answers (id, native_session_id, idempotency_key, envelope_json, digest, created_at, grounded)
529
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(params.id, params.nativeSessionId, params.idempotencyKey, params.envelopeJson, params.digest, createdAt, boolToInt(params.grounded));
508
530
  this.db.prepare(`DELETE FROM native_structured_answers
509
531
  WHERE id IN (
510
532
  SELECT id FROM native_structured_answers
@@ -539,6 +561,41 @@ export class Store {
539
561
  LIMIT 1`).get(nativeSessionId);
540
562
  return row ? rowToNativeStructuredAnswer(row) : undefined;
541
563
  }
564
+ // ---------------------------------------------------------------------
565
+ // warble profile registry
566
+ // ---------------------------------------------------------------------
567
+ upsertWarbleProfile(row) {
568
+ this.db.prepare(`INSERT INTO warble_profiles (id, kind, role, source_dir, profile_hash, warble_identity, admission_status, admission_reason,
569
+ entry_kind, entry_verb, ir_version, components_json, rules_version, created_at, updated_at, admitted_at)
570
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
571
+ ON CONFLICT(id) DO UPDATE SET
572
+ kind = excluded.kind, role = excluded.role, source_dir = excluded.source_dir,
573
+ profile_hash = excluded.profile_hash, warble_identity = excluded.warble_identity,
574
+ admission_status = excluded.admission_status, admission_reason = excluded.admission_reason,
575
+ entry_kind = excluded.entry_kind, entry_verb = excluded.entry_verb, ir_version = excluded.ir_version,
576
+ components_json = excluded.components_json, rules_version = excluded.rules_version,
577
+ updated_at = excluded.updated_at, admitted_at = excluded.admitted_at`).run(row.id, row.kind, row.role, row.sourceDir, row.profileHash, row.warbleIdentity, row.admissionStatus, row.admissionReason, row.entryKind, row.entryVerb, row.irVersion, row.componentsJson, row.rulesVersion, row.createdAt, row.updatedAt, row.admittedAt);
578
+ return this.getWarbleProfile(row.id);
579
+ }
580
+ getWarbleProfile(id) {
581
+ const row = this.db.prepare(`SELECT * FROM warble_profiles WHERE id = ?`).get(id);
582
+ return row ? rowToWarbleProfile(row) : undefined;
583
+ }
584
+ /** Built-ins first (in registration order), then user profiles by id. */
585
+ listWarbleProfiles() {
586
+ return this.db
587
+ .prepare(`SELECT * FROM warble_profiles ORDER BY CASE kind WHEN 'builtin' THEN 0 ELSE 1 END, rowid ASC`)
588
+ .all()
589
+ .map(rowToWarbleProfile);
590
+ }
591
+ deleteWarbleProfile(id) {
592
+ return this.db.prepare(`DELETE FROM warble_profiles WHERE id = ?`).run(id).changes > 0;
593
+ }
594
+ /** How many native sessions ever ran inside this profile; the registry refuses to remove a referenced one. */
595
+ countNativeSessionsByDispatchProfile(profileId) {
596
+ const row = this.db.prepare(`SELECT COUNT(*) AS n FROM native_sessions WHERE dispatch_profile = ?`).get(profileId);
597
+ return row ? num(row, "n") : 0;
598
+ }
542
599
  listNativeSessions() {
543
600
  return this.db.prepare(`SELECT * FROM native_sessions ORDER BY updated_at DESC, rowid DESC`).all().map(rowToNativeSession);
544
601
  }
@@ -853,7 +910,7 @@ export class Store {
853
910
  const createdAt = new Date().toISOString();
854
911
  const row = {
855
912
  id: params.id, sessionId: params.sessionId, name: params.name, kind: "dashboard", location: params.location,
856
- verified: true, createdAt, savedAt: createdAt, nativeSessionId: params.nativeSessionId,
913
+ verified: params.verified, createdAt, savedAt: createdAt, nativeSessionId: params.nativeSessionId,
857
914
  projectIdentity: params.projectIdentity, bindingGeneration: params.bindingGeneration,
858
915
  projectRevision: params.projectRevision, nativeVendor: params.vendor, nativeAgent: params.agent,
859
916
  contentDigest: params.digest, idempotencyKey: params.idempotencyKey,
@@ -865,7 +922,7 @@ export class Store {
865
922
  native_session_id, project_identity, binding_generation, project_revision,
866
923
  native_vendor, native_agent, content_digest, idempotency_key, source_answer_id
867
924
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
868
- .run(row.id, row.sessionId, row.name, row.kind, row.location, 1, row.createdAt, row.savedAt, row.nativeSessionId, row.projectIdentity, row.bindingGeneration, row.projectRevision, row.nativeVendor, row.nativeAgent, row.contentDigest, row.idempotencyKey, row.sourceAnswerId);
925
+ .run(row.id, row.sessionId, row.name, row.kind, row.location, boolToInt(row.verified), row.createdAt, row.savedAt, row.nativeSessionId, row.projectIdentity, row.bindingGeneration, row.projectRevision, row.nativeVendor, row.nativeAgent, row.contentDigest, row.idempotencyKey, row.sourceAnswerId);
869
926
  return { row, created: true };
870
927
  }
871
928
  catch (error) {
@@ -1724,8 +1781,9 @@ function rowToSession(row) {
1724
1781
  }
1725
1782
  function rowToNativeSession(row) {
1726
1783
  return {
1784
+ ...(row["transport"] === "conversation" ? { transport: "conversation" } : {}),
1727
1785
  id: str(row, "id"), purpose: str(row, "purpose"),
1728
- vendor: str(row, "vendor"), agent: str(row, "agent"),
1786
+ vendor: str(row, "vendor"), agent: str(row, "agent"), entryVerb: strOrNull(row, "entry_verb"),
1729
1787
  scopeKind: str(row, "scope_kind"), scopeId: str(row, "scope_id"),
1730
1788
  projectIdentity: strOrNull(row, "project_identity"),
1731
1789
  bindingGeneration: row["binding_generation"] === null ? null : num(row, "binding_generation"),
@@ -1739,6 +1797,26 @@ function rowToNativeSession(row) {
1739
1797
  exitCode: row["exit_code"] === null ? null : num(row, "exit_code"), failure: strOrNull(row, "failure"),
1740
1798
  };
1741
1799
  }
1800
+ function rowToWarbleProfile(row) {
1801
+ return {
1802
+ id: str(row, "id"),
1803
+ kind: str(row, "kind"),
1804
+ role: str(row, "role"),
1805
+ sourceDir: str(row, "source_dir"),
1806
+ profileHash: str(row, "profile_hash"),
1807
+ warbleIdentity: str(row, "warble_identity"),
1808
+ admissionStatus: str(row, "admission_status"),
1809
+ admissionReason: strOrNull(row, "admission_reason"),
1810
+ entryKind: strOrNull(row, "entry_kind"),
1811
+ entryVerb: strOrNull(row, "entry_verb"),
1812
+ irVersion: strOrNull(row, "ir_version"),
1813
+ componentsJson: str(row, "components_json"),
1814
+ rulesVersion: row["rules_version"] === null || row["rules_version"] === undefined ? 0 : num(row, "rules_version"),
1815
+ createdAt: str(row, "created_at"),
1816
+ updatedAt: str(row, "updated_at"),
1817
+ admittedAt: strOrNull(row, "admitted_at"),
1818
+ };
1819
+ }
1742
1820
  function rowToNativeSetupRecovery(row) {
1743
1821
  return {
1744
1822
  sessionId: str(row, "session_id"),
@@ -1761,6 +1839,7 @@ function rowToNativeStructuredAnswer(row) {
1761
1839
  envelopeJson: str(row, "envelope_json"),
1762
1840
  digest: str(row, "digest"),
1763
1841
  createdAt: str(row, "created_at"),
1842
+ grounded: intToBool(row["grounded"]),
1764
1843
  };
1765
1844
  }
1766
1845
  function rowToEvent(row) {
@@ -1,4 +1,10 @@
1
1
  import type { AdapterEnvStatus } from "./wire-types.js";
2
+ /**
3
+ * The one credential env var for `"gateway"` mode, whatever pi-ai provider it
4
+ * targets. The BFF reads it and passes it to the adapter per request; pi-ai's
5
+ * own provider env vars are never consulted.
6
+ */
7
+ export declare const GATEWAY_API_KEY_ENV = "GENBI_GATEWAY_API_KEY";
2
8
  /**
3
9
  * Reports which api-key adapters have their required credential env var present on the BFF
4
10
  * process — booleans only. The key values themselves (or any prefix/suffix/length derived
@@ -1,3 +1,9 @@
1
+ /**
2
+ * The one credential env var for `"gateway"` mode, whatever pi-ai provider it
3
+ * targets. The BFF reads it and passes it to the adapter per request; pi-ai's
4
+ * own provider env vars are never consulted.
5
+ */
6
+ export const GATEWAY_API_KEY_ENV = "GENBI_GATEWAY_API_KEY";
1
7
  /** True iff the named env var is set to a non-empty (non-whitespace-only) value. */
2
8
  function isNonEmptyEnv(name) {
3
9
  const value = process.env[name];
@@ -13,5 +19,6 @@ export function detectAdapterEnv() {
13
19
  return {
14
20
  anthropic: isNonEmptyEnv("ANTHROPIC_API_KEY"),
15
21
  openaiCompatible: isNonEmptyEnv("OPENAI_API_KEY"),
22
+ gateway: isNonEmptyEnv(GATEWAY_API_KEY_ENV),
16
23
  };
17
24
  }
@@ -14,7 +14,7 @@
14
14
  * forwards whatever the runtime actually reports.
15
15
  */
16
16
  import type { AgentEvent, RouteResult, StepTrace } from "../harness/index.js";
17
- import type { AnswerEvent, ArtifactEvent, ArtifactKind, RefusalEvent, ToolStep } from "./wire-types.js";
17
+ import type { AnswerEvent, ArtifactEvent, ArtifactKind, RefusalEvent, RenderEnvelope, ToolStep } from "./wire-types.js";
18
18
  /**
19
19
  * Canned remediation text for a refusal. `RefusalAgentEvent`/`RefusalResult`
20
20
  * carry no remediation hint from the agent runtime — the UI's `fix` field is
@@ -37,9 +37,11 @@ export declare function extractTrace(result: RouteResult): StepTrace | undefined
37
37
  * on the floor and the raw text always went out as `form: "text"`,
38
38
  * `verified: false`). `extractEnvelopeFromText` recovers it — including
39
39
  * `answer_query`'s flat `{columns, rows}` tool-contract shape, normalized
40
- * into a `table` block — and `verified` is whatever the recovered envelope
41
- * actually carries, never hardcoded. Only when no envelope can be recovered
42
- * at all does the raw text fall back to `form: "text"`.
40
+ * into a `table` block. The recovered envelope's `verified` is model-written,
41
+ * so {@link withHostVerification} decides it from host evidence instead (see
42
+ * there). Only when
43
+ * no envelope can be recovered at all does the raw text fall back to
44
+ * `form: "text"`.
43
45
  *
44
46
  * That `form: "text"` fallback is the one case where "verified: false" is
45
47
  * ambiguous between two very different situations: a pure conversational
@@ -51,6 +53,22 @@ export declare function extractTrace(result: RouteResult): StepTrace | undefined
51
53
  * introspection and artifact-saving tool calls don't count as a data claim.
52
54
  */
53
55
  export declare function toAnswerOrRefusalEvent(id: string, result: RouteResult, worklog?: readonly ToolStep[]): AnswerEvent | RefusalEvent;
56
+ /**
57
+ * The one place a subscription answer's `verified` is decided; a `verified`
58
+ * field in model-written text is never kept.
59
+ *
60
+ * - `codex-local`: the model's `run_sql` executes through this turn's host
61
+ * query service, so the answer is grounded against that turn's observations
62
+ * ({@link groundCodexLocalEnvelope}): verified only when every query it
63
+ * cites is one the host ran this turn and that read a table, and then its
64
+ * tables and definitions are the observed ones. Otherwise it stays as
65
+ * written, badged "Unverified".
66
+ * - `agent-sdk`: the host runs none of its queries and holds no evidence, so
67
+ * it is always `false`, with the rest of the envelope kept as-is.
68
+ *
69
+ * Other backends pass through.
70
+ */
71
+ export declare function withHostVerification(result: RouteResult, envelope: RenderEnvelope): RenderEnvelope;
54
72
  /** Maps a `RunFinishEvent`'s companion `ArtifactAgentEvent` straight through — no BFF-side transformation needed. */
55
73
  export declare function toArtifactEvent(id: string, name: string, artifactKind: ArtifactKind, location: string, artifactId: string): ArtifactEvent;
56
74
  export declare const DECISION_ROUTE_ID = "decision-route";
@@ -1,4 +1,5 @@
1
1
  import { BUILD_DASHBOARD_TOOL_NAME, extractEnvelopeFromText, WREN_QUERY_TOOL_NAME } from "../harness/index.js";
2
+ import { groundCodexLocalEnvelope } from "./codex-local-grounding.js";
2
3
  /**
3
4
  * Canned remediation text for a refusal. `RefusalAgentEvent`/`RefusalResult`
4
5
  * carry no remediation hint from the agent runtime — the UI's `fix` field is
@@ -35,9 +36,11 @@ export function extractTrace(result) {
35
36
  * on the floor and the raw text always went out as `form: "text"`,
36
37
  * `verified: false`). `extractEnvelopeFromText` recovers it — including
37
38
  * `answer_query`'s flat `{columns, rows}` tool-contract shape, normalized
38
- * into a `table` block — and `verified` is whatever the recovered envelope
39
- * actually carries, never hardcoded. Only when no envelope can be recovered
40
- * at all does the raw text fall back to `form: "text"`.
39
+ * into a `table` block. The recovered envelope's `verified` is model-written,
40
+ * so {@link withHostVerification} decides it from host evidence instead (see
41
+ * there). Only when
42
+ * no envelope can be recovered at all does the raw text fall back to
43
+ * `form: "text"`.
41
44
  *
42
45
  * That `form: "text"` fallback is the one case where "verified: false" is
43
46
  * ambiguous between two very different situations: a pure conversational
@@ -49,10 +52,15 @@ export function extractTrace(result) {
49
52
  * introspection and artifact-saving tool calls don't count as a data claim.
50
53
  */
51
54
  export function toAnswerOrRefusalEvent(id, result, worklog = []) {
55
+ if (result.backend === "codex-app-server") {
56
+ return { id, kind: "answer", answer: result.envelope
57
+ ? { form: "rich", envelope: result.envelope }
58
+ : { form: "text", text: result.finalText, verified: false, dataAnswer: result.dataAttempted === true || attemptedDataAccess(worklog) } };
59
+ }
52
60
  if (result.backend === "agent-sdk" || result.backend === "codex-local") {
53
61
  const envelope = extractEnvelopeFromText(result.finalText);
54
62
  if (envelope !== undefined) {
55
- return { id, kind: "answer", answer: { form: "rich", envelope } };
63
+ return { id, kind: "answer", answer: { form: "rich", envelope: withHostVerification(result, envelope) } };
56
64
  }
57
65
  return {
58
66
  id,
@@ -65,6 +73,26 @@ export function toAnswerOrRefusalEvent(id, result, worklog = []) {
65
73
  }
66
74
  return { id, kind: "refusal", reason: result.reason, fix: REFUSAL_FIX_HINT };
67
75
  }
76
+ /**
77
+ * The one place a subscription answer's `verified` is decided; a `verified`
78
+ * field in model-written text is never kept.
79
+ *
80
+ * - `codex-local`: the model's `run_sql` executes through this turn's host
81
+ * query service, so the answer is grounded against that turn's observations
82
+ * ({@link groundCodexLocalEnvelope}): verified only when every query it
83
+ * cites is one the host ran this turn and that read a table, and then its
84
+ * tables and definitions are the observed ones. Otherwise it stays as
85
+ * written, badged "Unverified".
86
+ * - `agent-sdk`: the host runs none of its queries and holds no evidence, so
87
+ * it is always `false`, with the rest of the envelope kept as-is.
88
+ *
89
+ * Other backends pass through.
90
+ */
91
+ export function withHostVerification(result, envelope) {
92
+ if (result.backend === "codex-local")
93
+ return groundCodexLocalEnvelope(envelope, result.finalText, result.hostObservations ?? []);
94
+ return result.backend === "agent-sdk" ? { ...envelope, verified: false } : envelope;
95
+ }
68
96
  /** In-process's native in-process tool names that constitute an actual data claim — schema browsing / artifact saving don't count. */
69
97
  const DATA_ACCESS_TOOL_NAMES = new Set([
70
98
  WREN_QUERY_TOOL_NAME,
@@ -139,6 +167,10 @@ export function clarifyDecisionStep(detail) {
139
167
  * -> `undefined`, so no gate entry is appended.
140
168
  */
141
169
  export function gateDecisionStep(result) {
170
+ if (result.backend === "codex-app-server")
171
+ return result.envelope?.verified === true
172
+ ? { id: DECISION_GATE_ID, kind: "decision", label: "Verify gate", state: "done", detail: "Query rows and SQL captured from successful host execution; interpretation is not verified." }
173
+ : undefined;
142
174
  if (result.backend === "agent-sdk" || result.backend === "codex-local")
143
175
  return undefined;
144
176
  if (result.kind === "refusal") {
@@ -317,7 +349,7 @@ function truncate(text, max) {
317
349
  }
318
350
  /** Short text used for `turns.answer_summary` — feeds `server/compose.ts`'s D3 context composition. Never the full envelope. */
319
351
  export function summarizeResult(result) {
320
- if (result.backend === "agent-sdk" || result.backend === "codex-local") {
352
+ if (result.backend === "agent-sdk" || (result.backend === "codex-local" || result.backend === "codex-app-server")) {
321
353
  return truncate(result.finalText, SUMMARY_MAX_LENGTH);
322
354
  }
323
355
  if (result.kind === "answer") {
@@ -14,6 +14,6 @@ export declare function collectBundleTierNames(bundle: Bundle): string[];
14
14
  * Keep the compiled bundle's declared profile tied to the BFF-selected
15
15
  * purpose before exposing it through the read-only Harness DTO.
16
16
  */
17
- export declare function assertHarnessBundlePurpose(bundle: Bundle, purpose: NativePurpose): void;
18
- export declare function buildHarnessDto(bundle: Bundle, store: Store, baseRouteOptions: BaseRouteOptions, purpose?: NativePurpose, nativeReadiness?: NativeSessionReadiness, setupReadiness?: HarnessSetupReadiness): HarnessDto;
17
+ export declare function assertHarnessBundlePurpose(bundle: Bundle, purpose: NativePurpose, selectedProfile?: string): void;
18
+ export declare function buildHarnessDto(bundle: Bundle, store: Store, baseRouteOptions: BaseRouteOptions, purpose?: NativePurpose, nativeReadiness?: NativeSessionReadiness, setupReadiness?: HarnessSetupReadiness, selectedProfile?: string): HarnessDto;
19
19
  export {};
@@ -33,7 +33,7 @@ import { createHash } from "node:crypto";
33
33
  import path from "node:path";
34
34
  import { describeConnection, resolveConnectionSource } from "./conn-config.js";
35
35
  import { bundleFormatVersion, deriveAdapterSpec, findLockedGatedCheck } from "../harness/index.js";
36
- import { assertHarnessPurposeProfile, NATIVE_DISPATCH_REGISTRY } from "./native-dispatch-registry.js";
36
+ import { assertHarnessPurposeProfile, HARNESS_PROFILE_IDENTITY_ERROR, NATIVE_DISPATCH_REGISTRY } from "./native-dispatch-registry.js";
37
37
  import { collectBundleTierNames as collectCompiledBundleTierNames, effectiveTierModel } from "./runtime-binding.js";
38
38
  /** snake_case/kebab-case id -> Title Case display name, e.g. "answer_query" -> "Answer Query". */
39
39
  function humanize(id) {
@@ -146,8 +146,10 @@ function runtimeBackendAndLabel(authChoice) {
146
146
  return { backend: "api-key", label: `API key (${authChoice.adapter})` };
147
147
  case "local":
148
148
  return { backend: "local", label: authChoice.endpoint ? `Local (${authChoice.endpoint})` : "Local" };
149
- case "gateway":
150
- return { backend: "gateway", label: "Gateway" };
149
+ case "gateway": {
150
+ const provider = authChoice.config?.["provider"];
151
+ return { backend: "gateway", label: typeof provider === "string" && provider.trim() ? `Gateway (pi-ai: ${provider})` : "Gateway" };
152
+ }
151
153
  }
152
154
  }
153
155
  /**
@@ -354,7 +356,14 @@ export function collectBundleTierNames(bundle) {
354
356
  * Keep the compiled bundle's declared profile tied to the BFF-selected
355
357
  * purpose before exposing it through the read-only Harness DTO.
356
358
  */
357
- export function assertHarnessBundlePurpose(bundle, purpose) {
359
+ export function assertHarnessBundlePurpose(bundle, purpose, selectedProfile) {
360
+ // A selected conversation profile is described by its registry id; the compiled bundle must
361
+ // carry exactly that id. Without a selection the purpose's fixed profile applies as before.
362
+ if (selectedProfile !== undefined && purpose === "analysis") {
363
+ if (bundle.profile !== selectedProfile)
364
+ throw new Error(HARNESS_PROFILE_IDENTITY_ERROR);
365
+ return;
366
+ }
358
367
  assertHarnessPurposeProfile(purpose, bundle.profile);
359
368
  }
360
369
  function buildRuntime(bundle, baseRouteOptions, resolver) {
@@ -397,8 +406,25 @@ function setupRunnerTarget(authChoice) {
397
406
  case "in-process": return { target: "in-process:setup", targetLabel: "In-process Setup runner" };
398
407
  }
399
408
  }
400
- function buildHarnessPurpose(purpose, baseRouteOptions, nativeReadiness, setupReadiness) {
409
+ function buildHarnessPurpose(purpose, baseRouteOptions, nativeReadiness, setupReadiness, selectedProfile) {
401
410
  const definition = NATIVE_DISPATCH_REGISTRY[purpose];
411
+ // A selected conversation profile is reported with its own id and per-profile readiness.
412
+ if (selectedProfile !== undefined && purpose === "analysis" && selectedProfile !== definition.profile) {
413
+ // Only the per-profile entry speaks for a selected profile. Falling back to the purpose-level
414
+ // row would report the default's availability under another profile's name and could hide a
415
+ // vendor refusal that applies to that profile alone.
416
+ const readiness = nativeReadiness?.profiles?.[selectedProfile];
417
+ const available = readiness?.available ?? false;
418
+ return {
419
+ purpose,
420
+ profile: selectedProfile,
421
+ scopeKind: definition.scopeKind,
422
+ executionKind: "native_session",
423
+ ...(readiness?.target ? { target: readiness.target, targetLabel: readiness.targetLabel } : {}),
424
+ available,
425
+ ...(!available ? { reason: readiness?.reason ?? NATIVE_PURPOSE_UNAVAILABLE_REASON } : {}),
426
+ };
427
+ }
402
428
  if (purpose === "setup") {
403
429
  const available = setupReadiness?.available ?? false;
404
430
  return {
@@ -419,9 +445,9 @@ function buildHarnessPurpose(purpose, baseRouteOptions, nativeReadiness, setupRe
419
445
  ...(!available ? { reason: readiness?.reason ?? NATIVE_PURPOSE_UNAVAILABLE_REASON } : {}),
420
446
  };
421
447
  }
422
- export function buildHarnessDto(bundle, store, baseRouteOptions, purpose = "analysis", nativeReadiness, setupReadiness) {
448
+ export function buildHarnessDto(bundle, store, baseRouteOptions, purpose = "analysis", nativeReadiness, setupReadiness, selectedProfile) {
423
449
  const resolver = buildRealTierResolver(baseRouteOptions, store);
424
- const purposeInfo = buildHarnessPurpose(purpose, baseRouteOptions, nativeReadiness, setupReadiness);
450
+ const purposeInfo = buildHarnessPurpose(purpose, baseRouteOptions, nativeReadiness, setupReadiness, selectedProfile);
425
451
  return {
426
452
  purpose: purposeInfo,
427
453
  profile: buildProfile(bundle, baseRouteOptions, store, purpose),
@@ -1,10 +1,11 @@
1
1
  import type { EnrichmentBinding } from "./enrichment.js";
2
2
  export { InteractiveLaunchError, legacyInteractiveWorkspace } from "./native-session-workspace.js";
3
3
  import type { NativeSessionStateBase } from "./native-session-workspace.js";
4
+ import type { NativeChildEnvironment, NativeExecutableIdentity } from "./native-runtime-spec.js";
4
5
  export declare const INTERACTIVE_TARGETS: readonly ["claude-code:interactive", "codex:interactive"];
5
6
  export type InteractiveTarget = (typeof INTERACTIVE_TARGETS)[number];
6
7
  export interface InteractiveLaunchSpec {
7
- readonly version: "1" | "2" | "3" | "4";
8
+ readonly version: "1" | "2" | "3" | "4" | "5";
8
9
  readonly target: InteractiveTarget;
9
10
  readonly executable: "claude" | "codex";
10
11
  readonly argv: readonly string[];
@@ -35,7 +36,7 @@ export interface PtyFactory {
35
36
  cwd: string;
36
37
  cols: number;
37
38
  rows: number;
38
- env?: NodeJS.ProcessEnv;
39
+ env?: NativeChildEnvironment;
39
40
  }): PtyProcess;
40
41
  }
41
42
  /**
@@ -48,12 +49,12 @@ export interface PtyFactory {
48
49
  * advertised xterm ANSI palette silently optional and allow a caller to
49
50
  * weaken the native-terminal contract.
50
51
  */
51
- export declare function nativeTerminalEnvironment(base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
52
+ export declare function nativeTerminalEnvironment(base?: NodeJS.ProcessEnv | NativeChildEnvironment): NativeChildEnvironment;
52
53
  /**
53
54
  * Mirrors the PATH lookup a shell-free PTY launch relies on. A shell alias or
54
55
  * function is deliberately not accepted: node-pty cannot execute either.
55
56
  */
56
- export declare function interactiveExecutableAvailable(executable: "claude" | "codex", pathValue?: string | undefined): boolean;
57
+ export declare function interactiveExecutableAvailable(executable: "claude" | "codex", pathValue?: string): boolean;
57
58
  export interface InteractiveTerminalSession {
58
59
  readonly id: string;
59
60
  readonly capability: string;
@@ -115,10 +116,11 @@ export declare function launchFallbackCommand(spec: Pick<InteractiveLaunchSpec,
115
116
  /** Parse exactly the public v1 producer contract; unknown fields fail closed. */
116
117
  export declare function readInteractiveLaunchSpec(boundProject: string, requestedTarget: InteractiveTarget, materializationRoot?: string, materializationState?: NativeSessionStateBase): InteractiveLaunchSpec;
117
118
  export declare function dispatchInteractiveArtifacts(input: {
118
- readonly warbleBin: string;
119
+ readonly producer: NativeExecutableIdentity;
119
120
  readonly irPath: string;
120
121
  readonly target: InteractiveTarget;
121
122
  readonly cwd: string;
123
+ readonly env: NativeChildEnvironment;
122
124
  readonly boundProject?: string;
123
125
  readonly materializationState?: NativeSessionStateBase;
124
126
  }): Promise<void>;
@@ -134,6 +136,7 @@ export interface InteractivePreparationInput {
134
136
  export interface InteractivePreparationDeps {
135
137
  readonly getCurrentBinding: () => EnrichmentBinding | undefined;
136
138
  readonly executableAvailable?: (executable: "claude" | "codex") => boolean;
139
+ readonly resolveExecutable?: (executable: "claude" | "codex") => NativeExecutableIdentity | undefined;
137
140
  }
138
141
  /**
139
142
  * Materializes and validates the clipboard/launch contract without loading a
@@ -158,6 +161,7 @@ export declare function getInteractiveTerminalReadiness(input: {
158
161
  readonly getCurrentBinding: () => EnrichmentBinding | undefined;
159
162
  readonly terminalHostAvailable: () => Promise<boolean>;
160
163
  readonly executableAvailable?: (executable: "claude" | "codex") => boolean;
164
+ readonly resolveExecutable?: (executable: "claude" | "codex") => NativeExecutableIdentity | undefined;
161
165
  /** BFF-owned base; validation is non-mutating and never reads the project root. */
162
166
  readonly materializationState?: NativeSessionStateBase;
163
167
  }): Promise<Record<InteractiveTarget, InteractiveTargetReadiness>>;
@@ -168,7 +172,7 @@ export declare class InteractiveTerminalManager {
168
172
  start(spec: InteractiveLaunchSpec, identity?: {
169
173
  readonly id: string;
170
174
  readonly capability: string;
171
- }, env?: NodeJS.ProcessEnv, leasePolicy?: InteractiveTerminalLeasePolicy): InteractiveTerminalSession;
175
+ }, env?: NodeJS.ProcessEnv | NativeChildEnvironment, leasePolicy?: InteractiveTerminalLeasePolicy): InteractiveTerminalSession;
172
176
  get(id: string): InteractiveTerminalSession | undefined;
173
177
  /** Runtime changes revoke every non-durable compatibility terminal immediately. */
174
178
  closeAll(): void;
@@ -7,9 +7,11 @@
7
7
  import { randomUUID } from "node:crypto";
8
8
  import { existsSync, realpathSync, readFileSync, statSync } from "node:fs";
9
9
  import path from "node:path";
10
+ import os from "node:os";
10
11
  import { spawn as spawnProcess } from "node:child_process";
11
12
  export { InteractiveLaunchError, legacyInteractiveWorkspace } from "./native-session-workspace.js";
12
13
  import { InteractiveLaunchError, nativeSessionStateBaseAvailable, validateLegacyInteractiveWorkspace } from "./native-session-workspace.js";
14
+ import { assertNativeExecutableIdentity, buildNativeChildEnvironment, nativeProcessEnvironment } from "./native-runtime-spec.js";
13
15
  export const INTERACTIVE_TARGETS = ["claude-code:interactive", "codex:interactive"];
14
16
  const EXECUTABLE = {
15
17
  "claude-code:interactive": "claude",
@@ -25,15 +27,33 @@ const EXECUTABLE = {
25
27
  * advertised xterm ANSI palette silently optional and allow a caller to
26
28
  * weaken the native-terminal contract.
27
29
  */
28
- export function nativeTerminalEnvironment(base = process.env) {
29
- const { NO_COLOR: _noColor, ...inherited } = base;
30
- return { ...inherited, TERM: "xterm-256color", COLORTERM: "truecolor" };
30
+ export function nativeTerminalEnvironment(base) {
31
+ const environment = base ?? buildNativeChildEnvironment({
32
+ toolDirectories: [path.dirname(process.execPath)],
33
+ home: os.homedir(),
34
+ });
35
+ const pathValue = environment.PATH ?? path.dirname(process.execPath);
36
+ const home = environment.HOME ?? os.homedir();
37
+ return Object.freeze({
38
+ PATH: pathValue,
39
+ HOME: home,
40
+ ...(process.platform === "darwin" ? { USER: os.userInfo().username, LOGNAME: os.userInfo().username } : {}),
41
+ TERM: "xterm-256color",
42
+ COLORTERM: "truecolor",
43
+ ...(environment.WREN_PROJECT_HOME ? { WREN_PROJECT_HOME: environment.WREN_PROJECT_HOME } : {}),
44
+ ...(environment.WREN_HOME ? { WREN_HOME: environment.WREN_HOME } : {}),
45
+ ...(environment.CODEX_HOME ? { CODEX_HOME: environment.CODEX_HOME } : {}),
46
+ ...(environment.WARBLE_MCP_CONNECTION_CREDENTIAL ? { WARBLE_MCP_CONNECTION_CREDENTIAL: environment.WARBLE_MCP_CONNECTION_CREDENTIAL } : {}),
47
+ ...(environment.WARBLE_SETUP_BOOTSTRAP_ROOT ? { WARBLE_SETUP_BOOTSTRAP_ROOT: environment.WARBLE_SETUP_BOOTSTRAP_ROOT } : {}),
48
+ ...(environment.PYTHONNOUSERSITE ? { PYTHONNOUSERSITE: "1" } : {}),
49
+ ...(environment.PYTHONUTF8 ? { PYTHONUTF8: "1" } : {}),
50
+ });
31
51
  }
32
52
  /**
33
53
  * Mirrors the PATH lookup a shell-free PTY launch relies on. A shell alias or
34
54
  * function is deliberately not accepted: node-pty cannot execute either.
35
55
  */
36
- export function interactiveExecutableAvailable(executable, pathValue = process.env["PATH"]) {
56
+ export function interactiveExecutableAvailable(executable, pathValue) {
37
57
  if (!pathValue)
38
58
  return false;
39
59
  return pathValue.split(path.delimiter).some((directory) => {
@@ -131,8 +151,9 @@ export async function dispatchInteractiveArtifacts(input) {
131
151
  throw new InteractiveLaunchError("interactive enrichment materialization failed");
132
152
  if (input.boundProject && input.materializationState)
133
153
  validateLegacyInteractiveWorkspace(input.materializationState, input.boundProject, input.cwd);
154
+ assertNativeExecutableIdentity(input.producer);
134
155
  await new Promise((resolve, reject) => {
135
- const child = spawnProcess(input.warbleBin, ["dispatch", input.irPath, "--target", input.target, "--out", input.cwd], { cwd: input.cwd, shell: false, stdio: "ignore" });
156
+ const child = spawnProcess(input.producer.executable, ["dispatch", input.irPath, "--target", input.target, "--out", input.cwd], { cwd: input.cwd, env: nativeProcessEnvironment(input.env), shell: false, stdio: "ignore" });
136
157
  child.once("error", () => reject(new InteractiveLaunchError("interactive enrichment materialization failed")));
137
158
  child.once("exit", (code) => code === 0 ? resolve() : reject(new InteractiveLaunchError("interactive enrichment materialization failed")));
138
159
  });
@@ -144,15 +165,19 @@ export async function dispatchInteractiveArtifacts(input) {
144
165
  */
145
166
  export async function prepareInteractiveHandoff(input, deps) {
146
167
  const executableAvailable = deps.executableAvailable ?? interactiveExecutableAvailable;
147
- if (!executableAvailable(EXECUTABLE[input.target])) {
168
+ const executable = deps.resolveExecutable?.(EXECUTABLE[input.target]);
169
+ if (!(executable ?? executableAvailable(EXECUTABLE[input.target]))) {
148
170
  throw new InteractiveLaunchError(`the ${EXECUTABLE[input.target]} interactive CLI is not available on this machine`);
149
171
  }
172
+ if (executable)
173
+ assertNativeExecutableIdentity(executable);
150
174
  await input.materialize();
151
175
  const current = deps.getCurrentBinding();
152
176
  if (!current || current.path !== input.binding.path || current.identity !== input.binding.identity || current.generation !== input.binding.generation || current.revision !== input.binding.revision) {
153
177
  throw new InteractiveLaunchError("interactive enrichment is stale because the bound project changed");
154
178
  }
155
- const spec = readInteractiveLaunchSpec(input.binding.path, input.target, input.artifactRoot, input.materializationState);
179
+ const parsed = readInteractiveLaunchSpec(input.binding.path, input.target, input.artifactRoot, input.materializationState);
180
+ const spec = executable ? { ...parsed, hostExecutable: executable.executable } : parsed;
156
181
  return { spec, handoff: { target: spec.target, fallbackCommand: launchFallbackCommand(spec) } };
157
182
  }
158
183
  /**
@@ -175,7 +200,7 @@ export async function getInteractiveTerminalReadiness(input) {
175
200
  const executableAvailable = input.executableAvailable ?? interactiveExecutableAvailable;
176
201
  const result = {};
177
202
  for (const target of INTERACTIVE_TARGETS) {
178
- if (!executableAvailable(EXECUTABLE[target])) {
203
+ if (!(input.resolveExecutable?.(EXECUTABLE[target]) ?? executableAvailable(EXECUTABLE[target]))) {
179
204
  const reason = `the ${EXECUTABLE[target]} interactive CLI is not available on this machine`;
180
205
  result[target] = { copyAvailable: false, embeddedTerminalAvailable: false, copyReason: reason, embeddedTerminalReason: reason };
181
206
  continue;