opencode-acp 1.14.23 → 1.14.24

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
@@ -282,8 +282,11 @@ Each level overrides the previous, so project settings take priority over global
282
282
  // on the installed dist-tag/spec (@stable follows stable, @latest follows latest).
283
283
  // Version-locked plugin specs are not updated.
284
284
  "autoUpdate": true,
285
- // Enable INFO/DEBUG logging + per-request snapshots to ~/.config/opencode/logs/acp/
286
- // (WARN/ERROR are always logged to daily/<date>.log)
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)
287
290
  "debug": false,
288
291
  // Notification display: "off", "minimal", or "detailed"
289
292
  "pruneNotification": "off",
package/README.zh-CN.md CHANGED
@@ -236,8 +236,11 @@ ACP 使用自己的配置文件,按以下顺序搜索:
236
236
  // 自动更新 npm 安装的 ACP:跟踪安装所用 dist-tag/规范(@stable 跟随 stable,@latest 跟随 latest)。
237
237
  // 版本锁定的规范不会被更新。
238
238
  "autoUpdate": true,
239
- // Enable INFO/DEBUG logging + per-request snapshots to ~/.config/opencode/logs/acp/
240
- // (WARN/ERROR are always logged to daily/<date>.log)
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",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA2BjD,QAAA,MAAM,MAAM,EAAE,MA6IK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
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,
@@ -7757,6 +7760,13 @@ Multiple entries create separate blocks: \`content: [{ startId: "b${firstBlock.b
7757
7760
  ${rules}`;
7758
7761
  appendToLastTextPart(suffixMessage, tierText);
7759
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
+ });
7760
7770
  if (tc.triggerTier === 2) {
7761
7771
  state.nudges.lastTier2NudgeTokens = currentTokens;
7762
7772
  } else {
@@ -7766,6 +7776,32 @@ ${rules}`;
7766
7776
  }
7767
7777
  }
7768
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
+ }
7769
7805
  if (shouldInject && config.debug && contextRanges.compressible.length > 0) {
7770
7806
  const compressible = contextRanges.compressible;
7771
7807
  const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
@@ -9074,15 +9110,36 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
9074
9110
  import { join as join3 } from "path";
9075
9111
  import { existsSync as existsSync3 } from "fs";
9076
9112
  import { homedir as homedir3 } from "os";
9077
- var LOG_VERSION = true ? "1.14.23" : "dev";
9113
+ var LOG_VERSION = true ? "1.14.24" : "dev";
9114
+ var LEVEL_RANK = {
9115
+ debug: 10,
9116
+ info: 20,
9117
+ warn: 30,
9118
+ error: 40,
9119
+ silent: 99
9120
+ };
9078
9121
  var Logger = class {
9079
9122
  logDir;
9080
- enabled;
9081
- constructor(enabled) {
9082
- this.enabled = enabled;
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
+ const resolved = level !== void 0 && LEVEL_RANK[level] !== void 0 ? level : void 0;
9132
+ this.level = resolved ?? (enabled ? "debug" : "warn");
9083
9133
  const configHome = process.env.XDG_CONFIG_HOME || join3(homedir3(), ".config");
9084
9134
  this.logDir = join3(configHome, "opencode", "logs", "acp");
9085
9135
  }
9136
+ /** Back-compat view of the old flag: true only at full debug verbosity. */
9137
+ get enabled() {
9138
+ return this.level === "debug";
9139
+ }
9140
+ shouldWrite(level) {
9141
+ return LEVEL_RANK[level] >= LEVEL_RANK[this.level];
9142
+ }
9086
9143
  async ensureLogDir() {
9087
9144
  if (!existsSync3(this.logDir)) {
9088
9145
  await mkdir2(this.logDir, { recursive: true });
@@ -9129,7 +9186,7 @@ var Logger = class {
9129
9186
  }
9130
9187
  }
9131
9188
  async write(level, component, message, data) {
9132
- if (!this.enabled && level !== "ERROR" && level !== "WARN") return;
9189
+ if (!this.shouldWrite(level.toLowerCase())) return;
9133
9190
  try {
9134
9191
  await this.ensureLogDir();
9135
9192
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -9146,12 +9203,12 @@ var Logger = class {
9146
9203
  }
9147
9204
  }
9148
9205
  info(message, data) {
9149
- if (!this.enabled) return;
9206
+ if (!this.shouldWrite("info")) return;
9150
9207
  const component = this.getCallerFile(2);
9151
9208
  return this.write("INFO", component, message, data);
9152
9209
  }
9153
9210
  debug(message, data) {
9154
- if (!this.enabled) return;
9211
+ if (!this.shouldWrite("debug")) return;
9155
9212
  const component = this.getCallerFile(2);
9156
9213
  return this.write("DEBUG", component, message, data);
9157
9214
  }
@@ -9249,7 +9306,7 @@ var Logger = class {
9249
9306
  });
9250
9307
  }
9251
9308
  async saveContext(sessionId, messages) {
9252
- if (!this.enabled) return;
9309
+ if (this.level !== "debug") return;
9253
9310
  try {
9254
9311
  const contextDir = join3(this.logDir, "context", sessionId);
9255
9312
  if (!existsSync3(contextDir)) {
@@ -11010,6 +11067,7 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11010
11067
  requestModel?.providerID,
11011
11068
  requestModel?.modelID
11012
11069
  );
11070
+ const prevModelID = state.modelID;
11013
11071
  if (requestModelLimit !== void 0) {
11014
11072
  state.modelContextLimit = requestModelLimit;
11015
11073
  state.modelProviderID = requestModel?.providerID;
@@ -11029,6 +11087,14 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11029
11087
  state.modelProviderID = requestModel.providerID;
11030
11088
  state.modelID = requestModel.modelID;
11031
11089
  }
11090
+ if (requestModel?.modelID && requestModel.modelID !== prevModelID) {
11091
+ logger.info("Model switched mid-session", {
11092
+ session: state.sessionId,
11093
+ from: prevModelID,
11094
+ to: requestModel.modelID,
11095
+ contextLimit: state.modelContextLimit
11096
+ });
11097
+ }
11032
11098
  await updatePerTurnState(state, logger, messages);
11033
11099
  }
11034
11100
  syncCompressPermissionState(state, config, hostPermissions, output.messages);
@@ -11090,6 +11156,17 @@ ${text}`);
11090
11156
  hideFailedCompressCalls(output.messages);
