clay-server 4.0.0-beta.8 → 4.0.0
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 +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 +15 -1
- package/lib/project-file-history.js +223 -0
- package/lib/project-file-watch.js +77 -49
- package/lib/project-filesystem.js +31 -220
- 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-request-access.js +66 -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-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +185 -6
- package/lib/public/app.js +63 -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 +84 -3
- package/lib/public/css/messages.css +40 -1
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +97 -10
- 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 +36 -3
- package/lib/public/index.html +43 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +124 -6
- package/lib/public/modules/app-home-hub.js +12 -8
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +92 -11
- 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 +3 -2
- package/lib/public/modules/app-projects.js +56 -18
- package/lib/public/modules/app-rendering.js +1 -0
- 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-surface-boot.js +7 -3
- 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-activation.js +45 -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-pair-ui.js +15 -4
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/split-worker-runtime.js +24 -0
- 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/thinking-lifecycle.js +175 -0
- package/lib/public/modules/thinking-summary.js +28 -0
- package/lib/public/modules/thinking-view.js +75 -0
- 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 +31 -123
- 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 -20
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +135 -46
- package/lib/sdk-message-processor.js +36 -19
- 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 +137 -22
- 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 +41 -0
- package/lib/sessions.js +57 -10
- 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 +6 -6
- 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 +12 -0
- package/lib/yoke/adapters/codex.js +24 -14
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +2 -2
|
@@ -1,48 +1,34 @@
|
|
|
1
1
|
var crypto = require("crypto");
|
|
2
2
|
var yoke = require("./yoke");
|
|
3
3
|
var buildShape = require("./session-spawn-mcp-server").buildShape;
|
|
4
|
-
|
|
4
|
+
var driverEligibility = require("./session-driver-eligibility");
|
|
5
|
+
var driverOrchestration = require("./session-driver-orchestration");
|
|
5
6
|
var MAX_SUMMARY_CHARS = 600;
|
|
6
7
|
var MAX_PLAN_CHARS = 6000;
|
|
7
8
|
var MAX_TASK_CHARS = 30000;
|
|
8
|
-
|
|
9
|
+
var MAX_RATIONALE_CHARS = 1000;
|
|
9
10
|
function modelValue(entry) {
|
|
10
11
|
if (typeof entry === "string") return entry;
|
|
11
12
|
return entry && (entry.value || entry.id) || "";
|
|
12
13
|
}
|
|
13
|
-
|
|
14
14
|
function modelLabel(entry) {
|
|
15
15
|
if (typeof entry === "string") return entry;
|
|
16
16
|
return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
function isFableSession(session, modelsByVendor, fallbackModel) {
|
|
20
|
-
if (!session || (session.vendor || "claude") !== "claude") return false;
|
|
21
|
-
var effectiveModel = session.model || fallbackModel || "";
|
|
22
|
-
var selected = String(effectiveModel).toLowerCase();
|
|
23
|
-
if (selected.indexOf("fable") !== -1) return true;
|
|
24
|
-
var models = (modelsByVendor && modelsByVendor.claude) || [];
|
|
25
|
-
for (var i = 0; i < models.length; i++) {
|
|
26
|
-
if (modelValue(models[i]) !== effectiveModel) continue;
|
|
27
|
-
if (modelLabel(models[i]).toLowerCase().indexOf("fable") !== -1) return true;
|
|
28
|
-
}
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
18
|
function toolResult(value) {
|
|
33
19
|
return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
|
|
34
20
|
}
|
|
35
|
-
|
|
36
21
|
function attachWorkerProposal(ctx) {
|
|
37
22
|
var sm = ctx.sm;
|
|
38
23
|
var store = ctx.splitStore;
|
|
24
|
+
function isLiveSession(session) {
|
|
25
|
+
return !!session && sm.sessions.get(session.localId) === session;
|
|
26
|
+
}
|
|
39
27
|
|
|
28
|
+
function canOffer(session) { return !ctx.isMate && isLiveSession(session) && driverEligibility.isEligibleDriverSession(session, sm); }
|
|
40
29
|
function isEligible(session) {
|
|
41
|
-
|
|
42
|
-
if (store.groupForMember(session.localId)) return false;
|
|
43
|
-
return isFableSession(session, sm.modelsByVendor, (sm.defaultModelByVendor || {})[session.vendor || "claude"]);
|
|
30
|
+
return canOffer(session) && !store.groupForMember(session.localId);
|
|
44
31
|
}
|
|
45
|
-
|
|
46
32
|
function safeModelsByVendor(installed) {
|
|
47
33
|
var result = {};
|
|
48
34
|
for (var i = 0; i < installed.length; i++) {
|
|
@@ -95,7 +81,21 @@ function attachWorkerProposal(ctx) {
|
|
|
95
81
|
for (var i = 0; i < models.length; i++) {
|
|
96
82
|
if (modelValue(models[i]) === model) return true;
|
|
97
83
|
}
|
|
98
|
-
return
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function effortIsAvailable(options, vendor, model, effort) {
|
|
88
|
+
var capabilities = options.capabilitiesByVendor[vendor] || {};
|
|
89
|
+
if (capabilities.effort === false) return !effort;
|
|
90
|
+
var clamped = yoke.clampEffort(vendor, effort) || "";
|
|
91
|
+
if (!effort || clamped !== effort) return false;
|
|
92
|
+
var models = options.modelsByVendor[vendor] || [];
|
|
93
|
+
for (var i = 0; i < models.length; i++) {
|
|
94
|
+
if (modelValue(models[i]) !== model) continue;
|
|
95
|
+
var levels = models[i] && models[i].supportedEffortLevels;
|
|
96
|
+
return !Array.isArray(levels) || levels.length === 0 || levels.indexOf(effort) !== -1;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function chooseRecommendation(args, session, options) {
|
|
@@ -117,7 +117,6 @@ function attachWorkerProposal(ctx) {
|
|
|
117
117
|
if (!model && models.length > 0) {
|
|
118
118
|
for (var j = 0; j < models.length; j++) {
|
|
119
119
|
if (vendor === session.vendor && modelValue(models[j]) === currentModel) continue;
|
|
120
|
-
if (modelLabel(models[j]).toLowerCase().indexOf("fable") !== -1) continue;
|
|
121
120
|
model = modelValue(models[j]);
|
|
122
121
|
break;
|
|
123
122
|
}
|
|
@@ -127,6 +126,31 @@ function attachWorkerProposal(ctx) {
|
|
|
127
126
|
return { vendor: vendor, model: model, effort: effort };
|
|
128
127
|
}
|
|
129
128
|
|
|
129
|
+
function skipPermissionsEnabled(session) {
|
|
130
|
+
return !!(session && (session.permissionMode === "bypassPermissions" ||
|
|
131
|
+
session.dangerouslySkipPermissions || ctx.dangerouslySkipPermissions));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function recommendationCanAutoAccept(args, recommendation, options) {
|
|
135
|
+
var requestedVendor = typeof args.recommendedVendor === "string" ? args.recommendedVendor.trim() : "";
|
|
136
|
+
var requestedModel = typeof args.recommendedModel === "string" ? args.recommendedModel.trim() : "";
|
|
137
|
+
var requestedEffort = typeof args.recommendedEffort === "string" ? args.recommendedEffort.trim() : "";
|
|
138
|
+
if (!requestedVendor || requestedVendor !== recommendation.vendor) return false;
|
|
139
|
+
if (!requestedModel || requestedModel !== recommendation.model ||
|
|
140
|
+
!modelIsAvailable(options, requestedVendor, requestedModel)) return false;
|
|
141
|
+
var capabilities = options.capabilitiesByVendor[requestedVendor] || {};
|
|
142
|
+
if (capabilities.effort === false) return !requestedEffort && !recommendation.effort;
|
|
143
|
+
return requestedEffort === recommendation.effort &&
|
|
144
|
+
effortIsAvailable(options, requestedVendor, requestedModel, requestedEffort);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function autoAcceptanceWs(session) {
|
|
148
|
+
return {
|
|
149
|
+
_clayActiveSession: session.localId,
|
|
150
|
+
_clayUser: session.ownerId ? { id: session.ownerId } : null,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
130
154
|
function findProposal(session, proposalId) {
|
|
131
155
|
var history = (session && session.history) || [];
|
|
132
156
|
for (var i = history.length - 1; i >= 0; i--) {
|
|
@@ -154,31 +178,24 @@ function attachWorkerProposal(ctx) {
|
|
|
154
178
|
}, patch));
|
|
155
179
|
}
|
|
156
180
|
|
|
157
|
-
function skipPermissionsEnabled(session) {
|
|
158
|
-
return !!session && (session.permissionMode === "bypassPermissions" || session.dangerouslySkipPermissions === true);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function autoApprovalWs(session) {
|
|
162
|
-
return {
|
|
163
|
-
_clayActiveSession: session.localId,
|
|
164
|
-
_clayUser: session.ownerId ? { id: session.ownerId } : null,
|
|
165
|
-
_autoApproval: true,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
181
|
async function propose(args, session) {
|
|
170
|
-
if (!isEligible(session)) return toolResult({ error: "Split Worker
|
|
182
|
+
if (!isEligible(session)) return toolResult({ error: "Split Worker proposals are only available in an eligible unpaired Driver session." });
|
|
171
183
|
if (hasPendingProposal(session)) return toolResult({ error: "A Split Worker suggestion is already awaiting a decision." });
|
|
172
184
|
var summary = typeof args.summary === "string" ? args.summary.trim() : "";
|
|
173
185
|
var plan = typeof args.plan === "string" ? args.plan.trim() : "";
|
|
174
186
|
var task = typeof args.message === "string" ? args.message.trim() : "";
|
|
175
|
-
|
|
187
|
+
var rationale = typeof args.recommendationRationale === "string" ? args.recommendationRationale.trim() : "";
|
|
188
|
+
if (!summary || !plan || !task || !rationale) {
|
|
189
|
+
return toolResult({ error: "summary, plan, message, and recommendationRationale are required." });
|
|
190
|
+
}
|
|
176
191
|
if (task.length > MAX_TASK_CHARS) return toolResult({ error: "The Split Worker task is too long." });
|
|
177
192
|
await ensureModelCatalogs();
|
|
193
|
+
if (!isEligible(session)) {
|
|
194
|
+
return toolResult({ error: "The Driver session changed while Split Worker runtimes were loading." });
|
|
195
|
+
}
|
|
178
196
|
var options = proposalOptions();
|
|
179
197
|
if (options.installedVendors.length === 0) return toolResult({ error: "No coding agent is installed for a Split Worker session." });
|
|
180
198
|
var recommendation = chooseRecommendation(args, session, options);
|
|
181
|
-
var autoApprove = skipPermissionsEnabled(session);
|
|
182
199
|
var proposal = {
|
|
183
200
|
type: "worker_proposal",
|
|
184
201
|
proposalId: "worker_" + crypto.randomUUID(),
|
|
@@ -189,22 +206,22 @@ function attachWorkerProposal(ctx) {
|
|
|
189
206
|
recommendedVendor: recommendation.vendor,
|
|
190
207
|
recommendedModel: recommendation.model,
|
|
191
208
|
recommendedEffort: recommendation.effort,
|
|
209
|
+
recommendationRationale: rationale.slice(0, MAX_RATIONALE_CHARS),
|
|
192
210
|
options: options,
|
|
193
211
|
};
|
|
194
|
-
if (autoApprove) proposal.autoApproved = true;
|
|
195
212
|
sm.sendAndRecord(session, proposal);
|
|
196
|
-
if (
|
|
213
|
+
if (skipPermissionsEnabled(session) && recommendationCanAutoAccept(args, recommendation, options)) {
|
|
197
214
|
var accepted = await acceptProposal(session, proposal, {
|
|
198
215
|
vendor: recommendation.vendor,
|
|
199
216
|
model: recommendation.model,
|
|
200
217
|
effort: recommendation.effort,
|
|
201
|
-
|
|
202
|
-
}, autoApprovalWs(session));
|
|
203
|
-
if (!accepted.ok) return toolResult({ error: accepted.error || "Could not start the Split Worker." });
|
|
218
|
+
}, autoAcceptanceWs(session), true);
|
|
204
219
|
return toolResult({
|
|
205
|
-
status: "
|
|
220
|
+
status: accepted.ok ? "auto_accepted" : "posted",
|
|
206
221
|
proposalId: proposal.proposalId,
|
|
207
|
-
instruction:
|
|
222
|
+
instruction: accepted.ok
|
|
223
|
+
? "The recorded Split Worker configuration was auto-accepted under the Driver session's full-access mode. End this turn now while the exact paired Worker runs."
|
|
224
|
+
: "The automatic decision failed closed. The configuration card remains pending for the user; end this turn and wait for their decision.",
|
|
208
225
|
});
|
|
209
226
|
}
|
|
210
227
|
return toolResult({
|
|
@@ -214,6 +231,75 @@ function attachWorkerProposal(ctx) {
|
|
|
214
231
|
});
|
|
215
232
|
}
|
|
216
233
|
|
|
234
|
+
async function proposeReplacement(args, session) {
|
|
235
|
+
var group = session && store.groupForMember(session.localId);
|
|
236
|
+
if (ctx.isMate || !isLiveSession(session) || !driverEligibility.isEligibleDriverSession(session, sm) || !group ||
|
|
237
|
+
!group.pair || group.pair.driverId !== session.localId) {
|
|
238
|
+
return toolResult({ error: "Split Worker replacement proposals require the exact paired Driver session." });
|
|
239
|
+
}
|
|
240
|
+
var sourceGroupId = group.id;
|
|
241
|
+
var sourceWorkerId = group.pair.workerId;
|
|
242
|
+
if (hasPendingProposal(session)) return toolResult({ error: "A Split Worker proposal is already awaiting a decision." });
|
|
243
|
+
var task = typeof args.message === "string" ? args.message.trim() : "";
|
|
244
|
+
var rationale = typeof args.recommendationRationale === "string" ? args.recommendationRationale.trim() : "";
|
|
245
|
+
if (!task) return toolResult({ error: "message is required so an accepted replacement delegates exactly once." });
|
|
246
|
+
if (!rationale) return toolResult({ error: "recommendationRationale is required for the replacement audit trail." });
|
|
247
|
+
if (task.length > MAX_TASK_CHARS) return toolResult({ error: "The Split Worker task is too long." });
|
|
248
|
+
await ensureModelCatalogs();
|
|
249
|
+
var liveGroup = store.groupForMember(session.localId);
|
|
250
|
+
if (!isLiveSession(session) || !driverEligibility.isEligibleDriverSession(session, sm) ||
|
|
251
|
+
!liveGroup || liveGroup.id !== sourceGroupId || !liveGroup.pair ||
|
|
252
|
+
liveGroup.pair.driverId !== session.localId || liveGroup.pair.workerId !== sourceWorkerId) {
|
|
253
|
+
return toolResult({ error: "The Driver/Split Worker pair changed while replacement runtimes were loading." });
|
|
254
|
+
}
|
|
255
|
+
var options = proposalOptions();
|
|
256
|
+
if (options.installedVendors.length === 0) return toolResult({ error: "No coding agent is installed for a Split Worker session." });
|
|
257
|
+
var recommendationArgs = {
|
|
258
|
+
recommendedVendor: args.workerVendor,
|
|
259
|
+
recommendedModel: args.workerModel,
|
|
260
|
+
recommendedEffort: args.workerEffort,
|
|
261
|
+
};
|
|
262
|
+
var recommendation = chooseRecommendation(recommendationArgs, session, options);
|
|
263
|
+
var proposal = {
|
|
264
|
+
type: "worker_proposal",
|
|
265
|
+
proposalId: "worker_" + crypto.randomUUID(),
|
|
266
|
+
action: "replace",
|
|
267
|
+
summary: "Replace the current Split Worker before the next delegated task.",
|
|
268
|
+
plan: "1. Preserve the current Worker's session and history\n2. Create the selected replacement runtime\n3. Delegate the next task exactly once",
|
|
269
|
+
message: task,
|
|
270
|
+
status: "pending",
|
|
271
|
+
recommendedVendor: recommendation.vendor,
|
|
272
|
+
recommendedModel: recommendation.model,
|
|
273
|
+
recommendedEffort: recommendation.effort,
|
|
274
|
+
recommendationRationale: rationale.slice(0, MAX_RATIONALE_CHARS),
|
|
275
|
+
options: options,
|
|
276
|
+
sourceGroupId: sourceGroupId,
|
|
277
|
+
sourceWorkerId: sourceWorkerId,
|
|
278
|
+
interrupt: args.interrupt === true,
|
|
279
|
+
evaluation: args.evaluation || null,
|
|
280
|
+
};
|
|
281
|
+
sm.sendAndRecord(session, proposal);
|
|
282
|
+
if (skipPermissionsEnabled(session) && recommendationCanAutoAccept(recommendationArgs, recommendation, options)) {
|
|
283
|
+
var accepted = await acceptProposal(session, proposal, {
|
|
284
|
+
vendor: recommendation.vendor,
|
|
285
|
+
model: recommendation.model,
|
|
286
|
+
effort: recommendation.effort,
|
|
287
|
+
}, autoAcceptanceWs(session), true);
|
|
288
|
+
return toolResult({
|
|
289
|
+
status: accepted.ok ? "auto_accepted" : "posted",
|
|
290
|
+
proposalId: proposal.proposalId,
|
|
291
|
+
instruction: accepted.ok
|
|
292
|
+
? "The recorded replacement configuration was auto-accepted under the Driver session's full-access mode."
|
|
293
|
+
: "The automatic replacement failed closed. The card remains pending for the user.",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return toolResult({
|
|
297
|
+
status: "posted",
|
|
298
|
+
proposalId: proposal.proposalId,
|
|
299
|
+
instruction: "The replacement configuration is visible in the chat. End this turn now and wait for the user's decision.",
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
217
303
|
function resumeDriver(session, text) {
|
|
218
304
|
var sdk = ctx.getSdk();
|
|
219
305
|
if (!sdk) return Promise.reject(new Error("SDK bridge is not ready"));
|
|
@@ -268,31 +354,86 @@ function attachWorkerProposal(ctx) {
|
|
|
268
354
|
return session;
|
|
269
355
|
}
|
|
270
356
|
|
|
271
|
-
async function acceptProposal(session, proposal, msg, ws) {
|
|
272
|
-
|
|
357
|
+
async function acceptProposal(session, proposal, msg, ws, trustedAutoAcceptance) {
|
|
358
|
+
if (!isLiveSession(session)) throw new Error("The Driver session is no longer live");
|
|
359
|
+
if (proposal.action !== "replace" && !isEligible(session)) {
|
|
360
|
+
throw new Error("The Driver is no longer eligible to create this Split Worker");
|
|
361
|
+
}
|
|
362
|
+
var approvedGroup = null;
|
|
363
|
+
if (proposal.action === "replace") {
|
|
364
|
+
approvedGroup = store.groupForMember(session.localId);
|
|
365
|
+
if (!driverEligibility.isEligibleDriverSession(session, sm) || !approvedGroup ||
|
|
366
|
+
approvedGroup.id !== proposal.sourceGroupId || !approvedGroup.pair ||
|
|
367
|
+
approvedGroup.pair.driverId !== session.localId || approvedGroup.pair.workerId !== proposal.sourceWorkerId) {
|
|
368
|
+
throw new Error("The Driver/Split Worker pair changed before the replacement was approved");
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
var options = proposalOptions();
|
|
273
372
|
var vendor = msg.vendor || proposal.recommendedVendor;
|
|
274
373
|
var model = msg.model || "";
|
|
275
374
|
if (options.installedVendors.indexOf(vendor) === -1) throw new Error("Selected Split Worker vendor is not installed");
|
|
276
375
|
if (!modelIsAvailable(options, vendor, model)) throw new Error("Selected Split Worker model is unavailable");
|
|
277
|
-
var
|
|
278
|
-
var
|
|
279
|
-
if (msg.
|
|
376
|
+
var requestedEffort = msg.effort || proposal.recommendedEffort || "medium";
|
|
377
|
+
var effort = yoke.clampEffort(vendor, requestedEffort) || "";
|
|
378
|
+
if ((msg.effort && effort !== msg.effort) || !effortIsAvailable(options, vendor, model, effort)) {
|
|
379
|
+
throw new Error("Selected Split Worker reasoning effort is unavailable");
|
|
380
|
+
}
|
|
381
|
+
var autoAccepted = trustedAutoAcceptance === true;
|
|
382
|
+
var startingPatch = {
|
|
383
|
+
status: "starting",
|
|
384
|
+
selectedVendor: vendor,
|
|
385
|
+
selectedModel: model,
|
|
386
|
+
selectedEffort: effort,
|
|
387
|
+
autoAccepted: autoAccepted,
|
|
388
|
+
decisionMode: autoAccepted ? "driver_recommendation" : "user",
|
|
389
|
+
decidedAt: Date.now(),
|
|
390
|
+
};
|
|
280
391
|
updateProposal(session, proposal, startingPatch);
|
|
281
392
|
try {
|
|
393
|
+
if (proposal.action === "replace") {
|
|
394
|
+
var liveGroup = approvedGroup;
|
|
395
|
+
var replaced = await ctx.replacePartner({
|
|
396
|
+
interrupt: proposal.interrupt === true,
|
|
397
|
+
workerVendor: vendor,
|
|
398
|
+
workerModel: model,
|
|
399
|
+
workerEffort: effort,
|
|
400
|
+
evaluation: proposal.evaluation || undefined,
|
|
401
|
+
}, session);
|
|
402
|
+
liveGroup = store.groupForMember(session.localId);
|
|
403
|
+
updateProposal(session, proposal, { status: "running", groupId: liveGroup.id, workerId: replaced.workerSessionId });
|
|
404
|
+
runWorker(session, proposal).catch(function (err) {
|
|
405
|
+
updateProposal(session, proposal, { status: "error", error: err.message || String(err) });
|
|
406
|
+
resumeDriver(session, "[Split Worker execution failed]\n" + (err.message || String(err))).catch(function () {});
|
|
407
|
+
});
|
|
408
|
+
return { ok: true, status: "running", group: liveGroup, replacement: replaced };
|
|
409
|
+
}
|
|
282
410
|
var created = ctx.createPairRecord(ws, {
|
|
283
411
|
driver: { sessionId: session.localId },
|
|
284
412
|
worker: { vendor: vendor, model: model, effort: effort },
|
|
285
413
|
});
|
|
414
|
+
ctx.recordGenerationStart(session, created.worker);
|
|
286
415
|
updateProposal(session, proposal, { status: "running", groupId: created.group.id, workerId: created.worker.localId });
|
|
287
|
-
|
|
288
|
-
else ctx.sendTo(ws, { type: "pair_session_created", ok: true, group: created.group });
|
|
416
|
+
ctx.sendTo(ws, { type: "pair_session_created", ok: true, group: created.group });
|
|
289
417
|
runWorker(session, proposal).catch(function (err) {
|
|
290
418
|
updateProposal(session, proposal, { status: "error", error: err.message || String(err) });
|
|
291
419
|
resumeDriver(session, "[Split Worker execution failed]\n" + (err.message || String(err))).catch(function () {});
|
|
292
420
|
});
|
|
293
421
|
return { ok: true, status: "running", group: created.group };
|
|
294
422
|
} catch (err) {
|
|
295
|
-
|
|
423
|
+
if (proposal.action === "replace") {
|
|
424
|
+
var restoredGroup = store.groupForMember(session.localId);
|
|
425
|
+
if (restoredGroup && restoredGroup.pair && restoredGroup.pair.driverId === session.localId &&
|
|
426
|
+
restoredGroup.pair.workerId === proposal.sourceWorkerId) {
|
|
427
|
+
proposal.sourceGroupId = restoredGroup.id;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
updateProposal(session, proposal, {
|
|
431
|
+
status: "pending",
|
|
432
|
+
error: err.message || String(err),
|
|
433
|
+
autoAccepted: false,
|
|
434
|
+
decisionMode: null,
|
|
435
|
+
decidedAt: null,
|
|
436
|
+
});
|
|
296
437
|
return { ok: false, status: "pending", error: err.message || String(err) };
|
|
297
438
|
}
|
|
298
439
|
}
|
|
@@ -304,7 +445,10 @@ function attachWorkerProposal(ctx) {
|
|
|
304
445
|
if (proposal.status !== "pending") throw new Error("Split Worker suggestion has already been resolved");
|
|
305
446
|
if (!msg.accepted) {
|
|
306
447
|
updateProposal(session, proposal, { status: "declined" });
|
|
307
|
-
|
|
448
|
+
var declinedText = proposal.action === "replace"
|
|
449
|
+
? "[Split Worker replacement declined]\nContinue with the existing Split Worker and the current task."
|
|
450
|
+
: "[Split Worker proposal declined]\nContinue this task in the current Driver session using the plan you already prepared.";
|
|
451
|
+
await resumeDriver(session, declinedText);
|
|
308
452
|
return { ok: true, status: "declined" };
|
|
309
453
|
}
|
|
310
454
|
return acceptProposal(session, proposal, msg, ws);
|
|
@@ -318,11 +462,12 @@ function attachWorkerProposal(ctx) {
|
|
|
318
462
|
return true;
|
|
319
463
|
}
|
|
320
464
|
|
|
321
|
-
function getToolDefs(session) {
|
|
322
|
-
|
|
465
|
+
function getToolDefs(session, options) {
|
|
466
|
+
var persistent = !!(options && options.persistent);
|
|
467
|
+
if (!isEligible(session) && !(persistent && canOffer(session))) return [];
|
|
323
468
|
return [{
|
|
324
469
|
name: "propose_worker",
|
|
325
|
-
description: "
|
|
470
|
+
description: "Propose a visible Split Worker for implementation-heavy execution. This non-mutating tool only shows the user a card where they choose vendor, model, and effort. It never creates a session or delegates work until the user accepts.",
|
|
326
471
|
inputSchema: buildShape({
|
|
327
472
|
summary: { type: "string", description: "One short sentence explaining why a Split Worker is useful for this task." },
|
|
328
473
|
plan: { type: "string", description: "A concise numbered implementation plan to show in the approval card." },
|
|
@@ -330,19 +475,21 @@ function attachWorkerProposal(ctx) {
|
|
|
330
475
|
recommendedVendor: { type: "string", description: "Optional installed vendor id for the Split Worker." },
|
|
331
476
|
recommendedModel: { type: "string", description: "Optional exact Split Worker model id. Omit when uncertain." },
|
|
332
477
|
recommendedEffort: { type: "string", description: "Optional reasoning effort: minimal, low, medium, high, xhigh, or max." },
|
|
333
|
-
|
|
478
|
+
recommendationRationale: { type: "string", description: "Concise Driver-authored explanation of why the recommended vendor, model, and effort fit this exact task." },
|
|
479
|
+
}, ["summary", "plan", "message", "recommendationRationale"]),
|
|
334
480
|
handler: function (args) { return propose(args || {}, session); },
|
|
335
481
|
}];
|
|
336
482
|
}
|
|
337
483
|
|
|
338
484
|
function getSystemPrompt(session) {
|
|
339
|
-
if (!isEligible(session)) return "";
|
|
340
|
-
return "
|
|
485
|
+
if (!isEligible(session) || !driverOrchestration.isHighTierDriverSession(session, sm)) return "";
|
|
486
|
+
return "For implementation-heavy work, use propose_worker before substantial execution. Work is implementation-heavy when it spans multiple modules, crosses client/server/data boundaries, requires a migration, or contains independent investigation and execution. Do not skip delegation merely because you can implement it yourself; preserve the Driver for decomposition, runtime selection, judgment, review, and integration. The user's explicit request to work directly takes precedence, and work that must remain sequential in this session stays here. Recommend an exact available vendor, model, and effort, and give a concise recommendationRationale explaining why all three fit the task. Clay always records and shows the runtime configuration card. In full-access mode Clay may auto-accept that exact validated recommendation; otherwise it waits for the user's explicit choice. After posting, end the turn. If accepted, Clay creates the exact Driver/Split Worker pair and delegates the proposed task once. If declined, continue in this Driver session. Do not call send_to_partner while unpaired.";
|
|
341
487
|
}
|
|
342
488
|
|
|
343
489
|
return {
|
|
344
490
|
getToolDefs: getToolDefs,
|
|
345
491
|
getSystemPrompt: getSystemPrompt,
|
|
492
|
+
proposeReplacement: proposeReplacement,
|
|
346
493
|
handleMessage: handleMessage,
|
|
347
494
|
respondToProposal: respondToProposal,
|
|
348
495
|
};
|
|
@@ -350,5 +497,4 @@ function attachWorkerProposal(ctx) {
|
|
|
350
497
|
|
|
351
498
|
module.exports = {
|
|
352
499
|
attachWorkerProposal: attachWorkerProposal,
|
|
353
|
-
isFableSession: isFableSession,
|
|
354
500
|
};
|