clay-server 4.1.0-beta.4 → 4.1.0-beta.6
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/autonomous-run-record.js +32 -0
- package/lib/autonomous-run-scheduler.js +92 -0
- package/lib/durable-scheduler-record.js +61 -0
- package/lib/durable-scheduler-store.js +185 -0
- package/lib/durable-scheduler.js +495 -0
- package/lib/loop-cron.js +47 -0
- package/lib/loop-durable-schedule.js +159 -0
- package/lib/loop-guidance.js +35 -0
- package/lib/loop-registry-occurrence.js +76 -0
- package/lib/project-autonomous-run.js +498 -0
- package/lib/project-connection.js +4 -1
- package/lib/project-full-access.js +86 -0
- package/lib/project-http.js +3 -1
- package/lib/project-loop-interview.js +163 -0
- package/lib/project-loop.js +150 -64
- package/lib/project-pair-autonomous-stop.js +16 -0
- package/lib/project-pair-owned-stop.js +18 -0
- package/lib/project-pair-task-control.js +14 -1
- package/lib/project-scheduled-messages.js +316 -0
- package/lib/project-scheduled-task-execution.js +236 -0
- package/lib/project-scheduled-tasks.js +483 -0
- package/lib/project-session-pair.js +16 -19
- package/lib/project-sessions.js +60 -28
- package/lib/project-user-message.js +24 -18
- package/lib/project.js +260 -52
- package/lib/public/app.js +60 -23
- package/lib/public/css/home-hub.css +15 -2
- package/lib/public/css/home-sidebar.css +50 -2
- package/lib/public/css/input.css +71 -0
- package/lib/public/css/mates.css +0 -1
- package/lib/public/css/pane.css +58 -7
- package/lib/public/css/rewind.css +11 -2
- package/lib/public/css/scheduled-tasks.css +123 -0
- package/lib/public/css/sticky-notes.css +24 -4
- package/lib/public/index.html +19 -134
- package/lib/public/modules/app-loop-ui.js +4 -8
- package/lib/public/modules/app-messages.js +22 -6
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rate-limit.js +8 -5
- package/lib/public/modules/autonomous-run.js +285 -0
- package/lib/public/modules/home-sidebar.js +16 -8
- package/lib/public/modules/home-surface.js +5 -0
- package/lib/public/modules/input.js +5 -4
- package/lib/public/modules/loop-interview.js +151 -0
- package/lib/public/modules/project-logs.js +2 -2
- package/lib/public/modules/scheduled-message-state.js +46 -0
- package/lib/public/modules/scheduled-task-edit-state.js +7 -0
- package/lib/public/modules/scheduled-task-engine.js +98 -0
- package/lib/public/modules/scheduled-task-form.js +122 -0
- package/lib/public/modules/scheduled-task-recurrence.js +46 -0
- package/lib/public/modules/scheduled-task-schedule-rule.js +115 -0
- package/lib/public/modules/scheduled-tasks.js +468 -0
- package/lib/public/modules/scheduler-config.js +6 -52
- package/lib/public/modules/scheduler.js +9 -21
- package/lib/public/modules/sidebar.js +0 -11
- package/lib/public/modules/split-view.js +2 -0
- package/lib/public/modules/sticky-notes-browser.js +1 -1
- package/lib/public/modules/sticky-notes.js +117 -4
- package/lib/public/modules/tool-palette-order.js +2 -3
- package/lib/public/modules/tools.js +5 -2
- package/lib/public/style.css +1 -0
- package/lib/record-revision.js +11 -0
- package/lib/schedule-rule.js +151 -0
- package/lib/schedule-validation.js +124 -0
- package/lib/scheduled-run-state.js +87 -0
- package/lib/scheduled-task-records.js +127 -0
- package/lib/scheduler.js +179 -196
- package/lib/sdk-bridge.js +123 -11
- package/lib/sdk-message-processor.js +3 -0
- package/lib/server-durable-scheduler.js +26 -0
- package/lib/server.js +22 -0
- package/lib/session-pair-factory.js +42 -4
- package/lib/session-pair-query-start.js +22 -0
- package/lib/session-query-tool-bridge.js +52 -0
- package/lib/session-split-groups.js +35 -1
- package/lib/sessions.js +51 -3
- package/lib/users-home-surface-preferences.js +2 -1
- package/lib/users-scheduled-task-preferences.js +44 -0
- package/lib/users.js +6 -0
- package/lib/ws-schema.js +25 -0
- package/lib/yoke/adapters/antigravity.js +1 -0
- package/lib/yoke/adapters/claude-worker.js +7 -4
- package/lib/yoke/adapters/claude.js +31 -1
- package/lib/yoke/adapters/codex.js +3 -0
- package/lib/yoke/claude-user-input.js +2 -0
- package/lib/yoke/index.js +2 -0
- package/lib/yoke/mcp-bridge-server.js +7 -1
- package/lib/yoke/session-tool-transport.js +53 -0
- package/lib/yoke/user-input.js +21 -7
- package/package.json +1 -1
- package/lib/public/modules/app-loop-wizard.js +0 -439
package/lib/project-sessions.js
CHANGED
|
@@ -12,6 +12,27 @@ function vendorSupportsTui(vendor) {
|
|
|
12
12
|
return !!(info && info.sessionModes.indexOf("tui") !== -1);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function catalogHasModel(models, value) {
|
|
16
|
+
for (var i = 0; i < (models || []).length; i++) {
|
|
17
|
+
var model = models[i];
|
|
18
|
+
if (typeof model === "string" && model === value) return true;
|
|
19
|
+
if (model && (model.value === value || model.id === value || model.resolvedModel === value)) return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function catalogSupportsEffort(models, modelValue, effort, vendorLevels) {
|
|
25
|
+
if (!effort) return true;
|
|
26
|
+
for (var i = 0; i < (models || []).length; i++) {
|
|
27
|
+
var model = models[i];
|
|
28
|
+
var value = typeof model === "string" ? model : model && (model.value || model.id || model.resolvedModel);
|
|
29
|
+
if (value !== modelValue) continue;
|
|
30
|
+
if (model && Array.isArray(model.supportedEffortLevels) && model.supportedEffortLevels.length) return model.supportedEffortLevels.indexOf(effort) !== -1;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
return (vendorLevels || []).indexOf(effort) !== -1;
|
|
34
|
+
}
|
|
35
|
+
|
|
15
36
|
// Format a user's answer to an ask_user_questions card as a plain user
|
|
16
37
|
// message so the MCP path can feed it back to the agent on the next turn.
|
|
17
38
|
// The agent sees its own question text alongside the selected answer(s),
|
|
@@ -98,6 +119,8 @@ function attachSessions(ctx) {
|
|
|
98
119
|
var onProcessingChanged = ctx.onProcessingChanged;
|
|
99
120
|
var broadcastPresence = ctx.broadcastPresence;
|
|
100
121
|
var adapter = ctx.adapter;
|
|
122
|
+
var fullAccessService = ctx.fullAccessService;
|
|
123
|
+
var autonomousRun = ctx.autonomousRun;
|
|
101
124
|
|
|
102
125
|
function ownsSearchSession(ws, session) {
|
|
103
126
|
if (!session) return false;
|
|
@@ -118,6 +141,7 @@ function attachSessions(ctx) {
|
|
|
118
141
|
catch (e) { return false; }
|
|
119
142
|
}
|
|
120
143
|
delete session.pendingAskUser[toolId];
|
|
144
|
+
if (autonomousRun) autonomousRun.onUserAnswer(session);
|
|
121
145
|
sm.sendAndRecord(session, { type: "ask_user_answered", toolId: toolId, answers: answers });
|
|
122
146
|
if (pending.mode === "yoke" && typeof pending.respond === "function") {
|
|
123
147
|
pending.respond(answers);
|
|
@@ -507,6 +531,34 @@ function attachSessions(ctx) {
|
|
|
507
531
|
}
|
|
508
532
|
|
|
509
533
|
if (msg.type === "new_session") {
|
|
534
|
+
if (msg.scheduleInterview === true) {
|
|
535
|
+
var sourceSession = getSessionForWs(ws);
|
|
536
|
+
var interviewVendor = typeof msg.vendor === "string" ? msg.vendor : "";
|
|
537
|
+
var installed = sm.installedVendors || [];
|
|
538
|
+
var knownModels = sm.modelsByVendor && sm.modelsByVendor[interviewVendor] || [];
|
|
539
|
+
var requestedModel = typeof msg.model === "string" ? msg.model : "";
|
|
540
|
+
var requestedEffort = typeof msg.effort === "string" ? msg.effort : "";
|
|
541
|
+
var vendorInfo = yoke.getVendorInfo(interviewVendor);
|
|
542
|
+
var engineError = "";
|
|
543
|
+
var sourceMatches = !!sourceSession && Number(msg.sourceSessionId) === Number(sourceSession.localId);
|
|
544
|
+
if (!sourceMatches || msg.projectSlug !== slug || !ctx.canCreateScheduledTaskInterview || !ctx.canCreateScheduledTaskInterview(ws, sourceSession)) engineError = "Scheduled Tasks is unavailable in the current project session.";
|
|
545
|
+
else if (!interviewVendor || installed.indexOf(interviewVendor) === -1 || !vendorInfo || yoke.sessionTools.capability(interviewVendor).kind === "none") engineError = "The selected interview engine is no longer available for assisted scheduling.";
|
|
546
|
+
else if (!knownModels.length) engineError = "Interview models are still loading or unavailable for this engine.";
|
|
547
|
+
else if (requestedModel && !catalogHasModel(knownModels, requestedModel)) engineError = "The selected interview model is no longer available.";
|
|
548
|
+
else if (!catalogSupportsEffort(knownModels, requestedModel, requestedEffort, vendorInfo.effortLevels)) engineError = "The selected interview effort is unavailable for this engine.";
|
|
549
|
+
if (engineError) {
|
|
550
|
+
if (msg.requestId) sendTo(ws, { type: "new_session_result", requestId: String(msg.requestId).substring(0, 200), ok: false, error: engineError });
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
var preferenceUserId = ws._clayUser ? ws._clayUser.id : "default";
|
|
554
|
+
var savedPreference;
|
|
555
|
+
try { savedPreference = usersModule.setScheduledTaskInterviewEngine(preferenceUserId, { vendor: interviewVendor, model: requestedModel, effort: requestedEffort }); }
|
|
556
|
+
catch (preferenceError) { savedPreference = { error: preferenceError.message || String(preferenceError) }; }
|
|
557
|
+
if (!savedPreference || !savedPreference.ok) {
|
|
558
|
+
if (msg.requestId) sendTo(ws, { type: "new_session_result", requestId: String(msg.requestId).substring(0, 200), ok: false, error: savedPreference && savedPreference.error || "The interview engine preference could not be saved." });
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
510
562
|
// Opportunistic hygiene: drop blanks that sat untouched past the grace
|
|
511
563
|
// period before deciding whether to reuse or create.
|
|
512
564
|
sm.sweepBlankSessions();
|
|
@@ -514,6 +566,7 @@ function attachSessions(ctx) {
|
|
|
514
566
|
if (ws._clayUser && usersModule.isMultiUser()) sessionOpts.ownerId = ws._clayUser.id;
|
|
515
567
|
sessionOpts.sessionVisibility = sessionVisibility.normalize(msg.sessionVisibility);
|
|
516
568
|
if (msg.vendor) sessionOpts.vendor = msg.vendor;
|
|
569
|
+
if (msg.scheduleInterview === true && msg.model) sessionOpts.model = msg.model;
|
|
517
570
|
// Mode resolution: vendors without a TUI session mode are always GUI.
|
|
518
571
|
// TUI-capable sessions honor the explicit msg.mode if provided, otherwise
|
|
519
572
|
// fall back to the user's claudeOpenMode preference. This is what
|
|
@@ -583,10 +636,9 @@ function attachSessions(ctx) {
|
|
|
583
636
|
sm.switchSession(newSess.localId, ws);
|
|
584
637
|
} else {
|
|
585
638
|
var newSessionVendor = sessionOpts.vendor || sm.defaultVendor || "claude";
|
|
586
|
-
sessionOpts.effort =
|
|
587
|
-
|
|
588
|
-
(sm.currentEffortByVendor && sm.currentEffortByVendor[newSessionVendor]) || sm.currentEffort || "medium"
|
|
589
|
-
) || null;
|
|
639
|
+
sessionOpts.effort = msg.scheduleInterview === true && msg.effort
|
|
640
|
+
? msg.effort
|
|
641
|
+
: yoke.clampEffort(newSessionVendor, (sm.currentEffortByVendor && sm.currentEffortByVendor[newSessionVendor]) || sm.currentEffort || "medium") || null;
|
|
590
642
|
// Reuse an existing blank GUI session instead of stacking another
|
|
591
643
|
// one. A vendor-less blank is acceptable: nothing has happened in it,
|
|
592
644
|
// so stamping the requested vendor is equivalent to a fresh create.
|
|
@@ -606,6 +658,7 @@ function attachSessions(ctx) {
|
|
|
606
658
|
}
|
|
607
659
|
}
|
|
608
660
|
ws._clayActiveSession = newSess.localId;
|
|
661
|
+
if (msg.requestId) sendTo(ws, { type: "new_session_result", requestId: String(msg.requestId).substring(0, 200), ok: true, sessionId: newSess.localId });
|
|
609
662
|
// Remember the vendor only when the client asked for one explicitly.
|
|
610
663
|
// new_session without a vendor (mate sidebar, notification banner,
|
|
611
664
|
// debate) falls through to the default adapter and must not clobber
|
|
@@ -1019,6 +1072,7 @@ function attachSessions(ctx) {
|
|
|
1019
1072
|
|
|
1020
1073
|
if (msg.type === "stop") {
|
|
1021
1074
|
var session = getSessionForWs(ws);
|
|
1075
|
+
if (autonomousRun && session && session.autonomousRun) autonomousRun.finish(session, "stopped", "Stopped by the user.");
|
|
1022
1076
|
stopSession(session);
|
|
1023
1077
|
return true;
|
|
1024
1078
|
}
|
|
@@ -1150,34 +1204,12 @@ function attachSessions(ctx) {
|
|
|
1150
1204
|
return true;
|
|
1151
1205
|
}
|
|
1152
1206
|
|
|
1153
|
-
var previousFullAccess = fullAccessSession.permissionMode === "bypassPermissions";
|
|
1154
|
-
var previousPermissionMode = fullAccessSession.permissionMode || null;
|
|
1155
|
-
var previousRestoreMode = fullAccessSession.permissionModeBeforeFullAccess || null;
|
|
1156
|
-
if (fullAccessEnabled) {
|
|
1157
|
-
if (!previousFullAccess) {
|
|
1158
|
-
fullAccessSession.permissionModeBeforeFullAccess = fullAccessSession.permissionMode || sm.currentPermissionMode || "default";
|
|
1159
|
-
}
|
|
1160
|
-
fullAccessSession.permissionMode = "bypassPermissions";
|
|
1161
|
-
} else {
|
|
1162
|
-
fullAccessSession.permissionMode = fullAccessSession.permissionModeBeforeFullAccess || "default";
|
|
1163
|
-
fullAccessSession.permissionModeBeforeFullAccess = null;
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
1207
|
(async function () {
|
|
1167
1208
|
try {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
// it in a normal mode so Clay remains the single approval layer.
|
|
1171
|
-
if (fullAccessSession.queryInstance && typeof fullAccessSession.queryInstance.setPermissionMode === "function") {
|
|
1172
|
-
await fullAccessSession.queryInstance.setPermissionMode(fullAccessEnabled ? "default" : fullAccessSession.permissionMode);
|
|
1173
|
-
}
|
|
1174
|
-
sm.saveSessionFile(fullAccessSession);
|
|
1175
|
-
sm.broadcastSessionList();
|
|
1176
|
-
send({ type: "session_full_access_changed", id: fullAccessSession.localId, enabled: fullAccessEnabled, permissionMode: fullAccessSession.permissionMode });
|
|
1209
|
+
if (!fullAccessEnabled && autonomousRun) await autonomousRun.stopForPermissionChange(fullAccessSession);
|
|
1210
|
+
await fullAccessService.setEnabled(fullAccessSession, fullAccessEnabled);
|
|
1177
1211
|
sendTo(ws, { type: "session_full_access_result", ok: true, id: fullAccessSession.localId, enabled: fullAccessEnabled });
|
|
1178
1212
|
} catch (e) {
|
|
1179
|
-
fullAccessSession.permissionMode = previousPermissionMode;
|
|
1180
|
-
fullAccessSession.permissionModeBeforeFullAccess = previousRestoreMode;
|
|
1181
1213
|
sendTo(ws, { type: "session_full_access_result", ok: false, id: fullAccessSession.localId, error: "Failed to change Skip Permissions: " + (e.message || e) });
|
|
1182
1214
|
}
|
|
1183
1215
|
})();
|
|
@@ -70,6 +70,7 @@ function attachUserMessage(ctx) {
|
|
|
70
70
|
|
|
71
71
|
var scheduleMessage = ctx.scheduleMessage;
|
|
72
72
|
var cancelScheduledMessage = ctx.cancelScheduledMessage;
|
|
73
|
+
var sendScheduledMessageNow = ctx.sendScheduledMessageNow;
|
|
73
74
|
|
|
74
75
|
var loadContextSources = ctx.loadContextSources;
|
|
75
76
|
var saveContextSources = ctx.saveContextSources;
|
|
@@ -281,34 +282,21 @@ function attachUserMessage(ctx) {
|
|
|
281
282
|
if (msg.type === "schedule_message") {
|
|
282
283
|
var schedSession = getSessionForWs(ws);
|
|
283
284
|
if (!schedSession || !msg.text || !msg.resetsAt) return true;
|
|
284
|
-
scheduleMessage(schedSession, msg.text, msg.resetsAt);
|
|
285
|
+
scheduleMessage(schedSession, msg.text, msg.resetsAt, ws._clayUser || null, ws);
|
|
285
286
|
return true;
|
|
286
287
|
}
|
|
287
288
|
|
|
288
289
|
if (msg.type === "cancel_scheduled_message") {
|
|
289
290
|
var cancelSession = getSessionForWs(ws);
|
|
290
291
|
if (!cancelSession) return true;
|
|
291
|
-
cancelScheduledMessage(cancelSession);
|
|
292
|
+
cancelScheduledMessage(cancelSession, ws._clayUser || null, ws, { jobId: msg.jobId, revision: msg.revision });
|
|
292
293
|
return true;
|
|
293
294
|
}
|
|
294
295
|
|
|
295
296
|
if (msg.type === "send_scheduled_now") {
|
|
296
297
|
var nowSession = getSessionForWs(ws);
|
|
297
298
|
if (!nowSession || !nowSession.scheduledMessage) return true;
|
|
298
|
-
|
|
299
|
-
clearTimeout(nowSession.scheduledMessage.timer);
|
|
300
|
-
nowSession.scheduledMessage = null;
|
|
301
|
-
console.log("[project] Scheduled message sent immediately for session " + nowSession.localId);
|
|
302
|
-
sm.sendAndRecord(nowSession, { type: "scheduled_message_sent" });
|
|
303
|
-
var userMsg = { type: "user_message", text: schedText, _ts: Date.now() };
|
|
304
|
-
nowSession.history.push(userMsg);
|
|
305
|
-
sm.appendToSessionFile(nowSession, userMsg);
|
|
306
|
-
sendToSession(nowSession.localId, userMsg);
|
|
307
|
-
nowSession.isProcessing = true;
|
|
308
|
-
onProcessingChanged();
|
|
309
|
-
sendToSession(nowSession.localId, { type: "status", status: "processing" });
|
|
310
|
-
sdk.startQuery(nowSession, schedText, null, ensureProjectAccessForSession(nowSession));
|
|
311
|
-
sm.broadcastSessionList();
|
|
299
|
+
sendScheduledMessageNow(nowSession, ws._clayUser || null, ws, { jobId: msg.jobId, revision: msg.revision });
|
|
312
300
|
return true;
|
|
313
301
|
}
|
|
314
302
|
|
|
@@ -318,6 +306,11 @@ function attachUserMessage(ctx) {
|
|
|
318
306
|
var session = getSessionForWs(ws);
|
|
319
307
|
if (!session) return true;
|
|
320
308
|
|
|
309
|
+
if (session.loopInterviewHandoff && session.loopInterviewHandoff.state === "starting") {
|
|
310
|
+
sendTo(ws, { type: "error", text: "Wait for the approved Loop handoff to finish." });
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
|
|
321
314
|
var deliveryReceipt = messageDelivery.inspect(ws, session, msg);
|
|
322
315
|
if (deliveryReceipt.duplicate) {
|
|
323
316
|
messageDelivery.acknowledge(ws, deliveryReceipt, hydrateImageRefs(deliveryReceipt.recordedMessage));
|
|
@@ -339,6 +332,19 @@ function attachUserMessage(ctx) {
|
|
|
339
332
|
});
|
|
340
333
|
return true;
|
|
341
334
|
}
|
|
335
|
+
if (msg.autonomousRunToken) {
|
|
336
|
+
if (!ctx.autonomousRun || !ctx.autonomousRun.consume(session, msg)) {
|
|
337
|
+
sendTo(ws, { type: "error", text: "This Until complete submission is stale or no longer armed." });
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
} else if (session.autonomousRun && session.autonomousRun.state === "armed") {
|
|
341
|
+
sendTo(ws, { type: "error", text: "Until complete is still being armed. Wait for its acknowledgement before sending." });
|
|
342
|
+
return true;
|
|
343
|
+
} else if (ctx.autonomousRun) {
|
|
344
|
+
ctx.autonomousRun.onHumanMessage(session, msg);
|
|
345
|
+
}
|
|
346
|
+
var autonomousDeliveryMeta = session.autonomousRun && ["running", "reviewing", "waiting-worker", "waiting-user"].indexOf(session.autonomousRun.state) !== -1
|
|
347
|
+
? { autonomousRunId: session.autonomousRun.id } : undefined;
|
|
342
348
|
if (typeof ctx.beginHumanPairTurn === "function") ctx.beginHumanPairTurn(session);
|
|
343
349
|
if (gitAttribution) gitAttribution.beginTurn(session);
|
|
344
350
|
|
|
@@ -552,7 +558,7 @@ function attachUserMessage(ctx) {
|
|
|
552
558
|
if (!session.queryInstance && !session._queryStarting && (!session.worker || session.messageQueue !== "worker")) {
|
|
553
559
|
// No active query (or worker idle between queries): start a new query
|
|
554
560
|
startFreshQuery(finalText);
|
|
555
|
-
} else if (!sdk.pushMessage(session, finalText, msg.images)) {
|
|
561
|
+
} else if (!sdk.pushMessage(session, finalText, msg.images, autonomousDeliveryMeta)) {
|
|
556
562
|
// Stale state won the branch (previous stream died between the
|
|
557
563
|
// turn boundary and its cleanup) but there is nothing to deliver
|
|
558
564
|
// to. Start a fresh query with this text instead of dropping it,
|
|
@@ -560,7 +566,7 @@ function attachUserMessage(ctx) {
|
|
|
560
566
|
// response until the user sent the message again.
|
|
561
567
|
startFreshQuery(finalText);
|
|
562
568
|
}
|
|
563
|
-
} else if (!sdk.pushMessage(session, finalText, msg.images)) {
|
|
569
|
+
} else if (!sdk.pushMessage(session, finalText, msg.images, autonomousDeliveryMeta)) {
|
|
564
570
|
// isProcessing was stuck true with no live query: same recovery.
|
|
565
571
|
startFreshQuery(finalText);
|
|
566
572
|
}
|