clay-server 2.34.0-beta.9 → 2.34.1-beta.1
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/lib/project-connection.js +45 -34
- package/lib/project-sessions.js +77 -10
- package/lib/public/css/mobile-nav.css +0 -14
- package/lib/public/css/notifications-center.css +23 -19
- package/lib/public/css/sidebar.css +326 -101
- package/lib/public/index.html +7 -10
- package/lib/public/modules/diff.js +21 -7
- package/lib/public/modules/sidebar-mobile.js +10 -20
- package/lib/public/modules/sidebar-projects.js +12 -4
- package/lib/public/modules/sidebar-sessions.js +490 -113
- package/lib/public/modules/sidebar.js +8 -6
- package/lib/public/modules/tools.js +37 -10
- package/lib/public/sw.js +1 -1
- package/lib/sdk-bridge.js +27 -2
- package/lib/sessions.js +92 -0
- package/lib/ws-schema.js +2 -0
- package/lib/yoke/adapters/codex.js +31 -4
- package/package.json +1 -1
|
@@ -60,6 +60,35 @@ function attachConnection(ctx) {
|
|
|
60
60
|
// warmup once per reconnect.
|
|
61
61
|
var _warmedUp = false;
|
|
62
62
|
|
|
63
|
+
function findRestoredActiveSession(ws, wsUser, allSessions) {
|
|
64
|
+
var active = null;
|
|
65
|
+
var presenceKey = wsUser ? wsUser.id : "_default";
|
|
66
|
+
var storedPresence = userPresence.getPresence(slug, presenceKey);
|
|
67
|
+
if (storedPresence && storedPresence.sessionId) {
|
|
68
|
+
if (sm.sessions.has(storedPresence.sessionId)) {
|
|
69
|
+
active = sm.sessions.get(storedPresence.sessionId);
|
|
70
|
+
} else {
|
|
71
|
+
sm.sessions.forEach(function (s) {
|
|
72
|
+
if (s.cliSessionId && s.cliSessionId === storedPresence.sessionId) active = s;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (active && usersModule.isMultiUser() && wsUser) {
|
|
76
|
+
if (!usersModule.canAccessSession(wsUser.id, active, { visibility: "public" })) active = null;
|
|
77
|
+
} else if (active && !usersModule.isMultiUser() && active.ownerId) {
|
|
78
|
+
active = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!active && allSessions.length > 0) {
|
|
82
|
+
active = allSessions[0];
|
|
83
|
+
for (var fi = 1; fi < allSessions.length; fi++) {
|
|
84
|
+
if ((allSessions[fi].lastActivity || 0) > (active.lastActivity || 0)) {
|
|
85
|
+
active = allSessions[fi];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { active: active, storedPresence: storedPresence };
|
|
90
|
+
}
|
|
91
|
+
|
|
63
92
|
function handleConnection(ws, wsUser, handleMessage, handleDisconnection) {
|
|
64
93
|
ws._clayUser = wsUser || null;
|
|
65
94
|
clients.add(ws);
|
|
@@ -90,6 +119,18 @@ function attachConnection(ctx) {
|
|
|
90
119
|
var title = getTitle();
|
|
91
120
|
var project = getProject();
|
|
92
121
|
var ownerLocked = !!(osUsers && osUsers.length > 0 && /^\/home\/[^/]+\//.test(cwd));
|
|
122
|
+
var allSessions = [].concat(Array.from(sm.sessions.values())).filter(function (s) { return !s.hidden; });
|
|
123
|
+
if (usersModule.isMultiUser() && wsUser) {
|
|
124
|
+
allSessions = allSessions.filter(function (s) {
|
|
125
|
+
return usersModule.canAccessSession(wsUser.id, s, { visibility: "public" });
|
|
126
|
+
});
|
|
127
|
+
} else if (!usersModule.isMultiUser()) {
|
|
128
|
+
allSessions = allSessions.filter(function (s) { return !s.ownerId; });
|
|
129
|
+
}
|
|
130
|
+
var restoredState = findRestoredActiveSession(ws, wsUser, allSessions);
|
|
131
|
+
var restoredActive = restoredState.active;
|
|
132
|
+
var initialVendor = (restoredActive && restoredActive.vendor) || sm.defaultVendor || "claude";
|
|
133
|
+
var initialModels = (sm.modelsByVendor && sm.modelsByVendor[initialVendor]) || sm.availableModels || [];
|
|
93
134
|
sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked });
|
|
94
135
|
// Update notifications are pushed on a scheduled interval (see
|
|
95
136
|
// scheduleUpdateBroadcast). We no longer push on connect to avoid
|
|
@@ -98,8 +139,7 @@ function attachConnection(ctx) {
|
|
|
98
139
|
sendTo(ws, { type: "slash_commands", commands: sm.slashCommands });
|
|
99
140
|
}
|
|
100
141
|
if (sm.currentModel) {
|
|
101
|
-
|
|
102
|
-
sendTo(ws, { type: "model_info", model: sm.currentModel, models: sm.availableModels || [], vendor: sm.defaultVendor || "claude", availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
|
|
142
|
+
sendTo(ws, { type: "model_info", model: sm.currentModel, models: initialModels, vendor: initialVendor, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
|
|
103
143
|
}
|
|
104
144
|
sendTo(ws, { type: "config_state", model: sm.currentModel || "", mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
105
145
|
sendTo(ws, Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
|
|
@@ -116,14 +156,6 @@ function attachConnection(ctx) {
|
|
|
116
156
|
if (_notifications) _notifications.sendConnectionState(ws, sendTo);
|
|
117
157
|
|
|
118
158
|
// Session list (filtered for access control)
|
|
119
|
-
var allSessions = [].concat(Array.from(sm.sessions.values())).filter(function (s) { return !s.hidden; });
|
|
120
|
-
if (usersModule.isMultiUser() && wsUser) {
|
|
121
|
-
allSessions = allSessions.filter(function (s) {
|
|
122
|
-
return usersModule.canAccessSession(wsUser.id, s, { visibility: "public" });
|
|
123
|
-
});
|
|
124
|
-
} else if (!usersModule.isMultiUser()) {
|
|
125
|
-
allSessions = allSessions.filter(function (s) { return !s.ownerId; });
|
|
126
|
-
}
|
|
127
159
|
sendTo(ws, {
|
|
128
160
|
type: "session_list",
|
|
129
161
|
sessions: allSessions.map(function (s) {
|
|
@@ -146,36 +178,15 @@ function attachConnection(ctx) {
|
|
|
146
178
|
ownerId: s.ownerId || null,
|
|
147
179
|
sessionVisibility: s.sessionVisibility || "shared",
|
|
148
180
|
bookmarked: !!s.bookmarked,
|
|
181
|
+
favoriteOrder: typeof s.favoriteOrder === "number" ? s.favoriteOrder : null,
|
|
149
182
|
};
|
|
150
183
|
}),
|
|
151
184
|
});
|
|
152
185
|
|
|
153
186
|
// Restore active session for this client from server-side presence
|
|
154
|
-
var active =
|
|
187
|
+
var active = restoredState.active;
|
|
155
188
|
var presenceKey = wsUser ? wsUser.id : "_default";
|
|
156
|
-
var storedPresence =
|
|
157
|
-
if (storedPresence && storedPresence.sessionId) {
|
|
158
|
-
if (sm.sessions.has(storedPresence.sessionId)) {
|
|
159
|
-
active = sm.sessions.get(storedPresence.sessionId);
|
|
160
|
-
} else {
|
|
161
|
-
sm.sessions.forEach(function (s) {
|
|
162
|
-
if (s.cliSessionId && s.cliSessionId === storedPresence.sessionId) active = s;
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
if (active && usersModule.isMultiUser() && wsUser) {
|
|
166
|
-
if (!usersModule.canAccessSession(wsUser.id, active, { visibility: "public" })) active = null;
|
|
167
|
-
} else if (active && !usersModule.isMultiUser() && active.ownerId) {
|
|
168
|
-
active = null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
if (!active && allSessions.length > 0) {
|
|
172
|
-
active = allSessions[0];
|
|
173
|
-
for (var fi = 1; fi < allSessions.length; fi++) {
|
|
174
|
-
if ((allSessions[fi].lastActivity || 0) > (active.lastActivity || 0)) {
|
|
175
|
-
active = allSessions[fi];
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
189
|
+
var storedPresence = restoredState.storedPresence;
|
|
179
190
|
var autoCreated = false;
|
|
180
191
|
if (!active) {
|
|
181
192
|
var autoOpts = {};
|
package/lib/project-sessions.js
CHANGED
|
@@ -164,6 +164,39 @@ function attachSessions(ctx) {
|
|
|
164
164
|
return true;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
if (msg.type === "reorder_session_bookmarks") {
|
|
168
|
+
if (typeof msg.sourceId === "number" && typeof msg.targetId === "number" && msg.sourceId !== msg.targetId) {
|
|
169
|
+
var source = sm.sessions.get(msg.sourceId);
|
|
170
|
+
var target = sm.sessions.get(msg.targetId);
|
|
171
|
+
if (!source || !target) return true;
|
|
172
|
+
if (usersModule.isMultiUser() && ws._clayUser) {
|
|
173
|
+
if (!usersModule.canAccessSession(ws._clayUser.id, source, { visibility: "public" })) return true;
|
|
174
|
+
if (!usersModule.canAccessSession(ws._clayUser.id, target, { visibility: "public" })) return true;
|
|
175
|
+
}
|
|
176
|
+
sm.reorderBookmarkedSessions(msg.sourceId, msg.targetId, msg.insertBefore !== false);
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (msg.type === "bulk_delete_sessions") {
|
|
182
|
+
if (!Array.isArray(msg.sessionIds) || msg.sessionIds.length === 0) return true;
|
|
183
|
+
var deletableIds = [];
|
|
184
|
+
for (var di = 0; di < msg.sessionIds.length; di++) {
|
|
185
|
+
var bulkId = msg.sessionIds[di];
|
|
186
|
+
if (typeof bulkId !== "number") continue;
|
|
187
|
+
var bulkTarget = sm.sessions.get(bulkId);
|
|
188
|
+
if (!bulkTarget) continue;
|
|
189
|
+
if (usersModule.isMultiUser() && ws._clayUser) {
|
|
190
|
+
if (!usersModule.canAccessSession(ws._clayUser.id, bulkTarget, { visibility: "public" })) continue;
|
|
191
|
+
}
|
|
192
|
+
deletableIds.push(bulkId);
|
|
193
|
+
}
|
|
194
|
+
if (deletableIds.length > 0) {
|
|
195
|
+
sm.deleteSessionsBulk(deletableIds, ws);
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
167
200
|
if (msg.type === "transfer_project_owner") {
|
|
168
201
|
// Home directory projects: ownership is permanently locked
|
|
169
202
|
if (osUsers && osUsers.length > 0 && /^\/home\/[^/]+\//.test(cwd)) {
|
|
@@ -194,6 +227,24 @@ function attachSessions(ctx) {
|
|
|
194
227
|
if (msg.type === "resume_session") {
|
|
195
228
|
if (!msg.cliSessionId) return true;
|
|
196
229
|
var cliSess = require("./cli-sessions");
|
|
230
|
+
|
|
231
|
+
// If Clay already has a persisted meta file for this cliSessionId, read
|
|
232
|
+
// its vendor so resumeSession doesn't silently default to the project's
|
|
233
|
+
// primary vendor (which would break codex sessions after server restart).
|
|
234
|
+
var persistedVendor = null;
|
|
235
|
+
try {
|
|
236
|
+
var _fsResume = require("fs");
|
|
237
|
+
var _pathResume = require("path");
|
|
238
|
+
var metaPath = _pathResume.join(sm.sessionsDir, msg.cliSessionId + ".jsonl");
|
|
239
|
+
if (_fsResume.existsSync(metaPath)) {
|
|
240
|
+
var firstLine = _fsResume.readFileSync(metaPath, "utf8").split("\n", 1)[0];
|
|
241
|
+
try {
|
|
242
|
+
var metaObj = JSON.parse(firstLine);
|
|
243
|
+
if (metaObj && metaObj.type === "meta" && metaObj.vendor) persistedVendor = metaObj.vendor;
|
|
244
|
+
} catch (e) {}
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {}
|
|
247
|
+
|
|
197
248
|
// Try SDK for title first, then fall back to manual parsing
|
|
198
249
|
var titlePromise = adapter.getSessionInfo(msg.cliSessionId, { dir: cwd }).then(function(info) {
|
|
199
250
|
return (info && info.summary) ? info.summary.substring(0, 100) : null;
|
|
@@ -214,10 +265,10 @@ function attachSessions(ctx) {
|
|
|
214
265
|
}
|
|
215
266
|
}
|
|
216
267
|
}
|
|
217
|
-
var resumed = sm.resumeSession(msg.cliSessionId, { history: history, title: title }, ws);
|
|
268
|
+
var resumed = sm.resumeSession(msg.cliSessionId, { history: history, title: title, vendor: persistedVendor || undefined }, ws);
|
|
218
269
|
if (resumed) ws._clayActiveSession = resumed.localId;
|
|
219
270
|
}).catch(function() {
|
|
220
|
-
var resumed = sm.resumeSession(msg.cliSessionId, undefined, ws);
|
|
271
|
+
var resumed = sm.resumeSession(msg.cliSessionId, persistedVendor ? { vendor: persistedVendor } : undefined, ws);
|
|
221
272
|
if (resumed) ws._clayActiveSession = resumed.localId;
|
|
222
273
|
});
|
|
223
274
|
return true;
|
|
@@ -278,7 +329,13 @@ function attachSessions(ctx) {
|
|
|
278
329
|
if (switchTargetSess && sm.currentModel) {
|
|
279
330
|
var targetVendor = switchTargetSess.vendor || sm.defaultVendor || null;
|
|
280
331
|
var tvModels = (targetVendor && sm.modelsByVendor && sm.modelsByVendor[targetVendor]) || [];
|
|
281
|
-
|
|
332
|
+
var found = false;
|
|
333
|
+
for (var tvi = 0; tvi < tvModels.length; tvi++) {
|
|
334
|
+
var tvEntry = tvModels[tvi];
|
|
335
|
+
var tvVal = typeof tvEntry === "string" ? tvEntry : (tvEntry && (tvEntry.value || tvEntry.id)) || "";
|
|
336
|
+
if (tvVal === sm.currentModel) { found = true; break; }
|
|
337
|
+
}
|
|
338
|
+
if (tvModels.length > 0 && !found) {
|
|
282
339
|
sm.currentModel = "";
|
|
283
340
|
}
|
|
284
341
|
}
|
|
@@ -474,14 +531,24 @@ function attachSessions(ctx) {
|
|
|
474
531
|
if (msg.type === "set_vendor" && msg.vendor) {
|
|
475
532
|
var vendorSession = getSessionForWs(ws);
|
|
476
533
|
if (vendorSession) {
|
|
477
|
-
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
534
|
+
// Refuse to rebind vendor on a session that is already bound to a
|
|
535
|
+
// different CLI (cliSessionId is vendor-specific). This prevents a
|
|
536
|
+
// stale client-side vendor state from clobbering the persisted vendor
|
|
537
|
+
// on page reload / server restart.
|
|
538
|
+
var alreadyBound = vendorSession.cliSessionId && vendorSession.vendor && vendorSession.vendor !== msg.vendor;
|
|
539
|
+
if (alreadyBound) {
|
|
540
|
+
console.warn("[project] set_vendor ignored: session " + vendorSession.localId +
|
|
541
|
+
" is bound to '" + vendorSession.vendor + "', refused rebind to '" + msg.vendor + "'");
|
|
542
|
+
} else {
|
|
543
|
+
vendorSession.vendor = msg.vendor;
|
|
544
|
+
// Clear the shared model so the next query uses the vendor's default
|
|
545
|
+
// instead of leaking the previous vendor's model into a fresh session.
|
|
546
|
+
if (sm.currentModel) {
|
|
547
|
+
sm.currentModel = "";
|
|
548
|
+
}
|
|
549
|
+
sm.saveSessionFile(vendorSession);
|
|
550
|
+
sm.broadcastSessionList();
|
|
482
551
|
}
|
|
483
|
-
sm.saveSessionFile(vendorSession);
|
|
484
|
-
sm.broadcastSessionList();
|
|
485
552
|
}
|
|
486
553
|
if (msg.vendor) {
|
|
487
554
|
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[msg.vendor]) || [];
|
|
@@ -526,20 +526,6 @@
|
|
|
526
526
|
white-space: nowrap;
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
-
.mobile-session-bookmark {
|
|
530
|
-
display: inline-flex;
|
|
531
|
-
align-items: center;
|
|
532
|
-
color: var(--accent, #ff7b54);
|
|
533
|
-
flex-shrink: 0;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
.mobile-session-bookmark .lucide,
|
|
537
|
-
.mobile-session-bookmark svg {
|
|
538
|
-
width: 13px;
|
|
539
|
-
height: 13px;
|
|
540
|
-
display: block;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
529
|
.mobile-session-processing {
|
|
544
530
|
width: 7px;
|
|
545
531
|
height: 7px;
|
|
@@ -309,45 +309,49 @@
|
|
|
309
309
|
|
|
310
310
|
.light-theme .notif-banner-clear-all {
|
|
311
311
|
background:
|
|
312
|
-
linear-gradient(180deg, rgba(255,255,255,0.
|
|
313
|
-
rgba(
|
|
314
|
-
|
|
312
|
+
linear-gradient(180deg, rgba(255,255,255,0.54), rgba(255,255,255,0.28)),
|
|
313
|
+
rgba(248,250,255,0.22);
|
|
314
|
+
backdrop-filter: blur(10px) saturate(1.08);
|
|
315
|
+
-webkit-backdrop-filter: blur(10px) saturate(1.08);
|
|
316
|
+
border-color: rgba(255,255,255,0.52);
|
|
315
317
|
color: rgba(36, 45, 66, 0.72);
|
|
316
318
|
box-shadow:
|
|
317
|
-
inset 0 1px 0 rgba(255,255,255,0.
|
|
318
|
-
0 8px 22px rgba(31, 41, 55, 0.
|
|
319
|
+
inset 0 1px 0 rgba(255,255,255,0.78),
|
|
320
|
+
0 8px 22px rgba(31, 41, 55, 0.07);
|
|
319
321
|
}
|
|
320
322
|
|
|
321
323
|
.light-theme .notif-banner-clear-all:hover {
|
|
322
324
|
color: rgba(22, 29, 45, 0.92);
|
|
323
325
|
background:
|
|
324
|
-
linear-gradient(180deg, rgba(255,255,255,0.
|
|
325
|
-
rgba(
|
|
326
|
-
border-color: rgba(
|
|
326
|
+
linear-gradient(180deg, rgba(255,255,255,0.62), rgba(255,255,255,0.34)),
|
|
327
|
+
rgba(248,250,255,0.26);
|
|
328
|
+
border-color: rgba(255,255,255,0.6);
|
|
327
329
|
}
|
|
328
330
|
|
|
329
331
|
.light-theme .notif-banner {
|
|
330
332
|
background:
|
|
331
|
-
linear-gradient(180deg, rgba(255,255,255,0.
|
|
332
|
-
rgba(
|
|
333
|
-
|
|
333
|
+
linear-gradient(180deg, rgba(255,255,255,0.58), rgba(255,255,255,0.22)),
|
|
334
|
+
rgba(248,250,255,0.2);
|
|
335
|
+
backdrop-filter: blur(12px) saturate(1.05);
|
|
336
|
+
-webkit-backdrop-filter: blur(12px) saturate(1.05);
|
|
337
|
+
border: 1px solid rgba(255,255,255,0.5);
|
|
334
338
|
box-shadow:
|
|
335
|
-
inset 0 1px 0 rgba(255,255,255,0.
|
|
336
|
-
0 10px 30px rgba(31, 41, 55, 0.
|
|
339
|
+
inset 0 1px 0 rgba(255,255,255,0.72),
|
|
340
|
+
0 10px 30px rgba(31, 41, 55, 0.09);
|
|
337
341
|
}
|
|
338
342
|
|
|
339
343
|
.light-theme .notif-banner:hover {
|
|
340
|
-
border-color: rgba(
|
|
344
|
+
border-color: rgba(255,255,255,0.62);
|
|
341
345
|
box-shadow:
|
|
342
|
-
inset 0 1px 0 rgba(255,255,255,0.
|
|
343
|
-
0 12px 34px rgba(31, 41, 55, 0.
|
|
346
|
+
inset 0 1px 0 rgba(255,255,255,0.8),
|
|
347
|
+
0 12px 34px rgba(31, 41, 55, 0.11);
|
|
344
348
|
}
|
|
345
349
|
|
|
346
350
|
.light-theme .notif-banner-icon {
|
|
347
|
-
background: linear-gradient(180deg, rgba(255,255,255,0.
|
|
348
|
-
border-color: rgba(
|
|
351
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.72), rgba(246,249,255,0.36));
|
|
352
|
+
border-color: rgba(255,255,255,0.52);
|
|
349
353
|
color: var(--text-secondary);
|
|
350
|
-
box-shadow: inset 0 1px 0 rgba(255,255,255,0.
|
|
354
|
+
box-shadow: inset 0 1px 0 rgba(255,255,255,0.78);
|
|
351
355
|
}
|
|
352
356
|
|
|
353
357
|
.light-theme .notif-banner-project {
|