@downcity/agent 1.1.232 → 1.1.237

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 (165) hide show
  1. package/bin/agent/local/Agent.d.ts +1 -1
  2. package/bin/agent/local/Agent.js +2 -2
  3. package/bin/agent/local/Agent.js.map +1 -1
  4. package/bin/agent/local/services/AgentSessions.d.ts +4 -4
  5. package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
  6. package/bin/agent/local/services/AgentSessions.js +8 -8
  7. package/bin/agent/local/services/AgentSessions.js.map +1 -1
  8. package/bin/agent/remote/RemoteSession.d.ts +2 -0
  9. package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
  10. package/bin/agent/remote/RemoteSession.js +4 -0
  11. package/bin/agent/remote/RemoteSession.js.map +1 -1
  12. package/bin/agent/remote/RemoteTransport.d.ts +2 -0
  13. package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
  14. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +1 -0
  15. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
  16. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +11 -0
  17. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
  18. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +1 -0
  19. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
  20. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +3 -0
  21. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
  22. package/bin/executor/Executor.d.ts +20 -2
  23. package/bin/executor/Executor.d.ts.map +1 -1
  24. package/bin/executor/Executor.js +102 -18
  25. package/bin/executor/Executor.js.map +1 -1
  26. package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts +6 -0
  27. package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts.map +1 -1
  28. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts +0 -9
  29. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts.map +1 -1
  30. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js +4 -38
  31. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js.map +1 -1
  32. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts +1 -3
  33. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts.map +1 -1
  34. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js +7 -59
  35. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js.map +1 -1
  36. package/bin/executor/core-engine/CoreEngineContextCompaction.d.ts +38 -0
  37. package/bin/executor/core-engine/CoreEngineContextCompaction.d.ts.map +1 -0
  38. package/bin/executor/core-engine/CoreEngineContextCompaction.js +392 -0
  39. package/bin/executor/core-engine/CoreEngineContextCompaction.js.map +1 -0
  40. package/bin/executor/core-engine/CoreEngineMessageState.d.ts +15 -0
  41. package/bin/executor/core-engine/CoreEngineMessageState.d.ts.map +1 -1
  42. package/bin/executor/core-engine/CoreEngineMessageState.js +20 -0
  43. package/bin/executor/core-engine/CoreEngineMessageState.js.map +1 -1
  44. package/bin/executor/core-engine/CoreEngineRunner.d.ts +9 -5
  45. package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
  46. package/bin/executor/core-engine/CoreEngineRunner.js +146 -35
  47. package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
  48. package/bin/executor/store/history/SessionHistoryStore.d.ts +4 -10
  49. package/bin/executor/store/history/SessionHistoryStore.d.ts.map +1 -1
  50. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
  51. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +1 -3
  52. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
  53. package/bin/executor/types/SessionRun.d.ts +8 -0
  54. package/bin/executor/types/SessionRun.d.ts.map +1 -1
  55. package/bin/index.d.ts +1 -1
  56. package/bin/index.d.ts.map +1 -1
  57. package/bin/index.js +1 -1
  58. package/bin/index.js.map +1 -1
  59. package/bin/plugin/core/PluginLocalExecution.js +3 -3
  60. package/bin/plugin/core/PluginLocalExecution.js.map +1 -1
  61. package/bin/plugin/core/PluginRegistry.d.ts +20 -2
  62. package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
  63. package/bin/plugin/core/PluginRegistry.js +100 -3
  64. package/bin/plugin/core/PluginRegistry.js.map +1 -1
  65. package/bin/rpc/Client.d.ts +6 -4
  66. package/bin/rpc/Client.d.ts.map +1 -1
  67. package/bin/rpc/Client.js +11 -0
  68. package/bin/rpc/Client.js.map +1 -1
  69. package/bin/session/Session.d.ts +7 -3
  70. package/bin/session/Session.d.ts.map +1 -1
  71. package/bin/session/Session.js +26 -19
  72. package/bin/session/Session.js.map +1 -1
  73. package/bin/session/SessionSystemBuilder.d.ts.map +1 -1
  74. package/bin/session/SessionSystemBuilder.js +3 -1
  75. package/bin/session/SessionSystemBuilder.js.map +1 -1
  76. package/bin/session/recorder/SessionRecorderCompaction.d.ts.map +1 -1
  77. package/bin/session/recorder/SessionRecorderCompaction.js +102 -60
  78. package/bin/session/recorder/SessionRecorderCompaction.js.map +1 -1
  79. package/bin/session/runtime/SessionPromptRuntime.d.ts +11 -7
  80. package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
  81. package/bin/session/runtime/SessionPromptRuntime.js +22 -24
  82. package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
  83. package/bin/session/services/SessionStateService.d.ts +10 -6
  84. package/bin/session/services/SessionStateService.d.ts.map +1 -1
  85. package/bin/session/services/SessionStateService.js +29 -20
  86. package/bin/session/services/SessionStateService.js.map +1 -1
  87. package/bin/session/services/SessionTurnService.d.ts +13 -3
  88. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  89. package/bin/session/services/SessionTurnService.js +90 -7
  90. package/bin/session/services/SessionTurnService.js.map +1 -1
  91. package/bin/types/agent/SessionActor.d.ts +8 -0
  92. package/bin/types/agent/SessionActor.d.ts.map +1 -1
  93. package/bin/types/executor/SessionRunContext.d.ts +16 -8
  94. package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
  95. package/bin/types/plugin/PluginRuntime.d.ts +36 -4
  96. package/bin/types/plugin/PluginRuntime.d.ts.map +1 -1
  97. package/bin/types/plugin/PluginState.d.ts +10 -0
  98. package/bin/types/plugin/PluginState.d.ts.map +1 -1
  99. package/bin/types/rpc/RpcProtocol.d.ts +21 -0
  100. package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
  101. package/bin/types/runtime/agent/AgentContext.d.ts +2 -2
  102. package/bin/types/runtime/agent/AgentContext.js +1 -1
  103. package/bin/types/session/SessionLocalState.d.ts +2 -2
  104. package/bin/types/session/SessionOptions.d.ts +5 -5
  105. package/bin/types/session/SessionOptions.d.ts.map +1 -1
  106. package/bin/types/session/SessionQueueCommand.d.ts +68 -0
  107. package/bin/types/session/SessionQueueCommand.d.ts.map +1 -0
  108. package/bin/types/session/SessionQueueCommand.js +10 -0
  109. package/bin/types/session/SessionQueueCommand.js.map +1 -0
  110. package/bin/utils/logger/Logger.d.ts +7 -9
  111. package/bin/utils/logger/Logger.d.ts.map +1 -1
  112. package/bin/utils/logger/Logger.js +11 -9
  113. package/bin/utils/logger/Logger.js.map +1 -1
  114. package/package.json +3 -3
  115. package/scripts/core-engine-context-compaction.test.mjs +411 -0
  116. package/scripts/logger-isolation.test.mjs +55 -0
  117. package/scripts/multi-agent-plugin-isolation.test.mjs +289 -0
  118. package/scripts/plugin-tool-bridge.test.mjs +52 -0
  119. package/scripts/remote-session-reconnect.test.mjs +18 -0
  120. package/scripts/session-compaction-context-window.test.mjs +27 -26
  121. package/scripts/session-config-turn-boundary.test.mjs +80 -26
  122. package/scripts/session-prompt-runtime.test.mjs +23 -15
  123. package/scripts/session-recorder.test.mjs +15 -24
  124. package/src/agent/local/Agent.ts +2 -2
  125. package/src/agent/local/services/AgentSessions.ts +10 -10
  126. package/src/agent/remote/RemoteSession.ts +5 -0
  127. package/src/agent/remote/RemoteTransport.ts +2 -0
  128. package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +15 -0
  129. package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +4 -0
  130. package/src/executor/Executor.ts +111 -19
  131. package/src/executor/composer/compaction/SessionCompactionComposer.ts +7 -0
  132. package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.ts +4 -57
  133. package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.ts +8 -66
  134. package/src/executor/core-engine/CoreEngineContextCompaction.ts +477 -0
  135. package/src/executor/core-engine/CoreEngineMessageState.ts +29 -0
  136. package/src/executor/core-engine/CoreEngineRunner.ts +200 -48
  137. package/src/executor/store/history/SessionHistoryStore.ts +4 -12
  138. package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +1 -3
  139. package/src/executor/types/SessionRun.ts +9 -0
  140. package/src/index.ts +1 -1
  141. package/src/plugin/core/PluginLocalExecution.ts +3 -3
  142. package/src/plugin/core/PluginRegistry.ts +116 -3
  143. package/src/rpc/Client.ts +15 -2
  144. package/src/session/Session.ts +28 -20
  145. package/src/session/SessionSystemBuilder.ts +4 -1
  146. package/src/session/recorder/SessionRecorderCompaction.ts +113 -65
  147. package/src/session/runtime/SessionPromptRuntime.ts +31 -46
  148. package/src/session/services/SessionStateService.ts +34 -25
  149. package/src/session/services/SessionTurnService.ts +97 -8
  150. package/src/types/agent/SessionActor.ts +9 -0
  151. package/src/types/executor/SessionRunContext.ts +17 -8
  152. package/src/types/plugin/PluginRuntime.ts +38 -4
  153. package/src/types/plugin/PluginState.ts +10 -0
  154. package/src/types/rpc/RpcProtocol.ts +23 -0
  155. package/src/types/runtime/agent/AgentContext.ts +2 -2
  156. package/src/types/session/SessionLocalState.ts +2 -2
  157. package/src/types/session/SessionOptions.ts +6 -6
  158. package/src/types/session/SessionQueueCommand.ts +78 -0
  159. package/src/utils/logger/Logger.ts +10 -10
  160. package/tsconfig.tsbuildinfo +1 -1
  161. package/bin/types/session/SessionConfigMutation.d.ts +0 -68
  162. package/bin/types/session/SessionConfigMutation.d.ts.map +0 -1
  163. package/bin/types/session/SessionConfigMutation.js +0 -10
  164. package/bin/types/session/SessionConfigMutation.js.map +0 -1
  165. package/src/types/session/SessionConfigMutation.ts +0 -78
