opencode-acp 1.18.2-pr.448.192 → 1.18.2-pr.450.194

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
@@ -42,9 +42,11 @@ A production-scale longitudinal study: 4.5 months, three hosts, 174,327 model ca
42
42
 
43
43
  ## Community
44
44
 
45
- Discussion, help, and updates on QQ — one group covers all three projects (`billion-context`, `billion-context-pi`, `opencode-acp`):
45
+ Discussion, help, and updates on QQ — one group covers all three projects (`billion-context`, `billion-context-pi`, `opencode-acp`). Please join the new group:
46
46
 
47
- **QQ Group: 1056132097**
47
+ **QQ Group: 1108730198**
48
+
49
+ > The original group **1056132097** is now full and no longer accepts new members.
48
50
 
49
51
  ---
50
52
 
package/README.zh-CN.md CHANGED
@@ -38,9 +38,11 @@
38
38
 
39
39
  ## 社区
40
40
 
41
- 交流、求助与更新都在 QQ——同一个群覆盖三个项目(`billion-context`、`billion-context-pi`、`opencode-acp`):
41
+ 交流、求助与更新都在 QQ——同一个群覆盖三个项目(`billion-context`、`billion-context-pi`、`opencode-acp`)。请加入新群:
42
42
 
43
- **QQ 群:1056132097**
43
+ **QQ 群:1108730198**
44
+
45
+ > 原群 **1056132097** 已满,不再接受新成员。
44
46
 
45
47
  ---
