@wrenai/genbi 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +65 -3
  2. package/dist/assets/{echarts-LTFC-ysd.js → echarts-DYM4hjvk.js} +2 -2
  3. package/dist/assets/index-A_Wrq7Ey.css +1 -0
  4. package/dist/assets/{index-Bo--QMUN.js → index-DsH129tV.js} +18 -17
  5. package/dist/index.html +2 -2
  6. package/dist-server/harness/bundle/schema.d.ts +38 -0
  7. package/dist-server/harness/bundle/schema.js +18 -10
  8. package/dist-server/harness/bundle/version.js +1 -1
  9. package/dist-server/harness/cli-args.d.ts +10 -2
  10. package/dist-server/harness/cli-args.js +24 -8
  11. package/dist-server/harness/cli.js +75 -9
  12. package/dist-server/harness/compile/cache.js +4 -4
  13. package/dist-server/harness/compile/compose-profile.d.ts +51 -14
  14. package/dist-server/harness/compile/compose-profile.js +85 -19
  15. package/dist-server/harness/compile/context-loader.d.ts +56 -0
  16. package/dist-server/harness/compile/context-loader.js +237 -0
  17. package/dist-server/harness/compile/errors.d.ts +19 -0
  18. package/dist-server/harness/compile/errors.js +35 -0
  19. package/dist-server/harness/compile/index.d.ts +4 -3
  20. package/dist-server/harness/compile/index.js +4 -3
  21. package/dist-server/harness/compile/pipeline.d.ts +15 -7
  22. package/dist-server/harness/compile/pipeline.js +62 -14
  23. package/dist-server/harness/compile/resolve-binary.d.ts +2 -2
  24. package/dist-server/harness/compile/resolve-binary.js +3 -3
  25. package/dist-server/harness/compile/types.d.ts +29 -0
  26. package/dist-server/harness/compile/warble-identity.d.ts +11 -0
  27. package/dist-server/harness/compile/warble-identity.js +13 -0
  28. package/dist-server/harness/components/ai-step.d.ts +4 -0
  29. package/dist-server/harness/components/ai-step.js +40 -0
  30. package/dist-server/harness/components/broker.d.ts +60 -0
  31. package/dist-server/harness/components/broker.js +117 -0
  32. package/dist-server/harness/components/capability-card.d.ts +98 -0
  33. package/dist-server/harness/components/capability-card.js +169 -0
  34. package/dist-server/harness/components/context.d.ts +2 -0
  35. package/dist-server/harness/components/context.js +30 -0
  36. package/dist-server/harness/components/display.d.ts +6 -0
  37. package/dist-server/harness/components/display.js +29 -0
  38. package/dist-server/harness/components/egress-judge.d.ts +12 -0
  39. package/dist-server/harness/components/egress-judge.js +27 -0
  40. package/dist-server/harness/components/egress.d.ts +142 -0
  41. package/dist-server/harness/components/egress.js +469 -0
  42. package/dist-server/harness/components/in-process.d.ts +7 -0
  43. package/dist-server/harness/components/in-process.js +245 -0
  44. package/dist-server/harness/components/normalize.d.ts +35 -0
  45. package/dist-server/harness/components/normalize.js +245 -0
  46. package/dist-server/harness/components/plan.d.ts +31 -0
  47. package/dist-server/harness/components/plan.js +184 -0
  48. package/dist-server/harness/components/report.d.ts +207 -0
  49. package/dist-server/harness/components/report.js +309 -0
  50. package/dist-server/harness/components/runner.d.ts +193 -0
  51. package/dist-server/harness/components/runner.js +492 -0
  52. package/dist-server/harness/components/semantic-guards.d.ts +20 -0
  53. package/dist-server/harness/components/semantic-guards.js +42 -0
  54. package/dist-server/harness/components/terminal-json.d.ts +18 -0
  55. package/dist-server/harness/components/terminal-json.js +82 -0
  56. package/dist-server/harness/components/usage.d.ts +14 -0
  57. package/dist-server/harness/components/usage.js +19 -0
  58. package/dist-server/harness/components/wren-access.d.ts +29 -0
  59. package/dist-server/harness/components/wren-access.js +233 -0
  60. package/dist-server/harness/components/zone-dry-run.d.ts +43 -0
  61. package/dist-server/harness/components/zone-dry-run.js +107 -0
  62. package/dist-server/harness/components/zone-gate.d.ts +76 -0
  63. package/dist-server/harness/components/zone-gate.js +144 -0
  64. package/dist-server/harness/events/index.d.ts +1 -1
  65. package/dist-server/harness/events/types.d.ts +52 -0
  66. package/dist-server/harness/index.d.ts +9 -2
  67. package/dist-server/harness/index.js +5 -1
  68. package/dist-server/harness/loop/executor.d.ts +1 -0
  69. package/dist-server/harness/loop/executor.js +37 -15
  70. package/dist-server/harness/npm-package-resolve.d.ts +2 -0
  71. package/dist-server/harness/npm-package-resolve.js +16 -4
  72. package/dist-server/harness/providers/adapters/openai-compatible.d.ts +13 -0
  73. package/dist-server/harness/providers/adapters/openai-compatible.js +17 -0
  74. package/dist-server/harness/providers/adapters/openai.d.ts +7 -0
  75. package/dist-server/harness/providers/adapters/openai.js +6 -0
  76. package/dist-server/harness/providers/adapters/pi-ai.d.ts +48 -0
  77. package/dist-server/harness/providers/adapters/pi-ai.js +518 -0
  78. package/dist-server/harness/providers/binding.d.ts +45 -4
  79. package/dist-server/harness/providers/binding.js +44 -8
  80. package/dist-server/harness/providers/index.d.ts +9 -3
  81. package/dist-server/harness/providers/index.js +10 -3
  82. package/dist-server/harness/providers/zone.d.ts +96 -0
  83. package/dist-server/harness/providers/zone.js +112 -0
  84. package/dist-server/harness/render/answering-query.d.ts +29 -0
  85. package/dist-server/harness/render/answering-query.js +62 -0
  86. package/dist-server/harness/render/envelope.d.ts +6 -0
  87. package/dist-server/harness/render/envelope.js +10 -3
  88. package/dist-server/harness/route/adapter-spec.d.ts +8 -9
  89. package/dist-server/harness/route/adapter-spec.js +13 -14
  90. package/dist-server/harness/route/codex-ask-events.js +2 -1
  91. package/dist-server/harness/route/codex-ask.d.ts +4 -0
  92. package/dist-server/harness/route/codex-ask.js +43 -45
  93. package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
  94. package/dist-server/harness/route/codex-local-manifest.js +96 -15
  95. package/dist-server/harness/route/describe.js +45 -6
  96. package/dist-server/harness/route/dispatched.d.ts +20 -0
  97. package/dist-server/harness/route/dispatched.js +122 -24
  98. package/dist-server/harness/route/host-query.d.ts +93 -0
  99. package/dist-server/harness/route/host-query.js +257 -0
  100. package/dist-server/harness/route/in-process.d.ts +22 -1
  101. package/dist-server/harness/route/in-process.js +54 -4
  102. package/dist-server/harness/route/route.js +8 -2
  103. package/dist-server/harness/route/types.d.ts +47 -1
  104. package/dist-server/harness/route/wren-host-proxy.d.ts +33 -0
  105. package/dist-server/harness/route/wren-host-proxy.js +228 -0
  106. package/dist-server/harness/session/run.js +30 -10
  107. package/dist-server/harness/setup/runner.d.ts +17 -3
  108. package/dist-server/harness/setup/runner.js +272 -89
  109. package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
  110. package/dist-server/harness/setup/yaml-scalar.js +62 -0
  111. package/dist-server/harness/tools/setup-native.d.ts +7 -0
  112. package/dist-server/harness/tools/setup-native.js +7 -4
  113. package/dist-server/server/adopt.js +1 -17
  114. package/dist-server/server/app.d.ts +1 -0
  115. package/dist-server/server/app.js +175 -17
  116. package/dist-server/server/auth-choice.d.ts +13 -1
  117. package/dist-server/server/auth-choice.js +66 -1
  118. package/dist-server/server/bin.js +185 -25
  119. package/dist-server/server/codex-native-session.d.ts +25 -0
  120. package/dist-server/server/codex-native-session.js +1 -0
  121. package/dist-server/server/codex-runtime-composition.d.ts +34 -0
  122. package/dist-server/server/codex-runtime-composition.js +250 -0
  123. package/dist-server/server/codex-structured-ask.d.ts +23 -0
  124. package/dist-server/server/codex-structured-ask.js +102 -0
  125. package/dist-server/server/conn-config.js +4 -16
  126. package/dist-server/server/conversation-socket.d.ts +16 -0
  127. package/dist-server/server/conversation-socket.js +65 -0
  128. package/dist-server/server/db.d.ts +46 -0
  129. package/dist-server/server/db.js +88 -9
  130. package/dist-server/server/env-detect.d.ts +6 -0
  131. package/dist-server/server/env-detect.js +7 -0
  132. package/dist-server/server/fold.d.ts +23 -4
  133. package/dist-server/server/fold.js +42 -5
  134. package/dist-server/server/harness.d.ts +2 -2
  135. package/dist-server/server/harness.js +33 -7
  136. package/dist-server/server/host-evidence-grounding.d.ts +21 -0
  137. package/dist-server/server/host-evidence-grounding.js +93 -0
  138. package/dist-server/server/interactive-terminal.d.ts +10 -6
  139. package/dist-server/server/interactive-terminal.js +33 -8
  140. package/dist-server/server/managed-wren-runtime.d.ts +116 -0
  141. package/dist-server/server/managed-wren-runtime.js +670 -0
  142. package/dist-server/server/native-artifacts.d.ts +112 -7
  143. package/dist-server/server/native-artifacts.js +194 -14
  144. package/dist-server/server/native-component-context.d.ts +19 -0
  145. package/dist-server/server/native-component-context.js +76 -0
  146. package/dist-server/server/native-component-preparation.d.ts +20 -0
  147. package/dist-server/server/native-component-preparation.js +129 -0
  148. package/dist-server/server/native-component-runtime.d.ts +36 -0
  149. package/dist-server/server/native-component-runtime.js +92 -0
  150. package/dist-server/server/native-components.d.ts +84 -0
  151. package/dist-server/server/native-components.js +160 -0
  152. package/dist-server/server/native-dispatch-process.d.ts +8 -0
  153. package/dist-server/server/native-dispatch-process.js +65 -0
  154. package/dist-server/server/native-runtime-spec.d.ts +89 -0
  155. package/dist-server/server/native-runtime-spec.js +169 -0
  156. package/dist-server/server/native-sessions.d.ts +172 -19
  157. package/dist-server/server/native-sessions.js +839 -244
  158. package/dist-server/server/native-shutdown.d.ts +6 -0
  159. package/dist-server/server/native-shutdown.js +28 -0
  160. package/dist-server/server/native-wren-home.d.ts +18 -0
  161. package/dist-server/server/native-wren-home.js +262 -0
  162. package/dist-server/server/node-pty-host.d.ts +2 -1
  163. package/dist-server/server/node-pty-host.js +3 -2
  164. package/dist-server/server/profile-registry.d.ts +139 -0
  165. package/dist-server/server/profile-registry.js +620 -0
  166. package/dist-server/server/runtime-binding.d.ts +4 -1
  167. package/dist-server/server/runtime-binding.js +50 -2
  168. package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
  169. package/dist-server/server/runtime-host/claude-component-step.js +186 -0
  170. package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
  171. package/dist-server/server/runtime-host/codex-app-server.js +239 -0
  172. package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
  173. package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
  174. package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
  175. package/dist-server/server/runtime-host/codex-component-step.js +308 -0
  176. package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
  177. package/dist-server/server/runtime-host/codex-conversation.js +295 -0
  178. package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
  179. package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
  180. package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
  181. package/dist-server/server/runtime-host/codex-events.js +66 -0
  182. package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
  183. package/dist-server/server/runtime-host/codex-policy.js +151 -0
  184. package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
  185. package/dist-server/server/runtime-host/codex-process.js +96 -0
  186. package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
  187. package/dist-server/server/runtime-host/codex-rpc.js +182 -0
  188. package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
  189. package/dist-server/server/runtime-host/codex-session.js +492 -0
  190. package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
  191. package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
  192. package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
  193. package/dist-server/server/runtime-host/codex-terminal.js +120 -0
  194. package/dist-server/server/runtime-host/index.d.ts +5 -0
  195. package/dist-server/server/runtime-host/index.js +3 -0
  196. package/dist-server/server/runtime-host/local.d.ts +29 -0
  197. package/dist-server/server/runtime-host/local.js +90 -0
  198. package/dist-server/server/runtime-host/policy.d.ts +8 -0
  199. package/dist-server/server/runtime-host/policy.js +78 -0
  200. package/dist-server/server/runtime-host/types.d.ts +52 -0
  201. package/dist-server/server/runtime-host/types.js +62 -0
  202. package/dist-server/server/structured-query-evidence.d.ts +7 -0
  203. package/dist-server/server/structured-query-evidence.js +47 -0
  204. package/dist-server/server/structured-runtime.d.ts +52 -0
  205. package/dist-server/server/structured-runtime.js +130 -0
  206. package/dist-server/server/structured-scope.d.ts +4 -0
  207. package/dist-server/server/structured-scope.js +12 -0
  208. package/dist-server/server/turn.d.ts +6 -1
  209. package/dist-server/server/turn.js +86 -26
  210. package/dist-server/server/wire-types.d.ts +67 -3
  211. package/dist-server/server/wren-profiles.js +1 -1
  212. package/managed-wren/README.md +60 -0
  213. package/managed-wren/dependency-wheels.json +282 -0
  214. package/managed-wren/manifest.json +373 -0
  215. package/managed-wren/release-inputs.json +45 -0
  216. package/managed-wren/runtime-tree.cjs +115 -0
  217. package/managed-wren/source-inputs.json +40 -0
  218. package/package.json +15 -6
  219. package/profiles/genbi-default/context/binding.yml +17 -7
  220. package/profiles/genbi-default/context/context.json +146 -0
  221. package/profiles/genbi-default/ir.golden.json +39 -43
  222. package/profiles/genbi-default/profile.yml +31 -0
  223. package/profiles/genbi-enrich-context/context/binding.yml +14 -3
  224. package/profiles/genbi-enrich-context/context/context.json +146 -0
  225. package/profiles/genbi-enrich-context/ir.golden.json +4 -5
  226. package/profiles/genbi-monitor/context/binding.yml +13 -5
  227. package/profiles/genbi-monitor/context/context.json +146 -0
  228. package/profiles/genbi-monitor/ir.golden.json +13 -7
  229. package/profiles/genbi-report/context/binding.yml +19 -0
  230. package/profiles/genbi-report/context/context.json +223 -0
  231. package/profiles/genbi-report/ir.golden.json +328 -0
  232. package/profiles/genbi-report/profile.yml +38 -0
  233. package/profiles/genbi-setup/ir.golden.json +3 -1
  234. package/dist/assets/index-Xs4q7pNo.css +0 -1
