clay-server 5.2.0-beta.4 → 5.2.0-beta.5
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/account-auth-socket.js +1 -0
- package/lib/builtin-mates.js +17 -14
- package/lib/canonical-clay-record-authority.js +15 -0
- package/lib/canonical-clay-scheduling-authority.js +19 -0
- package/lib/claude-hook-installer.js +28 -0
- package/lib/clay-mate-chat-receipts.js +26 -0
- package/lib/clay-mate-chat.js +87 -0
- package/lib/clay-mate-knowledge-input.js +26 -0
- package/lib/clay-mate-knowledge.js +95 -0
- package/lib/clay-mate-lifecycle.js +95 -0
- package/lib/clay-product-control-input.js +33 -0
- package/lib/clay-product-control-tools.js +51 -0
- package/lib/clay-product-control.js +237 -0
- package/lib/clay-project-lifecycle.js +154 -0
- package/lib/clay-project-record-input.js +44 -0
- package/lib/clay-project-records.js +83 -0
- package/lib/clay-scheduling-input.js +21 -0
- package/lib/clay-scheduling-receipts.js +26 -0
- package/lib/clay-scheduling.js +119 -0
- package/lib/clay-screen-controls.js +96 -0
- package/lib/clay-workspace-controls.js +83 -0
- package/lib/clay-workspace-input.js +17 -0
- package/lib/clay-workspace-receipts.js +26 -0
- package/lib/daemon-project-access.js +11 -0
- package/lib/daemon-project-lifecycle-callbacks.js +15 -0
- package/lib/daemon-project-lifecycle.js +262 -0
- package/lib/daemon-projects.js +1 -1
- package/lib/daemon.js +73 -221
- package/lib/issues-service.js +29 -15
- package/lib/knowledge-db-import.js +5 -4
- package/lib/knowledge-db-service.js +9 -6
- package/lib/knowledge-document-index.js +2 -1
- package/lib/knowledge-product-operations.js +46 -0
- package/lib/mate-creation-mcp-server.js +2 -0
- package/lib/mate-knowledge-canonical-access.js +10 -0
- package/lib/mate-knowledge-service.js +7 -4
- package/lib/mate-lifecycle-service.js +94 -0
- package/lib/mate-message-delivery.js +43 -0
- package/lib/mate-ready-creation.js +9 -1
- package/lib/mate-schedule-input.js +29 -0
- package/lib/mate-schedule-lifecycle.js +151 -0
- package/lib/mate-scheduling.js +103 -0
- package/lib/mates.js +4 -3
- package/lib/notes-checklist.js +50 -0
- package/lib/notes.js +49 -3
- package/lib/os-users.js +20 -5
- package/lib/project-admin-overview.js +51 -0
- package/lib/project-ask-user.js +4 -2
- package/lib/project-delegated-follow-up.js +2 -1
- package/lib/project-directory-claim.js +65 -0
- package/lib/project-file-operations.js +61 -0
- package/lib/project-filesystem.js +13 -87
- package/lib/project-full-access.js +23 -2
- package/lib/project-knowledge.js +32 -18
- package/lib/project-lifecycle-operations.js +98 -0
- package/lib/project-lifecycle-process-witness.js +32 -0
- package/lib/project-lifecycle-runtime.js +50 -0
- package/lib/project-linux-access.js +22 -0
- package/lib/project-logs-canonical-clay.js +27 -0
- package/lib/project-logs-clay-query.js +16 -0
- package/lib/project-logs-schema.js +4 -2
- package/lib/project-logs-service.js +8 -7
- package/lib/project-logs.js +1 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-mate-chat-control.js +55 -0
- package/lib/project-mate-creation-proposal.js +18 -3
- package/lib/project-models.js +14 -31
- package/lib/project-notifications.js +74 -16
- package/lib/project-ownership.js +77 -0
- package/lib/project-pair-result-delivery.js +14 -3
- package/lib/project-pending-message-queue.js +1 -0
- package/lib/project-product-control.js +87 -0
- package/lib/project-product-session-queue.js +46 -0
- package/lib/project-push-access.js +28 -0
- package/lib/project-scheduled-message-queue.js +30 -0
- package/lib/project-scheduled-messages.js +63 -64
- package/lib/project-scheduled-task-control.js +83 -0
- package/lib/project-scheduled-tasks.js +13 -2
- package/lib/project-scheduling-access.js +10 -0
- package/lib/project-session-actions.js +65 -0
- package/lib/project-session-delete.js +8 -1
- package/lib/project-session-github.js +47 -12
- package/lib/project-session-history-actions.js +165 -0
- package/lib/project-session-runtime-actions.js +63 -0
- package/lib/project-session-vendor.js +54 -0
- package/lib/project-sessions.js +64 -241
- package/lib/project-user-message.js +30 -1
- package/lib/project-workspace-operations.js +126 -0
- package/lib/project-workspace-query.js +9 -2
- package/lib/project.js +95 -3
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- package/lib/public/css/overlays.css +4 -2
- package/lib/public/css/project-admin-overview.css +13 -0
- package/lib/public/css/session-folders.css +11 -0
- package/lib/public/css/session-github.css +3 -0
- package/lib/public/css/sticky-notes.css +61 -1
- package/lib/public/index.html +1 -1
- package/lib/public/modules/admin-settings-projects.js +6 -2
- package/lib/public/modules/app-connection.js +8 -1
- package/lib/public/modules/app-message-router.js +1 -1
- package/lib/public/modules/app-messages.js +18 -3
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/clay-owner-navigation.js +27 -0
- package/lib/public/modules/clay-tool-activity.js +140 -0
- package/lib/public/modules/clay-tool-presentation.js +338 -0
- package/lib/public/modules/command-palette.js +1 -1
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/home-debate-planning.js +5 -5
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/session-folder-layout.js +1 -1
- package/lib/public/modules/session-folder-view-menu.js +9 -0
- package/lib/public/modules/session-github.js +65 -8
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/sticky-note-markdown.js +72 -6
- package/lib/public/modules/sticky-notes-actions.js +30 -0
- package/lib/public/modules/sticky-notes-browser.js +9 -2
- package/lib/public/modules/sticky-notes-card.js +23 -7
- package/lib/public/modules/sticky-notes-checklist.js +71 -0
- package/lib/public/modules/sticky-notes-editor.js +108 -44
- package/lib/public/modules/sticky-notes-shared.js +50 -2
- package/lib/public/modules/sticky-notes-state.js +66 -0
- package/lib/public/modules/sticky-notes.js +34 -51
- package/lib/public/modules/thinking-lifecycle.js +33 -3
- package/lib/public/modules/thinking-view.js +23 -1
- package/lib/public/modules/tools.js +34 -6
- package/lib/public/modules/user-settings-appearance.js +10 -3
- package/lib/public/modules/user-settings-editors.js +1 -1
- package/lib/public/modules/user-settings-layout.js +11 -2
- package/lib/public/modules/user-settings-preferences.js +6 -2
- package/lib/public/modules/user-settings-profile.js +12 -2
- package/lib/public/modules/user-settings.js +3 -2
- package/lib/public/style.css +2 -0
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +220 -21
- package/lib/sdk-message-processor.js +2 -0
- package/lib/sdk-response-recovery.js +155 -0
- package/lib/server-admin.js +63 -16
- package/lib/server-dm.js +1 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +7 -3
- package/lib/server-home-chat.js +4 -16
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +3 -3
- package/lib/server-mates.js +25 -67
- package/lib/server-owner-ui.js +77 -0
- package/lib/server-path-access.js +38 -0
- package/lib/server-project-removal.js +22 -0
- package/lib/server.js +99 -29
- package/lib/session-config-transition.js +45 -0
- package/lib/session-folders.js +7 -2
- package/lib/session-github.js +9 -2
- package/lib/session-visibility.js +5 -2
- package/lib/sessions.js +10 -0
- package/lib/terminal-manager.js +52 -0
- package/lib/users-permissions.js +2 -8
- package/lib/users.js +4 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +8 -2
- package/lib/yoke/adapters/claude.js +47 -8
- package/lib/yoke/adapters/codex.js +52 -111
- package/lib/yoke/claude-worker-startup.js +28 -0
- package/lib/yoke/codex-idle-recovery.js +81 -1
- package/lib/yoke/codex-recovery-admission.js +164 -0
- package/lib/yoke/session-tool-transport.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Shared runtime removal used by the server callback and lifecycle controls.
|
|
2
|
+
function removeProjectRuntime(projects, slug, awaitCompletion, registryRefresh) {
|
|
3
|
+
var project = projects.get(slug);
|
|
4
|
+
if (!project) return false;
|
|
5
|
+
var wasMate = project.getStatus().isMate;
|
|
6
|
+
var shutdownResult = project.destroy();
|
|
7
|
+
if (!awaitCompletion) projects.delete(slug);
|
|
8
|
+
if (shutdownResult && typeof shutdownResult.catch === 'function') {
|
|
9
|
+
shutdownResult.catch(function (error) {
|
|
10
|
+
console.error('[server] Project destroy failed for ' + slug + ':', error && error.message ? error.message : error);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
if (!wasMate && registryRefresh) registryRefresh();
|
|
14
|
+
if (awaitCompletion) return Promise.resolve(shutdownResult).then(function (result) {
|
|
15
|
+
if (result !== true) throw new Error('Project runtime shutdown did not complete successfully: ' + slug);
|
|
16
|
+
if (projects.get(slug) !== project) throw new Error('Project runtime was replaced during shutdown: ' + slug);
|
|
17
|
+
projects.delete(slug);
|
|
18
|
+
return true;
|
|
19
|
+
});
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
module.exports = { removeProjectRuntime: removeProjectRuntime };
|
package/lib/server.js
CHANGED
|
@@ -219,6 +219,14 @@ function createServer(opts) {
|
|
|
219
219
|
var isRequestAuthed = auth.isRequestAuthed;
|
|
220
220
|
var parseCookies = auth.parseCookies;
|
|
221
221
|
|
|
222
|
+
var projectAdminOverview = require("./project-admin-overview");
|
|
223
|
+
|
|
224
|
+
function administrativeMetadata(userId, slug) {
|
|
225
|
+
var user = userId && users.findUserById(userId);
|
|
226
|
+
var context = projects.get(slug);
|
|
227
|
+
return projectAdminOverview.metadataFor(user, context && context.getStatus(), onGetProjectAccess && onGetProjectAccess(slug), users);
|
|
228
|
+
}
|
|
229
|
+
|
|
222
230
|
function canUserAccessSlug(userId, slug) {
|
|
223
231
|
if (!userId || !slug) return false;
|
|
224
232
|
var project = projects && projects.get(slug);
|
|
@@ -237,6 +245,13 @@ function createServer(opts) {
|
|
|
237
245
|
return users.canAccessProject(userId, access) === true;
|
|
238
246
|
}
|
|
239
247
|
|
|
248
|
+
var projects = new Map(); // slug → projectContext
|
|
249
|
+
var lifecycleRuntime = require("./project-lifecycle-runtime").attachLifecycleRuntime({ projects: projects });
|
|
250
|
+
var canUsePath = require("./server-path-access").attachPathAccess({
|
|
251
|
+
users: users, projects: projects, canAccess: canUserAccessSlug, osUsers: osUsers,
|
|
252
|
+
resolveIdentity: require("./os-users").resolveOsUserInfo,
|
|
253
|
+
});
|
|
254
|
+
|
|
240
255
|
var realVersion = require("../package.json").version;
|
|
241
256
|
var currentVersion = debug ? "0.0.9" : realVersion;
|
|
242
257
|
|
|
@@ -341,7 +356,6 @@ function createServer(opts) {
|
|
|
341
356
|
var caContent = caPath ? (function () { try { return fs.readFileSync(caPath); } catch (e) { return null; } })() : null;
|
|
342
357
|
|
|
343
358
|
// --- Project registry ---
|
|
344
|
-
var projects = new Map(); // slug → projectContext
|
|
345
359
|
var assignmentService = attachWorkspaceAssignmentService({
|
|
346
360
|
storageDir: path.join(CONFIG_DIR, "workspace-assignments"),
|
|
347
361
|
getProjects: function () { return projects; },
|
|
@@ -363,6 +377,29 @@ function createServer(opts) {
|
|
|
363
377
|
},
|
|
364
378
|
assignmentService: assignmentService,
|
|
365
379
|
});
|
|
380
|
+
var ownerUiService = require('./server-owner-ui').attachOwnerUi({
|
|
381
|
+
isMultiUser: users.isMultiUser, findUser: users.findUserById,
|
|
382
|
+
forEachClient: function (visit) {
|
|
383
|
+
if (globalWs) globalWs.forEachClient(function (ws) { visit(ws, null); });
|
|
384
|
+
projects.forEach(function (project, slug) { project.forEachClient(function (ws) { visit(ws, slug); }); });
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
var clayProductControl = require("./clay-product-control").attachClayProductControl({
|
|
388
|
+
getOwnerUi: function () { return ownerUiService; },
|
|
389
|
+
getNotifications: function () { return _globalNotifications; },
|
|
390
|
+
getRecordServices: function () { return { issues: issuesService, logs: projectLogsService }; },
|
|
391
|
+
projectLifecycle: opts.projectLifecycle || null,
|
|
392
|
+
getMateLifecycle: function () { return mateHandler && mateHandler.lifecycle; },
|
|
393
|
+
getMateDir: function (ownerId, mateId) { return mates.getMateDir(mates.buildMateCtx(users.isMultiUser() ? ownerId : null), mateId); },
|
|
394
|
+
lifecyclePermissions: function (user) { return users.getEffectivePermissions(user, osUsers); },
|
|
395
|
+
canUsePath: canUsePath,
|
|
396
|
+
getProjects: function () { return projects; },
|
|
397
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
398
|
+
onGetProjectAccess: onGetProjectAccess,
|
|
399
|
+
canAccessProject: function (userId, access) { return users.canAccessProject(userId, access) === true; },
|
|
400
|
+
findUser: function (userId) { return users.findUserById(userId); },
|
|
401
|
+
resolveMate: function (userId, mateId) { return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId); },
|
|
402
|
+
});
|
|
366
403
|
// Project access is resolved from the authoritative project record, not from
|
|
367
404
|
// getStatus(), which carries no visibility or allowedUsers fields.
|
|
368
405
|
var projectLogsService = attachProjectLogsService({
|
|
@@ -409,6 +446,7 @@ function createServer(opts) {
|
|
|
409
446
|
// Mate Knowledge reads the synchronized Knowledge records. The Mate list is
|
|
410
447
|
// derived from the owning user alone, so a binding can never span users.
|
|
411
448
|
var mateKnowledgeService = attachMateKnowledgeService({
|
|
449
|
+
findUser: function (userId) { return users.findUserById(userId); },
|
|
412
450
|
getProjects: function () { return projects; },
|
|
413
451
|
isMultiUser: function () { return users.isMultiUser(); },
|
|
414
452
|
resolveMate: function (userId, mateId) {
|
|
@@ -547,7 +585,28 @@ function createServer(opts) {
|
|
|
547
585
|
broadcastAll: function (msg) { broadcastAll(msg); },
|
|
548
586
|
sendToUser: function (userId, msg) { sendToUser(userId, msg); },
|
|
549
587
|
pushModule: pushModule,
|
|
588
|
+
forEachClient: forEachAppClient,
|
|
589
|
+
getUsers: function () { return users.getAllUsers(); },
|
|
590
|
+
canView: function (ws, notification) { return canViewNotification(ws && ws._clayUser && ws._clayUser.id, notification); },
|
|
591
|
+
canViewPrincipal: function (principal, notification) { return canViewNotification(principal.userId, notification); },
|
|
550
592
|
});
|
|
593
|
+
function canViewNotification(userId, notification) {
|
|
594
|
+
if (!users.isMultiUser()) return true;
|
|
595
|
+
if (!userId || !users.findUserById(userId)) return false;
|
|
596
|
+
if (notification.targetUserId && notification.targetUserId !== userId) return false;
|
|
597
|
+
if (notification.slug) {
|
|
598
|
+
if (!canUserAccessSlug(userId, notification.slug)) return false;
|
|
599
|
+
if (notification.sessionId) {
|
|
600
|
+
var project = projects.get(notification.slug);
|
|
601
|
+
var session = project && project.getSessionManager().sessions.get(notification.sessionId);
|
|
602
|
+
var status = project && project.getStatus();
|
|
603
|
+
var access = status && status.isMate ? { isMate: true, visibility: "private", ownerId: status.projectOwnerId } : onGetProjectAccess(notification.slug);
|
|
604
|
+
if (!session || !users.canAccessSession(userId, session, access)) return false;
|
|
605
|
+
}
|
|
606
|
+
return !notification.ownerId || notification.ownerId === userId || !!notification.sessionId;
|
|
607
|
+
}
|
|
608
|
+
return notification.targetUserId === userId || notification.ownerId === userId;
|
|
609
|
+
}
|
|
551
610
|
|
|
552
611
|
// --- Security headers ---
|
|
553
612
|
var securityHeaders = {
|
|
@@ -980,8 +1039,8 @@ function createServer(opts) {
|
|
|
980
1039
|
var projectList = [];
|
|
981
1040
|
var infoUser = users.isMultiUser() ? getMultiUserFromReq(req) : null;
|
|
982
1041
|
projects.forEach(function (ctx, slug) {
|
|
983
|
-
if (users.isMultiUser() && (!infoUser || !canUserAccessSlug(infoUser.id, slug))) return;
|
|
984
|
-
projectList.push({ slug: slug, project: ctx.project });
|
|
1042
|
+
if (users.isMultiUser() && (!infoUser || (!canUserAccessSlug(infoUser.id, slug) && !administrativeMetadata(infoUser.id, slug)))) return;
|
|
1043
|
+
projectList.push({ slug: slug, project: ctx.project, contentAccess: !users.isMultiUser() || canUserAccessSlug(infoUser.id, slug) });
|
|
985
1044
|
});
|
|
986
1045
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
987
1046
|
res.end(JSON.stringify({ projects: projectList, version: currentVersion, authenticated: true }));
|
|
@@ -1057,8 +1116,14 @@ function createServer(opts) {
|
|
|
1057
1116
|
if (users.isMultiUser() && !isMcpBridgeAuthenticated) {
|
|
1058
1117
|
var httpUser = getMultiUserFromReq(req);
|
|
1059
1118
|
if (!httpUser || !canUserAccessSlug(httpUser.id, slug)) {
|
|
1060
|
-
|
|
1061
|
-
|
|
1119
|
+
var overview = httpUser && administrativeMetadata(httpUser.id, slug);
|
|
1120
|
+
if (overview && req.method === "GET" && (projectUrlForAuth === "/" || projectUrlForAuth === "/index.html")) {
|
|
1121
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
1122
|
+
res.end(projectAdminOverview.page(overview, getVisibleProjects(httpUser.id)));
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
1126
|
+
res.end(JSON.stringify({ error: "Project content access is not permitted" }));
|
|
1062
1127
|
return;
|
|
1063
1128
|
}
|
|
1064
1129
|
}
|
|
@@ -1167,6 +1232,7 @@ function createServer(opts) {
|
|
|
1167
1232
|
var globalWs = serverGlobalWs.attachGlobalWs({
|
|
1168
1233
|
osUsers: osUsers,
|
|
1169
1234
|
usersModule: users,
|
|
1235
|
+
canUsePath: canUsePath,
|
|
1170
1236
|
onAddProject: onAddProject,
|
|
1171
1237
|
onCreateProject: onCreateProject,
|
|
1172
1238
|
onCloneProject: onCloneProject,
|
|
@@ -1307,17 +1373,10 @@ function createServer(opts) {
|
|
|
1307
1373
|
if (processingUpdateTimer) clearTimeout(processingUpdateTimer);
|
|
1308
1374
|
processingUpdateTimer = setTimeout(function () {
|
|
1309
1375
|
processingUpdateTimer = null;
|
|
1310
|
-
var allProjectsList = getProjects();
|
|
1311
1376
|
// Always send per-client to include cross-project unread counts
|
|
1312
1377
|
projects.forEach(function (ctx, projSlug) {
|
|
1313
1378
|
ctx.forEachClient(function (ws) {
|
|
1314
|
-
var filtered =
|
|
1315
|
-
if (users.isMultiUser() && onGetProjectAccess) {
|
|
1316
|
-
var wsUser = ws._clayUser;
|
|
1317
|
-
filtered = wsUser ? allProjectsList.filter(function (p) {
|
|
1318
|
-
return canUserAccessSlug(wsUser.id, p.slug);
|
|
1319
|
-
}) : [];
|
|
1320
|
-
}
|
|
1379
|
+
var filtered = getVisibleProjects(ws._clayUser ? ws._clayUser.id : null);
|
|
1321
1380
|
// Attach per-project unread counts for this client
|
|
1322
1381
|
var unreadMap = getCrossProjectUnread(ws);
|
|
1323
1382
|
var projectsWithUnread = filtered.map(function (p) {
|
|
@@ -1367,6 +1426,10 @@ function createServer(opts) {
|
|
|
1367
1426
|
function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta, extraOpts) {
|
|
1368
1427
|
if (projects.has(slug)) return false;
|
|
1369
1428
|
var extra = extraOpts || {};
|
|
1429
|
+
if (extra.isMate) {
|
|
1430
|
+
var registeredMate = mates.getMate(mates.buildMateCtx(users.isMultiUser() ? projectOwnerId : null), extra.mateId || path.basename(cwd));
|
|
1431
|
+
if (registeredMate && registeredMate.lifecycleDeletion) return false;
|
|
1432
|
+
}
|
|
1370
1433
|
var ctx = createProjectContext({
|
|
1371
1434
|
cwd: cwd,
|
|
1372
1435
|
slug: slug,
|
|
@@ -1386,6 +1449,8 @@ function createServer(opts) {
|
|
|
1386
1449
|
return mateHandler.createReadyMateFromInterview(ws, userId, definition);
|
|
1387
1450
|
},
|
|
1388
1451
|
workspaceQueryService: workspaceQueryService,
|
|
1452
|
+
clayProductControl: clayProductControl,
|
|
1453
|
+
resumeLifecycleProject: function (userId) { return opts.projectLifecycle ? opts.projectLifecycle.resumeRecovered(slug, userId) : true; },
|
|
1389
1454
|
projectLogsService: projectLogsService,
|
|
1390
1455
|
issuesService: issuesService,
|
|
1391
1456
|
linearStorage: linear.storage,
|
|
@@ -1407,13 +1472,19 @@ function createServer(opts) {
|
|
|
1407
1472
|
return !!(access && !access.error && users.canAccessProject(userId, access));
|
|
1408
1473
|
},
|
|
1409
1474
|
getProjectAccess: function () {
|
|
1410
|
-
if (extra.isMate)
|
|
1475
|
+
if (extra.isMate) {
|
|
1476
|
+
var mate = mates.getMate(mates.buildMateCtx(users.isMultiUser() ? projectOwnerId : null), extra.mateId || path.basename(cwd));
|
|
1477
|
+
return { isMate: true, visibility: "private", ownerId: projectOwnerId || null, allowedUsers: [], lifecycleExecutionPaused: !mate || !!mate.lifecycleDeletion };
|
|
1478
|
+
}
|
|
1411
1479
|
return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
|
|
1412
1480
|
},
|
|
1413
1481
|
shouldSuppressResponseNotification: function (session) {
|
|
1414
1482
|
return shouldSuppressHomeResponse(projects, slug, session);
|
|
1415
1483
|
},
|
|
1416
|
-
pushModule: pushModule,
|
|
1484
|
+
pushModule: require("./project-push-access").scopedPush(pushModule, {
|
|
1485
|
+
slug: slug, users: users, getProject: function (target) { return projects.get(target); },
|
|
1486
|
+
canAccessProject: canUserAccessSlug, getAccess: onGetProjectAccess,
|
|
1487
|
+
}),
|
|
1417
1488
|
debug: debug,
|
|
1418
1489
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
1419
1490
|
osUsers: osUsers,
|
|
@@ -1445,6 +1516,7 @@ function createServer(opts) {
|
|
|
1445
1516
|
var status = ctx.getStatus();
|
|
1446
1517
|
var recs = ctx.getSchedules();
|
|
1447
1518
|
for (var i = 0; i < recs.length; i++) {
|
|
1519
|
+
if (users.isMultiUser() && recs[i].ownerId !== userId) continue;
|
|
1448
1520
|
// Shallow-copy full record and augment with project metadata
|
|
1449
1521
|
var copy = {};
|
|
1450
1522
|
var keys = Object.keys(recs[i]);
|
|
@@ -1502,6 +1574,7 @@ function createServer(opts) {
|
|
|
1502
1574
|
return ctx.getSchedules().length;
|
|
1503
1575
|
},
|
|
1504
1576
|
canAccessProjectSlug: canUserAccessSlug,
|
|
1577
|
+
canUsePath: canUsePath,
|
|
1505
1578
|
onPresenceChange: broadcastPresenceChange,
|
|
1506
1579
|
onProcessingChanged: broadcastProcessingChange,
|
|
1507
1580
|
onSessionDone: function () { onSessionDone(slug); },
|
|
@@ -1655,6 +1728,7 @@ function createServer(opts) {
|
|
|
1655
1728
|
}
|
|
1656
1729
|
|
|
1657
1730
|
function handleDmMessage(ws, msg, routedProjectSlug) {
|
|
1731
|
+
if (ownerUiService.handleMessage(ws, msg)) return true;
|
|
1658
1732
|
if (cursorSharingService && cursorSharingService.handleMessage(ws, msg)) return true;
|
|
1659
1733
|
if (defaultVendorService && defaultVendorService.handleMessage(ws, msg, routedProjectSlug)) return true;
|
|
1660
1734
|
if (contextViewService && contextViewService.handleMessage(ws, msg)) return true;
|
|
@@ -1669,19 +1743,8 @@ function createServer(opts) {
|
|
|
1669
1743
|
return false;
|
|
1670
1744
|
}
|
|
1671
1745
|
|
|
1672
|
-
function removeProject(slug) {
|
|
1673
|
-
|
|
1674
|
-
if (!ctx) return false;
|
|
1675
|
-
var wasMate = ctx.getStatus().isMate;
|
|
1676
|
-
var shutdownResult = ctx.destroy();
|
|
1677
|
-
projects.delete(slug);
|
|
1678
|
-
if (shutdownResult && typeof shutdownResult.catch === "function") {
|
|
1679
|
-
shutdownResult.catch(function(err) {
|
|
1680
|
-
console.error("[server] Project destroy failed for " + slug + ":", err && err.message ? err.message : err);
|
|
1681
|
-
});
|
|
1682
|
-
}
|
|
1683
|
-
if (!wasMate) scheduleRegistryRefresh();
|
|
1684
|
-
return true;
|
|
1746
|
+
function removeProject(slug, awaitCompletion) {
|
|
1747
|
+
return require('./server-project-removal').removeProjectRuntime(projects, slug, awaitCompletion, scheduleRegistryRefresh);
|
|
1685
1748
|
}
|
|
1686
1749
|
|
|
1687
1750
|
// Now that addProject and removeProject are defined, initialize mateHandler
|
|
@@ -1767,7 +1830,11 @@ function createServer(opts) {
|
|
|
1767
1830
|
function getVisibleProjects(userId) {
|
|
1768
1831
|
var visible = [];
|
|
1769
1832
|
projects.forEach(function (ctx, slug) {
|
|
1770
|
-
if (users.isMultiUser() && (!userId || !canUserAccessSlug(userId, slug)))
|
|
1833
|
+
if (users.isMultiUser() && (!userId || !canUserAccessSlug(userId, slug))) {
|
|
1834
|
+
var metadata = administrativeMetadata(userId, slug);
|
|
1835
|
+
if (metadata) visible.push(metadata);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1771
1838
|
var status = ctx.getStatus();
|
|
1772
1839
|
if (!status.isMate && onGetProjectAccess) {
|
|
1773
1840
|
var access = onGetProjectAccess(slug);
|
|
@@ -1966,6 +2033,7 @@ function createServer(opts) {
|
|
|
1966
2033
|
speech.destroy();
|
|
1967
2034
|
var shutdowns = [];
|
|
1968
2035
|
shutdowns.push(durableSchedulerLifecycle.shutdown());
|
|
2036
|
+
shutdowns.push(lifecycleRuntime.shutdown());
|
|
1969
2037
|
projects.forEach(function (ctx, slug) {
|
|
1970
2038
|
console.log("[server] Destroying project:", slug);
|
|
1971
2039
|
var result = ctx.destroy();
|
|
@@ -2033,6 +2101,8 @@ function createServer(opts) {
|
|
|
2033
2101
|
isTLS: !!tlsOptions,
|
|
2034
2102
|
addProject: addProject,
|
|
2035
2103
|
removeProject: removeProject,
|
|
2104
|
+
getProjectLifecycleTargets: lifecycleRuntime.targets,
|
|
2105
|
+
closeProjectLifecycle: lifecycleRuntime.close,
|
|
2036
2106
|
getProjects: getProjects,
|
|
2037
2107
|
reorderProjects: reorderProjects,
|
|
2038
2108
|
setProjectTitle: setProjectTitle,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Guarded configuration commit shared by SDK setters and permission transitions.
|
|
2
|
+
// Provider acknowledgement is distinct from local persistence. Once a request
|
|
3
|
+
// has crossed that boundary, failures never imply the provider was rolled back.
|
|
4
|
+
async function applySessionConfig(ctx) {
|
|
5
|
+
var session = ctx.session, sm = ctx.sm, handle = session.queryInstance;
|
|
6
|
+
var generation = Number(session._sdkQueryGeneration || 0), owner = session.ownerId;
|
|
7
|
+
var paused = !!session._pendingMessageDrainPaused, signal = session.abortController && session.abortController.signal;
|
|
8
|
+
var attempted = false, acknowledged = false, persisted = false, previous = {};
|
|
9
|
+
function current() {
|
|
10
|
+
ctx.revalidate();
|
|
11
|
+
if (sm.sessions.get(session.localId) !== session || session.ownerId !== owner || session.queryInstance !== handle ||
|
|
12
|
+
Number(session._sdkQueryGeneration || 0) !== generation || session.taskStopRequested ||
|
|
13
|
+
!!session._pendingMessageDrainPaused !== paused || signal && signal.aborted) throw new Error("Target ownership, runtime, or Stop state changed during the configuration request.");
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
current();
|
|
17
|
+
if (handle) {
|
|
18
|
+
if (typeof handle[ctx.method] !== "function") throw new Error("This provider cannot apply this configuration to the active runtime.");
|
|
19
|
+
attempted = true;
|
|
20
|
+
var reply = await handle[ctx.method](ctx.nativeValue);
|
|
21
|
+
if (reply === false || reply && reply.ok === false) throw new Error(reply && reply.error || "The provider refused the configuration change.");
|
|
22
|
+
acknowledged = true;
|
|
23
|
+
current();
|
|
24
|
+
}
|
|
25
|
+
var changes = typeof ctx.changes === "function" ? ctx.changes() : ctx.changes;
|
|
26
|
+
Object.keys(changes).forEach(function (key) { previous[key] = { present: Object.prototype.hasOwnProperty.call(session, key), value: session[key] }; });
|
|
27
|
+
Object.assign(session, changes);
|
|
28
|
+
try {
|
|
29
|
+
if (sm.saveSessionFile(session) !== true) throw new Error("Could not persist the configuration change.");
|
|
30
|
+
persisted = true;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
Object.keys(previous).forEach(function (key) { if (previous[key].present) session[key] = previous[key].value; else delete session[key]; });
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
// There is no await between the last authorization check, disk commit and
|
|
36
|
+
// publication. Never broadcast a desired value before its save succeeds.
|
|
37
|
+
ctx.publish();
|
|
38
|
+
return Object.assign({ ok: true, status: "completed", persisted: true, runtimeEffect: acknowledged ? "applied" : "none" }, changes);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return { ok: false, status: "failed", outcome: persisted ? "partial" : attempted ? "uncertain" : "failed", persisted: persisted,
|
|
41
|
+
runtimeEffect: acknowledged ? "applied" : attempted ? "unknown" : "none", retrySafe: !attempted && !persisted,
|
|
42
|
+
error: (persisted ? "The configuration was saved, but publication did not complete. " : "") + (attempted ? "The provider may retain the requested change; no external rollback was attempted. " : "") + (error.message || String(error)) };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
module.exports = { applySessionConfig: applySessionConfig };
|
package/lib/session-folders.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// also keeps its folder (or Unfiled) assignment
|
|
10
10
|
// orders: { containerKey: [keys] } manual order per container ("unfiled", custom id, "all")
|
|
11
11
|
// collapsed: { containerKey: true }
|
|
12
|
-
// view: { group, sort, direction }
|
|
12
|
+
// view: { group, sort, direction, showDateHeadings }
|
|
13
13
|
// sessionKey is the session's opaque, durable sessionOriginId.
|
|
14
14
|
|
|
15
15
|
var crypto = require("crypto");
|
|
@@ -35,7 +35,7 @@ function defaultState() {
|
|
|
35
35
|
favorites: [],
|
|
36
36
|
orders: {},
|
|
37
37
|
collapsed: {},
|
|
38
|
-
view: { group: "folders", sort: "activity", direction: "desc" },
|
|
38
|
+
view: { group: "folders", sort: "activity", direction: "desc", showDateHeadings: true },
|
|
39
39
|
legacyFavoritesMigrated: false,
|
|
40
40
|
};
|
|
41
41
|
}
|
|
@@ -147,6 +147,7 @@ function normalizeState(raw) {
|
|
|
147
147
|
var view = isPlainObject(source.view) ? source.view : {};
|
|
148
148
|
if (SORTS.indexOf(view.sort) !== -1) state.view.sort = view.sort;
|
|
149
149
|
if (DIRECTIONS.indexOf(view.direction) !== -1) state.view.direction = view.direction;
|
|
150
|
+
if (typeof view.showDateHeadings === "boolean") state.view.showDateHeadings = view.showDateHeadings;
|
|
150
151
|
state.legacyFavoritesMigrated = source.legacyFavoritesMigrated === true;
|
|
151
152
|
return state;
|
|
152
153
|
}
|
|
@@ -334,6 +335,10 @@ function applyRaw(input, op, env) {
|
|
|
334
335
|
if (DIRECTIONS.indexOf(op.direction) === -1) return { error: "Invalid direction" };
|
|
335
336
|
state.view.direction = op.direction;
|
|
336
337
|
}
|
|
338
|
+
if (op.showDateHeadings !== undefined) {
|
|
339
|
+
if (typeof op.showDateHeadings !== "boolean") return { error: "Invalid date heading setting" };
|
|
340
|
+
state.view.showDateHeadings = op.showDateHeadings;
|
|
341
|
+
}
|
|
337
342
|
return { state: state };
|
|
338
343
|
}
|
|
339
344
|
|
package/lib/session-github.js
CHANGED
|
@@ -33,22 +33,29 @@ function runGh(cwd, args, identity, input) {
|
|
|
33
33
|
}
|
|
34
34
|
async function readReference(cwd, url, identity, run, statusOnly) {
|
|
35
35
|
var ref = parseReference(url);
|
|
36
|
-
var fields = "title,state,url" + (ref.kind === "pr" ? (statusOnly ? ",isDraft" : ",isDraft,headRefName,reviewDecision,statusCheckRollup") : "");
|
|
36
|
+
var fields = "title,state,url" + (ref.kind === "pr" ? (statusOnly ? ",isDraft,reviewDecision,reviewRequests" : ",isDraft,headRefName,reviewDecision,statusCheckRollup") : "");
|
|
37
37
|
var data = await (run || runGh)(cwd, [ref.kind === "pr" ? "pr" : "issue", "view", String(ref.number), "--repo", ref.repository, "--json", fields], identity);
|
|
38
38
|
var verified = parseReference(data.url);
|
|
39
39
|
if (verified.url.toLowerCase() !== ref.url.toLowerCase()) throw new Error("GitHub returned a different reference.");
|
|
40
40
|
ref.title = String(data.title || "").slice(0, 200);
|
|
41
41
|
ref.state = data.state === "MERGED" ? "merged" : data.state === "CLOSED" ? "closed" : data.state === "OPEN" ? (data.isDraft ? "draft" : "open") : "unknown";
|
|
42
42
|
ref.checkedAt = Date.now();
|
|
43
|
+
if (ref.kind === "pr") {
|
|
44
|
+
ref.review = ["APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"].indexOf(data.reviewDecision) >= 0 ? data.reviewDecision : "";
|
|
45
|
+
}
|
|
43
46
|
if (ref.kind === "pr" && !statusOnly) {
|
|
44
47
|
ref.branch = String(data.headRefName || "").slice(0, 200);
|
|
45
|
-
ref.review = ["APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"].indexOf(data.reviewDecision) >= 0 ? data.reviewDecision : "";
|
|
46
48
|
ref.checks = (data.statusCheckRollup || []).slice(0, 30).map(function (check) {
|
|
47
49
|
var target = check.detailsUrl || check.targetUrl || "";
|
|
48
50
|
if (!/^https:\/\//i.test(target)) target = "";
|
|
49
51
|
return { name: String(check.name || check.context || "Check").slice(0, 100), state: String(check.conclusion || check.state || check.status || "PENDING").slice(0, 40), url: target };
|
|
50
52
|
});
|
|
51
53
|
}
|
|
54
|
+
if (ref.kind === "pr" && statusOnly) {
|
|
55
|
+
ref.reviewRequests = Array.isArray(data.reviewRequests) ? data.reviewRequests.slice(0, 30).map(function (request) {
|
|
56
|
+
return String(request.login || "").trim().toLowerCase().slice(0, 100);
|
|
57
|
+
}).filter(Boolean) : [];
|
|
58
|
+
}
|
|
52
59
|
return ref;
|
|
53
60
|
}
|
|
54
61
|
module.exports = { parseReference: parseReference, readReference: readReference, runGh: runGh };
|
|
@@ -54,7 +54,7 @@ function handleDefaultChange(ctx, ws, msg) {
|
|
|
54
54
|
? ctx.usersModule.getEffectivePermissions(user, ctx.osUsers) : {};
|
|
55
55
|
var isOwner = !!(user && access && access.ownerId && access.ownerId === user.id);
|
|
56
56
|
var isAdmin = !!(user && user.role === "admin");
|
|
57
|
-
if (!user || !permissions.projectSettings ||
|
|
57
|
+
if (!user || !permissions.projectSettings || !isOwner) {
|
|
58
58
|
defaultChangeResult(ctx, ws, msg, { ok: false, error: "Project settings access is not permitted" });
|
|
59
59
|
return true;
|
|
60
60
|
}
|
|
@@ -98,7 +98,10 @@ function revokeViewers(ctx, session) {
|
|
|
98
98
|
// Clear the server-side selection before reconnecting so no further
|
|
99
99
|
// session events or input can cross the revoked access boundary.
|
|
100
100
|
client._clayActiveSession = null;
|
|
101
|
-
if (client.readyState === 1)
|
|
101
|
+
if (client.readyState === 1) {
|
|
102
|
+
if (ctx.sendTo) ctx.sendTo(client, { type: "access_revoked", slug: ctx.slug });
|
|
103
|
+
client.close(1008, "Session access changed");
|
|
104
|
+
}
|
|
102
105
|
});
|
|
103
106
|
}
|
|
104
107
|
}
|
package/lib/sessions.js
CHANGED
|
@@ -227,6 +227,11 @@ function createSessionManager(opts) {
|
|
|
227
227
|
if (session.homeDebatePlanning) metaObj.homeDebatePlanning = true;
|
|
228
228
|
if (session.homeDebatePhase) metaObj.homeDebatePhase = session.homeDebatePhase;
|
|
229
229
|
if (session.homeDebateInitialTopic) metaObj.homeDebateInitialTopic = session.homeDebateInitialTopic;
|
|
230
|
+
if (session.mateScheduleDraft) metaObj.mateScheduleDraft = session.mateScheduleDraft;
|
|
231
|
+
if (session.mateSchedules) metaObj.mateSchedules = session.mateSchedules;
|
|
232
|
+
if (session.clayWorkspaceReceipts) metaObj.clayWorkspaceReceipts = session.clayWorkspaceReceipts;
|
|
233
|
+
if (session.clayMateChatReceipts) metaObj.clayMateChatReceipts = session.clayMateChatReceipts;
|
|
234
|
+
if (session.claySchedulingReceipts) metaObj.claySchedulingReceipts = session.claySchedulingReceipts;
|
|
230
235
|
if (session.mateCreationMode) metaObj.mateCreationMode = true;
|
|
231
236
|
if (session.homeMateCreationPhase) metaObj.homeMateCreationPhase = session.homeMateCreationPhase;
|
|
232
237
|
if (session.homeClayEntryRequestId) metaObj.homeClayEntryRequestId = session.homeClayEntryRequestId;
|
|
@@ -386,6 +391,11 @@ function createSessionManager(opts) {
|
|
|
386
391
|
if (m.homeDebatePlanning) session.homeDebatePlanning = true;
|
|
387
392
|
if (m.homeDebatePhase) session.homeDebatePhase = m.homeDebatePhase;
|
|
388
393
|
if (m.homeDebateInitialTopic) session.homeDebateInitialTopic = m.homeDebateInitialTopic;
|
|
394
|
+
if (m.mateScheduleDraft) session.mateScheduleDraft = m.mateScheduleDraft;
|
|
395
|
+
if (m.mateSchedules) session.mateSchedules = m.mateSchedules;
|
|
396
|
+
if (Array.isArray(m.clayWorkspaceReceipts)) session.clayWorkspaceReceipts = m.clayWorkspaceReceipts;
|
|
397
|
+
if (Array.isArray(m.clayMateChatReceipts)) session.clayMateChatReceipts = m.clayMateChatReceipts;
|
|
398
|
+
if (Array.isArray(m.claySchedulingReceipts)) session.claySchedulingReceipts = m.claySchedulingReceipts;
|
|
389
399
|
if (m.mateCreationMode) session.mateCreationMode = true;
|
|
390
400
|
if (m.homeMateCreationPhase) session.homeMateCreationPhase = m.homeMateCreationPhase;
|
|
391
401
|
if (m.homeClayEntryRequestId) session.homeClayEntryRequestId = m.homeClayEntryRequestId;
|
package/lib/terminal-manager.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var { createTerminal } = require("./terminal");
|
|
2
|
+
var crypto = require("crypto");
|
|
2
3
|
|
|
3
4
|
var MAX_TERMINALS = 10;
|
|
4
5
|
var SCROLLBACK_MAX = 50 * 1024; // 50 KB per terminal
|
|
@@ -48,6 +49,7 @@ function createTerminalManager(opts) {
|
|
|
48
49
|
var id = nextId++;
|
|
49
50
|
var session = {
|
|
50
51
|
id: id,
|
|
52
|
+
identity: crypto.randomUUID(),
|
|
51
53
|
pty: pty,
|
|
52
54
|
scrollback: [],
|
|
53
55
|
scrollbackSize: 0,
|
|
@@ -59,8 +61,11 @@ function createTerminalManager(opts) {
|
|
|
59
61
|
lastActivityAt: Date.now(),
|
|
60
62
|
exited: false,
|
|
61
63
|
exitCode: null,
|
|
64
|
+
exitWaiters: [],
|
|
62
65
|
subscribers: new Set(),
|
|
63
66
|
ownerWs: ownerWs || null,
|
|
67
|
+
ownerId: ownerWs && ownerWs._clayUser ? ownerWs._clayUser.id : opts && opts.ownerId || null,
|
|
68
|
+
osIdentity: osUserInfo ? JSON.stringify([osUserInfo.user, osUserInfo.uid, osUserInfo.gid, osUserInfo.home]) : null,
|
|
64
69
|
onExitHook: (opts && typeof opts.onExit === "function") ? opts.onExit : null,
|
|
65
70
|
onDataHook: (opts && typeof opts.onData === "function") ? opts.onData : null,
|
|
66
71
|
};
|
|
@@ -95,6 +100,7 @@ function createTerminalManager(opts) {
|
|
|
95
100
|
session.exited = true;
|
|
96
101
|
session.exitCode = e && e.exitCode != null ? e.exitCode : null;
|
|
97
102
|
session.pty = null;
|
|
103
|
+
session.exitWaiters.splice(0).forEach(function (resolve) { resolve(true); });
|
|
98
104
|
|
|
99
105
|
var msg = JSON.stringify({ type: "term_exited", id: id });
|
|
100
106
|
for (var ws of session.subscribers) {
|
|
@@ -204,6 +210,34 @@ function createTerminalManager(opts) {
|
|
|
204
210
|
if (terminals.size === 0) nextId = 1;
|
|
205
211
|
}
|
|
206
212
|
|
|
213
|
+
// Keep the terminal registered until its actual exit acknowledges shutdown.
|
|
214
|
+
async function closeAndWait(id, control) {
|
|
215
|
+
var session = terminals.get(id);
|
|
216
|
+
if (!session) throw new Error("Terminal closure cannot be confirmed: runtime is missing.");
|
|
217
|
+
if (control) { control.revalidate(); if (session.identity !== control.identity) throw new Error('The exact terminal identity changed.'); }
|
|
218
|
+
if (!session.exited) {
|
|
219
|
+
if (!session.pty) throw new Error("Terminal closure is unconfirmed.");
|
|
220
|
+
var timer, waiter;
|
|
221
|
+
var exited = new Promise(function (resolve, reject) {
|
|
222
|
+
waiter = resolve; session.exitWaiters.push(waiter);
|
|
223
|
+
timer = setTimeout(function () { reject(new Error("Terminal exit acknowledgement timed out.")); }, 5000);
|
|
224
|
+
});
|
|
225
|
+
try { session.pty.kill(); await exited; }
|
|
226
|
+
catch (error) { exited.catch(function () {}); throw error; }
|
|
227
|
+
finally { clearTimeout(timer); var index = session.exitWaiters.indexOf(waiter); if (index >= 0) session.exitWaiters.splice(index, 1); }
|
|
228
|
+
}
|
|
229
|
+
if (terminals.get(id) !== session) {
|
|
230
|
+
var replaced = new Error('Terminal was replaced during shutdown.');
|
|
231
|
+
if (control) replaced.productControlResult = { runtimeEffect: session.exited ? 'exited' : 'closure_unconfirmed', replacementRetained: true, retrySafe: false };
|
|
232
|
+
throw replaced;
|
|
233
|
+
}
|
|
234
|
+
if (control) {
|
|
235
|
+
try { control.revalidate(); }
|
|
236
|
+
catch (error) { error.productControlResult = { runtimeEffect: 'exited', retained: true, retrySafe: false }; throw error; }
|
|
237
|
+
}
|
|
238
|
+
close(id); return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
207
241
|
function rename(id, title) {
|
|
208
242
|
var session = terminals.get(id);
|
|
209
243
|
if (!session) return;
|
|
@@ -298,6 +332,24 @@ function createTerminalManager(opts) {
|
|
|
298
332
|
write: write,
|
|
299
333
|
resize: resize,
|
|
300
334
|
close: close,
|
|
335
|
+
closeAndWait: closeAndWait,
|
|
336
|
+
inspect: function (id) {
|
|
337
|
+
var terminal = terminals.get(id);
|
|
338
|
+
return terminal ? { id: id, identity: terminal.identity, ownerId: terminal.ownerId, osIdentity: terminal.osIdentity, title: terminal.title, kind: terminal.kind, exited: terminal.exited, cols: terminal.cols, rows: terminal.rows, totalBytesWritten: terminal.totalBytesWritten } : null;
|
|
339
|
+
},
|
|
340
|
+
resizeExact: function (id, cols, rows, identity, revalidate) {
|
|
341
|
+
revalidate();
|
|
342
|
+
var terminal = terminals.get(id);
|
|
343
|
+
if (!terminal || terminal.identity !== identity || !terminal.pty) throw new Error('The exact terminal is unavailable for resizing.');
|
|
344
|
+
terminal.pty.resize(cols, rows);
|
|
345
|
+
revalidate();
|
|
346
|
+
if (terminals.get(id) !== terminal) throw new Error('Terminal was replaced during resize.');
|
|
347
|
+
terminal.cols = cols; terminal.rows = rows;
|
|
348
|
+
var message = JSON.stringify({ type: 'term_resized', id: id, cols: cols, rows: rows });
|
|
349
|
+
terminal.subscribers.forEach(function (ws) { if (ws.readyState === 1) ws.send(message); });
|
|
350
|
+
return true;
|
|
351
|
+
},
|
|
352
|
+
identity: function (id) { var terminal = terminals.get(id); return terminal ? terminal.identity : null; },
|
|
301
353
|
rename: rename,
|
|
302
354
|
list: list,
|
|
303
355
|
getScrollback: getScrollback,
|
package/lib/users-permissions.js
CHANGED
|
@@ -64,15 +64,12 @@ function attachPermissions(deps) {
|
|
|
64
64
|
// --- Project access helpers ---
|
|
65
65
|
|
|
66
66
|
function canAccessProject(userId, project) {
|
|
67
|
-
if (!project) return false;
|
|
67
|
+
if (!project || !userId || !findUserById(userId) || project.ownershipRecovery === "required") return false;
|
|
68
68
|
if (project.isMate === true || (typeof project.slug === "string" && project.slug.indexOf("mate-") === 0)) {
|
|
69
69
|
return !!userId && !!project.ownerId && project.ownerId === userId;
|
|
70
70
|
}
|
|
71
71
|
// Public projects are accessible to all authenticated users
|
|
72
72
|
if (!project.visibility || project.visibility === "public") return true;
|
|
73
|
-
// Admin always has access
|
|
74
|
-
var user = findUserById(userId);
|
|
75
|
-
if (user && user.role === "admin") return true;
|
|
76
73
|
// Owner always has access to their own project
|
|
77
74
|
if (project.ownerId && project.ownerId === userId) return true;
|
|
78
75
|
// Private project -- check allowedUsers
|
|
@@ -96,10 +93,7 @@ function attachPermissions(deps) {
|
|
|
96
93
|
return !!userId && !!session.ownerId && session.ownerId === userId;
|
|
97
94
|
}
|
|
98
95
|
if (session.userKnowledgeOwnerId) return !!userId && session.userKnowledgeOwnerId === userId && session.ownerId === userId;
|
|
99
|
-
//
|
|
100
|
-
var user = findUserById(userId);
|
|
101
|
-
if (user && user.role === "admin") return true;
|
|
102
|
-
// Sessions without ownerId are legacy -- only admin can see them
|
|
96
|
+
// Unowned historical sessions require explicit recovery; role is not ownership.
|
|
103
97
|
if (!session.ownerId) return false;
|
|
104
98
|
// Owner can always see their own sessions
|
|
105
99
|
if (session.ownerId === userId) return true;
|
package/lib/users.js
CHANGED
|
@@ -155,6 +155,10 @@ function getOtherUsers(excludeUserId) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
function removeUser(userId) {
|
|
158
|
+
var registered = require("./config").loadConfig();
|
|
159
|
+
if (registered && (registered.projects || []).some(function (project) {
|
|
160
|
+
return !require("./project-ownership").isPersonal(project) && project.ownerId === userId;
|
|
161
|
+
})) return { error: "Transfer ordinary owned projects before deleting this account." };
|
|
158
162
|
var data = loadUsers();
|
|
159
163
|
var before = data.users.length;
|
|
160
164
|
data.users = data.users.filter(function (u) { return u.id !== userId; });
|
|
@@ -96,7 +96,10 @@ function attachWorkspaceQueryAccess(deps) {
|
|
|
96
96
|
// as ownership would let a stale status skip the authoritative record.
|
|
97
97
|
var access = record(status);
|
|
98
98
|
if (!access) return denied;
|
|
99
|
-
if (access.ownerId && access.ownerId === principal.userId)
|
|
99
|
+
if (access.ownerId && access.ownerId === principal.userId) {
|
|
100
|
+
var ownerPermitted = permitted(principal.userId, access);
|
|
101
|
+
return { owned: ownerPermitted, accessible: ownerPermitted };
|
|
102
|
+
}
|
|
100
103
|
return { owned: false, accessible: permitted(principal.userId, access) };
|
|
101
104
|
}
|
|
102
105
|
|
package/lib/ws-schema.js
CHANGED
|
@@ -495,10 +495,11 @@ var schema = {
|
|
|
495
495
|
"note_written": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Agent created or updated a sticky note" },
|
|
496
496
|
"note_update": { direction: "c2s", handler: "lib/project-user-message.js", description: "Update a sticky note" },
|
|
497
497
|
"note_updated": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Sticky note was updated" },
|
|
498
|
-
"
|
|
498
|
+
"note_update_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Correlated sticky note update result" },
|
|
499
|
+
"note_close": { direction: "c2s", handler: "lib/project-user-message.js", description: "Close a sticky note; human draft-aware requests remove empty notes, tool and legacy closes stay reversible" },
|
|
499
500
|
"note_reopen": { direction: "c2s", handler: "lib/project-user-message.js", description: "Reopen a closed sticky note" },
|
|
500
501
|
"note_delete": { direction: "c2s", handler: "lib/project-user-message.js", description: "Retired: older clients still send this; handled as a non-destructive close" },
|
|
501
|
-
"note_deleted": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "
|
|
502
|
+
"note_deleted": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "A sticky note was permanently removed after a human-authorized storage transaction" },
|
|
502
503
|
"note_list_request": { direction: "c2s", handler: "lib/project-user-message.js", description: "Request the full notes list" },
|
|
503
504
|
"note_bring_front": { direction: "c2s", handler: "lib/project-user-message.js", description: "Bring a note to front (update z-order)" },
|
|
504
505
|
"notes_list": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full list of sticky notes" },
|
|
@@ -809,6 +810,11 @@ var schema = {
|
|
|
809
810
|
"default_vendor_get": { direction: "c2s", handler: "lib/server-default-vendor.js", description: "Read the authenticated user's New session vendor preference" },
|
|
810
811
|
"default_vendor_set": { direction: "c2s", handler: "lib/server-default-vendor.js", description: "Validate and persist the authenticated user's New session vendor preference" },
|
|
811
812
|
"default_vendor_state": { direction: "s2c", handler: "lib/public/modules/default-vendor.js", description: "Correlated or same-user-broadcast New session vendor preference" },
|
|
813
|
+
"clay_ui_ready": { direction: "c2s", handler: "lib/server-owner-ui.js", description: "Advertise exact owner UI navigation support on this socket" },
|
|
814
|
+
"clay_navigation_offer": { direction: "s2c", handler: "lib/public/modules/clay-owner-navigation.js", description: "Offer one exact reviewed navigation to a selected owner connection" },
|
|
815
|
+
"clay_navigation_claim": { direction: "c2s", handler: "lib/server-owner-ui.js", description: "Claim an offered navigation with live source/target revalidation" },
|
|
816
|
+
"clay_navigation_apply": { direction: "s2c", handler: "lib/public/modules/clay-owner-navigation.js", description: "Invoke the shared conversation navigator after live claim" },
|
|
817
|
+
"clay_navigation_ack": { direction: "c2s", handler: "lib/server-owner-ui.js", description: "Client invocation acknowledgement; never proof of visible rendering" },
|
|
812
818
|
"context_view_get": { direction: "c2s", handler: "lib/server-context-view.js", description: "Read the authenticated user's context display preference" },
|
|
813
819
|
"context_view_set": { direction: "c2s", handler: "lib/server-context-view.js", description: "Persist the authenticated user's context display preference" },
|
|
814
820
|
"context_view_state": { direction: "s2c", handler: "lib/public/modules/context-view-preference.js", description: "Correlated or same-user-broadcast context display preference" },
|