clay-server 4.0.0 → 4.1.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-project-access.js +118 -0
- package/lib/daemon-projects.js +19 -5
- package/lib/daemon.js +19 -18
- package/lib/project-logs-access-scope.js +94 -0
- package/lib/project-logs-query.js +3 -0
- package/lib/project-logs-service.js +86 -84
- package/lib/project-logs-store.js +5 -3
- package/lib/project-pair-lifecycle.js +71 -61
- package/lib/project-pair-message.js +50 -0
- package/lib/project-pair-replacement-state.js +72 -0
- package/lib/project-pair-task-control.js +264 -0
- package/lib/project-pair-usage.js +73 -0
- package/lib/project-session-pair.js +115 -116
- package/lib/project-sessions.js +40 -6
- package/lib/project-worker-proposal.js +54 -57
- package/lib/project.js +27 -4
- package/lib/public/app.js +1 -0
- package/lib/public/css/command-palette.css +26 -0
- package/lib/public/css/icon-strip.css +40 -0
- package/lib/public/index.html +2 -2
- package/lib/public/modules/app-message-router.js +1 -1
- package/lib/public/modules/app-messages.js +9 -0
- package/lib/public/modules/project-access.js +141 -0
- package/lib/public/modules/project-grouping.js +22 -0
- package/lib/public/modules/search-clay-chat.js +47 -6
- package/lib/public/modules/search-clay-controls.js +86 -0
- package/lib/public/modules/sidebar-projects.js +14 -171
- package/lib/public/modules/worker-proposal.js +4 -0
- package/lib/public/style.css +1 -1
- package/lib/sdk-bridge.js +21 -1
- package/lib/server-admin.js +3 -3
- package/lib/server-global-ws.js +10 -0
- package/lib/server-home-chat-events.js +3 -0
- package/lib/server-home-chat.js +35 -0
- package/lib/server-mates.js +4 -3
- package/lib/server-palette.js +17 -14
- package/lib/server.js +184 -102
- package/lib/session-pair-factory.js +3 -0
- package/lib/session-pair-history.js +40 -0
- package/lib/session-pair-mcp-server.js +12 -1
- package/lib/session-pair-operation.js +11 -0
- package/lib/session-pair-prompts.js +24 -2
- package/lib/session-pair-turn-control.js +8 -3
- package/lib/worker-proposal-control.js +69 -0
- package/lib/worker-proposal-decision.js +7 -0
- package/lib/worker-runtime-catalog.js +52 -0
- package/lib/workspace-query-access.js +3 -2
- package/lib/workspace-query-service.js +1 -1
- package/lib/worktree.js +8 -4
- package/lib/ws-schema.js +7 -0
- package/lib/yoke/adapters/codex.js +6 -2
- package/package.json +1 -1
|
@@ -3,10 +3,9 @@ var yoke = require("./yoke");
|
|
|
3
3
|
var buildShape = require("./session-spawn-mcp-server").buildShape;
|
|
4
4
|
var driverEligibility = require("./session-driver-eligibility");
|
|
5
5
|
var driverOrchestration = require("./session-driver-orchestration");
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var MAX_TASK_CHARS = 30000;
|
|
9
|
-
var MAX_RATIONALE_CHARS = 1000;
|
|
6
|
+
var proposalControl = require("./worker-proposal-control");
|
|
7
|
+
var runtimeCatalog = require("./worker-runtime-catalog");
|
|
8
|
+
var MAX_SUMMARY_CHARS = 600, MAX_PLAN_CHARS = 6000, MAX_TASK_CHARS = 30000, MAX_RATIONALE_CHARS = 1000;
|
|
10
9
|
function modelValue(entry) {
|
|
11
10
|
if (typeof entry === "string") return entry;
|
|
12
11
|
return entry && (entry.value || entry.id) || "";
|
|
@@ -15,16 +14,14 @@ function modelLabel(entry) {
|
|
|
15
14
|
if (typeof entry === "string") return entry;
|
|
16
15
|
return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
|
|
17
16
|
}
|
|
18
|
-
function toolResult(value) {
|
|
19
|
-
return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
|
|
20
|
-
}
|
|
17
|
+
function toolResult(value) { return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] }); }
|
|
21
18
|
function attachWorkerProposal(ctx) {
|
|
22
19
|
var sm = ctx.sm;
|
|
23
20
|
var store = ctx.splitStore;
|
|
21
|
+
var catalogErrors = {};
|
|
24
22
|
function isLiveSession(session) {
|
|
25
23
|
return !!session && sm.sessions.get(session.localId) === session;
|
|
26
24
|
}
|
|
27
|
-
|
|
28
25
|
function canOffer(session) { return !ctx.isMate && isLiveSession(session) && driverEligibility.isEligibleDriverSession(session, sm); }
|
|
29
26
|
function isEligible(session) {
|
|
30
27
|
return canOffer(session) && !store.groupForMember(session.localId);
|
|
@@ -45,7 +42,6 @@ function attachWorkerProposal(ctx) {
|
|
|
45
42
|
}
|
|
46
43
|
return result;
|
|
47
44
|
}
|
|
48
|
-
|
|
49
45
|
function proposalOptions() {
|
|
50
46
|
var installed = (sm.installedVendors || []).slice();
|
|
51
47
|
var capabilities = {};
|
|
@@ -59,9 +55,10 @@ function attachWorkerProposal(ctx) {
|
|
|
59
55
|
installedVendors: installed,
|
|
60
56
|
modelsByVendor: safeModelsByVendor(installed),
|
|
61
57
|
capabilitiesByVendor: capabilities,
|
|
58
|
+
unavailableReasons: Object.assign({}, catalogErrors),
|
|
59
|
+
availableVendors: Array.isArray(sm.availableVendors) ? sm.availableVendors.slice() : null,
|
|
62
60
|
};
|
|
63
61
|
}
|
|
64
|
-
|
|
65
62
|
async function ensureModelCatalogs() {
|
|
66
63
|
var installed = sm.installedVendors || [];
|
|
67
64
|
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
@@ -70,11 +67,10 @@ function attachWorkerProposal(ctx) {
|
|
|
70
67
|
if (sm.modelsByVendor[vendor] && sm.modelsByVendor[vendor].length > 0) continue;
|
|
71
68
|
var adapter = ctx.adapters && ctx.adapters[vendor];
|
|
72
69
|
if (!adapter || typeof adapter.supportedModels !== "function") continue;
|
|
73
|
-
try { sm.modelsByVendor[vendor] = await adapter.supportedModels(); }
|
|
74
|
-
catch (err) { console.warn("[worker-proposal] Could not load " + vendor + " models:",
|
|
70
|
+
try { sm.modelsByVendor[vendor] = await adapter.supportedModels(); delete catalogErrors[vendor]; }
|
|
71
|
+
catch (err) { catalogErrors[vendor] = err.message || String(err); console.warn("[worker-proposal] Could not load " + vendor + " models:", catalogErrors[vendor]); }
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
|
-
|
|
78
74
|
function modelIsAvailable(options, vendor, model) {
|
|
79
75
|
if (!model) return true;
|
|
80
76
|
var models = options.modelsByVendor[vendor] || [];
|
|
@@ -83,7 +79,6 @@ function attachWorkerProposal(ctx) {
|
|
|
83
79
|
}
|
|
84
80
|
return false;
|
|
85
81
|
}
|
|
86
|
-
|
|
87
82
|
function effortIsAvailable(options, vendor, model, effort) {
|
|
88
83
|
var capabilities = options.capabilitiesByVendor[vendor] || {};
|
|
89
84
|
if (capabilities.effort === false) return !effort;
|
|
@@ -97,7 +92,6 @@ function attachWorkerProposal(ctx) {
|
|
|
97
92
|
}
|
|
98
93
|
return true;
|
|
99
94
|
}
|
|
100
|
-
|
|
101
95
|
function chooseRecommendation(args, session, options) {
|
|
102
96
|
var installed = options.installedVendors;
|
|
103
97
|
var vendor = args.recommendedVendor;
|
|
@@ -125,12 +119,10 @@ function attachWorkerProposal(ctx) {
|
|
|
125
119
|
var effort = yoke.clampEffort(vendor, args.recommendedEffort || "medium") || "";
|
|
126
120
|
return { vendor: vendor, model: model, effort: effort };
|
|
127
121
|
}
|
|
128
|
-
|
|
129
122
|
function skipPermissionsEnabled(session) {
|
|
130
123
|
return !!(session && (session.permissionMode === "bypassPermissions" ||
|
|
131
124
|
session.dangerouslySkipPermissions || ctx.dangerouslySkipPermissions));
|
|
132
125
|
}
|
|
133
|
-
|
|
134
126
|
function recommendationCanAutoAccept(args, recommendation, options) {
|
|
135
127
|
var requestedVendor = typeof args.recommendedVendor === "string" ? args.recommendedVendor.trim() : "";
|
|
136
128
|
var requestedModel = typeof args.recommendedModel === "string" ? args.recommendedModel.trim() : "";
|
|
@@ -143,32 +135,12 @@ function attachWorkerProposal(ctx) {
|
|
|
143
135
|
return requestedEffort === recommendation.effort &&
|
|
144
136
|
effortIsAvailable(options, requestedVendor, requestedModel, requestedEffort);
|
|
145
137
|
}
|
|
146
|
-
|
|
147
138
|
function autoAcceptanceWs(session) {
|
|
148
139
|
return {
|
|
149
140
|
_clayActiveSession: session.localId,
|
|
150
141
|
_clayUser: session.ownerId ? { id: session.ownerId } : null,
|
|
151
142
|
};
|
|
152
143
|
}
|
|
153
|
-
|
|
154
|
-
function findProposal(session, proposalId) {
|
|
155
|
-
var history = (session && session.history) || [];
|
|
156
|
-
for (var i = history.length - 1; i >= 0; i--) {
|
|
157
|
-
if (history[i] && history[i].type === "worker_proposal" && history[i].proposalId === proposalId) return history[i];
|
|
158
|
-
}
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function hasPendingProposal(session) {
|
|
163
|
-
var history = (session && session.history) || [];
|
|
164
|
-
for (var i = history.length - 1; i >= 0; i--) {
|
|
165
|
-
var item = history[i];
|
|
166
|
-
if (!item || item.type !== "worker_proposal") continue;
|
|
167
|
-
return item.status === "pending" || item.status === "starting" || item.status === "running";
|
|
168
|
-
}
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
144
|
function updateProposal(session, proposal, patch) {
|
|
173
145
|
Object.assign(proposal, patch, { updatedAt: Date.now() });
|
|
174
146
|
sm.saveSessionFile(session);
|
|
@@ -177,10 +149,13 @@ function attachWorkerProposal(ctx) {
|
|
|
177
149
|
proposalId: proposal.proposalId,
|
|
178
150
|
}, patch));
|
|
179
151
|
}
|
|
180
|
-
|
|
181
152
|
async function propose(args, session) {
|
|
182
153
|
if (!isEligible(session)) return toolResult({ error: "Split Worker proposals are only available in an eligible unpaired Driver session." });
|
|
183
|
-
|
|
154
|
+
var superseded = proposalControl.pendingProposal(session);
|
|
155
|
+
var supersedeId = typeof args.supersedeProposalId === "string" ? args.supersedeProposalId.trim() : "";
|
|
156
|
+
if (superseded && superseded.proposalId !== supersedeId) return toolResult({ error: "A Split Worker suggestion is already awaiting a decision. Inspect it or supply its exact id to supersede it." });
|
|
157
|
+
if (!superseded && supersedeId) return toolResult({ error: "The Split Worker suggestion to supersede is no longer pending." });
|
|
158
|
+
if (superseded && superseded.status !== "pending") return toolResult({ error: "The Split Worker suggestion is already starting and cannot be superseded." });
|
|
184
159
|
var summary = typeof args.summary === "string" ? args.summary.trim() : "";
|
|
185
160
|
var plan = typeof args.plan === "string" ? args.plan.trim() : "";
|
|
186
161
|
var task = typeof args.message === "string" ? args.message.trim() : "";
|
|
@@ -193,6 +168,8 @@ function attachWorkerProposal(ctx) {
|
|
|
193
168
|
if (!isEligible(session)) {
|
|
194
169
|
return toolResult({ error: "The Driver session changed while Split Worker runtimes were loading." });
|
|
195
170
|
}
|
|
171
|
+
var currentPending = proposalControl.pendingProposal(session);
|
|
172
|
+
if (currentPending !== superseded) return toolResult({ error: "The pending Split Worker suggestion changed while runtimes were loading." });
|
|
196
173
|
var options = proposalOptions();
|
|
197
174
|
if (options.installedVendors.length === 0) return toolResult({ error: "No coding agent is installed for a Split Worker session." });
|
|
198
175
|
var recommendation = chooseRecommendation(args, session, options);
|
|
@@ -203,12 +180,14 @@ function attachWorkerProposal(ctx) {
|
|
|
203
180
|
plan: plan.slice(0, MAX_PLAN_CHARS),
|
|
204
181
|
message: task,
|
|
205
182
|
status: "pending",
|
|
183
|
+
createdAt: Date.now(),
|
|
206
184
|
recommendedVendor: recommendation.vendor,
|
|
207
185
|
recommendedModel: recommendation.model,
|
|
208
186
|
recommendedEffort: recommendation.effort,
|
|
209
187
|
recommendationRationale: rationale.slice(0, MAX_RATIONALE_CHARS),
|
|
210
188
|
options: options,
|
|
211
189
|
};
|
|
190
|
+
if (superseded) updateProposal(session, superseded, { status: "superseded", supersededBy: proposal.proposalId });
|
|
212
191
|
sm.sendAndRecord(session, proposal);
|
|
213
192
|
if (skipPermissionsEnabled(session) && recommendationCanAutoAccept(args, recommendation, options)) {
|
|
214
193
|
var accepted = await acceptProposal(session, proposal, {
|
|
@@ -230,7 +209,6 @@ function attachWorkerProposal(ctx) {
|
|
|
230
209
|
instruction: "The Split Worker suggestion is visible in the chat. End this turn now and wait for the user's decision.",
|
|
231
210
|
});
|
|
232
211
|
}
|
|
233
|
-
|
|
234
212
|
async function proposeReplacement(args, session) {
|
|
235
213
|
var group = session && store.groupForMember(session.localId);
|
|
236
214
|
if (ctx.isMate || !isLiveSession(session) || !driverEligibility.isEligibleDriverSession(session, sm) || !group ||
|
|
@@ -239,7 +217,11 @@ function attachWorkerProposal(ctx) {
|
|
|
239
217
|
}
|
|
240
218
|
var sourceGroupId = group.id;
|
|
241
219
|
var sourceWorkerId = group.pair.workerId;
|
|
242
|
-
|
|
220
|
+
var superseded = proposalControl.pendingProposal(session);
|
|
221
|
+
var supersedeId = typeof args.supersedeProposalId === "string" ? args.supersedeProposalId.trim() : "";
|
|
222
|
+
if (superseded && superseded.proposalId !== supersedeId) return toolResult({ error: "A Split Worker proposal is already awaiting a decision. Inspect it or supply its exact id to supersede it." });
|
|
223
|
+
if (!superseded && supersedeId) return toolResult({ error: "The Split Worker proposal to supersede is no longer pending." });
|
|
224
|
+
if (superseded && superseded.status !== "pending") return toolResult({ error: "The Split Worker proposal is already starting and cannot be superseded." });
|
|
243
225
|
var task = typeof args.message === "string" ? args.message.trim() : "";
|
|
244
226
|
var rationale = typeof args.recommendationRationale === "string" ? args.recommendationRationale.trim() : "";
|
|
245
227
|
if (!task) return toolResult({ error: "message is required so an accepted replacement delegates exactly once." });
|
|
@@ -252,6 +234,8 @@ function attachWorkerProposal(ctx) {
|
|
|
252
234
|
liveGroup.pair.driverId !== session.localId || liveGroup.pair.workerId !== sourceWorkerId) {
|
|
253
235
|
return toolResult({ error: "The Driver/Split Worker pair changed while replacement runtimes were loading." });
|
|
254
236
|
}
|
|
237
|
+
var currentPending = proposalControl.pendingProposal(session);
|
|
238
|
+
if (currentPending !== superseded) return toolResult({ error: "The pending Split Worker proposal changed while runtimes were loading." });
|
|
255
239
|
var options = proposalOptions();
|
|
256
240
|
if (options.installedVendors.length === 0) return toolResult({ error: "No coding agent is installed for a Split Worker session." });
|
|
257
241
|
var recommendationArgs = {
|
|
@@ -268,6 +252,7 @@ function attachWorkerProposal(ctx) {
|
|
|
268
252
|
plan: "1. Preserve the current Worker's session and history\n2. Create the selected replacement runtime\n3. Delegate the next task exactly once",
|
|
269
253
|
message: task,
|
|
270
254
|
status: "pending",
|
|
255
|
+
createdAt: Date.now(),
|
|
271
256
|
recommendedVendor: recommendation.vendor,
|
|
272
257
|
recommendedModel: recommendation.model,
|
|
273
258
|
recommendedEffort: recommendation.effort,
|
|
@@ -277,7 +262,9 @@ function attachWorkerProposal(ctx) {
|
|
|
277
262
|
sourceWorkerId: sourceWorkerId,
|
|
278
263
|
interrupt: args.interrupt === true,
|
|
279
264
|
evaluation: args.evaluation || null,
|
|
265
|
+
transactionId: "replace_" + crypto.randomUUID(),
|
|
280
266
|
};
|
|
267
|
+
if (superseded) updateProposal(session, superseded, { status: "superseded", supersededBy: proposal.proposalId });
|
|
281
268
|
sm.sendAndRecord(session, proposal);
|
|
282
269
|
if (skipPermissionsEnabled(session) && recommendationCanAutoAccept(recommendationArgs, recommendation, options)) {
|
|
283
270
|
var accepted = await acceptProposal(session, proposal, {
|
|
@@ -299,7 +286,6 @@ function attachWorkerProposal(ctx) {
|
|
|
299
286
|
instruction: "The replacement configuration is visible in the chat. End this turn now and wait for the user's decision.",
|
|
300
287
|
});
|
|
301
288
|
}
|
|
302
|
-
|
|
303
289
|
function resumeDriver(session, text) {
|
|
304
290
|
var sdk = ctx.getSdk();
|
|
305
291
|
if (!sdk) return Promise.reject(new Error("SDK bridge is not ready"));
|
|
@@ -335,7 +321,7 @@ function attachWorkerProposal(ctx) {
|
|
|
335
321
|
if (result.status === "complete") {
|
|
336
322
|
followup = "[Split Worker execution completed]\nReview and verify the Split Worker's result. The Split 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 Split Worker-owned files yourself. If that Split Worker is no longer available, keep the work in the visible Split Worker flow and use send_to_partner again after the stale pair is removed; never substitute a background Sub-agent.\n\n" + (result.response || "The Split Worker completed without a text summary.");
|
|
337
323
|
} else if (result.status === "interrupted") {
|
|
338
|
-
followup = "[Split Worker execution interrupted]\nThe
|
|
324
|
+
followup = "[Split Worker execution interrupted]\nThe Split Worker stopped mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Check partner_status before deciding next steps; this result alone does not identify who interrupted it. If the human stopped it, do not retry until a new human Driver message. Otherwise continue within the user's authorized task.";
|
|
339
325
|
} else if (result.status === "running") {
|
|
340
326
|
followup = "[Split Worker execution is still running]\nUse read_partner to inspect progress before completing the task.";
|
|
341
327
|
} else {
|
|
@@ -398,6 +384,7 @@ function attachWorkerProposal(ctx) {
|
|
|
398
384
|
workerModel: model,
|
|
399
385
|
workerEffort: effort,
|
|
400
386
|
evaluation: proposal.evaluation || undefined,
|
|
387
|
+
transactionId: proposal.transactionId,
|
|
401
388
|
}, session);
|
|
402
389
|
liveGroup = store.groupForMember(session.localId);
|
|
403
390
|
updateProposal(session, proposal, { status: "running", groupId: liveGroup.id, workerId: replaced.workerSessionId });
|
|
@@ -426,6 +413,7 @@ function attachWorkerProposal(ctx) {
|
|
|
426
413
|
restoredGroup.pair.workerId === proposal.sourceWorkerId) {
|
|
427
414
|
proposal.sourceGroupId = restoredGroup.id;
|
|
428
415
|
}
|
|
416
|
+
proposal.transactionId = "replace_" + crypto.randomUUID();
|
|
429
417
|
}
|
|
430
418
|
updateProposal(session, proposal, {
|
|
431
419
|
status: "pending",
|
|
@@ -440,7 +428,7 @@ function attachWorkerProposal(ctx) {
|
|
|
440
428
|
|
|
441
429
|
async function respondToProposal(ws, msg) {
|
|
442
430
|
var session = sessionForResponse(ws);
|
|
443
|
-
var proposal = findProposal(session, msg.proposalId);
|
|
431
|
+
var proposal = proposalControl.findProposal(session, msg.proposalId);
|
|
444
432
|
if (!proposal) throw new Error("Split Worker suggestion not found");
|
|
445
433
|
if (proposal.status !== "pending") throw new Error("Split Worker suggestion has already been resolved");
|
|
446
434
|
if (!msg.accepted) {
|
|
@@ -464,8 +452,9 @@ function attachWorkerProposal(ctx) {
|
|
|
464
452
|
|
|
465
453
|
function getToolDefs(session, options) {
|
|
466
454
|
var persistent = !!(options && options.persistent);
|
|
455
|
+
var controlsOnly = !!(options && options.controlsOnly);
|
|
467
456
|
if (!isEligible(session) && !(persistent && canOffer(session))) return [];
|
|
468
|
-
|
|
457
|
+
var proposalTools = controlsOnly ? [] : [{
|
|
469
458
|
name: "propose_worker",
|
|
470
459
|
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.",
|
|
471
460
|
inputSchema: buildShape({
|
|
@@ -476,25 +465,33 @@ function attachWorkerProposal(ctx) {
|
|
|
476
465
|
recommendedModel: { type: "string", description: "Optional exact Split Worker model id. Omit when uncertain." },
|
|
477
466
|
recommendedEffort: { type: "string", description: "Optional reasoning effort: minimal, low, medium, high, xhigh, or max." },
|
|
478
467
|
recommendationRationale: { type: "string", description: "Concise Driver-authored explanation of why the recommended vendor, model, and effort fit this exact task." },
|
|
468
|
+
supersedeProposalId: { type: "string", description: "Exact pending proposal id to resolve as superseded when posting this replacement suggestion." },
|
|
479
469
|
}, ["summary", "plan", "message", "recommendationRationale"]),
|
|
480
470
|
handler: function (args) { return propose(args || {}, session); },
|
|
481
471
|
}];
|
|
472
|
+
return proposalTools.concat(proposalControl.getToolDefs({
|
|
473
|
+
inspect: function () { return toolResult(isLiveSession(session) ? proposalControl.projection(proposalControl.pendingProposal(session)) : { status: "rejected", reason: "The Driver session is no longer live." }); },
|
|
474
|
+
cancel: function (args) {
|
|
475
|
+
if (!isLiveSession(session)) return toolResult({ status: "rejected", proposalId: args.proposalId || null, reason: "The Driver session is no longer live." });
|
|
476
|
+
var proposal = proposalControl.findProposal(session, args.proposalId);
|
|
477
|
+
if (!proposal || proposal.status !== "pending") return toolResult({ status: "rejected", proposalId: args.proposalId || null, reason: "The exact proposal is not pending." });
|
|
478
|
+
updateProposal(session, proposal, { status: "cancelled" });
|
|
479
|
+
return toolResult({ status: "cancelled", proposalId: proposal.proposalId });
|
|
480
|
+
},
|
|
481
|
+
catalog: function () { return ensureModelCatalogs().then(function () {
|
|
482
|
+
return toolResult(runtimeCatalog.build(proposalOptions(), skipPermissionsEnabled(session), function (vendor, model, effort) {
|
|
483
|
+
return ctx.preflightWorkerForDriver ? ctx.preflightWorkerForDriver(session, { workerVendor: vendor, workerModel: model, workerEffort: effort || "" }) : null;
|
|
484
|
+
}));
|
|
485
|
+
}); },
|
|
486
|
+
}));
|
|
482
487
|
}
|
|
483
488
|
|
|
484
489
|
function getSystemPrompt(session) {
|
|
485
490
|
if (!isEligible(session) || !driverOrchestration.isHighTierDriverSession(session, sm)) return "";
|
|
486
491
|
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.";
|
|
487
492
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
getSystemPrompt: getSystemPrompt,
|
|
492
|
-
proposeReplacement: proposeReplacement,
|
|
493
|
-
handleMessage: handleMessage,
|
|
494
|
-
respondToProposal: respondToProposal,
|
|
495
|
-
};
|
|
493
|
+
return { getToolDefs: getToolDefs, getSystemPrompt: getSystemPrompt, proposeReplacement: proposeReplacement,
|
|
494
|
+
handleMessage: handleMessage, respondToProposal: respondToProposal,
|
|
495
|
+
proposalStatus: function (session) { return proposalControl.projection(proposalControl.pendingProposal(session)); } };
|
|
496
496
|
}
|
|
497
|
-
|
|
498
|
-
module.exports = {
|
|
499
|
-
attachWorkerProposal: attachWorkerProposal,
|
|
500
|
-
};
|
|
497
|
+
module.exports = { attachWorkerProposal: attachWorkerProposal };
|
package/lib/project.js
CHANGED
|
@@ -390,14 +390,31 @@ function createProjectContext(opts) {
|
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
function send(obj) {
|
|
393
|
-
var data = JSON.stringify(obj);
|
|
394
393
|
for (var ws of clients) {
|
|
395
|
-
if (ws.readyState
|
|
394
|
+
if (ws.readyState !== 1) continue;
|
|
395
|
+
var payload = obj;
|
|
396
|
+
if (obj && obj.type === "info") {
|
|
397
|
+
var infoUserId = ws._clayUser ? ws._clayUser.id : null;
|
|
398
|
+
var visibleProjects = getProjectList(infoUserId);
|
|
399
|
+
payload = Object.assign({}, obj, { projects: visibleProjects, projectCount: visibleProjects.length });
|
|
400
|
+
} else if (obj && obj.type === "loop_registry_updated") {
|
|
401
|
+
var userId = ws._clayUser ? ws._clayUser.id : null;
|
|
402
|
+
payload = Object.assign({}, obj, { records: getHubSchedules(userId) });
|
|
403
|
+
}
|
|
404
|
+
ws.send(JSON.stringify(payload));
|
|
396
405
|
}
|
|
397
406
|
}
|
|
398
407
|
|
|
399
408
|
function sendTo(ws, obj) {
|
|
400
|
-
if (ws.readyState
|
|
409
|
+
if (ws.readyState !== 1) return;
|
|
410
|
+
var payload = obj;
|
|
411
|
+
if (obj && (obj.type === "loop_registry_updated" || obj.type === "hub_schedules")) {
|
|
412
|
+
var userId = ws._clayUser ? ws._clayUser.id : null;
|
|
413
|
+
payload = Object.assign({}, obj);
|
|
414
|
+
if (obj.type === "loop_registry_updated") payload.records = getHubSchedules(userId);
|
|
415
|
+
else payload.schedules = getHubSchedules(userId);
|
|
416
|
+
}
|
|
417
|
+
ws.send(JSON.stringify(payload));
|
|
401
418
|
}
|
|
402
419
|
|
|
403
420
|
function sendToAdmins(obj) {
|
|
@@ -1331,6 +1348,10 @@ function createProjectContext(opts) {
|
|
|
1331
1348
|
if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
|
|
1332
1349
|
return;
|
|
1333
1350
|
}
|
|
1351
|
+
if (msg.type === "home_mate_stop" || msg.type === "home_mate_permission_response") {
|
|
1352
|
+
if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1334
1355
|
if (msg.type === "project_assignment_response" || msg.type === "dm_open" || msg.type === "dm_send" || msg.type === "dm_list" || msg.type === "dm_typing" || msg.type === "dm_add_favorite" || msg.type === "dm_remove_favorite" || msg.type === "mate_create" || msg.type === "mate_list" || msg.type === "mate_delete" || msg.type === "mate_update" || msg.type === "mate_readd_builtin" || msg.type === "mate_list_available_builtins" || msg.type === "email_accounts_list" || msg.type === "email_account_add" || msg.type === "email_account_remove" || msg.type === "email_account_test" || msg.type === "home_debates_list" || msg.type === "home_mate_present" || msg.type === "home_mate_open" || msg.type === "home_mate_sessions_list" || msg.type === "home_mate_session_open" || msg.type === "home_mate_send" || msg.type === "home_mate_new_session" || msg.type === "home_mate_debate_plan" || msg.type === "home_debate_proposal_response" || msg.type === "home_mate_creation_plan" || msg.type === "home_mate_creation_proposal_response" || msg.type === "home_mate_close" || msg.type === "home_mate_memory_list" || msg.type === "home_mate_knowledge_list" || msg.type === "home_mate_models_get" || msg.type === "home_mate_model_set" || msg.type === "home_dock_get" || msg.type === "home_dock_set" || msg.type === "home_surface_get" || msg.type === "home_surface_set" || msg.type === "tools_list" || msg.type === "tool_get" || msg.type === "tool_install" || msg.type === "tool_remove" || msg.type === "tool_storage_op" || msg.type === "tool_llm_op" || msg.type === "tool_llm_config_get" || msg.type === "tool_control_response" || msg.type === "tool_source_get" || msg.type === "tool_mate_access_set" || msg.type === "tool_server_control" || msg.type === "tool_frame_url") {
|
|
1335
1356
|
if (typeof opts.onDmMessage === "function") {
|
|
1336
1357
|
opts.onDmMessage(ws, msg, slug);
|
|
@@ -2252,7 +2273,7 @@ function createProjectContext(opts) {
|
|
|
2252
2273
|
var _mateId = path.basename(cwd);
|
|
2253
2274
|
var _mateCtx = matesModule.buildMateCtx(projectOwnerId);
|
|
2254
2275
|
// Collect non-mate projects for project registry injection
|
|
2255
|
-
var _projectList = (getProjectList() || []).filter(function (p) { return !p.isMate; });
|
|
2276
|
+
var _projectList = (getProjectList(projectOwnerId) || []).filter(function (p) { return !p.isMate; });
|
|
2256
2277
|
var _enforceOpts = { ctx: _mateCtx, mateId: _mateId, projects: _projectList };
|
|
2257
2278
|
// Enforce all system sections atomically on startup (single read/write)
|
|
2258
2279
|
var _selfWrite = false; // suppress watcher when we wrote the file ourselves
|
|
@@ -2314,6 +2335,8 @@ function createProjectContext(opts) {
|
|
|
2314
2335
|
handleHomeDebateProposalResponse: function (ws, msg, session) { return _debateProposal.handleHomeMessage(ws, msg, session); },
|
|
2315
2336
|
handleHomeMateCreationProposalResponse: function (ws, msg, session) { return _mateCreationProposal.handleHomeMessage(ws, msg, session); },
|
|
2316
2337
|
handleHomeAskUserResponse: function (msg, session) { return _sessions.respondToAskUser(session, msg); },
|
|
2338
|
+
handleHomePermissionResponse: function (ws, msg, session) { return _sessions.respondToHomePermission(ws, msg, session); },
|
|
2339
|
+
stopHomeSession: function (session) { return _sessions.stopHomeSession(session); },
|
|
2317
2340
|
handleHomeDebateControl: function (ws, msg, session) { return _debate.handleHomeControl(ws, msg, session); },
|
|
2318
2341
|
getVendorModelCatalog: _models.getVendorCatalog,
|
|
2319
2342
|
getVendorModelAvailability: _models.getVendorAvailability,
|
package/lib/public/app.js
CHANGED
|
@@ -294,6 +294,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
294
294
|
savedMainSlug: null,
|
|
295
295
|
connected: false,
|
|
296
296
|
pendingOutboundMessages: [],
|
|
297
|
+
searchClayChatState: null,
|
|
297
298
|
homeChatMateId: null,
|
|
298
299
|
homeChatSessionId: null,
|
|
299
300
|
homeChatSessionModel: null,
|
|
@@ -392,6 +392,32 @@
|
|
|
392
392
|
gap: 12px;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
.search-clay-permission {
|
|
396
|
+
margin: 0 6px;
|
|
397
|
+
padding: 12px;
|
|
398
|
+
border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border-subtle));
|
|
399
|
+
border-radius: 10px;
|
|
400
|
+
background: color-mix(in srgb, var(--accent) 6%, var(--bg-secondary));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.search-clay-permission strong { display: block; color: var(--text); font-size: 13px; }
|
|
404
|
+
.search-clay-permission p { margin: 5px 0 10px; color: var(--text-muted); font-size: 11px; line-height: 1.45; overflow-wrap: anywhere; }
|
|
405
|
+
.search-clay-permission > div { display: flex; justify-content: flex-end; gap: 7px; }
|
|
406
|
+
.search-clay-permission button {
|
|
407
|
+
min-height: 30px;
|
|
408
|
+
padding: 0 10px;
|
|
409
|
+
border: 1px solid var(--border);
|
|
410
|
+
border-radius: 7px;
|
|
411
|
+
background: var(--bg);
|
|
412
|
+
color: var(--text);
|
|
413
|
+
font: inherit;
|
|
414
|
+
font-size: 11px;
|
|
415
|
+
cursor: pointer;
|
|
416
|
+
}
|
|
417
|
+
.search-clay-permission button.is-primary { border-color: var(--accent); background: var(--accent); color: white; }
|
|
418
|
+
.search-clay-permission button:disabled { opacity: 0.55; cursor: default; }
|
|
419
|
+
.search-clay-permission small { display: block; margin-top: 7px; color: var(--text-muted); font-size: 11px; }
|
|
420
|
+
|
|
395
421
|
.search-clay-message { width: 100%; min-width: 0; }
|
|
396
422
|
.search-clay-transcript .search-clay-message-user {
|
|
397
423
|
display: flex;
|
|
@@ -593,6 +593,37 @@
|
|
|
593
593
|
line-height: 1;
|
|
594
594
|
}
|
|
595
595
|
.project-access-close:hover { color: var(--text); }
|
|
596
|
+
.project-access-scope {
|
|
597
|
+
display: flex;
|
|
598
|
+
gap: 10px;
|
|
599
|
+
margin: 12px 14px 4px;
|
|
600
|
+
padding: 10px;
|
|
601
|
+
color: var(--text);
|
|
602
|
+
background: rgba(var(--overlay-rgb), 0.045);
|
|
603
|
+
border: 1px solid var(--border);
|
|
604
|
+
border-radius: 9px;
|
|
605
|
+
}
|
|
606
|
+
.project-access-scope-icon {
|
|
607
|
+
display: flex;
|
|
608
|
+
align-items: center;
|
|
609
|
+
justify-content: center;
|
|
610
|
+
width: 28px;
|
|
611
|
+
height: 28px;
|
|
612
|
+
flex: 0 0 28px;
|
|
613
|
+
color: var(--accent, #6366f1);
|
|
614
|
+
background: var(--accent-8, rgba(99, 102, 241, 0.08));
|
|
615
|
+
border-radius: 7px;
|
|
616
|
+
}
|
|
617
|
+
.project-access-scope-icon .lucide { width: 15px; height: 15px; }
|
|
618
|
+
.project-access-scope strong,
|
|
619
|
+
.project-access-scope small { display: block; }
|
|
620
|
+
.project-access-scope strong { font-size: 12px; font-weight: 600; }
|
|
621
|
+
.project-access-scope small {
|
|
622
|
+
margin-top: 2px;
|
|
623
|
+
color: var(--text-secondary);
|
|
624
|
+
font-size: 10px;
|
|
625
|
+
line-height: 1.35;
|
|
626
|
+
}
|
|
596
627
|
.project-access-section {
|
|
597
628
|
padding: 10px 14px;
|
|
598
629
|
}
|
|
@@ -653,6 +684,15 @@
|
|
|
653
684
|
cursor: pointer;
|
|
654
685
|
}
|
|
655
686
|
.project-access-user-item:hover { color: var(--text); }
|
|
687
|
+
.project-access-user-item small {
|
|
688
|
+
margin-left: auto;
|
|
689
|
+
color: var(--text-tertiary);
|
|
690
|
+
font-size: 9px;
|
|
691
|
+
text-transform: uppercase;
|
|
692
|
+
letter-spacing: 0.04em;
|
|
693
|
+
}
|
|
694
|
+
.project-access-user-item.is-inherited { color: var(--text-secondary); cursor: default; }
|
|
695
|
+
.project-access-user-item.is-inherited input { cursor: default; }
|
|
656
696
|
.project-access-empty {
|
|
657
697
|
font-size: 12px;
|
|
658
698
|
color: var(--text-tertiary);
|
package/lib/public/index.html
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
(function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
|
|
32
32
|
</script>
|
|
33
33
|
<script>if(window.navigator.standalone||window.matchMedia("(display-mode:standalone)").matches){document.documentElement.classList.add("pwa-standalone")}</script>
|
|
34
|
-
<link rel="stylesheet" href="style.css?v=
|
|
34
|
+
<link rel="stylesheet" href="style.css?v=20260910-ask-clay-controls1">
|
|
35
35
|
<style>
|
|
36
36
|
@media(max-width:768px){
|
|
37
37
|
/* User messages: vertical stack, avatar on top, right-aligned */
|
|
@@ -2337,7 +2337,7 @@
|
|
|
2337
2337
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
|
|
2338
2338
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
|
|
2339
2339
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"></script>
|
|
2340
|
-
<script type="module" src="app.js?v=
|
|
2340
|
+
<script type="module" src="app.js?v=20260910-ask-clay-controls1"></script>
|
|
2341
2341
|
<div id="pwa-install-modal" class="pwa-modal hidden">
|
|
2342
2342
|
<div class="pwa-modal-backdrop"></div>
|
|
2343
2343
|
<div class="pwa-modal-card">
|
|
@@ -78,7 +78,7 @@ export function handleHomeProtocolMessage(msg) {
|
|
|
78
78
|
|
|
79
79
|
export function processMessage(msg) {
|
|
80
80
|
var searchHandled = handleSearchClayMessage(msg);
|
|
81
|
-
if (searchHandled && msg.type === "home_clay_activity") return;
|
|
81
|
+
if (searchHandled && (msg.type === "home_clay_activity" || msg.type === "home_mate_stopping" || msg.type.indexOf("home_mate_permission_") === 0)) return;
|
|
82
82
|
if (handleHomeProtocolMessage(msg)) return;
|
|
83
83
|
processAppMessage(msg);
|
|
84
84
|
}
|
|
@@ -89,6 +89,10 @@ var inputEl = document.getElementById("input");
|
|
|
89
89
|
var connectOverlay = document.getElementById("connect-overlay");
|
|
90
90
|
|
|
91
91
|
export function processMessage(msg) {
|
|
92
|
+
if (msg.type === "access_revoked") {
|
|
93
|
+
window.location.assign("/");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
92
96
|
// Preserve original timestamp from history replay
|
|
93
97
|
store.set({ currentMsgTs: msg._ts || null });
|
|
94
98
|
var isMateDm = store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate;
|
|
@@ -723,6 +727,11 @@ export function processMessage(msg) {
|
|
|
723
727
|
syncPaneTitles();
|
|
724
728
|
break;
|
|
725
729
|
|
|
730
|
+
case "partner_task_completed":
|
|
731
|
+
// The result is resumed into the Driver transcript; this correlated
|
|
732
|
+
// event is available to clients that track task state separately.
|
|
733
|
+
break;
|
|
734
|
+
|
|
726
735
|
case "session_presence":
|
|
727
736
|
updateSessionPresence(msg.presence || {});
|
|
728
737
|
break;
|