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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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,291 +7528,6 @@ function excludeProtectedRanges(ranges, protectedRefs) {
7358
7528
  );
7359
7529
  }
7360
7530
 
7361
- // lib/messages/prune-to-fit.ts
7362
- var WIRE_SAFETY_MARGIN = 8192;
7363
- var CLEAR_PLACEHOLDER = "[cleared by ACP overflow guard \u2014 re-run tool if needed]";
7364
- var MIN_CLEAR_TOKENS = 500;
7365
- function resolveKnownWindow(config, state, providerId, modelId) {
7366
- if (state.modelContextLimit !== void 0) {
7367
- return state.modelContextLimit;
7368
- }
7369
- const maxLimits = config.compress.modelMaxLimits;
7370
- if (maxLimits && providerId !== void 0 && modelId !== void 0) {
7371
- const perModel = maxLimits[`${providerId}/${modelId}`];
7372
- if (typeof perModel === "number") return perModel;
7373
- }
7374
- const global = config.compress.maxContextLimit;
7375
- if (typeof global === "number") return global;
7376
- return void 0;
7377
- }
7378
- function estimateWireTokens(state, messages) {
7379
- const base = getCurrentTokenUsage(state, messages);
7380
- if (base > 0) {
7381
- let trailing = 0;
7382
- const lastAsst = [...messages].reverse().find((m) => m.info.role === "assistant");
7383
- if (lastAsst) {
7384
- const parts = Array.isArray(lastAsst.parts) ? lastAsst.parts : [];
7385
- for (let i = parts.length - 1; i >= 0; i--) {
7386
- const p = parts[i];
7387
- if (p?.type !== "tool") break;
7388
- if (p.state.status !== "completed") break;
7389
- trailing += countTokens2(extractCompletedToolOutput(p) ?? "");
7390
- }
7391
- }
7392
- return base + trailing + WIRE_SAFETY_MARGIN;
7393
- }
7394
- let total = 0;
7395
- for (const msg of messages) {
7396
- total += countAllMessageTokens(msg);
7397
- }
7398
- return total + (state.systemPromptTokens ?? 0) + WIRE_SAFETY_MARGIN;
7399
- }
7400
- function pruneToFit(state, config, logger, messages) {
7401
- if (!config.compress.overflowGuard) return;
7402
- const knownWindow = resolveKnownWindow(config, state, state.modelProviderID, state.modelID);
7403
- if (knownWindow === void 0) return;
7404
- const reserve = config.compress.overflowGuardReserve ?? 32768;
7405
- const safeBudget = knownWindow - reserve;
7406
- if (safeBudget <= 0) return;
7407
- const estimate = estimateWireTokens(state, messages);
7408
- if (estimate <= safeBudget) return;
7409
- const protectedRefs = computeProtectedRefs(messages, state, config.compress);
7410
- const lastNonIgnored = findLastNonIgnoredMessage(messages);
7411
- const lastMsgId = lastNonIgnored?.message.info.id;
7412
- const protectedTools = config.compress.protectedTools;
7413
- const protectedFilePatterns = config.protectedFilePatterns;
7414
- let freed = 0;
7415
- let clearedCount = 0;
7416
- for (const msg of messages) {
7417
- if (estimate - freed <= safeBudget) break;
7418
- if (msg.info.id === lastMsgId) continue;
7419
- if (msg.info.role === "user") continue;
7420
- const ref = state.messageIds.byRawId.get(msg.info.id);
7421
- if (ref && protectedRefs.has(ref)) continue;
7422
- const parts = Array.isArray(msg.parts) ? msg.parts : [];
7423
- for (const part of parts) {
7424
- if (estimate - freed <= safeBudget) break;
7425
- if (part?.type !== "tool") continue;
7426
- const toolState = part.state;
7427
- if (toolState.status !== "completed") continue;
7428
- const content = extractCompletedToolOutput(part);
7429
- if (content === void 0) continue;
7430
- if (content === CLEAR_PLACEHOLDER) continue;
7431
- if (content === COMPACTED_TOOL_OUTPUT_PLACEHOLDER) continue;
7432
- if (isToolNameProtected(part.tool, protectedTools)) continue;
7433
- if (protectedFilePatterns.length > 0) {
7434
- const filePaths = getFilePathsFromParameters(part.tool, toolState.input);
7435
- if (isFilePathProtected(filePaths, protectedFilePatterns)) continue;
7436
- }
7437
- const outputTokens = countTokens2(content);
7438
- if (outputTokens < MIN_CLEAR_TOKENS) continue;
7439
- toolState.output = CLEAR_PLACEHOLDER;
7440
- freed += outputTokens - countTokens2(CLEAR_PLACEHOLDER);
7441
- clearedCount++;
7442
- }
7443
- }
7444
- const after = estimate - freed;
7445
- const detail = {
7446
- session: state.sessionId,
7447
- estimate,
7448
- safeBudget,
7449
- knownWindow,
7450
- reserve,
7451
- clearedCount,
7452
- freedTokens: Math.round(freed),
7453
- afterTokens: Math.round(after)
7454
- };
7455
- if (clearedCount === 0) {
7456
- logger.error("ACP overflow guard: over window but no clearable tool outputs", detail);
7457
- return;
7458
- }
7459
- if (after <= safeBudget) {
7460
- logger.warn("ACP overflow guard: cleared tool outputs to fit context window", detail);
7461
- } else {
7462
- logger.error(
7463
- "ACP overflow guard: cleared tool outputs but context STILL exceeds window",
7464
- detail
7465
- );
7466
- }
7467
- }
7468
-
7469
- // lib/messages/uncalibrated-window.ts
7470
- var UNCALIBRATED_WINDOW_WARN_THRESHOLD = 3;
7471
- function trackUncalibratedWindow(state, logger) {
7472
- if (state.modelContextLimit === void 0) {
7473
- state.uncalibratedWindowTransforms++;
7474
- if (state.uncalibratedWindowTransforms >= UNCALIBRATED_WINDOW_WARN_THRESHOLD && !state.uncalibratedWindowWarned) {
7475
- state.uncalibratedWindowWarned = true;
7476
- logger.warn(
7477
- "Model reports no context window \u2014 ACP percentage thresholds are disabled",
7478
- {
7479
- session: state.sessionId,
7480
- provider: state.modelProviderID,
7481
- model: state.modelID,
7482
- transforms: state.uncalibratedWindowTransforms,
7483
- hint: 'set the model\'s `limit` in opencode.json (e.g. {"context": 262144, "output": 16384}) or use absolute compress.maxContextLimit / compress.minContextLimit in acp.jsonc; the request-side overflow guard also needs a known window to fire'
7484
- }
7485
- );
7486
- }
7487
- } else {
7488
- state.uncalibratedWindowTransforms = 0;
7489
- }
7490
- }
7491
-
7492
- // lib/messages/sync.ts
7493
- function sortBlocksByCreation(a, b) {
7494
- const createdAtDiff = a.createdAt - b.createdAt;
7495
- if (createdAtDiff !== 0) {
7496
- return createdAtDiff;
7497
- }
7498
- return a.blockId - b.blockId;
7499
- }
7500
- var syncCompressionBlocks = (state, logger, messages) => {
7501
- const messagesState = state.prune.messages;
7502
- if (!messagesState?.blocksById?.size) {
7503
- return;
7504
- }
7505
- const messageIds = new Set(messages.map((msg) => msg.info.id));
7506
- const previousActiveBlockIds = new Set(
7507
- Array.from(messagesState.blocksById.values()).filter((block) => block.active).map((block) => block.blockId)
7508
- );
7509
- messagesState.activeBlockIds.clear();
7510
- messagesState.activeByAnchorMessageId.clear();
7511
- const now = Date.now();
7512
- const orderedBlocks = Array.from(messagesState.blocksById.values()).sort(sortBlocksByCreation);
7513
- for (const block of orderedBlocks) {
7514
- if (block.deactivatedByUser || block.deactivatedByUserDeep) {
7515
- block.active = false;
7516
- if (block.deactivatedAt === void 0) {
7517
- block.deactivatedAt = now;
7518
- }
7519
- block.deactivatedByBlockId = void 0;
7520
- continue;
7521
- }
7522
- for (const consumedBlockId of block.consumedBlockIds) {
7523
- if (!messagesState.activeBlockIds.has(consumedBlockId)) {
7524
- continue;
7525
- }
7526
- const consumedBlock = messagesState.blocksById.get(consumedBlockId);
7527
- if (consumedBlock) {
7528
- consumedBlock.active = false;
7529
- consumedBlock.deactivatedAt = now;
7530
- consumedBlock.deactivatedByBlockId = block.blockId;
7531
- const mappedBlockId = messagesState.activeByAnchorMessageId.get(
7532
- consumedBlock.anchorMessageId
7533
- );
7534
- if (mappedBlockId === consumedBlock.blockId) {
7535
- messagesState.activeByAnchorMessageId.delete(consumedBlock.anchorMessageId);
7536
- }
7537
- }
7538
- messagesState.activeBlockIds.delete(consumedBlockId);
7539
- }
7540
- block.active = true;
7541
- block.deactivatedAt = void 0;
7542
- block.deactivatedByBlockId = void 0;
7543
- messagesState.activeBlockIds.add(block.blockId);
7544
- if (messageIds.has(block.anchorMessageId)) {
7545
- messagesState.activeByAnchorMessageId.set(block.anchorMessageId, block.blockId);
7546
- }
7547
- }
7548
- for (const entry of messagesState.byMessageId.values()) {
7549
- const allBlockIds = Array.isArray(entry.allBlockIds) ? [...new Set(entry.allBlockIds.filter((id) => Number.isInteger(id) && id > 0))] : [];
7550
- entry.allBlockIds = allBlockIds;
7551
- entry.activeBlockIds = allBlockIds.filter((id) => messagesState.activeBlockIds.has(id));
7552
- }
7553
- const nextActiveBlockIds = messagesState.activeBlockIds;
7554
- let deactivatedCount = 0;
7555
- let reactivatedCount = 0;
7556
- for (const blockId of previousActiveBlockIds) {
7557
- if (!nextActiveBlockIds.has(blockId)) {
7558
- deactivatedCount++;
7559
- }
7560
- }
7561
- for (const blockId of nextActiveBlockIds) {
7562
- if (!previousActiveBlockIds.has(blockId)) {
7563
- reactivatedCount++;
7564
- }
7565
- }
7566
- if (deactivatedCount > 0 || reactivatedCount > 0) {
7567
- logger.info("Synced compress block state", {
7568
- deactivatedCount,
7569
- reactivatedCount
7570
- });
7571
- }
7572
- };
7573
-
7574
- // lib/host-permissions.ts
7575
- var findLastMatchingRule = (rules, predicate) => {
7576
- for (let index = rules.length - 1; index >= 0; index -= 1) {
7577
- const rule = rules[index];
7578
- if (rule && predicate(rule)) {
7579
- return rule;
7580
- }
7581
- }
7582
- return void 0;
7583
- };
7584
- var wildcardMatch = (value, pattern) => {
7585
- const normalizedValue = value.replaceAll("\\", "/");
7586
- let escaped = pattern.replaceAll("\\", "/").replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
7587
- if (escaped.endsWith(" .*")) {
7588
- escaped = escaped.slice(0, -3) + "( .*)?";
7589
- }
7590
- const flags = process.platform === "win32" ? "si" : "s";
7591
- return new RegExp(`^${escaped}$`, flags).test(normalizedValue);
7592
- };
7593
- var getPermissionRules = (permissionConfigs) => {
7594
- const rules = [];
7595
- for (const permissionConfig of permissionConfigs) {
7596
- if (!permissionConfig) {
7597
- continue;
7598
- }
7599
- for (const [permission, value] of Object.entries(permissionConfig)) {
7600
- if (value === "ask" || value === "allow" || value === "deny") {
7601
- rules.push({ permission, pattern: "*", action: value });
7602
- continue;
7603
- }
7604
- for (const [pattern, action] of Object.entries(value)) {
7605
- if (action === "ask" || action === "allow" || action === "deny") {
7606
- rules.push({ permission, pattern, action });
7607
- }
7608
- }
7609
- }
7610
- }
7611
- return rules;
7612
- };
7613
- var compressDisabledByOpencode = (...permissionConfigs) => {
7614
- const match = findLastMatchingRule(
7615
- getPermissionRules(permissionConfigs),
7616
- (rule) => wildcardMatch("compress", rule.permission)
7617
- );
7618
- return match?.pattern === "*" && match.action === "deny";
7619
- };
7620
- var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agentName) => {
7621
- if (basePermission === "deny") {
7622
- return "deny";
7623
- }
7624
- return compressDisabledByOpencode(
7625
- hostPermissions.global,
7626
- agentName ? hostPermissions.agents[agentName] : void 0
7627
- ) ? "deny" : basePermission;
7628
- };
7629
- var hasExplicitToolPermission = (permissionConfig, tool6) => {
7630
- return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool6) : false;
7631
- };
7632
-
7633
- // lib/compress-permission.ts
7634
- var compressPermission = (state, config) => {
7635
- return state.compressPermission ?? config.compress.permission;
7636
- };
7637
- var syncCompressPermissionState = (state, config, hostPermissions, messages) => {
7638
- const activeAgent = getLastUserMessage(messages)?.info.agent;
7639
- state.compressPermission = resolveEffectiveCompressPermission(
7640
- config.compress.permission,
7641
- hostPermissions,
7642
- activeAgent
7643
- );
7644
- };
7645
-
7646
7531
  // lib/messages/inject/inject.ts
