billion-context-pi 0.1.34 → 0.1.35

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
@@ -2416,7 +2416,9 @@ function resolveConfig(adapter, liveContextLimit) {
2416
2416
  }
2417
2417
 
2418
2418
  // src/messages.ts
2419
- var REF_TAG = new RegExp("^(?:<acp\\s[^>]*>m\\d{5}</acp>|\\[m\\d{1,5}\\])\\s?\\n?");
2419
+ var REF_TAG_SOURCE = "(?:<acp\\s[^>]*>m\\d{5}</acp>|\\[m\\d{1,5}\\])";
2420
+ var REF_TAG = new RegExp(`^${REF_TAG_SOURCE}\\s?\\n?`);
2421
+ var TRAILING_REF_TAG = new RegExp(`\\n*${REF_TAG_SOURCE}\\s*$`);
2420
2422
  function entriesToCoreMessages(entries) {
2421
2423
  const out = [];
2422
2424
  for (const entry of entries) {
@@ -2494,17 +2496,74 @@ function stringifyArgs(args) {
2494
2496
  return safeStringify(args);
2495
2497
  }
2496
2498
  function extractText(content) {
2497
- if (typeof content === "string") return content.replace(REF_TAG, "");
2499
+ if (typeof content === "string") return stripRefTag(content);
2498
2500
  if (!Array.isArray(content)) return "";
2499
2501
  const parts = [];
2500
2502
  for (const block of content) {
2501
2503
  const b = block;
2502
- if (b.type === "text" && typeof b.text === "string") {
2503
- parts.push(b.text.replace(REF_TAG, ""));
2504
- }
2504
+ if (b.type === "text" && typeof b.text === "string") parts.push(stripRefTag(b.text));
2505
2505
  }
2506
2506
  return parts.join("\n");
2507
2507
  }
2508
+ function stripRefTag(text) {
2509
+ return text.replace(REF_TAG, "").replace(TRAILING_REF_TAG, "");
2510
+ }
2511
+ function messageIdentity(message) {
2512
+ return JSON.stringify(normalizeIdentityValue(message, true));
2513
+ }
2514
+ function messageRef(message) {
2515
+ if (message === null || typeof message !== "object" || !("content" in message)) return void 0;
2516
+ const content = message.content;
2517
+ const texts = typeof content === "string" ? [content] : Array.isArray(content) ? content.flatMap((block) => {
2518
+ const value = block;
2519
+ return value.type === "text" && typeof value.text === "string" ? [value.text] : [];
2520
+ }) : [];
2521
+ for (const text of texts) {
2522
+ const tag = text.match(REF_TAG)?.[0] ?? text.match(TRAILING_REF_TAG)?.[0];
2523
+ const ref = tag?.match(/m\d{1,5}/)?.[0];
2524
+ if (ref) return ref;
2525
+ }
2526
+ return void 0;
2527
+ }
2528
+ function normalizeIdentityValue(value, message = false) {
2529
+ if (Array.isArray(value)) {
2530
+ return value.flatMap((item) => {
2531
+ if (!item || typeof item !== "object") return [normalizeIdentityValue(item)];
2532
+ const block = item;
2533
+ if (block.type === "text" && typeof block.text === "string") {
2534
+ const stripped = stripRefTag(block.text);
2535
+ if (block.text !== stripped && stripped === "") return [];
2536
+ }
2537
+ return [normalizeIdentityValue(item)];
2538
+ });
2539
+ }
2540
+ if (value === null || typeof value !== "object") return value;
2541
+ const out = {};
2542
+ for (const key of Object.keys(value).sort()) {
2543
+ if (message && key === "timestamp") continue;
2544
+ const item = value[key];
2545
+ if (message && key === "content" && typeof item === "string") {
2546
+ out[key] = [{ text: stripRefTag(item), type: "text" }];
2547
+ } else if (key === "text" && typeof item === "string" && value.type === "text") {
2548
+ out[key] = stripRefTag(item);
2549
+ } else {
2550
+ out[key] = normalizeIdentityValue(item);
2551
+ }
2552
+ }
2553
+ return out;
2554
+ }
2555
+ var TRUNCATION_MARKER2 = "[truncated for context space]";
2556
+ function matchesStoredText(stored, visible) {
2557
+ const marker = `...${TRUNCATION_MARKER2} \u2014 original ~`;
2558
+ const markerStart = visible.indexOf(marker);
2559
+ if (markerStart < 2 || visible.slice(markerStart - 2, markerStart) !== "\n\n") return false;
2560
+ const suffixMarker = " tokens]...\n\n";
2561
+ const suffixStart = visible.indexOf(suffixMarker, markerStart + marker.length);
2562
+ if (suffixStart < 0 || !/^\d+$/.test(visible.slice(markerStart + marker.length, suffixStart))) return false;
2563
+ const prefix = visible.slice(0, markerStart - 2);
2564
+ const suffix = visible.slice(suffixStart + suffixMarker.length);
2565
+ return prefix.length > 0 && suffix.length > 0 && stored.startsWith(prefix) && stored.endsWith(suffix);
2566
+ }
2508
2567
  function allToolCalls(content) {
2509
2568
  if (!Array.isArray(content)) return [];
2510
2569
  const calls = [];
@@ -2598,11 +2657,11 @@ function patchRefTag(original, core) {
2598
2657
  let injected = false;
2599
2658
  for (let i = newBlocks.length - 1; i >= 0; i--) {
2600
2659
  const b = newBlocks[i];
2601
- if (b?.type === "text" && typeof b.text === "string") {
2660
+ if (b?.type === "text" && typeof b.text === "string" && b.text.length > 0) {
2602
2661
  const baseText = b.text.replace(/\n*$/, "");
2603
- newBlocks[i] = { ...b, text: baseText.length > 0 ? `${baseText}
2662
+ newBlocks[i] = { ...b, text: `${baseText}
2604
2663
 
2605
- ${tag}` : tag };
2664
+ ${tag}` };
2606
2665
  injected = true;
2607
2666
  break;
2608
2667
  }
@@ -2637,8 +2696,8 @@ function peelRefTagBlocks(blocks) {
2637
2696
  for (const block of blocks) {
2638
2697
  const b = block;
2639
2698
  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 });
2699
+ const stripped = stripRefTag(b.text);
2700
+ if (stripped.length > 0 || b.text.length === 0) out.push({ ...b, text: stripped });
2642
2701
  } else {
2643
2702
  out.push(block);
2644
2703
  }
@@ -2735,51 +2794,127 @@ function stateFileFor(sessionFile) {
2735
2794
  if (sessionFile) return sessionFile + STATE_SUFFIX;
2736
2795
  return null;
2737
2796
  }
2797
+ async function readParentSessionPath(sessionFile) {
2798
+ try {
2799
+ const handle = await fs.open(sessionFile, "r");
2800
+ try {
2801
+ const buf = Buffer.alloc(65536);
2802
+ const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
2803
+ if (bytesRead === 0) return void 0;
2804
+ const firstLine = buf.subarray(0, bytesRead).toString("utf8").split("\n")[0] ?? "";
2805
+ if (!firstLine.startsWith("{")) return void 0;
2806
+ const header = JSON.parse(firstLine);
2807
+ return typeof header.parentSession === "string" ? header.parentSession : void 0;
2808
+ } finally {
2809
+ await handle.close();
2810
+ }
2811
+ } catch (e) {
2812
+ const code = e.code;
2813
+ if (code !== "ENOENT") {
2814
+ logWarn("state", { event: "read-parent-header-failed", file: sessionFile, error: e instanceof Error ? e.message : String(e) });
2815
+ }
2816
+ return void 0;
2817
+ }
2818
+ }
2819
+ function cacheKey(sessionFile, sessionId) {
2820
+ return sessionFile ? `file:${sessionFile}` : `session:${sessionId}`;
2821
+ }
2738
2822
  var SessionStateStore = class {
2739
- cache = null;
2740
- loadedKey = null;
2741
- async load(sessionFile, _sessionId) {
2823
+ cache = /* @__PURE__ */ new Map();
2824
+ async load(sessionFile, sessionId) {
2742
2825
  const file = stateFileFor(sessionFile);
2743
- if (file && this.loadedKey === file && this.cache) return this.cache;
2826
+ const key = cacheKey(sessionFile, sessionId);
2827
+ const cached = this.cache.get(key);
2828
+ if (cached) return cached.state;
2744
2829
  let state = createInitialState();
2830
+ let liveRefOrigins = [];
2745
2831
  if (file) {
2746
2832
  try {
2747
2833
  const raw = await fs.readFile(file, "utf8");
2748
2834
  const parsed = JSON.parse(raw);
2749
- if (parsed && Array.isArray(parsed.blocks)) state = mergeInitialState(parsed);
2835
+ if (parsed && Array.isArray(parsed.blocks)) {
2836
+ state = mergeInitialState(parsed);
2837
+ liveRefOrigins = parseLiveRefOrigins(parsed.liveRefOrigins);
2838
+ }
2750
2839
  } catch (e) {
2751
2840
  const code = e.code;
2752
2841
  if (code !== "ENOENT") {
2753
2842
  logWarn("state", { event: "load-failed", file, error: e instanceof Error ? e.message : String(e) });
2754
2843
  }
2755
2844
  }
2845
+ if (state.blocks.length === 0 && sessionFile) {
2846
+ const parentState = await this.tryLoadParentState(sessionFile);
2847
+ if (parentState) state = parentState;
2848
+ }
2756
2849
  }
2757
- this.cache = state;
2758
- this.loadedKey = file;
2850
+ this.cache.set(key, { state, liveRefOrigins });
2759
2851
  return state;
2760
2852
  }
2761
- async save(state, sessionFile, _sessionId) {
2853
+ async save(state, sessionFile, sessionId) {
2762
2854
  const file = stateFileFor(sessionFile);
2763
2855
  if (!file) return;
2764
- this.cache = state;
2765
- this.loadedKey = file;
2856
+ const key = cacheKey(sessionFile, sessionId);
2857
+ const liveRefOrigins = this.cache.get(key)?.liveRefOrigins ?? [];
2858
+ this.cache.set(key, { state, liveRefOrigins });
2766
2859
  const dir = path2.dirname(file);
2767
2860
  await fs.mkdir(dir, { recursive: true }).catch((e) => {
2768
2861
  logError("state", { event: "save-mkdir-failed", dir, error: e instanceof Error ? e.message : String(e) });
2769
2862
  });
2770
2863
  const tmp = path2.join(dir, `.acp-tmp-${path2.basename(file)}`);
2771
2864
  try {
2772
- await fs.writeFile(tmp, JSON.stringify(state), "utf8");
2865
+ await fs.writeFile(tmp, JSON.stringify({ ...state, liveRefOrigins }), "utf8");
2773
2866
  await fs.rename(tmp, file);
2774
2867
  } catch (e) {
2775
2868
  logError("state", { event: "save-failed", file, error: e instanceof Error ? e.message : String(e) });
2776
2869
  }
2777
2870
  }
2871
+ getLiveRefOrigins(sessionFile, sessionId) {
2872
+ return [...this.cache.get(cacheKey(sessionFile, sessionId))?.liveRefOrigins ?? []];
2873
+ }
2874
+ setLiveRefOrigins(sessionFile, sessionId, origins) {
2875
+ const key = cacheKey(sessionFile, sessionId);
2876
+ const slot = this.cache.get(key);
2877
+ if (slot) this.cache.set(key, { state: slot.state, liveRefOrigins: [...origins] });
2878
+ }
2778
2879
  invalidate() {
2779
- this.cache = null;
2780
- this.loadedKey = null;
2880
+ this.cache.clear();
2881
+ }
2882
+ async tryLoadParentState(sessionFile) {
2883
+ const MAX_CHAIN_DEPTH = 8;
2884
+ let current = sessionFile;
2885
+ for (let depth = 0; depth < MAX_CHAIN_DEPTH; depth++) {
2886
+ const parentJsonl = await readParentSessionPath(current);
2887
+ if (!parentJsonl) return void 0;
2888
+ const parentAcp = stateFileFor(parentJsonl);
2889
+ if (!parentAcp) return void 0;
2890
+ try {
2891
+ const raw = await fs.readFile(parentAcp, "utf8");
2892
+ const parsed = JSON.parse(raw);
2893
+ if (parsed && Array.isArray(parsed.blocks) && parsed.blocks.length > 0) {
2894
+ logInfo("state", { event: "inherited-parent-state", file: parentAcp, depth, blocks: parsed.blocks.length, tokensCompressed: parsed.stats?.tokensCompressed ?? 0 });
2895
+ return mergeInitialState(parsed);
2896
+ }
2897
+ } catch (e) {
2898
+ const code = e.code;
2899
+ if (code !== "ENOENT") {
2900
+ logWarn("state", { event: "parent-state-load-failed", file: parentAcp, error: e instanceof Error ? e.message : String(e) });
2901
+ return void 0;
2902
+ }
2903
+ }
2904
+ current = parentJsonl;
2905
+ }
2906
+ logWarn("state", { event: "parent-chain-exhausted", file: sessionFile, maxDepth: MAX_CHAIN_DEPTH });
2907
+ return void 0;
2781
2908
  }
2782
2909
  };
2910
+ function parseLiveRefOrigins(value) {
2911
+ if (!Array.isArray(value)) return [];
2912
+ return value.filter((item) => {
2913
+ if (!item || typeof item !== "object") return false;
2914
+ const origin = item;
2915
+ return typeof origin.rawId === "string" && typeof origin.identity === "string";
2916
+ });
2917
+ }
2783
2918
  function mergeInitialState(parsed) {
2784
2919
  const fresh = createInitialState();
2785
2920
  return {
@@ -2803,50 +2938,116 @@ function isPiHost(sm) {
2803
2938
  const source = sm;
2804
2939
  return typeof source.buildContextEntries === "function";
2805
2940
  }
2806
- function mergeLiveEntries(entries, live) {
2941
+ function mergeLiveEntries(entries, live, state, origins) {
2807
2942
  const persisted = entries.filter((e) => e.type === "message");
2943
+ const matched = matchPersistedSuffix(persisted, live);
2944
+ const prior = matchOrigins(origins, live);
2808
2945
  const out = [];
2809
- let p = 0;
2810
- let unmatched = 0;
2946
+ const nextOrigins = [];
2947
+ const usedIds = /* @__PURE__ */ new Set();
2811
2948
  for (let i = 0; i < live.length; i++) {
2812
2949
  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
- });
2950
+ const entry = matched[i];
2951
+ const origin = prior[i];
2952
+ if (entry) {
2953
+ if (origin) migrateLiveRefs(state, origin.rawId, entry.id);
2954
+ else migrateTaggedRef(state, msg, entry.id);
2955
+ out.push(entry);
2956
+ continue;
2831
2957
  }
2958
+ const id = origin?.rawId ?? nextLiveId(state, usedIds, i);
2959
+ usedIds.add(id);
2960
+ out.push({ type: "message", id, parentId: null, timestamp: String(msg.timestamp ?? Date.now()), message: msg });
2961
+ nextOrigins.push({ rawId: id, identity: messageIdentity(msg) });
2832
2962
  }
2963
+ origins.splice(0, origins.length, ...nextOrigins);
2964
+ const unmatched = live.length - matched.length;
2833
2965
  if (unmatched > 0) logInfo("runtime", { event: "merge-live-entries", live: live.length, unmatched });
2834
2966
  return out;
2835
2967
  }
2968
+ function matchOrigins(origins, live) {
2969
+ for (let start = 0; start < origins.length; start++) {
2970
+ const count = origins.length - start;
2971
+ if (count > live.length) continue;
2972
+ if (origins.slice(start).every((origin, index) => origin.identity === messageIdentity(live[index]))) {
2973
+ return [...origins.slice(start), ...Array(live.length - count)];
2974
+ }
2975
+ }
2976
+ return Array(live.length);
2977
+ }
2978
+ function nextLiveId(state, used, index) {
2979
+ let id = `live-${index}`;
2980
+ let suffix = index;
2981
+ while (used.has(id) || state.messageRefs.byRaw[id] !== void 0) id = `live-${++suffix}`;
2982
+ return id;
2983
+ }
2984
+ function migrateTaggedRef(state, message, stableId) {
2985
+ const ref = messageRef(message);
2986
+ const rawId = ref ? state.messageRefs.byRef[ref] : void 0;
2987
+ if (rawId?.startsWith("live-")) migrateLiveRefs(state, rawId, stableId);
2988
+ }
2989
+ function migrateLiveRefs(state, liveId, stableId) {
2990
+ const rootId = liveId.split("#", 1)[0];
2991
+ if (!rootId.startsWith("live-")) return;
2992
+ for (const [rawId, ref] of Object.entries(state.messageRefs.byRaw)) {
2993
+ if (rawId !== rootId && !rawId.startsWith(`${rootId}#`)) continue;
2994
+ const stableRawId = `${stableId}${rawId.slice(rootId.length)}`;
2995
+ if (state.messageRefs.byRaw[stableRawId] === void 0) {
2996
+ state.messageRefs.byRaw[stableRawId] = ref;
2997
+ state.messageRefs.byRef[ref] = stableRawId;
2998
+ } else if (state.messageRefs.byRef[ref] === rawId) {
2999
+ delete state.messageRefs.byRef[ref];
3000
+ }
3001
+ delete state.messageRefs.byRaw[rawId];
3002
+ }
3003
+ }
3004
+ function matchPersistedSuffix(persisted, live) {
3005
+ for (let start = 0; start < persisted.length; start++) {
3006
+ const count = persisted.length - start;
3007
+ if (count > live.length) continue;
3008
+ const suffix = persisted.slice(start);
3009
+ if (suffix.every((entry, index) => sameMessage(entry.message, live[index]))) return suffix;
3010
+ }
3011
+ return [];
3012
+ }
2836
3013
  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
3014
  try {
2844
- return JSON.stringify(ca) === JSON.stringify(cb);
3015
+ if (messageIdentity(a) === messageIdentity(b)) return true;
3016
+ const ra = a.role;
3017
+ const rb = b.role;
3018
+ if (ra !== rb || ra !== "toolResult") return false;
3019
+ const ca = a.content;
3020
+ const cb = b.content;
3021
+ return sameNonTextBlocks(ca, cb) && matchesStoredText(extractText(ca), extractText(cb));
2845
3022
  } catch (e) {
2846
3023
  logWarn("runtime", { event: "message-compare-failed", error: e instanceof Error ? e.message : String(e) });
2847
3024
  return a === b;
2848
3025
  }
2849
3026
  }
3027
+ function sameNonTextBlocks(a, b) {
3028
+ const nonText = (blocks) => blocks.filter((block) => block.type !== "text");
3029
+ try {
3030
+ const na = Array.isArray(a) ? nonText(a) : [];
3031
+ const nb = Array.isArray(b) ? nonText(b) : [];
3032
+ return JSON.stringify(na) === JSON.stringify(nb);
3033
+ } catch {
3034
+ return false;
3035
+ }
3036
+ }
3037
+ function pruneOrphanRefs(state, messages) {
3038
+ const retainedRawIds = new Set(messages.map((message) => message.id));
3039
+ for (const block of state.blocks) {
3040
+ for (const rawId of [...block.directMessageIds, ...block.effectiveMessageIds]) retainedRawIds.add(rawId);
3041
+ }
3042
+ for (const [rawId, ref] of Object.entries(state.messageRefs.byRaw)) {
3043
+ if (retainedRawIds.has(rawId)) continue;
3044
+ delete state.messageRefs.byRaw[rawId];
3045
+ if (state.messageRefs.byRef[ref] === rawId) delete state.messageRefs.byRef[ref];
3046
+ }
3047
+ for (const [ref, rawId] of Object.entries(state.messageRefs.byRef)) {
3048
+ if (!retainedRawIds.has(rawId)) delete state.messageRefs.byRef[ref];
3049
+ }
3050
+ }
2850
3051
  function createRuntime(adapter) {
2851
3052
  const core = createCore({ countTokens: defaultCountTokens });
2852
3053
  const store = new SessionStateStore();
@@ -2877,10 +3078,20 @@ function createRuntime(adapter) {
2877
3078
  }
2878
3079
  async function stateFor(ctx, liveMessages) {
2879
3080
  const sm = ctx.sessionManager;
2880
- const state = await store.load(sm.getSessionFile() ?? void 0, sm.getSessionId());
3081
+ const sessionFile = sm.getSessionFile() ?? void 0;
3082
+ const sessionId = sm.getSessionId();
3083
+ const state = await store.load(sessionFile, sessionId);
2881
3084
  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 };
3085
+ if (!isPiHost(sm) && liveMessages && liveMessages.length > 0) {
3086
+ const origins = store.getLiveRefOrigins(sessionFile, sessionId);
3087
+ const merged = mergeLiveEntries(entries, liveMessages, state, origins);
3088
+ store.setLiveRefOrigins(sessionFile, sessionId, origins);
3089
+ const coreMessages2 = entriesToCoreMessages(merged);
3090
+ return { state, coreMessages: coreMessages2, entries: merged };
3091
+ }
3092
+ const coreMessages = entriesToCoreMessages(entries);
3093
+ if (liveMessages === void 0) pruneOrphanRefs(state, coreMessages);
3094
+ return { state, coreMessages, entries };
2884
3095
  }
2885
3096
  async function save(state, ctx) {
2886
3097
  const sm = ctx.sessionManager;
@@ -7360,9 +7571,19 @@ function makeCompressTool(runtime) {
7360
7571
  async function handleCompress(args, runtime, ctx, toolCallId) {
7361
7572
  const ranges = args.content ?? [];
7362
7573
  if (ranges.length === 0) return "No ranges provided.";
7363
- const { state, coreMessages } = await runtime.stateFor(ctx);
7574
+ const { state: initialState, coreMessages } = await runtime.stateFor(ctx);
7364
7575
  const config = runtime.configFor(ctx);
7365
- const beforeTokens = estimateTokens(coreMessages, collectCoveredMessageIds(state));
7576
+ const estimatedTokens = estimateTokens(coreMessages, collectCoveredMessageIds(initialState));
7577
+ const realUsage = ctx.getContextUsage?.();
7578
+ const turn = runtime.core.processTurn({
7579
+ messages: coreMessages,
7580
+ state: initialState,
7581
+ config,
7582
+ tokenCount: realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : estimatedTokens
7583
+ });
7584
+ const state = turn.state;
7585
+ const messages = turn.messages;
7586
+ const beforeTokens = estimateTokens(messages, collectCoveredMessageIds(state));
7366
7587
  const summaryMaxChars = args.summaryMaxChars;
7367
7588
  const topLevelTopic = args.topic;
7368
7589
  debug.event("compress-in", {
@@ -7371,12 +7592,12 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
7371
7592
  spans: ranges.map((r) => ({ span: `${r.startId}..${r.endId}`, summaryLen: r.summary.length, summary: r.summary, topic: r.topic ?? topLevelTopic ?? null })),
7372
7593
  blocksBefore: state.blocks.length,
7373
7594
  activeBefore: state.blocks.filter((b) => b.active).length,
7374
- beforeMsgCount: coreMessages.length,
7595
+ beforeMsgCount: messages.length,
7375
7596
  beforeTokens
7376
7597
  });
7377
7598
  const applied = runtime.core.applyCompression({
7378
7599
  ranges: ranges.map((r) => ({ startRef: r.startId, endRef: r.endId, summary: r.summary, topic: r.topic ?? topLevelTopic, summaryMaxChars, compressCallId: toolCallId })),
7379
- messages: coreMessages,
7600
+ messages,
7380
7601
  state,
7381
7602
  config
7382
7603
  });
@@ -7699,76 +7920,6 @@ function formatSize(tokens) {
7699
7920
  return `${(tokens / 1e6).toFixed(1)}M`;
7700
7921
  }
7701
7922
 
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
7923
  // src/delegate-tool.ts
7773
7924
  import {
7774
7925
  spawn
@@ -7778,11 +7929,52 @@ import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } fro
7778
7929
  import { tmpdir as tmpdir2 } from "os";
7779
7930
  import { join as join4 } from "path";
7780
7931
 
7932
+ // src/footer-status.ts
7933
+ var FOOTER_STATUS_KEY = "billion-context-pi";
7934
+ var ui;
7935
+ var lastFooterText = "";
7936
+ function formatCompactTokens(count) {
7937
+ if (count < 1e3) return count.toString();
7938
+ if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
7939
+ if (count < 1e6) return `${Math.round(count / 1e3)}k`;
7940
+ if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
7941
+ return `${Math.round(count / 1e6)}M`;
7942
+ }
7943
+ function initFooterStatus(ctx) {
7944
+ ui = ctx.ui;
7945
+ lastFooterText = void 0;
7946
+ }
7947
+ function updateFooterStatus() {
7948
+ if (!ui) return;
7949
+ const usage = getDelegateUsage();
7950
+ let text;
7951
+ if (usage && usage.totalTokens > 0) {
7952
+ const costStr = usage.cost.total > 0 ? ` ($${usage.cost.total.toFixed(4)})` : "";
7953
+ text = `sub-agents \u2191${formatCompactTokens(usage.input)} \u2193${formatCompactTokens(usage.output)}${costStr}`;
7954
+ }
7955
+ if ((text ?? "") === lastFooterText) return;
7956
+ lastFooterText = text ?? "";
7957
+ try {
7958
+ ui.setStatus(FOOTER_STATUS_KEY, text);
7959
+ } catch {
7960
+ }
7961
+ }
7962
+ function disposeFooterStatus() {
7963
+ if (ui) {
7964
+ try {
7965
+ ui.setStatus(FOOTER_STATUS_KEY, void 0);
7966
+ } catch {
7967
+ }
7968
+ }
7969
+ ui = void 0;
7970
+ lastFooterText = "";
7971
+ }
7972
+
7781
7973
  // src/fleet-widget.ts
7782
7974
  var DELEGATE_WIDGET_KEY = "billion-context-pi-delegates";
7783
7975
  var REFRESH_MS = 500;
7784
7976
  var MAX_TASK_LEN = 48;
7785
- var ui;
7977
+ var ui2;
7786
7978
  var timer;
7787
7979
  var lastRenderKey = "";
7788
7980
  var runsSnapshot;
@@ -7811,20 +8003,21 @@ function stopTimer() {
7811
8003
  }
7812
8004
  }
7813
8005
  function clearWidget() {
7814
- if (!ui) return;
8006
+ if (!ui2) return;
7815
8007
  try {
7816
- ui.setWidget(DELEGATE_WIDGET_KEY, void 0);
8008
+ ui2.setWidget(DELEGATE_WIDGET_KEY, void 0);
7817
8009
  } catch {
7818
8010
  }
7819
8011
  }
7820
8012
  function refresh() {
7821
- if (!ui) return;
8013
+ if (!ui2) return;
7822
8014
  const runs2 = runsSnapshot ? runsSnapshot() : [];
7823
8015
  if (runs2.length === 0) {
7824
8016
  if (lastRenderKey !== "") {
7825
8017
  lastRenderKey = "";
7826
8018
  clearWidget();
7827
8019
  }
8020
+ updateFooterStatus();
7828
8021
  stopTimer();
7829
8022
  return;
7830
8023
  }
@@ -7834,16 +8027,18 @@ function refresh() {
7834
8027
  lastRenderKey = renderKey;
7835
8028
  const lines = renderLines(sorted);
7836
8029
  try {
7837
- ui.setWidget(DELEGATE_WIDGET_KEY, lines, { placement: "belowEditor" });
8030
+ ui2.setWidget(DELEGATE_WIDGET_KEY, lines, { placement: "belowEditor" });
7838
8031
  } catch {
7839
- ui = void 0;
8032
+ ui2 = void 0;
7840
8033
  stopTimer();
7841
8034
  }
8035
+ updateFooterStatus();
7842
8036
  }
7843
8037
  var delegateStatusWidget = {
7844
8038
  setContext(ctx, snapshot) {
7845
8039
  if (ctx.mode !== "tui") return;
7846
- ui = ctx.ui;
8040
+ initFooterStatus(ctx);
8041
+ ui2 = ctx.ui;
7847
8042
  runsSnapshot = snapshot;
7848
8043
  if (!timer) {
7849
8044
  timer = setInterval(refresh, REFRESH_MS);
@@ -7854,11 +8049,12 @@ var delegateStatusWidget = {
7854
8049
  dispose() {
7855
8050
  stopTimer();
7856
8051
  clearWidget();
7857
- ui = void 0;
8052
+ disposeFooterStatus();
8053
+ ui2 = void 0;
7858
8054
  lastRenderKey = "";
7859
8055
  },
7860
8056
  poke() {
7861
- if (ui && !timer) {
8057
+ if (ui2 && !timer) {
7862
8058
  timer = setInterval(refresh, REFRESH_MS);
7863
8059
  timer.unref?.();
7864
8060
  }
@@ -7872,11 +8068,13 @@ function attachWatchdogs(child, hooks, opts) {
7872
8068
  let eofTimer;
7873
8069
  let killGraceTimer;
7874
8070
  let timeoutTimer;
8071
+ let settledGraceTimer;
7875
8072
  const clearTimers = () => {
7876
8073
  if (idleTimer) clearTimeout(idleTimer);
7877
8074
  if (eofTimer) clearTimeout(eofTimer);
7878
8075
  if (killGraceTimer) clearTimeout(killGraceTimer);
7879
8076
  if (timeoutTimer) clearTimeout(timeoutTimer);
8077
+ if (settledGraceTimer) clearTimeout(settledGraceTimer);
7880
8078
  };
7881
8079
  const killByWatchdog = (reason) => {
7882
8080
  if (hooks.isSettled()) return;
@@ -7894,6 +8092,14 @@ function attachWatchdogs(child, hooks, opts) {
7894
8092
  }, opts.killGraceMs);
7895
8093
  killGraceTimer.unref?.();
7896
8094
  };
8095
+ const settledGrace = (graceMs, _killGraceMs, reason) => {
8096
+ if (hooks.isSettled() || settledGraceTimer) return;
8097
+ settledGraceTimer = setTimeout(() => {
8098
+ settledGraceTimer = void 0;
8099
+ killByWatchdog(reason);
8100
+ }, graceMs);
8101
+ settledGraceTimer.unref?.();
8102
+ };
7897
8103
  const poke = () => {
7898
8104
  if (idleTimer) clearTimeout(idleTimer);
7899
8105
  idleTimer = setTimeout(() => killByWatchdog(`no output for ${opts.idleMs / 6e4}m`), opts.idleMs);
@@ -7917,6 +8123,7 @@ function attachWatchdogs(child, hooks, opts) {
7917
8123
  child.stdout?.once("end", onStdoutEnd);
7918
8124
  return {
7919
8125
  poke,
8126
+ settledGrace,
7920
8127
  dispose: () => {
7921
8128
  clearTimers();
7922
8129
  child.stdout?.removeListener("end", onStdoutEnd);
@@ -7925,16 +8132,66 @@ function attachWatchdogs(child, hooks, opts) {
7925
8132
  }
7926
8133
 
7927
8134
  // src/delegate-events.ts
8135
+ function safeNumber(v) {
8136
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
8137
+ }
8138
+ function handleMessageEnd(event) {
8139
+ const msg = event.message;
8140
+ if (!msg || msg.role !== "assistant") return null;
8141
+ const u = msg.usage;
8142
+ if (!u || typeof u !== "object") return null;
8143
+ const raw = u;
8144
+ const input = safeNumber(raw.input);
8145
+ const output = safeNumber(raw.output);
8146
+ const cacheRead = safeNumber(raw.cacheRead);
8147
+ const cacheWrite = safeNumber(raw.cacheWrite);
8148
+ if (input === void 0 && output === void 0 && cacheRead === void 0 && cacheWrite === void 0) return null;
8149
+ const cost = raw.cost;
8150
+ let parsedCost;
8151
+ if (cost && typeof cost === "object") {
8152
+ const c = cost;
8153
+ parsedCost = {
8154
+ input: typeof c.input === "number" ? c.input : 0,
8155
+ output: typeof c.output === "number" ? c.output : 0,
8156
+ cacheRead: typeof c.cacheRead === "number" ? c.cacheRead : 0,
8157
+ cacheWrite: typeof c.cacheWrite === "number" ? c.cacheWrite : 0,
8158
+ total: typeof c.total === "number" ? c.total : 0
8159
+ };
8160
+ } else {
8161
+ parsedCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
8162
+ }
8163
+ return {
8164
+ kind: "usage-update",
8165
+ usage: {
8166
+ input: input ?? 0,
8167
+ output: output ?? 0,
8168
+ cacheRead: cacheRead ?? 0,
8169
+ cacheWrite: cacheWrite ?? 0,
8170
+ cacheWrite1h: safeNumber(raw.cacheWrite1h),
8171
+ reasoning: safeNumber(raw.reasoning),
8172
+ totalTokens: typeof raw.totalTokens === "number" ? raw.totalTokens : 0,
8173
+ cost: parsedCost
8174
+ }
8175
+ };
8176
+ }
7928
8177
  var ThinkingCollector = class {
7929
8178
  constructor(showThinking) {
7930
8179
  this.showThinking = showThinking;
7931
8180
  }
7932
8181
  showThinking;
7933
8182
  buf = "";
8183
+ usage;
7934
8184
  push(delta) {
7935
8185
  this.buf += delta;
7936
8186
  }
7937
- /** Return the segment line to write ("" when empty or disabled), resetting. */
8187
+ process(ev) {
8188
+ if (ev.kind === "thinking-delta") {
8189
+ this.push(ev.delta);
8190
+ }
8191
+ if (ev.kind === "usage-update") {
8192
+ this.usage = ev.usage;
8193
+ }
8194
+ }
7938
8195
  flush() {
7939
8196
  const text = this.buf.trim();
7940
8197
  this.buf = "";
@@ -7942,6 +8199,9 @@ var ThinkingCollector = class {
7942
8199
  return `[thinking] ${text}
7943
8200
  `;
7944
8201
  }
8202
+ getUsage() {
8203
+ return this.usage;
8204
+ }
7945
8205
  };
7946
8206
  function parseEventLine(line) {
7947
8207
  let ev;
@@ -8006,6 +8266,12 @@ function parseEventLine(line) {
8006
8266
  attempt: Number(e.attempt ?? 0)
8007
8267
  };
8008
8268
  }
8269
+ if (e.type === "message_end") {
8270
+ return handleMessageEnd(e);
8271
+ }
8272
+ if (e.type === "agent_settled") {
8273
+ return { kind: "agent-settled" };
8274
+ }
8009
8275
  return null;
8010
8276
  }
8011
8277
  function formatArgs(args) {
@@ -8060,6 +8326,7 @@ function newPortion(text, prev) {
8060
8326
  var MAX_DEPTH = 2;
8061
8327
  var SYNC_TIMEOUT_MS = 5 * 6e4;
8062
8328
  var EOF_GRACE_MS = 1e4;
8329
+ var SETTLED_GRACE_MS = 1e4;
8063
8330
  var IDLE_GRACE_MS = 5 * 6e4;
8064
8331
  var ASYNC_TIMEOUT_MS = 30 * 6e4;
8065
8332
  var KILL_GRACE_MS = 1e4;
@@ -8112,6 +8379,20 @@ Answer the question concisely with clear reasoning. Cite file:line when referenc
8112
8379
  };
8113
8380
  var AGENT_NAMES = Object.keys(AGENTS);
8114
8381
  var runs = /* @__PURE__ */ new Map();
8382
+ var delegateUsageTotal;
8383
+ function addDelegateUsage(u) {
8384
+ delegateUsageTotal = delegateUsageTotal ? accumulateUsage(delegateUsageTotal, u) : u;
8385
+ }
8386
+ function getDelegateUsage() {
8387
+ return delegateUsageTotal;
8388
+ }
8389
+ function resetDelegateUsage() {
8390
+ delegateUsageTotal = void 0;
8391
+ }
8392
+ var delegateDisplayUsage = "separate";
8393
+ function setDelegateDisplayUsage(mode) {
8394
+ delegateDisplayUsage = mode;
8395
+ }
8115
8396
  function runningRunsSnapshot() {
8116
8397
  const out = [];
8117
8398
  for (const r of runs.values()) {
@@ -8131,6 +8412,10 @@ function makeEventApplier(opts, writers) {
8131
8412
  const handleEventLine = (line) => {
8132
8413
  const ev = parseEventLine(line);
8133
8414
  if (!ev) return;
8415
+ if (ev.kind === "usage-update") {
8416
+ opts.onUsage?.(ev.usage);
8417
+ return;
8418
+ }
8134
8419
  if (ev.kind === "thinking-delta") {
8135
8420
  thinking.push(ev.delta);
8136
8421
  return;
@@ -8139,6 +8424,11 @@ function makeEventApplier(opts, writers) {
8139
8424
  flushThinking();
8140
8425
  return;
8141
8426
  }
8427
+ if (ev.kind === "agent-settled") {
8428
+ flushThinking();
8429
+ opts.onSettled?.();
8430
+ return;
8431
+ }
8142
8432
  if (ev.kind === "reply-delta") {
8143
8433
  flushThinking();
8144
8434
  replyText += ev.delta;
@@ -8219,6 +8509,37 @@ var WaitParams = typebox_exports.Object({
8219
8509
  })
8220
8510
  )
8221
8511
  });
8512
+ function safeCost(u) {
8513
+ if (u.cost.input > 0 || u.cost.output > 0 || u.cost.cacheRead > 0 || u.cost.cacheWrite > 0 || u.cost.total > 0) {
8514
+ return {
8515
+ input: u.cost.input > 0 ? u.cost.input : 0,
8516
+ output: u.cost.output > 0 ? u.cost.output : 0,
8517
+ cacheRead: u.cost.cacheRead > 0 ? u.cost.cacheRead : 0,
8518
+ cacheWrite: u.cost.cacheWrite > 0 ? u.cost.cacheWrite : 0,
8519
+ total: u.cost.total > 0 ? u.cost.total : 0
8520
+ };
8521
+ }
8522
+ return void 0;
8523
+ }
8524
+ function accumulateUsage(a, b) {
8525
+ if (!a) return b;
8526
+ return {
8527
+ input: a.input + b.input,
8528
+ output: a.output + b.output,
8529
+ cacheRead: a.cacheRead + b.cacheRead,
8530
+ cacheWrite: a.cacheWrite + b.cacheWrite,
8531
+ cacheWrite1h: (a.cacheWrite1h ?? 0) + (b.cacheWrite1h ?? 0),
8532
+ reasoning: (a.reasoning ?? 0) + (b.reasoning ?? 0),
8533
+ totalTokens: a.totalTokens + b.totalTokens,
8534
+ cost: {
8535
+ input: a.cost.input + b.cost.input,
8536
+ output: a.cost.output + b.cost.output,
8537
+ cacheRead: a.cost.cacheRead + b.cost.cacheRead,
8538
+ cacheWrite: a.cost.cacheWrite + b.cost.cacheWrite,
8539
+ total: a.cost.total + b.cost.total
8540
+ }
8541
+ };
8542
+ }
8222
8543
  var agentListLine = (name) => {
8223
8544
  const def = AGENTS[name];
8224
8545
  if (!def) return "";
@@ -8277,6 +8598,38 @@ function injectedWaitMessage(run, runId, remainingLine) {
8277
8598
  const fileLine = file ? ` If you need details, read the result file: \`${file}\`.` : "";
8278
8599
  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
8600
  }
8601
+ function buildWaitResult(run, content, mode = "separate", contentType = "text") {
8602
+ if (run.usage && !run.usageReported) {
8603
+ run.usageReported = true;
8604
+ if (mode === "merged") {
8605
+ const cost = safeCost(run.usage);
8606
+ return {
8607
+ details: void 0,
8608
+ content: [{ type: contentType, text: content }],
8609
+ usage: { ...run.usage, cost: cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }
8610
+ };
8611
+ } else {
8612
+ addDelegateUsage(run.usage);
8613
+ }
8614
+ }
8615
+ return { details: void 0, content: [{ type: contentType, text: content }] };
8616
+ }
8617
+ function buildCancelResult(run, content, mode = "separate") {
8618
+ if (run.usage && !run.usageReported) {
8619
+ run.usageReported = true;
8620
+ if (mode === "merged") {
8621
+ const cost = safeCost(run.usage);
8622
+ return {
8623
+ details: void 0,
8624
+ content: [{ type: "text", text: content }],
8625
+ usage: { ...run.usage, cost: cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }
8626
+ };
8627
+ } else {
8628
+ addDelegateUsage(run.usage);
8629
+ }
8630
+ }
8631
+ return { details: void 0, content: [{ type: "text", text: content }] };
8632
+ }
8280
8633
  function makeDelegateWaitTool(_pi) {
8281
8634
  return {
8282
8635
  name: "acp_delegate_wait",
@@ -8294,21 +8647,22 @@ function makeDelegateWaitTool(_pi) {
8294
8647
  if (!run) {
8295
8648
  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
8649
  }
8650
+ const displayMode = delegateDisplayUsage;
8297
8651
  if (run.status === "cancelled") {
8298
8652
  run.consumed = true;
8299
- return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` was cancelled (no result).${remainingLineForWait(args.runId)}` }] };
8653
+ return buildWaitResult(run, `Delegate \`${args.runId}\` was cancelled (no result).${remainingLineForWait(args.runId)}`, displayMode);
8300
8654
  }
8301
8655
  if (run.status !== "running") {
8302
8656
  const dedup = injectedWaitMessage(run, args.runId, remainingLineForWait(args.runId));
8303
8657
  if (dedup) {
8304
8658
  run.consumed = true;
8305
- return { details: void 0, content: [{ type: "text", text: dedup }] };
8659
+ return buildWaitResult(run, dedup, displayMode);
8306
8660
  }
8307
8661
  run.consumed = true;
8308
8662
  if (!run.result) {
8309
- return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` finished but no result is available (persist error).` }] };
8663
+ return buildWaitResult(run, `Delegate \`${args.runId}\` finished but no result is available (persist error).`, displayMode);
8310
8664
  }
8311
- return { details: void 0, content: [{ type: "text", text: formatRunResult(run) }] };
8665
+ return buildWaitResult(run, formatRunResult(run), displayMode);
8312
8666
  }
8313
8667
  const timeoutMs = Math.min(
8314
8668
  Math.max(args.timeout ?? WAIT_TIMEOUT_MS_DEFAULT, 1e3),
@@ -8320,28 +8674,28 @@ function makeDelegateWaitTool(_pi) {
8320
8674
  return new Promise((resolve2) => {
8321
8675
  let settled = false;
8322
8676
  let timer2;
8323
- const finish = (text) => {
8677
+ const finish = (result) => {
8324
8678
  if (settled) return;
8325
8679
  settled = true;
8326
8680
  run.waiter = void 0;
8327
8681
  if (timer2) clearTimeout(timer2);
8328
8682
  signal?.removeEventListener("abort", onAbort);
8329
- resolve2({ details: void 0, content: [{ type: "text", text }] });
8683
+ resolve2(result);
8330
8684
  };
8331
8685
  const onAbort = () => {
8332
- finish(`Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.`);
8686
+ 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
8687
  };
8334
8688
  run.waiter = () => {
8335
8689
  run.consumed = true;
8336
8690
  if (run.status === "cancelled") {
8337
- finish(`Delegate \`${run.runId}\` was cancelled (no result).${remainingLineForWait(run.runId)}`);
8691
+ finish(buildWaitResult(run, `Delegate \`${run.runId}\` was cancelled (no result).${remainingLineForWait(run.runId)}`, displayMode));
8338
8692
  return;
8339
8693
  }
8340
- finish(formatRunResult(run));
8694
+ finish(buildWaitResult(run, formatRunResult(run), displayMode));
8341
8695
  };
8342
8696
  signal?.addEventListener("abort", onAbort);
8343
8697
  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.`),
8698
+ () => 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
8699
  timeoutMs
8346
8700
  );
8347
8701
  });
@@ -8360,16 +8714,10 @@ function makeDelegateCancelTool(_pi) {
8360
8714
  const { runId } = params;
8361
8715
  const run = runs.get(runId);
8362
8716
  if (!run) {
8363
- return {
8364
- details: void 0,
8365
- content: [{ type: "text", text: `Unknown runId "${runId}".` }]
8366
- };
8717
+ return { details: void 0, content: [{ type: "text", text: `Unknown runId "${runId}".` }] };
8367
8718
  }
8368
8719
  if (run.status !== "running") {
8369
- return {
8370
- details: void 0,
8371
- content: [{ type: "text", text: `Run ${runId} already ${run.status} (no action).` }]
8372
- };
8720
+ return buildCancelResult(run, `Run ${runId} already ${run.status} (no action).`);
8373
8721
  }
8374
8722
  run.status = "cancelled";
8375
8723
  run.consumed = true;
@@ -8380,10 +8728,8 @@ function makeDelegateCancelTool(_pi) {
8380
8728
  logError("delegate", { event: "cancel-kill-error", runId, error: String(err) });
8381
8729
  }
8382
8730
  delegateStatusWidget.poke();
8383
- return {
8384
- details: void 0,
8385
- content: [{ type: "text", text: `Cancelled ${runId} (${run.agent}).` }]
8386
- };
8731
+ const displayMode = delegateDisplayUsage;
8732
+ return buildCancelResult(run, `Cancelled ${runId} (${run.agent}).`, displayMode);
8387
8733
  }
8388
8734
  };
8389
8735
  }
@@ -8432,11 +8778,11 @@ async function runDelegate(pi, args, ctx, signal) {
8432
8778
  {
8433
8779
  isSettled: () => settled || run.status !== "running",
8434
8780
  onKill: (reason) => {
8435
- run.timedOut = reason;
8781
+ if (!run.agentSettled) run.timedOut = reason;
8436
8782
  debug.event("delegate-watchdog", { runId, reason });
8437
8783
  },
8438
8784
  onEofGrace: () => {
8439
- run.timedOut = "output ended but process did not exit";
8785
+ if (!run.agentSettled) run.timedOut = "output ended but process did not exit";
8440
8786
  debug.event("delegate-eof-grace", { runId, ms: EOF_GRACE_MS });
8441
8787
  }
8442
8788
  },
@@ -8453,7 +8799,16 @@ async function runDelegate(pi, args, ctx, signal) {
8453
8799
  });
8454
8800
  let stdoutBuf = "";
8455
8801
  const applier = makeEventApplier(
8456
- { showThinking: args.showThinking === true },
8802
+ {
8803
+ showThinking: args.showThinking === true,
8804
+ onUsage: (u) => {
8805
+ run.usage = accumulateUsage(run.usage, u);
8806
+ },
8807
+ onSettled: () => {
8808
+ run.agentSettled = true;
8809
+ watchdog.settledGrace(SETTLED_GRACE_MS, KILL_GRACE_MS, "agent settled but process did not exit");
8810
+ }
8811
+ },
8457
8812
  { reply: replyStream, activity: activityStream }
8458
8813
  );
8459
8814
  child.stdout?.on("data", (c) => {
@@ -8527,7 +8882,11 @@ async function runDelegate(pi, args, ctx, signal) {
8527
8882
  delegateStatusWidget.poke();
8528
8883
  return;
8529
8884
  }
8530
- const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut);
8885
+ const mode = delegateDisplayUsage;
8886
+ const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut, run.usage, mode, run.usageReported);
8887
+ if (run.usage && !run.usageReported && (mode === "separate" || injected)) {
8888
+ run.usageReported = true;
8889
+ }
8531
8890
  run.injected = injected;
8532
8891
  debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
8533
8892
  logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected, outLen: output.length, file: file2 });
@@ -8648,7 +9007,7 @@ function formatSyncResult(agent, runId, task, r, file) {
8648
9007
  const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
8649
9008
  return formatPayload(header, file, task, body);
8650
9009
  }
8651
- function injectResult(pi, agent, runId, task, code, file, timedOut) {
9010
+ function injectResult(pi, agent, runId, task, code, file, timedOut, usage, mode = "separate", usageAlreadyReported) {
8652
9011
  const send = pi.sendUserMessage;
8653
9012
  if (typeof send !== "function") {
8654
9013
  debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
@@ -8659,7 +9018,37 @@ function injectResult(pi, agent, runId, task, code, file, timedOut) {
8659
9018
  const remaining = Array.from(runs.values()).filter((r) => r.status === "running").length;
8660
9019
  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
9020
  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.`;
9021
+ let usageNote = "";
9022
+ if (mode === "separate") {
9023
+ if (usage && !usageAlreadyReported) {
9024
+ addDelegateUsage(usage);
9025
+ }
9026
+ const totalUsage = getDelegateUsage();
9027
+ if (totalUsage) {
9028
+ const cost = totalUsage.cost.total;
9029
+ const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
9030
+ usageNote = `
9031
+
9032
+ \u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500
9033
+ Tokens: ${totalUsage.input.toLocaleString()} in, ${totalUsage.output.toLocaleString()} out (${totalUsage.totalTokens.toLocaleString()} total)${costStr}`;
9034
+ }
9035
+ } else if (usage) {
9036
+ const lines = [];
9037
+ if (usage.totalTokens) lines.push(`tokens=${usage.totalTokens.toLocaleString()}`);
9038
+ if (usage.input || usage.output) lines.push(`in=${usage.input.toLocaleString()} out=${usage.output.toLocaleString()}`);
9039
+ if (usage.cacheRead) lines.push(`cache_read=${usage.cacheRead.toLocaleString()}`);
9040
+ if (usage.cacheWrite) lines.push(`cache_write=${usage.cacheWrite.toLocaleString()}`);
9041
+ if (usage.cost && typeof usage.cost === "object") {
9042
+ const c = usage.cost;
9043
+ if (typeof c.total === "number" && c.total > 0) {
9044
+ lines.push(`cost=$${c.total.toFixed(4)}`);
9045
+ } else if (typeof c.input === "number" && c.input > 0 || typeof c.output === "number" && c.output > 0) {
9046
+ lines.push(`cost=${JSON.stringify(c)}`);
9047
+ }
9048
+ }
9049
+ if (lines.length) usageNote = ` Usage: ${lines.join(", ")}.`;
9050
+ }
9051
+ 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
9052
  const text = formatPayload(header, file, task);
8664
9053
  try {
8665
9054
  send.call(pi, text, { deliverAs: "followUp" });
@@ -8710,6 +9099,90 @@ function truncate2(s, n) {
8710
9099
  return s.slice(0, n - 1) + "\u2026";
8711
9100
  }
8712
9101
 
9102
+ // src/status-tool.ts
9103
+ var StatusParams = typebox_exports.Object({
9104
+ 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.' })),
9105
+ 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).' })),
9106
+ tool: typebox_exports.Optional(typebox_exports.String({ description: 'Filter by tool name (e.g. "bash", "read"). Only for uncompressed+messages.' })),
9107
+ 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." })),
9108
+ limit: typebox_exports.Optional(typebox_exports.Number({ description: "Max items to show (default: 30)." }))
9109
+ });
9110
+ function makeStatusTool(runtime) {
9111
+ return {
9112
+ name: "acp_status",
9113
+ label: "ACP Status",
9114
+ 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.",
9115
+ promptSnippet: 'acp_status({}) or acp_status({ scope: "uncompressed", view: "messages" })',
9116
+ promptGuidelines: [
9117
+ "Call with no args for a quick overview of context usage.",
9118
+ "Use scope:'uncompressed' to find the largest compressible ranges.",
9119
+ "Use scope:'compressed' to inspect existing compression blocks."
9120
+ ],
9121
+ parameters: StatusParams,
9122
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9123
+ let result;
9124
+ try {
9125
+ result = await handleStatus(params, runtime, ctx);
9126
+ } catch (e) {
9127
+ logThrow("status", e, { sid: ctx.sessionManager.getSessionId(), scope: params.scope ?? null });
9128
+ throw e;
9129
+ }
9130
+ return { details: void 0, content: [{ type: "text", text: result }] };
9131
+ }
9132
+ };
9133
+ }
9134
+ async function handleStatus(args, runtime, ctx) {
9135
+ const { state, coreMessages } = await runtime.stateFor(ctx);
9136
+ const config = runtime.configFor(ctx);
9137
+ const tokenCount = estimateTokens(coreMessages, collectCoveredMessageIds(state));
9138
+ const realUsage = ctx.getContextUsage?.();
9139
+ const turn = runtime.core.processTurn({
9140
+ messages: coreMessages,
9141
+ state,
9142
+ config,
9143
+ tokenCount: realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : tokenCount
9144
+ });
9145
+ const processed = turn.messages;
9146
+ const base = buildStatusReport(turn.state, processed, defaultCountTokens, {
9147
+ scope: args.scope,
9148
+ view: args.view,
9149
+ tool: args.tool,
9150
+ sort: args.sort,
9151
+ limit: args.limit
9152
+ });
9153
+ if (args.scope) return base;
9154
+ const nudge = turn.nudge;
9155
+ const ranges = nudge?.compressibleRanges ?? [];
9156
+ const protectedRanges = nudge?.protectedRanges ?? [];
9157
+ const extra = [];
9158
+ if (nudge) {
9159
+ extra.push("");
9160
+ extra.push(
9161
+ nudge.shouldInject ? `Nudge: ACTIVE \u2014 ${nudge.reason}` : `Nudge: idle \u2014 ${nudge.reason}`
9162
+ );
9163
+ }
9164
+ if (ranges.length > 0 || protectedRanges.length > 0) {
9165
+ extra.push("");
9166
+ extra.push(formatRanges(ranges, protectedRanges));
9167
+ }
9168
+ const delegateUsage = getDelegateUsage();
9169
+ if (delegateUsage && delegateUsage.totalTokens > 0) {
9170
+ extra.push("");
9171
+ const cost = delegateUsage.cost.total;
9172
+ const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
9173
+ extra.push("\u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500");
9174
+ extra.push(`Tokens: ${delegateUsage.input.toLocaleString()} in, ${delegateUsage.output.toLocaleString()} out (${delegateUsage.totalTokens.toLocaleString()} total)${costStr}`);
9175
+ } else if (runtime.adapter.displayUsage === "merged") {
9176
+ extra.push("");
9177
+ extra.push("merged mode: delegate usage is included in main session totals.");
9178
+ } else {
9179
+ extra.push("");
9180
+ extra.push("Delegate usage: none this session.");
9181
+ }
9182
+ return extra.length > 0 ? `${base}
9183
+ ${extra.join("\n")}` : base;
9184
+ }
9185
+
8713
9186
  // src/compat.ts
8714
9187
  function normalizeSystemPrompt(input) {
8715
9188
  if (input === void 0) return "";
@@ -8740,7 +9213,7 @@ function makeCommands(runtime) {
8740
9213
  {
8741
9214
  name: "acp-status",
8742
9215
  options: {
8743
- description: "Detailed ACP status (block tiers, token breakdown).",
9216
+ description: "Detailed ACP status (block tiers, token breakdown, delegate usage).",
8744
9217
  handler: async (_args, ctx) => ctx.ui.notify(await statusReport(runtime, ctx))
8745
9218
  }
8746
9219
  },
@@ -8795,9 +9268,7 @@ ${text}`);
8795
9268
  ];
8796
9269
  }
8797
9270
  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);
9271
+ return formatCompactTokens(n);
8801
9272
  }
8802
9273
  function bar(value, total, width = 20) {
8803
9274
  if (total === 0) return "";
@@ -8822,7 +9293,7 @@ async function statusReport(runtime, ctx) {
8822
9293
  const activeBlocksList = state.blocks.filter((b) => b.active);
8823
9294
  const totalBlocksList = state.blocks;
8824
9295
  const lines = [];
8825
- const versionStr = "0.1.34" ? `billion-context-pi@${"0.1.34"}` : "";
9296
+ const versionStr = "0.1.35" ? `billion-context-pi@${"0.1.35"}` : "";
8826
9297
  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
9298
  lines.push("\u2502 ACP Context Analysis \u2502");
8828
9299
  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 +9356,15 @@ async function statusReport(runtime, ctx) {
8885
9356
  lines.push("Blocks: none (nothing compressed yet)");
8886
9357
  }
8887
9358
  lines.push("");
9359
+ const delegateUsage = getDelegateUsage();
9360
+ if (delegateUsage && delegateUsage.totalTokens > 0) {
9361
+ lines.push("");
9362
+ const cost = delegateUsage.cost.total;
9363
+ const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
9364
+ lines.push("\u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500");
9365
+ lines.push(`Tokens: ${delegateUsage.input.toLocaleString()} in, ${delegateUsage.output.toLocaleString()} out (${delegateUsage.totalTokens.toLocaleString()} total)${costStr}`);
9366
+ }
9367
+ lines.push("");
8888
9368
  lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
8889
9369
  return lines.join("\n");
8890
9370
  }
@@ -9189,7 +9669,7 @@ async function checkForUpdate(autoUpdate, notify) {
9189
9669
  const data = await res.json();
9190
9670
  const latest = data.version;
9191
9671
  if (!latest) return;
9192
- const current = runtimeVersion ?? "0.1.34";
9672
+ const current = runtimeVersion ?? "0.1.35";
9193
9673
  const hasUpdate = isNewer(latest, current);
9194
9674
  debug.event("update-check", {
9195
9675
  current,
@@ -9382,7 +9862,7 @@ async function loadUserConfig(cwd) {
9382
9862
  function join8(...parts) {
9383
9863
  return path3.join(...parts);
9384
9864
  }
9385
- var KNOWN = /* @__PURE__ */ new Set(["debug", "autoUpdate", "modelContextLimit", "delegate", "toolBashDefaultTimeout", "toolOutputMaxBytes"]);
9865
+ var KNOWN = /* @__PURE__ */ new Set(["debug", "autoUpdate", "modelContextLimit", "delegate", "displayUsage", "toolBashDefaultTimeout", "toolOutputMaxBytes"]);
9386
9866
  function pickKnown(parsed) {
9387
9867
  const out = {};
9388
9868
  for (const [k, v] of Object.entries(parsed)) {
@@ -9428,11 +9908,14 @@ function wireSessionLifecycle(pi, runtime) {
9428
9908
  pi.on("session_start", async (_event, ctx) => {
9429
9909
  runtime.store.invalidate();
9430
9910
  runtime.clearNudgeTracking();
9911
+ resetDelegateUsage();
9912
+ setDelegateDisplayUsage("separate");
9431
9913
  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 });
9914
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.35" : null });
9433
9915
  try {
9434
9916
  const user = await loadUserConfig(ctx.cwd);
9435
9917
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
9918
+ setDelegateDisplayUsage(runtime.adapter.displayUsage ?? "separate");
9436
9919
  if (runtime.adapter.debug !== void 0) setDebugEnabled(runtime.adapter.debug);
9437
9920
  } catch (e) {
9438
9921
  logThrow("config", e, { sid, phase: "session_start" });