@pheem49/mint 1.4.0 → 1.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.
@@ -1,4 +1,4 @@
1
- const { exec } = require('child_process');
1
+ const { execFile } = require('child_process');
2
2
  let shell;
3
3
  try {
4
4
  shell = require('electron').shell;
@@ -9,6 +9,22 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
  const os = require('os');
11
11
 
12
+ const IGNORED_DIRECTORY_NAMES = new Set([
13
+ '.git',
14
+ 'node_modules',
15
+ '.cache',
16
+ 'dist',
17
+ 'build',
18
+ 'coverage'
19
+ ]);
20
+
21
+ function getSearchRoots() {
22
+ return Array.from(new Set([
23
+ process.cwd(),
24
+ os.homedir()
25
+ ]));
26
+ }
27
+
12
28
  /**
13
29
  * Smartly resolves a path.
14
30
  * If a path starts with '/' but doesn't exist at root, checks if it exists relative to home.
@@ -53,6 +69,109 @@ function resolveSmartPath(target) {
53
69
  return target;
54
70
  }
55
71
 
72
+ function findPath(target, options = {}) {
73
+ if (!target || !target.trim()) {
74
+ return { success: false, message: 'No search query provided.', matches: [] };
75
+ }
76
+
77
+ const normalizedType = ['file', 'dir', 'any'].includes(options.type) ? options.type : 'any';
78
+ const loweredQuery = target.trim().toLowerCase();
79
+ const exactMatches = [];
80
+ const partialMatches = [];
81
+ const visited = new Set();
82
+ const maxResults = options.maxResults || 20;
83
+ const searchRoots = Array.isArray(options.roots) && options.roots.length > 0
84
+ ? options.roots
85
+ : getSearchRoots();
86
+
87
+ function buildMatch(entryPath, entryType, rootPath, exactNameMatch) {
88
+ const relativeToCwd = path.relative(process.cwd(), entryPath);
89
+ const pathDepth = entryPath.split(path.sep).length;
90
+ return {
91
+ path: entryPath,
92
+ type: entryType,
93
+ exactNameMatch,
94
+ inCurrentWorkspace: !relativeToCwd.startsWith('..') && !path.isAbsolute(relativeToCwd),
95
+ pathDepth,
96
+ rootPath
97
+ };
98
+ }
99
+
100
+ function sortMatches(matches) {
101
+ return matches.sort((a, b) => {
102
+ if (a.exactNameMatch !== b.exactNameMatch) return a.exactNameMatch ? -1 : 1;
103
+ if (a.inCurrentWorkspace !== b.inCurrentWorkspace) return a.inCurrentWorkspace ? -1 : 1;
104
+ if (a.pathDepth !== b.pathDepth) return a.pathDepth - b.pathDepth;
105
+ return a.path.localeCompare(b.path);
106
+ });
107
+ }
108
+
109
+ function visit(currentPath, rootPath) {
110
+ let entries = [];
111
+ try {
112
+ entries = fs.readdirSync(currentPath, { withFileTypes: true });
113
+ } catch (_) {
114
+ return;
115
+ }
116
+
117
+ for (const entry of entries) {
118
+ const absoluteEntryPath = path.join(currentPath, entry.name);
119
+ if (visited.has(absoluteEntryPath)) continue;
120
+ visited.add(absoluteEntryPath);
121
+
122
+ const entryType = entry.isDirectory() ? 'dir' : 'file';
123
+ if (entry.isDirectory() && IGNORED_DIRECTORY_NAMES.has(entry.name)) {
124
+ continue;
125
+ }
126
+ const relativePath = path.relative(rootPath, absoluteEntryPath);
127
+ const searchablePath = relativePath || entry.name;
128
+ const matchesType = normalizedType === 'any' || normalizedType === entryType;
129
+ const lowerEntryName = entry.name.toLowerCase();
130
+ const exactNameMatch = lowerEntryName === loweredQuery;
131
+ const partialMatch = lowerEntryName.includes(loweredQuery) || searchablePath.toLowerCase().includes(loweredQuery);
132
+
133
+ if (matchesType && partialMatch) {
134
+ const match = buildMatch(absoluteEntryPath, entryType, rootPath, exactNameMatch);
135
+ if (exactNameMatch) {
136
+ exactMatches.push(match);
137
+ if (exactMatches.length >= maxResults) return;
138
+ } else if (exactMatches.length === 0) {
139
+ partialMatches.push(match);
140
+ if (partialMatches.length >= maxResults) return;
141
+ }
142
+ }
143
+
144
+ if (entry.isDirectory() && exactMatches.length < maxResults && partialMatches.length < maxResults) {
145
+ visit(absoluteEntryPath, rootPath);
146
+ if (exactMatches.length >= maxResults || partialMatches.length >= maxResults) return;
147
+ }
148
+ }
149
+ }
150
+
151
+ for (const rootPath of searchRoots) {
152
+ if (!fs.existsSync(rootPath)) continue;
153
+ visit(rootPath, rootPath);
154
+ if (exactMatches.length >= maxResults || partialMatches.length >= maxResults) break;
155
+ }
156
+
157
+ const matches = exactMatches.length > 0
158
+ ? sortMatches(exactMatches).slice(0, maxResults)
159
+ : sortMatches(partialMatches).slice(0, maxResults);
160
+
161
+ if (matches.length === 0) {
162
+ return {
163
+ success: false,
164
+ message: `ไม่พบ${normalizedType === 'dir' ? 'โฟลเดอร์' : normalizedType === 'file' ? 'ไฟล์' : 'ไฟล์หรือโฟลเดอร์'}ที่ตรงกับ "${target}" ค่ะ`,
165
+ matches: []
166
+ };
167
+ }
168
+
169
+ return {
170
+ success: true,
171
+ matches: matches.map(({ path: matchPath, type }) => ({ path: matchPath, type }))
172
+ };
173
+ }
174
+
56
175
  /**
57
176
  * สร้างโฟลเดอร์ใหม่
58
177
  * target: ชื่อโฟลเดอร์ หรือ absolute path
@@ -97,12 +216,23 @@ async function openFile(target) {
97
216
  console.error('openFile error:', result);
98
217
  return `เกิดข้อผิดพลาดในการเปิดไฟล์: ${result}`;
99
218
  }
219
+ return true;
100
220
  } else {
101
221
  return new Promise((resolve) => {
102
- exec(`xdg-open "${resolvedPath}"`, (err) => {
222
+ // บน Linux ลอง xdg-open แล้วค่อย gio open ถ้าอันแรกไม่ทำงาน
223
+ const { exec } = require('child_process');
224
+ const platformCmd = process.platform === 'darwin' ? 'open' : (process.platform === 'win32' ? 'start' : 'xdg-open');
225
+
226
+ // ใช้ exec เพื่อให้รันผ่าน shell และรองรับการทำ fallback
227
+ let cmd = `${platformCmd} "${resolvedPath}"`;
228
+ if (process.platform === 'linux') {
229
+ cmd = `xdg-open "${resolvedPath}" || gio open "${resolvedPath}" || nautilus "${resolvedPath}"`;
230
+ }
231
+
232
+ exec(cmd, (err) => {
103
233
  if (err) {
104
- console.error("Failed to open path via xdg-open:", err);
105
- resolve(`ไม่สามารถเปิดไฟล์ได้ค่ะ: ${err.message}`);
234
+ console.error("Failed to open path:", err);
235
+ resolve(`ไม่สามารถเปิดได้ค่ะ: ${err.message}`);
106
236
  } else {
107
237
  resolve(true);
108
238
  }
@@ -129,7 +259,7 @@ async function deleteFile(target) {
129
259
  }
130
260
  } else {
131
261
  return new Promise((resolve) => {
132
- exec(`gio trash "${resolvedPath}"`, (err) => {
262
+ execFile('gio', ['trash', resolvedPath], (err) => {
133
263
  if (err) {
134
264
  console.error("Failed to trash item via gio trash:", err);
135
265
  resolve({ success: false, message: err.message });
@@ -141,4 +271,4 @@ async function deleteFile(target) {
141
271
  }
142
272
  }
143
273
 
144
- module.exports = { createFolder, openFile, deleteFile };
274
+ module.exports = { createFolder, openFile, deleteFile, findPath };
@@ -1,56 +1,85 @@
1
- const { exec } = require('child_process');
1
+ const { execFile } = require('child_process');
2
2
 
3
- function openApp(target) {
3
+ function execFilePromise(command, args) {
4
+ return new Promise((resolve, reject) => {
5
+ execFile(command, args, (error) => {
6
+ if (error) {
7
+ reject(error);
8
+ return;
9
+ }
10
+ resolve();
11
+ });
12
+ });
13
+ }
14
+
15
+ async function tryCommands(commands) {
16
+ let lastError = null;
17
+ for (const { command, args } of commands) {
18
+ try {
19
+ await execFilePromise(command, args);
20
+ return true;
21
+ } catch (error) {
22
+ lastError = error;
23
+ }
24
+ }
25
+
26
+ if (lastError) {
27
+ console.error(`exec error: ${lastError}`);
28
+ }
29
+ return false;
30
+ }
31
+
32
+ async function openApp(target) {
4
33
  if (!target) return;
5
34
 
6
- let cmd = '';
7
35
  if (process.platform === 'win32') {
8
- cmd = `start "" "${target}"`;
9
- } else if (process.platform === 'darwin') {
10
- if (!target.includes('/')) {
11
- cmd = `open -X -a "${target}" || open -a "${target}"`;
12
- } else {
13
- cmd = `open "${target}"`;
14
- }
15
- } else {
16
- const tLower = target.toLowerCase();
17
- const tCapitalized = target.charAt(0).toUpperCase() + target.slice(1).toLowerCase();
18
-
19
- // Try common linux patterns: gtk-launch, exact name, lowercase, flatpak
36
+ await execFilePromise('cmd.exe', ['/c', 'start', '', target]).catch((error) => {
37
+ console.error(`exec error: ${error}`);
38
+ });
39
+ return;
40
+ }
41
+
42
+ if (process.platform === 'darwin') {
20
43
  if (!target.includes('/')) {
21
- const patterns = [
22
- `gtk-launch ${target}`,
23
- `gtk-launch ${tLower}`,
24
- `gtk-launch ${tCapitalized}`,
25
- `gtk-launch com.${tLower}app.${tCapitalized}`,
26
- `gtk-launch com.${tLower}.${tCapitalized}`,
27
- target,
28
- tLower,
29
- `flatpak run ${target}`, // In case target is already ID
30
- `flatpak run com.${tLower}app.${tCapitalized}`,
31
- `flatpak run com.${tLower}.${tCapitalized}`,
32
- `flatpak run com.${tLower}.Browser`,
33
- `flatpak run com.${tLower}.${target}`,
34
- `flatpak run com.valvesoftware.Steam`,
35
- `flatpak run net.lutris.Lutris`,
36
- `snap run ${tLower}`
37
- ];
38
- cmd = patterns.join(' || ');
44
+ await tryCommands([
45
+ { command: 'open', args: ['-X', '-a', target] },
46
+ { command: 'open', args: ['-a', target] }
47
+ ]);
39
48
  } else {
40
- cmd = `xdg-open "${target}"`;
49
+ await execFilePromise('open', [target]).catch((error) => {
50
+ console.error(`exec error: ${error}`);
51
+ });
41
52
  }
53
+ return;
42
54
  }
43
55
 
44
- exec(cmd, (error) => {
45
- if (error) {
56
+ const tLower = target.toLowerCase();
57
+ const tCapitalized = target.charAt(0).toUpperCase() + target.slice(1).toLowerCase();
58
+
59
+ if (target.includes('/')) {
60
+ await execFilePromise('xdg-open', [target]).catch((error) => {
46
61
  console.error(`exec error: ${error}`);
47
- if (process.platform !== 'win32') {
48
- exec(target.toLowerCase(), (err2) => {
49
- if (err2) console.error("Fallback lowercase exec failed:", err2);
50
- });
51
- }
52
- }
53
- });
62
+ });
63
+ return;
64
+ }
65
+
66
+ await tryCommands([
67
+ { command: 'gtk-launch', args: [target] },
68
+ { command: 'gtk-launch', args: [tLower] },
69
+ { command: 'gtk-launch', args: [tCapitalized] },
70
+ { command: 'gtk-launch', args: [`com.${tLower}app.${tCapitalized}`] },
71
+ { command: 'gtk-launch', args: [`com.${tLower}.${tCapitalized}`] },
72
+ { command: target, args: [] },
73
+ { command: tLower, args: [] },
74
+ { command: 'flatpak', args: ['run', target] },
75
+ { command: 'flatpak', args: ['run', `com.${tLower}app.${tCapitalized}`] },
76
+ { command: 'flatpak', args: ['run', `com.${tLower}.${tCapitalized}`] },
77
+ { command: 'flatpak', args: ['run', `com.${tLower}.Browser`] },
78
+ { command: 'flatpak', args: ['run', `com.${tLower}.${target}`] },
79
+ { command: 'flatpak', args: ['run', 'com.valvesoftware.Steam'] },
80
+ { command: 'flatpak', args: ['run', 'net.lutris.Lutris'] },
81
+ { command: 'snap', args: ['run', tLower] }
82
+ ]);
54
83
  }
55
84
 
56
85
  module.exports = { openApp };
@@ -1,4 +1,4 @@
1
- const { exec } = require('child_process');
1
+ const { execFile } = require('child_process');
2
2
 
3
3
  let shell;
4
4
  try {
@@ -17,7 +17,7 @@ function openWebsite(targetUrl) {
17
17
  shell.openExternal(url);
18
18
  } else {
19
19
  // Fallback for Node.js (Linux focus)
20
- exec(`xdg-open "${url}"`, (err) => {
20
+ execFile('xdg-open', [url], (err) => {
21
21
  if (err) console.error("Failed to open URL via xdg_open:", err);
22
22
  });
23
23
  }
@@ -29,7 +29,7 @@ function openSearch(query) {
29
29
  if (shell) {
30
30
  shell.openExternal(url);
31
31
  } else {
32
- exec(`xdg-open "${url}"`, (err) => {
32
+ execFile('xdg-open', [url], (err) => {
33
33
  if (err) console.error("Failed to open search via xdg-open:", err);
34
34
  });
35
35
  }
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { GoogleGenAI } = require('@google/genai');
4
- const { executeCodeTask } = require('./code_agent');
4
+ const { executeCodeTask, _helpers: codeAgentHelpers } = require('./code_agent');
5
5
  const { readConfig, getAvailableProviders } = require('../System/config_manager');
6
6
 
7
7
  const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
@@ -9,12 +9,15 @@ const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
9
9
  const CODE_KEYWORDS = [
10
10
  'code', 'repo', 'repository', 'project', 'workspace', 'file', 'files', 'readme',
11
11
  'package.json', 'bug', 'fix', 'refactor', 'test', 'tests', 'build', 'lint',
12
- 'implement', 'feature', 'cli', 'function', 'module', 'component', 'diff'
12
+ 'implement', 'feature', 'cli', 'function', 'module', 'component', 'diff',
13
+ 'list', 'show', 'ls', 'dir', 'directory', 'folders' // เพิ่มคำเหล่านี้ค่ะ
13
14
  ];
14
15
 
16
+
15
17
  const THAI_CODE_KEYWORDS = [
16
18
  'โค้ด', 'โปรเจค', 'โปรเจ็กต์', 'ไฟล์', 'รีโป', 'บั๊ก', 'แก้', 'ทดสอบ', 'เทสต์',
17
- 'รีแฟกเตอร์', 'ฟีเจอร์', 'คอมโพเนนต์', 'ฟังก์ชัน', 'อ่าน', 'สำรวจ', 'โครงสร้าง'
19
+ 'รีแฟกเตอร์', 'ฟีเจอร์', 'คอมโพเนนต์', 'ฟังก์ชัน', 'อ่าน', 'สำรวจ', 'โครงสร้าง',
20
+ 'ไดเรกทอรี', 'โฟลเดอร์'
18
21
  ];
19
22
 
20
23
  const ROUTER_PROMPT = `You classify whether a chat message should be routed to a coding agent for the current local workspace.
@@ -26,7 +29,19 @@ Return JSON only:
26
29
  }
27
30
 
28
31
  Choose "code" when the user is asking to inspect, edit, review, debug, explain, refactor, verify, or otherwise operate on the current project/workspace/codebase/files.
29
- Choose "chat" for general conversation, factual Q&A, or non-code assistant tasks.`;
32
+ Choose "chat" for general conversation, factual Q&A, small/simple requests, non-code assistant tasks, or direct file-system actions like finding/opening a folder or file by name.
33
+ Only choose "code" for substantial coding work that likely needs multiple steps, workspace inspection, edits, verification, or project-wide reasoning.`;
34
+
35
+ function isDirectFilesystemActionRequest(text) {
36
+ const input = (text || '').trim().toLowerCase();
37
+ if (!input) return false;
38
+
39
+ const filesystemActionPattern = /(open|find|locate|search for|look for|หา|ค้นหา|เปิด)/;
40
+ const filesystemTargetPattern = /(folder|directory|dir|file|โฟลเดอร์|ไฟล์|ไดเรกทอรี)/;
41
+ const codeOperationPattern = /(inspect|review|refactor|debug|implement|edit|change|fix|explain|analyze|สำรวจ|รีวิว|รีแฟกเตอร์|แก้|อธิบาย|วิเคราะห์)/;
42
+
43
+ return filesystemActionPattern.test(input) && filesystemTargetPattern.test(input) && !codeOperationPattern.test(input);
44
+ }
30
45
 
31
46
  function workspaceLooksLikeCodebase(workspaceRoot) {
32
47
  const markers = [
@@ -45,13 +60,31 @@ function detectCodeIntentHeuristic(text, workspaceRoot = process.cwd()) {
45
60
  const input = (text || '').trim().toLowerCase();
46
61
  if (!input) return false;
47
62
  if (input.startsWith('/code ')) return true;
63
+ if (isDirectFilesystemActionRequest(input)) return false;
64
+
65
+ return isLargeCodeTaskRequest(input, workspaceRoot);
66
+ }
67
+
68
+ function isLargeCodeTaskRequest(text, workspaceRoot = process.cwd()) {
69
+ const input = (text || '').trim().toLowerCase();
70
+ if (!input) return false;
71
+ if (!workspaceLooksLikeCodebase(workspaceRoot)) return false;
48
72
 
49
73
  const hasCodeKeyword = CODE_KEYWORDS.some(keyword => input.includes(keyword));
50
74
  const hasThaiCodeKeyword = THAI_CODE_KEYWORDS.some(keyword => input.includes(keyword));
51
- const referencesProject = /โปรเจคนี้|โปรเจ็กต์นี้|this project|this repo|this repository|codebase|workspace/.test(input);
52
- const asksForAction = /สำรวจ|ดู|แก้|เพิ่ม|ลบ|ปรับ|ตรวจ|วิเคราะห์|implement|inspect|explore|fix|update|change|refactor|review|explain|debug/.test(input);
53
-
54
- return workspaceLooksLikeCodebase(workspaceRoot) && (referencesProject || ((hasCodeKeyword || hasThaiCodeKeyword) && asksForAction));
75
+ const referencesProject = /โปรเจคนี้|โปรเจ็กต์นี้|this project|this repo|this repository|codebase|workspace|โฟลเดอร์นี้|ในนี้/.test(input);
76
+ const asksForAction = /สำรวจ|ดู|แก้|เพิ่ม|ลบ|ปรับ|ตรวจ|วิเคราะห์|ลิสต์|โชว์|แสดง|มี|implement|inspect|explore|fix|update|change|refactor|review|explain|debug|list|show/.test(input);
77
+ const strongTaskSignal = /failing tests?|run tests?|verify|verification|bug|issue|error|refactor|implement|feature|patch|edit|modify|analyze the project|แก้บั๊ก|รันเทสต์|ทดสอบ|ตรวจสอบ|ยืนยันผล|รีแฟกเตอร์|เพิ่มฟีเจอร์|แก้โค้ด|วิเคราะห์โปรเจค/.test(input);
78
+ const multiStepSignal = /and|then|พร้อม|แล้ว|จากนั้น|ทั้ง|ทั่วทั้ง|ทั้งโปรเจค|project-wide|entire project|whole project/.test(input);
79
+
80
+ // If they ask for files/folder content specifically, it's a code task because Chat can't do it accurately
81
+ const isListFilesRequest = (hasCodeKeyword || hasThaiCodeKeyword) && /มี|โชว์|แสดง|ลิสต์|list|show|what|anything|อะไรบ้าง/.test(input) && /ไฟล์|file|folder|dir|โฟลเดอร์/.test(input);
82
+
83
+ if (isListFilesRequest) return true;
84
+ if (referencesProject && strongTaskSignal) return true;
85
+ if ((hasCodeKeyword || hasThaiCodeKeyword) && asksForAction && strongTaskSignal) return true;
86
+ if ((hasCodeKeyword || hasThaiCodeKeyword) && multiStepSignal && asksForAction) return true;
87
+ return false;
55
88
  }
56
89
 
57
90
  function getRouterClient() {
@@ -71,7 +104,7 @@ function summarizeWorkspace(workspaceRoot) {
71
104
  .join(', ') || '(no obvious code markers)';
72
105
  }
73
106
 
74
- async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
107
+ async function detectCodeIntent(text, workspaceRoot = process.cwd(), history = []) {
75
108
  const input = (text || '').trim();
76
109
  if (!input) {
77
110
  return { route: 'chat', reason: 'Empty input.' };
@@ -81,6 +114,10 @@ async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
81
114
  return { route: 'code', reason: 'Explicit /code command.' };
82
115
  }
83
116
 
117
+ if (isDirectFilesystemActionRequest(input)) {
118
+ return { route: 'chat', reason: 'Direct file-system action request.' };
119
+ }
120
+
84
121
  const heuristicRoute = detectCodeIntentHeuristic(input, workspaceRoot);
85
122
  const routerClient = getRouterClient();
86
123
  if (!routerClient) {
@@ -103,7 +140,8 @@ async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
103
140
  text: [
104
141
  `Workspace: ${workspaceRoot}`,
105
142
  `Workspace markers: ${summarizeWorkspace(workspaceRoot)}`,
106
- `Message: ${input}`
143
+ `Context (Last 5 turns): ${history.slice(-10).map(m => `${m.sender}: ${m.text}`).join('\n')}`,
144
+ `Current Message: ${input}`
107
145
  ].join('\n')
108
146
  }]
109
147
  }]
@@ -112,6 +150,12 @@ async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
112
150
  const textOutput = typeof response.text === 'function' ? response.text() : response.text;
113
151
  const parsed = JSON.parse(textOutput);
114
152
  const route = parsed.route === 'code' ? 'code' : 'chat';
153
+ if (route === 'code' && !isLargeCodeTaskRequest(input, workspaceRoot)) {
154
+ return {
155
+ route: 'chat',
156
+ reason: 'Request looks small enough for normal chat.'
157
+ };
158
+ }
115
159
  return {
116
160
  route,
117
161
  reason: parsed.reason || (route === 'code' ? 'Model classified as code.' : 'Model classified as chat.')
@@ -126,21 +170,11 @@ async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
126
170
 
127
171
  async function runChatRoutedTask(input, context) {
128
172
  const text = input.startsWith('/code ') ? input.slice('/code '.length).trim() : input;
129
- const { appendMessage, setThinking, requestApproval, setMode } = context;
173
+ const { appendMessage, setThinking, requestApproval, setMode, history } = context;
130
174
 
131
175
  const config = readConfig();
132
176
  const availableProviders = getAvailableProviders(config);
133
-
134
- // Smart Routing Priority for Code Tasks
135
- let preferredProvider = config.aiProvider || 'gemini';
136
-
137
- // If preferred isn't actually available, try best available
138
- if (!availableProviders.includes(preferredProvider)) {
139
- if (availableProviders.includes('anthropic')) preferredProvider = 'anthropic';
140
- else if (availableProviders.includes('openai')) preferredProvider = 'openai';
141
- else if (availableProviders.includes('gemini')) preferredProvider = 'gemini';
142
- else preferredProvider = availableProviders[0] || 'gemini';
143
- }
177
+ const preferredProvider = codeAgentHelpers.selectSupportedCodeProvider(config, availableProviders);
144
178
 
145
179
  appendMessage('system', `Routing this request to Code Mode for workspace: ${process.cwd()} using [${preferredProvider}]`);
146
180
  if (setMode) setMode('Code');
@@ -157,7 +191,14 @@ async function runChatRoutedTask(input, context) {
157
191
  cwd: process.cwd(),
158
192
  requestApproval,
159
193
  provider: preferredProvider,
160
- onProgress: (message) => appendMessage('system', `[Code] ${message}`)
194
+ history: history,
195
+ onProgress: (info) => {
196
+ if (context.appendCodeStep) {
197
+ context.appendCodeStep(info);
198
+ } else {
199
+ appendMessage('system', `[Code] ${typeof info === 'string' ? info : (info.action || info.phase)}`);
200
+ }
201
+ }
161
202
  });
162
203
  clearInterval(timer);
163
204
  setThinking(false);
@@ -177,5 +218,10 @@ async function runChatRoutedTask(input, context) {
177
218
 
178
219
  module.exports = {
179
220
  detectCodeIntent,
180
- runChatRoutedTask
221
+ runChatRoutedTask,
222
+ _helpers: {
223
+ detectCodeIntentHeuristic,
224
+ isDirectFilesystemActionRequest,
225
+ isLargeCodeTaskRequest
226
+ }
181
227
  };