@ai-agent-forge/agent-forge 0.86.2 → 0.86.3

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.
Files changed (132) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/bundle/.build-manifest.json +151 -145
  3. package/dist/bundle/chunks/{anthropic-messages-RJYU4TCY.js → anthropic-messages-EVFQ6MLR.js} +3 -3
  4. package/dist/bundle/chunks/{azure-openai-responses-LESTCUL7.js → azure-openai-responses-WQCZWFAH.js} +1 -1
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  6. package/dist/bundle/chunks/{chunk-MDOTREMR.js → chunk-3WE3W52L.js} +2 -2
  7. package/dist/bundle/chunks/chunk-F6SBZUJG.js +141 -0
  8. package/dist/bundle/chunks/{chunk-SER2HCLE.js → chunk-N6BQJSGH.js} +1 -1
  9. package/dist/bundle/chunks/chunk-RK2X2KU2.js +1442 -0
  10. package/dist/bundle/chunks/chunk-TBPTHASB.js +2 -0
  11. package/dist/bundle/chunks/github-copilot.js +1 -1
  12. package/dist/bundle/chunks/{google-generative-ai-NZ66X6K7.js → google-generative-ai-ISTTUZMW.js} +1 -1
  13. package/dist/bundle/chunks/{google-vertex-2ZZOFBNQ.js → google-vertex-NS7ZF7DK.js} +1 -1
  14. package/dist/bundle/chunks/jiti-static-loader-CDSTNEDU.js +2 -0
  15. package/dist/bundle/chunks/{mistral-conversations-B6DZRMEK.js → mistral-conversations-GINO6Z4I.js} +1 -1
  16. package/dist/bundle/chunks/{openai-codex-responses-UFH6O5K6.js → openai-codex-responses-YDJSZOIH.js} +1 -1
  17. package/dist/bundle/chunks/{openai-completions-A55GHA4O.js → openai-completions-UPRWTQPT.js} +3 -3
  18. package/dist/bundle/chunks/openai-responses-O63W6HQ3.js +2 -0
  19. package/dist/bundle/chunks/virtual-modules-O72ICTMQ.js +2 -0
  20. package/dist/bundle/cli.js +1 -1
  21. package/dist/bundle/index.js +1 -1
  22. package/dist/bundle/rpc-entry.js +1 -1
  23. package/dist/cli/args.d.ts.map +1 -1
  24. package/dist/cli/args.js +14 -4
  25. package/dist/cli/args.js.map +1 -1
  26. package/dist/cli/session-picker.d.ts +1 -1
  27. package/dist/cli/session-picker.d.ts.map +1 -1
  28. package/dist/cli/session-picker.js +8 -2
  29. package/dist/cli/session-picker.js.map +1 -1
  30. package/dist/core/agent-session-compaction.d.ts +2 -2
  31. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  32. package/dist/core/agent-session-compaction.js +120 -44
  33. package/dist/core/agent-session-compaction.js.map +1 -1
  34. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  35. package/dist/core/agent-session-runtime.js +1 -1
  36. package/dist/core/agent-session-runtime.js.map +1 -1
  37. package/dist/core/agent-session.d.ts +13 -4
  38. package/dist/core/agent-session.d.ts.map +1 -1
  39. package/dist/core/agent-session.js +91 -93
  40. package/dist/core/agent-session.js.map +1 -1
  41. package/dist/core/compaction/compaction.d.ts +15 -0
  42. package/dist/core/compaction/compaction.d.ts.map +1 -1
  43. package/dist/core/compaction/compaction.js +20 -1
  44. package/dist/core/compaction/compaction.js.map +1 -1
  45. package/dist/core/export-html/template.css +10 -0
  46. package/dist/core/export-html/template.js +50 -17
  47. package/dist/core/extensions/capability-plugin.d.ts.map +1 -1
  48. package/dist/core/extensions/capability-plugin.js +25 -30
  49. package/dist/core/extensions/capability-plugin.js.map +1 -1
  50. package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
  51. package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
  52. package/dist/core/extensions/jiti-static-loader.js +5 -0
  53. package/dist/core/extensions/jiti-static-loader.js.map +1 -0
  54. package/dist/core/extensions/virtual-modules.d.ts +3 -0
  55. package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
  56. package/dist/core/extensions/virtual-modules.js +32 -0
  57. package/dist/core/extensions/virtual-modules.js.map +1 -0
  58. package/dist/core/facade/coding-agent-facade.d.ts +3 -3
  59. package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
  60. package/dist/core/facade/coding-agent-facade.js.map +1 -1
  61. package/dist/core/facade/coding-agent-runtime-facade.d.ts +2 -2
  62. package/dist/core/facade/coding-agent-runtime-facade.d.ts.map +1 -1
  63. package/dist/core/facade/coding-agent-runtime-facade.js +4 -4
  64. package/dist/core/facade/coding-agent-runtime-facade.js.map +1 -1
  65. package/dist/core/model-resolver.d.ts.map +1 -1
  66. package/dist/core/model-resolver.js +2 -2
  67. package/dist/core/model-resolver.js.map +1 -1
  68. package/dist/core/package-install-host.d.ts +1 -1
  69. package/dist/core/package-install-host.d.ts.map +1 -1
  70. package/dist/core/package-install-host.js +4 -3
  71. package/dist/core/package-install-host.js.map +1 -1
  72. package/dist/core/prompt-templates.d.ts +6 -1
  73. package/dist/core/prompt-templates.d.ts.map +1 -1
  74. package/dist/core/prompt-templates.js +61 -35
  75. package/dist/core/prompt-templates.js.map +1 -1
  76. package/dist/core/resource-loader.d.ts.map +1 -1
  77. package/dist/core/resource-loader.js +6 -2
  78. package/dist/core/resource-loader.js.map +1 -1
  79. package/dist/core/session-manager.d.ts +23 -4
  80. package/dist/core/session-manager.d.ts.map +1 -1
  81. package/dist/core/session-manager.js +146 -108
  82. package/dist/core/session-manager.js.map +1 -1
  83. package/dist/core/tools/bash.d.ts +2 -1
  84. package/dist/core/tools/bash.d.ts.map +1 -1
  85. package/dist/core/tools/bash.js +18 -3
  86. package/dist/core/tools/bash.js.map +1 -1
  87. package/dist/main.d.ts.map +1 -1
  88. package/dist/main.js +6 -7
  89. package/dist/main.js.map +1 -1
  90. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  91. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  92. package/dist/modes/interactive/components/session-selector.js +74 -50
  93. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  94. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  95. package/dist/modes/interactive/components/tool-execution.js +18 -9
  96. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  97. package/dist/modes/interactive/interactive-mode.d.ts +2 -2
  98. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  99. package/dist/modes/interactive/interactive-mode.js +35 -32
  100. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  101. package/dist/modes/rpc/rpc-client.d.ts +14 -6
  102. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  103. package/dist/modes/rpc/rpc-client.js +47 -10
  104. package/dist/modes/rpc/rpc-client.js.map +1 -1
  105. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  106. package/dist/modes/rpc/rpc-mode.js +9 -10
  107. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  108. package/dist/modes/rpc/rpc-types.d.ts +10 -1
  109. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  110. package/dist/modes/rpc/rpc-types.js.map +1 -1
  111. package/dist/remote/server-assembly.d.ts.map +1 -1
  112. package/dist/remote/server-assembly.js +1 -1
  113. package/dist/remote/server-assembly.js.map +1 -1
  114. package/dist/utils/clipboard-image.d.ts.map +1 -1
  115. package/dist/utils/clipboard-image.js +1 -12
  116. package/dist/utils/clipboard-image.js.map +1 -1
  117. package/dist/utils/clipboard.d.ts.map +1 -1
  118. package/dist/utils/clipboard.js +88 -13
  119. package/dist/utils/clipboard.js.map +1 -1
  120. package/dist/utils/mime.d.ts.map +1 -1
  121. package/dist/utils/mime.js +1 -1
  122. package/dist/utils/mime.js.map +1 -1
  123. package/dist/utils/wsl.d.ts +3 -0
  124. package/dist/utils/wsl.d.ts.map +1 -0
  125. package/dist/utils/wsl.js +15 -0
  126. package/dist/utils/wsl.js.map +1 -0
  127. package/docs/rpc.md +7 -5
  128. package/docs/sdk.md +9 -8
  129. package/package.json +1 -1
  130. package/dist/bundle/chunks/chunk-JR3Q27QP.js +0 -1581
  131. package/dist/bundle/chunks/chunk-KQEGUM34.js +0 -2
  132. package/dist/bundle/chunks/openai-responses-TJLXRAXR.js +0 -2
