opencode-acp 1.18.4-pr.484.217 → 1.18.4-pr.486.219

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
@@ -431,15 +431,9 @@ Each level overrides the previous, so project settings take priority over global
431
431
  // compression, so their thinking rides along every request as an
432
432
  // unreclaimable floor. Closed turns only; the active round is never
433
433
  // touched. Small thinkings under the threshold are kept.
434
- // [#482] Pin the wire form of historical thinking: fold each assistant
435
- // message's reasoning parts into one <thinking>-marked text part at
436
- // request time, so serialized bytes stay deterministic across host
437
- // metadata-table refreshes. On by default; set normalize:false for
438
- // direct providers needing structured thinking replay.
439
434
  "reasoning": {
440
435
  "drop": true,
441
436
  "threshold": 2048,
442
- "normalize": true
443
437
  },
444
438
  // [#478] Image archive: spill data-URL images to disk when they enter
445
439
  // the session so folded ranges keep re-readable pointers instead of
package/README.zh-CN.md CHANGED
@@ -376,14 +376,9 @@ ACP 使用自己的配置文件,按以下顺序搜索:
376
376
  // compress 调用被硬排除在压缩之外,其思考内容会随每次请求原样重发,
377
377
  // 形成无法回收的上下文底座。仅作用于已关闭的轮次,活跃轮永不触碰;
378
378
  // 低于阈值的小思考保留。
379
- // [#482] 钉死历史思考的 wire 形态:请求时把每个 assistant 消息的
380
- // reasoning 部分折叠为一个带 <thinking> 标记的 text 部分,使序列化字节
381
- // 在宿主元数据表刷新后保持确定。默认开启;直连需要结构化 thinking
382
- // 回放的 provider 设 normalize:false。
383
379
  "reasoning": {
384
380
  "drop": true,
385
381
  "threshold": 2048,
386
- "normalize": true
387
382
  },
388
383
  // [#478] 图片归档:图片(data-URL)进入会话时即落盘,折叠后摘要里保留
389
384
  // 可重读的路径指针,而不是把像素悄悄丢掉。文件落在
package/dist/index.js CHANGED
@@ -916,12 +916,12 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
916
916
  "compress.reasoning",
917
917
  "compress.reasoning.drop",
918
918
  "compress.reasoning.threshold",
919
- "compress.reasoning.normalize",
920
919
  "compress.images",
921
920
  "compress.images.enabled",
922
921
  "compress.images.minBytes",
923
922
  "compress.images.ttlDays",
924
923
  "compress.completionReserveTokens",
924
+ "compress.emergencyTruncation",
925
925
  "gc",
926
926
  "gc.algorithm",
927
927
  "gc.promotionThreshold",
@@ -1293,7 +1293,7 @@ function validateConfigTypes(config) {
1293
1293
  if (typeof compress.reasoning !== "object" || compress.reasoning === null || Array.isArray(compress.reasoning)) {
1294
1294
  errors.push({
1295
1295
  key: "compress.reasoning",
1296
- expected: "object { drop?: boolean; threshold?: integer (>= 0); normalize?: boolean }",
1296
+ expected: "object { drop?: boolean; threshold?: integer (>= 0) }",
1297
1297
  actual: JSON.stringify(compress.reasoning)
1298
1298
  });
1299
1299
  } else {
@@ -1311,13 +1311,6 @@ function validateConfigTypes(config) {
1311
1311
  actual: JSON.stringify(compress.reasoning.threshold)
1312
1312
  });
1313
1313
  }
1314
- if (compress.reasoning.normalize !== void 0 && typeof compress.reasoning.normalize !== "boolean") {
1315
- errors.push({
1316
- key: "compress.reasoning.normalize",
1317
- expected: "boolean",
1318
- actual: typeof compress.reasoning.normalize
1319
- });
1320
- }
1321
1314
  }
1322
1315
  }
