@downcity/agent 1.1.79 → 1.1.81

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 (173) hide show
  1. package/README.md +1 -1
  2. package/bin/agent/local/Agent.d.ts +8 -121
  3. package/bin/agent/local/Agent.d.ts.map +1 -1
  4. package/bin/agent/local/Agent.js +63 -381
  5. package/bin/agent/local/Agent.js.map +1 -1
  6. package/bin/agent/local/AgentRuntimeFactory.d.ts +2 -2
  7. package/bin/agent/local/AgentRuntimeFactory.d.ts.map +1 -1
  8. package/bin/agent/local/AgentRuntimeFactory.js +2 -23
  9. package/bin/agent/local/AgentRuntimeFactory.js.map +1 -1
  10. package/bin/agent/local/services/AgentAssemblyService.d.ts +112 -0
  11. package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -0
  12. package/bin/agent/local/services/AgentAssemblyService.js +135 -0
  13. package/bin/agent/local/services/AgentAssemblyService.js.map +1 -0
  14. package/bin/agent/local/services/AgentLifecycleService.d.ts +59 -0
  15. package/bin/agent/local/services/AgentLifecycleService.d.ts.map +1 -0
  16. package/bin/agent/local/services/AgentLifecycleService.js +136 -0
  17. package/bin/agent/local/services/AgentLifecycleService.js.map +1 -0
  18. package/bin/agent/local/services/AgentSessionManager.d.ts +106 -0
  19. package/bin/agent/local/services/AgentSessionManager.d.ts.map +1 -0
  20. package/bin/agent/local/services/AgentSessionManager.js +182 -0
  21. package/bin/agent/local/services/AgentSessionManager.js.map +1 -0
  22. package/bin/executor/Executor.d.ts +7 -24
  23. package/bin/executor/Executor.d.ts.map +1 -1
  24. package/bin/executor/Executor.js +73 -361
  25. package/bin/executor/Executor.js.map +1 -1
  26. package/bin/executor/SessionRunScope.d.ts +18 -34
  27. package/bin/executor/SessionRunScope.d.ts.map +1 -1
  28. package/bin/executor/SessionRunScope.js +42 -28
  29. package/bin/executor/SessionRunScope.js.map +1 -1
  30. package/bin/executor/composer/context/LocalSessionContextComposer.d.ts +5 -3
  31. package/bin/executor/composer/context/LocalSessionContextComposer.d.ts.map +1 -1
  32. package/bin/executor/composer/context/LocalSessionContextComposer.js +11 -18
  33. package/bin/executor/composer/context/LocalSessionContextComposer.js.map +1 -1
  34. package/bin/executor/composer/context/SessionContextComposer.d.ts +8 -3
  35. package/bin/executor/composer/context/SessionContextComposer.d.ts.map +1 -1
  36. package/bin/executor/composer/system/SessionSystemComposer.d.ts +2 -1
  37. package/bin/executor/composer/system/SessionSystemComposer.d.ts.map +1 -1
  38. package/bin/executor/composer/system/default/DefaultSessionSystemComposer.d.ts +2 -1
  39. package/bin/executor/composer/system/default/DefaultSessionSystemComposer.d.ts.map +1 -1
  40. package/bin/executor/composer/system/default/DefaultSessionSystemComposer.js +2 -4
  41. package/bin/executor/composer/system/default/DefaultSessionSystemComposer.js.map +1 -1
  42. package/bin/executor/core-engine/CoreEngineRunner.d.ts +62 -0
  43. package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -0
  44. package/bin/executor/core-engine/CoreEngineRunner.js +309 -0
  45. package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -0
  46. package/bin/executor/core-engine/CoreEngineUiStreamCollector.d.ts +5 -0
  47. package/bin/executor/core-engine/CoreEngineUiStreamCollector.d.ts.map +1 -1
  48. package/bin/executor/core-engine/CoreEngineUiStreamCollector.js +2 -4
  49. package/bin/executor/core-engine/CoreEngineUiStreamCollector.js.map +1 -1
  50. package/bin/executor/services/ExecutorInflightService.d.ts +39 -0
  51. package/bin/executor/services/ExecutorInflightService.d.ts.map +1 -0
  52. package/bin/executor/services/ExecutorInflightService.js +75 -0
  53. package/bin/executor/services/ExecutorInflightService.js.map +1 -0
  54. package/bin/executor/services/ExecutorRecoveryPolicy.d.ts +103 -0
  55. package/bin/executor/services/ExecutorRecoveryPolicy.d.ts.map +1 -0
  56. package/bin/executor/services/ExecutorRecoveryPolicy.js +87 -0
  57. package/bin/executor/services/ExecutorRecoveryPolicy.js.map +1 -0
  58. package/bin/executor/tools/plugin/PluginToolBridge.d.ts +19 -0
  59. package/bin/executor/tools/plugin/PluginToolBridge.d.ts.map +1 -0
  60. package/bin/executor/tools/plugin/PluginToolBridge.js +143 -0
  61. package/bin/executor/tools/plugin/PluginToolBridge.js.map +1 -0
  62. package/bin/executor/tools/plugin/PluginToolDefinition.d.ts +32 -0
  63. package/bin/executor/tools/plugin/PluginToolDefinition.d.ts.map +1 -0
  64. package/bin/executor/tools/plugin/PluginToolDefinition.js +27 -0
  65. package/bin/executor/tools/plugin/PluginToolDefinition.js.map +1 -0
  66. package/bin/executor/tools/plugin/PluginToolSchemas.d.ts +14 -0
  67. package/bin/executor/tools/plugin/PluginToolSchemas.d.ts.map +1 -0
  68. package/bin/executor/tools/plugin/PluginToolSchemas.js +19 -0
  69. package/bin/executor/tools/plugin/PluginToolSchemas.js.map +1 -0
  70. package/bin/executor/tools/plugin/types/PluginTool.d.ts +39 -0
  71. package/bin/executor/tools/plugin/types/PluginTool.d.ts.map +1 -0
  72. package/bin/executor/tools/plugin/types/PluginTool.js +9 -0
  73. package/bin/executor/tools/plugin/types/PluginTool.js.map +1 -0
  74. package/bin/executor/tools/shell/ShellToolBridge.js +3 -3
  75. package/bin/executor/tools/shell/ShellToolBridge.js.map +1 -1
  76. package/bin/executor/types/SessionRun.d.ts +18 -0
  77. package/bin/executor/types/SessionRun.d.ts.map +1 -1
  78. package/bin/index.d.ts +10 -1
  79. package/bin/index.d.ts.map +1 -1
  80. package/bin/index.js +3 -0
  81. package/bin/index.js.map +1 -1
  82. package/bin/plugin/core/BasePlugin.d.ts +2 -2
  83. package/bin/plugin/core/BasePlugin.d.ts.map +1 -1
  84. package/bin/plugin/core/BasePlugin.js.map +1 -1
  85. package/bin/plugin/core/ImagePlugin.d.ts +56 -0
  86. package/bin/plugin/core/ImagePlugin.d.ts.map +1 -0
  87. package/bin/plugin/core/ImagePlugin.js +109 -0
  88. package/bin/plugin/core/ImagePlugin.js.map +1 -0
  89. package/bin/session/Session.d.ts +14 -83
  90. package/bin/session/Session.d.ts.map +1 -1
  91. package/bin/session/Session.js +139 -362
  92. package/bin/session/Session.js.map +1 -1
  93. package/bin/session/SessionSystemBuilder.d.ts +2 -1
  94. package/bin/session/SessionSystemBuilder.d.ts.map +1 -1
  95. package/bin/session/SessionSystemBuilder.js +2 -3
  96. package/bin/session/SessionSystemBuilder.js.map +1 -1
  97. package/bin/session/services/SessionStateService.d.ts +132 -0
  98. package/bin/session/services/SessionStateService.d.ts.map +1 -0
  99. package/bin/session/services/SessionStateService.js +242 -0
  100. package/bin/session/services/SessionStateService.js.map +1 -0
  101. package/bin/session/services/SessionTurnService.d.ts +66 -0
  102. package/bin/session/services/SessionTurnService.d.ts.map +1 -0
  103. package/bin/session/services/SessionTurnService.js +137 -0
  104. package/bin/session/services/SessionTurnService.js.map +1 -0
  105. package/bin/session/services/SessionViewService.d.ts +105 -0
  106. package/bin/session/services/SessionViewService.d.ts.map +1 -0
  107. package/bin/session/services/SessionViewService.js +184 -0
  108. package/bin/session/services/SessionViewService.js.map +1 -0
  109. package/bin/types/agent/AgentOptions.d.ts +18 -0
  110. package/bin/types/agent/AgentOptions.d.ts.map +1 -1
  111. package/bin/types/agent/AgentTypes.d.ts +3 -1
  112. package/bin/types/agent/AgentTypes.d.ts.map +1 -1
  113. package/bin/types/agent/SessionTypes.d.ts.map +1 -1
  114. package/bin/types/executor/SessionRunContext.d.ts +66 -0
  115. package/bin/types/executor/SessionRunContext.d.ts.map +1 -0
  116. package/bin/types/executor/SessionRunContext.js +10 -0
  117. package/bin/types/executor/SessionRunContext.js.map +1 -0
  118. package/bin/types/plugin/ImagePlugin.d.ts +94 -0
  119. package/bin/types/plugin/ImagePlugin.d.ts.map +1 -0
  120. package/bin/types/plugin/ImagePlugin.js +10 -0
  121. package/bin/types/plugin/ImagePlugin.js.map +1 -0
  122. package/bin/types/session/SessionComposerOptions.d.ts +90 -0
  123. package/bin/types/session/SessionComposerOptions.d.ts.map +1 -0
  124. package/bin/types/session/SessionComposerOptions.js +10 -0
  125. package/bin/types/session/SessionComposerOptions.js.map +1 -0
  126. package/bin/types/session/SessionLocalState.d.ts +35 -0
  127. package/bin/types/session/SessionLocalState.d.ts.map +1 -0
  128. package/bin/types/session/SessionLocalState.js +10 -0
  129. package/bin/types/session/SessionLocalState.js.map +1 -0
  130. package/bin/types/session/SessionOptions.d.ts +85 -0
  131. package/bin/types/session/SessionOptions.d.ts.map +1 -0
  132. package/bin/types/session/SessionOptions.js +10 -0
  133. package/bin/types/session/SessionOptions.js.map +1 -0
  134. package/package.json +1 -1
  135. package/src/agent/local/Agent.ts +74 -433
  136. package/src/agent/local/AgentRuntimeFactory.ts +4 -25
  137. package/src/agent/local/services/AgentAssemblyService.ts +268 -0
  138. package/src/agent/local/services/AgentLifecycleService.ts +187 -0
  139. package/src/agent/local/services/AgentSessionManager.ts +291 -0
  140. package/src/executor/Executor.ts +103 -441
  141. package/src/executor/README.md +4 -4
  142. package/src/executor/SessionRunScope.ts +47 -71
  143. package/src/executor/composer/context/LocalSessionContextComposer.ts +24 -20
  144. package/src/executor/composer/context/SessionContextComposer.ts +13 -3
  145. package/src/executor/composer/system/SessionSystemComposer.ts +2 -1
  146. package/src/executor/composer/system/default/DefaultSessionSystemComposer.ts +3 -4
  147. package/src/executor/core-engine/CoreEngineRunner.ts +433 -0
  148. package/src/executor/core-engine/CoreEngineUiStreamCollector.ts +7 -5
  149. package/src/executor/services/ExecutorInflightService.ts +101 -0
  150. package/src/executor/services/ExecutorRecoveryPolicy.ts +213 -0
  151. package/src/executor/tools/plugin/PluginToolBridge.ts +161 -0
  152. package/src/executor/tools/plugin/PluginToolDefinition.ts +32 -0
  153. package/src/executor/tools/plugin/PluginToolSchemas.ts +20 -0
  154. package/src/executor/tools/plugin/types/PluginTool.ts +41 -0
  155. package/src/executor/tools/shell/ShellToolBridge.ts +3 -3
  156. package/src/executor/types/SessionRun.ts +20 -0
  157. package/src/index.ts +33 -0
  158. package/src/plugin/core/BasePlugin.ts +2 -2
  159. package/src/plugin/core/ImagePlugin.ts +128 -0
  160. package/src/session/Session.ts +178 -485
  161. package/src/session/SessionSystemBuilder.ts +3 -3
  162. package/src/session/services/SessionStateService.ts +341 -0
  163. package/src/session/services/SessionTurnService.ts +202 -0
  164. package/src/session/services/SessionViewService.ts +301 -0
  165. package/src/types/agent/AgentOptions.ts +25 -0
  166. package/src/types/agent/AgentTypes.ts +10 -0
  167. package/src/types/agent/SessionTypes.ts +1 -0
  168. package/src/types/executor/SessionRunContext.ts +76 -0
  169. package/src/types/plugin/ImagePlugin.ts +103 -0
  170. package/src/types/session/SessionComposerOptions.ts +107 -0
  171. package/src/types/session/SessionLocalState.ts +40 -0
  172. package/src/types/session/SessionOptions.ts +99 -0
  173. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,433 @@
