opencode-acp 1.17.0 → 1.17.1-pr.391.141

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
@@ -22,6 +22,14 @@ The model decides <em>when</em> and <em>what</em> to compress — not a hard lim
22
22
 
23
23
  ---
24
24
 
25
+ ## Community
26
+
27
+ Discussion, help, and updates on QQ — one group covers all three projects (`billion-context`, `billion-context-pi`, `opencode-acp`):
28
+
29
+ **QQ Group: 1056132097**
30
+
31
+ ---
32
+
25
33
  ## Why ACP
26
34
 
27
35
  ACP hands all context-management authority to the model itself — not relying on
package/README.zh-CN.md CHANGED
@@ -22,6 +22,14 @@
22
22
 
23
23
  ---
24
24
 
25
+ ## 社区
26
+
27
+ 交流、求助与更新都在 QQ——同一个群覆盖三个项目(`billion-context`、`billion-context-pi`、`opencode-acp`):
28
+
29
+ **QQ 群:1056132097**
30
+
31
+ ---
32
+
25
33
  ## 为什么选择 ACP
26
34
 
27
35
  ACP 将上下文管理的所有权限全部交给模型自己,而不依靠外部模型或各种复杂的机制去做上下文管理。它是迄今为止,市面上对上下文管理最好的实现。
