opencode-acp 1.5.0 → 1.5.1

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
@@ -894,6 +894,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
894
894
  "compress.modelMaxLimits",
895
895
  "compress.modelMinLimits",
896
896
  "compress.nudgeFrequency",
897
+ "compress.perMessageNudgeGrowthPercent",
897
898
  "compress.iterationNudgeThreshold",
898
899
  "compress.nudgeForce",
899
900
  "compress.protectedTools",
@@ -1117,6 +1118,13 @@ function validateConfigTypes(config) {
1117
1118
  actual: `${compress.nudgeFrequency} (will be clamped to 1)`
1118
1119
  });
1119
1120
  }
1121
+ if (compress.perMessageNudgeGrowthPercent !== void 0 && typeof compress.perMessageNudgeGrowthPercent !== "number") {
1122
+ errors.push({
1123
+ key: "compress.perMessageNudgeGrowthPercent",
1124
+ expected: "number",
1125
+ actual: typeof compress.perMessageNudgeGrowthPercent
1126
+ });
1127
+ }
1120
1128
  if (compress.iterationNudgeThreshold !== void 0 && typeof compress.iterationNudgeThreshold !== "number") {
1121
1129
  errors.push({
1122
1130
  key: "compress.iterationNudgeThreshold",
@@ -1461,6 +1469,7 @@ var defaultConfig = {
1461
1469
  maxContextLimit: "55%",
1462
1470
  minContextLimit: "45%",
1463
1471
  nudgeFrequency: 5,
1472
+ perMessageNudgeGrowthPercent: 3,
1464
1473
  iterationNudgeThreshold: 15,
1465
1474
  nudgeForce: "soft",
1466
1475
  protectedTools: [...COMPRESS_DEFAULT_PROTECTED_TOOLS],
@@ -1612,6 +1621,7 @@ function mergeCompress(base, override) {
1612
1621
  modelMaxLimits: override.modelMaxLimits ?? base.modelMaxLimits,
1613
1622
  modelMinLimits: override.modelMinLimits ?? base.modelMinLimits,
1614
1623
  nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
1624
+ perMessageNudgeGrowthPercent: override.perMessageNudgeGrowthPercent ?? base.perMessageNudgeGrowthPercent,
1615
1625
  iterationNudgeThreshold: override.iterationNudgeThreshold ?? base.iterationNudgeThreshold,
1616
1626
  nudgeForce: override.nudgeForce ?? base.nudgeForce,
1617
1627
  protectedTools: [.../* @__PURE__ */ new Set([...base.protectedTools, ...override.protectedTools ?? []])],
@@ -3019,7 +3029,9 @@ function resetOnCompaction(state) {
3019
3029
  state.nudges = {
3020
3030
  contextLimitAnchors: /* @__PURE__ */ new Set(),
3021
3031
  turnNudgeAnchors: /* @__PURE__ */ new Set(),
3022
- iterationNudgeAnchors: /* @__PURE__ */ new Set()
3032
+ iterationNudgeAnchors: /* @__PURE__ */ new Set(),
3033
+ lastPerMessageNudgeTurn: 0,
3034
+ lastPerMessageNudgeTokens: 0
3023
3035
  };
3024
3036
  state.messageIds = {
3025
3037
  byRawId: /* @__PURE__ */ new Map(),
@@ -3085,7 +3097,9 @@ async function saveSessionState(sessionState, logger, sessionName) {
3085
3097
  nudges: {
3086
3098
  contextLimitAnchors: Array.from(sessionState.nudges.contextLimitAnchors),
3087
3099
  turnNudgeAnchors: Array.from(sessionState.nudges.turnNudgeAnchors),
3088
- iterationNudgeAnchors: Array.from(sessionState.nudges.iterationNudgeAnchors)
3100
+ iterationNudgeAnchors: Array.from(sessionState.nudges.iterationNudgeAnchors),
3101
+ lastPerMessageNudgeTurn: sessionState.nudges.lastPerMessageNudgeTurn ?? 0,
3102
+ lastPerMessageNudgeTokens: sessionState.nudges.lastPerMessageNudgeTokens ?? 0
3089
3103
  },
3090
3104
  stats: sessionState.stats,
3091
3105
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3299,7 +3313,9 @@ function createSessionState() {
3299
3313
  nudges: {
3300
3314
  contextLimitAnchors: /* @__PURE__ */ new Set(),
3301
3315
  turnNudgeAnchors: /* @__PURE__ */ new Set(),
3302
- iterationNudgeAnchors: /* @__PURE__ */ new Set()
3316
+ iterationNudgeAnchors: /* @__PURE__ */ new Set(),
3317
+ lastPerMessageNudgeTurn: 0,
3318
+ lastPerMessageNudgeTokens: 0
3303
3319
  },
3304
3320
  stats: {
3305
3321
  pruneTokenCounter: 0,
@@ -3336,7 +3352,9 @@ function resetSessionState(state) {
3336
3352
  state.nudges = {
3337
3353
  contextLimitAnchors: /* @__PURE__ */ new Set(),
3338
3354
  turnNudgeAnchors: /* @__PURE__ */ new Set(),
3339
- iterationNudgeAnchors: /* @__PURE__ */ new Set()
3355
+ iterationNudgeAnchors: /* @__PURE__ */ new Set(),
3356
+ lastPerMessageNudgeTurn: 0,
3357
+ lastPerMessageNudgeTokens: 0
3340
3358
  };
3341
3359
  state.stats = {
3342
3360
  pruneTokenCounter: 0,
@@ -3381,6 +3399,8 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
3381
3399
  state.nudges.iterationNudgeAnchors = new Set(
3382
3400
  persisted.nudges.iterationNudgeAnchors || []
3383
3401
  );
3402
+ state.nudges.lastPerMessageNudgeTurn = persisted.nudges.lastPerMessageNudgeTurn ?? 0;
3403
+ state.nudges.lastPerMessageNudgeTokens = persisted.nudges.lastPerMessageNudgeTokens ?? 0;
3384
3404
  state.stats = {
3385
3405
  pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0,
3386
3406
  totalPruneTokens: persisted.stats?.totalPruneTokens || 0
@@ -4815,7 +4835,8 @@ var createSyntheticUserMessage = (baseMessage, content, stableSeed) => {
4815
4835
  sessionID: userInfo.sessionID,
4816
4836
  messageID: messageId,
4817
4837
  type: "text",
4818
- text: content
4838
+ text: content,
4839
+ synthetic: true
4819
4840
  }
4820
4841
  ]
4821
4842
  };
@@ -5225,12 +5246,18 @@ function buildCompressedBlockGuidance(state, gcConfig, context) {
5225
5246
  const recent = refs.slice(-20).join(", ");
5226
5247
  blockList = `${recent} (+${blockCount - 20} older, use decompress to access by ID)`;
5227
5248
  }
5249
+ const includeHint = context?.includeHint ?? true;
5228
5250
  const lines = [
5229
5251
  "Compressed block context:",
5230
5252
  `- Active compressed blocks: ${blockCount} (${blockList})`,
5231
- "- If your selected compression range includes any listed block, include each required placeholder exactly once in the summary using `(bN)`.",
5232
- "- \u{1F4A1} When you've finished using tool outputs, compress them \u2014 you can decompress later if needed. Lean context improves accuracy."
5253
+ "- If your selected compression range includes any listed block, include each required placeholder exactly once in the summary using `(bN)`."
5233
5254
  ];
5255
+ if (includeHint) {
5256
+ lines.push("- \u{1F4A1} When you've finished using tool outputs, compress them \u2014 you can decompress later if needed. Lean context improves accuracy.");
5257
+ }
5258
+ if (blockCount > 50) {
5259
+ lines.push(`- \u{1F500} You have ${blockCount} blocks \u2014 consider merging adjacent same-topic blocks instead of finding new content to compress. This permanently reduces per-turn overhead.`);
5260
+ }
5234
5261
  const usageRatio = context?.currentTokens && context?.modelContextLimit ? context.currentTokens / context.modelContextLimit : 0;
5235
5262
  if (gcConfig && usageRatio > 0.5) {
5236
5263
  const promotionThreshold = gcConfig.promotionThreshold;
@@ -5573,7 +5600,7 @@ function resolveThresholdPercent(threshold, modelContextLimit) {
5573
5600
  const parsed = parseFloat(threshold);
5574
5601
  return isNaN(parsed) ? void 0 : parsed;
5575
5602
  }
5576
- function buildContextUsageGuidance(config, currentTokens, modelContextLimit) {
5603
+ function buildContextUsageGuidance(config, currentTokens, modelContextLimit, minimal = false) {
5577
5604
  if (currentTokens === void 0 || modelContextLimit === void 0 || modelContextLimit === 0) {
5578
5605
  return "";
5579
5606
  }
@@ -5583,9 +5610,14 @@ function buildContextUsageGuidance(config, currentTokens, modelContextLimit) {
5583
5610
  const minPct = resolveThresholdPercent(config.compress.minContextLimit, modelContextLimit) ?? 45;
5584
5611
  const maxPct = resolveThresholdPercent(config.compress.maxContextLimit, modelContextLimit) ?? 55;
5585
5612
  const base = `Context usage: ${formatK(currentTokens)} / ${formatK(modelContextLimit)} tokens (${percentage}%).`;
5613
+ if (minimal) {
5614
+ return `
5615
+
5616
+ ${base}`;
5617
+ }
5586
5618
  let guidance;
5587
5619
  if (pct < minPct) {
5588
- guidance = " \u{1F4A1} Be frugal with context \u2014 compress tool outputs you've finished using into summaries. You can decompress later; nothing is permanently lost. Lean context means better accuracy. Extract and keep what matters: user intent, key decisions, file paths, and important findings \u2014 even if buried in large messages. Compress everything else, including verbose parts of any message.";
5620
+ guidance = " \u{1F4A1} Be frugal with context \u2014 if you see large completed outputs (>2000 tokens), compress them into summaries. If everything is already compressed, skip this nudge. You can decompress later if needed. Extract and keep what matters: user intent, key decisions, file paths, and important findings. Compress everything else.";
5589
5621
  } else if (pct < maxPct) {
5590
5622
  guidance = " \u26A0\uFE0F Context is growing \u2014 compress completed sections and high-token waste now. Preserve key details.";
5591
5623
  } else {
@@ -5687,6 +5719,18 @@ function createSuffixMessage(messages) {
5687
5719
  messages.push(synthetic);
5688
5720
  return synthetic;
5689
5721
  }
5722
+ function shouldInjectPerMessageNudge(state, config, currentTokens, modelContextLimit) {
5723
+ const turn = state.currentTurn ?? 0;
5724
+ const lastTurn = state.nudges.lastPerMessageNudgeTurn ?? 0;
5725
+ const turnsSinceLast = turn - lastTurn;
5726
+ const tokens = currentTokens ?? 0;
5727
+ const lastTokens = state.nudges.lastPerMessageNudgeTokens ?? 0;
5728
+ const tokenGrowth = tokens - lastTokens;
5729
+ const tokenGrowthPercent = modelContextLimit ? tokenGrowth / modelContextLimit * 100 : 0;
5730
+ const frequency = config.compress.nudgeFrequency ?? 5;
5731
+ const growthThreshold = config.compress.perMessageNudgeGrowthPercent ?? 3;
5732
+ return turnsSinceLast >= frequency || tokenGrowthPercent >= growthThreshold;
5733
+ }
5690
5734
  var injectCompressNudges = (state, config, logger, messages, prompts, compressionPriorities) => {
5691
5735
  if (compressPermission(state, config) === "deny") {
5692
5736
  return;
@@ -5771,21 +5815,26 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
5771
5815
  }
5772
5816
  const suffixMessage = createSuffixMessage(messages);
5773
5817
  applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
5774
- injectContextUsage(suffixMessage, config, currentTokens, modelContextLimit);
5818
+ const shouldNudge = shouldInjectPerMessageNudge(state, config, currentTokens, modelContextLimit);
5819
+ injectContextUsage(suffixMessage, config, currentTokens, modelContextLimit, !shouldNudge);
5775
5820
  if (config.compress.mode !== "message") {
5776
- const blockGuidance = buildCompressedBlockGuidance(state, config.gc, { currentTokens, modelContextLimit });
5821
+ const blockGuidance = buildCompressedBlockGuidance(state, config.gc, { currentTokens, modelContextLimit, includeHint: shouldNudge });
5777
5822
  if (blockGuidance.trim() && suffixMessage) {
5778
5823
  appendToLastTextPart(suffixMessage, "\n\n" + blockGuidance);
5779
5824
  }
5780
5825
  }
5826
+ if (shouldNudge) {
5827
+ state.nudges.lastPerMessageNudgeTurn = state.currentTurn ?? 0;
5828
+ state.nudges.lastPerMessageNudgeTokens = currentTokens ?? 0;
5829
+ }
5781
5830
  injectVisibleIdRange(state, messages, suffixMessage);
5782
5831
  if (anchorsChanged) {
5783
5832
  void saveSessionState(state, logger);
5784
5833
  }
5785
5834
  };
5786
- function injectContextUsage(target, config, currentTokens, modelContextLimit) {
5835
+ function injectContextUsage(target, config, currentTokens, modelContextLimit, minimal = false) {
5787
5836
  if (!target) return;
5788
- const usageTag = buildContextUsageGuidance(config, currentTokens, modelContextLimit);
5837
+ const usageTag = buildContextUsageGuidance(config, currentTokens, modelContextLimit, minimal);
5789
5838
  if (!usageTag) return;
5790
5839
  for (const part of target.parts) {
5791
5840
  if (part.type === "text") {