billion-context-pi 0.1.34 → 0.1.36

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.
package/dist/index.js CHANGED
@@ -263,7 +263,7 @@ function defaultConfig(modelContextLimit, overrides = {}) {
263
263
  const base = {
264
264
  tiers: { enabled: true, tier2Trigger: 5, tier3Trigger: 10 },
265
265
  nudge: {
266
- maxContextLimitPct: 0.55,
266
+ maxContextLimitPct: 0.75,
267
267
  minContextLimitPct: 0.45,
268
268
  frequency: 5,
269
269
  iterationThreshold: 15,
@@ -273,10 +273,10 @@ function defaultConfig(modelContextLimit, overrides = {}) {
273
273
  growthCap: 5e4,
274
274
  minGrowthFloor: 2e4,
275
275
  minGrowthRatio: 0.45,
276
- emergencyThresholdPct: 0.8
276
+ emergencyThresholdPct: 0.95
277
277
  },
278
278
  promotionThreshold: 5,
279
- truncate: { threshold: 1 },
279
+ truncate: { threshold: 0.95 },
280
280
  compress: {
281
281
  minCompressRange: 5e3,
282
282
  maxSummaryLength: 2e4,
@@ -306,6 +306,11 @@ function validateConfig(config) {
306
306
  "nudge.minContextLimitPct must not exceed nudge.maxContextLimitPct"
307
307
  );
308
308
  }
309
+ if (config.nudge.maxContextLimitPct > config.nudge.emergencyThresholdPct) {
310
+ errors.push(
311
+ "nudge.maxContextLimitPct must not exceed nudge.emergencyThresholdPct"
312
+ );
313
+ }
309
314
  if (config.promotionThreshold < 1) {
310
315
  errors.push("promotionThreshold must be >= 1");
311
316
  }
@@ -1466,6 +1471,7 @@ function decideNudge(input) {
1466
1471
  const limit = config.modelContextLimit;
1467
1472
  const usage = limit > 0 ? tokenCount / limit : 0;
1468
1473
  const nudgeGrowthTokens = resolveAdaptiveGrowth(limit, config.nudge);
1474
+ const overLimit = usage >= config.nudge.maxContextLimitPct;
1469
1475
  const emergencyOverride = usage >= config.nudge.emergencyThresholdPct;
1470
1476
  const baseline = state.nudge.lastPerMessageNudgeTokens;
1471
1477
  const hadPendingNudge = state.nudge.lastNudgeShownTokens > 0;
@@ -1482,7 +1488,7 @@ function decideNudge(input) {
1482
1488
  let injectedTier = null;
1483
1489
  let injectedReason = "";
1484
1490
  const growthReady = growthSinceReference >= growthFloor;
1485
- if (!emergencyOverride && growthReady) {
1491
+ if (!overLimit && growthReady) {
1486
1492
  for (const tier of [1, 2, 3]) {
1487
1493
  if (!config.tiers.enabled && tier > 1) break;
1488
1494
  const info = tiers[tier];
@@ -1494,11 +1500,26 @@ function decideNudge(input) {
1494
1500
  injectedReason = tier === 1 ? `T1 compressible ${info.pending} >= ${nudgeGrowthTokens}, growth ${growthSinceReference}, usage ${Math.round(usage * 100)}%` : `T${tier} distill ready: ${info.targetBlocks.length} tier-${tier - 1} blocks (${info.pending} tokens) >= ${nudgeGrowthTokens}, usage ${Math.round(usage * 100)}%`;
1495
1501
  break;
1496
1502
  }
1503
+ } else if (overLimit) {
1504
+ for (const tier of [1, 2, 3]) {
1505
+ if (!config.tiers.enabled && tier > 1) break;
1506
+ const info = tiers[tier];
1507
+ if (!info || info.pending < config.compress.minCompressRange) continue;
1508
+ injectedTier = tier;
1509
+ injectedReason = emergencyOverride ? `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}%, T${tier} pending ${info.pending}` : `OVER-LIMIT: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.maxContextLimitPct * 100)}%, T${tier} pending ${info.pending}`;
1510
+ break;
1511
+ }
1497
1512
  }
1498
- const shouldInject = emergencyOverride || injectedTier !== null;
1513
+ const shouldInject = injectedTier !== null || overLimit && (rec?.recommendedRanges?.length ?? 0) > 0;
1499
1514
  let reason;
1500
- if (emergencyOverride) {
1501
- reason = `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}%`;
1515
+ if (emergencyOverride && injectedTier !== null) {
1516
+ reason = injectedReason;
1517
+ } else if (emergencyOverride) {
1518
+ reason = `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}% (no compressible content)`;
1519
+ } else if (overLimit && injectedTier !== null) {
1520
+ reason = injectedReason;
1521
+ } else if (overLimit) {
1522
+ reason = `OVER-LIMIT: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.maxContextLimitPct * 100)}% (no compressible content)`;
1502
1523
  } else if (injectedTier !== null) {
1503
1524
  reason = injectedReason;
1504
1525
  } else {
@@ -1534,6 +1555,7 @@ function decideNudge(input) {
1534
1555
  nudgeGrowthTokens,
1535
1556
  growthFloor,
1536
1557
  hasPendingNudge: hasPendingNudge ? 1 : 0,
1558
+ overLimit: overLimit ? 1 : 0,
1537
1559
  emergencyOverride: emergencyOverride ? 1 : 0,
1538
1560
  pendingT1: tiers[1].pending,
1539
1561
  pendingT2: tiers[2].pending,
@@ -1845,7 +1867,7 @@ function renderNudgeText(decision) {
1845
1867
  ].join("\n")
1846
1868
  };
1847
1869
  }
1848
- const isEmergency = !!decision.breakdown?.emergencyOverride;
1870
+ const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
1849
1871
  if (isEmergency) {
1850
1872
  return {
1851
1873
  voice: "emergency",
@@ -2403,20 +2425,53 @@ function makePreview(text, query, len) {
2403
2425
  // src/config.ts
2404
2426
  var DEFAULT_TOOL_BASH_TIMEOUT = 60;
2405
2427
  var DEFAULT_TOOL_OUTPUT_MAX_BYTES = 2e5;
2428
+ function resolveDelegate(adapter) {
2429
+ const d = adapter.delegate;
2430
+ if (typeof d === "object" && d !== null) {
2431
+ return {
2432
+ enabled: d.enabled !== false,
2433
+ displayUsage: d.displayUsage ?? adapter.displayUsage ?? "separate"
2434
+ };
2435
+ }
2436
+ return {
2437
+ enabled: d !== false,
2438
+ displayUsage: adapter.displayUsage ?? "separate"
2439
+ };
2440
+ }
2406
2441
  function resolveConfig(adapter, liveContextLimit) {
2407
2442
  const envLimit = process.env.ACP_MODEL_CONTEXT_LIMIT;
2408
2443
  const envLimitNum = envLimit ? Number(envLimit) : NaN;
2409
2444
  const FALLBACK_LIMIT = 15e4;
2410
2445
  const limit = !Number.isNaN(envLimitNum) && envLimitNum > 0 ? envLimitNum : adapter.modelContextLimit && adapter.modelContextLimit > 0 ? adapter.modelContextLimit : liveContextLimit > 0 ? liveContextLimit : FALLBACK_LIMIT;
2411
- return defaultConfig(limit, {
2446
+ const config = defaultConfig(limit, {
2412
2447
  protectedTools: adapter.protectedTools ?? [],
2413
2448
  preserveRecentMessages: adapter.preserveRecentMessages ?? 5,
2414
2449
  ...adapter.coreOverrides
2415
2450
  });
2451
+ const c = adapter.compress;
2452
+ if (c?.maxContextLimit !== void 0) config.nudge.maxContextLimitPct = parsePercent(c.maxContextLimit);
2453
+ if (c?.emergencyThresholdPercent !== void 0) {
2454
+ const pct2 = parsePercent(c.emergencyThresholdPercent);
2455
+ config.nudge.emergencyThresholdPct = pct2;
2456
+ config.truncate.threshold = pct2;
2457
+ }
2458
+ if (c?.nudgeGrowthTokens !== void 0) {
2459
+ config.nudge.growthFloor = c.nudgeGrowthTokens;
2460
+ config.nudge.growthCap = c.nudgeGrowthTokens;
2461
+ }
2462
+ return config;
2463
+ }
2464
+ function parsePercent(v) {
2465
+ if (typeof v === "number") return v;
2466
+ const s = v.trim();
2467
+ if (s.endsWith("%")) return Number(s.slice(0, -1)) / 100;
2468
+ return Number(s);
2416
2469
  }
2417
2470
 
2418
2471
  // src/messages.ts
2419
- var REF_TAG = new RegExp("^(?:<acp\\s[^>]*>m\\d{5}</acp>|\\[m\\d{1,5}\\])\\s?\\n?");
2472
+ var REF_TAG_SOURCE = "(?:<acp\\s[^>]*>m\\d{5}</acp>|\\[m\\d{1,5}\\])";
2473
+ var REF_TAG = new RegExp(`^${REF_TAG_SOURCE}\\s?\\n?`);
2474
+ var TRAILING_REF_TAG = new RegExp(`\\n*${REF_TAG_SOURCE}\\s*$`);
2420
2475
  function entriesToCoreMessages(entries) {
2421
2476
  const out = [];
2422
2477
  for (const entry of entries) {
@@ -2494,17 +2549,74 @@ function stringifyArgs(args) {
2494
2549
  return safeStringify(args);
2495
2550
  }
2496
2551
  function extractText(content) {
2497
- if (typeof content === "string") return content.replace(REF_TAG, "");
2552
+ if (typeof content === "string") return stripRefTag(content);
2498
2553
  if (!Array.isArray(content)) return "";
2499
2554
  const parts = [];
2500
2555
  for (const block of content) {
2501
2556
  const b = block;
2502
- if (b.type === "text" && typeof b.text === "string") {
2503
- parts.push(b.text.replace(REF_TAG, ""));
2504
- }
2557
+ if (b.type === "text" && typeof b.text === "string") parts.push(stripRefTag(b.text));
2505
2558
  }
2506
2559
  return parts.join("\n");
2507
2560
  }
2561
+ function stripRefTag(text) {
2562
+ return text.replace(REF_TAG, "").replace(TRAILING_REF_TAG, "");
2563
+ }
2564
+ function messageIdentity(message) {
2565
+ return JSON.stringify(normalizeIdentityValue(message, true));
2566
+ }
2567
+ function messageRef(message) {
2568
+ if (message === null || typeof message !== "object" || !("content" in message)) return void 0;
2569
+ const content = message.content;
2570
+ const texts = typeof content === "string" ? [content] : Array.isArray(content) ? content.flatMap((block) => {
2571
+ const value = block;
2572
+ return value.type === "text" && typeof value.text === "string" ? [value.text] : [];
2573
+ }) : [];
2574
+ for (const text of texts) {
2575
+ const tag = text.match(REF_TAG)?.[0] ?? text.match(TRAILING_REF_TAG)?.[0];
2576
+ const ref = tag?.match(/m\d{1,5}/)?.[0];
2577
+ if (ref) return ref;
2578
+ }
2579
+ return void 0;
2580
+ }
2581
+ function normalizeIdentityValue(value, message = false) {
2582
+ if (Array.isArray(value)) {
2583
+ return value.flatMap((item) => {
2584
+ if (!item || typeof item !== "object") return [normalizeIdentityValue(item)];
2585
+ const block = item;
2586
+ if (block.type === "text" && typeof block.text === "string") {
2587
+ const stripped = stripRefTag(block.text);
2588
+ if (block.text !== stripped && stripped === "") return [];
2589
+ }
2590
+ return [normalizeIdentityValue(item)];
2591
+ });
2592
+ }
2593
+ if (value === null || typeof value !== "object") return value;
2594
+ const out = {};
2595
+ for (const key of Object.keys(value).sort()) {
2596
+ if (message && key === "timestamp") continue;
2597
+ const item = value[key];
2598
+ if (message && key === "content" && typeof item === "string") {
2599
+ out[key] = [{ text: stripRefTag(item), type: "text" }];
2600
+ } else if (key === "text" && typeof item === "string" && value.type === "text") {
2601
+ out[key] = stripRefTag(item);
2602
+ } else {
2603
+ out[key] = normalizeIdentityValue(item);
2604
+ }
2605
+ }
2606
+ return out;
2607
+ }
2608
+ var TRUNCATION_MARKER2 = "[truncated for context space]";
2609
+ function matchesStoredText(stored, visible) {
2610
+ const marker = `...${TRUNCATION_MARKER2} \u2014 original ~`;
2611
+ const markerStart = visible.indexOf(marker);
2612
+ if (markerStart < 2 || visible.slice(markerStart - 2, markerStart) !== "\n\n") return false;
2613
+ const suffixMarker = " tokens]...\n\n";
2614
+ const suffixStart = visible.indexOf(suffixMarker, markerStart + marker.length);
2615
+ if (suffixStart < 0 || !/^\d+$/.test(visible.slice(markerStart + marker.length, suffixStart))) return false;
2616
+ const prefix = visible.slice(0, markerStart - 2);
2617
+ const suffix = visible.slice(suffixStart + suffixMarker.length);
2618
+ return prefix.length > 0 && suffix.length > 0 && stored.startsWith(prefix) && stored.endsWith(suffix);
2619
+ }
2508
2620
  function allToolCalls(content) {
2509
2621
  if (!Array.isArray(content)) return [];
2510
2622
  const calls = [];
@@ -2598,11 +2710,11 @@ function patchRefTag(original, core) {
2598
2710
  let injected = false;
2599
2711
  for (let i = newBlocks.length - 1; i >= 0; i--) {
2600
2712
  const b = newBlocks[i];
2601
- if (b?.type === "text" && typeof b.text === "string") {
2713
+ if (b?.type === "text" && typeof b.text === "string" && b.text.length > 0) {
2602
2714
  const baseText = b.text.replace(/\n*$/, "");
2603
- newBlocks[i] = { ...b, text: baseText.length > 0 ? `${baseText}
2715
+ newBlocks[i] = { ...b, text: `${baseText}
2604
2716
 
2605
- ${tag}` : tag };
2717
+ ${tag}` };
2606
2718
  injected = true;
2607
2719
  break;
2608
2720
  }
@@ -2637,8 +2749,8 @@ function peelRefTagBlocks(blocks) {
2637
2749
  for (const block of blocks) {
2638
2750
  const b = block;
2639
2751
  if (b?.type === "text" && typeof b.text === "string") {
2640
- const stripped = b.text.replace(REF_TAG, "");
2641
- if (stripped.length > 0) out.push({ ...b, text: stripped });
2752
+ const stripped = stripRefTag(b.text);
2753
+ if (stripped.length > 0 || b.text.length === 0) out.push({ ...b, text: stripped });
2642
2754
  } else {
2643
2755
  out.push(block);
2644
2756
  }
@@ -2735,51 +2847,127 @@ function stateFileFor(sessionFile) {
2735
2847
  if (sessionFile) return sessionFile + STATE_SUFFIX;
2736
2848
  return null;
2737
2849
  }
2850
+ async function readParentSessionPath(sessionFile) {
2851
+ try {
2852
+ const handle = await fs.open(sessionFile, "r");
2853
+ try {
2854
+ const buf = Buffer.alloc(65536);
2855
+ const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
2856
+ if (bytesRead === 0) return void 0;
2857
+ const firstLine = buf.subarray(0, bytesRead).toString("utf8").split("\n")[0] ?? "";
2858
+ if (!firstLine.startsWith("{")) return void 0;
2859
+ const header = JSON.parse(firstLine);
2860
+ return typeof header.parentSession === "string" ? header.parentSession : void 0;
2861
+ } finally {
2862
+ await handle.close();
2863
+ }
2864
+ } catch (e) {
2865
+ const code = e.code;
2866
+ if (code !== "ENOENT") {
2867
+ logWarn("state", { event: "read-parent-header-failed", file: sessionFile, error: e instanceof Error ? e.message : String(e) });
2868
+ }
2869
+ return void 0;
2870
+ }
2871
+ }
2872
+ function cacheKey(sessionFile, sessionId) {
2873
+ return sessionFile ? `file:${sessionFile}` : `session:${sessionId}`;
2874
+ }
2738
2875
  var SessionStateStore = class {
2739
- cache = null;
2740
- loadedKey = null;
2741
- async load(sessionFile, _sessionId) {
2876
+ cache = /* @__PURE__ */ new Map();
2877
+ async load(sessionFile, sessionId) {
2742
2878
  const file = stateFileFor(sessionFile);
2743
- if (file && this.loadedKey === file && this.cache) return this.cache;
2879
+ const key = cacheKey(sessionFile, sessionId);
2880
+ const cached = this.cache.get(key);
2881
+ if (cached) return cached.state;
2744
2882
  let state = createInitialState();
2883
+ let liveRefOrigins = [];
2745
2884
  if (file) {
2746
2885
  try {
2747
2886
  const raw = await fs.readFile(file, "utf8");
2748
2887
  const parsed = JSON.parse(raw);
2749
- if (parsed && Array.isArray(parsed.blocks)) state = mergeInitialState(parsed);
2888
+ if (parsed && Array.isArray(parsed.blocks)) {
2889
+ state = mergeInitialState(parsed);
2890
+ liveRefOrigins = parseLiveRefOrigins(parsed.liveRefOrigins);
2891
+ }
2750
2892
  } catch (e) {
2751
2893
  const code = e.code;
2752
2894
  if (code !== "ENOENT") {
2753
2895
  logWarn("state", { event: "load-failed", file, error: e instanceof Error ? e.message : String(e) });
2754
2896
  }
2755
2897
  }
2898
+ if (state.blocks.length === 0 && sessionFile) {
2899
+ const parentState = await this.tryLoadParentState(sessionFile);
2900
+ if (parentState) state = parentState;
2901
+ }
2756
2902
  }
2757
- this.cache = state;
2758
- this.loadedKey = file;
2903
+ this.cache.set(key, { state, liveRefOrigins });
2759
2904
  return state;
2760
2905
  }
2761
- async save(state, sessionFile, _sessionId) {
2906
+ async save(state, sessionFile, sessionId) {
2762
2907
  const file = stateFileFor(sessionFile);
2763
2908
  if (!file) return;
2764
- this.cache = state;
2765
- this.loadedKey = file;
2909
+ const key = cacheKey(sessionFile, sessionId);
2910
+ const liveRefOrigins = this.cache.get(key)?.liveRefOrigins ?? [];
2911
+ this.cache.set(key, { state, liveRefOrigins });
2766
2912
  const dir = path2.dirname(file);
2767
2913
  await fs.mkdir(dir, { recursive: true }).catch((e) => {
2768
2914
  logError("state", { event: "save-mkdir-failed", dir, error: e instanceof Error ? e.message : String(e) });
2769
2915
  });
2770
2916
  const tmp = path2.join(dir, `.acp-tmp-${path2.basename(file)}`);
2771
2917
  try {
2772
- await fs.writeFile(tmp, JSON.stringify(state), "utf8");
2918
+ await fs.writeFile(tmp, JSON.stringify({ ...state, liveRefOrigins }), "utf8");
2773
2919
  await fs.rename(tmp, file);
2774
2920
  } catch (e) {
2775
2921
  logError("state", { event: "save-failed", file, error: e instanceof Error ? e.message : String(e) });
2776
2922
  }
2777
2923
  }
2924
+ getLiveRefOrigins(sessionFile, sessionId) {
2925
+ return [...this.cache.get(cacheKey(sessionFile, sessionId))?.liveRefOrigins ?? []];
2926
+ }
2927
+ setLiveRefOrigins(sessionFile, sessionId, origins) {
2928
+ const key = cacheKey(sessionFile, sessionId);
2929
+ const slot = this.cache.get(key);
2930
+ if (slot) this.cache.set(key, { state: slot.state, liveRefOrigins: [...origins] });
2931
+ }
2778
2932
  invalidate() {
2779
- this.cache = null;
2780
- this.loadedKey = null;
2933
+ this.cache.clear();
2934
+ }
2935
+ async tryLoadParentState(sessionFile) {
2936
+ const MAX_CHAIN_DEPTH = 8;
2937
+ let current = sessionFile;
2938
+ for (let depth = 0; depth < MAX_CHAIN_DEPTH; depth++) {
2939
+ const parentJsonl = await readParentSessionPath(current);
2940
+ if (!parentJsonl) return void 0;
2941
+ const parentAcp = stateFileFor(parentJsonl);
2942
+ if (!parentAcp) return void 0;
2943
+ try {
2944
+ const raw = await fs.readFile(parentAcp, "utf8");
2945
+ const parsed = JSON.parse(raw);
2946
+ if (parsed && Array.isArray(parsed.blocks) && parsed.blocks.length > 0) {
2947
+ logInfo("state", { event: "inherited-parent-state", file: parentAcp, depth, blocks: parsed.blocks.length, tokensCompressed: parsed.stats?.tokensCompressed ?? 0 });
2948
+ return mergeInitialState(parsed);
2949
+ }
2950
+ } catch (e) {
2951
+ const code = e.code;
2952
+ if (code !== "ENOENT") {
2953
+ logWarn("state", { event: "parent-state-load-failed", file: parentAcp, error: e instanceof Error ? e.message : String(e) });
2954
+ return void 0;
2955
+ }
2956
+ }
2957
+ current = parentJsonl;
2958
+ }
2959
+ logWarn("state", { event: "parent-chain-exhausted", file: sessionFile, maxDepth: MAX_CHAIN_DEPTH });
2960
+ return void 0;
2781
2961
  }
2782
2962
  };
2963
+ function parseLiveRefOrigins(value) {
2964
+ if (!Array.isArray(value)) return [];
2965
+ return value.filter((item) => {
2966
+ if (!item || typeof item !== "object") return false;
2967
+ const origin = item;
2968
+ return typeof origin.rawId === "string" && typeof origin.identity === "string";
2969
+ });
2970
+ }
2783
2971
  function mergeInitialState(parsed) {
2784
2972
  const fresh = createInitialState();
2785
2973
  return {
@@ -2803,50 +2991,116 @@ function isPiHost(sm) {
2803
2991
  const source = sm;
2804
2992
  return typeof source.buildContextEntries === "function";
2805
2993
  }
2806
- function mergeLiveEntries(entries, live) {
2994
+ function mergeLiveEntries(entries, live, state, origins) {
2807
2995
  const persisted = entries.filter((e) => e.type === "message");
2996
+ const matched = matchPersistedSuffix(persisted, live);
2997
+ const prior = matchOrigins(origins, live);
2808
2998
  const out = [];
2809
- let p = 0;
2810
- let unmatched = 0;
2999
+ const nextOrigins = [];
3000
+ const usedIds = /* @__PURE__ */ new Set();
2811
3001
  for (let i = 0; i < live.length; i++) {
2812
3002
  const msg = live[i];
2813
- let matched;
2814
- let j = p;
2815
- while (j < persisted.length && persisted[j].message.role !== msg.role) j++;
2816
- if (j < persisted.length && sameMessage(persisted[j].message, msg)) {
2817
- matched = persisted[j];
2818
- p = j + 1;
2819
- }
2820
- if (matched) {
2821
- out.push(matched);
2822
- } else {
2823
- unmatched++;
2824
- out.push({
2825
- type: "message",
2826
- id: `live-${i}`,
2827
- parentId: null,
2828
- timestamp: String(msg.timestamp ?? Date.now()),
2829
- message: msg
2830
- });
3003
+ const entry = matched[i];
3004
+ const origin = prior[i];
3005
+ if (entry) {
3006
+ if (origin) migrateLiveRefs(state, origin.rawId, entry.id);
3007
+ else migrateTaggedRef(state, msg, entry.id);
3008
+ out.push(entry);
3009
+ continue;
2831
3010
  }
3011
+ const id = origin?.rawId ?? nextLiveId(state, usedIds, i);
3012
+ usedIds.add(id);
3013
+ out.push({ type: "message", id, parentId: null, timestamp: String(msg.timestamp ?? Date.now()), message: msg });
3014
+ nextOrigins.push({ rawId: id, identity: messageIdentity(msg) });
2832
3015
  }
3016
+ origins.splice(0, origins.length, ...nextOrigins);
3017
+ const unmatched = live.length - matched.length;
2833
3018
  if (unmatched > 0) logInfo("runtime", { event: "merge-live-entries", live: live.length, unmatched });
2834
3019
  return out;
2835
3020
  }
3021
+ function matchOrigins(origins, live) {
3022
+ for (let start = 0; start < origins.length; start++) {
3023
+ const count = origins.length - start;
3024
+ if (count > live.length) continue;
3025
+ if (origins.slice(start).every((origin, index) => origin.identity === messageIdentity(live[index]))) {
3026
+ return [...origins.slice(start), ...Array(live.length - count)];
3027
+ }
3028
+ }
3029
+ return Array(live.length);
3030
+ }
3031
+ function nextLiveId(state, used, index) {
3032
+ let id = `live-${index}`;
3033
+ let suffix = index;
3034
+ while (used.has(id) || state.messageRefs.byRaw[id] !== void 0) id = `live-${++suffix}`;
3035
+ return id;
3036
+ }
3037
+ function migrateTaggedRef(state, message, stableId) {
3038
+ const ref = messageRef(message);
3039
+ const rawId = ref ? state.messageRefs.byRef[ref] : void 0;
3040
+ if (rawId?.startsWith("live-")) migrateLiveRefs(state, rawId, stableId);
3041
+ }
3042
+ function migrateLiveRefs(state, liveId, stableId) {
3043
+ const rootId = liveId.split("#", 1)[0];
3044
+ if (!rootId.startsWith("live-")) return;
3045
+ for (const [rawId, ref] of Object.entries(state.messageRefs.byRaw)) {
3046
+ if (rawId !== rootId && !rawId.startsWith(`${rootId}#`)) continue;
3047
+ const stableRawId = `${stableId}${rawId.slice(rootId.length)}`;
3048
+ if (state.messageRefs.byRaw[stableRawId] === void 0) {
3049
+ state.messageRefs.byRaw[stableRawId] = ref;
3050
+ state.messageRefs.byRef[ref] = stableRawId;
3051
+ } else if (state.messageRefs.byRef[ref] === rawId) {
3052
+ delete state.messageRefs.byRef[ref];
3053
+ }
3054
+ delete state.messageRefs.byRaw[rawId];
3055
+ }
3056
+ }
3057
+ function matchPersistedSuffix(persisted, live) {
3058
+ for (let start = 0; start < persisted.length; start++) {
3059
+ const count = persisted.length - start;
3060
+ if (count > live.length) continue;
3061
+ const suffix = persisted.slice(start);
3062
+ if (suffix.every((entry, index) => sameMessage(entry.message, live[index]))) return suffix;
3063
+ }
3064
+ return [];
3065
+ }
2836
3066
  function sameMessage(a, b) {
2837
- const ra = a.role;
2838
- const rb = b.role;
2839
- if (ra !== rb) return false;
2840
- const ca = a.content;
2841
- const cb = b.content;
2842
- if (ca === void 0 || cb === void 0) return false;
2843
3067
  try {
2844
- return JSON.stringify(ca) === JSON.stringify(cb);
3068
+ if (messageIdentity(a) === messageIdentity(b)) return true;
3069
+ const ra = a.role;
3070
+ const rb = b.role;
3071
+ if (ra !== rb || ra !== "toolResult") return false;
3072
+ const ca = a.content;
3073
+ const cb = b.content;
3074
+ return sameNonTextBlocks(ca, cb) && matchesStoredText(extractText(ca), extractText(cb));
2845
3075
  } catch (e) {
2846
3076
  logWarn("runtime", { event: "message-compare-failed", error: e instanceof Error ? e.message : String(e) });
2847
3077
  return a === b;
2848
3078
  }
2849
3079
  }
3080
+ function sameNonTextBlocks(a, b) {
3081
+ const nonText = (blocks) => blocks.filter((block) => block.type !== "text");
3082
+ try {
3083
+ const na = Array.isArray(a) ? nonText(a) : [];
3084
+ const nb = Array.isArray(b) ? nonText(b) : [];
3085
+ return JSON.stringify(na) === JSON.stringify(nb);
3086
+ } catch {
3087
+ return false;
3088
+ }
3089
+ }
3090
+ function pruneOrphanRefs(state, messages) {
3091
+ const retainedRawIds = new Set(messages.map((message) => message.id));
3092
+ for (const block of state.blocks) {
3093
+ for (const rawId of [...block.directMessageIds, ...block.effectiveMessageIds]) retainedRawIds.add(rawId);
3094
+ }
3095
+ for (const [rawId, ref] of Object.entries(state.messageRefs.byRaw)) {
3096
+ if (retainedRawIds.has(rawId)) continue;
3097
+ delete state.messageRefs.byRaw[rawId];
3098
+ if (state.messageRefs.byRef[ref] === rawId) delete state.messageRefs.byRef[ref];
3099
+ }
3100
+ for (const [ref, rawId] of Object.entries(state.messageRefs.byRef)) {
3101
+ if (!retainedRawIds.has(rawId)) delete state.messageRefs.byRef[ref];
3102
+ }
3103
+ }
2850
3104
  function createRuntime(adapter) {
2851
3105
  const core = createCore({ countTokens: defaultCountTokens });
2852
3106
  const store = new SessionStateStore();
@@ -2877,10 +3131,20 @@ function createRuntime(adapter) {
2877
3131
  }
2878
3132
  async function stateFor(ctx, liveMessages) {
2879
3133
  const sm = ctx.sessionManager;
2880
- const state = await store.load(sm.getSessionFile() ?? void 0, sm.getSessionId());
3134
+ const sessionFile = sm.getSessionFile() ?? void 0;
3135
+ const sessionId = sm.getSessionId();
3136
+ const state = await store.load(sessionFile, sessionId);
2881
3137
  const entries = readContextEntries(sm);
2882
- const merged = isPiHost(sm) || !liveMessages || liveMessages.length === 0 ? entries : mergeLiveEntries(entries, liveMessages);
2883
- return { state, coreMessages: entriesToCoreMessages(merged), entries: merged };
3138
+ if (!isPiHost(sm) && liveMessages && liveMessages.length > 0) {
3139
+ const origins = store.getLiveRefOrigins(sessionFile, sessionId);
3140
+ const merged = mergeLiveEntries(entries, liveMessages, state, origins);
3141
+ store.setLiveRefOrigins(sessionFile, sessionId, origins);
3142
+ const coreMessages2 = entriesToCoreMessages(merged);
3143
+ return { state, coreMessages: coreMessages2, entries: merged };
3144
+ }
3145
+ const coreMessages = entriesToCoreMessages(entries);
3146
+ if (liveMessages === void 0) pruneOrphanRefs(state, coreMessages);
3147
+ return { state, coreMessages, entries };
2884
3148
  }
2885
3149
  async function save(state, ctx) {
2886
3150
  const sm = ctx.sessionManager;
@@ -7360,9 +7624,19 @@ function makeCompressTool(runtime) {
7360
7624
  async function handleCompress(args, runtime, ctx, toolCallId) {
7361
7625
  const ranges = args.content ?? [];
7362
7626
  if (ranges.length === 0) return "No ranges provided.";
7363
- const { state, coreMessages } = await runtime.stateFor(ctx);
7627
+ const { state: initialState, coreMessages } = await runtime.stateFor(ctx);
7364
7628
  const config = runtime.configFor(ctx);
7365
- const beforeTokens = estimateTokens(coreMessages, collectCoveredMessageIds(state));
7629
+ const estimatedTokens = estimateTokens(coreMessages, collectCoveredMessageIds(initialState));
7630
+ const realUsage = ctx.getContextUsage?.();
7631
+ const turn = runtime.core.processTurn({
7632
+ messages: coreMessages,
7633
+ state: initialState,
7634
+ config,
7635
+ tokenCount: realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : estimatedTokens
7636
+ });
7637
+ const state = turn.state;
7638
+ const messages = turn.messages;
7639
+ const beforeTokens = estimateTokens(messages, collectCoveredMessageIds(state));
7366
7640
  const summaryMaxChars = args.summaryMaxChars;
7367
7641
  const topLevelTopic = args.topic;
7368
7642
  debug.event("compress-in", {
@@ -7371,12 +7645,12 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
7371
7645
  spans: ranges.map((r) => ({ span: `${r.startId}..${r.endId}`, summaryLen: r.summary.length, summary: r.summary, topic: r.topic ?? topLevelTopic ?? null })),
7372
7646
  blocksBefore: state.blocks.length,
7373
7647
  activeBefore: state.blocks.filter((b) => b.active).length,
7374
- beforeMsgCount: coreMessages.length,
7648
+ beforeMsgCount: messages.length,
7375
7649
  beforeTokens
7376
7650
  });
7377
7651
  const applied = runtime.core.applyCompression({
7378
7652
  ranges: ranges.map((r) => ({ startRef: r.startId, endRef: r.endId, summary: r.summary, topic: r.topic ?? topLevelTopic, summaryMaxChars, compressCallId: toolCallId })),
7379
- messages: coreMessages,
7653
+ messages,
7380
7654
  state,
7381
7655
  config
7382
7656
  });
@@ -7699,76 +7973,6 @@ function formatSize(tokens) {
7699
7973
  return `${(tokens / 1e6).toFixed(1)}M`;
7700
7974
  }
7701
7975
 
7702
- // src/status-tool.ts
7703
- var StatusParams = typebox_exports.Object({
7704
- scope: typebox_exports.Optional(typebox_exports.Union([typebox_exports.Literal("compressed"), typebox_exports.Literal("uncompressed")], { description: '"compressed" = drill into blocks; "uncompressed" = show visible messages/ranges. Default: overview.' })),
7705
- view: typebox_exports.Optional(typebox_exports.Union([typebox_exports.Literal("ranges"), typebox_exports.Literal("messages")], { description: 'For uncompressed scope: "ranges" (default) or "messages" (per-message listing).' })),
7706
- tool: typebox_exports.Optional(typebox_exports.String({ description: 'Filter by tool name (e.g. "bash", "read"). Only for uncompressed+messages.' })),
7707
- sort: typebox_exports.Optional(typebox_exports.Union([typebox_exports.Literal("size"), typebox_exports.Literal("time"), typebox_exports.Literal("tool"), typebox_exports.Literal("age")], { description: "Sort order. Default: size." })),
7708
- limit: typebox_exports.Optional(typebox_exports.Number({ description: "Max items to show (default: 30)." }))
7709
- });
7710
- function makeStatusTool(runtime) {
7711
- return {
7712
- name: "acp_status",
7713
- label: "ACP Status",
7714
- description: "Context status: overview, compressed blocks, or uncompressed ranges/messages. No args = overview + totals + compressible ranges. scope:'uncompressed' + view:'messages' for per-message listing. scope:'compressed' for block drilldown.",
7715
- promptSnippet: 'acp_status({}) or acp_status({ scope: "uncompressed", view: "messages" })',
7716
- promptGuidelines: [
7717
- "Call with no args for a quick overview of context usage.",
7718
- "Use scope:'uncompressed' to find the largest compressible ranges.",
7719
- "Use scope:'compressed' to inspect existing compression blocks."
7720
- ],
7721
- parameters: StatusParams,
7722
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
7723
- let result;
7724
- try {
7725
- result = await handleStatus(params, runtime, ctx);
7726
- } catch (e) {
7727
- logThrow("status", e, { sid: ctx.sessionManager.getSessionId(), scope: params.scope ?? null });
7728
- throw e;
7729
- }
7730
- return { details: void 0, content: [{ type: "text", text: result }] };
7731
- }
7732
- };
7733
- }
7734
- async function handleStatus(args, runtime, ctx) {
7735
- const { state, coreMessages } = await runtime.stateFor(ctx);
7736
- const config = runtime.configFor(ctx);
7737
- const tokenCount = estimateTokens(coreMessages, collectCoveredMessageIds(state));
7738
- const realUsage = ctx.getContextUsage?.();
7739
- const turn = runtime.core.processTurn({
7740
- messages: coreMessages,
7741
- state,
7742
- config,
7743
- tokenCount: realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : tokenCount
7744
- });
7745
- const processed = turn.messages;
7746
- const base = buildStatusReport(turn.state, processed, defaultCountTokens, {
7747
- scope: args.scope,
7748
- view: args.view,
7749
- tool: args.tool,
7750
- sort: args.sort,
7751
- limit: args.limit
7752
- });
7753
- if (args.scope) return base;
7754
- const nudge = turn.nudge;
7755
- const ranges = nudge?.compressibleRanges ?? [];
7756
- const protectedRanges = nudge?.protectedRanges ?? [];
7757
- const extra = [];
7758
- if (nudge) {
7759
- extra.push("");
7760
- extra.push(
7761
- nudge.shouldInject ? `Nudge: ACTIVE \u2014 ${nudge.reason}` : `Nudge: idle \u2014 ${nudge.reason}`
7762
- );
7763
- }
7764
- if (ranges.length > 0 || protectedRanges.length > 0) {
7765
- extra.push("");
7766
- extra.push(formatRanges(ranges, protectedRanges));
7767
- }
7768
- return extra.length > 0 ? `${base}
7769
- ${extra.join("\n")}` : base;
7770
- }
7771
-
7772
7976
  // src/delegate-tool.ts
7773
7977
  import {
7774
7978
  spawn
@@ -7778,11 +7982,52 @@ import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } fro
7778
7982
  import { tmpdir as tmpdir2 } from "os";
7779
7983
  import { join as join4 } from "path";
7780
7984
 
7985
+ // src/footer-status.ts
7986
+ var FOOTER_STATUS_KEY = "billion-context-pi";
7987
+ var ui;
7988
+ var lastFooterText = "";
7989
+ function formatCompactTokens(count) {
7990
+ if (count < 1e3) return count.toString();
7991
+ if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
7992
+ if (count < 1e6) return `${Math.round(count / 1e3)}k`;
7993
+ if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
7994
+ return `${Math.round(count / 1e6)}M`;
7995
+ }
7996
+ function initFooterStatus(ctx) {
7997
+ ui = ctx.ui;
7998
+ lastFooterText = void 0;
7999
+ }
8000
+ function updateFooterStatus() {
8001
+ if (!ui) return;
8002
+ const usage = getDelegateUsage();
8003
+ let text;
8004
+ if (usage && usage.totalTokens > 0) {
8005
+ const costStr = usage.cost.total > 0 ? ` ($${usage.cost.total.toFixed(4)})` : "";
8006
+ text = `sub-agents \u2191${formatCompactTokens(usage.input)} \u2193${formatCompactTokens(usage.output)}${costStr}`;
8007
+ }
8008
+ if ((text ?? "") === lastFooterText) return;
8009
+ lastFooterText = text ?? "";
8010
+ try {
8011
+ ui.setStatus(FOOTER_STATUS_KEY, text);
8012
+ } catch {
8013
+ }
8014
+ }
8015
+ function disposeFooterStatus() {
8016
+ if (ui) {
8017
+ try {
8018
+ ui.setStatus(FOOTER_STATUS_KEY, void 0);
8019
+ } catch {
8020
+ }
8021
+ }
8022
+ ui = void 0;
8023
+ lastFooterText = "";
8024
+ }
8025
+
7781
8026
  // src/fleet-widget.ts
7782
8027
  var DELEGATE_WIDGET_KEY = "billion-context-pi-delegates";
7783
8028
  var REFRESH_MS = 500;
7784
8029
  var MAX_TASK_LEN = 48;
7785
- var ui;
8030
+ var ui2;
7786
8031
  var timer;
7787
8032
  var lastRenderKey = "";
7788
8033
  var runsSnapshot;
@@ -7811,20 +8056,21 @@ function stopTimer() {
7811
8056
  }
7812
8057
  }
7813
8058
  function clearWidget() {
7814
- if (!ui) return;
8059
+ if (!ui2) return;
7815
8060
  try {
7816
- ui.setWidget(DELEGATE_WIDGET_KEY, void 0);
8061
+ ui2.setWidget(DELEGATE_WIDGET_KEY, void 0);
7817
8062
  } catch {
7818
8063
  }
7819
8064
  }
7820
8065
  function refresh() {
7821
- if (!ui) return;
8066
+ if (!ui2) return;
7822
8067
  const runs2 = runsSnapshot ? runsSnapshot() : [];
7823
8068
  if (runs2.length === 0) {
7824
8069
  if (lastRenderKey !== "") {
7825
8070
  lastRenderKey = "";
7826
8071
  clearWidget();
7827
8072
  }
8073
+ updateFooterStatus();
7828
8074
  stopTimer();
7829
8075
  return;
7830
8076
  }
@@ -7834,16 +8080,18 @@ function refresh() {
7834
8080
  lastRenderKey = renderKey;
7835
8081
  const lines = renderLines(sorted);
7836
8082
  try {
7837
- ui.setWidget(DELEGATE_WIDGET_KEY, lines, { placement: "belowEditor" });
8083
+ ui2.setWidget(DELEGATE_WIDGET_KEY, lines, { placement: "belowEditor" });
7838
8084
  } catch {
7839
- ui = void 0;
8085
+ ui2 = void 0;
7840
8086
  stopTimer();
7841
8087
  }
8088
+ updateFooterStatus();
7842
8089
  }
7843
8090
  var delegateStatusWidget = {
7844
8091
  setContext(ctx, snapshot) {
7845
8092
  if (ctx.mode !== "tui") return;
7846
- ui = ctx.ui;
8093
+ initFooterStatus(ctx);
8094
+ ui2 = ctx.ui;
7847
8095
  runsSnapshot = snapshot;
7848
8096
  if (!timer) {
7849
8097
  timer = setInterval(refresh, REFRESH_MS);
@@ -7854,11 +8102,12 @@ var delegateStatusWidget = {
7854
8102
  dispose() {
7855
8103
  stopTimer();
7856
8104
  clearWidget();
7857
- ui = void 0;
8105
+ disposeFooterStatus();
8106
+ ui2 = void 0;
7858
8107
  lastRenderKey = "";
7859
8108
  },
7860
8109
  poke() {
7861
- if (ui && !timer) {
8110
+ if (ui2 && !timer) {
7862
8111
  timer = setInterval(refresh, REFRESH_MS);
7863
8112
  timer.unref?.();
7864
8113
  }
@@ -7872,11 +8121,13 @@ function attachWatchdogs(child, hooks, opts) {
7872
8121
  let eofTimer;
7873
8122
  let killGraceTimer;
7874
8123
  let timeoutTimer;
8124
+ let settledGraceTimer;
7875
8125
  const clearTimers = () => {
7876
8126
  if (idleTimer) clearTimeout(idleTimer);
7877
8127
  if (eofTimer) clearTimeout(eofTimer);
7878
8128
  if (killGraceTimer) clearTimeout(killGraceTimer);
7879
8129
  if (timeoutTimer) clearTimeout(timeoutTimer);
8130
+ if (settledGraceTimer) clearTimeout(settledGraceTimer);
7880
8131
  };
7881
8132
  const killByWatchdog = (reason) => {
7882
8133
  if (hooks.isSettled()) return;
@@ -7894,6 +8145,14 @@ function attachWatchdogs(child, hooks, opts) {
7894
8145
  }, opts.killGraceMs);
7895
8146
  killGraceTimer.unref?.();
7896
8147
  };
8148
+ const settledGrace = (graceMs, _killGraceMs, reason) => {
8149
+ if (hooks.isSettled() || settledGraceTimer) return;
8150
+ settledGraceTimer = setTimeout(() => {
8151
+ settledGraceTimer = void 0;
8152
+ killByWatchdog(reason);
8153
+ }, graceMs);
8154
+ settledGraceTimer.unref?.();
8155
+ };
7897
8156
  const poke = () => {
7898
8157
  if (idleTimer) clearTimeout(idleTimer);
7899
8158
  idleTimer = setTimeout(() => killByWatchdog(`no output for ${opts.idleMs / 6e4}m`), opts.idleMs);
@@ -7917,6 +8176,7 @@ function attachWatchdogs(child, hooks, opts) {
7917
8176
  child.stdout?.once("end", onStdoutEnd);
7918
8177
  return {
7919
8178
  poke,
8179
+ settledGrace,
7920
8180
  dispose: () => {
7921
8181
  clearTimers();
7922
8182
  child.stdout?.removeListener("end", onStdoutEnd);
@@ -7925,16 +8185,66 @@ function attachWatchdogs(child, hooks, opts) {
7925
8185
  }
7926
8186
 
7927
8187
  // src/delegate-events.ts
8188
+ function safeNumber(v) {
8189
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
8190
+ }
8191
+ function handleMessageEnd(event) {
8192
+ const msg = event.message;
8193
+ if (!msg || msg.role !== "assistant") return null;
8194
+ const u = msg.usage;
8195
+ if (!u || typeof u !== "object") return null;
8196
+ const raw = u;
8197
+ const input = safeNumber(raw.input);
8198
+ const output = safeNumber(raw.output);
8199
+ const cacheRead = safeNumber(raw.cacheRead);
8200
+ const cacheWrite = safeNumber(raw.cacheWrite);
8201
+ if (input === void 0 && output === void 0 && cacheRead === void 0 && cacheWrite === void 0) return null;
8202
+ const cost = raw.cost;
8203
+ let parsedCost;
8204
+ if (cost && typeof cost === "object") {
8205
+ const c = cost;
8206
+ parsedCost = {
8207
+ input: typeof c.input === "number" ? c.input : 0,
8208
+ output: typeof c.output === "number" ? c.output : 0,
8209
+ cacheRead: typeof c.cacheRead === "number" ? c.cacheRead : 0,
8210
+ cacheWrite: typeof c.cacheWrite === "number" ? c.cacheWrite : 0,
8211
+ total: typeof c.total === "number" ? c.total : 0
8212
+ };
8213
+ } else {
8214
+ parsedCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
8215
+ }
8216
+ return {
8217
+ kind: "usage-update",
8218
+ usage: {
8219
+ input: input ?? 0,
8220
+ output: output ?? 0,
8221
+ cacheRead: cacheRead ?? 0,
8222
+ cacheWrite: cacheWrite ?? 0,
8223
+ cacheWrite1h: safeNumber(raw.cacheWrite1h),
8224
+ reasoning: safeNumber(raw.reasoning),
8225
+ totalTokens: typeof raw.totalTokens === "number" ? raw.totalTokens : 0,
8226
+ cost: parsedCost
8227
+ }
8228
+ };
8229
+ }
7928
8230
  var ThinkingCollector = class {
7929
8231
  constructor(showThinking) {
7930
8232
  this.showThinking = showThinking;
7931
8233
  }
7932
8234
  showThinking;
7933
8235
  buf = "";
8236
+ usage;
7934
8237
  push(delta) {
7935
8238
  this.buf += delta;
7936
8239
  }
7937
- /** Return the segment line to write ("" when empty or disabled), resetting. */
8240
+ process(ev) {
8241
+ if (ev.kind === "thinking-delta") {
8242
+ this.push(ev.delta);
8243
+ }
8244
+ if (ev.kind === "usage-update") {
8245
+ this.usage = ev.usage;
8246
+ }
8247
+ }
7938
8248
  flush() {
7939
8249
  const text = this.buf.trim();
7940
8250
  this.buf = "";
@@ -7942,6 +8252,9 @@ var ThinkingCollector = class {
7942
8252
  return `[thinking] ${text}
7943
8253
  `;
7944
8254
  }
8255
+ getUsage() {
8256
+ return this.usage;
8257
+ }
7945
8258
  };
7946
8259
  function parseEventLine(line) {
7947
8260
  let ev;
@@ -8006,6 +8319,12 @@ function parseEventLine(line) {
8006
8319
  attempt: Number(e.attempt ?? 0)
8007
8320
  };
8008
8321
  }
8322
+ if (e.type === "message_end") {
8323
+ return handleMessageEnd(e);
8324
+ }
8325
+ if (e.type === "agent_settled") {
8326
+ return { kind: "agent-settled" };
8327
+ }
8009
8328
  return null;
8010
8329
  }
8011
8330
  function formatArgs(args) {
@@ -8060,6 +8379,7 @@ function newPortion(text, prev) {
8060
8379
  var MAX_DEPTH = 2;
8061
8380
  var SYNC_TIMEOUT_MS = 5 * 6e4;
8062
8381
  var EOF_GRACE_MS = 1e4;
8382
+ var SETTLED_GRACE_MS = 1e4;
8063
8383
  var IDLE_GRACE_MS = 5 * 6e4;
8064
8384
  var ASYNC_TIMEOUT_MS = 30 * 6e4;
8065
8385
  var KILL_GRACE_MS = 1e4;
@@ -8112,6 +8432,20 @@ Answer the question concisely with clear reasoning. Cite file:line when referenc
8112
8432
  };
8113
8433
  var AGENT_NAMES = Object.keys(AGENTS);
8114
8434
  var runs = /* @__PURE__ */ new Map();
8435
+ var delegateUsageTotal;
8436
+ function addDelegateUsage(u) {
8437
+ delegateUsageTotal = delegateUsageTotal ? accumulateUsage(delegateUsageTotal, u) : u;
8438
+ }
8439
+ function getDelegateUsage() {
8440
+ return delegateUsageTotal;
8441
+ }
8442
+ function resetDelegateUsage() {
8443
+ delegateUsageTotal = void 0;
8444
+ }
8445
+ var delegateDisplayUsage = "separate";
8446
+ function setDelegateDisplayUsage(mode) {
8447
+ delegateDisplayUsage = mode;
8448
+ }
8115
8449
  function runningRunsSnapshot() {
8116
8450
  const out = [];
8117
8451
  for (const r of runs.values()) {
@@ -8131,6 +8465,10 @@ function makeEventApplier(opts, writers) {
8131
8465
  const handleEventLine = (line) => {
8132
8466
  const ev = parseEventLine(line);
8133
8467
  if (!ev) return;
8468
+ if (ev.kind === "usage-update") {
8469
+ opts.onUsage?.(ev.usage);
8470
+ return;
8471
+ }
8134
8472
  if (ev.kind === "thinking-delta") {
8135
8473
  thinking.push(ev.delta);
8136
8474
  return;
@@ -8139,6 +8477,11 @@ function makeEventApplier(opts, writers) {
8139
8477
  flushThinking();
8140
8478
  return;
8141
8479
  }
8480
+ if (ev.kind === "agent-settled") {
8481
+ flushThinking();
8482
+ opts.onSettled?.();
8483
+ return;
8484
+ }
8142
8485
  if (ev.kind === "reply-delta") {
8143
8486
  flushThinking();
8144
8487
  replyText += ev.delta;
@@ -8219,6 +8562,37 @@ var WaitParams = typebox_exports.Object({
8219
8562
  })
8220
8563
  )
8221
8564
  });
8565
+ function safeCost(u) {
8566
+ if (u.cost.input > 0 || u.cost.output > 0 || u.cost.cacheRead > 0 || u.cost.cacheWrite > 0 || u.cost.total > 0) {
8567
+ return {
8568
+ input: u.cost.input > 0 ? u.cost.input : 0,
8569
+ output: u.cost.output > 0 ? u.cost.output : 0,
8570
+ cacheRead: u.cost.cacheRead > 0 ? u.cost.cacheRead : 0,
8571
+ cacheWrite: u.cost.cacheWrite > 0 ? u.cost.cacheWrite : 0,
8572
+ total: u.cost.total > 0 ? u.cost.total : 0
8573
+ };
8574
+ }
8575
+ return void 0;
8576
+ }
8577
+ function accumulateUsage(a, b) {
8578
+ if (!a) return b;
8579
+ return {
8580
+ input: a.input + b.input,
8581
+ output: a.output + b.output,
8582
+ cacheRead: a.cacheRead + b.cacheRead,
8583
+ cacheWrite: a.cacheWrite + b.cacheWrite,
8584
+ cacheWrite1h: (a.cacheWrite1h ?? 0) + (b.cacheWrite1h ?? 0),
8585
+ reasoning: (a.reasoning ?? 0) + (b.reasoning ?? 0),
8586
+ totalTokens: a.totalTokens + b.totalTokens,
8587
+ cost: {
8588
+ input: a.cost.input + b.cost.input,
8589
+ output: a.cost.output + b.cost.output,
8590
+ cacheRead: a.cost.cacheRead + b.cost.cacheRead,
8591
+ cacheWrite: a.cost.cacheWrite + b.cost.cacheWrite,
8592
+ total: a.cost.total + b.cost.total
8593
+ }
8594
+ };
8595
+ }
8222
8596
  var agentListLine = (name) => {
8223
8597
  const def = AGENTS[name];
8224
8598
  if (!def) return "";
@@ -8277,6 +8651,38 @@ function injectedWaitMessage(run, runId, remainingLine) {
8277
8651
  const fileLine = file ? ` If you need details, read the result file: \`${file}\`.` : "";
8278
8652
  return `Delegate \`${runId}\` already delivered its result via a system notification when it finished \u2014 no need to wait on it again.${remainingLine}${fileLine}`;
8279
8653
  }
8654
+ function buildWaitResult(run, content, mode = "separate", contentType = "text") {
8655
+ if (run.usage && !run.usageReported) {
8656
+ run.usageReported = true;
8657
+ if (mode === "merged") {
8658
+ const cost = safeCost(run.usage);
8659
+ return {
8660
+ details: void 0,
8661
+ content: [{ type: contentType, text: content }],
8662
+ usage: { ...run.usage, cost: cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }
8663
+ };
8664
+ } else {
8665
+ addDelegateUsage(run.usage);
8666
+ }
8667
+ }
8668
+ return { details: void 0, content: [{ type: contentType, text: content }] };
8669
+ }
8670
+ function buildCancelResult(run, content, mode = "separate") {
8671
+ if (run.usage && !run.usageReported) {
8672
+ run.usageReported = true;
8673
+ if (mode === "merged") {
8674
+ const cost = safeCost(run.usage);
8675
+ return {
8676
+ details: void 0,
8677
+ content: [{ type: "text", text: content }],
8678
+ usage: { ...run.usage, cost: cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }
8679
+ };
8680
+ } else {
8681
+ addDelegateUsage(run.usage);
8682
+ }
8683
+ }
8684
+ return { details: void 0, content: [{ type: "text", text: content }] };
8685
+ }
8280
8686
  function makeDelegateWaitTool(_pi) {
8281
8687
  return {
8282
8688
  name: "acp_delegate_wait",
@@ -8294,21 +8700,22 @@ function makeDelegateWaitTool(_pi) {
8294
8700
  if (!run) {
8295
8701
  return { details: void 0, content: [{ type: "text", text: `No delegate run with runId \`${args.runId}\`. It may have already been reported or never existed.` }] };
8296
8702
  }
8703
+ const displayMode = delegateDisplayUsage;
8297
8704
  if (run.status === "cancelled") {
8298
8705
  run.consumed = true;
8299
- return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` was cancelled (no result).${remainingLineForWait(args.runId)}` }] };
8706
+ return buildWaitResult(run, `Delegate \`${args.runId}\` was cancelled (no result).${remainingLineForWait(args.runId)}`, displayMode);
8300
8707
  }
8301
8708
  if (run.status !== "running") {
8302
8709
  const dedup = injectedWaitMessage(run, args.runId, remainingLineForWait(args.runId));
8303
8710
  if (dedup) {
8304
8711
  run.consumed = true;
8305
- return { details: void 0, content: [{ type: "text", text: dedup }] };
8712
+ return buildWaitResult(run, dedup, displayMode);
8306
8713
  }
8307
8714
  run.consumed = true;
8308
8715
  if (!run.result) {
8309
- return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` finished but no result is available (persist error).` }] };
8716
+ return buildWaitResult(run, `Delegate \`${args.runId}\` finished but no result is available (persist error).`, displayMode);
8310
8717
  }
8311
- return { details: void 0, content: [{ type: "text", text: formatRunResult(run) }] };
8718
+ return buildWaitResult(run, formatRunResult(run), displayMode);
8312
8719
  }
8313
8720
  const timeoutMs = Math.min(
8314
8721
  Math.max(args.timeout ?? WAIT_TIMEOUT_MS_DEFAULT, 1e3),
@@ -8320,28 +8727,28 @@ function makeDelegateWaitTool(_pi) {
8320
8727
  return new Promise((resolve2) => {
8321
8728
  let settled = false;
8322
8729
  let timer2;
8323
- const finish = (text) => {
8730
+ const finish = (result) => {
8324
8731
  if (settled) return;
8325
8732
  settled = true;
8326
8733
  run.waiter = void 0;
8327
8734
  if (timer2) clearTimeout(timer2);
8328
8735
  signal?.removeEventListener("abort", onAbort);
8329
- resolve2({ details: void 0, content: [{ type: "text", text }] });
8736
+ resolve2(result);
8330
8737
  };
8331
8738
  const onAbort = () => {
8332
- finish(`Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.`);
8739
+ finish({ details: void 0, content: [{ type: "text", text: `Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.` }] });
8333
8740
  };
8334
8741
  run.waiter = () => {
8335
8742
  run.consumed = true;
8336
8743
  if (run.status === "cancelled") {
8337
- finish(`Delegate \`${run.runId}\` was cancelled (no result).${remainingLineForWait(run.runId)}`);
8744
+ finish(buildWaitResult(run, `Delegate \`${run.runId}\` was cancelled (no result).${remainingLineForWait(run.runId)}`, displayMode));
8338
8745
  return;
8339
8746
  }
8340
- finish(formatRunResult(run));
8747
+ finish(buildWaitResult(run, formatRunResult(run), displayMode));
8341
8748
  };
8342
8749
  signal?.addEventListener("abort", onAbort);
8343
8750
  timer2 = setTimeout(
8344
- () => finish(`Failed: delegate \`${args.runId}\` result not ready after ${Math.round(timeoutMs / 1e3)}s. Do NOT keep waiting or retry \u2014 go do other work now. The run continues in the background and a completion notification (with the result file path) will be injected into the chat when it finishes.`),
8751
+ () => finish({ details: void 0, content: [{ type: "text", text: `Failed: delegate \`${args.runId}\` result not ready after ${Math.round(timeoutMs / 1e3)}s. Do NOT keep waiting or retry \u2014 go do other work now. The run continues in the background and a completion notification (with the result file path) will be injected into the chat when it finishes.` }] }),
8345
8752
  timeoutMs
8346
8753
  );
8347
8754
  });
@@ -8360,16 +8767,10 @@ function makeDelegateCancelTool(_pi) {
8360
8767
  const { runId } = params;
8361
8768
  const run = runs.get(runId);
8362
8769
  if (!run) {
8363
- return {
8364
- details: void 0,
8365
- content: [{ type: "text", text: `Unknown runId "${runId}".` }]
8366
- };
8770
+ return { details: void 0, content: [{ type: "text", text: `Unknown runId "${runId}".` }] };
8367
8771
  }
8368
8772
  if (run.status !== "running") {
8369
- return {
8370
- details: void 0,
8371
- content: [{ type: "text", text: `Run ${runId} already ${run.status} (no action).` }]
8372
- };
8773
+ return buildCancelResult(run, `Run ${runId} already ${run.status} (no action).`);
8373
8774
  }
8374
8775
  run.status = "cancelled";
8375
8776
  run.consumed = true;
@@ -8380,10 +8781,8 @@ function makeDelegateCancelTool(_pi) {
8380
8781
  logError("delegate", { event: "cancel-kill-error", runId, error: String(err) });
8381
8782
  }
8382
8783
  delegateStatusWidget.poke();
8383
- return {
8384
- details: void 0,
8385
- content: [{ type: "text", text: `Cancelled ${runId} (${run.agent}).` }]
8386
- };
8784
+ const displayMode = delegateDisplayUsage;
8785
+ return buildCancelResult(run, `Cancelled ${runId} (${run.agent}).`, displayMode);
8387
8786
  }
8388
8787
  };
8389
8788
  }
@@ -8432,11 +8831,11 @@ async function runDelegate(pi, args, ctx, signal) {
8432
8831
  {
8433
8832
  isSettled: () => settled || run.status !== "running",
8434
8833
  onKill: (reason) => {
8435
- run.timedOut = reason;
8834
+ if (!run.agentSettled) run.timedOut = reason;
8436
8835
  debug.event("delegate-watchdog", { runId, reason });
8437
8836
  },
8438
8837
  onEofGrace: () => {
8439
- run.timedOut = "output ended but process did not exit";
8838
+ if (!run.agentSettled) run.timedOut = "output ended but process did not exit";
8440
8839
  debug.event("delegate-eof-grace", { runId, ms: EOF_GRACE_MS });
8441
8840
  }
8442
8841
  },
@@ -8453,7 +8852,16 @@ async function runDelegate(pi, args, ctx, signal) {
8453
8852
  });
8454
8853
  let stdoutBuf = "";
8455
8854
  const applier = makeEventApplier(
8456
- { showThinking: args.showThinking === true },
8855
+ {
8856
+ showThinking: args.showThinking === true,
8857
+ onUsage: (u) => {
8858
+ run.usage = accumulateUsage(run.usage, u);
8859
+ },
8860
+ onSettled: () => {
8861
+ run.agentSettled = true;
8862
+ watchdog.settledGrace(SETTLED_GRACE_MS, KILL_GRACE_MS, "agent settled but process did not exit");
8863
+ }
8864
+ },
8457
8865
  { reply: replyStream, activity: activityStream }
8458
8866
  );
8459
8867
  child.stdout?.on("data", (c) => {
@@ -8527,7 +8935,11 @@ async function runDelegate(pi, args, ctx, signal) {
8527
8935
  delegateStatusWidget.poke();
8528
8936
  return;
8529
8937
  }
8530
- const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut);
8938
+ const mode = delegateDisplayUsage;
8939
+ const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut, run.usage, mode, run.usageReported);
8940
+ if (run.usage && !run.usageReported && (mode === "separate" || injected)) {
8941
+ run.usageReported = true;
8942
+ }
8531
8943
  run.injected = injected;
8532
8944
  debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
8533
8945
  logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected, outLen: output.length, file: file2 });
@@ -8648,7 +9060,7 @@ function formatSyncResult(agent, runId, task, r, file) {
8648
9060
  const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
8649
9061
  return formatPayload(header, file, task, body);
8650
9062
  }
8651
- function injectResult(pi, agent, runId, task, code, file, timedOut) {
9063
+ function injectResult(pi, agent, runId, task, code, file, timedOut, usage, mode = "separate", usageAlreadyReported) {
8652
9064
  const send = pi.sendUserMessage;
8653
9065
  if (typeof send !== "function") {
8654
9066
  debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
@@ -8659,7 +9071,37 @@ function injectResult(pi, agent, runId, task, code, file, timedOut) {
8659
9071
  const remaining = Array.from(runs.values()).filter((r) => r.status === "running").length;
8660
9072
  const remainingLine = remaining > 0 ? ` ${remaining} delegate${remaining === 1 ? " is" : "s are"} still running; keep doing other work and their notifications will arrive as they finish.` : " No delegates are currently running.";
8661
9073
  const timeoutNote = timedOut ? ` (timed out: ${timedOut})` : "";
8662
- const header = `[acp_delegate ${status}] **${agent}** (runId \`${runId}\`, exit ${code ?? "?"})${timeoutNote}${remainingLine} This is an automated system notification, NOT a user message. Read the result file if you need the details, then continue your original task; do not treat this as a new user request.`;
9074
+ let usageNote = "";
9075
+ if (mode === "separate") {
9076
+ if (usage && !usageAlreadyReported) {
9077
+ addDelegateUsage(usage);
9078
+ }
9079
+ const totalUsage = getDelegateUsage();
9080
+ if (totalUsage) {
9081
+ const cost = totalUsage.cost.total;
9082
+ const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
9083
+ usageNote = `
9084
+
9085
+ \u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500
9086
+ Tokens: ${totalUsage.input.toLocaleString()} in, ${totalUsage.output.toLocaleString()} out (${totalUsage.totalTokens.toLocaleString()} total)${costStr}`;
9087
+ }
9088
+ } else if (usage) {
9089
+ const lines = [];
9090
+ if (usage.totalTokens) lines.push(`tokens=${usage.totalTokens.toLocaleString()}`);
9091
+ if (usage.input || usage.output) lines.push(`in=${usage.input.toLocaleString()} out=${usage.output.toLocaleString()}`);
9092
+ if (usage.cacheRead) lines.push(`cache_read=${usage.cacheRead.toLocaleString()}`);
9093
+ if (usage.cacheWrite) lines.push(`cache_write=${usage.cacheWrite.toLocaleString()}`);
9094
+ if (usage.cost && typeof usage.cost === "object") {
9095
+ const c = usage.cost;
9096
+ if (typeof c.total === "number" && c.total > 0) {
9097
+ lines.push(`cost=$${c.total.toFixed(4)}`);
9098
+ } else if (typeof c.input === "number" && c.input > 0 || typeof c.output === "number" && c.output > 0) {
9099
+ lines.push(`cost=${JSON.stringify(c)}`);
9100
+ }
9101
+ }
9102
+ if (lines.length) usageNote = ` Usage: ${lines.join(", ")}.`;
9103
+ }
9104
+ const header = `[acp_delegate ${status}] **${agent}** (runId \`${runId}\`, exit ${code ?? "?"})${timeoutNote}${remainingLine}${usageNote} This is an automated system notification, NOT a user message. Read the result file if you need the details, then continue your original task; do not treat this as a new user request.`;
8663
9105
  const text = formatPayload(header, file, task);
8664
9106
  try {
8665
9107
  send.call(pi, text, { deliverAs: "followUp" });
@@ -8710,6 +9152,90 @@ function truncate2(s, n) {
8710
9152
  return s.slice(0, n - 1) + "\u2026";
8711
9153
  }
8712
9154
 
9155
+ // src/status-tool.ts
9156
+ var StatusParams = typebox_exports.Object({
9157
+ scope: typebox_exports.Optional(typebox_exports.Union([typebox_exports.Literal("compressed"), typebox_exports.Literal("uncompressed")], { description: '"compressed" = drill into blocks; "uncompressed" = show visible messages/ranges. Default: overview.' })),
9158
+ view: typebox_exports.Optional(typebox_exports.Union([typebox_exports.Literal("ranges"), typebox_exports.Literal("messages")], { description: 'For uncompressed scope: "ranges" (default) or "messages" (per-message listing).' })),
9159
+ tool: typebox_exports.Optional(typebox_exports.String({ description: 'Filter by tool name (e.g. "bash", "read"). Only for uncompressed+messages.' })),
9160
+ sort: typebox_exports.Optional(typebox_exports.Union([typebox_exports.Literal("size"), typebox_exports.Literal("time"), typebox_exports.Literal("tool"), typebox_exports.Literal("age")], { description: "Sort order. Default: size." })),
9161
+ limit: typebox_exports.Optional(typebox_exports.Number({ description: "Max items to show (default: 30)." }))
9162
+ });
9163
+ function makeStatusTool(runtime) {
9164
+ return {
9165
+ name: "acp_status",
9166
+ label: "ACP Status",
9167
+ description: "Context status: overview, compressed blocks, or uncompressed ranges/messages. No args = overview + totals + compressible ranges. scope:'uncompressed' + view:'messages' for per-message listing. scope:'compressed' for block drilldown.",
9168
+ promptSnippet: 'acp_status({}) or acp_status({ scope: "uncompressed", view: "messages" })',
9169
+ promptGuidelines: [
9170
+ "Call with no args for a quick overview of context usage.",
9171
+ "Use scope:'uncompressed' to find the largest compressible ranges.",
9172
+ "Use scope:'compressed' to inspect existing compression blocks."
9173
+ ],
9174
+ parameters: StatusParams,
9175
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9176
+ let result;
9177
+ try {
9178
+ result = await handleStatus(params, runtime, ctx);
9179
+ } catch (e) {
9180
+ logThrow("status", e, { sid: ctx.sessionManager.getSessionId(), scope: params.scope ?? null });
9181
+ throw e;
9182
+ }
9183
+ return { details: void 0, content: [{ type: "text", text: result }] };
9184
+ }
9185
+ };
9186
+ }
9187
+ async function handleStatus(args, runtime, ctx) {
9188
+ const { state, coreMessages } = await runtime.stateFor(ctx);
9189
+ const config = runtime.configFor(ctx);
9190
+ const tokenCount = estimateTokens(coreMessages, collectCoveredMessageIds(state));
9191
+ const realUsage = ctx.getContextUsage?.();
9192
+ const turn = runtime.core.processTurn({
9193
+ messages: coreMessages,
9194
+ state,
9195
+ config,
9196
+ tokenCount: realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : tokenCount
9197
+ });
9198
+ const processed = turn.messages;
9199
+ const base = buildStatusReport(turn.state, processed, defaultCountTokens, {
9200
+ scope: args.scope,
9201
+ view: args.view,
9202
+ tool: args.tool,
9203
+ sort: args.sort,
9204
+ limit: args.limit
9205
+ });
9206
+ if (args.scope) return base;
9207
+ const nudge = turn.nudge;
9208
+ const ranges = nudge?.compressibleRanges ?? [];
9209
+ const protectedRanges = nudge?.protectedRanges ?? [];
9210
+ const extra = [];
9211
+ if (nudge) {
9212
+ extra.push("");
9213
+ extra.push(
9214
+ nudge.shouldInject ? `Nudge: ACTIVE \u2014 ${nudge.reason}` : `Nudge: idle \u2014 ${nudge.reason}`
9215
+ );
9216
+ }
9217
+ if (ranges.length > 0 || protectedRanges.length > 0) {
9218
+ extra.push("");
9219
+ extra.push(formatRanges(ranges, protectedRanges));
9220
+ }
9221
+ const delegateUsage = getDelegateUsage();
9222
+ if (delegateUsage && delegateUsage.totalTokens > 0) {
9223
+ extra.push("");
9224
+ const cost = delegateUsage.cost.total;
9225
+ const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
9226
+ extra.push("\u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500");
9227
+ extra.push(`Tokens: ${delegateUsage.input.toLocaleString()} in, ${delegateUsage.output.toLocaleString()} out (${delegateUsage.totalTokens.toLocaleString()} total)${costStr}`);
9228
+ } else if (resolveDelegate(runtime.adapter).displayUsage === "merged") {
9229
+ extra.push("");
9230
+ extra.push("merged mode: delegate usage is included in main session totals.");
9231
+ } else {
9232
+ extra.push("");
9233
+ extra.push("Delegate usage: none this session.");
9234
+ }
9235
+ return extra.length > 0 ? `${base}
9236
+ ${extra.join("\n")}` : base;
9237
+ }
9238
+
8713
9239
  // src/compat.ts
8714
9240
  function normalizeSystemPrompt(input) {
8715
9241
  if (input === void 0) return "";
@@ -8740,7 +9266,7 @@ function makeCommands(runtime) {
8740
9266
  {
8741
9267
  name: "acp-status",
8742
9268
  options: {
8743
- description: "Detailed ACP status (block tiers, token breakdown).",
9269
+ description: "Detailed ACP status (block tiers, token breakdown, delegate usage).",
8744
9270
  handler: async (_args, ctx) => ctx.ui.notify(await statusReport(runtime, ctx))
8745
9271
  }
8746
9272
  },
@@ -8795,9 +9321,7 @@ ${text}`);
8795
9321
  ];
8796
9322
  }
8797
9323
  function fmtTokens(n) {
8798
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
8799
- if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
8800
- return String(n);
9324
+ return formatCompactTokens(n);
8801
9325
  }
8802
9326
  function bar(value, total, width = 20) {
8803
9327
  if (total === 0) return "";
@@ -8822,7 +9346,7 @@ async function statusReport(runtime, ctx) {
8822
9346
  const activeBlocksList = state.blocks.filter((b) => b.active);
8823
9347
  const totalBlocksList = state.blocks;
8824
9348
  const lines = [];
8825
- const versionStr = "0.1.34" ? `billion-context-pi@${"0.1.34"}` : "";
9349
+ const versionStr = "0.1.36" ? `billion-context-pi@${"0.1.36"}` : "";
8826
9350
  lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
8827
9351
  lines.push("\u2502 ACP Context Analysis \u2502");
8828
9352
  lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
@@ -8885,6 +9409,15 @@ async function statusReport(runtime, ctx) {
8885
9409
  lines.push("Blocks: none (nothing compressed yet)");
8886
9410
  }
8887
9411
  lines.push("");
9412
+ const delegateUsage = getDelegateUsage();
9413
+ if (delegateUsage && delegateUsage.totalTokens > 0) {
9414
+ lines.push("");
9415
+ const cost = delegateUsage.cost.total;
9416
+ const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
9417
+ lines.push("\u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500");
9418
+ lines.push(`Tokens: ${delegateUsage.input.toLocaleString()} in, ${delegateUsage.output.toLocaleString()} out (${delegateUsage.totalTokens.toLocaleString()} total)${costStr}`);
9419
+ }
9420
+ lines.push("");
8888
9421
  lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
8889
9422
  return lines.join("\n");
8890
9423
  }
@@ -9189,7 +9722,7 @@ async function checkForUpdate(autoUpdate, notify) {
9189
9722
  const data = await res.json();
9190
9723
  const latest = data.version;
9191
9724
  if (!latest) return;
9192
- const current = runtimeVersion ?? "0.1.34";
9725
+ const current = runtimeVersion ?? "0.1.36";
9193
9726
  const hasUpdate = isNewer(latest, current);
9194
9727
  debug.event("update-check", {
9195
9728
  current,
@@ -9382,7 +9915,16 @@ async function loadUserConfig(cwd) {
9382
9915
  function join8(...parts) {
9383
9916
  return path3.join(...parts);
9384
9917
  }
9385
- var KNOWN = /* @__PURE__ */ new Set(["debug", "autoUpdate", "modelContextLimit", "delegate", "toolBashDefaultTimeout", "toolOutputMaxBytes"]);
9918
+ var KNOWN = /* @__PURE__ */ new Set([
9919
+ "debug",
9920
+ "autoUpdate",
9921
+ "modelContextLimit",
9922
+ "toolBashDefaultTimeout",
9923
+ "toolOutputMaxBytes",
9924
+ "delegate",
9925
+ "compress",
9926
+ "displayUsage"
9927
+ ]);
9386
9928
  function pickKnown(parsed) {
9387
9929
  const out = {};
9388
9930
  for (const [k, v] of Object.entries(parsed)) {
@@ -9394,8 +9936,6 @@ function applyUserConfig(adapter, user) {
9394
9936
  return {
9395
9937
  ...adapter,
9396
9938
  ...user,
9397
- // coreOverrides / protectedTools / preserveRecentMessages are not overridable
9398
- // from acp.json (keep them from the factory config).
9399
9939
  coreOverrides: adapter.coreOverrides,
9400
9940
  protectedTools: adapter.protectedTools,
9401
9941
  preserveRecentMessages: adapter.preserveRecentMessages
@@ -9428,16 +9968,19 @@ function wireSessionLifecycle(pi, runtime) {
9428
9968
  pi.on("session_start", async (_event, ctx) => {
9429
9969
  runtime.store.invalidate();
9430
9970
  runtime.clearNudgeTracking();
9971
+ resetDelegateUsage();
9972
+ setDelegateDisplayUsage("separate");
9431
9973
  const sid = ctx.sessionManager.getSessionId();
9432
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.34" : null });
9974
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.36" : null });
9433
9975
  try {
9434
9976
  const user = await loadUserConfig(ctx.cwd);
9435
9977
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
9978
+ setDelegateDisplayUsage(resolveDelegate(runtime.adapter).displayUsage);
9436
9979
  if (runtime.adapter.debug !== void 0) setDebugEnabled(runtime.adapter.debug);
9437
9980
  } catch (e) {
9438
9981
  logThrow("config", e, { sid, phase: "session_start" });
9439
9982
  }
9440
- if (runtime.adapter.delegate !== false) {
9983
+ if (resolveDelegate(runtime.adapter).enabled) {
9441
9984
  pi.registerTool(makeDelegateTool(pi));
9442
9985
  pi.registerTool(makeDelegateWaitTool(pi));
9443
9986
  pi.registerTool(makeDelegateCancelTool(pi));