@@ -61,6 +61,7 @@ import { buildUniformTierBinding } from "../route/tier-binding.js";
61
61
  import { createNativeToolRegistry, createSetupExecutionTool, SETUP_EXECUTION_TOOL_NAME } from "../tools/index.js";
62
62
  import { withResolvedTools } from "../tools/wiring.js";
63
63
  import { CodexSetupEventMapper } from "./codex-events.js";
64
+ import { readYamlScalarField } from "./yaml-scalar.js";
64
65
  /** The warble component the setup wizard's connect step dispatches. */
65
66
  export const CONNECT_SOURCE_AGENT_ID = "connect_source";
66
67
  /** The Warble component the setup wizard's context step dispatches (`profiles/genbi-setup/components/build_context`). */
@@ -123,6 +124,22 @@ function resolveDispatchedUserProject(runOptions, agentId) {
123
124
  }
124
125
  return canonicalProject;
125
126
  }
127
+ /**
128
+ * The project directory a project-bound step (`connect_resume`/`context`) runs
129
+ * against, as the default exec cwd of the in-process `setup_execution` tool.
130
+ * `undefined` for the initial `connect` step, which scaffolds the project from
131
+ * the workspace root, and for a missing/unsafe project name. The tool applies
132
+ * it only while the directory actually exists.
133
+ */
134
+ function boundSetupProjectDir(runOptions, agentId) {
135
+ const stepKey = runOptions.stepKey ?? (agentId === BUILD_CONTEXT_AGENT_ID ? "context" : "connect");
136
+ if (stepKey === "connect")
137
+ return undefined;
138
+ const projectName = runOptions.projectName;
139
+ if (projectName === undefined || !SAFE_PROJECT_NAME.test(projectName))
140
+ return undefined;
141
+ return path.resolve(runOptions.workspaceRoot, projectName);
142
+ }
126
143
  /**
127
144
  * Dispatches a setup-wizard turn over dispatched, bypassing `route()` entirely
128
145
  * (unlike the Ask turn path) so it can perform its own, setup-specific
@@ -142,6 +159,7 @@ export class DispatchedSetupRunner {
142
159
  return resolveEffectiveMaxTurns(this.options.maxTurns, agentId);
143
160
  }
144
161
  async run(runOptions) {
162
+ runOptions.signal?.throwIfAborted();
145
163
  const { authChoice } = runOptions;
146
164
  const agentId = runOptions.agentId ?? CONNECT_SOURCE_AGENT_ID;
147
165
  // Only build_context needs the raised turn budget — generating an MDL is
@@ -176,6 +194,7 @@ export class DispatchedSetupRunner {
176
194
  ...(modelsConfig !== undefined ? { modelsConfig } : {}),
177
195
  ...(maxTurns !== undefined ? { maxTurns } : {}),
178
196
  ...(runOptions.onEvent !== undefined ? { onEvent: runOptions.onEvent } : {}),
197
+ ...(runOptions.signal !== undefined ? { signal: runOptions.signal } : {}),
179
198
  ...(runOptions.resumeSessionId !== undefined ? { resumeSessionId: runOptions.resumeSessionId } : {}),
180
199
  });
181
200
  return { finalText: result.finalText, ...(result.sessionId !== undefined ? { sessionId: result.sessionId } : {}) };
@@ -248,6 +267,7 @@ export class CodexSetupRunner {
248
267
  this.options = options;
249
268
  }
250
269
  async run(runOptions) {
270
+ runOptions.signal?.throwIfAborted();
251
271
  if (runOptions.authChoice.mode !== "subscription" || runOptions.authChoice.provider !== "codex") {
252
272
  throw new Error("Codex setup runner requires a Codex subscription auth choice");
253
273
  }
@@ -286,10 +306,9 @@ export class CodexSetupRunner {
286
306
  "--server-arg=--workspace-root",
287
307
  "--server-arg",
288
308
  turnRoot,
289
- "--source-tool",
290
- SETUP_EXECUTION_TOOL_NAME,
291
- "--context-tool",
292
- SETUP_EXECUTION_TOOL_NAME,
309
+ "--transport", "exec",
310
+ "--step-tool", `${agentId === CONNECT_SOURCE_AGENT_ID ? "connect" : "build"}=${SETUP_EXECUTION_TOOL_NAME}`,
311
+ "--require-tool", agentId === CONNECT_SOURCE_AGENT_ID ? "connect" : "build",
293
312
  "--timeout",
294
313
  String(timeoutMs),
295
314
  ...(this.options.codexBin ? ["--codex-bin", this.options.codexBin] : []),
@@ -299,7 +318,7 @@ export class CodexSetupRunner {
299
318
  emitter.emit({ kind: "run.start", mode: "B", agentId });
300
319
  const mapper = new CodexSetupEventMapper(tracePath);
301
320
  try {
302
- const rawFinalText = await spawnCodexSetup(cli.command, args, mapper, emitter.emit, timeoutMs + 5_000);
321
+ const rawFinalText = await spawnCodexSetup(cli.command, args, mapper, emitter.emit, timeoutMs + 5_000, runOptions.signal);
303
322
  const finalText = unwrapCodexSetupEnvelope(rawFinalText, expectedProducedField);
304
323
  emitter.emit({ kind: "run.finish", status: "answer" });
305
324
  return { finalText };
@@ -325,8 +344,12 @@ function defaultCodexSetupMcpInvocation(tracePath) {
325
344
  function sanitizedCodexSetupEnvironment() {
326
345
  return Object.fromEntries(Object.entries(process.env).filter(([key, value]) => value !== undefined && !CODEX_BILLING_ENV_KEYS.has(key.toUpperCase())));
327
346
  }
328
- function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
347
+ function spawnCodexSetup(command, args, mapper, emit, timeoutMs, abortSignal) {
329
348
  return new Promise((resolve, reject) => {
349
+ if (abortSignal?.aborted) {
350
+ reject(new Error("Codex setup cancelled"));
351
+ return;
352
+ }
330
353
  const child = spawn(command, [...args], {
331
354
  env: sanitizedCodexSetupEnvironment(),
332
355
  stdio: ["ignore", "pipe", "pipe"],
@@ -335,6 +358,7 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
335
358
  let stderr = "";
336
359
  let settled = false;
337
360
  let timedOut = false;
361
+ let cancelled = false;
338
362
  let killTimer;
339
363
  const terminate = (signal) => {
340
364
  if (child.pid !== undefined && process.platform !== "win32") {
@@ -348,7 +372,17 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
348
372
  }
349
373
  child.kill(signal);
350
374
  };
375
+ const cancel = () => {
376
+ if (settled || cancelled || timedOut)
377
+ return;
378
+ cancelled = true;
379
+ terminate("SIGTERM");
380
+ killTimer = setTimeout(() => terminate("SIGKILL"), 1_000);
381
+ };
382
+ abortSignal?.addEventListener("abort", cancel, { once: true });
351
383
  const timer = setTimeout(() => {
384
+ if (cancelled)
385
+ return;
352
386
  timedOut = true;
353
387
  terminate("SIGTERM");
354
388
  killTimer = setTimeout(() => terminate("SIGKILL"), 1_000);
@@ -365,6 +399,7 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
365
399
  catch (error) {
366
400
  settled = true;
367
401
  clearTimeout(timer);
402
+ abortSignal?.removeEventListener("abort", cancel);
368
403
  terminate("SIGTERM");
369
404
  killTimer = setTimeout(() => terminate("SIGKILL"), 1_000);
370
405
  reject(error);
@@ -379,12 +414,14 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
379
414
  return;
380
415
  settled = true;
381
416
  clearTimeout(timer);
417
+ abortSignal?.removeEventListener("abort", cancel);
382
418
  if (killTimer)
383
419
  clearTimeout(killTimer);
384
420
  reject(new Error(`warble-codex-local failed to start: ${error.message}`));
385
421
  });
386
422
  child.on("close", (code, signal) => {
387
423
  clearTimeout(timer);
424
+ abortSignal?.removeEventListener("abort", cancel);
388
425
  lines.close();
389
426
  // Do not cancel an already-scheduled process-group SIGKILL here. The
390
427
  // direct dispatcher can exit on SIGTERM while one of its descendants
@@ -392,7 +429,10 @@ function spawnCodexSetup(command, args, mapper, emit, timeoutMs) {
392
429
  if (settled)
393
430
  return;
394
431
  settled = true;
395
- if (timedOut) {
432
+ if (cancelled) {
433
+ reject(new Error("Codex setup cancelled"));
434
+ }
435
+ else if (timedOut) {
396
436
  reject(new Error(`warble-codex-local timed out after ${timeoutMs}ms`));
397
437
  }
398
438
  else if (code !== 0) {
@@ -483,6 +523,7 @@ export class InProcessSetupRunner {
483
523
  return resolveEffectiveMaxTurns(this.options.maxTurns, agentId);
484
524
  }
485
525
  async run(runOptions) {
526
+ runOptions.signal?.throwIfAborted();
486
527
  const { authChoice } = runOptions;
487
528
  const agentId = runOptions.agentId ?? CONNECT_SOURCE_AGENT_ID;
488
529
  if (authChoice.mode === "subscription") {
@@ -537,8 +578,14 @@ export class InProcessSetupRunner {
537
578
  // project-root write scope on the dispatched side.
538
579
  const policy = { readOnly: false, artifactWriteScope: "." };
539
580
  const env = createLocalExecutionEnv({ rootDir: runOptions.workspaceRoot });
581
+ const boundProjectDir = boundSetupProjectDir(runOptions, agentId);
540
582
  const nativeTools = createNativeToolRegistry();
541
- nativeTools.register(SETUP_EXECUTION_TOOL_NAME, () => createSetupExecutionTool({ env, policy, workspaceRoot: runOptions.workspaceRoot }));
583
+ nativeTools.register(SETUP_EXECUTION_TOOL_NAME, () => createSetupExecutionTool({
584
+ env,
585
+ policy,
586
+ workspaceRoot: runOptions.workspaceRoot,
587
+ ...(boundProjectDir !== undefined ? { defaultExecCwd: boundProjectDir } : {}),
588
+ }));
542
589
  // Kept in lockstep with `effectiveMaxTurns()` above via the shared
543
590
  // `resolveEffectiveMaxTurns` helper, exactly like `DispatchedSetupRunner.run()`
544
591
  // does for its own `--max-turns` flag.
@@ -548,6 +595,7 @@ export class InProcessSetupRunner {
548
595
  registry,
549
596
  tools,
550
597
  userInput: runOptions.prompt,
598
+ ...(runOptions.signal ? { signal: runOptions.signal } : {}),
551
599
  onEvent: emitter.emit,
552
600
  ...(maxSteps !== undefined ? { maxSteps } : {}),
553
601
  }));
@@ -620,7 +668,9 @@ export class InProcessSetupRunner {
620
668
  * rather than pulling in a YAML parser dependency. Matches only an unindented
621
669
  * `field:` key (so it can't be fooled by a same-named key nested under
622
670
  * another mapping), strips a single layer of surrounding quotes, and returns
623
- * `undefined` if the file is missing/unreadable or the field isn't present.
671
+ * `undefined` if the file is missing/unreadable or the field isn't present. Values
672
+ * are read as YAML scalars (trailing comments dropped; blank or comment-only is `""`),
673
+ * via `./yaml-scalar.ts`.
624
674
  */
