clay-server 3.8.1 → 4.0.0-beta.10
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/ask-user-mcp-server.js +5 -119
- package/lib/background-task-timing.js +105 -0
- package/lib/builtin-mates.js +6 -6
- package/lib/claude-hook-installer.js +15 -0
- package/lib/clay-history-mcp-server.js +62 -297
- package/lib/daemon.js +23 -1
- package/lib/debate-model-selection.js +119 -0
- package/lib/home-debate-tool-policy.js +164 -0
- package/lib/home-response-notifications.js +26 -0
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +224 -0
- package/lib/mate-creation-mcp-server.js +55 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/mate-ready-creation.js +95 -0
- package/lib/mates.js +2 -0
- package/lib/notes.js +2 -1
- package/lib/project-ask-user.js +64 -0
- package/lib/project-capsule-catalog.js +101 -0
- package/lib/project-connection.js +17 -0
- package/lib/project-debate-proposal.js +133 -17
- package/lib/project-debate.js +214 -110
- package/lib/project-delegated-follow-up.js +84 -0
- package/lib/project-delegated-session.js +53 -0
- package/lib/project-filesystem.js +7 -9
- package/lib/project-knowledge.js +23 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-mcp-server.js +290 -0
- package/lib/project-logs-query.js +167 -0
- package/lib/project-logs-root.js +67 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +357 -0
- package/lib/project-logs-snapshot.js +202 -0
- package/lib/project-logs-store.js +498 -0
- package/lib/project-logs-versioning.js +128 -0
- package/lib/project-logs.js +347 -0
- package/lib/project-mate-creation-proposal.js +120 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +36 -7
- package/lib/project-models.js +157 -32
- package/lib/project-session-notes.js +2 -2
- package/lib/project-session-pair.js +8 -8
- package/lib/project-sessions.js +66 -51
- package/lib/project-vendor-login.js +414 -0
- package/lib/project-worker-proposal.js +25 -25
- package/lib/project-workspace-query.js +144 -0
- package/lib/project.js +433 -102
- package/lib/public/app.js +109 -48
- package/lib/public/css/avatar-imprints.css +0 -1
- package/lib/public/css/base.css +4 -0
- package/lib/public/css/capsule-ui.css +389 -0
- package/lib/public/css/command-palette.css +306 -6
- package/lib/public/css/home-capsule-library.css +243 -0
- package/lib/public/css/home-capsule-source.css +192 -0
- package/lib/public/css/home-debate-live.css +146 -0
- package/lib/public/css/home-debate-planning.css +125 -0
- package/lib/public/css/home-debates-archive.css +281 -0
- package/lib/public/css/home-hub.css +896 -405
- package/lib/public/css/home-mate-creation.css +34 -0
- package/lib/public/css/home-mate-model-picker.css +131 -0
- package/lib/public/css/home-mate-settings.css +344 -0
- package/lib/public/css/home-session-actions.css +265 -0
- package/lib/public/css/home-sidebar.css +667 -0
- package/lib/public/css/icon-strip.css +1 -2
- package/lib/public/css/input.css +79 -25
- package/lib/public/css/mates.css +7 -34
- package/lib/public/css/pane.css +1 -1
- package/lib/public/css/playbook.css +0 -80
- package/lib/public/css/project-logs.css +234 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/sticky-notes.css +53 -117
- package/lib/public/css/tui-attention.css +0 -44
- package/lib/public/css/workspace-assignment.css +45 -0
- package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
- package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
- package/lib/public/index.html +96 -74
- package/lib/public/modules/app-connection.js +100 -5
- package/lib/public/modules/app-dm.js +43 -160
- package/lib/public/modules/app-home-hub.js +328 -585
- package/lib/public/modules/app-message-cards.js +242 -0
- package/lib/public/modules/app-message-router.js +84 -0
- package/lib/public/modules/app-messages.js +124 -38
- package/lib/public/modules/app-notifications.js +8 -119
- package/lib/public/modules/app-panels.js +43 -2
- package/lib/public/modules/app-projects.js +9 -1
- package/lib/public/modules/app-rendering.js +21 -602
- package/lib/public/modules/avatar-imprint.js +11 -8
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/chat-bubble-renderer.js +152 -0
- package/lib/public/modules/chat-render-runtime.js +198 -0
- package/lib/public/modules/command-palette.js +225 -436
- package/lib/public/modules/dm-render.js +82 -0
- package/lib/public/modules/home-capsule-creation-intent.js +31 -0
- package/lib/public/modules/home-capsule-library.js +146 -0
- package/lib/public/modules/home-capsule-source.js +383 -0
- package/lib/public/modules/home-chat-identity.js +35 -0
- package/lib/public/modules/home-chat-scroll.js +77 -0
- package/lib/public/modules/home-chat-stream-state.js +34 -0
- package/lib/public/modules/home-composer-focus.js +25 -0
- package/lib/public/modules/home-conversations-sheet.js +179 -0
- package/lib/public/modules/home-debate-activity.js +48 -0
- package/lib/public/modules/home-debate-controls.js +295 -0
- package/lib/public/modules/home-debate-launch.js +41 -0
- package/lib/public/modules/home-debate-live.js +284 -0
- package/lib/public/modules/home-debate-models.js +73 -0
- package/lib/public/modules/home-debate-planning.js +335 -0
- package/lib/public/modules/home-debates-archive.js +279 -0
- package/lib/public/modules/home-dock-resize.js +74 -0
- package/lib/public/modules/home-dock.js +446 -0
- package/lib/public/modules/home-mate-chat.js +496 -0
- package/lib/public/modules/home-mate-creation.js +109 -0
- package/lib/public/modules/home-mate-model-picker.js +281 -0
- package/lib/public/modules/home-mate-selection.js +38 -0
- package/lib/public/modules/home-mate-settings-menu.js +131 -0
- package/lib/public/modules/home-mate-settings.js +383 -0
- package/lib/public/modules/home-session-actions.js +373 -0
- package/lib/public/modules/home-session-model-confirmation.js +13 -0
- package/lib/public/modules/home-shell.js +9 -0
- package/lib/public/modules/home-sidebar-chat-list.js +63 -0
- package/lib/public/modules/home-sidebar.js +165 -0
- package/lib/public/modules/home-sub-surface.js +19 -0
- package/lib/public/modules/home-surface-boot.js +68 -0
- package/lib/public/modules/home-surface.js +141 -0
- package/lib/public/modules/home-tools.js +271 -0
- package/lib/public/modules/markdown.js +50 -2
- package/lib/public/modules/mate-management.js +86 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/mate-wizard.js +0 -7
- package/lib/public/modules/mention.js +17 -19
- package/lib/public/modules/pane-bridge.js +9 -0
- package/lib/public/modules/profile.js +4 -5
- package/lib/public/modules/project-logs-ambient.js +238 -0
- package/lib/public/modules/project-logs-render.js +359 -0
- package/lib/public/modules/project-logs.js +328 -0
- package/lib/public/modules/project-settings.js +34 -28
- package/lib/public/modules/project-switcher.js +11 -5
- package/lib/public/modules/project-workspace-assignment.js +23 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/search-clay-chat.js +402 -0
- package/lib/public/modules/server-settings.js +14 -12
- package/lib/public/modules/session-actions.js +1 -1
- package/lib/public/modules/sidebar-mates.js +56 -552
- package/lib/public/modules/sidebar-mobile.js +12 -73
- package/lib/public/modules/sidebar-presence.js +37 -0
- package/lib/public/modules/sidebar-sessions.js +25 -1
- package/lib/public/modules/split-pair-ui.js +13 -13
- package/lib/public/modules/split-view.js +7 -0
- package/lib/public/modules/sticky-notes.js +106 -53
- package/lib/public/modules/tool-input-composition.js +48 -0
- package/lib/public/modules/tool-llm-status.js +167 -0
- package/lib/public/modules/tool-palette.js +76 -4
- package/lib/public/modules/tool-renderer-advanced.js +285 -0
- package/lib/public/modules/tool-renderer-chart.js +158 -0
- package/lib/public/modules/tool-renderer-semantics.js +35 -0
- package/lib/public/modules/tool-renderer.js +464 -0
- package/lib/public/modules/tool-runtime.js +309 -0
- package/lib/public/modules/tool-ui-evaluator.js +97 -0
- package/lib/public/modules/tool-ui-tree.js +21 -0
- package/lib/public/modules/tui-attention.js +10 -0
- package/lib/public/modules/user-settings.js +16 -23
- package/lib/public/modules/vendor-login.js +287 -0
- package/lib/public/modules/worker-proposal.js +2 -2
- package/lib/public/modules/workspace-assignment-card.js +149 -0
- package/lib/public/modules/workspace-assignment-routing.js +4 -0
- package/lib/public/style.css +14 -2
- package/lib/runtime-env.js +65 -0
- package/lib/sdk-bridge.js +120 -32
- package/lib/sdk-message-processor.js +34 -13
- package/lib/server-home-capsule-creation.js +36 -0
- package/lib/server-home-chat-events.js +287 -0
- package/lib/server-home-chat.js +518 -0
- package/lib/server-home-clay-entry.js +65 -0
- package/lib/server-home-clay-session-links.js +26 -0
- package/lib/server-home-debate-planning.js +214 -0
- package/lib/server-home-debates.js +137 -0
- package/lib/server-home-mate-creation.js +261 -0
- package/lib/server-home-models.js +241 -0
- package/lib/server-home-preferences.js +61 -0
- package/lib/server-mates.js +37 -0
- package/lib/server-tools.js +377 -0
- package/lib/server.js +166 -59
- package/lib/session-pair-mcp-server.js +6 -6
- package/lib/sessions.js +22 -4
- package/lib/tool-capsule-source.js +142 -0
- package/lib/tool-control-mcp-server.js +126 -0
- package/lib/tool-llm.js +48 -0
- package/lib/tool-storage.js +77 -0
- package/lib/tool-ui-spec-advanced.js +82 -0
- package/lib/tool-ui-spec.js +261 -0
- package/lib/tools-registry.js +286 -0
- package/lib/users-home-dock-preferences.js +77 -0
- package/lib/users-home-surface-preferences.js +99 -0
- package/lib/users-preferences.js +4 -7
- package/lib/users.js +12 -0
- package/lib/whats-new-content.js +3 -3
- package/lib/workspace-assignment-service.js +420 -0
- package/lib/workspace-query-mcp-server.js +154 -0
- package/lib/workspace-query-service.js +492 -0
- package/lib/ws-schema.js +107 -12
- package/lib/yoke/adapters/acp.js +2 -1
- package/lib/yoke/adapters/antigravity.js +2 -1
- package/lib/yoke/adapters/claude-worker.js +11 -4
- package/lib/yoke/adapters/claude.js +141 -46
- package/lib/yoke/adapters/codex.js +79 -11
- package/lib/yoke/adapters/kiro.js +2 -1
- package/lib/yoke/claude-user-input.js +38 -0
- package/lib/yoke/codex-app-server.js +20 -2
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/lib/yoke/codex-user-input.js +64 -0
- package/lib/yoke/complete-once.js +164 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/interface.js +19 -0
- package/lib/yoke/mcp-bridge-server.js +2 -6
- package/lib/yoke/user-input.js +254 -0
- package/package.json +4 -3
- package/lib/public/css/home-chat.css +0 -380
- package/lib/public/modules/home-chat.js +0 -438
- package/lib/server-clay-home.js +0 -245
package/lib/server.js
CHANGED
|
@@ -13,10 +13,18 @@ var serverSkills = require("./server-skills");
|
|
|
13
13
|
var claudeHookInstaller = require("./claude-hook-installer");
|
|
14
14
|
var serverDm = require("./server-dm");
|
|
15
15
|
var serverMates = require("./server-mates");
|
|
16
|
-
var
|
|
16
|
+
var serverHomeChat = require("./server-home-chat");
|
|
17
|
+
var shouldSuppressHomeResponse = require("./home-response-notifications").shouldSuppressHomeResponse;
|
|
18
|
+
var serverHomePreferences = require("./server-home-preferences");
|
|
19
|
+
var serverTools = require("./server-tools");
|
|
17
20
|
var serverAdmin = require("./server-admin");
|
|
18
21
|
var serverSettings = require("./server-settings");
|
|
19
22
|
var serverPalette = require("./server-palette");
|
|
23
|
+
var workspaceQueryModule = require("./workspace-query-service");
|
|
24
|
+
var { attachWorkspaceQueryService } = workspaceQueryModule;
|
|
25
|
+
var { attachWorkspaceAssignmentService } = require("./workspace-assignment-service");
|
|
26
|
+
var { attachProjectLogsService } = require("./project-logs-service");
|
|
27
|
+
var { attachMateKnowledgeService } = require("./mate-knowledge-service");
|
|
20
28
|
var serverEmail = require("./server-email");
|
|
21
29
|
var serverGlobalWs = require("./server-global-ws");
|
|
22
30
|
var { parseWsRequestUrl } = require("./ws-request");
|
|
@@ -291,6 +299,59 @@ function createServer(opts) {
|
|
|
291
299
|
|
|
292
300
|
// --- Project registry ---
|
|
293
301
|
var projects = new Map(); // slug → projectContext
|
|
302
|
+
var assignmentService = attachWorkspaceAssignmentService({
|
|
303
|
+
storageDir: path.join(CONFIG_DIR, "workspace-assignments"),
|
|
304
|
+
getProjects: function () { return projects; },
|
|
305
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
306
|
+
sessionRef: workspaceQueryModule.sessionRef,
|
|
307
|
+
});
|
|
308
|
+
var workspaceQueryService = attachWorkspaceQueryService({
|
|
309
|
+
getProjects: function () { return projects; },
|
|
310
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
311
|
+
canAccessProject: function (userId, status) {
|
|
312
|
+
return users.canAccessProject(userId, {
|
|
313
|
+
ownerId: status && status.projectOwnerId || null,
|
|
314
|
+
visibility: status && status.visibility || "public",
|
|
315
|
+
allowedUsers: status && status.allowedUsers || [],
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
resolveMate: function (userId, mateId) {
|
|
319
|
+
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
320
|
+
},
|
|
321
|
+
assignmentService: assignmentService,
|
|
322
|
+
});
|
|
323
|
+
// Project access is resolved from the authoritative project record, not from
|
|
324
|
+
// getStatus(), which carries no visibility or allowedUsers fields.
|
|
325
|
+
var projectLogsService = attachProjectLogsService({
|
|
326
|
+
getProjects: function () { return projects; },
|
|
327
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
328
|
+
canAccessProject: function (userId, status) {
|
|
329
|
+
if (!status || !status.slug || !onGetProjectAccess) return false;
|
|
330
|
+
var access = onGetProjectAccess(status.slug);
|
|
331
|
+
if (!access || access.error) return false;
|
|
332
|
+
return users.canAccessProject(userId, access) === true;
|
|
333
|
+
},
|
|
334
|
+
resolveMate: function (userId, mateId) {
|
|
335
|
+
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
336
|
+
},
|
|
337
|
+
findUserById: function (userId) { return users.findUserById(userId); },
|
|
338
|
+
});
|
|
339
|
+
// Mate Knowledge reads the synchronized Knowledge records. The Mate list is
|
|
340
|
+
// derived from the owning user alone, so a binding can never span users.
|
|
341
|
+
var mateKnowledgeService = attachMateKnowledgeService({
|
|
342
|
+
getProjects: function () { return projects; },
|
|
343
|
+
isMultiUser: function () { return users.isMultiUser(); },
|
|
344
|
+
resolveMate: function (userId, mateId) {
|
|
345
|
+
return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId);
|
|
346
|
+
},
|
|
347
|
+
listMates: function (userId) {
|
|
348
|
+
var mateCtx = mates.buildMateCtx(users.isMultiUser() ? userId : null);
|
|
349
|
+
var all = mates.getAllMates(mateCtx) || [];
|
|
350
|
+
return all.map(function (mate) {
|
|
351
|
+
return { id: mate.id, name: mate.name || null, createdBy: mate.createdBy || null, dir: mates.getMateDir(mateCtx, mate.id) };
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
});
|
|
294
355
|
|
|
295
356
|
// --- Admin module ---
|
|
296
357
|
var admin = serverAdmin.attachAdmin({
|
|
@@ -355,7 +416,7 @@ function createServer(opts) {
|
|
|
355
416
|
// origin. External sites still cannot frame Clay; frame-ancestors below
|
|
356
417
|
// is the modern equivalent for browsers that honor CSP.
|
|
357
418
|
"X-Frame-Options": "SAMEORIGIN",
|
|
358
|
-
"Content-Security-Policy": "default-src 'self'; frame-ancestors 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; img-src * data: blob:; connect-src 'self' ws: wss: https://cdn.jsdelivr.net https://esm.sh https://api.open-meteo.com https://ipapi.co; font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net;",
|
|
419
|
+
"Content-Security-Policy": "default-src 'self'; frame-ancestors 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://esm.sh; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; img-src * data: blob:; connect-src 'self' ws: wss: https://cdn.jsdelivr.net https://esm.sh https://api.open-meteo.com https://ipapi.co; font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net;",
|
|
359
420
|
};
|
|
360
421
|
if (tlsOptions) {
|
|
361
422
|
securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
|
|
@@ -655,7 +716,7 @@ function createServer(opts) {
|
|
|
655
716
|
// --- Skills routes (delegated to server-skills) ---
|
|
656
717
|
if (skills.handleRequest(req, res, fullUrl)) return;
|
|
657
718
|
|
|
658
|
-
// Root path —
|
|
719
|
+
// Root path — render home while keeping the last accessible project connected.
|
|
659
720
|
if (fullUrl === "/" && req.method === "GET") {
|
|
660
721
|
if (!isRequestAuthed(req)) {
|
|
661
722
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
@@ -665,9 +726,23 @@ function createServer(opts) {
|
|
|
665
726
|
if (projects.size > 0) {
|
|
666
727
|
var targetSlug = null;
|
|
667
728
|
var reqUser = users.isMultiUser() ? getMultiUserFromReq(req) : null;
|
|
729
|
+
var homePreference = typeof users.getHomeSurfacePreference === "function"
|
|
730
|
+
? users.getHomeSurfacePreference(reqUser ? reqUser.id : "default")
|
|
731
|
+
: null;
|
|
732
|
+
var preferredContextSlug = homePreference && homePreference.projectSlug;
|
|
733
|
+
if (preferredContextSlug && projects.has(preferredContextSlug)) {
|
|
734
|
+
if (reqUser && onGetProjectAccess) {
|
|
735
|
+
var preferredAccess = onGetProjectAccess(preferredContextSlug);
|
|
736
|
+
if (preferredAccess && !preferredAccess.error && users.canAccessProject(reqUser.id, preferredAccess)) {
|
|
737
|
+
targetSlug = preferredContextSlug;
|
|
738
|
+
}
|
|
739
|
+
} else {
|
|
740
|
+
targetSlug = preferredContextSlug;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
668
743
|
// Check for last-visited project cookie
|
|
669
744
|
var lastProject = parseCookies(req)["clay_last_project"];
|
|
670
|
-
if (lastProject && projects.has(lastProject)) {
|
|
745
|
+
if (!targetSlug && lastProject && projects.has(lastProject)) {
|
|
671
746
|
if (reqUser && onGetProjectAccess) {
|
|
672
747
|
var lpAccess = onGetProjectAccess(lastProject);
|
|
673
748
|
if (lpAccess && !lpAccess.error && users.canAccessProject(reqUser.id, lpAccess)) {
|
|
@@ -692,8 +767,15 @@ function createServer(opts) {
|
|
|
692
767
|
});
|
|
693
768
|
}
|
|
694
769
|
if (targetSlug) {
|
|
695
|
-
|
|
696
|
-
|
|
770
|
+
try {
|
|
771
|
+
var homeHtml = fs.readFileSync(path.join(publicDir, "index.html"), "utf8");
|
|
772
|
+
homeHtml = homeHtml.replace("<body>", '<body data-home-project-slug="' + targetSlug + '">');
|
|
773
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
|
|
774
|
+
res.end(homeHtml);
|
|
775
|
+
} catch (e) {
|
|
776
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
777
|
+
res.end("App shell missing.");
|
|
778
|
+
}
|
|
697
779
|
return;
|
|
698
780
|
}
|
|
699
781
|
}
|
|
@@ -1028,10 +1110,9 @@ function createServer(opts) {
|
|
|
1028
1110
|
unreadMap[wsSlug] = 0;
|
|
1029
1111
|
}
|
|
1030
1112
|
ctx.handleConnection(ws, wsUser);
|
|
1031
|
-
// Tear down the
|
|
1032
|
-
// we don't leak callbacks against Clay's session manager.
|
|
1113
|
+
// Tear down the embedded mate chat subscription on socket close.
|
|
1033
1114
|
ws.on("close", function () {
|
|
1034
|
-
try {
|
|
1115
|
+
try { homeChatHandler.handleDisconnection(ws); } catch (e) {}
|
|
1035
1116
|
});
|
|
1036
1117
|
});
|
|
1037
1118
|
});
|
|
@@ -1108,6 +1189,24 @@ function createServer(opts) {
|
|
|
1108
1189
|
}
|
|
1109
1190
|
|
|
1110
1191
|
// --- Project management ---
|
|
1192
|
+
function refreshVendorAuthEverywhere(vendor, linuxUser) {
|
|
1193
|
+
var refreshes = [];
|
|
1194
|
+
projects.forEach(function (projectCtx) {
|
|
1195
|
+
if (!projectCtx || typeof projectCtx.refreshVendorAuth !== "function") return;
|
|
1196
|
+
try {
|
|
1197
|
+
refreshes.push(Promise.resolve(projectCtx.refreshVendorAuth(vendor, linuxUser)).catch(function (err) {
|
|
1198
|
+
console.error("[server] Cross-project " + vendor + " auth refresh failed:", err && err.message ? err.message : err);
|
|
1199
|
+
return false;
|
|
1200
|
+
}));
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
console.error("[server] Cross-project " + vendor + " auth refresh threw:", err && err.message ? err.message : err);
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
return Promise.all(refreshes).then(function (results) {
|
|
1206
|
+
return results.some(function (refreshed) { return !!refreshed; });
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1111
1210
|
function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta, extraOpts) {
|
|
1112
1211
|
if (projects.has(slug)) return false;
|
|
1113
1212
|
var extra = extraOpts || {};
|
|
@@ -1119,8 +1218,22 @@ function createServer(opts) {
|
|
|
1119
1218
|
projectOwnerId: projectOwnerId || null,
|
|
1120
1219
|
worktreeMeta: worktreeMeta || null,
|
|
1121
1220
|
isMate: extra.isMate || false,
|
|
1221
|
+
mateId: extra.mateId || (extra.isMate ? path.basename(cwd) : null),
|
|
1122
1222
|
mateDisplayName: extra.mateDisplayName || "",
|
|
1123
1223
|
isHostAgent: !!extra.isHostAgent,
|
|
1224
|
+
createReadyMate: function (ws, userId, definition) {
|
|
1225
|
+
if (!mateHandler || typeof mateHandler.createReadyMateFromInterview !== "function") throw new Error("Mate creation is unavailable.");
|
|
1226
|
+
return mateHandler.createReadyMateFromInterview(ws, userId, definition);
|
|
1227
|
+
},
|
|
1228
|
+
workspaceQueryService: workspaceQueryService,
|
|
1229
|
+
projectLogsService: projectLogsService,
|
|
1230
|
+
mateKnowledgeService: mateKnowledgeService,
|
|
1231
|
+
getProjectAccess: function () {
|
|
1232
|
+
return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
|
|
1233
|
+
},
|
|
1234
|
+
shouldSuppressResponseNotification: function (session) {
|
|
1235
|
+
return shouldSuppressHomeResponse(projects, slug, session);
|
|
1236
|
+
},
|
|
1124
1237
|
pushModule: pushModule,
|
|
1125
1238
|
debug: debug,
|
|
1126
1239
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
@@ -1143,52 +1256,6 @@ function createServer(opts) {
|
|
|
1143
1256
|
});
|
|
1144
1257
|
return list;
|
|
1145
1258
|
},
|
|
1146
|
-
getAllProjectSessions: function () {
|
|
1147
|
-
var allSessions = [];
|
|
1148
|
-
projects.forEach(function (pCtx, pSlug) {
|
|
1149
|
-
if (pSlug === slug) return; // skip self
|
|
1150
|
-
var status = pCtx.getStatus();
|
|
1151
|
-
if (status.isWorktree) return;
|
|
1152
|
-
var pSm = pCtx.getSessionManager();
|
|
1153
|
-
if (!pSm) return;
|
|
1154
|
-
var projectTitle = status.title || status.project || pSlug;
|
|
1155
|
-
pSm.sessions.forEach(function (s) {
|
|
1156
|
-
if (!s.hidden && s.history && s.history.length > 0) {
|
|
1157
|
-
s._projectTitle = projectTitle;
|
|
1158
|
-
allSessions.push(s);
|
|
1159
|
-
}
|
|
1160
|
-
});
|
|
1161
|
-
});
|
|
1162
|
-
return allSessions;
|
|
1163
|
-
},
|
|
1164
|
-
// Like getAllProjectSessions but returns the per-project grouped
|
|
1165
|
-
// shape that session-search.searchPalette expects. Includes self
|
|
1166
|
-
// (so the host agent can search its own past Clay conversations).
|
|
1167
|
-
// Used by clay-history-mcp-server.
|
|
1168
|
-
getAllProjectsWithSessions: function () {
|
|
1169
|
-
var out = [];
|
|
1170
|
-
projects.forEach(function (pCtx, pSlug) {
|
|
1171
|
-
var status = pCtx.getStatus();
|
|
1172
|
-
if (status.isWorktree) return;
|
|
1173
|
-
var pSm = pCtx.getSessionManager();
|
|
1174
|
-
if (!pSm) return;
|
|
1175
|
-
var sessions = [];
|
|
1176
|
-
pSm.sessions.forEach(function (s) {
|
|
1177
|
-
if (s.hidden) return;
|
|
1178
|
-
sessions.push(s);
|
|
1179
|
-
});
|
|
1180
|
-
if (sessions.length === 0) return;
|
|
1181
|
-
out.push({
|
|
1182
|
-
projectSlug: pSlug,
|
|
1183
|
-
projectTitle: status.title || status.project || pSlug,
|
|
1184
|
-
projectIcon: status.icon || null,
|
|
1185
|
-
isMate: !!status.isMate,
|
|
1186
|
-
mateId: status.mateId || null,
|
|
1187
|
-
sessions: sessions,
|
|
1188
|
-
});
|
|
1189
|
-
});
|
|
1190
|
-
return out;
|
|
1191
|
-
},
|
|
1192
1259
|
getHubSchedules: function () {
|
|
1193
1260
|
var allSchedules = [];
|
|
1194
1261
|
projects.forEach(function (ctx, s) {
|
|
@@ -1277,6 +1344,29 @@ function createServer(opts) {
|
|
|
1277
1344
|
onSetProjectDefaultMode: onSetProjectDefaultMode,
|
|
1278
1345
|
onGetProjectLastVendor: onGetProjectLastVendor,
|
|
1279
1346
|
onSetProjectLastVendor: onSetProjectLastVendor,
|
|
1347
|
+
listControllableTools: function (userId) {
|
|
1348
|
+
return toolsHandler ? toolsHandler.installedManifests(userId) : [];
|
|
1349
|
+
},
|
|
1350
|
+
controlToolForMate: function (userId, mateId, toolId, kind, payload) {
|
|
1351
|
+
if (!toolsHandler) return Promise.reject(new Error("Tool control is unavailable."));
|
|
1352
|
+
return toolsHandler.controlForMate(userId, mateId, toolId, kind, payload);
|
|
1353
|
+
},
|
|
1354
|
+
installToolForMate: function (userId, input) {
|
|
1355
|
+
if (!toolsHandler) return Promise.reject(new Error("Tool installation is unavailable."));
|
|
1356
|
+
return toolsHandler.installForMate(userId, input);
|
|
1357
|
+
},
|
|
1358
|
+
sourceToolForMate: function (userId, toolId) {
|
|
1359
|
+
if (!toolsHandler) return Promise.reject(new Error("Capsule source access is unavailable."));
|
|
1360
|
+
return toolsHandler.sourceForMate(userId, toolId);
|
|
1361
|
+
},
|
|
1362
|
+
updateToolForMate: function (userId, toolId, input) {
|
|
1363
|
+
if (!toolsHandler) return Promise.reject(new Error("Capsule source updates are unavailable."));
|
|
1364
|
+
return toolsHandler.updateForMate(userId, toolId, input);
|
|
1365
|
+
},
|
|
1366
|
+
removeToolForMate: function (userId, toolId) {
|
|
1367
|
+
if (!toolsHandler) return Promise.reject(new Error("Tool removal is unavailable."));
|
|
1368
|
+
return toolsHandler.removeForMate(userId, toolId);
|
|
1369
|
+
},
|
|
1280
1370
|
onGetProjectMcpServers: onGetProjectMcpServers,
|
|
1281
1371
|
onSetProjectMcpServers: onSetProjectMcpServers,
|
|
1282
1372
|
onGetDaemonConfig: onGetDaemonConfig,
|
|
@@ -1291,10 +1381,12 @@ function createServer(opts) {
|
|
|
1291
1381
|
onDmMessage: handleDmMessage,
|
|
1292
1382
|
broadcastAll: broadcastAll,
|
|
1293
1383
|
notificationsModule: _globalNotifications,
|
|
1384
|
+
refreshVendorAuthEverywhere: refreshVendorAuthEverywhere,
|
|
1294
1385
|
getProject: function (s) { return projects.get(s) || null; },
|
|
1295
1386
|
isUserOnline: isUserOnline,
|
|
1296
1387
|
});
|
|
1297
1388
|
projects.set(slug, ctx);
|
|
1389
|
+
assignmentService.reconcileProject(slug);
|
|
1298
1390
|
// ctx.warmup() is now deferred to the first websocket connection into
|
|
1299
1391
|
// this project (see project-connection.js handleConnection). Warming
|
|
1300
1392
|
// every project at startup spawned a CodexAppServer and an mcp-bridge
|
|
@@ -1318,12 +1410,24 @@ function createServer(opts) {
|
|
|
1318
1410
|
// --- Email account handler (per-user email account management) ---
|
|
1319
1411
|
var emailHandler = serverEmail.attachEmail({ users: users });
|
|
1320
1412
|
|
|
1321
|
-
// ---
|
|
1322
|
-
var
|
|
1413
|
+
// --- Embedded mate chat handler ---
|
|
1414
|
+
var homeChatHandler = serverHomeChat.attachHomeChat({
|
|
1323
1415
|
users: users,
|
|
1324
1416
|
mates: mates,
|
|
1325
1417
|
projects: projects,
|
|
1326
1418
|
addProject: addProject,
|
|
1419
|
+
workspaceQueryService: workspaceQueryService,
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
var homePreferencesHandler = serverHomePreferences.attachHomePreferences({
|
|
1423
|
+
users: users,
|
|
1424
|
+
projects: projects,
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
// --- User-installed home tools handler ---
|
|
1428
|
+
var toolsHandler = serverTools.attachTools({
|
|
1429
|
+
users: users,
|
|
1430
|
+
projects: projects,
|
|
1327
1431
|
});
|
|
1328
1432
|
|
|
1329
1433
|
// --- Mate handler ---
|
|
@@ -1333,11 +1437,14 @@ function createServer(opts) {
|
|
|
1333
1437
|
if (mateHandler) mateHandler.scheduleRegistryRefresh();
|
|
1334
1438
|
}
|
|
1335
1439
|
|
|
1336
|
-
function handleDmMessage(ws, msg) {
|
|
1440
|
+
function handleDmMessage(ws, msg, routedProjectSlug) {
|
|
1441
|
+
if (assignmentService.handleMessage(ws, msg, routedProjectSlug)) return;
|
|
1337
1442
|
if (dmHandler.handleMessage(ws, msg)) return;
|
|
1338
1443
|
if (mateHandler && mateHandler.handleMessage(ws, msg)) return;
|
|
1339
1444
|
if (emailHandler.handleMessage(ws, msg)) return;
|
|
1340
|
-
if (
|
|
1445
|
+
if (homeChatHandler.handleMessage(ws, msg)) return;
|
|
1446
|
+
if (homePreferencesHandler.handleMessage(ws, msg)) return;
|
|
1447
|
+
if (toolsHandler.handleMessage(ws, msg)) return;
|
|
1341
1448
|
}
|
|
1342
1449
|
|
|
1343
1450
|
function removeProject(slug) {
|
|
@@ -6,14 +6,14 @@ function getToolDefs(handlers) {
|
|
|
6
6
|
return [
|
|
7
7
|
{
|
|
8
8
|
name: "send_to_partner",
|
|
9
|
-
description: "Delegate one concrete task to the Worker. If this session has no pair yet, Clay creates a real Worker session and opens it visibly in the right pane before starting the task. A completed turn does not end the Worker session: reuse the same Worker for follow-up implementation and corrections instead of taking over its work. Detached completions are pushed back automatically. A delegated turn cannot delegate back, so keep orchestration one hop deep.",
|
|
9
|
+
description: "Delegate one concrete task to the visible Split Worker. If this session has no pair yet, Clay creates a real Split Worker session and opens it visibly in the right pane before starting the task. A completed turn does not end the Split Worker session: reuse the same Split Worker for follow-up implementation and corrections instead of taking over its work. Detached completions are pushed back automatically. A delegated turn cannot delegate back, so keep orchestration one hop deep.",
|
|
10
10
|
inputSchema: buildShape({
|
|
11
11
|
message: { type: "string", description: "The complete task or question for the partner." },
|
|
12
12
|
wait: { type: "boolean", description: "Wait for the partner's turn to finish. Defaults to true." },
|
|
13
13
|
timeoutSeconds: { type: "number", description: "Maximum wait in seconds, from 1 to 900. Defaults to 300." },
|
|
14
|
-
workerVendor: { type: "string", description: "Optional Worker vendor to use only when creating a new pair. Defaults to a suitable installed vendor." },
|
|
15
|
-
workerModel: { type: "string", description: "Optional Worker model to use only when creating a new pair." },
|
|
16
|
-
workerEffort: { type: "string", description: "Optional Worker reasoning effort to use only when creating a new pair." },
|
|
14
|
+
workerVendor: { type: "string", description: "Optional Split Worker vendor to use only when creating a new pair. Defaults to a suitable installed vendor." },
|
|
15
|
+
workerModel: { type: "string", description: "Optional Split Worker model to use only when creating a new pair." },
|
|
16
|
+
workerEffort: { type: "string", description: "Optional Split Worker reasoning effort to use only when creating a new pair." },
|
|
17
17
|
}, ["message"]),
|
|
18
18
|
handler: function (args) { return handlers.send(args || {}); },
|
|
19
19
|
},
|
|
@@ -27,13 +27,13 @@ function getToolDefs(handlers) {
|
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
name: "interrupt_partner",
|
|
30
|
-
description: "Interrupt the Worker's current task. Use this when the task is going in the wrong direction, needs to be reprioritized, or must stop before the next instruction. The Worker returns any partial result to you for review.",
|
|
30
|
+
description: "Interrupt the Split Worker's current task. Use this when the task is going in the wrong direction, needs to be reprioritized, or must stop before the next instruction. The Split Worker returns any partial result to you for review.",
|
|
31
31
|
inputSchema: buildShape({}),
|
|
32
32
|
handler: function (args) { return handlers.interrupt(args || {}); },
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
name: "close_partner",
|
|
36
|
-
description: "Close the visible Worker pane and dissolve the Driver/Worker pair while preserving both session histories. If the Worker is still running, Clay interrupts it before closing the pair.",
|
|
36
|
+
description: "Close the visible Split Worker pane and dissolve the Driver/Split Worker pair while preserving both session histories. If the Split Worker is still running, Clay interrupts it before closing the pair.",
|
|
37
37
|
inputSchema: buildShape({}),
|
|
38
38
|
handler: function (args) { return handlers.close(args || {}); },
|
|
39
39
|
},
|
package/lib/sessions.js
CHANGED
|
@@ -5,6 +5,7 @@ var utils = require("./utils");
|
|
|
5
5
|
var users = require("./users");
|
|
6
6
|
var { CODEX_DEFAULTS } = require("./codex-defaults");
|
|
7
7
|
var hygiene = require("./session-hygiene");
|
|
8
|
+
var backgroundTaskTiming = require("./background-task-timing");
|
|
8
9
|
|
|
9
10
|
function createSessionManager(opts) {
|
|
10
11
|
var cwd = opts.cwd;
|
|
@@ -170,9 +171,15 @@ function createSessionManager(opts) {
|
|
|
170
171
|
if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
|
|
171
172
|
if (session.loop) metaObj.loop = session.loop;
|
|
172
173
|
if (session.spawn) metaObj.spawn = session.spawn;
|
|
174
|
+
if (session.assignment) metaObj.assignment = session.assignment;
|
|
173
175
|
if (session.handoff) metaObj.handoff = session.handoff;
|
|
174
176
|
if (session.debateState) metaObj.debateState = session.debateState;
|
|
175
177
|
if (session.debateSetupMode) metaObj.debateSetupMode = true;
|
|
178
|
+
if (session.homeDebatePlanning) metaObj.homeDebatePlanning = true;
|
|
179
|
+
if (session.homeDebatePhase) metaObj.homeDebatePhase = session.homeDebatePhase;
|
|
180
|
+
if (session.homeDebateInitialTopic) metaObj.homeDebateInitialTopic = session.homeDebateInitialTopic;
|
|
181
|
+
if (session.homeClayEntryRequestId) metaObj.homeClayEntryRequestId = session.homeClayEntryRequestId;
|
|
182
|
+
if (session.homeClayEntryMode) metaObj.homeClayEntryMode = session.homeClayEntryMode;
|
|
176
183
|
var meta = JSON.stringify(metaObj);
|
|
177
184
|
var lines = [meta];
|
|
178
185
|
for (var i = 0; i < session.history.length; i++) {
|
|
@@ -283,9 +290,15 @@ function createSessionManager(opts) {
|
|
|
283
290
|
session.effort = m.effort || null;
|
|
284
291
|
if (m.loop) session.loop = m.loop;
|
|
285
292
|
if (m.spawn) session.spawn = m.spawn;
|
|
293
|
+
if (m.assignment) session.assignment = m.assignment;
|
|
286
294
|
if (m.handoff) session.handoff = m.handoff;
|
|
287
295
|
if (m.debateState) session.debateState = m.debateState;
|
|
288
296
|
if (m.debateSetupMode) session.debateSetupMode = true;
|
|
297
|
+
if (m.homeDebatePlanning) session.homeDebatePlanning = true;
|
|
298
|
+
if (m.homeDebatePhase) session.homeDebatePhase = m.homeDebatePhase;
|
|
299
|
+
if (m.homeDebateInitialTopic) session.homeDebateInitialTopic = m.homeDebateInitialTopic;
|
|
300
|
+
if (m.homeClayEntryRequestId) session.homeClayEntryRequestId = m.homeClayEntryRequestId;
|
|
301
|
+
if (m.homeClayEntryMode) session.homeClayEntryMode = m.homeClayEntryMode;
|
|
289
302
|
if (m.ownerId) session.ownerId = m.ownerId;
|
|
290
303
|
// Born-TUI session: PTY is gone after restart, but the cliSessionId
|
|
291
304
|
// is still resumable via `claude --resume <id>`. We mark the mode
|
|
@@ -496,7 +509,9 @@ function createSessionManager(opts) {
|
|
|
496
509
|
title: s.title || "New Session",
|
|
497
510
|
active: isActive,
|
|
498
511
|
isProcessing: s.isProcessing,
|
|
499
|
-
|
|
512
|
+
// Sidebar badge: user work only. Ambient housekeeping is surfaced in the
|
|
513
|
+
// composer's expanded list, never as a count demanding attention.
|
|
514
|
+
backgroundTaskCount: backgroundTaskTiming.countUserTasks(s.activeBackgroundTasks),
|
|
500
515
|
lastActivity: s.lastActivity || s.createdAt || 0,
|
|
501
516
|
loop: loop,
|
|
502
517
|
spawn: s.spawn || null,
|
|
@@ -865,6 +880,7 @@ function createSessionManager(opts) {
|
|
|
865
880
|
if (sendEach) {
|
|
866
881
|
var data = JSON.stringify(obj);
|
|
867
882
|
sendEach(function (ws) {
|
|
883
|
+
if (session.homeDebatePlanning === true) return;
|
|
868
884
|
if (ws._clayActiveSession === session.localId && ws.readyState === 1) {
|
|
869
885
|
ws.send(data);
|
|
870
886
|
}
|
|
@@ -881,8 +897,8 @@ function createSessionManager(opts) {
|
|
|
881
897
|
if (!outgoingObj._ts) outgoingObj._ts = obj._ts;
|
|
882
898
|
session.history.push(obj);
|
|
883
899
|
appendToSessionFile(session, obj);
|
|
884
|
-
// Per-session out-of-band subscribers
|
|
885
|
-
//
|
|
900
|
+
// Per-session out-of-band subscribers mirror mate session events into
|
|
901
|
+
// a parallel UI without joining the project's
|
|
886
902
|
// ws clients set). Subscribers receive the same obj that goes to ws
|
|
887
903
|
// clients; they are responsible for any transform + dispatch.
|
|
888
904
|
if (session._subscribers && session._subscribers.size > 0) {
|
|
@@ -895,6 +911,7 @@ function createSessionManager(opts) {
|
|
|
895
911
|
var data = JSON.stringify(outgoingObj);
|
|
896
912
|
var ioData = null;
|
|
897
913
|
sendEach(function (ws) {
|
|
914
|
+
if (session.homeDebatePlanning === true) return;
|
|
898
915
|
if (ws._clayActiveSession === session.localId) {
|
|
899
916
|
if (ws.readyState === 1) ws.send(data);
|
|
900
917
|
} else if (session.isProcessing && !session._ioThrottle) {
|
|
@@ -993,11 +1010,12 @@ function createSessionManager(opts) {
|
|
|
993
1010
|
activeSessionId = mostRecent.localId;
|
|
994
1011
|
}
|
|
995
1012
|
|
|
996
|
-
function searchSessions(query) {
|
|
1013
|
+
function searchSessions(query, canInclude) {
|
|
997
1014
|
if (!query) return [];
|
|
998
1015
|
var q = query.toLowerCase();
|
|
999
1016
|
var results = [];
|
|
1000
1017
|
sessions.forEach(function (session) {
|
|
1018
|
+
if (typeof canInclude === "function" && !canInclude(session)) return;
|
|
1001
1019
|
var titleMatch = (session.title || "New Session").toLowerCase().indexOf(q) !== -1;
|
|
1002
1020
|
var contentMatch = false;
|
|
1003
1021
|
for (var i = 0; i < session.history.length; i++) {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
var fs = require("fs");
|
|
2
|
+
var path = require("path");
|
|
3
|
+
var crypto = require("crypto");
|
|
4
|
+
|
|
5
|
+
var METADATA_FILE = ".capsule-metadata.json";
|
|
6
|
+
|
|
7
|
+
function metadataPath(root) {
|
|
8
|
+
return path.join(root, METADATA_FILE);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function readMetadata(root) {
|
|
12
|
+
try {
|
|
13
|
+
var parsed = JSON.parse(fs.readFileSync(metadataPath(root), "utf8"));
|
|
14
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
15
|
+
} catch (error) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeFileAtomic(file, value) {
|
|
21
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
22
|
+
var temporary = file + ".tmp-" + process.pid + "-" + crypto.randomBytes(6).toString("hex");
|
|
23
|
+
try {
|
|
24
|
+
fs.writeFileSync(temporary, value, { encoding: "utf8", mode: 0o600 });
|
|
25
|
+
fs.renameSync(temporary, file);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
try { fs.rmSync(temporary, { force: true }); } catch (cleanupError) {}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function sanitizedMetadata(root, toolId) {
|
|
33
|
+
var record = readMetadata(root)[toolId];
|
|
34
|
+
return { mateEditingAllowed: !!(record && record.mateEditingAllowed === true) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function setMateEditingAllowed(root, toolId, allowed) {
|
|
38
|
+
var all = readMetadata(root);
|
|
39
|
+
all[toolId] = { mateEditingAllowed: allowed === true };
|
|
40
|
+
writeFileAtomic(metadataPath(root), JSON.stringify(all, null, 2) + "\n");
|
|
41
|
+
return sanitizedMetadata(root, toolId);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function removeMetadata(root, toolId) {
|
|
45
|
+
var all = readMetadata(root);
|
|
46
|
+
if (!Object.prototype.hasOwnProperty.call(all, toolId)) return;
|
|
47
|
+
delete all[toolId];
|
|
48
|
+
writeFileAtomic(metadataPath(root), JSON.stringify(all, null, 2) + "\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function revisionForParts(manifestSource, uiSource, logicSource) {
|
|
52
|
+
var hash = crypto.createHash("sha256");
|
|
53
|
+
var parts = [manifestSource, uiSource, logicSource === null ? "" : logicSource];
|
|
54
|
+
for (var i = 0; i < parts.length; i++) {
|
|
55
|
+
var bytes = Buffer.from(parts[i], "utf8");
|
|
56
|
+
hash.update(String(bytes.length));
|
|
57
|
+
hash.update(":");
|
|
58
|
+
hash.update(bytes);
|
|
59
|
+
}
|
|
60
|
+
return hash.digest("hex");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readSource(directory) {
|
|
64
|
+
var manifestSource = fs.readFileSync(path.join(directory, "manifest.json"), "utf8");
|
|
65
|
+
var uiSource = fs.readFileSync(path.join(directory, "ui.json"), "utf8");
|
|
66
|
+
var manifest = JSON.parse(manifestSource);
|
|
67
|
+
var uiTree = JSON.parse(uiSource);
|
|
68
|
+
var runtime = manifest.runtime || "worker";
|
|
69
|
+
var logicSource = runtime === "worker" ? fs.readFileSync(path.join(directory, "logic.js"), "utf8") : null;
|
|
70
|
+
return {
|
|
71
|
+
manifest: manifest,
|
|
72
|
+
uiTree: uiTree,
|
|
73
|
+
logicSource: logicSource,
|
|
74
|
+
logicAvailable: runtime === "worker",
|
|
75
|
+
revision: revisionForParts(manifestSource, uiSource, logicSource),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function replaceSourceFiles(directory, sources) {
|
|
80
|
+
var names = ["manifest.json", "ui.json", "logic.js"];
|
|
81
|
+
var transaction = crypto.randomBytes(6).toString("hex");
|
|
82
|
+
var staged = [];
|
|
83
|
+
var backups = [];
|
|
84
|
+
try {
|
|
85
|
+
for (var i = 0; i < names.length; i++) {
|
|
86
|
+
var target = path.join(directory, names[i]);
|
|
87
|
+
var stage = target + ".next-" + transaction;
|
|
88
|
+
staged.push({ target: target, stage: stage });
|
|
89
|
+
fs.writeFileSync(stage, sources[names[i]], "utf8");
|
|
90
|
+
}
|
|
91
|
+
for (var j = 0; j < staged.length; j++) {
|
|
92
|
+
var backup = staged[j].target + ".previous-" + transaction;
|
|
93
|
+
fs.renameSync(staged[j].target, backup);
|
|
94
|
+
backups.push({ target: staged[j].target, backup: backup });
|
|
95
|
+
fs.renameSync(staged[j].stage, staged[j].target);
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
for (var ri = backups.length - 1; ri >= 0; ri--) {
|
|
99
|
+
try {
|
|
100
|
+
fs.rmSync(backups[ri].target, { force: true });
|
|
101
|
+
fs.renameSync(backups[ri].backup, backups[ri].target);
|
|
102
|
+
} catch (rollbackError) {}
|
|
103
|
+
}
|
|
104
|
+
for (var si = 0; si < staged.length; si++) {
|
|
105
|
+
try { fs.rmSync(staged[si].stage, { force: true }); } catch (cleanupError) {}
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
for (var k = 0; k < backups.length; k++) {
|
|
110
|
+
try { fs.rmSync(backups[k].backup, { force: true }); } catch (cleanupError) {}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function updateSource(directory, input, validators) {
|
|
115
|
+
var current = readSource(directory);
|
|
116
|
+
if (!current.logicAvailable) throw new Error("Server-managed Capsules cannot be edited.");
|
|
117
|
+
if (!input || typeof input !== "object") throw new Error("Complete Capsule source is required.");
|
|
118
|
+
if (typeof input.baseRevision !== "string" || input.baseRevision !== current.revision) {
|
|
119
|
+
throw new Error("Capsule source changed. Read the latest source and retry your update.");
|
|
120
|
+
}
|
|
121
|
+
validators.validateManifest(input.manifest, { allowServerRuntime: false });
|
|
122
|
+
validators.validateUiNode(input.uiTree);
|
|
123
|
+
if (input.manifest.id !== current.manifest.id) throw new Error("Capsule updates cannot change the Capsule ID.");
|
|
124
|
+
if ((input.manifest.runtime || "worker") !== "worker") throw new Error("Capsule updates cannot change the runtime.");
|
|
125
|
+
if (typeof input.logicSource !== "string" || !input.logicSource.trim()) throw new Error("Tool logic source is required.");
|
|
126
|
+
var manifest = Object.assign({}, input.manifest, { name: input.manifest.name.trim(), runtime: "worker" });
|
|
127
|
+
replaceSourceFiles(directory, {
|
|
128
|
+
"manifest.json": JSON.stringify(manifest, null, 2) + "\n",
|
|
129
|
+
"ui.json": JSON.stringify(input.uiTree, null, 2) + "\n",
|
|
130
|
+
"logic.js": input.logicSource,
|
|
131
|
+
});
|
|
132
|
+
return readSource(directory);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
METADATA_FILE: METADATA_FILE,
|
|
137
|
+
sanitizedMetadata: sanitizedMetadata,
|
|
138
|
+
setMateEditingAllowed: setMateEditingAllowed,
|
|
139
|
+
removeMetadata: removeMetadata,
|
|
140
|
+
readSource: readSource,
|
|
141
|
+
updateSource: updateSource,
|
|
142
|
+
};
|