7647
7532
  var ACP_SUFFIX_SEED = "acp-dynamic-guidance";
7648
7533
  function createSuffixMessage(messages) {
@@ -8549,8 +8434,9 @@ function createDecompressTool(factoryCtx) {
8549
8434
  async execute(args, toolCtx) {
8550
8435
  const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
8551
8436
  const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
8552
- const contextUsageBefore = ctx.state.modelContextLimit ? Math.round(
8553
- 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
8554
8440
  ) : void 0;
8555
8441
  const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
8556
8442
  if (!resolved.ok) {
@@ -8614,8 +8500,9 @@ function createDecompressTool(factoryCtx) {
8614
8500
  0,
8615
8501
  ctx.state.stats.totalPruneTokens - restoredTokens
8616
8502
  );
8617
- const contextUsageAfter = ctx.state.modelContextLimit ? Math.round(
8618
- 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
8619
8506
  ) : void 0;
8620
8507
  await finalizeDecompressSession(ctx);
8621
8508
  const restoredContentPreview = buildRestoredContentPreview(
@@ -9272,7 +9159,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
9272
9159
  import { join as join3 } from "path";
9273
9160
  import { existsSync as existsSync3 } from "fs";
9274
9161
  import { homedir as homedir3 } from "os";
9275
- var LOG_VERSION = true ? "1.14.25-pr.348.72" : "dev";
9162
+ var LOG_VERSION = true ? "1.14.25-pr.349.71" : "dev";
9276
9163
  var LEVEL_RANK = {
9277
9164
  debug: 10,
9278
9165
  info: 20,
@@ -10096,6 +9983,7 @@ var MIN_OUTPUT_TOKENS = 1e3;
10096
9983
  var KEEP_PREFIX_CHARS = 2e3;
10097
9984
  var KEEP_SUFFIX_CHARS = 2e3;
10098
9985
  var PROTECT_RECENT_MESSAGES = 3;
9986
+ var OUTPUT_RESERVE_TOKENS = 16384;
10099
9987
  function parseGcThreshold(threshold, modelContextLimit) {
10100
9988
  if (typeof threshold === "number") return threshold;
10101
9989
  const str = threshold ?? "100%";
@@ -10104,10 +9992,22 @@ function parseGcThreshold(threshold, modelContextLimit) {
10104
9992
  return modelContextLimit;
10105
9993
  }
10106
9994
  function truncateLargeToolOutputs(state, config, logger, messages) {
10107
- if (!state.modelContextLimit) return;
9995
+ const effective = resolveEffectiveContextLimit(state, config);
9996
+ if (!effective) return;
10108
9997
  const currentTokens = getCurrentTokenUsage(state, messages);
10109
9998
  if (currentTokens === 0) return;
10110
- const 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
+ }
10111
10011
  if (currentTokens < threshold) return;
10112
10012
  const protectedIndex = messages.length - PROTECT_RECENT_MESSAGES;
10113
10013
  const candidates = [];
@@ -10152,7 +10052,9 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
10152
10052
  truncatedCount,
10153
10053
  estimatedSavedTokens: Math.round(savedTokens),
10154
10054
  currentTokens,
10155
- threshold
10055
+ threshold,
10056
+ contextLimit: effective.limit,
10057
+ contextLimitSource: effective.source
10156
10058
  });
10157
10059
  }
10158
10060
  }
@@ -11113,11 +11015,12 @@ function runBatchCleanup(state, config, logger, messages) {
11113
11015
  mergedCount: 0,
11114
11016
  savedTokens: 0
11115
11017
  };
11116
- if (!state.modelContextLimit || state.modelContextLimit <= 0) {
11018
+ const effective = resolveEffectiveContextLimit(state, config);
11019
+ if (!effective) {
11117
11020
  return noop;
11118
11021
  }
11119
11022
  const currentTokens = getCurrentTokenUsage(state, messages);
11120
- if (currentTokens < state.modelContextLimit) {
11023
+ if (currentTokens < effective.limit) {
11121
11024
  return noop;
11122
11025
  }
11123
11026
  const maxMergedLength = config.gc.maxOldGenSummaryLength;
@@ -11134,7 +11037,8 @@ function runBatchCleanup(state, config, logger, messages) {
11134
11037
  mergedCount: result.mergedCount,
11135
11038
  savedTokens: result.savedTokens,
11136
11039
  currentTokens,
11137
- contextLimit: state.modelContextLimit
11040
+ contextLimit: effective.limit,
11041
+ contextLimitSource: effective.source
11138
11042
  });
11139
11043
  return {
11140
11044
  tier: 3,
@@ -11168,11 +11072,6 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
11168
11072
  input.model?.limit?.context
11169
11073
  );
11170
11074
  const state = input.sessionID ? registry4.get(input.sessionID) : void 0;
11171
- if (state && input.model?.limit?.context) {
11172
- state.modelContextLimit = input.model.limit.context;
11173
- state.modelProviderID = input.model?.providerID;
11174
- state.modelID = input.model?.id;
11175
- }
11176
11075
  if (!state || state.isSubAgent && !config.allowSubAgents) {
11177
11076
  return;
11178
11077
  }
@@ -11181,6 +11080,19 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
11181
11080
  logger.info("Skipping DCP system prompt injection for internal agent");
11182
11081
  return;
11183
11082
  }
11083
+ if (input.model?.limit?.context) {
11084
+ const limit = input.model.limit.context;
11085
+ const providerID = input.model?.providerID;
11086
+ const modelID = input.model?.id;
11087
+ const changed = state.modelContextLimit !== limit || state.modelProviderID !== providerID || state.modelID !== modelID;
11088
+ state.modelContextLimit = limit;
11089
+ state.modelProviderID = providerID;
11090
+ state.modelID = modelID;
11091
+ if (changed) {
11092
+ saveSessionState(state, logger).catch(() => {
11093
+ });
11094
+ }
11095
+ }
11184
11096
  const effectivePermission = compressPermission(state, config);
11185
11097
  if (effectivePermission === "deny") {
11186
11098
  return;
@@ -11225,10 +11137,17 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11225
11137
  config
11226
11138
  );
11227
11139
  const requestModel = lastUserMessage.info.model;
11228
- const requestModelLimit = registry4.resolveModelLimit(
11140
+ let requestModelLimit = registry4.resolveModelLimit(
11229
11141
  requestModel?.providerID,
11230
11142
  requestModel?.modelID
11231
11143
  );
11144
+ if (requestModelLimit === void 0 && requestModel?.providerID && requestModel?.modelID) {
11145
+ requestModelLimit = await registry4.hydrateAndResolve(
11146
+ client,
11147
+ requestModel.providerID,
11148
+ requestModel.modelID
11149
+ );
11150
+ }
11232
11151
  const prevModelID = state.modelID;
11233
11152
  if (requestModelLimit !== void 0) {
11234
11153
  state.modelContextLimit = requestModelLimit;
@@ -11258,7 +11177,6 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11258
11177
  });
11259
11178
  }
11260
11179
  await updatePerTurnState(state, logger, messages);
11261
- trackUncalibratedWindow(state, logger);
11262
11180
  }
11263
11181
  syncCompressPermissionState(state, config, hostPermissions, output.messages);
11264
11182
  if (state.isSubAgent && !config.allowSubAgents) {
@@ -11266,10 +11184,11 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11266
11184
  }
11267
11185
  stripHallucinations(output.messages);
11268
11186
  ensureBuiltinFiltersRegistered();
11187
+ const effectiveLimit = resolveEffectiveContextLimit(state, config);
11269
11188
  applyMessageFilters(output.messages, config.messageFilters, logger, {
11270
11189
  sessionId: state.sessionId ?? "",
11271
11190
  isSubAgent: state.isSubAgent,
11272
- modelContextLimit: state.modelContextLimit
11191
+ modelContextLimit: effectiveLimit?.limit
11273
11192
  });
11274
11193
  cacheSystemPromptTokens(state, output.messages);
11275
11194
  assignMessageRefs(state, output.messages);
@@ -11289,7 +11208,6 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11289
11208
  const prePruneTokens = getCurrentTokenUsage(state, output.messages);
11290
11209
  prune(state, logger, config, output.messages);
11291
11210
  truncateLargeToolOutputs(state, config, logger, output.messages);
11292
- pruneToFit(state, config, logger, output.messages);
11293
11211
  hideConsumedCompressCalls(state, output.messages);
11294
11212
  assignMessageRefs(state, output.messages);
11295
11213
  const compressionPriorities = buildPriorityMap(config, state, output.messages);
@@ -11321,14 +11239,31 @@ ${text}`);
11321
11239
  stripStaleMetadata(output.messages);
11322
11240
  dropEmptyMessages(output.messages);
11323
11241
  const postTokens = getCurrentTokenUsage(state, output.messages);
11242
+ if (postTokens !== void 0 && effectiveLimit) {
11243
+ const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
11244
+ if (postTokens > budget) {
11245
+ logger.error(
11246
+ "ACP hard guard: context exceeds model budget after in-flight reduction",
11247
+ {
11248
+ session: state.sessionId,
11249
+ postTokens,
11250
+ budget,
11251
+ contextLimit: effectiveLimit.limit,
11252
+ contextLimitSource: effectiveLimit.source,
11253
+ hint: "request will likely be rejected; run /compact or start a new session"
11254
+ }
11255
+ );
11256
+ }
11257
+ }
11324
11258
  logger.info("Chat transform complete", {
11325
11259
  session: state.sessionId,
11326
11260
  model: state.modelID,
11327
11261
  messages: output.messages.length,
11328
11262
  prePruneTokens,
11329
11263
  postTokens,
11330
- contextLimit: state.modelContextLimit,
11331
- 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,
11332
11267
  nudged: state.nudges.shouldInjectThisTurn
11333
11268
  });
11334
11269
  if (state.sessionId) {
@@ -11715,7 +11650,7 @@ var server = (async (ctx) => {
11715
11650
  }
11716
11651
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
11717
11652
  logger.info("ACP plugin initialized", {
11718
- version: true ? "1.14.25-pr.348.72" : "dev",
11653
+ version: true ? "1.14.25-pr.349.71" : "dev",
11719
11654
  workspace: ctx.directory,
11720
11655
  logLevel: logger.level,
11721
11656
  debug: config.debug,