clay-server 4.0.0-beta.13 → 4.0.0-beta.15
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/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +229 -0
- package/lib/capsule-pig-logic.js +268 -0
- package/lib/capsule-server-runtimes.js +41 -0
- package/lib/capsule-tictactoe-logic.js +260 -0
- package/lib/capsules/pig/display.js +190 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +46 -0
- package/lib/capsules/tictactoe/manifest.json +9 -0
- package/lib/capsules/tictactoe/ui.json +203 -0
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-connection.js +1 -1
- package/lib/project-log-feedback-delivery.js +76 -0
- package/lib/project-logs.js +4 -0
- package/lib/project-pair-lifecycle.js +7 -12
- package/lib/project-session-pair.js +29 -39
- package/lib/project-worker-proposal.js +203 -72
- package/lib/project.js +13 -0
- package/lib/public/css/capsule-ui.css +18 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/sidebar.css +92 -0
- package/lib/public/css/worker-proposal.css +34 -1
- package/lib/public/modules/app-messages.js +14 -1
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- 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-tool-frame.js +165 -0
- package/lib/public/modules/home-tools.js +129 -1
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +35 -6
- package/lib/public/modules/sidebar-session-hierarchy.js +206 -0
- package/lib/public/modules/sidebar-sessions.js +49 -7
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -12
- package/lib/sdk-bridge.js +16 -8
- package/lib/sdk-message-processor.js +34 -4
- package/lib/server-home-chat.js +12 -1
- package/lib/server-tools.js +97 -9
- package/lib/server.js +3 -0
- package/lib/session-driver-eligibility.js +20 -165
- package/lib/session-pair-factory.js +31 -28
- package/lib/session-pair-mcp-server.js +5 -9
- package/lib/session-pair-prompts.js +15 -15
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/sessions.js +40 -2
- package/lib/tools-registry.js +44 -10
- package/lib/ws-schema.js +8 -3
- package/lib/yoke/adapters/claude.js +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
var crypto = require("crypto");
|
|
2
|
+
|
|
3
|
+
var PROVENANCE_VERSION = 1;
|
|
4
|
+
var MAX_ORIGIN_LENGTH = 128;
|
|
5
|
+
|
|
6
|
+
function safeOriginId(value) {
|
|
7
|
+
if (typeof value !== "string") return null;
|
|
8
|
+
var trimmed = value.trim();
|
|
9
|
+
if (!trimmed || trimmed.length > MAX_ORIGIN_LENGTH) return null;
|
|
10
|
+
return trimmed;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ensureOrigin(session) {
|
|
14
|
+
if (!session) return null;
|
|
15
|
+
var existing = safeOriginId(session.sessionOriginId);
|
|
16
|
+
if (existing) {
|
|
17
|
+
session.sessionOriginId = existing;
|
|
18
|
+
return existing;
|
|
19
|
+
}
|
|
20
|
+
session.sessionOriginId = crypto.randomUUID();
|
|
21
|
+
return session.sessionOriginId;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isWorker(session) {
|
|
25
|
+
return !!(session && session.sessionProvenance && session.sessionProvenance.kind === "worker");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function nextWorkerGeneration(parentOriginId, sessions) {
|
|
29
|
+
var next = 1;
|
|
30
|
+
sessions.forEach(function (session) {
|
|
31
|
+
var provenance = session && session.sessionProvenance;
|
|
32
|
+
if (!provenance || provenance.kind !== "worker" || provenance.parentSessionOriginId !== parentOriginId) return;
|
|
33
|
+
var generation = Number.isInteger(provenance.generation) && provenance.generation > 0 ? provenance.generation : 0;
|
|
34
|
+
if (generation >= next) next = generation + 1;
|
|
35
|
+
});
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function markWorker(driver, worker, sessions) {
|
|
40
|
+
if (!driver || !worker || driver === worker) throw new Error("Split Worker provenance requires distinct sessions");
|
|
41
|
+
if ((driver.ownerId || null) !== (worker.ownerId || null)) throw new Error("Split Worker provenance requires the same owner");
|
|
42
|
+
if (isWorker(driver)) throw new Error("A Split Worker cannot become a Driver");
|
|
43
|
+
var parentOriginId = ensureOrigin(driver);
|
|
44
|
+
ensureOrigin(worker);
|
|
45
|
+
worker.sessionProvenance = {
|
|
46
|
+
version: PROVENANCE_VERSION,
|
|
47
|
+
kind: "worker",
|
|
48
|
+
parentSessionOriginId: parentOriginId,
|
|
49
|
+
generation: nextWorkerGeneration(parentOriginId, sessions),
|
|
50
|
+
createdVia: "split-worker",
|
|
51
|
+
createdAt: Date.now(),
|
|
52
|
+
};
|
|
53
|
+
return worker.sessionProvenance;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function restore(meta, session) {
|
|
57
|
+
var storedOriginId = safeOriginId(meta && meta.sessionOriginId);
|
|
58
|
+
ensureOrigin(session);
|
|
59
|
+
var stored = meta && meta.sessionProvenance;
|
|
60
|
+
if (!stored || stored.kind !== "worker") return storedOriginId !== session.sessionOriginId;
|
|
61
|
+
var parentOriginId = safeOriginId(stored.parentSessionOriginId);
|
|
62
|
+
// A corrupt Worker record remains a Worker. It is projected as orphaned
|
|
63
|
+
// instead of silently gaining top-level Driver authority.
|
|
64
|
+
session.sessionProvenance = {
|
|
65
|
+
version: PROVENANCE_VERSION,
|
|
66
|
+
kind: "worker",
|
|
67
|
+
parentSessionOriginId: parentOriginId,
|
|
68
|
+
generation: Number.isInteger(stored.generation) && stored.generation > 0 ? stored.generation : null,
|
|
69
|
+
createdVia: stored.createdVia === "split-worker" ? "split-worker" : null,
|
|
70
|
+
createdAt: typeof stored.createdAt === "number" && isFinite(stored.createdAt) ? stored.createdAt : null,
|
|
71
|
+
};
|
|
72
|
+
return storedOriginId !== session.sessionOriginId || JSON.stringify(stored) !== JSON.stringify(session.sessionProvenance);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function metadata(session) {
|
|
76
|
+
var result = { sessionOriginId: ensureOrigin(session) };
|
|
77
|
+
if (isWorker(session)) result.sessionProvenance = Object.assign({}, session.sessionProvenance);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function hierarchyFor(sessions) {
|
|
82
|
+
var originMap = {};
|
|
83
|
+
var duplicateOrigins = {};
|
|
84
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
85
|
+
var originId = safeOriginId(sessions[i] && sessions[i].sessionOriginId);
|
|
86
|
+
if (!originId) continue;
|
|
87
|
+
if (originMap[originId]) duplicateOrigins[originId] = true;
|
|
88
|
+
else originMap[originId] = sessions[i];
|
|
89
|
+
}
|
|
90
|
+
var result = {};
|
|
91
|
+
for (var j = 0; j < sessions.length; j++) {
|
|
92
|
+
var session = sessions[j];
|
|
93
|
+
if (!isWorker(session)) {
|
|
94
|
+
result[session.localId] = { role: "driver", parentSessionId: null, parentAvailable: false, generation: null };
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
var provenance = session.sessionProvenance;
|
|
98
|
+
var parentOriginId = safeOriginId(provenance.parentSessionOriginId);
|
|
99
|
+
var parent = duplicateOrigins[parentOriginId] ? null : originMap[parentOriginId];
|
|
100
|
+
var sameOwner = parent && (parent.ownerId || null) === (session.ownerId || null);
|
|
101
|
+
var validParent = sameOwner && !isWorker(parent) ? parent : null;
|
|
102
|
+
result[session.localId] = {
|
|
103
|
+
role: "worker",
|
|
104
|
+
parentSessionId: validParent ? validParent.localId : null,
|
|
105
|
+
parentAvailable: !!validParent,
|
|
106
|
+
generation: Number.isInteger(provenance.generation) && provenance.generation > 0 ? provenance.generation : null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
ensureOrigin: ensureOrigin,
|
|
114
|
+
hierarchyFor: hierarchyFor,
|
|
115
|
+
isWorker: isWorker,
|
|
116
|
+
markWorker: markWorker,
|
|
117
|
+
metadata: metadata,
|
|
118
|
+
restore: restore,
|
|
119
|
+
};
|
|
@@ -27,7 +27,7 @@ function getToolDefs(handlers) {
|
|
|
27
27
|
return [
|
|
28
28
|
{
|
|
29
29
|
name: "spawn_sessions",
|
|
30
|
-
description: "Create sibling work sessions
|
|
30
|
+
description: "Create ordinary sibling work sessions for background parallel work. This does not create a visible Driver/Split Worker pair and must not be used when the user asks for a Worker, paired pane, or split collaborator; use send_to_partner for that. Set forkFromCurrent to copy this session's full conversation context into every child. Children share the project's working directory, so prefer parallel analysis or other non-conflicting work over concurrent edits. Results can be polled with check_spawned_sessions; spawned children cannot spawn further sessions.",
|
|
31
31
|
inputSchema: buildShape({
|
|
32
32
|
sessions: { type: "string", description: "JSON array of objects: [{\"title\":\"Task title\",\"prompt\":\"Task instructions\"}]" },
|
|
33
33
|
vendor: { type: "string", description: "Optional vendor id. Defaults to the parent session vendor, then the project default." },
|
package/lib/sessions.js
CHANGED
|
@@ -6,6 +6,7 @@ var users = require("./users");
|
|
|
6
6
|
var { CODEX_DEFAULTS } = require("./codex-defaults");
|
|
7
7
|
var hygiene = require("./session-hygiene");
|
|
8
8
|
var backgroundTaskTiming = require("./background-task-timing");
|
|
9
|
+
var sessionProvenance = require("./session-provenance");
|
|
9
10
|
|
|
10
11
|
function createSessionManager(opts) {
|
|
11
12
|
var cwd = opts.cwd;
|
|
@@ -180,6 +181,9 @@ function createSessionManager(opts) {
|
|
|
180
181
|
if (session.homeDebateInitialTopic) metaObj.homeDebateInitialTopic = session.homeDebateInitialTopic;
|
|
181
182
|
if (session.homeClayEntryRequestId) metaObj.homeClayEntryRequestId = session.homeClayEntryRequestId;
|
|
182
183
|
if (session.homeClayEntryMode) metaObj.homeClayEntryMode = session.homeClayEntryMode;
|
|
184
|
+
var provenanceMeta = sessionProvenance.metadata(session);
|
|
185
|
+
metaObj.sessionOriginId = provenanceMeta.sessionOriginId;
|
|
186
|
+
if (provenanceMeta.sessionProvenance) metaObj.sessionProvenance = provenanceMeta.sessionProvenance;
|
|
183
187
|
var meta = JSON.stringify(metaObj);
|
|
184
188
|
var lines = [meta];
|
|
185
189
|
for (var i = 0; i < session.history.length; i++) {
|
|
@@ -300,6 +304,8 @@ function createSessionManager(opts) {
|
|
|
300
304
|
if (m.homeClayEntryRequestId) session.homeClayEntryRequestId = m.homeClayEntryRequestId;
|
|
301
305
|
if (m.homeClayEntryMode) session.homeClayEntryMode = m.homeClayEntryMode;
|
|
302
306
|
if (m.ownerId) session.ownerId = m.ownerId;
|
|
307
|
+
session.sessionOriginId = m.sessionOriginId || null;
|
|
308
|
+
session._provenanceMigrationNeeded = sessionProvenance.restore(m, session);
|
|
303
309
|
// Born-TUI session: PTY is gone after restart, but the cliSessionId
|
|
304
310
|
// is still resumable via `claude --resume <id>`. We mark the mode
|
|
305
311
|
// here so it shows up in the sidebar with the right icon; the
|
|
@@ -320,6 +326,20 @@ function createSessionManager(opts) {
|
|
|
320
326
|
session.favoriteOrder = typeof m.favoriteOrder === "number" ? m.favoriteOrder : null;
|
|
321
327
|
sessions.set(localId, session);
|
|
322
328
|
}
|
|
329
|
+
|
|
330
|
+
// Versionless legacy records are safe top-level Drivers. Assigning an
|
|
331
|
+
// opaque origin ID is the only migration: no title, slug or active split
|
|
332
|
+
// state is used to invent historical parentage.
|
|
333
|
+
sessions.forEach(function (session) {
|
|
334
|
+
if (!session.cliSessionId || !session._provenanceMigrationNeeded) return;
|
|
335
|
+
var activityTime = session.lastActivity;
|
|
336
|
+
saveSessionFile(session);
|
|
337
|
+
try {
|
|
338
|
+
var preservedTime = new Date(activityTime);
|
|
339
|
+
fs.utimesSync(sessionFilePath(session.cliSessionId), preservedTime, preservedTime);
|
|
340
|
+
} catch (e) {}
|
|
341
|
+
delete session._provenanceMigrationNeeded;
|
|
342
|
+
});
|
|
323
343
|
}
|
|
324
344
|
|
|
325
345
|
// Adopt orphaned CLI sessions from ~/.claude/projects/<encoded-cwd>/ as
|
|
@@ -532,6 +552,19 @@ function createSessionManager(opts) {
|
|
|
532
552
|
};
|
|
533
553
|
}
|
|
534
554
|
|
|
555
|
+
function mapSessionsForClient(source, clientActiveId, wsUnread) {
|
|
556
|
+
var hierarchy = sessionProvenance.hierarchyFor(source);
|
|
557
|
+
return source.map(function (session) {
|
|
558
|
+
var mapped = mapSessionForClient(session, clientActiveId, wsUnread);
|
|
559
|
+
var node = hierarchy[session.localId];
|
|
560
|
+
mapped.sessionRole = node.role;
|
|
561
|
+
mapped.parentSessionId = node.parentSessionId;
|
|
562
|
+
mapped.parentAvailable = node.parentAvailable;
|
|
563
|
+
mapped.workerGeneration = node.generation;
|
|
564
|
+
return mapped;
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
535
568
|
function getVisibleSessions() {
|
|
536
569
|
var multiUser = users.isMultiUser();
|
|
537
570
|
return [...sessions.values()].filter(function (s) {
|
|
@@ -554,14 +587,14 @@ function createSessionManager(opts) {
|
|
|
554
587
|
if (ws.readyState === 1) {
|
|
555
588
|
ws.send(JSON.stringify({
|
|
556
589
|
type: "session_list",
|
|
557
|
-
sessions: filtered
|
|
590
|
+
sessions: mapSessionsForClient(filtered, clientActiveId, wsUnread),
|
|
558
591
|
}));
|
|
559
592
|
}
|
|
560
593
|
});
|
|
561
594
|
} else {
|
|
562
595
|
send({
|
|
563
596
|
type: "session_list",
|
|
564
|
-
sessions: allVisible
|
|
597
|
+
sessions: mapSessionsForClient(allVisible),
|
|
565
598
|
});
|
|
566
599
|
}
|
|
567
600
|
}
|
|
@@ -598,7 +631,9 @@ function createSessionManager(opts) {
|
|
|
598
631
|
permissionMode: (sessionOpts && sessionOpts.permissionMode) || null,
|
|
599
632
|
permissionModeBeforeFullAccess: null,
|
|
600
633
|
terminalId: null,
|
|
634
|
+
sessionOriginId: null,
|
|
601
635
|
};
|
|
636
|
+
sessionProvenance.ensureOrigin(session);
|
|
602
637
|
sessions.set(localId, session);
|
|
603
638
|
switchSession(localId, targetWs);
|
|
604
639
|
return session;
|
|
@@ -637,7 +672,9 @@ function createSessionManager(opts) {
|
|
|
637
672
|
permissionMode: (sessionOpts && sessionOpts.permissionMode) || null,
|
|
638
673
|
permissionModeBeforeFullAccess: null,
|
|
639
674
|
terminalId: null,
|
|
675
|
+
sessionOriginId: null,
|
|
640
676
|
};
|
|
677
|
+
sessionProvenance.ensureOrigin(session);
|
|
641
678
|
sessions.set(localId, session);
|
|
642
679
|
return session;
|
|
643
680
|
}
|
|
@@ -1248,6 +1285,7 @@ function createSessionManager(opts) {
|
|
|
1248
1285
|
searchSessionContent: searchSessionContent,
|
|
1249
1286
|
setResolveLoopInfo: setResolveLoopInfo,
|
|
1250
1287
|
mapSessionForClient: mapSessionForClient,
|
|
1288
|
+
mapSessionsForClient: mapSessionsForClient,
|
|
1251
1289
|
migrateSessionTitles: migrateSessionTitles,
|
|
1252
1290
|
setSessionVisibility: function (localId, visibility) {
|
|
1253
1291
|
var session = sessions.get(localId);
|
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-v9";
|
|
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,19 +135,31 @@ 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 });
|
|
@@ -185,6 +203,11 @@ function getTool(ctx, id) {
|
|
|
185
203
|
manifest: Object.assign({}, manifest, { runtime: runtime }),
|
|
186
204
|
logicSource: runtime === "worker" ? fs.readFileSync(logicPath, "utf8") : null,
|
|
187
205
|
uiTree: uiTree,
|
|
206
|
+
// Rich Display is opted into by shipping a display.js next to the floor,
|
|
207
|
+
// the same way logic.js opts into the worker runtime. Only the flag
|
|
208
|
+
// travels; the source is served solely by the isolated frame origin, so
|
|
209
|
+
// it can never leak into a manifest, a prompt, or a Mate surface.
|
|
210
|
+
hasRichDisplay: fs.existsSync(path.join(directory, "display.js")),
|
|
188
211
|
metadata: toolCapsuleSource.sanitizedMetadata(resolveToolsRoot(ctx), id),
|
|
189
212
|
};
|
|
190
213
|
}
|
|
@@ -199,8 +222,14 @@ function listTools(ctx) {
|
|
|
199
222
|
if (entries[i].name.charAt(0) === ".") continue;
|
|
200
223
|
try {
|
|
201
224
|
var tool = getTool(ctx, entries[i].name);
|
|
202
|
-
if (tool)
|
|
203
|
-
|
|
225
|
+
if (tool) {
|
|
226
|
+
// The flat manifest carries its own Display alongside it, so a
|
|
227
|
+
// Mate-facing gate can validate the real tree without a second read
|
|
228
|
+
// and without trusting a claim. Non-enumerable, so the tree never
|
|
229
|
+
// leaks into a projection, a prompt, or a serialized message.
|
|
230
|
+
Object.defineProperty(tool.manifest, "uiTree", { value: tool.uiTree, enumerable: false, configurable: true });
|
|
231
|
+
manifests.push(tool.manifest);
|
|
232
|
+
} else manifests.push({ id: entries[i].name, error: "Capsule requires manifest.json and ui.json." });
|
|
204
233
|
} catch (e) {
|
|
205
234
|
manifests.push({ id: entries[i].name, error: e.message });
|
|
206
235
|
}
|
|
@@ -216,6 +245,7 @@ function installTool(ctx, input) {
|
|
|
216
245
|
throw new Error("Tool logic source is required.");
|
|
217
246
|
}
|
|
218
247
|
validateUiTreeForManifest(input.uiTree, input.manifest);
|
|
248
|
+
capsuleFloor.assertFloor(input.uiTree, input.manifest);
|
|
219
249
|
var manifest = Object.assign({}, input.manifest, { name: input.manifest.name.trim(), runtime: "worker" });
|
|
220
250
|
var directory = toolDirectory(ctx, manifest.id);
|
|
221
251
|
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 +283,10 @@ function updateTool(ctx, id, input) {
|
|
|
253
283
|
if (!tool) throw new Error("Tool not found.");
|
|
254
284
|
toolCapsuleSource.updateSource(toolDirectory(ctx, id), input, {
|
|
255
285
|
validateManifest: validateManifest,
|
|
256
|
-
validateUiNode: function (uiTree) {
|
|
286
|
+
validateUiNode: function (uiTree) {
|
|
287
|
+
validateUiTreeForManifest(uiTree, input.manifest);
|
|
288
|
+
return capsuleFloor.assertFloor(uiTree, input.manifest);
|
|
289
|
+
},
|
|
257
290
|
});
|
|
258
291
|
return getTool(ctx, id);
|
|
259
292
|
}
|
|
@@ -268,6 +301,7 @@ function removeTool(ctx, id) {
|
|
|
268
301
|
|
|
269
302
|
module.exports = {
|
|
270
303
|
CAPSULES_ROOT: CAPSULES_ROOT,
|
|
304
|
+
SEED_MARKER: SEED_MARKER,
|
|
271
305
|
UI_NODE_TYPES: UI_NODE_TYPES,
|
|
272
306
|
resolveToolsRoot: resolveToolsRoot,
|
|
273
307
|
validateToolId: validateToolId,
|
package/lib/ws-schema.js
CHANGED
|
@@ -40,7 +40,7 @@ var schema = {
|
|
|
40
40
|
"split_group_set_pair": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Assign Driver/Worker roles on a split group (driverId null clears them)" },
|
|
41
41
|
"pair_session_options": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Request vendors/models for the pair-session dialog (response reuses the same type)" },
|
|
42
42
|
"pair_session_create": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Create a Driver/Split Worker session pair as a split group" },
|
|
43
|
-
"worker_proposal_response": { direction: "c2s", handler: "lib/project-worker-proposal.js", description: "Accept or decline
|
|
43
|
+
"worker_proposal_response": { direction: "c2s", handler: "lib/project-worker-proposal.js", description: "Accept or decline an exact Driver Split Worker configuration with the selected runtime" },
|
|
44
44
|
"project_assignment_response": { direction: "c2s", handler: "lib/workspace-assignment-service.js", description: "Approve or cancel an exact-session cross-project assignment proposal" },
|
|
45
45
|
"home_clay_session_resolve": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Resolve an opaque Mate session reference through the exact bound source session" },
|
|
46
46
|
"home_clay_session_target": { direction: "s2c", handler: "lib/public/modules/command-palette.js", description: "Return an owner-validated navigation target for a rendered Mate session reference" },
|
|
@@ -62,8 +62,8 @@ var schema = {
|
|
|
62
62
|
"split_groups": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full persistent split-group list for the current user" },
|
|
63
63
|
"split_group_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of a split-group mutation" },
|
|
64
64
|
"pair_session_created": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of pair_session_create; carries the new group" },
|
|
65
|
-
"worker_proposal": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "
|
|
66
|
-
"worker_proposal_update": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Worker
|
|
65
|
+
"worker_proposal": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Recorded Split Worker creation/replacement runtime card with Driver rationale" },
|
|
66
|
+
"worker_proposal_update": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Worker card lifecycle and manual/auto decision audit update" },
|
|
67
67
|
"project_assignment_proposal": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Approval card for a new owner-bound project assignment" },
|
|
68
68
|
"project_assignment_status": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Durable project assignment lifecycle update" },
|
|
69
69
|
"project_assignment_error": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Rejected or stale project assignment response" },
|
|
@@ -489,6 +489,8 @@ var schema = {
|
|
|
489
489
|
"tool_llm_op": { direction: "c2s", handler: "lib/server-tools.js", description: "Run a permission-gated vendor-neutral capsule LLM completion" },
|
|
490
490
|
"tool_llm_config_get": { direction: "c2s", handler: "lib/server-tools.js", description: "Get the concrete configured provider and model used by Capsule LLM calls" },
|
|
491
491
|
"tool_control_response": { direction: "c2s", handler: "lib/server-tools.js", description: "Resolve a mate tool-control request with browser runtime state or an error" },
|
|
492
|
+
"tool_server_control": { direction: "c2s", handler: "lib/server-tools.js", description: "Human snapshot or action against a server-runtime Capsule, through the same Logic a Mate calls" },
|
|
493
|
+
"tool_frame_url": { direction: "c2s", handler: "lib/server-tools.js", description: "Mint a one-time separate-origin URL for a Capsule's sandboxed rich Display frame" },
|
|
492
494
|
"tools_state": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full installed tool definitions" },
|
|
493
495
|
"tool_installed": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "An installed tool changed" },
|
|
494
496
|
"tool_source_state": { direction: "s2c", handler: "lib/public/modules/home-capsule-source.js", description: "Correlated authored Capsule source or read error" },
|
|
@@ -498,6 +500,9 @@ var schema = {
|
|
|
498
500
|
"tool_llm_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of a capsule LLM completion" },
|
|
499
501
|
"tool_llm_config_state": { direction: "s2c", handler: "lib/public/modules/tool-llm-status.js", description: "Concrete provider and model state for Capsule LLM calls" },
|
|
500
502
|
"tool_control_request": { direction: "s2c", handler: "lib/public/modules/home-tools.js", description: "Ask the connected home client to snapshot or drive a sandboxed tool" },
|
|
503
|
+
"tool_server_state": { direction: "s2c", handler: "lib/public/modules/home-tools.js", description: "Correlated server-runtime Capsule state for the human who requested it, or the refusal that blocked it" },
|
|
504
|
+
"tool_server_event": { direction: "s2c", handler: "lib/public/modules/home-tools.js", description: "Ordered causal act event {seq, actor, action, previous, next} pushed to the user's Displays whenever server-runtime Capsule Logic changes state" },
|
|
505
|
+
"tool_frame_url_state": { direction: "s2c", handler: "lib/public/modules/home-tool-frame.js", description: "Correlated one-time rich Display frame URL, or the refusal that blocked it" },
|
|
501
506
|
"tools_error": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Tool registry, runtime, or storage error" },
|
|
502
507
|
|
|
503
508
|
// -----------------------------------------------------------------------
|
|
@@ -260,6 +260,18 @@ function flattenEvent(raw) {
|
|
|
260
260
|
base.status = raw.status;
|
|
261
261
|
return base;
|
|
262
262
|
}
|
|
263
|
+
// API retry notices are transient transport state, not conversation
|
|
264
|
+
// errors. Preserve the structured counters so the host can bound overload
|
|
265
|
+
// churn without recording one red error row per SDK retry.
|
|
266
|
+
if (raw.subtype === "api_retry") {
|
|
267
|
+
base.yokeType = "api_retry";
|
|
268
|
+
base.attempt = raw.attempt || 0;
|
|
269
|
+
base.maxRetries = raw.max_retries || 0;
|
|
270
|
+
base.retryDelayMs = raw.retry_delay_ms || 0;
|
|
271
|
+
base.error = raw.error || "unknown";
|
|
272
|
+
base.errorStatus = raw.error_status == null ? null : raw.error_status;
|
|
273
|
+
return base;
|
|
274
|
+
}
|
|
263
275
|
if (raw.subtype === "task_started") {
|
|
264
276
|
base.yokeType = "task_started";
|
|
265
277
|
base.parentToolId = raw.tool_use_id;
|
package/package.json
CHANGED