@ccpocket/bridge 1.42.0 → 1.43.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/websocket.js CHANGED
@@ -8,7 +8,7 @@ import { SessionManager, } from "./session.js";
8
8
  import { SdkProcess } from "./sdk-process.js";
9
9
  import { CodexProcess } from "./codex-process.js";
10
10
  import { parseClientMessage, } from "./parser.js";
11
- import { getAllRecentSessions, getCodexSessionHistory, getSessionHistory, findSessionsByClaudeIds, extractMessageImages, getClaudeSessionName, loadCodexSessionNames, renameClaudeSession, renameCodexSession, } from "./sessions-index.js";
11
+ import { getAllRecentSessions, getCodexSessionHistory, getSessionHistory, findSessionsByClaudeIds, extractMessageImages, getClaudeSessionName, loadCodexSessionNames, renameClaudeSession, renameCodexSession, saveCodexSessionProfile, } from "./sessions-index.js";
12
12
  import { ArchiveStore } from "./archive-store.js";
13
13
  import { WorktreeStore } from "./worktree-store.js";
14
14
  import { listWorktrees, removeWorktree, worktreeExists, getMainBranch, } from "./worktree.js";
@@ -20,7 +20,7 @@ import { PushRelayClient } from "./push-relay.js";
20
20
  import { normalizePushLocale, t } from "./push-i18n.js";
21
21
  import { fetchAllUsage } from "./usage.js";
22
22
  import { getPackageVersion } from "./version.js";
23
- import { isPathWithinAllowedDirectory, resolvePlatformPath, } from "./path-utils.js";
23
+ import { isPathWithinAllowedDirectory, resolvePlatformPath, resolvePlatformPathFrom, } from "./path-utils.js";
24
24
  // ---- Available model lists (delivered to clients via session_list) ----
25
25
  const CLAUDE_MODELS = [
26
26
  "claude-opus-4-7",
@@ -256,6 +256,25 @@ export class BridgeWebSocketServer {
256
256
  errorCode: "path_not_allowed",
257
257
  };
258
258
  }
259
+ normalizeAdditionalWritableRoots(roots, projectPath) {
260
+ if (!roots || roots.length === 0)
261
+ return {};
262
+ const normalized = new Map();
263
+ for (const root of roots) {
264
+ const trimmed = root.trim();
265
+ if (!trimmed)
266
+ continue;
267
+ const resolved = resolvePlatformPathFrom(projectPath, trimmed, this.platform);
268
+ if (!this.isPathAllowed(resolved)) {
269
+ return { deniedRoot: root };
270
+ }
271
+ const key = this.platform === "win32" ? resolved.toLowerCase() : resolved;
272
+ if (!normalized.has(key)) {
273
+ normalized.set(key, resolved);
274
+ }
275
+ }
276
+ return { roots: [...normalized.values()] };
277
+ }
259
278
  buildSessionCreatedMessage(params) {
260
279
  const { sessionId, provider, projectPath, session, permissionMode, executionMode, planMode, approvalsReviewer, sandboxMode, slashCommands, skills, skillMetadata, apps, appMetadata, sourceSessionId, } = params;
261
280
  const msg = {
@@ -354,6 +373,10 @@ export class BridgeWebSocketServer {
354
373
  if (session.codexSettings.webSearchMode !== undefined) {
355
374
  msg.webSearchMode = session.codexSettings.webSearchMode;
356
375
  }
376
+ if (session.codexSettings.additionalWritableRoots !== undefined) {
377
+ msg.additionalWritableRoots =
378
+ session.codexSettings.additionalWritableRoots;
379
+ }
357
380
  }
358
381
  return msg;
359
382
  }
@@ -508,6 +531,13 @@ export class BridgeWebSocketServer {
508
531
  break;
509
532
  }
510
533
  }
534
+ const additionalWritableRoots = provider === "codex"
535
+ ? this.normalizeAdditionalWritableRoots(msg.additionalWritableRoots, projectPath)
536
+ : {};
537
+ if (additionalWritableRoots.deniedRoot) {
538
+ this.send(ws, this.buildPathNotAllowedError(additionalWritableRoots.deniedRoot));
539
+ break;
540
+ }
511
541
  const cached = provider === "claude"
512
542
  ? this.sessionManager.getCachedCommands(projectPath)
513
543
  : undefined;
