opencode-acp 1.14.25-pr.348.72 → 1.14.25-pr.349.71
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 +333 -398
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/decompress.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +7 -15
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/gc/merge.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/index.d.ts +0 -2
- package/dist/lib/messages/index.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/messages/truncate-tools.d.ts +1 -0
- package/dist/lib/messages/truncate-tools.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts +2 -0
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +0 -13
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/state/utils.d.ts +16 -0
- package/dist/lib/state/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/lib/messages/prune-to-fit.d.ts +0 -25
- package/dist/lib/messages/prune-to-fit.d.ts.map +0 -1
- package/dist/lib/messages/uncalibrated-window.d.ts +0 -31
- package/dist/lib/messages/uncalibrated-window.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -888,6 +888,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
888
888
|
"compress.minContextLimit",
|
|
889
889
|
"compress.modelMaxLimits",
|
|
890
890
|
"compress.modelMinLimits",
|
|
891
|
+
"compress.contextLimitFallback",
|
|
891
892
|
"compress.nudgeFrequency",
|
|
892
893
|
"compress.minNudgeContextPercent",
|
|
893
894
|
"compress.nudgeGrowthTokens",
|
|
@@ -908,8 +909,6 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
908
909
|
"compress.preserveRecentMessages",
|
|
909
910
|
"compress.preserveRecentTokens",
|
|
910
911
|
"compress.preserveLastUserMessage",
|
|
911
|
-
"compress.overflowGuard",
|
|
912
|
-
"compress.overflowGuardReserve",
|
|
913
912
|
"gc",
|
|
914
913
|
"gc.algorithm",
|
|
915
914
|
"gc.promotionThreshold",
|
|
@@ -1263,27 +1262,6 @@ function validateConfigTypes(config) {
|
|
|
1263
1262
|
actual: typeof compress.preserveLastUserMessage
|
|
1264
1263
|
});
|
|
1265
1264
|
}
|
|
1266
|
-
if (compress.overflowGuard !== void 0 && typeof compress.overflowGuard !== "boolean") {
|
|
1267
|
-
errors.push({
|
|
1268
|
-
key: "compress.overflowGuard",
|
|
1269
|
-
expected: "boolean",
|
|
1270
|
-
actual: typeof compress.overflowGuard
|
|
1271
|
-
});
|
|
1272
|
-
}
|
|
1273
|
-
if (compress.overflowGuardReserve !== void 0 && typeof compress.overflowGuardReserve !== "number") {
|
|
1274
|
-
errors.push({
|
|
1275
|
-
key: "compress.overflowGuardReserve",
|
|
1276
|
-
expected: "number",
|
|
1277
|
-
actual: typeof compress.overflowGuardReserve
|
|
1278
|
-
});
|
|
1279
|
-
}
|
|
1280
|
-
if (typeof compress.overflowGuardReserve === "number" && compress.overflowGuardReserve < 0) {
|
|
1281
|
-
errors.push({
|
|
1282
|
-
key: "compress.overflowGuardReserve",
|
|
1283
|
-
expected: "non-negative number (>= 0)",
|
|
1284
|
-
actual: `${compress.overflowGuardReserve}`
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
1265
|
if (typeof compress.iterationNudgeThreshold === "number" && compress.iterationNudgeThreshold < 1) {
|
|
1288
1266
|
errors.push({
|
|
1289
1267
|
key: "compress.iterationNudgeThreshold",
|
|
@@ -1334,6 +1312,20 @@ function validateConfigTypes(config) {
|
|
|
1334
1312
|
}
|
|
1335
1313
|
validateModelLimits("compress.modelMaxLimits", compress.modelMaxLimits);
|
|
1336
1314
|
validateModelLimits("compress.modelMinLimits", compress.modelMinLimits);
|
|
1315
|
+
if (compress.contextLimitFallback !== void 0 && typeof compress.contextLimitFallback !== "number") {
|
|
1316
|
+
errors.push({
|
|
1317
|
+
key: "compress.contextLimitFallback",
|
|
1318
|
+
expected: "number",
|
|
1319
|
+
actual: typeof compress.contextLimitFallback
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
if (typeof compress.contextLimitFallback === "number" && compress.contextLimitFallback < 0) {
|
|
1323
|
+
errors.push({
|
|
1324
|
+
key: "compress.contextLimitFallback",
|
|
1325
|
+
expected: "non-negative number (0 disables the fallback)",
|
|
1326
|
+
actual: `${compress.contextLimitFallback}`
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1337
1329
|
const validValues = ["ask", "allow", "deny"];
|
|
1338
1330
|
if (compress.permission !== void 0 && !validValues.includes(compress.permission)) {
|
|
1339
1331
|
errors.push({
|
|
@@ -1511,6 +1503,7 @@ var defaultConfig = {
|
|
|
1511
1503
|
summaryBuffer: true,
|
|
1512
1504
|
maxContextLimit: "80%",
|
|
1513
1505
|
minContextLimit: "80%",
|
|
1506
|
+
contextLimitFallback: 128e3,
|
|
1514
1507
|
nudgeFrequency: 5,
|
|
1515
1508
|
minNudgeContextPercent: 15,
|
|
1516
1509
|
iterationNudgeThreshold: 15,
|
|
@@ -1529,9 +1522,7 @@ var defaultConfig = {
|
|
|
1529
1522
|
lastSegmentSoftBlock: true,
|
|
1530
1523
|
preserveRecentMessages: 5,
|
|
1531
1524
|
preserveRecentTokens: 5e3,
|
|
1532
|
-
preserveLastUserMessage: true
|
|
1533
|
-
overflowGuard: true,
|
|
1534
|
-
overflowGuardReserve: 32768
|
|
1525
|
+
preserveLastUserMessage: true
|
|
1535
1526
|
},
|
|
1536
1527
|
gc: {
|
|
1537
1528
|
algorithm: "truncate",
|
|
@@ -1648,6 +1639,7 @@ function mergeCompress(base, override) {
|
|
|
1648
1639
|
minContextLimit: override.minContextLimit ?? base.minContextLimit,
|
|
1649
1640
|
modelMaxLimits: override.modelMaxLimits ?? base.modelMaxLimits,
|
|
1650
1641
|
modelMinLimits: override.modelMinLimits ?? base.modelMinLimits,
|
|
1642
|
+
contextLimitFallback: override.contextLimitFallback ?? base.contextLimitFallback,
|
|
1651
1643
|
nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
|
|
1652
1644
|
minNudgeContextPercent: override.minNudgeContextPercent ?? base.minNudgeContextPercent,
|
|
1653
1645
|
nudgeGrowthTokens: override.nudgeGrowthTokens,
|
|
@@ -1667,9 +1659,7 @@ function mergeCompress(base, override) {
|
|
|
1667
1659
|
lastSegmentSoftBlock: override.lastSegmentSoftBlock ?? base.lastSegmentSoftBlock,
|
|
1668
1660
|
preserveRecentMessages: override.preserveRecentMessages ?? base.preserveRecentMessages,
|
|
1669
1661
|
preserveRecentTokens: override.preserveRecentTokens ?? base.preserveRecentTokens,
|
|
1670
|
-
preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage
|
|
1671
|
-
overflowGuard: override.overflowGuard ?? base.overflowGuard,
|
|
1672
|
-
overflowGuardReserve: override.overflowGuardReserve ?? base.overflowGuardReserve
|
|
1662
|
+
preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage
|
|
1673
1663
|
};
|
|
1674
1664
|
}
|
|
1675
1665
|
function mergeCommands(base, override) {
|
|
@@ -2392,6 +2382,16 @@ function resetOnCompaction(state) {
|
|
|
2392
2382
|
nextRef: 1
|
|
2393
2383
|
};
|
|
2394
2384
|
}
|
|
2385
|
+
function resolveEffectiveContextLimit(state, config) {
|
|
2386
|
+
if (typeof state.modelContextLimit === "number" && state.modelContextLimit > 0) {
|
|
2387
|
+
return { limit: state.modelContextLimit, source: "model" };
|
|
2388
|
+
}
|
|
2389
|
+
const fallback = config.compress.contextLimitFallback;
|
|
2390
|
+
if (typeof fallback === "number" && fallback > 0) {
|
|
2391
|
+
return { limit: fallback, source: "fallback" };
|
|
2392
|
+
}
|
|
2393
|
+
return void 0;
|
|
2394
|
+
}
|
|
2395
2395
|
|
|
2396
2396
|
// lib/state/persistence.ts
|
|
2397
2397
|
function getStorageDir() {
|
|
@@ -4237,6 +4237,24 @@ var SessionStateRegistry = class {
|
|
|
4237
4237
|
hydrateModelLimitsFromClient(client) {
|
|
4238
4238
|
return this.catalog.hydrateFromClient(client);
|
|
4239
4239
|
}
|
|
4240
|
+
// [FIX #346] The init-time seed (above) is fire-and-forget and races
|
|
4241
|
+
// server readiness: in headless spawn+resume mode the provider-config
|
|
4242
|
+
// call can fail before the server is up, leaving the catalog empty for
|
|
4243
|
+
// the process's lifetime. During a request the server is guaranteed up
|
|
4244
|
+
// (we are inside its pipeline), so on a catalog miss we retry hydration
|
|
4245
|
+
// once per process before giving up (the fallback limit then applies).
|
|
4246
|
+
lazyHydrated = false;
|
|
4247
|
+
async hydrateAndResolve(client, providerId, modelId) {
|
|
4248
|
+
const existing = this.catalog.resolve(providerId, modelId);
|
|
4249
|
+
if (existing !== void 0) {
|
|
4250
|
+
return existing;
|
|
4251
|
+
}
|
|
4252
|
+
if (!this.lazyHydrated) {
|
|
4253
|
+
this.lazyHydrated = true;
|
|
4254
|
+
await this.catalog.hydrateFromClient(client);
|
|
4255
|
+
}
|
|
4256
|
+
return this.catalog.resolve(providerId, modelId);
|
|
4257
|
+
}
|
|
4240
4258
|
get(sessionId) {
|
|
4241
4259
|
return this.states.get(sessionId);
|
|
4242
4260
|
}
|
|
@@ -4328,9 +4346,7 @@ function createSessionState() {
|
|
|
4328
4346
|
modelProviderID: void 0,
|
|
4329
4347
|
modelID: void 0,
|
|
4330
4348
|
systemPromptTokens: void 0,
|
|
4331
|
-
qualityGateRetryPending: false
|
|
4332
|
-
uncalibratedWindowTransforms: 0,
|
|
4333
|
-
uncalibratedWindowWarned: false
|
|
4349
|
+
qualityGateRetryPending: false
|
|
4334
4350
|
};
|
|
4335
4351
|
}
|
|
4336
4352
|
function resetSessionState(state) {
|
|
@@ -4372,8 +4388,6 @@ function resetSessionState(state) {
|
|
|
4372
4388
|
state.modelID = void 0;
|
|
4373
4389
|
state.systemPromptTokens = void 0;
|
|
4374
4390
|
state.qualityGateRetryPending = false;
|
|
4375
|
-
state.uncalibratedWindowTransforms = 0;
|
|
4376
|
-
state.uncalibratedWindowWarned = false;
|
|
4377
4391
|
}
|
|
4378
4392
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config) {
|
|
4379
4393
|
if (state.sessionId === sessionId) {
|
|
@@ -6420,66 +6434,159 @@ var filterCompressedRanges = (state, messages) => {
|
|
|
6420
6434
|
messages.push(...result);
|
|
6421
6435
|
};
|
|
6422
6436
|
|
|
6423
|
-
// lib/
|
|
6424
|
-
function
|
|
6425
|
-
const
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
const
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
for (let i = 0; i < 3 && i * chunkSize < blocksWithRef.length; i++) {
|
|
6450
|
-
const chunk = blocksWithRef.slice(i * chunkSize, (i + 1) * chunkSize);
|
|
6451
|
-
if (chunk.length < 2) continue;
|
|
6452
|
-
const startRef = chunk[0].ref;
|
|
6453
|
-
const endRef = chunk[chunk.length - 1].ref;
|
|
6454
|
-
const chunkTokens = chunk.reduce((s, x) => s + (x.block.summaryTokens ?? 0), 0);
|
|
6455
|
-
const chunkK = Math.max(1, Math.round(chunkTokens / 1e3));
|
|
6456
|
-
targets.push(` \u2022 compress ${startRef}\u2192${endRef}: ${chunk.length} blocks (~${chunkK}K tokens)`);
|
|
6437
|
+
// lib/messages/sync.ts
|
|
6438
|
+
function sortBlocksByCreation(a, b) {
|
|
6439
|
+
const createdAtDiff = a.createdAt - b.createdAt;
|
|
6440
|
+
if (createdAtDiff !== 0) {
|
|
6441
|
+
return createdAtDiff;
|
|
6442
|
+
}
|
|
6443
|
+
return a.blockId - b.blockId;
|
|
6444
|
+
}
|
|
6445
|
+
var syncCompressionBlocks = (state, logger, messages) => {
|
|
6446
|
+
const messagesState = state.prune.messages;
|
|
6447
|
+
if (!messagesState?.blocksById?.size) {
|
|
6448
|
+
return;
|
|
6449
|
+
}
|
|
6450
|
+
const messageIds = new Set(messages.map((msg) => msg.info.id));
|
|
6451
|
+
const previousActiveBlockIds = new Set(
|
|
6452
|
+
Array.from(messagesState.blocksById.values()).filter((block) => block.active).map((block) => block.blockId)
|
|
6453
|
+
);
|
|
6454
|
+
messagesState.activeBlockIds.clear();
|
|
6455
|
+
messagesState.activeByAnchorMessageId.clear();
|
|
6456
|
+
const now = Date.now();
|
|
6457
|
+
const orderedBlocks = Array.from(messagesState.blocksById.values()).sort(sortBlocksByCreation);
|
|
6458
|
+
for (const block of orderedBlocks) {
|
|
6459
|
+
if (block.deactivatedByUser || block.deactivatedByUserDeep) {
|
|
6460
|
+
block.active = false;
|
|
6461
|
+
if (block.deactivatedAt === void 0) {
|
|
6462
|
+
block.deactivatedAt = now;
|
|
6457
6463
|
}
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6464
|
+
block.deactivatedByBlockId = void 0;
|
|
6465
|
+
continue;
|
|
6466
|
+
}
|
|
6467
|
+
for (const consumedBlockId of block.consumedBlockIds) {
|
|
6468
|
+
if (!messagesState.activeBlockIds.has(consumedBlockId)) {
|
|
6469
|
+
continue;
|
|
6470
|
+
}
|
|
6471
|
+
const consumedBlock = messagesState.blocksById.get(consumedBlockId);
|
|
6472
|
+
if (consumedBlock) {
|
|
6473
|
+
consumedBlock.active = false;
|
|
6474
|
+
consumedBlock.deactivatedAt = now;
|
|
6475
|
+
consumedBlock.deactivatedByBlockId = block.blockId;
|
|
6476
|
+
const mappedBlockId = messagesState.activeByAnchorMessageId.get(
|
|
6477
|
+
consumedBlock.anchorMessageId
|
|
6478
|
+
);
|
|
6479
|
+
if (mappedBlockId === consumedBlock.blockId) {
|
|
6480
|
+
messagesState.activeByAnchorMessageId.delete(consumedBlock.anchorMessageId);
|
|
6481
|
+
}
|
|
6462
6482
|
}
|
|
6483
|
+
messagesState.activeBlockIds.delete(consumedBlockId);
|
|
6484
|
+
}
|
|
6485
|
+
block.active = true;
|
|
6486
|
+
block.deactivatedAt = void 0;
|
|
6487
|
+
block.deactivatedByBlockId = void 0;
|
|
6488
|
+
messagesState.activeBlockIds.add(block.blockId);
|
|
6489
|
+
if (messageIds.has(block.anchorMessageId)) {
|
|
6490
|
+
messagesState.activeByAnchorMessageId.set(block.anchorMessageId, block.blockId);
|
|
6463
6491
|
}
|
|
6464
6492
|
}
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
return nudgeText;
|
|
6493
|
+
for (const entry of messagesState.byMessageId.values()) {
|
|
6494
|
+
const allBlockIds = Array.isArray(entry.allBlockIds) ? [...new Set(entry.allBlockIds.filter((id) => Number.isInteger(id) && id > 0))] : [];
|
|
6495
|
+
entry.allBlockIds = allBlockIds;
|
|
6496
|
+
entry.activeBlockIds = allBlockIds.filter((id) => messagesState.activeBlockIds.has(id));
|
|
6470
6497
|
}
|
|
6471
|
-
const
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6498
|
+
const nextActiveBlockIds = messagesState.activeBlockIds;
|
|
6499
|
+
let deactivatedCount = 0;
|
|
6500
|
+
let reactivatedCount = 0;
|
|
6501
|
+
for (const blockId of previousActiveBlockIds) {
|
|
6502
|
+
if (!nextActiveBlockIds.has(blockId)) {
|
|
6503
|
+
deactivatedCount++;
|
|
6504
|
+
}
|
|
6475
6505
|
}
|
|
6476
|
-
const
|
|
6477
|
-
|
|
6478
|
-
|
|
6506
|
+
for (const blockId of nextActiveBlockIds) {
|
|
6507
|
+
if (!previousActiveBlockIds.has(blockId)) {
|
|
6508
|
+
reactivatedCount++;
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
if (deactivatedCount > 0 || reactivatedCount > 0) {
|
|
6512
|
+
logger.info("Synced compress block state", {
|
|
6513
|
+
deactivatedCount,
|
|
6514
|
+
reactivatedCount
|
|
6515
|
+
});
|
|
6516
|
+
}
|
|
6517
|
+
};
|
|
6479
6518
|
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6519
|
+
// lib/host-permissions.ts
|
|
6520
|
+
var findLastMatchingRule = (rules, predicate) => {
|
|
6521
|
+
for (let index = rules.length - 1; index >= 0; index -= 1) {
|
|
6522
|
+
const rule = rules[index];
|
|
6523
|
+
if (rule && predicate(rule)) {
|
|
6524
|
+
return rule;
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
return void 0;
|
|
6528
|
+
};
|
|
6529
|
+
var wildcardMatch = (value, pattern) => {
|
|
6530
|
+
const normalizedValue = value.replaceAll("\\", "/");
|
|
6531
|
+
let escaped = pattern.replaceAll("\\", "/").replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
6532
|
+
if (escaped.endsWith(" .*")) {
|
|
6533
|
+
escaped = escaped.slice(0, -3) + "( .*)?";
|
|
6534
|
+
}
|
|
6535
|
+
const flags = process.platform === "win32" ? "si" : "s";
|
|
6536
|
+
return new RegExp(`^${escaped}$`, flags).test(normalizedValue);
|
|
6537
|
+
};
|
|
6538
|
+
var getPermissionRules = (permissionConfigs) => {
|
|
6539
|
+
const rules = [];
|
|
6540
|
+
for (const permissionConfig of permissionConfigs) {
|
|
6541
|
+
if (!permissionConfig) {
|
|
6542
|
+
continue;
|
|
6543
|
+
}
|
|
6544
|
+
for (const [permission, value] of Object.entries(permissionConfig)) {
|
|
6545
|
+
if (value === "ask" || value === "allow" || value === "deny") {
|
|
6546
|
+
rules.push({ permission, pattern: "*", action: value });
|
|
6547
|
+
continue;
|
|
6548
|
+
}
|
|
6549
|
+
for (const [pattern, action] of Object.entries(value)) {
|
|
6550
|
+
if (action === "ask" || action === "allow" || action === "deny") {
|
|
6551
|
+
rules.push({ permission, pattern, action });
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
}
|
|
6556
|
+
return rules;
|
|
6557
|
+
};
|
|
6558
|
+
var compressDisabledByOpencode = (...permissionConfigs) => {
|
|
6559
|
+
const match = findLastMatchingRule(
|
|
6560
|
+
getPermissionRules(permissionConfigs),
|
|
6561
|
+
(rule) => wildcardMatch("compress", rule.permission)
|
|
6562
|
+
);
|
|
6563
|
+
return match?.pattern === "*" && match.action === "deny";
|
|
6564
|
+
};
|
|
6565
|
+
var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agentName) => {
|
|
6566
|
+
if (basePermission === "deny") {
|
|
6567
|
+
return "deny";
|
|
6568
|
+
}
|
|
6569
|
+
return compressDisabledByOpencode(
|
|
6570
|
+
hostPermissions.global,
|
|
6571
|
+
agentName ? hostPermissions.agents[agentName] : void 0
|
|
6572
|
+
) ? "deny" : basePermission;
|
|
6573
|
+
};
|
|
6574
|
+
var hasExplicitToolPermission = (permissionConfig, tool6) => {
|
|
6575
|
+
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
|
|
6576
|
+
};
|
|
6577
|
+
|
|
6578
|
+
// lib/compress-permission.ts
|
|
6579
|
+
var compressPermission = (state, config) => {
|
|
6580
|
+
return state.compressPermission ?? config.compress.permission;
|
|
6581
|
+
};
|
|
6582
|
+
var syncCompressPermissionState = (state, config, hostPermissions, messages) => {
|
|
6583
|
+
const activeAgent = getLastUserMessage(messages)?.info.agent;
|
|
6584
|
+
state.compressPermission = resolveEffectiveCompressPermission(
|
|
6585
|
+
config.compress.permission,
|
|
6586
|
+
hostPermissions,
|
|
6587
|
+
activeAgent
|
|
6588
|
+
);
|
|
6589
|
+
};
|
|
6483
6590
|
|
|
6484
6591
|
// lib/messages/utils.ts
|
|
6485
6592
|
import { createHash } from "crypto";
|
|
@@ -6655,6 +6762,67 @@ var dropEmptyMessages = (messages) => {
|
|
|
6655
6762
|
return removed;
|
|
6656
6763
|
};
|
|
6657
6764
|
|
|
6765
|
+
// lib/prompts/extensions/nudge.ts
|
|
6766
|
+
function buildCompressedBlockGuidance(state, context) {
|
|
6767
|
+
const activeBlockIds = Array.from(state.prune.messages.activeBlockIds).filter((id) => Number.isInteger(id) && id > 0).sort((a, b) => a - b);
|
|
6768
|
+
const blockCount = activeBlockIds.length;
|
|
6769
|
+
const blocksForStats = activeBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0 && b.active);
|
|
6770
|
+
const totalSummaryTokens = blocksForStats.reduce((s, b) => s + (b.summaryTokens ?? 0), 0);
|
|
6771
|
+
const totalSummaryDisplay = totalSummaryTokens >= 1e3 ? `${(totalSummaryTokens / 1e3).toFixed(1)}K` : String(totalSummaryTokens);
|
|
6772
|
+
const lastBlock = blocksForStats.length > 0 ? blocksForStats.reduce((latest, b) => b.createdAt > latest.createdAt ? b : latest) : null;
|
|
6773
|
+
const ageStr = lastBlock ? formatAge(lastBlock.createdAt) : "never";
|
|
6774
|
+
const lines = [
|
|
6775
|
+
`- Compressed blocks: ${blockCount} (${totalSummaryDisplay} summary, last ${ageStr}). Use acp_status for details.`
|
|
6776
|
+
];
|
|
6777
|
+
if (blockCount > 50) {
|
|
6778
|
+
const oldBlockIds = activeBlockIds.slice(0, Math.max(0, blockCount - 20));
|
|
6779
|
+
const allOldBlocks = oldBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0);
|
|
6780
|
+
const visibleMessageIds = context?.visibleMessageIds;
|
|
6781
|
+
const visibleOldBlocks = visibleMessageIds === void 0 ? allOldBlocks : allOldBlocks.filter((b) => b.anchorMessageId && visibleMessageIds.has(b.anchorMessageId));
|
|
6782
|
+
if (visibleOldBlocks.length > 5) {
|
|
6783
|
+
const blocksWithRef = visibleOldBlocks.map((block) => {
|
|
6784
|
+
const ref = state.messageIds.byRawId.get(block.anchorMessageId);
|
|
6785
|
+
return ref ? { block, ref } : null;
|
|
6786
|
+
}).filter((x) => x !== null).sort((a, b) => a.ref.localeCompare(b.ref));
|
|
6787
|
+
const totalTokens = blocksWithRef.reduce((s, x) => s + (x.block.summaryTokens ?? 0), 0);
|
|
6788
|
+
const totalK = Math.max(1, Math.round(totalTokens / 1e3));
|
|
6789
|
+
const targets = [];
|
|
6790
|
+
const chunkSize = Math.ceil(blocksWithRef.length / 3);
|
|
6791
|
+
for (let i = 0; i < 3 && i * chunkSize < blocksWithRef.length; i++) {
|
|
6792
|
+
const chunk = blocksWithRef.slice(i * chunkSize, (i + 1) * chunkSize);
|
|
6793
|
+
if (chunk.length < 2) continue;
|
|
6794
|
+
const startRef = chunk[0].ref;
|
|
6795
|
+
const endRef = chunk[chunk.length - 1].ref;
|
|
6796
|
+
const chunkTokens = chunk.reduce((s, x) => s + (x.block.summaryTokens ?? 0), 0);
|
|
6797
|
+
const chunkK = Math.max(1, Math.round(chunkTokens / 1e3));
|
|
6798
|
+
targets.push(` \u2022 compress ${startRef}\u2192${endRef}: ${chunk.length} blocks (~${chunkK}K tokens)`);
|
|
6799
|
+
}
|
|
6800
|
+
if (targets.length > 0) {
|
|
6801
|
+
lines.push(`- \u{1F500} ${blocksWithRef.length} old blocks using ~${totalK}K tokens. Consolidate into ${targets.length}:`);
|
|
6802
|
+
lines.push(...targets);
|
|
6803
|
+
lines.push(` System auto-detects blocks in range \u2014 no need to manually list (bN) placeholders. Just write your summary normally.`);
|
|
6804
|
+
}
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
return lines.join("\n");
|
|
6808
|
+
}
|
|
6809
|
+
function appendGuidanceToDcpTag(nudgeText, guidance) {
|
|
6810
|
+
if (!guidance.trim()) {
|
|
6811
|
+
return nudgeText;
|
|
6812
|
+
}
|
|
6813
|
+
const closeTag = "</dcp-system-reminder>";
|
|
6814
|
+
const closeTagIndex = nudgeText.lastIndexOf(closeTag);
|
|
6815
|
+
if (closeTagIndex === -1) {
|
|
6816
|
+
return nudgeText;
|
|
6817
|
+
}
|
|
6818
|
+
const beforeClose = nudgeText.slice(0, closeTagIndex).trimEnd();
|
|
6819
|
+
const afterClose = nudgeText.slice(closeTagIndex);
|
|
6820
|
+
return `${beforeClose}
|
|
6821
|
+
|
|
6822
|
+
${guidance}
|
|
6823
|
+
${afterClose}`;
|
|
6824
|
+
}
|
|
6825
|
+
|
|
6658
6826
|
// node_modules/context-compress-algorithms/dist/chunk-BZYW3CH5.js
|
|
6659
6827
|
var NUDGE_GROWTH_FLOOR = 6e3;
|
|
6660
6828
|
var NUDGE_GROWTH_CAP = 5e4;
|
|
@@ -6757,6 +6925,7 @@ function getModelInfo(messages) {
|
|
|
6757
6925
|
};
|
|
6758
6926
|
}
|
|
6759
6927
|
function resolveContextTokenLimit(config, state, providerId, modelId, threshold) {
|
|
6928
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6760
6929
|
const parseLimitValue = (limit) => {
|
|
6761
6930
|
if (limit === void 0) {
|
|
6762
6931
|
return void 0;
|
|
@@ -6764,7 +6933,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6764
6933
|
if (typeof limit === "number") {
|
|
6765
6934
|
return limit;
|
|
6766
6935
|
}
|
|
6767
|
-
if (!limit.endsWith("%") ||
|
|
6936
|
+
if (!limit.endsWith("%") || effectiveLimit === void 0) {
|
|
6768
6937
|
return void 0;
|
|
6769
6938
|
}
|
|
6770
6939
|
const parsedPercent = parseFloat(limit.slice(0, -1));
|
|
@@ -6773,7 +6942,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6773
6942
|
}
|
|
6774
6943
|
const roundedPercent = Math.round(parsedPercent);
|
|
6775
6944
|
const clampedPercent = Math.max(0, Math.min(100, roundedPercent));
|
|
6776
|
-
return Math.round(clampedPercent / 100 *
|
|
6945
|
+
return Math.round(clampedPercent / 100 * effectiveLimit.limit);
|
|
6777
6946
|
};
|
|
6778
6947
|
const modelLimits = threshold === "max" ? config.compress.modelMaxLimits : config.compress.modelMinLimits;
|
|
6779
6948
|
if (modelLimits && providerId !== void 0 && modelId !== void 0) {
|
|
@@ -6818,11 +6987,12 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
|
6818
6987
|
if (!overMaxLimit) break;
|
|
6819
6988
|
}
|
|
6820
6989
|
}
|
|
6990
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6821
6991
|
return {
|
|
6822
6992
|
overMaxLimit,
|
|
6823
6993
|
overMinLimit,
|
|
6824
6994
|
currentTokens,
|
|
6825
|
-
modelContextLimit:
|
|
6995
|
+
modelContextLimit: effectiveLimit?.limit
|
|
6826
6996
|
};
|
|
6827
6997
|
}
|
|
6828
6998
|
ensureBuiltinTriggerPolicyRegistered();
|
|
@@ -7358,291 +7528,6 @@ function excludeProtectedRanges(ranges, protectedRefs) {
|
|
|
7358
7528
|
);
|
|
7359
7529
|
}
|
|
7360
7530
|
|
|
7361
|
-
// lib/messages/prune-to-fit.ts
|
|
7362
|
-
var WIRE_SAFETY_MARGIN = 8192;
|
|
7363
|
-
var CLEAR_PLACEHOLDER = "[cleared by ACP overflow guard \u2014 re-run tool if needed]";
|
|
7364
|
-
var MIN_CLEAR_TOKENS = 500;
|
|
7365
|
-
function resolveKnownWindow(config, state, providerId, modelId) {
|
|
7366
|
-
if (state.modelContextLimit !== void 0) {
|
|
7367
|
-
return state.modelContextLimit;
|
|
7368
|
-
}
|
|
7369
|
-
const maxLimits = config.compress.modelMaxLimits;
|
|
7370
|
-
if (maxLimits && providerId !== void 0 && modelId !== void 0) {
|
|
7371
|
-
const perModel = maxLimits[`${providerId}/${modelId}`];
|
|
7372
|
-
if (typeof perModel === "number") return perModel;
|
|
7373
|
-
}
|
|
7374
|
-
const global = config.compress.maxContextLimit;
|
|
7375
|
-
if (typeof global === "number") return global;
|
|
7376
|
-
return void 0;
|
|
7377
|
-
}
|
|
7378
|
-
function estimateWireTokens(state, messages) {
|
|
7379
|
-
const base = getCurrentTokenUsage(state, messages);
|
|
7380
|
-
if (base > 0) {
|
|
7381
|
-
let trailing = 0;
|
|
7382
|
-
const lastAsst = [...messages].reverse().find((m) => m.info.role === "assistant");
|
|
7383
|
-
if (lastAsst) {
|
|
7384
|
-
const parts = Array.isArray(lastAsst.parts) ? lastAsst.parts : [];
|
|
7385
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
7386
|
-
const p = parts[i];
|
|
7387
|
-
if (p?.type !== "tool") break;
|
|
7388
|
-
if (p.state.status !== "completed") break;
|
|
7389
|
-
trailing += countTokens2(extractCompletedToolOutput(p) ?? "");
|
|
7390
|
-
}
|
|
7391
|
-
}
|
|
7392
|
-
return base + trailing + WIRE_SAFETY_MARGIN;
|
|
7393
|
-
}
|
|
7394
|
-
let total = 0;
|
|
7395
|
-
for (const msg of messages) {
|
|
7396
|
-
total += countAllMessageTokens(msg);
|
|
7397
|
-
}
|
|
7398
|
-
return total + (state.systemPromptTokens ?? 0) + WIRE_SAFETY_MARGIN;
|
|
7399
|
-
}
|
|
7400
|
-
function pruneToFit(state, config, logger, messages) {
|
|
7401
|
-
if (!config.compress.overflowGuard) return;
|
|
7402
|
-
const knownWindow = resolveKnownWindow(config, state, state.modelProviderID, state.modelID);
|
|
7403
|
-
if (knownWindow === void 0) return;
|
|
7404
|
-
const reserve = config.compress.overflowGuardReserve ?? 32768;
|
|
7405
|
-
const safeBudget = knownWindow - reserve;
|
|
7406
|
-
if (safeBudget <= 0) return;
|
|
7407
|
-
const estimate = estimateWireTokens(state, messages);
|
|
7408
|
-
if (estimate <= safeBudget) return;
|
|
7409
|
-
const protectedRefs = computeProtectedRefs(messages, state, config.compress);
|
|
7410
|
-
const lastNonIgnored = findLastNonIgnoredMessage(messages);
|
|
7411
|
-
const lastMsgId = lastNonIgnored?.message.info.id;
|
|
7412
|
-
const protectedTools = config.compress.protectedTools;
|
|
7413
|
-
const protectedFilePatterns = config.protectedFilePatterns;
|
|
7414
|
-
let freed = 0;
|
|
7415
|
-
let clearedCount = 0;
|
|
7416
|
-
for (const msg of messages) {
|
|
7417
|
-
if (estimate - freed <= safeBudget) break;
|
|
7418
|
-
if (msg.info.id === lastMsgId) continue;
|
|
7419
|
-
if (msg.info.role === "user") continue;
|
|
7420
|
-
const ref = state.messageIds.byRawId.get(msg.info.id);
|
|
7421
|
-
if (ref && protectedRefs.has(ref)) continue;
|
|
7422
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
7423
|
-
for (const part of parts) {
|
|
7424
|
-
if (estimate - freed <= safeBudget) break;
|
|
7425
|
-
if (part?.type !== "tool") continue;
|
|
7426
|
-
const toolState = part.state;
|
|
7427
|
-
if (toolState.status !== "completed") continue;
|
|
7428
|
-
const content = extractCompletedToolOutput(part);
|
|
7429
|
-
if (content === void 0) continue;
|
|
7430
|
-
if (content === CLEAR_PLACEHOLDER) continue;
|
|
7431
|
-
if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
7432
|
-
if (isToolNameProtected(part.tool, protectedTools)) continue;
|
|
7433
|
-
if (protectedFilePatterns.length > 0) {
|
|
7434
|
-
const filePaths = getFilePathsFromParameters(part.tool, toolState.input);
|
|
7435
|
-
if (isFilePathProtected(filePaths, protectedFilePatterns)) continue;
|
|
7436
|
-
}
|
|
7437
|
-
const outputTokens = countTokens2(content);
|
|
7438
|
-
if (outputTokens < MIN_CLEAR_TOKENS) continue;
|
|
7439
|
-
toolState.output = CLEAR_PLACEHOLDER;
|
|
7440
|
-
freed += outputTokens - countTokens2(CLEAR_PLACEHOLDER);
|
|
7441
|
-
clearedCount++;
|
|
7442
|
-
}
|
|
7443
|
-
}
|
|
7444
|
-
const after = estimate - freed;
|
|
7445
|
-
const detail = {
|
|
7446
|
-
session: state.sessionId,
|
|
7447
|
-
estimate,
|
|
7448
|
-
safeBudget,
|
|
7449
|
-
knownWindow,
|
|
7450
|
-
reserve,
|
|
7451
|
-
clearedCount,
|
|
7452
|
-
freedTokens: Math.round(freed),
|
|
7453
|
-
afterTokens: Math.round(after)
|
|
7454
|
-
};
|
|
7455
|
-
if (clearedCount === 0) {
|
|
7456
|
-
logger.error("ACP overflow guard: over window but no clearable tool outputs", detail);
|
|
7457
|
-
return;
|
|
7458
|
-
}
|
|
7459
|
-
if (after <= safeBudget) {
|
|
7460
|
-
logger.warn("ACP overflow guard: cleared tool outputs to fit context window", detail);
|
|
7461
|
-
} else {
|
|
7462
|
-
logger.error(
|
|
7463
|
-
"ACP overflow guard: cleared tool outputs but context STILL exceeds window",
|
|
7464
|
-
detail
|
|
7465
|
-
);
|
|
7466
|
-
}
|
|
7467
|
-
}
|
|
7468
|
-
|
|
7469
|
-
// lib/messages/uncalibrated-window.ts
|
|
7470
|
-
var UNCALIBRATED_WINDOW_WARN_THRESHOLD = 3;
|
|
7471
|
-
function trackUncalibratedWindow(state, logger) {
|
|
7472
|
-
if (state.modelContextLimit === void 0) {
|
|
7473
|
-
state.uncalibratedWindowTransforms++;
|
|
7474
|
-
if (state.uncalibratedWindowTransforms >= UNCALIBRATED_WINDOW_WARN_THRESHOLD && !state.uncalibratedWindowWarned) {
|
|
7475
|
-
state.uncalibratedWindowWarned = true;
|
|
7476
|
-
logger.warn(
|
|
7477
|
-
"Model reports no context window \u2014 ACP percentage thresholds are disabled",
|
|
7478
|
-
{
|
|
7479
|
-
session: state.sessionId,
|
|
7480
|
-
provider: state.modelProviderID,
|
|
7481
|
-
model: state.modelID,
|
|
7482
|
-
transforms: state.uncalibratedWindowTransforms,
|
|
7483
|
-
hint: 'set the model\'s `limit` in opencode.json (e.g. {"context": 262144, "output": 16384}) or use absolute compress.maxContextLimit / compress.minContextLimit in acp.jsonc; the request-side overflow guard also needs a known window to fire'
|
|
7484
|
-
}
|
|
7485
|
-
);
|
|
7486
|
-
}
|
|
7487
|
-
} else {
|
|
7488
|
-
state.uncalibratedWindowTransforms = 0;
|
|
7489
|
-
}
|
|
7490
|
-
}
|
|
7491
|
-
|
|
7492
|
-
// lib/messages/sync.ts
|
|
7493
|
-
function sortBlocksByCreation(a, b) {
|
|
7494
|
-
const createdAtDiff = a.createdAt - b.createdAt;
|
|
7495
|
-
if (createdAtDiff !== 0) {
|
|
7496
|
-
return createdAtDiff;
|
|
7497
|
-
}
|
|
7498
|
-
return a.blockId - b.blockId;
|
|
7499
|
-
}
|
|
7500
|
-
var syncCompressionBlocks = (state, logger, messages) => {
|
|
7501
|
-
const messagesState = state.prune.messages;
|
|
7502
|
-
if (!messagesState?.blocksById?.size) {
|
|
7503
|
-
return;
|
|
7504
|
-
}
|
|
7505
|
-
const messageIds = new Set(messages.map((msg) => msg.info.id));
|
|
7506
|
-
const previousActiveBlockIds = new Set(
|
|
7507
|
-
Array.from(messagesState.blocksById.values()).filter((block) => block.active).map((block) => block.blockId)
|
|
7508
|
-
);
|
|
7509
|
-
messagesState.activeBlockIds.clear();
|
|
7510
|
-
messagesState.activeByAnchorMessageId.clear();
|
|
7511
|
-
const now = Date.now();
|
|
7512
|
-
const orderedBlocks = Array.from(messagesState.blocksById.values()).sort(sortBlocksByCreation);
|
|
7513
|
-
for (const block of orderedBlocks) {
|
|
7514
|
-
if (block.deactivatedByUser || block.deactivatedByUserDeep) {
|
|
7515
|
-
block.active = false;
|
|
7516
|
-
if (block.deactivatedAt === void 0) {
|
|
7517
|
-
block.deactivatedAt = now;
|
|
7518
|
-
}
|
|
7519
|
-
block.deactivatedByBlockId = void 0;
|
|
7520
|
-
continue;
|
|
7521
|
-
}
|
|
7522
|
-
for (const consumedBlockId of block.consumedBlockIds) {
|
|
7523
|
-
if (!messagesState.activeBlockIds.has(consumedBlockId)) {
|
|
7524
|
-
continue;
|
|
7525
|
-
}
|
|
7526
|
-
const consumedBlock = messagesState.blocksById.get(consumedBlockId);
|
|
7527
|
-
if (consumedBlock) {
|
|
7528
|
-
consumedBlock.active = false;
|
|
7529
|
-
consumedBlock.deactivatedAt = now;
|
|
7530
|
-
consumedBlock.deactivatedByBlockId = block.blockId;
|
|
7531
|
-
const mappedBlockId = messagesState.activeByAnchorMessageId.get(
|
|
7532
|
-
consumedBlock.anchorMessageId
|
|
7533
|
-
);
|
|
7534
|
-
if (mappedBlockId === consumedBlock.blockId) {
|
|
7535
|
-
messagesState.activeByAnchorMessageId.delete(consumedBlock.anchorMessageId);
|
|
7536
|
-
}
|
|
7537
|
-
}
|
|
7538
|
-
messagesState.activeBlockIds.delete(consumedBlockId);
|
|
7539
|
-
}
|
|
7540
|
-
block.active = true;
|
|
7541
|
-
block.deactivatedAt = void 0;
|
|
7542
|
-
block.deactivatedByBlockId = void 0;
|
|
7543
|
-
messagesState.activeBlockIds.add(block.blockId);
|
|
7544
|
-
if (messageIds.has(block.anchorMessageId)) {
|
|
7545
|
-
messagesState.activeByAnchorMessageId.set(block.anchorMessageId, block.blockId);
|
|
7546
|
-
}
|
|
7547
|
-
}
|
|
7548
|
-
for (const entry of messagesState.byMessageId.values()) {
|
|
7549
|
-
const allBlockIds = Array.isArray(entry.allBlockIds) ? [...new Set(entry.allBlockIds.filter((id) => Number.isInteger(id) && id > 0))] : [];
|
|
7550
|
-
entry.allBlockIds = allBlockIds;
|
|
7551
|
-
entry.activeBlockIds = allBlockIds.filter((id) => messagesState.activeBlockIds.has(id));
|
|
7552
|
-
}
|
|
7553
|
-
const nextActiveBlockIds = messagesState.activeBlockIds;
|
|
7554
|
-
let deactivatedCount = 0;
|
|
7555
|
-
let reactivatedCount = 0;
|
|
7556
|
-
for (const blockId of previousActiveBlockIds) {
|
|
7557
|
-
if (!nextActiveBlockIds.has(blockId)) {
|
|
7558
|
-
deactivatedCount++;
|
|
7559
|
-
}
|
|
7560
|
-
}
|
|
7561
|
-
for (const blockId of nextActiveBlockIds) {
|
|
7562
|
-
if (!previousActiveBlockIds.has(blockId)) {
|
|
7563
|
-
reactivatedCount++;
|
|
7564
|
-
}
|
|
7565
|
-
}
|
|
7566
|
-
if (deactivatedCount > 0 || reactivatedCount > 0) {
|
|
7567
|
-
logger.info("Synced compress block state", {
|
|
7568
|
-
deactivatedCount,
|
|
7569
|
-
reactivatedCount
|
|
7570
|
-
});
|
|
7571
|
-
}
|
|
7572
|
-
};
|
|
7573
|
-
|
|
7574
|
-
// lib/host-permissions.ts
|
|
7575
|
-
var findLastMatchingRule = (rules, predicate) => {
|
|
7576
|
-
for (let index = rules.length - 1; index >= 0; index -= 1) {
|
|
7577
|
-
const rule = rules[index];
|
|
7578
|
-
if (rule && predicate(rule)) {
|
|
7579
|
-
return rule;
|
|
7580
|
-
}
|
|
7581
|
-
}
|
|
7582
|
-
return void 0;
|
|
7583
|
-
};
|
|
7584
|
-
var wildcardMatch = (value, pattern) => {
|
|
7585
|
-
const normalizedValue = value.replaceAll("\\", "/");
|
|
7586
|
-
let escaped = pattern.replaceAll("\\", "/").replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
7587
|
-
if (escaped.endsWith(" .*")) {
|
|
7588
|
-
escaped = escaped.slice(0, -3) + "( .*)?";
|
|
7589
|
-
}
|
|
7590
|
-
const flags = process.platform === "win32" ? "si" : "s";
|
|
7591
|
-
return new RegExp(`^${escaped}$`, flags).test(normalizedValue);
|
|
7592
|
-
};
|
|
7593
|
-
var getPermissionRules = (permissionConfigs) => {
|
|
7594
|
-
const rules = [];
|
|
7595
|
-
for (const permissionConfig of permissionConfigs) {
|
|
7596
|
-
if (!permissionConfig) {
|
|
7597
|
-
continue;
|
|
7598
|
-
}
|
|
7599
|
-
for (const [permission, value] of Object.entries(permissionConfig)) {
|
|
7600
|
-
if (value === "ask" || value === "allow" || value === "deny") {
|
|
7601
|
-
rules.push({ permission, pattern: "*", action: value });
|
|
7602
|
-
continue;
|
|
7603
|
-
}
|
|
7604
|
-
for (const [pattern, action] of Object.entries(value)) {
|
|
7605
|
-
if (action === "ask" || action === "allow" || action === "deny") {
|
|
7606
|
-
rules.push({ permission, pattern, action });
|
|
7607
|
-
}
|
|
7608
|
-
}
|
|
7609
|
-
}
|
|
7610
|
-
}
|
|
7611
|
-
return rules;
|
|
7612
|
-
};
|
|
7613
|
-
var compressDisabledByOpencode = (...permissionConfigs) => {
|
|
7614
|
-
const match = findLastMatchingRule(
|
|
7615
|
-
getPermissionRules(permissionConfigs),
|
|
7616
|
-
(rule) => wildcardMatch("compress", rule.permission)
|
|
7617
|
-
);
|
|
7618
|
-
return match?.pattern === "*" && match.action === "deny";
|
|
7619
|
-
};
|
|
7620
|
-
var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agentName) => {
|
|
7621
|
-
if (basePermission === "deny") {
|
|
7622
|
-
return "deny";
|
|
7623
|
-
}
|
|
7624
|
-
return compressDisabledByOpencode(
|
|
7625
|
-
hostPermissions.global,
|
|
7626
|
-
agentName ? hostPermissions.agents[agentName] : void 0
|
|
7627
|
-
) ? "deny" : basePermission;
|
|
7628
|
-
};
|
|
7629
|
-
var hasExplicitToolPermission = (permissionConfig, tool6) => {
|
|
7630
|
-
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
|
|
7631
|
-
};
|
|
7632
|
-
|
|
7633
|
-
// lib/compress-permission.ts
|
|
7634
|
-
var compressPermission = (state, config) => {
|
|
7635
|
-
return state.compressPermission ?? config.compress.permission;
|
|
7636
|
-
};
|
|
7637
|
-
var syncCompressPermissionState = (state, config, hostPermissions, messages) => {
|
|
7638
|
-
const activeAgent = getLastUserMessage(messages)?.info.agent;
|
|
7639
|
-
state.compressPermission = resolveEffectiveCompressPermission(
|
|
7640
|
-
config.compress.permission,
|
|
7641
|
-
hostPermissions,
|
|
7642
|
-
activeAgent
|
|
7643
|
-
);
|
|
7644
|
-
};
|
|
7645
|
-
|
|
7646
7531
|
// lib/messages/inject/inject.ts
|
|
7647
7532
|
var ACP_SUFFIX_SEED = "acp-dynamic-guidance";
|
|
7648
7533
|
function createSuffixMessage(messages) {
|
|
@@ -8549,8 +8434,9 @@ function createDecompressTool(factoryCtx) {
|
|
|
8549
8434
|
async execute(args, toolCtx) {
|
|
8550
8435
|
const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
|
|
8551
8436
|
const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
|
|
8552
|
-
const
|
|
8553
|
-
|
|
8437
|
+
const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
8438
|
+
const contextUsageBefore = effectiveLimitBefore ? Math.round(
|
|
8439
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
|
|
8554
8440
|
) : void 0;
|
|
8555
8441
|
const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
|
|
8556
8442
|
if (!resolved.ok) {
|
|
@@ -8614,8 +8500,9 @@ function createDecompressTool(factoryCtx) {
|
|
|
8614
8500
|
0,
|
|
8615
8501
|
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
8616
8502
|
);
|
|
8617
|
-
const
|
|
8618
|
-
|
|
8503
|
+
const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
8504
|
+
const contextUsageAfter = effectiveLimitAfter ? Math.round(
|
|
8505
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
8619
8506
|
) : void 0;
|
|
8620
8507
|
await finalizeDecompressSession(ctx);
|
|
8621
8508
|
const restoredContentPreview = buildRestoredContentPreview(
|
|
@@ -9272,7 +9159,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
9272
9159
|
import { join as join3 } from "path";
|
|
9273
9160
|
import { existsSync as existsSync3 } from "fs";
|
|
9274
9161
|
import { homedir as homedir3 } from "os";
|
|
9275
|
-
var LOG_VERSION = true ? "1.14.25-pr.
|
|
9162
|
+
var LOG_VERSION = true ? "1.14.25-pr.349.71" : "dev";
|
|
9276
9163
|
var LEVEL_RANK = {
|
|
9277
9164
|
debug: 10,
|
|
9278
9165
|
info: 20,
|
|
@@ -10096,6 +9983,7 @@ var MIN_OUTPUT_TOKENS = 1e3;
|
|
|
10096
9983
|
var KEEP_PREFIX_CHARS = 2e3;
|
|
10097
9984
|
var KEEP_SUFFIX_CHARS = 2e3;
|
|
10098
9985
|
var PROTECT_RECENT_MESSAGES = 3;
|
|
9986
|
+
var OUTPUT_RESERVE_TOKENS = 16384;
|
|
10099
9987
|
function parseGcThreshold(threshold, modelContextLimit) {
|
|
10100
9988
|
if (typeof threshold === "number") return threshold;
|
|
10101
9989
|
const str = threshold ?? "100%";
|
|
@@ -10104,10 +9992,22 @@ function parseGcThreshold(threshold, modelContextLimit) {
|
|
|
10104
9992
|
return modelContextLimit;
|
|
10105
9993
|
}
|
|
10106
9994
|
function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
10107
|
-
|
|
9995
|
+
const effective = resolveEffectiveContextLimit(state, config);
|
|
9996
|
+
if (!effective) return;
|
|
10108
9997
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
10109
9998
|
if (currentTokens === 0) return;
|
|
10110
|
-
const
|
|
9999
|
+
const configuredThreshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, effective.limit);
|
|
10000
|
+
const overhead = (state.systemPromptTokens ?? 0) + OUTPUT_RESERVE_TOKENS;
|
|
10001
|
+
const threshold = Math.min(configuredThreshold, effective.limit - overhead);
|
|
10002
|
+
if (threshold <= 0) {
|
|
10003
|
+
logger.error("ACP: model context window too small to fit overhead", {
|
|
10004
|
+
session: state.sessionId,
|
|
10005
|
+
limit: effective.limit,
|
|
10006
|
+
contextLimitSource: effective.source,
|
|
10007
|
+
overhead
|
|
10008
|
+
});
|
|
10009
|
+
return;
|
|
10010
|
+
}
|
|
10111
10011
|
if (currentTokens < threshold) return;
|
|
10112
10012
|
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES;
|
|
10113
10013
|
const candidates = [];
|
|
@@ -10152,7 +10052,9 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
10152
10052
|
truncatedCount,
|
|
10153
10053
|
estimatedSavedTokens: Math.round(savedTokens),
|
|
10154
10054
|
currentTokens,
|
|
10155
|
-
threshold
|
|
10055
|
+
threshold,
|
|
10056
|
+
contextLimit: effective.limit,
|
|
10057
|
+
contextLimitSource: effective.source
|
|
10156
10058
|
});
|
|
10157
10059
|
}
|
|
10158
10060
|
}
|
|
@@ -11113,11 +11015,12 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11113
11015
|
mergedCount: 0,
|
|
11114
11016
|
savedTokens: 0
|
|
11115
11017
|
};
|
|
11116
|
-
|
|
11018
|
+
const effective = resolveEffectiveContextLimit(state, config);
|
|
11019
|
+
if (!effective) {
|
|
11117
11020
|
return noop;
|
|
11118
11021
|
}
|
|
11119
11022
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
11120
|
-
if (currentTokens <
|
|
11023
|
+
if (currentTokens < effective.limit) {
|
|
11121
11024
|
return noop;
|
|
11122
11025
|
}
|
|
11123
11026
|
const maxMergedLength = config.gc.maxOldGenSummaryLength;
|
|
@@ -11134,7 +11037,8 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11134
11037
|
mergedCount: result.mergedCount,
|
|
11135
11038
|
savedTokens: result.savedTokens,
|
|
11136
11039
|
currentTokens,
|
|
11137
|
-
contextLimit:
|
|
11040
|
+
contextLimit: effective.limit,
|
|
11041
|
+
contextLimitSource: effective.source
|
|
11138
11042
|
});
|
|
11139
11043
|
return {
|
|
11140
11044
|
tier: 3,
|
|
@@ -11168,11 +11072,6 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11168
11072
|
input.model?.limit?.context
|
|
11169
11073
|
);
|
|
11170
11074
|
const state = input.sessionID ? registry4.get(input.sessionID) : void 0;
|
|
11171
|
-
if (state && input.model?.limit?.context) {
|
|
11172
|
-
state.modelContextLimit = input.model.limit.context;
|
|
11173
|
-
state.modelProviderID = input.model?.providerID;
|
|
11174
|
-
state.modelID = input.model?.id;
|
|
11175
|
-
}
|
|
11176
11075
|
if (!state || state.isSubAgent && !config.allowSubAgents) {
|
|
11177
11076
|
return;
|
|
11178
11077
|
}
|
|
@@ -11181,6 +11080,19 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11181
11080
|
logger.info("Skipping DCP system prompt injection for internal agent");
|
|
11182
11081
|
return;
|
|
11183
11082
|
}
|
|
11083
|
+
if (input.model?.limit?.context) {
|
|
11084
|
+
const limit = input.model.limit.context;
|
|
11085
|
+
const providerID = input.model?.providerID;
|
|
11086
|
+
const modelID = input.model?.id;
|
|
11087
|
+
const changed = state.modelContextLimit !== limit || state.modelProviderID !== providerID || state.modelID !== modelID;
|
|
11088
|
+
state.modelContextLimit = limit;
|
|
11089
|
+
state.modelProviderID = providerID;
|
|
11090
|
+
state.modelID = modelID;
|
|
11091
|
+
if (changed) {
|
|
11092
|
+
saveSessionState(state, logger).catch(() => {
|
|
11093
|
+
});
|
|
11094
|
+
}
|
|
11095
|
+
}
|
|
11184
11096
|
const effectivePermission = compressPermission(state, config);
|
|
11185
11097
|
if (effectivePermission === "deny") {
|
|
11186
11098
|
return;
|
|
@@ -11225,10 +11137,17 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11225
11137
|
config
|
|
11226
11138
|
);
|
|
11227
11139
|
const requestModel = lastUserMessage.info.model;
|
|
11228
|
-
|
|
11140
|
+
let requestModelLimit = registry4.resolveModelLimit(
|
|
11229
11141
|
requestModel?.providerID,
|
|
11230
11142
|
requestModel?.modelID
|
|
11231
11143
|
);
|
|
11144
|
+
if (requestModelLimit === void 0 && requestModel?.providerID && requestModel?.modelID) {
|
|
11145
|
+
requestModelLimit = await registry4.hydrateAndResolve(
|
|
11146
|
+
client,
|
|
11147
|
+
requestModel.providerID,
|
|
11148
|
+
requestModel.modelID
|
|
11149
|
+
);
|
|
11150
|
+
}
|
|
11232
11151
|
const prevModelID = state.modelID;
|
|
11233
11152
|
if (requestModelLimit !== void 0) {
|
|
11234
11153
|
state.modelContextLimit = requestModelLimit;
|
|
@@ -11258,7 +11177,6 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11258
11177
|
});
|
|
11259
11178
|
}
|
|
11260
11179
|
await updatePerTurnState(state, logger, messages);
|
|
11261
|
-
trackUncalibratedWindow(state, logger);
|
|
11262
11180
|
}
|
|
11263
11181
|
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
11264
11182
|
if (state.isSubAgent && !config.allowSubAgents) {
|
|
@@ -11266,10 +11184,11 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11266
11184
|
}
|
|
11267
11185
|
stripHallucinations(output.messages);
|
|
11268
11186
|
ensureBuiltinFiltersRegistered();
|
|
11187
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
11269
11188
|
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
11270
11189
|
sessionId: state.sessionId ?? "",
|
|
11271
11190
|
isSubAgent: state.isSubAgent,
|
|
11272
|
-
modelContextLimit:
|
|
11191
|
+
modelContextLimit: effectiveLimit?.limit
|
|
11273
11192
|
});
|
|
11274
11193
|
cacheSystemPromptTokens(state, output.messages);
|
|
11275
11194
|
assignMessageRefs(state, output.messages);
|
|
@@ -11289,7 +11208,6 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11289
11208
|
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
11290
11209
|
prune(state, logger, config, output.messages);
|
|
11291
11210
|
truncateLargeToolOutputs(state, config, logger, output.messages);
|
|
11292
|
-
pruneToFit(state, config, logger, output.messages);
|
|
11293
11211
|
hideConsumedCompressCalls(state, output.messages);
|
|
11294
11212
|
assignMessageRefs(state, output.messages);
|
|
11295
11213
|
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
@@ -11321,14 +11239,31 @@ ${text}`);
|
|
|
11321
11239
|
stripStaleMetadata(output.messages);
|
|
11322
11240
|
dropEmptyMessages(output.messages);
|
|
11323
11241
|
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
11242
|
+
if (postTokens !== void 0 && effectiveLimit) {
|
|
11243
|
+
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
11244
|
+
if (postTokens > budget) {
|
|
11245
|
+
logger.error(
|
|
11246
|
+
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
11247
|
+
{
|
|
11248
|
+
session: state.sessionId,
|
|
11249
|
+
postTokens,
|
|
11250
|
+
budget,
|
|
11251
|
+
contextLimit: effectiveLimit.limit,
|
|
11252
|
+
contextLimitSource: effectiveLimit.source,
|
|
11253
|
+
hint: "request will likely be rejected; run /compact or start a new session"
|
|
11254
|
+
}
|
|
11255
|
+
);
|
|
11256
|
+
}
|
|
11257
|
+
}
|
|
11324
11258
|
logger.info("Chat transform complete", {
|
|
11325
11259
|
session: state.sessionId,
|
|
11326
11260
|
model: state.modelID,
|
|
11327
11261
|
messages: output.messages.length,
|
|
11328
11262
|
prePruneTokens,
|
|
11329
11263
|
postTokens,
|
|
11330
|
-
contextLimit:
|
|
11331
|
-
|
|
11264
|
+
contextLimit: effectiveLimit?.limit,
|
|
11265
|
+
contextLimitSource: effectiveLimit?.source,
|
|
11266
|
+
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
11332
11267
|
nudged: state.nudges.shouldInjectThisTurn
|
|
11333
11268
|
});
|
|
11334
11269
|
if (state.sessionId) {
|
|
@@ -11715,7 +11650,7 @@ var server = (async (ctx) => {
|
|
|
11715
11650
|
}
|
|
11716
11651
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
11717
11652
|
logger.info("ACP plugin initialized", {
|
|
11718
|
-
version: true ? "1.14.25-pr.
|
|
11653
|
+
version: true ? "1.14.25-pr.349.71" : "dev",
|
|
11719
11654
|
workspace: ctx.directory,
|
|
11720
11655
|
logLevel: logger.level,
|
|
11721
11656
|
debug: config.debug,
|