1323
1316
  if (compress.images !== void 0) {
@@ -1358,6 +1351,13 @@ function validateConfigTypes(config) {
1358
1351
  actual: typeof compress.completionReserveTokens
1359
1352
  });
1360
1353
  }
1354
+ if (compress.emergencyTruncation !== void 0 && typeof compress.emergencyTruncation !== "boolean") {
1355
+ errors.push({
1356
+ key: "compress.emergencyTruncation",
1357
+ expected: "boolean",
1358
+ actual: typeof compress.emergencyTruncation
1359
+ });
1360
+ }
1361
1361
  if (typeof compress.completionReserveTokens === "number" && compress.completionReserveTokens < 0) {
1362
1362
  errors.push({
1363
1363
  key: "compress.completionReserveTokens",
@@ -1430,6 +1430,7 @@ function validateConfigTypes(config) {
1430
1430
  const OVERRIDE_FIELD_TYPES = {
1431
1431
  showCompression: "boolean",
1432
1432
  summaryBuffer: "boolean",
1433
+ emergencyTruncation: "boolean",
1433
1434
  protectTags: "boolean",
1434
1435
  protectUserMessages: "boolean",
1435
1436
  lastSegmentSoftBlock: "boolean",
@@ -1504,7 +1505,7 @@ function validateConfigTypes(config) {
1504
1505
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
1505
1506
  errors.push({
1506
1507
  key,
1507
- expected: "object { drop?: boolean; threshold?: integer (>= 0); normalize?: boolean }",
1508
+ expected: "object { drop?: boolean; threshold?: integer (>= 0) }",
1508
1509
  actual: JSON.stringify(value)
1509
1510
  });
1510
1511
  break;
@@ -1524,13 +1525,6 @@ function validateConfigTypes(config) {
1524
1525
  actual: JSON.stringify(reasoning.threshold)
1525
1526
  });
1526
1527
  }
1527
- if (reasoning.normalize !== void 0 && typeof reasoning.normalize !== "boolean") {
1528
- errors.push({
1529
- key: `${key}.normalize`,
1530
- expected: "boolean",
1531
- actual: typeof reasoning.normalize
1532
- });
1533
- }
1534
1528
  break;
1535
1529
  }
1536
1530
  };
@@ -1824,6 +1818,7 @@ var defaultConfig = {
1824
1818
  permission: "allow",
1825
1819
  showCompression: true,
1826
1820
  summaryBuffer: true,
1821
+ emergencyTruncation: false,
1827
1822
  candidates: false,
1828
1823
  maxContextLimit: "80%",
1829
1824
  minContextLimit: "80%",
@@ -1991,7 +1986,6 @@ function mergeCompress(base, override) {
1991
1986
  if (!override) {
1992
1987
  return base;
1993
1988
  }
1994
- const normalizeResolved = override.reasoning?.normalize ?? base.reasoning?.normalize;
1995
1989
  return {
1996
1990
  permission: override.permission ?? base.permission,
1997
1991
  showCompression: override.showCompression ?? base.showCompression,
@@ -2026,15 +2020,15 @@ function mergeCompress(base, override) {
2026
2020
  preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage,
2027
2021
  reasoning: {
2028
2022
  drop: override.reasoning?.drop ?? base.reasoning?.drop ?? DEFAULT_COMPRESS_REASONING.drop,
2029
- threshold: override.reasoning?.threshold ?? base.reasoning?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold,
2030
- ...normalizeResolved !== void 0 ? { normalize: normalizeResolved } : {}
2023
+ threshold: override.reasoning?.threshold ?? base.reasoning?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
2031
2024
  },
2032
2025
  images: {
2033
2026
  enabled: override.images?.enabled ?? base.images?.enabled ?? DEFAULT_COMPRESS_IMAGES.enabled,
2034
2027
  minBytes: override.images?.minBytes ?? base.images?.minBytes ?? DEFAULT_COMPRESS_IMAGES.minBytes,
2035
2028
  ttlDays: override.images?.ttlDays ?? base.images?.ttlDays ?? DEFAULT_COMPRESS_IMAGES.ttlDays
2036
2029
  },
2037
- completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens
2030
+ completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens,
2031
+ emergencyTruncation: override.emergencyTruncation ?? base.emergencyTruncation
2038
2032
  };
2039
2033
  }
2040
2034
  function mergeCommands(base, override) {
@@ -2972,7 +2966,8 @@ function saveSessionState(sessionState, logger, sessionName) {
2972
2966
  modelContextLimit: sessionState.modelContextLimit,
2973
2967
  modelProviderID: sessionState.modelProviderID,
2974
2968
  modelID: sessionState.modelID,
2975
- imageArchive: sessionState.imageArchive
2969
+ imageArchive: sessionState.imageArchive,
2970
+ stickyTruncations: sessionState.stickyTruncations
2976
2971
  };
2977
2972
  const key = saveQueueKey(sessionState.sessionId, sessionState.storageDir);
2978
2973
  let queue = saveQueues.get(key);
@@ -5141,7 +5136,8 @@ function createSessionState() {
5141
5136
  qualityGateRetryPending: false,
5142
5137
  noContextLimitWarned: false,
5143
5138
  imageArchive: {},
5144
- imagePruneDone: false
5139
+ imagePruneDone: false,
5140
+ stickyTruncations: {}
5145
5141
  };
5146
5142
  }
5147
5143
  function resetSessionState(state) {
@@ -5187,6 +5183,7 @@ function resetSessionState(state) {
5187
5183
  state.noContextLimitWarned = false;
5188
5184
  state.imageArchive = {};
5189
5185
  state.imagePruneDone = false;
5186
+ state.stickyTruncations = {};
5190
5187
  }
5191
5188
  async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
5192
5189
  if (state.sessionId === sessionId) {
@@ -5280,6 +5277,14 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
5280
5277
  }
5281
5278
  }
5282
5279
  }
5280
+ const persistedSticky = persisted.stickyTruncations;
5281
+ if (persistedSticky && typeof persistedSticky === "object") {
5282
+ for (const [callID, output] of Object.entries(persistedSticky)) {
5283
+ if (typeof output === "string") {
5284
+ state.stickyTruncations[callID] = output;
5285
+ }
5286
+ }
5287
+ }
5283
5288
  const persistedAny = persisted;
5284
5289
  if (persistedAny._persistedMessageIds) {
5285
5290
  state.messageIds = {
@@ -9901,8 +9906,6 @@ var injectMessageIds = (state, config, messages, compressionPriorities) => {
9901
9906
  };
9902
9907
 
9903
9908
  // lib/messages/reasoning-strip.ts
9904
- var isReasoningWithText = (part) => part.type === "reasoning" && typeof part.text === "string";
9905
- var isTextWithText = (part) => part.type === "text" && typeof part.text === "string";
9906
9909
  function stripStaleMetadata(messages) {
9907
9910
  const lastUserMessage = getLastUserMessage(messages);
9908
9911
  if (lastUserMessage?.info.role !== "user") {
@@ -9971,61 +9974,6 @@ function dropCompressReasoning(messages, threshold) {
9971
9974
  }
9972
9975
  return removed;
9973
9976
  }
9974
- function normalizeThinkingForm(messages) {
9975
- let normalized = 0;
9976
- for (const message of messages) {
9977
- if (!message || message.info.role !== "assistant") {
9978
- continue;
9979
- }
9980
- const parts = Array.isArray(message.parts) ? message.parts : [];
9981
- const reasoningParts = parts.filter(isReasoningWithText);
9982
- if (reasoningParts.length === 0) {
9983
- continue;
9984
- }
9985
- const R = reasoningParts.map((part) => part.text).join("");
9986
- const textParts = parts.filter(isTextWithText);
9987
- const T = textParts.map((part) => part.text).join("");
9988
- const mergedText = `<thinking>
9989
- ${R}
9990
- </thinking>` + (T.length > 0 ? `
9991
- ${T}` : "");
9992
- const donor = textParts[0];
9993
- const newParts = [];
9994
- let inserted = false;
9995
- for (const part of parts) {
9996
- if (part.type === "reasoning" || part.type === "text") {
9997
- if (!inserted) {
9998
- if (donor) {
9999
- newParts.push({ ...donor, text: mergedText });
10000
- } else {
10001
- newParts.push({
10002
- id: `${message.info.id}-acp-think`,
10003
- sessionID: message.info.sessionID,
10004
- messageID: message.info.id,
10005
- type: "text",
10006
- text: mergedText
10007
- });
10008
- }
10009
- inserted = true;
10010
- }
10011
- continue;
10012
- }
10013
- newParts.push(part);
10014
- }
10015
- if (!inserted) {
10016
- newParts.unshift({
10017
- id: `${message.info.id}-acp-think`,
10018
- sessionID: message.info.sessionID,
10019
- messageID: message.info.id,
10020
- type: "text",
10021
- text: mergedText
10022
- });
10023
- }
10024
- message.parts = newParts;
10025
- normalized++;
10026
- }
10027
- return normalized;
10028
- }
10029
9977
 
10030
9978
  // lib/messages/priority.ts
10031
9979
  function buildPriorityMap(_config, _state, _messages) {
@@ -11403,7 +11351,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
11403
11351
  import { join as join5 } from "path";
11404
11352
  import { existsSync as existsSync5 } from "fs";
11405
11353
  import { homedir as homedir3 } from "os";
11406
- var LOG_VERSION = true ? "1.18.4-pr.484.217" : "dev";
11354
+ var LOG_VERSION = true ? "1.18.4-pr.486.219" : "dev";
11407
11355
  var LEVEL_RANK = {
11408
11356
  debug: 10,
11409
11357
  info: 20,
@@ -12298,6 +12246,71 @@ function truncateLargeToolOutputs(state, config, logger, messages) {
12298
12246
  }
12299
12247
  }
12300
12248
 
12249
+ // lib/messages/sticky-truncation.ts
12250
+ function collectToolParts(messages) {
12251
+ const parts = [];
12252
+ for (const message of messages) {
12253
+ const messageParts = Array.isArray(message.parts) ? message.parts : [];
12254
+ for (const part of messageParts) {
12255
+ if (part?.type === "tool") {
12256
+ parts.push(part);
12257
+ }
12258
+ }
12259
+ }
12260
+ return parts;
12261
+ }
12262
+ function applyStickyTruncations(state, messages, logger) {
12263
+ const frozen = state.stickyTruncations;
12264
+ if (Object.keys(frozen).length === 0) return;
12265
+ let replayed = 0;
12266
+ const seen = /* @__PURE__ */ new Set();
12267
+ for (const part of collectToolParts(messages)) {
12268
+ const callID = typeof part.callID === "string" ? part.callID : void 0;
12269
+ if (!callID) continue;
12270
+ seen.add(callID);
12271
+ const recorded = frozen[callID];
12272
+ if (recorded === void 0) continue;
12273
+ if (typeof part.state?.output !== "string" || part.state.output === recorded) continue;
12274
+ part.state.output = recorded;
12275
+ replayed++;
12276
+ }
12277
+ for (const callID of Object.keys(frozen)) {
12278
+ if (!seen.has(callID)) delete frozen[callID];
12279
+ }
12280
+ if (replayed > 0) {
12281
+ logger.debug("Sticky truncation: replayed frozen tool outputs", {
12282
+ replayed,
12283
+ frozen: Object.keys(frozen).length
12284
+ });
12285
+ }
12286
+ }
12287
+ function snapshotToolOutputs(messages) {
12288
+ const snapshot = /* @__PURE__ */ new Map();
12289
+ for (const part of collectToolParts(messages)) {
12290
+ const callID = typeof part.callID === "string" ? part.callID : void 0;
12291
+ if (!callID) continue;
12292
+ if (typeof part.state?.output !== "string") continue;
12293
+ snapshot.set(callID, part.state.output);
12294
+ }
12295
+ return snapshot;
12296
+ }
12297
+ function recordStickyTruncations(state, messages, before, logger) {
12298
+ let recorded = 0;
12299
+ for (const part of collectToolParts(messages)) {
12300
+ const callID = typeof part.callID === "string" ? part.callID : void 0;
12301
+ if (!callID) continue;
12302
+ if (typeof part.state?.output !== "string") continue;
12303
+ const prior = before.get(callID);
12304
+ if (prior === void 0 || prior === part.state.output) continue;
12305
+ if (state.stickyTruncations[callID] === part.state.output) continue;
12306
+ state.stickyTruncations[callID] = part.state.output;
12307
+ recorded++;
12308
+ }
12309
+ if (recorded > 0) {
12310
+ logger.info("Sticky truncation: froze truncated tool outputs", { recorded });
12311
+ }
12312
+ }
12313
+
12301
12314
  // lib/messages/enforce-budget.ts
12302
12315
  var DEFAULT_COMPLETION_RESERVE_TOKENS = 32768;
12303
12316
  var TRUNCATION_MARKER2 = "[truncated for context space";
@@ -13602,14 +13615,6 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
13602
13615
  });
13603
13616
  }
13604
13617
  }
13605
- if (reasoningConfig?.normalize !== false) {
13606
- const normalizedThinking = normalizeThinkingForm(output.messages);
13607
- if (normalizedThinking > 0) {
13608
- logger.debug("compress.reasoning: pinned thinking wire form", {
13609
- normalizedMessages: normalizedThinking
13610
- });
13611
- }
13612
- }
13613
13618
  ensureBuiltinFiltersRegistered();
13614
13619
  const effectiveLimit = resolveEffectiveContextLimit(state, config);
13615
13620
  applyMessageFilters(output.messages, config.messageFilters, logger, {
@@ -13676,13 +13681,18 @@ ${text}`);
13676
13681
  prePruneTokens,
13677
13682
  candidateMessages
13678
13683
  );
13679
- truncateLargeToolOutputs(
13680
- state,
13681
- config,
13682
- logger,
13683
- output.messages.filter((message) => !isSyntheticMessage(message))
13684
- );
13685
- enforceContextBudget(state, config, logger, output.messages);
13684
+ if (config.compress?.emergencyTruncation === true) {
13685
+ applyStickyTruncations(state, output.messages, logger);
13686
+ const toolOutputsBefore = snapshotToolOutputs(output.messages);
13687
+ truncateLargeToolOutputs(
13688
+ state,
13689
+ config,
13690
+ logger,
13691
+ output.messages.filter((message) => !isSyntheticMessage(message))
13692
+ );
13693
+ enforceContextBudget(state, config, logger, output.messages);
13694
+ recordStickyTruncations(state, output.messages, toolOutputsBefore, logger);
13695
+ }
13686
13696
  injectMessageIds(state, config, output.messages, compressionPriorities);
13687
13697
  hideFailedCompressCalls(output.messages);
13688
13698
  stripStaleMetadata(output.messages);
@@ -14121,7 +14131,7 @@ var server = (async (ctx) => {
14121
14131
  }
14122
14132
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
14123
14133
  logger.info("ACP plugin initialized", {
14124
- version: true ? "1.18.4-pr.484.217" : "dev",
14134
+ version: true ? "1.18.4-pr.486.219" : "dev",
14125
14135
  workspace: ctx.directory,
14126
14136
  logLevel: logger.level,
14127
14137
  debug: config.debug,