1
+ /**
2
+ * CoreEngineRunner:模型与 tool-loop 主循环执行器。
3
+ *
4
+ * 关键点(中文)
5
+ * - 只负责单次已装配输入的执行,不负责 run scope、外层重试与历史准备。
6
+ * - 把 step 循环、续写恢复、最终 assistant 汇总等细节从 Executor 中剥离。
7
+ * - 保持失败返回结构稳定,避免对外 Session 行为变化。
8
+ */
9
+
10
+ import { streamText, type FileUIPart, type LanguageModel } from "ai";
11
+ import { buildOpenAIResponsesProviderOptions } from "@executor/messages/SessionMessageCodec.js";
12
+ import { logAssistantMessageNow } from "@executor/messages/SessionMessageLog.js";
13
+ import {
14
+ MAX_INCOMPLETE_RESPONSE_RECOVERIES,
15
+ MAX_TEXT_ONLY_CONTINUATIONS,
16
+ MAX_TOOL_LOOP_STEPS,
17
+ buildIncompleteResponseRecoveryNudge,
18
+ buildTextOnlyContinuationNudge,
19
+ detectIncompleteResponse,
20
+ detectTextOnlyContinuationReason,
21
+ mergeAssistantUiMessages,
22
+ summarizeStepForDebug,
23
+ summarizeUiMessageForDebug,
24
+ toInlinePreview,
25
+ } from "@executor/core-engine/CoreEngineSignals.js";
26
+ import {
27
+ evaluateCoreEngineLoopDecision,
28
+ shouldContinueForTailMergedUserMessages,
29
+ } from "@executor/core-engine/CoreEngineLoopDecision.js";
30
+ import {
31
+ resolveEffectiveCoreEngineError,
32
+ summarizeStreamError,
33
+ } from "@executor/core-engine/CoreEngineError.js";
34
+ import { collectFinalAssistantMessageFromUiStream } from "@executor/core-engine/CoreEngineUiStreamCollector.js";
35
+ import { CoreEngineMessageState } from "@executor/core-engine/CoreEngineMessageState.js";
36
+ import type { Logger } from "@/utils/logger/Logger.js";
37
+ import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
38
+ import type { SessionContextComposer } from "@executor/composer/context/SessionContextComposer.js";
39
+ import type { SessionRunContext } from "@/types/executor/SessionRunContext.js";
40
+ import type {
41
+ SessionExecuteInput,
42
+ SessionRunResult,
43
+ } from "@/executor/types/SessionRun.js";
44
+ import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
45
+
46
+ /**
47
+ * 生成 file part 去重 key。
48
+ */
49
+ function build_file_part_key(part: FileUIPart): string {
50
+ return [
51
+ String(part.type || ""),
52
+ String(part.mediaType || ""),
53
+ String(part.filename || ""),
54
+ String(part.url || ""),
55
+ ].join("\n");
56
+ }
57
+
58
+ /**
59
+ * 把 tool/plugin 运行期产生的 file parts 并入最终 assistant UIMessage。
60
+ */
61
+ function mergePendingAssistantFileParts(
62
+ message: SessionMessageV1,
63
+ parts: FileUIPart[],
64
+ ): SessionMessageV1 {
65
+ if (!Array.isArray(parts) || parts.length === 0) return message;
66
+ const current_parts = Array.isArray(message.parts) ? message.parts : [];
67
+ const seen = new Set<string>();
68
+ for (const part of current_parts) {
69
+ const candidate = part as FileUIPart;
70
+ if (candidate?.type !== "file") continue;
71
+ seen.add(build_file_part_key(candidate));
72
+ }
73
+ const next_file_parts = parts.filter((part) => {
74
+ const key = build_file_part_key(part);
75
+ if (seen.has(key)) return false;
76
+ seen.add(key);
77
+ return true;
78
+ });
79
+ if (next_file_parts.length === 0) return message;
80
+ return {
81
+ ...message,
82
+ parts: [...current_parts, ...next_file_parts],
83
+ };
84
+ }
85
+
86
+ interface CoreEngineRunnerOptions {
87
+ /**
88
+ * 当前 session 对应的 history 事实源。
89
+ */
90
+ history_store: SessionHistoryStore;
91
+
92
+ /**
93
+ * 当前 session 对应的 context Composer。
94
+ */
95
+ context_composer: SessionContextComposer;
96
+
97
+ /**
98
+ * 当前 session 统一日志器。
99
+ */
100
+ logger: Logger;
101
+
102
+ /**
103
+ * 判断某次执行错误是否应该上抛给外层压缩重试。
104
+ */
105
+ should_compact_on_error: (error: unknown) => boolean;
106
+ }
107
+
108
+ interface CoreEngineRunInput {
109
+ /**
110
+ * 已装配好的执行输入。
111
+ */
112
+ execute_input: SessionExecuteInput;
113
+
114
+ /**
115
+ * 当前轮模型实例。
116
+ */
117
+ model: LanguageModel;
118
+
119
+ /**
120
+ * 当前显式运行上下文。
121
+ */
122
+ run_context: SessionRunContext;
123
+ }
124
+
125
+ /**
126
+ * 模型与 tool-loop 主循环执行器。
127
+ */
128
+ export class CoreEngineRunner {
129
+ private readonly history_store: SessionHistoryStore;
130
+ private readonly context_composer: SessionContextComposer;
131
+ private readonly logger: Logger;
132
+ private readonly should_compact_on_error: CoreEngineRunnerOptions["should_compact_on_error"];
133
+
134
+ constructor(options: CoreEngineRunnerOptions) {
135
+ this.history_store = options.history_store;
136
+ this.context_composer = options.context_composer;
137
+ this.logger = options.logger;
138
+ this.should_compact_on_error = options.should_compact_on_error;
139
+ }
140
+
141
+ /**
142
+ * 执行一次已装配完成的模型/tool-loop 运行。
143
+ */
144
+ async run(input: CoreEngineRunInput): Promise<SessionRunResult> {
145
+ const start_time = Date.now();
146
+ const session_id = String(this.history_store.sessionId || "").trim();
147
+ const system = Array.isArray(input.execute_input.system)
148
+ ? input.execute_input.system
149
+ : [];
150
+ const tools = input.execute_input.tools;
151
+ let last_observed_stream_error: unknown = undefined;
152
+
153
+ try {
154
+ const message_state = await CoreEngineMessageState.create({
155
+ messages: input.execute_input.messages,
156
+ tools,
157
+ });
158
+
159
+ const append_merged_user_messages = (messages: SessionMessageV1[]) =>
160
+ message_state.appendMergedUserMessages(messages);
161
+
162
+ const context_composer_on_step_finish =
163
+ this.context_composer.createOnStepFinishHandler(input.run_context);
164
+ let step_count = 0;
165
+ let total_tool_call_count = 0;
166
+ let total_tool_result_count = 0;
167
+ const on_step_finish = async (step_result: unknown): Promise<void> => {
168
+ step_count += 1;
169
+ const summary = summarizeStepForDebug(step_result);
170
+ total_tool_call_count +=
171
+ typeof summary.toolCallCount === "number" ? summary.toolCallCount : 0;
172
+ total_tool_result_count +=
173
+ typeof summary.toolResultCount === "number"
174
+ ? summary.toolResultCount
175
+ : 0;
176
+ await this.logger.log("info", "[agent] step.finish", {
177
+ sessionId: session_id,
178
+ stepIndex: step_count,
179
+ ...summary,
180
+ });
181
+ await context_composer_on_step_finish(step_result);
182
+ };
183
+
184
+ const prepare_step = this.context_composer.createPrepareStepHandler({
185
+ system,
186
+ appendMergedUserMessages: append_merged_user_messages,
187
+ runContext: input.run_context,
188
+ });
189
+
190
+ let final_assistant_ui_message: SessionMessageV1 | null = null;
191
+ let text_only_continuation_count = 0;
192
+ let incomplete_response_recovery_count = 0;
193
+
194
+ while (step_count < MAX_TOOL_LOOP_STEPS) {
195
+ const result = streamText({
196
+ model: input.model,
197
+ system,
198
+ onStepFinish: on_step_finish,
199
+ prepareStep: prepare_step,
200
+ messages: message_state.modelMessages,
201
+ tools,
202
+ providerOptions: buildOpenAIResponsesProviderOptions(),
203
+ onError: async ({ error }) => {
204
+ last_observed_stream_error = error;
205
+ await this.logger.log("error", "[agent] stream.error", {
206
+ sessionId: session_id,
207
+ ...summarizeStreamError(error),
208
+ });
209
+ },
210
+ });
211
+
212
+ const step_assistant_ui_message =
213
+ await collectFinalAssistantMessageFromUiStream({
214
+ result,
215
+ sessionId: session_id,
216
+ logger: this.logger,
217
+ buildFallbackAssistantMessage: (text) =>
218
+ this.context_composer.buildFallbackAssistantMessage(
219
+ text,
220
+ input.run_context,
221
+ ),
222
+ onUiMessageChunkCallback: input.run_context.onUiMessageChunkCallback,
223
+ });
224
+
225
+ const executed_steps = await result.steps;
226
+ const last_step = executed_steps[executed_steps.length - 1];
227
+ if (!last_step) break;
228
+
229
+ const incomplete_response = detectIncompleteResponse({
230
+ stepResult: last_step,
231
+ assistantMessage: step_assistant_ui_message,
232
+ });
233
+ const text_only_continuation_reason =
234
+ detectTextOnlyContinuationReason(last_step);
235
+ const loop_decision = evaluateCoreEngineLoopDecision({
236
+ hasIncompleteResponse: incomplete_response !== null,
237
+ incompleteRecoveryCount: incomplete_response_recovery_count,
238
+ maxIncompleteRecoveries: MAX_INCOMPLETE_RESPONSE_RECOVERIES,
239
+ textOnlyContinuationReason: text_only_continuation_reason,
240
+ textOnlyContinuationCount: text_only_continuation_count,
241
+ maxTextOnlyContinuations: MAX_TEXT_ONLY_CONTINUATIONS,
242
+ hasTools: Object.keys(tools).length > 0,
243
+ toolCallCount: last_step.toolCalls.length,
244
+ });
245
+
246
+ await this.logger.log("info", "[agent] loop.decision", {
247
+ sessionId: session_id,
248
+ stepIndex: step_count,
249
+ continueForToolCalls: loop_decision.continueForToolCalls,
250
+ continueForTextOnly: loop_decision.continueForTextOnly,
251
+ continueForIncompleteRecovery:
252
+ loop_decision.continueForIncompleteRecovery,
253
+ decisionKind: loop_decision.kind,
254
+ textOnlyContinuationReason: text_only_continuation_reason,
255
+ textOnlyContinuationCount: text_only_continuation_count,
256
+ incompleteResponseReason: incomplete_response?.reason ?? null,
257
+ incompleteResponseRecoveryCount: incomplete_response_recovery_count,
258
+ toolCallCount: last_step.toolCalls.length,
259
+ toolResultCount: last_step.toolResults.length,
260
+ finishReason: last_step.finishReason,
261
+ textPreview: toInlinePreview(last_step.text),
262
+ });
263
+
264
+ if (
265
+ loop_decision.continueForIncompleteRecovery &&
266
+ incomplete_response
267
+ ) {
268
+ incomplete_response_recovery_count += 1;
269
+ await this.logger.log("warn", "[agent] incomplete_response.recover", {
270
+ sessionId: session_id,
271
+ stepIndex: step_count,
272
+ recoveryCount: incomplete_response_recovery_count,
273
+ reason: incomplete_response.reason,
274
+ ...incomplete_response.details,
275
+ });
276
+ const recovery_message = this.history_store.userText({
277
+ text: buildIncompleteResponseRecoveryNudge(
278
+ incomplete_response_recovery_count,
279
+ ),
280
+ metadata: {
281
+ sessionId: session_id,
282
+ extra: {
283
+ internal: "agent_incomplete_response_recover",
284
+ reason: incomplete_response.reason,
285
+ stepIndex: step_count,
286
+ },
287
+ },
288
+ });
289
+ await message_state.appendUserTextMessage(recovery_message);
290
+ continue;
291
+ }
292
+
293
+ if (incomplete_response) {
294
+ await this.logger.log("error", "[agent] incomplete_response", {
295
+ sessionId: session_id,
296
+ stepIndex: step_count,
297
+ reason: incomplete_response.reason,
298
+ recoveryCount: incomplete_response_recovery_count,
299
+ ...incomplete_response.details,
300
+ });
301
+ throw new Error(
302
+ `Agent received incomplete response (${incomplete_response.reason})`,
303
+ );
304
+ }
305
+
306
+ const response_messages = Array.isArray(last_step.response?.messages)
307
+ ? last_step.response.messages
308
+ : [];
309
+ message_state.appendModelMessages(response_messages);
310
+
311
+ final_assistant_ui_message = mergeAssistantUiMessages(
312
+ final_assistant_ui_message,
313
+ step_assistant_ui_message,
314
+ );
315
+
316
+ // 关键点(中文):把本 step 的 assistant UI 消息并入运行时上下文,保证后续全量重算不丢历史。
317
+ message_state.appendRuntimeSessionMessage(step_assistant_ui_message);
318
+
319
+ if (loop_decision.continueForToolCalls) {
320
+ text_only_continuation_count = 0;
321
+ incomplete_response_recovery_count = 0;
322
+ continue;
323
+ }
324
+
325
+ if (loop_decision.continueForTextOnly) {
326
+ text_only_continuation_count += 1;
327
+ incomplete_response_recovery_count = 0;
328
+ const continuation_message = this.history_store.userText({
329
+ text: buildTextOnlyContinuationNudge(text_only_continuation_count),
330
+ metadata: {
331
+ sessionId: session_id,
332
+ extra: {
333
+ internal: "agent_loop_auto_continue",
334
+ reason: text_only_continuation_reason,
335
+ stepIndex: step_count,
336
+ },
337
+ },
338
+ });
339
+ await message_state.appendUserTextMessage(continuation_message);
340
+ continue;
341
+ }
342
+
343
+ // 关键点(中文):stop 前做 tail merge,覆盖最后一个 step 后才入队的新 user 消息。
344
+ const tail_prepared = await prepare_step({ messages: [] });
345
+ const tail_merged_message_count = Array.isArray(tail_prepared.messages)
346
+ ? tail_prepared.messages.length
347
+ : 0;
348
+ if (
349
+ shouldContinueForTailMergedUserMessages({
350
+ mergedUserMessageCount: tail_merged_message_count,
351
+ })
352
+ ) {
353
+ text_only_continuation_count = 0;
354
+ incomplete_response_recovery_count = 0;
355
+ await this.logger.log("info", "[agent] loop.tail_merge_continue", {
356
+ sessionId: session_id,
357
+ stepIndex: step_count,
358
+ mergedUserMessageCount: tail_merged_message_count,
359
+ });
360
+ continue;
361
+ }
362
+
363
+ break;
364
+ }
365
+
366
+ if (step_count >= MAX_TOOL_LOOP_STEPS) {
367
+ await this.logger.log("warn", "[agent] loop.max_steps_reached", {
368
+ sessionId: session_id,
369
+ stepCount: step_count,
370
+ totalToolCallCount: total_tool_call_count,
371
+ totalToolResultCount: total_tool_result_count,
372
+ });
373
+ }
374
+
375
+ const final_message = mergePendingAssistantFileParts(
376
+ final_assistant_ui_message ||
377
+ this.context_composer.buildFallbackAssistantMessage(
378
+ "Execution completed",
379
+ input.run_context,
380
+ ),
381
+ input.run_context.pendingAssistantFileParts,
382
+ );
383
+
384
+ await this.logger.log("info", "[agent] final.message", {
385
+ sessionId: session_id,
386
+ ...summarizeUiMessageForDebug(final_message),
387
+ });
388
+ await logAssistantMessageNow(this.logger, final_message);
389
+
390
+ const duration = Date.now() - start_time;
391
+ await this.logger.log("info", "[agent] finish", {
392
+ sessionId: session_id,
393
+ duration,
394
+ stepCount: step_count,
395
+ totalToolCallCount: total_tool_call_count,
396
+ totalToolResultCount: total_tool_result_count,
397
+ });
398
+
399
+ return {
400
+ success: true,
401
+ assistantMessage: final_message,
402
+ deferredPersistedUserMessages: [
403
+ ...input.run_context.deferredPersistedUserMessages,
404
+ ],
405
+ };
406
+ } catch (error) {
407
+ if (this.should_compact_on_error(error)) {
408
+ throw error;
409
+ }
410
+
411
+ const error_text = resolveEffectiveCoreEngineError({
412
+ error,
413
+ streamError: last_observed_stream_error,
414
+ });
415
+
416
+ await this.logger.log("error", "CoreEngine execution failed", {
417
+ error: error_text,
418
+ });
419
+
420
+ return {
421
+ success: false,
422
+ error: error_text,
423
+ assistantMessage: this.context_composer.buildFallbackAssistantMessage(
424
+ `Execution failed: ${error_text}`,
425
+ input.run_context,
426
+ ),
427
+ deferredPersistedUserMessages: [
428
+ ...input.run_context.deferredPersistedUserMessages,
429
+ ],
430
+ };
431
+ }
432
+ }
433
+ }
@@ -11,7 +11,7 @@ import type { streamText } from "ai";
11
11
  import type { Logger } from "@/utils/logger/Logger.js";
