zelari-code 2.2.0 → 2.4.0

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 (35) hide show
  1. package/dist/cli/gauntlet/blind.js +38 -0
  2. package/dist/cli/gauntlet/blind.js.map +1 -0
  3. package/dist/cli/gauntlet/complete.js +90 -0
  4. package/dist/cli/gauntlet/complete.js.map +1 -0
  5. package/dist/cli/gauntlet/decompose.js +163 -0
  6. package/dist/cli/gauntlet/decompose.js.map +1 -0
  7. package/dist/cli/gauntlet/events.js +30 -0
  8. package/dist/cli/gauntlet/events.js.map +1 -0
  9. package/dist/cli/gauntlet/loop.js +142 -0
  10. package/dist/cli/gauntlet/loop.js.map +1 -0
  11. package/dist/cli/gauntlet/policy.js +61 -0
  12. package/dist/cli/gauntlet/policy.js.map +1 -0
  13. package/dist/cli/gauntlet/prompts.js +70 -0
  14. package/dist/cli/gauntlet/prompts.js.map +1 -0
  15. package/dist/cli/gauntlet/run.js +213 -0
  16. package/dist/cli/gauntlet/run.js.map +1 -0
  17. package/dist/cli/gauntlet/schedule.js +34 -0
  18. package/dist/cli/gauntlet/schedule.js.map +1 -0
  19. package/dist/cli/gauntlet/verdict.js +58 -0
  20. package/dist/cli/gauntlet/verdict.js.map +1 -0
  21. package/dist/cli/headless.js +11 -0
  22. package/dist/cli/headless.js.map +1 -1
  23. package/dist/cli/kraken/executor.js +9 -8
  24. package/dist/cli/kraken/executor.js.map +1 -1
  25. package/dist/cli/kraken/tentacle.js +1 -1
  26. package/dist/cli/kraken/tentacle.js.map +1 -1
  27. package/dist/cli/main.bundled.js +1469 -238
  28. package/dist/cli/main.bundled.js.map +4 -4
  29. package/dist/cli/runHeadless.js +18 -0
  30. package/dist/cli/runHeadless.js.map +1 -1
  31. package/dist/cli/toolRegistry.js +13 -6
  32. package/dist/cli/toolRegistry.js.map +1 -1
  33. package/dist/cli/tools/taskTool.js +88 -72
  34. package/dist/cli/tools/taskTool.js.map +1 -1
  35. package/package.json +2 -2
@@ -595,8 +595,8 @@ async function runGrokOAuthFlow(options = {}) {
595
595
  );
596
596
  } catch {
597
597
  }
598
- const timeoutMs = options.timeoutMs ?? DEFAULT_OAUTH_TIMEOUT_MS;
599
- const effectiveTimeout = Math.min(timeoutMs, deviceAuth.expiresIn * 1e3);
598
+ const timeoutMs2 = options.timeoutMs ?? DEFAULT_OAUTH_TIMEOUT_MS;
599
+ const effectiveTimeout = Math.min(timeoutMs2, deviceAuth.expiresIn * 1e3);
600
600
  return pollForDeviceToken({
601
601
  clientId: clientId2,
602
602
  deviceCode: deviceAuth.deviceCode,
@@ -739,7 +739,7 @@ function generateOAuthState() {
739
739
  }
740
740
  async function waitForLoopbackCallback(options) {
741
741
  const host = options.host ?? "127.0.0.1";
742
- const timeoutMs = options.timeoutMs ?? 3e5;
742
+ const timeoutMs2 = options.timeoutMs ?? 3e5;
743
743
  const expectedPath = options.path.startsWith("/") ? options.path : `/${options.path}`;
744
744
  return new Promise((resolve3, reject) => {
745
745
  let settled = false;
@@ -759,12 +759,12 @@ async function waitForLoopbackCallback(options) {
759
759
  const ok = Boolean(result.code) && !result.error;
760
760
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
761
761
  res.end(oauthResultHtml(ok, result.errorDescription ?? result.error));
762
- finish(result);
762
+ finish2(result);
763
763
  });
764
764
  const timer = setTimeout(() => {
765
- finish(void 0, new Error(`Timed out waiting for OAuth callback on http://${host}:${options.port}`));
766
- }, timeoutMs);
767
- const finish = (value, err) => {
765
+ finish2(void 0, new Error(`Timed out waiting for OAuth callback on http://${host}:${options.port}`));
766
+ }, timeoutMs2);
767
+ const finish2 = (value, err) => {
768
768
  if (settled) return;
769
769
  settled = true;
770
770
  clearTimeout(timer);
@@ -773,7 +773,7 @@ async function waitForLoopbackCallback(options) {
773
773
  else resolve3(value ?? {});
774
774
  });
775
775
  };
776
- server.on("error", (err) => finish(void 0, err));
776
+ server.on("error", (err) => finish2(void 0, err));
777
777
  server.listen(options.port, host);
778
778
  });
779
779
  }
