@ccpocket/bridge 1.80.2 → 1.81.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/auto-rename.js +1 -0
- package/dist/auto-rename.js.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts +36 -0
- package/dist/parser.js +45 -1
- package/dist/parser.js.map +1 -1
- package/dist/sdk-process.d.ts +2 -0
- package/dist/sdk-process.js +3 -0
- package/dist/sdk-process.js.map +1 -1
- package/dist/sessions-index.d.ts +6 -0
- package/dist/sessions-index.js +3 -0
- package/dist/sessions-index.js.map +1 -1
- package/dist/websocket.d.ts +18 -0
- package/dist/websocket.js +453 -18
- package/dist/websocket.js.map +1 -1
- package/dist/workspace-store.d.ts +47 -0
- package/dist/workspace-store.js +209 -0
- package/dist/workspace-store.js.map +1 -0
- package/package.json +1 -1
package/dist/websocket.js
CHANGED
|
@@ -104,6 +104,7 @@ const OPT_IN_SERVER_MESSAGES = new Set([
|
|
|
104
104
|
"goal_state",
|
|
105
105
|
"guardian_approval",
|
|
106
106
|
"prompt_history_status",
|
|
107
|
+
"projects",
|
|
107
108
|
"push_registration_result",
|
|
108
109
|
]);
|
|
109
110
|
function isRecord(value) {
|
|
@@ -517,6 +518,8 @@ export class BridgeWebSocketServer {
|
|
|
517
518
|
uploadStore;
|
|
518
519
|
galleryStore;
|
|
519
520
|
projectHistory;
|
|
521
|
+
workspaceStore;
|
|
522
|
+
pendingSessionWorkspaces = new Map();
|
|
520
523
|
debugTraceStore;
|
|
521
524
|
recordingStore;
|
|
522
525
|
worktreeStore;
|
|
@@ -570,7 +573,7 @@ export class BridgeWebSocketServer {
|
|
|
570
573
|
pendingClaudeResumeInputs = new WeakMap();
|
|
571
574
|
resumeOperations = new Map();
|
|
572
575
|
constructor(options) {
|
|
573
|
-
const { server, apiKey, allowedDirs, imageStore, mediaStore, uploadStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, promptHistoryStore, platform, fileListMaxEntries, fileListMaxBytes, fileDownloadMaxBytes, fileUploadMaxBytes, deltaBatchMs, deltaBatchMaxChars, } = options;
|
|
576
|
+
const { server, apiKey, allowedDirs, imageStore, mediaStore, uploadStore, galleryStore, projectHistory, workspaceStore, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, promptHistoryStore, platform, fileListMaxEntries, fileListMaxBytes, fileDownloadMaxBytes, fileUploadMaxBytes, deltaBatchMs, deltaBatchMaxChars, } = options;
|
|
574
577
|
this.apiKey = apiKey ?? null;
|
|
575
578
|
this.allowedDirs = allowedDirs ?? [];
|
|
576
579
|
this.imageStore = imageStore ?? null;
|
|
@@ -578,6 +581,7 @@ export class BridgeWebSocketServer {
|
|
|
578
581
|
this.uploadStore = uploadStore ?? null;
|
|
579
582
|
this.galleryStore = galleryStore ?? null;
|
|
580
583
|
this.projectHistory = projectHistory ?? null;
|
|
584
|
+
this.workspaceStore = workspaceStore ?? null;
|
|
581
585
|
this.debugTraceStore = debugTraceStore ?? new DebugTraceStore();
|
|
582
586
|
this.recordingStore = recordingStore ?? null;
|
|
583
587
|
this.worktreeStore = new WorktreeStore();
|
|
@@ -619,7 +623,10 @@ export class BridgeWebSocketServer {
|
|
|
619
623
|
const info = this.galleryStore.metaToInfo(meta);
|
|
620
624
|
this.broadcast({ type: "gallery_new_image", image: info });
|
|
621
625
|
}
|
|
622
|
-
}, this.worktreeStore, () =>
|
|
626
|
+
}, this.worktreeStore, (sessionId) => {
|
|
627
|
+
void this.persistPendingSessionWorkspace(sessionId);
|
|
628
|
+
this.broadcastSessionList();
|
|
629
|
+
});
|
|
623
630
|
this.wss.on("connection", (ws, req) => {
|
|
624
631
|
// API key authentication
|
|
625
632
|
if (this.apiKey) {
|
|
@@ -1069,6 +1076,7 @@ export class BridgeWebSocketServer {
|
|
|
1069
1076
|
}
|
|
1070
1077
|
const projectPath = session.projectPath;
|
|
1071
1078
|
const codexSettings = session.codexSettings;
|
|
1079
|
+
const workspace = this.workspaceForRuntimeSession(session);
|
|
1072
1080
|
const worktreeOpts = session.worktreePath
|
|
1073
1081
|
? {
|
|
1074
1082
|
existingWorktreePath: session.worktreePath,
|
|
@@ -1086,6 +1094,7 @@ export class BridgeWebSocketServer {
|
|
|
1086
1094
|
...(codexSettings ?? {}),
|
|
1087
1095
|
threadId,
|
|
1088
1096
|
}));
|
|
1097
|
+
this.attachWorkspaceToRuntimeSession(newSessionId, workspace);
|
|
1089
1098
|
const newSession = this.sessionManager.get(newSessionId);
|
|
1090
1099
|
this.send(ws, {
|
|
1091
1100
|
type: "rewind_result",
|
|
@@ -1152,6 +1161,7 @@ export class BridgeWebSocketServer {
|
|
|
1152
1161
|
}
|
|
1153
1162
|
const projectPath = session.projectPath;
|
|
1154
1163
|
const codexSettings = session.codexSettings;
|
|
1164
|
+
const workspace = this.workspaceForRuntimeSession(session);
|
|
1155
1165
|
const worktreeOpts = session.worktreePath
|
|
1156
1166
|
? {
|
|
1157
1167
|
existingWorktreePath: session.worktreePath,
|
|
@@ -1174,6 +1184,7 @@ export class BridgeWebSocketServer {
|
|
|
1174
1184
|
...(codexSettings ?? {}),
|
|
1175
1185
|
threadId: forkedThreadId,
|
|
1176
1186
|
}));
|
|
1187
|
+
this.attachWorkspaceToRuntimeSession(newSessionId, workspace);
|
|
1177
1188
|
const newSession = this.sessionManager.get(newSessionId);
|
|
1178
1189
|
this.send(ws, this.buildSessionCreatedMessage({
|
|
1179
1190
|
sessionId: newSessionId,
|
|
@@ -1852,7 +1863,7 @@ export class BridgeWebSocketServer {
|
|
|
1852
1863
|
// Send session list and project history on connect
|
|
1853
1864
|
this.refreshConnectionMetadata();
|
|
1854
1865
|
this.sendSessionList(ws);
|
|
1855
|
-
const projects = this.
|
|
1866
|
+
const projects = this.legacyProjectHistoryProjects();
|
|
1856
1867
|
this.send(ws, { type: "project_history", projects });
|
|
1857
1868
|
ws.on("message", (data) => {
|
|
1858
1869
|
if (this.rejectedProtocolClients.has(ws))
|
|
@@ -1954,7 +1965,32 @@ export class BridgeWebSocketServer {
|
|
|
1954
1965
|
}
|
|
1955
1966
|
switch (msg.type) {
|
|
1956
1967
|
case "start": {
|
|
1957
|
-
|
|
1968
|
+
let projectPath = resolvePlatformPath(msg.projectPath, this.platform);
|
|
1969
|
+
let resolvedWorkspace;
|
|
1970
|
+
if (msg.projectId) {
|
|
1971
|
+
const project = this.workspaceStore?.getProject(msg.projectId);
|
|
1972
|
+
if (!project) {
|
|
1973
|
+
this.send(ws, {
|
|
1974
|
+
type: "error",
|
|
1975
|
+
requestId: msg.requestId,
|
|
1976
|
+
errorCode: "project_not_found",
|
|
1977
|
+
message: `Project not found: ${msg.projectId}`,
|
|
1978
|
+
});
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
const normalized = this.normalizeWorkspaceRoots(project.rootPaths);
|
|
1982
|
+
if (normalized.deniedRoot || !normalized.roots) {
|
|
1983
|
+
this.send(ws, this.buildPathNotAllowedError(normalized.deniedRoot ?? project.rootPaths[0]));
|
|
1984
|
+
break;
|
|
1985
|
+
}
|
|
1986
|
+
projectPath = normalized.roots[0];
|
|
1987
|
+
resolvedWorkspace = {
|
|
1988
|
+
kind: "project",
|
|
1989
|
+
projectId: project.id,
|
|
1990
|
+
projectName: project.name,
|
|
1991
|
+
rootPaths: normalized.roots,
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1958
1994
|
if (!this.isPathAllowed(projectPath)) {
|
|
1959
1995
|
this.send(ws, {
|
|
1960
1996
|
...this.buildPathNotAllowedError(msg.projectPath),
|
|
@@ -2012,9 +2048,9 @@ export class BridgeWebSocketServer {
|
|
|
2012
2048
|
break;
|
|
2013
2049
|
}
|
|
2014
2050
|
}
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2051
|
+
const requestedAdditionalRoots = resolvedWorkspace?.rootPaths.slice(1) ??
|
|
2052
|
+
msg.additionalWritableRoots;
|
|
2053
|
+
const additionalWritableRoots = this.normalizeAdditionalWritableRoots(requestedAdditionalRoots, projectPath);
|
|
2018
2054
|
if (additionalWritableRoots.deniedRoot) {
|
|
2019
2055
|
this.send(ws, {
|
|
2020
2056
|
...this.buildPathNotAllowedError(additionalWritableRoots.deniedRoot),
|
|
@@ -2041,6 +2077,7 @@ export class BridgeWebSocketServer {
|
|
|
2041
2077
|
...(msg.sandboxMode
|
|
2042
2078
|
? { sandboxEnabled: msg.sandboxMode === "on" }
|
|
2043
2079
|
: {}),
|
|
2080
|
+
additionalDirectories: additionalWritableRoots.roots,
|
|
2044
2081
|
},
|
|
2045
2082
|
worktreeOptions: {
|
|
2046
2083
|
useWorktree: msg.useWorktree,
|
|
@@ -2086,6 +2123,10 @@ export class BridgeWebSocketServer {
|
|
|
2086
2123
|
usedFallback: false,
|
|
2087
2124
|
};
|
|
2088
2125
|
const createdSession = this.sessionManager.get(sessionId);
|
|
2126
|
+
if (resolvedWorkspace) {
|
|
2127
|
+
this.pendingSessionWorkspaces.set(sessionId, resolvedWorkspace);
|
|
2128
|
+
void this.persistPendingSessionWorkspace(sessionId);
|
|
2129
|
+
}
|
|
2089
2130
|
const cached = this.sessionManager.getCachedCommands(provider, createdSession?.worktreePath ?? projectPath);
|
|
2090
2131
|
// Load saved session name from CLI storage (for resumed sessions)
|
|
2091
2132
|
void this.loadAndSetSessionName(createdSession, provider, projectPath, msg.sessionId).then(() => {
|
|
@@ -2152,7 +2193,9 @@ export class BridgeWebSocketServer {
|
|
|
2152
2193
|
projectPath,
|
|
2153
2194
|
createdAt: new Date().toISOString(),
|
|
2154
2195
|
});
|
|
2155
|
-
|
|
2196
|
+
if (!resolvedWorkspace) {
|
|
2197
|
+
this.projectHistory?.addProject(projectPath);
|
|
2198
|
+
}
|
|
2156
2199
|
}
|
|
2157
2200
|
catch (err) {
|
|
2158
2201
|
console.error(`[ws] Failed to start session:`, err);
|
|
@@ -2805,6 +2848,7 @@ export class BridgeWebSocketServer {
|
|
|
2805
2848
|
const worktreePath = session.worktreePath;
|
|
2806
2849
|
const worktreeBranch = session.worktreeBranch;
|
|
2807
2850
|
const sessionName = session.name;
|
|
2851
|
+
const workspace = this.workspaceForRuntimeSession(session);
|
|
2808
2852
|
this.destroySession(oldSessionId);
|
|
2809
2853
|
console.log(`[ws] Permission mode change: destroyed session ${oldSessionId}`);
|
|
2810
2854
|
const hasUserMessages = session.history?.some((m) => m.type === "user_input" || m.type === "assistant") ||
|
|
@@ -2822,8 +2866,10 @@ export class BridgeWebSocketServer {
|
|
|
2822
2866
|
serviceTier: oldSettings.serviceTier,
|
|
2823
2867
|
networkAccessEnabled: oldSettings.networkAccessEnabled,
|
|
2824
2868
|
webSearchMode: oldSettings.webSearchMode,
|
|
2869
|
+
additionalWritableRoots: oldSettings.additionalWritableRoots,
|
|
2825
2870
|
collaborationMode: newCollaboration,
|
|
2826
2871
|
}));
|
|
2872
|
+
this.attachWorkspaceToRuntimeSession(newId, workspace);
|
|
2827
2873
|
const newSession = this.sessionManager.get(newId);
|
|
2828
2874
|
if (newSession && sessionName)
|
|
2829
2875
|
newSession.name = sessionName;
|
|
@@ -2883,8 +2929,10 @@ export class BridgeWebSocketServer {
|
|
|
2883
2929
|
serviceTier: oldSettings.serviceTier,
|
|
2884
2930
|
networkAccessEnabled: oldSettings.networkAccessEnabled,
|
|
2885
2931
|
webSearchMode: oldSettings.webSearchMode,
|
|
2932
|
+
additionalWritableRoots: oldSettings.additionalWritableRoots,
|
|
2886
2933
|
collaborationMode: newCollaboration,
|
|
2887
2934
|
}));
|
|
2935
|
+
this.attachWorkspaceToRuntimeSession(newId, workspace);
|
|
2888
2936
|
const newSession = this.sessionManager.get(newId);
|
|
2889
2937
|
if (newSession && sessionName) {
|
|
2890
2938
|
newSession.name = sessionName;
|
|
@@ -3174,6 +3222,7 @@ export class BridgeWebSocketServer {
|
|
|
3174
3222
|
const sessionName = session.name;
|
|
3175
3223
|
const permissionMode = session.process.permissionMode;
|
|
3176
3224
|
const model = session.process.model;
|
|
3225
|
+
const workspace = this.workspaceForRuntimeSession(session);
|
|
3177
3226
|
this.destroySession(oldSessionId);
|
|
3178
3227
|
console.log(`[ws] Claude sandbox change: destroyed session ${oldSessionId}`);
|
|
3179
3228
|
const newId = this.sessionManager.create(projectPath, {
|
|
@@ -3181,9 +3230,11 @@ export class BridgeWebSocketServer {
|
|
|
3181
3230
|
permissionMode,
|
|
3182
3231
|
model,
|
|
3183
3232
|
sandboxEnabled: newEnabled,
|
|
3233
|
+
additionalDirectories: workspace?.rootPaths.slice(1),
|
|
3184
3234
|
}, undefined, worktreePath
|
|
3185
3235
|
? { existingWorktreePath: worktreePath, worktreeBranch }
|
|
3186
3236
|
: undefined, "claude");
|
|
3237
|
+
this.attachWorkspaceToRuntimeSession(newId, workspace);
|
|
3187
3238
|
const newSession = this.sessionManager.get(newId);
|
|
3188
3239
|
if (newSession && sessionName)
|
|
3189
3240
|
newSession.name = sessionName;
|
|
@@ -3227,6 +3278,7 @@ export class BridgeWebSocketServer {
|
|
|
3227
3278
|
const sessionName = session.name;
|
|
3228
3279
|
const collaborationMode = session.process
|
|
3229
3280
|
.collaborationMode;
|
|
3281
|
+
const workspace = this.workspaceForRuntimeSession(session);
|
|
3230
3282
|
const executionMode = oldSettings.approvalPolicy === "never" ? "fullAccess" : "default";
|
|
3231
3283
|
const planMode = collaborationMode === "plan";
|
|
3232
3284
|
const legacyPermissionMode = modesToLegacyPermissionMode("codex", executionMode, planMode);
|
|
@@ -3255,8 +3307,10 @@ export class BridgeWebSocketServer {
|
|
|
3255
3307
|
serviceTier: oldSettings.serviceTier,
|
|
3256
3308
|
networkAccessEnabled: oldSettings.networkAccessEnabled,
|
|
3257
3309
|
webSearchMode: oldSettings.webSearchMode,
|
|
3310
|
+
additionalWritableRoots: oldSettings.additionalWritableRoots,
|
|
3258
3311
|
collaborationMode,
|
|
3259
3312
|
}));
|
|
3313
|
+
this.attachWorkspaceToRuntimeSession(newId, workspace);
|
|
3260
3314
|
const newSession = this.sessionManager.get(newId);
|
|
3261
3315
|
if (newSession && sessionName)
|
|
3262
3316
|
newSession.name = sessionName;
|
|
@@ -3309,8 +3363,10 @@ export class BridgeWebSocketServer {
|
|
|
3309
3363
|
serviceTier: oldSettings.serviceTier,
|
|
3310
3364
|
networkAccessEnabled: oldSettings.networkAccessEnabled,
|
|
3311
3365
|
webSearchMode: oldSettings.webSearchMode,
|
|
3366
|
+
additionalWritableRoots: oldSettings.additionalWritableRoots,
|
|
3312
3367
|
collaborationMode,
|
|
3313
3368
|
}));
|
|
3369
|
+
this.attachWorkspaceToRuntimeSession(newId, workspace);
|
|
3314
3370
|
// Restore session name
|
|
3315
3371
|
const newSession = this.sessionManager.get(newId);
|
|
3316
3372
|
if (newSession && sessionName) {
|
|
@@ -3377,6 +3433,7 @@ export class BridgeWebSocketServer {
|
|
|
3377
3433
|
const permissionMode = sdkProc.permissionMode;
|
|
3378
3434
|
const worktreePath = session.worktreePath;
|
|
3379
3435
|
const worktreeBranch = session.worktreeBranch;
|
|
3436
|
+
const workspace = this.workspaceForRuntimeSession(session);
|
|
3380
3437
|
this.destroySession(sessionId);
|
|
3381
3438
|
console.log(`[ws] Clear context: destroyed session ${sessionId}`);
|
|
3382
3439
|
const newId = this.sessionManager.create(projectPath, {
|
|
@@ -3388,9 +3445,11 @@ export class BridgeWebSocketServer {
|
|
|
3388
3445
|
: {}),
|
|
3389
3446
|
permissionMode,
|
|
3390
3447
|
initialInput: planText || undefined,
|
|
3448
|
+
additionalDirectories: workspace?.rootPaths.slice(1),
|
|
3391
3449
|
}, undefined, worktreePath
|
|
3392
3450
|
? { existingWorktreePath: worktreePath, worktreeBranch }
|
|
3393
3451
|
: undefined);
|
|
3452
|
+
this.attachWorkspaceToRuntimeSession(newId, workspace);
|
|
3394
3453
|
console.log(`[ws] Clear context: created new session ${newId} (CLI session: ${claudeSessionId ?? "new"})`);
|
|
3395
3454
|
// Notify all clients. Broadcast is used so reconnecting clients also receive it.
|
|
3396
3455
|
const newSession = this.sessionManager.get(newId);
|
|
@@ -3576,7 +3635,7 @@ export class BridgeWebSocketServer {
|
|
|
3576
3635
|
this.send(ws, {
|
|
3577
3636
|
type: "session_context",
|
|
3578
3637
|
sessionId: msg.sessionId,
|
|
3579
|
-
context: { ...context },
|
|
3638
|
+
context: { ...this.runtimeSessionWithWorkspace(context) },
|
|
3580
3639
|
});
|
|
3581
3640
|
}
|
|
3582
3641
|
else {
|
|
@@ -3830,6 +3889,8 @@ export class BridgeWebSocketServer {
|
|
|
3830
3889
|
limit: msg.limit,
|
|
3831
3890
|
offset: msg.offset,
|
|
3832
3891
|
projectPath: msg.projectPath,
|
|
3892
|
+
projectId: msg.projectId,
|
|
3893
|
+
workspaceKind: msg.workspaceKind,
|
|
3833
3894
|
requestScope: msg.requestScope,
|
|
3834
3895
|
requestId: msg.requestId,
|
|
3835
3896
|
});
|
|
@@ -3844,6 +3905,8 @@ export class BridgeWebSocketServer {
|
|
|
3844
3905
|
message: `Failed to list recent sessions: ${err}`,
|
|
3845
3906
|
errorCode: "recent_sessions_failed",
|
|
3846
3907
|
path: msg.projectPath,
|
|
3908
|
+
projectId: msg.projectId,
|
|
3909
|
+
workspaceKind: msg.workspaceKind,
|
|
3847
3910
|
requestId: msg.requestId,
|
|
3848
3911
|
requestScope: msg.requestScope,
|
|
3849
3912
|
offset: msg.offset,
|
|
@@ -3904,9 +3967,41 @@ export class BridgeWebSocketServer {
|
|
|
3904
3967
|
}
|
|
3905
3968
|
case "resume_session": {
|
|
3906
3969
|
const resumeStartedAt = Date.now();
|
|
3907
|
-
console.log(`[ws] resume_session: sessionId=${msg.sessionId} projectPath=${msg.projectPath} provider=${msg.provider ?? "claude"}`);
|
|
3908
|
-
const resumeProjectPath = resolvePlatformPath(msg.projectPath, this.platform);
|
|
3909
3970
|
const provider = msg.provider ?? "claude";
|
|
3971
|
+
const storedAssignment = this.workspaceStore?.getAssignment(provider, msg.sessionId);
|
|
3972
|
+
const requestedProject = !storedAssignment && msg.projectId
|
|
3973
|
+
? this.workspaceStore?.getProject(msg.projectId)
|
|
3974
|
+
: undefined;
|
|
3975
|
+
const assignedProject = storedAssignment?.projectId
|
|
3976
|
+
? this.workspaceStore?.getProject(storedAssignment.projectId)
|
|
3977
|
+
: undefined;
|
|
3978
|
+
const resumeRoots = storedAssignment?.rootPaths ?? requestedProject?.rootPaths;
|
|
3979
|
+
console.log(`[ws] resume_session: sessionId=${msg.sessionId} projectPath=${msg.projectPath} provider=${msg.provider ?? "claude"}`);
|
|
3980
|
+
// A stored assignment supplies the root snapshot and secondary roots,
|
|
3981
|
+
// while the recent-session path may be a worktree cwd that must win.
|
|
3982
|
+
const resumeProjectPath = resolvePlatformPath(storedAssignment
|
|
3983
|
+
? msg.projectPath
|
|
3984
|
+
: (resumeRoots?.[0] ?? msg.projectPath), this.platform);
|
|
3985
|
+
const resumeAdditionalRoots = resumeRoots?.slice(1) ?? msg.additionalWritableRoots;
|
|
3986
|
+
const resolvedResumeWorkspace = storedAssignment
|
|
3987
|
+
? {
|
|
3988
|
+
kind: storedAssignment.kind,
|
|
3989
|
+
...(storedAssignment.projectId
|
|
3990
|
+
? { projectId: storedAssignment.projectId }
|
|
3991
|
+
: {}),
|
|
3992
|
+
...(assignedProject
|
|
3993
|
+
? { projectName: assignedProject.name }
|
|
3994
|
+
: {}),
|
|
3995
|
+
rootPaths: storedAssignment.rootPaths,
|
|
3996
|
+
}
|
|
3997
|
+
: requestedProject
|
|
3998
|
+
? {
|
|
3999
|
+
kind: "project",
|
|
4000
|
+
projectId: requestedProject.id,
|
|
4001
|
+
projectName: requestedProject.name,
|
|
4002
|
+
rootPaths: requestedProject.rootPaths,
|
|
4003
|
+
}
|
|
4004
|
+
: undefined;
|
|
3910
4005
|
if (!this.isPathAllowed(resumeProjectPath)) {
|
|
3911
4006
|
this.sendResumeFailed(ws, {
|
|
3912
4007
|
provider,
|
|
@@ -3917,6 +4012,13 @@ export class BridgeWebSocketServer {
|
|
|
3917
4012
|
this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
|
|
3918
4013
|
break;
|
|
3919
4014
|
}
|
|
4015
|
+
if (resolvedResumeWorkspace &&
|
|
4016
|
+
!storedAssignment &&
|
|
4017
|
+
this.workspaceStore) {
|
|
4018
|
+
void this.workspaceStore.assignSession(provider, msg.sessionId, resolvedResumeWorkspace).catch((error) => {
|
|
4019
|
+
console.error("[workspace] Failed to persist resume assignment:", error);
|
|
4020
|
+
});
|
|
4021
|
+
}
|
|
3920
4022
|
const normalizedCodexPermissionsMode = provider === "codex"
|
|
3921
4023
|
? normalizeCodexPermissionsMode(msg.codexPermissionsMode)
|
|
3922
4024
|
: undefined;
|
|
@@ -3960,7 +4062,7 @@ export class BridgeWebSocketServer {
|
|
|
3960
4062
|
const effectiveProfile = msg.profile
|
|
3961
4063
|
? await this.resolveCodexResumeProfile(msg.profile, sessionRefId, effectiveProjectPath)
|
|
3962
4064
|
: undefined;
|
|
3963
|
-
const additionalWritableRoots = this.normalizeAdditionalWritableRoots(
|
|
4065
|
+
const additionalWritableRoots = this.normalizeAdditionalWritableRoots(resumeAdditionalRoots, effectiveProjectPath);
|
|
3964
4066
|
if (additionalWritableRoots.deniedRoot) {
|
|
3965
4067
|
this.sendResumeFailed(ws, {
|
|
3966
4068
|
provider,
|
|
@@ -4098,7 +4200,9 @@ export class BridgeWebSocketServer {
|
|
|
4098
4200
|
type: "session_resumed",
|
|
4099
4201
|
detail: `provider=codex thread=${sessionRefId}`,
|
|
4100
4202
|
});
|
|
4101
|
-
|
|
4203
|
+
if (!resolvedResumeWorkspace) {
|
|
4204
|
+
this.projectHistory?.addProject(effectiveProjectPath);
|
|
4205
|
+
}
|
|
4102
4206
|
console.info(formatResumePerformanceLog({
|
|
4103
4207
|
provider: "codex",
|
|
4104
4208
|
sourceSessionId: sessionRefId,
|
|
@@ -4136,6 +4240,17 @@ export class BridgeWebSocketServer {
|
|
|
4136
4240
|
break;
|
|
4137
4241
|
}
|
|
4138
4242
|
const claudeSessionId = sessionRefId;
|
|
4243
|
+
const additionalDirectories = this.normalizeAdditionalWritableRoots(resumeAdditionalRoots, resumeProjectPath);
|
|
4244
|
+
if (additionalDirectories.deniedRoot) {
|
|
4245
|
+
this.sendResumeFailed(ws, {
|
|
4246
|
+
provider,
|
|
4247
|
+
sourceSessionId: sessionRefId,
|
|
4248
|
+
projectPath: resumeProjectPath,
|
|
4249
|
+
resumeRequestId: msg.resumeRequestId,
|
|
4250
|
+
});
|
|
4251
|
+
this.send(ws, this.buildPathNotAllowedError(additionalDirectories.deniedRoot));
|
|
4252
|
+
break;
|
|
4253
|
+
}
|
|
4139
4254
|
// Look up worktree mapping for this Claude session
|
|
4140
4255
|
const wtMapping = this.worktreeStore.get(claudeSessionId);
|
|
4141
4256
|
let worktreeOpts;
|
|
@@ -4190,6 +4305,7 @@ export class BridgeWebSocketServer {
|
|
|
4190
4305
|
...(msg.sandboxMode
|
|
4191
4306
|
? { sandboxEnabled: msg.sandboxMode === "on" }
|
|
4192
4307
|
: {}),
|
|
4308
|
+
additionalDirectories: additionalDirectories.roots,
|
|
4193
4309
|
},
|
|
4194
4310
|
pastMessages,
|
|
4195
4311
|
worktreeOptions: worktreeOpts,
|
|
@@ -4260,7 +4376,9 @@ export class BridgeWebSocketServer {
|
|
|
4260
4376
|
type: "session_resumed",
|
|
4261
4377
|
detail: `provider=claude session=${claudeSessionId}`,
|
|
4262
4378
|
});
|
|
4263
|
-
|
|
4379
|
+
if (!resolvedResumeWorkspace) {
|
|
4380
|
+
this.projectHistory?.addProject(resumeProjectPath);
|
|
4381
|
+
}
|
|
4264
4382
|
})
|
|
4265
4383
|
.catch((err) => {
|
|
4266
4384
|
if (!historyLoaded) {
|
|
@@ -4347,16 +4465,104 @@ export class BridgeWebSocketServer {
|
|
|
4347
4465
|
break;
|
|
4348
4466
|
}
|
|
4349
4467
|
case "list_project_history": {
|
|
4350
|
-
const projects = this.
|
|
4468
|
+
const projects = this.legacyProjectHistoryProjects();
|
|
4351
4469
|
this.send(ws, { type: "project_history", projects });
|
|
4352
4470
|
break;
|
|
4353
4471
|
}
|
|
4354
4472
|
case "remove_project_history": {
|
|
4355
4473
|
this.projectHistory?.removeProject(msg.projectPath);
|
|
4356
|
-
const projects = this.
|
|
4474
|
+
const projects = this.legacyProjectHistoryProjects();
|
|
4357
4475
|
this.send(ws, { type: "project_history", projects });
|
|
4358
4476
|
break;
|
|
4359
4477
|
}
|
|
4478
|
+
case "list_projects": {
|
|
4479
|
+
this.send(ws, this.projectsMessage(msg.requestId));
|
|
4480
|
+
break;
|
|
4481
|
+
}
|
|
4482
|
+
case "create_project": {
|
|
4483
|
+
if (!this.workspaceStore) {
|
|
4484
|
+
this.send(ws, {
|
|
4485
|
+
type: "error",
|
|
4486
|
+
requestId: msg.requestId,
|
|
4487
|
+
errorCode: "projects_unavailable",
|
|
4488
|
+
message: "Project storage is unavailable",
|
|
4489
|
+
});
|
|
4490
|
+
break;
|
|
4491
|
+
}
|
|
4492
|
+
const normalized = this.normalizeWorkspaceRoots(msg.rootPaths);
|
|
4493
|
+
if (normalized.deniedRoot || !normalized.roots) {
|
|
4494
|
+
this.send(ws, this.buildPathNotAllowedError(normalized.deniedRoot ?? msg.rootPaths[0]));
|
|
4495
|
+
break;
|
|
4496
|
+
}
|
|
4497
|
+
try {
|
|
4498
|
+
await this.workspaceStore.createProject(msg.name, normalized.roots);
|
|
4499
|
+
this.broadcast(this.projectsMessage(msg.requestId));
|
|
4500
|
+
}
|
|
4501
|
+
catch (error) {
|
|
4502
|
+
this.sendWorkspaceMutationError(ws, msg.requestId, "create Project", error);
|
|
4503
|
+
}
|
|
4504
|
+
break;
|
|
4505
|
+
}
|
|
4506
|
+
case "update_project": {
|
|
4507
|
+
if (!this.workspaceStore) {
|
|
4508
|
+
this.send(ws, {
|
|
4509
|
+
type: "error",
|
|
4510
|
+
requestId: msg.requestId,
|
|
4511
|
+
errorCode: "projects_unavailable",
|
|
4512
|
+
message: "Project storage is unavailable",
|
|
4513
|
+
});
|
|
4514
|
+
break;
|
|
4515
|
+
}
|
|
4516
|
+
const normalized = this.normalizeWorkspaceRoots(msg.rootPaths);
|
|
4517
|
+
if (normalized.deniedRoot || !normalized.roots) {
|
|
4518
|
+
this.send(ws, this.buildPathNotAllowedError(normalized.deniedRoot ?? msg.rootPaths[0]));
|
|
4519
|
+
break;
|
|
4520
|
+
}
|
|
4521
|
+
let updated;
|
|
4522
|
+
try {
|
|
4523
|
+
updated = await this.workspaceStore.updateProject(msg.projectId, msg.name, normalized.roots);
|
|
4524
|
+
}
|
|
4525
|
+
catch (error) {
|
|
4526
|
+
this.sendWorkspaceMutationError(ws, msg.requestId, "update Project", error);
|
|
4527
|
+
break;
|
|
4528
|
+
}
|
|
4529
|
+
if (!updated) {
|
|
4530
|
+
this.send(ws, {
|
|
4531
|
+
type: "error",
|
|
4532
|
+
requestId: msg.requestId,
|
|
4533
|
+
errorCode: "project_not_found",
|
|
4534
|
+
message: `Project not found: ${msg.projectId}`,
|
|
4535
|
+
});
|
|
4536
|
+
break;
|
|
4537
|
+
}
|
|
4538
|
+
this.broadcast(this.projectsMessage(msg.requestId));
|
|
4539
|
+
break;
|
|
4540
|
+
}
|
|
4541
|
+
case "remove_project": {
|
|
4542
|
+
if (!this.workspaceStore) {
|
|
4543
|
+
this.sendWorkspaceMutationError(ws, msg.requestId, "remove Project", new Error("Project storage is unavailable"));
|
|
4544
|
+
break;
|
|
4545
|
+
}
|
|
4546
|
+
let removed;
|
|
4547
|
+
try {
|
|
4548
|
+
removed = await this.workspaceStore.removeProject(msg.projectId);
|
|
4549
|
+
}
|
|
4550
|
+
catch (error) {
|
|
4551
|
+
this.sendWorkspaceMutationError(ws, msg.requestId, "remove Project", error);
|
|
4552
|
+
break;
|
|
4553
|
+
}
|
|
4554
|
+
if (!removed) {
|
|
4555
|
+
this.send(ws, {
|
|
4556
|
+
type: "error",
|
|
4557
|
+
requestId: msg.requestId,
|
|
4558
|
+
errorCode: "project_not_found",
|
|
4559
|
+
message: `Project not found: ${msg.projectId}`,
|
|
4560
|
+
});
|
|
4561
|
+
break;
|
|
4562
|
+
}
|
|
4563
|
+
this.broadcast(this.projectsMessage(msg.requestId));
|
|
4564
|
+
break;
|
|
4565
|
+
}
|
|
4360
4566
|
case "list_directory": {
|
|
4361
4567
|
try {
|
|
4362
4568
|
const listing = await listAllowedDirectories(msg.path, this.allowedDirs, this.platform, msg.includeHidden ?? false);
|
|
@@ -6204,7 +6410,7 @@ export class BridgeWebSocketServer {
|
|
|
6204
6410
|
}
|
|
6205
6411
|
sendSessionList(ws) {
|
|
6206
6412
|
this.pruneDebugEvents();
|
|
6207
|
-
const sessions = this.
|
|
6413
|
+
const sessions = this.runtimeSessionsWithWorkspaces();
|
|
6208
6414
|
this.send(ws, {
|
|
6209
6415
|
type: "session_list",
|
|
6210
6416
|
sessions,
|
|
@@ -6242,7 +6448,7 @@ export class BridgeWebSocketServer {
|
|
|
6242
6448
|
/** Broadcast session list to all connected clients. */
|
|
6243
6449
|
broadcastSessionList() {
|
|
6244
6450
|
this.pruneDebugEvents();
|
|
6245
|
-
const sessions = this.
|
|
6451
|
+
const sessions = this.runtimeSessionsWithWorkspaces();
|
|
6246
6452
|
this.broadcast({
|
|
6247
6453
|
type: "session_list",
|
|
6248
6454
|
sessions,
|
|
@@ -6264,6 +6470,18 @@ export class BridgeWebSocketServer {
|
|
|
6264
6470
|
],
|
|
6265
6471
|
});
|
|
6266
6472
|
}
|
|
6473
|
+
runtimeSessionsWithWorkspaces() {
|
|
6474
|
+
return this.sessionManager
|
|
6475
|
+
.list()
|
|
6476
|
+
.map((summary) => this.runtimeSessionWithWorkspace(summary));
|
|
6477
|
+
}
|
|
6478
|
+
runtimeSessionWithWorkspace(summary) {
|
|
6479
|
+
const session = this.sessionManager.get(summary.id);
|
|
6480
|
+
const workspace = session
|
|
6481
|
+
? this.workspaceForRuntimeSession(session)
|
|
6482
|
+
: undefined;
|
|
6483
|
+
return workspace ? { ...summary, workspace } : summary;
|
|
6484
|
+
}
|
|
6267
6485
|
broadcastPromptHistoryStatus() {
|
|
6268
6486
|
if (!this.promptHistoryStore)
|
|
6269
6487
|
return;
|
|
@@ -6390,6 +6608,7 @@ export class BridgeWebSocketServer {
|
|
|
6390
6608
|
}
|
|
6391
6609
|
destroySession(sessionId) {
|
|
6392
6610
|
this.flushSessionDeltaBatches(sessionId);
|
|
6611
|
+
this.pendingSessionWorkspaces.delete(sessionId);
|
|
6393
6612
|
this.sessionManager.destroy(sessionId);
|
|
6394
6613
|
}
|
|
6395
6614
|
trackSessionMessage(sessionId, msg) {
|
|
@@ -6433,6 +6652,128 @@ export class BridgeWebSocketServer {
|
|
|
6433
6652
|
}
|
|
6434
6653
|
}
|
|
6435
6654
|
async listRecentSessions(msg) {
|
|
6655
|
+
const primaryProjectIds = new Set(msg.projectPath === undefined
|
|
6656
|
+
? []
|
|
6657
|
+
: (this.workspaceStore?.listProjects() ?? [])
|
|
6658
|
+
.filter((project) => project.rootPaths[0] === msg.projectPath)
|
|
6659
|
+
.map((project) => project.id));
|
|
6660
|
+
const workspaceFilterRequested = msg.projectId !== undefined ||
|
|
6661
|
+
msg.workspaceKind !== undefined ||
|
|
6662
|
+
primaryProjectIds.size > 0;
|
|
6663
|
+
if (!workspaceFilterRequested) {
|
|
6664
|
+
const rawResult = await this.listRecentSessionsRaw(msg);
|
|
6665
|
+
const enriched = rawResult.sessions.map((session) => this.enrichRecentSessionWorkspace(session));
|
|
6666
|
+
return { sessions: enriched, hasMore: rawResult.hasMore };
|
|
6667
|
+
}
|
|
6668
|
+
const matchesWorkspace = (session) => {
|
|
6669
|
+
const value = session;
|
|
6670
|
+
const workspace = value.workspace;
|
|
6671
|
+
if (msg.projectId && workspace?.projectId !== msg.projectId)
|
|
6672
|
+
return false;
|
|
6673
|
+
if (msg.workspaceKind && workspace?.kind !== msg.workspaceKind)
|
|
6674
|
+
return false;
|
|
6675
|
+
if (msg.projectPath !== undefined) {
|
|
6676
|
+
const matchesPrimaryProject = typeof workspace?.projectId === "string" &&
|
|
6677
|
+
primaryProjectIds.has(workspace.projectId);
|
|
6678
|
+
if (value.projectPath !== msg.projectPath &&
|
|
6679
|
+
!matchesPrimaryProject) {
|
|
6680
|
+
return false;
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
return true;
|
|
6684
|
+
};
|
|
6685
|
+
const offset = msg.offset ?? 0;
|
|
6686
|
+
const limit = msg.limit ?? 20;
|
|
6687
|
+
const requiredMatches = offset + limit + 1;
|
|
6688
|
+
let filtered;
|
|
6689
|
+
if (msg.provider === "codex") {
|
|
6690
|
+
filtered = await this.listWorkspaceFilteredCodexSessions(msg, matchesWorkspace, requiredMatches);
|
|
6691
|
+
}
|
|
6692
|
+
else if (msg.provider === "claude") {
|
|
6693
|
+
filtered = await this.listWorkspaceFilteredIndexedSessions(msg, matchesWorkspace, requiredMatches);
|
|
6694
|
+
}
|
|
6695
|
+
else {
|
|
6696
|
+
// The filesystem index includes both providers and is also the fallback
|
|
6697
|
+
// for Codex rollouts not returned by app-server. Merge it with one
|
|
6698
|
+
// cursor-preserving app-server scan for the freshest Codex metadata.
|
|
6699
|
+
const [indexed, codex] = await Promise.all([
|
|
6700
|
+
this.listWorkspaceFilteredIndexedSessions(msg, matchesWorkspace, requiredMatches),
|
|
6701
|
+
this.listWorkspaceFilteredCodexSessions({ ...msg, provider: "codex" }, matchesWorkspace, requiredMatches),
|
|
6702
|
+
]);
|
|
6703
|
+
filtered = mergeRecentSessionPages([...codex, ...indexed]);
|
|
6704
|
+
}
|
|
6705
|
+
return {
|
|
6706
|
+
sessions: filtered.slice(offset, offset + limit),
|
|
6707
|
+
hasMore: filtered.length > offset + limit,
|
|
6708
|
+
};
|
|
6709
|
+
}
|
|
6710
|
+
async listWorkspaceFilteredIndexedSessions(msg, matchesWorkspace, limit) {
|
|
6711
|
+
const result = await getAllRecentSessions({
|
|
6712
|
+
limit,
|
|
6713
|
+
offset: 0,
|
|
6714
|
+
// Project identity is authoritative. Do not pre-filter by the current
|
|
6715
|
+
// primary root: assignments may retain an older snapshot or worktree cwd.
|
|
6716
|
+
provider: msg.provider,
|
|
6717
|
+
namedOnly: msg.namedOnly,
|
|
6718
|
+
searchQuery: msg.searchQuery,
|
|
6719
|
+
archivedSessionIds: this.archiveStore.archivedIds(),
|
|
6720
|
+
sessionFilter: (session) => matchesWorkspace(this.enrichRecentSessionWorkspace(session)),
|
|
6721
|
+
});
|
|
6722
|
+
return result.sessions.map((session) => this.enrichRecentSessionWorkspace(session));
|
|
6723
|
+
}
|
|
6724
|
+
async listWorkspaceFilteredCodexSessions(msg, matchesWorkspace, limit) {
|
|
6725
|
+
try {
|
|
6726
|
+
return await this.listWorkspaceFilteredCodexThreads(msg, matchesWorkspace, limit);
|
|
6727
|
+
}
|
|
6728
|
+
catch (err) {
|
|
6729
|
+
console.warn(`[ws] Codex thread/list failed, falling back to rollout scan: ${err}`);
|
|
6730
|
+
return this.listWorkspaceFilteredIndexedSessions({ ...msg, provider: "codex" }, matchesWorkspace, limit);
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
async listWorkspaceFilteredCodexThreads(msg, matchesWorkspace, limit) {
|
|
6734
|
+
const activeProcess = this.getActiveCodexProcess();
|
|
6735
|
+
const process = activeProcess ?? (await this.createStandaloneCodexProcess(undefined));
|
|
6736
|
+
const isStandalone = activeProcess === null;
|
|
6737
|
+
try {
|
|
6738
|
+
const archivedIds = this.archiveStore.archivedIds();
|
|
6739
|
+
const matchingThreads = [];
|
|
6740
|
+
let cursor;
|
|
6741
|
+
do {
|
|
6742
|
+
const request = {
|
|
6743
|
+
limit: 500,
|
|
6744
|
+
searchTerm: msg.searchQuery,
|
|
6745
|
+
sourceKinds: CODEX_RECENT_THREAD_SOURCE_KINDS,
|
|
6746
|
+
};
|
|
6747
|
+
if (cursor != null)
|
|
6748
|
+
request.cursor = cursor;
|
|
6749
|
+
const result = await process.listThreads(request);
|
|
6750
|
+
for (const thread of result.data) {
|
|
6751
|
+
if (archivedIds.has(thread.id))
|
|
6752
|
+
continue;
|
|
6753
|
+
if (msg.namedOnly && !thread.name)
|
|
6754
|
+
continue;
|
|
6755
|
+
const workspaceProbe = this.enrichRecentSessionWorkspace({
|
|
6756
|
+
provider: "codex",
|
|
6757
|
+
sessionId: thread.id,
|
|
6758
|
+
projectPath: thread.cwd,
|
|
6759
|
+
});
|
|
6760
|
+
if (!matchesWorkspace(workspaceProbe))
|
|
6761
|
+
continue;
|
|
6762
|
+
matchingThreads.push(thread);
|
|
6763
|
+
if (matchingThreads.length >= limit)
|
|
6764
|
+
break;
|
|
6765
|
+
}
|
|
6766
|
+
cursor = result.nextCursor;
|
|
6767
|
+
} while (matchingThreads.length < limit && cursor != null);
|
|
6768
|
+
const indexedById = await getCodexSessionIndexMetadata(matchingThreads.map((thread) => thread.id));
|
|
6769
|
+
return matchingThreads.map((thread) => this.enrichRecentSessionWorkspace(codexThreadToRecentSession(thread, indexedById.get(thread.id))));
|
|
6770
|
+
}
|
|
6771
|
+
finally {
|
|
6772
|
+
if (isStandalone)
|
|
6773
|
+
process.stop();
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
async listRecentSessionsRaw(msg) {
|
|
6436
6777
|
if (msg.provider === "codex") {
|
|
6437
6778
|
return this.listRecentCodexSessions(msg);
|
|
6438
6779
|
}
|
|
@@ -6449,11 +6790,105 @@ export class BridgeWebSocketServer {
|
|
|
6449
6790
|
archivedSessionIds: this.archiveStore.archivedIds(),
|
|
6450
6791
|
});
|
|
6451
6792
|
}
|
|
6793
|
+
enrichRecentSessionWorkspace(session) {
|
|
6794
|
+
if (!session || typeof session !== "object")
|
|
6795
|
+
return session;
|
|
6796
|
+
const value = session;
|
|
6797
|
+
const provider = value.provider;
|
|
6798
|
+
const providerSessionId = value.sessionId;
|
|
6799
|
+
const projectPath = value.projectPath;
|
|
6800
|
+
if ((provider !== "claude" && provider !== "codex") ||
|
|
6801
|
+
typeof providerSessionId !== "string" ||
|
|
6802
|
+
typeof projectPath !== "string") {
|
|
6803
|
+
return session;
|
|
6804
|
+
}
|
|
6805
|
+
const workspace = this.workspaceStore?.resolveRecentWorkspace(provider, providerSessionId);
|
|
6806
|
+
return {
|
|
6807
|
+
...value,
|
|
6808
|
+
workspace: workspace ?? {
|
|
6809
|
+
kind: "unassigned",
|
|
6810
|
+
rootPaths: projectPath ? [projectPath] : [],
|
|
6811
|
+
},
|
|
6812
|
+
};
|
|
6813
|
+
}
|
|
6814
|
+
normalizeWorkspaceRoots(rootPaths) {
|
|
6815
|
+
const roots = [];
|
|
6816
|
+
const seen = new Set();
|
|
6817
|
+
for (const rawPath of rootPaths) {
|
|
6818
|
+
const path = resolvePlatformPath(rawPath, this.platform);
|
|
6819
|
+
if (!this.isPathAllowed(path))
|
|
6820
|
+
return { deniedRoot: rawPath };
|
|
6821
|
+
if (!seen.has(path)) {
|
|
6822
|
+
seen.add(path);
|
|
6823
|
+
roots.push(path);
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
return roots.length > 0 ? { roots } : {};
|
|
6827
|
+
}
|
|
6828
|
+
async persistPendingSessionWorkspace(sessionId) {
|
|
6829
|
+
const workspace = this.pendingSessionWorkspaces.get(sessionId);
|
|
6830
|
+
const session = this.sessionManager.get(sessionId);
|
|
6831
|
+
if (!workspace || !session?.claudeSessionId || !this.workspaceStore)
|
|
6832
|
+
return;
|
|
6833
|
+
try {
|
|
6834
|
+
await this.workspaceStore.assignSession(session.provider, session.claudeSessionId, workspace);
|
|
6835
|
+
this.pendingSessionWorkspaces.delete(sessionId);
|
|
6836
|
+
}
|
|
6837
|
+
catch (error) {
|
|
6838
|
+
console.error("[workspace] Failed to persist session assignment:", error);
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
workspaceForRuntimeSession(session) {
|
|
6842
|
+
const pending = this.pendingSessionWorkspaces.get(session.id);
|
|
6843
|
+
if (pending) {
|
|
6844
|
+
return { ...pending, rootPaths: [...pending.rootPaths] };
|
|
6845
|
+
}
|
|
6846
|
+
if (!session.claudeSessionId || !this.workspaceStore)
|
|
6847
|
+
return undefined;
|
|
6848
|
+
const assignment = this.workspaceStore.getAssignment(session.provider, session.claudeSessionId);
|
|
6849
|
+
if (!assignment)
|
|
6850
|
+
return undefined;
|
|
6851
|
+
const project = assignment.projectId
|
|
6852
|
+
? this.workspaceStore.getProject(assignment.projectId)
|
|
6853
|
+
: undefined;
|
|
6854
|
+
return {
|
|
6855
|
+
kind: assignment.kind,
|
|
6856
|
+
...(assignment.projectId ? { projectId: assignment.projectId } : {}),
|
|
6857
|
+
...(project ? { projectName: project.name } : {}),
|
|
6858
|
+
rootPaths: [...assignment.rootPaths],
|
|
6859
|
+
};
|
|
6860
|
+
}
|
|
6861
|
+
attachWorkspaceToRuntimeSession(sessionId, workspace) {
|
|
6862
|
+
if (!workspace)
|
|
6863
|
+
return;
|
|
6864
|
+
this.pendingSessionWorkspaces.set(sessionId, workspace);
|
|
6865
|
+
void this.persistPendingSessionWorkspace(sessionId);
|
|
6866
|
+
}
|
|
6867
|
+
projectsMessage(requestId) {
|
|
6868
|
+
return {
|
|
6869
|
+
type: "projects",
|
|
6870
|
+
projects: this.workspaceStore?.listProjects() ?? [],
|
|
6871
|
+
...(requestId ? { requestId } : {}),
|
|
6872
|
+
};
|
|
6873
|
+
}
|
|
6874
|
+
sendWorkspaceMutationError(ws, requestId, action, error) {
|
|
6875
|
+
this.send(ws, {
|
|
6876
|
+
type: "error",
|
|
6877
|
+
requestId,
|
|
6878
|
+
errorCode: "workspace_write_failed",
|
|
6879
|
+
message: `Failed to ${action}: ${error instanceof Error ? error.message : String(error)}`,
|
|
6880
|
+
});
|
|
6881
|
+
}
|
|
6882
|
+
legacyProjectHistoryProjects() {
|
|
6883
|
+
return this.projectHistory?.getProjects() ?? [];
|
|
6884
|
+
}
|
|
6452
6885
|
listRecentSessionsCoalesced(msg) {
|
|
6453
6886
|
const key = JSON.stringify({
|
|
6454
6887
|
limit: msg.limit ?? null,
|
|
6455
6888
|
offset: msg.offset ?? null,
|
|
6456
6889
|
projectPath: msg.projectPath ?? null,
|
|
6890
|
+
projectId: msg.projectId ?? null,
|
|
6891
|
+
workspaceKind: msg.workspaceKind ?? null,
|
|
6457
6892
|
provider: msg.provider ?? null,
|
|
6458
6893
|
namedOnly: msg.namedOnly ?? null,
|
|
6459
6894
|
searchQuery: msg.searchQuery ?? null,
|