@ccpocket/bridge 1.63.4 → 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.d.ts +8 -0
- package/dist/sessions-index.js +99 -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 +412 -147
- 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,15 +319,51 @@ 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) {
|
|
355
|
+
// thread/list only exposes a single preview blob; prefer the real
|
|
356
|
+
// first/last/summary texts parsed from the rollout file so display-mode
|
|
357
|
+
// switches (first prompt / last prompt / summary) show distinct content.
|
|
348
358
|
return {
|
|
349
359
|
sessionId: thread.id,
|
|
350
360
|
provider: "codex",
|
|
351
361
|
...(thread.name ? { name: thread.name } : {}),
|
|
352
362
|
...(thread.agentNickname ? { agentNickname: thread.agentNickname } : {}),
|
|
353
363
|
...(thread.agentRole ? { agentRole: thread.agentRole } : {}),
|
|
354
|
-
summary: thread.preview || undefined,
|
|
355
|
-
firstPrompt: thread.preview || "",
|
|
364
|
+
summary: indexed?.summary || thread.preview || undefined,
|
|
365
|
+
firstPrompt: indexed?.firstPrompt || thread.preview || "",
|
|
366
|
+
...(indexed?.lastPrompt ? { lastPrompt: indexed.lastPrompt } : {}),
|
|
356
367
|
created: threadTimestampToIso(thread.createdAt),
|
|
357
368
|
modified: threadTimestampToIso(thread.updatedAt),
|
|
358
369
|
gitBranch: thread.gitBranch ?? "",
|
|
@@ -387,6 +398,11 @@ function mergeRecentSessionPages(sessions) {
|
|
|
387
398
|
export class BridgeWebSocketServer {
|
|
388
399
|
static MAX_DEBUG_EVENTS = 800;
|
|
389
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;
|
|
390
406
|
wss;
|
|
391
407
|
sessionManager;
|
|
392
408
|
apiKey;
|
|
@@ -406,7 +422,8 @@ export class BridgeWebSocketServer {
|
|
|
406
422
|
archiveStore;
|
|
407
423
|
codexProfiles = [];
|
|
408
424
|
defaultCodexProfile;
|
|
409
|
-
|
|
425
|
+
codexMetadataRequest = null;
|
|
426
|
+
lastConnectMetadataRefreshAt = null;
|
|
410
427
|
claudeModels = FALLBACK_CLAUDE_MODELS;
|
|
411
428
|
claudeModelEfforts = {
|
|
412
429
|
...FALLBACK_CLAUDE_MODEL_EFFORTS,
|
|
@@ -417,16 +434,21 @@ export class BridgeWebSocketServer {
|
|
|
417
434
|
model,
|
|
418
435
|
FALLBACK_CODEX_REASONING_EFFORTS,
|
|
419
436
|
]));
|
|
420
|
-
codexModelsRequest = null;
|
|
421
437
|
/** FCM token → push notification locale */
|
|
422
438
|
tokenLocales = new Map();
|
|
423
439
|
tokenPrivacyMode = new Map();
|
|
424
440
|
failSetPermissionMode = envFlagEnabled("BRIDGE_FAIL_SET_PERMISSION_MODE");
|
|
425
441
|
failSetSandboxMode = envFlagEnabled("BRIDGE_FAIL_SET_SANDBOX_MODE");
|
|
442
|
+
fileListMaxEntries;
|
|
443
|
+
fileListMaxBytes;
|
|
444
|
+
deltaBatchMs;
|
|
445
|
+
deltaBatchMaxChars;
|
|
446
|
+
deltaBatches = new Map();
|
|
426
447
|
platform;
|
|
427
448
|
clientSupportedServerMessages = new WeakMap();
|
|
449
|
+
pendingClaudeResumeInputs = new WeakMap();
|
|
428
450
|
constructor(options) {
|
|
429
|
-
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;
|
|
430
452
|
this.apiKey = apiKey ?? null;
|
|
431
453
|
this.allowedDirs = allowedDirs ?? [];
|
|
432
454
|
this.imageStore = imageStore ?? null;
|
|
@@ -439,6 +461,10 @@ export class BridgeWebSocketServer {
|
|
|
439
461
|
this.promptHistoryBackup = promptHistoryBackup ?? null;
|
|
440
462
|
this.promptHistoryStore = promptHistoryStore ?? null;
|
|
441
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));
|
|
442
468
|
this.archiveStore = new ArchiveStore();
|
|
443
469
|
void this.debugTraceStore.init().catch((err) => {
|
|
444
470
|
console.error("[ws] Failed to initialize debug trace store:", err);
|
|
@@ -525,6 +551,9 @@ export class BridgeWebSocketServer {
|
|
|
525
551
|
}
|
|
526
552
|
buildSessionCreatedMessage(params) {
|
|
527
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;
|
|
528
557
|
const msg = {
|
|
529
558
|
type: "system",
|
|
530
559
|
subtype: "session_created",
|
|
@@ -536,15 +565,15 @@ export class BridgeWebSocketServer {
|
|
|
536
565
|
permissionMode: permissionMode,
|
|
537
566
|
}
|
|
538
567
|
: {}),
|
|
539
|
-
...((approvalsReviewer ??
|
|
568
|
+
...((approvalsReviewer ?? derivedCodexSettings?.approvalsReviewer)
|
|
540
569
|
? {
|
|
541
|
-
approvalsReviewer: approvalsReviewer ??
|
|
570
|
+
approvalsReviewer: approvalsReviewer ?? derivedCodexSettings?.approvalsReviewer,
|
|
542
571
|
}
|
|
543
572
|
: {}),
|
|
544
|
-
...((codexPermissionsMode ??
|
|
573
|
+
...((codexPermissionsMode ?? derivedCodexSettings?.codexPermissionsMode)
|
|
545
574
|
? {
|
|
546
575
|
codexPermissionsMode: (codexPermissionsMode ??
|
|
547
|
-
|
|
576
|
+
derivedCodexSettings?.codexPermissionsMode),
|
|
548
577
|
}
|
|
549
578
|
: {}),
|
|
550
579
|
...((executionMode ??
|
|
@@ -617,28 +646,28 @@ export class BridgeWebSocketServer {
|
|
|
617
646
|
: {}),
|
|
618
647
|
...(sourceSessionId ? { sourceSessionId } : {}),
|
|
619
648
|
};
|
|
620
|
-
if (provider === "codex" &&
|
|
621
|
-
if (
|
|
622
|
-
msg.model =
|
|
649
|
+
if (provider === "codex" && derivedCodexSettings) {
|
|
650
|
+
if (derivedCodexSettings.model !== undefined) {
|
|
651
|
+
msg.model = derivedCodexSettings.model;
|
|
623
652
|
}
|
|
624
|
-
if (
|
|
625
|
-
msg.approvalPolicy =
|
|
653
|
+
if (derivedCodexSettings.approvalPolicy !== undefined) {
|
|
654
|
+
msg.approvalPolicy = derivedCodexSettings.approvalPolicy;
|
|
626
655
|
}
|
|
627
|
-
if (
|
|
628
|
-
msg.codexPermissionsMode =
|
|
656
|
+
if (derivedCodexSettings.codexPermissionsMode !== undefined) {
|
|
657
|
+
msg.codexPermissionsMode = derivedCodexSettings.codexPermissionsMode;
|
|
629
658
|
}
|
|
630
|
-
if (
|
|
631
|
-
msg.modelReasoningEffort =
|
|
659
|
+
if (derivedCodexSettings.modelReasoningEffort !== undefined) {
|
|
660
|
+
msg.modelReasoningEffort = derivedCodexSettings.modelReasoningEffort;
|
|
632
661
|
}
|
|
633
|
-
if (
|
|
634
|
-
msg.networkAccessEnabled =
|
|
662
|
+
if (derivedCodexSettings.networkAccessEnabled !== undefined) {
|
|
663
|
+
msg.networkAccessEnabled = derivedCodexSettings.networkAccessEnabled;
|
|
635
664
|
}
|
|
636
|
-
if (
|
|
637
|
-
msg.webSearchMode =
|
|
665
|
+
if (derivedCodexSettings.webSearchMode !== undefined) {
|
|
666
|
+
msg.webSearchMode = derivedCodexSettings.webSearchMode;
|
|
638
667
|
}
|
|
639
|
-
if (
|
|
668
|
+
if (derivedCodexSettings.additionalWritableRoots !== undefined) {
|
|
640
669
|
msg.additionalWritableRoots =
|
|
641
|
-
|
|
670
|
+
derivedCodexSettings.additionalWritableRoots;
|
|
642
671
|
}
|
|
643
672
|
}
|
|
644
673
|
return msg;
|
|
@@ -737,7 +766,7 @@ export class BridgeWebSocketServer {
|
|
|
737
766
|
expectedUserTurns: targetOrdinal - 1,
|
|
738
767
|
fallback: buildCodexHistoryPrefix(session, targetOrdinal - 1),
|
|
739
768
|
});
|
|
740
|
-
this.
|
|
769
|
+
this.destroySession(sessionId);
|
|
741
770
|
const newSessionId = this.sessionManager.create(projectPath, undefined, pastMessages, worktreeOpts, "codex", {
|
|
742
771
|
...(codexSettings ?? {}),
|
|
743
772
|
threadId,
|
|
@@ -1341,7 +1370,9 @@ export class BridgeWebSocketServer {
|
|
|
1341
1370
|
}
|
|
1342
1371
|
close() {
|
|
1343
1372
|
console.log("[ws] Shutting down...");
|
|
1373
|
+
this.flushAllDeltaBatches();
|
|
1344
1374
|
this.sessionManager.destroyAll();
|
|
1375
|
+
this.flushAllDeltaBatches();
|
|
1345
1376
|
stopManagedCodexAppServers();
|
|
1346
1377
|
this.debugEvents.clear();
|
|
1347
1378
|
this.wss.close();
|
|
@@ -1356,9 +1387,7 @@ export class BridgeWebSocketServer {
|
|
|
1356
1387
|
}
|
|
1357
1388
|
handleConnection(ws) {
|
|
1358
1389
|
// Send session list and project history on connect
|
|
1359
|
-
|
|
1360
|
-
void this.refreshCodexModels();
|
|
1361
|
-
void this.refreshClaudeModels();
|
|
1390
|
+
this.refreshConnectionMetadata();
|
|
1362
1391
|
this.sendSessionList(ws);
|
|
1363
1392
|
const projects = this.projectHistory?.getProjects() ?? [];
|
|
1364
1393
|
this.send(ws, { type: "project_history", projects });
|
|
@@ -1389,11 +1418,25 @@ export class BridgeWebSocketServer {
|
|
|
1389
1418
|
});
|
|
1390
1419
|
ws.on("close", () => {
|
|
1391
1420
|
console.log("[ws] Client disconnected");
|
|
1421
|
+
this.discardClientDeltaBatches(ws);
|
|
1422
|
+
this.clearPendingClaudeResumeInputs(ws);
|
|
1392
1423
|
});
|
|
1393
1424
|
ws.on("error", (err) => {
|
|
1394
1425
|
console.error("[ws] Client error:", err.message);
|
|
1395
1426
|
});
|
|
1396
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
|
+
}
|
|
1397
1440
|
async handleClientMessage(msg, ws) {
|
|
1398
1441
|
if (msg.type === "client_capabilities") {
|
|
1399
1442
|
this.clientSupportedServerMessages.set(ws, new Set(msg.supportedServerMessages ?? []));
|
|
@@ -1573,7 +1616,7 @@ export class BridgeWebSocketServer {
|
|
|
1573
1616
|
}));
|
|
1574
1617
|
this.broadcastSessionList();
|
|
1575
1618
|
if (provider === "codex") {
|
|
1576
|
-
void this.
|
|
1619
|
+
void this.refreshCodexMetadata(projectPath);
|
|
1577
1620
|
}
|
|
1578
1621
|
else {
|
|
1579
1622
|
void this.refreshClaudeModels(projectPath);
|
|
@@ -1612,6 +1655,13 @@ export class BridgeWebSocketServer {
|
|
|
1612
1655
|
case "input": {
|
|
1613
1656
|
const session = this.resolveSession(msg.sessionId);
|
|
1614
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
|
+
}
|
|
1615
1665
|
this.send(ws, {
|
|
1616
1666
|
type: "error",
|
|
1617
1667
|
message: "No active session. Send 'start' first.",
|
|
@@ -1810,11 +1860,20 @@ export class BridgeWebSocketServer {
|
|
|
1810
1860
|
// Claude Code input path — enqueue first, then interrupt if busy
|
|
1811
1861
|
const claudeProc = session.process;
|
|
1812
1862
|
let wasQueued = false;
|
|
1863
|
+
let shouldInterrupt = false;
|
|
1813
1864
|
if (images.length > 0) {
|
|
1814
1865
|
console.log(`[ws] Sending message with ${images.length} inline Base64 image(s)`);
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
+
}
|
|
1818
1877
|
}
|
|
1819
1878
|
// Legacy imageId mode (backward compatibility)
|
|
1820
1879
|
else if (msg.imageId && this.galleryStore) {
|
|
@@ -1830,39 +1889,74 @@ export class BridgeWebSocketServer {
|
|
|
1830
1889
|
.then((imageData) => {
|
|
1831
1890
|
let queuedAfterResolve = false;
|
|
1832
1891
|
if (imageData) {
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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
|
+
}
|
|
1838
1911
|
}
|
|
1839
1912
|
else {
|
|
1840
1913
|
console.warn(`[ws] Image not found: ${msg.imageId}`);
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
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
|
+
}
|
|
1848
1929
|
}
|
|
1849
1930
|
})
|
|
1850
1931
|
.catch((err) => {
|
|
1851
1932
|
console.error(`[ws] Failed to load image: ${err}`);
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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();
|
|
1857
1943
|
}
|
|
1858
1944
|
});
|
|
1859
1945
|
break;
|
|
1860
1946
|
}
|
|
1861
1947
|
// Text-only message
|
|
1862
1948
|
else {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
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
|
+
}
|
|
1866
1960
|
}
|
|
1867
1961
|
// Acknowledge receipt so the client can mark the message state.
|
|
1868
1962
|
// queued=true means the input was enqueued instead of being consumed
|
|
@@ -1874,7 +1968,7 @@ export class BridgeWebSocketServer {
|
|
|
1874
1968
|
acceptedSeq,
|
|
1875
1969
|
queued: wasQueued,
|
|
1876
1970
|
});
|
|
1877
|
-
if (
|
|
1971
|
+
if (shouldInterrupt) {
|
|
1878
1972
|
console.log(`[ws] Agent is busy — will queue input and interrupt current turn`);
|
|
1879
1973
|
claudeProc.interrupt();
|
|
1880
1974
|
}
|
|
@@ -2055,7 +2149,7 @@ export class BridgeWebSocketServer {
|
|
|
2055
2149
|
: currentSandboxMode;
|
|
2056
2150
|
const newPermissionsMode = codexPermissionSettings?.codexPermissionsMode ??
|
|
2057
2151
|
(collaborationOnlyChange ? currentPermissionsMode : undefined) ??
|
|
2058
|
-
|
|
2152
|
+
deriveCodexPermissionsMode({
|
|
2059
2153
|
approvalPolicy: newApproval,
|
|
2060
2154
|
approvalsReviewer: codexPermissionSettings?.approvalsReviewer ??
|
|
2061
2155
|
msg.approvalsReviewer ??
|
|
@@ -2129,7 +2223,7 @@ export class BridgeWebSocketServer {
|
|
|
2129
2223
|
const worktreePath = session.worktreePath;
|
|
2130
2224
|
const worktreeBranch = session.worktreeBranch;
|
|
2131
2225
|
const sessionName = session.name;
|
|
2132
|
-
this.
|
|
2226
|
+
this.destroySession(oldSessionId);
|
|
2133
2227
|
console.log(`[ws] Permission mode change: destroyed session ${oldSessionId}`);
|
|
2134
2228
|
const hasUserMessages = session.history?.some((m) => m.type === "user_input" || m.type === "assistant") ||
|
|
2135
2229
|
(session.pastMessages && session.pastMessages.length > 0);
|
|
@@ -2360,7 +2454,7 @@ export class BridgeWebSocketServer {
|
|
|
2360
2454
|
const sessionName = session.name;
|
|
2361
2455
|
const permissionMode = session.process.permissionMode;
|
|
2362
2456
|
const model = session.process.model;
|
|
2363
|
-
this.
|
|
2457
|
+
this.destroySession(oldSessionId);
|
|
2364
2458
|
console.log(`[ws] Claude sandbox change: destroyed session ${oldSessionId}`);
|
|
2365
2459
|
const newId = this.sessionManager.create(projectPath, {
|
|
2366
2460
|
sessionId: claudeSessionId,
|
|
@@ -2416,7 +2510,7 @@ export class BridgeWebSocketServer {
|
|
|
2416
2510
|
const executionMode = oldSettings.approvalPolicy === "never" ? "fullAccess" : "default";
|
|
2417
2511
|
const planMode = collaborationMode === "plan";
|
|
2418
2512
|
const legacyPermissionMode = modesToLegacyPermissionMode("codex", executionMode, planMode);
|
|
2419
|
-
this.
|
|
2513
|
+
this.destroySession(oldSessionId);
|
|
2420
2514
|
console.log(`[ws] Sandbox mode change: destroyed session ${oldSessionId}`);
|
|
2421
2515
|
// Check if the user actually exchanged messages in this session.
|
|
2422
2516
|
// session.history always contains system events (init, status, etc.)
|
|
@@ -2553,7 +2647,7 @@ export class BridgeWebSocketServer {
|
|
|
2553
2647
|
const permissionMode = sdkProc.permissionMode;
|
|
2554
2648
|
const worktreePath = session.worktreePath;
|
|
2555
2649
|
const worktreeBranch = session.worktreeBranch;
|
|
2556
|
-
this.
|
|
2650
|
+
this.destroySession(sessionId);
|
|
2557
2651
|
console.log(`[ws] Clear context: destroyed session ${sessionId}`);
|
|
2558
2652
|
const newId = this.sessionManager.create(projectPath, {
|
|
2559
2653
|
...(claudeSessionId
|
|
@@ -2638,7 +2732,7 @@ export class BridgeWebSocketServer {
|
|
|
2638
2732
|
subtype: "stopped",
|
|
2639
2733
|
sessionId: session.claudeSessionId,
|
|
2640
2734
|
});
|
|
2641
|
-
this.
|
|
2735
|
+
this.destroySession(msg.sessionId);
|
|
2642
2736
|
this.recordDebugEvent(msg.sessionId, {
|
|
2643
2737
|
direction: "internal",
|
|
2644
2738
|
channel: "bridge",
|
|
@@ -3061,6 +3155,19 @@ export class BridgeWebSocketServer {
|
|
|
3061
3155
|
}
|
|
3062
3156
|
const claudeSessionId = sessionRefId;
|
|
3063
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, []);
|
|
3064
3171
|
// Look up worktree mapping for this Claude session
|
|
3065
3172
|
const wtMapping = this.worktreeStore.get(claudeSessionId);
|
|
3066
3173
|
let worktreeOpts;
|
|
@@ -3102,7 +3209,7 @@ export class BridgeWebSocketServer {
|
|
|
3102
3209
|
worktreeOptions: worktreeOpts,
|
|
3103
3210
|
});
|
|
3104
3211
|
const createdSession = this.sessionManager.get(sessionId);
|
|
3105
|
-
|
|
3212
|
+
const finishResume = () => {
|
|
3106
3213
|
this.send(ws, {
|
|
3107
3214
|
...this.buildSessionCreatedMessage({
|
|
3108
3215
|
sessionId,
|
|
@@ -3133,10 +3240,19 @@ export class BridgeWebSocketServer {
|
|
|
3133
3240
|
}),
|
|
3134
3241
|
claudeSessionId,
|
|
3135
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
|
+
}
|
|
3136
3248
|
this.broadcastSessionList();
|
|
3137
3249
|
if (autoFallbackUsed) {
|
|
3138
3250
|
this.sendTip(ws, sessionId, "auto_mode_fallback_default", createdSession);
|
|
3139
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();
|
|
3140
3256
|
});
|
|
3141
3257
|
this.debugEvents.set(sessionId, []);
|
|
3142
3258
|
this.recordDebugEvent(sessionId, {
|
|
@@ -3148,6 +3264,18 @@ export class BridgeWebSocketServer {
|
|
|
3148
3264
|
this.projectHistory?.addProject(resumeProjectPath);
|
|
3149
3265
|
})
|
|
3150
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
|
+
}
|
|
3151
3279
|
this.send(ws, {
|
|
3152
3280
|
type: "error",
|
|
3153
3281
|
message: `Failed to load session history: ${err}`,
|
|
@@ -3385,8 +3513,16 @@ export class BridgeWebSocketServer {
|
|
|
3385
3513
|
}
|
|
3386
3514
|
void (async () => {
|
|
3387
3515
|
try {
|
|
3388
|
-
const
|
|
3389
|
-
|
|
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
|
+
});
|
|
3390
3526
|
}
|
|
3391
3527
|
catch (err) {
|
|
3392
3528
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4057,6 +4193,7 @@ export class BridgeWebSocketServer {
|
|
|
4057
4193
|
else if (msg.mode === "conversation") {
|
|
4058
4194
|
// Conversation-only rewind: restart session at the target UUID
|
|
4059
4195
|
try {
|
|
4196
|
+
this.flushSessionDeltaBatches(msg.sessionId);
|
|
4060
4197
|
this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
|
|
4061
4198
|
this.send(ws, {
|
|
4062
4199
|
type: "rewind_result",
|
|
@@ -4098,6 +4235,7 @@ export class BridgeWebSocketServer {
|
|
|
4098
4235
|
return;
|
|
4099
4236
|
}
|
|
4100
4237
|
try {
|
|
4238
|
+
this.flushSessionDeltaBatches(msg.sessionId);
|
|
4101
4239
|
this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
|
|
4102
4240
|
this.send(ws, {
|
|
4103
4241
|
type: "rewind_result",
|
|
@@ -4446,6 +4584,10 @@ export class BridgeWebSocketServer {
|
|
|
4446
4584
|
}
|
|
4447
4585
|
}
|
|
4448
4586
|
}
|
|
4587
|
+
clearPendingClaudeResumeInputs(ws) {
|
|
4588
|
+
this.pendingClaudeResumeInputs.get(ws)?.clear();
|
|
4589
|
+
this.pendingClaudeResumeInputs.delete(ws);
|
|
4590
|
+
}
|
|
4449
4591
|
/**
|
|
4450
4592
|
* Load the saved session name from CLI storage and set it on the SessionInfo.
|
|
4451
4593
|
* Called after SessionManager.create() so that session_created carries the name.
|
|
@@ -4588,6 +4730,121 @@ export class BridgeWebSocketServer {
|
|
|
4588
4730
|
});
|
|
4589
4731
|
}
|
|
4590
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) {
|
|
4591
4848
|
this.maybeSendPushNotification(sessionId, msg);
|
|
4592
4849
|
this.recordDebugEvent(sessionId, {
|
|
4593
4850
|
direction: "outgoing",
|
|
@@ -4610,7 +4867,8 @@ export class BridgeWebSocketServer {
|
|
|
4610
4867
|
});
|
|
4611
4868
|
}
|
|
4612
4869
|
}
|
|
4613
|
-
|
|
4870
|
+
}
|
|
4871
|
+
broadcastSessionMessageNow(sessionId, msg, exclude) {
|
|
4614
4872
|
const data = JSON.stringify({ ...msg, sessionId });
|
|
4615
4873
|
for (const client of this.wss.clients) {
|
|
4616
4874
|
if (client === exclude)
|
|
@@ -4687,28 +4945,58 @@ export class BridgeWebSocketServer {
|
|
|
4687
4945
|
});
|
|
4688
4946
|
}
|
|
4689
4947
|
}
|
|
4690
|
-
async
|
|
4691
|
-
if (this.
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
.then((models) => {
|
|
4695
|
-
if (models.length > 0) {
|
|
4696
|
-
this.applyCodexModels(models);
|
|
4697
|
-
}
|
|
4698
|
-
else {
|
|
4699
|
-
this.applyFallbackCodexModels();
|
|
4948
|
+
async refreshCodexMetadata(projectPath) {
|
|
4949
|
+
if (this.codexMetadataRequest) {
|
|
4950
|
+
if (projectPath) {
|
|
4951
|
+
return this.codexMetadataRequest.then(() => this.refreshCodexMetadata(projectPath));
|
|
4700
4952
|
}
|
|
4701
|
-
this.
|
|
4702
|
-
}
|
|
4953
|
+
return this.codexMetadataRequest;
|
|
4954
|
+
}
|
|
4955
|
+
this.codexMetadataRequest = this.loadAndApplyCodexMetadata(projectPath)
|
|
4703
4956
|
.catch((err) => {
|
|
4704
|
-
console.warn(`[ws] Failed to load Codex
|
|
4957
|
+
console.warn(`[ws] Failed to load Codex metadata: ${err}`);
|
|
4958
|
+
this.codexProfiles = [];
|
|
4959
|
+
this.defaultCodexProfile = undefined;
|
|
4705
4960
|
this.applyFallbackCodexModels();
|
|
4706
4961
|
this.broadcastSessionList();
|
|
4707
4962
|
})
|
|
4708
4963
|
.finally(() => {
|
|
4709
|
-
this.
|
|
4964
|
+
this.codexMetadataRequest = null;
|
|
4710
4965
|
});
|
|
4711
|
-
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
|
+
}
|
|
4712
5000
|
}
|
|
4713
5001
|
async refreshClaudeModels(projectPath) {
|
|
4714
5002
|
if (this.claudeModelsRequest)
|
|
@@ -4752,28 +5040,18 @@ export class BridgeWebSocketServer {
|
|
|
4752
5040
|
this.claudeModels = FALLBACK_CLAUDE_MODELS;
|
|
4753
5041
|
this.claudeModelEfforts = { ...FALLBACK_CLAUDE_MODEL_EFFORTS };
|
|
4754
5042
|
}
|
|
4755
|
-
async
|
|
4756
|
-
const
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
try {
|
|
4760
|
-
const modelSource = process;
|
|
4761
|
-
if (typeof modelSource.listAvailableModelMetadata === "function") {
|
|
4762
|
-
return await modelSource.listAvailableModelMetadata();
|
|
4763
|
-
}
|
|
4764
|
-
const models = typeof modelSource.listAvailableModels === "function"
|
|
4765
|
-
? await modelSource.listAvailableModels()
|
|
4766
|
-
: [];
|
|
4767
|
-
return models.map((model) => ({
|
|
4768
|
-
model,
|
|
4769
|
-
supportedReasoningEfforts: FALLBACK_CODEX_REASONING_EFFORTS,
|
|
4770
|
-
}));
|
|
4771
|
-
}
|
|
4772
|
-
finally {
|
|
4773
|
-
if (isStandalone) {
|
|
4774
|
-
process.stop();
|
|
4775
|
-
}
|
|
5043
|
+
async readCodexModels(codexProcess) {
|
|
5044
|
+
const modelSource = codexProcess;
|
|
5045
|
+
if (typeof modelSource.listAvailableModelMetadata === "function") {
|
|
5046
|
+
return modelSource.listAvailableModelMetadata();
|
|
4776
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
|
+
}));
|
|
4777
5055
|
}
|
|
4778
5056
|
applyCodexModels(models) {
|
|
4779
5057
|
this.codexModels = models.map((model) => model.model);
|
|
@@ -4791,25 +5069,6 @@ export class BridgeWebSocketServer {
|
|
|
4791
5069
|
FALLBACK_CODEX_REASONING_EFFORTS,
|
|
4792
5070
|
]));
|
|
4793
5071
|
}
|
|
4794
|
-
async refreshCodexProfiles(projectPath) {
|
|
4795
|
-
if (this.codexProfilesRequest)
|
|
4796
|
-
return this.codexProfilesRequest;
|
|
4797
|
-
this.codexProfilesRequest = this.loadCodexProfiles(projectPath)
|
|
4798
|
-
.then(({ profiles, defaultProfile }) => {
|
|
4799
|
-
this.codexProfiles = profiles;
|
|
4800
|
-
this.defaultCodexProfile = defaultProfile;
|
|
4801
|
-
this.broadcastSessionList();
|
|
4802
|
-
})
|
|
4803
|
-
.catch((err) => {
|
|
4804
|
-
console.warn(`[ws] Failed to load Codex profiles: ${err}`);
|
|
4805
|
-
this.codexProfiles = [];
|
|
4806
|
-
this.defaultCodexProfile = undefined;
|
|
4807
|
-
})
|
|
4808
|
-
.finally(() => {
|
|
4809
|
-
this.codexProfilesRequest = null;
|
|
4810
|
-
});
|
|
4811
|
-
return this.codexProfilesRequest;
|
|
4812
|
-
}
|
|
4813
5072
|
async loadCodexProfiles(projectPath) {
|
|
4814
5073
|
const process = this.getActiveCodexProcess() ??
|
|
4815
5074
|
(await this.createStandaloneCodexProcess(projectPath));
|
|
@@ -4921,8 +5180,14 @@ export class BridgeWebSocketServer {
|
|
|
4921
5180
|
}
|
|
4922
5181
|
async createStandaloneCodexProcess(projectPath) {
|
|
4923
5182
|
const proc = new CodexProcess();
|
|
4924
|
-
|
|
4925
|
-
|
|
5183
|
+
try {
|
|
5184
|
+
await proc.initializeOnly(projectPath ?? process.cwd());
|
|
5185
|
+
return proc;
|
|
5186
|
+
}
|
|
5187
|
+
catch (err) {
|
|
5188
|
+
proc.stop();
|
|
5189
|
+
throw err;
|
|
5190
|
+
}
|
|
4926
5191
|
}
|
|
4927
5192
|
/** Extract a short project label from the full projectPath (last directory name). */
|
|
4928
5193
|
projectLabel(sessionId) {
|