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/README.md +8 -2
- package/README.zh-CN.md +8 -2
- package/dist/config.d.ts +45 -4
- package/dist/delegate-events.d.ts +28 -2
- package/dist/delegate-tool.d.ts +62 -2
- package/dist/delegate-watchdog.d.ts +11 -0
- package/dist/footer-status.d.ts +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +729 -186
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +4 -0
- package/dist/runtime.d.ts +0 -5
- package/dist/state.d.ts +10 -3
- package/dist/user-config.d.ts +4 -2
- package/package.json +2 -2
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.
|
|
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.
|
|
276
|
+
emergencyThresholdPct: 0.95
|
|
277
277
|
},
|
|
278
278
|
promotionThreshold: 5,
|
|
279
|
-
truncate: { threshold:
|
|
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 (!
|
|
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 =
|
|
1513
|
+
const shouldInject = injectedTier !== null || overLimit && (rec?.recommendedRanges?.length ?? 0) > 0;
|
|
1499
1514
|
let reason;
|
|
1500
|
-
if (emergencyOverride) {
|
|
1501
|
-
reason =
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
2715
|
+
newBlocks[i] = { ...b, text: `${baseText}
|
|
2604
2716
|
|
|
2605
|
-
${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
|
|
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 =
|
|
2740
|
-
|
|
2741
|
-
async load(sessionFile, _sessionId) {
|
|
2876
|
+
cache = /* @__PURE__ */ new Map();
|
|
2877
|
+
async load(sessionFile, sessionId) {
|
|
2742
2878
|
const file = stateFileFor(sessionFile);
|
|
2743
|
-
|
|
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))
|
|
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
|
|
2758
|
-
this.loadedKey = file;
|
|
2903
|
+
this.cache.set(key, { state, liveRefOrigins });
|
|
2759
2904
|
return state;
|
|
2760
2905
|
}
|
|
2761
|
-
async save(state, sessionFile,
|
|
2906
|
+
async save(state, sessionFile, sessionId) {
|
|
2762
2907
|
const file = stateFileFor(sessionFile);
|
|
2763
2908
|
if (!file) return;
|
|
2764
|
-
|
|
2765
|
-
this.
|
|
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
|
|
2780
|
-
|
|
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
|
-
|
|
2810
|
-
|
|
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
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2883
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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 (!
|
|
8059
|
+
if (!ui2) return;
|
|
7815
8060
|
try {
|
|
7816
|
-
|
|
8061
|
+
ui2.setWidget(DELEGATE_WIDGET_KEY, void 0);
|
|
7817
8062
|
} catch {
|
|
7818
8063
|
}
|
|
7819
8064
|
}
|
|
7820
8065
|
function refresh() {
|
|
7821
|
-
if (!
|
|
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
|
-
|
|
8083
|
+
ui2.setWidget(DELEGATE_WIDGET_KEY, lines, { placement: "belowEditor" });
|
|
7838
8084
|
} catch {
|
|
7839
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8105
|
+
disposeFooterStatus();
|
|
8106
|
+
ui2 = void 0;
|
|
7858
8107
|
lastRenderKey = "";
|
|
7859
8108
|
},
|
|
7860
8109
|
poke() {
|
|
7861
|
-
if (
|
|
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
|
-
|
|
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
|
|
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
|
|
8712
|
+
return buildWaitResult(run, dedup, displayMode);
|
|
8306
8713
|
}
|
|
8307
8714
|
run.consumed = true;
|
|
8308
8715
|
if (!run.result) {
|
|
8309
|
-
return
|
|
8716
|
+
return buildWaitResult(run, `Delegate \`${args.runId}\` finished but no result is available (persist error).`, displayMode);
|
|
8310
8717
|
}
|
|
8311
|
-
return
|
|
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 = (
|
|
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(
|
|
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
|
-
|
|
8384
|
-
|
|
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
|
-
{
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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([
|
|
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.
|
|
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.
|
|
9983
|
+
if (resolveDelegate(runtime.adapter).enabled) {
|
|
9441
9984
|
pi.registerTool(makeDelegateTool(pi));
|
|
9442
9985
|
pi.registerTool(makeDelegateWaitTool(pi));
|
|
9443
9986
|
pi.registerTool(makeDelegateCancelTool(pi));
|