46
48
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA4BjD,QAAA,MAAM,MAAM,EAAE,MAwMK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA6BzD,QAAA,MAAM,MAAM,EAAE,MA8OK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
package/dist/index.js CHANGED
@@ -2148,8 +2148,57 @@ Using previous/default values`
2148
2148
  // lib/compress/range.ts
2149
2149
  import { tool as tool2 } from "@opencode-ai/plugin";
2150
2150
 
2151
+ // lib/bili-proxy.ts
2152
+ var BILI_PROXY_MARKER = "/bili/";
2153
+ function extractBaseURL(entry) {
2154
+ if (!entry || typeof entry !== "object") return void 0;
2155
+ const record = entry;
2156
+ const options = record.options;
2157
+ if (options && typeof options === "object") {
2158
+ const optionBaseURL = options.baseURL;
2159
+ if (typeof optionBaseURL === "string" && optionBaseURL.length > 0) {
2160
+ return optionBaseURL;
2161
+ }
2162
+ }
2163
+ const topLevelBaseURL = record.baseURL;
2164
+ if (typeof topLevelBaseURL === "string" && topLevelBaseURL.length > 0) {
2165
+ return topLevelBaseURL;
2166
+ }
2167
+ return void 0;
2168
+ }
2169
+ function findBiliProxyProviders(provider) {
2170
+ if (!provider || typeof provider !== "object") return [];
2171
+ const matches = [];
2172
+ for (const [name, entry] of Object.entries(provider)) {
2173
+ const baseURL = extractBaseURL(entry);
2174
+ if (baseURL !== void 0 && baseURL.includes(BILI_PROXY_MARKER)) {
2175
+ matches.push({ provider: name, baseURL });
2176
+ }
2177
+ }
2178
+ return matches;
2179
+ }
2180
+ var BILI_PROXY_ENV_VAR = "BILLION_CONTEXT_PROXY";
2181
+ var BILI_NATIVE_ENV_VAR = "BILLION_CONTEXT_NATIVE";
2182
+ function detectBiliEnvYield(env = process.env) {
2183
+ if (env[BILI_PROXY_ENV_VAR]) return "launcher";
2184
+ if (env[BILI_NATIVE_ENV_VAR]) return "native";
2185
+ return null;
2186
+ }
2187
+ function describeBiliEnvYield(source) {
2188
+ return source === "native" ? "BILLION_CONTEXT_NATIVE set (billion-context native mode)" : "BILLION_CONTEXT_PROXY set (billion-context launcher mode)";
2189
+ }
2190
+ function biliYieldLogMessage(source) {
2191
+ return source === "native" ? "[opencode-acp] disabled: BILLION_CONTEXT_NATIVE detected \u2014 billion-context native mode handles compression" : "[opencode-acp] disabled: BILLION_CONTEXT_PROXY detected \u2014 proxy handles compression";
2192
+ }
2193
+
2151
2194
  // lib/compress/types.ts
2152
2195
  function resolveToolContext(factoryCtx, sessionID) {
2196
+ const biliYield = detectBiliEnvYield();
2197
+ if (biliYield !== null) {
2198
+ throw new Error(
2199
+ `ACP is disabled in this process \u2014 ${describeBiliEnvYield(biliYield)}. billion-context owns context compression; do not call ACP tools again.`
2200
+ );
2201
+ }
2153
2202
  const state = factoryCtx.registry.get(sessionID);
2154
2203
  if (!state) {
2155
2204
  throw new Error(
@@ -6384,24 +6433,32 @@ function computeStats(plan) {
6384
6433
  const retentionPct = originalTokens > 0 ? (summaryChars / (originalTokens * 4) * 100).toFixed(2) : "?";
6385
6434
  return { originalTokens, summaryChars, ratio, retentionPct };
6386
6435
  }
6387
- function buildQualityRejectionError(plan, result) {
6436
+ function coreReason(reason) {
6437
+ if (!reason) return "unknown";
6438
+ const candidates = [reason.indexOf(":"), reason.indexOf("(")].filter((i) => i >= 0);
6439
+ const cut = Math.min(...candidates);
6440
+ return (cut > 0 ? reason.slice(0, cut) : reason).trim() || "unknown";
6441
+ }
6442
+ function buildQualityRejectionError(plan, result, logger) {
6388
6443
  const stats = computeStats(plan);
6389
- const metrics = [
6390
- `Reason: ${result.reason || "unknown"}`,
6391
- `Original: ~${stats.originalTokens} tokens`,
6392
- `Summary: ${stats.summaryChars} chars`,
6393
- `Ratio: ${stats.ratio}:1`,
6394
- `Retention: ${stats.retentionPct}%`,
6395
- `Gate layer: ${result.layer ?? "unknown"}`,
6396
- `rougeF1: ${formatMetric(result, "rougeF1")}`,
6397
- `top20Recall: ${formatMetric(result, "top20Recall")}`
6398
- ];
6444
+ logger?.warn("quality gate rejected compression", {
6445
+ range: `${plan.startId}\u2013${plan.endId}`,
6446
+ reason: result.reason || "unknown",
6447
+ layer: result.layer ?? "unknown",
6448
+ originalTokens: stats.originalTokens,
6449
+ summaryChars: stats.summaryChars,
6450
+ ratio: stats.ratio,
6451
+ retentionPct: stats.retentionPct,
6452
+ rougeF1: formatMetric(result, "rougeF1"),
6453
+ top20Recall: formatMetric(result, "top20Recall")
6454
+ });
6399
6455
  const message = `\u26A0\uFE0F COMPRESSION REJECTED \u2014 QUALITY GATE FAILURE
6400
6456
 
6401
6457
  Range: ${plan.startId}\u2013${plan.endId}
6402
- ${metrics.join("\n")}
6458
+ Reason: ${coreReason(result.reason)}
6459
+ Original: ~${stats.originalTokens} tokens \xB7 Summary: ${stats.summaryChars} chars \xB7 Retention: ${stats.retentionPct}%
6403
6460
 
6404
- Retry: rewrite a more complete summary that preserves critical details (file paths, decisions, exact values, errors) and call compress again on the same range \u2014 the gate re-evaluates automatically. Full compression rules are already in your system prompt. If you are confident the summary is correct despite the metrics, add "acknowledgeRisk": true to bypass the quality gate on your next compress call.`;
6461
+ Retry: write a more complete summary preserving critical details (file paths, decisions, exact values, errors), then call compress again on the same range. If you are confident the summary is adequate despite the metrics, pass "acknowledgeRisk": true.`;
6405
6462
  return new Error(message);
6406
6463
  }
6407
6464
 
@@ -8063,7 +8120,8 @@ function createCompressRangeTool(factoryCtx) {
8063
8120
  messageIds: plan.selection.messageIds,
8064
8121
  messageTokenById: plan.selection.messageTokenById
8065
8122
  },
