clay-server 5.2.0-beta.3 → 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 +19 -16
- package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
- 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-file-store.js +7 -1
- package/lib/knowledge-product-operations.js +46 -0
- package/lib/mate-avatar-catalog.js +12 -0
- package/lib/mate-avatar-migration.js +41 -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 +18 -6
- package/lib/mcp-agent-tools.js +117 -0
- package/lib/mcp-connection-store.js +119 -15
- package/lib/mcp-import-credentials.js +31 -0
- package/lib/mcp-local-transport.js +13 -0
- package/lib/mcp-oauth.js +8 -6
- package/lib/mcp-query-bridge.js +30 -0
- package/lib/mcp-remote-connections.js +74 -20
- package/lib/mcp-source-adapters.js +91 -0
- package/lib/mcp-source-context.js +42 -0
- package/lib/mcp-source-discovery.js +169 -0
- package/lib/mcp-source-plugins.js +68 -0
- package/lib/mcp-source-restrictions.js +47 -0
- package/lib/mcp-tool-policy.js +34 -0
- 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 +8 -4
- package/lib/project-debate.js +1 -0
- 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-mate-interaction.js +8 -0
- package/lib/project-mcp.js +54 -16
- 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-document.js +8 -3
- package/lib/project-session-github.js +63 -19
- package/lib/project-session-history-actions.js +165 -0
- package/lib/project-session-linear.js +14 -6
- package/lib/project-session-notes.js +7 -3
- package/lib/project-session-runtime-actions.js +63 -0
- package/lib/project-session-vendor.js +54 -0
- package/lib/project-sessions.js +68 -241
- package/lib/project-shared-browser.js +56 -12
- 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 +144 -37
- package/lib/public/app.js +5 -0
- package/lib/public/appearance-bootstrap.js +14 -0
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/clay-workspace.css +29 -0
- package/lib/public/css/home-debate-planning.css +2 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- package/lib/public/css/mate-avatar-gallery.css +12 -0
- package/lib/public/css/mate-conversation.css +10 -5
- 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 +46 -1
- package/lib/public/css/shared-browser.css +35 -1
- package/lib/public/css/sticky-notes.css +61 -1
- package/lib/public/index.html +11 -5
- package/lib/public/mate-avatars/B01.png +0 -0
- package/lib/public/mate-avatars/B02.png +0 -0
- package/lib/public/mate-avatars/B03.png +0 -0
- package/lib/public/mate-avatars/B04.png +0 -0
- package/lib/public/mate-avatars/B05.png +0 -0
- package/lib/public/mate-avatars/B06.png +0 -0
- package/lib/public/mate-avatars/B07.png +0 -0
- package/lib/public/mate-avatars/B08.png +0 -0
- package/lib/public/mate-avatars/B09.png +0 -0
- package/lib/public/mate-avatars/B10.png +0 -0
- package/lib/public/mate-avatars/B11.png +0 -0
- package/lib/public/mate-avatars/B12.png +0 -0
- package/lib/public/mate-avatars/B13.png +0 -0
- package/lib/public/mate-avatars/B14.png +0 -0
- package/lib/public/mate-avatars/B15.png +0 -0
- package/lib/public/mate-avatars/B16.png +0 -0
- package/lib/public/mate-avatars/B17.png +0 -0
- package/lib/public/mate-avatars/B18.png +0 -0
- package/lib/public/mate-avatars/B19.png +0 -0
- package/lib/public/mate-avatars/B20.png +0 -0
- package/lib/public/mate-avatars/B21.png +0 -0
- package/lib/public/mate-avatars/B22.png +0 -0
- package/lib/public/mate-avatars/B23.png +0 -0
- package/lib/public/mate-avatars/B24.png +0 -0
- package/lib/public/mate-avatars/B25.png +0 -0
- package/lib/public/mate-avatars/B26.png +0 -0
- package/lib/public/mate-avatars/B27.png +0 -0
- package/lib/public/mate-avatars/B28.png +0 -0
- package/lib/public/mate-avatars/B29.png +0 -0
- package/lib/public/mate-avatars/B30.png +0 -0
- package/lib/public/mate-avatars/B31.png +0 -0
- package/lib/public/mate-avatars/B32.png +0 -0
- package/lib/public/mate-avatars/B33.png +0 -0
- package/lib/public/mate-avatars/B34.png +0 -0
- package/lib/public/mate-avatars/B35.png +0 -0
- package/lib/public/mate-avatars/B36.png +0 -0
- package/lib/public/mate-avatars/B37.png +0 -0
- package/lib/public/mate-avatars/B38.png +0 -0
- package/lib/public/mate-avatars/B39.png +0 -0
- package/lib/public/mate-avatars/B40.png +0 -0
- package/lib/public/mate-avatars/B41.png +0 -0
- package/lib/public/mate-avatars/B42.png +0 -0
- package/lib/public/mate-avatars/B43.png +0 -0
- package/lib/public/mate-avatars/B44.png +0 -0
- package/lib/public/mate-avatars/B45.png +0 -0
- package/lib/public/mate-avatars/B46.png +0 -0
- package/lib/public/mate-avatars/B47.png +0 -0
- package/lib/public/mate-avatars/B48.png +0 -0
- package/lib/public/mate-avatars/C01.png +0 -0
- package/lib/public/mate-avatars/C02.png +0 -0
- package/lib/public/mate-avatars/C03.png +0 -0
- package/lib/public/mate-avatars/C04.png +0 -0
- package/lib/public/mate-avatars/C05.png +0 -0
- package/lib/public/mate-avatars/C06.png +0 -0
- package/lib/public/mate-avatars/C07.png +0 -0
- package/lib/public/mate-avatars/C08.png +0 -0
- package/lib/public/mate-avatars/C09.png +0 -0
- package/lib/public/mate-avatars/C10.png +0 -0
- package/lib/public/mate-avatars/C11.png +0 -0
- package/lib/public/mate-avatars/C12.png +0 -0
- package/lib/public/mate-avatars/C13.png +0 -0
- package/lib/public/mate-avatars/C14.png +0 -0
- package/lib/public/mate-avatars/C15.png +0 -0
- package/lib/public/mate-avatars/C16.png +0 -0
- package/lib/public/mate-avatars/C17.png +0 -0
- package/lib/public/mate-avatars/C18.png +0 -0
- package/lib/public/mate-avatars/C19.png +0 -0
- package/lib/public/mate-avatars/C20.png +0 -0
- package/lib/public/mate-avatars/C21.png +0 -0
- package/lib/public/mate-avatars/C22.png +0 -0
- package/lib/public/mate-avatars/C23.png +0 -0
- package/lib/public/mate-avatars/C24.png +0 -0
- package/lib/public/mate-avatars/C25.png +0 -0
- package/lib/public/mate-avatars/C26.png +0 -0
- package/lib/public/mate-avatars/C27.png +0 -0
- package/lib/public/mate-avatars/C28.png +0 -0
- package/lib/public/mate-avatars/C29.png +0 -0
- package/lib/public/mate-avatars/C30.png +0 -0
- package/lib/public/mate-avatars/C31.png +0 -0
- package/lib/public/mate-avatars/C32.png +0 -0
- package/lib/public/mate-avatars/C33.png +0 -0
- package/lib/public/mate-avatars/C34.png +0 -0
- package/lib/public/mate-avatars/C35.png +0 -0
- package/lib/public/mate-avatars/C36.png +0 -0
- package/lib/public/mate-avatars/C37.png +0 -0
- package/lib/public/mate-avatars/C38.png +0 -0
- package/lib/public/mate-avatars/C39.png +0 -0
- package/lib/public/mate-avatars/C40.png +0 -0
- package/lib/public/mate-avatars/C41.png +0 -0
- package/lib/public/mate-avatars/C42.png +0 -0
- package/lib/public/mate-avatars/C43.png +0 -0
- package/lib/public/mate-avatars/C44.png +0 -0
- package/lib/public/mate-avatars/C45.png +0 -0
- package/lib/public/mate-avatars/C46.png +0 -0
- package/lib/public/mate-avatars/C47.png +0 -0
- package/lib/public/mate-avatars/C48.png +0 -0
- package/lib/public/mate-avatars/catalog.json +482 -0
- package/lib/public/mate-avatars/lead-clay.png +0 -0
- 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 +4 -0
- package/lib/public/modules/app-messages.js +31 -5
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/app-rendering.js +1 -1
- package/lib/public/modules/avatar.js +15 -41
- package/lib/public/modules/chat-render-runtime.js +10 -1
- package/lib/public/modules/clay-browser-links.js +47 -0
- package/lib/public/modules/clay-log-links.js +1 -1
- 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 +2 -2
- package/lib/public/modules/debate.js +1 -0
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/history-replay-batch.js +5 -1
- package/lib/public/modules/home-debate-live.js +2 -0
- package/lib/public/modules/home-debate-planning.js +7 -7
- package/lib/public/modules/home-mate-avatar-editor.js +127 -208
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/home-mate-settings.js +19 -12
- package/lib/public/modules/markdown.js +4 -0
- package/lib/public/modules/mate-avatar-catalog.js +97 -0
- package/lib/public/modules/mate-management.js +40 -7
- package/lib/public/modules/mcp-connection-view.js +31 -13
- package/lib/public/modules/mcp-discovery-view.js +31 -0
- package/lib/public/modules/mcp-ui.js +23 -4
- package/lib/public/modules/mention.js +3 -0
- package/lib/public/modules/pane-browser-bridge.js +20 -0
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/profile.js +1 -238
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/project-mate-navigation.js +99 -14
- package/lib/public/modules/search-clay-chat.js +16 -2
- 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 +85 -14
- package/lib/public/modules/session-work-primary.js +51 -0
- package/lib/public/modules/shared-browser-settings.js +112 -0
- package/lib/public/modules/shared-browser-tabs.js +13 -3
- package/lib/public/modules/shared-browser.js +27 -6
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/split-view.js +2 -0
- 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/theme.js +8 -1
- package/lib/public/modules/thinking-lifecycle.js +33 -3
- package/lib/public/modules/thinking-view.js +23 -1
- package/lib/public/modules/tools.js +36 -7
- 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 +3 -0
- package/lib/public/sw.js +3 -1
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +228 -23
- 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-appearance-settings.js +13 -1
- package/lib/server-dm.js +2 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +18 -4
- package/lib/server-home-chat.js +12 -16
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +14 -0
- package/lib/server-mates.js +25 -62
- 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-settings.js +4 -1
- package/lib/server.js +144 -41
- 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/session-work-primary.js +50 -0
- package/lib/sessions.js +13 -0
- package/lib/shared-browser-cookie-sync.js +32 -0
- package/lib/shared-browser-cookies.js +79 -0
- package/lib/shared-browser-runtime.js +25 -9
- package/lib/shared-browser-storage-sync.js +43 -0
- package/lib/shared-browser-storage.js +101 -0
- package/lib/shared-browser-tools.js +2 -1
- package/lib/shared-browser-worker.js +46 -10
- 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/acp-agent-profiles.js +2 -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 +5 -1
package/lib/server-mates.js
CHANGED
|
@@ -74,12 +74,26 @@ function attachMates(ctx) {
|
|
|
74
74
|
var mateSlug = "mate-" + mate.id;
|
|
75
75
|
var mateName = (mate.profile && mate.profile.displayName) || mate.name || "Mate";
|
|
76
76
|
var registered = false;
|
|
77
|
+
var boundScheduleJob = null;
|
|
77
78
|
var favorites;
|
|
78
79
|
try {
|
|
79
80
|
registered = addProject(mateDir, mateSlug, mateName, null, mate.createdBy || effectiveUserId, null, { isMate: true, mateDisplayName: mateName }) !== false;
|
|
80
81
|
if (!registered) throw new Error("The Mate project could not be registered.");
|
|
82
|
+
var intent = mate.seedData && mate.seedData.scheduleIntent;
|
|
83
|
+
if (intent && intent.message && intent.scheduleRule) {
|
|
84
|
+
var targetProject = projects.get(mateSlug);
|
|
85
|
+
var targetManager = targetProject && targetProject.getSessionManager();
|
|
86
|
+
if (!targetProject || !targetManager || typeof targetProject.bindMateSchedule !== "function") throw new Error("The Mate schedule could not be bound to the new Mate.");
|
|
87
|
+
var targetSession = targetManager.createSessionRaw({ ownerId: users.isMultiUser() ? effectiveUserId : null, vendor: mate.vendor, model: mate.model });
|
|
88
|
+
boundScheduleJob = targetProject.bindMateSchedule(targetSession, intent, "creation-" + mate.id);
|
|
89
|
+
if (!boundScheduleJob) throw new Error("The Mate schedule could not be persisted.");
|
|
90
|
+
mates.updateMate(mateCtx, mate.id, { seedData: Object.assign({}, mate.seedData, { scheduleIntent: null }) });
|
|
91
|
+
}
|
|
81
92
|
favorites = users.addDmFavorite(effectiveUserId, mate.id);
|
|
82
93
|
} catch (error) {
|
|
94
|
+
if (targetSession && targetSession.mateSchedules && targetProject && typeof targetProject.cancelMateSchedule === "function") {
|
|
95
|
+
try { targetSession.mateSchedules.forEach(function (record) { targetProject.cancelMateSchedule(targetSession, record); }); } catch (cancelError) {}
|
|
96
|
+
}
|
|
83
97
|
if (registered) try { removeProject(mateSlug); } catch (removeError) {}
|
|
84
98
|
try { mates.deleteMate(mateCtx, mate.id); } catch (deleteError) {}
|
|
85
99
|
throw error;
|
|
@@ -97,6 +111,10 @@ function attachMates(ctx) {
|
|
|
97
111
|
return mate;
|
|
98
112
|
}
|
|
99
113
|
|
|
114
|
+
var lifecycle = require("./mate-lifecycle-service").attachMateLifecycleService({
|
|
115
|
+
users: users, mates: mates, projects: projects, removeProject: removeProject, refresh: refreshTeamSections,
|
|
116
|
+
});
|
|
117
|
+
|
|
100
118
|
// --- Mate message handlers ---
|
|
101
119
|
|
|
102
120
|
function handleMessage(ws, msg) {
|
|
@@ -168,7 +186,7 @@ function attachMates(ctx) {
|
|
|
168
186
|
for (var mi = 0; mi < mateList.length; mi++) {
|
|
169
187
|
var m = mateList[mi];
|
|
170
188
|
var mSlug = "mate-" + m.id;
|
|
171
|
-
if (!projects.has(mSlug)) {
|
|
189
|
+
if (!m.lifecycleDeletion && !projects.has(mSlug)) {
|
|
172
190
|
var mDir = mates.getMateDir(mateCtx5, m.id);
|
|
173
191
|
fs.mkdirSync(mDir, { recursive: true });
|
|
174
192
|
var mName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
@@ -200,45 +218,9 @@ function attachMates(ctx) {
|
|
|
200
218
|
|
|
201
219
|
if (msg.type === "mate_delete") {
|
|
202
220
|
if (!msg.mateId) return true;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (mateToDelete && mateToDelete.builtinKey) {
|
|
207
|
-
users.addDeletedBuiltinKey(userId, mateToDelete.builtinKey);
|
|
208
|
-
}
|
|
209
|
-
var result = mates.deleteMate(mateCtx6, msg.mateId);
|
|
210
|
-
if (result.error) {
|
|
211
|
-
ws.send(JSON.stringify({ type: "mate_error", error: result.error }));
|
|
212
|
-
} else {
|
|
213
|
-
removeProject("mate-" + msg.mateId);
|
|
214
|
-
// Build updated available builtins list
|
|
215
|
-
var builtinDefs3 = require("./builtin-mates");
|
|
216
|
-
var missingKeys3 = mates.getMissingBuiltinKeys(mateCtx6);
|
|
217
|
-
var availableBuiltins3 = [];
|
|
218
|
-
for (var abk3 = 0; abk3 < missingKeys3.length; abk3++) {
|
|
219
|
-
var bDef3 = builtinDefs3.getBuiltinByKey(missingKeys3[abk3]);
|
|
220
|
-
if (bDef3) {
|
|
221
|
-
availableBuiltins3.push({
|
|
222
|
-
key: bDef3.key,
|
|
223
|
-
displayName: bDef3.displayName,
|
|
224
|
-
bio: bDef3.bio,
|
|
225
|
-
avatarCustom: bDef3.avatarCustom || "",
|
|
226
|
-
avatarStyle: bDef3.avatarStyle || "imprint",
|
|
227
|
-
avatarColor: bDef3.avatarColor || "",
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
ws.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId, availableBuiltins: availableBuiltins3 }));
|
|
232
|
-
// Broadcast to all clients so strips update
|
|
233
|
-
projects.forEach(function (pCtx) {
|
|
234
|
-
pCtx.forEachClient(function (otherWs) {
|
|
235
|
-
if (otherWs === ws) return;
|
|
236
|
-
if (users.isMultiUser() && (!otherWs._clayUser || otherWs._clayUser.id !== userId)) return;
|
|
237
|
-
if (otherWs.readyState !== 1) return;
|
|
238
|
-
otherWs.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId, availableBuiltins: availableBuiltins3 }));
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
}
|
|
221
|
+
lifecycle.remove(userId, msg.mateId, null, ws).then(function () {}, function (error) {
|
|
222
|
+
ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, error: error.message }));
|
|
223
|
+
});
|
|
242
224
|
return true;
|
|
243
225
|
}
|
|
244
226
|
|
|
@@ -303,28 +285,8 @@ function attachMates(ctx) {
|
|
|
303
285
|
ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, error: "Use the Mate model selector to change this setting." }));
|
|
304
286
|
return true;
|
|
305
287
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (updated && updated.error) {
|
|
309
|
-
ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, requestId: msg.requestId, error: updated.error }));
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
if (updated) {
|
|
313
|
-
ws.send(JSON.stringify({ type: "mate_updated", mate: updated, requestId: msg.requestId }));
|
|
314
|
-
// Broadcast update
|
|
315
|
-
projects.forEach(function (pCtx) {
|
|
316
|
-
pCtx.forEachClient(function (otherWs) {
|
|
317
|
-
if (otherWs === ws) return;
|
|
318
|
-
if (users.isMultiUser() && (!otherWs._clayUser || otherWs._clayUser.id !== userId)) return;
|
|
319
|
-
if (otherWs.readyState !== 1) return;
|
|
320
|
-
otherWs.send(JSON.stringify({ type: "mate_updated", mate: updated }));
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
// Re-enforce team sections across all mate projects so roster stays current
|
|
324
|
-
refreshTeamSections(mateCtx7);
|
|
325
|
-
} else {
|
|
326
|
-
ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, requestId: msg.requestId, error: "Mate not found" }));
|
|
327
|
-
}
|
|
288
|
+
try { lifecycle.update(userId, msg.mateId, msg.updates, ws, msg.requestId); }
|
|
289
|
+
catch (error) { ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, requestId: msg.requestId, error: error.message })); }
|
|
328
290
|
return true;
|
|
329
291
|
}
|
|
330
292
|
|
|
@@ -333,6 +295,7 @@ function attachMates(ctx) {
|
|
|
333
295
|
|
|
334
296
|
return {
|
|
335
297
|
handleMessage: handleMessage,
|
|
298
|
+
lifecycle: lifecycle,
|
|
336
299
|
scheduleRegistryRefresh: scheduleRegistryRefresh,
|
|
337
300
|
createReadyMateFromInterview: createReadyMateFromInterview,
|
|
338
301
|
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
var crypto = require('crypto');
|
|
2
|
+
// Only authenticated application sockets enumerated by the server are targets.
|
|
3
|
+
function attachOwnerUi(ctx) {
|
|
4
|
+
var identities = new WeakMap(), capabilities = new WeakMap(), deliveries = new Map();
|
|
5
|
+
function alive(ws, owner) {
|
|
6
|
+
return ws && ws.readyState === 1 && (!ws._clayAuthCheck || ws._clayAuthCheck()) &&
|
|
7
|
+
(ctx.isMultiUser() ? !!owner && ws._clayUser && ws._clayUser.id === owner && !!ctx.findUser(owner) : !ws._clayUser);
|
|
8
|
+
}
|
|
9
|
+
function sockets(owner) {
|
|
10
|
+
var all = new Map();
|
|
11
|
+
ctx.forEachClient(function (ws, slug) {
|
|
12
|
+
if (!alive(ws, owner) || all.has(ws)) return;
|
|
13
|
+
if (!identities.has(ws)) identities.set(ws, 'viewer:' + crypto.randomUUID());
|
|
14
|
+
all.set(ws, { ws: ws, ref: identities.get(ws), slug: slug || null, sessionId: ws._clayActiveSession || null,
|
|
15
|
+
supported: capabilities.get(ws) === 1 && !ws._clayPane });
|
|
16
|
+
});
|
|
17
|
+
return Array.from(all.values());
|
|
18
|
+
}
|
|
19
|
+
function get(owner, ref) { return sockets(owner).find(function (item) { return item.ref === ref; }); }
|
|
20
|
+
function inspect(owner, ref) {
|
|
21
|
+
var viewer = get(owner, ref);
|
|
22
|
+
if (!viewer) throw new Error('The exact owner UI connection is absent or revoked.');
|
|
23
|
+
return viewer;
|
|
24
|
+
}
|
|
25
|
+
function check(owner, original) {
|
|
26
|
+
var live = inspect(owner, original.ref);
|
|
27
|
+
if (live.ws !== original.ws || live.slug !== original.slug || live.sessionId !== original.sessionId || live.supported !== original.supported) throw new Error('The selected viewer context changed. Inspect again.');
|
|
28
|
+
return live;
|
|
29
|
+
}
|
|
30
|
+
function deliver(owner, original, target, revalidate) {
|
|
31
|
+
revalidate(); var viewer = check(owner, original);
|
|
32
|
+
if (!viewer.supported) return { status: 'unsupported', delivered: false, acknowledged: false, visible: 'unknown' };
|
|
33
|
+
if (deliveries.size >= 100) {
|
|
34
|
+
deliveries.forEach(function (item, key) { if (Date.now() - item.created > 300000) deliveries.delete(key); });
|
|
35
|
+
if (deliveries.size >= 100) throw new Error('UI delivery capacity is full; wait for earlier attempts to expire.');
|
|
36
|
+
}
|
|
37
|
+
var id = 'navigation:' + crypto.randomUUID();
|
|
38
|
+
var record = { owner: owner, viewer: viewer, target: target, revalidate: revalidate, created: Date.now(), claimed: false,
|
|
39
|
+
result: { status: 'delivered', deliveryId: id, delivered: false, acknowledged: false, visible: 'unknown' } };
|
|
40
|
+
deliveries.set(id, record);
|
|
41
|
+
try {
|
|
42
|
+
viewer.ws.send(JSON.stringify({ type: 'clay_navigation_offer', deliveryId: id }));
|
|
43
|
+
if (!alive(viewer.ws, owner)) throw new Error('Viewer authorization or connection ended during delivery.');
|
|
44
|
+
record.result.delivered = true;
|
|
45
|
+
} catch (error) { record.result.status = 'failed'; record.result.error = error.message; record.result.retrySafe = false; }
|
|
46
|
+
return Object.assign({}, record.result);
|
|
47
|
+
}
|
|
48
|
+
function read(owner, id) {
|
|
49
|
+
var record = deliveries.get(id);
|
|
50
|
+
if (!record || record.owner !== owner) throw new Error('No delivery exists for this owner.');
|
|
51
|
+
record.revalidate();
|
|
52
|
+
return Object.assign({}, record.result);
|
|
53
|
+
}
|
|
54
|
+
function handleMessage(ws, msg) {
|
|
55
|
+
if (['clay_ui_ready', 'clay_navigation_claim', 'clay_navigation_ack'].indexOf(msg.type) < 0) return false;
|
|
56
|
+
var owner = ctx.isMultiUser() ? ws._clayUser && ws._clayUser.id : null;
|
|
57
|
+
if (!sockets(owner).some(function (item) { return item.ws === ws; })) return true;
|
|
58
|
+
if (msg.type === 'clay_ui_ready') { if (msg.version === 1) capabilities.set(ws, 1); return true; }
|
|
59
|
+
var record = deliveries.get(msg.deliveryId);
|
|
60
|
+
if (!record || record.viewer.ws !== ws || record.owner !== owner || Date.now() - record.created > 30000) return true;
|
|
61
|
+
try {
|
|
62
|
+
record.revalidate();
|
|
63
|
+
if (msg.type === 'clay_navigation_claim') {
|
|
64
|
+
if (record.claimed) return true;
|
|
65
|
+
check(owner, record.viewer); record.claimed = true;
|
|
66
|
+
ws.send(JSON.stringify({ type: 'clay_navigation_apply', deliveryId: msg.deliveryId, target: record.target }));
|
|
67
|
+
} else if (record.claimed && !record.result.acknowledged && ['accepted', 'refused', 'failed'].indexOf(msg.status) >= 0) {
|
|
68
|
+
record.result.acknowledged = true;
|
|
69
|
+
// Accepted only confirms invocation of the existing navigator, never rendering.
|
|
70
|
+
record.result.clientStatus = msg.status;
|
|
71
|
+
}
|
|
72
|
+
} catch (error) { record.result.status = 'failed'; record.result.error = error.message; record.result.retrySafe = false; }
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return { list: sockets, inspect: inspect, check: check, deliver: deliver, read: read, handleMessage: handleMessage };
|
|
76
|
+
}
|
|
77
|
+
module.exports = { attachOwnerUi: attachOwnerUi };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Directory pickers and project registration must not become content-access
|
|
2
|
+
// bypasses through another spelling, symlink or nested directory.
|
|
3
|
+
var fs = require("fs");
|
|
4
|
+
var path = require("path");
|
|
5
|
+
function inside(root, target) {
|
|
6
|
+
var relative = path.relative(root, target);
|
|
7
|
+
return relative !== ".." && relative.indexOf(".." + path.sep) !== 0 && !path.isAbsolute(relative);
|
|
8
|
+
}
|
|
9
|
+
function attachPathAccess(ctx) {
|
|
10
|
+
return function (userId, requested) {
|
|
11
|
+
if (!ctx.users.isMultiUser()) return true;
|
|
12
|
+
var user = userId && ctx.users.findUserById(userId);
|
|
13
|
+
if (!user) return false;
|
|
14
|
+
var target;
|
|
15
|
+
try { target = fs.realpathSync(requested); } catch (error) { return false; }
|
|
16
|
+
var allowed = false;
|
|
17
|
+
var denied = false;
|
|
18
|
+
ctx.projects.forEach(function (project, slug) {
|
|
19
|
+
var status = project.getStatus();
|
|
20
|
+
var root;
|
|
21
|
+
try { root = fs.realpathSync(status.path); } catch (error) { return; }
|
|
22
|
+
if (inside(root, target) || inside(target, root)) {
|
|
23
|
+
if (!ctx.canAccess(userId, slug)) denied = true;
|
|
24
|
+
else if (inside(root, target)) allowed = true;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
if (denied) return false;
|
|
28
|
+
if (ctx.osUsers) {
|
|
29
|
+
if (!user.linuxUser) return false;
|
|
30
|
+
try {
|
|
31
|
+
var identity = ctx.resolveIdentity(user.linuxUser);
|
|
32
|
+
return inside(fs.realpathSync(identity.home), target);
|
|
33
|
+
} catch (error) { return false; }
|
|
34
|
+
}
|
|
35
|
+
return allowed || ctx.users.getAllUsers().length === 1;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
module.exports = { attachPathAccess: attachPathAccess };
|
|
@@ -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-settings.js
CHANGED
|
@@ -755,7 +755,10 @@ function attachSettings(ctx) {
|
|
|
755
755
|
return false;
|
|
756
756
|
}
|
|
757
757
|
|
|
758
|
-
return {
|
|
758
|
+
return {
|
|
759
|
+
handleRequest: handleRequest,
|
|
760
|
+
getInitialAppearance: appearanceSettings.getInitialAppearance
|
|
761
|
+
};
|
|
759
762
|
}
|
|
760
763
|
|
|
761
764
|
module.exports = { attachSettings: attachSettings };
|
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) {
|
|
@@ -491,6 +529,41 @@ function createServer(opts) {
|
|
|
491
529
|
},
|
|
492
530
|
});
|
|
493
531
|
|
|
532
|
+
function injectAppearanceSeed(html, appearance) {
|
|
533
|
+
var validId = function (value) {
|
|
534
|
+
return typeof value === "string" && /^[a-zA-Z0-9_-]{1,120}$/.test(value) ? value : null;
|
|
535
|
+
};
|
|
536
|
+
var mode = appearance && ["light", "dark"].indexOf(appearance.mode) >= 0 ? appearance.mode : null;
|
|
537
|
+
var seed = appearance && appearance.configured === true ? {
|
|
538
|
+
configured: true,
|
|
539
|
+
theme: validId(appearance.theme),
|
|
540
|
+
mode: mode,
|
|
541
|
+
lightTheme: validId(appearance.lightTheme),
|
|
542
|
+
darkTheme: validId(appearance.darkTheme),
|
|
543
|
+
} : null;
|
|
544
|
+
var payload = JSON.stringify(seed).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
545
|
+
var script = '<script>window.__clayAppearanceSeed=' + payload + ';</script>';
|
|
546
|
+
return html.replace('<script src="appearance-bootstrap.js"></script>', script + '<script src="appearance-bootstrap.js"></script>');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function sendAppShell(req, res, slug) {
|
|
550
|
+
try {
|
|
551
|
+
var shell = fs.readFileSync(path.join(publicDir, "index.html"), "utf8");
|
|
552
|
+
if (slug) shell = injectRootProjectSlug(shell, slug);
|
|
553
|
+
var appearance = null;
|
|
554
|
+
try { appearance = settings.getInitialAppearance(req); } catch (e) {}
|
|
555
|
+
shell = injectAppearanceSeed(shell, appearance);
|
|
556
|
+
res.writeHead(200, {
|
|
557
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
558
|
+
"Cache-Control": "private, no-store",
|
|
559
|
+
});
|
|
560
|
+
res.end(shell);
|
|
561
|
+
} catch (e) {
|
|
562
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
563
|
+
res.end("App shell missing.");
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
494
567
|
var palette = serverPalette.attachPalette({
|
|
495
568
|
users: users,
|
|
496
569
|
projects: projects,
|
|
@@ -512,7 +585,28 @@ function createServer(opts) {
|
|
|
512
585
|
broadcastAll: function (msg) { broadcastAll(msg); },
|
|
513
586
|
sendToUser: function (userId, msg) { sendToUser(userId, msg); },
|
|
514
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); },
|
|
515
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
|
+
}
|
|
516
610
|
|
|
517
611
|
// --- Security headers ---
|
|
518
612
|
var securityHeaders = {
|
|
@@ -910,15 +1004,7 @@ function createServer(opts) {
|
|
|
910
1004
|
});
|
|
911
1005
|
}
|
|
912
1006
|
if (targetSlug) {
|
|
913
|
-
|
|
914
|
-
var homeHtml = fs.readFileSync(path.join(publicDir, "index.html"), "utf8");
|
|
915
|
-
homeHtml = injectRootProjectSlug(homeHtml, targetSlug);
|
|
916
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
|
|
917
|
-
res.end(homeHtml);
|
|
918
|
-
} catch (e) {
|
|
919
|
-
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
920
|
-
res.end("App shell missing.");
|
|
921
|
-
}
|
|
1007
|
+
sendAppShell(req, res, targetSlug);
|
|
922
1008
|
return;
|
|
923
1009
|
}
|
|
924
1010
|
}
|
|
@@ -936,9 +1022,7 @@ function createServer(opts) {
|
|
|
936
1022
|
return;
|
|
937
1023
|
}
|
|
938
1024
|
}
|
|
939
|
-
|
|
940
|
-
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
941
|
-
res.end("App shell missing.");
|
|
1025
|
+
sendAppShell(req, res, null);
|
|
942
1026
|
return;
|
|
943
1027
|
}
|
|
944
1028
|
|
|
@@ -955,8 +1039,8 @@ function createServer(opts) {
|
|
|
955
1039
|
var projectList = [];
|
|
956
1040
|
var infoUser = users.isMultiUser() ? getMultiUserFromReq(req) : null;
|
|
957
1041
|
projects.forEach(function (ctx, slug) {
|
|
958
|
-
if (users.isMultiUser() && (!infoUser || !canUserAccessSlug(infoUser.id, slug))) return;
|
|
959
|
-
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) });
|
|
960
1044
|
});
|
|
961
1045
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
962
1046
|
res.end(JSON.stringify({ projects: projectList, version: currentVersion, authenticated: true }));
|
|
@@ -965,6 +1049,10 @@ function createServer(opts) {
|
|
|
965
1049
|
|
|
966
1050
|
// Static files (favicon, manifest, icons, sw.js, mate avatars, etc.)
|
|
967
1051
|
if (!fullUrl.includes("..") && !fullUrl.startsWith("/p/") && !fullUrl.startsWith("/api/")) {
|
|
1052
|
+
if (fullUrl === "/index.html") {
|
|
1053
|
+
sendAppShell(req, res, null);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
968
1056
|
if (serveStatic(fullUrl, res)) return;
|
|
969
1057
|
}
|
|
970
1058
|
|
|
@@ -1028,8 +1116,14 @@ function createServer(opts) {
|
|
|
1028
1116
|
if (users.isMultiUser() && !isMcpBridgeAuthenticated) {
|
|
1029
1117
|
var httpUser = getMultiUserFromReq(req);
|
|
1030
1118
|
if (!httpUser || !canUserAccessSlug(httpUser.id, slug)) {
|
|
1031
|
-
|
|
1032
|
-
|
|
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" }));
|
|
1033
1127
|
return;
|
|
1034
1128
|
}
|
|
1035
1129
|
}
|
|
@@ -1054,6 +1148,10 @@ function createServer(opts) {
|
|
|
1054
1148
|
|
|
1055
1149
|
// Static files (same assets for all projects)
|
|
1056
1150
|
if (req.method === "GET") {
|
|
1151
|
+
if (projectUrl === "/" || projectUrl === "/index.html") {
|
|
1152
|
+
sendAppShell(req, res, slug);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1057
1155
|
if (serveStatic(projectUrl, res)) return;
|
|
1058
1156
|
}
|
|
1059
1157
|
|
|
@@ -1134,6 +1232,7 @@ function createServer(opts) {
|
|
|
1134
1232
|
var globalWs = serverGlobalWs.attachGlobalWs({
|
|
1135
1233
|
osUsers: osUsers,
|
|
1136
1234
|
usersModule: users,
|
|
1235
|
+
canUsePath: canUsePath,
|
|
1137
1236
|
onAddProject: onAddProject,
|
|
1138
1237
|
onCreateProject: onCreateProject,
|
|
1139
1238
|
onCloneProject: onCloneProject,
|
|
@@ -1274,17 +1373,10 @@ function createServer(opts) {
|
|
|
1274
1373
|
if (processingUpdateTimer) clearTimeout(processingUpdateTimer);
|
|
1275
1374
|
processingUpdateTimer = setTimeout(function () {
|
|
1276
1375
|
processingUpdateTimer = null;
|
|
1277
|
-
var allProjectsList = getProjects();
|
|
1278
1376
|
// Always send per-client to include cross-project unread counts
|
|
1279
1377
|
projects.forEach(function (ctx, projSlug) {
|
|
1280
1378
|
ctx.forEachClient(function (ws) {
|
|
1281
|
-
var filtered =
|
|
1282
|
-
if (users.isMultiUser() && onGetProjectAccess) {
|
|
1283
|
-
var wsUser = ws._clayUser;
|
|
1284
|
-
filtered = wsUser ? allProjectsList.filter(function (p) {
|
|
1285
|
-
return canUserAccessSlug(wsUser.id, p.slug);
|
|
1286
|
-
}) : [];
|
|
1287
|
-
}
|
|
1379
|
+
var filtered = getVisibleProjects(ws._clayUser ? ws._clayUser.id : null);
|
|
1288
1380
|
// Attach per-project unread counts for this client
|
|
1289
1381
|
var unreadMap = getCrossProjectUnread(ws);
|
|
1290
1382
|
var projectsWithUnread = filtered.map(function (p) {
|
|
@@ -1334,6 +1426,10 @@ function createServer(opts) {
|
|
|
1334
1426
|
function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta, extraOpts) {
|
|
1335
1427
|
if (projects.has(slug)) return false;
|
|
1336
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
|
+
}
|
|
1337
1433
|
var ctx = createProjectContext({
|
|
1338
1434
|
cwd: cwd,
|
|
1339
1435
|
slug: slug,
|
|
@@ -1353,6 +1449,8 @@ function createServer(opts) {
|
|
|
1353
1449
|
return mateHandler.createReadyMateFromInterview(ws, userId, definition);
|
|
1354
1450
|
},
|
|
1355
1451
|
workspaceQueryService: workspaceQueryService,
|
|
1452
|
+
clayProductControl: clayProductControl,
|
|
1453
|
+
resumeLifecycleProject: function (userId) { return opts.projectLifecycle ? opts.projectLifecycle.resumeRecovered(slug, userId) : true; },
|
|
1356
1454
|
projectLogsService: projectLogsService,
|
|
1357
1455
|
issuesService: issuesService,
|
|
1358
1456
|
linearStorage: linear.storage,
|
|
@@ -1374,13 +1472,19 @@ function createServer(opts) {
|
|
|
1374
1472
|
return !!(access && !access.error && users.canAccessProject(userId, access));
|
|
1375
1473
|
},
|
|
1376
1474
|
getProjectAccess: function () {
|
|
1377
|
-
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
|
+
}
|
|
1378
1479
|
return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
|
|
1379
1480
|
},
|
|
1380
1481
|
shouldSuppressResponseNotification: function (session) {
|
|
1381
1482
|
return shouldSuppressHomeResponse(projects, slug, session);
|
|
1382
1483
|
},
|
|
1383
|
-
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
|
+
}),
|
|
1384
1488
|
debug: debug,
|
|
1385
1489
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
1386
1490
|
osUsers: osUsers,
|
|
@@ -1412,6 +1516,7 @@ function createServer(opts) {
|
|
|
1412
1516
|
var status = ctx.getStatus();
|
|
1413
1517
|
var recs = ctx.getSchedules();
|
|
1414
1518
|
for (var i = 0; i < recs.length; i++) {
|
|
1519
|
+
if (users.isMultiUser() && recs[i].ownerId !== userId) continue;
|
|
1415
1520
|
// Shallow-copy full record and augment with project metadata
|
|
1416
1521
|
var copy = {};
|
|
1417
1522
|
var keys = Object.keys(recs[i]);
|
|
@@ -1469,6 +1574,7 @@ function createServer(opts) {
|
|
|
1469
1574
|
return ctx.getSchedules().length;
|
|
1470
1575
|
},
|
|
1471
1576
|
canAccessProjectSlug: canUserAccessSlug,
|
|
1577
|
+
canUsePath: canUsePath,
|
|
1472
1578
|
onPresenceChange: broadcastPresenceChange,
|
|
1473
1579
|
onProcessingChanged: broadcastProcessingChange,
|
|
1474
1580
|
onSessionDone: function () { onSessionDone(slug); },
|
|
@@ -1622,6 +1728,7 @@ function createServer(opts) {
|
|
|
1622
1728
|
}
|
|
1623
1729
|
|
|
1624
1730
|
function handleDmMessage(ws, msg, routedProjectSlug) {
|
|
1731
|
+
if (ownerUiService.handleMessage(ws, msg)) return true;
|
|
1625
1732
|
if (cursorSharingService && cursorSharingService.handleMessage(ws, msg)) return true;
|
|
1626
1733
|
if (defaultVendorService && defaultVendorService.handleMessage(ws, msg, routedProjectSlug)) return true;
|
|
1627
1734
|
if (contextViewService && contextViewService.handleMessage(ws, msg)) return true;
|
|
@@ -1636,19 +1743,8 @@ function createServer(opts) {
|
|
|
1636
1743
|
return false;
|
|
1637
1744
|
}
|
|
1638
1745
|
|
|
1639
|
-
function removeProject(slug) {
|
|
1640
|
-
|
|
1641
|
-
if (!ctx) return false;
|
|
1642
|
-
var wasMate = ctx.getStatus().isMate;
|
|
1643
|
-
var shutdownResult = ctx.destroy();
|
|
1644
|
-
projects.delete(slug);
|
|
1645
|
-
if (shutdownResult && typeof shutdownResult.catch === "function") {
|
|
1646
|
-
shutdownResult.catch(function(err) {
|
|
1647
|
-
console.error("[server] Project destroy failed for " + slug + ":", err && err.message ? err.message : err);
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
if (!wasMate) scheduleRegistryRefresh();
|
|
1651
|
-
return true;
|
|
1746
|
+
function removeProject(slug, awaitCompletion) {
|
|
1747
|
+
return require('./server-project-removal').removeProjectRuntime(projects, slug, awaitCompletion, scheduleRegistryRefresh);
|
|
1652
1748
|
}
|
|
1653
1749
|
|
|
1654
1750
|
// Now that addProject and removeProject are defined, initialize mateHandler
|
|
@@ -1734,7 +1830,11 @@ function createServer(opts) {
|
|
|
1734
1830
|
function getVisibleProjects(userId) {
|
|
1735
1831
|
var visible = [];
|
|
1736
1832
|
projects.forEach(function (ctx, slug) {
|
|
1737
|
-
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
|
+
}
|
|
1738
1838
|
var status = ctx.getStatus();
|
|
1739
1839
|
if (!status.isMate && onGetProjectAccess) {
|
|
1740
1840
|
var access = onGetProjectAccess(slug);
|
|
@@ -1933,6 +2033,7 @@ function createServer(opts) {
|
|
|
1933
2033
|
speech.destroy();
|
|
1934
2034
|
var shutdowns = [];
|
|
1935
2035
|
shutdowns.push(durableSchedulerLifecycle.shutdown());
|
|
2036
|
+
shutdowns.push(lifecycleRuntime.shutdown());
|
|
1936
2037
|
projects.forEach(function (ctx, slug) {
|
|
1937
2038
|
console.log("[server] Destroying project:", slug);
|
|
1938
2039
|
var result = ctx.destroy();
|
|
@@ -2000,6 +2101,8 @@ function createServer(opts) {
|
|
|
2000
2101
|
isTLS: !!tlsOptions,
|
|
2001
2102
|
addProject: addProject,
|
|
2002
2103
|
removeProject: removeProject,
|
|
2104
|
+
getProjectLifecycleTargets: lifecycleRuntime.targets,
|
|
2105
|
+
closeProjectLifecycle: lifecycleRuntime.close,
|
|
2003
2106
|
getProjects: getProjects,
|
|
2004
2107
|
reorderProjects: reorderProjects,
|
|
2005
2108
|
setProjectTitle: setProjectTitle,
|