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 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/prompts/extensions/nudge.ts
6426
- function buildCompressedBlockGuidance(state, context) {
6427
- const activeBlockIds = Array.from(state.prune.messages.activeBlockIds).filter((id) => Number.isInteger(id) && id > 0).sort((a, b) => a - b);
6428
- const blockCount = activeBlockIds.length;
6429
- const blocksForStats = activeBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0 && b.active);
6430
- const totalSummaryTokens = blocksForStats.reduce((s, b) => s + (b.summaryTokens ?? 0), 0);
6431
- const totalSummaryDisplay = totalSummaryTokens >= 1e3 ? `${(totalSummaryTokens / 1e3).toFixed(1)}K` : String(totalSummaryTokens);
6432
- const lastBlock = blocksForStats.length > 0 ? blocksForStats.reduce((latest, b) => b.createdAt > latest.createdAt ? b : latest) : null;
6433
- const ageStr = lastBlock ? formatAge(lastBlock.createdAt) : "never";
6434
- const lines = [
6435
- `- Compressed blocks: ${blockCount} (${totalSummaryDisplay} summary, last ${ageStr}). Use acp_status for details.`
6436
- ];
6437
- if (blockCount > 50) {
6438
- const oldBlockIds = activeBlockIds.slice(0, Math.max(0, blockCount - 20));
6439
- const allOldBlocks = oldBlockIds.map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0);
6440
- const visibleMessageIds = context?.visibleMessageIds;
6441
- const visibleOldBlocks = visibleMessageIds === void 0 ? allOldBlocks : allOldBlocks.filter((b) => b.anchorMessageId && visibleMessageIds.has(b.anchorMessageId));
6442
- if (visibleOldBlocks.length > 5) {
6443
- const blocksWithRef = visibleOldBlocks.map((block) => {
6444
- const ref = state.messageIds.byRawId.get(block.anchorMessageId);
6445
- return ref ? { block, ref } : null;
6446
- }).filter((x) => x !== null).sort((a, b) => a.ref.localeCompare(b.ref));
6447
- const totalTokens = blocksWithRef.reduce((s, x) => s + (x.block.summaryTokens ?? 0), 0);
6448
- const totalK = Math.max(1, Math.round(totalTokens / 1e3));
6449
- const targets = [];
6450
- const chunkSize = Math.ceil(blocksWithRef.length / 3);
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
- if (targets.length > 0) {
6461
- lines.push(`- \u{1F500} ${blocksWithRef.length} old blocks using ~${totalK}K tokens. Consolidate into ${targets.length}:`);
6462
- lines.push(...targets);
6463
- lines.push(` System auto-detects blocks in range \u2014 no need to manually list (bN) placeholders. Just write your summary normally.`);
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
- return lines.join("\n");
6468
- }
6469
- function appendGuidanceToDcpTag(nudgeText, guidance) {
6470
- if (!guidance.trim()) {
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 closeTag = "</dcp-system-reminder>";
6474
- const closeTagIndex = nudgeText.lastIndexOf(closeTag);
6475
- if (closeTagIndex === -1) {
6476
- return nudgeText;
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 beforeClose = nudgeText.slice(0, closeTagIndex).trimEnd();
6479
- const afterClose = nudgeText.slice(closeTagIndex);
6480
- return `${beforeClose}
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
- ${guidance}
6483
- ${afterClose}`;
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("%") || state.modelContextLimit === void 0) {
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 * state.modelContextLimit);
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: state.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 contextUsageBefore = ctx.state.modelContextLimit ? Math.round(
8586
- getCurrentTokenUsage(ctx.state, rawMessages) / ctx.state.modelContextLimit * 100
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 contextUsageAfter = ctx.state.modelContextLimit ? Math.round(
8651
- getCurrentTokenUsage(ctx.state, rawMessages) / ctx.state.modelContextLimit * 100
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.348.74" : "dev";
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
- if (!state.modelContextLimit) return;
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 threshold = parseGcThreshold(config.gc?.majorGcThresholdPercent, state.modelContextLimit);
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
- if (!state.modelContextLimit || state.modelContextLimit <= 0) {
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 < state.modelContextLimit) {
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: state.modelContextLimit
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
- const requestModelLimit = registry4.resolveModelLimit(
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: state.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: state.modelContextLimit,
11364
- usagePct: postTokens !== void 0 && state.modelContextLimit ? `${(postTokens / state.modelContextLimit * 100).toFixed(1)}%` : void 0,
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.348.74" : "dev",
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,