zelari-code 2.4.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.
@@ -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();
@@ -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();
@@ -40354,717 +40831,171 @@ var init_toolRegistry = __esm({
40354
40831
  }
40355
40832
  });
40356
40833
 
40357
- // src/cli/phase.ts
40358
- var phase_exports = {};
40359
- __export(phase_exports, {
40360
- PHASES: () => PHASES,
40361
- PLAN_ALLOWED_WRITE_TOOLS: () => PLAN_ALLOWED_WRITE_TOOLS,
40362
- PLAN_BLOCKED_TOOLS: () => PLAN_BLOCKED_TOOLS,
40363
- describePhase: () => describePhase,
40364
- nextPhase: () => nextPhase,
40365
- parsePhase: () => parsePhase
40366
- });
40367
- function parsePhase(input) {
40368
- const v = input.trim().toLowerCase();
40369
- 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);
40370
40840
  }
40371
- function nextPhase(current) {
40372
- 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);
40373
40846
  }
40374
- function describePhase(phase2) {
40375
- switch (phase2) {
40376
- case "plan":
40377
- return "plan \u2014 explore & design only (no project writes; plan files allowed)";
40378
- default:
40379
- 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;
40380
40853
  }
40381
40854
  }
40382
- var PHASES, PLAN_ALLOWED_WRITE_TOOLS, PLAN_BLOCKED_TOOLS;
40383
- var init_phase = __esm({
40384
- "src/cli/phase.ts"() {
40385
- "use strict";
40386
- PHASES = ["plan", "build"];
40387
- PLAN_ALLOWED_WRITE_TOOLS = /* @__PURE__ */ new Set([
40388
- // Workspace plan/docs intentional plan-mode outputs
40389
- "createPlan",
40390
- "createTask",
40391
- "updateTask",
40392
- "createMilestone",
40393
- "createDocument",
40394
- "createDecision",
40395
- "linkDocuments"
40396
- // Soft writes that only touch .zelari / plan paths are still gated in
40397
- // toolRegistry by path when needed; write_file/edit_file stay DENIED.
40398
- ]);
40399
- PLAN_BLOCKED_TOOLS = /* @__PURE__ */ new Set([
40400
- "write_file",
40401
- "edit_file",
40402
- "apply_diff",
40403
- "bash"
40404
- ]);
40405
- }
40406
- });
40407
-
40408
- // src/cli/mode.ts
40409
- function nextMode(current) {
40410
- const i = MODES.indexOf(current);
40411
- 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");
40412
40868
  }
40413
- function parseMode(input) {
40414
- const v = input.trim().toLowerCase();
40415
- if (MODES.includes(v)) return v;
40416
- return MODE_ALIASES[v] ?? null;
40869
+ function stripStored(s) {
40870
+ const { artifactDir: _a3, ...meta3 } = s;
40871
+ return meta3;
40417
40872
  }
40418
- function describeMode(mode) {
40419
- switch (mode) {
40420
- case "council":
40421
- return "council \u2014 multi-member plan/design (Caronte\u2026Lucifero; build needs ZELARI_COUNCIL_CAN_BUILD=1)";
40422
- case "zelari":
40423
- return "zelari \u2014 mission: plan@council \u2192 build@kraken (legacy: ZELARI_BUILD_VIA_AGENT=0)";
40424
- default:
40425
- return "kraken \u2014 super-agent lead (spawns explore/general/verify tentacles; default implementer)";
40426
- }
40873
+ function isStateEnabled(env = process.env) {
40874
+ return env.ZELARI_STATE !== "0";
40427
40875
  }
40428
- var MODES, MODE_ALIASES;
40429
- var init_mode = __esm({
40430
- "src/cli/mode.ts"() {
40431
- "use strict";
40432
- MODES = ["kraken", "council", "zelari"];
40433
- MODE_ALIASES = {
40434
- agent: "kraken",
40435
- single: "kraken"
40436
- };
40437
- }
40438
- });
40439
-
40440
- // src/cli/headless.ts
40441
- import { readFileSync as readFileSync22 } from "node:fs";
40442
- function defaultProfileForMode(mode) {
40443
- switch (mode) {
40444
- case "council":
40445
- return "council/v1";
40446
- case "zelari":
40447
- return "mission/v1";
40448
- default:
40449
- 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();
40450
40884
  }
40451
40885
  }
40452
- function parseHeadlessFlags(argv) {
40453
- if (!argv.includes("--headless")) {
40454
- return { options: null };
40455
- }
40456
- let task;
40457
- let output = "json";
40458
- let mode = "kraken";
40459
- let phase2 = "build";
40460
- let modeExplicit = false;
40461
- let councilFlag = false;
40462
- let provider;
40463
- let model;
40464
- let history2;
40465
- let todos2;
40466
- let once = false;
40467
- let profile;
40468
- let resumeSessionId;
40469
- let exportSessionPath;
40470
- let strictDone = false;
40471
- let krakenGraph;
40472
- let planOnly = process.env.ZELARI_KRAKEN_PLAN_ONLY === "1" || process.env.ZELARI_KRAKEN_PLAN_ONLY === "true";
40473
- let runPlan = process.env.ZELARI_KRAKEN_RUN_PLAN;
40474
- let gauntlet = process.env.ZELARI_GAUNTLET === "1" || process.env.ZELARI_GAUNTLET === "true";
40475
- for (let i = 0; i < argv.length; i++) {
40476
- const arg = argv[i];
40477
- if (arg === "--headless") continue;
40478
- if (arg === "--output") {
40479
- const next = argv[i + 1];
40480
- if (next === "json" || next === "plain") {
40481
- output = next;
40482
- i++;
40483
- } else {
40484
- return {
40485
- options: null,
40486
- error: `--output requires 'json' or 'plain', got '${next ?? "(missing)"}'`
40487
- };
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 });
40488
40910
  }
40489
- } else if (arg === "--task") {
40490
- task = argv[i + 1];
40491
- i++;
40492
- } else if (arg === "--task-file") {
40493
- const next = argv[i + 1];
40494
- if (next) {
40495
- try {
40496
- const fromFile = readFileSync22(next, "utf-8");
40497
- if (fromFile.trim()) task = fromFile;
40498
- } 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
+ );
40499
40916
  }
40500
- }
40501
- i++;
40502
- } else if (arg === "--council") {
40503
- councilFlag = true;
40504
- } else if (arg === "--mode") {
40505
- const next = argv[i + 1];
40506
- const parsed = next ? parseMode(next) : null;
40507
- if (!parsed) {
40508
- return {
40509
- options: null,
40510
- 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, "/")
40511
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);
40512
40949
  }
