aegiscode 3.1.7 → 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 +555 -556
  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,450 @@
1
+ /**
2
+ *
3
+ *
4
+ *
5
+ */
6
+
7
+ import { nanoid } from 'nanoid';
8
+ import * as fs from 'node:fs';
9
+ import * as os from 'node:os';
10
+ import * as path from 'node:path';
11
+ import type {
12
+ ContextData,
13
+ ContextMessage,
14
+ ContextManagerOptions,
15
+ SystemContext,
16
+ WorkspaceContext,
17
+ CompactionResult,
18
+ } from './types.js';
19
+ import { MemoryStore, PersistentStore, CacheStore, getStorageRoot, detectGitBranch, detectGitRemote } from './storage/index.js';
20
+ import { TokenCounter } from './TokenCounter.js';
21
+ import { CompactionService } from './CompactionService.js';
22
+
23
+ export class ContextManager {
24
+ private readonly memory: MemoryStore;
25
+ private readonly persistent: PersistentStore;
26
+ private readonly cache: CacheStore;
27
+ private readonly options: ContextManagerOptions;
28
+
29
+ private currentSessionId: string | null = null;
30
+ private readonly pendingSaves: Array<Promise<void>> = [];
31
+
32
+ constructor(options: Partial<ContextManagerOptions> = {}) {
33
+ // 默认配
34
+ this.options = {
35
+ storage: {
36
+ maxMemorySize: 1000,
37
+ persistentPath: getStorageRoot(),
38
+ cacheSize: 100,
39
+ compressionEnabled: true,
40
+ ...options.storage,
41
+ },
42
+ defaultFilter: {
43
+ maxTokens: 32000,
44
+ maxMessages: 50,
45
+ timeWindow: 24 * 60 * 60 * 1000, // 24小
46
+ ...options.defaultFilter,
47
+ },
48
+ compressionThreshold: options.compressionThreshold || 100000, // 100k tokens
49
+ };
50
+
51
+ // 初始化存储
52
+ this.memory = new MemoryStore(this.options.storage.maxMemorySize);
53
+ this.persistent = new PersistentStore(process.cwd(), 100);
54
+ this.cache = new CacheStore(this.options.storage.cacheSize, 5 * 60 * 1000);
55
+ }
56
+
57
+ /**
58
+ *
59
+ */
60
+ async createSession(
61
+ userId?: string,
62
+ preferences: Record<string, unknown> = {},
63
+ configuration: Record<string, unknown> = {}
64
+ ): Promise<string> {
65
+ // 使用 nanoid 生成会话 ID,或使用提供
66
+ const sessionId = (configuration.sessionId as string) || nanoid();
67
+ const now = Date.now();
68
+
69
+ // 创建初始上下
70
+ const contextData: ContextData = {
71
+ layers: {
72
+ system: await this.createSystemContext(),
73
+ session: {
74
+ sessionId,
75
+ userId,
76
+ preferences,
77
+ configuration,
78
+ startTime: now,
79
+ },
80
+ conversation: {
81
+ messages: [],
82
+ topics: [],
83
+ lastActivity: now,
84
+ },
85
+ tool: {
86
+ recentCalls: [],
87
+ toolStates: {},
88
+ dependencies: {},
89
+ },
90
+ workspace: await this.createWorkspaceContext(),
91
+ },
92
+ metadata: {
93
+ totalTokens: 0,
94
+ priority: 1,
95
+ lastUpdated: now,
96
+ },
97
+ };
98
+
99
+ // 存储到内
100
+ this.memory.setContext(contextData);
101
+
102
+ this.currentSessionId = sessionId;
103
+ return sessionId;
104
+ }
105
+
106
+ /**
107
+ *
108
+ */
109
+ private async createSystemContext(): Promise<SystemContext> {
110
+ return {
111
+ osType: os.type(),
112
+ osVersion: os.release(),
113
+ shell: process.env.SHELL || 'unknown',
114
+ nodeVersion: process.version,
115
+ cwd: process.cwd(),
116
+ };
117
+ }
118
+
119
+ /**
120
+ *
121
+ */
122
+ private async createWorkspaceContext(): Promise<WorkspaceContext> {
123
+ const projectPath = process.cwd();
124
+
125
+ // 尝试读
126
+ let packageJson: WorkspaceContext['packageJson'];
127
+ try {
128
+ const { readFile } = await import('node:fs/promises');
129
+ const { join } = await import('node:path');
130
+ const content = await readFile(join(projectPath, 'package.json'), 'utf-8');
131
+ const pkg = JSON.parse(content);
132
+ packageJson = {
133
+ name: pkg.name,
134
+ version: pkg.version,
135
+ dependencies: pkg.dependencies,
136
+ };
137
+ } catch {
138
+ // 忽略错
139
+ }
140
+
141
+ return {
142
+ projectPath,
143
+ gitBranch: detectGitBranch(projectPath),
144
+ gitRemote: detectGitRemote(projectPath),
145
+ packageJson,
146
+ };
147
+ }
148
+
149
+ /**
150
+ *
151
+ */
152
+ async addMessage(
153
+ role: ContextMessage['role'],
154
+ content: string,
155
+ metadata?: Record<string, unknown>
156
+ ): Promise<void> {
157
+ if (!this.currentSessionId) {
158
+ throw new Error('没有活动会话');
159
+ }
160
+
161
+ const message: ContextMessage = {
162
+ id: nanoid(),
163
+ role,
164
+ content,
165
+ timestamp: Date.now(),
166
+ metadata,
167
+ };
168
+
169
+ // 添加到内
170
+ this.memory.addMessage(message);
171
+
172
+ // 检查是否需要压
173
+ const contextData = this.memory.getContext();
174
+ if (contextData && this.shouldCompress(contextData)) {
175
+ await this.compressCurrentContext();
176
+ }
177
+
178
+ // 持久化保存到 JSONL 文件
179
+ await this.saveMessagePersist(message);
180
+ }
181
+
182
+ /**
183
+ *
184
+ */
185
+ private shouldCompress(contextData: ContextData): boolean {
186
+ return contextData.metadata.totalTokens > this.options.compressionThreshold;
187
+ }
188
+
189
+ /**
190
+ * Persist message to JSONL synchronously (awaited by the caller).
191
+ * The promise is tracked so cleanup() can flush before exit.
192
+ */
193
+ private async saveMessagePersist(message: ContextMessage): Promise<void> {
194
+ if (!this.currentSessionId) return;
195
+ const promise = this.persistent.saveMessage(
196
+ this.currentSessionId!,
197
+ message.role as 'user' | 'assistant' | 'system',
198
+ message.content,
199
+ null,
200
+ message.metadata as any
201
+ ).catch(error => {
202
+ console.error('[ContextManager] 保存消息失败:', error);
203
+ });
204
+ this.pendingSaves.push(promise as Promise<void>);
205
+ await promise;
206
+ }
207
+
208
+ /**
209
+ * Flush all pending saves — call before exit to ensure no data loss.
210
+ */
211
+ async flush(): Promise<void> {
212
+ const pending = [...this.pendingSaves];
213
+ this.pendingSaves.length = 0;
214
+ await Promise.all(pending);
215
+ }
216
+
217
+ /**
218
+ *
219
+ */
220
+ async compressCurrentContext(): Promise<CompactionResult | null> {
221
+ const contextData = this.memory.getContext();
222
+ if (!contextData) {
223
+ return null;
224
+ }
225
+
226
+ const messages = contextData.layers.conversation.messages.map(m => ({
227
+ role: m.role as 'user' | 'assistant' | 'system' | 'tool',
228
+ content: m.content,
229
+ }));
230
+
231
+ const result = await CompactionService.compact(messages, {
232
+ trigger: 'auto',
233
+ modelName: (() => {
234
+ try {
235
+ const cfg = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.aegiscode', 'config.json'), 'utf8'));
236
+ return cfg?.default?.model || 'claude-sonnet-4-6';
237
+ } catch { return 'claude-sonnet-4-6'; }
238
+ })(),
239
+ maxContextTokens: this.options.compressionThreshold,
240
+ });
241
+
242
+ if (result.success) {
243
+ // 更新内存中的消
244
+ const newMessages: ContextMessage[] = result.compactedMessages.map(m => ({
245
+ id: nanoid(),
246
+ role: m.role as ContextMessage['role'],
247
+ content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
248
+ timestamp: Date.now(),
249
+ }));
250
+
251
+ this.memory.setMessages(newMessages);
252
+ this.memory.updateTokenCount(result.postTokens);
253
+
254
+ // 保存压缩记
255
+ if (this.currentSessionId) {
256
+ await this.persistent.saveCompaction(
257
+ this.currentSessionId,
258
+ result.summary,
259
+ {
260
+ trigger: 'auto',
261
+ preTokens: result.preTokens,
262
+ postTokens: result.postTokens,
263
+ filesIncluded: result.filesIncluded,
264
+ }
265
+ );
266
+ }
267
+ }
268
+
269
+ return result;
270
+ }
271
+
272
+ /**
273
+ *
274
+ */
275
+ async manualCompact(): Promise<CompactionResult | null> {
276
+ const contextData = this.memory.getContext();
277
+ if (!contextData) {
278
+ return null;
279
+ }
280
+
281
+ const messages = contextData.layers.conversation.messages.map(m => ({
282
+ role: m.role as 'user' | 'assistant' | 'system' | 'tool',
283
+ content: m.content,
284
+ }));
285
+
286
+ const result = await CompactionService.compact(messages, {
287
+ trigger: 'manual',
288
+ modelName: 'claude-sonnet-4-6',
289
+ maxContextTokens: this.options.compressionThreshold,
290
+ });
291
+
292
+ if (result.success) {
293
+ const newMessages: ContextMessage[] = result.compactedMessages.map(m => ({
294
+ id: nanoid(),
295
+ role: m.role as ContextMessage['role'],
296
+ content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
297
+ timestamp: Date.now(),
298
+ }));
299
+
300
+ this.memory.setMessages(newMessages);
301
+ this.memory.updateTokenCount(result.postTokens);
302
+
303
+ if (this.currentSessionId) {
304
+ await this.persistent.saveCompaction(
305
+ this.currentSessionId,
306
+ result.summary,
307
+ {
308
+ trigger: 'manual',
309
+ preTokens: result.preTokens,
310
+ postTokens: result.postTokens,
311
+ filesIncluded: result.filesIncluded,
312
+ }
313
+ );
314
+ }
315
+ }
316
+
317
+ return result;
318
+ }
319
+
320
+ /**
321
+ *
322
+ */
323
+ async loadSession(sessionId: string): Promise<boolean> {
324
+ try {
325
+ // 先尝试从内存加
326
+ let contextData = this.memory.getContext();
327
+
328
+ if (!contextData || contextData.layers.session.sessionId !== sessionId) {
329
+ // 从持久化存储加
330
+ const [session, conversation] = await Promise.all([
331
+ this.persistent.loadSession(sessionId),
332
+ this.persistent.loadConversation(sessionId),
333
+ ]);
334
+
335
+ if (!session || !conversation) {
336
+ return false;
337
+ }
338
+
339
+ // 重建完整的上下文数
340
+ contextData = {
341
+ layers: {
342
+ system: await this.createSystemContext(),
343
+ session,
344
+ conversation,
345
+ tool: { recentCalls: [], toolStates: {}, dependencies: {} },
346
+ workspace: await this.createWorkspaceContext(),
347
+ },
348
+ metadata: {
349
+ totalTokens: 0,
350
+ priority: 1,
351
+ lastUpdated: Date.now(),
352
+ },
353
+ };
354
+
355
+ this.memory.setContext(contextData);
356
+ }
357
+
358
+ this.currentSessionId = sessionId;
359
+ return true;
360
+ } catch (error) {
361
+ console.error('[ContextManager] 加载会话失败:', error);
362
+ return false;
363
+ }
364
+ }
365
+
366
+ /**
367
+ *
368
+ */
369
+ getCurrentSessionId(): string | null {
370
+ return this.currentSessionId;
371
+ }
372
+
373
+ /**
374
+ *
375
+ */
376
+ getContext(): ContextData | null {
377
+ return this.memory.getContext();
378
+ }
379
+
380
+ /**
381
+ *
382
+ */
383
+ getMessages(): ContextMessage[] {
384
+ return this.memory.getMessages();
385
+ }
386
+
387
+ /**
388
+ *
389
+ */
390
+ getTokenCount(): number {
391
+ return this.memory.getTokenCount();
392
+ }
393
+
394
+ /**
395
+ *
396
+ */
397
+ updateTokenCount(tokens: number): void {
398
+ this.memory.updateTokenCount(tokens);
399
+ }
400
+
401
+ /**
402
+ *
403
+ */
404
+ replaceMessages(messages: ContextMessage[]): void {
405
+ this.memory.setMessages(messages);
406
+ }
407
+
408
+ /**
409
+ *
410
+ */
411
+ getCache<T>(key: string): T | undefined {
412
+ return this.cache.get<T>(key);
413
+ }
414
+
415
+ /**
416
+ *
417
+ */
418
+ setCache<T>(key: string, value: T, ttl?: number): void {
419
+ this.cache.set(key, value, ttl);
420
+ }
421
+
422
+ /**
423
+ *
424
+ */
425
+ async listSessions(): Promise<string[]> {
426
+ return this.persistent.listSessions();
427
+ }
428
+
429
+ /**
430
+ *
431
+ */
432
+ async deleteSession(sessionId: string): Promise<void> {
433
+ await this.persistent.deleteSession(sessionId);
434
+
435
+ if (this.currentSessionId === sessionId) {
436
+ this.memory.clear();
437
+ this.currentSessionId = null;
438
+ }
439
+ }
440
+
441
+ /**
442
+ *
443
+ */
444
+ async cleanup(): Promise<void> {
445
+ await this.flush();
446
+ this.memory.clear();
447
+ this.cache.clear();
448
+ TokenCounter.clearCache();
449
+ }
450
+ }
@@ -0,0 +1,267 @@
1
+ /**
2
+ *
3
+ *
4
+ *
5
+ */
6
+
7
+ import * as fs from 'node:fs/promises';
8
+ import { existsSync } from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import type { Message } from '../agent/types.js';
11
+ import type { FileReference, FileContent } from './types.js';
12
+
13
+ export class FileAnalyzer {
14
+ /** 最多包含的文件数量 */
15
+ private static readonly MAX_FILES = 5;
16
+ /** 单个文件最大行数 */
17
+ private static readonly MAX_LINES_PER_FILE = 1000;
18
+ /** 单个文件最大字符数 */
19
+ private static readonly MAX_CHARS_PER_FILE = 50000;
20
+
21
+ /**
22
+ *
23
+ */
24
+ static analyzeFiles(messages: Message[]): FileReference[] {
25
+ const fileMap = new Map<string, FileReference>();
26
+
27
+ messages.forEach((msg, index) => {
28
+ // 从消息内容中提取文件路
29
+ if (msg.content) {
30
+ const content = typeof msg.content === 'string'
31
+ ? msg.content
32
+ : JSON.stringify(msg.content);
33
+ const contentFiles = this.extractFilePathsFromContent(content);
34
+ contentFiles.forEach(filePath => {
35
+ this.updateFileReference(fileMap, filePath, index, false);
36
+ });
37
+ }
38
+
39
+ // 从工具调用中提取文件路
40
+ if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
41
+ for (const call of msg.tool_calls) {
42
+ const toolFiles = this.extractFilePathsFromToolCall(call);
43
+ const wasModified = ['Write', 'Edit'].includes(call.function?.name || '');
44
+ toolFiles.forEach(filePath => {
45
+ this.updateFileReference(fileMap, filePath, index, wasModified);
46
+ });
47
+ }
48
+ }
49
+ });
50
+
51
+ // 按重要性排序:1. 是否被修改 2. 提及次数 3. 最近
52
+ return Array.from(fileMap.values())
53
+ .filter(ref => this.isValidFilePath(ref.path))
54
+ .sort((a, b) => {
55
+ if (a.wasModified !== b.wasModified) return a.wasModified ? -1 : 1;
56
+ if (a.mentions !== b.mentions) return b.mentions - a.mentions;
57
+ return b.lastMentioned - a.lastMentioned;
58
+ })
59
+ .slice(0, this.MAX_FILES);
60
+ }
61
+
62
+ /**
63
+ *
64
+ */
65
+ private static extractFilePathsFromContent(content: string): string[] {
66
+ const paths: string[] = [];
67
+
68
+ // 匹配常见的文件路径模
69
+ const patterns = [
70
+ // 绝对路
71
+ /(?:^|\s|["'`])(\/?(?:[\w.-]+\/)+[\w.-]+\.[a-zA-Z]{1,10})(?:\s|$|["'`]|:)/gm,
72
+ // 相对路
73
+ /(?:^|\s|["'`])(\.\/(?:[\w.-]+\/)*[\w.-]+\.[a-zA-Z]{1,10})(?:\s|$|["'`]|:)/gm,
74
+ // 代码块中的文件路
75
+ /```\d+:\d+:([\w./-]+)/gm,
76
+ ];
77
+
78
+ for (const pattern of patterns) {
79
+ let match;
80
+ while ((match = pattern.exec(content)) !== null) {
81
+ const filePath = match[1];
82
+ if (filePath && !paths.includes(filePath)) {
83
+ paths.push(filePath);
84
+ }
85
+ }
86
+ }
87
+
88
+ return paths;
89
+ }
90
+
91
+ /**
92
+ *
93
+ */
94
+ private static extractFilePathsFromToolCall(
95
+ toolCall: { function?: { name?: string; arguments?: string } }
96
+ ): string[] {
97
+ const paths: string[] = [];
98
+
99
+ const functionName = toolCall.function?.name;
100
+ let args: Record<string, unknown> = {};
101
+
102
+ try {
103
+ if (typeof toolCall.function?.arguments === 'string') {
104
+ args = JSON.parse(toolCall.function.arguments);
105
+ } else if (toolCall.function?.arguments) {
106
+ args = toolCall.function.arguments as Record<string, unknown>;
107
+ }
108
+ } catch {
109
+ return paths;
110
+ }
111
+
112
+ // 文件操作工
113
+ const fileTools = ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'NotebookEdit'];
114
+
115
+ if (fileTools.includes(functionName || '')) {
116
+ const pathKeys = ['file_path', 'path', 'notebook_path', 'filePath'];
117
+ for (const key of pathKeys) {
118
+ if (args[key] && typeof args[key] === 'string') {
119
+ paths.push(args[key] as string);
120
+ }
121
+ }
122
+ }
123
+
124
+ return paths;
125
+ }
126
+
127
+ /**
128
+ *
129
+ */
130
+ private static updateFileReference(
131
+ fileMap: Map<string, FileReference>,
132
+ filePath: string,
133
+ messageIndex: number,
134
+ wasModified: boolean
135
+ ): void {
136
+ const existing = fileMap.get(filePath);
137
+
138
+ if (existing) {
139
+ existing.mentions++;
140
+ existing.lastMentioned = Math.max(existing.lastMentioned, messageIndex);
141
+ existing.wasModified = existing.wasModified || wasModified;
142
+ } else {
143
+ fileMap.set(filePath, {
144
+ path: filePath,
145
+ mentions: 1,
146
+ lastMentioned: messageIndex,
147
+ wasModified,
148
+ });
149
+ }
150
+ }
151
+
152
+ /**
153
+ *
154
+ */
155
+ private static isValidFilePath(filePath: string): boolean {
156
+ // 排除常见的非文件路
157
+ const excludePatterns = [
158
+ /^https?:\/\//, // URL
159
+ /^node_modules\//, // node_modules
160
+ /^\.git\//, // git 目
161
+ /\.(png|jpg|jpeg|gif|svg|ico|webp|mp4|mp3|wav|pdf|zip|tar|gz)$/i, // 二进制文
162
+ ];
163
+
164
+ for (const pattern of excludePatterns) {
165
+ if (pattern.test(filePath)) {
166
+ return false;
167
+ }
168
+ }
169
+
170
+ // 检查文件是否存
171
+ try {
172
+ const absolutePath = path.isAbsolute(filePath)
173
+ ? filePath
174
+ : path.join(process.cwd(), filePath);
175
+ return existsSync(absolutePath);
176
+ } catch {
177
+ return false;
178
+ }
179
+ }
180
+
181
+ /**
182
+ *
183
+ */
184
+ static async readFilesContent(filePaths: string[]): Promise<FileContent[]> {
185
+ const results: FileContent[] = [];
186
+
187
+ for (const filePath of filePaths) {
188
+ try {
189
+ const absolutePath = path.isAbsolute(filePath)
190
+ ? filePath
191
+ : path.join(process.cwd(), filePath);
192
+
193
+ if (!existsSync(absolutePath)) {
194
+ continue;
195
+ }
196
+
197
+ const content = await fs.readFile(absolutePath, 'utf-8');
198
+ const lines = content.split('\n');
199
+
200
+ let truncated = false;
201
+ let finalContent = content;
202
+
203
+ // 检查行数限
204
+ if (lines.length > this.MAX_LINES_PER_FILE) {
205
+ finalContent = lines.slice(0, this.MAX_LINES_PER_FILE).join('\n');
206
+ truncated = true;
207
+ }
208
+
209
+ // 检查字符数限
210
+ if (finalContent.length > this.MAX_CHARS_PER_FILE) {
211
+ finalContent = finalContent.substring(0, this.MAX_CHARS_PER_FILE);
212
+ truncated = true;
213
+ }
214
+
215
+ if (truncated) {
216
+ finalContent += '\n\n[... 内容已截断 ...]';
217
+ }
218
+
219
+ results.push({
220
+ path: filePath,
221
+ content: finalContent,
222
+ lines: lines.length,
223
+ truncated,
224
+ });
225
+ } catch (error) {
226
+ console.warn(`[FileAnalyzer] 读取文件失败: ${filePath}`, error);
227
+ }
228
+ }
229
+
230
+ return results;
231
+ }
232
+
233
+ /**
234
+ *
235
+ */
236
+ static async getFileSummary(filePath: string): Promise<string | null> {
237
+ try {
238
+ const absolutePath = path.isAbsolute(filePath)
239
+ ? filePath
240
+ : path.join(process.cwd(), filePath);
241
+
242
+ if (!existsSync(absolutePath)) {
243
+ return null;
244
+ }
245
+
246
+ const stats = await fs.stat(absolutePath);
247
+ const content = await fs.readFile(absolutePath, 'utf-8');
248
+ const lines = content.split('\n');
249
+
250
+ return `文件: ${filePath}
251
+ 大小: ${this.formatFileSize(stats.size)}
252
+ 行数: ${lines.length}
253
+ 最后修改: ${stats.mtime.toISOString()}`;
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
258
+
259
+ /**
260
+ *
261
+ */
262
+ private static formatFileSize(bytes: number): string {
263
+ if (bytes < 1024) return `${bytes} B`;
264
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
265
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
266
+ }
267
+ }