opencode-acp 1.14.22-pr.327.45 → 1.14.22-pr.330.48

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/README.md CHANGED
@@ -278,7 +278,8 @@ Each level overrides the previous, so project settings take priority over global
278
278
  "$schema": "https://raw.githubusercontent.com/ranxianglei/opencode-acp/master/dcp.schema.json",
279
279
  // Enable or disable the plugin
280
280
  "enabled": true,
281
- // Automatically update npm-installed ACP when a newer npm latest is available.
281
+ // Automatically update npm-installed ACP when a newer version is available
282
+ // on the installed dist-tag/spec (@stable follows stable, @latest follows latest).
282
283
  // Version-locked plugin specs are not updated.
283
284
  "autoUpdate": true,
284
285
  // Enable INFO/DEBUG logging + per-request snapshots to ~/.config/opencode/logs/acp/
package/README.zh-CN.md CHANGED
@@ -233,8 +233,8 @@ ACP 使用自己的配置文件,按以下顺序搜索:
233
233
  "$schema": "https://raw.githubusercontent.com/ranxianglei/opencode-acp/master/dcp.schema.json",
234
234
  // Enable or disable the plugin
235
235
  "enabled": true,
236
- // Automatically update npm-installed ACP when a newer npm latest is available.
237
- // Version-locked plugin specs are not updated.
236
+ // 自动更新 npm 安装的 ACP:跟踪安装所用 dist-tag/规范(@stable 跟随 stable,@latest 跟随 latest)。
237
+ // 版本锁定的规范不会被更新。
238
238
  "autoUpdate": true,
239
239
  // Enable INFO/DEBUG logging + per-request snapshots to ~/.config/opencode/logs/acp/
240
240
  // (WARN/ERROR are always logged to daily/<date>.log)
package/dist/index.js CHANGED
@@ -7186,7 +7186,9 @@ function refNum(ref) {
7186
7186
  function buildCompressibleRanges(messages, state, protectedTools = [], protectedFilePatterns = [], protectedZoneRefs) {
7187
7187
  const msgInfo = [];
7188
7188
  const protectedMsgInfo = [];
7189
- for (const msg of messages) {
7189
+ const lastUserRefIdx = [];
7190
+ for (let mi = 0; mi < messages.length; mi++) {
7191
+ const msg = messages[mi];
7190
7192
  if (isSyntheticMessage(msg)) continue;
7191
7193
  const ref = state.messageIds.byRawId.get(msg.info.id);
7192
7194
  if (!ref) continue;
@@ -7221,15 +7223,27 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
7221
7223
  }
7222
7224
  let tokens = 0;
7223
7225
  let isTool = false;
7226
+ let hasMeaningfulPart = false;
7224
7227
  for (const part of msg.parts || []) {
7225
7228
  if (part.type === "text" && typeof part.text === "string") {
7226
7229
  tokens += Math.round(part.text.length / 4);
7230
+ if (part.text.trim().length > 0) hasMeaningfulPart = true;
7227
7231
  } else if (part.type !== "text" && part.type !== "reasoning") {
7228
7232
  tokens += Math.round(JSON.stringify(part).length / 4);
7229
7233
  isTool = true;
7234
+ hasMeaningfulPart = true;
7230
7235
  }
7231
7236
  }
7232
- msgInfo.push({ ref, refNum: rn, tokens, isTool, isUser: msg.info.role === "user" });
7237
+ if (msg.info.role === "user" && !isIgnoredUserMessage(msg)) {
7238
+ lastUserRefIdx.length = 0;
7239
+ lastUserRefIdx.push(msgInfo.length);
7240
+ }
7241
+ msgInfo.push({ ref, refNum: rn, tokens, effectiveTokens: 0, meaningful: hasMeaningfulPart, isTool, isUser: msg.info.role === "user" });
7242
+ }
7243
+ const lastUserIdx = lastUserRefIdx.length > 0 ? lastUserRefIdx[0] : -1;
7244
+ for (let i = 0; i < msgInfo.length; i++) {
7245
+ const info = msgInfo[i];
7246
+ info.effectiveTokens = i !== lastUserIdx && info.meaningful ? info.tokens : 0;
7233
7247
  }
7234
7248
  const groups = [];
7235
7249
  let cur = null;
@@ -7255,6 +7269,7 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
7255
7269
  endRef: info.ref,
7256
7270
  count: 1,
7257
7271
  tokens: info.tokens,
7272
+ effectiveTokens: info.effectiveTokens,
7258
7273
  toolPct: info.isTool ? 100 : 0,
7259
7274
  textPct: info.isTool ? 0 : 100
7260
7275
  };
@@ -7262,6 +7277,7 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
7262
7277
  cur.endRef = info.ref;
7263
7278
  cur.count++;
7264
7279
  cur.tokens += info.tokens;
7280
+ cur.effectiveTokens += info.effectiveTokens;
7265
7281
  if (info.isTool) {
7266
7282
  cur.toolPct = Math.round((cur.toolPct * (cur.count - 1) + 100) / cur.count);
7267
7283
  } else {
@@ -7304,6 +7320,11 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
7304
7320
  protected: protectedGroups
7305
7321
  };
7306
7322
  }