12
12
  import type { JsonObject } from "@/types/common/Json.js";
13
13
  import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
14
- import { getSessionRunScope } from "@executor/SessionRunScope.js";
14
+ import type { SessionUiMessageChunkCallback } from "@/executor/types/SessionRun.js";
15
15
  import {
16
16
  summarizeUiMessageForDebug,
17
17
  toInlinePreview,
@@ -37,6 +37,10 @@ export async function collectFinalAssistantMessageFromUiStream(params: {
37
37
  * 构造 fallback assistant 消息的工厂函数。
38
38
  */
39
39
  buildFallbackAssistantMessage: (text: string) => SessionMessageV1;
40
+ /**
41
+ * UI stream chunk 回调。
42
+ */
43
+ onUiMessageChunkCallback?: SessionUiMessageChunkCallback;
40
44
  }): Promise<SessionMessageV1> {
41
45
  let streamedAssistantMessage: SessionMessageV1 | null = null;
42
46
  let uiFinishSummary: JsonObject | null = null;
@@ -57,12 +61,10 @@ export async function collectFinalAssistantMessageFromUiStream(params: {
57
61
  },
58
62
  });
59
63
 
60
- const onUiMessageChunkCallback =
61
- getSessionRunScope()?.onUiMessageChunkCallback;
62
64
  for await (const chunk of uiStream) {
63
- if (typeof onUiMessageChunkCallback !== "function") continue;
65
+ if (typeof params.onUiMessageChunkCallback !== "function") continue;
64
66
  try {
65
- await onUiMessageChunkCallback(chunk);
67
+ await params.onUiMessageChunkCallback(chunk);
66
68
  } catch {
67
69
  // ignore UI stream callback failures
68
70
  }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * ExecutorInflightService:运行中 assistant 快照写入服务。
3
+ *
4
+ * 关键点(中文)
5
+ * - 只负责把 step/tool 增量写入当前 inflight assistant 快照。
6
+ * - 不负责长期 history 组装,不负责主执行循环。
7
+ * - 保持与 SessionHistoryStore 的交互边界清晰,避免 Executor 自身同时承担编排与写入细节。
8
+ */
9
+
10
+ import { generateId } from "@/utils/Id.js";
11
+ import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
12
+ import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
13
+
14
+ interface ExecutorInflightServiceOptions {
15
+ /**
16
+ * 当前 session 标识。
17
+ */
18
+ session_id: string;
19
+
20
+ /**
21
+ * 当前 session 对应的 history 事实源。
22
+ */
23
+ history_store: SessionHistoryStore;
24
+
25
+ /**
26
+ * inflight 更新完成后的异步通知。
27
+ */
28
+ run_after_session_updated_async?: () => Promise<void>;
29
+ }
30
+
31
+ /**
32
+ * 运行中 assistant 快照写入服务。
33
+ */
34
+ export class ExecutorInflightService {
35
+ private readonly session_id: string;
36
+ private readonly history_store: SessionHistoryStore;
37
+ private readonly run_after_session_updated_async?: ExecutorInflightServiceOptions["run_after_session_updated_async"];
38
+
39
+ constructor(options: ExecutorInflightServiceOptions) {
40
+ this.session_id = String(options.session_id || "").trim();
41
+ this.history_store = options.history_store;
42
+ this.run_after_session_updated_async =
43
+ options.run_after_session_updated_async;
44
+
45
+ if (!this.session_id) {
46
+ throw new Error("ExecutorInflightService requires a non-empty session_id");
47
+ }
48
+ }
49
+
50
+ /**
51
+ * 把 step/tool 过程增量写入当前运行中的 assistant 快照。
52
+ */
53
+ async append_assistant_step_parts(
54
+ parts: SessionMessageV1["parts"],
55
+ ): Promise<void> {
56
+ const normalized_parts = Array.isArray(parts)
57
+ ? parts.filter((part) => part && typeof part === "object")
58
+ : [];
59
+ if (normalized_parts.length === 0) return;
60
+
61
+ const current_inflight = await this.history_store.readInflight();
62
+ const next_message: SessionMessageV1 = current_inflight
63
+ ? {
64
+ ...current_inflight,
65
+ metadata: {
66
+ ...(current_inflight.metadata || {
67
+ v: 1 as const,
68
+ ts: Date.now(),
69
+ sessionId: this.session_id,
70
+ }),
71
+ ts: Date.now(),
72
+ sessionId: this.session_id,
73
+ source: "egress",
74
+ kind: "normal",
75
+ },
76
+ parts: [
77
+ ...(Array.isArray(current_inflight.parts)
78
+ ? current_inflight.parts
79
+ : []),
80
+ ...normalized_parts,
81
+ ],
82
+ }
83
+ : {
84
+ id: `a:${this.session_id}:${generateId()}`,
85
+ role: "assistant",
86
+ metadata: {
87
+ v: 1,
88
+ ts: Date.now(),
89
+ sessionId: this.session_id,
90
+ source: "egress",
91
+ kind: "normal",
92
+ },
93
+ parts: normalized_parts,
94
+ };
95
+
96
+ await this.history_store.writeInflight(next_message);
97
+ if (this.run_after_session_updated_async) {
98
+ await this.run_after_session_updated_async();
99
+ }
100
+ }
101
+ }