625
675
  function readProjectYamlField(yamlPath, field) {
626
676
  let content;
@@ -630,14 +680,7 @@ function readProjectYamlField(yamlPath, field) {
630
680
  catch {
631
681
  return undefined;
632
682
  }
633
- const pattern = new RegExp(`^${field}:\\s*(.+?)\\s*$`, "m");
634
- const match = pattern.exec(content);
635
- if (!match) {
636
- return undefined;
637
- }
638
- const raw = match[1];
639
- const unquoted = /^"(.*)"$/.exec(raw) ?? /^'(.*)'$/.exec(raw);
640
- return (unquoted ? unquoted[1] : raw).trim();
683
+ return readYamlScalarField(content, field);
641
684
  }
642
685
  /** Parses the small KEY=value subset of a project .env file used by Wren profiles. */
643
686
  function parseDotEnv(text) {
@@ -809,7 +852,7 @@ export function contextLifecycleIdentityFingerprint(root, name, selectedSourceTy
809
852
  const declaredSourceType = readProjectYamlField(projectYml, "data_source");
810
853
  const normalizedSelectedSourceType = selectedSourceType.trim().toLowerCase();
811
854
  const normalizedDeclaredSourceType = declaredSourceType?.trim().toLowerCase();
812
- if (profile === undefined || normalizedDeclaredSourceType === undefined || normalizedDeclaredSourceType !== normalizedSelectedSourceType)
855
+ if (!profile || normalizedDeclaredSourceType === undefined || normalizedDeclaredSourceType !== normalizedSelectedSourceType)
813
856
  return undefined;
814
857
  const connectionTargetDigest = effectiveConnectionTargetDigest(projectDir, normalizedSelectedSourceType);
815
858
  if (connectionTargetDigest === undefined)
@@ -1031,6 +1074,64 @@ const SCHEMA_INTROSPECTION_COMMANDS = [
1031
1074
  /\b(?:psql|mysql|sqlite3|duckdb|snowsql|clickhouse-client|trino(?:-cli)?)\b[\s\S]*(?:\s(?:-c|--command|--execute|--query)\s|\b(?:information_schema|show\s+(?:tables|columns)|describe\s+(?:table|schema)|\.tables)\b)/i,
1032
1075
  /\bbq\s+query\b/i,
1033
1076
  ];
1077
+ const CD_PREFIX = /^\s*cd\s+("[^"$`\\]*"|'[^']*'|[^\s;&|<>()$`\\'"~]+)\s*&&\s*([\s\S]*)$/;
1078
+ const STARTS_WITH_CD = /^\s*cd(?:\s|$)/;
1079
+ /**
1080
+ * A recorded relative `cwd` resolves against the tool's own root: the workspace
1081
+ * for the in-process tool, the project for Codex local (whose tool root is the
1082
+ * project directory for project steps).
1083
+ */
1084
+ function recordedCwdBase(entry, cwd, binding) {
1085
+ return path.resolve(entry.label === CODEX_SETUP_EXECUTION_TOOL_NAME ? binding.projectDir : binding.root, cwd);
1086
+ }
1087
+ function samePath(a, b) {
1088
+ const resolvedA = path.resolve(a);
1089
+ const resolvedB = path.resolve(b);
1090
+ if (resolvedA === resolvedB)
1091
+ return true;
1092
+ try {
1093
+ return realpathSync(resolvedA) === realpathSync(resolvedB);
1094
+ }
1095
+ catch {
1096
+ return false;
1097
+ }
1098
+ }
1099
+ /**
1100
+ * The ONE recogniser rule shared by schema discovery and the context
1101
+ * lifecycle: a recorded command counts only as the bare command or as exactly
1102
+ * one `cd <dir> && <cmd>` whose `<dir>` is the bound project. Returns the
1103
+ * command to match against the per-stage patterns (the `cd` prefix stripped),
1104
+ * or `undefined` when the shape is not recognised (another directory, a second
1105
+ * `cd`, a dynamic or `~` directory, or no binding to verify against).
1106
+ *
1107
+ * `<dir>` resolves against the exec's explicit `cwd` when the entry recorded
1108
+ * one. Otherwise the effective cwd was not recorded, so the target is accepted
1109
+ * if it names the project from either candidate default (workspace root, the
1110
+ * pre-binding default; or the project itself, the post-binding default). The
1111
+ * entry already exited 0, so the `cd` really resolved to that directory.
1112
+ */
1113
+ function projectLocalCommand(entry, command, binding) {
1114
+ const explicitCwd = entry.input?.cwd;
1115
+ const recordedCwd = typeof explicitCwd === "string" && explicitCwd.length > 0 ? explicitCwd : undefined;
1116
+ if (!STARTS_WITH_CD.test(command)) {
1117
+ if (binding === undefined || recordedCwd === undefined)
1118
+ return command;
1119
+ // A recorded cwd must itself be the project for a bare command to count.
1120
+ return samePath(recordedCwdBase(entry, recordedCwd, binding), binding.projectDir) ? command : undefined;
1121
+ }
1122
+ if (binding === undefined)
1123
+ return undefined;
1124
+ const match = CD_PREFIX.exec(command);
1125
+ if (!match)
1126
+ return undefined;
1127
+ const raw = match[1];
1128
+ const dir = raw.startsWith('"') || raw.startsWith("'") ? raw.slice(1, -1) : raw;
1129
+ const inner = match[2];
1130
+ if (dir.length === 0 || STARTS_WITH_CD.test(inner))
1131
+ return undefined;
1132
+ const bases = recordedCwd !== undefined ? [recordedCwdBase(entry, recordedCwd, binding)] : [binding.root, binding.projectDir];
1133
+ return bases.some((base) => samePath(path.resolve(base, dir), binding.projectDir)) ? inner : undefined;
1134
+ }
1034
1135
  /**
1035
1136
  * The single setup-workflow contract for recognisable schema discovery. A
1036
1137
  * matching command is evidence only after it completed successfully (see
@@ -1039,13 +1140,18 @@ const SCHEMA_INTROSPECTION_COMMANDS = [
1039
1140
  * this for both terminal acceptance and the no-introspection diagnostic,
1040
1141
  * rather than reimplementing the allowlist elsewhere.
1041
1142
  */
1042
- export function classifyRecordedSchemaDiscovery(worklog) {
1143
+ export function classifyRecordedSchemaDiscovery(worklog, binding) {
1043
1144
  let failed;
1044
1145
  for (const entry of worklog) {
1045
1146
  const command = isSetupExecutionEntry(entry) ? execCommandOf(entry) : undefined;
1046
- if (command === undefined ||
1047
- !SCHEMA_DISCOVERY_EVIDENCE.test(command) ||
1048
- !SCHEMA_INTROSPECTION_COMMANDS.some((pattern) => pattern.test(command))) {
1147
+ if (command === undefined)
1148
+ continue;
1149
+ // Without a binding (legacy callers) the matching below is unchanged; with
1150
+ // one, a leading `cd` must satisfy the shared project-local rule.
1151
+ const recognised = binding === undefined ? command : projectLocalCommand(entry, command, binding);
1152
+ if (recognised === undefined ||
1153
+ !SCHEMA_DISCOVERY_EVIDENCE.test(recognised) ||
1154
+ !SCHEMA_INTROSPECTION_COMMANDS.some((pattern) => pattern.test(recognised))) {
1049
1155
  continue;
1050
1156
  }
1051
1157
  const succeeded = execSucceeded(entry);
@@ -1066,7 +1172,7 @@ const DIRECT_CONTEXT_LIFECYCLE_COMMAND = new RegExp(String.raw `^\s*(?:(?:${SHEL
1066
1172
  * context build`, each recorded as a successful setup execution. An artifact
1067
1173
  * alone or merely naming a command in the final message is not evidence.
1068
1174
  */
1069
- function contextLifecycleStage(command) {
1175
+ function contextLifecycleStage(entry, command, binding) {
1070
1176
  // This evidence must describe the actual lifecycle invocation, rather than
1071
1177
  // merely mention it. `setup_execution` already supplies the project cwd as
1072
1178
  // a separate structured field; the only shell prefixes we accept here are
@@ -1075,7 +1181,12 @@ function contextLifecycleStage(command) {
1075
1181
  // command cwd to the canonical project, so this is the same project-local
1076
1182
  // lifecycle operation emitted by some Wren guidance. Arbitrary paths, other
1077
1183
  // flags, shell sequencing, quoted text, and wrappers remain unrecognized.
1078
- const match = DIRECT_CONTEXT_LIFECYCLE_COMMAND.exec(command);
1184
+ // The one additional shape is a single `cd <project> &&` prefix, decided by
1185
+ // the shared `projectLocalCommand` rule (also used for schema discovery).
1186
+ const inner = projectLocalCommand(entry, command, binding);
1187
+ if (inner === undefined)
1188
+ return undefined;
1189
+ const match = DIRECT_CONTEXT_LIFECYCLE_COMMAND.exec(inner);
1079
1190
  return match?.[1]?.toLowerCase();
1080
1191
  }
1081
1192
  function stageAfter(prefix) {
@@ -1090,40 +1201,55 @@ function prefixFor(stage) {
1090
1201
  return "discovery";
1091
1202
  return stage === "build" ? "validate" : "none";
1092
1203
  }
1093
- function scanRecordedContextLifecycle(worklog, prior) {
1204
+ const CONTEXT_LIFECYCLE_MENTION = /\bwren\s+context\s+(validate|build)\b/i;
1205
+ function scanRecordedContextLifecycle(worklog, prior, binding) {
1094
1206
  let stage = stageAfter(prior);
1095
1207
  let completed = prior === "build";
1096
1208
  let outOfOrderObserved;
1209
+ // Successful commands that mention a lifecycle command but were not
1210
+ // recognised as one, so a failure can quote them instead of claiming the
1211
+ // command never ran.
1212
+ const unrecognisedMentions = [];
1097
1213
  for (const entry of worklog) {
1098
1214
  if (!isSetupExecutionEntry(entry) || execSucceeded(entry) !== true)
1099
1215
  continue;
1100
1216
  const command = execCommandOf(entry);
1101
1217
  if (command === undefined)
1102
1218
  continue;
1219
+ const noteUnrecognised = (candidate, text) => {
1220
+ if (contextLifecycleStage(candidate, text, binding) !== undefined)
1221
+ return;
1222
+ const mention = CONTEXT_LIFECYCLE_MENTION.exec(text);
1223
+ if (mention)
1224
+ unrecognisedMentions.push({ command: text, stage: mention[1].toLowerCase() });
1225
+ };
1103
1226
  if (stage === "discovery") {
1104
- if (classifyRecordedSchemaDiscovery([entry]).kind === "successful") {
1227
+ if (classifyRecordedSchemaDiscovery([entry], binding).kind === "successful") {
1105
1228
  stage = "validate";
1106
1229
  continue;
1107
1230
  }
1108
- if (contextLifecycleStage(command) !== undefined) {
1109
- outOfOrderObserved ??= `successful \`${command}\` ran before successful schema discovery`;
1231
+ if (contextLifecycleStage(entry, command, binding) !== undefined) {
1232
+ outOfOrderObserved ??= `successful ${quoteCommand(command)} ran before successful schema discovery`;
1110
1233
  }
1234
+ noteUnrecognised(entry, command);
1111
1235
  continue;
1112
1236
  }
1113
- const nativeStage = contextLifecycleStage(command);
1237
+ const nativeStage = contextLifecycleStage(entry, command, binding);
1238
+ if (nativeStage === undefined)
1239
+ noteUnrecognised(entry, command);
1114
1240
  if (stage === "validate") {
1115
1241
  if (nativeStage === "validate") {
1116
1242
  stage = "build";
1117
1243
  }
1118
1244
  else if (nativeStage === "build") {
1119
- outOfOrderObserved ??= `successful \`${command}\` ran before successful \`wren context validate\``;
1245
+ outOfOrderObserved ??= `successful ${quoteCommand(command)} ran before successful \`wren context validate\``;
1120
1246
  }
1121
1247
  continue;
1122
1248
  }
1123
1249
  if (nativeStage === "build")
1124
1250
  completed = true;
1125
1251
  }
1126
- return { stage, completed, outOfOrderObserved };
1252
+ return { stage, completed, outOfOrderObserved, unrecognisedMentions };
1127
1253
  }
1128
1254
  /**
1129
1255
  * Returns the greatest ordered prefix proved by the supplied successful tool
@@ -1131,26 +1257,122 @@ function scanRecordedContextLifecycle(worklog, prior) {
1131
1257
  * call in the same attempt is out of order, so a corrective attempt can ask
1132
1258
  * only for the unproven suffix.
1133
1259
  */
1134
- export function recordedContextLifecyclePrefix(worklog, prior = "none") {
1135
- const scan = scanRecordedContextLifecycle(worklog, prior);
1260
+ export function recordedContextLifecyclePrefix(worklog, prior = "none", binding) {
1261
+ const scan = scanRecordedContextLifecycle(worklog, prior, binding);
1136
1262
  return scan.completed ? "build" : prefixFor(scan.stage);
1137
1263
  }
1138
- export function classifyRecordedContextLifecycle(worklog, prior = "none") {
1139
- const { stage, completed, outOfOrderObserved } = scanRecordedContextLifecycle(worklog, prior);
1264
+ export function classifyRecordedContextLifecycle(worklog, prior = "none", binding) {
1265
+ const { stage, completed, outOfOrderObserved, unrecognisedMentions } = scanRecordedContextLifecycle(worklog, prior, binding);
1266
+ const wanted = stage === "validate" ? "validate" : "build";
1267
+ const unrecognised = (unrecognisedMentions.find((mention) => mention.stage === wanted) ?? unrecognisedMentions[0])?.command;
1140
1268
  if (completed)
1141
1269
  return { kind: "successful" };
1270
+ const withUnrecognised = unrecognised !== undefined ? { unrecognised } : {};
1142
1271
  if (outOfOrderObserved !== undefined)
1143
- return { kind: "out_of_order", observed: outOfOrderObserved };
1272
+ return { kind: "out_of_order", observed: outOfOrderObserved, ...withUnrecognised };
1144
1273
  if (stage === "discovery")
1145
1274
  return { kind: "missing_discovery" };
1146
- if (stage === "validate")
1147
- return { kind: "validate_missing", observed: "no successful `wren context validate` followed successful schema discovery" };
1148
- return { kind: "build_missing", observed: "no successful `wren context build` followed successful `wren context validate`" };
1275
+ if (stage === "validate") {
1276
+ return { kind: "validate_missing", observed: "no recognised successful `wren context validate` followed successful schema discovery", ...withUnrecognised };
1277
+ }
1278
+ return { kind: "build_missing", observed: "no recognised successful `wren context build` followed successful `wren context validate`", ...withUnrecognised };
1149
1279
  }
1150
1280
  /** A narrow terminal-message check: only override a context error that explicitly claims a zero-table/model outcome. */
1151
1281
  function claimsNoTablesOrModels(message) {
1152
1282
  return /\b(?:no|zero)\s+(?:tables?|models?)\b|\b0\s+(?:tables?|models?)\b/i.test(message);
1153
1283
  }
1284
+ const ENV_ASSIGNMENT_VALUE = /(^|[\s;&|(])([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|'[^']*'|[^\s;&|]*)/g;
1285
+ /** Masks the value of every `NAME=value` assignment (leading, after `env`, or after `&&`/`;`). */
1286
+ function redactEnvAssignments(command) {
1287
+ return command.replace(ENV_ASSIGNMENT_VALUE, "$1$2=***");
1288
+ }
1289
+ /** Quotes a command for an error message: env values redacted first, then bounded so a long script cannot flood it. */
1290
+ function quoteCommand(command) {
1291
+ const flat = redactEnvAssignments(command).replace(/\s+/g, " ").trim();
1292
+ return `\`${flat.length > 300 ? `${flat.slice(0, 300)}...` : flat}\``;
1293
+ }
1294
+ const UNRECOGNISED_LIFECYCLE_HINT = "It exited successfully but is not a recognised lifecycle invocation: only the bare command (optionally with environment assignments or `--path .`) or a single `cd <project directory> &&` prefix naming this step's project directory is accepted. Run it again as the bare command and set the `setup_execution` `cwd` field to the project directory instead of shell chaining.";
1295
+ function projectBindingOf(context) {
1296
+ return { root: context.root, projectDir: path.join(context.root, context.name) };
1297
+ }
1298
+ /**
1299
+ * The host-held evidence gate for a successful `context` step: the recorded
1300
+ * discovery -> validate -> build lifecycle, then the built `target/mdl.json`.
1301
+ * Returns the failure, or `undefined` when the evidence supports success. It
1302
+ * never looks at the agent's own status line.
1303
+ */
1304
+ function verifyContextEvidence(context) {
1305
+ if (context.worklog !== undefined) {
1306
+ const lifecycle = classifyRecordedContextLifecycle(context.worklog, context.priorContextLifecycle, projectBindingOf(context));
1307
+ if (lifecycle.kind === "missing_discovery") {
1308
+ return hostContractFailure("context_schema_discovery_missing", "The agent never completed recognized schema discovery in its recorded worklog, so its model/build result cannot be accepted. This is an agent-workflow failure, not evidence that the connection or data source lacks tables.", "the recorded worklog has no successful recognized schema-discovery command", [
1309
+ "run a recognized schema-discovery command against the connected source and make sure it exits successfully",
1310
+ "build target/mdl.json only from that discovered schema, with at least one model",
1311
+ ], "no_successful_schema_discovery");
1312
+ }
1313
+ const discovery = classifyRecordedSchemaDiscovery(context.worklog, projectBindingOf(context));
1314
+ if (discovery.kind === "failed") {
1315
+ const exitCodeSuffix = discovery.exitCode !== undefined ? ` with exit code ${discovery.exitCode}` : "";
1316
+ return hostContractFailure("context_schema_discovery_failed", `${quoteCommand(discovery.command)} failed${exitCodeSuffix} during schema discovery — a model/build result cannot be accepted until that command/tool failure is resolved; this is not evidence that the connection or data source lacks tables.`, discovery.exitCode !== undefined
1317
+ ? `the recorded schema-discovery command exited with code ${discovery.exitCode}`
1318
+ : "the recorded schema-discovery command failed", [
1319
+ "resolve the schema-discovery command failure and run a recognized discovery command successfully",
1320
+ "build target/mdl.json only from that discovered schema, with at least one model",
1321
+ ]);
1322
+ }
1323
+ if (lifecycle.kind === "validate_missing") {
1324
+ return hostContractFailure("context_validate_missing", lifecycle.unrecognised !== undefined
1325
+ ? `The agent ran ${quoteCommand(lifecycle.unrecognised)} successfully, but it was not recognised as \`wren context validate\` after schema discovery. ${UNRECOGNISED_LIFECYCLE_HINT}`
1326
+ : "The agent did not successfully run `wren context validate` after recognized schema discovery, so its model/build result cannot be accepted.", lifecycle.unrecognised !== undefined
1327
+ ? `${lifecycle.observed}; unrecognised successful command: ${quoteCommand(lifecycle.unrecognised)}`
1328
+ : lifecycle.observed, [
1329
+ "run a recognized schema-discovery command successfully",
1330
+ "run `wren context validate` successfully before `wren context build`",
1331
+ "build target/mdl.json only after validation, with at least one model",
1332
+ ...(lifecycle.unrecognised !== undefined
1333
+ ? ["pass the project directory through the `setup_execution` `cwd` field rather than `cd`/shell chaining"]
1334
+ : []),
1335
+ ]);
1336
+ }
1337
+ if (lifecycle.kind === "build_missing") {
1338
+ return hostContractFailure("context_build_missing", lifecycle.unrecognised !== undefined
1339
+ ? `The agent ran ${quoteCommand(lifecycle.unrecognised)} successfully, but it was not recognised as \`wren context build\` after \`wren context validate\`. ${UNRECOGNISED_LIFECYCLE_HINT}`
1340
+ : "The agent did not successfully run `wren context build` after `wren context validate`, so its claimed context success cannot be accepted.", lifecycle.unrecognised !== undefined
1341
+ ? `${lifecycle.observed}; unrecognised successful command: ${quoteCommand(lifecycle.unrecognised)}`
1342
+ : lifecycle.observed, [
1343
+ "run a recognized schema-discovery command successfully",
1344
+ "run `wren context validate` successfully",
1345
+ "run `wren context build` successfully after validation and produce target/mdl.json with at least one model",
1346
+ ...(lifecycle.unrecognised !== undefined
1347
+ ? ["pass the project directory through the `setup_execution` `cwd` field rather than `cd`/shell chaining"]
1348
+ : []),
1349
+ ]);
1350
+ }
1351
+ if (lifecycle.kind === "out_of_order") {
1352
+ return hostContractFailure("context_lifecycle_out_of_order", lifecycle.unrecognised !== undefined
1353
+ ? `The native context lifecycle ran out of order; Setup requires successful schema discovery followed by \`wren context validate\` and then \`wren context build\`. The agent also ran ${quoteCommand(lifecycle.unrecognised)} successfully, but it was not recognised. ${UNRECOGNISED_LIFECYCLE_HINT}`
1354
+ : "The native context lifecycle ran out of order; Setup requires successful schema discovery followed by `wren context validate` and then `wren context build`.", lifecycle.unrecognised !== undefined
1355
+ ? `${lifecycle.observed}; unrecognised successful command: ${quoteCommand(lifecycle.unrecognised)}`
1356
+ : lifecycle.observed, [
1357
+ "run a recognized schema-discovery command successfully",
1358
+ "run `wren context validate` successfully before `wren context build`",
1359
+ "produce target/mdl.json with at least one schema-derived model",
1360
+ ...(lifecycle.unrecognised !== undefined
1361
+ ? ["pass the project directory through the `setup_execution` `cwd` field rather than `cd`/shell chaining"]
1362
+ : []),
1363
+ ]);
1364
+ }
1365
+ }
1366
+ const mdlPath = path.join(context.root, context.name, "target", "mdl.json");
1367
+ if (!existsSync(mdlPath)) {
1368
+ return hostContractFailure("context_mdl_missing", `the setup agent reported "ok" but ${mdlPath} does not exist — treating this as a failed context build`, "target/mdl.json is missing after the claimed context build", ["target/mdl.json must exist", "target/mdl.json must contain at least one model"]);
1369
+ }
1370
+ const modelCount = countMdlModels(mdlPath);
1371
+ if (modelCount < 1) {
1372
+ return hostContractFailure("context_mdl_empty", `the setup agent reported "ok" but ${mdlPath} has ${modelCount} models — treating this as a failed context build`, `target/mdl.json has ${modelCount} models after the claimed context build`, ["target/mdl.json must contain at least one model"]);
1373
+ }
1374
+ return undefined;
1375
+ }
1154
1376
  /**
1155
1377
  * Parses the LAST `SETUP_STATUS: ok|needs_input|error` line out of a setup
1156
1378
  * turn's finalText (the terminal contract lives in the composed
@@ -1193,6 +1415,12 @@ export function parseSetupTerminal(finalText, context) {
1193
1415
  }
1194
1416
  }
1195
1417
  if (!matched) {
1418
+ // For the context step the status line is advisory: recorded execution
1419
+ // evidence plus the built project decide success. Anything short of that
1420
+ // keeps the missing-status failure (and its bounded correction turn).
1421
+ if (context.stepKey === "context" && context.worklog !== undefined && verifyContextEvidence(context) === undefined) {
1422
+ return { status: "ok", message: defaultMessageFor("ok") };
1423
+ }
1196
1424
  return {
1197
1425
  status: "error",
1198
1426
  message: "the setup agent's final message did not contain a SETUP_STATUS line",
@@ -1201,54 +1429,9 @@ export function parseSetupTerminal(finalText, context) {
1201
1429
  }
1202
1430
  if (matched.status === "ok") {
1203
1431
  if (context.stepKey === "context") {
1204
- if (context.worklog !== undefined) {
1205
- const lifecycle = classifyRecordedContextLifecycle(context.worklog, context.priorContextLifecycle);
1206
- if (lifecycle.kind === "missing_discovery") {
1207
- return hostContractFailure("context_schema_discovery_missing", "The agent never completed recognized schema discovery in its recorded worklog, so its model/build result cannot be accepted. This is an agent-workflow failure, not evidence that the connection or data source lacks tables.", "the recorded worklog has no successful recognized schema-discovery command", [
1208
- "run a recognized schema-discovery command against the connected source and make sure it exits successfully",
1209
- "build target/mdl.json only from that discovered schema, with at least one model",
1210
- ], "no_successful_schema_discovery");
1211
- }
1212
- const discovery = classifyRecordedSchemaDiscovery(context.worklog);
1213
- if (discovery.kind === "failed") {
1214
- const exitCodeSuffix = discovery.exitCode !== undefined ? ` with exit code ${discovery.exitCode}` : "";
1215
- return hostContractFailure("context_schema_discovery_failed", `\`${discovery.command}\` failed${exitCodeSuffix} during schema discovery — a model/build result cannot be accepted until that command/tool failure is resolved; this is not evidence that the connection or data source lacks tables.`, discovery.exitCode !== undefined
1216
- ? `the recorded schema-discovery command exited with code ${discovery.exitCode}`
1217
- : "the recorded schema-discovery command failed", [
1218
- "resolve the schema-discovery command failure and run a recognized discovery command successfully",
1219
- "build target/mdl.json only from that discovered schema, with at least one model",
1220
- ]);
1221
- }
1222
- if (lifecycle.kind === "validate_missing") {
1223
- return hostContractFailure("context_validate_missing", "The agent did not successfully run `wren context validate` after recognized schema discovery, so its model/build result cannot be accepted.", lifecycle.observed, [
1224
- "run a recognized schema-discovery command successfully",
1225
- "run `wren context validate` successfully before `wren context build`",
1226
- "build target/mdl.json only after validation, with at least one model",
1227
- ]);
1228
- }
1229
- if (lifecycle.kind === "build_missing") {
1230
- return hostContractFailure("context_build_missing", "The agent did not successfully run `wren context build` after `wren context validate`, so its claimed context success cannot be accepted.", lifecycle.observed, [
1231
- "run a recognized schema-discovery command successfully",
1232
- "run `wren context validate` successfully",
1233
- "run `wren context build` successfully after validation and produce target/mdl.json with at least one model",
1234
- ]);
1235
- }
1236
- if (lifecycle.kind === "out_of_order") {
1237
- return hostContractFailure("context_lifecycle_out_of_order", "The native context lifecycle ran out of order; Setup requires successful schema discovery followed by `wren context validate` and then `wren context build`.", lifecycle.observed, [
1238
- "run a recognized schema-discovery command successfully",
1239
- "run `wren context validate` successfully before `wren context build`",
1240
- "produce target/mdl.json with at least one schema-derived model",
1241
- ]);
1242
- }
1243
- }
1244
- const mdlPath = path.join(context.root, context.name, "target", "mdl.json");
1245
- if (!existsSync(mdlPath)) {
1246
- return hostContractFailure("context_mdl_missing", `the setup agent reported "ok" but ${mdlPath} does not exist — treating this as a failed context build`, "target/mdl.json is missing after the claimed context build", ["target/mdl.json must exist", "target/mdl.json must contain at least one model"]);
1247
- }
1248
- const modelCount = countMdlModels(mdlPath);
1249
- if (modelCount < 1) {
1250
- return hostContractFailure("context_mdl_empty", `the setup agent reported "ok" but ${mdlPath} has ${modelCount} models — treating this as a failed context build`, `target/mdl.json has ${modelCount} models after the claimed context build`, ["target/mdl.json must contain at least one model"]);
1251
- }
1432
+ const failure = verifyContextEvidence(context);
1433
+ if (failure !== undefined)
1434
+ return failure;
1252
1435
  }
1253
1436
  else {
1254
1437
  const markerName = context.stepKey === "connect_resume" ? ".wren-validated" : "wren_project.yml";
@@ -1295,14 +1478,14 @@ export function parseSetupTerminal(finalText, context) {
1295
1478
  // `setup_execution` call that exited non-zero, and reframe if one is found.
1296
1479
  const failedExec = firstFailedExec(context.worklog);
1297
1480
  if (failedExec !== undefined) {
1298
- const command = failedExec.command !== undefined ? `\`${failedExec.command}\`` : "a command";
1481
+ const command = failedExec.command !== undefined ? quoteCommand(failedExec.command) : "a command";
1299
1482
  const exitCodeSuffix = failedExec.exitCode !== undefined ? ` with exit code ${failedExec.exitCode}` : "";
1300
1483
  return {
1301
1484
  status: "error",
1302
1485
  message: `${command} failed${exitCodeSuffix} during this step — a command in this step's own history failed, so this step's error framing can't be trusted until that's ruled out; this is a command/tool failure, not by itself evidence that the connection or data source lacks tables. The agent's own report was: "${matched.message}"`,
1303
1486
  };
1304
1487
  }
1305
- if (claimsNoTablesOrModels(matched.message) && classifyRecordedSchemaDiscovery(context.worklog).kind === "none") {
1488
+ if (claimsNoTablesOrModels(matched.message) && classifyRecordedSchemaDiscovery(context.worklog, projectBindingOf(context)).kind === "none") {
1306
1489
  return {
1307
1490
  status: "error",
1308
1491
  failureKind: "no_successful_schema_discovery",
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Reads a top-level `field: value` scalar from a `wren_project.yml`-shaped
3
+ * document and returns the YAML *value*, not the raw line remainder.
4
+ *
5
+ * `wren context init` scaffolds lines such as
6
+ * `data_source: # not set yet — ...`, and hand-edited projects keep trailing
7
+ * comments (`data_source: duckdb # change to your datasource type`). A raw
8
+ * remainder would hand that comment to callers as part of the value. This
9
+ * stays a single-purpose reader (no YAML dependency, matching the rest of
10
+ * `server/`); it only understands what a plain scalar line can contain:
11
+ *
12
+ * - an unquoted value ends at the first ` #` (a `#` preceded by whitespace);
13
+ * - a quoted value ends at its closing quote, so `"a#b"` stays `a#b` and
14
+ * anything after the closing quote is a comment;
15
+ * - a blank value, or one that is only a comment, reads as `""` (unset).
16
+ *
17
+ * Double-quoted escapes are NOT unescaped (`"a\"b"` yields `a\"b`): only plain
18
+ * identifiers (data source types, profile names) are ever read through here.
19
+ * A leading BOM is ignored.
20
+ *
21
+ * Returns `undefined` only when the field's key line is absent.
22
+ */
23
+ export declare function readYamlScalarField(content: string, field: string): string | undefined;