mistagent 0.1.18 → 0.1.20

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 (200) hide show
  1. package/dist/src/components/App.d.ts +2 -0
  2. package/dist/src/components/App.js +23 -5
  3. package/dist/src/components/Composer.js +2 -1
  4. package/dist/src/components/Header.js +74 -166
  5. package/dist/src/components/MainContent.js +3 -14
  6. package/dist/src/components/shared/MarkdownRenderer.js +15 -26
  7. package/dist/src/components/shared/TextInput.js +62 -3
  8. package/dist/src/contexts/ChatContext.d.ts +2 -0
  9. package/dist/src/contexts/ChatContext.js +1 -1
  10. package/dist/src/hooks/useChat.js +71 -10
  11. package/dist/src/main.js +62 -4
  12. package/dist/src/types/api.d.ts +4 -0
  13. package/dist/src/utils/config.d.ts +2 -0
  14. package/dist/src/utils/config.js +23 -0
  15. package/dist/src/utils/constants.d.ts +1 -1
  16. package/dist/src/utils/constants.js +1 -1
  17. package/dist/src/utils/fileTunnel.d.ts +7 -2
  18. package/dist/src/utils/fileTunnel.js +376 -5
  19. package/dist/src/utils/markdown.d.ts +10 -0
  20. package/dist/src/utils/markdown.js +223 -0
  21. package/dist/src/utils/updateChecker.js +10 -4
  22. package/package.json +3 -2
  23. package/dist/index.d.ts.map +0 -1
  24. package/dist/index.js.map +0 -1
  25. package/dist/src/api/auth.d.ts.map +0 -1
  26. package/dist/src/api/auth.js.map +0 -1
  27. package/dist/src/api/chat.d.ts.map +0 -1
  28. package/dist/src/api/chat.js.map +0 -1
  29. package/dist/src/api/client.d.ts.map +0 -1
  30. package/dist/src/api/client.js.map +0 -1
  31. package/dist/src/api/models.d.ts.map +0 -1
  32. package/dist/src/api/models.js.map +0 -1
  33. package/dist/src/api/sessions.d.ts.map +0 -1
  34. package/dist/src/api/sessions.js.map +0 -1
  35. package/dist/src/api/skills.d.ts.map +0 -1
  36. package/dist/src/api/skills.js.map +0 -1
  37. package/dist/src/api/tools.d.ts.map +0 -1
  38. package/dist/src/api/tools.js.map +0 -1
  39. package/dist/src/api/tunnel.d.ts.map +0 -1
  40. package/dist/src/api/tunnel.js.map +0 -1
  41. package/dist/src/components/App.d.ts.map +0 -1
  42. package/dist/src/components/App.js.map +0 -1
  43. package/dist/src/components/AppLayout.d.ts.map +0 -1
  44. package/dist/src/components/AppLayout.js.map +0 -1
  45. package/dist/src/components/Composer.d.ts.map +0 -1
  46. package/dist/src/components/Composer.js.map +0 -1
  47. package/dist/src/components/Footer.d.ts.map +0 -1
  48. package/dist/src/components/Footer.js.map +0 -1
  49. package/dist/src/components/Header.d.ts.map +0 -1
  50. package/dist/src/components/Header.js.map +0 -1
  51. package/dist/src/components/HistoryItemDisplay.d.ts.map +0 -1
  52. package/dist/src/components/HistoryItemDisplay.js.map +0 -1
  53. package/dist/src/components/InputPrompt.d.ts.map +0 -1
  54. package/dist/src/components/InputPrompt.js.map +0 -1
  55. package/dist/src/components/LoadingIndicator.d.ts.map +0 -1
  56. package/dist/src/components/LoadingIndicator.js.map +0 -1
  57. package/dist/src/components/LoginPrompt.d.ts.map +0 -1
  58. package/dist/src/components/LoginPrompt.js.map +0 -1
  59. package/dist/src/components/MainContent.d.ts.map +0 -1
  60. package/dist/src/components/MainContent.js.map +0 -1
  61. package/dist/src/components/ModelPicker.d.ts.map +0 -1
  62. package/dist/src/components/ModelPicker.js.map +0 -1
  63. package/dist/src/components/SessionPicker.d.ts.map +0 -1
  64. package/dist/src/components/SessionPicker.js.map +0 -1
  65. package/dist/src/components/SuggestionsDisplay.d.ts.map +0 -1
  66. package/dist/src/components/SuggestionsDisplay.js.map +0 -1
  67. package/dist/src/components/ThemePicker.d.ts.map +0 -1
  68. package/dist/src/components/ThemePicker.js.map +0 -1
  69. package/dist/src/components/messages/AssistantMessage.d.ts.map +0 -1
  70. package/dist/src/components/messages/AssistantMessage.js.map +0 -1
  71. package/dist/src/components/messages/CommandResult.d.ts.map +0 -1
  72. package/dist/src/components/messages/CommandResult.js.map +0 -1
  73. package/dist/src/components/messages/ErrorMessage.d.ts.map +0 -1
  74. package/dist/src/components/messages/ErrorMessage.js.map +0 -1
  75. package/dist/src/components/messages/InfoMessage.d.ts.map +0 -1
  76. package/dist/src/components/messages/InfoMessage.js.map +0 -1
  77. package/dist/src/components/messages/ModelMessage.d.ts.map +0 -1
  78. package/dist/src/components/messages/ModelMessage.js.map +0 -1
  79. package/dist/src/components/messages/SessionMessage.d.ts.map +0 -1
  80. package/dist/src/components/messages/SessionMessage.js.map +0 -1
  81. package/dist/src/components/messages/ToolCallMessage.d.ts.map +0 -1
  82. package/dist/src/components/messages/ToolCallMessage.js.map +0 -1
  83. package/dist/src/components/messages/UserMessage.d.ts.map +0 -1
  84. package/dist/src/components/messages/UserMessage.js.map +0 -1
  85. package/dist/src/components/shared/HorizontalLine.d.ts.map +0 -1
  86. package/dist/src/components/shared/HorizontalLine.js.map +0 -1
  87. package/dist/src/components/shared/MarkdownRenderer.d.ts.map +0 -1
  88. package/dist/src/components/shared/MarkdownRenderer.js.map +0 -1
  89. package/dist/src/components/shared/Spinner.d.ts.map +0 -1
  90. package/dist/src/components/shared/Spinner.js.map +0 -1
  91. package/dist/src/components/shared/TextInput.d.ts.map +0 -1
  92. package/dist/src/components/shared/TextInput.js.map +0 -1
  93. package/dist/src/contexts/AppContext.d.ts.map +0 -1
  94. package/dist/src/contexts/AppContext.js.map +0 -1
  95. package/dist/src/contexts/ChatContext.d.ts.map +0 -1
  96. package/dist/src/contexts/ChatContext.js.map +0 -1
  97. package/dist/src/contexts/KeypressContext.d.ts.map +0 -1
  98. package/dist/src/contexts/KeypressContext.js.map +0 -1
  99. package/dist/src/contexts/ModelContext.d.ts.map +0 -1
  100. package/dist/src/contexts/ModelContext.js.map +0 -1
  101. package/dist/src/contexts/SessionContext.d.ts.map +0 -1
  102. package/dist/src/contexts/SessionContext.js.map +0 -1
  103. package/dist/src/contexts/UIContext.d.ts.map +0 -1
  104. package/dist/src/contexts/UIContext.js.map +0 -1
  105. package/dist/src/hooks/useChat.d.ts.map +0 -1
  106. package/dist/src/hooks/useChat.js.map +0 -1
  107. package/dist/src/hooks/useFileCompletion.d.ts.map +0 -1
  108. package/dist/src/hooks/useFileCompletion.js.map +0 -1
  109. package/dist/src/hooks/useInputHistory.d.ts.map +0 -1
  110. package/dist/src/hooks/useInputHistory.js.map +0 -1
  111. package/dist/src/hooks/useKeypress.d.ts.map +0 -1
  112. package/dist/src/hooks/useKeypress.js.map +0 -1
  113. package/dist/src/hooks/useLoadingIndicator.d.ts.map +0 -1
  114. package/dist/src/hooks/useLoadingIndicator.js.map +0 -1
  115. package/dist/src/hooks/usePasteBuffer.d.ts.map +0 -1
  116. package/dist/src/hooks/usePasteBuffer.js.map +0 -1
  117. package/dist/src/hooks/useSlashCommand.d.ts.map +0 -1
  118. package/dist/src/hooks/useSlashCommand.js.map +0 -1
  119. package/dist/src/hooks/useStdinInterceptor.d.ts.map +0 -1
  120. package/dist/src/hooks/useStdinInterceptor.js.map +0 -1
  121. package/dist/src/hooks/useSymbolCompletion.d.ts.map +0 -1
  122. package/dist/src/hooks/useSymbolCompletion.js.map +0 -1
  123. package/dist/src/hooks/useTextBuffer.d.ts.map +0 -1
  124. package/dist/src/hooks/useTextBuffer.js.map +0 -1
  125. package/dist/src/main.d.ts.map +0 -1
  126. package/dist/src/main.js.map +0 -1
  127. package/dist/src/tools/code-analyzer/config/ignore-service.d.ts.map +0 -1
  128. package/dist/src/tools/code-analyzer/config/ignore-service.js.map +0 -1
  129. package/dist/src/tools/code-analyzer/config/supported-languages.d.ts.map +0 -1
  130. package/dist/src/tools/code-analyzer/config/supported-languages.js.map +0 -1
  131. package/dist/src/tools/code-analyzer/core/graph/graph.d.ts.map +0 -1
  132. package/dist/src/tools/code-analyzer/core/graph/graph.js.map +0 -1
  133. package/dist/src/tools/code-analyzer/core/graph/types.d.ts.map +0 -1
  134. package/dist/src/tools/code-analyzer/core/graph/types.js.map +0 -1
  135. package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.d.ts.map +0 -1
  136. package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.js.map +0 -1
  137. package/dist/src/tools/code-analyzer/core/ingestion/call-processor.d.ts.map +0 -1
  138. package/dist/src/tools/code-analyzer/core/ingestion/call-processor.js.map +0 -1
  139. package/dist/src/tools/code-analyzer/core/ingestion/community-processor.d.ts.map +0 -1
  140. package/dist/src/tools/code-analyzer/core/ingestion/community-processor.js.map +0 -1
  141. package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.d.ts.map +0 -1
  142. package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.js.map +0 -1
  143. package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.d.ts.map +0 -1
  144. package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.js.map +0 -1
  145. package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.d.ts.map +0 -1
  146. package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.js.map +0 -1
  147. package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.d.ts.map +0 -1
  148. package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.js.map +0 -1
  149. package/dist/src/tools/code-analyzer/core/ingestion/import-processor.d.ts.map +0 -1
  150. package/dist/src/tools/code-analyzer/core/ingestion/import-processor.js.map +0 -1
  151. package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.d.ts.map +0 -1
  152. package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.js.map +0 -1
  153. package/dist/src/tools/code-analyzer/core/ingestion/pipeline.d.ts.map +0 -1
  154. package/dist/src/tools/code-analyzer/core/ingestion/pipeline.js.map +0 -1
  155. package/dist/src/tools/code-analyzer/core/ingestion/process-processor.d.ts.map +0 -1
  156. package/dist/src/tools/code-analyzer/core/ingestion/process-processor.js.map +0 -1
  157. package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.d.ts.map +0 -1
  158. package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.js.map +0 -1
  159. package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.d.ts.map +0 -1
  160. package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.js.map +0 -1
  161. package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.d.ts.map +0 -1
  162. package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.js.map +0 -1
  163. package/dist/src/tools/code-analyzer/core/ingestion/utils.d.ts.map +0 -1
  164. package/dist/src/tools/code-analyzer/core/ingestion/utils.js.map +0 -1
  165. package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.d.ts.map +0 -1
  166. package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.js.map +0 -1
  167. package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.d.ts.map +0 -1
  168. package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.js.map +0 -1
  169. package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.d.ts.map +0 -1
  170. package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.js.map +0 -1
  171. package/dist/src/tools/code-analyzer/index.d.ts.map +0 -1
  172. package/dist/src/tools/code-analyzer/index.js.map +0 -1
  173. package/dist/src/tools/code-analyzer/lib/utils.d.ts.map +0 -1
  174. package/dist/src/tools/code-analyzer/lib/utils.js.map +0 -1
  175. package/dist/src/tools/code-analyzer/types/pipeline.d.ts.map +0 -1
  176. package/dist/src/tools/code-analyzer/types/pipeline.js.map +0 -1
  177. package/dist/src/types/api.d.ts.map +0 -1
  178. package/dist/src/types/api.js.map +0 -1
  179. package/dist/src/types/history.d.ts.map +0 -1
  180. package/dist/src/types/history.js.map +0 -1
  181. package/dist/src/utils/colors.d.ts.map +0 -1
  182. package/dist/src/utils/colors.js.map +0 -1
  183. package/dist/src/utils/config.d.ts.map +0 -1
  184. package/dist/src/utils/config.js.map +0 -1
  185. package/dist/src/utils/constants.d.ts.map +0 -1
  186. package/dist/src/utils/constants.js.map +0 -1
  187. package/dist/src/utils/fileRef.d.ts.map +0 -1
  188. package/dist/src/utils/fileRef.js.map +0 -1
  189. package/dist/src/utils/fileTunnel.d.ts.map +0 -1
  190. package/dist/src/utils/fileTunnel.js.map +0 -1
  191. package/dist/src/utils/formatters.d.ts.map +0 -1
  192. package/dist/src/utils/formatters.js.map +0 -1
  193. package/dist/src/utils/pasteUtils.d.ts.map +0 -1
  194. package/dist/src/utils/pasteUtils.js.map +0 -1
  195. package/dist/src/utils/skillScanner.d.ts.map +0 -1
  196. package/dist/src/utils/skillScanner.js.map +0 -1
  197. package/dist/src/utils/textUtils.d.ts.map +0 -1
  198. package/dist/src/utils/textUtils.js.map +0 -1
  199. package/dist/src/utils/updateChecker.d.ts.map +0 -1
  200. package/dist/src/utils/updateChecker.js.map +0 -1
