opencode-acp 1.18.4-pr.488.221 → 1.18.5-pr.408.227
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 +610 -531
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/decompress.d.ts.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/hooks.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/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4990,6 +4990,30 @@ async function updatePerTurnState(state, logger, messages) {
|
|
|
4990
4990
|
state.currentTurn = countTurns(state, messages);
|
|
4991
4991
|
}
|
|
4992
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
|
+
}
|
|
4993
5017
|
var SessionStateRegistry = class {
|
|
4994
5018
|
constructor(logger, projectDir) {
|
|
4995
5019
|
this.logger = logger;
|
|
@@ -5049,9 +5073,17 @@ var SessionStateRegistry = class {
|
|
|
5049
5073
|
get size() {
|
|
5050
5074
|
return this.states.size;
|
|
5051
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();
|
|
5052
5083
|
// Idempotent: ensureSessionInitialized returns immediately once
|
|
5053
|
-
// state.sessionId === sessionId
|
|
5054
|
-
// 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.
|
|
5055
5087
|
async getOrCreate(client, sessionId, messages, config) {
|
|
5056
5088
|
let state = this.states.get(sessionId);
|
|
5057
5089
|
if (!state) {
|
|
@@ -5185,10 +5217,33 @@ function resetSessionState(state) {
|
|
|
5185
5217
|
state.imagePruneDone = false;
|
|
5186
5218
|
state.stickyTruncations = {};
|
|
5187
5219
|
}
|
|
5220
|
+
var inflightInits = /* @__PURE__ */ new WeakMap();
|
|
5188
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
|
+
}
|
|
5189
5227
|
if (state.sessionId === sessionId) {
|
|
5190
5228
|
return;
|
|
5191
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) {
|
|
5192
5247
|
resetSessionState(state);
|
|
5193
5248
|
state.sessionId = sessionId;
|
|
5194
5249
|
state.storageDir = config?.storagePath ? resolveStorageDir(config.storagePath, projectDir ?? cwd()) : void 0;
|
|
@@ -8199,240 +8254,243 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
8199
8254
|
}
|
|
8200
8255
|
}
|
|
8201
8256
|
const callId = typeof toolCtx.callID === "string" ? toolCtx.callID : void 0;
|
|
8202
|
-
const
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
const { providerId, modelId } = getModelInfo(rawMessages);
|
|
8208
|
-
const ctx = {
|
|
8209
|
-
...ctx0,
|
|
8210
|
-
config: applyCompressOverrides(ctx0.config, providerId, modelId)
|
|
8211
|
-
};
|
|
8212
|
-
const needsSmartPlan = requiresSmartPlan(
|
|
8213
|
-
ctx.config.compress.smartPlanRequired === true,
|
|
8214
|
-
input.content
|
|
8215
|
-
);
|
|
8216
|
-
if (needsSmartPlan && input.content.length !== 1) {
|
|
8217
|
-
throw new Error(
|
|
8218
|
-
'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.'
|
|
8219
|
-
);
|
|
8220
|
-
}
|
|
8221
|
-
const { plans: filteredPlans, totalChars } = prepareExecutableRangePlans(
|
|
8222
|
-
input,
|
|
8223
|
-
searchContext,
|
|
8224
|
-
ctx.state,
|
|
8225
|
-
ctx.config,
|
|
8226
|
-
ctx.logger
|
|
8227
|
-
);
|
|
8228
|
-
if (needsSmartPlan) {
|
|
8229
|
-
validateSmartPlan(
|
|
8230
|
-
toolCtx.sessionID,
|
|
8231
|
-
filteredPlans[0].entry.startId,
|
|
8232
|
-
filteredPlans[0].entry.endId,
|
|
8233
|
-
filteredPlans[0].selection.messageIds,
|
|
8234
|
-
totalChars,
|
|
8235
|
-
ctx.state.prune.messages.structureVersion ?? 0
|
|
8236
|
-
);
|
|
8237
|
-
}
|
|
8238
|
-
const notifications = [];
|
|
8239
|
-
let preparedPlans = [];
|
|
8240
|
-
let totalCompressedMessages = 0;
|
|
8241
|
-
for (const plan of filteredPlans) {
|
|
8242
|
-
const parsedPlaceholders = parseBlockPlaceholders(plan.entry.summary);
|
|
8243
|
-
validateSummaryPlaceholders(
|
|
8244
|
-
parsedPlaceholders,
|
|
8245
|
-
plan.selection.requiredBlockIds,
|
|
8246
|
-
plan.selection.startReference,
|
|
8247
|
-
plan.selection.endReference,
|
|
8248
|
-
searchContext.summaryByBlockId,
|
|
8249
|
-
ctx.logger
|
|
8257
|
+
const run = async () => {
|
|
8258
|
+
const { rawMessages, searchContext } = await prepareSession(
|
|
8259
|
+
ctx0,
|
|
8260
|
+
toolCtx,
|
|
8261
|
+
`Compress Range: ${input.topic ?? "(batch)"}`
|
|
8250
8262
|
);
|
|
8251
|
-
const
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
injected.expandedSummary,
|
|
8260
|
-
plan.selection,
|
|
8261
|
-
searchContext,
|
|
8262
|
-
ctx.state,
|
|
8263
|
-
ctx.config.compress.protectUserMessages
|
|
8263
|
+
const { providerId, modelId } = getModelInfo(rawMessages);
|
|
8264
|
+
const ctx = {
|
|
8265
|
+
...ctx0,
|
|
8266
|
+
config: applyCompressOverrides(ctx0.config, providerId, modelId)
|
|
8267
|
+
};
|
|
8268
|
+
const needsSmartPlan = requiresSmartPlan(
|
|
8269
|
+
ctx.config.compress.smartPlanRequired === true,
|
|
8270
|
+
input.content
|
|
8264
8271
|
);
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8272
|
+
if (needsSmartPlan && input.content.length !== 1) {
|
|
8273
|
+
throw new Error(
|
|
8274
|
+
'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.'
|
|
8275
|
+
);
|
|
8276
|
+
}
|
|
8277
|
+
const { plans: filteredPlans, totalChars } = prepareExecutableRangePlans(
|
|
8278
|
+
input,
|
|
8268
8279
|
searchContext,
|
|
8269
8280
|
ctx.state,
|
|
8270
|
-
ctx.config
|
|
8271
|
-
|
|
8272
|
-
const summaryWithTools = await appendProtectedTools(
|
|
8273
|
-
ctx.client,
|
|
8274
|
-
ctx.state,
|
|
8275
|
-
summaryWithPromptInfo,
|
|
8276
|
-
plan.selection,
|
|
8277
|
-
searchContext,
|
|
8278
|
-
ctx.config.compress.protectedTools,
|
|
8279
|
-
ctx.config.protectedFilePatterns
|
|
8280
|
-
);
|
|
8281
|
-
const completedSummary = appendMissingBlockSummaries(
|
|
8282
|
-
summaryWithTools,
|
|
8283
|
-
[],
|
|
8284
|
-
searchContext.summaryByBlockId,
|
|
8285
|
-
injected.consumedBlockIds
|
|
8286
|
-
);
|
|
8287
|
-
const boundaryConsumed = extractBoundaryConsumedBlocks2(
|
|
8288
|
-
plan.selection.startReference,
|
|
8289
|
-
plan.selection.endReference
|
|
8281
|
+
ctx.config,
|
|
8282
|
+
ctx.logger
|
|
8290
8283
|
);
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8284
|
+
if (needsSmartPlan) {
|
|
8285
|
+
validateSmartPlan(
|
|
8286
|
+
toolCtx.sessionID,
|
|
8287
|
+
filteredPlans[0].entry.startId,
|
|
8288
|
+
filteredPlans[0].entry.endId,
|
|
8289
|
+
filteredPlans[0].selection.messageIds,
|
|
8290
|
+
totalChars,
|
|
8291
|
+
ctx.state.prune.messages.structureVersion ?? 0
|
|
8292
|
+
);
|
|
8293
|
+
}
|
|
8294
|
+
const notifications = [];
|
|
8295
|
+
let preparedPlans = [];
|
|
8296
|
+
let totalCompressedMessages = 0;
|
|
8297
|
+
for (const plan of filteredPlans) {
|
|
8298
|
+
const parsedPlaceholders = parseBlockPlaceholders(plan.entry.summary);
|
|
8299
|
+
validateSummaryPlaceholders(
|
|
8300
|
+
parsedPlaceholders,
|
|
8301
|
+
plan.selection.requiredBlockIds,
|
|
8302
|
+
plan.selection.startReference,
|
|
8303
|
+
plan.selection.endReference,
|
|
8304
|
+
searchContext.summaryByBlockId,
|
|
8305
|
+
ctx.logger
|
|
8306
|
+
);
|
|
8307
|
+
const injected = injectBlockPlaceholders(
|
|
8308
|
+
plan.entry.summary,
|
|
8309
|
+
parsedPlaceholders,
|
|
8310
|
+
searchContext.summaryByBlockId,
|
|
8311
|
+
plan.selection.startReference,
|
|
8312
|
+
plan.selection.endReference
|
|
8313
|
+
);
|
|
8314
|
+
const summaryWithUsers = appendProtectedUserMessages(
|
|
8315
|
+
injected.expandedSummary,
|
|
8316
|
+
plan.selection,
|
|
8317
|
+
searchContext,
|
|
8318
|
+
ctx.state,
|
|
8319
|
+
ctx.config.compress.protectUserMessages
|
|
8320
|
+
);
|
|
8321
|
+
const summaryWithPromptInfo = appendProtectedPromptInfo(
|
|
8322
|
+
summaryWithUsers,
|
|
8323
|
+
plan.selection,
|
|
8324
|
+
searchContext,
|
|
8325
|
+
ctx.state,
|
|
8326
|
+
ctx.config.compress.protectTags
|
|
8327
|
+
);
|
|
8328
|
+
const summaryWithTools = await appendProtectedTools(
|
|
8329
|
+
ctx.client,
|
|
8330
|
+
ctx.state,
|
|
8331
|
+
summaryWithPromptInfo,
|
|
8306
8332
|
plan.selection,
|
|
8333
|
+
searchContext,
|
|
8334
|
+
ctx.config.compress.protectedTools,
|
|
8335
|
+
ctx.config.protectedFilePatterns
|
|
8336
|
+
);
|
|
8337
|
+
const completedSummary = appendMissingBlockSummaries(
|
|
8338
|
+
summaryWithTools,
|
|
8339
|
+
[],
|
|
8340
|
+
searchContext.summaryByBlockId,
|
|
8341
|
+
injected.consumedBlockIds
|
|
8342
|
+
);
|
|
8343
|
+
const boundaryConsumed = extractBoundaryConsumedBlocks2(
|
|
8344
|
+
plan.selection.startReference,
|
|
8345
|
+
plan.selection.endReference
|
|
8346
|
+
);
|
|
8347
|
+
const seenConsumed = /* @__PURE__ */ new Set();
|
|
8348
|
+
const mergeConsumedBlockIds = [
|
|
8349
|
+
...plan.selection.requiredBlockIds,
|
|
8350
|
+
...boundaryConsumed
|
|
8351
|
+
].filter((id) => {
|
|
8352
|
+
if (seenConsumed.has(id)) return false;
|
|
8353
|
+
seenConsumed.add(id);
|
|
8354
|
+
return true;
|
|
8355
|
+
});
|
|
8356
|
+
preparedPlans.push({
|
|
8357
|
+
entry: plan.entry,
|
|
8358
|
+
selection: plan.selection,
|
|
8359
|
+
anchorMessageId: plan.anchorMessageId,
|
|
8360
|
+
finalSummary: appendArchivedImagePointers(
|
|
8361
|
+
completedSummary.expandedSummary,
|
|
8362
|
+
plan.selection,
|
|
8363
|
+
ctx.state,
|
|
8364
|
+
ctx.config.compress.images?.enabled !== false
|
|
8365
|
+
),
|
|
8366
|
+
consumedBlockIds: mergeConsumedBlockIds
|
|
8367
|
+
});
|
|
8368
|
+
}
|
|
8369
|
+
const partition = partitionPhantomPlans(
|
|
8370
|
+
identifyPhantomPlans(
|
|
8307
8371
|
ctx.state,
|
|
8308
|
-
|
|
8372
|
+
preparedPlans.map((p) => ({
|
|
8373
|
+
messageIds: p.selection.messageIds,
|
|
8374
|
+
consumedBlockIds: p.consumedBlockIds
|
|
8375
|
+
}))
|
|
8309
8376
|
),
|
|
8310
|
-
|
|
8311
|
-
});
|
|
8312
|
-
}
|
|
8313
|
-
const partition = partitionPhantomPlans(
|
|
8314
|
-
identifyPhantomPlans(
|
|
8315
|
-
ctx.state,
|
|
8316
|
-
preparedPlans.map((p) => ({
|
|
8317
|
-
messageIds: p.selection.messageIds,
|
|
8318
|
-
consumedBlockIds: p.consumedBlockIds
|
|
8319
|
-
}))
|
|
8320
|
-
),
|
|
8321
|
-
preparedPlans.length
|
|
8322
|
-
);
|
|
8323
|
-
let phantomSkipNotice = null;
|
|
8324
|
-
if (partition.kind === "all-phantom") {
|
|
8325
|
-
ctx.logger.warn("Batch compress: ALL entries phantom", {
|
|
8326
|
-
totalCount: preparedPlans.length,
|
|
8327
|
-
details: partition.details
|
|
8328
|
-
});
|
|
8329
|
-
throw new Error(buildPhantomErrorMessage(partition.details));
|
|
8330
|
-
}
|
|
8331
|
-
if (partition.kind === "partial") {
|
|
8332
|
-
ctx.logger.warn("Batch compress: phantom entries detected", {
|
|
8333
|
-
phantomCount: partition.dropIndices.length,
|
|
8334
|
-
totalCount: preparedPlans.length,
|
|
8335
|
-
phantomIndices: partition.dropIndices,
|
|
8336
|
-
details: partition.details
|
|
8337
|
-
});
|
|
8338
|
-
const dropSet = new Set(partition.dropIndices);
|
|
8339
|
-
preparedPlans = preparedPlans.filter((_, i) => !dropSet.has(i));
|
|
8340
|
-
phantomSkipNotice = partition.notice;
|
|
8341
|
-
}
|
|
8342
|
-
const acknowledgeRisk = args.acknowledgeRisk === true;
|
|
8343
|
-
const qualityGateRetryPendingBefore = ctx.state.qualityGateRetryPending;
|
|
8344
|
-
const bypassQuality = acknowledgeRisk && ctx.state.qualityGateRetryPending;
|
|
8345
|
-
const ignoredAcknowledgeRisk = acknowledgeRisk && !ctx.state.qualityGateRetryPending;
|
|
8346
|
-
if (ignoredAcknowledgeRisk) {
|
|
8347
|
-
ctx.logger.warn(
|
|
8348
|
-
"compress: acknowledgeRisk ignored \u2014 no quality gate rejection pending"
|
|
8377
|
+
preparedPlans.length
|
|
8349
8378
|
);
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8379
|
+
let phantomSkipNotice = null;
|
|
8380
|
+
if (partition.kind === "all-phantom") {
|
|
8381
|
+
ctx.logger.warn("Batch compress: ALL entries phantom", {
|
|
8382
|
+
totalCount: preparedPlans.length,
|
|
8383
|
+
details: partition.details
|
|
8384
|
+
});
|
|
8385
|
+
throw new Error(buildPhantomErrorMessage(partition.details));
|
|
8386
|
+
}
|
|
8387
|
+
if (partition.kind === "partial") {
|
|
8388
|
+
ctx.logger.warn("Batch compress: phantom entries detected", {
|
|
8389
|
+
phantomCount: partition.dropIndices.length,
|
|
8390
|
+
totalCount: preparedPlans.length,
|
|
8391
|
+
phantomIndices: partition.dropIndices,
|
|
8392
|
+
details: partition.details
|
|
8393
|
+
});
|
|
8394
|
+
const dropSet = new Set(partition.dropIndices);
|
|
8395
|
+
preparedPlans = preparedPlans.filter((_, i) => !dropSet.has(i));
|
|
8396
|
+
phantomSkipNotice = partition.notice;
|
|
8397
|
+
}
|
|
8398
|
+
const acknowledgeRisk = args.acknowledgeRisk === true;
|
|
8399
|
+
const qualityGateRetryPendingBefore = ctx.state.qualityGateRetryPending;
|
|
8400
|
+
const bypassQuality = acknowledgeRisk && ctx.state.qualityGateRetryPending;
|
|
8401
|
+
const ignoredAcknowledgeRisk = acknowledgeRisk && !ctx.state.qualityGateRetryPending;
|
|
8402
|
+
if (ignoredAcknowledgeRisk) {
|
|
8403
|
+
ctx.logger.warn(
|
|
8404
|
+
"compress: acknowledgeRisk ignored \u2014 no quality gate rejection pending"
|
|
8361
8405
|
);
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
result,
|
|
8406
|
+
}
|
|
8407
|
+
ctx.state.qualityGateRetryPending = false;
|
|
8408
|
+
if (!bypassQuality) {
|
|
8409
|
+
for (const plan of preparedPlans) {
|
|
8410
|
+
const result = evaluatePreCommitQuality(
|
|
8411
|
+
rawMessages,
|
|
8412
|
+
plan.selection.messageIds,
|
|
8413
|
+
plan.selection.messageTokenById,
|
|
8414
|
+
plan.finalSummary,
|
|
8415
|
+
ctx.config,
|
|
8373
8416
|
ctx.logger
|
|
8374
8417
|
);
|
|
8418
|
+
if (result && !result.passed) {
|
|
8419
|
+
ctx.state.qualityGateRetryPending = true;
|
|
8420
|
+
throw buildQualityRejectionError(
|
|
8421
|
+
{
|
|
8422
|
+
startId: plan.entry.startId,
|
|
8423
|
+
endId: plan.entry.endId,
|
|
8424
|
+
summary: plan.finalSummary,
|
|
8425
|
+
messageIds: plan.selection.messageIds,
|
|
8426
|
+
messageTokenById: plan.selection.messageTokenById
|
|
8427
|
+
},
|
|
8428
|
+
result,
|
|
8429
|
+
ctx.logger
|
|
8430
|
+
);
|
|
8431
|
+
}
|
|
8375
8432
|
}
|
|
8376
8433
|
}
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8434
|
+
const snapshot = snapshotCompressionState(ctx.state);
|
|
8435
|
+
const runId = allocateRunId(ctx.state);
|
|
8436
|
+
try {
|
|
8437
|
+
for (const preparedPlan of preparedPlans) {
|
|
8438
|
+
const blockId = allocateBlockId(ctx.state);
|
|
8439
|
+
const keepResult = resolveKeepMarkers(
|
|
8440
|
+
preparedPlan.finalSummary,
|
|
8441
|
+
rawMessages,
|
|
8442
|
+
ctx.state,
|
|
8443
|
+
ctx.config
|
|
8444
|
+
);
|
|
8445
|
+
preparedPlan.finalSummary = keepResult.summary;
|
|
8446
|
+
const storedSummary = wrapCompressedSummary(blockId, preparedPlan.finalSummary);
|
|
8447
|
+
const summaryTokens = countTokens2(storedSummary);
|
|
8448
|
+
const applied = applyCompressionState(
|
|
8449
|
+
ctx.state,
|
|
8450
|
+
{
|
|
8451
|
+
topic: preparedPlan.entry.topic ?? input.topic ?? "",
|
|
8452
|
+
batchTopic: input.topic,
|
|
8453
|
+
startId: preparedPlan.entry.startId,
|
|
8454
|
+
endId: preparedPlan.entry.endId,
|
|
8455
|
+
mode: "range",
|
|
8456
|
+
runId,
|
|
8457
|
+
compressMessageId: toolCtx.messageID,
|
|
8458
|
+
compressCallId: callId,
|
|
8459
|
+
summaryTokens
|
|
8460
|
+
},
|
|
8461
|
+
preparedPlan.selection,
|
|
8462
|
+
preparedPlan.anchorMessageId,
|
|
8463
|
+
blockId,
|
|
8464
|
+
storedSummary,
|
|
8465
|
+
preparedPlan.consumedBlockIds,
|
|
8466
|
+
ctx.config.gc
|
|
8467
|
+
);
|
|
8468
|
+
totalCompressedMessages += applied.messageIds.length;
|
|
8469
|
+
notifications.push({
|
|
8470
|
+
blockId,
|
|
8400
8471
|
runId,
|
|
8401
|
-
|
|
8402
|
-
compressCallId: callId,
|
|
8472
|
+
summary: preparedPlan.finalSummary,
|
|
8403
8473
|
summaryTokens
|
|
8404
|
-
}
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
totalCompressedMessages += applied.messageIds.length;
|
|
8413
|
-
notifications.push({
|
|
8414
|
-
blockId,
|
|
8415
|
-
runId,
|
|
8416
|
-
summary: preparedPlan.finalSummary,
|
|
8417
|
-
summaryTokens
|
|
8418
|
-
});
|
|
8474
|
+
});
|
|
8475
|
+
}
|
|
8476
|
+
await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic);
|
|
8477
|
+
if (needsSmartPlan) consumeSmartPlan(toolCtx.sessionID);
|
|
8478
|
+
} catch (error) {
|
|
8479
|
+
restoreCompressionState(ctx.state, snapshot);
|
|
8480
|
+
ctx.state.qualityGateRetryPending = qualityGateRetryPendingBefore;
|
|
8481
|
+
throw error;
|
|
8419
8482
|
}
|
|
8420
|
-
|
|
8421
|
-
if (needsSmartPlan) consumeSmartPlan(toolCtx.sessionID);
|
|
8422
|
-
} catch (error) {
|
|
8423
|
-
restoreCompressionState(ctx.state, snapshot);
|
|
8424
|
-
ctx.state.qualityGateRetryPending = qualityGateRetryPendingBefore;
|
|
8425
|
-
throw error;
|
|
8426
|
-
}
|
|
8427
|
-
const skippedNote = phantomSkipNotice !== null ? `
|
|
8483
|
+
const skippedNote = phantomSkipNotice !== null ? `
|
|
8428
8484
|
\u26A0\uFE0F ${phantomSkipNotice}
|
|
8429
8485
|
` : "";
|
|
8430
|
-
|
|
8486
|
+
const ackNote = ignoredAcknowledgeRisk ? `
|
|
8431
8487
|
\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.
|
|
8432
8488
|
` : "";
|
|
8433
|
-
|
|
8489
|
+
return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.${skippedNote}${ackNote}
|
|
8434
8490
|
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.
|
|
8435
8491
|
\u{1F4A1} Tip: Use search_context('keyword') to find compressed content when you need it later.`;
|
|
8492
|
+
};
|
|
8493
|
+
return factoryCtx.registry.withSessionGuard(toolCtx.sessionID, () => run());
|
|
8436
8494
|
}
|
|
8437
8495
|
});
|
|
8438
8496
|
}
|
|
@@ -10468,139 +10526,142 @@ function createDecompressTool(factoryCtx) {
|
|
|
10468
10526
|
args: buildSchema2(),
|
|
10469
10527
|
async execute(args, toolCtx) {
|
|
10470
10528
|
const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
|
|
10471
|
-
const
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
const targets = resolved.targets;
|
|
10481
|
-
const messagesState = ctx.state.prune.messages;
|
|
10482
|
-
const activeBlocks = [];
|
|
10483
|
-
for (const target of targets) {
|
|
10484
|
-
for (const block of target.blocks) {
|
|
10485
|
-
if (block.active) {
|
|
10486
|
-
activeBlocks.push(block);
|
|
10487
|
-
}
|
|
10529
|
+
const run = async () => {
|
|
10530
|
+
const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
|
|
10531
|
+
const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
10532
|
+
const contextUsageBefore = effectiveLimitBefore ? Math.round(
|
|
10533
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
|
|
10534
|
+
) : void 0;
|
|
10535
|
+
const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
|
|
10536
|
+
if (!resolved.ok) {
|
|
10537
|
+
return resolved.error;
|
|
10488
10538
|
}
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
const
|
|
10492
|
-
const
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
const resolvedPath = path.resolve(targetPath);
|
|
10499
|
-
const isAllowed = allowedDirs.some((dir) => {
|
|
10500
|
-
const rel = path.relative(dir, resolvedPath);
|
|
10501
|
-
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
10502
|
-
});
|
|
10503
|
-
if (!isAllowed) {
|
|
10504
|
-
return `Error: toFile path must be under ${os.tmpdir()} or ~/.cache/opencode/. Got: ${targetPath}`;
|
|
10539
|
+
const targets = resolved.targets;
|
|
10540
|
+
const messagesState = ctx.state.prune.messages;
|
|
10541
|
+
const activeBlocks = [];
|
|
10542
|
+
for (const target of targets) {
|
|
10543
|
+
for (const block of target.blocks) {
|
|
10544
|
+
if (block.active) {
|
|
10545
|
+
activeBlocks.push(block);
|
|
10546
|
+
}
|
|
10547
|
+
}
|
|
10505
10548
|
}
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10549
|
+
if (args.toFile) {
|
|
10550
|
+
const targetPath = args.toFile;
|
|
10551
|
+
const os = await import("os");
|
|
10552
|
+
const path = await import("path");
|
|
10553
|
+
const allowedDirs = [
|
|
10554
|
+
os.tmpdir() + "/",
|
|
10555
|
+
path.join(os.homedir(), ".cache", "opencode") + "/"
|
|
10556
|
+
];
|
|
10557
|
+
const resolvedPath = path.resolve(targetPath);
|
|
10558
|
+
const isAllowed = allowedDirs.some((dir) => {
|
|
10559
|
+
const rel = path.relative(dir, resolvedPath);
|
|
10560
|
+
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
10561
|
+
});
|
|
10562
|
+
if (!isAllowed) {
|
|
10563
|
+
return `Error: toFile path must be under ${os.tmpdir()} or ~/.cache/opencode/. Got: ${targetPath}`;
|
|
10564
|
+
}
|
|
10565
|
+
const msgIdSet = /* @__PURE__ */ new Set();
|
|
10566
|
+
for (const block of activeBlocks) {
|
|
10567
|
+
for (const id of block.effectiveMessageIds ?? []) {
|
|
10568
|
+
msgIdSet.add(id);
|
|
10569
|
+
}
|
|
10570
|
+
}
|
|
10571
|
+
const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
|
|
10572
|
+
const lines2 = blockMessages.map(extractMessageText2);
|
|
10573
|
+
const { writeFile: writeFile4 } = await import("fs/promises");
|
|
10574
|
+
let fileContent;
|
|
10575
|
+
let sourceNote;
|
|
10576
|
+
if (lines2.length > 0) {
|
|
10577
|
+
fileContent = lines2.join("\n\n---\n\n");
|
|
10578
|
+
sourceNote = `${blockMessages.length} original message${blockMessages.length === 1 ? "" : "s"}`;
|
|
10579
|
+
} else {
|
|
10580
|
+
fileContent = targets[0]?.blocks[0]?.summary ?? "(no content available)";
|
|
10581
|
+
sourceNote = `no original messages found; wrote block summary (${targets.length} block${targets.length === 1 ? "" : "s"})`;
|
|
10510
10582
|
}
|
|
10583
|
+
await writeFile4(targetPath, fileContent, "utf-8");
|
|
10584
|
+
const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10585
|
+
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.`;
|
|
10511
10586
|
}
|
|
10512
|
-
const
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
if (lines2.length > 0) {
|
|
10518
|
-
fileContent = lines2.join("\n\n---\n\n");
|
|
10519
|
-
sourceNote = `${blockMessages.length} original message${blockMessages.length === 1 ? "" : "s"}`;
|
|
10520
|
-
} else {
|
|
10521
|
-
fileContent = targets[0]?.blocks[0]?.summary ?? "(no content available)";
|
|
10522
|
-
sourceNote = `no original messages found; wrote block summary (${targets.length} block${targets.length === 1 ? "" : "s"})`;
|
|
10523
|
-
}
|
|
10524
|
-
await writeFile4(targetPath, fileContent, "utf-8");
|
|
10525
|
-
const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10526
|
-
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.`;
|
|
10527
|
-
}
|
|
10528
|
-
const availability = checkDecompressSourceAvailability(
|
|
10529
|
-
messagesState,
|
|
10530
|
-
targets,
|
|
10531
|
-
{ full: args.full === true },
|
|
10532
|
-
new Set(rawMessages.map((m) => m.info.id))
|
|
10533
|
-
);
|
|
10534
|
-
if (availability.missingMessageIds.length > 0) {
|
|
10535
|
-
const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10536
|
-
const missingRefs = availability.missingMessageIds.map(
|
|
10537
|
-
(id) => ctx.state.messageIds.byRawId.get(id) ?? id
|
|
10587
|
+
const availability = checkDecompressSourceAvailability(
|
|
10588
|
+
messagesState,
|
|
10589
|
+
targets,
|
|
10590
|
+
{ full: args.full === true },
|
|
10591
|
+
new Set(rawMessages.map((m) => m.info.id))
|
|
10538
10592
|
);
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10593
|
+
if (availability.missingMessageIds.length > 0) {
|
|
10594
|
+
const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10595
|
+
const missingRefs = availability.missingMessageIds.map(
|
|
10596
|
+
(id) => ctx.state.messageIds.byRawId.get(id) ?? id
|
|
10597
|
+
);
|
|
10598
|
+
const shownRefs = missingRefs.slice(0, 10).join(", ");
|
|
10599
|
+
const extraCount = missingRefs.length - Math.min(missingRefs.length, 10);
|
|
10600
|
+
ctx.logger.warn("Decompress aborted: source messages missing from host history", {
|
|
10601
|
+
targetDisplayIds: displayIds2,
|
|
10602
|
+
full: args.full === true,
|
|
10603
|
+
requiredCount: availability.requiredMessageIds.length,
|
|
10604
|
+
missingCount: availability.missingMessageIds.length,
|
|
10605
|
+
missingRefs
|
|
10606
|
+
});
|
|
10607
|
+
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.`;
|
|
10608
|
+
}
|
|
10609
|
+
const activeMessagesBefore = snapshotActiveMessages(messagesState);
|
|
10610
|
+
const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
|
|
10611
|
+
for (const target of targets) {
|
|
10612
|
+
deactivateCompressionTarget(messagesState, target, { full: args.full === true });
|
|
10613
|
+
}
|
|
10614
|
+
syncCompressionBlocks(ctx.state, ctx.logger, rawMessages);
|
|
10615
|
+
const { restoredMessageCount, restoredTokens } = computeRestoredMessages(
|
|
10616
|
+
messagesState,
|
|
10617
|
+
activeMessagesBefore
|
|
10618
|
+
);
|
|
10619
|
+
const reactivatedBlockIds = computeReactivatedBlockIds(
|
|
10620
|
+
messagesState,
|
|
10621
|
+
activeBlockIdsBefore
|
|
10622
|
+
);
|
|
10623
|
+
ctx.state.stats.totalPruneTokens = Math.max(
|
|
10624
|
+
0,
|
|
10625
|
+
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
10626
|
+
);
|
|
10627
|
+
const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
10628
|
+
const contextUsageAfter = effectiveLimitAfter ? Math.round(
|
|
10629
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
10630
|
+
) : void 0;
|
|
10631
|
+
await finalizeDecompressSession(ctx);
|
|
10632
|
+
const restoredContentPreview = buildRestoredContentPreview(
|
|
10633
|
+
rawMessages,
|
|
10634
|
+
activeMessagesBefore,
|
|
10635
|
+
messagesState
|
|
10636
|
+
);
|
|
10637
|
+
const displayIds = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10638
|
+
const lines = [];
|
|
10639
|
+
const headerNoun = targets.length === 1 ? "block" : "blocks";
|
|
10640
|
+
lines.push(
|
|
10641
|
+
`Decompressed ${headerNoun} ${displayIds}. Restored ${restoredMessageCount} message(s) (~${formatTokenCount(restoredTokens)}).`
|
|
10642
|
+
);
|
|
10643
|
+
if (contextUsageBefore !== void 0 && contextUsageAfter !== void 0) {
|
|
10644
|
+
lines.push(`Context usage: ${contextUsageBefore}% \u2192 ${contextUsageAfter}%.`);
|
|
10645
|
+
}
|
|
10646
|
+
if (reactivatedBlockIds.length > 0) {
|
|
10647
|
+
const refs = reactivatedBlockIds.map((id) => `b${id}`).join(", ");
|
|
10648
|
+
lines.push(`Also restored nested block(s): ${refs}.`);
|
|
10649
|
+
}
|
|
10650
|
+
if (restoredContentPreview) {
|
|
10651
|
+
lines.push("");
|
|
10652
|
+
lines.push("RESTORED CONTENT (condensed):");
|
|
10653
|
+
lines.push(restoredContentPreview);
|
|
10654
|
+
}
|
|
10655
|
+
ctx.logger.info("Decompress tool completed", {
|
|
10656
|
+
mode: typeof args.startId === "string" ? "range" : "block",
|
|
10657
|
+
targetBlockIds: targets.map((t) => t.displayId),
|
|
10658
|
+
restoredMessageCount,
|
|
10659
|
+
restoredTokens,
|
|
10660
|
+
reactivatedBlockIds
|
|
10547
10661
|
});
|
|
10548
|
-
return
|
|
10549
|
-
}
|
|
10550
|
-
|
|
10551
|
-
const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
|
|
10552
|
-
for (const target of targets) {
|
|
10553
|
-
deactivateCompressionTarget(messagesState, target, { full: args.full === true });
|
|
10554
|
-
}
|
|
10555
|
-
syncCompressionBlocks(ctx.state, ctx.logger, rawMessages);
|
|
10556
|
-
const { restoredMessageCount, restoredTokens } = computeRestoredMessages(
|
|
10557
|
-
messagesState,
|
|
10558
|
-
activeMessagesBefore
|
|
10559
|
-
);
|
|
10560
|
-
const reactivatedBlockIds = computeReactivatedBlockIds(
|
|
10561
|
-
messagesState,
|
|
10562
|
-
activeBlockIdsBefore
|
|
10563
|
-
);
|
|
10564
|
-
ctx.state.stats.totalPruneTokens = Math.max(
|
|
10565
|
-
0,
|
|
10566
|
-
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
10567
|
-
);
|
|
10568
|
-
const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
10569
|
-
const contextUsageAfter = effectiveLimitAfter ? Math.round(
|
|
10570
|
-
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
10571
|
-
) : void 0;
|
|
10572
|
-
await finalizeDecompressSession(ctx);
|
|
10573
|
-
const restoredContentPreview = buildRestoredContentPreview(
|
|
10574
|
-
rawMessages,
|
|
10575
|
-
activeMessagesBefore,
|
|
10576
|
-
messagesState
|
|
10577
|
-
);
|
|
10578
|
-
const displayIds = targets.map((t) => `b${t.displayId}`).join(", ");
|
|
10579
|
-
const lines = [];
|
|
10580
|
-
const headerNoun = targets.length === 1 ? "block" : "blocks";
|
|
10581
|
-
lines.push(
|
|
10582
|
-
`Decompressed ${headerNoun} ${displayIds}. Restored ${restoredMessageCount} message(s) (~${formatTokenCount(restoredTokens)}).`
|
|
10583
|
-
);
|
|
10584
|
-
if (contextUsageBefore !== void 0 && contextUsageAfter !== void 0) {
|
|
10585
|
-
lines.push(`Context usage: ${contextUsageBefore}% \u2192 ${contextUsageAfter}%.`);
|
|
10586
|
-
}
|
|
10587
|
-
if (reactivatedBlockIds.length > 0) {
|
|
10588
|
-
const refs = reactivatedBlockIds.map((id) => `b${id}`).join(", ");
|
|
10589
|
-
lines.push(`Also restored nested block(s): ${refs}.`);
|
|
10590
|
-
}
|
|
10591
|
-
if (restoredContentPreview) {
|
|
10592
|
-
lines.push("");
|
|
10593
|
-
lines.push("RESTORED CONTENT (condensed):");
|
|
10594
|
-
lines.push(restoredContentPreview);
|
|
10595
|
-
}
|
|
10596
|
-
ctx.logger.info("Decompress tool completed", {
|
|
10597
|
-
mode: typeof args.startId === "string" ? "range" : "block",
|
|
10598
|
-
targetBlockIds: targets.map((t) => t.displayId),
|
|
10599
|
-
restoredMessageCount,
|
|
10600
|
-
restoredTokens,
|
|
10601
|
-
reactivatedBlockIds
|
|
10602
|
-
});
|
|
10603
|
-
return lines.join("\n");
|
|
10662
|
+
return lines.join("\n");
|
|
10663
|
+
};
|
|
10664
|
+
return factoryCtx.registry.withSessionGuard(toolCtx.sessionID, () => run());
|
|
10604
10665
|
}
|
|
10605
10666
|
});
|
|
10606
10667
|
}
|
|
@@ -11358,7 +11419,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
11358
11419
|
import { join as join5 } from "path";
|
|
11359
11420
|
import { existsSync as existsSync5 } from "fs";
|
|
11360
11421
|
import { homedir as homedir3 } from "os";
|
|
11361
|
-
var LOG_VERSION = true ? "1.18.
|
|
11422
|
+
var LOG_VERSION = true ? "1.18.5-pr.408.227" : "dev";
|
|
11362
11423
|
var LEVEL_RANK = {
|
|
11363
11424
|
debug: 10,
|
|
11364
11425
|
info: 20,
|
|
@@ -13491,22 +13552,24 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
13491
13552
|
logger.info("Skipping DCP system prompt injection for internal agent");
|
|
13492
13553
|
return;
|
|
13493
13554
|
}
|
|
13494
|
-
if (input.model?.limit?.context) {
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13555
|
+
if (input.model?.limit?.context && input.sessionID) {
|
|
13556
|
+
await registry4.withSessionGuard(input.sessionID, () => {
|
|
13557
|
+
const limit = input.model.limit.context;
|
|
13558
|
+
const providerID = input.model?.providerID;
|
|
13559
|
+
const modelID = input.model?.id;
|
|
13560
|
+
const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
|
|
13561
|
+
state.modelContextLimit = limit;
|
|
13562
|
+
if (providerID !== void 0) {
|
|
13563
|
+
state.modelProviderID = providerID;
|
|
13564
|
+
}
|
|
13565
|
+
if (modelID !== void 0) {
|
|
13566
|
+
state.modelID = modelID;
|
|
13567
|
+
}
|
|
13568
|
+
if (changed) {
|
|
13569
|
+
saveSessionState(state, logger).catch(() => {
|
|
13570
|
+
});
|
|
13571
|
+
}
|
|
13572
|
+
});
|
|
13510
13573
|
}
|
|
13511
13574
|
const effectivePermission = compressPermission(state, config);
|
|
13512
13575
|
if (effectivePermission === "deny") {
|
|
@@ -13541,11 +13604,172 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
13541
13604
|
return;
|
|
13542
13605
|
}
|
|
13543
13606
|
const lastUserMessage = getLastUserMessage(messages);
|
|
13544
|
-
|
|
13607
|
+
const runPipeline = async (state) => {
|
|
13608
|
+
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
13609
|
+
if (state.isSubAgent && !config.allowSubAgents) {
|
|
13610
|
+
return;
|
|
13611
|
+
}
|
|
13612
|
+
stripHallucinations(output.messages);
|
|
13613
|
+
const dropReasoningModel = lastUserMessage?.info?.model;
|
|
13614
|
+
const reasoningConfig = applyCompressOverrides(
|
|
13615
|
+
config,
|
|
13616
|
+
dropReasoningModel?.providerID ?? state.modelProviderID,
|
|
13617
|
+
dropReasoningModel?.modelID ?? state.modelID
|
|
13618
|
+
).compress.reasoning;
|
|
13619
|
+
if (reasoningConfig?.drop !== false) {
|
|
13620
|
+
const droppedReasoning = dropCompressReasoning(
|
|
13621
|
+
output.messages,
|
|
13622
|
+
reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
13623
|
+
);
|
|
13624
|
+
if (droppedReasoning > 0) {
|
|
13625
|
+
logger.debug("compress.reasoning: dropped oversized reasoning parts", {
|
|
13626
|
+
dropped: droppedReasoning,
|
|
13627
|
+
threshold: reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
13628
|
+
});
|
|
13629
|
+
}
|
|
13630
|
+
}
|
|
13631
|
+
ensureBuiltinFiltersRegistered();
|
|
13632
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
13633
|
+
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
13634
|
+
sessionId: state.sessionId ?? "",
|
|
13635
|
+
isSubAgent: state.isSubAgent,
|
|
13636
|
+
modelContextLimit: effectiveLimit?.limit
|
|
13637
|
+
});
|
|
13638
|
+
cacheSystemPromptTokens(state, output.messages);
|
|
13639
|
+
assignMessageRefs(state, output.messages);
|
|
13640
|
+
const imagesConfig = config.compress.images ?? DEFAULT_COMPRESS_IMAGES;
|
|
13641
|
+
if (imagesConfig.enabled) {
|
|
13642
|
+
const archivedImages = archiveSessionImages(
|
|
13643
|
+
state,
|
|
13644
|
+
output.messages,
|
|
13645
|
+
imagesConfig,
|
|
13646
|
+
logger
|
|
13647
|
+
);
|
|
13648
|
+
if (archivedImages > 0) {
|
|
13649
|
+
saveSessionState(state, logger).catch(() => {
|
|
13650
|
+
});
|
|
13651
|
+
}
|
|
13652
|
+
pruneImageArchive(state, imagesConfig, logger);
|
|
13653
|
+
}
|
|
13654
|
+
const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
|
|
13655
|
+
const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
|
|
13656
|
+
if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
|
|
13657
|
+
saveSessionState(state, logger).catch(() => {
|
|
13658
|
+
});
|
|
13659
|
+
}
|
|
13660
|
+
syncToolCache(state, config, logger, output.messages);
|
|
13661
|
+
buildToolIdList(state, output.messages);
|
|
13662
|
+
const batchResult = runBatchCleanup(state, config, logger, output.messages);
|
|
13663
|
+
if (batchResult.mergedCount > 0) {
|
|
13664
|
+
saveSessionState(state, logger).catch(() => {
|
|
13665
|
+
});
|
|
13666
|
+
}
|
|
13667
|
+
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
13668
|
+
const candidateMessages = config.compress.candidates === true ? output.messages.slice() : void 0;
|
|
13669
|
+
prune(state, logger, config, output.messages);
|
|
13670
|
+
hideConsumedCompressCalls(state, output.messages);
|
|
13671
|
+
assignMessageRefs(state, output.messages);
|
|
13672
|
+
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
13673
|
+
prompts.reload();
|
|
13674
|
+
injectCompressNudges(
|
|
13675
|
+
state,
|
|
13676
|
+
config,
|
|
13677
|
+
logger,
|
|
13678
|
+
output.messages,
|
|
13679
|
+
prompts.getRuntimePrompts(),
|
|
13680
|
+
compressionPriorities,
|
|
13681
|
+
config.debug ? (text) => {
|
|
13682
|
+
logger.debug(`[ACP Debug] Nudge injected:
|
|
13683
|
+
${text}`);
|
|
13684
|
+
client.tui.showToast({
|
|
13685
|
+
body: {
|
|
13686
|
+
title: "ACP: Nudge Injected",
|
|
13687
|
+
message: text.slice(0, 500),
|
|
13688
|
+
variant: "info",
|
|
13689
|
+
duration: 5e3
|
|
13690
|
+
}
|
|
13691
|
+
}).catch(() => {
|
|
13692
|
+
});
|
|
13693
|
+
} : void 0,
|
|
13694
|
+
prePruneTokens,
|
|
13695
|
+
candidateMessages
|
|
13696
|
+
);
|
|
13697
|
+
const emergencyTruncationEnabled = applyCompressOverrides(
|
|
13698
|
+
config,
|
|
13699
|
+
dropReasoningModel?.providerID ?? state.modelProviderID,
|
|
13700
|
+
dropReasoningModel?.modelID ?? state.modelID
|
|
13701
|
+
).compress.emergencyTruncation;
|
|
13702
|
+
if (emergencyTruncationEnabled === true) {
|
|
13703
|
+
applyStickyTruncations(state, output.messages, logger);
|
|
13704
|
+
const toolOutputsBefore = snapshotToolOutputs(output.messages);
|
|
13705
|
+
truncateLargeToolOutputs(
|
|
13706
|
+
state,
|
|
13707
|
+
config,
|
|
13708
|
+
logger,
|
|
13709
|
+
output.messages.filter((message) => !isSyntheticMessage(message))
|
|
13710
|
+
);
|
|
13711
|
+
enforceContextBudget(state, config, logger, output.messages);
|
|
13712
|
+
const frozenCount = recordStickyTruncations(
|
|
13713
|
+
state,
|
|
13714
|
+
output.messages,
|
|
13715
|
+
toolOutputsBefore,
|
|
13716
|
+
logger
|
|
13717
|
+
);
|
|
13718
|
+
if (frozenCount > 0) {
|
|
13719
|
+
saveSessionState(state, logger).catch(() => {
|
|
13720
|
+
});
|
|
13721
|
+
}
|
|
13722
|
+
}
|
|
13723
|
+
injectMessageIds(state, config, output.messages, compressionPriorities);
|
|
13724
|
+
hideFailedCompressCalls(output.messages);
|
|
13725
|
+
stripStaleMetadata(output.messages);
|
|
13726
|
+
dropEmptyMessages(output.messages);
|
|
13727
|
+
const smartPlanEnabled = applyCompressOverrides(
|
|
13728
|
+
config,
|
|
13729
|
+
state.modelProviderID,
|
|
13730
|
+
state.modelID
|
|
13731
|
+
).compress.smartPlanRequired;
|
|
13732
|
+
if (state.sessionId && smartPlanEnabled === true) {
|
|
13733
|
+
recordVisibleMessages(state.sessionId, output.messages);
|
|
13734
|
+
}
|
|
13735
|
+
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
13736
|
+
if (postTokens !== void 0 && effectiveLimit) {
|
|
13737
|
+
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
13738
|
+
if (postTokens > budget) {
|
|
13739
|
+
logger.error(
|
|
13740
|
+
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
13741
|
+
{
|
|
13742
|
+
session: state.sessionId,
|
|
13743
|
+
postTokens,
|
|
13744
|
+
budget,
|
|
13745
|
+
contextLimit: effectiveLimit.limit,
|
|
13746
|
+
contextLimitSource: effectiveLimit.source,
|
|
13747
|
+
hint: "request will likely be rejected; run /compact or start a new session"
|
|
13748
|
+
}
|
|
13749
|
+
);
|
|
13750
|
+
}
|
|
13751
|
+
}
|
|
13752
|
+
logger.info("Chat transform complete", {
|
|
13753
|
+
session: state.sessionId,
|
|
13754
|
+
model: state.modelID,
|
|
13755
|
+
messages: output.messages.length,
|
|
13756
|
+
prePruneTokens,
|
|
13757
|
+
postTokens,
|
|
13758
|
+
contextLimit: effectiveLimit?.limit,
|
|
13759
|
+
contextLimitSource: effectiveLimit?.source,
|
|
13760
|
+
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
13761
|
+
nudged: state.nudges.shouldInjectThisTurn
|
|
13762
|
+
});
|
|
13763
|
+
if (state.sessionId) {
|
|
13764
|
+
await logger.saveContext(state.sessionId, output.messages);
|
|
13765
|
+
}
|
|
13766
|
+
};
|
|
13545
13767
|
if (!lastUserMessage) {
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13768
|
+
await runPipeline(createSessionState());
|
|
13769
|
+
return;
|
|
13770
|
+
}
|
|
13771
|
+
await registry4.withSessionGuard(lastUserMessage.info.sessionID, async () => {
|
|
13772
|
+
const state = await registry4.getOrCreate(
|
|
13549
13773
|
client,
|
|
13550
13774
|
lastUserMessage.info.sessionID,
|
|
13551
13775
|
messages,
|
|
@@ -13602,165 +13826,8 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
13602
13826
|
}
|
|
13603
13827
|
);
|
|
13604
13828
|
}
|
|
13605
|
-
|
|
13606
|
-
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
13607
|
-
if (state.isSubAgent && !config.allowSubAgents) {
|
|
13608
|
-
return;
|
|
13609
|
-
}
|
|
13610
|
-
stripHallucinations(output.messages);
|
|
13611
|
-
const dropReasoningModel = lastUserMessage?.info?.model;
|
|
13612
|
-
const reasoningConfig = applyCompressOverrides(
|
|
13613
|
-
config,
|
|
13614
|
-
dropReasoningModel?.providerID ?? state.modelProviderID,
|
|
13615
|
-
dropReasoningModel?.modelID ?? state.modelID
|
|
13616
|
-
).compress.reasoning;
|
|
13617
|
-
if (reasoningConfig?.drop !== false) {
|
|
13618
|
-
const droppedReasoning = dropCompressReasoning(
|
|
13619
|
-
output.messages,
|
|
13620
|
-
reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
13621
|
-
);
|
|
13622
|
-
if (droppedReasoning > 0) {
|
|
13623
|
-
logger.debug("compress.reasoning: dropped oversized reasoning parts", {
|
|
13624
|
-
dropped: droppedReasoning,
|
|
13625
|
-
threshold: reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
|
|
13626
|
-
});
|
|
13627
|
-
}
|
|
13628
|
-
}
|
|
13629
|
-
ensureBuiltinFiltersRegistered();
|
|
13630
|
-
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
13631
|
-
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
13632
|
-
sessionId: state.sessionId ?? "",
|
|
13633
|
-
isSubAgent: state.isSubAgent,
|
|
13634
|
-
modelContextLimit: effectiveLimit?.limit
|
|
13635
|
-
});
|
|
13636
|
-
cacheSystemPromptTokens(state, output.messages);
|
|
13637
|
-
assignMessageRefs(state, output.messages);
|
|
13638
|
-
const imagesConfig = config.compress.images ?? DEFAULT_COMPRESS_IMAGES;
|
|
13639
|
-
if (imagesConfig.enabled) {
|
|
13640
|
-
const archivedImages = archiveSessionImages(
|
|
13641
|
-
state,
|
|
13642
|
-
output.messages,
|
|
13643
|
-
imagesConfig,
|
|
13644
|
-
logger
|
|
13645
|
-
);
|
|
13646
|
-
if (archivedImages > 0) {
|
|
13647
|
-
saveSessionState(state, logger).catch(() => {
|
|
13648
|
-
});
|
|
13649
|
-
}
|
|
13650
|
-
pruneImageArchive(state, imagesConfig, logger);
|
|
13651
|
-
}
|
|
13652
|
-
const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
|
|
13653
|
-
const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
|
|
13654
|
-
if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
|
|
13655
|
-
saveSessionState(state, logger).catch(() => {
|
|
13656
|
-
});
|
|
13657
|
-
}
|
|
13658
|
-
syncToolCache(state, config, logger, output.messages);
|
|
13659
|
-
buildToolIdList(state, output.messages);
|
|
13660
|
-
const batchResult = runBatchCleanup(state, config, logger, output.messages);
|
|
13661
|
-
if (batchResult.mergedCount > 0) {
|
|
13662
|
-
saveSessionState(state, logger).catch(() => {
|
|
13663
|
-
});
|
|
13664
|
-
}
|
|
13665
|
-
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
13666
|
-
const candidateMessages = config.compress.candidates === true ? output.messages.slice() : void 0;
|
|
13667
|
-
prune(state, logger, config, output.messages);
|
|
13668
|
-
hideConsumedCompressCalls(state, output.messages);
|
|
13669
|
-
assignMessageRefs(state, output.messages);
|
|
13670
|
-
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
13671
|
-
prompts.reload();
|
|
13672
|
-
injectCompressNudges(
|
|
13673
|
-
state,
|
|
13674
|
-
config,
|
|
13675
|
-
logger,
|
|
13676
|
-
output.messages,
|
|
13677
|
-
prompts.getRuntimePrompts(),
|
|
13678
|
-
compressionPriorities,
|
|
13679
|
-
config.debug ? (text) => {
|
|
13680
|
-
logger.debug(`[ACP Debug] Nudge injected:
|
|
13681
|
-
${text}`);
|
|
13682
|
-
client.tui.showToast({
|
|
13683
|
-
body: {
|
|
13684
|
-
title: "ACP: Nudge Injected",
|
|
13685
|
-
message: text.slice(0, 500),
|
|
13686
|
-
variant: "info",
|
|
13687
|
-
duration: 5e3
|
|
13688
|
-
}
|
|
13689
|
-
}).catch(() => {
|
|
13690
|
-
});
|
|
13691
|
-
} : void 0,
|
|
13692
|
-
prePruneTokens,
|
|
13693
|
-
candidateMessages
|
|
13694
|
-
);
|
|
13695
|
-
const emergencyTruncationEnabled = applyCompressOverrides(
|
|
13696
|
-
config,
|
|
13697
|
-
dropReasoningModel?.providerID ?? state.modelProviderID,
|
|
13698
|
-
dropReasoningModel?.modelID ?? state.modelID
|
|
13699
|
-
).compress.emergencyTruncation;
|
|
13700
|
-
if (emergencyTruncationEnabled === true) {
|
|
13701
|
-
applyStickyTruncations(state, output.messages, logger);
|
|
13702
|
-
const toolOutputsBefore = snapshotToolOutputs(output.messages);
|
|
13703
|
-
truncateLargeToolOutputs(
|
|
13704
|
-
state,
|
|
13705
|
-
config,
|
|
13706
|
-
logger,
|
|
13707
|
-
output.messages.filter((message) => !isSyntheticMessage(message))
|
|
13708
|
-
);
|
|
13709
|
-
enforceContextBudget(state, config, logger, output.messages);
|
|
13710
|
-
const frozenCount = recordStickyTruncations(
|
|
13711
|
-
state,
|
|
13712
|
-
output.messages,
|
|
13713
|
-
toolOutputsBefore,
|
|
13714
|
-
logger
|
|
13715
|
-
);
|
|
13716
|
-
if (frozenCount > 0) {
|
|
13717
|
-
saveSessionState(state, logger).catch(() => {
|
|
13718
|
-
});
|
|
13719
|
-
}
|
|
13720
|
-
}
|
|
13721
|
-
injectMessageIds(state, config, output.messages, compressionPriorities);
|
|
13722
|
-
hideFailedCompressCalls(output.messages);
|
|
13723
|
-
stripStaleMetadata(output.messages);
|
|
13724
|
-
dropEmptyMessages(output.messages);
|
|
13725
|
-
const smartPlanEnabled = applyCompressOverrides(
|
|
13726
|
-
config,
|
|
13727
|
-
state.modelProviderID,
|
|
13728
|
-
state.modelID
|
|
13729
|
-
).compress.smartPlanRequired;
|
|
13730
|
-
if (state.sessionId && smartPlanEnabled === true) {
|
|
13731
|
-
recordVisibleMessages(state.sessionId, output.messages);
|
|
13732
|
-
}
|
|
13733
|
-
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
13734
|
-
if (postTokens !== void 0 && effectiveLimit) {
|
|
13735
|
-
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
13736
|
-
if (postTokens > budget) {
|
|
13737
|
-
logger.error(
|
|
13738
|
-
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
13739
|
-
{
|
|
13740
|
-
session: state.sessionId,
|
|
13741
|
-
postTokens,
|
|
13742
|
-
budget,
|
|
13743
|
-
contextLimit: effectiveLimit.limit,
|
|
13744
|
-
contextLimitSource: effectiveLimit.source,
|
|
13745
|
-
hint: "request will likely be rejected; run /compact or start a new session"
|
|
13746
|
-
}
|
|
13747
|
-
);
|
|
13748
|
-
}
|
|
13749
|
-
}
|
|
13750
|
-
logger.info("Chat transform complete", {
|
|
13751
|
-
session: state.sessionId,
|
|
13752
|
-
model: state.modelID,
|
|
13753
|
-
messages: output.messages.length,
|
|
13754
|
-
prePruneTokens,
|
|
13755
|
-
postTokens,
|
|
13756
|
-
contextLimit: effectiveLimit?.limit,
|
|
13757
|
-
contextLimitSource: effectiveLimit?.source,
|
|
13758
|
-
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
13759
|
-
nudged: state.nudges.shouldInjectThisTurn
|
|
13829
|
+
await runPipeline(state);
|
|
13760
13830
|
});
|
|
13761
|
-
if (state.sessionId) {
|
|
13762
|
-
await logger.saveContext(state.sessionId, output.messages);
|
|
13763
|
-
}
|
|
13764
13831
|
};
|
|
13765
13832
|
}
|
|
13766
13833
|
function buildHelpText() {
|
|
@@ -13787,8 +13854,11 @@ function createCommandExecuteHandler(client, registry4, logger, config, workingD
|
|
|
13787
13854
|
path: { id: input.sessionID }
|
|
13788
13855
|
});
|
|
13789
13856
|
const messages = filterMessages(messagesResponse.data || messagesResponse);
|
|
13790
|
-
const state = await registry4.
|
|
13791
|
-
|
|
13857
|
+
const state = await registry4.withSessionGuard(input.sessionID, async () => {
|
|
13858
|
+
const s = await registry4.getOrCreate(client, input.sessionID, messages, config);
|
|
13859
|
+
syncCompressPermissionState(s, config, hostPermissions, messages);
|
|
13860
|
+
return s;
|
|
13861
|
+
});
|
|
13792
13862
|
const commandCtx = {
|
|
13793
13863
|
client,
|
|
13794
13864
|
state,
|
|
@@ -13892,18 +13962,27 @@ function createEventHandler(registry4, logger) {
|
|
|
13892
13962
|
callId: part.callID,
|
|
13893
13963
|
durationMs
|
|
13894
13964
|
});
|
|
13965
|
+
const settles = [];
|
|
13895
13966
|
for (const state of registry4.all()) {
|
|
13896
|
-
|
|
13897
|
-
|
|
13898
|
-
await saveSessionState(state, logger);
|
|
13899
|
-
logger.info("Attached compression time to blocks", {
|
|
13900
|
-
messageID: part.messageID,
|
|
13901
|
-
callID: part.callID,
|
|
13902
|
-
blocks: updates,
|
|
13903
|
-
durationMs
|
|
13904
|
-
});
|
|
13967
|
+
if (!state.sessionId) {
|
|
13968
|
+
continue;
|
|
13905
13969
|
}
|
|
13970
|
+
settles.push(
|
|
13971
|
+
registry4.withSessionGuard(state.sessionId, async () => {
|
|
13972
|
+
const updates = applyPendingCompressionDurations(state);
|
|
13973
|
+
if (updates > 0) {
|
|
13974
|
+
await saveSessionState(state, logger);
|
|
13975
|
+
logger.info("Attached compression time to blocks", {
|
|
13976
|
+
messageID: part.messageID,
|
|
13977
|
+
callID: part.callID,
|
|
13978
|
+
blocks: updates,
|
|
13979
|
+
durationMs
|
|
13980
|
+
});
|
|
13981
|
+
}
|
|
13982
|
+
})
|
|
13983
|
+
);
|
|
13906
13984
|
}
|
|
13985
|
+
await Promise.allSettled(settles);
|
|
13907
13986
|
return;
|
|
13908
13987
|
}
|
|
13909
13988
|
if (part.state.status === "running") {
|
|
@@ -14156,7 +14235,7 @@ var server = (async (ctx) => {
|
|
|
14156
14235
|
}
|
|
14157
14236
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
14158
14237
|
logger.info("ACP plugin initialized", {
|
|
14159
|
-
version: true ? "1.18.
|
|
14238
|
+
version: true ? "1.18.5-pr.408.227" : "dev",
|
|
14160
14239
|
workspace: ctx.directory,
|
|
14161
14240
|
logLevel: logger.level,
|
|
14162
14241
|
debug: config.debug,
|