billion-context-omp 0.1.6 → 0.1.8
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 +12 -9
- package/README.zh-CN.md +12 -9
- package/dist/index.js +628 -88
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +1 -1
- package/dist/tokens.d.ts +13 -0
- package/dist/user-config.d.ts +7 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -287,7 +287,8 @@ function defaultConfig(modelContextLimit, overrides = {}) {
|
|
|
287
287
|
growthCap: 5e4,
|
|
288
288
|
minGrowthFloor: 2e4,
|
|
289
289
|
minGrowthRatio: 0.45,
|
|
290
|
-
emergencyThresholdPct: 0.95
|
|
290
|
+
emergencyThresholdPct: 0.95,
|
|
291
|
+
tier2GrowthMultiplier: 1.5
|
|
291
292
|
},
|
|
292
293
|
promotionThreshold: 5,
|
|
293
294
|
truncate: { threshold: 0.95 },
|
|
@@ -1012,6 +1013,44 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
|
|
|
1012
1013
|
protected: protectedRanges
|
|
1013
1014
|
};
|
|
1014
1015
|
}
|
|
1016
|
+
function mergeBatch(batch) {
|
|
1017
|
+
const first = batch[0];
|
|
1018
|
+
const last = batch[batch.length - 1];
|
|
1019
|
+
const count = batch.reduce((s, r) => s + r.count, 0);
|
|
1020
|
+
const tokens = batch.reduce((s, r) => s + r.tokens, 0);
|
|
1021
|
+
const toolPct = Math.round(
|
|
1022
|
+
batch.reduce((s, r) => s + r.toolPct * r.count, 0) / count
|
|
1023
|
+
);
|
|
1024
|
+
const merged = {
|
|
1025
|
+
startRef: first.startRef,
|
|
1026
|
+
endRef: last.endRef,
|
|
1027
|
+
count,
|
|
1028
|
+
tokens,
|
|
1029
|
+
toolPct,
|
|
1030
|
+
textPct: 100 - toolPct
|
|
1031
|
+
};
|
|
1032
|
+
if (batch.some((r) => r.dangerous === true)) {
|
|
1033
|
+
merged.dangerous = true;
|
|
1034
|
+
}
|
|
1035
|
+
return merged;
|
|
1036
|
+
}
|
|
1037
|
+
function mergeRangesToThreshold(ranges, minChars) {
|
|
1038
|
+
if (minChars <= 0 || ranges.length === 0) return ranges;
|
|
1039
|
+
const result = [];
|
|
1040
|
+
let batch = [];
|
|
1041
|
+
for (const r of ranges) {
|
|
1042
|
+
batch.push(r);
|
|
1043
|
+
const batchTokens = batch.reduce((s, x) => s + x.tokens, 0);
|
|
1044
|
+
if (batchTokens * 4 >= minChars) {
|
|
1045
|
+
result.push(mergeBatch(batch));
|
|
1046
|
+
batch = [];
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (batch.length > 0) {
|
|
1050
|
+
result.push(mergeBatch(batch));
|
|
1051
|
+
}
|
|
1052
|
+
return result;
|
|
1053
|
+
}
|
|
1015
1054
|
function runPipeline(nodes, initial, ctx) {
|
|
1016
1055
|
let io = initial;
|
|
1017
1056
|
for (const node of nodes) {
|
|
@@ -1291,7 +1330,10 @@ var recommendNode = {
|
|
|
1291
1330
|
const nothingToCompress = contextRanges.compressible.length === 0;
|
|
1292
1331
|
const recommendation = {
|
|
1293
1332
|
contextRanges,
|
|
1294
|
-
recommendedRanges:
|
|
1333
|
+
recommendedRanges: mergeRangesToThreshold(
|
|
1334
|
+
contextRanges.compressible,
|
|
1335
|
+
ctx.config.compress.minCompressRange
|
|
1336
|
+
),
|
|
1295
1337
|
nothingToCompress
|
|
1296
1338
|
};
|
|
1297
1339
|
return { ...io, effects: { ...io.effects, recommendation } };
|
|
@@ -1317,6 +1359,7 @@ var nudgeNode = {
|
|
|
1317
1359
|
if (baseline > 0 && ctx.tokenCount < baseline - nudgeGrowthTokens) {
|
|
1318
1360
|
stamped.lastPerMessageNudgeTokens = ctx.tokenCount;
|
|
1319
1361
|
stamped.lastNudgeShownTokens = 0;
|
|
1362
|
+
stamped.lastShownByTier = {};
|
|
1320
1363
|
}
|
|
1321
1364
|
if (stamped.lastPerMessageNudgeTokens === 0) {
|
|
1322
1365
|
stamped.lastPerMessageNudgeTokens = ctx.tokenCount;
|
|
@@ -1585,10 +1628,11 @@ function resolveAdaptiveGrowth(modelContextLimit, nudge) {
|
|
|
1585
1628
|
)
|
|
1586
1629
|
);
|
|
1587
1630
|
}
|
|
1588
|
-
function pendingByTier(state, recommendation, countTokens) {
|
|
1631
|
+
function pendingByTier(state, recommendation, countTokens, minCompressRange) {
|
|
1589
1632
|
const out = {};
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1633
|
+
const merged = recommendation?.recommendedRanges ?? [];
|
|
1634
|
+
const effective = minCompressRange > 0 ? merged.filter((r) => r.tokens * 4 >= minCompressRange) : merged;
|
|
1635
|
+
out[1] = { pending: effective.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };
|
|
1592
1636
|
const active = activeBlocks(state);
|
|
1593
1637
|
const t1 = active.filter((b) => b.tier === 1);
|
|
1594
1638
|
const t2 = active.filter((b) => b.tier === 2);
|
|
@@ -1603,6 +1647,7 @@ function decideNudge(input) {
|
|
|
1603
1647
|
const nudgeGrowthTokens = resolveAdaptiveGrowth(limit, config.nudge);
|
|
1604
1648
|
const overLimit = usage >= config.nudge.maxContextLimitPct;
|
|
1605
1649
|
const emergencyOverride = usage >= config.nudge.emergencyThresholdPct;
|
|
1650
|
+
const pressure = overLimit || emergencyOverride;
|
|
1606
1651
|
const baseline = state.nudge.lastPerMessageNudgeTokens;
|
|
1607
1652
|
const hadPendingNudge = state.nudge.lastNudgeShownTokens > 0;
|
|
1608
1653
|
const hasPendingNudge = hadPendingNudge;
|
|
@@ -1614,44 +1659,67 @@ function decideNudge(input) {
|
|
|
1614
1659
|
);
|
|
1615
1660
|
const growthSinceReference = tokenCount - growthReference;
|
|
1616
1661
|
const rec = recommendation;
|
|
1617
|
-
const tiers = pendingByTier(
|
|
1662
|
+
const tiers = pendingByTier(
|
|
1663
|
+
state,
|
|
1664
|
+
rec,
|
|
1665
|
+
countTokens,
|
|
1666
|
+
config.compress.minCompressRange
|
|
1667
|
+
);
|
|
1668
|
+
const tier2Threshold = Math.round(
|
|
1669
|
+
nudgeGrowthTokens * (config.nudge.tier2GrowthMultiplier ?? 1.5)
|
|
1670
|
+
);
|
|
1618
1671
|
let injectedTier = null;
|
|
1619
1672
|
let injectedReason = "";
|
|
1620
1673
|
const growthReady = growthSinceReference >= growthFloor;
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1674
|
+
const t1Eff = tiers[1]?.pending ?? 0;
|
|
1675
|
+
const t2Pen = tiers[2]?.pending ?? 0;
|
|
1676
|
+
const t3Pen = tiers[3]?.pending ?? 0;
|
|
1677
|
+
if (pressure) {
|
|
1678
|
+
const candidates = [1];
|
|
1679
|
+
if (config.tiers.enabled) {
|
|
1680
|
+
candidates.push(2, 3);
|
|
1681
|
+
}
|
|
1682
|
+
let best = null;
|
|
1683
|
+
let bestPending = 0;
|
|
1684
|
+
for (const t of candidates) {
|
|
1685
|
+
const p = tiers[t]?.pending ?? 0;
|
|
1686
|
+
if (p > bestPending) {
|
|
1687
|
+
bestPending = p;
|
|
1688
|
+
best = t;
|
|
1689
|
+
}
|
|
1632
1690
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1691
|
+
if (best !== null && bestPending > 0) {
|
|
1692
|
+
injectedTier = best;
|
|
1693
|
+
const label = emergencyOverride ? "EMERGENCY" : "OVER-LIMIT";
|
|
1694
|
+
injectedReason = best === 1 ? `${label} T1: max effective pending ${bestPending}, usage ${Math.round(usage * 100)}%` : `${label} T${best} distill: max pending ${bestPending} (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}), usage ${Math.round(usage * 100)}%`;
|
|
1695
|
+
}
|
|
1696
|
+
} else if (growthReady) {
|
|
1697
|
+
if (t1Eff >= nudgeGrowthTokens) {
|
|
1698
|
+
injectedTier = 1;
|
|
1699
|
+
injectedReason = `T1 effective ${t1Eff} >= ${nudgeGrowthTokens}, growth ${growthSinceReference}, usage ${Math.round(usage * 100)}%`;
|
|
1700
|
+
} else if (config.tiers.enabled && t2Pen >= tier2Threshold && t2Pen > t1Eff) {
|
|
1701
|
+
const lastShown = state.nudge.lastShownByTier[2] ?? 0;
|
|
1702
|
+
const cadenceMet = lastShown === 0 || tokenCount - lastShown >= growthFloor;
|
|
1703
|
+
if (cadenceMet) {
|
|
1704
|
+
injectedTier = 2;
|
|
1705
|
+
injectedReason = `T2 distill ready: ${tiers[2].targetBlocks.length} tier-1 blocks (${t2Pen} tokens) >= ${tier2Threshold} (1.5x) and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;
|
|
1706
|
+
}
|
|
1707
|
+
} else if (config.tiers.enabled && t3Pen >= tier2Threshold && t3Pen > t2Pen && t3Pen > t1Eff) {
|
|
1708
|
+
const lastShown = state.nudge.lastShownByTier[3] ?? 0;
|
|
1709
|
+
const cadenceMet = lastShown === 0 || tokenCount - lastShown >= growthFloor;
|
|
1710
|
+
if (cadenceMet) {
|
|
1711
|
+
injectedTier = 3;
|
|
1712
|
+
injectedReason = `T3 condense ready: ${tiers[3].targetBlocks.length} tier-2 blocks (${t3Pen} tokens) >= ${tier2Threshold} (1.5x) and > T2 ${t2Pen} and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;
|
|
1713
|
+
}
|
|
1641
1714
|
}
|
|
1642
1715
|
}
|
|
1643
|
-
const shouldInject = injectedTier !== null
|
|
1716
|
+
const shouldInject = injectedTier !== null;
|
|
1644
1717
|
let reason;
|
|
1645
|
-
if (
|
|
1646
|
-
reason = injectedReason;
|
|
1647
|
-
} else if (emergencyOverride) {
|
|
1648
|
-
reason = `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}% (no compressible content)`;
|
|
1649
|
-
} else if (overLimit && injectedTier !== null) {
|
|
1650
|
-
reason = injectedReason;
|
|
1651
|
-
} else if (overLimit) {
|
|
1652
|
-
reason = `OVER-LIMIT: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.maxContextLimitPct * 100)}% (no compressible content)`;
|
|
1653
|
-
} else if (injectedTier !== null) {
|
|
1718
|
+
if (injectedTier !== null) {
|
|
1654
1719
|
reason = injectedReason;
|
|
1720
|
+
} else if (pressure) {
|
|
1721
|
+
const label = emergencyOverride ? "EMERGENCY" : "OVER-LIMIT";
|
|
1722
|
+
reason = `${label}: usage ${Math.round(usage * 100)}% but no tier has effective compressible content (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}) \u2014 nudge suppressed to avoid offering ranges below minCompressRange`;
|
|
1655
1723
|
} else {
|
|
1656
1724
|
const tiersList = [1, 2, 3];
|
|
1657
1725
|
const eligible = tiersList.filter((t) => config.tiers.enabled || t === 1);
|
|
@@ -2000,20 +2068,23 @@ ${lines.join("\n")}`;
|
|
|
2000
2068
|
function renderNudgeText(decision, prompts = defaultPrompts) {
|
|
2001
2069
|
const breakdownStr = formatBreakdown(decision.contextBreakdown);
|
|
2002
2070
|
const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);
|
|
2071
|
+
const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
|
|
2003
2072
|
if (decision.tier !== null && decision.tier >= 2) {
|
|
2004
2073
|
const isT2 = decision.tier === 2;
|
|
2005
2074
|
const targets = decision.tierTargetBlocks ?? [];
|
|
2006
2075
|
const blockList = formatTierTargetBlocks(targets);
|
|
2007
2076
|
const startId = targets[0]?.blockId ?? "b1";
|
|
2008
2077
|
const endId = targets[targets.length - 1]?.blockId ?? "b5";
|
|
2078
|
+
const voice = isEmergency ? "emergency" : "gentle";
|
|
2079
|
+
const triggerLine = isEmergency ? `[EMERGENCY \u2014 TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"}] Context limit reached \u2014 distill NOW into a denser summary to reclaim tokens.` : `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`;
|
|
2009
2080
|
return {
|
|
2010
|
-
voice
|
|
2081
|
+
voice,
|
|
2011
2082
|
text: [
|
|
2012
2083
|
efficiencyNote(prompts),
|
|
2013
2084
|
"",
|
|
2014
2085
|
breakdownStr,
|
|
2015
2086
|
"",
|
|
2016
|
-
|
|
2087
|
+
triggerLine,
|
|
2017
2088
|
isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
|
|
2018
2089
|
blockList,
|
|
2019
2090
|
`Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
|
|
@@ -2024,7 +2095,6 @@ function renderNudgeText(decision, prompts = defaultPrompts) {
|
|
|
2024
2095
|
].join("\n")
|
|
2025
2096
|
};
|
|
2026
2097
|
}
|
|
2027
|
-
const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
|
|
2028
2098
|
if (isEmergency) {
|
|
2029
2099
|
return {
|
|
2030
2100
|
voice: "emergency",
|
|
@@ -2388,9 +2458,9 @@ function charBigrams(text) {
|
|
|
2388
2458
|
}
|
|
2389
2459
|
return grams;
|
|
2390
2460
|
}
|
|
2391
|
-
function tfMap(text,
|
|
2461
|
+
function tfMap(text, stem22) {
|
|
2392
2462
|
const m = /* @__PURE__ */ new Map();
|
|
2393
|
-
for (const t of tokenize(text, { stem:
|
|
2463
|
+
for (const t of tokenize(text, { stem: stem22 })) m.set(t, (m.get(t) ?? 0) + 1);
|
|
2394
2464
|
return m;
|
|
2395
2465
|
}
|
|
2396
2466
|
var bm25Algorithm = {
|
|
@@ -2784,6 +2854,13 @@ function compressToolArgs(call) {
|
|
|
2784
2854
|
function projectMessage(message, id) {
|
|
2785
2855
|
const msg = message;
|
|
2786
2856
|
const role = msg.role;
|
|
2857
|
+
if (role === "compactionSummary" || role === "branchSummary") {
|
|
2858
|
+
const text = (msg.summary ?? "").trim();
|
|
2859
|
+
if (text.length === 0) return [];
|
|
2860
|
+
const label = role === "branchSummary" ? "branch summary" : "compaction summary";
|
|
2861
|
+
return [{ id, role: "user", contentType: "text", text: `${SUMMARY_HEADER} \u2014 omp ${label}
|
|
2862
|
+
${text}` }];
|
|
2863
|
+
}
|
|
2787
2864
|
if (role === "user") {
|
|
2788
2865
|
return [{ id, role: "user", contentType: "text", text: extractText(msg.content) }];
|
|
2789
2866
|
}
|
|
@@ -3310,7 +3387,17 @@ function makeCompressTool(runtime) {
|
|
|
3310
3387
|
return {
|
|
3311
3388
|
name: "compress",
|
|
3312
3389
|
label: "Compress",
|
|
3313
|
-
|
|
3390
|
+
// Stay a top-level tool. Extension tools default to "discoverable", which
|
|
3391
|
+
// omp's tools.xdev mounts behind the xd://compress device — forcing the
|
|
3392
|
+
// model to hand-write JSON-inside-a-JSON-string via the write tool. That
|
|
3393
|
+
// double-escaping layer was the direct cause of issue #21's parse errors
|
|
3394
|
+
// and truncated write calls; top-level structured args eliminate it.
|
|
3395
|
+
// (Found independently in #36, which also surfaced that device-mounted
|
|
3396
|
+
// descriptions are capped at 200 chars — XDEV_EXTERNAL_DESCRIPTION_CAP in
|
|
3397
|
+
// the host — so the escaping guidance in this description never even
|
|
3398
|
+
// reached the model while device-mounted.)
|
|
3399
|
+
loadMode: "essential",
|
|
3400
|
+
description: 'Replace older conversation ranges with detailed summaries you write. Single range: compress({ content: [{ "topic": "Session Opener", "startId": "m00004", "endId": "m00022", "summary": "..." }] }) \u2014 a short topic label is recommended but optional. Batch: one entry per range in content[].',
|
|
3314
3401
|
parameters: CompressParams,
|
|
3315
3402
|
async execute(toolCallId, params, _signal, _onUpdate, ctx) {
|
|
3316
3403
|
let result;
|
|
@@ -3435,6 +3522,9 @@ function makeDecompressTool(runtime) {
|
|
|
3435
3522
|
return {
|
|
3436
3523
|
name: "decompress",
|
|
3437
3524
|
label: "Decompress",
|
|
3525
|
+
// Top-level (see compress-tool.ts): the xd:// device indirection trades
|
|
3526
|
+
// a few schema tokens per request for a JSON-in-JSON escaping trap.
|
|
3527
|
+
loadMode: "essential",
|
|
3438
3528
|
description: "Restore a previously compressed block's content, or a single message by its ref. The block/message stays compressed \u2014 context and cache prefix are not disrupted. BLOCK decompress (blockId b5) defaults to writing a file (blocks can be large); use the read tool to access it, or inline:true to return inline. MESSAGE decompress (blockId = a message ref from search_context) returns that ONE message's original text \u2014 defaults to inline since a single message is usually small; oversized messages go to a file. full:true recurses through nested block tiers (block mode only). You can pass a block id (b5) OR a message ref (e.g. m00123) from search_context results.",
|
|
3439
3529
|
parameters: DecompressParams,
|
|
3440
3530
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -3629,6 +3719,9 @@ function makeSearchTool(runtime) {
|
|
|
3629
3719
|
return {
|
|
3630
3720
|
name: "search_context",
|
|
3631
3721
|
label: "Search Context",
|
|
3722
|
+
// Top-level (see compress-tool.ts): schema is tiny; the xd://
|
|
3723
|
+
// indirection costs more in call friction than it saves in tokens.
|
|
3724
|
+
loadMode: "essential",
|
|
3632
3725
|
description: "Search compressed blocks AND historical messages by keyword. Use to cheaply locate detail before decompressing. Returns ranked results with ref, size, preview, and the decompress command to retrieve full content.",
|
|
3633
3726
|
parameters: SearchParams,
|
|
3634
3727
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -3680,6 +3773,437 @@ function truncate(s, n) {
|
|
|
3680
3773
|
// src/status-tool.ts
|
|
3681
3774
|
import { type as type4 } from "@oh-my-pi/omptype";
|
|
3682
3775
|
|
|
3776
|
+
// src/compat.ts
|
|
3777
|
+
function normalizeSystemPrompt(input) {
|
|
3778
|
+
if (input === void 0) return "";
|
|
3779
|
+
if (Array.isArray(input)) return input.join("\n");
|
|
3780
|
+
return input;
|
|
3781
|
+
}
|
|
3782
|
+
function formatSystemPromptForEvent(base, append) {
|
|
3783
|
+
const normalized = normalizeSystemPrompt(base);
|
|
3784
|
+
return [`${normalized}
|
|
3785
|
+
|
|
3786
|
+
${append}`];
|
|
3787
|
+
}
|
|
3788
|
+
function getSystemPromptText(ctx) {
|
|
3789
|
+
const result = ctx.getSystemPrompt?.();
|
|
3790
|
+
return normalizeSystemPrompt(result);
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
// node_modules/billion-context-kit/node_modules/acp-kernel/dist/index.js
|
|
3794
|
+
import { createRequire as createRequire2 } from "module";
|
|
3795
|
+
var BLOCKED_REF2 = "BLOCKED";
|
|
3796
|
+
function refForRaw2(map, rawId) {
|
|
3797
|
+
return map.byRaw[rawId] ?? null;
|
|
3798
|
+
}
|
|
3799
|
+
var require22 = createRequire2(import.meta.url);
|
|
3800
|
+
function defaultCountTokens2(text) {
|
|
3801
|
+
if (!text) return 0;
|
|
3802
|
+
const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
|
|
3803
|
+
const cjkCount = cjk?.length ?? 0;
|
|
3804
|
+
return cjkCount + Math.ceil((text.length - cjkCount) / 4);
|
|
3805
|
+
}
|
|
3806
|
+
function formatTokens4(tokens) {
|
|
3807
|
+
if (tokens < 1e3) return String(tokens);
|
|
3808
|
+
if (tokens < 1e4) return (tokens / 1e3).toFixed(1) + "K";
|
|
3809
|
+
return Math.round(tokens / 1e3) + "K";
|
|
3810
|
+
}
|
|
3811
|
+
function classifyType2(message) {
|
|
3812
|
+
if (message.contentType === "tool-call" || message.contentType === "tool-result") {
|
|
3813
|
+
return message.toolName || "tool";
|
|
3814
|
+
}
|
|
3815
|
+
return message.contentType;
|
|
3816
|
+
}
|
|
3817
|
+
function escapeRegex2(s) {
|
|
3818
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3819
|
+
}
|
|
3820
|
+
var LT2 = "<";
|
|
3821
|
+
var GT2 = ">";
|
|
3822
|
+
var TAG_OPEN2 = LT2 + "acp ";
|
|
3823
|
+
var TAG_CLOSE2 = LT2 + "/acp" + GT2;
|
|
3824
|
+
function acpTag2(ref, tokens, type5) {
|
|
3825
|
+
return TAG_OPEN2 + 'tokens="' + formatTokens4(tokens) + '" type="' + type5 + '"' + GT2 + ref + TAG_CLOSE2;
|
|
3826
|
+
}
|
|
3827
|
+
function renderMessage2(message, map, countTokens, strategy) {
|
|
3828
|
+
const ref = refForRaw2(map, message.id);
|
|
3829
|
+
if (!ref || ref === BLOCKED_REF2) return message;
|
|
3830
|
+
if (strategy === "none") return message;
|
|
3831
|
+
if (strategy === "text-only" && message.contentType !== "text") {
|
|
3832
|
+
return message;
|
|
3833
|
+
}
|
|
3834
|
+
const ownTagRe = new RegExp(
|
|
3835
|
+
"^" + escapeRegex2(TAG_OPEN2) + "[^>]*" + GT2 + escapeRegex2(ref) + escapeRegex2(TAG_CLOSE2) + "\\n?"
|
|
3836
|
+
);
|
|
3837
|
+
const cleanText = (message.text || "").replace(ownTagRe, "");
|
|
3838
|
+
const tokens = countTokens(cleanText);
|
|
3839
|
+
const type5 = classifyType2(message);
|
|
3840
|
+
const prefix = acpTag2(ref, tokens, type5) + "\n";
|
|
3841
|
+
if (!cleanText) return { ...message, text: prefix };
|
|
3842
|
+
return { ...message, text: prefix + cleanText };
|
|
3843
|
+
}
|
|
3844
|
+
function renderVisibleRefs2(messages, state, countTokens = (text) => Math.ceil(text.length / 4), strategy = "all") {
|
|
3845
|
+
const map = state.messageRefs;
|
|
3846
|
+
return messages.map(
|
|
3847
|
+
(message) => renderMessage2(message, map, countTokens, strategy)
|
|
3848
|
+
);
|
|
3849
|
+
}
|
|
3850
|
+
function createRenderRefsNode2(strategy) {
|
|
3851
|
+
return {
|
|
3852
|
+
name: "render-refs",
|
|
3853
|
+
run(io, ctx) {
|
|
3854
|
+
return {
|
|
3855
|
+
...io,
|
|
3856
|
+
messages: renderVisibleRefs2(io.messages, io.state, ctx.countTokens, strategy)
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3861
|
+
var renderRefsNode2 = createRenderRefsNode2("all");
|
|
3862
|
+
var COMPRESS_PHILOSOPHY2 = `Compression Philosophy:
|
|
3863
|
+
- All compression serves the primary task, but be frugal.
|
|
3864
|
+
- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
|
|
3865
|
+
- Compress by need, not by percentage.
|
|
3866
|
+
- Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.`;
|
|
3867
|
+
var HOW_TO_COMPRESS_RULES2 = `HOW TO COMPRESS
|
|
3868
|
+
|
|
3869
|
+
When you call \`compress\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original.
|
|
3870
|
+
|
|
3871
|
+
KEEP VERBATIM \u2014 never paraphrase or abbreviate these:
|
|
3872
|
+
- Full file paths with line numbers, directory prefix on every mention (\`lib/hooks.ts:347\`, \`src/index.ts:12-18\`, \`gatenet_v3/model.py:45\`). Never abbreviate to a bare filename (\`hooks.ts\`, \`model.py\`) \u2014 they are ambiguous and cannot be grepped or decompressed-to later.
|
|
3873
|
+
- Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic \u2014 the line that IS the finding, not just the function name (e.g. \`kv_keys += define_gate * a_key[i](emb)\` is more useful than "see model_kvnet.py").
|
|
3874
|
+
- Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).
|
|
3875
|
+
- Key details from reports and analyses \u2014 not just the conclusion. Keep the comparison numbers and the mechanism, not "X is worse" alone (write "1.76\xD7 PPL gap because KV store is static", not "KVNet underperforms").
|
|
3876
|
+
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing; without it the decision looks arbitrary).
|
|
3877
|
+
- Constraints discovered ("must support Node 22", "no new dependencies", "AGENTS.md forbids \`as any\`").
|
|
3878
|
+
- Exact values: versions, config keys, thresholds, magic numbers.
|
|
3879
|
+
- User intent \u2014 quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., "User said: ..."), not as current directives. Losing these changes the task itself.
|
|
3880
|
+
- The user's overall goal and any changes to it \u2014 the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., "initially: fix bug X \u2192 pivoted to: refactor module Y after discovering root cause"). Losing the goal or its evolution makes all subsequent work appear unmotivated.
|
|
3881
|
+
- Purpose behind each significant action \u2014 preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.
|
|
3882
|
+
- Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.
|
|
3883
|
+
- Message refs of key anchors (\`m00420\`, \`m00510\u2013m00520\`) \u2014 they let you or a later reader jump back via decompress to the exact original.
|
|
3884
|
+
|
|
3885
|
+
DROP \u2014 extract the signal, discard the vessel:
|
|
3886
|
+
- Verbose logs (build/test/\`npm\` output) once you have captured the error line or the result.
|
|
3887
|
+
- Duplicate file reads once the needed content is recorded.
|
|
3888
|
+
- Consumed exploration \u2014 search hits, agent return values, successful tool outputs \u2014 once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).
|
|
3889
|
+
- Dead-end exploration \u2014 but PRESERVE the lesson in one line: "tried X, failed because Y".
|
|
3890
|
+
- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).
|
|
3891
|
+
- Repeated status checks (\`git status\`, \`ls\`) once state is known.
|
|
3892
|
+
|
|
3893
|
+
For each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers \u2014 not where it lives. Bad: "probe script at /path/probe_kvnet.py". Good: "probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention." This lets a later decompress target the right block by relevance, not by guessing locations.
|
|
3894
|
+
|
|
3895
|
+
PRIORITY \u2014 when the summary must be compact, preserve in this order:
|
|
3896
|
+
1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).
|
|
3897
|
+
2. Decisions and rationale.
|
|
3898
|
+
3. Exact technical artifacts: paths, signatures, errors, values.
|
|
3899
|
+
4. Conclusions and key findings.
|
|
3900
|
+
5. Lessons learned: what failed and why.
|
|
3901
|
+
|
|
3902
|
+
Write dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;
|
|
3903
|
+
var TIER2_DISTILL_RULES2 = `TIER 2 COMPRESSION \u2014 DISTILLATION
|
|
3904
|
+
|
|
3905
|
+
You are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.
|
|
3906
|
+
|
|
3907
|
+
KEEP \u2014 these are the only things that survive distillation:
|
|
3908
|
+
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing).
|
|
3909
|
+
- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.
|
|
3910
|
+
- Key lessons: what failed and why ("tried X, failed because Y"). These prevent repeating mistakes.
|
|
3911
|
+
- Critical constraints discovered ("must support Node 22", "AGENTS.md forbids as any").
|
|
3912
|
+
- Design decisions with architectural impact ("chose compress-as-anchor over synthetic messages because prefix cache").
|
|
3913
|
+
- Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: "[SUPERSEDED by PR #NNN]" or "[OBSOLETE: deleted in vX.Y.Z]". Do NOT keep the obsolete content's details \u2014 just the marker and reason.
|
|
3914
|
+
- Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., "fixed filterCompressedRanges in prune.ts", "added SessionStateRegistry in state.ts". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.
|
|
3915
|
+
- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line ("explored X, not viable because Y"). Do not keep the exploration process.
|
|
3916
|
+
|
|
3917
|
+
DROP \u2014 these were useful during the work but are no longer needed:
|
|
3918
|
+
- Exact line numbers, diffs, verbose function signatures, full code listings.
|
|
3919
|
+
- Build/deploy process details, test execution steps.
|
|
3920
|
+
- Review process details (who reviewed, what rounds, test counts).
|
|
3921
|
+
- Verbose logs, command output, intermediate debugging steps.
|
|
3922
|
+
|
|
3923
|
+
FORMAT:
|
|
3924
|
+
- Start each distilled block with a source header line:
|
|
3925
|
+
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
3926
|
+
Example: \`Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]\`
|
|
3927
|
+
- 3-5 bullet points per source block, each a self-contained fact.
|
|
3928
|
+
- Dense, scannable \u2014 no narrative prose.
|
|
3929
|
+
- Start with the outcome, not the process: "v1.13.0 shipped (7 PRs bundled)" not "implemented 7 PRs then reviewed then merged".
|
|
3930
|
+
- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.
|
|
3931
|
+
|
|
3932
|
+
SIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by "[no actionable content]."`;
|
|
3933
|
+
var TIER3_CONDENSE_RULES2 = `TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION
|
|
3934
|
+
|
|
3935
|
+
You are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.
|
|
3936
|
+
|
|
3937
|
+
PRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:
|
|
3938
|
+
1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.
|
|
3939
|
+
2. Open work (PRs/issues still pending) \u2014 these may need follow-up.
|
|
3940
|
+
3. Key decisions with architectural impact ("chose X over Y because Z").
|
|
3941
|
+
4. Critical constraints ("must support Node 22").
|
|
3942
|
+
Drop everything else. Tier 3 is a lookup index, not a knowledge base.
|
|
3943
|
+
|
|
3944
|
+
FORMAT:
|
|
3945
|
+
- Start with a source header line:
|
|
3946
|
+
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
3947
|
+
- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.
|
|
3948
|
+
- No explanations, no rationale, no process \u2014 just the fact.
|
|
3949
|
+
- Format: "[PR/Issue/Version] \u2014 [outcome in \u22648 words]"
|
|
3950
|
+
- Merge related facts from different source blocks if they concern the same topic.
|
|
3951
|
+
|
|
3952
|
+
EXAMPLES:
|
|
3953
|
+
- "v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)"
|
|
3954
|
+
- "PR #196 merged \u2014 preserve-first-user (supersedes #169)"
|
|
3955
|
+
- "Bug 1214 fixed \u2014 compress consumed all user messages"
|
|
3956
|
+
- "Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection"
|
|
3957
|
+
- "Constraint: AGENTS.md forbids as any \u2014 never suppress types"
|
|
3958
|
+
|
|
3959
|
+
DROP:
|
|
3960
|
+
- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.
|
|
3961
|
+
- Lessons learned ("tried X, failed because Y") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.
|
|
3962
|
+
- Design rationale details \u2014 keep the decision, drop the "because" unless it's a critical constraint.
|
|
3963
|
+
- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
|
|
3964
|
+
|
|
3965
|
+
SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
|
|
3966
|
+
var defaultPrompts2 = Object.freeze({
|
|
3967
|
+
compressPhilosophy: COMPRESS_PHILOSOPHY2,
|
|
3968
|
+
howToCompressRules: HOW_TO_COMPRESS_RULES2,
|
|
3969
|
+
tier2DistillRules: TIER2_DISTILL_RULES2,
|
|
3970
|
+
tier3CondenseRules: TIER3_CONDENSE_RULES2
|
|
3971
|
+
});
|
|
3972
|
+
function formatK2(n) {
|
|
3973
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
3974
|
+
return `${n}`;
|
|
3975
|
+
}
|
|
3976
|
+
function formatRanges2(compressible, protectedRanges) {
|
|
3977
|
+
if (compressible.length === 0 && protectedRanges.length === 0) {
|
|
3978
|
+
return "[No specific ranges detected \u2014 compress any consumed content.]";
|
|
3979
|
+
}
|
|
3980
|
+
const refNum2 = (ref) => {
|
|
3981
|
+
const m = ref.match(/\d+/);
|
|
3982
|
+
return m ? parseInt(m[0], 10) : 0;
|
|
3983
|
+
};
|
|
3984
|
+
const entries = [];
|
|
3985
|
+
for (const r of compressible) {
|
|
3986
|
+
entries.push({
|
|
3987
|
+
startRef: r.startRef,
|
|
3988
|
+
endRef: r.endRef,
|
|
3989
|
+
startNum: refNum2(r.startRef),
|
|
3990
|
+
endNum: refNum2(r.endRef),
|
|
3991
|
+
count: r.count,
|
|
3992
|
+
tokens: r.tokens,
|
|
3993
|
+
toolPct: r.toolPct,
|
|
3994
|
+
textPct: r.textPct,
|
|
3995
|
+
compressibleTokens: r.tokens,
|
|
3996
|
+
compressibleCount: r.count,
|
|
3997
|
+
protectedTokens: 0,
|
|
3998
|
+
protectedCount: 0,
|
|
3999
|
+
protectedTools: [],
|
|
4000
|
+
dangerous: r.dangerous ?? false
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
for (const r of protectedRanges) {
|
|
4004
|
+
entries.push({
|
|
4005
|
+
startRef: r.startRef,
|
|
4006
|
+
endRef: r.endRef,
|
|
4007
|
+
startNum: refNum2(r.startRef),
|
|
4008
|
+
endNum: refNum2(r.endRef),
|
|
4009
|
+
count: r.count,
|
|
4010
|
+
tokens: r.tokens,
|
|
4011
|
+
toolPct: 0,
|
|
4012
|
+
textPct: 0,
|
|
4013
|
+
compressibleTokens: 0,
|
|
4014
|
+
compressibleCount: 0,
|
|
4015
|
+
protectedTokens: r.tokens,
|
|
4016
|
+
protectedCount: r.count,
|
|
4017
|
+
protectedTools: [...r.tools],
|
|
4018
|
+
dangerous: false
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
entries.sort((a, b) => a.startNum - b.startNum);
|
|
4022
|
+
const merged = [];
|
|
4023
|
+
for (const e of entries) {
|
|
4024
|
+
const last = merged[merged.length - 1];
|
|
4025
|
+
if (last && e.startNum <= last.endNum + 1) {
|
|
4026
|
+
last.endRef = e.endRef;
|
|
4027
|
+
last.endNum = Math.max(last.endNum, e.endNum);
|
|
4028
|
+
last.count += e.count;
|
|
4029
|
+
last.tokens += e.tokens;
|
|
4030
|
+
last.compressibleTokens += e.compressibleTokens;
|
|
4031
|
+
last.compressibleCount += e.compressibleCount;
|
|
4032
|
+
last.protectedTokens += e.protectedTokens;
|
|
4033
|
+
last.protectedCount += e.protectedCount;
|
|
4034
|
+
if (e.dangerous) last.dangerous = true;
|
|
4035
|
+
for (const t of e.protectedTools) {
|
|
4036
|
+
if (!last.protectedTools.includes(t)) last.protectedTools.push(t);
|
|
4037
|
+
}
|
|
4038
|
+
} else {
|
|
4039
|
+
merged.push({ ...e });
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
const lines = merged.map((e) => {
|
|
4043
|
+
const suffix = e.dangerous && e.compressibleTokens > 0 ? " \u26A0\uFE0F NOT recommended unless you are certain." : "";
|
|
4044
|
+
if (e.protectedTokens > 0 && e.compressibleTokens === 0) {
|
|
4045
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK2(e.tokens)} [PROTECTED: ${e.protectedTools.join(", ")} \u2014 not compressible]${suffix}`;
|
|
4046
|
+
}
|
|
4047
|
+
if (e.protectedTokens > 0 && e.compressibleTokens > 0) {
|
|
4048
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK2(e.tokens)} [${formatK2(e.compressibleTokens)} compressible | ${formatK2(e.protectedTokens)} protected: ${e.protectedTools.join(", ")}]${suffix}`;
|
|
4049
|
+
}
|
|
4050
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK2(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;
|
|
4051
|
+
});
|
|
4052
|
+
return `Compressible ranges (${merged.length}, oldest first):
|
|
4053
|
+
${lines.join("\n")}`;
|
|
4054
|
+
}
|
|
4055
|
+
var substringAlgorithm2 = {
|
|
4056
|
+
name: "substring",
|
|
4057
|
+
description: "Exact substring counting (original baseline). Predictable, no normalization.",
|
|
4058
|
+
score(docs, query) {
|
|
4059
|
+
const terms = query.toLowerCase().trim().split(/\s+/).filter((t) => t.length > 0);
|
|
4060
|
+
if (terms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
4061
|
+
return docs.map((d) => {
|
|
4062
|
+
const haystack = d.text.toLowerCase();
|
|
4063
|
+
let score = 0;
|
|
4064
|
+
for (const term of terms) score += countOccurrences22(haystack, term);
|
|
4065
|
+
return { ref: d.ref, score };
|
|
4066
|
+
});
|
|
4067
|
+
}
|
|
4068
|
+
};
|
|
4069
|
+
function countOccurrences22(haystack, needle) {
|
|
4070
|
+
if (!needle) return 0;
|
|
4071
|
+
return haystack.split(needle).length - 1;
|
|
4072
|
+
}
|
|
4073
|
+
function stem2(word) {
|
|
4074
|
+
let w = word;
|
|
4075
|
+
if (w.length <= 3) return w;
|
|
4076
|
+
if (w.endsWith("ies")) w = w.slice(0, -3) + "y";
|
|
4077
|
+
else if (w.endsWith("ses") || w.endsWith("xes") || w.endsWith("zes")) w = w.slice(0, -2);
|
|
4078
|
+
else if (w.endsWith("ches") || w.endsWith("shes")) w = w.slice(0, -2);
|
|
4079
|
+
else if (w.endsWith("s") && !w.endsWith("ss")) w = w.slice(0, -1);
|
|
4080
|
+
if (w.endsWith("ing") && w.length > 5) w = w.slice(0, -3);
|
|
4081
|
+
if (w.endsWith("ed") && w.length > 4) w = w.slice(0, -2);
|
|
4082
|
+
if (w.endsWith("ation") && w.length > 6) w = w.slice(0, -3);
|
|
4083
|
+
else if (w.endsWith("tion") && w.length > 5) w = w.slice(0, -4) + "t";
|
|
4084
|
+
else if (w.endsWith("ion") && w.length > 4) w = w.slice(0, -3);
|
|
4085
|
+
if (w.endsWith("ment") && w.length > 6) w = w.slice(0, -4);
|
|
4086
|
+
if (w.endsWith("ness") && w.length > 6) w = w.slice(0, -4);
|
|
4087
|
+
if (w.endsWith("ly") && w.length > 4) w = w.slice(0, -2);
|
|
4088
|
+
return w;
|
|
4089
|
+
}
|
|
4090
|
+
var CJK2 = /[\u3400-\u9fff\uf900-\ufaff\u3040-\u30ff\uac00-\ud7af]/;
|
|
4091
|
+
var CJK_RUN2 = new RegExp(`${CJK2.source}+`, "g");
|
|
4092
|
+
var LATIN_WORD2 = /[a-z][a-z0-9_]*[a-z0-9]|[a-z0-9]/g;
|
|
4093
|
+
function tokenize2(text, opts = {}) {
|
|
4094
|
+
const lower = text.toLowerCase();
|
|
4095
|
+
const tokens = [];
|
|
4096
|
+
const latin = lower.match(LATIN_WORD2) ?? [];
|
|
4097
|
+
for (let w of latin) {
|
|
4098
|
+
if (w.length >= 2) {
|
|
4099
|
+
if (opts.stem) w = stem2(w);
|
|
4100
|
+
tokens.push(w);
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
const cjkRuns = lower.match(CJK_RUN2) ?? [];
|
|
4104
|
+
for (const run of cjkRuns) {
|
|
4105
|
+
if (run.length === 1) {
|
|
4106
|
+
tokens.push(run);
|
|
4107
|
+
} else {
|
|
4108
|
+
for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2));
|
|
4109
|
+
for (const ch of run) tokens.push(ch);
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
return tokens;
|
|
4113
|
+
}
|
|
4114
|
+
function charBigrams2(text) {
|
|
4115
|
+
const grams = [];
|
|
4116
|
+
for (let i = 0; i < text.length - 1; i++) {
|
|
4117
|
+
const pair = text.slice(i, i + 2);
|
|
4118
|
+
if (pair.trim().length === pair.length) grams.push(pair);
|
|
4119
|
+
}
|
|
4120
|
+
return grams;
|
|
4121
|
+
}
|
|
4122
|
+
function tfMap2(text, stem22) {
|
|
4123
|
+
const m = /* @__PURE__ */ new Map();
|
|
4124
|
+
for (const t of tokenize2(text, { stem: stem22 })) m.set(t, (m.get(t) ?? 0) + 1);
|
|
4125
|
+
return m;
|
|
4126
|
+
}
|
|
4127
|
+
var bm25Algorithm2 = {
|
|
4128
|
+
name: "bm25",
|
|
4129
|
+
description: "BM25 with stemming + CJK bigram tokenization. IR-standard relevance ranking.",
|
|
4130
|
+
score(docs, query) {
|
|
4131
|
+
const N = docs.length;
|
|
4132
|
+
const k1 = 1.2;
|
|
4133
|
+
const b = 0.75;
|
|
4134
|
+
const parsed = docs.map((d) => {
|
|
4135
|
+
const text = d.text;
|
|
4136
|
+
const tf = tfMap2(text, true);
|
|
4137
|
+
let len = 0;
|
|
4138
|
+
for (const v of tf.values()) len += v;
|
|
4139
|
+
return { id: d.ref, tf, len };
|
|
4140
|
+
});
|
|
4141
|
+
const avgdl = parsed.reduce((s, d) => s + d.len, 0) / (N || 1);
|
|
4142
|
+
const qTerms = tokenize2(query, { stem: true });
|
|
4143
|
+
if (qTerms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
4144
|
+
const idf = /* @__PURE__ */ new Map();
|
|
4145
|
+
for (const t of new Set(qTerms)) {
|
|
4146
|
+
let df = 0;
|
|
4147
|
+
for (const d of parsed) if (d.tf.has(t)) df++;
|
|
4148
|
+
idf.set(t, Math.log(1 + (N - df + 0.5) / (df + 0.5)));
|
|
4149
|
+
}
|
|
4150
|
+
return parsed.map((d) => {
|
|
4151
|
+
let score = 0;
|
|
4152
|
+
for (const t of qTerms) {
|
|
4153
|
+
const f = d.tf.get(t) ?? 0;
|
|
4154
|
+
if (f === 0) continue;
|
|
4155
|
+
const idfT = idf.get(t) ?? 0;
|
|
4156
|
+
score += idfT * (f * (k1 + 1)) / (f + k1 * (1 - b + b * d.len / (avgdl || 1)));
|
|
4157
|
+
}
|
|
4158
|
+
return { ref: d.id, score };
|
|
4159
|
+
});
|
|
4160
|
+
}
|
|
4161
|
+
};
|
|
4162
|
+
var fuzzyAlgorithm2 = {
|
|
4163
|
+
name: "fuzzy",
|
|
4164
|
+
description: "Character bigram overlap. Typo-tolerant, script-agnostic, high recall.",
|
|
4165
|
+
score(docs, query) {
|
|
4166
|
+
const qTokens = query.toLowerCase().split(/[\s,]+/).filter((t) => t.length >= 4);
|
|
4167
|
+
if (qTokens.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
4168
|
+
const qGrams = /* @__PURE__ */ new Set();
|
|
4169
|
+
for (const t of qTokens) for (const g of charBigrams2(t)) qGrams.add(g);
|
|
4170
|
+
if (qGrams.size === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
|
|
4171
|
+
return docs.map((d) => {
|
|
4172
|
+
const haystack = d.text.toLowerCase();
|
|
4173
|
+
const docGrams = new Set(charBigrams2(haystack));
|
|
4174
|
+
let hits = 0;
|
|
4175
|
+
for (const g of qGrams) if (docGrams.has(g)) hits++;
|
|
4176
|
+
return { ref: d.ref, score: hits / qGrams.size };
|
|
4177
|
+
});
|
|
4178
|
+
}
|
|
4179
|
+
};
|
|
4180
|
+
var W_BM252 = 0.7;
|
|
4181
|
+
var W_FUZZY2 = 0.3;
|
|
4182
|
+
var hybridAlgorithm2 = {
|
|
4183
|
+
name: "hybrid",
|
|
4184
|
+
description: "Weighted BM25(stem) + fuzzy n-gram. Default \u2014 best precision + recall.",
|
|
4185
|
+
score(docs, query) {
|
|
4186
|
+
const bm = bm25Algorithm2.score(docs, query);
|
|
4187
|
+
const fz = fuzzyAlgorithm2.score(docs, query);
|
|
4188
|
+
const maxBm = Math.max(...bm.map((r) => r.score), 1e-9);
|
|
4189
|
+
const maxFz = Math.max(...fz.map((r) => r.score), 1e-9);
|
|
4190
|
+
const bmMap = new Map(bm.map((r) => [r.ref, r.score / maxBm]));
|
|
4191
|
+
const fzMap = new Map(fz.map((r) => [r.ref, r.score / maxFz]));
|
|
4192
|
+
return docs.map((d) => ({
|
|
4193
|
+
ref: d.ref,
|
|
4194
|
+
score: W_BM252 * (bmMap.get(d.ref) ?? 0) + W_FUZZY2 * (fzMap.get(d.ref) ?? 0)
|
|
4195
|
+
}));
|
|
4196
|
+
}
|
|
4197
|
+
};
|
|
4198
|
+
var registry22 = /* @__PURE__ */ new Map();
|
|
4199
|
+
function registerSearchAlgorithm2(algo) {
|
|
4200
|
+
registry22.set(algo.name, algo);
|
|
4201
|
+
}
|
|
4202
|
+
registerSearchAlgorithm2(substringAlgorithm2);
|
|
4203
|
+
registerSearchAlgorithm2(bm25Algorithm2);
|
|
4204
|
+
registerSearchAlgorithm2(fuzzyAlgorithm2);
|
|
4205
|
+
registerSearchAlgorithm2(hybridAlgorithm2);
|
|
4206
|
+
|
|
3683
4207
|
// node_modules/billion-context-kit/dist/index.js
|
|
3684
4208
|
var VIABLE_RANGE_MIN_TOKENS = 200;
|
|
3685
4209
|
function viableRanges(ranges) {
|
|
@@ -3710,9 +4234,10 @@ function buildStatusPanel(input) {
|
|
|
3710
4234
|
const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
|
|
3711
4235
|
const systemPromptTokens = input.systemPromptTokens;
|
|
3712
4236
|
const sentTotal = classified + systemPromptTokens;
|
|
3713
|
-
const sessionOnly = Math.max(0,
|
|
4237
|
+
const sessionOnly = input.unprunedTokens !== void 0 ? Math.max(0, input.unprunedTokens - sentTotal) : 0;
|
|
3714
4238
|
const displayTotal = tokenCount;
|
|
3715
4239
|
const displayPct = limit > 0 ? Math.round(displayTotal / limit * 100) : 0;
|
|
4240
|
+
const sentPct = limit > 0 ? Math.round(sentTotal / limit * 100) : 0;
|
|
3716
4241
|
const activeBlocksList = state.blocks.filter((b) => b.active);
|
|
3717
4242
|
const totalBlocksList = state.blocks;
|
|
3718
4243
|
const lines = [];
|
|
@@ -3721,16 +4246,16 @@ function buildStatusPanel(input) {
|
|
|
3721
4246
|
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");
|
|
3722
4247
|
if (input.version) lines.push(input.version);
|
|
3723
4248
|
lines.push("");
|
|
3724
|
-
lines.push(`Context (session accounting): ${displayPct}% (${fmt2(displayTotal)} / ${fmt2(limit)})`);
|
|
4249
|
+
lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt2(displayTotal)} / ${fmt2(limit)}) \u2014 never shrinks; includes compressed originals`);
|
|
3725
4250
|
if (nudge && bd) {
|
|
3726
4251
|
const growth = bd.growth;
|
|
3727
4252
|
if (growth > 0 && displayTotal > 0) {
|
|
3728
4253
|
lines.push(`Growth: +${fmt2(growth)} since last nudge`);
|
|
3729
4254
|
}
|
|
3730
4255
|
lines.push("");
|
|
3731
|
-
lines.push(`Sent to LLM (after compression): ${fmt2(sentTotal)}`);
|
|
3732
|
-
if (sessionOnly > 0) {
|
|
3733
|
-
lines.push(`Session-only (compressed originals
|
|
4256
|
+
lines.push(`Sent to LLM (after compression, est.): ${fmt2(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : ""}`);
|
|
4257
|
+
if (input.unprunedTokens !== void 0 && sessionOnly > 0) {
|
|
4258
|
+
lines.push(`Session-only (compressed originals, est.): ${fmt2(sessionOnly)} \u2014 pruned from every request; the footer/nudge still count them`);
|
|
3734
4259
|
}
|
|
3735
4260
|
lines.push("");
|
|
3736
4261
|
lines.push("Token Breakdown (sent view):");
|
|
@@ -3761,14 +4286,14 @@ function buildStatusPanel(input) {
|
|
|
3761
4286
|
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
3762
4287
|
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
3763
4288
|
lines.push("");
|
|
3764
|
-
lines.push(
|
|
4289
|
+
lines.push(formatRanges2(ranges, protectedRanges));
|
|
3765
4290
|
}
|
|
3766
4291
|
if (activeBlocksList.length > 0) {
|
|
3767
4292
|
lines.push("");
|
|
3768
4293
|
lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
|
|
3769
4294
|
for (const b of activeBlocksList) {
|
|
3770
4295
|
const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
|
|
3771
|
-
const summaryTok =
|
|
4296
|
+
const summaryTok = defaultCountTokens2(b.summary || "");
|
|
3772
4297
|
const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;
|
|
3773
4298
|
lines.push(` [${b.blockId}] T${b.tier} ${fmt2(origTok)}\u2192${fmt2(summaryTok)}${topic}`);
|
|
3774
4299
|
}
|
|
@@ -3796,6 +4321,7 @@ function makeStatusTool(runtime) {
|
|
|
3796
4321
|
return {
|
|
3797
4322
|
name: "acp_status",
|
|
3798
4323
|
label: "ACP Status",
|
|
4324
|
+
loadMode: "essential",
|
|
3799
4325
|
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.",
|
|
3800
4326
|
parameters: StatusParams,
|
|
3801
4327
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -3813,13 +4339,16 @@ function makeStatusTool(runtime) {
|
|
|
3813
4339
|
async function handleStatus(args, runtime, ctx) {
|
|
3814
4340
|
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
3815
4341
|
const config = runtime.configFor(ctx);
|
|
3816
|
-
const tokenCount = estimateTokens(coreMessages, collectCoveredMessageIds(state));
|
|
3817
|
-
const realUsage = ctx.getContextUsage?.();
|
|
4342
|
+
const tokenCount = estimateTokens(coreMessages, collectCoveredMessageIds(state)) + estimateTextTokens2(getSystemPromptText(ctx) ?? "");
|
|
3818
4343
|
const turn = runtime.core.processTurn({
|
|
3819
4344
|
messages: coreMessages,
|
|
3820
4345
|
state,
|
|
3821
4346
|
config,
|
|
3822
|
-
|
|
4347
|
+
// Sent-view scale — see src/index.ts context handler. The session-tree
|
|
4348
|
+
// number (ctx.getContextUsage) must never arbitrate emergencies for the
|
|
4349
|
+
// sent view: a tree that outgrew the model window reads as a permanent
|
|
4350
|
+
// 200%+ emergency while the real sent view is a few percent.
|
|
4351
|
+
tokenCount
|
|
3823
4352
|
});
|
|
3824
4353
|
const processed = turn.messages;
|
|
3825
4354
|
const base = buildStatusReport(turn.state, processed, defaultCountTokens, {
|
|
@@ -3848,23 +4377,6 @@ async function handleStatus(args, runtime, ctx) {
|
|
|
3848
4377
|
${extra.join("\n")}` : base;
|
|
3849
4378
|
}
|
|
3850
4379
|
|
|
3851
|
-
// src/compat.ts
|
|
3852
|
-
function normalizeSystemPrompt(input) {
|
|
3853
|
-
if (input === void 0) return "";
|
|
3854
|
-
if (Array.isArray(input)) return input.join("\n");
|
|
3855
|
-
return input;
|
|
3856
|
-
}
|
|
3857
|
-
function formatSystemPromptForEvent(base, append) {
|
|
3858
|
-
const normalized = normalizeSystemPrompt(base);
|
|
3859
|
-
return [`${normalized}
|
|
3860
|
-
|
|
3861
|
-
${append}`];
|
|
3862
|
-
}
|
|
3863
|
-
function getSystemPromptText(ctx) {
|
|
3864
|
-
const result = ctx.getSystemPrompt?.();
|
|
3865
|
-
return normalizeSystemPrompt(result);
|
|
3866
|
-
}
|
|
3867
|
-
|
|
3868
4380
|
// src/commands.ts
|
|
3869
4381
|
function safeHandler(handler) {
|
|
3870
4382
|
return async (args, ctx) => {
|
|
@@ -3946,17 +4458,21 @@ async function statusReport(runtime, ctx) {
|
|
|
3946
4458
|
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
3947
4459
|
const config = runtime.configFor(ctx);
|
|
3948
4460
|
const realUsage = ctx.getContextUsage?.();
|
|
3949
|
-
const
|
|
3950
|
-
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
4461
|
+
const sessionTokens = realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : defaultCountTokens(coreMessages.map((m) => m.text ?? "").join("\n"));
|
|
3951
4462
|
const systemPromptText = getSystemPromptText(ctx);
|
|
3952
|
-
const
|
|
4463
|
+
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
4464
|
+
const coveredIds = collectCoveredMessageIds(state);
|
|
4465
|
+
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
4466
|
+
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: sentTokens });
|
|
4467
|
+
const versionStr = "0.1.8" ? `billion-context-omp@${"0.1.8"}` : void 0;
|
|
3953
4468
|
return buildStatusPanel({
|
|
3954
4469
|
version: versionStr,
|
|
3955
|
-
tokenCount,
|
|
3956
|
-
systemPromptTokens
|
|
4470
|
+
tokenCount: sessionTokens,
|
|
4471
|
+
systemPromptTokens,
|
|
3957
4472
|
state: turn.state,
|
|
3958
4473
|
nudge: turn.nudge,
|
|
3959
|
-
modelContextLimit: config.modelContextLimit
|
|
4474
|
+
modelContextLimit: config.modelContextLimit,
|
|
4475
|
+
unprunedTokens: coreMessages.reduce((sum, m) => sum + defaultCountTokens(m.text ?? ""), 0)
|
|
3960
4476
|
});
|
|
3961
4477
|
}
|
|
3962
4478
|
|
|
@@ -4100,7 +4616,7 @@ TOOLS
|
|
|
4100
4616
|
|
|
4101
4617
|
You have four context-management tools:
|
|
4102
4618
|
|
|
4103
|
-
- compress \u2014 Replace a contiguous range of older conversation with a single detailed summary you write. Use when content is genuinely consumed (no longer needed for the current task step). Single range: compress({ content: [{ topic: "Session Opener", startId: "m00150", endId: "m00220", summary: "..." }] }) \u2014 topic is recommended but optional. Batch (multiple unrelated ranges, each with its own topic): compress({ content: [{ topic: "Auth", startId: "m00150", endId: "m00220", summary: "..." }, { topic: "Deploy", startId: "m00300", endId: "m00350", summary: "..." }] }).
|
|
4619
|
+
- compress \u2014 Replace a contiguous range of older conversation with a single detailed summary you write. Use when content is genuinely consumed (no longer needed for the current task step). Single range: compress({ content: [{ topic: "Session Opener", startId: "m00150", endId: "m00220", summary: "..." }] }) \u2014 topic is recommended but optional. Batch (multiple unrelated ranges, each with its own topic): compress({ content: [{ topic: "Auth", startId: "m00150", endId: "m00220", summary: "..." }, { topic: "Deploy", startId: "m00300", endId: "m00350", summary: "..." }] }). Call it as a normal tool \u2014 summaries are plain string arguments.
|
|
4104
4620
|
- decompress \u2014 Restore a previously compressed block's content. The block stays compressed \u2014 context and cache prefix are not disrupted. By DEFAULT content is written to an auto-generated file (avoids context bloat); use the read tool to view it. Pass inline:true to return content in the tool result instead (appends to context). full:true recurses to original messages. Example: decompress({ blockId: "b5" }) or decompress({ blockId: "b5", full: true }) or decompress({ blockId: "b5", inline: true }).
|
|
4105
4621
|
- search_context \u2014 Search compressed block summaries (and optionally visible messages) by keyword. Use BEFORE decompressing to find the right block. Example: search_context({ query: "auth token refresh" }).
|
|
4106
4622
|
- acp_status \u2014 Context status with compressible ranges. No args = overview + totals. scope:"uncompressed" for range view; add view:"messages" for per-message listing. scope:"compressed" for block details.
|
|
@@ -4268,7 +4784,7 @@ var PACKAGE_NAME = "billion-context-omp";
|
|
|
4268
4784
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
4269
4785
|
var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/;
|
|
4270
4786
|
var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
|
|
4271
|
-
var THROTTLE_FILE = join4(homeDir(), CONFIG_DIR_NAME3, "
|
|
4787
|
+
var THROTTLE_FILE = join4(homeDir(), CONFIG_DIR_NAME3, ".billion-context-omp-update-check");
|
|
4272
4788
|
var updateInFlight = false;
|
|
4273
4789
|
function parseVersion(v) {
|
|
4274
4790
|
return v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -4388,7 +4904,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
4388
4904
|
const data = await res.json();
|
|
4389
4905
|
const latest = data.version;
|
|
4390
4906
|
if (!latest) return;
|
|
4391
|
-
const current = runtimeVersion ?? "0.1.
|
|
4907
|
+
const current = runtimeVersion ?? "0.1.8";
|
|
4392
4908
|
const hasUpdate = isNewer(latest, current);
|
|
4393
4909
|
debug.event("update-check", {
|
|
4394
4910
|
current,
|
|
@@ -4423,10 +4939,27 @@ async function getRuntimeVersion() {
|
|
|
4423
4939
|
}
|
|
4424
4940
|
|
|
4425
4941
|
// src/dump.ts
|
|
4426
|
-
import { mkdirSync as mkdirSync2, writeFileSync, readdirSync } from "fs";
|
|
4942
|
+
import { mkdirSync as mkdirSync2, writeFileSync, readdirSync, unlinkSync } from "fs";
|
|
4427
4943
|
import * as path2 from "path";
|
|
4428
4944
|
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME4 } from "@oh-my-pi/pi-utils";
|
|
4429
4945
|
var counters = {};
|
|
4946
|
+
var MAX_FILES_PER_PREFIX = 200;
|
|
4947
|
+
function pruneDumps(dir, prefixTest, seqOf) {
|
|
4948
|
+
try {
|
|
4949
|
+
const files = readdirSync(dir).filter(prefixTest).map((f) => ({ f, n: seqOf(f) })).filter((x) => !Number.isNaN(x.n));
|
|
4950
|
+
if (files.length <= MAX_FILES_PER_PREFIX) return;
|
|
4951
|
+
files.sort((a, b) => a.n - b.n);
|
|
4952
|
+
const excess = files.slice(0, files.length - MAX_FILES_PER_PREFIX);
|
|
4953
|
+
for (const { f } of excess) {
|
|
4954
|
+
try {
|
|
4955
|
+
unlinkSync(path2.join(dir, f));
|
|
4956
|
+
} catch {
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
debug.event("dump-pruned", { dir, removed: excess.length });
|
|
4960
|
+
} catch {
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4430
4963
|
function dumpDir() {
|
|
4431
4964
|
return path2.join(homeDir(), CONFIG_DIR_NAME4, "acp-omp-dumps");
|
|
4432
4965
|
}
|
|
@@ -4461,6 +4994,7 @@ function dumpContextMessages(messages, meta) {
|
|
|
4461
4994
|
})
|
|
4462
4995
|
);
|
|
4463
4996
|
debug.event("context-out-dump", { path: fullPath, msgs: messages.length });
|
|
4997
|
+
pruneDumps(dir, (f) => /^\d{4}\.json$/.test(f), (f) => parseInt(f, 10));
|
|
4464
4998
|
return fullPath;
|
|
4465
4999
|
} catch (e) {
|
|
4466
5000
|
debug.event("context-out-dump-error", { error: e instanceof Error ? e.message : String(e) });
|
|
@@ -4544,6 +5078,7 @@ function dumpProviderRequest(payload, meta) {
|
|
|
4544
5078
|
})
|
|
4545
5079
|
);
|
|
4546
5080
|
debug.event("provider-request-dump", { path: fullPath, ...summary });
|
|
5081
|
+
pruneDumps(dir, (f) => /^req_\d+\.json$/.test(f), (f) => parseInt(f.slice(4, -5), 10));
|
|
4547
5082
|
return fullPath;
|
|
4548
5083
|
} catch (e) {
|
|
4549
5084
|
debug.event("provider-request-dump-error", { error: e instanceof Error ? e.message : String(e) });
|
|
@@ -4560,11 +5095,12 @@ async function loadUserConfig(cwd) {
|
|
|
4560
5095
|
const merged = {};
|
|
4561
5096
|
for (const base of [join7(home, CONFIG_DIR_NAME5), join7(cwd, CONFIG_DIR_NAME5)]) {
|
|
4562
5097
|
const file = join7(base, "acp-omp.json");
|
|
5098
|
+
const allowPrompts = base.startsWith(home);
|
|
4563
5099
|
try {
|
|
4564
5100
|
const raw = await fs.readFile(file, "utf8");
|
|
4565
5101
|
const parsed = JSON.parse(raw);
|
|
4566
5102
|
if (parsed && typeof parsed === "object") {
|
|
4567
|
-
Object.assign(merged, pickKnown(parsed));
|
|
5103
|
+
Object.assign(merged, pickKnown(parsed, allowPrompts));
|
|
4568
5104
|
debug.event("config-loaded", { file });
|
|
4569
5105
|
}
|
|
4570
5106
|
} catch (e) {
|
|
@@ -4592,11 +5128,15 @@ var KNOWN = /* @__PURE__ */ new Set([
|
|
|
4592
5128
|
"prompts",
|
|
4593
5129
|
"acknowledgePromptsRisk"
|
|
4594
5130
|
]);
|
|
4595
|
-
function pickKnown(parsed) {
|
|
5131
|
+
function pickKnown(parsed, allowPrompts) {
|
|
4596
5132
|
const out = {};
|
|
4597
5133
|
for (const [k, v] of Object.entries(parsed)) {
|
|
4598
5134
|
if (KNOWN.has(k)) out[k] = v;
|
|
4599
5135
|
}
|
|
5136
|
+
if (!allowPrompts) {
|
|
5137
|
+
delete out.prompts;
|
|
5138
|
+
delete out.acknowledgePromptsRisk;
|
|
5139
|
+
}
|
|
4600
5140
|
return out;
|
|
4601
5141
|
}
|
|
4602
5142
|
function applyUserConfig(adapter, user) {
|
|
@@ -4676,7 +5216,7 @@ function wireCompactionDisable(pi, runtime) {
|
|
|
4676
5216
|
function wireSessionLifecycle(pi, runtime) {
|
|
4677
5217
|
pi.on("session_start", async (_event, ctx) => {
|
|
4678
5218
|
const sid = ctx.sessionManager.getSessionId();
|
|
4679
|
-
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.
|
|
5219
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.8" : null });
|
|
4680
5220
|
try {
|
|
4681
5221
|
const user = await loadUserConfig(ctx.cwd);
|
|
4682
5222
|
runtime.setAdapter(applyUserConfig(runtime.adapter, user));
|
|
@@ -4717,18 +5257,17 @@ function wireContextTransform(pi, runtime) {
|
|
|
4717
5257
|
const { state, coreMessages, originalById, streamLen } = runtime.foldStream(ctx, input);
|
|
4718
5258
|
const config = runtime.configFor(ctx);
|
|
4719
5259
|
const coveredIds = collectCoveredMessageIds(state);
|
|
4720
|
-
const
|
|
4721
|
-
const
|
|
4722
|
-
const
|
|
5260
|
+
const systemPromptTokens = estimateTextTokens2(getSystemPromptText(ctx) ?? "");
|
|
5261
|
+
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
5262
|
+
const sessionTokens = ctx.getContextUsage?.()?.tokens ?? null;
|
|
5263
|
+
const tokenCount = sentTokens;
|
|
4723
5264
|
debug.event("context-in", {
|
|
4724
5265
|
sid,
|
|
4725
5266
|
eventMsgs: event.messages?.length ?? 0,
|
|
4726
5267
|
streamLen,
|
|
4727
5268
|
coreMsgs: coreMessages.length,
|
|
4728
5269
|
tokenCount,
|
|
4729
|
-
|
|
4730
|
-
realTokens: realUsage?.tokens ?? null,
|
|
4731
|
-
realPercent: realUsage?.percent ?? null,
|
|
5270
|
+
sessionTokens,
|
|
4732
5271
|
limit: config.modelContextLimit,
|
|
4733
5272
|
blocksBefore: state.blocks.length,
|
|
4734
5273
|
activeBefore: state.blocks.filter((b) => b.active).length
|
|
@@ -4740,7 +5279,8 @@ function wireContextTransform(pi, runtime) {
|
|
|
4740
5279
|
inMsgs: coreMessages.length,
|
|
4741
5280
|
outMsgs: turn.messages.length,
|
|
4742
5281
|
tokens: tokenCount,
|
|
4743
|
-
|
|
5282
|
+
sessionTokens,
|
|
5283
|
+
pct: config.modelContextLimit > 0 ? Math.round(tokenCount / config.modelContextLimit * 100) : null,
|
|
4744
5284
|
limit: config.modelContextLimit,
|
|
4745
5285
|
nudge: turn.nudge?.shouldInject ? turn.nudge.breakdown?.emergencyOverride === 1 ? "emergency" : "active" : "idle",
|
|
4746
5286
|
nudgeReason: turn.nudge?.reason ?? null,
|