opencode-acp 1.18.4 → 1.18.5-pr.413.228
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/dist/index.js +725 -524
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/decompress.d.ts.map +1 -1
- package/dist/lib/compress/pipeline.d.ts +15 -1
- package/dist/lib/compress/pipeline.d.ts.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +13 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/sticky-truncation.d.ts +55 -0
- package/dist/lib/messages/sticky-truncation.d.ts.map +1 -0
- package/dist/lib/messages/truncate-tools.d.ts.map +1 -1
- package/dist/lib/state/persistence.d.ts +2 -0
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts +17 -0
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +10 -0
- package/dist/lib/state/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -921,6 +921,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
921
921
|
"compress.images.minBytes",
|
|
922
922
|
"compress.images.ttlDays",
|
|
923
923
|
"compress.completionReserveTokens",
|
|
924
|
+
"compress.emergencyTruncation",
|
|
924
925
|
"gc",
|
|
925
926
|
"gc.algorithm",
|
|
926
927
|
"gc.promotionThreshold",
|
|
@@ -1350,6 +1351,13 @@ function validateConfigTypes(config) {
|
|
|
1350
1351
|
actual: typeof compress.completionReserveTokens
|
|
1351
1352
|
});
|
|
1352
1353
|
}
|
|
1354
|
+
if (compress.emergencyTruncation !== void 0 && typeof compress.emergencyTruncation !== "boolean") {
|
|
1355
|
+
errors.push({
|
|
1356
|
+
key: "compress.emergencyTruncation",
|
|
1357
|
+
expected: "boolean",
|
|
1358
|
+
actual: typeof compress.emergencyTruncation
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1353
1361
|
if (typeof compress.completionReserveTokens === "number" && compress.completionReserveTokens < 0) {
|
|
1354
1362
|
errors.push({
|
|
1355
1363
|
key: "compress.completionReserveTokens",
|
|
@@ -1422,6 +1430,7 @@ function validateConfigTypes(config) {
|
|
|
1422
1430
|
const OVERRIDE_FIELD_TYPES = {
|
|
1423
1431
|
showCompression: "boolean",
|
|
1424
1432
|
summaryBuffer: "boolean",
|
|
1433
|
+
emergencyTruncation: "boolean",
|
|
1425
1434
|
protectTags: "boolean",
|
|
1426
1435
|
protectUserMessages: "boolean",
|
|
1427
1436
|
lastSegmentSoftBlock: "boolean",
|
|
@@ -1809,6 +1818,7 @@ var defaultConfig = {
|
|
|
1809
1818
|
permission: "allow",
|
|
1810
1819
|
showCompression: true,
|
|
1811
1820
|
summaryBuffer: true,
|
|
1821
|
+
emergencyTruncation: false,
|
|
1812
1822
|
candidates: false,
|
|
1813
1823
|
maxContextLimit: "80%",
|
|
1814
1824
|
minContextLimit: "80%",
|
|
@@ -2017,7 +2027,8 @@ function mergeCompress(base, override) {
|
|
|
2017
2027
|
minBytes: override.images?.minBytes ?? base.images?.minBytes ?? DEFAULT_COMPRESS_IMAGES.minBytes,
|
|
2018
2028
|
ttlDays: override.images?.ttlDays ?? base.images?.ttlDays ?? DEFAULT_COMPRESS_IMAGES.ttlDays
|
|
2019
2029
|
},
|
|
2020
|
-
completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens
|
|
2030
|
+
completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens,
|
|
2031
|
+
emergencyTruncation: override.emergencyTruncation ?? base.emergencyTruncation
|
|
2021
2032
|
};
|
|
2022
2033
|
}
|
|
2023
2034
|
function mergeCommands(base, override) {
|
|
@@ -2955,7 +2966,8 @@ function saveSessionState(sessionState, logger, sessionName) {
|
|
|
2955
2966
|
modelContextLimit: sessionState.modelContextLimit,
|
|
2956
2967
|
modelProviderID: sessionState.modelProviderID,
|
|
2957
2968
|
modelID: sessionState.modelID,
|
|
2958
|
-
imageArchive: sessionState.imageArchive
|
|
2969
|
+
imageArchive: sessionState.imageArchive,
|
|
2970
|
+
stickyTruncations: sessionState.stickyTruncations
|
|
2959
2971
|
};
|
|
2960
2972
|
const key = saveQueueKey(sessionState.sessionId, sessionState.storageDir);
|
|
2961
2973
|
let queue = saveQueues.get(key);
|
|
@@ -4978,6 +4990,30 @@ async function updatePerTurnState(state, logger, messages) {
|
|
|
4978
4990
|
state.currentTurn = countTurns(state, messages);
|
|
4979
4991
|
}
|
|
4980
4992
|
var REGISTRY_SOFT_CAP = 32;
|
|
4993
|
+
function createSessionGuard() {
|
|
4994
|
+
const locks = /* @__PURE__ */ new Map();
|
|
4995
|
+
return async (sessionId, fn) => {
|
|
4996
|
+
const previous = locks.get(sessionId) ?? Promise.resolve();
|
|
4997
|
+
let release;
|
|
4998
|
+
const released = new Promise((resolve2) => {
|
|
4999
|
+
release = resolve2;
|
|
5000
|
+
});
|
|
5001
|
+
const chain = previous.then(() => released);
|
|
5002
|
+
locks.set(sessionId, chain);
|
|
5003
|
+
try {
|
|
5004
|
+
await previous;
|
|
5005
|
+
} catch {
|
|
5006
|
+
}
|
|
5007
|
+
try {
|
|
5008
|
+
return await fn();
|
|
5009
|
+
} finally {
|
|
5010
|
+
release();
|
|
5011
|
+
if (locks.get(sessionId) === chain) {
|
|
5012
|
+
locks.delete(sessionId);
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
};
|
|
5016
|
+
}
|
|
4981
5017
|
var SessionStateRegistry = class {
|
|
4982
5018
|
constructor(logger, projectDir) {
|
|
4983
5019
|
this.logger = logger;
|
|
@@ -5037,9 +5073,17 @@ var SessionStateRegistry = class {
|
|
|
5037
5073
|
get size() {
|
|
5038
5074
|
return this.states.size;
|
|
5039
5075
|
}
|
|
5076
|
+
// [Issue #404] Per-session FIFO mutex serializing same-session mutations
|
|
5077
|
+
// (message transforms, compress/decompress tools, event-hook saves,
|
|
5078
|
+
// system-hook limit writes). Shared factory so test stubs compose the
|
|
5079
|
+
// exact same implementation instead of drifting.
|
|
5080
|
+
// Not readonly: the session-guard wiring test wraps this to observe
|
|
5081
|
+
// acquisition order (Issue #404).
|
|
5082
|
+
withSessionGuard = createSessionGuard();
|
|
5040
5083
|
// Idempotent: ensureSessionInitialized returns immediately once
|
|
5041
|
-
// state.sessionId === sessionId
|
|
5042
|
-
// so
|
|
5084
|
+
// state.sessionId === sessionId, and coalesces concurrent inits per state
|
|
5085
|
+
// object ([Issue #404]), so racing calls for the same session never
|
|
5086
|
+
// re-reset or observe partially initialized state.
|
|
5043
5087
|
async getOrCreate(client, sessionId, messages, config) {
|
|
5044
5088
|
let state = this.states.get(sessionId);
|
|
5045
5089
|
if (!state) {
|
|
@@ -5124,7 +5168,8 @@ function createSessionState() {
|
|
|
5124
5168
|
qualityGateRetryPending: false,
|
|
5125
5169
|
noContextLimitWarned: false,
|
|
5126
5170
|
imageArchive: {},
|
|
5127
|
-
imagePruneDone: false
|
|
5171
|
+
imagePruneDone: false,
|
|
5172
|
+
stickyTruncations: {}
|
|
5128
5173
|
};
|
|
5129
5174
|
}
|
|
5130
5175
|
function resetSessionState(state) {
|
|
@@ -5170,11 +5215,35 @@ function resetSessionState(state) {
|
|
|
5170
5215
|
state.noContextLimitWarned = false;
|
|
5171
5216
|
state.imageArchive = {};
|
|
5172
5217
|
state.imagePruneDone = false;
|
|
5218
|
+
state.stickyTruncations = {};
|
|
5173
5219
|
}
|
|
5220
|
+
var inflightInits = /* @__PURE__ */ new WeakMap();
|
|
5174
5221
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
|
|
5222
|
+
const inflight = inflightInits.get(state);
|
|
5223
|
+
if (inflight) {
|
|
5224
|
+
await inflight;
|
|
5225
|
+
return;
|
|
5226
|
+
}
|
|
5175
5227
|
if (state.sessionId === sessionId) {
|
|
5176
5228
|
return;
|
|
5177
5229
|
}
|
|
5230
|
+
const run = runSessionInitialization(
|
|
5231
|
+
client,
|
|
5232
|
+
state,
|
|
5233
|
+
sessionId,
|
|
5234
|
+
logger,
|
|
5235
|
+
messages,
|
|
5236
|
+
config,
|
|
5237
|
+
projectDir
|
|
5238
|
+
);
|
|
5239
|
+
inflightInits.set(state, run);
|
|
5240
|
+
try {
|
|
5241
|
+
await run;
|
|
5242
|
+
} finally {
|
|
5243
|
+
inflightInits.delete(state);
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
async function runSessionInitialization(client, state, sessionId, logger, messages, config, projectDir) {
|
|
5178
5247
|
resetSessionState(state);
|
|
5179
5248
|
state.sessionId = sessionId;
|
|
5180
5249
|
state.storageDir = config?.storagePath ? resolveStorageDir(config.storagePath, projectDir ?? cwd()) : void 0;
|
|
@@ -5198,6 +5267,13 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
5198
5267
|
if (parentSessionId) {
|
|
5199
5268
|
try {
|
|
5200
5269
|
const parent = await loadSessionState(parentSessionId, logger);
|
|
5270
|
+
if (parent?.stickyTruncations) {
|
|
5271
|
+
for (const [callID, output] of Object.entries(parent.stickyTruncations)) {
|
|
5272
|
+
if (typeof output === "string") {
|
|
5273
|
+
state.stickyTruncations[callID] = output;
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
}
|
|
5201
5277
|
const response = parent ? await client.session.messages({ path: { id: parentSessionId } }) : void 0;
|
|
5202
5278
|
const parentMessages = Array.isArray(response?.data) ? response.data : Array.isArray(response) ? response : [];
|
|
5203
5279
|
if (parent && parentMessages.length > 0) {
|
|
@@ -5263,6 +5339,14 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
5263
5339
|
}
|
|
5264
5340
|
}
|
|
5265
5341
|
}
|
|
5342
|
+
const persistedSticky = persisted.stickyTruncations;
|
|
5343
|
+
if (persistedSticky && typeof persistedSticky === "object") {
|
|
5344
|
+
for (const [callID, output] of Object.entries(persistedSticky)) {
|
|
5345
|
+
if (typeof output === "string") {
|
|
5346
|
+
state.stickyTruncations[callID] = output;
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5266
5350
|
const persistedAny = persisted;
|
|
5267
5351
|
if (persistedAny._persistedMessageIds) {
|
|
5268
5352
|
state.messageIds = {
|
|
@@ -6537,7 +6621,7 @@ function restoreCompressionState(state, snapshot) {
|
|
|
6537
6621
|
state.prune.messages = structuredClone(snapshot.messages);
|
|
6538
6622
|
state.stats = { ...snapshot.stats };
|
|
6539
6623
|
}
|
|
6540
|
-
async function
|
|
6624
|
+
async function requestCompressPermission(toolCtx, title) {
|
|
6541
6625
|
await toolCtx.ask({
|
|
6542
6626
|
permission: "compress",
|
|
6543
6627
|
patterns: ["*"],
|
|
@@ -6545,6 +6629,11 @@ async function prepareSession(ctx, toolCtx, title) {
|
|
|
6545
6629
|
metadata: {}
|
|
6546
6630
|
});
|
|
6547
6631
|
toolCtx.metadata({ title });
|
|
6632
|
+
}
|
|
6633
|
+
async function prepareSession(ctx, toolCtx, title, opts) {
|
|
6634
|
+
if (!opts?.preApproved) {
|
|
6635
|
+
await requestCompressPermission(toolCtx, title);
|
|
6636
|
+
}
|
|
6548
6637
|
const rawMessages = await fetchSessionMessages(ctx.client, toolCtx.sessionID);
|
|
6549
6638
|
await ensureSessionInitialized(
|
|
6550
6639
|
ctx.client,
|
|
@@ -8170,240 +8259,246 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
8170
8259
|
}
|
|
8171
8260
|
}
|
|
8172
8261
|
const callId = typeof toolCtx.callID === "string" ? toolCtx.callID : void 0;
|
|
8173
|
-
const
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
...ctx0,
|
|
8181
|
-
config: applyCompressOverrides(ctx0.config, providerId, modelId)
|
|
8182
|
-
};
|
|
8183
|
-
const needsSmartPlan = requiresSmartPlan(
|
|
8184
|
-
ctx.config.compress.smartPlanRequired === true,
|
|
8185
|
-
input.content
|
|
8186
|
-
);
|
|
8187
|
-
if (needsSmartPlan && input.content.length !== 1) {
|
|
8188
|
-
throw new Error(
|
|
8189
|
-
'Raw-message compression accepts exactly one canonical SMART PLAN range per call. Use the latest ACP nudge, or call acp_status({scope:"uncompressed"}) to refresh it.'
|
|
8190
|
-
);
|
|
8191
|
-
}
|
|
8192
|
-
const { plans: filteredPlans, totalChars } = prepareExecutableRangePlans(
|
|
8193
|
-
input,
|
|
8194
|
-
searchContext,
|
|
8195
|
-
ctx.state,
|
|
8196
|
-
ctx.config,
|
|
8197
|
-
ctx.logger
|
|
8198
|
-
);
|
|
8199
|
-
if (needsSmartPlan) {
|
|
8200
|
-
validateSmartPlan(
|
|
8201
|
-
toolCtx.sessionID,
|
|
8202
|
-
filteredPlans[0].entry.startId,
|
|
8203
|
-
filteredPlans[0].entry.endId,
|
|
8204
|
-
filteredPlans[0].selection.messageIds,
|
|
8205
|
-
totalChars,
|
|
8206
|
-
ctx.state.prune.messages.structureVersion ?? 0
|
|
8207
|
-
);
|
|
8208
|
-
}
|
|
8209
|
-
const notifications = [];
|
|
8210
|
-
let preparedPlans = [];
|
|
8211
|
-
let totalCompressedMessages = 0;
|
|
8212
|
-
for (const plan of filteredPlans) {
|
|
8213
|
-
const parsedPlaceholders = parseBlockPlaceholders(plan.entry.summary);
|
|
8214
|
-
validateSummaryPlaceholders(
|
|
8215
|
-
parsedPlaceholders,
|
|
8216
|
-
plan.selection.requiredBlockIds,
|
|
8217
|
-
plan.selection.startReference,
|
|
8218
|
-
plan.selection.endReference,
|
|
8219
|
-
searchContext.summaryByBlockId,
|
|
8220
|
-
ctx.logger
|
|
8221
|
-
);
|
|
8222
|
-
const injected = injectBlockPlaceholders(
|
|
8223
|
-
plan.entry.summary,
|
|
8224
|
-
parsedPlaceholders,
|
|
8225
|
-
searchContext.summaryByBlockId,
|
|
8226
|
-
plan.selection.startReference,
|
|
8227
|
-
plan.selection.endReference
|
|
8262
|
+
const title = `Compress Range: ${input.topic ?? "(batch)"}`;
|
|
8263
|
+
const run = async () => {
|
|
8264
|
+
const { rawMessages, searchContext } = await prepareSession(
|
|
8265
|
+
ctx0,
|
|
8266
|
+
toolCtx,
|
|
8267
|
+
title,
|
|
8268
|
+
{ preApproved: true }
|
|
8228
8269
|
);
|
|
8229
|
-
const
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8270
|
+
const { providerId, modelId } = getModelInfo(rawMessages);
|
|
8271
|
+
const ctx = {
|
|
8272
|
+
...ctx0,
|
|
8273
|
+
config: applyCompressOverrides(ctx0.config, providerId, modelId)
|
|
8274
|
+
};
|
|
8275
|
+
const needsSmartPlan = requiresSmartPlan(
|
|
8276
|
+
ctx.config.compress.smartPlanRequired === true,
|
|
8277
|
+
input.content
|
|
8235
8278
|
);
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8279
|
+
if (needsSmartPlan && input.content.length !== 1) {
|
|
8280
|
+
throw new Error(
|
|
8281
|
+
'Raw-message compression accepts exactly one canonical SMART PLAN range per call. Use the latest ACP nudge, or call acp_status({scope:"uncompressed"}) to refresh it.'
|
|
8282
|
+
);
|
|
8283
|
+
}
|
|
8284
|
+
const { plans: filteredPlans, totalChars } = prepareExecutableRangePlans(
|
|
8285
|
+
input,
|
|
8239
8286
|
searchContext,
|
|
8240
8287
|
ctx.state,
|
|
8241
|
-
ctx.config
|
|
8242
|
-
|
|
8243
|
-
const summaryWithTools = await appendProtectedTools(
|
|
8244
|
-
ctx.client,
|
|
8245
|
-
ctx.state,
|
|
8246
|
-
summaryWithPromptInfo,
|
|
8247
|
-
plan.selection,
|
|
8248
|
-
searchContext,
|
|
8249
|
-
ctx.config.compress.protectedTools,
|
|
8250
|
-
ctx.config.protectedFilePatterns
|
|
8251
|
-
);
|
|
8252
|
-
const completedSummary = appendMissingBlockSummaries(
|
|
8253
|
-
summaryWithTools,
|
|
8254
|
-
[],
|
|
8255
|
-
searchContext.summaryByBlockId,
|
|
8256
|
-
injected.consumedBlockIds
|
|
8257
|
-
);
|
|
8258
|
-
const boundaryConsumed = extractBoundaryConsumedBlocks2(
|
|
8259
|
-
plan.selection.startReference,
|
|
8260
|
-
plan.selection.endReference
|
|
8288
|
+
ctx.config,
|
|
8289
|
+
ctx.logger
|
|
8261
8290
|
);
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8291
|
+
if (needsSmartPlan) {
|
|
8292
|
+
validateSmartPlan(
|
|
8293
|
+
toolCtx.sessionID,
|
|
8294
|
+
filteredPlans[0].entry.startId,
|
|
8295
|
+
filteredPlans[0].entry.endId,
|
|
8296
|
+
filteredPlans[0].selection.messageIds,
|
|
8297
|
+
totalChars,
|
|
8298
|
+
ctx.state.prune.messages.structureVersion ?? 0
|
|
8299
|
+
);
|
|
8300
|
+
}
|
|
8301
|
+
const notifications = [];
|
|
8302
|
+
let preparedPlans = [];
|
|
8303
|
+
let totalCompressedMessages = 0;
|
|
8304
|
+
for (const plan of filteredPlans) {
|
|
8305
|
+
const parsedPlaceholders = parseBlockPlaceholders(plan.entry.summary);
|
|
8306
|
+
validateSummaryPlaceholders(
|
|
8307
|
+
parsedPlaceholders,
|
|
8308
|
+
plan.selection.requiredBlockIds,
|
|
8309
|
+
plan.selection.startReference,
|
|
8310
|
+
plan.selection.endReference,
|
|
8311
|
+
searchContext.summaryByBlockId,
|
|
8312
|
+
ctx.logger
|
|
8313
|
+
);
|
|
8314
|
+
const injected = injectBlockPlaceholders(
|
|
8315
|
+
plan.entry.summary,
|
|
8316
|
+
parsedPlaceholders,
|
|
8317
|
+
searchContext.summaryByBlockId,
|
|
8318
|
+
plan.selection.startReference,
|
|
8319
|
+
plan.selection.endReference
|
|
8320
|
+
);
|
|
8321
|
+
const summaryWithUsers = appendProtectedUserMessages(
|
|
8322
|
+
injected.expandedSummary,
|
|
8277
8323
|
plan.selection,
|
|
8324
|
+
searchContext,
|
|
8278
8325
|
ctx.state,
|
|
8279
|
-
ctx.config.compress.
|
|
8326
|
+
ctx.config.compress.protectUserMessages
|
|
8327
|
+
);
|
|
8328
|
+
const summaryWithPromptInfo = appendProtectedPromptInfo(
|
|
8329
|
+
summaryWithUsers,
|
|
8330
|
+
plan.selection,
|
|
8331
|
+
searchContext,
|
|
8332
|
+
ctx.state,
|
|
8333
|
+
ctx.config.compress.protectTags
|
|
8334
|
+
);
|
|
8335
|
+
const summaryWithTools = await appendProtectedTools(
|
|
8336
|
+
ctx.client,
|
|
8337
|
+
ctx.state,
|
|
8338
|
+
summaryWithPromptInfo,
|
|
8339
|
+
plan.selection,
|
|
8340
|
+
searchContext,
|
|
8341
|
+
ctx.config.compress.protectedTools,
|
|
8342
|
+
ctx.config.protectedFilePatterns
|
|
8343
|
+
);
|
|
8344
|
+
const completedSummary = appendMissingBlockSummaries(
|
|
8345
|
+
summaryWithTools,
|
|
8346
|
+
[],
|
|
8347
|
+
searchContext.summaryByBlockId,
|
|
8348
|
+
injected.consumedBlockIds
|
|
8349
|
+
);
|
|
8350
|
+
const boundaryConsumed = extractBoundaryConsumedBlocks2(
|
|
8351
|
+
plan.selection.startReference,
|
|
8352
|
+
plan.selection.endReference
|
|
8353
|
+
);
|
|
8354
|
+
const seenConsumed = /* @__PURE__ */ new Set();
|
|
8355
|
+
const mergeConsumedBlockIds = [
|
|
8356
|
+
...plan.selection.requiredBlockIds,
|
|
8357
|
+
...boundaryConsumed
|
|
8358
|
+
].filter((id) => {
|
|
8359
|
+
if (seenConsumed.has(id)) return false;
|
|
8360
|
+
seenConsumed.add(id);
|
|
8361
|
+
return true;
|
|
8362
|
+
});
|
|
8363
|
+
preparedPlans.push({
|
|
8364
|
+
entry: plan.entry,
|
|
8365
|
+
selection: plan.selection,
|
|
8366
|
+
anchorMessageId: plan.anchorMessageId,
|
|
8367
|
+
finalSummary: appendArchivedImagePointers(
|
|
8368
|
+
completedSummary.expandedSummary,
|
|
8369
|
+
plan.selection,
|
|
8370
|
+
ctx.state,
|
|
8371
|
+
ctx.config.compress.images?.enabled !== false
|
|
8372
|
+
),
|
|
8373
|
+
consumedBlockIds: mergeConsumedBlockIds
|
|
8374
|
+
});
|
|
8375
|
+
}
|
|
8376
|
+
const partition = partitionPhantomPlans(
|
|
8377
|
+
identifyPhantomPlans(
|
|
8378
|
+
ctx.state,
|
|
8379
|
+
preparedPlans.map((p) => ({
|
|
8380
|
+
messageIds: p.selection.messageIds,
|
|
8381
|
+
consumedBlockIds: p.consumedBlockIds
|
|
8382
|
+
}))
|
|
8280
8383
|
),
|
|
8281
|
-
|
|
8282
|
-
});
|
|
8283
|
-
}
|
|
8284
|
-
const partition = partitionPhantomPlans(
|
|
8285
|
-
identifyPhantomPlans(
|
|
8286
|
-
ctx.state,
|
|
8287
|
-
preparedPlans.map((p) => ({
|
|
8288
|
-
messageIds: p.selection.messageIds,
|
|
8289
|
-
consumedBlockIds: p.consumedBlockIds
|
|
8290
|
-
}))
|
|
8291
|
-
),
|
|
8292
|
-
preparedPlans.length
|
|
8293
|
-
);
|
|
8294
|
-
let phantomSkipNotice = null;
|
|
8295
|
-
if (partition.kind === "all-phantom") {
|
|
8296
|
-
ctx.logger.warn("Batch compress: ALL entries phantom", {
|
|
8297
|
-
totalCount: preparedPlans.length,
|
|
8298
|
-
details: partition.details
|
|
8299
|
-
});
|
|
8300
|
-
throw new Error(buildPhantomErrorMessage(partition.details));
|
|
8301
|
-
}
|
|
8302
|
-
if (partition.kind === "partial") {
|
|
8303
|
-
ctx.logger.warn("Batch compress: phantom entries detected", {
|
|
8304
|
-
phantomCount: partition.dropIndices.length,
|
|
8305
|
-
totalCount: preparedPlans.length,
|
|
8306
|
-
phantomIndices: partition.dropIndices,
|
|
8307
|
-
details: partition.details
|
|
8308
|
-
});
|
|
8309
|
-
const dropSet = new Set(partition.dropIndices);
|
|
8310
|
-
preparedPlans = preparedPlans.filter((_, i) => !dropSet.has(i));
|
|
8311
|
-
phantomSkipNotice = partition.notice;
|
|
8312
|
-
}
|
|
8313
|
-
const acknowledgeRisk = args.acknowledgeRisk === true;
|
|
8314
|
-
const qualityGateRetryPendingBefore = ctx.state.qualityGateRetryPending;
|
|
8315
|
-
const bypassQuality = acknowledgeRisk && ctx.state.qualityGateRetryPending;
|
|
8316
|
-
const ignoredAcknowledgeRisk = acknowledgeRisk && !ctx.state.qualityGateRetryPending;
|
|
8317
|
-
if (ignoredAcknowledgeRisk) {
|
|
8318
|
-
ctx.logger.warn(
|
|
8319
|
-
"compress: acknowledgeRisk ignored \u2014 no quality gate rejection pending"
|
|
8384
|
+
preparedPlans.length
|
|
8320
8385
|
);
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8386
|
+
let phantomSkipNotice = null;
|
|
8387
|
+
if (partition.kind === "all-phantom") {
|
|
8388
|
+
ctx.logger.warn("Batch compress: ALL entries phantom", {
|
|
8389
|
+
totalCount: preparedPlans.length,
|
|
8390
|
+
details: partition.details
|
|
8391
|
+
});
|
|
8392
|
+
throw new Error(buildPhantomErrorMessage(partition.details));
|
|
8393
|
+
}
|
|
8394
|
+
if (partition.kind === "partial") {
|
|
8395
|
+
ctx.logger.warn("Batch compress: phantom entries detected", {
|
|
8396
|
+
phantomCount: partition.dropIndices.length,
|
|
8397
|
+
totalCount: preparedPlans.length,
|
|
8398
|
+
phantomIndices: partition.dropIndices,
|
|
8399
|
+
details: partition.details
|
|
8400
|
+
});
|
|
8401
|
+
const dropSet = new Set(partition.dropIndices);
|
|
8402
|
+
preparedPlans = preparedPlans.filter((_, i) => !dropSet.has(i));
|
|
8403
|
+
phantomSkipNotice = partition.notice;
|
|
8404
|
+
}
|
|
8405
|
+
const acknowledgeRisk = args.acknowledgeRisk === true;
|
|
8406
|
+
const qualityGateRetryPendingBefore = ctx.state.qualityGateRetryPending;
|
|
8407
|
+
const bypassQuality = acknowledgeRisk && ctx.state.qualityGateRetryPending;
|
|
8408
|
+
const ignoredAcknowledgeRisk = acknowledgeRisk && !ctx.state.qualityGateRetryPending;
|
|
8409
|
+
if (ignoredAcknowledgeRisk) {
|
|
8410
|
+
ctx.logger.warn(
|
|
8411
|
+
"compress: acknowledgeRisk ignored \u2014 no quality gate rejection pending"
|
|
8332
8412
|
);
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
result,
|
|
8413
|
+
}
|
|
8414
|
+
ctx.state.qualityGateRetryPending = false;
|
|
8415
|
+
if (!bypassQuality) {
|
|
8416
|
+
for (const plan of preparedPlans) {
|
|
8417
|
+
const result = evaluatePreCommitQuality(
|
|
8418
|
+
rawMessages,
|
|
8419
|
+
plan.selection.messageIds,
|
|
8420
|
+
plan.selection.messageTokenById,
|
|
8421
|
+
plan.finalSummary,
|
|
8422
|
+
ctx.config,
|
|
8344
8423
|
ctx.logger
|
|
8345
8424
|
);
|
|
8425
|
+
if (result && !result.passed) {
|
|
8426
|
+
ctx.state.qualityGateRetryPending = true;
|
|
8427
|
+
throw buildQualityRejectionError(
|
|
8428
|
+
{
|
|
8429
|
+
startId: plan.entry.startId,
|
|
8430
|
+
endId: plan.entry.endId,
|
|
8431
|
+
summary: plan.finalSummary,
|
|
8432
|
+
messageIds: plan.selection.messageIds,
|
|
8433
|
+
messageTokenById: plan.selection.messageTokenById
|
|
8434
|
+
},
|
|
8435
|
+
result,
|
|
8436
|
+
ctx.logger
|
|
8437
|
+
);
|
|
8438
|
+
}
|
|
8346
8439
|
}
|
|
8347
8440
|
}
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8441
|
+
const snapshot = snapshotCompressionState(ctx.state);
|
|
8442
|
+
const runId = allocateRunId(ctx.state);
|
|
8443
|
+
try {
|
|
8444
|
+
for (const preparedPlan of preparedPlans) {
|
|
8445
|
+
const blockId = allocateBlockId(ctx.state);
|
|
8446
|
+
const keepResult = resolveKeepMarkers(
|
|
8447
|
+
preparedPlan.finalSummary,
|
|
8448
|
+
rawMessages,
|
|
8449
|
+
ctx.state,
|
|
8450
|
+
ctx.config
|
|
8451
|
+
);
|
|
8452
|
+
preparedPlan.finalSummary = keepResult.summary;
|
|
8453
|
+
const storedSummary = wrapCompressedSummary(blockId, preparedPlan.finalSummary);
|
|
8454
|
+
const summaryTokens = countTokens2(storedSummary);
|
|
8455
|
+
const applied = applyCompressionState(
|
|
8456
|
+
ctx.state,
|
|
8457
|
+
{
|
|
8458
|
+
topic: preparedPlan.entry.topic ?? input.topic ?? "",
|
|
8459
|
+
batchTopic: input.topic,
|
|
8460
|
+
startId: preparedPlan.entry.startId,
|
|
8461
|
+
endId: preparedPlan.entry.endId,
|
|
8462
|
+
mode: "range",
|
|
8463
|
+
runId,
|
|
8464
|
+
compressMessageId: toolCtx.messageID,
|
|
8465
|
+
compressCallId: callId,
|
|
8466
|
+
summaryTokens
|
|
8467
|
+
},
|
|
8468
|
+
preparedPlan.selection,
|
|
8469
|
+
preparedPlan.anchorMessageId,
|
|
8470
|
+
blockId,
|
|
8471
|
+
storedSummary,
|
|
8472
|
+
preparedPlan.consumedBlockIds,
|
|
8473
|
+
ctx.config.gc
|
|
8474
|
+
);
|
|
8475
|
+
totalCompressedMessages += applied.messageIds.length;
|
|
8476
|
+
notifications.push({
|
|
8477
|
+
blockId,
|
|
8371
8478
|
runId,
|
|
8372
|
-
|
|
8373
|
-
compressCallId: callId,
|
|
8479
|
+
summary: preparedPlan.finalSummary,
|
|
8374
8480
|
summaryTokens
|
|
8375
|
-
}
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
totalCompressedMessages += applied.messageIds.length;
|
|
8384
|
-
notifications.push({
|
|
8385
|
-
blockId,
|
|
8386
|
-
runId,
|
|
8387
|
-
summary: preparedPlan.finalSummary,
|
|
8388
|
-
summaryTokens
|
|
8389
|
-
});
|
|
8481
|
+
});
|
|
8482
|
+
}
|
|
8483
|
+
await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic);
|
|
8484
|
+
if (needsSmartPlan) consumeSmartPlan(toolCtx.sessionID);
|
|
8485
|
+
} catch (error) {
|
|
8486
|
+
restoreCompressionState(ctx.state, snapshot);
|
|
8487
|
+
ctx.state.qualityGateRetryPending = qualityGateRetryPendingBefore;
|
|
8488
|
+
throw error;
|
|
8390
8489
|
}
|
|
8391
|
-
|
|
8392
|
-
if (needsSmartPlan) consumeSmartPlan(toolCtx.sessionID);
|
|
8393
|
-
} catch (error) {
|
|
8394
|
-
restoreCompressionState(ctx.state, snapshot);
|
|
8395
|
-
ctx.state.qualityGateRetryPending = qualityGateRetryPendingBefore;
|
|
8396
|
-
throw error;
|
|
8397
|
-
}
|
|
8398
|
-
const skippedNote = phantomSkipNotice !== null ? `
|
|
8490
|
+
const skippedNote = phantomSkipNotice !== null ? `
|
|
8399
8491
|
\u26A0\uFE0F ${phantomSkipNotice}
|
|
8400
8492
|
` : "";
|
|
8401
|
-
|
|
8493
|
+
const ackNote = ignoredAcknowledgeRisk ? `
|
|
8402
8494
|
\u26A0\uFE0F acknowledgeRisk was ignored: no quality gate rejection was pending, so quality checks ran normally. Only pass it when retrying immediately after a quality gate rejection.
|
|
8403
8495
|
` : "";
|
|
8404
|
-
|
|
8496
|
+
return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.${skippedNote}${ackNote}
|
|
8405
8497
|
IMPORTANT: This was an automatic context compression. You MUST continue your previous task exactly where you left off. Do NOT ask the user what to do next.
|
|
8406
8498
|
\u{1F4A1} Tip: Use search_context('keyword') to find compressed content when you need it later.`;
|
|
8499
|
+
};
|
|
8500
|
+
await requestCompressPermission(toolCtx, title);
|
|
8501
|
+
return factoryCtx.registry.withSessionGuard(toolCtx.sessionID, () => run());
|
|
8407
8502
|
}
|
|
8408
8503
|
});
|
|
8409
8504
|
}
|
|
@@ -10255,14 +10350,10 @@ function extractTextContent(msg) {
|
|
|
10255
10350
|
}
|
|
10256
10351
|
|
|
10257
10352
|
// lib/compress/decompress.ts
|
|
10258
|
-
async function prepareDecompressSession(ctx, toolCtx) {
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
always: ["*"],
|
|
10263
|
-
metadata: {}
|
|
10264
|
-
});
|
|
10265
|
-
toolCtx.metadata({ title: "Decompress" });
|
|
10353
|
+
async function prepareDecompressSession(ctx, toolCtx, opts) {
|
|
10354
|
+
if (!opts?.preApproved) {
|
|
10355
|
+
await requestCompressPermission(toolCtx, "Decompress");
|
|
10356
|
+
}
|
|
10266
10357
|
const rawMessages = await fetchSessionMessages(ctx.client, toolCtx.sessionID);
|
|
10267
10358
|
await ensureSessionInitialized(
|
|
10268
10359
|
ctx.client,
|
|
@@ -10439,139 +10530,143 @@ function createDecompressTool(factoryCtx) {
|
|
|
10439
10530
|
args: buildSchema2(),
|
|
10440
10531
|
async execute(args, toolCtx) {
|
|
10441
10532
|
const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
|
|
10442
|
-
const
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
const targets = resolved.targets;
|
|
10452
|
-
const messagesState = ctx.state.prune.messages;
|
|
10453
|
-
const activeBlocks = [];
|
|
10454
|
-
for (const target of targets) {
|
|
10455
|
-
for (const block of target.blocks) {
|
|
10456
|
-
if (block.active) {
|
|
10457
|
-
activeBlocks.push(block);
|
|
10458
|
-
}
|
|
10533
|
+
const run = async () => {
|
|
10534
|
+
const { rawMessages } = await prepareDecompressSession(ctx, toolCtx, { preApproved: true });
|
|
10535
|
+
const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
10536
|
+
const contextUsageBefore = effectiveLimitBefore ? Math.round(
|
|
10537
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
|
|
10538
|
+
) : void 0;
|
|
10539
|
+
const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
|
|
10540
|
+
if (!resolved.ok) {
|
|
10541
|
+
return resolved.error;
|
|
10459
10542
|
}
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
const
|
|
10463
|
-
const
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
const resolvedPath = path.resolve(targetPath);
|
|
10470
|
-
const isAllowed = allowedDirs.some((dir) => {
|
|
10471
|
-
const rel = path.relative(dir, resolvedPath);
|
|
10472
|
-
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
10473
|
-
});
|
|
10474
|
-
if (!isAllowed) {
|
|
10475
|
-
return `Error: toFile path must be under ${os.tmpdir()} or ~/.cache/opencode/. Got: ${targetPath}`;
|
|
10543
|
+
const targets = resolved.targets;
|
|
10544
|
+
const messagesState = ctx.state.prune.messages;
|
|
10545
|
+
const activeBlocks = [];
|
|
10546
|
+
for (const target of targets) {
|
|
10547
|
+
for (const block of target.blocks) {
|
|
10548
|
+
if (block.active) {
|
|
10549
|
+
activeBlocks.push(block);
|
|
10550
|
+
}
|
|
10551
|
+
}
|
|
10476
10552
|
}
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10553
|
+
if (args.toFile) {
|
|
10554
|
+
const targetPath = args.toFile;
|
|
10555
|
+
const os = await import("os");
|
|
10556
|
+
const path = await import("path");
|
|
10557
|
+
const allowedDirs = [
|
|
10558
|
+
os.tmpdir() + "/",
|
|
10559
|
+
path.join(os.homedir(), ".cache", "opencode") + "/"
|
|
10560
|
+
];
|
|
10561
|
+
const resolvedPath = path.resolve(targetPath);
|
|
10562
|
+
const isAllowed = allowedDirs.some((dir) => {
|
|
10563
|
+
const rel = path.relative(dir, resolvedPath);
|
|
10564
|
+
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
10565
|
+
});
|
|
10566
|
+
if (!isAllowed) {
|
|
10567
|
+
return `Error: toFile path must be under ${os.tmpdir()} or ~/.cache/opencode/. Got: ${targetPath}`;
|
|
10568
|
+
}
|
|
10569
|
+
const msgIdSet = /* @__PURE__ */ new Set();
|
|
10570
|
+
for (const block of activeBlocks) {
|
|
10571
|
+
for (const id of block.effectiveMessageIds ?? []) {
|
|
10572
|
+
msgIdSet.add(id);
|
|
10573
|
+
}
|
|
10574
|
+
}
|
|
10575
|
+
const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
|
|
10576
|
+
const lines2 = blockMessages.map(extractMessageText2);
|
|
10577
|
+
const { writeFile: writeFile4 } = await import("fs/promises");
|
|
10578
|
+
let fileContent;
|
|
10579
|
+
let sourceNote;
|
|
10580
|
+
if (lines2.length > 0) {
|
|
10581
|
+
fileContent = lines2.join("\n\n---\n\n");
|
|
10582
|
+
sourceNote = `${blockMessages.length} original message${blockMessages.length === 1 ? "" : "s"}`;
|
|
10583
|
+
} else {
|
|
10584
|
+
fileContent = targets[0]?.blocks[0]?.summary ?? "(no content available)";
|
|
10585
|
+
sourceNote = `no original messages found; wrote block summary (${targets.length} block${targets.length === 1 ? "" : "s"})`;
|
|
10481
10586
|
}
|
|
10587
|
+
await writeFile4(targetPath, fileContent, "utf-8");
|
|
10588
|
+
const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10589
|
+
return `Block(s) ${displayIds2} content (${sourceNote}, ${fileContent.length} chars) written to ${targetPath}. Block(s) stay compressed \u2014 context unchanged. Use read tool to access specific parts.`;
|
|
10482
10590
|
}
|
|
10483
|
-
const
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
const
|
|
10507
|
-
const
|
|
10508
|
-
(
|
|
10591
|
+
const availability = checkDecompressSourceAvailability(
|
|
10592
|
+
messagesState,
|
|
10593
|
+
targets,
|
|
10594
|
+
{ full: args.full === true },
|
|
10595
|
+
new Set(rawMessages.map((m) => m.info.id))
|
|
10596
|
+
);
|
|
10597
|
+
if (availability.missingMessageIds.length > 0) {
|
|
10598
|
+
const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10599
|
+
const missingRefs = availability.missingMessageIds.map(
|
|
10600
|
+
(id) => ctx.state.messageIds.byRawId.get(id) ?? id
|
|
10601
|
+
);
|
|
10602
|
+
const shownRefs = missingRefs.slice(0, 10).join(", ");
|
|
10603
|
+
const extraCount = missingRefs.length - Math.min(missingRefs.length, 10);
|
|
10604
|
+
ctx.logger.warn("Decompress aborted: source messages missing from host history", {
|
|
10605
|
+
targetDisplayIds: displayIds2,
|
|
10606
|
+
full: args.full === true,
|
|
10607
|
+
requiredCount: availability.requiredMessageIds.length,
|
|
10608
|
+
missingCount: availability.missingMessageIds.length,
|
|
10609
|
+
missingRefs
|
|
10610
|
+
});
|
|
10611
|
+
return `Error: Cannot decompress ${displayIds2}: ${availability.missingMessageIds.length} of ${availability.requiredMessageIds.length} source message(s) are no longer present in session history (removed by native compaction or external deletion). Missing: ${shownRefs}` + (extraCount > 0 ? ` (+${extraCount} more)` : "") + `. Block(s) stay compressed \u2014 the summary remains available and nothing was changed. This is a missing-source condition, not a transient fetch failure.`;
|
|
10612
|
+
}
|
|
10613
|
+
const activeMessagesBefore = snapshotActiveMessages(messagesState);
|
|
10614
|
+
const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
|
|
10615
|
+
for (const target of targets) {
|
|
10616
|
+
deactivateCompressionTarget(messagesState, target, { full: args.full === true });
|
|
10617
|
+
}
|
|
10618
|
+
syncCompressionBlocks(ctx.state, ctx.logger, rawMessages);
|
|
10619
|
+
const { restoredMessageCount, restoredTokens } = computeRestoredMessages(
|
|
10620
|
+
messagesState,
|
|
10621
|
+
activeMessagesBefore
|
|
10622
|
+
);
|
|
10623
|
+
const reactivatedBlockIds = computeReactivatedBlockIds(
|
|
10624
|
+
messagesState,
|
|
10625
|
+
activeBlockIdsBefore
|
|
10626
|
+
);
|
|
10627
|
+
ctx.state.stats.totalPruneTokens = Math.max(
|
|
10628
|
+
0,
|
|
10629
|
+
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
10630
|
+
);
|
|
10631
|
+
const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
10632
|
+
const contextUsageAfter = effectiveLimitAfter ? Math.round(
|
|
10633
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
10634
|
+
) : void 0;
|
|
10635
|
+
await finalizeDecompressSession(ctx);
|
|
10636
|
+
const restoredContentPreview = buildRestoredContentPreview(
|
|
10637
|
+
rawMessages,
|
|
10638
|
+
activeMessagesBefore,
|
|
10639
|
+
messagesState
|
|
10640
|
+
);
|
|
10641
|
+
const displayIds = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10642
|
+
const lines = [];
|
|
10643
|
+
const headerNoun = targets.length === 1 ? "block" : "blocks";
|
|
10644
|
+
lines.push(
|
|
10645
|
+
`Decompressed ${headerNoun} ${displayIds}. Restored ${restoredMessageCount} message(s) (~${formatTokenCount(restoredTokens)}).`
|
|
10509
10646
|
);
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10647
|
+
if (contextUsageBefore !== void 0 && contextUsageAfter !== void 0) {
|
|
10648
|
+
lines.push(`Context usage: ${contextUsageBefore}% \u2192 ${contextUsageAfter}%.`);
|
|
10649
|
+
}
|
|
10650
|
+
if (reactivatedBlockIds.length > 0) {
|
|
10651
|
+
const refs = reactivatedBlockIds.map((id) => `b${id}`).join(", ");
|
|
10652
|
+
lines.push(`Also restored nested block(s): ${refs}.`);
|
|
10653
|
+
}
|
|
10654
|
+
if (restoredContentPreview) {
|
|
10655
|
+
lines.push("");
|
|
10656
|
+
lines.push("RESTORED CONTENT (condensed):");
|
|
10657
|
+
lines.push(restoredContentPreview);
|
|
10658
|
+
}
|
|
10659
|
+
ctx.logger.info("Decompress tool completed", {
|
|
10660
|
+
mode: typeof args.startId === "string" ? "range" : "block",
|
|
10661
|
+
targetBlockIds: targets.map((t) => t.displayId),
|
|
10662
|
+
restoredMessageCount,
|
|
10663
|
+
restoredTokens,
|
|
10664
|
+
reactivatedBlockIds
|
|
10518
10665
|
});
|
|
10519
|
-
return
|
|
10520
|
-
}
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
for (const target of targets) {
|
|
10524
|
-
deactivateCompressionTarget(messagesState, target, { full: args.full === true });
|
|
10525
|
-
}
|
|
10526
|
-
syncCompressionBlocks(ctx.state, ctx.logger, rawMessages);
|
|
10527
|
-
const { restoredMessageCount, restoredTokens } = computeRestoredMessages(
|
|
10528
|
-
messagesState,
|
|
10529
|
-
activeMessagesBefore
|
|
10530
|
-
);
|
|
10531
|
-
const reactivatedBlockIds = computeReactivatedBlockIds(
|
|
10532
|
-
messagesState,
|
|
10533
|
-
activeBlockIdsBefore
|
|
10534
|
-
);
|
|
10535
|
-
ctx.state.stats.totalPruneTokens = Math.max(
|
|
10536
|
-
0,
|
|
10537
|
-
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
10538
|
-
);
|
|
10539
|
-
const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
10540
|
-
const contextUsageAfter = effectiveLimitAfter ? Math.round(
|
|
10541
|
-
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
10542
|
-
) : void 0;
|
|
10543
|
-
await finalizeDecompressSession(ctx);
|
|
10544
|
-
const restoredContentPreview = buildRestoredContentPreview(
|
|
10545
|
-
rawMessages,
|
|
10546
|
-
activeMessagesBefore,
|
|
10547
|
-
messagesState
|
|
10548
|
-
);
|
|
10549
|
-
const displayIds = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10550
|
-
const lines = [];
|
|
10551
|
-
const headerNoun = targets.length === 1 ? "block" : "blocks";
|
|
10552
|
-
lines.push(
|
|
10553
|
-
`Decompressed ${headerNoun} ${displayIds}. Restored ${restoredMessageCount} message(s) (~${formatTokenCount(restoredTokens)}).`
|
|
10554
|
-
);
|
|
10555
|
-
if (contextUsageBefore !== void 0 && contextUsageAfter !== void 0) {
|
|
10556
|
-
lines.push(`Context usage: ${contextUsageBefore}% \u2192 ${contextUsageAfter}%.`);
|
|
10557
|
-
}
|
|
10558
|
-
if (reactivatedBlockIds.length > 0) {
|
|
10559
|
-
const refs = reactivatedBlockIds.map((id) => `b${id}`).join(", ");
|
|
10560
|
-
lines.push(`Also restored nested block(s): ${refs}.`);
|
|
10561
|
-
}
|
|
10562
|
-
if (restoredContentPreview) {
|
|
10563
|
-
lines.push("");
|
|
10564
|
-
lines.push("RESTORED CONTENT (condensed):");
|
|
10565
|
-
lines.push(restoredContentPreview);
|
|
10566
|
-
}
|
|
10567
|
-
ctx.logger.info("Decompress tool completed", {
|
|
10568
|
-
mode: typeof args.startId === "string" ? "range" : "block",
|
|
10569
|
-
targetBlockIds: targets.map((t) => t.displayId),
|
|
10570
|
-
restoredMessageCount,
|
|
10571
|
-
restoredTokens,
|
|
10572
|
-
reactivatedBlockIds
|
|
10573
|
-
});
|
|
10574
|
-
return lines.join("\n");
|
|
10666
|
+
return lines.join("\n");
|
|
10667
|
+
};
|
|
10668
|
+
await requestCompressPermission(toolCtx, "Decompress");
|
|
10669
|
+
return factoryCtx.registry.withSessionGuard(toolCtx.sessionID, () => run());
|
|
10575
10670
|
}
|
|
10576
10671
|
});
|
|
10577
10672
|
}
|
|
@@ -11329,7 +11424,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
11329
11424
|
import { join as join5 } from "path";
|
|
11330
11425
|
import { existsSync as existsSync5 } from "fs";
|
|
11331
11426
|
import { homedir as homedir3 } from "os";
|
|
11332
|
-
var LOG_VERSION = true ? "1.18.
|
|
11427
|
+
var LOG_VERSION = true ? "1.18.5-pr.413.228" : "dev";
|
|
11333
11428
|
var LEVEL_RANK = {
|
|
11334
11429
|
debug: 10,
|
|
11335
11430
|
info: 20,
|
|
@@ -12184,6 +12279,7 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
12184
12279
|
const part = parts[pi];
|
|
12185
12280
|
if (part?.type !== "tool") continue;
|
|
12186
12281
|
if (part.state?.status !== "completed") continue;
|
|
12282
|
+
if (part.tool === "compress") continue;
|
|
12187
12283
|
const content = extractCompletedToolOutput(part);
|
|
12188
12284
|
if (content === void 0) continue;
|
|
12189
12285
|
if (content === "[Old tool result content cleared]") continue;
|
|
@@ -12224,6 +12320,74 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
12224
12320
|
}
|
|
12225
12321
|
}
|
|
12226
12322
|
|
|
12323
|
+
// lib/messages/sticky-truncation.ts
|
|
12324
|
+
function collectToolParts(messages) {
|
|
12325
|
+
const parts = [];
|
|
12326
|
+
for (const message of messages) {
|
|
12327
|
+
const messageParts = Array.isArray(message.parts) ? message.parts : [];
|
|
12328
|
+
for (const part of messageParts) {
|
|
12329
|
+
if (part?.type === "tool") {
|
|
12330
|
+
parts.push(part);
|
|
12331
|
+
}
|
|
12332
|
+
}
|
|
12333
|
+
}
|
|
12334
|
+
return parts;
|
|
12335
|
+
}
|
|
12336
|
+
function applyStickyTruncations(state, messages, logger) {
|
|
12337
|
+
const frozen = state.stickyTruncations;
|
|
12338
|
+
if (Object.keys(frozen).length === 0) return;
|
|
12339
|
+
let replayed = 0;
|
|
12340
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12341
|
+
for (const part of collectToolParts(messages)) {
|
|
12342
|
+
const callID = typeof part.callID === "string" ? part.callID : void 0;
|
|
12343
|
+
if (!callID) continue;
|
|
12344
|
+
seen.add(callID);
|
|
12345
|
+
const recorded = frozen[callID];
|
|
12346
|
+
if (recorded === void 0) continue;
|
|
12347
|
+
if (!part.state) continue;
|
|
12348
|
+
if (part.state.output === recorded) continue;
|
|
12349
|
+
part.state.output = recorded;
|
|
12350
|
+
replayed++;
|
|
12351
|
+
}
|
|
12352
|
+
for (const callID of Object.keys(frozen)) {
|
|
12353
|
+
if (!seen.has(callID)) delete frozen[callID];
|
|
12354
|
+
}
|
|
12355
|
+
if (replayed > 0) {
|
|
12356
|
+
logger.debug("Sticky truncation: replayed frozen tool outputs", {
|
|
12357
|
+
replayed,
|
|
12358
|
+
frozen: Object.keys(frozen).length
|
|
12359
|
+
});
|
|
12360
|
+
}
|
|
12361
|
+
}
|
|
12362
|
+
function snapshotToolOutputs(messages) {
|
|
12363
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
12364
|
+
for (const part of collectToolParts(messages)) {
|
|
12365
|
+
const callID = typeof part.callID === "string" ? part.callID : void 0;
|
|
12366
|
+
if (!callID) continue;
|
|
12367
|
+
const content = extractCompletedToolOutput(part);
|
|
12368
|
+
if (content === void 0) continue;
|
|
12369
|
+
snapshot.set(callID, content);
|
|
12370
|
+
}
|
|
12371
|
+
return snapshot;
|
|
12372
|
+
}
|
|
12373
|
+
function recordStickyTruncations(state, messages, before, logger) {
|
|
12374
|
+
let recorded = 0;
|
|
12375
|
+
for (const part of collectToolParts(messages)) {
|
|
12376
|
+
const callID = typeof part.callID === "string" ? part.callID : void 0;
|
|
12377
|
+
if (!callID) continue;
|
|
12378
|
+
if (typeof part.state?.output !== "string") continue;
|
|
12379
|
+
const prior = before.get(callID);
|
|
12380
|
+
if (prior === void 0 || prior === part.state.output) continue;
|
|
12381
|
+
if (state.stickyTruncations[callID] === part.state.output) continue;
|
|
12382
|
+
state.stickyTruncations[callID] = part.state.output;
|
|
12383
|
+
recorded++;
|
|
12384
|
+
}
|
|
12385
|
+
if (recorded > 0) {
|
|
12386
|
+
logger.info("Sticky truncation: froze truncated tool outputs", { recorded });
|
|
12387
|
+
}
|
|
12388
|
+
return recorded;
|
|
12389
|
+
}
|
|
12390
|
+
|
|
12227
12391
|
// lib/messages/enforce-budget.ts
|
|
12228
12392
|
var DEFAULT_COMPLETION_RESERVE_TOKENS = 32768;
|
|
12229
12393
|
var TRUNCATION_MARKER2 = "[truncated for context space";
|
|
@@ -13393,22 +13557,24 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
13393
13557
|
logger.info("Skipping DCP system prompt injection for internal agent");
|
|
13394
13558
|
return;
|
|
13395
13559
|
}
|
|
13396
|
-
if (input.model?.limit?.context) {
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
|
|
13560
|
+
if (input.model?.limit?.context && input.sessionID) {
|
|
13561
|
+
await registry4.withSessionGuard(input.sessionID, () => {
|
|
13562
|
+
const limit = input.model.limit.context;
|
|
13563
|
+
const providerID = input.model?.providerID;
|
|
13564
|
+
const modelID = input.model?.id;
|
|
13565
|
+
const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
|
|
13566
|
+
state.modelContextLimit = limit;
|
|
13567
|
+
if (providerID !== void 0) {
|
|
13568
|
+
state.modelProviderID = providerID;
|
|
13569
|
+
}
|
|
13570
|
+
if (modelID !== void 0) {
|
|
13571
|
+
state.modelID = modelID;
|
|
13572
|
+
}
|
|
13573
|
+
if (changed) {
|
|
13574
|
+
saveSessionState(state, logger).catch(() => {
|
|
13575
|
+
});
|
|
13576
|
+
}
|
|
13577
|
+
});
|
|
13412
13578
|
}
|
|
13413
13579
|
const effectivePermission = compressPermission(state, config);
|
|
13414
13580
|
if (effectivePermission === "deny") {
|
|
@@ -13443,11 +13609,172 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
13443
13609
|
return;
|
|
13444
13610
|
}
|
|
13445
13611
|
const lastUserMessage = getLastUserMessage(messages);
|
|
13446
|
-
|
|
13612
|
+
const runPipeline = async (state) => {
|
|
13613
|
+
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
13614
|
+
if (state.isSubAgent && !config.allowSubAgents) {
|
|
13615
|
+
return;
|
|
13616
|
+
}
|
|
13617
|
+
stripHallucinations(output.messages);
|
|
13618
|
+
const dropReasoningModel = lastUserMessage?.info?.model;
|
|
13619
|
+
const reasoningConfig = applyCompressOverrides(
|
|
13620
|
+
config,
|
|
13621
|
+
dropReasoningModel?.providerID ?? state.modelProviderID,
|
|
13622
|
+
dropReasoningModel?.modelID ?? state.modelID
|
|
13623
|
+
).compress.reasoning;
|
|
13624
|
+
if (reasoningConfig?.drop !== false) {
|
|
13625
|
+
const droppedReasoning = dropCompressReasoning(
|
|
13626
|
+
output.messages,
|
|
13627
|
+
reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
13628
|
+
);
|
|
13629
|
+
if (droppedReasoning > 0) {
|
|
13630
|
+
logger.debug("compress.reasoning: dropped oversized reasoning parts", {
|
|
13631
|
+
dropped: droppedReasoning,
|
|
13632
|
+
threshold: reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
13633
|
+
});
|
|
13634
|
+
}
|
|
13635
|
+
}
|
|
13636
|
+
ensureBuiltinFiltersRegistered();
|
|
13637
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
13638
|
+
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
13639
|
+
sessionId: state.sessionId ?? "",
|
|
13640
|
+
isSubAgent: state.isSubAgent,
|
|
13641
|
+
modelContextLimit: effectiveLimit?.limit
|
|
13642
|
+
});
|
|
13643
|
+
cacheSystemPromptTokens(state, output.messages);
|
|
13644
|
+
assignMessageRefs(state, output.messages);
|
|
13645
|
+
const imagesConfig = config.compress.images ?? DEFAULT_COMPRESS_IMAGES;
|
|
13646
|
+
if (imagesConfig.enabled) {
|
|
13647
|
+
const archivedImages = archiveSessionImages(
|
|
13648
|
+
state,
|
|
13649
|
+
output.messages,
|
|
13650
|
+
imagesConfig,
|
|
13651
|
+
logger
|
|
13652
|
+
);
|
|
13653
|
+
if (archivedImages > 0) {
|
|
13654
|
+
saveSessionState(state, logger).catch(() => {
|
|
13655
|
+
});
|
|
13656
|
+
}
|
|
13657
|
+
pruneImageArchive(state, imagesConfig, logger);
|
|
13658
|
+
}
|
|
13659
|
+
const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
|
|
13660
|
+
const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
|
|
13661
|
+
if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
|
|
13662
|
+
saveSessionState(state, logger).catch(() => {
|
|
13663
|
+
});
|
|
13664
|
+
}
|
|
13665
|
+
syncToolCache(state, config, logger, output.messages);
|
|
13666
|
+
buildToolIdList(state, output.messages);
|
|
13667
|
+
const batchResult = runBatchCleanup(state, config, logger, output.messages);
|
|
13668
|
+
if (batchResult.mergedCount > 0) {
|
|
13669
|
+
saveSessionState(state, logger).catch(() => {
|
|
13670
|
+
});
|
|
13671
|
+
}
|
|
13672
|
+
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
13673
|
+
const candidateMessages = config.compress.candidates === true ? output.messages.slice() : void 0;
|
|
13674
|
+
prune(state, logger, config, output.messages);
|
|
13675
|
+
hideConsumedCompressCalls(state, output.messages);
|
|
13676
|
+
assignMessageRefs(state, output.messages);
|
|
13677
|
+
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
13678
|
+
prompts.reload();
|
|
13679
|
+
injectCompressNudges(
|
|
13680
|
+
state,
|
|
13681
|
+
config,
|
|
13682
|
+
logger,
|
|
13683
|
+
output.messages,
|
|
13684
|
+
prompts.getRuntimePrompts(),
|
|
13685
|
+
compressionPriorities,
|
|
13686
|
+
config.debug ? (text) => {
|
|
13687
|
+
logger.debug(`[ACP Debug] Nudge injected:
|
|
13688
|
+
${text}`);
|
|
13689
|
+
client.tui.showToast({
|
|
13690
|
+
body: {
|
|
13691
|
+
title: "ACP: Nudge Injected",
|
|
13692
|
+
message: text.slice(0, 500),
|
|
13693
|
+
variant: "info",
|
|
13694
|
+
duration: 5e3
|
|
13695
|
+
}
|
|
13696
|
+
}).catch(() => {
|
|
13697
|
+
});
|
|
13698
|
+
} : void 0,
|
|
13699
|
+
prePruneTokens,
|
|
13700
|
+
candidateMessages
|
|
13701
|
+
);
|
|
13702
|
+
const emergencyTruncationEnabled = applyCompressOverrides(
|
|
13703
|
+
config,
|
|
13704
|
+
dropReasoningModel?.providerID ?? state.modelProviderID,
|
|
13705
|
+
dropReasoningModel?.modelID ?? state.modelID
|
|
13706
|
+
).compress.emergencyTruncation;
|
|
13707
|
+
if (emergencyTruncationEnabled === true) {
|
|
13708
|
+
applyStickyTruncations(state, output.messages, logger);
|
|
13709
|
+
const toolOutputsBefore = snapshotToolOutputs(output.messages);
|
|
13710
|
+
truncateLargeToolOutputs(
|
|
13711
|
+
state,
|
|
13712
|
+
config,
|
|
13713
|
+
logger,
|
|
13714
|
+
output.messages.filter((message) => !isSyntheticMessage(message))
|
|
13715
|
+
);
|
|
13716
|
+
enforceContextBudget(state, config, logger, output.messages);
|
|
13717
|
+
const frozenCount = recordStickyTruncations(
|
|
13718
|
+
state,
|
|
13719
|
+
output.messages,
|
|
13720
|
+
toolOutputsBefore,
|
|
13721
|
+
logger
|
|
13722
|
+
);
|
|
13723
|
+
if (frozenCount > 0) {
|
|
13724
|
+
saveSessionState(state, logger).catch(() => {
|
|
13725
|
+
});
|
|
13726
|
+
}
|
|
13727
|
+
}
|
|
13728
|
+
injectMessageIds(state, config, output.messages, compressionPriorities);
|
|
13729
|
+
hideFailedCompressCalls(output.messages);
|
|
13730
|
+
stripStaleMetadata(output.messages);
|
|
13731
|
+
dropEmptyMessages(output.messages);
|
|
13732
|
+
const smartPlanEnabled = applyCompressOverrides(
|
|
13733
|
+
config,
|
|
13734
|
+
state.modelProviderID,
|
|
13735
|
+
state.modelID
|
|
13736
|
+
).compress.smartPlanRequired;
|
|
13737
|
+
if (state.sessionId && smartPlanEnabled === true) {
|
|
13738
|
+
recordVisibleMessages(state.sessionId, output.messages);
|
|
13739
|
+
}
|
|
13740
|
+
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
13741
|
+
if (postTokens !== void 0 && effectiveLimit) {
|
|
13742
|
+
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
13743
|
+
if (postTokens > budget) {
|
|
13744
|
+
logger.error(
|
|
13745
|
+
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
13746
|
+
{
|
|
13747
|
+
session: state.sessionId,
|
|
13748
|
+
postTokens,
|
|
13749
|
+
budget,
|
|
13750
|
+
contextLimit: effectiveLimit.limit,
|
|
13751
|
+
contextLimitSource: effectiveLimit.source,
|
|
13752
|
+
hint: "request will likely be rejected; run /compact or start a new session"
|
|
13753
|
+
}
|
|
13754
|
+
);
|
|
13755
|
+
}
|
|
13756
|
+
}
|
|
13757
|
+
logger.info("Chat transform complete", {
|
|
13758
|
+
session: state.sessionId,
|
|
13759
|
+
model: state.modelID,
|
|
13760
|
+
messages: output.messages.length,
|
|
13761
|
+
prePruneTokens,
|
|
13762
|
+
postTokens,
|
|
13763
|
+
contextLimit: effectiveLimit?.limit,
|
|
13764
|
+
contextLimitSource: effectiveLimit?.source,
|
|
13765
|
+
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
13766
|
+
nudged: state.nudges.shouldInjectThisTurn
|
|
13767
|
+
});
|
|
13768
|
+
if (state.sessionId) {
|
|
13769
|
+
await logger.saveContext(state.sessionId, output.messages);
|
|
13770
|
+
}
|
|
13771
|
+
};
|
|
13447
13772
|
if (!lastUserMessage) {
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
13773
|
+
await runPipeline(createSessionState());
|
|
13774
|
+
return;
|
|
13775
|
+
}
|
|
13776
|
+
await registry4.withSessionGuard(lastUserMessage.info.sessionID, async () => {
|
|
13777
|
+
const state = await registry4.getOrCreate(
|
|
13451
13778
|
client,
|
|
13452
13779
|
lastUserMessage.info.sessionID,
|
|
13453
13780
|
messages,
|
|
@@ -13504,146 +13831,8 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
13504
13831
|
}
|
|
13505
13832
|
);
|
|
13506
13833
|
}
|
|
13507
|
-
|
|
13508
|
-
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
13509
|
-
if (state.isSubAgent && !config.allowSubAgents) {
|
|
13510
|
-
return;
|
|
13511
|
-
}
|
|
13512
|
-
stripHallucinations(output.messages);
|
|
13513
|
-
const dropReasoningModel = lastUserMessage?.info?.model;
|
|
13514
|
-
const reasoningConfig = applyCompressOverrides(
|
|
13515
|
-
config,
|
|
13516
|
-
dropReasoningModel?.providerID ?? state.modelProviderID,
|
|
13517
|
-
dropReasoningModel?.modelID ?? state.modelID
|
|
13518
|
-
).compress.reasoning;
|
|
13519
|
-
if (reasoningConfig?.drop !== false) {
|
|
13520
|
-
const droppedReasoning = dropCompressReasoning(
|
|
13521
|
-
output.messages,
|
|
13522
|
-
reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
13523
|
-
);
|
|
13524
|
-
if (droppedReasoning > 0) {
|
|
13525
|
-
logger.debug("compress.reasoning: dropped oversized reasoning parts", {
|
|
13526
|
-
dropped: droppedReasoning,
|
|
13527
|
-
threshold: reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
13528
|
-
});
|
|
13529
|
-
}
|
|
13530
|
-
}
|
|
13531
|
-
ensureBuiltinFiltersRegistered();
|
|
13532
|
-
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
13533
|
-
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
13534
|
-
sessionId: state.sessionId ?? "",
|
|
13535
|
-
isSubAgent: state.isSubAgent,
|
|
13536
|
-
modelContextLimit: effectiveLimit?.limit
|
|
13834
|
+
await runPipeline(state);
|
|
13537
13835
|
});
|
|
13538
|
-
cacheSystemPromptTokens(state, output.messages);
|
|
13539
|
-
assignMessageRefs(state, output.messages);
|
|
13540
|
-
const imagesConfig = config.compress.images ?? DEFAULT_COMPRESS_IMAGES;
|
|
13541
|
-
if (imagesConfig.enabled) {
|
|
13542
|
-
const archivedImages = archiveSessionImages(
|
|
13543
|
-
state,
|
|
13544
|
-
output.messages,
|
|
13545
|
-
imagesConfig,
|
|
13546
|
-
logger
|
|
13547
|
-
);
|
|
13548
|
-
if (archivedImages > 0) {
|
|
13549
|
-
saveSessionState(state, logger).catch(() => {
|
|
13550
|
-
});
|
|
13551
|
-
}
|
|
13552
|
-
pruneImageArchive(state, imagesConfig, logger);
|
|
13553
|
-
}
|
|
13554
|
-
const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
|
|
13555
|
-
const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
|
|
13556
|
-
if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
|
|
13557
|
-
saveSessionState(state, logger).catch(() => {
|
|
13558
|
-
});
|
|
13559
|
-
}
|
|
13560
|
-
syncToolCache(state, config, logger, output.messages);
|
|
13561
|
-
buildToolIdList(state, output.messages);
|
|
13562
|
-
const batchResult = runBatchCleanup(state, config, logger, output.messages);
|
|
13563
|
-
if (batchResult.mergedCount > 0) {
|
|
13564
|
-
saveSessionState(state, logger).catch(() => {
|
|
13565
|
-
});
|
|
13566
|
-
}
|
|
13567
|
-
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
13568
|
-
const candidateMessages = config.compress.candidates === true ? output.messages.slice() : void 0;
|
|
13569
|
-
prune(state, logger, config, output.messages);
|
|
13570
|
-
hideConsumedCompressCalls(state, output.messages);
|
|
13571
|
-
assignMessageRefs(state, output.messages);
|
|
13572
|
-
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
13573
|
-
prompts.reload();
|
|
13574
|
-
injectCompressNudges(
|
|
13575
|
-
state,
|
|
13576
|
-
config,
|
|
13577
|
-
logger,
|
|
13578
|
-
output.messages,
|
|
13579
|
-
prompts.getRuntimePrompts(),
|
|
13580
|
-
compressionPriorities,
|
|
13581
|
-
config.debug ? (text) => {
|
|
13582
|
-
logger.debug(`[ACP Debug] Nudge injected:
|
|
13583
|
-
${text}`);
|
|
13584
|
-
client.tui.showToast({
|
|
13585
|
-
body: {
|
|
13586
|
-
title: "ACP: Nudge Injected",
|
|
13587
|
-
message: text.slice(0, 500),
|
|
13588
|
-
variant: "info",
|
|
13589
|
-
duration: 5e3
|
|
13590
|
-
}
|
|
13591
|
-
}).catch(() => {
|
|
13592
|
-
});
|
|
13593
|
-
} : void 0,
|
|
13594
|
-
prePruneTokens,
|
|
13595
|
-
candidateMessages
|
|
13596
|
-
);
|
|
13597
|
-
truncateLargeToolOutputs(
|
|
13598
|
-
state,
|
|
13599
|
-
config,
|
|
13600
|
-
logger,
|
|
13601
|
-
output.messages.filter((message) => !isSyntheticMessage(message))
|
|
13602
|
-
);
|
|
13603
|
-
enforceContextBudget(state, config, logger, output.messages);
|
|
13604
|
-
injectMessageIds(state, config, output.messages, compressionPriorities);
|
|
13605
|
-
hideFailedCompressCalls(output.messages);
|
|
13606
|
-
stripStaleMetadata(output.messages);
|
|
13607
|
-
dropEmptyMessages(output.messages);
|
|
13608
|
-
const smartPlanEnabled = applyCompressOverrides(
|
|
13609
|
-
config,
|
|
13610
|
-
state.modelProviderID,
|
|
13611
|
-
state.modelID
|
|
13612
|
-
).compress.smartPlanRequired;
|
|
13613
|
-
if (state.sessionId && smartPlanEnabled === true) {
|
|
13614
|
-
recordVisibleMessages(state.sessionId, output.messages);
|
|
13615
|
-
}
|
|
13616
|
-
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
13617
|
-
if (postTokens !== void 0 && effectiveLimit) {
|
|
13618
|
-
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
13619
|
-
if (postTokens > budget) {
|
|
13620
|
-
logger.error(
|
|
13621
|
-
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
13622
|
-
{
|
|
13623
|
-
session: state.sessionId,
|
|
13624
|
-
postTokens,
|
|
13625
|
-
budget,
|
|
13626
|
-
contextLimit: effectiveLimit.limit,
|
|
13627
|
-
contextLimitSource: effectiveLimit.source,
|
|
13628
|
-
hint: "request will likely be rejected; run /compact or start a new session"
|
|
13629
|
-
}
|
|
13630
|
-
);
|
|
13631
|
-
}
|
|
13632
|
-
}
|
|
13633
|
-
logger.info("Chat transform complete", {
|
|
13634
|
-
session: state.sessionId,
|
|
13635
|
-
model: state.modelID,
|
|
13636
|
-
messages: output.messages.length,
|
|
13637
|
-
prePruneTokens,
|
|
13638
|
-
postTokens,
|
|
13639
|
-
contextLimit: effectiveLimit?.limit,
|
|
13640
|
-
contextLimitSource: effectiveLimit?.source,
|
|
13641
|
-
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
13642
|
-
nudged: state.nudges.shouldInjectThisTurn
|
|
13643
|
-
});
|
|
13644
|
-
if (state.sessionId) {
|
|
13645
|
-
await logger.saveContext(state.sessionId, output.messages);
|
|
13646
|
-
}
|
|
13647
13836
|
};
|
|
13648
13837
|
}
|
|
13649
13838
|
function buildHelpText() {
|
|
@@ -13670,8 +13859,11 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
|
|
|
13670
13859
|
path: { id: input.sessionID }
|
|
13671
13860
|
});
|
|
13672
13861
|
const messages = filterMessages(messagesResponse.data || messagesResponse);
|
|
13673
|
-
const state = await registry4.
|
|
13674
|
-
|
|
13862
|
+
const state = await registry4.withSessionGuard(input.sessionID, async () => {
|
|
13863
|
+
const s = await registry4.getOrCreate(client, input.sessionID, messages, config);
|
|
13864
|
+
syncCompressPermissionState(s, config, hostPermissions, messages);
|
|
13865
|
+
return s;
|
|
13866
|
+
});
|
|
13675
13867
|
const commandCtx = {
|
|
13676
13868
|
client,
|
|
13677
13869
|
state,
|
|
@@ -13775,18 +13967,27 @@ function createEventHandler(registry4, logger) {
|
|
|
13775
13967
|
callId: part.callID,
|
|
13776
13968
|
durationMs
|
|
13777
13969
|
});
|
|
13970
|
+
const settles = [];
|
|
13778
13971
|
for (const state of registry4.all()) {
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
await saveSessionState(state, logger);
|
|
13782
|
-
logger.info("Attached compression time to blocks", {
|
|
13783
|
-
messageID: part.messageID,
|
|
13784
|
-
callID: part.callID,
|
|
13785
|
-
blocks: updates,
|
|
13786
|
-
durationMs
|
|
13787
|
-
});
|
|
13972
|
+
if (!state.sessionId) {
|
|
13973
|
+
continue;
|
|
13788
13974
|
}
|
|
13975
|
+
settles.push(
|
|
13976
|
+
registry4.withSessionGuard(state.sessionId, async () => {
|
|
13977
|
+
const updates = applyPendingCompressionDurations(state);
|
|
13978
|
+
if (updates > 0) {
|
|
13979
|
+
await saveSessionState(state, logger);
|
|
13980
|
+
logger.info("Attached compression time to blocks", {
|
|
13981
|
+
messageID: part.messageID,
|
|
13982
|
+
callID: part.callID,
|
|
13983
|
+
blocks: updates,
|
|
13984
|
+
durationMs
|
|
13985
|
+
});
|
|
13986
|
+
}
|
|
13987
|
+
})
|
|
13988
|
+
);
|
|
13789
13989
|
}
|
|
13990
|
+
await Promise.allSettled(settles);
|
|
13790
13991
|
return;
|
|
13791
13992
|
}
|
|
13792
13993
|
if (part.state.status === "running") {
|
|
@@ -14039,7 +14240,7 @@ var server = (async (ctx) => {
|
|
|
14039
14240
|
}
|
|
14040
14241
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
14041
14242
|
logger.info("ACP plugin initialized", {
|
|
14042
|
-
version: true ? "1.18.
|
|
14243
|
+
version: true ? "1.18.5-pr.413.228" : "dev",
|
|
14043
14244
|
workspace: ctx.directory,
|
|
14044
14245
|
logLevel: logger.level,
|
|
14045
14246
|
debug: config.debug,
|