opencode-acp 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/README.zh-CN.md +64 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +286 -254
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/decompress.d.ts.map +1 -1
- package/dist/lib/compress/index.d.ts +1 -0
- package/dist/lib/compress/index.d.ts.map +1 -1
- package/dist/lib/compress/message.d.ts.map +1 -1
- package/dist/lib/compress/pipeline.d.ts.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/compress/status.d.ts +4 -0
- package/dist/lib/compress/status.d.ts.map +1 -0
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +20 -1
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/prompts/extensions/nudge.d.ts.map +1 -1
- package/dist/lib/prompts/system.d.ts +1 -1
- package/dist/lib/prompts/system.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +3 -1
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/state/utils.d.ts.map +1 -1
- package/dist/lib/ui/notification.d.ts +1 -1
- package/dist/lib/ui/notification.d.ts.map +1 -1
- package/dist/lib/ui/utils.d.ts +1 -0
- package/dist/lib/ui/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -894,13 +894,13 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
894
894
|
"compress.modelMaxLimits",
|
|
895
895
|
"compress.modelMinLimits",
|
|
896
896
|
"compress.nudgeFrequency",
|
|
897
|
-
"compress.
|
|
897
|
+
"compress.minNudgeContextPercent",
|
|
898
|
+
"compress.nudgeGrowthTokens",
|
|
898
899
|
"compress.iterationNudgeThreshold",
|
|
899
900
|
"compress.nudgeForce",
|
|
900
901
|
"compress.protectedTools",
|
|
901
902
|
"compress.protectTags",
|
|
902
903
|
"compress.protectUserMessages",
|
|
903
|
-
"compress.maxSummaryLength",
|
|
904
904
|
"compress.maxSummaryLengthHard",
|
|
905
905
|
"compress.minCompressRange",
|
|
906
906
|
"gc",
|
|
@@ -1121,13 +1121,6 @@ function validateConfigTypes(config) {
|
|
|
1121
1121
|
actual: `${compress.nudgeFrequency} (will be clamped to 1)`
|
|
1122
1122
|
});
|
|
1123
1123
|
}
|
|
1124
|
-
if (compress.perMessageNudgeGrowthPercent !== void 0 && typeof compress.perMessageNudgeGrowthPercent !== "number") {
|
|
1125
|
-
errors.push({
|
|
1126
|
-
key: "compress.perMessageNudgeGrowthPercent",
|
|
1127
|
-
expected: "number",
|
|
1128
|
-
actual: typeof compress.perMessageNudgeGrowthPercent
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
1124
|
if (compress.iterationNudgeThreshold !== void 0 && typeof compress.iterationNudgeThreshold !== "number") {
|
|
1132
1125
|
errors.push({
|
|
1133
1126
|
key: "compress.iterationNudgeThreshold",
|
|
@@ -1163,20 +1156,6 @@ function validateConfigTypes(config) {
|
|
|
1163
1156
|
actual: typeof compress.protectUserMessages
|
|
1164
1157
|
});
|
|
1165
1158
|
}
|
|
1166
|
-
if (compress.maxSummaryLength !== void 0 && typeof compress.maxSummaryLength !== "number") {
|
|
1167
|
-
errors.push({
|
|
1168
|
-
key: "compress.maxSummaryLength",
|
|
1169
|
-
expected: "number",
|
|
1170
|
-
actual: typeof compress.maxSummaryLength
|
|
1171
|
-
});
|
|
1172
|
-
}
|
|
1173
|
-
if (typeof compress.maxSummaryLength === "number" && compress.maxSummaryLength < 1) {
|
|
1174
|
-
errors.push({
|
|
1175
|
-
key: "compress.maxSummaryLength",
|
|
1176
|
-
expected: "positive number (>= 1)",
|
|
1177
|
-
actual: `${compress.maxSummaryLength}`
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
1159
|
if (compress.maxSummaryLengthHard !== void 0 && typeof compress.maxSummaryLengthHard !== "number") {
|
|
1181
1160
|
errors.push({
|
|
1182
1161
|
key: "compress.maxSummaryLengthHard",
|
|
@@ -1191,13 +1170,6 @@ function validateConfigTypes(config) {
|
|
|
1191
1170
|
actual: `${compress.maxSummaryLengthHard}`
|
|
1192
1171
|
});
|
|
1193
1172
|
}
|
|
1194
|
-
if (typeof compress.maxSummaryLength === "number" && typeof compress.maxSummaryLengthHard === "number" && compress.maxSummaryLengthHard < compress.maxSummaryLength) {
|
|
1195
|
-
errors.push({
|
|
1196
|
-
key: "compress.maxSummaryLengthHard",
|
|
1197
|
-
expected: `>= maxSummaryLength (${compress.maxSummaryLength})`,
|
|
1198
|
-
actual: `${compress.maxSummaryLengthHard}`
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
1173
|
if (compress.minCompressRange !== void 0 && typeof compress.minCompressRange !== "number") {
|
|
1202
1174
|
errors.push({
|
|
1203
1175
|
key: "compress.minCompressRange",
|
|
@@ -1519,14 +1491,13 @@ var defaultConfig = {
|
|
|
1519
1491
|
maxContextLimit: "55%",
|
|
1520
1492
|
minContextLimit: "45%",
|
|
1521
1493
|
nudgeFrequency: 5,
|
|
1522
|
-
|
|
1494
|
+
minNudgeContextPercent: 15,
|
|
1523
1495
|
iterationNudgeThreshold: 15,
|
|
1524
1496
|
nudgeForce: "soft",
|
|
1525
1497
|
protectedTools: [...COMPRESS_DEFAULT_PROTECTED_TOOLS],
|
|
1526
1498
|
protectTags: false,
|
|
1527
1499
|
protectUserMessages: false,
|
|
1528
|
-
|
|
1529
|
-
maxSummaryLengthHard: 3e3,
|
|
1500
|
+
maxSummaryLengthHard: 4e3,
|
|
1530
1501
|
minCompressRange: 2e3
|
|
1531
1502
|
},
|
|
1532
1503
|
strategies: {
|
|
@@ -1674,13 +1645,13 @@ function mergeCompress(base, override) {
|
|
|
1674
1645
|
modelMaxLimits: override.modelMaxLimits ?? base.modelMaxLimits,
|
|
1675
1646
|
modelMinLimits: override.modelMinLimits ?? base.modelMinLimits,
|
|
1676
1647
|
nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
|
|
1677
|
-
|
|
1648
|
+
minNudgeContextPercent: override.minNudgeContextPercent ?? base.minNudgeContextPercent,
|
|
1649
|
+
nudgeGrowthTokens: override.nudgeGrowthTokens,
|
|
1678
1650
|
iterationNudgeThreshold: override.iterationNudgeThreshold ?? base.iterationNudgeThreshold,
|
|
1679
1651
|
nudgeForce: override.nudgeForce ?? base.nudgeForce,
|
|
1680
1652
|
protectedTools: [.../* @__PURE__ */ new Set([...base.protectedTools, ...override.protectedTools ?? []])],
|
|
1681
1653
|
protectTags: override.protectTags ?? base.protectTags,
|
|
1682
1654
|
protectUserMessages: override.protectUserMessages ?? base.protectUserMessages,
|
|
1683
|
-
maxSummaryLength: override.maxSummaryLength ?? base.maxSummaryLength,
|
|
1684
1655
|
maxSummaryLengthHard: override.maxSummaryLengthHard ?? base.maxSummaryLengthHard,
|
|
1685
1656
|
minCompressRange: override.minCompressRange ?? base.minCompressRange
|
|
1686
1657
|
};
|
|
@@ -3216,7 +3187,8 @@ function resetOnCompaction(state) {
|
|
|
3216
3187
|
turnNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3217
3188
|
iterationNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3218
3189
|
lastPerMessageNudgeTurn: 0,
|
|
3219
|
-
lastPerMessageNudgeTokens: 0
|
|
3190
|
+
lastPerMessageNudgeTokens: void 0,
|
|
3191
|
+
shouldInjectThisTurn: void 0
|
|
3220
3192
|
};
|
|
3221
3193
|
state.messageIds = {
|
|
3222
3194
|
byRawId: /* @__PURE__ */ new Map(),
|
|
@@ -3284,7 +3256,7 @@ async function saveSessionState(sessionState, logger, sessionName) {
|
|
|
3284
3256
|
turnNudgeAnchors: Array.from(sessionState.nudges.turnNudgeAnchors),
|
|
3285
3257
|
iterationNudgeAnchors: Array.from(sessionState.nudges.iterationNudgeAnchors),
|
|
3286
3258
|
lastPerMessageNudgeTurn: sessionState.nudges.lastPerMessageNudgeTurn ?? 0,
|
|
3287
|
-
lastPerMessageNudgeTokens: sessionState.nudges.lastPerMessageNudgeTokens
|
|
3259
|
+
lastPerMessageNudgeTokens: sessionState.nudges.lastPerMessageNudgeTokens
|
|
3288
3260
|
},
|
|
3289
3261
|
stats: sessionState.stats,
|
|
3290
3262
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3500,7 +3472,8 @@ function createSessionState() {
|
|
|
3500
3472
|
turnNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3501
3473
|
iterationNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3502
3474
|
lastPerMessageNudgeTurn: 0,
|
|
3503
|
-
lastPerMessageNudgeTokens: 0
|
|
3475
|
+
lastPerMessageNudgeTokens: void 0,
|
|
3476
|
+
shouldInjectThisTurn: void 0
|
|
3504
3477
|
},
|
|
3505
3478
|
stats: {
|
|
3506
3479
|
pruneTokenCounter: 0,
|
|
@@ -3539,7 +3512,8 @@ function resetSessionState(state) {
|
|
|
3539
3512
|
turnNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3540
3513
|
iterationNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3541
3514
|
lastPerMessageNudgeTurn: 0,
|
|
3542
|
-
lastPerMessageNudgeTokens: 0
|
|
3515
|
+
lastPerMessageNudgeTokens: void 0,
|
|
3516
|
+
shouldInjectThisTurn: void 0
|
|
3543
3517
|
};
|
|
3544
3518
|
state.stats = {
|
|
3545
3519
|
pruneTokenCounter: 0,
|
|
@@ -3585,7 +3559,7 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
3585
3559
|
persisted.nudges.iterationNudgeAnchors || []
|
|
3586
3560
|
);
|
|
3587
3561
|
state.nudges.lastPerMessageNudgeTurn = persisted.nudges.lastPerMessageNudgeTurn ?? 0;
|
|
3588
|
-
state.nudges.lastPerMessageNudgeTokens = persisted.nudges.lastPerMessageNudgeTokens
|
|
3562
|
+
state.nudges.lastPerMessageNudgeTokens = persisted.nudges.lastPerMessageNudgeTokens;
|
|
3589
3563
|
state.stats = {
|
|
3590
3564
|
pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0,
|
|
3591
3565
|
totalPruneTokens: persisted.stats?.totalPruneTokens || 0
|
|
@@ -3735,20 +3709,20 @@ function matchesGlob(inputPath, pattern) {
|
|
|
3735
3709
|
regex += "$";
|
|
3736
3710
|
return new RegExp(regex).test(input);
|
|
3737
3711
|
}
|
|
3738
|
-
function getFilePathsFromParameters(
|
|
3712
|
+
function getFilePathsFromParameters(tool6, parameters) {
|
|
3739
3713
|
if (typeof parameters !== "object" || parameters === null) {
|
|
3740
3714
|
return [];
|
|
3741
3715
|
}
|
|
3742
3716
|
const paths = [];
|
|
3743
3717
|
const params = parameters;
|
|
3744
|
-
if (
|
|
3718
|
+
if (tool6 === "apply_patch" && typeof params.patchText === "string") {
|
|
3745
3719
|
const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g;
|
|
3746
3720
|
let match;
|
|
3747
3721
|
while ((match = pathRegex.exec(params.patchText)) !== null) {
|
|
3748
3722
|
paths.push(match[1].trim());
|
|
3749
3723
|
}
|
|
3750
3724
|
}
|
|
3751
|
-
if (
|
|
3725
|
+
if (tool6 === "multiedit") {
|
|
3752
3726
|
if (typeof params.filePath === "string") {
|
|
3753
3727
|
paths.push(params.filePath);
|
|
3754
3728
|
}
|
|
@@ -3843,13 +3817,13 @@ var deduplicate = (state, logger, config, messages) => {
|
|
|
3843
3817
|
logger.debug(`Marked ${newPruneIds.length} duplicate tool calls for pruning`);
|
|
3844
3818
|
}
|
|
3845
3819
|
};
|
|
3846
|
-
function createToolSignature(
|
|
3820
|
+
function createToolSignature(tool6, parameters) {
|
|
3847
3821
|
if (!parameters) {
|
|
3848
|
-
return
|
|
3822
|
+
return tool6;
|
|
3849
3823
|
}
|
|
3850
3824
|
const normalized = normalizeParameters(parameters);
|
|
3851
3825
|
const sorted = sortObjectKeys(normalized);
|
|
3852
|
-
return `${
|
|
3826
|
+
return `${tool6}::${JSON.stringify(sorted)}`;
|
|
3853
3827
|
}
|
|
3854
3828
|
function normalizeParameters(params) {
|
|
3855
3829
|
if (typeof params !== "object" || params === null) return params;
|
|
@@ -3924,9 +3898,9 @@ var purgeErrors = (state, logger, config, messages) => {
|
|
|
3924
3898
|
};
|
|
3925
3899
|
|
|
3926
3900
|
// lib/ui/utils.ts
|
|
3927
|
-
function extractParameterKey(
|
|
3901
|
+
function extractParameterKey(tool6, parameters) {
|
|
3928
3902
|
if (!parameters) return "";
|
|
3929
|
-
if (
|
|
3903
|
+
if (tool6 === "read" && parameters.filePath) {
|
|
3930
3904
|
const offset = parameters.offset;
|
|
3931
3905
|
const limit = parameters.limit;
|
|
3932
3906
|
if (offset !== void 0 && limit !== void 0) {
|
|
@@ -3940,10 +3914,10 @@ function extractParameterKey(tool5, parameters) {
|
|
|
3940
3914
|
}
|
|
3941
3915
|
return parameters.filePath;
|
|
3942
3916
|
}
|
|
3943
|
-
if ((
|
|
3917
|
+
if ((tool6 === "write" || tool6 === "edit" || tool6 === "multiedit") && parameters.filePath) {
|
|
3944
3918
|
return parameters.filePath;
|
|
3945
3919
|
}
|
|
3946
|
-
if (
|
|
3920
|
+
if (tool6 === "apply_patch" && typeof parameters.patchText === "string") {
|
|
3947
3921
|
const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g;
|
|
3948
3922
|
const paths = [];
|
|
3949
3923
|
let match;
|
|
@@ -3960,51 +3934,51 @@ function extractParameterKey(tool5, parameters) {
|
|
|
3960
3934
|
}
|
|
3961
3935
|
return "patch";
|
|
3962
3936
|
}
|
|
3963
|
-
if (
|
|
3937
|
+
if (tool6 === "list") {
|
|
3964
3938
|
return parameters.path || "(current directory)";
|
|
3965
3939
|
}
|
|
3966
|
-
if (
|
|
3940
|
+
if (tool6 === "glob") {
|
|
3967
3941
|
if (parameters.pattern) {
|
|
3968
3942
|
const pathInfo = parameters.path ? ` in ${parameters.path}` : "";
|
|
3969
3943
|
return `"${parameters.pattern}"${pathInfo}`;
|
|
3970
3944
|
}
|
|
3971
3945
|
return "(unknown pattern)";
|
|
3972
3946
|
}
|
|
3973
|
-
if (
|
|
3947
|
+
if (tool6 === "grep") {
|
|
3974
3948
|
if (parameters.pattern) {
|
|
3975
3949
|
const pathInfo = parameters.path ? ` in ${parameters.path}` : "";
|
|
3976
3950
|
return `"${parameters.pattern}"${pathInfo}`;
|
|
3977
3951
|
}
|
|
3978
3952
|
return "(unknown pattern)";
|
|
3979
3953
|
}
|
|
3980
|
-
if (
|
|
3954
|
+
if (tool6 === "bash") {
|
|
3981
3955
|
if (parameters.description) return parameters.description;
|
|
3982
3956
|
if (parameters.command) {
|
|
3983
3957
|
return parameters.command.length > 50 ? parameters.command.substring(0, 50) + "..." : parameters.command;
|
|
3984
3958
|
}
|
|
3985
3959
|
}
|
|
3986
|
-
if (
|
|
3960
|
+
if (tool6 === "webfetch" && parameters.url) {
|
|
3987
3961
|
return parameters.url;
|
|
3988
3962
|
}
|
|
3989
|
-
if (
|
|
3963
|
+
if (tool6 === "websearch" && parameters.query) {
|
|
3990
3964
|
return `"${parameters.query}"`;
|
|
3991
3965
|
}
|
|
3992
|
-
if (
|
|
3966
|
+
if (tool6 === "codesearch" && parameters.query) {
|
|
3993
3967
|
return `"${parameters.query}"`;
|
|
3994
3968
|
}
|
|
3995
|
-
if (
|
|
3969
|
+
if (tool6 === "todowrite") {
|
|
3996
3970
|
return `${parameters.todos?.length || 0} todos`;
|
|
3997
3971
|
}
|
|
3998
|
-
if (
|
|
3972
|
+
if (tool6 === "todoread") {
|
|
3999
3973
|
return "read todo list";
|
|
4000
3974
|
}
|
|
4001
|
-
if (
|
|
3975
|
+
if (tool6 === "task" && parameters.description) {
|
|
4002
3976
|
return parameters.description;
|
|
4003
3977
|
}
|
|
4004
|
-
if (
|
|
3978
|
+
if (tool6 === "skill" && parameters.name) {
|
|
4005
3979
|
return parameters.name;
|
|
4006
3980
|
}
|
|
4007
|
-
if (
|
|
3981
|
+
if (tool6 === "lsp") {
|
|
4008
3982
|
const op = parameters.operation || "lsp";
|
|
4009
3983
|
const path = parameters.filePath || "";
|
|
4010
3984
|
const line = parameters.line;
|
|
@@ -4017,7 +3991,7 @@ function extractParameterKey(tool5, parameters) {
|
|
|
4017
3991
|
}
|
|
4018
3992
|
return op;
|
|
4019
3993
|
}
|
|
4020
|
-
if (
|
|
3994
|
+
if (tool6 === "question") {
|
|
4021
3995
|
const questions = parameters.questions;
|
|
4022
3996
|
if (Array.isArray(questions) && questions.length > 0) {
|
|
4023
3997
|
const headers = questions.map((q) => q.header || "").filter(Boolean).slice(0, 3);
|
|
@@ -4037,6 +4011,13 @@ function extractParameterKey(tool5, parameters) {
|
|
|
4037
4011
|
}
|
|
4038
4012
|
return paramStr.substring(0, 50);
|
|
4039
4013
|
}
|
|
4014
|
+
function formatAge(createdAt) {
|
|
4015
|
+
const elapsed = Date.now() - createdAt;
|
|
4016
|
+
if (elapsed < 6e4) return "just now";
|
|
4017
|
+
if (elapsed < 36e5) return `${Math.floor(elapsed / 6e4)}m ago`;
|
|
4018
|
+
if (elapsed < 864e5) return `${Math.floor(elapsed / 36e5)}h ago`;
|
|
4019
|
+
return `${Math.floor(elapsed / 864e5)}d ago`;
|
|
4020
|
+
}
|
|
4040
4021
|
function formatTokenCount(tokens, compact) {
|
|
4041
4022
|
const suffix = compact ? "" : " tokens";
|
|
4042
4023
|
if (tokens >= 1e3) {
|
|
@@ -4203,7 +4184,12 @@ function formatCompressionMetrics(removedTokens, summaryTokens) {
|
|
|
4203
4184
|
}
|
|
4204
4185
|
return metrics.join(", ");
|
|
4205
4186
|
}
|
|
4206
|
-
|
|
4187
|
+
function formatContextTransition(tokensBefore, tokensAfter) {
|
|
4188
|
+
const beforeStr = formatTokenCount(tokensBefore, true);
|
|
4189
|
+
const afterStr = formatTokenCount(tokensAfter, true);
|
|
4190
|
+
return `Context ${beforeStr}\u2192${afterStr}`;
|
|
4191
|
+
}
|
|
4192
|
+
async function sendCompressNotification(client, logger, config, state, sessionId, entries, batchTopic, sessionMessageIds, params, contextTokensBefore) {
|
|
4207
4193
|
if (config.pruneNotification === "off") {
|
|
4208
4194
|
return false;
|
|
4209
4195
|
}
|
|
@@ -4251,9 +4237,14 @@ async function sendCompressNotification(client, logger, config, state, sessionId
|
|
|
4251
4237
|
}
|
|
4252
4238
|
}
|
|
4253
4239
|
const topic = batchTopic ?? (entries.length === 1 ? state.prune.messages.blocksById.get(entries[0]?.blockId ?? -1)?.topic ?? "(unknown topic)" : "(unknown topic)");
|
|
4254
|
-
const
|
|
4255
|
-
|
|
4256
|
-
|
|
4240
|
+
const contextTokensAfter = Math.max(
|
|
4241
|
+
0,
|
|
4242
|
+
contextTokensBefore - compressedTokens + summaryTokens
|
|
4243
|
+
);
|
|
4244
|
+
const notificationHeader = `\u25A3 ACP | ${formatContextTransition(
|
|
4245
|
+
contextTokensBefore,
|
|
4246
|
+
contextTokensAfter
|
|
4247
|
+
)}`;
|
|
4257
4248
|
if (config.pruneNotification === "minimal") {
|
|
4258
4249
|
message = `${notificationHeader} \u2014 ${compressionLabel}`;
|
|
4259
4250
|
} else {
|
|
@@ -4385,6 +4376,7 @@ async function finalizeSession(ctx, toolCtx, rawMessages, entries, batchTopic) {
|
|
|
4385
4376
|
await saveSessionState(ctx.state, ctx.logger);
|
|
4386
4377
|
const params = getCurrentParams(ctx.state, rawMessages, ctx.logger);
|
|
4387
4378
|
const sessionMessageIds = rawMessages.filter((msg) => !isIgnoredUserMessage(msg)).map((msg) => msg.info.id);
|
|
4379
|
+
const contextTokensBefore = getCurrentTokenUsage(ctx.state, rawMessages);
|
|
4388
4380
|
await sendCompressNotification(
|
|
4389
4381
|
ctx.client,
|
|
4390
4382
|
ctx.logger,
|
|
@@ -4394,7 +4386,8 @@ async function finalizeSession(ctx, toolCtx, rawMessages, entries, batchTopic) {
|
|
|
4394
4386
|
entries,
|
|
4395
4387
|
batchTopic,
|
|
4396
4388
|
sessionMessageIds,
|
|
4397
|
-
params
|
|
4389
|
+
params,
|
|
4390
|
+
contextTokensBefore
|
|
4398
4391
|
);
|
|
4399
4392
|
}
|
|
4400
4393
|
|
|
@@ -4596,7 +4589,7 @@ ${output}`);
|
|
|
4596
4589
|
}
|
|
4597
4590
|
|
|
4598
4591
|
// lib/compress/message.ts
|
|
4599
|
-
function buildSchema(
|
|
4592
|
+
function buildSchema() {
|
|
4600
4593
|
return {
|
|
4601
4594
|
topic: tool2.schema.string().describe(
|
|
4602
4595
|
"Short label (3-5 words) for the overall batch - e.g., 'Closed Research Notes'"
|
|
@@ -4606,10 +4599,11 @@ function buildSchema(maxSummaryLength) {
|
|
|
4606
4599
|
messageId: tool2.schema.string().describe("Raw message ID to compress (e.g. m00001)"),
|
|
4607
4600
|
topic: tool2.schema.string().describe("Short label (3-5 words) for this one message summary"),
|
|
4608
4601
|
summary: tool2.schema.string().describe(
|
|
4609
|
-
|
|
4602
|
+
"Complete technical summary replacing that one message. Keep only essential details (conclusions, file paths, decisions, exact values, etc.)."
|
|
4610
4603
|
)
|
|
4611
4604
|
})
|
|
4612
|
-
).describe("Batch of individual message summaries to create in one tool call")
|
|
4605
|
+
).describe("Batch of individual message summaries to create in one tool call"),
|
|
4606
|
+
summaryMaxChars: tool2.schema.number().optional().describe("Override max summary length (default max: 4000 chars). Use when content is important and needs more detail \u2014 don't lose critical info just to fit the limit.")
|
|
4613
4607
|
};
|
|
4614
4608
|
}
|
|
4615
4609
|
function createCompressMessageTool(ctx) {
|
|
@@ -4617,15 +4611,18 @@ function createCompressMessageTool(ctx) {
|
|
|
4617
4611
|
const runtimePrompts = ctx.prompts.getRuntimePrompts();
|
|
4618
4612
|
return tool2({
|
|
4619
4613
|
description: runtimePrompts.compressMessage + MESSAGE_FORMAT_EXTENSION,
|
|
4620
|
-
args: buildSchema(
|
|
4614
|
+
args: buildSchema(),
|
|
4621
4615
|
async execute(args, toolCtx) {
|
|
4622
4616
|
const input = args;
|
|
4623
4617
|
validateArgs(input);
|
|
4624
|
-
const
|
|
4618
|
+
const maxLen = args.summaryMaxChars ?? ctx.config.compress.maxSummaryLengthHard;
|
|
4625
4619
|
for (const entry of input.content) {
|
|
4626
|
-
if (entry.summary.length >
|
|
4620
|
+
if (entry.summary.length > maxLen) {
|
|
4627
4621
|
throw new Error(
|
|
4628
|
-
`Summary too long (${entry.summary.length} chars
|
|
4622
|
+
`Summary too long (${entry.summary.length} chars, max ${maxLen}).
|
|
4623
|
+
1. If this summary is nearly the same size as the original content, it may not be worth compressing \u2014 skip it.
|
|
4624
|
+
2. Strip noise (failed attempts, verbose outputs) but keep project-critical details (file paths, decisions, exact values).
|
|
4625
|
+
3. For important content needing detail, pass summaryMaxChars to increase the limit \u2014 don't lose critical info just to fit. Example: add "summaryMaxChars": 6000 to the tool call args.`
|
|
4629
4626
|
);
|
|
4630
4627
|
}
|
|
4631
4628
|
}
|
|
@@ -4870,7 +4867,7 @@ function appendMissingBlockSummaries(summary, _missingBlockIds, _summaryByBlockI
|
|
|
4870
4867
|
}
|
|
4871
4868
|
|
|
4872
4869
|
// lib/compress/range.ts
|
|
4873
|
-
function buildSchema2(
|
|
4870
|
+
function buildSchema2() {
|
|
4874
4871
|
return {
|
|
4875
4872
|
topic: tool3.schema.string().describe("Short label (3-5 words) for display - e.g., 'Auth System Exploration'"),
|
|
4876
4873
|
content: tool3.schema.array(
|
|
@@ -4880,12 +4877,13 @@ function buildSchema2(maxSummaryLength) {
|
|
|
4880
4877
|
),
|
|
4881
4878
|
endId: tool3.schema.string().describe("Message or block ID marking the end of range (e.g. m00012, b5)"),
|
|
4882
4879
|
summary: tool3.schema.string().describe(
|
|
4883
|
-
|
|
4880
|
+
"Complete technical summary replacing all content in range. Keep only essential details (conclusions, file paths, decisions, exact values, etc.)."
|
|
4884
4881
|
)
|
|
4885
4882
|
})
|
|
4886
4883
|
).describe(
|
|
4887
4884
|
"One or more ranges to compress, each with start/end boundaries and a summary"
|
|
4888
|
-
)
|
|
4885
|
+
),
|
|
4886
|
+
summaryMaxChars: tool3.schema.number().optional().describe("Override max summary length (default max: 4000 chars). Use when content is important and needs more detail \u2014 don't lose critical info just to fit the limit.")
|
|
4889
4887
|
};
|
|
4890
4888
|
}
|
|
4891
4889
|
function createCompressRangeTool(ctx) {
|
|
@@ -4893,15 +4891,18 @@ function createCompressRangeTool(ctx) {
|
|
|
4893
4891
|
const runtimePrompts = ctx.prompts.getRuntimePrompts();
|
|
4894
4892
|
return tool3({
|
|
4895
4893
|
description: runtimePrompts.compressRange + RANGE_FORMAT_EXTENSION,
|
|
4896
|
-
args: buildSchema2(
|
|
4894
|
+
args: buildSchema2(),
|
|
4897
4895
|
async execute(args, toolCtx) {
|
|
4898
4896
|
const input = args;
|
|
4899
4897
|
validateArgs2(input);
|
|
4900
|
-
const
|
|
4898
|
+
const maxLen = args.summaryMaxChars ?? ctx.config.compress.maxSummaryLengthHard;
|
|
4901
4899
|
for (const entry of input.content) {
|
|
4902
|
-
if (entry.summary.length >
|
|
4900
|
+
if (entry.summary.length > maxLen) {
|
|
4903
4901
|
throw new Error(
|
|
4904
|
-
`Summary too long (${entry.summary.length} chars
|
|
4902
|
+
`Summary too long (${entry.summary.length} chars, max ${maxLen}).
|
|
4903
|
+
1. If this summary is nearly the same size as the original content, it may not be worth compressing \u2014 skip it.
|
|
4904
|
+
2. Strip noise (failed attempts, verbose outputs) but keep project-critical details (file paths, decisions, exact values).
|
|
4905
|
+
3. For important content needing detail, pass summaryMaxChars to increase the limit \u2014 don't lose critical info just to fit. Example: add "summaryMaxChars": 6000 to the tool call args.`
|
|
4905
4906
|
);
|
|
4906
4907
|
}
|
|
4907
4908
|
}
|
|
@@ -5067,10 +5068,10 @@ var MERGED_SUMMARY_FOOTER = `
|
|
|
5067
5068
|
[End ACP compressed context summary]
|
|
5068
5069
|
|
|
5069
5070
|
`;
|
|
5070
|
-
var DCP_BLOCK_ID_TAG_REGEX = /(<dcp-message-id(?=[\s>])[^>]*>)b\d+(<\/dcp-message-id>)/g;
|
|
5071
|
-
var DCP_MESSAGE_REF_TAG_REGEX = /<dcp-message-id>m\d+<\/dcp-message-id>/g;
|
|
5072
|
-
var DCP_PAIRED_TAG_REGEX = /<dcp[^>]*>[\s\S]*?<\/dcp[^>]*>/gi;
|
|
5073
|
-
var DCP_UNPAIRED_TAG_REGEX = /<\/?dcp[^>]*>/gi;
|
|
5071
|
+
var DCP_BLOCK_ID_TAG_REGEX = /(<dcp-message-id(?=[\s>])[^>]*>)b\d+(<\/(?:dcp|acp)-message-id>)/g;
|
|
5072
|
+
var DCP_MESSAGE_REF_TAG_REGEX = /<dcp-message-id>m\d+<\/(?:dcp|acp)-message-id>/g;
|
|
5073
|
+
var DCP_PAIRED_TAG_REGEX = /<dcp[^>]*>[\s\S]*?<\/(?:dcp|acp)[^>]*>/gi;
|
|
5074
|
+
var DCP_UNPAIRED_TAG_REGEX = /<\/?(?:dcp|acp)[^>]*>/gi;
|
|
5074
5075
|
var generateStableId = (prefix, seed) => {
|
|
5075
5076
|
const hash = createHash("sha256").update(seed).digest("hex").slice(0, SUMMARY_ID_HASH_LENGTH);
|
|
5076
5077
|
return `${prefix}_${hash}`;
|
|
@@ -5508,8 +5509,8 @@ var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agent
|
|
|
5508
5509
|
agentName ? hostPermissions.agents[agentName] : void 0
|
|
5509
5510
|
) ? "deny" : basePermission;
|
|
5510
5511
|
};
|
|
5511
|
-
var hasExplicitToolPermission = (permissionConfig,
|
|
5512
|
-
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig,
|
|
5512
|
+
var hasExplicitToolPermission = (permissionConfig, tool6) => {
|
|
5513
|
+
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
|
|
5513
5514
|
};
|
|
5514
5515
|
|
|
5515
5516
|
// lib/compress-permission.ts
|
|
@@ -5528,24 +5529,15 @@ var syncCompressPermissionState = (state, config, hostPermissions, messages) =>
|
|
|
5528
5529
|
// lib/prompts/extensions/nudge.ts
|
|
5529
5530
|
function buildCompressedBlockGuidance(state, gcConfig, context) {
|
|
5530
5531
|
const activeBlockIds = Array.from(state.prune.messages.activeBlockIds).filter((id) => Number.isInteger(id) && id > 0).sort((a, b) => a - b);
|
|
5531
|
-
const
|
|
5532
|
-
const
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
const recent = refs.slice(-20).join(", ");
|
|
5538
|
-
blockList = `${recent} (+${blockCount - 20} older, use decompress to access by ID)`;
|
|
5539
|
-
}
|
|
5540
|
-
const includeHint = context?.includeHint ?? true;
|
|
5532
|
+
const blockCount = activeBlockIds.length;
|
|
5533
|
+
const blocksForStats = activeBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0 && b.active);
|
|
5534
|
+
const totalSummaryTokens = blocksForStats.reduce((s, b) => s + (b.summaryTokens ?? 0), 0);
|
|
5535
|
+
const totalSummaryDisplay = totalSummaryTokens >= 1e3 ? `${(totalSummaryTokens / 1e3).toFixed(1)}K` : String(totalSummaryTokens);
|
|
5536
|
+
const lastBlock = blocksForStats.length > 0 ? blocksForStats.reduce((latest, b) => b.createdAt > latest.createdAt ? b : latest) : null;
|
|
5537
|
+
const ageStr = lastBlock ? formatAge(lastBlock.createdAt) : "never";
|
|
5541
5538
|
const lines = [
|
|
5542
|
-
|
|
5543
|
-
`- Active compressed blocks: ${blockCount} (${blockList})`,
|
|
5544
|
-
"- System auto-detects blocks in range \u2014 no need to manually list (bN) placeholders. Just write a short prose summary."
|
|
5539
|
+
`- Compressed blocks: ${blockCount} (${totalSummaryDisplay} summary, last ${ageStr}). Use acp_status for details.`
|
|
5545
5540
|
];
|
|
5546
|
-
if (includeHint) {
|
|
5547
|
-
lines.push("- \u{1F4A1} When you've finished using tool outputs, compress them \u2014 you can decompress later if needed. Lean context improves accuracy.");
|
|
5548
|
-
}
|
|
5549
5541
|
if (blockCount > 50) {
|
|
5550
5542
|
const oldBlockIds = activeBlockIds.slice(0, Math.max(0, blockCount - 20));
|
|
5551
5543
|
const allOldBlocks = oldBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0);
|
|
@@ -5574,8 +5566,6 @@ function buildCompressedBlockGuidance(state, gcConfig, context) {
|
|
|
5574
5566
|
lines.push(...targets);
|
|
5575
5567
|
lines.push(` System auto-detects blocks in range \u2014 no need to manually list (bN) placeholders. Just write a short prose summary.`);
|
|
5576
5568
|
}
|
|
5577
|
-
} else {
|
|
5578
|
-
lines.push(`- \u{1F500} You have ${blockCount} blocks \u2014 use compress to consolidate adjacent same-topic blocks.`);
|
|
5579
5569
|
}
|
|
5580
5570
|
}
|
|
5581
5571
|
const usageRatio = context?.currentTokens && context?.modelContextLimit ? context.currentTokens / context.modelContextLimit : 0;
|
|
@@ -5736,8 +5726,8 @@ function getModelInfo(messages) {
|
|
|
5736
5726
|
}
|
|
5737
5727
|
const userInfo = lastUserMessage.info;
|
|
5738
5728
|
return {
|
|
5739
|
-
providerId: userInfo.model
|
|
5740
|
-
modelId: userInfo.model
|
|
5729
|
+
providerId: userInfo.model?.providerID,
|
|
5730
|
+
modelId: userInfo.model?.modelID
|
|
5741
5731
|
};
|
|
5742
5732
|
}
|
|
5743
5733
|
function resolveContextTokenLimit(config, state, providerId, modelId, threshold) {
|
|
@@ -5806,6 +5796,29 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
|
5806
5796
|
modelContextLimit: state.modelContextLimit
|
|
5807
5797
|
};
|
|
5808
5798
|
}
|
|
5799
|
+
function computeShouldNudge(params) {
|
|
5800
|
+
const { currentTokens, modelContextLimit, overMinLimit, overMaxLimit } = params;
|
|
5801
|
+
const contextPct = modelContextLimit && currentTokens ? currentTokens / modelContextLimit * 100 : 0;
|
|
5802
|
+
const lastNudgeTokens = params.lastNudgeTokens;
|
|
5803
|
+
const growthSinceLastNudge = (currentTokens ?? 0) - (lastNudgeTokens ?? 0);
|
|
5804
|
+
const frequencyTriggered = lastNudgeTokens === void 0 || growthSinceLastNudge >= params.nudgeGrowthTokens || overMaxLimit;
|
|
5805
|
+
const shouldNudge = contextPct >= params.minNudgeContextPercent && frequencyTriggered;
|
|
5806
|
+
if (!shouldNudge) {
|
|
5807
|
+
return { shouldNudge: false, tipsVariant: null };
|
|
5808
|
+
}
|
|
5809
|
+
const tipsVariant = overMaxLimit ? "maxLimit" : overMinLimit ? "minLimit" : "normal";
|
|
5810
|
+
return { shouldNudge: true, tipsVariant };
|
|
5811
|
+
}
|
|
5812
|
+
var NUDGE_GROWTH_FLOOR = 6e3;
|
|
5813
|
+
var NUDGE_GROWTH_CAP = 5e4;
|
|
5814
|
+
var NUDGE_GROWTH_RATIO = 0.05;
|
|
5815
|
+
function resolveAdaptiveNudgeGrowth(modelContextLimit) {
|
|
5816
|
+
if (!modelContextLimit || modelContextLimit <= 0) return NUDGE_GROWTH_FLOOR;
|
|
5817
|
+
return Math.min(
|
|
5818
|
+
NUDGE_GROWTH_CAP,
|
|
5819
|
+
Math.max(NUDGE_GROWTH_FLOOR, Math.round(modelContextLimit * NUDGE_GROWTH_RATIO))
|
|
5820
|
+
);
|
|
5821
|
+
}
|
|
5809
5822
|
function addAnchor(anchorMessageIds, anchorMessageId, anchorMessageIndex, messages, interval) {
|
|
5810
5823
|
if (anchorMessageIndex < 0) {
|
|
5811
5824
|
return false;
|
|
@@ -5911,45 +5924,17 @@ function applyMessageModeAnchoredNudge(anchorMessageIds, messages, baseNudgeText
|
|
|
5911
5924
|
injectAnchoredNudge(message, nudgeText);
|
|
5912
5925
|
}
|
|
5913
5926
|
}
|
|
5914
|
-
function
|
|
5915
|
-
if (threshold === void 0) return void 0;
|
|
5916
|
-
if (typeof threshold === "number") {
|
|
5917
|
-
if (!modelContextLimit) return void 0;
|
|
5918
|
-
return threshold / modelContextLimit * 100;
|
|
5919
|
-
}
|
|
5920
|
-
const parsed = parseFloat(threshold);
|
|
5921
|
-
return isNaN(parsed) ? void 0 : parsed;
|
|
5922
|
-
}
|
|
5923
|
-
function buildContextUsageGuidance(config, currentTokens, modelContextLimit, minimal = false) {
|
|
5927
|
+
function buildContextUsageGuidance(config, currentTokens, modelContextLimit) {
|
|
5924
5928
|
if (currentTokens === void 0 || modelContextLimit === void 0 || modelContextLimit === 0) {
|
|
5925
5929
|
return "";
|
|
5926
5930
|
}
|
|
5927
|
-
const pct = currentTokens / modelContextLimit * 100;
|
|
5928
|
-
const percentage = pct.toFixed(1);
|
|
5929
5931
|
const formatK = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
5930
|
-
const minPct = resolveThresholdPercent(config.compress.minContextLimit, modelContextLimit) ?? 45;
|
|
5931
|
-
const maxPct = resolveThresholdPercent(config.compress.maxContextLimit, modelContextLimit) ?? 55;
|
|
5932
|
-
const base = `Context usage: ${formatK(currentTokens)} / ${formatK(modelContextLimit)} tokens (${percentage}%).`;
|
|
5933
|
-
if (minimal) {
|
|
5934
|
-
return `
|
|
5935
|
-
|
|
5936
|
-
${base}`;
|
|
5937
|
-
}
|
|
5938
|
-
let guidance;
|
|
5939
|
-
if (pct < minPct) {
|
|
5940
|
-
guidance = " \u{1F4A1} Be frugal with context. If any visible tool output exceeds 5000 characters and you've finished reading it, compress it into a summary now \u2014 don't keep large outputs 'just in case'. You can decompress later if needed.";
|
|
5941
|
-
} else if (pct < maxPct) {
|
|
5942
|
-
guidance = " \u26A0\uFE0F Context is growing \u2014 compress completed sections and high-token waste now.";
|
|
5943
|
-
} else {
|
|
5944
|
-
guidance = " \u{1F525} Context is high \u2014 compress aggressively, preserve only what is essential.";
|
|
5945
|
-
}
|
|
5946
5932
|
return `
|
|
5947
5933
|
|
|
5948
|
-
${
|
|
5934
|
+
Context: ${formatK(currentTokens)} tokens.
|
|
5935
|
+
All compression serves the primary task, but be frugal. Context capacity is precious \u2014 compress waste promptly. Save context by compressing consumed outputs, not by avoiding tools. Compress by need, not by percentage.`;
|
|
5949
5936
|
}
|
|
5950
5937
|
function applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage) {
|
|
5951
|
-
const contextUsageInfo = buildContextUsageGuidance(config, currentTokens, modelContextLimit);
|
|
5952
|
-
const contextLimitNudgeWithUsage = prompts.contextLimitNudge + contextUsageInfo;
|
|
5953
5938
|
const turnNudgeAnchors = collectTurnNudgeAnchors2(state, config, messages);
|
|
5954
5939
|
if (suffixMessage) {
|
|
5955
5940
|
const nudgeParts = [];
|
|
@@ -5957,7 +5942,7 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
5957
5942
|
if (state.nudges.contextLimitAnchors.size > 0) {
|
|
5958
5943
|
for (const { index } of collectAnchoredMessages(state.nudges.contextLimitAnchors, messages)) {
|
|
5959
5944
|
const guidance = buildMessagePriorityGuidance(messages, compressionPriorities, index, MESSAGE_MODE_NUDGE_PRIORITY);
|
|
5960
|
-
nudgeParts.push(appendGuidanceToDcpTag(
|
|
5945
|
+
nudgeParts.push(appendGuidanceToDcpTag(prompts.contextLimitNudge, guidance));
|
|
5961
5946
|
}
|
|
5962
5947
|
}
|
|
5963
5948
|
if (turnNudgeAnchors.size > 0) {
|
|
@@ -5974,7 +5959,7 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
5974
5959
|
}
|
|
5975
5960
|
} else {
|
|
5976
5961
|
if (state.nudges.contextLimitAnchors.size > 0) {
|
|
5977
|
-
nudgeParts.push(
|
|
5962
|
+
nudgeParts.push(prompts.contextLimitNudge);
|
|
5978
5963
|
}
|
|
5979
5964
|
if (turnNudgeAnchors.size > 0) {
|
|
5980
5965
|
nudgeParts.push(prompts.turnNudge);
|
|
@@ -5993,7 +5978,7 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
5993
5978
|
applyMessageModeAnchoredNudge(
|
|
5994
5979
|
state.nudges.contextLimitAnchors,
|
|
5995
5980
|
messages,
|
|
5996
|
-
|
|
5981
|
+
prompts.contextLimitNudge,
|
|
5997
5982
|
compressionPriorities
|
|
5998
5983
|
);
|
|
5999
5984
|
applyMessageModeAnchoredNudge(
|
|
@@ -6013,7 +5998,7 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
6013
5998
|
applyRangeModeAnchoredNudge(
|
|
6014
5999
|
state.nudges.contextLimitAnchors,
|
|
6015
6000
|
messages,
|
|
6016
|
-
|
|
6001
|
+
prompts.contextLimitNudge,
|
|
6017
6002
|
""
|
|
6018
6003
|
);
|
|
6019
6004
|
applyRangeModeAnchoredNudge(
|
|
@@ -6039,18 +6024,6 @@ function createSuffixMessage(messages) {
|
|
|
6039
6024
|
messages.push(synthetic);
|
|
6040
6025
|
return synthetic;
|
|
6041
6026
|
}
|
|
6042
|
-
function shouldInjectPerMessageNudge(state, config, currentTokens, modelContextLimit) {
|
|
6043
|
-
const turn = state.currentTurn ?? 0;
|
|
6044
|
-
const lastTurn = state.nudges.lastPerMessageNudgeTurn ?? 0;
|
|
6045
|
-
const turnsSinceLast = turn - lastTurn;
|
|
6046
|
-
const tokens = currentTokens ?? 0;
|
|
6047
|
-
const lastTokens = state.nudges.lastPerMessageNudgeTokens ?? 0;
|
|
6048
|
-
const tokenGrowth = tokens - lastTokens;
|
|
6049
|
-
const tokenGrowthPercent = modelContextLimit ? tokenGrowth / modelContextLimit * 100 : 0;
|
|
6050
|
-
const frequency = config.compress.nudgeFrequency ?? 5;
|
|
6051
|
-
const growthThreshold = config.compress.perMessageNudgeGrowthPercent ?? 3;
|
|
6052
|
-
return turnsSinceLast >= frequency || tokenGrowthPercent >= growthThreshold;
|
|
6053
|
-
}
|
|
6054
6027
|
var injectCompressNudges = (state, config, logger, messages, prompts, compressionPriorities) => {
|
|
6055
6028
|
if (compressPermission(state, config) === "deny") {
|
|
6056
6029
|
return;
|
|
@@ -6060,15 +6033,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6060
6033
|
}
|
|
6061
6034
|
const lastMessage = findLastNonIgnoredMessage(messages);
|
|
6062
6035
|
const lastAssistantMessage = messages.findLast((message) => message.info.role === "assistant");
|
|
6063
|
-
if (lastAssistantMessage && messageHasCompress(lastAssistantMessage)) {
|
|
6064
|
-
state.nudges.contextLimitAnchors.clear();
|
|
6065
|
-
state.nudges.turnNudgeAnchors.clear();
|
|
6066
|
-
state.nudges.iterationNudgeAnchors.clear();
|
|
6067
|
-
void saveSessionState(state, logger);
|
|
6068
|
-
return;
|
|
6069
|
-
}
|
|
6070
6036
|
const { providerId, modelId } = getModelInfo(messages);
|
|
6071
|
-
let anchorsChanged = false;
|
|
6072
6037
|
const { overMaxLimit, overMinLimit, currentTokens, modelContextLimit } = isContextOverLimits(
|
|
6073
6038
|
config,
|
|
6074
6039
|
state,
|
|
@@ -6076,6 +6041,15 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6076
6041
|
modelId,
|
|
6077
6042
|
messages
|
|
6078
6043
|
);
|
|
6044
|
+
if (lastAssistantMessage && messageHasCompress(lastAssistantMessage)) {
|
|
6045
|
+
state.nudges.contextLimitAnchors.clear();
|
|
6046
|
+
state.nudges.turnNudgeAnchors.clear();
|
|
6047
|
+
state.nudges.iterationNudgeAnchors.clear();
|
|
6048
|
+
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
6049
|
+
void saveSessionState(state, logger);
|
|
6050
|
+
return;
|
|
6051
|
+
}
|
|
6052
|
+
let anchorsChanged = false;
|
|
6079
6053
|
if (!overMinLimit) {
|
|
6080
6054
|
const hadTurnAnchors = state.nudges.turnNudgeAnchors.size > 0;
|
|
6081
6055
|
const hadIterationAnchors = state.nudges.iterationNudgeAnchors.size > 0;
|
|
@@ -6135,35 +6109,59 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6135
6109
|
}
|
|
6136
6110
|
const suffixMessage = createSuffixMessage(messages);
|
|
6137
6111
|
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
6138
|
-
const
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6112
|
+
const decision = computeShouldNudge({
|
|
6113
|
+
currentTokens,
|
|
6114
|
+
modelContextLimit,
|
|
6115
|
+
overMinLimit,
|
|
6116
|
+
overMaxLimit,
|
|
6117
|
+
lastNudgeTokens: state.nudges.lastPerMessageNudgeTokens,
|
|
6118
|
+
minNudgeContextPercent: config.compress?.minNudgeContextPercent ?? 15,
|
|
6119
|
+
nudgeGrowthTokens: config.compress?.nudgeGrowthTokens ?? resolveAdaptiveNudgeGrowth(modelContextLimit)
|
|
6120
|
+
});
|
|
6121
|
+
state.nudges.shouldInjectThisTurn = decision.shouldNudge;
|
|
6122
|
+
let tipsText = null;
|
|
6123
|
+
if (decision.shouldNudge) {
|
|
6124
|
+
injectContextUsage(suffixMessage, config, currentTokens, modelContextLimit);
|
|
6125
|
+
if (decision.tipsVariant === "maxLimit") {
|
|
6126
|
+
tipsText = '\n\n\u26A0\uFE0F Context limit reached \u2014 compress now. Prioritize consumed tool outputs.\n\n{ "topic": "...", "content": [{ "startId": "<ID>", "endId": "<ID>", "summary": "..." }] }\n\nOnly use IDs from visible messages above. Compress older work first.';
|
|
6127
|
+
} else if (decision.tipsVariant === "minLimit") {
|
|
6128
|
+
tipsText = "\n\n\u26A0\uFE0F Context is growing \u2014 consider compressing older work. Tools: compress, decompress, search_context.";
|
|
6129
|
+
} else {
|
|
6130
|
+
tipsText = "\n\n\u{1F4A1} Tools: compress, decompress, search_context.";
|
|
6152
6131
|
}
|
|
6153
|
-
|
|
6154
|
-
if (shouldNudge) {
|
|
6132
|
+
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
6155
6133
|
state.nudges.lastPerMessageNudgeTurn = state.currentTurn ?? 0;
|
|
6156
|
-
|
|
6134
|
+
if (config.compress.mode !== "message") {
|
|
6135
|
+
const visibleMessageIds = new Set(
|
|
6136
|
+
messages.map((message) => message.info.id)
|
|
6137
|
+
);
|
|
6138
|
+
const blockGuidance = buildCompressedBlockGuidance(state, config.gc, {
|
|
6139
|
+
currentTokens,
|
|
6140
|
+
modelContextLimit,
|
|
6141
|
+
includeHint: tipsText !== null,
|
|
6142
|
+
visibleMessageIds
|
|
6143
|
+
});
|
|
6144
|
+
if (blockGuidance.trim() && suffixMessage) {
|
|
6145
|
+
appendToLastTextPart(suffixMessage, "\n\n" + blockGuidance);
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
if (tipsText && suffixMessage) {
|
|
6149
|
+
appendToLastTextPart(suffixMessage, tipsText);
|
|
6150
|
+
}
|
|
6151
|
+
injectVisibleIdRange(state, messages, suffixMessage);
|
|
6152
|
+
}
|
|
6153
|
+
if (suffixMessage) {
|
|
6154
|
+
appendToLastTextPart(suffixMessage, "\n");
|
|
6157
6155
|
}
|
|
6158
|
-
injectVisibleIdRange(state, messages, suffixMessage);
|
|
6159
6156
|
if (anchorsChanged) {
|
|
6160
6157
|
void saveSessionState(state, logger);
|
|
6161
6158
|
}
|
|
6162
6159
|
};
|
|
6163
|
-
function injectContextUsage(target, config, currentTokens, modelContextLimit
|
|
6160
|
+
function injectContextUsage(target, config, currentTokens, modelContextLimit) {
|
|
6164
6161
|
if (!target) return;
|
|
6165
|
-
const
|
|
6166
|
-
if (!
|
|
6162
|
+
const rawUsage = buildContextUsageGuidance(config, currentTokens, modelContextLimit);
|
|
6163
|
+
if (!rawUsage) return;
|
|
6164
|
+
const usageTag = rawUsage;
|
|
6167
6165
|
for (const part of target.parts) {
|
|
6168
6166
|
if (part.type === "text") {
|
|
6169
6167
|
appendToTextPart(part, usageTag);
|
|
@@ -6187,7 +6185,7 @@ function injectVisibleIdRange(state, messages, target) {
|
|
|
6187
6185
|
const last = visibleRefs[visibleRefs.length - 1];
|
|
6188
6186
|
const rangeTag = `
|
|
6189
6187
|
|
|
6190
|
-
[Visible
|
|
6188
|
+
[Visible messages: ${first} to ${last} (${visibleRefs.length} messages)]`;
|
|
6191
6189
|
for (const part of target.parts) {
|
|
6192
6190
|
if (part.type === "text") {
|
|
6193
6191
|
appendToTextPart(part, rangeTag);
|
|
@@ -6611,7 +6609,8 @@ IMPORTANT:
|
|
|
6611
6609
|
- Do NOT call this tool in parallel with compress \u2014 their state mutations may conflict.`;
|
|
6612
6610
|
function buildSchema3() {
|
|
6613
6611
|
return {
|
|
6614
|
-
blockId: tool4.schema.string().describe('Block reference to decompress (e.g., "b0", "b2")')
|
|
6612
|
+
blockId: tool4.schema.string().describe('Block reference to decompress (e.g., "b0", "b2")'),
|
|
6613
|
+
toFile: tool4.schema.string().optional().describe("If provided, writes restored content to this file path instead of inflating context. Block stays compressed. Use read tool to access specific parts. Example: '/tmp/block52.txt'")
|
|
6615
6614
|
};
|
|
6616
6615
|
}
|
|
6617
6616
|
function createDecompressTool(ctx) {
|
|
@@ -6640,6 +6639,40 @@ function createDecompressTool(ctx) {
|
|
|
6640
6639
|
}
|
|
6641
6640
|
return `Error: Block ${target.displayId} is not active. It may have already been decompressed.`;
|
|
6642
6641
|
}
|
|
6642
|
+
if (args.toFile) {
|
|
6643
|
+
const targetPath = args.toFile;
|
|
6644
|
+
const os = await import("os");
|
|
6645
|
+
const path = await import("path");
|
|
6646
|
+
const allowedDirs = [
|
|
6647
|
+
os.tmpdir() + "/",
|
|
6648
|
+
path.join(os.homedir(), ".cache", "opencode") + "/"
|
|
6649
|
+
];
|
|
6650
|
+
const resolved = path.resolve(targetPath);
|
|
6651
|
+
const isAllowed = allowedDirs.some((dir) => {
|
|
6652
|
+
const rel = path.relative(dir, resolved);
|
|
6653
|
+
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
6654
|
+
});
|
|
6655
|
+
if (!isAllowed) {
|
|
6656
|
+
return `Error: toFile path must be under ${os.tmpdir()} or ~/.cache/opencode/. Got: ${targetPath}`;
|
|
6657
|
+
}
|
|
6658
|
+
const block = activeBlocks[0];
|
|
6659
|
+
const msgIds = new Set(block.effectiveMessageIds ?? []);
|
|
6660
|
+
const blockMessages = rawMessages.filter((m) => {
|
|
6661
|
+
const id = m.id ?? m.messageId ?? "";
|
|
6662
|
+
return msgIds.has(id);
|
|
6663
|
+
});
|
|
6664
|
+
const lines2 = blockMessages.map((m) => {
|
|
6665
|
+
const msg = m;
|
|
6666
|
+
const role = msg.role || msg.type || "unknown";
|
|
6667
|
+
const content = typeof msg.content === "string" ? msg.content : typeof msg.text === "string" ? msg.text : JSON.stringify(msg.content || msg.text || "");
|
|
6668
|
+
return `[${role}]
|
|
6669
|
+
${content}`;
|
|
6670
|
+
});
|
|
6671
|
+
const { writeFile: writeFile3 } = await import("fs/promises");
|
|
6672
|
+
const fileContent = lines2.length > 0 ? lines2.join("\n\n---\n\n") : block.summary ?? "(no content available)";
|
|
6673
|
+
await writeFile3(args.toFile, fileContent, "utf-8");
|
|
6674
|
+
return `Block b${target.displayId} content (${blockMessages.length} messages, ${fileContent.length} chars) written to ${args.toFile}. Block stays compressed \u2014 context unchanged. Use read tool to access specific parts.`;
|
|
6675
|
+
}
|
|
6643
6676
|
const activeMessagesBefore = snapshotActiveMessages(messagesState);
|
|
6644
6677
|
const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
|
|
6645
6678
|
deactivateCompressionTarget(messagesState, target);
|
|
@@ -6693,6 +6726,52 @@ function createDecompressTool(ctx) {
|
|
|
6693
6726
|
});
|
|
6694
6727
|
}
|
|
6695
6728
|
|
|
6729
|
+
// lib/compress/status.ts
|
|
6730
|
+
import { tool as tool5 } from "@opencode-ai/plugin";
|
|
6731
|
+
var ACP_STATUS_TOOL_DESCRIPTION = `Show detailed status of all active compressed context blocks. Returns a table of block IDs, summary sizes, ages, and topics \u2014 use this to decide which blocks to decompress or search. No arguments needed.
|
|
6732
|
+
|
|
6733
|
+
Use this tool when:
|
|
6734
|
+
- You need to know what content has been compressed away
|
|
6735
|
+
- You want to see block sizes before deciding to decompress
|
|
6736
|
+
- You need a quick overview of context compression state`;
|
|
6737
|
+
function formatTokens(n) {
|
|
6738
|
+
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
6739
|
+
}
|
|
6740
|
+
function createAcpStatusTool(ctx) {
|
|
6741
|
+
ctx.prompts.reload();
|
|
6742
|
+
return tool5({
|
|
6743
|
+
description: ACP_STATUS_TOOL_DESCRIPTION,
|
|
6744
|
+
args: {},
|
|
6745
|
+
async execute() {
|
|
6746
|
+
const messages = ctx.state.prune.messages;
|
|
6747
|
+
const activeIds = Array.from(messages.activeBlockIds).sort((a, b) => a - b);
|
|
6748
|
+
if (activeIds.length === 0) {
|
|
6749
|
+
return "No compressed blocks. Context is fully visible.";
|
|
6750
|
+
}
|
|
6751
|
+
const blocks = activeIds.map((id) => messages.blocksById.get(id)).filter((b) => b !== void 0 && b.active);
|
|
6752
|
+
const totalSummary = blocks.reduce((sum, b) => sum + b.summaryTokens, 0);
|
|
6753
|
+
const totalCompressed = blocks.reduce((sum, b) => sum + b.compressedTokens, 0);
|
|
6754
|
+
const lines = [
|
|
6755
|
+
`ACP Status \u2014 ${blocks.length} active compressed block${blocks.length === 1 ? "" : "s"} (${formatTokens(totalSummary)} summary tokens, ${formatTokens(totalCompressed)} original content compressed)`,
|
|
6756
|
+
""
|
|
6757
|
+
];
|
|
6758
|
+
const idWidth = String(Math.max(...blocks.map((b) => b.blockId))).length;
|
|
6759
|
+
for (const b of blocks) {
|
|
6760
|
+
const idStr = `b${b.blockId}`.padEnd(idWidth + 1);
|
|
6761
|
+
const tokStr = `${formatTokens(b.summaryTokens)}t`.padStart(7);
|
|
6762
|
+
const ageStr = formatAge(b.createdAt).padStart(10);
|
|
6763
|
+
const topic = b.topic || "(no topic)";
|
|
6764
|
+
lines.push(` ${idStr} ${tokStr} ${ageStr} "${topic}"`);
|
|
6765
|
+
}
|
|
6766
|
+
lines.push("");
|
|
6767
|
+
lines.push(
|
|
6768
|
+
"Use decompress to restore a block's full content, or search_context to search within compressed blocks."
|
|
6769
|
+
);
|
|
6770
|
+
return lines.join("\n");
|
|
6771
|
+
}
|
|
6772
|
+
});
|
|
6773
|
+
}
|
|
6774
|
+
|
|
6696
6775
|
// lib/logger.ts
|
|
6697
6776
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
6698
6777
|
import { join as join3 } from "path";
|
|
@@ -6903,71 +6982,19 @@ You operate in a context-constrained environment. Context management helps prese
|
|
|
6903
6982
|
|
|
6904
6983
|
The tools you have for context management are \`compress\`, \`decompress\`, and \`search_context\`. \`compress\` replaces older conversation content with technical summaries you produce. \`decompress\` restores previously compressed content when you need exact details. \`search_context\` searches compressed block summaries (and visible messages) to locate relevant content before you decompress.
|
|
6905
6984
|
|
|
6906
|
-
\`<dcp-message-id>\` and \`<dcp-system-reminder>\` tags are
|
|
6985
|
+
\`<acp-context>\` tags wrap ACP (Agent Context Pruning) system metadata \u2014 context management information injected each turn. This is system data, not user input. You may also see \`<dcp-message-id>\` and \`<dcp-system-reminder>\` tags \u2014 these are equivalent (DCP was the previous name for ACP).
|
|
6907
6986
|
|
|
6908
6987
|
COMPRESSION PHILOSOPHY
|
|
6909
6988
|
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
Target the largest UNCOMPRESSED content first. Savings scale with original size \u2014 compressing a 5000-token tool output frees far more than re-shrinking an already-summarized 300-token block.
|
|
6915
|
-
|
|
6916
|
-
CONTEXT PRESSURE LEVELS
|
|
6917
|
-
|
|
6918
|
-
- Normal: After completing a task or sub-task, compress its tool outputs (agent results, verbose commands, large tool outputs) into summaries. Do NOT compress content you're actively using for an ongoing task \u2014 wait until the task is complete. You can decompress later if needed.
|
|
6919
|
-
- Elevated: Context is growing \u2014 compress completed sections and high-token waste now.
|
|
6920
|
-
- Critical: Compress aggressively now \u2014 preserve only what is essential for the current task.
|
|
6921
|
-
|
|
6922
|
-
WHAT TO COMPRESS FIRST (high value, low risk)
|
|
6989
|
+
All compression serves the primary task, but be frugal. Two failure modes to avoid:
|
|
6990
|
+
- Over-compression: Compressing too aggressively loses critical details, decisions, and state needed for your task. This directly harms task quality.
|
|
6991
|
+
- Under-compression: Failing to compress verbose outputs causes context overflow, reducing accuracy and eventually blocking your work.
|
|
6923
6992
|
|
|
6924
|
-
|
|
6925
|
-
- Verbose command output (build/test runs, git diff/log/status, publish logs, directory listings): Once you have read the result, compress. Keep only the verdict \u2014 pass/fail status, commit hash, version number, or count. For failures, keep the specific error messages and file/line references needed to act on them. The full output is reproducible by re-running the command.
|
|
6926
|
-
- Exploration that led nowhere (failed approaches, dead-end searches): Compress to a one-line note about what was tried and why it failed.
|
|
6927
|
-
- Redundant tool results (reading the same file multiple times, repeated status checks, exhausted search results): Keep only the most recent result.
|
|
6928
|
-
- Intermediate steps of completed multi-step tasks: Once the task is done, compress the process. Keep only the final outcome.
|
|
6929
|
-
- Resolved discussion threads (clarification rounds, negotiated requirements, design debate that reached a decision): Once a conclusion is recorded, compress the back-and-forth. Keep the decision and its rationale.
|
|
6930
|
-
- Large file contents that have already been used and are no longer needed: Compress to a summary of key functions, types, or patterns.
|
|
6993
|
+
Balance is key. Compress selectively to keep context lean. But never compress content you're actively using for an ongoing task. Use \`search_context\` to find compressed content when needed, and \`decompress\` to restore details.
|
|
6931
6994
|
|
|
6932
|
-
|
|
6995
|
+
BE FRUGAL
|
|
6933
6996
|
|
|
6934
|
-
|
|
6935
|
-
- Short messages (1-3 sentences): The compression overhead (block metadata, summary structure) may exceed the tokens saved.
|
|
6936
|
-
- Content whose immediate use is complete \u2014 the task it supported is done and no open todo/plan references it. If still in active use, let it stay.
|
|
6937
|
-
- User instructions and requirements: These must remain visible until the task is complete.
|
|
6938
|
-
- Tool calls that are still pending or in-progress: Wait until the result is returned and consumed.
|
|
6939
|
-
|
|
6940
|
-
WHAT TO COMPRESS CAREFULLY (high risk - verify before compressing)
|
|
6941
|
-
|
|
6942
|
-
- Temporary secrets/keys/tokens needed later: Do NOT compress unless recorded elsewhere
|
|
6943
|
-
- File paths and directory structures: Keep in summary - losing these wastes tokens rediscovering them
|
|
6944
|
-
- Key function/method signatures and APIs: Summarize with exact names and signatures
|
|
6945
|
-
- Critical error messages and stack traces: Keep the error type and key detail in summary
|
|
6946
|
-
- User preferences and requirements: These must survive compression intact
|
|
6947
|
-
- Architectural decisions and rationale: Summarize the decision, not just the conclusion
|
|
6948
|
-
|
|
6949
|
-
BEFORE COMPRESSING IMPORTANT CONTENT
|
|
6950
|
-
|
|
6951
|
-
Verify the information is persisted in one of:
|
|
6952
|
-
- A file you have written or edited
|
|
6953
|
-
- An issue, PR, or devlog entry
|
|
6954
|
-
- The compression summary itself (include the critical bits explicitly)
|
|
6955
|
-
|
|
6956
|
-
If it is not persisted anywhere, either persist it first or include it explicitly in your compression summary.
|
|
6957
|
-
|
|
6958
|
-
AFTER COMPRESSING
|
|
6959
|
-
|
|
6960
|
-
Generate recovery breadcrumbs in your summary so future-you can reconstruct the context:
|
|
6961
|
-
- Reference specific files by path
|
|
6962
|
-
- Include key variable names, function signatures, or configuration values
|
|
6963
|
-
- Note what was decided and why, not just what was done
|
|
6964
|
-
- Example: "Implemented auth check in src/middleware.ts using validateToken() from auth.ts - user table is users not user"
|
|
6965
|
-
|
|
6966
|
-
If you later realize you need the original details from a compressed block, use \`decompress\` to restore them. You can decompress, read the content, then re-compress if needed.
|
|
6967
|
-
|
|
6968
|
-
Use \`search_context\` to find relevant compressed content before decompressing \u2014 it returns ranked matches across all active block summaries so you can pick the right block ID without inflating context by trial-and-error decompression.
|
|
6969
|
-
|
|
6970
|
-
Use \`compress\` and \`decompress\` deliberately with quality-first summaries. Prioritize stale content intelligently to maintain a high-signal context window.
|
|
6997
|
+
Be frugal with context \u2014 compress obvious waste promptly. Examples include verbose command output (build/test logs, git diff/status, npm install), sub-agent results once consumed, experiment/training logs (keep final metrics only), duplicate file reads, and failed explorations. Any content that is finished serving the task and would not be needed in upcoming turns should be compressed \u2014 not just these examples.
|
|
6971
6998
|
`;
|
|
6972
6999
|
|
|
6973
7000
|
// lib/prompts/compress-range.ts
|
|
@@ -7230,7 +7257,7 @@ var PROMPT_DEFINITIONS = [
|
|
|
7230
7257
|
];
|
|
7231
7258
|
var HTML_COMMENT_REGEX = /<!--[\s\S]*?-->/g;
|
|
7232
7259
|
var LEGACY_INLINE_COMMENT_LINE_REGEX = /^[ \t]*\/\/.*?\/\/[ \t]*$/gm;
|
|
7233
|
-
var DCP_SYSTEM_REMINDER_TAG_REGEX = /^\s*<dcp-system-reminder\b[^>]*>[\s\S]*<\/dcp-system-reminder>\s*$/i;
|
|
7260
|
+
var DCP_SYSTEM_REMINDER_TAG_REGEX = /^\s*<dcp-system-reminder\b[^>]*>[\s\S]*<\/(?:dcp|acp)-system-reminder>\s*$/i;
|
|
7234
7261
|
var DEFAULTS_README_FILE = "README.md";
|
|
7235
7262
|
var BUNDLED_EDITABLE_PROMPTS = {
|
|
7236
7263
|
system: SYSTEM,
|
|
@@ -7309,7 +7336,7 @@ function stripConditionalTag(content, tagName) {
|
|
|
7309
7336
|
function unwrapDcpTagIfWrapped(content) {
|
|
7310
7337
|
const trimmed = content.trim();
|
|
7311
7338
|
if (DCP_SYSTEM_REMINDER_TAG_REGEX.test(trimmed)) {
|
|
7312
|
-
return trimmed.replace(/^\s*<dcp-system-reminder\b[^>]*>\s*/i, "").replace(/\s*<\/dcp-system-reminder>\s*$/i, "").trim();
|
|
7339
|
+
return trimmed.replace(/^\s*<dcp-system-reminder\b[^>]*>\s*/i, "").replace(/\s*<\/(?:dcp|acp)-system-reminder>\s*$/i, "").trim();
|
|
7313
7340
|
}
|
|
7314
7341
|
return trimmed;
|
|
7315
7342
|
}
|
|
@@ -7319,7 +7346,7 @@ function normalizeReminderPromptContent(content) {
|
|
|
7319
7346
|
return "";
|
|
7320
7347
|
}
|
|
7321
7348
|
const startsWrapped = /^\s*<dcp-system-reminder\b[^>]*>/i.test(normalized);
|
|
7322
|
-
const endsWrapped = /<\/dcp-system-reminder>\s*$/i.test(normalized);
|
|
7349
|
+
const endsWrapped = /<\/(?:dcp|acp)-system-reminder>\s*$/i.test(normalized);
|
|
7323
7350
|
if (startsWrapped !== endsWrapped) {
|
|
7324
7351
|
return "";
|
|
7325
7352
|
}
|
|
@@ -7919,7 +7946,7 @@ var COMPRESS_TRIGGER_PROMPT = [
|
|
|
7919
7946
|
"Follow the active compress mode, preserve all critical implementation details, and choose safe targets.",
|
|
7920
7947
|
"Return after compress with a brief explanation of what content was compressed."
|
|
7921
7948
|
].join("\n\n");
|
|
7922
|
-
function getTriggerPrompt(
|
|
7949
|
+
function getTriggerPrompt(tool6, state, config, userFocus) {
|
|
7923
7950
|
const base = COMPRESS_TRIGGER_PROMPT;
|
|
7924
7951
|
const compressedBlockGuidance = config.compress.mode === "message" ? "" : buildCompressedBlockGuidance(state, config.gc);
|
|
7925
7952
|
const sections = [base, compressedBlockGuidance];
|
|
@@ -7948,8 +7975,8 @@ async function handleManualToggleCommand(ctx, modeArg) {
|
|
|
7948
7975
|
);
|
|
7949
7976
|
logger.info("Manual mode toggled", { manualMode: state.manualMode });
|
|
7950
7977
|
}
|
|
7951
|
-
async function handleManualTriggerCommand(ctx,
|
|
7952
|
-
return getTriggerPrompt(
|
|
7978
|
+
async function handleManualTriggerCommand(ctx, tool6, userFocus) {
|
|
7979
|
+
return getTriggerPrompt(tool6, ctx.state, ctx.config, userFocus);
|
|
7953
7980
|
}
|
|
7954
7981
|
function applyPendingManualTrigger(state, messages, logger) {
|
|
7955
7982
|
const pending = state.pendingManualTrigger;
|
|
@@ -8682,6 +8709,9 @@ function createSystemPromptHandler(state, logger, config, prompts) {
|
|
|
8682
8709
|
if (effectivePermission === "deny") {
|
|
8683
8710
|
return;
|
|
8684
8711
|
}
|
|
8712
|
+
if (state.nudges.shouldInjectThisTurn === false && !state.manualMode) {
|
|
8713
|
+
return;
|
|
8714
|
+
}
|
|
8685
8715
|
prompts.reload();
|
|
8686
8716
|
const runtimePrompts = prompts.getRuntimePrompts();
|
|
8687
8717
|
const newPrompt = renderSystemPrompt(
|
|
@@ -9210,7 +9240,8 @@ var server = (async (ctx) => {
|
|
|
9210
9240
|
...config.compress.permission !== "deny" && {
|
|
9211
9241
|
compress: config.compress.mode === "message" ? createCompressMessageTool(compressToolContext) : createCompressRangeTool(compressToolContext),
|
|
9212
9242
|
decompress: createDecompressTool(compressToolContext),
|
|
9213
|
-
search_context: createSearchContextTool(compressToolContext)
|
|
9243
|
+
search_context: createSearchContextTool(compressToolContext),
|
|
9244
|
+
acp_status: createAcpStatusTool(compressToolContext)
|
|
9214
9245
|
}
|
|
9215
9246
|
},
|
|
9216
9247
|
config: async (opencodeConfig) => {
|
|
@@ -9226,7 +9257,7 @@ var server = (async (ctx) => {
|
|
|
9226
9257
|
}
|
|
9227
9258
|
const toolsToAdd = [];
|
|
9228
9259
|
if (config.compress.permission !== "deny" && !config.experimental.allowSubAgents) {
|
|
9229
|
-
toolsToAdd.push("compress", "decompress", "search_context");
|
|
9260
|
+
toolsToAdd.push("compress", "decompress", "search_context", "acp_status");
|
|
9230
9261
|
}
|
|
9231
9262
|
if (toolsToAdd.length > 0) {
|
|
9232
9263
|
const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? [];
|
|
@@ -9239,7 +9270,8 @@ var server = (async (ctx) => {
|
|
|
9239
9270
|
const permission = opencodeConfig.permission ?? {};
|
|
9240
9271
|
opencodeConfig.permission = {
|
|
9241
9272
|
...permission,
|
|
9242
|
-
compress: config.compress.permission
|
|
9273
|
+
compress: config.compress.permission,
|
|
9274
|
+
acp_status: "allow"
|
|
9243
9275
|
};
|
|
9244
9276
|
}
|
|
9245
9277
|
hostPermissions.global = opencodeConfig.permission;
|