autosnippet 3.2.7 → 3.2.9

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 (147) hide show
  1. package/bin/cli.js +13 -5
  2. package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
  3. package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +26 -29
  6. package/lib/cli/SetupService.js +1 -1
  7. package/lib/core/AstAnalyzer.js +27 -5
  8. package/lib/core/analysis/CallEdgeResolver.js +402 -0
  9. package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
  10. package/lib/core/analysis/CallSiteExtractor.js +629 -0
  11. package/lib/core/analysis/DataFlowInferrer.js +57 -0
  12. package/lib/core/analysis/ImportPathResolver.js +189 -0
  13. package/lib/core/analysis/ImportRecord.js +105 -0
  14. package/lib/core/analysis/SymbolTableBuilder.js +211 -0
  15. package/lib/core/ast/ProjectGraph.js +8 -0
  16. package/lib/core/ast/lang-dart.js +352 -5
  17. package/lib/core/ast/lang-go.js +212 -10
  18. package/lib/core/ast/lang-java.js +205 -1
  19. package/lib/core/ast/lang-kotlin.js +330 -1
  20. package/lib/core/ast/lang-python.js +31 -2
  21. package/lib/core/ast/lang-rust.js +284 -3
  22. package/lib/core/ast/lang-swift.js +180 -1
  23. package/lib/core/ast/lang-typescript.js +290 -1
  24. package/lib/core/discovery/index.js +2 -2
  25. package/lib/external/ai/AiProvider.js +66 -172
  26. package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
  27. package/lib/external/mcp/McpServer.js +1 -0
  28. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
  29. package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
  30. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
  31. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
  32. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
  33. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
  34. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  35. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
  36. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
  37. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
  38. package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
  39. package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
  40. package/lib/external/mcp/handlers/consolidated.js +9 -0
  41. package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
  42. package/lib/external/mcp/handlers/guard.js +3 -3
  43. package/lib/external/mcp/handlers/structure.js +62 -0
  44. package/lib/external/mcp/handlers/wiki-external.js +66 -3
  45. package/lib/external/mcp/tools.js +36 -1
  46. package/lib/http/HttpServer.js +1 -1
  47. package/lib/http/middleware/requestLogger.js +1 -0
  48. package/lib/http/routes/ai.js +240 -35
  49. package/lib/http/routes/candidates.js +2 -3
  50. package/lib/http/routes/extract.js +13 -11
  51. package/lib/http/routes/modules.js +2 -2
  52. package/lib/http/routes/recipes.js +9 -5
  53. package/lib/http/routes/remote.js +149 -270
  54. package/lib/http/routes/violations.js +0 -54
  55. package/lib/http/utils/sse-sessions.js +1 -1
  56. package/lib/infrastructure/logging/Logger.js +5 -4
  57. package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
  58. package/lib/injection/ServiceContainer.js +70 -28
  59. package/lib/platform/ScreenCaptureService.js +177 -0
  60. package/lib/platform/ios/index.js +2 -2
  61. package/lib/platform/ios/routes/spm.js +2 -2
  62. package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
  63. package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
  64. package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
  65. package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
  66. package/lib/service/agent/AgentEventBus.js +207 -0
  67. package/lib/service/agent/AgentFactory.js +490 -0
  68. package/lib/service/agent/AgentMessage.js +240 -0
  69. package/lib/service/agent/AgentRouter.js +228 -0
  70. package/lib/service/agent/AgentRuntime.js +1016 -0
  71. package/lib/service/agent/AgentState.js +217 -0
  72. package/lib/service/agent/IntentClassifier.js +331 -0
  73. package/lib/service/agent/LarkTransport.js +389 -0
  74. package/lib/service/agent/capabilities.js +408 -0
  75. package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
  76. package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
  77. package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
  78. package/lib/service/agent/core/LoopContext.js +170 -0
  79. package/lib/service/agent/core/MessageAdapter.js +223 -0
  80. package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
  81. package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
  82. package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
  83. package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
  84. package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
  85. package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
  86. package/lib/service/agent/domain/insight-producer.js +267 -0
  87. package/lib/service/agent/domain/scan-prompts.js +105 -0
  88. package/lib/service/agent/forced-summary.js +266 -0
  89. package/lib/service/agent/index.js +91 -0
  90. package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
  91. package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
  92. package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
  93. package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
  94. package/lib/service/{chat → agent}/memory/index.js +1 -1
  95. package/lib/service/agent/policies.js +442 -0
  96. package/lib/service/agent/presets.js +303 -0
  97. package/lib/service/agent/strategies.js +717 -0
  98. package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
  99. package/lib/service/agent/tools/ai-analysis.js +75 -0
  100. package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
  101. package/lib/service/{chat → agent}/tools/composite.js +2 -1
  102. package/lib/service/{chat → agent}/tools/guard.js +1 -121
  103. package/lib/service/{chat → agent}/tools/index.js +33 -22
  104. package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
  105. package/lib/service/agent/tools/knowledge-graph.js +112 -0
  106. package/lib/service/agent/tools/scan-recipe.js +189 -0
  107. package/lib/service/agent/tools/system-interaction.js +476 -0
  108. package/lib/service/automation/DirectiveDetector.js +0 -1
  109. package/lib/service/automation/FileWatcher.js +0 -8
  110. package/lib/service/automation/handlers/CreateHandler.js +7 -3
  111. package/lib/service/automation/handlers/DraftHandler.js +7 -6
  112. package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
  113. package/lib/service/knowledge/CodeEntityGraph.js +327 -2
  114. package/lib/service/knowledge/KnowledgeService.js +5 -1
  115. package/lib/service/module/ModuleService.js +49 -73
  116. package/lib/service/skills/SignalCollector.js +26 -19
  117. package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
  118. package/lib/service/wiki/WikiGenerator.js +1 -1
  119. package/lib/shared/FieldSpec.js +1 -1
  120. package/lib/shared/PathGuard.js +1 -1
  121. package/lib/shared/StyleGuide.js +1 -1
  122. package/package.json +4 -1
  123. package/resources/native-ui/screenshot.swift +228 -0
  124. package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
  125. package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
  126. package/lib/core/discovery/SpmDiscoverer.js +0 -5
  127. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
  128. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
  129. package/lib/http/routes/spm.js +0 -5
  130. package/lib/infrastructure/external/XcodeAutomation.js +0 -15
  131. package/lib/service/chat/ChatAgent.js +0 -1602
  132. package/lib/service/chat/Memory.js +0 -161
  133. package/lib/service/chat/ProducerAgent.js +0 -431
  134. package/lib/service/chat/ReasoningTrace.js +0 -523
  135. package/lib/service/chat/TaskPipeline.js +0 -357
  136. package/lib/service/chat/WorkingMemory.js +0 -357
  137. package/lib/service/chat/memory/PersistentMemory.js +0 -450
  138. package/lib/service/chat/tools/ai-analysis.js +0 -267
  139. package/lib/service/chat/tools/knowledge-graph.js +0 -234
  140. package/lib/service/chat/tools.js +0 -18
  141. package/lib/service/snippet/PlaceholderConverter.js +0 -5
  142. package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
  143. /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
  144. /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
  145. /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
  146. /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
  147. /package/lib/service/{chat → agent}/tools/query.js +0 -0
