zelari-code 2.3.0 → 2.5.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 (49) hide show
  1. package/dist/cli/budget/historySummary.js +40 -2
  2. package/dist/cli/budget/historySummary.js.map +1 -1
  3. package/dist/cli/budget/modelContextBuilder.js +126 -0
  4. package/dist/cli/budget/modelContextBuilder.js.map +1 -0
  5. package/dist/cli/budget/persistCompact.js +67 -0
  6. package/dist/cli/budget/persistCompact.js.map +1 -0
  7. package/dist/cli/budget/tokenBudget.js +47 -13
  8. package/dist/cli/budget/tokenBudget.js.map +1 -1
  9. package/dist/cli/compaction.js +5 -0
  10. package/dist/cli/compaction.js.map +1 -1
  11. package/dist/cli/gauntlet/blind.js +38 -0
  12. package/dist/cli/gauntlet/blind.js.map +1 -0
  13. package/dist/cli/gauntlet/complete.js +90 -0
  14. package/dist/cli/gauntlet/complete.js.map +1 -0
  15. package/dist/cli/gauntlet/decompose.js +163 -0
  16. package/dist/cli/gauntlet/decompose.js.map +1 -0
  17. package/dist/cli/gauntlet/events.js +30 -0
  18. package/dist/cli/gauntlet/events.js.map +1 -0
  19. package/dist/cli/gauntlet/loop.js +142 -0
  20. package/dist/cli/gauntlet/loop.js.map +1 -0
  21. package/dist/cli/gauntlet/policy.js +61 -0
  22. package/dist/cli/gauntlet/policy.js.map +1 -0
  23. package/dist/cli/gauntlet/prompts.js +70 -0
  24. package/dist/cli/gauntlet/prompts.js.map +1 -0
  25. package/dist/cli/gauntlet/run.js +213 -0
  26. package/dist/cli/gauntlet/run.js.map +1 -0
  27. package/dist/cli/gauntlet/schedule.js +34 -0
  28. package/dist/cli/gauntlet/schedule.js.map +1 -0
  29. package/dist/cli/gauntlet/verdict.js +58 -0
  30. package/dist/cli/gauntlet/verdict.js.map +1 -0
  31. package/dist/cli/headless.js +11 -0
  32. package/dist/cli/headless.js.map +1 -1
  33. package/dist/cli/headlessSpine.js +3 -2
  34. package/dist/cli/headlessSpine.js.map +1 -1
  35. package/dist/cli/hooks/historyCompaction.js +56 -8
  36. package/dist/cli/hooks/historyCompaction.js.map +1 -1
  37. package/dist/cli/hooks/useChatTurn.js +52 -68
  38. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  39. package/dist/cli/main.bundled.js +2654 -908
  40. package/dist/cli/main.bundled.js.map +4 -4
  41. package/dist/cli/metrics.js +16 -0
  42. package/dist/cli/metrics.js.map +1 -1
  43. package/dist/cli/runHeadless.js +109 -10
  44. package/dist/cli/runHeadless.js.map +1 -1
  45. package/dist/cli/sessionSpine.js +65 -7
  46. package/dist/cli/sessionSpine.js.map +1 -1
  47. package/dist/cli/toolRegistry.js +13 -6
  48. package/dist/cli/toolRegistry.js.map +1 -1
  49. 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;
@@ -19419,9 +19419,9 @@ function assertHttpUrl(raw) {
19419
19419
  }
19420
19420
  return url2;
19421
19421
  }
