@ccpocket/bridge 0.2.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +0 -0
- package/dist/codex-process.d.ts +74 -6
- package/dist/codex-process.js +1138 -205
- package/dist/codex-process.js.map +1 -1
- package/dist/parser.d.ts +13 -2
- package/dist/parser.js +4 -0
- package/dist/parser.js.map +1 -1
- package/dist/session.d.ts +9 -0
- package/dist/session.js +23 -4
- package/dist/session.js.map +1 -1
- package/dist/sessions-index.d.ts +21 -0
- package/dist/sessions-index.js +118 -1
- package/dist/sessions-index.js.map +1 -1
- package/dist/usage.js +102 -7
- package/dist/usage.js.map +1 -1
- package/dist/websocket.d.ts +12 -0
- package/dist/websocket.js +166 -74
- package/dist/websocket.js.map +1 -1
- package/package.json +4 -1
- package/dist/claude-process.d.ts +0 -108
- package/dist/claude-process.js +0 -471
- package/dist/claude-process.js.map +0 -1
package/dist/websocket.js
CHANGED
|
@@ -4,7 +4,7 @@ import { WebSocketServer, WebSocket } from "ws";
|
|
|
4
4
|
import { SessionManager } from "./session.js";
|
|
5
5
|
import { SdkProcess } from "./sdk-process.js";
|
|
6
6
|
import { parseClientMessage } from "./parser.js";
|
|
7
|
-
import { getAllRecentSessions, getCodexSessionHistory, getSessionHistory, findSessionsByClaudeIds, extractMessageImages } from "./sessions-index.js";
|
|
7
|
+
import { getAllRecentSessions, getCodexSessionHistory, getSessionHistory, findSessionsByClaudeIds, extractMessageImages, getClaudeSessionName, loadCodexSessionNames, renameClaudeSession, renameCodexSession } from "./sessions-index.js";
|
|
8
8
|
import { WorktreeStore } from "./worktree-store.js";
|
|
9
9
|
import { listWorktrees, removeWorktree, worktreeExists } from "./worktree.js";
|
|
10
10
|
import { listWindows, takeScreenshot } from "./screenshot.js";
|
|
@@ -13,6 +13,16 @@ import { RecordingStore } from "./recording-store.js";
|
|
|
13
13
|
import { PushRelayClient } from "./push-relay.js";
|
|
14
14
|
import { normalizePushLocale, t } from "./push-i18n.js";
|
|
15
15
|
import { fetchAllUsage } from "./usage.js";
|
|
16
|
+
// ---- Codex mode mapping helpers ----
|
|
17
|
+
/** Map unified PermissionMode to Codex approval_policy.
|
|
18
|
+
* Only "bypassPermissions" maps to "never"; all others use "on-request". */
|
|
19
|
+
function permissionModeToApprovalPolicy(mode) {
|
|
20
|
+
return mode === "bypassPermissions" ? "never" : "on-request";
|
|
21
|
+
}
|
|
22
|
+
/** Map simplified SandboxMode (on/off) to Codex internal sandbox mode. */
|
|
23
|
+
function sandboxModeToInternal(mode) {
|
|
24
|
+
return mode === "off" ? "danger-full-access" : "workspace-write";
|
|
25
|
+
}
|
|
16
26
|
export class BridgeWebSocketServer {
|
|
17
27
|
static MAX_DEBUG_EVENTS = 800;
|
|
18
28
|
static MAX_HISTORY_SUMMARY_ITEMS = 300;
|
|
@@ -135,6 +145,9 @@ export class BridgeWebSocketServer {
|
|
|
135
145
|
switch (msg.type) {
|
|
136
146
|
case "start": {
|
|
137
147
|
const provider = msg.provider ?? "claude";
|
|
148
|
+
if (provider === "codex") {
|
|
149
|
+
console.log(`[ws] start(codex): permissionMode=${msg.permissionMode} → collaboration=${msg.permissionMode === "plan" ? "plan" : "default"}`);
|
|
150
|
+
}
|
|
138
151
|
const cached = provider === "claude" ? this.sessionManager.getCachedCommands(msg.projectPath) : undefined;
|
|
139
152
|
const sessionId = this.sessionManager.create(msg.projectPath, {
|
|
140
153
|
sessionId: msg.sessionId,
|
|
@@ -153,29 +166,34 @@ export class BridgeWebSocketServer {
|
|
|
153
166
|
existingWorktreePath: msg.existingWorktreePath,
|
|
154
167
|
}, provider, provider === "codex"
|
|
155
168
|
? {
|
|
156
|
-
approvalPolicy: msg.
|
|
157
|
-
sandboxMode: msg.sandboxMode
|
|
169
|
+
approvalPolicy: permissionModeToApprovalPolicy(msg.permissionMode),
|
|
170
|
+
sandboxMode: sandboxModeToInternal(msg.sandboxMode),
|
|
158
171
|
model: msg.model,
|
|
159
172
|
modelReasoningEffort: msg.modelReasoningEffort ?? undefined,
|
|
160
173
|
networkAccessEnabled: msg.networkAccessEnabled,
|
|
161
174
|
webSearchMode: msg.webSearchMode ?? undefined,
|
|
162
175
|
threadId: msg.sessionId,
|
|
176
|
+
collaborationMode: msg.permissionMode === "plan" ? "plan" : "default",
|
|
163
177
|
}
|
|
164
178
|
: undefined);
|
|
165
179
|
const createdSession = this.sessionManager.get(sessionId);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
// Load saved session name from CLI storage (for resumed sessions)
|
|
181
|
+
void this.loadAndSetSessionName(createdSession, provider, msg.projectPath, msg.sessionId).then(() => {
|
|
182
|
+
this.send(ws, {
|
|
183
|
+
type: "system",
|
|
184
|
+
subtype: "session_created",
|
|
185
|
+
sessionId,
|
|
186
|
+
provider,
|
|
187
|
+
projectPath: msg.projectPath,
|
|
188
|
+
...(provider === "claude" && msg.permissionMode ? { permissionMode: msg.permissionMode } : {}),
|
|
189
|
+
...(provider === "codex" && msg.sandboxMode ? { sandboxMode: msg.sandboxMode } : {}),
|
|
190
|
+
...(cached ? { slashCommands: cached.slashCommands, skills: cached.skills } : {}),
|
|
191
|
+
...(createdSession?.worktreePath ? {
|
|
192
|
+
worktreePath: createdSession.worktreePath,
|
|
193
|
+
worktreeBranch: createdSession.worktreeBranch,
|
|
194
|
+
} : {}),
|
|
195
|
+
});
|
|
196
|
+
this.broadcastSessionList();
|
|
179
197
|
});
|
|
180
198
|
this.debugEvents.set(sessionId, []);
|
|
181
199
|
this.recordDebugEvent(sessionId, {
|
|
@@ -326,11 +344,13 @@ export class BridgeWebSocketServer {
|
|
|
326
344
|
return;
|
|
327
345
|
}
|
|
328
346
|
if (session.provider === "codex") {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
});
|
|
333
|
-
|
|
347
|
+
const codexProcess = session.process;
|
|
348
|
+
const approval = permissionModeToApprovalPolicy(msg.mode);
|
|
349
|
+
const collaboration = msg.mode === "plan" ? "plan" : "default";
|
|
350
|
+
console.log(`[ws] set_permission_mode(codex): mode=${msg.mode} → approval=${approval}, collaboration=${collaboration}`);
|
|
351
|
+
codexProcess.setApprovalPolicy(approval);
|
|
352
|
+
codexProcess.setCollaborationMode(collaboration);
|
|
353
|
+
break;
|
|
334
354
|
}
|
|
335
355
|
session.process.setPermissionMode(msg.mode).catch((err) => {
|
|
336
356
|
this.send(ws, {
|
|
@@ -350,32 +370,23 @@ export class BridgeWebSocketServer {
|
|
|
350
370
|
this.send(ws, { type: "error", message: "Only Codex sessions support sandbox mode changes" });
|
|
351
371
|
return;
|
|
352
372
|
}
|
|
353
|
-
|
|
354
|
-
if (
|
|
373
|
+
// Map on/off to internal Codex sandbox modes
|
|
374
|
+
if (msg.sandboxMode !== "on" && msg.sandboxMode !== "off") {
|
|
355
375
|
this.send(ws, { type: "error", message: `Invalid sandbox mode: ${msg.sandboxMode}` });
|
|
356
376
|
return;
|
|
357
377
|
}
|
|
358
|
-
const newSandboxMode = msg.sandboxMode;
|
|
359
|
-
// Update stored settings
|
|
378
|
+
const newSandboxMode = sandboxModeToInternal(msg.sandboxMode);
|
|
379
|
+
// Update stored settings.
|
|
380
|
+
// Note: Codex app-server does not reliably support resuming an existing
|
|
381
|
+
// thread with a different sandbox mode. Restarting here can terminate
|
|
382
|
+
// the process (`codex app-server exited`) and leave the session in a
|
|
383
|
+
// broken state. We therefore persist the preference and keep the
|
|
384
|
+
// current runtime alive.
|
|
360
385
|
if (!session.codexSettings) {
|
|
361
386
|
session.codexSettings = {};
|
|
362
387
|
}
|
|
363
388
|
session.codexSettings.sandboxMode = newSandboxMode;
|
|
364
|
-
|
|
365
|
-
const codexProc = session.process;
|
|
366
|
-
const threadId = session.claudeSessionId ?? undefined;
|
|
367
|
-
const effectiveCwd = session.worktreePath ?? session.projectPath;
|
|
368
|
-
codexProc.stop();
|
|
369
|
-
codexProc.start(effectiveCwd, {
|
|
370
|
-
approvalPolicy: session.codexSettings.approvalPolicy ?? undefined,
|
|
371
|
-
sandboxMode: newSandboxMode,
|
|
372
|
-
model: session.codexSettings.model,
|
|
373
|
-
modelReasoningEffort: session.codexSettings.modelReasoningEffort ?? undefined,
|
|
374
|
-
networkAccessEnabled: session.codexSettings.networkAccessEnabled,
|
|
375
|
-
webSearchMode: session.codexSettings.webSearchMode ?? undefined,
|
|
376
|
-
threadId,
|
|
377
|
-
});
|
|
378
|
-
console.log(`[ws] Sandbox mode changed to ${newSandboxMode} for session ${session.id} (thread restart)`);
|
|
389
|
+
console.log(`[ws] Sandbox mode changed to ${newSandboxMode} for session ${session.id} (deferred apply)`);
|
|
379
390
|
break;
|
|
380
391
|
}
|
|
381
392
|
case "approve": {
|
|
@@ -385,8 +396,8 @@ export class BridgeWebSocketServer {
|
|
|
385
396
|
return;
|
|
386
397
|
}
|
|
387
398
|
if (session.provider === "codex") {
|
|
388
|
-
|
|
389
|
-
|
|
399
|
+
session.process.approve(msg.id, msg.updatedInput);
|
|
400
|
+
break;
|
|
390
401
|
}
|
|
391
402
|
const sdkProc = session.process;
|
|
392
403
|
if (msg.clearContext) {
|
|
@@ -447,8 +458,8 @@ export class BridgeWebSocketServer {
|
|
|
447
458
|
return;
|
|
448
459
|
}
|
|
449
460
|
if (session.provider === "codex") {
|
|
450
|
-
|
|
451
|
-
|
|
461
|
+
session.process.approveAlways(msg.id);
|
|
462
|
+
break;
|
|
452
463
|
}
|
|
453
464
|
session.process.approveAlways(msg.id);
|
|
454
465
|
break;
|
|
@@ -460,8 +471,8 @@ export class BridgeWebSocketServer {
|
|
|
460
471
|
return;
|
|
461
472
|
}
|
|
462
473
|
if (session.provider === "codex") {
|
|
463
|
-
|
|
464
|
-
|
|
474
|
+
session.process.reject(msg.id, msg.message);
|
|
475
|
+
break;
|
|
465
476
|
}
|
|
466
477
|
session.process.reject(msg.id, msg.message);
|
|
467
478
|
break;
|
|
@@ -473,8 +484,8 @@ export class BridgeWebSocketServer {
|
|
|
473
484
|
return;
|
|
474
485
|
}
|
|
475
486
|
if (session.provider === "codex") {
|
|
476
|
-
|
|
477
|
-
|
|
487
|
+
session.process.answer(msg.toolUseId, msg.result);
|
|
488
|
+
break;
|
|
478
489
|
}
|
|
479
490
|
session.process.answer(msg.toolUseId, msg.result);
|
|
480
491
|
break;
|
|
@@ -629,25 +640,29 @@ export class BridgeWebSocketServer {
|
|
|
629
640
|
getCodexSessionHistory(sessionRefId).then((pastMessages) => {
|
|
630
641
|
const sessionId = this.sessionManager.create(effectiveProjectPath, undefined, pastMessages, worktreeOpts, "codex", {
|
|
631
642
|
threadId: sessionRefId,
|
|
632
|
-
approvalPolicy: msg.
|
|
633
|
-
sandboxMode: msg.sandboxMode
|
|
643
|
+
approvalPolicy: permissionModeToApprovalPolicy(msg.permissionMode),
|
|
644
|
+
sandboxMode: sandboxModeToInternal(msg.sandboxMode),
|
|
634
645
|
model: msg.model,
|
|
635
646
|
modelReasoningEffort: msg.modelReasoningEffort ?? undefined,
|
|
636
647
|
networkAccessEnabled: msg.networkAccessEnabled,
|
|
637
648
|
webSearchMode: msg.webSearchMode ?? undefined,
|
|
649
|
+
collaborationMode: msg.permissionMode === "plan" ? "plan" : "default",
|
|
638
650
|
});
|
|
639
651
|
const createdSession = this.sessionManager.get(sessionId);
|
|
640
|
-
this.
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
worktreePath
|
|
649
|
-
|
|
650
|
-
|
|
652
|
+
void this.loadAndSetSessionName(createdSession, "codex", effectiveProjectPath, sessionRefId).then(() => {
|
|
653
|
+
this.send(ws, {
|
|
654
|
+
type: "system",
|
|
655
|
+
subtype: "session_created",
|
|
656
|
+
sessionId,
|
|
657
|
+
provider: "codex",
|
|
658
|
+
projectPath: effectiveProjectPath,
|
|
659
|
+
...(createdSession?.codexSettings?.sandboxMode ? { sandboxMode: createdSession.codexSettings.sandboxMode } : {}),
|
|
660
|
+
...(createdSession?.worktreePath ? {
|
|
661
|
+
worktreePath: createdSession.worktreePath,
|
|
662
|
+
worktreeBranch: createdSession.worktreeBranch,
|
|
663
|
+
} : {}),
|
|
664
|
+
});
|
|
665
|
+
this.broadcastSessionList();
|
|
651
666
|
});
|
|
652
667
|
this.debugEvents.set(sessionId, []);
|
|
653
668
|
this.recordDebugEvent(sessionId, {
|
|
@@ -693,19 +708,22 @@ export class BridgeWebSocketServer {
|
|
|
693
708
|
persistSession: msg.persistSession,
|
|
694
709
|
}, pastMessages, worktreeOpts);
|
|
695
710
|
const createdSession = this.sessionManager.get(sessionId);
|
|
696
|
-
this.
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
worktreePath
|
|
707
|
-
|
|
708
|
-
|
|
711
|
+
void this.loadAndSetSessionName(createdSession, "claude", msg.projectPath, claudeSessionId).then(() => {
|
|
712
|
+
this.send(ws, {
|
|
713
|
+
type: "system",
|
|
714
|
+
subtype: "session_created",
|
|
715
|
+
sessionId,
|
|
716
|
+
claudeSessionId,
|
|
717
|
+
provider: "claude",
|
|
718
|
+
projectPath: msg.projectPath,
|
|
719
|
+
...(msg.permissionMode ? { permissionMode: msg.permissionMode } : {}),
|
|
720
|
+
...(cached ? { slashCommands: cached.slashCommands, skills: cached.skills } : {}),
|
|
721
|
+
...(createdSession?.worktreePath ? {
|
|
722
|
+
worktreePath: createdSession.worktreePath,
|
|
723
|
+
worktreeBranch: createdSession.worktreeBranch,
|
|
724
|
+
} : {}),
|
|
725
|
+
});
|
|
726
|
+
this.broadcastSessionList();
|
|
709
727
|
});
|
|
710
728
|
this.debugEvents.set(sessionId, []);
|
|
711
729
|
this.recordDebugEvent(sessionId, {
|
|
@@ -1083,7 +1101,81 @@ export class BridgeWebSocketServer {
|
|
|
1083
1101
|
});
|
|
1084
1102
|
break;
|
|
1085
1103
|
}
|
|
1104
|
+
case "rename_session": {
|
|
1105
|
+
const name = msg.name || null;
|
|
1106
|
+
this.handleRenameSession(ws, msg.sessionId, name, msg);
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Load the saved session name from CLI storage and set it on the SessionInfo.
|
|
1113
|
+
* Called after SessionManager.create() so that session_created carries the name.
|
|
1114
|
+
*/
|
|
1115
|
+
async loadAndSetSessionName(session, provider, projectPath, cliSessionId) {
|
|
1116
|
+
if (!session || !cliSessionId)
|
|
1117
|
+
return;
|
|
1118
|
+
try {
|
|
1119
|
+
if (provider === "claude") {
|
|
1120
|
+
const name = await getClaudeSessionName(projectPath, cliSessionId);
|
|
1121
|
+
if (name)
|
|
1122
|
+
session.name = name;
|
|
1123
|
+
}
|
|
1124
|
+
else if (provider === "codex") {
|
|
1125
|
+
const names = await loadCodexSessionNames();
|
|
1126
|
+
const name = names.get(cliSessionId);
|
|
1127
|
+
if (name)
|
|
1128
|
+
session.name = name;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
catch {
|
|
1132
|
+
// Non-critical: session works without name
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Handle rename_session: update in-memory name and persist to CLI storage.
|
|
1137
|
+
*
|
|
1138
|
+
* Supports both running sessions (by bridge session id) and recent sessions
|
|
1139
|
+
* (by provider session id, i.e. claudeSessionId or codex threadId).
|
|
1140
|
+
*/
|
|
1141
|
+
async handleRenameSession(ws, sessionId, name, msg) {
|
|
1142
|
+
// 1. Try running session first
|
|
1143
|
+
const runningSession = this.sessionManager.get(sessionId);
|
|
1144
|
+
if (runningSession) {
|
|
1145
|
+
this.sessionManager.renameSession(sessionId, name);
|
|
1146
|
+
// Persist to provider storage
|
|
1147
|
+
if (runningSession.provider === "claude" && runningSession.claudeSessionId) {
|
|
1148
|
+
await renameClaudeSession(runningSession.worktreePath ?? runningSession.projectPath, runningSession.claudeSessionId, name);
|
|
1149
|
+
}
|
|
1150
|
+
else if (runningSession.provider === "codex" && runningSession.process) {
|
|
1151
|
+
try {
|
|
1152
|
+
await runningSession.process.renameThread(name ?? "");
|
|
1153
|
+
}
|
|
1154
|
+
catch (err) {
|
|
1155
|
+
console.warn(`[websocket] Failed to rename Codex thread:`, err);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
this.broadcastSessionList();
|
|
1159
|
+
this.send(ws, { type: "rename_result", sessionId, name, success: true });
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
// 2. Recent session (not running) — use provider + providerSessionId + projectPath from message
|
|
1163
|
+
const renameMsg = msg;
|
|
1164
|
+
const provider = renameMsg.provider;
|
|
1165
|
+
const providerSessionId = renameMsg.providerSessionId;
|
|
1166
|
+
const projectPath = renameMsg.projectPath;
|
|
1167
|
+
if (provider === "claude" && providerSessionId && projectPath) {
|
|
1168
|
+
const success = await renameClaudeSession(projectPath, providerSessionId, name);
|
|
1169
|
+
this.send(ws, { type: "rename_result", sessionId, name, success });
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
// For Codex recent sessions, write directly to session_index.jsonl.
|
|
1173
|
+
if (provider === "codex" && providerSessionId) {
|
|
1174
|
+
const success = await renameCodexSession(providerSessionId, name);
|
|
1175
|
+
this.send(ws, { type: "rename_result", sessionId, name, success });
|
|
1176
|
+
return;
|
|
1086
1177
|
}
|
|
1178
|
+
this.send(ws, { type: "rename_result", sessionId, name, success: false });
|
|
1087
1179
|
}
|
|
1088
1180
|
resolveSession(sessionId) {
|
|
1089
1181
|
if (sessionId)
|