aegiscode 3.1.8 → 3.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +23 -15
  2. package/dist/main.js +549 -552
  3. package/package.json +4 -2
  4. package/src/agent/Agent.ts +903 -0
  5. package/src/agent/SimpleAgent.ts +48 -0
  6. package/src/agent/index.ts +54 -0
  7. package/src/agent/orchestrator/AppBuilder.ts +443 -0
  8. package/src/agent/orchestrator/CouncilAgent.ts +310 -0
  9. package/src/agent/orchestrator/DiscussionRoom.ts +462 -0
  10. package/src/agent/orchestrator/OrchestratorAgent.ts +637 -0
  11. package/src/agent/orchestrator/index.ts +38 -0
  12. package/src/agent/orchestrator/utils.ts +397 -0
  13. package/src/agent/pricing.ts +115 -0
  14. package/src/agent/router.ts +74 -0
  15. package/src/agent/routerStats.ts +121 -0
  16. package/src/agent/types.ts +318 -0
  17. package/src/auth/login.ts +383 -0
  18. package/src/cli/config.ts +189 -0
  19. package/src/cli/index.ts +17 -0
  20. package/src/cli/middleware.ts +119 -0
  21. package/src/cli/types.ts +75 -0
  22. package/src/config/ConfigManager.ts +587 -0
  23. package/src/config/index.ts +7 -0
  24. package/src/config/types.ts +584 -0
  25. package/src/context/CompactionService.ts +300 -0
  26. package/src/context/ContextManager.ts +450 -0
  27. package/src/context/FileAnalyzer.ts +267 -0
  28. package/src/context/TokenCounter.ts +265 -0
  29. package/src/context/index.ts +27 -0
  30. package/src/context/storage/CacheStore.ts +176 -0
  31. package/src/context/storage/JSONLStore.ts +201 -0
  32. package/src/context/storage/MemoryStore.ts +205 -0
  33. package/src/context/storage/PersistentStore.ts +327 -0
  34. package/src/context/storage/index.ts +9 -0
  35. package/src/context/storage/pathUtils.ts +114 -0
  36. package/src/context/test.ts +309 -0
  37. package/src/context/types.ts +268 -0
  38. package/src/hooks/HookExecutor.ts +434 -0
  39. package/src/hooks/HookManager.ts +596 -0
  40. package/src/hooks/HookService.ts +269 -0
  41. package/src/hooks/Matcher.ts +157 -0
  42. package/src/hooks/index.ts +63 -0
  43. package/src/hooks/types.ts +424 -0
  44. package/src/main.tsx +596 -0
  45. package/src/mcp/HealthMonitor.ts +150 -0
  46. package/src/mcp/McpClient.ts +491 -0
  47. package/src/mcp/McpRegistry.ts +321 -0
  48. package/src/mcp/createMcpTool.ts +251 -0
  49. package/src/mcp/index.ts +15 -0
  50. package/src/mcp/server.ts +334 -0
  51. package/src/mcp/test-server.ts +88 -0
  52. package/src/mcp/test.ts +372 -0
  53. package/src/mcp/types.ts +247 -0
  54. package/src/memory/AgentMemoryBus.ts +432 -0
  55. package/src/memory/CloudSync.ts +99 -0
  56. package/src/memory/DriveSync.ts +106 -0
  57. package/src/memory/SharedMemory.ts +951 -0
  58. package/src/memory/index.ts +14 -0
  59. package/src/memory/machineFingerprint.ts +40 -0
  60. package/src/orchestrator/SubAgentMetadata.ts +136 -0
  61. package/src/prompts/builder.ts +213 -0
  62. package/src/prompts/default.ts +144 -0
  63. package/src/prompts/index.ts +16 -0
  64. package/src/prompts/plan.ts +64 -0
  65. package/src/prompts/test.ts +78 -0
  66. package/src/services/AnthropicChatService.ts +341 -0
  67. package/src/services/ChatService.ts +347 -0
  68. package/src/services/ClaudeCliChatService.ts +256 -0
  69. package/src/services/CloudSync.ts +168 -0
  70. package/src/services/CostLedger.ts +211 -0
  71. package/src/services/Heartbeat.ts +135 -0
  72. package/src/services/LearningCollector.ts +291 -0
  73. package/src/services/OllamaInstaller.ts +342 -0
  74. package/src/services/VersionChecker.ts +445 -0
  75. package/src/services/index.ts +57 -0
  76. package/src/services/streaming/OpenAIEventAdapter.ts +126 -0
  77. package/src/services/streaming/RenderingProfile.ts +90 -0
  78. package/src/services/streaming/StreamEventParser.ts +181 -0
  79. package/src/services/streaming/ThrottledRenderer.ts +139 -0
  80. package/src/services/streaming/TranscriptBuffer.ts +574 -0
  81. package/src/services/streaming/eventStatusMap.ts +52 -0
  82. package/src/services/streaming/index.ts +46 -0
  83. package/src/services/streaming/renderFormatting.ts +79 -0
  84. package/src/services/streaming/types.ts +234 -0
  85. package/src/skills/SkillLoader.ts +126 -0
  86. package/src/skills/SkillRegistry.ts +366 -0
  87. package/src/skills/index.ts +48 -0
  88. package/src/skills/types.ts +146 -0
  89. package/src/slash-commands/billing.ts +70 -0
  90. package/src/slash-commands/build.ts +413 -0
  91. package/src/slash-commands/builtinCommands.ts +2733 -0
  92. package/src/slash-commands/clone.ts +242 -0
  93. package/src/slash-commands/council.ts +125 -0
  94. package/src/slash-commands/custom/CustomCommandExecutor.ts +143 -0
  95. package/src/slash-commands/custom/CustomCommandLoader.ts +251 -0
  96. package/src/slash-commands/custom/CustomCommandRegistry.ts +144 -0
  97. package/src/slash-commands/custom/index.ts +7 -0
  98. package/src/slash-commands/debate.ts +254 -0
  99. package/src/slash-commands/gmail.ts +105 -0
  100. package/src/slash-commands/index.ts +388 -0
  101. package/src/slash-commands/mcpCommand.ts +205 -0
  102. package/src/slash-commands/types.ts +201 -0
  103. package/src/store/index.ts +76 -0
  104. package/src/store/selectors.ts +246 -0
  105. package/src/store/slices/appSlice.ts +205 -0
  106. package/src/store/slices/commandSlice.ts +115 -0
  107. package/src/store/slices/configSlice.ts +46 -0
  108. package/src/store/slices/focusSlice.ts +64 -0
  109. package/src/store/slices/index.ts +9 -0
  110. package/src/store/slices/sessionSlice.ts +424 -0
  111. package/src/store/streaming-buffer.ts +425 -0
  112. package/src/store/test.ts +296 -0
  113. package/src/store/types.ts +274 -0
  114. package/src/store/vanilla.ts +186 -0
  115. package/src/tools/builtin/bash.ts +236 -0
  116. package/src/tools/builtin/council.ts +105 -0
  117. package/src/tools/builtin/edit.ts +213 -0
  118. package/src/tools/builtin/glob.ts +136 -0
  119. package/src/tools/builtin/grep.ts +263 -0
  120. package/src/tools/builtin/index.ts +61 -0
  121. package/src/tools/builtin/memory.ts +66 -0
  122. package/src/tools/builtin/read.ts +168 -0
  123. package/src/tools/builtin/skill.ts +97 -0
  124. package/src/tools/builtin/snapshot.ts +40 -0
  125. package/src/tools/builtin/task.ts +106 -0
  126. package/src/tools/builtin/write.ts +134 -0
  127. package/src/tools/createTool.ts +221 -0
  128. package/src/tools/execution/ExecutionPipeline.ts +263 -0
  129. package/src/tools/execution/index.ts +40 -0
  130. package/src/tools/execution/stages/CacheStage.ts +131 -0
  131. package/src/tools/execution/stages/ConfirmationStage.ts +203 -0
  132. package/src/tools/execution/stages/DiscoveryStage.ts +46 -0
  133. package/src/tools/execution/stages/ExecutionStage.ts +48 -0
  134. package/src/tools/execution/stages/FormattingStage.ts +44 -0
  135. package/src/tools/execution/stages/HookStage.ts +72 -0
  136. package/src/tools/execution/stages/PermissionStage.ts +287 -0
  137. package/src/tools/execution/stages/PostHookStage.ts +71 -0
  138. package/src/tools/execution/stages/index.ts +12 -0
  139. package/src/tools/execution/test.ts +266 -0
  140. package/src/tools/execution/types.ts +273 -0
  141. package/src/tools/index.ts +81 -0
  142. package/src/tools/registry.ts +304 -0
  143. package/src/tools/schemas.ts +109 -0
  144. package/src/tools/test.ts +220 -0
  145. package/src/tools/types.ts +175 -0
  146. package/src/tools/validation/PermissionChecker.ts +242 -0
  147. package/src/tools/validation/SensitiveFileDetector.ts +210 -0
  148. package/src/tools/validation/index.ts +11 -0
  149. package/src/ui/App.tsx +166 -0
  150. package/src/ui/components/AegisInterface.tsx +484 -0
  151. package/src/ui/components/common/ChatSearch.tsx +150 -0
  152. package/src/ui/components/common/ErrorBoundary.tsx +82 -0
  153. package/src/ui/components/common/ExitMessage.tsx +120 -0
  154. package/src/ui/components/common/LoadingIndicator.tsx +49 -0
  155. package/src/ui/components/common/index.ts +6 -0
  156. package/src/ui/components/dialog/ConfirmationPrompt.tsx +208 -0
  157. package/src/ui/components/dialog/InteractiveSelector.tsx +149 -0
  158. package/src/ui/components/dialog/SetupWizard.tsx +297 -0
  159. package/src/ui/components/dialog/UpdatePrompt.tsx +155 -0
  160. package/src/ui/components/dialog/index.ts +8 -0
  161. package/src/ui/components/index.ts +28 -0
  162. package/src/ui/components/input/CommandSuggestions.tsx +139 -0
  163. package/src/ui/components/input/CustomTextInput.tsx +220 -0
  164. package/src/ui/components/input/InputArea.tsx +361 -0
  165. package/src/ui/components/input/PromptSuggestions.tsx +66 -0
  166. package/src/ui/components/input/index.ts +6 -0
  167. package/src/ui/components/layout/ChatStatusBar.tsx +90 -0
  168. package/src/ui/components/layout/ContextBar.tsx +79 -0
  169. package/src/ui/components/layout/MessageArea.tsx +96 -0
  170. package/src/ui/components/layout/MessageList.tsx +647 -0
  171. package/src/ui/components/layout/MessageSeparator.tsx +26 -0
  172. package/src/ui/components/layout/WelcomeMessage.tsx +93 -0
  173. package/src/ui/components/layout/index.ts +7 -0
  174. package/src/ui/components/markdown/CodeHighlighter.tsx +292 -0
  175. package/src/ui/components/markdown/MessageRenderer.tsx +1211 -0
  176. package/src/ui/components/markdown/index.ts +8 -0
  177. package/src/ui/components/markdown/parser.ts +336 -0
  178. package/src/ui/components/markdown/types.ts +66 -0
  179. package/src/ui/focus/FocusManager.ts +137 -0
  180. package/src/ui/focus/index.ts +13 -0
  181. package/src/ui/focus/types.ts +54 -0
  182. package/src/ui/focus/useFocus.ts +75 -0
  183. package/src/ui/hooks/index.ts +11 -0
  184. package/src/ui/hooks/useAgent.ts +284 -0
  185. package/src/ui/hooks/useCommandHistory.ts +87 -0
  186. package/src/ui/hooks/useCommandProcessor.ts +443 -0
  187. package/src/ui/hooks/useConfirmation.ts +99 -0
  188. package/src/ui/hooks/useCtrlCHandler.ts +100 -0
  189. package/src/ui/hooks/useInputBuffer.ts +122 -0
  190. package/src/ui/hooks/useTerminalSize.ts +68 -0
  191. package/src/ui/hooks/useTerminalWidth.ts +5 -0
  192. package/src/ui/hooks/useWindowedList.ts +118 -0
  193. package/src/ui/render-debugger.ts +621 -0
  194. package/src/ui/test.ts +189 -0
  195. package/src/ui/themes/ThemeManager.ts +332 -0
  196. package/src/ui/themes/aegisTheme.ts +87 -0
  197. package/src/ui/themes/darkTheme.ts +87 -0
  198. package/src/ui/themes/defaultTheme.ts +85 -0
  199. package/src/ui/themes/index.ts +10 -0
  200. package/src/ui/themes/lightTheme.ts +89 -0
  201. package/src/ui/themes/popularThemes.ts +187 -0
  202. package/src/ui/themes/types.ts +130 -0
  203. package/src/utils/clipboard.ts +48 -0
  204. package/src/utils/debug.ts +43 -0
  205. package/src/utils/environment.ts +68 -0
  206. package/src/utils/index.ts +10 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ *
