mcp-osp-prompt 1.0.0
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 +358 -0
- package/config.js +340 -0
- package/dialog/browser.js +532 -0
- package/dialog/constants.js +68 -0
- package/dialog/http-server.js +95 -0
- package/dialog/index.js +106 -0
- package/dialog/native.js +345 -0
- package/dialog/objc.js +303 -0
- package/dialog/utils.js +140 -0
- package/fetcher.js +56 -0
- package/package.json +49 -0
- package/platform-utils.js +206 -0
- package/prompt-manager.js +1027 -0
- package/resource-manager.js +358 -0
- package/server.js +305 -0
- package/test.js +93 -0
- package/tools.js +701 -0
- package/utils.js +145 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import url from 'url';
|
|
3
|
+
|
|
4
|
+
let resolverQueue = [];
|
|
5
|
+
let server = null;
|
|
6
|
+
let serverPort = null;
|
|
7
|
+
|
|
8
|
+
export function startDialogServer() {
|
|
9
|
+
if (server && serverPort) {
|
|
10
|
+
console.log(`[Dialog Server] Reusing existing server on port ${serverPort}`);
|
|
11
|
+
return Promise.resolve(serverPort);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
server = http.createServer((req, res) => {
|
|
16
|
+
const parsedUrl = url.parse(req.url, true);
|
|
17
|
+
|
|
18
|
+
// 设置CORS头
|
|
19
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
20
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
21
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
22
|
+
|
|
23
|
+
if (req.method === 'OPTIONS') {
|
|
24
|
+
res.writeHead(200);
|
|
25
|
+
res.end();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (parsedUrl.pathname === '/submit') {
|
|
30
|
+
const response = parsedUrl.query.text || '';
|
|
31
|
+
console.log(`[Dialog Server] Received response: "${response}"`);
|
|
32
|
+
|
|
33
|
+
// 设置CORS头并返回成功响应
|
|
34
|
+
res.writeHead(200, {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
'Access-Control-Allow-Origin': '*'
|
|
37
|
+
});
|
|
38
|
+
res.end(JSON.stringify({ success: true, message: 'Response received successfully' }));
|
|
39
|
+
|
|
40
|
+
// 解决最新的Promise
|
|
41
|
+
if (resolverQueue.length > 0) {
|
|
42
|
+
const resolver = resolverQueue.shift(); // 取出最早的resolver
|
|
43
|
+
console.log(`[Dialog Server] Resolving promise with response: "${response}" (${resolverQueue.length} remaining)`);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
resolver(response);
|
|
47
|
+
console.log('[Dialog Server] Promise resolved successfully');
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.log('[Dialog Server] Error resolving promise:', error);
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
console.log('[Dialog Server] Warning: No resolver waiting for response');
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
res.writeHead(404);
|
|
56
|
+
res.end('Not found');
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
server.listen(0, 'localhost', () => {
|
|
61
|
+
serverPort = server.address().port;
|
|
62
|
+
console.log(`[Dialog Server] Started on http://localhost:${serverPort}`);
|
|
63
|
+
resolve(serverPort);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function waitForResponse() {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
console.log(`[Dialog Server] Adding resolver to queue (current queue size: ${resolverQueue.length})`);
|
|
71
|
+
resolverQueue.push(resolve);
|
|
72
|
+
|
|
73
|
+
// 30秒超时
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
const index = resolverQueue.indexOf(resolve);
|
|
76
|
+
if (index !== -1) {
|
|
77
|
+
console.log('[Dialog Server] Response timeout (30s), removing resolver from queue');
|
|
78
|
+
resolverQueue.splice(index, 1);
|
|
79
|
+
resolve('');
|
|
80
|
+
}
|
|
81
|
+
}, 30000);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function stopDialogServer() {
|
|
86
|
+
if (server) {
|
|
87
|
+
server.close();
|
|
88
|
+
server = null;
|
|
89
|
+
serverPort = null;
|
|
90
|
+
// 清理所有pending resolvers
|
|
91
|
+
resolverQueue.forEach(resolver => resolver(''));
|
|
92
|
+
resolverQueue = [];
|
|
93
|
+
console.log('[Dialog Server] Stopped');
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dialog/index.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { showInputDialog as nativeInput, showConfirmDialog as nativeConfirm, showSelectDialog as nativeSelect, showPlanAdjustmentDialog as nativePlanAdjustment } from './native.js';
|
|
2
|
+
import { showInputDialog as browserInput, showConfirmDialog as browserConfirm, showSelectDialog as browserSelect, showPlanAdjustmentDialog as browserPlanAdjustment } from './browser.js';
|
|
3
|
+
// ✅ UNIFIED: 导入统一的常量定义,避免重复定义
|
|
4
|
+
import {
|
|
5
|
+
BASE_DIALOG_BUTTONS as BASE_BUTTONS,
|
|
6
|
+
STANDARD_DIALOG_BUTTONS as STD_BUTTONS,
|
|
7
|
+
getStandardButtons as getStandardButtonsFromConstants,
|
|
8
|
+
validateDialogButtons as validateDialogButtonsFromConstants
|
|
9
|
+
} from './constants.js';
|
|
10
|
+
|
|
11
|
+
// 🔵 UNIFIED: 重新导出统一的常量和函数,保持API兼容性
|
|
12
|
+
export const BASE_DIALOG_BUTTONS = BASE_BUTTONS;
|
|
13
|
+
export const STANDARD_DIALOG_BUTTONS = STD_BUTTONS;
|
|
14
|
+
|
|
15
|
+
function getDialogMode() {
|
|
16
|
+
return process.env.DIALOG_MODE || 'auto'; // 'native', 'browser', 'auto'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function showInputDialog(opts) {
|
|
20
|
+
// 🔵 REFACTOR: 统一按钮标准 - 输入弹窗使用默认【继续】和【修改计划】
|
|
21
|
+
const standardizedOpts = {
|
|
22
|
+
...opts,
|
|
23
|
+
buttons: opts.buttons || getStandardButtons()
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const mode = getDialogMode();
|
|
27
|
+
if (mode === 'browser') {
|
|
28
|
+
return await browserInput(standardizedOpts);
|
|
29
|
+
} else if (mode === 'native') {
|
|
30
|
+
return await nativeInput(standardizedOpts);
|
|
31
|
+
} else {
|
|
32
|
+
try {
|
|
33
|
+
return await nativeInput(standardizedOpts);
|
|
34
|
+
} catch (_) {
|
|
35
|
+
return await browserInput(standardizedOpts);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function showConfirmDialog(opts = {}) {
|
|
41
|
+
// 🔵 REFACTOR: 统一按钮标准 - 确认弹窗使用默认【继续】和【修改计划】
|
|
42
|
+
const standardizedOpts = {
|
|
43
|
+
...opts,
|
|
44
|
+
buttons: opts.buttons || getStandardButtons()
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const mode = getDialogMode();
|
|
48
|
+
if (mode === 'browser') {
|
|
49
|
+
return await browserConfirm(standardizedOpts);
|
|
50
|
+
} else if (mode === 'native') {
|
|
51
|
+
return await nativeConfirm(standardizedOpts);
|
|
52
|
+
} else {
|
|
53
|
+
try {
|
|
54
|
+
return await nativeConfirm(standardizedOpts);
|
|
55
|
+
} catch (_) {
|
|
56
|
+
return await browserConfirm(standardizedOpts);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function showSelectDialog(opts) {
|
|
62
|
+
// 🔵 REFACTOR: Task 1.1 - 应用标准按钮配置到选择弹窗
|
|
63
|
+
const standardizedOpts = {
|
|
64
|
+
...opts,
|
|
65
|
+
// 对于选择弹窗,应用统一的标准按钮配置
|
|
66
|
+
items: getStandardButtons(opts.items)
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const mode = getDialogMode();
|
|
70
|
+
if (mode === 'browser') {
|
|
71
|
+
return await browserSelect(standardizedOpts);
|
|
72
|
+
} else if (mode === 'native') {
|
|
73
|
+
return await nativeSelect(standardizedOpts);
|
|
74
|
+
} else {
|
|
75
|
+
try {
|
|
76
|
+
return await nativeSelect(standardizedOpts);
|
|
77
|
+
} catch (_) {
|
|
78
|
+
return await browserSelect(standardizedOpts);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function showPlanAdjustmentDialog(opts = {}) {
|
|
84
|
+
// 🔵 REFACTOR: 统一按钮标准 - 计划调整弹窗使用默认【继续】和【修改计划】
|
|
85
|
+
const standardizedOpts = {
|
|
86
|
+
...opts,
|
|
87
|
+
options: opts.options || getStandardButtons()
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const mode = getDialogMode();
|
|
91
|
+
if (mode === 'browser') {
|
|
92
|
+
return await browserPlanAdjustment(standardizedOpts);
|
|
93
|
+
} else if (mode === 'native') {
|
|
94
|
+
return await nativePlanAdjustment(standardizedOpts);
|
|
95
|
+
} else {
|
|
96
|
+
try {
|
|
97
|
+
return await nativePlanAdjustment(standardizedOpts);
|
|
98
|
+
} catch (_) {
|
|
99
|
+
return await browserPlanAdjustment(standardizedOpts);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ✅ UNIFIED: 重新导出统一的函数,避免重复实现
|
|
105
|
+
export const getStandardButtons = getStandardButtonsFromConstants;
|
|
106
|
+
export const validateDialogButtons = validateDialogButtonsFromConstants;
|
package/dialog/native.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { escapeAppleScriptString } from './utils.js';
|
|
4
|
+
// 🟢 GREEN: Task 2.2 - 导入CFG配置用于native弹窗宽度设置
|
|
5
|
+
import { CFG } from '../config.js';
|
|
6
|
+
// ✅ UNIFIED: 导入统一的按钮管理常量
|
|
7
|
+
import { BASE_DIALOG_BUTTONS, getSafeButtons, validateDialogButtons } from './constants.js';
|
|
8
|
+
|
|
9
|
+
export async function showInputDialog({ title = '输入', message = '请输入内容:', defaultValue = '' }) {
|
|
10
|
+
// Handle automated testing mode - return default value without showing dialog
|
|
11
|
+
if (process.env.AUTOMATED_MODE === 'true') {
|
|
12
|
+
console.log(`🤖 [AUTOMATED] showInputDialog: ${title}, returning: "${defaultValue}"`);
|
|
13
|
+
return defaultValue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const platform = os.platform();
|
|
17
|
+
|
|
18
|
+
// 🎯 USER-REQUIRED: 创建真正的20行高度输入框(用户明确要求)
|
|
19
|
+
let expandedDefaultText = defaultValue || '';
|
|
20
|
+
|
|
21
|
+
if (!expandedDefaultText) {
|
|
22
|
+
// 创建简洁的20行高度输入框(用户要求更空白)
|
|
23
|
+
const lines = [
|
|
24
|
+
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '' // 15个空行
|
|
25
|
+
];
|
|
26
|
+
expandedDefaultText = lines.join('\n');
|
|
27
|
+
} else {
|
|
28
|
+
// 如果有默认文本,强制扩展到20行
|
|
29
|
+
const lines = expandedDefaultText.split('\n');
|
|
30
|
+
const targetLines = Math.max(20, lines.length);
|
|
31
|
+
if (lines.length < targetLines) {
|
|
32
|
+
expandedDefaultText += '\n'.repeat(targetLines - lines.length);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 验证行数
|
|
37
|
+
const actualLines = expandedDefaultText.split('\n').length;
|
|
38
|
+
console.log(`[Native Dialog] 📏 REAL 20-line input box: ${actualLines} lines created (target: 20 lines, CFG.nativeDialogWidth=${CFG.nativeDialogWidth}px)`);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
let command, args;
|
|
42
|
+
if (platform === 'darwin') {
|
|
43
|
+
// Use the improved escapeAppleScriptString from utils.js for consistency
|
|
44
|
+
const escapeForAppleScript = (str) => {
|
|
45
|
+
return escapeAppleScriptString(str);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const safeTitle = escapeForAppleScript(title);
|
|
49
|
+
const safeMessage = escapeForAppleScript(message);
|
|
50
|
+
const safeDefaultText = escapeForAppleScript(expandedDefaultText);
|
|
51
|
+
|
|
52
|
+
// Use optimized AppleScript to increase input box size
|
|
53
|
+
const applescript = `
|
|
54
|
+
set dialogResult to display dialog "${safeMessage}" with title "${safeTitle}" ¬
|
|
55
|
+
default answer "${safeDefaultText}" ¬
|
|
56
|
+
buttons {"确认"} ¬
|
|
57
|
+
default button "确认" ¬
|
|
58
|
+
giving up after 300
|
|
59
|
+
|
|
60
|
+
if button returned of dialogResult is "确认" then
|
|
61
|
+
return text returned of dialogResult
|
|
62
|
+
else
|
|
63
|
+
error "用户取消"
|
|
64
|
+
end if
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
console.log(`[Native Dialog] 🔄 Executing AppleScript for textarea...`);
|
|
69
|
+
const result = execSync(`osascript -e '${applescript}'`, {
|
|
70
|
+
encoding: 'utf8',
|
|
71
|
+
timeout: 1800000 // 30 minutes timeout (用户要求增加timeout)
|
|
72
|
+
}).trim();
|
|
73
|
+
|
|
74
|
+
console.log(`[Native Dialog] ✅ AppleScript returned result (length: ${result.length})`);
|
|
75
|
+
|
|
76
|
+
// Clean up the result - remove the placeholder text we added
|
|
77
|
+
let cleanedResult = result;
|
|
78
|
+
if (!defaultValue && result.includes('在此输入您的内容...')) {
|
|
79
|
+
cleanedResult = result.replace(/在此输入您的内容\.\.\.\n*/g, '').trim();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return cleanedResult;
|
|
83
|
+
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.log(`[Native Dialog] ⚠️ Textarea AppleScript error:`, {
|
|
86
|
+
message: error.message,
|
|
87
|
+
status: error.status,
|
|
88
|
+
signal: error.signal
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// 更精确的用户取消检测
|
|
92
|
+
const isUserCancelled =
|
|
93
|
+
error.message && (
|
|
94
|
+
error.message.includes('User canceled') ||
|
|
95
|
+
error.message.includes('用户取消') ||
|
|
96
|
+
error.message.includes('execution error: User canceled') ||
|
|
97
|
+
error.message.includes('button returned:取消') ||
|
|
98
|
+
(error.status === 1 && error.message.includes('button returned'))
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (isUserCancelled) {
|
|
102
|
+
console.log(`[Native Dialog] 👤 User cancelled textarea dialog`);
|
|
103
|
+
return defaultValue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(`[Native Dialog] 💥 Textarea system error: ${error.message}`);
|
|
107
|
+
return defaultValue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
} else {
|
|
111
|
+
// Linux zenity support for multiline
|
|
112
|
+
// Use text-info with editable mode for multiline
|
|
113
|
+
const command = 'zenity';
|
|
114
|
+
const args = [
|
|
115
|
+
'--text-info',
|
|
116
|
+
'--editable',
|
|
117
|
+
'--title', title,
|
|
118
|
+
'--text', `${message}\n\n\n\n请输入内容(支持多行):`
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
let result;
|
|
122
|
+
if (defaultValue) {
|
|
123
|
+
// Special handling for Linux multiline with default value
|
|
124
|
+
result = execSync(`echo "${expandedDefaultText || defaultValue}" | "${command}" ${args.map(a => `"${a.replace(/"/g, '\\"')}"`).join(' ')}`, {
|
|
125
|
+
encoding: 'utf8',
|
|
126
|
+
timeout: 30000
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
result = execSync(`"${command}" ${args.map(a => `"${a.replace(/"/g, '\\"')}"`).join(' ')}`, {
|
|
130
|
+
encoding: 'utf8',
|
|
131
|
+
timeout: 30000
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 清理结果 - 移除我们添加的placeholder文本
|
|
136
|
+
let cleanedResult = result.trim();
|
|
137
|
+
if (!defaultValue && cleanedResult.includes('在此输入您的内容...')) {
|
|
138
|
+
cleanedResult = cleanedResult.replace(/在此输入您的内容\.\.\.\n*/g, '').trim();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return cleanedResult;
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('❌ Dialog error:', error.message);
|
|
145
|
+
return defaultValue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function showConfirmDialog({ title = '确认', message = '请选择:', buttons }) {
|
|
150
|
+
// ✅ UNIFIED: 使用统一的按钮管理系统
|
|
151
|
+
buttons = getSafeButtons(buttons);
|
|
152
|
+
console.log('🔧 [BUTTON-UNIFIED] Native dialog using unified button management:', buttons);
|
|
153
|
+
|
|
154
|
+
// Handle automated testing mode
|
|
155
|
+
if (process.env.AUTOMATED_MODE === 'true') {
|
|
156
|
+
// ✅ UNIFIED: 验证按钮包含"修改计划"选项,确保统一标准
|
|
157
|
+
if (!validateDialogButtons(buttons)) {
|
|
158
|
+
console.warn(`⚠️ [AUTOMATED] Native dialog missing '修改计划' option:`, buttons);
|
|
159
|
+
}
|
|
160
|
+
console.log(`🤖 [AUTOMATED] Native showConfirmDialog: ${title}, returning: "${buttons[0]}" (buttons: ${JSON.stringify(buttons)})`);
|
|
161
|
+
return buttons[0];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const platform = os.platform();
|
|
165
|
+
|
|
166
|
+
console.log('🖥️ [NATIVE DIALOG] Showing confirmation dialog:', { title, message, buttons, platform });
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
let command, args;
|
|
170
|
+
if (platform === 'darwin') {
|
|
171
|
+
// Use enhanced escaping for all text content to handle special characters safely
|
|
172
|
+
const safeTitle = escapeAppleScriptString(title).trim() || 'Confirmation';
|
|
173
|
+
const safeMessage = escapeAppleScriptString(message).trim();
|
|
174
|
+
|
|
175
|
+
// Clean button text and ensure they're safe for AppleScript
|
|
176
|
+
const safeButtons = buttons.map(b => {
|
|
177
|
+
// Remove emojis from buttons but keep meaningful text
|
|
178
|
+
let cleanButton = b
|
|
179
|
+
.replace(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu, '')
|
|
180
|
+
.trim();
|
|
181
|
+
// 🔥 关键修复:在调用escapeAppleScriptString之前,先移除逗号(全角和半角)
|
|
182
|
+
// 因为escapeAppleScriptString会将全角逗号转为半角逗号,而半角逗号会破坏AppleScript按钮语法
|
|
183
|
+
cleanButton = cleanButton.replace(/[,,]/g, ' '); // 移除全角和半角逗号,替换为空格
|
|
184
|
+
return escapeAppleScriptString(cleanButton) || '确定';
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const buttonsList = safeButtons.map(b => `"${b}"`).join(', ');
|
|
188
|
+
const script = `set selectedButton to button returned of (display dialog "${safeMessage}" with title "${safeTitle}" buttons {${buttonsList}} default button "${safeButtons[0]}")`;
|
|
189
|
+
|
|
190
|
+
console.log('📱 [APPLESCRIPT] Executing script...');
|
|
191
|
+
console.log('🔧 [DEBUG] Safe title:', safeTitle);
|
|
192
|
+
console.log('🔧 [DEBUG] Safe buttons:', safeButtons);
|
|
193
|
+
console.log('🔧 [DEBUG] Script length:', script.length);
|
|
194
|
+
|
|
195
|
+
const result = execSync(`osascript -e '${script}'`, {
|
|
196
|
+
encoding: 'utf8',
|
|
197
|
+
timeout: 1800000, // 30 minutes timeout (用户要求增加timeout)
|
|
198
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const selectedButton = result.trim();
|
|
202
|
+
console.log('✅ [USER SELECTED]:', selectedButton);
|
|
203
|
+
|
|
204
|
+
// Map safe button back to original button
|
|
205
|
+
const safeIndex = safeButtons.indexOf(selectedButton);
|
|
206
|
+
return safeIndex >= 0 ? buttons[safeIndex] : buttons[0];
|
|
207
|
+
|
|
208
|
+
} else {
|
|
209
|
+
// Linux zenity
|
|
210
|
+
console.log('🐧 [ZENITY] Showing dialog...');
|
|
211
|
+
const result = execSync(`zenity --question --title="${title}" --text="${message}" --ok-label="${buttons[0]}" --cancel-label="${buttons[1]}"`, {
|
|
212
|
+
encoding: 'utf8',
|
|
213
|
+
timeout: 300000
|
|
214
|
+
});
|
|
215
|
+
console.log('✅ [USER SELECTED]:', result.trim());
|
|
216
|
+
return result.trim() || buttons[0];
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('❌ [DIALOG ERROR]:', error.message);
|
|
220
|
+
console.log('⚠️ Dialog failed to display - this may indicate a system issue');
|
|
221
|
+
|
|
222
|
+
// Throw a specific error that indicates dialog system failure
|
|
223
|
+
throw new Error(`DIALOG_SYSTEM_FAILURE: ${error.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function showPlanAdjustmentDialog({ title = '', message = '', currentPlan = '', options }) { // ✅ UNIFIED: 只接收传入的选项,不设置默认值
|
|
228
|
+
if (!title || !message) {
|
|
229
|
+
throw new Error('Title and message are required for plan adjustment dialog');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ✅ UNIFIED: 使用统一的按钮管理系统
|
|
233
|
+
options = getSafeButtons(options);
|
|
234
|
+
console.log('🔧 [BUTTON-UNIFIED] Native plan dialog using unified button management:', options);
|
|
235
|
+
|
|
236
|
+
// Handle automated mode
|
|
237
|
+
if (process.env.AUTOMATED_MODE === 'true') {
|
|
238
|
+
// ✅ UNIFIED: 验证选项包含"修改计划"选项,确保统一标准
|
|
239
|
+
if (!validateDialogButtons(options)) {
|
|
240
|
+
console.warn(`⚠️ [AUTOMATED] Native plan dialog missing '修改计划' option:`, options);
|
|
241
|
+
}
|
|
242
|
+
console.log(`🤖 [AUTOMATED] Native showPlanAdjustmentDialog: ${title}, returning: "${options[0]}" (options: ${JSON.stringify(options)})`);
|
|
243
|
+
return options[0]; // Return first option
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const platform = os.platform();
|
|
247
|
+
|
|
248
|
+
// Enhanced message with current plan
|
|
249
|
+
let fullMessage = message;
|
|
250
|
+
if (currentPlan) {
|
|
251
|
+
fullMessage += `\n\n📝 当前计划:\n${currentPlan}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
let result;
|
|
256
|
+
|
|
257
|
+
if (platform === 'darwin') {
|
|
258
|
+
// Use enhanced escaping for all text content to handle special characters safely
|
|
259
|
+
const safeTitle = escapeAppleScriptString(title).trim() || 'Dialog';
|
|
260
|
+
const safeMessage = escapeAppleScriptString(fullMessage);
|
|
261
|
+
const safeOptions = options.map(b => {
|
|
262
|
+
// Remove emojis from options but keep meaningful text
|
|
263
|
+
let cleanOption = b
|
|
264
|
+
.replace(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu, '')
|
|
265
|
+
.trim();
|
|
266
|
+
// 🔥 关键修复:在调用escapeAppleScriptString之前,先移除逗号(全角和半角)
|
|
267
|
+
cleanOption = cleanOption.replace(/[,,]/g, ' '); // 移除全角和半角逗号,替换为空格
|
|
268
|
+
return escapeAppleScriptString(cleanOption) || '继续';
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Build script with properly escaped content
|
|
272
|
+
const scriptParts = [
|
|
273
|
+
'set selectedButton to button returned of',
|
|
274
|
+
`(display dialog "${safeMessage}"`,
|
|
275
|
+
`with title "${safeTitle}"`,
|
|
276
|
+
`buttons {"${safeOptions.join('", "')}"} `,
|
|
277
|
+
`default button "${safeOptions[0]}")`
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const script = scriptParts.join(' ');
|
|
281
|
+
console.log('🔧 [DEBUG] Plan adjustment script length:', script.length);
|
|
282
|
+
result = execSync(`osascript -e '${script}'`, { encoding: 'utf8', timeout: 30000 }).trim();
|
|
283
|
+
|
|
284
|
+
// If "修改计划" is selected, show input dialog using improved showInputDialog
|
|
285
|
+
if (result.includes('修改计划')) {
|
|
286
|
+
// 使用改进的showInputDialog函数,支持多行输入
|
|
287
|
+
const inputValue = await showInputDialog({
|
|
288
|
+
title: '计划修改',
|
|
289
|
+
message: '请输入您的调整建议:',
|
|
290
|
+
defaultValue: '请在此输入您的修改建议或调整要求...'
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
action: result,
|
|
295
|
+
input: inputValue
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
} else {
|
|
300
|
+
// Linux fallback using zenity
|
|
301
|
+
result = execSync(`zenity --list --title="${title}" --text="${fullMessage}" --column=Options ${options.map(i=>`\"${i}\"`).join(' ')}`, { encoding: 'utf8', timeout: 30000 }).trim();
|
|
302
|
+
|
|
303
|
+
// Handle input for adjustment if needed
|
|
304
|
+
if (result.includes('修改计划')) {
|
|
305
|
+
const inputValue = execSync(`zenity --text-info --editable --title="计划修改建议" --text="请输入您的调整建议:"`, { encoding: 'utf8', timeout: 300000 }).trim();
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
action: result,
|
|
309
|
+
input: inputValue
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return result;
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.warn(`Native plan adjustment dialog failed: ${error.message}, returning default option`);
|
|
317
|
+
return options[0];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
export async function showSelectDialog({ title = '选择', message = '请选择', items = [] }) {
|
|
321
|
+
const platform = os.platform();
|
|
322
|
+
if (!items.length) return '';
|
|
323
|
+
try {
|
|
324
|
+
if (platform === 'darwin') {
|
|
325
|
+
// Use enhanced escaping for all text content
|
|
326
|
+
const safeItems = items.map(i => `\"${escapeAppleScriptString(i)}\"`).join(', ');
|
|
327
|
+
const safeTitle = escapeAppleScriptString(title);
|
|
328
|
+
const safeMessage = escapeAppleScriptString(message);
|
|
329
|
+
const script = `set theChoice to choose from list {${safeItems}} with title \"${safeTitle}\" with prompt \"${safeMessage}\"`;
|
|
330
|
+
console.log('🔧 [DEBUG] Select dialog script length:', script.length);
|
|
331
|
+
const result = execSync(`osascript -e "${script}"`, { encoding: 'utf8', timeout: 30000 });
|
|
332
|
+
return result.trim();
|
|
333
|
+
|
|
334
|
+
} else {
|
|
335
|
+
// linux zenity list
|
|
336
|
+
const result = execSync(`zenity --list --title="${title}" --column=Options ${items.map(i=>`\"${i}\"`).join(' ')}`, { encoding: 'utf8', timeout: 30000 });
|
|
337
|
+
return result.trim();
|
|
338
|
+
}
|
|
339
|
+
} catch (_) {
|
|
340
|
+
/* ignore */
|
|
341
|
+
}
|
|
342
|
+
// fallback input
|
|
343
|
+
return await showInputDialog({ title, message: `${message}\n输入选项:`, defaultValue: items[0] });
|
|
344
|
+
}
|
|
345
|
+
|