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
@@ -110,6 +110,95 @@ export async function executeFileOperation(req) {
110
110
  });
111
111
  break;
112
112
  }
113
+ // ─── code_search 工具(Codebase Investigator 子图) ───
114
+ case 'grep_search': {
115
+ const pattern = req.args.pattern;
116
+ const searchPath = req.args.path || '.';
117
+ const include = req.args.include || '';
118
+ const contextLines = parseInt(req.args.context_lines || '2', 10);
119
+ if (!pattern) {
120
+ return fail(req, 'Missing required argument: pattern');
121
+ }
122
+ const { execSync } = await import('node:child_process');
123
+ const cmd = ['grep', '-rn', '--color=never'];
124
+ if (contextLines > 0)
125
+ cmd.push('-C', String(Math.min(contextLines, 5)));
126
+ if (include)
127
+ cmd.push('--include', include);
128
+ IGNORE_DIRS.forEach((d) => cmd.push('--exclude-dir', d));
129
+ cmd.push(pattern, searchPath);
130
+ try {
131
+ const stdout = execSync(cmd.join(' '), {
132
+ encoding: 'utf-8',
133
+ timeout: 30_000,
134
+ maxBuffer: 10 * 1024 * 1024,
135
+ });
136
+ const lines = stdout.split('\n');
137
+ result = lines.length > 200
138
+ ? lines.slice(0, 200).join('\n') + `\n\n... (结果已截断,共 ${lines.length} 行,显示前 200 行)`
139
+ : stdout;
140
+ }
141
+ catch (err) {
142
+ const exitCode = err.status;
143
+ if (exitCode === 1) {
144
+ result = `未找到匹配 '${pattern}' 的结果`;
145
+ }
146
+ else {
147
+ throw err;
148
+ }
149
+ }
150
+ break;
151
+ }
152
+ case 'find_files': {
153
+ const pattern = req.args.pattern;
154
+ const searchPath = req.args.path || '.';
155
+ if (!pattern) {
156
+ return fail(req, 'Missing required argument: pattern');
157
+ }
158
+ const root = path.resolve(searchPath);
159
+ if (!fs.existsSync(root)) {
160
+ return fail(req, `路径不存在: ${searchPath}`);
161
+ }
162
+ const found = globFiles(root, pattern, 100);
163
+ result = found.length > 0 ? found.join('\n') : `未找到匹配 '${pattern}' 的文件`;
164
+ break;
165
+ }
166
+ case 'directory_tree': {
167
+ const treePath = req.args.path || '.';
168
+ const maxDepth = Math.min(parseInt(req.args.max_depth || '3', 10), 4);
169
+ const root = path.resolve(treePath);
170
+ if (!fs.existsSync(root)) {
171
+ return fail(req, `路径不存在: ${treePath}`);
172
+ }
173
+ result = buildTree(root, maxDepth);
174
+ break;
175
+ }
176
+ case 'replace_in_file': {
177
+ const filePath = req.args.file_path;
178
+ const oldStr = req.args.old_string ?? '';
179
+ const newStr = req.args.new_string ?? '';
180
+ const allowMultiple = req.args.allow_multiple === 'true';
181
+ if (!filePath) {
182
+ return fail(req, 'Missing required argument: file_path');
183
+ }
184
+ if (!fs.existsSync(filePath)) {
185
+ return fail(req, `File not found: ${filePath}`);
186
+ }
187
+ if (oldStr === newStr) {
188
+ return fail(req, 'old_string and new_string are identical');
189
+ }
190
+ const content = fs.readFileSync(filePath, 'utf-8');
191
+ const replaceResult = calculateReplacement(content, oldStr, newStr, allowMultiple);
192
+ if (replaceResult.occurrences === 0) {
193
+ return fail(req, 'No match found for old_string in the file.');
194
+ }
195
+ if (replaceResult.strategy.includes('error')) {
196
+ return fail(req, `Found ${replaceResult.occurrences} occurrences but allow_multiple=false.`);
197
+ }
198
+ fs.writeFileSync(filePath, replaceResult.newContent, 'utf-8');
199
+ result = `Replaced ${replaceResult.occurrences} occurrence(s) in ${filePath} (strategy: ${replaceResult.strategy})`;
200
+ break;
201
+ }
113
202
  default:
