aegiscode 3.1.10 → 3.4.2
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.
- package/bin/aegis.js +165 -0
- package/package.json +18 -86
- package/LICENSE +0 -21
- package/README.md +0 -394
- package/dist/main.js +0 -2171
- package/gui/run-gui.cjs +0 -13
- package/src/agent/Agent.ts +0 -903
- package/src/agent/SimpleAgent.ts +0 -48
- package/src/agent/index.ts +0 -54
- package/src/agent/orchestrator/AppBuilder.ts +0 -443
- package/src/agent/orchestrator/CouncilAgent.ts +0 -310
- package/src/agent/orchestrator/DiscussionRoom.ts +0 -462
- package/src/agent/orchestrator/OrchestratorAgent.ts +0 -637
- package/src/agent/orchestrator/index.ts +0 -38
- package/src/agent/orchestrator/utils.ts +0 -397
- package/src/agent/pricing.ts +0 -115
- package/src/agent/router.ts +0 -74
- package/src/agent/routerStats.ts +0 -121
- package/src/agent/types.ts +0 -318
- package/src/auth/login.ts +0 -383
- package/src/cli/config.ts +0 -189
- package/src/cli/index.ts +0 -17
- package/src/cli/middleware.ts +0 -119
- package/src/cli/types.ts +0 -75
- package/src/config/ConfigManager.ts +0 -587
- package/src/config/index.ts +0 -7
- package/src/config/types.ts +0 -584
- package/src/context/CompactionService.ts +0 -300
- package/src/context/ContextManager.ts +0 -450
- package/src/context/FileAnalyzer.ts +0 -267
- package/src/context/TokenCounter.ts +0 -265
- package/src/context/index.ts +0 -27
- package/src/context/storage/CacheStore.ts +0 -176
- package/src/context/storage/JSONLStore.ts +0 -201
- package/src/context/storage/MemoryStore.ts +0 -205
- package/src/context/storage/PersistentStore.ts +0 -327
- package/src/context/storage/index.ts +0 -9
- package/src/context/storage/pathUtils.ts +0 -114
- package/src/context/test.ts +0 -309
- package/src/context/types.ts +0 -268
- package/src/hooks/HookExecutor.ts +0 -434
- package/src/hooks/HookManager.ts +0 -596
- package/src/hooks/HookService.ts +0 -269
- package/src/hooks/Matcher.ts +0 -157
- package/src/hooks/index.ts +0 -63
- package/src/hooks/types.ts +0 -424
- package/src/main.tsx +0 -596
- package/src/mcp/HealthMonitor.ts +0 -150
- package/src/mcp/McpClient.ts +0 -491
- package/src/mcp/McpRegistry.ts +0 -321
- package/src/mcp/createMcpTool.ts +0 -251
- package/src/mcp/index.ts +0 -15
- package/src/mcp/server.ts +0 -334
- package/src/mcp/test-server.ts +0 -88
- package/src/mcp/test.ts +0 -372
- package/src/mcp/types.ts +0 -247
- package/src/memory/AgentMemoryBus.ts +0 -432
- package/src/memory/CloudSync.ts +0 -99
- package/src/memory/DriveSync.ts +0 -106
- package/src/memory/SharedMemory.ts +0 -951
- package/src/memory/index.ts +0 -14
- package/src/memory/machineFingerprint.ts +0 -40
- package/src/orchestrator/SubAgentMetadata.ts +0 -136
- package/src/prompts/builder.ts +0 -213
- package/src/prompts/default.ts +0 -144
- package/src/prompts/index.ts +0 -16
- package/src/prompts/plan.ts +0 -64
- package/src/prompts/test.ts +0 -78
- package/src/services/AnthropicChatService.ts +0 -341
- package/src/services/ChatService.ts +0 -347
- package/src/services/ClaudeCliChatService.ts +0 -256
- package/src/services/CloudSync.ts +0 -168
- package/src/services/CostLedger.ts +0 -211
- package/src/services/Heartbeat.ts +0 -135
- package/src/services/LearningCollector.ts +0 -291
- package/src/services/OllamaInstaller.ts +0 -342
- package/src/services/VersionChecker.ts +0 -445
- package/src/services/index.ts +0 -57
- package/src/services/streaming/OpenAIEventAdapter.ts +0 -126
- package/src/services/streaming/RenderingProfile.ts +0 -90
- package/src/services/streaming/StreamEventParser.ts +0 -181
- package/src/services/streaming/ThrottledRenderer.ts +0 -139
- package/src/services/streaming/TranscriptBuffer.ts +0 -574
- package/src/services/streaming/eventStatusMap.ts +0 -52
- package/src/services/streaming/index.ts +0 -46
- package/src/services/streaming/renderFormatting.ts +0 -79
- package/src/services/streaming/types.ts +0 -234
- package/src/skills/SkillLoader.ts +0 -126
- package/src/skills/SkillRegistry.ts +0 -366
- package/src/skills/index.ts +0 -48
- package/src/skills/types.ts +0 -146
- package/src/slash-commands/billing.ts +0 -70
- package/src/slash-commands/build.ts +0 -413
- package/src/slash-commands/builtinCommands.ts +0 -2733
- package/src/slash-commands/clone.ts +0 -242
- package/src/slash-commands/council.ts +0 -125
- package/src/slash-commands/custom/CustomCommandExecutor.ts +0 -143
- package/src/slash-commands/custom/CustomCommandLoader.ts +0 -251
- package/src/slash-commands/custom/CustomCommandRegistry.ts +0 -144
- package/src/slash-commands/custom/index.ts +0 -7
- package/src/slash-commands/debate.ts +0 -254
- package/src/slash-commands/gmail.ts +0 -105
- package/src/slash-commands/index.ts +0 -388
- package/src/slash-commands/mcpCommand.ts +0 -205
- package/src/slash-commands/types.ts +0 -201
- package/src/store/index.ts +0 -76
- package/src/store/selectors.ts +0 -246
- package/src/store/slices/appSlice.ts +0 -205
- package/src/store/slices/commandSlice.ts +0 -115
- package/src/store/slices/configSlice.ts +0 -46
- package/src/store/slices/focusSlice.ts +0 -64
- package/src/store/slices/index.ts +0 -9
- package/src/store/slices/sessionSlice.ts +0 -424
- package/src/store/streaming-buffer.ts +0 -425
- package/src/store/test.ts +0 -296
- package/src/store/types.ts +0 -274
- package/src/store/vanilla.ts +0 -186
- package/src/tools/builtin/bash.ts +0 -236
- package/src/tools/builtin/council.ts +0 -105
- package/src/tools/builtin/edit.ts +0 -213
- package/src/tools/builtin/glob.ts +0 -136
- package/src/tools/builtin/grep.ts +0 -263
- package/src/tools/builtin/index.ts +0 -61
- package/src/tools/builtin/memory.ts +0 -66
- package/src/tools/builtin/read.ts +0 -168
- package/src/tools/builtin/skill.ts +0 -97
- package/src/tools/builtin/snapshot.ts +0 -40
- package/src/tools/builtin/task.ts +0 -106
- package/src/tools/builtin/write.ts +0 -134
- package/src/tools/createTool.ts +0 -221
- package/src/tools/execution/ExecutionPipeline.ts +0 -263
- package/src/tools/execution/index.ts +0 -40
- package/src/tools/execution/stages/CacheStage.ts +0 -131
- package/src/tools/execution/stages/ConfirmationStage.ts +0 -203
- package/src/tools/execution/stages/DiscoveryStage.ts +0 -46
- package/src/tools/execution/stages/ExecutionStage.ts +0 -48
- package/src/tools/execution/stages/FormattingStage.ts +0 -44
- package/src/tools/execution/stages/HookStage.ts +0 -72
- package/src/tools/execution/stages/PermissionStage.ts +0 -287
- package/src/tools/execution/stages/PostHookStage.ts +0 -71
- package/src/tools/execution/stages/index.ts +0 -12
- package/src/tools/execution/test.ts +0 -266
- package/src/tools/execution/types.ts +0 -273
- package/src/tools/index.ts +0 -81
- package/src/tools/registry.ts +0 -304
- package/src/tools/schemas.ts +0 -109
- package/src/tools/test.ts +0 -220
- package/src/tools/types.ts +0 -175
- package/src/tools/validation/PermissionChecker.ts +0 -242
- package/src/tools/validation/SensitiveFileDetector.ts +0 -210
- package/src/tools/validation/index.ts +0 -11
- package/src/ui/App.tsx +0 -166
- package/src/ui/components/AegisInterface.tsx +0 -484
- package/src/ui/components/common/ChatSearch.tsx +0 -150
- package/src/ui/components/common/ErrorBoundary.tsx +0 -82
- package/src/ui/components/common/ExitMessage.tsx +0 -120
- package/src/ui/components/common/LoadingIndicator.tsx +0 -49
- package/src/ui/components/common/index.ts +0 -6
- package/src/ui/components/dialog/ConfirmationPrompt.tsx +0 -208
- package/src/ui/components/dialog/InteractiveSelector.tsx +0 -149
- package/src/ui/components/dialog/SetupWizard.tsx +0 -297
- package/src/ui/components/dialog/UpdatePrompt.tsx +0 -155
- package/src/ui/components/dialog/index.ts +0 -8
- package/src/ui/components/index.ts +0 -28
- package/src/ui/components/input/CommandSuggestions.tsx +0 -139
- package/src/ui/components/input/CustomTextInput.tsx +0 -220
- package/src/ui/components/input/InputArea.tsx +0 -361
- package/src/ui/components/input/PromptSuggestions.tsx +0 -66
- package/src/ui/components/input/index.ts +0 -6
- package/src/ui/components/layout/ChatStatusBar.tsx +0 -90
- package/src/ui/components/layout/ContextBar.tsx +0 -79
- package/src/ui/components/layout/MessageArea.tsx +0 -96
- package/src/ui/components/layout/MessageList.tsx +0 -647
- package/src/ui/components/layout/MessageSeparator.tsx +0 -26
- package/src/ui/components/layout/WelcomeMessage.tsx +0 -93
- package/src/ui/components/layout/index.ts +0 -7
- package/src/ui/components/markdown/CodeHighlighter.tsx +0 -292
- package/src/ui/components/markdown/MessageRenderer.tsx +0 -1211
- package/src/ui/components/markdown/index.ts +0 -8
- package/src/ui/components/markdown/parser.ts +0 -336
- package/src/ui/components/markdown/types.ts +0 -66
- package/src/ui/focus/FocusManager.ts +0 -137
- package/src/ui/focus/index.ts +0 -13
- package/src/ui/focus/types.ts +0 -54
- package/src/ui/focus/useFocus.ts +0 -75
- package/src/ui/hooks/index.ts +0 -11
- package/src/ui/hooks/useAgent.ts +0 -284
- package/src/ui/hooks/useCommandHistory.ts +0 -87
- package/src/ui/hooks/useCommandProcessor.ts +0 -443
- package/src/ui/hooks/useConfirmation.ts +0 -99
- package/src/ui/hooks/useCtrlCHandler.ts +0 -100
- package/src/ui/hooks/useInputBuffer.ts +0 -122
- package/src/ui/hooks/useTerminalSize.ts +0 -68
- package/src/ui/hooks/useTerminalWidth.ts +0 -5
- package/src/ui/hooks/useWindowedList.ts +0 -118
- package/src/ui/render-debugger.ts +0 -621
- package/src/ui/test.ts +0 -189
- package/src/ui/themes/ThemeManager.ts +0 -332
- package/src/ui/themes/aegisTheme.ts +0 -87
- package/src/ui/themes/darkTheme.ts +0 -87
- package/src/ui/themes/defaultTheme.ts +0 -85
- package/src/ui/themes/index.ts +0 -10
- package/src/ui/themes/lightTheme.ts +0 -89
- package/src/ui/themes/popularThemes.ts +0 -187
- package/src/ui/themes/types.ts +0 -130
- package/src/utils/clipboard.ts +0 -48
- package/src/utils/debug.ts +0 -43
- package/src/utils/environment.ts +0 -68
- package/src/utils/index.ts +0 -10
|
@@ -1,203 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,287 +0,0 @@
|
|
|
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
|
-
}
|