@lexwdex-org/opencode-dcp 3.4.8 → 3.4.10
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.en.md +2 -2
- package/README.md +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +870 -357
- package/dist/index.js.map +1 -1
- package/dist/lib/commands/context.d.ts +2 -1
- package/dist/lib/commands/context.d.ts.map +1 -1
- package/dist/lib/commands/decompress.d.ts +2 -1
- package/dist/lib/commands/decompress.d.ts.map +1 -1
- package/dist/lib/commands/help.d.ts +2 -1
- package/dist/lib/commands/help.d.ts.map +1 -1
- package/dist/lib/commands/manual.d.ts +2 -1
- package/dist/lib/commands/manual.d.ts.map +1 -1
- package/dist/lib/commands/recompress.d.ts +2 -1
- package/dist/lib/commands/recompress.d.ts.map +1 -1
- package/dist/lib/commands/stats.d.ts +2 -1
- package/dist/lib/commands/stats.d.ts.map +1 -1
- package/dist/lib/commands/sweep.d.ts +2 -1
- package/dist/lib/commands/sweep.d.ts.map +1 -1
- package/dist/lib/compress/external-content.d.ts +4 -0
- package/dist/lib/compress/external-content.d.ts.map +1 -0
- package/dist/lib/compress/external-inference.d.ts.map +1 -1
- package/dist/lib/compress/message.d.ts.map +1 -1
- package/dist/lib/compress/pipeline.d.ts +3 -2
- package/dist/lib/compress/pipeline.d.ts.map +1 -1
- package/dist/lib/compress/protected-content.d.ts +2 -1
- package/dist/lib/compress/protected-content.d.ts.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/compress/search.d.ts +2 -1
- package/dist/lib/compress/search.d.ts.map +1 -1
- package/dist/lib/compress/timing.d.ts +2 -0
- package/dist/lib/compress/timing.d.ts.map +1 -1
- package/dist/lib/compress/types.d.ts +4 -3
- package/dist/lib/compress/types.d.ts.map +1 -1
- package/dist/lib/compress-permission.d.ts.map +1 -1
- package/dist/lib/concurrency.d.ts +2 -0
- package/dist/lib/concurrency.d.ts.map +1 -0
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts +6 -5
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/inject/subagent-results.d.ts +2 -1
- package/dist/lib/messages/inject/subagent-results.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +2 -2
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/messages/utils.d.ts.map +1 -1
- package/dist/lib/opencode-client.d.ts +3 -0
- package/dist/lib/opencode-client.d.ts.map +1 -0
- package/dist/lib/prompts/context-limit-nudge.d.ts +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/store.d.ts +1 -0
- package/dist/lib/prompts/store.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/prompts/turn-nudge.d.ts +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts.map +1 -1
- package/dist/lib/state/index.d.ts +1 -0
- package/dist/lib/state/index.d.ts.map +1 -1
- package/dist/lib/state/persistence.d.ts +1 -0
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts +5 -2
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/store.d.ts +38 -0
- package/dist/lib/state/store.d.ts.map +1 -0
- package/dist/lib/state/tool-cache.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +2 -1
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/state/utils.d.ts +4 -1
- package/dist/lib/state/utils.d.ts.map +1 -1
- package/dist/lib/strategies/deduplication.d.ts.map +1 -1
- package/dist/lib/token-utils.d.ts +0 -1
- package/dist/lib/token-utils.d.ts.map +1 -1
- package/dist/lib/ui/notification.d.ts +4 -3
- package/dist/lib/ui/notification.d.ts.map +1 -1
- package/dist/lib/update.d.ts +3 -9
- package/dist/lib/update.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/lib/auth.d.ts +0 -4
- package/dist/lib/auth.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -912,7 +912,6 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
912
912
|
"enabled",
|
|
913
913
|
"autoUpdate",
|
|
914
914
|
"debug",
|
|
915
|
-
"showUpdateToasts",
|
|
916
915
|
"pruneNotification",
|
|
917
916
|
"pruneNotificationType",
|
|
918
917
|
"turnProtection",
|
|
@@ -935,6 +934,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
935
934
|
"compress.summaryBuffer",
|
|
936
935
|
"compress.maxContextLimit",
|
|
937
936
|
"compress.minContextLimit",
|
|
937
|
+
"compress.boundaryNudge",
|
|
938
938
|
"compress.modelMaxLimits",
|
|
939
939
|
"compress.modelMinLimits",
|
|
940
940
|
"compress.nudgeFrequency",
|
|
@@ -944,6 +944,11 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
944
944
|
"compress.protectTags",
|
|
945
945
|
"compress.protectUserMessages",
|
|
946
946
|
"compress.externalModel",
|
|
947
|
+
"compress.externalModel.url",
|
|
948
|
+
"compress.externalModel.model",
|
|
949
|
+
"compress.externalModel.apiKey",
|
|
950
|
+
"compress.externalModel.timeout",
|
|
951
|
+
"compress.externalModel.retries",
|
|
947
952
|
"strategies",
|
|
948
953
|
"strategies.deduplication",
|
|
949
954
|
"strategies.deduplication.enabled",
|
|
@@ -1138,6 +1143,13 @@ function validateConfigTypes(config) {
|
|
|
1138
1143
|
actual: typeof compress.summaryBuffer
|
|
1139
1144
|
});
|
|
1140
1145
|
}
|
|
1146
|
+
if (compress.boundaryNudge !== void 0 && typeof compress.boundaryNudge !== "boolean") {
|
|
1147
|
+
errors.push({
|
|
1148
|
+
key: "compress.boundaryNudge",
|
|
1149
|
+
expected: "boolean",
|
|
1150
|
+
actual: typeof compress.boundaryNudge
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1141
1153
|
if (compress.nudgeFrequency !== void 0 && typeof compress.nudgeFrequency !== "number") {
|
|
1142
1154
|
errors.push({
|
|
1143
1155
|
key: "compress.nudgeFrequency",
|
|
@@ -1259,6 +1271,12 @@ function validateConfigTypes(config) {
|
|
|
1259
1271
|
expected: "string",
|
|
1260
1272
|
actual: typeof compress.externalModel.url
|
|
1261
1273
|
});
|
|
1274
|
+
} else if (compress.externalModel.url.trim().length === 0) {
|
|
1275
|
+
errors.push({
|
|
1276
|
+
key: "compress.externalModel.url",
|
|
1277
|
+
expected: "non-empty string",
|
|
1278
|
+
actual: "empty string"
|
|
1279
|
+
});
|
|
1262
1280
|
}
|
|
1263
1281
|
if (typeof compress.externalModel.model !== "string") {
|
|
1264
1282
|
errors.push({
|
|
@@ -1266,6 +1284,33 @@ function validateConfigTypes(config) {
|
|
|
1266
1284
|
expected: "string",
|
|
1267
1285
|
actual: typeof compress.externalModel.model
|
|
1268
1286
|
});
|
|
1287
|
+
} else if (compress.externalModel.model.trim().length === 0) {
|
|
1288
|
+
errors.push({
|
|
1289
|
+
key: "compress.externalModel.model",
|
|
1290
|
+
expected: "non-empty string",
|
|
1291
|
+
actual: "empty string"
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
if (compress.externalModel.apiKey !== void 0 && typeof compress.externalModel.apiKey !== "string") {
|
|
1295
|
+
errors.push({
|
|
1296
|
+
key: "compress.externalModel.apiKey",
|
|
1297
|
+
expected: "string",
|
|
1298
|
+
actual: typeof compress.externalModel.apiKey
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
if (compress.externalModel.timeout !== void 0 && (typeof compress.externalModel.timeout !== "number" || !Number.isFinite(compress.externalModel.timeout) || compress.externalModel.timeout <= 0)) {
|
|
1302
|
+
errors.push({
|
|
1303
|
+
key: "compress.externalModel.timeout",
|
|
1304
|
+
expected: "positive finite number",
|
|
1305
|
+
actual: JSON.stringify(compress.externalModel.timeout)
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
if (compress.externalModel.retries !== void 0 && (!Number.isInteger(compress.externalModel.retries) || compress.externalModel.retries < 0)) {
|
|
1309
|
+
errors.push({
|
|
1310
|
+
key: "compress.externalModel.retries",
|
|
1311
|
+
expected: "non-negative integer",
|
|
1312
|
+
actual: JSON.stringify(compress.externalModel.retries)
|
|
1313
|
+
});
|
|
1269
1314
|
}
|
|
1270
1315
|
}
|
|
1271
1316
|
}
|
|
@@ -1393,6 +1438,7 @@ var defaultConfig = {
|
|
|
1393
1438
|
summaryBuffer: true,
|
|
1394
1439
|
maxContextLimit: "85%",
|
|
1395
1440
|
minContextLimit: "50%",
|
|
1441
|
+
boundaryNudge: true,
|
|
1396
1442
|
nudgeFrequency: 2,
|
|
1397
1443
|
iterationNudgeThreshold: 15,
|
|
1398
1444
|
nudgeForce: "strong",
|
|
@@ -1515,6 +1561,7 @@ function mergeCompress(base, override) {
|
|
|
1515
1561
|
summaryBuffer: override.summaryBuffer ?? base.summaryBuffer,
|
|
1516
1562
|
maxContextLimit: override.maxContextLimit ?? base.maxContextLimit,
|
|
1517
1563
|
minContextLimit: override.minContextLimit ?? base.minContextLimit,
|
|
1564
|
+
boundaryNudge: override.boundaryNudge ?? base.boundaryNudge,
|
|
1518
1565
|
modelMaxLimits: override.modelMaxLimits ?? base.modelMaxLimits,
|
|
1519
1566
|
modelMinLimits: override.modelMinLimits ?? base.modelMinLimits,
|
|
1520
1567
|
nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
|
|
@@ -1746,6 +1793,8 @@ function isProtectedUserMessage(config, message) {
|
|
|
1746
1793
|
// lib/token-utils.ts
|
|
1747
1794
|
var anthropicCountTokens = _anthropicTokenizer.countTokens ?? _anthropicTokenizer.default?.countTokens;
|
|
1748
1795
|
function getCurrentTokenUsage(state, messages) {
|
|
1796
|
+
let reportedTokens = 0;
|
|
1797
|
+
let lastReportedIndex = -1;
|
|
1749
1798
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1750
1799
|
const msg = messages[i];
|
|
1751
1800
|
if (msg.info.role !== "assistant") {
|
|
@@ -1756,16 +1805,23 @@ function getCurrentTokenUsage(state, messages) {
|
|
|
1756
1805
|
continue;
|
|
1757
1806
|
}
|
|
1758
1807
|
if (state.lastCompaction > 0 && (msg.info.time.created < state.lastCompaction || msg.info.summary === true && msg.info.time.created === state.lastCompaction)) {
|
|
1759
|
-
|
|
1808
|
+
lastReportedIndex = i;
|
|
1809
|
+
break;
|
|
1760
1810
|
}
|
|
1761
1811
|
const input = assistantInfo.tokens?.input || 0;
|
|
1762
1812
|
const output = assistantInfo.tokens?.output || 0;
|
|
1763
1813
|
const reasoning = assistantInfo.tokens?.reasoning || 0;
|
|
1764
1814
|
const cacheRead = assistantInfo.tokens?.cache?.read || 0;
|
|
1765
1815
|
const cacheWrite = assistantInfo.tokens?.cache?.write || 0;
|
|
1766
|
-
|
|
1816
|
+
reportedTokens = input + output + reasoning + cacheRead + cacheWrite;
|
|
1817
|
+
lastReportedIndex = i;
|
|
1818
|
+
break;
|
|
1767
1819
|
}
|
|
1768
|
-
|
|
1820
|
+
let unreportedTokens = 0;
|
|
1821
|
+
for (let i = lastReportedIndex + 1; i < messages.length; i++) {
|
|
1822
|
+
unreportedTokens += countAllMessageTokens(messages[i]);
|
|
1823
|
+
}
|
|
1824
|
+
return reportedTokens + unreportedTokens;
|
|
1769
1825
|
}
|
|
1770
1826
|
function getCurrentParams(state, messages, logger) {
|
|
1771
1827
|
const userMsg = getLastUserMessage(messages);
|
|
@@ -1774,16 +1830,14 @@ function getCurrentParams(state, messages, logger) {
|
|
|
1774
1830
|
return {
|
|
1775
1831
|
providerId: void 0,
|
|
1776
1832
|
modelId: void 0,
|
|
1777
|
-
agent: void 0
|
|
1778
|
-
variant: void 0
|
|
1833
|
+
agent: void 0
|
|
1779
1834
|
};
|
|
1780
1835
|
}
|
|
1781
1836
|
const userInfo = userMsg.info;
|
|
1782
1837
|
const agent = userInfo.agent;
|
|
1783
1838
|
const providerId = userInfo.model.providerID;
|
|
1784
1839
|
const modelId = userInfo.model.modelID;
|
|
1785
|
-
|
|
1786
|
-
return { providerId, modelId, agent, variant };
|
|
1840
|
+
return { providerId, modelId, agent };
|
|
1787
1841
|
}
|
|
1788
1842
|
function countTokens2(text) {
|
|
1789
1843
|
if (!text) return 0;
|
|
@@ -1828,7 +1882,8 @@ function extractToolContent(part) {
|
|
|
1828
1882
|
}
|
|
1829
1883
|
function countToolTokens(part) {
|
|
1830
1884
|
const contents = extractToolContent(part);
|
|
1831
|
-
|
|
1885
|
+
if (contents.length === 0) return 0;
|
|
1886
|
+
return Math.ceil(Buffer.byteLength(contents.join(" "), "utf-8") / 4);
|
|
1832
1887
|
}
|
|
1833
1888
|
function getTotalToolTokens(state, toolIds) {
|
|
1834
1889
|
let total = 0;
|
|
@@ -1910,7 +1965,7 @@ var MESSAGE_REF_MAX_INDEX = 9999;
|
|
|
1910
1965
|
function formatMessageRef(index) {
|
|
1911
1966
|
if (!Number.isInteger(index) || index < MESSAGE_REF_MIN_INDEX || index > MESSAGE_REF_MAX_INDEX) {
|
|
1912
1967
|
throw new Error(
|
|
1913
|
-
`Message ID index out of bounds: ${index}. Supported range is
|
|
1968
|
+
`Message ID index out of bounds: ${index}. Supported range is ${MESSAGE_REF_MIN_INDEX}-${MESSAGE_REF_MAX_INDEX}.`
|
|
1914
1969
|
);
|
|
1915
1970
|
}
|
|
1916
1971
|
return `m${index.toString().padStart(MESSAGE_REF_WIDTH, "0")}`;
|
|
@@ -2011,14 +2066,16 @@ function assignMessageRefs(state, messages) {
|
|
|
2011
2066
|
return assigned;
|
|
2012
2067
|
}
|
|
2013
2068
|
function allocateNextMessageRef(state) {
|
|
2014
|
-
|
|
2015
|
-
|
|
2069
|
+
const capacity = MESSAGE_REF_MAX_INDEX - MESSAGE_REF_MIN_INDEX + 1;
|
|
2070
|
+
const requested = Number.isInteger(state.messageIds.nextRef) ? state.messageIds.nextRef : MESSAGE_REF_MIN_INDEX;
|
|
2071
|
+
const start = requested >= MESSAGE_REF_MIN_INDEX && requested <= MESSAGE_REF_MAX_INDEX ? requested : MESSAGE_REF_MIN_INDEX;
|
|
2072
|
+
for (let offset = 0; offset < capacity; offset++) {
|
|
2073
|
+
const candidate = MESSAGE_REF_MIN_INDEX + (start - MESSAGE_REF_MIN_INDEX + offset) % capacity;
|
|
2016
2074
|
const ref = formatMessageRef(candidate);
|
|
2017
2075
|
if (!state.messageIds.byRef.has(ref)) {
|
|
2018
|
-
state.messageIds.nextRef = candidate + 1;
|
|
2076
|
+
state.messageIds.nextRef = candidate === MESSAGE_REF_MAX_INDEX ? MESSAGE_REF_MIN_INDEX : candidate + 1;
|
|
2019
2077
|
return ref;
|
|
2020
2078
|
}
|
|
2021
|
-
candidate++;
|
|
2022
2079
|
}
|
|
2023
2080
|
throw new Error(
|
|
2024
2081
|
`Message ID alias capacity exceeded. Cannot allocate more than ${formatMessageRef(MESSAGE_REF_MAX_INDEX)} aliases in this session.`
|
|
@@ -2694,6 +2751,7 @@ function resolveMessage(entry, searchContext, state, config) {
|
|
|
2694
2751
|
// lib/state/persistence.ts
|
|
2695
2752
|
import * as fs from "fs/promises";
|
|
2696
2753
|
import { existsSync as existsSync2 } from "fs";
|
|
2754
|
+
import { randomUUID } from "crypto";
|
|
2697
2755
|
import { homedir as homedir2 } from "os";
|
|
2698
2756
|
import { join as join2 } from "path";
|
|
2699
2757
|
|
|
@@ -2932,78 +2990,161 @@ function getActiveSummaryTokenUsage(state) {
|
|
|
2932
2990
|
}
|
|
2933
2991
|
return total;
|
|
2934
2992
|
}
|
|
2993
|
+
var MAX_SUBAGENT_CACHE_ENTRIES = 64;
|
|
2994
|
+
var MAX_SUBAGENT_CACHE_CHARS = 5e5;
|
|
2995
|
+
var MAX_SUBAGENT_CACHE_TOTAL_CHARS = 2e6;
|
|
2996
|
+
function getCachedSubAgentResult(state, callId) {
|
|
2997
|
+
const cached = state.subAgentResultCache.get(callId);
|
|
2998
|
+
if (cached === void 0) return void 0;
|
|
2999
|
+
state.subAgentResultCache.delete(callId);
|
|
3000
|
+
state.subAgentResultCache.set(callId, cached);
|
|
3001
|
+
return cached;
|
|
3002
|
+
}
|
|
3003
|
+
function cacheSubAgentResult(state, callId, result) {
|
|
3004
|
+
const cached = result.length <= MAX_SUBAGENT_CACHE_CHARS ? result : result.slice(0, MAX_SUBAGENT_CACHE_CHARS) + "\n[\u5B50\u4EE3\u7406\u7ED3\u679C\u56E0\u957F\u5EA6\u9650\u5236\u88AB\u622A\u65AD]";
|
|
3005
|
+
state.subAgentResultCache.delete(callId);
|
|
3006
|
+
state.subAgentResultCache.set(callId, cached);
|
|
3007
|
+
let totalChars = Array.from(state.subAgentResultCache.values()).reduce(
|
|
3008
|
+
(total, value) => total + value.length,
|
|
3009
|
+
0
|
|
3010
|
+
);
|
|
3011
|
+
while (state.subAgentResultCache.size > MAX_SUBAGENT_CACHE_ENTRIES || totalChars > MAX_SUBAGENT_CACHE_TOTAL_CHARS) {
|
|
3012
|
+
const oldest = state.subAgentResultCache.keys().next().value;
|
|
3013
|
+
if (typeof oldest !== "string") break;
|
|
3014
|
+
totalChars -= state.subAgentResultCache.get(oldest)?.length ?? 0;
|
|
3015
|
+
state.subAgentResultCache.delete(oldest);
|
|
3016
|
+
}
|
|
3017
|
+
return cached;
|
|
3018
|
+
}
|
|
2935
3019
|
function resetOnCompaction(state, messages = []) {
|
|
2936
3020
|
state.toolParameters.clear();
|
|
2937
3021
|
if (messages.length === 0) {
|
|
2938
3022
|
return;
|
|
2939
3023
|
}
|
|
2940
3024
|
const liveMessageIds = new Set(messages.map((msg) => msg.info.id));
|
|
3025
|
+
const liveToolIds = new Set(
|
|
3026
|
+
messages.flatMap(
|
|
3027
|
+
(message) => message.parts.flatMap(
|
|
3028
|
+
(part) => part.type === "tool" && part.callID ? [part.callID] : []
|
|
3029
|
+
)
|
|
3030
|
+
)
|
|
3031
|
+
);
|
|
2941
3032
|
for (const [ref, rawId] of state.messageIds.byRef) {
|
|
2942
3033
|
if (!liveMessageIds.has(rawId)) {
|
|
2943
3034
|
state.messageIds.byRef.delete(ref);
|
|
2944
3035
|
state.messageIds.byRawId.delete(rawId);
|
|
2945
3036
|
}
|
|
2946
3037
|
}
|
|
3038
|
+
for (const callId of state.subAgentResultCache.keys()) {
|
|
3039
|
+
if (!liveToolIds.has(callId)) state.subAgentResultCache.delete(callId);
|
|
3040
|
+
}
|
|
3041
|
+
const anchorSets = [
|
|
3042
|
+
state.nudges.contextLimitAnchors,
|
|
3043
|
+
state.nudges.turnNudgeAnchors,
|
|
3044
|
+
state.nudges.iterationNudgeAnchors
|
|
3045
|
+
];
|
|
3046
|
+
for (const anchorSet of anchorSets) {
|
|
3047
|
+
for (const anchorId of anchorSet) {
|
|
3048
|
+
if (!liveMessageIds.has(anchorId)) {
|
|
3049
|
+
anchorSet.delete(anchorId);
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
// lib/concurrency.ts
|
|
3056
|
+
async function runWithConcurrency(items, concurrency, task) {
|
|
3057
|
+
let nextIndex = 0;
|
|
3058
|
+
const workerCount = Math.min(items.length, Math.max(1, Math.floor(concurrency)));
|
|
3059
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
3060
|
+
for (; ; ) {
|
|
3061
|
+
const index = nextIndex++;
|
|
3062
|
+
if (index >= items.length) return;
|
|
3063
|
+
await task(items[index]);
|
|
3064
|
+
}
|
|
3065
|
+
});
|
|
3066
|
+
await Promise.all(workers);
|
|
2947
3067
|
}
|
|
2948
3068
|
|
|
2949
3069
|
// lib/state/persistence.ts
|
|
2950
|
-
var
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
3070
|
+
var saveQueues = /* @__PURE__ */ new Map();
|
|
3071
|
+
function getStorageDir() {
|
|
3072
|
+
return join2(
|
|
3073
|
+
process.env.XDG_DATA_HOME || join2(homedir2(), ".local", "share"),
|
|
3074
|
+
"opencode",
|
|
3075
|
+
"storage",
|
|
3076
|
+
"plugin",
|
|
3077
|
+
"dcp"
|
|
3078
|
+
);
|
|
3079
|
+
}
|
|
2957
3080
|
async function ensureStorageDir() {
|
|
2958
|
-
|
|
2959
|
-
|
|
3081
|
+
const storageDir = getStorageDir();
|
|
3082
|
+
if (!existsSync2(storageDir)) {
|
|
3083
|
+
await fs.mkdir(storageDir, { recursive: true });
|
|
2960
3084
|
}
|
|
2961
3085
|
}
|
|
2962
3086
|
function getSessionFilePath(sessionId) {
|
|
2963
|
-
return join2(
|
|
3087
|
+
return join2(getStorageDir(), `${sessionId}.json`);
|
|
2964
3088
|
}
|
|
2965
3089
|
async function writePersistedSessionState(sessionId, state, logger) {
|
|
2966
3090
|
await ensureStorageDir();
|
|
2967
3091
|
const filePath = getSessionFilePath(sessionId);
|
|
3092
|
+
const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
2968
3093
|
const content = JSON.stringify(state, null, 2);
|
|
2969
|
-
|
|
3094
|
+
try {
|
|
3095
|
+
await fs.writeFile(temporaryPath, content, "utf-8");
|
|
3096
|
+
await fs.rename(temporaryPath, filePath);
|
|
3097
|
+
} catch (error) {
|
|
3098
|
+
await fs.unlink(temporaryPath).catch(() => void 0);
|
|
3099
|
+
throw error;
|
|
3100
|
+
}
|
|
2970
3101
|
logger.info("Saved session state to disk", {
|
|
2971
3102
|
sessionId,
|
|
2972
3103
|
totalTokensSaved: state.stats.totalPruneTokens
|
|
2973
3104
|
});
|
|
2974
3105
|
}
|
|
2975
3106
|
async function saveSessionState(sessionState, logger, sessionName) {
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
3107
|
+
if (!sessionState.sessionId) {
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
const sessionId = sessionState.sessionId;
|
|
3111
|
+
const state = {
|
|
3112
|
+
sessionName,
|
|
3113
|
+
manualMode: !!sessionState.manualMode,
|
|
3114
|
+
prune: {
|
|
3115
|
+
tools: Object.fromEntries(sessionState.prune.tools),
|
|
3116
|
+
messages: serializePruneMessagesState(sessionState.prune.messages)
|
|
3117
|
+
},
|
|
3118
|
+
nudges: {
|
|
3119
|
+
contextLimitAnchors: Array.from(sessionState.nudges.contextLimitAnchors),
|
|
3120
|
+
turnNudgeAnchors: Array.from(sessionState.nudges.turnNudgeAnchors),
|
|
3121
|
+
iterationNudgeAnchors: Array.from(sessionState.nudges.iterationNudgeAnchors),
|
|
3122
|
+
boundaryPending: sessionState.nudges.boundaryPending === true
|
|
3123
|
+
},
|
|
3124
|
+
stats: { ...sessionState.stats },
|
|
3125
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3126
|
+
lastCompaction: sessionState.lastCompaction,
|
|
3127
|
+
messageIds: {
|
|
3128
|
+
byRawId: Object.fromEntries(sessionState.messageIds.byRawId),
|
|
3129
|
+
byRef: Object.fromEntries(sessionState.messageIds.byRef),
|
|
3130
|
+
nextRef: sessionState.messageIds.nextRef
|
|
2979
3131
|
}
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
},
|
|
2987
|
-
nudges: {
|
|
2988
|
-
contextLimitAnchors: Array.from(sessionState.nudges.contextLimitAnchors),
|
|
2989
|
-
turnNudgeAnchors: Array.from(sessionState.nudges.turnNudgeAnchors),
|
|
2990
|
-
iterationNudgeAnchors: Array.from(sessionState.nudges.iterationNudgeAnchors)
|
|
2991
|
-
},
|
|
2992
|
-
stats: sessionState.stats,
|
|
2993
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2994
|
-
lastCompaction: sessionState.lastCompaction,
|
|
2995
|
-
messageIds: {
|
|
2996
|
-
byRawId: Object.fromEntries(sessionState.messageIds.byRawId),
|
|
2997
|
-
byRef: Object.fromEntries(sessionState.messageIds.byRef),
|
|
2998
|
-
nextRef: sessionState.messageIds.nextRef
|
|
2999
|
-
}
|
|
3000
|
-
};
|
|
3001
|
-
await writePersistedSessionState(sessionState.sessionId, state, logger);
|
|
3132
|
+
};
|
|
3133
|
+
const previous = saveQueues.get(sessionId) ?? Promise.resolve();
|
|
3134
|
+
const current = previous.catch(() => void 0).then(() => writePersistedSessionState(sessionId, state, logger));
|
|
3135
|
+
saveQueues.set(sessionId, current);
|
|
3136
|
+
try {
|
|
3137
|
+
await current;
|
|
3002
3138
|
} catch (error) {
|
|
3003
3139
|
logger.error("Failed to save session state", {
|
|
3004
|
-
sessionId
|
|
3140
|
+
sessionId,
|
|
3005
3141
|
error: error?.message
|
|
3006
3142
|
});
|
|
3143
|
+
throw error;
|
|
3144
|
+
} finally {
|
|
3145
|
+
if (saveQueues.get(sessionId) === current) {
|
|
3146
|
+
saveQueues.delete(sessionId);
|
|
3147
|
+
}
|
|
3007
3148
|
}
|
|
3008
3149
|
}
|
|
3009
3150
|
async function loadSessionState(sessionId, logger) {
|
|
@@ -3062,6 +3203,7 @@ async function loadSessionState(sessionId, logger) {
|
|
|
3062
3203
|
});
|
|
3063
3204
|
}
|
|
3064
3205
|
state.nudges.iterationNudgeAnchors = dedupedIterationAnchors;
|
|
3206
|
+
state.nudges.boundaryPending = state.nudges.boundaryPending === true;
|
|
3065
3207
|
logger.info("Loaded session state from disk", {
|
|
3066
3208
|
sessionId
|
|
3067
3209
|
});
|
|
@@ -3082,14 +3224,15 @@ async function loadAllSessionStats(logger) {
|
|
|
3082
3224
|
sessionCount: 0
|
|
3083
3225
|
};
|
|
3084
3226
|
try {
|
|
3085
|
-
|
|
3227
|
+
const storageDir = getStorageDir();
|
|
3228
|
+
if (!existsSync2(storageDir)) {
|
|
3086
3229
|
return result;
|
|
3087
3230
|
}
|
|
3088
|
-
const files = await fs.readdir(
|
|
3231
|
+
const files = await fs.readdir(storageDir);
|
|
3089
3232
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
3090
|
-
|
|
3233
|
+
await runWithConcurrency(jsonFiles, 16, async (file) => {
|
|
3091
3234
|
try {
|
|
3092
|
-
const filePath = join2(
|
|
3235
|
+
const filePath = join2(storageDir, file);
|
|
3093
3236
|
const content = await fs.readFile(filePath, "utf-8");
|
|
3094
3237
|
const state = JSON.parse(content);
|
|
3095
3238
|
if (state?.stats?.totalPruneTokens && state?.prune) {
|
|
@@ -3100,7 +3243,7 @@ async function loadAllSessionStats(logger) {
|
|
|
3100
3243
|
}
|
|
3101
3244
|
} catch {
|
|
3102
3245
|
}
|
|
3103
|
-
}
|
|
3246
|
+
});
|
|
3104
3247
|
logger.debug("Loaded all-time stats", result);
|
|
3105
3248
|
} catch (error) {
|
|
3106
3249
|
logger.warn("Failed to load all-time stats", { error: error?.message });
|
|
@@ -3109,6 +3252,8 @@ async function loadAllSessionStats(logger) {
|
|
|
3109
3252
|
}
|
|
3110
3253
|
|
|
3111
3254
|
// lib/compress/timing.ts
|
|
3255
|
+
var MAX_TIMING_ENTRIES = 2048;
|
|
3256
|
+
var TIMING_TTL_MS = 30 * 60 * 1e3;
|
|
3112
3257
|
function buildCompressionTimingKey(messageId, callId) {
|
|
3113
3258
|
return `${messageId}:${callId}`;
|
|
3114
3259
|
}
|
|
@@ -3116,8 +3261,26 @@ function consumeCompressionStart(state, messageId, callId) {
|
|
|
3116
3261
|
const key = buildCompressionTimingKey(messageId, callId);
|
|
3117
3262
|
const start = state.compressionTiming.startsByCallId.get(key);
|
|
3118
3263
|
state.compressionTiming.startsByCallId.delete(key);
|
|
3264
|
+
state.compressionTiming.recordedAtByCallId.delete(key);
|
|
3119
3265
|
return start;
|
|
3120
3266
|
}
|
|
3267
|
+
function pruneCompressionTiming(state, now = Date.now()) {
|
|
3268
|
+
const timing = state.compressionTiming;
|
|
3269
|
+
const cutoff = now - TIMING_TTL_MS;
|
|
3270
|
+
for (const [key, recordedAt] of timing.recordedAtByCallId) {
|
|
3271
|
+
if (recordedAt >= cutoff) continue;
|
|
3272
|
+
timing.recordedAtByCallId.delete(key);
|
|
3273
|
+
timing.startsByCallId.delete(key);
|
|
3274
|
+
timing.pendingByCallId.delete(key);
|
|
3275
|
+
}
|
|
3276
|
+
while (timing.recordedAtByCallId.size > MAX_TIMING_ENTRIES) {
|
|
3277
|
+
const oldest = timing.recordedAtByCallId.keys().next().value;
|
|
3278
|
+
if (typeof oldest !== "string") break;
|
|
3279
|
+
timing.recordedAtByCallId.delete(oldest);
|
|
3280
|
+
timing.startsByCallId.delete(oldest);
|
|
3281
|
+
timing.pendingByCallId.delete(oldest);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3121
3284
|
function resolveCompressionDuration(startedAt, eventTime, partTime) {
|
|
3122
3285
|
const runningAt = typeof partTime?.start === "number" && Number.isFinite(partTime.start) ? partTime.start : eventTime;
|
|
3123
3286
|
const pendingToRunningMs = typeof startedAt === "number" && typeof runningAt === "number" ? Math.max(0, runningAt - startedAt) : void 0;
|
|
@@ -3141,24 +3304,170 @@ function applyPendingCompressionDurations(state) {
|
|
|
3141
3304
|
if (applied > 0) {
|
|
3142
3305
|
updates += applied;
|
|
3143
3306
|
state.compressionTiming.pendingByCallId.delete(key);
|
|
3307
|
+
state.compressionTiming.recordedAtByCallId.delete(key);
|
|
3144
3308
|
}
|
|
3145
3309
|
}
|
|
3146
3310
|
return updates;
|
|
3147
3311
|
}
|
|
3148
3312
|
|
|
3313
|
+
// lib/state/store.ts
|
|
3314
|
+
var MAX_RESIDENT_SESSIONS = 64;
|
|
3315
|
+
var MAX_MESSAGE_OWNERS = 2e4;
|
|
3316
|
+
var MAX_TIMING_ENTRIES2 = 2048;
|
|
3317
|
+
var TIMING_TTL_MS2 = 30 * 60 * 1e3;
|
|
3318
|
+
var SessionStateStore = class {
|
|
3319
|
+
constructor(createState) {
|
|
3320
|
+
this.createState = createState;
|
|
3321
|
+
}
|
|
3322
|
+
states = /* @__PURE__ */ new Map();
|
|
3323
|
+
initializationQueues = /* @__PURE__ */ new Map();
|
|
3324
|
+
messageOwners = /* @__PURE__ */ new Map();
|
|
3325
|
+
orphanStarts = /* @__PURE__ */ new Map();
|
|
3326
|
+
orphanPending = /* @__PURE__ */ new Map();
|
|
3327
|
+
get(sessionId) {
|
|
3328
|
+
const existing = this.states.get(sessionId);
|
|
3329
|
+
if (existing) {
|
|
3330
|
+
this.touch(sessionId, existing);
|
|
3331
|
+
return existing;
|
|
3332
|
+
}
|
|
3333
|
+
const state = this.createState();
|
|
3334
|
+
this.states.set(sessionId, state);
|
|
3335
|
+
this.trimSessions();
|
|
3336
|
+
return state;
|
|
3337
|
+
}
|
|
3338
|
+
peek(sessionId) {
|
|
3339
|
+
return this.states.get(sessionId);
|
|
3340
|
+
}
|
|
3341
|
+
forEach(callback) {
|
|
3342
|
+
for (const [sessionId, state] of this.states) {
|
|
3343
|
+
callback(state, sessionId);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
async initialize(sessionId, initializer) {
|
|
3347
|
+
const state = this.get(sessionId);
|
|
3348
|
+
const previous = this.initializationQueues.get(sessionId) ?? Promise.resolve();
|
|
3349
|
+
const current = previous.catch(() => void 0).then(() => initializer(state));
|
|
3350
|
+
this.initializationQueues.set(sessionId, current);
|
|
3351
|
+
try {
|
|
3352
|
+
await current;
|
|
3353
|
+
} finally {
|
|
3354
|
+
if (this.initializationQueues.get(sessionId) === current) {
|
|
3355
|
+
this.initializationQueues.delete(sessionId);
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
return state;
|
|
3359
|
+
}
|
|
3360
|
+
registerMessages(sessionId, messages) {
|
|
3361
|
+
const state = this.get(sessionId);
|
|
3362
|
+
for (const message of messages) {
|
|
3363
|
+
const messageId = message.info?.id;
|
|
3364
|
+
if (typeof messageId !== "string") continue;
|
|
3365
|
+
this.messageOwners.delete(messageId);
|
|
3366
|
+
this.messageOwners.set(messageId, sessionId);
|
|
3367
|
+
this.moveOrphanTiming(messageId, state.compressionTiming);
|
|
3368
|
+
}
|
|
3369
|
+
while (this.messageOwners.size > MAX_MESSAGE_OWNERS) {
|
|
3370
|
+
const oldest = this.messageOwners.keys().next().value;
|
|
3371
|
+
if (typeof oldest !== "string") break;
|
|
3372
|
+
this.messageOwners.delete(oldest);
|
|
3373
|
+
}
|
|
3374
|
+
return state;
|
|
3375
|
+
}
|
|
3376
|
+
resolveEventState(sessionId, messageId) {
|
|
3377
|
+
const resolvedSessionId = sessionId ?? this.messageOwners.get(messageId);
|
|
3378
|
+
if (!resolvedSessionId) return void 0;
|
|
3379
|
+
this.messageOwners.set(messageId, resolvedSessionId);
|
|
3380
|
+
return this.get(resolvedSessionId);
|
|
3381
|
+
}
|
|
3382
|
+
recordOrphanStart(key, startedAt) {
|
|
3383
|
+
this.pruneOrphanTiming();
|
|
3384
|
+
if (this.orphanStarts.has(key)) return;
|
|
3385
|
+
this.orphanStarts.set(key, { startedAt, recordedAt: Date.now() });
|
|
3386
|
+
this.trimMap(this.orphanStarts);
|
|
3387
|
+
}
|
|
3388
|
+
consumeOrphanStart(key) {
|
|
3389
|
+
const entry = this.orphanStarts.get(key);
|
|
3390
|
+
this.orphanStarts.delete(key);
|
|
3391
|
+
return entry?.startedAt;
|
|
3392
|
+
}
|
|
3393
|
+
recordOrphanPending(key, entry) {
|
|
3394
|
+
this.pruneOrphanTiming();
|
|
3395
|
+
this.orphanPending.set(key, { ...entry, recordedAt: Date.now() });
|
|
3396
|
+
this.trimMap(this.orphanPending);
|
|
3397
|
+
}
|
|
3398
|
+
deleteOrphanStart(key) {
|
|
3399
|
+
this.orphanStarts.delete(key);
|
|
3400
|
+
}
|
|
3401
|
+
moveOrphanTiming(messageId, timing) {
|
|
3402
|
+
const prefix = `${messageId}:`;
|
|
3403
|
+
for (const [key, entry] of this.orphanStarts) {
|
|
3404
|
+
if (!key.startsWith(prefix)) continue;
|
|
3405
|
+
if (!timing.startsByCallId.has(key)) {
|
|
3406
|
+
timing.startsByCallId.set(key, entry.startedAt);
|
|
3407
|
+
}
|
|
3408
|
+
timing.recordedAtByCallId.set(key, entry.recordedAt);
|
|
3409
|
+
this.orphanStarts.delete(key);
|
|
3410
|
+
}
|
|
3411
|
+
for (const [key, entry] of this.orphanPending) {
|
|
3412
|
+
if (!key.startsWith(prefix)) continue;
|
|
3413
|
+
timing.pendingByCallId.set(key, {
|
|
3414
|
+
messageId: entry.messageId,
|
|
3415
|
+
callId: entry.callId,
|
|
3416
|
+
durationMs: entry.durationMs
|
|
3417
|
+
});
|
|
3418
|
+
timing.recordedAtByCallId.set(key, entry.recordedAt);
|
|
3419
|
+
this.orphanPending.delete(key);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
touch(sessionId, state) {
|
|
3423
|
+
this.states.delete(sessionId);
|
|
3424
|
+
this.states.set(sessionId, state);
|
|
3425
|
+
}
|
|
3426
|
+
trimSessions() {
|
|
3427
|
+
while (this.states.size > MAX_RESIDENT_SESSIONS) {
|
|
3428
|
+
const oldest = Array.from(this.states.keys()).find(
|
|
3429
|
+
(sessionId) => !this.initializationQueues.has(sessionId)
|
|
3430
|
+
);
|
|
3431
|
+
if (!oldest) return;
|
|
3432
|
+
this.states.delete(oldest);
|
|
3433
|
+
for (const [messageId, owner] of this.messageOwners) {
|
|
3434
|
+
if (owner === oldest) this.messageOwners.delete(messageId);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
pruneOrphanTiming() {
|
|
3439
|
+
const cutoff = Date.now() - TIMING_TTL_MS2;
|
|
3440
|
+
for (const [key, entry] of this.orphanStarts) {
|
|
3441
|
+
if (entry.recordedAt < cutoff) this.orphanStarts.delete(key);
|
|
3442
|
+
}
|
|
3443
|
+
for (const [key, entry] of this.orphanPending) {
|
|
3444
|
+
if (entry.recordedAt < cutoff) this.orphanPending.delete(key);
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
trimMap(map) {
|
|
3448
|
+
while (map.size > MAX_TIMING_ENTRIES2) {
|
|
3449
|
+
const oldest = map.keys().next().value;
|
|
3450
|
+
if (typeof oldest !== "string") return;
|
|
3451
|
+
map.delete(oldest);
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
};
|
|
3455
|
+
function resolveSessionState(target, sessionId) {
|
|
3456
|
+
return target instanceof SessionStateStore ? target.get(sessionId) : target;
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3149
3459
|
// lib/state/state.ts
|
|
3150
|
-
var checkSession = async (client,
|
|
3460
|
+
var checkSession = async (client, target, logger, messages, manualModeDefault) => {
|
|
3151
3461
|
const lastUserMessage = getLastUserMessage(messages);
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
const lastSessionId = lastUserMessage.info.sessionID;
|
|
3462
|
+
const lastSessionId = lastUserMessage?.info.sessionID ?? messages.findLast((message) => typeof message.info?.sessionID === "string")?.info.sessionID;
|
|
3463
|
+
if (!lastSessionId) return target instanceof SessionStateStore ? void 0 : target;
|
|
3464
|
+
const state = target instanceof SessionStateStore ? target.registerMessages(lastSessionId, messages) : resolveSessionState(target, lastSessionId);
|
|
3156
3465
|
if (state.sessionId === null || state.sessionId !== lastSessionId) {
|
|
3157
3466
|
logger.info(`Session changed: ${state.sessionId} -> ${lastSessionId}`);
|
|
3158
3467
|
try {
|
|
3159
|
-
await
|
|
3468
|
+
await initializeSessionState(
|
|
3160
3469
|
client,
|
|
3161
|
-
|
|
3470
|
+
target,
|
|
3162
3471
|
lastSessionId,
|
|
3163
3472
|
logger,
|
|
3164
3473
|
messages,
|
|
@@ -3182,6 +3491,7 @@ var checkSession = async (client, state, logger, messages, manualModeDefault) =>
|
|
|
3182
3491
|
});
|
|
3183
3492
|
}
|
|
3184
3493
|
state.currentTurn = countTurns(state, messages);
|
|
3494
|
+
return state;
|
|
3185
3495
|
};
|
|
3186
3496
|
function createSessionState() {
|
|
3187
3497
|
return {
|
|
@@ -3197,7 +3507,8 @@ function createSessionState() {
|
|
|
3197
3507
|
nudges: {
|
|
3198
3508
|
contextLimitAnchors: /* @__PURE__ */ new Set(),
|
|
3199
3509
|
turnNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3200
|
-
iterationNudgeAnchors: /* @__PURE__ */ new Set()
|
|
3510
|
+
iterationNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3511
|
+
boundaryPending: false
|
|
3201
3512
|
},
|
|
3202
3513
|
stats: {
|
|
3203
3514
|
pruneTokenCounter: 0,
|
|
@@ -3205,7 +3516,8 @@ function createSessionState() {
|
|
|
3205
3516
|
},
|
|
3206
3517
|
compressionTiming: {
|
|
3207
3518
|
startsByCallId: /* @__PURE__ */ new Map(),
|
|
3208
|
-
pendingByCallId: /* @__PURE__ */ new Map()
|
|
3519
|
+
pendingByCallId: /* @__PURE__ */ new Map(),
|
|
3520
|
+
recordedAtByCallId: /* @__PURE__ */ new Map()
|
|
3209
3521
|
},
|
|
3210
3522
|
toolParameters: /* @__PURE__ */ new Map(),
|
|
3211
3523
|
subAgentResultCache: /* @__PURE__ */ new Map(),
|
|
@@ -3222,6 +3534,7 @@ function createSessionState() {
|
|
|
3222
3534
|
};
|
|
3223
3535
|
}
|
|
3224
3536
|
function resetSessionState(state) {
|
|
3537
|
+
const modelContextLimit = state.modelContextLimit;
|
|
3225
3538
|
state.sessionId = null;
|
|
3226
3539
|
state.isSubAgent = false;
|
|
3227
3540
|
state.manualMode = false;
|
|
@@ -3234,7 +3547,8 @@ function resetSessionState(state) {
|
|
|
3234
3547
|
state.nudges = {
|
|
3235
3548
|
contextLimitAnchors: /* @__PURE__ */ new Set(),
|
|
3236
3549
|
turnNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3237
|
-
iterationNudgeAnchors: /* @__PURE__ */ new Set()
|
|
3550
|
+
iterationNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3551
|
+
boundaryPending: false
|
|
3238
3552
|
};
|
|
3239
3553
|
state.stats = {
|
|
3240
3554
|
pruneTokenCounter: 0,
|
|
@@ -3250,9 +3564,20 @@ function resetSessionState(state) {
|
|
|
3250
3564
|
};
|
|
3251
3565
|
state.lastCompaction = 0;
|
|
3252
3566
|
state.currentTurn = 0;
|
|
3253
|
-
state.modelContextLimit =
|
|
3567
|
+
state.modelContextLimit = modelContextLimit;
|
|
3254
3568
|
state.systemPromptTokens = void 0;
|
|
3255
3569
|
}
|
|
3570
|
+
async function initializeSessionState(client, target, sessionId, logger, messages, manualModeEnabled) {
|
|
3571
|
+
if (target instanceof SessionStateStore) {
|
|
3572
|
+
target.registerMessages(sessionId, messages);
|
|
3573
|
+
return target.initialize(
|
|
3574
|
+
sessionId,
|
|
3575
|
+
(state) => ensureSessionInitialized(client, state, sessionId, logger, messages, manualModeEnabled)
|
|
3576
|
+
);
|
|
3577
|
+
}
|
|
3578
|
+
await ensureSessionInitialized(client, target, sessionId, logger, messages, manualModeEnabled);
|
|
3579
|
+
return target;
|
|
3580
|
+
}
|
|
3256
3581
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, manualModeEnabled) {
|
|
3257
3582
|
if (state.sessionId === sessionId) {
|
|
3258
3583
|
return;
|
|
@@ -3282,6 +3607,7 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
3282
3607
|
state.nudges.iterationNudgeAnchors = new Set(
|
|
3283
3608
|
persisted.nudges.iterationNudgeAnchors || []
|
|
3284
3609
|
);
|
|
3610
|
+
state.nudges.boundaryPending = persisted.nudges.boundaryPending === true;
|
|
3285
3611
|
state.stats = {
|
|
3286
3612
|
pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0,
|
|
3287
3613
|
totalPruneTokens: persisted.stats?.totalPruneTokens || 0
|
|
@@ -3316,6 +3642,7 @@ function syncToolCache(state, config, logger, messages) {
|
|
|
3316
3642
|
try {
|
|
3317
3643
|
logger.info("Syncing tool parameters from OpenCode messages");
|
|
3318
3644
|
let turnCounter = 0;
|
|
3645
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
3319
3646
|
for (const msg of messages) {
|
|
3320
3647
|
if (isMessageCompacted(state, msg)) {
|
|
3321
3648
|
continue;
|
|
@@ -3329,26 +3656,35 @@ function syncToolCache(state, config, logger, messages) {
|
|
|
3329
3656
|
if (part.type !== "tool" || !part.callID) {
|
|
3330
3657
|
continue;
|
|
3331
3658
|
}
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3659
|
+
candidates.delete(part.callID);
|
|
3660
|
+
candidates.set(part.callID, { part, turn: turnCounter });
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
const recentCandidates = Array.from(candidates.entries()).filter(([, candidate]) => {
|
|
3664
|
+
const turnProtectionEnabled = config.turnProtection.enabled;
|
|
3665
|
+
const turnProtectionTurns = config.turnProtection.turns;
|
|
3666
|
+
return !(turnProtectionEnabled && turnProtectionTurns > 0 && state.currentTurn - candidate.turn < turnProtectionTurns);
|
|
3667
|
+
}).slice(-MAX_TOOL_CACHE_SIZE);
|
|
3668
|
+
const retainedIds = new Set(recentCandidates.map(([callId]) => callId));
|
|
3669
|
+
for (const callId of state.toolParameters.keys()) {
|
|
3670
|
+
if (!retainedIds.has(callId)) state.toolParameters.delete(callId);
|
|
3671
|
+
}
|
|
3672
|
+
for (const [callId, { part, turn }] of recentCandidates) {
|
|
3673
|
+
const status = part.state.status;
|
|
3674
|
+
const cached = state.toolParameters.get(callId);
|
|
3675
|
+
if (cached?.status === status) continue;
|
|
3676
|
+
const tokenCount = countToolTokens(part);
|
|
3677
|
+
state.toolParameters.set(callId, {
|
|
3678
|
+
tool: part.tool,
|
|
3679
|
+
parameters: part.state?.input ?? {},
|
|
3680
|
+
status,
|
|
3681
|
+
error: part.state.status === "error" ? part.state.error : void 0,
|
|
3682
|
+
turn,
|
|
3683
|
+
tokenCount
|
|
3684
|
+
});
|
|
3685
|
+
if (!cached) {
|
|
3350
3686
|
logger.info(
|
|
3351
|
-
`Cached tool id: ${
|
|
3687
|
+
`Cached tool id: ${callId} (turn ${turn}${tokenCount !== void 0 ? `, ${tokenCount} tokens` : ""})`
|
|
3352
3688
|
);
|
|
3353
3689
|
}
|
|
3354
3690
|
}
|
|
@@ -3494,6 +3830,9 @@ var deduplicate = (state, logger, config, messages) => {
|
|
|
3494
3830
|
if (!metadata) {
|
|
3495
3831
|
continue;
|
|
3496
3832
|
}
|
|
3833
|
+
if (metadata.status !== void 0 && metadata.status !== "completed" && metadata.status !== "success") {
|
|
3834
|
+
continue;
|
|
3835
|
+
}
|
|
3497
3836
|
if (isToolNameProtected(metadata.tool, protectedTools)) {
|
|
3498
3837
|
continue;
|
|
3499
3838
|
}
|
|
@@ -3991,7 +4330,6 @@ ${progressBar}`;
|
|
|
3991
4330
|
}
|
|
3992
4331
|
async function sendIgnoredMessage(client, sessionID, text, params, logger) {
|
|
3993
4332
|
const agent = params.agent || void 0;
|
|
3994
|
-
const variant = params.variant || void 0;
|
|
3995
4333
|
const model = params.providerId && params.modelId ? {
|
|
3996
4334
|
providerID: params.providerId,
|
|
3997
4335
|
modelID: params.modelId
|
|
@@ -4005,7 +4343,6 @@ async function sendIgnoredMessage(client, sessionID, text, params, logger) {
|
|
|
4005
4343
|
noReply: true,
|
|
4006
4344
|
agent,
|
|
4007
4345
|
model,
|
|
4008
|
-
variant,
|
|
4009
4346
|
parts: [
|
|
4010
4347
|
{
|
|
4011
4348
|
type: "text",
|
|
@@ -4022,7 +4359,18 @@ async function sendIgnoredMessage(client, sessionID, text, params, logger) {
|
|
|
4022
4359
|
|
|
4023
4360
|
// lib/compress/pipeline.ts
|
|
4024
4361
|
async function prepareSession(ctx, toolCtx, title) {
|
|
4025
|
-
|
|
4362
|
+
const state = resolveSessionState(ctx.state, toolCtx.sessionID);
|
|
4363
|
+
toolCtx.metadata({ title });
|
|
4364
|
+
const rawMessages = await fetchSessionMessages(ctx.client, toolCtx.sessionID);
|
|
4365
|
+
await initializeSessionState(
|
|
4366
|
+
ctx.client,
|
|
4367
|
+
ctx.state,
|
|
4368
|
+
toolCtx.sessionID,
|
|
4369
|
+
ctx.logger,
|
|
4370
|
+
rawMessages,
|
|
4371
|
+
ctx.config.manualMode.enabled
|
|
4372
|
+
);
|
|
4373
|
+
if (state.manualMode && state.manualMode !== "compress-pending") {
|
|
4026
4374
|
throw new Error(
|
|
4027
4375
|
"Manual mode: compress blocked. Do not retry until `<compress triggered manually>` appears in user context."
|
|
4028
4376
|
);
|
|
@@ -4033,35 +4381,26 @@ async function prepareSession(ctx, toolCtx, title) {
|
|
|
4033
4381
|
always: ["*"],
|
|
4034
4382
|
metadata: {}
|
|
4035
4383
|
});
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
ctx.client,
|
|
4040
|
-
ctx.state,
|
|
4041
|
-
toolCtx.sessionID,
|
|
4042
|
-
ctx.logger,
|
|
4043
|
-
rawMessages,
|
|
4044
|
-
ctx.config.manualMode.enabled
|
|
4045
|
-
);
|
|
4046
|
-
assignMessageRefs(ctx.state, rawMessages);
|
|
4047
|
-
deduplicate(ctx.state, ctx.logger, ctx.config, rawMessages);
|
|
4048
|
-
purgeErrors(ctx.state, ctx.logger, ctx.config, rawMessages);
|
|
4384
|
+
assignMessageRefs(state, rawMessages);
|
|
4385
|
+
deduplicate(state, ctx.logger, ctx.config, rawMessages);
|
|
4386
|
+
purgeErrors(state, ctx.logger, ctx.config, rawMessages);
|
|
4049
4387
|
return {
|
|
4388
|
+
state,
|
|
4050
4389
|
rawMessages,
|
|
4051
|
-
searchContext: buildSearchContext(
|
|
4390
|
+
searchContext: buildSearchContext(state, rawMessages)
|
|
4052
4391
|
};
|
|
4053
4392
|
}
|
|
4054
|
-
async function finalizeSession(ctx, toolCtx, rawMessages, entries, batchTopic) {
|
|
4055
|
-
|
|
4056
|
-
applyPendingCompressionDurations(
|
|
4057
|
-
await saveSessionState(
|
|
4058
|
-
const params = getCurrentParams(
|
|
4393
|
+
async function finalizeSession(ctx, state, toolCtx, rawMessages, entries, batchTopic) {
|
|
4394
|
+
state.manualMode = state.manualMode ? "active" : false;
|
|
4395
|
+
applyPendingCompressionDurations(state);
|
|
4396
|
+
await saveSessionState(state, ctx.logger);
|
|
4397
|
+
const params = getCurrentParams(state, rawMessages, ctx.logger);
|
|
4059
4398
|
const sessionMessageIds = rawMessages.filter((msg) => !isIgnoredUserMessage(msg)).map((msg) => msg.info.id);
|
|
4060
4399
|
await sendCompressNotification(
|
|
4061
4400
|
ctx.client,
|
|
4062
4401
|
ctx.logger,
|
|
4063
4402
|
ctx.config,
|
|
4064
|
-
|
|
4403
|
+
state,
|
|
4065
4404
|
toolCtx.sessionID,
|
|
4066
4405
|
entries,
|
|
4067
4406
|
batchTopic,
|
|
@@ -4220,7 +4559,7 @@ async function appendProtectedTools(client, state, allowSubAgents, summary, sele
|
|
|
4220
4559
|
output = typeof part.state.output === "string" ? part.state.output : JSON.stringify(part.state.output);
|
|
4221
4560
|
}
|
|
4222
4561
|
if (allowSubAgents && part.tool === "task" && part.state?.status === "completed" && typeof part.state?.output === "string") {
|
|
4223
|
-
const cachedSubAgentResult = state
|
|
4562
|
+
const cachedSubAgentResult = getCachedSubAgentResult(state, part.callID);
|
|
4224
4563
|
if (cachedSubAgentResult !== void 0) {
|
|
4225
4564
|
if (cachedSubAgentResult) {
|
|
4226
4565
|
output = mergeSubagentResult(
|
|
@@ -4242,7 +4581,11 @@ async function appendProtectedTools(client, state, allowSubAgents, summary, sele
|
|
|
4242
4581
|
subAgentResultText = "";
|
|
4243
4582
|
}
|
|
4244
4583
|
if (subAgentResultText) {
|
|
4245
|
-
|
|
4584
|
+
subAgentResultText = cacheSubAgentResult(
|
|
4585
|
+
state,
|
|
4586
|
+
part.callID,
|
|
4587
|
+
subAgentResultText
|
|
4588
|
+
);
|
|
4246
4589
|
output = mergeSubagentResult(
|
|
4247
4590
|
part.state.output,
|
|
4248
4591
|
subAgentResultText
|
|
@@ -4350,6 +4693,21 @@ async function callExternalModel(endpoint, cfg, req) {
|
|
|
4350
4693
|
return content;
|
|
4351
4694
|
}
|
|
4352
4695
|
async function generateSummaryViaExternal(cfg, req) {
|
|
4696
|
+
if (typeof cfg.url !== "string" || cfg.url.trim().length === 0) {
|
|
4697
|
+
throw new Error("\u5916\u90E8\u538B\u7F29\u6A21\u578B url \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
4698
|
+
}
|
|
4699
|
+
if (typeof cfg.model !== "string" || cfg.model.trim().length === 0) {
|
|
4700
|
+
throw new Error("\u5916\u90E8\u538B\u7F29\u6A21\u578B model \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
4701
|
+
}
|
|
4702
|
+
if (cfg.apiKey !== void 0 && typeof cfg.apiKey !== "string") {
|
|
4703
|
+
throw new Error("\u5916\u90E8\u538B\u7F29\u6A21\u578B apiKey \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
4704
|
+
}
|
|
4705
|
+
if (cfg.timeout !== void 0 && (!Number.isFinite(cfg.timeout) || cfg.timeout <= 0)) {
|
|
4706
|
+
throw new Error("\u5916\u90E8\u538B\u7F29\u6A21\u578B timeout \u5FC5\u987B\u662F\u6B63\u6570");
|
|
4707
|
+
}
|
|
4708
|
+
if (cfg.retries !== void 0 && (!Number.isInteger(cfg.retries) || cfg.retries < 0)) {
|
|
4709
|
+
throw new Error("\u5916\u90E8\u538B\u7F29\u6A21\u578B retries \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570");
|
|
4710
|
+
}
|
|
4353
4711
|
const endpoint = resolveCompressBaseUrl(cfg.url);
|
|
4354
4712
|
const maxRetries = cfg.retries ?? 1;
|
|
4355
4713
|
let lastError;
|
|
@@ -4363,18 +4721,57 @@ async function generateSummaryViaExternal(cfg, req) {
|
|
|
4363
4721
|
throw lastError;
|
|
4364
4722
|
}
|
|
4365
4723
|
|
|
4366
|
-
// lib/compress/
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4724
|
+
// lib/compress/external-content.ts
|
|
4725
|
+
var MAX_PART_CHARS = 1e5;
|
|
4726
|
+
var MAX_TOTAL_CHARS = 1e6;
|
|
4727
|
+
var TRUNCATED_MARKER = "\n[\u5185\u5BB9\u56E0\u957F\u5EA6\u9650\u5236\u88AB\u622A\u65AD]";
|
|
4728
|
+
function stringify(value) {
|
|
4729
|
+
if (typeof value === "string") return value;
|
|
4730
|
+
try {
|
|
4731
|
+
return JSON.stringify(value, null, 2) ?? String(value);
|
|
4732
|
+
} catch {
|
|
4733
|
+
return String(value);
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
function truncate2(value, limit = MAX_PART_CHARS) {
|
|
4737
|
+
if (value.length <= limit) return value;
|
|
4738
|
+
return value.slice(0, limit) + TRUNCATED_MARKER;
|
|
4739
|
+
}
|
|
4740
|
+
function serializePart(part) {
|
|
4741
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
4742
|
+
return [truncate2(part.text)];
|
|
4743
|
+
}
|
|
4744
|
+
if (part.type !== "tool") return [];
|
|
4745
|
+
const chunks = [`[\u5DE5\u5177\uFF1A${part.tool}\uFF1B\u72B6\u6001\uFF1A${part.state.status}]`];
|
|
4746
|
+
if (part.state.input !== void 0) {
|
|
4747
|
+
chunks.push("\u8F93\u5165\uFF1A\n" + truncate2(stringify(part.state.input)));
|
|
4748
|
+
}
|
|
4749
|
+
if (part.state.status === "completed" && part.state.output !== void 0) {
|
|
4750
|
+
chunks.push("\u8F93\u51FA\uFF1A\n" + truncate2(stringify(part.state.output)));
|
|
4751
|
+
} else if (part.state.status === "error" && part.state.error !== void 0) {
|
|
4752
|
+
chunks.push("\u9519\u8BEF\uFF1A\n" + truncate2(stringify(part.state.error)));
|
|
4753
|
+
}
|
|
4754
|
+
return chunks;
|
|
4755
|
+
}
|
|
4756
|
+
function serializeMessagesForExternalSummary(messages) {
|
|
4757
|
+
const chunks = [];
|
|
4758
|
+
let length = 0;
|
|
4759
|
+
for (const message of messages) {
|
|
4760
|
+
const header = `[\u6D88\u606F\uFF1A${message.info.id}\uFF1B\u89D2\u8272\uFF1A${message.info.role}]`;
|
|
4761
|
+
const content = [header, ...message.parts.flatMap(serializePart)].join("\n");
|
|
4762
|
+
if (length + content.length > MAX_TOTAL_CHARS) {
|
|
4763
|
+
const remaining = Math.max(0, MAX_TOTAL_CHARS - length);
|
|
4764
|
+
if (remaining > 0) chunks.push(content.slice(0, remaining));
|
|
4765
|
+
chunks.push(TRUNCATED_MARKER.trim());
|
|
4766
|
+
break;
|
|
4374
4767
|
}
|
|
4768
|
+
chunks.push(content);
|
|
4769
|
+
length += content.length;
|
|
4375
4770
|
}
|
|
4376
|
-
return
|
|
4771
|
+
return chunks.join("\n\n");
|
|
4377
4772
|
}
|
|
4773
|
+
|
|
4774
|
+
// lib/compress/message.ts
|
|
4378
4775
|
function buildSchema(externalModelEnabled) {
|
|
4379
4776
|
const summaryField = externalModelEnabled ? tool.schema.string().optional().describe(
|
|
4380
4777
|
"Complete technical summary replacing that one message (optional when external model is configured)"
|
|
@@ -4403,7 +4800,7 @@ function createCompressMessageTool(ctx) {
|
|
|
4403
4800
|
const input = args;
|
|
4404
4801
|
validateArgs(input);
|
|
4405
4802
|
const callId = typeof toolCtx.callID === "string" ? toolCtx.callID : void 0;
|
|
4406
|
-
const { rawMessages, searchContext } = await prepareSession(
|
|
4803
|
+
const { state, rawMessages, searchContext } = await prepareSession(
|
|
4407
4804
|
ctx,
|
|
4408
4805
|
toolCtx,
|
|
4409
4806
|
`Compress Message: ${input.topic}`
|
|
@@ -4411,26 +4808,27 @@ function createCompressMessageTool(ctx) {
|
|
|
4411
4808
|
const { plans, skippedIssues, skippedCount } = resolveMessages(
|
|
4412
4809
|
input,
|
|
4413
4810
|
searchContext,
|
|
4414
|
-
|
|
4811
|
+
state,
|
|
4415
4812
|
ctx.config
|
|
4416
4813
|
);
|
|
4417
4814
|
if (plans.length === 0 && skippedCount > 0) {
|
|
4418
4815
|
throw new Error(formatIssues(skippedIssues, skippedCount));
|
|
4419
4816
|
}
|
|
4420
|
-
|
|
4421
|
-
|
|
4817
|
+
const externalModel = ctx.config.compress.externalModel;
|
|
4818
|
+
if (externalModel) {
|
|
4819
|
+
await runWithConcurrency(plans, 3, async (plan) => {
|
|
4422
4820
|
if (plan.entry.summary === void 0) {
|
|
4423
|
-
const
|
|
4424
|
-
const
|
|
4425
|
-
|
|
4426
|
-
{
|
|
4427
|
-
systemPrompt: runtimePrompts.compressMessage,
|
|
4428
|
-
userContent
|
|
4429
|
-
}
|
|
4821
|
+
const message = searchContext.rawMessagesById.get(plan.entry.messageId);
|
|
4822
|
+
const userContent = serializeMessagesForExternalSummary(
|
|
4823
|
+
message ? [message] : []
|
|
4430
4824
|
);
|
|
4825
|
+
const generated = await generateSummaryViaExternal(externalModel, {
|
|
4826
|
+
systemPrompt: runtimePrompts.compressMessage,
|
|
4827
|
+
userContent
|
|
4828
|
+
});
|
|
4431
4829
|
plan.entry.summary = generated;
|
|
4432
4830
|
}
|
|
4433
|
-
}
|
|
4831
|
+
});
|
|
4434
4832
|
}
|
|
4435
4833
|
const notifications = [];
|
|
4436
4834
|
const preparedPlans = [];
|
|
@@ -4444,12 +4842,12 @@ function createCompressMessageTool(ctx) {
|
|
|
4444
4842
|
plan.entry.summary,
|
|
4445
4843
|
plan.selection,
|
|
4446
4844
|
searchContext,
|
|
4447
|
-
|
|
4845
|
+
state,
|
|
4448
4846
|
ctx.config.compress.protectTags
|
|
4449
4847
|
);
|
|
4450
4848
|
const summaryWithTools = await appendProtectedTools(
|
|
4451
4849
|
ctx.client,
|
|
4452
|
-
|
|
4850
|
+
state,
|
|
4453
4851
|
ctx.config.experimental.allowSubAgents,
|
|
4454
4852
|
summaryWithPromptInfo,
|
|
4455
4853
|
plan.selection,
|
|
@@ -4462,13 +4860,13 @@ function createCompressMessageTool(ctx) {
|
|
|
4462
4860
|
summaryWithTools
|
|
4463
4861
|
});
|
|
4464
4862
|
}
|
|
4465
|
-
const runId = allocateRunId(
|
|
4863
|
+
const runId = allocateRunId(state);
|
|
4466
4864
|
for (const { plan, summaryWithTools } of preparedPlans) {
|
|
4467
|
-
const blockId = allocateBlockId(
|
|
4865
|
+
const blockId = allocateBlockId(state);
|
|
4468
4866
|
const storedSummary = wrapCompressedSummary(blockId, summaryWithTools);
|
|
4469
4867
|
const summaryTokens = countTokens2(storedSummary);
|
|
4470
4868
|
applyCompressionState(
|
|
4471
|
-
|
|
4869
|
+
state,
|
|
4472
4870
|
{
|
|
4473
4871
|
topic: plan.entry.topic,
|
|
4474
4872
|
batchTopic: input.topic,
|
|
@@ -4493,7 +4891,7 @@ function createCompressMessageTool(ctx) {
|
|
|
4493
4891
|
summaryTokens
|
|
4494
4892
|
});
|
|
4495
4893
|
}
|
|
4496
|
-
await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic);
|
|
4894
|
+
await finalizeSession(ctx, state, toolCtx, rawMessages, notifications, input.topic);
|
|
4497
4895
|
return formatResult(plans.length, skippedIssues, skippedCount);
|
|
4498
4896
|
}
|
|
4499
4897
|
});
|
|
@@ -4729,19 +5127,6 @@ ${right}`;
|
|
|
4729
5127
|
}
|
|
4730
5128
|
|
|
4731
5129
|
// lib/compress/range.ts
|
|
4732
|
-
function extractRangeText(selection, searchContext) {
|
|
4733
|
-
const parts = [];
|
|
4734
|
-
for (const messageId of selection.messageIds) {
|
|
4735
|
-
const msg = searchContext.rawMessagesById.get(messageId);
|
|
4736
|
-
if (!msg) continue;
|
|
4737
|
-
for (const part of msg.parts) {
|
|
4738
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
4739
|
-
parts.push(part.text);
|
|
4740
|
-
}
|
|
4741
|
-
}
|
|
4742
|
-
}
|
|
4743
|
-
return parts.join("\n\n");
|
|
4744
|
-
}
|
|
4745
5130
|
function buildSchema2(externalModelEnabled) {
|
|
4746
5131
|
const summaryField = externalModelEnabled ? tool2.schema.string().optional().describe(
|
|
4747
5132
|
"Complete technical summary replacing all content in range (optional when external model is configured)"
|
|
@@ -4774,27 +5159,29 @@ function createCompressRangeTool(ctx) {
|
|
|
4774
5159
|
const input = args;
|
|
4775
5160
|
validateArgs2(input);
|
|
4776
5161
|
const callId = typeof toolCtx.callID === "string" ? toolCtx.callID : void 0;
|
|
4777
|
-
const { rawMessages, searchContext } = await prepareSession(
|
|
5162
|
+
const { state, rawMessages, searchContext } = await prepareSession(
|
|
4778
5163
|
ctx,
|
|
4779
5164
|
toolCtx,
|
|
4780
5165
|
`Compress Range: ${input.topic}`
|
|
4781
5166
|
);
|
|
4782
|
-
const resolvedPlans = resolveRanges(input, searchContext,
|
|
5167
|
+
const resolvedPlans = resolveRanges(input, searchContext, state);
|
|
4783
5168
|
validateNonOverlapping(resolvedPlans);
|
|
4784
|
-
|
|
4785
|
-
|
|
5169
|
+
const externalModel = ctx.config.compress.externalModel;
|
|
5170
|
+
if (externalModel) {
|
|
5171
|
+
await runWithConcurrency(resolvedPlans, 3, async (plan) => {
|
|
4786
5172
|
if (plan.entry.summary === void 0) {
|
|
4787
|
-
const
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
5173
|
+
const selectedMessages = plan.selection.messageIds.flatMap((messageId) => {
|
|
5174
|
+
const message = searchContext.rawMessagesById.get(messageId);
|
|
5175
|
+
return message ? [message] : [];
|
|
5176
|
+
});
|
|
5177
|
+
const userContent = serializeMessagesForExternalSummary(selectedMessages);
|
|
5178
|
+
const generated = await generateSummaryViaExternal(externalModel, {
|
|
5179
|
+
systemPrompt: runtimePrompts.compressRange,
|
|
5180
|
+
userContent
|
|
5181
|
+
});
|
|
4795
5182
|
plan.entry.summary = generated;
|
|
4796
5183
|
}
|
|
4797
|
-
}
|
|
5184
|
+
});
|
|
4798
5185
|
}
|
|
4799
5186
|
const notifications = [];
|
|
4800
5187
|
const preparedPlans = [];
|
|
@@ -4824,19 +5211,19 @@ function createCompressRangeTool(ctx) {
|
|
|
4824
5211
|
injected.expandedSummary,
|
|
4825
5212
|
plan.selection,
|
|
4826
5213
|
searchContext,
|
|
4827
|
-
|
|
5214
|
+
state,
|
|
4828
5215
|
ctx.config.compress.protectUserMessages
|
|
4829
5216
|
);
|
|
4830
5217
|
const summaryWithPromptInfo = appendProtectedPromptInfo(
|
|
4831
5218
|
summaryWithUsers,
|
|
4832
5219
|
plan.selection,
|
|
4833
5220
|
searchContext,
|
|
4834
|
-
|
|
5221
|
+
state,
|
|
4835
5222
|
ctx.config.compress.protectTags
|
|
4836
5223
|
);
|
|
4837
5224
|
const summaryWithTools = await appendProtectedTools(
|
|
4838
5225
|
ctx.client,
|
|
4839
|
-
|
|
5226
|
+
state,
|
|
4840
5227
|
ctx.config.experimental.allowSubAgents,
|
|
4841
5228
|
summaryWithPromptInfo,
|
|
4842
5229
|
plan.selection,
|
|
@@ -4858,13 +5245,13 @@ function createCompressRangeTool(ctx) {
|
|
|
4858
5245
|
consumedBlockIds: completedSummary.consumedBlockIds
|
|
4859
5246
|
});
|
|
4860
5247
|
}
|
|
4861
|
-
const runId = allocateRunId(
|
|
5248
|
+
const runId = allocateRunId(state);
|
|
4862
5249
|
for (const preparedPlan of preparedPlans) {
|
|
4863
|
-
const blockId = allocateBlockId(
|
|
5250
|
+
const blockId = allocateBlockId(state);
|
|
4864
5251
|
const storedSummary = wrapCompressedSummary(blockId, preparedPlan.finalSummary);
|
|
4865
5252
|
const summaryTokens = countTokens2(storedSummary);
|
|
4866
5253
|
const applied = applyCompressionState(
|
|
4867
|
-
|
|
5254
|
+
state,
|
|
4868
5255
|
{
|
|
4869
5256
|
topic: input.topic,
|
|
4870
5257
|
batchTopic: input.topic,
|
|
@@ -4890,7 +5277,7 @@ function createCompressRangeTool(ctx) {
|
|
|
4890
5277
|
summaryTokens
|
|
4891
5278
|
});
|
|
4892
5279
|
}
|
|
4893
|
-
await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic);
|
|
5280
|
+
await finalizeSession(ctx, state, toolCtx, rawMessages, notifications, input.topic);
|
|
4894
5281
|
return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.`;
|
|
4895
5282
|
}
|
|
4896
5283
|
});
|
|
@@ -5156,51 +5543,51 @@ import { homedir as homedir4 } from "os";
|
|
|
5156
5543
|
|
|
5157
5544
|
// lib/prompts/system.ts
|
|
5158
5545
|
var SYSTEM = `
|
|
5159
|
-
|
|
5546
|
+
\u4F60\u5728\u4E0A\u4E0B\u6587\u957F\u5EA6\u53D7\u9650\u7684\u73AF\u5883\u4E2D\u8FD0\u884C\u3002\u4E3B\u52A8\u7BA1\u7406\u4E0A\u4E0B\u6587\uFF0C\u907F\u514D\u65E0\u6548\u7D2F\u79EF\u5E76\u4FDD\u6301\u8F83\u9AD8\u7684\u68C0\u7D22\u8D28\u91CF\u3002
|
|
5160
5547
|
|
|
5161
|
-
|
|
5548
|
+
\u552F\u4E00\u7684\u4E0A\u4E0B\u6587\u7BA1\u7406\u5DE5\u5177\u662F \`compress\`\u3002\u5B83\u4F1A\u7528\u4F60\u751F\u6210\u7684\u6280\u672F\u6458\u8981\u66FF\u6362\u8F83\u65E9\u7684\u5BF9\u8BDD\u5185\u5BB9\u3002
|
|
5162
5549
|
|
|
5163
|
-
|
|
5164
|
-
-
|
|
5165
|
-
-
|
|
5166
|
-
-
|
|
5167
|
-
-
|
|
5550
|
+
\u786C\u6027\u7EA6\u675F
|
|
5551
|
+
- \u53EA\u80FD\u901A\u8FC7\u8C03\u7528 \`compress\` \u7BA1\u7406\u4E0A\u4E0B\u6587\uFF0C\u7EDD\u4E0D\u80FD\u5728\u666E\u901A\u6587\u672C\u4E2D\u76F4\u63A5\u751F\u6210\u538B\u7F29\u6458\u8981\u3002
|
|
5552
|
+
- \u6587\u672C\u56DE\u590D\u4E2D\u7EDD\u4E0D\u80FD\u8F93\u51FA \`<summary>\` \u6216 \`<analysis>\` XML \u6807\u7B7E\uFF0C\u5B83\u4EEC\u4F1A\u5BFC\u81F4\u7CFB\u7EDF\u9519\u8BEF\u3002
|
|
5553
|
+
- \u9700\u8981\u538B\u7F29\u65F6\u8C03\u7528 \`compress\`\uFF0C\u4E0D\u8981\u5728\u56DE\u590D\u4E2D\u5185\u8054\u7F16\u5199\u6458\u8981\u3002
|
|
5554
|
+
- \u4F7F\u7528 Markdown \u6807\u9898\uFF08\u4F8B\u5982 \`## \u5206\u6790\`\u3001\`## \u6458\u8981\`\uFF09\u7EC4\u7EC7\u4FE1\u606F\uFF0C\u7EDD\u4E0D\u80FD\u4F7F\u7528 XML \u6807\u7B7E\u3002
|
|
5168
5555
|
|
|
5169
|
-
\`<dcp-message-id>\`
|
|
5556
|
+
\`<dcp-message-id>\` \u548C \`<dcp-system-reminder>\` \u662F\u73AF\u5883\u6CE8\u5165\u7684\u5143\u6570\u636E\uFF0C\u4E0D\u8981\u8F93\u51FA\u5B83\u4EEC\u3002
|
|
5170
5557
|
|
|
5171
|
-
|
|
5558
|
+
\u4F55\u65F6\u538B\u7F29
|
|
5172
5559
|
|
|
5173
|
-
|
|
5560
|
+
\u6700\u9AD8\u4F18\u5148\u7EA7\u7684\u5F3A\u5236\u89E6\u53D1\u6761\u4EF6\uFF1A\u53EA\u8981\u5BF9\u8BDD\u4E3B\u9898\u53D1\u751F\u53D8\u5316\uFF0C\u7ACB\u5373\u8C03\u7528 \`compress\`\uFF0C\u628A\u4E0A\u4E00\u4E3B\u9898\u5DF2\u7ECF\u5B8C\u6210\u7684\u4E0A\u4E0B\u6587\u538B\u7F29\u4E3A\u6458\u8981\u3002
|
|
5174
5561
|
|
|
5175
|
-
|
|
5562
|
+
\u4E3B\u9898\u53D8\u5316\u662F\u5BA2\u89C2\u4E14\u4E0D\u9700\u8981\u590D\u6742\u5224\u65AD\u7684\u4FE1\u53F7\u3002\u4EE5\u4E0B\u4EFB\u4E00\u60C5\u51B5\u90FD\u7B97\u4E3B\u9898\u53D8\u5316\uFF1A
|
|
5176
5563
|
|
|
5177
|
-
-
|
|
5178
|
-
-
|
|
5179
|
-
-
|
|
5180
|
-
-
|
|
5564
|
+
- \u7528\u6237\u63D0\u51FA\u4E0E\u4E0A\u4E00\u4EFB\u52A1\u65E0\u5173\u7684\u95EE\u9898
|
|
5565
|
+
- \u5DE5\u4F5C\u91CD\u70B9\u4ECE\u4E00\u4E2A\u6A21\u5757\u3001\u6587\u4EF6\u6216\u529F\u80FD\u8F6C\u79FB\u5230\u53E6\u4E00\u4E2A
|
|
5566
|
+
- \u76EE\u6807\u4ECE\u63A2\u7D22/\u7814\u7A76\u8F6C\u4E3A\u5B9E\u73B0\uFF0C\u6216\u4ECE\u5B9E\u73B0\u8F6C\u4E3A\u8C03\u8BD5/\u9A8C\u8BC1
|
|
5567
|
+
- \u4E00\u4E2A\u72EC\u7ACB\u95EE\u9898\u5DF2\u7ECF\u89E3\u51B3\uFF0C\u5F00\u59CB\u5904\u7406\u4E0B\u4E00\u4E2A\u95EE\u9898
|
|
5181
5568
|
|
|
5182
|
-
|
|
5569
|
+
\u51FA\u73B0\u4EFB\u4E00\u60C5\u51B5\u65F6\uFF0C\u7ACB\u5373\u538B\u7F29\u4E0A\u4E00\u4E3B\u9898\u5DF2\u7ECF\u5B8C\u6210\u7684\u8303\u56F4\uFF0C\u4E0D\u8981\u7B49\u5F85\u3002\u8FD9\u662F\u4FDD\u6301\u4E0A\u4E0B\u6587\u6E05\u6670\u6700\u6709\u6548\u7684\u65B9\u6CD5\u3002
|
|
5183
5570
|
|
|
5184
|
-
|
|
5571
|
+
\u5373\u4F7F\u4E3B\u9898\u6CA1\u6709\u53D8\u5316\uFF0C\u5728\u4EE5\u4E0B\u9636\u6BB5\u786E\u5B9E\u7ED3\u675F\u65F6\u4E5F\u5E94\u538B\u7F29\uFF1A
|
|
5185
5572
|
|
|
5186
|
-
-
|
|
5187
|
-
-
|
|
5188
|
-
-
|
|
5189
|
-
-
|
|
5573
|
+
- \u7814\u7A76\u5B8C\u6210\uFF0C\u7ED3\u8BBA\u5DF2\u7ECF\u786E\u5B9A
|
|
5574
|
+
- \u5B9E\u73B0\u5B8C\u6210\u5E76\u901A\u8FC7\u9A8C\u8BC1
|
|
5575
|
+
- \u63A2\u7D22\u5DF2\u7ECF\u5145\u5206\uFF0C\u76F8\u5173\u89C4\u5F8B\u5DF2\u7ECF\u660E\u786E
|
|
5576
|
+
- \u5DF2\u786E\u8BA4\u65E0\u6548\u7684\u65B9\u5411\u53EF\u4EE5\u4E22\u5F03\uFF0C\u65E0\u9700\u7B49\u5F85\u6574\u4E2A\u9636\u6BB5\u7ED3\u675F
|
|
5190
5577
|
|
|
5191
|
-
|
|
5578
|
+
\u4EE5\u4E0B\u60C5\u51B5\u4E0D\u8981\u538B\u7F29\uFF1A
|
|
5192
5579
|
|
|
5193
|
-
-
|
|
5194
|
-
-
|
|
5195
|
-
-
|
|
5580
|
+
- \u7F16\u8F91\u6216\u7CBE\u786E\u5F15\u7528\u4ECD\u9700\u8981\u539F\u59CB\u4E0A\u4E0B\u6587
|
|
5581
|
+
- \u76EE\u6807\u5DE5\u4F5C\u4ECD\u5728\u8FDB\u884C
|
|
5582
|
+
- \u540E\u7EED\u6B65\u9AA4\u53EF\u80FD\u9700\u8981\u7CBE\u786E\u7684\u4EE3\u7801\u3001\u9519\u8BEF\u4FE1\u606F\u6216\u6587\u4EF6\u5185\u5BB9
|
|
5196
5583
|
|
|
5197
|
-
|
|
5584
|
+
\u538B\u7F29\u524D\u5148\u95EE\u81EA\u5DF1\uFF1A_\u201C\u8FD9\u4E00\u90E8\u5206\u662F\u5426\u5DF2\u7ECF\u7ED3\u675F\uFF0C\u53EF\u4EE5\u53EA\u4FDD\u7559\u6458\u8981\uFF1F\u201D_
|
|
5198
5585
|
|
|
5199
|
-
|
|
5586
|
+
\u5982\u679C\u60C5\u51B5\u662F\u201C\u4E3B\u9898\u5DF2\u7ECF\u5207\u6362\uFF0C\u4E0D\u518D\u9700\u8981\u65E7\u5185\u5BB9\u201D\uFF0C\u7B54\u6848\u59CB\u7EC8\u662F\u201C\u53EF\u4EE5\u201D\u3002
|
|
5200
5587
|
|
|
5201
|
-
|
|
5588
|
+
\u5B9A\u671F\u8BC4\u4F30\u5BF9\u8BDD\u7684\u6709\u6548\u4FE1\u606F\u6BD4\u4F8B\u3002\u8C28\u614E\u4F7F\u7528 \`compress\` \u5E76\u751F\u6210\u9AD8\u8D28\u91CF\u6458\u8981\uFF0C\u4F18\u5148\u5904\u7406\u8FC7\u671F\u5185\u5BB9\uFF0C\u4FDD\u6301\u9AD8\u8D28\u91CF\u4E0A\u4E0B\u6587\u3002
|
|
5202
5589
|
|
|
5203
|
-
|
|
5590
|
+
\u4F60\u6709\u8D23\u4EFB\u7EF4\u62A4\u7CBE\u786E\u3001\u9AD8\u8D28\u91CF\u7684\u4E0A\u4E0B\u6587\uFF0C\u4EE5\u83B7\u5F97\u6700\u4F73\u8868\u73B0\u3002
|
|
5204
5591
|
`;
|
|
5205
5592
|
|
|
5206
5593
|
// lib/prompts/compress-range.ts
|
|
@@ -5337,44 +5724,44 @@ var COMPRESS_MESSAGE = `\u5C06\u5BF9\u8BDD\u4E2D\u9009\u5B9A\u7684\u5355\u6761\u
|
|
|
5337
5724
|
|
|
5338
5725
|
// lib/prompts/context-limit-nudge.ts
|
|
5339
5726
|
var CONTEXT_LIMIT_NUDGE = `<dcp-system-reminder>
|
|
5340
|
-
|
|
5727
|
+
\u7D27\u6025\uFF1A\u5DF2\u8FBE\u5230\u4E0A\u4E0B\u6587\u4E0A\u9650\u3002
|
|
5341
5728
|
|
|
5342
|
-
|
|
5729
|
+
\u5F53\u524D\u4E0A\u4E0B\u6587\u5DF2\u8FBE\u5230\u6216\u8D85\u8FC7\u914D\u7F6E\u7684\u6700\u5927\u9608\u503C\uFF0C\u5FC5\u987B\u7ACB\u5373\u56DE\u6536\u4E0A\u4E0B\u6587\u3002
|
|
5343
5730
|
|
|
5344
|
-
|
|
5731
|
+
\u4F60\u5FC5\u987B\u73B0\u5728\u8C03\u7528 \`compress\`\u3002\u538B\u7F29\u5B8C\u6210\u524D\u4E0D\u8981\u7EE7\u7EED\u5E38\u89C4\u63A2\u7D22\u3002
|
|
5345
5732
|
|
|
5346
|
-
|
|
5733
|
+
\u5982\u679C\u6B63\u5728\u6267\u884C\u4E00\u4E2A\u4E0D\u53EF\u4E2D\u65AD\u7684\u6B65\u9AA4\uFF0C\u53EA\u5B8C\u6210\u8BE5\u6B65\u9AA4\uFF0C\u968F\u540E\u7ACB\u523B\u538B\u7F29\u3002
|
|
5347
5734
|
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5735
|
+
\u9009\u62E9\u8303\u56F4
|
|
5736
|
+
\u4ECE\u8F83\u65E9\u4E14\u5DF2\u7ECF\u5B8C\u6210\u7684\u5386\u53F2\u5F00\u59CB\uFF0C\u4E00\u6B21\u8986\u76D6\u5C3D\u53EF\u80FD\u591A\u7684\u8FC7\u671F\u4E0A\u4E0B\u6587\u3002
|
|
5737
|
+
\u9664\u975E\u6700\u65B0\u5DE5\u4F5C\u5DF2\u7ECF\u660E\u786E\u7ED3\u675F\uFF0C\u5426\u5219\u4E0D\u8981\u9009\u62E9\u6700\u8FD1\u7684\u5DE5\u4F5C\u6D88\u606F\u3002
|
|
5351
5738
|
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5739
|
+
\u6458\u8981\u8981\u6C42
|
|
5740
|
+
\u6458\u8981\u5FC5\u987B\u8986\u76D6\u6240\u9009\u6D88\u606F\u4E2D\u7684\u6240\u6709\u5173\u952E\u4FE1\u606F\uFF0C\u786E\u4FDD\u540E\u7EED\u5DE5\u4F5C\u53EF\u4EE5\u7EE7\u7EED\u3002
|
|
5741
|
+
\u5982\u679C\u8303\u56F4\u5305\u542B\u7528\u6237\u6D88\u606F\uFF0C\u5FC5\u987B\u5B8C\u6574\u4FDD\u7559\u7528\u6237\u610F\u56FE\u3002\u5BF9\u4E8E\u8F83\u77ED\u7684\u7528\u6237\u6D88\u606F\uFF0C\u4F18\u5148\u76F4\u63A5\u5F15\u7528\uFF0C\u907F\u514D\u8BED\u4E49\u504F\u79FB\u3002
|
|
5355
5742
|
</dcp-system-reminder>
|
|
5356
5743
|
`;
|
|
5357
5744
|
|
|
5358
5745
|
// lib/prompts/turn-nudge.ts
|
|
5359
5746
|
var TURN_NUDGE = `
|
|
5360
|
-
|
|
5747
|
+
\u7528\u6237\u5F00\u59CB\u4E86\u65B0\u4E00\u8F6E\u5BF9\u8BDD\uFF0C\u6B64\u65F6\u6700\u5BB9\u6613\u53D1\u751F\u4E3B\u9898\u53D8\u5316\u3002
|
|
5361
5748
|
|
|
5362
|
-
|
|
5363
|
-
-
|
|
5364
|
-
-
|
|
5749
|
+
\u672C\u8F6E\u8BF7\u6C42\u662F\u5426\u4E0E\u4E0A\u4E00\u8F6E\u5C5E\u4E8E\u4E0D\u540C\u4E3B\u9898\u6216\u4EFB\u52A1\uFF1F
|
|
5750
|
+
- \u65B0\u4E3B\u9898\u6216\u72EC\u7ACB\u4EFB\u52A1\uFF1A\u73B0\u5728\u8C03\u7528 \`compress\`\uFF0C\u628A\u4E0A\u4E00\u4E3B\u9898\u5DF2\u7ECF\u5B8C\u6210\u7684\u4E0A\u4E0B\u6587\u538B\u7F29\u4E3A\u6458\u8981\uFF0C\u4E0D\u518D\u9010\u5B57\u4FDD\u7559\u7EC6\u8282\u3002
|
|
5751
|
+
- \u540C\u4E00\u4E3B\u9898\u7684\u5EF6\u7EED\uFF1A\u53EF\u4EE5\u4FDD\u7559\uFF0C\u4F46\u4ECD\u9700\u6CE8\u610F\u8FC7\u671F\u5185\u5BB9\u7684\u7D2F\u79EF\u3002
|
|
5365
5752
|
|
|
5366
|
-
|
|
5753
|
+
\u4E3B\u9898\u53D8\u5316\u662F\u6700\u660E\u786E\u3001\u6700\u5BA2\u89C2\u7684\u538B\u7F29\u89E6\u53D1\u6761\u4EF6\uFF0C\u4E0D\u8981\u9057\u6F0F\u3002
|
|
5367
5754
|
`;
|
|
5368
5755
|
|
|
5369
5756
|
// lib/prompts/iteration-nudge.ts
|
|
5370
5757
|
var ITERATION_NUDGE = `
|
|
5371
|
-
|
|
5758
|
+
\u5728\u6CA1\u6709\u65B0\u7528\u6237\u8F93\u5165\u7684\u60C5\u51B5\u4E0B\u5DF2\u7ECF\u6267\u884C\u4E86\u591A\u8F6E\u3002
|
|
5372
5759
|
|
|
5373
|
-
|
|
5374
|
-
-
|
|
5375
|
-
-
|
|
5760
|
+
\u5F53\u524D\u4EFB\u52A1\u662F\u5426\u5DF2\u7ECF\u5B8C\u6210\uFF0C\u6216\u8005\u5F53\u524D\u4E3B\u9898\u662F\u5426\u5DF2\u7ECF\u504F\u79BB\u65E9\u671F\u6B65\u9AA4\uFF1F
|
|
5761
|
+
- \u5982\u679C\u65E9\u671F\u6B65\u9AA4\u5C5E\u4E8E\u5DF2\u7ECF\u89E3\u51B3\u7684\u95EE\u9898\u6216\u5DF2\u88AB\u66FF\u4EE3\u7684\u63A2\u7D22\uFF0C\u73B0\u5728\u8C03\u7528 \`compress\` \u5C06\u5B83\u4EEC\u538B\u7F29\u4E3A\u6458\u8981\u3002
|
|
5762
|
+
- \u5982\u679C\u4ECD\u5728\u7EE7\u7EED\u540C\u4E00\u4E2A\u672A\u5B8C\u6210\u76EE\u6807\uFF0C\u800C\u4E14\u8FD8\u9700\u8981\u65E9\u671F\u4E0A\u4E0B\u6587\uFF0C\u5219\u4FDD\u7559\u8FD9\u4E9B\u5185\u5BB9\u3002
|
|
5376
5763
|
|
|
5377
|
-
|
|
5764
|
+
\u4E0D\u8981\u8BA9\u5DF2\u7ECF\u7ED3\u675F\u7684\u63A2\u7D22\u65B9\u5411\u7EE7\u7EED\u5360\u7528\u4E0A\u4E0B\u6587\u3002\u4E3B\u9898\u53D1\u751F\u504F\u79FB\u65F6\u6700\u9002\u5408\u538B\u7F29\u3002
|
|
5378
5765
|
`;
|
|
5379
5766
|
|
|
5380
5767
|
// lib/prompts/extensions/system.ts
|
|
@@ -5613,6 +6000,7 @@ var PromptStore = class {
|
|
|
5613
6000
|
paths;
|
|
5614
6001
|
customPromptsEnabled;
|
|
5615
6002
|
runtimePrompts;
|
|
6003
|
+
lastReloadAt = 0;
|
|
5616
6004
|
constructor(logger, workingDirectory, customPromptsEnabled = false) {
|
|
5617
6005
|
this.logger = logger;
|
|
5618
6006
|
this.paths = resolvePromptPaths(workingDirectory);
|
|
@@ -5627,6 +6015,9 @@ var PromptStore = class {
|
|
|
5627
6015
|
return { ...this.runtimePrompts };
|
|
5628
6016
|
}
|
|
5629
6017
|
reload() {
|
|
6018
|
+
const now = Date.now();
|
|
6019
|
+
if (now - this.lastReloadAt < 1e3) return;
|
|
6020
|
+
this.lastReloadAt = now;
|
|
5630
6021
|
const nextPrompts = createBundledRuntimePrompts();
|
|
5631
6022
|
if (!this.customPromptsEnabled) {
|
|
5632
6023
|
this.runtimePrompts = nextPrompts;
|
|
@@ -5865,13 +6256,13 @@ var stripHallucinationsFromString = (text) => {
|
|
|
5865
6256
|
};
|
|
5866
6257
|
var stripHallucinations = (messages) => {
|
|
5867
6258
|
for (const message of messages) {
|
|
6259
|
+
if (message.info.role !== "assistant") {
|
|
6260
|
+
continue;
|
|
6261
|
+
}
|
|
5868
6262
|
for (const part of message.parts) {
|
|
5869
6263
|
if (part.type === "text" && typeof part.text === "string") {
|
|
5870
6264
|
part.text = stripHallucinationsFromString(part.text);
|
|
5871
6265
|
}
|
|
5872
|
-
if (part.type === "tool" && part.state?.status === "completed" && typeof part.state.output === "string") {
|
|
5873
|
-
part.state.output = stripHallucinationsFromString(part.state.output);
|
|
5874
|
-
}
|
|
5875
6266
|
}
|
|
5876
6267
|
}
|
|
5877
6268
|
};
|
|
@@ -5966,7 +6357,8 @@ var filterCompressedRanges = (state, logger, config, messages) => {
|
|
|
5966
6357
|
return;
|
|
5967
6358
|
}
|
|
5968
6359
|
const result = [];
|
|
5969
|
-
for (
|
|
6360
|
+
for (let msgIndex = 0; msgIndex < messages.length; msgIndex++) {
|
|
6361
|
+
const msg = messages[msgIndex];
|
|
5970
6362
|
const msgId = msg.info.id;
|
|
5971
6363
|
const blockId = state.prune.messages.activeByAnchorMessageId.get(msgId);
|
|
5972
6364
|
const summary = blockId !== void 0 ? state.prune.messages.blocksById.get(blockId) : void 0;
|
|
@@ -5978,7 +6370,6 @@ var filterCompressedRanges = (state, logger, config, messages) => {
|
|
|
5978
6370
|
blockId: summary.blockId
|
|
5979
6371
|
});
|
|
5980
6372
|
} else {
|
|
5981
|
-
const msgIndex = messages.indexOf(msg);
|
|
5982
6373
|
const userMessage = getLastUserMessage(messages, msgIndex);
|
|
5983
6374
|
if (userMessage) {
|
|
5984
6375
|
const userInfo = userMessage.info;
|
|
@@ -6105,7 +6496,7 @@ var compressPermission = (state, config) => {
|
|
|
6105
6496
|
return state.compressPermission ?? config.compress.permission;
|
|
6106
6497
|
};
|
|
6107
6498
|
var syncCompressPermissionState = (state, config, hostPermissions, messages) => {
|
|
6108
|
-
const activeAgent = getLastUserMessage(messages)?.info
|
|
6499
|
+
const activeAgent = getLastUserMessage(messages)?.info?.agent;
|
|
6109
6500
|
state.compressPermission = resolveEffectiveCompressPermission(
|
|
6110
6501
|
config.compress.permission,
|
|
6111
6502
|
hostPermissions,
|
|
@@ -6243,11 +6634,13 @@ function listPriorityRefsBeforeIndex(messages, priorities, anchorIndex, priority
|
|
|
6243
6634
|
|
|
6244
6635
|
// lib/messages/inject/utils.ts
|
|
6245
6636
|
var MESSAGE_MODE_NUDGE_PRIORITY = "high";
|
|
6246
|
-
function getNudgeFrequency(
|
|
6247
|
-
|
|
6637
|
+
function getNudgeFrequency(config) {
|
|
6638
|
+
const value = config.compress.nudgeFrequency;
|
|
6639
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
6248
6640
|
}
|
|
6249
|
-
function getIterationNudgeThreshold(
|
|
6250
|
-
|
|
6641
|
+
function getIterationNudgeThreshold(config) {
|
|
6642
|
+
const value = config.compress.iterationNudgeThreshold;
|
|
6643
|
+
return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
|
|
6251
6644
|
}
|
|
6252
6645
|
function findLastNonIgnoredMessage(messages) {
|
|
6253
6646
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -6360,10 +6753,28 @@ function buildMessagePriorityGuidance(messages, compressionPriorities, anchorInd
|
|
|
6360
6753
|
const priorityLabel = `${priority[0].toUpperCase()}${priority.slice(1)}`;
|
|
6361
6754
|
return renderMessagePriorityGuidance(priorityLabel, refs);
|
|
6362
6755
|
}
|
|
6363
|
-
function
|
|
6756
|
+
function normalizeNudgeSignature(baseNudgeText) {
|
|
6757
|
+
return baseNudgeText.replace(/<\/dcp-system-reminder>\s*$/, "").replace(/^\n+/, "").trim();
|
|
6758
|
+
}
|
|
6759
|
+
function messageContainsNudgeSignature(message, baseNudgeText) {
|
|
6760
|
+
const signature = normalizeNudgeSignature(baseNudgeText);
|
|
6761
|
+
if (!signature) {
|
|
6762
|
+
return false;
|
|
6763
|
+
}
|
|
6764
|
+
for (const part of message.parts) {
|
|
6765
|
+
if (part.type === "text" && typeof part.text === "string" && part.text.includes(signature)) {
|
|
6766
|
+
return true;
|
|
6767
|
+
}
|
|
6768
|
+
}
|
|
6769
|
+
return false;
|
|
6770
|
+
}
|
|
6771
|
+
function injectAnchoredNudge(message, nudgeText, baseNudgeText) {
|
|
6364
6772
|
if (!nudgeText.trim()) {
|
|
6365
6773
|
return;
|
|
6366
6774
|
}
|
|
6775
|
+
if (messageContainsNudgeSignature(message, baseNudgeText)) {
|
|
6776
|
+
return;
|
|
6777
|
+
}
|
|
6367
6778
|
if (message.info.role === "user") {
|
|
6368
6779
|
if (appendToLastTextPart(message, nudgeText)) {
|
|
6369
6780
|
return;
|
|
@@ -6424,7 +6835,7 @@ function applyRangeModeAnchoredNudge(anchorMessageIds, messages, baseNudgeText,
|
|
|
6424
6835
|
return;
|
|
6425
6836
|
}
|
|
6426
6837
|
for (const { message } of collectAnchoredMessages(anchorMessageIds, messages)) {
|
|
6427
|
-
injectAnchoredNudge(message, nudgeText);
|
|
6838
|
+
injectAnchoredNudge(message, nudgeText, baseNudgeText);
|
|
6428
6839
|
}
|
|
6429
6840
|
}
|
|
6430
6841
|
function applyMessageModeAnchoredNudge(anchorMessageIds, messages, baseNudgeText, availableMessageIdGuidance, compressionPriorities) {
|
|
@@ -6437,7 +6848,7 @@ function applyMessageModeAnchoredNudge(anchorMessageIds, messages, baseNudgeText
|
|
|
6437
6848
|
);
|
|
6438
6849
|
const combinedGuidance = [availableMessageIdGuidance, priorityGuidance].filter((g) => g.trim().length > 0).join("\n\n");
|
|
6439
6850
|
const nudgeText = appendGuidanceToDcpTag(baseNudgeText, combinedGuidance);
|
|
6440
|
-
injectAnchoredNudge(message, nudgeText);
|
|
6851
|
+
injectAnchoredNudge(message, nudgeText, baseNudgeText);
|
|
6441
6852
|
}
|
|
6442
6853
|
}
|
|
6443
6854
|
function applyAnchoredNudges(state, config, messages, prompts, compressionPriorities) {
|
|
@@ -6493,11 +6904,44 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
6493
6904
|
}
|
|
6494
6905
|
|
|
6495
6906
|
// lib/messages/inject/inject.ts
|
|
6907
|
+
function userMessagesAfterLastAnchor(state, messages) {
|
|
6908
|
+
let latestAnchorIndex = -1;
|
|
6909
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
6910
|
+
if (state.nudges.turnNudgeAnchors.has(messages[index].info.id)) {
|
|
6911
|
+
latestAnchorIndex = index;
|
|
6912
|
+
break;
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
let userCount = 0;
|
|
6916
|
+
for (let index = latestAnchorIndex + 1; index < messages.length; index++) {
|
|
6917
|
+
const message = messages[index];
|
|
6918
|
+
if (message.info.role === "user" && !isIgnoredUserMessage(message)) {
|
|
6919
|
+
userCount++;
|
|
6920
|
+
}
|
|
6921
|
+
}
|
|
6922
|
+
return userCount;
|
|
6923
|
+
}
|
|
6496
6924
|
var injectCompressNudges = (state, config, logger, messages, prompts, compressionPriorities) => {
|
|
6925
|
+
const clearPendingBoundary = () => {
|
|
6926
|
+
if (!state.nudges.boundaryPending) {
|
|
6927
|
+
return;
|
|
6928
|
+
}
|
|
6929
|
+
state.nudges.boundaryPending = false;
|
|
6930
|
+
if (!state.sessionId) {
|
|
6931
|
+
return;
|
|
6932
|
+
}
|
|
6933
|
+
void saveSessionState(state, logger).catch(
|
|
6934
|
+
(error) => logger.warn("Failed to persist boundary nudge cleanup", {
|
|
6935
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6936
|
+
})
|
|
6937
|
+
);
|
|
6938
|
+
};
|
|
6497
6939
|
if (compressPermission(state, config) === "deny") {
|
|
6940
|
+
clearPendingBoundary();
|
|
6498
6941
|
return;
|
|
6499
6942
|
}
|
|
6500
6943
|
if (state.manualMode) {
|
|
6944
|
+
clearPendingBoundary();
|
|
6501
6945
|
return;
|
|
6502
6946
|
}
|
|
6503
6947
|
const lastMessage = findLastNonIgnoredMessage(messages);
|
|
@@ -6523,10 +6967,16 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6523
6967
|
state.nudges.contextLimitAnchors.clear();
|
|
6524
6968
|
state.nudges.turnNudgeAnchors.clear();
|
|
6525
6969
|
state.nudges.iterationNudgeAnchors.clear();
|
|
6526
|
-
|
|
6970
|
+
state.nudges.boundaryPending = false;
|
|
6971
|
+
void saveSessionState(state, logger).catch(
|
|
6972
|
+
(error) => logger.warn("Failed to persist context-limit nudge", {
|
|
6973
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6974
|
+
})
|
|
6975
|
+
);
|
|
6527
6976
|
return;
|
|
6528
6977
|
}
|
|
6529
6978
|
const { providerId, modelId } = getModelInfo(messages);
|
|
6979
|
+
const boundaryNudgeEnabled = config.compress.boundaryNudge !== false;
|
|
6530
6980
|
let anchorsChanged = false;
|
|
6531
6981
|
const { overMaxLimit, overMinLimit } = isContextOverLimits(
|
|
6532
6982
|
config,
|
|
@@ -6536,13 +6986,18 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6536
6986
|
messages
|
|
6537
6987
|
);
|
|
6538
6988
|
if (!overMinLimit) {
|
|
6539
|
-
const hadTurnAnchors = state.nudges.turnNudgeAnchors.size > 0;
|
|
6540
6989
|
const hadIterationAnchors = state.nudges.iterationNudgeAnchors.size > 0;
|
|
6541
|
-
if (
|
|
6542
|
-
state.nudges.turnNudgeAnchors.clear();
|
|
6990
|
+
if (hadIterationAnchors) {
|
|
6543
6991
|
state.nudges.iterationNudgeAnchors.clear();
|
|
6544
6992
|
anchorsChanged = true;
|
|
6545
6993
|
}
|
|
6994
|
+
if (!boundaryNudgeEnabled) {
|
|
6995
|
+
const hadTurnAnchors = state.nudges.turnNudgeAnchors.size > 0;
|
|
6996
|
+
if (hadTurnAnchors) {
|
|
6997
|
+
state.nudges.turnNudgeAnchors.clear();
|
|
6998
|
+
anchorsChanged = true;
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
6546
7001
|
}
|
|
6547
7002
|
if (overMaxLimit) {
|
|
6548
7003
|
if (lastMessage) {
|
|
@@ -6560,7 +7015,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6560
7015
|
}
|
|
6561
7016
|
} else if (overMinLimit) {
|
|
6562
7017
|
const isLastMessageUser = lastMessage?.message.info.role === "user";
|
|
6563
|
-
if (isLastMessageUser && lastAssistantMessage) {
|
|
7018
|
+
if (!boundaryNudgeEnabled && isLastMessageUser && lastAssistantMessage) {
|
|
6564
7019
|
const previousSize = state.nudges.turnNudgeAnchors.size;
|
|
6565
7020
|
state.nudges.turnNudgeAnchors.add(lastMessage.message.info.id);
|
|
6566
7021
|
state.nudges.turnNudgeAnchors.add(lastAssistantMessage.info.id);
|
|
@@ -6592,9 +7047,40 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6592
7047
|
}
|
|
6593
7048
|
}
|
|
6594
7049
|
}
|
|
7050
|
+
if (boundaryNudgeEnabled && !overMaxLimit) {
|
|
7051
|
+
const lastUserMessage = getLastUserMessage(messages);
|
|
7052
|
+
const isLastMessageUser = lastMessage?.message.info.role === "user";
|
|
7053
|
+
const hasPendingBoundary = state.nudges.boundaryPending;
|
|
7054
|
+
if (lastUserMessage && lastAssistantMessage) {
|
|
7055
|
+
const lastUserMessageIndex = messages.findIndex(
|
|
7056
|
+
(message) => message.info.id === lastUserMessage.info.id
|
|
7057
|
+
);
|
|
7058
|
+
const lastAssistantMessageIndex = messages.findIndex(
|
|
7059
|
+
(message) => message.info.id === lastAssistantMessage.info.id
|
|
7060
|
+
);
|
|
7061
|
+
const alreadyAnchored = state.nudges.turnNudgeAnchors.has(lastUserMessage.info.id) && state.nudges.turnNudgeAnchors.has(lastAssistantMessage.info.id);
|
|
7062
|
+
if (!alreadyAnchored && lastUserMessageIndex >= 0 && lastAssistantMessageIndex >= 0) {
|
|
7063
|
+
const interval = getNudgeFrequency(config);
|
|
7064
|
+
const shouldAnchor = hasPendingBoundary || isLastMessageUser && userMessagesAfterLastAnchor(state, messages) >= interval;
|
|
7065
|
+
if (shouldAnchor) {
|
|
7066
|
+
state.nudges.turnNudgeAnchors.add(lastAssistantMessage.info.id);
|
|
7067
|
+
state.nudges.turnNudgeAnchors.add(lastUserMessage.info.id);
|
|
7068
|
+
anchorsChanged = true;
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
if (hasPendingBoundary) {
|
|
7072
|
+
state.nudges.boundaryPending = false;
|
|
7073
|
+
anchorsChanged = true;
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
6595
7077
|
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities);
|
|
6596
7078
|
if (anchorsChanged) {
|
|
6597
|
-
void saveSessionState(state, logger)
|
|
7079
|
+
void saveSessionState(state, logger).catch(
|
|
7080
|
+
(error) => logger.warn("Failed to persist compression nudge", {
|
|
7081
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7082
|
+
})
|
|
7083
|
+
);
|
|
6598
7084
|
}
|
|
6599
7085
|
};
|
|
6600
7086
|
var injectMessageIds = (state, config, messages, compressionPriorities) => {
|
|
@@ -6661,6 +7147,7 @@ var injectExtendedSubAgentResults = async (client, state, logger, messages, allo
|
|
|
6661
7147
|
if (!allowSubAgents) {
|
|
6662
7148
|
return;
|
|
6663
7149
|
}
|
|
7150
|
+
const tasks = [];
|
|
6664
7151
|
for (const message of messages) {
|
|
6665
7152
|
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
6666
7153
|
for (const part of parts) {
|
|
@@ -6673,40 +7160,43 @@ var injectExtendedSubAgentResults = async (client, state, logger, messages, allo
|
|
|
6673
7160
|
if (part.state?.status !== "completed" || typeof part.state.output !== "string") {
|
|
6674
7161
|
continue;
|
|
6675
7162
|
}
|
|
6676
|
-
|
|
6677
|
-
if (cachedResult !== void 0) {
|
|
6678
|
-
if (cachedResult) {
|
|
6679
|
-
part.state.output = stripHallucinationsFromString(
|
|
6680
|
-
mergeSubagentResult(part.state.output, cachedResult)
|
|
6681
|
-
);
|
|
6682
|
-
}
|
|
6683
|
-
continue;
|
|
6684
|
-
}
|
|
6685
|
-
const subAgentSessionId = getSubAgentId(part);
|
|
6686
|
-
if (!subAgentSessionId) {
|
|
6687
|
-
continue;
|
|
6688
|
-
}
|
|
6689
|
-
let subAgentMessages = [];
|
|
6690
|
-
try {
|
|
6691
|
-
subAgentMessages = await fetchSubAgentMessages(client, subAgentSessionId);
|
|
6692
|
-
} catch (error) {
|
|
6693
|
-
logger.warn("Failed to fetch subagent session for output expansion", {
|
|
6694
|
-
subAgentSessionId,
|
|
6695
|
-
callID: part.callID,
|
|
6696
|
-
error: error instanceof Error ? error.message : String(error)
|
|
6697
|
-
});
|
|
6698
|
-
continue;
|
|
6699
|
-
}
|
|
6700
|
-
const subAgentResultText = buildSubagentResultText(subAgentMessages);
|
|
6701
|
-
if (!subAgentResultText) {
|
|
6702
|
-
continue;
|
|
6703
|
-
}
|
|
6704
|
-
state.subAgentResultCache.set(part.callID, subAgentResultText);
|
|
6705
|
-
part.state.output = stripHallucinationsFromString(
|
|
6706
|
-
mergeSubagentResult(part.state.output, subAgentResultText)
|
|
6707
|
-
);
|
|
7163
|
+
tasks.push({ part });
|
|
6708
7164
|
}
|
|
6709
7165
|
}
|
|
7166
|
+
await runWithConcurrency(tasks, 4, async ({ part }) => {
|
|
7167
|
+
const cachedResult = getCachedSubAgentResult(state, part.callID);
|
|
7168
|
+
if (cachedResult !== void 0) {
|
|
7169
|
+
if (cachedResult) {
|
|
7170
|
+
part.state.output = stripHallucinationsFromString(
|
|
7171
|
+
mergeSubagentResult(part.state.output, cachedResult)
|
|
7172
|
+
);
|
|
7173
|
+
}
|
|
7174
|
+
return;
|
|
7175
|
+
}
|
|
7176
|
+
const subAgentSessionId = getSubAgentId(part);
|
|
7177
|
+
if (!subAgentSessionId) {
|
|
7178
|
+
return;
|
|
7179
|
+
}
|
|
7180
|
+
let subAgentMessages = [];
|
|
7181
|
+
try {
|
|
7182
|
+
subAgentMessages = await fetchSubAgentMessages(client, subAgentSessionId);
|
|
7183
|
+
} catch (error) {
|
|
7184
|
+
logger.warn("Failed to fetch subagent session for output expansion", {
|
|
7185
|
+
subAgentSessionId,
|
|
7186
|
+
callID: part.callID,
|
|
7187
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7188
|
+
});
|
|
7189
|
+
return;
|
|
7190
|
+
}
|
|
7191
|
+
const subAgentResultText = buildSubagentResultText(subAgentMessages);
|
|
7192
|
+
if (!subAgentResultText) {
|
|
7193
|
+
return;
|
|
7194
|
+
}
|
|
7195
|
+
const cached = cacheSubAgentResult(state, part.callID, subAgentResultText);
|
|
7196
|
+
part.state.output = stripHallucinationsFromString(
|
|
7197
|
+
mergeSubagentResult(part.state.output, cached)
|
|
7198
|
+
);
|
|
7199
|
+
});
|
|
6710
7200
|
};
|
|
6711
7201
|
|
|
6712
7202
|
// lib/messages/reasoning-strip.ts
|
|
@@ -7294,11 +7784,7 @@ var COMPRESS_TRIGGER_PROMPT = [
|
|
|
7294
7784
|
function getTriggerPrompt(tool3, state, config, messages, userFocus) {
|
|
7295
7785
|
const base = COMPRESS_TRIGGER_PROMPT;
|
|
7296
7786
|
const compressedBlockGuidance = config.compress.mode === "message" ? "" : buildCompressedBlockGuidance(state);
|
|
7297
|
-
const availableMessageIdGuidance = buildAvailableMessageIdGuidance(
|
|
7298
|
-
state,
|
|
7299
|
-
config,
|
|
7300
|
-
messages
|
|
7301
|
-
);
|
|
7787
|
+
const availableMessageIdGuidance = buildAvailableMessageIdGuidance(state, config, messages);
|
|
7302
7788
|
const sections = [base, availableMessageIdGuidance, compressedBlockGuidance];
|
|
7303
7789
|
if (userFocus && userFocus.trim().length > 0) {
|
|
7304
7790
|
sections.push(`Additional user focus:
|
|
@@ -7811,13 +8297,14 @@ var INTERNAL_AGENT_SIGNATURES = [
|
|
|
7811
8297
|
"You are an anchored context summarization assistant for coding sessions",
|
|
7812
8298
|
"Summarize what was done in this conversation"
|
|
7813
8299
|
];
|
|
7814
|
-
function createSystemPromptHandler(
|
|
8300
|
+
function createSystemPromptHandler(target, logger, config, prompts) {
|
|
7815
8301
|
return async (input, output) => {
|
|
7816
|
-
|
|
8302
|
+
const state = input.sessionID ? resolveSessionState(target, input.sessionID) : target instanceof SessionStateStore ? void 0 : target;
|
|
8303
|
+
if (state && input.model?.limit?.context) {
|
|
7817
8304
|
state.modelContextLimit = input.model.limit.context;
|
|
7818
8305
|
logger.debug("Cached model context limit", { limit: state.modelContextLimit });
|
|
7819
8306
|
}
|
|
7820
|
-
if (state
|
|
8307
|
+
if (state?.isSubAgent && !config.experimental.allowSubAgents) {
|
|
7821
8308
|
return;
|
|
7822
8309
|
}
|
|
7823
8310
|
const systemText = output.system.join("\n");
|
|
@@ -7825,7 +8312,7 @@ function createSystemPromptHandler(state, logger, config, prompts) {
|
|
|
7825
8312
|
logger.info("Skipping DCP system prompt injection for internal agent");
|
|
7826
8313
|
return;
|
|
7827
8314
|
}
|
|
7828
|
-
const effectivePermission = input.sessionID && state
|
|
8315
|
+
const effectivePermission = input.sessionID && state?.sessionId === input.sessionID ? compressPermission(state, config) : config.compress.permission;
|
|
7829
8316
|
if (effectivePermission === "deny") {
|
|
7830
8317
|
return;
|
|
7831
8318
|
}
|
|
@@ -7834,8 +8321,8 @@ function createSystemPromptHandler(state, logger, config, prompts) {
|
|
|
7834
8321
|
const newPrompt = renderSystemPrompt(
|
|
7835
8322
|
runtimePrompts,
|
|
7836
8323
|
buildProtectedToolsExtension(config.compress.protectedTools),
|
|
7837
|
-
!!state
|
|
7838
|
-
state
|
|
8324
|
+
!!state?.manualMode,
|
|
8325
|
+
!!state?.isSubAgent && config.experimental.allowSubAgents
|
|
7839
8326
|
);
|
|
7840
8327
|
if (output.system.length > 0) {
|
|
7841
8328
|
output.system[output.system.length - 1] += "\n\n" + newPrompt;
|
|
@@ -7844,7 +8331,7 @@ function createSystemPromptHandler(state, logger, config, prompts) {
|
|
|
7844
8331
|
}
|
|
7845
8332
|
};
|
|
7846
8333
|
}
|
|
7847
|
-
function createChatMessageTransformHandler(client,
|
|
8334
|
+
function createChatMessageTransformHandler(client, target, logger, config, prompts, hostPermissions) {
|
|
7848
8335
|
return async (input, output) => {
|
|
7849
8336
|
const receivedMessages = Array.isArray(output.messages) ? output.messages.length : 0;
|
|
7850
8337
|
const messages = filterMessagesInPlace(output.messages);
|
|
@@ -7854,7 +8341,14 @@ function createChatMessageTransformHandler(client, state, logger, config, prompt
|
|
|
7854
8341
|
usable: messages.length
|
|
7855
8342
|
});
|
|
7856
8343
|
}
|
|
7857
|
-
|
|
8344
|
+
const state = await checkSession(
|
|
8345
|
+
client,
|
|
8346
|
+
target,
|
|
8347
|
+
logger,
|
|
8348
|
+
output.messages,
|
|
8349
|
+
config.manualMode.enabled
|
|
8350
|
+
);
|
|
8351
|
+
if (!state) return;
|
|
7858
8352
|
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
7859
8353
|
if (state.isSubAgent && !config.experimental.allowSubAgents) {
|
|
7860
8354
|
return;
|
|
@@ -7900,7 +8394,7 @@ function createChatMessageTransformHandler(client, state, logger, config, prompt
|
|
|
7900
8394
|
}
|
|
7901
8395
|
};
|
|
7902
8396
|
}
|
|
7903
|
-
function createCommandExecuteHandler(client,
|
|
8397
|
+
function createCommandExecuteHandler(client, target, logger, config, workingDirectory, hostPermissions) {
|
|
7904
8398
|
return async (input, output) => {
|
|
7905
8399
|
if (!config.commands.enabled) {
|
|
7906
8400
|
return;
|
|
@@ -7910,9 +8404,9 @@ function createCommandExecuteHandler(client, state, logger, config, workingDirec
|
|
|
7910
8404
|
path: { id: input.sessionID }
|
|
7911
8405
|
});
|
|
7912
8406
|
const messages = filterMessages(messagesResponse.data || messagesResponse);
|
|
7913
|
-
await
|
|
8407
|
+
const state = await initializeSessionState(
|
|
7914
8408
|
client,
|
|
7915
|
-
|
|
8409
|
+
target,
|
|
7916
8410
|
input.sessionID,
|
|
7917
8411
|
logger,
|
|
7918
8412
|
messages,
|
|
@@ -7997,8 +8491,50 @@ function createTextCompleteHandler() {
|
|
|
7997
8491
|
output.text = stripHallucinationsFromString(output.text);
|
|
7998
8492
|
};
|
|
7999
8493
|
}
|
|
8000
|
-
function createEventHandler(
|
|
8494
|
+
function createEventHandler(target, logger, config) {
|
|
8495
|
+
const markBoundaryPending = async (state) => {
|
|
8496
|
+
if (!state) return;
|
|
8497
|
+
if (config.compress.boundaryNudge === false) return;
|
|
8498
|
+
if (state.manualMode) return;
|
|
8499
|
+
if (state.isSubAgent && !config.experimental.allowSubAgents) return;
|
|
8500
|
+
if (compressPermission(state, config) === "deny") return;
|
|
8501
|
+
if (!state.sessionId) return;
|
|
8502
|
+
if (state.nudges.boundaryPending) return;
|
|
8503
|
+
state.nudges.boundaryPending = true;
|
|
8504
|
+
await saveSessionState(state, logger);
|
|
8505
|
+
logger.debug("Marked boundary nudge pending", { sessionId: state.sessionId });
|
|
8506
|
+
};
|
|
8001
8507
|
return async (input) => {
|
|
8508
|
+
if (input.event.type === "session.idle") {
|
|
8509
|
+
const sessionId = input.event.properties?.sessionID;
|
|
8510
|
+
if (typeof sessionId === "string") {
|
|
8511
|
+
const state2 = target instanceof SessionStateStore ? target.peek(sessionId) : target.sessionId === sessionId ? target : void 0;
|
|
8512
|
+
await markBoundaryPending(state2).catch(
|
|
8513
|
+
(error) => logger.warn("Failed to persist boundary nudge", {
|
|
8514
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8515
|
+
})
|
|
8516
|
+
);
|
|
8517
|
+
}
|
|
8518
|
+
return;
|
|
8519
|
+
}
|
|
8520
|
+
if (input.event.type === "vcs.branch.updated") {
|
|
8521
|
+
if (target instanceof SessionStateStore) {
|
|
8522
|
+
const sessions = [];
|
|
8523
|
+
target.forEach((state2) => sessions.push(state2));
|
|
8524
|
+
await Promise.all(
|
|
8525
|
+
sessions.map(
|
|
8526
|
+
(state2) => markBoundaryPending(state2).catch(
|
|
8527
|
+
(error) => logger.warn("Failed to persist boundary nudge", {
|
|
8528
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8529
|
+
})
|
|
8530
|
+
)
|
|
8531
|
+
)
|
|
8532
|
+
);
|
|
8533
|
+
} else {
|
|
8534
|
+
await markBoundaryPending(target);
|
|
8535
|
+
}
|
|
8536
|
+
return;
|
|
8537
|
+
}
|
|
8002
8538
|
const eventTime = typeof input.event?.time === "number" && Number.isFinite(input.event.time) ? input.event.time : typeof input.event?.properties?.time === "number" && Number.isFinite(input.event.properties.time) ? input.event.properties.time : void 0;
|
|
8003
8539
|
if (input.event.type !== "message.part.updated") {
|
|
8004
8540
|
return;
|
|
@@ -8007,16 +8543,25 @@ function createEventHandler(state, logger) {
|
|
|
8007
8543
|
if (part?.type !== "tool" || part.tool !== "compress") {
|
|
8008
8544
|
return;
|
|
8009
8545
|
}
|
|
8546
|
+
if (typeof part.callID !== "string" || typeof part.messageID !== "string") {
|
|
8547
|
+
return;
|
|
8548
|
+
}
|
|
8549
|
+
const eventSessionId = typeof input.event.properties?.sessionID === "string" ? input.event.properties.sessionID : typeof part.sessionID === "string" ? part.sessionID : void 0;
|
|
8550
|
+
const state = target instanceof SessionStateStore ? target.resolveEventState(eventSessionId, part.messageID) : target;
|
|
8551
|
+
const key = buildCompressionTimingKey(part.messageID, part.callID);
|
|
8552
|
+
if (state) pruneCompressionTiming(state);
|
|
8010
8553
|
if (part.state.status === "pending") {
|
|
8011
|
-
|
|
8554
|
+
const startedAt = eventTime ?? Date.now();
|
|
8555
|
+
if (!state && target instanceof SessionStateStore) {
|
|
8556
|
+
target.recordOrphanStart(key, startedAt);
|
|
8012
8557
|
return;
|
|
8013
8558
|
}
|
|
8014
|
-
|
|
8015
|
-
const key = buildCompressionTimingKey(part.messageID, part.callID);
|
|
8559
|
+
if (!state) return;
|
|
8016
8560
|
if (state.compressionTiming.startsByCallId.has(key)) {
|
|
8017
8561
|
return;
|
|
8018
8562
|
}
|
|
8019
8563
|
state.compressionTiming.startsByCallId.set(key, startedAt);
|
|
8564
|
+
state.compressionTiming.recordedAtByCallId.set(key, Date.now());
|
|
8020
8565
|
logger.debug("Recorded compression start", {
|
|
8021
8566
|
messageID: part.messageID,
|
|
8022
8567
|
callID: part.callID,
|
|
@@ -8025,20 +8570,26 @@ function createEventHandler(state, logger) {
|
|
|
8025
8570
|
return;
|
|
8026
8571
|
}
|
|
8027
8572
|
if (part.state.status === "completed") {
|
|
8028
|
-
|
|
8029
|
-
return;
|
|
8030
|
-
}
|
|
8031
|
-
const key = buildCompressionTimingKey(part.messageID, part.callID);
|
|
8032
|
-
const start = consumeCompressionStart(state, part.messageID, part.callID);
|
|
8573
|
+
const start = state ? consumeCompressionStart(state, part.messageID, part.callID) : target instanceof SessionStateStore ? target.consumeOrphanStart(key) : void 0;
|
|
8033
8574
|
const durationMs = resolveCompressionDuration(start, eventTime, part.state.time);
|
|
8034
8575
|
if (typeof durationMs !== "number") {
|
|
8035
8576
|
return;
|
|
8036
8577
|
}
|
|
8578
|
+
if (!state && target instanceof SessionStateStore) {
|
|
8579
|
+
target.recordOrphanPending(key, {
|
|
8580
|
+
messageId: part.messageID,
|
|
8581
|
+
callId: part.callID,
|
|
8582
|
+
durationMs
|
|
8583
|
+
});
|
|
8584
|
+
return;
|
|
8585
|
+
}
|
|
8586
|
+
if (!state) return;
|
|
8037
8587
|
state.compressionTiming.pendingByCallId.set(key, {
|
|
8038
8588
|
messageId: part.messageID,
|
|
8039
8589
|
callId: part.callID,
|
|
8040
8590
|
durationMs
|
|
8041
8591
|
});
|
|
8592
|
+
state.compressionTiming.recordedAtByCallId.set(key, Date.now());
|
|
8042
8593
|
const updates = applyPendingCompressionDurations(state);
|
|
8043
8594
|
if (updates === 0) {
|
|
8044
8595
|
return;
|
|
@@ -8055,44 +8606,19 @@ function createEventHandler(state, logger) {
|
|
|
8055
8606
|
if (part.state.status === "running") {
|
|
8056
8607
|
return;
|
|
8057
8608
|
}
|
|
8058
|
-
if (
|
|
8609
|
+
if (state) {
|
|
8059
8610
|
state.compressionTiming.startsByCallId.delete(
|
|
8060
8611
|
buildCompressionTimingKey(part.messageID, part.callID)
|
|
8061
8612
|
);
|
|
8613
|
+
state.compressionTiming.recordedAtByCallId.delete(key);
|
|
8614
|
+
} else if (target instanceof SessionStateStore) {
|
|
8615
|
+
target.deleteOrphanStart(key);
|
|
8062
8616
|
}
|
|
8063
8617
|
};
|
|
8064
8618
|
}
|
|
8065
8619
|
|
|
8066
|
-
// lib/auth.ts
|
|
8067
|
-
function isSecureMode() {
|
|
8068
|
-
return !!process.env.OPENCODE_SERVER_PASSWORD;
|
|
8069
|
-
}
|
|
8070
|
-
function getAuthorizationHeader() {
|
|
8071
|
-
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
8072
|
-
if (!password) return void 0;
|
|
8073
|
-
const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode";
|
|
8074
|
-
const credentials = Buffer.from(`${username}:${password}`).toString("base64");
|
|
8075
|
-
return `Basic ${credentials}`;
|
|
8076
|
-
}
|
|
8077
|
-
function configureClientAuth(client) {
|
|
8078
|
-
const authHeader = getAuthorizationHeader();
|
|
8079
|
-
if (!authHeader) {
|
|
8080
|
-
return client;
|
|
8081
|
-
}
|
|
8082
|
-
const innerClient = client._client || client.client;
|
|
8083
|
-
if (innerClient?.interceptors?.request) {
|
|
8084
|
-
innerClient.interceptors.request.use((request) => {
|
|
8085
|
-
if (!request.headers.has("Authorization")) {
|
|
8086
|
-
request.headers.set("Authorization", authHeader);
|
|
8087
|
-
}
|
|
8088
|
-
return request;
|
|
8089
|
-
});
|
|
8090
|
-
}
|
|
8091
|
-
return client;
|
|
8092
|
-
}
|
|
8093
|
-
|
|
8094
8620
|
// lib/update.ts
|
|
8095
|
-
import { readFile as readFile2
|
|
8621
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
8096
8622
|
import { basename, dirname as dirname3, join as join5 } from "path";
|
|
8097
8623
|
import { fileURLToPath } from "url";
|
|
8098
8624
|
var PACKAGE_NAME = "@lexwdex-org/opencode-dcp";
|
|
@@ -8101,15 +8627,16 @@ function startAutoUpdate(ctx, enabled) {
|
|
|
8101
8627
|
const controller = new AbortController();
|
|
8102
8628
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
8103
8629
|
void checkAutoUpdate(controller.signal).then((result) => {
|
|
8104
|
-
if (!result.
|
|
8630
|
+
if (!result.available) return;
|
|
8105
8631
|
setTimeout(() => {
|
|
8106
|
-
ctx.client.tui.showToast({
|
|
8632
|
+
void ctx.client.tui.showToast({
|
|
8107
8633
|
body: {
|
|
8108
|
-
title: "DCP update
|
|
8109
|
-
message:
|
|
8634
|
+
title: "DCP update available",
|
|
8635
|
+
message: `${result.name} ${result.latest} is available (current ${result.current}). Reinstall the plugin to update.`,
|
|
8110
8636
|
variant: "info",
|
|
8111
8637
|
duration: 7e3
|
|
8112
8638
|
}
|
|
8639
|
+
}).catch(() => {
|
|
8113
8640
|
});
|
|
8114
8641
|
}, 5e3);
|
|
8115
8642
|
}).catch(() => {
|
|
@@ -8117,25 +8644,14 @@ function startAutoUpdate(ctx, enabled) {
|
|
|
8117
8644
|
}
|
|
8118
8645
|
async function checkAutoUpdate(signal) {
|
|
8119
8646
|
const packageDir = await findPackageDir(PACKAGE_NAME);
|
|
8120
|
-
if (!packageDir) return {
|
|
8647
|
+
if (!packageDir) return { available: false };
|
|
8121
8648
|
const pkg = await readPackageJson(join5(packageDir, "package.json"));
|
|
8122
|
-
if (!pkg?.name || !pkg.version) return {
|
|
8649
|
+
if (!pkg?.name || !pkg.version) return { available: false };
|
|
8123
8650
|
const latest = await fetchLatestVersion(pkg.name, signal);
|
|
8124
|
-
if (!latest || !isVersionNewer(latest, pkg.version)) return {
|
|
8125
|
-
const
|
|
8126
|
-
if (!
|
|
8127
|
-
|
|
8128
|
-
await rm(removeDir, { recursive: true, force: true });
|
|
8129
|
-
} catch {
|
|
8130
|
-
return {
|
|
8131
|
-
updated: false,
|
|
8132
|
-
error: "remove_failed",
|
|
8133
|
-
name: pkg.name,
|
|
8134
|
-
current: pkg.version,
|
|
8135
|
-
latest
|
|
8136
|
-
};
|
|
8137
|
-
}
|
|
8138
|
-
return { updated: true, name: pkg.name, current: pkg.version, latest };
|
|
8651
|
+
if (!latest || !isVersionNewer(latest, pkg.version)) return { available: false };
|
|
8652
|
+
const wrapperDir = await findAutoUpdateWrapperDir(packageDir, pkg.name);
|
|
8653
|
+
if (!wrapperDir) return { available: false };
|
|
8654
|
+
return { available: true, name: pkg.name, current: pkg.version, latest };
|
|
8139
8655
|
}
|
|
8140
8656
|
async function findPackageDir(name) {
|
|
8141
8657
|
let dir = dirname3(fileURLToPath(import.meta.url));
|
|
@@ -8147,7 +8663,7 @@ async function findPackageDir(name) {
|
|
|
8147
8663
|
dir = parent;
|
|
8148
8664
|
}
|
|
8149
8665
|
}
|
|
8150
|
-
async function
|
|
8666
|
+
async function findAutoUpdateWrapperDir(packageDir, name) {
|
|
8151
8667
|
const packageParent = dirname3(packageDir);
|
|
8152
8668
|
const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname3(packageParent) : packageParent;
|
|
8153
8669
|
if (basename(nodeModulesDir) !== "node_modules") return void 0;
|
|
@@ -8243,36 +8759,33 @@ var server = (async (ctx) => {
|
|
|
8243
8759
|
return {};
|
|
8244
8760
|
}
|
|
8245
8761
|
const logger = new Logger(config.debug);
|
|
8246
|
-
const
|
|
8762
|
+
const states = new SessionStateStore(createSessionState);
|
|
8247
8763
|
const prompts = new PromptStore(logger, ctx.directory, config.experimental.customPrompts);
|
|
8248
8764
|
const hostPermissions = {
|
|
8249
8765
|
global: void 0,
|
|
8250
8766
|
agents: {}
|
|
8251
8767
|
};
|
|
8252
|
-
if (isSecureMode()) {
|
|
8253
|
-
configureClientAuth(ctx.client);
|
|
8254
|
-
}
|
|
8255
8768
|
logger.info("DCP initialized", {
|
|
8256
8769
|
strategies: config.strategies
|
|
8257
8770
|
});
|
|
8258
8771
|
startAutoUpdate(ctx, config.autoUpdate);
|
|
8259
8772
|
const compressToolContext = {
|
|
8260
8773
|
client: ctx.client,
|
|
8261
|
-
state,
|
|
8774
|
+
state: states,
|
|
8262
8775
|
logger,
|
|
8263
8776
|
config,
|
|
8264
8777
|
prompts
|
|
8265
8778
|
};
|
|
8266
8779
|
return {
|
|
8267
8780
|
"experimental.chat.system.transform": createSystemPromptHandler(
|
|
8268
|
-
|
|
8781
|
+
states,
|
|
8269
8782
|
logger,
|
|
8270
8783
|
config,
|
|
8271
8784
|
prompts
|
|
8272
8785
|
),
|
|
8273
8786
|
"experimental.chat.messages.transform": createChatMessageTransformHandler(
|
|
8274
8787
|
ctx.client,
|
|
8275
|
-
|
|
8788
|
+
states,
|
|
8276
8789
|
logger,
|
|
8277
8790
|
config,
|
|
8278
8791
|
prompts,
|
|
@@ -8281,13 +8794,13 @@ var server = (async (ctx) => {
|
|
|
8281
8794
|
"experimental.text.complete": createTextCompleteHandler(),
|
|
8282
8795
|
"command.execute.before": createCommandExecuteHandler(
|
|
8283
8796
|
ctx.client,
|
|
8284
|
-
|
|
8797
|
+
states,
|
|
8285
8798
|
logger,
|
|
8286
8799
|
config,
|
|
8287
8800
|
ctx.directory,
|
|
8288
8801
|
hostPermissions
|
|
8289
8802
|
),
|
|
8290
|
-
event: createEventHandler(
|
|
8803
|
+
event: createEventHandler(states, logger, config),
|
|
8291
8804
|
tool: {
|
|
8292
8805
|
...config.compress.permission !== "deny" && {
|
|
8293
8806
|
compress: config.compress.mode === "message" ? createCompressMessageTool(compressToolContext) : createCompressRangeTool(compressToolContext)
|