@@ -9,12 +9,60 @@ import { executeFileOperation, isSensitiveOperation, isAlwaysConfirmRequired } f
9
9
  import { DEBUG_LOG_AITEXT, DEBUG_AITEXT_FILE, CODE_ANALYZER_DIR } from '../utils/config.js';
10
10
  import { useChatState, useChatDispatch, StreamingState, } from '../contexts/ChatContext.js';
11
11
  import { useModelState } from '../contexts/ModelContext.js';
12
+ /**
13
+ * Throttled token buffer — accumulates tokens and flushes at most every
14
+ * FLUSH_INTERVAL_MS to reduce Ink re-renders and prevent iTerm scroll jitter.
15
+ */
16
+ const FLUSH_INTERVAL_MS = 50;
17
+ function createTokenBuffer(dispatch) {
18
+ let buffer = '';
19
+ let timer = null;
20
+ function flush() {
21
+ timer = null;
22
+ if (buffer) {
23
+ const content = buffer;
24
+ buffer = '';
25
+ dispatch({ type: 'STREAM_TOKEN', content });
26
+ }
27
+ }
28
+ return {
29
+ push(content) {
30
+ buffer += content;
31
+ if (!timer) {
32
+ timer = setTimeout(flush, FLUSH_INTERVAL_MS);
33
+ }
34
+ },
35
+ /** Flush any remaining buffered tokens immediately (e.g. on stream end) */
36
+ flushNow() {
37
+ if (timer) {
38
+ clearTimeout(timer);
39
+ timer = null;
40
+ }
41
+ if (buffer) {
42
+ const content = buffer;
43
+ buffer = '';
44
+ dispatch({ type: 'STREAM_TOKEN', content });
45
+ }
46
+ },
47
+ dispose() {
48
+ if (timer) {
49
+ clearTimeout(timer);
50
+ timer = null;
51
+ }
52
+ buffer = '';
53
+ },
54
+ };
55
+ }
12
56
  export function useChat() {
13
57
  const state = useChatState();
14
58
  const dispatch = useChatDispatch();
15
59
  const { currentModel } = useModelState();
16
60
  const abortRef = useRef(null);
17
61
  const toolApprovalResolveRef = useRef(null);
62
+ // Ref 保持 sessionAutoApproved 最新值,避免 stale closure 问题
63
+ // (plan confirm 流式执行中 handleToolExecuteRequest 闭包会捕获旧值)
64
+ const sessionAutoApprovedRef = useRef(state.sessionAutoApproved);
65
+ sessionAutoApprovedRef.current = state.sessionAutoApproved;
18
66
  /**
19
67
  * 请求用户批准敏感操作。返回 Promise,在用户做出选择后 resolve。
20
68
  */
@@ -46,9 +94,10 @@ export function useChat() {
46
94
  name: `[local] ${data.tool_name}`,
47
95
  args: JSON.stringify(data.args),
48
96
  });
