@goodandready/dsh-cron 0.3.24 → 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 (84) 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 -190
  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/client.js +48 -3
  32. package/lib/commands.js +200 -0
  33. package/lib/durable-journal.js +1 -188
  34. package/lib/external-api.js +1 -74
  35. package/lib/index.js +17 -0
  36. package/lib/log-streamer.js +10 -2
  37. package/lib/metrics.js +1 -125
  38. package/lib/routes/api-actions.js +317 -0
  39. package/lib/routes/api-collection.js +250 -0
  40. package/lib/routes/api-helpers.js +100 -0
  41. package/lib/routes/api-import-export.js +155 -0
  42. package/lib/routes/api-item.js +191 -0
  43. package/lib/routes/api-task-webhook.js +247 -0
  44. package/lib/routes/api-webhook.js +183 -0
  45. package/lib/routes/api.js +178 -0
  46. package/lib/routes/external-api.js +74 -0
  47. package/lib/routes/index.js +11 -0
  48. package/lib/routes/metrics.js +125 -0
  49. package/lib/routes/routes.js +549 -0
  50. package/lib/routes.js +1 -549
  51. package/lib/runner-external.js +1 -352
  52. package/lib/runner-worktree.js +1 -90
  53. package/lib/runner.js +1 -881
  54. package/lib/runtimes/index.js +4 -0
  55. package/lib/runtimes/runner-external.js +352 -0
  56. package/lib/runtimes/runner-worktree.js +90 -0
  57. package/lib/runtimes/runner.js +881 -0
  58. package/lib/runtimes/runtimes.js +198 -0
  59. package/lib/runtimes.js +1 -198
  60. package/lib/scheduler/index.js +6 -0
  61. package/lib/scheduler/scheduler-assists.js +142 -0
  62. package/lib/scheduler/scheduler-completion.js +417 -0
  63. package/lib/scheduler/scheduler-execution.js +360 -0
  64. package/lib/scheduler/scheduler-lifecycle.js +157 -0
  65. package/lib/scheduler/scheduler-preflight.js +195 -0
  66. package/lib/scheduler/scheduler.js +751 -0
  67. package/lib/scheduler-assists.js +1 -142
  68. package/lib/scheduler-completion.js +1 -417
  69. package/lib/scheduler-execution.js +1 -360
  70. package/lib/scheduler-lifecycle.js +1 -157
  71. package/lib/scheduler-preflight.js +1 -195
  72. package/lib/scheduler.js +1 -751
  73. package/lib/store/artifacts.js +200 -0
  74. package/lib/store/chain-checkpoints.js +223 -0
  75. package/lib/store/durable-journal.js +188 -0
  76. package/lib/store/index.js +6 -0
  77. package/lib/store/store.js +1179 -0
  78. package/lib/store/task-schema.js +180 -0
  79. package/lib/store/task-transfer.js +310 -0
  80. package/lib/store.js +2 -1179
  81. package/lib/task-schema.js +1 -180
  82. package/lib/task-transfer.js +1 -310
  83. package/lib/tui-settings.js +157 -0
  84. package/package.json +6 -3
