opencode-acp 1.8.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 +37 -1
- package/README.zh-CN.md +37 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +198 -108
- package/dist/index.js.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/pipeline.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 +1 -0
- 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 +19 -0
- 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/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
|
@@ -895,6 +895,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
895
895
|
"compress.modelMinLimits",
|
|
896
896
|
"compress.nudgeFrequency",
|
|
897
897
|
"compress.minNudgeContextPercent",
|
|
898
|
+
"compress.nudgeGrowthTokens",
|
|
898
899
|
"compress.iterationNudgeThreshold",
|
|
899
900
|
"compress.nudgeForce",
|
|
900
901
|
"compress.protectedTools",
|
|
@@ -1645,6 +1646,7 @@ function mergeCompress(base, override) {
|
|
|
1645
1646
|
modelMinLimits: override.modelMinLimits ?? base.modelMinLimits,
|
|
1646
1647
|
nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
|
|
1647
1648
|
minNudgeContextPercent: override.minNudgeContextPercent ?? base.minNudgeContextPercent,
|
|
1649
|
+
nudgeGrowthTokens: override.nudgeGrowthTokens,
|
|
1648
1650
|
iterationNudgeThreshold: override.iterationNudgeThreshold ?? base.iterationNudgeThreshold,
|
|
1649
1651
|
nudgeForce: override.nudgeForce ?? base.nudgeForce,
|
|
1650
1652
|
protectedTools: [.../* @__PURE__ */ new Set([...base.protectedTools, ...override.protectedTools ?? []])],
|
|
@@ -3185,7 +3187,8 @@ function resetOnCompaction(state) {
|
|
|
3185
3187
|
turnNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3186
3188
|
iterationNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3187
3189
|
lastPerMessageNudgeTurn: 0,
|
|
3188
|
-
lastPerMessageNudgeTokens: 0
|
|
3190
|
+
lastPerMessageNudgeTokens: void 0,
|
|
3191
|
+
shouldInjectThisTurn: void 0
|
|
3189
3192
|
};
|
|
3190
3193
|
state.messageIds = {
|
|
3191
3194
|
byRawId: /* @__PURE__ */ new Map(),
|
|
@@ -3253,7 +3256,7 @@ async function saveSessionState(sessionState, logger, sessionName) {
|
|
|
3253
3256
|
turnNudgeAnchors: Array.from(sessionState.nudges.turnNudgeAnchors),
|
|
3254
3257
|
iterationNudgeAnchors: Array.from(sessionState.nudges.iterationNudgeAnchors),
|
|
3255
3258
|
lastPerMessageNudgeTurn: sessionState.nudges.lastPerMessageNudgeTurn ?? 0,
|
|
3256
|
-
lastPerMessageNudgeTokens: sessionState.nudges.lastPerMessageNudgeTokens
|
|
3259
|
+
lastPerMessageNudgeTokens: sessionState.nudges.lastPerMessageNudgeTokens
|
|
3257
3260
|
},
|
|
3258
3261
|
stats: sessionState.stats,
|
|
3259
3262
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3469,7 +3472,8 @@ function createSessionState() {
|
|
|
3469
3472
|
turnNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3470
3473
|
iterationNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3471
3474
|
lastPerMessageNudgeTurn: 0,
|
|
3472
|
-
lastPerMessageNudgeTokens: 0
|
|
3475
|
+
lastPerMessageNudgeTokens: void 0,
|
|
3476
|
+
shouldInjectThisTurn: void 0
|
|
3473
3477
|
},
|
|
3474
3478
|
stats: {
|
|
3475
3479
|
pruneTokenCounter: 0,
|
|
@@ -3508,7 +3512,8 @@ function resetSessionState(state) {
|
|
|
3508
3512
|
turnNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3509
3513
|
iterationNudgeAnchors: /* @__PURE__ */ new Set(),
|
|
3510
3514
|
lastPerMessageNudgeTurn: 0,
|
|
3511
|
-
lastPerMessageNudgeTokens: 0
|
|
3515
|
+
lastPerMessageNudgeTokens: void 0,
|
|
3516
|
+
shouldInjectThisTurn: void 0
|
|
3512
3517
|
};
|
|
3513
3518
|
state.stats = {
|
|
3514
3519
|
pruneTokenCounter: 0,
|
|
@@ -3554,7 +3559,7 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
3554
3559
|
persisted.nudges.iterationNudgeAnchors || []
|
|
3555
3560
|
);
|
|
3556
3561
|
state.nudges.lastPerMessageNudgeTurn = persisted.nudges.lastPerMessageNudgeTurn ?? 0;
|
|
3557
|
-
state.nudges.lastPerMessageNudgeTokens = persisted.nudges.lastPerMessageNudgeTokens
|
|
3562
|
+
state.nudges.lastPerMessageNudgeTokens = persisted.nudges.lastPerMessageNudgeTokens;
|
|
3558
3563
|
state.stats = {
|
|
3559
3564
|
pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0,
|
|
3560
3565
|
totalPruneTokens: persisted.stats?.totalPruneTokens || 0
|
|
@@ -3704,20 +3709,20 @@ function matchesGlob(inputPath, pattern) {
|
|
|
3704
3709
|
regex += "$";
|
|
3705
3710
|
return new RegExp(regex).test(input);
|
|
3706
3711
|
}
|
|
3707
|
-
function getFilePathsFromParameters(
|
|
3712
|
+
function getFilePathsFromParameters(tool6, parameters) {
|
|
3708
3713
|
if (typeof parameters !== "object" || parameters === null) {
|
|
3709
3714
|
return [];
|
|
3710
3715
|
}
|
|
3711
3716
|
const paths = [];
|
|
3712
3717
|
const params = parameters;
|
|
3713
|
-
if (
|
|
3718
|
+
if (tool6 === "apply_patch" && typeof params.patchText === "string") {
|
|
3714
3719
|
const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g;
|
|
3715
3720
|
let match;
|
|
3716
3721
|
while ((match = pathRegex.exec(params.patchText)) !== null) {
|
|
3717
3722
|
paths.push(match[1].trim());
|
|
3718
3723
|
}
|
|
3719
3724
|
}
|
|
3720
|
-
if (
|
|
3725
|
+
if (tool6 === "multiedit") {
|
|
3721
3726
|
if (typeof params.filePath === "string") {
|
|
3722
3727
|
paths.push(params.filePath);
|
|
3723
3728
|
}
|
|
@@ -3812,13 +3817,13 @@ var deduplicate = (state, logger, config, messages) => {
|
|
|
3812
3817
|
logger.debug(`Marked ${newPruneIds.length} duplicate tool calls for pruning`);
|
|
3813
3818
|
}
|
|
3814
3819
|
};
|
|
3815
|
-
function createToolSignature(
|
|
3820
|
+
function createToolSignature(tool6, parameters) {
|
|
3816
3821
|
if (!parameters) {
|
|
3817
|
-
return
|
|
3822
|
+
return tool6;
|
|
3818
3823
|
}
|
|
3819
3824
|
const normalized = normalizeParameters(parameters);
|
|
3820
3825
|
const sorted = sortObjectKeys(normalized);
|
|
3821
|
-
return `${
|
|
3826
|
+
return `${tool6}::${JSON.stringify(sorted)}`;
|
|
3822
3827
|
}
|
|
3823
3828
|
function normalizeParameters(params) {
|
|
3824
3829
|
if (typeof params !== "object" || params === null) return params;
|
|
@@ -3893,9 +3898,9 @@ var purgeErrors = (state, logger, config, messages) => {
|
|
|
3893
3898
|
};
|
|
3894
3899
|
|
|
3895
3900
|
// lib/ui/utils.ts
|
|
3896
|
-
function extractParameterKey(
|
|
3901
|
+
function extractParameterKey(tool6, parameters) {
|
|
3897
3902
|
if (!parameters) return "";
|
|
3898
|
-
if (
|
|
3903
|
+
if (tool6 === "read" && parameters.filePath) {
|
|
3899
3904
|
const offset = parameters.offset;
|
|
3900
3905
|
const limit = parameters.limit;
|
|
3901
3906
|
if (offset !== void 0 && limit !== void 0) {
|
|
@@ -3909,10 +3914,10 @@ function extractParameterKey(tool5, parameters) {
|
|
|
3909
3914
|
}
|
|
3910
3915
|
return parameters.filePath;
|
|
3911
3916
|
}
|
|
3912
|
-
if ((
|
|
3917
|
+
if ((tool6 === "write" || tool6 === "edit" || tool6 === "multiedit") && parameters.filePath) {
|
|
3913
3918
|
return parameters.filePath;
|
|
3914
3919
|
}
|
|
3915
|
-
if (
|
|
3920
|
+
if (tool6 === "apply_patch" && typeof parameters.patchText === "string") {
|
|
3916
3921
|
const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g;
|
|
3917
3922
|
const paths = [];
|
|
3918
3923
|
let match;
|
|
@@ -3929,51 +3934,51 @@ function extractParameterKey(tool5, parameters) {
|
|
|
3929
3934
|
}
|
|
3930
3935
|
return "patch";
|
|
3931
3936
|
}
|
|
3932
|
-
if (
|
|
3937
|
+
if (tool6 === "list") {
|
|
3933
3938
|
return parameters.path || "(current directory)";
|
|
3934
3939
|
}
|
|
3935
|
-
if (
|
|
3940
|
+
if (tool6 === "glob") {
|
|
3936
3941
|
if (parameters.pattern) {
|
|
3937
3942
|
const pathInfo = parameters.path ? ` in ${parameters.path}` : "";
|
|
3938
3943
|
return `"${parameters.pattern}"${pathInfo}`;
|
|
3939
3944
|
}
|
|
3940
3945
|
return "(unknown pattern)";
|
|
3941
3946
|
}
|
|
3942
|
-
if (
|
|
3947
|
+
if (tool6 === "grep") {
|
|
3943
3948
|
if (parameters.pattern) {
|
|
3944
3949
|
const pathInfo = parameters.path ? ` in ${parameters.path}` : "";
|
|
3945
3950
|
return `"${parameters.pattern}"${pathInfo}`;
|
|
3946
3951
|
}
|
|
3947
3952
|
return "(unknown pattern)";
|
|
3948
3953
|
}
|
|
3949
|
-
if (
|
|
3954
|
+
if (tool6 === "bash") {
|
|
3950
3955
|
if (parameters.description) return parameters.description;
|
|
3951
3956
|
if (parameters.command) {
|
|
3952
3957
|
return parameters.command.length > 50 ? parameters.command.substring(0, 50) + "..." : parameters.command;
|
|
3953
3958
|
}
|
|
3954
3959
|
}
|
|
3955
|
-
if (
|
|
3960
|
+
if (tool6 === "webfetch" && parameters.url) {
|
|
3956
3961
|
return parameters.url;
|
|
3957
3962
|
}
|
|
3958
|
-
if (
|
|
3963
|
+
if (tool6 === "websearch" && parameters.query) {
|
|
3959
3964
|
return `"${parameters.query}"`;
|
|
3960
3965
|
}
|
|
3961
|
-
if (
|
|
3966
|
+
if (tool6 === "codesearch" && parameters.query) {
|
|
3962
3967
|
return `"${parameters.query}"`;
|
|
3963
3968
|
}
|
|
3964
|
-
if (
|
|
3969
|
+
if (tool6 === "todowrite") {
|
|
3965
3970
|
return `${parameters.todos?.length || 0} todos`;
|
|
3966
3971
|
}
|
|
3967
|
-
if (
|
|
3972
|
+
if (tool6 === "todoread") {
|
|
3968
3973
|
return "read todo list";
|
|
3969
3974
|
}
|
|
3970
|
-
if (
|
|
3975
|
+
if (tool6 === "task" && parameters.description) {
|
|
3971
3976
|
return parameters.description;
|
|
3972
3977
|
}
|
|
3973
|
-
if (
|
|
3978
|
+
if (tool6 === "skill" && parameters.name) {
|
|
3974
3979
|
return parameters.name;
|
|
3975
3980
|
}
|
|
3976
|
-
if (
|
|
3981
|
+
if (tool6 === "lsp") {
|
|
3977
3982
|
const op = parameters.operation || "lsp";
|
|
3978
3983
|
const path = parameters.filePath || "";
|
|
3979
3984
|
const line = parameters.line;
|
|
@@ -3986,7 +3991,7 @@ function extractParameterKey(tool5, parameters) {
|
|
|
3986
3991
|
}
|
|
3987
3992
|
return op;
|
|
3988
3993
|
}
|
|
3989
|
-
if (
|
|
3994
|
+
if (tool6 === "question") {
|
|
3990
3995
|
const questions = parameters.questions;
|
|
3991
3996
|
if (Array.isArray(questions) && questions.length > 0) {
|
|
3992
3997
|
const headers = questions.map((q) => q.header || "").filter(Boolean).slice(0, 3);
|
|
@@ -4006,6 +4011,13 @@ function extractParameterKey(tool5, parameters) {
|
|
|
4006
4011
|
}
|
|
4007
4012
|
return paramStr.substring(0, 50);
|
|
4008
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
|
+
}
|
|
4009
4021
|
function formatTokenCount(tokens, compact) {
|
|
4010
4022
|
const suffix = compact ? "" : " tokens";
|
|
4011
4023
|
if (tokens >= 1e3) {
|
|
@@ -4172,7 +4184,12 @@ function formatCompressionMetrics(removedTokens, summaryTokens) {
|
|
|
4172
4184
|
}
|
|
4173
4185
|
return metrics.join(", ");
|
|
4174
4186
|
}
|
|
4175
|
-
|
|
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) {
|
|
4176
4193
|
if (config.pruneNotification === "off") {
|
|
4177
4194
|
return false;
|
|
4178
4195
|
}
|
|
@@ -4220,9 +4237,14 @@ async function sendCompressNotification(client, logger, config, state, sessionId
|
|
|
4220
4237
|
}
|
|
4221
4238
|
}
|
|
4222
4239
|
const topic = batchTopic ?? (entries.length === 1 ? state.prune.messages.blocksById.get(entries[0]?.blockId ?? -1)?.topic ?? "(unknown topic)" : "(unknown topic)");
|
|
4223
|
-
const
|
|
4224
|
-
|
|
4225
|
-
|
|
4240
|
+
const contextTokensAfter = Math.max(
|
|
4241
|
+
0,
|
|
4242
|
+
contextTokensBefore - compressedTokens + summaryTokens
|
|
4243
|
+
);
|
|
4244
|
+
const notificationHeader = `\u25A3 ACP | ${formatContextTransition(
|
|
4245
|
+
contextTokensBefore,
|
|
4246
|
+
contextTokensAfter
|
|
4247
|
+
)}`;
|
|
4226
4248
|
if (config.pruneNotification === "minimal") {
|
|
4227
4249
|
message = `${notificationHeader} \u2014 ${compressionLabel}`;
|
|
4228
4250
|
} else {
|
|
@@ -4354,6 +4376,7 @@ async function finalizeSession(ctx, toolCtx, rawMessages, entries, batchTopic) {
|
|
|
4354
4376
|
await saveSessionState(ctx.state, ctx.logger);
|
|
4355
4377
|
const params = getCurrentParams(ctx.state, rawMessages, ctx.logger);
|
|
4356
4378
|
const sessionMessageIds = rawMessages.filter((msg) => !isIgnoredUserMessage(msg)).map((msg) => msg.info.id);
|
|
4379
|
+
const contextTokensBefore = getCurrentTokenUsage(ctx.state, rawMessages);
|
|
4357
4380
|
await sendCompressNotification(
|
|
4358
4381
|
ctx.client,
|
|
4359
4382
|
ctx.logger,
|
|
@@ -4363,7 +4386,8 @@ async function finalizeSession(ctx, toolCtx, rawMessages, entries, batchTopic) {
|
|
|
4363
4386
|
entries,
|
|
4364
4387
|
batchTopic,
|
|
4365
4388
|
sessionMessageIds,
|
|
4366
|
-
params
|
|
4389
|
+
params,
|
|
4390
|
+
contextTokensBefore
|
|
4367
4391
|
);
|
|
4368
4392
|
}
|
|
4369
4393
|
|
|
@@ -5485,8 +5509,8 @@ var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agent
|
|
|
5485
5509
|
agentName ? hostPermissions.agents[agentName] : void 0
|
|
5486
5510
|
) ? "deny" : basePermission;
|
|
5487
5511
|
};
|
|
5488
|
-
var hasExplicitToolPermission = (permissionConfig,
|
|
5489
|
-
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig,
|
|
5512
|
+
var hasExplicitToolPermission = (permissionConfig, tool6) => {
|
|
5513
|
+
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
|
|
5490
5514
|
};
|
|
5491
5515
|
|
|
5492
5516
|
// lib/compress-permission.ts
|
|
@@ -5505,26 +5529,15 @@ var syncCompressPermissionState = (state, config, hostPermissions, messages) =>
|
|
|
5505
5529
|
// lib/prompts/extensions/nudge.ts
|
|
5506
5530
|
function buildCompressedBlockGuidance(state, gcConfig, context) {
|
|
5507
5531
|
const activeBlockIds = Array.from(state.prune.messages.activeBlockIds).filter((id) => Number.isInteger(id) && id > 0).sort((a, b) => a - b);
|
|
5508
|
-
const
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
const
|
|
5514
|
-
let blockList;
|
|
5515
|
-
if (blockCount <= 20) {
|
|
5516
|
-
blockList = blockCount > 0 ? refs.join(", ") : "none";
|
|
5517
|
-
} else {
|
|
5518
|
-
const recent = refs.slice(-20).join(", ");
|
|
5519
|
-
blockList = `${recent} (+${blockCount - 20} older, use decompress to access by ID)`;
|
|
5520
|
-
}
|
|
5521
|
-
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";
|
|
5522
5538
|
const lines = [
|
|
5523
|
-
`- Compressed blocks: ${blockCount} (${
|
|
5539
|
+
`- Compressed blocks: ${blockCount} (${totalSummaryDisplay} summary, last ${ageStr}). Use acp_status for details.`
|
|
5524
5540
|
];
|
|
5525
|
-
if (includeHint) {
|
|
5526
|
-
lines.push("- \u{1F4A1} Tools: compress, decompress, search_context.");
|
|
5527
|
-
}
|
|
5528
5541
|
if (blockCount > 50) {
|
|
5529
5542
|
const oldBlockIds = activeBlockIds.slice(0, Math.max(0, blockCount - 20));
|
|
5530
5543
|
const allOldBlocks = oldBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0);
|
|
@@ -5713,8 +5726,8 @@ function getModelInfo(messages) {
|
|
|
5713
5726
|
}
|
|
5714
5727
|
const userInfo = lastUserMessage.info;
|
|
5715
5728
|
return {
|
|
5716
|
-
providerId: userInfo.model
|
|
5717
|
-
modelId: userInfo.model
|
|
5729
|
+
providerId: userInfo.model?.providerID,
|
|
5730
|
+
modelId: userInfo.model?.modelID
|
|
5718
5731
|
};
|
|
5719
5732
|
}
|
|
5720
5733
|
function resolveContextTokenLimit(config, state, providerId, modelId, threshold) {
|
|
@@ -5783,6 +5796,29 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
|
5783
5796
|
modelContextLimit: state.modelContextLimit
|
|
5784
5797
|
};
|
|
5785
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
|
+
}
|
|
5786
5822
|
function addAnchor(anchorMessageIds, anchorMessageId, anchorMessageIndex, messages, interval) {
|
|
5787
5823
|
if (anchorMessageIndex < 0) {
|
|
5788
5824
|
return false;
|
|
@@ -5899,8 +5935,6 @@ Context: ${formatK(currentTokens)} tokens.
|
|
|
5899
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.`;
|
|
5900
5936
|
}
|
|
5901
5937
|
function applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage) {
|
|
5902
|
-
const contextUsageInfo = buildContextUsageGuidance(config, currentTokens, modelContextLimit);
|
|
5903
|
-
const contextLimitNudgeWithUsage = prompts.contextLimitNudge + contextUsageInfo;
|
|
5904
5938
|
const turnNudgeAnchors = collectTurnNudgeAnchors2(state, config, messages);
|
|
5905
5939
|
if (suffixMessage) {
|
|
5906
5940
|
const nudgeParts = [];
|
|
@@ -5908,7 +5942,7 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
5908
5942
|
if (state.nudges.contextLimitAnchors.size > 0) {
|
|
5909
5943
|
for (const { index } of collectAnchoredMessages(state.nudges.contextLimitAnchors, messages)) {
|
|
5910
5944
|
const guidance = buildMessagePriorityGuidance(messages, compressionPriorities, index, MESSAGE_MODE_NUDGE_PRIORITY);
|
|
5911
|
-
nudgeParts.push(appendGuidanceToDcpTag(
|
|
5945
|
+
nudgeParts.push(appendGuidanceToDcpTag(prompts.contextLimitNudge, guidance));
|
|
5912
5946
|
}
|
|
5913
5947
|
}
|
|
5914
5948
|
if (turnNudgeAnchors.size > 0) {
|
|
@@ -5925,7 +5959,7 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
5925
5959
|
}
|
|
5926
5960
|
} else {
|
|
5927
5961
|
if (state.nudges.contextLimitAnchors.size > 0) {
|
|
5928
|
-
nudgeParts.push(
|
|
5962
|
+
nudgeParts.push(prompts.contextLimitNudge);
|
|
5929
5963
|
}
|
|
5930
5964
|
if (turnNudgeAnchors.size > 0) {
|
|
5931
5965
|
nudgeParts.push(prompts.turnNudge);
|
|
@@ -5944,7 +5978,7 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
5944
5978
|
applyMessageModeAnchoredNudge(
|
|
5945
5979
|
state.nudges.contextLimitAnchors,
|
|
5946
5980
|
messages,
|
|
5947
|
-
|
|
5981
|
+
prompts.contextLimitNudge,
|
|
5948
5982
|
compressionPriorities
|
|
5949
5983
|
);
|
|
5950
5984
|
applyMessageModeAnchoredNudge(
|
|
@@ -5964,7 +5998,7 @@ function applyAnchoredNudges(state, config, messages, prompts, compressionPriori
|
|
|
5964
5998
|
applyRangeModeAnchoredNudge(
|
|
5965
5999
|
state.nudges.contextLimitAnchors,
|
|
5966
6000
|
messages,
|
|
5967
|
-
|
|
6001
|
+
prompts.contextLimitNudge,
|
|
5968
6002
|
""
|
|
5969
6003
|
);
|
|
5970
6004
|
applyRangeModeAnchoredNudge(
|
|
@@ -5999,16 +6033,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
5999
6033
|
}
|
|
6000
6034
|
const lastMessage = findLastNonIgnoredMessage(messages);
|
|
6001
6035
|
const lastAssistantMessage = messages.findLast((message) => message.info.role === "assistant");
|
|
6002
|
-
if (lastAssistantMessage && messageHasCompress(lastAssistantMessage)) {
|
|
6003
|
-
state.nudges.contextLimitAnchors.clear();
|
|
6004
|
-
state.nudges.turnNudgeAnchors.clear();
|
|
6005
|
-
state.nudges.iterationNudgeAnchors.clear();
|
|
6006
|
-
state.nudges.lastPerMessageNudgeTokens = 0;
|
|
6007
|
-
void saveSessionState(state, logger);
|
|
6008
|
-
return;
|
|
6009
|
-
}
|
|
6010
6036
|
const { providerId, modelId } = getModelInfo(messages);
|
|
6011
|
-
let anchorsChanged = false;
|
|
6012
6037
|
const { overMaxLimit, overMinLimit, currentTokens, modelContextLimit } = isContextOverLimits(
|
|
6013
6038
|
config,
|
|
6014
6039
|
state,
|
|
@@ -6016,6 +6041,15 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6016
6041
|
modelId,
|
|
6017
6042
|
messages
|
|
6018
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;
|
|
6019
6053
|
if (!overMinLimit) {
|
|
6020
6054
|
const hadTurnAnchors = state.nudges.turnNudgeAnchors.size > 0;
|
|
6021
6055
|
const hadIterationAnchors = state.nudges.iterationNudgeAnchors.size > 0;
|
|
@@ -6075,44 +6109,49 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6075
6109
|
}
|
|
6076
6110
|
const suffixMessage = createSuffixMessage(messages);
|
|
6077
6111
|
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
6078
|
-
const
|
|
6079
|
-
|
|
6080
|
-
|
|
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;
|
|
6081
6122
|
let tipsText = null;
|
|
6082
|
-
if (
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
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.";
|
|
6089
6131
|
}
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
if (
|
|
6093
|
-
|
|
6132
|
+
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
6133
|
+
state.nudges.lastPerMessageNudgeTurn = state.currentTurn ?? 0;
|
|
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
|
+
}
|
|
6094
6147
|
}
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
const visibleMessageIds = new Set(
|
|
6098
|
-
messages.map((message) => message.info.id)
|
|
6099
|
-
);
|
|
6100
|
-
const blockGuidance = buildCompressedBlockGuidance(state, config.gc, {
|
|
6101
|
-
currentTokens,
|
|
6102
|
-
modelContextLimit,
|
|
6103
|
-
includeHint: tipsText !== null,
|
|
6104
|
-
visibleMessageIds
|
|
6105
|
-
});
|
|
6106
|
-
if (blockGuidance.trim() && suffixMessage) {
|
|
6107
|
-
appendToLastTextPart(suffixMessage, "\n\n" + blockGuidance);
|
|
6148
|
+
if (tipsText && suffixMessage) {
|
|
6149
|
+
appendToLastTextPart(suffixMessage, tipsText);
|
|
6108
6150
|
}
|
|
6151
|
+
injectVisibleIdRange(state, messages, suffixMessage);
|
|
6109
6152
|
}
|
|
6110
|
-
if (tipsText && suffixMessage) {
|
|
6111
|
-
appendToLastTextPart(suffixMessage, tipsText);
|
|
6112
|
-
}
|
|
6113
|
-
injectVisibleIdRange(state, messages, suffixMessage);
|
|
6114
6153
|
if (suffixMessage) {
|
|
6115
|
-
appendToLastTextPart(suffixMessage, "\n
|
|
6154
|
+
appendToLastTextPart(suffixMessage, "\n");
|
|
6116
6155
|
}
|
|
6117
6156
|
if (anchorsChanged) {
|
|
6118
6157
|
void saveSessionState(state, logger);
|
|
@@ -6687,6 +6726,52 @@ ${content}`;
|
|
|
6687
6726
|
});
|
|
6688
6727
|
}
|
|
6689
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
|
+
|
|
6690
6775
|
// lib/logger.ts
|
|
6691
6776
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
6692
6777
|
import { join as join3 } from "path";
|
|
@@ -7861,7 +7946,7 @@ var COMPRESS_TRIGGER_PROMPT = [
|
|
|
7861
7946
|
"Follow the active compress mode, preserve all critical implementation details, and choose safe targets.",
|
|
7862
7947
|
"Return after compress with a brief explanation of what content was compressed."
|
|
7863
7948
|
].join("\n\n");
|
|
7864
|
-
function getTriggerPrompt(
|
|
7949
|
+
function getTriggerPrompt(tool6, state, config, userFocus) {
|
|
7865
7950
|
const base = COMPRESS_TRIGGER_PROMPT;
|
|
7866
7951
|
const compressedBlockGuidance = config.compress.mode === "message" ? "" : buildCompressedBlockGuidance(state, config.gc);
|
|
7867
7952
|
const sections = [base, compressedBlockGuidance];
|
|
@@ -7890,8 +7975,8 @@ async function handleManualToggleCommand(ctx, modeArg) {
|
|
|
7890
7975
|
);
|
|
7891
7976
|
logger.info("Manual mode toggled", { manualMode: state.manualMode });
|
|
7892
7977
|
}
|
|
7893
|
-
async function handleManualTriggerCommand(ctx,
|
|
7894
|
-
return getTriggerPrompt(
|
|
7978
|
+
async function handleManualTriggerCommand(ctx, tool6, userFocus) {
|
|
7979
|
+
return getTriggerPrompt(tool6, ctx.state, ctx.config, userFocus);
|
|
7895
7980
|
}
|
|
7896
7981
|
function applyPendingManualTrigger(state, messages, logger) {
|
|
7897
7982
|
const pending = state.pendingManualTrigger;
|
|
@@ -8624,6 +8709,9 @@ function createSystemPromptHandler(state, logger, config, prompts) {
|
|
|
8624
8709
|
if (effectivePermission === "deny") {
|
|
8625
8710
|
return;
|
|
8626
8711
|
}
|
|
8712
|
+
if (state.nudges.shouldInjectThisTurn === false && !state.manualMode) {
|
|
8713
|
+
return;
|
|
8714
|
+
}
|
|
8627
8715
|
prompts.reload();
|
|
8628
8716
|
const runtimePrompts = prompts.getRuntimePrompts();
|
|
8629
8717
|
const newPrompt = renderSystemPrompt(
|
|
@@ -9152,7 +9240,8 @@ var server = (async (ctx) => {
|
|
|
9152
9240
|
...config.compress.permission !== "deny" && {
|
|
9153
9241
|
compress: config.compress.mode === "message" ? createCompressMessageTool(compressToolContext) : createCompressRangeTool(compressToolContext),
|
|
9154
9242
|
decompress: createDecompressTool(compressToolContext),
|
|
9155
|
-
search_context: createSearchContextTool(compressToolContext)
|
|
9243
|
+
search_context: createSearchContextTool(compressToolContext),
|
|
9244
|
+
acp_status: createAcpStatusTool(compressToolContext)
|
|
9156
9245
|
}
|
|
9157
9246
|
},
|
|
9158
9247
|
config: async (opencodeConfig) => {
|
|
@@ -9168,7 +9257,7 @@ var server = (async (ctx) => {
|
|
|
9168
9257
|
}
|
|
9169
9258
|
const toolsToAdd = [];
|
|
9170
9259
|
if (config.compress.permission !== "deny" && !config.experimental.allowSubAgents) {
|
|
9171
|
-
toolsToAdd.push("compress", "decompress", "search_context");
|
|
9260
|
+
toolsToAdd.push("compress", "decompress", "search_context", "acp_status");
|
|
9172
9261
|
}
|
|
9173
9262
|
if (toolsToAdd.length > 0) {
|
|
9174
9263
|
const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? [];
|
|
@@ -9181,7 +9270,8 @@ var server = (async (ctx) => {
|
|
|
9181
9270
|
const permission = opencodeConfig.permission ?? {};
|
|
9182
9271
|
opencodeConfig.permission = {
|
|
9183
9272
|
...permission,
|
|
9184
|
-
compress: config.compress.permission
|
|
9273
|
+
compress: config.compress.permission,
|
|
9274
|
+
acp_status: "allow"
|
|
9185
9275
|
};
|
|
9186
9276
|
}
|
|
9187
9277
|
hostPermissions.global = opencodeConfig.permission;
|