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.
Files changed (209) hide show
  1. package/bin/aegis.js +165 -0
  2. package/package.json +18 -86
  3. package/LICENSE +0 -21
  4. package/README.md +0 -394
  5. package/dist/main.js +0 -2171
  6. package/gui/run-gui.cjs +0 -13
  7. package/src/agent/Agent.ts +0 -903
  8. package/src/agent/SimpleAgent.ts +0 -48
  9. package/src/agent/index.ts +0 -54
  10. package/src/agent/orchestrator/AppBuilder.ts +0 -443
  11. package/src/agent/orchestrator/CouncilAgent.ts +0 -310
  12. package/src/agent/orchestrator/DiscussionRoom.ts +0 -462
  13. package/src/agent/orchestrator/OrchestratorAgent.ts +0 -637
  14. package/src/agent/orchestrator/index.ts +0 -38
  15. package/src/agent/orchestrator/utils.ts +0 -397
  16. package/src/agent/pricing.ts +0 -115
  17. package/src/agent/router.ts +0 -74
  18. package/src/agent/routerStats.ts +0 -121
  19. package/src/agent/types.ts +0 -318
  20. package/src/auth/login.ts +0 -383
  21. package/src/cli/config.ts +0 -189
  22. package/src/cli/index.ts +0 -17
  23. package/src/cli/middleware.ts +0 -119
  24. package/src/cli/types.ts +0 -75
  25. package/src/config/ConfigManager.ts +0 -587
  26. package/src/config/index.ts +0 -7
  27. package/src/config/types.ts +0 -584
  28. package/src/context/CompactionService.ts +0 -300
  29. package/src/context/ContextManager.ts +0 -450
  30. package/src/context/FileAnalyzer.ts +0 -267
  31. package/src/context/TokenCounter.ts +0 -265
  32. package/src/context/index.ts +0 -27
  33. package/src/context/storage/CacheStore.ts +0 -176
  34. package/src/context/storage/JSONLStore.ts +0 -201
  35. package/src/context/storage/MemoryStore.ts +0 -205
  36. package/src/context/storage/PersistentStore.ts +0 -327
  37. package/src/context/storage/index.ts +0 -9
  38. package/src/context/storage/pathUtils.ts +0 -114
  39. package/src/context/test.ts +0 -309
  40. package/src/context/types.ts +0 -268
  41. package/src/hooks/HookExecutor.ts +0 -434
  42. package/src/hooks/HookManager.ts +0 -596
  43. package/src/hooks/HookService.ts +0 -269
  44. package/src/hooks/Matcher.ts +0 -157
  45. package/src/hooks/index.ts +0 -63
  46. package/src/hooks/types.ts +0 -424
  47. package/src/main.tsx +0 -596
  48. package/src/mcp/HealthMonitor.ts +0 -150
  49. package/src/mcp/McpClient.ts +0 -491
  50. package/src/mcp/McpRegistry.ts +0 -321
  51. package/src/mcp/createMcpTool.ts +0 -251
  52. package/src/mcp/index.ts +0 -15
  53. package/src/mcp/server.ts +0 -334
  54. package/src/mcp/test-server.ts +0 -88
  55. package/src/mcp/test.ts +0 -372
  56. package/src/mcp/types.ts +0 -247
  57. package/src/memory/AgentMemoryBus.ts +0 -432
  58. package/src/memory/CloudSync.ts +0 -99
  59. package/src/memory/DriveSync.ts +0 -106
  60. package/src/memory/SharedMemory.ts +0 -951
  61. package/src/memory/index.ts +0 -14
  62. package/src/memory/machineFingerprint.ts +0 -40
  63. package/src/orchestrator/SubAgentMetadata.ts +0 -136
  64. package/src/prompts/builder.ts +0 -213
  65. package/src/prompts/default.ts +0 -144
  66. package/src/prompts/index.ts +0 -16
  67. package/src/prompts/plan.ts +0 -64
  68. package/src/prompts/test.ts +0 -78
  69. package/src/services/AnthropicChatService.ts +0 -341
  70. package/src/services/ChatService.ts +0 -347
  71. package/src/services/ClaudeCliChatService.ts +0 -256
  72. package/src/services/CloudSync.ts +0 -168
  73. package/src/services/CostLedger.ts +0 -211
  74. package/src/services/Heartbeat.ts +0 -135
  75. package/src/services/LearningCollector.ts +0 -291
  76. package/src/services/OllamaInstaller.ts +0 -342
  77. package/src/services/VersionChecker.ts +0 -445
  78. package/src/services/index.ts +0 -57
  79. package/src/services/streaming/OpenAIEventAdapter.ts +0 -126
  80. package/src/services/streaming/RenderingProfile.ts +0 -90
  81. package/src/services/streaming/StreamEventParser.ts +0 -181
  82. package/src/services/streaming/ThrottledRenderer.ts +0 -139
  83. package/src/services/streaming/TranscriptBuffer.ts +0 -574
  84. package/src/services/streaming/eventStatusMap.ts +0 -52
  85. package/src/services/streaming/index.ts +0 -46
  86. package/src/services/streaming/renderFormatting.ts +0 -79
  87. package/src/services/streaming/types.ts +0 -234
  88. package/src/skills/SkillLoader.ts +0 -126
  89. package/src/skills/SkillRegistry.ts +0 -366
  90. package/src/skills/index.ts +0 -48
  91. package/src/skills/types.ts +0 -146
  92. package/src/slash-commands/billing.ts +0 -70
  93. package/src/slash-commands/build.ts +0 -413
  94. package/src/slash-commands/builtinCommands.ts +0 -2733
  95. package/src/slash-commands/clone.ts +0 -242
  96. package/src/slash-commands/council.ts +0 -125
  97. package/src/slash-commands/custom/CustomCommandExecutor.ts +0 -143
  98. package/src/slash-commands/custom/CustomCommandLoader.ts +0 -251
  99. package/src/slash-commands/custom/CustomCommandRegistry.ts +0 -144
  100. package/src/slash-commands/custom/index.ts +0 -7
  101. package/src/slash-commands/debate.ts +0 -254
  102. package/src/slash-commands/gmail.ts +0 -105
  103. package/src/slash-commands/index.ts +0 -388
  104. package/src/slash-commands/mcpCommand.ts +0 -205
  105. package/src/slash-commands/types.ts +0 -201
  106. package/src/store/index.ts +0 -76
  107. package/src/store/selectors.ts +0 -246
  108. package/src/store/slices/appSlice.ts +0 -205
  109. package/src/store/slices/commandSlice.ts +0 -115
  110. package/src/store/slices/configSlice.ts +0 -46
  111. package/src/store/slices/focusSlice.ts +0 -64
  112. package/src/store/slices/index.ts +0 -9
  113. package/src/store/slices/sessionSlice.ts +0 -424
  114. package/src/store/streaming-buffer.ts +0 -425
  115. package/src/store/test.ts +0 -296
  116. package/src/store/types.ts +0 -274
  117. package/src/store/vanilla.ts +0 -186
  118. package/src/tools/builtin/bash.ts +0 -236
  119. package/src/tools/builtin/council.ts +0 -105
  120. package/src/tools/builtin/edit.ts +0 -213
  121. package/src/tools/builtin/glob.ts +0 -136
  122. package/src/tools/builtin/grep.ts +0 -263
  123. package/src/tools/builtin/index.ts +0 -61
  124. package/src/tools/builtin/memory.ts +0 -66
  125. package/src/tools/builtin/read.ts +0 -168
  126. package/src/tools/builtin/skill.ts +0 -97
  127. package/src/tools/builtin/snapshot.ts +0 -40
  128. package/src/tools/builtin/task.ts +0 -106
  129. package/src/tools/builtin/write.ts +0 -134
  130. package/src/tools/createTool.ts +0 -221
  131. package/src/tools/execution/ExecutionPipeline.ts +0 -263
  132. package/src/tools/execution/index.ts +0 -40
  133. package/src/tools/execution/stages/CacheStage.ts +0 -131
  134. package/src/tools/execution/stages/ConfirmationStage.ts +0 -203
  135. package/src/tools/execution/stages/DiscoveryStage.ts +0 -46
  136. package/src/tools/execution/stages/ExecutionStage.ts +0 -48
  137. package/src/tools/execution/stages/FormattingStage.ts +0 -44
  138. package/src/tools/execution/stages/HookStage.ts +0 -72
  139. package/src/tools/execution/stages/PermissionStage.ts +0 -287
  140. package/src/tools/execution/stages/PostHookStage.ts +0 -71
  141. package/src/tools/execution/stages/index.ts +0 -12
  142. package/src/tools/execution/test.ts +0 -266
  143. package/src/tools/execution/types.ts +0 -273
  144. package/src/tools/index.ts +0 -81
  145. package/src/tools/registry.ts +0 -304
  146. package/src/tools/schemas.ts +0 -109
  147. package/src/tools/test.ts +0 -220
  148. package/src/tools/types.ts +0 -175
  149. package/src/tools/validation/PermissionChecker.ts +0 -242
  150. package/src/tools/validation/SensitiveFileDetector.ts +0 -210
  151. package/src/tools/validation/index.ts +0 -11
  152. package/src/ui/App.tsx +0 -166
  153. package/src/ui/components/AegisInterface.tsx +0 -484
  154. package/src/ui/components/common/ChatSearch.tsx +0 -150
  155. package/src/ui/components/common/ErrorBoundary.tsx +0 -82
  156. package/src/ui/components/common/ExitMessage.tsx +0 -120
  157. package/src/ui/components/common/LoadingIndicator.tsx +0 -49
  158. package/src/ui/components/common/index.ts +0 -6
  159. package/src/ui/components/dialog/ConfirmationPrompt.tsx +0 -208
  160. package/src/ui/components/dialog/InteractiveSelector.tsx +0 -149
  161. package/src/ui/components/dialog/SetupWizard.tsx +0 -297
  162. package/src/ui/components/dialog/UpdatePrompt.tsx +0 -155
  163. package/src/ui/components/dialog/index.ts +0 -8
  164. package/src/ui/components/index.ts +0 -28
  165. package/src/ui/components/input/CommandSuggestions.tsx +0 -139
  166. package/src/ui/components/input/CustomTextInput.tsx +0 -220
  167. package/src/ui/components/input/InputArea.tsx +0 -361
  168. package/src/ui/components/input/PromptSuggestions.tsx +0 -66
  169. package/src/ui/components/input/index.ts +0 -6
  170. package/src/ui/components/layout/ChatStatusBar.tsx +0 -90
  171. package/src/ui/components/layout/ContextBar.tsx +0 -79
  172. package/src/ui/components/layout/MessageArea.tsx +0 -96
  173. package/src/ui/components/layout/MessageList.tsx +0 -647
  174. package/src/ui/components/layout/MessageSeparator.tsx +0 -26
  175. package/src/ui/components/layout/WelcomeMessage.tsx +0 -93
  176. package/src/ui/components/layout/index.ts +0 -7
  177. package/src/ui/components/markdown/CodeHighlighter.tsx +0 -292
  178. package/src/ui/components/markdown/MessageRenderer.tsx +0 -1211
  179. package/src/ui/components/markdown/index.ts +0 -8
  180. package/src/ui/components/markdown/parser.ts +0 -336
  181. package/src/ui/components/markdown/types.ts +0 -66
  182. package/src/ui/focus/FocusManager.ts +0 -137
  183. package/src/ui/focus/index.ts +0 -13
  184. package/src/ui/focus/types.ts +0 -54
  185. package/src/ui/focus/useFocus.ts +0 -75
  186. package/src/ui/hooks/index.ts +0 -11
  187. package/src/ui/hooks/useAgent.ts +0 -284
  188. package/src/ui/hooks/useCommandHistory.ts +0 -87
  189. package/src/ui/hooks/useCommandProcessor.ts +0 -443
  190. package/src/ui/hooks/useConfirmation.ts +0 -99
  191. package/src/ui/hooks/useCtrlCHandler.ts +0 -100
  192. package/src/ui/hooks/useInputBuffer.ts +0 -122
  193. package/src/ui/hooks/useTerminalSize.ts +0 -68
  194. package/src/ui/hooks/useTerminalWidth.ts +0 -5
  195. package/src/ui/hooks/useWindowedList.ts +0 -118
  196. package/src/ui/render-debugger.ts +0 -621
  197. package/src/ui/test.ts +0 -189
  198. package/src/ui/themes/ThemeManager.ts +0 -332
  199. package/src/ui/themes/aegisTheme.ts +0 -87
  200. package/src/ui/themes/darkTheme.ts +0 -87
  201. package/src/ui/themes/defaultTheme.ts +0 -85
  202. package/src/ui/themes/index.ts +0 -10
  203. package/src/ui/themes/lightTheme.ts +0 -89
  204. package/src/ui/themes/popularThemes.ts +0 -187
  205. package/src/ui/themes/types.ts +0 -130
  206. package/src/utils/clipboard.ts +0 -48
  207. package/src/utils/debug.ts +0 -43
  208. package/src/utils/environment.ts +0 -68
  209. package/src/utils/index.ts +0 -10
