mcp-osp-prompt 1.0.4 → 1.0.6
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.
- package/README.md +0 -17
- package/config.js +2 -11
- package/dialog/browser.js +6 -9
- package/dialog/http-server.js +5 -13
- package/dialog/native.js +17 -61
- package/dialog/objc.js +11 -27
- package/package.json +1 -1
- package/prompt-manager.js +29 -26
- package/server.js +60 -79
- package/tools.js +6 -18
- package/utils.js +3 -1
package/README.md
CHANGED
|
@@ -97,23 +97,6 @@ Add to your IDE's MCP configuration file (e.g., `~/.cursor/mcp.json`):
|
|
|
97
97
|
}
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
### 2️⃣ Universal URL Parser - Handle Any Complex URL!
|
|
101
|
-
|
|
102
|
-
Our advanced URL parser supports **any complex** GitLab and GitHub repository structure:
|
|
103
|
-
|
|
104
|
-
#### Complex URL Examples That Work:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
# GitLab with multi-level projects and complex branches
|
|
108
|
-
"https://gitlab.com/Keccac256-evg/opensocial/osp-prompt/-/tree/master/dev-arch"
|
|
109
|
-
"https://gitlab.com/group/subgroup/project/-/tree/feature/new-ui/src/components"
|
|
110
|
-
"https://gitlab.com/company/team/service/-/tree/v1.2.3/docs/api"
|
|
111
|
-
|
|
112
|
-
# GitHub with deep nested paths and feature branches
|
|
113
|
-
"https://github.com/feast-dev/feast/tree/master/examples/java-demo/feature_repo/data"
|
|
114
|
-
"https://github.com/microsoft/vscode/tree/main/src/vs/editor/contrib"
|
|
115
|
-
"https://github.com/facebook/react/tree/feature/concurrent/packages/react/src"
|
|
116
|
-
```
|
|
117
100
|
|
|
118
101
|
#### Auto-Detection Results:
|
|
119
102
|
|
package/config.js
CHANGED
|
@@ -87,7 +87,6 @@ export async function generateAutoPromptTools() {
|
|
|
87
87
|
|
|
88
88
|
if (platform === 'local') {
|
|
89
89
|
// 本地模式:扫描配置的本地目录
|
|
90
|
-
console.log(`[Config] Using local prompt path: ${promptPath}`);
|
|
91
90
|
scannedFiles = await scanPromptFiles(promptPath);
|
|
92
91
|
|
|
93
92
|
// 如果本地扫描失败或没有找到文件,回退到基础工具
|
|
@@ -97,7 +96,6 @@ export async function generateAutoPromptTools() {
|
|
|
97
96
|
}
|
|
98
97
|
} else {
|
|
99
98
|
// 远程模式:从GitHub或GitLab获取prompt文件列表
|
|
100
|
-
console.log(`[Config] Using remote ${platform} prompt files from: ${promptPath}`);
|
|
101
99
|
scannedFiles = await scanRemotePromptFiles();
|
|
102
100
|
|
|
103
101
|
// 如果远程扫描失败或没有找到文件,回退到基础工具
|
|
@@ -119,7 +117,7 @@ export async function generateAutoPromptTools() {
|
|
|
119
117
|
}
|
|
120
118
|
}));
|
|
121
119
|
} catch (error) {
|
|
122
|
-
console.error('[Config] Auto prompt tool generation failed:', error.message);
|
|
120
|
+
if (CFG.debug) console.error('[Config] Auto prompt tool generation failed:', error.message);
|
|
123
121
|
return getBasicPromptTools();
|
|
124
122
|
}
|
|
125
123
|
}
|
|
@@ -247,7 +245,6 @@ async function scanRemotePromptFiles() {
|
|
|
247
245
|
|
|
248
246
|
if (cacheAge < CFG.ttl * 1000) {
|
|
249
247
|
const cachedList = await fs.readFile(cacheFile, 'utf8');
|
|
250
|
-
console.log(`[Config] Using cached file list (age: ${cacheAgeMinutes}min)`);
|
|
251
248
|
return JSON.parse(cachedList);
|
|
252
249
|
}
|
|
253
250
|
} catch (_) {
|
|
@@ -257,8 +254,6 @@ async function scanRemotePromptFiles() {
|
|
|
257
254
|
|
|
258
255
|
// 从远程获取文件列表
|
|
259
256
|
try {
|
|
260
|
-
console.log(`[Config] Fetching fresh file list from ${platform}...`);
|
|
261
|
-
|
|
262
257
|
let scannedFiles = [];
|
|
263
258
|
|
|
264
259
|
if (platform === 'github') {
|
|
@@ -269,18 +264,15 @@ async function scanRemotePromptFiles() {
|
|
|
269
264
|
|
|
270
265
|
// 缓存结果
|
|
271
266
|
await fs.writeFile(cacheFile, JSON.stringify(scannedFiles, null, 2), 'utf8');
|
|
272
|
-
console.log(`[Config] Cached ${scannedFiles.length} files to ${cacheFile}`);
|
|
273
267
|
|
|
274
268
|
return scannedFiles;
|
|
275
269
|
} catch (error) {
|
|
276
270
|
// API失败时尝试使用旧缓存
|
|
277
|
-
console.warn(`[Config] Remote scanning failed: ${error.message}`);
|
|
278
271
|
try {
|
|
279
272
|
const cachedList = await fs.readFile(cacheFile, 'utf8');
|
|
280
|
-
console.log(`[Config] Using stale cache due to API failure`);
|
|
281
273
|
return JSON.parse(cachedList);
|
|
282
274
|
} catch (_) {
|
|
283
|
-
console.error('[Config] No cache available, returning empty list');
|
|
275
|
+
if (CFG.debug) console.error('[Config] No cache available, returning empty list');
|
|
284
276
|
return [];
|
|
285
277
|
}
|
|
286
278
|
}
|
|
@@ -323,7 +315,6 @@ async function initializeTools() {
|
|
|
323
315
|
];
|
|
324
316
|
|
|
325
317
|
const allTools = [...autoPromptTools, ...handlerTools];
|
|
326
|
-
console.log(`[Config] Initialized ${allTools.length} tools (${autoPromptTools.length} prompt + ${handlerTools.length} handler)`);
|
|
327
318
|
|
|
328
319
|
return allTools;
|
|
329
320
|
}
|
package/dialog/browser.js
CHANGED
|
@@ -8,6 +8,8 @@ import { BASE_DIALOG_BUTTONS, getSafeButtons, validateDialogButtons } from './co
|
|
|
8
8
|
|
|
9
9
|
let serverPort = null;
|
|
10
10
|
|
|
11
|
+
const debug = process.env.DEBUG_LOG === 'true';
|
|
12
|
+
|
|
11
13
|
async function ensureServer() {
|
|
12
14
|
if (!serverPort) {
|
|
13
15
|
serverPort = await startDialogServer();
|
|
@@ -18,7 +20,6 @@ async function ensureServer() {
|
|
|
18
20
|
export async function showBrowserDialog(html) {
|
|
19
21
|
// 确保HTTP服务器已启动
|
|
20
22
|
const port = await ensureServer();
|
|
21
|
-
console.log(`[Browser Dialog] Using HTTP server on port ${port}`);
|
|
22
23
|
|
|
23
24
|
const tempFile = path.join(tmpdir(), `dialog_${Date.now()}.html`);
|
|
24
25
|
writeFileSync(tempFile, html, 'utf8');
|
|
@@ -31,7 +32,7 @@ export async function showInputDialog(opts) {
|
|
|
31
32
|
|
|
32
33
|
// Handle automated testing mode
|
|
33
34
|
if (process.env.AUTOMATED_MODE === 'true') {
|
|
34
|
-
console.
|
|
35
|
+
console.error(`🤖 [AUTOMATED] Browser showInputDialog: ${title}, returning: "${defaultValue || ''}"`);
|
|
35
36
|
return defaultValue || '';
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -128,7 +129,6 @@ export async function showInputDialog(opts) {
|
|
|
128
129
|
</html>
|
|
129
130
|
`;
|
|
130
131
|
|
|
131
|
-
console.log(`[Browser Dialog] Opening input dialog - Server: http://localhost:${port}`);
|
|
132
132
|
return await showBrowserDialog(html);
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -145,7 +145,7 @@ export async function showConfirmDialog(opts) {
|
|
|
145
145
|
if (!validateDialogButtons(finalButtons)) {
|
|
146
146
|
console.warn(`⚠️ [AUTOMATED] Browser dialog missing '修改计划' option:`, finalButtons);
|
|
147
147
|
}
|
|
148
|
-
console.
|
|
148
|
+
console.error(`🤖 [AUTOMATED] Browser showConfirmDialog: ${title}, returning: "${finalButtons[0]}" (buttons: ${JSON.stringify(finalButtons)})`);
|
|
149
149
|
return finalButtons[0];
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -231,7 +231,6 @@ export async function showConfirmDialog(opts) {
|
|
|
231
231
|
</html>
|
|
232
232
|
`;
|
|
233
233
|
|
|
234
|
-
console.log(`[Browser Dialog] Opening confirm dialog - Server: http://localhost:${port}`);
|
|
235
234
|
return await showBrowserDialog(html);
|
|
236
235
|
}
|
|
237
236
|
|
|
@@ -252,7 +251,7 @@ export async function showPlanAdjustmentDialog(opts) {
|
|
|
252
251
|
if (!validateDialogButtons(finalOptions)) {
|
|
253
252
|
console.warn(`⚠️ [AUTOMATED] Browser plan dialog missing '修改计划' option:`, finalOptions);
|
|
254
253
|
}
|
|
255
|
-
console.
|
|
254
|
+
console.error(`🤖 [AUTOMATED] Browser showPlanAdjustmentDialog: ${title}, returning: "${finalOptions[0]}" (options: ${JSON.stringify(finalOptions)})`);
|
|
256
255
|
return finalOptions[0]; // Return first option
|
|
257
256
|
}
|
|
258
257
|
|
|
@@ -420,7 +419,6 @@ export async function showPlanAdjustmentDialog(opts) {
|
|
|
420
419
|
</html>
|
|
421
420
|
`;
|
|
422
421
|
|
|
423
|
-
console.log(`[Browser Dialog] Opening plan adjustment dialog - Server: http://localhost:${port}`);
|
|
424
422
|
const result = await showBrowserDialog(html);
|
|
425
423
|
|
|
426
424
|
// Try to parse as JSON for input responses
|
|
@@ -441,7 +439,7 @@ export async function showSelectDialog(opts) {
|
|
|
441
439
|
|
|
442
440
|
// Handle automated testing mode
|
|
443
441
|
if (process.env.AUTOMATED_MODE === 'true') {
|
|
444
|
-
console.
|
|
442
|
+
console.error(`🤖 [AUTOMATED] Browser showSelectDialog: ${title}, returning: "${items[0] || ''}"`);
|
|
445
443
|
return items[0] || '';
|
|
446
444
|
}
|
|
447
445
|
|
|
@@ -527,6 +525,5 @@ export async function showSelectDialog(opts) {
|
|
|
527
525
|
</html>
|
|
528
526
|
`;
|
|
529
527
|
|
|
530
|
-
console.log(`[Browser Dialog] Opening select dialog - Server: http://localhost:${port}`);
|
|
531
528
|
return await showBrowserDialog(html);
|
|
532
529
|
}
|
package/dialog/http-server.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import url from 'url';
|
|
3
3
|
|
|
4
|
+
const debug = process.env.DEBUG_LOG === 'true';
|
|
5
|
+
|
|
4
6
|
let resolverQueue = [];
|
|
5
7
|
let server = null;
|
|
6
8
|
let serverPort = null;
|
|
7
9
|
|
|
8
10
|
export function startDialogServer() {
|
|
9
11
|
if (server && serverPort) {
|
|
10
|
-
console.log(`[Dialog Server] Reusing existing server on port ${serverPort}`);
|
|
11
12
|
return Promise.resolve(serverPort);
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -28,7 +29,6 @@ export function startDialogServer() {
|
|
|
28
29
|
|
|
29
30
|
if (parsedUrl.pathname === '/submit') {
|
|
30
31
|
const response = parsedUrl.query.text || '';
|
|
31
|
-
console.log(`[Dialog Server] Received response: "${response}"`);
|
|
32
32
|
|
|
33
33
|
// 设置CORS头并返回成功响应
|
|
34
34
|
res.writeHead(200, {
|
|
@@ -40,16 +40,12 @@ export function startDialogServer() {
|
|
|
40
40
|
// 解决最新的Promise
|
|
41
41
|
if (resolverQueue.length > 0) {
|
|
42
42
|
const resolver = resolverQueue.shift(); // 取出最早的resolver
|
|
43
|
-
console.log(`[Dialog Server] Resolving promise with response: "${response}" (${resolverQueue.length} remaining)`);
|
|
44
43
|
|
|
45
44
|
try {
|
|
46
45
|
resolver(response);
|
|
47
|
-
console.log('[Dialog Server] Promise resolved successfully');
|
|
48
46
|
} catch (error) {
|
|
49
|
-
console.
|
|
47
|
+
console.error('[Dialog Server] Error resolving promise:', error);
|
|
50
48
|
}
|
|
51
|
-
} else {
|
|
52
|
-
console.log('[Dialog Server] Warning: No resolver waiting for response');
|
|
53
49
|
}
|
|
54
50
|
} else {
|
|
55
51
|
res.writeHead(404);
|
|
@@ -59,7 +55,6 @@ export function startDialogServer() {
|
|
|
59
55
|
|
|
60
56
|
server.listen(0, 'localhost', () => {
|
|
61
57
|
serverPort = server.address().port;
|
|
62
|
-
console.log(`[Dialog Server] Started on http://localhost:${serverPort}`);
|
|
63
58
|
resolve(serverPort);
|
|
64
59
|
});
|
|
65
60
|
});
|
|
@@ -67,18 +62,16 @@ export function startDialogServer() {
|
|
|
67
62
|
|
|
68
63
|
export function waitForResponse() {
|
|
69
64
|
return new Promise((resolve) => {
|
|
70
|
-
console.log(`[Dialog Server] Adding resolver to queue (current queue size: ${resolverQueue.length})`);
|
|
71
65
|
resolverQueue.push(resolve);
|
|
72
66
|
|
|
73
|
-
//
|
|
67
|
+
// 60分钟超时
|
|
74
68
|
setTimeout(() => {
|
|
75
69
|
const index = resolverQueue.indexOf(resolve);
|
|
76
70
|
if (index !== -1) {
|
|
77
|
-
console.log('[Dialog Server] Response timeout (30s), removing resolver from queue');
|
|
78
71
|
resolverQueue.splice(index, 1);
|
|
79
72
|
resolve('');
|
|
80
73
|
}
|
|
81
|
-
},
|
|
74
|
+
}, 3600000);
|
|
82
75
|
});
|
|
83
76
|
}
|
|
84
77
|
|
|
@@ -90,6 +83,5 @@ export function stopDialogServer() {
|
|
|
90
83
|
// 清理所有pending resolvers
|
|
91
84
|
resolverQueue.forEach(resolver => resolver(''));
|
|
92
85
|
resolverQueue = [];
|
|
93
|
-
console.log('[Dialog Server] Stopped');
|
|
94
86
|
}
|
|
95
87
|
}
|
package/dialog/native.js
CHANGED
|
@@ -6,27 +6,9 @@ import { escapeAppleScriptString } from './utils.js';
|
|
|
6
6
|
// 使用 promisify 将 exec 转为 Promise 版本,避免阻塞事件循环
|
|
7
7
|
const execAsync = promisify(exec);
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
heartbeatCounter++;
|
|
13
|
-
|
|
14
|
-
// 尝试使用 MCP 标准的 progress notification 格式
|
|
15
|
-
const notification = {
|
|
16
|
-
jsonrpc: '2.0',
|
|
17
|
-
method: 'notifications/progress',
|
|
18
|
-
params: {
|
|
19
|
-
progressToken: `heartbeat-${Date.now()}`,
|
|
20
|
-
progress: heartbeatCounter,
|
|
21
|
-
total: -1 // -1 表示未知总数
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// 写入 stdout,这是 MCP 客户端监听的通道
|
|
26
|
-
process.stdout.write(JSON.stringify(notification) + '\n');
|
|
27
|
-
// 同时写入 stderr 供调试(只保留一行日志)
|
|
28
|
-
console.error(`[Heartbeat #${heartbeatCounter}] progress notification sent`);
|
|
29
|
-
}
|
|
9
|
+
// 调试模式标志
|
|
10
|
+
const debug = process.env.DEBUG_LOG === 'true';
|
|
11
|
+
|
|
30
12
|
// 🟢 GREEN: Task 2.2 - 导入CFG配置用于native弹窗宽度设置
|
|
31
13
|
import { CFG } from '../config.js';
|
|
32
14
|
// ✅ UNIFIED: 导入统一的按钮管理常量
|
|
@@ -35,7 +17,7 @@ import { BASE_DIALOG_BUTTONS, getSafeButtons, validateDialogButtons } from './co
|
|
|
35
17
|
export async function showInputDialog({ title = '输入', message = '请输入内容:', defaultValue = '' }) {
|
|
36
18
|
// Handle automated testing mode - return default value without showing dialog
|
|
37
19
|
if (process.env.AUTOMATED_MODE === 'true') {
|
|
38
|
-
console.
|
|
20
|
+
console.error(`🤖 [AUTOMATED] showInputDialog: ${title}, returning: "${defaultValue}"`);
|
|
39
21
|
return defaultValue;
|
|
40
22
|
}
|
|
41
23
|
|
|
@@ -89,23 +71,14 @@ export async function showInputDialog({ title = '输入', message = '请输入
|
|
|
89
71
|
end if
|
|
90
72
|
`;
|
|
91
73
|
|
|
92
|
-
// 🆕 添加保活机制(5秒间隔,通过 stdout 发送 JSON-RPC notification)
|
|
93
|
-
const keepAliveInterval = setInterval(() => {
|
|
94
|
-
sendHeartbeat('input');
|
|
95
|
-
}, 5000);
|
|
96
|
-
|
|
97
74
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// 🔧 关键修改:使用异步 exec 替代 execSync,避免阻塞事件循环
|
|
75
|
+
// 🔧 使用异步 exec,避免阻塞事件循环
|
|
101
76
|
const { stdout } = await execAsync(`osascript -e '${applescript}'`, {
|
|
102
77
|
encoding: 'utf8',
|
|
103
|
-
timeout:
|
|
78
|
+
timeout: 3600000 // 60 minutes timeout
|
|
104
79
|
});
|
|
105
80
|
|
|
106
81
|
const result = stdout.trim();
|
|
107
|
-
|
|
108
|
-
clearInterval(keepAliveInterval); // 清理定时器
|
|
109
82
|
|
|
110
83
|
// Clean up the result - remove the placeholder text we added
|
|
111
84
|
let cleanedResult = result;
|
|
@@ -116,7 +89,6 @@ export async function showInputDialog({ title = '输入', message = '请输入
|
|
|
116
89
|
return cleanedResult;
|
|
117
90
|
|
|
118
91
|
} catch (error) {
|
|
119
|
-
clearInterval(keepAliveInterval); // 出错时也要清理定时器
|
|
120
92
|
// 更精确的用户取消检测
|
|
121
93
|
const isUserCancelled =
|
|
122
94
|
error.message && (
|
|
@@ -128,11 +100,9 @@ export async function showInputDialog({ title = '输入', message = '请输入
|
|
|
128
100
|
);
|
|
129
101
|
|
|
130
102
|
if (isUserCancelled) {
|
|
131
|
-
console.error(`[Native Dialog] 👤 User cancelled textarea dialog`);
|
|
132
103
|
return defaultValue;
|
|
133
104
|
}
|
|
134
105
|
|
|
135
|
-
console.error(`[Native Dialog] 💥 Textarea system error: ${error.message}`);
|
|
136
106
|
return defaultValue;
|
|
137
107
|
}
|
|
138
108
|
|
|
@@ -152,12 +122,12 @@ export async function showInputDialog({ title = '输入', message = '请输入
|
|
|
152
122
|
// Special handling for Linux multiline with default value
|
|
153
123
|
result = execSync(`echo "${expandedDefaultText || defaultValue}" | "${command}" ${args.map(a => `"${a.replace(/"/g, '\\"')}"`).join(' ')}`, {
|
|
154
124
|
encoding: 'utf8',
|
|
155
|
-
timeout:
|
|
125
|
+
timeout: 3600000 // 60 minutes timeout
|
|
156
126
|
});
|
|
157
127
|
} else {
|
|
158
128
|
result = execSync(`"${command}" ${args.map(a => `"${a.replace(/"/g, '\\"')}"`).join(' ')}`, {
|
|
159
129
|
encoding: 'utf8',
|
|
160
|
-
timeout:
|
|
130
|
+
timeout: 3600000 // 60 minutes timeout
|
|
161
131
|
});
|
|
162
132
|
}
|
|
163
133
|
|
|
@@ -170,7 +140,6 @@ export async function showInputDialog({ title = '输入', message = '请输入
|
|
|
170
140
|
return cleanedResult;
|
|
171
141
|
}
|
|
172
142
|
} catch (error) {
|
|
173
|
-
console.error('❌ Dialog error:', error.message);
|
|
174
143
|
return defaultValue;
|
|
175
144
|
}
|
|
176
145
|
}
|
|
@@ -184,17 +153,12 @@ export async function showConfirmDialog({ title = '确认', message = '请选择
|
|
|
184
153
|
if (!validateDialogButtons(buttons)) {
|
|
185
154
|
console.warn(`⚠️ [AUTOMATED] Native dialog missing '修改计划' option:`, buttons);
|
|
186
155
|
}
|
|
187
|
-
console.
|
|
156
|
+
console.error(`🤖 [AUTOMATED] Native showConfirmDialog: ${title}, returning: "${buttons[0]}" (buttons: ${JSON.stringify(buttons)})`);
|
|
188
157
|
return buttons[0];
|
|
189
158
|
}
|
|
190
159
|
|
|
191
160
|
const platform = os.platform();
|
|
192
161
|
|
|
193
|
-
// 🆕 添加保活机制(5秒间隔,通过 stdout 发送 JSON-RPC notification)
|
|
194
|
-
const keepAliveInterval = setInterval(() => {
|
|
195
|
-
sendHeartbeat('button selection');
|
|
196
|
-
}, 5000);
|
|
197
|
-
|
|
198
162
|
try {
|
|
199
163
|
let result;
|
|
200
164
|
if (platform === 'darwin') {
|
|
@@ -214,16 +178,12 @@ export async function showConfirmDialog({ title = '确认', message = '请选择
|
|
|
214
178
|
const buttonsList = safeButtons.map(b => `"${b}"`).join(', ');
|
|
215
179
|
const script = `set selectedButton to button returned of (display dialog "${safeMessage}" with title "${safeTitle}" buttons {${buttonsList}} default button "${safeButtons[0]}")`;
|
|
216
180
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// 🔧 关键修改:使用异步 exec 替代 execSync,避免阻塞事件循环
|
|
181
|
+
// 使用异步 exec,避免阻塞事件循环
|
|
220
182
|
const { stdout } = await execAsync(`osascript -e '${script}'`, {
|
|
221
183
|
encoding: 'utf8',
|
|
222
184
|
timeout: 3600000 // 60 minutes timeout
|
|
223
185
|
});
|
|
224
186
|
|
|
225
|
-
clearInterval(keepAliveInterval); // 清理定时器
|
|
226
|
-
|
|
227
187
|
const selectedButton = stdout.trim();
|
|
228
188
|
|
|
229
189
|
// Map safe button back to original button
|
|
@@ -231,18 +191,14 @@ export async function showConfirmDialog({ title = '确认', message = '请选择
|
|
|
231
191
|
return safeIndex >= 0 ? buttons[safeIndex] : buttons[0];
|
|
232
192
|
|
|
233
193
|
} else {
|
|
234
|
-
// Linux zenity
|
|
235
|
-
console.log('🐧 [ZENITY] Showing dialog...');
|
|
194
|
+
// Linux zenity
|
|
236
195
|
const { stdout } = await execAsync(`zenity --question --title="${title}" --text="${message}" --ok-label="${buttons[0]}" --cancel-label="${buttons[1]}"`, {
|
|
237
196
|
encoding: 'utf8',
|
|
238
197
|
timeout: 3600000 // 60 minutes timeout
|
|
239
198
|
});
|
|
240
|
-
clearInterval(keepAliveInterval); // 清理定时器
|
|
241
199
|
return stdout.trim() || buttons[0];
|
|
242
200
|
}
|
|
243
201
|
} catch (error) {
|
|
244
|
-
clearInterval(keepAliveInterval); // 出错时也要清理
|
|
245
|
-
console.error('❌ [DIALOG ERROR]:', error.message);
|
|
246
202
|
// Throw a specific error that indicates dialog system failure
|
|
247
203
|
throw new Error(`DIALOG_SYSTEM_FAILURE: ${error.message}`);
|
|
248
204
|
}
|
|
@@ -263,7 +219,7 @@ export async function showPlanAdjustmentDialog({ title = '', message = '', curre
|
|
|
263
219
|
if (!validateDialogButtons(options)) {
|
|
264
220
|
console.warn(`⚠️ [AUTOMATED] Native plan dialog missing '修改计划' option:`, options);
|
|
265
221
|
}
|
|
266
|
-
console.
|
|
222
|
+
console.error(`🤖 [AUTOMATED] Native showPlanAdjustmentDialog: ${title}, returning: "${options[0]}" (options: ${JSON.stringify(options)})`);
|
|
267
223
|
return options[0]; // Return first option
|
|
268
224
|
}
|
|
269
225
|
|
|
@@ -300,7 +256,7 @@ export async function showPlanAdjustmentDialog({ title = '', message = '', curre
|
|
|
300
256
|
];
|
|
301
257
|
|
|
302
258
|
const script = scriptParts.join(' ');
|
|
303
|
-
result = execSync(`osascript -e '${script}'`, { encoding: 'utf8', timeout:
|
|
259
|
+
result = execSync(`osascript -e '${script}'`, { encoding: 'utf8', timeout: 3600000 }).trim(); // 60 minutes timeout
|
|
304
260
|
|
|
305
261
|
// If "修改计划" is selected, show input dialog using improved showInputDialog
|
|
306
262
|
if (result.includes('修改计划')) {
|
|
@@ -319,11 +275,11 @@ export async function showPlanAdjustmentDialog({ title = '', message = '', curre
|
|
|
319
275
|
|
|
320
276
|
} else {
|
|
321
277
|
// Linux fallback using zenity
|
|
322
|
-
result = execSync(`zenity --list --title="${title}" --text="${fullMessage}" --column=Options ${options.map(i=>`\"${i}\"`).join(' ')}`, { encoding: 'utf8', timeout:
|
|
278
|
+
result = execSync(`zenity --list --title="${title}" --text="${fullMessage}" --column=Options ${options.map(i=>`\"${i}\"`).join(' ')}`, { encoding: 'utf8', timeout: 3600000 }).trim(); // 60 minutes timeout
|
|
323
279
|
|
|
324
280
|
// Handle input for adjustment if needed
|
|
325
281
|
if (result.includes('修改计划')) {
|
|
326
|
-
const inputValue = execSync(`zenity --text-info --editable --title="计划修改建议" --text="请输入您的调整建议:"`, { encoding: 'utf8', timeout:
|
|
282
|
+
const inputValue = execSync(`zenity --text-info --editable --title="计划修改建议" --text="请输入您的调整建议:"`, { encoding: 'utf8', timeout: 3600000 }).trim(); // 60 minutes timeout
|
|
327
283
|
|
|
328
284
|
return {
|
|
329
285
|
action: result,
|
|
@@ -348,12 +304,12 @@ export async function showSelectDialog({ title = '选择', message = '请选择'
|
|
|
348
304
|
const safeTitle = escapeAppleScriptString(title);
|
|
349
305
|
const safeMessage = escapeAppleScriptString(message);
|
|
350
306
|
const script = `set theChoice to choose from list {${safeItems}} with title \"${safeTitle}\" with prompt \"${safeMessage}\"`;
|
|
351
|
-
const result = execSync(`osascript -e "${script}"`, { encoding: 'utf8', timeout:
|
|
307
|
+
const result = execSync(`osascript -e "${script}"`, { encoding: 'utf8', timeout: 3600000 }); // 60 minutes timeout
|
|
352
308
|
return result.trim();
|
|
353
309
|
|
|
354
310
|
} else {
|
|
355
311
|
// linux zenity list
|
|
356
|
-
const result = execSync(`zenity --list --title="${title}" --column=Options ${items.map(i=>`\"${i}\"`).join(' ')}`, { encoding: 'utf8', timeout:
|
|
312
|
+
const result = execSync(`zenity --list --title="${title}" --column=Options ${items.map(i=>`\"${i}\"`).join(' ')}`, { encoding: 'utf8', timeout: 3600000 }); // 60 minutes timeout
|
|
357
313
|
return result.trim();
|
|
358
314
|
}
|
|
359
315
|
} catch (_) {
|
package/dialog/objc.js
CHANGED
|
@@ -9,8 +9,8 @@ import { BASE_DIALOG_BUTTONS, getSafeButtons, validateDialogButtons } from './co
|
|
|
9
9
|
* Uses NSTextView for true multi-line text input with no external dependencies
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
// Default timeout for all dialogs (
|
|
13
|
-
const DEFAULT_TIMEOUT =
|
|
12
|
+
// Default timeout for all dialogs (60 minutes)
|
|
13
|
+
const DEFAULT_TIMEOUT = 3600000;
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Execute AppleScript using a temporary file to handle multiline scripts properly
|
|
@@ -38,7 +38,7 @@ async function executeAppleScript(script, timeout = DEFAULT_TIMEOUT) {
|
|
|
38
38
|
export async function showInputDialog({ title = '输入', message = '请输入内容:', defaultValue = '' }) {
|
|
39
39
|
// Handle automated testing mode
|
|
40
40
|
if (process.env.AUTOMATED_MODE === 'true') {
|
|
41
|
-
console.
|
|
41
|
+
console.error(`🤖 [AUTOMATED] AppleScriptObjC showInputDialog: ${title}, returning: "${defaultValue}"`);
|
|
42
42
|
return defaultValue;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -48,8 +48,6 @@ export async function showInputDialog({ title = '输入', message = '请输入
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
try {
|
|
51
|
-
console.log('✨ [AppleScriptObjC] Showing multi-line capable input dialog...');
|
|
52
|
-
|
|
53
51
|
// For AppleScriptObjC, minimal escaping to avoid syntax errors
|
|
54
52
|
const safeTitle = title.replace(/"/g, '""');
|
|
55
53
|
const safeMessage = message.replace(/"/g, '""');
|
|
@@ -113,7 +111,7 @@ return resultText`;
|
|
|
113
111
|
if (errorMsg.includes('timeout')) {
|
|
114
112
|
console.warn(`WARNING: AppleScriptObjC input dialog timed out after ${DEFAULT_TIMEOUT/1000} seconds. Returning default value.`);
|
|
115
113
|
} else if (errorMsg.includes('User canceled')) {
|
|
116
|
-
|
|
114
|
+
// User cancelled - this is expected behavior
|
|
117
115
|
} else {
|
|
118
116
|
console.warn(`WARNING: AppleScriptObjC input dialog failed: ${errorMsg}. Returning default value.`);
|
|
119
117
|
}
|
|
@@ -131,7 +129,7 @@ export async function showConfirmDialog({ title = '确认', message = '请选择
|
|
|
131
129
|
if (!validateDialogButtons(buttons)) {
|
|
132
130
|
console.warn(`⚠️ [AUTOMATED] AppleScriptObjC dialog missing '修改计划' option:`, buttons);
|
|
133
131
|
}
|
|
134
|
-
console.
|
|
132
|
+
console.error(`🤖 [AUTOMATED] AppleScriptObjC showConfirmDialog: ${title}, returning: "${buttons[0]}" (buttons: ${JSON.stringify(buttons)})`);
|
|
135
133
|
return buttons[0];
|
|
136
134
|
}
|
|
137
135
|
|
|
@@ -141,8 +139,6 @@ export async function showConfirmDialog({ title = '确认', message = '请选择
|
|
|
141
139
|
}
|
|
142
140
|
|
|
143
141
|
try {
|
|
144
|
-
console.log('✨ [AppleScriptObjC] Showing confirmation dialog...');
|
|
145
|
-
|
|
146
142
|
// Use safe string processing
|
|
147
143
|
const safeTitle = cleanAppleScriptString(title) || 'Confirmation';
|
|
148
144
|
const safeMessage = cleanAppleScriptString(message);
|
|
@@ -154,7 +150,6 @@ export async function showConfirmDialog({ title = '确认', message = '请选择
|
|
|
154
150
|
const result = await executeAppleScript(script, DEFAULT_TIMEOUT);
|
|
155
151
|
|
|
156
152
|
const selectedButton = result.trim();
|
|
157
|
-
console.log('✅ [AppleScriptObjC] User selected:', selectedButton);
|
|
158
153
|
|
|
159
154
|
// Map clean button back to original button
|
|
160
155
|
const cleanIndex = safeButtons.indexOf(selectedButton);
|
|
@@ -165,9 +160,7 @@ export async function showConfirmDialog({ title = '确认', message = '请选择
|
|
|
165
160
|
if (errorMsg.includes('timeout')) {
|
|
166
161
|
console.warn(`WARNING: AppleScriptObjC confirm dialog timed out after ${DEFAULT_TIMEOUT/1000} seconds. Returning first button.`);
|
|
167
162
|
} else if (errorMsg.includes('User canceled')) {
|
|
168
|
-
|
|
169
|
-
} else {
|
|
170
|
-
console.warn(`WARNING: AppleScriptObjC confirm dialog failed: ${errorMsg}. Returning first button.`);
|
|
163
|
+
// User cancelled - this is expected behavior
|
|
171
164
|
}
|
|
172
165
|
return buttons[0];
|
|
173
166
|
}
|
|
@@ -175,7 +168,7 @@ export async function showConfirmDialog({ title = '确认', message = '请选择
|
|
|
175
168
|
|
|
176
169
|
export async function showSelectDialog({ title = '选择', message = '请选择', items = [] }) {
|
|
177
170
|
if (process.env.AUTOMATED_MODE === 'true') {
|
|
178
|
-
console.
|
|
171
|
+
console.error(`🤖 [AUTOMATED] AppleScriptObjC showSelectDialog: ${title}, returning: "${items[0] || ''}"`);
|
|
179
172
|
return items[0] || '';
|
|
180
173
|
}
|
|
181
174
|
|
|
@@ -187,8 +180,6 @@ export async function showSelectDialog({ title = '选择', message = '请选择'
|
|
|
187
180
|
if (!items.length) return '';
|
|
188
181
|
|
|
189
182
|
try {
|
|
190
|
-
console.log('✨ [AppleScriptObjC] Showing select dialog...');
|
|
191
|
-
|
|
192
183
|
// Use safe string processing
|
|
193
184
|
const safeTitle = cleanAppleScriptString(title);
|
|
194
185
|
const safeMessage = cleanAppleScriptString(message);
|
|
@@ -219,13 +210,10 @@ end if`;
|
|
|
219
210
|
if (errorMsg.includes('timeout')) {
|
|
220
211
|
console.warn(`WARNING: AppleScriptObjC select dialog timed out after ${DEFAULT_TIMEOUT/1000} seconds.`);
|
|
221
212
|
} else if (errorMsg.includes('User canceled')) {
|
|
222
|
-
|
|
223
|
-
} else {
|
|
224
|
-
console.warn(`WARNING: AppleScriptObjC select dialog failed: ${errorMsg}.`);
|
|
213
|
+
// User cancelled - this is expected behavior
|
|
225
214
|
}
|
|
226
215
|
|
|
227
|
-
//
|
|
228
|
-
console.log('🔄 Falling back to input dialog...');
|
|
216
|
+
// Fallback to input dialog
|
|
229
217
|
return await showInputDialog({ title, message: `${message}\n输入选项:`, defaultValue: items[0] || '' });
|
|
230
218
|
}
|
|
231
219
|
}
|
|
@@ -244,7 +232,7 @@ export async function showPlanAdjustmentDialog({ title = '', message = '', curre
|
|
|
244
232
|
if (!validateDialogButtons(options)) {
|
|
245
233
|
console.warn(`⚠️ [AUTOMATED] AppleScriptObjC plan dialog missing '修改计划' option:`, options);
|
|
246
234
|
}
|
|
247
|
-
console.
|
|
235
|
+
console.error(`🤖 [AUTOMATED] AppleScriptObjC showPlanAdjustmentDialog: ${title}, returning: "${options[0]}" (options: ${JSON.stringify(options)})`);
|
|
248
236
|
return options[0]; // Return first option
|
|
249
237
|
}
|
|
250
238
|
|
|
@@ -260,8 +248,6 @@ export async function showPlanAdjustmentDialog({ title = '', message = '', curre
|
|
|
260
248
|
}
|
|
261
249
|
|
|
262
250
|
try {
|
|
263
|
-
console.log('✨ [AppleScriptObjC] Showing plan adjustment dialog...');
|
|
264
|
-
|
|
265
251
|
// Use safe string processing
|
|
266
252
|
const safeTitle = cleanAppleScriptString(title);
|
|
267
253
|
const safeMessage = escapeAppleScriptString(fullMessage);
|
|
@@ -294,9 +280,7 @@ export async function showPlanAdjustmentDialog({ title = '', message = '', curre
|
|
|
294
280
|
if (errorMsg.includes('timeout')) {
|
|
295
281
|
console.warn(`WARNING: AppleScriptObjC plan adjustment dialog timed out after ${DEFAULT_TIMEOUT/1000} seconds. Returning first option.`);
|
|
296
282
|
} else if (errorMsg.includes('User canceled')) {
|
|
297
|
-
|
|
298
|
-
} else {
|
|
299
|
-
console.warn(`WARNING: AppleScriptObjC plan adjustment dialog failed: ${errorMsg}. Returning first option.`);
|
|
283
|
+
// User cancelled - this is expected behavior
|
|
300
284
|
}
|
|
301
285
|
return options[0];
|
|
302
286
|
}
|
package/package.json
CHANGED
package/prompt-manager.js
CHANGED
|
@@ -2,6 +2,9 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { detectPlatformFromPath, parseRemotePath, createAuthHeaders } from './platform-utils.js';
|
|
4
4
|
|
|
5
|
+
// 调试模式标志
|
|
6
|
+
const debug = process.env.DEBUG_LOG === 'true';
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Prompt管理器
|
|
7
10
|
* 职责:
|
|
@@ -30,7 +33,7 @@ class PromptManager {
|
|
|
30
33
|
return this.toolsConfig;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
console.error('[PromptManager] Starting synchronous initialization...');
|
|
36
|
+
if (debug) console.error('[PromptManager] Starting synchronous initialization...');
|
|
34
37
|
|
|
35
38
|
// 🟢 GREEN: Task 1.1 - 确保promptPath正确初始化
|
|
36
39
|
this.promptPath = process.env.PROMPT_PATH;
|
|
@@ -39,7 +42,7 @@ class PromptManager {
|
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
this.platform = detectPlatformFromPath(this.promptPath);
|
|
42
|
-
console.error(`[PromptManager] Platform: ${this.platform}, Path: ${this.promptPath}`);
|
|
45
|
+
if (debug) console.error(`[PromptManager] Platform: ${this.platform}, Path: ${this.promptPath}`);
|
|
43
46
|
|
|
44
47
|
// 确保缓存目录存在
|
|
45
48
|
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
@@ -52,7 +55,7 @@ class PromptManager {
|
|
|
52
55
|
this.validateToolsConfigExists();
|
|
53
56
|
|
|
54
57
|
this.isInitialized = true;
|
|
55
|
-
console.error(`[PromptManager]
|
|
58
|
+
if (debug) console.error(`[PromptManager] Initialization complete: ${this.toolsConfig.length} tools ready`);
|
|
56
59
|
|
|
57
60
|
return this.toolsConfig;
|
|
58
61
|
}
|
|
@@ -86,7 +89,7 @@ class PromptManager {
|
|
|
86
89
|
};
|
|
87
90
|
});
|
|
88
91
|
|
|
89
|
-
console.error(`[PromptManager] Local scan: ${promptFiles.length} files found`);
|
|
92
|
+
if (debug) console.error(`[PromptManager] Local scan: ${promptFiles.length} files found`);
|
|
90
93
|
return promptFiles;
|
|
91
94
|
} catch (error) {
|
|
92
95
|
throw new Error(`Failed to scan local prompt directory: ${error.message}`);
|
|
@@ -111,7 +114,7 @@ class PromptManager {
|
|
|
111
114
|
const cachedList = JSON.parse(await fs.readFile(fileListCache, 'utf8'));
|
|
112
115
|
|
|
113
116
|
// 🔧 关键修正:启动时不区分缓存新鲜度,只要有缓存就立即启动+异步更新
|
|
114
|
-
console.error(`[PromptManager] Using cached file list for startup (age: ${cacheAgeMinutes}min), scheduling async update`);
|
|
117
|
+
if (debug) console.error(`[PromptManager] Using cached file list for startup (age: ${cacheAgeMinutes}min), scheduling async update`);
|
|
115
118
|
|
|
116
119
|
// 启动阶段总是异步更新,确保缓存最新
|
|
117
120
|
this.asyncUpdateFileListCache().catch(error => {
|
|
@@ -121,7 +124,7 @@ class PromptManager {
|
|
|
121
124
|
return cachedList;
|
|
122
125
|
} catch (_) {
|
|
123
126
|
// 缓存不存在,必须同步获取
|
|
124
|
-
console.error('[PromptManager] No cache found, fetching synchronously for startup');
|
|
127
|
+
if (debug) console.error('[PromptManager] No cache found, fetching synchronously for startup');
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
|
|
@@ -135,7 +138,7 @@ class PromptManager {
|
|
|
135
138
|
async fetchRemoteFileListSync() {
|
|
136
139
|
const fileListCache = path.join(this.cacheDir, 'file-list.json');
|
|
137
140
|
|
|
138
|
-
console.error('[PromptManager] Fetching remote file list synchronously...');
|
|
141
|
+
if (debug) console.error('[PromptManager] Fetching remote file list synchronously...');
|
|
139
142
|
const pathInfo = parseRemotePath(this.promptPath);
|
|
140
143
|
if (!pathInfo) {
|
|
141
144
|
throw new Error(`Unable to parse PROMPT_PATH: ${this.promptPath}`);
|
|
@@ -153,7 +156,7 @@ class PromptManager {
|
|
|
153
156
|
|
|
154
157
|
// 缓存文件列表
|
|
155
158
|
await fs.writeFile(fileListCache, JSON.stringify(promptFiles, null, 2), 'utf8');
|
|
156
|
-
console.error(`[PromptManager] Remote scan: ${promptFiles.length} files cached`);
|
|
159
|
+
if (debug) console.error(`[PromptManager] Remote scan: ${promptFiles.length} files cached`);
|
|
157
160
|
|
|
158
161
|
return promptFiles;
|
|
159
162
|
} catch (error) {
|
|
@@ -173,12 +176,12 @@ class PromptManager {
|
|
|
173
176
|
*/
|
|
174
177
|
async asyncUpdateFileListCache() {
|
|
175
178
|
try {
|
|
176
|
-
console.error('[PromptManager] Starting async file list update...');
|
|
179
|
+
if (debug) console.error('[PromptManager] Starting async file list update...');
|
|
177
180
|
const promptFiles = await this.fetchRemoteFileListSync();
|
|
178
|
-
console.error(`[PromptManager]
|
|
181
|
+
if (debug) console.error(`[PromptManager] Async file list update completed: ${promptFiles.length} files`);
|
|
179
182
|
return promptFiles;
|
|
180
183
|
} catch (error) {
|
|
181
|
-
console.
|
|
184
|
+
if (debug) console.error(`[PromptManager] Async file list update failed: ${error.message}`);
|
|
182
185
|
throw error;
|
|
183
186
|
}
|
|
184
187
|
}
|
|
@@ -188,7 +191,7 @@ class PromptManager {
|
|
|
188
191
|
*/
|
|
189
192
|
async fetchGitLabFileList(pathInfo) {
|
|
190
193
|
const headers = createAuthHeaders('gitlab', process.env.GIT_TOKEN);
|
|
191
|
-
console.error(`[PromptManager] GitLab API call: ${pathInfo.apiUrl}`);
|
|
194
|
+
if (debug) console.error(`[PromptManager] GitLab API call: ${pathInfo.apiUrl}`);
|
|
192
195
|
const requestOptions = { headers, timeout: 10000 };
|
|
193
196
|
const response = await fetch(pathInfo.apiUrl, requestOptions);
|
|
194
197
|
|
|
@@ -227,7 +230,7 @@ class PromptManager {
|
|
|
227
230
|
async fetchGitHubFileList(pathInfo) {
|
|
228
231
|
const url = `${pathInfo.apiUrl}?ref=${pathInfo.branch}`;
|
|
229
232
|
const headers = createAuthHeaders('github', process.env.GIT_TOKEN);
|
|
230
|
-
console.error(`[PromptManager] GitHub API call: ${url}`);
|
|
233
|
+
if (debug) console.error(`[PromptManager] GitHub API call: ${url}`);
|
|
231
234
|
const requestOptions = { headers, timeout: 10000 };
|
|
232
235
|
const response = await fetch(url, requestOptions);
|
|
233
236
|
|
|
@@ -339,8 +342,8 @@ class PromptManager {
|
|
|
339
342
|
throw new Error('No prompt tools configured after initialization');
|
|
340
343
|
}
|
|
341
344
|
|
|
342
|
-
console.error(`[PromptManager] Tools configured: ${promptTools.length} prompt tools available`);
|
|
343
|
-
console.error(`[PromptManager] Available tools: ${promptTools.map(t => t.name).join(', ')}`);
|
|
345
|
+
if (debug) console.error(`[PromptManager] Tools configured: ${promptTools.length} prompt tools available`);
|
|
346
|
+
if (debug) console.error(`[PromptManager] Available tools: ${promptTools.map(t => t.name).join(', ')}`);
|
|
344
347
|
}
|
|
345
348
|
|
|
346
349
|
/**
|
|
@@ -360,11 +363,11 @@ class PromptManager {
|
|
|
360
363
|
|
|
361
364
|
if (cacheAge < this.ttl && !this.force) {
|
|
362
365
|
// 🟢 缓存新鲜:直接返回,不进行异步更新
|
|
363
|
-
console.error(`[PromptManager] Using fresh cached ${fileName} (age: ${Math.round(cacheAge / 1000 / 60)}min)`);
|
|
366
|
+
if (debug) console.error(`[PromptManager] Using fresh cached ${fileName} (age: ${Math.round(cacheAge / 1000 / 60)}min)`);
|
|
364
367
|
return content;
|
|
365
368
|
} else {
|
|
366
369
|
// 🟡 缓存过期:立即返回缓存内容,同时异步更新
|
|
367
|
-
console.error(`[PromptManager] Using stale cached ${fileName} (age: ${Math.round(cacheAge / 1000 / 60)}min), scheduling async update`);
|
|
370
|
+
if (debug) console.error(`[PromptManager] Using stale cached ${fileName} (age: ${Math.round(cacheAge / 1000 / 60)}min), scheduling async update`);
|
|
368
371
|
|
|
369
372
|
// 异步更新缓存(不阻塞本次返回)
|
|
370
373
|
this.asyncUpdatePromptCache(fileName).catch(error => {
|
|
@@ -375,7 +378,7 @@ class PromptManager {
|
|
|
375
378
|
}
|
|
376
379
|
} catch (error) {
|
|
377
380
|
// 🔴 缓存不存在:必须立即获取
|
|
378
|
-
console.error(`[PromptManager] Cache miss for ${fileName}, fetching immediately`);
|
|
381
|
+
if (debug) console.error(`[PromptManager] Cache miss for ${fileName}, fetching immediately`);
|
|
379
382
|
return await this.fetchPromptContent(fileName);
|
|
380
383
|
}
|
|
381
384
|
}
|
|
@@ -385,7 +388,7 @@ class PromptManager {
|
|
|
385
388
|
* 🔧 重构:统一缓存保存逻辑,减少重复代码
|
|
386
389
|
*/
|
|
387
390
|
async fetchPromptContent(fileName) {
|
|
388
|
-
console.error(`[PromptManager] Fetching prompt content: ${fileName} (${this.platform} mode)`);
|
|
391
|
+
if (debug) console.error(`[PromptManager] Fetching prompt content: ${fileName} (${this.platform} mode)`);
|
|
389
392
|
|
|
390
393
|
let content, version;
|
|
391
394
|
|
|
@@ -423,7 +426,7 @@ class PromptManager {
|
|
|
423
426
|
throw new Error(`Unsupported platform: ${pathInfo.platform}`);
|
|
424
427
|
}
|
|
425
428
|
|
|
426
|
-
console.error(`[PromptManager] API call: ${url}`);
|
|
429
|
+
if (debug) console.error(`[PromptManager] API call: ${url}`);
|
|
427
430
|
|
|
428
431
|
const requestOptions = { headers, timeout: 10000 };
|
|
429
432
|
const response = await fetch(url, requestOptions);
|
|
@@ -456,7 +459,7 @@ class PromptManager {
|
|
|
456
459
|
|
|
457
460
|
// 统一保存到缓存
|
|
458
461
|
await this.saveToCacheWithVersion(fileName, content, version);
|
|
459
|
-
console.error(`[PromptManager]
|
|
462
|
+
if (debug) console.error(`[PromptManager] Successfully fetched and cached ${fileName} with version ${version}`);
|
|
460
463
|
|
|
461
464
|
return content;
|
|
462
465
|
}
|
|
@@ -466,11 +469,11 @@ class PromptManager {
|
|
|
466
469
|
*/
|
|
467
470
|
async asyncUpdatePromptCache(fileName) {
|
|
468
471
|
try {
|
|
469
|
-
console.error(`[PromptManager] Async updating cache for ${fileName}...`);
|
|
472
|
+
if (debug) console.error(`[PromptManager] Async updating cache for ${fileName}...`);
|
|
470
473
|
await this.fetchPromptContent(fileName);
|
|
471
|
-
console.error(`[PromptManager]
|
|
474
|
+
if (debug) console.error(`[PromptManager] Async cache update completed for ${fileName}`);
|
|
472
475
|
} catch (error) {
|
|
473
|
-
console.
|
|
476
|
+
if (debug) console.error(`[PromptManager] Async cache update failed for ${fileName}: ${error.message}`);
|
|
474
477
|
}
|
|
475
478
|
}
|
|
476
479
|
|
|
@@ -502,9 +505,9 @@ class PromptManager {
|
|
|
502
505
|
};
|
|
503
506
|
await fs.writeFile(versionFile, JSON.stringify(versionInfo, null, 2), 'utf8');
|
|
504
507
|
|
|
505
|
-
console.error(`[PromptManager] Cached ${fileName} with version ${finalVersion}`);
|
|
508
|
+
if (debug) console.error(`[PromptManager] Cached ${fileName} with version ${finalVersion}`);
|
|
506
509
|
} catch (error) {
|
|
507
|
-
console.
|
|
510
|
+
if (debug) console.error(`[PromptManager] Failed to cache ${fileName}: ${error.message}`);
|
|
508
511
|
}
|
|
509
512
|
}
|
|
510
513
|
|
package/server.js
CHANGED
|
@@ -19,15 +19,18 @@ import {
|
|
|
19
19
|
const debug = process.env.DEBUG_LOG === 'true';
|
|
20
20
|
|
|
21
21
|
process.on('uncaughtException', (error) => {
|
|
22
|
-
|
|
22
|
+
// 仅在debug模式下输出异常信息(使用stderr,不干扰MCP协议)
|
|
23
23
|
if (debug) {
|
|
24
|
-
console.error(error.stack);
|
|
24
|
+
console.error('[MCP-Server] Uncaught Exception:', error.message, error.stack);
|
|
25
25
|
}
|
|
26
26
|
// 不要退出进程,继续服务
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
process.on('unhandledRejection', (reason, promise) => {
|
|
30
|
-
|
|
30
|
+
// 仅在debug模式下输出(使用stderr,不干扰MCP协议)
|
|
31
|
+
if (debug) {
|
|
32
|
+
console.error('[MCP-Server] Unhandled Rejection:', reason);
|
|
33
|
+
}
|
|
31
34
|
// 不要退出进程,继续服务
|
|
32
35
|
});
|
|
33
36
|
|
|
@@ -43,12 +46,12 @@ if (debug) {
|
|
|
43
46
|
|
|
44
47
|
// 🔄 优雅关闭处理
|
|
45
48
|
process.on('SIGTERM', () => {
|
|
46
|
-
console.error('
|
|
49
|
+
if (debug) console.error('[MCP-Server] Received SIGTERM, shutting down gracefully');
|
|
47
50
|
process.exit(0);
|
|
48
51
|
});
|
|
49
52
|
|
|
50
53
|
process.on('SIGINT', () => {
|
|
51
|
-
console.error('
|
|
54
|
+
if (debug) console.error('[MCP-Server] Received SIGINT, shutting down gracefully');
|
|
52
55
|
process.exit(0);
|
|
53
56
|
});
|
|
54
57
|
|
|
@@ -58,47 +61,26 @@ let isServerReady = false;
|
|
|
58
61
|
|
|
59
62
|
// 同步初始化函数 - 阻塞式等待初始化完成
|
|
60
63
|
async function initializeServerSync() {
|
|
61
|
-
// ✅ 添加keepAlive定时器,防止客户端超时
|
|
62
|
-
let keepAliveInterval;
|
|
63
|
-
|
|
64
64
|
try {
|
|
65
|
-
console.error('[MCP-Server] Starting
|
|
66
|
-
|
|
67
|
-
// 每5秒输出一次日志,保持连接活跃(防止客户端超时)
|
|
68
|
-
keepAliveInterval = setInterval(() => {
|
|
69
|
-
console.error('[MCP-KeepAlive] I\'m alive, Heartbeat, Heartbeat...');
|
|
70
|
-
}, 5000);
|
|
65
|
+
if (debug) console.error('[MCP-Server] Starting initialization...');
|
|
71
66
|
|
|
72
67
|
// 同步初始化prompt管理器
|
|
73
68
|
globalToolsConfig = await initializePrompts();
|
|
74
69
|
|
|
75
|
-
// 清理keepAlive定时器
|
|
76
|
-
clearInterval(keepAliveInterval);
|
|
77
|
-
keepAliveInterval = null;
|
|
78
|
-
|
|
79
70
|
// 标记服务器就绪
|
|
80
71
|
isServerReady = true;
|
|
81
|
-
console.error(`[MCP-Server]
|
|
72
|
+
if (debug) console.error(`[MCP-Server] Server ready with ${globalToolsConfig.length} tools`);
|
|
82
73
|
|
|
83
74
|
return true;
|
|
84
75
|
} catch (error) {
|
|
85
|
-
|
|
86
|
-
if (keepAliveInterval) {
|
|
87
|
-
clearInterval(keepAliveInterval);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
console.error('[MCP-Server] ❌ Initialization failed:', error.message);
|
|
91
|
-
console.error('[MCP-Server] Server will not accept requests');
|
|
76
|
+
if (debug) console.error('[MCP-Server] Initialization failed:', error.message);
|
|
92
77
|
isServerReady = false;
|
|
93
78
|
|
|
94
|
-
//
|
|
95
|
-
console.error('[MCP-Server] ⚠️ Entering degraded mode due to initialization failure');
|
|
96
|
-
|
|
97
|
-
// 设置基本的降级工具配置
|
|
79
|
+
// 优雅降级而不是崩溃
|
|
98
80
|
globalToolsConfig = getBasicFallbackTools();
|
|
99
81
|
isServerReady = true; // 允许服务器继续运行
|
|
100
82
|
|
|
101
|
-
console.error(`[MCP-Server]
|
|
83
|
+
if (debug) console.error(`[MCP-Server] Degraded mode active with ${globalToolsConfig.length} basic tools`);
|
|
102
84
|
return false; // 表示降级模式
|
|
103
85
|
}
|
|
104
86
|
}
|
|
@@ -211,8 +193,8 @@ async function handleToolCall(req) {
|
|
|
211
193
|
|
|
212
194
|
// Format feedback result for AI consumption
|
|
213
195
|
if (typeof feedbackResult === 'object' && feedbackResult.hasUserInput) {
|
|
214
|
-
resultText =
|
|
215
|
-
|
|
196
|
+
resultText = `
|
|
197
|
+
📝 **用户反馈处理结果**
|
|
216
198
|
**用户选择:** ${feedbackResult.action}
|
|
217
199
|
**具体建议:** "${feedbackResult.input}"
|
|
218
200
|
|
|
@@ -221,16 +203,14 @@ async function handleToolCall(req) {
|
|
|
221
203
|
2. 调整相应的实现方案
|
|
222
204
|
3. 说明具体如何应用这个建议
|
|
223
205
|
4. 继续执行调整后的计划
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
**重要:** 不要忽略用户的建议,必须基于此建议修改后续的执行步骤。`;
|
|
206
|
+
${devFeedbackContentSuffix}
|
|
207
|
+
`;
|
|
228
208
|
} else {
|
|
229
|
-
resultText =
|
|
230
|
-
|
|
231
|
-
继续按原计划执行。
|
|
232
|
-
|
|
233
|
-
|
|
209
|
+
resultText = `
|
|
210
|
+
✅ **用户确认:** ${feedbackResult}
|
|
211
|
+
**决策:** 继续按原计划执行。
|
|
212
|
+
${devFeedbackContentSuffix}
|
|
213
|
+
`;
|
|
234
214
|
}
|
|
235
215
|
break;
|
|
236
216
|
case 'handleGitlabMrFetch':
|
|
@@ -253,33 +233,7 @@ async function handleToolCall(req) {
|
|
|
253
233
|
**工作流提醒:** 按计划执行,完成后汇报结果。`;
|
|
254
234
|
} else {
|
|
255
235
|
// AUTO=false: 强制分步确认+自检
|
|
256
|
-
resultText = `${promptResult}
|
|
257
|
-
|
|
258
|
-
---
|
|
259
|
-
## ⚠️ **CRITICAL WORKFLOW REQUIREMENT:**
|
|
260
|
-
|
|
261
|
-
**每完成一步,必须调用 \`dev-feedback\` 并展示以下信息:**
|
|
262
|
-
|
|
263
|
-
### 展示内容
|
|
264
|
-
1. **当前阶段**:完成的步骤/阶段名称
|
|
265
|
-
2. **工作摘要**:完成的主要工作内容
|
|
266
|
-
3. **自检结果**:
|
|
267
|
-
- 是否有多方案未选择
|
|
268
|
-
- 是否有信息不明确或需要澄清的内容
|
|
269
|
-
- 是否发现冲突(代码/逻辑/信息)
|
|
270
|
-
- 是否偏离计划需要调整
|
|
271
|
-
- 是否即将执行重要操作(删除代码、修改核心逻辑、数据库变更等)
|
|
272
|
-
- 是否存在"TBD"、"待确认"、"Question"等需要用户确认的内容
|
|
273
|
-
- 是否涉及项目/系统范围确认(无论已确认还是待确认,都必须调用 dev-feedback 让用户确认)
|
|
274
|
-
4. **下一步**:下一步计划(如有)
|
|
275
|
-
|
|
276
|
-
**最后一步结束后,确认:是否满足所有需求和完成了计划?**
|
|
277
|
-
|
|
278
|
-
### 规则
|
|
279
|
-
- 宁可多确认,不可漏确认
|
|
280
|
-
- 严禁将待确认内容留到最终文档
|
|
281
|
-
|
|
282
|
-
**⚠️ 立即调用 dev-feedback 开始确认!**`;
|
|
236
|
+
resultText = `${promptResult}\n\n${devFeedbackContentSuffix}`;
|
|
283
237
|
}
|
|
284
238
|
} else {
|
|
285
239
|
throw new Error(`Unknown tool type: ${tool.type}`);
|
|
@@ -289,12 +243,11 @@ async function handleToolCall(req) {
|
|
|
289
243
|
content: [{ type: 'text', text: resultText }]
|
|
290
244
|
});
|
|
291
245
|
} catch (error) {
|
|
292
|
-
//
|
|
293
|
-
console.error(`[MCP
|
|
246
|
+
// 仅debug模式下输出错误日志(使用stderr,不干扰MCP协议)
|
|
247
|
+
if (debug) console.error(`[MCP] Tool error: ${name} - ${error.message}`);
|
|
294
248
|
|
|
295
249
|
// 特殊处理:某些"错误"实际上是正常的用户交互结果
|
|
296
250
|
if (error.message.includes('用户要求调整计划')) {
|
|
297
|
-
console.error(`[MCP信息] 用户计划调整请求: ${error.message}`);
|
|
298
251
|
return createSuccessResponse(req.id, {
|
|
299
252
|
content: [{ type: 'text', text: error.message }]
|
|
300
253
|
});
|
|
@@ -319,14 +272,10 @@ async function handleToolCall(req) {
|
|
|
319
272
|
}
|
|
320
273
|
}
|
|
321
274
|
|
|
322
|
-
//
|
|
275
|
+
// 支持优雅降级的初始化
|
|
323
276
|
const initSuccess = await initializeServerSync();
|
|
324
|
-
if (!initSuccess) {
|
|
325
|
-
console.error('[MCP-Server]
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (CFG.debug) {
|
|
329
|
-
console.error('[MCP-Server] Debug mode enabled');
|
|
277
|
+
if (!initSuccess && debug) {
|
|
278
|
+
console.error('[MCP-Server] Running in degraded mode');
|
|
330
279
|
}
|
|
331
280
|
|
|
332
281
|
// 初始化完成后才开始监听stdin
|
|
@@ -349,3 +298,35 @@ process.stdin.on('data', chunk => {
|
|
|
349
298
|
});
|
|
350
299
|
}
|
|
351
300
|
});
|
|
301
|
+
|
|
302
|
+
const devFeedbackContentSuffix = `
|
|
303
|
+
---
|
|
304
|
+
## ⚠️ **CRITICAL WORKFLOW REQUIREMENT:**
|
|
305
|
+
|
|
306
|
+
**每完成一步,必须调用 \`dev-feedback\` 并展示以下信息:**
|
|
307
|
+
|
|
308
|
+
### 触发时机
|
|
309
|
+
- 每一步**工作开始之前** 或 每一步**工作完成之后**
|
|
310
|
+
- 任何**计划开始执行之前** 或 任何**计划完成之后**
|
|
311
|
+
- 有设计,计划调整或重要变化的时候
|
|
312
|
+
- 有**重大问题或风险**需要用户确认的时候
|
|
313
|
+
- 有**决策/方案**需要用户确认或者有**任何疑问**需要用户确认的时候
|
|
314
|
+
|
|
315
|
+
### 展示内容
|
|
316
|
+
1. **当前阶段**:完成的步骤/阶段名称
|
|
317
|
+
2. **工作摘要**:完成的主要工作内容
|
|
318
|
+
3. **自检结果**:
|
|
319
|
+
- 是否有多方案未选择
|
|
320
|
+
- 是否有信息不明确或需要澄清的内容
|
|
321
|
+
- 是否发现冲突(代码/逻辑/信息)
|
|
322
|
+
- 是否偏离计划需要调整
|
|
323
|
+
- 是否即将执行重要操作(删除代码、修改核心逻辑、数据库变更等)
|
|
324
|
+
- 是否存在"TBD"、"待确认"、"Question"等需要用户确认的内容
|
|
325
|
+
- 是否涉及项目/系统范围确认(无论已确认还是待确认,都必须调用 dev-feedback 让用户确认)
|
|
326
|
+
4. **下一步**:下一步计划(如有)
|
|
327
|
+
|
|
328
|
+
### 规则
|
|
329
|
+
- 尽可能多确认,不可遗漏确认
|
|
330
|
+
- 严禁将待确认内容留到最终文档(包括代码、文档、配置等)
|
|
331
|
+
|
|
332
|
+
**⚠️ 立即调用 dev-feedback 开始确认!**`;
|
package/tools.js
CHANGED
|
@@ -262,7 +262,7 @@ export async function enhancedDevFeedback(feedbackInput = {}) {
|
|
|
262
262
|
|
|
263
263
|
// For automated mode (testing only), return first option
|
|
264
264
|
if (process.env.AUTOMATED_MODE === 'true') {
|
|
265
|
-
console.
|
|
265
|
+
console.error('🤖 [AUTOMATED_MODE] Returning:', options[0]);
|
|
266
266
|
return options[0];
|
|
267
267
|
}
|
|
268
268
|
|
|
@@ -479,30 +479,23 @@ export async function handleDevManual() {
|
|
|
479
479
|
}
|
|
480
480
|
|
|
481
481
|
export async function handleDevFeedback(args) {
|
|
482
|
-
console.error('[DEBUG] handleDevFeedback called with args:', JSON.stringify(args, null, 2));
|
|
483
|
-
|
|
484
482
|
// Support both old simple format and new enhanced format
|
|
485
483
|
if (args.phase || args.context || args.allowPlanAdjustment) {
|
|
486
|
-
console.error('[DEBUG] Taking enhanced feedback branch');
|
|
487
484
|
// Use enhanced feedback for new format
|
|
488
485
|
return await enhancedDevFeedback(args);
|
|
489
486
|
}
|
|
490
487
|
|
|
491
|
-
console.error('[DEBUG] Taking legacy feedback branch');
|
|
492
|
-
|
|
493
488
|
// Legacy support for old format
|
|
494
489
|
const { title, message, options = [] } = args;
|
|
495
490
|
if (process.env.AUTOMATED_MODE === 'true') {
|
|
496
491
|
// Return first option or default for automated testing
|
|
497
|
-
console.
|
|
492
|
+
console.error('🤖 [AUTOMATED_MODE] Returning:', options.length ? options[0] : '继续');
|
|
498
493
|
return options.length ? options[0] : '继续';
|
|
499
494
|
}
|
|
500
495
|
|
|
501
496
|
// 简化逻辑:直接传入options,函数内部自动判断场景
|
|
502
497
|
const finalOptions = getStandardButtons(options.length > 0 ? options : null);
|
|
503
498
|
|
|
504
|
-
console.error('[DEBUG] Calling showConfirmDialog (heartbeat is handled in native.js)...');
|
|
505
|
-
|
|
506
499
|
try {
|
|
507
500
|
const result = await showConfirmDialog({
|
|
508
501
|
title,
|
|
@@ -531,8 +524,6 @@ export async function handleDevFeedback(args) {
|
|
|
531
524
|
}
|
|
532
525
|
return result;
|
|
533
526
|
} catch (error) {
|
|
534
|
-
console.error('❌ [FEEDBACK ERROR]:', error.message);
|
|
535
|
-
|
|
536
527
|
// Check if this is a dialog system failure
|
|
537
528
|
if (error.message.includes('DIALOG_SYSTEM_FAILURE')) {
|
|
538
529
|
// Critical failure - inform user and stop AI conversation
|
|
@@ -545,15 +536,11 @@ export async function handleDevFeedback(args) {
|
|
|
545
536
|
|
|
546
537
|
**需要您确认:** 请输入 "confirmed" 确认已了解此错误,AI将停止当前对话。`;
|
|
547
538
|
|
|
548
|
-
console.log('🚨 [CRITICAL] Dialog system failure - stopping AI conversation');
|
|
549
|
-
console.log(failureMessage);
|
|
550
|
-
|
|
551
539
|
// This will be returned to the AI, causing it to stop and wait for user input
|
|
552
540
|
throw new Error(`CRITICAL_DIALOG_FAILURE: ${failureMessage}`);
|
|
553
541
|
}
|
|
554
542
|
|
|
555
543
|
// For other errors, try browser fallback
|
|
556
|
-
console.log('🔄 [FALLBACK] Trying browser dialog...');
|
|
557
544
|
const { showConfirmDialog: browserConfirm } = await import('./dialog/browser.js');
|
|
558
545
|
|
|
559
546
|
return await browserConfirm({
|
|
@@ -609,7 +596,8 @@ export async function handleGitlabMrFetch(args) {
|
|
|
609
596
|
const projectEnc = encodeURIComponent(projectPath);
|
|
610
597
|
const headers = createAuthHeaders('gitlab', token);
|
|
611
598
|
|
|
612
|
-
|
|
599
|
+
const debug = process.env.DEBUG_LOG === 'true';
|
|
600
|
+
if (debug) console.error(`[GitLab MR] Fetching MR ${mrIid} from ${projectPath}`);
|
|
613
601
|
|
|
614
602
|
try {
|
|
615
603
|
// Fetch MR details
|
|
@@ -680,12 +668,12 @@ ${mrData.description || '_No description provided_'}
|
|
|
680
668
|
output += '_No changes found_\n';
|
|
681
669
|
}
|
|
682
670
|
|
|
683
|
-
console.error(`[GitLab MR] Successfully fetched MR with ${changesData.changes?.length || 0} changed files`);
|
|
671
|
+
if (debug) console.error(`[GitLab MR] Successfully fetched MR with ${changesData.changes?.length || 0} changed files`);
|
|
684
672
|
|
|
685
673
|
return output;
|
|
686
674
|
|
|
687
675
|
} catch (error) {
|
|
688
|
-
console.error(`[GitLab MR] Error:`, error.message);
|
|
676
|
+
if (debug) console.error(`[GitLab MR] Error:`, error.message);
|
|
689
677
|
throw error;
|
|
690
678
|
}
|
|
691
679
|
}
|
package/utils.js
CHANGED
|
@@ -113,6 +113,7 @@ export function validateRequest(req) {
|
|
|
113
113
|
* @returns {Function} Wrapped handler
|
|
114
114
|
*/
|
|
115
115
|
export function withErrorHandling(handler) {
|
|
116
|
+
const debug = process.env.DEBUG_LOG === 'true';
|
|
116
117
|
return async (req) => {
|
|
117
118
|
try {
|
|
118
119
|
const validation = validateRequest(req);
|
|
@@ -123,7 +124,8 @@ export function withErrorHandling(handler) {
|
|
|
123
124
|
|
|
124
125
|
return await handler(req);
|
|
125
126
|
} catch (error) {
|
|
126
|
-
|
|
127
|
+
// 使用stderr输出,不干扰MCP协议的stdout通信
|
|
128
|
+
if (debug) console.error(`Handler error for ${req.method}:`, error.message);
|
|
127
129
|
return createErrorResponse(req.id, MCP_ERROR_CODES.INTERNAL_ERROR, error.message);
|
|
128
130
|
}
|
|
129
131
|
};
|