@@ -7,7 +7,13 @@
7
7
  * - 保持失败返回结构稳定,避免对外 Session 行为变化。
8
8
  */
9
9
 
10
- import { streamText, type FileUIPart, type LanguageModel } from "ai";
10
+ import {
11
+ streamText,
12
+ type FileUIPart,
13
+ type LanguageModel,
14
+ type StepResult,
15
+ type Tool,
16
+ } from "ai";
11
17
  import { buildOpenAIResponsesProviderOptions } from "@executor/messages/SessionMessageCodec.js";
12
18
  import { logAssistantMessageNow } from "@executor/messages/SessionMessageLog.js";
13
19
  import {
@@ -33,6 +39,11 @@ import {
33
39
  } from "@executor/core-engine/CoreEngineError.js";
34
40
  import { collectFinalAssistantMessageFromUiStream } from "@executor/core-engine/CoreEngineUiStreamCollector.js";
35
41
  import { CoreEngineMessageState } from "@executor/core-engine/CoreEngineMessageState.js";
42
+ import {
43
+ deep_compact_model_messages,
44
+ resolve_model_usage_ratio,
45
+ should_compact_after_usage,
46
+ } from "@executor/core-engine/CoreEngineContextCompaction.js";
36
47
  import type { Logger } from "@/utils/logger/Logger.js";
37
48
  import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
38
49
  import type { SessionContextComposer } from "@executor/composer/context/SessionContextComposer.js";
@@ -48,6 +59,9 @@ import type {
48
59
 
49
60
  const TURN_STOPPED_MESSAGE = "Turn stopped";
50
61
 
62
+ /** Provider context-length error 在当前 step 内最多压缩重试三次。 */
63
+ const MAX_CONTEXT_ERROR_COMPACTION_RETRIES = 3;
64
+
51
65
  /**
52
66
  * 生成 file part 去重 key。
53
67
  */
@@ -127,15 +141,17 @@ interface CoreEngineRunInput {
127
141
  run_context: SessionRunContext;
128
142
 
129
143
  /**
130
- * 在统一输入队列提交后解析当前模型 turn 的 effective 配置。
144
+ * 在统一输入队列提交后解析当前 Session step 的 effective 配置。
131
145
  */
132
- resolve_model_turn: () => Promise<{
133
- /** 当前模型 turn 使用的模型。 */
146
+ resolve_step_inputs: () => Promise<{
147
+ /** 当前 Session step 使用的模型。 */
134
148
  model: LanguageModel;
135
- /** 当前模型 turn 使用的 system messages。 */
149
+ /** 当前 Session step 使用的 system messages。 */
136
150
  system: SessionExecuteInput["system"];
137
- /** 当前模型 turn 使用的工具集合。 */
151
+ /** 当前 Session step 使用的工具集合。 */
138
152
  tools: SessionExecuteInput["tools"];
153
+ /** 当前 Session step 模型支持的总上下文窗口长度。 */
154
+ context_window?: number;
139
155
  }>;
140
156
  }
141
157
 
@@ -148,6 +164,9 @@ export class CoreEngineRunner {
148
164
  private readonly logger: Logger;
149
165
  private readonly should_compact_on_error: CoreEngineRunnerOptions["should_compact_on_error"];
150
166
 
167
+ /** 最近一次已经通过真实 usage 验收的持久化 Summary 标识。 */
168
+ private validated_compaction_summary_id = "";
169
+
151
170
  constructor(options: CoreEngineRunnerOptions) {
152
171
  this.history_store = options.history_store;
153
172
  this.context_composer = options.context_composer;
@@ -167,6 +186,7 @@ export class CoreEngineRunner {
167
186
  let tools = input.execute_input.tools;
168
187
  let last_observed_stream_error: unknown = undefined;
169
188
  let final_assistant_ui_message: SessionMessageRecordV1 | null = null;
189
+ let compact_required = false;
170
190
 
171
191
  try {
172
192
  const message_state = await CoreEngineMessageState.create({
@@ -174,6 +194,9 @@ export class CoreEngineRunner {
174
194
  tools,
175
195
  projectRoot: input.run_context.projectRoot,
176
196
  });
197
+ let persisted_compaction_summary_id = resolve_compaction_summary_id(
198
+ input.execute_input.messages,
199
+ );
177
200
 
178
201
  const append_merged_user_messages = (messages: SessionRecordV1[]) =>
179
202
  message_state.appendMergedUserMessages(messages);
@@ -200,7 +223,7 @@ export class CoreEngineRunner {
200
223
  await context_composer_on_step_finish(step_result);
201
224
  };
202
225
 
203
- const prepare_model_turn_inputs = this.context_composer.createPrepareStepHandler({
226
+ const prepare_step_inputs = this.context_composer.createPrepareStepHandler({
204
227
  system,
205
228
  appendMergedUserMessages: append_merged_user_messages,
206
229
  runContext: input.run_context,
@@ -208,58 +231,169 @@ export class CoreEngineRunner {
208
231
 
209
232
  let text_only_continuation_count = 0;
210
233
  let incomplete_response_recovery_count = 0;
234
+ let context_error_compaction_retries = 0;
235
+ let compact_pending = false;
236
+ let compact_validation_pending = Boolean(
237
+ persisted_compaction_summary_id &&
238
+ persisted_compaction_summary_id !==
239
+ this.validated_compaction_summary_id,
240
+ );
241
+ let compact_depth = 0;
211
242
 
212
243
  while (step_count < MAX_TOOL_LOOP_STEPS) {
213
- // 关键点(中文):steer 与配置 mutation 必须在选择 model/system/tools 前提交。
214
- // 这样运行中写入的配置只影响下一次 provider 请求,不会改变当前流或 tool callback。
215
- await prepare_model_turn_inputs({ messages: [] });
216
- const model_turn = await input.resolve_model_turn();
217
- system = Array.isArray(model_turn.system) ? model_turn.system : [];
218
- tools = model_turn.tools;
219
- const result = streamText({
220
- model: model_turn.model,
221
- system,
222
- onStepFinish: on_step_finish,
223
- messages: message_state.modelMessages,
224
- tools,
225
- abortSignal: input.run_context.abortSignal,
226
- providerOptions: buildOpenAIResponsesProviderOptions(),
227
- onError: async ({ error }) => {
228
- last_observed_stream_error = error;
229
- await this.logger.log("error", "[agent] stream.error", {
230
- sessionId: session_id,
231
- ...summarizeStreamError(error),
232
- });
233
- },
234
- });
235
-
236
- const step_assistant_ui_message =
237
- await collectFinalAssistantMessageFromUiStream({
238
- result,
244
+ // 关键点(中文):steer command 在同一个 Session step 检查点执行。
245
+ // 当前流与 tool callback 保持原执行视图,下一 step 再统一读取 effective 配置。
246
+ await prepare_step_inputs({ messages: [] });
247
+ const step_inputs = await input.resolve_step_inputs();
248
+ system = Array.isArray(step_inputs.system) ? step_inputs.system : [];
249
+ tools = step_inputs.tools;
250
+ if (input.run_context.consume_history_reload?.()) {
251
+ const canonical_records = await this.history_store.list_records();
252
+ await message_state.replace_session_messages(canonical_records, tools);
253
+ persisted_compaction_summary_id = resolve_compaction_summary_id(
254
+ canonical_records,
255
+ );
256
+ compact_validation_pending = Boolean(
257
+ persisted_compaction_summary_id &&
258
+ persisted_compaction_summary_id !==
259
+ this.validated_compaction_summary_id,
260
+ );
261
+ await this.logger.log("info", "[agent] context.history_reloaded", {
239
262
  sessionId: session_id,
240
- logger: this.logger,
241
- buildFallbackAssistantMessage: (text) =>
242
- this.context_composer.buildFallbackAssistantMessage(
243
- text,
244
- input.run_context,
245
- ),
246
- onUiMessageChunkCallback: input.run_context.onUiMessageChunkCallback,
263
+ recordCount: canonical_records.length,
264
+ compactionSummaryId: persisted_compaction_summary_id || undefined,
265
+ });
266
+ }
267
+ if (compact_pending) {
268
+ const previous_message_count = message_state.modelMessages.length;
269
+ message_state.replace_model_messages(
270
+ deep_compact_model_messages(
271
+ message_state.modelMessages,
272
+ compact_depth,
273
+ ),
274
+ );
275
+ compact_depth += 1;
276
+ compact_pending = false;
277
+ compact_validation_pending = true;
278
+ compact_required = true;
279
+ await this.logger.log("info", "[agent] context.compacted", {
280
+ sessionId: session_id,
281
+ reason: "usage_threshold",
282
+ compactDepth: compact_depth,
283
+ previousMessageCount: previous_message_count,
284
+ nextMessageCount: message_state.modelMessages.length,
285
+ });
286
+ }
287
+
288
+ last_observed_stream_error = undefined;
289
+ let step_assistant_ui_message: SessionMessageRecordV1;
290
+ let executed_steps: StepResult<Record<string, Tool>>[];
291
+ try {
292
+ const result = streamText({
293
+ model: step_inputs.model,
294
+ system,
295
+ onStepFinish: on_step_finish,
296
+ messages: message_state.modelMessages,
297
+ tools,
247
298
  abortSignal: input.run_context.abortSignal,
299
+ providerOptions: buildOpenAIResponsesProviderOptions(),
300
+ onError: async ({ error }) => {
301
+ last_observed_stream_error = error;
302
+ await this.logger.log("error", "[agent] stream.error", {
303
+ sessionId: session_id,
304
+ ...summarizeStreamError(error),
305
+ });
306
+ },
248
307
  });
249
308
 
250
- final_assistant_ui_message = mergeAssistantUiMessages(
251
- final_assistant_ui_message,
252
- step_assistant_ui_message,
253
- );
309
+ step_assistant_ui_message =
310
+ await collectFinalAssistantMessageFromUiStream({
311
+ result,
312
+ sessionId: session_id,
313
+ logger: this.logger,
314
+ buildFallbackAssistantMessage: (text) =>
315
+ this.context_composer.buildFallbackAssistantMessage(
316
+ text,
317
+ input.run_context,
318
+ ),
319
+ onUiMessageChunkCallback: input.run_context.onUiMessageChunkCallback,
320
+ abortSignal: input.run_context.abortSignal,
321
+ });
254
322
 
255
- // 关键点(中文):先保存本 step 已收敛的 assistant 消息,再等待 steps。
256
- // stop/abort 时 `result.steps` 可能抛错,但当前已经生成的文本仍应沉淀。
257
- message_state.appendRuntimeSessionMessage(step_assistant_ui_message);
323
+ final_assistant_ui_message = mergeAssistantUiMessages(
324
+ final_assistant_ui_message,
325
+ step_assistant_ui_message,
326
+ );
258
327
 
259
- const executed_steps = await result.steps;
328
+ // 关键点(中文):先保存本 step 已收敛的 assistant 消息,再等待 steps
329
+ // stop/abort 时 `result.steps` 可能抛错,但当前已经生成的文本仍应沉淀。
330
+ message_state.appendRuntimeSessionMessage(step_assistant_ui_message);
331
+ executed_steps = await result.steps;
332
+ } catch (error) {
333
+ const compact_error = this.should_compact_on_error(error)
334
+ ? error
335
+ : last_observed_stream_error;
336
+ if (
337
+ this.should_compact_on_error(compact_error) &&
338
+ context_error_compaction_retries <
339
+ MAX_CONTEXT_ERROR_COMPACTION_RETRIES
340
+ ) {
341
+ context_error_compaction_retries += 1;
342
+ const previous_message_count = message_state.modelMessages.length;
343
+ message_state.replace_model_messages(
344
+ deep_compact_model_messages(
345
+ message_state.modelMessages,
346
+ compact_depth,
347
+ ),
348
+ );
349
+ compact_depth += 1;
350
+ compact_pending = false;
351
+ compact_validation_pending = true;
352
+ compact_required = true;
353
+ await this.logger.log("warn", "[agent] context.compacted", {
354
+ sessionId: session_id,
355
+ reason: "provider_context_error",
356
+ retryCount: context_error_compaction_retries,
357
+ compactDepth: compact_depth,
358
+ previousMessageCount: previous_message_count,
359
+ nextMessageCount: message_state.modelMessages.length,
360
+ ...summarizeStreamError(compact_error),
361
+ });
362
+ continue;
363
+ }
364
+ throw error;
365
+ }
366
+
367
+ context_error_compaction_retries = 0;
260
368
  const last_step = executed_steps[executed_steps.length - 1];
261
369
  if (!last_step) break;
262
370
 
371
+ const usage_ratio = resolve_model_usage_ratio(
372
+ last_step.usage,
373
+ step_inputs.context_window,
374
+ );
375
+ if (usage_ratio !== null) {
376
+ const validating_compaction = compact_validation_pending;
377
+ compact_pending = should_compact_after_usage(
378
+ usage_ratio,
379
+ validating_compaction,
380
+ );
381
+ compact_validation_pending = false;
382
+ if (validating_compaction && persisted_compaction_summary_id) {
383
+ this.validated_compaction_summary_id =
384
+ persisted_compaction_summary_id;
385
+ }
386
+ if (compact_pending) compact_required = true;
387
+ await this.logger.log("info", "[agent] context.usage", {
388
+ sessionId: session_id,
389
+ stepIndex: step_count,
390
+ usageRatio: usage_ratio,
391
+ contextWindow: step_inputs.context_window,
392
+ validatingCompaction: validating_compaction,
393
+ compactPending: compact_pending,
394
+ });
395
+ }
396
+
263
397
  const incomplete_response = detectIncompleteResponse({
264
398
  stepResult: last_step,
265
399
  assistantMessage: step_assistant_ui_message,
@@ -424,6 +558,7 @@ export class CoreEngineRunner {
424
558
  return {
425
559
  success: true,
426
560
  assistantMessage: final_message,
561
+ ...(compact_required ? { compact_required: true } : {}),
427
562
  deferredPersistedUserMessages: [
428
563
  ...input.run_context.deferredPersistedUserMessages,
429
564
  ],
@@ -444,6 +579,7 @@ export class CoreEngineRunner {
444
579
  success: false,
445
580
  error: error_text,
446
581
  ...(stopped_message ? { assistantMessage: stopped_message } : {}),
582
+ ...(compact_required ? { compact_required: true } : {}),
447
583
  deferredPersistedUserMessages: [
448
584
  ...input.run_context.deferredPersistedUserMessages,
449
585
  ],
@@ -470,6 +606,7 @@ export class CoreEngineRunner {
470
606
  `Execution failed: ${error_text}`,
471
607
  input.run_context,
472
608
  ),
609
+ ...(compact_required ? { compact_required: true } : {}),
473
610
  deferredPersistedUserMessages: [
474
611
  ...input.run_context.deferredPersistedUserMessages,
475
612
  ],
@@ -477,3 +614,18 @@ export class CoreEngineRunner {
477
614
  }
478
615
  }
479
616
  }
617
+
618
+ /** 读取当前模型上下文中最新的持久化 compact Summary 标识。 */
619
+ function resolve_compaction_summary_id(messages: SessionRecordV1[]): string {
620
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
621
+ const message = messages[index];
622
+ if (!("role" in message) || message.role !== "assistant") continue;
623
+ if (
624
+ message.metadata?.source === "compact" &&
625
+ message.metadata.kind === "summary"
626
+ ) {
627
+ return String(message.id || "").trim();
628
+ }
629
+ }
630
+ return "";
631
+ }
@@ -30,19 +30,11 @@ export type SessionHistoryCompactInput = {
30
30
  system: SessionSystemMessage[];
31
31
 
32
32
  /**
33
- * 保留最近消息条数。
34
- */
35
- keepLastMessages: number;
36
-
37
- /**
38
- * 输入 token 近似上限。
39
- */
40
- maxInputTokensApprox: number;
41
-
42
- /**
43
- * 前段压缩比例(0-1)。
33
+ * 是否明确要求本次执行持久化 deep compact。
34
+ *
35
+ * 真实 usage 触发与内存消息折叠由 CoreEngine 决定;Store 只负责归档已经完成的历史。
44
36
  */
45
- compactRatio: number;
37
+ force?: boolean;
46
38
 
47
39
  /**
48
40
  * 可选 action 发布回调。
@@ -665,10 +665,8 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
665
665
  {
666
666
  model: input.model,
667
667
  system: this.normalizeSystem(input.system),
668
- keepLastMessages: input.keepLastMessages,
669
- maxInputTokensApprox: input.maxInputTokensApprox,
670
668
  archiveOnCompact: false,
671
- compactRatio: input.compactRatio,
669
+ force: input.force,
672
670
  ...(input.onAction ? { onAction: input.onAction } : {}),
673
671
  },
674
672
  );
@@ -112,6 +112,15 @@ export interface SessionRunResult {
112
112
  * - 为保证消息顺序稳定,统一在 assistant 结果落盘后再由外层 Session 持久化。
113
113
  */
114
114
  deferredPersistedUserMessages?: SessionUserMessageV1[];
115
+
116
+ /**
117
+ * 本轮结束后是否需要把已完成的 canonical 历史持久化压缩。
118
+ *
119
+ * 关键点(中文)
120
+ * - 真实 usage 达到 95% 或本轮已经执行过内存 compact 时为 true。
121
+ * - 上层必须等 Assistant writer 收口后再执行,避免压缩流式草稿。
122
+ */
123
+ compact_required?: boolean;
115
124
  }
116
125
 
117
126
  /**
package/src/index.ts CHANGED
@@ -247,7 +247,7 @@ export {
247
247
  export { assertProjectExecutionTarget } from "./config/ExecutionBinding.js";
248
248
 
249
249
  // 日志
250
- export { getLogger, logger, type Logger } from "./utils/logger/Logger.js";
250
+ export { getLogger, type Logger } from "./utils/logger/Logger.js";
251
251
  export { generateId } from "./utils/Id.js";
252
252
  export {
253
253
  formatDateTimeInTimezone,
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import path from "node:path";
11
- import { logger as defaultLogger } from "@/utils/logger/Logger.js";
11
+ import { getLogger } from "@/utils/logger/Logger.js";
12
12
  import { resolve_agent_env } from "@/config/AgentEnv.js";
13
13
  import { findPluginByName } from "@/plugin/core/PluginCatalog.js";
14
14
  import {
@@ -45,12 +45,12 @@ export function createLocalPluginCommandContext(
45
45
  : input.config;
46
46
  const agent_id = String(config.id || "").trim() || path.basename(rootPath) || "agent";
47
47
 
48
- defaultLogger.bindProjectRoot(rootPath);
48
+ const logger = getLogger(rootPath);
49
49
 
50
50
  return {
51
51
  cwd: rootPath,
52
52
  rootPath,
53
- logger: defaultLogger,
53
+ logger,
54
54
  config,
55
55
  env,
56
56
  paths: createAgentPathRuntime(
@@ -13,6 +13,7 @@ import type { Plugin } from "@/types/plugin/PluginDefinition.js";
13
13
  import type { PluginActionResult } from "@/types/plugin/PluginAction.js";
14
14
  import type {
15
15
  AgentPlugins,
16
+ AgentPluginExecutionLease,
16
17
  AgentPluginExecutionRuntime,
17
18
  PluginAvailability,
18
19
  PluginActionReadView,
@@ -46,6 +47,9 @@ function create_record(plugin: Plugin): PluginRuntimeRecord {
46
47
  updated_at: current_time,
47
48
  chain: Promise.resolve(),
48
49
  lifecycle_started: false,
50
+ active_execution_leases: 0,
51
+ retired: false,
52
+ retirement_started: false,
49
53
  };
50
54
  }
51
55
 
@@ -102,6 +106,8 @@ export class PluginRegistry implements AgentPlugins {
102
106
 
103
107
  private readonly records = new Map<string, PluginRuntimeRecord>();
104
108
 
109
+ private readonly retired_records = new Set<PluginRuntimeRecord>();
110
+
105
111
  private change_listener?: (input: {
106
112
  /** 当前修改是注册还是卸载。 */
107
113
  type: "register" | "unregister";
@@ -183,7 +189,12 @@ export class PluginRegistry implements AgentPlugins {
183
189
  }
184
190
 
185
191
  /**
186
- * 卸载指定 plugin。
192
+ * 从 configured registry 卸载指定 plugin。
193
+ *
194
+ * 关键点(中文)
195
+ * - configured registry、hooks 与直接调用入口立即移除。
196
+ * - lifecycle.stop 等当前活跃 Session step 的 execution lease 全部释放后执行。
197
+ * - 该方法返回配置修改结果,不等待仍在运行的 step 结束。
187
198
  */
188
199
  async unregister(pluginName: string): Promise<boolean> {
189
200
  const key = normalize_plugin_name(pluginName);
@@ -191,10 +202,10 @@ export class PluginRegistry implements AgentPlugins {
191
202
  const record = this.records.get(key);
192
203
  if (!record) return false;
193
204
 
194
- await this.stop_record(record);
195
205
  this.unregister_hooks(key);
196
206
  this.records.delete(key);
197
207
  this.pluginInstances.delete(key);
208
+ this.retire_record(record);
198
209
  this.change_listener?.({ type: "unregister", plugin_name: key });
199
210
  return true;
200
211
  }
@@ -222,6 +233,10 @@ export class PluginRegistry implements AgentPlugins {
222
233
  for (const name of Array.from(this.records.keys())) {
223
234
  await this.unregister(name);
224
235
  }
236
+ const retirements = Array.from(this.retired_records)
237
+ .map((record) => record.retirement_promise)
238
+ .filter((promise): promise is Promise<void> => Boolean(promise));
239
+ await Promise.all(retirements);
225
240
  }
226
241
 
227
242
  /**
@@ -616,7 +631,7 @@ export class PluginRegistry implements AgentPlugins {
616
631
  }
617
632
 
618
633
  /**
619
- * 创建当前 configured registry 的模型 turn 执行视图。
634
+ * 创建当前 configured registry Session step 执行视图。
620
635
  */
621
636
  execution_view(): AgentPluginExecutionRuntime {
622
637
  const records = new Map(this.records);
@@ -626,6 +641,104 @@ export class PluginRegistry implements AgentPlugins {
626
641
  await this.run_action_from_records(records, params),
627
642
  systemBlocks: async () =>
628
643
  await this.system_blocks_from_records(records),
644
+ acquire: () => this.acquire_execution_view(records),
645
+ };
646
+ }
647
+
648
+ /**
649
+ * 为单次 Session step 获取 Plugin execution lease。
650
+ */
651
+ private acquire_execution_view(
652
+ records: ReadonlyMap<string, PluginRuntimeRecord>,
653
+ ): AgentPluginExecutionLease {
654
+ const leased_records = new Map<string, PluginRuntimeRecord>();
655
+ for (const [name, record] of records) {
656
+ if (
657
+ record.retired ||
658
+ record.state !== "ready" ||
659
+ !record.lifecycle_started
660
+ ) {
661
+ continue;
662
+ }
663
+ record.active_execution_leases += 1;
664
+ leased_records.set(name, record);
665
+ }
666
+
667
+ let released = false;
668
+ return {
669
+ read: (params) => this.read_from_records(leased_records, params),
670
+ runAction: async (params) =>
671
+ await this.run_action_from_records(leased_records, params),
672
+ systemBlocks: async () =>
673
+ await this.system_blocks_from_records(leased_records),
674
+ release: async () => {
675
+ if (released) return;
676
+ released = true;
677
+ const retirements: Promise<void>[] = [];
678
+ for (const record of leased_records.values()) {
679
+ record.active_execution_leases = Math.max(
680
+ 0,
681
+ record.active_execution_leases - 1,
682
+ );
683
+ this.try_finalize_retired_record(record);
684
+ if (record.retired && record.retirement_promise) {
685
+ retirements.push(record.retirement_promise);
686
+ }
687
+ }
688
+ await Promise.all(retirements);
689
+ },
629
690
  };
630
691
  }
692
+
693
+ /**
694
+ * 把已移出 configured registry 的 Plugin 标记为等待释放。
695
+ */
696
+ private retire_record(record: PluginRuntimeRecord): void {
697
+ if (record.retired) return;
698
+ record.retired = true;
699
+ let resolve_retirement!: () => void;
700
+ record.retirement_promise = new Promise<void>((resolve) => {
701
+ resolve_retirement = resolve;
702
+ });
703
+ record.resolve_retirement = resolve_retirement;
704
+ this.retired_records.add(record);
705
+ this.try_finalize_retired_record(record);
706
+ }
707
+
708
+ /**
709
+ * 在最后一个 execution lease 释放后停止退休 Plugin。
710
+ */
711
+ private try_finalize_retired_record(record: PluginRuntimeRecord): void {
712
+ if (
713
+ !record.retired ||
714
+ record.retirement_started ||
715
+ record.active_execution_leases > 0
716
+ ) {
717
+ return;
718
+ }
719
+ record.retirement_started = true;
720
+ void (async () => {
721
+ try {
722
+ await this.stop_record(record);
723
+ } catch (error) {
724
+ update_record_state(record, "error", String(error));
725
+ try {
726
+ await this.contextResolver().logger.log(
727
+ "error",
728
+ "[plugin] lifecycle.stop failed after execution release",
729
+ {
730
+ plugin: record.plugin.name,
731
+ error: String(error),
732
+ },
733
+ );
734
+ } catch {
735
+ // 退休清理不能因日志失败再次中断。
736
+ }
737
+ } finally {
738
+ this.retired_records.delete(record);
739
+ record.resolve_retirement?.();
740
+ delete record.resolve_retirement;
741
+ }
742
+ })();
743
+ }
631
744
  }
package/src/rpc/Client.ts CHANGED
@@ -50,6 +50,7 @@ import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js"
50
50
  import type {
51
51
  RpcClientEndpoint,
52
52
  RpcClientFrame,
53
+ RpcInternalStatus,
53
54
  RpcRequest,
54
55
  RpcSessionSubscription,
55
56
  RpcSystemPromptPayload,
@@ -217,6 +218,18 @@ export class RpcClient {
217
218
  return data.result;
218
219
  }
219
220
 
221
+ /**
222
+ * 把一次显式历史压缩加入远程 Session 的有序输入队列。
223
+ */
224
+ async compact_session(session_id: string): Promise<void> {
225
+ await this.request<{ queued: true }>({
226
+ method: "sdk.sessions.compact",
227
+ params: {
228
+ sessionId: session_id,
229
+ },
230
+ });
231
+ }
232
+
220
233
  /**
221
234
  * 读取 session records。
222
235
  */
@@ -344,8 +357,8 @@ export class RpcClient {
344
357
  /**
345
358
  * 读取 Agent 内部状态。
346
359
  */
347
- async get_internal_status(): Promise<{ status: string }> {
348
- return await this.request<{ status: string }>({
360
+ async get_internal_status(): Promise<RpcInternalStatus> {
361
+ return await this.request<RpcInternalStatus>({
349
362
  method: "internal.status.get",
350
363
  });
351
364
  }