@@ -12,7 +12,7 @@ import { completeSimple, streamSimple } from "@agent-forge/ai/compat";
12
12
  import { detachedSessionSnapshot, SessionCancelledError, } from "./agent-session-internal.js";
13
13
  import { dispatchCompactionDecisionLifecycle, dispatchCompactionFailedLifecycle } from "./agent-session-lifecycle.js";
14
14
  import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
15
- import { calculateContextTokens, collectEntriesForBranchSummary, estimateContextTokens, estimateTokens, isTurnStartMessage, } from "./compaction/index.js";
15
+ import { calculateContextTokens, collectEntriesForBranchSummary, effectiveKeepRecentTokens, estimateContextTokens, estimateTokens, isTurnStartMessage, } from "./compaction/index.js";
16
16
  import { DEFAULT_COMPACTION_POLICY_NAME, SESSION_COMPACTION_COMMIT_EVENT_ID, SESSION_COMPACTION_COMMIT_EVENT_VERSION, } from "./extensions/index.js";
17
17
  import { SUMMARY_MODEL_INVOCATION_ERROR_CODES } from "./extensions/public-api.js";
18
18
  import { getLatestCompactionEntry } from "./session-manager.js";
@@ -48,11 +48,15 @@ const OVERFLOW_TRIM_STAGE2_TARGET_TOKENS = 10_000;
48
48
  * 冷却把"阈值带上反复触发中部改写"的最坏情形封顶。
