opencode-acp 1.14.22 → 1.14.23-pr.332.52
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 +9 -5
- package/README.zh-CN.md +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +226 -47
- package/dist/index.js.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +3 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +22 -5
- package/dist/lib/logger.d.ts.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 +17 -1
- 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,11 +278,15 @@ 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
|
-
//
|
|
285
|
+
// File log verbosity: "debug" | "info" | "warn" | "error" | "silent".
|
|
286
|
+
// Default "info" writes decision-level events (nudges, transforms, updates)
|
|
287
|
+
// to ~/.config/opencode/logs/acp/daily/<date>.log
|
|
288
|
+
"logLevel": "info",
|
|
289
|
+
// Enable full DEBUG logging + per-request snapshots (overrides logLevel)
|
|
286
290
|
"debug": false,
|
|
287
291
|
// Notification display: "off", "minimal", or "detailed"
|
|
288
292
|
"pruneNotification": "off",
|
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,11 +233,14 @@ 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
|
-
//
|
|
240
|
-
//
|
|
239
|
+
// 文件日志级别: "debug" | "info" | "warn" | "error" | "silent"
|
|
240
|
+
// 默认 "info":决策级事件(压缩提示、转换摘要、更新检查)落盘到
|
|
241
|
+
// ~/.config/opencode/logs/acp/daily/<日期>.log
|
|
242
|
+
"logLevel": "info",
|
|
243
|
+
// 启用完整 DEBUG 日志 + 按请求快照(优先于 logLevel)
|
|
241
244
|
"debug": false,
|
|
242
245
|
// Notification display: "off", "minimal", or "detailed"
|
|
243
246
|
"pruneNotification": "detailed",
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA2BjD,QAAA,MAAM,MAAM,EAAE,MAqJK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -868,6 +868,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
868
868
|
"enabled",
|
|
869
869
|
"autoUpdate",
|
|
870
870
|
"debug",
|
|
871
|
+
"logLevel",
|
|
871
872
|
"showUpdateToasts",
|
|
872
873
|
"allowSubAgents",
|
|
873
874
|
"pruneNotification",
|
|
@@ -1466,6 +1467,7 @@ var defaultConfig = {
|
|
|
1466
1467
|
enabled: true,
|
|
1467
1468
|
autoUpdate: true,
|
|
1468
1469
|
debug: false,
|
|
1470
|
+
logLevel: "info",
|
|
1469
1471
|
allowSubAgents: true,
|
|
1470
1472
|
pruneNotification: "off",
|
|
1471
1473
|
// [FIX #20] Default to toast — chat-mode notifications inject an empty
|
|
@@ -1727,6 +1729,7 @@ function mergeLayer(config, data) {
|
|
|
1727
1729
|
enabled: data.enabled ?? config.enabled,
|
|
1728
1730
|
autoUpdate: data.autoUpdate ?? config.autoUpdate,
|
|
1729
1731
|
debug: data.debug ?? config.debug,
|
|
1732
|
+
logLevel: data.logLevel ?? config.logLevel,
|
|
1730
1733
|
allowSubAgents: data.allowSubAgents ?? data.experimental?.allowSubAgents ?? config.allowSubAgents,
|
|
1731
1734
|
pruneNotification: data.pruneNotification ?? config.pruneNotification,
|
|
1732
1735
|
pruneNotificationType: data.pruneNotificationType ?? config.pruneNotificationType,
|
|
@@ -6953,13 +6956,7 @@ function computeShouldNudge2(params) {
|
|
|
6953
6956
|
}
|
|
6954
6957
|
return policy.computeShouldNudge(params);
|
|
6955
6958
|
}
|
|
6956
|
-
|
|
6957
|
-
const policy = getDefaultTriggerPolicy();
|
|
6958
|
-
if (!policy) {
|
|
6959
|
-
return 6e3;
|
|
6960
|
-
}
|
|
6961
|
-
return policy.resolveAdaptiveNudgeGrowth(modelContextLimit);
|
|
6962
|
-
}
|
|
6959
|
+
var DEFAULT_NUDGE_GROWTH_TOKENS = 5e4;
|
|
6963
6960
|
function addAnchor(anchorMessageIds, anchorMessageId, anchorMessageIndex, messages, interval) {
|
|
6964
6961
|
if (anchorMessageIndex < 0) {
|
|
6965
6962
|
return false;
|
|
@@ -7192,7 +7189,9 @@ function refNum(ref) {
|
|
|
7192
7189
|
function buildCompressibleRanges(messages, state, protectedTools = [], protectedFilePatterns = [], protectedZoneRefs) {
|
|
7193
7190
|
const msgInfo = [];
|
|
7194
7191
|
const protectedMsgInfo = [];
|
|
7195
|
-
|
|
7192
|
+
const lastUserRefIdx = [];
|
|
7193
|
+
for (let mi = 0; mi < messages.length; mi++) {
|
|
7194
|
+
const msg = messages[mi];
|
|
7196
7195
|
if (isSyntheticMessage(msg)) continue;
|
|
7197
7196
|
const ref = state.messageIds.byRawId.get(msg.info.id);
|
|
7198
7197
|
if (!ref) continue;
|
|
@@ -7227,15 +7226,27 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7227
7226
|
}
|
|
7228
7227
|
let tokens = 0;
|
|
7229
7228
|
let isTool = false;
|
|
7229
|
+
let hasMeaningfulPart = false;
|
|
7230
7230
|
for (const part of msg.parts || []) {
|
|
7231
7231
|
if (part.type === "text" && typeof part.text === "string") {
|
|
7232
7232
|
tokens += Math.round(part.text.length / 4);
|
|
7233
|
+
if (part.text.trim().length > 0) hasMeaningfulPart = true;
|
|
7233
7234
|
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
7234
7235
|
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
7235
7236
|
isTool = true;
|
|
7237
|
+
hasMeaningfulPart = true;
|
|
7236
7238
|
}
|
|
7237
7239
|
}
|
|
7238
|
-
|
|
7240
|
+
if (msg.info.role === "user" && !isIgnoredUserMessage(msg)) {
|
|
7241
|
+
lastUserRefIdx.length = 0;
|
|
7242
|
+
lastUserRefIdx.push(msgInfo.length);
|
|
7243
|
+
}
|
|
7244
|
+
msgInfo.push({ ref, refNum: rn, tokens, effectiveTokens: 0, meaningful: hasMeaningfulPart, isTool, isUser: msg.info.role === "user" });
|
|
7245
|
+
}
|
|
7246
|
+
const lastUserIdx = lastUserRefIdx.length > 0 ? lastUserRefIdx[0] : -1;
|
|
7247
|
+
for (let i = 0; i < msgInfo.length; i++) {
|
|
7248
|
+
const info = msgInfo[i];
|
|
7249
|
+
info.effectiveTokens = i !== lastUserIdx && info.meaningful ? info.tokens : 0;
|
|
7239
7250
|
}
|
|
7240
7251
|
const groups = [];
|
|
7241
7252
|
let cur = null;
|
|
@@ -7261,6 +7272,7 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7261
7272
|
endRef: info.ref,
|
|
7262
7273
|
count: 1,
|
|
7263
7274
|
tokens: info.tokens,
|
|
7275
|
+
effectiveTokens: info.effectiveTokens,
|
|
7264
7276
|
toolPct: info.isTool ? 100 : 0,
|
|
7265
7277
|
textPct: info.isTool ? 0 : 100
|
|
7266
7278
|
};
|
|
@@ -7268,6 +7280,7 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7268
7280
|
cur.endRef = info.ref;
|
|
7269
7281
|
cur.count++;
|
|
7270
7282
|
cur.tokens += info.tokens;
|
|
7283
|
+
cur.effectiveTokens += info.effectiveTokens;
|
|
7271
7284
|
if (info.isTool) {
|
|
7272
7285
|
cur.toolPct = Math.round((cur.toolPct * (cur.count - 1) + 100) / cur.count);
|
|
7273
7286
|
} else {
|
|
@@ -7310,6 +7323,11 @@ function buildCompressibleRanges(messages, state, protectedTools = [], protected
|
|
|
7310
7323
|
protected: protectedGroups
|
|
7311
7324
|
};
|
|
7312
7325
|
}
|
|
7326
|
+
var EFFECTIVE_MIN_COMPRESSIBLE_TOKENS = 1250;
|
|
7327
|
+
function resolveEffectiveFloor(config) {
|
|
7328
|
+
const minChars = config.compress?.minCompressRange ?? 5e3;
|
|
7329
|
+
return minChars > 0 ? Math.floor(minChars / 4) : 0;
|
|
7330
|
+
}
|
|
7313
7331
|
function filterRecommendedRanges(compressible, _protectedRanges, options) {
|
|
7314
7332
|
const { logger } = options;
|
|
7315
7333
|
const log = logger?.debug.bind(logger);
|
|
@@ -7317,12 +7335,22 @@ function filterRecommendedRanges(compressible, _protectedRanges, options) {
|
|
|
7317
7335
|
log?.("filterRecommendedRanges: no compressible ranges, returning empty");
|
|
7318
7336
|
return [];
|
|
7319
7337
|
}
|
|
7320
|
-
const
|
|
7321
|
-
|
|
7338
|
+
const floor = options.minEffectiveTokens ?? EFFECTIVE_MIN_COMPRESSIBLE_TOKENS;
|
|
7339
|
+
const kept = compressible.filter((r) => {
|
|
7340
|
+
const effective = r.effectiveTokens ?? r.tokens;
|
|
7341
|
+
return effective > 0 && effective >= floor;
|
|
7342
|
+
});
|
|
7343
|
+
const result = kept.map(
|
|
7344
|
+
(r, i) => i === kept.length - 1 ? { ...r, dangerous: true } : r
|
|
7322
7345
|
);
|
|
7323
|
-
log?.("filterRecommendedRanges:
|
|
7346
|
+
log?.("filterRecommendedRanges: effective-token floor applied", {
|
|
7324
7347
|
inputRanges: compressible.length,
|
|
7325
|
-
outputRanges: result.length
|
|
7348
|
+
outputRanges: result.length,
|
|
7349
|
+
floor,
|
|
7350
|
+
dropped: compressible.filter((r) => {
|
|
7351
|
+
const effective = r.effectiveTokens ?? r.tokens;
|
|
7352
|
+
return effective <= 0 || effective < floor;
|
|
7353
|
+
}).map((r) => `${r.startRef}\u2013${r.endRef} (${r.effectiveTokens ?? r.tokens} eff tokens)`)
|
|
7326
7354
|
});
|
|
7327
7355
|
return result;
|
|
7328
7356
|
}
|
|
@@ -7331,8 +7359,10 @@ function formatCompressibleRanges(ranges, protectedRanges) {
|
|
|
7331
7359
|
if (!protectedRanges || protectedRanges.length === 0) {
|
|
7332
7360
|
if (ranges.length === 0) return "";
|
|
7333
7361
|
const lines2 = ranges.map((r) => {
|
|
7362
|
+
const eff = r.effectiveTokens ?? r.tokens;
|
|
7363
|
+
const size = eff < r.tokens ? `${fmt(eff)} effective of ${fmt(r.tokens)}` : fmt(r.tokens);
|
|
7334
7364
|
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 ${
|
|
7365
|
+
return ` ${r.startRef}\u2013${r.endRef} ${r.count} msgs ${size} [tool ${r.toolPct}% | text ${r.textPct}%]${suffix}`;
|
|
7336
7366
|
});
|
|
7337
7367
|
return `Compressible ranges (oldest first):
|
|
7338
7368
|
${lines2.join("\n")}`;
|
|
@@ -7348,7 +7378,7 @@ ${lines2.join("\n")}`;
|
|
|
7348
7378
|
tokens: r.tokens,
|
|
7349
7379
|
toolPct: r.toolPct,
|
|
7350
7380
|
textPct: r.textPct,
|
|
7351
|
-
compressibleTokens: r.tokens,
|
|
7381
|
+
compressibleTokens: r.effectiveTokens ?? r.tokens,
|
|
7352
7382
|
compressibleCount: r.count,
|
|
7353
7383
|
protectedTokens: 0,
|
|
7354
7384
|
protectedCount: 0,
|
|
@@ -7588,7 +7618,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
7588
7618
|
}
|
|
7589
7619
|
}
|
|
7590
7620
|
const suffixMessage = createSuffixMessage(messages);
|
|
7591
|
-
const nudgeGrowthTokens = config.compress?.nudgeGrowthTokens ??
|
|
7621
|
+
const nudgeGrowthTokens = config.compress?.nudgeGrowthTokens ?? DEFAULT_NUDGE_GROWTH_TOKENS;
|
|
7592
7622
|
const growthFloor = Math.max(
|
|
7593
7623
|
config.compress?.minNudgeGrowthFloor ?? 5e3,
|
|
7594
7624
|
(config.compress?.minNudgeGrowthRatio ?? 0.45) * nudgeGrowthTokens
|
|
@@ -7637,14 +7667,18 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
7637
7667
|
const recommendedRanges = filterRecommendedRanges(
|
|
7638
7668
|
unprotectedCompressible,
|
|
7639
7669
|
contextRanges.protected,
|
|
7640
|
-
{ logger }
|
|
7670
|
+
{ logger, minEffectiveTokens: resolveEffectiveFloor(config) }
|
|
7641
7671
|
);
|
|
7642
7672
|
const hasRecommendations = recommendedRanges.length > 0;
|
|
7643
7673
|
const allProtected = contextRanges.compressible.length === 0 && contextRanges.protected.length > 0;
|
|
7644
7674
|
const allInProtectedZone = protectedRefs.size > 0 && unprotectedCompressible.length === 0;
|
|
7645
|
-
const
|
|
7646
|
-
const
|
|
7647
|
-
|
|
7675
|
+
const allBelowMin = contextRanges.compressible.length > 0 && recommendedRanges.length === 0;
|
|
7676
|
+
const nothingToCompress = allProtected || allInProtectedZone || allBelowMin;
|
|
7677
|
+
const emergencyNoTargets = emergencyOverride && nothingToCompress;
|
|
7678
|
+
const noticeCadenceMet = state.nudges.lastNudgeShownTokens === void 0 || growthSinceBaseline !== void 0 && growthSinceBaseline >= growthFloor;
|
|
7679
|
+
const shouldInjectNudge = nudgeAllowed && !nothingToCompress;
|
|
7680
|
+
const shouldInjectNotice = emergencyNoTargets && noticeCadenceMet;
|
|
7681
|
+
let shouldInject = shouldInjectNudge || shouldInjectNotice;
|
|
7648
7682
|
if (shouldInjectNudge) {
|
|
7649
7683
|
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
7650
7684
|
}
|
|
@@ -7726,6 +7760,13 @@ Multiple entries create separate blocks: \`content: [{ startId: "b${firstBlock.b
|
|
|
7726
7760
|
${rules}`;
|
|
7727
7761
|
appendToLastTextPart(suffixMessage, tierText);
|
|
7728
7762
|
shouldInject = true;
|
|
7763
|
+
logger.info(`Tier ${tc.triggerTier} trigger nudge injected`, {
|
|
7764
|
+
session: state.sessionId,
|
|
7765
|
+
action,
|
|
7766
|
+
targetTier: tc.targetTier,
|
|
7767
|
+
blocks: candidates.length,
|
|
7768
|
+
candidateTokens
|
|
7769
|
+
});
|
|
7729
7770
|
if (tc.triggerTier === 2) {
|
|
7730
7771
|
state.nudges.lastTier2NudgeTokens = currentTokens;
|
|
7731
7772
|
} else {
|
|
@@ -7735,6 +7776,32 @@ ${rules}`;
|
|
|
7735
7776
|
}
|
|
7736
7777
|
}
|
|
7737
7778
|
state.nudges.shouldInjectThisTurn = shouldInject;
|
|
7779
|
+
const usagePct = currentTokens !== void 0 && modelContextLimit ? `${(currentTokens / modelContextLimit * 100).toFixed(1)}%` : void 0;
|
|
7780
|
+
if (shouldInjectNudge) {
|
|
7781
|
+
logger.info("Compression nudge injected", {
|
|
7782
|
+
session: state.sessionId,
|
|
7783
|
+
trigger: emergencyOverride ? "emergency" : "growth",
|
|
7784
|
+
currentTokens,
|
|
7785
|
+
usagePct,
|
|
7786
|
+
growthSinceBaseline,
|
|
7787
|
+
growthFloor,
|
|
7788
|
+
recommendedRanges: recommendedRanges.length
|
|
7789
|
+
});
|
|
7790
|
+
} else if (shouldInjectNotice) {
|
|
7791
|
+
logger.info("Emergency /compact notice injected (no compressible targets)", {
|
|
7792
|
+
session: state.sessionId,
|
|
7793
|
+
currentTokens,
|
|
7794
|
+
usagePct
|
|
7795
|
+
});
|
|
7796
|
+
} else if (nudgeAllowed && nothingToCompress) {
|
|
7797
|
+
logger.info("Nudge suppressed: nothing to compress", {
|
|
7798
|
+
session: state.sessionId,
|
|
7799
|
+
reason: allProtected ? "all_protected" : allInProtectedZone ? "in_protected_zone" : "below_effective_floor",
|
|
7800
|
+
currentTokens,
|
|
7801
|
+
usagePct,
|
|
7802
|
+
compressibleRanges: contextRanges.compressible.length
|
|
7803
|
+
});
|
|
7804
|
+
}
|
|
7738
7805
|
if (shouldInject && config.debug && contextRanges.compressible.length > 0) {
|
|
7739
7806
|
const compressible = contextRanges.compressible;
|
|
7740
7807
|
const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
@@ -7778,8 +7845,19 @@ ${formatCompressibleRanges(recommendedRanges, contextRanges.protected)}`;
|
|
|
7778
7845
|
Use \`acp_status({scope:"uncompressed"})\` to re-fetch compressible ranges after compressing, or \`acp_status\` for compressed block details.`;
|
|
7779
7846
|
appendToLastTextPart(suffixMessage, breakdown);
|
|
7780
7847
|
}
|
|
7781
|
-
if (effectiveTipsVariant === "maxLimit") {
|
|
7848
|
+
if (effectiveTipsVariant === "maxLimit" && !emergencyNoTargets) {
|
|
7782
7849
|
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.';
|
|
7850
|
+
} else if (shouldInjectNotice) {
|
|
7851
|
+
const emergencyPct = currentTokens !== void 0 && modelContextLimit !== void 0 && modelContextLimit > 0 ? Math.round(currentTokens / modelContextLimit * 100) : void 0;
|
|
7852
|
+
tipsText = `
|
|
7853
|
+
|
|
7854
|
+
\u{1F6A8} Context is critically full${emergencyPct !== void 0 ? ` (${emergencyPct}% of limit)` : ""} and there is nothing left that can be safely compressed.
|
|
7855
|
+
Do NOT retry compress on the same ranges \u2014 they will keep failing.
|
|
7856
|
+
You cannot execute user commands yourself. Act now via your reply/message tool:
|
|
7857
|
+
- Inform the user that context is full and compression is exhausted
|
|
7858
|
+
- Recommend they run /acp export (archives compression summaries to a file), then /compact or start a new session
|
|
7859
|
+
- Alternatively, ask them to relax protected-tool / preserve-recent settings so compression becomes possible
|
|
7860
|
+
Then stop retrying and await the user's response.`;
|
|
7783
7861
|
}
|
|
7784
7862
|
state.nudges.lastNudgeShownTokens = currentTokens;
|
|
7785
7863
|
{
|
|
@@ -9032,15 +9110,35 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
9032
9110
|
import { join as join3 } from "path";
|
|
9033
9111
|
import { existsSync as existsSync3 } from "fs";
|
|
9034
9112
|
import { homedir as homedir3 } from "os";
|
|
9035
|
-
var LOG_VERSION = true ? "1.14.
|
|
9113
|
+
var LOG_VERSION = true ? "1.14.23-pr.332.52" : "dev";
|
|
9114
|
+
var LEVEL_RANK = {
|
|
9115
|
+
debug: 10,
|
|
9116
|
+
info: 20,
|
|
9117
|
+
warn: 30,
|
|
9118
|
+
error: 40,
|
|
9119
|
+
silent: 99
|
|
9120
|
+
};
|
|
9036
9121
|
var Logger = class {
|
|
9037
9122
|
logDir;
|
|
9038
|
-
enabled
|
|
9039
|
-
|
|
9040
|
-
|
|
9123
|
+
/** Resolved verbosity; replaces the old boolean `enabled` flag. */
|
|
9124
|
+
level;
|
|
9125
|
+
/**
|
|
9126
|
+
* @param enabled legacy boolean toggle — `true` maps to `debug`,
|
|
9127
|
+
* `false` maps to `warn` (errors + warnings only).
|
|
9128
|
+
* @param level explicit verbosity; when given it wins over `enabled`.
|
|
9129
|
+
*/
|
|
9130
|
+
constructor(enabled, level) {
|
|
9131
|
+
this.level = level ?? (enabled ? "debug" : "warn");
|
|
9041
9132
|
const configHome = process.env.XDG_CONFIG_HOME || join3(homedir3(), ".config");
|
|
9042
9133
|
this.logDir = join3(configHome, "opencode", "logs", "acp");
|
|
9043
9134
|
}
|
|
9135
|
+
/** Back-compat view of the old flag: true only at full debug verbosity. */
|
|
9136
|
+
get enabled() {
|
|
9137
|
+
return this.level === "debug";
|
|
9138
|
+
}
|
|
9139
|
+
shouldWrite(level) {
|
|
9140
|
+
return LEVEL_RANK[level] >= LEVEL_RANK[this.level];
|
|
9141
|
+
}
|
|
9044
9142
|
async ensureLogDir() {
|
|
9045
9143
|
if (!existsSync3(this.logDir)) {
|
|
9046
9144
|
await mkdir2(this.logDir, { recursive: true });
|
|
@@ -9087,7 +9185,7 @@ var Logger = class {
|
|
|
9087
9185
|
}
|
|
9088
9186
|
}
|
|
9089
9187
|
async write(level, component, message, data) {
|
|
9090
|
-
if (!this.
|
|
9188
|
+
if (!this.shouldWrite(level.toLowerCase())) return;
|
|
9091
9189
|
try {
|
|
9092
9190
|
await this.ensureLogDir();
|
|
9093
9191
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9104,12 +9202,10 @@ var Logger = class {
|
|
|
9104
9202
|
}
|
|
9105
9203
|
}
|
|
9106
9204
|
info(message, data) {
|
|
9107
|
-
if (!this.enabled) return;
|
|
9108
9205
|
const component = this.getCallerFile(2);
|
|
9109
9206
|
return this.write("INFO", component, message, data);
|
|
9110
9207
|
}
|
|
9111
9208
|
debug(message, data) {
|
|
9112
|
-
if (!this.enabled) return;
|
|
9113
9209
|
const component = this.getCallerFile(2);
|
|
9114
9210
|
return this.write("DEBUG", component, message, data);
|
|
9115
9211
|
}
|
|
@@ -9207,7 +9303,7 @@ var Logger = class {
|
|
|
9207
9303
|
});
|
|
9208
9304
|
}
|
|
9209
9305
|
async saveContext(sessionId, messages) {
|
|
9210
|
-
if (
|
|
9306
|
+
if (this.level !== "debug") return;
|
|
9211
9307
|
try {
|
|
9212
9308
|
const contextDir = join3(this.logDir, "context", sessionId);
|
|
9213
9309
|
if (!existsSync3(contextDir)) {
|
|
@@ -10968,6 +11064,7 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
10968
11064
|
requestModel?.providerID,
|
|
10969
11065
|
requestModel?.modelID
|
|
10970
11066
|
);
|
|
11067
|
+
const prevModelID = state.modelID;
|
|
10971
11068
|
if (requestModelLimit !== void 0) {
|
|
10972
11069
|
state.modelContextLimit = requestModelLimit;
|
|
10973
11070
|
state.modelProviderID = requestModel?.providerID;
|
|
@@ -10987,6 +11084,14 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
|
|
|
10987
11084
|
state.modelProviderID = requestModel.providerID;
|
|
10988
11085
|
state.modelID = requestModel.modelID;
|
|
10989
11086
|
}
|
|
11087
|
+
if (requestModel?.modelID && requestModel.modelID !== prevModelID) {
|
|
11088
|
+
logger.info("Model switched mid-session", {
|
|
11089
|
+
session: state.sessionId,
|
|
11090
|
+
from: prevModelID,
|
|
11091
|
+
to: requestModel.modelID,
|
|
11092
|
+
contextLimit: state.modelContextLimit
|
|
11093
|
+
});
|
|
11094
|
+
}
|
|
10990
11095
|
await updatePerTurnState(state, logger, messages);
|
|
10991
11096
|
}
|
|
10992
11097
|
syncCompressPermissionState(state, config, hostPermissions, output.messages);
|
|
@@ -11048,6 +11153,17 @@ ${text}`);
|
|
|
11048
11153
|
hideFailedCompressCalls(output.messages);
|
|
11049
11154
|
stripStaleMetadata(output.messages);
|
|
11050
11155
|
dropEmptyMessages(output.messages);
|
|
11156
|
+
const postTokens = getCurrentTokenUsage(state, output.messages);
|
|
11157
|
+
logger.info("Chat transform complete", {
|
|
11158
|
+
session: state.sessionId,
|
|
11159
|
+
model: state.modelID,
|
|
11160
|
+
messages: output.messages.length,
|
|
11161
|
+
prePruneTokens,
|
|
11162
|
+
postTokens,
|
|
11163
|
+
contextLimit: state.modelContextLimit,
|
|
11164
|
+
usagePct: postTokens !== void 0 && state.modelContextLimit ? `${(postTokens / state.modelContextLimit * 100).toFixed(1)}%` : void 0,
|
|
11165
|
+
nudged: state.nudges.shouldInjectThisTurn
|
|
11166
|
+
});
|
|
11051
11167
|
if (state.sessionId) {
|
|
11052
11168
|
await logger.saveContext(state.sessionId, output.messages);
|
|
11053
11169
|
}
|
|
@@ -11225,12 +11341,31 @@ import { readFile as readFile2, rm } from "fs/promises";
|
|
|
11225
11341
|
import { basename, dirname as dirname4, join as join6 } from "path";
|
|
11226
11342
|
import { fileURLToPath } from "url";
|
|
11227
11343
|
var PACKAGE_NAME = "opencode-acp";
|
|
11228
|
-
function startAutoUpdate(ctx, enabled) {
|
|
11229
|
-
if (!enabled)
|
|
11344
|
+
function startAutoUpdate(ctx, enabled, logger) {
|
|
11345
|
+
if (!enabled) {
|
|
11346
|
+
logger?.info("Auto-update disabled by config");
|
|
11347
|
+
return;
|
|
11348
|
+
}
|
|
11349
|
+
logger?.info("Auto-update check starting");
|
|
11230
11350
|
const controller = new AbortController();
|
|
11231
11351
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
11232
|
-
void checkAutoUpdate(controller.signal).then((result) => {
|
|
11233
|
-
if (!result.updated)
|
|
11352
|
+
void checkAutoUpdate(controller.signal, logger).then((result) => {
|
|
11353
|
+
if (!result.updated) {
|
|
11354
|
+
if ("error" in result) {
|
|
11355
|
+
logger?.warn("Auto-update failed", {
|
|
11356
|
+
error: result.error,
|
|
11357
|
+
name: result.name,
|
|
11358
|
+
current: result.current,
|
|
11359
|
+
latest: result.latest
|
|
11360
|
+
});
|
|
11361
|
+
}
|
|
11362
|
+
return;
|
|
11363
|
+
}
|
|
11364
|
+
logger?.info("Auto-update applied update", {
|
|
11365
|
+
name: result.name,
|
|
11366
|
+
from: result.current,
|
|
11367
|
+
to: result.latest
|
|
11368
|
+
});
|
|
11234
11369
|
setTimeout(() => {
|
|
11235
11370
|
ctx.client.tui.showToast({
|
|
11236
11371
|
body: {
|
|
@@ -11244,17 +11379,39 @@ function startAutoUpdate(ctx, enabled) {
|
|
|
11244
11379
|
}).catch(() => {
|
|
11245
11380
|
}).finally(() => clearTimeout(timeout));
|
|
11246
11381
|
}
|
|
11247
|
-
async function checkAutoUpdate(signal) {
|
|
11382
|
+
async function checkAutoUpdate(signal, logger) {
|
|
11248
11383
|
const packageDir = await findPackageDir(PACKAGE_NAME);
|
|
11249
|
-
if (!packageDir)
|
|
11384
|
+
if (!packageDir) {
|
|
11385
|
+
logger?.info("Auto-update skipped: package dir not found");
|
|
11386
|
+
return { updated: false };
|
|
11387
|
+
}
|
|
11250
11388
|
const pkg = await readPackageJson(join6(packageDir, "package.json"));
|
|
11251
|
-
if (!pkg?.name || !pkg.version)
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11389
|
+
if (!pkg?.name || !pkg.version) {
|
|
11390
|
+
logger?.info("Auto-update skipped: package.json unreadable", { packageDir });
|
|
11391
|
+
return { updated: false };
|
|
11392
|
+
}
|
|
11393
|
+
const target = await updateTarget(packageDir, pkg.name);
|
|
11394
|
+
if (!target) {
|
|
11395
|
+
logger?.info("Auto-update skipped: no update target", { name: pkg.name, version: pkg.version });
|
|
11396
|
+
return { updated: false };
|
|
11397
|
+
}
|
|
11398
|
+
const tag = specUpdateTag(target.spec);
|
|
11399
|
+
if (!tag) {
|
|
11400
|
+
logger?.info("Auto-update skipped: spec not auto-updatable", { spec: target.spec });
|
|
11401
|
+
return { updated: false };
|
|
11402
|
+
}
|
|
11403
|
+
const latest = await fetchLatestVersion(pkg.name, tag, signal);
|
|
11404
|
+
if (!latest) {
|
|
11405
|
+
logger?.info("Auto-update skipped: no version published for tag", { name: pkg.name, tag });
|
|
11406
|
+
return { updated: false };
|
|
11407
|
+
}
|
|
11408
|
+
if (!isVersionNewer(latest, pkg.version)) {
|
|
11409
|
+
logger?.info("Auto-update: already up to date", { name: pkg.name, tag, current: pkg.version, latest });
|
|
11410
|
+
return { updated: false };
|
|
11411
|
+
}
|
|
11412
|
+
logger?.info("Auto-update: newer version available", { name: pkg.name, tag, current: pkg.version, latest });
|
|
11256
11413
|
try {
|
|
11257
|
-
await rm(removeDir, { recursive: true, force: true });
|
|
11414
|
+
await rm(target.removeDir, { recursive: true, force: true });
|
|
11258
11415
|
} catch {
|
|
11259
11416
|
return {
|
|
11260
11417
|
updated: false,
|
|
@@ -11276,7 +11433,7 @@ async function findPackageDir(name) {
|
|
|
11276
11433
|
dir = parent;
|
|
11277
11434
|
}
|
|
11278
11435
|
}
|
|
11279
|
-
async function
|
|
11436
|
+
async function updateTarget(packageDir, name) {
|
|
11280
11437
|
const packageParent = dirname4(packageDir);
|
|
11281
11438
|
const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname4(packageParent) : packageParent;
|
|
11282
11439
|
if (basename(nodeModulesDir) !== "node_modules") return void 0;
|
|
@@ -11284,7 +11441,7 @@ async function updateRemoveDir(packageDir, name) {
|
|
|
11284
11441
|
const wrapperPkg = await readPackageJson(join6(wrapperDir, "package.json"));
|
|
11285
11442
|
const spec = wrapperSpec(wrapperDir, name) ?? wrapperPkg?.dependencies?.[name];
|
|
11286
11443
|
if (!spec || !isAutoUpdatableSpec(spec)) return void 0;
|
|
11287
|
-
return wrapperDir;
|
|
11444
|
+
return { removeDir: wrapperDir, spec };
|
|
11288
11445
|
}
|
|
11289
11446
|
function wrapperSpec(wrapperDir, name) {
|
|
11290
11447
|
if (name.startsWith("@")) {
|
|
@@ -11305,8 +11462,22 @@ function isAutoUpdatableSpec(spec) {
|
|
|
11305
11462
|
if (/^[~^]/.test(value)) return true;
|
|
11306
11463
|
if (/^(?:>=|>|<=|<)/.test(value)) return true;
|
|
11307
11464
|
if (/\s+(?:\|\||-|[<>=])\s+/.test(value)) return true;
|
|
11465
|
+
if (isDistTag(value)) return true;
|
|
11308
11466
|
return false;
|
|
11309
11467
|
}
|
|
11468
|
+
function specUpdateTag(spec) {
|
|
11469
|
+
const value = spec.trim();
|
|
11470
|
+
if (!isAutoUpdatableSpec(value)) return void 0;
|
|
11471
|
+
if (value === "*") return "latest";
|
|
11472
|
+
if (isDistTag(value)) return value;
|
|
11473
|
+
return "latest";
|
|
11474
|
+
}
|
|
11475
|
+
function isDistTag(value) {
|
|
11476
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)) return false;
|
|
11477
|
+
if (parseVersion(value)) return false;
|
|
11478
|
+
if (/\.x(\.|$)/i.test(value)) return false;
|
|
11479
|
+
return true;
|
|
11480
|
+
}
|
|
11310
11481
|
async function readPackageJson(path) {
|
|
11311
11482
|
try {
|
|
11312
11483
|
const data = JSON.parse(await readFile2(path, "utf-8"));
|
|
@@ -11315,10 +11486,10 @@ async function readPackageJson(path) {
|
|
|
11315
11486
|
return void 0;
|
|
11316
11487
|
}
|
|
11317
11488
|
}
|
|
11318
|
-
async function fetchLatestVersion(name, signal) {
|
|
11489
|
+
async function fetchLatestVersion(name, tag, signal) {
|
|
11319
11490
|
try {
|
|
11320
11491
|
const response = await fetch(
|
|
11321
|
-
`https://registry.npmjs.org/${encodeURIComponent(name)}
|
|
11492
|
+
`https://registry.npmjs.org/${encodeURIComponent(name)}/${encodeURIComponent(tag)}`,
|
|
11322
11493
|
{
|
|
11323
11494
|
signal
|
|
11324
11495
|
}
|
|
@@ -11371,7 +11542,15 @@ var server = (async (ctx) => {
|
|
|
11371
11542
|
if (!config.enabled) {
|
|
11372
11543
|
return {};
|
|
11373
11544
|
}
|
|
11374
|
-
const logger = new Logger(config.debug);
|
|
11545
|
+
const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
|
|
11546
|
+
logger.info("ACP plugin initialized", {
|
|
11547
|
+
version: true ? "1.14.23-pr.332.52" : "dev",
|
|
11548
|
+
workspace: ctx.directory,
|
|
11549
|
+
logLevel: logger.level,
|
|
11550
|
+
debug: config.debug,
|
|
11551
|
+
autoUpdate: config.autoUpdate,
|
|
11552
|
+
secureMode: isSecureMode()
|
|
11553
|
+
});
|
|
11375
11554
|
const registry4 = new SessionStateRegistry(logger);
|
|
11376
11555
|
const prompts = new PromptStore(logger, ctx.directory, config.experimental.customPrompts);
|
|
11377
11556
|
const hostPermissions = {
|
|
@@ -11401,7 +11580,7 @@ var server = (async (ctx) => {
|
|
|
11401
11580
|
}
|
|
11402
11581
|
);
|
|
11403
11582
|
logger.info("DCP initialized");
|
|
11404
|
-
startAutoUpdate(ctx, config.autoUpdate);
|
|
11583
|
+
startAutoUpdate(ctx, config.autoUpdate, logger);
|
|
11405
11584
|
const compressToolContext = {
|
|
11406
11585
|
client: ctx.client,
|
|
11407
11586
|
registry: registry4,
|