3
+ *
4
+ *
5
+ */
6
+
7
+ import type { ContextData, ContextMessage, MemoryInfo, ToolCallRecord } from '../types.js';
8
+
9
+ export class MemoryStore {
10
+ private contextData: ContextData | null = null;
11
+ private readonly maxSize: number;
12
+ private readonly accessLog: Map<string, number> = new Map();
13
+
14
+ constructor(maxSize: number = 1000) {
15
+ this.maxSize = maxSize;
16
+ }
17
+
18
+ /**
19
+ *
20
+ */
21
+ setContext(data: ContextData): void {
22
+ this.contextData = data;
23
+ this.recordAccess('context');
24
+ }
25
+
26
+ /**
27
+ *
28
+ */
29
+ getContext(): ContextData | null {
30
+ this.recordAccess('context');
31
+ return this.contextData;
32
+ }
33
+
34
+ /**
35
+ *
36
+ */
37
+ hasData(): boolean {
38
+ return this.contextData !== null;
39
+ }
40
+
41
+ /**
42
+ *
43
+ */
44
+ addMessage(message: ContextMessage): void {
45
+ if (!this.contextData) {
46
+ throw new Error('上下文数据未初始化');
47
+ }
48
+
49
+ this.contextData.layers.conversation.messages.push(message);
50
+ this.contextData.layers.conversation.lastActivity = Date.now();
51
+ this.contextData.metadata.lastUpdated = Date.now();
52
+
53
+ // 检查是否超过大小限
54
+ this.enforceMemoryLimit();
55
+ this.recordAccess('messages');
56
+ }
57
+
58
+ /**
59
+ *
60
+ */
61
+ getMessages(): ContextMessage[] {
62
+ if (!this.contextData) {
63
+ return [];
64
+ }
65
+ this.recordAccess('messages');
66
+ return this.contextData.layers.conversation.messages;
67
+ }
68
+
69
+ /**
70
+ *
71
+ */
72
+ setMessages(messages: ContextMessage[]): void {
73
+ if (!this.contextData) {
74
+ throw new Error('上下文数据未初始化');
75
+ }
76
+ this.contextData.layers.conversation.messages = messages;
77
+ this.contextData.metadata.lastUpdated = Date.now();
78
+ }
79
+
80
+ /**
81
+ *
82
+ */
83
+ addToolCall(toolCall: ToolCallRecord): void {
84
+ if (!this.contextData) {
85
+ throw new Error('上下文数据未初始化');
86
+ }
87
+
88
+ this.contextData.layers.tool.recentCalls.push(toolCall);
89
+ this.contextData.metadata.lastUpdated = Date.now();
90
+
91
+ // 限制工具调用记录数
92
+ const maxToolCalls = 100;
93
+ if (this.contextData.layers.tool.recentCalls.length > maxToolCalls) {
94
+ this.contextData.layers.tool.recentCalls =
95
+ this.contextData.layers.tool.recentCalls.slice(-maxToolCalls);
96
+ }
97
+
98
+ this.recordAccess('toolCalls');
99
+ }
100
+
101
+ /**
102
+ *
103
+ */
104
+ updateToolCallResult(toolCallId: string, output: unknown, error?: string): void {
105
+ if (!this.contextData) return;
106
+
107
+ const toolCall = this.contextData.layers.tool.recentCalls.find(
108
+ tc => tc.id === toolCallId
109
+ );
110
+
111
+ if (toolCall) {
112
+ toolCall.output = output;
113
+ toolCall.status = error ? 'error' : 'success';
114
+ if (error) {
115
+ toolCall.error = error;
116
+ }
117
+ this.contextData.metadata.lastUpdated = Date.now();
118
+ }
119
+ }
120
+
121
+ /**
122
+ *
123
+ */
124
+ getRecentToolCalls(count: number = 10): ToolCallRecord[] {
125
+ if (!this.contextData) {
126
+ return [];
127
+ }
128
+ return this.contextData.layers.tool.recentCalls.slice(-count);
129
+ }
130
+
131
+ /**
132
+ *
133
+ */
134
+ updateTokenCount(tokens: number): void {
135
+ if (!this.contextData) return;
136
+ this.contextData.metadata.totalTokens = tokens;
137
+ this.contextData.metadata.lastUpdated = Date.now();
138
+ }
139
+
140
+ /**
141
+ *
142
+ */
143
+ getTokenCount(): number {
144
+ return this.contextData?.metadata.totalTokens ?? 0;
145
+ }
146
+
147
+ /**
148
+ *
149
+ */
150
+ private enforceMemoryLimit(): void {
151
+ if (!this.contextData) return;
152
+
153
+ const messages = this.contextData.layers.conversation.messages;
154
+ if (messages.length > this.maxSize) {
155
+ // 保留最近的消息,删除较旧
156
+ const keepCount = Math.floor(this.maxSize * 0.8); // 保
157
+ this.contextData.layers.conversation.messages = messages.slice(-keepCount);
158
+ }
159
+ }
160
+
161
+ /**
162
+ *
163
+ */
164
+ private recordAccess(key: string): void {
165
+ this.accessLog.set(key, Date.now());
166
+ }
167
+
168
+ /**
169
+ *
170
+ */
171
+ getLastAccess(key: string): number | undefined {
172
+ return this.accessLog.get(key);
173
+ }
174
+
175
+ /**
176
+ *
177
+ */
178
+ getMemoryInfo(): MemoryInfo {
179
+ if (!this.contextData) {
180
+ return { hasData: false, messageCount: 0, toolCallCount: 0, lastUpdated: null };
181
+ }
182
+
183
+ return {
184
+ hasData: true,
185
+ messageCount: this.contextData.layers.conversation.messages.length,
186
+ toolCallCount: this.contextData.layers.tool.recentCalls.length,
187
+ lastUpdated: this.contextData.metadata.lastUpdated,
188
+ };
189
+ }
190
+
191
+ /**
192
+ *
193
+ */
194
+ getSessionId(): string | null {
195
+ return this.contextData?.layers.session.sessionId ?? null;
196
+ }
197
+
198
+ /**
199
+ *
200
+ */
201
+ clear(): void {
202
+ this.contextData = null;
203
+ this.accessLog.clear();
204
+ }
205
+ }
@@ -0,0 +1,327 @@
1
+ /**
2
+ *
3
+ *
4
+ *
5
+ */
6
+
7
+ import { nanoid } from 'nanoid';
8
+ import type { JSONLEntry, CompactMetadata, SessionContext, ConversationContext, ContextMessage } from '../types.js';
9
+ import { JSONLStore } from './JSONLStore.js';
10
+ import { getSessionFilePath, detectGitBranch } from './pathUtils.js';
11
+
12
+ // 获取版本
13
+ let packageVersion = '0.1.0';
14
+ try {
15
+ const packageJson = await import('../../../package.json', { with: { type: 'json' } });
16
+ packageVersion = packageJson.default.version || '0.1.0';
17
+ } catch {
18
+ // 忽略错
19
+ }
20
+
21
+ export class PersistentStore {
22
+ private readonly projectPath: string;
23
+ private readonly maxSessions: number;
24
+ private readonly version: string;
25
+
26
+ constructor(projectPath: string = process.cwd(), maxSessions: number = 100) {
27
+ this.projectPath = projectPath;
28
+ this.maxSessions = maxSessions;
29
+ this.version = packageVersion;
30
+ }
31
+
32
+ /**
33
+ *
34
+ */
35
+ private getStore(sessionId: string): JSONLStore {
36
+ const filePath = getSessionFilePath(this.projectPath, sessionId);
37
+ return new JSONLStore(filePath);
38
+ }
39
+
40
+ /**
41
+ *
42
+ */
43
+ async saveMessage(
44
+ sessionId: string,
45
+ messageRole: 'user' | 'assistant' | 'system',
46
+ content: string,
47
+ parentUuid: string | null = null,
48
+ metadata?: { model?: string; usage?: { input_tokens: number; output_tokens: number } }
49
+ ): Promise<string> {
50
+ const store = this.getStore(sessionId);
51
+
52
+ const entry: JSONLEntry = {
53
+ uuid: nanoid(),
54
+ parentUuid,
55
+ sessionId,
56
+ timestamp: new Date().toISOString(),
57
+ type: messageRole,
58
+ cwd: this.projectPath,
59
+ gitBranch: detectGitBranch(this.projectPath),
60
+ version: this.version,
61
+ message: {
62
+ role: messageRole,
63
+ content,
64
+ ...(metadata || {}),
65
+ },
66
+ };
67
+
68
+ await store.append(entry);
69
+ return entry.uuid;
70
+ }
71
+
72
+ /**
73
+ *
74
+ */
75
+ async saveToolUse(
76
+ sessionId: string,
77
+ toolId: string,
78
+ toolName: string,
79
+ input: unknown,
80
+ parentUuid: string | null = null
81
+ ): Promise<string> {
82
+ const store = this.getStore(sessionId);
83
+
84
+ const entry: JSONLEntry = {
85
+ uuid: nanoid(),
86
+ parentUuid,
87
+ sessionId,
88
+ timestamp: new Date().toISOString(),
89
+ type: 'tool_use',
90
+ cwd: this.projectPath,
91
+ gitBranch: detectGitBranch(this.projectPath),
92
+ version: this.version,
93
+ message: {
94
+ role: 'assistant',
95
+ content: '',
96
+ },
97
+ tool: {
98
+ id: toolId,
99
+ name: toolName,
100
+ input,
101
+ },
102
+ };
103
+
104
+ await store.append(entry);
105
+ return entry.uuid;
106
+ }
107
+
108
+ /**
109
+ *
110
+ */
111
+ async saveToolResult(
112
+ sessionId: string,
113
+ toolId: string,
114
+ output: unknown,
115
+ error?: string,
116
+ parentUuid: string | null = null
117
+ ): Promise<string> {
118
+ const store = this.getStore(sessionId);
119
+
120
+ const entry: JSONLEntry = {
121
+ uuid: nanoid(),
122
+ parentUuid,
123
+ sessionId,
124
+ timestamp: new Date().toISOString(),
125
+ type: 'tool_result',
126
+ cwd: this.projectPath,
127
+ gitBranch: detectGitBranch(this.projectPath),
128
+ version: this.version,
129
+ message: {
130
+ role: 'assistant',
131
+ content: '',
132
+ },
133
+ toolResult: {
134
+ id: toolId,
135
+ output,
136
+ error,
137
+ },
138
+ };
139
+
140
+ await store.append(entry);
141
+ return entry.uuid;
142
+ }
143
+
144
+ /**
145
+ *
146
+ */
147
+ async saveCompaction(
148
+ sessionId: string,
149
+ summary: string,
150
+ metadata: CompactMetadata,
151
+ parentUuid: string | null = null
152
+ ): Promise<string> {
153
+ const store = this.getStore(sessionId);
154
+
155
+ // 1. 保存压缩边界标
156
+ const boundaryEntry: JSONLEntry = {
157
+ uuid: nanoid(),
158
+ parentUuid,
159
+ sessionId,
160
+ timestamp: new Date().toISOString(),
161
+ type: 'system',
162
+ subtype: 'compact_boundary',
163
+ cwd: this.projectPath,
164
+ gitBranch: detectGitBranch(this.projectPath),
165
+ version: this.version,
166
+ message: {
167
+ role: 'system',
168
+ content: '=== 上下文压缩边界 ===',
169
+ },
170
+ compactMetadata: metadata,
171
+ };
172
+ await store.append(boundaryEntry);
173
+
174
+ // 2. 保存压缩总
175
+ const summaryEntry: JSONLEntry = {
176
+ uuid: nanoid(),
177
+ parentUuid: boundaryEntry.uuid,
178
+ sessionId,
179
+ timestamp: new Date().toISOString(),
180
+ type: 'user',
181
+ isCompactSummary: true,
182
+ cwd: this.projectPath,
183
+ version: this.version,
184
+ message: {
185
+ role: 'user',
186
+ content: summary,
187
+ },
188
+ compactMetadata: metadata,
189
+ };
190
+ await store.append(summaryEntry);
191
+
192
+ return summaryEntry.uuid;
193
+ }
194
+
195
+ /**
196
+ *
197
+ */
198
+ async loadSession(sessionId: string): Promise<SessionContext | null> {
199
+ const store = this.getStore(sessionId);
200
+
201
+ if (!store.exists()) {
202
+ return null;
203
+ }
204
+
205
+ const entries = await store.readAll();
206
+ if (entries.length === 0) {
207
+ return null;
208
+ }
209
+
210
+ const firstEntry = entries[0];
211
+ return {
212
+ sessionId,
213
+ preferences: {},
214
+ startTime: new Date(firstEntry.timestamp).getTime(),
215
+ };
216
+ }
217
+
218
+ /**
219
+ *
220
+ */
221
+ async loadConversation(sessionId: string): Promise<ConversationContext | null> {
222
+ const store = this.getStore(sessionId);
223
+
224
+ if (!store.exists()) {
225
+ return null;
226
+ }
227
+
228
+ // 只加载压缩边界后的消
229
+ const entries = await store.readAfterCompaction();
230
+
231
+ const messages: ContextMessage[] = [];
232
+ let lastActivity = 0;
233
+
234
+ for (const entry of entries) {
235
+ const timestamp = new Date(entry.timestamp).getTime();
236
+ if (timestamp > lastActivity) {
237
+ lastActivity = timestamp;
238
+ }
239
+
240
+ // 跳过压缩边
241
+ if (entry.subtype === 'compact_boundary') {
242
+ continue;
243
+ }
244
+
245
+ // 转换
246
+ if (entry.type === 'user' || entry.type === 'assistant' || entry.type === 'system') {
247
+ messages.push({
248
+ id: entry.uuid,
249
+ role: entry.message.role as ContextMessage['role'],
250
+ content: typeof entry.message.content === 'string'
251
+ ? entry.message.content
252
+ : JSON.stringify(entry.message.content),
253
+ timestamp,
254
+ metadata: entry.isCompactSummary ? { isCompactSummary: true } : undefined,
255
+ });
256
+ }
257
+ }
258
+
259
+ return {
260
+ messages,
261
+ topics: [],
262
+ lastActivity,
263
+ };
264
+ }
265
+
266
+ /**
267
+ *
268
+ */
269
+ async listSessions(): Promise<string[]> {
270
+ const { readdir } = await import('node:fs/promises');
271
+ const { getProjectStoragePath } = await import('./pathUtils.js');
272
+
273
+ const storagePath = getProjectStoragePath(this.projectPath);
274
+
275
+ try {
276
+ const files = await readdir(storagePath);
277
+ return files
278
+ .filter(f => f.endsWith('.jsonl'))
279
+ .map(f => f.replace('.jsonl', ''));
280
+ } catch {
281
+ return [];
282
+ }
283
+ }
284
+
285
+ /**
286
+ *
287
+ */
288
+ async deleteSession(sessionId: string): Promise<void> {
289
+ const store = this.getStore(sessionId);
290
+ await store.delete();
291
+ }
292
+
293
+ /**
294
+ *
295
+ */
296
+ async getSessionStats(sessionId: string): Promise<{
297
+ messageCount: number;
298
+ fileSize: number;
299
+ createdAt: Date | null;
300
+ lastUpdatedAt: Date | null;
301
+ } | null> {
302
+ const store = this.getStore(sessionId);
303
+
304
+ if (!store.exists()) {
305
+ return null;
306
+ }
307
+
308
+ const entries = await store.readAll();
309
+ const fileSize = await store.getFileSize();
310
+
311
+ if (entries.length === 0) {
312
+ return {
313
+ messageCount: 0,
314
+ fileSize,
315
+ createdAt: null,
316
+ lastUpdatedAt: null,
317
+ };
318
+ }
319
+
320
+ return {
321
+ messageCount: entries.length,
322
+ fileSize,
323
+ createdAt: new Date(entries[0].timestamp),
324
+ lastUpdatedAt: new Date(entries[entries.length - 1].timestamp),
325
+ };
326
+ }
327
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ *
3
+ */
4
+
5
+ export { MemoryStore } from './MemoryStore.js';
6
+ export { PersistentStore } from './PersistentStore.js';
7
+ export { CacheStore } from './CacheStore.js';
8
+ export { JSONLStore } from './JSONLStore.js';
9
+ export * from './pathUtils.js';
@@ -0,0 +1,114 @@
1
+ /**
2
+ *
3
+ *
4
+ *
5
+ */
6
+
7
+ import * as path from 'node:path';
8
+ import * as os from 'node:os';
9
+ import { execSync } from 'node:child_process';
10
+
11
+ /**
12
+ *
13
+ */
14
+ export function getStorageRoot(): string {
15
+ return path.join(os.homedir(), '.aegis');
16
+ }
17
+
18
+ /**
19
+ *
20
+ * /Users/foo/project → -Users-foo-project
21
+ */
22
+ export function escapeProjectPath(absPath: string): string {
23
+ const normalized = path.resolve(absPath);
24
+ // 将路径分隔符替换为 -,移除开头
25
+ return normalized.replace(/[/\\]/g, '-').replace(/^-/, '');
26
+ }
27
+
28
+ /**
29
+ *
30
+ * @returns ~/.aegis/projects/{escaped-path}/
31
+ */
32
+ export function getProjectStoragePath(projectPath: string): string {
33
+ const escaped = escapeProjectPath(projectPath);
34
+ return path.join(getStorageRoot(), 'projects', escaped);
35
+ }
36
+
37
+ /**
38
+ *
39
+ * @returns ~/.aegis/projects/{escaped-path}/{sessionId}.jsonl
40
+ */
41
+ export function getSessionFilePath(projectPath: string, sessionId: string): string {
42
+ return path.join(getProjectStoragePath(projectPath), `${sessionId}.jsonl`);
43
+ }
44
+
45
+ /**
46
+ *
47
+ * @returns ~/.aegis/projects/{escaped-path}/sessions.json
48
+ */
49
+ export function getSessionIndexPath(projectPath: string): string {
50
+ return path.join(getProjectStoragePath(projectPath), 'sessions.json');
51
+ }
52
+
53
+ /**
54
+ *
55
+ */
56
+ export function detectGitBranch(projectPath: string): string | undefined {
57
+ try {
58
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
59
+ cwd: projectPath,
60
+ encoding: 'utf-8',
61
+ stdio: ['ignore', 'pipe', 'ignore'],
62
+ }).trim();
63
+ return branch || undefined;
64
+ } catch {
65
+ return undefined;
66
+ }
67
+ }
68
+
69
+ /**
70
+ *
71
+ */
72
+ export function detectGitRemote(projectPath: string): string | undefined {
73
+ try {
74
+ const remote = execSync('git remote get-url origin', {
75
+ cwd: projectPath,
76
+ encoding: 'utf-8',
77
+ stdio: ['ignore', 'pipe', 'ignore'],
78
+ }).trim();
79
+ return remote || undefined;
80
+ } catch {
81
+ return undefined;
82
+ }
83
+ }
84
+
85
+ /**
86
+ *
87
+ */
88
+ export async function getLatestSessionFile(projectPath: string): Promise<string | null> {
89
+ const { readdir, stat } = await import('node:fs/promises');
90
+ const storagePath = getProjectStoragePath(projectPath);
91
+
92
+ try {
93
+ const files = await readdir(storagePath);
94
+ const jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
95
+
96
+ if (jsonlFiles.length === 0) {
97
+ return null;
98
+ }
99
+
100
+ // 按修改时间排
101
+ const fileStats = await Promise.all(
102
+ jsonlFiles.map(async (file) => {
103
+ const filePath = path.join(storagePath, file);
104
+ const stats = await stat(filePath);
105
+ return { file, mtime: stats.mtime.getTime() };
106
+ })
107
+ );
108
+
109
+ fileStats.sort((a, b) => b.mtime - a.mtime);
110
+ return path.join(storagePath, fileStats[0].file);
111
+ } catch {
112
+ return null;
113
+ }
114
+ }