coding-tool-x 3.4.0 → 3.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.
- package/dist/web/assets/{Analytics-DEjfL5Jx.js → Analytics-CbGxotgz.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DkRL_-tf.js → ConfigTemplates-oP6nrFEb.js} +1 -1
- package/dist/web/assets/{Home-CF-L640I.js → Home-DMntmEvh.js} +1 -1
- package/dist/web/assets/{PluginManager-BzNYTdNB.js → PluginManager-BUC_c7nH.js} +1 -1
- package/dist/web/assets/{ProjectList-C0-JgHMM.js → ProjectList-CW8J49n7.js} +1 -1
- package/dist/web/assets/{SessionList-CkZUdX5N.js → SessionList-7lYnF92v.js} +1 -1
- package/dist/web/assets/{SkillManager-Cak0-4d4.js → SkillManager-Cs08216i.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CGDJzwEr.js → WorkspaceManager-CY-oGtyB.js} +1 -1
- package/dist/web/assets/{index-Dz7v9OM0.css → index-5qy5NMIP.css} +1 -1
- package/dist/web/assets/index-ClCqKpvX.js +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +6 -2
- package/src/commands/doctor.js +2 -2
- package/src/commands/resume.js +1 -0
- package/src/commands/update.js +2 -1
- package/src/plugins/plugin-installer.js +1 -0
- package/src/server/api/claude-hooks.js +2 -3
- package/src/server/api/workspaces.js +2 -1
- package/src/server/codex-proxy-server.js +4 -92
- package/src/server/gemini-proxy-server.js +5 -28
- package/src/server/opencode-proxy-server.js +3 -93
- package/src/server/proxy-server.js +2 -57
- package/src/server/services/base/base-channel-service.js +247 -0
- package/src/server/services/base/proxy-utils.js +152 -0
- package/src/server/services/channel-health.js +30 -19
- package/src/server/services/channels.js +125 -293
- package/src/server/services/codex-channels.js +148 -513
- package/src/server/services/codex-env-manager.js +81 -21
- package/src/server/services/codex-settings-manager.js +20 -5
- package/src/server/services/gemini-channels.js +2 -7
- package/src/server/services/mcp-client.js +2 -1
- package/src/server/services/notification-hooks.js +9 -8
- package/src/server/services/oauth-credentials-service.js +12 -2
- package/src/server/services/opencode-channels.js +7 -9
- package/src/server/services/opencode-sessions.js +4 -2
- package/src/server/services/plugins-service.js +2 -1
- package/src/server/services/repo-scanner-base.js +1 -0
- package/src/server/services/skill-service.js +4 -2
- package/src/server/services/workspace-service.js +1 -0
- package/src/utils/port-helper.js +5 -5
- package/dist/web/assets/index-D_WItvHE.js +0 -2
|
@@ -77,6 +77,22 @@ function upsertManagedBlock(content, snippet) {
|
|
|
77
77
|
return `${stripped.trimEnd()}\n\n${snippet}\n`;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function buildExportCommand(envFilePath, homeDir) {
|
|
81
|
+
return `. "${buildHomeRelativeShellPath(envFilePath, homeDir)}"`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function buildPosixEnvFileContent(nextValues) {
|
|
85
|
+
const keys = Object.keys(nextValues).sort();
|
|
86
|
+
if (!keys.length) {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
return [
|
|
90
|
+
'# Managed by Coding-Tool',
|
|
91
|
+
...keys.map((key) => `export ${key}=${shellQuote(nextValues[key])}`),
|
|
92
|
+
''
|
|
93
|
+
].join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
function readJsonFile(filePath, fallbackValue) {
|
|
81
97
|
try {
|
|
82
98
|
if (!fs.existsSync(filePath)) {
|
|
@@ -181,29 +197,43 @@ function syncPosixEnvironment(nextValues, previousState, options) {
|
|
|
181
197
|
const {
|
|
182
198
|
runtime,
|
|
183
199
|
homeDir,
|
|
200
|
+
envFilePath,
|
|
184
201
|
stateFilePath,
|
|
185
202
|
shellEnv,
|
|
186
203
|
execSync
|
|
187
204
|
} = options;
|
|
188
205
|
const nextKeys = Object.keys(nextValues).sort();
|
|
189
206
|
let changed = false;
|
|
207
|
+
const { preferred, candidates } = getPosixProfileCandidates(homeDir, shellEnv);
|
|
208
|
+
const sourceSnippet = buildSourceSnippet(envFilePath, homeDir);
|
|
209
|
+
const sourceCommand = buildExportCommand(envFilePath, homeDir);
|
|
190
210
|
|
|
191
211
|
// 清理旧版本遗留的 shell profile 注入(迁移兼容)
|
|
192
212
|
const previousProfiles = Array.isArray(previousState.profiles) ? previousState.profiles : [];
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
213
|
+
const cleanupTargets = new Set([
|
|
214
|
+
...previousProfiles,
|
|
215
|
+
...candidates.filter((filePath) => fs.existsSync(filePath))
|
|
216
|
+
]);
|
|
217
|
+
if (!nextKeys.length) {
|
|
218
|
+
cleanupTargets.add(preferred);
|
|
219
|
+
}
|
|
220
|
+
for (const profilePath of cleanupTargets) {
|
|
221
|
+
if (!fs.existsSync(profilePath)) continue;
|
|
222
|
+
const currentContent = fs.readFileSync(profilePath, 'utf8');
|
|
223
|
+
if (!currentContent.includes(PROFILE_MARKER_START)) continue;
|
|
224
|
+
const nextContent = stripManagedBlock(currentContent);
|
|
225
|
+
const finalContent = nextContent ? `${nextContent}\n` : '';
|
|
226
|
+
changed = writeTextFileIfChanged(profilePath, finalContent) || changed;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (nextKeys.length > 0) {
|
|
230
|
+
changed = writeTextFileIfChanged(envFilePath, buildPosixEnvFileContent(nextValues)) || changed;
|
|
231
|
+
const currentProfileContent = fs.existsSync(preferred) ? fs.readFileSync(preferred, 'utf8') : '';
|
|
232
|
+
const nextProfileContent = upsertManagedBlock(currentProfileContent, sourceSnippet);
|
|
233
|
+
changed = writeTextFileIfChanged(preferred, nextProfileContent) || changed;
|
|
234
|
+
} else if (fs.existsSync(envFilePath)) {
|
|
235
|
+
fs.unlinkSync(envFilePath);
|
|
236
|
+
changed = true;
|
|
207
237
|
}
|
|
208
238
|
|
|
209
239
|
// macOS:用 launchctl 写入全局环境变量,新开终端/进程即生效
|
|
@@ -225,7 +255,7 @@ function syncPosixEnvironment(nextValues, previousState, options) {
|
|
|
225
255
|
writeJsonFile(stateFilePath, {
|
|
226
256
|
version: 1,
|
|
227
257
|
values: nextValues,
|
|
228
|
-
profiles: []
|
|
258
|
+
profiles: [preferred]
|
|
229
259
|
});
|
|
230
260
|
} else if (fs.existsSync(stateFilePath)) {
|
|
231
261
|
fs.unlinkSync(stateFilePath);
|
|
@@ -235,10 +265,10 @@ function syncPosixEnvironment(nextValues, previousState, options) {
|
|
|
235
265
|
changed,
|
|
236
266
|
reloadRequired: changed,
|
|
237
267
|
isFirstTime: Object.keys(previousState.values || {}).length === 0 && nextKeys.length > 0,
|
|
238
|
-
sourceCommand: null,
|
|
239
|
-
shellConfigPath: null,
|
|
240
|
-
shellConfigPaths: [],
|
|
241
|
-
envFilePath: null,
|
|
268
|
+
sourceCommand: nextKeys.length > 0 ? sourceCommand : null,
|
|
269
|
+
shellConfigPath: nextKeys.length > 0 ? preferred : null,
|
|
270
|
+
shellConfigPaths: nextKeys.length > 0 ? [preferred] : [],
|
|
271
|
+
envFilePath: nextKeys.length > 0 ? envFilePath : null,
|
|
242
272
|
managedKeys: nextKeys
|
|
243
273
|
};
|
|
244
274
|
}
|
|
@@ -302,13 +332,32 @@ function runLaunchctlCommand(args, execSync) {
|
|
|
302
332
|
try {
|
|
303
333
|
execSync('launchctl', args, {
|
|
304
334
|
stdio: ['ignore', 'ignore', 'ignore'],
|
|
305
|
-
timeout: 3000
|
|
335
|
+
timeout: 3000,
|
|
336
|
+
windowsHide: true
|
|
306
337
|
});
|
|
307
338
|
} catch {
|
|
308
339
|
// ignore launchctl failures; shell profile remains the durable source
|
|
309
340
|
}
|
|
310
341
|
}
|
|
311
342
|
|
|
343
|
+
function broadcastWindowsSettingChange(execSync) {
|
|
344
|
+
const script = [
|
|
345
|
+
'Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"',
|
|
346
|
+
'[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]',
|
|
347
|
+
'public static extern IntPtr SendMessageTimeout(',
|
|
348
|
+
' IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,',
|
|
349
|
+
' uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);',
|
|
350
|
+
'"@',
|
|
351
|
+
'$HWND_BROADCAST = [IntPtr]0xffff',
|
|
352
|
+
'$WM_SETTINGCHANGE = 0x1a',
|
|
353
|
+
'$SMTO_ABORTIFHUNG = 0x0002',
|
|
354
|
+
'$result = [UIntPtr]::Zero',
|
|
355
|
+
'[Win32.NativeMethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE,',
|
|
356
|
+
' [UIntPtr]::Zero, "Environment", $SMTO_ABORTIFHUNG, 5000, [ref]$result) | Out-Null'
|
|
357
|
+
].join('\n');
|
|
358
|
+
runWindowsEnvCommand(script, execSync);
|
|
359
|
+
}
|
|
360
|
+
|
|
312
361
|
function syncWindowsEnvironment(nextValues, previousState, options) {
|
|
313
362
|
const { stateFilePath, execSync } = options;
|
|
314
363
|
const nextKeys = Object.keys(nextValues).sort();
|
|
@@ -327,6 +376,15 @@ function syncWindowsEnvironment(nextValues, previousState, options) {
|
|
|
327
376
|
changed = true;
|
|
328
377
|
}
|
|
329
378
|
|
|
379
|
+
// 广播 WM_SETTINGCHANGE,通知已打开的应用(如 VSCode)刷新环境变量
|
|
380
|
+
if (changed) {
|
|
381
|
+
try {
|
|
382
|
+
broadcastWindowsSettingChange(execSync);
|
|
383
|
+
} catch {
|
|
384
|
+
// 广播失败不影响主流程,环境变量已写入注册表
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
330
388
|
if (nextKeys.length > 0) {
|
|
331
389
|
writeJsonFile(stateFilePath, {
|
|
332
390
|
version: 1,
|
|
@@ -356,7 +414,8 @@ function runWindowsEnvCommand(script, execSync) {
|
|
|
356
414
|
try {
|
|
357
415
|
execSync(command, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
358
416
|
stdio: ['ignore', 'ignore', 'ignore'],
|
|
359
|
-
timeout: 5000
|
|
417
|
+
timeout: 5000,
|
|
418
|
+
windowsHide: true
|
|
360
419
|
});
|
|
361
420
|
return;
|
|
362
421
|
} catch (error) {
|
|
@@ -413,6 +472,7 @@ function syncCodexUserEnvironment(envMap = {}, options = {}) {
|
|
|
413
472
|
module.exports = {
|
|
414
473
|
syncCodexUserEnvironment,
|
|
415
474
|
_test: {
|
|
475
|
+
broadcastWindowsSettingChange,
|
|
416
476
|
buildHomeRelativeShellPath,
|
|
417
477
|
buildNextEnvValues,
|
|
418
478
|
buildSourceSnippet,
|
|
@@ -196,6 +196,9 @@ function restoreSettings() {
|
|
|
196
196
|
removeKeys: ['CC_PROXY_KEY']
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
+
// 清理 process.env 中的代理 key(与 setProxyConfig 中的设置对应)
|
|
200
|
+
delete process.env.CC_PROXY_KEY;
|
|
201
|
+
|
|
199
202
|
console.log('Codex settings restored from backup');
|
|
200
203
|
return { success: true };
|
|
201
204
|
} catch (err) {
|
|
@@ -206,11 +209,18 @@ function restoreSettings() {
|
|
|
206
209
|
// 设置代理配置
|
|
207
210
|
function setProxyConfig(proxyPort) {
|
|
208
211
|
try {
|
|
209
|
-
//
|
|
210
|
-
|
|
212
|
+
// 先备份(config.toml 不存在时跳过备份)
|
|
213
|
+
if (configExists()) {
|
|
214
|
+
backupSettings();
|
|
215
|
+
}
|
|
211
216
|
|
|
212
|
-
//
|
|
213
|
-
|
|
217
|
+
// 读取当前配置(不存在时使用空配置)
|
|
218
|
+
let config;
|
|
219
|
+
try {
|
|
220
|
+
config = readConfig();
|
|
221
|
+
} catch (err) {
|
|
222
|
+
config = {};
|
|
223
|
+
}
|
|
214
224
|
|
|
215
225
|
// 设置 model_provider 为 proxy
|
|
216
226
|
config.model_provider = 'cc-proxy';
|
|
@@ -225,12 +235,17 @@ function setProxyConfig(proxyPort) {
|
|
|
225
235
|
name: 'cc-proxy',
|
|
226
236
|
base_url: `http://127.0.0.1:${proxyPort}/v1`,
|
|
227
237
|
wire_api: 'responses',
|
|
228
|
-
env_key: 'CC_PROXY_KEY'
|
|
238
|
+
env_key: 'CC_PROXY_KEY',
|
|
239
|
+
requires_openai_auth: false
|
|
229
240
|
};
|
|
230
241
|
|
|
231
242
|
// 写入配置
|
|
232
243
|
writeConfig(config);
|
|
233
244
|
|
|
245
|
+
// Windows: 注册表写入后当前进程和已打开的终端不会自动刷新环境变量
|
|
246
|
+
// 直接设置 process.env 确保从本进程派生的 Codex CLI 能读到 CC_PROXY_KEY
|
|
247
|
+
process.env.CC_PROXY_KEY = 'PROXY_KEY';
|
|
248
|
+
|
|
234
249
|
const envResult = syncCodexUserEnvironment({
|
|
235
250
|
CC_PROXY_KEY: 'PROXY_KEY'
|
|
236
251
|
}, {
|
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
5
5
|
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
6
|
+
const { normalizeGatewaySourceType } = require('./base/proxy-utils');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Gemini 渠道管理服务(多渠道架构)
|
|
@@ -17,13 +18,7 @@ const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
|
17
18
|
* - 使用 weight 和 maxConcurrency 控制负载均衡
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
22
|
-
if (normalized === 'claude') return 'claude';
|
|
23
|
-
if (normalized === 'codex') return 'codex';
|
|
24
|
-
if (normalized === 'gemini') return 'gemini';
|
|
25
|
-
return fallback;
|
|
26
|
-
}
|
|
21
|
+
// normalizeGatewaySourceType imported from base/proxy-utils
|
|
27
22
|
|
|
28
23
|
// 获取 Gemini 配置目录
|
|
29
24
|
function getGeminiDir() {
|
|
@@ -389,7 +389,8 @@ class McpClient extends EventEmitter {
|
|
|
389
389
|
this._child = spawn(resolvedCommand, args, {
|
|
390
390
|
env: mergedEnv,
|
|
391
391
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
392
|
-
cwd: finalCwd
|
|
392
|
+
cwd: finalCwd,
|
|
393
|
+
windowsHide: true
|
|
393
394
|
});
|
|
394
395
|
} catch (err) {
|
|
395
396
|
clearTimeout(timer);
|
|
@@ -200,7 +200,8 @@ function fire(eventType) {
|
|
|
200
200
|
\`--event-type=\${eventType}\`
|
|
201
201
|
], {
|
|
202
202
|
detached: true,
|
|
203
|
-
stdio: 'ignore'
|
|
203
|
+
stdio: 'ignore',
|
|
204
|
+
windowsHide: true
|
|
204
205
|
})
|
|
205
206
|
child.unref()
|
|
206
207
|
} catch (error) {
|
|
@@ -320,7 +321,7 @@ function notify(mode, message) {
|
|
|
320
321
|
const appleScript = 'display dialog "' + escapeForAppleScript(message) +
|
|
321
322
|
'" with title "' + escapeForAppleScript(title) +
|
|
322
323
|
'" buttons {"好的"} default button 1 with icon note'
|
|
323
|
-
execSync('osascript -e ' + JSON.stringify(appleScript), { stdio: 'ignore' })
|
|
324
|
+
execSync('osascript -e ' + JSON.stringify(appleScript), { stdio: 'ignore', windowsHide: true })
|
|
324
325
|
} else {
|
|
325
326
|
const fallbackScript = 'display notification "' + escapeForAppleScript(message) +
|
|
326
327
|
'" with title "' + escapeForAppleScript(title) + '" sound name "Glass"'
|
|
@@ -329,7 +330,7 @@ function notify(mode, message) {
|
|
|
329
330
|
' -message ' + JSON.stringify(message) +
|
|
330
331
|
' -sound Glass -activate com.apple.Terminal; ' +
|
|
331
332
|
'else osascript -e ' + JSON.stringify(fallbackScript) + '; fi'
|
|
332
|
-
execSync(command, { stdio: 'ignore' })
|
|
333
|
+
execSync(command, { stdio: 'ignore', windowsHide: true })
|
|
333
334
|
}
|
|
334
335
|
return
|
|
335
336
|
}
|
|
@@ -339,7 +340,7 @@ function notify(mode, message) {
|
|
|
339
340
|
const ps = "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('" +
|
|
340
341
|
escapeForPowerShellSingleQuote(message) + "', '" +
|
|
341
342
|
escapeForPowerShellSingleQuote(title) + "', 'OK', 'Information')"
|
|
342
|
-
execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore' })
|
|
343
|
+
execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore', windowsHide: true })
|
|
343
344
|
} else {
|
|
344
345
|
const toastXml = '<toast><visual><binding template="ToastGeneric"><text>' +
|
|
345
346
|
escapeForXml(title) + '</text><text>' + escapeForXml(message) +
|
|
@@ -353,7 +354,7 @@ function notify(mode, message) {
|
|
|
353
354
|
"[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Coding Tool').Show($toast) " +
|
|
354
355
|
"} catch { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('" +
|
|
355
356
|
escapeForPowerShellSingleQuote(message) + "', 5, '" + escapeForPowerShellSingleQuote(title) + "', 0x40) }"
|
|
356
|
-
execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore' })
|
|
357
|
+
execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore', windowsHide: true })
|
|
357
358
|
}
|
|
358
359
|
return
|
|
359
360
|
}
|
|
@@ -364,10 +365,10 @@ function notify(mode, message) {
|
|
|
364
365
|
execSync(
|
|
365
366
|
'zenity --info --title="' + escapedTitle + '" --text="' + escapedMessage +
|
|
366
367
|
'" 2>/dev/null || notify-send "' + escapedTitle + '" "' + escapedMessage + '"',
|
|
367
|
-
{ stdio: 'ignore' }
|
|
368
|
+
{ stdio: 'ignore', windowsHide: true }
|
|
368
369
|
)
|
|
369
370
|
} else {
|
|
370
|
-
execSync('notify-send "' + escapedTitle + '" "' + escapedMessage + '"', { stdio: 'ignore' })
|
|
371
|
+
execSync('notify-send "' + escapedTitle + '" "' + escapedMessage + '"', { stdio: 'ignore', windowsHide: true })
|
|
371
372
|
}
|
|
372
373
|
} catch (error) {
|
|
373
374
|
// ignore system notification failures
|
|
@@ -916,7 +917,7 @@ function testNotification({ type, testFeishu, webhookUrl } = {}) {
|
|
|
916
917
|
return sendFeishuTest(webhookUrl);
|
|
917
918
|
}
|
|
918
919
|
|
|
919
|
-
execSync(generateSystemNotificationCommand(type || 'notification', '这是一条测试通知'), { stdio: 'ignore' });
|
|
920
|
+
execSync(generateSystemNotificationCommand(type || 'notification', '这是一条测试通知'), { stdio: 'ignore', windowsHide: true });
|
|
920
921
|
}
|
|
921
922
|
|
|
922
923
|
module.exports = {
|
|
@@ -380,12 +380,21 @@ function stableFingerprintValue(tool, metadata) {
|
|
|
380
380
|
return stableId;
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
+
function resolveFingerprintValue(tool, metadata, options = {}) {
|
|
384
|
+
if (options.fingerprintMode === 'primary-token') {
|
|
385
|
+
return metadata.primaryToken
|
|
386
|
+
|| metadata.accessToken
|
|
387
|
+
|| stableFingerprintValue(tool, metadata);
|
|
388
|
+
}
|
|
389
|
+
return stableFingerprintValue(tool, metadata);
|
|
390
|
+
}
|
|
391
|
+
|
|
383
392
|
function upsertCredential(tool, metadata, options = {}) {
|
|
384
393
|
const store = readStore();
|
|
385
394
|
const toolStore = getToolStore(store, tool);
|
|
386
395
|
const now = Date.now();
|
|
387
396
|
const primaryToken = metadata.primaryToken || metadata.accessToken || '';
|
|
388
|
-
const fingerprint = fingerprintFor(tool,
|
|
397
|
+
const fingerprint = fingerprintFor(tool, resolveFingerprintValue(tool, metadata, options));
|
|
389
398
|
const existingIndex = toolStore.credentials.findIndex((item) => item.fingerprint === fingerprint);
|
|
390
399
|
const existing = existingIndex >= 0 ? toolStore.credentials[existingIndex] : null;
|
|
391
400
|
|
|
@@ -456,7 +465,8 @@ function syncLocalCredential(tool) {
|
|
|
456
465
|
}
|
|
457
466
|
|
|
458
467
|
const credentials = nativeCredentials.map((metadata) => upsertCredential(tool, metadata, {
|
|
459
|
-
source: 'synced-local'
|
|
468
|
+
source: 'synced-local',
|
|
469
|
+
fingerprintMode: 'primary-token'
|
|
460
470
|
}));
|
|
461
471
|
|
|
462
472
|
return {
|
|
@@ -4,18 +4,15 @@ const crypto = require('crypto');
|
|
|
4
4
|
const { PATHS } = require('../../config/paths');
|
|
5
5
|
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
6
6
|
const { setChannelConfig } = require('./opencode-settings-manager');
|
|
7
|
+
const { normalizeGatewaySourceType } = require('./base/proxy-utils');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* OpenCode 渠道管理服务
|
|
10
11
|
* 存储位置: ~/.cc-tool/opencode-channels.json
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (normalized === 'claude') return 'claude';
|
|
16
|
-
if (normalized === 'gemini') return 'gemini';
|
|
17
|
-
return 'codex';
|
|
18
|
-
}
|
|
14
|
+
// normalizeGatewaySourceType imported from base/proxy-utils
|
|
15
|
+
// OpenCode default fallback is 'codex'
|
|
19
16
|
|
|
20
17
|
function normalizeApiKey(value) {
|
|
21
18
|
if (typeof value !== 'string') return '';
|
|
@@ -80,7 +77,7 @@ function loadChannels() {
|
|
|
80
77
|
modelRedirects: ch.modelRedirects || [],
|
|
81
78
|
speedTestModel: ch.speedTestModel || null,
|
|
82
79
|
wireApi: ch.wireApi || 'openai', // OpenCode 默认使用 OpenAI 兼容格式
|
|
83
|
-
gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType),
|
|
80
|
+
gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType, 'codex'),
|
|
84
81
|
allowedModels: ch.allowedModels || []
|
|
85
82
|
};
|
|
86
83
|
normalized.providerKey = deriveProviderKey(normalized);
|
|
@@ -132,7 +129,7 @@ function createChannel(name, baseUrl, apiKey, extraConfig = {}) {
|
|
|
132
129
|
modelRedirects: extraConfig.modelRedirects || [],
|
|
133
130
|
speedTestModel: extraConfig.speedTestModel || null,
|
|
134
131
|
model: extraConfig.model || null,
|
|
135
|
-
gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType),
|
|
132
|
+
gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'codex'),
|
|
136
133
|
providerKey: extraConfig.providerKey || null,
|
|
137
134
|
presetId: extraConfig.presetId || null,
|
|
138
135
|
websiteUrl: extraConfig.websiteUrl || '',
|
|
@@ -169,7 +166,8 @@ function updateChannel(channelId, updates) {
|
|
|
169
166
|
gatewaySourceType: normalizeGatewaySourceType(
|
|
170
167
|
updates.gatewaySourceType !== undefined
|
|
171
168
|
? updates.gatewaySourceType
|
|
172
|
-
: oldChannel.gatewaySourceType
|
|
169
|
+
: oldChannel.gatewaySourceType,
|
|
170
|
+
'codex'
|
|
173
171
|
),
|
|
174
172
|
updatedAt: Date.now()
|
|
175
173
|
};
|
|
@@ -168,7 +168,8 @@ function runSqliteQuery(sql) {
|
|
|
168
168
|
const output = execFileSync('sqlite3', ['-json', OPENCODE_DB_PATH, sql], {
|
|
169
169
|
encoding: 'utf8',
|
|
170
170
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
171
|
-
maxBuffer: 10 * 1024 * 1024
|
|
171
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
172
|
+
windowsHide: true
|
|
172
173
|
}).trim();
|
|
173
174
|
|
|
174
175
|
if (!output) {
|
|
@@ -191,7 +192,8 @@ function runSqliteExec(sql) {
|
|
|
191
192
|
execFileSync('sqlite3', [OPENCODE_DB_PATH, sql], {
|
|
192
193
|
encoding: 'utf8',
|
|
193
194
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
194
|
-
maxBuffer: 10 * 1024 * 1024
|
|
195
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
196
|
+
windowsHide: true
|
|
195
197
|
});
|
|
196
198
|
}
|
|
197
199
|
|
|
@@ -1055,7 +1055,8 @@ class PluginsService {
|
|
|
1055
1055
|
execSync(`claude plugin marketplace add ${repo.url}`, {
|
|
1056
1056
|
encoding: 'utf8',
|
|
1057
1057
|
timeout: 30000,
|
|
1058
|
-
stdio: 'pipe'
|
|
1058
|
+
stdio: 'pipe',
|
|
1059
|
+
windowsHide: true
|
|
1059
1060
|
});
|
|
1060
1061
|
results.push({ repo: repo.url, success: true });
|
|
1061
1062
|
} catch (err) {
|
|
@@ -849,7 +849,8 @@ class SkillService {
|
|
|
849
849
|
const output = execFileSync(command, args, {
|
|
850
850
|
encoding: 'utf-8',
|
|
851
851
|
timeout: 3000,
|
|
852
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
852
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
853
|
+
windowsHide: true
|
|
853
854
|
}).trim();
|
|
854
855
|
return output || null;
|
|
855
856
|
} catch {
|
|
@@ -866,7 +867,8 @@ class SkillService {
|
|
|
866
867
|
input: `protocol=https\nhost=${hostname}\n\n`,
|
|
867
868
|
encoding: 'utf-8',
|
|
868
869
|
timeout: 3000,
|
|
869
|
-
stdio: ['pipe', 'pipe', 'ignore']
|
|
870
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
871
|
+
windowsHide: true
|
|
870
872
|
});
|
|
871
873
|
const passwordLine = output
|
|
872
874
|
.split(/\r?\n/)
|
package/src/utils/port-helper.js
CHANGED
|
@@ -138,7 +138,7 @@ function findProcessByPort(port) {
|
|
|
138
138
|
if (isWindows) {
|
|
139
139
|
try {
|
|
140
140
|
// Windows: 直接解析 netstat 输出,避免依赖 findstr/lsof
|
|
141
|
-
const result = execSync('netstat -ano', { encoding: 'utf-8' });
|
|
141
|
+
const result = execSync('netstat -ano', { encoding: 'utf-8', windowsHide: true });
|
|
142
142
|
return parsePidsFromNetstatOutput(result, port);
|
|
143
143
|
} catch (e) {
|
|
144
144
|
if (isMissingCommandError(e)) {
|
|
@@ -151,13 +151,13 @@ function findProcessByPort(port) {
|
|
|
151
151
|
let lsofMissing = false;
|
|
152
152
|
try {
|
|
153
153
|
// macOS/Linux 使用 lsof
|
|
154
|
-
const result = execSync(`lsof -ti :${port}`, { encoding: 'utf-8' }).trim();
|
|
154
|
+
const result = execSync(`lsof -ti :${port}`, { encoding: 'utf-8', windowsHide: true }).trim();
|
|
155
155
|
return result.split('\n').filter(pid => pid);
|
|
156
156
|
} catch (err) {
|
|
157
157
|
lsofMissing = isMissingCommandError(err);
|
|
158
158
|
// 如果 lsof 失败,尝试使用 fuser(某些 Linux 系统)
|
|
159
159
|
try {
|
|
160
|
-
const result = execSync(`fuser ${port}/tcp 2>/dev/null`, { encoding: 'utf-8' }).trim();
|
|
160
|
+
const result = execSync(`fuser ${port}/tcp 2>/dev/null`, { encoding: 'utf-8', windowsHide: true }).trim();
|
|
161
161
|
return result.split(/\s+/).filter(pid => pid);
|
|
162
162
|
} catch (e) {
|
|
163
163
|
if (lsofMissing && isMissingCommandError(e)) {
|
|
@@ -183,9 +183,9 @@ function killProcessByPort(port) {
|
|
|
183
183
|
pids.forEach(pid => {
|
|
184
184
|
try {
|
|
185
185
|
if (isWindows) {
|
|
186
|
-
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
|
|
186
|
+
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore', windowsHide: true });
|
|
187
187
|
} else {
|
|
188
|
-
execSync(`kill -9 ${pid}`, { stdio: 'ignore' });
|
|
188
|
+
execSync(`kill -9 ${pid}`, { stdio: 'ignore', windowsHide: true });
|
|
189
189
|
}
|
|
190
190
|
killedAny = true;
|
|
191
191
|
} catch (err) {
|