8066
- result
8123
+ result,
8124
+ ctx.logger
8067
8125
  );
8068
8126
  }
8069
8127
  }
@@ -9823,6 +9881,81 @@ function computeRestoredMessages(messagesState, activeMessagesBefore) {
9823
9881
  }
9824
9882
  return { restoredMessageCount, restoredTokens };
9825
9883
  }
9884
+ function collectRequiredDecompressMessageIds(messagesState, targets, options = {}) {
9885
+ const targetBlockIds = /* @__PURE__ */ new Set();
9886
+ for (const target of targets) {
9887
+ for (const block of target.blocks) {
9888
+ targetBlockIds.add(block.blockId);
9889
+ }
9890
+ }
9891
+ if (targetBlockIds.size === 0) {
9892
+ return [];
9893
+ }
9894
+ const consumedClosure = /* @__PURE__ */ new Set();
9895
+ const queue = [];
9896
+ for (const target of targets) {
9897
+ for (const block of target.blocks) {
9898
+ for (const id of block.consumedBlockIds ?? []) {
9899
+ if (!consumedClosure.has(id)) {
9900
+ queue.push(id);
9901
+ }
9902
+ }
9903
+ }
9904
+ }
9905
+ while (queue.length > 0) {
9906
+ const id = queue.shift();
9907
+ if (consumedClosure.has(id)) continue;
9908
+ consumedClosure.add(id);
9909
+ const consumed = messagesState.blocksById.get(id);
9910
+ if (consumed) {
9911
+ for (const nested of consumed.consumedBlockIds ?? []) {
9912
+ if (!consumedClosure.has(nested)) {
9913
+ queue.push(nested);
9914
+ }
9915
+ }
9916
+ }
9917
+ }
9918
+ let shieldedMessageIds = null;
9919
+ if (!options.full) {
9920
+ shieldedMessageIds = /* @__PURE__ */ new Set();
9921
+ for (const id of consumedClosure) {
9922
+ const block = messagesState.blocksById.get(id);
9923
+ if (!block || block.deactivatedByUser || block.deactivatedByUserDeep) continue;
9924
+ for (const messageId of block.effectiveMessageIds ?? []) {
9925
+ shieldedMessageIds.add(messageId);
9926
+ }
9927
+ }
9928
+ }
9929
+ const required = [];
9930
+ for (const [messageId, entry] of messagesState.byMessageId) {
9931
+ if (entry.activeBlockIds.length === 0) continue;
9932
+ let exclusivelyCoveredByTargets = true;
9933
+ for (const blockId of entry.activeBlockIds) {
9934
+ if (!targetBlockIds.has(blockId)) {
9935
+ exclusivelyCoveredByTargets = false;
9936
+ break;
9937
+ }
9938
+ }
9939
+ if (!exclusivelyCoveredByTargets) continue;
9940
+ if (shieldedMessageIds?.has(messageId)) continue;
9941
+ required.push(messageId);
9942
+ }
9943
+ required.sort();
9944
+ return required;
9945
+ }
9946
+ function checkDecompressSourceAvailability(messagesState, targets, options, presentInHistory) {
9947
+ const requiredMessageIds = collectRequiredDecompressMessageIds(messagesState, targets, options);
9948
+ const available = [];
9949
+ const missing = [];
9950
+ for (const messageId of requiredMessageIds) {
9951
+ if (presentInHistory.has(messageId)) {
9952
+ available.push(messageId);
9953
+ } else {
9954
+ missing.push(messageId);
9955
+ }
9956
+ }
9957
+ return { requiredMessageIds, availableMessageIds: available, missingMessageIds: missing };
9958
+ }
9826
9959
  function computeReactivatedBlockIds(messagesState, activeBlockIdsBefore) {
9827
9960
  return Array.from(messagesState.activeBlockIds).filter((blockId) => !activeBlockIdsBefore.has(blockId)).sort((a, b) => a - b);
9828
9961
  }
