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,443 +0,0 @@
1
- /**
2
- * useCommandProcessor - Command processing hook
3
- *
4
- * Extracts the core command processing logic (slash commands, agent chat,
5
- * streaming, tool calls, auto-compaction) from AegisInterface.
6
- */
7
-
8
- import { useCallback, useRef } from 'react';
9
- import type { Agent } from '../../agent/Agent.js';
10
- import type { ContextManager } from '../../context/index.js';
11
- import { TokenCounter } from '../../context/index.js';
12
- import type { Message, ToolCall, ToolResult } from '../../agent/types.js';
13
- import type { SlashCommandResult } from '../../slash-commands/types.js';
14
- import {
15
- sessionActions,
16
- commandActions,
17
- appActions,
18
- getState,
19
- } from '../../store/index.js';
20
- import { classifyComplexity, resolveModelForTier } from '../../agent/router.js';
21
- import type { ComplexityTier } from '../../agent/router.js';
22
- import {
23
- applyStreamEvent,
24
- finishToolCallInBuffer,
25
- getBufferedToolCalls,
26
- } from '../../store/streaming-buffer.js';
27
- import type { ConfirmationHandler } from './useConfirmation.js';
28
-
29
- export interface UseCommandProcessorOptions {
30
- agentRef: React.MutableRefObject<Agent | null>;
31
- contextManagerRef: React.MutableRefObject<ContextManager | null>;
32
- modelRef: React.MutableRefObject<string | undefined>;
33
- debugRef: React.MutableRefObject<boolean | undefined>;
34
- getMessagesRef: React.MutableRefObject<() => any[]>;
35
- confirmationHandlerRef: React.MutableRefObject<ConfirmationHandler>;
36
- onSelectorRequest?: (state: {
37
- title: string;
38
- options: Array<{ value: string; label: string }>;
39
- handler: 'theme' | 'model' | null;
40
- }) => void;
41
- }
42
-
43
- export interface UseCommandProcessorResult {
44
- processCommand: (value: string, options?: { silent?: boolean }) => Promise<void>;
45
- handleSubmit: (value: string) => Promise<void>;
46
- processQueue: () => Promise<void>;
47
- }
48
-
49
- export function useCommandProcessor(options: UseCommandProcessorOptions): UseCommandProcessorResult {
50
- const {
51
- agentRef,
52
- contextManagerRef,
53
- modelRef,
54
- debugRef,
55
- getMessagesRef,
56
- confirmationHandlerRef,
57
- onSelectorRequest,
58
- } = options;
59
-
60
- // Auto-router: lazily-created Agent instances per model id, keyed by config
61
- // model id, reused across turns so picking the same tier twice in a row
62
- // doesn't pay an Agent.create() again. Each entry also stores the settings
63
- // snapshot it was built with — requireConfirmation/allowedTools/
64
- // disallowedTools are frozen into the Agent at creation time, so if the
65
- // user toggles e.g. /confirm for that model after it's cached, the stale
66
- // Agent would keep prompting forever. Comparing the snapshot before reuse
67
- // (mirrors the same fix already applied to the main agentRef in
68
- // useAgent.ts's model-switch subscription) catches that drift.
69
- const routerAgentCacheRef = useRef<Map<string, { agent: Agent; settingsSnapshot: string }>>(new Map());
70
- // Which model id the auto-router last swapped agentRef.current to (or
71
- // undefined if it's still whatever /model or initial setup left it as).
72
- const routerActiveModelIdRef = useRef<string | undefined>(undefined);
73
-
74
- const processCommand = useCallback(async (value: string, options?: { silent?: boolean }) => {
75
- const { isSlashCommand, executeSlashCommand } = await import('../../slash-commands/index.js');
76
- const { vanillaStore } = await import('../../store/vanilla.js');
77
- const { startBatch, batchAddUserMessage, batchAddAssistantMessage, batchSetThinking, flushBatchWithStore, cancelBatch } = await import('../../store/streaming-buffer.js');
78
- const streamingBuf = await import('../../store/streaming-buffer.js');
79
-
80
- if (isSlashCommand(value)) {
81
- // Phase 1: flush user message + thinking=true immediately
82
- sessionActions().setCurrentCommand(value);
83
- startBatch();
84
- batchAddUserMessage(value);
85
- batchSetThinking(true);
86
- flushBatchWithStore(vanillaStore);
87
-
88
- let streamingMsgId: string | null = null;
89
- let streamingResult: SlashCommandResult | null = null;
90
- try {
91
- streamingMsgId = sessionActions().startStreamingMessage();
92
- } catch { /* streaming not available */ }
93
-
94
- const onContentDelta = streamingMsgId
95
- ? (delta: string) => { streamingBuf.appendToBuffer(delta); }
96
- : undefined;
97
-
98
- try {
99
- streamingResult = await executeSlashCommand(value, {
100
- cwd: process.cwd(),
101
- sessionId: getState().session.sessionId,
102
- messages: getMessagesRef.current(),
103
- contextManager: contextManagerRef.current,
104
- chatService: agentRef.current?.getChatService(),
105
- modelName: modelRef.current,
106
- onContentDelta,
107
- onThinkingDelta: streamingMsgId
108
- ? (delta: string) => { streamingBuf.appendThinkingToBuffer(delta); }
109
- : undefined,
110
- confirmationHandler: confirmationHandlerRef.current,
111
- });
112
-
113
- if (streamingResult.type === 'selector' && streamingResult.selector) {
114
- sessionActions().setCurrentCommand(null);
115
- sessionActions().setThinking(false);
116
- if (streamingMsgId) sessionActions().finishStreamingMessage(streamingMsgId);
117
- // Rollback: remove user command message + empty streaming message
118
- // so WelcomeScreen stays visible (messages.length === 0)
119
- sessionActions().removeLastMessages(2);
120
- onSelectorRequest?.(streamingResult.selector);
121
- return;
122
- }
123
-
124
- if (streamingResult.sendToAgent && streamingResult.content) {
125
- // Don't clear currentCommand — it continues via processCommand below
126
- sessionActions().setThinking(false);
127
- if (streamingMsgId) sessionActions().finishStreamingMessage(streamingMsgId);
128
- await processCommand(streamingResult.content, { silent: true });
129
- return;
130
- }
131
-
132
- if (streamingResult.type === 'silent') {
133
- if (streamingMsgId) sessionActions().finishStreamingMessage(streamingMsgId);
134
- sessionActions().setCurrentCommand(null);
135
- sessionActions().setThinking(false);
136
- return;
137
- }
138
-
139
- startBatch();
140
- if (streamingResult.content) {
141
- batchAddAssistantMessage(streamingResult.content);
142
- } else if (streamingResult.message) {
143
- batchAddAssistantMessage(streamingResult.message);
144
- } else if (streamingResult.error) {
145
- batchAddAssistantMessage('error: ' + streamingResult.error);
146
- }
147
- } catch (error) {
148
- startBatch();
149
- batchAddAssistantMessage(
150
- 'error: ' + (error instanceof Error ? error.message : String(error))
151
- );
152
- } finally {
153
- if (streamingMsgId && streamingResult?.type !== 'silent') {
154
- try { sessionActions().finishStreamingMessage(streamingMsgId); } catch {}
155
- }
156
- sessionActions().setCurrentCommand(null);
157
- batchSetThinking(false);
158
- flushBatchWithStore(vanillaStore);
159
- }
160
- return;
161
- }
162
-
163
- if (!agentRef.current || !contextManagerRef.current) return;
164
-
165
- const ctxManager = contextManagerRef.current;
166
-
167
- // ==================== Auto-router ====================
168
- // Pick a model for this turn based on task complexity, unless the user
169
- // has manually chosen one with /model this session. Never throws —
170
- // a classification or Agent.create failure just leaves the current
171
- // agent/model in place.
172
- // Captured here, recorded after the chat call resolves/aborts below —
173
- // this is the learning loop's only outcome signal (see routerStats.ts).
174
- let routedTier: ComplexityTier | undefined;
175
- let routedModelId: string | undefined;
176
- try {
177
- const routerState = getState();
178
- const autoRouter = routerState.config.config?.autoRouter;
179
- const defaultModelId = routerState.config.config?.currentModelId;
180
- const activeModelId = routerActiveModelIdRef.current ?? defaultModelId;
181
-
182
- if (autoRouter?.enabled && !routerState.app.manualModelOverride) {
183
- const models = routerState.config.config?.models || [];
184
- const tier = classifyComplexity(value);
185
- const targetModel = resolveModelForTier(tier, models, autoRouter.tiers);
186
- const targetId = targetModel?.id;
187
- const targetLabel = targetModel ? (targetModel.model || targetId) : undefined;
188
-
189
- if (targetModel && targetId && targetId !== activeModelId) {
190
- const settingsSnapshot = JSON.stringify({
191
- requireConfirmation: targetModel.requireConfirmation,
192
- allowedTools: targetModel.allowedTools,
193
- disallowedTools: targetModel.disallowedTools,
194
- });
195
- const cached = routerAgentCacheRef.current.get(targetId);
196
- let agent = cached && cached.settingsSnapshot === settingsSnapshot ? cached.agent : undefined;
197
- if (!agent) {
198
- const { Agent } = await import('../../agent/Agent.js');
199
- agent = await Agent.create({
200
- apiKey: targetModel.apiKey!,
201
- baseURL: targetModel.baseURL,
202
- model: targetLabel!,
203
- requireConfirmation: targetModel.requireConfirmation,
204
- });
205
- routerAgentCacheRef.current.set(targetId, { agent, settingsSnapshot });
206
- }
207
- agentRef.current = agent;
208
- modelRef.current = targetLabel;
209
- routerActiveModelIdRef.current = targetId;
210
- }
211
-
212
- if (targetId) {
213
- routedTier = tier;
214
- routedModelId = targetId;
215
- }
216
-
217
- appActions().setAutoRouterActiveModel(
218
- targetId && targetId !== defaultModelId ? (targetLabel ?? null) : null
219
- );
220
- }
221
- } catch { /* non-fatal — keep using whatever agent is already active */ }
222
-
223
- // Capture the agent for this turn by value now that routing is decided.
224
- // Several awaits follow before chat() actually dispatches; reading
225
- // `agentRef.current` fresh at each of those points would let a
226
- // concurrent /model switch (its Agent.create() resolves async, on its
227
- // own timeline) retarget a request that's already committed to this turn.
228
- const dispatchAgent = agentRef.current;
229
-
230
- if (!options?.silent) {
231
- sessionActions().addUserMessage(value);
232
- }
233
-
234
- sessionActions().setCurrentCommand(value);
235
- sessionActions().setThinking(true);
236
-
237
- const { onUserPromptSubmit } = await import('../../hooks/index.js');
238
- const injectedContext = await onUserPromptSubmit(value, getState().session.sessionId, process.cwd());
239
-
240
- if (injectedContext) {
241
- if (debugRef.current) {
242
- console.log('[DEBUG] Hook injected context:', injectedContext);
243
- }
244
- sessionActions().addAssistantMessage('[Hook] ' + injectedContext);
245
- }
246
-
247
- await ctxManager.addMessage('user', value);
248
-
249
- // Auto-compact when context approaches 80% of the token limit
250
- {
251
- const currentTokens = ctxManager.getTokenCount();
252
- const runtimeConfig = getState().config.config;
253
- const maxCtx = runtimeConfig?.maxContextTokens ?? 200000;
254
- if (currentTokens > 0 && currentTokens >= maxCtx * 0.8) {
255
- try {
256
- sessionActions().setCompacting(true);
257
- sessionActions().addAssistantMessage('⟳ Context near limit - auto-compacting...');
258
- const { CompactionService } = await import('../../context/CompactionService.js');
259
- const ctxMsgs = ctxManager.getMessages();
260
- const msgs = ctxMsgs.map((m: { role: string; content: string }) => ({
261
- role: m.role as Message['role'],
262
- content: m.content,
263
- }));
264
- const result = await CompactionService.compact(msgs, {
265
- modelName: modelRef.current || 'claude-sonnet-4-6',
266
- maxContextTokens: maxCtx,
267
- chatService: dispatchAgent?.getChatService(),
268
- trigger: 'auto',
269
- actualPreTokens: currentTokens,
270
- });
271
- if (result.success) {
272
- const { nanoid } = await import('nanoid');
273
- ctxManager.replaceMessages(result.compactedMessages.map((m: Message) => ({
274
- id: nanoid(),
275
- role: m.role as 'user' | 'assistant' | 'system' | 'tool',
276
- content: m.content,
277
- timestamp: Date.now(),
278
- })));
279
- ctxManager.updateTokenCount(result.postTokens);
280
- const saved = result.preTokens - result.postTokens;
281
- sessionActions().addAssistantMessage(
282
- `✓ Auto-compact: ${result.preTokens.toLocaleString()} → ${result.postTokens.toLocaleString()} tokens (−${saved.toLocaleString()})`
283
- );
284
- }
285
- } catch { /* non-fatal */ }
286
- finally { sessionActions().setCompacting(false); }
287
- }
288
- }
289
-
290
- // Dual-path streaming: mutable buffer + store
291
- const streamingMessageId = sessionActions().startStreamingMessage();
292
- // Wire to store so Escape/Ctrl+C can actually abort the agent
293
- const abortController = commandActions().createAbortController();
294
-
295
- try {
296
- const contextMessages = ctxManager.getMessages();
297
- const modelName = modelRef.current || 'claude-sonnet-4-6';
298
-
299
- const inputTokens = TokenCounter.countTokens(
300
- contextMessages.map(m => ({ role: m.role as Message['role'], content: m.content })),
301
- modelName
302
- );
303
-
304
- const { nanoid } = await import('nanoid');
305
-
306
- const chatContext = {
307
- sessionId: ctxManager.getCurrentSessionId() || getState().session.sessionId,
308
- messages: contextMessages.map(m => ({
309
- role: m.role as Message['role'],
310
- content: m.content,
311
- })),
312
- confirmationHandler: confirmationHandlerRef.current,
313
- permissionMode: getState().config.config?.defaultPermissionMode || 'default',
314
- };
315
-
316
- const result = await dispatchAgent!.chat(value, chatContext, {
317
- signal: abortController.signal,
318
- onStreamEvent: (event) => {
319
- if (!abortController.signal.aborted) {
320
- applyStreamEvent(event);
321
- }
322
- },
323
- onToolCallStart: (toolCall) => {
324
- if (abortController.signal.aborted) return;
325
- sessionActions().flushStreamBuffer(streamingMessageId);
326
- const name = toolCall.function?.name || 'tool';
327
- const toolId = toolCall.id || `${name}-${Date.now()}`;
328
- sessionActions().addContentBlock(streamingMessageId, {
329
- type: 'tool_use',
330
- id: toolId,
331
- name,
332
- input: '',
333
- status: 'running',
334
- startedAt: Date.now(),
335
- });
336
- },
337
- onToolResult: (_toolCall: ToolCall, toolResult: ToolResult) => {
338
- if (abortController.signal.aborted) return;
339
- const toolId = _toolCall.id;
340
- const isError = !toolResult.success;
341
- finishToolCallInBuffer(toolId, isError);
342
- const bufferedCalls = getBufferedToolCalls();
343
- const bufferedCall = bufferedCalls.find(tc => tc.id === toolId);
344
- if (bufferedCall?.arguments) {
345
- sessionActions().setToolCallInput(streamingMessageId, toolId, bufferedCall.arguments);
346
- }
347
- sessionActions().updateToolCallStatus(streamingMessageId, toolId, isError ? 'error' : 'success', Date.now());
348
- const DISPLAY_CONTENT_CAP = 3000; // diffs need more room than plain text results
349
- const resultContent = toolResult.error
350
- ? (toolResult.error.length > 200 ? toolResult.error.slice(0, 200) + '...' : toolResult.error)
351
- : (toolResult.displayContent
352
- ? (toolResult.displayContent.length > DISPLAY_CONTENT_CAP ? toolResult.displayContent.slice(0, DISPLAY_CONTENT_CAP) + '...' : toolResult.displayContent)
353
- : '');
354
- sessionActions().addToolResultBlock(streamingMessageId, toolId, resultContent, isError);
355
- },
356
- });
357
-
358
- sessionActions().finishStreamingMessage(streamingMessageId);
359
- await ctxManager.addMessage('assistant', result);
360
-
361
- const outputTokens = TokenCounter.countTextTokens(result, modelName);
362
- const totalTokens = inputTokens + outputTokens;
363
- ctxManager.updateTokenCount(totalTokens);
364
-
365
- const currentTokenUsage = getState().session.tokenUsage;
366
- const prevModel = currentTokenUsage.modelBreakdown[modelName] ?? { inputTokens: 0, outputTokens: 0 };
367
- sessionActions().updateTokenUsage({
368
- inputTokens: currentTokenUsage.inputTokens + inputTokens,
369
- outputTokens: currentTokenUsage.outputTokens + outputTokens,
370
- modelBreakdown: {
371
- ...currentTokenUsage.modelBreakdown,
372
- [modelName]: {
373
- inputTokens: prevModel.inputTokens + inputTokens,
374
- outputTokens: prevModel.outputTokens + outputTokens,
375
- },
376
- },
377
- });
378
-
379
- if (debugRef.current) {
380
- console.log('[DEBUG] Token usage - input:', inputTokens, 'output:', outputTokens);
381
- console.log('[DEBUG] Total context tokens:', ctxManager.getTokenCount());
382
- }
383
-
384
- if (routedTier && routedModelId) {
385
- const { recordRouterOutcome } = await import('../../agent/routerStats.js');
386
- recordRouterOutcome(routedTier, routedModelId, true);
387
- }
388
- } catch (error) {
389
- if (routedTier && routedModelId) {
390
- const { recordRouterOutcome } = await import('../../agent/routerStats.js');
391
- recordRouterOutcome(routedTier, routedModelId, false);
392
- }
393
- if ((error as Error)?.name !== 'AbortError') {
394
- const errorContent = 'Error: ' + (error as Error).message;
395
- sessionActions().forceAppendToMessage(streamingMessageId, errorContent);
396
- sessionActions().finishStreamingMessage(streamingMessageId);
397
- await ctxManager.addMessage('assistant', errorContent);
398
- } else {
399
- sessionActions().finishStreamingMessage(streamingMessageId);
400
- }
401
- } finally {
402
- sessionActions().setCurrentCommand(null);
403
- sessionActions().setThinking(false);
404
- }
405
- }, [onSelectorRequest]);
406
-
407
- const processQueue = useCallback(async () => {
408
- const nextCommand = commandActions().dequeueCommand();
409
- if (nextCommand) {
410
- if (debugRef.current) {
411
- console.log('[DEBUG] Processing queued command:', nextCommand);
412
- }
413
- await processCommand(nextCommand);
414
- }
415
- }, [processCommand]);
416
-
417
- const handleSubmit = useCallback(async (value: string) => {
418
- if (!value.trim()) return;
419
-
420
- const currentState = getState();
421
- const currentIsThinking = currentState.session.isThinking;
422
- const currentPendingCount = currentState.command.pendingCommands.length;
423
-
424
- if (currentIsThinking) {
425
- commandActions().enqueueCommand(value);
426
- if (debugRef.current) {
427
- console.log('[DEBUG] Command queued:', value, 'Queue size:', currentPendingCount + 1);
428
- }
429
- return;
430
- }
431
-
432
- // Acquire the lock synchronously, before processCommand's first await
433
- // (dynamic imports, auto-router's Agent.create, etc.). Without this,
434
- // isThinking stays false across that gap and a second Enter press in
435
- // the same window slips past the check above instead of queueing,
436
- // producing two concurrent in-flight commands.
437
- sessionActions().setThinking(true);
438
-
439
- await processCommand(value);
440
- }, [processCommand]);
441
-
442
- return { processCommand, handleSubmit, processQueue };
443
- }
@@ -1,99 +0,0 @@
1
- /**
2
- * useConfirmation - 确认对话框状态管理
3
- *
4
- *
5
- *
6
- */
7
-
8
- import { useState, useCallback, useMemo } from 'react';
9
- import { focusActions, FocusId } from '../focus/index.js';
10
- import type { ConfirmationHandler, ConfirmationDetails, ConfirmationResponse } from '../../tools/execution/types.js';
11
-
12
- // Re-export for useCommandProcessor
13
- export type { ConfirmationHandler, ConfirmationDetails, ConfirmationResponse };
14
-
15
- /**
16
- *
17
- */
18
- interface ConfirmationState {
19
- isVisible: boolean;
20
- details: ConfirmationDetails | null;
21
- resolver: ((response: ConfirmationResponse) => void) | null;
22
- }
23
-
24
- interface UseConfirmationResult {
25
- /** 确认状态 */
26
- confirmationState: ConfirmationState;
27
- /** 确认处理器(供 Agent/Pipeline 使用) */
28
- confirmationHandler: ConfirmationHandler;
29
- /** 处理用户响应 */
30
- handleResponse: (response: ConfirmationResponse) => void;
31
- /** 显示确认对话框 */
32
- showConfirmation: (details: ConfirmationDetails) => Promise<ConfirmationResponse>;
33
- }
34
-
35
- /**
36
- *
37
- *
38
- *
39
- */
40
- export const useConfirmation = (): UseConfirmationResult => {
41
- const [confirmationState, setConfirmationState] = useState<ConfirmationState>({
42
- isVisible: false,
43
- details: null,
44
- resolver: null,
45
- });
46
-
47
- /**
48
- *
49
- *
50
- */
51
- const showConfirmation = useCallback(
52
- (details: ConfirmationDetails): Promise<ConfirmationResponse> => {
53
- return new Promise((resolve) => {
54
- // 同步设置焦点 — 在 React 调度 render 之前就生
55
- focusActions.setFocus(FocusId.CONFIRMATION_PROMPT);
56
- setConfirmationState({
57
- isVisible: true,
58
- details,
59
- resolver: resolve,
60
- });
61
- });
62
- },
63
- []
64
- );
65
-
66
- /**
67
- *
68
- *
69
- */
70
- const handleResponse = useCallback((response: ConfirmationResponse) => {
71
- if (confirmationState.resolver) {
72
- confirmationState.resolver(response);
73
- }
74
- // 同步恢复焦
75
- focusActions.setFocus(FocusId.MAIN_INPUT);
76
- setConfirmationState({
77
- isVisible: false,
78
- details: null,
79
- resolver: null,
80
- });
81
- }, [confirmationState.resolver]);
82
-
83
- /**
84
- *
85
- */
86
- const confirmationHandler: ConfirmationHandler = useMemo(
87
- () => ({
88
- requestConfirmation: showConfirmation,
89
- }),
90
- [showConfirmation]
91
- );
92
-
93
- return {
94
- confirmationState,
95
- confirmationHandler,
96
- handleResponse,
97
- showConfirmation,
98
- };
99
- };
@@ -1,100 +0,0 @@
1
- /**
2
- * useCtrlCHandler - Ctrl+Z / Ctrl+C 处理
3
- *
4
- * Ctrl+Z 主退出键 (Kitty 中 Ctrl+C 用于复制)
5
- * Ctrl+C 备用退出键
6
- *
7
- * - 有任务运行时:请求中断
8
- * - 无任务时:退出应用
9
- */
10
-
11
- import { useCallback, useRef } from 'react';
12
- import { useApp, useInput } from 'ink';
13
- import { getState } from '../../store/index.js';
14
-
15
- interface CtrlCHandlerOptions {
16
- /** 中断回调 */
17
- onInterrupt?: () => void;
18
- /**
19
- *
20
- *
21
- */
22
- onBeforeExit?: () => boolean | void;
23
- /** 强制退出前的确认时间(毫秒) */
24
- forceExitDelay?: number;
25
- }
26
-
27
- interface CtrlCHandlerResult {
28
- /** 处理退出信号 (Ctrl+Z / Ctrl+C) */
29
- handleExit: () => void;
30
- /** 重置强制退出状态 */
31
- resetForceExit: () => void;
32
- }
33
-
34
- /**
35
- * 退出处理 Hook
36
- *
37
- * Ctrl+Z = 主退出 (Kitty 中 Ctrl+C 用于复制)
38
- * Ctrl+C = 备用退出
39
- */
40
- export const useCtrlCHandler = (options: CtrlCHandlerOptions): CtrlCHandlerResult => {
41
- const { onInterrupt, onBeforeExit, forceExitDelay = 2000 } = options;
42
- const { exit } = useApp();
43
-
44
- const lastExitTime = useRef<number>(0);
45
- const forceExitPending = useRef(false);
46
-
47
- const doExit = useCallback(() => {
48
- if (onBeforeExit) {
49
- const handled = onBeforeExit();
50
- if (handled === true) {
51
- return;
52
- }
53
- }
54
- exit();
55
- setTimeout(() => process.exit(0), 50);
56
- }, [onBeforeExit, exit]);
57
-
58
- const handleExit = useCallback(() => {
59
- const now = Date.now();
60
- const timeSinceLastExit = now - lastExitTime.current;
61
-
62
- const hasRunningTask = getState().session.isThinking;
63
-
64
- if (hasRunningTask) {
65
- if (forceExitPending.current && timeSinceLastExit < forceExitDelay) {
66
- // 第二次退出信号:强制退出
67
- doExit();
68
- return;
69
- }
70
-
71
- // 第一次退出信号:请求中断
72
- forceExitPending.current = true;
73
- lastExitTime.current = now;
74
-
75
- if (onInterrupt) {
76
- onInterrupt();
77
- }
78
- } else {
79
- // 没有任务,直接退出
80
- doExit();
81
- }
82
- }, [onInterrupt, forceExitDelay, doExit]);
83
-
84
- // Ctrl+Z = primary exit key (Ctrl+C reserved for terminal copy in Kitty & others)
85
- useInput((input, key) => {
86
- if (input === 'z' && key.ctrl) {
87
- handleExit();
88
- }
89
- });
90
-
91
- const resetForceExit = useCallback(() => {
92
- forceExitPending.current = false;
93
- lastExitTime.current = 0;
94
- }, []);
95
-
96
- return {
97
- handleExit,
98
- resetForceExit,
99
- };
100
- };