aegiscode 3.1.8 → 3.1.10

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 (206) hide show
  1. package/README.md +23 -15
  2. package/dist/main.js +549 -552
  3. package/package.json +4 -2
  4. package/src/agent/Agent.ts +903 -0
  5. package/src/agent/SimpleAgent.ts +48 -0
  6. package/src/agent/index.ts +54 -0
  7. package/src/agent/orchestrator/AppBuilder.ts +443 -0
  8. package/src/agent/orchestrator/CouncilAgent.ts +310 -0
  9. package/src/agent/orchestrator/DiscussionRoom.ts +462 -0
  10. package/src/agent/orchestrator/OrchestratorAgent.ts +637 -0
  11. package/src/agent/orchestrator/index.ts +38 -0
  12. package/src/agent/orchestrator/utils.ts +397 -0
  13. package/src/agent/pricing.ts +115 -0
  14. package/src/agent/router.ts +74 -0
  15. package/src/agent/routerStats.ts +121 -0
  16. package/src/agent/types.ts +318 -0
  17. package/src/auth/login.ts +383 -0
  18. package/src/cli/config.ts +189 -0
  19. package/src/cli/index.ts +17 -0
  20. package/src/cli/middleware.ts +119 -0
  21. package/src/cli/types.ts +75 -0
  22. package/src/config/ConfigManager.ts +587 -0
  23. package/src/config/index.ts +7 -0
  24. package/src/config/types.ts +584 -0
  25. package/src/context/CompactionService.ts +300 -0
  26. package/src/context/ContextManager.ts +450 -0
  27. package/src/context/FileAnalyzer.ts +267 -0
  28. package/src/context/TokenCounter.ts +265 -0
  29. package/src/context/index.ts +27 -0
  30. package/src/context/storage/CacheStore.ts +176 -0
  31. package/src/context/storage/JSONLStore.ts +201 -0
  32. package/src/context/storage/MemoryStore.ts +205 -0
  33. package/src/context/storage/PersistentStore.ts +327 -0
  34. package/src/context/storage/index.ts +9 -0
  35. package/src/context/storage/pathUtils.ts +114 -0
  36. package/src/context/test.ts +309 -0
  37. package/src/context/types.ts +268 -0
  38. package/src/hooks/HookExecutor.ts +434 -0
  39. package/src/hooks/HookManager.ts +596 -0
  40. package/src/hooks/HookService.ts +269 -0
  41. package/src/hooks/Matcher.ts +157 -0
  42. package/src/hooks/index.ts +63 -0
  43. package/src/hooks/types.ts +424 -0
  44. package/src/main.tsx +596 -0
  45. package/src/mcp/HealthMonitor.ts +150 -0
  46. package/src/mcp/McpClient.ts +491 -0
  47. package/src/mcp/McpRegistry.ts +321 -0
  48. package/src/mcp/createMcpTool.ts +251 -0
  49. package/src/mcp/index.ts +15 -0
  50. package/src/mcp/server.ts +334 -0
  51. package/src/mcp/test-server.ts +88 -0
  52. package/src/mcp/test.ts +372 -0
  53. package/src/mcp/types.ts +247 -0
  54. package/src/memory/AgentMemoryBus.ts +432 -0
  55. package/src/memory/CloudSync.ts +99 -0
  56. package/src/memory/DriveSync.ts +106 -0
  57. package/src/memory/SharedMemory.ts +951 -0
  58. package/src/memory/index.ts +14 -0
  59. package/src/memory/machineFingerprint.ts +40 -0
  60. package/src/orchestrator/SubAgentMetadata.ts +136 -0
  61. package/src/prompts/builder.ts +213 -0
  62. package/src/prompts/default.ts +144 -0
  63. package/src/prompts/index.ts +16 -0
  64. package/src/prompts/plan.ts +64 -0
  65. package/src/prompts/test.ts +78 -0
  66. package/src/services/AnthropicChatService.ts +341 -0
  67. package/src/services/ChatService.ts +347 -0
  68. package/src/services/ClaudeCliChatService.ts +256 -0
  69. package/src/services/CloudSync.ts +168 -0
  70. package/src/services/CostLedger.ts +211 -0
  71. package/src/services/Heartbeat.ts +135 -0
  72. package/src/services/LearningCollector.ts +291 -0
  73. package/src/services/OllamaInstaller.ts +342 -0
  74. package/src/services/VersionChecker.ts +445 -0
  75. package/src/services/index.ts +57 -0
  76. package/src/services/streaming/OpenAIEventAdapter.ts +126 -0
  77. package/src/services/streaming/RenderingProfile.ts +90 -0
  78. package/src/services/streaming/StreamEventParser.ts +181 -0
  79. package/src/services/streaming/ThrottledRenderer.ts +139 -0
  80. package/src/services/streaming/TranscriptBuffer.ts +574 -0
  81. package/src/services/streaming/eventStatusMap.ts +52 -0
  82. package/src/services/streaming/index.ts +46 -0
  83. package/src/services/streaming/renderFormatting.ts +79 -0
  84. package/src/services/streaming/types.ts +234 -0
  85. package/src/skills/SkillLoader.ts +126 -0
  86. package/src/skills/SkillRegistry.ts +366 -0
  87. package/src/skills/index.ts +48 -0
  88. package/src/skills/types.ts +146 -0
  89. package/src/slash-commands/billing.ts +70 -0
  90. package/src/slash-commands/build.ts +413 -0
  91. package/src/slash-commands/builtinCommands.ts +2733 -0
  92. package/src/slash-commands/clone.ts +242 -0
  93. package/src/slash-commands/council.ts +125 -0
  94. package/src/slash-commands/custom/CustomCommandExecutor.ts +143 -0
  95. package/src/slash-commands/custom/CustomCommandLoader.ts +251 -0
  96. package/src/slash-commands/custom/CustomCommandRegistry.ts +144 -0
  97. package/src/slash-commands/custom/index.ts +7 -0
  98. package/src/slash-commands/debate.ts +254 -0
  99. package/src/slash-commands/gmail.ts +105 -0
  100. package/src/slash-commands/index.ts +388 -0
  101. package/src/slash-commands/mcpCommand.ts +205 -0
  102. package/src/slash-commands/types.ts +201 -0
  103. package/src/store/index.ts +76 -0
  104. package/src/store/selectors.ts +246 -0
  105. package/src/store/slices/appSlice.ts +205 -0
  106. package/src/store/slices/commandSlice.ts +115 -0
  107. package/src/store/slices/configSlice.ts +46 -0
  108. package/src/store/slices/focusSlice.ts +64 -0
  109. package/src/store/slices/index.ts +9 -0
  110. package/src/store/slices/sessionSlice.ts +424 -0
  111. package/src/store/streaming-buffer.ts +425 -0
  112. package/src/store/test.ts +296 -0
  113. package/src/store/types.ts +274 -0
  114. package/src/store/vanilla.ts +186 -0
  115. package/src/tools/builtin/bash.ts +236 -0
  116. package/src/tools/builtin/council.ts +105 -0
  117. package/src/tools/builtin/edit.ts +213 -0
  118. package/src/tools/builtin/glob.ts +136 -0
  119. package/src/tools/builtin/grep.ts +263 -0
  120. package/src/tools/builtin/index.ts +61 -0
  121. package/src/tools/builtin/memory.ts +66 -0
  122. package/src/tools/builtin/read.ts +168 -0
  123. package/src/tools/builtin/skill.ts +97 -0
  124. package/src/tools/builtin/snapshot.ts +40 -0
  125. package/src/tools/builtin/task.ts +106 -0
  126. package/src/tools/builtin/write.ts +134 -0
  127. package/src/tools/createTool.ts +221 -0
  128. package/src/tools/execution/ExecutionPipeline.ts +263 -0
  129. package/src/tools/execution/index.ts +40 -0
  130. package/src/tools/execution/stages/CacheStage.ts +131 -0
  131. package/src/tools/execution/stages/ConfirmationStage.ts +203 -0
  132. package/src/tools/execution/stages/DiscoveryStage.ts +46 -0
  133. package/src/tools/execution/stages/ExecutionStage.ts +48 -0
  134. package/src/tools/execution/stages/FormattingStage.ts +44 -0
  135. package/src/tools/execution/stages/HookStage.ts +72 -0
  136. package/src/tools/execution/stages/PermissionStage.ts +287 -0
  137. package/src/tools/execution/stages/PostHookStage.ts +71 -0
  138. package/src/tools/execution/stages/index.ts +12 -0
  139. package/src/tools/execution/test.ts +266 -0
  140. package/src/tools/execution/types.ts +273 -0
  141. package/src/tools/index.ts +81 -0
  142. package/src/tools/registry.ts +304 -0
  143. package/src/tools/schemas.ts +109 -0
  144. package/src/tools/test.ts +220 -0
  145. package/src/tools/types.ts +175 -0
  146. package/src/tools/validation/PermissionChecker.ts +242 -0
  147. package/src/tools/validation/SensitiveFileDetector.ts +210 -0
  148. package/src/tools/validation/index.ts +11 -0
  149. package/src/ui/App.tsx +166 -0
  150. package/src/ui/components/AegisInterface.tsx +484 -0
  151. package/src/ui/components/common/ChatSearch.tsx +150 -0
  152. package/src/ui/components/common/ErrorBoundary.tsx +82 -0
  153. package/src/ui/components/common/ExitMessage.tsx +120 -0
  154. package/src/ui/components/common/LoadingIndicator.tsx +49 -0
  155. package/src/ui/components/common/index.ts +6 -0
  156. package/src/ui/components/dialog/ConfirmationPrompt.tsx +208 -0
  157. package/src/ui/components/dialog/InteractiveSelector.tsx +149 -0
  158. package/src/ui/components/dialog/SetupWizard.tsx +297 -0
  159. package/src/ui/components/dialog/UpdatePrompt.tsx +155 -0
  160. package/src/ui/components/dialog/index.ts +8 -0
  161. package/src/ui/components/index.ts +28 -0
  162. package/src/ui/components/input/CommandSuggestions.tsx +139 -0
  163. package/src/ui/components/input/CustomTextInput.tsx +220 -0
  164. package/src/ui/components/input/InputArea.tsx +361 -0
  165. package/src/ui/components/input/PromptSuggestions.tsx +66 -0
  166. package/src/ui/components/input/index.ts +6 -0
  167. package/src/ui/components/layout/ChatStatusBar.tsx +90 -0
  168. package/src/ui/components/layout/ContextBar.tsx +79 -0
  169. package/src/ui/components/layout/MessageArea.tsx +96 -0
  170. package/src/ui/components/layout/MessageList.tsx +647 -0
  171. package/src/ui/components/layout/MessageSeparator.tsx +26 -0
  172. package/src/ui/components/layout/WelcomeMessage.tsx +93 -0
  173. package/src/ui/components/layout/index.ts +7 -0
  174. package/src/ui/components/markdown/CodeHighlighter.tsx +292 -0
  175. package/src/ui/components/markdown/MessageRenderer.tsx +1211 -0
  176. package/src/ui/components/markdown/index.ts +8 -0
  177. package/src/ui/components/markdown/parser.ts +336 -0
  178. package/src/ui/components/markdown/types.ts +66 -0
  179. package/src/ui/focus/FocusManager.ts +137 -0
  180. package/src/ui/focus/index.ts +13 -0
  181. package/src/ui/focus/types.ts +54 -0
  182. package/src/ui/focus/useFocus.ts +75 -0
  183. package/src/ui/hooks/index.ts +11 -0
  184. package/src/ui/hooks/useAgent.ts +284 -0
  185. package/src/ui/hooks/useCommandHistory.ts +87 -0
  186. package/src/ui/hooks/useCommandProcessor.ts +443 -0
  187. package/src/ui/hooks/useConfirmation.ts +99 -0
  188. package/src/ui/hooks/useCtrlCHandler.ts +100 -0
  189. package/src/ui/hooks/useInputBuffer.ts +122 -0
  190. package/src/ui/hooks/useTerminalSize.ts +68 -0
  191. package/src/ui/hooks/useTerminalWidth.ts +5 -0
  192. package/src/ui/hooks/useWindowedList.ts +118 -0
  193. package/src/ui/render-debugger.ts +621 -0
  194. package/src/ui/test.ts +189 -0
  195. package/src/ui/themes/ThemeManager.ts +332 -0
  196. package/src/ui/themes/aegisTheme.ts +87 -0
  197. package/src/ui/themes/darkTheme.ts +87 -0
  198. package/src/ui/themes/defaultTheme.ts +85 -0
  199. package/src/ui/themes/index.ts +10 -0
  200. package/src/ui/themes/lightTheme.ts +89 -0
  201. package/src/ui/themes/popularThemes.ts +187 -0
  202. package/src/ui/themes/types.ts +130 -0
  203. package/src/utils/clipboard.ts +48 -0
  204. package/src/utils/debug.ts +43 -0
  205. package/src/utils/environment.ts +68 -0
  206. package/src/utils/index.ts +10 -0
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Permission Stage - 权限检查阶段
3
+ *
4
+ */
5
+
6
+ import { ToolKind } from '../../types.js';
7
+ import {
8
+ PermissionMode,
9
+ PermissionResult,
10
+ type PipelineStage,
11
+ type ToolExecution,
12
+ type PermissionConfig,
13
+ type PermissionCheckResult,
14
+ } from '../types.js';
15
+ import { PermissionChecker } from '../../validation/PermissionChecker.js';
16
+ import { SensitiveFileDetector, SensitivityLevel } from '../../validation/SensitiveFileDetector.js';
17
+ import { onPermissionRequest } from '../../../hooks/index.js';
18
+
19
+ export class PermissionStage implements PipelineStage {
20
+ readonly name = 'permission';
21
+ private permissionChecker: PermissionChecker;
22
+ private defaultMode: PermissionMode;
23
+
24
+ constructor(
25
+ config?: Partial<PermissionConfig>,
26
+ private sessionApprovals?: Set<string>,
27
+ private sessionDenials?: Set<string>,
28
+ defaultMode: PermissionMode = PermissionMode.DEFAULT
29
+ ) {
30
+ this.permissionChecker = new PermissionChecker(config);
31
+ this.defaultMode = defaultMode;
32
+ }
33
+
34
+ async process(execution: ToolExecution): Promise<void> {
35
+ const tool = execution._internal.tool;
36
+
37
+ if (!tool) {
38
+ execution.abort('Tool not found in execution context');
39
+ return;
40
+ }
41
+
42
+ // 1. 创建工具调用实例(含 Zod 验
43
+ try {
44
+ const invocation = tool.build(execution.params);
45
+ execution._internal.invocation = invocation;
46
+ } catch (error) {
47
+ execution.abort(
48
+ `Parameter validation failed: ${error instanceof Error ? error.message : String(error)}`
49
+ );
50
+ return;
51
+ }
52
+
53
+ // 2. 构建权限签
54
+ const signature = PermissionChecker.buildSignature({
55
+ toolName: tool.name,
56
+ params: execution.params,
57
+ tool,
58
+ });
59
+ execution._internal.permissionSignature = signature;
60
+
61
+ // 3. 检查会话拒绝列
62
+ if (this.sessionDenials?.has(signature)) {
63
+ // Phrased to avoid "permission/denied" vocabulary: this exact string becomes
64
+ // llmContent in a real tool_result, and the model can later free-associate a
65
+ // hallucinated continuation of it in an unrelated turn with no tool call.
66
+ execution.abort('Not executed: you tried this exact action earlier in this session and the user declined it. Choose a different approach.');
67
+ return;
68
+ }
69
+
70
+ // 4. 检查会话批准列
71
+ if (this.sessionApprovals?.has(signature)) {
72
+ // 已在会话中批准,跳过权限检
73
+ return;
74
+ }
75
+
76
+ // 5. 执行权限检
77
+ let checkResult = this.permissionChecker.check({
78
+ toolName: tool.name,
79
+ params: execution.params,
80
+ tool,
81
+ });
82
+
83
+ // 6. 应用权限模式覆
84
+ const currentMode = execution.context.permissionMode || this.defaultMode;
85
+ checkResult = this.applyModeOverrides(tool.kind, checkResult, currentMode);
86
+
87
+ // 7. 根据结果采取行
88
+ switch (checkResult.result) {
89
+ case PermissionResult.DENY:
90
+ execution.abort(checkResult.reason || 'Not executed: this action is not allowed under current settings. Choose a different approach.');
91
+ return;
92
+
93
+ case PermissionResult.ASK:
94
+ // 执
95
+ const hookDecision = await this.executePermissionHook(execution, tool.name);
96
+ if (hookDecision === 'approve') {
97
+ // Hook 批准,跳过确
98
+ break;
99
+ } else if (hookDecision === 'deny') {
100
+ // Hook 拒
101
+ execution.abort('Not executed: a configured hook excludes this action. Choose a different approach.');
102
+ return;
103
+ }
104
+ // Hook 返回 ask 或无 hook,继续标记需要确
105
+ execution._internal.needsConfirmation = true;
106
+ execution._internal.confirmationReason =
107
+ checkResult.reason || 'This operation requires confirmation';
108
+ break;
109
+
110
+ case PermissionResult.ALLOW:
111
+ // 继续执
112
+ break;
113
+ }
114
+
115
+ // 7. 额外安全检查:敏感文
116
+ this.checkSensitiveFiles(execution);
117
+
118
+ // 8. 额外安全检查:终端操纵(tmux/screen 注入按键到另一个会话
119
+ this.checkTerminalPiloting(execution);
120
+ }
121
+
122
+ /**
123
+ * Detects Bash commands that pilot another terminal session by injecting
124
+ * keystrokes (tmux send-keys, screen -X stuff, etc.). This is the same
125
+ * vector a model used to mutate ~/.aegiscode/config.json via a second
126
+ * live aegis instance while looping unattended — the injected keystrokes
127
+ * are indistinguishable from real user input to the receiving process, so
128
+ * the only place to catch this is here, before the command runs. Forces
129
+ * confirmation regardless of requireConfirmation or session approvals.
130
+ */
131
+ private checkTerminalPiloting(execution: ToolExecution): void {
132
+ const tool = execution._internal.tool;
133
+ if (!tool || tool.name !== 'Bash') return;
134
+
135
+ const command = execution.params.command;
136
+ if (typeof command !== 'string') return;
137
+
138
+ const PILOTING_PATTERNS = [
139
+ /\btmux\s+send-keys\b/,
140
+ /\btmux\s+(?:run-shell|wait-for)\b/,
141
+ /\bscreen\s+-S\s+\S+\s+-X\s+stuff\b/,
142
+ /\bexpect\s+-c\b/,
143
+ ];
144
+
145
+ if (PILOTING_PATTERNS.some((re) => re.test(command))) {
146
+ execution._internal.needsConfirmation = true;
147
+ execution._internal.forceConfirmation = true;
148
+ execution._internal.confirmationReason =
149
+ 'This command injects input into another terminal session — it can mutate shared ' +
150
+ 'config/files exactly like a real user typing, bypassing normal confirmation settings.';
151
+ }
152
+ }
153
+
154
+ /**
155
+ *
156
+ */
157
+ private applyModeOverrides(
158
+ toolKind: ToolKind,
159
+ checkResult: PermissionCheckResult,
160
+ permissionMode: PermissionMode
161
+ ): PermissionCheckResult {
162
+ // 1. YOLO 模式:批准所有(最高优先
163
+ if (permissionMode === PermissionMode.YOLO) {
164
+ return {
165
+ result: PermissionResult.ALLOW,
166
+ matchedRule: 'mode:yolo',
167
+ reason: 'YOLO mode: auto-approve all operations',
168
+ };
169
+ }
170
+
171
+ // 2. PLAN 模式:拒绝非只读工
172
+ if (permissionMode === PermissionMode.PLAN) {
173
+ if (toolKind !== ToolKind.ReadOnly) {
174
+ return {
175
+ result: PermissionResult.DENY,
176
+ matchedRule: 'mode:plan',
177
+ reason: 'Plan mode: only read-only tools allowed',
178
+ };
179
+ }
180
+ }
181
+
182
+ // 3. 已被 deny 规则拒绝,不覆
183
+ if (checkResult.result === PermissionResult.DENY) {
184
+ return checkResult;
185
+ }
186
+
187
+ // 4. 已被 allow 规则批准,不覆
188
+ if (checkResult.result === PermissionResult.ALLOW) {
189
+ return checkResult;
190
+ }
191
+
192
+ // 5. 只读工具:所有模式下都批
193
+ if (toolKind === ToolKind.ReadOnly) {
194
+ return {
195
+ result: PermissionResult.ALLOW,
196
+ matchedRule: `mode:${permissionMode}:readonly`,
197
+ reason: 'Read-only tools are auto-approved',
198
+ };
199
+ }
200
+
201
+ // 6. AUTO_EDIT 模式:批准 Write 工
202
+ if (permissionMode === PermissionMode.AUTO_EDIT && toolKind === ToolKind.Write) {
203
+ return {
204
+ result: PermissionResult.ALLOW,
205
+ matchedRule: 'mode:autoEdit:write',
206
+ reason: 'AUTO_EDIT mode: auto-approve write tools',
207
+ };
208
+ }
209
+
210
+ // 7. 其他情况:保持原检查结果(通常
211
+ return checkResult;
212
+ }
213
+
214
+ /**
215
+ *
216
+ */
217
+ private checkSensitiveFiles(execution: ToolExecution): void {
218
+ const tool = execution._internal.tool;
219
+ if (!tool) return;
220
+
221
+ // 只检查写入相关工
222
+ if (tool.kind === ToolKind.ReadOnly) return;
223
+
224
+ // 获取受影响的文件路
225
+ const affectedPaths = this.getAffectedPaths(execution);
226
+ if (affectedPaths.length === 0) return;
227
+
228
+ // 检查敏感文
229
+ const sensitiveFiles = SensitiveFileDetector.filterSensitive(
230
+ affectedPaths,
231
+ SensitivityLevel.MEDIUM
232
+ );
233
+
234
+ if (sensitiveFiles.length === 0) return;
235
+
236
+ // 高敏感文件直接拒
237
+ const highSensitive = sensitiveFiles.filter(
238
+ f => f.result.level === SensitivityLevel.HIGH
239
+ );
240
+
241
+ if (highSensitive.length > 0) {
242
+ const files = highSensitive.map(f => f.path).join(', ');
243
+ execution.abort(`Not executed: these files are excluded for safety — ${files}. Choose a different approach.`);
244
+ return;
245
+ }
246
+
247
+ // 中敏感文件需要确
248
+ execution._internal.needsConfirmation = true;
249
+ const reasons = sensitiveFiles.map(f => `${f.path}: ${f.result.reason}`);
250
+ execution._internal.confirmationReason = `Sensitive file access detected:\n${reasons.join('\n')}`;
251
+ }
252
+
253
+ /**
254
+ *
255
+ */
256
+ private getAffectedPaths(execution: ToolExecution): string[] {
257
+ const params = execution.params;
258
+
259
+ // 从常见参数名中提取路
260
+ const pathKeys = ['file_path', 'path', 'target', 'destination'];
261
+ const paths: string[] = [];
262
+
263
+ for (const key of pathKeys) {
264
+ if (typeof params[key] === 'string') {
265
+ paths.push(params[key] as string);
266
+ }
267
+ }
268
+
269
+ return paths;
270
+ }
271
+
272
+ /**
273
+ *
274
+ */
275
+ private async executePermissionHook(
276
+ execution: ToolExecution,
277
+ toolName: string
278
+ ): Promise<'approve' | 'deny' | 'ask'> {
279
+ return onPermissionRequest(
280
+ toolName,
281
+ execution.params as Record<string, unknown>,
282
+ execution.context.sessionId || 'unknown',
283
+ execution.context.workspaceRoot || process.cwd(),
284
+ execution.context.permissionMode
285
+ );
286
+ }
287
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Post Hook Stage - PostToolUse Hooks 执行阶段
3
+ *
4
+ */
5
+
6
+ import type { PipelineStage, ToolExecution } from '../types.js';
7
+ import { onPostToolUse, onPostToolUseFailure } from '../../../hooks/index.js';
8
+
9
+ export class PostHookStage implements PipelineStage {
10
+ readonly name = 'postHook';
11
+
12
+ async process(execution: ToolExecution): Promise<void> {
13
+ const result = execution.getResult();
14
+ if (!result) {
15
+ return;
16
+ }
17
+
18
+ const tool = execution._internal.tool;
19
+ if (!tool) {
20
+ return;
21
+ }
22
+
23
+ // 复用 PreToolUse 阶段生成
24
+ const toolUseId = execution._internal.hookToolUseId || `tool_post_${Date.now()}`;
25
+ const sessionId = execution.context.sessionId || 'unknown';
26
+ const projectDir = execution.context.workspaceRoot || process.cwd();
27
+ const permissionMode = execution.context.permissionMode;
28
+
29
+ // 根据执行成
30
+ if (result.success) {
31
+ // 执
32
+ const hookResult = await onPostToolUse(
33
+ tool.name,
34
+ toolUseId,
35
+ execution.params as Record<string, unknown>,
36
+ result,
37
+ sessionId,
38
+ projectDir,
39
+ permissionMode
40
+ );
41
+
42
+ // 处理 Hook 结果:添加额外上下
43
+ if (hookResult.additionalContext) {
44
+ // 将 Hook 注入的上下文追加
45
+ const currentResult = execution.getResult();
46
+ if (currentResult) {
47
+ currentResult.llmContent += `\n\n[Hook Context]\n${hookResult.additionalContext}`;
48
+ }
49
+ }
50
+
51
+ // 处理 Hook 结果:修改输
52
+ if (hookResult.modifiedOutput !== undefined) {
53
+ const currentResult = execution.getResult();
54
+ if (currentResult) {
55
+ currentResult.llmContent = String(hookResult.modifiedOutput);
56
+ }
57
+ }
58
+ } else {
59
+ // 执
60
+ await onPostToolUseFailure(
61
+ tool.name,
62
+ toolUseId,
63
+ execution.params as Record<string, unknown>,
64
+ result.error?.message || 'Unknown error',
65
+ sessionId,
66
+ projectDir,
67
+ permissionMode
68
+ );
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ *
3
+ */
4
+
5
+ export { DiscoveryStage } from './DiscoveryStage.js';
6
+ export { CacheStage } from './CacheStage.js';
7
+ export { PermissionStage } from './PermissionStage.js';
8
+ export { HookStage } from './HookStage.js';
9
+ export { ConfirmationStage } from './ConfirmationStage.js';
10
+ export { ExecutionStage } from './ExecutionStage.js';
11
+ export { PostHookStage } from './PostHookStage.js';
12
+ export { FormattingStage } from './FormattingStage.js';
@@ -0,0 +1,266 @@
1
+ /**
2
+ *
3
+ *
4
+ *
5
+ */
6
+
7
+ import { ExecutionPipeline, PermissionMode, type PipelineExecutionContext } from './index.js';
8
+ import { ToolRegistry, createToolRegistry, getBuiltinTools } from '../index.js';
9
+ import { PermissionChecker } from '../validation/PermissionChecker.js';
10
+ import { SensitiveFileDetector, SensitivityLevel } from '../validation/SensitiveFileDetector.js';
11
+ import * as fs from 'fs/promises';
12
+ import * as path from 'path';
13
+ import * as os from 'os';
14
+
15
+ // ========== 测试辅
16
+
17
+ let testDir: string;
18
+
19
+ async function setup() {
20
+ testDir = path.join(os.tmpdir(), `pipeline-test-${Date.now()}`);
21
+ await fs.mkdir(testDir, { recursive: true });
22
+
23
+ // 创建测试文
24
+ await fs.writeFile(path.join(testDir, 'test.txt'), 'Hello, World!');
25
+ await fs.writeFile(path.join(testDir, 'config.json'), '{"key": "value"}');
26
+ }
27
+
28
+ async function cleanup() {
29
+ try {
30
+ await fs.rm(testDir, { recursive: true, force: true });
31
+ } catch {
32
+ // Ignore cleanup errors
33
+ }
34
+ }
35
+
36
+ function createTestContext(mode: PermissionMode = PermissionMode.DEFAULT): PipelineExecutionContext {
37
+ return {
38
+ sessionId: 'test-session',
39
+ workspaceRoot: testDir,
40
+ permissionMode: mode,
41
+ };
42
+ }
43
+
44
+ // ========== 测
45
+
46
+ async function testPermissionChecker() {
47
+ console.log('\n1. 测试 PermissionChecker');
48
+
49
+ const checker = new PermissionChecker({
50
+ allow: ['Bash(npm:*)'],
51
+ deny: ['Bash(rm -rf:*)'],
52
+ });
53
+
54
+ // 测试 allow 规
55
+ const allowResult = checker.check({
56
+ toolName: 'Bash',
57
+ params: { command: 'npm test' },
58
+ });
59
+ console.log(` npm test: ${allowResult.result} (expected: allow)`);
60
+
61
+ // 测试 deny 规
62
+ const denyResult = checker.check({
63
+ toolName: 'Bash',
64
+ params: { command: 'rm -rf /' },
65
+ });
66
+ console.log(` rm -rf: ${denyResult.result} (expected: deny)`);
67
+
68
+ // 测试默
69
+ const askResult = checker.check({
70
+ toolName: 'Write',
71
+ params: { file_path: '/tmp/test.txt' },
72
+ });
73
+ console.log(` Write: ${askResult.result} (expected: ask)`);
74
+ }
75
+
76
+ async function testSensitiveFileDetector() {
77
+ console.log('\n2. 测试 SensitiveFileDetector');
78
+
79
+ // 高敏
80
+ const envResult = SensitiveFileDetector.check('.env');
81
+ console.log(` .env: ${envResult.level} (expected: high)`);
82
+
83
+ // 中敏
84
+ const logResult = SensitiveFileDetector.check('app.log');
85
+ console.log(` app.log: ${logResult.level} (expected: medium)`);
86
+
87
+ // 低敏
88
+ const configResult = SensitiveFileDetector.check('config.json');
89
+ console.log(` config.json: ${configResult.level} (expected: low)`);
90
+
91
+ // 非敏
92
+ const normalResult = SensitiveFileDetector.check('main.ts');
93
+ console.log(` main.ts: sensitive=${normalResult.sensitive} (expected: false)`);
94
+
95
+ // 危险路
96
+ const dangerous = SensitiveFileDetector.isDangerousPath('/etc/passwd');
97
+ console.log(` /etc/passwd: dangerous=${dangerous} (expected: true)`);
98
+ }
99
+
100
+ async function testPipelineReadOnly() {
101
+ console.log('\n3. 测试只读工具执行');
102
+
103
+ const registry = createToolRegistry();
104
+ const builtinTools = getBuiltinTools();
105
+ for (const tool of builtinTools) {
106
+ registry.register(tool);
107
+ }
108
+
109
+ const pipeline = new ExecutionPipeline(registry);
110
+ const context = createTestContext();
111
+
112
+ // 测试 Read 工
113
+ const testFile = path.join(testDir, 'test.txt');
114
+ const readResult = await pipeline.execute('Read', { file_path: testFile }, context);
115
+ console.log(` Read: success=${readResult.success}, content includes 'Hello'=${readResult.llmContent.includes('Hello')}`);
116
+
117
+ // 测试 Glob 工
118
+ const globResult = await pipeline.execute('Glob', { pattern: '*.txt', path: testDir }, context);
119
+ console.log(` Glob: success=${globResult.success}, found files=${globResult.llmContent.includes('test.txt')}`);
120
+ }
121
+
122
+ async function testPermissionModes() {
123
+ console.log('\n4. 测试权限模式');
124
+
125
+ const registry = createToolRegistry();
126
+ const builtinTools = getBuiltinTools();
127
+ for (const tool of builtinTools) {
128
+ registry.register(tool);
129
+ }
130
+
131
+ const pipeline = new ExecutionPipeline(registry);
132
+ const testFile = path.join(testDir, 'write-test.txt');
133
+
134
+ // PLAN 模式:Write 应该被拒
135
+ const planContext = createTestContext(PermissionMode.PLAN);
136
+ const planResult = await pipeline.execute(
137
+ 'Write',
138
+ { file_path: testFile, contents: 'test' },
139
+ planContext
140
+ );
141
+ console.log(` PLAN + Write: success=${planResult.success} (expected: false)`);
142
+
143
+ // YOLO 模式:Write 应该自动批
144
+ const yoloContext = createTestContext(PermissionMode.YOLO);
145
+ const yoloResult = await pipeline.execute(
146
+ 'Write',
147
+ { file_path: testFile, contents: 'YOLO test' },
148
+ yoloContext
149
+ );
150
+ console.log(` YOLO + Write: success=${yoloResult.success} (expected: true)`);
151
+
152
+ // 验证文件内
153
+ const content = await fs.readFile(testFile, 'utf8');
154
+ console.log(` File content correct: ${content === 'YOLO test'}`);
155
+ }
156
+
157
+ async function testPipelineStages() {
158
+ console.log('\n5. 测试管道阶段事件');
159
+
160
+ const registry = createToolRegistry();
161
+ const builtinTools = getBuiltinTools();
162
+ for (const tool of builtinTools) {
163
+ registry.register(tool);
164
+ }
165
+
166
+ const pipeline = new ExecutionPipeline(registry);
167
+ const context = createTestContext(PermissionMode.YOLO);
168
+
169
+ const stagesExecuted = pipeline.getStageNames();
170
+
171
+ const testFile = path.join(testDir, 'test.txt');
172
+ await pipeline.execute('Read', { file_path: testFile }, context);
173
+
174
+ console.log(` Stages: ${stagesExecuted.join(' → ')}`);
175
+ console.log(` All ${stagesExecuted.length} stages executed: ${stagesExecuted.length === 8}`);
176
+ }
177
+
178
+ async function testExecutionHistory() {
179
+ console.log('\n6. 测试执行历史');
180
+
181
+ const registry = createToolRegistry();
182
+ const builtinTools = getBuiltinTools();
183
+ for (const tool of builtinTools) {
184
+ registry.register(tool);
185
+ }
186
+
187
+ const pipeline = new ExecutionPipeline(registry);
188
+ const context = createTestContext(PermissionMode.YOLO);
189
+
190
+ // 执行几个操
191
+ const testFile = path.join(testDir, 'test.txt');
192
+ await pipeline.execute('Read', { file_path: testFile }, context);
193
+ await pipeline.execute('Glob', { pattern: '*', path: testDir }, context);
194
+
195
+ const history = pipeline.getHistory();
196
+ console.log(` History entries: ${history.length} (expected: 2)`);
197
+ console.log(` First entry tool: ${history[0]?.toolName} (expected: Read)`);
198
+ }
199
+
200
+ async function testSessionApprovals() {
201
+ console.log('\n7. 测试会话批准');
202
+
203
+ const registry = createToolRegistry();
204
+ const builtinTools = getBuiltinTools();
205
+ for (const tool of builtinTools) {
206
+ registry.register(tool);
207
+ }
208
+
209
+ const pipeline = new ExecutionPipeline(registry);
210
+
211
+ // 手动添加会话批
212
+ pipeline.addSessionApproval('Write(/tmp/approved.txt)');
213
+
214
+ console.log(` Has approval: ${pipeline.hasSessionApproval('Write(/tmp/approved.txt)')} (expected: true)`);
215
+ console.log(` No approval: ${pipeline.hasSessionApproval('Write(/tmp/other.txt)')} (expected: false)`);
216
+
217
+ // 清
218
+ pipeline.clearSessionApprovals();
219
+ console.log(` After clear: ${pipeline.hasSessionApproval('Write(/tmp/approved.txt)')} (expected: false)`);
220
+ }
221
+
222
+ async function testBashTool() {
223
+ console.log('\n8. 测试 Bash 工具');
224
+
225
+ const registry = createToolRegistry();
226
+ const builtinTools = getBuiltinTools();
227
+ for (const tool of builtinTools) {
228
+ registry.register(tool);
229
+ }
230
+
231
+ const pipeline = new ExecutionPipeline(registry);
232
+ const context = createTestContext(PermissionMode.YOLO);
233
+
234
+ // 简单命
235
+ const echoResult = await pipeline.execute(
236
+ 'Bash',
237
+ { command: 'echo "Pipeline Test"' },
238
+ context
239
+ );
240
+ console.log(` echo: success=${echoResult.success}, output includes 'Pipeline Test'=${echoResult.llmContent.includes('Pipeline Test')}`);
241
+ }
242
+
243
+ // ========== 主函
244
+
245
+ async function main() {
246
+ console.log('=== 执行管道测试 ===');
247
+
248
+ try {
249
+ await setup();
250
+
251
+ await testPermissionChecker();
252
+ await testSensitiveFileDetector();
253
+ await testPipelineReadOnly();
254
+ await testPermissionModes();
255
+ await testPipelineStages();
256
+ await testExecutionHistory();
257
+ await testSessionApprovals();
258
+ await testBashTool();
259
+
260
+ console.log('\n=== 测试完成 ===');
261
+ } finally {
262
+ await cleanup();
263
+ }
264
+ }
265
+
266
+ main().catch(console.error);