clay-server 3.4.0-beta.9 → 3.5.0-beta.1
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/daemon.js +58 -58
- package/lib/project-connection.js +19 -6
- package/lib/project-models.js +220 -0
- package/lib/project-session-handoff.js +281 -0
- package/lib/project-session-pair.js +14 -7
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +13 -45
- package/lib/project-worker-proposal.js +51 -14
- package/lib/project.js +74 -80
- package/lib/public/app.js +11 -0
- package/lib/public/copilot-avatar.svg +5 -0
- package/lib/public/css/filebrowser.css +6 -1
- package/lib/public/css/input.css +56 -0
- package/lib/public/css/mates.css +6 -0
- package/lib/public/css/menus.css +35 -56
- package/lib/public/css/pane.css +18 -3
- package/lib/public/css/session-actions.css +98 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +26 -9
- package/lib/public/junie-avatar.svg +11 -0
- package/lib/public/kimi-avatar.svg +4 -0
- package/lib/public/modules/agent-config-selects.js +69 -0
- package/lib/public/modules/app-connection.js +6 -0
- package/lib/public/modules/app-header.js +0 -22
- package/lib/public/modules/app-messages.js +48 -9
- package/lib/public/modules/app-panels.js +33 -82
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rendering.js +16 -1
- package/lib/public/modules/background-tasks-ui.js +55 -0
- package/lib/public/modules/filebrowser-tabs.js +65 -0
- package/lib/public/modules/filebrowser.js +23 -3
- package/lib/public/modules/mate-sidebar.js +10 -2
- package/lib/public/modules/model-picker.js +263 -0
- package/lib/public/modules/pane-bridge.js +6 -0
- package/lib/public/modules/session-actions.js +294 -0
- package/lib/public/modules/sidebar-mobile.js +2 -1
- package/lib/public/modules/sidebar-sessions.js +17 -1
- package/lib/public/modules/split-pair-ui.js +16 -77
- package/lib/public/modules/split-view.js +11 -1
- package/lib/public/modules/tools.js +5 -0
- package/lib/public/modules/vendor-priority.js +6 -1
- package/lib/public/modules/worker-proposal.js +6 -1
- package/lib/public/qwen-avatar.svg +4 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +57 -34
- package/lib/sdk-message-processor.js +26 -4
- package/lib/session-handoff-context.js +113 -0
- package/lib/session-handoff-mcp-server.js +36 -0
- package/lib/session-notes-mcp-server.js +1 -0
- package/lib/sessions.js +4 -0
- package/lib/ws-schema.js +9 -1
- package/lib/yoke/acp-agent-profiles.js +64 -0
- package/lib/yoke/adapters/acp.js +2 -1
- package/lib/yoke/adapters/claude.js +34 -0
- package/lib/yoke/adapters/codex.js +38 -3
- package/lib/yoke/adapters/copilot.js +7 -0
- package/lib/yoke/adapters/grok.js +7 -0
- package/lib/yoke/adapters/junie.js +7 -0
- package/lib/yoke/adapters/kimi.js +7 -0
- package/lib/yoke/adapters/kiro.js +4 -3
- package/lib/yoke/adapters/qwen.js +7 -0
- package/lib/yoke/codex-background-tasks.js +84 -0
- package/lib/yoke/index.js +43 -14
- package/lib/yoke/instructions.js +3 -0
- package/lib/yoke/interface.js +21 -0
- package/lib/yoke/vendor-registry.js +66 -0
- package/package.json +1 -1
|
@@ -98,10 +98,15 @@ function attachSessionPair(ctx) {
|
|
|
98
98
|
token.delivered = true;
|
|
99
99
|
var failure = token.failure || null;
|
|
100
100
|
var response = token.response || "";
|
|
101
|
-
var text
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
var text;
|
|
102
|
+
if (token.interrupted) {
|
|
103
|
+
text = "[Worker execution interrupted] The user interrupted the Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
|
|
104
|
+
} else {
|
|
105
|
+
text = "A Worker task delegated through send_to_partner has finished.\n\n" +
|
|
106
|
+
"Original task:\n" + token.message + "\n\n" +
|
|
107
|
+
(failure ? "Worker error:\n" + failure : "Worker result:\n" + (response || "(No text response was recorded.)")) +
|
|
108
|
+
"\n\nReview the result, verify it as needed, and continue the task.";
|
|
109
|
+
}
|
|
105
110
|
sm.sendAndRecord(caller, {
|
|
106
111
|
type: "user_message",
|
|
107
112
|
text: text,
|
|
@@ -146,6 +151,7 @@ function attachSessionPair(ctx) {
|
|
|
146
151
|
}
|
|
147
152
|
token.response = responseText(partner.history || [], token.startIndex);
|
|
148
153
|
token.failure = errorSince(partner.history || [], token.startIndex);
|
|
154
|
+
token.interrupted = !token.failure && !!partner._lastTurnInterrupted;
|
|
149
155
|
finishDelegation(group, caller, partner, token);
|
|
150
156
|
return resumeDriverWithResult(caller, partner, token);
|
|
151
157
|
}
|
|
@@ -165,8 +171,9 @@ function attachSessionPair(ctx) {
|
|
|
165
171
|
clearInterval(timer);
|
|
166
172
|
finishDelegation(group, caller, partner, token);
|
|
167
173
|
var failure = errorSince(partner.history || [], token.startIndex);
|
|
174
|
+
var interrupted = !failure && !!partner._lastTurnInterrupted;
|
|
168
175
|
resolve({
|
|
169
|
-
status: failure ? "error" : "complete",
|
|
176
|
+
status: failure ? "error" : (interrupted ? "interrupted" : "complete"),
|
|
170
177
|
response: responseText(partner.history || [], token.startIndex),
|
|
171
178
|
error: failure || undefined,
|
|
172
179
|
});
|
|
@@ -260,7 +267,7 @@ function attachSessionPair(ctx) {
|
|
|
260
267
|
var count = Number.isFinite(args.lastTurns) ? Math.floor(args.lastTurns) : 1;
|
|
261
268
|
count = Math.max(1, Math.min(5, count));
|
|
262
269
|
return toolResult({
|
|
263
|
-
status: resolved.partner.isProcessing ? "running" : "idle",
|
|
270
|
+
status: resolved.partner.isProcessing ? "running" : (resolved.partner._lastTurnInterrupted ? "interrupted" : "idle"),
|
|
264
271
|
partnerId: resolved.partner.localId,
|
|
265
272
|
title: resolved.partner.title || "New Session",
|
|
266
273
|
turns: recentTurns(resolved.partner, count),
|
|
@@ -383,7 +390,7 @@ function attachSessionPair(ctx) {
|
|
|
383
390
|
var group = store.groupForMember(session.localId);
|
|
384
391
|
var pairPrompt = "";
|
|
385
392
|
if (group && group.pair && group.pair.driverId === session.localId) {
|
|
386
|
-
pairPrompt = "You are the Driver in a two-agent pair. The tools send_to_partner and read_partner are provided directly to you. Use send_to_partner to delegate concrete bounded work to the Worker. A completed Worker turn leaves the Worker session available for more work. If review or user feedback requires corrections to the Worker's implementation, delegate a follow-up turn to that Worker instead of editing the Worker-owned files yourself. If send_to_partner reports that the Worker is no longer available, create a replacement with spawn_sessions and delegate the remaining implementation rather than taking it over. If a non-waiting delegation finishes later, Clay pushes the result back and resumes you automatically; use read_partner only when you need an interim status check. Integrate and verify the final outcome. Do not search the project for implementations of these tools, and do not delegate work that must be performed sequentially in this same session.";
|
|
393
|
+
pairPrompt = "You are the Driver in a two-agent pair. The tools send_to_partner and read_partner are provided directly to you. Use send_to_partner to delegate concrete bounded work to the Worker. A completed Worker turn leaves the Worker session available for more work. If a Worker turn is interrupted, its work is partial and unverified; review it and decide next steps with the user. If review or user feedback requires corrections to the Worker's implementation, delegate a follow-up turn to that Worker instead of editing the Worker-owned files yourself. If send_to_partner reports that the Worker is no longer available, create a replacement with spawn_sessions and delegate the remaining implementation rather than taking it over. If a non-waiting delegation finishes later, Clay pushes the result back and resumes you automatically; use read_partner only when you need an interim status check. Integrate and verify the final outcome. Do not search the project for implementations of these tools, and do not delegate work that must be performed sequentially in this same session.";
|
|
387
394
|
}
|
|
388
395
|
return [pairPrompt, workerProposal.getSystemPrompt(session)].filter(Boolean).join("\n\n");
|
|
389
396
|
}
|
|
@@ -319,7 +319,7 @@ function attachSessionSpawn(ctx) {
|
|
|
319
319
|
statuses.push({
|
|
320
320
|
localId: session.localId,
|
|
321
321
|
title: session.title || "New Session",
|
|
322
|
-
status: session.isProcessing ? "running" : (hasSessionError(session) ? "error" : "done"),
|
|
322
|
+
status: session.isProcessing ? "running" : (session._lastTurnInterrupted ? "interrupted" : (hasSessionError(session) ? "error" : "done")),
|
|
323
323
|
turnCount: session.turnCount || 0,
|
|
324
324
|
lastActivity: session.lastActivity || session.createdAt || 0,
|
|
325
325
|
});
|
package/lib/project-sessions.js
CHANGED
|
@@ -658,10 +658,6 @@ function attachSessions(ctx) {
|
|
|
658
658
|
// (resume_tui_session), and born-GUI never auto-converts to a terminal.
|
|
659
659
|
resolveSessionForView(xmTarget, ws);
|
|
660
660
|
}
|
|
661
|
-
// If the target session's vendor doesn't own the currently cached
|
|
662
|
-
// model, clear sm.currentModel so the UI and next query don't leak
|
|
663
|
-
// the previous session's vendor-specific model into this one. A
|
|
664
|
-
// session-specific model always wins and survives daemon restarts.
|
|
665
661
|
var switchTargetSess = sm.sessions.get(msg.id);
|
|
666
662
|
var switchTargetVendor = switchTargetSess && (switchTargetSess.vendor || sm.defaultVendor || "claude");
|
|
667
663
|
if (switchTargetSess && !switchTargetSess.effort) {
|
|
@@ -671,22 +667,6 @@ function attachSessions(ctx) {
|
|
|
671
667
|
) || null;
|
|
672
668
|
sm.saveSessionFile(switchTargetSess);
|
|
673
669
|
}
|
|
674
|
-
if (switchTargetSess && switchTargetSess.model) {
|
|
675
|
-
sm.currentModel = switchTargetSess.model;
|
|
676
|
-
} else if (switchTargetSess && sm.currentModel) {
|
|
677
|
-
var targetVendor = switchTargetSess.vendor || sm.defaultVendor || null;
|
|
678
|
-
var tvModels = (targetVendor && sm.modelsByVendor && sm.modelsByVendor[targetVendor]) || [];
|
|
679
|
-
var found = false;
|
|
680
|
-
var _curLc = sm.currentModel.toLowerCase();
|
|
681
|
-
for (var tvi = 0; tvi < tvModels.length; tvi++) {
|
|
682
|
-
var tvEntry = tvModels[tvi];
|
|
683
|
-
var tvVal = typeof tvEntry === "string" ? tvEntry : (tvEntry && (tvEntry.value || tvEntry.id)) || "";
|
|
684
|
-
if (tvVal === sm.currentModel || (tvVal && (tvVal.toLowerCase().indexOf(_curLc) !== -1 || _curLc.indexOf(tvVal.toLowerCase()) !== -1))) { found = true; break; }
|
|
685
|
-
}
|
|
686
|
-
if (tvModels.length > 0 && !found) {
|
|
687
|
-
sm.currentModel = "";
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
670
|
// Check access in multi-user mode
|
|
691
671
|
if (usersModule.isMultiUser() && ws._clayUser) {
|
|
692
672
|
var switchTarget = sm.sessions.get(msg.id);
|
|
@@ -958,14 +938,6 @@ function attachSessions(ctx) {
|
|
|
958
938
|
return true;
|
|
959
939
|
}
|
|
960
940
|
|
|
961
|
-
if (msg.type === "set_model" && msg.model) {
|
|
962
|
-
var session = getSessionForWs(ws);
|
|
963
|
-
if (session) {
|
|
964
|
-
sdk.setModel(session, msg.model);
|
|
965
|
-
}
|
|
966
|
-
return true;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
941
|
if (msg.type === "reload_skills") {
|
|
970
942
|
var session = getSessionForWs(ws);
|
|
971
943
|
if (session && sdk.reloadSkills) {
|
|
@@ -997,11 +969,7 @@ function attachSessions(ctx) {
|
|
|
997
969
|
" is bound to '" + vendorSession.vendor + "', refused rebind to '" + msg.vendor + "'");
|
|
998
970
|
} else {
|
|
999
971
|
vendorSession.vendor = msg.vendor;
|
|
1000
|
-
|
|
1001
|
-
// instead of leaking the previous vendor's model into a fresh session.
|
|
1002
|
-
if (sm.currentModel) {
|
|
1003
|
-
sm.currentModel = "";
|
|
1004
|
-
}
|
|
972
|
+
if (vendorSession.vendor !== msg.vendor) vendorSession.model = "";
|
|
1005
973
|
sm.saveSessionFile(vendorSession);
|
|
1006
974
|
sm.broadcastSessionList();
|
|
1007
975
|
}
|
|
@@ -1012,11 +980,11 @@ function attachSessions(ctx) {
|
|
|
1012
980
|
type: "model_info",
|
|
1013
981
|
model: "",
|
|
1014
982
|
models: vendorModels,
|
|
1015
|
-
vendor: msg.vendor,
|
|
983
|
+
vendor: msg.vendor, sessionId: vendorSession ? vendorSession.localId : null,
|
|
1016
984
|
availableVendors: sm.availableVendors || [],
|
|
1017
985
|
installedVendors: sm.installedVendors || [],
|
|
1018
986
|
});
|
|
1019
|
-
send({ type: "config_state",
|
|
987
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1020
988
|
}
|
|
1021
989
|
return true;
|
|
1022
990
|
}
|
|
@@ -1052,7 +1020,7 @@ function attachSessions(ctx) {
|
|
|
1052
1020
|
sm.broadcastSessionList();
|
|
1053
1021
|
sdk.setPermissionMode(session, msg.mode);
|
|
1054
1022
|
}
|
|
1055
|
-
send({ type: "config_state",
|
|
1023
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1056
1024
|
return true;
|
|
1057
1025
|
}
|
|
1058
1026
|
|
|
@@ -1113,7 +1081,7 @@ function attachSessions(ctx) {
|
|
|
1113
1081
|
if (session) {
|
|
1114
1082
|
sdk.setPermissionMode(session, msg.mode);
|
|
1115
1083
|
}
|
|
1116
|
-
send({ type: "config_state",
|
|
1084
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1117
1085
|
return true;
|
|
1118
1086
|
}
|
|
1119
1087
|
|
|
@@ -1126,7 +1094,7 @@ function attachSessions(ctx) {
|
|
|
1126
1094
|
if (session) {
|
|
1127
1095
|
sdk.setPermissionMode(session, msg.mode);
|
|
1128
1096
|
}
|
|
1129
|
-
send({ type: "config_state",
|
|
1097
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1130
1098
|
return true;
|
|
1131
1099
|
}
|
|
1132
1100
|
|
|
@@ -1142,7 +1110,7 @@ function attachSessions(ctx) {
|
|
|
1142
1110
|
sdk.setEffort(session, sessionEffort).catch(function (e) {
|
|
1143
1111
|
sendTo(ws, { type: "error", text: "Failed to change reasoning effort: " + (e.message || e) });
|
|
1144
1112
|
});
|
|
1145
|
-
sm.sendToSession(session, { type: "config_state", model: session.model || sm.
|
|
1113
|
+
sm.sendToSession(session, { type: "config_state", model: session.model || ((sm.defaultModelByVendor || {})[effortVendor]) || "", vendor: effortVendor, sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: sessionEffort, betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1146
1114
|
}
|
|
1147
1115
|
return true;
|
|
1148
1116
|
}
|
|
@@ -1152,7 +1120,7 @@ function attachSessions(ctx) {
|
|
|
1152
1120
|
opts.onSetServerDefaultEffort(msg.effort);
|
|
1153
1121
|
}
|
|
1154
1122
|
sm.currentEffort = msg.effort;
|
|
1155
|
-
send({ type: "config_state",
|
|
1123
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort, betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1156
1124
|
return true;
|
|
1157
1125
|
}
|
|
1158
1126
|
|
|
@@ -1161,20 +1129,20 @@ function attachSessions(ctx) {
|
|
|
1161
1129
|
opts.onSetProjectDefaultEffort(slug, msg.effort);
|
|
1162
1130
|
}
|
|
1163
1131
|
sm.currentEffort = msg.effort;
|
|
1164
|
-
send({ type: "config_state",
|
|
1132
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort, betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1165
1133
|
return true;
|
|
1166
1134
|
}
|
|
1167
1135
|
|
|
1168
1136
|
if (msg.type === "set_betas") {
|
|
1169
1137
|
sm.currentBetas = msg.betas || [];
|
|
1170
|
-
send({ type: "config_state",
|
|
1138
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas, thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1171
1139
|
return true;
|
|
1172
1140
|
}
|
|
1173
1141
|
|
|
1174
1142
|
if (msg.type === "set_thinking") {
|
|
1175
1143
|
sm.currentThinking = msg.thinking || "adaptive";
|
|
1176
1144
|
if (msg.budgetTokens) sm.currentThinkingBudget = msg.budgetTokens;
|
|
1177
|
-
send({ type: "config_state",
|
|
1145
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1178
1146
|
return true;
|
|
1179
1147
|
}
|
|
1180
1148
|
|
|
@@ -1456,7 +1424,7 @@ function attachSessions(ctx) {
|
|
|
1456
1424
|
if (decision === "allow_accept_edits") {
|
|
1457
1425
|
sdk.setPermissionMode(session, "acceptEdits");
|
|
1458
1426
|
sm.currentPermissionMode = "acceptEdits";
|
|
1459
|
-
send({ type: "config_state",
|
|
1427
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1460
1428
|
pending.resolve({ behavior: "allow", updatedInput: pending.toolInput });
|
|
1461
1429
|
sm.sendAndRecord(session, { type: "permission_resolved", requestId: requestId, decision: decision });
|
|
1462
1430
|
return true;
|
|
@@ -1485,7 +1453,7 @@ function attachSessions(ctx) {
|
|
|
1485
1453
|
|
|
1486
1454
|
// Update permission mode for the new session
|
|
1487
1455
|
sm.currentPermissionMode = "acceptEdits";
|
|
1488
|
-
send({ type: "config_state",
|
|
1456
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1489
1457
|
|
|
1490
1458
|
// Build prompt from plan content (sent from client) or plan file path
|
|
1491
1459
|
var clientPlanContent = msg.planContent || "";
|
|
@@ -40,7 +40,7 @@ function attachWorkerProposal(ctx) {
|
|
|
40
40
|
function isEligible(session) {
|
|
41
41
|
if (ctx.isMate || !session || session.mode === "tui") return false;
|
|
42
42
|
if (store.groupForMember(session.localId)) return false;
|
|
43
|
-
return isFableSession(session, sm.modelsByVendor, sm.
|
|
43
|
+
return isFableSession(session, sm.modelsByVendor, (sm.defaultModelByVendor || {})[session.vendor || "claude"]);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function safeModelsByVendor(installed) {
|
|
@@ -112,7 +112,7 @@ function attachWorkerProposal(ctx) {
|
|
|
112
112
|
}
|
|
113
113
|
var models = options.modelsByVendor[vendor] || [];
|
|
114
114
|
var model = modelIsAvailable(options, vendor, args.recommendedModel) ? (args.recommendedModel || "") : "";
|
|
115
|
-
var currentModel = session.model || sm.
|
|
115
|
+
var currentModel = session.model || (sm.defaultModelByVendor || {})[session.vendor || "claude"] || "";
|
|
116
116
|
if (vendor === session.vendor && model === currentModel) model = "";
|
|
117
117
|
if (!model && models.length > 0) {
|
|
118
118
|
for (var j = 0; j < models.length; j++) {
|
|
@@ -154,6 +154,18 @@ function attachWorkerProposal(ctx) {
|
|
|
154
154
|
}, patch));
|
|
155
155
|
}
|
|
156
156
|
|
|
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
|
+
|
|
157
169
|
async function propose(args, session) {
|
|
158
170
|
if (!isEligible(session)) return toolResult({ error: "Worker suggestions are only available in an unpaired Fable session." });
|
|
159
171
|
if (hasPendingProposal(session)) return toolResult({ error: "A Worker suggestion is already awaiting a decision." });
|
|
@@ -166,6 +178,7 @@ function attachWorkerProposal(ctx) {
|
|
|
166
178
|
var options = proposalOptions();
|
|
167
179
|
if (options.installedVendors.length === 0) return toolResult({ error: "No coding agent is installed for a Worker session." });
|
|
168
180
|
var recommendation = chooseRecommendation(args, session, options);
|
|
181
|
+
var autoApprove = skipPermissionsEnabled(session);
|
|
169
182
|
var proposal = {
|
|
170
183
|
type: "worker_proposal",
|
|
171
184
|
proposalId: "worker_" + crypto.randomUUID(),
|
|
@@ -178,7 +191,22 @@ function attachWorkerProposal(ctx) {
|
|
|
178
191
|
recommendedEffort: recommendation.effort,
|
|
179
192
|
options: options,
|
|
180
193
|
};
|
|
194
|
+
if (autoApprove) proposal.autoApproved = true;
|
|
181
195
|
sm.sendAndRecord(session, proposal);
|
|
196
|
+
if (autoApprove) {
|
|
197
|
+
var accepted = await acceptProposal(session, proposal, {
|
|
198
|
+
vendor: recommendation.vendor,
|
|
199
|
+
model: recommendation.model,
|
|
200
|
+
effort: recommendation.effort,
|
|
201
|
+
autoApproved: true,
|
|
202
|
+
}, autoApprovalWs(session));
|
|
203
|
+
if (!accepted.ok) return toolResult({ error: accepted.error || "Could not start the Worker." });
|
|
204
|
+
return toolResult({
|
|
205
|
+
status: "running",
|
|
206
|
+
proposalId: proposal.proposalId,
|
|
207
|
+
instruction: "The Worker was auto-approved and started because skip permissions is enabled. Its result will return for review.",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
182
210
|
return toolResult({
|
|
183
211
|
status: "posted",
|
|
184
212
|
proposalId: proposal.proposalId,
|
|
@@ -220,6 +248,8 @@ function attachWorkerProposal(ctx) {
|
|
|
220
248
|
var followup;
|
|
221
249
|
if (result.status === "complete") {
|
|
222
250
|
followup = "[Worker execution completed]\nReview and verify the Worker's result. The Worker session remains available: if the implementation needs corrections or additional edits, send a follow-up with send_to_partner instead of taking over the Worker-owned files yourself. If that Worker is no longer available, create a replacement with spawn_sessions for the remaining implementation.\n\n" + (result.response || "The Worker completed without a text summary.");
|
|
251
|
+
} else if (result.status === "interrupted") {
|
|
252
|
+
followup = "[Worker execution interrupted]\nThe user interrupted the Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
|
|
223
253
|
} else if (result.status === "running") {
|
|
224
254
|
followup = "[Worker execution is still running]\nUse read_partner to inspect progress before completing the task.";
|
|
225
255
|
} else {
|
|
@@ -238,30 +268,24 @@ function attachWorkerProposal(ctx) {
|
|
|
238
268
|
return session;
|
|
239
269
|
}
|
|
240
270
|
|
|
241
|
-
async function
|
|
242
|
-
var session = sessionForResponse(ws);
|
|
243
|
-
var proposal = findProposal(session, msg.proposalId);
|
|
244
|
-
if (!proposal) throw new Error("Worker suggestion not found");
|
|
245
|
-
if (proposal.status !== "pending") throw new Error("Worker suggestion has already been resolved");
|
|
246
|
-
if (!msg.accepted) {
|
|
247
|
-
updateProposal(session, proposal, { status: "declined" });
|
|
248
|
-
await resumeDriver(session, "[Worker suggestion declined]\nContinue this task in the current session using the plan you already prepared.");
|
|
249
|
-
return { ok: true, status: "declined" };
|
|
250
|
-
}
|
|
271
|
+
async function acceptProposal(session, proposal, msg, ws) {
|
|
251
272
|
var options = proposal.options || proposalOptions();
|
|
252
273
|
var vendor = msg.vendor || proposal.recommendedVendor;
|
|
253
274
|
var model = msg.model || "";
|
|
254
275
|
if (options.installedVendors.indexOf(vendor) === -1) throw new Error("Selected Worker vendor is not installed");
|
|
255
276
|
if (!modelIsAvailable(options, vendor, model)) throw new Error("Selected Worker model is unavailable");
|
|
256
277
|
var effort = yoke.clampEffort(vendor, msg.effort || proposal.recommendedEffort || "medium") || "";
|
|
257
|
-
|
|
278
|
+
var startingPatch = { status: "starting", selectedVendor: vendor, selectedModel: model, selectedEffort: effort };
|
|
279
|
+
if (msg.autoApproved) startingPatch.autoApproved = true;
|
|
280
|
+
updateProposal(session, proposal, startingPatch);
|
|
258
281
|
try {
|
|
259
282
|
var created = ctx.createPairRecord(ws, {
|
|
260
283
|
driver: { sessionId: session.localId },
|
|
261
284
|
worker: { vendor: vendor, model: model, effort: effort },
|
|
262
285
|
});
|
|
263
286
|
updateProposal(session, proposal, { status: "running", groupId: created.group.id, workerId: created.worker.localId });
|
|
264
|
-
|
|
287
|
+
if (ws._autoApproval) sm.sendToSession(session, { type: "pair_session_created", ok: true, group: created.group });
|
|
288
|
+
else ctx.sendTo(ws, { type: "pair_session_created", ok: true, group: created.group });
|
|
265
289
|
runWorker(session, proposal).catch(function (err) {
|
|
266
290
|
updateProposal(session, proposal, { status: "error", error: err.message || String(err) });
|
|
267
291
|
resumeDriver(session, "[Worker execution failed]\n" + (err.message || String(err))).catch(function () {});
|
|
@@ -273,6 +297,19 @@ function attachWorkerProposal(ctx) {
|
|
|
273
297
|
}
|
|
274
298
|
}
|
|
275
299
|
|
|
300
|
+
async function respondToProposal(ws, msg) {
|
|
301
|
+
var session = sessionForResponse(ws);
|
|
302
|
+
var proposal = findProposal(session, msg.proposalId);
|
|
303
|
+
if (!proposal) throw new Error("Worker suggestion not found");
|
|
304
|
+
if (proposal.status !== "pending") throw new Error("Worker suggestion has already been resolved");
|
|
305
|
+
if (!msg.accepted) {
|
|
306
|
+
updateProposal(session, proposal, { status: "declined" });
|
|
307
|
+
await resumeDriver(session, "[Worker suggestion declined]\nContinue this task in the current session using the plan you already prepared.");
|
|
308
|
+
return { ok: true, status: "declined" };
|
|
309
|
+
}
|
|
310
|
+
return acceptProposal(session, proposal, msg, ws);
|
|
311
|
+
}
|
|
312
|
+
|
|
276
313
|
function handleMessage(ws, msg) {
|
|
277
314
|
if (msg.type !== "worker_proposal_response") return false;
|
|
278
315
|
respondToProposal(ws, msg).catch(function (err) {
|
package/lib/project.js
CHANGED
|
@@ -27,6 +27,7 @@ var { attachImage } = require("./project-image");
|
|
|
27
27
|
var { attachKnowledge } = require("./project-knowledge");
|
|
28
28
|
var { attachFilesystem } = require("./project-filesystem");
|
|
29
29
|
var { attachSessions } = require("./project-sessions");
|
|
30
|
+
var { attachModels } = require("./project-models");
|
|
30
31
|
var { attachUserMessage } = require("./project-user-message");
|
|
31
32
|
var { attachShellCommand } = require("./project-shell-command");
|
|
32
33
|
var { attachConnection } = require("./project-connection");
|
|
@@ -35,6 +36,7 @@ var { createLocalMcp } = require("./mcp-local");
|
|
|
35
36
|
var { attachEmail: attachEmailModule } = require("./project-email");
|
|
36
37
|
var { attachSessionSpawn } = require("./project-session-spawn");
|
|
37
38
|
var { attachSessionPair } = require("./project-session-pair");
|
|
39
|
+
var { attachSessionHandoff } = require("./project-session-handoff");
|
|
38
40
|
var { attachSessionNotes, composeSystemPrompts } = require("./project-session-notes");
|
|
39
41
|
var { attachSessionDocument } = require("./project-session-document");
|
|
40
42
|
var { attachSplitGroups } = require("./session-split-groups");
|
|
@@ -470,10 +472,9 @@ function createProjectContext(opts) {
|
|
|
470
472
|
|
|
471
473
|
var _projModel = typeof opts.onGetProjectDefaultModel === "function" ? opts.onGetProjectDefaultModel(slug) : null;
|
|
472
474
|
var _srvModel = typeof opts.onGetServerDefaultModel === "function" ? opts.onGetServerDefaultModel() : null;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (sm._savedDefaultModel) sm.currentModel = sm._savedDefaultModel;
|
|
475
|
+
var savedDefaultModel = (_projModel && _projModel.model) || (_srvModel && _srvModel.model) || null;
|
|
476
|
+
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
477
|
+
if (savedDefaultModel) sm.defaultModelByVendor[sm.defaultVendor || "claude"] = savedDefaultModel;
|
|
477
478
|
|
|
478
479
|
// --- Local MCP (direct process management for localhost clients) ---
|
|
479
480
|
var _localMcp = createLocalMcp();
|
|
@@ -569,6 +570,18 @@ function createProjectContext(opts) {
|
|
|
569
570
|
getLinuxUserForSession: getLinuxUserForSession,
|
|
570
571
|
getPairToolDefs: function (boundSession) { return _sessionPair.getToolDefs(boundSession); },
|
|
571
572
|
});
|
|
573
|
+
var _sessionHandoff = attachSessionHandoff({
|
|
574
|
+
cwd: cwd,
|
|
575
|
+
sm: sm,
|
|
576
|
+
isMate: isMate,
|
|
577
|
+
splitStore: _splitGroups.store,
|
|
578
|
+
getSdk: function () { return sdk; },
|
|
579
|
+
sendTo: sendTo,
|
|
580
|
+
usersModule: usersModule,
|
|
581
|
+
adapters: adapters,
|
|
582
|
+
getLinuxUserForSession: getLinuxUserForSession,
|
|
583
|
+
onProcessingChanged: onProcessingChanged,
|
|
584
|
+
});
|
|
572
585
|
var _debate = null;
|
|
573
586
|
var _debateProposal = attachDebateProposal({
|
|
574
587
|
cwd: cwd,
|
|
@@ -607,6 +620,16 @@ function createProjectContext(opts) {
|
|
|
607
620
|
}
|
|
608
621
|
}
|
|
609
622
|
|
|
623
|
+
// Session-bound access to the source of a handoff (main projects only).
|
|
624
|
+
if (!isMate) {
|
|
625
|
+
try {
|
|
626
|
+
var sessionHandoffMcpConfig = _sessionHandoff.createMcpServer(adapter);
|
|
627
|
+
if (sessionHandoffMcpConfig) servers[sessionHandoffMcpConfig.name || "clay-handoff"] = sessionHandoffMcpConfig;
|
|
628
|
+
} catch (e) {
|
|
629
|
+
console.error("[project] Failed to create session handoff MCP server:", e.message);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
610
633
|
// Explicit agent signal for user-requested Markdown presentation.
|
|
611
634
|
if (!isMate) {
|
|
612
635
|
try {
|
|
@@ -767,7 +790,7 @@ function createProjectContext(opts) {
|
|
|
767
790
|
// clay-email -> only when the user has an account or server SMTP
|
|
768
791
|
//
|
|
769
792
|
// forSession (optional): the session whose query these servers are mounted
|
|
770
|
-
// into. Session, notes, and document tools must know their caller, so they
|
|
793
|
+
// into. Session, notes, handoff, and document tools must know their caller, so they
|
|
771
794
|
// are re-instantiated bound to that session; static instances only serve
|
|
772
795
|
// descriptor listing and fail closed on calls.
|
|
773
796
|
function getLocalMcpServers(forSession) {
|
|
@@ -799,6 +822,15 @@ function createProjectContext(opts) {
|
|
|
799
822
|
}
|
|
800
823
|
continue;
|
|
801
824
|
}
|
|
825
|
+
if (name === "clay-handoff" && forSession) {
|
|
826
|
+
try {
|
|
827
|
+
var boundHandoff = _sessionHandoff.createMcpServer(adapter, forSession);
|
|
828
|
+
if (boundHandoff) { filtered[name] = boundHandoff; hasAny = true; }
|
|
829
|
+
} catch (e) {
|
|
830
|
+
console.error("[project] Failed to bind session handoff MCP server:", e.message);
|
|
831
|
+
}
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
802
834
|
if (name === "clay-documents" && forSession) {
|
|
803
835
|
try {
|
|
804
836
|
var boundDocuments = _sessionDocument.createMcpServer(adapter, forSession);
|
|
@@ -866,12 +898,14 @@ function createProjectContext(opts) {
|
|
|
866
898
|
return composeSystemPrompts([
|
|
867
899
|
_sessionPair.getSystemPrompt(session),
|
|
868
900
|
_sessionNotes.getSystemPrompt(session),
|
|
901
|
+
_sessionHandoff.getSystemPrompt(session),
|
|
869
902
|
_sessionDocument.getSystemPrompt(session),
|
|
870
903
|
]);
|
|
871
904
|
},
|
|
872
905
|
getSessionToolDefs: function (session) {
|
|
873
906
|
return _sessionPair.getToolDefs(session)
|
|
874
907
|
.concat(_sessionNotes.getToolDefs(session))
|
|
908
|
+
.concat(_sessionHandoff.getToolDefs(session))
|
|
875
909
|
.concat(_sessionDocument.getToolDefs(session));
|
|
876
910
|
},
|
|
877
911
|
});
|
|
@@ -1053,79 +1087,6 @@ function createProjectContext(opts) {
|
|
|
1053
1087
|
return;
|
|
1054
1088
|
}
|
|
1055
1089
|
|
|
1056
|
-
// --- Vendor model switching ---
|
|
1057
|
-
if (msg.type === "get_vendor_models") {
|
|
1058
|
-
(async function() {
|
|
1059
|
-
if (msg.vendor) {
|
|
1060
|
-
try {
|
|
1061
|
-
var modelLinuxUser = getLinuxUserForWs(ws);
|
|
1062
|
-
var vendorAdapter = adapters[msg.vendor] || null;
|
|
1063
|
-
if (!vendorAdapter) {
|
|
1064
|
-
vendorAdapter = await yoke.lazyCreateAdapter(adapters, msg.vendor, {
|
|
1065
|
-
cwd: cwd,
|
|
1066
|
-
linuxUser: modelLinuxUser || undefined,
|
|
1067
|
-
clayPort: serverPort,
|
|
1068
|
-
clayTls: serverTls,
|
|
1069
|
-
clayAuthToken: serverAuthToken,
|
|
1070
|
-
slug: slug,
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
var needsReadyMetadata = !sm.capabilitiesByVendor || !sm.capabilitiesByVendor[msg.vendor]
|
|
1074
|
-
|| !sm.modelsByVendor || !sm.modelsByVendor[msg.vendor];
|
|
1075
|
-
if (vendorAdapter && needsReadyMetadata && typeof vendorAdapter.init === "function") {
|
|
1076
|
-
// Init warms the adapter, but a slow/failed init must not block
|
|
1077
|
-
// model listing (e.g. Codex models are a fixed list). Keep going
|
|
1078
|
-
// to supportedModels() even if init throws.
|
|
1079
|
-
try {
|
|
1080
|
-
var readyResult = await vendorAdapter.init({
|
|
1081
|
-
cwd: cwd,
|
|
1082
|
-
linuxUser: modelLinuxUser || undefined,
|
|
1083
|
-
clayPort: serverPort,
|
|
1084
|
-
clayTls: serverTls,
|
|
1085
|
-
clayAuthToken: serverAuthToken,
|
|
1086
|
-
slug: slug,
|
|
1087
|
-
});
|
|
1088
|
-
sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
|
|
1089
|
-
sm.capabilitiesByVendor[msg.vendor] = readyResult.capabilities || {};
|
|
1090
|
-
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
1091
|
-
if (Array.isArray(readyResult.models)) sm.modelsByVendor[msg.vendor] = readyResult.models;
|
|
1092
|
-
} catch (e) {
|
|
1093
|
-
console.error("[project] " + msg.vendor + " init failed (continuing to model list):", e.message || e);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
if (vendorAdapter) {
|
|
1097
|
-
sm.availableVendors = Object.keys(adapters);
|
|
1098
|
-
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
1099
|
-
if (!sm.modelsByVendor[msg.vendor] && typeof vendorAdapter.supportedModels === "function") {
|
|
1100
|
-
sm.modelsByVendor[msg.vendor] = await vendorAdapter.supportedModels();
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
} catch (e) {
|
|
1104
|
-
console.error("[project] get_vendor_models lazy init failed for " + msg.vendor + ":", e.message || e);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[msg.vendor]) || [];
|
|
1108
|
-
var firstModel = vendorModels[0] || "";
|
|
1109
|
-
// model value can be string or {value, displayName} object
|
|
1110
|
-
var defaultModel = typeof firstModel === "string" ? firstModel : (firstModel.value || "");
|
|
1111
|
-
// Preserve the user's current model selection if it belongs to this
|
|
1112
|
-
// vendor, rather than always snapping back to the vendor's default.
|
|
1113
|
-
var modelToSend = defaultModel;
|
|
1114
|
-
if (sm.currentModel) {
|
|
1115
|
-
for (var mi = 0; mi < vendorModels.length; mi++) {
|
|
1116
|
-
var mv = typeof vendorModels[mi] === "string" ? vendorModels[mi] : (vendorModels[mi].value || "");
|
|
1117
|
-
if (mv === sm.currentModel) {
|
|
1118
|
-
modelToSend = sm.currentModel;
|
|
1119
|
-
break;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
var vendorCapabilities = (sm.capabilitiesByVendor && sm.capabilitiesByVendor[msg.vendor]) || {};
|
|
1124
|
-
sendTo(ws, { type: "model_info", model: modelToSend, models: vendorModels, vendor: msg.vendor, capabilities: vendorCapabilities, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
|
|
1125
|
-
})();
|
|
1126
|
-
return;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
1090
|
// --- Debate ---
|
|
1130
1091
|
if (msg.type === "debate_start") {
|
|
1131
1092
|
handleDebateStart(ws, msg);
|
|
@@ -1173,7 +1134,9 @@ function createProjectContext(opts) {
|
|
|
1173
1134
|
|
|
1174
1135
|
// --- Sessions, config, project mgmt (delegated to project-sessions.js) ---
|
|
1175
1136
|
if (_sessionPair.handleMessage(ws, msg)) return;
|
|
1137
|
+
if (_sessionHandoff.handleMessage(ws, msg)) return;
|
|
1176
1138
|
if (_splitGroups.handleMessage(ws, msg)) return;
|
|
1139
|
+
if (_models.handleMessage(ws, msg)) return;
|
|
1177
1140
|
if (_sessions.handleSessionsMessage(ws, msg)) return;
|
|
1178
1141
|
|
|
1179
1142
|
// --- Filesystem, settings, env (delegated to project-filesystem.js) ---
|
|
@@ -1386,6 +1349,20 @@ function createProjectContext(opts) {
|
|
|
1386
1349
|
_notifications: _notifications,
|
|
1387
1350
|
});
|
|
1388
1351
|
|
|
1352
|
+
var _models = attachModels({
|
|
1353
|
+
cwd: cwd,
|
|
1354
|
+
slug: slug,
|
|
1355
|
+
sm: sm,
|
|
1356
|
+
sdk: sdk,
|
|
1357
|
+
adapters: adapters,
|
|
1358
|
+
sendTo: sendTo,
|
|
1359
|
+
getSessionForWs: getSessionForWs,
|
|
1360
|
+
getLinuxUserForWs: getLinuxUserForWs,
|
|
1361
|
+
serverPort: serverPort,
|
|
1362
|
+
serverTls: serverTls,
|
|
1363
|
+
serverAuthToken: serverAuthToken,
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1389
1366
|
// --- User message handler (delegated to project-user-message.js) ---
|
|
1390
1367
|
var _userMessage = attachUserMessage({
|
|
1391
1368
|
cwd: cwd,
|
|
@@ -1533,6 +1510,15 @@ function createProjectContext(opts) {
|
|
|
1533
1510
|
inputSchema: normalizeToolSchema(noteTools[nti].inputSchema),
|
|
1534
1511
|
});
|
|
1535
1512
|
}
|
|
1513
|
+
var handoffTools = _sessionHandoff.getToolDefs(boundSession);
|
|
1514
|
+
for (var hti = 0; hti < handoffTools.length; hti++) {
|
|
1515
|
+
tools.push({
|
|
1516
|
+
server: "clay-handoff",
|
|
1517
|
+
name: handoffTools[hti].name,
|
|
1518
|
+
description: handoffTools[hti].description || handoffTools[hti].name,
|
|
1519
|
+
inputSchema: normalizeToolSchema(handoffTools[hti].inputSchema),
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1536
1522
|
var documentTools = _sessionDocument.getToolDefs(boundSession);
|
|
1537
1523
|
for (var dti = 0; dti < documentTools.length; dti++) {
|
|
1538
1524
|
tools.push({
|
|
@@ -1553,7 +1539,7 @@ function createProjectContext(opts) {
|
|
|
1553
1539
|
if (localMcp) {
|
|
1554
1540
|
var inAppNames = Object.keys(localMcp);
|
|
1555
1541
|
for (var i = 0; i < inAppNames.length; i++) {
|
|
1556
|
-
if (inAppNames[i] === "clay-sessions" || inAppNames[i] === "clay-notes" || inAppNames[i] === "clay-documents") continue;
|
|
1542
|
+
if (inAppNames[i] === "clay-sessions" || inAppNames[i] === "clay-notes" || inAppNames[i] === "clay-handoff" || inAppNames[i] === "clay-documents") continue;
|
|
1557
1543
|
extractServerTools(inAppNames[i], localMcp[inAppNames[i]]);
|
|
1558
1544
|
}
|
|
1559
1545
|
}
|
|
@@ -1588,6 +1574,14 @@ function createProjectContext(opts) {
|
|
|
1588
1574
|
}
|
|
1589
1575
|
}
|
|
1590
1576
|
}
|
|
1577
|
+
if (boundSession && serverName === "clay-handoff") {
|
|
1578
|
+
var handoffTools = _sessionHandoff.getToolDefs(boundSession);
|
|
1579
|
+
for (var hti = 0; hti < handoffTools.length; hti++) {
|
|
1580
|
+
if (handoffTools[hti].name === toolName && typeof handoffTools[hti].handler === "function") {
|
|
1581
|
+
return Promise.resolve(handoffTools[hti].handler(args || {}));
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1591
1585
|
if (boundSession && serverName === "clay-documents") {
|
|
1592
1586
|
var documentTools = _sessionDocument.getToolDefs(boundSession);
|
|
1593
1587
|
for (var dti = 0; dti < documentTools.length; dti++) {
|
|
@@ -1596,7 +1590,7 @@ function createProjectContext(opts) {
|
|
|
1596
1590
|
}
|
|
1597
1591
|
}
|
|
1598
1592
|
}
|
|
1599
|
-
if (serverName === "clay-sessions" || serverName === "clay-notes" || serverName === "clay-documents") {
|
|
1593
|
+
if (serverName === "clay-sessions" || serverName === "clay-notes" || serverName === "clay-handoff" || serverName === "clay-documents") {
|
|
1600
1594
|
return Promise.reject(new Error("Session-bound tool requires a valid Clay session: " + serverName + "/" + toolName));
|
|
1601
1595
|
}
|
|
1602
1596
|
if (sessionOnly) return Promise.reject(new Error("Session tool not found: " + serverName + "/" + toolName));
|