@goodandready/dsh-cron 0.3.25 → 0.3.26

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 (82) hide show
  1. package/lib/api-actions.js +1 -317
  2. package/lib/api-collection.js +1 -250
  3. package/lib/api-helpers.js +1 -100
  4. package/lib/api-import-export.js +1 -155
  5. package/lib/api-item.js +1 -191
  6. package/lib/api-task-webhook.js +1 -247
  7. package/lib/api-webhook.js +1 -183
  8. package/lib/api.js +1 -178
  9. package/lib/artifacts.js +1 -200
  10. package/lib/chain-checkpoints.js +1 -223
  11. package/lib/client-src/00-open.js +62 -0
  12. package/lib/client-src/10-locales.js +484 -0
  13. package/lib/client-src/20-styles.js +64 -0
  14. package/lib/client-src/30-utils.js +248 -0
  15. package/lib/client-src/40-delivery-settings.js +96 -0
  16. package/lib/client-src/45-dag-graph-view.js +287 -0
  17. package/lib/client-src/46-live-logs-modal.js +108 -0
  18. package/lib/client-src/47-analytics-digest-modal.js +166 -0
  19. package/lib/client-src/48-timeline-heatmap-view.js +176 -0
  20. package/lib/client-src/50-cron-screen-state.js +447 -0
  21. package/lib/client-src/52-cron-screen-actions.js +330 -0
  22. package/lib/client-src/53-cron-screen-modal-actions.js +640 -0
  23. package/lib/client-src/54-cron-screen-analytics-actions.js +68 -0
  24. package/lib/client-src/55-cron-screen-view.js +378 -0
  25. package/lib/client-src/60-modal-task-form-head.js +580 -0
  26. package/lib/client-src/62-modal-task-form-tail.js +466 -0
  27. package/lib/client-src/65-modal-dialogs.js +350 -0
  28. package/lib/client-src/70-settings-card.js +420 -0
  29. package/lib/client-src/80-sidebar-jobs.js +200 -0
  30. package/lib/client-src/90-close.js +200 -0
  31. package/lib/commands.js +200 -0
  32. package/lib/durable-journal.js +1 -188
  33. package/lib/external-api.js +1 -74
  34. package/lib/index.js +17 -0
  35. package/lib/metrics.js +1 -125
  36. package/lib/routes/api-actions.js +317 -0
  37. package/lib/routes/api-collection.js +250 -0
  38. package/lib/routes/api-helpers.js +100 -0
  39. package/lib/routes/api-import-export.js +155 -0
  40. package/lib/routes/api-item.js +191 -0
  41. package/lib/routes/api-task-webhook.js +247 -0
  42. package/lib/routes/api-webhook.js +183 -0
  43. package/lib/routes/api.js +178 -0
  44. package/lib/routes/external-api.js +74 -0
  45. package/lib/routes/index.js +11 -0
  46. package/lib/routes/metrics.js +125 -0
  47. package/lib/routes/routes.js +549 -0
  48. package/lib/routes.js +1 -549
  49. package/lib/runner-external.js +1 -352
  50. package/lib/runner-worktree.js +1 -90
  51. package/lib/runner.js +1 -881
  52. package/lib/runtimes/index.js +4 -0
  53. package/lib/runtimes/runner-external.js +352 -0
  54. package/lib/runtimes/runner-worktree.js +90 -0
  55. package/lib/runtimes/runner.js +881 -0
  56. package/lib/runtimes/runtimes.js +198 -0
  57. package/lib/runtimes.js +1 -198
  58. package/lib/scheduler/index.js +6 -0
  59. package/lib/scheduler/scheduler-assists.js +142 -0
  60. package/lib/scheduler/scheduler-completion.js +417 -0
  61. package/lib/scheduler/scheduler-execution.js +360 -0
  62. package/lib/scheduler/scheduler-lifecycle.js +157 -0
  63. package/lib/scheduler/scheduler-preflight.js +195 -0
  64. package/lib/scheduler/scheduler.js +751 -0
  65. package/lib/scheduler-assists.js +1 -142
  66. package/lib/scheduler-completion.js +1 -417
  67. package/lib/scheduler-execution.js +1 -360
  68. package/lib/scheduler-lifecycle.js +1 -157
  69. package/lib/scheduler-preflight.js +1 -195
  70. package/lib/scheduler.js +1 -751
  71. package/lib/store/artifacts.js +200 -0
  72. package/lib/store/chain-checkpoints.js +223 -0
  73. package/lib/store/durable-journal.js +188 -0
  74. package/lib/store/index.js +6 -0
  75. package/lib/store/store.js +1179 -0
  76. package/lib/store/task-schema.js +180 -0
  77. package/lib/store/task-transfer.js +310 -0
  78. package/lib/store.js +2 -1179
  79. package/lib/task-schema.js +1 -180
  80. package/lib/task-transfer.js +1 -310
  81. package/lib/tui-settings.js +157 -0
  82. package/package.json +2 -1