@@ -0,0 +1,1016 @@
1
+ /**
2
+ * AgentRuntime — 统一 Agent 执行引擎 (The Brain)
3
+ *
4
+ * 核心思想: 不存在类型分野,只有 ONE Runtime。
5
+ * 只有 ONE Runtime,由 Capability + Strategy + Policy 配置驱动。
6
+ *
7
+ * AgentRuntime 是:
8
+ * - ReAct 循环的宿主 (Thought → Action → Observation)
9
+ * - Capability 的组合容器 (加载哪些技能)
10
+ * - Policy 的执行者 (遵守哪些约束)
11
+ * - Strategy 的被委托者 (Strategy 调用 runtime.reactLoop())
12
+ *
13
+ * 认知架构 (CoALA):
14
+ * Perception → Working Memory → Reasoning → Action → Reflection
15
+ * │ │ │ │ │
16
+ * AgentMessage history+memory LLM call Tools Policy.validateAfter
17
+ *
18
+ * 引擎级能力:
19
+ * - ContextWindow: 三级递进压缩,动态 token 预算 (可选注入)
20
+ * - ExplorationTracker: 阶段状态机 + 信号收集 + Nudge + Graceful exit (可选注入)
21
+ * - AI 错误恢复: consecutiveAiErrors 2-strike → context reset → forced summary
22
+ * - 空响应重试: consecutiveEmptyResponses + rollback (system 场景)
23
+ * - 熔断器感知: _circuitState === 'OPEN' → 直接合成摘要
24
+ * - 工具调用数量限制: MAX_TOOL_CALLS_PER_ITER = 8
25
+ * - 提交去重: submittedTitles / submittedPatterns
26
+ * - cleanFinalAnswer: 去除 nudge 噪声
27
+ *
28
+ * @module AgentRuntime
29
+ */
30
+
31
+ import { randomUUID } from 'node:crypto';
32
+ import Logger from '../../infrastructure/logging/Logger.js';
33
+ import { AgentState, AgentPhase } from './AgentState.js';
34
+ import { AgentEventBus, AgentEvents } from './AgentEventBus.js';
35
+ import { PolicyEngine, BudgetPolicy, SafetyPolicy } from './policies.js';
36
+ import { CapabilityRegistry, Capability } from './capabilities.js';
37
+ import { limitToolResult } from './context/ContextWindow.js';
38
+ import { cleanFinalAnswer } from './core/ChatAgentPrompts.js';
39
+ import { produceForcedSummary } from './forced-summary.js';
40
+ import { createMessageAdapter } from './core/MessageAdapter.js';
41
+ import { LoopContext } from './core/LoopContext.js';
42
+ import { createToolPipeline } from './core/ToolExecutionPipeline.js';
43
+
44
+ /** 单次迭代允许的最大工具调用数 */
45
+ const MAX_TOOL_CALLS_PER_ITER = 8;
46
+
47
+ /**
48
+ * @typedef {Object} RuntimeConfig
49
+ * @property {string} [id] — 运行时唯一 ID
50
+ * @property {string} [presetName] — 使用的 Preset 名称 (仅标识)
51
+ * @property {import('../../external/ai/AiProvider.js').AiProvider} aiProvider — LLM 提供商
52
+ * @property {import('./tools/ToolRegistry.js').ToolRegistry} toolRegistry — 工具注册表
53
+ * @property {Object} [container] — DI 容器
54
+ * @property {Capability[]} capabilities — 已实例化的 Capability 列表
55
+ * @property {import('./strategies.js').Strategy} strategy — 执行策略
56
+ * @property {PolicyEngine} policies — 策略引擎
57
+ * @property {Object} [persona] — 人格配置
58
+ * @property {Object} [memory] — 记忆配置
59
+ * @property {Function} [onProgress] — 进度回调
60
+ * @property {Function} [onToolCall] — 工具调用通知钩子 (name, args, result, iteration)
61
+ * @property {string} [lang] — 语言偏好
62
+ * @property {string} [projectRoot] — 项目根目录 (传入工具上下文)
63
+ */
64
+
65
+ export class AgentRuntime {
66
+ /** @type {string} */
67
+ id;
68
+ /** @type {string} */
69
+ presetName;
70
+ /** @type {AgentState} */
71
+ state;
72
+ /** @type {AgentEventBus} */
73
+ bus;
74
+ /** @type {import('../../external/ai/AiProvider.js').AiProvider} */
75
+ aiProvider;
76
+ /** @type {import('./tools/ToolRegistry.js').ToolRegistry} */
77
+ toolRegistry;
78
+ /** @type {Object} */
79
+ container;
80
+ /** @type {Capability[]} */
81
+ capabilities;
82
+ /** @type {import('./strategies.js').Strategy} */
83
+ strategy;
84
+ /** @type {PolicyEngine} */
85
+ policies;
86
+ /** @type {Object} */
87
+ persona;
88
+ /** @type {Object} */
89
+ memoryConfig;
90
+ /** @type {Function|null} */
91
+ onProgress;
92
+ /** @type {string|null} */
93
+ lang;
94
+ /** @type {Logger} */
95
+ logger;
96
+ /** @type {string} */
97
+ #projectRoot;
98
+ /** @type {Array|null} 文件缓存 (bootstrap 场景注入) */
99
+ #fileCache = null;
100
+ /** @type {string[]} 额外工具白名单 (调用方按需注入,不经 Capability) */
101
+ #additionalTools = [];
102
+ /** @type {import('./core/ToolExecutionPipeline.js').ToolExecutionPipeline} */
103
+ #toolPipeline;
104
+
105
+ // ── 执行统计 ──
106
+ iterationCount = 0;
107
+ toolCallHistory = [];
108
+ tokenUsage = { input: 0, output: 0 };
109
+ startTime = 0;
110
+
111
+ /**
112
+ * @param {RuntimeConfig} config
113
+ */
114
+ constructor(config) {
115
+ this.id = config.id || `runtime_${randomUUID().slice(0, 8)}`;
116
+ this.presetName = config.presetName || 'custom';
117
+ this.aiProvider = config.aiProvider;
118
+ this.toolRegistry = config.toolRegistry;
119
+ this.container = config.container || null;
120
+ this.capabilities = config.capabilities || [];
121
+ this.strategy = config.strategy;
122
+ this.policies = config.policies || new PolicyEngine([]);
123
+ this.persona = config.persona || {};
124
+ this.memoryConfig = config.memory || {};
125
+ this.onProgress = config.onProgress || null;
126
+ this.onToolCall = config.onToolCall || null;
127
+ this.lang = config.lang || null;
128
+ this.logger = Logger.getInstance();
129
+ this.bus = AgentEventBus.getInstance();
130
+ this.#projectRoot = config.projectRoot || process.cwd();
131
+ this.#additionalTools = config.additionalTools || [];
132
+ this.#toolPipeline = createToolPipeline();
133
+
134
+ this.state = new AgentState({
135
+ initialData: { runtimeId: this.id, preset: this.presetName },
136
+ });
137
+
138
+ this.bus.publish(AgentEvents.AGENT_CREATED, {
139
+ agentId: this.id,
140
+ preset: this.presetName,
141
+ capabilities: this.capabilities.map(c => c.name),
142
+ strategy: this.strategy?.name,
143
+ }, { source: this.id });
144
+ }
145
+
146
+ // ─── 公共 API ─────────────────────────────────
147
+
148
+ /**
149
+ * 执行 Agent — 入口
150
+ *
151
+ * @param {import('./AgentMessage.js').AgentMessage} message — 统一消息
152
+ * @param {Object} [opts] — 策略特定选项 (如 FanOut 的 items)
153
+ * @returns {Promise<AgentResult>}
154
+ *
155
+ * @typedef {Object} AgentResult
156
+ * @property {string} reply — 最终文本回复
157
+ * @property {Array} toolCalls — 工具调用记录
158
+ * @property {Object} tokenUsage — Token 用量
159
+ * @property {number} iterations — 循环次数
160
+ * @property {number} durationMs — 执行耗时
161
+ * @property {Object} [phases] — Pipeline/FanOut 阶段详情
162
+ * @property {Object} state — 状态快照
163
+ */
164
+ async execute(message, opts = {}) {
165
+ this.startTime = Date.now();
166
+ this.iterationCount = 0;
167
+ this.toolCallHistory = [];
168
+ this.tokenUsage = { input: 0, output: 0 };
169
+
170
+ // ── Policy: 执行前校验 ──
171
+ const beforeCheck = this.policies.validateBefore({ message, capabilities: this.capabilities });
172
+ if (!beforeCheck.ok) {
173
+ this.logger.warn(`[AgentRuntime] Policy rejected: ${beforeCheck.reason}`);
174
+ return {
175
+ reply: `⚠️ ${beforeCheck.reason}`,
176
+ toolCalls: [],
177
+ tokenUsage: { input: 0, output: 0 },
178
+ iterations: 0,
179
+ durationMs: 0,
180
+ state: this.state.toJSON(),
181
+ };
182
+ }
183
+
184
+ // ── 超时保护 ──
185
+ const budget = this.policies.getBudget();
186
+ const timeoutMs = budget?.timeoutMs || 300_000;
187
+
188
+ let timeoutId;
189
+ const timeoutPromise = new Promise((_, reject) => {
190
+ timeoutId = setTimeout(() => reject(new Error(`Agent timeout after ${timeoutMs}ms`)), timeoutMs);
191
+ });
192
+
193
+ try {
194
+ // ── 委托给 Strategy ──
195
+ const resultPromise = this.strategy.execute(this, message, opts);
196
+ const result = await Promise.race([resultPromise, timeoutPromise]);
197
+ clearTimeout(timeoutId);
198
+
199
+ // ── Policy: 执行后校验 ──
200
+ const afterCheck = this.policies.validateAfter(result);
201
+ if (!afterCheck.ok) {
202
+ this.logger.warn(`[AgentRuntime] Quality check: ${afterCheck.reason}`);
203
+ result.qualityWarning = afterCheck.reason;
204
+ }
205
+
206
+ // 状态完成
207
+ this.#safeTransition('finish', { reply: result.reply?.slice(0, 100) });
208
+
209
+ // 回复给原始渠道
210
+ if (message.replyFn && result.reply) {
211
+ await message.reply(result.reply);
212
+ }
213
+
214
+ result.state = this.state.toJSON();
215
+ result.durationMs = Date.now() - this.startTime;
216
+
217
+ this.bus.publish(AgentEvents.AGENT_COMPLETED, {
218
+ agentId: this.id,
219
+ preset: this.presetName,
220
+ iterations: result.iterations,
221
+ durationMs: result.durationMs,
222
+ }, { source: this.id });
223
+
224
+ return result;
225
+
226
+ } catch (err) {
227
+ clearTimeout(timeoutId);
228
+ this.state.send('error', { error: err.message });
229
+ this.bus.publish(AgentEvents.AGENT_FAILED, {
230
+ agentId: this.id,
231
+ error: err.message,
232
+ }, { source: this.id });
233
+ throw err;
234
+ }
235
+ }
236
+
237
+ // ─── ReAct Loop — 供 Strategy 调用 ──────────
238
+
239
+ /**
240
+ * 核心 ReAct 循环。Strategy 调用此方法执行实际的 LLM + Tool 交互。
241
+ *
242
+ * 引擎级能力通过可选参数注入:
243
+ * - contextWindow → 三级递进压缩 + 动态工具结果限额
244
+ * - tracker → ExplorationTracker 阶段管理 + Nudge + Graceful exit
245
+ * - trace → ActiveContext 推理链记录
246
+ * - memoryCoordinator → 缓存/动态提示/观察记录
247
+ * - sharedState → 提交去重 { submittedTitles, submittedPatterns }
248
+ * - source → 'user' | 'system' (影响错误恢复 + 强制摘要行为)
249
+ *
250
+ * 向后兼容: 以上参数均为可选。不提供时退化为原始裸循环。
251
+ *
252
+ * @param {string} prompt — 用户/系统提示
253
+ * @param {Object} [opts]
254
+ * @param {Array} [opts.history] — 对话历史
255
+ * @param {Object} [opts.context] — 额外上下文
256
+ * @param {string[]} [opts.capabilityOverride] — 临时覆盖 capability (Pipeline 阶段用)
257
+ * @param {Object} [opts.budgetOverride] — 临时覆盖 budget
258
+ * @param {string} [opts.systemPromptOverride] — 完全覆盖系统提示词 (Bootstrap 阶段专用)
259
+ * @param {Function} [opts.onToolCall] — 本轮独立的工具调用钩子,优先于 runtime 级
260
+ * @param {import('./context/ContextWindow.js').ContextWindow} [opts.contextWindow] — 上下文窗口管理器
261
+ * @param {Object} [opts.tracker] — ExplorationTracker 实例
262
+ * @param {Object} [opts.trace] — ActiveContext 实例
263
+ * @param {Object} [opts.memoryCoordinator] — MemoryCoordinator 实例
264
+ * @param {Object} [opts.sharedState] — 共享状态 { submittedTitles, submittedPatterns }
265
+ * @param {string} [opts.source] — 'user' | 'system'
266
+ * @param {string} [opts.toolChoiceOverride] — 首轮 toolChoice 覆盖 ('required'/'auto'/'none')
267
+ * 首轮强制 LLM 生成 tool call(LLM 自行决定调哪个工具、传什么参数)。
268
+ * 这不是替 LLM 做决定,而是告诉 LLM "你必须调用某个工具"。
269
+ * 仅在第一轮生效,后续轮次恢复正常 toolChoice 逻辑。
270
+ * @returns {Promise<AgentResult>}
271
+ */
272
+ async reactLoop(prompt, opts = {}) {
273
+ const ctx = this.#initLoop(prompt, opts);
274
+
275
+ // ─── ReAct 主循环 (编排骨架) ─────
276
+ while (true) {
277
+ ctx.iteration++;
278
+ this.iterationCount++;
279
+
280
+ // ActiveContext: 开始新轮次 (必须在 #shouldExit 前, 保证 endRound 有配对)
281
+ ctx.trace?.startRound(ctx.iteration);
282
+
283
+ // 退出判定 (tracker + policy)
284
+ if (this.#shouldExit(ctx)) break;
285
+
286
+ // 迭代准备 (hooks + nudge + compact + toolChoice + prompt)
287
+ const { toolChoice, effectiveSystemPrompt, effectivePrompt } =
288
+ this.#prepareIteration(ctx);
289
+
290
+ // LLM 调用 (含错误恢复 + 空响应重试)
291
+ const llmResult = await this.#callLLM(ctx, toolChoice, effectiveSystemPrompt, effectivePrompt);
292
+ if (!llmResult) break;
293
+ if (llmResult.__continue) continue;
294
+
295
+ // ActiveContext: 记录 AI 的推理文本 + 提取/更新计划
296
+ if (ctx.trace && llmResult.text) {
297
+ ctx.trace.setThought(llmResult.text);
298
+ ctx.trace.extractAndSetPlan?.(llmResult.text, ctx.iteration);
299
+ }
300
+
301
+ // 分支: 有 Tool Call
302
+ if (llmResult.functionCalls?.length > 0) {
303
+ const exitAfterTools = await this.#processToolCalls(ctx, llmResult, effectiveSystemPrompt);
304
+ if (exitAfterTools) break;
305
+ continue;
306
+ }
307
+
308
+ // 分支: 纯文本回复
309
+ if (this.#processTextResponse(ctx, llmResult)) break;
310
+ }
311
+
312
+ return this.#finalize(ctx);
313
+ }
314
+
315
+ // ─── 提取方法: reactLoop 内部阶段 ────────────
316
+
317
+ /**
318
+ * 初始化循环上下文 — 封装 reactLoop 前 ~60 行初始化逻辑
319
+ * @param {string} prompt
320
+ * @param {Object} opts
321
+ * @returns {LoopContext}
322
+ */
323
+ #initLoop(prompt, opts) {
324
+ const {
325
+ history = [], context = {}, capabilityOverride, budgetOverride,
326
+ systemPromptOverride, onToolCall,
327
+ contextWindow, tracker, trace, memoryCoordinator, sharedState, source,
328
+ toolChoiceOverride,
329
+ } = opts;
330
+
331
+ // 解析 capabilities
332
+ const caps = capabilityOverride
333
+ ? this.#resolveCapabilities(capabilityOverride)
334
+ : this.capabilities;
335
+
336
+ // 构建基础系统提示词
337
+ const baseSystemPrompt = systemPromptOverride || this.#buildSystemPrompt(caps, context);
338
+
339
+ // 收集工具 (caps 为空数组时 = 明确无工具)
340
+ const allowedTools = this.#collectTools(caps);
341
+ const noToolsExplicit = Array.isArray(capabilityOverride) && capabilityOverride.length === 0;
342
+ const toolSchemas = noToolsExplicit
343
+ ? []
344
+ : this.toolRegistry.getToolSchemas(allowedTools.length > 0 ? allowedTools : null);
345
+
346
+ // 创建统一消息适配器 (消除 useCtxWin 双模式)
347
+ const messages = createMessageAdapter(contextWindow);
348
+
349
+ // 加载历史 + 用户 prompt
350
+ for (const h of history) {
351
+ if (h.role === 'assistant') {
352
+ messages.appendAssistantText(h.content);
353
+ } else {
354
+ messages.appendUserMessage(h.content);
355
+ }
356
+ }
357
+ messages.appendUserMessage(prompt);
358
+
359
+ // 预算
360
+ const budget = budgetOverride || this.policies.getBudget() || {
361
+ maxIterations: 20, maxTokens: 4096, temperature: 0.7,
362
+ };
363
+
364
+ // 状态转移
365
+ this.#safeTransition('start', { prompt: prompt.slice(0, 100) });
366
+ this.#safeTransition('plan_ready');
367
+
368
+ this.bus.publish(AgentEvents.AGENT_STARTED, {
369
+ agentId: this.id,
370
+ prompt: prompt.slice(0, 100),
371
+ capabilities: caps.map(c => c.name),
372
+ }, { source: this.id });
373
+
374
+ return new LoopContext({
375
+ messages,
376
+ tracker: tracker || null,
377
+ trace: trace || null,
378
+ memoryCoordinator: memoryCoordinator || null,
379
+ sharedState: sharedState || null,
380
+ source: source || 'user',
381
+ budget,
382
+ capabilities: caps,
383
+ baseSystemPrompt,
384
+ toolSchemas,
385
+ prompt,
386
+ onToolCall: onToolCall || null,
387
+ context: context || {},
388
+ contextWindow: contextWindow || null,
389
+ toolChoiceOverride: toolChoiceOverride || null,
390
+ });
391
+ }
392
+
393
+ /**
394
+ * 退出判定 — 合并 tracker/policy 退出检查
395
+ * @param {LoopContext} ctx
396
+ * @returns {boolean} true = 应退出循环
397
+ */
398
+ #shouldExit(ctx) {
399
+ // ExplorationTracker: tick + 退出检查
400
+ if (ctx.tracker) {
401
+ ctx.tracker.tick();
402
+ if (ctx.tracker.shouldExit()) {
403
+ this.logger.info(
404
+ `[AgentRuntime] tracker exit: phase=${ctx.tracker.phase}, iter=${ctx.tracker.iteration}, submits=${ctx.tracker.totalSubmits}`
405
+ );
406
+ return true;
407
+ }
408
+ }
409
+
410
+ // Capability 前置钩子
411
+ for (const cap of ctx.capabilities) {
412
+ cap.onBeforeStep({
413
+ iteration: ctx.iteration,
414
+ messages: ctx.messages.toMessages(),
415
+ prompt: ctx.prompt,
416
+ });
417
+ }
418
+
419
+ // ── Per-stage budget timeout (从 budgetOverride 注入) ──
420
+ // 与 BudgetPolicy 的全局 timeoutMs (600s) 不同,此处使用阶段级 budget.timeoutMs
421
+ // 保证 Analyst/Producer 各自有独立的超时边界,避免一个阶段消耗完所有时长
422
+ if (ctx.budget?.timeoutMs && Date.now() - ctx.loopStartTime > ctx.budget.timeoutMs) {
423
+ this.logger.info(
424
+ `[AgentRuntime] ⏰ Stage budget timeout: ${ctx.budget.timeoutMs}ms exceeded (elapsed: ${Date.now() - ctx.loopStartTime}ms)`,
425
+ );
426
+ return true;
427
+ }
428
+
429
+ // Policy 实时校验
430
+ // 当 ExplorationTracker 存在时,由 tracker 自己管理 maxIterations + grace 轮次,
431
+ // 跳过 BudgetPolicy 的 iteration 限制,避免竞争(tracker 给了 grace 但 policy 立即杀掉循环)。
432
+ // tracker 内部有硬上限 (maxIterations + 2) 保证不会无限循环。
433
+ const skipPolicyIterCheck = !!ctx.tracker;
434
+ const duringCheck = this.policies.validateDuring({
435
+ iteration: skipPolicyIterCheck ? 0 : ctx.iteration, // tracker 模式下用 0 绕过 iteration 检查
436
+ toolCalls: this.toolCallHistory,
437
+ tokenUsage: this.tokenUsage,
438
+ startTime: ctx.loopStartTime,
439
+ });
440
+ if (!duringCheck.ok) {
441
+ this.logger.info(`[AgentRuntime] Policy stop: ${duringCheck.reason}`);
442
+ return true;
443
+ }
444
+
445
+ return false;
446
+ }
447
+
448
+ /**
449
+ * 迭代准备 — 合并 nudge/压缩/提示词增强/toolChoice
450
+ * @param {LoopContext} ctx
451
+ * @returns {{ toolChoice: string, effectiveSystemPrompt: string, effectivePrompt: string }}
452
+ */
453
+ #prepareIteration(ctx) {
454
+ const { tracker, trace, capabilities, messages, prompt } = ctx;
455
+ const maxIterations = ctx.maxIterations;
456
+
457
+ this.#emitProgress('thinking', { iteration: ctx.iteration, maxIterations });
458
+
459
+ // Nudge 注入 (ExplorationTracker)
460
+ if (tracker) {
461
+ const nudge = tracker.getNudge(trace);
462
+ if (nudge) {
463
+ messages.appendUserNudge(nudge.text);
464
+ this.logger.info(
465
+ `[AgentRuntime] 💬 injected ${nudge.type} nudge at iter ${ctx.iteration}`
466
+ );
467
+ const _dim = ctx.sharedState?._dimensionMeta?.id || '';
468
+ console.log(`\n\x1b[36m━━━ Nudge [${nudge.type}] iter=${ctx.iteration}${_dim ? ` dim=${_dim}` : ''} ━━━\x1b[0m`);
469
+ console.log(`\x1b[33m${nudge.text}\x1b[0m\n`);
470
+ }
471
+ }
472
+
473
+ // 压缩检查
474
+ const compactResult = messages.compactIfNeeded();
475
+ if (compactResult.level > 0) {
476
+ this.logger.info(
477
+ `[AgentRuntime] context compacted: L${compactResult.level}, removed ${compactResult.removed} items`
478
+ );
479
+ }
480
+
481
+ // 动态 toolChoice
482
+ const forceSummaryAt = Math.max(2, Math.ceil(maxIterations * 0.8));
483
+ const forceSummary = !tracker && ctx.iteration >= forceSummaryAt;
484
+ let toolChoice;
485
+ if (ctx.toolChoiceOverride && ctx.iteration === 1) {
486
+ // 首轮 toolChoice 覆盖: 强制 LLM 生成 tool call (LLM 自行决定调哪个、传什么)
487
+ toolChoice = ctx.toolChoiceOverride;
488
+ } else if (tracker) {
489
+ toolChoice = tracker.getToolChoice();
490
+ } else {
491
+ toolChoice = ctx.toolSchemas.length > 0
492
+ ? (forceSummary ? 'none' : 'auto')
493
+ : 'none';
494
+ }
495
+
496
+ // 系统提示词增强 (阶段上下文 + 动态记忆)
497
+ let effectiveSystemPrompt = ctx.baseSystemPrompt;
498
+ if (tracker) {
499
+ effectiveSystemPrompt += tracker.getPhaseContext();
500
+ } else if (ctx.isSystem && !tracker) {
501
+ const remaining = maxIterations - ctx.iteration;
502
+ effectiveSystemPrompt += `\n\n## 当前进度\n第 ${ctx.iteration}/${maxIterations} 轮 | 剩余 ${remaining} 轮`;
503
+ }
504
+ if (ctx.isSystem && ctx.memoryCoordinator) {
505
+ const wmContext = ctx.memoryCoordinator.buildDynamicMemoryPrompt?.({
506
+ mode: ctx.source || 'analyst',
507
+ scopeId: ctx.context?.dimensionScopeId || null,
508
+ });
509
+ if (wmContext) effectiveSystemPrompt += `\n\n${wmContext}`;
510
+ }
511
+
512
+ // 非 tracker 模式的强制摘要提示注入
513
+ const effectivePrompt = forceSummary
514
+ ? `${prompt}\n\n[系统提示] 已进入最后阶段,请停止调用工具,基于已有信息输出总结。`
515
+ : prompt;
516
+
517
+ return { toolChoice, effectiveSystemPrompt, effectivePrompt };
518
+ }
519
+
520
+ /**
521
+ * LLM 调用 — 含错误恢复 + 空响应重试
522
+ *
523
+ * @param {LoopContext} ctx
524
+ * @param {string} toolChoice
525
+ * @param {string} effectiveSystemPrompt
526
+ * @param {string} effectivePrompt
527
+ * @returns {Promise<Object|null>} llmResult 或 null (表示应退出)
528
+ */
529
+ async #callLLM(ctx, toolChoice, effectiveSystemPrompt, effectivePrompt) {
530
+ this.bus.publish(AgentEvents.LLM_CALL_START, {
531
+ agentId: this.id,
532
+ iteration: ctx.iteration,
533
+ }, { source: this.id });
534
+
535
+ let llmResult;
536
+ try {
537
+ llmResult = await this.aiProvider.chatWithTools(effectivePrompt, {
538
+ messages: ctx.messages.toMessages(),
539
+ toolSchemas: ctx.toolSchemas.length > 0 ? ctx.toolSchemas : undefined,
540
+ toolChoice,
541
+ systemPrompt: effectiveSystemPrompt,
542
+ temperature: ctx.budget.temperature ?? (ctx.isSystem ? 0.3 : 0.7),
543
+ maxTokens: ctx.budget.maxTokens ?? (ctx.isSystem ? 8192 : 4096),
544
+ });
545
+ ctx.consecutiveAiErrors = 0;
546
+ } catch (aiErr) {
547
+ return this.#handleAiError(ctx, aiErr);
548
+ }
549
+
550
+ // 累计 Token (runtime 级 + loop 级)
551
+ if (llmResult.usage) {
552
+ this.tokenUsage.input += (llmResult.usage.inputTokens || 0);
553
+ this.tokenUsage.output += (llmResult.usage.outputTokens || 0);
554
+ ctx.addTokenUsage(llmResult.usage);
555
+ }
556
+
557
+ this.bus.publish(AgentEvents.LLM_CALL_END, {
558
+ agentId: this.id,
559
+ hasToolCalls: !!llmResult.functionCalls?.length,
560
+ hasText: !!llmResult.text,
561
+ usage: llmResult.usage,
562
+ }, { source: this.id });
563
+
564
+ // 空响应重试
565
+ if (!llmResult.text && !llmResult.functionCalls?.length) {
566
+ if (ctx.isSystem && ctx.consecutiveEmptyResponses < 2) {
567
+ ctx.consecutiveEmptyResponses++;
568
+ this.logger.warn(
569
+ `[AgentRuntime] ⚠ empty response — retrying (${ctx.consecutiveEmptyResponses}/2)`
570
+ );
571
+ ctx.tracker?.rollbackTick?.();
572
+ await new Promise(r => setTimeout(r, 1500));
573
+ // 返回 'continue' 信号 — 调用方需重走循环
574
+ return { __continue: true };
575
+ }
576
+ return null; // 退出
577
+ }
578
+ if (llmResult.text || llmResult.functionCalls?.length) {
579
+ ctx.consecutiveEmptyResponses = 0;
580
+ }
581
+
582
+ // Graceful exit 保护
583
+ if (ctx.tracker?.isGracefulExit && llmResult.functionCalls?.length > 0) {
584
+ this.logger.warn(
585
+ `[AgentRuntime] ⚠ AI returned ${llmResult.functionCalls.length} tool calls despite toolChoice=none (graceful exit) — ignoring`
586
+ );
587
+ if (llmResult.text) {
588
+ ctx.lastReply = cleanFinalAnswer(llmResult.text);
589
+ return null; // 退出
590
+ }
591
+ return { __continue: true };
592
+ }
593
+
594
+ return llmResult;
595
+ }
596
+
597
+ /**
598
+ * AI 错误处理 — 熔断器感知 + 2-strike 策略
599
+ * @returns {Object|null} — { __continue: true } 或 null (退出)
600
+ */
601
+ async #handleAiError(ctx, aiErr) {
602
+ ctx.consecutiveAiErrors++;
603
+ this.logger.warn(
604
+ `[AgentRuntime] AI call failed (attempt ${ctx.consecutiveAiErrors}): ${aiErr.message}`
605
+ );
606
+
607
+ ctx.tracker?.rollbackTick?.();
608
+
609
+ // 熔断器感知
610
+ if (aiErr.code === 'CIRCUIT_OPEN') {
611
+ this.logger.warn('[AgentRuntime] 🛑 circuit breaker OPEN — breaking to summary');
612
+ if (!ctx.isSystem) {
613
+ ctx.lastReply = `抱歉,AI 服务暂时不可用(${aiErr.message})。请稍后重试,或检查 API 配置。`;
614
+ }
615
+ return null;
616
+ }
617
+
618
+ // 2-strike 策略
619
+ if (ctx.consecutiveAiErrors >= 2) {
620
+ this.logger.warn('[AgentRuntime] 🛑 2 consecutive AI errors — breaking to summary');
621
+ ctx.messages.resetToPromptOnly();
622
+ if (!ctx.isSystem) {
623
+ ctx.lastReply = `抱歉,AI 服务暂时不可用(${aiErr.message})。请稍后重试,或检查 API 配置。`;
624
+ }
625
+ return null;
626
+ }
627
+
628
+ await new Promise(r => setTimeout(r, 2000));
629
+ return { __continue: true };
630
+ }
631
+
632
+ /**
633
+ * 工具调用处理 — 执行 + 记录 + 去重 + 阶段转换
634
+ *
635
+ * @param {LoopContext} ctx
636
+ * @param {Object} llmResult
637
+ * @param {string} effectiveSystemPrompt — 用于 budget 耗尽时的摘要调用
638
+ * @returns {Promise<boolean>} true = 应退出循环
639
+ */
640
+ async #processToolCalls(ctx, llmResult, effectiveSystemPrompt) {
641
+ const { tracker, trace, messages } = ctx;
642
+
643
+ // 工具调用数量限制
644
+ let activeCalls = llmResult.functionCalls;
645
+ if (activeCalls.length > MAX_TOOL_CALLS_PER_ITER) {
646
+ this.logger.warn(
647
+ `[AgentRuntime] ⚠ ${activeCalls.length} tool calls, capping to ${MAX_TOOL_CALLS_PER_ITER}`
648
+ );
649
+ tracker?.recordTruncatedCalls?.(activeCalls.length - MAX_TOOL_CALLS_PER_ITER);
650
+ activeCalls = activeCalls.slice(0, MAX_TOOL_CALLS_PER_ITER);
651
+ }
652
+
653
+ // 追加 assistant 消息
654
+ messages.appendAssistantWithToolCalls(llmResult.text || null, activeCalls);
655
+
656
+ let roundSubmitCount = 0;
657
+ let roundHasNewInfo = false;
658
+ const roundToolNames = [];
659
+
660
+ // 执行每个工具
661
+ for (const fc of activeCalls) {
662
+ this.#emitProgress('tool_call', { tool: fc.name, args: fc.args });
663
+
664
+ this.bus.publish(AgentEvents.TOOL_CALL_START, {
665
+ agentId: this.id,
666
+ tool: fc.name,
667
+ }, { source: this.id });
668
+
669
+ // 通过 Pipeline 执行 (safety → cache → execute → observe → track → trace → dedup)
670
+ const { result: toolResult, metadata } = await this.#toolPipeline.execute(fc, {
671
+ runtime: this,
672
+ loopCtx: ctx,
673
+ iteration: ctx.iteration,
674
+ });
675
+
676
+ const durationMs = metadata.durationMs;
677
+ const toolEntry = { tool: fc.name, args: fc.args, result: toolResult, durationMs };
678
+ ctx.toolCalls.push(toolEntry);
679
+ this.toolCallHistory.push(toolEntry);
680
+
681
+ if (metadata.isNew) roundHasNewInfo = true;
682
+ roundToolNames.push(fc.name);
683
+
684
+ // onToolCall 通知
685
+ const effectiveHook = ctx.onToolCall || this.onToolCall;
686
+ if (effectiveHook) {
687
+ try { effectiveHook(fc.name, fc.args, toolResult, ctx.iteration); } catch { /* 观察者错误不中断 */ }
688
+ }
689
+
690
+ this.bus.publish(AgentEvents.TOOL_CALL_END, {
691
+ agentId: this.id,
692
+ tool: fc.name,
693
+ durationMs,
694
+ success: !toolResult?.error,
695
+ }, { source: this.id });
696
+
697
+ // 工具结果格式化 (统一通过 MessageAdapter)
698
+ let resultStr = messages.formatToolResult(fc.name, toolResult);
699
+
700
+ // 提交去重: pipeline 中间件已标记 metadata
701
+ if (metadata.dedupMessage) {
702
+ resultStr = metadata.dedupMessage;
703
+ } else if (metadata.isSubmit) {
704
+ roundSubmitCount++;
705
+ }
706
+
707
+ // 进度回调 (tool_end 需要 resultStr.length)
708
+ this.#emitProgress('tool_end', {
709
+ tool: fc.name,
710
+ duration: durationMs,
711
+ status: toolResult?.error ? 'error' : 'ok',
712
+ error: toolResult?.error || undefined,
713
+ resultSize: resultStr.length,
714
+ });
715
+
716
+ // 追加 tool result
717
+ messages.appendToolResult(fc.id, fc.name, resultStr);
718
+ }
719
+
720
+ // ExplorationTracker: endRound → 检查阶段转换
721
+ if (tracker) {
722
+ tracker.updatePlanProgress?.(trace);
723
+ const transitionNudge = tracker.endRound({
724
+ hasNewInfo: roundHasNewInfo,
725
+ submitCount: roundSubmitCount,
726
+ toolNames: roundToolNames,
727
+ });
728
+ if (transitionNudge) {
729
+ messages.appendUserNudge(transitionNudge.text);
730
+ this.logger.info(
731
+ `[AgentRuntime] 📝 injected ${transitionNudge.type} nudge (${tracker.phase})`
732
+ );
733
+ const _dimT = ctx.sharedState?._dimensionMeta?.id || '';
734
+ console.log(`\n\x1b[35m━━━ Transition Nudge [${transitionNudge.type}] phase=${tracker.phase}${_dimT ? ` dim=${_dimT}` : ''} ━━━\x1b[0m`);
735
+ console.log(`\x1b[33m${transitionNudge.text}\x1b[0m\n`);
736
+ }
737
+ }
738
+
739
+ // ActiveContext: 关闭轮次
740
+ if (trace) {
741
+ trace.setRoundSummary?.({
742
+ newInfoCount: roundHasNewInfo ? 1 : 0,
743
+ totalCalls: activeCalls.length,
744
+ submits: roundSubmitCount,
745
+ cumulativeFiles: tracker?.getMetrics?.()?.uniqueFiles || 0,
746
+ cumulativePatterns: tracker?.getMetrics?.()?.uniquePatterns || 0,
747
+ });
748
+ trace.endRound?.();
749
+ }
750
+
751
+ // Capability 后置钩子
752
+ const stepToolEntries = ctx.toolCalls.slice(-activeCalls.length);
753
+ const stepResult = {
754
+ type: 'tool_calls',
755
+ toolCalls: stepToolEntries,
756
+ iteration: ctx.iteration,
757
+ };
758
+ for (const cap of ctx.capabilities) {
759
+ cap.onAfterStep(stepResult);
760
+ }
761
+
762
+ this.#safeTransition('step_done', stepResult);
763
+
764
+ // 检查预算 (非 tracker 模式)
765
+ if (!tracker && ctx.iteration >= ctx.maxIterations) {
766
+ const summary = await this.aiProvider.chatWithTools(ctx.prompt, {
767
+ messages: messages.toMessages(),
768
+ systemPrompt: effectiveSystemPrompt,
769
+ toolChoice: 'none',
770
+ temperature: ctx.budget.temperature ?? 0.7,
771
+ maxTokens: ctx.budget.maxTokens ?? 4096,
772
+ });
773
+ if (summary.usage) {
774
+ this.tokenUsage.input += (summary.usage.inputTokens || 0);
775
+ this.tokenUsage.output += (summary.usage.outputTokens || 0);
776
+ ctx.addTokenUsage(summary.usage);
777
+ }
778
+ ctx.lastReply = cleanFinalAnswer(summary.text || '');
779
+ return true; // 退出
780
+ }
781
+
782
+ this.#safeTransition('continue');
783
+ return false; // 继续循环
784
+ }
785
+
786
+ /**
787
+ * 文本响应处理 — tracker 阶段路由 + 非 tracker 直接终止
788
+ *
789
+ * @param {LoopContext} ctx
790
+ * @param {Object} llmResult
791
+ * @returns {boolean} true = 应退出循环
792
+ */
793
+ #processTextResponse(ctx, llmResult) {
794
+ const { tracker, trace, messages } = ctx;
795
+
796
+ if (tracker) {
797
+ // (setThought + extractAndSetPlan 已在主循环中统一处理)
798
+
799
+ const textResult = tracker.onTextResponse();
800
+
801
+ if (textResult.isFinalAnswer) {
802
+ ctx.lastReply = cleanFinalAnswer(llmResult.text || '');
803
+ this.logger.info(
804
+ `[AgentRuntime] ✅ final answer — ${ctx.lastReply.length} chars, ${tracker.iteration} iters, ${ctx.toolCalls.length} tool calls`
805
+ );
806
+ trace?.endRound?.();
807
+ return true;
808
+ }
809
+
810
+ if (textResult.needsDigestNudge) {
811
+ messages.appendAssistantText(llmResult.text || '');
812
+ messages.appendUserNudge(textResult.nudge);
813
+ this.logger.info('[AgentRuntime] 📝 injected SUMMARIZE nudge (text-triggered transition)');
814
+ const _dimD = ctx.sharedState?._dimensionMeta?.id || '';
815
+ console.log(`\n\x1b[34m━━━ Digest Nudge [SUMMARIZE]${_dimD ? ` dim=${_dimD}` : ''} ━━━\x1b[0m`);
816
+ console.log(`\x1b[33m${textResult.nudge}\x1b[0m\n`);
817
+ trace?.endRound?.();
818
+ return false; // continue
819
+ }
820
+
821
+ if (textResult.shouldContinue) {
822
+ messages.appendAssistantText(llmResult.text || '');
823
+ if (textResult.nudge) {
824
+ messages.appendUserNudge(textResult.nudge);
825
+ const _dimC = ctx.sharedState?._dimensionMeta?.id || '';
826
+ console.log(`\n\x1b[32m━━━ Continue Nudge${_dimC ? ` dim=${_dimC}` : ''} ━━━\x1b[0m`);
827
+ console.log(`\x1b[33m${textResult.nudge}\x1b[0m\n`);
828
+ }
829
+ trace?.endRound?.();
830
+ return false; // continue
831
+ }
832
+ }
833
+
834
+ // 非 tracker 模式: 文字回答即最终回答
835
+ ctx.lastReply = cleanFinalAnswer(llmResult.text || '');
836
+ trace?.endRound?.();
837
+ return true;
838
+ }
839
+
840
+ /**
841
+ * 循环退出后处理 — 强制摘要 + 构建返回值
842
+ * @param {LoopContext} ctx
843
+ * @returns {Promise<Object>}
844
+ */
845
+ async #finalize(ctx) {
846
+ // 强制摘要 (系统场景或 tracker 场景)
847
+ if (!ctx.lastReply && (ctx.tracker || ctx.isSystem)) {
848
+ const forcedResult = await produceForcedSummary({
849
+ aiProvider: this.aiProvider,
850
+ source: ctx.source,
851
+ toolCalls: ctx.toolCalls,
852
+ tracker: ctx.tracker,
853
+ contextWindow: ctx.contextWindow,
854
+ prompt: ctx.prompt,
855
+ tokenUsage: this.tokenUsage,
856
+ });
857
+ ctx.lastReply = forcedResult.reply;
858
+ if (forcedResult.tokenUsage) {
859
+ this.tokenUsage.input += (forcedResult.tokenUsage.input || 0);
860
+ this.tokenUsage.output += (forcedResult.tokenUsage.output || 0);
861
+ ctx.addTokenUsage({
862
+ inputTokens: forcedResult.tokenUsage.input || 0,
863
+ outputTokens: forcedResult.tokenUsage.output || 0,
864
+ });
865
+ }
866
+ }
867
+
868
+ return ctx.buildResult();
869
+ }
870
+
871
+ // ─── 公共工具方法 ────────────────────────────
872
+
873
+ /**
874
+ * 中止执行
875
+ */
876
+ abort(reason = 'User aborted') {
877
+ this.#safeTransition('abort', { reason });
878
+ this.bus.publish(AgentEvents.AGENT_ABORTED, {
879
+ agentId: this.id,
880
+ reason,
881
+ }, { source: this.id });
882
+ }
883
+
884
+ /**
885
+ * 注入内存文件缓存(bootstrap 场景: allFiles 已在内存中,避免重复磁盘读取)
886
+ * @param {Array|null} files — [{ relativePath, content, name }]
887
+ */
888
+ setFileCache(files) {
889
+ this.#fileCache = files;
890
+ }
891
+
892
+ /** 项目根目录 (供 ToolExecutionPipeline 等访问) */
893
+ get projectRoot() {
894
+ return this.#projectRoot;
895
+ }
896
+
897
+ /** 文件缓存 (供 ToolExecutionPipeline 等访问) */
898
+ get fileCache() {
899
+ return this.#fileCache;
900
+ }
901
+
902
+ /**
903
+ * 发送进度事件 (公开方法,供 ToolExecutionPipeline 中间件调用)
904
+ */
905
+ emitProgress(type, data = {}) {
906
+ this.#emitProgress(type, data);
907
+ }
908
+
909
+ // ─── 私有方法 ────────────────────────────────
910
+
911
+ /**
912
+ * 安全状态转移 — 忽略不合法转移而不是抛异常。
913
+ *
914
+ * Pipeline/FanOut 场景下 reactLoop() 被多次调用,
915
+ * 第 2+ 次调用时状态已不在 IDLE,直接 send('start') 会抛错。
916
+ * 此方法在转移不合法时静默跳过,保证多阶段执行不中断。
917
+ */
918
+ #safeTransition(event, payload = {}) {
919
+ try {
920
+ this.state.send(event, payload);
921
+ } catch {
922
+ // 转移不合法 — 在多阶段场景中这是预期行为,静默跳过
923
+ }
924
+ }
925
+
926
+ /**
927
+ * 构建系统提示词 — 从 Persona + Capabilities 组装
928
+ */
929
+ #buildSystemPrompt(caps, context) {
930
+ const parts = [];
931
+
932
+ // Persona (角色定义)
933
+ if (this.persona?.description) {
934
+ parts.push(`# 角色\n${this.persona.description}`);
935
+ }
936
+
937
+ // fileCache 文件清单 — 让 LLM 知道有哪些文件可用于工具调用
938
+ if (this.#fileCache && this.#fileCache.length > 0) {
939
+ const fileList = this.#fileCache.map(f => {
940
+ const lines = f.content ? f.content.split('\n').length : 0;
941
+ const name = f.name || f.relativePath || 'unknown';
942
+ return `- ${name} (${lines} 行${f.language ? ', ' + f.language : ''})`;
943
+ }).join('\n');
944
+ parts.push(`## 预加载文件\n以下文件已加载到缓存中,工具可通过 filePath 参数引用:\n${fileList}`);
945
+ }
946
+
947
+ // Capability prompt fragments
948
+ for (const cap of caps) {
949
+ parts.push(cap.promptFragment);
950
+
951
+ // Capability 动态上下文 (记忆、项目概况等)
952
+ const dynamicCtx = cap.buildContext({
953
+ ...context,
954
+ lang: this.lang,
955
+ memoryMode: this.memoryConfig?.mode,
956
+ });
957
+ if (dynamicCtx) parts.push(dynamicCtx);
958
+ }
959
+
960
+ // 语言要求
961
+ if (this.lang === 'en') {
962
+ parts.push('\n## Language\nRespond in English.');
963
+ } else if (this.lang === 'zh') {
964
+ parts.push('\n## 语言\n用中文回复。代码/字段名保持英文。');
965
+ }
966
+
967
+ return parts.join('\n\n');
968
+ }
969
+
970
+ /**
971
+ * 收集所有 Capability 的工具白名单
972
+ * 如果任一 Capability tools 为空数组, 返回空 (使用全部工具)
973
+ */
974
+ #collectTools(caps) {
975
+ const toolSet = new Set();
976
+ let hasUnlimited = false;
977
+ for (const cap of caps) {
978
+ const tools = cap.tools;
979
+ if (!tools || tools.length === 0) {
980
+ hasUnlimited = true;
981
+ break;
982
+ }
983
+ for (const t of tools) toolSet.add(t);
984
+ }
985
+ // 合并调用方按需注入的额外工具 (不经 Capability,避免污染共享能力)
986
+ for (const t of this.#additionalTools) toolSet.add(t);
987
+ return hasUnlimited ? [] : [...toolSet];
988
+ }
989
+
990
+ /**
991
+ * 解析 capability 名称为实例 (Pipeline 阶段覆盖时调用)
992
+ */
993
+ #resolveCapabilities(capNames) {
994
+ if (capNames == null) return this.capabilities;
995
+ if (capNames.length === 0) return []; // explicit empty = no tools
996
+ return capNames.map(name => {
997
+ if (typeof name === 'object' && name instanceof Capability) return name;
998
+ // 先在已加载的 capabilities 中查找
999
+ const existing = this.capabilities.find(c => c.name === name);
1000
+ if (existing) return existing;
1001
+ // 否则从注册表创建
1002
+ return CapabilityRegistry.create(name);
1003
+ });
1004
+ }
1005
+
1006
+ /**
1007
+ * 发送进度事件
1008
+ */
1009
+ #emitProgress(type, data = {}) {
1010
+ const event = { type, agentId: this.id, preset: this.presetName, ...data, timestamp: Date.now() };
1011
+ if (this.onProgress) this.onProgress(event);
1012
+ this.bus.publish(AgentEvents.PROGRESS, event, { source: this.id });
1013
+ }
1014
+ }
1015
+
1016
+ export default AgentRuntime;