opencode-acp 1.14.3 → 1.14.5
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 +18 -23
- package/README.zh-CN.md +18 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +171 -906
- package/dist/index.js.map +1 -1
- package/dist/lib/commands/context.d.ts.map +1 -1
- package/dist/lib/commands/help.d.ts.map +1 -1
- package/dist/lib/commands/index.d.ts +0 -1
- package/dist/lib/commands/index.d.ts.map +1 -1
- package/dist/lib/compress/index.d.ts +1 -2
- package/dist/lib/compress/index.d.ts.map +1 -1
- package/dist/lib/compress/pipeline.d.ts.map +1 -1
- package/dist/lib/compress/state.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +0 -14
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/prune.d.ts.map +1 -1
- package/dist/lib/messages/query.d.ts +12 -0
- package/dist/lib/messages/query.d.ts.map +1 -1
- package/dist/lib/messages/truncate-tools.d.ts +10 -0
- package/dist/lib/messages/truncate-tools.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/nudge.d.ts +1 -2
- package/dist/lib/prompts/extensions/nudge.d.ts.map +1 -1
- package/dist/lib/prompts/system.d.ts +1 -1
- package/dist/lib/prompts/system.d.ts.map +1 -1
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +12 -1
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/state/utils.d.ts +0 -1
- package/dist/lib/state/utils.d.ts.map +1 -1
- package/dist/lib/ui/notification.d.ts +0 -2
- package/dist/lib/ui/notification.d.ts.map +1 -1
- package/dist/lib/ui/utils.d.ts +1 -3
- package/dist/lib/ui/utils.d.ts.map +1 -1
- package/package.json +2 -1
- package/dist/lib/commands/sweep.d.ts +0 -23
- package/dist/lib/commands/sweep.d.ts.map +0 -1
- package/dist/lib/compress/prune-tool.d.ts +0 -4
- package/dist/lib/compress/prune-tool.d.ts.map +0 -1
- package/dist/lib/gc/truncate.d.ts +0 -15
- package/dist/lib/gc/truncate.d.ts.map +0 -1
- package/dist/lib/strategies/deduplication.d.ts +0 -10
- package/dist/lib/strategies/deduplication.d.ts.map +0 -1
- package/dist/lib/strategies/index.d.ts +0 -3
- package/dist/lib/strategies/index.d.ts.map +0 -1
- package/dist/lib/strategies/purge-errors.d.ts +0 -13
- package/dist/lib/strategies/purge-errors.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -883,7 +883,6 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
883
883
|
"commands.protectedTools",
|
|
884
884
|
"manualMode",
|
|
885
885
|
"manualMode.enabled",
|
|
886
|
-
"manualMode.automaticStrategies",
|
|
887
886
|
"compress",
|
|
888
887
|
"compress.mode",
|
|
889
888
|
"compress.permission",
|
|
@@ -923,14 +922,6 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
923
922
|
"gc.batchCleanup.lowThreshold",
|
|
924
923
|
"gc.batchCleanup.highThreshold",
|
|
925
924
|
"gc.batchCleanup.forceThreshold",
|
|
926
|
-
"strategies",
|
|
927
|
-
"strategies.deduplication",
|
|
928
|
-
"strategies.deduplication.enabled",
|
|
929
|
-
"strategies.deduplication.protectedTools",
|
|
930
|
-
"strategies.purgeErrors",
|
|
931
|
-
"strategies.purgeErrors.enabled",
|
|
932
|
-
"strategies.purgeErrors.turns",
|
|
933
|
-
"strategies.purgeErrors.protectedTools",
|
|
934
925
|
"qualityGate",
|
|
935
926
|
"qualityGate.enabled",
|
|
936
927
|
"qualityGate.algorithm",
|
|
@@ -1089,13 +1080,6 @@ function validateConfigTypes(config) {
|
|
|
1089
1080
|
actual: typeof manualMode.enabled
|
|
1090
1081
|
});
|
|
1091
1082
|
}
|
|
1092
|
-
if (manualMode.automaticStrategies !== void 0 && typeof manualMode.automaticStrategies !== "boolean") {
|
|
1093
|
-
errors.push({
|
|
1094
|
-
key: "manualMode.automaticStrategies",
|
|
1095
|
-
expected: "boolean",
|
|
1096
|
-
actual: typeof manualMode.automaticStrategies
|
|
1097
|
-
});
|
|
1098
|
-
}
|
|
1099
1083
|
}
|
|
1100
1084
|
}
|
|
1101
1085
|
const compress = config.compress;
|
|
@@ -1470,81 +1454,6 @@ function validateConfigTypes(config) {
|
|
|
1470
1454
|
}
|
|
1471
1455
|
}
|
|
1472
1456
|
}
|
|
1473
|
-
const strategies = config.strategies;
|
|
1474
|
-
if (strategies !== void 0) {
|
|
1475
|
-
if (typeof strategies !== "object" || strategies === null || Array.isArray(strategies)) {
|
|
1476
|
-
errors.push({
|
|
1477
|
-
key: "strategies",
|
|
1478
|
-
expected: "object",
|
|
1479
|
-
actual: typeof strategies
|
|
1480
|
-
});
|
|
1481
|
-
} else {
|
|
1482
|
-
const dedup = strategies.deduplication;
|
|
1483
|
-
if (dedup !== void 0) {
|
|
1484
|
-
if (typeof dedup !== "object" || dedup === null || Array.isArray(dedup)) {
|
|
1485
|
-
errors.push({
|
|
1486
|
-
key: "strategies.deduplication",
|
|
1487
|
-
expected: "object",
|
|
1488
|
-
actual: typeof dedup
|
|
1489
|
-
});
|
|
1490
|
-
} else {
|
|
1491
|
-
if (dedup.enabled !== void 0 && typeof dedup.enabled !== "boolean") {
|
|
1492
|
-
errors.push({
|
|
1493
|
-
key: "strategies.deduplication.enabled",
|
|
1494
|
-
expected: "boolean",
|
|
1495
|
-
actual: typeof dedup.enabled
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
if (dedup.protectedTools !== void 0 && !Array.isArray(dedup.protectedTools)) {
|
|
1499
|
-
errors.push({
|
|
1500
|
-
key: "strategies.deduplication.protectedTools",
|
|
1501
|
-
expected: "string[]",
|
|
1502
|
-
actual: typeof dedup.protectedTools
|
|
1503
|
-
});
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
const purge = strategies.purgeErrors;
|
|
1508
|
-
if (purge !== void 0) {
|
|
1509
|
-
if (typeof purge !== "object" || purge === null || Array.isArray(purge)) {
|
|
1510
|
-
errors.push({
|
|
1511
|
-
key: "strategies.purgeErrors",
|
|
1512
|
-
expected: "object",
|
|
1513
|
-
actual: typeof purge
|
|
1514
|
-
});
|
|
1515
|
-
} else {
|
|
1516
|
-
if (purge.enabled !== void 0 && typeof purge.enabled !== "boolean") {
|
|
1517
|
-
errors.push({
|
|
1518
|
-
key: "strategies.purgeErrors.enabled",
|
|
1519
|
-
expected: "boolean",
|
|
1520
|
-
actual: typeof purge.enabled
|
|
1521
|
-
});
|
|
1522
|
-
}
|
|
1523
|
-
if (purge.turns !== void 0 && typeof purge.turns !== "number") {
|
|
1524
|
-
errors.push({
|
|
1525
|
-
key: "strategies.purgeErrors.turns",
|
|
1526
|
-
expected: "number",
|
|
1527
|
-
actual: typeof purge.turns
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
if (typeof purge.turns === "number" && purge.turns < 1) {
|
|
1531
|
-
errors.push({
|
|
1532
|
-
key: "strategies.purgeErrors.turns",
|
|
1533
|
-
expected: "positive number (>= 1)",
|
|
1534
|
-
actual: `${purge.turns} (will be clamped to 1)`
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1537
|
-
if (purge.protectedTools !== void 0 && !Array.isArray(purge.protectedTools)) {
|
|
1538
|
-
errors.push({
|
|
1539
|
-
key: "strategies.purgeErrors.protectedTools",
|
|
1540
|
-
expected: "string[]",
|
|
1541
|
-
actual: typeof purge.protectedTools
|
|
1542
|
-
});
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
1457
|
return errors;
|
|
1549
1458
|
}
|
|
1550
1459
|
|
|
@@ -1614,8 +1523,7 @@ var defaultConfig = {
|
|
|
1614
1523
|
protectedTools: [...DEFAULT_PROTECTED_TOOLS]
|
|
1615
1524
|
},
|
|
1616
1525
|
manualMode: {
|
|
1617
|
-
enabled: false
|
|
1618
|
-
automaticStrategies: true
|
|
1526
|
+
enabled: false
|
|
1619
1527
|
},
|
|
1620
1528
|
turnProtection: {
|
|
1621
1529
|
enabled: false,
|
|
@@ -1652,17 +1560,6 @@ var defaultConfig = {
|
|
|
1652
1560
|
preserveRecentTokens: 5e3,
|
|
1653
1561
|
preserveLastUserMessage: true
|
|
1654
1562
|
},
|
|
1655
|
-
strategies: {
|
|
1656
|
-
deduplication: {
|
|
1657
|
-
enabled: true,
|
|
1658
|
-
protectedTools: []
|
|
1659
|
-
},
|
|
1660
|
-
purgeErrors: {
|
|
1661
|
-
enabled: true,
|
|
1662
|
-
turns: 4,
|
|
1663
|
-
protectedTools: []
|
|
1664
|
-
}
|
|
1665
|
-
},
|
|
1666
1563
|
gc: {
|
|
1667
1564
|
algorithm: "truncate",
|
|
1668
1565
|
promotionThreshold: 5,
|
|
@@ -1770,32 +1667,6 @@ function loadConfigFile(configPath) {
|
|
|
1770
1667
|
return { data: null, parseError: error.message || "Failed to parse config" };
|
|
1771
1668
|
}
|
|
1772
1669
|
}
|
|
1773
|
-
function mergeStrategies(base, override) {
|
|
1774
|
-
if (!override) {
|
|
1775
|
-
return base;
|
|
1776
|
-
}
|
|
1777
|
-
return {
|
|
1778
|
-
deduplication: {
|
|
1779
|
-
enabled: override.deduplication?.enabled ?? base.deduplication.enabled,
|
|
1780
|
-
protectedTools: [
|
|
1781
|
-
.../* @__PURE__ */ new Set([
|
|
1782
|
-
...base.deduplication.protectedTools,
|
|
1783
|
-
...override.deduplication?.protectedTools ?? []
|
|
1784
|
-
])
|
|
1785
|
-
]
|
|
1786
|
-
},
|
|
1787
|
-
purgeErrors: {
|
|
1788
|
-
enabled: override.purgeErrors?.enabled ?? base.purgeErrors.enabled,
|
|
1789
|
-
turns: override.purgeErrors?.turns ?? base.purgeErrors.turns,
|
|
1790
|
-
protectedTools: [
|
|
1791
|
-
.../* @__PURE__ */ new Set([
|
|
1792
|
-
...base.purgeErrors.protectedTools,
|
|
1793
|
-
...override.purgeErrors?.protectedTools ?? []
|
|
1794
|
-
])
|
|
1795
|
-
]
|
|
1796
|
-
}
|
|
1797
|
-
};
|
|
1798
|
-
}
|
|
1799
1670
|
function mergeCompress(base, override) {
|
|
1800
1671
|
if (!override) {
|
|
1801
1672
|
return base;
|
|
@@ -1843,8 +1714,7 @@ function mergeCommands(base, override) {
|
|
|
1843
1714
|
function mergeManualMode(base, override) {
|
|
1844
1715
|
if (override === void 0) return base;
|
|
1845
1716
|
return {
|
|
1846
|
-
enabled: override.enabled ?? base.enabled
|
|
1847
|
-
automaticStrategies: override.automaticStrategies ?? base.automaticStrategies
|
|
1717
|
+
enabled: override.enabled ?? base.enabled
|
|
1848
1718
|
};
|
|
1849
1719
|
}
|
|
1850
1720
|
function mergeExperimental(base, override) {
|
|
@@ -1862,8 +1732,7 @@ function deepCloneConfig(config) {
|
|
|
1862
1732
|
protectedTools: [...config.commands.protectedTools]
|
|
1863
1733
|
},
|
|
1864
1734
|
manualMode: {
|
|
1865
|
-
enabled: config.manualMode.enabled
|
|
1866
|
-
automaticStrategies: config.manualMode.automaticStrategies
|
|
1735
|
+
enabled: config.manualMode.enabled
|
|
1867
1736
|
},
|
|
1868
1737
|
turnProtection: { ...config.turnProtection },
|
|
1869
1738
|
experimental: { ...config.experimental },
|
|
@@ -1874,16 +1743,6 @@ function deepCloneConfig(config) {
|
|
|
1874
1743
|
modelMinLimits: { ...config.compress.modelMinLimits },
|
|
1875
1744
|
protectedTools: [...config.compress.protectedTools]
|
|
1876
1745
|
},
|
|
1877
|
-
strategies: {
|
|
1878
|
-
deduplication: {
|
|
1879
|
-
...config.strategies.deduplication,
|
|
1880
|
-
protectedTools: [...config.strategies.deduplication.protectedTools]
|
|
1881
|
-
},
|
|
1882
|
-
purgeErrors: {
|
|
1883
|
-
...config.strategies.purgeErrors,
|
|
1884
|
-
protectedTools: [...config.strategies.purgeErrors.protectedTools]
|
|
1885
|
-
}
|
|
1886
|
-
},
|
|
1887
1746
|
gc: {
|
|
1888
1747
|
...config.gc,
|
|
1889
1748
|
batchCleanup: { ...config.gc.batchCleanup }
|
|
@@ -1932,7 +1791,6 @@ function mergeLayer(config, data) {
|
|
|
1932
1791
|
],
|
|
1933
1792
|
compress: mergeCompress(config.compress, data.compress),
|
|
1934
1793
|
gc: mergeGC(config.gc, data.gc),
|
|
1935
|
-
strategies: mergeStrategies(config.strategies, data.strategies),
|
|
1936
1794
|
qualityGate: mergeQualityGate(config.qualityGate, data.qualityGate)
|
|
1937
1795
|
};
|
|
1938
1796
|
}
|
|
@@ -2000,6 +1858,9 @@ Using previous/default values`
|
|
|
2000
1858
|
return config;
|
|
2001
1859
|
}
|
|
2002
1860
|
|
|
1861
|
+
// lib/compress/message.ts
|
|
1862
|
+
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
1863
|
+
|
|
2003
1864
|
// lib/compress/types.ts
|
|
2004
1865
|
function resolveToolContext(factoryCtx, sessionID) {
|
|
2005
1866
|
const state = factoryCtx.registry.get(sessionID);
|
|
@@ -2017,9 +1878,6 @@ function resolveToolContext(factoryCtx, sessionID) {
|
|
|
2017
1878
|
};
|
|
2018
1879
|
}
|
|
2019
1880
|
|
|
2020
|
-
// lib/compress/message.ts
|
|
2021
|
-
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
2022
|
-
|
|
2023
1881
|
// lib/token-utils.ts
|
|
2024
1882
|
import * as _anthropicTokenizer from "@anthropic-ai/tokenizer";
|
|
2025
1883
|
|
|
@@ -2085,6 +1943,16 @@ var messageHasCompress = (message) => {
|
|
|
2085
1943
|
(part) => part.type === "tool" && part.tool === "compress" && part.state?.status === "completed"
|
|
2086
1944
|
);
|
|
2087
1945
|
};
|
|
1946
|
+
var messageHasCompressAttempt = (message) => {
|
|
1947
|
+
if (!isMessageWithInfo(message)) {
|
|
1948
|
+
return false;
|
|
1949
|
+
}
|
|
1950
|
+
if (message.info.role !== "assistant") {
|
|
1951
|
+
return false;
|
|
1952
|
+
}
|
|
1953
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
1954
|
+
return parts.some((part) => part.type === "tool" && part.tool === "compress");
|
|
1955
|
+
};
|
|
2088
1956
|
var isIgnoredUserMessage = (message) => {
|
|
2089
1957
|
if (!isMessageWithInfo(message)) {
|
|
2090
1958
|
return false;
|
|
@@ -2197,14 +2065,6 @@ function extractToolContent(part) {
|
|
|
2197
2065
|
}
|
|
2198
2066
|
return contents;
|
|
2199
2067
|
}
|
|
2200
|
-
function getTotalToolTokens(state, toolIds) {
|
|
2201
|
-
let total = 0;
|
|
2202
|
-
for (const id of toolIds) {
|
|
2203
|
-
const entry = state.toolParameters.get(id);
|
|
2204
|
-
total += entry?.tokenCount ?? 0;
|
|
2205
|
-
}
|
|
2206
|
-
return total;
|
|
2207
|
-
}
|
|
2208
2068
|
function countAllMessageTokens(msg) {
|
|
2209
2069
|
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
2210
2070
|
const texts = [];
|
|
@@ -2850,20 +2710,20 @@ function matchesGlob(inputPath, pattern) {
|
|
|
2850
2710
|
regex += "$";
|
|
2851
2711
|
return new RegExp(regex).test(input);
|
|
2852
2712
|
}
|
|
2853
|
-
function getFilePathsFromParameters(
|
|
2713
|
+
function getFilePathsFromParameters(tool7, parameters) {
|
|
2854
2714
|
if (typeof parameters !== "object" || parameters === null) {
|
|
2855
2715
|
return [];
|
|
2856
2716
|
}
|
|
2857
2717
|
const paths = [];
|
|
2858
2718
|
const params = parameters;
|
|
2859
|
-
if (
|
|
2719
|
+
if (tool7 === "apply_patch" && typeof params.patchText === "string") {
|
|
2860
2720
|
const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g;
|
|
2861
2721
|
let match;
|
|
2862
2722
|
while ((match = pathRegex.exec(params.patchText)) !== null) {
|
|
2863
2723
|
paths.push(match[1].trim());
|
|
2864
2724
|
}
|
|
2865
2725
|
}
|
|
2866
|
-
if (
|
|
2726
|
+
if (tool7 === "multiedit") {
|
|
2867
2727
|
if (typeof params.filePath === "string") {
|
|
2868
2728
|
paths.push(params.filePath);
|
|
2869
2729
|
}
|
|
@@ -3200,6 +3060,7 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3200
3060
|
const messagesState = state.prune.messages;
|
|
3201
3061
|
const consumed = [...new Set(consumedBlockIds.filter((id) => Number.isInteger(id) && id > 0))];
|
|
3202
3062
|
const createdAt = Date.now();
|
|
3063
|
+
const isBlockBoundary = selection.startReference?.kind === "compressed-block" || selection.endReference?.kind === "compressed-block";
|
|
3203
3064
|
let minConsumedTier;
|
|
3204
3065
|
for (const consumedBlockId of consumed) {
|
|
3205
3066
|
const cb = messagesState.blocksById.get(consumedBlockId);
|
|
@@ -3210,9 +3071,8 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3210
3071
|
}
|
|
3211
3072
|
}
|
|
3212
3073
|
}
|
|
3213
|
-
const
|
|
3214
|
-
const
|
|
3215
|
-
const targetTierForConsumption = effectiveMinTier;
|
|
3074
|
+
const outputTier = isBlockBoundary ? Math.min(3, (minConsumedTier ?? 1) + 1) : 1;
|
|
3075
|
+
const targetTierForConsumption = isBlockBoundary ? minConsumedTier ?? 1 : 1;
|
|
3216
3076
|
const effectiveMessageIds = new Set(selection.messageIds);
|
|
3217
3077
|
const effectiveToolIds = new Set(selection.toolIds);
|
|
3218
3078
|
for (const consumedBlockId of consumed) {
|
|
@@ -3686,15 +3546,6 @@ function countTurns(state, messages) {
|
|
|
3686
3546
|
}
|
|
3687
3547
|
return turnCount;
|
|
3688
3548
|
}
|
|
3689
|
-
function loadPruneMap(obj) {
|
|
3690
|
-
if (!obj || typeof obj !== "object") {
|
|
3691
|
-
return /* @__PURE__ */ new Map();
|
|
3692
|
-
}
|
|
3693
|
-
const entries = Object.entries(obj).filter(
|
|
3694
|
-
(entry) => typeof entry[0] === "string" && typeof entry[1] === "number"
|
|
3695
|
-
);
|
|
3696
|
-
return new Map(entries);
|
|
3697
|
-
}
|
|
3698
3549
|
function createPruneMessagesState() {
|
|
3699
3550
|
return {
|
|
3700
3551
|
byMessageId: /* @__PURE__ */ new Map(),
|
|
@@ -3882,7 +3733,6 @@ function getTierTokenUsage(state) {
|
|
|
3882
3733
|
}
|
|
3883
3734
|
function resetOnCompaction(state) {
|
|
3884
3735
|
state.toolParameters.clear();
|
|
3885
|
-
state.prune.tools = /* @__PURE__ */ new Map();
|
|
3886
3736
|
state.nudges = {
|
|
3887
3737
|
contextLimitAnchors: /* @__PURE__ */ new Set(),
|
|
3888
3738
|
turnNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
@@ -3894,7 +3744,8 @@ function resetOnCompaction(state) {
|
|
|
3894
3744
|
lastTier2NudgeTokens: void 0,
|
|
3895
3745
|
lastTier3NudgeTokens: void 0,
|
|
3896
3746
|
shouldInjectThisTurn: void 0,
|
|
3897
|
-
compressBaselineSet: false
|
|
3747
|
+
compressBaselineSet: false,
|
|
3748
|
+
lastProcessedCompressMessageId: void 0
|
|
3898
3749
|
};
|
|
3899
3750
|
state.messageIds = {
|
|
3900
3751
|
byRawId: /* @__PURE__ */ new Map(),
|
|
@@ -3958,7 +3809,6 @@ async function saveSessionState(sessionState, logger, sessionName) {
|
|
|
3958
3809
|
const state = {
|
|
3959
3810
|
sessionName,
|
|
3960
3811
|
prune: {
|
|
3961
|
-
tools: Object.fromEntries(sessionState.prune.tools),
|
|
3962
3812
|
messages: serializePruneMessagesState(sessionState.prune.messages)
|
|
3963
3813
|
},
|
|
3964
3814
|
nudges: {
|
|
@@ -3993,10 +3843,9 @@ async function loadSessionState(sessionId, logger) {
|
|
|
3993
3843
|
}
|
|
3994
3844
|
const content = await fs.readFile(filePath, "utf-8");
|
|
3995
3845
|
const state = JSON.parse(content);
|
|
3996
|
-
const hasPruneTools = state?.prune?.tools && typeof state.prune.tools === "object";
|
|
3997
3846
|
const hasPruneMessages = state?.prune?.messages && typeof state.prune.messages === "object";
|
|
3998
3847
|
const hasNudgeFormat = state?.nudges && typeof state.nudges === "object";
|
|
3999
|
-
if (!state || !state.prune || !
|
|
3848
|
+
if (!state || !state.prune || !hasPruneMessages || !state.stats || !hasNudgeFormat) {
|
|
4000
3849
|
logger.warn("Invalid session state file, ignoring", {
|
|
4001
3850
|
sessionId
|
|
4002
3851
|
});
|
|
@@ -4084,7 +3933,8 @@ async function loadAllSessionStats(logger) {
|
|
|
4084
3933
|
const state = JSON.parse(content);
|
|
4085
3934
|
if (state?.stats?.totalPruneTokens && state?.prune) {
|
|
4086
3935
|
result.totalTokens += state.stats.totalPruneTokens;
|
|
4087
|
-
|
|
3936
|
+
const legacy = state.prune.tools;
|
|
3937
|
+
result.totalTools += legacy ? Object.keys(legacy).length : 0;
|
|
4088
3938
|
result.totalMessages += state.prune.messages?.byMessageId ? Object.keys(state.prune.messages.byMessageId).length : 0;
|
|
4089
3939
|
result.sessionCount++;
|
|
4090
3940
|
}
|
|
@@ -4572,7 +4422,6 @@ function createSessionState() {
|
|
|
4572
4422
|
compressPermission: void 0,
|
|
4573
4423
|
pendingManualTrigger: null,
|
|
4574
4424
|
prune: {
|
|
4575
|
-
tools: /* @__PURE__ */ new Map(),
|
|
4576
4425
|
messages: createPruneMessagesState()
|
|
4577
4426
|
},
|
|
4578
4427
|
nudges: {
|
|
@@ -4586,7 +4435,8 @@ function createSessionState() {
|
|
|
4586
4435
|
lastTier2NudgeTokens: void 0,
|
|
4587
4436
|
lastTier3NudgeTokens: void 0,
|
|
4588
4437
|
shouldInjectThisTurn: void 0,
|
|
4589
|
-
compressBaselineSet: false
|
|
4438
|
+
compressBaselineSet: false,
|
|
4439
|
+
lastProcessedCompressMessageId: void 0
|
|
4590
4440
|
},
|
|
4591
4441
|
stats: {
|
|
4592
4442
|
pruneTokenCounter: 0,
|
|
@@ -4617,7 +4467,6 @@ function resetSessionState(state) {
|
|
|
4617
4467
|
state.compressPermission = void 0;
|
|
4618
4468
|
state.pendingManualTrigger = null;
|
|
4619
4469
|
state.prune = {
|
|
4620
|
-
tools: /* @__PURE__ */ new Map(),
|
|
4621
4470
|
messages: createPruneMessagesState()
|
|
4622
4471
|
};
|
|
4623
4472
|
state.nudges = {
|
|
@@ -4631,7 +4480,8 @@ function resetSessionState(state) {
|
|
|
4631
4480
|
lastTier2NudgeTokens: void 0,
|
|
4632
4481
|
lastTier3NudgeTokens: void 0,
|
|
4633
4482
|
shouldInjectThisTurn: void 0,
|
|
4634
|
-
compressBaselineSet: false
|
|
4483
|
+
compressBaselineSet: false,
|
|
4484
|
+
lastProcessedCompressMessageId: void 0
|
|
4635
4485
|
};
|
|
4636
4486
|
state.stats = {
|
|
4637
4487
|
pruneTokenCounter: 0,
|
|
@@ -4672,7 +4522,6 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
4672
4522
|
}
|
|
4673
4523
|
return;
|
|
4674
4524
|
}
|
|
4675
|
-
state.prune.tools = loadPruneMap(persisted.prune.tools);
|
|
4676
4525
|
state.prune.messages = loadPruneMessagesState(persisted.prune.messages);
|
|
4677
4526
|
state.nudges.contextLimitAnchors = new Set(persisted.nudges.contextLimitAnchors || []);
|
|
4678
4527
|
state.nudges.turnNudgeAnchors = /* @__PURE__ */ new Set([
|
|
@@ -4798,255 +4647,7 @@ function trimToolParametersCache(state) {
|
|
|
4798
4647
|
}
|
|
4799
4648
|
}
|
|
4800
4649
|
|
|
4801
|
-
// lib/strategies/deduplication.ts
|
|
4802
|
-
var deduplicate = (state, logger, config, messages) => {
|
|
4803
|
-
if (state.manualMode && !config.manualMode.automaticStrategies) {
|
|
4804
|
-
return;
|
|
4805
|
-
}
|
|
4806
|
-
if (!config.strategies.deduplication.enabled) {
|
|
4807
|
-
return;
|
|
4808
|
-
}
|
|
4809
|
-
const allToolIds = state.toolIdList;
|
|
4810
|
-
if (allToolIds.length === 0) {
|
|
4811
|
-
return;
|
|
4812
|
-
}
|
|
4813
|
-
const unprunedIds = allToolIds.filter((id) => !state.prune.tools.has(id));
|
|
4814
|
-
if (unprunedIds.length === 0) {
|
|
4815
|
-
return;
|
|
4816
|
-
}
|
|
4817
|
-
const protectedTools = config.strategies.deduplication.protectedTools;
|
|
4818
|
-
const signatureMap = /* @__PURE__ */ new Map();
|
|
4819
|
-
for (const id of unprunedIds) {
|
|
4820
|
-
const metadata = state.toolParameters.get(id);
|
|
4821
|
-
if (!metadata) {
|
|
4822
|
-
continue;
|
|
4823
|
-
}
|
|
4824
|
-
if (isToolNameProtected(metadata.tool, protectedTools)) {
|
|
4825
|
-
continue;
|
|
4826
|
-
}
|
|
4827
|
-
const filePaths = getFilePathsFromParameters(metadata.tool, metadata.parameters);
|
|
4828
|
-
if (isFilePathProtected(filePaths, config.protectedFilePatterns)) {
|
|
4829
|
-
continue;
|
|
4830
|
-
}
|
|
4831
|
-
const signature = createToolSignature(metadata.tool, metadata.parameters);
|
|
4832
|
-
if (!signatureMap.has(signature)) {
|
|
4833
|
-
signatureMap.set(signature, []);
|
|
4834
|
-
}
|
|
4835
|
-
const ids = signatureMap.get(signature);
|
|
4836
|
-
if (ids) {
|
|
4837
|
-
ids.push(id);
|
|
4838
|
-
}
|
|
4839
|
-
}
|
|
4840
|
-
const newPruneIds = [];
|
|
4841
|
-
for (const [, ids] of signatureMap.entries()) {
|
|
4842
|
-
if (ids.length > 1) {
|
|
4843
|
-
const idsToRemove = ids.slice(0, -1);
|
|
4844
|
-
newPruneIds.push(...idsToRemove);
|
|
4845
|
-
}
|
|
4846
|
-
}
|
|
4847
|
-
state.stats.totalPruneTokens += getTotalToolTokens(state, newPruneIds);
|
|
4848
|
-
if (newPruneIds.length > 0) {
|
|
4849
|
-
for (const id of newPruneIds) {
|
|
4850
|
-
const entry = state.toolParameters.get(id);
|
|
4851
|
-
state.prune.tools.set(id, entry?.tokenCount ?? 0);
|
|
4852
|
-
}
|
|
4853
|
-
logger.debug(`Marked ${newPruneIds.length} duplicate tool calls for pruning`);
|
|
4854
|
-
}
|
|
4855
|
-
};
|
|
4856
|
-
function createToolSignature(tool8, parameters) {
|
|
4857
|
-
if (!parameters) {
|
|
4858
|
-
return tool8;
|
|
4859
|
-
}
|
|
4860
|
-
const normalized = normalizeParameters(parameters);
|
|
4861
|
-
const sorted = sortObjectKeys(normalized);
|
|
4862
|
-
return `${tool8}::${JSON.stringify(sorted)}`;
|
|
4863
|
-
}
|
|
4864
|
-
function normalizeParameters(params) {
|
|
4865
|
-
if (typeof params !== "object" || params === null) return params;
|
|
4866
|
-
if (Array.isArray(params)) return params;
|
|
4867
|
-
const normalized = {};
|
|
4868
|
-
for (const [key, value] of Object.entries(params)) {
|
|
4869
|
-
if (value !== void 0 && value !== null) {
|
|
4870
|
-
normalized[key] = value;
|
|
4871
|
-
}
|
|
4872
|
-
}
|
|
4873
|
-
return normalized;
|
|
4874
|
-
}
|
|
4875
|
-
function sortObjectKeys(obj) {
|
|
4876
|
-
if (typeof obj !== "object" || obj === null) return obj;
|
|
4877
|
-
if (Array.isArray(obj)) return obj.map(sortObjectKeys);
|
|
4878
|
-
const sorted = {};
|
|
4879
|
-
for (const key of Object.keys(obj).sort()) {
|
|
4880
|
-
sorted[key] = sortObjectKeys(obj[key]);
|
|
4881
|
-
}
|
|
4882
|
-
return sorted;
|
|
4883
|
-
}
|
|
4884
|
-
|
|
4885
|
-
// lib/strategies/purge-errors.ts
|
|
4886
|
-
var purgeErrors = (state, logger, config, messages) => {
|
|
4887
|
-
if (state.manualMode && !config.manualMode.automaticStrategies) {
|
|
4888
|
-
return;
|
|
4889
|
-
}
|
|
4890
|
-
if (!config.strategies.purgeErrors.enabled) {
|
|
4891
|
-
return;
|
|
4892
|
-
}
|
|
4893
|
-
const allToolIds = state.toolIdList;
|
|
4894
|
-
if (allToolIds.length === 0) {
|
|
4895
|
-
return;
|
|
4896
|
-
}
|
|
4897
|
-
const unprunedIds = allToolIds.filter((id) => !state.prune.tools.has(id));
|
|
4898
|
-
if (unprunedIds.length === 0) {
|
|
4899
|
-
return;
|
|
4900
|
-
}
|
|
4901
|
-
const protectedTools = config.strategies.purgeErrors.protectedTools;
|
|
4902
|
-
const turnThreshold = Math.max(1, config.strategies.purgeErrors.turns);
|
|
4903
|
-
const newPruneIds = [];
|
|
4904
|
-
for (const id of unprunedIds) {
|
|
4905
|
-
const metadata = state.toolParameters.get(id);
|
|
4906
|
-
if (!metadata) {
|
|
4907
|
-
continue;
|
|
4908
|
-
}
|
|
4909
|
-
if (isToolNameProtected(metadata.tool, protectedTools)) {
|
|
4910
|
-
continue;
|
|
4911
|
-
}
|
|
4912
|
-
const filePaths = getFilePathsFromParameters(metadata.tool, metadata.parameters);
|
|
4913
|
-
if (isFilePathProtected(filePaths, config.protectedFilePatterns)) {
|
|
4914
|
-
continue;
|
|
4915
|
-
}
|
|
4916
|
-
if (metadata.status !== "error") {
|
|
4917
|
-
continue;
|
|
4918
|
-
}
|
|
4919
|
-
const turnAge = state.currentTurn - metadata.turn;
|
|
4920
|
-
if (turnAge >= turnThreshold) {
|
|
4921
|
-
newPruneIds.push(id);
|
|
4922
|
-
}
|
|
4923
|
-
}
|
|
4924
|
-
if (newPruneIds.length > 0) {
|
|
4925
|
-
state.stats.totalPruneTokens += getTotalToolTokens(state, newPruneIds);
|
|
4926
|
-
for (const id of newPruneIds) {
|
|
4927
|
-
const entry = state.toolParameters.get(id);
|
|
4928
|
-
state.prune.tools.set(id, entry?.tokenCount ?? 0);
|
|
4929
|
-
}
|
|
4930
|
-
logger.debug(
|
|
4931
|
-
`Marked ${newPruneIds.length} error tool calls for pruning (older than ${turnThreshold} turns)`
|
|
4932
|
-
);
|
|
4933
|
-
}
|
|
4934
|
-
};
|
|
4935
|
-
|
|
4936
4650
|
// lib/ui/utils.ts
|
|
4937
|
-
function extractParameterKey(tool8, parameters) {
|
|
4938
|
-
if (!parameters) return "";
|
|
4939
|
-
if (tool8 === "read" && parameters.filePath) {
|
|
4940
|
-
const offset = parameters.offset;
|
|
4941
|
-
const limit = parameters.limit;
|
|
4942
|
-
if (offset !== void 0 && limit !== void 0) {
|
|
4943
|
-
return `${parameters.filePath} (lines ${offset}-${offset + limit})`;
|
|
4944
|
-
}
|
|
4945
|
-
if (offset !== void 0) {
|
|
4946
|
-
return `${parameters.filePath} (lines ${offset}+)`;
|
|
4947
|
-
}
|
|
4948
|
-
if (limit !== void 0) {
|
|
4949
|
-
return `${parameters.filePath} (lines 0-${limit})`;
|
|
4950
|
-
}
|
|
4951
|
-
return parameters.filePath;
|
|
4952
|
-
}
|
|
4953
|
-
if ((tool8 === "write" || tool8 === "edit" || tool8 === "multiedit") && parameters.filePath) {
|
|
4954
|
-
return parameters.filePath;
|
|
4955
|
-
}
|
|
4956
|
-
if (tool8 === "apply_patch" && typeof parameters.patchText === "string") {
|
|
4957
|
-
const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g;
|
|
4958
|
-
const paths = [];
|
|
4959
|
-
let match;
|
|
4960
|
-
while ((match = pathRegex.exec(parameters.patchText)) !== null) {
|
|
4961
|
-
paths.push(match[1].trim());
|
|
4962
|
-
}
|
|
4963
|
-
if (paths.length > 0) {
|
|
4964
|
-
const uniquePaths = [...new Set(paths)];
|
|
4965
|
-
const count = uniquePaths.length;
|
|
4966
|
-
const plural = count > 1 ? "s" : "";
|
|
4967
|
-
if (count === 1) return uniquePaths[0];
|
|
4968
|
-
if (count === 2) return uniquePaths.join(", ");
|
|
4969
|
-
return `${count} file${plural}: ${uniquePaths[0]}, ${uniquePaths[1]}...`;
|
|
4970
|
-
}
|
|
4971
|
-
return "patch";
|
|
4972
|
-
}
|
|
4973
|
-
if (tool8 === "list") {
|
|
4974
|
-
return parameters.path || "(current directory)";
|
|
4975
|
-
}
|
|
4976
|
-
if (tool8 === "glob") {
|
|
4977
|
-
if (parameters.pattern) {
|
|
4978
|
-
const pathInfo = parameters.path ? ` in ${parameters.path}` : "";
|
|
4979
|
-
return `"${parameters.pattern}"${pathInfo}`;
|
|
4980
|
-
}
|
|
4981
|
-
return "(unknown pattern)";
|
|
4982
|
-
}
|
|
4983
|
-
if (tool8 === "grep") {
|
|
4984
|
-
if (parameters.pattern) {
|
|
4985
|
-
const pathInfo = parameters.path ? ` in ${parameters.path}` : "";
|
|
4986
|
-
return `"${parameters.pattern}"${pathInfo}`;
|
|
4987
|
-
}
|
|
4988
|
-
return "(unknown pattern)";
|
|
4989
|
-
}
|
|
4990
|
-
if (tool8 === "bash") {
|
|
4991
|
-
if (parameters.description) return parameters.description;
|
|
4992
|
-
if (parameters.command) {
|
|
4993
|
-
return parameters.command.length > 50 ? parameters.command.substring(0, 50) + "..." : parameters.command;
|
|
4994
|
-
}
|
|
4995
|
-
}
|
|
4996
|
-
if (tool8 === "webfetch" && parameters.url) {
|
|
4997
|
-
return parameters.url;
|
|
4998
|
-
}
|
|
4999
|
-
if (tool8 === "websearch" && parameters.query) {
|
|
5000
|
-
return `"${parameters.query}"`;
|
|
5001
|
-
}
|
|
5002
|
-
if (tool8 === "codesearch" && parameters.query) {
|
|
5003
|
-
return `"${parameters.query}"`;
|
|
5004
|
-
}
|
|
5005
|
-
if (tool8 === "todowrite") {
|
|
5006
|
-
return `${parameters.todos?.length || 0} todos`;
|
|
5007
|
-
}
|
|
5008
|
-
if (tool8 === "todoread") {
|
|
5009
|
-
return "read todo list";
|
|
5010
|
-
}
|
|
5011
|
-
if (tool8 === "task" && parameters.description) {
|
|
5012
|
-
return parameters.description;
|
|
5013
|
-
}
|
|
5014
|
-
if (tool8 === "skill" && parameters.name) {
|
|
5015
|
-
return parameters.name;
|
|
5016
|
-
}
|
|
5017
|
-
if (tool8 === "lsp") {
|
|
5018
|
-
const op = parameters.operation || "lsp";
|
|
5019
|
-
const path = parameters.filePath || "";
|
|
5020
|
-
const line = parameters.line;
|
|
5021
|
-
const char = parameters.character;
|
|
5022
|
-
if (path && line !== void 0 && char !== void 0) {
|
|
5023
|
-
return `${op} ${path}:${line}:${char}`;
|
|
5024
|
-
}
|
|
5025
|
-
if (path) {
|
|
5026
|
-
return `${op} ${path}`;
|
|
5027
|
-
}
|
|
5028
|
-
return op;
|
|
5029
|
-
}
|
|
5030
|
-
if (tool8 === "question") {
|
|
5031
|
-
const questions = parameters.questions;
|
|
5032
|
-
if (Array.isArray(questions) && questions.length > 0) {
|
|
5033
|
-
const headers = questions.map((q) => q.header || "").filter(Boolean).slice(0, 3);
|
|
5034
|
-
const count = questions.length;
|
|
5035
|
-
const plural = count > 1 ? "s" : "";
|
|
5036
|
-
if (headers.length > 0) {
|
|
5037
|
-
const suffix = count > 3 ? ` (+${count - 3} more)` : "";
|
|
5038
|
-
return `${count} question${plural}: ${headers.join(", ")}${suffix}`;
|
|
5039
|
-
}
|
|
5040
|
-
return `${count} question${plural}`;
|
|
5041
|
-
}
|
|
5042
|
-
return "question";
|
|
5043
|
-
}
|
|
5044
|
-
const paramStr = JSON.stringify(parameters);
|
|
5045
|
-
if (paramStr === "{}" || paramStr === "[]" || paramStr === "null") {
|
|
5046
|
-
return "";
|
|
5047
|
-
}
|
|
5048
|
-
return paramStr.substring(0, 50);
|
|
5049
|
-
}
|
|
5050
4651
|
function formatAge(createdAt) {
|
|
5051
4652
|
const elapsed = Date.now() - createdAt;
|
|
5052
4653
|
if (elapsed < 6e4) return "just now";
|
|
@@ -5061,10 +4662,6 @@ function formatTokenCount(tokens, compact) {
|
|
|
5061
4662
|
}
|
|
5062
4663
|
return tokens.toString() + suffix;
|
|
5063
4664
|
}
|
|
5064
|
-
function truncate(str, maxLen = 60) {
|
|
5065
|
-
if (str.length <= maxLen) return str;
|
|
5066
|
-
return str.slice(0, maxLen - 3) + "...";
|
|
5067
|
-
}
|
|
5068
4665
|
function formatProgressBar(messageIds, prunedMessages, recentMessageIds, width = 50) {
|
|
5069
4666
|
const ACTIVE = "\u2588";
|
|
5070
4667
|
const PRUNED = "\u2591";
|
|
@@ -5124,48 +4721,6 @@ function cacheSystemPromptTokens(state, messages) {
|
|
|
5124
4721
|
const estimatedSystemTokens = Math.max(0, firstInputTokens - countTokens2(firstUserText));
|
|
5125
4722
|
state.systemPromptTokens = estimatedSystemTokens > 0 ? estimatedSystemTokens : void 0;
|
|
5126
4723
|
}
|
|
5127
|
-
function shortenPath(input, workingDirectory) {
|
|
5128
|
-
const inPathMatch = input.match(/^(.+) in (.+)$/);
|
|
5129
|
-
if (inPathMatch) {
|
|
5130
|
-
const prefix = inPathMatch[1];
|
|
5131
|
-
const pathPart = inPathMatch[2];
|
|
5132
|
-
const shortenedPath = shortenSinglePath(pathPart, workingDirectory);
|
|
5133
|
-
return `${prefix} in ${shortenedPath}`;
|
|
5134
|
-
}
|
|
5135
|
-
return shortenSinglePath(input, workingDirectory);
|
|
5136
|
-
}
|
|
5137
|
-
function shortenSinglePath(path, workingDirectory) {
|
|
5138
|
-
if (workingDirectory) {
|
|
5139
|
-
if (path.startsWith(workingDirectory + "/")) {
|
|
5140
|
-
return path.slice(workingDirectory.length + 1);
|
|
5141
|
-
}
|
|
5142
|
-
if (path === workingDirectory) {
|
|
5143
|
-
return ".";
|
|
5144
|
-
}
|
|
5145
|
-
}
|
|
5146
|
-
return path;
|
|
5147
|
-
}
|
|
5148
|
-
function formatPrunedItemsList(pruneToolIds, toolMetadata, workingDirectory) {
|
|
5149
|
-
const lines = [];
|
|
5150
|
-
for (const id of pruneToolIds) {
|
|
5151
|
-
const metadata = toolMetadata.get(id);
|
|
5152
|
-
if (metadata) {
|
|
5153
|
-
const paramKey = extractParameterKey(metadata.tool, metadata.parameters);
|
|
5154
|
-
if (paramKey) {
|
|
5155
|
-
const displayKey = truncate(shortenPath(paramKey, workingDirectory), 60);
|
|
5156
|
-
lines.push(`\u2192 ${metadata.tool}: ${displayKey}`);
|
|
5157
|
-
} else {
|
|
5158
|
-
lines.push(`\u2192 ${metadata.tool}`);
|
|
5159
|
-
}
|
|
5160
|
-
}
|
|
5161
|
-
}
|
|
5162
|
-
const knownCount = pruneToolIds.filter((id) => toolMetadata.has(id)).length;
|
|
5163
|
-
const unknownCount = pruneToolIds.length - knownCount;
|
|
5164
|
-
if (unknownCount > 0) {
|
|
5165
|
-
lines.push(`\u2192 (${unknownCount} tool${unknownCount > 1 ? "s" : ""} with unknown metadata)`);
|
|
5166
|
-
}
|
|
5167
|
-
return lines;
|
|
5168
|
-
}
|
|
5169
4724
|
|
|
5170
4725
|
// lib/ui/notification.ts
|
|
5171
4726
|
var TOAST_BODY_MAX_LINES = 12;
|
|
@@ -5369,6 +4924,10 @@ ${progressBar}`;
|
|
|
5369
4924
|
}
|
|
5370
4925
|
let toastMessage = message;
|
|
5371
4926
|
toastMessage = config.pruneNotification === "minimal" ? toastMessage : truncateToastBody(toastMessage);
|
|
4927
|
+
if (config.debug) {
|
|
4928
|
+
const chatMessage = config.pruneNotification === "minimal" ? message : truncateToastBody(message);
|
|
4929
|
+
await sendIgnoredMessage(client, sessionId, chatMessage, params, logger);
|
|
4930
|
+
}
|
|
5372
4931
|
await client.tui.showToast({
|
|
5373
4932
|
body: {
|
|
5374
4933
|
title: "ACP: Compress Notification",
|
|
@@ -6399,8 +5958,6 @@ async function prepareSession(ctx, toolCtx, title) {
|
|
|
6399
5958
|
ctx.config
|
|
6400
5959
|
);
|
|
6401
5960
|
assignMessageRefs(ctx.state, rawMessages);
|
|
6402
|
-
deduplicate(ctx.state, ctx.logger, ctx.config, rawMessages);
|
|
6403
|
-
purgeErrors(ctx.state, ctx.logger, ctx.config, rawMessages);
|
|
6404
5961
|
return {
|
|
6405
5962
|
rawMessages,
|
|
6406
5963
|
searchContext: buildSearchContext(ctx.state, rawMessages)
|
|
@@ -6523,7 +6080,7 @@ function normalizeRef(ref) {
|
|
|
6523
6080
|
}
|
|
6524
6081
|
function formatKeptMessage(msg, ref, maxChars) {
|
|
6525
6082
|
const formatted = formatByType(msg);
|
|
6526
|
-
const truncated =
|
|
6083
|
+
const truncated = truncate(formatted, maxChars);
|
|
6527
6084
|
return `
|
|
6528
6085
|
--- [${ref}: ${labelForMessage(msg)}] ---
|
|
6529
6086
|
${truncated}
|
|
@@ -6536,11 +6093,11 @@ function formatByType(msg) {
|
|
|
6536
6093
|
return part.text;
|
|
6537
6094
|
}
|
|
6538
6095
|
if (part.type === "tool") {
|
|
6539
|
-
const
|
|
6096
|
+
const tool7 = part.tool || "unknown";
|
|
6540
6097
|
const state = part.state || {};
|
|
6541
6098
|
const input = state.input || {};
|
|
6542
6099
|
const output = state.output || "";
|
|
6543
|
-
switch (
|
|
6100
|
+
switch (tool7) {
|
|
6544
6101
|
case "bash":
|
|
6545
6102
|
case "interactive_bash": {
|
|
6546
6103
|
const cmd = typeof input === "string" ? input : input.command || JSON.stringify(input);
|
|
@@ -6569,7 +6126,7 @@ ${content}`;
|
|
|
6569
6126
|
if (output && typeof output === "string" && output.length > 0) {
|
|
6570
6127
|
return output;
|
|
6571
6128
|
}
|
|
6572
|
-
const compact = JSON.stringify({ tool:
|
|
6129
|
+
const compact = JSON.stringify({ tool: tool7, input }, null, 0);
|
|
6573
6130
|
return compact.length > 500 ? compact.slice(0, 500) + "..." : compact;
|
|
6574
6131
|
}
|
|
6575
6132
|
}
|
|
@@ -6580,15 +6137,15 @@ ${content}`;
|
|
|
6580
6137
|
function labelForMessage(msg) {
|
|
6581
6138
|
for (const part of msg.parts || []) {
|
|
6582
6139
|
if (part.type === "tool") {
|
|
6583
|
-
const
|
|
6140
|
+
const tool7 = part.tool || "unknown";
|
|
6584
6141
|
const input = part.state?.input || {};
|
|
6585
6142
|
const fp = input.filePath || input.path || input.command || "";
|
|
6586
|
-
return fp ? `${
|
|
6143
|
+
return fp ? `${tool7}: ${String(fp).slice(0, 60)}` : tool7;
|
|
6587
6144
|
}
|
|
6588
6145
|
}
|
|
6589
6146
|
return msg.info.role === "user" ? "user" : "text";
|
|
6590
6147
|
}
|
|
6591
|
-
function
|
|
6148
|
+
function truncate(text, maxChars) {
|
|
6592
6149
|
if (text.length <= maxChars) return text;
|
|
6593
6150
|
return text.slice(0, maxChars) + `
|
|
6594
6151
|
... [truncated, ${text.length} chars total]`;
|
|
@@ -7303,8 +6860,8 @@ var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agent
|
|
|
7303
6860
|
agentName ? hostPermissions.agents[agentName] : void 0
|
|
7304
6861
|
) ? "deny" : basePermission;
|
|
7305
6862
|
};
|
|
7306
|
-
var hasExplicitToolPermission = (permissionConfig,
|
|
7307
|
-
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig,
|
|
6863
|
+
var hasExplicitToolPermission = (permissionConfig, tool7) => {
|
|
6864
|
+
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool7) : false;
|
|
7308
6865
|
};
|
|
7309
6866
|
|
|
7310
6867
|
// lib/compress-permission.ts
|
|
@@ -7495,7 +7052,7 @@ var dropEmptyMessages = (messages) => {
|
|
|
7495
7052
|
};
|
|
7496
7053
|
|
|
7497
7054
|
// lib/prompts/extensions/nudge.ts
|
|
7498
|
-
function buildCompressedBlockGuidance(state,
|
|
7055
|
+
function buildCompressedBlockGuidance(state, context) {
|
|
7499
7056
|
const activeBlockIds = Array.from(state.prune.messages.activeBlockIds).filter((id) => Number.isInteger(id) && id > 0).sort((a, b) => a - b);
|
|
7500
7057
|
const blockCount = activeBlockIds.length;
|
|
7501
7058
|
const blocksForStats = activeBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0 && b.active);
|
|
@@ -7536,32 +7093,6 @@ function buildCompressedBlockGuidance(state, gcConfig, context) {
|
|
|
7536
7093
|
}
|
|
7537
7094
|
}
|
|
7538
7095
|
}
|
|
7539
|
-
const usageRatio = context?.currentTokens && context?.modelContextLimit ? context.currentTokens / context.modelContextLimit : 0;
|
|
7540
|
-
if (gcConfig && usageRatio > 0.9) {
|
|
7541
|
-
const promotionThreshold = gcConfig.promotionThreshold;
|
|
7542
|
-
const agingBlocks = [];
|
|
7543
|
-
for (const blockId of activeBlockIds) {
|
|
7544
|
-
const block = state.prune.messages.blocksById.get(blockId);
|
|
7545
|
-
if (!block) continue;
|
|
7546
|
-
const survived = block.survivedCount ?? 0;
|
|
7547
|
-
const gen = block.generation ?? "young";
|
|
7548
|
-
const sizeK = (block.summary.length / 1e3).toFixed(1);
|
|
7549
|
-
const preview = block.summary.slice(0, 120).replace(/\n/g, " ");
|
|
7550
|
-
if (gen === "old" || survived >= promotionThreshold - 2) {
|
|
7551
|
-
agingBlocks.push(
|
|
7552
|
-
` b${blockId}: age=${survived}/${promotionThreshold}, gen=${gen}, size=${sizeK}K chars \u2014 ${preview}...`
|
|
7553
|
-
);
|
|
7554
|
-
}
|
|
7555
|
-
}
|
|
7556
|
-
if (agingBlocks.length > 0) {
|
|
7557
|
-
lines.push("");
|
|
7558
|
-
lines.push("\u26A0\uFE0F Block aging warning \u2014 context near limit, these blocks may be truncated by last-resort GC:");
|
|
7559
|
-
lines.push(...agingBlocks);
|
|
7560
|
-
lines.push(
|
|
7561
|
-
"Re-summarize these blocks into concise new ones to preserve key facts. At 100% context, oversized blocks are auto-truncated as a last resort."
|
|
7562
|
-
);
|
|
7563
|
-
}
|
|
7564
|
-
}
|
|
7565
7096
|
return lines.join("\n");
|
|
7566
7097
|
}
|
|
7567
7098
|
function renderMessagePriorityGuidance(priorityLabel, refs) {
|
|
@@ -8137,7 +7668,7 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
|
|
|
8137
7668
|
perTool.sort((a, b) => b.tokens - a.tokens);
|
|
8138
7669
|
perCode.sort((a, b) => b.tokens - a.tokens);
|
|
8139
7670
|
perText.sort((a, b) => b.tokens - a.tokens);
|
|
8140
|
-
const toolTypeBreakdown = Array.from(toolTypeMap.entries()).map(([
|
|
7671
|
+
const toolTypeBreakdown = Array.from(toolTypeMap.entries()).map(([tool7, tokens]) => ({ tool: tool7, tokens })).sort((a, b) => b.tokens - a.tokens);
|
|
8141
7672
|
return {
|
|
8142
7673
|
toolTokens,
|
|
8143
7674
|
codeTokens,
|
|
@@ -8492,39 +8023,47 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8492
8023
|
(m) => m.info.role === "user" && !isIgnoredUserMessage(m)
|
|
8493
8024
|
);
|
|
8494
8025
|
const currentTurnStart = lastUserIdx >= 0 ? lastUserIdx + 1 : 0;
|
|
8495
|
-
const currentTurnHasCompress = messages.slice(currentTurnStart).some((m) => m.info.role === "assistant" &&
|
|
8026
|
+
const currentTurnHasCompress = messages.slice(currentTurnStart).some((m) => m.info.role === "assistant" && messageHasCompressAttempt(m));
|
|
8496
8027
|
if (currentTurnHasCompress) {
|
|
8497
|
-
const
|
|
8498
|
-
|
|
8499
|
-
state.nudges.
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
const
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
const
|
|
8516
|
-
|
|
8028
|
+
const lastCompressMsg = messages.slice(currentTurnStart).findLast((m) => m.info.role === "assistant" && messageHasCompressAttempt(m));
|
|
8029
|
+
const lastCompressMsgId = lastCompressMsg?.info?.id;
|
|
8030
|
+
if (lastCompressMsgId !== state.nudges.lastProcessedCompressMessageId) {
|
|
8031
|
+
state.nudges.lastProcessedCompressMessageId = lastCompressMsgId;
|
|
8032
|
+
const wasNudgeTriggered = state.nudges.lastNudgeShownTokens !== void 0;
|
|
8033
|
+
state.nudges.contextLimitAnchors.clear();
|
|
8034
|
+
state.nudges.turnNudgeAnchors.clear();
|
|
8035
|
+
state.nudges.iterationNudgeAnchors.clear();
|
|
8036
|
+
state.nudges.lastNudgeShownTokens = void 0;
|
|
8037
|
+
state.nudges.lastToolOutputNudgeTokens = void 0;
|
|
8038
|
+
state.nudges.lastTier2NudgeTokens = void 0;
|
|
8039
|
+
state.nudges.lastTier3NudgeTokens = void 0;
|
|
8040
|
+
const currentTurnHasSuccessfulCompress = messages.slice(currentTurnStart).some((m) => m.info.role === "assistant" && messageHasCompress(m));
|
|
8041
|
+
if (currentTurnHasSuccessfulCompress && wasNudgeTriggered && !state.nudges.compressBaselineSet) {
|
|
8042
|
+
const baseline = state.nudges.lastPerMessageNudgeTokens;
|
|
8043
|
+
const postCompress = currentTokens;
|
|
8044
|
+
const preCompress = preCompressTokens;
|
|
8045
|
+
if (baseline !== void 0 && postCompress !== void 0 && preCompress !== void 0 && preCompress > postCompress) {
|
|
8046
|
+
const growth = preCompress - baseline;
|
|
8047
|
+
const compressed = preCompress - postCompress;
|
|
8048
|
+
if (growth > 0 && compressed > 0) {
|
|
8049
|
+
const ratio = Math.min(1, compressed / growth);
|
|
8050
|
+
const adjustment = Math.min(1, ratio * 2);
|
|
8051
|
+
state.nudges.lastPerMessageNudgeTokens = baseline + Math.round((postCompress - baseline) * adjustment);
|
|
8052
|
+
} else {
|
|
8053
|
+
state.nudges.lastPerMessageNudgeTokens = postCompress;
|
|
8054
|
+
}
|
|
8517
8055
|
} else {
|
|
8518
8056
|
state.nudges.lastPerMessageNudgeTokens = postCompress;
|
|
8519
8057
|
}
|
|
8520
|
-
|
|
8521
|
-
state.nudges.lastPerMessageNudgeTokens = postCompress;
|
|
8058
|
+
state.nudges.compressBaselineSet = true;
|
|
8522
8059
|
}
|
|
8523
|
-
state.nudges.
|
|
8060
|
+
state.nudges.shouldInjectThisTurn = false;
|
|
8061
|
+
saveSessionState(state, logger).catch(() => {
|
|
8062
|
+
});
|
|
8063
|
+
return;
|
|
8524
8064
|
}
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
return;
|
|
8065
|
+
} else {
|
|
8066
|
+
state.nudges.lastProcessedCompressMessageId = void 0;
|
|
8528
8067
|
}
|
|
8529
8068
|
state.nudges.compressBaselineSet = false;
|
|
8530
8069
|
let anchorsChanged = false;
|
|
@@ -8621,9 +8160,6 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8621
8160
|
const growthSinceBaseline = currentTokens !== void 0 && growthReference !== void 0 ? currentTokens - growthReference : void 0;
|
|
8622
8161
|
const nudgeAllowed = emergencyOverride || decision.shouldNudge && growthSinceBaseline !== void 0 && growthSinceBaseline >= growthFloor;
|
|
8623
8162
|
const effectiveTipsVariant = emergencyOverride ? "maxLimit" : decision.tipsVariant;
|
|
8624
|
-
if (nudgeAllowed) {
|
|
8625
|
-
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
8626
|
-
}
|
|
8627
8163
|
if (state.nudges.lastPerMessageNudgeTokens === void 0 && currentTokens !== void 0) {
|
|
8628
8164
|
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
8629
8165
|
baselineReEstablished = true;
|
|
@@ -8672,10 +8208,18 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8672
8208
|
const allProtected = contextRanges.compressible.length === 0 && contextRanges.protected.length > 0;
|
|
8673
8209
|
const allInProtectedZone = protectedRefs.size > 0 && unprotectedCompressible.length === 0;
|
|
8674
8210
|
const nothingToCompress = filterSuppressed || allProtected || allInProtectedZone;
|
|
8675
|
-
|
|
8211
|
+
const shouldInjectNudge = nudgeAllowed && (!nothingToCompress || emergencyOverride);
|
|
8212
|
+
let shouldInject = shouldInjectNudge;
|
|
8676
8213
|
if (nudgeAllowed && nothingToCompress && !emergencyOverride) {
|
|
8677
8214
|
state.nudges.lastNudgeShownTokens = void 0;
|
|
8678
8215
|
}
|
|
8216
|
+
if (shouldInjectNudge) {
|
|
8217
|
+
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
8218
|
+
}
|
|
8219
|
+
if (state.nudges.lastPerMessageNudgeTokens === void 0 && currentTokens !== void 0) {
|
|
8220
|
+
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
8221
|
+
baselineReEstablished = true;
|
|
8222
|
+
}
|
|
8679
8223
|
if (suffixMessage && !shouldInject) {
|
|
8680
8224
|
const tierUsage = getTierTokenUsage(state);
|
|
8681
8225
|
const tierChecks = [
|
|
@@ -8802,7 +8346,7 @@ ${HOW_TO_COMPRESS_RULES}`;
|
|
|
8802
8346
|
const visibleMessageIds = new Set(
|
|
8803
8347
|
messages.map((message) => message.info.id)
|
|
8804
8348
|
);
|
|
8805
|
-
const blockGuidance = buildCompressedBlockGuidance(state,
|
|
8349
|
+
const blockGuidance = buildCompressedBlockGuidance(state, {
|
|
8806
8350
|
currentTokens,
|
|
8807
8351
|
modelContextLimit,
|
|
8808
8352
|
includeHint: tipsText !== null,
|
|
@@ -9622,7 +9166,7 @@ function renderOverview(visibleMessages, summaryTokens, blocks, fetchFailed, raw
|
|
|
9622
9166
|
lines.push(
|
|
9623
9167
|
` ${formatTokens(total)} total | ${formatTokens(totalTool)} tool (${toolPct}%) | ${formatTokens(totalText)} text (${textPct}%) | ${formatTokens(summaryTokens)} summaries (${summaryPct}%)`
|
|
9624
9168
|
);
|
|
9625
|
-
const topTypes = Array.from(toolTypeMap.entries()).map(([
|
|
9169
|
+
const topTypes = Array.from(toolTypeMap.entries()).map(([tool7, tokens]) => ({ tool: tool7, tokens })).sort((a, b) => b.tokens - a.tokens).slice(0, 3);
|
|
9626
9170
|
if (topTypes.length > 0) {
|
|
9627
9171
|
lines.push(
|
|
9628
9172
|
` Top tools: ${topTypes.map((t) => `${t.tool} (${pct(t.tokens, total)}%)`).join(", ")}`
|
|
@@ -9947,69 +9491,6 @@ Call with blockId to get the full summary: acp_context_recap({ blockId: N })`);
|
|
|
9947
9491
|
});
|
|
9948
9492
|
}
|
|
9949
9493
|
|
|
9950
|
-
// lib/compress/prune-tool.ts
|
|
9951
|
-
import { tool as tool7 } from "@opencode-ai/plugin";
|
|
9952
|
-
var PRUNE_TOOL_DESCRIPTION = `Remove old tool outputs by tool type \u2014 frees context without compression.
|
|
9953
|
-
|
|
9954
|
-
Unlike compress (which creates summaries), prune directly strips tool call outputs from context. Use for disposable tool outputs where the content is no longer needed: old todowrite states, edit success echoes, repeated status checks.
|
|
9955
|
-
|
|
9956
|
-
Args:
|
|
9957
|
-
- toolType: tool name to prune (e.g., "todowrite", "bash", "edit")
|
|
9958
|
-
- keepLatest: how many recent calls to keep visible (default 3)`;
|
|
9959
|
-
function createPruneTool(factoryCtx) {
|
|
9960
|
-
factoryCtx.prompts.reload();
|
|
9961
|
-
return tool7({
|
|
9962
|
-
description: PRUNE_TOOL_DESCRIPTION,
|
|
9963
|
-
args: {
|
|
9964
|
-
toolType: tool7.schema.string().describe('Tool name to prune (e.g., "todowrite", "bash", "edit")'),
|
|
9965
|
-
keepLatest: tool7.schema.number().optional().describe("How many recent calls to keep visible (default 3)")
|
|
9966
|
-
},
|
|
9967
|
-
async execute(args, toolCtx) {
|
|
9968
|
-
const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
|
|
9969
|
-
const keepLatest = args.keepLatest ?? 3;
|
|
9970
|
-
const { rawMessages } = await prepareSession(
|
|
9971
|
-
ctx,
|
|
9972
|
-
toolCtx,
|
|
9973
|
-
`Prune: ${args.toolType}`
|
|
9974
|
-
);
|
|
9975
|
-
const matchingCalls = [];
|
|
9976
|
-
for (let i = 0; i < rawMessages.length; i++) {
|
|
9977
|
-
const msg = rawMessages[i];
|
|
9978
|
-
if (!msg) continue;
|
|
9979
|
-
for (const part of msg.parts || []) {
|
|
9980
|
-
if (part.type !== "tool") continue;
|
|
9981
|
-
const partTool = part?.tool || "";
|
|
9982
|
-
if (partTool !== args.toolType) continue;
|
|
9983
|
-
const callId = part?.callID;
|
|
9984
|
-
if (!callId || typeof callId !== "string") continue;
|
|
9985
|
-
if (ctx.state.prune.tools.has(callId)) continue;
|
|
9986
|
-
const tokens = Math.round(JSON.stringify(part).length / 4);
|
|
9987
|
-
matchingCalls.push({ callId, index: i, tokens });
|
|
9988
|
-
}
|
|
9989
|
-
}
|
|
9990
|
-
if (matchingCalls.length <= keepLatest) {
|
|
9991
|
-
return `Nothing to prune \u2014 only ${matchingCalls.length} ${args.toolType} calls visible (keepLatest=${keepLatest}).`;
|
|
9992
|
-
}
|
|
9993
|
-
matchingCalls.sort((a, b) => a.index - b.index);
|
|
9994
|
-
const toPrune = matchingCalls.slice(0, matchingCalls.length - keepLatest);
|
|
9995
|
-
let totalTokens = 0;
|
|
9996
|
-
for (const item of toPrune) {
|
|
9997
|
-
ctx.state.prune.tools.set(item.callId, item.tokens);
|
|
9998
|
-
totalTokens += item.tokens;
|
|
9999
|
-
}
|
|
10000
|
-
await finalizeSession(
|
|
10001
|
-
ctx,
|
|
10002
|
-
toolCtx,
|
|
10003
|
-
rawMessages,
|
|
10004
|
-
[],
|
|
10005
|
-
`Prune ${args.toolType}`
|
|
10006
|
-
);
|
|
10007
|
-
return `Pruned ${toPrune.length} ${args.toolType} calls (~${totalTokens} tokens). Kept latest ${keepLatest}. Outputs will be stripped on next context refresh.
|
|
10008
|
-
IMPORTANT: This was an automatic context pruning. You MUST continue your previous task exactly where you left off. Do NOT ask the user what to do next.`;
|
|
10009
|
-
}
|
|
10010
|
-
});
|
|
10011
|
-
}
|
|
10012
|
-
|
|
10013
9494
|
// lib/compress/hide-consumed.ts
|
|
10014
9495
|
function hideConsumedCompressCalls(state, messages) {
|
|
10015
9496
|
if (state.prune.messages.blocksById.size === 0) {
|
|
@@ -10062,7 +9543,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
10062
9543
|
import { join as join3 } from "path";
|
|
10063
9544
|
import { existsSync as existsSync3 } from "fs";
|
|
10064
9545
|
import { homedir as homedir3 } from "os";
|
|
10065
|
-
var LOG_VERSION = true ? "1.14.
|
|
9546
|
+
var LOG_VERSION = true ? "1.14.5" : "dev";
|
|
10066
9547
|
var Logger = class {
|
|
10067
9548
|
logDir;
|
|
10068
9549
|
enabled;
|
|
@@ -10292,7 +9773,6 @@ You have five context-management tools:
|
|
|
10292
9773
|
- \`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({ topic: "API exploration", content: [{ startId: "m00150", endId: "m00220", summary: "..." }] })\`. 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: "..." }] })\`.
|
|
10293
9774
|
- \`decompress\` \u2014 Restore a previously compressed block's content. By default restores one tier up (T2\u2192T1 summaries, not raw messages). Use \`full: true\` to restore all the way to original messages. Use \`toFile\` to write to file instead of inflating context. Example: \`decompress({ blockId: "b5" })\` or \`decompress({ blockId: "b5", toFile: "path" })\` or \`decompress({ blockId: "b5", full: true })\`.
|
|
10294
9775
|
- \`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" })\`.
|
|
10295
|
-
- \`prune\` \u2014 Remove old tool outputs by tool type, keeping only recent calls. Unlike compress (which creates summaries), prune directly strips outputs. Use for disposable outputs like old todowrite states or edit echoes. Example: \`prune({ toolType: "todowrite", keepLatest: 3 })\`.
|
|
10296
9776
|
- \`acp_status\` \u2014 Context status with compressible ranges. No args = overview + ranges. \`scope:"uncompressed"\` for range view; add \`view:"messages"\` for per-message listing with \`tool\`/\`sort\` filters. \`scope:"compressed"\` for block details.
|
|
10297
9777
|
|
|
10298
9778
|
COMPRESSION PHILOSOPHY
|
|
@@ -10947,6 +10427,73 @@ function renderSystemPrompt(prompts, protectedToolsExtension, manual, subagent)
|
|
|
10947
10427
|
return [prompts.system.trim(), ...extensions].filter(Boolean).join("\n\n").replace(/\n([ \t]*\n)+/g, "\n\n").trim();
|
|
10948
10428
|
}
|
|
10949
10429
|
|
|
10430
|
+
// lib/messages/truncate-tools.ts
|
|
10431
|
+
var TRUNCATION_MARKER = "[truncated for context space";
|
|
10432
|
+
var MIN_OUTPUT_TOKENS = 1e3;
|
|
10433
|
+
var KEEP_PREFIX_CHARS = 2e3;
|
|
10434
|
+
var KEEP_SUFFIX_CHARS = 2e3;
|
|
10435
|
+
var PROTECT_RECENT_MESSAGES = 3;
|
|
10436
|
+
function parseGcThreshold(threshold, modelContextLimit) {
|
|
10437
|
+
if (typeof threshold === "number") return threshold;
|
|
10438
|
+
const str = threshold ?? "100%";
|
|
10439
|
+
const match = /^(\d+(?:\.\d+)?)%$/.exec(str);
|
|
10440
|
+
if (match) return Math.round(Number(match[1]) / 100 * modelContextLimit);
|
|
10441
|
+
return modelContextLimit;
|
|
10442
|
+
}
|
|
10443
|
+
function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
10444
|
+
if (!state.modelContextLimit) return;
|
|
10445
|
+
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
10446
|
+
if (currentTokens === 0) return;
|
|
10447
|
+
const threshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, state.modelContextLimit);
|
|
10448
|
+
if (currentTokens < threshold) return;
|
|
10449
|
+
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES;
|
|
10450
|
+
const candidates = [];
|
|
10451
|
+
for (let mi = 0; mi < messages.length; mi++) {
|
|
10452
|
+
if (mi >= protectedIndex) break;
|
|
10453
|
+
const msg = messages[mi];
|
|
10454
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
10455
|
+
for (let pi = 0; pi < parts.length; pi++) {
|
|
10456
|
+
const part = parts[pi];
|
|
10457
|
+
if (part?.type !== "tool") continue;
|
|
10458
|
+
if (part.state?.status !== "completed") continue;
|
|
10459
|
+
const content = extractCompletedToolOutput(part);
|
|
10460
|
+
if (content === void 0) continue;
|
|
10461
|
+
if (content === "[Old tool result content cleared]") continue;
|
|
10462
|
+
if (content.includes(TRUNCATION_MARKER)) continue;
|
|
10463
|
+
const tokens = countTokens2(content);
|
|
10464
|
+
if (tokens < MIN_OUTPUT_TOKENS) continue;
|
|
10465
|
+
candidates.push({ part, content, tokens });
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
if (candidates.length === 0) return;
|
|
10469
|
+
candidates.sort((a, b) => b.tokens - a.tokens);
|
|
10470
|
+
const targetTokens = threshold * 0.9;
|
|
10471
|
+
let savedTokens = 0;
|
|
10472
|
+
let truncatedCount = 0;
|
|
10473
|
+
for (const { part, content, tokens } of candidates) {
|
|
10474
|
+
if (currentTokens - savedTokens <= targetTokens) break;
|
|
10475
|
+
if (content.length <= KEEP_PREFIX_CHARS + KEEP_SUFFIX_CHARS) continue;
|
|
10476
|
+
const prefix = content.slice(0, KEEP_PREFIX_CHARS);
|
|
10477
|
+
const suffix = content.slice(-KEEP_SUFFIX_CHARS);
|
|
10478
|
+
const truncated = prefix + `
|
|
10479
|
+
|
|
10480
|
+
...${TRUNCATION_MARKER} \u2014 original ~${tokens} tokens]...
|
|
10481
|
+
|
|
10482
|
+
` + suffix;
|
|
10483
|
+
part.state.output = truncated;
|
|
10484
|
+
savedTokens += tokens - countTokens2(truncated);
|
|
10485
|
+
truncatedCount++;
|
|
10486
|
+
}
|
|
10487
|
+
if (truncatedCount > 0) {
|
|
10488
|
+
logger.info("Emergency tool output truncation", {
|
|
10489
|
+
truncatedCount,
|
|
10490
|
+
estimatedSavedTokens: Math.round(savedTokens),
|
|
10491
|
+
currentTokens,
|
|
10492
|
+
threshold
|
|
10493
|
+
});
|
|
10494
|
+
}
|
|
10495
|
+
}
|
|
10496
|
+
|
|
10950
10497
|
// lib/commands/context.ts
|
|
10951
10498
|
function analyzeTokens(state, messages) {
|
|
10952
10499
|
const breakdown = {
|
|
@@ -10995,7 +10542,7 @@ function analyzeTokens(state, messages) {
|
|
|
10995
10542
|
let foundFirstUser = false;
|
|
10996
10543
|
const allToolIds = /* @__PURE__ */ new Set();
|
|
10997
10544
|
const activeToolIds = /* @__PURE__ */ new Set();
|
|
10998
|
-
const
|
|
10545
|
+
const prunedToolIds = /* @__PURE__ */ new Set();
|
|
10999
10546
|
const allMessageIds = /* @__PURE__ */ new Set();
|
|
11000
10547
|
for (const msg of messages) {
|
|
11001
10548
|
allMessageIds.add(msg.info.id);
|
|
@@ -11013,11 +10560,10 @@ function analyzeTokens(state, messages) {
|
|
|
11013
10560
|
activeToolIds.add(toolPart.callID);
|
|
11014
10561
|
}
|
|
11015
10562
|
if (isMessagePruned) {
|
|
11016
|
-
|
|
10563
|
+
prunedToolIds.add(toolPart.callID);
|
|
11017
10564
|
}
|
|
11018
10565
|
}
|
|
11019
|
-
|
|
11020
|
-
if (!isCompacted && !isPruned) {
|
|
10566
|
+
if (!isCompacted) {
|
|
11021
10567
|
if (toolPart.state?.input) {
|
|
11022
10568
|
const inputStr = typeof toolPart.state.input === "string" ? toolPart.state.input : JSON.stringify(toolPart.state.input);
|
|
11023
10569
|
toolInputParts.push(inputStr);
|
|
@@ -11040,14 +10586,7 @@ function analyzeTokens(state, messages) {
|
|
|
11040
10586
|
foundFirstUser = true;
|
|
11041
10587
|
}
|
|
11042
10588
|
}
|
|
11043
|
-
const
|
|
11044
|
-
for (const id of allToolIds) {
|
|
11045
|
-
if (state.prune.tools.has(id)) {
|
|
11046
|
-
prunedByToolIds.add(id);
|
|
11047
|
-
}
|
|
11048
|
-
}
|
|
11049
|
-
const prunedToolIds = /* @__PURE__ */ new Set([...prunedByToolIds, ...prunedByMessageToolIds]);
|
|
11050
|
-
const toolsInContextCount = [...activeToolIds].filter((id) => !prunedByToolIds.has(id)).length;
|
|
10589
|
+
const toolsInContextCount = [...activeToolIds].filter((id) => !prunedToolIds.has(id)).length;
|
|
11051
10590
|
let prunedMessageCount = 0;
|
|
11052
10591
|
for (const [id, entry] of state.prune.messages.byMessageId) {
|
|
11053
10592
|
if (allMessageIds.has(id) && entry.activeBlockIds.length > 0) {
|
|
@@ -11271,7 +10810,6 @@ async function handleDecompressCommand(ctx) {
|
|
|
11271
10810
|
var BASE_COMMANDS = [
|
|
11272
10811
|
["/acp context", "Show token usage breakdown for current session"],
|
|
11273
10812
|
["/acp stats", "Show ACP pruning statistics"],
|
|
11274
|
-
["/acp sweep [n]", "Prune tools since last user message, or last n tools"],
|
|
11275
10813
|
["/acp manual [on|off]", "Toggle manual mode or set explicit state"]
|
|
11276
10814
|
];
|
|
11277
10815
|
var TOOL_COMMANDS = {
|
|
@@ -11323,9 +10861,9 @@ var COMPRESS_TRIGGER_PROMPT = [
|
|
|
11323
10861
|
"Follow the active compress mode, preserve all critical implementation details, and choose safe targets.",
|
|
11324
10862
|
"Return after compress with a brief explanation of what content was compressed."
|
|
11325
10863
|
].join("\n\n");
|
|
11326
|
-
function getTriggerPrompt(
|
|
10864
|
+
function getTriggerPrompt(tool7, state, config, userFocus) {
|
|
11327
10865
|
const base = COMPRESS_TRIGGER_PROMPT;
|
|
11328
|
-
const compressedBlockGuidance = config.compress.mode === "message" ? "" : buildCompressedBlockGuidance(state
|
|
10866
|
+
const compressedBlockGuidance = config.compress.mode === "message" ? "" : buildCompressedBlockGuidance(state);
|
|
11329
10867
|
const sections = [base, compressedBlockGuidance];
|
|
11330
10868
|
if (userFocus && userFocus.trim().length > 0) {
|
|
11331
10869
|
sections.push(`Additional user focus:
|
|
@@ -11352,8 +10890,8 @@ async function handleManualToggleCommand(ctx, modeArg) {
|
|
|
11352
10890
|
);
|
|
11353
10891
|
logger.info("Manual mode toggled", { manualMode: state.manualMode });
|
|
11354
10892
|
}
|
|
11355
|
-
async function handleManualTriggerCommand(ctx,
|
|
11356
|
-
return getTriggerPrompt(
|
|
10893
|
+
async function handleManualTriggerCommand(ctx, tool7, userFocus) {
|
|
10894
|
+
return getTriggerPrompt(tool7, ctx.state, ctx.config, userFocus);
|
|
11357
10895
|
}
|
|
11358
10896
|
function applyPendingManualTrigger(state, messages, logger) {
|
|
11359
10897
|
const pending = state.pendingManualTrigger;
|
|
@@ -11621,7 +11159,7 @@ async function handleStatsCommand(ctx) {
|
|
|
11621
11159
|
(total, target) => total + target.durationMs,
|
|
11622
11160
|
0
|
|
11623
11161
|
);
|
|
11624
|
-
const prunedToolIds = new Set(
|
|
11162
|
+
const prunedToolIds = /* @__PURE__ */ new Set();
|
|
11625
11163
|
for (const block of state.prune.messages.blocksById.values()) {
|
|
11626
11164
|
if (block.active) {
|
|
11627
11165
|
for (const toolId of block.effectiveToolIds) {
|
|
@@ -11659,243 +11197,6 @@ async function handleStatsCommand(ctx) {
|
|
|
11659
11197
|
});
|
|
11660
11198
|
}
|
|
11661
11199
|
|
|
11662
|
-
// lib/commands/sweep.ts
|
|
11663
|
-
function findLastUserMessageIndex(messages) {
|
|
11664
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
11665
|
-
const msg = messages[i];
|
|
11666
|
-
if (msg.info.role === "user" && !isIgnoredUserMessage(msg)) {
|
|
11667
|
-
return i;
|
|
11668
|
-
}
|
|
11669
|
-
}
|
|
11670
|
-
return -1;
|
|
11671
|
-
}
|
|
11672
|
-
function collectToolIdsAfterIndex(state, messages, afterIndex) {
|
|
11673
|
-
const toolIds = [];
|
|
11674
|
-
for (let i = afterIndex + 1; i < messages.length; i++) {
|
|
11675
|
-
const msg = messages[i];
|
|
11676
|
-
if (isMessageCompacted(state, msg)) {
|
|
11677
|
-
continue;
|
|
11678
|
-
}
|
|
11679
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
11680
|
-
if (parts.length > 0) {
|
|
11681
|
-
for (const part of parts) {
|
|
11682
|
-
if (part.type === "tool" && part.callID && part.tool) {
|
|
11683
|
-
toolIds.push(part.callID);
|
|
11684
|
-
}
|
|
11685
|
-
}
|
|
11686
|
-
}
|
|
11687
|
-
}
|
|
11688
|
-
return toolIds;
|
|
11689
|
-
}
|
|
11690
|
-
function formatNoUserMessage() {
|
|
11691
|
-
const lines = [];
|
|
11692
|
-
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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
|
|
11693
|
-
lines.push("\u2502 ACP Sweep \u2502");
|
|
11694
|
-
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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
|
|
11695
|
-
lines.push("");
|
|
11696
|
-
lines.push("Nothing swept: no user message found.");
|
|
11697
|
-
return lines.join("\n");
|
|
11698
|
-
}
|
|
11699
|
-
function formatSweepMessage(toolCount, tokensSaved, mode, toolIds, toolMetadata, workingDirectory, skippedProtected) {
|
|
11700
|
-
const lines = [];
|
|
11701
|
-
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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
|
|
11702
|
-
lines.push("\u2502 ACP Sweep \u2502");
|
|
11703
|
-
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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
|
|
11704
|
-
lines.push("");
|
|
11705
|
-
if (toolCount === 0) {
|
|
11706
|
-
if (mode === "since-user") {
|
|
11707
|
-
lines.push("No tools found since the previous user message.");
|
|
11708
|
-
} else {
|
|
11709
|
-
lines.push(`No tools found to sweep.`);
|
|
11710
|
-
}
|
|
11711
|
-
if (skippedProtected && skippedProtected > 0) {
|
|
11712
|
-
lines.push(`(${skippedProtected} protected tool(s) skipped)`);
|
|
11713
|
-
}
|
|
11714
|
-
} else {
|
|
11715
|
-
if (mode === "since-user") {
|
|
11716
|
-
lines.push(`Swept ${toolCount} tool(s) since the previous user message.`);
|
|
11717
|
-
} else {
|
|
11718
|
-
lines.push(`Swept the last ${toolCount} tool(s).`);
|
|
11719
|
-
}
|
|
11720
|
-
lines.push(`Tokens saved: ~${tokensSaved.toLocaleString()}`);
|
|
11721
|
-
if (skippedProtected && skippedProtected > 0) {
|
|
11722
|
-
lines.push(`(${skippedProtected} protected tool(s) skipped)`);
|
|
11723
|
-
}
|
|
11724
|
-
lines.push("");
|
|
11725
|
-
const itemLines = formatPrunedItemsList(toolIds, toolMetadata, workingDirectory);
|
|
11726
|
-
lines.push(...itemLines);
|
|
11727
|
-
}
|
|
11728
|
-
return lines.join("\n");
|
|
11729
|
-
}
|
|
11730
|
-
async function handleSweepCommand(ctx) {
|
|
11731
|
-
const { client, state, config, logger, sessionId, messages, args, workingDirectory } = ctx;
|
|
11732
|
-
const params = getCurrentParams(state, messages, logger);
|
|
11733
|
-
const protectedTools = config.commands.protectedTools;
|
|
11734
|
-
syncToolCache(state, config, logger, messages);
|
|
11735
|
-
buildToolIdList(state, messages);
|
|
11736
|
-
const numArg = args[0] ? parseInt(args[0], 10) : null;
|
|
11737
|
-
const isLastNMode = numArg !== null && !isNaN(numArg) && numArg > 0;
|
|
11738
|
-
let toolIdsToSweep;
|
|
11739
|
-
let mode;
|
|
11740
|
-
if (isLastNMode) {
|
|
11741
|
-
mode = "last-n";
|
|
11742
|
-
const startIndex = Math.max(0, state.toolIdList.length - numArg);
|
|
11743
|
-
toolIdsToSweep = state.toolIdList.slice(startIndex);
|
|
11744
|
-
logger.info(`Sweep command: last ${numArg} mode, found ${toolIdsToSweep.length} tools`);
|
|
11745
|
-
} else {
|
|
11746
|
-
mode = "since-user";
|
|
11747
|
-
const lastUserMsgIndex = findLastUserMessageIndex(messages);
|
|
11748
|
-
if (lastUserMsgIndex === -1) {
|
|
11749
|
-
const message2 = formatNoUserMessage();
|
|
11750
|
-
await sendIgnoredMessage(client, sessionId, message2, params, logger);
|
|
11751
|
-
logger.info("Sweep command: no user message found");
|
|
11752
|
-
return;
|
|
11753
|
-
} else {
|
|
11754
|
-
toolIdsToSweep = collectToolIdsAfterIndex(state, messages, lastUserMsgIndex);
|
|
11755
|
-
logger.info(
|
|
11756
|
-
`Sweep command: found last user at index ${lastUserMsgIndex}, sweeping ${toolIdsToSweep.length} tools`
|
|
11757
|
-
);
|
|
11758
|
-
}
|
|
11759
|
-
}
|
|
11760
|
-
const newToolIds = toolIdsToSweep.filter((id) => {
|
|
11761
|
-
if (state.prune.tools.has(id)) {
|
|
11762
|
-
return false;
|
|
11763
|
-
}
|
|
11764
|
-
const entry = state.toolParameters.get(id);
|
|
11765
|
-
if (!entry) {
|
|
11766
|
-
return true;
|
|
11767
|
-
}
|
|
11768
|
-
if (isToolNameProtected(entry.tool, protectedTools)) {
|
|
11769
|
-
logger.debug(`Sweep: skipping protected tool ${entry.tool} (${id})`);
|
|
11770
|
-
return false;
|
|
11771
|
-
}
|
|
11772
|
-
const filePaths = getFilePathsFromParameters(entry.tool, entry.parameters);
|
|
11773
|
-
if (isFilePathProtected(filePaths, config.protectedFilePatterns)) {
|
|
11774
|
-
logger.debug(`Sweep: skipping protected file path(s) ${filePaths.join(", ")} (${id})`);
|
|
11775
|
-
return false;
|
|
11776
|
-
}
|
|
11777
|
-
return true;
|
|
11778
|
-
});
|
|
11779
|
-
const skippedProtected = toolIdsToSweep.filter((id) => {
|
|
11780
|
-
const entry = state.toolParameters.get(id);
|
|
11781
|
-
if (!entry) {
|
|
11782
|
-
return false;
|
|
11783
|
-
}
|
|
11784
|
-
if (isToolNameProtected(entry.tool, protectedTools)) {
|
|
11785
|
-
return true;
|
|
11786
|
-
}
|
|
11787
|
-
const filePaths = getFilePathsFromParameters(entry.tool, entry.parameters);
|
|
11788
|
-
if (isFilePathProtected(filePaths, config.protectedFilePatterns)) {
|
|
11789
|
-
return true;
|
|
11790
|
-
}
|
|
11791
|
-
return false;
|
|
11792
|
-
}).length;
|
|
11793
|
-
if (newToolIds.length === 0) {
|
|
11794
|
-
const message2 = formatSweepMessage(
|
|
11795
|
-
0,
|
|
11796
|
-
0,
|
|
11797
|
-
mode,
|
|
11798
|
-
[],
|
|
11799
|
-
/* @__PURE__ */ new Map(),
|
|
11800
|
-
workingDirectory,
|
|
11801
|
-
skippedProtected
|
|
11802
|
-
);
|
|
11803
|
-
await sendIgnoredMessage(client, sessionId, message2, params, logger);
|
|
11804
|
-
logger.info("Sweep command: no new tools to sweep", { skippedProtected });
|
|
11805
|
-
return;
|
|
11806
|
-
}
|
|
11807
|
-
const tokensSaved = getTotalToolTokens(state, newToolIds);
|
|
11808
|
-
for (const id of newToolIds) {
|
|
11809
|
-
const entry = state.toolParameters.get(id);
|
|
11810
|
-
state.prune.tools.set(id, entry?.tokenCount ?? 0);
|
|
11811
|
-
}
|
|
11812
|
-
state.stats.pruneTokenCounter += tokensSaved;
|
|
11813
|
-
state.stats.totalPruneTokens += state.stats.pruneTokenCounter;
|
|
11814
|
-
state.stats.pruneTokenCounter = 0;
|
|
11815
|
-
const toolMetadata = /* @__PURE__ */ new Map();
|
|
11816
|
-
for (const id of newToolIds) {
|
|
11817
|
-
const entry = state.toolParameters.get(id);
|
|
11818
|
-
if (entry) {
|
|
11819
|
-
toolMetadata.set(id, entry);
|
|
11820
|
-
}
|
|
11821
|
-
}
|
|
11822
|
-
saveSessionState(state, logger).catch(
|
|
11823
|
-
(err) => logger.error("Failed to persist state after sweep", { error: err.message })
|
|
11824
|
-
);
|
|
11825
|
-
const message = formatSweepMessage(
|
|
11826
|
-
newToolIds.length,
|
|
11827
|
-
tokensSaved,
|
|
11828
|
-
mode,
|
|
11829
|
-
newToolIds,
|
|
11830
|
-
toolMetadata,
|
|
11831
|
-
workingDirectory,
|
|
11832
|
-
skippedProtected
|
|
11833
|
-
);
|
|
11834
|
-
await sendIgnoredMessage(client, sessionId, message, params, logger);
|
|
11835
|
-
logger.info("Sweep command completed", {
|
|
11836
|
-
toolsSwept: newToolIds.length,
|
|
11837
|
-
tokensSaved,
|
|
11838
|
-
skippedProtected,
|
|
11839
|
-
mode,
|
|
11840
|
-
tools: Array.from(toolMetadata.entries()).map(([id, entry]) => ({
|
|
11841
|
-
id,
|
|
11842
|
-
tool: entry.tool
|
|
11843
|
-
}))
|
|
11844
|
-
});
|
|
11845
|
-
}
|
|
11846
|
-
|
|
11847
|
-
// lib/gc/truncate.ts
|
|
11848
|
-
function runTruncateGC(blocks, params) {
|
|
11849
|
-
let compactedBlocks = 0;
|
|
11850
|
-
let savedTokens = 0;
|
|
11851
|
-
for (const block of blocks) {
|
|
11852
|
-
if (!block.active) continue;
|
|
11853
|
-
if (block.summary.length <= params.maxOldGenSummaryLength) continue;
|
|
11854
|
-
const originalLength = block.summary.length;
|
|
11855
|
-
const truncated = truncateSummary(block.summary, params.maxOldGenSummaryLength, block.blockId);
|
|
11856
|
-
const savedChars = originalLength - truncated.length;
|
|
11857
|
-
if (savedChars > 0) {
|
|
11858
|
-
block.summary = truncated;
|
|
11859
|
-
block.summaryTokens = Math.round(truncated.length / 4);
|
|
11860
|
-
compactedBlocks++;
|
|
11861
|
-
savedTokens += Math.round(savedChars / 4);
|
|
11862
|
-
}
|
|
11863
|
-
}
|
|
11864
|
-
return { compactedBlocks, savedTokens };
|
|
11865
|
-
}
|
|
11866
|
-
function truncateSummary(summary, maxLength, _blockId) {
|
|
11867
|
-
if (summary.length <= maxLength) return summary;
|
|
11868
|
-
const headerEnd = summary.indexOf("\n");
|
|
11869
|
-
if (headerEnd === -1) return summary.slice(0, maxLength) + "\n...\n[GC truncated]";
|
|
11870
|
-
const header = summary.slice(0, headerEnd + 1);
|
|
11871
|
-
const footerStart = summary.lastIndexOf("\n\n");
|
|
11872
|
-
const footer = footerStart > headerEnd ? summary.slice(footerStart) : "";
|
|
11873
|
-
const availableForContent = maxLength - header.length - footer.length - 20;
|
|
11874
|
-
if (availableForContent < 100) {
|
|
11875
|
-
return header + "...\n[GC truncated]" + footer;
|
|
11876
|
-
}
|
|
11877
|
-
const content = summary.slice(headerEnd + 1, headerEnd + 1 + availableForContent);
|
|
11878
|
-
return header + content + "\n...\n[GC truncated]" + footer;
|
|
11879
|
-
}
|
|
11880
|
-
function shouldRunMajorGC(currentTokens, modelContextLimit, gcConfig) {
|
|
11881
|
-
if (!modelContextLimit || modelContextLimit === 0) return false;
|
|
11882
|
-
const threshold = parseGcThreshold(gcConfig.majorGcThresholdPercent, modelContextLimit);
|
|
11883
|
-
return currentTokens >= threshold;
|
|
11884
|
-
}
|
|
11885
|
-
function getGCParams(gcConfig, modelContextLimit, currentTokens) {
|
|
11886
|
-
return {
|
|
11887
|
-
maxOldGenSummaryLength: gcConfig.maxOldGenSummaryLength,
|
|
11888
|
-
modelContextLimit,
|
|
11889
|
-
currentTokens
|
|
11890
|
-
};
|
|
11891
|
-
}
|
|
11892
|
-
function parseGcThreshold(limit, modelContextLimit) {
|
|
11893
|
-
if (typeof limit === "number") return limit;
|
|
11894
|
-
const percent = parseFloat(limit.slice(0, -1));
|
|
11895
|
-
if (isNaN(percent)) return modelContextLimit;
|
|
11896
|
-
return Math.round(Math.max(0, Math.min(100, Math.round(percent))) / 100 * modelContextLimit);
|
|
11897
|
-
}
|
|
11898
|
-
|
|
11899
11200
|
// lib/gc/merge.ts
|
|
11900
11201
|
function collectActiveOldGenBlocks(state, maxOldGenSummaryLength) {
|
|
11901
11202
|
const blocks = [];
|
|
@@ -12116,31 +11417,6 @@ function createSystemPromptHandler(registry3, logger, config, prompts) {
|
|
|
12116
11417
|
}
|
|
12117
11418
|
};
|
|
12118
11419
|
}
|
|
12119
|
-
function runMajorGC(state, config, logger, messages) {
|
|
12120
|
-
if (!state.modelContextLimit) return;
|
|
12121
|
-
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
12122
|
-
if (!shouldRunMajorGC(currentTokens, state.modelContextLimit, config.gc)) return;
|
|
12123
|
-
const oldBlocks = [];
|
|
12124
|
-
for (const [blockId, block] of state.prune.messages.blocksById) {
|
|
12125
|
-
if (!block.active) continue;
|
|
12126
|
-
if (block.generation === "old" || block.generation === void 0 || block.summary.length > config.gc.maxOldGenSummaryLength) {
|
|
12127
|
-
oldBlocks.push(block);
|
|
12128
|
-
}
|
|
12129
|
-
}
|
|
12130
|
-
if (oldBlocks.length === 0) return;
|
|
12131
|
-
const params = getGCParams(config.gc, state.modelContextLimit, currentTokens);
|
|
12132
|
-
const result = runTruncateGC(oldBlocks, params);
|
|
12133
|
-
if (result.compactedBlocks > 0) {
|
|
12134
|
-
logger.info("Major GC: truncated old-gen blocks", {
|
|
12135
|
-
compactedBlocks: result.compactedBlocks,
|
|
12136
|
-
savedTokens: result.savedTokens,
|
|
12137
|
-
currentTokens,
|
|
12138
|
-
threshold: config.gc.majorGcThresholdPercent
|
|
12139
|
-
});
|
|
12140
|
-
saveSessionState(state, logger).catch(() => {
|
|
12141
|
-
});
|
|
12142
|
-
}
|
|
12143
|
-
}
|
|
12144
11420
|
function createChatMessageTransformHandler(client, registry3, logger, config, prompts, hostPermissions) {
|
|
12145
11421
|
return async (input, output) => {
|
|
12146
11422
|
const receivedMessages = Array.isArray(output.messages) ? output.messages.length : 0;
|
|
@@ -12184,7 +11460,6 @@ function createChatMessageTransformHandler(client, registry3, logger, config, pr
|
|
|
12184
11460
|
}
|
|
12185
11461
|
syncToolCache(state, config, logger, output.messages);
|
|
12186
11462
|
buildToolIdList(state, output.messages);
|
|
12187
|
-
runMajorGC(state, config, logger, output.messages);
|
|
12188
11463
|
const batchResult = runBatchCleanup(state, config, logger, output.messages);
|
|
12189
11464
|
if (batchResult.mergedCount > 0) {
|
|
12190
11465
|
saveSessionState(state, logger).catch(() => {
|
|
@@ -12192,6 +11467,7 @@ function createChatMessageTransformHandler(client, registry3, logger, config, pr
|
|
|
12192
11467
|
}
|
|
12193
11468
|
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
12194
11469
|
prune(state, logger, config, output.messages);
|
|
11470
|
+
truncateLargeToolOutputs(state, config, logger, output.messages);
|
|
12195
11471
|
hideConsumedCompressCalls(state, output.messages);
|
|
12196
11472
|
assignMessageRefs(state, output.messages);
|
|
12197
11473
|
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
@@ -12268,14 +11544,6 @@ function createCommandExecuteHandler(client, registry3, logger, config, workingD
|
|
|
12268
11544
|
await handleStatsCommand(commandCtx);
|
|
12269
11545
|
throw new Error("__DCP_STATS_HANDLED__");
|
|
12270
11546
|
}
|
|
12271
|
-
if (subcommand === "sweep") {
|
|
12272
|
-
await handleSweepCommand({
|
|
12273
|
-
...commandCtx,
|
|
12274
|
-
args: subArgs,
|
|
12275
|
-
workingDirectory
|
|
12276
|
-
});
|
|
12277
|
-
throw new Error("__DCP_SWEEP_HANDLED__");
|
|
12278
|
-
}
|
|
12279
11547
|
if (subcommand === "manual") {
|
|
12280
11548
|
await handleManualToggleCommand(commandCtx, subArgs[0]?.toLowerCase());
|
|
12281
11549
|
throw new Error("__DCP_MANUAL_HANDLED__");
|
|
@@ -12581,9 +11849,7 @@ var server = (async (ctx) => {
|
|
|
12581
11849
|
if (isSecureMode()) {
|
|
12582
11850
|
configureClientAuth(ctx.client);
|
|
12583
11851
|
}
|
|
12584
|
-
logger.info("DCP initialized"
|
|
12585
|
-
strategies: config.strategies
|
|
12586
|
-
});
|
|
11852
|
+
logger.info("DCP initialized");
|
|
12587
11853
|
startAutoUpdate(ctx, config.autoUpdate);
|
|
12588
11854
|
const compressToolContext = {
|
|
12589
11855
|
client: ctx.client,
|
|
@@ -12621,7 +11887,6 @@ var server = (async (ctx) => {
|
|
|
12621
11887
|
...config.compress.permission !== "deny" && {
|
|
12622
11888
|
compress: config.compress.mode === "message" ? createCompressMessageTool(compressToolContext) : createCompressRangeTool(compressToolContext),
|
|
12623
11889
|
decompress: createDecompressTool(compressToolContext),
|
|
12624
|
-
prune: createPruneTool(compressToolContext),
|
|
12625
11890
|
search_context: createSearchContextTool(compressToolContext),
|
|
12626
11891
|
acp_status: createAcpStatusTool(compressToolContext),
|
|
12627
11892
|
acp_context_recap: createAcpContextRecapTool(compressToolContext)
|