clay-server 3.4.0-beta.16 → 3.4.0-beta.18
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.
|
@@ -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,
|
|
@@ -238,30 +266,24 @@ function attachWorkerProposal(ctx) {
|
|
|
238
266
|
return session;
|
|
239
267
|
}
|
|
240
268
|
|
|
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
|
-
}
|
|
269
|
+
async function acceptProposal(session, proposal, msg, ws) {
|
|
251
270
|
var options = proposal.options || proposalOptions();
|
|
252
271
|
var vendor = msg.vendor || proposal.recommendedVendor;
|
|
253
272
|
var model = msg.model || "";
|
|
254
273
|
if (options.installedVendors.indexOf(vendor) === -1) throw new Error("Selected Worker vendor is not installed");
|
|
255
274
|
if (!modelIsAvailable(options, vendor, model)) throw new Error("Selected Worker model is unavailable");
|
|
256
275
|
var effort = yoke.clampEffort(vendor, msg.effort || proposal.recommendedEffort || "medium") || "";
|
|
257
|
-
|
|
276
|
+
var startingPatch = { status: "starting", selectedVendor: vendor, selectedModel: model, selectedEffort: effort };
|
|
277
|
+
if (msg.autoApproved) startingPatch.autoApproved = true;
|
|
278
|
+
updateProposal(session, proposal, startingPatch);
|
|
258
279
|
try {
|
|
259
280
|
var created = ctx.createPairRecord(ws, {
|
|
260
281
|
driver: { sessionId: session.localId },
|
|
261
282
|
worker: { vendor: vendor, model: model, effort: effort },
|
|
262
283
|
});
|
|
263
284
|
updateProposal(session, proposal, { status: "running", groupId: created.group.id, workerId: created.worker.localId });
|
|
264
|
-
|
|
285
|
+
if (ws._autoApproval) sm.sendToSession(session, { type: "pair_session_created", ok: true, group: created.group });
|
|
286
|
+
else ctx.sendTo(ws, { type: "pair_session_created", ok: true, group: created.group });
|
|
265
287
|
runWorker(session, proposal).catch(function (err) {
|
|
266
288
|
updateProposal(session, proposal, { status: "error", error: err.message || String(err) });
|
|
267
289
|
resumeDriver(session, "[Worker execution failed]\n" + (err.message || String(err))).catch(function () {});
|
|
@@ -273,6 +295,19 @@ function attachWorkerProposal(ctx) {
|
|
|
273
295
|
}
|
|
274
296
|
}
|
|
275
297
|
|
|
298
|
+
async function respondToProposal(ws, msg) {
|
|
299
|
+
var session = sessionForResponse(ws);
|
|
300
|
+
var proposal = findProposal(session, msg.proposalId);
|
|
301
|
+
if (!proposal) throw new Error("Worker suggestion not found");
|
|
302
|
+
if (proposal.status !== "pending") throw new Error("Worker suggestion has already been resolved");
|
|
303
|
+
if (!msg.accepted) {
|
|
304
|
+
updateProposal(session, proposal, { status: "declined" });
|
|
305
|
+
await resumeDriver(session, "[Worker suggestion declined]\nContinue this task in the current session using the plan you already prepared.");
|
|
306
|
+
return { ok: true, status: "declined" };
|
|
307
|
+
}
|
|
308
|
+
return acceptProposal(session, proposal, msg, ws);
|
|
309
|
+
}
|
|
310
|
+
|
|
276
311
|
function handleMessage(ws, msg) {
|
|
277
312
|
if (msg.type !== "worker_proposal_response") return false;
|
|
278
313
|
respondToProposal(ws, msg).catch(function (err) {
|
|
@@ -103,8 +103,10 @@ function statusLabel(status) {
|
|
|
103
103
|
function applyState(card, msg) {
|
|
104
104
|
var status = msg.status || "pending";
|
|
105
105
|
card.dataset.status = status;
|
|
106
|
+
if (msg.autoApproved) card.dataset.autoApproved = "true";
|
|
107
|
+
var autoApproved = card.dataset.autoApproved === "true";
|
|
106
108
|
var badge = card.querySelector(".worker-proposal-status");
|
|
107
|
-
if (badge) badge.textContent = statusLabel(status);
|
|
109
|
+
if (badge) badge.textContent = statusLabel(status) + (autoApproved ? " · auto-approved" : "");
|
|
108
110
|
var error = card.querySelector(".worker-proposal-error");
|
|
109
111
|
if (error) {
|
|
110
112
|
error.textContent = msg.error || "";
|
|
@@ -116,6 +118,8 @@ function applyState(card, msg) {
|
|
|
116
118
|
preview.classList.remove("hidden");
|
|
117
119
|
}
|
|
118
120
|
setControlsDisabled(card, status !== "pending");
|
|
121
|
+
var actions = card.querySelector(".worker-proposal-actions");
|
|
122
|
+
if (actions) actions.classList.toggle("hidden", autoApproved);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
function sendDecision(card, accepted) {
|
|
@@ -7,6 +7,7 @@ var MEMORY_CONTRACT =
|
|
|
7
7
|
"Default to not writing. Create a note proactively only when the user explicitly asks to remember or track something, or when all of these are true: it will remain useful after the current task and session, it is not already adequately recorded in the repository or another note, and the user would likely be glad to find it on the board a week later. " +
|
|
8
8
|
"Good notes capture an unresolved commitment, durable product decision, user preference, constraint, or handoff that will materially change future work. " +
|
|
9
9
|
"Important exception for deferred defects: while doing code or technical work, actively create a sticky note when you discover a concrete defect, regression risk, security issue, or data-loss risk that is outside the current session goal and will remain unfixed when the turn ends. Do not wait for the user to ask. Include the observable evidence, affected component, likely impact, and a clear next action. Check active notes first when a duplicate is plausible. Do not create defect notes for speculation, general cleanup ideas, or problems you fixed in the current session. " +
|
|
10
|
+
"Important exception for deferred proposals: when you propose work (a fix, follow-up, improvement, or next step) and the user defers it rather than declining it (\"let's do that later\", \"next time\", \"after this\"), actively create a sticky note before the topic moves on. Do not wait to be asked. Capture what was proposed, why it mattered, and the agreed timing if any. Deferred agreements scroll out of chat history quickly and are hard to track; the board is where they survive. If the user declines the idea outright, do not write a note. " +
|
|
10
11
|
"Never create a note merely because work is important, lengthy, spans agents or restarts, or might help another agent. Do not record completed work, implementation details, test results, investigation logs, transient blockers, conversation summaries, or announcements of your own activity. When uncertain, do not write. " +
|
|
11
12
|
"Updates are visible too: update only when durable state materially changes, and remove a note created by your session when it stops being useful instead of turning it into a completion log. Put a concise plain-text title on the first line, stay focused on one topic, and include only the context needed for future action.";
|
|
12
13
|
|
package/package.json
CHANGED