@@ -971,8 +971,8 @@ async function pollChatgptDeviceAuth(options) {
971
971
  const clientId2 = options.clientId || process.env.CHATGPT_OAUTH_CLIENT_ID || DEFAULT_CHATGPT_CLIENT_ID;
972
972
  const fetchImpl = options.fetchImpl ?? fetch;
973
973
  const sleep = options.sleepImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
974
- const timeoutMs = options.timeoutMs ?? 3e5;
975
- const deadline = Date.now() + timeoutMs;
974
+ const timeoutMs2 = options.timeoutMs ?? 3e5;
975
+ const deadline = Date.now() + timeoutMs2;
976
976
  let interval = options.interval ?? 5;
977
977
  let authorizationCode;
978
978
  let codeVerifier;
@@ -18246,14 +18246,15 @@ var init_filesystem = __esm({
18246
18246
  init_toolTypes();
18247
18247
  ReadFileArgsSchema = external_exports.object({
18248
18248
  path: external_exports.string().min(1),
18249
- startLine: external_exports.number().int().nonnegative().optional(),
18250
- endLine: external_exports.number().int().positive().optional(),
18249
+ startLine: external_exports.number().int().nonnegative().optional().describe("0-based first line to include. Range is applied to the full file before maxBytes."),
18250
+ endLine: external_exports.number().int().positive().optional().describe("Exclusive 0-based end line. Omit to read through EOF (still capped by maxBytes)."),
18251
18251
  maxBytes: external_exports.number().int().positive().max(1e7).default(1e6)
18252
18252
  });
18253
18253
  readFileTool = {
18254
18254
  name: "read_file",
18255
- description: "Read a file with optional line range. Returns content + metadata. Use before edit_file.",
18255
+ description: "Read a file with optional 0-based line range (endLine exclusive). maxBytes caps the selected range, not a prefix of the file. Returns content + metadata. Use before edit_file.",
18256
18256
  permissions: ["read"],
18257
+ sideEffect: "none",
18257
18258
  timeoutMs: 5e3,
18258
18259
  inputSchema: ReadFileArgsSchema,
18259
18260
  execute: async (args, ctx) => {
@@ -18262,24 +18263,31 @@ var init_filesystem = __esm({
18262
18263
  const buf = await fs4.readFile(absPath, { encoding: "utf-8", signal: ctx.signal });
18263
18264
  const content = typeof buf === "string" ? buf : buf.toString("utf-8");
18264
18265
  const allLines = content.split("\n");
18265
- const truncated = content.length > args.maxBytes ? content.slice(0, args.maxBytes) : content;
18266
- const lines = truncated === content ? allLines : truncated.split("\n");
18267
18266
  const totalLines = allLines.length;
18268
18267
  const start = args.startLine ?? 0;
18269
- const end = Math.min(args.endLine ?? lines.length, lines.length);
18270
- const wasTruncated = truncated !== content;
18268
+ const endExclusive = Math.min(args.endLine ?? totalLines, totalLines);
18271
18269
  const empty = content.length === 0;
18272
- const status = empty ? "empty" : wasTruncated ? "partial" : "complete";
18270
+ const rangeEmpty = !empty && (start >= totalLines || endExclusive <= start);
18271
+ const selected = rangeEmpty ? [] : allLines.slice(start, endExclusive);
18272
+ let text = selected.join("\n");
18273
+ const wasTruncated = text.length > args.maxBytes;
18274
+ if (wasTruncated)
18275
+ text = text.slice(0, args.maxBytes);
18276
+ const returnedLines = text.length === 0 ? 0 : text.split("\n").length;
18277
+ const readEnd = returnedLines === 0 ? Math.max(0, start - 1) : start + returnedLines - 1;
18278
+ const status = empty || rangeEmpty ? "empty" : wasTruncated ? "partial" : "complete";
18273
18279
  const warnings = [];
18274
18280
  if (empty)
18275
18281
  warnings.push("EMPTY_FILE");
18282
+ if (rangeEmpty)
18283
+ warnings.push("LINE_RANGE_EMPTY");
18276
18284
  if (wasTruncated)
18277
18285
  warnings.push("MAX_BYTES_TRUNCATED");
18278
18286
  return typedOk({
18279
18287
  path: absPath,
18280
- content: lines.slice(start, end).join("\n"),
18288
+ content: text,
18281
18289
  totalLines,
18282
- readLines: { start, end: end - 1 },
18290
+ readLines: { start, end: readEnd },
18283
18291
  sizeBytes: content.length
18284
18292
  }, {
18285
18293
  status,
@@ -18305,6 +18313,7 @@ var init_filesystem = __esm({
18305
18313
  name: "write_file",
18306
18314
  description: "Write or create a file. Use createDirs=true to auto-create parent directories.",
18307
18315
  permissions: ["write"],
18316
+ sideEffect: "local",
18308
18317
  timeoutMs: 1e4,
18309
18318
  inputSchema: WriteFileArgsSchema,
18310
18319
  execute: async (args, ctx) => {
@@ -18330,6 +18339,7 @@ var init_filesystem = __esm({
18330
18339
  name: "edit_file",
18331
18340
  description: "Replace exact string match in a file. Idempotent: returns 0 occurrences if no match.",
18332
18341
  permissions: ["write"],
18342
+ sideEffect: "local",
18333
18343
  timeoutMs: 1e4,
18334
18344
  inputSchema: EditFileArgsSchema,
18335
18345
  execute: async (args, ctx) => {
@@ -18561,6 +18571,7 @@ var init_shell = __esm({
18561
18571
  name: "bash",
18562
18572
  description: "Run a shell command. On Windows uses Git Bash when available (POSIX semantics: ls, $VAR, && work); falls back to cmd.exe otherwise. Streams stdout/stderr. Respects timeout and cancellation. Returns exit code. stdin is CLOSED (non-interactive): any command that prompts for input will fail or be cancelled \u2014 always pass non-interactive flags (--yes, -y, --template), and if a scaffolder insists on prompting (e.g. create-vite in a non-empty directory), create the files manually with write_file instead.",
18563
18573
  permissions: ["execute"],
18574
+ sideEffect: "local",
18564
18575
  timeoutMs: 6e4,
18565
18576
  inputSchema: BashArgsSchema,
18566
18577
  execute: async (args, ctx) => {
@@ -18870,6 +18881,7 @@ var init_search = __esm({
18870
18881
  name: "grep_content",
18871
18882
  description: 'Regex search for content in a file OR recursively in a directory. When path is a directory, include/exclude globs filter which files are searched (default: all files, excluding node_modules/dist/.git/etc.). Glob semantics: "*" matches ONE path segment only ("*.ts" does NOT match "sub/file.ts"); use "**" for recursive matching ("**/*.ts" matches at any depth). include/exclude accept a single glob string (e.g. "*.ts") OR an array of globs. Returns matches with line numbers and context, plus filesWalked/filesInTree counts and a warning when the include globs matched suspiciously few files.',
18872
18883
  permissions: ["read"],
18884
+ sideEffect: "none",
18873
18885
  timeoutMs: 3e4,
18874
18886
  inputSchema: GrepContentArgsSchema,
18875
18887
  execute: async (args, ctx) => {
@@ -18987,6 +18999,7 @@ var init_listFiles = __esm({
18987
18999
  name: "list_files",
18988
19000
  description: "List files and directories in the given path (defaults to the working directory). Returns names with types (file/directory). Use this to discover the project structure before reading specific files. Supports a maxDepth for recursive listing and excludes common dependency/build directories by default.",
18989
19001
  permissions: ["read"],
19002
+ sideEffect: "none",
18990
19003
  timeoutMs: 15e3,
18991
19004
  inputSchema: ListFilesArgsSchema,
18992
19005
  execute: async (args, ctx) => {
@@ -19241,6 +19254,7 @@ var init_diff = __esm({
19241
19254
  name: "show_diff",
19242
19255
  description: "Show unified diff between current file content and proposed content. Read-only \u2014 does NOT modify the file. Use this to preview edits before applying with edit_file or apply_diff.",
19243
19256
  permissions: ["read"],
19257
+ sideEffect: "none",
19244
19258
  timeoutMs: 15e3,
19245
19259
  inputSchema: ShowDiffArgsSchema,
19246
19260
  execute: async (args, ctx) => {
@@ -19343,6 +19357,7 @@ var init_diff = __esm({
19343
19357
  name: "apply_diff",
19344
19358
  description: "Apply a unified diff patch to a file. Parses ---/+++/@@ headers and applies each hunk sequentially. With fuzzyMatch=true, tolerates whitespace differences. With dryRun=true, returns the final content without writing. Atomic: if any hunk fails, no partial write occurs.",
19345
19359
  permissions: ["write"],
19360
+ sideEffect: "local",
19346
19361
  timeoutMs: 15e3,
19347
19362
  inputSchema: ApplyDiffArgsSchema,
19348
19363
  execute: async (args, ctx) => {
@@ -19404,9 +19419,9 @@ function assertHttpUrl(raw) {
19404
19419
  }
19405
19420
  return url2;
19406
19421
  }
19407
- async function fetchWithTimeout(url2, init, outerSignal, timeoutMs) {
19422
+ async function fetchWithTimeout(url2, init, outerSignal, timeoutMs2) {
19408
19423
  const ctrl = new AbortController();
19409
- const timer = setTimeout(() => ctrl.abort(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
19424
+ const timer = setTimeout(() => ctrl.abort(new Error(`timeout after ${timeoutMs2}ms`)), timeoutMs2);
19410
19425
  const onOuterAbort = () => ctrl.abort(outerSignal?.reason);
19411
19426
  outerSignal?.addEventListener("abort", onOuterAbort, { once: true });
19412
19427
  try {
@@ -21471,6 +21486,42 @@ var init_types = __esm({
21471
21486
  }
21472
21487
  });
21473
21488
 
21489
+ // packages/core/dist/core/tools/concurrency.js
21490
+ function taskAgent(args) {
21491
+ if (!args || typeof args !== "object")
21492
+ return "explore";
21493
+ const agent = args.agent;
21494
+ return typeof agent === "string" && agent.trim() ? agent : "explore";
21495
+ }
21496
+ function classifyToolConcurrency(input) {
21497
+ const env = input.env ?? process.env;
21498
+ if (env.ZELARI_PARALLEL_TOOLS === "0")
21499
+ return "exclusive";
21500
+ const name = input.toolName;
21501
+ if (name === "task") {
21502
+ return taskAgent(input.args) === "general" ? "exclusive" : "parallel-safe";
21503
+ }
21504
+ if (input.registered === false) {
21505
+ if (name.startsWith("mcp_")) {
21506
+ const lower = name.toLowerCase();
21507
+ if (lower.includes("write") || lower.includes("edit") || lower.includes("delete")) {
21508
+ return "exclusive";
21509
+ }
21510
+ return "parallel-safe";
21511
+ }
21512
+ return "exclusive";
21513
+ }
21514
+ const perms = input.permissions ?? [];
21515
+ if (perms.includes("write") || perms.includes("execute"))
21516
+ return "exclusive";
21517
+ return "parallel-safe";
21518
+ }
21519
+ var init_concurrency = __esm({
21520
+ "packages/core/dist/core/tools/concurrency.js"() {
21521
+ "use strict";
21522
+ }
21523
+ });
21524
+
21474
21525
  // packages/core/dist/core/contextGrowth.js
21475
21526
  function emptyContextGrowthStats() {
21476
21527
  return {
@@ -22022,6 +22073,7 @@ var init_AgentHarness = __esm({
22022
22073
  "packages/core/dist/core/AgentHarness.js"() {
22023
22074
  "use strict";
22024
22075
  init_events();
22076
+ init_concurrency();
22025
22077
  init_toolTypes();
22026
22078
  init_contextGrowth();
22027
22079
  init_requestSnapshot();
@@ -22106,30 +22158,19 @@ var init_AgentHarness = __esm({
22106
22158
  };
22107
22159
  }
22108
22160
  /**
22109
- * v1.8.0: true when a tool may run concurrently with other parallel-safe
22110
- * tools. Write/execute tools stay serial (preserve file/order safety).
22111
- * `task` is parallel-safe (read-only sub-agents). Opt out: ZELARI_PARALLEL_TOOLS=0.
22161
+ * v1.8.0 / 2.x F: true when a tool may run concurrently with other
22162
+ * parallel-safe tools. Write/execute stay serial. `task agent=general`
22163
+ * is exclusive (it writes); explore/verify tentacles stay parallel-safe.
22164
+ * Opt out: ZELARI_PARALLEL_TOOLS=0.
22112
22165
  */
22113
- isParallelSafeTool(toolName) {
22114
- if (process.env.ZELARI_PARALLEL_TOOLS === "0")
22115
- return false;
22116
- if (toolName === "task")
22117
- return true;
22166
+ isParallelSafeTool(toolName, args) {
22118
22167
  const def = this.config.toolRegistry?.get(toolName);
22119
- if (!def) {
22120
- if (toolName.startsWith("mcp_")) {
22121
- const lower = toolName.toLowerCase();
22122
- if (lower.includes("write") || lower.includes("edit") || lower.includes("delete")) {
22123
- return false;
22124
- }
22125
- return true;
22126
- }
22127
- return false;
22128
- }
22129
- const perms = def.permissions ?? [];
22130
- if (perms.includes("write") || perms.includes("execute"))
22131
- return false;
22132
- return true;
22168
+ return classifyToolConcurrency({
22169
+ toolName,
22170
+ args,
22171
+ permissions: def?.permissions,
22172
+ registered: Boolean(def) || toolName === "task"
22173
+ }) === "parallel-safe";
22133
22174
  }
22134
22175
  /**
22135
22176
  * Execute buffered native tool calls: consecutive parallel-safe tools run
@@ -22234,13 +22275,13 @@ ${shared2.content}`,
22234
22275
  let i = 0;
22235
22276
  while (i < pending.length) {
22236
22277
  const p3 = pending[i];
22237
- if (p3.skipped || p3.cached !== void 0 || !this.isParallelSafeTool(p3.toolName)) {
22278
+ if (p3.skipped || p3.cached !== void 0 || !this.isParallelSafeTool(p3.toolName, p3.args)) {
22238
22279
  out[i] = toOut(p3, await invokeOne(p3));
22239
22280
  i += 1;
22240
22281
  continue;
22241
22282
  }
22242
22283
  let j = i;
22243
- while (j < pending.length && !pending[j].skipped && pending[j].cached === void 0 && this.isParallelSafeTool(pending[j].toolName)) {
22284
+ while (j < pending.length && !pending[j].skipped && pending[j].cached === void 0 && this.isParallelSafeTool(pending[j].toolName, pending[j].args)) {
22244
22285
  j += 1;
22245
22286
  }
22246
22287
  for (let off = i; off < j; off += maxParallel) {
@@ -23286,8 +23327,8 @@ var init_lifecycleHookRunner = __esm({
23286
23327
  /** Execute a single hook, swallowing every failure into an allow. */
23287
23328
  async runHookSafely(hook, payload) {
23288
23329
  try {
23289
- const timeoutMs = hook.timeoutMs ?? this.defaultTimeoutMs;
23290
- const raw = hook.url ? await this.postHttp(hook.url, payload, timeoutMs) : await this.execCommand(hook.command ?? "", payload, timeoutMs, hook.cwd);
23330
+ const timeoutMs2 = hook.timeoutMs ?? this.defaultTimeoutMs;
23331
+ const raw = hook.url ? await this.postHttp(hook.url, payload, timeoutMs2) : await this.execCommand(hook.command ?? "", payload, timeoutMs2, hook.cwd);
23291
23332
  const decision = parseJson(raw);
23292
23333
  if (!decision) {
23293
23334
  this.logger(`hook "${hook.name}" returned invalid JSON (fail-open): ${raw.slice(0, 200)}`);
@@ -23304,7 +23345,7 @@ var init_lifecycleHookRunner = __esm({
23304
23345
  }
23305
23346
  }
23306
23347
  /** Spawn `command`, write JSON payload to stdin, read stdout until close. */
23307
- execCommand(command, payload, timeoutMs, cwd) {
23348
+ execCommand(command, payload, timeoutMs2, cwd) {
23308
23349
  return new Promise((resolve3, reject) => {
23309
23350
  const child = spawn2(command, {
23310
23351
  shell: true,
@@ -23316,8 +23357,8 @@ var init_lifecycleHookRunner = __esm({
23316
23357
  let stderr = "";
23317
23358
  const timer = setTimeout(() => {
23318
23359
  child.kill("SIGKILL");
23319
- reject(new Error(`timed out after ${timeoutMs}ms`));
23320
- }, timeoutMs);
23360
+ reject(new Error(`timed out after ${timeoutMs2}ms`));
23361
+ }, timeoutMs2);
23321
23362
  child.stdout?.on("data", (d) => {
23322
23363
  stdout += d.toString();
23323
23364
  });
@@ -23343,9 +23384,9 @@ var init_lifecycleHookRunner = __esm({
23343
23384
  });
23344
23385
  }
23345
23386
  /** POST JSON payload; resolve with the response body text. */
23346
- async postHttp(url2, payload, timeoutMs) {
23387
+ async postHttp(url2, payload, timeoutMs2) {
23347
23388
  const ctrl = new AbortController();
23348
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
23389
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs2);
23349
23390
  try {
23350
23391
  const res = await fetch(url2, {
23351
23392
  method: "POST",
@@ -27921,7 +27962,7 @@ function deepFreeze(value) {
27921
27962
  return Object.freeze(value);
27922
27963
  }
27923
27964
  async function runInSandbox(opts) {
27924
- const { bundleCode, sdk, timeoutMs } = opts;
27965
+ const { bundleCode, sdk, timeoutMs: timeoutMs2 } = opts;
27925
27966
  if (!opts.skipFootgunScan) {
27926
27967
  const footguns = scanForFootguns(bundleCode);
27927
27968
  if (footguns.length > 0) {
@@ -27941,7 +27982,7 @@ async function runInSandbox(opts) {
27941
27982
  });
27942
27983
  const start = Date.now();
27943
27984
  const vmOpts = {
27944
- timeout: Math.max(1, timeoutMs),
27985
+ timeout: Math.max(1, timeoutMs2),
27945
27986
  displayErrors: true,
27946
27987
  breakOnSigint: true
27947
27988
  };
@@ -27951,7 +27992,7 @@ async function runInSandbox(opts) {
27951
27992
  } catch (err) {
27952
27993
  const msg = err && typeof err === "object" && "message" in err ? String(err.message ?? "") : String(err);
27953
27994
  if (/timed out/i.test(msg)) {
27954
- throw new PlanError("budget_exceeded", `script exceeded ${timeoutMs}ms budget`);
27995
+ throw new PlanError("budget_exceeded", `script exceeded ${timeoutMs2}ms budget`);
27955
27996
  }
27956
27997
  if (opts.signal?.aborted) {
27957
27998
  throw new PlanError("cancelled", "script aborted by host");
@@ -28263,6 +28304,8 @@ var init_types8 = __esm({
28263
28304
  "assistant.message",
28264
28305
  "tool.call",
28265
28306
  "tool.result",
28307
+ // 2.x B (crash-safe recovery): dangling call classified. State-only.
28308
+ "tool.interrupted",
28266
28309
  "context.injected",
28267
28310
  "session.compacted",
28268
28311
  "task.created",
@@ -28515,6 +28558,82 @@ var init_writer = __esm({
28515
28558
  }
28516
28559
  });
28517
28560
 
28561
+ // packages/core/dist/session/recovery.js
28562
+ function sideEffectForTool(tool, declared) {
28563
+ if (declared)
28564
+ return declared;
28565
+ if (EXTERNAL.has(tool))
28566
+ return "external";
28567
+ if (READ_ONLY.has(tool))
28568
+ return "none";
28569
+ return "local";
28570
+ }
28571
+ function retrySafetyForSideEffect(sideEffect) {
28572
+ return sideEffect === "none" ? "safe" : "inspect-first";
28573
+ }
28574
+ function toolNameOf(call) {
28575
+ return typeof call.data.tool === "string" && call.data.tool.trim() ? call.data.tool : "unknown";
28576
+ }
28577
+ function callIdOf(call) {
28578
+ return typeof call.data.callId === "string" ? call.data.callId : `seq:${call.seq}`;
28579
+ }
28580
+ function classifyInterruptedTools(events, sideEffects = /* @__PURE__ */ new Map()) {
28581
+ const alreadyClassified = new Set(events.filter((e) => e.kind === "tool.interrupted").map((e) => typeof e.data.callId === "string" ? e.data.callId : void 0).filter((id) => Boolean(id)));
28582
+ const out = [];
28583
+ for (const pair of pairToolCalls(events)) {
28584
+ if (pair.result)
28585
+ continue;
28586
+ const callId = callIdOf(pair.call);
28587
+ if (alreadyClassified.has(callId))
28588
+ continue;
28589
+ const tool = toolNameOf(pair.call);
28590
+ const sideEffect = sideEffectForTool(tool, sideEffects.get(tool));
28591
+ const retrySafety = retrySafetyForSideEffect(sideEffect);
28592
+ out.push({
28593
+ toolCallSeq: pair.call.seq,
28594
+ callId,
28595
+ tool,
28596
+ state: retrySafety === "safe" ? "not-started" : "started-outcome-unknown",
28597
+ retrySafety,
28598
+ sideEffect
28599
+ });
28600
+ }
28601
+ return out;
28602
+ }
28603
+ function interruptedEventData(item) {
28604
+ return {
28605
+ toolCallSeq: item.toolCallSeq,
28606
+ callId: item.callId,
28607
+ tool: item.tool,
28608
+ state: item.state,
28609
+ retrySafety: item.retrySafety,
28610
+ sideEffect: item.sideEffect
28611
+ };
28612
+ }
28613
+ var READ_ONLY, EXTERNAL;
28614
+ var init_recovery = __esm({
28615
+ "packages/core/dist/session/recovery.js"() {
28616
+ "use strict";
28617
+ init_modelSurface();
28618
+ READ_ONLY = /* @__PURE__ */ new Set([
28619
+ "read_file",
28620
+ "list_files",
28621
+ "grep_content",
28622
+ "show_diff",
28623
+ "web_search",
28624
+ "fetch_url",
28625
+ "lsp_definition",
28626
+ "lsp_references",
28627
+ "lsp_hover",
28628
+ "lsp_symbols",
28629
+ "ast_outline",
28630
+ "ast_find_symbol",
28631
+ "semantic_search"
28632
+ ]);
28633
+ EXTERNAL = /* @__PURE__ */ new Set(["fetch_url", "web_search"]);
28634
+ }
28635
+ });
28636
+
28518
28637
  // packages/core/dist/session/replay.js
28519
28638
  import { promises as fs10 } from "node:fs";
28520
28639
  async function readSessionLog(filePath) {
@@ -28599,7 +28718,8 @@ function buildProjection(events, issues = []) {
28599
28718
  missionPhases: [],
28600
28719
  missionAdvice: [],
28601
28720
  replans: 0,
28602
- issues
28721
+ issues,
28722
+ interruptedTools: classifyInterruptedTools(events)
28603
28723
  };
28604
28724
  for (const e of events) {
28605
28725
  switch (e.kind) {
@@ -28650,6 +28770,7 @@ var init_replay = __esm({
28650
28770
  "use strict";
28651
28771
  init_types8();
28652
28772
  init_modelSurface();
28773
+ init_recovery();
28653
28774
  }
28654
28775
  });
28655
28776
 
@@ -28789,12 +28910,20 @@ async function forkSession(store6, parentSessionId, options = {}) {
28789
28910
  }
28790
28911
  async function resumeSession(store6, sessionId2) {
28791
28912
  const opened = await store6.open(sessionId2);
28913
+ const classified = [];
28914
+ for (const item of classifyInterruptedTools(opened.report.events)) {
28915
+ classified.push(await opened.writer.append({
28916
+ kind: "tool.interrupted",
28917
+ actor: ACTOR_SYSTEM,
28918
+ data: interruptedEventData(item)
28919
+ }));
28920
+ }
28792
28921
  const resumed = await opened.writer.append({
28793
28922
  kind: "session.resumed",
28794
28923
  actor: ACTOR_SYSTEM,
28795
28924
  data: { fromSeq: opened.projection.lastSeq }
28796
28925
  });
28797
- const projection = buildProjection([...opened.report.events, resumed]);
28926
+ const projection = buildProjection([...opened.report.events, ...classified, resumed]);
28798
28927
  return { writer: opened.writer, projection };
28799
28928
  }
28800
28929
  async function lineageOf(store6, sessionId2) {
@@ -28822,6 +28951,7 @@ var init_lineage = __esm({
28822
28951
  "use strict";
28823
28952
  init_types8();
28824
28953
  init_replay();
28954
+ init_recovery();
28825
28955
  }
28826
28956
  });
28827
28957
 
@@ -28862,6 +28992,104 @@ var init_exportSession = __esm({
28862
28992
  }
28863
28993
  });
28864
28994
 
28995
+ // packages/core/dist/session/invariants.js
28996
+ function asSeq(value) {
28997
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
28998
+ }
28999
+ function collectEvidenceSeqs(events) {
29000
+ const out = [];
29001
+ const walk2 = (ownerSeq, value) => {
29002
+ if (Array.isArray(value)) {
29003
+ for (const item of value)
29004
+ walk2(ownerSeq, item);
29005
+ return;
29006
+ }
29007
+ if (!value || typeof value !== "object")
29008
+ return;
29009
+ const rec = value;
29010
+ const seq = asSeq(rec.seq);
29011
+ if (seq !== void 0 && ("digest" in rec || "tier" in rec || "ref" in rec)) {
29012
+ out.push({ ownerSeq, refSeq: seq });
29013
+ }
29014
+ for (const v of Object.values(rec))
29015
+ walk2(ownerSeq, v);
29016
+ };
29017
+ for (const e of events)
29018
+ walk2(e.seq, e.data);
29019
+ return out;
29020
+ }
29021
+ function validateSessionTrace(events, mode = "minimal") {
29022
+ const violations = [];
29023
+ let expected = 1;
29024
+ for (const e of events) {
29025
+ if (e.seq !== expected) {
29026
+ violations.push({
29027
+ code: "SEQ_NOT_MONOTONIC",
29028
+ seq: e.seq,
29029
+ message: `expected seq ${expected}, got ${e.seq}`
29030
+ });
29031
+ }
29032
+ expected = e.seq + 1;
29033
+ }
29034
+ const pairs = pairToolCalls(events);
29035
+ const resultCounts = /* @__PURE__ */ new Map();
29036
+ for (const e of events) {
29037
+ if (e.kind !== "tool.result")
29038
+ continue;
29039
+ const callId = typeof e.data.callId === "string" ? e.data.callId : `seq:${e.seq}`;
29040
+ resultCounts.set(callId, (resultCounts.get(callId) ?? 0) + 1);
29041
+ }
29042
+ for (const [callId, n] of resultCounts) {
29043
+ if (n > 1) {
29044
+ violations.push({
29045
+ code: "DUPLICATE_TOOL_RESULT",
29046
+ message: `callId ${callId} has ${n} tool.result events`
29047
+ });
29048
+ }
29049
+ }
29050
+ const interruptedCallIds = new Set(events.filter((e) => e.kind === "tool.interrupted").map((e) => typeof e.data.callId === "string" ? e.data.callId : void 0).filter((id) => Boolean(id)));
29051
+ for (const pair of pairs) {
29052
+ if (pair.result)
29053
+ continue;
29054
+ const callId = typeof pair.call.data.callId === "string" ? pair.call.data.callId : `seq:${pair.call.seq}`;
29055
+ if (interruptedCallIds.has(callId))
29056
+ continue;
29057
+ if (mode === "strict") {
29058
+ violations.push({
29059
+ code: "DANGLING_TOOL_CALL",
29060
+ seq: pair.call.seq,
29061
+ message: `tool.call ${callId} has no tool.result and no tool.interrupted`
29062
+ });
29063
+ }
29064
+ }
29065
+ const knownSeq = new Set(events.map((e) => e.seq));
29066
+ for (const ref of collectEvidenceSeqs(events)) {
29067
+ if (!knownSeq.has(ref.refSeq)) {
29068
+ violations.push({
29069
+ code: "EVIDENCE_SEQ_MISSING",
29070
+ seq: ref.ownerSeq,
29071
+ message: `EvidenceRef.seq ${ref.refSeq} is not an event in this trace`
29072
+ });
29073
+ }
29074
+ }
29075
+ const firstVerification = events.find((e) => e.kind === "verification.run");
29076
+ const firstEnded = events.find((e) => e.kind === "session.ended");
29077
+ if (firstVerification && firstEnded && firstEnded.seq < firstVerification.seq) {
29078
+ violations.push({
29079
+ code: "COMPLETION_BEFORE_VERIFICATION",
29080
+ seq: firstEnded.seq,
29081
+ message: `session.ended seq ${firstEnded.seq} precedes verification.run seq ${firstVerification.seq}`
29082
+ });
29083
+ }
29084
+ return violations;
29085
+ }
29086
+ var init_invariants = __esm({
29087
+ "packages/core/dist/session/invariants.js"() {
29088
+ "use strict";
29089
+ init_modelSurface();
29090
+ }
29091
+ });
29092
+
28865
29093
  // packages/core/dist/session/index.js
28866
29094
  var init_session = __esm({
28867
29095
  "packages/core/dist/session/index.js"() {
@@ -28874,6 +29102,8 @@ var init_session = __esm({
28874
29102
  init_store();
28875
29103
  init_lineage();
28876
29104
  init_exportSession();
29105
+ init_invariants();
29106
+ init_recovery();
28877
29107
  }
28878
29108
  });
28879
29109
 
@@ -29007,7 +29237,7 @@ var init_nodeProviders = __esm({
29007
29237
  async exec(command, options = {}) {
29008
29238
  const started = Date.now();
29009
29239
  const cwd = this.workspace.resolve(options.cwd ?? ".");
29010
- const timeoutMs = options.timeoutMs ?? this.defaults.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
29240
+ const timeoutMs2 = options.timeoutMs ?? this.defaults.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
29011
29241
  const maxChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
29012
29242
  const isWindows = process.platform === "win32";
29013
29243
  const file2 = isWindows ? "cmd.exe" : "/bin/sh";
@@ -29030,7 +29260,7 @@ var init_nodeProviders = __esm({
29030
29260
  const timer = setTimeout(() => {
29031
29261
  timedOut = true;
29032
29262
  killTree(child);
29033
- }, timeoutMs);
29263
+ }, timeoutMs2);
29034
29264
  child.stdout?.on("data", (d) => {
29035
29265
  stdout = append(stdout, d.toString("utf-8"));
29036
29266
  });
@@ -29486,6 +29716,72 @@ var init_types9 = __esm({
29486
29716
  }
29487
29717
  });
29488
29718
 
29719
+ // packages/core/dist/verification/scopeDiscipline.js
29720
+ function normalize(p3) {
29721
+ return p3.replace(/\\/g, "/").replace(/^\.\//, "");
29722
+ }
29723
+ function isGeneratedPath(file2) {
29724
+ const n = normalize(file2);
29725
+ return GENERATED_NAME.test(n) || GENERATED_DIR.test(n);
29726
+ }
29727
+ function parseNameOnlyDiff(stdout) {
29728
+ return stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map(normalize);
29729
+ }
29730
+ function analyzeScope(input) {
29731
+ const changed = input.changedFiles.map(normalize);
29732
+ if (changed.length === 0) {
29733
+ return {
29734
+ status: "unknown",
29735
+ expected: [],
29736
+ unexpected: [],
29737
+ generated: [],
29738
+ reasons: ["no changed files observed"]
29739
+ };
29740
+ }
29741
+ const expectedSet = new Set((input.expectedFiles ?? []).map(normalize));
29742
+ const generatedExtra = new Set((input.generatedFiles ?? []).map(normalize));
29743
+ const generated = [];
29744
+ const unexpected = [];
29745
+ const expectedHit = [];
29746
+ for (const file2 of changed) {
29747
+ if (generatedExtra.has(file2) || isGeneratedPath(file2)) {
29748
+ generated.push(file2);
29749
+ continue;
29750
+ }
29751
+ if (expectedSet.size === 0) {
29752
+ expectedHit.push(file2);
29753
+ continue;
29754
+ }
29755
+ if (expectedSet.has(file2))
29756
+ expectedHit.push(file2);
29757
+ else
29758
+ unexpected.push(file2);
29759
+ }
29760
+ const reasons = [];
29761
+ if (unexpected.length > 0) {
29762
+ reasons.push(`${unexpected.length} file(s) outside expected scope: ${unexpected.join(", ")}`);
29763
+ }
29764
+ if (generated.length > 0) {
29765
+ reasons.push(`${generated.length} generated/lockfile path(s) ignored for the concern`);
29766
+ }
29767
+ let status = "pass";
29768
+ if (expectedSet.size === 0) {
29769
+ status = "unknown";
29770
+ reasons.push("no expected-files allowlist \u2014 cannot judge scope");
29771
+ } else if (unexpected.length > 0) {
29772
+ status = "concern";
29773
+ }
29774
+ return { status, expected: expectedHit, unexpected, generated, reasons };
29775
+ }
29776
+ var GENERATED_NAME, GENERATED_DIR;
29777
+ var init_scopeDiscipline = __esm({
29778
+ "packages/core/dist/verification/scopeDiscipline.js"() {
29779
+ "use strict";
29780
+ GENERATED_NAME = /(?:^|\/)(?:package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb|Cargo\.lock|go\.sum|poetry\.lock|composer\.lock)$/i;
29781
+ GENERATED_DIR = /(?:^|\/)(?:dist|build|out|coverage|\.next|node_modules)\//;
29782
+ }
29783
+ });
29784
+
29489
29785
  // packages/core/dist/verification/engine.js
29490
29786
  import { createHash as createHash4 } from "node:crypto";
29491
29787
  function defaultSha256(input) {
@@ -29498,6 +29794,7 @@ var VerificationEngine;
29498
29794
  var init_engine = __esm({
29499
29795
  "packages/core/dist/verification/engine.js"() {
29500
29796
  "use strict";
29797
+ init_scopeDiscipline();
29501
29798
  VerificationEngine = class {
29502
29799
  services;
29503
29800
  options;
@@ -29509,7 +29806,7 @@ var init_engine = __esm({
29509
29806
  async evaluate(criteria, context = {}) {
29510
29807
  const results = [];
29511
29808
  for (const criterion of criteria) {
29512
- results.push(await this.evaluateOne(criterion));
29809
+ results.push(await this.evaluateOne(criterion, context.scope));
29513
29810
  }
29514
29811
  if (this.options.emit) {
29515
29812
  try {
@@ -29559,7 +29856,7 @@ var init_engine = __esm({
29559
29856
  return void 0;
29560
29857
  }
29561
29858
  }
29562
- async evaluateOne(criterion) {
29859
+ async evaluateOne(criterion, scope) {
29563
29860
  const started = this.options.now?.() ?? Date.now();
29564
29861
  const base = {
29565
29862
  criterionId: criterion.id,
@@ -29571,6 +29868,15 @@ var init_engine = __esm({
29571
29868
  ...patch,
29572
29869
  durationMs: Math.max(0, (this.options.now?.() ?? Date.now()) - started)
29573
29870
  });
29871
+ if (criterion.id === "quality.scope-discipline" && scope) {
29872
+ const analysis = analyzeScope(scope);
29873
+ const status = analysis.status === "pass" ? "pass" : "unknown";
29874
+ return done({
29875
+ status,
29876
+ evidence: [],
29877
+ detail: analysis.reasons.join("; ") || analysis.status
29878
+ });
29879
+ }
29574
29880
  const check2 = criterion.check;
29575
29881
  if (!check2 || check2.kind === "none") {
29576
29882
  return done({
@@ -29892,9 +30198,9 @@ var init_sessionEvidence = __esm({
29892
30198
 
29893
30199
  // packages/core/dist/verification/criteriaPack.v1.js
29894
30200
  function codingCriteriaPack(options = {}) {
29895
- const timeoutMs = options.commandTimeoutMs ?? 6e5;
30201
+ const timeoutMs2 = options.commandTimeoutMs ?? 6e5;
29896
30202
  const withDefault = (o, d) => o === void 0 ? d : o;
29897
- const command = (command2) => command2 ? { kind: "command", command: command2, timeoutMs } : void 0;
30203
+ const command = (command2) => command2 ? { kind: "command", command: command2, timeoutMs: timeoutMs2 } : void 0;
29898
30204
  const criteria = [
29899
30205
  {
29900
30206
  id: "correctness.error-signals",
@@ -30184,6 +30490,7 @@ var init_verification2 = __esm({
30184
30490
  init_criteriaPack_v1();
30185
30491
  init_metrics();
30186
30492
  init_verifier();
30493
+ init_scopeDiscipline();
30187
30494
  }
30188
30495
  });
30189
30496
 
@@ -30448,6 +30755,7 @@ __export(dist_exports, {
30448
30755
  WorkspacePathEscapeError: () => WorkspacePathEscapeError,
30449
30756
  WorktreeWorkspace: () => WorktreeWorkspace,
30450
30757
  ZELARI_CODING_PACK_ID: () => ZELARI_CODING_PACK_ID,
30758
+ analyzeScope: () => analyzeScope,
30451
30759
  applyCompletionRetry: () => applyCompletionRetry,
30452
30760
  applyDeterministicAutofix: () => applyDeterministicAutofix,
30453
30761
  applyImplementationWriteRetry: () => applyImplementationWriteRetry,
@@ -30478,6 +30786,7 @@ __export(dist_exports, {
30478
30786
  checkImplementationDelivery: () => checkImplementationDelivery,
30479
30787
  checkMemberToolEmissionSets: () => checkMemberToolEmissionSets,
30480
30788
  checkMemberToolEmissions: () => checkMemberToolEmissions,
30789
+ classifyInterruptedTools: () => classifyInterruptedTools,
30481
30790
  classifyMission: () => classifyMission,
30482
30791
  classifyTaskScope: () => classifyTaskScope,
30483
30792
  cleanAgentContent: () => cleanAgentContent,
@@ -30545,6 +30854,7 @@ __export(dist_exports, {
30545
30854
  hasInteractiveClarification: () => hasInteractiveClarification,
30546
30855
  hashToolCall: () => hashToolCall,
30547
30856
  hookMatches: () => hookMatches,
30857
+ interruptedEventData: () => interruptedEventData,
30548
30858
  isAnswerLeak: () => isAnswerLeak,
30549
30859
  isBrainAgentEndEvent: () => isBrainAgentEndEvent,
30550
30860
  isBrainAgentStartEvent: () => isBrainAgentStartEvent,
@@ -30568,6 +30878,7 @@ __export(dist_exports, {
30568
30878
  isConverged: () => isConverged,
30569
30879
  isEventBackedEvidence: () => isEventBackedEvidence,
30570
30880
  isExperimentalEnabled: () => isExperimentalEnabled,
30881
+ isGeneratedPath: () => isGeneratedPath,
30571
30882
  isModelSurfaceEvent: () => isModelSurfaceEvent,
30572
30883
  isReviewerKind: () => isReviewerKind,
30573
30884
  isSettled: () => isSettled,
@@ -30594,6 +30905,7 @@ __export(dist_exports, {
30594
30905
  parseClarificationRequest: () => parseClarificationRequest,
30595
30906
  parseEvidenceTier: () => parseEvidenceTier,
30596
30907
  parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
30908
+ parseNameOnlyDiff: () => parseNameOnlyDiff,
30597
30909
  parsePersonaVerdict: () => parsePersonaVerdict,
30598
30910
  parseProjectRootFromWorkspaceContext: () => parseProjectRootFromWorkspaceContext,
30599
30911
  parseTextToolCalls: () => parseTextToolCalls,
@@ -30631,6 +30943,7 @@ __export(dist_exports, {
30631
30943
  resolveVerifyRetryTool: () => resolveVerifyRetryTool,
30632
30944
  restrictImplementationWrites: () => restrictImplementationWrites,
30633
30945
  resumeSession: () => resumeSession,
30946
+ retrySafetyForSideEffect: () => retrySafetyForSideEffect,
30634
30947
  runChairmanDeliveryLoop: () => runChairmanDeliveryLoop,
30635
30948
  runChairmanFixLoop: () => runChairmanFixLoop,
30636
30949
  runChairmanMicroGate: () => runChairmanMicroGate,
@@ -30647,6 +30960,7 @@ __export(dist_exports, {
30647
30960
  setWorkspaceStubs: () => setWorkspaceStubs,
30648
30961
  sha256Hex: () => sha256Hex,
30649
30962
  shouldRetryMember: () => shouldRetryMember,
30963
+ sideEffectForTool: () => sideEffectForTool,
30650
30964
  slugify: () => slugify2,
30651
30965
  snapshotToCompletionEvaluation: () => snapshotToCompletionEvaluation,
30652
30966
  specificityFromAssumptions: () => specificityFromAssumptions,
@@ -30666,6 +30980,7 @@ __export(dist_exports, {
30666
30980
  utf8Bytes: () => utf8Bytes,
30667
30981
  validateCodingSkillRequires: () => validateCodingSkillRequires,
30668
30982
  validateGraph: () => validateGraph,
30983
+ validateSessionTrace: () => validateSessionTrace,
30669
30984
  verificationCostRatio: () => verificationCostRatio,
30670
30985
  verifiedSolveRate: () => verifiedSolveRate,
30671
30986
  verifyCitations: () => verifyCitations,
@@ -32966,10 +33281,26 @@ var init_registry2 = __esm({
32966
33281
  if (!parsed.success) {
32967
33282
  return typedErr(`Invalid input: ${parsed.error.message}`);
32968
33283
  }
32969
- const timeoutMs = options.timeoutMs ?? tool.timeoutMs ?? 3e4;
32970
- const signal = options.signal;
33284
+ const timeoutMs2 = options.timeoutMs ?? tool.timeoutMs ?? 3e4;
33285
+ const parentSignal = options.signal;
33286
+ const controller = new AbortController();
33287
+ let timer;
33288
+ let rejectRace;
33289
+ const onParentAbort = () => {
33290
+ if (timer !== void 0)
33291
+ clearTimeout(timer);
33292
+ if (!controller.signal.aborted)
33293
+ controller.abort();
33294
+ rejectRace?.(new Error(`Tool "${name}" aborted`));
33295
+ };
33296
+ if (parentSignal) {
33297
+ if (parentSignal.aborted)
33298
+ onParentAbort();
33299
+ else
33300
+ parentSignal.addEventListener("abort", onParentAbort);
33301
+ }
32971
33302
  const ctx = {
32972
- signal: signal ?? new AbortController().signal,
33303
+ signal: controller.signal,
32973
33304
  cwd: options.cwd ?? process.cwd(),
32974
33305
  audit: () => {
32975
33306
  },
@@ -32982,6 +33313,7 @@ var init_registry2 = __esm({
32982
33313
  cwd: ctx.cwd
32983
33314
  });
32984
33315
  if (!pre.ok) {
33316
+ parentSignal?.removeEventListener("abort", onParentAbort);
32985
33317
  return typedErr(`[hook:${pre.hookName ?? "unknown"}] ${pre.reason ?? "denied"}`);
32986
33318
  }
32987
33319
  } catch (hookErr) {
@@ -32991,11 +33323,16 @@ var init_registry2 = __esm({
32991
33323
  const result = await Promise.race([
32992
33324
  tool.execute(parsed.data, ctx),
32993
33325
  new Promise((_, reject) => {
32994
- const timer = setTimeout(() => reject(new Error(`Tool "${name}" timed out after ${timeoutMs}ms`)), timeoutMs);
32995
- signal?.addEventListener("abort", () => {
32996
- clearTimeout(timer);
32997
- reject(new Error(`Tool "${name}" aborted`));
32998
- });
33326
+ rejectRace = reject;
33327
+ if (parentSignal?.aborted) {
33328
+ onParentAbort();
33329
+ return;
33330
+ }
33331
+ timer = setTimeout(() => {
33332
+ if (!controller.signal.aborted)
33333
+ controller.abort();
33334
+ reject(new Error(`Tool "${name}" timed out after ${timeoutMs2}ms`));
33335
+ }, timeoutMs2);
32999
33336
  })
33000
33337
  ]);
33001
33338
  if (result.ok) {
@@ -33030,6 +33367,11 @@ var init_registry2 = __esm({
33030
33367
  } catch (err) {
33031
33368
  const error51 = err instanceof Error ? err.message : String(err);
33032
33369
  return typedErr(error51);
33370
+ } finally {
33371
+ rejectRace = void 0;
33372
+ if (timer !== void 0)
33373
+ clearTimeout(timer);
33374
+ parentSignal?.removeEventListener("abort", onParentAbort);
33033
33375
  }
33034
33376
  }
33035
33377
  /** Return all tool definitions in OpenAI function-calling format (memoized). */
@@ -33336,11 +33678,11 @@ async function runDiagnosticsForFile(file2, options = {}) {
33336
33678
  const provider = providerForFile(file2, options.providers);
33337
33679
  if (!provider) return [];
33338
33680
  const cwd = options.cwd ?? process.cwd();
33339
- const timeoutMs = options.timeoutMs ?? 5e3;
33681
+ const timeoutMs2 = options.timeoutMs ?? 5e3;
33340
33682
  const runner = options.runner ?? defaultRunner;
33341
33683
  try {
33342
33684
  const bin = options.runner ? provider.bin : resolveBin(provider.bin, cwd);
33343
- const result = await runner(bin, provider.args(file2), { cwd, timeoutMs });
33685
+ const result = await runner(bin, provider.args(file2), { cwd, timeoutMs: timeoutMs2 });
33344
33686
  return provider.parse(result.stdout, file2);
33345
33687
  } catch {
33346
33688
  return [];
@@ -33829,7 +34171,7 @@ var init_candidateRegistry = __esm({
33829
34171
  });
33830
34172
 
33831
34173
  // src/cli/kraken/verifyReport.ts
33832
- function normalize(text) {
34174
+ function normalize2(text) {
33833
34175
  return text.toLowerCase().replace(/\s+/g, " ").trim();
33834
34176
  }
33835
34177
  function extractVerifyReportBlocks(raw) {
@@ -33858,11 +34200,11 @@ function extractVerifyReportBlocks(raw) {
33858
34200
  function parseVerifyReport(raw, requiredChecks) {
33859
34201
  const byCriterion = /* @__PURE__ */ new Map();
33860
34202
  for (const block of extractVerifyReportBlocks(raw)) {
33861
- byCriterion.set(normalize(block.check), block);
34203
+ byCriterion.set(normalize2(block.check), block);
33862
34204
  }
33863
34205
  const keys = [...byCriterion.keys()];
33864
34206
  return requiredChecks.map((check2) => {
33865
- const norm = normalize(check2);
34207
+ const norm = normalize2(check2);
33866
34208
  let block = byCriterion.get(norm) ?? null;
33867
34209
  if (!block) {
33868
34210
  for (const key of keys) {
@@ -33901,18 +34243,18 @@ var init_verifyReport = __esm({
33901
34243
  });
33902
34244
 
33903
34245
  // src/cli/kraken/completionGate.ts
33904
- function normalize2(text) {
34246
+ function normalize3(text) {
33905
34247
  return text.toLowerCase().replace(/\s+/g, " ").trim();
33906
34248
  }
33907
34249
  function classifyKrakenChecks(requiredChecks, results) {
33908
34250
  const byCriterion = /* @__PURE__ */ new Map();
33909
34251
  for (const result of results ?? []) {
33910
- byCriterion.set(normalize2(result.check), result);
34252
+ byCriterion.set(normalize3(result.check), result);
33911
34253
  }
33912
34254
  const keys = [...byCriterion.keys()];
33913
34255
  const out = { passed: [], failed: [], unknown: [] };
33914
34256
  for (const check2 of requiredChecks) {
33915
- const norm = normalize2(check2);
34257
+ const norm = normalize3(check2);
33916
34258
  let result = byCriterion.get(norm) ?? null;
33917
34259
  if (!result) {
33918
34260
  for (const key of keys) {
@@ -34137,69 +34479,79 @@ async function runSubAgent(harness, opts = {}) {
34137
34479
  let usage;
34138
34480
  const pendingTools = /* @__PURE__ */ new Map();
34139
34481
  const toolTrace = [];
34140
- if (signal?.aborted) return { result: "", aborted: true };
34141
- for await (const ev of harness.run()) {
34142
- if (signal?.aborted) {
34143
- return {
34144
- result: (lastCompleted || current).trim(),
34145
- ...error51 ? { error: error51 } : {},
34146
- aborted: true,
34147
- ...toolTrace.length > 0 ? { toolTrace } : {}
34148
- };
34149
- }
34150
- if (ev.type === "tool_execution_start") {
34151
- pendingTools.set(ev.toolCallId, { tool: ev.toolName, command: toolCommandHint(ev.args) });
34152
- } else if (ev.type === "tool_execution_end") {
34153
- const started = pendingTools.get(ev.toolCallId);
34154
- pendingTools.delete(ev.toolCallId);
34155
- toolTrace.push({
34156
- tool: started?.tool ?? "unknown",
34157
- callId: ev.toolCallId,
34158
- ok: !ev.isError,
34159
- ...started?.command ? { command: started.command } : {},
34160
- output: String(ev.result ?? "").slice(0, TOOL_TRACE_OUTPUT_MAX),
34161
- durationMs: ev.durationMs,
34162
- endedAt: Date.now()
34163
- });
34164
- if (toolTrace.length > TOOL_TRACE_RING) {
34165
- toolTrace.splice(0, toolTrace.length - TOOL_TRACE_RING);
34482
+ const onAbort = () => harness.cancel?.();
34483
+ if (signal?.aborted) {
34484
+ onAbort();
34485
+ return { result: "", aborted: true };
34486
+ }
34487
+ signal?.addEventListener("abort", onAbort, { once: true });
34488
+ try {
34489
+ for await (const ev of harness.run()) {
34490
+ if (signal?.aborted) {
34491
+ onAbort();
34492
+ return {
34493
+ result: (lastCompleted || current).trim(),
34494
+ ...error51 ? { error: error51 } : {},
34495
+ aborted: true,
34496
+ ...toolTrace.length > 0 ? { toolTrace } : {}
34497
+ };
34166
34498
  }
34167
- }
34168
- switch (ev.type) {
34169
- case "message_start":
34170
- current = "";
34171
- break;
34172
- case "message_delta":
34173
- current += ev.delta;
34174
- break;
34175
- case "message_end":
34176
- if (current.trim()) lastCompleted = current;
34177
- if (ev.usage) {
34178
- usage = usage ? {
34179
- promptTokens: usage.promptTokens + ev.usage.promptTokens,
34180
- completionTokens: usage.completionTokens + ev.usage.completionTokens,
34181
- totalTokens: usage.totalTokens + ev.usage.totalTokens,
34182
- ...(usage.cachedPromptTokens ?? 0) + (ev.usage.cachedPromptTokens ?? 0) > 0 ? {
34183
- cachedPromptTokens: (usage.cachedPromptTokens ?? 0) + (ev.usage.cachedPromptTokens ?? 0)
34184
- } : {}
34185
- } : ev.usage;
34499
+ if (ev.type === "tool_execution_start") {
34500
+ pendingTools.set(ev.toolCallId, { tool: ev.toolName, command: toolCommandHint(ev.args) });
34501
+ } else if (ev.type === "tool_execution_end") {
34502
+ const started = pendingTools.get(ev.toolCallId);
34503
+ pendingTools.delete(ev.toolCallId);
34504
+ toolTrace.push({
34505
+ tool: started?.tool ?? "unknown",
34506
+ callId: ev.toolCallId,
34507
+ ok: !ev.isError,
34508
+ ...started?.command ? { command: started.command } : {},
34509
+ output: String(ev.result ?? "").slice(0, TOOL_TRACE_OUTPUT_MAX),
34510
+ durationMs: ev.durationMs,
34511
+ endedAt: Date.now()
34512
+ });
34513
+ if (toolTrace.length > TOOL_TRACE_RING) {
34514
+ toolTrace.splice(0, toolTrace.length - TOOL_TRACE_RING);
34186
34515
  }
34187
- current = "";
34188
- break;
34189
- case "error":
34190
- error51 = ev.message;
34191
- break;
34192
- default:
34193
- break;
34516
+ }
34517
+ switch (ev.type) {
34518
+ case "message_start":
34519
+ current = "";
34520
+ break;
34521
+ case "message_delta":
34522
+ current += ev.delta;
34523
+ break;
34524
+ case "message_end":
34525
+ if (current.trim()) lastCompleted = current;
34526
+ if (ev.usage) {
34527
+ usage = usage ? {
34528
+ promptTokens: usage.promptTokens + ev.usage.promptTokens,
34529
+ completionTokens: usage.completionTokens + ev.usage.completionTokens,
34530
+ totalTokens: usage.totalTokens + ev.usage.totalTokens,
34531
+ ...(usage.cachedPromptTokens ?? 0) + (ev.usage.cachedPromptTokens ?? 0) > 0 ? {
34532
+ cachedPromptTokens: (usage.cachedPromptTokens ?? 0) + (ev.usage.cachedPromptTokens ?? 0)
34533
+ } : {}
34534
+ } : ev.usage;
34535
+ }
34536
+ current = "";
34537
+ break;
34538
+ case "error":
34539
+ error51 = ev.message;
34540
+ break;
34541
+ default:
34542
+ break;
34543
+ }
34194
34544
  }
34545
+ const result = (lastCompleted || current).trim();
34546
+ return {
34547
+ result,
34548
+ ...error51 ? { error: error51 } : {},
34549
+ ...usage ? { usage } : {},
34550
+ ...toolTrace.length > 0 ? { toolTrace } : {}
34551
+ };
34552
+ } finally {
34553
+ signal?.removeEventListener("abort", onAbort);
34195
34554
  }
34196
- const result = (lastCompleted || current).trim();
34197
- return {
34198
- result,
34199
- ...error51 ? { error: error51 } : {},
34200
- ...usage ? { usage } : {},
34201
- ...toolTrace.length > 0 ? { toolTrace } : {}
34202
- };
34203
34555
  }
34204
34556
  async function runTentacle(opts) {
34205
34557
  const { deps, args, agent, thoroughness, parentCwd, sessionId: sessionId2 } = opts;
@@ -34429,7 +34781,7 @@ function createTaskTool(deps, policy = {}) {
34429
34781
  description: "Delegate a focused sub-task to an isolated sub-agent with its own context; returns only a concise conclusion (keeps parent context lean).\n- agent=explore (default): read-only research/search\n- agent=general: can edit files for one bounded unit of work\n- agent=verify: read + bash to run tests/checks\nProvide a fully self-contained `prompt` (sub-agent cannot see this conversation). Optional scope[] + acceptance[] contracts. After general, follow up with verify." + (restricted ? `
34430
34782
  RESTRICTED in this mode: only agent=${allowedAgents.join("|")} is allowed.` : ""),
34431
34783
  permissions: ["read", "network", "write", "execute"],
34432
- timeoutMs: 3e5,
34784
+ timeoutMs: TASK_TOOL_TIMEOUT_MS,
34433
34785
  inputSchema: inputSchema2,
34434
34786
  execute: async (args, ctx) => {
34435
34787
  const agent = args.agent ?? "explore";
@@ -34532,7 +34884,7 @@ ${res.result}${res.footer}`,
34532
34884
  }
34533
34885
  };
34534
34886
  }
34535
- var EXPLORE_PROMPT, GENERAL_PROMPT, VERIFY_PROMPT, TaskArgsSchema, TaskPurposeSchema, TaskArgsWithPurposeSchema, TOOL_TRACE_RING, TOOL_TRACE_OUTPUT_MAX;
34887
+ var TASK_TOOL_TIMEOUT_MS, EXPLORE_PROMPT, GENERAL_PROMPT, VERIFY_PROMPT, TaskArgsSchema, TaskPurposeSchema, TaskArgsWithPurposeSchema, TOOL_TRACE_RING, TOOL_TRACE_OUTPUT_MAX;
34536
34888
  var init_taskTool = __esm({
34537
34889
  "src/cli/tools/taskTool.ts"() {
34538
34890
  "use strict";
@@ -34544,6 +34896,7 @@ var init_taskTool = __esm({
34544
34896
  init_candidateRegistry();
34545
34897
  init_verifyReport();
34546
34898
  init_metrics3();
34899
+ TASK_TOOL_TIMEOUT_MS = 9e5;
34547
34900
  EXPLORE_PROMPT = [
34548
34901
  "You are a focused EXPLORE tentacle of Kraken (parent super-agent).",
34549
34902
  "READ-ONLY tools only (read, list, grep, fetch). No edits, no shell.",
@@ -34831,7 +35184,7 @@ function renderVerdict(verdict, candidateCount) {
34831
35184
  return lines.join("\n");
34832
35185
  }
34833
35186
  function createKrakenSelectTool(deps) {
34834
- const timeoutMs = deps.timeoutMs ?? 12e4;
35187
+ const timeoutMs2 = deps.timeoutMs ?? 12e4;
34835
35188
  return {
34836
35189
  name: "kraken_select",
34837
35190
  description: "Compare the candidate research reports spawned this turn (task purpose=candidate) and select the hypothesis best supported by OBSERVED EVIDENCE. A dedicated verifier call (default: the current model) judges grounded vs unsupported claims; degraded observations are never treated as proof of absence. Returns the selected candidate, a rationale, and requiredChecks the implementation must pass. Call it ONCE per turn, after candidate tentacles finished and BEFORE implementing. If it reports needs_more_evidence, either spawn ONE more differentiated candidate or proceed with your own judgment.",
@@ -34888,7 +35241,7 @@ function createKrakenSelectTool(deps) {
34888
35241
  model: id.model,
34889
35242
  provider: id.provider,
34890
35243
  tools: [],
34891
- signal: AbortSignal.timeout(timeoutMs)
35244
+ signal: AbortSignal.timeout(timeoutMs2)
34892
35245
  });
34893
35246
  judgingUsage = usage;
34894
35247
  if (raw.trim().length === 0) throw new Error("empty verifier response");
@@ -38439,7 +38792,7 @@ exec node "$(dirname "$0")/askpass.cjs"
38439
38792
  }
38440
38793
  return sh;
38441
38794
  }
38442
- function runSsh(target, remoteCommand, timeoutMs = 6e4) {
38795
+ function runSsh(target, remoteCommand, timeoutMs2 = 6e4) {
38443
38796
  return new Promise((resolve3) => {
38444
38797
  if (target.auth === "password" && !getSshPassword(target.id)) {
38445
38798
  resolve3({
@@ -38479,7 +38832,7 @@ function runSsh(target, remoteCommand, timeoutMs = 6e4) {
38479
38832
  stdout,
38480
38833
  stderr: stderr + "\n[ssh] timeout"
38481
38834
  });
38482
- }, timeoutMs);
38835
+ }, timeoutMs2);
38483
38836
  child.on("error", (err) => {
38484
38837
  clearTimeout(timer);
38485
38838
  resolve3({
@@ -38708,7 +39061,7 @@ async function readChecks(cwd) {
38708
39061
  return [];
38709
39062
  }
38710
39063
  }
38711
- function runShell(command, cwd, timeoutMs, signal) {
39064
+ function runShell(command, cwd, timeoutMs2, signal) {
38712
39065
  return new Promise((resolve3) => {
38713
39066
  const isWin = process.platform === "win32";
38714
39067
  const child = spawn10(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
@@ -38720,7 +39073,7 @@ function runShell(command, cwd, timeoutMs, signal) {
38720
39073
  let stdout = "";
38721
39074
  let stderr = "";
38722
39075
  let settled = false;
38723
- const finish = (exitCode) => {
39076
+ const finish2 = (exitCode) => {
38724
39077
  if (settled) return;
38725
39078
  settled = true;
38726
39079
  resolve3({ exitCode, stdout, stderr });
@@ -38730,8 +39083,8 @@ function runShell(command, cwd, timeoutMs, signal) {
38730
39083
  child.kill("SIGTERM");
38731
39084
  } catch {
38732
39085
  }
38733
- finish(124);
38734
- }, timeoutMs);
39086
+ finish2(124);
39087
+ }, timeoutMs2);
38735
39088
  if (signal) {
38736
39089
  if (signal.aborted) {
38737
39090
  try {
@@ -38739,7 +39092,7 @@ function runShell(command, cwd, timeoutMs, signal) {
38739
39092
  } catch {
38740
39093
  }
38741
39094
  clearTimeout(timer);
38742
- finish(130);
39095
+ finish2(130);
38743
39096
  return;
38744
39097
  }
38745
39098
  signal.addEventListener(
@@ -38750,7 +39103,7 @@ function runShell(command, cwd, timeoutMs, signal) {
38750
39103
  } catch {
38751
39104
  }
38752
39105
  clearTimeout(timer);
38753
- finish(130);
39106
+ finish2(130);
38754
39107
  },
38755
39108
  { once: true }
38756
39109
  );
@@ -38765,11 +39118,11 @@ function runShell(command, cwd, timeoutMs, signal) {
38765
39118
  });
38766
39119
  child.on("error", () => {
38767
39120
  clearTimeout(timer);
38768
- finish(1);
39121
+ finish2(1);
38769
39122
  });
38770
39123
  child.on("close", (code) => {
38771
39124
  clearTimeout(timer);
38772
- finish(code ?? 1);
39125
+ finish2(code ?? 1);
38773
39126
  });
38774
39127
  });
38775
39128
  }
@@ -38791,9 +39144,9 @@ async function runBacktest(cwd, signal) {
38791
39144
  const results = [];
38792
39145
  for (const c of checks) {
38793
39146
  const expectExit = c.expectExit ?? 0;
38794
- const timeoutMs = c.timeoutMs ?? 12e4;
39147
+ const timeoutMs2 = c.timeoutMs ?? 12e4;
38795
39148
  const start = Date.now();
38796
- const { exitCode, stdout, stderr } = await runShell(c.command, cwd, timeoutMs, signal);
39149
+ const { exitCode, stdout, stderr } = await runShell(c.command, cwd, timeoutMs2, signal);
38797
39150
  const combined = `${stdout}${stderr}`;
38798
39151
  const preview = combined.slice(0, 400);
38799
39152
  let ok = exitCode === expectExit;
@@ -39077,7 +39430,7 @@ import path38 from "node:path";
39077
39430
  function trustStorePath() {
39078
39431
  return _overrideStorePath ?? path38.join(homedir8(), ".zelari-code", "trust.json");
39079
39432
  }
39080
- function normalize3(p3) {
39433
+ function normalize4(p3) {
39081
39434
  const resolved = path38.resolve(p3);
39082
39435
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
39083
39436
  }
@@ -39114,14 +39467,14 @@ function isFolderTrusted(folderPath) {
39114
39467
  const env = envTrustedFolder();
39115
39468
  if (env === "all") return true;
39116
39469
  if (env === "none") return false;
39117
- if (env) return normalize3(env) === normalize3(folderPath);
39118
- const target = normalize3(folderPath);
39119
- return readStore3().folders.some((f) => normalize3(f.path) === target);
39470
+ if (env) return normalize4(env) === normalize4(folderPath);
39471
+ const target = normalize4(folderPath);
39472
+ return readStore3().folders.some((f) => normalize4(f.path) === target);
39120
39473
  }
39121
39474
  function trustFolder(folderPath) {
39122
39475
  const store6 = readStore3();
39123
39476
  const normalized = path38.resolve(folderPath);
39124
- if (!store6.folders.some((f) => normalize3(f.path) === normalize3(normalized))) {
39477
+ if (!store6.folders.some((f) => normalize4(f.path) === normalize4(normalized))) {
39125
39478
  store6.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
39126
39479
  writeStore3(store6);
39127
39480
  }
@@ -39129,9 +39482,9 @@ function trustFolder(folderPath) {
39129
39482
  }
39130
39483
  function untrustFolder(folderPath) {
39131
39484
  const store6 = readStore3();
39132
- const target = normalize3(folderPath);
39485
+ const target = normalize4(folderPath);
39133
39486
  const before = store6.folders.length;
39134
- store6.folders = store6.folders.filter((f) => normalize3(f.path) !== target);
39487
+ store6.folders = store6.folders.filter((f) => normalize4(f.path) !== target);
39135
39488
  if (store6.folders.length === before) return { ok: true, removed: false };
39136
39489
  writeStore3(store6);
39137
39490
  return { ok: true, removed: true };
@@ -39505,8 +39858,9 @@ function createBuiltinToolRegistry(options = {}) {
39505
39858
  if (hooks) registry4.setLifecycleHooks(hooks);
39506
39859
  const readOnly = options.readOnly === true || options.planMode === true || profile === "explore";
39507
39860
  const verifyMode = profile === "verify";
39508
- const allowMutators = !readOnly && !verifyMode;
39509
- const allowBash = allowMutators || verifyMode;
39861
+ const gauntletParent = options.gauntletParent === true;
39862
+ const allowMutators = !readOnly && !verifyMode && !gauntletParent;
39863
+ const allowBash = (allowMutators || verifyMode) && !gauntletParent;
39510
39864
  const permPolicy = options.permissionPolicy ?? defaultPermissionPolicy();
39511
39865
  const withPerm = (t) => wrapWithPermissions(t, permPolicy, options.onPermissionAsk);
39512
39866
  registry4.register(withPerm(safeReadFile));
@@ -39539,21 +39893,21 @@ function createBuiltinToolRegistry(options = {}) {
39539
39893
  if (allowBash) {
39540
39894
  registry4.register(withPerm(safeBash));
39541
39895
  }
39542
- const askUserTool = options.readOnly === true || profile === "explore" || profile === "verify" ? null : createAskUserTool(options.onAskUser);
39896
+ const askUserTool = options.readOnly === true || profile === "explore" || profile === "verify" || gauntletParent ? null : createAskUserTool(options.onAskUser);
39543
39897
  if (askUserTool) {
39544
39898
  registry4.register(withPerm(askUserTool));
39545
39899
  }
39546
- const enableSkill = options.enableSkill !== false && options.readOnly !== true && profile !== "explore" && profile !== "verify";
39900
+ const enableSkill = options.enableSkill !== false && options.readOnly !== true && !gauntletParent && profile !== "explore" && profile !== "verify";
39547
39901
  const skillTool = enableSkill ? withPerm(createSkillTool({ cwd: root })) : null;
39548
39902
  if (skillTool) {
39549
39903
  registry4.register(skillTool);
39550
39904
  }
39551
- const enableTodos = options.enableTodos !== false && options.readOnly !== true && profile === "full";
39905
+ const enableTodos = options.enableTodos !== false && options.readOnly !== true && !gauntletParent && profile === "full";
39552
39906
  const todoWrite = enableTodos ? withPerm(createTodoWriteTool()) : null;
39553
39907
  const todoRead = enableTodos ? withPerm(createTodoReadTool()) : null;
39554
39908
  if (todoWrite) registry4.register(todoWrite);
39555
39909
  if (todoRead) registry4.register(todoRead);
39556
- const enablePlanTasks = options.enablePlanTasks !== false && options.readOnly !== true && (profile === "full" || options.planMode === true) && profile !== "explore" && profile !== "verify" && profile !== "general";
39910
+ const enablePlanTasks = options.enablePlanTasks !== false && options.readOnly !== true && !gauntletParent && (profile === "full" || options.planMode === true) && profile !== "explore" && profile !== "verify" && profile !== "general";
39557
39911
  const planTaskToolsWrapped = (enablePlanTasks ? createPlanTaskTools({ projectRoot: root, onTaskEvent: options.onTaskEvent }) : []).map((t) => withPerm(t));
39558
39912
  for (const t of planTaskToolsWrapped) {
39559
39913
  registry4.register(t);
@@ -39604,7 +39958,7 @@ function createBuiltinToolRegistry(options = {}) {
39604
39958
  permissions: semanticTool.permissions ?? []
39605
39959
  });
39606
39960
  }
39607
- if (!readOnly && process.env.ZELARI_BROWSER !== "0") {
39961
+ if (!readOnly && !gauntletParent && process.env.ZELARI_BROWSER !== "0") {
39608
39962
  const browserTool = createBrowserTool();
39609
39963
  registry4.register(browserTool);
39610
39964
  tools.push({
@@ -39613,7 +39967,7 @@ function createBuiltinToolRegistry(options = {}) {
39613
39967
  permissions: browserTool.permissions ?? []
39614
39968
  });
39615
39969
  }
39616
- if (!readOnly && process.env.ZELARI_SSH !== "0") {
39970
+ if (!readOnly && !gauntletParent && process.env.ZELARI_SSH !== "0") {
39617
39971
  for (const t of createSshTools()) {
39618
39972
  registry4.register(t);
39619
39973
  tools.push({
@@ -39623,7 +39977,7 @@ function createBuiltinToolRegistry(options = {}) {
39623
39977
  });
39624
39978
  }
39625
39979
  }
39626
- if (!readOnly) {
39980
+ if (!readOnly && !gauntletParent) {
39627
39981
  for (const t of createWorldModelTools()) {
39628
39982
  const safe = wrapWithAudit(t, audit, sessionId2);
39629
39983
  registry4.register(safe);
@@ -39849,10 +40203,10 @@ function wrapWithDiagnostics(original, root, runner) {
39849
40203
  const filePath = value && typeof value === "object" && typeof value.path === "string" ? value.path : void 0;
39850
40204
  if (!filePath) return result;
39851
40205
  try {
39852
- const timeoutMs = Number(process.env.ZELARI_DIAGNOSTICS_TIMEOUT_MS) || 5e3;
40206
+ const timeoutMs2 = Number(process.env.ZELARI_DIAGNOSTICS_TIMEOUT_MS) || 5e3;
39853
40207
  const diags = await runDiagnosticsForFile(filePath, {
39854
40208
  cwd: root,
39855
- timeoutMs,
40209
+ timeoutMs: timeoutMs2,
39856
40210
  ...runner ? { runner } : {}
39857
40211
  });
39858
40212
  const formatted = formatDiagnostics(diags, { relativeTo: root });
@@ -40117,6 +40471,7 @@ function parseHeadlessFlags(argv) {
40117
40471
  let krakenGraph;
40118
40472
  let planOnly = process.env.ZELARI_KRAKEN_PLAN_ONLY === "1" || process.env.ZELARI_KRAKEN_PLAN_ONLY === "true";
40119
40473
  let runPlan = process.env.ZELARI_KRAKEN_RUN_PLAN;
40474
+ let gauntlet = process.env.ZELARI_GAUNTLET === "1" || process.env.ZELARI_GAUNTLET === "true";
40120
40475
  for (let i = 0; i < argv.length; i++) {
40121
40476
  const arg = argv[i];
40122
40477
  if (arg === "--headless") continue;
@@ -40287,6 +40642,10 @@ function parseHeadlessFlags(argv) {
40287
40642
  } else if (arg === "--run-plan") {
40288
40643
  runPlan = argv[i + 1];
40289
40644
  i++;
40645
+ } else if (arg === "--gauntlet") {
40646
+ gauntlet = true;
40647
+ } else if (arg === "--no-gauntlet") {
40648
+ gauntlet = false;
40290
40649
  }
40291
40650
  }
40292
40651
  if (councilFlag && !modeExplicit) {
@@ -40321,7 +40680,8 @@ function parseHeadlessFlags(argv) {
40321
40680
  ...strictDone ? { strictDone: true } : {},
40322
40681
  ...krakenGraph ? { krakenGraph } : {},
40323
40682
  ...planOnly ? { planOnly: true } : {},
40324
- ...runPlan ? { runPlan } : {}
40683
+ ...runPlan ? { runPlan } : {},
40684
+ ...gauntlet ? { gauntlet: true } : {}
40325
40685
  }
40326
40686
  };
40327
40687
  }
@@ -41632,10 +41992,10 @@ __export(claudeProvider_exports, {
41632
41992
  createLocalCliProvider: () => createLocalCliProvider
41633
41993
  });
41634
41994
  import { spawn as spawn11 } from "node:child_process";
41635
- function waitForExit(child, timeoutMs = 2e3) {
41995
+ function waitForExit(child, timeoutMs2 = 2e3) {
41636
41996
  return new Promise((resolve3) => {
41637
41997
  if (child.exitCode != null) return resolve3(child.exitCode);
41638
- const timer = setTimeout(() => resolve3(child.exitCode ?? null), timeoutMs);
41998
+ const timer = setTimeout(() => resolve3(child.exitCode ?? null), timeoutMs2);
41639
41999
  timer.unref?.();
41640
42000
  child.once("exit", (code) => {
41641
42001
  clearTimeout(timer);
@@ -43206,10 +43566,10 @@ function distTagForVersion(version2) {
43206
43566
  function registryUrlForTag(tag = distTagForVersion(getCurrentVersion())) {
43207
43567
  return `https://registry.npmjs.org/zelari-code/${tag}`;
43208
43568
  }
43209
- async function fetchLatestVersion(fetcher = fetch, registryUrl = REGISTRY_URL, timeoutMs = 5e3) {
43569
+ async function fetchLatestVersion(fetcher = fetch, registryUrl = REGISTRY_URL, timeoutMs2 = 5e3) {
43210
43570
  try {
43211
43571
  const controller = new AbortController();
43212
- const timer = setTimeout(() => controller.abort(), timeoutMs);
43572
+ const timer = setTimeout(() => controller.abort(), timeoutMs2);
43213
43573
  const response = await fetcher(registryUrl, { signal: controller.signal });
43214
43574
  clearTimeout(timer);
43215
43575
  if (!response.ok) {
@@ -43379,11 +43739,11 @@ var init_mcpClient = __esm({
43379
43739
  * Call a tool. Returns the concatenated text content; non-text content
43380
43740
  * items are summarized by type. Throws when the server flags isError.
43381
43741
  */
43382
- async callTool(name, args, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
43742
+ async callTool(name, args, timeoutMs2 = DEFAULT_REQUEST_TIMEOUT_MS) {
43383
43743
  const res = await this.request(
43384
43744
  "tools/call",
43385
43745
  { name, arguments: args },
43386
- timeoutMs
43746
+ timeoutMs2
43387
43747
  );
43388
43748
  const text = (res.content ?? []).map(
43389
43749
  (c) => c.type === "text" && typeof c.text === "string" ? c.text : `[${c.type ?? "unknown"} content]`
@@ -43400,7 +43760,7 @@ var init_mcpClient = __esm({
43400
43760
  this.child = null;
43401
43761
  }
43402
43762
  // ── JSON-RPC plumbing ────────────────────────────────────────────────
43403
- request(method, params, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
43763
+ request(method, params, timeoutMs2 = DEFAULT_REQUEST_TIMEOUT_MS) {
43404
43764
  const child = this.child;
43405
43765
  if (!child)
43406
43766
  return Promise.reject(new Error(`[mcp:${this.serverName}] not started`));
@@ -43411,10 +43771,10 @@ var init_mcpClient = __esm({
43411
43771
  this.pending.delete(id);
43412
43772
  reject(
43413
43773
  new Error(
43414
- `[mcp:${this.serverName}] ${method} timed out after ${timeoutMs}ms`
43774
+ `[mcp:${this.serverName}] ${method} timed out after ${timeoutMs2}ms`
43415
43775
  )
43416
43776
  );
43417
- }, timeoutMs);
43777
+ }, timeoutMs2);
43418
43778
  this.pending.set(id, { resolve: resolve3, reject, timer });
43419
43779
  child.stdin.write(payload + "\n", (err) => {
43420
43780
  if (err) {
@@ -44488,7 +44848,7 @@ function pickSmokeScript(scripts) {
44488
44848
  }
44489
44849
  return null;
44490
44850
  }
44491
- async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
44851
+ async function runProjectSmoke(projectRoot, timeoutMs2 = DEFAULT_TIMEOUT_MS3) {
44492
44852
  if (process.env["ZELARI_SMOKE"] === "0") {
44493
44853
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
44494
44854
  }
@@ -44521,7 +44881,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
44521
44881
  let stdout = "";
44522
44882
  let stderr = "";
44523
44883
  let settled = false;
44524
- const finish = (result) => {
44884
+ const finish2 = (result) => {
44525
44885
  if (settled) return;
44526
44886
  settled = true;
44527
44887
  clearTimeout(timer);
@@ -44529,15 +44889,15 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
44529
44889
  };
44530
44890
  const timer = setTimeout(() => {
44531
44891
  child.kill("SIGTERM");
44532
- finish({
44892
+ finish2({
44533
44893
  ran: true,
44534
44894
  ok: false,
44535
44895
  script,
44536
44896
  exitCode: -1,
44537
44897
  output: stdout + stderr,
44538
- reason: `smoke timeout after ${timeoutMs}ms`
44898
+ reason: `smoke timeout after ${timeoutMs2}ms`
44539
44899
  });
44540
- }, timeoutMs);
44900
+ }, timeoutMs2);
44541
44901
  child.stdout?.on("data", (chunk) => {
44542
44902
  stdout += chunk.toString("utf8");
44543
44903
  });
@@ -44545,7 +44905,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
44545
44905
  stderr += chunk.toString("utf8");
44546
44906
  });
44547
44907
  child.on("error", (err) => {
44548
- finish({
44908
+ finish2({
44549
44909
  ran: true,
44550
44910
  ok: false,
44551
44911
  script,
@@ -44557,7 +44917,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
44557
44917
  child.on("close", (code) => {
44558
44918
  const exitCode = code ?? -1;
44559
44919
  const ok = exitCode === 0;
44560
- finish({
44920
+ finish2({
44561
44921
  ran: true,
44562
44922
  ok,
44563
44923
  script,
@@ -46166,7 +46526,7 @@ function startPermissionBroker(socketPath, handlers, opts) {
46166
46526
  const closeAll = server.closeAllConnections;
46167
46527
  if (typeof closeAll === "function") closeAll.call(server);
46168
46528
  let done = false;
46169
- const finish = () => {
46529
+ const finish2 = () => {
46170
46530
  if (done) return;
46171
46531
  done = true;
46172
46532
  if (process.platform !== "win32") {
@@ -46175,8 +46535,8 @@ function startPermissionBroker(socketPath, handlers, opts) {
46175
46535
  res();
46176
46536
  }
46177
46537
  };
46178
- server.close(() => finish());
46179
- setTimeout(finish, 1e3).unref?.();
46538
+ server.close(() => finish2());
46539
+ setTimeout(finish2, 1e3).unref?.();
46180
46540
  })
46181
46541
  });
46182
46542
  });
@@ -46613,13 +46973,13 @@ async function createDefaultLlmClient(opts) {
46613
46973
  const llm = await resolveLlm(opts);
46614
46974
  return {
46615
46975
  async complete({ system, user }) {
46616
- const timeoutMs = resolvePlannerTimeoutMs();
46976
+ const timeoutMs2 = resolvePlannerTimeoutMs();
46617
46977
  const controller = new AbortController();
46618
46978
  let timedOut = false;
46619
- const t = timeoutMs > 0 ? setTimeout(() => {
46979
+ const t = timeoutMs2 > 0 ? setTimeout(() => {
46620
46980
  timedOut = true;
46621
46981
  controller.abort();
46622
- }, timeoutMs) : void 0;
46982
+ }, timeoutMs2) : void 0;
46623
46983
  try {
46624
46984
  const url2 = `${llm.baseUrl.replace(/\/$/, "")}/chat/completions`;
46625
46985
  let res;
@@ -46645,7 +47005,7 @@ async function createDefaultLlmClient(opts) {
46645
47005
  } catch (err) {
46646
47006
  if (timedOut) {
46647
47007
  throw new PlannerTransportError(
46648
- `Planner request timed out after ${Math.round(timeoutMs / 1e3)}s (no response). Raise ZELARI_KRAKEN_PLANNER_TIMEOUT_MS, or set ZELARI_KRAKEN_PLANNER_MODEL to a faster non-reasoning model.`
47008
+ `Planner request timed out after ${Math.round(timeoutMs2 / 1e3)}s (no response). Raise ZELARI_KRAKEN_PLANNER_TIMEOUT_MS, or set ZELARI_KRAKEN_PLANNER_MODEL to a faster non-reasoning model.`
46649
47009
  );
46650
47010
  }
46651
47011
  throw new PlannerTransportError(
@@ -47538,7 +47898,7 @@ var init_executor = __esm({
47538
47898
  DEFAULT_MAX_REVIEW_ROUNDS = 1;
47539
47899
  DEFAULT_GRAPH_TIMEOUT_MS = 0;
47540
47900
  DEFAULT_NODE_TIMEOUT_MS = 3e5;
47541
- DEFAULT_WRITER_NODE_TIMEOUT_MS = 9e5;
47901
+ DEFAULT_WRITER_NODE_TIMEOUT_MS = TASK_TOOL_TIMEOUT_MS;
47542
47902
  DEFAULT_CANCEL_GRACE_MS = 3e4;
47543
47903
  MAX_UPSTREAM_CHARS_PER_DEP = 2800;
47544
47904
  MAX_UPSTREAM_CHARS_TOTAL = 8e3;
@@ -49028,10 +49388,10 @@ async function validateApiKey(providerId, apiKey, options = {}) {
49028
49388
  return { ok: true, skipped: true, reason: "no_base_url" };
49029
49389
  }
49030
49390
  const probeUrl = options.probeUrl ?? `${spec.baseUrl.replace(/\/+$/, "")}/models`;
49031
- const timeoutMs = options.timeoutMs ?? 5e3;
49391
+ const timeoutMs2 = options.timeoutMs ?? 5e3;
49032
49392
  const fetchImpl = options.fetchImpl ?? fetch;
49033
49393
  const controller = new AbortController();
49034
- const timer = setTimeout(() => controller.abort(), timeoutMs);
49394
+ const timer = setTimeout(() => controller.abort(), timeoutMs2);
49035
49395
  try {
49036
49396
  const response = await fetchImpl(probeUrl, {
49037
49397
  method: "GET",
@@ -49060,7 +49420,7 @@ async function validateApiKey(providerId, apiKey, options = {}) {
49060
49420
  return {
49061
49421
  ok: false,
49062
49422
  reason: isAbort ? "network" : "network",
49063
- detail: isAbort ? `timeout after ${timeoutMs}ms` : msg,
49423
+ detail: isAbort ? `timeout after ${timeoutMs2}ms` : msg,
49064
49424
  durationMs
49065
49425
  };
49066
49426
  } finally {
@@ -49829,6 +50189,864 @@ var init_atMentions = __esm({
49829
50189
  }
49830
50190
  });
49831
50191
 
50192
+ // src/cli/gauntlet/policy.ts
50193
+ var policy_exports = {};
50194
+ __export(policy_exports, {
50195
+ DEFAULT_MAX_PARALLEL: () => DEFAULT_MAX_PARALLEL2,
50196
+ DEFAULT_MAX_PIECES: () => DEFAULT_MAX_PIECES,
50197
+ DEFAULT_MAX_ROUNDS: () => DEFAULT_MAX_ROUNDS,
50198
+ DEFAULT_WALL_MS: () => DEFAULT_WALL_MS,
50199
+ GAUNTLET_PARENT_BLOCKED_TOOLS: () => GAUNTLET_PARENT_BLOCKED_TOOLS,
50200
+ isGauntletFlagOn: () => isGauntletFlagOn,
50201
+ resolveGauntletCaps: () => resolveGauntletCaps,
50202
+ shouldRunGauntletHostLoop: () => shouldRunGauntletHostLoop
50203
+ });
50204
+ function envInt(raw, fallback, min, max) {
50205
+ if (raw === void 0 || raw === "") return fallback;
50206
+ const n = Number.parseInt(raw, 10);
50207
+ if (!Number.isFinite(n)) return fallback;
50208
+ return Math.min(max, Math.max(min, n));
50209
+ }
50210
+ function isGauntletFlagOn(explicit, env = process.env) {
50211
+ if (explicit === true) return true;
50212
+ if (explicit === false) return false;
50213
+ const raw = env.ZELARI_GAUNTLET;
50214
+ if (!raw) return false;
50215
+ return raw === "1" || raw.toLowerCase() === "true";
50216
+ }
50217
+ function resolveGauntletCaps(env = process.env) {
50218
+ const wallRaw = env.ZELARI_GAUNTLET_WALL_MS;
50219
+ let wallClockMs = DEFAULT_WALL_MS;
50220
+ if (wallRaw !== void 0 && wallRaw !== "") {
50221
+ const n = Number.parseInt(wallRaw, 10);
50222
+ if (Number.isFinite(n) && n >= 0) wallClockMs = n;
50223
+ }
50224
+ return {
50225
+ maxPieces: envInt(env.ZELARI_GAUNTLET_MAX_PIECES, DEFAULT_MAX_PIECES, 1, 16),
50226
+ maxRounds: envInt(env.ZELARI_GAUNTLET_MAX_ROUNDS, DEFAULT_MAX_ROUNDS, 1, 8),
50227
+ maxParallel: envInt(env.ZELARI_GAUNTLET_MAX_PARALLEL, DEFAULT_MAX_PARALLEL2, 1, 4),
50228
+ wallClockMs
50229
+ };
50230
+ }
50231
+ function shouldRunGauntletHostLoop(opts) {
50232
+ if (!isGauntletFlagOn(opts.gauntlet)) return false;
50233
+ if (opts.krakenGraph) return false;
50234
+ if (opts.useCouncil || opts.mode === "council" || opts.mode === "zelari") return false;
50235
+ const phase2 = opts.phase ?? "build";
50236
+ return phase2 !== "plan";
50237
+ }
50238
+ var DEFAULT_MAX_PIECES, DEFAULT_MAX_ROUNDS, DEFAULT_MAX_PARALLEL2, DEFAULT_WALL_MS, GAUNTLET_PARENT_BLOCKED_TOOLS;
50239
+ var init_policy = __esm({
50240
+ "src/cli/gauntlet/policy.ts"() {
50241
+ "use strict";
50242
+ DEFAULT_MAX_PIECES = 6;
50243
+ DEFAULT_MAX_ROUNDS = 3;
50244
+ DEFAULT_MAX_PARALLEL2 = 2;
50245
+ DEFAULT_WALL_MS = 45 * 60 * 1e3;
50246
+ GAUNTLET_PARENT_BLOCKED_TOOLS = [
50247
+ "write_file",
50248
+ "edit_file",
50249
+ "apply_diff",
50250
+ "bash"
50251
+ ];
50252
+ }
50253
+ });
50254
+
50255
+ // src/cli/gauntlet/complete.ts
50256
+ function timeoutMs(env = process.env) {
50257
+ const raw = env.ZELARI_GAUNTLET_DECOMPOSE_TIMEOUT_MS;
50258
+ if (raw === void 0 || raw === "") return DEFAULT_TIMEOUT_MS4;
50259
+ const n = Number.parseInt(raw, 10);
50260
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_TIMEOUT_MS4;
50261
+ }
50262
+ async function gauntletComplete(args, opts) {
50263
+ const provider = opts.provider;
50264
+ const meta3 = await resolveApiKeyWithMeta(provider);
50265
+ if (!meta3?.apiKey) {
50266
+ throw new Error(`No API key for provider '${provider}'`);
50267
+ }
50268
+ const baseUrl = resolveBaseUrl(provider);
50269
+ if (!baseUrl) {
50270
+ throw new Error(`No base URL for provider '${provider}'`);
50271
+ }
50272
+ const model = process.env.ZELARI_KRAKEN_PLANNER_MODEL?.trim() || opts.model || getModelForProvider(provider) || getProviderConfig().modelByProvider[provider] || "";
50273
+ if (!model) throw new Error(`No model for provider '${provider}'`);
50274
+ const ms = timeoutMs();
50275
+ const controller = new AbortController();
50276
+ const onParentAbort = () => controller.abort();
50277
+ opts.signal?.addEventListener("abort", onParentAbort, { once: true });
50278
+ let timedOut = false;
50279
+ const timer = ms > 0 ? setTimeout(() => {
50280
+ timedOut = true;
50281
+ controller.abort();
50282
+ }, ms) : void 0;
50283
+ try {
50284
+ const url2 = `${baseUrl.replace(/\/$/, "")}/chat/completions`;
50285
+ const res = await fetch(url2, {
50286
+ method: "POST",
50287
+ signal: controller.signal,
50288
+ headers: {
50289
+ "content-type": "application/json",
50290
+ authorization: `Bearer ${meta3.apiKey}`
50291
+ },
50292
+ body: JSON.stringify({
50293
+ model,
50294
+ temperature: 0.2,
50295
+ max_tokens: DEFAULT_MAX_TOKENS,
50296
+ stream: false,
50297
+ messages: [
50298
+ { role: "system", content: args.system },
50299
+ { role: "user", content: args.user }
50300
+ ]
50301
+ })
50302
+ });
50303
+ if (!res.ok) {
50304
+ const errBody = await res.text().catch(() => "");
50305
+ throw new Error(`decompose HTTP ${res.status}${errBody ? `: ${errBody.slice(0, 160)}` : ""}`);
50306
+ }
50307
+ const json2 = await res.json();
50308
+ const msg = json2.choices?.[0]?.message;
50309
+ const text = msg?.content?.trim() || msg?.reasoning_content?.trim() || "";
50310
+ if (!text) throw new Error("decompose: empty model response");
50311
+ return text;
50312
+ } catch (err) {
50313
+ if (timedOut) {
50314
+ throw new Error(
50315
+ `decompose timed out after ${Math.round(ms / 1e3)}s \u2014 using a single piece. Raise ZELARI_GAUNTLET_DECOMPOSE_TIMEOUT_MS or set ZELARI_KRAKEN_PLANNER_MODEL to a fast model.`
50316
+ );
50317
+ }
50318
+ throw err instanceof Error ? err : new Error(String(err));
50319
+ } finally {
50320
+ if (timer) clearTimeout(timer);
50321
+ opts.signal?.removeEventListener("abort", onParentAbort);
50322
+ }
50323
+ }
50324
+ var DEFAULT_TIMEOUT_MS4, DEFAULT_MAX_TOKENS;
50325
+ var init_complete = __esm({
50326
+ "src/cli/gauntlet/complete.ts"() {
50327
+ "use strict";
50328
+ init_providerConfig();
50329
+ init_keyStore();
50330
+ init_openai_compatible();
50331
+ DEFAULT_TIMEOUT_MS4 = 6e4;
50332
+ DEFAULT_MAX_TOKENS = 2048;
50333
+ }
50334
+ });
50335
+
50336
+ // src/cli/gauntlet/decompose.ts
50337
+ function fallbackPieces(goal) {
50338
+ const prompt = goal.trim() || "Complete the user Goal.";
50339
+ return [
50340
+ {
50341
+ id: "g1",
50342
+ label: labelFromGoal(prompt),
50343
+ prompt,
50344
+ acceptance: []
50345
+ }
50346
+ ];
50347
+ }
50348
+ function labelFromGoal(goal) {
50349
+ const line = goal.trim().split(/\r?\n/, 1)[0] ?? "Goal";
50350
+ return line.slice(0, MAX_LABEL) || "Goal";
50351
+ }
50352
+ function parsePiecesJson(raw, maxPieces) {
50353
+ if (!raw || typeof raw !== "object") return null;
50354
+ const rec = raw;
50355
+ const list = rec.pieces ?? rec.nodes;
50356
+ if (!Array.isArray(list) || list.length === 0) return null;
50357
+ const out = [];
50358
+ for (let i = 0; i < list.length && out.length < maxPieces; i++) {
50359
+ const item = list[i];
50360
+ if (!item || typeof item !== "object") continue;
50361
+ const row = item;
50362
+ const prompt = typeof row.prompt === "string" ? row.prompt.trim() : typeof row.goal === "string" ? row.goal.trim() : "";
50363
+ if (!prompt) continue;
50364
+ const id = typeof row.id === "string" && row.id.trim() ? row.id.trim().slice(0, 32) : `g${out.length + 1}`;
50365
+ const label = typeof row.label === "string" && row.label.trim() ? row.label.trim().slice(0, MAX_LABEL) : labelFromGoal(prompt);
50366
+ const acceptance = Array.isArray(row.acceptance) ? row.acceptance.filter((a) => typeof a === "string" && a.trim().length > 0).map((a) => a.trim()).slice(0, MAX_ACCEPTANCE) : [];
50367
+ const scope = Array.isArray(row.scope) ? row.scope.filter((s) => typeof s === "string" && s.trim().length > 0).map((s) => s.trim()).slice(0, 16) : void 0;
50368
+ const bar = Array.isArray(row.bar) ? row.bar.filter((s) => typeof s === "string" && s.trim().length > 0).map((s) => s.trim()).slice(0, 4) : void 0;
50369
+ out.push({
50370
+ id: uniqueId2(id, out),
50371
+ label,
50372
+ prompt: prompt.slice(0, MAX_PROMPT),
50373
+ acceptance,
50374
+ ...scope && scope.length > 0 ? { scope } : {},
50375
+ ...bar && bar.length > 0 ? { bar } : {}
50376
+ });
50377
+ }
50378
+ return out.length > 0 ? out : null;
50379
+ }
50380
+ function formatHistoryNote(messages, maxMessages = 4, each = 400) {
50381
+ const slice = messages.filter((m) => m.role === "user" || m.role === "assistant").slice(-maxMessages);
50382
+ if (slice.length === 0) return "";
50383
+ return slice.map((m) => {
50384
+ const body = m.content.replace(/\s+/g, " ").trim().slice(0, each);
50385
+ return `${m.role}: ${body}`;
50386
+ }).join("\n");
50387
+ }
50388
+ function buildDecomposeUser(opts) {
50389
+ const parts = [
50390
+ `Max pieces: ${opts.maxPieces}`,
50391
+ "",
50392
+ "## Goal",
50393
+ opts.goal.trim() || "(empty)"
50394
+ ];
50395
+ if (opts.historyNote?.trim()) {
50396
+ parts.push("", "## Prior turns (context only \u2014 the Goal above is authoritative)", opts.historyNote.trim());
50397
+ }
50398
+ if (opts.workspace?.trim()) {
50399
+ parts.push("", "## Workspace", opts.workspace.trim());
50400
+ }
50401
+ return parts.join("\n");
50402
+ }
50403
+ function piecesFromModelText(text, maxPieces) {
50404
+ try {
50405
+ const json2 = extractJsonObject(text, { requireKey: "pieces" });
50406
+ return parsePiecesJson(json2, maxPieces);
50407
+ } catch {
50408
+ return null;
50409
+ }
50410
+ }
50411
+ async function decomposeGoal(opts) {
50412
+ const fallback = fallbackPieces(opts.goal);
50413
+ if (!opts.complete) return { pieces: fallback, source: "fallback" };
50414
+ try {
50415
+ const text = await opts.complete({
50416
+ system: GAUNTLET_DECOMPOSE_SYSTEM,
50417
+ user: buildDecomposeUser(opts)
50418
+ });
50419
+ const parsed = piecesFromModelText(text, opts.maxPieces);
50420
+ if (parsed && parsed.length > 0) return { pieces: parsed, source: "llm" };
50421
+ return { pieces: fallback, source: "fallback", error: "decompose JSON unusable" };
50422
+ } catch (err) {
50423
+ return {
50424
+ pieces: fallback,
50425
+ source: "fallback",
50426
+ error: err instanceof Error ? err.message : String(err)
50427
+ };
50428
+ }
50429
+ }
50430
+ function uniqueId2(id, existing) {
50431
+ if (!existing.some((p3) => p3.id === id)) return id;
50432
+ let n = 2;
50433
+ while (existing.some((p3) => p3.id === `${id}-${n}`)) n += 1;
50434
+ return `${id}-${n}`;
50435
+ }
50436
+ var MAX_LABEL, MAX_PROMPT, MAX_ACCEPTANCE, GAUNTLET_DECOMPOSE_SYSTEM;
50437
+ var init_decompose = __esm({
50438
+ "src/cli/gauntlet/decompose.ts"() {
50439
+ "use strict";
50440
+ init_planner();
50441
+ MAX_LABEL = 80;
50442
+ MAX_PROMPT = 8e3;
50443
+ MAX_ACCEPTANCE = 8;
50444
+ GAUNTLET_DECOMPOSE_SYSTEM = [
50445
+ "Decompose the user Goal into independently shippable Gauntlet pieces.",
50446
+ "Return ONLY JSON (no markdown, no prose):",
50447
+ '{ "pieces": [ { "id": string, "label": string, "prompt": string, "scope"?: string[], "acceptance"?: string[], "bar"?: string[] } ] }',
50448
+ "Rules:",
50449
+ "- 1 to N pieces (N is given in the user message). Prefer fewer. One piece is valid when the goal is atomic.",
50450
+ '- "prompt" is self-contained: the builder will not see this conversation.',
50451
+ '- "scope" is a path allowlist so disjoint pieces can run in parallel. Omit if unsure (forces sequential).',
50452
+ '- "acceptance" is checkable (file, command, observable). Never subjective ("elegant").',
50453
+ '- "bar" (optional) is on-disk reference path(s) the critic compares against blindly (gold file, screenshot, test).',
50454
+ "- Use paths from the workspace listing. Do not invent a tree.",
50455
+ "- Decompose the USER GOAL literally. Do not enlarge it into a different project."
50456
+ ].join("\n");
50457
+ }
50458
+ });
50459
+
50460
+ // src/cli/gauntlet/events.ts
50461
+ function gauntletProgressEvent(sessionId2, progress) {
50462
+ return {
50463
+ type: "gauntlet_progress",
50464
+ sessionId: sessionId2,
50465
+ ts: Date.now(),
50466
+ progress
50467
+ };
50468
+ }
50469
+ function formatGauntletSummary(results, opts) {
50470
+ if (results.length === 0) {
50471
+ if (opts?.timedOut) return "Gauntlet stopped: wall clock. No pieces finished.";
50472
+ if (opts?.cancelled) return "Gauntlet cancelled. No pieces finished.";
50473
+ return "Gauntlet: no pieces ran.";
50474
+ }
50475
+ const head = opts?.timedOut ? "Gauntlet stopped: wall clock." : opts?.cancelled ? "Gauntlet cancelled." : "Gauntlet finished.";
50476
+ const lines = [head, ""];
50477
+ for (const r of results) {
50478
+ const gap = r.gap ? ` \u2014 ${r.gap}` : "";
50479
+ const win = r.winner ? ` [A/B ${r.winner}]` : "";
50480
+ lines.push(`- ${r.label} (${r.id}): ${r.verdict} after ${r.rounds} round(s)${win}${gap}`);
50481
+ }
50482
+ return lines.join("\n");
50483
+ }
50484
+ var init_events3 = __esm({
50485
+ "src/cli/gauntlet/events.ts"() {
50486
+ "use strict";
50487
+ }
50488
+ });
50489
+
50490
+ // src/cli/gauntlet/blind.ts
50491
+ function assignBlindLabels(items, rand = Math.random) {
50492
+ if (rand() < 0.5) {
50493
+ return { A: items[0], B: items[1], firstLabel: "A" };
50494
+ }
50495
+ return { A: items[1], B: items[0], firstLabel: "B" };
50496
+ }
50497
+ function qualityBarSection(bars, rand = Math.random) {
50498
+ if (!bars || bars.length === 0) return "";
50499
+ if (bars.length === 1) {
50500
+ return [
50501
+ "## Quality bar (blind)",
50502
+ "Inspect this reference on disk. PASS only if the work in Scope meets or beats it.",
50503
+ "Do not assume the new files are better \u2014 compare concretely.",
50504
+ `- ${bars[0]}`
50505
+ ].join("\n");
50506
+ }
50507
+ const pair = assignBlindLabels([bars[0], bars[1]], rand);
50508
+ return [
50509
+ "## Blind A/B",
50510
+ "Two unlabeled artifacts on disk. Compare them against the piece acceptance.",
50511
+ 'Do not assume which is newer or which you are "supposed" to prefer.',
50512
+ `A: ${pair.A}`,
50513
+ `B: ${pair.B}`,
50514
+ "Also emit: WINNER: A | B | TIE"
50515
+ ].join("\n");
50516
+ }
50517
+ function parseBlindWinner(text) {
50518
+ const m = WINNER_RE.exec(text);
50519
+ if (!m) return void 0;
50520
+ const v = m[1].toUpperCase();
50521
+ if (v === "A" || v === "B" || v === "TIE") return v;
50522
+ return void 0;
50523
+ }
50524
+ var WINNER_RE;
50525
+ var init_blind = __esm({
50526
+ "src/cli/gauntlet/blind.ts"() {
50527
+ "use strict";
50528
+ WINNER_RE = /\bWINNER:\s*(A|B|TIE)\b/i;
50529
+ }
50530
+ });
50531
+
50532
+ // src/cli/gauntlet/verdict.ts
50533
+ function parseGauntletVerdict(text, opts = {}) {
50534
+ const evidence = (opts.toolTraceCount ?? 0) > 0;
50535
+ if (opts.builderFailed) {
50536
+ return {
50537
+ kind: "GAP",
50538
+ gap: (opts.builderError ?? "builder failed").trim() || "builder failed",
50539
+ evidence
50540
+ };
50541
+ }
50542
+ const raw = (text ?? "").trim();
50543
+ if (!raw) {
50544
+ return { kind: "BLOCKED", gap: "critic produced no output", evidence };
50545
+ }
50546
+ const explicit = VERDICT_RE.exec(raw);
50547
+ let kind = explicit ? explicit[1].toUpperCase() : void 0;
50548
+ const reports = [...raw.matchAll(REPORT_STATUS_RE)].map((m) => m[1].toLowerCase());
50549
+ const anyFail = reports.includes("fail");
50550
+ const anyUnknown = reports.includes("unknown");
50551
+ const anyPass = reports.includes("pass");
50552
+ if (!kind) {
50553
+ if (anyFail) kind = "GAP";
50554
+ else if (anyPass && !anyUnknown) kind = "PASS";
50555
+ else if (anyUnknown && !anyPass) kind = "BLOCKED";
50556
+ else kind = "BLOCKED";
50557
+ }
50558
+ const gapMatch = GAP_RE.exec(raw);
50559
+ let gap = gapMatch ? gapMatch[1].trim() : void 0;
50560
+ if (kind === "GAP" && !gap) {
50561
+ gap = anyFail ? "a verify-report check failed" : "critic named a gap without a GAP: line";
50562
+ }
50563
+ if (kind === "PASS" && !evidence) {
50564
+ return {
50565
+ kind: "GAP",
50566
+ gap: "unknown \u2260 pass: critic declared PASS without tool evidence",
50567
+ evidence: false
50568
+ };
50569
+ }
50570
+ if (kind === "PASS" && anyFail) {
50571
+ return {
50572
+ kind: "GAP",
50573
+ gap: gap ?? "verify-report contains a fail",
50574
+ evidence
50575
+ };
50576
+ }
50577
+ return { kind, ...gap ? { gap } : {}, evidence };
50578
+ }
50579
+ var VERDICT_RE, GAP_RE, REPORT_STATUS_RE;
50580
+ var init_verdict2 = __esm({
50581
+ "src/cli/gauntlet/verdict.ts"() {
50582
+ "use strict";
50583
+ VERDICT_RE = /\bVERDICT:\s*(PASS|GAP|BLOCKED)\b/i;
50584
+ GAP_RE = /\bGAP:\s*(.+)/i;
50585
+ REPORT_STATUS_RE = /^status:\s*(pass|fail|unknown)\b/gim;
50586
+ }
50587
+ });
50588
+
50589
+ // src/cli/gauntlet/prompts.ts
50590
+ function builderUserPrompt(piece, gap, briefing) {
50591
+ const parts = [
50592
+ `You are the BUILDER for one Gauntlet piece. Implement it on disk.`,
50593
+ `Do not spawn sub-agents. Stay inside Scope if provided.`
50594
+ ];
50595
+ if (briefing?.trim()) {
50596
+ parts.push("", "## Workspace briefing (do not treat as already-done work)", briefing.trim());
50597
+ }
50598
+ parts.push("", `## Piece: ${piece.label}`, piece.prompt.trim());
50599
+ if (piece.scope && piece.scope.length > 0) {
50600
+ parts.push("", "## Scope", ...piece.scope.map((s) => `- ${s}`));
50601
+ }
50602
+ if (piece.acceptance.length > 0) {
50603
+ parts.push("", "## Acceptance", ...piece.acceptance.map((a) => `- ${a}`));
50604
+ }
50605
+ if (gap && gap.trim()) {
50606
+ parts.push(
50607
+ "",
50608
+ "## Previous critic GAP (fix ONLY this)",
50609
+ gap.trim(),
50610
+ "Do not widen scope. Re-check the acceptance after the fix."
50611
+ );
50612
+ }
50613
+ parts.push("", "Return: files touched, what changed, residual risks.");
50614
+ return parts.join("\n");
50615
+ }
50616
+ function criticUserPrompt(piece, round, rand) {
50617
+ const parts = [
50618
+ `Round ${round}. Inspect the current tree against this piece.`,
50619
+ "Do not edit files. Do not take the builder's word \u2014 open files / run checks.",
50620
+ "",
50621
+ `## Piece: ${piece.label}`,
50622
+ piece.prompt.trim()
50623
+ ];
50624
+ if (piece.scope && piece.scope.length > 0) {
50625
+ parts.push("", "## Scope", ...piece.scope.map((s) => `- ${s}`));
50626
+ }
50627
+ if (piece.acceptance.length > 0) {
50628
+ parts.push("", "## Acceptance (check each)", ...piece.acceptance.map((a) => `- ${a}`));
50629
+ } else {
50630
+ parts.push(
50631
+ "",
50632
+ "## Acceptance",
50633
+ "- The piece prompt is satisfied by files on disk.",
50634
+ "- Relevant typecheck/tests pass when the project has them."
50635
+ );
50636
+ }
50637
+ const bar = qualityBarSection(piece.bar, rand);
50638
+ if (bar) parts.push("", bar);
50639
+ return parts.join("\n");
50640
+ }
50641
+ var GAUNTLET_CRITIC_SYSTEM;
50642
+ var init_prompts = __esm({
50643
+ "src/cli/gauntlet/prompts.ts"() {
50644
+ "use strict";
50645
+ init_blind();
50646
+ GAUNTLET_CRITIC_SYSTEM = [
50647
+ "You are a ruthless VERIFY critic in a Gauntlet Loop.",
50648
+ "Inspect the REAL files and commands on disk. Do not trust a builder self-report.",
50649
+ "You have no builder transcript \u2014 only the Goal, Scope, Acceptance, and the tree.",
50650
+ "OBSERVATION INTEGRITY: unknown \u2260 pass. EMPTY/degraded tools are not evidence.",
50651
+ "Name at most ONE biggest remaining gap. Do not write a laundry list.",
50652
+ "You may read files and run test/build commands via bash. Prefer targeted checks.",
50653
+ "",
50654
+ "End with BOTH:",
50655
+ "1. One <verify-report> block per acceptance criterion:",
50656
+ "<verify-report>",
50657
+ "check: <criterion text as given>",
50658
+ "status: pass | fail | unknown",
50659
+ "note: <one line of evidence (command or file + outcome)>",
50660
+ "</verify-report>",
50661
+ "2. A trailer on its own lines:",
50662
+ "VERDICT: PASS | GAP | BLOCKED",
50663
+ "GAP: <single biggest remaining gap; omit on PASS>",
50664
+ "3. If a Blind A/B section is present, also emit WINNER: A | B | TIE"
50665
+ ].join("\n");
50666
+ }
50667
+ });
50668
+
50669
+ // src/cli/gauntlet/schedule.ts
50670
+ function piecesCanRunInParallel(a, b) {
50671
+ return disjointScopeSets(a.scope, b.scope);
50672
+ }
50673
+ function scheduleWaves(pieces, maxParallel) {
50674
+ const cap3 = Math.max(1, maxParallel);
50675
+ const remaining = [...pieces];
50676
+ const waves = [];
50677
+ while (remaining.length > 0) {
50678
+ const wave = [];
50679
+ const leftover = [];
50680
+ for (const p3 of remaining) {
50681
+ if (wave.length >= cap3) {
50682
+ leftover.push(p3);
50683
+ continue;
50684
+ }
50685
+ if (wave.length === 0 || wave.every((w) => piecesCanRunInParallel(w, p3))) {
50686
+ wave.push(p3);
50687
+ } else {
50688
+ leftover.push(p3);
50689
+ }
50690
+ }
50691
+ waves.push(wave);
50692
+ remaining.splice(0, remaining.length, ...leftover);
50693
+ }
50694
+ return waves;
50695
+ }
50696
+ var init_schedule = __esm({
50697
+ "src/cli/gauntlet/schedule.ts"() {
50698
+ "use strict";
50699
+ init_dist();
50700
+ }
50701
+ });
50702
+
50703
+ // src/cli/gauntlet/loop.ts
50704
+ async function runGauntletLoop(args) {
50705
+ const { caps, deps } = args;
50706
+ const pieces = args.pieces.slice(0, caps.maxPieces);
50707
+ const started = (deps.now ?? Date.now)();
50708
+ const results = [];
50709
+ const emitProgress = (partial2) => {
50710
+ const progress = {
50711
+ ...partial2,
50712
+ elapsedMs: (deps.now ?? Date.now)() - started
50713
+ };
50714
+ deps.emit(gauntletProgressEvent(deps.sessionId, progress));
50715
+ deps.note?.("gauntlet.progress", progress);
50716
+ };
50717
+ const wall = caps.wallClockMs === void 0 ? DEFAULT_WALL_MS : caps.wallClockMs;
50718
+ const expired = () => wall > 0 && (deps.now ?? Date.now)() - started >= wall;
50719
+ const aborted2 = () => Boolean(deps.signal?.aborted);
50720
+ const stop = () => aborted2() || expired();
50721
+ const indexOf = (piece) => pieces.findIndex((p3) => p3.id === piece.id);
50722
+ const runOnePiece = async (piece) => {
50723
+ let last = {
50724
+ kind: "BLOCKED",
50725
+ gap: "no round ran",
50726
+ evidence: false
50727
+ };
50728
+ let gap;
50729
+ let winner;
50730
+ let rounds = 0;
50731
+ const i = Math.max(0, indexOf(piece));
50732
+ for (let round = 1; round <= caps.maxRounds; round++) {
50733
+ if (stop()) break;
50734
+ rounds = round;
50735
+ const phase2 = gap ? "repairing" : "building";
50736
+ emitProgress({
50737
+ phase: phase2,
50738
+ pieceId: piece.id,
50739
+ pieceLabel: piece.label,
50740
+ pieceIndex: i,
50741
+ pieceCount: pieces.length,
50742
+ round,
50743
+ maxRounds: caps.maxRounds
50744
+ });
50745
+ const built = await deps.runBuilder({
50746
+ piece,
50747
+ prompt: builderUserPrompt(piece, gap, deps.briefing),
50748
+ round
50749
+ });
50750
+ if (stop()) break;
50751
+ emitProgress({
50752
+ phase: "critiquing",
50753
+ pieceId: piece.id,
50754
+ pieceLabel: piece.label,
50755
+ pieceIndex: i,
50756
+ pieceCount: pieces.length,
50757
+ round,
50758
+ maxRounds: caps.maxRounds
50759
+ });
50760
+ const criticized = await deps.runCritic({
50761
+ piece,
50762
+ prompt: criticUserPrompt(piece, round),
50763
+ systemPrompt: GAUNTLET_CRITIC_SYSTEM,
50764
+ round
50765
+ });
50766
+ last = parseGauntletVerdict(criticized.result, {
50767
+ toolTraceCount: criticized.toolTraceCount ?? 0,
50768
+ builderFailed: !built.ok,
50769
+ builderError: built.error
50770
+ });
50771
+ winner = parseBlindWinner(criticized.result) ?? winner;
50772
+ emitProgress({
50773
+ phase: last.kind === "PASS" ? "settled" : last.kind === "BLOCKED" ? "blocked" : "repairing",
50774
+ pieceId: piece.id,
50775
+ pieceLabel: piece.label,
50776
+ pieceIndex: i,
50777
+ pieceCount: pieces.length,
50778
+ round,
50779
+ maxRounds: caps.maxRounds,
50780
+ verdict: last.kind,
50781
+ gap: last.gap,
50782
+ winner
50783
+ });
50784
+ if (last.kind === "PASS" || last.kind === "BLOCKED") break;
50785
+ gap = last.gap;
50786
+ }
50787
+ return {
50788
+ id: piece.id,
50789
+ label: piece.label,
50790
+ verdict: last,
50791
+ rounds,
50792
+ ...winner ? { winner } : {}
50793
+ };
50794
+ };
50795
+ for (const wave of scheduleWaves(pieces, caps.maxParallel)) {
50796
+ if (stop()) return finish(results, { cancelled: aborted2(), timedOut: expired() });
50797
+ if (wave.length === 1) {
50798
+ results.push(await runOnePiece(wave[0]));
50799
+ } else {
50800
+ const waveResults = await Promise.all(wave.map((p3) => runOnePiece(p3)));
50801
+ results.push(...waveResults);
50802
+ }
50803
+ if (stop()) return finish(results, { cancelled: aborted2(), timedOut: expired() });
50804
+ }
50805
+ return finish(results, { cancelled: aborted2(), timedOut: expired() });
50806
+ }
50807
+ function finish(results, flags) {
50808
+ const cancelled = flags.timedOut ? false : flags.cancelled;
50809
+ const timedOut = flags.timedOut;
50810
+ const settled = !cancelled && !timedOut && results.length > 0 && results.every((r) => r.verdict.kind === "PASS");
50811
+ return {
50812
+ settled,
50813
+ cancelled,
50814
+ timedOut,
50815
+ pieces: results,
50816
+ summary: formatGauntletSummary(
50817
+ results.map((r) => ({
50818
+ id: r.id,
50819
+ label: r.label,
50820
+ verdict: r.verdict.kind,
50821
+ rounds: r.rounds,
50822
+ gap: r.verdict.gap,
50823
+ winner: r.winner
50824
+ })),
50825
+ { timedOut, cancelled }
50826
+ )
50827
+ };
50828
+ }
50829
+ var init_loop = __esm({
50830
+ "src/cli/gauntlet/loop.ts"() {
50831
+ "use strict";
50832
+ init_blind();
50833
+ init_policy();
50834
+ init_verdict2();
50835
+ init_prompts();
50836
+ init_schedule();
50837
+ init_events3();
50838
+ }
50839
+ });
50840
+
50841
+ // src/cli/gauntlet/run.ts
50842
+ var run_exports = {};
50843
+ __export(run_exports, {
50844
+ runHeadlessGauntlet: () => runHeadlessGauntlet
50845
+ });
50846
+ async function runHeadlessGauntlet(opts, provider, model) {
50847
+ const sessionId2 = opts.resumeSessionId ?? crypto.randomUUID();
50848
+ const spine = await openHeadlessSpine({
50849
+ sessionId: sessionId2,
50850
+ mode: opts.mode,
50851
+ profile: opts.profile,
50852
+ workspace: process.cwd()
50853
+ });
50854
+ emitEvent(sessionStartedEvent(spine));
50855
+ const seeded = await seedHeadlessModelHistory(spine, opts.history);
50856
+ if (opts.task) spine.userMessage(opts.task);
50857
+ spine.note("gauntlet.start", { goal: opts.task.slice(0, 200) });
50858
+ const abort = new AbortController();
50859
+ const onSigint = () => {
50860
+ emitEvent({ type: "log", message: "[gauntlet] SIGINT \u2014 cancelling" });
50861
+ abort.abort();
50862
+ };
50863
+ process.once("SIGINT", onSigint);
50864
+ const wallMs = resolveGauntletCaps().wallClockMs ?? 0;
50865
+ const wallTimer = wallMs > 0 ? setTimeout(() => {
50866
+ emitEvent({ type: "log", message: "[gauntlet] wall clock \u2014 aborting tentacles" });
50867
+ abort.abort();
50868
+ }, wallMs) : void 0;
50869
+ const cwd = process.cwd();
50870
+ const audit = new AuditLogger();
50871
+ const createSubAgentContext = createKrakenSubAgentContextFactory({
50872
+ root: cwd,
50873
+ audit,
50874
+ sessionId: sessionId2,
50875
+ provider,
50876
+ model
50877
+ });
50878
+ const deps = {
50879
+ createSubAgentContext,
50880
+ allowWorktree: false,
50881
+ harnessFactory: (config2) => {
50882
+ const harness = new AgentHarness(config2);
50883
+ return {
50884
+ async *run() {
50885
+ for await (const ev of harness.run()) {
50886
+ if (ev.type === "thinking_delta" || ev.type === "tool_execution_start" || ev.type === "tool_execution_end") {
50887
+ emitEvent(ev);
50888
+ spine.observe(ev);
50889
+ }
50890
+ yield ev;
50891
+ }
50892
+ },
50893
+ cancel: () => harness.cancel()
50894
+ };
50895
+ }
50896
+ };
50897
+ const emit = (event) => {
50898
+ if (opts.output === "json") emitEvent(event);
50899
+ else if (typeof event.message === "string") {
50900
+ process.stderr.write(`${event.message}
50901
+ `);
50902
+ }
50903
+ };
50904
+ emitEvent({ type: "agent_start", model, provider, role: "gauntlet" });
50905
+ emit({ type: "log", message: "[gauntlet] host loop \u2014 builder/critic rounds, parent cannot write" });
50906
+ const caps = resolveGauntletCaps();
50907
+ const workspace = buildWorkspaceSummary(cwd, { maxChars: 2500 });
50908
+ const historyNote = formatHistoryNote(seeded.history);
50909
+ emitEvent(
50910
+ gauntletProgressEvent(sessionId2, {
50911
+ phase: "decomposing",
50912
+ pieceId: "",
50913
+ pieceLabel: "Goal",
50914
+ pieceIndex: 0,
50915
+ pieceCount: 1,
50916
+ round: 0,
50917
+ maxRounds: caps.maxRounds,
50918
+ elapsedMs: 0
50919
+ })
50920
+ );
50921
+ try {
50922
+ const decomposed = await decomposeGoal({
50923
+ goal: opts.task,
50924
+ maxPieces: caps.maxPieces,
50925
+ workspace,
50926
+ historyNote,
50927
+ complete: (req) => gauntletComplete(req, { provider, model, signal: abort.signal })
50928
+ });
50929
+ spine.note("gauntlet.decompose", {
50930
+ source: decomposed.source,
50931
+ count: decomposed.pieces.length,
50932
+ ...decomposed.error ? { error: decomposed.error.slice(0, 240) } : {}
50933
+ });
50934
+ emit({
50935
+ type: "log",
50936
+ message: `[gauntlet] ${decomposed.pieces.length} piece(s) from ${decomposed.source}${decomposed.error ? ` (${decomposed.error.slice(0, 80)})` : ""}`
50937
+ });
50938
+ const result = await runGauntletLoop({
50939
+ pieces: decomposed.pieces,
50940
+ caps,
50941
+ deps: {
50942
+ sessionId: sessionId2,
50943
+ signal: abort.signal,
50944
+ emit,
50945
+ briefing: workspace.slice(0, 1200),
50946
+ note: (text, data) => spine.note(text, data),
50947
+ runBuilder: async ({ piece, prompt, round }) => {
50948
+ emit({
50949
+ type: "log",
50950
+ message: `[gauntlet] ${piece.id} round ${round}/${caps.maxRounds} builder`
50951
+ });
50952
+ const tent = await runTentacle({
50953
+ deps,
50954
+ agent: "general",
50955
+ thoroughness: "medium",
50956
+ parentCwd: cwd,
50957
+ sessionId: sessionId2,
50958
+ signal: abort.signal,
50959
+ args: {
50960
+ description: `gauntlet-builder ${piece.id} r${round}`,
50961
+ prompt,
50962
+ ...piece.scope ? { scope: piece.scope } : {},
50963
+ ...piece.acceptance.length > 0 ? { acceptance: piece.acceptance } : {}
50964
+ }
50965
+ });
50966
+ if (!tent.ok) return { ok: false, result: "", error: tent.error };
50967
+ return {
50968
+ ok: true,
50969
+ result: tent.result,
50970
+ toolTraceCount: tent.toolTrace?.length ?? 0
50971
+ };
50972
+ },
50973
+ runCritic: async ({ piece, prompt, systemPrompt, round }) => {
50974
+ emit({
50975
+ type: "log",
50976
+ message: `[gauntlet] ${piece.id} round ${round}/${caps.maxRounds} critic`
50977
+ });
50978
+ const tent = await runTentacle({
50979
+ deps,
50980
+ agent: "verify",
50981
+ thoroughness: "medium",
50982
+ parentCwd: cwd,
50983
+ sessionId: sessionId2,
50984
+ signal: abort.signal,
50985
+ systemPromptOverride: systemPrompt,
50986
+ args: {
50987
+ description: `gauntlet-critic ${piece.id} r${round}`,
50988
+ prompt,
50989
+ ...piece.scope ? { scope: piece.scope } : {},
50990
+ ...piece.acceptance.length > 0 ? { acceptance: piece.acceptance } : {}
50991
+ }
50992
+ });
50993
+ if (!tent.ok) {
50994
+ return { ok: false, result: tent.error, error: tent.error, toolTraceCount: 0 };
50995
+ }
50996
+ return {
50997
+ ok: true,
50998
+ result: tent.result,
50999
+ toolTraceCount: tent.toolTrace?.length ?? 0
51000
+ };
51001
+ }
51002
+ }
51003
+ });
51004
+ emitEvent({ type: "message_start", role: "assistant" });
51005
+ emitEvent({ type: "message_delta", delta: result.summary });
51006
+ emitEvent({ type: "message_end", totalLength: result.summary.length });
51007
+ emitEvent({
51008
+ type: "agent_end",
51009
+ reason: result.cancelled || result.timedOut ? "cancelled" : result.settled ? "completed" : "error"
51010
+ });
51011
+ if (result.timedOut) {
51012
+ emit({ type: "log", message: `[gauntlet] wall clock (${Math.round(wallMs / 6e4)}m)` });
51013
+ await spine.interrupt("gauntlet-wall-clock");
51014
+ return 1;
51015
+ }
51016
+ if (result.cancelled) {
51017
+ await spine.interrupt("gauntlet-cancelled");
51018
+ return 1;
51019
+ }
51020
+ await spine.close(result.settled ? "gauntlet-pass" : "gauntlet-incomplete");
51021
+ return result.settled ? 0 : 3;
51022
+ } catch (err) {
51023
+ const msg = err instanceof Error ? err.message : String(err);
51024
+ emitEvent({ type: "error", severity: "fatal", message: msg, code: "gauntlet" });
51025
+ await spine.interrupt(`gauntlet-error: ${msg}`).catch(() => void 0);
51026
+ return 2;
51027
+ } finally {
51028
+ if (wallTimer) clearTimeout(wallTimer);
51029
+ process.removeListener("SIGINT", onSigint);
51030
+ }
51031
+ }
51032
+ var init_run = __esm({
51033
+ "src/cli/gauntlet/run.ts"() {
51034
+ "use strict";
51035
+ init_harness();
51036
+ init_headless();
51037
+ init_headlessSpine();
51038
+ init_auditLogger();
51039
+ init_toolRegistry();
51040
+ init_taskTool();
51041
+ init_complete();
51042
+ init_decompose();
51043
+ init_events3();
51044
+ init_loop();
51045
+ init_policy();
51046
+ init_workspaceSummary();
51047
+ }
51048
+ });
51049
+
49832
51050
  // src/cli/triggerLock.ts
49833
51051
  var triggerLock_exports = {};
49834
51052
  __export(triggerLock_exports, {
@@ -50603,7 +51821,7 @@ function startPermissionMcpServer(opts) {
50603
51821
  }
50604
51822
  return { error: { code: -32601, message: `method not found: ${method}` } };
50605
51823
  }
50606
- async function callApprove(args, timeoutMs) {
51824
+ async function callApprove(args, timeoutMs2) {
50607
51825
  const toolName = typeof args.tool_name === "string" ? args.tool_name : "";
50608
51826
  if (!toolName) {
50609
51827
  return { error: { code: -32602, message: "approve requires a string tool_name" } };
@@ -50622,7 +51840,7 @@ function startPermissionMcpServer(opts) {
50622
51840
  toolUseId: typeof args.tool_use_id === "string" ? args.tool_use_id : void 0,
50623
51841
  suggestions
50624
51842
  },
50625
- { requestTimeoutMs: timeoutMs }
51843
+ { requestTimeoutMs: timeoutMs2 }
50626
51844
  );
50627
51845
  const result = res.behavior === "allow" ? {
50628
51846
  behavior: "allow",
@@ -50642,7 +51860,7 @@ function startPermissionMcpServer(opts) {
50642
51860
  );
50643
51861
  }
50644
51862
  }
50645
- async function callAskUser(args, timeoutMs) {
51863
+ async function callAskUser(args, timeoutMs2) {
50646
51864
  const question = typeof args.question === "string" ? args.question : "";
50647
51865
  if (!question) {
50648
51866
  return { error: { code: -32602, message: "ask_user requires a string question" } };
@@ -50660,7 +51878,7 @@ function startPermissionMcpServer(opts) {
50660
51878
  choices,
50661
51879
  context
50662
51880
  },
50663
- { requestTimeoutMs: timeoutMs }
51881
+ { requestTimeoutMs: timeoutMs2 }
50664
51882
  );
50665
51883
  const text = res.behavior === "allow" && typeof res.answer === "string" ? res.answer : res.message ?? "No answer was given \u2014 use your best judgment.";
50666
51884
  return textResult(text);
@@ -55508,8 +56726,8 @@ async function readPackageScripts(cwd = process.cwd()) {
55508
56726
  return {};
55509
56727
  }
55510
56728
  }
55511
- function buildNativeCriteria(commands, timeoutMs) {
55512
- const pack = codingCriteriaPack({ ...commands, commandTimeoutMs: timeoutMs });
56729
+ function buildNativeCriteria(commands, timeoutMs2) {
56730
+ const pack = codingCriteriaPack({ ...commands, commandTimeoutMs: timeoutMs2 });
55513
56731
  return pack.criteria.filter(
55514
56732
  (c) => !(c.required && (!c.check || c.check.kind === "none"))
55515
56733
  );
@@ -55543,11 +56761,11 @@ function criterionId(check2, index) {
55543
56761
  const slug = check2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
55544
56762
  return `check-${index + 1}-${slug || "criterion"}`;
55545
56763
  }
55546
- function normalize4(text) {
56764
+ function normalize5(text) {
55547
56765
  return text.toLowerCase().replace(/\s+/g, " ").trim();
55548
56766
  }
55549
56767
  function matchResult(check2, byNormalized) {
55550
- const norm = normalize4(check2);
56768
+ const norm = normalize5(check2);
55551
56769
  const direct = byNormalized.get(norm);
55552
56770
  if (direct) return direct;
55553
56771
  for (const key of byNormalized.keys()) {
@@ -55559,7 +56777,7 @@ function matchResult(check2, byNormalized) {
55559
56777
  }
55560
56778
  function krakenResultsToContract(requiredChecks, results, now = Date.now()) {
55561
56779
  const byNormalized = /* @__PURE__ */ new Map();
55562
- for (const r of results ?? []) byNormalized.set(normalize4(r.check), r);
56780
+ for (const r of results ?? []) byNormalized.set(normalize5(r.check), r);
55563
56781
  const criteria = [];
55564
56782
  const verifications = [];
55565
56783
  requiredChecks.forEach((check2, i) => {
@@ -55595,16 +56813,16 @@ function sha256Hex2(input) {
55595
56813
  return createHash10("sha256").update(input).digest("hex");
55596
56814
  }
55597
56815
  function matchNoteToToolTrace(note, trace) {
55598
- const n = normalize4(note);
56816
+ const n = normalize5(note);
55599
56817
  if (!n) return null;
55600
56818
  for (let i = trace.length - 1; i >= 0; i--) {
55601
56819
  const t = trace[i];
55602
- const cmd = t.command ? normalize4(t.command) : "";
56820
+ const cmd = t.command ? normalize5(t.command) : "";
55603
56821
  if (cmd.length >= 4 && (n.includes(cmd) || cmd.includes(n))) return t;
55604
56822
  }
55605
56823
  for (let i = trace.length - 1; i >= 0; i--) {
55606
56824
  const t = trace[i];
55607
- const out = normalize4(t.output);
56825
+ const out = normalize5(t.output);
55608
56826
  if (!out) continue;
55609
56827
  if (n.length >= 8 && out.includes(n)) return t;
55610
56828
  const fragments = n.match(/\S*\d[\d.,/%]*\S*/g) ?? [];
@@ -55811,7 +57029,7 @@ ${detail}
55811
57029
  let settled = false;
55812
57030
  const askTimeoutMs = askUserTimeoutMs();
55813
57031
  let cancelAskTimeout = () => void 0;
55814
- const finish = (ok, note) => {
57032
+ const finish2 = (ok, note) => {
55815
57033
  if (settled) return;
55816
57034
  settled = true;
55817
57035
  cancelAskTimeout();
@@ -55820,7 +57038,7 @@ ${detail}
55820
57038
  resolve3(ok);
55821
57039
  };
55822
57040
  cancelAskTimeout = armPickerTimeout(
55823
- () => finish(
57041
+ () => finish2(
55824
57042
  false,
55825
57043
  `[permission] ask for "${req.toolName}" timed out after ${Math.round(
55826
57044
  askTimeoutMs / 1e3
@@ -55849,12 +57067,12 @@ ${detail}
55849
57067
  onAnswer: (value) => {
55850
57068
  const v = value.trim().toLowerCase();
55851
57069
  if (v === "deny" || v.startsWith("deny")) {
55852
- finish(false);
57070
+ finish2(false);
55853
57071
  return;
55854
57072
  }
55855
57073
  if (v === "always-tool" || v.includes("always") && v.includes("tool")) {
55856
57074
  grantSessionTool(req.toolName);
55857
- finish(
57075
+ finish2(
55858
57076
  true,
55859
57077
  `[permission] Granted "${req.toolName}" for this session (tool).`
55860
57078
  );
@@ -55863,19 +57081,19 @@ ${detail}
55863
57081
  if (v === "always-cat" || v.includes("always") && !v.includes("tool")) {
55864
57082
  for (const c of cats) grantSessionCategory(c);
55865
57083
  grantSessionTool(req.toolName);
55866
- finish(
57084
+ finish2(
55867
57085
  true,
55868
57086
  `[permission] Granted ${catLabel} (+ ${req.toolName}) for this session.`
55869
57087
  );
55870
57088
  return;
55871
57089
  }
55872
57090
  if (v === "allow" || v.startsWith("allow") || v === "yes" || v === "y" || v === "1") {
55873
- finish(true);
57091
+ finish2(true);
55874
57092
  return;
55875
57093
  }
55876
- finish(false);
57094
+ finish2(false);
55877
57095
  },
55878
- onCancel: () => finish(false)
57096
+ onCancel: () => finish2(false)
55879
57097
  });
55880
57098
  });
55881
57099
  }
@@ -56194,7 +57412,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
56194
57412
  let settled = false;
56195
57413
  const askTimeoutMs = askUserTimeoutMs();
56196
57414
  let cancelAskTimeout = () => void 0;
56197
- const finish = (value) => {
57415
+ const finish2 = (value) => {
56198
57416
  if (settled) return;
56199
57417
  settled = true;
56200
57418
  cancelAskTimeout();
@@ -56210,7 +57428,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
56210
57428
  )}s \u2014 proseguo con assunzione documentata (ZELARI_ASK_USER_TIMEOUT_MS).`,
56211
57429
  Date.now()
56212
57430
  );
56213
- finish(null);
57431
+ finish2(null);
56214
57432
  },
56215
57433
  askTimeoutMs
56216
57434
  );
@@ -56218,8 +57436,8 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
56218
57436
  kind: "clarification",
56219
57437
  title: req.question,
56220
57438
  items: choices.map((c) => ({ value: c, label: c })),
56221
- onAnswer: (value) => finish(value),
56222
- onCancel: () => finish(null)
57439
+ onAnswer: (value) => finish2(value),
57440
+ onCancel: () => finish2(null)
56223
57441
  });
56224
57442
  }) : void 0;
56225
57443
  const onPermissionAsk = setPicker2 ? createPermissionAskHandler({
@@ -57008,7 +58226,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57008
58226
  let settled = false;
57009
58227
  const askTimeoutMs = askUserTimeoutMs();
57010
58228
  let cancelAskTimeout = () => void 0;
57011
- const finish = (value) => {
58229
+ const finish2 = (value) => {
57012
58230
  if (settled) return;
57013
58231
  settled = true;
57014
58232
  cancelAskTimeout();
@@ -57024,7 +58242,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57024
58242
  )}s \u2014 il membro prosegue con assunzione documentata (ZELARI_ASK_USER_TIMEOUT_MS).`,
57025
58243
  Date.now()
57026
58244
  );
57027
- finish(null);
58245
+ finish2(null);
57028
58246
  },
57029
58247
  askTimeoutMs
57030
58248
  );
@@ -57032,8 +58250,8 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57032
58250
  kind: "clarification",
57033
58251
  title: req.question,
57034
58252
  items: choices.map((c) => ({ value: c, label: c })),
57035
- onAnswer: (value) => finish(value),
57036
- onCancel: () => finish(null)
58253
+ onAnswer: (value) => finish2(value),
58254
+ onCancel: () => finish2(null)
57037
58255
  });
57038
58256
  }) : void 0;
57039
58257
  let phaseRunMode = overrides.runMode ?? (workPhase === "plan" ? "design-phase" : "implementation");
@@ -57177,7 +58395,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57177
58395
  Date.now()
57178
58396
  );
57179
58397
  let settled = false;
57180
- const finish = (value) => {
58398
+ const finish2 = (value) => {
57181
58399
  if (settled) return;
57182
58400
  settled = true;
57183
58401
  setPicker2(null);
@@ -57187,8 +58405,8 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57187
58405
  kind: "clarification",
57188
58406
  title: req.question,
57189
58407
  items: choices.map((c) => ({ value: c, label: c })),
57190
- onAnswer: (value) => finish(value),
57191
- onCancel: () => finish(null)
58408
+ onAnswer: (value) => finish2(value),
58409
+ onCancel: () => finish2(null)
57192
58410
  });
57193
58411
  }) : void 0
57194
58412
  })) {
@@ -57793,7 +59011,7 @@ function usePermissionBroker(opts) {
57793
59011
  }
57794
59012
  let settled = false;
57795
59013
  let cancelWaitTimeout;
57796
- const finish = (value) => {
59014
+ const finish2 = (value) => {
57797
59015
  if (settled) return;
57798
59016
  settled = true;
57799
59017
  cancelWaitTimeout?.();
@@ -57809,7 +59027,7 @@ function usePermissionBroker(opts) {
57809
59027
  )}s \u2014 richiesta annullata.`,
57810
59028
  Date.now()
57811
59029
  );
57812
- finish(null);
59030
+ finish2(null);
57813
59031
  }, waitTimeoutMs);
57814
59032
  appendSystem(
57815
59033
  setMessages,
@@ -57821,8 +59039,8 @@ function usePermissionBroker(opts) {
57821
59039
  kind: "clarification",
57822
59040
  title: req.question,
57823
59041
  items: choices.map((c) => ({ value: c, label: c })),
57824
- onAnswer: (value) => finish(value),
57825
- onCancel: () => finish(null)
59042
+ onAnswer: (value) => finish2(value),
59043
+ onCancel: () => finish2(null)
57826
59044
  });
57827
59045
  });
57828
59046
  void startPermissionBroker(socketPath, {
@@ -61927,7 +63145,7 @@ function verifierReviewEnabled(selection = loadVerifierModelSelection(), env = p
61927
63145
  if (v === "1" || v === "true" || v === "on") return true;
61928
63146
  return selection.mode === "fixed";
61929
63147
  }
61930
- function makeVerifierCallModel(loadStream, identity, timeoutMs = 12e4) {
63148
+ function makeVerifierCallModel(loadStream, identity, timeoutMs2 = 12e4) {
61931
63149
  return async ({ system, user }) => {
61932
63150
  const stream = await loadStream(identity.provider, identity.model);
61933
63151
  if (!stream) {
@@ -61941,7 +63159,7 @@ function makeVerifierCallModel(loadStream, identity, timeoutMs = 12e4) {
61941
63159
  model: identity.model,
61942
63160
  provider: identity.provider,
61943
63161
  tools: [],
61944
- signal: AbortSignal.timeout(timeoutMs)
63162
+ signal: AbortSignal.timeout(timeoutMs2)
61945
63163
  });
61946
63164
  return { text, provider: identity.provider, model: identity.model };
61947
63165
  };
@@ -62098,6 +63316,18 @@ ${err.stack}` : "";
62098
63316
  if (opts.krakenGraph) {
62099
63317
  return runHeadlessKrakenGraph(opts, provider, model);
62100
63318
  }
63319
+ const { shouldRunGauntletHostLoop: shouldRunGauntletHostLoop2 } = await Promise.resolve().then(() => (init_policy(), policy_exports));
63320
+ if (shouldRunGauntletHostLoop2(opts)) {
63321
+ const { runHeadlessGauntlet: runHeadlessGauntlet2 } = await Promise.resolve().then(() => (init_run(), run_exports));
63322
+ return runHeadlessGauntlet2(opts, provider, model);
63323
+ }
63324
+ if (opts.gauntlet) {
63325
+ const why = opts.krakenGraph ? "kraken-graph owns dispatch" : (opts.phase ?? "build") === "plan" ? "PLAN is already write-stripped; host loop is BUILD-only" : "mode is not kraken-build";
63326
+ const line = `[gauntlet] flag set but host loop skipped (${why})`;
63327
+ if (opts.output === "json") emitEvent({ type: "log", message: line });
63328
+ else process.stderr.write(`[zelari-code --headless] ${line}
63329
+ `);
63330
+ }
62101
63331
  if (mode === "zelari") {
62102
63332
  return runHeadlessZelari(opts, provider, model, providerStream);
62103
63333
  }
@@ -62314,6 +63544,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
62314
63544
  resetKrakenTurnMetrics();
62315
63545
  const { registry: toolRegistry } = createBuiltinToolRegistry({
62316
63546
  planMode: planModeFromOpts(opts),
63547
+ gauntletParent: Boolean(opts.gauntlet) && !planModeFromOpts(opts),
62317
63548
  // Fase 1 (ADR-0020): anchor tentacles to the provider/model THIS run
62318
63549
  // resolved (--provider/--model opts or Desktop's selector), mirroring
62319
63550
  // what the kraken-graph path already does for its executor.