package/dist/index.js CHANGED
@@ -938,7 +938,7 @@ function getConfigKeyPaths(obj, prefix = "") {
938
938
  for (const key of Object.keys(obj)) {
939
939
  const fullKey = prefix ? `${prefix}.${key}` : key;
940
940
  keys.push(fullKey);
941
- if (fullKey === "compress.modelMaxLimits" || fullKey === "compress.modelMinLimits" || fullKey === "compress.providers" || fullKey === "messageFilters.filters") {
941
+ if (fullKey === "compress.modelMaxLimits" || fullKey === "compress.modelMinLimits" || fullKey === "compress.providers" || fullKey === "messageFilters.filters" || fullKey === "qualityGate.algorithms") {
942
942
  continue;
943
943
  }
944
944
  if (obj[key] && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
@@ -2442,6 +2442,9 @@ function countMessageCharacters(msg) {
2442
2442
  }
2443
2443
  return total;
2444
2444
  }
2445
+ function estimateAllMessageTokensFast(msg) {
2446
+ return Math.round(countMessageCharacters(msg) / 4);
2447
+ }
2445
2448
 
2446
2449
  // lib/prompts/extensions/tool.ts
2447
2450
  var RANGE_FORMAT_EXTENSION = `
@@ -2491,6 +2494,9 @@ var isMessageCompacted = (state, msg) => {
2491
2494
  }
2492
2495
  return false;
2493
2496
  };
2497
+ function bumpPruneStructureVersion(messagesState) {
2498
+ messagesState.structureVersion = (messagesState.structureVersion ?? 0) + 1;
2499
+ }
2494
2500
  function serializePruneMessagesState(messagesState) {
2495
2501
  return {
2496
2502
  byMessageId: Object.fromEntries(messagesState.byMessageId),
@@ -2808,9 +2814,13 @@ async function writePersistedSessionState(sessionId, state, logger, storageDir)
2808
2814
  totalTokensSaved: state.stats.totalPruneTokens
2809
2815
  });
2810
2816
  }
2811
- async function saveSessionState(sessionState, logger, sessionName) {
2817
+ var saveQueues = /* @__PURE__ */ new Map();
2818
+ function saveQueueKey(sessionId, storageDir) {
2819
+ return `${sessionId}\0${storageDir ?? ""}`;
2820
+ }
2821
+ function saveSessionState(sessionState, logger, sessionName) {
2812
2822
  if (!sessionState.sessionId) {
2813
- return;
2823
+ return Promise.resolve();
2814
2824
  }
2815
2825
  const state = {
2816
2826
  sessionName,
@@ -2841,7 +2851,53 @@ async function saveSessionState(sessionState, logger, sessionName) {
2841
2851
  modelProviderID: sessionState.modelProviderID,
2842
2852
  modelID: sessionState.modelID
2843
2853
  };
2844
- await writePersistedSessionState(sessionState.sessionId, state, logger, sessionState.storageDir);
2854
+ const key = saveQueueKey(sessionState.sessionId, sessionState.storageDir);
2855
+ let queue = saveQueues.get(key);
2856
+ if (!queue) {
2857
+ queue = { pending: [], waiters: [], draining: false };
2858
+ saveQueues.set(key, queue);
2859
+ }
2860
+ const promise = new Promise((resolve2, reject) => {
2861
+ queue.waiters.push({ resolve: resolve2, reject });
2862
+ });
2863
+ queue.pending.push({
2864
+ sessionId: sessionState.sessionId,
2865
+ state,
2866
+ storageDir: sessionState.storageDir,
2867
+ logger
2868
+ });
2869
+ if (!queue.draining) {
2870
+ queue.draining = true;
2871
+ setImmediate(() => drainSaveQueue(key));
2872
+ }
2873
+ return promise;
2874
+ }
2875
+ function drainSaveQueue(key) {
2876
+ const queue = saveQueues.get(key);
2877
+ if (!queue || queue.pending.length === 0) {
2878
+ if (queue) {
2879
+ queue.draining = false;
2880
+ if (queue.waiters.length === 0) saveQueues.delete(key);
2881
+ }
2882
+ return;
2883
+ }
2884
+ const batch = queue.pending.splice(0, queue.pending.length);
2885
+ const waiters = queue.waiters.splice(0, batch.length);
2886
+ const latest = batch[batch.length - 1];
2887
+ writePersistedSessionState(latest.sessionId, latest.state, latest.logger, latest.storageDir).then(() => {
2888
+ for (const waiter of waiters) waiter.resolve();
2889
+ }).catch((err) => {
2890
+ for (const waiter of waiters) waiter.reject(err);
2891
+ }).finally(() => {
2892
+ const q = saveQueues.get(key);
2893
+ if (!q) return;
2894
+ if (q.pending.length > 0) {
2895
+ drainSaveQueue(key);
2896
+ } else {
2897
+ q.draining = false;
2898
+ if (q.waiters.length === 0) saveQueues.delete(key);
2899
+ }
2900
+ });
2845
2901
  }
2846
2902
  async function loadSessionState(sessionId, logger, storageDir) {
2847
2903
  try {
@@ -3335,6 +3391,7 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
3335
3391
  state.stats.pruneTokenCounter += compressedTokens;
3336
3392
  state.stats.totalPruneTokens += state.stats.pruneTokenCounter;
3337
3393
  state.stats.pruneTokenCounter = 0;
3394
+ bumpPruneStructureVersion(messagesState);
3338
3395
  return {
3339
3396
  compressedTokens,
3340
3397
  messageIds: selection.messageIds,
@@ -3448,15 +3505,17 @@ function buildSearchContext(state, rawMessages) {
3448
3505
  }
3449
3506
  summaryByBlockId.set(blockId, block);
3450
3507
  }
3451
- return {
3508
+ const context = {
3452
3509
  rawMessages,
3453
3510
  rawMessagesById,
3454
3511
  rawIndexById,
3455
3512
  summaryByBlockId
3456
3513
  };
3514
+ context.boundaryLookup = buildBoundaryLookup(context, state);
3515
+ return context;
3457
3516
  }
3458
3517
  function resolveBoundaryIds(context, state, startId, endId, logger) {
3459
- const lookup = buildBoundaryLookup(context, state);
3518
+ const lookup = context.boundaryLookup ?? (context.boundaryLookup = buildBoundaryLookup(context, state));
3460
3519
  const issues = [];
3461
3520
  const parsedStartId = parseBoundaryId(startId);
3462
3521
  const parsedEndId = parseBoundaryId(endId);
@@ -3656,7 +3715,7 @@ function resolveSelection(context, startReference, endReference) {
3656
3715
  messageIds.push(messageId);
3657
3716
  }
3658
3717
  if (!messageTokenById.has(messageId)) {
3659
- messageTokenById.set(messageId, countAllMessageTokens(rawMessage));
3718
+ messageTokenById.set(messageId, estimateAllMessageTokensFast(rawMessage));
3660
3719
  }
3661
3720
  const parts = Array.isArray(rawMessage.parts) ? rawMessage.parts : [];
3662
3721
  for (const part of parts) {
@@ -7863,6 +7922,23 @@ var syncCompressionBlocks = (state, logger, messages) => {
7863
7922
  return;
7864
7923
  }
7865
7924
  const messageIds = new Set(messages.map((msg) => msg.info.id));
7925
+ const structureVersion = messagesState.structureVersion ?? 0;
7926
+ if (messagesState.lastSyncedStructureVersion === structureVersion) {
7927
+ const activeBlocks = [...messagesState.activeBlockIds].map((id) => messagesState.blocksById.get(id)).filter((b) => b !== void 0).sort(sortBlocksByCreation);
7928
+ const nextAnchorMap = /* @__PURE__ */ new Map();
7929
+ for (const block of activeBlocks) {
7930
+ if (!messageIds.has(block.anchorMessageId)) continue;
7931
+ nextAnchorMap.set(block.anchorMessageId, block.blockId);
7932
+ }
7933
+ const previous = messagesState.activeByAnchorMessageId;
7934
+ if (previous.size !== nextAnchorMap.size || ![...nextAnchorMap.entries()].every(([anchor, id]) => previous.get(anchor) === id)) {
7935
+ previous.clear();
7936
+ for (const [anchor, id] of nextAnchorMap) {
7937
+ previous.set(anchor, id);
7938
+ }
7939
+ }
7940
+ return;
7941
+ }
7866
7942
  const previousActiveBlockIds = new Set(
7867
7943
  Array.from(messagesState.blocksById.values()).filter((block) => block.active).map((block) => block.blockId)
7868
7944
  );
@@ -7929,6 +8005,7 @@ var syncCompressionBlocks = (state, logger, messages) => {
7929
8005
  reactivatedCount
7930
8006
  });
7931
8007
  }
8008
+ messagesState.lastSyncedStructureVersion = structureVersion;
7932
8009
  };
7933
8010
 
7934
8011
  // lib/host-permissions.ts
@@ -8182,20 +8259,23 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
8182
8259
  state.nudges.lastPerMessageNudgeTokens = currentTokens;
8183
8260
  baselineReEstablished = true;
8184
8261
  }
8185
- const composition = estimateContextComposition(
8262
+ const tierUsageEarly = getTierTokenUsage(state);
8263
+ const tierTriggerPossible = !!suffixMessage && (tierUsageEarly.tier1Tokens >= nudgeGrowthTokens || tierUsageEarly.tier2Tokens >= nudgeGrowthTokens);
8264
+ const needsNudgeAnalysis = nudgeAllowed || emergencyOverride || tierTriggerPossible;
8265
+ const composition = needsNudgeAnalysis ? estimateContextComposition(
8186
8266
  messages,
8187
8267
  state,
8188
8268
  config.compress.protectedTools,
8189
8269
  config.protectedFilePatterns
8190
- );
8191
- const protectedRefs = computeProtectedRefs(messages, state, config.compress);
8192
- const contextRanges = buildCompressibleRanges(
8270
+ ) : null;
8271
+ const protectedRefs = needsNudgeAnalysis ? computeProtectedRefs(messages, state, config.compress) : /* @__PURE__ */ new Set();
8272
+ const contextRanges = needsNudgeAnalysis ? buildCompressibleRanges(
8193
8273
  messages,
8194
8274
  state,
8195
8275
  config.compress.protectedTools,
8196
8276
  config.protectedFilePatterns,
8197
8277
  protectedRefs
8198
- );
8278
+ ) : { compressible: [], protected: [] };
8199
8279
  const unprotectedCompressible = excludeProtectedRanges(contextRanges.compressible, protectedRefs);
8200
8280
  const recommendedRanges = filterRecommendedRanges(
8201
8281
  unprotectedCompressible,
@@ -8220,7 +8300,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
8220
8300
  baselineReEstablished = true;
8221
8301
  }
8222
8302
  if (suffixMessage && !shouldInject) {
8223
- const tierUsage = getTierTokenUsage(state);
8303
+ const tierUsage = tierUsageEarly;
8224
8304
  const tierChecks = [
8225
8305
  { triggerTier: 2, targetTier: 1, tokens: tierUsage.tier1Tokens, lastNudge: state.nudges.lastTier2NudgeTokens },
8226
8306
  { triggerTier: 3, targetTier: 2, tokens: tierUsage.tier2Tokens, lastNudge: state.nudges.lastTier3NudgeTokens }
@@ -8347,7 +8427,7 @@ ${rules}`;
8347
8427
  }
8348
8428
  let tipsText = null;
8349
8429
  if (shouldInject) {
8350
- if (suffixMessage && composition.total > 0) {
8430
+ if (suffixMessage && composition !== null && composition.total > 0) {
8351
8431
  const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
8352
8432
  const pct2 = (n) => n > 0 ? Math.max(1, Math.round(n / composition.total * 100)) : 0;
8353
8433
  const growth = currentTokens !== void 0 && (state.nudges.lastNudgeShownTokens ?? state.nudges.lastPerMessageNudgeTokens) !== void 0 ? currentTokens - (state.nudges.lastNudgeShownTokens ?? state.nudges.lastPerMessageNudgeTokens) : 0;
@@ -8722,6 +8802,9 @@ function deactivateCompressionTarget(messagesState, target, options) {
8722
8802
  }
8723
8803
  }
8724
8804
  }
8805
+ if (target.blocks.length > 0) {
8806
+ bumpPruneStructureVersion(messagesState);
8807
+ }
8725
8808
  }
8726
8809
  function computeRestoredMessages(messagesState, activeMessagesBefore) {
8727
8810
  let restoredMessageCount = 0;
@@ -9103,21 +9186,29 @@ function rewriteCompressInput(part, liveKeys) {
9103
9186
  };
9104
9187
  }
9105
9188
  function hideConsumedCompressCalls(state, messages) {
9106
- const allBlockCallIds = /* @__PURE__ */ new Set();
9107
- const liveRangeKeysByCallId = /* @__PURE__ */ new Map();
9108
- const activeCallIds = /* @__PURE__ */ new Set();
9109
- for (const block of state.prune.messages.blocksById.values()) {
9110
- if (!block.compressCallId) continue;
9111
- allBlockCallIds.add(block.compressCallId);
9112
- if (!isLiveBlock(block)) continue;
9113
- activeCallIds.add(block.compressCallId);
9114
- let keys = liveRangeKeysByCallId.get(block.compressCallId);
9115
- if (!keys) {
9116
- keys = /* @__PURE__ */ new Set();
9117
- liveRangeKeysByCallId.set(block.compressCallId, keys);
9118
- }
9119
- keys.add(rangeKey(block.startId, block.endId));
9120
- }
9189
+ const messagesState = state.prune.messages;
9190
+ const version = messagesState.structureVersion ?? 0;
9191
+ let cached = messagesState.hideConsumedIndex;
9192
+ if (!cached || cached.version !== version) {
9193
+ const allBlockCallIds2 = /* @__PURE__ */ new Set();
9194
+ const liveRangeKeysByCallId2 = /* @__PURE__ */ new Map();
9195
+ const activeCallIds2 = /* @__PURE__ */ new Set();
9196
+ for (const block of messagesState.blocksById.values()) {
9197
+ if (!block.compressCallId) continue;
9198
+ allBlockCallIds2.add(block.compressCallId);
9199
+ if (!isLiveBlock(block)) continue;
9200
+ activeCallIds2.add(block.compressCallId);
9201
+ let keys = liveRangeKeysByCallId2.get(block.compressCallId);
9202
+ if (!keys) {
9203
+ keys = /* @__PURE__ */ new Set();
9204
+ liveRangeKeysByCallId2.set(block.compressCallId, keys);
9205
+ }
9206
+ keys.add(rangeKey(block.startId, block.endId));
9207
+ }
9208
+ cached = { version, allBlockCallIds: allBlockCallIds2, liveRangeKeysByCallId: liveRangeKeysByCallId2, activeCallIds: activeCallIds2 };
9209
+ messagesState.hideConsumedIndex = cached;
9210
+ }
9211
+ const { allBlockCallIds, liveRangeKeysByCallId, activeCallIds } = cached;
9121
9212
  const lastOrphanedCallIds = [];
9122
9213
  for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {
9123
9214
  const parts = Array.isArray(messages[i]?.parts) ? messages[i].parts : [];
@@ -9691,7 +9782,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
9691
9782
  import { join as join4 } from "path";
9692
9783
  import { existsSync as existsSync4 } from "fs";
9693
9784
  import { homedir as homedir3 } from "os";
9694
- var LOG_VERSION = true ? "1.17.0" : "dev";
9785
+ var LOG_VERSION = true ? "1.17.1-pr.391.141" : "dev";
9695
9786
  var LEVEL_RANK = {
9696
9787
  debug: 10,
9697
9788
  info: 20,
@@ -11691,6 +11782,7 @@ function mergeMarkedBlocks(state, markedIds, maxMergedLength) {
11691
11782
  0
11692
11783
  );
11693
11784
  const savedTokens = Math.max(0, sourceTokens - newSummaryTokens);
11785
+ bumpPruneStructureVersion(messagesState);
11694
11786
  return { mergedCount: sourceBlocks.length, savedTokens };
11695
11787
  }
11696
11788
  function runBatchCleanup(state, config, logger, messages) {
@@ -12386,7 +12478,7 @@ var server = (async (ctx) => {
12386
12478
  }
12387
12479
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
12388
12480
  logger.info("ACP plugin initialized", {
12389
- version: true ? "1.17.0" : "dev",
12481
+ version: true ? "1.17.1-pr.391.141" : "dev",
12390
12482
  workspace: ctx.directory,
12391
12483
  logLevel: logger.level,
12392
12484
  debug: config.debug,