clay-server 4.0.0-beta.19 → 4.0.0-beta.20
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-session-handoff.js +1 -1
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +5 -5
- package/lib/public/modules/sidebar-sessions.js +2 -2
- package/lib/session-visibility.js +39 -0
- package/lib/sessions.js +17 -8
- package/lib/users-permissions.js +2 -2
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/package.json +1 -1
|
@@ -78,7 +78,7 @@ function attachSessionHandoff(ctx) {
|
|
|
78
78
|
) || null;
|
|
79
79
|
var target = sm.createSessionRaw({
|
|
80
80
|
ownerId: source.ownerId || null,
|
|
81
|
-
sessionVisibility:
|
|
81
|
+
sessionVisibility: "private",
|
|
82
82
|
vendor: targetVendor,
|
|
83
83
|
model: targetModel || null,
|
|
84
84
|
effort: targetEffort,
|
|
@@ -198,7 +198,7 @@ function attachSessionSpawn(ctx) {
|
|
|
198
198
|
var create = typeof sm.createSessionRaw === "function" ? sm.createSessionRaw : sm.createSession;
|
|
199
199
|
var session = create.call(sm, {
|
|
200
200
|
ownerId: parent.ownerId || null,
|
|
201
|
-
sessionVisibility:
|
|
201
|
+
sessionVisibility: "private",
|
|
202
202
|
vendor: vendor,
|
|
203
203
|
});
|
|
204
204
|
session.spawn = { parentId: parent.localId, index: index, batchId: batchId };
|
package/lib/project-sessions.js
CHANGED
|
@@ -5,6 +5,7 @@ var { execFileSync } = require("child_process");
|
|
|
5
5
|
var { CODEX_DEFAULTS, getCodexConfig } = require("./codex-defaults");
|
|
6
6
|
var updateSnooze = require("./update-snooze");
|
|
7
7
|
var yoke = require("./yoke");
|
|
8
|
+
var sessionVisibility = require("./session-visibility");
|
|
8
9
|
|
|
9
10
|
function vendorSupportsTui(vendor) {
|
|
10
11
|
var info = yoke.getVendorInfo(vendor);
|
|
@@ -496,7 +497,7 @@ function attachSessions(ctx) {
|
|
|
496
497
|
sm.sweepBlankSessions();
|
|
497
498
|
var sessionOpts = {};
|
|
498
499
|
if (ws._clayUser && usersModule.isMultiUser()) sessionOpts.ownerId = ws._clayUser.id;
|
|
499
|
-
|
|
500
|
+
sessionOpts.sessionVisibility = sessionVisibility.normalize(msg.sessionVisibility);
|
|
500
501
|
if (msg.vendor) sessionOpts.vendor = msg.vendor;
|
|
501
502
|
// Mode resolution: vendors without a TUI session mode are always GUI.
|
|
502
503
|
// TUI-capable sessions honor the explicit msg.mode if provided, otherwise
|
|
@@ -581,7 +582,8 @@ function attachSessions(ctx) {
|
|
|
581
582
|
if (reusable) {
|
|
582
583
|
if (sessionOpts.vendor && reusable.vendor !== sessionOpts.vendor) reusable.vendor = sessionOpts.vendor;
|
|
583
584
|
if (!reusable.effort) reusable.effort = sessionOpts.effort;
|
|
584
|
-
|
|
585
|
+
sm.setSessionVisibility(reusable.localId, sessionOpts.sessionVisibility);
|
|
586
|
+
sessionVisibility.revokeViewers(ctx, reusable);
|
|
585
587
|
sm.switchSession(reusable.localId, ws);
|
|
586
588
|
newSess = reusable;
|
|
587
589
|
} else {
|
|
@@ -609,9 +611,7 @@ function attachSessions(ctx) {
|
|
|
609
611
|
}
|
|
610
612
|
|
|
611
613
|
if (msg.type === "set_session_visibility") {
|
|
612
|
-
|
|
613
|
-
sm.setSessionVisibility(msg.sessionId, msg.visibility);
|
|
614
|
-
}
|
|
614
|
+
sessionVisibility.handleChange(ctx, ws, msg);
|
|
615
615
|
return true;
|
|
616
616
|
}
|
|
617
617
|
|
|
@@ -697,7 +697,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
697
697
|
|
|
698
698
|
// Session visibility toggle (only the session owner can change)
|
|
699
699
|
if (store.get('isMultiUserMode') && sessionData && sessionData.ownerId && sessionData.ownerId === store.get('myUserId')) {
|
|
700
|
-
var currentVis = (sessionData && sessionData.sessionVisibility) || "
|
|
700
|
+
var currentVis = (sessionData && sessionData.sessionVisibility) || "private";
|
|
701
701
|
var isPrivate = currentVis === "private";
|
|
702
702
|
var visItem = document.createElement("button");
|
|
703
703
|
visItem.className = "session-ctx-item";
|
|
@@ -1290,7 +1290,7 @@ function renderSessionItem(s, options) {
|
|
|
1290
1290
|
if (s.loop && s.loop.source === "debate") {
|
|
1291
1291
|
textHtml += '<span class="session-debate-icon" title="Debate">' + iconHtml("mic") + '</span>';
|
|
1292
1292
|
}
|
|
1293
|
-
if (store.get('isMultiUserMode') && s.sessionVisibility
|
|
1293
|
+
if (store.get('isMultiUserMode') && s.sessionVisibility !== "shared") {
|
|
1294
1294
|
textHtml += '<span class="session-private-icon" title="Private session">' + iconHtml("lock") + '</span>';
|
|
1295
1295
|
}
|
|
1296
1296
|
textHtml += highlightMatch(s.title || "New Session", searchQuery);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function normalize(value) {
|
|
2
|
+
return value === "shared" ? "shared" : "private";
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function restore(meta) {
|
|
6
|
+
// Older releases persisted the shared default without recording intent.
|
|
7
|
+
return meta.sessionVisibilityExplicit === true ? normalize(meta.sessionVisibility) : "private";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function handleChange(ctx, ws, msg) {
|
|
11
|
+
if (typeof msg.sessionId !== "number" || (msg.visibility !== "shared" && msg.visibility !== "private")) return;
|
|
12
|
+
var session = ctx.sm.sessions.get(msg.sessionId);
|
|
13
|
+
if (!session) return;
|
|
14
|
+
if (ctx.usersModule.isMultiUser()) {
|
|
15
|
+
var user = ws._clayUser;
|
|
16
|
+
var project = ctx.getProjectAccess ? ctx.getProjectAccess() : { visibility: "public" };
|
|
17
|
+
if (!user || !session.ownerId || session.ownerId !== user.id ||
|
|
18
|
+
!ctx.usersModule.canAccessSession(user.id, session, project)) return;
|
|
19
|
+
} else if (session.ownerId) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
ctx.sm.setSessionVisibility(msg.sessionId, msg.visibility);
|
|
23
|
+
revokeViewers(ctx, session);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function revokeViewers(ctx, session) {
|
|
27
|
+
if (ctx.usersModule.isMultiUser() && session.sessionVisibility === "private") {
|
|
28
|
+
ctx.clients.forEach(function(client) {
|
|
29
|
+
if (client._clayActiveSession !== session.localId) return;
|
|
30
|
+
if (client._clayUser && client._clayUser.id === session.ownerId) return;
|
|
31
|
+
// Clear the server-side selection before reconnecting so no further
|
|
32
|
+
// session events or input can cross the revoked access boundary.
|
|
33
|
+
client._clayActiveSession = null;
|
|
34
|
+
if (client.readyState === 1) client.close(1008, "Session access changed");
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { normalize: normalize, restore: restore, handleChange: handleChange, revokeViewers: revokeViewers };
|
package/lib/sessions.js
CHANGED
|
@@ -7,6 +7,7 @@ var { CODEX_DEFAULTS } = require("./codex-defaults");
|
|
|
7
7
|
var hygiene = require("./session-hygiene");
|
|
8
8
|
var backgroundTaskTiming = require("./background-task-timing");
|
|
9
9
|
var sessionProvenance = require("./session-provenance");
|
|
10
|
+
var sessionVisibility = require("./session-visibility");
|
|
10
11
|
|
|
11
12
|
function createSessionManager(opts) {
|
|
12
13
|
var cwd = opts.cwd;
|
|
@@ -166,7 +167,8 @@ function createSessionManager(opts) {
|
|
|
166
167
|
if (session.dangerouslySkipPermissions) metaObj.dangerouslySkipPermissions = true;
|
|
167
168
|
if (session.permissionMode) metaObj.permissionMode = session.permissionMode;
|
|
168
169
|
if (session.permissionModeBeforeFullAccess) metaObj.permissionModeBeforeFullAccess = session.permissionModeBeforeFullAccess;
|
|
169
|
-
|
|
170
|
+
metaObj.sessionVisibility = sessionVisibility.normalize(session.sessionVisibility);
|
|
171
|
+
if (session.sessionVisibilityExplicit) metaObj.sessionVisibilityExplicit = true;
|
|
170
172
|
if (session.bookmarked) metaObj.bookmarked = true;
|
|
171
173
|
if (typeof session.favoriteOrder === "number") metaObj.favoriteOrder = session.favoriteOrder;
|
|
172
174
|
if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
|
|
@@ -321,7 +323,8 @@ function createSessionManager(opts) {
|
|
|
321
323
|
session.terminalId = null;
|
|
322
324
|
session.runtimeMode = null;
|
|
323
325
|
session.runtimeTerminalId = null;
|
|
324
|
-
session.sessionVisibility = m
|
|
326
|
+
session.sessionVisibility = sessionVisibility.restore(m);
|
|
327
|
+
session.sessionVisibilityExplicit = m.sessionVisibilityExplicit === true;
|
|
325
328
|
session.bookmarked = !!m.bookmarked;
|
|
326
329
|
session.favoriteOrder = typeof m.favoriteOrder === "number" ? m.favoriteOrder : null;
|
|
327
330
|
sessions.set(localId, session);
|
|
@@ -447,7 +450,7 @@ function createSessionManager(opts) {
|
|
|
447
450
|
terminalId: null,
|
|
448
451
|
runtimeMode: null,
|
|
449
452
|
runtimeTerminalId: null,
|
|
450
|
-
sessionVisibility: "
|
|
453
|
+
sessionVisibility: "private",
|
|
451
454
|
bookmarked: false,
|
|
452
455
|
favoriteOrder: null,
|
|
453
456
|
};
|
|
@@ -536,7 +539,7 @@ function createSessionManager(opts) {
|
|
|
536
539
|
loop: loop,
|
|
537
540
|
spawn: s.spawn || null,
|
|
538
541
|
ownerId: s.ownerId || null,
|
|
539
|
-
sessionVisibility: s.sessionVisibility
|
|
542
|
+
sessionVisibility: sessionVisibility.normalize(s.sessionVisibility),
|
|
540
543
|
bookmarked: !!s.bookmarked,
|
|
541
544
|
favoriteOrder: typeof s.favoriteOrder === "number" ? s.favoriteOrder : null,
|
|
542
545
|
unread: unreadMap[s.localId] || 0,
|
|
@@ -620,7 +623,8 @@ function createSessionManager(opts) {
|
|
|
620
623
|
history: [],
|
|
621
624
|
messageUUIDs: [],
|
|
622
625
|
ownerId: (sessionOpts && sessionOpts.ownerId) || null,
|
|
623
|
-
sessionVisibility: (sessionOpts && sessionOpts.sessionVisibility)
|
|
626
|
+
sessionVisibility: sessionVisibility.normalize(sessionOpts && sessionOpts.sessionVisibility),
|
|
627
|
+
sessionVisibilityExplicit: !!(sessionOpts && sessionOpts.sessionVisibility === "shared"),
|
|
624
628
|
bookmarked: false,
|
|
625
629
|
favoriteOrder: null,
|
|
626
630
|
vendor: (sessionOpts && sessionOpts.vendor) || null,
|
|
@@ -661,7 +665,8 @@ function createSessionManager(opts) {
|
|
|
661
665
|
history: [],
|
|
662
666
|
messageUUIDs: [],
|
|
663
667
|
ownerId: (sessionOpts && sessionOpts.ownerId) || null,
|
|
664
|
-
sessionVisibility: (sessionOpts && sessionOpts.sessionVisibility)
|
|
668
|
+
sessionVisibility: sessionVisibility.normalize(sessionOpts && sessionOpts.sessionVisibility),
|
|
669
|
+
sessionVisibilityExplicit: !!(sessionOpts && sessionOpts.sessionVisibility === "shared"),
|
|
665
670
|
bookmarked: false,
|
|
666
671
|
favoriteOrder: null,
|
|
667
672
|
vendor: (sessionOpts && sessionOpts.vendor) || null,
|
|
@@ -916,7 +921,8 @@ function createSessionManager(opts) {
|
|
|
916
921
|
// Send to active clients without recording to history/disk (ephemeral data)
|
|
917
922
|
if (sendEach) {
|
|
918
923
|
var data = JSON.stringify(obj);
|
|
919
|
-
sendEach(function (ws) {
|
|
924
|
+
sendEach(function (ws, filterFn) {
|
|
925
|
+
if (filterFn && !filterFn(session)) return;
|
|
920
926
|
if (session.homeDebatePlanning === true) return;
|
|
921
927
|
if (ws._clayActiveSession === session.localId && ws.readyState === 1) {
|
|
922
928
|
ws.send(data);
|
|
@@ -947,7 +953,8 @@ function createSessionManager(opts) {
|
|
|
947
953
|
// Multi-user: send to clients whose active session matches this one
|
|
948
954
|
var data = JSON.stringify(outgoingObj);
|
|
949
955
|
var ioData = null;
|
|
950
|
-
sendEach(function (ws) {
|
|
956
|
+
sendEach(function (ws, filterFn) {
|
|
957
|
+
if (filterFn && !filterFn(session)) return;
|
|
951
958
|
if (session.homeDebatePlanning === true) return;
|
|
952
959
|
if (ws._clayActiveSession === session.localId) {
|
|
953
960
|
if (ws.readyState === 1) ws.send(data);
|
|
@@ -1290,7 +1297,9 @@ function createSessionManager(opts) {
|
|
|
1290
1297
|
setSessionVisibility: function (localId, visibility) {
|
|
1291
1298
|
var session = sessions.get(localId);
|
|
1292
1299
|
if (!session) return { error: "Session not found" };
|
|
1300
|
+
if (visibility !== "shared" && visibility !== "private") return { error: "Invalid session visibility" };
|
|
1293
1301
|
session.sessionVisibility = visibility;
|
|
1302
|
+
session.sessionVisibilityExplicit = true;
|
|
1294
1303
|
saveSessionFile(session);
|
|
1295
1304
|
broadcastSessionList();
|
|
1296
1305
|
return { ok: true };
|
package/lib/users-permissions.js
CHANGED
|
@@ -96,8 +96,8 @@ function attachPermissions(deps) {
|
|
|
96
96
|
}
|
|
97
97
|
// Owner can always see their own sessions
|
|
98
98
|
if (session.ownerId === userId) return true;
|
|
99
|
-
//
|
|
100
|
-
if (
|
|
99
|
+
// Sharing is opt-in; missing or invalid visibility stays private.
|
|
100
|
+
if (session.sessionVisibility === "shared") return true;
|
|
101
101
|
// Private sessions are only visible to the owner
|
|
102
102
|
return false;
|
|
103
103
|
}
|
|
@@ -424,22 +424,32 @@ async function handleQueryStart(msg) {
|
|
|
424
424
|
function perf(label) { console.log("[PERF] sdk-worker: " + label + " +" + (Date.now() - t0) + "ms (local +" + (Date.now() - localT0) + "ms)"); }
|
|
425
425
|
perf("handleQueryStart entered");
|
|
426
426
|
|
|
427
|
+
// Socket messages keep arriving while the SDK import awaits. Accept input
|
|
428
|
+
// and cancellation synchronously, before yielding to the next IPC message.
|
|
429
|
+
messageQueue = createMessageQueue();
|
|
430
|
+
abortController = new AbortController();
|
|
431
|
+
if (msg.prompt) messageQueue.push(msg.prompt);
|
|
432
|
+
|
|
427
433
|
var sdk;
|
|
428
434
|
try {
|
|
429
435
|
perf("loading SDK");
|
|
430
436
|
sdk = await getSDK();
|
|
431
437
|
perf("SDK loaded");
|
|
432
438
|
} catch (e) {
|
|
439
|
+
messageQueue.end();
|
|
440
|
+
messageQueue = null;
|
|
441
|
+
abortController = null;
|
|
442
|
+
sdkModule = null;
|
|
433
443
|
sendToDaemon({ type: "query_error", error: "Failed to load SDK: " + (e.message || e), exitCode: null, stderr: null });
|
|
434
444
|
return;
|
|
435
445
|
}
|
|
436
446
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
447
|
+
if (abortController.signal.aborted) {
|
|
448
|
+
messageQueue.end();
|
|
449
|
+
messageQueue = null;
|
|
450
|
+
abortController = null;
|
|
451
|
+
sendToDaemon({ type: "query_done" });
|
|
452
|
+
return;
|
|
443
453
|
}
|
|
444
454
|
|
|
445
455
|
// Build query options (callbacks are local, everything else from daemon)
|
package/package.json
CHANGED