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
@@ -4,7 +4,7 @@
4
4
  * Phase 2: MemoryCoordinator + legacy module re-exports
5
5
  * Phase 3: ActiveContext (合并 WorkingMemory + ReasoningTrace)
6
6
  * Phase 4: SessionStore (合并 EpisodicMemory + ToolResultCache)
7
- * Phase 5: PersistentMemory (继承 ProjectSemanticMemory + 增强)
7
+ * Phase 5: PersistentMemory (统一的持久化语义记忆)
8
8
  */
9
9
 
10
10
  export { MemoryCoordinator } from './MemoryCoordinator.js';
@@ -0,0 +1,442 @@
1
+ /**
2
+ * Policies — Agent 执行约束 (横切关注点)
3
+ *
4
+ * Policy 不改变 Agent 做什么,而是约束 Agent 如何做。
5
+ * 多个 Policy 可叠加,形成复合约束。
6
+ *
7
+ * 三类 Policy:
8
+ * 1. BudgetPolicy — 资源预算 (迭代次数 / Token / 时间)
9
+ * 2. SafetyPolicy — 安全沙箱 (命令黑名单 / 文件范围 / 发送者鉴权)
10
+ * 3. QualityGatePolicy — 质量门控 (证据数量 / 分析深度)
11
+ *
12
+ * 这就是为什么"飞书远程执行"不需要独立 Agent:
13
+ * 它只是 Conversation + SystemInteraction + SafetyPolicy 的组合。
14
+ * SafetyPolicy 提供命令沙箱,而不是由 LarkBridgeAgent 硬编码。
15
+ *
16
+ * @module policies
17
+ */
18
+
19
+ import _path from 'node:path';
20
+
21
+ // ─── Base Policy ──────────────────────────────
22
+
23
+ /**
24
+ * Policy 基类 — 所有约束的抽象接口
25
+ */
26
+ export class Policy {
27
+ /** @type {string} 策略名称 */
28
+ get name() { throw new Error('Subclass must implement name'); }
29
+
30
+ /**
31
+ * 执行前校验 — 拒绝不满足条件的请求
32
+ * @param {Object} context — { message, capabilities, budget }
33
+ * @returns {{ ok: boolean, reason?: string }}
34
+ */
35
+ validateBefore(_context) { return { ok: true }; }
36
+
37
+ /**
38
+ * 执行中校验 — 每轮 ReAct 步骤后检查
39
+ * @param {Object} stepState — { iteration, toolCalls, tokenUsage, startTime, reply }
40
+ * @returns {{ ok: boolean, action?: 'continue'|'stop'|'warn', reason?: string }}
41
+ */
42
+ validateDuring(_stepState) { return { ok: true, action: 'continue' }; }
43
+
44
+ /**
45
+ * 执行后校验 — 对最终结果质量把关
46
+ * @param {Object} result — { reply, toolCalls, tokenUsage, iterations }
47
+ * @returns {{ ok: boolean, reason?: string }}
48
+ */
49
+ validateAfter(_result) { return { ok: true }; }
50
+
51
+ /**
52
+ * 修改配置 — 在执行前注入额外约束
53
+ * @param {Object} config
54
+ * @returns {Object} 修改后的 config
55
+ */
56
+ applyToConfig(config) { return config; }
57
+ }
58
+
59
+ // ─── BudgetPolicy — 资源预算 ─────────────────
60
+
61
+ /**
62
+ * 控制 Agent 的资源消耗上限。
63
+ *
64
+ * 适用于所有场景,不同 Preset 配置不同预算:
65
+ * - 聊天: { maxIterations: 8, timeoutMs: 120_000 }
66
+ * - 深度分析: { maxIterations: 24, timeoutMs: 300_000 }
67
+ * - 冷启动: { maxIterations: 24, timeoutMs: 600_000 }
68
+ * - 远程执行: { maxIterations: 6, timeoutMs: 60_000 }
69
+ */
70
+ export class BudgetPolicy extends Policy {
71
+ #maxIterations;
72
+ #maxTokens;
73
+ #timeoutMs;
74
+ #temperature;
75
+
76
+ /**
77
+ * @param {Object} [opts]
78
+ * @param {number} [opts.maxIterations=20]
79
+ * @param {number} [opts.maxTokens=4096]
80
+ * @param {number} [opts.timeoutMs=300000]
81
+ * @param {number} [opts.temperature=0.7]
82
+ */
83
+ constructor({
84
+ maxIterations = 20,
85
+ maxTokens = 4096,
86
+ timeoutMs = 300_000,
87
+ temperature = 0.7,
88
+ } = {}) {
89
+ super();
90
+ this.#maxIterations = maxIterations;
91
+ this.#maxTokens = maxTokens;
92
+ this.#timeoutMs = timeoutMs;
93
+ this.#temperature = temperature;
94
+ }
95
+
96
+ get name() { return 'budget'; }
97
+
98
+ get maxIterations() { return this.#maxIterations; }
99
+ get maxTokens() { return this.#maxTokens; }
100
+ get timeoutMs() { return this.#timeoutMs; }
101
+ get temperature() { return this.#temperature; }
102
+
103
+ validateDuring(stepState) {
104
+ if (stepState.iteration >= this.#maxIterations) {
105
+ return { ok: false, action: 'stop', reason: `Budget: max iterations (${this.#maxIterations}) reached` };
106
+ }
107
+ if (Date.now() - stepState.startTime > this.#timeoutMs) {
108
+ return { ok: false, action: 'stop', reason: `Budget: timeout (${this.#timeoutMs}ms) exceeded` };
109
+ }
110
+ return { ok: true, action: 'continue' };
111
+ }
112
+
113
+ applyToConfig(config) {
114
+ return {
115
+ ...config,
116
+ budget: {
117
+ maxIterations: this.#maxIterations,
118
+ maxTokens: this.#maxTokens,
119
+ timeoutMs: this.#timeoutMs,
120
+ temperature: this.#temperature,
121
+ },
122
+ };
123
+ }
124
+ }
125
+
126
+ // ─── SafetyPolicy — 安全沙箱 ────────────────
127
+
128
+ /**
129
+ * 安全约束: 命令过滤、文件范围限制、发送者鉴权。
130
+ *
131
+ * 这取代了旧 LarkBridgeAgent 中硬编码的安全逻辑:
132
+ * - SafetyPolicy 是可组合的、可配置的、可复用的
133
+ * - 任何需要安全约束的场景都可以叠加这个 Policy
134
+ * - 不局限于飞书场景 — CLI 远程执行同样适用
135
+ */
136
+ export class SafetyPolicy extends Policy {
137
+
138
+ /** 危险命令正则黑名单 */
139
+ static DANGEROUS_COMMANDS = Object.freeze([
140
+ /\brm\s+-rf\s+[\/~]/,
141
+ /\bsudo\b/,
142
+ /\bmkfs\b/,
143
+ /\bdd\s+if=/,
144
+ /\b(shutdown|reboot|halt)\b/,
145
+ />\s*\/dev\//,
146
+ /\bcurl\b.*\|\s*(bash|sh)/,
147
+ /\bchmod\s+777/,
148
+ /\bpasswd\b/,
149
+ /\bkillall\b/,
150
+ ]);
151
+
152
+ /** 安全命令前缀白名单 */
153
+ static SAFE_COMMANDS = Object.freeze([
154
+ 'ls', 'cat', 'head', 'tail', 'grep', 'find', 'wc',
155
+ 'echo', 'pwd', 'date', 'which', 'file', 'stat',
156
+ 'git log', 'git status', 'git diff', 'git branch',
157
+ 'npm list', 'npm outdated', 'node -v', 'npm -v',
158
+ ]);
159
+
160
+ #fileScope;
161
+ #allowedSenders;
162
+ #commandBlacklist;
163
+ #requireApprovalFor;
164
+
165
+ /**
166
+ * @param {Object} [opts]
167
+ * @param {string} [opts.fileScope] — 文件操作范围 (目录路径)
168
+ * @param {string[]} [opts.allowedSenders] — 允许的发送者 ID (空=不限制)
169
+ * @param {RegExp[]} [opts.commandBlacklist] — 额外命令黑名单
170
+ * @param {string[]} [opts.requireApprovalFor] — 需要人工确认的工具名
171
+ */
172
+ constructor({
173
+ fileScope,
174
+ allowedSenders = [],
175
+ commandBlacklist = [],
176
+ requireApprovalFor = [],
177
+ } = {}) {
178
+ super();
179
+ this.#fileScope = fileScope || null;
180
+ this.#allowedSenders = allowedSenders;
181
+ this.#commandBlacklist = [...SafetyPolicy.DANGEROUS_COMMANDS, ...commandBlacklist];
182
+ this.#requireApprovalFor = requireApprovalFor;
183
+ }
184
+
185
+ get name() { return 'safety'; }
186
+
187
+ validateBefore(context) {
188
+ // 发送者鉴权
189
+ if (this.#allowedSenders.length > 0) {
190
+ const senderId = context.message?.sender?.id;
191
+ if (!senderId || !this.#allowedSenders.includes(senderId)) {
192
+ return { ok: false, reason: `Safety: sender "${senderId}" not in allowlist` };
193
+ }
194
+ }
195
+ return { ok: true };
196
+ }
197
+
198
+ /**
199
+ * 检查命令是否安全
200
+ * @param {string} command
201
+ * @returns {{ safe: boolean, reason?: string }}
202
+ */
203
+ checkCommand(command) {
204
+ for (const pattern of this.#commandBlacklist) {
205
+ if (pattern.test(command)) {
206
+ return { safe: false, reason: `Blocked: matches dangerous pattern ${pattern}` };
207
+ }
208
+ }
209
+ return { safe: true };
210
+ }
211
+
212
+ /**
213
+ * 检查文件路径是否在允许范围内
214
+ * @param {string} filePath
215
+ * @returns {{ safe: boolean, reason?: string }}
216
+ */
217
+ checkFilePath(filePath) {
218
+ if (!this.#fileScope) return { safe: true };
219
+ const resolved = _path.resolve(filePath);
220
+ const scope = _path.resolve(this.#fileScope);
221
+ if (!resolved.startsWith(scope)) {
222
+ return { safe: false, reason: `File path "${filePath}" outside allowed scope "${this.#fileScope}"` };
223
+ }
224
+ return { safe: true };
225
+ }
226
+
227
+ /**
228
+ * 是否需要人工确认
229
+ * @param {string} toolName
230
+ */
231
+ needsApproval(toolName) {
232
+ return this.#requireApprovalFor.includes(toolName);
233
+ }
234
+
235
+ applyToConfig(config) {
236
+ return {
237
+ ...config,
238
+ safetyPolicy: this,
239
+ };
240
+ }
241
+ }
242
+
243
+ // ─── QualityGatePolicy — 质量门控 ────────────
244
+
245
+ /**
246
+ * 评估 Agent 输出质量,决定是否接受结果。
247
+ *
248
+ * 用于 Pipeline 的 gate 阶段,也可用于最终结果校验。
249
+ * 取代了旧 BootstrapOrchestrator 中硬编码的 qualityCheck。
250
+ */
251
+ export class QualityGatePolicy extends Policy {
252
+ #minEvidenceLength;
253
+ #minFileRefs;
254
+ #minToolCalls;
255
+ #customValidator;
256
+
257
+ /**
258
+ * @param {Object} [opts]
259
+ * @param {number} [opts.minEvidenceLength=500] — 分析文本最小长度
260
+ * @param {number} [opts.minFileRefs=3] — 最少文件引用数
261
+ * @param {number} [opts.minToolCalls=2] — 最少工具调用数
262
+ * @param {Function} [opts.customValidator] — 自定义校验 (result) => { ok, reason }
263
+ */
264
+ constructor({
265
+ minEvidenceLength = 500,
266
+ minFileRefs = 3,
267
+ minToolCalls = 2,
268
+ customValidator,
269
+ } = {}) {
270
+ super();
271
+ this.#minEvidenceLength = minEvidenceLength;
272
+ this.#minFileRefs = minFileRefs;
273
+ this.#minToolCalls = minToolCalls;
274
+ this.#customValidator = customValidator || null;
275
+ }
276
+
277
+ get name() { return 'quality_gate'; }
278
+
279
+ validateAfter(result) {
280
+ const reasons = [];
281
+
282
+ if (result.reply && result.reply.length < this.#minEvidenceLength) {
283
+ reasons.push(`分析长度不足: ${result.reply.length} < ${this.#minEvidenceLength}`);
284
+ }
285
+
286
+ if (result.reply) {
287
+ const fileRefCount = (result.reply.match(/[\w\/\-]+\.\w{1,6}/g) || []).length;
288
+ if (fileRefCount < this.#minFileRefs) {
289
+ reasons.push(`文件引用不足: ${fileRefCount} < ${this.#minFileRefs}`);
290
+ }
291
+ }
292
+
293
+ if ((result.toolCalls?.length || 0) < this.#minToolCalls) {
294
+ reasons.push(`工具调用不足: ${result.toolCalls?.length || 0} < ${this.#minToolCalls}`);
295
+ }
296
+
297
+ if (this.#customValidator) {
298
+ const custom = this.#customValidator(result);
299
+ if (!custom.ok) reasons.push(custom.reason);
300
+ }
301
+
302
+ return reasons.length === 0
303
+ ? { ok: true }
304
+ : { ok: false, reason: reasons.join('; ') };
305
+ }
306
+
307
+ /** 导出为 PipelineStrategy gate 配置格式 */
308
+ toGateConfig() {
309
+ return {
310
+ minEvidenceLength: this.#minEvidenceLength,
311
+ minFileRefs: this.#minFileRefs,
312
+ minToolCalls: this.#minToolCalls,
313
+ custom: this.#customValidator,
314
+ };
315
+ }
316
+ }
317
+
318
+ // ─── PolicyEngine — 策略引擎 ─────────────────
319
+
320
+ /**
321
+ * 组合多个 Policy 并统一执行校验。
322
+ *
323
+ * @example
324
+ * const engine = new PolicyEngine([
325
+ * new BudgetPolicy({ maxIterations: 8 }),
326
+ * new SafetyPolicy({ fileScope: '/project' }),
327
+ * ]);
328
+ * engine.validateBefore(context); // 所有 policy 依次检查
329
+ */
330
+ export class PolicyEngine {
331
+ /** @type {Policy[]} */
332
+ #policies;
333
+
334
+ constructor(policies = []) {
335
+ this.#policies = policies;
336
+ }
337
+
338
+ get policies() { return [...this.#policies]; }
339
+
340
+ /**
341
+ * 获取特定类型的 Policy
342
+ * @template T
343
+ * @param {new (...args: any[]) => T} PolicyClass
344
+ * @returns {T|null}
345
+ */
346
+ get(PolicyClass) {
347
+ return this.#policies.find(p => p instanceof PolicyClass) || null;
348
+ }
349
+
350
+ validateBefore(context) {
351
+ for (const policy of this.#policies) {
352
+ const result = policy.validateBefore(context);
353
+ if (!result.ok) return result;
354
+ }
355
+ return { ok: true };
356
+ }
357
+
358
+ validateDuring(stepState) {
359
+ for (const policy of this.#policies) {
360
+ const result = policy.validateDuring(stepState);
361
+ if (!result.ok) return result;
362
+ }
363
+ return { ok: true, action: 'continue' };
364
+ }
365
+
366
+ validateAfter(result) {
367
+ for (const policy of this.#policies) {
368
+ const val = policy.validateAfter(result);
369
+ if (!val.ok) return val;
370
+ }
371
+ return { ok: true };
372
+ }
373
+
374
+ applyToConfig(config) {
375
+ let result = config;
376
+ for (const policy of this.#policies) {
377
+ result = policy.applyToConfig(result);
378
+ }
379
+ return result;
380
+ }
381
+
382
+ /**
383
+ * 获取合并后的 Budget (从 BudgetPolicy)
384
+ */
385
+ getBudget() {
386
+ const bp = this.get(BudgetPolicy);
387
+ return bp ? {
388
+ maxIterations: bp.maxIterations,
389
+ maxTokens: bp.maxTokens,
390
+ timeoutMs: bp.timeoutMs,
391
+ temperature: bp.temperature,
392
+ } : null;
393
+ }
394
+
395
+ /**
396
+ * 工具执行前的安全校验 — 在 reactLoop 中每次工具调用前自动触发
397
+ *
398
+ * 对有副作用的工具 (run_safe_command, write_project_file) 执行安全检查。
399
+ * 委托给 SafetyPolicy,如果没有加载 SafetyPolicy 则放行。
400
+ *
401
+ * @param {string} toolName — 工具名称
402
+ * @param {Object} args — 工具参数
403
+ * @returns {{ ok: boolean, reason?: string }}
404
+ */
405
+ validateToolCall(toolName, args) {
406
+ const safety = this.get(SafetyPolicy);
407
+ if (!safety) return { ok: true };
408
+
409
+ // 终端命令安全检查
410
+ if (toolName === 'run_safe_command' && args?.command) {
411
+ const check = safety.checkCommand(args.command);
412
+ if (!check.safe) {
413
+ return { ok: false, reason: `[SafetyPolicy] 命令拦截: ${check.reason}` };
414
+ }
415
+ }
416
+
417
+ // 文件写入路径检查
418
+ if (toolName === 'write_project_file' && args?.filePath) {
419
+ const check = safety.checkFilePath(args.filePath);
420
+ if (!check.safe) {
421
+ return { ok: false, reason: `[SafetyPolicy] 路径拦截: ${check.reason}` };
422
+ }
423
+ }
424
+
425
+ // 文件读取路径检查
426
+ if (toolName === 'read_project_file' && args?.filePath) {
427
+ const check = safety.checkFilePath(args.filePath);
428
+ if (!check.safe) {
429
+ return { ok: false, reason: `[SafetyPolicy] 路径拦截: ${check.reason}` };
430
+ }
431
+ }
432
+
433
+ // 需要人工确认的工具
434
+ if (safety.needsApproval(toolName)) {
435
+ return { ok: false, reason: `[SafetyPolicy] 工具 "${toolName}" 需要人工确认` };
436
+ }
437
+
438
+ return { ok: true };
439
+ }
440
+ }
441
+
442
+ export default { Policy, BudgetPolicy, SafetyPolicy, QualityGatePolicy, PolicyEngine };