@ccpocket/bridge 1.63.5 → 1.63.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 +5 -1
- package/dist/bridge-port.d.ts +2 -0
- package/dist/bridge-port.js +20 -0
- package/dist/bridge-port.js.map +1 -0
- package/dist/cli-args.js +3 -0
- package/dist/cli-args.js.map +1 -1
- package/dist/cli.js +8 -2
- package/dist/cli.js.map +1 -1
- package/dist/codex-permissions.d.ts +10 -0
- package/dist/codex-permissions.js +62 -0
- package/dist/codex-permissions.js.map +1 -0
- package/dist/git-operations.d.ts +13 -0
- package/dist/git-operations.js +171 -3
- package/dist/git-operations.js.map +1 -1
- package/dist/index.js +22 -16
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js.map +1 -1
- package/dist/path-utils.d.ts +1 -0
- package/dist/path-utils.js +16 -0
- package/dist/path-utils.js.map +1 -1
- package/dist/sdk-process.d.ts +16 -1
- package/dist/sdk-process.js +126 -23
- package/dist/sdk-process.js.map +1 -1
- package/dist/server-listen.d.ts +2 -0
- package/dist/server-listen.js +23 -0
- package/dist/server-listen.js.map +1 -0
- package/dist/session.js +18 -4
- package/dist/session.js.map +1 -1
- package/dist/sessions-index.js +96 -11
- package/dist/sessions-index.js.map +1 -1
- package/dist/setup-launchd.js +3 -2
- package/dist/setup-launchd.js.map +1 -1
- package/dist/setup-systemd.js +3 -2
- package/dist/setup-systemd.js.map +1 -1
- package/dist/websocket.d.ts +32 -5
- package/dist/websocket.js +406 -145
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/websocket.js
CHANGED
|
@@ -14,7 +14,7 @@ import { getAllRecentSessions, getCodexSessionHistory, getSessionHistory, codexU
|
|
|
14
14
|
import { ArchiveStore } from "./archive-store.js";
|
|
15
15
|
import { WorktreeStore } from "./worktree-store.js";
|
|
16
16
|
import { listWorktrees, removeWorktree, worktreeExists, getMainBranch, } from "./worktree.js";
|
|
17
|
-
import { stageFiles, stageHunks, unstageFiles, unstageHunks, gitCommit, gitPush,
|
|
17
|
+
import { stageFiles, stageHunks, unstageFiles, unstageHunks, gitCommit, gitPush, listProjectFilesAndDirectoriesForClient, listBranches, createBranch, checkoutBranch, revertFiles, revertHunks, gitFetch, gitPull, gitRemoteStatus, gitStatus, } from "./git-operations.js";
|
|
18
18
|
import { generateCommitMessage } from "./git-assist.js";
|
|
19
19
|
import { listWindows, takeScreenshot } from "./screenshot.js";
|
|
20
20
|
import { DebugTraceStore } from "./debug-trace-store.js";
|
|
@@ -23,6 +23,7 @@ import { normalizePushLocale, t } from "./push-i18n.js";
|
|
|
23
23
|
import { fetchAllUsage } from "./usage.js";
|
|
24
24
|
import { getPackageVersion } from "./version.js";
|
|
25
25
|
import { isPathWithinAllowedDirectory, resolvePlatformPath, resolvePlatformPathFrom, } from "./path-utils.js";
|
|
26
|
+
import { deriveCodexPermissionsMode, normalizeCodexPermissionsMode, withDerivedCodexPermissionsMode, } from "./codex-permissions.js";
|
|
26
27
|
// ---- Available model lists (delivered to clients via session_list) ----
|
|
27
28
|
const FALLBACK_CLAUDE_MODELS = [
|
|
28
29
|
"claude-opus-4-7",
|
|
@@ -207,17 +208,6 @@ function normalizeCodexApprovalPolicy(value) {
|
|
|
207
208
|
return "on-request";
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
|
-
function normalizeCodexPermissionsMode(value) {
|
|
211
|
-
switch (value) {
|
|
212
|
-
case "default":
|
|
213
|
-
case "autoReview":
|
|
214
|
-
case "fullAccess":
|
|
215
|
-
case "custom":
|
|
216
|
-
return value;
|
|
217
|
-
default:
|
|
218
|
-
return undefined;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
211
|
function sanitizeCodexModel(model) {
|
|
222
212
|
if (typeof model !== "string")
|
|
223
213
|
return undefined;
|
|
@@ -253,21 +243,6 @@ function codexSettingsFromPermissionsMode(mode) {
|
|
|
253
243
|
return { codexPermissionsMode: mode };
|
|
254
244
|
}
|
|
255
245
|
}
|
|
256
|
-
function inferCodexPermissionsMode(params) {
|
|
257
|
-
const approvalPolicy = params.approvalPolicy;
|
|
258
|
-
const approvalsReviewer = params.approvalsReviewer ?? "user";
|
|
259
|
-
const sandboxMode = params.sandboxMode;
|
|
260
|
-
if (approvalPolicy === "never" && sandboxMode === "danger-full-access") {
|
|
261
|
-
return "fullAccess";
|
|
262
|
-
}
|
|
263
|
-
if (approvalPolicy === "on-request" && sandboxMode === "workspace-write") {
|
|
264
|
-
return approvalsReviewer === "auto_review" ||
|
|
265
|
-
approvalsReviewer === "guardian_subagent"
|
|
266
|
-
? "autoReview"
|
|
267
|
-
: "default";
|
|
268
|
-
}
|
|
269
|
-
return undefined;
|
|
270
|
-
}
|
|
271
246
|
function errorMessageOf(err) {
|
|
272
247
|
return err instanceof Error ? err.message : String(err);
|
|
273
248
|
}
|
|
@@ -344,6 +319,38 @@ function envFlagEnabled(name) {
|
|
|
344
319
|
const value = process.env[name]?.trim().toLowerCase();
|
|
345
320
|
return value === "1" || value === "true" || value === "yes" || value === "on";
|
|
346
321
|
}
|
|
322
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
323
|
+
function positiveEnvInt(name, fallback) {
|
|
324
|
+
const raw = process.env[name]?.trim();
|
|
325
|
+
if (!raw)
|
|
326
|
+
return fallback;
|
|
327
|
+
const value = Number(raw);
|
|
328
|
+
return Number.isSafeInteger(value) && value > 0 ? value : fallback;
|
|
329
|
+
}
|
|
330
|
+
function nonNegativeEnvInt(name, fallback) {
|
|
331
|
+
const raw = process.env[name]?.trim();
|
|
332
|
+
if (!raw)
|
|
333
|
+
return fallback;
|
|
334
|
+
const value = Number(raw);
|
|
335
|
+
return Number.isSafeInteger(value) &&
|
|
336
|
+
value >= 0 &&
|
|
337
|
+
value <= MAX_TIMER_DELAY_MS
|
|
338
|
+
? value
|
|
339
|
+
: fallback;
|
|
340
|
+
}
|
|
341
|
+
function normalizePositiveLimit(value, fallback) {
|
|
342
|
+
return value !== undefined && Number.isSafeInteger(value) && value > 0
|
|
343
|
+
? value
|
|
344
|
+
: fallback;
|
|
345
|
+
}
|
|
346
|
+
function normalizeNonNegativeLimit(value, fallback) {
|
|
347
|
+
return value !== undefined &&
|
|
348
|
+
Number.isSafeInteger(value) &&
|
|
349
|
+
value >= 0 &&
|
|
350
|
+
value <= MAX_TIMER_DELAY_MS
|
|
351
|
+
? value
|
|
352
|
+
: fallback;
|
|
353
|
+
}
|
|
347
354
|
function codexThreadToRecentSession(thread, indexed) {
|
|
348
355
|
// thread/list only exposes a single preview blob; prefer the real
|
|
349
356
|
// first/last/summary texts parsed from the rollout file so display-mode
|
|
@@ -391,6 +398,11 @@ function mergeRecentSessionPages(sessions) {
|
|
|
391
398
|
export class BridgeWebSocketServer {
|
|
392
399
|
static MAX_DEBUG_EVENTS = 800;
|
|
393
400
|
static MAX_HISTORY_SUMMARY_ITEMS = 300;
|
|
401
|
+
static CONNECT_METADATA_REFRESH_COOLDOWN_MS = 5 * 60 * 1000;
|
|
402
|
+
static DEFAULT_FILE_LIST_MAX_ENTRIES = 5000;
|
|
403
|
+
static DEFAULT_FILE_LIST_MAX_BYTES = 512 * 1024;
|
|
404
|
+
static DEFAULT_DELTA_BATCH_MS = 100;
|
|
405
|
+
static DEFAULT_DELTA_BATCH_MAX_CHARS = 4096;
|
|
394
406
|
wss;
|
|
395
407
|
sessionManager;
|
|
396
408
|
apiKey;
|
|
@@ -410,7 +422,8 @@ export class BridgeWebSocketServer {
|
|
|
410
422
|
archiveStore;
|
|
411
423
|
codexProfiles = [];
|
|
412
424
|
defaultCodexProfile;
|
|
413
|
-
|
|
425
|
+
codexMetadataRequest = null;
|
|
426
|
+
lastConnectMetadataRefreshAt = null;
|
|
414
427
|
claudeModels = FALLBACK_CLAUDE_MODELS;
|
|
415
428
|
claudeModelEfforts = {
|
|
416
429
|
...FALLBACK_CLAUDE_MODEL_EFFORTS,
|
|
@@ -421,16 +434,21 @@ export class BridgeWebSocketServer {
|
|
|
421
434
|
model,
|
|
422
435
|
FALLBACK_CODEX_REASONING_EFFORTS,
|
|
423
436
|
]));
|
|
424
|
-
codexModelsRequest = null;
|
|
425
437
|
/** FCM token → push notification locale */
|
|
426
438
|
tokenLocales = new Map();
|
|
427
439
|
tokenPrivacyMode = new Map();
|
|
428
440
|
failSetPermissionMode = envFlagEnabled("BRIDGE_FAIL_SET_PERMISSION_MODE");
|
|
429
441
|
failSetSandboxMode = envFlagEnabled("BRIDGE_FAIL_SET_SANDBOX_MODE");
|
|
442
|
+
fileListMaxEntries;
|
|
443
|
+
fileListMaxBytes;
|
|
444
|
+
deltaBatchMs;
|
|
445
|
+
deltaBatchMaxChars;
|
|
446
|
+
deltaBatches = new Map();
|
|
430
447
|
platform;
|
|
431
448
|
clientSupportedServerMessages = new WeakMap();
|
|
449
|
+
pendingClaudeResumeInputs = new WeakMap();
|
|
432
450
|
constructor(options) {
|
|
433
|
-
const { server, apiKey, allowedDirs, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, promptHistoryStore, platform, } = options;
|
|
451
|
+
const { server, apiKey, allowedDirs, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, promptHistoryStore, platform, fileListMaxEntries, fileListMaxBytes, deltaBatchMs, deltaBatchMaxChars, } = options;
|
|
434
452
|
this.apiKey = apiKey ?? null;
|
|
435
453
|
this.allowedDirs = allowedDirs ?? [];
|
|
436
454
|
this.imageStore = imageStore ?? null;
|
|
@@ -443,6 +461,10 @@ export class BridgeWebSocketServer {
|
|
|
443
461
|
this.promptHistoryBackup = promptHistoryBackup ?? null;
|
|
444
462
|
this.promptHistoryStore = promptHistoryStore ?? null;
|
|
445
463
|
this.platform = platform ?? process.platform;
|
|
464
|
+
this.fileListMaxEntries = normalizePositiveLimit(fileListMaxEntries, positiveEnvInt("BRIDGE_FILE_LIST_MAX_ENTRIES", BridgeWebSocketServer.DEFAULT_FILE_LIST_MAX_ENTRIES));
|
|
465
|
+
this.fileListMaxBytes = normalizePositiveLimit(fileListMaxBytes, positiveEnvInt("BRIDGE_FILE_LIST_MAX_BYTES", BridgeWebSocketServer.DEFAULT_FILE_LIST_MAX_BYTES));
|
|
466
|
+
this.deltaBatchMs = normalizeNonNegativeLimit(deltaBatchMs, nonNegativeEnvInt("BRIDGE_DELTA_BATCH_MS", BridgeWebSocketServer.DEFAULT_DELTA_BATCH_MS));
|
|
467
|
+
this.deltaBatchMaxChars = normalizePositiveLimit(deltaBatchMaxChars, positiveEnvInt("BRIDGE_DELTA_BATCH_MAX_CHARS", BridgeWebSocketServer.DEFAULT_DELTA_BATCH_MAX_CHARS));
|
|
446
468
|
this.archiveStore = new ArchiveStore();
|
|
447
469
|
void this.debugTraceStore.init().catch((err) => {
|
|
448
470
|
console.error("[ws] Failed to initialize debug trace store:", err);
|
|
@@ -529,6 +551,9 @@ export class BridgeWebSocketServer {
|
|
|
529
551
|
}
|
|
530
552
|
buildSessionCreatedMessage(params) {
|
|
531
553
|
const { sessionId, provider, projectPath, session, permissionMode, executionMode, planMode, approvalsReviewer, codexPermissionsMode, sandboxMode, slashCommands, skills, skillMetadata, apps, appMetadata, plugins, pluginMetadata, sourceSessionId, } = params;
|
|
554
|
+
const derivedCodexSettings = provider === "codex"
|
|
555
|
+
? withDerivedCodexPermissionsMode(session?.codexSettings)
|
|
556
|
+
: session?.codexSettings;
|
|
532
557
|
const msg = {
|
|
533
558
|
type: "system",
|
|
534
559
|
subtype: "session_created",
|
|
@@ -540,15 +565,15 @@ export class BridgeWebSocketServer {
|
|
|
540
565
|
permissionMode: permissionMode,
|
|
541
566
|
}
|
|
542
567
|
: {}),
|
|
543
|
-
...((approvalsReviewer ??
|
|
568
|
+
...((approvalsReviewer ?? derivedCodexSettings?.approvalsReviewer)
|
|
544
569
|
? {
|
|
545
|
-
approvalsReviewer: approvalsReviewer ??
|
|
570
|
+
approvalsReviewer: approvalsReviewer ?? derivedCodexSettings?.approvalsReviewer,
|
|
546
571
|
}
|
|
547
572
|
: {}),
|
|
548
|
-
...((codexPermissionsMode ??
|
|
573
|
+
...((codexPermissionsMode ?? derivedCodexSettings?.codexPermissionsMode)
|
|
549
574
|
? {
|
|
550
575
|
codexPermissionsMode: (codexPermissionsMode ??
|
|
551
|
-
|
|
576
|
+
derivedCodexSettings?.codexPermissionsMode),
|
|
552
577
|
}
|
|
553
578
|
: {}),
|
|
554
579
|
...((executionMode ??
|
|
@@ -621,28 +646,28 @@ export class BridgeWebSocketServer {
|
|
|
621
646
|
: {}),
|
|
622
647
|
...(sourceSessionId ? { sourceSessionId } : {}),
|
|
623
648
|
};
|
|
624
|
-
if (provider === "codex" &&
|
|
625
|
-
if (
|
|
626
|
-
msg.model =
|
|
649
|
+
if (provider === "codex" && derivedCodexSettings) {
|
|
650
|
+
if (derivedCodexSettings.model !== undefined) {
|
|
651
|
+
msg.model = derivedCodexSettings.model;
|
|
627
652
|
}
|
|
628
|
-
if (
|
|
629
|
-
msg.approvalPolicy =
|
|
653
|
+
if (derivedCodexSettings.approvalPolicy !== undefined) {
|
|
654
|
+
msg.approvalPolicy = derivedCodexSettings.approvalPolicy;
|
|
630
655
|
}
|
|
631
|
-
if (
|
|
632
|
-
msg.codexPermissionsMode =
|
|
656
|
+
if (derivedCodexSettings.codexPermissionsMode !== undefined) {
|
|
657
|
+
msg.codexPermissionsMode = derivedCodexSettings.codexPermissionsMode;
|
|
633
658
|
}
|
|
634
|
-
if (
|
|
635
|
-
msg.modelReasoningEffort =
|
|
659
|
+
if (derivedCodexSettings.modelReasoningEffort !== undefined) {
|
|
660
|
+
msg.modelReasoningEffort = derivedCodexSettings.modelReasoningEffort;
|
|
636
661
|
}
|
|
637
|
-
if (
|
|
638
|
-
msg.networkAccessEnabled =
|
|
662
|
+
if (derivedCodexSettings.networkAccessEnabled !== undefined) {
|
|
663
|
+
msg.networkAccessEnabled = derivedCodexSettings.networkAccessEnabled;
|
|
639
664
|
}
|
|
640
|
-
if (
|
|
641
|
-
msg.webSearchMode =
|
|
665
|
+
if (derivedCodexSettings.webSearchMode !== undefined) {
|
|
666
|
+
msg.webSearchMode = derivedCodexSettings.webSearchMode;
|
|
642
667
|
}
|
|
643
|
-
if (
|
|
668
|
+
if (derivedCodexSettings.additionalWritableRoots !== undefined) {
|
|
644
669
|
msg.additionalWritableRoots =
|
|
645
|
-
|
|
670
|
+
derivedCodexSettings.additionalWritableRoots;
|
|
646
671
|
}
|
|
647
672
|
}
|
|
648
673
|
return msg;
|
|
@@ -741,7 +766,7 @@ export class BridgeWebSocketServer {
|
|
|
741
766
|
expectedUserTurns: targetOrdinal - 1,
|
|
742
767
|
fallback: buildCodexHistoryPrefix(session, targetOrdinal - 1),
|
|
743
768
|
});
|
|
744
|
-
this.
|
|
769
|
+
this.destroySession(sessionId);
|
|
745
770
|
const newSessionId = this.sessionManager.create(projectPath, undefined, pastMessages, worktreeOpts, "codex", {
|
|
746
771
|
...(codexSettings ?? {}),
|
|
747
772
|
threadId,
|
|
@@ -1345,7 +1370,9 @@ export class BridgeWebSocketServer {
|
|
|
1345
1370
|
}
|
|
1346
1371
|
close() {
|
|
1347
1372
|
console.log("[ws] Shutting down...");
|
|
1373
|
+
this.flushAllDeltaBatches();
|
|
1348
1374
|
this.sessionManager.destroyAll();
|
|
1375
|
+
this.flushAllDeltaBatches();
|
|
1349
1376
|
stopManagedCodexAppServers();
|
|
1350
1377
|
this.debugEvents.clear();
|
|
1351
1378
|
this.wss.close();
|
|
@@ -1360,9 +1387,7 @@ export class BridgeWebSocketServer {
|
|
|
1360
1387
|
}
|
|
1361
1388
|
handleConnection(ws) {
|
|
1362
1389
|
// Send session list and project history on connect
|
|
1363
|
-
|
|
1364
|
-
void this.refreshCodexModels();
|
|
1365
|
-
void this.refreshClaudeModels();
|
|
1390
|
+
this.refreshConnectionMetadata();
|
|
1366
1391
|
this.sendSessionList(ws);
|
|
1367
1392
|
const projects = this.projectHistory?.getProjects() ?? [];
|
|
1368
1393
|
this.send(ws, { type: "project_history", projects });
|
|
@@ -1393,11 +1418,25 @@ export class BridgeWebSocketServer {
|
|
|
1393
1418
|
});
|
|
1394
1419
|
ws.on("close", () => {
|
|
1395
1420
|
console.log("[ws] Client disconnected");
|
|
1421
|
+
this.discardClientDeltaBatches(ws);
|
|
1422
|
+
this.clearPendingClaudeResumeInputs(ws);
|
|
1396
1423
|
});
|
|
1397
1424
|
ws.on("error", (err) => {
|
|
1398
1425
|
console.error("[ws] Client error:", err.message);
|
|
1399
1426
|
});
|
|
1400
1427
|
}
|
|
1428
|
+
refreshConnectionMetadata(now = Date.now()) {
|
|
1429
|
+
const lastRefreshAt = this.lastConnectMetadataRefreshAt;
|
|
1430
|
+
if (lastRefreshAt !== null &&
|
|
1431
|
+
now >= lastRefreshAt &&
|
|
1432
|
+
now - lastRefreshAt <
|
|
1433
|
+
BridgeWebSocketServer.CONNECT_METADATA_REFRESH_COOLDOWN_MS) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
this.lastConnectMetadataRefreshAt = now;
|
|
1437
|
+
void this.refreshCodexMetadata();
|
|
1438
|
+
void this.refreshClaudeModels();
|
|
1439
|
+
}
|
|
1401
1440
|
async handleClientMessage(msg, ws) {
|
|
1402
1441
|
if (msg.type === "client_capabilities") {
|
|
1403
1442
|
this.clientSupportedServerMessages.set(ws, new Set(msg.supportedServerMessages ?? []));
|
|
@@ -1577,7 +1616,7 @@ export class BridgeWebSocketServer {
|
|
|
1577
1616
|
}));
|
|
1578
1617
|
this.broadcastSessionList();
|
|
1579
1618
|
if (provider === "codex") {
|
|
1580
|
-
void this.
|
|
1619
|
+
void this.refreshCodexMetadata(projectPath);
|
|
1581
1620
|
}
|
|
1582
1621
|
else {
|
|
1583
1622
|
void this.refreshClaudeModels(projectPath);
|
|
@@ -1616,6 +1655,13 @@ export class BridgeWebSocketServer {
|
|
|
1616
1655
|
case "input": {
|
|
1617
1656
|
const session = this.resolveSession(msg.sessionId);
|
|
1618
1657
|
if (!session) {
|
|
1658
|
+
const pendingInputs = msg.sessionId
|
|
1659
|
+
? this.pendingClaudeResumeInputs.get(ws)?.get(msg.sessionId)
|
|
1660
|
+
: undefined;
|
|
1661
|
+
if (pendingInputs) {
|
|
1662
|
+
pendingInputs.push(msg);
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1619
1665
|
this.send(ws, {
|
|
1620
1666
|
type: "error",
|
|
1621
1667
|
message: "No active session. Send 'start' first.",
|
|
@@ -1814,11 +1860,20 @@ export class BridgeWebSocketServer {
|
|
|
1814
1860
|
// Claude Code input path — enqueue first, then interrupt if busy
|
|
1815
1861
|
const claudeProc = session.process;
|
|
1816
1862
|
let wasQueued = false;
|
|
1863
|
+
let shouldInterrupt = false;
|
|
1817
1864
|
if (images.length > 0) {
|
|
1818
1865
|
console.log(`[ws] Sending message with ${images.length} inline Base64 image(s)`);
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1866
|
+
if (typeof claudeProc.dispatchInputWithImages === "function") {
|
|
1867
|
+
const result = claudeProc.dispatchInputWithImages(text, images);
|
|
1868
|
+
wasQueued = result.queued;
|
|
1869
|
+
shouldInterrupt = result.shouldInterrupt;
|
|
1870
|
+
}
|
|
1871
|
+
else {
|
|
1872
|
+
const result = claudeProc.sendInputWithImages(text, images);
|
|
1873
|
+
wasQueued =
|
|
1874
|
+
typeof result === "boolean" ? result : isAgentBusySnapshot;
|
|
1875
|
+
shouldInterrupt = wasQueued;
|
|
1876
|
+
}
|
|
1822
1877
|
}
|
|
1823
1878
|
// Legacy imageId mode (backward compatibility)
|
|
1824
1879
|
else if (msg.imageId && this.galleryStore) {
|
|
@@ -1834,39 +1889,74 @@ export class BridgeWebSocketServer {
|
|
|
1834
1889
|
.then((imageData) => {
|
|
1835
1890
|
let queuedAfterResolve = false;
|
|
1836
1891
|
if (imageData) {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1892
|
+
if (typeof claudeProc.dispatchInputWithImages === "function") {
|
|
1893
|
+
const result = claudeProc.dispatchInputWithImages(text, [
|
|
1894
|
+
imageData,
|
|
1895
|
+
]);
|
|
1896
|
+
queuedAfterResolve = result.queued;
|
|
1897
|
+
if (result.shouldInterrupt)
|
|
1898
|
+
claudeProc.interrupt();
|
|
1899
|
+
}
|
|
1900
|
+
else {
|
|
1901
|
+
const result = claudeProc.sendInputWithImages(text, [
|
|
1902
|
+
imageData,
|
|
1903
|
+
]);
|
|
1904
|
+
queuedAfterResolve =
|
|
1905
|
+
typeof result === "boolean"
|
|
1906
|
+
? result
|
|
1907
|
+
: isAgentBusySnapshot;
|
|
1908
|
+
if (queuedAfterResolve)
|
|
1909
|
+
claudeProc.interrupt();
|
|
1910
|
+
}
|
|
1842
1911
|
}
|
|
1843
1912
|
else {
|
|
1844
1913
|
console.warn(`[ws] Image not found: ${msg.imageId}`);
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1914
|
+
if (typeof claudeProc.dispatchInput === "function") {
|
|
1915
|
+
const result = claudeProc.dispatchInput(text);
|
|
1916
|
+
queuedAfterResolve = result.queued;
|
|
1917
|
+
if (result.shouldInterrupt)
|
|
1918
|
+
claudeProc.interrupt();
|
|
1919
|
+
}
|
|
1920
|
+
else {
|
|
1921
|
+
const result = session.process.sendInput(text);
|
|
1922
|
+
queuedAfterResolve =
|
|
1923
|
+
typeof result === "boolean"
|
|
1924
|
+
? result
|
|
1925
|
+
: isAgentBusySnapshot;
|
|
1926
|
+
if (queuedAfterResolve)
|
|
1927
|
+
claudeProc.interrupt();
|
|
1928
|
+
}
|
|
1852
1929
|
}
|
|
1853
1930
|
})
|
|
1854
1931
|
.catch((err) => {
|
|
1855
1932
|
console.error(`[ws] Failed to load image: ${err}`);
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1933
|
+
if (typeof claudeProc.dispatchInput === "function") {
|
|
1934
|
+
const result = claudeProc.dispatchInput(text);
|
|
1935
|
+
if (result.shouldInterrupt)
|
|
1936
|
+
claudeProc.interrupt();
|
|
1937
|
+
}
|
|
1938
|
+
else {
|
|
1939
|
+
const result = session.process.sendInput(text);
|
|
1940
|
+
const queuedAfterResolve = typeof result === "boolean" ? result : isAgentBusySnapshot;
|
|
1941
|
+
if (queuedAfterResolve)
|
|
1942
|
+
claudeProc.interrupt();
|
|
1861
1943
|
}
|
|
1862
1944
|
});
|
|
1863
1945
|
break;
|
|
1864
1946
|
}
|
|
1865
1947
|
// Text-only message
|
|
1866
1948
|
else {
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1949
|
+
if (typeof claudeProc.dispatchInput === "function") {
|
|
1950
|
+
const result = claudeProc.dispatchInput(text);
|
|
1951
|
+
wasQueued = result.queued;
|
|
1952
|
+
shouldInterrupt = result.shouldInterrupt;
|
|
1953
|
+
}
|
|
1954
|
+
else {
|
|
1955
|
+
const result = session.process.sendInput(text);
|
|
1956
|
+
wasQueued =
|
|
1957
|
+
typeof result === "boolean" ? result : isAgentBusySnapshot;
|
|
1958
|
+
shouldInterrupt = wasQueued;
|
|
1959
|
+
}
|
|
1870
1960
|
}
|
|
1871
1961
|
// Acknowledge receipt so the client can mark the message state.
|
|
1872
1962
|
// queued=true means the input was enqueued instead of being consumed
|
|
@@ -1878,7 +1968,7 @@ export class BridgeWebSocketServer {
|
|
|
1878
1968
|
acceptedSeq,
|
|
1879
1969
|
queued: wasQueued,
|
|
1880
1970
|
});
|
|
1881
|
-
if (
|
|
1971
|
+
if (shouldInterrupt) {
|
|
1882
1972
|
console.log(`[ws] Agent is busy — will queue input and interrupt current turn`);
|
|
1883
1973
|
claudeProc.interrupt();
|
|
1884
1974
|
}
|
|
@@ -2059,7 +2149,7 @@ export class BridgeWebSocketServer {
|
|
|
2059
2149
|
: currentSandboxMode;
|
|
2060
2150
|
const newPermissionsMode = codexPermissionSettings?.codexPermissionsMode ??
|
|
2061
2151
|
(collaborationOnlyChange ? currentPermissionsMode : undefined) ??
|
|
2062
|
-
|
|
2152
|
+
deriveCodexPermissionsMode({
|
|
2063
2153
|
approvalPolicy: newApproval,
|
|
2064
2154
|
approvalsReviewer: codexPermissionSettings?.approvalsReviewer ??
|
|
2065
2155
|
msg.approvalsReviewer ??
|
|
@@ -2133,7 +2223,7 @@ export class BridgeWebSocketServer {
|
|
|
2133
2223
|
const worktreePath = session.worktreePath;
|
|
2134
2224
|
const worktreeBranch = session.worktreeBranch;
|
|
2135
2225
|
const sessionName = session.name;
|
|
2136
|
-
this.
|
|
2226
|
+
this.destroySession(oldSessionId);
|
|
2137
2227
|
console.log(`[ws] Permission mode change: destroyed session ${oldSessionId}`);
|
|
2138
2228
|
const hasUserMessages = session.history?.some((m) => m.type === "user_input" || m.type === "assistant") ||
|
|
2139
2229
|
(session.pastMessages && session.pastMessages.length > 0);
|
|
@@ -2364,7 +2454,7 @@ export class BridgeWebSocketServer {
|
|
|
2364
2454
|
const sessionName = session.name;
|
|
2365
2455
|
const permissionMode = session.process.permissionMode;
|
|
2366
2456
|
const model = session.process.model;
|
|
2367
|
-
this.
|
|
2457
|
+
this.destroySession(oldSessionId);
|
|
2368
2458
|
console.log(`[ws] Claude sandbox change: destroyed session ${oldSessionId}`);
|
|
2369
2459
|
const newId = this.sessionManager.create(projectPath, {
|
|
2370
2460
|
sessionId: claudeSessionId,
|
|
@@ -2420,7 +2510,7 @@ export class BridgeWebSocketServer {
|
|
|
2420
2510
|
const executionMode = oldSettings.approvalPolicy === "never" ? "fullAccess" : "default";
|
|
2421
2511
|
const planMode = collaborationMode === "plan";
|
|
2422
2512
|
const legacyPermissionMode = modesToLegacyPermissionMode("codex", executionMode, planMode);
|
|
2423
|
-
this.
|
|
2513
|
+
this.destroySession(oldSessionId);
|
|
2424
2514
|
console.log(`[ws] Sandbox mode change: destroyed session ${oldSessionId}`);
|
|
2425
2515
|
// Check if the user actually exchanged messages in this session.
|
|
2426
2516
|
// session.history always contains system events (init, status, etc.)
|
|
@@ -2557,7 +2647,7 @@ export class BridgeWebSocketServer {
|
|
|
2557
2647
|
const permissionMode = sdkProc.permissionMode;
|
|
2558
2648
|
const worktreePath = session.worktreePath;
|
|
2559
2649
|
const worktreeBranch = session.worktreeBranch;
|
|
2560
|
-
this.
|
|
2650
|
+
this.destroySession(sessionId);
|
|
2561
2651
|
console.log(`[ws] Clear context: destroyed session ${sessionId}`);
|
|
2562
2652
|
const newId = this.sessionManager.create(projectPath, {
|
|
2563
2653
|
...(claudeSessionId
|
|
@@ -2642,7 +2732,7 @@ export class BridgeWebSocketServer {
|
|
|
2642
2732
|
subtype: "stopped",
|
|
2643
2733
|
sessionId: session.claudeSessionId,
|
|
2644
2734
|
});
|
|
2645
|
-
this.
|
|
2735
|
+
this.destroySession(msg.sessionId);
|
|
2646
2736
|
this.recordDebugEvent(msg.sessionId, {
|
|
2647
2737
|
direction: "internal",
|
|
2648
2738
|
channel: "bridge",
|
|
@@ -3065,6 +3155,19 @@ export class BridgeWebSocketServer {
|
|
|
3065
3155
|
}
|
|
3066
3156
|
const claudeSessionId = sessionRefId;
|
|
3067
3157
|
const cached = this.sessionManager.getCachedCommands(resumeProjectPath);
|
|
3158
|
+
let pendingResumes = this.pendingClaudeResumeInputs.get(ws);
|
|
3159
|
+
if (!pendingResumes) {
|
|
3160
|
+
pendingResumes = new Map();
|
|
3161
|
+
this.pendingClaudeResumeInputs.set(ws, pendingResumes);
|
|
3162
|
+
}
|
|
3163
|
+
if (pendingResumes.has(claudeSessionId)) {
|
|
3164
|
+
this.send(ws, {
|
|
3165
|
+
type: "error",
|
|
3166
|
+
message: `Session resume already in progress: ${claudeSessionId}`,
|
|
3167
|
+
});
|
|
3168
|
+
break;
|
|
3169
|
+
}
|
|
3170
|
+
pendingResumes.set(claudeSessionId, []);
|
|
3068
3171
|
// Look up worktree mapping for this Claude session
|
|
3069
3172
|
const wtMapping = this.worktreeStore.get(claudeSessionId);
|
|
3070
3173
|
let worktreeOpts;
|
|
@@ -3106,7 +3209,7 @@ export class BridgeWebSocketServer {
|
|
|
3106
3209
|
worktreeOptions: worktreeOpts,
|
|
3107
3210
|
});
|
|
3108
3211
|
const createdSession = this.sessionManager.get(sessionId);
|
|
3109
|
-
|
|
3212
|
+
const finishResume = () => {
|
|
3110
3213
|
this.send(ws, {
|
|
3111
3214
|
...this.buildSessionCreatedMessage({
|
|
3112
3215
|
sessionId,
|
|
@@ -3137,10 +3240,19 @@ export class BridgeWebSocketServer {
|
|
|
3137
3240
|
}),
|
|
3138
3241
|
claudeSessionId,
|
|
3139
3242
|
});
|
|
3243
|
+
const queuedInputs = pendingResumes.get(claudeSessionId) ?? [];
|
|
3244
|
+
pendingResumes.delete(claudeSessionId);
|
|
3245
|
+
for (const input of queuedInputs) {
|
|
3246
|
+
void this.handleClientMessage({ ...input, sessionId }, ws);
|
|
3247
|
+
}
|
|
3140
3248
|
this.broadcastSessionList();
|
|
3141
3249
|
if (autoFallbackUsed) {
|
|
3142
3250
|
this.sendTip(ws, sessionId, "auto_mode_fallback_default", createdSession);
|
|
3143
3251
|
}
|
|
3252
|
+
};
|
|
3253
|
+
void this.loadAndSetSessionName(createdSession, "claude", resumeProjectPath, claudeSessionId).then(finishResume, (err) => {
|
|
3254
|
+
console.error("[ws] Failed to load resumed session name:", err);
|
|
3255
|
+
finishResume();
|
|
3144
3256
|
});
|
|
3145
3257
|
this.debugEvents.set(sessionId, []);
|
|
3146
3258
|
this.recordDebugEvent(sessionId, {
|
|
@@ -3152,6 +3264,18 @@ export class BridgeWebSocketServer {
|
|
|
3152
3264
|
this.projectHistory?.addProject(resumeProjectPath);
|
|
3153
3265
|
})
|
|
3154
3266
|
.catch((err) => {
|
|
3267
|
+
const queuedInputs = pendingResumes.get(claudeSessionId) ?? [];
|
|
3268
|
+
pendingResumes.delete(claudeSessionId);
|
|
3269
|
+
for (const input of queuedInputs) {
|
|
3270
|
+
if (input.clientMessageId) {
|
|
3271
|
+
this.send(ws, {
|
|
3272
|
+
type: "input_rejected",
|
|
3273
|
+
sessionId: claudeSessionId,
|
|
3274
|
+
clientMessageId: input.clientMessageId,
|
|
3275
|
+
reason: "Session resume failed",
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3155
3279
|
this.send(ws, {
|
|
3156
3280
|
type: "error",
|
|
3157
3281
|
message: `Failed to load session history: ${err}`,
|
|
@@ -3389,8 +3513,16 @@ export class BridgeWebSocketServer {
|
|
|
3389
3513
|
}
|
|
3390
3514
|
void (async () => {
|
|
3391
3515
|
try {
|
|
3392
|
-
const
|
|
3393
|
-
|
|
3516
|
+
const result = await listProjectFilesAndDirectoriesForClient(msg.projectPath, {
|
|
3517
|
+
maxEntries: this.fileListMaxEntries,
|
|
3518
|
+
maxBytes: this.fileListMaxBytes,
|
|
3519
|
+
});
|
|
3520
|
+
this.send(ws, {
|
|
3521
|
+
type: "file_list",
|
|
3522
|
+
files: result.files,
|
|
3523
|
+
totalFiles: result.totalFiles,
|
|
3524
|
+
truncated: result.truncated,
|
|
3525
|
+
});
|
|
3394
3526
|
}
|
|
3395
3527
|
catch (err) {
|
|
3396
3528
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4061,6 +4193,7 @@ export class BridgeWebSocketServer {
|
|
|
4061
4193
|
else if (msg.mode === "conversation") {
|
|
4062
4194
|
// Conversation-only rewind: restart session at the target UUID
|
|
4063
4195
|
try {
|
|
4196
|
+
this.flushSessionDeltaBatches(msg.sessionId);
|
|
4064
4197
|
this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
|
|
4065
4198
|
this.send(ws, {
|
|
4066
4199
|
type: "rewind_result",
|
|
@@ -4102,6 +4235,7 @@ export class BridgeWebSocketServer {
|
|
|
4102
4235
|
return;
|
|
4103
4236
|
}
|
|
4104
4237
|
try {
|
|
4238
|
+
this.flushSessionDeltaBatches(msg.sessionId);
|
|
4105
4239
|
this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
|
|
4106
4240
|
this.send(ws, {
|
|
4107
4241
|
type: "rewind_result",
|
|
@@ -4450,6 +4584,10 @@ export class BridgeWebSocketServer {
|
|
|
4450
4584
|
}
|
|
4451
4585
|
}
|
|
4452
4586
|
}
|
|
4587
|
+
clearPendingClaudeResumeInputs(ws) {
|
|
4588
|
+
this.pendingClaudeResumeInputs.get(ws)?.clear();
|
|
4589
|
+
this.pendingClaudeResumeInputs.delete(ws);
|
|
4590
|
+
}
|
|
4453
4591
|
/**
|
|
4454
4592
|
* Load the saved session name from CLI storage and set it on the SessionInfo.
|
|
4455
4593
|
* Called after SessionManager.create() so that session_created carries the name.
|
|
@@ -4592,6 +4730,121 @@ export class BridgeWebSocketServer {
|
|
|
4592
4730
|
});
|
|
4593
4731
|
}
|
|
4594
4732
|
broadcastSessionMessage(sessionId, msg, exclude) {
|
|
4733
|
+
if (this.shouldBatchDelta(msg, exclude)) {
|
|
4734
|
+
this.trackSessionMessage(sessionId, msg);
|
|
4735
|
+
const chunks = this.splitDeltaText(msg.text);
|
|
4736
|
+
for (const client of this.wss.clients) {
|
|
4737
|
+
if (client.readyState !== WebSocket.OPEN)
|
|
4738
|
+
continue;
|
|
4739
|
+
if (!this.shouldSendToClient(client, msg))
|
|
4740
|
+
continue;
|
|
4741
|
+
this.queueDeltaForClient(client, sessionId, msg.type, chunks);
|
|
4742
|
+
}
|
|
4743
|
+
return;
|
|
4744
|
+
}
|
|
4745
|
+
this.flushSessionDeltaBatches(sessionId);
|
|
4746
|
+
this.trackSessionMessage(sessionId, msg);
|
|
4747
|
+
this.broadcastSessionMessageNow(sessionId, msg, exclude);
|
|
4748
|
+
}
|
|
4749
|
+
shouldBatchDelta(msg, exclude) {
|
|
4750
|
+
if (this.deltaBatchMs === 0 || exclude)
|
|
4751
|
+
return false;
|
|
4752
|
+
return msg.type === "stream_delta" || msg.type === "thinking_delta";
|
|
4753
|
+
}
|
|
4754
|
+
queueDeltaForClient(client, sessionId, type, chunks) {
|
|
4755
|
+
for (const chunk of chunks) {
|
|
4756
|
+
let batch = this.deltaBatches.get(client)?.get(sessionId);
|
|
4757
|
+
if (batch &&
|
|
4758
|
+
batch.charCount > 0 &&
|
|
4759
|
+
batch.charCount + chunk.charCount > this.deltaBatchMaxChars) {
|
|
4760
|
+
this.flushClientDeltaBatch(client, sessionId);
|
|
4761
|
+
batch = undefined;
|
|
4762
|
+
}
|
|
4763
|
+
if (!batch) {
|
|
4764
|
+
const clientBatches = this.deltaBatches.get(client) ?? new Map();
|
|
4765
|
+
batch = {
|
|
4766
|
+
messages: [],
|
|
4767
|
+
charCount: 0,
|
|
4768
|
+
timer: setTimeout(() => {
|
|
4769
|
+
this.flushClientDeltaBatch(client, sessionId);
|
|
4770
|
+
}, this.deltaBatchMs),
|
|
4771
|
+
};
|
|
4772
|
+
clientBatches.set(sessionId, batch);
|
|
4773
|
+
this.deltaBatches.set(client, clientBatches);
|
|
4774
|
+
}
|
|
4775
|
+
const last = batch.messages.at(-1);
|
|
4776
|
+
if (last?.type === type) {
|
|
4777
|
+
last.text += chunk.text;
|
|
4778
|
+
}
|
|
4779
|
+
else {
|
|
4780
|
+
batch.messages.push({ type, text: chunk.text });
|
|
4781
|
+
}
|
|
4782
|
+
batch.charCount += chunk.charCount;
|
|
4783
|
+
if (batch.charCount >= this.deltaBatchMaxChars) {
|
|
4784
|
+
this.flushClientDeltaBatch(client, sessionId);
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
splitDeltaText(text) {
|
|
4789
|
+
if (text.length === 0)
|
|
4790
|
+
return [{ text, charCount: 0 }];
|
|
4791
|
+
const chunks = [];
|
|
4792
|
+
let chars = [];
|
|
4793
|
+
let charCount = 0;
|
|
4794
|
+
for (const char of text) {
|
|
4795
|
+
chars.push(char);
|
|
4796
|
+
charCount += 1;
|
|
4797
|
+
if (charCount >= this.deltaBatchMaxChars) {
|
|
4798
|
+
chunks.push({ text: chars.join(""), charCount });
|
|
4799
|
+
chars = [];
|
|
4800
|
+
charCount = 0;
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
if (chars.length > 0) {
|
|
4804
|
+
chunks.push({ text: chars.join(""), charCount });
|
|
4805
|
+
}
|
|
4806
|
+
return chunks;
|
|
4807
|
+
}
|
|
4808
|
+
flushSessionDeltaBatches(sessionId) {
|
|
4809
|
+
for (const client of Array.from(this.deltaBatches.keys())) {
|
|
4810
|
+
this.flushClientDeltaBatch(client, sessionId);
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
flushAllDeltaBatches() {
|
|
4814
|
+
for (const [client, batches] of Array.from(this.deltaBatches.entries())) {
|
|
4815
|
+
for (const sessionId of Array.from(batches.keys())) {
|
|
4816
|
+
this.flushClientDeltaBatch(client, sessionId);
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
flushClientDeltaBatch(client, sessionId) {
|
|
4821
|
+
const clientBatches = this.deltaBatches.get(client);
|
|
4822
|
+
const batch = clientBatches?.get(sessionId);
|
|
4823
|
+
if (!batch)
|
|
4824
|
+
return;
|
|
4825
|
+
clearTimeout(batch.timer);
|
|
4826
|
+
clientBatches?.delete(sessionId);
|
|
4827
|
+
if (clientBatches?.size === 0)
|
|
4828
|
+
this.deltaBatches.delete(client);
|
|
4829
|
+
if (client.readyState !== WebSocket.OPEN)
|
|
4830
|
+
return;
|
|
4831
|
+
for (const msg of batch.messages) {
|
|
4832
|
+
client.send(JSON.stringify({ ...msg, sessionId }));
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
discardClientDeltaBatches(client) {
|
|
4836
|
+
const batches = this.deltaBatches.get(client);
|
|
4837
|
+
if (!batches)
|
|
4838
|
+
return;
|
|
4839
|
+
for (const batch of batches.values())
|
|
4840
|
+
clearTimeout(batch.timer);
|
|
4841
|
+
this.deltaBatches.delete(client);
|
|
4842
|
+
}
|
|
4843
|
+
destroySession(sessionId) {
|
|
4844
|
+
this.flushSessionDeltaBatches(sessionId);
|
|
4845
|
+
this.sessionManager.destroy(sessionId);
|
|
4846
|
+
}
|
|
4847
|
+
trackSessionMessage(sessionId, msg) {
|
|
4595
4848
|
this.maybeSendPushNotification(sessionId, msg);
|
|
4596
4849
|
this.recordDebugEvent(sessionId, {
|
|
4597
4850
|
direction: "outgoing",
|
|
@@ -4614,7 +4867,8 @@ export class BridgeWebSocketServer {
|
|
|
4614
4867
|
});
|
|
4615
4868
|
}
|
|
4616
4869
|
}
|
|
4617
|
-
|
|
4870
|
+
}
|
|
4871
|
+
broadcastSessionMessageNow(sessionId, msg, exclude) {
|
|
4618
4872
|
const data = JSON.stringify({ ...msg, sessionId });
|
|
4619
4873
|
for (const client of this.wss.clients) {
|
|
4620
4874
|
if (client === exclude)
|
|
@@ -4691,28 +4945,58 @@ export class BridgeWebSocketServer {
|
|
|
4691
4945
|
});
|
|
4692
4946
|
}
|
|
4693
4947
|
}
|
|
4694
|
-
async
|
|
4695
|
-
if (this.
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
.then((models) => {
|
|
4699
|
-
if (models.length > 0) {
|
|
4700
|
-
this.applyCodexModels(models);
|
|
4701
|
-
}
|
|
4702
|
-
else {
|
|
4703
|
-
this.applyFallbackCodexModels();
|
|
4948
|
+
async refreshCodexMetadata(projectPath) {
|
|
4949
|
+
if (this.codexMetadataRequest) {
|
|
4950
|
+
if (projectPath) {
|
|
4951
|
+
return this.codexMetadataRequest.then(() => this.refreshCodexMetadata(projectPath));
|
|
4704
4952
|
}
|
|
4705
|
-
this.
|
|
4706
|
-
}
|
|
4953
|
+
return this.codexMetadataRequest;
|
|
4954
|
+
}
|
|
4955
|
+
this.codexMetadataRequest = this.loadAndApplyCodexMetadata(projectPath)
|
|
4707
4956
|
.catch((err) => {
|
|
4708
|
-
console.warn(`[ws] Failed to load Codex
|
|
4957
|
+
console.warn(`[ws] Failed to load Codex metadata: ${err}`);
|
|
4958
|
+
this.codexProfiles = [];
|
|
4959
|
+
this.defaultCodexProfile = undefined;
|
|
4709
4960
|
this.applyFallbackCodexModels();
|
|
4710
4961
|
this.broadcastSessionList();
|
|
4711
4962
|
})
|
|
4712
4963
|
.finally(() => {
|
|
4713
|
-
this.
|
|
4964
|
+
this.codexMetadataRequest = null;
|
|
4714
4965
|
});
|
|
4715
|
-
return this.
|
|
4966
|
+
return this.codexMetadataRequest;
|
|
4967
|
+
}
|
|
4968
|
+
async loadAndApplyCodexMetadata(projectPath) {
|
|
4969
|
+
const activeProcess = this.getActiveCodexProcess();
|
|
4970
|
+
const codexProcess = activeProcess ?? (await this.createStandaloneCodexProcess(projectPath));
|
|
4971
|
+
try {
|
|
4972
|
+
const [profileResult, modelResult] = await Promise.allSettled([
|
|
4973
|
+
codexProcess.readProfileConfig(projectPath),
|
|
4974
|
+
this.readCodexModels(codexProcess),
|
|
4975
|
+
]);
|
|
4976
|
+
if (profileResult.status === "fulfilled") {
|
|
4977
|
+
this.codexProfiles = profileResult.value.profiles;
|
|
4978
|
+
this.defaultCodexProfile = profileResult.value.defaultProfile;
|
|
4979
|
+
}
|
|
4980
|
+
else {
|
|
4981
|
+
console.warn(`[ws] Failed to load Codex profiles: ${profileResult.reason}`);
|
|
4982
|
+
this.codexProfiles = [];
|
|
4983
|
+
this.defaultCodexProfile = undefined;
|
|
4984
|
+
}
|
|
4985
|
+
if (modelResult.status === "fulfilled" && modelResult.value.length > 0) {
|
|
4986
|
+
this.applyCodexModels(modelResult.value);
|
|
4987
|
+
}
|
|
4988
|
+
else {
|
|
4989
|
+
if (modelResult.status === "rejected") {
|
|
4990
|
+
console.warn(`[ws] Failed to load Codex models: ${modelResult.reason}`);
|
|
4991
|
+
}
|
|
4992
|
+
this.applyFallbackCodexModels();
|
|
4993
|
+
}
|
|
4994
|
+
this.broadcastSessionList();
|
|
4995
|
+
}
|
|
4996
|
+
finally {
|
|
4997
|
+
if (!activeProcess)
|
|
4998
|
+
codexProcess.stop();
|
|
4999
|
+
}
|
|
4716
5000
|
}
|
|
4717
5001
|
async refreshClaudeModels(projectPath) {
|
|
4718
5002
|
if (this.claudeModelsRequest)
|
|
@@ -4756,28 +5040,18 @@ export class BridgeWebSocketServer {
|
|
|
4756
5040
|
this.claudeModels = FALLBACK_CLAUDE_MODELS;
|
|
4757
5041
|
this.claudeModelEfforts = { ...FALLBACK_CLAUDE_MODEL_EFFORTS };
|
|
4758
5042
|
}
|
|
4759
|
-
async
|
|
4760
|
-
const
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
try {
|
|
4764
|
-
const modelSource = process;
|
|
4765
|
-
if (typeof modelSource.listAvailableModelMetadata === "function") {
|
|
4766
|
-
return await modelSource.listAvailableModelMetadata();
|
|
4767
|
-
}
|
|
4768
|
-
const models = typeof modelSource.listAvailableModels === "function"
|
|
4769
|
-
? await modelSource.listAvailableModels()
|
|
4770
|
-
: [];
|
|
4771
|
-
return models.map((model) => ({
|
|
4772
|
-
model,
|
|
4773
|
-
supportedReasoningEfforts: FALLBACK_CODEX_REASONING_EFFORTS,
|
|
4774
|
-
}));
|
|
4775
|
-
}
|
|
4776
|
-
finally {
|
|
4777
|
-
if (isStandalone) {
|
|
4778
|
-
process.stop();
|
|
4779
|
-
}
|
|
5043
|
+
async readCodexModels(codexProcess) {
|
|
5044
|
+
const modelSource = codexProcess;
|
|
5045
|
+
if (typeof modelSource.listAvailableModelMetadata === "function") {
|
|
5046
|
+
return modelSource.listAvailableModelMetadata();
|
|
4780
5047
|
}
|
|
5048
|
+
const models = typeof modelSource.listAvailableModels === "function"
|
|
5049
|
+
? await modelSource.listAvailableModels()
|
|
5050
|
+
: [];
|
|
5051
|
+
return models.map((model) => ({
|
|
5052
|
+
model,
|
|
5053
|
+
supportedReasoningEfforts: FALLBACK_CODEX_REASONING_EFFORTS,
|
|
5054
|
+
}));
|
|
4781
5055
|
}
|
|
4782
5056
|
applyCodexModels(models) {
|
|
4783
5057
|
this.codexModels = models.map((model) => model.model);
|
|
@@ -4795,25 +5069,6 @@ export class BridgeWebSocketServer {
|
|
|
4795
5069
|
FALLBACK_CODEX_REASONING_EFFORTS,
|
|
4796
5070
|
]));
|
|
4797
5071
|
}
|
|
4798
|
-
async refreshCodexProfiles(projectPath) {
|
|
4799
|
-
if (this.codexProfilesRequest)
|
|
4800
|
-
return this.codexProfilesRequest;
|
|
4801
|
-
this.codexProfilesRequest = this.loadCodexProfiles(projectPath)
|
|
4802
|
-
.then(({ profiles, defaultProfile }) => {
|
|
4803
|
-
this.codexProfiles = profiles;
|
|
4804
|
-
this.defaultCodexProfile = defaultProfile;
|
|
4805
|
-
this.broadcastSessionList();
|
|
4806
|
-
})
|
|
4807
|
-
.catch((err) => {
|
|
4808
|
-
console.warn(`[ws] Failed to load Codex profiles: ${err}`);
|
|
4809
|
-
this.codexProfiles = [];
|
|
4810
|
-
this.defaultCodexProfile = undefined;
|
|
4811
|
-
})
|
|
4812
|
-
.finally(() => {
|
|
4813
|
-
this.codexProfilesRequest = null;
|
|
4814
|
-
});
|
|
4815
|
-
return this.codexProfilesRequest;
|
|
4816
|
-
}
|
|
4817
5072
|
async loadCodexProfiles(projectPath) {
|
|
4818
5073
|
const process = this.getActiveCodexProcess() ??
|
|
4819
5074
|
(await this.createStandaloneCodexProcess(projectPath));
|
|
@@ -4925,8 +5180,14 @@ export class BridgeWebSocketServer {
|
|
|
4925
5180
|
}
|
|
4926
5181
|
async createStandaloneCodexProcess(projectPath) {
|
|
4927
5182
|
const proc = new CodexProcess();
|
|
4928
|
-
|
|
4929
|
-
|
|
5183
|
+
try {
|
|
5184
|
+
await proc.initializeOnly(projectPath ?? process.cwd());
|
|
5185
|
+
return proc;
|
|
5186
|
+
}
|
|
5187
|
+
catch (err) {
|
|
5188
|
+
proc.stop();
|
|
5189
|
+
throw err;
|
|
5190
|
+
}
|
|
4930
5191
|
}
|
|
4931
5192
|
/** Extract a short project label from the full projectPath (last directory name). */
|
|
4932
5193
|
projectLabel(sessionId) {
|