40513
- mode = parsed;
40514
- modeExplicit = true;
40515
- i++;
40516
- } else if (arg === "--phase") {
40517
- const next = argv[i + 1];
40518
- const parsed = next ? parsePhase(next) : null;
40519
- if (!parsed) {
40520
- return {
40521
- options: null,
40522
- error: `--phase requires 'plan' or 'build', got '${next ?? "(missing)"}'`
40523
- };
40950
+ async head() {
40951
+ const head = await readJsonFile(this.headPath);
40952
+ if (!head?.id) return null;
40953
+ return this.get(head.id);
40524
40954
  }
40525
- phase2 = parsed;
40526
- i++;
40527
- } else if (arg === "--provider") {
40528
- provider = argv[i + 1];
40529
- i++;
40530
- } else if (arg === "--model") {
40531
- model = argv[i + 1];
40532
- i++;
40533
- } else if (arg === "--history" || arg === "--history-file") {
40534
- const next = argv[i + 1];
40535
- if (next) {
40536
- let raw = null;
40537
- if (arg === "--history-file") {
40538
- try {
40539
- raw = readFileSync22(next, "utf-8");
40540
- } catch {
40541
- raw = null;
40542
- }
40543
- } else {
40544
- 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 [];
40545
40965
  }
40546
- if (raw) {
40966
+ const ids = [];
40967
+ for (const line of raw.split("\n")) {
40968
+ const t = line.trim();
40969
+ if (!t) continue;
40547
40970
  try {
40548
- const parsedHist = JSON.parse(raw);
40549
- if (Array.isArray(parsedHist)) {
40550
- history2 = parsedHist.filter(
40551
- (m) => !!m && typeof m === "object" && typeof m.role === "string"
40552
- ).map((m) => {
40553
- const role = String(m.role);
40554
- const raw2 = m.content;
40555
- const content = typeof raw2 === "string" ? raw2 : raw2 == null ? "" : typeof raw2 === "object" ? JSON.stringify(raw2) : String(raw2);
40556
- const msg = {
40557
- role,
40558
- content
40559
- };
40560
- if (typeof m.toolCallId === "string") {
40561
- msg.toolCallId = m.toolCallId;
40562
- }
40563
- return msg;
40564
- }).filter(
40565
- (m) => m.role === "user" || m.role === "assistant" || m.role === "tool" || m.role === "system"
40566
- );
40567
- }
40971
+ const row = JSON.parse(t);
40972
+ if (row.id) ids.push(row.id);
40568
40973
  } catch {
40569
40974
  }
40570
40975
  }
40571
- 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;
40572
40983
  }
40573
- } else if (arg === "--todos") {
40574
- const next = argv[i + 1];
40575
- if (next) {
40576
- try {
40577
- const parsed = JSON.parse(next);
40578
- if (Array.isArray(parsed)) {
40579
- todos2 = parsed.filter(
40580
- (t) => !!t && typeof t === "object" && typeof t.content === "string"
40581
- ).map((t) => ({
40582
- id: typeof t.id === "string" ? t.id : void 0,
40583
- content: String(t.content).slice(0, 500),
40584
- status: t.status
40585
- }));
40586
- }
40587
- } catch {
40984
+ async setHead(id) {
40985
+ const meta3 = await this.get(id);
40986
+ if (!meta3) {
40987
+ throw new Error(`DurableStateStore.setHead: unknown commit ${id}`);
40588
40988
  }
40589
- i++;
40989
+ await writeJsonAtomic(this.headPath, { id, updatedAt: Date.now() });
40990
+ return meta3;
40590
40991
  }
40591
- } else if (arg === "--once") {
40592
- once = true;
40593
- } else if (arg === "--profile") {
40594
- const next = argv[i + 1];
40595
- if (!next || next.startsWith("--")) {
40596
- return { options: null, error: `--profile requires a profile id (e.g. kraken/v1), got '${next ?? "(missing)"}'` };
40597
- }
40598
- try {
40599
- resolveProfile(next);
40600
- } catch (err) {
40601
- return {
40602
- options: null,
40603
- error: err instanceof Error ? err.message : String(err)
40604
- };
40605
- }
40606
- profile = next;
40607
- i++;
40608
- } else if (arg === "--resume") {
40609
- const next = argv[i + 1];
40610
- if (!next || next.startsWith("--")) {
40611
- return { options: null, error: `--resume requires a session id, got '${next ?? "(missing)"}'` };
40612
- }
40613
- resumeSessionId = next;
40614
- i++;
40615
- } else if (arg === "--export-session") {
40616
- const next = argv[i + 1];
40617
- if (!next || next.startsWith("--")) {
40618
- return { options: null, error: `--export-session requires a path (or - for stdout), got '${next ?? "(missing)"}'` };
40619
- }
40620
- exportSessionPath = next;
40621
- i++;
40622
- } else if (arg === "--strict-done") {
40623
- strictDone = true;
40624
- } else if (arg === "--no-strict-done") {
40625
- strictDone = false;
40626
- process.env.ZELARI_MISSION_STRICT = "0";
40627
- } else if (arg === "--kraken-graph") {
40628
- krakenGraph = argv[i + 1];
40629
- i++;
40630
- } else if (arg === "--kraken-graph-file") {
40631
- const next = argv[i + 1];
40632
- if (next) {
40633
- try {
40634
- const fromFile = readFileSync22(next, "utf-8");
40635
- if (fromFile.trim()) krakenGraph = fromFile;
40636
- } catch {
40637
- }
40638
- }
40639
- i++;
40640
- } else if (arg === "--plan-only") {
40641
- planOnly = true;
40642
- } else if (arg === "--run-plan") {
40643
- runPlan = argv[i + 1];
40644
- i++;
40645
- } else if (arg === "--gauntlet") {
40646
- gauntlet = true;
40647
- } else if (arg === "--no-gauntlet") {
40648
- gauntlet = false;
40649
- }
40650
- }
40651
- if (councilFlag && !modeExplicit) {
40652
- mode = "council";
40653
- } else if (councilFlag && modeExplicit && mode !== "council") {
40654
- return {
40655
- options: null,
40656
- error: `--council conflicts with --mode ${mode}`
40657
- };
40658
- }
40659
- if (task && krakenGraph) {
40660
- return { options: null, error: "--task and --kraken-graph are mutually exclusive" };
40661
- }
40662
- if ((!task || task.trim().length === 0) && (!krakenGraph || krakenGraph.trim().length === 0)) {
40663
- return { options: null, error: "--headless requires --task <prompt> or --kraken-graph <goal>" };
40664
- }
40665
- return {
40666
- options: {
40667
- task: task ?? "",
40668
- output,
40669
- mode,
40670
- phase: phase2,
40671
- useCouncil: mode === "council",
40672
- provider,
40673
- model,
40674
- ...history2 && history2.length > 0 ? { history: history2 } : {},
40675
- ...todos2 && todos2.length > 0 ? { todos: todos2 } : {},
40676
- ...once ? { once: true } : {},
40677
- ...profile ? { profile } : {},
40678
- ...resumeSessionId ? { resumeSessionId } : {},
40679
- ...exportSessionPath ? { exportSessionPath } : {},
40680
- ...strictDone ? { strictDone: true } : {},
40681
- ...krakenGraph ? { krakenGraph } : {},
40682
- ...planOnly ? { planOnly: true } : {},
40683
- ...runPlan ? { runPlan } : {},
40684
- ...gauntlet ? { gauntlet: true } : {}
40685
- }
40686
- };
40687
- }
40688
- async function resolveHeadlessKey(providerId) {
40689
- const spec = PROVIDERS.find((p3) => p3.id === providerId);
40690
- if (!spec) {
40691
- return { error: `unknown provider: '${providerId}'` };
40692
- }
40693
- const resolved = await resolveApiKeyWithMeta(providerId);
40694
- if (!resolved || !resolved.apiKey) {
40695
- return {
40696
- error: `no API key for provider '${providerId}'.
40697
- Set the env var ${spec.envVar} or save a key via /login.`
40698
- };
40699
- }
40700
- const { resolveBaseUrl: resolveBaseUrl2 } = await Promise.resolve().then(() => (init_openai_compatible(), openai_compatible_exports));
40701
- return {
40702
- apiKey: resolved.apiKey,
40703
- baseUrl: resolveBaseUrl2(providerId)
40704
- };
40705
- }
40706
- function resolveHeadlessProvider(opts) {
40707
- const provider = opts.provider ?? getActiveProvider().id;
40708
- const model = opts.model ?? getModelForProvider(provider);
40709
- return { provider, model };
40710
- }
40711
- function emitEvent(event) {
40712
- process.stdout.write(JSON.stringify(event) + "\n");
40713
- }
40714
- var init_headless = __esm({
40715
- "src/cli/headless.ts"() {
40716
- "use strict";
40717
- init_keyStore();
40718
- init_providerConfig();
40719
- init_openai_compatible();
40720
- init_phase();
40721
- init_mode();
40722
- init_runtime2();
40723
- }
40724
- });
40725
-
40726
- // src/cli/headlessSpine.ts
40727
- var headlessSpine_exports = {};
40728
- __export(headlessSpine_exports, {
40729
- derivedModelSeed: () => derivedModelSeed,
40730
- exportSessionById: () => exportSessionById,
40731
- missionStateFromSpine: () => missionStateFromSpine,
40732
- openHeadlessSpine: () => openHeadlessSpine,
40733
- resolveHeadlessProfileId: () => resolveHeadlessProfileId,
40734
- seedHeadlessModelHistory: () => seedHeadlessModelHistory,
40735
- sessionStartedEvent: () => sessionStartedEvent
40736
- });
40737
- function sessionStartedEvent(handle) {
40738
- return {
40739
- type: "session_started",
40740
- sessionId: handle.sessionId,
40741
- spine: handle.spine.status
40742
- };
40743
- }
40744
- function resolveHeadlessProfileId(mode, explicit) {
40745
- if (explicit) return resolveProfile(explicit).id;
40746
- return defaultProfileForMode(mode ?? "kraken");
40747
- }
40748
- async function openHeadlessSpine(opts) {
40749
- const profileId = resolveHeadlessProfileId(opts.mode, opts.profile);
40750
- let profileTools = [];
40751
- try {
40752
- profileTools = resolveProfile(profileId).tools;
40753
- } catch {
40754
- profileTools = [];
40755
- }
40756
- const extra = {
40757
- profile: profileId,
40758
- workspace: opts.workspace ?? process.cwd(),
40759
- toolManifestHash: profileTools.length > 0 ? toolManifestHash(profileTools) : void 0
40760
- };
40761
- const mirrorOpts = {
40762
- baseDir: opts.baseDir,
40763
- quiet: opts.quiet,
40764
- extraStarted: extra
40765
- };
40766
- const spine = await SessionSpineMirror.adopt(opts.sessionId, mirrorOpts);
40767
- if (spine.status === "active") {
40768
- spine.note("headless.profile", { profile: profileId, mode: opts.mode ?? "kraken" });
40769
- }
40770
- return {
40771
- sessionId: opts.sessionId,
40772
- profileId,
40773
- spine,
40774
- observe(ev) {
40775
- if (ev && typeof ev === "object" && "type" in ev) {
40776
- spine.mirrorBrainEvent(ev);
40777
- }
40778
- },
40779
- userMessage(text) {
40780
- spine.userMessage(text);
40781
- },
40782
- verificationRun(payload) {
40783
- spine.verificationRun(payload);
40784
- },
40785
- appendEvent(input) {
40786
- return spine.appendEvent(input);
40787
- },
40788
- lastVerificationRun() {
40789
- return spine.lastVerificationRun();
40790
- },
40791
- missionPhase(phase2, note) {
40792
- spine.missionPhase(phase2, note);
40793
- },
40794
- missionProgress(advice) {
40795
- spine.missionProgress(advice);
40796
- },
40797
- note(text, data) {
40798
- spine.note(text, data);
40799
- },
40800
- async close(reason = "host-exit") {
40801
- await spine.close(reason);
40802
- },
40803
- async interrupt(note) {
40804
- if (note) spine.note("headless.interrupt", { note });
40805
- await spine.release();
40806
- },
40807
- async exportJson() {
40808
- try {
40809
- const store6 = new SessionStore(resolveSessionsDir({ baseDir: opts.baseDir }));
40810
- if (!await store6.exists(opts.sessionId)) return null;
40811
- return await exportSessionJson(store6, opts.sessionId);
40812
- } catch {
40813
- return null;
40814
- }
40815
- }
40816
- };
40817
- }
40818
- async function exportSessionById(sessionId2, baseDir) {
40819
- try {
40820
- const store6 = SessionStore.withDefaults(baseDir ? { baseDir } : {});
40821
- if (!await store6.exists(sessionId2)) {
40822
- return { ok: false, error: `session not found: ${sessionId2}` };
40823
- }
40824
- return { ok: true, json: await exportSessionJson(store6, sessionId2) };
40825
- } catch (err) {
40826
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
40827
- }
40828
- }
40829
- async function missionStateFromSpine(sessionId2, baseDir) {
40830
- try {
40831
- const store6 = SessionStore.withDefaults(baseDir ? { baseDir } : {});
40832
- if (!await store6.exists(sessionId2)) return null;
40833
- const projection = await store6.projection(sessionId2);
40834
- return deriveMissionState(projection);
40835
- } catch {
40836
- return null;
40837
- }
40838
- }
40839
- async function seedHeadlessModelHistory(handle, legacy) {
40840
- const mirror = handle.spine;
40841
- const legacySeed = filterLegacySeed(legacy);
40842
- if (mirror.status !== "active") {
40843
- return { history: legacySeed, importedCount: 0, source: "legacy-fallback" };
40844
- }
40845
- const existing = await mirror.derivedPriorTurns();
40846
- if (existing && existing.length > 0) {
40847
- return { history: derivedModelSeed(existing), importedCount: 0, source: "spine" };
40848
- }
40849
- if (legacySeed.length === 0) {
40850
- return { history: [], importedCount: 0, source: "spine" };
40851
- }
40852
- for (const m of legacySeed) {
40853
- if (m.role === "user") {
40854
- mirror.userMessage(m.content);
40855
- } else {
40856
- mirror.assistantMessage(m.content, { imported: "legacy-history" });
40857
- }
40858
- }
40859
- await mirror.flush();
40860
- const derived = await mirror.derivedPriorTurns() ?? [];
40861
- return {
40862
- history: derivedModelSeed(derived),
40863
- importedCount: legacySeed.length,
40864
- source: "spine-import"
40865
- };
40866
- }
40867
- function filterLegacySeed(legacy) {
40868
- return (legacy ?? []).filter((m) => m.role === "user" || m.role === "assistant").map(
40869
- (m) => m.role === "assistant" && m.content ? {
40870
- role: "assistant",
40871
- content: cleanAgentContent(m.content, {
40872
- stripQuestion: false,
40873
- stripThink: false
40874
- })
40875
- } : { role: m.role, content: m.content ?? "" }
40876
- ).filter((m) => (m.content ?? "").trim().length > 0);
40877
- }
40878
- function derivedModelSeed(derived) {
40879
- return derivedToAgentMessages(derived).map(
40880
- (m) => m.role === "system" ? { role: "user", content: m.content } : m
40881
- ).map(
40882
- (m) => m.role === "assistant" && m.content ? {
40883
- role: "assistant",
40884
- content: cleanAgentContent(m.content, {
40885
- stripQuestion: false,
40886
- stripThink: false
40887
- })
40888
- } : m
40889
- ).filter((m) => m.role === "user" || m.role === "assistant").filter((m) => (m.content ?? "").trim().length > 0);
40890
- }
40891
- var init_headlessSpine = __esm({
40892
- "src/cli/headlessSpine.ts"() {
40893
- "use strict";
40894
- init_dist();
40895
- init_session();
40896
- init_mission2();
40897
- init_runtime2();
40898
- init_sessionSpine();
40899
- init_headless();
40900
- }
40901
- });
40902
-
40903
- // src/cli/state/fileStateStore.ts
40904
- import { createHash as createHash11, randomUUID as randomUUID2 } from "node:crypto";
40905
- import { promises as fs21 } from "node:fs";
40906
- import * as path41 from "node:path";
40907
- function shortId() {
40908
- return randomUUID2().replace(/-/g, "").slice(0, 12);
40909
- }
40910
- async function writeJsonAtomic(filePath, data) {
40911
- await fs21.mkdir(path41.dirname(filePath), { recursive: true });
40912
- const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
40913
- await fs21.writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
40914
- await fs21.rename(tmp, filePath);
40915
- }
40916
- async function readJsonFile(filePath) {
40917
- try {
40918
- const raw = await fs21.readFile(filePath, "utf8");
40919
- return JSON.parse(raw);
40920
- } catch {
40921
- return null;
40922
- }
40923
- }
40924
- function defaultSummary(input, discoveries) {
40925
- if (input.summary?.trim()) return input.summary.trim();
40926
- const lines = [
40927
- `# ${input.label}`,
40928
- "",
40929
- `- mode: ${input.mode}`,
40930
- input.layer ? `- layer: ${input.layer}` : null,
40931
- `- verification: ran=${input.verification.ran} ok=${input.verification.ok}`,
40932
- "",
40933
- "## Discoveries",
40934
- ...discoveries.map((d) => `- [${d.kind}] ${d.summary}`)
40935
- ].filter((x) => x !== null);
40936
- return lines.join("\n");
40937
- }
40938
- function stripStored(s) {
40939
- const { artifactDir: _a3, ...meta3 } = s;
40940
- return meta3;
40941
- }
40942
- function isStateEnabled(env = process.env) {
40943
- return env.ZELARI_STATE !== "0";
40944
- }
40945
- async function getStateStore(projectRoot, env = process.env) {
40946
- if (!isStateEnabled(env)) return new NoopDurableStateStore();
40947
- const store6 = new FileDurableStateStore();
40948
- try {
40949
- await store6.init(projectRoot);
40950
- return store6;
40951
- } catch {
40952
- return new NoopDurableStateStore();
40953
- }
40954
- }
40955
- function hashStablePrompt(stable) {
40956
- return createHash11("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
40957
- }
40958
- var DEFAULT_MATERIALIZE_CHARS, FileDurableStateStore, NoopDurableStateStore;
40959
- var init_fileStateStore = __esm({
40960
- "src/cli/state/fileStateStore.ts"() {
40961
- "use strict";
40962
- DEFAULT_MATERIALIZE_CHARS = 4e3;
40963
- FileDurableStateStore = class {
40964
- root = "";
40965
- stateDir = "";
40966
- commitsDir = "";
40967
- artifactsDir = "";
40968
- headPath = "";
40969
- indexPath = "";
40970
- async init(projectRoot) {
40971
- this.root = projectRoot;
40972
- this.stateDir = path41.join(projectRoot, ".zelari", "state");
40973
- this.commitsDir = path41.join(this.stateDir, "commits");
40974
- this.artifactsDir = path41.join(this.stateDir, "artifacts");
40975
- this.headPath = path41.join(this.stateDir, "HEAD.json");
40976
- this.indexPath = path41.join(this.stateDir, "index.jsonl");
40977
- await fs21.mkdir(this.commitsDir, { recursive: true });
40978
- await fs21.mkdir(this.artifactsDir, { recursive: true });
40979
- }
40980
- async commit(input) {
40981
- if (!input.force && input.verification.ran && !input.verification.ok) {
40982
- throw new Error(
40983
- "DurableStateStore.commit refused: verification ran and failed (pass force:true for soft commit)"
40984
- );
40985
- }
40986
- const discoveries = input.discoveries ?? [];
40987
- const parent = await this.head();
40988
- const id = shortId();
40989
- const artifactRel = path41.join("artifacts", id);
40990
- const artifactAbs = path41.join(this.artifactsDir, id);
40991
- await fs21.mkdir(artifactAbs, { recursive: true });
40992
- const summary = defaultSummary(input, discoveries);
40993
- await fs21.writeFile(path41.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
40994
- await writeJsonAtomic(path41.join(artifactAbs, "discoveries.json"), discoveries);
40995
- await writeJsonAtomic(path41.join(artifactAbs, "verification.json"), input.verification);
40996
- const meta3 = {
40997
- id,
40998
- parentId: parent?.id ?? null,
40999
- createdAt: Date.now(),
41000
- sessionId: input.sessionId,
41001
- mode: input.mode,
41002
- layer: input.layer,
41003
- label: input.label,
41004
- workspaceCheckpointId: input.workspaceCheckpointId,
41005
- verification: {
41006
- ...input.verification,
41007
- reportPath: input.verification.reportPath ?? path41.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
41008
- },
41009
- changedPaths: input.changedPaths ?? [],
41010
- stablePromptHash: input.stablePromptHash,
41011
- discoveryCount: discoveries.length,
41012
- artifactDir: artifactRel.replace(/\\/g, "/")
41013
- };
41014
- await writeJsonAtomic(path41.join(this.commitsDir, `${id}.json`), meta3);
41015
- await writeJsonAtomic(this.headPath, { id, updatedAt: meta3.createdAt });
41016
- await fs21.appendFile(this.indexPath, JSON.stringify({ id, createdAt: meta3.createdAt, label: meta3.label }) + "\n", "utf8");
41017
- return stripStored(meta3);
41018
- }
41019
- async head() {
41020
- const head = await readJsonFile(this.headPath);
41021
- if (!head?.id) return null;
41022
- return this.get(head.id);
41023
- }
41024
- async get(id) {
41025
- const stored = await readJsonFile(path41.join(this.commitsDir, `${id}.json`));
41026
- return stored ? stripStored(stored) : null;
41027
- }
41028
- async list(limit = 20) {
41029
- let raw;
41030
- try {
41031
- raw = await fs21.readFile(this.indexPath, "utf8");
41032
- } catch {
41033
- return [];
41034
- }
41035
- const ids = [];
41036
- for (const line of raw.split("\n")) {
41037
- const t = line.trim();
41038
- if (!t) continue;
41039
- try {
41040
- const row = JSON.parse(t);
41041
- if (row.id) ids.push(row.id);
41042
- } catch {
41043
- }
41044
- }
41045
- const slice = ids.slice(-Math.max(1, limit)).reverse();
41046
- const out = [];
41047
- for (const id of slice) {
41048
- const m = await this.get(id);
41049
- if (m) out.push(m);
41050
- }
41051
- return out;
41052
- }
41053
- async setHead(id) {
41054
- const meta3 = await this.get(id);
41055
- if (!meta3) {
41056
- throw new Error(`DurableStateStore.setHead: unknown commit ${id}`);
41057
- }
41058
- await writeJsonAtomic(this.headPath, { id, updatedAt: Date.now() });
41059
- return meta3;
41060
- }
41061
- async loadDiscoveries(id) {
41062
- const meta3 = id ? await this.get(id) : await this.head();
41063
- if (!meta3) return [];
41064
- const stored = await readJsonFile(path41.join(this.commitsDir, `${meta3.id}.json`));
41065
- if (!stored?.artifactDir) return [];
41066
- const discPath = path41.join(this.stateDir, stored.artifactDir, "discoveries.json");
41067
- 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) ?? [];
41068
40999
  }
41069
41000
  async materializeContext(id, maxChars = DEFAULT_MATERIALIZE_CHARS) {
41070
41001
  const meta3 = id ? await this.get(id) : await this.head();
@@ -41234,25 +41165,37 @@ function extractiveHistorySummary(dropped, opts) {
41234
41165
  if (dropped.length === 0) return "No prior turns.";
41235
41166
  const userGoals = [];
41236
41167
  const assistantNotes = [];
41168
+ const userConstraints = [];
41169
+ const unresolved = [];
41170
+ const verification = [];
41171
+ const decisions = [];
41237
41172
  const tools = /* @__PURE__ */ new Map();
41238
41173
  const files = /* @__PURE__ */ new Set();
41239
41174
  let toolResults = 0;
41240
41175
  for (const m of dropped) {
41241
41176
  if (m.role === "user" && m.content.trim()) {
41242
- 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);
41243
41180
  } else if (m.role === "assistant") {
41244
41181
  if (m.content.trim()) {
41245
- 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);
41246
41187
  }
41247
41188
  if (m.toolCalls) {
41248
41189
  for (const tc of m.toolCalls) {
41249
41190
  tools.set(tc.name, (tools.get(tc.name) ?? 0) + 1);
41250
- collectPaths(tc.args, files);
41191
+ collectPaths2(tc.args, files);
41251
41192
  }
41252
41193
  }
41253
41194
  } else if (m.role === "tool") {
41254
41195
  toolResults += 1;
41255
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));
41256
41199
  }
41257
41200
  }
41258
41201
  const parts = [
@@ -41263,6 +41206,22 @@ function extractiveHistorySummary(dropped, opts) {
41263
41206
  parts.push("## User goals / requests");
41264
41207
  for (const g of userGoals.slice(-6)) parts.push(`- ${g}`);
41265
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
+ }
41266
41225
  if (assistantNotes.length) {
41267
41226
  parts.push("## Assistant conclusions (truncated)");
41268
41227
  for (const a of assistantNotes.slice(-5)) parts.push(`- ${a}`);
@@ -41289,7 +41248,7 @@ function oneLine(s, max) {
41289
41248
  if (t.length <= max) return t;
41290
41249
  return `${t.slice(0, max - 1)}\u2026`;
41291
41250
  }
41292
- function collectPaths(args, out) {
41251
+ function collectPaths2(args, out) {
41293
41252
  if (!args || typeof args !== "object") return;
41294
41253
  const obj = args;
41295
41254
  for (const key of ["path", "file", "filepath", "filePath", "target", "cwd"]) {
@@ -41408,6 +41367,36 @@ Be concise.
41408
41367
  });
41409
41368
 
41410
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
+ }
41411
41400
  function resolveMaxMessages(opts) {
41412
41401
  const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
41413
41402
  let turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
@@ -41481,16 +41470,25 @@ function pruneToolResultsDetailed(messages, opts) {
41481
41470
  function compactHistory(messages, opts) {
41482
41471
  return compactHistoryDetailed(messages, opts).messages;
41483
41472
  }
41484
- function buildCheckpointMessage(summaryText) {
41473
+ function buildCheckpointMessage(summaryText, range) {
41485
41474
  return {
41486
41475
  role: "user",
41487
- 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
+ } : {}
41488
41482
  };
41489
41483
  }
41490
41484
  function compactHistoryDetailed(messages, opts) {
41491
41485
  const maxMessages = resolveMaxMessages(opts);
41492
41486
  if (maxMessages === 0) {
41493
- return { messages: [], compacted: true, messagesRemoved: messages.length, summary: "" };
41487
+ return withDroppedRange(
41488
+ { messages: [], compacted: true, messagesRemoved: messages.length, summary: "" },
41489
+ messages,
41490
+ "extractive"
41491
+ );
41494
41492
  }
41495
41493
  if (messages.length <= maxMessages * 2 && !opts?.force) {
41496
41494
  return {
@@ -41511,19 +41509,25 @@ function compactHistoryDetailed(messages, opts) {
41511
41509
  };
41512
41510
  }
41513
41511
  const droppedMsgs = messages.slice(0, cut);
41512
+ const droppedRange = compactedRangeFromDropped(droppedMsgs);
41514
41513
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
41515
41514
  const kept = pruned.messages;
41516
41515
  const summaryText = extractiveHistorySummary(droppedMsgs);
41517
41516
  const summary = buildCheckpointMessage(
41518
- 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"
41519
41530
  );
41520
- return {
41521
- messages: [summary, ...kept],
41522
- compacted: true,
41523
- messagesRemoved: cut,
41524
- summary: summary.content,
41525
- prunedToolResults: pruned.stats.pruned
41526
- };
41527
41531
  }
41528
41532
  async function compactHistoryAsync(messages, opts) {
41529
41533
  const base = compactHistoryDetailed(messages, opts);
@@ -41559,16 +41563,21 @@ async function compactHistoryAsync(messages, opts) {
41559
41563
  }
41560
41564
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
41561
41565
  const kept = pruned.messages;
41562
- const summary = buildCheckpointMessage(summaryText);
41563
- return {
41564
- messages: [summary, ...kept],
41565
- compacted: true,
41566
- messagesRemoved: cut,
41567
- summary: summaryText,
41568
- prunedToolResults: pruned.stats.pruned,
41569
- cacheReuseExpected,
41570
- replayExactPrefix
41571
- };
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
+ );
41572
41581
  }
41573
41582
  function roughTokens(msgs) {
41574
41583
  let n = 0;
@@ -41603,252 +41612,799 @@ function recordRequestUsage(sessionId2, usage) {
41603
41612
  if (!entry) return;
41604
41613
  entry.usage = usage;
41605
41614
  }
41606
- function getRequestSnapshotWithUsage(sessionId2) {
41607
- return store5.get(sessionId2) ?? null;
41615
+ function getRequestSnapshotWithUsage(sessionId2) {
41616
+ return store5.get(sessionId2) ?? null;
41617
+ }
41618
+ function clearAllRequestSnapshots() {
41619
+ store5.clear();
41620
+ }
41621
+ var store5;
41622
+ var init_requestSnapshotStore = __esm({
41623
+ "src/cli/budget/requestSnapshotStore.ts"() {
41624
+ "use strict";
41625
+ store5 = /* @__PURE__ */ new Map();
41626
+ }
41627
+ });
41628
+
41629
+ // src/cli/hooks/conversationContext.ts
41630
+ var conversationContext_exports = {};
41631
+ __export(conversationContext_exports, {
41632
+ _resetConversationContextForTests: () => _resetConversationContextForTests,
41633
+ appendMessages: () => appendMessages,
41634
+ buildAgentUserWithHistory: () => buildAgentUserWithHistory,
41635
+ buildContinueUserMessage: () => buildContinueUserMessage,
41636
+ buildCouncilTaskWithHistory: () => buildCouncilTaskWithHistory,
41637
+ clearHistory: () => clearHistory,
41638
+ compactInPlace: () => compactInPlace,
41639
+ expectsDiskImplementation: () => expectsDiskImplementation,
41640
+ formatHistoryForCouncil: () => formatHistoryForCouncil,
41641
+ formatHistoryMessages: () => formatHistoryMessages,
41642
+ getHistory: () => getHistory,
41643
+ getLastClarification: () => getLastClarification,
41644
+ hydrateHistory: () => hydrateHistory,
41645
+ isShortContinueReply: () => isShortContinueReply,
41646
+ maybeAnchorShortAnswer: () => maybeAnchorShortAnswer,
41647
+ serializeHistory: () => serializeHistory,
41648
+ setHistory: () => setHistory,
41649
+ setLastClarification: () => setLastClarification
41650
+ });
41651
+ import { existsSync as existsSync25 } from "node:fs";
41652
+ import { join as join19 } from "node:path";
41653
+ function getHistory() {
41654
+ return history;
41655
+ }
41656
+ function setHistory(messages) {
41657
+ const projected = applySessionSurface(messages);
41658
+ history = projected === messages ? [...messages] : projected;
41659
+ }
41660
+ function compactInPlace(cwd = process.cwd()) {
41661
+ const durableStatePresent = existsSync25(join19(cwd, ".zelari", "state", "HEAD.json"));
41662
+ history = applySessionSurface(compactHistory(history, { durableStatePresent }));
41663
+ }
41664
+ function appendMessages(msgs) {
41665
+ if (msgs.length === 0) return;
41666
+ history = applySessionSurface(history.concat(msgs));
41667
+ }
41668
+ function clearHistory() {
41669
+ history = [];
41670
+ lastClarification = null;
41671
+ clearAllRequestSnapshots();
41672
+ clearSessionTodos();
41673
+ clearSessionPermissionGrants();
41674
+ }
41675
+ function serializeHistory() {
41676
+ return [...history];
41677
+ }
41678
+ function hydrateHistory(messages) {
41679
+ const projected = applySessionSurface(messages);
41680
+ history = projected === messages ? [...messages] : projected;
41681
+ }
41682
+ function getLastClarification() {
41683
+ return lastClarification;
41684
+ }
41685
+ function setLastClarification(c) {
41686
+ lastClarification = c ? { question: c.question, choices: c.choices, at: Date.now() } : null;
41687
+ }
41688
+ function maybeAnchorShortAnswer(userText) {
41689
+ const clar = lastClarification;
41690
+ if (!clar) return null;
41691
+ const trimmed = userText.trim();
41692
+ if (!trimmed) return null;
41693
+ if (trimmed.length > 80 || trimmed.includes("\n")) return null;
41694
+ const lower = trimmed.toLowerCase();
41695
+ const choices = clar.choices;
41696
+ const matched = choices.find((c) => c.toLowerCase() === lower) ?? choices.find((c) => c.toLowerCase().startsWith(lower)) ?? choices.find((c) => lower.startsWith(c.toLowerCase().slice(0, Math.min(4, c.length))));
41697
+ let choiceLabel = matched ?? null;
41698
+ if (!choiceLabel && /^\d{1,2}$/.test(trimmed)) {
41699
+ const idx = Number.parseInt(trimmed, 10) - 1;
41700
+ if (idx >= 0 && idx < choices.length) choiceLabel = choices[idx] ?? null;
41701
+ }
41702
+ if (!choiceLabel && trimmed.length > 24) return null;
41703
+ const picked = choiceLabel ?? trimmed;
41704
+ return `The user is answering your previous clarifying question.
41705
+ Question: ${clar.question}
41706
+ Choices were: ${choices.join(" | ")}
41707
+ User's answer: ${picked}
41708
+ Proceed using this answer; do not re-ask the same question unless the answer is still ambiguous.`;
41709
+ }
41710
+ function formatHistoryForCouncil(maxTurns = 4) {
41711
+ return formatHistoryMessages(history, maxTurns);
41712
+ }
41713
+ function formatHistoryMessages(messages, maxTurns = 6, maxTotalChars = 12e3) {
41714
+ if (messages.length === 0) return "";
41715
+ let turns = 0;
41716
+ const chunk = [];
41717
+ for (let i = messages.length - 1; i >= 0 && turns < maxTurns; i--) {
41718
+ const m = messages[i];
41719
+ if (m.role === "user") {
41720
+ chunk.push(`User: ${truncate(m.content, 800)}`);
41721
+ turns += 1;
41722
+ } else if (m.role === "assistant" && m.content.trim()) {
41723
+ chunk.push(`Assistant: ${truncate(m.content, 2e3)}`);
41724
+ }
41725
+ }
41726
+ if (chunk.length === 0) return "";
41727
+ let body = ["## Prior conversation (rolling context)", ...chunk.reverse()].join(
41728
+ "\n"
41729
+ );
41730
+ if (body.length > maxTotalChars) {
41731
+ body = `\u2026
41732
+ ${body.slice(body.length - maxTotalChars)}`;
41733
+ }
41734
+ return body;
41735
+ }
41736
+ function isShortContinueReply(task) {
41737
+ const trimmed = task.trim();
41738
+ if (!trimmed) return false;
41739
+ if (SHORT_CONTINUE.test(trimmed)) return true;
41740
+ if (trimmed.length <= 80 && !trimmed.includes("\n") && SHORT_CONTINUE_LOOSE.test(trimmed)) {
41741
+ return true;
41742
+ }
41743
+ return false;
41744
+ }
41745
+ function buildContinueUserMessage(task, prior, opts) {
41746
+ if (!isShortContinueReply(task) || prior.length === 0) return null;
41747
+ const lastAsst = [...prior].reverse().find((m) => m.role === "assistant" && (m.content ?? "").trim());
41748
+ if (!lastAsst) return null;
41749
+ const max = opts?.maxPriorChars ?? 8e3;
41750
+ const trimmed = task.trim();
41751
+ return `The user says "${trimmed}" \u2014 this is a CONTINUATION of an existing multi-turn session (the Desktop phase may have switched plan\u2194build; mode may have changed). This is NOT a new conversation and you DO have prior context below.
41752
+
41753
+ ## CRITICAL \u2014 plan text \u2260 done on disk
41754
+ The prior assistant output is a PLAN / SPEC / PROPOSAL (or analysis). It is NOT proof that project files already contain those changes.
41755
+ The user CONFIRMED the plan and wants you to IMPLEMENT it ON DISK NOW.
41756
+ - You MUST use write_file and/or edit_file (and bash when needed) to apply every planned change.
41757
+ - Reading files alone is incomplete. Do not stop after read_file/list_files/grep.
41758
+ - Do NOT claim "already implemented" / "tutto fatto" unless you verified the changes exist on disk in THIS turn (read after your own successful writes).
41759
+ - Do NOT restart from zero or re-ask for the overall goal.
41760
+
41761
+ ## Prior assistant output (plan to implement \u2014 authoritative)
41762
+ ${truncate(lastAsst.content, max)}
41763
+
41764
+ ## Instruction
41765
+ Implement the plan on disk now with mutating tools, then briefly list the files you wrote/edited.`;
41766
+ }
41767
+ function expectsDiskImplementation(task, phase2, prior) {
41768
+ if ((phase2 ?? "build") === "plan") return false;
41769
+ const trimmed = task.trim();
41770
+ if (!trimmed) return false;
41771
+ if (isShortContinueReply(trimmed)) return true;
41772
+ if (/\b(implement|implementa|scrivi|scriviamo|applica|modifica|fix|write|edit|crea|aggiungi|aggiorna|apply|patch)\b/i.test(
41773
+ trimmed
41774
+ )) {
41775
+ return true;
41776
+ }
41777
+ if (prior && prior.length > 0 && isShortContinueReply(trimmed)) {
41778
+ const lastAsst = [...prior].reverse().find((m) => m.role === "assistant" && (m.content ?? "").trim());
41779
+ if (lastAsst && /\b(se confermi|passo alla|scriv|implement|on disk|write_file|modifiche proposte|riepilogo delle modifiche)\b/i.test(
41780
+ lastAsst.content
41781
+ )) {
41782
+ return true;
41783
+ }
41784
+ }
41785
+ return false;
41786
+ }
41787
+ function buildCouncilTaskWithHistory(task, prior) {
41788
+ const messages = prior ?? [];
41789
+ const trimmed = task.trim();
41790
+ let userPart = maybeAnchorShortAnswer(task) ?? task;
41791
+ const continued = buildContinueUserMessage(trimmed, messages, {
41792
+ maxPriorChars: 4500
41793
+ });
41794
+ if (continued) {
41795
+ userPart = continued;
41796
+ }
41797
+ const block = formatHistoryMessages(messages, 6, 12e3);
41798
+ if (!block) return userPart;
41799
+ if (userPart.includes("Prior assistant output")) {
41800
+ return userPart;
41801
+ }
41802
+ return `${block}
41803
+
41804
+ ## Current user request
41805
+ ${userPart}`;
41806
+ }
41807
+ function buildAgentUserWithHistory(task, prior) {
41808
+ const messages = prior ?? [];
41809
+ const anchored = maybeAnchorShortAnswer(task);
41810
+ if (anchored) return anchored;
41811
+ return buildContinueUserMessage(task, messages, { maxPriorChars: 8e3 }) ?? task;
41812
+ }
41813
+ function truncate(s, max) {
41814
+ const t = s.replace(/\s+/g, " ").trim();
41815
+ if (t.length <= max) return t;
41816
+ return `${t.slice(0, max - 1)}\u2026`;
41817
+ }
41818
+ function _resetConversationContextForTests() {
41819
+ history = [];
41820
+ lastClarification = null;
41821
+ clearSessionTodos();
41822
+ clearSessionPermissionGrants();
41823
+ }
41824
+ var history, lastClarification, SHORT_CONTINUE, SHORT_CONTINUE_LOOSE;
41825
+ var init_conversationContext = __esm({
41826
+ "src/cli/hooks/conversationContext.ts"() {
41827
+ "use strict";
41828
+ init_toolPermissions();
41829
+ init_sessionTodos();
41830
+ init_historyCompaction();
41831
+ init_requestSnapshotStore();
41832
+ init_observationStore();
41833
+ history = [];
41834
+ lastClarification = null;
41835
+ SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed|conferma|confermo|applica|fai|scrivi|esegui|implementa|vai pure|fai pure|ok procedi|sì procedi|si procedi)$/i;
41836
+ SHORT_CONTINUE_LOOSE = /\b(procedi|continua|continue|conferma|confermo|applica|implementa|scriv[ia]|esegui|vai pure|fai pure|go ahead|proceed)\b/i;
41837
+ }
41838
+ });
41839
+
41840
+ // src/cli/phaseState.ts
41841
+ var phaseState_exports = {};
41842
+ __export(phaseState_exports, {
41843
+ _resetPhaseForTests: () => _resetPhaseForTests,
41844
+ getPhase: () => getPhase,
41845
+ setPhase: () => setPhase
41846
+ });
41847
+ function getPhase() {
41848
+ return phase;
41849
+ }
41850
+ function setPhase(next) {
41851
+ phase = next;
41852
+ }
41853
+ function _resetPhaseForTests() {
41854
+ phase = "build";
41855
+ }
41856
+ var phase;
41857
+ var init_phaseState = __esm({
41858
+ "src/cli/phaseState.ts"() {
41859
+ "use strict";
41860
+ phase = "build";
41861
+ }
41862
+ });
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 };
41608
42217
  }
41609
- function clearAllRequestSnapshots() {
41610
- store5.clear();
42218
+ function emitEvent(event) {
42219
+ process.stdout.write(JSON.stringify(event) + "\n");
41611
42220
  }
41612
- var store5;
41613
- var init_requestSnapshotStore = __esm({
41614
- "src/cli/budget/requestSnapshotStore.ts"() {
42221
+ var init_headless = __esm({
42222
+ "src/cli/headless.ts"() {
41615
42223
  "use strict";
41616
- store5 = /* @__PURE__ */ new Map();
42224
+ init_keyStore();
42225
+ init_providerConfig();
42226
+ init_openai_compatible();
42227
+ init_phase();
42228
+ init_mode();
42229
+ init_runtime2();
41617
42230
  }
41618
42231
  });
41619
42232
 
41620
- // src/cli/hooks/conversationContext.ts
41621
- var conversationContext_exports = {};
41622
- __export(conversationContext_exports, {
41623
- _resetConversationContextForTests: () => _resetConversationContextForTests,
41624
- appendMessages: () => appendMessages,
41625
- buildAgentUserWithHistory: () => buildAgentUserWithHistory,
41626
- buildContinueUserMessage: () => buildContinueUserMessage,
41627
- buildCouncilTaskWithHistory: () => buildCouncilTaskWithHistory,
41628
- clearHistory: () => clearHistory,
41629
- compactInPlace: () => compactInPlace,
41630
- expectsDiskImplementation: () => expectsDiskImplementation,
41631
- formatHistoryForCouncil: () => formatHistoryForCouncil,
41632
- formatHistoryMessages: () => formatHistoryMessages,
41633
- getHistory: () => getHistory,
41634
- getLastClarification: () => getLastClarification,
41635
- hydrateHistory: () => hydrateHistory,
41636
- isShortContinueReply: () => isShortContinueReply,
41637
- maybeAnchorShortAnswer: () => maybeAnchorShortAnswer,
41638
- serializeHistory: () => serializeHistory,
41639
- setHistory: () => setHistory,
41640
- setLastClarification: () => setLastClarification
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
41641
42243
  });
41642
- import { existsSync as existsSync25 } from "node:fs";
41643
- import { join as join19 } from "node:path";
41644
- function getHistory() {
41645
- return history;
41646
- }
41647
- function setHistory(messages) {
41648
- const projected = applySessionSurface(messages);
41649
- history = projected === messages ? [...messages] : projected;
41650
- }
41651
- function compactInPlace(cwd = process.cwd()) {
41652
- const durableStatePresent = existsSync25(join19(cwd, ".zelari", "state", "HEAD.json"));
41653
- history = applySessionSurface(compactHistory(history, { durableStatePresent }));
41654
- }
41655
- function appendMessages(msgs) {
41656
- if (msgs.length === 0) return;
41657
- history = applySessionSurface(history.concat(msgs));
41658
- }
41659
- function clearHistory() {
41660
- history = [];
41661
- lastClarification = null;
41662
- clearAllRequestSnapshots();
41663
- clearSessionTodos();
41664
- clearSessionPermissionGrants();
41665
- }
41666
- function serializeHistory() {
41667
- return [...history];
41668
- }
41669
- function hydrateHistory(messages) {
41670
- const projected = applySessionSurface(messages);
41671
- history = projected === messages ? [...messages] : projected;
41672
- }
41673
- function getLastClarification() {
41674
- return lastClarification;
42244
+ function sessionStartedEvent(handle) {
42245
+ return {
42246
+ type: "session_started",
42247
+ sessionId: handle.sessionId,
42248
+ spine: handle.spine.status
42249
+ };
41675
42250
  }
41676
- function setLastClarification(c) {
41677
- lastClarification = c ? { question: c.question, choices: c.choices, at: Date.now() } : null;
42251
+ function resolveHeadlessProfileId(mode, explicit) {
42252
+ if (explicit) return resolveProfile(explicit).id;
42253
+ return defaultProfileForMode(mode ?? "kraken");
41678
42254
  }
41679
- function maybeAnchorShortAnswer(userText) {
41680
- const clar = lastClarification;
41681
- if (!clar) return null;
41682
- const trimmed = userText.trim();
41683
- if (!trimmed) return null;
41684
- if (trimmed.length > 80 || trimmed.includes("\n")) return null;
41685
- const lower = trimmed.toLowerCase();
41686
- const choices = clar.choices;
41687
- const matched = choices.find((c) => c.toLowerCase() === lower) ?? choices.find((c) => c.toLowerCase().startsWith(lower)) ?? choices.find((c) => lower.startsWith(c.toLowerCase().slice(0, Math.min(4, c.length))));
41688
- let choiceLabel = matched ?? null;
41689
- if (!choiceLabel && /^\d{1,2}$/.test(trimmed)) {
41690
- const idx = Number.parseInt(trimmed, 10) - 1;
41691
- if (idx >= 0 && idx < choices.length) choiceLabel = choices[idx] ?? null;
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 = [];
41692
42262
  }
41693
- if (!choiceLabel && trimmed.length > 24) return null;
41694
- const picked = choiceLabel ?? trimmed;
41695
- return `The user is answering your previous clarifying question.
41696
- Question: ${clar.question}
41697
- Choices were: ${choices.join(" | ")}
41698
- User's answer: ${picked}
41699
- Proceed using this answer; do not re-ask the same question unless the answer is still ambiguous.`;
41700
- }
41701
- function formatHistoryForCouncil(maxTurns = 4) {
41702
- return formatHistoryMessages(history, maxTurns);
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
+ };
41703
42324
  }
41704
- function formatHistoryMessages(messages, maxTurns = 6, maxTotalChars = 12e3) {
41705
- if (messages.length === 0) return "";
41706
- let turns = 0;
41707
- const chunk = [];
41708
- for (let i = messages.length - 1; i >= 0 && turns < maxTurns; i--) {
41709
- const m = messages[i];
41710
- if (m.role === "user") {
41711
- chunk.push(`User: ${truncate(m.content, 800)}`);
41712
- turns += 1;
41713
- } else if (m.role === "assistant" && m.content.trim()) {
41714
- chunk.push(`Assistant: ${truncate(m.content, 2e3)}`);
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}` };
41715
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) };
41716
42334
  }
41717
- if (chunk.length === 0) return "";
41718
- let body = ["## Prior conversation (rolling context)", ...chunk.reverse()].join(
41719
- "\n"
41720
- );
41721
- if (body.length > maxTotalChars) {
41722
- body = `\u2026
41723
- ${body.slice(body.length - maxTotalChars)}`;
41724
- }
41725
- return body;
41726
42335
  }
41727
- function isShortContinueReply(task) {
41728
- const trimmed = task.trim();
41729
- if (!trimmed) return false;
41730
- if (SHORT_CONTINUE.test(trimmed)) return true;
41731
- if (trimmed.length <= 80 && !trimmed.includes("\n") && SHORT_CONTINUE_LOOSE.test(trimmed)) {
41732
- return true;
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;
41733
42344
  }
41734
- return false;
41735
- }
41736
- function buildContinueUserMessage(task, prior, opts) {
41737
- if (!isShortContinueReply(task) || prior.length === 0) return null;
41738
- const lastAsst = [...prior].reverse().find((m) => m.role === "assistant" && (m.content ?? "").trim());
41739
- if (!lastAsst) return null;
41740
- const max = opts?.maxPriorChars ?? 8e3;
41741
- const trimmed = task.trim();
41742
- return `The user says "${trimmed}" \u2014 this is a CONTINUATION of an existing multi-turn session (the Desktop phase may have switched plan\u2194build; mode may have changed). This is NOT a new conversation and you DO have prior context below.
41743
-
41744
- ## CRITICAL \u2014 plan text \u2260 done on disk
41745
- The prior assistant output is a PLAN / SPEC / PROPOSAL (or analysis). It is NOT proof that project files already contain those changes.
41746
- The user CONFIRMED the plan and wants you to IMPLEMENT it ON DISK NOW.
41747
- - You MUST use write_file and/or edit_file (and bash when needed) to apply every planned change.
41748
- - Reading files alone is incomplete. Do not stop after read_file/list_files/grep.
41749
- - Do NOT claim "already implemented" / "tutto fatto" unless you verified the changes exist on disk in THIS turn (read after your own successful writes).
41750
- - Do NOT restart from zero or re-ask for the overall goal.
41751
-
41752
- ## Prior assistant output (plan to implement \u2014 authoritative)
41753
- ${truncate(lastAsst.content, max)}
41754
-
41755
- ## Instruction
41756
- Implement the plan on disk now with mutating tools, then briefly list the files you wrote/edited.`;
41757
42345
  }
41758
- function expectsDiskImplementation(task, phase2, prior) {
41759
- if ((phase2 ?? "build") === "plan") return false;
41760
- const trimmed = task.trim();
41761
- if (!trimmed) return false;
41762
- if (isShortContinueReply(trimmed)) return true;
41763
- if (/\b(implement|implementa|scrivi|scriviamo|applica|modifica|fix|write|edit|crea|aggiungi|aggiorna|apply|patch)\b/i.test(
41764
- trimmed
41765
- )) {
41766
- return true;
41767
- }
41768
- if (prior && prior.length > 0 && isShortContinueReply(trimmed)) {
41769
- const lastAsst = [...prior].reverse().find((m) => m.role === "assistant" && (m.content ?? "").trim());
41770
- if (lastAsst && /\b(se confermi|passo alla|scriv|implement|on disk|write_file|modifiche proposte|riepilogo delle modifiche)\b/i.test(
41771
- lastAsst.content
41772
- )) {
41773
- return true;
41774
- }
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" };
41775
42351
  }
41776
- return false;
41777
- }
41778
- function buildCouncilTaskWithHistory(task, prior) {
41779
- const messages = prior ?? [];
41780
- const trimmed = task.trim();
41781
- let userPart = maybeAnchorShortAnswer(task) ?? task;
41782
- const continued = buildContinueUserMessage(trimmed, messages, {
41783
- maxPriorChars: 4500
41784
- });
41785
- if (continued) {
41786
- userPart = continued;
42352
+ const existing = await mirror.derivedPriorTurns();
42353
+ if (existing && existing.length > 0) {
42354
+ return { history: derivedModelSeed(existing), importedCount: 0, source: "spine" };
41787
42355
  }
41788
- const block = formatHistoryMessages(messages, 6, 12e3);
41789
- if (!block) return userPart;
41790
- if (userPart.includes("Prior assistant output")) {
41791
- return userPart;
42356
+ if (legacySeed.length === 0) {
42357
+ return { history: [], importedCount: 0, source: "spine" };
41792
42358
  }
41793
- return `${block}
41794
-
41795
- ## Current user request
41796
- ${userPart}`;
41797
- }
41798
- function buildAgentUserWithHistory(task, prior) {
41799
- const messages = prior ?? [];
41800
- const anchored = maybeAnchorShortAnswer(task);
41801
- if (anchored) return anchored;
41802
- return buildContinueUserMessage(task, messages, { maxPriorChars: 8e3 }) ?? task;
41803
- }
41804
- function truncate(s, max) {
41805
- const t = s.replace(/\s+/g, " ").trim();
41806
- if (t.length <= max) return t;
41807
- return `${t.slice(0, max - 1)}\u2026`;
41808
- }
41809
- function _resetConversationContextForTests() {
41810
- history = [];
41811
- lastClarification = null;
41812
- clearSessionTodos();
41813
- clearSessionPermissionGrants();
41814
- }
41815
- var history, lastClarification, SHORT_CONTINUE, SHORT_CONTINUE_LOOSE;
41816
- var init_conversationContext = __esm({
41817
- "src/cli/hooks/conversationContext.ts"() {
41818
- "use strict";
41819
- init_toolPermissions();
41820
- init_sessionTodos();
41821
- init_historyCompaction();
41822
- init_requestSnapshotStore();
41823
- init_observationStore();
41824
- history = [];
41825
- lastClarification = null;
41826
- SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed|conferma|confermo|applica|fai|scrivi|esegui|implementa|vai pure|fai pure|ok procedi|sì procedi|si procedi)$/i;
41827
- SHORT_CONTINUE_LOOSE = /\b(procedi|continua|continue|conferma|confermo|applica|implementa|scriv[ia]|esegui|vai pure|fai pure|go ahead|proceed)\b/i;
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
+ }
41828
42365
  }
41829
- });
41830
-
41831
- // src/cli/phaseState.ts
41832
- var phaseState_exports = {};
41833
- __export(phaseState_exports, {
41834
- _resetPhaseForTests: () => _resetPhaseForTests,
41835
- getPhase: () => getPhase,
41836
- setPhase: () => setPhase
41837
- });
41838
- function getPhase() {
41839
- return phase;
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
+ };
41840
42373
  }
41841
- function setPhase(next) {
41842
- phase = next;
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);
41843
42384
  }
41844
- function _resetPhaseForTests() {
41845
- phase = "build";
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);
41846
42398
  }
41847
- var phase;
41848
- var init_phaseState = __esm({
41849
- "src/cli/phaseState.ts"() {
42399
+ var init_headlessSpine = __esm({
42400
+ "src/cli/headlessSpine.ts"() {
41850
42401
  "use strict";
41851
- phase = "build";
42402
+ init_dist();
42403
+ init_session();
42404
+ init_mission2();
42405
+ init_runtime2();
42406
+ init_sessionSpine();
42407
+ init_headless();
41852
42408
  }
41853
42409
  });
41854
42410
 
@@ -56992,9 +57548,6 @@ function strictGateEventPayload(evaluation) {
56992
57548
  };
56993
57549
  }
56994
57550
 
56995
- // src/cli/hooks/useChatTurn.ts
56996
- init_headlessSpine();
56997
-
56998
57551
  // src/cli/hooks/permissionPicker.ts
56999
57552
  init_toolPermissions();
57000
57553
 
@@ -57143,6 +57696,15 @@ init_envNumber();
57143
57696
  init_historyCompaction();
57144
57697
  init_observationStore();
57145
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
+ }
57146
57708
  function estimateTokens(text) {
57147
57709
  if (!text) return 0;
57148
57710
  return Math.max(1, Math.ceil(text.length / 4));
@@ -57186,21 +57748,23 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
57186
57748
  const warnings = [];
57187
57749
  let { historyTurns, maxToolLoopIterations } = phaseKnobs(phase2);
57188
57750
  const envelope = opts?.requestSnapshot ?? null;
57189
- const replayBase = envelope ? {
57190
- provider: envelope.snapshot.provider,
57191
- model: envelope.snapshot.model,
57192
- systemMessages: envelope.snapshot.systemMessages,
57193
- 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
57194
57757
  } : opts?.providerStream ? { provider: "local", model: opts?.model ?? "unknown", systemMessages: [], tools: [] } : null;
57195
- const headerTokens = envelope ? estimateSystemTokensLite(envelope.snapshot.systemMessages) + estimateToolSchemaTokensLite(envelope.snapshot.tools) : 0;
57758
+ const headerTokens = surface ? estimateSystemTokensLite(surface.systemMessages) + estimateToolSchemaTokensLite(surface.tools) : 0;
57196
57759
  const convTokensOf = (h) => estimateConversationTokensLite(h);
57197
57760
  let hist = history2;
57198
- 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;
57199
57762
  let occupancy = Math.min(1, estimated / contextLimit);
57200
57763
  let compactSummary = "";
57201
57764
  let messagesRemoved = 0;
57202
57765
  let cacheReuseExpected;
57203
57766
  let prunedTotal = 0;
57767
+ const compactRange = { sourceSeqs: [] };
57204
57768
  if (occupancy >= compact.warnAt && occupancy < compact.compactAt) {
57205
57769
  warnings.push(
57206
57770
  `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated}/${contextLimit} tok full-request est.) \u2014 consider /compact or shorter replies.`
@@ -57211,7 +57775,7 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
57211
57775
  if (pruned.stats.pruned > 0) {
57212
57776
  hist = pruned.messages;
57213
57777
  prunedTotal += pruned.stats.pruned;
57214
- estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
57778
+ estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
57215
57779
  occupancy = Math.min(1, estimated / contextLimit);
57216
57780
  warnings.push(
57217
57781
  `[budget] pruned ${pruned.stats.pruned} oversized tool result(s) \u2192 ${Math.round(occupancy * 100)}% (${estimated} tok).`
@@ -57223,11 +57787,12 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
57223
57787
  if (r.compacted) {
57224
57788
  messagesRemoved += r.messagesRemoved;
57225
57789
  if (r.summary) compactSummary = r.summary;
57790
+ mergeCompactRange(compactRange, r);
57226
57791
  if (r.cacheReuseExpected !== void 0) {
57227
57792
  cacheReuseExpected = r.cacheReuseExpected;
57228
57793
  }
57229
57794
  }
57230
- estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
57795
+ estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
57231
57796
  occupancy = Math.min(1, estimated / contextLimit);
57232
57797
  warnings.push(
57233
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)" : "") + ")."
@@ -57283,12 +57848,18 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
57283
57848
  warnings,
57284
57849
  maxToolLoopIterations,
57285
57850
  historyTurns,
57286
- estimatedHistoryTokens: envelope ? convTokensOf(hist) : estimated,
57851
+ estimatedHistoryTokens: surface ? convTokensOf(hist) : estimated,
57287
57852
  contextLimit,
57288
57853
  occupancy,
57289
57854
  compactSummary: compactSummary || void 0,
57290
57855
  messagesRemoved: messagesRemoved || void 0,
57291
- ...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 } : {},
57292
57863
  ...cacheReuseExpected !== void 0 ? { cacheReuseExpected } : {},
57293
57864
  ...cacheMetricsLine ? { cacheMetricsLine } : {}
57294
57865
  };
@@ -57323,6 +57894,231 @@ function estimateConversationTokensLite(messages) {
57323
57894
  return n;
57324
57895
  }
57325
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
+
57326
58122
  // src/cli/hooks/useChatTurn.ts
57327
58123
  init_requestSnapshotStore();
57328
58124
  function useChatTurn(params) {
@@ -57358,19 +58154,7 @@ function useChatTurn(params) {
57358
58154
  try {
57359
58155
  const anchored = maybeAnchorShortAnswer(userText);
57360
58156
  const effectiveUserText = anchored ?? userText;
57361
- let historyForModel;
57362
- {
57363
- const mirror = writerRef.current?.spine ?? null;
57364
- let spineSeed = null;
57365
- if (mirror && mirror.status === "active") {
57366
- const derived = await mirror.derivedPriorTurns();
57367
- if (derived && derived.length > 0) {
57368
- spineSeed = derivedModelSeed(derived);
57369
- }
57370
- }
57371
- historyForModel = spineSeed ?? getHistory();
57372
- }
57373
- writerRef.current?.spine?.userMessage(effectiveUserText);
58157
+ let historyForModel = getHistory();
57374
58158
  const localCli = (process.env.ZELARI_LOCAL_CLI ?? "").trim();
57375
58159
  let localCliProvider = null;
57376
58160
  if (localCli) {
@@ -57499,33 +58283,42 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
57499
58283
  });
57500
58284
  }
57501
58285
  const cwd = process.cwd();
57502
- 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,
57503
58291
  model: getActiveModel(),
58292
+ provider: envConfig?.providerId ?? (localCli || "local"),
57504
58293
  sessionId: sessionId2,
57505
- // v1.36.0: envelope for full-request metering + cache-aware
57506
- // compaction replay (last warm prefix + provider usage anchor).
57507
- requestSnapshot: getRequestSnapshotWithUsage(sessionId2),
57508
- 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
+ }
57509
58314
  });
57510
- setHistory(budget.history);
57511
- for (const w of budget.warnings) {
57512
- appendSystem(setMessages, w, Date.now());
57513
- }
57514
- if ((budget.messagesRemoved ?? 0) > 0) {
57515
- const envelope = getRequestSnapshotWithUsage(sessionId2);
57516
- const compactionEvent = createBrainEvent("session_compacted", sessionId2, {
57517
- summary: budget.compactSummary ?? "",
57518
- messagesRemoved: budget.messagesRemoved ?? 0,
57519
- ...envelope ? {
57520
- sourceRequestFingerprint: envelope.snapshot.requestFingerprint,
57521
- headerFingerprint: envelope.snapshot.headerFingerprint
57522
- } : {},
57523
- ...budget.contextPressureTokens !== void 0 ? { sourceEstimatedTokens: budget.contextPressureTokens } : {},
57524
- ...budget.cacheReuseExpected !== void 0 ? { cacheReuseExpected: budget.cacheReuseExpected } : {}
57525
- });
57526
- 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());
57527
58320
  }
57528
- historyForModel = budget.history;
58321
+ writerRef.current?.spine?.userMessage(effectiveUserText);
57529
58322
  historySeedLen = historyForModel.length;
57530
58323
  let composedWorkspace = "";
57531
58324
  let composedInstructions = "";
@@ -58164,33 +58957,33 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
58164
58957
  return { completionOk: false, ran: false };
58165
58958
  }
58166
58959
  setBusy(true);
58167
- let councilHistory = getHistory();
58168
- {
58169
- const mirror = writerRef.current?.spine ?? null;
58170
- if (mirror && mirror.status === "active") {
58171
- const derived = await mirror.derivedPriorTurns();
58172
- if (derived && derived.length > 0) {
58173
- councilHistory = derivedModelSeed(derived);
58174
- }
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
+ );
58175
58979
  }
58176
- }
58177
- const councilBudget = await applyBudgetPolicyAsync(councilHistory, getPhase(), {
58178
- model: envConfig.model
58179
58980
  });
58180
- setHistory(councilBudget.history);
58181
- for (const w of councilBudget.warnings) {
58182
- appendSystem(setMessages, w, Date.now());
58183
- }
58184
- if ((councilBudget.messagesRemoved ?? 0) > 0) {
58185
- void writerRef.current?.append(
58186
- createBrainEvent("session_compacted", sessionId2, {
58187
- summary: councilBudget.compactSummary ?? "",
58188
- messagesRemoved: councilBudget.messagesRemoved ?? 0
58189
- })
58190
- );
58981
+ const councilBudget = councilContext.budget;
58982
+ setHistory(councilContext.history);
58983
+ for (const warning of councilBudget.warnings) {
58984
+ appendSystem(setMessages, warning, Date.now());
58191
58985
  }
58192
- const anchored = maybeAnchorShortAnswer(text);
58193
- const effectiveText = anchored ?? text;
58986
+ writerRef.current?.spine?.userMessage(effectiveText);
58194
58987
  appendSystem(
58195
58988
  setMessages,
58196
58989
  `[phase] ${describePhase(getPhase())}`,
@@ -63204,6 +63997,7 @@ async function runAdvisoryVerifierReview(evaluation, deps = {}) {
63204
63997
  }
63205
63998
 
63206
63999
  // src/cli/runHeadless.ts
64000
+ init_metrics2();
63207
64001
  init_headlessSpine();
63208
64002
  async function runHeadless(opts) {
63209
64003
  resetTaskSpawnCount();
@@ -63539,7 +64333,6 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
63539
64333
  });
63540
64334
  const seededHistory = await seedHeadlessModelHistory(spine, opts.history);
63541
64335
  emitEvent(sessionStartedEvent(spine));
63542
- if (opts.task) spine.userMessage(opts.task);
63543
64336
  resetKrakenCandidates();
63544
64337
  resetKrakenTurnMetrics();
63545
64338
  const { registry: toolRegistry } = createBuiltinToolRegistry({
@@ -63689,14 +64482,38 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
63689
64482
  }
63690
64483
  ];
63691
64484
  }
63692
- 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
+ }
63693
64509
  const effectiveTask = buildAgentUserWithHistory(opts.task, historySeed);
64510
+ if (opts.task) spine.userMessage(effectiveTask);
63694
64511
  const maxToolLoop = (() => {
63695
64512
  const n = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
63696
64513
  default: 30,
63697
64514
  min: 1
63698
64515
  });
63699
- return n;
64516
+ return Math.min(n, modelContext.budget.maxToolLoopIterations);
63700
64517
  })();
63701
64518
  async function runSinglePass(messages, passSessionId) {
63702
64519
  const harness = new AgentHarness({
@@ -63997,7 +64814,6 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
63997
64814
  });
63998
64815
  const seededHistory = await seedHeadlessModelHistory(spine, opts.history);
63999
64816
  emitEvent(sessionStartedEvent(spine));
64000
- if (opts.task) spine.userMessage(opts.task);
64001
64817
  const { shouldAllowCouncilBuild: shouldAllowCouncilBuild2 } = await Promise.resolve().then(() => (init_buildPolicy(), buildPolicy_exports));
64002
64818
  let councilRunMode = planModeFromOpts(opts) ? "design-phase" : "implementation";
64003
64819
  let softGated = false;
@@ -64014,8 +64830,36 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
64014
64830
  );
64015
64831
  const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
64016
64832
  const feedbackStore = new FeedbackStore2();
64017
- 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
+ }
64018
64861
  const effectiveTask = buildCouncilTaskWithHistory(opts.task, historySeed);
64862
+ if (opts.task) spine.userMessage(effectiveTask);
64019
64863
  let exitCode = 0;
64020
64864
  const scrub = createStreamScrubber2();
64021
64865
  let lastAssistantText = "";
@@ -64125,7 +64969,6 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
64125
64969
  });
64126
64970
  const seededHistory = await seedHeadlessModelHistory(spine, opts.history);
64127
64971
  emitEvent(sessionStartedEvent(spine));
64128
- if (opts.task) spine.userMessage(opts.task);
64129
64972
  spine.missionPhase("design", "mission-start");
64130
64973
  const { buildMissionBrief: buildMissionBrief2 } = await Promise.resolve().then(() => (init_council(), council_exports));
64131
64974
  const { hasWorkspacePlan: hasWorkspacePlan2 } = await Promise.resolve().then(() => (init_planDetect(), planDetect_exports));
@@ -64157,8 +65000,33 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
64157
65000
  process.stderr.write(message + "\n");
64158
65001
  }
64159
65002
  };
64160
- 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);
64161
65028
  const missionTask = buildCouncilTaskWithHistory(opts.task, historySeed);
65029
+ if (opts.task) spine.userMessage(missionTask);
64162
65030
  emit(`[zelari] mission brief
64163
65031
  ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMvp?.title }, null, 0)}`);
64164
65032
  if (buildViaAgent) {