@@ -10120,6 +10253,28 @@ function createDecompressTool(factoryCtx) {
10120
10253
  const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
10121
10254
  return `Block(s) ${displayIds2} content (${sourceNote}, ${fileContent.length} chars) written to ${targetPath}. Block(s) stay compressed \u2014 context unchanged. Use read tool to access specific parts.`;
10122
10255
  }
10256
+ const availability = checkDecompressSourceAvailability(
10257
+ messagesState,
10258
+ targets,
10259
+ { full: args.full === true },
10260
+ new Set(rawMessages.map((m) => m.info.id))
10261
+ );
10262
+ if (availability.missingMessageIds.length > 0) {
10263
+ const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
10264
+ const missingRefs = availability.missingMessageIds.map(
10265
+ (id) => ctx.state.messageIds.byRawId.get(id) ?? id
10266
+ );
10267
+ const shownRefs = missingRefs.slice(0, 10).join(", ");
10268
+ const extraCount = missingRefs.length - Math.min(missingRefs.length, 10);
10269
+ ctx.logger.warn("Decompress aborted: source messages missing from host history", {
10270
+ targetDisplayIds: displayIds2,
10271
+ full: args.full === true,
10272
+ requiredCount: availability.requiredMessageIds.length,
10273
+ missingCount: availability.missingMessageIds.length,
10274
+ missingRefs
10275
+ });
10276
+ return `Error: Cannot decompress ${displayIds2}: ${availability.missingMessageIds.length} of ${availability.requiredMessageIds.length} source message(s) are no longer present in session history (removed by native compaction or external deletion). Missing: ${shownRefs}` + (extraCount > 0 ? ` (+${extraCount} more)` : "") + `. Block(s) stay compressed \u2014 the summary remains available and nothing was changed. This is a missing-source condition, not a transient fetch failure.`;
10277
+ }
10123
10278
  const activeMessagesBefore = snapshotActiveMessages(messagesState);
10124
10279
  const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
