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,274 @@
1
+ /**
2
+ * Zustand Store 类型定义
3
+ */
4
+
5
+ import type { RuntimeConfig } from '../config/types.js';
6
+
7
+ // ========== Content Block Model (mirrors Claude SSE content blocks)
8
+
9
+ export type ContentBlockType = 'text' | 'thinking' | 'tool_use' | 'tool_result';
10
+
11
+ export type ToolCallStatus = 'running' | 'success' | 'error';
12
+
13
+ export interface TextBlock {
14
+ type: 'text';
15
+ text: string;
16
+ }
17
+
18
+ export interface ThinkingBlock {
19
+ type: 'thinking';
20
+ thinking: string;
21
+ }
22
+
23
+ export interface ToolUseBlock {
24
+ type: 'tool_use';
25
+ id: string;
26
+ name: string;
27
+ input: string; // JSON args being accumulated
28
+ status: ToolCallStatus;
29
+ startedAt: number;
30
+ completedAt?: number;
31
+ }
32
+
33
+ export interface ToolResultBlock {
34
+ type: 'tool_result';
35
+ tool_use_id: string;
36
+ content: string;
37
+ is_error: boolean;
38
+ }
39
+
40
+ export type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock;
41
+
42
+ // ========== 会话状
43
+
44
+ export interface ModelTokens {
45
+ inputTokens: number;
46
+ outputTokens: number;
47
+ }
48
+
49
+ export interface TokenUsage {
50
+ inputTokens: number;
51
+ outputTokens: number;
52
+ totalTokens: number;
53
+ maxContextTokens: number;
54
+ modelBreakdown: Record<string, ModelTokens>;
55
+ }
56
+
57
+ export interface SessionMessage {
58
+ id: string;
59
+ role: 'user' | 'assistant' | 'system' | 'tool';
60
+ content: string;
61
+ timestamp: number;
62
+ toolCalls?: unknown[];
63
+ toolCallId?: string;
64
+ /** 思考过程内容(用于支持 DeepSeek R1 等推理模型) */
65
+ thinking?: string;
66
+ isStreaming?: boolean;
67
+ /** Content blocks for structured rendering (Claude-style: text, thinking, tool_use, tool_result) */
68
+ contentBlocks?: ContentBlock[];
69
+ }
70
+
71
+ export interface SessionState {
72
+ sessionId: string;
73
+ messages: SessionMessage[];
74
+ isThinking: boolean;
75
+ isCompacting: boolean;
76
+ currentCommand: string | null;
77
+ error: string | null;
78
+ isActive: boolean;
79
+ tokenUsage: TokenUsage;
80
+ }
81
+
82
+ export interface SessionActions {
83
+ addMessage: (message: SessionMessage) => void;
84
+ addUserMessage: (content: string) => void;
85
+ addAssistantMessage: (content: string) => void;
86
+ /** 开始流式助手消息(创建空消息占位) */
87
+ startStreamingMessage: () => string;
88
+ appendToStreamingMessage: (id: string, contentDelta: string) => void;
89
+ appendThinkingToStreamingMessage: (id: string, thinkingDelta: string) => void;
90
+ /** Force-flush streaming buffer content to store (for tool calls, final flush) */
91
+ flushStreamBuffer: (id: string) => void;
92
+ /** Write directly to store message, bypassing buffer (for tool calls) */
93
+ forceAppendToMessage: (id: string, contentDelta: string) => void;
94
+ finishStreamingMessage: (id: string) => void;
95
+
96
+ // ===== Content Block Operations (Claude-style) =====
97
+ /** Add a content block to the currently streaming message */
98
+ addContentBlock: (messageId: string, block: ContentBlock) => void;
99
+ /** Update a tool_use block's accumulated input JSON */
100
+ updateToolCallInput: (messageId: string, toolCallId: string, partialJson: string) => void;
101
+ /** Set a tool_use block's full input JSON (replaces, used once on completion) */
102
+ setToolCallInput: (messageId: string, toolCallId: string, fullInput: string) => void;
103
+ /** Update a tool_use block's status */
104
+ updateToolCallStatus: (messageId: string, toolCallId: string, status: ToolCallStatus, completedAt?: number) => void;
105
+ /** Add a tool_result block linked to a tool_use */
106
+ addToolResultBlock: (messageId: string, toolUseId: string, content: string, isError: boolean) => void;
107
+
108
+ setThinking: (isThinking: boolean) => void;
109
+ setCompacting: (isCompacting: boolean) => void;
110
+ setCurrentCommand: (command: string | null) => void;
111
+ setError: (error: string | null) => void;
112
+ setSessionId: (sessionId: string) => void;
113
+ restoreSession: (sessionId: string, messages: SessionMessage[]) => void;
114
+ updateTokenUsage: (usage: Partial<TokenUsage>) => void;
115
+ removeLastMessages: (count: number) => void;
116
+ clearMessages: () => void;
117
+ resetSession: () => void;
118
+ }
119
+
120
+ export interface SessionSlice extends SessionState {
121
+ actions: SessionActions;
122
+ }
123
+
124
+ // ========== 配置状
125
+
126
+ export interface ConfigState {
127
+ config: RuntimeConfig | null;
128
+ }
129
+
130
+ export interface ConfigActions {
131
+ setConfig: (config: RuntimeConfig) => void;
132
+ updateConfig: (partial: Partial<RuntimeConfig>) => void;
133
+ }
134
+
135
+ export interface ConfigSlice extends ConfigState {
136
+ actions: ConfigActions;
137
+ }
138
+
139
+ // ========== 应用状
140
+
141
+ export type InitializationStatus = 'pending' | 'loading' | 'ready' | 'error' | 'needsSetup';
142
+ export type ActiveModal = 'none' | 'shortcuts' | 'settings' | 'confirmation' | 'update' | 'themeSelector';
143
+
144
+ export interface AppState {
145
+ initializationStatus: InitializationStatus;
146
+ initializationError: string | null;
147
+ activeModal: ActiveModal;
148
+ awaitingSecondCtrlC: boolean;
149
+ /** 是否展开所有思考块(全局开关) */
150
+ showAllThinking: boolean;
151
+ todos: TodoItem[];
152
+ /** Set once the user runs /model this session — auto-router backs off until /router on resets it */
153
+ manualModelOverride: boolean;
154
+ /** Display label of the model the auto-router picked for the in-flight/last turn, or null if none */
155
+ autoRouterActiveModel: string | null;
156
+ workflow: WorkflowState;
157
+ }
158
+
159
+ export interface AppActions {
160
+ setInitializationStatus: (status: InitializationStatus) => void;
161
+ setInitializationError: (error: string | null) => void;
162
+ setActiveModal: (modal: ActiveModal) => void;
163
+ setTodos: (todos: TodoItem[]) => void;
164
+ addTodo: (todo: TodoItem) => void;
165
+ updateTodo: (id: string, updates: Partial<TodoItem>) => void;
166
+ removeTodo: (id: string) => void;
167
+ setAwaitingSecondCtrlC: (awaiting: boolean) => void;
168
+ toggleShowAllThinking: () => void;
169
+ setManualModelOverride: (value: boolean) => void;
170
+ setAutoRouterActiveModel: (label: string | null) => void;
171
+ workflow: WorkflowActions;
172
+ }
173
+
174
+ export interface AppSlice extends AppState {
175
+ actions: AppActions;
176
+ }
177
+
178
+ // ========== 焦点状
179
+
180
+ export type FocusId = 'input' | 'messages' | 'confirmation' | 'modal' | 'none' | 'theme-selector' | 'selector';
181
+
182
+ /** FocusId 常量枚举 */
183
+ export const FocusId = {
184
+ MAIN_INPUT: 'input' as FocusId,
185
+ MESSAGES: 'messages' as FocusId,
186
+ CONFIRMATION_PROMPT: 'confirmation' as FocusId,
187
+ THEME_SELECTOR: 'theme-selector' as FocusId,
188
+ SELECTOR: 'selector' as FocusId,
189
+ MODAL: 'modal' as FocusId,
190
+ NONE: 'none' as FocusId,
191
+ } as const;
192
+
193
+ export interface FocusState {
194
+ currentFocus: FocusId;
195
+ previousFocus: FocusId | null;
196
+ }
197
+
198
+ export interface FocusActions {
199
+ setFocus: (focus: FocusId) => void;
200
+ restoreFocus: () => void;
201
+ pushFocus: (focus: FocusId) => void;
202
+ }
203
+
204
+ export interface FocusSlice extends FocusState {
205
+ actions: FocusActions;
206
+ }
207
+
208
+ // ========== 命令状
209
+
210
+ export interface CommandState {
211
+ isProcessing: boolean;
212
+ abortController: AbortController | null;
213
+ pendingCommands: string[];
214
+ }
215
+
216
+ export interface CommandActions {
217
+ setProcessing: (isProcessing: boolean) => void;
218
+ createAbortController: () => AbortController;
219
+ abort: () => void;
220
+ enqueueCommand: (command: string) => void;
221
+ dequeueCommand: () => string | undefined;
222
+ clearQueue: () => void;
223
+ }
224
+
225
+ export interface CommandSlice extends CommandState {
226
+ actions: CommandActions;
227
+ }
228
+
229
+ // ========== Workflow State (for context bar)
230
+
231
+ export interface WorkflowStep {
232
+ label: string;
233
+ status: 'pending' | 'active' | 'done';
234
+ }
235
+
236
+ export interface WorkflowState {
237
+ visible: boolean;
238
+ phase: string; // "Building", "Refactoring", "Debugging" etc.
239
+ target: string; // what's being worked on, e.g. "auth system"
240
+ steps: WorkflowStep[];
241
+ currentStepIndex: number;
242
+ totalSteps: number;
243
+ }
244
+
245
+ export const initialWorkflowState: WorkflowState = {
246
+ visible: false,
247
+ phase: '',
248
+ target: '',
249
+ steps: [],
250
+ currentStepIndex: 0,
251
+ totalSteps: 0,
252
+ };
253
+
254
+ export interface WorkflowActions {
255
+ setWorkflow: (opts: { phase: string; target: string; steps: string[] }) => void;
256
+ advanceStep: () => void;
257
+ clearWorkflow: () => void;
258
+ }
259
+
260
+ // ========== Todo support (for progress tracking in tasks)
261
+ export interface TodoItem {
262
+ id: string;
263
+ title: string;
264
+ status: 'pending' | 'in_progress' | 'completed';
265
+ createdAt: number;
266
+ }
267
+
268
+ export interface ClawdStore {
269
+ session: SessionSlice;
270
+ config: ConfigSlice;
271
+ app: AppSlice;
272
+ focus: FocusSlice;
273
+ command: CommandSlice;
274
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Zustand Vanilla Store
3
+ *
4
+ *
5
+ */
6
+
7
+ import { createStore } from 'zustand/vanilla';
8
+ import { devtools, subscribeWithSelector } from 'zustand/middleware';
9
+
10
+ import type { ClawdStore } from './types.js';
11
+ import type { RuntimeConfig } from '../config/types.js';
12
+ import {
13
+ createSessionSlice,
14
+ createConfigSlice,
15
+ createAppSlice,
16
+ createFocusSlice,
17
+ createCommandSlice,
18
+ } from './slices/index.js';
19
+
20
+ /**
21
+ *
22
+ *
23
+ *
24
+ * - devtools: 开发工具支持
25
+ * - subscribeWithSelector: 支持选择器订阅
26
+ */
27
+ export const vanillaStore = createStore<ClawdStore>()(
28
+ devtools(
29
+ subscribeWithSelector((...a) => ({
30
+ session: createSessionSlice(...a),
31
+ config: createConfigSlice(...a),
32
+ app: createAppSlice(...a),
33
+ focus: createFocusSlice(...a),
34
+ command: createCommandSlice(...a),
35
+ })),
36
+ {
37
+ name: 'ClawdStore',
38
+ enabled: process.env.NODE_ENV === 'development',
39
+ }
40
+ )
41
+ );
42
+
43
+ // ========== 便捷访问
44
+
45
+ /**
46
+ *
47
+ */
48
+ export const getState = () => vanillaStore.getState();
49
+
50
+ /**
51
+ *
52
+ */
53
+ export const subscribe = vanillaStore.subscribe;
54
+
55
+ // ========== Actions 快捷访
56
+
57
+ export const sessionActions = () => getState().session.actions;
58
+ export const configActions = () => getState().config.actions;
59
+ export const appActions = () => getState().app.actions;
60
+ export const focusActions = () => getState().focus.actions;
61
+ export const commandActions = () => getState().command.actions;
62
+
63
+ // ========== 配置便捷访
64
+
65
+ /**
66
+ *
67
+ */
68
+ export const getConfig = (): RuntimeConfig | null => getState().config.config;
69
+
70
+ /**
71
+ *
72
+ */
73
+ export const getCurrentModel = () => {
74
+ const config = getConfig();
75
+ if (!config) return undefined;
76
+
77
+ // 优先使用 currentModelId
78
+ if (config.currentModelId && config.models) {
79
+ const model = config.models.find((m) => m.id === config.currentModelId);
80
+ if (model) return model;
81
+ }
82
+
83
+ // 回退:按 model 字段匹配(兼容 currentModelId 被设为 model name 的情况)
84
+ if (config.currentModelId && config.models) {
85
+ const model = config.models.find((m) => m.model === config.currentModelId);
86
+ if (model) return model;
87
+ }
88
+
89
+ // 回退
90
+ if (config.models && config.models.length > 0) {
91
+ return config.models[0];
92
+ }
93
+
94
+ // 回退到 default
95
+ return config.default;
96
+ };
97
+
98
+ /**
99
+ *
100
+ */
101
+ export const getPermissionMode = () => {
102
+ const config = getConfig();
103
+ return config?.defaultPermissionMode || 'default';
104
+ };
105
+
106
+ // ========== 初始化机
107
+
108
+ let initializationPromise: Promise<void> | null = null;
109
+
110
+ /**
111
+ *
112
+ *
113
+ *
114
+ * - 幂等:已初始化直接返回
115
+ * - 并发安全:共享 Promise
116
+ * - 失败重试:下次调用重新尝试
117
+ */
118
+ export async function ensureStoreInitialized(): Promise<void> {
119
+ // 1. 快速路径:已初始
120
+ const config = getConfig();
121
+ if (config !== null) {
122
+ return;
123
+ }
124
+
125
+ // 2. 并发保护:等待共
126
+ if (initializationPromise) {
127
+ return initializationPromise;
128
+ }
129
+
130
+ // 3. 开始初始
131
+ initializationPromise = (async () => {
132
+ try {
133
+ // 动态导入避免循环依
134
+ const { ConfigManager } = await import('../config/ConfigManager.js');
135
+ const configManager = ConfigManager.getInstance();
136
+ const loadedConfig = await configManager.initialize();
137
+ getState().config.actions.setConfig(loadedConfig as RuntimeConfig);
138
+ } catch (error) {
139
+ initializationPromise = null; // 允许重
140
+ throw new Error(
141
+ `❌ Store 初始化失败\n\n` +
142
+ `原因: ${error instanceof Error ? error.message : '未知错误'}`
143
+ );
144
+ } finally {
145
+ initializationPromise = null;
146
+ }
147
+ })();
148
+
149
+ return initializationPromise;
150
+ }
151
+
152
+ // ========== 订阅工
153
+
154
+ /**
155
+ *
156
+ */
157
+ export function subscribeToState<T>(
158
+ selector: (state: ClawdStore) => T,
159
+ callback: (value: T, prevValue: T) => void
160
+ ): () => void {
161
+ return vanillaStore.subscribe((state, prevState) => {
162
+ const value = selector(state);
163
+ const prevValue = selector(prevState);
164
+ if (value !== prevValue) {
165
+ callback(value, prevValue);
166
+ }
167
+ });
168
+ }
169
+
170
+ /**
171
+ *
172
+ */
173
+ export function subscribeToTodos(
174
+ callback: (todos: ClawdStore['app']['todos']) => void
175
+ ): () => void {
176
+ return subscribeToState((state) => state.app.todos, callback);
177
+ }
178
+
179
+ /**
180
+ *
181
+ */
182
+ export function subscribeToMessages(
183
+ callback: (messages: ClawdStore['session']['messages']) => void
184
+ ): () => void {
185
+ return subscribeToState((state) => state.session.messages, callback);
186
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Bash 工具
3
+ *
4
+ *
5
+ */
6
+
7
+ import { exec, spawn } from 'child_process';
8
+ import { promisify } from 'util';
9
+ import { createWriteStream, mkdirSync } from 'fs';
10
+ import path from 'path';
11
+ import os from 'os';
12
+ import { z } from 'zod';
13
+ import { createTool } from '../createTool.js';
14
+ import { ToolKind, ToolErrorType } from '../types.js';
15
+
16
+ const BG_JOBS_DIR = path.join(os.homedir(), '.aegiscode', 'bg');
17
+
18
+ const execAsync = promisify(exec);
19
+
20
+ // ========== Schema 定
21
+
22
+ const BashSchema = z.object({
23
+ command: z.string()
24
+ .min(1, '命令不能为空')
25
+ .describe('The shell command to execute'),
26
+ description: z.string()
27
+ .optional()
28
+ .describe('A brief description of what the command does (for logging)'),
29
+ timeout: z.number()
30
+ .max(600000)
31
+ .default(120000)
32
+ .describe('Timeout in milliseconds (max 10 minutes, default 2 minutes)'),
33
+ working_directory: z.string()
34
+ .optional()
35
+ .describe('The working directory to execute the command in'),
36
+ run_in_background: z.boolean()
37
+ .default(false)
38
+ .describe('Whether to run the command in the background'),
39
+ });
40
+
41
+ // ========== Bash 工
42
+
43
+ export const bashTool = createTool({
44
+ name: 'Bash',
45
+ displayName: 'Shell Command',
46
+ kind: ToolKind.Execute,
47
+ schema: BashSchema,
48
+
49
+ // Bash 不是并发安全的(可能修改共享状
50
+ isConcurrencySafe: false,
51
+
52
+ description: {
53
+ short: 'Executes bash commands in a shell session',
54
+ long: 'Executes shell commands and returns the output. Use this for system operations, git commands, package management, etc.',
55
+ usageNotes: [
56
+ 'Avoid using for file operations - use dedicated tools (Read, Write, Edit) instead',
57
+ 'Do not use cat/head/tail to read files - use the Read tool',
58
+ 'Do not use sed/awk to edit files - use the Edit tool',
59
+ 'Use && to chain dependent commands',
60
+ 'Use run_in_background for long-running dev servers',
61
+ 'Always quote file paths that contain spaces',
62
+ ],
63
+ examples: [
64
+ {
65
+ description: 'Run npm install',
66
+ params: {
67
+ command: 'npm install',
68
+ description: 'Install npm dependencies',
69
+ },
70
+ },
71
+ {
72
+ description: 'Check git status',
73
+ params: {
74
+ command: 'git status',
75
+ },
76
+ },
77
+ {
78
+ description: 'Run a build command with timeout',
79
+ params: {
80
+ command: 'npm run build',
81
+ timeout: 300000,
82
+ description: 'Build the project',
83
+ },
84
+ },
85
+ ],
86
+ important: [
87
+ 'NEVER use git commands with -i flag (interactive mode)',
88
+ 'NEVER run destructive commands like rm -rf / without explicit user request',
89
+ 'NEVER use echo or printf to communicate - output text directly instead',
90
+ 'Avoid long-running processes that block (like npm run dev) unless using run_in_background',
91
+ ],
92
+ },
93
+
94
+ category: 'Shell',
95
+ tags: ['shell', 'bash', 'command', 'execute'],
96
+
97
+ // 提取签名内容(用于权限规
98
+ extractSignatureContent: (params: unknown) => {
99
+ const p = params as { command: string };
100
+ return p.command;
101
+ },
102
+
103
+ async execute(params, context) {
104
+ const { command, description, timeout, working_directory, run_in_background } = params;
105
+
106
+ // 危险命令检
107
+ const dangerousPatterns = [
108
+ /rm\s+-rf\s+\/(?!\w)/, // rm -rf / (但允许 rm -rf /path/to/dir)
109
+ />\s*\/dev\/sd[a-z]/, // 写入磁盘设
110
+ /mkfs\./, // 格式化文件系
111
+ /dd\s+if=.*of=\/dev/, // dd 写入设
112
+ ];
113
+
114
+ for (const pattern of dangerousPatterns) {
115
+ if (pattern.test(command)) {
116
+ return {
117
+ success: false,
118
+ llmContent: `Error: Potentially dangerous command detected: ${command}`,
119
+ displayContent: `blocked: dangerous command`,
120
+ error: {
121
+ type: ToolErrorType.PERMISSION_ERROR,
122
+ message: 'Dangerous command blocked',
123
+ },
124
+ };
125
+ }
126
+ }
127
+
128
+ try {
129
+ if (run_in_background) {
130
+ const jobId = `bg-${Date.now()}`;
131
+ const logPath = path.join(BG_JOBS_DIR, `${jobId}.log`);
132
+ mkdirSync(BG_JOBS_DIR, { recursive: true });
133
+
134
+ const logStream = createWriteStream(logPath, { flags: 'a' });
135
+ logStream.write(`[${new Date().toISOString()}] $ ${command}\n\n`);
136
+
137
+ const child = spawn(command, [], {
138
+ shell: '/bin/bash',
139
+ cwd: working_directory || context?.cwd || process.cwd(),
140
+ stdio: ['ignore', 'pipe', 'pipe'],
141
+ });
142
+
143
+ child.stdout!.on('data', (chunk: Buffer) => logStream.write(chunk));
144
+ child.stderr!.on('data', (chunk: Buffer) => logStream.write(chunk));
145
+ child.on('close', (code: number | null) => {
146
+ logStream.write(`\n[${new Date().toISOString()}] exited with code ${code}\n`);
147
+ logStream.end();
148
+ });
149
+
150
+ return {
151
+ success: true,
152
+ llmContent: `Background process started.\nJob ID: ${jobId}\nPID: ${child.pid}\nLog file: ${logPath}\n\nMonitor with: tail -f ${logPath}\nCheck output: cat ${logPath}`,
153
+ displayContent: `▶ bg: ${description || command.substring(0, 50)} (PID ${child.pid})`,
154
+ metadata: { jobId, pid: child.pid, logPath, command },
155
+ };
156
+ }
157
+
158
+ // 执行命
159
+ const options = {
160
+ timeout,
161
+ cwd: working_directory || context?.cwd || process.cwd(),
162
+ maxBuffer: 10 * 1024 * 1024, // 10MB
163
+ shell: '/bin/bash',
164
+ };
165
+
166
+ const { stdout, stderr } = await execAsync(command, options);
167
+
168
+ // 组合输
169
+ const output = [
170
+ stdout ? stdout.trim() : '',
171
+ stderr ? `[stderr]\n${stderr.trim()}` : '',
172
+ ].filter(Boolean).join('\n\n');
173
+
174
+ return {
175
+ success: true,
176
+ llmContent: output || '(no output)',
177
+ displayContent: description
178
+ ? description
179
+ : command.length > 60 ? command.substring(0, 60) + '…' : command,
180
+ metadata: {
181
+ command,
182
+ exit_code: 0,
183
+ working_directory: options.cwd,
184
+ },
185
+ };
186
+ } catch (error: unknown) {
187
+ // 处理执行错
188
+ const execError = error as {
189
+ code?: number | string;
190
+ killed?: boolean;
191
+ signal?: string;
192
+ stdout?: string;
193
+ stderr?: string;
194
+ message?: string;
195
+ };
196
+
197
+ // 超时处
198
+ if (execError.killed && execError.signal === 'SIGTERM') {
199
+ return {
200
+ success: false,
201
+ llmContent: `Command timed out after ${timeout}ms: ${command}`,
202
+ displayContent: `error: timeout (${timeout}ms)`,
203
+ error: {
204
+ type: ToolErrorType.TIMEOUT_ERROR,
205
+ message: 'Command timed out',
206
+ },
207
+ };
208
+ }
209
+
210
+ // 命令执行失
211
+ const exitCode = typeof execError.code === 'number' ? execError.code : 1;
212
+ const stderr = execError.stderr || execError.message || 'unknown error';
213
+ const stdout = execError.stdout || '';
214
+
215
+ const output = [
216
+ stdout ? stdout.trim() : '',
217
+ stderr ? stderr.trim() : '',
218
+ ].filter(Boolean).join('\n\n');
219
+
220
+ return {
221
+ success: false,
222
+ llmContent: `Command failed with exit code ${exitCode}:\n${output}`,
223
+ displayContent: `error: exit ${exitCode}`,
224
+ error: {
225
+ type: ToolErrorType.EXECUTION_ERROR,
226
+ message: stderr,
227
+ details: { exit_code: exitCode },
228
+ },
229
+ metadata: {
230
+ command,
231
+ exit_code: exitCode,
232
+ },
233
+ };
234
+ }
235
+ },
236
+ });