49
49
  */
50
50
  const MICROCOMPACT_COOLDOWN_ASSISTANT_RESPONSES = 3;
51
- /** 溢出裁剪失败文案:实际发生过裁剪时附上前缀,0 次保持策略原文。 */
52
- function overflowTrimFailureMessage(attempts, failureMessage) {
53
- return attempts > 0
54
- ? `Context overflow persists after compaction and ${attempts} trim attempts. ${failureMessage}`
55
- : failureMessage;
51
+ /**
52
+ * 溢出/截断裁剪失败文案:实际发生过裁剪时附上前缀(分语:context overflow
53
+ * 维持既有精确文案,recoverable length 用截断响应措辞),0 次保持策略原文。
54
+ */
55
+ function overflowTrimFailureMessage(attempts, failureMessage, kind) {
56
+ if (attempts <= 0)
57
+ return failureMessage;
58
+ const head = kind === "contextOverflow" ? "Context overflow" : "Truncated response";
59
+ return `${head} persists after compaction and ${attempts} trim attempts. ${failureMessage}`;
56
60
  }
57
61
  /**
58
62
  * Trim the runtime projection for one overflow-recovery stage: drop the oldest
@@ -99,10 +103,10 @@ function trimOverflowProjection(messages, stage, keepRecentTokens) {
99
103
  const floorStart = turnStarts[turnStarts.length - 1];
100
104
  return floorStart === 0 ? messages : messages.slice(floorStart);
101
105
  }
102
- export async function getRequiredRequestAuth(session, model) {
106
+ export async function getRequiredRequestAuth(session, model, signal) {
103
107
  let result;
104
108
  try {
105
- result = await session._modelRuntime.getAuth(model);
109
+ result = await session._modelRuntime.getAuth(model, { signal });
106
110
  }
107
111
  catch (error) {
108
112
  const cause = error instanceof Error ? error.cause : undefined;
@@ -128,12 +132,12 @@ export async function getRequiredRequestAuth(session, model) {
128
132
  }
129
133
  throw new Error(formatNoApiKeyFoundMessage(model.provider));
130
134
  }
131
- export async function getSummarizationRequestAuth(session, model) {
135
+ export async function getSummarizationRequestAuth(session, model, signal) {
132
136
  if (session.agent.streamFunction === streamSimple) {
133
- return getRequiredRequestAuth(session, model);
137
+ return getRequiredRequestAuth(session, model, signal);
134
138
  }
135
139
  try {
136
- const result = await session._modelRuntime.getAuth(model);
140
+ const result = await session._modelRuntime.getAuth(model, { signal });
137
141
  if (!result)
138
142
  return { model };
139
143
  const requestModel = result.auth.baseUrl ? { ...model, baseUrl: result.auth.baseUrl } : model;
@@ -144,7 +148,12 @@ export async function getSummarizationRequestAuth(session, model) {
144
148
  env: result.env,
145
149
  };
146
150
  }
147
- catch {
151
+ catch (error) {
152
+ // Auth resolution normally falls back to an unauthenticated summary when
153
+ // it fails, but an abort must not be swallowed into that fallback: the
154
+ // caller's signal decided this run is over.
155
+ if (signal?.aborted)
156
+ throw error;
148
157
  return { model };
149
158
  }
150
159
  }
@@ -266,6 +275,13 @@ export async function compactBeforeNextAssistantResponse(session, context, signa
266
275
  if (!model || model.contextWindow <= 0) {
267
276
  return context;
268
277
  }
278
+ // Adaptive keepRecentTokens:小窗口把近期保护预算钳到可摘要窗口的一半
279
+ //(effectiveKeepRecentTokens,动机见其注释;大窗口原值不变),本路径与
280
+ // checkCompaction/compactInternal/runAutoCompactionInternal 一致使用 effective 值。
281
+ const effectiveSettings = {
282
+ ...settings,
283
+ keepRecentTokens: effectiveKeepRecentTokens(settings.keepRecentTokens, model.contextWindow, settings.reserveTokens),
284
+ };
269
285
  const contextTokens = estimateContextTokens(context.messages).tokens;
270
286
  const decision = await decideCompaction(session, {
271
287
  reason: "threshold",
@@ -277,7 +293,10 @@ export async function compactBeforeNextAssistantResponse(session, context, signa
277
293
  reserveTokens: settings.reserveTokens,
278
294
  },
279
295
  canContinue: false,
280
- // 设置开关 ∧ 冷却归零:被任一条件挡住时策略不做便宜缓解,直接走全量压缩。
296
+ // 便宜缓解的事实输入与门控,与 checkCompaction 的 threshold decide 相同
297
+ //(设置开关 ∧ 冷却归零):被任一条件挡住时策略不做便宜缓解,直接走全量压缩。
298
+ branchEntries: session.sessionManager.getBranch(),
299
+ keepRecentTokens: effectiveSettings.keepRecentTokens,
281
300
  microcompactEnabled: settings.microcompactEnabled && session._microcompactCooldown === 0,
282
301
  signal: signal ?? session._policyDecisionAbortController?.signal,
283
302
  });
@@ -292,6 +311,27 @@ export async function compactBeforeNextAssistantResponse(session, context, signa
292
311
  reserveTokens: settings.reserveTokens,
293
312
  ...(decision.failureMessage === undefined ? {} : { failureMessage: decision.failureMessage }),
294
313
  });
314
+ // 策略便宜缓解(通用通道),镜像 checkCompaction 的 relief 落地分支:宿主原样
315
+ // 追加条目并重投影上下文,条目语义与投影归策略。执行上下文:本函数经默认
316
+ // prepareNextTurn loop link 在 agent 回合之间被 agent run await,不在
317
+ // _compactionQueue 内;手动压缩入口 compactInternal 先 _abortInternal() 等待
318
+ // 活跃回合结束,而本路径位于回合执行链上,回合结束前必先完成——relief 落地
319
+ // (同步 append + 投影)不会与手动压缩的摘要/提交交错,与 postAgentRun 的
320
+ // relief 落地同模式,不加队列锁。
321
+ if (decision.relief !== undefined && !decision.compact) {
322
+ session.sessionManager.appendCustomEntry(decision.relief.customType, decision.relief.data);
323
+ const sessionContext = session.sessionManager.buildSessionContext();
324
+ await session._applyContextTransition(sessionContext.messages, "compaction");
325
+ // 冷却起点:relief 已落地即开始计数(由 assistant message_end 递减)。
326
+ session._microcompactCooldown = MICROCOMPACT_COOLDOWN_ASSISTANT_RESPONSES;
327
+ session._hostLogger?.info("compaction", "policy relief applied", {
328
+ fields: { customType: decision.relief.customType, path: "pre-response" },
329
+ });
330
+ return {
331
+ ...context,
332
+ messages: session.agent.state.messages.slice(),
333
+ };
334
+ }
295
335
  if (!decision.compact)
296
336
  return context;
297
337
  await session._runAutoCompaction("threshold", false);
@@ -534,9 +574,16 @@ export async function compactInternal(session, customInstructions) {
534
574
  if (!session.model) {
535
575
  throw new Error(formatNoModelSelectedMessage());
536
576
  }
537
- const auth = await getSummarizationRequestAuth(session, session.model);
577
+ const auth = await getSummarizationRequestAuth(session, session.model, session._compactionAbortController.signal);
538
578
  const { pathEntries, expectedRevision, operationId } = captureCompactionCommitBoundary(session, "manual");
539
- const settings = session.settingsManager.getCompactionSettings();
579
+ const rawSettings = session.settingsManager.getCompactionSettings();
580
+ // Adaptive keepRecentTokens(effectiveKeepRecentTokens,动机见其注释):手动
581
+ // 压缩与自动路径同一 effective 值,小窗口下 /compact 不再因保护预算吃满
582
+ // 整个可摘要窗口而报 "Nothing to compact"。
583
+ const settings = {
584
+ ...rawSettings,
585
+ keepRecentTokens: effectiveKeepRecentTokens(rawSettings.keepRecentTokens, session.model.contextWindow, rawSettings.reserveTokens),
586
+ };
540
587
  const execution = executionFacts(session, "compaction", operationId);
541
588
  const preparation = await prepareCompaction(session, pathEntries, settings, execution, session._compactionAbortController.signal);
542
589
  if (!preparation) {
@@ -649,6 +696,13 @@ export async function checkCompaction(session, assistantMessage, skipAbortedChec
649
696
  if (signal?.aborted)
650
697
  return false;
651
698
  const settings = session.settingsManager.getCompactionSettings();
699
+ // Adaptive keepRecentTokens(effectiveKeepRecentTokens,动机见其注释:座席实测
700
+ // 30K 窗口 20K 保护预算使可摘要前缀为零,阈值反复命中却无事可做)。一次性
701
+ // 替换后全文一致使用:下方 trim 阶梯 stage 0 目标与 threshold decide 请求。
702
+ const effectiveSettings = {
703
+ ...settings,
704
+ keepRecentTokens: effectiveKeepRecentTokens(settings.keepRecentTokens, session.model?.contextWindow ?? 0, settings.reserveTokens),
705
+ };
652
706
  // Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
653
707
  if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
654
708
  return false;
@@ -692,11 +746,12 @@ export async function checkCompaction(session, assistantMessage, skipAbortedChec
692
746
  ...(decision.failureMessage === undefined ? {} : { failureMessage: decision.failureMessage }),
693
747
  });
694
748
  if (!decision.compact) {
695
- // 溢出裁剪兜底:压缩恢复(recoveryAttempted)后仍 context overflow 时,
696
- // 先对运行时投影做逐级裁剪重试,3 级全失败才走策略 failureMessage 失败
697
- // 路径。执行逻辑归 AgentSession,策略接口与事件面不变。
749
+ // 溢出裁剪兜底:压缩恢复(recoveryAttempted)后仍 context overflow 或
750
+ // recoverable length(截断响应=输出被掐,缩输入可让输出有空间,turn 边界
751
+ // 裁剪对两种溢出同样有效)时,先对运行时投影做逐级裁剪重试,3 级全失败才
752
+ // 走策略 failureMessage 失败路径。执行逻辑归 AgentSession,策略接口与事件面不变。
698
753
  if (decision.failureMessage &&
699
- contextOverflow &&
754
+ (contextOverflow || recoverableLength) &&
700
755
  session._overflowRecoveryAttempted &&
701
756
  session._overflowTrimStage < OVERFLOW_TRIM_MAX_STAGES) {
702
757
  const stage = session._overflowTrimStage;
@@ -717,11 +772,11 @@ export async function checkCompaction(session, assistantMessage, skipAbortedChec
717
772
  const base = messages.length > 0 && messages[messages.length - 1].role === "assistant"
718
773
  ? messages.slice(0, -1)
719
774
  : messages;
720
- const trimmed = trimOverflowProjection(base, stage, settings.keepRecentTokens);
775
+ const trimmed = trimOverflowProjection(base, stage, effectiveSettings.keepRecentTokens);
721
776
  // 返回原引用 = 已无可裁(估算已在目标内或无 turn 边界):重试注定
722
777
  // 同样溢出,不空转请求,按策略失败文案直接失败(级数未增,无前缀)。
723
778
  if (trimmed.length === 0 || trimmed === base) {
724
- const errorMessage = overflowTrimFailureMessage(stage, failureMessage);
779
+ const errorMessage = overflowTrimFailureMessage(stage, failureMessage, contextOverflow ? "contextOverflow" : "recoverableLength");
725
780
  session._emit({
726
781
  type: "compaction_end",
727
782
  reason: "overflow",
@@ -785,9 +840,7 @@ export async function checkCompaction(session, assistantMessage, skipAbortedChec
785
840
  });
786
841
  }
787
842
  if (decision.failureMessage) {
788
- const errorMessage = contextOverflow
789
- ? overflowTrimFailureMessage(session._overflowTrimStage, decision.failureMessage)
790
- : decision.failureMessage;
843
+ const errorMessage = overflowTrimFailureMessage(session._overflowTrimStage, decision.failureMessage, contextOverflow ? "contextOverflow" : "recoverableLength");
791
844
  session._emit({
792
845
  type: "compaction_end",
793
846
  reason: "overflow",
@@ -858,8 +911,8 @@ export async function checkCompaction(session, assistantMessage, skipAbortedChec
858
911
  // 策略读取,内核不解释其内容。门控(设置开关 ∧ 冷却归零)被挡住时,
859
912
  // 策略直接走全量压缩。
860
913
  branchEntries: session.sessionManager.getBranch(),
861
- keepRecentTokens: settings.keepRecentTokens,
862
- microcompactEnabled: settings.microcompactEnabled && session._microcompactCooldown === 0,
914
+ keepRecentTokens: effectiveSettings.keepRecentTokens,
915
+ microcompactEnabled: effectiveSettings.microcompactEnabled && session._microcompactCooldown === 0,
863
916
  signal,
864
917
  });
865
918
  await dispatchCompactionDecisionLifecycle(session, {
@@ -930,13 +983,19 @@ export async function runAutoCompactionInternal(session, reason, willRetry) {
930
983
  session._autoCompactionAbortController = undefined;
931
984
  throw error;
932
985
  }
933
- const settings = session.settingsManager.getCompactionSettings();
986
+ const rawSettings = session.settingsManager.getCompactionSettings();
987
+ // Adaptive keepRecentTokens(effectiveKeepRecentTokens,动机见其注释):
988
+ // 小窗口下可摘要前缀不再被保护预算吃满,阈值命中的压缩真实落地而非空转。
989
+ const settings = {
990
+ ...rawSettings,
991
+ keepRecentTokens: effectiveKeepRecentTokens(rawSettings.keepRecentTokens, session.model?.contextWindow ?? 0, rawSettings.reserveTokens),
992
+ };
934
993
  let started = false;
935
994
  try {
936
995
  if (!session.model) {
937
996
  return false;
938
997
  }
939
- const auth = await getSummarizationRequestAuth(session, session.model);
998
+ const auth = await getSummarizationRequestAuth(session, session.model, autoCompactionSignal);
940
999
  const { pathEntries, expectedRevision, operationId } = captureCompactionCommitBoundary(session, reason);
941
1000
  const execution = executionFacts(session, "compaction", operationId);
942
1001
  const preparation = await prepareCompaction(session, pathEntries, settings, execution, autoCompactionSignal);
@@ -1004,22 +1063,39 @@ export async function runAutoCompactionInternal(session, reason, willRetry) {
1004
1063
  return session.agent.hasQueuedMessages();
1005
1064
  }
1006
1065
  catch (error) {
1007
- const errorMessage = error instanceof Error ? error.message : "compaction failed";
1066
+ const message = error instanceof Error ? error.message : "compaction failed";
1067
+ // Cancellation is decided by the controller state, not the error shape:
1068
+ // aborts surface as AbortError from the summary pipeline, but so can
1069
+ // unrelated failures — only the signal tells them apart.
1070
+ const aborted = autoCompactionSignal.aborted;
1008
1071
  if (started) {
1009
- const formattedErrorMessage = reason === "overflow"
1010
- ? `Context overflow recovery failed: ${errorMessage}`
1011
- : `Auto-compaction failed: ${errorMessage}`;
1012
- session._emit({
1013
- type: "compaction_end",
1014
- reason,
1015
- result: undefined,
1016
- aborted: false,
1017
- willRetry: false,
1018
- errorMessage: formattedErrorMessage,
1019
- });
1020
- // 压缩失败打点:与下方 failed 事件同一事实;started 前的取消路径不打点。
1021
- session._hostLogger?.error("compaction", "compaction failed", { fields: { reason }, cause: error });
1022
- await dispatchCompactionFailedLifecycle(session, { reason, errorMessage: formattedErrorMessage });
1072
+ if (aborted) {
1073
+ // 取消按正常结果收场(aborted:true、无错误文案),不打失败日志、
1074
+ // 不 dispatch failed lifecycle(契约见 dispatchCompactionFailedLifecycle 注释)。
1075
+ session._emit({
1076
+ type: "compaction_end",
1077
+ reason,
1078
+ result: undefined,
1079
+ aborted: true,
1080
+ willRetry: false,
1081
+ });
1082
+ }
1083
+ else {
1084
+ const formattedErrorMessage = reason === "overflow"
1085
+ ? `Context overflow recovery failed: ${message}`
1086
+ : `Auto-compaction failed: ${message}`;
1087
+ session._emit({
1088
+ type: "compaction_end",
1089
+ reason,
1090
+ result: undefined,
1091
+ aborted: false,
1092
+ willRetry: false,
1093
+ errorMessage: formattedErrorMessage,
1094
+ });
1095
+ // 压缩失败打点:与下方 failed 事件同一事实;取消路径不打点。
1096
+ session._hostLogger?.error("compaction", "compaction failed", { fields: { reason }, cause: error });
1097
+ await dispatchCompactionFailedLifecycle(session, { reason, errorMessage: formattedErrorMessage });
1098
+ }
1023
1099
  }
1024
1100
  return false;
1025
1101
  }
@@ -1159,7 +1235,7 @@ export async function navigateTree(session, targetId, options = {}) {
1159
1235
  let summaryUsage;
1160
1236
  if (options.summarize && entriesToSummarize.length > 0) {
1161
1237
  const model = session.model;
1162
- const auth = await getSummarizationRequestAuth(session, model);
1238
+ const auth = await getSummarizationRequestAuth(session, model, session._branchSummaryAbortController.signal);
1163
1239
  const branchSummarySettings = session.settingsManager.getBranchSummarySettings();
1164
1240
  const runtime = session._capabilityRuntime;
1165
1241
  if (!runtime || !runtime.hasBranchSummaryPolicy(session._branchSummaryPolicyName)) {