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
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
var fs = require("fs");
|
|
2
|
+
var path = require("path");
|
|
3
|
+
var config = require("./config");
|
|
4
|
+
var toolUiSpec = require("./tool-ui-spec");
|
|
5
|
+
var toolCapsuleSource = require("./tool-capsule-source");
|
|
6
|
+
|
|
7
|
+
var CAPSULES_ROOT = path.join(__dirname, "capsules");
|
|
8
|
+
var SEED_MARKER = ".capsules-v7";
|
|
9
|
+
var REMOVED_BUILTIN_IDS = ["board", "translator", "scratchpad"];
|
|
10
|
+
var UI_NODE_TYPES = toolUiSpec.UI_NODE_TYPES;
|
|
11
|
+
var DISCOVERY_FIELD_MAX_LENGTH = 240;
|
|
12
|
+
|
|
13
|
+
function resolveToolsRoot(ctx) {
|
|
14
|
+
if (ctx && ctx.linuxUser) return path.join("/home", ctx.linuxUser, ".clay", "tools");
|
|
15
|
+
if (ctx && ctx.multiUser && ctx.userId) return path.join(config.CONFIG_DIR, "tools", ctx.userId);
|
|
16
|
+
return path.join(config.CONFIG_DIR, "tools");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function validateToolId(id) {
|
|
20
|
+
if (typeof id !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id)) {
|
|
21
|
+
throw new Error("Tool ID must be a lowercase slug.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isTrustedServerRuntime(manifest) {
|
|
26
|
+
if (!manifest || manifest.runtime !== "server") return false;
|
|
27
|
+
var sourceManifest = path.join(CAPSULES_ROOT, manifest.id || "", "manifest.json");
|
|
28
|
+
try {
|
|
29
|
+
var shipped = JSON.parse(fs.readFileSync(sourceManifest, "utf8"));
|
|
30
|
+
return shipped.id === manifest.id && shipped.runtime === "server";
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function filesMatch(first, second) {
|
|
37
|
+
try {
|
|
38
|
+
return fs.readFileSync(first).equals(fs.readFileSync(second));
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function manifestWithoutHydratedMetadata(manifest) {
|
|
45
|
+
var result = Object.assign({}, manifest);
|
|
46
|
+
delete result.modelAlias;
|
|
47
|
+
delete result.description;
|
|
48
|
+
delete result.useWhen;
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hydrateShippedManifestMetadata(directory, manifest) {
|
|
53
|
+
if (!manifest) return manifest;
|
|
54
|
+
var shippedDirectory = path.join(CAPSULES_ROOT, manifest.id || "");
|
|
55
|
+
var shippedManifestPath = path.join(shippedDirectory, "manifest.json");
|
|
56
|
+
try {
|
|
57
|
+
var shipped = JSON.parse(fs.readFileSync(shippedManifestPath, "utf8"));
|
|
58
|
+
if (shipped.id !== manifest.id) return manifest;
|
|
59
|
+
if (JSON.stringify(manifestWithoutHydratedMetadata(manifest)) !== JSON.stringify(manifestWithoutHydratedMetadata(shipped))) return manifest;
|
|
60
|
+
if (!filesMatch(path.join(directory, "ui.json"), path.join(shippedDirectory, "ui.json"))) return manifest;
|
|
61
|
+
if ((manifest.runtime || "worker") === "worker" && !filesMatch(path.join(directory, "logic.js"), path.join(shippedDirectory, "logic.js"))) return manifest;
|
|
62
|
+
var hydrated = Object.assign({}, manifest);
|
|
63
|
+
var fields = ["modelAlias", "description", "useWhen"];
|
|
64
|
+
for (var i = 0; i < fields.length; i++) {
|
|
65
|
+
if (hydrated[fields[i]] === undefined && shipped[fields[i]] !== undefined) hydrated[fields[i]] = shipped[fields[i]];
|
|
66
|
+
}
|
|
67
|
+
return hydrated;
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return manifest;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function validateDiscoveryField(manifest, field, label) {
|
|
74
|
+
if (manifest[field] === undefined) return;
|
|
75
|
+
if (typeof manifest[field] !== "string" || !manifest[field].trim()) {
|
|
76
|
+
throw new Error("Tool " + label + " must be non-empty text.");
|
|
77
|
+
}
|
|
78
|
+
if (manifest[field] !== manifest[field].trim() || /[\u0000-\u001f\u007f]/.test(manifest[field])) {
|
|
79
|
+
throw new Error("Tool " + label + " must be a single trimmed line.");
|
|
80
|
+
}
|
|
81
|
+
if (manifest[field].length > DISCOVERY_FIELD_MAX_LENGTH) {
|
|
82
|
+
throw new Error("Tool " + label + " must be " + DISCOVERY_FIELD_MAX_LENGTH + " characters or fewer.");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateManifest(manifest, opts) {
|
|
87
|
+
opts = opts || {};
|
|
88
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
89
|
+
throw new Error("Tool manifest is required.");
|
|
90
|
+
}
|
|
91
|
+
validateToolId(manifest.id);
|
|
92
|
+
if (typeof manifest.name !== "string" || !manifest.name.trim()) {
|
|
93
|
+
throw new Error("Tool name is required.");
|
|
94
|
+
}
|
|
95
|
+
if (manifest.lucideIcon !== undefined && (typeof manifest.lucideIcon !== "string" || !/^[a-z0-9-]+$/.test(manifest.lucideIcon))) {
|
|
96
|
+
throw new Error("Tool icon must be a lowercase Lucide icon name.");
|
|
97
|
+
}
|
|
98
|
+
if (manifest.skills !== undefined && typeof manifest.skills !== "string") {
|
|
99
|
+
throw new Error("Tool skills must be markdown text.");
|
|
100
|
+
}
|
|
101
|
+
validateDiscoveryField(manifest, "description", "description");
|
|
102
|
+
validateDiscoveryField(manifest, "useWhen", "useWhen");
|
|
103
|
+
if (manifest.permissions !== undefined) {
|
|
104
|
+
if (!Array.isArray(manifest.permissions)) throw new Error("Tool permissions must be an array.");
|
|
105
|
+
for (var pi = 0; pi < manifest.permissions.length; pi++) {
|
|
106
|
+
if (manifest.permissions[pi] !== "llm") throw new Error("Unknown tool permission '" + manifest.permissions[pi] + "'.");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (manifest.modelAlias !== undefined && ["fast", "standard", "deep"].indexOf(manifest.modelAlias) === -1) {
|
|
110
|
+
throw new Error("Tool modelAlias must be fast, standard, or deep.");
|
|
111
|
+
}
|
|
112
|
+
var runtime = manifest.runtime || "worker";
|
|
113
|
+
if (runtime !== "worker" && runtime !== "server") {
|
|
114
|
+
throw new Error("Tool runtime must be 'worker' or 'server'.");
|
|
115
|
+
}
|
|
116
|
+
if (runtime === "server" && !opts.allowServerRuntime) {
|
|
117
|
+
throw new Error("Server-runtime capsules cannot be installed over WebSocket.");
|
|
118
|
+
}
|
|
119
|
+
if (runtime === "server" && !isTrustedServerRuntime(manifest)) {
|
|
120
|
+
throw new Error("Server runtime is reserved for shipped built-in capsules.");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
var validateUiNode = toolUiSpec.validateUiNode;
|
|
125
|
+
var validateUiTreeForManifest = toolUiSpec.validateUiTreeForManifest;
|
|
126
|
+
|
|
127
|
+
function toolDirectory(ctx, id) {
|
|
128
|
+
validateToolId(id);
|
|
129
|
+
return path.join(resolveToolsRoot(ctx), id);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function seedBuiltInCapsules(ctx) {
|
|
133
|
+
var root = resolveToolsRoot(ctx);
|
|
134
|
+
var marker = path.join(root, SEED_MARKER);
|
|
135
|
+
if (fs.existsSync(marker)) return;
|
|
136
|
+
fs.mkdirSync(root, { recursive: true });
|
|
137
|
+
for (var ri = 0; ri < REMOVED_BUILTIN_IDS.length; ri++) {
|
|
138
|
+
fs.rmSync(path.join(root, REMOVED_BUILTIN_IDS[ri]), { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
// Clay ships no built-in Capsules any more, so lib/capsules/ is absent from a
|
|
141
|
+
// fresh checkout and from the npm tarball (neither git nor npm carries an
|
|
142
|
+
// empty directory). Seeding still owns the removed-built-in migration above
|
|
143
|
+
// and the marker below, so an absent root means "nothing to copy" rather than
|
|
144
|
+
// a failure. Anything other than a missing root is still a real error.
|
|
145
|
+
var entries = [];
|
|
146
|
+
try {
|
|
147
|
+
entries = fs.readdirSync(CAPSULES_ROOT, { withFileTypes: true });
|
|
148
|
+
} catch (e) {
|
|
149
|
+
if (e.code !== "ENOENT") throw e;
|
|
150
|
+
}
|
|
151
|
+
var seeded = [];
|
|
152
|
+
for (var i = 0; i < entries.length; i++) {
|
|
153
|
+
if (!entries[i].isDirectory()) continue;
|
|
154
|
+
var destination = path.join(root, entries[i].name);
|
|
155
|
+
if (!fs.existsSync(destination)) {
|
|
156
|
+
fs.cpSync(path.join(CAPSULES_ROOT, entries[i].name), destination, { recursive: true });
|
|
157
|
+
seeded.push(entries[i].name);
|
|
158
|
+
} else {
|
|
159
|
+
var sourceFiles = fs.readdirSync(path.join(CAPSULES_ROOT, entries[i].name));
|
|
160
|
+
for (var fi = 0; fi < sourceFiles.length; fi++) {
|
|
161
|
+
var sourceFile = path.join(CAPSULES_ROOT, entries[i].name, sourceFiles[fi]);
|
|
162
|
+
var destinationFile = path.join(destination, sourceFiles[fi]);
|
|
163
|
+
if (!fs.existsSync(destinationFile)) fs.copyFileSync(sourceFile, destinationFile);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
fs.writeFileSync(marker, seeded.join("\n") + "\n", "utf8");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getTool(ctx, id) {
|
|
171
|
+
var directory = toolDirectory(ctx, id);
|
|
172
|
+
var manifestPath = path.join(directory, "manifest.json");
|
|
173
|
+
var logicPath = path.join(directory, "logic.js");
|
|
174
|
+
var uiPath = path.join(directory, "ui.json");
|
|
175
|
+
if (!fs.existsSync(manifestPath) || !fs.existsSync(uiPath)) return null;
|
|
176
|
+
var manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
177
|
+
var uiTree = JSON.parse(fs.readFileSync(uiPath, "utf8"));
|
|
178
|
+
validateManifest(manifest, { allowServerRuntime: true });
|
|
179
|
+
if (manifest.id !== id) throw new Error("Capsule folder name must match manifest ID.");
|
|
180
|
+
validateUiTreeForManifest(uiTree, manifest);
|
|
181
|
+
var runtime = manifest.runtime || "worker";
|
|
182
|
+
if (runtime === "worker" && !fs.existsSync(logicPath)) throw new Error("Worker capsule logic.js is required.");
|
|
183
|
+
manifest = hydrateShippedManifestMetadata(directory, manifest);
|
|
184
|
+
return {
|
|
185
|
+
manifest: Object.assign({}, manifest, { runtime: runtime }),
|
|
186
|
+
logicSource: runtime === "worker" ? fs.readFileSync(logicPath, "utf8") : null,
|
|
187
|
+
uiTree: uiTree,
|
|
188
|
+
metadata: toolCapsuleSource.sanitizedMetadata(resolveToolsRoot(ctx), id),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function listTools(ctx) {
|
|
193
|
+
seedBuiltInCapsules(ctx);
|
|
194
|
+
var root = resolveToolsRoot(ctx);
|
|
195
|
+
var entries = fs.readdirSync(root, { withFileTypes: true });
|
|
196
|
+
var manifests = [];
|
|
197
|
+
for (var i = 0; i < entries.length; i++) {
|
|
198
|
+
if (!entries[i].isDirectory()) continue;
|
|
199
|
+
if (entries[i].name.charAt(0) === ".") continue;
|
|
200
|
+
try {
|
|
201
|
+
var tool = getTool(ctx, entries[i].name);
|
|
202
|
+
if (tool) manifests.push(tool.manifest);
|
|
203
|
+
else manifests.push({ id: entries[i].name, error: "Capsule requires manifest.json and ui.json." });
|
|
204
|
+
} catch (e) {
|
|
205
|
+
manifests.push({ id: entries[i].name, error: e.message });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
manifests.sort(function (a, b) { return (a.name || a.id).localeCompare(b.name || b.id); });
|
|
209
|
+
return manifests;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function installTool(ctx, input) {
|
|
213
|
+
if (!input || typeof input !== "object") throw new Error("Tool installation payload is required.");
|
|
214
|
+
validateManifest(input.manifest, { allowServerRuntime: false });
|
|
215
|
+
if (typeof input.logicSource !== "string" || !input.logicSource.trim()) {
|
|
216
|
+
throw new Error("Tool logic source is required.");
|
|
217
|
+
}
|
|
218
|
+
validateUiTreeForManifest(input.uiTree, input.manifest);
|
|
219
|
+
var manifest = Object.assign({}, input.manifest, { name: input.manifest.name.trim(), runtime: "worker" });
|
|
220
|
+
var directory = toolDirectory(ctx, manifest.id);
|
|
221
|
+
if (fs.existsSync(directory)) throw new Error("A Capsule with this ID already exists. Read its source and use the update tool instead.");
|
|
222
|
+
fs.mkdirSync(resolveToolsRoot(ctx), { recursive: true });
|
|
223
|
+
fs.mkdirSync(directory);
|
|
224
|
+
try {
|
|
225
|
+
fs.writeFileSync(path.join(directory, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
226
|
+
fs.writeFileSync(path.join(directory, "logic.js"), input.logicSource, "utf8");
|
|
227
|
+
fs.writeFileSync(path.join(directory, "ui.json"), JSON.stringify(input.uiTree, null, 2) + "\n", "utf8");
|
|
228
|
+
} catch (error) {
|
|
229
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
return getTool(ctx, manifest.id);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getToolSource(ctx, id) {
|
|
236
|
+
var tool = getTool(ctx, id);
|
|
237
|
+
if (!tool) throw new Error("Tool not found.");
|
|
238
|
+
return toolCapsuleSource.readSource(toolDirectory(ctx, id));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function getToolMetadata(ctx, id) {
|
|
242
|
+
if (!getTool(ctx, id)) throw new Error("Tool not found.");
|
|
243
|
+
return toolCapsuleSource.sanitizedMetadata(resolveToolsRoot(ctx), id);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function setMateEditingAllowed(ctx, id, allowed) {
|
|
247
|
+
if (!getTool(ctx, id)) throw new Error("Tool not found.");
|
|
248
|
+
return toolCapsuleSource.setMateEditingAllowed(resolveToolsRoot(ctx), id, allowed);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function updateTool(ctx, id, input) {
|
|
252
|
+
var tool = getTool(ctx, id);
|
|
253
|
+
if (!tool) throw new Error("Tool not found.");
|
|
254
|
+
toolCapsuleSource.updateSource(toolDirectory(ctx, id), input, {
|
|
255
|
+
validateManifest: validateManifest,
|
|
256
|
+
validateUiNode: function (uiTree) { return validateUiTreeForManifest(uiTree, input.manifest); },
|
|
257
|
+
});
|
|
258
|
+
return getTool(ctx, id);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function removeTool(ctx, id) {
|
|
262
|
+
var directory = toolDirectory(ctx, id);
|
|
263
|
+
if (!fs.existsSync(directory)) return false;
|
|
264
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
265
|
+
toolCapsuleSource.removeMetadata(resolveToolsRoot(ctx), id);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
module.exports = {
|
|
270
|
+
CAPSULES_ROOT: CAPSULES_ROOT,
|
|
271
|
+
UI_NODE_TYPES: UI_NODE_TYPES,
|
|
272
|
+
resolveToolsRoot: resolveToolsRoot,
|
|
273
|
+
validateToolId: validateToolId,
|
|
274
|
+
validateManifest: validateManifest,
|
|
275
|
+
validateUiNode: validateUiNode,
|
|
276
|
+
validateUiTreeForManifest: validateUiTreeForManifest,
|
|
277
|
+
seedBuiltInCapsules: seedBuiltInCapsules,
|
|
278
|
+
listTools: listTools,
|
|
279
|
+
getTool: getTool,
|
|
280
|
+
installTool: installTool,
|
|
281
|
+
getToolSource: getToolSource,
|
|
282
|
+
getToolMetadata: getToolMetadata,
|
|
283
|
+
setMateEditingAllowed: setMateEditingAllowed,
|
|
284
|
+
updateTool: updateTool,
|
|
285
|
+
removeTool: removeTool,
|
|
286
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Per-user persistence for the home tool dock.
|
|
2
|
+
|
|
3
|
+
function attachHomeDockPreferences(deps) {
|
|
4
|
+
var loadUsers = deps.loadUsers;
|
|
5
|
+
var saveUsers = deps.saveUsers;
|
|
6
|
+
|
|
7
|
+
function defaults() {
|
|
8
|
+
return { dockOpen: false, dockFocus: false, dockWidth: null, activeToolId: null };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalize(value) {
|
|
12
|
+
var source = value && typeof value === "object" ? value : {};
|
|
13
|
+
var width = typeof source.dockWidth === "number" && Number.isFinite(source.dockWidth)
|
|
14
|
+
? Math.max(420, Math.min(1600, Math.round(source.dockWidth)))
|
|
15
|
+
: null;
|
|
16
|
+
var toolId = typeof source.activeToolId === "string" && /^[a-z0-9][a-z0-9_-]{0,63}$/.test(source.activeToolId)
|
|
17
|
+
? source.activeToolId
|
|
18
|
+
: null;
|
|
19
|
+
return {
|
|
20
|
+
dockOpen: source.dockOpen === true,
|
|
21
|
+
dockFocus: source.dockOpen === true && source.dockFocus === true,
|
|
22
|
+
dockWidth: width,
|
|
23
|
+
activeToolId: toolId,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function loadSingleUserPreference() {
|
|
28
|
+
try {
|
|
29
|
+
var config = require("./config");
|
|
30
|
+
var current = config.loadConfig() || {};
|
|
31
|
+
return normalize(current.homeDockPreference);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return defaults();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function saveSingleUserPreference(preference) {
|
|
38
|
+
try {
|
|
39
|
+
var config = require("./config");
|
|
40
|
+
var current = config.loadConfig() || {};
|
|
41
|
+
config.saveConfig(Object.assign({}, current, { homeDockPreference: preference }));
|
|
42
|
+
return { ok: true, preference: preference };
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return { error: "Unable to save home dock preference" };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getHomeDockPreference(userId) {
|
|
49
|
+
var data = loadUsers();
|
|
50
|
+
for (var i = 0; i < data.users.length; i++) {
|
|
51
|
+
if (data.users[i].id === userId) return normalize(data.users[i].homeDockPreference);
|
|
52
|
+
}
|
|
53
|
+
if (userId === "default") return loadSingleUserPreference();
|
|
54
|
+
return defaults();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function setHomeDockPreference(userId, patch) {
|
|
58
|
+
var current = getHomeDockPreference(userId);
|
|
59
|
+
var next = normalize(Object.assign({}, current, patch || {}));
|
|
60
|
+
var data = loadUsers();
|
|
61
|
+
for (var i = 0; i < data.users.length; i++) {
|
|
62
|
+
if (data.users[i].id !== userId) continue;
|
|
63
|
+
data.users[i].homeDockPreference = next;
|
|
64
|
+
saveUsers(data);
|
|
65
|
+
return { ok: true, preference: next };
|
|
66
|
+
}
|
|
67
|
+
if (userId === "default") return saveSingleUserPreference(next);
|
|
68
|
+
return { error: "User not found" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
getHomeDockPreference: getHomeDockPreference,
|
|
73
|
+
setHomeDockPreference: setHomeDockPreference,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { attachHomeDockPreferences: attachHomeDockPreferences };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Per-user persistence for durable Home conversation selections.
|
|
2
|
+
|
|
3
|
+
function attachHomeSurfacePreferences(deps) {
|
|
4
|
+
var loadUsers = deps.loadUsers;
|
|
5
|
+
var saveUsers = deps.saveUsers;
|
|
6
|
+
|
|
7
|
+
function defaults() {
|
|
8
|
+
return { surface: null, projectSlug: null, activeMateId: null, activeSessionByMate: {}, sidebarCollapsed: false, chatScope: "all", subSurface: "chat" };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function validId(value) {
|
|
12
|
+
return typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function validSessionReference(value) {
|
|
16
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256 && !/[\x00-\x1f]/.test(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function validProjectSlug(value) {
|
|
20
|
+
return typeof value === "string" && /^[a-z0-9][a-z0-9_-]{0,127}$/.test(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalize(value) {
|
|
24
|
+
var source = value && typeof value === "object" ? value : {};
|
|
25
|
+
var sessions = source.activeSessionByMate && typeof source.activeSessionByMate === "object" && !Array.isArray(source.activeSessionByMate)
|
|
26
|
+
? source.activeSessionByMate
|
|
27
|
+
: {};
|
|
28
|
+
var normalizedSessions = {};
|
|
29
|
+
var mateIds = Object.keys(sessions);
|
|
30
|
+
for (var i = 0; i < mateIds.length; i++) {
|
|
31
|
+
if (!validId(mateIds[i]) || !validSessionReference(sessions[mateIds[i]])) continue;
|
|
32
|
+
normalizedSessions[mateIds[i]] = sessions[mateIds[i]];
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
surface: source.surface === "home" || source.surface === "project" ? source.surface : null,
|
|
36
|
+
projectSlug: validProjectSlug(source.projectSlug) ? source.projectSlug : null,
|
|
37
|
+
activeMateId: validId(source.activeMateId) ? source.activeMateId : null,
|
|
38
|
+
activeSessionByMate: normalizedSessions,
|
|
39
|
+
sidebarCollapsed: source.sidebarCollapsed === true,
|
|
40
|
+
chatScope: source.chatScope === "current" ? "current" : "all",
|
|
41
|
+
subSurface: source.subSurface === "debates" ? "debates" : "chat",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function loadSingleUserPreference() {
|
|
46
|
+
try {
|
|
47
|
+
var config = require("./config");
|
|
48
|
+
var current = config.loadConfig() || {};
|
|
49
|
+
return normalize(current.homeSurfacePreference);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return defaults();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function saveSingleUserPreference(preference) {
|
|
56
|
+
try {
|
|
57
|
+
var config = require("./config");
|
|
58
|
+
var current = config.loadConfig() || {};
|
|
59
|
+
config.saveConfig(Object.assign({}, current, { homeSurfacePreference: preference }));
|
|
60
|
+
return { ok: true, preference: preference };
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return { error: "Unable to save Home surface preference" };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getHomeSurfacePreference(userId) {
|
|
67
|
+
var data = loadUsers();
|
|
68
|
+
for (var i = 0; i < data.users.length; i++) {
|
|
69
|
+
if (data.users[i].id === userId) return normalize(data.users[i].homeSurfacePreference);
|
|
70
|
+
}
|
|
71
|
+
if (userId === "default") return loadSingleUserPreference();
|
|
72
|
+
return defaults();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function setHomeSurfacePreference(userId, patch) {
|
|
76
|
+
var current = getHomeSurfacePreference(userId);
|
|
77
|
+
var merged = Object.assign({}, current, patch || {});
|
|
78
|
+
if (patch && patch.activeSessionByMate && typeof patch.activeSessionByMate === "object") {
|
|
79
|
+
merged.activeSessionByMate = Object.assign({}, current.activeSessionByMate, patch.activeSessionByMate);
|
|
80
|
+
}
|
|
81
|
+
var next = normalize(merged);
|
|
82
|
+
var data = loadUsers();
|
|
83
|
+
for (var i = 0; i < data.users.length; i++) {
|
|
84
|
+
if (data.users[i].id !== userId) continue;
|
|
85
|
+
data.users[i].homeSurfacePreference = next;
|
|
86
|
+
saveUsers(data);
|
|
87
|
+
return { ok: true, preference: next };
|
|
88
|
+
}
|
|
89
|
+
if (userId === "default") return saveSingleUserPreference(next);
|
|
90
|
+
return { error: "User not found" };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
getHomeSurfacePreference: getHomeSurfacePreference,
|
|
95
|
+
setHomeSurfacePreference: setHomeSurfacePreference,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { attachHomeSurfacePreferences: attachHomeSurfacePreferences };
|
package/lib/users-preferences.js
CHANGED
|
@@ -334,14 +334,11 @@ function attachPreferences(deps) {
|
|
|
334
334
|
return { error: "User not found" };
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
// --- Per-user
|
|
337
|
+
// --- Per-user project Mate DM shortcut toggle ---
|
|
338
338
|
//
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
// opt-in, not opt-out. Stored as `matesEnabled`; only the literal `true`
|
|
343
|
-
// enables it, so brand-new users (no field set) get the default-off
|
|
344
|
-
// experience and must turn Mates on explicitly in settings.
|
|
339
|
+
// `matesEnabled` is retained as the persisted field for compatibility.
|
|
340
|
+
// It now controls only project-local Mate DM shortcuts. The broader Mate
|
|
341
|
+
// capability surface is always available. Default remains off.
|
|
345
342
|
|
|
346
343
|
function getMatesEnabled(userId) {
|
|
347
344
|
var data = loadUsers();
|
package/lib/users.js
CHANGED
|
@@ -6,6 +6,8 @@ var { CONFIG_DIR } = require("./config");
|
|
|
6
6
|
var { attachAuth } = require("./users-auth");
|
|
7
7
|
var { DEFAULT_PERMISSIONS, ALL_PERMISSIONS, attachPermissions } = require("./users-permissions");
|
|
8
8
|
var { attachPreferences } = require("./users-preferences");
|
|
9
|
+
var { attachHomeDockPreferences } = require("./users-home-dock-preferences");
|
|
10
|
+
var { attachHomeSurfacePreferences } = require("./users-home-surface-preferences");
|
|
9
11
|
|
|
10
12
|
var USERS_FILE = path.join(CONFIG_DIR, "users.json");
|
|
11
13
|
|
|
@@ -394,6 +396,8 @@ function removeExpiredInvites() {
|
|
|
394
396
|
var auth = attachAuth({ loadUsers: loadUsers, saveUsers: saveUsers, findAdmin: findAdmin });
|
|
395
397
|
var permissions = attachPermissions({ loadUsers: loadUsers, saveUsers: saveUsers, findUserById: findUserById });
|
|
396
398
|
var preferences = attachPreferences({ loadUsers: loadUsers, saveUsers: saveUsers });
|
|
399
|
+
var homeDockPreferences = attachHomeDockPreferences({ loadUsers: loadUsers, saveUsers: saveUsers });
|
|
400
|
+
var homeSurfacePreferences = attachHomeSurfacePreferences({ loadUsers: loadUsers, saveUsers: saveUsers });
|
|
397
401
|
|
|
398
402
|
// Alias auth functions
|
|
399
403
|
var isMultiUser = auth.isMultiUser;
|
|
@@ -442,6 +446,10 @@ var markWhatsNewSeen = preferences.markWhatsNewSeen;
|
|
|
442
446
|
var getTerminalFont = preferences.getTerminalFont;
|
|
443
447
|
var setTerminalFont = preferences.setTerminalFont;
|
|
444
448
|
var setMateOnboarded = preferences.setMateOnboarded;
|
|
449
|
+
var getHomeDockPreference = homeDockPreferences.getHomeDockPreference;
|
|
450
|
+
var setHomeDockPreference = homeDockPreferences.setHomeDockPreference;
|
|
451
|
+
var getHomeSurfacePreference = homeSurfacePreferences.getHomeSurfacePreference;
|
|
452
|
+
var setHomeSurfacePreference = homeSurfacePreferences.setHomeSurfacePreference;
|
|
445
453
|
|
|
446
454
|
module.exports = {
|
|
447
455
|
USERS_FILE: USERS_FILE,
|
|
@@ -511,6 +519,10 @@ module.exports = {
|
|
|
511
519
|
markWhatsNewSeen: markWhatsNewSeen,
|
|
512
520
|
getTerminalFont: getTerminalFont,
|
|
513
521
|
setTerminalFont: setTerminalFont,
|
|
522
|
+
getHomeDockPreference: getHomeDockPreference,
|
|
523
|
+
setHomeDockPreference: setHomeDockPreference,
|
|
524
|
+
getHomeSurfacePreference: getHomeSurfacePreference,
|
|
525
|
+
setHomeSurfacePreference: setHomeSurfacePreference,
|
|
514
526
|
getDeletedBuiltinKeys: getDeletedBuiltinKeys,
|
|
515
527
|
addDeletedBuiltinKey: addDeletedBuiltinKey,
|
|
516
528
|
removeDeletedBuiltinKey: removeDeletedBuiltinKey,
|
package/lib/whats-new-content.js
CHANGED
|
@@ -70,13 +70,13 @@ var ENTRIES = [
|
|
|
70
70
|
title: "GUI is back as the default",
|
|
71
71
|
publishedAt: "2026-07-01",
|
|
72
72
|
image: null,
|
|
73
|
-
summary: "Anthropic reversed the June 15 billing split, so SDK usage is no longer billed separately. Clay opens Claude sessions in the GUI chat again by default.
|
|
73
|
+
summary: "Anthropic reversed the June 15 billing split, so SDK usage is no longer billed separately. Clay opens Claude sessions in the GUI chat again by default. Project Mate DM shortcuts are hidden by default.",
|
|
74
74
|
body: '' +
|
|
75
75
|
'<p><strong>Good news: Anthropic walked back the June 15 billing change.</strong> The planned Interactive vs. Programmatic split (which would have charged Claude Agent SDK usage at full API rates) is off the table, so the reason we moved everyone to the TUI is gone.</p>' +
|
|
76
76
|
'<h3>GUI is the default again</h3>' +
|
|
77
77
|
'<p>New Claude sessions open in the <strong>GUI chat</strong> (SDK) by default, the way they did before. The embedded <code>claude</code> <strong>TUI</strong> is still there whenever you want it — toggle <strong>"Open Claude as terminal (TUI)"</strong> in Settings.</p>' +
|
|
78
|
-
'<h3>
|
|
79
|
-
'<p>
|
|
78
|
+
'<h3>Project Mate DM shortcuts are optional</h3>' +
|
|
79
|
+
'<p>Mate DM shortcuts inside project workspaces are hidden by default and can be shown in <strong>Settings</strong>. Mates themselves remain available in Clay Home, together with their conversations, memory, debates, and project collaboration.</p>' +
|
|
80
80
|
'<p>Clay only gets sharper from here.</p>',
|
|
81
81
|
},
|
|
82
82
|
];
|