zelari-code 1.41.0 → 1.42.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.
@@ -1468,27 +1468,27 @@ function readStore() {
1468
1468
  }
1469
1469
  return { providers: {} };
1470
1470
  }
1471
- function writeStore(store3) {
1471
+ function writeStore(store4) {
1472
1472
  const file2 = getKeyStorePath();
1473
1473
  mkdirSync2(path3.dirname(file2), { recursive: true });
1474
- writeFileSync2(file2, JSON.stringify(store3, null, 2), { encoding: "utf-8", mode: 384 });
1474
+ writeFileSync2(file2, JSON.stringify(store4, null, 2), { encoding: "utf-8", mode: 384 });
1475
1475
  }
1476
1476
  function setApiKey(providerId, key) {
1477
- const store3 = readStore();
1478
- store3.providers[providerId] = { apiKey: key };
1479
- writeStore(store3);
1477
+ const store4 = readStore();
1478
+ store4.providers[providerId] = { apiKey: key };
1479
+ writeStore(store4);
1480
1480
  }
1481
1481
  function clearApiKey(providerId) {
1482
- const store3 = readStore();
1483
- delete store3.providers[providerId];
1484
- writeStore(store3);
1482
+ const store4 = readStore();
1483
+ delete store4.providers[providerId];
1484
+ writeStore(store4);
1485
1485
  }
1486
1486
  function getStoredApiKey(providerId) {
1487
- const store3 = readStore();
1488
- return store3.providers[providerId]?.apiKey ?? null;
1487
+ const store4 = readStore();
1488
+ return store4.providers[providerId]?.apiKey ?? null;
1489
1489
  }
1490
1490
  function setOAuthToken(providerId, token) {
1491
- const store3 = readStore();
1491
+ const store4 = readStore();
1492
1492
  const entry = { apiKey: token.apiKey };
1493
1493
  if (typeof token.expiresAt === "number" && Number.isFinite(token.expiresAt)) {
1494
1494
  entry.expiresAt = token.expiresAt;
@@ -1502,12 +1502,12 @@ function setOAuthToken(providerId, token) {
1502
1502
  if (typeof token.idToken === "string" && token.idToken.length > 0) {
1503
1503
  entry.idToken = token.idToken;
1504
1504
  }
1505
- store3.providers[providerId] = entry;
1506
- writeStore(store3);
1505
+ store4.providers[providerId] = entry;
1506
+ writeStore(store4);
1507
1507
  }
1508
1508
  function getOAuthToken(providerId) {
1509
- const store3 = readStore();
1510
- return store3.providers[providerId] ?? null;
1509
+ const store4 = readStore();
1510
+ return store4.providers[providerId] ?? null;
1511
1511
  }
1512
1512
  function resolveApiKey(providerId) {
1513
1513
  const spec = getProviderSpec(providerId);
@@ -1566,8 +1566,8 @@ async function forceRefreshOAuth(providerId, options = {}) {
1566
1566
  function readStoreDirect() {
1567
1567
  return readStore();
1568
1568
  }
1569
- function writeStoreDirect(store3) {
1570
- writeStore(store3);
1569
+ function writeStoreDirect(store4) {
1570
+ writeStore(store4);
1571
1571
  }
1572
1572
  function maskKey(key) {
1573
1573
  if (key.length <= 12) return "****";
@@ -21214,6 +21214,79 @@ var init_types = __esm({
21214
21214
  }
21215
21215
  });
21216
21216
 
21217
+ // packages/core/dist/core/requestSnapshot.js
21218
+ import { createHash as createHash2 } from "node:crypto";
21219
+ function stableStringify(value) {
21220
+ if (value === null || typeof value !== "object")
21221
+ return JSON.stringify(value) ?? "null";
21222
+ if (Array.isArray(value)) {
21223
+ const items = value.map((v) => stableStringify(v));
21224
+ return `[${items.join(",")}]`;
21225
+ }
21226
+ const obj = value;
21227
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
21228
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
21229
+ return `{${parts.join(",")}}`;
21230
+ }
21231
+ function sha256Hex(input) {
21232
+ return createHash2("sha256").update(input, "utf8").digest("hex").slice(0, 32);
21233
+ }
21234
+ function cloneMessages(messages) {
21235
+ return structuredClone(messages);
21236
+ }
21237
+ function canonicalTools(tools) {
21238
+ return [...tools].sort((a, b) => a.name.localeCompare(b.name));
21239
+ }
21240
+ function createRoutedRequestSnapshot(params) {
21241
+ let split = 0;
21242
+ while (split < params.messages.length && params.messages[split].role === "system") {
21243
+ split++;
21244
+ }
21245
+ const systemMessages = cloneMessages(params.messages.slice(0, split));
21246
+ const conversation = cloneMessages(params.messages.slice(split));
21247
+ const tools = canonicalTools(params.tools).map((t) => structuredClone(t));
21248
+ const header = stableStringify({
21249
+ provider: params.provider,
21250
+ model: params.model,
21251
+ systemMessages,
21252
+ tools
21253
+ });
21254
+ const request = stableStringify({
21255
+ provider: params.provider,
21256
+ model: params.model,
21257
+ systemMessages,
21258
+ tools,
21259
+ conversation
21260
+ });
21261
+ return {
21262
+ provider: params.provider,
21263
+ model: params.model,
21264
+ systemMessages,
21265
+ conversation,
21266
+ tools,
21267
+ headerFingerprint: sha256Hex(header),
21268
+ requestFingerprint: sha256Hex(request),
21269
+ createdAt: Date.now()
21270
+ };
21271
+ }
21272
+ function compareReplayPrefix(snapshot, messages) {
21273
+ const base = snapshot.conversation;
21274
+ const n = Math.min(base.length, messages.length);
21275
+ let matching = 0;
21276
+ for (let i = 0; i < n; i++) {
21277
+ if (stableStringify(base[i]) !== stableStringify(messages[i])) {
21278
+ return { exact: false, matchingMessages: matching, mismatchIndex: i };
21279
+ }
21280
+ matching++;
21281
+ }
21282
+ return { exact: true, matchingMessages: matching };
21283
+ }
21284
+ var init_requestSnapshot = __esm({
21285
+ "packages/core/dist/core/requestSnapshot.js"() {
21286
+ "use strict";
21287
+ }
21288
+ });
21289
+
21217
21290
  // packages/core/dist/core/textLoopDetect.js
21218
21291
  function isStatusTheaterUnit(unit) {
21219
21292
  const u = normalizeLoopUnit(unit).toLowerCase();
@@ -21412,10 +21485,10 @@ var init_textLoopDetect = __esm({
21412
21485
 
21413
21486
  // packages/core/dist/core/AgentHarness.js
21414
21487
  function hashToolCall(toolName, args) {
21415
- const canonical = stableStringify(args);
21488
+ const canonical = stableStringify2(args);
21416
21489
  return `${toolName}::${canonical}`;
21417
21490
  }
21418
- function stableStringify(value) {
21491
+ function stableStringify2(value) {
21419
21492
  return JSON.stringify(value, (_k, v) => {
21420
21493
  if (v && typeof v === "object" && !Array.isArray(v)) {
21421
21494
  const sorted = {};
@@ -21648,6 +21721,7 @@ var init_AgentHarness = __esm({
21648
21721
  "packages/core/dist/core/AgentHarness.js"() {
21649
21722
  "use strict";
21650
21723
  init_events();
21724
+ init_requestSnapshot();
21651
21725
  init_textLoopDetect();
21652
21726
  init_textLoopDetect();
21653
21727
  AgentHarness = class {
@@ -22084,6 +22158,23 @@ ${shared2.content}`,
22084
22158
  yield agentEnd;
22085
22159
  this.activeController = null;
22086
22160
  }
22161
+ /**
22162
+ * v1.36.0: capture a deterministic snapshot of the routed request just
22163
+ * before it goes out. Never throws into the request path.
22164
+ */
22165
+ emitSnapshot(tools, generation) {
22166
+ if (!this.config.onRequestSnapshot)
22167
+ return;
22168
+ try {
22169
+ this.config.onRequestSnapshot(createRoutedRequestSnapshot({
22170
+ messages: this.config.messages,
22171
+ model: this.config.model,
22172
+ provider: this.config.provider,
22173
+ tools
22174
+ }), generation);
22175
+ } catch {
22176
+ }
22177
+ }
22087
22178
  /**
22088
22179
  * Run a single provider turn for the current message buffer.
22089
22180
  * Streams from the provider, dispatches deltas to events, executes
@@ -22101,6 +22192,7 @@ ${shared2.content}`,
22101
22192
  */
22102
22193
  async *runSingleTurn(messageId, finishRef, usageRef) {
22103
22194
  try {
22195
+ this.emitSnapshot(this.config.tools);
22104
22196
  const stream = this.config.providerStream({
22105
22197
  messages: this.config.messages,
22106
22198
  model: this.config.model,
@@ -22391,6 +22483,7 @@ ${cached2}`
22391
22483
  const finishRef = { value: "stop" };
22392
22484
  const usageRef = { value: null };
22393
22485
  try {
22486
+ this.emitSnapshot([]);
22394
22487
  const stream = this.config.providerStream({
22395
22488
  messages: this.config.messages,
22396
22489
  model: this.config.model,
@@ -22971,7 +23064,10 @@ __export(harness_exports, {
22971
23064
  SessionJsonlWriter: () => SessionJsonlWriter,
22972
23065
  TEXT_LOOP_RECOVERY_SYSTEM: () => TEXT_LOOP_RECOVERY_SYSTEM,
22973
23066
  TEXT_LOOP_RECOVERY_USER_PROMPT: () => TEXT_LOOP_RECOVERY_USER_PROMPT,
23067
+ canonicalTools: () => canonicalTools,
22974
23068
  collapseLoopedAssistantText: () => collapseLoopedAssistantText,
23069
+ compareReplayPrefix: () => compareReplayPrefix,
23070
+ createRoutedRequestSnapshot: () => createRoutedRequestSnapshot,
22975
23071
  detectAssistantTextLoop: () => detectAssistantTextLoop,
22976
23072
  detectAssistantTextLoopWindow: () => detectAssistantTextLoopWindow,
22977
23073
  hashToolCall: () => hashToolCall,
@@ -22983,6 +23079,8 @@ __export(harness_exports, {
22983
23079
  parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
22984
23080
  parseTextToolCalls: () => parseTextToolCalls,
22985
23081
  readSession: () => readSession,
23082
+ sha256Hex: () => sha256Hex,
23083
+ stableStringify: () => stableStringify,
22986
23084
  toolMatches: () => toolMatches,
22987
23085
  wrapLegacyStream: () => wrapLegacyStream
22988
23086
  });
@@ -22991,6 +23089,7 @@ var init_harness = __esm({
22991
23089
  "use strict";
22992
23090
  init_AgentHarness();
22993
23091
  init_providerStream();
23092
+ init_requestSnapshot();
22994
23093
  init_sessionJsonl();
22995
23094
  init_hooks();
22996
23095
  }
@@ -27902,6 +28001,7 @@ __export(dist_exports, {
27902
28001
  buildSystemPrompt: () => buildSystemPrompt,
27903
28002
  buildSystemPromptSplit: () => buildSystemPromptSplit,
27904
28003
  canRunParallel: () => canRunParallel,
28004
+ canonicalTools: () => canonicalTools,
27905
28005
  captureFailure: () => captureFailure,
27906
28006
  checkImplementationCompletion: () => checkImplementationCompletion,
27907
28007
  checkImplementationDelivery: () => checkImplementationDelivery,
@@ -27913,6 +28013,7 @@ __export(dist_exports, {
27913
28013
  clearCustomTools: () => clearCustomTools,
27914
28014
  cliToolToEnhanced: () => cliToolToEnhanced,
27915
28015
  collapseLoopedAssistantText: () => collapseLoopedAssistantText,
28016
+ compareReplayPrefix: () => compareReplayPrefix,
27916
28017
  computeAgentSkills: () => computeAgentSkills,
27917
28018
  computeAgentTools: () => computeAgentTools,
27918
28019
  councilModeBanner: () => councilModeBanner,
@@ -27922,6 +28023,7 @@ __export(dist_exports, {
27922
28023
  createBrainEvent: () => createBrainEvent,
27923
28024
  createDefaultSystemPromptConfig: () => createDefaultSystemPromptConfig,
27924
28025
  createGraph: () => createGraph,
28026
+ createRoutedRequestSnapshot: () => createRoutedRequestSnapshot,
27925
28027
  defaultPersonaParse: () => defaultPersonaParse,
27926
28028
  detectAssistantTextLoop: () => detectAssistantTextLoop,
27927
28029
  detectAssistantTextLoopWindow: () => detectAssistantTextLoopWindow,
@@ -28038,9 +28140,11 @@ __export(dist_exports, {
28038
28140
  scrubProprietaryLeak: () => scrubProprietaryLeak,
28039
28141
  selectParallelWave: () => selectParallelWave,
28040
28142
  setWorkspaceStubs: () => setWorkspaceStubs,
28143
+ sha256Hex: () => sha256Hex,
28041
28144
  shouldRetryMember: () => shouldRetryMember,
28042
28145
  slugify: () => slugify2,
28043
28146
  specificityFromAssumptions: () => specificityFromAssumptions,
28147
+ stableStringify: () => stableStringify,
28044
28148
  stripClarificationProtocol: () => stripClarificationProtocol,
28045
28149
  swapMembers: () => swapMembers,
28046
28150
  systemMessagesFromSplit: () => systemMessagesFromSplit,
@@ -28403,6 +28507,7 @@ function openaiCompatibleProvider(config2) {
28403
28507
  if (cacheable) messageMappingCache.set(m, mapped);
28404
28508
  return mapped;
28405
28509
  });
28510
+ const generation = params.generation;
28406
28511
  const body = {
28407
28512
  // Use `params.model` (per-call override from AgentHarness, e.g. for
28408
28513
  // `agentModels` config) rather than the closed-over `config.model`
@@ -28410,7 +28515,7 @@ function openaiCompatibleProvider(config2) {
28410
28515
  model: params.model,
28411
28516
  messages,
28412
28517
  stream: true,
28413
- temperature: 0.7,
28518
+ temperature: generation?.temperature ?? 0.7,
28414
28519
  // Task G.4.2 — request the provider to send real token usage in
28415
28520
  // the final chunk (gated by `stream_options.include_usage` on the
28416
28521
  // OpenAI-compatible API). Providers that don't honor this (some
@@ -28418,6 +28523,9 @@ function openaiCompatibleProvider(config2) {
28418
28523
  // the harness will fall back to the ~4-char/token approximation.
28419
28524
  stream_options: { include_usage: true }
28420
28525
  };
28526
+ if (typeof generation?.maxTokens === "number" && generation.maxTokens > 0) {
28527
+ body.max_tokens = generation.maxTokens;
28528
+ }
28421
28529
  const thinkingSpec = config2.thinking ?? "auto";
28422
28530
  if (config2.providerId === "deepseek" && thinkingSpec === "auto") {
28423
28531
  const thinking = resolveDeepSeekThinking();
@@ -29312,7 +29420,7 @@ var init_resolveStream = __esm({
29312
29420
  });
29313
29421
 
29314
29422
  // packages/core/dist/core/tools/toolOutputSpill.js
29315
- import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
29423
+ import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
29316
29424
  import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
29317
29425
  import { homedir as homedir3, tmpdir } from "node:os";
29318
29426
  import { join as join11 } from "node:path";
@@ -29342,7 +29450,7 @@ function spillToolOutput(fullText, meta3) {
29342
29450
  if (!existsSync12(dir)) {
29343
29451
  mkdirSync7(dir, { recursive: true });
29344
29452
  }
29345
- const hash3 = createHash2("sha256").update(fullText).digest("hex").slice(0, 12);
29453
+ const hash3 = createHash3("sha256").update(fullText).digest("hex").slice(0, 12);
29346
29454
  const stamp = Date.now().toString(36);
29347
29455
  const rnd = randomBytes2(3).toString("hex");
29348
29456
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
@@ -31749,9 +31857,9 @@ var init_store = __esm({
31749
31857
  import { promises as fs12, existsSync as existsSync17, readFileSync as readFileSync17 } from "node:fs";
31750
31858
  import { homedir as homedir5 } from "node:os";
31751
31859
  import path23 from "node:path";
31752
- import { createHash as createHash3 } from "node:crypto";
31860
+ import { createHash as createHash4 } from "node:crypto";
31753
31861
  function getIndexPath(root) {
31754
- const hash3 = createHash3("sha1").update(path23.resolve(root)).digest("hex").slice(0, 16);
31862
+ const hash3 = createHash4("sha1").update(path23.resolve(root)).digest("hex").slice(0, 16);
31755
31863
  return process.env.ZELARI_SEMANTIC_FILE ?? path23.join(homedir5(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
31756
31864
  }
31757
31865
  async function collectSourceFiles(root, maxFiles = 1500) {
@@ -33232,11 +33340,11 @@ function readStore3() {
33232
33340
  return DEFAULT_STORE;
33233
33341
  }
33234
33342
  }
33235
- function writeStore3(store3) {
33343
+ function writeStore3(store4) {
33236
33344
  const p3 = trustStorePath();
33237
33345
  try {
33238
33346
  mkdirSync11(path28.dirname(p3), { recursive: true });
33239
- writeFileSync13(p3, JSON.stringify(store3, null, 2), "utf8");
33347
+ writeFileSync13(p3, JSON.stringify(store4, null, 2), "utf8");
33240
33348
  } catch (err) {
33241
33349
  throw new Error(
33242
33350
  `failed to persist trust store ${p3}: ${err instanceof Error ? err.message : String(err)}`
@@ -33260,21 +33368,21 @@ function isFolderTrusted(folderPath) {
33260
33368
  return readStore3().folders.some((f) => normalize(f.path) === target);
33261
33369
  }
33262
33370
  function trustFolder(folderPath) {
33263
- const store3 = readStore3();
33371
+ const store4 = readStore3();
33264
33372
  const normalized = path28.resolve(folderPath);
33265
- if (!store3.folders.some((f) => normalize(f.path) === normalize(normalized))) {
33266
- store3.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
33267
- writeStore3(store3);
33373
+ if (!store4.folders.some((f) => normalize(f.path) === normalize(normalized))) {
33374
+ store4.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
33375
+ writeStore3(store4);
33268
33376
  }
33269
33377
  return { ok: true, path: normalized };
33270
33378
  }
33271
33379
  function untrustFolder(folderPath) {
33272
- const store3 = readStore3();
33380
+ const store4 = readStore3();
33273
33381
  const target = normalize(folderPath);
33274
- const before = store3.folders.length;
33275
- store3.folders = store3.folders.filter((f) => normalize(f.path) !== target);
33276
- if (store3.folders.length === before) return { ok: true, removed: false };
33277
- writeStore3(store3);
33382
+ const before = store4.folders.length;
33383
+ store4.folders = store4.folders.filter((f) => normalize(f.path) !== target);
33384
+ if (store4.folders.length === before) return { ok: true, removed: false };
33385
+ writeStore3(store4);
33278
33386
  return { ok: true, removed: true };
33279
33387
  }
33280
33388
  function listTrustedFolders() {
@@ -33375,7 +33483,7 @@ var init_lifecycleHooks = __esm({
33375
33483
  });
33376
33484
 
33377
33485
  // src/cli/toolResultCache.ts
33378
- import { createHash as createHash4 } from "node:crypto";
33486
+ import { createHash as createHash5 } from "node:crypto";
33379
33487
  import { promises as fs14 } from "node:fs";
33380
33488
  import path29 from "node:path";
33381
33489
  function isToolCacheEnabled() {
@@ -33388,7 +33496,7 @@ function resolveToolCacheTtlMs() {
33388
33496
  return Number.isFinite(n) && n >= 0 ? n : TOOL_CACHE_DEFAULT_TTL_MS;
33389
33497
  }
33390
33498
  function hashKey(parts) {
33391
- return createHash4("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
33499
+ return createHash5("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
33392
33500
  }
33393
33501
  function resultBytes(result) {
33394
33502
  try {
@@ -34069,7 +34177,7 @@ var init_toolRegistry = __esm({
34069
34177
  });
34070
34178
 
34071
34179
  // src/cli/state/fileStateStore.ts
34072
- import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
34180
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "node:crypto";
34073
34181
  import { promises as fs15 } from "node:fs";
34074
34182
  import * as path30 from "node:path";
34075
34183
  function shortId() {
@@ -34112,16 +34220,16 @@ function isStateEnabled(env = process.env) {
34112
34220
  }
34113
34221
  async function getStateStore(projectRoot, env = process.env) {
34114
34222
  if (!isStateEnabled(env)) return new NoopDurableStateStore();
34115
- const store3 = new FileDurableStateStore();
34223
+ const store4 = new FileDurableStateStore();
34116
34224
  try {
34117
- await store3.init(projectRoot);
34118
- return store3;
34225
+ await store4.init(projectRoot);
34226
+ return store4;
34119
34227
  } catch {
34120
34228
  return new NoopDurableStateStore();
34121
34229
  }
34122
34230
  }
34123
34231
  function hashStablePrompt(stable) {
34124
- return createHash5("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
34232
+ return createHash6("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
34125
34233
  }
34126
34234
  var DEFAULT_MATERIALIZE_CHARS, FileDurableStateStore, NoopDurableStateStore;
34127
34235
  var init_fileStateStore = __esm({
@@ -34452,29 +34560,6 @@ function extractiveHistorySummary(dropped, opts) {
34452
34560
  }
34453
34561
  return out;
34454
34562
  }
34455
- function formatDroppedForLlm(dropped) {
34456
- const lines = [];
34457
- for (const m of dropped) {
34458
- if (m.role === "user") {
34459
- lines.push(`USER: ${oneLine(m.content, 400)}`);
34460
- } else if (m.role === "assistant") {
34461
- const tools = m.toolCalls?.map((t) => t.name).join(",") || "";
34462
- const body2 = oneLine(m.content, 300);
34463
- lines.push(
34464
- tools ? `ASSISTANT(tools=${tools}): ${body2}` : `ASSISTANT: ${body2}`
34465
- );
34466
- } else if (m.role === "tool") {
34467
- lines.push(`TOOL(${m.toolCallId ?? "?"}): ${oneLine(m.content, 160)}`);
34468
- } else if (m.role === "system") {
34469
- lines.push(`SYSTEM: ${oneLine(m.content, 200)}`);
34470
- }
34471
- }
34472
- let body = lines.join("\n");
34473
- if (body.length > MAX_LLM_INPUT_CHARS) {
34474
- body = body.slice(body.length - MAX_LLM_INPUT_CHARS);
34475
- }
34476
- return body;
34477
- }
34478
34563
  function oneLine(s, max) {
34479
34564
  const t = s.replace(/\s+/g, " ").trim();
34480
34565
  if (t.length <= max) return t;
@@ -34500,12 +34585,11 @@ function collectPathsFromText(text, out) {
34500
34585
  n += 1;
34501
34586
  }
34502
34587
  }
34503
- var MAX_SUMMARY_CHARS, MAX_LLM_INPUT_CHARS;
34588
+ var MAX_SUMMARY_CHARS;
34504
34589
  var init_historySummary = __esm({
34505
34590
  "src/cli/budget/historySummary.ts"() {
34506
34591
  "use strict";
34507
34592
  MAX_SUMMARY_CHARS = 3500;
34508
- MAX_LLM_INPUT_CHARS = 24e3;
34509
34593
  }
34510
34594
  });
34511
34595
 
@@ -34515,92 +34599,87 @@ function isLlmCompactEnabled() {
34515
34599
  if (v === "0" || v === "false" || v === "off" || v === "no") return false;
34516
34600
  return true;
34517
34601
  }
34518
- async function llmSummarizeHistory(input) {
34519
- if (!isLlmCompactEnabled()) return null;
34520
- if (!input.droppedTranscript.trim()) return null;
34521
- let config2 = null;
34522
- try {
34523
- config2 = await resolveCompactProviderConfig();
34524
- } catch {
34525
- return null;
34602
+ function compactModelOverride() {
34603
+ const v = process.env.ZELARI_COMPACT_MODEL?.trim();
34604
+ return v ? v : void 0;
34605
+ }
34606
+ async function llmSummarizeHistoryReplay(input) {
34607
+ const override = input.overrideModel ?? compactModelOverride();
34608
+ const model = override ?? input.model;
34609
+ const cacheReuseExpected = !override;
34610
+ if (!isLlmCompactEnabled()) return { summary: null, model, cacheReuseExpected };
34611
+ if (input.droppedMessages.length === 0) {
34612
+ return { summary: null, model, cacheReuseExpected };
34526
34613
  }
34527
- if (!config2) return null;
34528
- const model = process.env.ZELARI_COMPACT_MODEL?.trim() || config2.model;
34614
+ const messages = [
34615
+ ...input.systemMessages,
34616
+ ...input.droppedMessages,
34617
+ {
34618
+ role: "user",
34619
+ content: COMPACTION_INSTRUCTION
34620
+ }
34621
+ ];
34529
34622
  const controller = new AbortController();
34530
- const timeout = setTimeout(() => controller.abort(), 45e3);
34623
+ const timeout = setTimeout(() => controller.abort(), REPLAY_TIMEOUT_MS);
34531
34624
  const onOuterAbort = () => controller.abort();
34532
34625
  input.signal?.addEventListener("abort", onOuterAbort, { once: true });
34533
34626
  try {
34534
- const url2 = `${config2.baseUrl.replace(/\/$/, "")}/chat/completions`;
34535
- const res = await fetch(url2, {
34536
- method: "POST",
34627
+ let text = "";
34628
+ let emittedToolCall = false;
34629
+ for await (const delta of input.providerStream({
34630
+ provider: input.provider,
34631
+ model,
34632
+ messages,
34633
+ // Tools stay advertised: dropping them would change the prefix token
34634
+ // sequence and destroy cache reuse (explicit DSH decision). They are
34635
+ // sorted canonically (same discipline as the live routed request and
34636
+ // the snapshot fingerprints) so the replay prefix is byte-identical.
34637
+ tools: [...input.tools].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
34537
34638
  signal: controller.signal,
34538
- headers: {
34539
- "content-type": "application/json",
34540
- authorization: `Bearer ${config2.apiKey}`
34541
- },
34542
- body: JSON.stringify({
34543
- model,
34639
+ generation: {
34640
+ purpose: "compaction",
34544
34641
  temperature: 0.1,
34545
- max_tokens: 900,
34546
- stream: false,
34547
- messages: [
34548
- { role: "system", content: COMPACT_SYSTEM },
34549
- {
34550
- role: "user",
34551
- content: `Extractive sketch (may be incomplete):
34552
- ${input.extractive.slice(0, 2500)}
34553
-
34554
- Transcript of dropped turns:
34555
- ${input.droppedTranscript}`
34556
- }
34557
- ]
34558
- })
34559
- });
34560
- if (!res.ok) return null;
34561
- const json2 = await res.json();
34562
- const text = json2.choices?.[0]?.message?.content?.trim();
34563
- if (!text) return null;
34564
- return "[history-summary \xB7 llm]\n" + text + "\n\nContinue from the recent messages below; honor decisions already made above.";
34642
+ maxTokens: 900
34643
+ }
34644
+ })) {
34645
+ if (delta.kind === "text") text += delta.delta;
34646
+ if (delta.kind === "tool_call") emittedToolCall = true;
34647
+ }
34648
+ if (emittedToolCall) return { summary: null, model, cacheReuseExpected };
34649
+ if (!text.trim()) return { summary: null, model, cacheReuseExpected };
34650
+ return { summary: text.trim(), model, cacheReuseExpected };
34565
34651
  } catch {
34566
- return null;
34652
+ return { summary: null, model, cacheReuseExpected };
34567
34653
  } finally {
34568
34654
  clearTimeout(timeout);
34569
34655
  input.signal?.removeEventListener("abort", onOuterAbort);
34570
34656
  }
34571
34657
  }
34572
- async function resolveCompactProviderConfig() {
34573
- const active = getProviderConfig().activeProviderId;
34574
- const meta3 = await resolveApiKeyWithMeta(active);
34575
- const apiKey = meta3?.apiKey;
34576
- if (!apiKey) return null;
34577
- const custom2 = getCustomEndpoint(active);
34578
- let baseUrl = custom2 || (active === "openai-compatible" || active === "custom" ? process.env.OPENAI_BASE_URL ?? PROVIDER_ENDPOINTS[active] : PROVIDER_ENDPOINTS[active]);
34579
- if (!baseUrl) return null;
34580
- const model = getModelForProvider(active);
34581
- return {
34582
- apiKey,
34583
- baseUrl,
34584
- model,
34585
- providerId: active
34586
- };
34587
- }
34588
- var COMPACT_SYSTEM;
34658
+ var COMPACTION_INSTRUCTION, REPLAY_TIMEOUT_MS;
34589
34659
  var init_llmCompact = __esm({
34590
34660
  "src/cli/budget/llmCompact.ts"() {
34591
34661
  "use strict";
34592
- init_providerConfig();
34593
- init_keyStore();
34594
- init_openai_compatible();
34595
- COMPACT_SYSTEM = `You compress earlier turns of a coding-agent session into a dense continuity brief.
34596
- Output plain text (no markdown fences) with these sections:
34597
- 1) Goal \u2014 what the user wants
34598
- 2) Decisions \u2014 choices already made
34599
- 3) Done \u2014 completed work / files changed
34600
- 4) Open \u2014 remaining tasks / blockers
34601
- 5) Constraints \u2014 important rules the agent must keep
34662
+ COMPACTION_INSTRUCTION = `
34663
+ You are now acting as a compaction engine for this coding-agent session.
34664
+
34665
+ Condense the conversation ABOVE into a compact checkpoint sufficient to continue the task.
34666
+
34667
+ Preserve:
34668
+ - user's goal and evolving intent
34669
+ - decisions already made
34670
+ - exact file paths and identifiers
34671
+ - code changes already completed
34672
+ - commands/errors that still matter
34673
+ - constraints
34674
+ - unfinished work
34675
+ - the single most likely next action
34602
34676
 
34603
- Be factual and concise. Max ~400 words. Do not invent work that was not present.`;
34677
+ Do not call tools.
34678
+ Do not mention this summarization request.
34679
+ Output only the checkpoint.
34680
+ Be concise.
34681
+ `.trim();
34682
+ REPLAY_TIMEOUT_MS = 6e4;
34604
34683
  }
34605
34684
  });
34606
34685
 
@@ -34639,6 +34718,7 @@ function resolveMaxMessages(opts) {
34639
34718
  turns = Math.min(turns, 3);
34640
34719
  }
34641
34720
  if (turns <= 0) return 0;
34721
+ if (opts?.force && opts?.maxMessages) return Math.max(1, opts.maxMessages);
34642
34722
  return turns * 4;
34643
34723
  }
34644
34724
  function findValidCutIndex(messages, naiveCut) {
@@ -34704,12 +34784,18 @@ function pruneToolResultsDetailed(messages, opts) {
34704
34784
  function compactHistory(messages, opts) {
34705
34785
  return compactHistoryDetailed(messages, opts).messages;
34706
34786
  }
34787
+ function buildCheckpointMessage(summaryText) {
34788
+ return {
34789
+ role: "user",
34790
+ content: CHECKPOINT_WRAPPER_PREFIX + "\n\n<compacted-summary>\n" + summaryText + "\n</compacted-summary>"
34791
+ };
34792
+ }
34707
34793
  function compactHistoryDetailed(messages, opts) {
34708
34794
  const maxMessages = resolveMaxMessages(opts);
34709
34795
  if (maxMessages === 0) {
34710
34796
  return { messages: [], compacted: true, messagesRemoved: messages.length, summary: "" };
34711
34797
  }
34712
- if (messages.length <= maxMessages * 2) {
34798
+ if (messages.length <= maxMessages * 2 && !opts?.force) {
34713
34799
  return {
34714
34800
  messages,
34715
34801
  compacted: false,
@@ -34717,7 +34803,7 @@ function compactHistoryDetailed(messages, opts) {
34717
34803
  summary: ""
34718
34804
  };
34719
34805
  }
34720
- const naiveCut = messages.length - maxMessages;
34806
+ const naiveCut = Math.max(0, messages.length - maxMessages);
34721
34807
  const cut = findValidCutIndex(messages, naiveCut);
34722
34808
  if (cut === 0) {
34723
34809
  return {
@@ -34731,10 +34817,9 @@ function compactHistoryDetailed(messages, opts) {
34731
34817
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
34732
34818
  const kept = pruned.messages;
34733
34819
  const summaryText = extractiveHistorySummary(droppedMsgs);
34734
- const summary = {
34735
- role: "system",
34736
- content: summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`
34737
- };
34820
+ const summary = buildCheckpointMessage(
34821
+ summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`
34822
+ );
34738
34823
  return {
34739
34824
  messages: [summary, ...kept],
34740
34825
  compacted: true,
@@ -34749,29 +34834,58 @@ async function compactHistoryAsync(messages, opts) {
34749
34834
  const cut = base.messagesRemoved;
34750
34835
  const droppedMsgs = messages.slice(0, cut);
34751
34836
  const extractive = extractiveHistorySummary(droppedMsgs);
34752
- const droppedTranscript = formatDroppedForLlm(droppedMsgs);
34753
34837
  let summaryText = extractive;
34754
- try {
34755
- const llm = await llmSummarizeHistory({
34756
- extractive,
34757
- droppedTranscript,
34758
- signal: opts?.signal
34759
- });
34760
- if (llm && llm.trim().length > 40) summaryText = llm.trim();
34761
- } catch {
34838
+ let cacheReuseExpected;
34839
+ let replayExactPrefix;
34840
+ const canReplay = !!(opts?.providerStream && opts?.requestSnapshot);
34841
+ if (canReplay) {
34842
+ try {
34843
+ const replay = await llmSummarizeHistoryReplay({
34844
+ providerStream: opts.providerStream,
34845
+ provider: opts.requestSnapshot.provider,
34846
+ model: opts.requestSnapshot.model,
34847
+ systemMessages: opts.requestSnapshot.systemMessages,
34848
+ tools: opts.requestSnapshot.tools,
34849
+ droppedMessages: droppedMsgs,
34850
+ signal: opts?.signal
34851
+ });
34852
+ cacheReuseExpected = replay.cacheReuseExpected;
34853
+ if (replay.summary && replay.summary.trim().length > 40) {
34854
+ const sourceTokens = roughTokens(droppedMsgs);
34855
+ const summaryTok = Math.ceil(replay.summary.length / 4);
34856
+ if (summaryTok < sourceTokens) {
34857
+ summaryText = replay.summary.trim();
34858
+ }
34859
+ }
34860
+ } catch {
34861
+ }
34762
34862
  }
34763
34863
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
34764
34864
  const kept = pruned.messages;
34765
- const summary = { role: "system", content: summaryText };
34865
+ const summary = buildCheckpointMessage(summaryText);
34766
34866
  return {
34767
34867
  messages: [summary, ...kept],
34768
34868
  compacted: true,
34769
34869
  messagesRemoved: cut,
34770
34870
  summary: summaryText,
34771
- prunedToolResults: pruned.stats.pruned
34871
+ prunedToolResults: pruned.stats.pruned,
34872
+ cacheReuseExpected,
34873
+ replayExactPrefix
34772
34874
  };
34773
34875
  }
34774
- var COMPACT_MARKER;
34876
+ function roughTokens(msgs) {
34877
+ let n = 0;
34878
+ for (const m of msgs) {
34879
+ n += Math.ceil((m.content ?? "").length / 4);
34880
+ if (m.toolCalls) {
34881
+ for (const tc of m.toolCalls) {
34882
+ n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
34883
+ }
34884
+ }
34885
+ }
34886
+ return Math.max(1, n);
34887
+ }
34888
+ var COMPACT_MARKER, CHECKPOINT_WRAPPER_PREFIX;
34775
34889
  var init_historyCompaction = __esm({
34776
34890
  "src/cli/hooks/historyCompaction.ts"() {
34777
34891
  "use strict";
@@ -34779,6 +34893,30 @@ var init_historyCompaction = __esm({
34779
34893
  init_llmCompact();
34780
34894
  init_envNumber();
34781
34895
  COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
34896
+ CHECKPOINT_WRAPPER_PREFIX = "This is an automatically generated checkpoint of earlier conversation. Treat it as established context and continue directly.";
34897
+ }
34898
+ });
34899
+
34900
+ // src/cli/budget/requestSnapshotStore.ts
34901
+ function recordRequestSnapshot(sessionId, snapshot) {
34902
+ store3.set(sessionId, { snapshot });
34903
+ }
34904
+ function recordRequestUsage(sessionId, usage) {
34905
+ const entry = store3.get(sessionId);
34906
+ if (!entry) return;
34907
+ entry.usage = usage;
34908
+ }
34909
+ function getRequestSnapshotWithUsage(sessionId) {
34910
+ return store3.get(sessionId) ?? null;
34911
+ }
34912
+ function clearAllRequestSnapshots() {
34913
+ store3.clear();
34914
+ }
34915
+ var store3;
34916
+ var init_requestSnapshotStore = __esm({
34917
+ "src/cli/budget/requestSnapshotStore.ts"() {
34918
+ "use strict";
34919
+ store3 = /* @__PURE__ */ new Map();
34782
34920
  }
34783
34921
  });
34784
34922
 
@@ -34823,6 +34961,7 @@ function appendMessages(msgs) {
34823
34961
  function clearHistory() {
34824
34962
  history = [];
34825
34963
  lastClarification = null;
34964
+ clearAllRequestSnapshots();
34826
34965
  clearSessionTodos();
34827
34966
  clearSessionPermissionGrants();
34828
34967
  }
@@ -34981,6 +35120,7 @@ var init_conversationContext = __esm({
34981
35120
  init_toolPermissions();
34982
35121
  init_sessionTodos();
34983
35122
  init_historyCompaction();
35123
+ init_requestSnapshotStore();
34984
35124
  history = [];
34985
35125
  lastClarification = null;
34986
35126
  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;
@@ -35379,7 +35519,7 @@ import {
35379
35519
  } from "node:fs";
35380
35520
  import { join as join18, basename } from "node:path";
35381
35521
  import { homedir as homedir9 } from "node:os";
35382
- import { createHash as createHash6 } from "node:crypto";
35522
+ import { createHash as createHash7 } from "node:crypto";
35383
35523
  function resolveWorkspaceRoot(projectRoot = process.cwd()) {
35384
35524
  const candidates = [
35385
35525
  join18(projectRoot, ".zelari"),
@@ -35395,7 +35535,7 @@ function resolveWorkspaceRoot(projectRoot = process.cwd()) {
35395
35535
  return candidates[0];
35396
35536
  }
35397
35537
  function hashProject(projectPath) {
35398
- return createHash6("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
35538
+ return createHash7("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
35399
35539
  }
35400
35540
  function isWritableDir(dir) {
35401
35541
  try {
@@ -35986,8 +36126,8 @@ async function loadDurableContext(projectRoot, opts) {
35986
36126
  return cache.text;
35987
36127
  }
35988
36128
  try {
35989
- const store3 = await getStateStore(projectRoot, env);
35990
- const text = await store3.materializeContext(void 0, opts?.maxChars);
36129
+ const store4 = await getStateStore(projectRoot, env);
36130
+ const text = await store4.materializeContext(void 0, opts?.maxChars);
35991
36131
  cache = { text: text || "", at: now, projectRoot };
35992
36132
  return cache.text;
35993
36133
  } catch {
@@ -37907,7 +38047,7 @@ __export(agentsMd_exports, {
37907
38047
  updateAgentsMd: () => updateAgentsMd
37908
38048
  });
37909
38049
  import { existsSync as existsSync32, readFileSync as readFileSync28, writeFileSync as writeFileSync18 } from "node:fs";
37910
- import { createHash as createHash7 } from "node:crypto";
38050
+ import { createHash as createHash8 } from "node:crypto";
37911
38051
  import { join as join27 } from "node:path";
37912
38052
  import { readFile as readFile3 } from "node:fs/promises";
37913
38053
  async function readPackageJson2(projectRoot) {
@@ -38122,7 +38262,7 @@ async function updateAgentsMd(ctx, projectRoot) {
38122
38262
  return { changed: true, sections: changedSections };
38123
38263
  }
38124
38264
  function hash2(s) {
38125
- return createHash7("sha256").update(s).digest("hex").slice(0, 16);
38265
+ return createHash8("sha256").update(s).digest("hex").slice(0, 16);
38126
38266
  }
38127
38267
  var AUTO_SECTIONS, MARKER_OPEN, MARKER_CLOSE, GENERATORS;
38128
38268
  var init_agentsMd = __esm({
@@ -38933,7 +39073,7 @@ __export(commitHelpers_exports, {
38933
39073
  });
38934
39074
  async function tryStateCommit(args) {
38935
39075
  try {
38936
- const store3 = args.store ?? await getStateStore(args.projectRoot, args.env);
39076
+ const store4 = args.store ?? await getStateStore(args.projectRoot, args.env);
38937
39077
  let workspaceCheckpointId = args.workspaceCheckpointId;
38938
39078
  if (!workspaceCheckpointId && args.withCheckpoint && (args.env ?? process.env).ZELARI_CHECKPOINT !== "0") {
38939
39079
  const cp = await createCheckpoint(
@@ -38942,7 +39082,7 @@ async function tryStateCommit(args) {
38942
39082
  );
38943
39083
  if (cp.ok) workspaceCheckpointId = cp.value.id;
38944
39084
  }
38945
- const meta3 = await store3.commit({
39085
+ const meta3 = await store4.commit({
38946
39086
  mode: args.mode,
38947
39087
  label: args.label,
38948
39088
  layer: args.layer,
@@ -44491,7 +44631,7 @@ import {
44491
44631
  } from "node:fs";
44492
44632
  import { join as join36 } from "node:path";
44493
44633
  import { homedir as homedir13 } from "node:os";
44494
- import { createHash as createHash8, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
44634
+ import { createHash as createHash9, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
44495
44635
  function getZelariHome() {
44496
44636
  return join36(homedir13(), ".zelari-code");
44497
44637
  }
@@ -44567,8 +44707,8 @@ function loadOrCreateToken(explicit) {
44567
44707
  }
44568
44708
  function tokenMatches(expected, provided) {
44569
44709
  if (!provided) return false;
44570
- const a = createHash8("sha256").update(expected).digest();
44571
- const b = createHash8("sha256").update(provided).digest();
44710
+ const a = createHash9("sha256").update(expected).digest();
44711
+ const b = createHash9("sha256").update(provided).digest();
44572
44712
  try {
44573
44713
  return timingSafeEqual(a, b);
44574
44714
  } catch {
@@ -49235,34 +49375,58 @@ function phaseKnobs(phase2) {
49235
49375
  })
49236
49376
  };
49237
49377
  }
49238
- function occupancyOf(hist, sessionExtra, contextLimit) {
49239
- const estimated = estimateHistoryTokens(hist);
49240
- const occupancy = Math.min(1, (estimated + sessionExtra) / contextLimit);
49241
- return { estimated, occupancy };
49242
- }
49378
+ var RESERVED_OUTPUT_TOKENS = 8192;
49243
49379
  async function applyBudgetPolicyAsync(history2, phase2, opts) {
49244
49380
  const contextLimit = resolveContextLimit(opts?.model);
49245
49381
  const sessionExtra = opts?.sessionTokens ?? 0;
49246
49382
  const warnings = [];
49247
49383
  let { historyTurns, maxToolLoopIterations } = phaseKnobs(phase2);
49384
+ const envelope = opts?.requestSnapshot ?? null;
49385
+ const replayBase = envelope ? {
49386
+ provider: envelope.snapshot.provider,
49387
+ model: envelope.snapshot.model,
49388
+ systemMessages: envelope.snapshot.systemMessages,
49389
+ tools: envelope.snapshot.tools
49390
+ } : opts?.providerStream ? { provider: "local", model: opts?.model ?? "unknown", systemMessages: [], tools: [] } : null;
49391
+ const headerTokens = envelope ? estimateSystemTokensLite(envelope.snapshot.systemMessages) + estimateToolSchemaTokensLite(envelope.snapshot.tools) : 0;
49392
+ const convTokensOf = (h) => estimateConversationTokensLite(h);
49248
49393
  let hist = history2;
49249
- let { estimated, occupancy } = occupancyOf(hist, sessionExtra, contextLimit);
49394
+ let estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
49395
+ let occupancy = Math.min(1, estimated / contextLimit);
49250
49396
  let compactSummary = "";
49251
49397
  let messagesRemoved = 0;
49398
+ let cacheReuseExpected;
49399
+ let prunedTotal = 0;
49252
49400
  if (occupancy >= 0.7 && occupancy < 0.85) {
49253
49401
  warnings.push(
49254
- `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated + sessionExtra}/${contextLimit} tok est.) \u2014 consider /compact or shorter replies.`
49402
+ `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated}/${contextLimit} tok full-request est.) \u2014 consider /compact or shorter replies.`
49255
49403
  );
49256
49404
  }
49405
+ if (occupancy >= 0.8) {
49406
+ const pruned = pruneToolResultsDetailed(hist);
49407
+ if (pruned.stats.pruned > 0) {
49408
+ hist = pruned.messages;
49409
+ prunedTotal += pruned.stats.pruned;
49410
+ estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
49411
+ occupancy = Math.min(1, estimated / contextLimit);
49412
+ warnings.push(
49413
+ `[budget] pruned ${pruned.stats.pruned} oversized tool result(s) \u2192 ${Math.round(occupancy * 100)}% (${estimated} tok).`
49414
+ );
49415
+ }
49416
+ }
49257
49417
  const fold = (r, label, forcedTurns) => {
49258
49418
  hist = r.messages;
49259
49419
  if (r.compacted) {
49260
49420
  messagesRemoved += r.messagesRemoved;
49261
49421
  if (r.summary) compactSummary = r.summary;
49422
+ if (r.cacheReuseExpected !== void 0) {
49423
+ cacheReuseExpected = r.cacheReuseExpected;
49424
+ }
49262
49425
  }
49263
- ({ estimated, occupancy } = occupancyOf(hist, sessionExtra, contextLimit));
49426
+ estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
49427
+ occupancy = Math.min(1, estimated / contextLimit);
49264
49428
  warnings.push(
49265
- `[budget] ${label} at ${forcedTurns === 2 ? "95%" : "85%"} \u2014 kept ~${forcedTurns} turns (${estimated} tok history est.` + (r.messagesRemoved ? `, removed ${r.messagesRemoved} msgs` : "") + `).`
49429
+ `[budget] ${label} \u2014 kept ~${forcedTurns} turns (${estimated} tok est.` + (r.messagesRemoved ? `, removed ${r.messagesRemoved} msgs` : "") + (r.cacheReuseExpected === false ? ", cache reuse NOT expected (model override)" : "") + ")."
49266
49430
  );
49267
49431
  };
49268
49432
  if (occupancy >= 0.85) {
@@ -49272,39 +49436,91 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
49272
49436
  maxToolLoopIterations,
49273
49437
  phase2 === "plan" ? 24 : 40
49274
49438
  );
49275
- const r = await compactHistoryAsync(hist, {
49276
- maxMessages: forcedTurns * 4,
49277
- signal: opts?.signal
49278
- });
49279
- const label = r.summary.includes("\xB7 llm") ? "llm-compact" : "auto-compact";
49439
+ let r = await compactHistoryAsync(hist, {
49440
+ maxMessages: Math.max(2, forcedTurns * 4),
49441
+ force: true,
49442
+ signal: opts?.signal,
49443
+ ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
49444
+ });
49445
+ if (!r.compacted) {
49446
+ r = await compactHistoryAsync(hist, {
49447
+ maxMessages: 2,
49448
+ force: true,
49449
+ signal: opts?.signal,
49450
+ ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
49451
+ });
49452
+ }
49453
+ const label = r.cacheReuseExpected === false ? "llm-compact (model override)" : "auto-compact";
49280
49454
  fold(r, label, forcedTurns);
49281
49455
  }
49282
49456
  if (occupancy >= 0.95) {
49283
49457
  const hard = await compactHistoryAsync(hist, {
49284
- maxMessages: 8,
49458
+ maxMessages: 2,
49459
+ force: true,
49285
49460
  signal: opts?.signal
49286
49461
  });
49287
- fold(hard, hard.summary.includes("\xB7 llm") ? "llm-compact" : "auto-compact", 2);
49462
+ fold(hard, hard.summary.includes("\xB7 llm") ? "llm-compact" : "HARD trim", 2);
49288
49463
  historyTurns = 2;
49289
49464
  maxToolLoopIterations = Math.min(maxToolLoopIterations, 16);
49290
49465
  warnings.push(
49291
- `[budget] HARD context pressure (\u226595%) \u2014 prefer /clear or a new session if quality drops.`
49466
+ "[budget] HARD context pressure (\u226595%) \u2014 prefer /clear or a new session if quality drops."
49292
49467
  );
49293
49468
  }
49469
+ const cacheMetricsLine = envelope ? [
49470
+ "compaction meter:",
49471
+ `provider/model: ${envelope.snapshot.provider}/${envelope.snapshot.model}`,
49472
+ `headerFingerprint: ${envelope.snapshot.headerFingerprint.slice(0, 12)}`,
49473
+ `occupancy: ${Math.round(occupancy * 100)}% (${estimated}/${contextLimit})`,
49474
+ ...envelope.usage?.cachedPromptTokens !== void 0 ? [`cachedPromptTokens: ${envelope.usage.cachedPromptTokens}`] : [],
49475
+ ...cacheReuseExpected !== void 0 ? [`cacheReuseExpected: ${cacheReuseExpected}`] : []
49476
+ ].join(" | ") : void 0;
49294
49477
  return {
49295
49478
  history: hist,
49296
49479
  warnings,
49297
49480
  maxToolLoopIterations,
49298
49481
  historyTurns,
49299
- estimatedHistoryTokens: estimated,
49482
+ estimatedHistoryTokens: envelope ? convTokensOf(hist) : estimated,
49300
49483
  contextLimit,
49301
49484
  occupancy,
49302
49485
  compactSummary: compactSummary || void 0,
49303
- messagesRemoved: messagesRemoved || void 0
49486
+ messagesRemoved: messagesRemoved || void 0,
49487
+ ...envelope ? { contextPressureTokens: estimated } : {},
49488
+ ...cacheReuseExpected !== void 0 ? { cacheReuseExpected } : {},
49489
+ ...cacheMetricsLine ? { cacheMetricsLine } : {}
49304
49490
  };
49305
49491
  }
49492
+ function estimateSystemTokensLite(systemMessages) {
49493
+ let n = 0;
49494
+ for (const m of systemMessages) n += 4 + Math.ceil((m.content ?? "").length / 4);
49495
+ return n;
49496
+ }
49497
+ function estimateToolSchemaTokensLite(tools) {
49498
+ let n = 0;
49499
+ for (const t of tools) {
49500
+ n += Math.ceil((t.name ?? "").length / 4);
49501
+ n += Math.ceil((t.description ?? "").length / 4);
49502
+ n += Math.ceil(JSON.stringify(t.parameters ?? {}).length / 4);
49503
+ }
49504
+ return n + tools.length * 4;
49505
+ }
49506
+ function estimateConversationTokensLite(messages) {
49507
+ let n = 0;
49508
+ for (const m of messages) {
49509
+ n += 4 + Math.ceil((m.content ?? "").length / 4);
49510
+ if (m.toolCalls) {
49511
+ for (const tc of m.toolCalls) {
49512
+ n += Math.ceil(tc.name.length / 4) + Math.ceil(tc.id.length / 4);
49513
+ n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
49514
+ }
49515
+ }
49516
+ if (m.reasoningContent) n += Math.ceil(m.reasoningContent.length / 4);
49517
+ if (m.toolCallId) n += Math.ceil(m.toolCallId.length / 4);
49518
+ }
49519
+ return n;
49520
+ }
49306
49521
 
49307
49522
  // src/cli/hooks/useChatTurn.ts
49523
+ init_requestSnapshotStore();
49308
49524
  function useChatTurn(params) {
49309
49525
  const {
49310
49526
  sessionId,
@@ -49331,17 +49547,9 @@ function useChatTurn(params) {
49331
49547
  let envConfig;
49332
49548
  let harness;
49333
49549
  let historySeedLen = 0;
49550
+ let systemPrefixLen = 0;
49334
49551
  let turnSucceeded = false;
49335
49552
  try {
49336
- compactInPlace();
49337
- const budget = await applyBudgetPolicyAsync(getHistory(), getPhase(), {
49338
- model: getActiveModel()
49339
- });
49340
- setHistory(budget.history);
49341
- for (const w of budget.warnings) {
49342
- appendSystem(setMessages, w, Date.now());
49343
- }
49344
- historySeedLen = getHistory().length;
49345
49553
  const anchored = maybeAnchorShortAnswer(userText);
49346
49554
  const effectiveUserText = anchored ?? userText;
49347
49555
  const localCli = (process.env.ZELARI_LOCAL_CLI ?? "").trim();
@@ -49436,6 +49644,33 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49436
49644
  });
49437
49645
  }
49438
49646
  const cwd = process.cwd();
49647
+ const budget = await applyBudgetPolicyAsync(getHistory(), getPhase(), {
49648
+ model: getActiveModel(),
49649
+ sessionId,
49650
+ // v1.36.0: envelope for full-request metering + cache-aware
49651
+ // compaction replay (last warm prefix + provider usage anchor).
49652
+ requestSnapshot: getRequestSnapshotWithUsage(sessionId),
49653
+ providerStream
49654
+ });
49655
+ setHistory(budget.history);
49656
+ for (const w of budget.warnings) {
49657
+ appendSystem(setMessages, w, Date.now());
49658
+ }
49659
+ if ((budget.messagesRemoved ?? 0) > 0) {
49660
+ const envelope = getRequestSnapshotWithUsage(sessionId);
49661
+ const compactionEvent = createBrainEvent("session_compacted", sessionId, {
49662
+ summary: budget.compactSummary ?? "",
49663
+ messagesRemoved: budget.messagesRemoved ?? 0,
49664
+ ...envelope ? {
49665
+ sourceRequestFingerprint: envelope.snapshot.requestFingerprint,
49666
+ headerFingerprint: envelope.snapshot.headerFingerprint
49667
+ } : {},
49668
+ ...budget.contextPressureTokens !== void 0 ? { sourceEstimatedTokens: budget.contextPressureTokens } : {},
49669
+ ...budget.cacheReuseExpected !== void 0 ? { cacheReuseExpected: budget.cacheReuseExpected } : {}
49670
+ });
49671
+ void writerRef.current?.append(compactionEvent);
49672
+ }
49673
+ historySeedLen = getHistory().length;
49439
49674
  let composedWorkspace = "";
49440
49675
  let composedInstructions = "";
49441
49676
  let hasPlan = false;
@@ -49593,6 +49828,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49593
49828
  lastStableHash = hashStablePrompt(fallback);
49594
49829
  systemMessages = [{ role: "system", content: fallback }];
49595
49830
  }
49831
+ systemPrefixLen = systemMessages.length;
49596
49832
  const maxToolCallsPerTurn = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
49597
49833
  default: 25,
49598
49834
  min: 1
@@ -49605,7 +49841,10 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49605
49841
  });
49606
49842
  const harness2 = new AgentHarness({
49607
49843
  model: envConfig.model,
49608
- provider: "openai-compatible",
49844
+ // v1.36.0 (P0.2): real provider identity — the harness used to
49845
+ // hardcode "openai-compatible" (the transport family) so snapshots
49846
+ // and telemetry mislabeled deepseek/glm/minimax routing.
49847
+ provider: envConfig.providerId,
49609
49848
  messages: [
49610
49849
  ...systemMessages,
49611
49850
  // v1.8.0: shared rolling history (agent/council/zelari) so short
@@ -49624,6 +49863,9 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49624
49863
  cwd,
49625
49864
  maxToolCallsPerTurn,
49626
49865
  maxToolLoopIterations,
49866
+ // v1.36.0: routed-request snapshots feed the meter (occupancy) and
49867
+ // the cache-aware compaction replay (last warm prefix).
49868
+ onRequestSnapshot: (snap) => recordRequestSnapshot(sessionId, snap),
49627
49869
  ...maxToolLoopHardCap > 0 ? { maxToolLoopHardCap } : {}
49628
49870
  });
49629
49871
  harnessRef.current = harness2;
@@ -49638,6 +49880,14 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49638
49880
  for await (const event of harness2.run()) {
49639
49881
  if (event.type === "message_end") {
49640
49882
  if (event.usage) realUsage = event.usage;
49883
+ if (event.usage) {
49884
+ recordRequestUsage(sessionId, {
49885
+ promptTokens: event.usage.promptTokens,
49886
+ completionTokens: event.usage.completionTokens,
49887
+ totalTokens: event.usage.totalTokens,
49888
+ cachedPromptTokens: event.usage.cachedPromptTokens
49889
+ });
49890
+ }
49641
49891
  if (streamContent) {
49642
49892
  const sealed = streamScrub.finalize(streamContent);
49643
49893
  if (useLiveModel) setStreaming(commitStreaming, sealed, event.ts);
@@ -49797,18 +50047,17 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49797
50047
  const h = harnessRef.current;
49798
50048
  if (h && turnSucceeded) {
49799
50049
  const all = h.getMessages();
49800
- const seedLen = 1 + historySeedLen + 1;
50050
+ const seedLen = systemPrefixLen + historySeedLen + 1;
49801
50051
  if (all.length > seedLen) {
49802
50052
  appendMessages(
49803
- all.slice(seedLen).map(
49804
- (m) => m.role === "assistant" && m.content ? {
49805
- ...m,
49806
- content: cleanAgentContent(m.content, {
49807
- stripQuestion: false,
49808
- stripThink: false
49809
- })
49810
- } : m
49811
- )
50053
+ all.slice(seedLen).map((m) => {
50054
+ if (m.role !== "assistant" || !m.content) return m;
50055
+ const cleaned = cleanAgentContent(m.content, {
50056
+ stripQuestion: false,
50057
+ stripThink: false
50058
+ });
50059
+ return cleaned === m.content ? m : { ...m, content: cleaned };
50060
+ })
49812
50061
  );
49813
50062
  }
49814
50063
  }
@@ -51751,12 +52000,12 @@ init_fileStateStore();
51751
52000
  async function restoreDurableState(opts) {
51752
52001
  const restoreTree = opts.restoreTree !== false;
51753
52002
  try {
51754
- const store3 = opts.store ?? await getStateStore(opts.projectRoot);
52003
+ const store4 = opts.store ?? await getStateStore(opts.projectRoot);
51755
52004
  let meta3;
51756
52005
  if (opts.commitId) {
51757
- meta3 = await store3.setHead(opts.commitId);
52006
+ meta3 = await store4.setHead(opts.commitId);
51758
52007
  } else {
51759
- meta3 = await store3.head();
52008
+ meta3 = await store4.head();
51760
52009
  if (!meta3) {
51761
52010
  return {
51762
52011
  ok: false,
@@ -51809,8 +52058,8 @@ function ago2(ms) {
51809
52058
  return `${Math.round(s / 3600)}h ago`;
51810
52059
  }
51811
52060
  async function handleStateStatus(ctx) {
51812
- const store3 = await getStateStore(ctx.cwd);
51813
- const head = await store3.head();
52061
+ const store4 = await getStateStore(ctx.cwd);
52062
+ const head = await store4.head();
51814
52063
  if (!head) {
51815
52064
  appendSystem(
51816
52065
  ctx.setMessages,
@@ -51818,9 +52067,9 @@ async function handleStateStatus(ctx) {
51818
52067
  );
51819
52068
  return;
51820
52069
  }
51821
- const discoveries = await store3.loadDiscoveries(head.id);
52070
+ const discoveries = await store4.loadDiscoveries(head.id);
51822
52071
  const reusable = discoveries.filter((d) => d.reusable).length;
51823
- const recent = await store3.list(8);
52072
+ const recent = await store4.list(8);
51824
52073
  const lines = recent.map((c, i) => {
51825
52074
  const ver2 = c.verification.ran ? c.verification.ok ? "ok" : "fail" : "n/a";
51826
52075
  return ` ${i === 0 ? "\u2192" : " "} ${c.id} ${ago2(c.createdAt)} ${c.label} ver=${ver2}` + (c.layer ? ` [${c.layer}]` : "") + (c.stablePromptHash ? ` hash=${c.stablePromptHash.slice(0, 8)}` : "");
@@ -51839,9 +52088,9 @@ async function handleStateStatus(ctx) {
51839
52088
  );
51840
52089
  }
51841
52090
  async function handleStateCommit(ctx, label) {
51842
- const store3 = await getStateStore(ctx.cwd);
52091
+ const store4 = await getStateStore(ctx.cwd);
51843
52092
  try {
51844
- const meta3 = await store3.commit({
52093
+ const meta3 = await store4.commit({
51845
52094
  mode: "agent",
51846
52095
  label: label?.trim() || "manual state commit",
51847
52096
  layer: "manual",
@@ -51868,8 +52117,8 @@ async function handleStateCommit(ctx, label) {
51868
52117
  }
51869
52118
  }
51870
52119
  async function handleStateShow(ctx, id) {
51871
- const store3 = await getStateStore(ctx.cwd);
51872
- const meta3 = id ? await store3.get(id) : await store3.head();
52120
+ const store4 = await getStateStore(ctx.cwd);
52121
+ const meta3 = id ? await store4.get(id) : await store4.head();
51873
52122
  if (!meta3) {
51874
52123
  appendSystem(
51875
52124
  ctx.setMessages,
@@ -51877,7 +52126,7 @@ async function handleStateShow(ctx, id) {
51877
52126
  );
51878
52127
  return;
51879
52128
  }
51880
- const text = await store3.materializeContext(meta3.id, 6e3);
52129
+ const text = await store4.materializeContext(meta3.id, 6e3);
51881
52130
  appendSystem(ctx.setMessages, `[state] show ${meta3.id}
51882
52131
  ${text}`);
51883
52132
  }
@@ -53386,14 +53635,14 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
53386
53635
  }
53387
53636
  function handleCouncilFeedback(ctx, memberId, score, note) {
53388
53637
  try {
53389
- const store3 = new FeedbackStore();
53390
- const entry = store3.record({
53638
+ const store4 = new FeedbackStore();
53639
+ const entry = store4.record({
53391
53640
  memberId,
53392
53641
  score,
53393
53642
  ...note ? { note } : {},
53394
53643
  ...ctx.sessionId ? { sessionId: ctx.sessionId } : {}
53395
53644
  });
53396
- const stats = store3.getStats(memberId);
53645
+ const stats = store4.getStats(memberId);
53397
53646
  appendSystem(
53398
53647
  ctx.setMessages,
53399
53648
  `[council-feedback] ${memberId} rated ${entry.score}/5 \u2014 running avg ${stats.avg.toFixed(2)} over ${stats.count} rating(s).`