19422
- async function fetchWithTimeout(url2, init, outerSignal, timeoutMs) {
19422
+ async function fetchWithTimeout(url2, init, outerSignal, timeoutMs2) {
19423
19423
  const ctrl = new AbortController();
19424
- 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);
19425
19425
  const onOuterAbort = () => ctrl.abort(outerSignal?.reason);
19426
19426
  outerSignal?.addEventListener("abort", onOuterAbort, { once: true });
19427
19427
  try {
@@ -23327,8 +23327,8 @@ var init_lifecycleHookRunner = __esm({
23327
23327
  /** Execute a single hook, swallowing every failure into an allow. */
23328
23328
  async runHookSafely(hook, payload) {
23329
23329
  try {
23330
- const timeoutMs = hook.timeoutMs ?? this.defaultTimeoutMs;
23331
- 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);
23332
23332
  const decision = parseJson(raw);
23333
23333
  if (!decision) {
23334
23334
  this.logger(`hook "${hook.name}" returned invalid JSON (fail-open): ${raw.slice(0, 200)}`);
@@ -23345,7 +23345,7 @@ var init_lifecycleHookRunner = __esm({
23345
23345
  }
23346
23346
  }
23347
23347
  /** Spawn `command`, write JSON payload to stdin, read stdout until close. */
23348
- execCommand(command, payload, timeoutMs, cwd) {
23348
+ execCommand(command, payload, timeoutMs2, cwd) {
23349
23349
  return new Promise((resolve3, reject) => {
23350
23350
  const child = spawn2(command, {
23351
23351
  shell: true,
@@ -23357,8 +23357,8 @@ var init_lifecycleHookRunner = __esm({
23357
23357
  let stderr = "";
23358
23358
  const timer = setTimeout(() => {
23359
23359
  child.kill("SIGKILL");
23360
- reject(new Error(`timed out after ${timeoutMs}ms`));
23361
- }, timeoutMs);
23360
+ reject(new Error(`timed out after ${timeoutMs2}ms`));
23361
+ }, timeoutMs2);
23362
23362
  child.stdout?.on("data", (d) => {
23363
23363
  stdout += d.toString();
23364
23364
  });
@@ -23384,9 +23384,9 @@ var init_lifecycleHookRunner = __esm({
23384
23384
  });
23385
23385
  }
23386
23386
  /** POST JSON payload; resolve with the response body text. */
23387
- async postHttp(url2, payload, timeoutMs) {
23387
+ async postHttp(url2, payload, timeoutMs2) {
23388
23388
  const ctrl = new AbortController();
23389
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
23389
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs2);
23390
23390
  try {
23391
23391
  const res = await fetch(url2, {
23392
23392
  method: "POST",
@@ -27962,7 +27962,7 @@ function deepFreeze(value) {
27962
27962
  return Object.freeze(value);
27963
27963
  }
27964
27964
  async function runInSandbox(opts) {
27965
- const { bundleCode, sdk, timeoutMs } = opts;
27965
+ const { bundleCode, sdk, timeoutMs: timeoutMs2 } = opts;
27966
27966
  if (!opts.skipFootgunScan) {
27967
27967
  const footguns = scanForFootguns(bundleCode);
27968
27968
  if (footguns.length > 0) {
@@ -27982,7 +27982,7 @@ async function runInSandbox(opts) {
27982
27982
  });
27983
27983
  const start = Date.now();
27984
27984
  const vmOpts = {
27985
- timeout: Math.max(1, timeoutMs),
27985
+ timeout: Math.max(1, timeoutMs2),
27986
27986
  displayErrors: true,
27987
27987
  breakOnSigint: true
27988
27988
  };
@@ -27992,7 +27992,7 @@ async function runInSandbox(opts) {
27992
27992
  } catch (err) {
27993
27993
  const msg = err && typeof err === "object" && "message" in err ? String(err.message ?? "") : String(err);
27994
27994
  if (/timed out/i.test(msg)) {
27995
- throw new PlanError("budget_exceeded", `script exceeded ${timeoutMs}ms budget`);
27995
+ throw new PlanError("budget_exceeded", `script exceeded ${timeoutMs2}ms budget`);
27996
27996
  }
27997
27997
  if (opts.signal?.aborted) {
27998
27998
  throw new PlanError("cancelled", "script aborted by host");
@@ -28340,6 +28340,292 @@ var init_types8 = __esm({
28340
28340
  }
28341
28341
  });
28342
28342
 
28343
+ // packages/core/dist/session/compactionState.js
28344
+ function asPositiveInt(value) {
28345
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
28346
+ }
28347
+ function recordOf(value) {
28348
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
28349
+ }
28350
+ function recordsOf(value) {
28351
+ return Array.isArray(value) ? value.map(recordOf).filter((v) => v !== void 0) : [];
28352
+ }
28353
+ function stringsOf(value) {
28354
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
28355
+ }
28356
+ function addBounded(target, value, maxLength = 260) {
28357
+ const normalized = value.trim().replace(/\\/g, "/");
28358
+ if (normalized && normalized.length <= maxLength)
28359
+ target.add(normalized);
28360
+ }
28361
+ function collectPaths(value, target, depth = 0) {
28362
+ if (depth > 4 || value === null || value === void 0)
28363
+ return;
28364
+ if (Array.isArray(value)) {
28365
+ for (const item of value)
28366
+ collectPaths(item, target, depth + 1);
28367
+ return;
28368
+ }
28369
+ const record2 = recordOf(value);
28370
+ if (record2) {
28371
+ for (const [key, item] of Object.entries(record2)) {
28372
+ if (typeof item === "string" && ["path", "file", "filepath", "filePath", "file_path", "target", "cwd"].includes(key)) {
28373
+ addBounded(target, item);
28374
+ } else {
28375
+ collectPaths(item, target, depth + 1);
28376
+ }
28377
+ }
28378
+ return;
28379
+ }
28380
+ if (typeof value !== "string")
28381
+ return;
28382
+ const pathLike = /(?:^|[\s"'])((?:[\w.@-]+[\\/])+[\w.@-]+\.[A-Za-z0-9]{1,10})/g;
28383
+ let match;
28384
+ while ((match = pathLike.exec(value)) !== null && target.size < 64) {
28385
+ addBounded(target, match[1]);
28386
+ }
28387
+ }
28388
+ function evidenceFromResults(results) {
28389
+ const seen = /* @__PURE__ */ new Set();
28390
+ const out = [];
28391
+ for (const result of results) {
28392
+ for (const raw of recordsOf(result.evidence)) {
28393
+ const seq = asPositiveInt(raw.seq);
28394
+ const tier = typeof raw.tier === "string" ? raw.tier : void 0;
28395
+ const ref = typeof raw.ref === "string" ? raw.ref : void 0;
28396
+ const digest = typeof raw.digest === "string" ? raw.digest : void 0;
28397
+ const capturedAt = typeof raw.capturedAt === "number" && Number.isInteger(raw.capturedAt) ? raw.capturedAt : void 0;
28398
+ if (seq === void 0 && tier === void 0 && ref === void 0 && digest === void 0)
28399
+ continue;
28400
+ const key = [seq ?? "", tier ?? "", ref ?? "", digest ?? ""].join("|");
28401
+ if (seen.has(key))
28402
+ continue;
28403
+ seen.add(key);
28404
+ out.push({
28405
+ ...seq !== void 0 ? { seq } : {},
28406
+ ...tier !== void 0 ? { tier } : {},
28407
+ ...ref !== void 0 ? { ref } : {},
28408
+ ...digest !== void 0 ? { digest } : {},
28409
+ ...capturedAt !== void 0 ? { capturedAt } : {}
28410
+ });
28411
+ }
28412
+ }
28413
+ return out;
28414
+ }
28415
+ function buildCompactionStateSnapshot(events, toSeq) {
28416
+ const scoped = events.filter((event) => event.seq <= toSeq);
28417
+ const latestVerification = [...scoped].reverse().find((event) => event.kind === "verification.run");
28418
+ const verificationData = latestVerification?.data ?? {};
28419
+ const native = recordOf(verificationData.native);
28420
+ const results = recordsOf(native?.results ?? verificationData.results);
28421
+ const criteriaRaw = recordsOf(native?.criteria ?? verificationData.criteria);
28422
+ const statusById = new Map(results.filter((result) => typeof result.criterionId === "string").map((result) => [String(result.criterionId), String(result.status ?? "unknown")]));
28423
+ const evidenceState = recordOf(verificationData.evidence);
28424
+ const satisfied = stringsOf(evidenceState?.satisfied);
28425
+ const unsatisfiedRaw = recordsOf(evidenceState?.unsatisfied);
28426
+ const activeCriteria = criteriaRaw.filter((criterion) => typeof criterion.id === "string").map((criterion) => ({
28427
+ id: String(criterion.id),
28428
+ required: criterion.required !== false,
28429
+ ...statusById.has(String(criterion.id)) ? { status: statusById.get(String(criterion.id)) } : {}
28430
+ }));
28431
+ if (activeCriteria.length === 0) {
28432
+ const ids = /* @__PURE__ */ new Set([
28433
+ ...results.map((result) => String(result.criterionId ?? "")).filter(Boolean),
28434
+ ...satisfied,
28435
+ ...unsatisfiedRaw.map((issue2) => String(issue2.id ?? "")).filter(Boolean)
28436
+ ]);
28437
+ for (const id of ids) {
28438
+ activeCriteria.push({
28439
+ id,
28440
+ required: true,
28441
+ status: statusById.get(id) ?? (satisfied.includes(id) ? "pass" : "unknown")
28442
+ });
28443
+ }
28444
+ }
28445
+ const unresolvedIssues = unsatisfiedRaw.length > 0 ? unsatisfiedRaw.filter((issue2) => typeof issue2.id === "string").map((issue2) => ({
28446
+ id: String(issue2.id),
28447
+ status: String(issue2.status ?? "unknown"),
28448
+ ...typeof issue2.reason === "string" ? { reason: issue2.reason } : {}
28449
+ })) : results.filter((result) => String(result.status ?? "unknown") !== "pass").map((result) => ({
28450
+ id: String(result.criterionId ?? "unknown"),
28451
+ status: String(result.status ?? "unknown"),
28452
+ ...typeof result.detail === "string" ? { reason: result.detail } : {}
28453
+ }));
28454
+ const affectedFiles = /* @__PURE__ */ new Set();
28455
+ for (const event of scoped.slice(-500)) {
28456
+ if (event.kind === "tool.call" || event.kind === "tool.result" || event.kind === "verification.evidence") {
28457
+ collectPaths(event.data, affectedFiles);
28458
+ }
28459
+ }
28460
+ const userConstraints = /* @__PURE__ */ new Set();
28461
+ for (const event of scoped) {
28462
+ if (event.kind !== "user.message")
28463
+ continue;
28464
+ for (const constraint of stringsOf(event.data.constraints ?? event.data.userConstraints)) {
28465
+ addBounded(userConstraints, constraint, 320);
28466
+ }
28467
+ const messageText = typeof event.data.text === "string" ? event.data.text : "";
28468
+ for (const line of messageText.split(/\r?\n/)) {
28469
+ if (CONSTRAINT_RE.test(line))
28470
+ addBounded(userConstraints, line, 320);
28471
+ if (userConstraints.size >= 12)
28472
+ break;
28473
+ }
28474
+ }
28475
+ const lastMissionPhase = [...scoped].reverse().find((event) => event.kind === "mission.phase");
28476
+ const lastMissionAdvice = [...scoped].reverse().find((event) => event.kind === "mission.progress");
28477
+ const missionState = lastMissionPhase || lastMissionAdvice ? {
28478
+ ...typeof lastMissionPhase?.data.phase === "string" ? { phase: lastMissionPhase.data.phase } : {},
28479
+ ...typeof lastMissionAdvice?.data.recommendation === "string" ? { recommendation: lastMissionAdvice.data.recommendation } : {},
28480
+ ...stringsOf(lastMissionAdvice?.data.blockers).length > 0 ? { blockers: stringsOf(lastMissionAdvice?.data.blockers) } : {}
28481
+ } : void 0;
28482
+ return {
28483
+ version: 1,
28484
+ activeCriteria,
28485
+ unresolvedIssues,
28486
+ ...latestVerification ? {
28487
+ latestVerification: {
28488
+ seq: latestVerification.seq,
28489
+ ...typeof verificationData.verdict === "string" ? { verdict: verificationData.verdict } : {},
28490
+ ...typeof verificationData.summary === "string" ? { summary: verificationData.summary } : {}
28491
+ }
28492
+ } : {},
28493
+ retainedEvidenceRefs: evidenceFromResults(results),
28494
+ affectedFiles: [...affectedFiles].slice(0, 64),
28495
+ userConstraints: [...userConstraints].slice(-12),
28496
+ ...missionState ? { missionState } : {}
28497
+ };
28498
+ }
28499
+ function formatCompactionStateSnapshot(snapshot) {
28500
+ const lines = ['<compaction-state version="1">'];
28501
+ if (snapshot.activeCriteria.length > 0) {
28502
+ lines.push("activeCriteria: " + JSON.stringify(snapshot.activeCriteria));
28503
+ }
28504
+ if (snapshot.unresolvedIssues.length > 0) {
28505
+ lines.push("unresolvedIssues: " + JSON.stringify(snapshot.unresolvedIssues));
28506
+ }
28507
+ if (snapshot.latestVerification) {
28508
+ lines.push("latestVerification: " + JSON.stringify(snapshot.latestVerification));
28509
+ }
28510
+ if (snapshot.affectedFiles.length > 0) {
28511
+ lines.push("affectedFiles: " + JSON.stringify(snapshot.affectedFiles));
28512
+ }
28513
+ if (snapshot.userConstraints.length > 0) {
28514
+ lines.push("userConstraints: " + JSON.stringify(snapshot.userConstraints));
28515
+ }
28516
+ if (snapshot.missionState) {
28517
+ lines.push("missionState: " + JSON.stringify(snapshot.missionState));
28518
+ }
28519
+ lines.push("</compaction-state>");
28520
+ return lines.join("\n");
28521
+ }
28522
+ var CONSTRAINT_RE;
28523
+ var init_compactionState = __esm({
28524
+ "packages/core/dist/session/compactionState.js"() {
28525
+ "use strict";
28526
+ CONSTRAINT_RE = /\b(must|never|required|only|do not|don't|constraint|vincolo|deve|devono|non\s+deve|senza)\b/i;
28527
+ }
28528
+ });
28529
+
28530
+ // packages/core/dist/session/compaction.js
28531
+ function asPositiveInt2(value) {
28532
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
28533
+ }
28534
+ function checkpointContent(data) {
28535
+ const cp = data.checkpoint;
28536
+ if (cp && typeof cp === "object") {
28537
+ const content = cp.content;
28538
+ if (typeof content === "string")
28539
+ return content;
28540
+ }
28541
+ if (typeof data.summary === "string")
28542
+ return data.summary;
28543
+ return "[session compacted]";
28544
+ }
28545
+ function checkpointRole(data) {
28546
+ const cp = data.checkpoint;
28547
+ if (cp && typeof cp === "object") {
28548
+ const role = cp.role;
28549
+ if (role === "user" || role === "system")
28550
+ return role;
28551
+ }
28552
+ return "system";
28553
+ }
28554
+ function strategyOf(data) {
28555
+ return data.strategy === "extractive" || data.strategy === "llm" ? data.strategy : void 0;
28556
+ }
28557
+ function parseCompactedEvent(event) {
28558
+ if (event.kind !== "session.compacted")
28559
+ return null;
28560
+ const fromSeq = asPositiveInt2(event.data.fromSeq);
28561
+ const toSeq = asPositiveInt2(event.data.toSeq);
28562
+ if (fromSeq === void 0 || toSeq === void 0 || fromSeq > toSeq)
28563
+ return null;
28564
+ if (event.seq <= toSeq)
28565
+ return null;
28566
+ const sourceRaw = event.data.sourceEventSeqs;
28567
+ const sourceEventSeqs = Array.isArray(sourceRaw) ? sourceRaw.filter((n) => typeof n === "number" && Number.isInteger(n) && n > 0) : void 0;
28568
+ return {
28569
+ seq: event.seq,
28570
+ fromSeq,
28571
+ toSeq,
28572
+ role: checkpointRole(event.data),
28573
+ content: checkpointContent(event.data),
28574
+ ...strategyOf(event.data) ? { strategy: strategyOf(event.data) } : {},
28575
+ ...sourceEventSeqs && sourceEventSeqs.length > 0 ? { sourceEventSeqs } : {}
28576
+ };
28577
+ }
28578
+ function coveringCompactions(events) {
28579
+ const effective = events.map(parseCompactedEvent).filter((c) => c !== null).map((c) => ({
28580
+ ...c,
28581
+ ...c.sourceEventSeqs ? { sourceEventSeqs: [...c.sourceEventSeqs] } : {}
28582
+ })).sort((a, b) => a.seq - b.seq);
28583
+ let changed = true;
28584
+ while (changed) {
28585
+ changed = false;
28586
+ for (let i = 0; i < effective.length; i++) {
28587
+ const earlier = effective[i];
28588
+ let target;
28589
+ for (let j = i + 1; j < effective.length; j++) {
28590
+ const later = effective[j];
28591
+ if (earlier.seq >= later.fromSeq && earlier.seq <= later.toSeq) {
28592
+ target = later;
28593
+ }
28594
+ }
28595
+ if (!target)
28596
+ continue;
28597
+ target.fromSeq = Math.min(target.fromSeq, earlier.fromSeq);
28598
+ target.toSeq = Math.max(target.toSeq, earlier.toSeq);
28599
+ if (earlier.sourceEventSeqs?.length) {
28600
+ target.sourceEventSeqs = [
28601
+ .../* @__PURE__ */ new Set([...target.sourceEventSeqs ?? [], ...earlier.sourceEventSeqs])
28602
+ ];
28603
+ }
28604
+ effective.splice(i, 1);
28605
+ changed = true;
28606
+ break;
28607
+ }
28608
+ }
28609
+ return effective;
28610
+ }
28611
+ function shadowedSeqSet(coverings) {
28612
+ const set2 = /* @__PURE__ */ new Set();
28613
+ for (const c of coverings) {
28614
+ for (let s = c.fromSeq; s <= c.toSeq; s++)
28615
+ set2.add(s);
28616
+ }
28617
+ return set2;
28618
+ }
28619
+ function isSeqShadowed(seq, coverings) {
28620
+ return coverings.some((c) => seq >= c.fromSeq && seq <= c.toSeq);
28621
+ }
28622
+ var init_compaction = __esm({
28623
+ "packages/core/dist/session/compaction.js"() {
28624
+ "use strict";
28625
+ init_compactionState();
28626
+ }
28627
+ });
28628
+
28343
28629
  // packages/core/dist/session/modelSurface.js
28344
28630
  function isModelSurfaceEvent(event) {
28345
28631
  return MODEL_SURFACE_KINDS.has(event.kind);
@@ -28348,8 +28634,33 @@ function asString(value) {
28348
28634
  return typeof value === "string" ? value : void 0;
28349
28635
  }
28350
28636
  function deriveMessages(events, options = {}) {
28637
+ const coverings = coveringCompactions(events);
28638
+ const orderedCoverings = [...coverings].sort((a, b) => a.fromSeq - b.fromSeq || a.seq - b.seq);
28639
+ const compactBySeq = new Map(coverings.map((c) => [c.seq, c]));
28351
28640
  const messages = [];
28641
+ let nextCheckpoint = 0;
28642
+ const pushCheckpoint = (compact) => {
28643
+ messages.push({
28644
+ role: compact.role,
28645
+ content: compact.content,
28646
+ seq: compact.seq,
28647
+ compactedFromSeq: compact.fromSeq,
28648
+ compactedToSeq: compact.toSeq,
28649
+ ...compact.sourceEventSeqs ? { sourceEventSeqs: compact.sourceEventSeqs } : {}
28650
+ });
28651
+ };
28652
+ const pushDueCheckpoints = (seq) => {
28653
+ while (nextCheckpoint < orderedCoverings.length && orderedCoverings[nextCheckpoint].fromSeq <= seq) {
28654
+ pushCheckpoint(orderedCoverings[nextCheckpoint]);
28655
+ nextCheckpoint += 1;
28656
+ }
28657
+ };
28352
28658
  for (const e of events) {
28659
+ pushDueCheckpoints(e.seq);
28660
+ if (compactBySeq.has(e.seq))
28661
+ continue;
28662
+ if (isSeqShadowed(e.seq, coverings))
28663
+ continue;
28353
28664
  if (!isModelSurfaceEvent(e))
28354
28665
  continue;
28355
28666
  const d = e.data;
@@ -28394,6 +28705,7 @@ function deriveMessages(events, options = {}) {
28394
28705
  break;
28395
28706
  }
28396
28707
  }
28708
+ pushDueCheckpoints(Number.POSITIVE_INFINITY);
28397
28709
  return messages;
28398
28710
  }
28399
28711
  function pairToolCalls(events) {
@@ -28421,6 +28733,7 @@ var MODEL_SURFACE_KINDS;
28421
28733
  var init_modelSurface = __esm({
28422
28734
  "packages/core/dist/session/modelSurface.js"() {
28423
28735
  "use strict";
28736
+ init_compaction();
28424
28737
  MODEL_SURFACE_KINDS = /* @__PURE__ */ new Set([
28425
28738
  "user.message",
28426
28739
  "assistant.message",
@@ -28438,6 +28751,14 @@ function derivedToAgentMessages(messages) {
28438
28751
  const agent = { role: m.role, content: m.content };
28439
28752
  if (m.toolCallId !== void 0)
28440
28753
  agent.toolCallId = m.toolCallId;
28754
+ if (m.seq !== void 0)
28755
+ agent.seq = m.seq;
28756
+ if (m.compactedFromSeq !== void 0)
28757
+ agent.compactedFromSeq = m.compactedFromSeq;
28758
+ if (m.compactedToSeq !== void 0)
28759
+ agent.compactedToSeq = m.compactedToSeq;
28760
+ if (m.sourceEventSeqs !== void 0)
28761
+ agent.sourceEventSeqs = [...m.sourceEventSeqs];
28441
28762
  out.push(agent);
28442
28763
  }
28443
28764
  return out;
@@ -29081,8 +29402,97 @@ function validateSessionTrace(events, mode = "minimal") {
29081
29402
  message: `session.ended seq ${firstEnded.seq} precedes verification.run seq ${firstVerification.seq}`
29082
29403
  });
29083
29404
  }
29405
+ pushCompactionViolations(events, knownSeq, pairs, violations);
29084
29406
  return violations;
29085
29407
  }
29408
+ function pushCompactionViolations(events, knownSeq, pairs, violations) {
29409
+ for (const e of events) {
29410
+ if (e.kind !== "session.compacted")
29411
+ continue;
29412
+ const fromSeq = asSeq(e.data.fromSeq);
29413
+ const toSeq = asSeq(e.data.toSeq);
29414
+ if (fromSeq === void 0 && toSeq === void 0)
29415
+ continue;
29416
+ if (fromSeq === void 0 || toSeq === void 0 || fromSeq > toSeq) {
29417
+ violations.push({
29418
+ code: "COMPACTION_RANGE_INVALID",
29419
+ seq: e.seq,
29420
+ message: `session.compacted seq ${e.seq} has invalid fromSeq/toSeq`
29421
+ });
29422
+ continue;
29423
+ }
29424
+ if (e.seq <= toSeq) {
29425
+ violations.push({
29426
+ code: "COMPACTION_EVENT_INSIDE_RANGE",
29427
+ seq: e.seq,
29428
+ message: `session.compacted seq ${e.seq} must be > toSeq ${toSeq}`
29429
+ });
29430
+ }
29431
+ if (!knownSeq.has(fromSeq) || !knownSeq.has(toSeq)) {
29432
+ violations.push({
29433
+ code: "COMPACTION_BOUNDARY_SEQ_MISSING",
29434
+ seq: e.seq,
29435
+ message: `session.compacted seq ${e.seq} range endpoints must exist in the trace`
29436
+ });
29437
+ }
29438
+ const checkpoint = e.data.checkpoint;
29439
+ if (!checkpoint || typeof checkpoint !== "object" || !["user", "system"].includes(String(checkpoint.role ?? "")) || typeof checkpoint.content !== "string") {
29440
+ violations.push({
29441
+ code: "COMPACTION_CHECKPOINT_INVALID",
29442
+ seq: e.seq,
29443
+ message: `session.compacted seq ${e.seq} must carry a user/system checkpoint with string content`
29444
+ });
29445
+ }
29446
+ const sourceRaw = e.data.sourceEventSeqs;
29447
+ if (Array.isArray(sourceRaw)) {
29448
+ for (const raw of sourceRaw) {
29449
+ const s = asSeq(raw);
29450
+ if (s === void 0) {
29451
+ violations.push({
29452
+ code: "COMPACTION_SOURCE_SEQ_INVALID",
29453
+ seq: e.seq,
29454
+ message: "sourceEventSeqs entries must be positive integers"
29455
+ });
29456
+ } else if (!knownSeq.has(s)) {
29457
+ violations.push({
29458
+ code: "COMPACTION_SOURCE_SEQ_MISSING",
29459
+ seq: e.seq,
29460
+ message: `sourceEventSeqs ${s} is not an event in this trace`
29461
+ });
29462
+ } else if (s < fromSeq || s > toSeq) {
29463
+ violations.push({
29464
+ code: "COMPACTION_SOURCE_SEQ_OUTSIDE_RANGE",
29465
+ seq: e.seq,
29466
+ message: `sourceEventSeqs ${s} is outside ${fromSeq}..${toSeq}`
29467
+ });
29468
+ }
29469
+ }
29470
+ }
29471
+ for (const pair of pairs) {
29472
+ const callSeq = pair.call.seq;
29473
+ const callInside = callSeq >= fromSeq && callSeq <= toSeq;
29474
+ if (!pair.result) {
29475
+ if (callInside) {
29476
+ violations.push({
29477
+ code: "COMPACTION_ACTIVE_TOOL_CALL",
29478
+ seq: e.seq,
29479
+ message: `tool.call seq ${callSeq} is compacted before a result or interruption`
29480
+ });
29481
+ }
29482
+ continue;
29483
+ }
29484
+ const resultSeq = pair.result.seq;
29485
+ const resultInside = resultSeq >= fromSeq && resultSeq <= toSeq;
29486
+ if (callInside !== resultInside) {
29487
+ violations.push({
29488
+ code: "COMPACTION_TOOL_PAIR_SPLIT",
29489
+ seq: e.seq,
29490
+ message: `tool pair ${callSeq}/${resultSeq} is split by range ${fromSeq}..${toSeq}`
29491
+ });
29492
+ }
29493
+ }
29494
+ }
29495
+ }
29086
29496
  var init_invariants = __esm({
29087
29497
  "packages/core/dist/session/invariants.js"() {
29088
29498
  "use strict";
@@ -29096,6 +29506,7 @@ var init_session = __esm({
29096
29506
  "use strict";
29097
29507
  init_types8();
29098
29508
  init_modelSurface();
29509
+ init_compaction();
29099
29510
  init_agentAdapter();
29100
29511
  init_writer();
29101
29512
  init_replay();
@@ -29237,7 +29648,7 @@ var init_nodeProviders = __esm({
29237
29648
  async exec(command, options = {}) {
29238
29649
  const started = Date.now();
29239
29650
  const cwd = this.workspace.resolve(options.cwd ?? ".");
29240
- const timeoutMs = options.timeoutMs ?? this.defaults.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
29651
+ const timeoutMs2 = options.timeoutMs ?? this.defaults.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
29241
29652
  const maxChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
29242
29653
  const isWindows = process.platform === "win32";
29243
29654
  const file2 = isWindows ? "cmd.exe" : "/bin/sh";
@@ -29260,7 +29671,7 @@ var init_nodeProviders = __esm({
29260
29671
  const timer = setTimeout(() => {
29261
29672
  timedOut = true;
29262
29673
  killTree(child);
29263
- }, timeoutMs);
29674
+ }, timeoutMs2);
29264
29675
  child.stdout?.on("data", (d) => {
29265
29676
  stdout = append(stdout, d.toString("utf-8"));
29266
29677
  });
@@ -30198,9 +30609,9 @@ var init_sessionEvidence = __esm({
30198
30609
 
30199
30610
  // packages/core/dist/verification/criteriaPack.v1.js
30200
30611
  function codingCriteriaPack(options = {}) {
30201
- const timeoutMs = options.commandTimeoutMs ?? 6e5;
30612
+ const timeoutMs2 = options.commandTimeoutMs ?? 6e5;
30202
30613
  const withDefault = (o, d) => o === void 0 ? d : o;
30203
- const command = (command2) => command2 ? { kind: "command", command: command2, timeoutMs } : void 0;
30614
+ const command = (command2) => command2 ? { kind: "command", command: command2, timeoutMs: timeoutMs2 } : void 0;
30204
30615
  const criteria = [
30205
30616
  {
30206
30617
  id: "correctness.error-signals",
@@ -30764,6 +31175,7 @@ __export(dist_exports, {
30764
31175
  applyRetryIfMissing: () => applyRetryIfMissing,
30765
31176
  auditDegradedBanner: () => auditDegradedBanner,
30766
31177
  auditSynthesisTiers: () => auditSynthesisTiers,
31178
+ buildCompactionStateSnapshot: () => buildCompactionStateSnapshot,
30767
31179
  buildCouncilCompletion: () => buildCouncilCompletion,
30768
31180
  buildCustomParameters: () => buildCustomParameters,
30769
31181
  buildDeliveryFixPrompt: () => buildDeliveryFixPrompt,
@@ -30802,6 +31214,7 @@ __export(dist_exports, {
30802
31214
  councilTierFromSize: () => councilTierFromSize,
30803
31215
  countByStatus: () => countByStatus,
30804
31216
  countEmittedWriteTools: () => countEmittedWriteTools,
31217
+ coveringCompactions: () => coveringCompactions,
30805
31218
  createBrainEvent: () => createBrainEvent,
30806
31219
  createDefaultSystemPromptConfig: () => createDefaultSystemPromptConfig,
30807
31220
  createExecutionContext: () => createExecutionContext,
@@ -30834,6 +31247,7 @@ __export(dist_exports, {
30834
31247
  findSkillsByIds: () => findSkillsByIds,
30835
31248
  findSkillsByTag: () => findSkillsByTag,
30836
31249
  forkSession: () => forkSession,
31250
+ formatCompactionStateSnapshot: () => formatCompactionStateSnapshot,
30837
31251
  formatLessonsForContext: () => formatLessonsForContext,
30838
31252
  getAgent: () => getAgent,
30839
31253
  getAllTools: () => getAllTools,
@@ -30881,6 +31295,7 @@ __export(dist_exports, {
30881
31295
  isGeneratedPath: () => isGeneratedPath,
30882
31296
  isModelSurfaceEvent: () => isModelSurfaceEvent,
30883
31297
  isReviewerKind: () => isReviewerKind,
31298
+ isSeqShadowed: () => isSeqShadowed,
30884
31299
  isSettled: () => isSettled,
30885
31300
  isStatusTheaterUnit: () => isStatusTheaterUnit,
30886
31301
  isValidTool: () => isValidTool,
@@ -30903,6 +31318,7 @@ __export(dist_exports, {
30903
31318
  normalizeToolName: () => normalizeToolName,
30904
31319
  pairToolCalls: () => pairToolCalls,
30905
31320
  parseClarificationRequest: () => parseClarificationRequest,
31321
+ parseCompactedEvent: () => parseCompactedEvent,
30906
31322
  parseEvidenceTier: () => parseEvidenceTier,
30907
31323
  parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
30908
31324
  parseNameOnlyDiff: () => parseNameOnlyDiff,
@@ -30959,6 +31375,7 @@ __export(dist_exports, {
30959
31375
  selectParallelWave: () => selectParallelWave,
30960
31376
  setWorkspaceStubs: () => setWorkspaceStubs,
30961
31377
  sha256Hex: () => sha256Hex,
31378
+ shadowedSeqSet: () => shadowedSeqSet,
30962
31379
  shouldRetryMember: () => shouldRetryMember,
30963
31380
  sideEffectForTool: () => sideEffectForTool,
30964
31381
  slugify: () => slugify2,
@@ -31302,12 +31719,46 @@ function mapBrainEventToSpine(ev) {
31302
31719
  durationMs: ev.durationMs
31303
31720
  }
31304
31721
  };
31305
- case "session_compacted":
31306
- return {
31307
- kind: "session.compacted",
31308
- actor: ACTOR_SYSTEM,
31309
- data: { summary: ev.summary ?? "" }
31310
- };
31722
+ case "session_compacted": {
31723
+ const compact = ev;
31724
+ const data = { summary: compact.summary ?? "" };
31725
+ if (typeof compact.messagesRemoved === "number") data.messagesRemoved = compact.messagesRemoved;
31726
+ if (typeof compact.fromSeq === "number" && typeof compact.toSeq === "number") {
31727
+ data.fromSeq = compact.fromSeq;
31728
+ data.toSeq = compact.toSeq;
31729
+ }
31730
+ if (compact.checkpoint && typeof compact.checkpoint === "object") data.checkpoint = compact.checkpoint;
31731
+ if (compact.strategy === "extractive" || compact.strategy === "llm") data.strategy = compact.strategy;
31732
+ if (Array.isArray(compact.sourceEventSeqs)) data.sourceEventSeqs = compact.sourceEventSeqs;
31733
+ if (Array.isArray(compact.retainedCriterionIds)) {
31734
+ data.retainedCriterionIds = compact.retainedCriterionIds;
31735
+ }
31736
+ if (Array.isArray(compact.retainedEvidenceRefs)) {
31737
+ data.retainedEvidenceRefs = compact.retainedEvidenceRefs;
31738
+ }
31739
+ if (compact.retainedState && typeof compact.retainedState === "object") {
31740
+ data.retainedState = compact.retainedState;
31741
+ }
31742
+ if (compact.stateSnapshot && typeof compact.stateSnapshot === "object") data.stateSnapshot = compact.stateSnapshot;
31743
+ if (typeof compact.sourceRequestFingerprint === "string") {
31744
+ data.sourceRequestFingerprint = compact.sourceRequestFingerprint;
31745
+ }
31746
+ if (typeof compact.headerFingerprint === "string") data.headerFingerprint = compact.headerFingerprint;
31747
+ if (typeof compact.sourceEstimatedTokens === "number") {
31748
+ data.sourceEstimatedTokens = compact.sourceEstimatedTokens;
31749
+ }
31750
+ if (typeof compact.cacheReuseExpected === "boolean") data.cacheReuseExpected = compact.cacheReuseExpected;
31751
+ if (typeof compact.inputTokens === "number") data.inputTokens = compact.inputTokens;
31752
+ if (typeof compact.outputTokens === "number") data.outputTokens = compact.outputTokens;
31753
+ if (typeof compact.savedTokens === "number") data.savedTokens = compact.savedTokens;
31754
+ if (typeof compact.recompactionRate === "number") data.recompactionRate = compact.recompactionRate;
31755
+ if (compact.summaryStrategy === "extractive" || compact.summaryStrategy === "llm") {
31756
+ data.summaryStrategy = compact.summaryStrategy;
31757
+ }
31758
+ if (typeof compact.provider === "string") data.provider = compact.provider;
31759
+ if (typeof compact.model === "string") data.model = compact.model;
31760
+ return { kind: "session.compacted", actor: ACTOR_SYSTEM, data };
31761
+ }
31311
31762
  case "agent_start":
31312
31763
  return {
31313
31764
  kind: "note",
@@ -31421,6 +31872,16 @@ var init_sessionSpine = __esm({
31421
31872
  if (!report || report.events.length === 0) return null;
31422
31873
  return deriveMessages(report.events);
31423
31874
  }
31875
+ /** Deterministic operational state retained beside a compact checkpoint. */
31876
+ async compactionStateSnapshot(toSeq) {
31877
+ if (this.status !== "active" && this.status !== "closed") return null;
31878
+ await this.flush();
31879
+ const report = await readSessionLog(
31880
+ path21.join(this.sessionsDir, this.sessionId, "events.jsonl")
31881
+ ).catch(() => null);
31882
+ if (!report || report.events.length === 0) return null;
31883
+ return buildCompactionStateSnapshot(report.events, toSeq);
31884
+ }
31424
31885
  /**
31425
31886
  * E2.1 (ADR-0023 × ADR-0021): last recognizable strict verification record
31426
31887
  * in this session's log — the completion verdict is reconstructible from
@@ -31567,6 +32028,7 @@ var init_sessionSpine = __esm({
31567
32028
  }
31568
32029
  async flush() {
31569
32030
  await this.inner.flush?.();
32031
+ await this.spine?.flush();
31570
32032
  }
31571
32033
  async close() {
31572
32034
  await this.inner.close();
@@ -31867,6 +32329,21 @@ async function readMetrics(file2) {
31867
32329
  }
31868
32330
  return out;
31869
32331
  }
32332
+ function recordCompactionMetrics(sessionId2, provider, model, metrics) {
32333
+ getMetricsLogger().record({
32334
+ kind: "compaction",
32335
+ sessionId: sessionId2,
32336
+ provider,
32337
+ model,
32338
+ compactionCount: metrics.count,
32339
+ compactionInputTokens: metrics.inputTokens,
32340
+ compactionOutputTokens: metrics.outputTokens,
32341
+ compactionSavedTokens: metrics.savedTokens,
32342
+ compactionRecompactionRate: metrics.recompactionRate,
32343
+ compactionSummaryStrategy: metrics.summaryStrategy,
32344
+ compactionRestoreFailures: metrics.restoreFailures
32345
+ });
32346
+ }
31870
32347
  function getMetricsLogger() {
31871
32348
  if (!_singleton) {
31872
32349
  _singleton = new MetricsLogger();
@@ -33281,7 +33758,7 @@ var init_registry2 = __esm({
33281
33758
  if (!parsed.success) {
33282
33759
  return typedErr(`Invalid input: ${parsed.error.message}`);
33283
33760
  }
33284
- const timeoutMs = options.timeoutMs ?? tool.timeoutMs ?? 3e4;
33761
+ const timeoutMs2 = options.timeoutMs ?? tool.timeoutMs ?? 3e4;
33285
33762
  const parentSignal = options.signal;
33286
33763
  const controller = new AbortController();
33287
33764
  let timer;
@@ -33331,8 +33808,8 @@ var init_registry2 = __esm({
33331
33808
  timer = setTimeout(() => {
33332
33809
  if (!controller.signal.aborted)
33333
33810
  controller.abort();
33334
- reject(new Error(`Tool "${name}" timed out after ${timeoutMs}ms`));
33335
- }, timeoutMs);
33811
+ reject(new Error(`Tool "${name}" timed out after ${timeoutMs2}ms`));
33812
+ }, timeoutMs2);
33336
33813
  })
33337
33814
  ]);
33338
33815
  if (result.ok) {
@@ -33678,11 +34155,11 @@ async function runDiagnosticsForFile(file2, options = {}) {
33678
34155
  const provider = providerForFile(file2, options.providers);
33679
34156
  if (!provider) return [];
33680
34157
  const cwd = options.cwd ?? process.cwd();
33681
- const timeoutMs = options.timeoutMs ?? 5e3;
34158
+ const timeoutMs2 = options.timeoutMs ?? 5e3;
33682
34159
  const runner = options.runner ?? defaultRunner;
33683
34160
  try {
33684
34161
  const bin = options.runner ? provider.bin : resolveBin(provider.bin, cwd);
33685
- const result = await runner(bin, provider.args(file2), { cwd, timeoutMs });
34162
+ const result = await runner(bin, provider.args(file2), { cwd, timeoutMs: timeoutMs2 });
33686
34163
  return provider.parse(result.stdout, file2);
33687
34164
  } catch {
33688
34165
  return [];
@@ -35184,7 +35661,7 @@ function renderVerdict(verdict, candidateCount) {
35184
35661
  return lines.join("\n");
35185
35662
  }
35186
35663
  function createKrakenSelectTool(deps) {
35187
- const timeoutMs = deps.timeoutMs ?? 12e4;
35664
+ const timeoutMs2 = deps.timeoutMs ?? 12e4;
35188
35665
  return {
35189
35666
  name: "kraken_select",
35190
35667
  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.",
@@ -35241,7 +35718,7 @@ function createKrakenSelectTool(deps) {
35241
35718
  model: id.model,
35242
35719
  provider: id.provider,
35243
35720
  tools: [],
35244
- signal: AbortSignal.timeout(timeoutMs)
35721
+ signal: AbortSignal.timeout(timeoutMs2)
35245
35722
  });
35246
35723
  judgingUsage = usage;
35247
35724
  if (raw.trim().length === 0) throw new Error("empty verifier response");
@@ -38792,7 +39269,7 @@ exec node "$(dirname "$0")/askpass.cjs"
38792
39269
  }
38793
39270
  return sh;
38794
39271
  }
38795
- function runSsh(target, remoteCommand, timeoutMs = 6e4) {
39272
+ function runSsh(target, remoteCommand, timeoutMs2 = 6e4) {
38796
39273
  return new Promise((resolve3) => {
38797
39274
  if (target.auth === "password" && !getSshPassword(target.id)) {
38798
39275
  resolve3({
@@ -38832,7 +39309,7 @@ function runSsh(target, remoteCommand, timeoutMs = 6e4) {
38832
39309
  stdout,
38833
39310
  stderr: stderr + "\n[ssh] timeout"
38834
39311
  });
38835
- }, timeoutMs);
39312
+ }, timeoutMs2);
38836
39313
  child.on("error", (err) => {
38837
39314
  clearTimeout(timer);
38838
39315
  resolve3({
@@ -39061,7 +39538,7 @@ async function readChecks(cwd) {
39061
39538
  return [];
39062
39539
  }
39063
39540
  }
39064
- function runShell(command, cwd, timeoutMs, signal) {
39541
+ function runShell(command, cwd, timeoutMs2, signal) {
39065
39542
  return new Promise((resolve3) => {
39066
39543
  const isWin = process.platform === "win32";
39067
39544
  const child = spawn10(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
@@ -39073,7 +39550,7 @@ function runShell(command, cwd, timeoutMs, signal) {
39073
39550
  let stdout = "";
39074
39551
  let stderr = "";
39075
39552
  let settled = false;
39076
- const finish = (exitCode) => {
39553
+ const finish2 = (exitCode) => {
39077
39554
  if (settled) return;
39078
39555
  settled = true;
39079
39556
  resolve3({ exitCode, stdout, stderr });
@@ -39083,8 +39560,8 @@ function runShell(command, cwd, timeoutMs, signal) {
39083
39560
  child.kill("SIGTERM");
39084
39561
  } catch {
39085
39562
  }
39086
- finish(124);
39087
- }, timeoutMs);
39563
+ finish2(124);
39564
+ }, timeoutMs2);
39088
39565
  if (signal) {
39089
39566
  if (signal.aborted) {
39090
39567
  try {
@@ -39092,7 +39569,7 @@ function runShell(command, cwd, timeoutMs, signal) {
39092
39569
  } catch {
39093
39570
  }
39094
39571
  clearTimeout(timer);
39095
- finish(130);
39572
+ finish2(130);
39096
39573
  return;
39097
39574
  }
39098
39575
  signal.addEventListener(
@@ -39103,7 +39580,7 @@ function runShell(command, cwd, timeoutMs, signal) {
39103
39580
  } catch {
39104
39581
  }
39105
39582
  clearTimeout(timer);
39106
- finish(130);
39583
+ finish2(130);
39107
39584
  },
39108
39585
  { once: true }
39109
39586
  );
@@ -39118,11 +39595,11 @@ function runShell(command, cwd, timeoutMs, signal) {
39118
39595
  });
39119
39596
  child.on("error", () => {
39120
39597
  clearTimeout(timer);
39121
- finish(1);
39598
+ finish2(1);
39122
39599
  });
39123
39600
  child.on("close", (code) => {
39124
39601
  clearTimeout(timer);
39125
- finish(code ?? 1);
39602
+ finish2(code ?? 1);
39126
39603
  });
39127
39604
  });
39128
39605
  }
@@ -39144,9 +39621,9 @@ async function runBacktest(cwd, signal) {
39144
39621
  const results = [];
39145
39622
  for (const c of checks) {
39146
39623
  const expectExit = c.expectExit ?? 0;
39147
- const timeoutMs = c.timeoutMs ?? 12e4;
39624
+ const timeoutMs2 = c.timeoutMs ?? 12e4;
39148
39625
  const start = Date.now();
39149
- const { exitCode, stdout, stderr } = await runShell(c.command, cwd, timeoutMs, signal);
39626
+ const { exitCode, stdout, stderr } = await runShell(c.command, cwd, timeoutMs2, signal);
39150
39627
  const combined = `${stdout}${stderr}`;
39151
39628
  const preview = combined.slice(0, 400);
39152
39629
  let ok = exitCode === expectExit;
@@ -39858,8 +40335,9 @@ function createBuiltinToolRegistry(options = {}) {
39858
40335
  if (hooks) registry4.setLifecycleHooks(hooks);
39859
40336
  const readOnly = options.readOnly === true || options.planMode === true || profile === "explore";
39860
40337
  const verifyMode = profile === "verify";
39861
- const allowMutators = !readOnly && !verifyMode;
39862
- const allowBash = allowMutators || verifyMode;
40338
+ const gauntletParent = options.gauntletParent === true;
40339
+ const allowMutators = !readOnly && !verifyMode && !gauntletParent;
40340
+ const allowBash = (allowMutators || verifyMode) && !gauntletParent;
39863
40341
  const permPolicy = options.permissionPolicy ?? defaultPermissionPolicy();
39864
40342
  const withPerm = (t) => wrapWithPermissions(t, permPolicy, options.onPermissionAsk);
39865
40343
  registry4.register(withPerm(safeReadFile));
@@ -39892,21 +40370,21 @@ function createBuiltinToolRegistry(options = {}) {
39892
40370
  if (allowBash) {
39893
40371
  registry4.register(withPerm(safeBash));
39894
40372
  }
39895
- const askUserTool = options.readOnly === true || profile === "explore" || profile === "verify" ? null : createAskUserTool(options.onAskUser);
40373
+ const askUserTool = options.readOnly === true || profile === "explore" || profile === "verify" || gauntletParent ? null : createAskUserTool(options.onAskUser);
39896
40374
  if (askUserTool) {
39897
40375
  registry4.register(withPerm(askUserTool));
39898
40376
  }
39899
- const enableSkill = options.enableSkill !== false && options.readOnly !== true && profile !== "explore" && profile !== "verify";
40377
+ const enableSkill = options.enableSkill !== false && options.readOnly !== true && !gauntletParent && profile !== "explore" && profile !== "verify";
39900
40378
  const skillTool = enableSkill ? withPerm(createSkillTool({ cwd: root })) : null;
39901
40379
  if (skillTool) {
39902
40380
  registry4.register(skillTool);
39903
40381
  }
39904
- const enableTodos = options.enableTodos !== false && options.readOnly !== true && profile === "full";
40382
+ const enableTodos = options.enableTodos !== false && options.readOnly !== true && !gauntletParent && profile === "full";
39905
40383
  const todoWrite = enableTodos ? withPerm(createTodoWriteTool()) : null;
39906
40384
  const todoRead = enableTodos ? withPerm(createTodoReadTool()) : null;
39907
40385
  if (todoWrite) registry4.register(todoWrite);
39908
40386
  if (todoRead) registry4.register(todoRead);
39909
- const enablePlanTasks = options.enablePlanTasks !== false && options.readOnly !== true && (profile === "full" || options.planMode === true) && profile !== "explore" && profile !== "verify" && profile !== "general";
40387
+ const enablePlanTasks = options.enablePlanTasks !== false && options.readOnly !== true && !gauntletParent && (profile === "full" || options.planMode === true) && profile !== "explore" && profile !== "verify" && profile !== "general";
39910
40388
  const planTaskToolsWrapped = (enablePlanTasks ? createPlanTaskTools({ projectRoot: root, onTaskEvent: options.onTaskEvent }) : []).map((t) => withPerm(t));
39911
40389
  for (const t of planTaskToolsWrapped) {
39912
40390
  registry4.register(t);
@@ -39957,7 +40435,7 @@ function createBuiltinToolRegistry(options = {}) {
39957
40435
  permissions: semanticTool.permissions ?? []
39958
40436
  });
39959
40437
  }
39960
- if (!readOnly && process.env.ZELARI_BROWSER !== "0") {
40438
+ if (!readOnly && !gauntletParent && process.env.ZELARI_BROWSER !== "0") {
39961
40439
  const browserTool = createBrowserTool();
39962
40440
  registry4.register(browserTool);
39963
40441
  tools.push({
@@ -39966,7 +40444,7 @@ function createBuiltinToolRegistry(options = {}) {
39966
40444
  permissions: browserTool.permissions ?? []
39967
40445
  });
39968
40446
  }
39969
- if (!readOnly && process.env.ZELARI_SSH !== "0") {
40447
+ if (!readOnly && !gauntletParent && process.env.ZELARI_SSH !== "0") {
39970
40448
  for (const t of createSshTools()) {
39971
40449
  registry4.register(t);
39972
40450
  tools.push({
@@ -39976,7 +40454,7 @@ function createBuiltinToolRegistry(options = {}) {
39976
40454
  });
39977
40455
  }
39978
40456
  }
39979
- if (!readOnly) {
40457
+ if (!readOnly && !gauntletParent) {
39980
40458
  for (const t of createWorldModelTools()) {
39981
40459
  const safe = wrapWithAudit(t, audit, sessionId2);
39982
40460
  registry4.register(safe);
@@ -40202,10 +40680,10 @@ function wrapWithDiagnostics(original, root, runner) {
40202
40680
  const filePath = value && typeof value === "object" && typeof value.path === "string" ? value.path : void 0;
40203
40681
  if (!filePath) return result;
40204
40682
  try {
40205
- const timeoutMs = Number(process.env.ZELARI_DIAGNOSTICS_TIMEOUT_MS) || 5e3;
40683
+ const timeoutMs2 = Number(process.env.ZELARI_DIAGNOSTICS_TIMEOUT_MS) || 5e3;
40206
40684
  const diags = await runDiagnosticsForFile(filePath, {
40207
40685
  cwd: root,
40208
- timeoutMs,
40686
+ timeoutMs: timeoutMs2,
40209
40687
  ...runner ? { runner } : {}
40210
40688
  });
40211
40689
  const formatted = formatDiagnostics(diags, { relativeTo: root });
@@ -40353,711 +40831,171 @@ var init_toolRegistry = __esm({
40353
40831
  }
40354
40832
  });
40355
40833
 
40356
- // src/cli/phase.ts
40357
- var phase_exports = {};
40358
- __export(phase_exports, {
40359
- PHASES: () => PHASES,
40360
- PLAN_ALLOWED_WRITE_TOOLS: () => PLAN_ALLOWED_WRITE_TOOLS,
40361
- PLAN_BLOCKED_TOOLS: () => PLAN_BLOCKED_TOOLS,
40362
- describePhase: () => describePhase,
40363
- nextPhase: () => nextPhase,
40364
- parsePhase: () => parsePhase
40365
- });
40366
- function parsePhase(input) {
40367
- const v = input.trim().toLowerCase();
40368
- return PHASES.includes(v) ? v : null;
40834
+ // src/cli/state/fileStateStore.ts
40835
+ import { createHash as createHash11, randomUUID as randomUUID2 } from "node:crypto";
40836
+ import { promises as fs21 } from "node:fs";
40837
+ import * as path41 from "node:path";
40838
+ function shortId() {
40839
+ return randomUUID2().replace(/-/g, "").slice(0, 12);
40369
40840
  }
40370
- function nextPhase(current) {
40371
- return current === "plan" ? "build" : "plan";
40841
+ async function writeJsonAtomic(filePath, data) {
40842
+ await fs21.mkdir(path41.dirname(filePath), { recursive: true });
40843
+ const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
40844
+ await fs21.writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
40845
+ await fs21.rename(tmp, filePath);
40372
40846
  }
40373
- function describePhase(phase2) {
40374
- switch (phase2) {
40375
- case "plan":
40376
- return "plan \u2014 explore & design only (no project writes; plan files allowed)";
40377
- default:
40378
- return "build \u2014 implement with full tools";
40847
+ async function readJsonFile(filePath) {
40848
+ try {
40849
+ const raw = await fs21.readFile(filePath, "utf8");
40850
+ return JSON.parse(raw);
40851
+ } catch {
40852
+ return null;
40379
40853
  }
40380
40854
  }
40381
- var PHASES, PLAN_ALLOWED_WRITE_TOOLS, PLAN_BLOCKED_TOOLS;
40382
- var init_phase = __esm({
40383
- "src/cli/phase.ts"() {
40384
- "use strict";
40385
- PHASES = ["plan", "build"];
40386
- PLAN_ALLOWED_WRITE_TOOLS = /* @__PURE__ */ new Set([
40387
- // Workspace plan/docs intentional plan-mode outputs
40388
- "createPlan",
40389
- "createTask",
40390
- "updateTask",
40391
- "createMilestone",
40392
- "createDocument",
40393
- "createDecision",
40394
- "linkDocuments"
40395
- // Soft writes that only touch .zelari / plan paths are still gated in
40396
- // toolRegistry by path when needed; write_file/edit_file stay DENIED.
40397
- ]);
40398
- PLAN_BLOCKED_TOOLS = /* @__PURE__ */ new Set([
40399
- "write_file",
40400
- "edit_file",
40401
- "apply_diff",
40402
- "bash"
40403
- ]);
40404
- }
40405
- });
40406
-
40407
- // src/cli/mode.ts
40408
- function nextMode(current) {
40409
- const i = MODES.indexOf(current);
40410
- return MODES[(i + 1) % MODES.length] ?? "kraken";
40855
+ function defaultSummary(input, discoveries) {
40856
+ if (input.summary?.trim()) return input.summary.trim();
40857
+ const lines = [
40858
+ `# ${input.label}`,
40859
+ "",
40860
+ `- mode: ${input.mode}`,
40861
+ input.layer ? `- layer: ${input.layer}` : null,
40862
+ `- verification: ran=${input.verification.ran} ok=${input.verification.ok}`,
40863
+ "",
40864
+ "## Discoveries",
40865
+ ...discoveries.map((d) => `- [${d.kind}] ${d.summary}`)
40866
+ ].filter((x) => x !== null);
40867
+ return lines.join("\n");
40411
40868
  }
40412
- function parseMode(input) {
40413
- const v = input.trim().toLowerCase();
40414
- if (MODES.includes(v)) return v;
40415
- return MODE_ALIASES[v] ?? null;
40869
+ function stripStored(s) {
40870
+ const { artifactDir: _a3, ...meta3 } = s;
40871
+ return meta3;
40416
40872
  }
40417
- function describeMode(mode) {
40418
- switch (mode) {
40419
- case "council":
40420
- return "council \u2014 multi-member plan/design (Caronte\u2026Lucifero; build needs ZELARI_COUNCIL_CAN_BUILD=1)";
40421
- case "zelari":
40422
- return "zelari \u2014 mission: plan@council \u2192 build@kraken (legacy: ZELARI_BUILD_VIA_AGENT=0)";
40423
- default:
40424
- return "kraken \u2014 super-agent lead (spawns explore/general/verify tentacles; default implementer)";
40425
- }
40873
+ function isStateEnabled(env = process.env) {
40874
+ return env.ZELARI_STATE !== "0";
40426
40875
  }
40427
- var MODES, MODE_ALIASES;
40428
- var init_mode = __esm({
40429
- "src/cli/mode.ts"() {
40430
- "use strict";
40431
- MODES = ["kraken", "council", "zelari"];
40432
- MODE_ALIASES = {
40433
- agent: "kraken",
40434
- single: "kraken"
40435
- };
40436
- }
40437
- });
40438
-
40439
- // src/cli/headless.ts
40440
- import { readFileSync as readFileSync22 } from "node:fs";
40441
- function defaultProfileForMode(mode) {
40442
- switch (mode) {
40443
- case "council":
40444
- return "council/v1";
40445
- case "zelari":
40446
- return "mission/v1";
40447
- default:
40448
- return "kraken/v1";
40876
+ async function getStateStore(projectRoot, env = process.env) {
40877
+ if (!isStateEnabled(env)) return new NoopDurableStateStore();
40878
+ const store6 = new FileDurableStateStore();
40879
+ try {
40880
+ await store6.init(projectRoot);
40881
+ return store6;
40882
+ } catch {
40883
+ return new NoopDurableStateStore();
40449
40884
  }
40450
40885
  }
40451
- function parseHeadlessFlags(argv) {
40452
- if (!argv.includes("--headless")) {
40453
- return { options: null };
40454
- }
40455
- let task;
40456
- let output = "json";
40457
- let mode = "kraken";
40458
- let phase2 = "build";
40459
- let modeExplicit = false;
40460
- let councilFlag = false;
40461
- let provider;
40462
- let model;
40463
- let history2;
40464
- let todos2;
40465
- let once = false;
40466
- let profile;
40467
- let resumeSessionId;
40468
- let exportSessionPath;
40469
- let strictDone = false;
40470
- let krakenGraph;
40471
- let planOnly = process.env.ZELARI_KRAKEN_PLAN_ONLY === "1" || process.env.ZELARI_KRAKEN_PLAN_ONLY === "true";
40472
- let runPlan = process.env.ZELARI_KRAKEN_RUN_PLAN;
40473
- for (let i = 0; i < argv.length; i++) {
40474
- const arg = argv[i];
40475
- if (arg === "--headless") continue;
40476
- if (arg === "--output") {
40477
- const next = argv[i + 1];
40478
- if (next === "json" || next === "plain") {
40479
- output = next;
40480
- i++;
40481
- } else {
40482
- return {
40483
- options: null,
40484
- error: `--output requires 'json' or 'plain', got '${next ?? "(missing)"}'`
40485
- };
40886
+ function hashStablePrompt(stable) {
40887
+ return createHash11("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
40888
+ }
40889
+ var DEFAULT_MATERIALIZE_CHARS, FileDurableStateStore, NoopDurableStateStore;
40890
+ var init_fileStateStore = __esm({
40891
+ "src/cli/state/fileStateStore.ts"() {
40892
+ "use strict";
40893
+ DEFAULT_MATERIALIZE_CHARS = 4e3;
40894
+ FileDurableStateStore = class {
40895
+ root = "";
40896
+ stateDir = "";
40897
+ commitsDir = "";
40898
+ artifactsDir = "";
40899
+ headPath = "";
40900
+ indexPath = "";
40901
+ async init(projectRoot) {
40902
+ this.root = projectRoot;
40903
+ this.stateDir = path41.join(projectRoot, ".zelari", "state");
40904
+ this.commitsDir = path41.join(this.stateDir, "commits");
40905
+ this.artifactsDir = path41.join(this.stateDir, "artifacts");
40906
+ this.headPath = path41.join(this.stateDir, "HEAD.json");
40907
+ this.indexPath = path41.join(this.stateDir, "index.jsonl");
40908
+ await fs21.mkdir(this.commitsDir, { recursive: true });
40909
+ await fs21.mkdir(this.artifactsDir, { recursive: true });
40486
40910
  }
40487
- } else if (arg === "--task") {
40488
- task = argv[i + 1];
40489
- i++;
40490
- } else if (arg === "--task-file") {
40491
- const next = argv[i + 1];
40492
- if (next) {
40493
- try {
40494
- const fromFile = readFileSync22(next, "utf-8");
40495
- if (fromFile.trim()) task = fromFile;
40496
- } catch {
40911
+ async commit(input) {
40912
+ if (!input.force && input.verification.ran && !input.verification.ok) {
40913
+ throw new Error(
40914
+ "DurableStateStore.commit refused: verification ran and failed (pass force:true for soft commit)"
40915
+ );
40497
40916
  }
40498
- }
40499
- i++;
40500
- } else if (arg === "--council") {
40501
- councilFlag = true;
40502
- } else if (arg === "--mode") {
40503
- const next = argv[i + 1];
40504
- const parsed = next ? parseMode(next) : null;
40505
- if (!parsed) {
40506
- return {
40507
- options: null,
40508
- error: `--mode requires 'kraken', 'council', or 'zelari' (agent=alias), got '${next ?? "(missing)"}'`
40917
+ const discoveries = input.discoveries ?? [];
40918
+ const parent = await this.head();
40919
+ const id = shortId();
40920
+ const artifactRel = path41.join("artifacts", id);
40921
+ const artifactAbs = path41.join(this.artifactsDir, id);
40922
+ await fs21.mkdir(artifactAbs, { recursive: true });
40923
+ const summary = defaultSummary(input, discoveries);
40924
+ await fs21.writeFile(path41.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
40925
+ await writeJsonAtomic(path41.join(artifactAbs, "discoveries.json"), discoveries);
40926
+ await writeJsonAtomic(path41.join(artifactAbs, "verification.json"), input.verification);
40927
+ const meta3 = {
40928
+ id,
40929
+ parentId: parent?.id ?? null,
40930
+ createdAt: Date.now(),
40931
+ sessionId: input.sessionId,
40932
+ mode: input.mode,
40933
+ layer: input.layer,
40934
+ label: input.label,
40935
+ workspaceCheckpointId: input.workspaceCheckpointId,
40936
+ verification: {
40937
+ ...input.verification,
40938
+ reportPath: input.verification.reportPath ?? path41.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
40939
+ },
40940
+ changedPaths: input.changedPaths ?? [],
40941
+ stablePromptHash: input.stablePromptHash,
40942
+ discoveryCount: discoveries.length,
40943
+ artifactDir: artifactRel.replace(/\\/g, "/")
40509
40944
  };
40945
+ await writeJsonAtomic(path41.join(this.commitsDir, `${id}.json`), meta3);
40946
+ await writeJsonAtomic(this.headPath, { id, updatedAt: meta3.createdAt });
40947
+ await fs21.appendFile(this.indexPath, JSON.stringify({ id, createdAt: meta3.createdAt, label: meta3.label }) + "\n", "utf8");
40948
+ return stripStored(meta3);
40510
40949
  }
40511
- mode = parsed;
40512
- modeExplicit = true;
40513
- i++;
40514
- } else if (arg === "--phase") {
40515
- const next = argv[i + 1];
40516
- const parsed = next ? parsePhase(next) : null;
40517
- if (!parsed) {
40518
- return {
40519
- options: null,
40520
- error: `--phase requires 'plan' or 'build', got '${next ?? "(missing)"}'`
40521
- };
40950
+ async head() {
40951
+ const head = await readJsonFile(this.headPath);
40952
+ if (!head?.id) return null;
40953
+ return this.get(head.id);
40522
40954
  }
40523
- phase2 = parsed;
40524
- i++;
40525
- } else if (arg === "--provider") {
40526
- provider = argv[i + 1];
40527
- i++;
40528
- } else if (arg === "--model") {
40529
- model = argv[i + 1];
40530
- i++;
40531
- } else if (arg === "--history" || arg === "--history-file") {
40532
- const next = argv[i + 1];
40533
- if (next) {
40534
- let raw = null;
40535
- if (arg === "--history-file") {
40536
- try {
40537
- raw = readFileSync22(next, "utf-8");
40538
- } catch {
40539
- raw = null;
40540
- }
40541
- } else {
40542
- raw = next;
40955
+ async get(id) {
40956
+ const stored = await readJsonFile(path41.join(this.commitsDir, `${id}.json`));
40957
+ return stored ? stripStored(stored) : null;
40958
+ }
40959
+ async list(limit = 20) {
40960
+ let raw;
40961
+ try {
40962
+ raw = await fs21.readFile(this.indexPath, "utf8");
40963
+ } catch {
40964
+ return [];
40543
40965
  }
40544
- if (raw) {
40966
+ const ids = [];
40967
+ for (const line of raw.split("\n")) {
40968
+ const t = line.trim();
40969
+ if (!t) continue;
40545
40970
  try {
40546
- const parsedHist = JSON.parse(raw);
40547
- if (Array.isArray(parsedHist)) {
40548
- history2 = parsedHist.filter(
40549
- (m) => !!m && typeof m === "object" && typeof m.role === "string"
40550
- ).map((m) => {
40551
- const role = String(m.role);
40552
- const raw2 = m.content;
40553
- const content = typeof raw2 === "string" ? raw2 : raw2 == null ? "" : typeof raw2 === "object" ? JSON.stringify(raw2) : String(raw2);
40554
- const msg = {
40555
- role,
40556
- content
40557
- };
40558
- if (typeof m.toolCallId === "string") {
40559
- msg.toolCallId = m.toolCallId;
40560
- }
40561
- return msg;
40562
- }).filter(
40563
- (m) => m.role === "user" || m.role === "assistant" || m.role === "tool" || m.role === "system"
40564
- );
40565
- }
40971
+ const row = JSON.parse(t);
40972
+ if (row.id) ids.push(row.id);
40566
40973
  } catch {
40567
40974
  }
40568
40975
  }
40569
- i++;
40976
+ const slice = ids.slice(-Math.max(1, limit)).reverse();
40977
+ const out = [];
40978
+ for (const id of slice) {
40979
+ const m = await this.get(id);
40980
+ if (m) out.push(m);
40981
+ }
40982
+ return out;
40570
40983
  }
40571
- } else if (arg === "--todos") {
40572
- const next = argv[i + 1];
40573
- if (next) {
40574
- try {
40575
- const parsed = JSON.parse(next);
40576
- if (Array.isArray(parsed)) {
40577
- todos2 = parsed.filter(
40578
- (t) => !!t && typeof t === "object" && typeof t.content === "string"
40579
- ).map((t) => ({
40580
- id: typeof t.id === "string" ? t.id : void 0,
40581
- content: String(t.content).slice(0, 500),
40582
- status: t.status
40583
- }));
40584
- }
40585
- } catch {
40984
+ async setHead(id) {
40985
+ const meta3 = await this.get(id);
40986
+ if (!meta3) {
40987
+ throw new Error(`DurableStateStore.setHead: unknown commit ${id}`);
40586
40988
  }
40587
- i++;
40989
+ await writeJsonAtomic(this.headPath, { id, updatedAt: Date.now() });
40990
+ return meta3;
40588
40991
  }
40589
- } else if (arg === "--once") {
40590
- once = true;
40591
- } else if (arg === "--profile") {
40592
- const next = argv[i + 1];
40593
- if (!next || next.startsWith("--")) {
40594
- return { options: null, error: `--profile requires a profile id (e.g. kraken/v1), got '${next ?? "(missing)"}'` };
40595
- }
40596
- try {
40597
- resolveProfile(next);
40598
- } catch (err) {
40599
- return {
40600
- options: null,
40601
- error: err instanceof Error ? err.message : String(err)
40602
- };
40603
- }
40604
- profile = next;
40605
- i++;
40606
- } else if (arg === "--resume") {
40607
- const next = argv[i + 1];
40608
- if (!next || next.startsWith("--")) {
40609
- return { options: null, error: `--resume requires a session id, got '${next ?? "(missing)"}'` };
40610
- }
40611
- resumeSessionId = next;
40612
- i++;
40613
- } else if (arg === "--export-session") {
40614
- const next = argv[i + 1];
40615
- if (!next || next.startsWith("--")) {
40616
- return { options: null, error: `--export-session requires a path (or - for stdout), got '${next ?? "(missing)"}'` };
40617
- }
40618
- exportSessionPath = next;
40619
- i++;
40620
- } else if (arg === "--strict-done") {
40621
- strictDone = true;
40622
- } else if (arg === "--no-strict-done") {
40623
- strictDone = false;
40624
- process.env.ZELARI_MISSION_STRICT = "0";
40625
- } else if (arg === "--kraken-graph") {
40626
- krakenGraph = argv[i + 1];
40627
- i++;
40628
- } else if (arg === "--kraken-graph-file") {
40629
- const next = argv[i + 1];
40630
- if (next) {
40631
- try {
40632
- const fromFile = readFileSync22(next, "utf-8");
40633
- if (fromFile.trim()) krakenGraph = fromFile;
40634
- } catch {
40635
- }
40636
- }
40637
- i++;
40638
- } else if (arg === "--plan-only") {
40639
- planOnly = true;
40640
- } else if (arg === "--run-plan") {
40641
- runPlan = argv[i + 1];
40642
- i++;
40643
- }
40644
- }
40645
- if (councilFlag && !modeExplicit) {
40646
- mode = "council";
40647
- } else if (councilFlag && modeExplicit && mode !== "council") {
40648
- return {
40649
- options: null,
40650
- error: `--council conflicts with --mode ${mode}`
40651
- };
40652
- }
40653
- if (task && krakenGraph) {
40654
- return { options: null, error: "--task and --kraken-graph are mutually exclusive" };
40655
- }
40656
- if ((!task || task.trim().length === 0) && (!krakenGraph || krakenGraph.trim().length === 0)) {
40657
- return { options: null, error: "--headless requires --task <prompt> or --kraken-graph <goal>" };
40658
- }
40659
- return {
40660
- options: {
40661
- task: task ?? "",
40662
- output,
40663
- mode,
40664
- phase: phase2,
40665
- useCouncil: mode === "council",
40666
- provider,
40667
- model,
40668
- ...history2 && history2.length > 0 ? { history: history2 } : {},
40669
- ...todos2 && todos2.length > 0 ? { todos: todos2 } : {},
40670
- ...once ? { once: true } : {},
40671
- ...profile ? { profile } : {},
40672
- ...resumeSessionId ? { resumeSessionId } : {},
40673
- ...exportSessionPath ? { exportSessionPath } : {},
40674
- ...strictDone ? { strictDone: true } : {},
40675
- ...krakenGraph ? { krakenGraph } : {},
40676
- ...planOnly ? { planOnly: true } : {},
40677
- ...runPlan ? { runPlan } : {}
40678
- }
40679
- };
40680
- }
40681
- async function resolveHeadlessKey(providerId) {
40682
- const spec = PROVIDERS.find((p3) => p3.id === providerId);
40683
- if (!spec) {
40684
- return { error: `unknown provider: '${providerId}'` };
40685
- }
40686
- const resolved = await resolveApiKeyWithMeta(providerId);
40687
- if (!resolved || !resolved.apiKey) {
40688
- return {
40689
- error: `no API key for provider '${providerId}'.
40690
- Set the env var ${spec.envVar} or save a key via /login.`
40691
- };
40692
- }
40693
- const { resolveBaseUrl: resolveBaseUrl2 } = await Promise.resolve().then(() => (init_openai_compatible(), openai_compatible_exports));
40694
- return {
40695
- apiKey: resolved.apiKey,
40696
- baseUrl: resolveBaseUrl2(providerId)
40697
- };
40698
- }
40699
- function resolveHeadlessProvider(opts) {
40700
- const provider = opts.provider ?? getActiveProvider().id;
40701
- const model = opts.model ?? getModelForProvider(provider);
40702
- return { provider, model };
40703
- }
40704
- function emitEvent(event) {
40705
- process.stdout.write(JSON.stringify(event) + "\n");
40706
- }
40707
- var init_headless = __esm({
40708
- "src/cli/headless.ts"() {
40709
- "use strict";
40710
- init_keyStore();
40711
- init_providerConfig();
40712
- init_openai_compatible();
40713
- init_phase();
40714
- init_mode();
40715
- init_runtime2();
40716
- }
40717
- });
40718
-
40719
- // src/cli/headlessSpine.ts
40720
- var headlessSpine_exports = {};
40721
- __export(headlessSpine_exports, {
40722
- derivedModelSeed: () => derivedModelSeed,
40723
- exportSessionById: () => exportSessionById,
40724
- missionStateFromSpine: () => missionStateFromSpine,
40725
- openHeadlessSpine: () => openHeadlessSpine,
40726
- resolveHeadlessProfileId: () => resolveHeadlessProfileId,
40727
- seedHeadlessModelHistory: () => seedHeadlessModelHistory,
40728
- sessionStartedEvent: () => sessionStartedEvent
40729
- });
40730
- function sessionStartedEvent(handle) {
40731
- return {
40732
- type: "session_started",
40733
- sessionId: handle.sessionId,
40734
- spine: handle.spine.status
40735
- };
40736
- }
40737
- function resolveHeadlessProfileId(mode, explicit) {
40738
- if (explicit) return resolveProfile(explicit).id;
40739
- return defaultProfileForMode(mode ?? "kraken");
40740
- }
40741
- async function openHeadlessSpine(opts) {
40742
- const profileId = resolveHeadlessProfileId(opts.mode, opts.profile);
40743
- let profileTools = [];
40744
- try {
40745
- profileTools = resolveProfile(profileId).tools;
40746
- } catch {
40747
- profileTools = [];
40748
- }
40749
- const extra = {
40750
- profile: profileId,
40751
- workspace: opts.workspace ?? process.cwd(),
40752
- toolManifestHash: profileTools.length > 0 ? toolManifestHash(profileTools) : void 0
40753
- };
40754
- const mirrorOpts = {
40755
- baseDir: opts.baseDir,
40756
- quiet: opts.quiet,
40757
- extraStarted: extra
40758
- };
40759
- const spine = await SessionSpineMirror.adopt(opts.sessionId, mirrorOpts);
40760
- if (spine.status === "active") {
40761
- spine.note("headless.profile", { profile: profileId, mode: opts.mode ?? "kraken" });
40762
- }
40763
- return {
40764
- sessionId: opts.sessionId,
40765
- profileId,
40766
- spine,
40767
- observe(ev) {
40768
- if (ev && typeof ev === "object" && "type" in ev) {
40769
- spine.mirrorBrainEvent(ev);
40770
- }
40771
- },
40772
- userMessage(text) {
40773
- spine.userMessage(text);
40774
- },
40775
- verificationRun(payload) {
40776
- spine.verificationRun(payload);
40777
- },
40778
- appendEvent(input) {
40779
- return spine.appendEvent(input);
40780
- },
40781
- lastVerificationRun() {
40782
- return spine.lastVerificationRun();
40783
- },
40784
- missionPhase(phase2, note) {
40785
- spine.missionPhase(phase2, note);
40786
- },
40787
- missionProgress(advice) {
40788
- spine.missionProgress(advice);
40789
- },
40790
- note(text, data) {
40791
- spine.note(text, data);
40792
- },
40793
- async close(reason = "host-exit") {
40794
- await spine.close(reason);
40795
- },
40796
- async interrupt(note) {
40797
- if (note) spine.note("headless.interrupt", { note });
40798
- await spine.release();
40799
- },
40800
- async exportJson() {
40801
- try {
40802
- const store6 = new SessionStore(resolveSessionsDir({ baseDir: opts.baseDir }));
40803
- if (!await store6.exists(opts.sessionId)) return null;
40804
- return await exportSessionJson(store6, opts.sessionId);
40805
- } catch {
40806
- return null;
40807
- }
40808
- }
40809
- };
40810
- }
40811
- async function exportSessionById(sessionId2, baseDir) {
40812
- try {
40813
- const store6 = SessionStore.withDefaults(baseDir ? { baseDir } : {});
40814
- if (!await store6.exists(sessionId2)) {
40815
- return { ok: false, error: `session not found: ${sessionId2}` };
40816
- }
40817
- return { ok: true, json: await exportSessionJson(store6, sessionId2) };
40818
- } catch (err) {
40819
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
40820
- }
40821
- }
40822
- async function missionStateFromSpine(sessionId2, baseDir) {
40823
- try {
40824
- const store6 = SessionStore.withDefaults(baseDir ? { baseDir } : {});
40825
- if (!await store6.exists(sessionId2)) return null;
40826
- const projection = await store6.projection(sessionId2);
40827
- return deriveMissionState(projection);
40828
- } catch {
40829
- return null;
40830
- }
40831
- }
40832
- async function seedHeadlessModelHistory(handle, legacy) {
40833
- const mirror = handle.spine;
40834
- const legacySeed = filterLegacySeed(legacy);
40835
- if (mirror.status !== "active") {
40836
- return { history: legacySeed, importedCount: 0, source: "legacy-fallback" };
40837
- }
40838
- const existing = await mirror.derivedPriorTurns();
40839
- if (existing && existing.length > 0) {
40840
- return { history: derivedModelSeed(existing), importedCount: 0, source: "spine" };
40841
- }
40842
- if (legacySeed.length === 0) {
40843
- return { history: [], importedCount: 0, source: "spine" };
40844
- }
40845
- for (const m of legacySeed) {
40846
- if (m.role === "user") {
40847
- mirror.userMessage(m.content);
40848
- } else {
40849
- mirror.assistantMessage(m.content, { imported: "legacy-history" });
40850
- }
40851
- }
40852
- await mirror.flush();
40853
- const derived = await mirror.derivedPriorTurns() ?? [];
40854
- return {
40855
- history: derivedModelSeed(derived),
40856
- importedCount: legacySeed.length,
40857
- source: "spine-import"
40858
- };
40859
- }
40860
- function filterLegacySeed(legacy) {
40861
- return (legacy ?? []).filter((m) => m.role === "user" || m.role === "assistant").map(
40862
- (m) => m.role === "assistant" && m.content ? {
40863
- role: "assistant",
40864
- content: cleanAgentContent(m.content, {
40865
- stripQuestion: false,
40866
- stripThink: false
40867
- })
40868
- } : { role: m.role, content: m.content ?? "" }
40869
- ).filter((m) => (m.content ?? "").trim().length > 0);
40870
- }
40871
- function derivedModelSeed(derived) {
40872
- return derivedToAgentMessages(derived).map(
40873
- (m) => m.role === "system" ? { role: "user", content: m.content } : m
40874
- ).map(
40875
- (m) => m.role === "assistant" && m.content ? {
40876
- role: "assistant",
40877
- content: cleanAgentContent(m.content, {
40878
- stripQuestion: false,
40879
- stripThink: false
40880
- })
40881
- } : m
40882
- ).filter((m) => m.role === "user" || m.role === "assistant").filter((m) => (m.content ?? "").trim().length > 0);
40883
- }
40884
- var init_headlessSpine = __esm({
40885
- "src/cli/headlessSpine.ts"() {
40886
- "use strict";
40887
- init_dist();
40888
- init_session();
40889
- init_mission2();
40890
- init_runtime2();
40891
- init_sessionSpine();
40892
- init_headless();
40893
- }
40894
- });
40895
-
40896
- // src/cli/state/fileStateStore.ts
40897
- import { createHash as createHash11, randomUUID as randomUUID2 } from "node:crypto";
40898
- import { promises as fs21 } from "node:fs";
40899
- import * as path41 from "node:path";
40900
- function shortId() {
40901
- return randomUUID2().replace(/-/g, "").slice(0, 12);
40902
- }
40903
- async function writeJsonAtomic(filePath, data) {
40904
- await fs21.mkdir(path41.dirname(filePath), { recursive: true });
40905
- const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
40906
- await fs21.writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
40907
- await fs21.rename(tmp, filePath);
40908
- }
40909
- async function readJsonFile(filePath) {
40910
- try {
40911
- const raw = await fs21.readFile(filePath, "utf8");
40912
- return JSON.parse(raw);
40913
- } catch {
40914
- return null;
40915
- }
40916
- }
40917
- function defaultSummary(input, discoveries) {
40918
- if (input.summary?.trim()) return input.summary.trim();
40919
- const lines = [
40920
- `# ${input.label}`,
40921
- "",
40922
- `- mode: ${input.mode}`,
40923
- input.layer ? `- layer: ${input.layer}` : null,
40924
- `- verification: ran=${input.verification.ran} ok=${input.verification.ok}`,
40925
- "",
40926
- "## Discoveries",
40927
- ...discoveries.map((d) => `- [${d.kind}] ${d.summary}`)
40928
- ].filter((x) => x !== null);
40929
- return lines.join("\n");
40930
- }
40931
- function stripStored(s) {
40932
- const { artifactDir: _a3, ...meta3 } = s;
40933
- return meta3;
40934
- }
40935
- function isStateEnabled(env = process.env) {
40936
- return env.ZELARI_STATE !== "0";
40937
- }
40938
- async function getStateStore(projectRoot, env = process.env) {
40939
- if (!isStateEnabled(env)) return new NoopDurableStateStore();
40940
- const store6 = new FileDurableStateStore();
40941
- try {
40942
- await store6.init(projectRoot);
40943
- return store6;
40944
- } catch {
40945
- return new NoopDurableStateStore();
40946
- }
40947
- }
40948
- function hashStablePrompt(stable) {
40949
- return createHash11("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
40950
- }
40951
- var DEFAULT_MATERIALIZE_CHARS, FileDurableStateStore, NoopDurableStateStore;
40952
- var init_fileStateStore = __esm({
40953
- "src/cli/state/fileStateStore.ts"() {
40954
- "use strict";
40955
- DEFAULT_MATERIALIZE_CHARS = 4e3;
40956
- FileDurableStateStore = class {
40957
- root = "";
40958
- stateDir = "";
40959
- commitsDir = "";
40960
- artifactsDir = "";
40961
- headPath = "";
40962
- indexPath = "";
40963
- async init(projectRoot) {
40964
- this.root = projectRoot;
40965
- this.stateDir = path41.join(projectRoot, ".zelari", "state");
40966
- this.commitsDir = path41.join(this.stateDir, "commits");
40967
- this.artifactsDir = path41.join(this.stateDir, "artifacts");
40968
- this.headPath = path41.join(this.stateDir, "HEAD.json");
40969
- this.indexPath = path41.join(this.stateDir, "index.jsonl");
40970
- await fs21.mkdir(this.commitsDir, { recursive: true });
40971
- await fs21.mkdir(this.artifactsDir, { recursive: true });
40972
- }
40973
- async commit(input) {
40974
- if (!input.force && input.verification.ran && !input.verification.ok) {
40975
- throw new Error(
40976
- "DurableStateStore.commit refused: verification ran and failed (pass force:true for soft commit)"
40977
- );
40978
- }
40979
- const discoveries = input.discoveries ?? [];
40980
- const parent = await this.head();
40981
- const id = shortId();
40982
- const artifactRel = path41.join("artifacts", id);
40983
- const artifactAbs = path41.join(this.artifactsDir, id);
40984
- await fs21.mkdir(artifactAbs, { recursive: true });
40985
- const summary = defaultSummary(input, discoveries);
40986
- await fs21.writeFile(path41.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
40987
- await writeJsonAtomic(path41.join(artifactAbs, "discoveries.json"), discoveries);
40988
- await writeJsonAtomic(path41.join(artifactAbs, "verification.json"), input.verification);
40989
- const meta3 = {
40990
- id,
40991
- parentId: parent?.id ?? null,
40992
- createdAt: Date.now(),
40993
- sessionId: input.sessionId,
40994
- mode: input.mode,
40995
- layer: input.layer,
40996
- label: input.label,
40997
- workspaceCheckpointId: input.workspaceCheckpointId,
40998
- verification: {
40999
- ...input.verification,
41000
- reportPath: input.verification.reportPath ?? path41.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
41001
- },
41002
- changedPaths: input.changedPaths ?? [],
41003
- stablePromptHash: input.stablePromptHash,
41004
- discoveryCount: discoveries.length,
41005
- artifactDir: artifactRel.replace(/\\/g, "/")
41006
- };
41007
- await writeJsonAtomic(path41.join(this.commitsDir, `${id}.json`), meta3);
41008
- await writeJsonAtomic(this.headPath, { id, updatedAt: meta3.createdAt });
41009
- await fs21.appendFile(this.indexPath, JSON.stringify({ id, createdAt: meta3.createdAt, label: meta3.label }) + "\n", "utf8");
41010
- return stripStored(meta3);
41011
- }
41012
- async head() {
41013
- const head = await readJsonFile(this.headPath);
41014
- if (!head?.id) return null;
41015
- return this.get(head.id);
41016
- }
41017
- async get(id) {
41018
- const stored = await readJsonFile(path41.join(this.commitsDir, `${id}.json`));
41019
- return stored ? stripStored(stored) : null;
41020
- }
41021
- async list(limit = 20) {
41022
- let raw;
41023
- try {
41024
- raw = await fs21.readFile(this.indexPath, "utf8");
41025
- } catch {
41026
- return [];
41027
- }
41028
- const ids = [];
41029
- for (const line of raw.split("\n")) {
41030
- const t = line.trim();
41031
- if (!t) continue;
41032
- try {
41033
- const row = JSON.parse(t);
41034
- if (row.id) ids.push(row.id);
41035
- } catch {
41036
- }
41037
- }
41038
- const slice = ids.slice(-Math.max(1, limit)).reverse();
41039
- const out = [];
41040
- for (const id of slice) {
41041
- const m = await this.get(id);
41042
- if (m) out.push(m);
41043
- }
41044
- return out;
41045
- }
41046
- async setHead(id) {
41047
- const meta3 = await this.get(id);
41048
- if (!meta3) {
41049
- throw new Error(`DurableStateStore.setHead: unknown commit ${id}`);
41050
- }
41051
- await writeJsonAtomic(this.headPath, { id, updatedAt: Date.now() });
41052
- return meta3;
41053
- }
41054
- async loadDiscoveries(id) {
41055
- const meta3 = id ? await this.get(id) : await this.head();
41056
- if (!meta3) return [];
41057
- const stored = await readJsonFile(path41.join(this.commitsDir, `${meta3.id}.json`));
41058
- if (!stored?.artifactDir) return [];
41059
- const discPath = path41.join(this.stateDir, stored.artifactDir, "discoveries.json");
41060
- return await readJsonFile(discPath) ?? [];
40992
+ async loadDiscoveries(id) {
40993
+ const meta3 = id ? await this.get(id) : await this.head();
40994
+ if (!meta3) return [];
40995
+ const stored = await readJsonFile(path41.join(this.commitsDir, `${meta3.id}.json`));
40996
+ if (!stored?.artifactDir) return [];
40997
+ const discPath = path41.join(this.stateDir, stored.artifactDir, "discoveries.json");
40998
+ return await readJsonFile(discPath) ?? [];
41061
40999
  }
41062
41000
  async materializeContext(id, maxChars = DEFAULT_MATERIALIZE_CHARS) {
41063
41001
  const meta3 = id ? await this.get(id) : await this.head();
@@ -41227,25 +41165,37 @@ function extractiveHistorySummary(dropped, opts) {
41227
41165
  if (dropped.length === 0) return "No prior turns.";
41228
41166
  const userGoals = [];
41229
41167
  const assistantNotes = [];
41168
+ const userConstraints = [];
41169
+ const unresolved = [];
41170
+ const verification = [];
41171
+ const decisions = [];
41230
41172
  const tools = /* @__PURE__ */ new Map();
41231
41173
  const files = /* @__PURE__ */ new Set();
41232
41174
  let toolResults = 0;
41233
41175
  for (const m of dropped) {
41234
41176
  if (m.role === "user" && m.content.trim()) {
41235
- userGoals.push(oneLine(m.content, 220));
41177
+ const goal = oneLine(m.content, 220);
41178
+ userGoals.push(goal);
41179
+ if (/\b(must|never|required|only|do not|constraint|vincolo|deve|senza)\b/i.test(goal)) userConstraints.push(goal);
41236
41180
  } else if (m.role === "assistant") {
41237
41181
  if (m.content.trim()) {
41238
- assistantNotes.push(oneLine(m.content, 180));
41182
+ const note = oneLine(m.content, 220);
41183
+ assistantNotes.push(note);
41184
+ if (/\b(fail|failed|error|unresolved|remaining|todo|blocked|gap|errore|fallit|irrisolt|manca)\b/i.test(note)) unresolved.push(note);
41185
+ if (/\b(test|typecheck|build|verify|verification|passed|failed|green|red)\b/i.test(note)) verification.push(note);
41186
+ if (/\b(decid|decision|chosen|choose|scelt|adopt|implement)\b/i.test(note)) decisions.push(note);
41239
41187
  }
41240
41188
  if (m.toolCalls) {
41241
41189
  for (const tc of m.toolCalls) {
41242
41190
  tools.set(tc.name, (tools.get(tc.name) ?? 0) + 1);
41243
- collectPaths(tc.args, files);
41191
+ collectPaths2(tc.args, files);
41244
41192
  }
41245
41193
  }
41246
41194
  } else if (m.role === "tool") {
41247
41195
  toolResults += 1;
41248
41196
  collectPathsFromText(m.content, files);
41197
+ if (/\b(fail|failed|error|exception|blocked|errore|fallit)\b/i.test(m.content)) unresolved.push(oneLine(m.content, 220));
41198
+ if (/\b(test|typecheck|build|verify|passed|failed|success)\b/i.test(m.content)) verification.push(oneLine(m.content, 220));
41249
41199
  }
41250
41200
  }
41251
41201
  const parts = [
@@ -41256,6 +41206,22 @@ function extractiveHistorySummary(dropped, opts) {
41256
41206
  parts.push("## User goals / requests");
41257
41207
  for (const g of userGoals.slice(-6)) parts.push(`- ${g}`);
41258
41208
  }
41209
+ if (userConstraints.length) {
41210
+ parts.push("## User constraints (preserve exactly)");
41211
+ for (const item of [...new Set(userConstraints)].slice(-8)) parts.push(`- ${item}`);
41212
+ }
41213
+ if (unresolved.length) {
41214
+ parts.push("## Unresolved failures / pending repair");
41215
+ for (const item of [...new Set(unresolved)].slice(-8)) parts.push(`- ${item}`);
41216
+ }
41217
+ if (verification.length) {
41218
+ parts.push("## Latest verification state");
41219
+ for (const item of [...new Set(verification)].slice(-6)) parts.push(`- ${item}`);
41220
+ }
41221
+ if (decisions.length) {
41222
+ parts.push("## Recent active decisions");
41223
+ for (const item of [...new Set(decisions)].slice(-6)) parts.push(`- ${item}`);
41224
+ }
41259
41225
  if (assistantNotes.length) {
41260
41226
  parts.push("## Assistant conclusions (truncated)");
41261
41227
  for (const a of assistantNotes.slice(-5)) parts.push(`- ${a}`);
@@ -41282,7 +41248,7 @@ function oneLine(s, max) {
41282
41248
  if (t.length <= max) return t;
41283
41249
  return `${t.slice(0, max - 1)}\u2026`;
41284
41250
  }
41285
- function collectPaths(args, out) {
41251
+ function collectPaths2(args, out) {
41286
41252
  if (!args || typeof args !== "object") return;
41287
41253
  const obj = args;
41288
41254
  for (const key of ["path", "file", "filepath", "filePath", "target", "cwd"]) {
@@ -41401,6 +41367,36 @@ Be concise.
41401
41367
  });
41402
41368
 
41403
41369
  // src/cli/hooks/historyCompaction.ts
41370
+ function compactedRangeFromDropped(dropped) {
41371
+ if (dropped.length === 0) return void 0;
41372
+ const seqs = [];
41373
+ const sources = [];
41374
+ for (const m of dropped) {
41375
+ const hasCompactRange = typeof m.compactedFromSeq === "number" && Number.isInteger(m.compactedFromSeq) && m.compactedFromSeq > 0 && typeof m.compactedToSeq === "number" && Number.isInteger(m.compactedToSeq) && m.compactedToSeq >= m.compactedFromSeq;
41376
+ if (hasCompactRange) {
41377
+ seqs.push(m.compactedFromSeq, m.compactedToSeq);
41378
+ sources.push(...m.sourceEventSeqs ?? []);
41379
+ if (typeof m.seq === "number" && Number.isInteger(m.seq) && m.seq > 0) {
41380
+ seqs.push(m.seq);
41381
+ sources.push(m.seq);
41382
+ }
41383
+ continue;
41384
+ }
41385
+ if (typeof m.seq !== "number" || !Number.isInteger(m.seq) || m.seq < 1) return void 0;
41386
+ seqs.push(m.seq);
41387
+ sources.push(m.seq);
41388
+ }
41389
+ return {
41390
+ fromSeq: Math.min(...seqs),
41391
+ toSeq: Math.max(...seqs),
41392
+ sourceEventSeqs: [...new Set(sources)]
41393
+ };
41394
+ }
41395
+ function withDroppedRange(result, dropped, strategy) {
41396
+ const range = compactedRangeFromDropped(dropped);
41397
+ if (!range) return { ...result, strategy };
41398
+ return { ...result, ...range, strategy };
41399
+ }
41404
41400
  function resolveMaxMessages(opts) {
41405
41401
  const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
41406
41402
  let turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
@@ -41474,16 +41470,25 @@ function pruneToolResultsDetailed(messages, opts) {
41474
41470
  function compactHistory(messages, opts) {
41475
41471
  return compactHistoryDetailed(messages, opts).messages;
41476
41472
  }
41477
- function buildCheckpointMessage(summaryText) {
41473
+ function buildCheckpointMessage(summaryText, range) {
41478
41474
  return {
41479
41475
  role: "user",
41480
- content: CHECKPOINT_WRAPPER_PREFIX + "\n\n<compacted-summary>\n" + summaryText + "\n</compacted-summary>"
41476
+ content: CHECKPOINT_WRAPPER_PREFIX + "\n\n<compacted-summary>\n" + summaryText + "\n</compacted-summary>",
41477
+ ...range ? {
41478
+ compactedFromSeq: range.fromSeq,
41479
+ compactedToSeq: range.toSeq,
41480
+ sourceEventSeqs: [...range.sourceEventSeqs]
41481
+ } : {}
41481
41482
  };
41482
41483
  }
41483
41484
  function compactHistoryDetailed(messages, opts) {
41484
41485
  const maxMessages = resolveMaxMessages(opts);
41485
41486
  if (maxMessages === 0) {
41486
- return { messages: [], compacted: true, messagesRemoved: messages.length, summary: "" };
41487
+ return withDroppedRange(
41488
+ { messages: [], compacted: true, messagesRemoved: messages.length, summary: "" },
41489
+ messages,
41490
+ "extractive"
41491
+ );
41487
41492
  }
41488
41493
  if (messages.length <= maxMessages * 2 && !opts?.force) {
41489
41494
  return {
@@ -41504,19 +41509,25 @@ function compactHistoryDetailed(messages, opts) {
41504
41509
  };
41505
41510
  }
41506
41511
  const droppedMsgs = messages.slice(0, cut);
41512
+ const droppedRange = compactedRangeFromDropped(droppedMsgs);
41507
41513
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
41508
41514
  const kept = pruned.messages;
41509
41515
  const summaryText = extractiveHistorySummary(droppedMsgs);
41510
41516
  const summary = buildCheckpointMessage(
41511
- summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`
41517
+ summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`,
41518
+ droppedRange
41519
+ );
41520
+ return withDroppedRange(
41521
+ {
41522
+ messages: [summary, ...kept],
41523
+ compacted: true,
41524
+ messagesRemoved: cut,
41525
+ summary: summary.content,
41526
+ prunedToolResults: pruned.stats.pruned
41527
+ },
41528
+ droppedMsgs,
41529
+ "extractive"
41512
41530
  );
41513
- return {
41514
- messages: [summary, ...kept],
41515
- compacted: true,
41516
- messagesRemoved: cut,
41517
- summary: summary.content,
41518
- prunedToolResults: pruned.stats.pruned
41519
- };
41520
41531
  }
41521
41532
  async function compactHistoryAsync(messages, opts) {
41522
41533
  const base = compactHistoryDetailed(messages, opts);
@@ -41552,16 +41563,21 @@ async function compactHistoryAsync(messages, opts) {
41552
41563
  }
41553
41564
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
41554
41565
  const kept = pruned.messages;
41555
- const summary = buildCheckpointMessage(summaryText);
41556
- return {
41557
- messages: [summary, ...kept],
41558
- compacted: true,
41559
- messagesRemoved: cut,
41560
- summary: summaryText,
41561
- prunedToolResults: pruned.stats.pruned,
41562
- cacheReuseExpected,
41563
- replayExactPrefix
41564
- };
41566
+ const summary = buildCheckpointMessage(summaryText, compactedRangeFromDropped(droppedMsgs));
41567
+ const usedLlm = summaryText !== extractive && summaryText.trim().length > 40;
41568
+ return withDroppedRange(
41569
+ {
41570
+ messages: [summary, ...kept],
41571
+ compacted: true,
41572
+ messagesRemoved: cut,
41573
+ summary: summaryText,
41574
+ prunedToolResults: pruned.stats.pruned,
41575
+ cacheReuseExpected,
41576
+ replayExactPrefix
41577
+ },
41578
+ droppedMsgs,
41579
+ usedLlm ? "llm" : "extractive"
41580
+ );
41565
41581
  }
41566
41582
  function roughTokens(msgs) {
41567
41583
  let n = 0;
@@ -41845,6 +41861,553 @@ var init_phaseState = __esm({
41845
41861
  }
41846
41862
  });
41847
41863
 
41864
+ // src/cli/phase.ts
41865
+ var phase_exports = {};
41866
+ __export(phase_exports, {
41867
+ PHASES: () => PHASES,
41868
+ PLAN_ALLOWED_WRITE_TOOLS: () => PLAN_ALLOWED_WRITE_TOOLS,
41869
+ PLAN_BLOCKED_TOOLS: () => PLAN_BLOCKED_TOOLS,
41870
+ describePhase: () => describePhase,
41871
+ nextPhase: () => nextPhase,
41872
+ parsePhase: () => parsePhase
41873
+ });
41874
+ function parsePhase(input) {
41875
+ const v = input.trim().toLowerCase();
41876
+ return PHASES.includes(v) ? v : null;
41877
+ }
41878
+ function nextPhase(current) {
41879
+ return current === "plan" ? "build" : "plan";
41880
+ }
41881
+ function describePhase(phase2) {
41882
+ switch (phase2) {
41883
+ case "plan":
41884
+ return "plan \u2014 explore & design only (no project writes; plan files allowed)";
41885
+ default:
41886
+ return "build \u2014 implement with full tools";
41887
+ }
41888
+ }
41889
+ var PHASES, PLAN_ALLOWED_WRITE_TOOLS, PLAN_BLOCKED_TOOLS;
41890
+ var init_phase = __esm({
41891
+ "src/cli/phase.ts"() {
41892
+ "use strict";
41893
+ PHASES = ["plan", "build"];
41894
+ PLAN_ALLOWED_WRITE_TOOLS = /* @__PURE__ */ new Set([
41895
+ // Workspace plan/docs — intentional plan-mode outputs
41896
+ "createPlan",
41897
+ "createTask",
41898
+ "updateTask",
41899
+ "createMilestone",
41900
+ "createDocument",
41901
+ "createDecision",
41902
+ "linkDocuments"
41903
+ // Soft writes that only touch .zelari / plan paths are still gated in
41904
+ // toolRegistry by path when needed; write_file/edit_file stay DENIED.
41905
+ ]);
41906
+ PLAN_BLOCKED_TOOLS = /* @__PURE__ */ new Set([
41907
+ "write_file",
41908
+ "edit_file",
41909
+ "apply_diff",
41910
+ "bash"
41911
+ ]);
41912
+ }
41913
+ });
41914
+
41915
+ // src/cli/mode.ts
41916
+ function nextMode(current) {
41917
+ const i = MODES.indexOf(current);
41918
+ return MODES[(i + 1) % MODES.length] ?? "kraken";
41919
+ }
41920
+ function parseMode(input) {
41921
+ const v = input.trim().toLowerCase();
41922
+ if (MODES.includes(v)) return v;
41923
+ return MODE_ALIASES[v] ?? null;
41924
+ }
41925
+ function describeMode(mode) {
41926
+ switch (mode) {
41927
+ case "council":
41928
+ return "council \u2014 multi-member plan/design (Caronte\u2026Lucifero; build needs ZELARI_COUNCIL_CAN_BUILD=1)";
41929
+ case "zelari":
41930
+ return "zelari \u2014 mission: plan@council \u2192 build@kraken (legacy: ZELARI_BUILD_VIA_AGENT=0)";
41931
+ default:
41932
+ return "kraken \u2014 super-agent lead (spawns explore/general/verify tentacles; default implementer)";
41933
+ }
41934
+ }
41935
+ var MODES, MODE_ALIASES;
41936
+ var init_mode = __esm({
41937
+ "src/cli/mode.ts"() {
41938
+ "use strict";
41939
+ MODES = ["kraken", "council", "zelari"];
41940
+ MODE_ALIASES = {
41941
+ agent: "kraken",
41942
+ single: "kraken"
41943
+ };
41944
+ }
41945
+ });
41946
+
41947
+ // src/cli/headless.ts
41948
+ import { readFileSync as readFileSync22 } from "node:fs";
41949
+ function defaultProfileForMode(mode) {
41950
+ switch (mode) {
41951
+ case "council":
41952
+ return "council/v1";
41953
+ case "zelari":
41954
+ return "mission/v1";
41955
+ default:
41956
+ return "kraken/v1";
41957
+ }
41958
+ }
41959
+ function parseHeadlessFlags(argv) {
41960
+ if (!argv.includes("--headless")) {
41961
+ return { options: null };
41962
+ }
41963
+ let task;
41964
+ let output = "json";
41965
+ let mode = "kraken";
41966
+ let phase2 = "build";
41967
+ let modeExplicit = false;
41968
+ let councilFlag = false;
41969
+ let provider;
41970
+ let model;
41971
+ let history2;
41972
+ let todos2;
41973
+ let once = false;
41974
+ let profile;
41975
+ let resumeSessionId;
41976
+ let exportSessionPath;
41977
+ let strictDone = false;
41978
+ let krakenGraph;
41979
+ let planOnly = process.env.ZELARI_KRAKEN_PLAN_ONLY === "1" || process.env.ZELARI_KRAKEN_PLAN_ONLY === "true";
41980
+ let runPlan = process.env.ZELARI_KRAKEN_RUN_PLAN;
41981
+ let gauntlet = process.env.ZELARI_GAUNTLET === "1" || process.env.ZELARI_GAUNTLET === "true";
41982
+ for (let i = 0; i < argv.length; i++) {
41983
+ const arg = argv[i];
41984
+ if (arg === "--headless") continue;
41985
+ if (arg === "--output") {
41986
+ const next = argv[i + 1];
41987
+ if (next === "json" || next === "plain") {
41988
+ output = next;
41989
+ i++;
41990
+ } else {
41991
+ return {
41992
+ options: null,
41993
+ error: `--output requires 'json' or 'plain', got '${next ?? "(missing)"}'`
41994
+ };
41995
+ }
41996
+ } else if (arg === "--task") {
41997
+ task = argv[i + 1];
41998
+ i++;
41999
+ } else if (arg === "--task-file") {
42000
+ const next = argv[i + 1];
42001
+ if (next) {
42002
+ try {
42003
+ const fromFile = readFileSync22(next, "utf-8");
42004
+ if (fromFile.trim()) task = fromFile;
42005
+ } catch {
42006
+ }
42007
+ }
42008
+ i++;
42009
+ } else if (arg === "--council") {
42010
+ councilFlag = true;
42011
+ } else if (arg === "--mode") {
42012
+ const next = argv[i + 1];
42013
+ const parsed = next ? parseMode(next) : null;
42014
+ if (!parsed) {
42015
+ return {
42016
+ options: null,
42017
+ error: `--mode requires 'kraken', 'council', or 'zelari' (agent=alias), got '${next ?? "(missing)"}'`
42018
+ };
42019
+ }
42020
+ mode = parsed;
42021
+ modeExplicit = true;
42022
+ i++;
42023
+ } else if (arg === "--phase") {
42024
+ const next = argv[i + 1];
42025
+ const parsed = next ? parsePhase(next) : null;
42026
+ if (!parsed) {
42027
+ return {
42028
+ options: null,
42029
+ error: `--phase requires 'plan' or 'build', got '${next ?? "(missing)"}'`
42030
+ };
42031
+ }
42032
+ phase2 = parsed;
42033
+ i++;
42034
+ } else if (arg === "--provider") {
42035
+ provider = argv[i + 1];
42036
+ i++;
42037
+ } else if (arg === "--model") {
42038
+ model = argv[i + 1];
42039
+ i++;
42040
+ } else if (arg === "--history" || arg === "--history-file") {
42041
+ const next = argv[i + 1];
42042
+ if (next) {
42043
+ let raw = null;
42044
+ if (arg === "--history-file") {
42045
+ try {
42046
+ raw = readFileSync22(next, "utf-8");
42047
+ } catch {
42048
+ raw = null;
42049
+ }
42050
+ } else {
42051
+ raw = next;
42052
+ }
42053
+ if (raw) {
42054
+ try {
42055
+ const parsedHist = JSON.parse(raw);
42056
+ if (Array.isArray(parsedHist)) {
42057
+ history2 = parsedHist.filter(
42058
+ (m) => !!m && typeof m === "object" && typeof m.role === "string"
42059
+ ).map((m) => {
42060
+ const role = String(m.role);
42061
+ const raw2 = m.content;
42062
+ const content = typeof raw2 === "string" ? raw2 : raw2 == null ? "" : typeof raw2 === "object" ? JSON.stringify(raw2) : String(raw2);
42063
+ const msg = {
42064
+ role,
42065
+ content
42066
+ };
42067
+ if (typeof m.toolCallId === "string") {
42068
+ msg.toolCallId = m.toolCallId;
42069
+ }
42070
+ return msg;
42071
+ }).filter(
42072
+ (m) => m.role === "user" || m.role === "assistant" || m.role === "tool" || m.role === "system"
42073
+ );
42074
+ }
42075
+ } catch {
42076
+ }
42077
+ }
42078
+ i++;
42079
+ }
42080
+ } else if (arg === "--todos") {
42081
+ const next = argv[i + 1];
42082
+ if (next) {
42083
+ try {
42084
+ const parsed = JSON.parse(next);
42085
+ if (Array.isArray(parsed)) {
42086
+ todos2 = parsed.filter(
42087
+ (t) => !!t && typeof t === "object" && typeof t.content === "string"
42088
+ ).map((t) => ({
42089
+ id: typeof t.id === "string" ? t.id : void 0,
42090
+ content: String(t.content).slice(0, 500),
42091
+ status: t.status
42092
+ }));
42093
+ }
42094
+ } catch {
42095
+ }
42096
+ i++;
42097
+ }
42098
+ } else if (arg === "--once") {
42099
+ once = true;
42100
+ } else if (arg === "--profile") {
42101
+ const next = argv[i + 1];
42102
+ if (!next || next.startsWith("--")) {
42103
+ return { options: null, error: `--profile requires a profile id (e.g. kraken/v1), got '${next ?? "(missing)"}'` };
42104
+ }
42105
+ try {
42106
+ resolveProfile(next);
42107
+ } catch (err) {
42108
+ return {
42109
+ options: null,
42110
+ error: err instanceof Error ? err.message : String(err)
42111
+ };
42112
+ }
42113
+ profile = next;
42114
+ i++;
42115
+ } else if (arg === "--resume") {
42116
+ const next = argv[i + 1];
42117
+ if (!next || next.startsWith("--")) {
42118
+ return { options: null, error: `--resume requires a session id, got '${next ?? "(missing)"}'` };
42119
+ }
42120
+ resumeSessionId = next;
42121
+ i++;
42122
+ } else if (arg === "--export-session") {
42123
+ const next = argv[i + 1];
42124
+ if (!next || next.startsWith("--")) {
42125
+ return { options: null, error: `--export-session requires a path (or - for stdout), got '${next ?? "(missing)"}'` };
42126
+ }
42127
+ exportSessionPath = next;
42128
+ i++;
42129
+ } else if (arg === "--strict-done") {
42130
+ strictDone = true;
42131
+ } else if (arg === "--no-strict-done") {
42132
+ strictDone = false;
42133
+ process.env.ZELARI_MISSION_STRICT = "0";
42134
+ } else if (arg === "--kraken-graph") {
42135
+ krakenGraph = argv[i + 1];
42136
+ i++;
42137
+ } else if (arg === "--kraken-graph-file") {
42138
+ const next = argv[i + 1];
42139
+ if (next) {
42140
+ try {
42141
+ const fromFile = readFileSync22(next, "utf-8");
42142
+ if (fromFile.trim()) krakenGraph = fromFile;
42143
+ } catch {
42144
+ }
42145
+ }
42146
+ i++;
42147
+ } else if (arg === "--plan-only") {
42148
+ planOnly = true;
42149
+ } else if (arg === "--run-plan") {
42150
+ runPlan = argv[i + 1];
42151
+ i++;
42152
+ } else if (arg === "--gauntlet") {
42153
+ gauntlet = true;
42154
+ } else if (arg === "--no-gauntlet") {
42155
+ gauntlet = false;
42156
+ }
42157
+ }
42158
+ if (councilFlag && !modeExplicit) {
42159
+ mode = "council";
42160
+ } else if (councilFlag && modeExplicit && mode !== "council") {
42161
+ return {
42162
+ options: null,
42163
+ error: `--council conflicts with --mode ${mode}`
42164
+ };
42165
+ }
42166
+ if (task && krakenGraph) {
42167
+ return { options: null, error: "--task and --kraken-graph are mutually exclusive" };
42168
+ }
42169
+ if ((!task || task.trim().length === 0) && (!krakenGraph || krakenGraph.trim().length === 0)) {
42170
+ return { options: null, error: "--headless requires --task <prompt> or --kraken-graph <goal>" };
42171
+ }
42172
+ return {
42173
+ options: {
42174
+ task: task ?? "",
42175
+ output,
42176
+ mode,
42177
+ phase: phase2,
42178
+ useCouncil: mode === "council",
42179
+ provider,
42180
+ model,
42181
+ ...history2 && history2.length > 0 ? { history: history2 } : {},
42182
+ ...todos2 && todos2.length > 0 ? { todos: todos2 } : {},
42183
+ ...once ? { once: true } : {},
42184
+ ...profile ? { profile } : {},
42185
+ ...resumeSessionId ? { resumeSessionId } : {},
42186
+ ...exportSessionPath ? { exportSessionPath } : {},
42187
+ ...strictDone ? { strictDone: true } : {},
42188
+ ...krakenGraph ? { krakenGraph } : {},
42189
+ ...planOnly ? { planOnly: true } : {},
42190
+ ...runPlan ? { runPlan } : {},
42191
+ ...gauntlet ? { gauntlet: true } : {}
42192
+ }
42193
+ };
42194
+ }
42195
+ async function resolveHeadlessKey(providerId) {
42196
+ const spec = PROVIDERS.find((p3) => p3.id === providerId);
42197
+ if (!spec) {
42198
+ return { error: `unknown provider: '${providerId}'` };
42199
+ }
42200
+ const resolved = await resolveApiKeyWithMeta(providerId);
42201
+ if (!resolved || !resolved.apiKey) {
42202
+ return {
42203
+ error: `no API key for provider '${providerId}'.
42204
+ Set the env var ${spec.envVar} or save a key via /login.`
42205
+ };
42206
+ }
42207
+ const { resolveBaseUrl: resolveBaseUrl2 } = await Promise.resolve().then(() => (init_openai_compatible(), openai_compatible_exports));
42208
+ return {
42209
+ apiKey: resolved.apiKey,
42210
+ baseUrl: resolveBaseUrl2(providerId)
42211
+ };
42212
+ }
42213
+ function resolveHeadlessProvider(opts) {
42214
+ const provider = opts.provider ?? getActiveProvider().id;
42215
+ const model = opts.model ?? getModelForProvider(provider);
42216
+ return { provider, model };
42217
+ }
42218
+ function emitEvent(event) {
42219
+ process.stdout.write(JSON.stringify(event) + "\n");
42220
+ }
42221
+ var init_headless = __esm({
42222
+ "src/cli/headless.ts"() {
42223
+ "use strict";
42224
+ init_keyStore();
42225
+ init_providerConfig();
42226
+ init_openai_compatible();
42227
+ init_phase();
42228
+ init_mode();
42229
+ init_runtime2();
42230
+ }
42231
+ });
42232
+
42233
+ // src/cli/headlessSpine.ts
42234
+ var headlessSpine_exports = {};
42235
+ __export(headlessSpine_exports, {
42236
+ derivedModelSeed: () => derivedModelSeed,
42237
+ exportSessionById: () => exportSessionById,
42238
+ missionStateFromSpine: () => missionStateFromSpine,
42239
+ openHeadlessSpine: () => openHeadlessSpine,
42240
+ resolveHeadlessProfileId: () => resolveHeadlessProfileId,
42241
+ seedHeadlessModelHistory: () => seedHeadlessModelHistory,
42242
+ sessionStartedEvent: () => sessionStartedEvent
42243
+ });
42244
+ function sessionStartedEvent(handle) {
42245
+ return {
42246
+ type: "session_started",
42247
+ sessionId: handle.sessionId,
42248
+ spine: handle.spine.status
42249
+ };
42250
+ }
42251
+ function resolveHeadlessProfileId(mode, explicit) {
42252
+ if (explicit) return resolveProfile(explicit).id;
42253
+ return defaultProfileForMode(mode ?? "kraken");
42254
+ }
42255
+ async function openHeadlessSpine(opts) {
42256
+ const profileId = resolveHeadlessProfileId(opts.mode, opts.profile);
42257
+ let profileTools = [];
42258
+ try {
42259
+ profileTools = resolveProfile(profileId).tools;
42260
+ } catch {
42261
+ profileTools = [];
42262
+ }
42263
+ const extra = {
42264
+ profile: profileId,
42265
+ workspace: opts.workspace ?? process.cwd(),
42266
+ toolManifestHash: profileTools.length > 0 ? toolManifestHash(profileTools) : void 0
42267
+ };
42268
+ const mirrorOpts = {
42269
+ baseDir: opts.baseDir,
42270
+ quiet: opts.quiet,
42271
+ extraStarted: extra
42272
+ };
42273
+ const spine = await SessionSpineMirror.adopt(opts.sessionId, mirrorOpts);
42274
+ if (spine.status === "active") {
42275
+ spine.note("headless.profile", { profile: profileId, mode: opts.mode ?? "kraken" });
42276
+ }
42277
+ return {
42278
+ sessionId: opts.sessionId,
42279
+ profileId,
42280
+ spine,
42281
+ observe(ev) {
42282
+ if (ev && typeof ev === "object" && "type" in ev) {
42283
+ spine.mirrorBrainEvent(ev);
42284
+ }
42285
+ },
42286
+ userMessage(text) {
42287
+ spine.userMessage(text);
42288
+ },
42289
+ verificationRun(payload) {
42290
+ spine.verificationRun(payload);
42291
+ },
42292
+ appendEvent(input) {
42293
+ return spine.appendEvent(input);
42294
+ },
42295
+ lastVerificationRun() {
42296
+ return spine.lastVerificationRun();
42297
+ },
42298
+ missionPhase(phase2, note) {
42299
+ spine.missionPhase(phase2, note);
42300
+ },
42301
+ missionProgress(advice) {
42302
+ spine.missionProgress(advice);
42303
+ },
42304
+ note(text, data) {
42305
+ spine.note(text, data);
42306
+ },
42307
+ async close(reason = "host-exit") {
42308
+ await spine.close(reason);
42309
+ },
42310
+ async interrupt(note) {
42311
+ if (note) spine.note("headless.interrupt", { note });
42312
+ await spine.release();
42313
+ },
42314
+ async exportJson() {
42315
+ try {
42316
+ const store6 = new SessionStore(resolveSessionsDir({ baseDir: opts.baseDir }));
42317
+ if (!await store6.exists(opts.sessionId)) return null;
42318
+ return await exportSessionJson(store6, opts.sessionId);
42319
+ } catch {
42320
+ return null;
42321
+ }
42322
+ }
42323
+ };
42324
+ }
42325
+ async function exportSessionById(sessionId2, baseDir) {
42326
+ try {
42327
+ const store6 = SessionStore.withDefaults(baseDir ? { baseDir } : {});
42328
+ if (!await store6.exists(sessionId2)) {
42329
+ return { ok: false, error: `session not found: ${sessionId2}` };
42330
+ }
42331
+ return { ok: true, json: await exportSessionJson(store6, sessionId2) };
42332
+ } catch (err) {
42333
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
42334
+ }
42335
+ }
42336
+ async function missionStateFromSpine(sessionId2, baseDir) {
42337
+ try {
42338
+ const store6 = SessionStore.withDefaults(baseDir ? { baseDir } : {});
42339
+ if (!await store6.exists(sessionId2)) return null;
42340
+ const projection = await store6.projection(sessionId2);
42341
+ return deriveMissionState(projection);
42342
+ } catch {
42343
+ return null;
42344
+ }
42345
+ }
42346
+ async function seedHeadlessModelHistory(handle, legacy) {
42347
+ const mirror = handle.spine;
42348
+ const legacySeed = filterLegacySeed(legacy);
42349
+ if (mirror.status !== "active") {
42350
+ return { history: legacySeed, importedCount: 0, source: "legacy-fallback" };
42351
+ }
42352
+ const existing = await mirror.derivedPriorTurns();
42353
+ if (existing && existing.length > 0) {
42354
+ return { history: derivedModelSeed(existing), importedCount: 0, source: "spine" };
42355
+ }
42356
+ if (legacySeed.length === 0) {
42357
+ return { history: [], importedCount: 0, source: "spine" };
42358
+ }
42359
+ for (const m of legacySeed) {
42360
+ if (m.role === "user") {
42361
+ mirror.userMessage(m.content);
42362
+ } else {
42363
+ mirror.assistantMessage(m.content, { imported: "legacy-history" });
42364
+ }
42365
+ }
42366
+ await mirror.flush();
42367
+ const derived = await mirror.derivedPriorTurns() ?? [];
42368
+ return {
42369
+ history: derivedModelSeed(derived),
42370
+ importedCount: legacySeed.length,
42371
+ source: "spine-import"
42372
+ };
42373
+ }
42374
+ function filterLegacySeed(legacy) {
42375
+ return (legacy ?? []).filter((m) => m.role === "user" || m.role === "assistant").map(
42376
+ (m) => m.role === "assistant" && m.content ? {
42377
+ role: "assistant",
42378
+ content: cleanAgentContent(m.content, {
42379
+ stripQuestion: false,
42380
+ stripThink: false
42381
+ })
42382
+ } : { role: m.role, content: m.content ?? "" }
42383
+ ).filter((m) => (m.content ?? "").trim().length > 0);
42384
+ }
42385
+ function derivedModelSeed(derived) {
42386
+ return derivedToAgentMessages(derived).map(
42387
+ (m) => m.role === "system" ? { ...m, role: "user" } : m
42388
+ ).map(
42389
+ (m) => m.role === "assistant" && m.content ? {
42390
+ ...m,
42391
+ content: cleanAgentContent(m.content, {
42392
+ stripQuestion: false,
42393
+ stripThink: false
42394
+ }),
42395
+ ...m.seq !== void 0 ? { seq: m.seq } : {}
42396
+ } : m
42397
+ ).filter((m) => m.role === "user" || m.role === "assistant").filter((m) => (m.content ?? "").trim().length > 0);
42398
+ }
42399
+ var init_headlessSpine = __esm({
42400
+ "src/cli/headlessSpine.ts"() {
42401
+ "use strict";
42402
+ init_dist();
42403
+ init_session();
42404
+ init_mission2();
42405
+ init_runtime2();
42406
+ init_sessionSpine();
42407
+ init_headless();
42408
+ }
42409
+ });
42410
+
41848
42411
  // src/cli/provider/localCli/claudeStreamJson.ts
41849
42412
  function textBlock(text) {
41850
42413
  return { type: "text", text };
@@ -41985,10 +42548,10 @@ __export(claudeProvider_exports, {
41985
42548
  createLocalCliProvider: () => createLocalCliProvider
41986
42549
  });
41987
42550
  import { spawn as spawn11 } from "node:child_process";
41988
- function waitForExit(child, timeoutMs = 2e3) {
42551
+ function waitForExit(child, timeoutMs2 = 2e3) {
41989
42552
  return new Promise((resolve3) => {
41990
42553
  if (child.exitCode != null) return resolve3(child.exitCode);
41991
- const timer = setTimeout(() => resolve3(child.exitCode ?? null), timeoutMs);
42554
+ const timer = setTimeout(() => resolve3(child.exitCode ?? null), timeoutMs2);
41992
42555
  timer.unref?.();
41993
42556
  child.once("exit", (code) => {
41994
42557
  clearTimeout(timer);
@@ -43559,10 +44122,10 @@ function distTagForVersion(version2) {
43559
44122
  function registryUrlForTag(tag = distTagForVersion(getCurrentVersion())) {
43560
44123
  return `https://registry.npmjs.org/zelari-code/${tag}`;
43561
44124
  }
43562
- async function fetchLatestVersion(fetcher = fetch, registryUrl = REGISTRY_URL, timeoutMs = 5e3) {
44125
+ async function fetchLatestVersion(fetcher = fetch, registryUrl = REGISTRY_URL, timeoutMs2 = 5e3) {
43563
44126
  try {
43564
44127
  const controller = new AbortController();
43565
- const timer = setTimeout(() => controller.abort(), timeoutMs);
44128
+ const timer = setTimeout(() => controller.abort(), timeoutMs2);
43566
44129
  const response = await fetcher(registryUrl, { signal: controller.signal });
43567
44130
  clearTimeout(timer);
43568
44131
  if (!response.ok) {
@@ -43732,11 +44295,11 @@ var init_mcpClient = __esm({
43732
44295
  * Call a tool. Returns the concatenated text content; non-text content
43733
44296
  * items are summarized by type. Throws when the server flags isError.
43734
44297
  */
43735
- async callTool(name, args, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
44298
+ async callTool(name, args, timeoutMs2 = DEFAULT_REQUEST_TIMEOUT_MS) {
43736
44299
  const res = await this.request(
43737
44300
  "tools/call",
43738
44301
  { name, arguments: args },
43739
- timeoutMs
44302
+ timeoutMs2
43740
44303
  );
43741
44304
  const text = (res.content ?? []).map(
43742
44305
  (c) => c.type === "text" && typeof c.text === "string" ? c.text : `[${c.type ?? "unknown"} content]`
@@ -43753,7 +44316,7 @@ var init_mcpClient = __esm({
43753
44316
  this.child = null;
43754
44317
  }
43755
44318
  // ── JSON-RPC plumbing ────────────────────────────────────────────────
43756
- request(method, params, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
44319
+ request(method, params, timeoutMs2 = DEFAULT_REQUEST_TIMEOUT_MS) {
43757
44320
  const child = this.child;
43758
44321
  if (!child)
43759
44322
  return Promise.reject(new Error(`[mcp:${this.serverName}] not started`));
@@ -43764,10 +44327,10 @@ var init_mcpClient = __esm({
43764
44327
  this.pending.delete(id);
43765
44328
  reject(
43766
44329
  new Error(
43767
- `[mcp:${this.serverName}] ${method} timed out after ${timeoutMs}ms`
44330
+ `[mcp:${this.serverName}] ${method} timed out after ${timeoutMs2}ms`
43768
44331
  )
43769
44332
  );
43770
- }, timeoutMs);
44333
+ }, timeoutMs2);
43771
44334
  this.pending.set(id, { resolve: resolve3, reject, timer });
43772
44335
  child.stdin.write(payload + "\n", (err) => {
43773
44336
  if (err) {
@@ -44841,7 +45404,7 @@ function pickSmokeScript(scripts) {
44841
45404
  }
44842
45405
  return null;
44843
45406
  }
44844
- async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
45407
+ async function runProjectSmoke(projectRoot, timeoutMs2 = DEFAULT_TIMEOUT_MS3) {
44845
45408
  if (process.env["ZELARI_SMOKE"] === "0") {
44846
45409
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
44847
45410
  }
@@ -44874,7 +45437,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
44874
45437
  let stdout = "";
44875
45438
  let stderr = "";
44876
45439
  let settled = false;
44877
- const finish = (result) => {
45440
+ const finish2 = (result) => {
44878
45441
  if (settled) return;
44879
45442
  settled = true;
44880
45443
  clearTimeout(timer);
@@ -44882,15 +45445,15 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
44882
45445
  };
44883
45446
  const timer = setTimeout(() => {
44884
45447
  child.kill("SIGTERM");
44885
- finish({
45448
+ finish2({
44886
45449
  ran: true,
44887
45450
  ok: false,
44888
45451
  script,
44889
45452
  exitCode: -1,
44890
45453
  output: stdout + stderr,
44891
- reason: `smoke timeout after ${timeoutMs}ms`
45454
+ reason: `smoke timeout after ${timeoutMs2}ms`
44892
45455
  });
44893
- }, timeoutMs);
45456
+ }, timeoutMs2);
44894
45457
  child.stdout?.on("data", (chunk) => {
44895
45458
  stdout += chunk.toString("utf8");
44896
45459
  });
@@ -44898,7 +45461,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
44898
45461
  stderr += chunk.toString("utf8");
44899
45462
  });
44900
45463
  child.on("error", (err) => {
44901
- finish({
45464
+ finish2({
44902
45465
  ran: true,
44903
45466
  ok: false,
44904
45467
  script,
@@ -44910,7 +45473,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS3) {
44910
45473
  child.on("close", (code) => {
44911
45474
  const exitCode = code ?? -1;
44912
45475
  const ok = exitCode === 0;
44913
- finish({
45476
+ finish2({
44914
45477
  ran: true,
44915
45478
  ok,
44916
45479
  script,
@@ -46519,7 +47082,7 @@ function startPermissionBroker(socketPath, handlers, opts) {
46519
47082
  const closeAll = server.closeAllConnections;
46520
47083
  if (typeof closeAll === "function") closeAll.call(server);
46521
47084
  let done = false;
46522
- const finish = () => {
47085
+ const finish2 = () => {
46523
47086
  if (done) return;
46524
47087
  done = true;
46525
47088
  if (process.platform !== "win32") {
@@ -46528,8 +47091,8 @@ function startPermissionBroker(socketPath, handlers, opts) {
46528
47091
  res();
46529
47092
  }
46530
47093
  };
46531
- server.close(() => finish());
46532
- setTimeout(finish, 1e3).unref?.();
47094
+ server.close(() => finish2());
47095
+ setTimeout(finish2, 1e3).unref?.();
46533
47096
  })
46534
47097
  });
46535
47098
  });
@@ -46966,13 +47529,13 @@ async function createDefaultLlmClient(opts) {
46966
47529
  const llm = await resolveLlm(opts);
46967
47530
  return {
46968
47531
  async complete({ system, user }) {
46969
- const timeoutMs = resolvePlannerTimeoutMs();
47532
+ const timeoutMs2 = resolvePlannerTimeoutMs();
46970
47533
  const controller = new AbortController();
46971
47534
  let timedOut = false;
46972
- const t = timeoutMs > 0 ? setTimeout(() => {
47535
+ const t = timeoutMs2 > 0 ? setTimeout(() => {
46973
47536
  timedOut = true;
46974
47537
  controller.abort();
46975
- }, timeoutMs) : void 0;
47538
+ }, timeoutMs2) : void 0;
46976
47539
  try {
46977
47540
  const url2 = `${llm.baseUrl.replace(/\/$/, "")}/chat/completions`;
46978
47541
  let res;
@@ -46998,7 +47561,7 @@ async function createDefaultLlmClient(opts) {
46998
47561
  } catch (err) {
46999
47562
  if (timedOut) {
47000
47563
  throw new PlannerTransportError(
47001
- `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.`
47564
+ `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.`
47002
47565
  );
47003
47566
  }
47004
47567
  throw new PlannerTransportError(
@@ -49381,10 +49944,10 @@ async function validateApiKey(providerId, apiKey, options = {}) {
49381
49944
  return { ok: true, skipped: true, reason: "no_base_url" };
49382
49945
  }
49383
49946
  const probeUrl = options.probeUrl ?? `${spec.baseUrl.replace(/\/+$/, "")}/models`;
49384
- const timeoutMs = options.timeoutMs ?? 5e3;
49947
+ const timeoutMs2 = options.timeoutMs ?? 5e3;
49385
49948
  const fetchImpl = options.fetchImpl ?? fetch;
49386
49949
  const controller = new AbortController();
49387
- const timer = setTimeout(() => controller.abort(), timeoutMs);
49950
+ const timer = setTimeout(() => controller.abort(), timeoutMs2);
49388
49951
  try {
49389
49952
  const response = await fetchImpl(probeUrl, {
49390
49953
  method: "GET",
@@ -49413,7 +49976,7 @@ async function validateApiKey(providerId, apiKey, options = {}) {
49413
49976
  return {
49414
49977
  ok: false,
49415
49978
  reason: isAbort ? "network" : "network",
49416
- detail: isAbort ? `timeout after ${timeoutMs}ms` : msg,
49979
+ detail: isAbort ? `timeout after ${timeoutMs2}ms` : msg,
49417
49980
  durationMs
49418
49981
  };
49419
49982
  } finally {
@@ -50182,6 +50745,864 @@ var init_atMentions = __esm({
50182
50745
  }
50183
50746
  });
50184
50747
 
50748
+ // src/cli/gauntlet/policy.ts
50749
+ var policy_exports = {};
50750
+ __export(policy_exports, {
50751
+ DEFAULT_MAX_PARALLEL: () => DEFAULT_MAX_PARALLEL2,
50752
+ DEFAULT_MAX_PIECES: () => DEFAULT_MAX_PIECES,
50753
+ DEFAULT_MAX_ROUNDS: () => DEFAULT_MAX_ROUNDS,
50754
+ DEFAULT_WALL_MS: () => DEFAULT_WALL_MS,
50755
+ GAUNTLET_PARENT_BLOCKED_TOOLS: () => GAUNTLET_PARENT_BLOCKED_TOOLS,
50756
+ isGauntletFlagOn: () => isGauntletFlagOn,
50757
+ resolveGauntletCaps: () => resolveGauntletCaps,
50758
+ shouldRunGauntletHostLoop: () => shouldRunGauntletHostLoop
50759
+ });
50760
+ function envInt(raw, fallback, min, max) {
50761
+ if (raw === void 0 || raw === "") return fallback;
50762
+ const n = Number.parseInt(raw, 10);
50763
+ if (!Number.isFinite(n)) return fallback;
50764
+ return Math.min(max, Math.max(min, n));
50765
+ }
50766
+ function isGauntletFlagOn(explicit, env = process.env) {
50767
+ if (explicit === true) return true;
50768
+ if (explicit === false) return false;
50769
+ const raw = env.ZELARI_GAUNTLET;
50770
+ if (!raw) return false;
50771
+ return raw === "1" || raw.toLowerCase() === "true";
50772
+ }
50773
+ function resolveGauntletCaps(env = process.env) {
50774
+ const wallRaw = env.ZELARI_GAUNTLET_WALL_MS;
50775
+ let wallClockMs = DEFAULT_WALL_MS;
50776
+ if (wallRaw !== void 0 && wallRaw !== "") {
50777
+ const n = Number.parseInt(wallRaw, 10);
50778
+ if (Number.isFinite(n) && n >= 0) wallClockMs = n;
50779
+ }
50780
+ return {
50781
+ maxPieces: envInt(env.ZELARI_GAUNTLET_MAX_PIECES, DEFAULT_MAX_PIECES, 1, 16),
50782
+ maxRounds: envInt(env.ZELARI_GAUNTLET_MAX_ROUNDS, DEFAULT_MAX_ROUNDS, 1, 8),
50783
+ maxParallel: envInt(env.ZELARI_GAUNTLET_MAX_PARALLEL, DEFAULT_MAX_PARALLEL2, 1, 4),
50784
+ wallClockMs
50785
+ };
50786
+ }
50787
+ function shouldRunGauntletHostLoop(opts) {
50788
+ if (!isGauntletFlagOn(opts.gauntlet)) return false;
50789
+ if (opts.krakenGraph) return false;
50790
+ if (opts.useCouncil || opts.mode === "council" || opts.mode === "zelari") return false;
50791
+ const phase2 = opts.phase ?? "build";
50792
+ return phase2 !== "plan";
50793
+ }
50794
+ var DEFAULT_MAX_PIECES, DEFAULT_MAX_ROUNDS, DEFAULT_MAX_PARALLEL2, DEFAULT_WALL_MS, GAUNTLET_PARENT_BLOCKED_TOOLS;
50795
+ var init_policy = __esm({
50796
+ "src/cli/gauntlet/policy.ts"() {
50797
+ "use strict";
50798
+ DEFAULT_MAX_PIECES = 6;
50799
+ DEFAULT_MAX_ROUNDS = 3;
50800
+ DEFAULT_MAX_PARALLEL2 = 2;
50801
+ DEFAULT_WALL_MS = 45 * 60 * 1e3;
50802
+ GAUNTLET_PARENT_BLOCKED_TOOLS = [
50803
+ "write_file",
50804
+ "edit_file",
50805
+ "apply_diff",
50806
+ "bash"
50807
+ ];
50808
+ }
50809
+ });
50810
+
50811
+ // src/cli/gauntlet/complete.ts
50812
+ function timeoutMs(env = process.env) {
50813
+ const raw = env.ZELARI_GAUNTLET_DECOMPOSE_TIMEOUT_MS;
50814
+ if (raw === void 0 || raw === "") return DEFAULT_TIMEOUT_MS4;
50815
+ const n = Number.parseInt(raw, 10);
50816
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_TIMEOUT_MS4;
50817
+ }
50818
+ async function gauntletComplete(args, opts) {
50819
+ const provider = opts.provider;
50820
+ const meta3 = await resolveApiKeyWithMeta(provider);
50821
+ if (!meta3?.apiKey) {
50822
+ throw new Error(`No API key for provider '${provider}'`);
50823
+ }
50824
+ const baseUrl = resolveBaseUrl(provider);
50825
+ if (!baseUrl) {
50826
+ throw new Error(`No base URL for provider '${provider}'`);
50827
+ }
50828
+ const model = process.env.ZELARI_KRAKEN_PLANNER_MODEL?.trim() || opts.model || getModelForProvider(provider) || getProviderConfig().modelByProvider[provider] || "";
50829
+ if (!model) throw new Error(`No model for provider '${provider}'`);
50830
+ const ms = timeoutMs();
50831
+ const controller = new AbortController();
50832
+ const onParentAbort = () => controller.abort();
50833
+ opts.signal?.addEventListener("abort", onParentAbort, { once: true });
50834
+ let timedOut = false;
50835
+ const timer = ms > 0 ? setTimeout(() => {
50836
+ timedOut = true;
50837
+ controller.abort();
50838
+ }, ms) : void 0;
50839
+ try {
50840
+ const url2 = `${baseUrl.replace(/\/$/, "")}/chat/completions`;
50841
+ const res = await fetch(url2, {
50842
+ method: "POST",
50843
+ signal: controller.signal,
50844
+ headers: {
50845
+ "content-type": "application/json",
50846
+ authorization: `Bearer ${meta3.apiKey}`
50847
+ },
50848
+ body: JSON.stringify({
50849
+ model,
50850
+ temperature: 0.2,
50851
+ max_tokens: DEFAULT_MAX_TOKENS,
50852
+ stream: false,
50853
+ messages: [
50854
+ { role: "system", content: args.system },
50855
+ { role: "user", content: args.user }
50856
+ ]
50857
+ })
50858
+ });
50859
+ if (!res.ok) {
50860
+ const errBody = await res.text().catch(() => "");
50861
+ throw new Error(`decompose HTTP ${res.status}${errBody ? `: ${errBody.slice(0, 160)}` : ""}`);
50862
+ }
50863
+ const json2 = await res.json();
50864
+ const msg = json2.choices?.[0]?.message;
50865
+ const text = msg?.content?.trim() || msg?.reasoning_content?.trim() || "";
50866
+ if (!text) throw new Error("decompose: empty model response");
50867
+ return text;
50868
+ } catch (err) {
50869
+ if (timedOut) {
50870
+ throw new Error(
50871
+ `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.`
50872
+ );
50873
+ }
50874
+ throw err instanceof Error ? err : new Error(String(err));
50875
+ } finally {
50876
+ if (timer) clearTimeout(timer);
50877
+ opts.signal?.removeEventListener("abort", onParentAbort);
50878
+ }
50879
+ }
50880
+ var DEFAULT_TIMEOUT_MS4, DEFAULT_MAX_TOKENS;
50881
+ var init_complete = __esm({
50882
+ "src/cli/gauntlet/complete.ts"() {
50883
+ "use strict";
50884
+ init_providerConfig();
50885
+ init_keyStore();
50886
+ init_openai_compatible();
50887
+ DEFAULT_TIMEOUT_MS4 = 6e4;
50888
+ DEFAULT_MAX_TOKENS = 2048;
50889
+ }
50890
+ });
50891
+
50892
+ // src/cli/gauntlet/decompose.ts
50893
+ function fallbackPieces(goal) {
50894
+ const prompt = goal.trim() || "Complete the user Goal.";
50895
+ return [
50896
+ {
50897
+ id: "g1",
50898
+ label: labelFromGoal(prompt),
50899
+ prompt,
50900
+ acceptance: []
50901
+ }
50902
+ ];
50903
+ }
50904
+ function labelFromGoal(goal) {
50905
+ const line = goal.trim().split(/\r?\n/, 1)[0] ?? "Goal";
50906
+ return line.slice(0, MAX_LABEL) || "Goal";
50907
+ }
50908
+ function parsePiecesJson(raw, maxPieces) {
50909
+ if (!raw || typeof raw !== "object") return null;
50910
+ const rec = raw;
50911
+ const list = rec.pieces ?? rec.nodes;
50912
+ if (!Array.isArray(list) || list.length === 0) return null;
50913
+ const out = [];
50914
+ for (let i = 0; i < list.length && out.length < maxPieces; i++) {
50915
+ const item = list[i];
50916
+ if (!item || typeof item !== "object") continue;
50917
+ const row = item;
50918
+ const prompt = typeof row.prompt === "string" ? row.prompt.trim() : typeof row.goal === "string" ? row.goal.trim() : "";
50919
+ if (!prompt) continue;
50920
+ const id = typeof row.id === "string" && row.id.trim() ? row.id.trim().slice(0, 32) : `g${out.length + 1}`;
50921
+ const label = typeof row.label === "string" && row.label.trim() ? row.label.trim().slice(0, MAX_LABEL) : labelFromGoal(prompt);
50922
+ 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) : [];
50923
+ 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;
50924
+ 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;
50925
+ out.push({
50926
+ id: uniqueId2(id, out),
50927
+ label,
50928
+ prompt: prompt.slice(0, MAX_PROMPT),
50929
+ acceptance,
50930
+ ...scope && scope.length > 0 ? { scope } : {},
50931
+ ...bar && bar.length > 0 ? { bar } : {}
50932
+ });
50933
+ }
50934
+ return out.length > 0 ? out : null;
50935
+ }
50936
+ function formatHistoryNote(messages, maxMessages = 4, each = 400) {
50937
+ const slice = messages.filter((m) => m.role === "user" || m.role === "assistant").slice(-maxMessages);
50938
+ if (slice.length === 0) return "";
50939
+ return slice.map((m) => {
50940
+ const body = m.content.replace(/\s+/g, " ").trim().slice(0, each);
50941
+ return `${m.role}: ${body}`;
50942
+ }).join("\n");
50943
+ }
50944
+ function buildDecomposeUser(opts) {
50945
+ const parts = [
50946
+ `Max pieces: ${opts.maxPieces}`,
50947
+ "",
50948
+ "## Goal",
50949
+ opts.goal.trim() || "(empty)"
50950
+ ];
50951
+ if (opts.historyNote?.trim()) {
50952
+ parts.push("", "## Prior turns (context only \u2014 the Goal above is authoritative)", opts.historyNote.trim());
50953
+ }
50954
+ if (opts.workspace?.trim()) {
50955
+ parts.push("", "## Workspace", opts.workspace.trim());
50956
+ }
50957
+ return parts.join("\n");
50958
+ }
50959
+ function piecesFromModelText(text, maxPieces) {
50960
+ try {
50961
+ const json2 = extractJsonObject(text, { requireKey: "pieces" });
50962
+ return parsePiecesJson(json2, maxPieces);
50963
+ } catch {
50964
+ return null;
50965
+ }
50966
+ }
50967
+ async function decomposeGoal(opts) {
50968
+ const fallback = fallbackPieces(opts.goal);
50969
+ if (!opts.complete) return { pieces: fallback, source: "fallback" };
50970
+ try {
50971
+ const text = await opts.complete({
50972
+ system: GAUNTLET_DECOMPOSE_SYSTEM,
50973
+ user: buildDecomposeUser(opts)
50974
+ });
50975
+ const parsed = piecesFromModelText(text, opts.maxPieces);
50976
+ if (parsed && parsed.length > 0) return { pieces: parsed, source: "llm" };
50977
+ return { pieces: fallback, source: "fallback", error: "decompose JSON unusable" };
50978
+ } catch (err) {
50979
+ return {
50980
+ pieces: fallback,
50981
+ source: "fallback",
50982
+ error: err instanceof Error ? err.message : String(err)
50983
+ };
50984
+ }
50985
+ }
50986
+ function uniqueId2(id, existing) {
50987
+ if (!existing.some((p3) => p3.id === id)) return id;
50988
+ let n = 2;
50989
+ while (existing.some((p3) => p3.id === `${id}-${n}`)) n += 1;
50990
+ return `${id}-${n}`;
50991
+ }
50992
+ var MAX_LABEL, MAX_PROMPT, MAX_ACCEPTANCE, GAUNTLET_DECOMPOSE_SYSTEM;
50993
+ var init_decompose = __esm({
50994
+ "src/cli/gauntlet/decompose.ts"() {
50995
+ "use strict";
50996
+ init_planner();
50997
+ MAX_LABEL = 80;
50998
+ MAX_PROMPT = 8e3;
50999
+ MAX_ACCEPTANCE = 8;
51000
+ GAUNTLET_DECOMPOSE_SYSTEM = [
51001
+ "Decompose the user Goal into independently shippable Gauntlet pieces.",
51002
+ "Return ONLY JSON (no markdown, no prose):",
51003
+ '{ "pieces": [ { "id": string, "label": string, "prompt": string, "scope"?: string[], "acceptance"?: string[], "bar"?: string[] } ] }',
51004
+ "Rules:",
51005
+ "- 1 to N pieces (N is given in the user message). Prefer fewer. One piece is valid when the goal is atomic.",
51006
+ '- "prompt" is self-contained: the builder will not see this conversation.',
51007
+ '- "scope" is a path allowlist so disjoint pieces can run in parallel. Omit if unsure (forces sequential).',
51008
+ '- "acceptance" is checkable (file, command, observable). Never subjective ("elegant").',
51009
+ '- "bar" (optional) is on-disk reference path(s) the critic compares against blindly (gold file, screenshot, test).',
51010
+ "- Use paths from the workspace listing. Do not invent a tree.",
51011
+ "- Decompose the USER GOAL literally. Do not enlarge it into a different project."
51012
+ ].join("\n");
51013
+ }
51014
+ });
51015
+
51016
+ // src/cli/gauntlet/events.ts
51017
+ function gauntletProgressEvent(sessionId2, progress) {
51018
+ return {
51019
+ type: "gauntlet_progress",
51020
+ sessionId: sessionId2,
51021
+ ts: Date.now(),
51022
+ progress
51023
+ };
51024
+ }
51025
+ function formatGauntletSummary(results, opts) {
51026
+ if (results.length === 0) {
51027
+ if (opts?.timedOut) return "Gauntlet stopped: wall clock. No pieces finished.";
51028
+ if (opts?.cancelled) return "Gauntlet cancelled. No pieces finished.";
51029
+ return "Gauntlet: no pieces ran.";
51030
+ }
51031
+ const head = opts?.timedOut ? "Gauntlet stopped: wall clock." : opts?.cancelled ? "Gauntlet cancelled." : "Gauntlet finished.";
51032
+ const lines = [head, ""];
51033
+ for (const r of results) {
51034
+ const gap = r.gap ? ` \u2014 ${r.gap}` : "";
51035
+ const win = r.winner ? ` [A/B ${r.winner}]` : "";
51036
+ lines.push(`- ${r.label} (${r.id}): ${r.verdict} after ${r.rounds} round(s)${win}${gap}`);
51037
+ }
51038
+ return lines.join("\n");
51039
+ }
51040
+ var init_events3 = __esm({
51041
+ "src/cli/gauntlet/events.ts"() {
51042
+ "use strict";
51043
+ }
51044
+ });
51045
+
51046
+ // src/cli/gauntlet/blind.ts
51047
+ function assignBlindLabels(items, rand = Math.random) {
51048
+ if (rand() < 0.5) {
51049
+ return { A: items[0], B: items[1], firstLabel: "A" };
51050
+ }
51051
+ return { A: items[1], B: items[0], firstLabel: "B" };
51052
+ }
51053
+ function qualityBarSection(bars, rand = Math.random) {
51054
+ if (!bars || bars.length === 0) return "";
51055
+ if (bars.length === 1) {
51056
+ return [
51057
+ "## Quality bar (blind)",
51058
+ "Inspect this reference on disk. PASS only if the work in Scope meets or beats it.",
51059
+ "Do not assume the new files are better \u2014 compare concretely.",
51060
+ `- ${bars[0]}`
51061
+ ].join("\n");
51062
+ }
51063
+ const pair = assignBlindLabels([bars[0], bars[1]], rand);
51064
+ return [
51065
+ "## Blind A/B",
51066
+ "Two unlabeled artifacts on disk. Compare them against the piece acceptance.",
51067
+ 'Do not assume which is newer or which you are "supposed" to prefer.',
51068
+ `A: ${pair.A}`,
51069
+ `B: ${pair.B}`,
51070
+ "Also emit: WINNER: A | B | TIE"
51071
+ ].join("\n");
51072
+ }
51073
+ function parseBlindWinner(text) {
51074
+ const m = WINNER_RE.exec(text);
51075
+ if (!m) return void 0;
51076
+ const v = m[1].toUpperCase();
51077
+ if (v === "A" || v === "B" || v === "TIE") return v;
51078
+ return void 0;
51079
+ }
51080
+ var WINNER_RE;
51081
+ var init_blind = __esm({
51082
+ "src/cli/gauntlet/blind.ts"() {
51083
+ "use strict";
51084
+ WINNER_RE = /\bWINNER:\s*(A|B|TIE)\b/i;
51085
+ }
51086
+ });
51087
+
51088
+ // src/cli/gauntlet/verdict.ts
51089
+ function parseGauntletVerdict(text, opts = {}) {
51090
+ const evidence = (opts.toolTraceCount ?? 0) > 0;
51091
+ if (opts.builderFailed) {
51092
+ return {
51093
+ kind: "GAP",
51094
+ gap: (opts.builderError ?? "builder failed").trim() || "builder failed",
51095
+ evidence
51096
+ };
51097
+ }
51098
+ const raw = (text ?? "").trim();
51099
+ if (!raw) {
51100
+ return { kind: "BLOCKED", gap: "critic produced no output", evidence };
51101
+ }
51102
+ const explicit = VERDICT_RE.exec(raw);
51103
+ let kind = explicit ? explicit[1].toUpperCase() : void 0;
51104
+ const reports = [...raw.matchAll(REPORT_STATUS_RE)].map((m) => m[1].toLowerCase());
51105
+ const anyFail = reports.includes("fail");
51106
+ const anyUnknown = reports.includes("unknown");
51107
+ const anyPass = reports.includes("pass");
51108
+ if (!kind) {
51109
+ if (anyFail) kind = "GAP";
51110
+ else if (anyPass && !anyUnknown) kind = "PASS";
51111
+ else if (anyUnknown && !anyPass) kind = "BLOCKED";
51112
+ else kind = "BLOCKED";
51113
+ }
51114
+ const gapMatch = GAP_RE.exec(raw);
51115
+ let gap = gapMatch ? gapMatch[1].trim() : void 0;
51116
+ if (kind === "GAP" && !gap) {
51117
+ gap = anyFail ? "a verify-report check failed" : "critic named a gap without a GAP: line";
51118
+ }
51119
+ if (kind === "PASS" && !evidence) {
51120
+ return {
51121
+ kind: "GAP",
51122
+ gap: "unknown \u2260 pass: critic declared PASS without tool evidence",
51123
+ evidence: false
51124
+ };
51125
+ }
51126
+ if (kind === "PASS" && anyFail) {
51127
+ return {
51128
+ kind: "GAP",
51129
+ gap: gap ?? "verify-report contains a fail",
51130
+ evidence
51131
+ };
51132
+ }
51133
+ return { kind, ...gap ? { gap } : {}, evidence };
51134
+ }
51135
+ var VERDICT_RE, GAP_RE, REPORT_STATUS_RE;
51136
+ var init_verdict2 = __esm({
51137
+ "src/cli/gauntlet/verdict.ts"() {
51138
+ "use strict";
51139
+ VERDICT_RE = /\bVERDICT:\s*(PASS|GAP|BLOCKED)\b/i;
51140
+ GAP_RE = /\bGAP:\s*(.+)/i;
51141
+ REPORT_STATUS_RE = /^status:\s*(pass|fail|unknown)\b/gim;
51142
+ }
51143
+ });
51144
+
51145
+ // src/cli/gauntlet/prompts.ts
51146
+ function builderUserPrompt(piece, gap, briefing) {
51147
+ const parts = [
51148
+ `You are the BUILDER for one Gauntlet piece. Implement it on disk.`,
51149
+ `Do not spawn sub-agents. Stay inside Scope if provided.`
51150
+ ];
51151
+ if (briefing?.trim()) {
51152
+ parts.push("", "## Workspace briefing (do not treat as already-done work)", briefing.trim());
51153
+ }
51154
+ parts.push("", `## Piece: ${piece.label}`, piece.prompt.trim());
51155
+ if (piece.scope && piece.scope.length > 0) {
51156
+ parts.push("", "## Scope", ...piece.scope.map((s) => `- ${s}`));
51157
+ }
51158
+ if (piece.acceptance.length > 0) {
51159
+ parts.push("", "## Acceptance", ...piece.acceptance.map((a) => `- ${a}`));
51160
+ }
51161
+ if (gap && gap.trim()) {
51162
+ parts.push(
51163
+ "",
51164
+ "## Previous critic GAP (fix ONLY this)",
51165
+ gap.trim(),
51166
+ "Do not widen scope. Re-check the acceptance after the fix."
51167
+ );
51168
+ }
51169
+ parts.push("", "Return: files touched, what changed, residual risks.");
51170
+ return parts.join("\n");
51171
+ }
51172
+ function criticUserPrompt(piece, round, rand) {
51173
+ const parts = [
51174
+ `Round ${round}. Inspect the current tree against this piece.`,
51175
+ "Do not edit files. Do not take the builder's word \u2014 open files / run checks.",
51176
+ "",
51177
+ `## Piece: ${piece.label}`,
51178
+ piece.prompt.trim()
51179
+ ];
51180
+ if (piece.scope && piece.scope.length > 0) {
51181
+ parts.push("", "## Scope", ...piece.scope.map((s) => `- ${s}`));
51182
+ }
51183
+ if (piece.acceptance.length > 0) {
51184
+ parts.push("", "## Acceptance (check each)", ...piece.acceptance.map((a) => `- ${a}`));
51185
+ } else {
51186
+ parts.push(
51187
+ "",
51188
+ "## Acceptance",
51189
+ "- The piece prompt is satisfied by files on disk.",
51190
+ "- Relevant typecheck/tests pass when the project has them."
51191
+ );
51192
+ }
51193
+ const bar = qualityBarSection(piece.bar, rand);
51194
+ if (bar) parts.push("", bar);
51195
+ return parts.join("\n");
51196
+ }
51197
+ var GAUNTLET_CRITIC_SYSTEM;
51198
+ var init_prompts = __esm({
51199
+ "src/cli/gauntlet/prompts.ts"() {
51200
+ "use strict";
51201
+ init_blind();
51202
+ GAUNTLET_CRITIC_SYSTEM = [
51203
+ "You are a ruthless VERIFY critic in a Gauntlet Loop.",
51204
+ "Inspect the REAL files and commands on disk. Do not trust a builder self-report.",
51205
+ "You have no builder transcript \u2014 only the Goal, Scope, Acceptance, and the tree.",
51206
+ "OBSERVATION INTEGRITY: unknown \u2260 pass. EMPTY/degraded tools are not evidence.",
51207
+ "Name at most ONE biggest remaining gap. Do not write a laundry list.",
51208
+ "You may read files and run test/build commands via bash. Prefer targeted checks.",
51209
+ "",
51210
+ "End with BOTH:",
51211
+ "1. One <verify-report> block per acceptance criterion:",
51212
+ "<verify-report>",
51213
+ "check: <criterion text as given>",
51214
+ "status: pass | fail | unknown",
51215
+ "note: <one line of evidence (command or file + outcome)>",
51216
+ "</verify-report>",
51217
+ "2. A trailer on its own lines:",
51218
+ "VERDICT: PASS | GAP | BLOCKED",
51219
+ "GAP: <single biggest remaining gap; omit on PASS>",
51220
+ "3. If a Blind A/B section is present, also emit WINNER: A | B | TIE"
51221
+ ].join("\n");
51222
+ }
51223
+ });
51224
+
51225
+ // src/cli/gauntlet/schedule.ts
51226
+ function piecesCanRunInParallel(a, b) {
51227
+ return disjointScopeSets(a.scope, b.scope);
51228
+ }
51229
+ function scheduleWaves(pieces, maxParallel) {
51230
+ const cap3 = Math.max(1, maxParallel);
51231
+ const remaining = [...pieces];
51232
+ const waves = [];
51233
+ while (remaining.length > 0) {
51234
+ const wave = [];
51235
+ const leftover = [];
51236
+ for (const p3 of remaining) {
51237
+ if (wave.length >= cap3) {
51238
+ leftover.push(p3);
51239
+ continue;
51240
+ }
51241
+ if (wave.length === 0 || wave.every((w) => piecesCanRunInParallel(w, p3))) {
51242
+ wave.push(p3);
51243
+ } else {
51244
+ leftover.push(p3);
51245
+ }
51246
+ }
51247
+ waves.push(wave);
51248
+ remaining.splice(0, remaining.length, ...leftover);
51249
+ }
51250
+ return waves;
51251
+ }
51252
+ var init_schedule = __esm({
51253
+ "src/cli/gauntlet/schedule.ts"() {
51254
+ "use strict";
51255
+ init_dist();
51256
+ }
51257
+ });
51258
+
51259
+ // src/cli/gauntlet/loop.ts
51260
+ async function runGauntletLoop(args) {
51261
+ const { caps, deps } = args;
51262
+ const pieces = args.pieces.slice(0, caps.maxPieces);
51263
+ const started = (deps.now ?? Date.now)();
51264
+ const results = [];
51265
+ const emitProgress = (partial2) => {
51266
+ const progress = {
51267
+ ...partial2,
51268
+ elapsedMs: (deps.now ?? Date.now)() - started
51269
+ };
51270
+ deps.emit(gauntletProgressEvent(deps.sessionId, progress));
51271
+ deps.note?.("gauntlet.progress", progress);
51272
+ };
51273
+ const wall = caps.wallClockMs === void 0 ? DEFAULT_WALL_MS : caps.wallClockMs;
51274
+ const expired = () => wall > 0 && (deps.now ?? Date.now)() - started >= wall;
51275
+ const aborted2 = () => Boolean(deps.signal?.aborted);
51276
+ const stop = () => aborted2() || expired();
51277
+ const indexOf = (piece) => pieces.findIndex((p3) => p3.id === piece.id);
51278
+ const runOnePiece = async (piece) => {
51279
+ let last = {
51280
+ kind: "BLOCKED",
51281
+ gap: "no round ran",
51282
+ evidence: false
51283
+ };
51284
+ let gap;
51285
+ let winner;
51286
+ let rounds = 0;
51287
+ const i = Math.max(0, indexOf(piece));
51288
+ for (let round = 1; round <= caps.maxRounds; round++) {
51289
+ if (stop()) break;
51290
+ rounds = round;
51291
+ const phase2 = gap ? "repairing" : "building";
51292
+ emitProgress({
51293
+ phase: phase2,
51294
+ pieceId: piece.id,
51295
+ pieceLabel: piece.label,
51296
+ pieceIndex: i,
51297
+ pieceCount: pieces.length,
51298
+ round,
51299
+ maxRounds: caps.maxRounds
51300
+ });
51301
+ const built = await deps.runBuilder({
51302
+ piece,
51303
+ prompt: builderUserPrompt(piece, gap, deps.briefing),
51304
+ round
51305
+ });
51306
+ if (stop()) break;
51307
+ emitProgress({
51308
+ phase: "critiquing",
51309
+ pieceId: piece.id,
51310
+ pieceLabel: piece.label,
51311
+ pieceIndex: i,
51312
+ pieceCount: pieces.length,
51313
+ round,
51314
+ maxRounds: caps.maxRounds
51315
+ });
51316
+ const criticized = await deps.runCritic({
51317
+ piece,
51318
+ prompt: criticUserPrompt(piece, round),
51319
+ systemPrompt: GAUNTLET_CRITIC_SYSTEM,
51320
+ round
51321
+ });
51322
+ last = parseGauntletVerdict(criticized.result, {
51323
+ toolTraceCount: criticized.toolTraceCount ?? 0,
51324
+ builderFailed: !built.ok,
51325
+ builderError: built.error
51326
+ });
51327
+ winner = parseBlindWinner(criticized.result) ?? winner;
51328
+ emitProgress({
51329
+ phase: last.kind === "PASS" ? "settled" : last.kind === "BLOCKED" ? "blocked" : "repairing",
51330
+ pieceId: piece.id,
51331
+ pieceLabel: piece.label,
51332
+ pieceIndex: i,
51333
+ pieceCount: pieces.length,
51334
+ round,
51335
+ maxRounds: caps.maxRounds,
51336
+ verdict: last.kind,
51337
+ gap: last.gap,
51338
+ winner
51339
+ });
51340
+ if (last.kind === "PASS" || last.kind === "BLOCKED") break;
51341
+ gap = last.gap;
51342
+ }
51343
+ return {
51344
+ id: piece.id,
51345
+ label: piece.label,
51346
+ verdict: last,
51347
+ rounds,
51348
+ ...winner ? { winner } : {}
51349
+ };
51350
+ };
51351
+ for (const wave of scheduleWaves(pieces, caps.maxParallel)) {
51352
+ if (stop()) return finish(results, { cancelled: aborted2(), timedOut: expired() });
51353
+ if (wave.length === 1) {
51354
+ results.push(await runOnePiece(wave[0]));
51355
+ } else {
51356
+ const waveResults = await Promise.all(wave.map((p3) => runOnePiece(p3)));
51357
+ results.push(...waveResults);
51358
+ }
51359
+ if (stop()) return finish(results, { cancelled: aborted2(), timedOut: expired() });
51360
+ }
51361
+ return finish(results, { cancelled: aborted2(), timedOut: expired() });
51362
+ }
51363
+ function finish(results, flags) {
51364
+ const cancelled = flags.timedOut ? false : flags.cancelled;
51365
+ const timedOut = flags.timedOut;
51366
+ const settled = !cancelled && !timedOut && results.length > 0 && results.every((r) => r.verdict.kind === "PASS");
51367
+ return {
51368
+ settled,
51369
+ cancelled,
51370
+ timedOut,
51371
+ pieces: results,
51372
+ summary: formatGauntletSummary(
51373
+ results.map((r) => ({
51374
+ id: r.id,
51375
+ label: r.label,
51376
+ verdict: r.verdict.kind,
51377
+ rounds: r.rounds,
51378
+ gap: r.verdict.gap,
51379
+ winner: r.winner
51380
+ })),
51381
+ { timedOut, cancelled }
51382
+ )
51383
+ };
51384
+ }
51385
+ var init_loop = __esm({
51386
+ "src/cli/gauntlet/loop.ts"() {
51387
+ "use strict";
51388
+ init_blind();
51389
+ init_policy();
51390
+ init_verdict2();
51391
+ init_prompts();
51392
+ init_schedule();
51393
+ init_events3();
51394
+ }
51395
+ });
51396
+
51397
+ // src/cli/gauntlet/run.ts
51398
+ var run_exports = {};
51399
+ __export(run_exports, {
51400
+ runHeadlessGauntlet: () => runHeadlessGauntlet
51401
+ });
51402
+ async function runHeadlessGauntlet(opts, provider, model) {
51403
+ const sessionId2 = opts.resumeSessionId ?? crypto.randomUUID();
51404
+ const spine = await openHeadlessSpine({
51405
+ sessionId: sessionId2,
51406
+ mode: opts.mode,
51407
+ profile: opts.profile,
51408
+ workspace: process.cwd()
51409
+ });
51410
+ emitEvent(sessionStartedEvent(spine));
51411
+ const seeded = await seedHeadlessModelHistory(spine, opts.history);
51412
+ if (opts.task) spine.userMessage(opts.task);
51413
+ spine.note("gauntlet.start", { goal: opts.task.slice(0, 200) });
51414
+ const abort = new AbortController();
51415
+ const onSigint = () => {
51416
+ emitEvent({ type: "log", message: "[gauntlet] SIGINT \u2014 cancelling" });
51417
+ abort.abort();
51418
+ };
51419
+ process.once("SIGINT", onSigint);
51420
+ const wallMs = resolveGauntletCaps().wallClockMs ?? 0;
51421
+ const wallTimer = wallMs > 0 ? setTimeout(() => {
51422
+ emitEvent({ type: "log", message: "[gauntlet] wall clock \u2014 aborting tentacles" });
51423
+ abort.abort();
51424
+ }, wallMs) : void 0;
51425
+ const cwd = process.cwd();
51426
+ const audit = new AuditLogger();
51427
+ const createSubAgentContext = createKrakenSubAgentContextFactory({
51428
+ root: cwd,
51429
+ audit,
51430
+ sessionId: sessionId2,
51431
+ provider,
51432
+ model
51433
+ });
51434
+ const deps = {
51435
+ createSubAgentContext,
51436
+ allowWorktree: false,
51437
+ harnessFactory: (config2) => {
51438
+ const harness = new AgentHarness(config2);
51439
+ return {
51440
+ async *run() {
51441
+ for await (const ev of harness.run()) {
51442
+ if (ev.type === "thinking_delta" || ev.type === "tool_execution_start" || ev.type === "tool_execution_end") {
51443
+ emitEvent(ev);
51444
+ spine.observe(ev);
51445
+ }
51446
+ yield ev;
51447
+ }
51448
+ },
51449
+ cancel: () => harness.cancel()
51450
+ };
51451
+ }
51452
+ };
51453
+ const emit = (event) => {
51454
+ if (opts.output === "json") emitEvent(event);
51455
+ else if (typeof event.message === "string") {
51456
+ process.stderr.write(`${event.message}
51457
+ `);
51458
+ }
51459
+ };
51460
+ emitEvent({ type: "agent_start", model, provider, role: "gauntlet" });
51461
+ emit({ type: "log", message: "[gauntlet] host loop \u2014 builder/critic rounds, parent cannot write" });
51462
+ const caps = resolveGauntletCaps();
51463
+ const workspace = buildWorkspaceSummary(cwd, { maxChars: 2500 });
51464
+ const historyNote = formatHistoryNote(seeded.history);
51465
+ emitEvent(
51466
+ gauntletProgressEvent(sessionId2, {
51467
+ phase: "decomposing",
51468
+ pieceId: "",
51469
+ pieceLabel: "Goal",
51470
+ pieceIndex: 0,
51471
+ pieceCount: 1,
51472
+ round: 0,
51473
+ maxRounds: caps.maxRounds,
51474
+ elapsedMs: 0
51475
+ })
51476
+ );
51477
+ try {
51478
+ const decomposed = await decomposeGoal({
51479
+ goal: opts.task,
51480
+ maxPieces: caps.maxPieces,
51481
+ workspace,
51482
+ historyNote,
51483
+ complete: (req) => gauntletComplete(req, { provider, model, signal: abort.signal })
51484
+ });
51485
+ spine.note("gauntlet.decompose", {
51486
+ source: decomposed.source,
51487
+ count: decomposed.pieces.length,
51488
+ ...decomposed.error ? { error: decomposed.error.slice(0, 240) } : {}
51489
+ });
51490
+ emit({
51491
+ type: "log",
51492
+ message: `[gauntlet] ${decomposed.pieces.length} piece(s) from ${decomposed.source}${decomposed.error ? ` (${decomposed.error.slice(0, 80)})` : ""}`
51493
+ });
51494
+ const result = await runGauntletLoop({
51495
+ pieces: decomposed.pieces,
51496
+ caps,
51497
+ deps: {
51498
+ sessionId: sessionId2,
51499
+ signal: abort.signal,
51500
+ emit,
51501
+ briefing: workspace.slice(0, 1200),
51502
+ note: (text, data) => spine.note(text, data),
51503
+ runBuilder: async ({ piece, prompt, round }) => {
51504
+ emit({
51505
+ type: "log",
51506
+ message: `[gauntlet] ${piece.id} round ${round}/${caps.maxRounds} builder`
51507
+ });
51508
+ const tent = await runTentacle({
51509
+ deps,
51510
+ agent: "general",
51511
+ thoroughness: "medium",
51512
+ parentCwd: cwd,
51513
+ sessionId: sessionId2,
51514
+ signal: abort.signal,
51515
+ args: {
51516
+ description: `gauntlet-builder ${piece.id} r${round}`,
51517
+ prompt,
51518
+ ...piece.scope ? { scope: piece.scope } : {},
51519
+ ...piece.acceptance.length > 0 ? { acceptance: piece.acceptance } : {}
51520
+ }
51521
+ });
51522
+ if (!tent.ok) return { ok: false, result: "", error: tent.error };
51523
+ return {
51524
+ ok: true,
51525
+ result: tent.result,
51526
+ toolTraceCount: tent.toolTrace?.length ?? 0
51527
+ };
51528
+ },
51529
+ runCritic: async ({ piece, prompt, systemPrompt, round }) => {
51530
+ emit({
51531
+ type: "log",
51532
+ message: `[gauntlet] ${piece.id} round ${round}/${caps.maxRounds} critic`
51533
+ });
51534
+ const tent = await runTentacle({
51535
+ deps,
51536
+ agent: "verify",
51537
+ thoroughness: "medium",
51538
+ parentCwd: cwd,
51539
+ sessionId: sessionId2,
51540
+ signal: abort.signal,
51541
+ systemPromptOverride: systemPrompt,
51542
+ args: {
51543
+ description: `gauntlet-critic ${piece.id} r${round}`,
51544
+ prompt,
51545
+ ...piece.scope ? { scope: piece.scope } : {},
51546
+ ...piece.acceptance.length > 0 ? { acceptance: piece.acceptance } : {}
51547
+ }
51548
+ });
51549
+ if (!tent.ok) {
51550
+ return { ok: false, result: tent.error, error: tent.error, toolTraceCount: 0 };
51551
+ }
51552
+ return {
51553
+ ok: true,
51554
+ result: tent.result,
51555
+ toolTraceCount: tent.toolTrace?.length ?? 0
51556
+ };
51557
+ }
51558
+ }
51559
+ });
51560
+ emitEvent({ type: "message_start", role: "assistant" });
51561
+ emitEvent({ type: "message_delta", delta: result.summary });
51562
+ emitEvent({ type: "message_end", totalLength: result.summary.length });
51563
+ emitEvent({
51564
+ type: "agent_end",
51565
+ reason: result.cancelled || result.timedOut ? "cancelled" : result.settled ? "completed" : "error"
51566
+ });
51567
+ if (result.timedOut) {
51568
+ emit({ type: "log", message: `[gauntlet] wall clock (${Math.round(wallMs / 6e4)}m)` });
51569
+ await spine.interrupt("gauntlet-wall-clock");
51570
+ return 1;
51571
+ }
51572
+ if (result.cancelled) {
51573
+ await spine.interrupt("gauntlet-cancelled");
51574
+ return 1;
51575
+ }
51576
+ await spine.close(result.settled ? "gauntlet-pass" : "gauntlet-incomplete");
51577
+ return result.settled ? 0 : 3;
51578
+ } catch (err) {
51579
+ const msg = err instanceof Error ? err.message : String(err);
51580
+ emitEvent({ type: "error", severity: "fatal", message: msg, code: "gauntlet" });
51581
+ await spine.interrupt(`gauntlet-error: ${msg}`).catch(() => void 0);
51582
+ return 2;
51583
+ } finally {
51584
+ if (wallTimer) clearTimeout(wallTimer);
51585
+ process.removeListener("SIGINT", onSigint);
51586
+ }
51587
+ }
51588
+ var init_run = __esm({
51589
+ "src/cli/gauntlet/run.ts"() {
51590
+ "use strict";
51591
+ init_harness();
51592
+ init_headless();
51593
+ init_headlessSpine();
51594
+ init_auditLogger();
51595
+ init_toolRegistry();
51596
+ init_taskTool();
51597
+ init_complete();
51598
+ init_decompose();
51599
+ init_events3();
51600
+ init_loop();
51601
+ init_policy();
51602
+ init_workspaceSummary();
51603
+ }
51604
+ });
51605
+
50185
51606
  // src/cli/triggerLock.ts
50186
51607
  var triggerLock_exports = {};
50187
51608
  __export(triggerLock_exports, {
@@ -50956,7 +52377,7 @@ function startPermissionMcpServer(opts) {
50956
52377
  }
50957
52378
  return { error: { code: -32601, message: `method not found: ${method}` } };
50958
52379
  }
50959
- async function callApprove(args, timeoutMs) {
52380
+ async function callApprove(args, timeoutMs2) {
50960
52381
  const toolName = typeof args.tool_name === "string" ? args.tool_name : "";
50961
52382
  if (!toolName) {
50962
52383
  return { error: { code: -32602, message: "approve requires a string tool_name" } };
@@ -50975,7 +52396,7 @@ function startPermissionMcpServer(opts) {
50975
52396
  toolUseId: typeof args.tool_use_id === "string" ? args.tool_use_id : void 0,
50976
52397
  suggestions
50977
52398
  },
50978
- { requestTimeoutMs: timeoutMs }
52399
+ { requestTimeoutMs: timeoutMs2 }
50979
52400
  );
50980
52401
  const result = res.behavior === "allow" ? {
50981
52402
  behavior: "allow",
@@ -50995,7 +52416,7 @@ function startPermissionMcpServer(opts) {
50995
52416
  );
50996
52417
  }
50997
52418
  }
50998
- async function callAskUser(args, timeoutMs) {
52419
+ async function callAskUser(args, timeoutMs2) {
50999
52420
  const question = typeof args.question === "string" ? args.question : "";
51000
52421
  if (!question) {
51001
52422
  return { error: { code: -32602, message: "ask_user requires a string question" } };
@@ -51013,7 +52434,7 @@ function startPermissionMcpServer(opts) {
51013
52434
  choices,
51014
52435
  context
51015
52436
  },
51016
- { requestTimeoutMs: timeoutMs }
52437
+ { requestTimeoutMs: timeoutMs2 }
51017
52438
  );
51018
52439
  const text = res.behavior === "allow" && typeof res.answer === "string" ? res.answer : res.message ?? "No answer was given \u2014 use your best judgment.";
51019
52440
  return textResult(text);
@@ -55861,8 +57282,8 @@ async function readPackageScripts(cwd = process.cwd()) {
55861
57282
  return {};
55862
57283
  }
55863
57284
  }
55864
- function buildNativeCriteria(commands, timeoutMs) {
55865
- const pack = codingCriteriaPack({ ...commands, commandTimeoutMs: timeoutMs });
57285
+ function buildNativeCriteria(commands, timeoutMs2) {
57286
+ const pack = codingCriteriaPack({ ...commands, commandTimeoutMs: timeoutMs2 });
55866
57287
  return pack.criteria.filter(
55867
57288
  (c) => !(c.required && (!c.check || c.check.kind === "none"))
55868
57289
  );
@@ -56127,9 +57548,6 @@ function strictGateEventPayload(evaluation) {
56127
57548
  };
56128
57549
  }
56129
57550
 
56130
- // src/cli/hooks/useChatTurn.ts
56131
- init_headlessSpine();
56132
-
56133
57551
  // src/cli/hooks/permissionPicker.ts
56134
57552
  init_toolPermissions();
56135
57553
 
@@ -56164,7 +57582,7 @@ ${detail}
56164
57582
  let settled = false;
56165
57583
  const askTimeoutMs = askUserTimeoutMs();
56166
57584
  let cancelAskTimeout = () => void 0;
56167
- const finish = (ok, note) => {
57585
+ const finish2 = (ok, note) => {
56168
57586
  if (settled) return;
56169
57587
  settled = true;
56170
57588
  cancelAskTimeout();
@@ -56173,7 +57591,7 @@ ${detail}
56173
57591
  resolve3(ok);
56174
57592
  };
56175
57593
  cancelAskTimeout = armPickerTimeout(
56176
- () => finish(
57594
+ () => finish2(
56177
57595
  false,
56178
57596
  `[permission] ask for "${req.toolName}" timed out after ${Math.round(
56179
57597
  askTimeoutMs / 1e3
@@ -56202,12 +57620,12 @@ ${detail}
56202
57620
  onAnswer: (value) => {
56203
57621
  const v = value.trim().toLowerCase();
56204
57622
  if (v === "deny" || v.startsWith("deny")) {
56205
- finish(false);
57623
+ finish2(false);
56206
57624
  return;
56207
57625
  }
56208
57626
  if (v === "always-tool" || v.includes("always") && v.includes("tool")) {
56209
57627
  grantSessionTool(req.toolName);
56210
- finish(
57628
+ finish2(
56211
57629
  true,
56212
57630
  `[permission] Granted "${req.toolName}" for this session (tool).`
56213
57631
  );
@@ -56216,19 +57634,19 @@ ${detail}
56216
57634
  if (v === "always-cat" || v.includes("always") && !v.includes("tool")) {
56217
57635
  for (const c of cats) grantSessionCategory(c);
56218
57636
  grantSessionTool(req.toolName);
56219
- finish(
57637
+ finish2(
56220
57638
  true,
56221
57639
  `[permission] Granted ${catLabel} (+ ${req.toolName}) for this session.`
56222
57640
  );
56223
57641
  return;
56224
57642
  }
56225
57643
  if (v === "allow" || v.startsWith("allow") || v === "yes" || v === "y" || v === "1") {
56226
- finish(true);
57644
+ finish2(true);
56227
57645
  return;
56228
57646
  }
56229
- finish(false);
57647
+ finish2(false);
56230
57648
  },
56231
- onCancel: () => finish(false)
57649
+ onCancel: () => finish2(false)
56232
57650
  });
56233
57651
  });
56234
57652
  }
@@ -56278,6 +57696,15 @@ init_envNumber();
56278
57696
  init_historyCompaction();
56279
57697
  init_observationStore();
56280
57698
  init_capabilities();
57699
+ function mergeCompactRange(into, r) {
57700
+ if (r.fromSeq !== void 0 && r.toSeq !== void 0) {
57701
+ into.fromSeq = into.fromSeq === void 0 ? r.fromSeq : Math.min(into.fromSeq, r.fromSeq);
57702
+ into.toSeq = into.toSeq === void 0 ? r.toSeq : Math.max(into.toSeq, r.toSeq);
57703
+ if (r.sourceEventSeqs) into.sourceSeqs.push(...r.sourceEventSeqs);
57704
+ }
57705
+ if (r.strategy === "llm") into.strategy = "llm";
57706
+ else if (r.strategy && !into.strategy) into.strategy = r.strategy;
57707
+ }
56281
57708
  function estimateTokens(text) {
56282
57709
  if (!text) return 0;
56283
57710
  return Math.max(1, Math.ceil(text.length / 4));
@@ -56321,21 +57748,23 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
56321
57748
  const warnings = [];
56322
57749
  let { historyTurns, maxToolLoopIterations } = phaseKnobs(phase2);
56323
57750
  const envelope = opts?.requestSnapshot ?? null;
56324
- const replayBase = envelope ? {
56325
- provider: envelope.snapshot.provider,
56326
- model: envelope.snapshot.model,
56327
- systemMessages: envelope.snapshot.systemMessages,
56328
- tools: envelope.snapshot.tools
57751
+ const surface = envelope?.snapshot ?? opts?.requestSurface ?? null;
57752
+ const replayBase = surface ? {
57753
+ provider: surface.provider,
57754
+ model: surface.model,
57755
+ systemMessages: surface.systemMessages,
57756
+ tools: surface.tools
56329
57757
  } : opts?.providerStream ? { provider: "local", model: opts?.model ?? "unknown", systemMessages: [], tools: [] } : null;
56330
- const headerTokens = envelope ? estimateSystemTokensLite(envelope.snapshot.systemMessages) + estimateToolSchemaTokensLite(envelope.snapshot.tools) : 0;
57758
+ const headerTokens = surface ? estimateSystemTokensLite(surface.systemMessages) + estimateToolSchemaTokensLite(surface.tools) : 0;
56331
57759
  const convTokensOf = (h) => estimateConversationTokensLite(h);
56332
57760
  let hist = history2;
56333
- let estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
57761
+ let estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
56334
57762
  let occupancy = Math.min(1, estimated / contextLimit);
56335
57763
  let compactSummary = "";
56336
57764
  let messagesRemoved = 0;
56337
57765
  let cacheReuseExpected;
56338
57766
  let prunedTotal = 0;
57767
+ const compactRange = { sourceSeqs: [] };
56339
57768
  if (occupancy >= compact.warnAt && occupancy < compact.compactAt) {
56340
57769
  warnings.push(
56341
57770
  `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated}/${contextLimit} tok full-request est.) \u2014 consider /compact or shorter replies.`
@@ -56346,7 +57775,7 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
56346
57775
  if (pruned.stats.pruned > 0) {
56347
57776
  hist = pruned.messages;
56348
57777
  prunedTotal += pruned.stats.pruned;
56349
- estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
57778
+ estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
56350
57779
  occupancy = Math.min(1, estimated / contextLimit);
56351
57780
  warnings.push(
56352
57781
  `[budget] pruned ${pruned.stats.pruned} oversized tool result(s) \u2192 ${Math.round(occupancy * 100)}% (${estimated} tok).`
@@ -56358,11 +57787,12 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
56358
57787
  if (r.compacted) {
56359
57788
  messagesRemoved += r.messagesRemoved;
56360
57789
  if (r.summary) compactSummary = r.summary;
57790
+ mergeCompactRange(compactRange, r);
56361
57791
  if (r.cacheReuseExpected !== void 0) {
56362
57792
  cacheReuseExpected = r.cacheReuseExpected;
56363
57793
  }
56364
57794
  }
56365
- estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
57795
+ estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
56366
57796
  occupancy = Math.min(1, estimated / contextLimit);
56367
57797
  warnings.push(
56368
57798
  `[budget] ${label} \u2014 kept ~${forcedTurns} turns (${estimated} tok est.` + (r.messagesRemoved ? `, removed ${r.messagesRemoved} msgs` : "") + (r.cacheReuseExpected === false ? ", cache reuse NOT expected (model override)" : "") + ")."
@@ -56418,12 +57848,18 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
56418
57848
  warnings,
56419
57849
  maxToolLoopIterations,
56420
57850
  historyTurns,
56421
- estimatedHistoryTokens: envelope ? convTokensOf(hist) : estimated,
57851
+ estimatedHistoryTokens: surface ? convTokensOf(hist) : estimated,
56422
57852
  contextLimit,
56423
57853
  occupancy,
56424
57854
  compactSummary: compactSummary || void 0,
56425
57855
  messagesRemoved: messagesRemoved || void 0,
56426
- ...envelope ? { contextPressureTokens: estimated } : {},
57856
+ ...compactRange.fromSeq !== void 0 && compactRange.toSeq !== void 0 ? {
57857
+ compactedFromSeq: compactRange.fromSeq,
57858
+ compactedToSeq: compactRange.toSeq,
57859
+ compactSourceSeqs: compactRange.sourceSeqs,
57860
+ compactStrategy: compactRange.strategy
57861
+ } : {},
57862
+ ...surface ? { contextPressureTokens: estimated } : {},
56427
57863
  ...cacheReuseExpected !== void 0 ? { cacheReuseExpected } : {},
56428
57864
  ...cacheMetricsLine ? { cacheMetricsLine } : {}
56429
57865
  };
@@ -56458,6 +57894,231 @@ function estimateConversationTokensLite(messages) {
56458
57894
  return n;
56459
57895
  }
56460
57896
 
57897
+ // src/cli/budget/requestMeter.ts
57898
+ init_harness();
57899
+ function estimateTokensLocal(text) {
57900
+ if (!text) return 0;
57901
+ return Math.max(1, Math.ceil(text.length / 4));
57902
+ }
57903
+ var MESSAGE_OVERHEAD_TOKENS = 4;
57904
+ function estimateMessageTokens(m) {
57905
+ let n = MESSAGE_OVERHEAD_TOKENS + estimateTokensLocal(m.content ?? "");
57906
+ if (m.toolCalls) {
57907
+ for (const tc of m.toolCalls) {
57908
+ n += estimateTokensLocal(tc.name) + estimateTokensLocal(tc.id);
57909
+ n += estimateTokensLocal(JSON.stringify(tc.args ?? {}));
57910
+ }
57911
+ }
57912
+ if (m.reasoningContent) n += estimateTokensLocal(m.reasoningContent);
57913
+ if (m.toolCallId) n += estimateTokensLocal(m.toolCallId);
57914
+ return n;
57915
+ }
57916
+ function estimateToolSchemaTokens(tools) {
57917
+ let n = 0;
57918
+ for (const t of tools) {
57919
+ n += estimateTokensLocal(t.name) + estimateTokensLocal(t.description ?? "");
57920
+ n += estimateTokensLocal(JSON.stringify(t.parameters ?? {}));
57921
+ }
57922
+ return n + tools.length * MESSAGE_OVERHEAD_TOKENS;
57923
+ }
57924
+ function estimateSystemTokens(systemMessages) {
57925
+ let n = 0;
57926
+ for (const m of systemMessages) n += estimateMessageTokens(m);
57927
+ return n;
57928
+ }
57929
+ function estimateConversationTokens(conversation) {
57930
+ let n = 0;
57931
+ for (const m of conversation) n += estimateMessageTokens(m);
57932
+ return n;
57933
+ }
57934
+ function createFingerprintOnly(input) {
57935
+ return sha256Hex(
57936
+ stableStringify({
57937
+ provider: input.provider,
57938
+ model: input.model,
57939
+ systemMessages: input.systemMessages,
57940
+ tools: canonicalTools(input.tools)
57941
+ })
57942
+ );
57943
+ }
57944
+ function measureRequest(input) {
57945
+ const estimatedHeaderTokens = estimateSystemTokens(input.systemMessages) + estimateToolSchemaTokens(input.tools);
57946
+ const currentConversationTokens = estimateConversationTokens(input.conversation);
57947
+ let headerAnchored = false;
57948
+ let headerTokens = estimatedHeaderTokens;
57949
+ const anchorUsage = input.anchor?.usage;
57950
+ const anchorSnapshot = input.anchor?.snapshot;
57951
+ if (anchorUsage && anchorSnapshot) {
57952
+ const currentHeaderFp = createFingerprintOnly({
57953
+ provider: anchorSnapshot.provider,
57954
+ model: anchorSnapshot.model,
57955
+ systemMessages: input.systemMessages,
57956
+ tools: input.tools
57957
+ });
57958
+ if (currentHeaderFp === anchorSnapshot.headerFingerprint) {
57959
+ const anchorConv = estimateConversationTokens(anchorSnapshot.conversation);
57960
+ const headerFromUsage = Math.max(0, anchorUsage.promptTokens - anchorConv);
57961
+ if (headerFromUsage > 0) {
57962
+ headerTokens = headerFromUsage;
57963
+ headerAnchored = true;
57964
+ }
57965
+ }
57966
+ }
57967
+ const estimatedPromptTokens = headerTokens + currentConversationTokens;
57968
+ const reservedOutput = input.reservedOutputTokens ?? 0;
57969
+ const contextPressureTokens = estimatedPromptTokens + reservedOutput;
57970
+ return {
57971
+ estimatedPromptTokens,
57972
+ estimatedHeaderTokens,
57973
+ headerAnchored,
57974
+ contextPressureTokens,
57975
+ occupancy: Math.min(1, contextPressureTokens / input.contextLimit),
57976
+ purpose: input.purpose ?? "conversation"
57977
+ };
57978
+ }
57979
+
57980
+ // src/cli/budget/persistCompact.ts
57981
+ init_dist();
57982
+ init_session();
57983
+ init_headlessSpine();
57984
+ function compactEventPayload(budget, stateSnapshot, telemetry) {
57985
+ const summary = budget.compactSummary ?? "";
57986
+ const first = budget.history[0];
57987
+ const narrative = first?.role === "user" && first.content.includes("<compacted-summary>") ? first.content : summary;
57988
+ const checkpointContent2 = stateSnapshot ? formatCompactionStateSnapshot(stateSnapshot) + "\n\n" + narrative : narrative;
57989
+ const ranged = budget.compactedFromSeq !== void 0 && budget.compactedToSeq !== void 0;
57990
+ return {
57991
+ summary,
57992
+ messagesRemoved: budget.messagesRemoved ?? 0,
57993
+ ...telemetry ? { ...telemetry } : {},
57994
+ ...ranged ? {
57995
+ fromSeq: budget.compactedFromSeq,
57996
+ toSeq: budget.compactedToSeq,
57997
+ checkpoint: { role: "user", content: checkpointContent2 },
57998
+ ...budget.compactStrategy ? { strategy: budget.compactStrategy } : {},
57999
+ ...budget.compactSourceSeqs && budget.compactSourceSeqs.length > 0 ? { sourceEventSeqs: budget.compactSourceSeqs } : {},
58000
+ ...stateSnapshot ? {
58001
+ retainedCriterionIds: stateSnapshot.activeCriteria.filter((criterion) => criterion.required).map((criterion) => criterion.id),
58002
+ retainedEvidenceRefs: stateSnapshot.retainedEvidenceRefs,
58003
+ retainedState: {
58004
+ unresolvedIssueIds: stateSnapshot.unresolvedIssues.map((issue2) => issue2.id),
58005
+ affectedFiles: stateSnapshot.affectedFiles,
58006
+ ...stateSnapshot.missionState?.phase ? { missionStateRef: "phase:" + stateSnapshot.missionState.phase } : {}
58007
+ },
58008
+ stateSnapshot
58009
+ } : {}
58010
+ } : {}
58011
+ };
58012
+ }
58013
+
58014
+ // src/cli/budget/modelContextBuilder.ts
58015
+ init_headlessSpine();
58016
+ function messageWasRecompacted(message, history2) {
58017
+ if (message.compactedFromSeq === void 0) return false;
58018
+ return !history2.some((candidate) => candidate.seq !== void 0 && candidate.seq === message.seq);
58019
+ }
58020
+ async function sessionHistory(session) {
58021
+ if (!session || session.status !== "active") return null;
58022
+ const derived = await session.derivedPriorTurns();
58023
+ if (!derived || derived.length === 0) return null;
58024
+ return derivedModelSeed(derived);
58025
+ }
58026
+ async function buildModelContext(input) {
58027
+ const derived = await sessionHistory(input.session);
58028
+ const source = derived ? "session" : "fallback";
58029
+ const sourceHistory = derived ?? [...input.fallbackHistory];
58030
+ const inputTokens = estimateHistoryTokens(sourceHistory);
58031
+ const requestSurface = input.systemMessages || input.tools ? {
58032
+ provider: input.provider ?? "local",
58033
+ model: input.model ?? "unknown",
58034
+ systemMessages: input.systemMessages ?? [],
58035
+ tools: input.tools ?? []
58036
+ } : null;
58037
+ let budget = await applyBudgetPolicyAsync(sourceHistory, input.phase, {
58038
+ model: input.model,
58039
+ sessionTokens: input.sessionTokens,
58040
+ sessionId: input.sessionId,
58041
+ signal: input.signal,
58042
+ requestSnapshot: input.requestSnapshot,
58043
+ requestSurface,
58044
+ providerStream: input.providerStream
58045
+ });
58046
+ let history2 = budget.history;
58047
+ let compactionPayload;
58048
+ let durableCompaction = false;
58049
+ let reconstructedFromSession = false;
58050
+ let compactionMetrics;
58051
+ if ((budget.messagesRemoved ?? 0) > 0) {
58052
+ const ranged = budget.compactedFromSeq !== void 0 && budget.compactedToSeq !== void 0;
58053
+ const stateSnapshot = ranged && input.session?.compactionStateSnapshot ? await input.session.compactionStateSnapshot(budget.compactedToSeq) : null;
58054
+ const outputTokens = estimateHistoryTokens(budget.history);
58055
+ const telemetry = {
58056
+ inputTokens,
58057
+ outputTokens,
58058
+ savedTokens: Math.max(0, inputTokens - outputTokens),
58059
+ recompactionRate: sourceHistory.some(
58060
+ (message) => messageWasRecompacted(message, budget.history)
58061
+ ) ? 1 : 0,
58062
+ summaryStrategy: budget.compactStrategy ?? "extractive",
58063
+ ...budget.compactStrategy === "llm" && input.provider ? { provider: input.provider } : {},
58064
+ ...budget.compactStrategy === "llm" && input.model ? { model: input.model } : {}
58065
+ };
58066
+ compactionPayload = compactEventPayload(budget, stateSnapshot, telemetry);
58067
+ if (input.persistCompaction) {
58068
+ await input.persistCompaction(compactionPayload, budget);
58069
+ await input.session?.flush?.();
58070
+ if (ranged && input.session?.status === "active") {
58071
+ const replayed = await sessionHistory(input.session);
58072
+ if (replayed) {
58073
+ history2 = replayed;
58074
+ durableCompaction = true;
58075
+ reconstructedFromSession = true;
58076
+ }
58077
+ }
58078
+ }
58079
+ compactionMetrics = {
58080
+ count: 1,
58081
+ ...telemetry,
58082
+ restoreFailures: ranged && input.persistCompaction && !reconstructedFromSession ? 1 : 0
58083
+ };
58084
+ input.onCompactionMetric?.(compactionMetrics);
58085
+ }
58086
+ if (requestSurface) {
58087
+ const measured = measureRequest({
58088
+ systemMessages: requestSurface.systemMessages,
58089
+ tools: requestSurface.tools,
58090
+ conversation: history2,
58091
+ anchor: input.requestSnapshot,
58092
+ contextLimit: budget.contextLimit,
58093
+ reservedOutputTokens: 8192
58094
+ });
58095
+ budget = {
58096
+ ...budget,
58097
+ history: history2,
58098
+ estimatedHistoryTokens: estimateHistoryTokens(history2),
58099
+ occupancy: measured.occupancy,
58100
+ contextPressureTokens: measured.contextPressureTokens
58101
+ };
58102
+ } else {
58103
+ const estimated = estimateHistoryTokens(history2);
58104
+ budget = {
58105
+ ...budget,
58106
+ history: history2,
58107
+ estimatedHistoryTokens: estimated,
58108
+ occupancy: Math.min(1, estimated / budget.contextLimit)
58109
+ };
58110
+ }
58111
+ return {
58112
+ history: history2,
58113
+ budget,
58114
+ source,
58115
+ ...compactionPayload ? { compactionPayload } : {},
58116
+ ...compactionMetrics ? { compactionMetrics } : {},
58117
+ durableCompaction,
58118
+ reconstructedFromSession
58119
+ };
58120
+ }
58121
+
56461
58122
  // src/cli/hooks/useChatTurn.ts
56462
58123
  init_requestSnapshotStore();
56463
58124
  function useChatTurn(params) {
@@ -56493,19 +58154,7 @@ function useChatTurn(params) {
56493
58154
  try {
56494
58155
  const anchored = maybeAnchorShortAnswer(userText);
56495
58156
  const effectiveUserText = anchored ?? userText;
56496
- let historyForModel;
56497
- {
56498
- const mirror = writerRef.current?.spine ?? null;
56499
- let spineSeed = null;
56500
- if (mirror && mirror.status === "active") {
56501
- const derived = await mirror.derivedPriorTurns();
56502
- if (derived && derived.length > 0) {
56503
- spineSeed = derivedModelSeed(derived);
56504
- }
56505
- }
56506
- historyForModel = spineSeed ?? getHistory();
56507
- }
56508
- writerRef.current?.spine?.userMessage(effectiveUserText);
58157
+ let historyForModel = getHistory();
56509
58158
  const localCli = (process.env.ZELARI_LOCAL_CLI ?? "").trim();
56510
58159
  let localCliProvider = null;
56511
58160
  if (localCli) {
@@ -56547,7 +58196,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
56547
58196
  let settled = false;
56548
58197
  const askTimeoutMs = askUserTimeoutMs();
56549
58198
  let cancelAskTimeout = () => void 0;
56550
- const finish = (value) => {
58199
+ const finish2 = (value) => {
56551
58200
  if (settled) return;
56552
58201
  settled = true;
56553
58202
  cancelAskTimeout();
@@ -56563,7 +58212,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
56563
58212
  )}s \u2014 proseguo con assunzione documentata (ZELARI_ASK_USER_TIMEOUT_MS).`,
56564
58213
  Date.now()
56565
58214
  );
56566
- finish(null);
58215
+ finish2(null);
56567
58216
  },
56568
58217
  askTimeoutMs
56569
58218
  );
@@ -56571,8 +58220,8 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
56571
58220
  kind: "clarification",
56572
58221
  title: req.question,
56573
58222
  items: choices.map((c) => ({ value: c, label: c })),
56574
- onAnswer: (value) => finish(value),
56575
- onCancel: () => finish(null)
58223
+ onAnswer: (value) => finish2(value),
58224
+ onCancel: () => finish2(null)
56576
58225
  });
56577
58226
  }) : void 0;
56578
58227
  const onPermissionAsk = setPicker2 ? createPermissionAskHandler({
@@ -56634,33 +58283,42 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
56634
58283
  });
56635
58284
  }
56636
58285
  const cwd = process.cwd();
56637
- const budget = await applyBudgetPolicyAsync(historyForModel, getPhase(), {
58286
+ const requestSnapshot = getRequestSnapshotWithUsage(sessionId2);
58287
+ const modelContext = await buildModelContext({
58288
+ fallbackHistory: historyForModel,
58289
+ session: writerRef.current?.spine ?? null,
58290
+ phase: workPhase,
56638
58291
  model: getActiveModel(),
58292
+ provider: envConfig?.providerId ?? (localCli || "local"),
56639
58293
  sessionId: sessionId2,
56640
- // v1.36.0: envelope for full-request metering + cache-aware
56641
- // compaction replay (last warm prefix + provider usage anchor).
56642
- requestSnapshot: getRequestSnapshotWithUsage(sessionId2),
56643
- providerStream
58294
+ requestSnapshot,
58295
+ providerStream,
58296
+ onCompactionMetric: (metrics2) => recordCompactionMetrics(
58297
+ sessionId2,
58298
+ envConfig?.providerId ?? (localCli || "local"),
58299
+ getActiveModel(),
58300
+ metrics2
58301
+ ),
58302
+ persistCompaction: async (payload, compactBudget) => {
58303
+ const compactionEvent = createBrainEvent("session_compacted", sessionId2, {
58304
+ ...payload,
58305
+ ...requestSnapshot ? {
58306
+ sourceRequestFingerprint: requestSnapshot.snapshot.requestFingerprint,
58307
+ headerFingerprint: requestSnapshot.snapshot.headerFingerprint
58308
+ } : {},
58309
+ ...compactBudget.contextPressureTokens !== void 0 ? { sourceEstimatedTokens: compactBudget.contextPressureTokens } : {},
58310
+ ...compactBudget.cacheReuseExpected !== void 0 ? { cacheReuseExpected: compactBudget.cacheReuseExpected } : {}
58311
+ });
58312
+ await writerRef.current?.append(compactionEvent);
58313
+ }
56644
58314
  });
56645
- setHistory(budget.history);
56646
- for (const w of budget.warnings) {
56647
- appendSystem(setMessages, w, Date.now());
56648
- }
56649
- if ((budget.messagesRemoved ?? 0) > 0) {
56650
- const envelope = getRequestSnapshotWithUsage(sessionId2);
56651
- const compactionEvent = createBrainEvent("session_compacted", sessionId2, {
56652
- summary: budget.compactSummary ?? "",
56653
- messagesRemoved: budget.messagesRemoved ?? 0,
56654
- ...envelope ? {
56655
- sourceRequestFingerprint: envelope.snapshot.requestFingerprint,
56656
- headerFingerprint: envelope.snapshot.headerFingerprint
56657
- } : {},
56658
- ...budget.contextPressureTokens !== void 0 ? { sourceEstimatedTokens: budget.contextPressureTokens } : {},
56659
- ...budget.cacheReuseExpected !== void 0 ? { cacheReuseExpected: budget.cacheReuseExpected } : {}
56660
- });
56661
- void writerRef.current?.append(compactionEvent);
58315
+ const budget = modelContext.budget;
58316
+ historyForModel = modelContext.history;
58317
+ setHistory(historyForModel);
58318
+ for (const warning of budget.warnings) {
58319
+ appendSystem(setMessages, warning, Date.now());
56662
58320
  }
56663
- historyForModel = budget.history;
58321
+ writerRef.current?.spine?.userMessage(effectiveUserText);
56664
58322
  historySeedLen = historyForModel.length;
56665
58323
  let composedWorkspace = "";
56666
58324
  let composedInstructions = "";
@@ -57299,33 +58957,33 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
57299
58957
  return { completionOk: false, ran: false };
57300
58958
  }
57301
58959
  setBusy(true);
57302
- let councilHistory = getHistory();
57303
- {
57304
- const mirror = writerRef.current?.spine ?? null;
57305
- if (mirror && mirror.status === "active") {
57306
- const derived = await mirror.derivedPriorTurns();
57307
- if (derived && derived.length > 0) {
57308
- councilHistory = derivedModelSeed(derived);
57309
- }
58960
+ const anchored = maybeAnchorShortAnswer(text);
58961
+ const effectiveText = anchored ?? text;
58962
+ const councilContext = await buildModelContext({
58963
+ fallbackHistory: getHistory(),
58964
+ session: writerRef.current?.spine ?? null,
58965
+ phase: getPhase(),
58966
+ model: envConfig.model,
58967
+ provider: envConfig.providerId,
58968
+ sessionId: sessionId2,
58969
+ onCompactionMetric: (metrics) => recordCompactionMetrics(
58970
+ sessionId2,
58971
+ envConfig.providerId,
58972
+ envConfig.model,
58973
+ metrics
58974
+ ),
58975
+ persistCompaction: async (payload) => {
58976
+ await writerRef.current?.append(
58977
+ createBrainEvent("session_compacted", sessionId2, payload)
58978
+ );
57310
58979
  }
57311
- }
57312
- const councilBudget = await applyBudgetPolicyAsync(councilHistory, getPhase(), {
57313
- model: envConfig.model
57314
58980
  });
57315
- setHistory(councilBudget.history);
57316
- for (const w of councilBudget.warnings) {
57317
- appendSystem(setMessages, w, Date.now());
58981
+ const councilBudget = councilContext.budget;
58982
+ setHistory(councilContext.history);
58983
+ for (const warning of councilBudget.warnings) {
58984
+ appendSystem(setMessages, warning, Date.now());
57318
58985
  }
57319
- if ((councilBudget.messagesRemoved ?? 0) > 0) {
57320
- void writerRef.current?.append(
57321
- createBrainEvent("session_compacted", sessionId2, {
57322
- summary: councilBudget.compactSummary ?? "",
57323
- messagesRemoved: councilBudget.messagesRemoved ?? 0
57324
- })
57325
- );
57326
- }
57327
- const anchored = maybeAnchorShortAnswer(text);
57328
- const effectiveText = anchored ?? text;
58986
+ writerRef.current?.spine?.userMessage(effectiveText);
57329
58987
  appendSystem(
57330
58988
  setMessages,
57331
58989
  `[phase] ${describePhase(getPhase())}`,
@@ -57361,7 +59019,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57361
59019
  let settled = false;
57362
59020
  const askTimeoutMs = askUserTimeoutMs();
57363
59021
  let cancelAskTimeout = () => void 0;
57364
- const finish = (value) => {
59022
+ const finish2 = (value) => {
57365
59023
  if (settled) return;
57366
59024
  settled = true;
57367
59025
  cancelAskTimeout();
@@ -57377,7 +59035,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57377
59035
  )}s \u2014 il membro prosegue con assunzione documentata (ZELARI_ASK_USER_TIMEOUT_MS).`,
57378
59036
  Date.now()
57379
59037
  );
57380
- finish(null);
59038
+ finish2(null);
57381
59039
  },
57382
59040
  askTimeoutMs
57383
59041
  );
@@ -57385,8 +59043,8 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57385
59043
  kind: "clarification",
57386
59044
  title: req.question,
57387
59045
  items: choices.map((c) => ({ value: c, label: c })),
57388
- onAnswer: (value) => finish(value),
57389
- onCancel: () => finish(null)
59046
+ onAnswer: (value) => finish2(value),
59047
+ onCancel: () => finish2(null)
57390
59048
  });
57391
59049
  }) : void 0;
57392
59050
  let phaseRunMode = overrides.runMode ?? (workPhase === "plan" ? "design-phase" : "implementation");
@@ -57530,7 +59188,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57530
59188
  Date.now()
57531
59189
  );
57532
59190
  let settled = false;
57533
- const finish = (value) => {
59191
+ const finish2 = (value) => {
57534
59192
  if (settled) return;
57535
59193
  settled = true;
57536
59194
  setPicker2(null);
@@ -57540,8 +59198,8 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
57540
59198
  kind: "clarification",
57541
59199
  title: req.question,
57542
59200
  items: choices.map((c) => ({ value: c, label: c })),
57543
- onAnswer: (value) => finish(value),
57544
- onCancel: () => finish(null)
59201
+ onAnswer: (value) => finish2(value),
59202
+ onCancel: () => finish2(null)
57545
59203
  });
57546
59204
  }) : void 0
57547
59205
  })) {
@@ -58146,7 +59804,7 @@ function usePermissionBroker(opts) {
58146
59804
  }
58147
59805
  let settled = false;
58148
59806
  let cancelWaitTimeout;
58149
- const finish = (value) => {
59807
+ const finish2 = (value) => {
58150
59808
  if (settled) return;
58151
59809
  settled = true;
58152
59810
  cancelWaitTimeout?.();
@@ -58162,7 +59820,7 @@ function usePermissionBroker(opts) {
58162
59820
  )}s \u2014 richiesta annullata.`,
58163
59821
  Date.now()
58164
59822
  );
58165
- finish(null);
59823
+ finish2(null);
58166
59824
  }, waitTimeoutMs);
58167
59825
  appendSystem(
58168
59826
  setMessages,
@@ -58174,8 +59832,8 @@ function usePermissionBroker(opts) {
58174
59832
  kind: "clarification",
58175
59833
  title: req.question,
58176
59834
  items: choices.map((c) => ({ value: c, label: c })),
58177
- onAnswer: (value) => finish(value),
58178
- onCancel: () => finish(null)
59835
+ onAnswer: (value) => finish2(value),
59836
+ onCancel: () => finish2(null)
58179
59837
  });
58180
59838
  });
58181
59839
  void startPermissionBroker(socketPath, {
@@ -62280,7 +63938,7 @@ function verifierReviewEnabled(selection = loadVerifierModelSelection(), env = p
62280
63938
  if (v === "1" || v === "true" || v === "on") return true;
62281
63939
  return selection.mode === "fixed";
62282
63940
  }
62283
- function makeVerifierCallModel(loadStream, identity, timeoutMs = 12e4) {
63941
+ function makeVerifierCallModel(loadStream, identity, timeoutMs2 = 12e4) {
62284
63942
  return async ({ system, user }) => {
62285
63943
  const stream = await loadStream(identity.provider, identity.model);
62286
63944
  if (!stream) {
@@ -62294,7 +63952,7 @@ function makeVerifierCallModel(loadStream, identity, timeoutMs = 12e4) {
62294
63952
  model: identity.model,
62295
63953
  provider: identity.provider,
62296
63954
  tools: [],
62297
- signal: AbortSignal.timeout(timeoutMs)
63955
+ signal: AbortSignal.timeout(timeoutMs2)
62298
63956
  });
62299
63957
  return { text, provider: identity.provider, model: identity.model };
62300
63958
  };
@@ -62339,6 +63997,7 @@ async function runAdvisoryVerifierReview(evaluation, deps = {}) {
62339
63997
  }
62340
63998
 
62341
63999
  // src/cli/runHeadless.ts
64000
+ init_metrics2();
62342
64001
  init_headlessSpine();
62343
64002
  async function runHeadless(opts) {
62344
64003
  resetTaskSpawnCount();
@@ -62451,6 +64110,18 @@ ${err.stack}` : "";
62451
64110
  if (opts.krakenGraph) {
62452
64111
  return runHeadlessKrakenGraph(opts, provider, model);
62453
64112
  }
64113
+ const { shouldRunGauntletHostLoop: shouldRunGauntletHostLoop2 } = await Promise.resolve().then(() => (init_policy(), policy_exports));
64114
+ if (shouldRunGauntletHostLoop2(opts)) {
64115
+ const { runHeadlessGauntlet: runHeadlessGauntlet2 } = await Promise.resolve().then(() => (init_run(), run_exports));
64116
+ return runHeadlessGauntlet2(opts, provider, model);
64117
+ }
64118
+ if (opts.gauntlet) {
64119
+ 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";
64120
+ const line = `[gauntlet] flag set but host loop skipped (${why})`;
64121
+ if (opts.output === "json") emitEvent({ type: "log", message: line });
64122
+ else process.stderr.write(`[zelari-code --headless] ${line}
64123
+ `);
64124
+ }
62454
64125
  if (mode === "zelari") {
62455
64126
  return runHeadlessZelari(opts, provider, model, providerStream);
62456
64127
  }
@@ -62662,11 +64333,11 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
62662
64333
  });
62663
64334
  const seededHistory = await seedHeadlessModelHistory(spine, opts.history);
62664
64335
  emitEvent(sessionStartedEvent(spine));
62665
- if (opts.task) spine.userMessage(opts.task);
62666
64336
  resetKrakenCandidates();
62667
64337
  resetKrakenTurnMetrics();
62668
64338
  const { registry: toolRegistry } = createBuiltinToolRegistry({
62669
64339
  planMode: planModeFromOpts(opts),
64340
+ gauntletParent: Boolean(opts.gauntlet) && !planModeFromOpts(opts),
62670
64341
  // Fase 1 (ADR-0020): anchor tentacles to the provider/model THIS run
62671
64342
  // resolved (--provider/--model opts or Desktop's selector), mirroring
62672
64343
  // what the kraken-graph path already does for its executor.
@@ -62811,14 +64482,38 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
62811
64482
  }
62812
64483
  ];
62813
64484
  }
62814
- const historySeed = seededHistory.history;
64485
+ const modelContext = await buildModelContext({
64486
+ fallbackHistory: seededHistory.history,
64487
+ session: spine.spine,
64488
+ phase: opts.phase ?? "build",
64489
+ model,
64490
+ provider,
64491
+ systemMessages,
64492
+ tools,
64493
+ sessionId: spine.sessionId,
64494
+ providerStream,
64495
+ onCompactionMetric: (metrics) => recordCompactionMetrics(spine.sessionId, provider, model, metrics),
64496
+ persistCompaction: async (payload) => {
64497
+ await spine.appendEvent({
64498
+ kind: "session.compacted",
64499
+ actor: { type: "system" },
64500
+ data: { ...payload }
64501
+ });
64502
+ }
64503
+ });
64504
+ const historySeed = modelContext.history;
64505
+ for (const warning of modelContext.budget.warnings) {
64506
+ if (opts.output === "json") emitEvent({ type: "log", message: warning });
64507
+ else process.stderr.write("[zelari-code --headless] " + warning + "\n");
64508
+ }
62815
64509
  const effectiveTask = buildAgentUserWithHistory(opts.task, historySeed);
64510
+ if (opts.task) spine.userMessage(effectiveTask);
62816
64511
  const maxToolLoop = (() => {
62817
64512
  const n = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
62818
64513
  default: 30,
62819
64514
  min: 1
62820
64515
  });
62821
- return n;
64516
+ return Math.min(n, modelContext.budget.maxToolLoopIterations);
62822
64517
  })();
62823
64518
  async function runSinglePass(messages, passSessionId) {
62824
64519
  const harness = new AgentHarness({
@@ -63119,7 +64814,6 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
63119
64814
  });
63120
64815
  const seededHistory = await seedHeadlessModelHistory(spine, opts.history);
63121
64816
  emitEvent(sessionStartedEvent(spine));
63122
- if (opts.task) spine.userMessage(opts.task);
63123
64817
  const { shouldAllowCouncilBuild: shouldAllowCouncilBuild2 } = await Promise.resolve().then(() => (init_buildPolicy(), buildPolicy_exports));
63124
64818
  let councilRunMode = planModeFromOpts(opts) ? "design-phase" : "implementation";
63125
64819
  let softGated = false;
@@ -63136,8 +64830,36 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
63136
64830
  );
63137
64831
  const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
63138
64832
  const feedbackStore = new FeedbackStore2();
63139
- const historySeed = seededHistory.history;
64833
+ const contextTools = toolRegistry.toOpenAITools().map((tool) => ({
64834
+ name: tool.function.name,
64835
+ description: tool.function.description,
64836
+ parameters: tool.function.parameters
64837
+ }));
64838
+ const councilContext = await buildModelContext({
64839
+ fallbackHistory: seededHistory.history,
64840
+ session: spine.spine,
64841
+ phase: councilRunMode === "design-phase" ? "plan" : "build",
64842
+ model,
64843
+ provider,
64844
+ tools: contextTools,
64845
+ sessionId: spine.sessionId,
64846
+ providerStream,
64847
+ onCompactionMetric: (metrics) => recordCompactionMetrics(spine.sessionId, provider, model, metrics),
64848
+ persistCompaction: async (payload) => {
64849
+ await spine.appendEvent({
64850
+ kind: "session.compacted",
64851
+ actor: { type: "system" },
64852
+ data: { ...payload }
64853
+ });
64854
+ }
64855
+ });
64856
+ const historySeed = councilContext.history;
64857
+ for (const warning of councilContext.budget.warnings) {
64858
+ if (opts.output === "json") emitEvent({ type: "log", message: warning });
64859
+ else process.stderr.write("[zelari-code --headless] " + warning + "\n");
64860
+ }
63140
64861
  const effectiveTask = buildCouncilTaskWithHistory(opts.task, historySeed);
64862
+ if (opts.task) spine.userMessage(effectiveTask);
63141
64863
  let exitCode = 0;
63142
64864
  const scrub = createStreamScrubber2();
63143
64865
  let lastAssistantText = "";
@@ -63247,7 +64969,6 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
63247
64969
  });
63248
64970
  const seededHistory = await seedHeadlessModelHistory(spine, opts.history);
63249
64971
  emitEvent(sessionStartedEvent(spine));
63250
- if (opts.task) spine.userMessage(opts.task);
63251
64972
  spine.missionPhase("design", "mission-start");
63252
64973
  const { buildMissionBrief: buildMissionBrief2 } = await Promise.resolve().then(() => (init_council(), council_exports));
63253
64974
  const { hasWorkspacePlan: hasWorkspacePlan2 } = await Promise.resolve().then(() => (init_planDetect(), planDetect_exports));
@@ -63279,8 +65000,33 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
63279
65000
  process.stderr.write(message + "\n");
63280
65001
  }
63281
65002
  };
63282
- const historySeed = seededHistory.history;
65003
+ const contextTools = toolRegistry.toOpenAITools().map((tool) => ({
65004
+ name: tool.function.name,
65005
+ description: tool.function.description,
65006
+ parameters: tool.function.parameters
65007
+ }));
65008
+ const missionContext = await buildModelContext({
65009
+ fallbackHistory: seededHistory.history,
65010
+ session: spine.spine,
65011
+ phase: opts.phase ?? "build",
65012
+ model,
65013
+ provider,
65014
+ tools: contextTools,
65015
+ sessionId: spine.sessionId,
65016
+ providerStream,
65017
+ onCompactionMetric: (metrics) => recordCompactionMetrics(spine.sessionId, provider, model, metrics),
65018
+ persistCompaction: async (payload) => {
65019
+ await spine.appendEvent({
65020
+ kind: "session.compacted",
65021
+ actor: { type: "system" },
65022
+ data: { ...payload }
65023
+ });
65024
+ }
65025
+ });
65026
+ const historySeed = missionContext.history;
65027
+ for (const warning of missionContext.budget.warnings) emit(warning);
63283
65028
  const missionTask = buildCouncilTaskWithHistory(opts.task, historySeed);
65029
+ if (opts.task) spine.userMessage(missionTask);
63284
65030
  emit(`[zelari] mission brief
63285
65031
  ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMvp?.title }, null, 0)}`);
63286
65032
  if (buildViaAgent) {