49
- const sensitive = isSensitiveOperation(data.tool_name);
97
+ const sensitive = isSensitiveOperation(data.tool_name, data.args);
50
98
  const alwaysConfirm = isAlwaysConfirmRequired(data);
51
- const autoApproved = state.sessionAutoApproved.has(data.tool_name);
99
+ // 使用 ref 读取最新值,避免 stale closure(plan confirm 流式中尤为重要)
100
+ const autoApproved = sessionAutoApprovedRef.current.has(data.tool_name);
52
101
  // 需要确认:(1) 敏感操作且未自动批准,或 (2) 始终需要确认的高危操作
53
102
  if (sensitive && (!autoApproved || alwaysConfirm)) {
54
103
  const decision = await requestToolApproval(data, alwaysConfirm);
@@ -75,7 +124,7 @@ export function useChat() {
75
124
  error: 'Failed to submit tunnel result',
76
125
  }).catch(() => { });
77
126
  }
78
- }, [dispatch, state.sessionAutoApproved, requestToolApproval]);
127
+ }, [dispatch, requestToolApproval]);
79
128
  const sendMessage = useCallback(async (message) => {
80
129
  if (state.streamingState !== StreamingState.Idle)
81
130
  return;
@@ -100,6 +149,7 @@ export function useChat() {
100
149
  dispatch({ type: 'ADD_ITEM', item: { type: 'user', text: displayText } });
101
150
  // Enter streaming state immediately so UI shows loading indicator
102
151
  dispatch({ type: 'STREAM_START', threadId: state.threadId ?? 'pending' });
152
+ const tokenBuf = createTokenBuffer(dispatch);
103
153
  try {
104
154
  const reader = await chatApi.stream(finalAiText, state.threadId, currentModel?.model, currentModel?.provider, state.forceMode === 'plan' ? 'plan' : null);
105
155
  const decoder = new TextDecoder();
@@ -126,7 +176,7 @@ export function useChat() {
126
176
  }
127
177
  case 'text': {
128
178
  const data = JSON.parse(event.data);
129
- dispatch({ type: 'STREAM_TOKEN', content: data.content });
179
+ tokenBuf.push(data.content);
130
180
  break;
131
181
  }
132
182
  case 'status': {
@@ -158,10 +208,12 @@ export function useChat() {
158
208
  break;
159
209
  }
160
210
  case 'plan_confirm': {
211
+ tokenBuf.flushNow();
161
212
  const data = JSON.parse(event.data);
162
213
  const planData = data.content || {};
163
214
  const steps = planData.plan || [];
164
- dispatch({ type: 'PLAN_CONFIRM', steps });
215
+ const planContent = planData.content || '';
216
+ dispatch({ type: 'PLAN_CONFIRM', steps, content: planContent });
165
217
  // Stop reading the SSE stream — the plan review UI takes over.
166
218
  abort.abort();
167
219
  break;
@@ -192,19 +244,20 @@ export function useChat() {
192
244
  const planText = '\n**Plan:**\n' +
193
245
  meta.plan.map((s, i) => `${i + 1}. ${s}`).join('\n') +
194
246
  '\n\n';
195
- dispatch({ type: 'STREAM_TOKEN', content: planText });
247
+ tokenBuf.push(planText);
196
248
  }
197
249
  // Show replan result
198
250
  if (nodeName === 'replan' && meta.plan) {
199
251
  const replanText = '\n**Updated Plan:**\n' +
200
252
  meta.plan.map((s, i) => `${i + 1}. ${s}`).join('\n') +
201
253
  '\n\n';
202
- dispatch({ type: 'STREAM_TOKEN', content: replanText });
254
+ tokenBuf.push(replanText);
203
255
  }
204
256
  dispatch({ type: 'TOOL_CALL_DONE' });
205
257
  break;
206
258
  }
207
259
  case 'done': {
260
+ tokenBuf.flushNow();
208
261
  dispatch({ type: 'STREAM_DONE' });
209
262
  break;
210
263
  }
@@ -233,10 +286,12 @@ export function useChat() {
233
286
  }
234
287
  // If stream ended without a done event, finalize
235
288
  if (!streamDoneReceived && !abort.signal.aborted) {
289
+ tokenBuf.flushNow();
236
290
  dispatch({ type: 'STREAM_DONE' });
237
291
  }
238
292
  }
239
293
  catch (err) {
294
+ tokenBuf.dispose();
240
295
  const errorMessage = err instanceof Error ? err.message : 'Unknown error';
241
296
  dispatch({ type: 'STREAM_ERROR', error: errorMessage });
242
297
  }
@@ -249,6 +304,7 @@ export function useChat() {
249
304
  const decoder = new TextDecoder();
250
305
  const abort = new AbortController();
251
306
  abortRef.current = abort;
307
+ const tokenBuf = createTokenBuffer(dispatch);
252
308
  dispatch({ type: 'STREAM_START', threadId: state.threadId ?? 'pending' });
253
309
  const parser = createParser({
254
310
  onEvent(event) {
@@ -265,7 +321,7 @@ export function useChat() {
265
321
  }
266
322
  case 'text': {
267
323
  const data = JSON.parse(event.data);
268
- dispatch({ type: 'STREAM_TOKEN', content: data.content });
324
+ tokenBuf.push(data.content);
269
325
  break;
270
326
  }
271
327
  case 'tool_call': {
@@ -305,23 +361,27 @@ export function useChat() {
305
361
  if (data.content === 'replan' && meta.plan) {
306
362
  const text = '\n**Updated Plan:**\n' +
307
363
  meta.plan.map((s, i) => `${i + 1}. ${s}`).join('\n') + '\n\n';
308
- dispatch({ type: 'STREAM_TOKEN', content: text });
364
+ tokenBuf.push(text);
309
365
  }
310
366
  dispatch({ type: 'TOOL_CALL_DONE' });
311
367
  break;
312
368
  }
313
369
  case 'plan_confirm': {
370
+ tokenBuf.flushNow();
314
371
  const data = JSON.parse(event.data);
315
372
  const planData = data.content || {};
316
373
  const steps = planData.plan || [];
317
- dispatch({ type: 'PLAN_CONFIRM', steps });
374
+ const planContent = planData.content || '';
375
+ dispatch({ type: 'PLAN_CONFIRM', steps, content: planContent });
318
376
  break;
319
377
  }
320
378
  case 'done': {
379
+ tokenBuf.flushNow();
321
380
  dispatch({ type: 'STREAM_DONE' });
322
381
  break;
323
382
  }
324
383
  case 'error': {
384
+ tokenBuf.dispose();
325
385
  const data = JSON.parse(event.data);
326
386
  dispatch({ type: 'STREAM_ERROR', error: data.content });
327
387
  break;
@@ -342,6 +402,7 @@ export function useChat() {
342
402
  parser.feed(text);
343
403
  }
344
404
  if (!streamDoneReceived && !abort.signal.aborted) {
405
+ tokenBuf.flushNow();
345
406
  dispatch({ type: 'STREAM_DONE' });
346
407
  }
347
408
  abortRef.current = null;
package/dist/src/main.js CHANGED
@@ -9,7 +9,7 @@ import { toolsApi } from './api/tools.js';
9
9
  import { skillsApi } from './api/skills.js';
10
10
  import { scanLocalSkills } from './utils/skillScanner.js';
11
11
  import { sessionsApi } from './api/sessions.js';
12
- import { getServerUrl, loadToken, saveToken, clearToken, loadTheme, loadModel } from './utils/config.js';
12
+ import { getServerUrl, loadToken, saveToken, clearToken, loadTheme, loadModel, saveRefreshToken, loadRefreshToken } from './utils/config.js';
13
13
  import chalk from 'chalk';
14
14
  import { setTheme, palette } from './utils/colors.js';
15
15
  import { VERSION } from './utils/constants.js';
@@ -64,7 +64,6 @@ export async function main() {
64
64
  const muted = chalk.hex(palette.textMuted);
65
65
  const accent = chalk.hex(palette.accent);
66
66
  const rawMsg = err instanceof Error ? err.message : String(err);
67
- // Detect specific failure reasons
68
67
  let reason;
69
68
  let hint;
70
69
  if (rawMsg.includes('ECONNREFUSED') || rawMsg.includes('fetch failed')) {
@@ -101,6 +100,41 @@ export async function main() {
101
100
  catch {
102
101
  authStatus = { auth_enabled: false };
103
102
  }
103
+ // Token refresh (Supabase tokens expire in ~1 hour)
104
+ let refreshTimer = null;
105
+ const startTokenRefresh = () => {
106
+ if (refreshTimer)
107
+ return;
108
+ const supabaseUrl = authStatus.supabase_url;
109
+ const supabaseKey = authStatus.supabase_anon_key;
110
+ if (!supabaseUrl || !supabaseKey)
111
+ return;
112
+ refreshTimer = setInterval(async () => {
113
+ const rt = loadRefreshToken();
114
+ if (!rt)
115
+ return;
116
+ try {
117
+ const res = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
118
+ method: 'POST',
119
+ headers: {
120
+ 'Content-Type': 'application/json',
121
+ 'apikey': supabaseKey,
122
+ },
123
+ body: JSON.stringify({ refresh_token: rt }),
124
+ });
125
+ if (res.ok) {
126
+ const data = await res.json();
127
+ client.setToken(data.access_token);
128
+ saveToken(data.access_token);
129
+ saveRefreshToken(data.refresh_token);
130
+ token = data.access_token;
131
+ }
132
+ }
133
+ catch {
134
+ // Silent — will retry next interval
135
+ }
136
+ }, 50 * 60 * 1000); // 50 minutes
137
+ };
104
138
  // Try to restore token
105
139
  let token = null;
106
140
  let username = null;
@@ -114,6 +148,7 @@ export async function main() {
114
148
  token = savedToken;
115
149
  username = me.username;
116
150
  isAuthenticated = true;
151
+ startTokenRefresh();
117
152
  }
118
153
  catch {
119
154
  client.setToken(null);
@@ -167,16 +202,27 @@ export async function main() {
167
202
  ];
168
203
  // Login handler
169
204
  let loginError = null;
170
- const renderApp = (overrides = {}) => (_jsx(App, { serverUrl: serverUrl, token: overrides.token ?? token, username: overrides.username ?? username, authEnabled: authStatus.auth_enabled, version: healthData?.version ?? VERSION, healthData: healthData, availableCommands: commands, availableTools: tools, initialModels: models, initialCurrentModel: currentModel, initialSessions: initialSessions, initialTheme: initialTheme, onLogin: handleLogin, onLogout: handleLogout, loginError: overrides.loginError ?? loginError, isAuthenticated: overrides.isAuthenticated ?? isAuthenticated }));
205
+ // Terminal size tracked outside React so rerender() can inject fresh values
206
+ // This mirrors the claude-code pattern: Ink's renderer owns resize detection,
207
+ // and we piggyback by re-calling render()/rerender() with updated props.
208
+ const getTerminalSize = () => ({
209
+ terminalWidth: process.stdout.columns || 80,
210
+ terminalHeight: process.stdout.rows || 24,
211
+ });
212
+ const renderApp = (overrides = {}) => (_jsx(App, { serverUrl: serverUrl, token: overrides.token ?? token, username: overrides.username ?? username, authEnabled: authStatus.auth_enabled, version: healthData?.version ?? VERSION, healthData: healthData, availableCommands: commands, availableTools: tools, initialModels: models, initialCurrentModel: currentModel, initialSessions: initialSessions, initialTheme: initialTheme, onLogin: handleLogin, onLogout: handleLogout, loginError: overrides.loginError ?? loginError, isAuthenticated: overrides.isAuthenticated ?? isAuthenticated, ...getTerminalSize() }));
171
213
  const handleLogin = async (user, pass) => {
172
214
  try {
173
215
  const res = await authApi.login(user, pass);
174
216
  client.setToken(res.access_token);
175
217
  saveToken(res.access_token);
218
+ if (res.refresh_token) {
219
+ saveRefreshToken(res.refresh_token);
220
+ }
176
221
  token = res.access_token;
177
222
  username = res.user.username;
178
223
  isAuthenticated = true;
179
224
  loginError = null;
225
+ startTokenRefresh();
180
226
  rerender(renderApp({ token, username, isAuthenticated: true, loginError: null }));
181
227
  }
182
228
  catch (err) {
@@ -185,6 +231,10 @@ export async function main() {
185
231
  }
186
232
  };
187
233
  const handleLogout = () => {
234
+ if (refreshTimer) {
235
+ clearInterval(refreshTimer);
236
+ refreshTimer = null;
237
+ }
188
238
  client.setToken(null);
189
239
  clearToken();
190
240
  token = null;
@@ -199,9 +249,17 @@ export async function main() {
199
249
  process.stderr.write(formatUpdateMessage(updateInfo) + '\n\n');
200
250
  }
201
251
  // Render the app
202
- const { rerender } = render(renderApp(), {
252
+ const { rerender, clear } = render(renderApp(), {
203
253
  exitOnCtrlC: false,
204
254
  kittyKeyboard: { mode: 'auto' },
205
255
  });
256
+ // On resize: clear the screen first to avoid stale rows left over when the
257
+ // Header shrinks (e.g. horizontal→compact reduces height by ~10 rows and
258
+ // Ink's in-place diff won't erase the leftover lines). Then rerender with
259
+ // fresh terminal dimensions.
260
+ process.stdout.on('resize', () => {
261
+ clear();
262
+ rerender(renderApp());
263
+ });
206
264
  }
207
265
  //# sourceMappingURL=main.js.map
@@ -1,6 +1,7 @@
1
1
  export interface LoginResponse {
2
2
  success: boolean;
3
3
  access_token: string;
4
+ refresh_token?: string;
4
5
  token_type: string;
5
6
  user: {
6
7
  user_id: string;
@@ -13,9 +14,12 @@ export interface UserInfo {
13
14
  user_id: string;
14
15
  username: string;
15
16
  display_name: string;
17
+ is_admin?: boolean;
16
18
  }
17
19
  export interface AuthStatus {
18
20
  auth_enabled: boolean;
21
+ supabase_url?: string;
22
+ supabase_anon_key?: string;
19
23
  }
20
24
  export interface SessionInfo {
21
25
  thread_id: string;
@@ -3,6 +3,8 @@ export declare function getServerUrl(cliArg?: string): string;
3
3
  export declare function saveToken(token: string): void;
4
4
  export declare function loadToken(): string | null;
5
5
  export declare function clearToken(): void;
6
+ export declare function saveRefreshToken(token: string): void;
7
+ export declare function loadRefreshToken(): string | null;
6
8
  export type ThemeMode = 'dark' | 'light';
7
9
  export declare function saveTheme(mode: ThemeMode): void;
8
10
  export declare function loadTheme(): ThemeMode;
@@ -6,6 +6,8 @@ import { homedir } from 'node:os';
6
6
  const CONFIG_DIR = join(homedir(), '.mistagent');
7
7
  // 登录 token 文件路径,文件权限 0o600 仅当前用户可读写
8
8
  const TOKEN_FILE = join(CONFIG_DIR, 'token');
9
+ // Supabase refresh token(用于自动续期 access_token)
10
+ const REFRESH_TOKEN_FILE = join(CONFIG_DIR, 'refresh_token');
9
11
  // 后端服务器默认地址,可通过 CLI 参数或环境变量 MISTAGENT_SERVER 覆盖
10
12
  // 发布时由 publish.sh 自动切换为 'https://solidity.slowmist.ai',发布后自动改回
11
13
  export const DEFAULT_SERVER_URL = 'https://solidity.slowmist.ai';
@@ -41,11 +43,32 @@ export function loadToken() {
41
43
  export function clearToken() {
42
44
  try {
43
45
  writeFileSync(TOKEN_FILE, '', { mode: 0o600 });
46
+ writeFileSync(REFRESH_TOKEN_FILE, '', { mode: 0o600 });
44
47
  }
45
48
  catch {
46
49
  // 写入失败时静默处理
47
50
  }
48
51
  }
52
+ // 保存 refresh token(Supabase token 续期用)
53
+ export function saveRefreshToken(token) {
54
+ try {
55
+ mkdirSync(CONFIG_DIR, { recursive: true });
56
+ writeFileSync(REFRESH_TOKEN_FILE, token, { mode: 0o600 });
57
+ }
58
+ catch {
59
+ // 写入失败时静默处理
60
+ }
61
+ }
62
+ // 加载 refresh token
63
+ export function loadRefreshToken() {
64
+ try {
65
+ const val = readFileSync(REFRESH_TOKEN_FILE, 'utf-8').trim();
66
+ return val || null;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
49
72
  // ─── 主题偏好 ────────────────────────────────────────────────
50
73
  // 主题偏好文件路径,保存 'dark' 或 'light'
51
74
  const THEME_FILE = join(CONFIG_DIR, 'theme');
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.1.18";
1
+ export declare const VERSION = "0.1.20";
2
2
  export declare const BANNER_LINES: string[];
3
3
  export declare const BANNER_SUBTITLE = " A G E N T";
4
4
  export declare function getBannerGradient(): string[];
@@ -1,4 +1,4 @@
1
- export const VERSION = '0.1.18';
1
+ export const VERSION = '0.1.20';
2
2
  // Banner as line array for per-line gradient coloring
3
3
  export const BANNER_LINES = [
4
4
  ' ███╗ ███╗██╗███████╗████████╗',
@@ -23,7 +23,12 @@ export interface TunnelResult {
23
23
  * 工具名称和参数与 LangChain FileManagementToolkit 保持一致。
24
24
  */
25
25
  export declare function executeFileOperation(req: TunnelRequest): Promise<TunnelResult>;
26
- /** 是否为需要用户确认的敏感操作 */
27
- export declare function isSensitiveOperation(toolName: string): boolean;
26
+ /**
27
+ * 是否为需要用户确认的敏感操作
28
+ * - 写入/修改工具:始终 sensitive
29
+ * - bash_shell_run:只读命令不 sensitive,其他命令 sensitive
30
+ * - read_file / list_directory 等只读工具:不 sensitive
31
+ */
32
+ export declare function isSensitiveOperation(toolName: string, args?: Record<string, unknown>): boolean;
28
33
  export declare function isAlwaysConfirmRequired(req: TunnelRequest): boolean;
29
34
  //# sourceMappingURL=fileTunnel.d.ts.map