opencode-acp 1.14.25-pr.348.74 → 1.14.25-pr.349.75
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 +342 -431
- 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 +9 -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 -20
- 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
|
+
// The in-flight promise (not a boolean) lets concurrent callers await the
|
|
4247
|
+
// same hydration instead of skipping it.
|
|
4248
|
+
lazyHydration;
|
|
4249
|
+
async hydrateAndResolve(client, providerId, modelId) {
|
|
4250
|
+
const existing = this.catalog.resolve(providerId, modelId);
|
|
4251
|
+
if (existing !== void 0) {
|
|
4252
|
+
return existing;
|
|
4253
|
+
}
|
|
4254
|
+
this.lazyHydration ??= this.catalog.hydrateFromClient(client);
|
|
4255
|
+
await this.lazyHydration;
|
|
4256
|
+
return this.catalog.resolve(providerId, modelId);
|
|
4257
|
+
}
|
|
4240
4258
|
get(sessionId) {
|
|
4241
4259
|
return this.states.get(sessionId);
|
|
4242
4260
|
}
|
|
@@ -4328,10 +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,
|
|
4334
|
-
overflowGuardStuckLogged: false
|
|
4349
|
+
qualityGateRetryPending: false
|
|
4335
4350
|
};
|
|
4336
4351
|
}
|
|
4337
4352
|
function resetSessionState(state) {
|
|
@@ -4373,9 +4388,6 @@ function resetSessionState(state) {
|
|
|
4373
4388
|
state.modelID = void 0;
|
|
4374
4389
|
state.systemPromptTokens = void 0;
|
|
4375
4390
|
state.qualityGateRetryPending = false;
|
|
4376
|
-
state.uncalibratedWindowTransforms = 0;
|
|
4377
|
-
state.uncalibratedWindowWarned = false;
|
|
4378
|
-
state.overflowGuardStuckLogged = false;
|
|
4379
4391
|
}
|
|
4380
4392
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, config) {
|
|
4381
4393
|
if (state.sessionId === sessionId) {
|
|
@@ -6422,66 +6434,159 @@ var filterCompressedRanges = (state, messages) => {
|
|
|
6422
6434
|
messages.push(...result);
|
|
6423
6435
|
};
|
|
6424
6436
|
|
|
6425
|
-
// lib/
|
|
6426
|
-
function
|
|
6427
|
-
const
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
const
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
for (let i = 0; i < 3 && i * chunkSize < blocksWithRef.length; i++) {
|
|
6452
|
-
const chunk = blocksWithRef.slice(i * chunkSize, (i + 1) * chunkSize);
|
|
6453
|
-
if (chunk.length < 2) continue;
|
|
6454
|
-
const startRef = chunk[0].ref;
|
|
6455
|
-
const endRef = chunk[chunk.length - 1].ref;
|
|
6456
|
-
const chunkTokens = chunk.reduce((s, x) => s + (x.block.summaryTokens ?? 0), 0);
|
|
6457
|
-
const chunkK = Math.max(1, Math.round(chunkTokens / 1e3));
|
|
6458
|
-
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;
|
|
6459
6463
|
}
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
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
|
+
}
|
|
6464
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);
|
|
6465
6491
|
}
|
|
6466
6492
|
}
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
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));
|
|
6472
6497
|
}
|
|
6473
|
-
const
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
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
|
+
}
|
|
6477
6505
|
}
|
|
6478
|
-
const
|
|
6479
|
-
|
|
6480
|
-
|
|
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
|
+
};
|
|
6481
6518
|
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
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
|
+
};
|
|
6485
6590
|
|
|
6486
6591
|
// lib/messages/utils.ts
|
|
6487
6592
|
import { createHash } from "crypto";
|
|
@@ -6657,6 +6762,67 @@ var dropEmptyMessages = (messages) => {
|
|
|
6657
6762
|
return removed;
|
|
6658
6763
|
};
|
|
6659
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
|
+
|
|
6660
6826
|
// node_modules/context-compress-algorithms/dist/chunk-BZYW3CH5.js
|
|
6661
6827
|
var NUDGE_GROWTH_FLOOR = 6e3;
|
|
6662
6828
|
var NUDGE_GROWTH_CAP = 5e4;
|
|
@@ -6759,6 +6925,7 @@ function getModelInfo(messages) {
|
|
|
6759
6925
|
};
|
|
6760
6926
|
}
|
|
6761
6927
|
function resolveContextTokenLimit(config, state, providerId, modelId, threshold) {
|
|
6928
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6762
6929
|
const parseLimitValue = (limit) => {
|
|
6763
6930
|
if (limit === void 0) {
|
|
6764
6931
|
return void 0;
|
|
@@ -6766,7 +6933,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6766
6933
|
if (typeof limit === "number") {
|
|
6767
6934
|
return limit;
|
|
6768
6935
|
}
|
|
6769
|
-
if (!limit.endsWith("%") ||
|
|
6936
|
+
if (!limit.endsWith("%") || effectiveLimit === void 0) {
|
|
6770
6937
|
return void 0;
|
|
6771
6938
|
}
|
|
6772
6939
|
const parsedPercent = parseFloat(limit.slice(0, -1));
|
|
@@ -6775,7 +6942,7 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
6775
6942
|
}
|
|
6776
6943
|
const roundedPercent = Math.round(parsedPercent);
|
|
6777
6944
|
const clampedPercent = Math.max(0, Math.min(100, roundedPercent));
|
|
6778
|
-
return Math.round(clampedPercent / 100 *
|
|
6945
|
+
return Math.round(clampedPercent / 100 * effectiveLimit.limit);
|
|
6779
6946
|
};
|
|
6780
6947
|
const modelLimits = threshold === "max" ? config.compress.modelMaxLimits : config.compress.modelMinLimits;
|
|
6781
6948
|
if (modelLimits && providerId !== void 0 && modelId !== void 0) {
|
|
@@ -6820,11 +6987,12 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
|
6820
6987
|
if (!overMaxLimit) break;
|
|
6821
6988
|
}
|
|
6822
6989
|
}
|
|
6990
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
6823
6991
|
return {
|
|
6824
6992
|
overMaxLimit,
|
|
6825
6993
|
overMinLimit,
|
|
6826
6994
|
currentTokens,
|
|
6827
|
-
modelContextLimit:
|
|
6995
|
+
modelContextLimit: effectiveLimit?.limit
|
|
6828
6996
|
};
|
|
6829
6997
|
}
|
|
6830
6998
|
ensureBuiltinTriggerPolicyRegistered();
|
|
@@ -7360,322 +7528,6 @@ function excludeProtectedRanges(ranges, protectedRefs) {
|
|
|
7360
7528
|
);
|
|
7361
7529
|
}
|
|
7362
7530
|
|
|
7363
|
-
// lib/messages/prune-to-fit.ts
|
|
7364
|
-
var WIRE_SAFETY_MARGIN = 8192;
|
|
7365
|
-
var CLEAR_PLACEHOLDER = "[cleared by ACP overflow guard \u2014 re-run tool if needed]";
|
|
7366
|
-
var MIN_CLEAR_TOKENS = 500;
|
|
7367
|
-
function resolveKnownWindow(config, state, providerId, modelId) {
|
|
7368
|
-
if (state.modelContextLimit !== void 0) {
|
|
7369
|
-
return state.modelContextLimit;
|
|
7370
|
-
}
|
|
7371
|
-
const maxLimits = config.compress.modelMaxLimits;
|
|
7372
|
-
if (maxLimits && providerId !== void 0 && modelId !== void 0) {
|
|
7373
|
-
const perModel = maxLimits[`${providerId}/${modelId}`];
|
|
7374
|
-
if (typeof perModel === "number") return perModel;
|
|
7375
|
-
}
|
|
7376
|
-
const global = config.compress.maxContextLimit;
|
|
7377
|
-
if (typeof global === "number") return global;
|
|
7378
|
-
return void 0;
|
|
7379
|
-
}
|
|
7380
|
-
function estimateWireTokens(state, messages) {
|
|
7381
|
-
const base = getCurrentTokenUsage(state, messages);
|
|
7382
|
-
if (base > 0) {
|
|
7383
|
-
let lastAsstIdx = -1;
|
|
7384
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
7385
|
-
if (messages[i].info.role === "assistant") {
|
|
7386
|
-
lastAsstIdx = i;
|
|
7387
|
-
break;
|
|
7388
|
-
}
|
|
7389
|
-
}
|
|
7390
|
-
if (lastAsstIdx >= 0 && hasCompletedCompressPart(messages[lastAsstIdx])) {
|
|
7391
|
-
return preciseWireTokens(state, messages);
|
|
7392
|
-
}
|
|
7393
|
-
let trailing = 0;
|
|
7394
|
-
if (lastAsstIdx >= 0) {
|
|
7395
|
-
const parts = Array.isArray(messages[lastAsstIdx].parts) ? messages[lastAsstIdx].parts : [];
|
|
7396
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
7397
|
-
const p = parts[i];
|
|
7398
|
-
if (p?.type !== "tool") break;
|
|
7399
|
-
if (p.state.status !== "completed") break;
|
|
7400
|
-
trailing += countTokens2(extractCompletedToolOutput(p) ?? "");
|
|
7401
|
-
}
|
|
7402
|
-
}
|
|
7403
|
-
let afterLastAsst = 0;
|
|
7404
|
-
for (let i = lastAsstIdx + 1; i < messages.length; i++) {
|
|
7405
|
-
afterLastAsst += countAllMessageTokens(messages[i]);
|
|
7406
|
-
}
|
|
7407
|
-
return base + trailing + afterLastAsst + WIRE_SAFETY_MARGIN;
|
|
7408
|
-
}
|
|
7409
|
-
return preciseWireTokens(state, messages);
|
|
7410
|
-
}
|
|
7411
|
-
function preciseWireTokens(state, messages) {
|
|
7412
|
-
let total = state.systemPromptTokens ?? 0;
|
|
7413
|
-
for (const msg of messages) {
|
|
7414
|
-
total += countAllMessageTokens(msg);
|
|
7415
|
-
}
|
|
7416
|
-
return total + WIRE_SAFETY_MARGIN;
|
|
7417
|
-
}
|
|
7418
|
-
function hasCompletedCompressPart(message) {
|
|
7419
|
-
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
7420
|
-
for (const p of parts) {
|
|
7421
|
-
if (p?.type === "tool" && p.tool === "compress" && p.state.status === "completed") {
|
|
7422
|
-
return true;
|
|
7423
|
-
}
|
|
7424
|
-
}
|
|
7425
|
-
return false;
|
|
7426
|
-
}
|
|
7427
|
-
function pruneToFit(state, config, logger, messages) {
|
|
7428
|
-
if (!config.compress.overflowGuard) return;
|
|
7429
|
-
const knownWindow = resolveKnownWindow(config, state, state.modelProviderID, state.modelID);
|
|
7430
|
-
if (knownWindow === void 0) return;
|
|
7431
|
-
const reserve = config.compress.overflowGuardReserve ?? 32768;
|
|
7432
|
-
const safeBudget = knownWindow - reserve;
|
|
7433
|
-
if (safeBudget <= 0) return;
|
|
7434
|
-
const estimate = estimateWireTokens(state, messages);
|
|
7435
|
-
if (estimate <= safeBudget) {
|
|
7436
|
-
state.overflowGuardStuckLogged = false;
|
|
7437
|
-
return;
|
|
7438
|
-
}
|
|
7439
|
-
const protectedRefs = computeProtectedRefs(messages, state, config.compress);
|
|
7440
|
-
const lastNonIgnored = findLastNonIgnoredMessage(messages);
|
|
7441
|
-
const lastMsgId = lastNonIgnored?.message.info.id;
|
|
7442
|
-
const protectedTools = config.compress.protectedTools;
|
|
7443
|
-
const protectedFilePatterns = config.protectedFilePatterns;
|
|
7444
|
-
let freed = 0;
|
|
7445
|
-
let clearedCount = 0;
|
|
7446
|
-
for (const msg of messages) {
|
|
7447
|
-
if (estimate - freed <= safeBudget) break;
|
|
7448
|
-
if (msg.info.id === lastMsgId) continue;
|
|
7449
|
-
if (msg.info.role === "user") continue;
|
|
7450
|
-
const ref = state.messageIds.byRawId.get(msg.info.id);
|
|
7451
|
-
if (ref && protectedRefs.has(ref)) continue;
|
|
7452
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
7453
|
-
for (const part of parts) {
|
|
7454
|
-
if (estimate - freed <= safeBudget) break;
|
|
7455
|
-
if (part?.type !== "tool") continue;
|
|
7456
|
-
const toolState = part.state;
|
|
7457
|
-
if (toolState.status !== "completed") continue;
|
|
7458
|
-
const content = extractCompletedToolOutput(part);
|
|
7459
|
-
if (content === void 0) continue;
|
|
7460
|
-
if (content === CLEAR_PLACEHOLDER) continue;
|
|
7461
|
-
if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
|
|
7462
|
-
if (isToolNameProtected(part.tool, protectedTools)) continue;
|
|
7463
|
-
if (protectedFilePatterns.length > 0) {
|
|
7464
|
-
const filePaths = getFilePathsFromParameters(part.tool, toolState.input);
|
|
7465
|
-
if (isFilePathProtected(filePaths, protectedFilePatterns)) continue;
|
|
7466
|
-
}
|
|
7467
|
-
const outputTokens = countTokens2(content);
|
|
7468
|
-
if (outputTokens < MIN_CLEAR_TOKENS) continue;
|
|
7469
|
-
toolState.output = CLEAR_PLACEHOLDER;
|
|
7470
|
-
freed += outputTokens - countTokens2(CLEAR_PLACEHOLDER);
|
|
7471
|
-
clearedCount++;
|
|
7472
|
-
}
|
|
7473
|
-
}
|
|
7474
|
-
const after = estimate - freed;
|
|
7475
|
-
const detail = {
|
|
7476
|
-
session: state.sessionId,
|
|
7477
|
-
estimate,
|
|
7478
|
-
safeBudget,
|
|
7479
|
-
knownWindow,
|
|
7480
|
-
reserve,
|
|
7481
|
-
clearedCount,
|
|
7482
|
-
freedTokens: Math.round(freed),
|
|
7483
|
-
afterTokens: Math.round(after)
|
|
7484
|
-
};
|
|
7485
|
-
if (clearedCount === 0) {
|
|
7486
|
-
if (!state.overflowGuardStuckLogged) {
|
|
7487
|
-
state.overflowGuardStuckLogged = true;
|
|
7488
|
-
logger.error("ACP overflow guard: over window but no clearable tool outputs", detail);
|
|
7489
|
-
}
|
|
7490
|
-
return;
|
|
7491
|
-
}
|
|
7492
|
-
if (after <= safeBudget) {
|
|
7493
|
-
logger.warn("ACP overflow guard: cleared tool outputs to fit context window", detail);
|
|
7494
|
-
} else {
|
|
7495
|
-
logger.error(
|
|
7496
|
-
"ACP overflow guard: cleared tool outputs but context STILL exceeds window",
|
|
7497
|
-
detail
|
|
7498
|
-
);
|
|
7499
|
-
}
|
|
7500
|
-
}
|
|
7501
|
-
|
|
7502
|
-
// lib/messages/uncalibrated-window.ts
|
|
7503
|
-
var UNCALIBRATED_WINDOW_WARN_THRESHOLD = 3;
|
|
7504
|
-
function trackUncalibratedWindow(state, logger) {
|
|
7505
|
-
if (state.modelContextLimit === void 0) {
|
|
7506
|
-
state.uncalibratedWindowTransforms++;
|
|
7507
|
-
if (state.uncalibratedWindowTransforms >= UNCALIBRATED_WINDOW_WARN_THRESHOLD && !state.uncalibratedWindowWarned) {
|
|
7508
|
-
state.uncalibratedWindowWarned = true;
|
|
7509
|
-
logger.warn(
|
|
7510
|
-
"Model reports no context window \u2014 ACP percentage thresholds are disabled",
|
|
7511
|
-
{
|
|
7512
|
-
session: state.sessionId,
|
|
7513
|
-
provider: state.modelProviderID,
|
|
7514
|
-
model: state.modelID,
|
|
7515
|
-
transforms: state.uncalibratedWindowTransforms,
|
|
7516
|
-
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'
|
|
7517
|
-
}
|
|
7518
|
-
);
|
|
7519
|
-
}
|
|
7520
|
-
} else {
|
|
7521
|
-
state.uncalibratedWindowTransforms = 0;
|
|
7522
|
-
}
|
|
7523
|
-
}
|
|
7524
|
-
|
|
7525
|
-
// lib/messages/sync.ts
|
|
7526
|
-
function sortBlocksByCreation(a, b) {
|
|
7527
|
-
const createdAtDiff = a.createdAt - b.createdAt;
|
|
7528
|
-
if (createdAtDiff !== 0) {
|
|
7529
|
-
return createdAtDiff;
|
|
7530
|
-
}
|
|
7531
|
-
return a.blockId - b.blockId;
|
|
7532
|
-
}
|
|
7533
|
-
var syncCompressionBlocks = (state, logger, messages) => {
|
|
7534
|
-
const messagesState = state.prune.messages;
|
|
7535
|
-
if (!messagesState?.blocksById?.size) {
|
|
7536
|
-
return;
|
|
7537
|
-
}
|
|
7538
|
-
const messageIds = new Set(messages.map((msg) => msg.info.id));
|
|
7539
|
-
const previousActiveBlockIds = new Set(
|
|
7540
|
-
Array.from(messagesState.blocksById.values()).filter((block) => block.active).map((block) => block.blockId)
|
|
7541
|
-
);
|
|
7542
|
-
messagesState.activeBlockIds.clear();
|
|
7543
|
-
messagesState.activeByAnchorMessageId.clear();
|
|
7544
|
-
const now = Date.now();
|
|
7545
|
-
const orderedBlocks = Array.from(messagesState.blocksById.values()).sort(sortBlocksByCreation);
|
|
7546
|
-
for (const block of orderedBlocks) {
|
|
7547
|
-
if (block.deactivatedByUser || block.deactivatedByUserDeep) {
|
|
7548
|
-
block.active = false;
|
|
7549
|
-
if (block.deactivatedAt === void 0) {
|
|
7550
|
-
block.deactivatedAt = now;
|
|
7551
|
-
}
|
|
7552
|
-
block.deactivatedByBlockId = void 0;
|
|
7553
|
-
continue;
|
|
7554
|
-
}
|
|
7555
|
-
for (const consumedBlockId of block.consumedBlockIds) {
|
|
7556
|
-
if (!messagesState.activeBlockIds.has(consumedBlockId)) {
|
|
7557
|
-
continue;
|
|
7558
|
-
}
|
|
7559
|
-
const consumedBlock = messagesState.blocksById.get(consumedBlockId);
|
|
7560
|
-
if (consumedBlock) {
|
|
7561
|
-
consumedBlock.active = false;
|
|
7562
|
-
consumedBlock.deactivatedAt = now;
|
|
7563
|
-
consumedBlock.deactivatedByBlockId = block.blockId;
|
|
7564
|
-
const mappedBlockId = messagesState.activeByAnchorMessageId.get(
|
|
7565
|
-
consumedBlock.anchorMessageId
|
|
7566
|
-
);
|
|
7567
|
-
if (mappedBlockId === consumedBlock.blockId) {
|
|
7568
|
-
messagesState.activeByAnchorMessageId.delete(consumedBlock.anchorMessageId);
|
|
7569
|
-
}
|
|
7570
|
-
}
|
|
7571
|
-
messagesState.activeBlockIds.delete(consumedBlockId);
|
|
7572
|
-
}
|
|
7573
|
-
block.active = true;
|
|
7574
|
-
block.deactivatedAt = void 0;
|
|
7575
|
-
block.deactivatedByBlockId = void 0;
|
|
7576
|
-
messagesState.activeBlockIds.add(block.blockId);
|
|
7577
|
-
if (messageIds.has(block.anchorMessageId)) {
|
|
7578
|
-
messagesState.activeByAnchorMessageId.set(block.anchorMessageId, block.blockId);
|
|
7579
|
-
}
|
|
7580
|
-
}
|
|
7581
|
-
for (const entry of messagesState.byMessageId.values()) {
|
|
7582
|
-
const allBlockIds = Array.isArray(entry.allBlockIds) ? [...new Set(entry.allBlockIds.filter((id) => Number.isInteger(id) && id > 0))] : [];
|
|
7583
|
-
entry.allBlockIds = allBlockIds;
|
|
7584
|
-
entry.activeBlockIds = allBlockIds.filter((id) => messagesState.activeBlockIds.has(id));
|
|
7585
|
-
}
|
|
7586
|
-
const nextActiveBlockIds = messagesState.activeBlockIds;
|
|
7587
|
-
let deactivatedCount = 0;
|
|
7588
|
-
let reactivatedCount = 0;
|
|
7589
|
-
for (const blockId of previousActiveBlockIds) {
|
|
7590
|
-
if (!nextActiveBlockIds.has(blockId)) {
|
|
7591
|
-
deactivatedCount++;
|
|
7592
|
-
}
|
|
7593
|
-
}
|
|
7594
|
-
for (const blockId of nextActiveBlockIds) {
|
|
7595
|
-
if (!previousActiveBlockIds.has(blockId)) {
|
|
7596
|
-
reactivatedCount++;
|
|
7597
|
-
}
|
|
7598
|
-
}
|
|
7599
|
-
if (deactivatedCount > 0 || reactivatedCount > 0) {
|
|
7600
|
-
logger.info("Synced compress block state", {
|
|
7601
|
-
deactivatedCount,
|
|
7602
|
-
reactivatedCount
|
|
7603
|
-
});
|
|
7604
|
-
}
|
|
7605
|
-
};
|
|
7606
|
-
|
|
7607
|
-
// lib/host-permissions.ts
|
|
7608
|
-
var findLastMatchingRule = (rules, predicate) => {
|
|
7609
|
-
for (let index = rules.length - 1; index >= 0; index -= 1) {
|
|
7610
|
-
const rule = rules[index];
|
|
7611
|
-
if (rule && predicate(rule)) {
|
|
7612
|
-
return rule;
|
|
7613
|
-
}
|
|
7614
|
-
}
|
|
7615
|
-
return void 0;
|
|
7616
|
-
};
|
|
7617
|
-
var wildcardMatch = (value, pattern) => {
|
|
7618
|
-
const normalizedValue = value.replaceAll("\\", "/");
|
|
7619
|
-
let escaped = pattern.replaceAll("\\", "/").replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
7620
|
-
if (escaped.endsWith(" .*")) {
|
|
7621
|
-
escaped = escaped.slice(0, -3) + "( .*)?";
|
|
7622
|
-
}
|
|
7623
|
-
const flags = process.platform === "win32" ? "si" : "s";
|
|
7624
|
-
return new RegExp(`^${escaped}$`, flags).test(normalizedValue);
|
|
7625
|
-
};
|
|
7626
|
-
var getPermissionRules = (permissionConfigs) => {
|
|
7627
|
-
const rules = [];
|
|
7628
|
-
for (const permissionConfig of permissionConfigs) {
|
|
7629
|
-
if (!permissionConfig) {
|
|
7630
|
-
continue;
|
|
7631
|
-
}
|
|
7632
|
-
for (const [permission, value] of Object.entries(permissionConfig)) {
|
|
7633
|
-
if (value === "ask" || value === "allow" || value === "deny") {
|
|
7634
|
-
rules.push({ permission, pattern: "*", action: value });
|
|
7635
|
-
continue;
|
|
7636
|
-
}
|
|
7637
|
-
for (const [pattern, action] of Object.entries(value)) {
|
|
7638
|
-
if (action === "ask" || action === "allow" || action === "deny") {
|
|
7639
|
-
rules.push({ permission, pattern, action });
|
|
7640
|
-
}
|
|
7641
|
-
}
|
|
7642
|
-
}
|
|
7643
|
-
}
|
|
7644
|
-
return rules;
|
|
7645
|
-
};
|
|
7646
|
-
var compressDisabledByOpencode = (...permissionConfigs) => {
|
|
7647
|
-
const match = findLastMatchingRule(
|
|
7648
|
-
getPermissionRules(permissionConfigs),
|
|
7649
|
-
(rule) => wildcardMatch("compress", rule.permission)
|
|
7650
|
-
);
|
|
7651
|
-
return match?.pattern === "*" && match.action === "deny";
|
|
7652
|
-
};
|
|
7653
|
-
var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agentName) => {
|
|
7654
|
-
if (basePermission === "deny") {
|
|
7655
|
-
return "deny";
|
|
7656
|
-
}
|
|
7657
|
-
return compressDisabledByOpencode(
|
|
7658
|
-
hostPermissions.global,
|
|
7659
|
-
agentName ? hostPermissions.agents[agentName] : void 0
|
|
7660
|
-
) ? "deny" : basePermission;
|
|
7661
|
-
};
|
|
7662
|
-
var hasExplicitToolPermission = (permissionConfig, tool6) => {
|
|
7663
|
-
return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
|
|
7664
|
-
};
|
|
7665
|
-
|
|
7666
|
-
// lib/compress-permission.ts
|
|
7667
|
-
var compressPermission = (state, config) => {
|
|
7668
|
-
return state.compressPermission ?? config.compress.permission;
|
|
7669
|
-
};
|
|
7670
|
-
var syncCompressPermissionState = (state, config, hostPermissions, messages) => {
|
|
7671
|
-
const activeAgent = getLastUserMessage(messages)?.info.agent;
|
|
7672
|
-
state.compressPermission = resolveEffectiveCompressPermission(
|
|
7673
|
-
config.compress.permission,
|
|
7674
|
-
hostPermissions,
|
|
7675
|
-
activeAgent
|
|
7676
|
-
);
|
|
7677
|
-
};
|
|
7678
|
-
|
|
7679
7531
|
// lib/messages/inject/inject.ts
|
|
7680
7532
|
var ACP_SUFFIX_SEED = "acp-dynamic-guidance";
|
|
7681
7533
|
function createSuffixMessage(messages) {
|
|
@@ -8582,8 +8434,9 @@ function createDecompressTool(factoryCtx) {
|
|
|
8582
8434
|
async execute(args, toolCtx) {
|
|
8583
8435
|
const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
|
|
8584
8436
|
const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
|
|
8585
|
-
const
|
|
8586
|
-
|
|
8437
|
+
const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
8438
|
+
const contextUsageBefore = effectiveLimitBefore ? Math.round(
|
|
8439
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
|
|
8587
8440
|
) : void 0;
|
|
8588
8441
|
const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
|
|
8589
8442
|
if (!resolved.ok) {
|
|
@@ -8647,8 +8500,9 @@ function createDecompressTool(factoryCtx) {
|
|
|
8647
8500
|
0,
|
|
8648
8501
|
ctx.state.stats.totalPruneTokens - restoredTokens
|
|
8649
8502
|
);
|
|
8650
|
-
const
|
|
8651
|
-
|
|
8503
|
+
const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
|
|
8504
|
+
const contextUsageAfter = effectiveLimitAfter ? Math.round(
|
|
8505
|
+
getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
|
|
8652
8506
|
) : void 0;
|
|
8653
8507
|
await finalizeDecompressSession(ctx);
|
|
8654
8508
|
const restoredContentPreview = buildRestoredContentPreview(
|
|
@@ -9305,7 +9159,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
9305
9159
|
import { join as join3 } from "path";
|
|
9306
9160
|
import { existsSync as existsSync3 } from "fs";
|
|
9307
9161
|
import { homedir as homedir3 } from "os";
|
|
9308
|
-
var LOG_VERSION = true ? "1.14.25-pr.
|
|
9162
|
+
var LOG_VERSION = true ? "1.14.25-pr.349.75" : "dev";
|
|
9309
9163
|
var LEVEL_RANK = {
|
|
9310
9164
|
debug: 10,
|
|
9311
9165
|
info: 20,
|
|
@@ -10129,6 +9983,8 @@ var MIN_OUTPUT_TOKENS = 1e3;
|
|
|
10129
9983
|
var KEEP_PREFIX_CHARS = 2e3;
|
|
10130
9984
|
var KEEP_SUFFIX_CHARS = 2e3;
|
|
10131
9985
|
var PROTECT_RECENT_MESSAGES = 3;
|
|
9986
|
+
var OUTPUT_RESERVE_TOKENS = 16384;
|
|
9987
|
+
var overheadErrorLogged = /* @__PURE__ */ new Set();
|
|
10132
9988
|
function parseGcThreshold(threshold, modelContextLimit) {
|
|
10133
9989
|
if (typeof threshold === "number") return threshold;
|
|
10134
9990
|
const str = threshold ?? "100%";
|
|
@@ -10137,10 +9993,26 @@ function parseGcThreshold(threshold, modelContextLimit) {
|
|
|
10137
9993
|
return modelContextLimit;
|
|
10138
9994
|
}
|
|
10139
9995
|
function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
10140
|
-
|
|
9996
|
+
const effective = resolveEffectiveContextLimit(state, config);
|
|
9997
|
+
if (!effective) return;
|
|
10141
9998
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
10142
9999
|
if (currentTokens === 0) return;
|
|
10143
|
-
const
|
|
10000
|
+
const configuredThreshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, effective.limit);
|
|
10001
|
+
const overhead = (state.systemPromptTokens ?? 0) + OUTPUT_RESERVE_TOKENS;
|
|
10002
|
+
const threshold = Math.min(configuredThreshold, effective.limit - overhead);
|
|
10003
|
+
if (threshold <= 0) {
|
|
10004
|
+
const sessionKey = state.sessionId ?? "unknown";
|
|
10005
|
+
if (!overheadErrorLogged.has(sessionKey)) {
|
|
10006
|
+
overheadErrorLogged.add(sessionKey);
|
|
10007
|
+
logger.error("ACP: model context window too small to fit overhead", {
|
|
10008
|
+
session: state.sessionId,
|
|
10009
|
+
limit: effective.limit,
|
|
10010
|
+
contextLimitSource: effective.source,
|
|
10011
|
+
overhead
|
|
10012
|
+
});
|
|
10013
|
+
}
|
|
10014
|
+
return;
|
|
10015
|
+
}
|
|
10144
10016
|
if (currentTokens < threshold) return;
|
|
10145
10017
|
const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES;
|
|
10146
10018
|
const candidates = [];
|
|
@@ -10185,7 +10057,9 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
|
|
|
10185
10057
|
truncatedCount,
|
|
10186
10058
|
estimatedSavedTokens: Math.round(savedTokens),
|
|
10187
10059
|
currentTokens,
|
|
10188
|
-
threshold
|
|
10060
|
+
threshold,
|
|
10061
|
+
contextLimit: effective.limit,
|
|
10062
|
+
contextLimitSource: effective.source
|
|
10189
10063
|
});
|
|
10190
10064
|
}
|
|
10191
10065
|
}
|
|
@@ -11146,11 +11020,12 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11146
11020
|
mergedCount: 0,
|
|
11147
11021
|
savedTokens: 0
|
|
11148
11022
|
};
|
|
11149
|
-
|
|
11023
|
+
const effective = resolveEffectiveContextLimit(state, config);
|
|
11024
|
+
if (!effective) {
|
|
11150
11025
|
return noop;
|
|
11151
11026
|
}
|
|
11152
11027
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
11153
|
-
if (currentTokens <
|
|
11028
|
+
if (currentTokens < effective.limit) {
|
|
11154
11029
|
return noop;
|
|
11155
11030
|
}
|
|
11156
11031
|
const maxMergedLength = config.gc.maxOldGenSummaryLength;
|
|
@@ -11167,7 +11042,8 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
11167
11042
|
mergedCount: result.mergedCount,
|
|
11168
11043
|
savedTokens: result.savedTokens,
|
|
11169
11044
|
currentTokens,
|
|
11170
|
-
contextLimit:
|
|
11045
|
+
contextLimit: effective.limit,
|
|
11046
|
+
contextLimitSource: effective.source
|
|
11171
11047
|
});
|
|
11172
11048
|
return {
|
|
11173
11049
|
tier: 3,
|
|
@@ -11201,11 +11077,6 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11201
11077
|
input.model?.limit?.context
|
|
11202
11078
|
);
|
|
11203
11079
|
const state = input.sessionID ? registry4.get(input.sessionID) : void 0;
|
|
11204
|
-
if (state && input.model?.limit?.context) {
|
|
11205
|
-
state.modelContextLimit = input.model.limit.context;
|
|
11206
|
-
state.modelProviderID = input.model?.providerID;
|
|
11207
|
-
state.modelID = input.model?.id;
|
|
11208
|
-
}
|
|
11209
11080
|
if (!state || state.isSubAgent && !config.allowSubAgents) {
|
|
11210
11081
|
return;
|
|
11211
11082
|
}
|
|
@@ -11214,6 +11085,23 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
|
|
|
11214
11085
|
logger.info("Skipping DCP system prompt injection for internal agent");
|
|
11215
11086
|
return;
|
|
11216
11087
|
}
|
|
11088
|
+
if (input.model?.limit?.context) {
|
|
11089
|
+
const limit = input.model.limit.context;
|
|
11090
|
+
const providerID = input.model?.providerID;
|
|
11091
|
+
const modelID = input.model?.id;
|
|
11092
|
+
const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
|
|
11093
|
+
state.modelContextLimit = limit;
|
|
11094
|
+
if (providerID !== void 0) {
|
|
11095
|
+
state.modelProviderID = providerID;
|
|
11096
|
+
}
|
|
11097
|
+
if (modelID !== void 0) {
|
|
11098
|
+
state.modelID = modelID;
|
|
11099
|
+
}
|
|
11100
|
+
if (changed) {
|
|
11101
|
+
saveSessionState(state, logger).catch(() => {
|
|
11102
|
+
});
|
|
11103
|
+
}
|
|
11104
|
+
}
|
|
11217
11105
|
const effectivePermission = compressPermission(state, config);
|
|
11218
11106
|
if (effectivePermission === "deny") {
|
|
11219
11107
|
return;
|
|
@@ -11258,10 +11146,17 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11258
11146
|
config
|
|
11259
11147
|
);
|
|
11260
11148
|
const requestModel = lastUserMessage.info.model;
|
|
11261
|
-
|
|
11149
|
+
let requestModelLimit = registry4.resolveModelLimit(
|
|
11262
11150
|
requestModel?.providerID,
|
|
11263
11151
|
requestModel?.modelID
|
|
11264
11152
|
);
|
|
11153
|
+
if (requestModelLimit === void 0 && requestModel?.providerID && requestModel?.modelID) {
|
|
11154
|
+
requestModelLimit = await registry4.hydrateAndResolve(
|
|
11155
|
+
client,
|
|
11156
|
+
requestModel.providerID,
|
|
11157
|
+
requestModel.modelID
|
|
11158
|
+
);
|
|
11159
|
+
}
|
|
11265
11160
|
const prevModelID = state.modelID;
|
|
11266
11161
|
if (requestModelLimit !== void 0) {
|
|
11267
11162
|
state.modelContextLimit = requestModelLimit;
|
|
@@ -11291,7 +11186,6 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11291
11186
|
});
|
|
11292
11187
|
}
|
|
11293
11188
|
await updatePerTurnState(state, logger, messages);
|
|
11294
|
-
trackUncalibratedWindow(state, logger);
|
|
11295
11189
|
}
|
|
11296
11190
|
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
11297
11191
|
if (state.isSubAgent && !config.allowSubAgents) {
|
|
@@ -11299,10 +11193,11 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11299
11193
|
}
|
|
11300
11194
|
stripHallucinations(output.messages);
|
|
11301
11195
|
ensureBuiltinFiltersRegistered();
|
|
11196
|
+
const effectiveLimit = resolveEffectiveContextLimit(state, config);
|
|
11302
11197
|
applyMessageFilters(output.messages, config.messageFilters, logger, {
|
|
11303
11198
|
sessionId: state.sessionId ?? "",
|
|
11304
11199
|
isSubAgent: state.isSubAgent,
|
|
11305
|
-
modelContextLimit:
|
|
11200
|
+
modelContextLimit: effectiveLimit?.limit
|
|
11306
11201
|
});
|
|
11307
11202
|
cacheSystemPromptTokens(state, output.messages);
|
|
11308
11203
|
assignMessageRefs(state, output.messages);
|
|
@@ -11322,7 +11217,6 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
11322
11217
|
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
11323
11218
|
prune(state, logger, config, output.messages);
|
|
11324
11219
|
truncateLargeToolOutputs(state, config, logger, output.messages);
|
|
11325
|
-
pruneToFit(state, config, logger, output.messages);
|
|
11326
11220
|
hideConsumedCompressCalls(state, output.messages);
|
|
11327
11221
|
assignMessageRefs(state, output.messages);
|
|
11328
11222
|
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
@@ -11354,14 +11248,31 @@ ${text}`);
|
|
|
11354
11248
|
stripStaleMetadata(output.messages);
|
|
11355
11249
|
dropEmptyMessages(output.messages);
|
|
11356
11250
|
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
11251
|
+
if (postTokens !== void 0 && effectiveLimit) {
|
|
11252
|
+
const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
|
|
11253
|
+
if (postTokens > budget) {
|
|
11254
|
+
logger.error(
|
|
11255
|
+
"ACP hard guard: context exceeds model budget after in-flight reduction",
|
|
11256
|
+
{
|
|
11257
|
+
session: state.sessionId,
|
|
11258
|
+
postTokens,
|
|
11259
|
+
budget,
|
|
11260
|
+
contextLimit: effectiveLimit.limit,
|
|
11261
|
+
contextLimitSource: effectiveLimit.source,
|
|
11262
|
+
hint: "request will likely be rejected; run /compact or start a new session"
|
|
11263
|
+
}
|
|
11264
|
+
);
|
|
11265
|
+
}
|
|
11266
|
+
}
|
|
11357
11267
|
logger.info("Chat transform complete", {
|
|
11358
11268
|
session: state.sessionId,
|
|
11359
11269
|
model: state.modelID,
|
|
11360
11270
|
messages: output.messages.length,
|
|
11361
11271
|
prePruneTokens,
|
|
11362
11272
|
postTokens,
|
|
11363
|
-
contextLimit:
|
|
11364
|
-
|
|
11273
|
+
contextLimit: effectiveLimit?.limit,
|
|
11274
|
+
contextLimitSource: effectiveLimit?.source,
|
|
11275
|
+
usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
|
|
11365
11276
|
nudged: state.nudges.shouldInjectThisTurn
|
|
11366
11277
|
});
|
|
11367
11278
|
if (state.sessionId) {
|
|
@@ -11748,7 +11659,7 @@ var server = (async (ctx) => {
|
|
|
11748
11659
|
}
|
|
11749
11660
|
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
11750
11661
|
logger.info("ACP plugin initialized", {
|
|
11751
|
-
version: true ? "1.14.25-pr.
|
|
11662
|
+
version: true ? "1.14.25-pr.349.75" : "dev",
|
|
11752
11663
|
workspace: ctx.directory,
|
|
11753
11664
|
logLevel: logger.level,
|
|
11754
11665
|
debug: config.debug,
|