@@ -1,223 +1 @@
1
- /**
2
- * Chain Checkpoint, Step Replay, and DAG Barrier Manager (#319, #328)
3
- */
4
-
5
- import { sanitizeDependsOn, sanitizeJoinPolicy } from './dag.js';
6
-
7
- export class ChainCheckpointManager {
8
- constructor(store) {
9
- this.store = store;
10
- this.activeRetries = new Set();
11
- this.triggeredBarriers = new Set();
12
- }
13
-
14
- /**
15
- * Records a snapshot of a completed or failed chain step
16
- */
17
- recordStepSnapshot(chainRunId, stepTaskId, snapshot) {
18
- if (!chainRunId || !stepTaskId) return;
19
- this.store.chainCheckpoints = this.store.chainCheckpoints || {};
20
- this.store.chainCheckpoints[chainRunId] = this.store.chainCheckpoints[chainRunId] || [];
21
-
22
- const list = this.store.chainCheckpoints[chainRunId];
23
- const existingIndex = list.findIndex((s) => s.stepTaskId === stepTaskId);
24
-
25
- const record = {
26
- chainRunId,
27
- stepTaskId,
28
- status: snapshot.status || 'completed',
29
- input: snapshot.input || {},
30
- output: snapshot.output || '',
31
- at: Date.now(),
32
- durationMs: snapshot.durationMs || 0,
33
- retryCount: (existingIndex >= 0 ? (list[existingIndex].retryCount || 0) + 1 : 0),
34
- };
35
-
36
- if (existingIndex >= 0) {
37
- list[existingIndex] = record;
38
- } else {
39
- list.push(record);
40
- }
41
-
42
- this.store.scheduleSave(50);
43
- return record;
44
- }
45
-
46
- /**
47
- * Retrieves all step snapshots for a given chain run
48
- */
49
- getChainSnapshots(chainRunId) {
50
- if (!chainRunId || !this.store.chainCheckpoints) return [];
51
- return this.store.chainCheckpoints[chainRunId] || [];
52
- }
53
-
54
- /**
55
- * Checks whether all prerequisites for a task are satisfied (DAG Join Barrier, #328).
56
- *
57
- * @param {string} chainRunId
58
- * @param {Object} task
59
- * @returns {{ ready: boolean, pending?: boolean, failed?: boolean, outputs: Record<string, string>, latestOutput: string, waitingFor?: string[], failedDep?: string }}
60
- */
61
- checkJoinBarrier(chainRunId, task) {
62
- if (!task) return { ready: false, outputs: {}, latestOutput: '' };
63
- const deps = sanitizeDependsOn(task.dependsOn);
64
- if (!deps.length) {
65
- return { ready: true, outputs: {}, latestOutput: '' };
66
- }
67
-
68
- const snapshots = this.getChainSnapshots(chainRunId);
69
- const snapMap = new Map();
70
- for (const s of snapshots) {
71
- if (s?.stepTaskId) snapMap.set(s.stepTaskId, s);
72
- }
73
-
74
- const policy = sanitizeJoinPolicy(task.joinPolicy);
75
- const outputs = {};
76
- let latestOutput = '';
77
- const waitingFor = [];
78
- let hasSuccess = false;
79
- let failedDep = null;
80
-
81
- for (const depId of deps) {
82
- const snap = snapMap.get(depId);
83
- if (!snap) {
84
- waitingFor.push(depId);
85
- continue;
86
- }
87
- const isOk = snap.status === 'completed' || snap.status === 'success';
88
- if (isOk) {
89
- hasSuccess = true;
90
- outputs[depId] = String(snap.output || '');
91
- latestOutput = String(snap.output || '');
92
- } else {
93
- failedDep = depId;
94
- }
95
- }
96
-
97
- if (policy === 'any_success') {
98
- if (hasSuccess) {
99
- return { ready: true, outputs, latestOutput };
100
- }
101
- if (waitingFor.length === 0) {
102
- return { ready: false, failed: true, failedDep: failedDep || deps[0], outputs, latestOutput };
103
- }
104
- return { ready: false, pending: true, waitingFor, outputs, latestOutput };
105
- }
106
-
107
- // Default: 'all_success'
108
- if (waitingFor.length > 0) {
109
- return { ready: false, pending: true, waitingFor, outputs, latestOutput };
110
- }
111
- if (failedDep) {
112
- return { ready: false, failed: true, failedDep, outputs, latestOutput };
113
- }
114
-
115
- return { ready: true, outputs, latestOutput };
116
- }
117
-
118
- /**
119
- * Barrier idempotency guard: returns true if barrier has not yet fired for this task in this run.
120
- */
121
- canTriggerBarrier(chainRunId, taskId) {
122
- if (!chainRunId || !taskId) return false;
123
- return !this.triggeredBarriers.has(`${chainRunId}:${taskId}`);
124
- }
125
-
126
- /**
127
- * Marks a barrier as triggered for this chain run to prevent duplicate executions.
128
- */
129
- markBarrierTriggered(chainRunId, taskId) {
130
- if (!chainRunId || !taskId) return;
131
- this.triggeredBarriers.add(`${chainRunId}:${taskId}`);
132
- }
133
-
134
- /**
135
- * Checks whether a step can be retried in a chain run
136
- */
137
- canRetryFromStep(chainRunId, stepTaskId) {
138
- if (!chainRunId || !stepTaskId) return { ok: false, error: 'Missing chainRunId or stepTaskId' };
139
-
140
- const lockKey = `${chainRunId}:${stepTaskId}`;
141
- if (this.activeRetries.has(lockKey)) {
142
- return { ok: false, error: 'Retry already in progress for this step' };
143
- }
144
-
145
- const snapshots = this.getChainSnapshots(chainRunId);
146
- const stepSnapshot = snapshots.find((s) => s.stepTaskId === stepTaskId);
147
- if (!stepSnapshot) {
148
- return { ok: false, error: `No checkpoint found for step "${stepTaskId}" in chain "${chainRunId}"` };
149
- }
150
-
151
- if (stepSnapshot.status === 'success' || stepSnapshot.status === 'completed') {
152
- return { ok: false, error: 'Step already completed successfully; cannot retry a successful step' };
153
- }
154
-
155
- const task = this.store.get(stepTaskId);
156
- if (!task) {
157
- return { ok: false, error: `Task "${stepTaskId}" was deleted or no longer exists` };
158
- }
159
-
160
- return { ok: true, task, stepSnapshot };
161
- }
162
-
163
- /**
164
- * Retries chain execution from the specified failed step (#319, #328)
165
- * Reuses upstream outputs without re-executing successful upstream steps.
166
- */
167
- async retryChainFromStep(scheduler, chainRunId, stepTaskId) {
168
- const check = this.canRetryFromStep(chainRunId, stepTaskId);
169
- if (!check.ok) return check;
170
-
171
- const lockKey = `${chainRunId}:${stepTaskId}`;
172
- this.activeRetries.add(lockKey);
173
-
174
- try {
175
- const snapshots = this.getChainSnapshots(chainRunId);
176
-
177
- // Collect upstream outputs from checkpoints
178
- const prevOutputs = {};
179
- let prevOutput = '';
180
- let prevTaskId = '';
181
-
182
- for (const s of snapshots) {
183
- if (s.stepTaskId !== stepTaskId && (s.status === 'success' || s.status === 'completed')) {
184
- prevOutputs[s.stepTaskId] = String(s.output || '');
185
- prevOutput = String(s.output || '');
186
- prevTaskId = s.stepTaskId;
187
- }
188
- }
189
-
190
- // Reset barrier locks for stepTaskId and downstream dependents so they can re-trigger
191
- this.triggeredBarriers.delete(`${chainRunId}:${stepTaskId}`);
192
- if (this.store?.tasks) {
193
- for (const t of Object.values(this.store.tasks)) {
194
- const deps = sanitizeDependsOn(t?.dependsOn);
195
- if (deps.includes(stepTaskId)) {
196
- this.triggeredBarriers.delete(`${chainRunId}:${t.id}`);
197
- }
198
- }
199
- }
200
-
201
- // Execute only the failed step, passing reconstructed chain context
202
- const runFn = (scheduler.runTask || scheduler.runNow).bind(scheduler);
203
- const res = await runFn(stepTaskId, {
204
- chainRunId,
205
- prevOutputs,
206
- chainPrevOutput: prevOutput,
207
- prevOutput,
208
- chainPrevTaskId: prevTaskId,
209
- prevTaskId,
210
- isRetry: true,
211
- });
212
-
213
- return {
214
- ok: true,
215
- chainRunId,
216
- stepTaskId,
217
- result: res,
218
- };
219
- } finally {
220
- this.activeRetries.delete(lockKey);
221
- }
222
- }
223
- }
1
+ export * from './store/chain-checkpoints.js';
@@ -0,0 +1,62 @@
1
+ window.__ModuleLoader__.load({
2
+ id: '@goodandready/dsh-cron',
3
+ factory: (require) => {
4
+ var module = { exports: {} }
5
+ var exports = module.exports
6
+ const React = window.React || require('react');
7
+ const ReactDOM = window.ReactDOM || require('react-dom/client');
8
+
9
+ // Settings namespace, locale namespace and plugin marker share the same
10
+ // canonical identifier — the settings.plugin.item slot key MUST match the
11
+ // namespace registered on the server ('dsh-cron', Issue #85).
12
+ const PLUGIN_ID = '@goodandready/dsh-cron';
13
+ const NS = 'dsh-cron';
14
+ // Plugins page row seat (DSH 0.1.6-alpha.2): key = '<package name>#<row id>'.
15
+ const ROW_ID = 'dsh-cron';
16
+ const ROW_CONFIG_KEY = PLUGIN_ID + '#' + ROW_ID;
17
+ const PANEL = 'cron';
18
+ const ACTIVATE_EVENT = 'dsh-panel-activate';
19
+ const ACTIVE_ATTR = 'data-dsh-cron-active';
20
+ const ENTRY_ATTR = 'data-dsh-cron-entry';
21
+ // Set by the sidebar job list (#34) so the panel can highlight the task the
22
+ // user clicked; cleared once the panel has scrolled to it.
23
+ let pendingTaskHighlight = null;
24
+ const VIEW_ATTR = 'data-dsh-cron-view';
25
+ const COLUMN_SELECTOR = '[data-pane=conversation], [class*=centerCol]';
26
+ const SESSION_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="newSession"]';
27
+ const SCRIPT_CONFIRM_HEADER = 'x-dsh-cron-confirm';
28
+
29
+ const BACK_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 3.5 5.5 8l4.5 4.5"/></svg>';
30
+ const ICON_TIMER = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>';
31
+ const ICON_PLAY = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="5 3 19 12 5 21 5 21"></polygon></svg>';
32
+ const ICON_PAUSE = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>';
33
+ const ICON_TRASH = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>';
34
+ const ICON_SEARCH = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';
35
+ const ICON_CHEVRON_DOWN = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>';
36
+ const ICON_BELL = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>';
37
+ const ICON_CLIPBOARD = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>';
38
+ const ICON_ACTIVITY = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>';
39
+ const ICON_EDIT = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>';
40
+ const ICON_SEND = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>';
41
+ const ICON_COPY = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
42
+ const ICON_TUNE = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line></svg>';
43
+ const ICON_DOWNLOAD = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>';
44
+ const ICON_UPLOAD = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>';
45
+ const ICON_TELEGRAM = '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8-1.7 8.01c-.13.58-.47.72-.95.45l-2.6-1.92-1.25 1.21c-.14.14-.26.26-.53.26l.19-2.64 4.81-4.35c.21-.19-.05-.29-.32-.11L8.34 13.5 5.78 12.7c-.56-.17-.57-.56.12-.83l10-3.86c.46-.17.87.11.74.79z"/></svg>';
46
+
47
+ // Locale dictionary: English is the canonical source language (Issue #87).
48
+ // Russian and other languages are provided at runtime by the translation
49
+ // plugin; no per-language duplicates are stored here.
50
+ // Core chevron primitive when the build ships it; own SVG fallback otherwise.
51
+ let CoreChevron = null;
52
+ try {
53
+ const primitives = require('@deepseek-ai/dsh-client-ui-primitives');
54
+ CoreChevron = primitives && primitives.IconChevronDownOutline14;
55
+ } catch (e) {
56
+ /* Optional core chevron primitive not available; SVG fallback used */
57
+ }
58
+ const chevronNode = () => (CoreChevron ? React.createElement(CoreChevron) : React.createElement('span', { style: { display: 'flex', alignItems: 'center' }, dangerouslySetInnerHTML: { __html: ICON_CHEVRON_DOWN } }));
59
+
60
+ // Own React error boundary (#106): the core's SlotErrorBoundary renders an
61
+ // empty div on crash, so a slot failure would silently vanish. Ours keeps
62
+ // the failure visible with a retry.