10125
10280
  for (const target of targets) {
@@ -10931,7 +11086,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
10931
11086
  import { join as join4 } from "path";
10932
11087
  import { existsSync as existsSync4 } from "fs";
10933
11088
  import { homedir as homedir3 } from "os";
10934
- var LOG_VERSION = true ? "1.18.2-pr.448.192" : "dev";
11089
+ var LOG_VERSION = true ? "1.18.2-pr.450.194" : "dev";
10935
11090
  var LEVEL_RANK = {
10936
11091
  debug: 10,
10937
11092
  info: 20,
@@ -13405,36 +13560,6 @@ function configureClientAuth(client) {
13405
13560
  return client;
13406
13561
  }
13407
13562
 
13408
- // lib/bili-proxy.ts
13409
- var BILI_PROXY_MARKER = "/bili/";
13410
- function extractBaseURL(entry) {
13411
- if (!entry || typeof entry !== "object") return void 0;
13412
- const record = entry;
13413
- const options = record.options;
13414
- if (options && typeof options === "object") {
13415
- const optionBaseURL = options.baseURL;
13416
- if (typeof optionBaseURL === "string" && optionBaseURL.length > 0) {
13417
- return optionBaseURL;
13418
- }
13419
- }
13420
- const topLevelBaseURL = record.baseURL;
13421
- if (typeof topLevelBaseURL === "string" && topLevelBaseURL.length > 0) {
13422
- return topLevelBaseURL;
13423
- }
13424
- return void 0;
13425
- }
13426
- function findBiliProxyProviders(provider) {
13427
- if (!provider || typeof provider !== "object") return [];
13428
- const matches = [];
13429
- for (const [name, entry] of Object.entries(provider)) {
13430
- const baseURL = extractBaseURL(entry);
13431
- if (baseURL !== void 0 && baseURL.includes(BILI_PROXY_MARKER)) {
13432
- matches.push({ provider: name, baseURL });
13433
- }
13434
- }
13435
- return matches;
13436
- }
13437
-
13438
13563
  // lib/update.ts
13439
13564
  import { readFile as readFile2, rm } from "fs/promises";
13440
13565
  import { basename, dirname as dirname4, join as join7 } from "path";
@@ -13641,15 +13766,14 @@ var server = (async (ctx) => {
13641
13766
  if (!config.enabled) {
13642
13767
  return {};
13643
13768
  }
13644
- if (process.env.BILLION_CONTEXT_PROXY) {
13645
- console.log(
13646
- "[opencode-acp] disabled: BILLION_CONTEXT_PROXY detected \u2014 proxy handles compression"
13647
- );
13769
+ const setupBiliYield = detectBiliEnvYield();
13770
+ if (setupBiliYield) {
13771
+ console.log(biliYieldLogMessage(setupBiliYield));
13648
13772
  return {};
13649
13773
  }
13650
13774
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
13651
13775
  logger.info("ACP plugin initialized", {
13652
- version: true ? "1.18.2-pr.448.192" : "dev",
13776
+ version: true ? "1.18.2-pr.450.194" : "dev",
13653
13777
  workspace: ctx.directory,
13654
13778
  logLevel: logger.level,
13655
13779
  debug: config.debug,
@@ -13699,7 +13823,21 @@ var server = (async (ctx) => {
13699
13823
  prompts
13700
13824
  };
13701
13825
  let disabledByBiliProxy = false;
13702
- const guard = (fn) => (...args) => disabledByBiliProxy ? Promise.resolve() : fn(...args);
13826
+ let announcedEnvYieldSource;
13827
+ const noteEnvYield = (source) => {
13828
+ if (source === null || announcedEnvYieldSource === source) return;
13829
+ announcedEnvYieldSource = source;
13830
+ console.log(biliYieldLogMessage(source));
13831
+ };
13832
+ const guard = (fn) => (...args) => {
13833
+ if (disabledByBiliProxy) return Promise.resolve();
13834
+ const envYield = detectBiliEnvYield();
13835
+ if (envYield !== null) {
13836
+ noteEnvYield(envYield);
13837
+ return Promise.resolve();
13838
+ }
13839
+ return fn(...args);
13840
+ };
13703
13841
  return {
13704
13842
  "experimental.chat.system.transform": guard(
13705
13843
  createSystemPromptHandler(registry4, logger, config, prompts)
@@ -13736,14 +13874,9 @@ var server = (async (ctx) => {
13736
13874
  }
13737
13875
  },
13738
13876
  config: async (opencodeConfig) => {
13739
- const biliMatches = findBiliProxyProviders(opencodeConfig.provider);
13740
- disabledByBiliProxy = biliMatches.length > 0;
13741
- if (biliMatches.length > 0) {
13742
- console.log(
13743
- "[opencode-acp] disabled: /bili/ proxy detected in provider baseURL (" + biliMatches.map((m) => m.provider).join(", ") + ") \u2014 proxy handles compression"
13744
- );
13745
- const permission = opencodeConfig.permission ?? {};
13746
- opencodeConfig.permission = {
13877
+ const denyAcpTools = (opencodeConfig2) => {
13878
+ const permission = opencodeConfig2.permission ?? {};
13879
+ opencodeConfig2.permission = {
13747
13880
  ...permission,
13748
13881
  compress: "deny",
13749
13882
  decompress: "deny",
@@ -13751,6 +13884,20 @@ var server = (async (ctx) => {
13751
13884
  acp_status: "deny",
13752
13885
  acp_context_recap: "deny"
13753
13886
  };
13887
+ };
13888
+ const biliMatches = findBiliProxyProviders(opencodeConfig.provider);
13889
+ disabledByBiliProxy = biliMatches.length > 0;
13890
+ const envYield = detectBiliEnvYield();
13891
+ if (envYield !== null) {
13892
+ noteEnvYield(envYield);
13893
+ denyAcpTools(opencodeConfig);
13894
+ return;
13895
+ }
13896
+ if (biliMatches.length > 0) {
13897
+ console.log(
13898
+ "[opencode-acp] disabled: /bili/ proxy detected in provider baseURL (" + biliMatches.map((m) => m.provider).join(", ") + ") \u2014 proxy handles compression"
13899
+ );
13900
+ denyAcpTools(opencodeConfig);
13754
13901
  return;
13755
13902
  }
13756
13903
  if (config.compress.permission !== "deny" && compressDisabledByOpencode(opencodeConfig.permission)) {