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,40 @@
1
+ /**
2
+ *
3
+ */
4
+
5
+ // 类
6
+ export {
7
+ PermissionMode,
8
+ PermissionResult,
9
+ ToolExecution,
10
+ type PipelineStage,
11
+ type PipelineExecutionContext,
12
+ type ExecutionPipelineConfig,
13
+ type ExecutionHistoryEntry,
14
+ type PermissionCheckResult,
15
+ type PermissionConfig,
16
+ type ToolInvocationDescriptor,
17
+ type ConfirmationDetails,
18
+ type ConfirmationResponse,
19
+ type ConfirmationHandler,
20
+ type ToolProgress,
21
+ type PreToolHookResult,
22
+ type PostToolHookParams,
23
+ type StageStartEvent,
24
+ type StageCompleteEvent,
25
+ } from './types.js';
26
+
27
+ // 主
28
+ export { ExecutionPipeline, type ExecutionPipelineEvents } from './ExecutionPipeline.js';
29
+
30
+ // 阶
31
+ export {
32
+ DiscoveryStage,
33
+ CacheStage,
34
+ PermissionStage,
35
+ HookStage,
36
+ ConfirmationStage,
37
+ ExecutionStage,
38
+ PostHookStage,
39
+ FormattingStage,
40
+ } from './stages/index.js';
@@ -0,0 +1,131 @@
1
+ /**
2
+ * CacheStage — session-scoped tool result caching
3
+ *
4
+ * Caches results from read-only tools (Read, Grep, Glob) so that re-requesting
5
+ * the same file / same glob / same grep within the same session does not re-execute
6
+ * the tool. The result is injected directly into the ToolExecution object, allowing
7
+ * the pipeline to short-circuit.
8
+ *
9
+ * Design parallels Claude Code's `tool-results/` directory per session UUID.
10
+ *
11
+ * Key decisions:
12
+ * - Cache key = SHA-256 hash of (toolName + sorted-JSON(params) + sessionId)
13
+ * - Only caches SUCCESSFUL results from read-only tools
14
+ * - Cache is in-memory (Map) + optionally persisted to disk
15
+ * - TTL = session lifetime (cleared when session ends or cache is purged)
16
+ */
17
+
18
+ import * as crypto from 'node:crypto';
19
+ import type { ToolResult } from '../../types.js';
20
+ import type { PipelineStage, ToolExecution } from '../types.js';
21
+
22
+ // ── Types ────────────────────────────────────────────────────────────────────
23
+
24
+ export interface CacheEntry {
25
+ result: ToolResult;
26
+ cachedAt: number;
27
+ hitCount: number;
28
+ }
29
+
30
+ export interface CacheStageOptions {
31
+ /** Maximum cache entries (default 500) */
32
+ maxSize?: number;
33
+ }
34
+
35
+ // ── Read-only tool names that are safe to cache ──────────────────────────────
36
+
37
+ const READONLY_TOOLS = new Set(['Read', 'Grep', 'Glob']);
38
+
39
+ // ── CacheStage ───────────────────────────────────────────────────────────────
40
+
41
+ export class CacheStage implements PipelineStage {
42
+ readonly name = 'cache';
43
+
44
+ /** Cache key → CacheEntry */
45
+ private store = new Map<string, CacheEntry>();
46
+ private maxSize: number;
47
+
48
+ constructor(options: CacheStageOptions = {}) {
49
+ this.maxSize = options.maxSize ?? 500;
50
+ }
51
+
52
+ async process(execution: ToolExecution): Promise<void> {
53
+ // Only cache read-only tools
54
+ if (!READONLY_TOOLS.has(execution.toolName)) return;
55
+
56
+ const cacheKey = this.buildKey(
57
+ execution.toolName,
58
+ execution.params,
59
+ execution.context.sessionId,
60
+ );
61
+
62
+ const cached = this.store.get(cacheKey);
63
+ if (cached) {
64
+ cached.hitCount++;
65
+ execution.setResult(cached.result);
66
+ return;
67
+ }
68
+
69
+ // Not in cache — ExecutionStage reads this key to store the result once
70
+ // the tool actually runs.
71
+ execution._internal.cacheKey = cacheKey;
72
+ }
73
+
74
+ /**
75
+ * Called by ExecutionStage after a tool executes successfully, to store
76
+ * the result in the cache.
77
+ */
78
+ cacheResult(cacheKey: string, result: ToolResult): void {
79
+ if (!result.success) return;
80
+ if (!result.llmContent && !result.displayContent) return;
81
+
82
+ // Evict oldest if at capacity
83
+ if (this.store.size >= this.maxSize) {
84
+ let oldestKey: string | undefined;
85
+ let oldestTime = Infinity;
86
+ for (const [k, v] of this.store) {
87
+ if (v.cachedAt < oldestTime) {
88
+ oldestTime = v.cachedAt;
89
+ oldestKey = k;
90
+ }
91
+ }
92
+ if (oldestKey) this.store.delete(oldestKey);
93
+ }
94
+
95
+ this.store.set(cacheKey, {
96
+ result,
97
+ cachedAt: Date.now(),
98
+ hitCount: 0,
99
+ });
100
+ }
101
+
102
+ /** Clear all cached entries */
103
+ clear(): void {
104
+ this.store.clear();
105
+ }
106
+
107
+ /** Clear entries for a specific session */
108
+ clearSession(sessionId: string): void {
109
+ for (const [key, entry] of this.store) {
110
+ // Keys embed the sessionId — check by extracting it
111
+ if (key.includes(sessionId)) {
112
+ this.store.delete(key);
113
+ }
114
+ }
115
+ }
116
+
117
+ /** Get cache stats */
118
+ stats(): { size: number; maxSize: number } {
119
+ return { size: this.store.size, maxSize: this.maxSize };
120
+ }
121
+
122
+ // ── Private ──────────────────────────────────────────────────────────────
123
+
124
+ private buildKey(toolName: string, params: Record<string, unknown>, sessionId: string): string {
125
+ const hash = crypto.createHash('sha256');
126
+ hash.update(toolName);
127
+ hash.update(JSON.stringify(params, Object.keys(params).sort()));
128
+ hash.update(sessionId);
129
+ return hash.digest('hex');
130
+ }
131
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Confirmation Stage - 用户确认阶段
3
+ *
4
+ */
5
+
6
+ import type {
7
+ PipelineStage,
8
+ ToolExecution,
9
+ ConfirmationDetails,
10
+ } from '../types.js';
11
+
12
+ export class ConfirmationStage implements PipelineStage {
13
+ readonly name = 'confirmation';
14
+
15
+ constructor(
16
+ private sessionApprovals: Set<string>,
17
+ private sessionDenials?: Set<string>
18
+ ) {}
19
+
20
+ async process(execution: ToolExecution): Promise<void> {
21
+ // 如果不需要确认,直接通
22
+ if (!execution._internal.needsConfirmation) {
23
+ return;
24
+ }
25
+
26
+ const forceConfirmation = execution._internal.forceConfirmation === true;
27
+
28
+ // 模型级设置:禁用确认提示时直接放行(除非该操作被标记为强制确认
29
+ if (!forceConfirmation && execution.context.requireConfirmation === false) {
30
+ return;
31
+ }
32
+
33
+ const tool = execution._internal.tool;
34
+ if (!tool) {
35
+ execution.abort('Tool not found in execution context');
36
+ return;
37
+ }
38
+
39
+ // 检查是否已在会话中拒绝
40
+ const signature = execution._internal.permissionSignature;
41
+ if (signature && this.sessionDenials?.has(signature)) {
42
+ execution.abort('Not executed: you tried this exact action earlier in this session and the user declined it. Choose a different approach.');
43
+ return;
44
+ }
45
+
46
+ // 检查是否已在会话中批准(强制确认的操作不允许通过"永远允许"跳过
47
+ if (!forceConfirmation && signature && this.sessionApprovals.has(signature)) {
48
+ return;
49
+ }
50
+
51
+ // 构建确认详
52
+ const confirmationDetails: ConfirmationDetails = {
53
+ title: `Permission Required: ${tool.name}`,
54
+ message: execution._internal.confirmationReason || 'This operation requires your confirmation',
55
+ details: this.generatePreview(execution),
56
+ risks: this.extractRisks(execution),
57
+ affectedFiles: this.getAffectedPaths(execution),
58
+ };
59
+
60
+ // 请求用户确
61
+ const handler = execution.context.confirmationHandler;
62
+ if (!handler) {
63
+ // 无确认处理器,默认拒
64
+ // Worded to avoid "permission/approval/dialog" vocabulary — this string becomes
65
+ // llmContent in a real tool_result, and re-using that vocabulary cluster is what
66
+ // lets the model later free-associate a hallucinated "blocked, click Allow" reply
67
+ // in an unrelated turn with no tool call at all.
68
+ execution.abort('Not executed: this session cannot ask the user a yes/no question right now. Retrying this exact action will not help — explain what you wanted to do in your text response instead, or ask the user directly.');
69
+ return;
70
+ }
71
+
72
+ const response = await handler.requestConfirmation(confirmationDetails);
73
+
74
+ if (!response.approved) {
75
+ // 如果用户选择"永远拒绝",保存到会话拒绝列
76
+ if (response.scope === 'session' && signature && this.sessionDenials) {
77
+ this.sessionDenials.add(signature);
78
+ }
79
+ execution.abort(`Not executed: the user declined this action${response.reason ? ` — ${response.reason}` : ''}. Choose a different approach.`);
80
+ return;
81
+ }
82
+
83
+ // 如果用户选择"永远允许",保存到会话批准列
84
+ if (response.scope === 'session' && signature) {
85
+ this.sessionApprovals.add(signature);
86
+ }
87
+ }
88
+
89
+ /**
90
+ *
91
+ */
92
+ private generatePreview(execution: ToolExecution): string | undefined {
93
+ const { toolName, params } = execution;
94
+
95
+ switch (toolName) {
96
+ case 'Edit': {
97
+ const oldString = params.old_string as string;
98
+ const newString = params.new_string as string;
99
+ const filePath = params.file_path as string;
100
+
101
+ return `**File:** ${filePath}
102
+
103
+ **Before:**
104
+ \`\`\`
105
+ ${this.truncate(oldString, 10)}
106
+ \`\`\`
107
+
108
+ **After:**
109
+ \`\`\`
110
+ ${this.truncate(newString, 10)}
111
+ \`\`\``;
112
+ }
113
+
114
+ case 'Write': {
115
+ const content = params.contents as string;
116
+ const filePath = params.file_path as string;
117
+
118
+ return `**File:** ${filePath}
119
+
120
+ **Content Preview:**
121
+ \`\`\`
122
+ ${this.truncate(content, 20)}
123
+ \`\`\``;
124
+ }
125
+
126
+ case 'Bash': {
127
+ const command = params.command as string;
128
+ const cwd = params.working_directory as string;
129
+
130
+ return `**Command:** \`${command}\`${cwd ? `\n**Directory:** ${cwd}` : ''}`;
131
+ }
132
+
133
+ default:
134
+ return undefined;
135
+ }
136
+ }
137
+
138
+ /**
139
+ *
140
+ */
141
+ private extractRisks(execution: ToolExecution): string[] {
142
+ const risks: string[] = [];
143
+ const { toolName, params } = execution;
144
+ const tool = execution._internal.tool;
145
+
146
+ // 基于工具类型的风
147
+ if (tool?.kind === 'write') {
148
+ risks.push('This operation will modify files');
149
+ } else if (tool?.kind === 'execute') {
150
+ risks.push('This operation will execute system commands');
151
+ }
152
+
153
+ // 基于参数的风
154
+ if (toolName === 'Bash') {
155
+ const command = params.command as string;
156
+ if (command.includes('rm')) {
157
+ risks.push('Command may delete files');
158
+ }
159
+ if (command.includes('sudo')) {
160
+ risks.push('Command requires elevated privileges');
161
+ }
162
+ if (command.includes('|')) {
163
+ risks.push('Command uses piping');
164
+ }
165
+ }
166
+
167
+ // 检查敏感文
168
+ const confirmReason = execution._internal.confirmationReason || '';
169
+ if (confirmReason.includes('Sensitive file')) {
170
+ risks.push('Operation involves sensitive files');
171
+ }
172
+
173
+ return risks;
174
+ }
175
+
176
+ /**
177
+ *
178
+ */
179
+ private getAffectedPaths(execution: ToolExecution): string[] {
180
+ const params = execution.params;
181
+ const pathKeys = ['file_path', 'path', 'target', 'destination'];
182
+ const paths: string[] = [];
183
+
184
+ for (const key of pathKeys) {
185
+ if (typeof params[key] === 'string') {
186
+ paths.push(params[key] as string);
187
+ }
188
+ }
189
+
190
+ return paths;
191
+ }
192
+
193
+ /**
194
+ *
195
+ */
196
+ private truncate(text: string, maxLines: number = 10): string {
197
+ const lines = text.split('\n');
198
+ if (lines.length <= maxLines) {
199
+ return text;
200
+ }
201
+ return lines.slice(0, maxLines).join('\n') + `\n... (${lines.length - maxLines} more lines)`;
202
+ }
203
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Discovery Stage - 工具发现阶段
3
+ *
4
+ * Per-model allowedTools/disallowedTools filtering added here.
5
+ */
6
+
7
+ import type { PipelineStage, ToolExecution } from '../types.js';
8
+ import type { ToolRegistry } from '../../registry.js';
9
+
10
+ export class DiscoveryStage implements PipelineStage {
11
+ readonly name = 'discovery';
12
+
13
+ constructor(
14
+ private registry: ToolRegistry,
15
+ private allowedTools?: string[],
16
+ private disallowedTools?: string[],
17
+ ) {}
18
+
19
+ async process(execution: ToolExecution): Promise<void> {
20
+ // 1. Check per-model disallowed tools
21
+ if (this.disallowedTools?.includes(execution.toolName)) {
22
+ execution.abort(`Tool "${execution.toolName}" is disallowed for the current model`);
23
+ return;
24
+ }
25
+
26
+ // 2. Check per-model allowed tools (if set, only these are allowed)
27
+ if (this.allowedTools && this.allowedTools.length > 0) {
28
+ if (!this.allowedTools.includes(execution.toolName)) {
29
+ execution.abort(
30
+ `Tool "${execution.toolName}" is not in the allowed tools list for the current model`
31
+ );
32
+ return;
33
+ }
34
+ }
35
+
36
+ const tool = this.registry.get(execution.toolName);
37
+
38
+ if (!tool) {
39
+ execution.abort(`Tool "${execution.toolName}" not found in registry`);
40
+ return;
41
+ }
42
+
43
+ // 将工具实例附加到执行上下
44
+ execution._internal.tool = tool;
45
+ }
46
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Execution Stage - 实际执行阶段
3
+ *
4
+ */
5
+
6
+ import type { PipelineStage, ToolExecution } from '../types.js';
7
+ import type { ExecutionContext } from '../../types.js';
8
+ import type { CacheStage } from './CacheStage.js';
9
+
10
+ export class ExecutionStage implements PipelineStage {
11
+ readonly name = 'execution';
12
+
13
+ constructor(private cacheStage?: CacheStage) {}
14
+
15
+ async process(execution: ToolExecution): Promise<void> {
16
+ // CacheStage already set the result on a cache hit — nothing to run.
17
+ if (execution.getResult()) return;
18
+
19
+ const tool = execution._internal.tool;
20
+
21
+ if (!tool) {
22
+ execution.abort('Tool not found in execution context');
23
+ return;
24
+ }
25
+
26
+ try {
27
+ // 构建执行上下
28
+ const context: ExecutionContext = {
29
+ sessionId: execution.context.sessionId,
30
+ signal: execution.context.signal,
31
+ cwd: execution.context.workspaceRoot,
32
+ };
33
+
34
+ // 执行工
35
+ const result = await tool.execute(execution.params, context);
36
+
37
+ // 设置结
38
+ execution.setResult(result);
39
+
40
+ const cacheKey = execution._internal.cacheKey;
41
+ if (cacheKey) this.cacheStage?.cacheResult(cacheKey, result);
42
+ } catch (error) {
43
+ // 处理执行错
44
+ const errorMessage = error instanceof Error ? error.message : String(error);
45
+ execution.abort(`Tool execution failed: ${errorMessage}`);
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Formatting Stage - 结果格式化阶段
3
+ *
4
+ */
5
+
6
+ import type { PipelineStage, ToolExecution } from '../types.js';
7
+
8
+ export class FormattingStage implements PipelineStage {
9
+ readonly name = 'formatting';
10
+
11
+ async process(execution: ToolExecution): Promise<void> {
12
+ const result = execution.getResult();
13
+
14
+ if (!result) {
15
+ // 没有结果(可能是被中止了),不处
16
+ return;
17
+ }
18
+
19
+ // 确保结果格式正
20
+ if (!result.llmContent) {
21
+ result.llmContent = result.success
22
+ ? 'Execution completed successfully'
23
+ : 'Execution failed';
24
+ }
25
+
26
+ if (!result.displayContent) {
27
+ result.displayContent = result.success
28
+ ? `✅ ${execution.toolName} completed`
29
+ : `❌ ${execution.toolName} failed`;
30
+ }
31
+
32
+ // 添加执行元数
33
+ result.metadata = {
34
+ ...result.metadata,
35
+ executionId: execution.context.sessionId,
36
+ toolName: execution.toolName,
37
+ timestamp: Date.now(),
38
+ permissionMode: execution.context.permissionMode,
39
+ };
40
+
41
+ // 更新结
42
+ execution.setResult(result);
43
+ }
44
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Hook Stage (Pre) - PreToolUse Hooks 执行阶段
3
+ *
4
+ */
5
+
6
+ import { nanoid } from 'nanoid';
7
+ import type { PipelineStage, ToolExecution } from '../types.js';
8
+ import { onPreToolUse } from '../../../hooks/index.js';
9
+
10
+ export class HookStage implements PipelineStage {
11
+ readonly name = 'hook';
12
+
13
+ async process(execution: ToolExecution): Promise<void> {
14
+ const tool = execution._internal.tool;
15
+ if (!tool) {
16
+ return;
17
+ }
18
+
19
+ // 生成唯一的 toolUseId(PostToolUse 阶段复
20
+ const toolUseId = execution.context.messageId || `tool_${nanoid()}`;
21
+ execution._internal.hookToolUseId = toolUseId;
22
+
23
+ // 执行 PreToolUse hooks(通
24
+ const result = await onPreToolUse(
25
+ tool.name,
26
+ toolUseId,
27
+ execution.params as Record<string, unknown>,
28
+ execution.context.sessionId || 'unknown',
29
+ execution.context.workspaceRoot || process.cwd(),
30
+ execution.context.permissionMode
31
+ );
32
+
33
+ // 处理 Hook 决
34
+ if (result.decision === 'deny') {
35
+ // 直接拒绝,中止执
36
+ execution.abort(result.reason || 'Hook blocked execution');
37
+ return;
38
+ }
39
+
40
+ if (result.decision === 'ask') {
41
+ // 标记需要用户确认(传递给 Confirmation 阶
42
+ execution._internal.needsConfirmation = true;
43
+ execution._internal.confirmationReason =
44
+ result.reason || 'Hook requires confirmation';
45
+ return;
46
+ }
47
+
48
+ // decision === 'allow':应用修改后的输
49
+ if (result.modifiedInput) {
50
+ const newParams = { ...execution.params, ...result.modifiedInput };
51
+
52
+ // 重新验证修改后的参
53
+ if (tool.build) {
54
+ try {
55
+ tool.build(newParams);
56
+ // 更新参
57
+ execution.params = newParams;
58
+ } catch (err) {
59
+ execution.abort(
60
+ `Hook modified parameters are invalid: ${err instanceof Error ? err.message : String(err)}`
61
+ );
62
+ return;
63
+ }
64
+ }
65
+ }
66
+
67
+ // 输出警告信
68
+ if (result.warning) {
69
+ console.warn(`[Hook Warning] ${result.warning}`);
70
+ }
71
+ }
72
+ }