11091
11157
  stripStaleMetadata(output.messages);
11092
11158
  dropEmptyMessages(output.messages);
11159
+ const postTokens = getCurrentTokenUsage(state, output.messages);
11160
+ logger.info("Chat transform complete", {
11161
+ session: state.sessionId,
11162
+ model: state.modelID,
11163
+ messages: output.messages.length,
11164
+ prePruneTokens,
11165
+ postTokens,
11166
+ contextLimit: state.modelContextLimit,
11167
+ usagePct: postTokens !== void 0 && state.modelContextLimit ? `${(postTokens / state.modelContextLimit * 100).toFixed(1)}%` : void 0,
11168
+ nudged: state.nudges.shouldInjectThisTurn
11169
+ });
11093
11170
  if (state.sessionId) {
11094
11171
  await logger.saveContext(state.sessionId, output.messages);
11095
11172
  }
@@ -11267,12 +11344,31 @@ import { readFile as readFile2, rm } from "fs/promises";
11267
11344
  import { basename, dirname as dirname4, join as join6 } from "path";
11268
11345
  import { fileURLToPath } from "url";
11269
11346
  var PACKAGE_NAME = "opencode-acp";
11270
- function startAutoUpdate(ctx, enabled) {
11271
- if (!enabled) return;
11347
+ function startAutoUpdate(ctx, enabled, logger) {
11348
+ if (!enabled) {
11349
+ logger?.info("Auto-update disabled by config");
11350
+ return;
11351
+ }
11352
+ logger?.info("Auto-update check starting");
11272
11353
  const controller = new AbortController();
11273
11354
  const timeout = setTimeout(() => controller.abort(), 1e4);
11274
- void checkAutoUpdate(controller.signal).then((result) => {
11275
- if (!result.updated) return;
11355
+ void checkAutoUpdate(controller.signal, logger).then((result) => {
11356
+ if (!result.updated) {
11357
+ if ("error" in result) {
11358
+ logger?.warn("Auto-update failed", {
11359
+ error: result.error,
11360
+ name: result.name,
11361
+ current: result.current,
11362
+ latest: result.latest
11363
+ });
11364
+ }
11365
+ return;
11366
+ }
11367
+ logger?.info("Auto-update applied update", {
11368
+ name: result.name,
11369
+ from: result.current,
11370
+ to: result.latest
11371
+ });
11276
11372
  setTimeout(() => {
11277
11373
  ctx.client.tui.showToast({
11278
11374
  body: {
@@ -11286,17 +11382,37 @@ function startAutoUpdate(ctx, enabled) {
11286
11382
  }).catch(() => {
11287
11383
  }).finally(() => clearTimeout(timeout));
11288
11384
  }
11289
- async function checkAutoUpdate(signal) {
11385
+ async function checkAutoUpdate(signal, logger) {
11290
11386
  const packageDir = await findPackageDir(PACKAGE_NAME);
11291
- if (!packageDir) return { updated: false };
11387
+ if (!packageDir) {
11388
+ logger?.info("Auto-update skipped: package dir not found");
11389
+ return { updated: false };
11390
+ }
11292
11391
  const pkg = await readPackageJson(join6(packageDir, "package.json"));
11293
- if (!pkg?.name || !pkg.version) return { updated: false };
11392
+ if (!pkg?.name || !pkg.version) {
11393
+ logger?.info("Auto-update skipped: package.json unreadable", { packageDir });
11394
+ return { updated: false };
11395
+ }
11294
11396
  const target = await updateTarget(packageDir, pkg.name);
11295
- if (!target) return { updated: false };
11397
+ if (!target) {
11398
+ logger?.info("Auto-update skipped: no update target", { name: pkg.name, version: pkg.version });
11399
+ return { updated: false };
11400
+ }
11296
11401
  const tag = specUpdateTag(target.spec);
11297
- if (!tag) return { updated: false };
11402
+ if (!tag) {
11403
+ logger?.info("Auto-update skipped: spec not auto-updatable", { spec: target.spec });
11404
+ return { updated: false };
11405
+ }
11298
11406
  const latest = await fetchLatestVersion(pkg.name, tag, signal);
11299
- if (!latest || !isVersionNewer(latest, pkg.version)) return { updated: false };
11407
+ if (!latest) {
11408
+ logger?.info("Auto-update skipped: no version published for tag", { name: pkg.name, tag });
11409
+ return { updated: false };
11410
+ }
11411
+ if (!isVersionNewer(latest, pkg.version)) {
11412
+ logger?.info("Auto-update: already up to date", { name: pkg.name, tag, current: pkg.version, latest });
11413
+ return { updated: false };
11414
+ }
11415
+ logger?.info("Auto-update: newer version available", { name: pkg.name, tag, current: pkg.version, latest });
11300
11416
  try {
11301
11417
  await rm(target.removeDir, { recursive: true, force: true });
11302
11418
  } catch {
@@ -11429,7 +11545,15 @@ var server = (async (ctx) => {
11429
11545
  if (!config.enabled) {
11430
11546
  return {};
11431
11547
  }
11432
- const logger = new Logger(config.debug);
11548
+ const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
11549
+ logger.info("ACP plugin initialized", {
11550
+ version: true ? "1.14.24" : "dev",
11551
+ workspace: ctx.directory,
11552
+ logLevel: logger.level,
11553
+ debug: config.debug,
11554
+ autoUpdate: config.autoUpdate,
11555
+ secureMode: isSecureMode()
11556
+ });
11433
11557
  const registry4 = new SessionStateRegistry(logger);
11434
11558
  const prompts = new PromptStore(logger, ctx.directory, config.experimental.customPrompts);
11435
11559
  const hostPermissions = {
@@ -11459,7 +11583,7 @@ var server = (async (ctx) => {
11459
11583
  }
11460
11584
  );
11461
11585
  logger.info("DCP initialized");
11462
- startAutoUpdate(ctx, config.autoUpdate);
11586
+ startAutoUpdate(ctx, config.autoUpdate, logger);
11463
11587
  const compressToolContext = {
11464
11588
  client: ctx.client,
11465
11589
  registry: registry4,