clay-server 4.0.0-beta.2 → 4.0.0-beta.21
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/background-task-timing.js +105 -0
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -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/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +14 -1
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-vendor-login.js +20 -3
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +178 -6
- package/lib/public/app.js +58 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +163 -28
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +52 -0
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +34 -1
- package/lib/public/index.html +45 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +109 -4
- package/lib/public/modules/app-home-hub.js +19 -7
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +84 -7
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +46 -4
- package/lib/public/modules/app-projects.js +13 -8
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +21 -1
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -12
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +132 -46
- package/lib/sdk-message-processor.js +60 -14
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +124 -18
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +39 -0
- package/lib/sessions.js +61 -11
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +2 -2
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +81 -4
- package/lib/yoke/adapters/codex.js +63 -15
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +3 -3
package/lib/sessions.js
CHANGED
|
@@ -5,6 +5,9 @@ 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");
|
|
9
|
+
var sessionProvenance = require("./session-provenance");
|
|
10
|
+
var sessionVisibility = require("./session-visibility");
|
|
8
11
|
|
|
9
12
|
function createSessionManager(opts) {
|
|
10
13
|
var cwd = opts.cwd;
|
|
@@ -164,7 +167,8 @@ function createSessionManager(opts) {
|
|
|
164
167
|
if (session.dangerouslySkipPermissions) metaObj.dangerouslySkipPermissions = true;
|
|
165
168
|
if (session.permissionMode) metaObj.permissionMode = session.permissionMode;
|
|
166
169
|
if (session.permissionModeBeforeFullAccess) metaObj.permissionModeBeforeFullAccess = session.permissionModeBeforeFullAccess;
|
|
167
|
-
|
|
170
|
+
metaObj.sessionVisibility = sessionVisibility.normalize(session.sessionVisibility);
|
|
171
|
+
if (session.sessionVisibilityExplicit) metaObj.sessionVisibilityExplicit = true;
|
|
168
172
|
if (session.bookmarked) metaObj.bookmarked = true;
|
|
169
173
|
if (typeof session.favoriteOrder === "number") metaObj.favoriteOrder = session.favoriteOrder;
|
|
170
174
|
if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
|
|
@@ -179,6 +183,9 @@ function createSessionManager(opts) {
|
|
|
179
183
|
if (session.homeDebateInitialTopic) metaObj.homeDebateInitialTopic = session.homeDebateInitialTopic;
|
|
180
184
|
if (session.homeClayEntryRequestId) metaObj.homeClayEntryRequestId = session.homeClayEntryRequestId;
|
|
181
185
|
if (session.homeClayEntryMode) metaObj.homeClayEntryMode = session.homeClayEntryMode;
|
|
186
|
+
var provenanceMeta = sessionProvenance.metadata(session);
|
|
187
|
+
metaObj.sessionOriginId = provenanceMeta.sessionOriginId;
|
|
188
|
+
if (provenanceMeta.sessionProvenance) metaObj.sessionProvenance = provenanceMeta.sessionProvenance;
|
|
182
189
|
var meta = JSON.stringify(metaObj);
|
|
183
190
|
var lines = [meta];
|
|
184
191
|
for (var i = 0; i < session.history.length; i++) {
|
|
@@ -299,6 +306,8 @@ function createSessionManager(opts) {
|
|
|
299
306
|
if (m.homeClayEntryRequestId) session.homeClayEntryRequestId = m.homeClayEntryRequestId;
|
|
300
307
|
if (m.homeClayEntryMode) session.homeClayEntryMode = m.homeClayEntryMode;
|
|
301
308
|
if (m.ownerId) session.ownerId = m.ownerId;
|
|
309
|
+
session.sessionOriginId = m.sessionOriginId || null;
|
|
310
|
+
session._provenanceMigrationNeeded = sessionProvenance.restore(m, session);
|
|
302
311
|
// Born-TUI session: PTY is gone after restart, but the cliSessionId
|
|
303
312
|
// is still resumable via `claude --resume <id>`. We mark the mode
|
|
304
313
|
// here so it shows up in the sidebar with the right icon; the
|
|
@@ -314,11 +323,26 @@ function createSessionManager(opts) {
|
|
|
314
323
|
session.terminalId = null;
|
|
315
324
|
session.runtimeMode = null;
|
|
316
325
|
session.runtimeTerminalId = null;
|
|
317
|
-
session.sessionVisibility = m
|
|
326
|
+
session.sessionVisibility = sessionVisibility.restore(m);
|
|
327
|
+
session.sessionVisibilityExplicit = m.sessionVisibilityExplicit === true;
|
|
318
328
|
session.bookmarked = !!m.bookmarked;
|
|
319
329
|
session.favoriteOrder = typeof m.favoriteOrder === "number" ? m.favoriteOrder : null;
|
|
320
330
|
sessions.set(localId, session);
|
|
321
331
|
}
|
|
332
|
+
|
|
333
|
+
// Versionless legacy records are safe top-level Drivers. Assigning an
|
|
334
|
+
// opaque origin ID is the only migration: no title, slug or active split
|
|
335
|
+
// state is used to invent historical parentage.
|
|
336
|
+
sessions.forEach(function (session) {
|
|
337
|
+
if (!session.cliSessionId || !session._provenanceMigrationNeeded) return;
|
|
338
|
+
var activityTime = session.lastActivity;
|
|
339
|
+
saveSessionFile(session);
|
|
340
|
+
try {
|
|
341
|
+
var preservedTime = new Date(activityTime);
|
|
342
|
+
fs.utimesSync(sessionFilePath(session.cliSessionId), preservedTime, preservedTime);
|
|
343
|
+
} catch (e) {}
|
|
344
|
+
delete session._provenanceMigrationNeeded;
|
|
345
|
+
});
|
|
322
346
|
}
|
|
323
347
|
|
|
324
348
|
// Adopt orphaned CLI sessions from ~/.claude/projects/<encoded-cwd>/ as
|
|
@@ -426,7 +450,7 @@ function createSessionManager(opts) {
|
|
|
426
450
|
terminalId: null,
|
|
427
451
|
runtimeMode: null,
|
|
428
452
|
runtimeTerminalId: null,
|
|
429
|
-
sessionVisibility: "
|
|
453
|
+
sessionVisibility: "private",
|
|
430
454
|
bookmarked: false,
|
|
431
455
|
favoriteOrder: null,
|
|
432
456
|
};
|
|
@@ -508,12 +532,14 @@ function createSessionManager(opts) {
|
|
|
508
532
|
title: s.title || "New Session",
|
|
509
533
|
active: isActive,
|
|
510
534
|
isProcessing: s.isProcessing,
|
|
511
|
-
|
|
535
|
+
// Sidebar badge: user work only. Ambient housekeeping is surfaced in the
|
|
536
|
+
// composer's expanded list, never as a count demanding attention.
|
|
537
|
+
backgroundTaskCount: backgroundTaskTiming.countUserTasks(s.activeBackgroundTasks),
|
|
512
538
|
lastActivity: s.lastActivity || s.createdAt || 0,
|
|
513
539
|
loop: loop,
|
|
514
540
|
spawn: s.spawn || null,
|
|
515
541
|
ownerId: s.ownerId || null,
|
|
516
|
-
sessionVisibility: s.sessionVisibility
|
|
542
|
+
sessionVisibility: sessionVisibility.normalize(s.sessionVisibility),
|
|
517
543
|
bookmarked: !!s.bookmarked,
|
|
518
544
|
favoriteOrder: typeof s.favoriteOrder === "number" ? s.favoriteOrder : null,
|
|
519
545
|
unread: unreadMap[s.localId] || 0,
|
|
@@ -529,6 +555,19 @@ function createSessionManager(opts) {
|
|
|
529
555
|
};
|
|
530
556
|
}
|
|
531
557
|
|
|
558
|
+
function mapSessionsForClient(source, clientActiveId, wsUnread) {
|
|
559
|
+
var hierarchy = sessionProvenance.hierarchyFor(source);
|
|
560
|
+
return source.map(function (session) {
|
|
561
|
+
var mapped = mapSessionForClient(session, clientActiveId, wsUnread);
|
|
562
|
+
var node = hierarchy[session.localId];
|
|
563
|
+
mapped.sessionRole = node.role;
|
|
564
|
+
mapped.parentSessionId = node.parentSessionId;
|
|
565
|
+
mapped.parentAvailable = node.parentAvailable;
|
|
566
|
+
mapped.workerGeneration = node.generation;
|
|
567
|
+
return mapped;
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
532
571
|
function getVisibleSessions() {
|
|
533
572
|
var multiUser = users.isMultiUser();
|
|
534
573
|
return [...sessions.values()].filter(function (s) {
|
|
@@ -551,14 +590,14 @@ function createSessionManager(opts) {
|
|
|
551
590
|
if (ws.readyState === 1) {
|
|
552
591
|
ws.send(JSON.stringify({
|
|
553
592
|
type: "session_list",
|
|
554
|
-
sessions: filtered
|
|
593
|
+
sessions: mapSessionsForClient(filtered, clientActiveId, wsUnread),
|
|
555
594
|
}));
|
|
556
595
|
}
|
|
557
596
|
});
|
|
558
597
|
} else {
|
|
559
598
|
send({
|
|
560
599
|
type: "session_list",
|
|
561
|
-
sessions: allVisible
|
|
600
|
+
sessions: mapSessionsForClient(allVisible),
|
|
562
601
|
});
|
|
563
602
|
}
|
|
564
603
|
}
|
|
@@ -584,7 +623,8 @@ function createSessionManager(opts) {
|
|
|
584
623
|
history: [],
|
|
585
624
|
messageUUIDs: [],
|
|
586
625
|
ownerId: (sessionOpts && sessionOpts.ownerId) || null,
|
|
587
|
-
sessionVisibility: (sessionOpts && sessionOpts.sessionVisibility)
|
|
626
|
+
sessionVisibility: sessionVisibility.normalize(sessionOpts && sessionOpts.sessionVisibility),
|
|
627
|
+
sessionVisibilityExplicit: !!(sessionOpts && sessionOpts.sessionVisibility === "shared"),
|
|
588
628
|
bookmarked: false,
|
|
589
629
|
favoriteOrder: null,
|
|
590
630
|
vendor: (sessionOpts && sessionOpts.vendor) || null,
|
|
@@ -595,7 +635,9 @@ function createSessionManager(opts) {
|
|
|
595
635
|
permissionMode: (sessionOpts && sessionOpts.permissionMode) || null,
|
|
596
636
|
permissionModeBeforeFullAccess: null,
|
|
597
637
|
terminalId: null,
|
|
638
|
+
sessionOriginId: null,
|
|
598
639
|
};
|
|
640
|
+
sessionProvenance.ensureOrigin(session);
|
|
599
641
|
sessions.set(localId, session);
|
|
600
642
|
switchSession(localId, targetWs);
|
|
601
643
|
return session;
|
|
@@ -623,7 +665,8 @@ function createSessionManager(opts) {
|
|
|
623
665
|
history: [],
|
|
624
666
|
messageUUIDs: [],
|
|
625
667
|
ownerId: (sessionOpts && sessionOpts.ownerId) || null,
|
|
626
|
-
sessionVisibility: (sessionOpts && sessionOpts.sessionVisibility)
|
|
668
|
+
sessionVisibility: sessionVisibility.normalize(sessionOpts && sessionOpts.sessionVisibility),
|
|
669
|
+
sessionVisibilityExplicit: !!(sessionOpts && sessionOpts.sessionVisibility === "shared"),
|
|
627
670
|
bookmarked: false,
|
|
628
671
|
favoriteOrder: null,
|
|
629
672
|
vendor: (sessionOpts && sessionOpts.vendor) || null,
|
|
@@ -634,7 +677,9 @@ function createSessionManager(opts) {
|
|
|
634
677
|
permissionMode: (sessionOpts && sessionOpts.permissionMode) || null,
|
|
635
678
|
permissionModeBeforeFullAccess: null,
|
|
636
679
|
terminalId: null,
|
|
680
|
+
sessionOriginId: null,
|
|
637
681
|
};
|
|
682
|
+
sessionProvenance.ensureOrigin(session);
|
|
638
683
|
sessions.set(localId, session);
|
|
639
684
|
return session;
|
|
640
685
|
}
|
|
@@ -876,7 +921,8 @@ function createSessionManager(opts) {
|
|
|
876
921
|
// Send to active clients without recording to history/disk (ephemeral data)
|
|
877
922
|
if (sendEach) {
|
|
878
923
|
var data = JSON.stringify(obj);
|
|
879
|
-
sendEach(function (ws) {
|
|
924
|
+
sendEach(function (ws, filterFn) {
|
|
925
|
+
if (filterFn && !filterFn(session)) return;
|
|
880
926
|
if (session.homeDebatePlanning === true) return;
|
|
881
927
|
if (ws._clayActiveSession === session.localId && ws.readyState === 1) {
|
|
882
928
|
ws.send(data);
|
|
@@ -907,7 +953,8 @@ function createSessionManager(opts) {
|
|
|
907
953
|
// Multi-user: send to clients whose active session matches this one
|
|
908
954
|
var data = JSON.stringify(outgoingObj);
|
|
909
955
|
var ioData = null;
|
|
910
|
-
sendEach(function (ws) {
|
|
956
|
+
sendEach(function (ws, filterFn) {
|
|
957
|
+
if (filterFn && !filterFn(session)) return;
|
|
911
958
|
if (session.homeDebatePlanning === true) return;
|
|
912
959
|
if (ws._clayActiveSession === session.localId) {
|
|
913
960
|
if (ws.readyState === 1) ws.send(data);
|
|
@@ -1245,11 +1292,14 @@ function createSessionManager(opts) {
|
|
|
1245
1292
|
searchSessionContent: searchSessionContent,
|
|
1246
1293
|
setResolveLoopInfo: setResolveLoopInfo,
|
|
1247
1294
|
mapSessionForClient: mapSessionForClient,
|
|
1295
|
+
mapSessionsForClient: mapSessionsForClient,
|
|
1248
1296
|
migrateSessionTitles: migrateSessionTitles,
|
|
1249
1297
|
setSessionVisibility: function (localId, visibility) {
|
|
1250
1298
|
var session = sessions.get(localId);
|
|
1251
1299
|
if (!session) return { error: "Session not found" };
|
|
1300
|
+
if (visibility !== "shared" && visibility !== "private") return { error: "Invalid session visibility" };
|
|
1252
1301
|
session.sessionVisibility = visibility;
|
|
1302
|
+
session.sessionVisibilityExplicit = true;
|
|
1253
1303
|
saveSessionFile(session);
|
|
1254
1304
|
broadcastSessionList();
|
|
1255
1305
|
return { ok: true };
|
package/lib/tools-registry.js
CHANGED
|
@@ -3,9 +3,10 @@ var path = require("path");
|
|
|
3
3
|
var config = require("./config");
|
|
4
4
|
var toolUiSpec = require("./tool-ui-spec");
|
|
5
5
|
var toolCapsuleSource = require("./tool-capsule-source");
|
|
6
|
+
var capsuleFloor = require("./capsule-display-floor");
|
|
6
7
|
|
|
7
8
|
var CAPSULES_ROOT = path.join(__dirname, "capsules");
|
|
8
|
-
var SEED_MARKER = ".capsules-
|
|
9
|
+
var SEED_MARKER = ".capsules-v10";
|
|
9
10
|
var REMOVED_BUILTIN_IDS = ["board", "translator", "scratchpad"];
|
|
10
11
|
var UI_NODE_TYPES = toolUiSpec.UI_NODE_TYPES;
|
|
11
12
|
var DISCOVERY_FIELD_MAX_LENGTH = 240;
|
|
@@ -98,6 +99,11 @@ function validateManifest(manifest, opts) {
|
|
|
98
99
|
if (manifest.skills !== undefined && typeof manifest.skills !== "string") {
|
|
99
100
|
throw new Error("Tool skills must be markdown text.");
|
|
100
101
|
}
|
|
102
|
+
// The floor gate always validates the Display itself, so a manifest may
|
|
103
|
+
// never carry a claim about it.
|
|
104
|
+
if (manifest.displayFloor !== undefined) {
|
|
105
|
+
throw new Error("Tool displayFloor cannot be authored; Clay validates the Display itself.");
|
|
106
|
+
}
|
|
101
107
|
validateDiscoveryField(manifest, "description", "description");
|
|
102
108
|
validateDiscoveryField(manifest, "useWhen", "useWhen");
|
|
103
109
|
if (manifest.permissions !== undefined) {
|
|
@@ -129,33 +135,90 @@ function toolDirectory(ctx, id) {
|
|
|
129
135
|
return path.join(resolveToolsRoot(ctx), id);
|
|
130
136
|
}
|
|
131
137
|
|
|
138
|
+
function hasAnySeedMarker(root) {
|
|
139
|
+
try {
|
|
140
|
+
return fs.readdirSync(root).some(function (name) { return /^\.capsules-v\d+$/.test(name); });
|
|
141
|
+
} catch (e) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
132
146
|
function seedBuiltInCapsules(ctx) {
|
|
133
147
|
var root = resolveToolsRoot(ctx);
|
|
134
148
|
var marker = path.join(root, SEED_MARKER);
|
|
135
149
|
if (fs.existsSync(marker)) return;
|
|
150
|
+
// The removed-built-in cleanup is a one-time migration. Any earlier marker
|
|
151
|
+
// means it already ran, so a later marker bump must not delete a Capsule the
|
|
152
|
+
// user has since authored under one of those IDs.
|
|
153
|
+
var migrated = hasAnySeedMarker(root);
|
|
136
154
|
fs.mkdirSync(root, { recursive: true });
|
|
137
|
-
for (var ri = 0; ri < REMOVED_BUILTIN_IDS.length; ri++) {
|
|
155
|
+
for (var ri = 0; !migrated && ri < REMOVED_BUILTIN_IDS.length; ri++) {
|
|
138
156
|
fs.rmSync(path.join(root, REMOVED_BUILTIN_IDS[ri]), { recursive: true, force: true });
|
|
139
157
|
}
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
158
|
+
// lib/capsules/ holds the shipped Capsules, currently the Pig validation
|
|
159
|
+
// Capsule. It may still be absent from an older checkout or tarball, and
|
|
160
|
+
// seeding also owns the removed-built-in migration above and the marker
|
|
161
|
+
// below, so a missing root means "nothing to copy" rather than a failure.
|
|
162
|
+
// Anything other than a missing root is still a real error.
|
|
145
163
|
var entries = [];
|
|
146
164
|
try {
|
|
147
165
|
entries = fs.readdirSync(CAPSULES_ROOT, { withFileTypes: true });
|
|
148
166
|
} catch (e) {
|
|
149
167
|
if (e.code !== "ENOENT") throw e;
|
|
150
168
|
}
|
|
169
|
+
// A previously seeded server-runtime Capsule that is no longer shipped is
|
|
170
|
+
// always a stale copy: the server runtime is reserved for lib/capsules/, so
|
|
171
|
+
// no user-authored Capsule can ever match this test. Remove it, or it would
|
|
172
|
+
// linger as a dead entry with no Logic behind it.
|
|
173
|
+
var shippedIds = Object.create(null);
|
|
174
|
+
for (var si = 0; si < entries.length; si++) {
|
|
175
|
+
if (entries[si].isDirectory()) shippedIds[entries[si].name] = true;
|
|
176
|
+
}
|
|
177
|
+
var rootEntries = [];
|
|
178
|
+
try {
|
|
179
|
+
rootEntries = fs.readdirSync(root, { withFileTypes: true });
|
|
180
|
+
} catch (e) {
|
|
181
|
+
if (e.code !== "ENOENT") throw e;
|
|
182
|
+
}
|
|
183
|
+
for (var re = 0; re < rootEntries.length; re++) {
|
|
184
|
+
if (!rootEntries[re].isDirectory() || shippedIds[rootEntries[re].name]) continue;
|
|
185
|
+
try {
|
|
186
|
+
var staleManifest = JSON.parse(fs.readFileSync(path.join(root, rootEntries[re].name, "manifest.json"), "utf8"));
|
|
187
|
+
if (staleManifest && staleManifest.runtime === "server") {
|
|
188
|
+
fs.rmSync(path.join(root, rootEntries[re].name), { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
} catch (e) { /* not a capsule directory; leave it alone */ }
|
|
191
|
+
}
|
|
192
|
+
|
|
151
193
|
var seeded = [];
|
|
194
|
+
var SOURCE_FILES = ["manifest.json", "ui.json", "logic.js", "display.js", "skills.md"];
|
|
152
195
|
for (var i = 0; i < entries.length; i++) {
|
|
153
196
|
if (!entries[i].isDirectory()) continue;
|
|
154
197
|
var destination = path.join(root, entries[i].name);
|
|
155
198
|
if (!fs.existsSync(destination)) {
|
|
156
199
|
fs.cpSync(path.join(CAPSULES_ROOT, entries[i].name), destination, { recursive: true });
|
|
157
200
|
seeded.push(entries[i].name);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
var shippedManifestPath = path.join(CAPSULES_ROOT, entries[i].name, "manifest.json");
|
|
204
|
+
var shippedRuntime = "worker";
|
|
205
|
+
try {
|
|
206
|
+
shippedRuntime = JSON.parse(fs.readFileSync(shippedManifestPath, "utf8")).runtime || "worker";
|
|
207
|
+
} catch (e) { /* fall through to the conservative worker path */ }
|
|
208
|
+
if (shippedRuntime === "server") {
|
|
209
|
+
// Server-runtime Capsules are Clay-owned: their source is not editable
|
|
210
|
+
// by users or Mates, so a marker bump syncs the shipped source files
|
|
211
|
+
// exactly, dropping ones the shipped set no longer carries. The
|
|
212
|
+
// Capsule's datastore and anything else in the directory is preserved.
|
|
213
|
+
for (var sf = 0; sf < SOURCE_FILES.length; sf++) {
|
|
214
|
+
var shippedFile = path.join(CAPSULES_ROOT, entries[i].name, SOURCE_FILES[sf]);
|
|
215
|
+
var localFile = path.join(destination, SOURCE_FILES[sf]);
|
|
216
|
+
if (fs.existsSync(shippedFile)) fs.copyFileSync(shippedFile, localFile);
|
|
217
|
+
else fs.rmSync(localFile, { force: true });
|
|
218
|
+
}
|
|
158
219
|
} else {
|
|
220
|
+
// Worker Capsules may have been edited by the user, so only files the
|
|
221
|
+
// local copy is missing entirely are filled in.
|
|
159
222
|
var sourceFiles = fs.readdirSync(path.join(CAPSULES_ROOT, entries[i].name));
|
|
160
223
|
for (var fi = 0; fi < sourceFiles.length; fi++) {
|
|
161
224
|
var sourceFile = path.join(CAPSULES_ROOT, entries[i].name, sourceFiles[fi]);
|
|
@@ -185,6 +248,11 @@ function getTool(ctx, id) {
|
|
|
185
248
|
manifest: Object.assign({}, manifest, { runtime: runtime }),
|
|
186
249
|
logicSource: runtime === "worker" ? fs.readFileSync(logicPath, "utf8") : null,
|
|
187
250
|
uiTree: uiTree,
|
|
251
|
+
// Rich Display is opted into by shipping a display.js next to the floor,
|
|
252
|
+
// the same way logic.js opts into the worker runtime. Only the flag
|
|
253
|
+
// travels; the source is served solely by the isolated frame origin, so
|
|
254
|
+
// it can never leak into a manifest, a prompt, or a Mate surface.
|
|
255
|
+
hasRichDisplay: fs.existsSync(path.join(directory, "display.js")),
|
|
188
256
|
metadata: toolCapsuleSource.sanitizedMetadata(resolveToolsRoot(ctx), id),
|
|
189
257
|
};
|
|
190
258
|
}
|
|
@@ -199,8 +267,14 @@ function listTools(ctx) {
|
|
|
199
267
|
if (entries[i].name.charAt(0) === ".") continue;
|
|
200
268
|
try {
|
|
201
269
|
var tool = getTool(ctx, entries[i].name);
|
|
202
|
-
if (tool)
|
|
203
|
-
|
|
270
|
+
if (tool) {
|
|
271
|
+
// The flat manifest carries its own Display alongside it, so a
|
|
272
|
+
// Mate-facing gate can validate the real tree without a second read
|
|
273
|
+
// and without trusting a claim. Non-enumerable, so the tree never
|
|
274
|
+
// leaks into a projection, a prompt, or a serialized message.
|
|
275
|
+
Object.defineProperty(tool.manifest, "uiTree", { value: tool.uiTree, enumerable: false, configurable: true });
|
|
276
|
+
manifests.push(tool.manifest);
|
|
277
|
+
} else manifests.push({ id: entries[i].name, error: "Capsule requires manifest.json and ui.json." });
|
|
204
278
|
} catch (e) {
|
|
205
279
|
manifests.push({ id: entries[i].name, error: e.message });
|
|
206
280
|
}
|
|
@@ -216,6 +290,7 @@ function installTool(ctx, input) {
|
|
|
216
290
|
throw new Error("Tool logic source is required.");
|
|
217
291
|
}
|
|
218
292
|
validateUiTreeForManifest(input.uiTree, input.manifest);
|
|
293
|
+
capsuleFloor.assertFloor(input.uiTree, input.manifest);
|
|
219
294
|
var manifest = Object.assign({}, input.manifest, { name: input.manifest.name.trim(), runtime: "worker" });
|
|
220
295
|
var directory = toolDirectory(ctx, manifest.id);
|
|
221
296
|
if (fs.existsSync(directory)) throw new Error("A Capsule with this ID already exists. Read its source and use the update tool instead.");
|
|
@@ -253,7 +328,10 @@ function updateTool(ctx, id, input) {
|
|
|
253
328
|
if (!tool) throw new Error("Tool not found.");
|
|
254
329
|
toolCapsuleSource.updateSource(toolDirectory(ctx, id), input, {
|
|
255
330
|
validateManifest: validateManifest,
|
|
256
|
-
validateUiNode: function (uiTree) {
|
|
331
|
+
validateUiNode: function (uiTree) {
|
|
332
|
+
validateUiTreeForManifest(uiTree, input.manifest);
|
|
333
|
+
return capsuleFloor.assertFloor(uiTree, input.manifest);
|
|
334
|
+
},
|
|
257
335
|
});
|
|
258
336
|
return getTool(ctx, id);
|
|
259
337
|
}
|
|
@@ -268,6 +346,7 @@ function removeTool(ctx, id) {
|
|
|
268
346
|
|
|
269
347
|
module.exports = {
|
|
270
348
|
CAPSULES_ROOT: CAPSULES_ROOT,
|
|
349
|
+
SEED_MARKER: SEED_MARKER,
|
|
271
350
|
UI_NODE_TYPES: UI_NODE_TYPES,
|
|
272
351
|
resolveToolsRoot: resolveToolsRoot,
|
|
273
352
|
validateToolId: validateToolId,
|