opencode-acp 1.14.22 → 1.14.23
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 +4 -3
- package/README.zh-CN.md +3 -3
- package/dist/index.js +88 -30
- package/dist/index.js.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +46 -6
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/state/model-limits.d.ts +2 -2
- package/dist/lib/update.d.ts +15 -0
- package/dist/lib/update.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,8 +123,8 @@ stateDiagram-v2
|
|
|
123
123
|
- **T1** fires when raw context exceeds the configured limit. The model sees
|
|
124
124
|
compressible ranges and writes a detailed summary preserving file paths,
|
|
125
125
|
signatures, decisions, and rationale.
|
|
126
|
-
- **T2** fires when T1 summary tokens reach `nudgeGrowthTokens` (default
|
|
127
|
-
|
|
126
|
+
- **T2** fires when T1 summary tokens reach `nudgeGrowthTokens` (fixed default
|
|
127
|
+
50000). The model distills old T1 blocks — keeping decisions and
|
|
128
128
|
outcomes, dropping verbose process details.
|
|
129
129
|
- **T3** fires when T2 summary tokens reach the same threshold. The model
|
|
130
130
|
condenses to bare facts (shipped releases, key bugs, architecture decisions).
|
|
@@ -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
|
|
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
|
@@ -100,7 +100,7 @@ stateDiagram-v2
|
|
|
100
100
|
**触发机制:**
|
|
101
101
|
|
|
102
102
|
- **T1** 在原始上下文超过配置限制时触发。模型看到可压缩范围,编写详细摘要,保留文件路径、函数签名、决策和理由。
|
|
103
|
-
- **T2** 在 T1 摘要 token 达到 `nudgeGrowthTokens
|
|
103
|
+
- **T2** 在 T1 摘要 token 达到 `nudgeGrowthTokens`(固定默认 50000)时触发。模型蒸馏旧的 T1 块 — 保留决策和结果,丢弃冗长的过程细节。
|
|
104
104
|
- **T3** 在 T2 摘要 token 达到同样阈值时触发。模型浓缩为纯事实(已发布的版本、关键 bug、架构决策)。
|
|
105
105
|
|
|
106
106
|
每层有**独立的节奏计数器** — T2 触发不阻塞 T3。T1 通过 `!shouldInject` 守卫获得优先级:如果 T1 触发了,T2/T3 等到下一轮。这确保原始上下文压缩优先发生(影响最大)。
|
|
@@ -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
|
-
//
|
|
237
|
-
//
|
|
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
|
@@ -6953,13 +6953,7 @@ function computeShouldNudge2(params) {
|
|
|
6953
6953
|
}
|
|
6954
6954
|
return policy.computeShouldNudge(params);
|
|
6955
6955
|
}
|
|
6956
|
-
|
|
6957
|
-
const policy = getDefaultTriggerPolicy();
|
|
6958
|
-
if (!policy) {
|
|
6959
|
-
return 6e3;
|
|
6960
|
-
}
|
|
6961
|
-
return policy.resolveAdaptiveNudgeGrowth(modelContextLimit);
|
|
6962
|
-
}
|
|
6956
|
+
var DEFAULT_NUDGE_GROWTH_TOKENS = 5e4;
|
|
6963
6957
|
function addAnchor(anchorMessageIds, anchorMessageId, anchorMessageIndex, messages, interval) {
|
|
6964
6958
|
if (anchorMessageIndex < 0) {
|
|
6965
6959
|
return false;
|
|
@@ -7192,7 +7186,9 @@ function refNum(ref) {
|
|
|
7192
7186
|
function buildCompressibleRanges(messages, state, protectedTools = [], protectedFilePatterns = [], protectedZoneRefs) {
|
|
7193
7187
|
const msgInfo = [];
|
|
7194
7188
|
const protectedMsgInfo = [];
|
|
7195
|
-
|
|
7189
|
+
const lastUserRefIdx = [];
|
|
7190
|
+
for (let mi = 0; mi < messages.length; mi++) {
|
|
7191
|
+
const msg = messages[mi];
|
|
7196
7192
|
if (isSyntheticMessage(msg)) continue;
|
|
7197
7193
|
const ref = state.messageIds.byRawId.get(msg.info.id);
|
|
7198
7194
|
if (!ref) continue;
|
|
@@ -7227,15 +7223,27 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7227
7223
|
}
|
|
7228
7224
|
let tokens = 0;
|
|
7229
7225
|
let isTool = false;
|
|
7226
|
+
let hasMeaningfulPart = false;
|
|
7230
7227
|
for (const part of msg.parts || []) {
|
|
7231
7228
|
if (part.type === "text" && typeof part.text === "string") {
|
|
7232
7229
|
tokens += Math.round(part.text.length / 4);
|
|
7230
|
+
if (part.text.trim().length > 0) hasMeaningfulPart = true;
|
|
7233
7231
|
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
7234
7232
|
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
7235
7233
|
isTool = true;
|
|
7234
|
+
hasMeaningfulPart = true;
|
|
7236
7235
|
}
|
|
7237
7236
|
}
|
|
7238
|
-
|
|
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;
|
|
7239
7247
|
}
|
|
7240
7248
|
const groups = [];
|
|
7241
7249
|
let cur = null;
|
|
@@ -7261,6 +7269,7 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7261
7269
|
endRef: info.ref,
|
|
7262
7270
|
count: 1,
|
|
7263
7271
|
tokens: info.tokens,
|
|
7272
|
+
effectiveTokens: info.effectiveTokens,
|
|
7264
7273
|
toolPct: info.isTool ? 100 : 0,
|
|
7265
7274
|
textPct: info.isTool ? 0 : 100
|
|
7266
7275
|
};
|
|
@@ -7268,6 +7277,7 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7268
7277
|
cur.endRef = info.ref;
|
|
7269
7278
|
cur.count++;
|
|
7270
7279
|
cur.tokens += info.tokens;
|
|
7280
|
+
cur.effectiveTokens += info.effectiveTokens;
|
|
7271
7281
|
if (info.isTool) {
|
|
7272
7282
|
cur.toolPct = Math.round((cur.toolPct * (cur.count - 1) + 100) / cur.count);
|
|
7273
7283
|
} else {
|
|
@@ -7310,6 +7320,11 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7310
7320
|
protected: protectedGroups
|
|
7311
7321
|
};
|
|
7312
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
|
+
}
|
|
7313
7328
|
function filterRecommendedRanges(compressible, _protectedRanges, options) {
|
|
7314
7329
|
const { logger } = options;
|
|
7315
7330
|
const log = logger?.debug.bind(logger);
|
|
@@ -7317,12 +7332,22 @@ function filterRecommendedRanges(compressible, _protectedRanges, options) {
|
|
|
7317
7332
|
log?.("filterRecommendedRanges: no compressible ranges, returning empty");
|
|
7318
7333
|
return [];
|
|
7319
7334
|
}
|
|
7320
|
-
const
|
|
7321
|
-
|
|
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
|
|
7322
7342
|
);
|
|
7323
|
-
log?.("filterRecommendedRanges:
|
|
7343
|
+
log?.("filterRecommendedRanges: effective-token floor applied", {
|
|
7324
7344
|
inputRanges: compressible.length,
|
|
7325
|
-
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)`)
|
|
7326
7351
|
});
|
|
7327
7352
|
return result;
|
|
7328
7353
|
}
|
|
@@ -7331,8 +7356,10 @@ function formatCompressibleRanges(ranges, protectedRanges) {
|
|
|
7331
7356
|
if (!protectedRanges || protectedRanges.length === 0) {
|
|
7332
7357
|
if (ranges.length === 0) return "";
|
|
7333
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);
|
|
7334
7361
|
const suffix = r.dangerous ? " \u26A0\uFE0F NOT recommended unless you are certain. If you MUST compress this, pass `dangerous: true`." : "";
|
|
7335
|
-
return ` ${r.startRef}\u2013${r.endRef} ${r.count} msgs ${
|
|
7362
|
+
return ` ${r.startRef}\u2013${r.endRef} ${r.count} msgs ${size} [tool ${r.toolPct}% | text ${r.textPct}%]${suffix}`;
|
|
7336
7363
|
});
|
|
7337
7364
|
return `Compressible ranges (oldest first):
|
|
7338
7365
|
${lines2.join("\n")}`;
|
|
@@ -7348,7 +7375,7 @@ ${lines2.join("\n")}`;
|
|
|
7348
7375
|
tokens: r.tokens,
|
|
7349
7376
|
toolPct: r.toolPct,
|
|
7350
7377
|
textPct: r.textPct,
|
|
7351
|
-
compressibleTokens: r.tokens,
|
|
7378
|
+
compressibleTokens: r.effectiveTokens ?? r.tokens,
|
|
7352
7379
|
compressibleCount: r.count,
|
|
7353
7380
|
protectedTokens: 0,
|
|
7354
7381
|
protectedCount: 0,
|
|
@@ -7588,7 +7615,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
7588
7615
|
}
|
|
7589
7616
|
}
|
|
7590
7617
|
const suffixMessage = createSuffixMessage(messages);
|
|
7591
|
-
const nudgeGrowthTokens = config.compress?.nudgeGrowthTokens ??
|
|
7618
|
+
const nudgeGrowthTokens = config.compress?.nudgeGrowthTokens ?? DEFAULT_NUDGE_GROWTH_TOKENS;
|
|
7592
7619
|
const growthFloor = Math.max(
|
|
7593
7620
|
config.compress?.minNudgeGrowthFloor ?? 5e3,
|
|
7594
7621
|
(config.compress?.minNudgeGrowthRatio ?? 0.45) * nudgeGrowthTokens
|
|
@@ -7637,14 +7664,18 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
7637
7664
|
const recommendedRanges = filterRecommendedRanges(
|
|
7638
7665
|
unprotectedCompressible,
|
|
7639
7666
|
contextRanges.protected,
|
|
7640
|
-
{ logger }
|
|
7667
|
+
{ logger, minEffectiveTokens: resolveEffectiveFloor(config) }
|
|
7641
7668
|
);
|
|
7642
7669
|
const hasRecommendations = recommendedRanges.length > 0;
|
|
7643
7670
|
const allProtected = contextRanges.compressible.length === 0 && contextRanges.protected.length > 0;
|
|
7644
7671
|
const allInProtectedZone = protectedRefs.size > 0 && unprotectedCompressible.length === 0;
|
|
7645
|
-
const
|
|
7646
|
-
const
|
|
7647
|
-
|
|
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;
|
|
7648
7679
|
if (shouldInjectNudge) {
|
|
7649
7680
|
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
7650
7681
|
}
|
|
@@ -7778,8 +7809,19 @@ ${formatCompressibleRanges(recommendedRanges, contextRanges.protected)}`;
|
|
|
7778
7809
|
Use \`acp_status({scope:"uncompressed"})\` to re-fetch compressible ranges after compressing, or \`acp_status\` for compressed block details.`;
|
|
7779
7810
|
appendToLastTextPart(suffixMessage, breakdown);
|
|
7780
7811
|
}
|
|
7781
|
-
if (effectiveTipsVariant === "maxLimit") {
|
|
7812
|
+
if (effectiveTipsVariant === "maxLimit" && !emergencyNoTargets) {
|
|
7782
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.`;
|
|
7783
7825
|
}
|
|
7784
7826
|
state.nudges.lastNudgeShownTokens = currentTokens;
|
|
7785
7827
|
{
|
|
@@ -9032,7 +9074,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
9032
9074
|
import { join as join3 } from "path";
|
|
9033
9075
|
import { existsSync as existsSync3 } from "fs";
|
|
9034
9076
|
import { homedir as homedir3 } from "os";
|
|
9035
|
-
var LOG_VERSION = true ? "1.14.
|
|
9077
|
+
var LOG_VERSION = true ? "1.14.23" : "dev";
|
|
9036
9078
|
var Logger = class {
|
|
9037
9079
|
logDir;
|
|
9038
9080
|
enabled;
|
|
@@ -11249,12 +11291,14 @@ async function checkAutoUpdate(signal) {
|
|
|
11249
11291
|
if (!packageDir) return { updated: false };
|
|
11250
11292
|
const pkg = await readPackageJson(join6(packageDir, "package.json"));
|
|
11251
11293
|
if (!pkg?.name || !pkg.version) return { updated: false };
|
|
11252
|
-
const
|
|
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);
|
|
11253
11299
|
if (!latest || !isVersionNewer(latest, pkg.version)) return { updated: false };
|
|
11254
|
-
const removeDir = await updateRemoveDir(packageDir, pkg.name);
|
|
11255
|
-
if (!removeDir) return { updated: false };
|
|
11256
11300
|
try {
|
|
11257
|
-
await rm(removeDir, { recursive: true, force: true });
|
|
11301
|
+
await rm(target.removeDir, { recursive: true, force: true });
|
|
11258
11302
|
} catch {
|
|
11259
11303
|
return {
|
|
11260
11304
|
updated: false,
|
|
@@ -11276,7 +11320,7 @@ async function findPackageDir(name) {
|
|
|
11276
11320
|
dir = parent;
|
|
11277
11321
|
}
|
|
11278
11322
|
}
|
|
11279
|
-
async function
|
|
11323
|
+
async function updateTarget(packageDir, name) {
|
|
11280
11324
|
const packageParent = dirname4(packageDir);
|
|
11281
11325
|
const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname4(packageParent) : packageParent;
|
|
11282
11326
|
if (basename(nodeModulesDir) !== "node_modules") return void 0;
|
|
@@ -11284,7 +11328,7 @@ async function updateRemoveDir(packageDir, name) {
|
|
|
11284
11328
|
const wrapperPkg = await readPackageJson(join6(wrapperDir, "package.json"));
|
|
11285
11329
|
const spec = wrapperSpec(wrapperDir, name) ?? wrapperPkg?.dependencies?.[name];
|
|
11286
11330
|
if (!spec || !isAutoUpdatableSpec(spec)) return void 0;
|
|
11287
|
-
return wrapperDir;
|
|
11331
|
+
return { removeDir: wrapperDir, spec };
|
|
11288
11332
|
}
|
|
11289
11333
|
function wrapperSpec(wrapperDir, name) {
|
|
11290
11334
|
if (name.startsWith("@")) {
|
|
@@ -11305,8 +11349,22 @@ function isAutoUpdatableSpec(spec) {
|
|
|
11305
11349
|
if (/^[~^]/.test(value)) return true;
|
|
11306
11350
|
if (/^(?:>=|>|<=|<)/.test(value)) return true;
|
|
11307
11351
|
if (/\s+(?:\|\||-|[<>=])\s+/.test(value)) return true;
|
|
11352
|
+
if (isDistTag(value)) return true;
|
|
11308
11353
|
return false;
|
|
11309
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
|
+
}
|
|
11310
11368
|
async function readPackageJson(path) {
|
|
11311
11369
|
try {
|
|
11312
11370
|
const data = JSON.parse(await readFile2(path, "utf-8"));
|
|
@@ -11315,10 +11373,10 @@ async function readPackageJson(path) {
|
|
|
11315
11373
|
return void 0;
|
|
11316
11374
|
}
|
|
11317
11375
|
}
|
|
11318
|
-
async function fetchLatestVersion(name, signal) {
|
|
11376
|
+
async function fetchLatestVersion(name, tag, signal) {
|
|
11319
11377
|
try {
|
|
11320
11378
|
const response = await fetch(
|
|
11321
|
-
`https://registry.npmjs.org/${encodeURIComponent(name)}
|
|
11379
|
+
`https://registry.npmjs.org/${encodeURIComponent(name)}/${encodeURIComponent(tag)}`,
|
|
11322
11380
|
{
|
|
11323
11381
|
signal
|
|
11324
11382
|
}
|