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.
Files changed (155) hide show
  1. package/LICENSE +661 -21
  2. package/LICENSES/MIT-legacy.txt +21 -0
  3. package/LICENSES/README.md +7 -0
  4. package/README.md +2 -2
  5. package/bin/cli.js +0 -2
  6. package/lib/autonomous-run-record.js +32 -0
  7. package/lib/autonomous-run-scheduler.js +92 -0
  8. package/lib/daemon.js +0 -2
  9. package/lib/default-ai-runtime.js +117 -0
  10. package/lib/durable-scheduler-record.js +61 -0
  11. package/lib/durable-scheduler-store.js +185 -0
  12. package/lib/durable-scheduler.js +495 -0
  13. package/lib/git-cli.js +1 -0
  14. package/lib/issue-work-session.js +10 -0
  15. package/lib/issues-comments.js +40 -0
  16. package/lib/issues-commit-verification.js +30 -0
  17. package/lib/issues-mcp-server.js +27 -0
  18. package/lib/issues-schema.js +119 -0
  19. package/lib/issues-service.js +339 -0
  20. package/lib/issues-store.js +210 -0
  21. package/lib/loop-cron.js +47 -0
  22. package/lib/loop-durable-schedule.js +159 -0
  23. package/lib/loop-guidance.js +35 -0
  24. package/lib/loop-registry-occurrence.js +76 -0
  25. package/lib/model-entry.js +15 -0
  26. package/lib/project-autonomous-run.js +498 -0
  27. package/lib/project-capsule-turn.js +1 -0
  28. package/lib/project-connection.js +6 -1
  29. package/lib/project-delegated-session.js +1 -0
  30. package/lib/project-filesystem.js +10 -10
  31. package/lib/project-full-access.js +86 -0
  32. package/lib/project-http.js +3 -1
  33. package/lib/project-issue-launch.js +154 -0
  34. package/lib/project-issues.js +58 -0
  35. package/lib/project-logs-mcp-server.js +8 -12
  36. package/lib/project-loop-interview.js +163 -0
  37. package/lib/project-loop.js +150 -64
  38. package/lib/project-message-delivery.js +4 -2
  39. package/lib/project-models.js +71 -3
  40. package/lib/project-pair-autonomous-stop.js +16 -0
  41. package/lib/project-pair-owned-stop.js +18 -0
  42. package/lib/project-pair-task-control.js +14 -1
  43. package/lib/project-pending-message-queue.js +264 -0
  44. package/lib/project-scheduled-messages.js +316 -0
  45. package/lib/project-scheduled-task-execution.js +236 -0
  46. package/lib/project-scheduled-tasks.js +481 -0
  47. package/lib/project-session-pair.js +16 -19
  48. package/lib/project-sessions.js +114 -31
  49. package/lib/project-tool-llm-runtime.js +48 -0
  50. package/lib/project-user-message.js +202 -60
  51. package/lib/project-workspace-query.js +25 -0
  52. package/lib/project.js +361 -69
  53. package/lib/public/app.js +85 -22
  54. package/lib/public/css/default-ai.css +33 -0
  55. package/lib/public/css/home-hub.css +15 -2
  56. package/lib/public/css/home-sidebar.css +50 -2
  57. package/lib/public/css/input.css +160 -5
  58. package/lib/public/css/issues.css +101 -0
  59. package/lib/public/css/messages.css +19 -0
  60. package/lib/public/css/pane.css +58 -7
  61. package/lib/public/css/pending-message-queue.css +187 -0
  62. package/lib/public/css/rewind.css +11 -2
  63. package/lib/public/css/scheduled-tasks.css +122 -0
  64. package/lib/public/index.html +42 -150
  65. package/lib/public/modules/app-connection.js +3 -0
  66. package/lib/public/modules/app-cursors.js +29 -12
  67. package/lib/public/modules/app-loop-ui.js +4 -8
  68. package/lib/public/modules/app-message-router.js +8 -0
  69. package/lib/public/modules/app-messages.js +34 -9
  70. package/lib/public/modules/app-projects.js +2 -0
  71. package/lib/public/modules/app-rate-limit.js +8 -5
  72. package/lib/public/modules/autonomous-run.js +285 -0
  73. package/lib/public/modules/clay-file-links.js +111 -0
  74. package/lib/public/modules/clay-issue-links.js +131 -0
  75. package/lib/public/modules/clay-log-links.js +2 -0
  76. package/lib/public/modules/cursor-sharing-preference.js +38 -0
  77. package/lib/public/modules/default-ai.js +377 -0
  78. package/lib/public/modules/filebrowser.js +43 -2
  79. package/lib/public/modules/home-sidebar.js +16 -8
  80. package/lib/public/modules/home-surface.js +5 -0
  81. package/lib/public/modules/input.js +11 -20
  82. package/lib/public/modules/issues-context.js +117 -0
  83. package/lib/public/modules/issues-render.js +29 -0
  84. package/lib/public/modules/issues.js +270 -0
  85. package/lib/public/modules/loop-interview.js +151 -0
  86. package/lib/public/modules/markdown.js +4 -0
  87. package/lib/public/modules/message-delivery.js +5 -3
  88. package/lib/public/modules/pending-message-image-editor.js +143 -0
  89. package/lib/public/modules/pending-message-queue-model.js +22 -0
  90. package/lib/public/modules/pending-message-queue.js +449 -0
  91. package/lib/public/modules/project-logs.js +2 -2
  92. package/lib/public/modules/scheduled-message-state.js +46 -0
  93. package/lib/public/modules/scheduled-task-edit-state.js +7 -0
  94. package/lib/public/modules/scheduled-task-engine.js +48 -0
  95. package/lib/public/modules/scheduled-task-form.js +122 -0
  96. package/lib/public/modules/scheduled-task-recurrence.js +46 -0
  97. package/lib/public/modules/scheduled-task-schedule-rule.js +115 -0
  98. package/lib/public/modules/scheduled-tasks.js +459 -0
  99. package/lib/public/modules/scheduler-config.js +6 -52
  100. package/lib/public/modules/scheduler.js +9 -21
  101. package/lib/public/modules/sidebar-mobile.js +3 -0
  102. package/lib/public/modules/sidebar.js +0 -11
  103. package/lib/public/modules/split-view.js +2 -0
  104. package/lib/public/modules/sticky-note-markdown.js +1 -0
  105. package/lib/public/modules/terminal.js +2 -0
  106. package/lib/public/modules/tool-palette-order.js +3 -3
  107. package/lib/public/modules/tools.js +5 -2
  108. package/lib/public/style.css +3 -0
  109. package/lib/record-revision.js +11 -0
  110. package/lib/schedule-rule.js +151 -0
  111. package/lib/schedule-validation.js +124 -0
  112. package/lib/scheduled-run-state.js +87 -0
  113. package/lib/scheduled-task-records.js +127 -0
  114. package/lib/scheduler.js +179 -196
  115. package/lib/sdk-bridge.js +157 -22
  116. package/lib/sdk-message-processor.js +3 -0
  117. package/lib/server-cursor-sharing.js +65 -0
  118. package/lib/server-default-ai.js +265 -0
  119. package/lib/server-durable-scheduler.js +26 -0
  120. package/lib/server-home-chat.js +29 -9
  121. package/lib/server-home-clay-entry.js +4 -1
  122. package/lib/server-home-debate-planning.js +32 -14
  123. package/lib/server-home-mate-creation.js +30 -11
  124. package/lib/server-home-models.js +14 -6
  125. package/lib/server-issue-links.js +18 -0
  126. package/lib/server.js +63 -0
  127. package/lib/session-notes-mcp-server.js +1 -1
  128. package/lib/session-pair-factory.js +42 -4
  129. package/lib/session-pair-query-start.js +22 -0
  130. package/lib/session-query-tool-bridge.js +52 -0
  131. package/lib/session-search.js +4 -1
  132. package/lib/session-split-groups.js +35 -1
  133. package/lib/sessions.js +51 -3
  134. package/lib/tool-llm.js +1 -0
  135. package/lib/users-default-ai-preferences.js +80 -0
  136. package/lib/users-home-surface-preferences.js +2 -1
  137. package/lib/users-preferences.js +35 -0
  138. package/lib/users-scheduled-task-preferences.js +44 -0
  139. package/lib/users.js +16 -0
  140. package/lib/workspace-query-projection.js +29 -0
  141. package/lib/workspace-query-service.js +19 -29
  142. package/lib/ws-schema.js +61 -1
  143. package/lib/yoke/adapters/antigravity.js +1 -0
  144. package/lib/yoke/adapters/claude-worker.js +11 -6
  145. package/lib/yoke/adapters/claude.js +35 -1
  146. package/lib/yoke/adapters/codex.js +3 -0
  147. package/lib/yoke/claude-user-input.js +2 -0
  148. package/lib/yoke/index.js +2 -0
  149. package/lib/yoke/mcp-bridge-server.js +7 -1
  150. package/lib/yoke/package.json +1 -1
  151. package/lib/yoke/session-tool-transport.js +53 -0
  152. package/lib/yoke/user-input.js +21 -7
  153. package/package.json +5 -6
  154. package/bin/claude-relay.js +0 -6
  155. package/lib/public/modules/app-loop-wizard.js +0 -439