114
203
  return fail(req, `Unknown tool: ${req.tool_name}`);
115
204
  }
@@ -125,17 +214,216 @@ export async function executeFileOperation(req) {
125
214
  return fail(req, errorMsg);
126
215
  }
127
216
  }
217
+ function exactReplace(content, oldStr, newStr, allowMultiple) {
218
+ const norm = content.replace(/\r\n/g, '\n');
219
+ const oldNorm = oldStr.replace(/\r\n/g, '\n');
220
+ const count = norm.split(oldNorm).length - 1;
221
+ if (count === 0)
222
+ return null;
223
+ if (count > 1 && !allowMultiple) {
224
+ return { newContent: content, occurrences: count, strategy: 'exact_multiple_error' };
225
+ }
226
+ const newNorm = newStr.replace(/\r\n/g, '\n');
227
+ const result = allowMultiple
228
+ ? norm.split(oldNorm).join(newNorm)
229
+ : norm.replace(oldNorm, newNorm);
230
+ return { newContent: result, occurrences: allowMultiple ? count : 1, strategy: 'exact' };
231
+ }
232
+ function flexibleReplace(content, oldStr, newStr, allowMultiple) {
233
+ const contentLines = content.replace(/\r\n/g, '\n').split('\n');
234
+ const searchLines = oldStr.replace(/\r\n/g, '\n').split('\n');
235
+ const replaceLines = newStr.replace(/\r\n/g, '\n').split('\n');
236
+ const strippedSearch = searchLines.map((l) => l.trim());
237
+ if (!strippedSearch[0])
238
+ return null;
239
+ const matches = [];
240
+ let i = 0;
241
+ while (i <= contentLines.length - searchLines.length) {
242
+ let matched = true;
243
+ for (let j = 0; j < strippedSearch.length; j++) {
244
+ if (contentLines[i + j].trim() !== strippedSearch[j]) {
245
+ matched = false;
246
+ break;
247
+ }
248
+ }
249
+ if (matched) {
250
+ matches.push(i);
251
+ i += searchLines.length;
252
+ }
253
+ else {
254
+ i++;
255
+ }
256
+ }
257
+ if (matches.length === 0)
258
+ return null;
259
+ if (matches.length > 1 && !allowMultiple) {
260
+ return { newContent: content, occurrences: matches.length, strategy: 'flexible_multiple_error' };
261
+ }
262
+ const targets = allowMultiple ? matches : [matches[0]];
263
+ const resultLines = [...contentLines];
264
+ for (const start of [...targets].reverse()) {
265
+ const firstLine = contentLines[start];
266
+ const indent = firstLine.slice(0, firstLine.length - firstLine.trimStart().length);
267
+ const indented = replaceLines.map((r, k) => k === 0 ? indent + r.trimStart() : (r.trim() ? indent + r : r));
268
+ resultLines.splice(start, searchLines.length, ...indented);
269
+ }
270
+ return { newContent: resultLines.join('\n'), occurrences: targets.length, strategy: 'flexible' };
271
+ }
272
+ function regexReplace(content, oldStr, newStr, allowMultiple) {
273
+ const norm = content.replace(/\r\n/g, '\n');
274
+ const oldNorm = oldStr.replace(/\r\n/g, '\n').trim();
275
+ if (!oldNorm)
276
+ return null;
277
+ const tokens = oldNorm.split(/([():\[\]{}<>=,;])/).filter(Boolean).map((t) => t.trim()).filter(Boolean);
278
+ const parts = tokens.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
279
+ if (parts.length === 0)
280
+ return null;
281
+ const pattern = '([ \\t]*)' + parts.join('\\s*');
282
+ let globalRegex;
283
+ try {
284
+ globalRegex = new RegExp(pattern, 'gm');
285
+ }
286
+ catch {
287
+ return null;
288
+ }
289
+ const matches = [];
290
+ let m;
291
+ let lastIndex = 0;
292
+ while ((m = globalRegex.exec(norm)) !== null) {
293
+ matches.push(m);
294
+ if (!allowMultiple)
295
+ break;
296
+ if (globalRegex.lastIndex === lastIndex)
297
+ globalRegex.lastIndex++;
298
+ lastIndex = globalRegex.lastIndex;
299
+ }
300
+ if (matches.length === 0)
301
+ return null;
302
+ if (matches.length > 1 && !allowMultiple) {
303
+ return { newContent: content, occurrences: matches.length, strategy: 'regex_multiple_error' };
304
+ }
305
+ const newNorm = newStr.replace(/\r\n/g, '\n');
306
+ let result = norm;
307
+ for (const match of [...matches].reverse()) {
308
+ const indent = match[1] || '';
309
+ const indented = newNorm.split('\n').map((line, idx) => idx > 0 && line.trim() ? indent + line : line).join('\n');
310
+ result = result.slice(0, match.index) + indent + indented.trimStart() + result.slice(match.index + match[0].length);
311
+ }
312
+ return { newContent: result, occurrences: matches.length, strategy: 'regex' };
313
+ }
314
+ function levenshteinDistance(s1, s2) {
315
+ if (s1.length < s2.length)
316
+ return levenshteinDistance(s2, s1);
317
+ if (s2.length === 0)
318
+ return s1.length;
319
+ let prevRow = Array.from({ length: s2.length + 1 }, (_, i) => i);
320
+ for (let i = 0; i < s1.length; i++) {
321
+ const currRow = [i + 1];
322
+ for (let j = 0; j < s2.length; j++) {
323
+ currRow.push(Math.min(prevRow[j + 1] + 1, currRow[j] + 1, prevRow[j] + (s1[i] !== s2[j] ? 1 : 0)));
324
+ }
325
+ prevRow = currRow;
326
+ }
327
+ return prevRow[s2.length];
328
+ }
329
+ function fuzzyReplace(content, oldStr, newStr, allowMultiple) {
330
+ if (oldStr.trim().length < 10)
331
+ return null;
332
+ const norm = content.replace(/\r\n/g, '\n');
333
+ const oldNorm = oldStr.replace(/\r\n/g, '\n');
334
+ const sourceLines = norm.split('\n');
335
+ const searchLines = oldNorm.split('\n');
336
+ const nSearch = searchLines.length;
337
+ if (nSearch === 0 || sourceLines.length * oldNorm.length ** 2 > 4e8)
338
+ return null;
339
+ const searchBlock = oldNorm.replace(/\s/g, '');
340
+ if (searchBlock.length === 0)
341
+ return null;
342
+ const candidates = [];
343
+ for (let i = 0; i <= sourceLines.length - nSearch; i++) {
344
+ const window = sourceLines.slice(i, i + nSearch).join('\n');
345
+ const windowStripped = window.replace(/\s/g, '');
346
+ if (Math.abs(windowStripped.length - searchBlock.length) > searchBlock.length * 0.2)
347
+ continue;
348
+ const dNorm = levenshteinDistance(windowStripped, searchBlock);
349
+ const dRaw = levenshteinDistance(window, oldNorm);
350
+ const weighted = dNorm + (dRaw - dNorm) * 0.1;
351
+ const score = weighted / searchBlock.length;
352
+ if (score <= 0.1)
353
+ candidates.push({ score, start: i });
354
+ }
355
+ if (candidates.length === 0)
356
+ return null;
357
+ candidates.sort((a, b) => a.score - b.score);
358
+ const selected = [];
359
+ const usedLines = new Set();
360
+ for (const { start } of candidates) {
361
+ let overlap = false;
362
+ for (let l = start; l < start + nSearch; l++) {
363
+ if (usedLines.has(l)) {
364
+ overlap = true;
365
+ break;
366
+ }
367
+ }
368
+ if (overlap)
369
+ continue;
370
+ selected.push(start);
371
+ for (let l = start; l < start + nSearch; l++)
372
+ usedLines.add(l);
373
+ if (!allowMultiple)
374
+ break;
375
+ }
376
+ if (selected.length === 0)
377
+ return null;
378
+ const newNorm = newStr.replace(/\r\n/g, '\n');
379
+ const resultLines = [...sourceLines];
380
+ for (const start of [...selected].sort((a, b) => b - a)) {
381
+ const firstLine = sourceLines[start];
382
+ const indent = firstLine.slice(0, firstLine.length - firstLine.trimStart().length);
383
+ const replLines = newNorm.split('\n').map((r, k) => k === 0 ? indent + r.trimStart() : (r.trim() ? indent + r : r));
384
+ resultLines.splice(start, nSearch, ...replLines);
385
+ }
386
+ return { newContent: resultLines.join('\n'), occurrences: selected.length, strategy: 'fuzzy' };
387
+ }
388
+ function calculateReplacement(content, oldStr, newStr, allowMultiple) {
389
+ if (!oldStr)
390
+ return { newContent: content, occurrences: 0, strategy: 'none' };
391
+ for (const fn of [exactReplace, flexibleReplace, regexReplace, fuzzyReplace]) {
392
+ const r = fn(content, oldStr, newStr, allowMultiple);
393
+ if (r && (r.occurrences > 0 || r.strategy.includes('error')))
394
+ return r;
395
+ }
396
+ return { newContent: content, occurrences: 0, strategy: 'none' };
397
+ }
128
398
  // ─── 敏感操作判断 ───
