clay-server 4.1.0-beta.5 → 4.1.0-beta.7
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/LICENSE +661 -21
- package/LICENSES/MIT-legacy.txt +21 -0
- package/LICENSES/README.md +7 -0
- package/README.md +2 -2
- package/bin/cli.js +0 -2
- package/lib/autonomous-run-record.js +32 -0
- package/lib/autonomous-run-scheduler.js +92 -0
- package/lib/daemon.js +0 -2
- package/lib/default-ai-runtime.js +117 -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/git-cli.js +1 -0
- package/lib/issue-work-session.js +10 -0
- package/lib/issues-comments.js +40 -0
- package/lib/issues-commit-verification.js +30 -0
- package/lib/issues-mcp-server.js +27 -0
- package/lib/issues-schema.js +119 -0
- package/lib/issues-service.js +339 -0
- package/lib/issues-store.js +210 -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/model-entry.js +15 -0
- package/lib/project-autonomous-run.js +498 -0
- package/lib/project-capsule-turn.js +1 -0
- package/lib/project-connection.js +6 -1
- package/lib/project-delegated-session.js +1 -0
- package/lib/project-filesystem.js +10 -10
- package/lib/project-full-access.js +86 -0
- package/lib/project-http.js +3 -1
- package/lib/project-issue-launch.js +154 -0
- package/lib/project-issues.js +58 -0
- package/lib/project-logs-mcp-server.js +8 -12
- package/lib/project-loop-interview.js +163 -0
- package/lib/project-loop.js +150 -64
- package/lib/project-message-delivery.js +4 -2
- package/lib/project-models.js +71 -3
- 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-pending-message-queue.js +264 -0
- package/lib/project-scheduled-messages.js +316 -0
- package/lib/project-scheduled-task-execution.js +236 -0
- package/lib/project-scheduled-tasks.js +481 -0
- package/lib/project-session-pair.js +16 -19
- package/lib/project-sessions.js +114 -31
- package/lib/project-tool-llm-runtime.js +48 -0
- package/lib/project-user-message.js +202 -60
- package/lib/project-workspace-query.js +25 -0
- package/lib/project.js +361 -69
- package/lib/public/app.js +85 -22
- package/lib/public/css/default-ai.css +33 -0
- package/lib/public/css/home-hub.css +15 -2
- package/lib/public/css/home-sidebar.css +50 -2
- package/lib/public/css/input.css +160 -5
- package/lib/public/css/issues.css +101 -0
- package/lib/public/css/messages.css +19 -0
- package/lib/public/css/pane.css +58 -7
- package/lib/public/css/pending-message-queue.css +187 -0
- package/lib/public/css/rewind.css +11 -2
- package/lib/public/css/scheduled-tasks.css +122 -0
- package/lib/public/index.html +42 -150
- package/lib/public/modules/app-connection.js +3 -0
- package/lib/public/modules/app-cursors.js +29 -12
- package/lib/public/modules/app-loop-ui.js +4 -8
- package/lib/public/modules/app-message-router.js +8 -0
- package/lib/public/modules/app-messages.js +34 -9
- 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/clay-file-links.js +111 -0
- package/lib/public/modules/clay-issue-links.js +131 -0
- package/lib/public/modules/clay-log-links.js +2 -0
- package/lib/public/modules/cursor-sharing-preference.js +38 -0
- package/lib/public/modules/default-ai.js +377 -0
- package/lib/public/modules/filebrowser.js +43 -2
- package/lib/public/modules/home-sidebar.js +16 -8
- package/lib/public/modules/home-surface.js +5 -0
- package/lib/public/modules/input.js +11 -20
- package/lib/public/modules/issues-context.js +117 -0
- package/lib/public/modules/issues-render.js +29 -0
- package/lib/public/modules/issues.js +270 -0
- package/lib/public/modules/loop-interview.js +151 -0
- package/lib/public/modules/markdown.js +4 -0
- package/lib/public/modules/message-delivery.js +5 -3
- package/lib/public/modules/pending-message-image-editor.js +143 -0
- package/lib/public/modules/pending-message-queue-model.js +22 -0
- package/lib/public/modules/pending-message-queue.js +449 -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 +48 -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 +459 -0
- package/lib/public/modules/scheduler-config.js +6 -52
- package/lib/public/modules/scheduler.js +9 -21
- package/lib/public/modules/sidebar-mobile.js +3 -0
- package/lib/public/modules/sidebar.js +0 -11
- package/lib/public/modules/split-view.js +2 -0
- package/lib/public/modules/sticky-note-markdown.js +1 -0
- package/lib/public/modules/terminal.js +2 -0
- package/lib/public/modules/tool-palette-order.js +3 -3
- package/lib/public/modules/tools.js +5 -2
- package/lib/public/style.css +3 -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 +157 -22
- package/lib/sdk-message-processor.js +3 -0
- package/lib/server-cursor-sharing.js +65 -0
- package/lib/server-default-ai.js +265 -0
- package/lib/server-durable-scheduler.js +26 -0
- package/lib/server-home-chat.js +29 -9
- package/lib/server-home-clay-entry.js +4 -1
- package/lib/server-home-debate-planning.js +32 -14
- package/lib/server-home-mate-creation.js +30 -11
- package/lib/server-home-models.js +14 -6
- package/lib/server-issue-links.js +18 -0
- package/lib/server.js +63 -0
- package/lib/session-notes-mcp-server.js +1 -1
- 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-search.js +4 -1
- package/lib/session-split-groups.js +35 -1
- package/lib/sessions.js +51 -3
- package/lib/tool-llm.js +1 -0
- package/lib/users-default-ai-preferences.js +80 -0
- package/lib/users-home-surface-preferences.js +2 -1
- package/lib/users-preferences.js +35 -0
- package/lib/users-scheduled-task-preferences.js +44 -0
- package/lib/users.js +16 -0
- package/lib/workspace-query-projection.js +29 -0
- package/lib/workspace-query-service.js +19 -29
- package/lib/ws-schema.js +61 -1
- package/lib/yoke/adapters/antigravity.js +1 -0
- package/lib/yoke/adapters/claude-worker.js +11 -6
- package/lib/yoke/adapters/claude.js +35 -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/package.json +1 -1
- package/lib/yoke/session-tool-transport.js +53 -0
- package/lib/yoke/user-input.js +21 -7
- package/package.json +5 -6
- package/bin/claude-relay.js +0 -6
- package/lib/public/modules/app-loop-wizard.js +0 -439
package/lib/project-loop.js
CHANGED
|
@@ -3,6 +3,10 @@ var path = require("path");
|
|
|
3
3
|
var crypto = require("crypto");
|
|
4
4
|
var { execFileSync } = require("child_process");
|
|
5
5
|
var { createLoopRegistry } = require("./scheduler");
|
|
6
|
+
var loopGuidance = require("./loop-guidance");
|
|
7
|
+
var validateCron = require("./schedule-validation").validateCron;
|
|
8
|
+
var validateScheduleRule = require("./schedule-rule").validateScheduleRule;
|
|
9
|
+
var attachLoopDurableSchedule = require("./loop-durable-schedule").attachLoopDurableSchedule;
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Attach loop engine to a project context.
|
|
@@ -62,7 +66,7 @@ function attachLoop(ctx) {
|
|
|
62
66
|
var _loopUtils = require("./utils");
|
|
63
67
|
var _loopDir = path.join(_loopConfig.CONFIG_DIR, "loops");
|
|
64
68
|
var _loopEncodedCwd = _loopUtils.resolveEncodedFile(_loopDir, cwd, ".json");
|
|
65
|
-
var _loopStatePath = path.join(_loopDir, _loopEncodedCwd + ".json");
|
|
69
|
+
var _loopStatePath = ctx.loopStatePath || path.join(_loopDir, _loopEncodedCwd + ".json");
|
|
66
70
|
|
|
67
71
|
function saveLoopState() {
|
|
68
72
|
try {
|
|
@@ -269,18 +273,43 @@ function attachLoop(ctx) {
|
|
|
269
273
|
|
|
270
274
|
// --- Loop Registry (unified one-off + scheduled) ---
|
|
271
275
|
var activeRegistryId = null; // track which registry record triggered current loop
|
|
272
|
-
var
|
|
276
|
+
var durableSchedule = null;
|
|
277
|
+
function isMultiUser() { return typeof ctx.isMultiUser === "function" ? ctx.isMultiUser() : !!ctx.isMultiUser; }
|
|
278
|
+
|
|
279
|
+
function legacyRuntimeFor(session) {
|
|
280
|
+
var record = loopRegistry.getById(loopState.loopId);
|
|
281
|
+
var linuxUser = getLinuxUserForSession(session);
|
|
282
|
+
if (!isMultiUser()) return { ok: true, linuxUser: linuxUser };
|
|
283
|
+
if (!record || !record.ownerId || session.ownerId !== record.ownerId) return { ok: false, error: "The scheduled Loop owner changed before execution." };
|
|
284
|
+
if (ctx.authorizeScheduledRun && !ctx.authorizeScheduledRun(record)) return { ok: false, error: "The scheduled Loop owner is no longer authorized for this project." };
|
|
285
|
+
if (ctx.requiresLinuxUser && ctx.requiresLinuxUser() && !linuxUser) return { ok: false, error: "The scheduled Loop owner no longer has a valid OS identity." };
|
|
286
|
+
return { ok: true, linuxUser: linuxUser };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function rejectLegacyRuntime(session, error) {
|
|
290
|
+
var message = error || "The scheduled Loop execution identity is no longer valid.";
|
|
291
|
+
var entry = { type: "error", text: message };
|
|
292
|
+
session.isProcessing = false; session.singleTurn = false; if (session.loop) session.loop.active = false;
|
|
293
|
+
session.history.push(entry); sm.appendToSessionFile(session, entry); sm.saveSessionFile(session);
|
|
294
|
+
sendToSession(session.localId, entry); sendToSession(session.localId, { type: "status", status: "idle" }); send({ type: "loop_error", text: message }); finishLoop("error");
|
|
295
|
+
}
|
|
273
296
|
|
|
274
297
|
function triggerFromQueue(record) {
|
|
275
|
-
|
|
298
|
+
if (record.createdViaScheduledTasks && ctx.authorizeScheduledRun && !ctx.authorizeScheduledRun(record)) {
|
|
299
|
+
console.error("[loop-registry] Scheduled task owner is no longer authorized: " + record.name);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
// Linked calendar schedules use their task's files. Standalone Scheduled Tasks own theirs.
|
|
276
303
|
var loopFilesId = record.id;
|
|
277
304
|
if (record.source === "schedule") {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
305
|
+
var standaloneScheduledTask = record.createdViaScheduledTasks && record.mode === "simple" && !record.linkedTaskId;
|
|
306
|
+
if (!record.linkedTaskId && !standaloneScheduledTask) {
|
|
307
|
+
failScheduledDispatch("Schedule has no linked task: " + record.name);
|
|
308
|
+
}
|
|
309
|
+
if (record.linkedTaskId) {
|
|
310
|
+
loopFilesId = record.linkedTaskId;
|
|
311
|
+
console.log("[loop-registry] Schedule triggered: " + record.name + " -> linked task " + loopFilesId);
|
|
281
312
|
}
|
|
282
|
-
loopFilesId = record.linkedTaskId;
|
|
283
|
-
console.log("[loop-registry] Schedule triggered: " + record.name + " -> linked task " + loopFilesId);
|
|
284
313
|
}
|
|
285
314
|
|
|
286
315
|
// Verify the loop directory and PROMPT.md exist
|
|
@@ -288,8 +317,7 @@ function attachLoop(ctx) {
|
|
|
288
317
|
try {
|
|
289
318
|
fs.accessSync(path.join(recDir, "PROMPT.md"));
|
|
290
319
|
} catch (e) {
|
|
291
|
-
|
|
292
|
-
return;
|
|
320
|
+
failScheduledDispatch("PROMPT.md is missing for " + loopFilesId + ".");
|
|
293
321
|
}
|
|
294
322
|
// Set the loopId to the schedule's own id (not the linked task) so sidebar groups correctly
|
|
295
323
|
loopState.loopId = record.id;
|
|
@@ -304,28 +332,65 @@ function attachLoop(ctx) {
|
|
|
304
332
|
startLoop({ maxIterations: record.maxIterations, name: record.name });
|
|
305
333
|
}
|
|
306
334
|
|
|
335
|
+
function failScheduledDispatch(message) {
|
|
336
|
+
console.error("[loop-registry] " + message);
|
|
337
|
+
send({ type: "loop_error", text: message });
|
|
338
|
+
var error = new Error(message); error.code = "SCHEDULE_DISPATCH_FAILED"; throw error;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function usesPairExecution(record) { return !!(record && (record.createdViaScheduledTasks === true || record.source === "schedule")); }
|
|
342
|
+
|
|
307
343
|
var loopRegistry = createLoopRegistry({
|
|
308
344
|
cwd: cwd,
|
|
309
|
-
|
|
310
|
-
|
|
345
|
+
registryPath: ctx.loopRegistryPath,
|
|
346
|
+
canTrigger: function (record) {
|
|
347
|
+
if (!usesPairExecution(record)) return true;
|
|
348
|
+
return ctx.canExecuteScheduledTask ? ctx.canExecuteScheduledTask(record) : false;
|
|
349
|
+
},
|
|
350
|
+
prepareTrigger: function (record) {
|
|
351
|
+
if (!usesPairExecution(record)) return null;
|
|
352
|
+
return ctx.prepareScheduledTask ? ctx.prepareScheduledTask(record, "schedule") : { ok: false };
|
|
353
|
+
},
|
|
354
|
+
onTrigger: function (record, prepared) {
|
|
355
|
+
if (usesPairExecution(record)) {
|
|
356
|
+
if (prepared && (prepared.skipped || prepared.queued)) return;
|
|
357
|
+
var scheduledResult = ctx.executeScheduledTask && ctx.executeScheduledTask(record, "schedule", prepared);
|
|
358
|
+
if (!scheduledResult || !scheduledResult.ok) failScheduledDispatch("Pair execution did not start: " + (scheduledResult && scheduledResult.error || record.name));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
// A busy non-skipping occurrence remains queued in the durable scheduler.
|
|
311
362
|
if (loopState.active || loopState.phase === "executing") {
|
|
312
363
|
if (record.skipIfRunning !== false) {
|
|
313
364
|
console.log("[loop-registry] Skipping trigger for " + record.name + " — loop already active (skipIfRunning)");
|
|
314
365
|
return;
|
|
315
366
|
}
|
|
316
|
-
console.log("[loop-registry] Loop active, queuing trigger for " + record.name);
|
|
317
|
-
pendingTriggers.push(record);
|
|
318
367
|
return;
|
|
319
368
|
}
|
|
320
369
|
|
|
321
370
|
triggerFromQueue(record);
|
|
322
371
|
},
|
|
323
372
|
onChange: function () {
|
|
373
|
+
if (durableSchedule) durableSchedule.sync();
|
|
324
374
|
send({ type: "loop_registry_updated", records: getHubSchedules() });
|
|
325
375
|
},
|
|
326
376
|
});
|
|
327
377
|
loopRegistry.load();
|
|
328
|
-
|
|
378
|
+
if (ctx.durableScheduler) {
|
|
379
|
+
durableSchedule = attachLoopDurableSchedule({
|
|
380
|
+
scheduler: ctx.durableScheduler,
|
|
381
|
+
registry: loopRegistry,
|
|
382
|
+
projectId: slug,
|
|
383
|
+
isMultiUser: ctx.isMultiUser,
|
|
384
|
+
authorize: function (record) {
|
|
385
|
+
if (!isMultiUser() && !usesPairExecution(record)) return true;
|
|
386
|
+
return !ctx.authorizeScheduledRun || ctx.authorizeScheduledRun(record);
|
|
387
|
+
},
|
|
388
|
+
canRun: function (record) {
|
|
389
|
+
if (usesPairExecution(record)) return ctx.canExecuteScheduledTask ? ctx.canExecuteScheduledTask(record) : false;
|
|
390
|
+
return !(record.skipIfRunning === false && (loopState.active || loopState.phase === "executing"));
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
} else loopRegistry.startTimer();
|
|
329
394
|
|
|
330
395
|
// Wire loop info resolution for session list broadcasts
|
|
331
396
|
sm.setResolveLoopInfo(function (loopId) {
|
|
@@ -412,8 +477,8 @@ function attachLoop(ctx) {
|
|
|
412
477
|
return;
|
|
413
478
|
}
|
|
414
479
|
|
|
415
|
-
var session = sm.createSession();
|
|
416
480
|
var loopSource = loopRegistry.getById(loopState.loopId);
|
|
481
|
+
var session = sm.createSession({ ownerId: loopSource && loopSource.ownerId || null });
|
|
417
482
|
var loopName = (loopState.wizardData && loopState.wizardData.name) || (loopSource && loopSource.name) || "";
|
|
418
483
|
var loopSourceTag = (loopSource && loopSource.source) || null;
|
|
419
484
|
var isRalphLoop = loopSourceTag === "ralph";
|
|
@@ -430,6 +495,8 @@ function attachLoop(ctx) {
|
|
|
430
495
|
maxIterations: loopState.maxIterations,
|
|
431
496
|
sessionId: session.localId,
|
|
432
497
|
});
|
|
498
|
+
var coderRuntime = legacyRuntimeFor(session);
|
|
499
|
+
if (!coderRuntime.ok) { rejectLegacyRuntime(session, coderRuntime.error); return; }
|
|
433
500
|
|
|
434
501
|
var coderCompleted = false;
|
|
435
502
|
session.onQueryComplete = function(completedSession) {
|
|
@@ -508,7 +575,7 @@ function attachLoop(ctx) {
|
|
|
508
575
|
session.acceptEditsAfterStart = true;
|
|
509
576
|
session.singleTurn = true;
|
|
510
577
|
if (loopState.settings) session.loopSettings = loopState.settings;
|
|
511
|
-
sdk.startQuery(session, loopState.promptText, undefined,
|
|
578
|
+
sdk.startQuery(session, loopState.promptText, undefined, coderRuntime.linuxUser);
|
|
512
579
|
}
|
|
513
580
|
|
|
514
581
|
function runJudge() {
|
|
@@ -549,8 +616,8 @@ function attachLoop(ctx) {
|
|
|
549
616
|
"- PASS: [brief explanation]\n" +
|
|
550
617
|
"- FAIL: [brief explanation of what is still missing]";
|
|
551
618
|
|
|
552
|
-
var judgeSession = sm.createSession();
|
|
553
619
|
var judgeSource = loopRegistry.getById(loopState.loopId);
|
|
620
|
+
var judgeSession = sm.createSession({ ownerId: judgeSource && judgeSource.ownerId || null });
|
|
554
621
|
var judgeName = (loopState.wizardData && loopState.wizardData.name) || (judgeSource && judgeSource.name) || "";
|
|
555
622
|
var judgeSourceTag = (judgeSource && judgeSource.source) || null;
|
|
556
623
|
var isRalphJudge = judgeSourceTag === "ralph";
|
|
@@ -565,6 +632,8 @@ function attachLoop(ctx) {
|
|
|
565
632
|
iteration: loopState.iteration,
|
|
566
633
|
sessionId: judgeSession.localId,
|
|
567
634
|
});
|
|
635
|
+
var judgeRuntime = legacyRuntimeFor(judgeSession);
|
|
636
|
+
if (!judgeRuntime.ok) { rejectLegacyRuntime(judgeSession, judgeRuntime.error); return; }
|
|
568
637
|
|
|
569
638
|
var judgeCompleted = false;
|
|
570
639
|
judgeSession.onQueryComplete = function(completedSession) {
|
|
@@ -625,7 +694,7 @@ function attachLoop(ctx) {
|
|
|
625
694
|
judgeSession.acceptEditsAfterStart = true;
|
|
626
695
|
judgeSession.singleTurn = true;
|
|
627
696
|
if (loopState.settings) judgeSession.loopSettings = loopState.settings;
|
|
628
|
-
sdk.startQuery(judgeSession, judgePrompt, undefined,
|
|
697
|
+
sdk.startQuery(judgeSession, judgePrompt, undefined, judgeRuntime.linuxUser);
|
|
629
698
|
}
|
|
630
699
|
|
|
631
700
|
function parseJudgeVerdict(session) {
|
|
@@ -710,14 +779,7 @@ function attachLoop(ctx) {
|
|
|
710
779
|
});
|
|
711
780
|
}
|
|
712
781
|
|
|
713
|
-
|
|
714
|
-
if (pendingTriggers.length > 0) {
|
|
715
|
-
var next = pendingTriggers.shift();
|
|
716
|
-
console.log("[loop-registry] Processing queued trigger: " + next.name);
|
|
717
|
-
setTimeout(function () {
|
|
718
|
-
triggerFromQueue(next);
|
|
719
|
-
}, 1000);
|
|
720
|
-
}
|
|
782
|
+
if (durableSchedule) durableSchedule.wake();
|
|
721
783
|
}
|
|
722
784
|
|
|
723
785
|
function resumeLoop() {
|
|
@@ -891,13 +953,7 @@ function attachLoop(ctx) {
|
|
|
891
953
|
loopState.phase = "crafting";
|
|
892
954
|
saveLoopState();
|
|
893
955
|
|
|
894
|
-
var judgeCraftPrompt =
|
|
895
|
-
"The user has already provided PROMPT.md, so do NOT create or modify PROMPT.md. " +
|
|
896
|
-
"You MUST invoke the clay-ralph skill — do NOT execute the task yourself. " +
|
|
897
|
-
"Your job is to read the existing PROMPT.md and create a JUDGE.md " +
|
|
898
|
-
"that will evaluate whether the coder session completed the task successfully.\n\n" +
|
|
899
|
-
"## Task\n" + (wData.task || "") +
|
|
900
|
-
"\n\n## Loop Directory\n" + lDir;
|
|
956
|
+
var judgeCraftPrompt = loopGuidance.legacyCraftPrompt({ judgeOnly: true, task: wData.task, directory: lDir });
|
|
901
957
|
|
|
902
958
|
var judgeCraftSession = sm.createSession();
|
|
903
959
|
judgeCraftSession.title = (isRalphCraft ? "Ralph" : "Task") + (craftName ? " " + craftName : "") + " Crafting";
|
|
@@ -934,31 +990,9 @@ function attachLoop(ctx) {
|
|
|
934
990
|
return true;
|
|
935
991
|
}
|
|
936
992
|
|
|
937
|
-
// Default: "draft" mode — Clay crafts
|
|
993
|
+
// Default: "draft" mode — Clay crafts legacy PROMPT.md/JUDGE.md files
|
|
938
994
|
var _draftIsSimple = wData.loopMode === "simple";
|
|
939
|
-
var craftingPrompt;
|
|
940
|
-
if (_draftIsSimple) {
|
|
941
|
-
craftingPrompt = "Use the /clay-ralph skill to design a Ralph Loop for the following task. " +
|
|
942
|
-
"You MUST invoke the clay-ralph skill — do NOT execute the task yourself. " +
|
|
943
|
-
"This is a Simple Loop (no judge). Your job is to create ONLY a PROMPT.md file " +
|
|
944
|
-
"that a future autonomous session will execute. Do NOT create a JUDGE.md file.\n\n" +
|
|
945
|
-
"## Task\n" + (wData.task || "") +
|
|
946
|
-
"\n\n## Loop Directory\n" + lDir;
|
|
947
|
-
} else if (wData.judgeAuthor === "me") {
|
|
948
|
-
craftingPrompt = "Use the /clay-ralph skill to design a Ralph Loop for the following task. " +
|
|
949
|
-
"You MUST invoke the clay-ralph skill — do NOT execute the task yourself. " +
|
|
950
|
-
"The user will provide their own JUDGE.md, so create ONLY a PROMPT.md file " +
|
|
951
|
-
"that a future autonomous session will execute. Do NOT create a JUDGE.md file.\n\n" +
|
|
952
|
-
"## Task\n" + (wData.task || "") +
|
|
953
|
-
"\n\n## Loop Directory\n" + lDir;
|
|
954
|
-
} else {
|
|
955
|
-
craftingPrompt = "Use the /clay-ralph skill to design a Ralph Loop for the following task. " +
|
|
956
|
-
"You MUST invoke the clay-ralph skill — do NOT execute the task yourself. " +
|
|
957
|
-
"Your job is to interview me, then create PROMPT.md and JUDGE.md files " +
|
|
958
|
-
"that a future autonomous session will execute.\n\n" +
|
|
959
|
-
"## Task\n" + (wData.task || "") +
|
|
960
|
-
"\n\n## Loop Directory\n" + lDir;
|
|
961
|
-
}
|
|
995
|
+
var craftingPrompt = loopGuidance.legacyCraftPrompt({ simple: _draftIsSimple, userJudge: wData.judgeAuthor === "me", task: wData.task, directory: lDir });
|
|
962
996
|
|
|
963
997
|
// Pre-write user-provided files before crafting starts
|
|
964
998
|
if (wData.judgeText && wData.judgeAuthor === "me") {
|
|
@@ -1106,6 +1140,27 @@ function attachLoop(ctx) {
|
|
|
1106
1140
|
// --- Schedule create (from calendar click) ---
|
|
1107
1141
|
if (msg.type === "schedule_create") {
|
|
1108
1142
|
var sData = msg.data || {};
|
|
1143
|
+
var scheduleActor = ctx.getScheduleActor ? ctx.getScheduleActor(ws) : { ownerId: null, ownerName: null, sessionId: null };
|
|
1144
|
+
if (!scheduleActor) {
|
|
1145
|
+
sendTo(ws, { type: "loop_registry_error", text: "The schedule owner could not be verified." });
|
|
1146
|
+
return true;
|
|
1147
|
+
}
|
|
1148
|
+
if (sData.cron) {
|
|
1149
|
+
var scheduleCreateCron = validateCron(sData.cron);
|
|
1150
|
+
if (!scheduleCreateCron.ok) {
|
|
1151
|
+
sendTo(ws, { type: "loop_registry_error", text: scheduleCreateCron.error });
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
sData.cron = scheduleCreateCron.cron;
|
|
1155
|
+
}
|
|
1156
|
+
if (sData.scheduleRule) {
|
|
1157
|
+
var scheduleCreateRule = validateScheduleRule(sData.scheduleRule);
|
|
1158
|
+
if (!scheduleCreateRule.ok) {
|
|
1159
|
+
sendTo(ws, { type: "loop_registry_error", text: scheduleCreateRule.error });
|
|
1160
|
+
return true;
|
|
1161
|
+
}
|
|
1162
|
+
sData.scheduleRule = scheduleCreateRule.value;
|
|
1163
|
+
}
|
|
1109
1164
|
loopRegistry.register({
|
|
1110
1165
|
name: sData.name || "Untitled",
|
|
1111
1166
|
task: sData.name || "",
|
|
@@ -1122,6 +1177,11 @@ function attachLoop(ctx) {
|
|
|
1122
1177
|
recurrenceEnd: sData.recurrenceEnd || null,
|
|
1123
1178
|
skipIfRunning: sData.skipIfRunning !== undefined ? sData.skipIfRunning : true,
|
|
1124
1179
|
intervalEnd: sData.intervalEnd || null,
|
|
1180
|
+
scheduleRule: sData.scheduleRule || null,
|
|
1181
|
+
ownerId: scheduleActor.ownerId,
|
|
1182
|
+
ownerName: scheduleActor.ownerName,
|
|
1183
|
+
sessionId: scheduleActor.sessionId,
|
|
1184
|
+
createdViaScheduledTasks: true,
|
|
1125
1185
|
});
|
|
1126
1186
|
return true;
|
|
1127
1187
|
}
|
|
@@ -1139,7 +1199,28 @@ function attachLoop(ctx) {
|
|
|
1139
1199
|
}
|
|
1140
1200
|
|
|
1141
1201
|
if (msg.type === "loop_registry_update") {
|
|
1142
|
-
|
|
1202
|
+
if (msg.data && msg.data.cron !== undefined && msg.data.cron !== null) {
|
|
1203
|
+
var scheduleUpdateCron = validateCron(msg.data.cron);
|
|
1204
|
+
if (!scheduleUpdateCron.ok) {
|
|
1205
|
+
sendTo(ws, { type: "loop_registry_error", text: scheduleUpdateCron.error });
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1208
|
+
msg.data.cron = scheduleUpdateCron.cron;
|
|
1209
|
+
}
|
|
1210
|
+
if (msg.data && msg.data.scheduleRule) {
|
|
1211
|
+
var scheduleUpdateRule = validateScheduleRule(msg.data.scheduleRule);
|
|
1212
|
+
if (!scheduleUpdateRule.ok) {
|
|
1213
|
+
sendTo(ws, { type: "loop_registry_error", text: scheduleUpdateRule.error });
|
|
1214
|
+
return true;
|
|
1215
|
+
}
|
|
1216
|
+
msg.data.scheduleRule = scheduleUpdateRule.value;
|
|
1217
|
+
}
|
|
1218
|
+
var registryUpdateData = Object.assign({}, msg.data || {});
|
|
1219
|
+
delete registryUpdateData.ownerId;
|
|
1220
|
+
delete registryUpdateData.ownerName;
|
|
1221
|
+
delete registryUpdateData.sessionId;
|
|
1222
|
+
delete registryUpdateData.createdViaScheduledTasks;
|
|
1223
|
+
var updatedRec = loopRegistry.update(msg.id, registryUpdateData);
|
|
1143
1224
|
if (!updatedRec) {
|
|
1144
1225
|
sendTo(ws, { type: "loop_registry_error", text: "Record not found" });
|
|
1145
1226
|
}
|
|
@@ -1197,15 +1278,20 @@ function attachLoop(ctx) {
|
|
|
1197
1278
|
|
|
1198
1279
|
if (msg.type === "loop_registry_rerun") {
|
|
1199
1280
|
// Re-run an existing job (one-off from library)
|
|
1200
|
-
if (loopState.active || loopState.phase === "executing") {
|
|
1201
|
-
sendTo(ws, { type: "loop_registry_error", text: "A loop is already running" });
|
|
1202
|
-
return true;
|
|
1203
|
-
}
|
|
1204
1281
|
var rerunRec = loopRegistry.getById(msg.id);
|
|
1205
1282
|
if (!rerunRec) {
|
|
1206
1283
|
sendTo(ws, { type: "loop_registry_error", text: "Record not found" });
|
|
1207
1284
|
return true;
|
|
1208
1285
|
}
|
|
1286
|
+
if (usesPairExecution(rerunRec)) {
|
|
1287
|
+
var pairRun = ctx.executeScheduledTask && ctx.executeScheduledTask(rerunRec, "manual");
|
|
1288
|
+
if (!pairRun || !pairRun.ok) sendTo(ws, { type: "loop_registry_error", text: pairRun && pairRun.error || "The scheduled pair could not start." });
|
|
1289
|
+
return true;
|
|
1290
|
+
}
|
|
1291
|
+
if (loopState.active || loopState.phase === "executing") {
|
|
1292
|
+
sendTo(ws, { type: "loop_registry_error", text: "A loop is already running" });
|
|
1293
|
+
return true;
|
|
1294
|
+
}
|
|
1209
1295
|
var rerunDir = path.join(cwd, ".claude", "loops", rerunRec.id);
|
|
1210
1296
|
try {
|
|
1211
1297
|
fs.accessSync(path.join(rerunDir, "PROMPT.md"));
|
|
@@ -1297,7 +1383,7 @@ function attachLoop(ctx) {
|
|
|
1297
1383
|
getSchedules: function () { return loopRegistry.getAll(); },
|
|
1298
1384
|
importSchedule: function (data) { return loopRegistry.register(data); },
|
|
1299
1385
|
removeSchedule: function (id) { return loopRegistry.remove(id); },
|
|
1300
|
-
stopTimer: function () { loopRegistry.stopTimer(); },
|
|
1386
|
+
stopTimer: function () { if (durableSchedule) durableSchedule.shutdown(); else loopRegistry.stopTimer(); },
|
|
1301
1387
|
};
|
|
1302
1388
|
}
|
|
1303
1389
|
|
|
@@ -29,7 +29,7 @@ function ensureRecordedMessages(session) {
|
|
|
29
29
|
return recorded;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function createProjectMessageDelivery(sendTo) {
|
|
32
|
+
function createProjectMessageDelivery(sendTo, projectSlug) {
|
|
33
33
|
function inspect(ws, session, msg) {
|
|
34
34
|
var clientMessageId = normalizeClientMessageId(msg && msg.clientMessageId);
|
|
35
35
|
if (!clientMessageId) return { clientMessageId: null, ownerId: ownerKey(ws), duplicate: false };
|
|
@@ -48,11 +48,13 @@ function createProjectMessageDelivery(sendTo) {
|
|
|
48
48
|
ensureRecordedMessages(session).set(deliveryKey(receipt.ownerId, receipt.clientMessageId), message);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function acknowledge(ws, receipt, message) {
|
|
51
|
+
function acknowledge(ws, receipt, message, session) {
|
|
52
52
|
if (!receipt || !receipt.clientMessageId) return;
|
|
53
53
|
sendTo(ws, {
|
|
54
54
|
type: "message_ack",
|
|
55
55
|
clientMessageId: receipt.clientMessageId,
|
|
56
|
+
projectSlug: projectSlug || null,
|
|
57
|
+
sessionId: session ? session.localId : null,
|
|
56
58
|
message: message || receipt.recordedMessage || null,
|
|
57
59
|
});
|
|
58
60
|
}
|
package/lib/project-models.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var yoke = require("./yoke");
|
|
2
|
+
var defaultAiRuntime = require("./default-ai-runtime");
|
|
2
3
|
|
|
3
4
|
function modelEntryValue(entry) {
|
|
4
5
|
if (!entry) return "";
|
|
@@ -12,6 +13,17 @@ function modelEntryMatches(entry, value) {
|
|
|
12
13
|
return entry.value === value || entry.id === value || entry.resolvedModel === value;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
function modelsWithEffortMetadata(models, vendorInfo) {
|
|
17
|
+
var fallback = vendorInfo && Array.isArray(vendorInfo.effortLevels) ? vendorInfo.effortLevels : [];
|
|
18
|
+
return (models || []).map(function (model) {
|
|
19
|
+
if (typeof model === "string") {
|
|
20
|
+
return { value: model, displayName: model, supportedEffortLevels: fallback.slice() };
|
|
21
|
+
}
|
|
22
|
+
if (!model || Array.isArray(model.supportedEffortLevels)) return model;
|
|
23
|
+
return Object.assign({}, model, { supportedEffortLevels: fallback.slice() });
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
function normalizeCatalogModels(models) {
|
|
16
28
|
var normalized = [];
|
|
17
29
|
models = models || [];
|
|
@@ -64,8 +76,12 @@ function attachModels(ctx) {
|
|
|
64
76
|
var serverPort = ctx.serverPort;
|
|
65
77
|
var serverTls = ctx.serverTls;
|
|
66
78
|
var serverAuthToken = ctx.serverAuthToken;
|
|
79
|
+
var resolveDefaultAi = ctx.resolveDefaultAi;
|
|
67
80
|
|
|
68
|
-
function getVendorAvailability() {
|
|
81
|
+
function getVendorAvailability(ws) {
|
|
82
|
+
if (sm.installedVendors === undefined && sdk && typeof sdk.refreshInstalledVendors === "function") {
|
|
83
|
+
sdk.refreshInstalledVendors(getLinuxUserForWs(ws));
|
|
84
|
+
}
|
|
69
85
|
var available = (sm.availableVendors || []).slice();
|
|
70
86
|
var installed = (sm.installedVendors || []).slice();
|
|
71
87
|
var ids = available.slice();
|
|
@@ -160,8 +176,8 @@ function attachModels(ctx) {
|
|
|
160
176
|
console.error("[project-models] model loading failed for " + vendor + ":", e.message || e);
|
|
161
177
|
}
|
|
162
178
|
|
|
163
|
-
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[vendor]) || [];
|
|
164
179
|
var vendorInfo = yoke.getVendorInfo(vendor);
|
|
180
|
+
var vendorModels = modelsWithEffortMetadata((sm.modelsByVendor && sm.modelsByVendor[vendor]) || [], vendorInfo);
|
|
165
181
|
var displayName = vendorInfo ? vendorInfo.displayName : vendor;
|
|
166
182
|
var status = vendorModels.length > 0 ? "ready" : (loadError ? "error" : "empty");
|
|
167
183
|
var defaultModel = "";
|
|
@@ -191,11 +207,62 @@ function attachModels(ctx) {
|
|
|
191
207
|
installedVendors: sm.installedVendors || [],
|
|
192
208
|
status: status,
|
|
193
209
|
error: errorText,
|
|
210
|
+
effortLevels: vendorInfo && Array.isArray(vendorInfo.effortLevels) ? vendorInfo.effortLevels.slice() : [],
|
|
194
211
|
};
|
|
195
212
|
}
|
|
196
213
|
|
|
197
214
|
async function resolveConfiguredModel(ws, alias) {
|
|
198
|
-
|
|
215
|
+
if (typeof resolveDefaultAi === "function") {
|
|
216
|
+
var defaultSelection = await resolveDefaultAi(ws);
|
|
217
|
+
var normalizedAlias = alias === "fast" || alias === "deep" ? alias : "standard";
|
|
218
|
+
if (!defaultSelection || !defaultSelection.ready) {
|
|
219
|
+
return {
|
|
220
|
+
status: "error",
|
|
221
|
+
vendor: defaultSelection && defaultSelection.vendor || "",
|
|
222
|
+
vendorName: defaultSelection && defaultSelection.vendor || "",
|
|
223
|
+
model: "",
|
|
224
|
+
modelName: "",
|
|
225
|
+
alias: normalizedAlias,
|
|
226
|
+
effort: "",
|
|
227
|
+
error: defaultSelection && defaultSelection.error || "Default AI is unavailable.",
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
var selectedModel = defaultSelection.model;
|
|
231
|
+
var selectedName = defaultSelection.model;
|
|
232
|
+
if (normalizedAlias !== "standard") {
|
|
233
|
+
var selectedCatalog = await getVendorCatalog(ws, defaultSelection.vendor);
|
|
234
|
+
var aliasEntry = selectCatalogModel(selectedCatalog.models || [], defaultSelection.model, normalizedAlias);
|
|
235
|
+
if (!aliasEntry || selectedCatalog.status !== "ready") {
|
|
236
|
+
return {
|
|
237
|
+
status: "error",
|
|
238
|
+
vendor: defaultSelection.vendor,
|
|
239
|
+
vendorName: defaultSelection.vendor,
|
|
240
|
+
model: "",
|
|
241
|
+
modelName: "",
|
|
242
|
+
alias: normalizedAlias,
|
|
243
|
+
effort: defaultSelection.effort || "",
|
|
244
|
+
error: selectedCatalog.error || "The selected Default AI provider has no available model.",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
selectedModel = aliasEntry.value;
|
|
248
|
+
selectedName = aliasEntry.displayName || selectedModel;
|
|
249
|
+
var aliasEffort = defaultAiRuntime.resolveEffort({ effort: defaultSelection.effort || "" }, aliasEntry.source, selectedCatalog);
|
|
250
|
+
if (!aliasEffort.ok) aliasEffort = defaultAiRuntime.resolveEffort(null, aliasEntry.source, selectedCatalog);
|
|
251
|
+
defaultSelection = Object.assign({}, defaultSelection, { effort: aliasEffort.effort || "" });
|
|
252
|
+
}
|
|
253
|
+
var selectedInfo = yoke.getVendorInfo(defaultSelection.vendor);
|
|
254
|
+
return {
|
|
255
|
+
status: "ready",
|
|
256
|
+
vendor: defaultSelection.vendor,
|
|
257
|
+
vendorName: selectedInfo && selectedInfo.displayName ? selectedInfo.displayName : defaultSelection.vendor,
|
|
258
|
+
alias: normalizedAlias,
|
|
259
|
+
model: selectedModel,
|
|
260
|
+
modelName: selectedName,
|
|
261
|
+
effort: defaultSelection.effort || "",
|
|
262
|
+
error: "",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
var availability = getVendorAvailability(ws);
|
|
199
266
|
var candidates = [];
|
|
200
267
|
var preferredVendor = sm.defaultVendor || "";
|
|
201
268
|
if (preferredVendor) candidates.push(preferredVendor);
|
|
@@ -341,5 +408,6 @@ module.exports = {
|
|
|
341
408
|
modelEntryMatches: modelEntryMatches,
|
|
342
409
|
modelEntryValue: modelEntryValue,
|
|
343
410
|
normalizeCatalogModels: normalizeCatalogModels,
|
|
411
|
+
modelsWithEffortMetadata: modelsWithEffortMetadata,
|
|
344
412
|
selectCatalogModel: selectCatalogModel,
|
|
345
413
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function stopAutonomousWork(session, roles, turnControl, taskControl, workerPermission) {
|
|
2
|
+
if (!roles) return false;
|
|
3
|
+
var runId = session.autonomousRun && session.autonomousRun.id;
|
|
4
|
+
var cancelled = taskControl.cancelAll(roles.worker, runId);
|
|
5
|
+
var owned = roles.worker._pairDelegation && roles.worker._pairDelegation.autonomousRunId === runId;
|
|
6
|
+
if (!owned && !cancelled) return true;
|
|
7
|
+
turnControl.markHumanStop(session);
|
|
8
|
+
if (!owned) return true;
|
|
9
|
+
taskControl.markInterruption(roles.worker, "user", "The Until complete run was stopped.");
|
|
10
|
+
workerPermission.cancelForSession(roles.worker, "The Until complete run was stopped.");
|
|
11
|
+
roles.worker.taskStopRequested = true;
|
|
12
|
+
if (roles.worker.abortController) roles.worker.abortController.abort();
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { stopAutonomousWork: stopAutonomousWork };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function stopOwnedPair(session, roles, turnControl, taskControl, workerPermission, reason) {
|
|
2
|
+
if (!roles) return false;
|
|
3
|
+
turnControl.markHumanStop(session);
|
|
4
|
+
taskControl.cancelAll(roles.worker);
|
|
5
|
+
taskControl.markInterruption(roles.worker, "user", reason || "The human stopped this scheduled run.");
|
|
6
|
+
workerPermission.cancelForSession(roles.worker, reason || "The human stopped this scheduled run.");
|
|
7
|
+
var members = [roles.driver, roles.worker];
|
|
8
|
+
for (var i = 0; i < members.length; i++) {
|
|
9
|
+
members[i].taskStopRequested = true;
|
|
10
|
+
if (members[i].queryInstance && typeof members[i].queryInstance.close === "function") {
|
|
11
|
+
try { members[i].queryInstance.close(); } catch (e) {}
|
|
12
|
+
}
|
|
13
|
+
if (members[i].abortController) { try { members[i].abortController.abort(); } catch (e) {} }
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { stopOwnedPair: stopOwnedPair };
|
|
@@ -67,6 +67,7 @@ function attachPairTaskControl(ctx) {
|
|
|
67
67
|
workerSessionId: worker.localId,
|
|
68
68
|
driverSessionId: caller.localId,
|
|
69
69
|
message: message,
|
|
70
|
+
autonomousRunId: caller.autonomousRun && caller.autonomousRun.id || null,
|
|
70
71
|
createdAt: Date.now(),
|
|
71
72
|
status: "running",
|
|
72
73
|
};
|
|
@@ -118,6 +119,18 @@ function attachPairTaskControl(ctx) {
|
|
|
118
119
|
return result({ status: "cancelled", taskId: task.taskId });
|
|
119
120
|
} catch (err) { return result({ status: "rejected", taskId: args.taskId || null, reason: err.message || String(err) }); }
|
|
120
121
|
}
|
|
122
|
+
function cancelAll(worker, autonomousRunId) {
|
|
123
|
+
var queue = queueFor(worker);
|
|
124
|
+
var cancelled = 0;
|
|
125
|
+
for (var i = 0; i < queue.length; i++) {
|
|
126
|
+
if (queue[i].status !== "queued" && queue[i].status !== "waiting_for_interrupt") continue;
|
|
127
|
+
if (autonomousRunId && queue[i].autonomousRunId !== autonomousRunId) continue;
|
|
128
|
+
queue[i].status = "cancelled";
|
|
129
|
+
queue[i].completedAt = Date.now();
|
|
130
|
+
cancelled++;
|
|
131
|
+
}
|
|
132
|
+
return cancelled;
|
|
133
|
+
}
|
|
121
134
|
function markInterruption(worker, by, reason, targetTaskId) {
|
|
122
135
|
var active = current(worker);
|
|
123
136
|
worker._pairInterruption = {
|
|
@@ -258,7 +271,7 @@ function attachPairTaskControl(ctx) {
|
|
|
258
271
|
function workerToolDefs(worker) {
|
|
259
272
|
return [{ name: "report_partner_outcome", description: "Report structured, Worker-observed task outcome data. This does not claim Driver verification.", inputSchema: buildShape({ taskId: { type: "string" }, outcome: { type: "string" }, summary: { type: "string" }, changedFiles: { type: "array", items: { type: "string" } }, verifications: { type: "array", items: { type: "object" } }, unverified: { type: "array", items: { type: "string" } }, processes: { type: "array", items: { type: "string" } }, nextAction: { type: "string" } }, ["taskId", "outcome"]), handler: function (args) { return report(args || {}, worker); } }];
|
|
260
273
|
}
|
|
261
|
-
return { begin: begin, cancel: cancel, complete: complete, drain: drain, driverToolDefs: driverToolDefs, ensureSystemInterruption: ensureSystemInterruption, inspect: inspect, markInterruption: markInterruption, queue: queue, replaceTask: replaceTask, resumeTask: resumeTask, status: status, workerToolDefs: workerToolDefs };
|
|
274
|
+
return { begin: begin, cancel: cancel, cancelAll: cancelAll, complete: complete, drain: drain, driverToolDefs: driverToolDefs, ensureSystemInterruption: ensureSystemInterruption, inspect: inspect, markInterruption: markInterruption, queue: queue, replaceTask: replaceTask, resumeTask: resumeTask, status: status, workerToolDefs: workerToolDefs };
|
|
262
275
|
}
|
|
263
276
|
|
|
264
277
|
module.exports = { attachPairTaskControl: attachPairTaskControl };
|