@@ -553,6 +583,7 @@ export class BridgeWebSocketServer {
553
583
  networkAccessEnabled: msg.networkAccessEnabled,
554
584
  webSearchMode: msg.webSearchMode ??
555
585
  undefined,
586
+ additionalWritableRoots: additionalWritableRoots.roots,
556
587
  threadId: msg.sessionId,
557
588
  collaborationMode: planMode
558
589
  ? "plan"
@@ -1662,16 +1693,16 @@ export class BridgeWebSocketServer {
1662
1693
  // Resume flow: keep past history in SessionInfo and deliver it only
1663
1694
  // via get_history(sessionId) to avoid duplicate/missed replay races.
1664
1695
  if (provider === "codex") {
1665
- if (msg.profile &&
1666
- !(await this.validateCodexProfile(msg.profile, resumeProjectPath))) {
1667
- this.send(ws, {
1668
- type: "error",
1669
- message: `Codex profile not found: ${msg.profile}`,
1670
- });
1671
- break;
1672
- }
1673
1696
  const wtMapping = this.worktreeStore.get(sessionRefId);
1674
1697
  const effectiveProjectPath = resolvePlatformPath(wtMapping?.projectPath ?? resumeProjectPath, this.platform);
1698
+ const effectiveProfile = msg.profile
1699
+ ? await this.resolveCodexResumeProfile(msg.profile, sessionRefId, effectiveProjectPath)
1700
+ : undefined;
1701
+ const additionalWritableRoots = this.normalizeAdditionalWritableRoots(msg.additionalWritableRoots, effectiveProjectPath);
1702
+ if (additionalWritableRoots.deniedRoot) {
1703
+ this.send(ws, this.buildPathNotAllowedError(additionalWritableRoots.deniedRoot));
1704
+ break;
1705
+ }
1675
1706
  let worktreeOpts;
1676
1707
  if (wtMapping) {
1677
1708
  if (worktreeExists(wtMapping.worktreePath)) {
@@ -1691,7 +1722,7 @@ export class BridgeWebSocketServer {
1691
1722
  .then((pastMessages) => {
1692
1723
  const sessionId = this.sessionManager.create(effectiveProjectPath, undefined, pastMessages, worktreeOpts, "codex", {
1693
1724
  threadId: sessionRefId,
1694
- profile: msg.profile,
1725
+ profile: effectiveProfile,
1695
1726
  approvalPolicy: codexApprovalPolicy ??
1696
1727
  normalizeCodexApprovalPolicy(executionMode === "fullAccess" ? "never" : "on-request"),
1697
1728
  approvalsReviewer: msg.approvalsReviewer,
@@ -1701,6 +1732,7 @@ export class BridgeWebSocketServer {
1701
1732
  networkAccessEnabled: msg.networkAccessEnabled,
1702
1733
  webSearchMode: msg.webSearchMode ??
1703
1734
  undefined,
1735
+ additionalWritableRoots: additionalWritableRoots.roots,
1704
1736
  collaborationMode: planMode
1705
1737
  ? "plan"
1706
1738
  : "default",
@@ -3075,6 +3107,26 @@ export class BridgeWebSocketServer {
3075
3107
  this.defaultCodexProfile = snapshot.defaultProfile;
3076
3108
  return snapshot.profiles.includes(profile);
3077
3109
  }
3110
+ async resolveCodexResumeProfile(requestedProfile, threadId, projectPath) {
3111
+ const snapshot = await this.loadCodexProfiles(projectPath);
3112
+ this.codexProfiles = snapshot.profiles;
3113
+ this.defaultCodexProfile = snapshot.defaultProfile;
3114
+ if (snapshot.profiles.includes(requestedProfile)) {
3115
+ return requestedProfile;
3116
+ }
3117
+ const fallbackProfile = snapshot.defaultProfile &&
3118
+ snapshot.profiles.includes(snapshot.defaultProfile)
3119
+ ? snapshot.defaultProfile
3120
+ : undefined;
3121
+ console.warn(`[ws] Codex profile not found on resume: ${requestedProfile}; ` +
3122
+ (fallbackProfile
3123
+ ? `falling back to default profile: ${fallbackProfile}`
3124
+ : "falling back to Codex config default"));
3125
+ saveCodexSessionProfile(threadId, fallbackProfile ?? null).catch((err) => {
3126
+ console.warn(`[ws] Failed to update Codex session profile cache: ${err}`);
3127
+ });
3128
+ return fallbackProfile;
3129
+ }
3078
3130
  getActiveCodexProcess() {
3079
3131
  const summary = this.sessionManager
3080
3132
  .list()
@@ -3802,6 +3854,10 @@ export class BridgeWebSocketServer {
3802
3854
  if (session.codexSettings.webSearchMode !== undefined) {
3803
3855
  msg.webSearchMode = session.codexSettings.webSearchMode;
3804
3856
  }
3857
+ if (session.codexSettings.additionalWritableRoots !== undefined) {
3858
+ msg.additionalWritableRoots =
3859
+ session.codexSettings.additionalWritableRoots;
3860
+ }
3805
3861
  }
3806
3862
  return msg;
3807
3863
  }