@@ -1,903 +0,0 @@
1
- /**
2
- * Agent 核心类 - 无状态设计
3
- *
4
- *
5
- * 1. Agent 本身不保存任何会话状态(sessionId, messages 等)
6
- * 2. 所有状态通过 context 参数传入
7
- * 3. Agent 实例可以每次命令创建,用完即弃
8
- * 4. 历史连续性由外部 SessionContext 保证
9
- */
10
-
11
- import type {
12
- AgentConfig,
13
- AgentOptions,
14
- ChatContext,
15
- LoopOptions,
16
- LoopResult,
17
- Message,
18
- IChatService,
19
- ToolCall,
20
- ToolResult,
21
- ToolDefinition,
22
- StreamCallbacks,
23
- } from './types.js';
24
- import { createChatService } from '../services/ChatService.js';
25
- import { buildSystemPrompt } from '../prompts/builder.js';
26
- import { createPlanModeReminder } from '../prompts/plan.js';
27
- import { LOCAL_SYSTEM_PROMPT } from '../prompts/default.js';
28
- import { isLocalOllamaUrl } from '../services/OllamaInstaller.js';
29
- import {
30
- ExecutionPipeline,
31
- ToolRegistry,
32
- createToolRegistry,
33
- getBuiltinTools,
34
- PermissionMode,
35
- type PipelineExecutionContext,
36
- } from '../tools/index.js';
37
- import { configManager } from '../config/ConfigManager.js';
38
- import { McpRegistry } from '../mcp/index.js';
39
- import { agentDebug } from '../utils/debug.js';
40
- import { onStop } from '../hooks/index.js';
41
- import { sharedMemory, setOllamaBaseUrl } from '../memory/SharedMemory.js';
42
- import { syncSessionToDrive } from '../memory/DriveSync.js';
43
- import { ensureOllama } from '../services/OllamaInstaller.js';
44
- import { TokenCounter } from '../context/TokenCounter.js';
45
- import { trackInteraction } from '../services/Heartbeat.js';
46
- import { extractLearning, pushLearnings } from '../services/LearningCollector.js';
47
- import type { Learning } from '../services/LearningCollector.js';
48
- import { recordCost, startCostLedger } from '../services/CostLedger.js';
49
-
50
- // Buffer of extracted learnings awaiting a cloud push
51
- const pendingLearnings: Learning[] = [];
52
-
53
- // Flush remaining learnings on process exit (best-effort)
54
- process.once('beforeExit', () => {
55
- if (pendingLearnings.length > 0) {
56
- void pushLearnings(pendingLearnings.splice(0));
57
- }
58
- });
59
- import fs from 'fs/promises';
60
-
61
- // ========== Hallucination logger ===
62
-
63
- /** Hallucination log path — written as JSONL in debug mode so the dev can grep patterns. */
64
- const HALLUCINATION_LOG_PATH = process.env.AEGIS_HALLUCINATION_LOG || '';
65
-
66
- interface HallucinationLogEntry {
67
- ts: string;
68
- sessionId: string;
69
- family: HallucinationFamily;
70
- snippet: string; // first 200 chars of the matched text
71
- model: string;
72
- turn: number;
73
- }
74
-
75
- function writeHallucinationLog(entry: HallucinationLogEntry): void {
76
- if (!HALLUCINATION_LOG_PATH) return;
77
- const line = JSON.stringify(entry) + '\n';
78
- fs.appendFile(HALLUCINATION_LOG_PATH, line, 'utf-8').catch(() => {});
79
- }
80
-
81
- function logHallucination(
82
- families: HallucinationFamily[],
83
- content: string,
84
- sessionId: string,
85
- model: string,
86
- turn: number,
87
- ): void {
88
- for (const family of families) {
89
- agentDebug.warn(`[Hallucination] ${family} detected (turn ${turn}, model ${model})`);
90
- writeHallucinationLog({
91
- ts: new Date().toISOString(),
92
- sessionId,
93
- family,
94
- snippet: content.slice(0, 200).replace(/\n/g, '\\n'),
95
- model,
96
- turn,
97
- });
98
- }
99
- }
100
-
101
- /** 轮次上限(Infinity = 无限制) */
102
- const TURN_LIMIT = Infinity;
103
-
104
- /** 意图未完成检测模式 */
105
- const INCOMPLETE_INTENT_PATTERNS = [
106
- /:\s*$/, // 中文冒号结
107
- /:\s*$/, // 英文冒号结
108
- /\.\.\.\s*$/, // 省略号结
109
- /让我(先|来|开始|查看|检查|修复)/, // 中文意图
110
- /Let me (first|start|check|look|fix)/i, // 英文意图
111
- ];
112
-
113
- /**
114
- * Hallucination detection — multi-pattern matcher for known false-claim families.
115
- *
116
- * All patterns here target responses *without* tool calls. If the model actually
117
- * issues the tool call, the action is real regardless of accompanying text.
118
- *
119
- * Pattern families:
120
- * 1. Permission-dialog hallucination ("click Allow", "approve the prompt")
121
- * 2. Sandbox/workspace boundary hallucination ("scoped to X", "can only access Y")
122
- * 3. False config-file hallucination ("~/.claude/settings.json has sandbox disabled")
123
- * 4. False tool-capability hallucination ("I can't read outside the project")
124
- */
125
- const HALLUCINATED_PERMISSION_BLOCK_PATTERN = new RegExp(
126
- // Family 1: Permission-dialog hallucination
127
- '\\b(permission|approv\\w*)\\b[^\\n]{0,150}\\b(blocked|stuck|denied|approve|dialog|click|allow|grant|settings\\.json|prompted)\\b' +
128
- '|\\b(blocked|stuck|denied)\\b[^\\n]{0,150}\\b(permission|approv\\w*)\\b' +
129
- '|click\\s+(\\*\\*)?Allow(\\*\\*)?\\b' +
130
- // Family 2: Sandbox/workspace boundary hallucination
131
- '|\\b(sandbox\\w*|confine\\w*|restrict\\w*)\\b[^\\n]{0,80}\\b(session|director|workspace|project|path|access|scope|limit)\\b' +
132
- '|\\b(session|workspace|project)\\b[^\\n]{0,80}\\b(sandbox\\w*|confine\\w*|restrict\\w*)\\b' +
133
- '|\\b(scop\\w*|limit\\w*)\\b[^\\n]{0,60}\\b(tool|read|write|edit|access)\\b[^\\n]{0,60}\\b(director|project|workspace|path)\\b',
134
- 'i'
135
- );
136
-
137
- /** Family 3: False config/settings claims — e.g. "~/.claude/settings.json disables sandbox" */
138
- const HALLUCINATED_CONFIG_PATTERN = new RegExp(
139
- // Unverified claims about ~/.claude/settings.json or similar
140
- '~[/\\\\]\\.claude[/\\\\]settings\\.json' +
141
- '|\\.claude\\b[^\\n]{0,80}\\bsettings\\.json\\b' +
142
- '|(settings\\.json|claude_desktop_config\\.json)\\b[^\\n]{0,80}\\b(sandbox\\w*|restrict\\w*|enabl\\w*|disabl\\w*|false|true)\\b' +
143
- '|\\b(sandbox|scope|restrict)\\w*\\b[^\\n]{0,80}\\b(false|disabled|enabled|off|on)\\b' +
144
- // Cursor/Windsurf/Sonnet-specific config hallucinations
145
- '\\b(cursor|windsurf|sonnet)\\b[^\\n]{0,60}\\b(sandbox|restrict|confine)\\w*\\b',
146
- 'i'
147
- );
148
-
149
- /** Family 4: False tool-capability denial — "I can't/am not able to access that" */
150
- const HALLUCINATED_DENIAL_PATTERN = new RegExp(
151
- '\\b(I|my|tools?)\\b[^\\n]{0,40}\\b(can\'t|cannot|not able|unable|don\'t have access|no access|restricted from)\\b' +
152
- '[^\\n]{0,80}\\b(path|file|director|home|desktop|outside|system|config|tool)\\b' +
153
- '|\\b(path|file|director|access|tool)\\b[^\\n]{0,40}\\b(limited|restricted|scoped|confined)\\b[^\\n]{0,40}\\b(current|project|workspace|director)\\b',
154
- 'i'
155
- );
156
-
157
- /**
158
- * Classify a detected hallucination by family for logging and stats.
159
- */
160
- type HallucinationFamily = 'permission_dialog' | 'sandbox_boundary' | 'false_config' | 'tool_denial';
161
- function classifyHallucination(content: string): HallucinationFamily[] {
162
- const families: HallucinationFamily[] = [];
163
- if (/click\s+(\*\*)?Allow(\*\*)?\b|permission[^]{0,150}block|blocked[^]{0,150}permission/i.test(content))
164
- families.push('permission_dialog');
165
- if (/\b(sandbox|confine|restrict)\w*[^]{0,80}\b(session|workspace|project)\b/i.test(content))
166
- families.push('sandbox_boundary');
167
- if (/~\/\.claude|settings\.json[^]{0,80}(sandbox|false|disabled)/i.test(content))
168
- families.push('false_config');
169
- if (/\b(can't|cannot|unable)[^]{0,80}\b(access|read|write|path)[^]{0,80}\b(outside|desktop|home)\b/i.test(content))
170
- families.push('tool_denial');
171
- return families;
172
- }
173
-
174
- // ========== Agent
175
-
176
- export class Agent {
177
- // 配置(只
178
- private config: AgentConfig;
179
- private runtimeOptions: AgentOptions;
180
-
181
- // 初始化状
182
- private isInitialized = false;
183
-
184
- // 核心组
185
- private chatService!: IChatService;
186
- private systemPrompt: string;
187
-
188
- // 工具系
189
- private toolRegistry!: ToolRegistry;
190
- private executionPipeline!: ExecutionPipeline;
191
-
192
- /** Stable session ID reused across all chat() calls — enables cross-session memory */
193
- private agentSessionId!: string;
194
-
195
- /**
196
- *
197
- */
198
- private constructor(config: AgentConfig, options: AgentOptions = {}) {
199
- this.config = config;
200
- this.runtimeOptions = options;
201
- this.systemPrompt = ''; // 在 initialize() 中构
202
- }
203
-
204
- // ========== 静态工厂方
205
-
206
- /**
207
- *
208
- *
209
- * @param config Agent 配置
210
- * @param options 运行时选项
211
- */
212
- static async create(
213
- config: AgentConfig,
214
- options: AgentOptions = {}
215
- ): Promise<Agent> {
216
- // 验证配
217
- if (!config.apiKey) {
218
- throw new Error('❌ API Key 未配置');
219
- }
220
-
221
- // 创建并初始
222
- const agent = new Agent(config, options);
223
- await agent.initialize();
224
-
225
- return agent;
226
- }
227
-
228
- // ========== 初始
229
-
230
- /**
231
- *
232
- */
233
- private async initialize(): Promise<void> {
234
- if (this.isInitialized) return;
235
-
236
- try {
237
- // 1. 构建系统提示词(使用四层架
238
- if (isLocalOllamaUrl(this.config.baseURL)) {
239
- this.systemPrompt = LOCAL_SYSTEM_PROMPT;
240
- } else {
241
- const promptResult = await buildSystemPrompt({
242
- projectPath: process.cwd(),
243
- replaceDefault: this.config.systemPrompt,
244
- includeEnvironment: true,
245
- });
246
- this.systemPrompt = promptResult.prompt;
247
- }
248
-
249
- // 2. 创
250
- const defaultPermissionMode = configManager.getDefaultPermissionMode();
251
- const thinkingBudget = configManager.getThinkingBudget();
252
- this.chatService = createChatService({
253
- apiKey: this.config.apiKey,
254
- baseURL: this.config.baseURL,
255
- model: this.config.model,
256
- permissionMode: defaultPermissionMode,
257
- thinkingBudget,
258
- maxOutputTokens: this.config.maxOutputTokens,
259
- });
260
-
261
- // Set Ollama base URL for cross-semantic memory embeddings
262
- setOllamaBaseUrl(this.config.baseURL);
263
-
264
- // Auto-install / start Ollama; may return a different model if the
265
- // requested one doesn't support tools (e.g. bare "llama3" → "llama3.2")
266
- const resolvedModel = await ensureOllama(this.config.baseURL, this.config.model);
267
- if (resolvedModel && resolvedModel !== this.config.model) {
268
- this.config.model = resolvedModel;
269
- // Rebuild chat service with the corrected model
270
- this.chatService = createChatService({
271
- apiKey: this.config.apiKey,
272
- baseURL: this.config.baseURL,
273
- model: this.config.model,
274
- permissionMode: defaultPermissionMode,
275
- thinkingBudget,
276
- maxOutputTokens: this.config.maxOutputTokens,
277
- });
278
- }
279
-
280
- // 3. 初始化工具系
281
- this.toolRegistry = createToolRegistry();
282
-
283
- // 注册内置工
284
- const builtinTools = getBuiltinTools();
285
- for (const tool of builtinTools) {
286
- this.toolRegistry.register(tool);
287
- }
288
-
289
- // 4. 注册 MCP 工具(如果启
290
- if (configManager.isMcpEnabled()) {
291
- await this.registerMcpTools();
292
- }
293
-
294
- // 5. 创建执行管道(使用 settings.json 中的权限配 + 模型级工具过滤
295
- const permissionConfig = configManager.getPermissionConfig();
296
- const defaultMode = defaultPermissionMode as 'default' | 'autoEdit' | 'yolo' | 'plan';
297
-
298
- // Hämta modellspecifika verktygsbegränsningar
299
- const modelRestrictions = configManager.getModelToolRestrictions(this.config.model);
300
-
301
- this.executionPipeline = new ExecutionPipeline(this.toolRegistry, {
302
- permissions: permissionConfig,
303
- defaultMode: this.mapPermissionMode(defaultMode),
304
- allowedTools: modelRestrictions.allowedTools,
305
- disallowedTools: modelRestrictions.disallowedTools,
306
- });
307
-
308
- this.isInitialized = true;
309
- } catch (error) {
310
- throw new Error(
311
- `Agent 初始化失败: ${error instanceof Error ? error.message : '未知错误'}`
312
- );
313
- }
314
- }
315
-
316
- // ========== 公共方
317
-
318
- /** Expose chat service so callers (e.g. auto-compact) can use the configured LLM. */
319
- getChatService(): IChatService {
320
- return this.chatService;
321
- }
322
-
323
- /**
324
- *
325
- *
326
- * @param message 用户消息
327
- * @param context 聊天上下文(包含历史消息、sessionId 等)
328
- * @param options 循环选项
329
- */
330
- async chat(
331
- message: string,
332
- context?: ChatContext,
333
- options?: LoopOptions
334
- ): Promise<string> {
335
- const result = await this.chatWithMetadata(message, context, options);
336
-
337
- if (!result.success) {
338
- if (result.error?.type === 'aborted') {
339
- return ''; // 用户中
340
- }
341
- throw new Error(result.error?.message || '执行失败');
342
- }
343
-
344
- return result.finalMessage || '';
345
- }
346
-
347
- /**
348
- * Same as chat(), but returns the full LoopResult (turn/tool-call counts,
349
- * token totals) instead of just the final text — used by headless callers
350
- * like --print --output-format json that need to report usage.
351
- */
352
- async chatWithMetadata(
353
- message: string,
354
- context?: ChatContext,
355
- options?: LoopOptions
356
- ): Promise<LoopResult> {
357
- if (!this.isInitialized) {
358
- throw new Error('Agent 未初始化,请使用 Agent.create() 创建实例');
359
- }
360
- // Use stable session ID for cross-session memory
361
- const sessionId = context?.sessionId || this.agentSessionId || `session-${Date.now()}`;
362
- this.agentSessionId = sessionId;
363
- const ctx: ChatContext = context || {
364
- sessionId,
365
- messages: [],
366
- };
367
-
368
- return this.executeLoop(message, ctx, options);
369
- }
370
-
371
- /**
372
- *
373
- */
374
- getConfig(): AgentConfig {
375
- return { ...this.config };
376
- }
377
-
378
- /**
379
- * Update requireConfirmation in place so toggling /confirm takes effect
380
- * immediately, without waiting on an async Agent rebuild that could race
381
- * against the next user message.
382
- */
383
- setRequireConfirmation(value: boolean | undefined): void {
384
- this.config.requireConfirmation = value;
385
- }
386
-
387
- // ========== 核心执行循
388
-
389
- /**
390
- *
391
- *
392
- *
393
- */
394
- private async executeLoop(
395
- message: string,
396
- context: ChatContext,
397
- options?: LoopOptions
398
- ): Promise<LoopResult> {
399
- // === 1. 准备阶
400
-
401
- // 构建消息历
402
- const messages: Message[] = [];
403
-
404
- // 添加系统提
405
- const isLocal = (this.config.baseURL || '').includes('11434');
406
- // Shared memory is NOT auto-injected here — a past session's mistakes
407
- // (or hallucinations) getting recalled as unconditional "context" on
408
- // every turn is exactly what caused models to repeat them. Memory is
409
- // opt-in via the Memory tool; the model calls it only when it decides
410
- // recalling past context would help.
411
- messages.push({ role: 'system', content: this.systemPrompt });
412
-
413
- // 添加历史消息(带 Token 上下文窗口截断)
414
- const maxTokens = this.config.maxContextTokens || 200000;
415
- const systemTokens = TokenCounter.countTokens([messages[0]], this.config.model || 'gpt-4o');
416
- const maxHistoryTokens = Math.min(maxTokens * 0.85, maxTokens - systemTokens - 5000); // 保留 15% 给 system+user+response
417
- const truncatedMessages = TokenCounter.truncateMessages(context.messages, this.config.model || 'gpt-4o', maxHistoryTokens);
418
- if (context.messages.length > truncatedMessages.length) {
419
- console.log(`[Agent] Context window: truncated ${context.messages.length} → ${truncatedMessages.length} messages (kept ${maxHistoryTokens.toLocaleString()} tokens)`);
420
- }
421
- messages.push(...truncatedMessages);
422
-
423
- // 添加当前用户消
424
- // Append language hint to user message
425
- const langHint = /[\u4e00-\u9fff]/.test(message) ? ' (respond in Chinese)' :
426
- /[\u00c0-\u017e\u00e0-\u00ff]/.test(message) ? ' (respond in the same language)' :
427
- ' (respond in English)';
428
- messages.push({ role: 'user', content: message + langHint });
429
- sharedMemory.add(message, 'aegis-cli', context.sessionId || 'default', [], 'user', true).catch(() => {});
430
- trackInteraction('user', message, context.sessionId, {
431
- model: this.config.model,
432
- provider: this.config.baseURL,
433
- });
434
-
435
- // === 2. 循环配
436
-
437
- // 计算最大轮
438
- const configuredMaxTurns =
439
- this.runtimeOptions.maxTurns ??
440
- options?.maxTurns ??
441
- this.config.maxTurns ??
442
- -1;
443
-
444
- // 特殊值处理:0 = 禁用对
445
- if (configuredMaxTurns === 0) {
446
- return {
447
- success: false,
448
- error: { type: 'chat_disabled', message: '对话功能已禁用' },
449
- };
450
- }
451
-
452
- // 应用安全上
453
- const maxTurns = configuredMaxTurns === -1
454
- ? TURN_LIMIT
455
- : Math.min(configuredMaxTurns, TURN_LIMIT);
456
-
457
- let turnsCount = 0;
458
- let recentRetries = 0;
459
- let consecutiveToolFailures = 0;
460
- const MAX_CONSECUTIVE_FAILURES = 3;
461
- const allToolResults: ToolResult[] = [];
462
-
463
- // 获取工具定
464
- const mode = context.permissionMode === 'plan' ? 'plan' : undefined;
465
- const functionDeclarations = this.toolRegistry.getFunctionDeclarationsByMode(mode);
466
- const tools: ToolDefinition[] = functionDeclarations.map(fn => ({
467
- type: 'function' as const,
468
- function: {
469
- name: fn.name,
470
- description: fn.description,
471
- parameters: fn.parameters as ToolDefinition['function']['parameters'],
472
- },
473
- }));
474
-
475
- // === 3. 核心循
476
- while (true) {
477
- // 3.1 检查中断信
478
- if (options?.signal?.aborted) {
479
- return { success: false, error: { type: 'aborted' } };
480
- }
481
-
482
- // 3.2 检查并压缩上下文
483
- // context compression will trim token usage when approaching limit
484
- // if (this.shouldCompact(context)) {
485
- // await this.compactContext(context);
486
- // }
487
-
488
- // 3.3 轮次计
489
- turnsCount++;
490
- options?.onTurnStart?.({ turn: turnsCount, maxTurns });
491
-
492
- // 3.4 构建流式回调(onToolCallStart 在执行阶段单独触发,此处不
493
- const streamCallbacks: StreamCallbacks = {
494
- onStreamEvent: options?.onStreamEvent,
495
- onContentDelta: options?.onContentDelta,
496
- onThinkingDelta: options?.onThinkingDelta,
497
- onToolCallDelta: options?.onToolCallDelta,
498
- };
499
-
500
- // 3.5 调用 LLM(流
501
- let turnResult;
502
- try {
503
- turnResult = await this.chatService.chat(
504
- messages,
505
- tools.length > 0 ? tools : undefined,
506
- options?.signal,
507
- streamCallbacks
508
- );
509
- } catch (error) {
510
- // 处理中
511
- if (error instanceof Error && error.name === 'AbortError') {
512
- return { success: false, error: { type: 'aborted' } };
513
- }
514
- return {
515
- success: false,
516
- error: {
517
- type: 'llm_error',
518
- message: error instanceof Error ? error.message : 'LLM 调用失败',
519
- cause: error instanceof Error ? error : undefined,
520
- },
521
- };
522
- }
523
-
524
- // Record cost for this LLM call — fire-and-forget, never blocks
525
- if (turnResult.usage) {
526
- recordCost(
527
- this.config.model || 'unknown',
528
- turnResult.usage.promptTokens || 0,
529
- turnResult.usage.completionTokens || 0,
530
- context.sessionId,
531
- );
532
- }
533
-
534
- // 3.6 通知 UI 显示完整内容(非流式回调或兜
535
- if (turnResult.content && options?.onContent) {
536
- options.onContent(turnResult.content);
537
- }
538
- if (turnResult.reasoningContent && options?.onThinking) {
539
- options.onThinking(turnResult.reasoningContent);
540
- }
541
-
542
- // 3.6 检查是否完成(无工具调
543
- if (!turnResult.toolCalls || turnResult.toolCalls.length === 0) {
544
- // 意图未完成检 — disabled for local Ollama: the check triggers on
545
- // conversational replies, causing a second LLM turn whose content
546
- // gets streamed into the same buffer → duplicate response in the UI.
547
- if (!isLocal && this.detectIncompleteIntent(turnResult.content) && recentRetries < 2) {
548
- recentRetries++;
549
- messages.push({
550
- role: 'user',
551
- content: '请执行你提到的操作,不要只是描述。',
552
- });
553
- continue;
554
- }
555
-
556
- if (
557
- turnResult.content &&
558
- !isLocal &&
559
- (
560
- HALLUCINATED_PERMISSION_BLOCK_PATTERN.test(turnResult.content) ||
561
- HALLUCINATED_CONFIG_PATTERN.test(turnResult.content) ||
562
- HALLUCINATED_DENIAL_PATTERN.test(turnResult.content)
563
- ) &&
564
- recentRetries < 3
565
- ) {
566
- const families = classifyHallucination(turnResult.content);
567
- logHallucination(families, turnResult.content, context.sessionId || 'unknown', this.config.model || 'unknown', turnsCount);
568
-
569
- recentRetries++;
570
- messages.push({
571
- role: 'user',
572
- content: '[System] You did not make a tool call — there is nothing pending, nothing was ' +
573
- 'denied, and there is no sandbox/workspace/config boundary restricting you to any directory. ' +
574
- 'You just described a permission system, sandbox restriction, or configuration file setting ' +
575
- 'that does not exist in this application. Issue the tool call now (e.g. Write/Read with the ' +
576
- 'requested file_path); any real confirmation prompt, if one is needed, appears automatically ' +
577
- 'the instant you call it. Do not write any more text about permissions, sandboxes, restrictions, ' +
578
- 'or config settings — call the tool in this response.',
579
- });
580
- continue;
581
- }
582
-
583
- // 执行 Stop Hook - 允许 Hook 强制继续执
584
- const shouldContinue = await onStop(
585
- 'end_turn',
586
- context.sessionId || 'unknown',
587
- process.cwd()
588
- );
589
-
590
- if (shouldContinue) {
591
- agentDebug.log('Stop hook requested to continue');
592
- messages.push({
593
- role: 'user',
594
- content: '[System] Hook requested continuation. Please continue.',
595
- });
596
- continue;
597
- }
598
-
599
- // Track assistant response in admin panel
600
- if (turnResult.content) {
601
- trackInteraction('assistant', turnResult.content, context.sessionId, {
602
- model: this.config.model,
603
- provider: this.config.baseURL,
604
- turns: turnsCount,
605
- tools: allToolResults.length,
606
- });
607
-
608
- // Extract and buffer learnings from important responses
609
- const learning = extractLearning(turnResult.content, 'assistant', context.sessionId, this.config.model);
610
- if (learning) {
611
- pendingLearnings.push(learning);
612
- // Flush every 20 learnings or on session end
613
- if (pendingLearnings.length >= 20) {
614
- void pushLearnings(pendingLearnings.splice(0));
615
- }
616
- }
617
- }
618
-
619
- // Save to shared memory
620
- if (turnResult.content && turnResult.content.length > 50) {
621
- sharedMemory.add(turnResult.content, 'aegis-cli', context.sessionId || 'default', [], 'assistant', true).catch(() => {});
622
- if (context.sessionId) {
623
- syncSessionToDrive(context.sessionId).catch(() => {});
624
- // Auto-summarize session if it has 5+ entries
625
- sharedMemory.summarizeAndStoreSession(
626
- context.sessionId,
627
- this.config.apiKey,
628
- this.config.baseURL,
629
- this.config.model
630
- ).catch(() => {});
631
- }
632
- }
633
- return {
634
- success: true,
635
- finalMessage: turnResult.content,
636
- metadata: {
637
- turnsCount,
638
- toolCallsCount: allToolResults.length,
639
- totalTokens: turnResult.usage?.totalTokens,
640
- },
641
- };
642
- }
643
-
644
- // 重置重试计
645
- recentRetries = 0;
646
-
647
- // 3.7 添加 assistant 消息到历
648
- const assistantMsg: any = {
649
- role: 'assistant',
650
- content: turnResult.content || '',
651
- tool_calls: turnResult.toolCalls,
652
- };
653
- if (turnResult.reasoningContent) {
654
- assistantMsg.reasoning_content = turnResult.reasoningContent;
655
- }
656
- messages.push(assistantMsg);
657
-
658
- // 3.8 执行每个工具调
659
- let turnHasFailure = false;
660
- for (const toolCall of turnResult.toolCalls) {
661
- if (toolCall.type !== 'function') continue;
662
-
663
- // 检查中
664
- if (options?.signal?.aborted) {
665
- return { success: false, error: { type: 'aborted' } };
666
- }
667
-
668
- // 通知 UI 工具调用开始(此处有完整的工具调用数
669
- options?.onToolCallStart?.(toolCall);
670
-
671
- // 执行工
672
- const result = await this.executeToolCall(toolCall, context);
673
- allToolResults.push(result);
674
-
675
- // 通知 UI 工具调用完
676
- options?.onToolResult?.(toolCall, result);
677
-
678
- // 用户拒绝 → 立即终止循环,不再继续思
679
- if (!result.success && result.error?.includes('User rejected')) {
680
- return {
681
- success: true,
682
- metadata: {
683
- turnsCount,
684
- toolCallsCount: allToolResults.length,
685
- },
686
- };
687
- }
688
-
689
- if (!result.success) turnHasFailure = true;
690
-
691
- // 添加工具结果到消息历
692
- messages.push({
693
- role: 'tool',
694
- tool_call_id: toolCall.id,
695
- name: toolCall.function.name,
696
- content: result.llmContent || result.displayContent || '',
697
- });
698
- }
699
-
700
- // 3.9 连续工具失败检测(防止无限重试循
701
- if (turnHasFailure) {
702
- consecutiveToolFailures++;
703
- if (consecutiveToolFailures >= MAX_CONSECUTIVE_FAILURES) {
704
- // 注入提示让 LLM 停止重
705
- messages.push({
706
- role: 'user',
707
- content: '[System] Multiple consecutive tool failures detected. Please stop retrying and explain the issue to the user.',
708
- });
709
- }
710
- } else {
711
- consecutiveToolFailures = 0;
712
- }
713
-
714
- // === 4. 轮次上限处
715
- if (turnsCount >= maxTurns) {
716
- // 询问用户是否继
717
- if (options?.onTurnLimitReached) {
718
- const response = await options.onTurnLimitReached({ turnsCount });
719
- if (response.continue) {
720
- // 用户选择继续:重置计数
721
- turnsCount = 0;
722
- continue;
723
- }
724
- }
725
-
726
- // 用户选择停止或非交互模
727
- return {
728
- success: false,
729
- error: {
730
- type: 'max_turns_exceeded',
731
- message: `达到最大轮次限制 (${maxTurns})`,
732
- },
733
- metadata: {
734
- turnsCount,
735
- toolCallsCount: allToolResults.length,
736
- },
737
- };
738
- }
739
- }
740
- }
741
-
742
- // ========== 私有方
743
-
744
- /**
745
- *
746
- */
747
- private detectIncompleteIntent(content: string | undefined): boolean {
748
- if (!content) return false;
749
- return INCOMPLETE_INTENT_PATTERNS.some(pattern => pattern.test(content));
750
- }
751
-
752
- /**
753
- *
754
- *
755
- *
756
- */
757
- private async executeToolCall(
758
- toolCall: ToolCall,
759
- context: ChatContext
760
- ): Promise<ToolResult> {
761
- // 解析工具参
762
- let params: Record<string, unknown>;
763
- try {
764
- params = JSON.parse(toolCall.function.arguments);
765
- } catch {
766
- return {
767
- success: false,
768
- error: `Invalid tool arguments: ${toolCall.function.arguments}`,
769
- displayContent: `❌ Invalid arguments for ${toolCall.function.name}`,
770
- llmContent: `Error: Failed to parse tool arguments as JSON.`,
771
- };
772
- }
773
-
774
- // 构建执行上下
775
- const pipelineContext: PipelineExecutionContext = {
776
- sessionId: context.sessionId,
777
- workspaceRoot: process.cwd(),
778
- permissionMode: this.mapPermissionMode(context.permissionMode),
779
- signal: context.signal,
780
- confirmationHandler: context.confirmationHandler,
781
- messageId: toolCall.id,
782
- requireConfirmation: this.config.requireConfirmation !== false,
783
- };
784
-
785
- // 通过执行管道执行工
786
- const result = await this.executionPipeline.execute(
787
- toolCall.function.name,
788
- params,
789
- pipelineContext
790
- );
791
-
792
- // 转换为 Agent 的 ToolResult 格
793
- return {
794
- success: result.success,
795
- displayContent: result.displayContent,
796
- llmContent: result.llmContent,
797
- error: result.error?.message,
798
- metadata: result.metadata,
799
- };
800
- }
801
-
802
- /**
803
- *
804
- */
805
- private mapPermissionMode(mode?: string): PermissionMode {
806
- switch (mode) {
807
- case 'autoEdit':
808
- return PermissionMode.AUTO_EDIT;
809
- case 'yolo':
810
- return PermissionMode.YOLO;
811
- case 'plan':
812
- return PermissionMode.PLAN;
813
- default:
814
- return PermissionMode.DEFAULT;
815
- }
816
- }
817
-
818
- /**
819
- *
820
- */
821
- getToolRegistry(): ToolRegistry {
822
- return this.toolRegistry;
823
- }
824
-
825
- /**
826
- *
827
- */
828
- getExecutionPipeline(): ExecutionPipeline {
829
- return this.executionPipeline;
830
- }
831
-
832
- /**
833
- *
834
- *
835
- *
836
- */
837
- private async registerMcpTools(): Promise<void> {
838
- try {
839
- // 1. 获取 MCP 服务器配
840
- const mcpServers = configManager.getMcpServers();
841
-
842
- if (Object.keys(mcpServers).length === 0) {
843
- return; // 没有配置任何 MCP 服务
844
- }
845
-
846
- agentDebug.log(`正在加载 MCP 服务器 (${Object.keys(mcpServers).length} 个)...`);
847
-
848
- // 2. 注册并连接服务
849
- const registry = McpRegistry.getInstance();
850
- await registry.registerServers(mcpServers);
851
-
852
- // 3. 获取工具并注册到 Agent 的工具注册
853
- const mcpTools = await registry.getAvailableTools();
854
-
855
- if (mcpTools.length > 0) {
856
- for (const tool of mcpTools) {
857
- try {
858
- this.toolRegistry.register(tool);
859
- } catch (error) {
860
- agentDebug.warn(`注册 MCP 工具 "${tool.name}" 失败:`, (error as Error).message);
861
- }
862
- }
863
- agentDebug.log(`已加载 ${mcpTools.length} 个 MCP 工具`);
864
- }
865
-
866
- // 4. 监听工具更新事
867
- // 注意:由于 McpRegistry 是单例,需要先移除旧监听器再添加新
868
- // 避免每次创建 Agent 时累积监听
869
- registry.removeAllListeners('toolsUpdated');
870
- registry.on('toolsUpdated', async () => {
871
- try {
872
- const currentNames = new Set(this.toolRegistry.getMcpTools().map(t => t.name));
873
- const freshTools = await registry.getAvailableTools();
874
- const freshNames = new Set(freshTools.map(t => t.name));
875
-
876
- // Remove tools that disappeared
877
- for (const name of currentNames) {
878
- if (!freshNames.has(name)) this.toolRegistry.unregisterMcpTool(name);
879
- }
880
-
881
- // Register tools that are new
882
- for (const tool of freshTools) {
883
- if (!currentNames.has(tool.name)) {
884
- try { this.toolRegistry.registerMcpTool(tool); } catch { /* already registered */ }
885
- }
886
- }
887
-
888
- agentDebug.log(`MCP hot-reload: ${this.toolRegistry.mcpSize} tools active`);
889
- } catch (err) {
890
- agentDebug.warn('MCP hot-reload failed:', (err as Error).message);
891
- }
892
- });
893
-
894
- } catch (error) {
895
- agentDebug.warn('MCP 工具加载失败:', (error as Error).message);
896
- // MCP 加载失败不应该阻止 Agent 启
897
- }
898
- }
899
- }
900
-
901
- // ========== 导
902
-
903
- export default Agent;