129
- const SENSITIVE_TOOLS = new Set([
399
+ /** 始终需要确认的写入/修改工具 */
400
+ const WRITE_TOOLS = new Set([
130
401
  'write_file',
402
+ 'replace_in_file',
131
403
  'file_delete',
132
404
  'move_file',
133
405
  'copy_file',
134
- 'bash_shell_run',
135
406
  ]);
136
- /** 是否为需要用户确认的敏感操作 */
137
- export function isSensitiveOperation(toolName) {
138
- return SENSITIVE_TOOLS.has(toolName);
407
+ /**
408
+ * 只读 bash 命令模式 — 匹配 grep, find, cat, head, tail, ls, wc, git log/diff/show 等
409
+ * 这些命令在 plan 研究阶段可自动放行
410
+ */
411
+ const READONLY_CMD_RE = /^\s*(grep|rg|find|cat|head|tail|less|ls|tree|wc|file|stat|which|echo|pwd|env|git\s+(log|diff|show|status|branch|remote|tag|blame|rev-parse))\b/;
412
+ /**
413
+ * 是否为需要用户确认的敏感操作
414
+ * - 写入/修改工具:始终 sensitive
415
+ * - bash_shell_run:只读命令不 sensitive,其他命令 sensitive
416
+ * - read_file / list_directory 等只读工具:不 sensitive
417
+ */
418
+ export function isSensitiveOperation(toolName, args) {
419
+ if (WRITE_TOOLS.has(toolName))
420
+ return true;
421
+ if (toolName === 'bash_shell_run') {
422
+ const cmd = args?.command || '';
423
+ // 只读命令自动放行
424
+ return !READONLY_CMD_RE.test(cmd);
425
+ }
426
+ return false;
139
427
  }
140
428
  /**
141
429
  * 是否为无论 session 设置都必须确认的高危操作
@@ -152,6 +440,89 @@ export function isAlwaysConfirmRequired(req) {
152
440
  }
153
441
  return false;
154
442
  }
443
+ // ─── code_search 辅助 ───
444
+ const IGNORE_DIRS = new Set([
445
+ 'node_modules', '.git', '__pycache__', '.venv', 'venv',
446
+ '.tox', '.mypy_cache', '.pytest_cache', 'dist', 'build',
447
+ '.next', '.nuxt', 'target', 'vendor',
448
+ ]);
449
+ /** 递归 glob 匹配文件,返回相对路径列表 */
450
+ function globFiles(root, pattern, maxResults) {
451
+ const results = [];
452
+ function walk(dir) {
453
+ if (results.length >= maxResults)
454
+ return;
455
+ let entries;
456
+ try {
457
+ entries = fs.readdirSync(dir, { withFileTypes: true });
458
+ }
459
+ catch {
460
+ return;
461
+ }
462
+ for (const entry of entries) {
463
+ if (results.length >= maxResults)
464
+ break;
465
+ if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith('.'))
466
+ continue;
467
+ const full = path.join(dir, entry.name);
468
+ if (entry.isDirectory()) {
469
+ walk(full);
470
+ }
471
+ else if (entry.isFile() && minimatch(path.relative(root, full), pattern)) {
472
+ results.push(path.relative(root, full));
473
+ }
474
+ }
475
+ }
476
+ walk(root);
477
+ return results;
478
+ }
479
+ /** 简易 glob 匹配(不依赖外部库,支持 *, **, ? 模式) */
480
+ function minimatch(filePath, pattern) {
481
+ // 将 glob 转为正则
482
+ let re = pattern
483
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // 转义特殊字符
484
+ .replace(/\*\*/g, '{{GLOBSTAR}}') // 占位 **
485
+ .replace(/\*/g, '[^/]*') // * → 匹配非 / 字符
486
+ .replace(/\?/g, '[^/]') // ? → 匹配单个非 / 字符
487
+ .replace(/\{\{GLOBSTAR\}\}/g, '.*'); // ** → 匹配任意路径
488
+ re = '^' + re + '$';
489
+ return new RegExp(re).test(filePath);
490
+ }
491
+ /** 构建缩进的目录树文本 */
492
+ function buildTree(root, maxDepth) {
493
+ const lines = [path.basename(root) + '/'];
494
+ function walk(dir, prefix, depth) {
495
+ if (depth >= maxDepth)
496
+ return;
497
+ let entries;
498
+ try {
499
+ entries = fs.readdirSync(dir, { withFileTypes: true });
500
+ }
501
+ catch {
502
+ return;
503
+ }
504
+ entries = entries
505
+ .filter((e) => !IGNORE_DIRS.has(e.name) && !e.name.startsWith('.'))
506
+ .sort((a, b) => {
507
+ if (a.isDirectory() !== b.isDirectory())
508
+ return a.isDirectory() ? -1 : 1;
509
+ return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
510
+ });
511
+ for (let i = 0; i < entries.length; i++) {
512
+ const entry = entries[i];
513
+ const isLast = i === entries.length - 1;
514
+ const connector = isLast ? '└── ' : '├── ';
515
+ const suffix = entry.isDirectory() ? '/' : '';
516
+ lines.push(`${prefix}${connector}${entry.name}${suffix}`);
517
+ if (entry.isDirectory()) {
518
+ const extension = isLast ? ' ' : '│ ';
519
+ walk(path.join(dir, entry.name), prefix + extension, depth + 1);
520
+ }
521
+ }
522
+ }
523
+ walk(root, '', 0);
524
+ return lines.join('\n');
525
+ }
155
526
  function fail(req, error) {
156
527
  return {
157
528
  request_id: req.request_id,
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Custom markdown-to-ANSI renderer.
3
+ *
4
+ * Replaces `marked-terminal` with a hand-rolled `formatToken()` pipeline
5
+ * (modelled after Claude Code's `utils/markdown.ts`). Gives us full control
6
+ * over styling, performance (token caching + plain-text fast-path), and
7
+ * terminal-width responsiveness.
8
+ */
9
+ export declare function applyMarkdown(content: string): string;
10
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Custom markdown-to-ANSI renderer.
3
+ *
4
+ * Replaces `marked-terminal` with a hand-rolled `formatToken()` pipeline
5
+ * (modelled after Claude Code's `utils/markdown.ts`). Gives us full control
6
+ * over styling, performance (token caching + plain-text fast-path), and
7
+ * terminal-width responsiveness.
8
+ */
9
+ import chalk from 'chalk';
10
+ import { marked } from 'marked';
11
+ import { highlight as cliHighlight, supportsLanguage } from 'cli-highlight';
12
+ import stripAnsi from 'strip-ansi';
13
+ import stringWidth from 'string-width';
14
+ import { palette } from './colors.js';
15
+ const EOL = '\n';
16
+ // ── marked configuration (run once) ────────────────────────────
17
+ let configured = false;
18
+ function configureMarked() {
19
+ if (configured)
20
+ return;
21
+ configured = true;
22
+ // Disable strikethrough — models use ~ for "approximately"
23
+ marked.use({ tokenizer: { del() { return undefined; } } });
24
+ }
25
+ // ── LRU token cache ────────────────────────────────────────────
26
+ const TOKEN_CACHE_MAX = 256;
27
+ const tokenCache = new Map();
28
+ // Quick regex test: does the string contain any markdown syntax?
29
+ const MD_SYNTAX_RE = /[#*`|[>\-_~]|\n\n|^\d+\. |\n\d+\. /;
30
+ function cachedLexer(content) {
31
+ // Fast path: plain text → synthetic paragraph (skip full GFM parse)
32
+ const sample = content.length > 500 ? content.slice(0, 500) : content;
33
+ if (!MD_SYNTAX_RE.test(sample)) {
34
+ return [{
35
+ type: 'paragraph', raw: content, text: content,
36
+ tokens: [{ type: 'text', raw: content, text: content }],
37
+ }];
38
+ }
39
+ const key = content;
40
+ const hit = tokenCache.get(key);
41
+ if (hit) {
42
+ // promote to MRU
43
+ tokenCache.delete(key);
44
+ tokenCache.set(key, hit);
45
+ return hit;
46
+ }
47
+ const tokens = marked.lexer(content);
48
+ if (tokenCache.size >= TOKEN_CACHE_MAX) {
49
+ const first = tokenCache.keys().next().value;
50
+ if (first !== undefined)
51
+ tokenCache.delete(first);
52
+ }
53
+ tokenCache.set(key, tokens);
54
+ return tokens;
55
+ }
56
+ // ── Public API ─────────────────────────────────────────────────
57
+ export function applyMarkdown(content) {
58
+ configureMarked();
59
+ // Strip internal heading markers
60
+ const cleaned = content.replace(/\{mist_session_heading:[^}]*\}\s*/g, '');
61
+ return cachedLexer(cleaned)
62
+ .map(t => formatToken(t))
63
+ .join('')
64
+ .trim();
65
+ }
66
+ // ── Token formatter ────────────────────────────────────────────
67
+ function formatToken(token, listDepth = 0, orderedListNumber = null, parent = null) {
68
+ switch (token.type) {
69
+ // ── Headings ──────────────────────────────────────────────
70
+ case 'heading': {
71
+ const inner = children(token, 0, null, null);
72
+ switch (token.depth) {
73
+ case 1:
74
+ return chalk.bold.italic.underline(inner) + EOL + EOL;
75
+ default:
76
+ return chalk.bold(inner) + EOL + EOL;
77
+ }
78
+ }
79
+ // ── Emphasis ──────────────────────────────────────────────
80
+ case 'strong':
81
+ return chalk.bold(children(token, 0, null, parent));
82
+ case 'em':
83
+ return chalk.italic(children(token, 0, null, parent));
84
+ // ── Code ─────────────────────────────────────────────────
85
+ case 'code': {
86
+ const codeToken = token;
87
+ let highlighted;
88
+ if (codeToken.lang && supportsLanguage(codeToken.lang)) {
89
+ try {
90
+ highlighted = cliHighlight(codeToken.text, { language: codeToken.lang });
91
+ }
92
+ catch {
93
+ highlighted = codeToken.text;
94
+ }
95
+ }
96
+ else {
97
+ highlighted = codeToken.text;
98
+ }
99
+ return highlighted + EOL;
100
+ }
101
+ case 'codespan':
102
+ return chalk.hex(palette.info)(token.text);
103
+ // ── Blockquote ───────────────────────────────────────────
104
+ case 'blockquote': {
105
+ const inner = children(token, 0, null, null);
106
+ const bar = chalk.dim('│');
107
+ return inner
108
+ .split(EOL)
109
+ .map(line => stripAnsi(line).trim() ? `${bar} ${chalk.italic(line)}` : line)
110
+ .join(EOL);
111
+ }
112
+ // ── Lists ────────────────────────────────────────────────
113
+ case 'list':
114
+ return token.items
115
+ .map((item, i) => formatToken(item, listDepth, token.ordered ? (token.start ?? 1) + i : null, token))
116
+ .join('');
117
+ case 'list_item':
118
+ return (token.tokens ?? [])
119
+ .map(t => {
120
+ const indent = ' '.repeat(listDepth);
121
+ return `${indent}${formatToken(t, listDepth + 1, orderedListNumber, token)}`;
122
+ })
123
+ .join('');
124
+ // ── Paragraph / text ─────────────────────────────────────
125
+ case 'paragraph':
126
+ return children(token, 0, null, null) + EOL;
127
+ case 'text': {
128
+ if (parent?.type === 'list_item') {
129
+ const bullet = orderedListNumber === null
130
+ ? '-'
131
+ : `${orderedListNumber}.`;
132
+ const inner = token.tokens
133
+ ? token.tokens.map(t => formatToken(t, listDepth, orderedListNumber, token)).join('')
134
+ : token.text;
135
+ return `${bullet} ${inner}${EOL}`;
136
+ }
137
+ // If the text token has sub-tokens (e.g. bold inside text), render them
138
+ if (token.tokens) {
139
+ return token.tokens.map(t => formatToken(t, listDepth, orderedListNumber, token)).join('');
140
+ }
141
+ return token.text;
142
+ }
143
+ // ── Links ────────────────────────────────────────────────
144
+ case 'link': {
145
+ const linkToken = token;
146
+ if (linkToken.href.startsWith('mailto:')) {
147
+ return linkToken.href.replace(/^mailto:/, '');
148
+ }
149
+ const linkText = children(token, 0, null, token);
150
+ const plain = stripAnsi(linkText);
151
+ // If display text differs from URL, show both
152
+ if (plain && plain !== linkToken.href) {
153
+ return `${chalk.blue(plain)} ${chalk.dim(`(${linkToken.href})`)}`;
154
+ }
155
+ return chalk.blue.underline(linkToken.href);
156
+ }
157
+ case 'image':
158
+ return token.href;
159
+ // ── Table ────────────────────────────────────────────────
160
+ case 'table': {
161
+ const tableToken = token;
162
+ const getDisplay = (tokens) => stripAnsi(tokens?.map(t => formatToken(t)).join('') ?? '');
163
+ // Column widths
164
+ const colWidths = tableToken.header.map((h, i) => {
165
+ let max = stringWidth(getDisplay(h.tokens));
166
+ for (const row of tableToken.rows) {
167
+ max = Math.max(max, stringWidth(getDisplay(row[i]?.tokens)));
168
+ }
169
+ return Math.max(max, 3);
170
+ });
171
+ // Header
172
+ let out = '| ';
173
+ tableToken.header.forEach((h, i) => {
174
+ const content = h.tokens?.map(t => formatToken(t)).join('') ?? '';
175
+ const display = getDisplay(h.tokens);
176
+ out += padCell(content, stringWidth(display), colWidths[i]) + ' | ';
177
+ });
178
+ out = out.trimEnd() + EOL;
179
+ // Separator
180
+ out += '|';
181
+ colWidths.forEach(w => { out += '-'.repeat(w + 2) + '|'; });
182
+ out += EOL;
183
+ // Rows
184
+ tableToken.rows.forEach(row => {
185
+ out += '| ';
186
+ row.forEach((cell, i) => {
187
+ const content = cell.tokens?.map(t => formatToken(t)).join('') ?? '';
188
+ const display = getDisplay(cell.tokens);
189
+ out += padCell(content, stringWidth(display), colWidths[i]) + ' | ';
190
+ });
191
+ out = out.trimEnd() + EOL;
192
+ });
193
+ return out + EOL;
194
+ }
195
+ // ── Whitespace / misc ────────────────────────────────────
196
+ case 'space':
197
+ return EOL;
198
+ case 'br':
199
+ return EOL;
200
+ case 'hr':
201
+ return '---' + EOL;
202
+ case 'escape':
203
+ return token.text;
204
+ case 'html':
205
+ case 'def':
206
+ case 'del':
207
+ return '';
208
+ }
209
+ return '';
210
+ }
211
+ // ── Helpers ────────────────────────────────────────────────────
212
+ /** Recursively render child tokens. */
213
+ function children(token, listDepth, orderedListNumber, parent) {
214
+ return (token.tokens ?? [])
215
+ .map((t) => formatToken(t, listDepth, orderedListNumber, parent))
216
+ .join('');
217
+ }
218
+ /** Pad cell content to target width, preserving ANSI codes. */
219
+ function padCell(content, displayWidth, targetWidth) {
220
+ const pad = targetWidth - displayWidth;
221
+ return pad > 0 ? content + ' '.repeat(pad) : content;
222
+ }
223
+ //# sourceMappingURL=markdown.js.map
@@ -39,9 +39,15 @@ export async function checkForUpdate() {
39
39
  }
40
40
  }
41
41
  export function formatUpdateMessage(info) {
42
- return (`\x1b[33m╭───────────────────────────────────────╮\x1b[0m\n` +
43
- `\x1b[33m│\x1b[0m Update available: \x1b[90m${info.current}\x1b[0m \x1b[32m${info.latest}\x1b[0m${' '.repeat(Math.max(0, 18 - info.current.length - info.latest.length))}\x1b[33m│\x1b[0m\n` +
44
- `\x1b[33m│\x1b[0m Run \x1b[36mnpm install -g ${PACKAGE_NAME}\x1b[0m to update \x1b[33m│\x1b[0m\n` +
45
- `\x1b[33m╰───────────────────────────────────────╯\x1b[0m`);
42
+ const line1 = ` Update available: ${info.current} → ${info.latest} `;
43
+ const line2 = ` Run npm install -g ${PACKAGE_NAME} to update `;
44
+ const width = Math.max(line1.length, line2.length);
45
+ const top = '─'.repeat(width);
46
+ const pad1 = ' '.repeat(Math.max(0, width - line1.length));
47
+ const pad2 = ' '.repeat(Math.max(0, width - line2.length));
48
+ return (`\x1b[33m╭${top}╮\x1b[0m\n` +
49
+ `\x1b[33m│\x1b[0m Update available: \x1b[90m${info.current}\x1b[0m → \x1b[32m${info.latest}\x1b[0m ${pad1}\x1b[33m│\x1b[0m\n` +
50
+ `\x1b[33m│\x1b[0m Run \x1b[36mnpm install -g ${PACKAGE_NAME}\x1b[0m to update ${pad2}\x1b[33m│\x1b[0m\n` +
51
+ `\x1b[33m╰${top}╯\x1b[0m`);
46
52
  }
47
53
  //# sourceMappingURL=updateChecker.js.map