7323
+ var EFFECTIVE_MIN_COMPRESSIBLE_TOKENS = 1250;
7324
+ function resolveEffectiveFloor(config) {
7325
+ const minChars = config.compress?.minCompressRange ?? 5e3;
7326
+ return minChars > 0 ? Math.floor(minChars / 4) : 0;
7327
+ }
7307
7328
  function filterRecommendedRanges(compressible, _protectedRanges, options) {
7308
7329
  const { logger } = options;
7309
7330
  const log = logger?.debug.bind(logger);
@@ -7311,12 +7332,22 @@ function filterRecommendedRanges(compressible, _protectedRanges, options) {
7311
7332
  log?.("filterRecommendedRanges: no compressible ranges, returning empty");
7312
7333
  return [];
7313
7334
  }
7314
- const result = compressible.map(
7315
- (r, i) => i === compressible.length - 1 ? { ...r, dangerous: true } : r
7335
+ const floor = options.minEffectiveTokens ?? EFFECTIVE_MIN_COMPRESSIBLE_TOKENS;
7336
+ const kept = compressible.filter((r) => {
7337
+ const effective = r.effectiveTokens ?? r.tokens;
7338
+ return effective > 0 && effective >= floor;
7339
+ });
7340
+ const result = kept.map(
7341
+ (r, i) => i === kept.length - 1 ? { ...r, dangerous: true } : r
7316
7342
  );
7317
- log?.("filterRecommendedRanges: passthrough (last segment marked dangerous)", {
7343
+ log?.("filterRecommendedRanges: effective-token floor applied", {
7318
7344
  inputRanges: compressible.length,
7319
- outputRanges: result.length
7345
+ outputRanges: result.length,
7346
+ floor,
7347
+ dropped: compressible.filter((r) => {
7348
+ const effective = r.effectiveTokens ?? r.tokens;
7349
+ return effective <= 0 || effective < floor;
7350
+ }).map((r) => `${r.startRef}\u2013${r.endRef} (${r.effectiveTokens ?? r.tokens} eff tokens)`)
7320
7351
  });
7321
7352
  return result;
7322
7353
  }
@@ -7325,8 +7356,10 @@ function formatCompressibleRanges(ranges, protectedRanges) {
7325
7356
  if (!protectedRanges || protectedRanges.length === 0) {
7326
7357
  if (ranges.length === 0) return "";
7327
7358
  const lines2 = ranges.map((r) => {
7359
+ const eff = r.effectiveTokens ?? r.tokens;
7360
+ const size = eff < r.tokens ? `${fmt(eff)} effective of ${fmt(r.tokens)}` : fmt(r.tokens);
7328
7361
  const suffix = r.dangerous ? " \u26A0\uFE0F NOT recommended unless you are certain. If you MUST compress this, pass `dangerous: true`." : "";
7329
- return ` ${r.startRef}\u2013${r.endRef} ${r.count} msgs ${fmt(r.tokens)} [tool ${r.toolPct}% | text ${r.textPct}%]${suffix}`;
7362
+ return ` ${r.startRef}\u2013${r.endRef} ${r.count} msgs ${size} [tool ${r.toolPct}% | text ${r.textPct}%]${suffix}`;
7330
7363
  });
7331
7364
  return `Compressible ranges (oldest first):
7332
7365
  ${lines2.join("\n")}`;
@@ -7342,7 +7375,7 @@ ${lines2.join("\n")}`;
7342
7375
  tokens: r.tokens,
7343
7376
  toolPct: r.toolPct,
7344
7377
  textPct: r.textPct,
7345
- compressibleTokens: r.tokens,
7378
+ compressibleTokens: r.effectiveTokens ?? r.tokens,
7346
7379
  compressibleCount: r.count,
7347
7380
  protectedTokens: 0,
7348
7381
  protectedCount: 0,
@@ -7631,14 +7664,18 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
7631
7664
  const recommendedRanges = filterRecommendedRanges(
7632
7665
  unprotectedCompressible,
7633
7666
  contextRanges.protected,
7634
- { logger }
7667
+ { logger, minEffectiveTokens: resolveEffectiveFloor(config) }
7635
7668
  );
7636
7669
  const hasRecommendations = recommendedRanges.length > 0;
7637
7670
  const allProtected = contextRanges.compressible.length === 0 && contextRanges.protected.length > 0;
7638
7671
  const allInProtectedZone = protectedRefs.size > 0 && unprotectedCompressible.length === 0;
7639
- const nothingToCompress = allProtected || allInProtectedZone;
7640
- const shouldInjectNudge = nudgeAllowed && (!nothingToCompress || emergencyOverride);
7641
- let shouldInject = shouldInjectNudge;
7672
+ const allBelowMin = contextRanges.compressible.length > 0 && recommendedRanges.length === 0;
7673
+ const nothingToCompress = allProtected || allInProtectedZone || allBelowMin;
7674
+ const emergencyNoTargets = emergencyOverride && nothingToCompress;
7675
+ const noticeCadenceMet = state.nudges.lastNudgeShownTokens === void 0 || growthSinceBaseline !== void 0 && growthSinceBaseline >= growthFloor;
7676
+ const shouldInjectNudge = nudgeAllowed && !nothingToCompress;
7677
+ const shouldInjectNotice = emergencyNoTargets && noticeCadenceMet;
7678
+ let shouldInject = shouldInjectNudge || shouldInjectNotice;
7642
7679
  if (shouldInjectNudge) {
7643
7680
  applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
7644
7681
  }
@@ -7772,8 +7809,19 @@ ${formatCompressibleRanges(recommendedRanges, contextRanges.protected)}`;
7772
7809
  Use \`acp_status({scope:"uncompressed"})\` to re-fetch compressible ranges after compressing, or \`acp_status\` for compressed block details.`;
7773
7810
  appendToLastTextPart(suffixMessage, breakdown);
7774
7811
  }
7775
- if (effectiveTipsVariant === "maxLimit") {
7812
+ if (effectiveTipsVariant === "maxLimit" && !emergencyNoTargets) {
7776
7813
  tipsText = "\n\n\u26A0\uFE0F Context limit reached \u2014 compress now. Prioritize consumed tool outputs.\n\n" + HOW_TO_COMPRESS_RULES + '\n\n{ "topic": "...", "content": [{ "startId": "<ID>", "endId": "<ID>", "summary": "..." }] }\n\nOnly use IDs from visible messages above. Compress older work first.';
7814
+ } else if (shouldInjectNotice) {
7815
+ const emergencyPct = currentTokens !== void 0 && modelContextLimit !== void 0 && modelContextLimit > 0 ? Math.round(currentTokens / modelContextLimit * 100) : void 0;
7816
+ tipsText = `
7817
+
7818
+ \u{1F6A8} Context is critically full${emergencyPct !== void 0 ? ` (${emergencyPct}% of limit)` : ""} and there is nothing left that can be safely compressed.
7819
+ Do NOT retry compress on the same ranges \u2014 they will keep failing.
7820
+ You cannot execute user commands yourself. Act now via your reply/message tool:
7821
+ - Inform the user that context is full and compression is exhausted
7822
+ - Recommend they run /acp export (archives compression summaries to a file), then /compact or start a new session
7823
+ - Alternatively, ask them to relax protected-tool / preserve-recent settings so compression becomes possible
7824
+ Then stop retrying and await the user's response.`;
7777
7825
  }
7778
7826
  state.nudges.lastNudgeShownTokens = currentTokens;
7779
7827
  {
@@ -9026,7 +9074,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
9026
9074
  import { join as join3 } from "path";
9027
9075
  import { existsSync as existsSync3 } from "fs";
9028
9076
  import { homedir as homedir3 } from "os";
9029
- var LOG_VERSION = true ? "1.14.22-pr.327.45" : "dev";
9077
+ var LOG_VERSION = true ? "1.14.22-pr.330.48" : "dev";
9030
9078
  var Logger = class {
9031
9079
  logDir;
9032
9080
  enabled;
@@ -11243,12 +11291,14 @@ async function checkAutoUpdate(signal) {
11243
11291
  if (!packageDir) return { updated: false };
11244
11292
  const pkg = await readPackageJson(join6(packageDir, "package.json"));
11245
11293
  if (!pkg?.name || !pkg.version) return { updated: false };
11246
- const latest = await fetchLatestVersion(pkg.name, signal);
11294
+ const target = await updateTarget(packageDir, pkg.name);
11295
+ if (!target) return { updated: false };
11296
+ const tag = specUpdateTag(target.spec);
11297
+ if (!tag) return { updated: false };
11298
+ const latest = await fetchLatestVersion(pkg.name, tag, signal);
11247
11299
  if (!latest || !isVersionNewer(latest, pkg.version)) return { updated: false };
11248
- const removeDir = await updateRemoveDir(packageDir, pkg.name);
11249
- if (!removeDir) return { updated: false };
11250
11300
  try {
11251
- await rm(removeDir, { recursive: true, force: true });
11301
+ await rm(target.removeDir, { recursive: true, force: true });
11252
11302
  } catch {
11253
11303
  return {
11254
11304
  updated: false,
@@ -11270,7 +11320,7 @@ async function findPackageDir(name) {
11270
11320
  dir = parent;
11271
11321
  }
11272
11322
  }
11273
- async function updateRemoveDir(packageDir, name) {
11323
+ async function updateTarget(packageDir, name) {
11274
11324
  const packageParent = dirname4(packageDir);
11275
11325
  const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname4(packageParent) : packageParent;
11276
11326
  if (basename(nodeModulesDir) !== "node_modules") return void 0;
@@ -11278,7 +11328,7 @@ async function updateRemoveDir(packageDir, name) {
11278
11328
  const wrapperPkg = await readPackageJson(join6(wrapperDir, "package.json"));
11279
11329
  const spec = wrapperSpec(wrapperDir, name) ?? wrapperPkg?.dependencies?.[name];
11280
11330
  if (!spec || !isAutoUpdatableSpec(spec)) return void 0;
11281
- return wrapperDir;
11331
+ return { removeDir: wrapperDir, spec };
11282
11332
  }
11283
11333
  function wrapperSpec(wrapperDir, name) {
11284
11334
  if (name.startsWith("@")) {
@@ -11299,8 +11349,22 @@ function isAutoUpdatableSpec(spec) {
11299
11349
  if (/^[~^]/.test(value)) return true;
11300
11350
  if (/^(?:>=|>|<=|<)/.test(value)) return true;
11301
11351
  if (/\s+(?:\|\||-|[<>=])\s+/.test(value)) return true;
11352
+ if (isDistTag(value)) return true;
11302
11353
  return false;
11303
11354
  }
11355
+ function specUpdateTag(spec) {
11356
+ const value = spec.trim();
11357
+ if (!isAutoUpdatableSpec(value)) return void 0;
11358
+ if (value === "*") return "latest";
11359
+ if (isDistTag(value)) return value;
11360
+ return "latest";
11361
+ }
11362
+ function isDistTag(value) {
11363
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)) return false;
11364
+ if (parseVersion(value)) return false;
11365
+ if (/\.x(\.|$)/i.test(value)) return false;
11366
+ return true;
11367
+ }
11304
11368
  async function readPackageJson(path) {
11305
11369
  try {
11306
11370
  const data = JSON.parse(await readFile2(path, "utf-8"));
@@ -11309,10 +11373,10 @@ async function readPackageJson(path) {
11309
11373
  return void 0;
11310
11374
  }
11311
11375
  }
11312
- async function fetchLatestVersion(name, signal) {
11376
+ async function fetchLatestVersion(name, tag, signal) {
11313
11377
  try {
11314
11378
  const response = await fetch(
11315
- `https://registry.npmjs.org/${encodeURIComponent(name)}/latest`,
11379
+ `https://registry.npmjs.org/${encodeURIComponent(name)}/${encodeURIComponent(tag)}`,
11316
11380
  {
11317
11381
  signal
11318
11382
  }