@@ -0,0 +1,46 @@
1
+ import { store } from './store.js';
2
+
3
+ function correlation(message) {
4
+ if (!message || !message.jobId || !message.revision) return null;
5
+ return { jobId: message.jobId, revision: message.revision };
6
+ }
7
+
8
+ function sameCorrelation(left, right) {
9
+ return !!left && !!right && left.jobId === right.jobId && left.revision === right.revision;
10
+ }
11
+
12
+ export function applyScheduledMessageQueued(message, actions) {
13
+ store.set({ scheduledMessageCorrelation: correlation(message) });
14
+ actions.add(message.text, message.resetsAt, message);
15
+ actions.setDisabled(true);
16
+ }
17
+
18
+ export function applyScheduledMessageTerminal(message, actions) {
19
+ var current = store.get('scheduledMessageCorrelation');
20
+ if (current && !sameCorrelation(current, correlation(message))) return false;
21
+ store.set({ scheduledMessageCorrelation: null });
22
+ actions.remove();
23
+ actions.setDisabled(false);
24
+ return true;
25
+ }
26
+
27
+ export function applyScheduledMessageState(message, actions) {
28
+ actions.remove();
29
+ actions.setDisabled(false);
30
+ var pending = message && message.scheduledMessage;
31
+ store.set({ scheduledMessageCorrelation: correlation(pending) });
32
+ if (!pending && message && message.interrupted && actions.interrupted) actions.interrupted(message.interrupted);
33
+ if (!pending) return false;
34
+ actions.add(pending.text, pending.resetsAt, pending);
35
+ actions.setDisabled(true);
36
+ return true;
37
+ }
38
+
39
+ export function scheduledMessageControl(type, correlation) {
40
+ var message = { type: type };
41
+ if (correlation && correlation.jobId && correlation.revision) {
42
+ message.jobId = correlation.jobId;
43
+ message.revision = correlation.revision;
44
+ }
45
+ return message;
46
+ }
@@ -0,0 +1,7 @@
1
+ export function reconcileScheduledTaskEdit(editState, serverRecord) {
2
+ if (!editState || !serverRecord || editState.id !== serverRecord.id) return editState || null;
3
+ if (Number(editState.revision) === Number(serverRecord.revision)) return editState;
4
+ return Object.assign({}, editState, {
5
+ conflict: "This task changed while you were editing. Your text is preserved; reopen or reconcile it before saving.",
6
+ });
7
+ }
@@ -0,0 +1,48 @@
1
+ import { effortLevelsFor, effortDisplayName } from './app-panels.js';
2
+
3
+ function modelValue(model) { return typeof model === "string" ? model : model && (model.value || model.id) || ""; }
4
+ function modelLabel(model) { return typeof model === "string" ? model : model && (model.displayName || model.name || model.value || model.id) || ""; }
5
+
6
+ function fillRuntimeRole(root, state, role, runtime) {
7
+ var vendorSelect = root.querySelector('[data-runtime="' + role + '-vendor"]');
8
+ var modelSelect = root.querySelector('[data-runtime="' + role + '-model"]');
9
+ var effortSelect = root.querySelector('[data-runtime="' + role + '-effort"]');
10
+ if (!vendorSelect || !modelSelect || !effortSelect) return null;
11
+ var installed = state && (state.runtimeInstalledVendors || state.installedVendors) || [];
12
+ runtime = runtime || {};
13
+ var fallbackIndex = role === "worker" && installed.length > 1 ? 1 : 0;
14
+ var vendor = runtime.vendor || installed[fallbackIndex] || "";
15
+ vendorSelect.innerHTML = '<option value="">Choose vendor</option>';
16
+ for (var i = 0; i < installed.length; i++) {
17
+ var vendorOption = document.createElement("option"); vendorOption.value = installed[i]; vendorOption.textContent = installed[i].charAt(0).toUpperCase() + installed[i].slice(1); vendorSelect.appendChild(vendorOption);
18
+ }
19
+ if (vendor && installed.indexOf(vendor) === -1) { var unavailableVendor = document.createElement("option"); unavailableVendor.value = vendor; unavailableVendor.textContent = "Unavailable: " + vendor; unavailableVendor.disabled = true; vendorSelect.appendChild(unavailableVendor); }
20
+ vendorSelect.value = vendor;
21
+ var models = state && state.modelsByVendor && state.modelsByVendor[vendor] || [];
22
+ var ready = !!(state && state.catalogReadyByVendor && state.catalogReadyByVendor[vendor]);
23
+ modelSelect.innerHTML = '<option value="">Choose model</option>';
24
+ var selectedModel = runtime.vendor === vendor ? runtime.model || "" : ""; var foundModel = !selectedModel;
25
+ for (var mi = 0; mi < models.length; mi++) { var option = document.createElement("option"); option.value = modelValue(models[mi]); option.textContent = modelLabel(models[mi]); modelSelect.appendChild(option); if (option.value === selectedModel) foundModel = true; }
26
+ if (!foundModel) { var unavailableModel = document.createElement("option"); unavailableModel.value = selectedModel; unavailableModel.textContent = "Unavailable: " + selectedModel; unavailableModel.disabled = true; modelSelect.appendChild(unavailableModel); }
27
+ modelSelect.value = selectedModel;
28
+ var levels = effortLevelsFor(vendor, models, modelSelect.value);
29
+ effortSelect.innerHTML = '<option value="">Choose effort</option>';
30
+ for (var ei = 0; ei < levels.length; ei++) { var effortOption = document.createElement("option"); effortOption.value = levels[ei]; effortOption.textContent = effortDisplayName(levels[ei]); effortSelect.appendChild(effortOption); }
31
+ var selectedEffort = runtime.vendor === vendor ? runtime.effort || "" : "";
32
+ if (selectedEffort && levels.indexOf(selectedEffort) === -1) { var unavailableEffort = document.createElement("option"); unavailableEffort.value = selectedEffort; unavailableEffort.textContent = "Unavailable: " + selectedEffort; unavailableEffort.disabled = true; effortSelect.appendChild(unavailableEffort); }
33
+ effortSelect.value = selectedEffort;
34
+ modelSelect.disabled = !ready; effortSelect.disabled = !ready;
35
+ return { vendor: vendor, model: modelSelect.value, effort: effortSelect.value };
36
+ }
37
+
38
+ export function renderExecutionRuntime(root, state, execution) {
39
+ execution = execution || {};
40
+ return { driver: fillRuntimeRole(root, state, "driver", execution.driver), worker: fillRuntimeRole(root, state, "worker", execution.worker) };
41
+ }
42
+
43
+ export function readExecutionRuntime(root) {
44
+ function role(name) {
45
+ return { vendor: root.querySelector('[data-runtime="' + name + '-vendor"]').value, model: root.querySelector('[data-runtime="' + name + '-model"]').value || null, effort: root.querySelector('[data-runtime="' + name + '-effort"]').value || null };
46
+ }
47
+ return { driver: role("driver"), worker: role("worker") };
48
+ }
@@ -0,0 +1,122 @@
1
+ import { isStrictCron, humanRecurrence } from './scheduled-task-recurrence.js';
2
+ import { humanScheduleRule, scheduleRuleState, serializeScheduleRule } from './scheduled-task-schedule-rule.js';
3
+ import { readExecutionRuntime, renderExecutionRuntime } from './scheduled-task-engine.js';
4
+
5
+ var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
6
+
7
+ function pad(value) { return String(value).padStart(2, "0"); }
8
+
9
+ function defaultOnce() {
10
+ var date = new Date();
11
+ date.setMinutes(0, 0, 0); date.setHours(date.getHours() + 1);
12
+ return { date: date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()), time: pad(date.getHours()) + ":00" };
13
+ }
14
+
15
+ export function editorFields(data, mode) {
16
+ var saveLabel = mode === "edit" ? "Save changes" : "Create task";
17
+ var days = DAY_NAMES.map(function (day, index) { return '<label><input data-field="weekday" type="checkbox" value="' + index + '" aria-label="' + day + '"><span title="' + day + '">' + day.slice(0, 1) + '</span></label>'; }).join("");
18
+ return '<label>Name<input data-field="name" maxlength="120" autocomplete="off"></label>' +
19
+ '<label>Instructions<textarea data-field="instructions" maxlength="20000"></textarea></label>' +
20
+ '<fieldset class="scheduled-task-schedule-card"><legend>Schedule</legend>' +
21
+ '<div class="scheduled-task-date-time"><label>Date<input data-field="date" type="date"></label><label>Time<input data-field="time" type="time"></label></div>' +
22
+ '<details class="scheduled-task-accordion" data-accordion="repeat"><summary><span><strong>Repeat</strong><small data-summary="repeat">Does not repeat</small></span></summary><div class="scheduled-task-accordion-body">' +
23
+ '<label class="scheduled-task-checkbox"><input data-field="repeatEnabled" type="checkbox"><span>Repeat this task</span></label>' +
24
+ '<div class="scheduled-task-every"><span>Every</span><input data-field="repeatEvery" aria-label="Repeat every" type="number" min="1" max="99"><select data-field="repeatUnit" aria-label="Repeat unit"><option value="day">day</option><option value="week">week</option><option value="month">month</option><option value="year">year</option></select></div>' +
25
+ '<div data-repeat="week"><span class="scheduled-task-control-label">On</span><div class="scheduled-task-weekdays">' + days + '</div></div>' +
26
+ '<div class="scheduled-task-ending"><span>Ends</span><select data-field="repeatEndType" aria-label="Repeat ends"><option value="never">Never</option><option value="until">Until date</option><option value="after">After occurrences</option></select><input data-field="repeatEndDate" aria-label="Repeat until date" type="date"><span data-end-label="repeat-count"><input data-field="repeatEndCount" aria-label="Repeat occurrence count" type="number" min="1" max="999"> occurrences</span></div>' +
27
+ '</div></details>' +
28
+ '<details class="scheduled-task-accordion" data-accordion="interval"><summary><span><strong>Interval</strong><small data-summary="interval">Run once at selected time</small></span></summary><div class="scheduled-task-accordion-body">' +
29
+ '<label class="scheduled-task-checkbox"><input data-field="intervalEnabled" type="checkbox"><span>Run repeatedly within each active day</span></label>' +
30
+ '<div class="scheduled-task-every"><span>Every</span><input data-field="intervalEvery" aria-label="Interval every" type="number" min="1" max="999"><select data-field="intervalUnit" aria-label="Interval unit"><option value="minute">minutes</option><option value="hour">hours</option></select></div>' +
31
+ '<div class="scheduled-task-ending"><span>Ends</span><select data-field="intervalEndType" aria-label="Interval ends"><option value="allday">All day</option><option value="after">Stop after runs</option><option value="until">Stop at time</option></select><span data-end-label="interval-count"><input data-field="intervalEndCount" aria-label="Interval run count" type="number" min="1" max="999"> runs</span><input data-field="intervalEndTime" aria-label="Interval stop time" type="time"></div>' +
32
+ '</div></details>' +
33
+ '<details class="scheduled-task-advanced" data-accordion="custom"><summary>Advanced custom schedule</summary><div class="scheduled-task-advanced-body"><label class="scheduled-task-checkbox"><input data-field="advanced" type="checkbox"><span>Use a five-field cron expression</span></label><label data-advanced-cron>Custom cron<input data-field="customCron" class="scheduled-task-cron" autocomplete="off" spellcheck="false"><small>Existing custom expressions remain unchanged unless edited here.</small></label></div></details>' +
34
+ '<div class="scheduled-task-schedule-summary"><strong data-schedule-summary></strong><span>Times use the server local timezone: <span data-timezone></span>.</span></div>' +
35
+ '</fieldset>' +
36
+ '<fieldset class="scheduled-task-runtime"><legend>Execution pair</legend><div class="scheduled-task-runtime-row"><strong>Driver</strong><select data-runtime="driver-vendor" aria-label="Driver vendor"></select><select data-runtime="driver-model" aria-label="Driver model"></select><select data-runtime="driver-effort" aria-label="Driver effort"></select></div><div class="scheduled-task-runtime-row"><strong>Split Worker</strong><select data-runtime="worker-vendor" aria-label="Split Worker vendor"></select><select data-runtime="worker-model" aria-label="Split Worker model"></select><select data-runtime="worker-effort" aria-label="Split Worker effort"></select></div><label class="scheduled-task-checkbox"><input data-field="skipIfRunning" type="checkbox"><span>Skip a scheduled start while this task already has an active run</span><small>When off, overlapping starts are combined into one queued run.</small></label></fieldset>' +
37
+ '<p class="scheduled-task-form-error" role="alert"></p><div class="scheduled-task-form-actions"><button type="button" data-action="' + (mode === "edit" ? "edit-cancel" : "draft-cancel") + '">Cancel</button><button type="button" class="primary" data-action="' + (mode === "edit" ? "edit-save" : "draft-save") + '">' + saveLabel + '</button></div>';
38
+ }
39
+
40
+ function readSchedule(root) {
41
+ return {
42
+ originalCron: root.dataset.originalCron || "", scheduleDirty: root.dataset.scheduleDirty === "true",
43
+ advanced: root.querySelector('[data-field="advanced"]').checked, customCron: root.querySelector('[data-field="customCron"]').value.trim(), date: root.querySelector('[data-field="date"]').value, time: root.querySelector('[data-field="time"]').value,
44
+ repeatEnabled: root.querySelector('[data-field="repeatEnabled"]').checked, repeatEvery: Number(root.querySelector('[data-field="repeatEvery"]').value), repeatUnit: root.querySelector('[data-field="repeatUnit"]').value,
45
+ weekdays: Array.from(root.querySelectorAll('[data-field="weekday"]:checked')).map(function (input) { return Number(input.value); }), repeatEndType: root.querySelector('[data-field="repeatEndType"]').value, repeatEndDate: root.querySelector('[data-field="repeatEndDate"]').value, repeatEndCount: Number(root.querySelector('[data-field="repeatEndCount"]').value),
46
+ intervalEnabled: root.querySelector('[data-field="intervalEnabled"]').checked, intervalEvery: Number(root.querySelector('[data-field="intervalEvery"]').value), intervalUnit: root.querySelector('[data-field="intervalUnit"]').value, intervalEndType: root.querySelector('[data-field="intervalEndType"]').value, intervalEndCount: Number(root.querySelector('[data-field="intervalEndCount"]').value), intervalEndTime: root.querySelector('[data-field="intervalEndTime"]').value,
47
+ };
48
+ }
49
+
50
+ function setValue(root, field, value) { root.querySelector('[data-field="' + field + '"]').value = value; }
51
+
52
+ function normalizeEditorRoot(root) {
53
+ if (!root || !root.matches) return root;
54
+ if (root.matches(".scheduled-task-draft-form, .scheduled-task-edit-form")) return root;
55
+ return root.querySelector(".scheduled-task-draft-form, .scheduled-task-edit-form") || root;
56
+ }
57
+
58
+ function updateVisibility(root) {
59
+ var state = readSchedule(root); var serialized = serializeScheduleRule(state);
60
+ root.querySelector('[data-repeat="week"]').classList.toggle("hidden", !state.repeatEnabled || state.repeatUnit !== "week");
61
+ root.querySelector('[data-field="repeatEndDate"]').classList.toggle("hidden", !state.repeatEnabled || state.repeatEndType !== "until");
62
+ root.querySelector('[data-end-label="repeat-count"]').classList.toggle("hidden", !state.repeatEnabled || state.repeatEndType !== "after");
63
+ root.querySelector('[data-end-label="interval-count"]').classList.toggle("hidden", !state.intervalEnabled || state.intervalEndType !== "after");
64
+ root.querySelector('[data-field="intervalEndTime"]').classList.toggle("hidden", !state.intervalEnabled || state.intervalEndType !== "until");
65
+ root.querySelector('[data-advanced-cron]').classList.toggle("hidden", !state.advanced);
66
+ root.querySelector('[data-summary="repeat"]').textContent = state.advanced ? "Advanced custom expression" : (state.repeatEnabled ? humanScheduleRule(serialized.scheduleRule).split(" · every ")[0] : "Does not repeat");
67
+ root.querySelector('[data-summary="interval"]').textContent = state.intervalEnabled ? "Every " + state.intervalEvery + " " + state.intervalUnit + (state.intervalEvery === 1 ? "" : "s") : "Run once at selected time";
68
+ root.querySelector('[data-schedule-summary]').textContent = state.advanced ? (isStrictCron(state.customCron) ? humanRecurrence(state.customCron) : "Complete the advanced custom expression") : humanScheduleRule(serialized.scheduleRule);
69
+ }
70
+
71
+ export function fillEditor(root, data, timeZone, runtimeState) {
72
+ root = normalizeEditorRoot(root);
73
+ var defaults = defaultOnce(); var state = data._scheduleState || scheduleRuleState(data, defaults);
74
+ root.dataset.originalCron = state.originalCron || ""; root.dataset.scheduleDirty = state.scheduleDirty ? "true" : "false";
75
+ setValue(root, "name", data.name || ""); setValue(root, "instructions", data.instructions || data.task || data.prompt || "");
76
+ var fields = ["customCron", "date", "time", "repeatEvery", "repeatUnit", "repeatEndType", "repeatEndDate", "repeatEndCount", "intervalEvery", "intervalUnit", "intervalEndType", "intervalEndCount", "intervalEndTime"];
77
+ for (var i = 0; i < fields.length; i++) setValue(root, fields[i], state[fields[i]]);
78
+ root.querySelector('[data-field="advanced"]').checked = state.advanced; root.querySelector('[data-field="repeatEnabled"]').checked = state.repeatEnabled; root.querySelector('[data-field="intervalEnabled"]').checked = state.intervalEnabled;
79
+ var weekdayInputs = root.querySelectorAll('[data-field="weekday"]'); for (var wi = 0; wi < weekdayInputs.length; wi++) weekdayInputs[wi].checked = state.weekdays.indexOf(Number(weekdayInputs[wi].value)) !== -1;
80
+ renderExecutionRuntime(root, runtimeState || {}, data.execution); root.querySelector('[data-field="skipIfRunning"]').checked = data.skipIfRunning !== false;
81
+ var instructions = root.querySelector('[data-field="instructions"]'); if (data.linkedTaskId) { instructions.readOnly = true; instructions.setAttribute("aria-description", "Instructions are managed by the linked task."); }
82
+ root.querySelector('[data-timezone]').textContent = timeZone || "server local";
83
+ var accordions = root.querySelectorAll("details[data-accordion]");
84
+ for (var ai = 0; ai < accordions.length; ai++) accordions[ai].open = Array.isArray(data._openAccordions) ? data._openAccordions.indexOf(accordions[ai].dataset.accordion) !== -1 : (state.advanced && accordions[ai].dataset.accordion === "custom");
85
+ updateVisibility(root);
86
+ }
87
+
88
+ export function readEditor(root) {
89
+ var scheduleState = readSchedule(root);
90
+ var schedule = scheduleState.originalCron && !scheduleState.scheduleDirty ? { cron: scheduleState.originalCron, date: scheduleState.date, time: scheduleState.time, scheduleRule: null } : serializeScheduleRule(scheduleState);
91
+ var openAccordions = Array.from(root.querySelectorAll("details[data-accordion][open]")).map(function (details) { return details.dataset.accordion; });
92
+ return Object.assign({ name: root.querySelector('[data-field="name"]').value.trim(), instructions: root.querySelector('[data-field="instructions"]').value.trim(), execution: readExecutionRuntime(root), skipIfRunning: root.querySelector('[data-field="skipIfRunning"]').checked, _openAccordions: openAccordions, _scheduleState: scheduleState }, schedule);
93
+ }
94
+
95
+ export function serverEditorData(data) { var copy = Object.assign({}, data); delete copy._scheduleState; delete copy._advancedOpen; delete copy._openAccordions; return copy; }
96
+
97
+ export function validateEditor(root, data) {
98
+ var error = ""; var rule = data.scheduleRule;
99
+ if (!data.name) error = "Enter a task name."; else if (!data.instructions) error = "Enter task instructions.";
100
+ else if (!rule && !isStrictCron(data.cron)) error = "Enter a valid five-field custom cron expression.";
101
+ else if (rule && (!/^\d{4}-\d{2}-\d{2}$/.test(data.date) || !/^([01]\d|2[0-3]):[0-5]\d$/.test(data.time))) error = "Choose a valid date and time.";
102
+ else if (rule && rule.recurrence && (!Number.isInteger(rule.recurrence.every) || rule.recurrence.every < 1 || rule.recurrence.every > 99)) error = "Repeat cadence must be between 1 and 99.";
103
+ else if (rule && rule.recurrence && rule.recurrence.unit === "week" && !rule.recurrence.weekdays.length) error = "Choose at least one weekday.";
104
+ else if (rule && rule.recurrenceEnd && rule.recurrenceEnd.type === "after" && (!Number.isInteger(rule.recurrenceEnd.count) || rule.recurrenceEnd.count < 1 || rule.recurrenceEnd.count > 999)) error = "Occurrence count must be between 1 and 999.";
105
+ else if (rule && rule.interval && (!Number.isInteger(rule.interval.every) || rule.interval.every < 1 || rule.interval.every > 999)) error = "Interval must be between 1 and 999.";
106
+ else if (rule && rule.interval && rule.interval.unit === "hour" && rule.interval.every > 23) error = "Hour intervals must be between 1 and 23.";
107
+ else if (rule && rule.interval && rule.interval.end && rule.interval.end.type === "after" && (!Number.isInteger(rule.interval.end.count) || rule.interval.end.count < 1 || rule.interval.end.count > 999)) error = "Run count must be between 1 and 999.";
108
+ else if (rule && rule.interval && rule.interval.end && rule.interval.end.type === "until" && rule.interval.end.time <= data.time) error = "Stop time must be after the selected time.";
109
+ else if (!data.execution || !data.execution.driver.vendor || !data.execution.driver.model || !data.execution.worker.vendor || !data.execution.worker.model) error = "Choose a model for both the Driver and Split Worker.";
110
+ else if ((root.querySelector('[data-runtime="driver-effort"]').options.length > 1 && !data.execution.driver.effort) || (root.querySelector('[data-runtime="worker-effort"]').options.length > 1 && !data.execution.worker.effort)) error = "Choose a reasoning effort for each runtime that supports it.";
111
+ root.querySelector(".scheduled-task-form-error").textContent = error; return !error;
112
+ }
113
+
114
+ export function handleEditorScheduleChange(target) {
115
+ var root = target && target.closest && (target.closest(".scheduled-task-draft-form") || target.closest(".scheduled-task-edit-form"));
116
+ if (!root || !target.dataset || !target.dataset.field) return false;
117
+ if (["date", "time", "repeatEnabled", "repeatEvery", "repeatUnit", "weekday", "repeatEndType", "repeatEndDate", "repeatEndCount", "intervalEnabled", "intervalEvery", "intervalUnit", "intervalEndType", "intervalEndCount", "intervalEndTime", "advanced", "customCron"].indexOf(target.dataset.field) !== -1) root.dataset.scheduleDirty = "true";
118
+ if (target.dataset.field === "date" && root.querySelector('[data-field="repeatEndDate"]').value === "") root.querySelector('[data-field="repeatEndDate"]').value = target.value;
119
+ updateVisibility(root); return true;
120
+ }
121
+
122
+ export { scheduleRuleState, serializeScheduleRule };
@@ -0,0 +1,46 @@
1
+ export function isStrictCron(value) {
2
+ if (typeof value !== "string") return false;
3
+ var ranges = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
4
+ var fields = value.trim().split(/\s+/);
5
+ if (fields.length !== 5) return false;
6
+ function validNumber(text, range) { return /^\d+$/.test(text) && Number(text) >= range[0] && Number(text) <= range[1]; }
7
+ function validPart(part, range) {
8
+ var step = part.split("/");
9
+ if (step.length > 2 || (step.length === 2 && (!/^\d+$/.test(step[1]) || Number(step[1]) < 1))) return false;
10
+ var base = step[0];
11
+ if (base === "*") return true;
12
+ var span = base.split("-");
13
+ if (span.length === 2) return validNumber(span[0], range) && validNumber(span[1], range) && Number(span[0]) <= Number(span[1]);
14
+ return span.length === 1 && validNumber(base, range);
15
+ }
16
+ for (var i = 0; i < fields.length; i++) {
17
+ var parts = fields[i].split(",");
18
+ for (var j = 0; j < parts.length; j++) if (!validPart(parts[j], ranges[i])) return false;
19
+ }
20
+ return true;
21
+ }
22
+
23
+ export function humanRecurrence(cron) {
24
+ if (!isStrictCron(cron)) return "Invalid schedule";
25
+ var fields = cron.trim().split(/\s+/);
26
+ var minute = fields[0];
27
+ var hour = fields[1];
28
+ var dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
29
+ var weekdayText = "";
30
+ if (fields[4] === "1-5") weekdayText = "Mon–Fri";
31
+ else if (fields[4] === "0,6" || fields[4] === "6,0") weekdayText = "weekends";
32
+ else if (/^[0-6](,[0-6])*$/.test(fields[4])) {
33
+ weekdayText = fields[4].split(",").map(function (day) { return dayNames[Number(day)]; }).join(", ");
34
+ } else if (/^[0-6]$/.test(fields[4])) weekdayText = dayNames[Number(fields[4])];
35
+ var weekday = weekdayText ? " · " + weekdayText : (fields[4] === "*" ? "" : " · " + fields[4]);
36
+ if (minute.indexOf("*/") === 0 && hour === "*" && fields[2] === "*" && fields[3] === "*") return "Every " + minute.slice(2) + " minutes" + weekday;
37
+ if (minute === "0" && hour.indexOf("*/") === 0 && fields[2] === "*" && fields[3] === "*") return "Every " + hour.slice(2) + " hours" + weekday;
38
+ if (/^\d+$/.test(minute) && /^\d+$/.test(hour) && fields[2] === "*" && fields[3] === "*" && fields[4] === "*") {
39
+ var h = Number(hour); var suffix = h >= 12 ? "pm" : "am"; var displayHour = h % 12 || 12;
40
+ return "Daily at " + displayHour + ":" + String(Number(minute)).padStart(2, "0") + suffix;
41
+ }
42
+ if (/^\d+$/.test(minute) && /^\d+$/.test(hour) && fields[2] === "*" && fields[3] === "*" && fields[4] === "1-5") return "Weekdays at " + String(Number(hour)).padStart(2, "0") + ":" + String(Number(minute)).padStart(2, "0");
43
+ if (/^\d+$/.test(minute) && /^\d+$/.test(hour) && fields[2] === "*" && fields[3] === "*" && fields[4] !== "*") return "Weekly on " + (weekdayText || fields[4]) + " at " + String(Number(hour)).padStart(2, "0") + ":" + String(Number(minute)).padStart(2, "0");
44
+ if (/^\d+$/.test(minute) && /^\d+$/.test(hour) && /^\d+$/.test(fields[2]) && fields[3] === "*" && fields[4] === "*") return "Monthly on day " + fields[2] + " at " + String(Number(hour)).padStart(2, "0") + ":" + String(Number(minute)).padStart(2, "0");
45
+ return "Cron · " + cron;
46
+ }
@@ -0,0 +1,115 @@
1
+ var DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
2
+
3
+ function pad(value) { return String(value).padStart(2, "0"); }
4
+
5
+ function detectedInterval(field, max) {
6
+ if (/^\*\/\d+$/.test(field)) {
7
+ var wildcardStep = Number(field.slice(2));
8
+ return wildcardStep > 0 && max % wildcardStep === 0 ? { every: wildcardStep, offset: 0 } : null;
9
+ }
10
+ if (!/^\d+(,\d+)+$/.test(field)) return null;
11
+ var values = field.split(",").map(Number);
12
+ if (values[0] < 0 || values[values.length - 1] >= max) return null;
13
+ var step = values[1] - values[0];
14
+ if (step < 1) return null;
15
+ for (var i = 1; i < values.length; i++) if (values[i] - values[i - 1] !== step) return null;
16
+ return values[values.length - 1] + step === max + values[0] ? { every: step, offset: values[0] } : null;
17
+ }
18
+
19
+ function parsedWeekdays(field) {
20
+ if (/^[0-6](,[0-6])*$/.test(field)) return field.split(",").map(Number);
21
+ var range = field.match(/^([0-6])-([0-6])$/);
22
+ if (!range || Number(range[1]) > Number(range[2])) return null;
23
+ var days = [];
24
+ for (var day = Number(range[1]); day <= Number(range[2]); day++) days.push(day);
25
+ return days;
26
+ }
27
+
28
+ function cronState(record, defaults) {
29
+ var fields = record.cron.trim().split(/\s+/);
30
+ if (fields.length !== 5) return null;
31
+ var minuteInterval = fields[1] === "*" && fields[2] === "*" && fields[3] === "*" ? detectedInterval(fields[0], 60) : null;
32
+ var hourInterval = /^\d+$/.test(fields[0]) && fields[2] === "*" && fields[3] === "*" ? detectedInterval(fields[1], 24) : null;
33
+ if (hourInterval && hourInterval.every > 23) hourInterval = null;
34
+ var minute = /^\d+$/.test(fields[0]) ? Number(fields[0]) : (minuteInterval ? minuteInterval.offset : 0);
35
+ var hour = /^\d+$/.test(fields[1]) ? Number(fields[1]) : (hourInterval ? hourInterval.offset : 0);
36
+ var weekdays = fields[4] === "*" ? null : parsedWeekdays(fields[4]);
37
+ if (fields[4] !== "*" && !weekdays) return null;
38
+ var recurrence = null;
39
+ if (fields[2] === "*" && fields[3] === "*" && fields[4] === "*") recurrence = { every: 1, unit: "day" };
40
+ else if (fields[2] === "*" && fields[3] === "*" && weekdays) recurrence = { every: 1, unit: "week", weekdays: weekdays };
41
+ else if (/^\d+$/.test(fields[2]) && fields[3] === "*" && fields[4] === "*") recurrence = { every: 1, unit: "month" };
42
+ else if (/^\d+$/.test(fields[2]) && /^\d+$/.test(fields[3]) && fields[4] === "*") recurrence = { every: 1, unit: "year" };
43
+ if ((minuteInterval || hourInterval) && record.recurrenceEnd && record.recurrenceEnd.type === "until" && record.recurrenceEnd.date === record.date) recurrence = null;
44
+ if ((!recurrence && !minuteInterval && !hourInterval) || (!minuteInterval && !hourInterval && (!/^\d+$/.test(fields[0]) || !/^\d+$/.test(fields[1])))) return null;
45
+ var date = record.date || defaults.date;
46
+ if (recurrence && recurrence.unit === "month") date = date.slice(0, 8) + pad(fields[2]);
47
+ if (recurrence && recurrence.unit === "year") date = date.slice(0, 5) + pad(fields[3]) + "-" + pad(fields[2]);
48
+ return { version: 1, timezone: "server-local", anchorDate: date, time: pad(hour) + ":" + pad(minute), recurrence: recurrence, recurrenceEnd: recurrence ? record.recurrenceEnd || null : null, interval: minuteInterval || hourInterval ? { every: (minuteInterval || hourInterval).every, unit: minuteInterval ? "minute" : "hour", end: record.intervalEnd || null } : null };
49
+ }
50
+
51
+ export function humanScheduleRule(rule) {
52
+ if (!rule || !rule.anchorDate || !rule.time) return "Incomplete schedule";
53
+ var repeat = rule.recurrence;
54
+ var text = "Does not repeat";
55
+ if (repeat) {
56
+ var unit = repeat.unit + (repeat.every === 1 ? "" : "s");
57
+ text = "Every " + (repeat.every === 1 ? "" : repeat.every + " ") + unit;
58
+ if (repeat.unit === "week" && repeat.weekdays && repeat.weekdays.length) text += " · " + repeat.weekdays.map(function (day) { return DAY_NAMES[day]; }).join(", ");
59
+ }
60
+ if (rule.interval) text += " · every " + rule.interval.every + " " + rule.interval.unit + (rule.interval.every === 1 ? "" : "s");
61
+ text += " · " + rule.anchorDate + " at " + rule.time;
62
+ if (rule.recurrenceEnd && rule.recurrenceEnd.type === "until") text += " · until " + rule.recurrenceEnd.date;
63
+ if (rule.recurrenceEnd && rule.recurrenceEnd.type === "after") text += " · after " + rule.recurrenceEnd.count + " scheduled starts";
64
+ if (rule.interval && rule.interval.end && rule.interval.end.type === "until") text += " · stop at " + rule.interval.end.time;
65
+ if (rule.interval && rule.interval.end && rule.interval.end.type === "after") text += " · stop after " + rule.interval.end.count + " runs per day";
66
+ return text;
67
+ }
68
+
69
+ export function scheduleRuleState(record, defaults) {
70
+ var parsedCron = record && record.cron ? cronState(record, defaults) : null;
71
+ var rule = record && record.scheduleRule || parsedCron;
72
+ if (!rule) return {
73
+ advanced: !!(record && record.cron), customCron: record && record.cron || "", date: record && record.date || defaults.date, time: record && record.time || defaults.time,
74
+ repeatEnabled: false, repeatEvery: 1, repeatUnit: "week", weekdays: [new Date((record && record.date || defaults.date) + "T00:00:00").getDay()], repeatEndType: "never", repeatEndDate: defaults.date, repeatEndCount: 10,
75
+ intervalEnabled: false, intervalEvery: 10, intervalUnit: "minute", intervalEndType: "allday", intervalEndCount: 5, intervalEndTime: "18:00",
76
+ };
77
+ return {
78
+ advanced: false, customCron: record && record.cron || "", originalCron: parsedCron && record.cron || "", date: rule.anchorDate, time: rule.time,
79
+ repeatEnabled: !!rule.recurrence, repeatEvery: rule.recurrence && rule.recurrence.every || 1, repeatUnit: rule.recurrence && rule.recurrence.unit || "week", weekdays: rule.recurrence && rule.recurrence.weekdays || [new Date(rule.anchorDate + "T00:00:00").getDay()],
80
+ repeatEndType: rule.recurrenceEnd && rule.recurrenceEnd.type || "never", repeatEndDate: rule.recurrenceEnd && rule.recurrenceEnd.date || rule.anchorDate, repeatEndCount: rule.recurrenceEnd && rule.recurrenceEnd.count || 10,
81
+ intervalEnabled: !!rule.interval, intervalEvery: rule.interval && rule.interval.every || 10, intervalUnit: rule.interval && rule.interval.unit || "minute", intervalEndType: rule.interval && rule.interval.end && rule.interval.end.type || "allday", intervalEndCount: rule.interval && rule.interval.end && rule.interval.end.count || 5, intervalEndTime: rule.interval && rule.interval.end && rule.interval.end.time || "18:00",
82
+ };
83
+ }
84
+
85
+ export function serializeScheduleRule(state) {
86
+ if (state.advanced) return { cron: state.customCron || "", date: null, time: null, scheduleRule: null };
87
+ var recurrence = state.repeatEnabled ? { every: Number(state.repeatEvery), unit: state.repeatUnit } : null;
88
+ if (recurrence && recurrence.unit === "week") recurrence.weekdays = state.weekdays.slice().sort(function (a, b) { return a - b; });
89
+ var recurrenceEnd = null;
90
+ if (state.repeatEnabled && state.repeatEndType === "until") recurrenceEnd = { type: "until", date: state.repeatEndDate };
91
+ if (state.repeatEnabled && state.repeatEndType === "after") recurrenceEnd = { type: "after", count: Number(state.repeatEndCount) };
92
+ var interval = null;
93
+ if (state.intervalEnabled) {
94
+ interval = { every: Number(state.intervalEvery), unit: state.intervalUnit, end: null };
95
+ if (state.intervalEndType === "until") interval.end = { type: "until", time: state.intervalEndTime };
96
+ if (state.intervalEndType === "after") interval.end = { type: "after", count: Number(state.intervalEndCount) };
97
+ }
98
+ return { cron: null, date: state.date, time: state.time, scheduleRule: { version: 1, timezone: "server-local", anchorDate: state.date, time: state.time, recurrence: recurrence, recurrenceEnd: recurrenceEnd, interval: interval } };
99
+ }
100
+
101
+ export function legacyScheduleRule(state) {
102
+ var recurrence = null;
103
+ if (state.repeatType === "daily") recurrence = { every: 1, unit: "day" };
104
+ else if (state.repeatType === "weekly") recurrence = { every: 1, unit: "week", weekdays: [state.anchorWeekday] };
105
+ else if (state.repeatType === "biweekly") recurrence = { every: 2, unit: "week", weekdays: [state.anchorWeekday] };
106
+ else if (state.repeatType === "monthly") recurrence = { every: 1, unit: "month" };
107
+ else if (state.repeatType === "yearly") recurrence = { every: 1, unit: "year" };
108
+ else if (state.repeatType === "weekdays") recurrence = { every: 1, unit: "week", weekdays: [1, 2, 3, 4, 5] };
109
+ else if (state.repeatType === "custom" && ["day", "week", "month", "year"].indexOf(state.repeatUnit) !== -1) {
110
+ recurrence = { every: state.repeatEvery, unit: state.repeatUnit };
111
+ if (state.repeatUnit === "week") recurrence.weekdays = state.weekdays.length ? state.weekdays : [state.anchorWeekday];
112
+ }
113
+ if (!recurrence && !state.interval) return null;
114
+ return { version: 1, timezone: "server-local", anchorDate: state.anchorDate, time: state.time, recurrence: recurrence, recurrenceEnd: state.recurrenceEnd, interval: state.interval };
115
+ }