coding-tool-x 3.8.7 → 3.8.9
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 +2 -2
- package/dist/web/assets/{AgentsDrawer-C7ePByZx.js → AgentsDrawer-Cj5CJ4d9.js} +1 -1
- package/dist/web/assets/Analytics-1doS-8uC.js +1 -0
- package/dist/web/assets/AssetPathField-C9TOPl7o.js +1 -0
- package/dist/web/assets/{ChatHistoryDrawer-Ccl4rhdz.js → ChatHistoryDrawer-BTmQ1aQr.js} +1 -1
- package/dist/web/assets/{CollapseItem-MxG_UD4o.js → CollapseItem-CwqJk3bE.js} +1 -1
- package/dist/web/assets/{CommandsDrawer-DWbaxHRZ.js → CommandsDrawer-CaZWDI_G.js} +1 -1
- package/dist/web/assets/ConfigExportDrawer-C3vSZEsS.js +1 -0
- package/dist/web/assets/{ConfigTemplates-AiRPv2cd.js → ConfigTemplates-gyERg9iC.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesDrawer-CKMFsfEF.js → ConfigTemplatesDrawer-B17RqFi4.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesPanel-Cvbnkvpt.js → ConfigTemplatesPanel-DKIC_FqG.js} +1 -1
- package/dist/web/assets/{Divider-D28g1gwj.js → Divider-DsdbkHrG.js} +1 -1
- package/dist/web/assets/{FavoritesDrawer-DA-dR_vP.js → FavoritesDrawer-DaziQmBp.js} +1 -1
- package/dist/web/assets/Forward-DLCL7aMY.js +1 -0
- package/dist/web/assets/{GatewayConvertDrawer-402_raGb.js → GatewayConvertDrawer-OMgWUUSc.js} +1 -1
- package/dist/web/assets/Home-DOSUTfrE.js +1 -0
- package/dist/web/assets/{MarkdownEditor-BvkDECwH.js → MarkdownEditor-9TTwTS-U.js} +1 -1
- package/dist/web/assets/McpDrawer-I8rl8lGH.js +1 -0
- package/dist/web/assets/{McpFormDrawer-DPMnyt5L.js → McpFormDrawer-DEP3C7ut.js} +1 -1
- package/dist/web/assets/{PluginManager-FKVeugr4.js → PluginManager-BuRcWuBj.js} +1 -1
- package/dist/web/assets/{PluginsDrawer-MvM1R55d.js → PluginsDrawer-_zZENaQn.js} +1 -1
- package/dist/web/assets/PluginsPanel-Bs5qZ-TK.js +1 -0
- package/dist/web/assets/PluginsPanel-CzGMK-Ap.css +1 -0
- package/dist/web/assets/ProjectList-BSxuL2td.js +1 -0
- package/dist/web/assets/{ProjectList-677RLZ8O.css → ProjectList-VbIYSHbH.css} +1 -1
- package/dist/web/assets/PromptsDrawer-CoZ-ZtsY.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-B_17aJBG.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-yrAD4p1A.css +1 -0
- package/dist/web/assets/SessionCard-BL_x0d3t.js +1 -0
- package/dist/web/assets/SessionList-DhRYZ6vl.js +1 -0
- package/dist/web/assets/SessionList-qG_XpPBJ.css +1 -0
- package/dist/web/assets/{SettingsDrawer-lMjttbkD.js → SettingsDrawer-fYb0DU7X.js} +1 -1
- package/dist/web/assets/SkillManager-S698Hns8.js +1 -0
- package/dist/web/assets/{SkillsDrawer-DF6JsSyj.js → SkillsDrawer-Ms4lPCow.js} +1 -1
- package/dist/web/assets/{SkillsPanel-B3Nm5j32.css → SkillsPanel-C4ke56nF.css} +1 -1
- package/dist/web/assets/SkillsPanel-rqnqAOGL.js +1 -0
- package/dist/web/assets/{SpeedTestDrawer-BaZkZqPP.js → SpeedTestDrawer-Bbn38SfV.js} +1 -1
- package/dist/web/assets/Upload-CdyVHxCI.js +1 -0
- package/dist/web/assets/{WorkspaceDrawer-BFSwZ5yU.js → WorkspaceDrawer-L7HJRc6u.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-BHL1uYct.js → WorkspaceManager-DsoNg2z8.js} +1 -1
- package/dist/web/assets/{WorkspacePanel-B5PooBtg.js → WorkspacePanel-Be8PVZah.js} +1 -1
- package/dist/web/assets/{headers-cf-QapUq.js → headers-B5JWGn3v.js} +1 -1
- package/dist/web/assets/{icons-B2Vg-IOC.js → icons-DmFe4b5j.js} +1 -1
- package/dist/web/assets/{index-BohDDAy_.css → index-3Lp6z8TQ.css} +1 -1
- package/dist/web/assets/index-BKlAWTH-.js +2 -0
- package/dist/web/assets/{light-Cp0t7v32.js → light-0eavxNvt.js} +1 -1
- package/dist/web/assets/platforms-BmzzwnSw.js +1 -0
- package/dist/web/assets/platforms-DWXgmy2w.js +1 -0
- package/dist/web/assets/session-visibility-BAmxh3cK.js +1 -0
- package/dist/web/assets/sessions-CC0j-KH_.js +1 -0
- package/dist/web/assets/sessions-D4yQtfKb.js +1 -0
- package/dist/web/assets/use-message-DqCwefG6.js +1 -0
- package/dist/web/assets/{useResponsiveDrawer-D-DDpbnI.js → useResponsiveDrawer-V_onl9NV.js} +1 -1
- package/dist/web/index.html +7 -7
- package/package.json +1 -1
- package/src/commands/daemon.js +27 -4
- package/src/commands/search.js +17 -2
- package/src/config/paths.js +2 -0
- package/src/platforms/channel-auth-registry.js +36 -6
- package/src/platforms/channel-auth-service.js +12 -2
- package/src/platforms/driver-registry.js +9 -1
- package/src/platforms/drivers/claude/session-history-adapter.js +72 -1
- package/src/platforms/drivers/claude/sessions-implementation.js +49 -7
- package/src/platforms/drivers/claude/sessions.js +30 -6
- package/src/platforms/drivers/codex/parser.js +57 -1
- package/src/platforms/drivers/codex/session-history-adapter.js +8 -2
- package/src/platforms/drivers/codex/sessions-implementation.js +25 -3
- package/src/platforms/drivers/codex/sessions.js +18 -1
- package/src/platforms/drivers/dsh/api-operations.js +396 -0
- package/src/platforms/drivers/dsh/channels-implementation.js +458 -0
- package/src/platforms/drivers/dsh/channels.js +191 -0
- package/src/platforms/drivers/dsh/common.js +571 -0
- package/src/platforms/drivers/dsh/native-config.js +93 -0
- package/src/platforms/drivers/dsh/native-logs.js +116 -0
- package/src/platforms/drivers/dsh/plugin-manager.js +117 -0
- package/src/platforms/drivers/dsh/projects.js +23 -0
- package/src/platforms/drivers/dsh/proxy-implementation.js +260 -0
- package/src/platforms/drivers/dsh/proxy.js +23 -0
- package/src/platforms/drivers/dsh/resources.js +346 -0
- package/src/platforms/drivers/dsh/sessions.js +587 -0
- package/src/platforms/drivers/dsh/statistics-implementation.js +238 -0
- package/src/platforms/drivers/dsh/statistics.js +24 -0
- package/src/platforms/drivers/gemini/session-history-adapter.js +49 -1
- package/src/platforms/drivers/gemini/sessions-implementation.js +46 -1
- package/src/platforms/drivers/gemini/sessions.js +2 -1
- package/src/platforms/drivers/omp/prompts.js +58 -2
- package/src/platforms/drivers/omp/proxy-implementation.js +25 -1
- package/src/platforms/drivers/omp/session-history-adapter.js +42 -1
- package/src/platforms/drivers/omp/sessions-implementation.js +51 -3
- package/src/platforms/drivers/omp/sessions.js +33 -2
- package/src/platforms/drivers/opencode/channels-implementation.js +25 -22
- package/src/platforms/drivers/opencode/channels.js +6 -1
- package/src/platforms/drivers/opencode/sessions-implementation.js +89 -9
- package/src/platforms/drivers/opencode/sessions.js +37 -1
- package/src/platforms/manifest-schema.js +43 -3
- package/src/platforms/manifests/dsh.json +542 -0
- package/src/platforms/manifests/omp.json +2 -1
- package/src/platforms/manifests/opencode.json +28 -0
- package/src/platforms/native-oauth-adapters.js +318 -18
- package/src/platforms/oauth-credentials-service.js +344 -13
- package/src/platforms/path-resolver.js +2 -0
- package/src/platforms/platform-path-context.js +12 -2
- package/src/platforms/registry.js +16 -1
- package/src/platforms/runtime.js +5 -0
- package/src/platforms/session-history-adapters.js +1 -0
- package/src/server/api/platform-api-config.js +23 -2
- package/src/server/api/platform-route-factory.js +9 -2
- package/src/server/api/platforms.js +19 -1
- package/src/server/api/plugins.js +108 -13
- package/src/server/index.js +35 -1
- package/src/server/services/config-registry-service.js +10 -1
- package/src/server/services/config-templates-service.js +1 -0
- package/src/server/services/plugins-service.js +474 -103
- package/src/server/services/session-history-index.js +900 -140
- package/src/server/services/session-history-worker.js +3 -1
- package/src/server/services/skill-service.js +214 -7
- package/src/server/services/usage-log-utils.js +1 -0
- package/src/server/services/workspace-service.js +39 -10
- package/src/shared/driver-factories/api.js +22 -6
- package/src/shared/driver-factories/projects.js +8 -0
- package/dist/web/assets/Analytics-BVG6ehFv.js +0 -1
- package/dist/web/assets/AssetPathField-BZI9NvEr.js +0 -1
- package/dist/web/assets/ConfigExportDrawer-DrawKwxE.js +0 -1
- package/dist/web/assets/Home-CTF8ZWKW.js +0 -1
- package/dist/web/assets/McpDrawer-DZGHkuDm.js +0 -1
- package/dist/web/assets/PluginsPanel-CKuBs38B.js +0 -1
- package/dist/web/assets/PluginsPanel-D6ZqITcR.css +0 -1
- package/dist/web/assets/ProjectList-tsjwjXV1.js +0 -1
- package/dist/web/assets/PromptsDrawer-B-X9ci8n.js +0 -1
- package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +0 -1
- package/dist/web/assets/RecentSessionsDrawer-wLktMF_c.js +0 -1
- package/dist/web/assets/SessionCard-DkJPy0TY.js +0 -1
- package/dist/web/assets/SessionList-BpYRz1cX.css +0 -1
- package/dist/web/assets/SessionList-Gzhh4FOZ.js +0 -1
- package/dist/web/assets/SkillManager-CzFxWxx-.js +0 -1
- package/dist/web/assets/SkillsPanel--lLEts9y.js +0 -1
- package/dist/web/assets/Upload-BEQ-3Kqq.js +0 -1
- package/dist/web/assets/index-BFInlZGI.js +0 -2
- package/dist/web/assets/platforms-CBkB45JP.js +0 -1
- package/dist/web/assets/platforms-FTpjxAT6.js +0 -1
- package/dist/web/assets/sessions-Cmy31slD.js +0 -1
- package/dist/web/assets/sessions-yC_oVVh8.js +0 -1
- package/dist/web/assets/use-message-DoLM8C1C.js +0 -1
|
@@ -2,7 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const crypto = require('crypto');
|
|
5
|
-
const { spawnSync } = require('child_process');
|
|
5
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
6
6
|
const toml = require('toml');
|
|
7
7
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
8
8
|
const pathsModule = require('../config/paths');
|
|
@@ -17,7 +17,7 @@ const { syncCodexUserEnvironment } = require('./drivers/codex/env-manager');
|
|
|
17
17
|
const nativeKeychain = require('../server/services/native-keychain');
|
|
18
18
|
const { maskToken, decodeJwtPayload, removeFileIfExists, sha256 } = require('../server/services/oauth-utils');
|
|
19
19
|
|
|
20
|
-
const SUPPORTED_TOOLS = ['claude', 'codex', 'gemini', 'omp'];
|
|
20
|
+
const SUPPORTED_TOOLS = ['claude', 'codex', 'gemini', 'omp', 'opencode'];
|
|
21
21
|
const GEMINI_MAIN_ACCOUNT_KEY = 'main-account';
|
|
22
22
|
const GEMINI_KEYCHAIN_SERVICE = 'gemini-cli-oauth';
|
|
23
23
|
const CODEX_KEYCHAIN_SERVICE = 'Codex Auth';
|
|
@@ -295,6 +295,43 @@ function readJsonFileFromString(raw) {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
function firstString(...values) {
|
|
299
|
+
return values.find(value => typeof value === 'string' && value.trim())?.trim() || '';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function extractTokenMetadata(...tokens) {
|
|
303
|
+
const payloads = tokens
|
|
304
|
+
.filter(Boolean)
|
|
305
|
+
.map(token => decodeJwtPayload(token))
|
|
306
|
+
.filter(payload => payload && typeof payload === 'object');
|
|
307
|
+
const payload = payloads.find(item => (
|
|
308
|
+
item.chatgpt_account_id
|
|
309
|
+
|| item['https://api.openai.com/auth.chatgpt_account_id']
|
|
310
|
+
|| item['https://api.openai.com/auth']?.chatgpt_account_id
|
|
311
|
+
|| item.email
|
|
312
|
+
)) || payloads[0] || {};
|
|
313
|
+
const auth = payload['https://api.openai.com/auth'] || {};
|
|
314
|
+
const profile = payload['https://api.openai.com/profile'] || {};
|
|
315
|
+
const accountId = firstString(
|
|
316
|
+
payload.chatgpt_account_id,
|
|
317
|
+
payload['https://api.openai.com/auth.chatgpt_account_id'],
|
|
318
|
+
auth.chatgpt_account_id,
|
|
319
|
+
payload.account_id,
|
|
320
|
+
payload.organizations?.[0]?.id
|
|
321
|
+
);
|
|
322
|
+
const accountEmail = firstString(
|
|
323
|
+
payload.email,
|
|
324
|
+
profile.email,
|
|
325
|
+
auth.email
|
|
326
|
+
);
|
|
327
|
+
const exp = Number(payload.exp || 0);
|
|
328
|
+
return {
|
|
329
|
+
accountId,
|
|
330
|
+
accountEmail,
|
|
331
|
+
expiresAt: Number.isFinite(exp) && exp > 0 ? exp * 1000 : null
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
298
335
|
function getCodexKeychainAccount() {
|
|
299
336
|
const codexHome = NATIVE_PATHS.codex.dir;
|
|
300
337
|
const resolvedPath = fs.existsSync(codexHome)
|
|
@@ -320,7 +357,13 @@ function parseCodexAuthPayload(raw) {
|
|
|
320
357
|
|
|
321
358
|
const idTokenPayload = decodeJwtPayload(tokens.id_token);
|
|
322
359
|
const accessTokenPayload = decodeJwtPayload(tokens.access_token);
|
|
323
|
-
const
|
|
360
|
+
const tokenMetadata = extractTokenMetadata(tokens.id_token, tokens.access_token);
|
|
361
|
+
// The id token can expire before the access token. Use the newest usable
|
|
362
|
+
// expiry so a still-valid access token is not unnecessarily refreshed.
|
|
363
|
+
const exp = Math.max(
|
|
364
|
+
Number(idTokenPayload?.exp || 0),
|
|
365
|
+
Number(accessTokenPayload?.exp || 0)
|
|
366
|
+
) || null;
|
|
324
367
|
const accountId = tokens.account_id
|
|
325
368
|
|| idTokenPayload?.chatgpt_account_id
|
|
326
369
|
|| accessTokenPayload?.chatgpt_account_id
|
|
@@ -337,8 +380,8 @@ function parseCodexAuthPayload(raw) {
|
|
|
337
380
|
refreshToken: String(tokens.refresh_token || '').trim() || '',
|
|
338
381
|
idToken: String(tokens.id_token || '').trim() || '',
|
|
339
382
|
accountId: String(accountId).trim(),
|
|
340
|
-
accountEmail:
|
|
341
|
-
expiresAt: exp ? exp * 1000 :
|
|
383
|
+
accountEmail: tokenMetadata.accountEmail,
|
|
384
|
+
expiresAt: exp ? exp * 1000 : tokenMetadata.expiresAt,
|
|
342
385
|
lastRefresh: auth.last_refresh || null,
|
|
343
386
|
primaryToken: String(tokens.access_token || '').trim()
|
|
344
387
|
};
|
|
@@ -362,7 +405,21 @@ function readCodexFileAuth() {
|
|
|
362
405
|
}
|
|
363
406
|
|
|
364
407
|
function readCodexNativeOAuth() {
|
|
365
|
-
|
|
408
|
+
const keychain = readCodexKeychainAuth();
|
|
409
|
+
const file = readCodexFileAuth();
|
|
410
|
+
if (!keychain) return file;
|
|
411
|
+
if (!file) return keychain;
|
|
412
|
+
|
|
413
|
+
const recency = (credential) => {
|
|
414
|
+
const lastRefresh = Date.parse(credential.lastRefresh || '') || 0;
|
|
415
|
+
const expiresAt = Number(credential.expiresAt || 0);
|
|
416
|
+
return lastRefresh || (Number.isFinite(expiresAt) ? expiresAt : 0);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// Codex stores the same auth payload in the keychain and auth.json. When
|
|
420
|
+
// they diverge, prefer the copy refreshed most recently instead of blindly
|
|
421
|
+
// selecting a stale keychain entry.
|
|
422
|
+
return recency(file) > recency(keychain) ? file : keychain;
|
|
366
423
|
}
|
|
367
424
|
|
|
368
425
|
function clearCodexOAuth() {
|
|
@@ -447,6 +504,45 @@ function applyCodexOAuth(credential) {
|
|
|
447
504
|
return { storage: wroteKeychain ? 'auth-file+keychain' : 'auth-file' };
|
|
448
505
|
}
|
|
449
506
|
|
|
507
|
+
// Refreshing an OAuth token must not clear the user's configured channels.
|
|
508
|
+
// `applyCodexOAuth` is intentionally broader because it switches the active
|
|
509
|
+
// auth mode; this helper only replaces the rotating token pair in place.
|
|
510
|
+
function updateCodexOAuthTokens(credential = {}) {
|
|
511
|
+
const current = readCodexNativeOAuth();
|
|
512
|
+
const currentAuth = (() => {
|
|
513
|
+
try {
|
|
514
|
+
return codexSettingsManager.readAuth() || {};
|
|
515
|
+
} catch {
|
|
516
|
+
return {};
|
|
517
|
+
}
|
|
518
|
+
})();
|
|
519
|
+
const currentTokens = currentAuth.tokens && typeof currentAuth.tokens === 'object'
|
|
520
|
+
? currentAuth.tokens
|
|
521
|
+
: {};
|
|
522
|
+
const authPayload = {
|
|
523
|
+
...currentAuth,
|
|
524
|
+
auth_mode: credential.authMode || current?.authMode || currentAuth.auth_mode || 'chatgpt',
|
|
525
|
+
tokens: {
|
|
526
|
+
...currentTokens,
|
|
527
|
+
access_token: credential.accessToken || current?.accessToken || currentTokens.access_token || '',
|
|
528
|
+
refresh_token: credential.refreshToken || current?.refreshToken || currentTokens.refresh_token || '',
|
|
529
|
+
id_token: credential.idToken || current?.idToken || currentTokens.id_token || '',
|
|
530
|
+
account_id: credential.accountId || current?.accountId || currentTokens.account_id || ''
|
|
531
|
+
},
|
|
532
|
+
last_refresh: credential.lastRefresh || new Date().toISOString()
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
if (!authPayload.tokens.access_token) {
|
|
536
|
+
throw new Error('Codex OAuth access token is missing');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
writeJsonFile(NATIVE_PATHS.codex.auth, authPayload);
|
|
540
|
+
const wroteKeychain = nativeKeychain.isSupported()
|
|
541
|
+
? nativeKeychain.setPassword(CODEX_KEYCHAIN_SERVICE, getCodexKeychainAccount(), JSON.stringify(authPayload))
|
|
542
|
+
: false;
|
|
543
|
+
return { storage: wroteKeychain ? 'auth-file+keychain' : 'auth-file' };
|
|
544
|
+
}
|
|
545
|
+
|
|
450
546
|
function inspectCodexState() {
|
|
451
547
|
const { getCodexProxyStatus } = require('./drivers/codex/proxy-implementation');
|
|
452
548
|
const proxyStatus = getCodexProxyStatus();
|
|
@@ -749,23 +845,184 @@ function getOmpAuthSnapshot(options = {}) {
|
|
|
749
845
|
}
|
|
750
846
|
|
|
751
847
|
function readAllOmpNativeOAuth() {
|
|
848
|
+
const storedOAuth = readOmpOAuthCredentialsFromDb();
|
|
752
849
|
const snapshot = getOmpAuthSnapshot({ forceRefresh: true });
|
|
753
|
-
if (!snapshot?.available || !Array.isArray(snapshot.providers))
|
|
754
|
-
return [];
|
|
755
|
-
}
|
|
850
|
+
if (!snapshot?.available || !Array.isArray(snapshot.providers)) return storedOAuth;
|
|
756
851
|
|
|
757
|
-
|
|
852
|
+
const snapshotCredentials = snapshot.providers
|
|
758
853
|
.filter(provider => provider?.loggedIn || Number(provider?.accountCount || 0) > 0)
|
|
759
854
|
.flatMap((provider) => {
|
|
760
855
|
const accounts = Array.isArray(provider.accounts) ? provider.accounts : [];
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
accountId
|
|
764
|
-
accountEmail
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
856
|
+
const providerCredentials = storedOAuth.filter(item => item.providerId === provider.id);
|
|
857
|
+
return accounts.map((account, index) => {
|
|
858
|
+
const accountId = String(account.index || account.id || account.accountId || '').trim();
|
|
859
|
+
const accountEmail = String(account.identity || account.email || account.accountEmail || '').trim();
|
|
860
|
+
const stored = providerCredentials.find(item => (
|
|
861
|
+
(item.accountId && item.accountId === accountId)
|
|
862
|
+
|| (item.accountEmail && item.accountEmail === accountEmail)
|
|
863
|
+
)) || providerCredentials[index] || null;
|
|
864
|
+
return {
|
|
865
|
+
providerId: provider.id,
|
|
866
|
+
accountId: stored?.accountId || accountId,
|
|
867
|
+
accountEmail: stored?.accountEmail || accountEmail,
|
|
868
|
+
identityKey: stored?.identityKey || '',
|
|
869
|
+
accessToken: stored?.accessToken || '',
|
|
870
|
+
refreshToken: stored?.refreshToken || '',
|
|
871
|
+
expiresAt: stored?.expiresAt || null,
|
|
872
|
+
storage: stored?.storage || 'auth-broker',
|
|
873
|
+
primaryToken: stored?.primaryToken || ''
|
|
874
|
+
};
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
return snapshotCredentials.length > 0 ? snapshotCredentials : storedOAuth;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function getOmpAgentDatabasePath() {
|
|
881
|
+
const agentDir = NATIVE_PATHS.omp?.dir;
|
|
882
|
+
return agentDir ? path.join(agentDir, 'agent.db') : '';
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function readSqliteRows(databasePath, sql) {
|
|
886
|
+
if (!databasePath || !fs.existsSync(databasePath)) return [];
|
|
887
|
+
try {
|
|
888
|
+
const output = execFileSync('sqlite3', ['-json', databasePath, sql], {
|
|
889
|
+
encoding: 'utf8',
|
|
890
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
891
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
892
|
+
windowsHide: true
|
|
893
|
+
}).trim();
|
|
894
|
+
if (!output) return [];
|
|
895
|
+
const parsed = JSON.parse(output);
|
|
896
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
897
|
+
} catch {
|
|
898
|
+
return [];
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function parseOAuthCredentialData(value) {
|
|
903
|
+
if (!value) return null;
|
|
904
|
+
try {
|
|
905
|
+
const parsed = typeof value === 'string' ? JSON.parse(value) : value;
|
|
906
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
907
|
+
} catch {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function normalizeCredentialExpiry(value) {
|
|
913
|
+
const numeric = Number(value);
|
|
914
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return null;
|
|
915
|
+
return numeric < 1e12 ? numeric * 1000 : numeric;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function readOmpOAuthCredentialsFromDb() {
|
|
919
|
+
const rows = readSqliteRows(
|
|
920
|
+
getOmpAgentDatabasePath(),
|
|
921
|
+
"select provider, credential_type, data, identity_key, disabled_cause from auth_credentials where credential_type = 'oauth' order by updated_at desc"
|
|
922
|
+
);
|
|
923
|
+
return rows.map(row => {
|
|
924
|
+
if (row?.disabled_cause) return null;
|
|
925
|
+
const data = parseOAuthCredentialData(row?.data) || {};
|
|
926
|
+
const accessToken = firstString(data.access, data.accessToken, data.access_token, data.token);
|
|
927
|
+
if (!accessToken) return null;
|
|
928
|
+
const tokenMetadata = extractTokenMetadata(accessToken, data.id_token, data.idToken);
|
|
929
|
+
const identityKey = firstString(data.identityKey, data.identity_key, row?.identity_key);
|
|
930
|
+
const identityAccountId = identityKey.match(/(?:^|[|:])org:([^|]+)/)?.[1] || '';
|
|
931
|
+
return {
|
|
932
|
+
providerId: firstString(row?.provider),
|
|
933
|
+
accountId: firstString(data.accountId, data.account_id, data.orgId, data.org_id, tokenMetadata.accountId, identityAccountId),
|
|
934
|
+
accountEmail: firstString(data.accountEmail, data.account_email, data.email, tokenMetadata.accountEmail),
|
|
935
|
+
identityKey,
|
|
936
|
+
accessToken,
|
|
937
|
+
refreshToken: firstString(data.refresh, data.refreshToken, data.refresh_token),
|
|
938
|
+
expiresAt: normalizeCredentialExpiry(data.expires || data.expiresAt || data.expires_at) || tokenMetadata.expiresAt,
|
|
939
|
+
storage: 'auth-broker',
|
|
940
|
+
primaryToken: accessToken
|
|
941
|
+
};
|
|
942
|
+
}).filter(Boolean);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function normalizeOpenCodeProviderId(value = '') {
|
|
946
|
+
const text = String(value || '').trim().toLowerCase();
|
|
947
|
+
if (text.includes('openai') || text.includes('codex')) return 'openai-codex';
|
|
948
|
+
try {
|
|
949
|
+
return new URL(text).hostname.replace(/^www\./, '') || text;
|
|
950
|
+
} catch {
|
|
951
|
+
return text || 'opencode';
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function parseOpenCodeCredential(row = {}, storage = 'opencode') {
|
|
956
|
+
const accessToken = firstString(row.accessToken, row.access_token, row.access, row.token);
|
|
957
|
+
if (!accessToken) return null;
|
|
958
|
+
const tokenMetadata = extractTokenMetadata(accessToken, row.idToken, row.id_token);
|
|
959
|
+
const providerId = normalizeOpenCodeProviderId(row.providerId || row.provider || row.url);
|
|
960
|
+
return {
|
|
961
|
+
providerId,
|
|
962
|
+
accountId: firstString(row.accountId, row.account_id, tokenMetadata.accountId),
|
|
963
|
+
accountEmail: firstString(row.accountEmail, row.account_email, row.email, tokenMetadata.accountEmail),
|
|
964
|
+
identityKey: firstString(row.identityKey, row.identity_key),
|
|
965
|
+
accessToken,
|
|
966
|
+
idToken: firstString(row.idToken, row.id_token),
|
|
967
|
+
refreshToken: firstString(row.refreshToken, row.refresh_token, row.refresh),
|
|
968
|
+
expiresAt: normalizeCredentialExpiry(row.expiresAt || row.expires_at || row.token_expiry) || tokenMetadata.expiresAt,
|
|
969
|
+
storage,
|
|
970
|
+
primaryToken: accessToken
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function readOpenCodeAuthFile() {
|
|
975
|
+
const filePath = NATIVE_PATHS.opencode?.auth;
|
|
976
|
+
const payload = filePath ? readJsonFile(filePath, null) : null;
|
|
977
|
+
if (!payload || typeof payload !== 'object') return [];
|
|
978
|
+
const entries = payload.access_token || payload.accessToken || payload.access
|
|
979
|
+
? [{ ...payload }]
|
|
980
|
+
: Object.entries(payload).map(([providerId, value]) => ({
|
|
981
|
+
...(value && typeof value === 'object' ? value : {}),
|
|
982
|
+
providerId
|
|
983
|
+
}));
|
|
984
|
+
return entries.map(entry => parseOpenCodeCredential(entry, 'opencode-auth-file')).filter(Boolean);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function readOpenCodeDatabaseCredentials() {
|
|
988
|
+
const dataDir = NATIVE_PATHS.opencode?.data;
|
|
989
|
+
if (!dataDir || !fs.existsSync(dataDir)) return [];
|
|
990
|
+
const databases = fs.readdirSync(dataDir)
|
|
991
|
+
.filter(name => /^opencode-.*\.db$/i.test(name) || /^opencode\.db$/i.test(name))
|
|
992
|
+
.map(name => path.join(dataDir, name))
|
|
993
|
+
.filter(filePath => {
|
|
994
|
+
try { return fs.statSync(filePath).isFile(); } catch { return false; }
|
|
995
|
+
})
|
|
996
|
+
.sort((left, right) => {
|
|
997
|
+
try { return fs.statSync(right).mtimeMs - fs.statSync(left).mtimeMs; } catch { return 0; }
|
|
768
998
|
});
|
|
999
|
+
const rows = [];
|
|
1000
|
+
for (const databasePath of databases) {
|
|
1001
|
+
rows.push(...readSqliteRows(
|
|
1002
|
+
databasePath,
|
|
1003
|
+
'select email, url, access_token, refresh_token, token_expiry from account'
|
|
1004
|
+
));
|
|
1005
|
+
rows.push(...readSqliteRows(
|
|
1006
|
+
databasePath,
|
|
1007
|
+
'select email, url, access_token, refresh_token, token_expiry from control_account'
|
|
1008
|
+
));
|
|
1009
|
+
}
|
|
1010
|
+
return rows.map(row => parseOpenCodeCredential(row, 'opencode-sqlite')).filter(Boolean);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function readAllOpenCodeNativeOAuth() {
|
|
1014
|
+
const entries = [...readOpenCodeAuthFile(), ...readOpenCodeDatabaseCredentials()];
|
|
1015
|
+
const seen = new Set();
|
|
1016
|
+
return entries.filter(entry => {
|
|
1017
|
+
const key = `${entry.providerId}:${entry.accountId || entry.accountEmail || entry.primaryToken}`;
|
|
1018
|
+
if (seen.has(key)) return false;
|
|
1019
|
+
seen.add(key);
|
|
1020
|
+
return true;
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function readOpenCodeNativeOAuth() {
|
|
1025
|
+
return readAllOpenCodeNativeOAuth()[0] || null;
|
|
769
1026
|
}
|
|
770
1027
|
|
|
771
1028
|
function readOmpNativeOAuth() {
|
|
@@ -805,17 +1062,32 @@ function buildOmpImportPayload(credential = {}) {
|
|
|
805
1062
|
}
|
|
806
1063
|
|
|
807
1064
|
const access = String(credential.accessToken || credential.primaryToken || '').trim();
|
|
1065
|
+
const refresh = String(credential.refreshToken || '').trim();
|
|
1066
|
+
const expiresAt = Number(credential.expiresAt || 0);
|
|
1067
|
+
const expired = Number.isFinite(expiresAt) && expiresAt > 0
|
|
1068
|
+
? new Date(expiresAt).toISOString()
|
|
1069
|
+
: undefined;
|
|
808
1070
|
if (!access) {
|
|
809
1071
|
return null;
|
|
810
1072
|
}
|
|
811
1073
|
|
|
812
1074
|
return {
|
|
813
1075
|
provider: credential.providerId,
|
|
1076
|
+
type: credential.credentialType || 'oauth',
|
|
814
1077
|
credential_type: credential.credentialType || 'oauth',
|
|
1078
|
+
// OMP keeps a logged-out credential row in the broker database. Explicitly
|
|
1079
|
+
// marking the imported row active is required to revive a token after a
|
|
1080
|
+
// restart or token rotation.
|
|
1081
|
+
disabled: false,
|
|
815
1082
|
identity_key: credential.identityKey || credential.accountId || credential.accountEmail || undefined,
|
|
1083
|
+
access_token: access,
|
|
1084
|
+
refresh_token: refresh,
|
|
1085
|
+
expired,
|
|
1086
|
+
account_id: credential.accountId || undefined,
|
|
1087
|
+
email: credential.accountEmail || undefined,
|
|
816
1088
|
data: {
|
|
817
1089
|
access,
|
|
818
|
-
refresh
|
|
1090
|
+
refresh,
|
|
819
1091
|
expires: credential.expiresAt || null,
|
|
820
1092
|
accountId: credential.accountId || undefined,
|
|
821
1093
|
accountEmail: credential.accountEmail || undefined
|
|
@@ -853,6 +1125,9 @@ function applyOmpOAuth(credential = {}) {
|
|
|
853
1125
|
const detail = result.stderr.trim() || result.stdout.trim() || result.error?.message || `exit code ${result.status}`;
|
|
854
1126
|
throw new Error(`OMP auth-broker import failed: ${detail}`);
|
|
855
1127
|
}
|
|
1128
|
+
if (/no importable credentials|missing access_token|cannot parse expired|^skip\b/im.test(result.stdout)) {
|
|
1129
|
+
throw new Error(`OMP auth-broker import rejected the credential: ${result.stdout.trim()}`);
|
|
1130
|
+
}
|
|
856
1131
|
try {
|
|
857
1132
|
require('./drivers/omp/auth-providers').clearOmpAuthProviderCache();
|
|
858
1133
|
} catch {
|
|
@@ -894,6 +1169,24 @@ function inspectOmpState() {
|
|
|
894
1169
|
};
|
|
895
1170
|
}
|
|
896
1171
|
|
|
1172
|
+
function inspectOpenCodeState() {
|
|
1173
|
+
let proxyRunning = false;
|
|
1174
|
+
try {
|
|
1175
|
+
proxyRunning = Boolean(require('./drivers/opencode/proxy-implementation').getOpenCodeProxyStatus()?.running);
|
|
1176
|
+
} catch {
|
|
1177
|
+
proxyRunning = false;
|
|
1178
|
+
}
|
|
1179
|
+
const nativeCredentials = readAllOpenCodeNativeOAuth();
|
|
1180
|
+
return {
|
|
1181
|
+
tool: 'opencode',
|
|
1182
|
+
mode: proxyRunning ? 'proxy' : (nativeCredentials.length > 0 ? 'oauth' : 'idle'),
|
|
1183
|
+
proxyRunning,
|
|
1184
|
+
oauthPresent: nativeCredentials.length > 0,
|
|
1185
|
+
channelConfigured: false,
|
|
1186
|
+
nativeCredential: nativeCredentials[0] ? buildNativeSummary(nativeCredentials[0]) : null
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
897
1190
|
function inspectTool(tool) {
|
|
898
1191
|
switch (tool) {
|
|
899
1192
|
case 'claude':
|
|
@@ -904,6 +1197,8 @@ function inspectTool(tool) {
|
|
|
904
1197
|
return inspectGeminiState();
|
|
905
1198
|
case 'omp':
|
|
906
1199
|
return inspectOmpState();
|
|
1200
|
+
case 'opencode':
|
|
1201
|
+
return inspectOpenCodeState();
|
|
907
1202
|
default:
|
|
908
1203
|
throw new Error(`Unsupported OAuth tool: ${tool}`);
|
|
909
1204
|
}
|
|
@@ -919,6 +1214,8 @@ function readNativeOAuth(tool) {
|
|
|
919
1214
|
return readGeminiNativeOAuth();
|
|
920
1215
|
case 'omp':
|
|
921
1216
|
return readOmpNativeOAuth();
|
|
1217
|
+
case 'opencode':
|
|
1218
|
+
return readOpenCodeNativeOAuth();
|
|
922
1219
|
default:
|
|
923
1220
|
throw new Error(`Unsupported OAuth tool: ${tool}`);
|
|
924
1221
|
}
|
|
@@ -940,6 +1237,8 @@ function readAllNativeOAuth(tool) {
|
|
|
940
1237
|
}
|
|
941
1238
|
case 'omp':
|
|
942
1239
|
return readAllOmpNativeOAuth();
|
|
1240
|
+
case 'opencode':
|
|
1241
|
+
return readAllOpenCodeNativeOAuth();
|
|
943
1242
|
default:
|
|
944
1243
|
throw new Error(`Unsupported OAuth tool: ${tool}`);
|
|
945
1244
|
}
|
|
@@ -1008,6 +1307,7 @@ module.exports = {
|
|
|
1008
1307
|
clearClaudeChannelConfig,
|
|
1009
1308
|
clearGeminiChannelConfig,
|
|
1010
1309
|
clearCodexChannelConfig,
|
|
1310
|
+
updateCodexOAuthTokens,
|
|
1011
1311
|
clearNativeOAuth,
|
|
1012
1312
|
disableNativeOAuthCredential,
|
|
1013
1313
|
applyOAuthCredential
|