@@ -0,0 +1,580 @@
1
+ manualModalOpen && React.createElement('div', {
2
+ className: 'dsh-cron-modal-overlay',
3
+ onClick: (e) => {
4
+ if (e.target === e.currentTarget) closeManualModalWithConfirm();
5
+ }
6
+ },
7
+ React.createElement('div', {
8
+ className: 'dsh-cron-modal dsh-cron-modal-scroll',
9
+ style: { maxWidth: '640px' },
10
+ role: 'dialog',
11
+ 'aria-modal': 'true',
12
+ onClick: (e) => e.stopPropagation()
13
+ },
14
+ React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: formId ? '12px' : '18px' } },
15
+ React.createElement('div', { className: 'dsh-cron-modal-title', style: { margin: 0 } }, formId ? T_KEY(t, 'modal.editTitle') : T_KEY(t, 'modal.newTitle')),
16
+ selectedTaskMeta && React.createElement('span', {
17
+ style: {
18
+ fontSize: '12px',
19
+ padding: '3px 8px',
20
+ borderRadius: '6px',
21
+ background: selectedTaskMeta.status === 'active' ? 'var(--dsh-cron-success-bg)' : 'var(--dsh-cron-danger-bg)',
22
+ color: selectedTaskMeta.status === 'active' ? 'var(--dsh-cron-success)' : 'var(--dsh-cron-danger)'
23
+ }
24
+ }, selectedTaskMeta.status === 'active' ? T_KEY(t, 'modal.statusActive') : T_KEY(t, 'modal.statusPaused'))
25
+ ),
26
+
27
+ formId && React.createElement('div', { className: 'dsh-cron-modal-nav' },
28
+ React.createElement('button', {
29
+ type: 'button',
30
+ className: 'dsh-cron-modal-tab',
31
+ 'data-active': modalTab === 'params' ? 'true' : undefined,
32
+ onClick: () => setModalTab('params')
33
+ }, T_KEY(t, 'modal.params')),
34
+ React.createElement('button', {
35
+ type: 'button',
36
+ className: 'dsh-cron-modal-tab',
37
+ 'data-active': modalTab === 'history' ? 'true' : undefined,
38
+ onClick: () => { setModalTab('history'); if (formId) loadTaskHistory(formId); }
39
+ }, T_KEY(t, 'modal.history', { count: taskHistory.length || 0 }))
40
+ ),
41
+
42
+ modalTab === 'history' && formId ? React.createElement('div', null,
43
+ historyLoading ? React.createElement('div', { className: 'dsh-cron-history-empty' }, T_KEY(t, 'history.loading')) :
44
+ taskHistory.length === 0 ? React.createElement('div', { className: 'dsh-cron-history-empty' }, T_KEY(t, 'history.empty')) :
45
+ React.createElement('div', { className: 'dsh-cron-history-list' },
46
+ taskHistory.map((run) => React.createElement('div', { key: run.id, className: 'dsh-cron-history-item' },
47
+ React.createElement('div', { className: 'dsh-cron-history-head' },
48
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
49
+ React.createElement('span', {
50
+ className: 'dsh-cron-history-status',
51
+ 'data-status': run.status
52
+ }, statusLabel(t, run.status)),
53
+ React.createElement('span', { className: 'dsh-cron-history-time' }, run.at ? new Date(run.at).toLocaleString() : '')
54
+ ),
55
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
56
+ run.costUsd > 0 && React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + run.costUsd.toFixed(5)),
57
+ run.usage && (run.usage.inputTokens > 0 || run.usage.outputTokens > 0) && React.createElement('span', { className: 'dsh-cron-tokens-tag' }, (run.usage.inputTokens + run.usage.outputTokens) + ' tok'),
58
+ React.createElement('span', { style: { color: 'var(--dsw-alias-label-tertiary, #888)', fontSize: '11.5px' } }, (run.durationMs || 0) + ' ms')
59
+ )
60
+ ),
61
+ // #315: Run card with stages flow, cost, stopped reason and delivery retry
62
+ run.stoppedReason && React.createElement('div', {
63
+ className: 'dsh-cron-hint',
64
+ style: { display: 'flex', gap: '6px', alignItems: 'center', marginTop: '6px' }
65
+ },
66
+ React.createElement('span', { className: 'dsh-cron-badge-ref' }, T_KEY(t, 'history.stoppedReasonTitle')),
67
+ React.createElement('span', null, run.stoppedReason)
68
+ ),
69
+ Array.isArray(run.stages) && run.stages.length > 0 && React.createElement('div', {
70
+ className: 'dsh-cron-stages-flow',
71
+ style: { display: 'flex', gap: '6px', flexWrap: 'wrap', marginTop: '6px' }
72
+ },
73
+ run.stages.map((st) => React.createElement('span', {
74
+ key: st.name,
75
+ className: 'dsh-cron-stage-pill',
76
+ 'data-status': st.status || 'unknown',
77
+ title: st.error ? (st.name + ': ' + st.error) : (st.name + (st.durationMs !== undefined ? ' (' + st.durationMs + 'ms)' : ''))
78
+ }, (T_KEY(t, 'history.stage_' + st.name) || st.name) + (st.durationMs !== undefined ? ' ' + st.durationMs + 'ms' : '')))
79
+ ),
80
+ run.delivery && React.createElement('div', {
81
+ style: { display: 'flex', alignItems: 'center', gap: '8px', marginTop: '6px' }
82
+ },
83
+ React.createElement('span', {
84
+ className: 'dsh-cron-badge-ref',
85
+ 'data-status': run.delivery.status
86
+ }, 'Delivery: ' + run.delivery.status),
87
+ (run.delivery.status === 'failed' || run.delivery.status === 'partial') && React.createElement('button', {
88
+ type: 'button',
89
+ className: 'dsh-cron-btn-secondary',
90
+ style: { padding: '2px 8px', fontSize: '11px' },
91
+ disabled: retryingDeliveryId === run.id,
92
+ onClick: () => handleRetryDelivery(formId, run.id)
93
+ }, retryingDeliveryId === run.id ? T_KEY(t, 'history.retryingDelivery') : T_KEY(t, 'history.retryDelivery'))
94
+ ),
95
+ run.artifacts && run.artifacts.length > 0 && React.createElement('div', {
96
+ style: { display: 'flex', flexWrap: 'wrap', gap: '6px', marginTop: '6px', alignItems: 'center' }
97
+ },
98
+ React.createElement('span', { className: 'dsh-cron-hint', style: { fontSize: '11px' } }, '📦 ' + T_KEY(t, 'history.artifacts') + ':'),
99
+ run.artifacts.map((art) => React.createElement('a', {
100
+ key: art.name || art.id,
101
+ href: '/dsh-cron/runs/' + encodeURIComponent(run.id) + '/artifacts/' + encodeURIComponent(art.name) + '/preview',
102
+ target: '_blank',
103
+ rel: 'noopener noreferrer',
104
+ className: 'dsh-cron-btn-secondary',
105
+ style: { padding: '2px 8px', fontSize: '11px', textDecoration: 'none' },
106
+ title: art.name + ' (' + Math.round((art.size || 0) / 1024 * 10) / 10 + ' KB)'
107
+ }, art.name))
108
+ ),
109
+ run.status === 'uncertain' && React.createElement('div', {
110
+ className: 'dsh-cron-status-banner',
111
+ 'data-type': 'warning',
112
+ style: { marginTop: '8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' }
113
+ },
114
+ React.createElement('span', null, '⚠️ ' + T_KEY(t, 'history.statusUncertainHint')),
115
+ React.createElement('div', { style: { display: 'flex', gap: '6px' } },
116
+ React.createElement('button', {
117
+ type: 'button',
118
+ className: 'dsh-cron-btn-secondary',
119
+ style: { padding: '2px 8px', fontSize: '11px' },
120
+ onClick: () => handleResolveUncertain(formId, run.id, 'completed')
121
+ }, '✓ ' + T_KEY(t, 'history.resolveCompleted')),
122
+ React.createElement('button', {
123
+ type: 'button',
124
+ className: 'dsh-cron-btn-secondary',
125
+ style: { padding: '2px 8px', fontSize: '11px' },
126
+ onClick: () => handleResolveUncertain(formId, run.id, 'failed')
127
+ }, '✕ ' + T_KEY(t, 'history.resolveFailed'))
128
+ )
129
+ ),
130
+ // #43, #353: Self-Healing Doctor Diagnosis & 1-Click Fix
131
+ (run.diagnosis || run.doctorReport) && React.createElement('div', {
132
+ className: 'dsh-cron-status-banner',
133
+ 'data-type': 'info',
134
+ style: { marginTop: '8px', display: 'flex', flexDirection: 'column', gap: '6px' }
135
+ },
136
+ React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
137
+ React.createElement('strong', null, '🩺 ' + T_KEY(t, run.doctorReport ? 'history.doctorDiagnosis' : 'history.diagnosis')),
138
+ React.createElement('div', { style: { display: 'flex', gap: '6px', alignItems: 'center' } },
139
+ (run.autoHealed || run.doctorReport?.autoHealed) && React.createElement('span', {
140
+ style: { fontSize: '10px', background: 'var(--dsh-cron-success-bg)', color: 'var(--dsh-cron-success)', padding: '1px 6px', borderRadius: '8px', fontWeight: '600' }
141
+ }, '✨ ' + T_KEY(t, 'history.autoHealed')),
142
+ (run.doctorReport?.category || run.category) && React.createElement('span', {
143
+ style: { fontSize: '10px', background: 'var(--dsw-alias-bg-layer-3)', padding: '1px 6px', borderRadius: '4px' }
144
+ }, run.doctorReport?.category || run.category),
145
+ run.confidence ? React.createElement('span', { style: { fontSize: '10px', color: 'var(--dsw-alias-label-secondary)' } }, '(' + run.confidence + ')') : null
146
+ )
147
+ ),
148
+ React.createElement('div', null, run.doctorReport?.cause || run.diagnosis),
149
+ run.doctorReport?.diff && React.createElement('div', {
150
+ style: {
151
+ background: 'var(--dsw-alias-bg-layer-2)',
152
+ border: '1px solid var(--dsw-alias-border-l2)',
153
+ borderRadius: '4px',
154
+ padding: '6px',
155
+ fontFamily: 'monospace',
156
+ fontSize: '11px',
157
+ maxHeight: '120px',
158
+ overflowY: 'auto',
159
+ whiteSpace: 'pre'
160
+ }
161
+ }, run.doctorReport.diff),
162
+ (run.doctorReport?.suggestedFix || run.suggestion) && React.createElement('div', { style: { display: 'flex', gap: '8px', marginTop: '4px' } },
163
+ typeof handleApplyDoctorFix === 'function' && React.createElement('button', {
164
+ type: 'button',
165
+ className: 'dsh-cron-btn-primary',
166
+ style: { padding: '3px 10px', fontSize: '12px' },
167
+ onClick: () => handleApplyDoctorFix(formId, run.doctorReport || { suggestedFix: run.suggestion })
168
+ }, '⚡ ' + T_KEY(t, 'history.doctorApply')),
169
+ React.createElement('button', {
170
+ type: 'button',
171
+ className: 'dsh-cron-btn-secondary',
172
+ style: { padding: '3px 10px', fontSize: '12px' },
173
+ onClick: () => openEditModal(selectedTaskMeta, { prompt: run.suggestion })
174
+ }, T_KEY(t, 'history.applySuggestion'))
175
+ )
176
+ ),
177
+ // #350 Swarm Workers Breakdown
178
+ run.subtaskRuns && Array.isArray(run.subtaskRuns) && run.subtaskRuns.length > 0 && React.createElement('div', {
179
+ style: { marginTop: '8px', padding: '8px', background: 'var(--dsw-alias-bg-layer-2)', borderRadius: '6px', fontSize: '12px' }
180
+ },
181
+ React.createElement('div', { style: { fontWeight: '600', marginBottom: '4px' } }, '🐝 ' + T_KEY(t, 'history.swarmBadge') + ' (' + run.subtaskRuns.filter(r => r.status === 'success').length + '/' + run.subtaskRuns.length + ')'),
182
+ React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px' } },
183
+ run.subtaskRuns.map((st, sidx) => React.createElement('div', {
184
+ key: sidx,
185
+ style: { display: 'flex', justifyContent: 'space-between', padding: '2px 6px', background: 'var(--dsw-alias-bg-layer-3)', borderRadius: '4px' }
186
+ },
187
+ React.createElement('span', null, (st.status === 'success' ? '✅ ' : '❌ ') + st.title),
188
+ React.createElement('span', { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: '11px' } }, (st.durationMs || 0) + 'ms')
189
+ ))
190
+ )
191
+ ),
192
+ // #351 Git Diff & Patch Review
193
+ run.gitDiff && run.gitDiff.hasChanges && React.createElement('div', {
194
+ className: 'dsh-cron-status-banner',
195
+ style: { marginTop: '8px', display: 'flex', flexDirection: 'column', gap: '6px' }
196
+ },
197
+ React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
198
+ React.createElement('strong', null, '📝 ' + T_KEY(t, 'history.gitDiff')),
199
+ React.createElement('span', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary)' } }, run.gitDiff.stat || '')
200
+ ),
201
+ run.gitDiff.diff && React.createElement('div', {
202
+ style: {
203
+ background: 'var(--dsw-alias-bg-layer-2)',
204
+ border: '1px solid var(--dsw-alias-border-l2)',
205
+ borderRadius: '4px',
206
+ padding: '6px',
207
+ fontFamily: 'monospace',
208
+ fontSize: '11px',
209
+ maxHeight: '140px',
210
+ overflowY: 'auto',
211
+ whiteSpace: 'pre'
212
+ }
213
+ }, run.gitDiff.diff),
214
+ React.createElement('div', { style: { display: 'flex', gap: '8px', marginTop: '4px' } },
215
+ React.createElement('button', {
216
+ type: 'button',
217
+ className: 'dsh-cron-btn-secondary',
218
+ style: { padding: '2px 8px', fontSize: '12px' },
219
+ onClick: async () => {
220
+ if (!confirm('Discard all uncommitted changes in task directory?')) return;
221
+ const c = typeof AbortController !== 'undefined' ? new AbortController() : null;
222
+ const tId = c ? setTimeout(() => c.abort(), 15000) : null;
223
+ try {
224
+ const res = await fetch('/dsh-cron/tasks/' + encodeURIComponent(formId) + '/git-diff/discard', {
225
+ method: 'POST',
226
+ signal: c ? c.signal : undefined
227
+ });
228
+ const d = await res.json();
229
+ if (d.ok) { alert('Working directory reset successfully.'); if (formId) loadTaskHistory(formId); }
230
+ else alert(d.error || 'Failed to discard changes');
231
+ } catch (e) { alert(e.message); }
232
+ finally { if (tId) clearTimeout(tId); }
233
+ }
234
+ }, '🗑️ ' + T_KEY(t, 'history.discardDiff')),
235
+ React.createElement('button', {
236
+ type: 'button',
237
+ className: 'dsh-cron-btn-primary',
238
+ style: { padding: '2px 8px', fontSize: '12px' },
239
+ onClick: async () => {
240
+ const c = typeof AbortController !== 'undefined' ? new AbortController() : null;
241
+ const tId = c ? setTimeout(() => c.abort(), 15000) : null;
242
+ try {
243
+ const res = await fetch('/dsh-cron/tasks/' + encodeURIComponent(formId) + '/git-diff/apply', {
244
+ method: 'POST',
245
+ headers: { 'Content-Type': 'application/json' },
246
+ signal: c ? c.signal : undefined,
247
+ body: JSON.stringify({ diff: run.gitDiff.diff })
248
+ });
249
+ const d = await res.json();
250
+ if (d.ok) { alert('Patch applied successfully.'); if (formId) loadTaskHistory(formId); }
251
+ else alert(d.error || 'Failed to apply patch');
252
+ } catch (e) { alert(e.message); }
253
+ finally { if (tId) clearTimeout(tId); }
254
+ }
255
+ }, '✅ ' + T_KEY(t, 'history.applyDiff'))
256
+ )
257
+ ),
258
+ // #44: a run suppressed by its silent rule says why.
259
+ run.silentSkip && React.createElement('div', {
260
+ className: 'dsh-cron-hint',
261
+ style: { display: 'flex', gap: '6px', alignItems: 'center' }
262
+ },
263
+ React.createElement('span', { className: 'dsh-cron-badge-ref' }, T_KEY(t, 'history.silentSkip')),
264
+ React.createElement('span', null, run.silentReason || '')
265
+ ),
266
+ run.output && React.createElement('div', { className: 'dsh-cron-history-output' }, run.output),
267
+ run.error && React.createElement('div', { className: 'dsh-cron-history-output', style: { color: 'var(--dsh-cron-danger)', borderColor: 'var(--dsh-cron-danger-bg)' } }, run.error),
268
+ run.sessionId && React.createElement('button', {
269
+ type: 'button',
270
+ className: 'dsh-cron-btn-secondary',
271
+ style: { marginTop: '8px', fontSize: '12px', padding: '4px 10px' },
272
+ onClick: () => openSession(ctx, run.sessionId)
273
+ }, T_KEY(t, 'history.openSession'))
274
+ ))
275
+ ),
276
+ React.createElement('div', { className: 'dsh-cron-modal-foot', style: { marginTop: '18px' } },
277
+ React.createElement('button', {
278
+ type: 'button',
279
+ className: 'dsh-cron-btn-secondary',
280
+ onClick: () => { if (formId) loadTaskHistory(formId); }
281
+ }, T_KEY(t, 'history.refresh')),
282
+ React.createElement('button', {
283
+ type: 'button',
284
+ className: 'dsh-cron-btn-primary',
285
+ onClick: closeManualModalWithConfirm
286
+ }, T_KEY(t, 'modal.close'))
287
+ )
288
+ ) :
289
+
290
+ React.createElement('form', { onSubmit: handleSaveManual },
291
+ React.createElement('div', { className: 'dsh-cron-form-group' },
292
+ React.createElement('label', null, T_KEY(t, 'form.nameLabel')),
293
+ React.createElement('input', {
294
+ required: true,
295
+ autoFocus: true,
296
+ value: formTitle,
297
+ placeholder: T_KEY(t, 'form.namePlaceholder'),
298
+ onChange: (e) => setFormTitle(e.target.value)
299
+ })
300
+ ),
301
+ React.createElement('div', { className: 'dsh-cron-form-row' },
302
+ React.createElement('div', { className: 'dsh-cron-form-group' },
303
+ React.createElement('label', null, T_KEY(t, 'form.typeLabel')),
304
+ React.createElement('select', {
305
+ value: formType,
306
+ onChange: (e) => setFormType(e.target.value)
307
+ },
308
+ React.createElement('option', { value: 'llm' }, T_KEY(t, 'form.typeLlm')),
309
+ React.createElement('option', { value: 'script' }, T_KEY(t, 'form.typeScript')),
310
+ React.createElement('option', { value: 'node' }, T_KEY(t, 'form.typeNode')),
311
+ React.createElement('option', { value: 'python' }, T_KEY(t, 'form.typePython')),
312
+ React.createElement('option', { value: 'http' }, T_KEY(t, 'form.typeHttp')),
313
+ React.createElement('option', { value: 'ssh' }, T_KEY(t, 'form.typeSsh')),
314
+ React.createElement('option', { value: 'docker' }, T_KEY(t, 'form.typeDocker')),
315
+ React.createElement('option', { value: 'skill' }, T_KEY(t, 'form.typeSkill')),
316
+ React.createElement('option', { value: 'workflow' }, T_KEY(t, 'form.typeWorkflow'))
317
+ )
318
+ ),
319
+ React.createElement('div', { className: 'dsh-cron-form-group' },
320
+ React.createElement('label', null, T_KEY(t, 'form.scheduleLabel')),
321
+ React.createElement('input', {
322
+ required: true,
323
+ value: formSchedule,
324
+ placeholder: T_KEY(t, 'form.schedulePlaceholder'),
325
+ onChange: (e) => setFormSchedule(e.target.value)
326
+ }),
327
+ React.createElement('div', { style: { display: 'flex', gap: '5px', flexWrap: 'wrap', marginTop: '5px' } },
328
+ [
329
+ { label: '15m', expr: '*/15 * * * *' },
330
+ { label: '1h', expr: '0 * * * *' },
331
+ { label: 'Daily 09:00', expr: '0 9 * * *' },
332
+ { label: 'Weekdays', expr: '0 9 * * 1-5' },
333
+ { label: 'Weekly Mon', expr: '0 9 * * 1' },
334
+ ].map((p) => React.createElement('button', {
335
+ key: p.label,
336
+ type: 'button',
337
+ className: 'dsh-cron-btn-secondary',
338
+ style: { padding: '2px 7px', fontSize: '11px', borderRadius: '4px' },
339
+ onClick: () => { setFormSchedule(p.expr); handlePreviewSchedule(p.expr); }
340
+ }, p.label))
341
+ ),
342
+ React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', marginTop: '6px' } },
343
+ React.createElement('button', {
344
+ type: 'button',
345
+ className: 'dsh-cron-btn-secondary',
346
+ style: { padding: '3px 8px', fontSize: '11.5px' },
347
+ onClick: () => handlePreviewSchedule(formSchedule)
348
+ }, T_KEY(t, 'form.previewSchedule')),
349
+ schedulePreviewRuns && schedulePreviewRuns.length > 0 && React.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary, #999)' } },
350
+ T_KEY(t, 'form.previewScheduleTitle') + ' ' + schedulePreviewRuns.slice(0, 3).map(r => new Date(r).toLocaleTimeString()).join(', ')
351
+ )
352
+ )
353
+ )
354
+ ),
355
+ React.createElement('div', { className: 'dsh-cron-form-row' },
356
+ React.createElement('div', { className: 'dsh-cron-form-group' },
357
+ React.createElement('label', null, T_KEY(t, 'form.timeoutLabel')),
358
+ React.createElement('input', {
359
+ type: 'number',
360
+ min: 1,
361
+ value: formTimeoutSeconds,
362
+ placeholder: '1800',
363
+ onChange: (e) => setFormTimeoutSeconds(e.target.value)
364
+ })
365
+ ),
366
+ React.createElement('div', { className: 'dsh-cron-form-group' },
367
+ React.createElement('label', null, T_KEY(t, 'form.overlapLabel')),
368
+ React.createElement('select', {
369
+ value: formOverlapPolicy,
370
+ onChange: (e) => setFormOverlapPolicy(e.target.value)
371
+ },
372
+ React.createElement('option', { value: 'skip' }, T_KEY(t, 'form.overlapSkip')),
373
+ React.createElement('option', { value: 'queue' }, T_KEY(t, 'form.overlapQueue')),
374
+ React.createElement('option', { value: 'replace' }, T_KEY(t, 'form.overlapReplace'))
375
+ )
376
+ )
377
+ ),
378
+ React.createElement('div', { className: 'dsh-cron-form-row' },
379
+ React.createElement('div', { className: 'dsh-cron-form-group' },
380
+ React.createElement('label', null, T_KEY(t, 'form.timezoneLabel')),
381
+ React.createElement('input', {
382
+ value: formTimezone,
383
+ placeholder: T_KEY(t, 'form.timezonePlaceholder'),
384
+ onChange: (e) => setFormTimezone(e.target.value)
385
+ })
386
+ ),
387
+ React.createElement('div', { className: 'dsh-cron-form-group' },
388
+ React.createElement('label', null, T_KEY(t, 'form.misfireLabel')),
389
+ React.createElement('select', {
390
+ value: formMisfirePolicy,
391
+ onChange: (e) => setFormMisfirePolicy(e.target.value)
392
+ },
393
+ React.createElement('option', { value: 'skip' }, T_KEY(t, 'form.misfireSkip')),
394
+ React.createElement('option', { value: 'runOnce' }, T_KEY(t, 'form.misfireRunOnce')),
395
+ React.createElement('option', { value: 'catchUpAll' }, T_KEY(t, 'form.misfireCatchUp'))
396
+ )
397
+ )
398
+ ),
399
+ AGENT_FORM_TYPES.includes(formType) ? (
400
+ React.createElement('div', { className: 'dsh-cron-form-row' },
401
+ React.createElement('div', { className: 'dsh-cron-form-group' },
402
+ React.createElement('label', null, T_KEY(t, 'form.retriesLabel')),
403
+ React.createElement('input', {
404
+ type: 'number',
405
+ min: 0,
406
+ value: formMaxRetries,
407
+ placeholder: '0',
408
+ onChange: (e) => setFormMaxRetries(e.target.value)
409
+ })
410
+ ),
411
+ React.createElement('div', { className: 'dsh-cron-form-group' },
412
+ React.createElement('label', null, T_KEY(t, 'form.permissionLabel')),
413
+ React.createElement('select', {
414
+ value: formPermissionPreset,
415
+ onChange: (e) => setFormPermissionPreset(e.target.value)
416
+ },
417
+ React.createElement('option', { value: 'default' }, T_KEY(t, 'form.permissionDefault')),
418
+ React.createElement('option', { value: 'read-only' }, T_KEY(t, 'form.permissionReadonly')),
419
+ React.createElement('option', { value: 'workspace-write' }, T_KEY(t, 'form.permissionWrite')),
420
+ React.createElement('option', { value: 'danger-full-access' }, T_KEY(t, 'form.permissionFull'))
421
+ )
422
+ )
423
+ )
424
+ ) : (
425
+ React.createElement('div', { className: 'dsh-cron-form-group' },
426
+ React.createElement('label', null, T_KEY(t, 'form.retriesLabel')),
427
+ React.createElement('input', {
428
+ type: 'number',
429
+ min: 0,
430
+ value: formMaxRetries,
431
+ placeholder: '0',
432
+ onChange: (e) => setFormMaxRetries(e.target.value)
433
+ })
434
+ )
435
+ ),
436
+ React.createElement('div', { className: 'dsh-cron-form-group' },
437
+ React.createElement('label', null, T_KEY(t, 'form.kanbanLabel')),
438
+ React.createElement('select', {
439
+ value: formKanbanMode,
440
+ onChange: (e) => setFormKanbanMode(e.target.value)
441
+ },
442
+ React.createElement('option', { value: 'none' }, T_KEY(t, 'form.kanbanNone')),
443
+ React.createElement('option', { value: 'on_failure' }, T_KEY(t, 'form.kanbanOnFailure')),
444
+ React.createElement('option', { value: 'always' }, T_KEY(t, 'form.kanbanAlways'))
445
+ )
446
+ ),
447
+ AGENT_FORM_TYPES.includes(formType) && React.createElement('div', { className: 'dsh-cron-form-group' },
448
+ React.createElement('label', null, T_KEY(t, 'form.agentPresetLabel')),
449
+ React.createElement('input', {
450
+ type: 'text',
451
+ value: formAgentPreset,
452
+ placeholder: T_KEY(t, 'form.agentPresetPlaceholder'),
453
+ onChange: (e) => setFormAgentPreset(e.target.value)
454
+ }),
455
+ React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.agentPresetHint'))
456
+ ),
457
+ AGENT_FORM_TYPES.includes(formType) && React.createElement('div', { className: 'dsh-cron-form-group' },
458
+ React.createElement('label', null, T_KEY(t, 'form.sessionTitleLabel')),
459
+ React.createElement('input', {
460
+ type: 'text',
461
+ value: formSessionTitle,
462
+ placeholder: T_KEY(t, 'form.sessionTitlePlaceholder'),
463
+ onChange: (e) => setFormSessionTitle(e.target.value)
464
+ }),
465
+ React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.sessionTitleHint'))
466
+ ),
467
+ AGENT_FORM_TYPES.includes(formType) && React.createElement('div', { className: 'dsh-cron-form-row' },
468
+ React.createElement('div', { className: 'dsh-cron-form-group' },
469
+ React.createElement('label', null, T_KEY(t, 'form.targetSessionIdLabel')),
470
+ React.createElement('input', {
471
+ type: 'text',
472
+ value: formTargetSessionId,
473
+ placeholder: T_KEY(t, 'form.targetSessionIdPlaceholder'),
474
+ onChange: (e) => setFormTargetSessionId(e.target.value)
475
+ }),
476
+ React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.targetSessionIdHint'))
477
+ ),
478
+ React.createElement('div', { className: 'dsh-cron-form-group' },
479
+ React.createElement('label', null, T_KEY(t, 'form.targetSessionResetLabel')),
480
+ React.createElement('select', {
481
+ value: formTargetSessionReset,
482
+ onChange: (e) => setFormTargetSessionReset(e.target.value)
483
+ },
484
+ React.createElement('option', { value: 'never' }, T_KEY(t, 'form.targetSessionResetNever')),
485
+ React.createElement('option', { value: 'daily' }, T_KEY(t, 'form.targetSessionResetDaily')),
486
+ React.createElement('option', { value: 'weekly' }, T_KEY(t, 'form.targetSessionResetWeekly'))
487
+ ),
488
+ React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.targetSessionResetHint'))
489
+ )
490
+ ),
491
+ AGENT_FORM_TYPES.includes(formType) && React.createElement('div', { className: 'dsh-cron-form-row' },
492
+ React.createElement('div', { className: 'dsh-cron-form-group' },
493
+ React.createElement('label', null, T_KEY(t, 'form.providerLabel')),
494
+ React.createElement('select', {
495
+ value: formProvider,
496
+ onChange: handleProviderChange
497
+ },
498
+ React.createElement('option', { value: '' }, T_KEY(t, 'form.providerDefault')),
499
+ providers.map((p) => React.createElement('option', { key: p.id, value: p.id }, p.name || p.id))
500
+ )
501
+ ),
502
+ React.createElement('div', { className: 'dsh-cron-form-group' },
503
+ React.createElement('label', null, T_KEY(t, 'form.modelLabel')),
504
+ React.createElement('select', {
505
+ value: formModel,
506
+ onChange: (e) => setFormModel(e.target.value)
507
+ },
508
+ React.createElement('option', { value: '' }, T_KEY(t, 'form.modelDefault')),
509
+ models.map((m) => React.createElement('option', { key: m.id, value: m.id }, m.name || m.id))
510
+ )
511
+ ),
512
+ // #45: one retry on a stronger model when the cheap one fails.
513
+ React.createElement('div', { className: 'dsh-cron-form-group' },
514
+ React.createElement('label', null, T_KEY(t, 'form.fallbackModelLabel')),
515
+ React.createElement('input', {
516
+ type: 'text',
517
+ value: formFallbackModel,
518
+ placeholder: T_KEY(t, 'form.fallbackModelPlaceholder'),
519
+ onChange: (e) => setFormFallbackModel(e.target.value)
520
+ }),
521
+ React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.fallbackModelHint'))
522
+ ),
523
+ // #43: diagnose failures with a model.
524
+ React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginTop: '4px' } },
525
+ React.createElement('input', {
526
+ type: 'checkbox',
527
+ checked: formInspectOnFailure,
528
+ onChange: (e) => setFormInspectOnFailure(e.target.checked)
529
+ }),
530
+ React.createElement('span', null, T_KEY(t, 'form.inspectOnFailureLabel'))
531
+ ),
532
+ formInspectOnFailure && React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.inspectOnFailureHint')),
533
+ // #318: require approval checkbox
534
+ React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginTop: '8px' } },
535
+ React.createElement('input', {
536
+ type: 'checkbox',
537
+ checked: formRequireApproval,
538
+ onChange: (e) => setFormRequireApproval(e.target.checked)
539
+ }),
540
+ React.createElement('span', null, T_KEY(t, 'form.requireApprovalLabel'))
541
+ ),
542
+ formRequireApproval && React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.requireApprovalHint'))
543
+ ),
544
+ formType === 'http' && React.createElement('div', { className: 'dsh-cron-form-row' },
545
+ React.createElement('div', { className: 'dsh-cron-form-group' },
546
+ React.createElement('label', null, T_KEY(t, 'form.httpMethodLabel')),
547
+ React.createElement('select', {
548
+ value: formHttpMethod,
549
+ onChange: (e) => setFormHttpMethod(e.target.value)
550
+ },
551
+ ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'].map((m) => React.createElement('option', { key: m, value: m }, m))
552
+ )
553
+ ),
554
+ React.createElement('div', { className: 'dsh-cron-form-group' },
555
+ React.createElement('label', null, T_KEY(t, 'form.httpUrlLabel')),
556
+ React.createElement('input', {
557
+ required: true,
558
+ value: formHttpUrl,
559
+ placeholder: 'https://example.test/webhook',
560
+ onChange: (e) => setFormHttpUrl(e.target.value)
561
+ })
562
+ )
563
+ ),
564
+ formType === 'http' && React.createElement('div', { className: 'dsh-cron-form-row' },
565
+ React.createElement('div', { className: 'dsh-cron-form-group' },
566
+ React.createElement('label', null, T_KEY(t, 'form.httpHeadersLabel')),
567
+ React.createElement('input', {
568
+ value: formHttpHeaders,
569
+ placeholder: '{"Authorization":"Bearer ..."}',
570
+ onChange: (e) => setFormHttpHeaders(e.target.value)
571
+ })
572
+ ),
573
+ React.createElement('div', { className: 'dsh-cron-form-group' },
574
+ React.createElement('label', null, T_KEY(t, 'form.httpBodyLabel')),
575
+ React.createElement('input', {
576
+ value: formHttpBody,
577
+ onChange: (e) => setFormHttpBody(e.target.value)
578
+ })
579
+ )
580
+ ),