opencode-acp 1.14.25-pr.348.70 → 1.14.25-pr.349.71

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