@goodandready/dsh-cron 0.3.15 → 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api-actions.js +317 -0
- package/lib/api-collection.js +249 -0
- package/lib/api-item.js +190 -0
- package/lib/api.js +12 -747
- package/lib/http-utils.js +7 -1
- package/lib/scheduler-assists.js +142 -0
- package/lib/scheduler-completion.js +413 -0
- package/lib/scheduler-execution.js +30 -738
- package/lib/scheduler-preflight.js +195 -0
- package/package.json +1 -1
package/lib/api-item.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { logStreamer } from './log-streamer.js';
|
|
2
|
+
import { applyTaskPatch } from './task-patch.js';
|
|
3
|
+
import { MANAGED_BY_CONFIG } from './config-jobs.js';
|
|
4
|
+
import { sendJson, rejectCrossOrigin, readBody, SCRIPT_CONFIRM_HEADER, redactTaskSecrets } from './http-utils.js';
|
|
5
|
+
import { normalizeTaskType } from './runtimes.js';
|
|
6
|
+
import { handleHeartbeatPing, handleDryRunTask } from './api-helpers.js';
|
|
7
|
+
import { handleItemPost } from './api-actions.js';
|
|
8
|
+
import { refuseConfigOwned, NOT_FOUND, NOT_ALLOWED } from './api-collection.js';
|
|
9
|
+
|
|
10
|
+
// ------------------------------------------------------------ single task
|
|
11
|
+
|
|
12
|
+
export async function handleTaskItem({ store, scheduler, req, res, url, id, action, subAction, apiToken, ctx }) {
|
|
13
|
+
if (action === 'heartbeat') {
|
|
14
|
+
handleHeartbeatPing({ store, req, res, taskId: id, apiToken, ctx });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (req.method === 'POST' && action === 'dry-run') {
|
|
19
|
+
await handleDryRunTask({ store, scheduler, req, res, id, apiToken, ctx });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (req.method === 'GET') {
|
|
24
|
+
if (action === 'stream') {
|
|
25
|
+
logStreamer.handleSseRequest(id, req, res);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (rejectCrossOrigin(req, res, { apiToken, ctx })) return;
|
|
29
|
+
if (action === 'archive') {
|
|
30
|
+
const limit = parseInt(url.searchParams.get('limit') || '50', 10);
|
|
31
|
+
const offset = parseInt(url.searchParams.get('offset') || '0', 10);
|
|
32
|
+
const search = url.searchParams.get('search') || '';
|
|
33
|
+
const result = typeof store.getArchivedRuns === 'function'
|
|
34
|
+
? store.getArchivedRuns(id, { limit, offset, search })
|
|
35
|
+
: { runs: [], total: 0 };
|
|
36
|
+
sendJson(res, 200, { ok: true, taskId: id, ...result });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (action === 'stats') {
|
|
40
|
+
const stats = typeof store.getTaskStats === 'function' ? store.getTaskStats(id) : null;
|
|
41
|
+
sendJson(res, 200, { ok: true, taskId: id, stats });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (action === 'prompt-history') {
|
|
45
|
+
const task = store.get(id);
|
|
46
|
+
if (!task) {
|
|
47
|
+
sendJson(res, 404, NOT_FOUND);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
sendJson(res, 200, {
|
|
51
|
+
ok: true,
|
|
52
|
+
taskId: id,
|
|
53
|
+
promptVersion: task.promptVersion || 1,
|
|
54
|
+
promptHistory: task.promptHistory || [],
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (action === 'history') {
|
|
59
|
+
const limit = parseInt(url.searchParams.get('limit') || '20', 10);
|
|
60
|
+
sendJson(res, 200, { ok: true, history: store.getHistory(id, limit) });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (action === 'git-diff') {
|
|
64
|
+
const task = store.get(id);
|
|
65
|
+
if (!task) {
|
|
66
|
+
sendJson(res, 404, NOT_FOUND);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const runs = store.getHistory(id, 10);
|
|
70
|
+
const runId = url.searchParams.get('runId') || subAction;
|
|
71
|
+
const targetRun = runId ? runs.find((r) => r.id === runId) : runs[0];
|
|
72
|
+
const diffData = targetRun?.gitDiff || null;
|
|
73
|
+
sendJson(res, 200, {
|
|
74
|
+
ok: true,
|
|
75
|
+
taskId: id,
|
|
76
|
+
runId: targetRun?.id || null,
|
|
77
|
+
gitDiff: diffData,
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (action === 'doctor') {
|
|
82
|
+
const task = store.get(id);
|
|
83
|
+
if (!task) {
|
|
84
|
+
sendJson(res, 404, NOT_FOUND);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const runs = store.getHistory(id, 5);
|
|
88
|
+
const latestRun = runs[0] || null;
|
|
89
|
+
const reportRun = runs.find((r) => r.doctorReport) || latestRun;
|
|
90
|
+
sendJson(res, 200, {
|
|
91
|
+
ok: true,
|
|
92
|
+
taskId: id,
|
|
93
|
+
doctorReport: reportRun?.doctorReport || null,
|
|
94
|
+
revisions: task.revisions || [],
|
|
95
|
+
previousPrompt: task.previousPrompt || null,
|
|
96
|
+
autoHeal: Boolean(task.autoHeal || task.selfHealing?.autoApply),
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!action) {
|
|
101
|
+
const task = store.get(id);
|
|
102
|
+
if (!task) {
|
|
103
|
+
sendJson(res, 404, NOT_FOUND);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
sendJson(res, 200, {
|
|
107
|
+
ok: true,
|
|
108
|
+
task: {
|
|
109
|
+
...redactTaskSecrets(task),
|
|
110
|
+
running: scheduler.isRunning(task.id),
|
|
111
|
+
runningSince: scheduler.runningSince(task.id),
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (req.method === 'POST') {
|
|
118
|
+
const handled = await handleItemPost({ store, scheduler, req, res, url, id, action, subAction });
|
|
119
|
+
if (handled) return;
|
|
120
|
+
}
|
|
121
|
+
if (req.method === 'PATCH') {
|
|
122
|
+
await patchTask({ store, scheduler, req, res, id });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (req.method === 'DELETE' || (req.method === 'POST' && action === 'delete')) {
|
|
126
|
+
deleteTask({ store, scheduler, res, id });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
sendJson(res, 405, NOT_ALLOWED);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Task actions that are plain POSTs. Returns true when one matched. */
|
|
133
|
+
|
|
134
|
+
export async function patchTask({ store, scheduler, req, res, id }) {
|
|
135
|
+
const current = store.get(id);
|
|
136
|
+
if (!current) {
|
|
137
|
+
sendJson(res, 404, NOT_FOUND);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (current.managedBy === MANAGED_BY_CONFIG) {
|
|
141
|
+
refuseConfigOwned(res, id);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const { body, error } = await readBody(req, res);
|
|
145
|
+
if (error) return;
|
|
146
|
+
const result = applyTaskPatch({
|
|
147
|
+
store,
|
|
148
|
+
scheduler,
|
|
149
|
+
id,
|
|
150
|
+
body,
|
|
151
|
+
allowCodeSwitch: req.headers[SCRIPT_CONFIRM_HEADER] === 'script',
|
|
152
|
+
});
|
|
153
|
+
if (!result.ok) {
|
|
154
|
+
const status = result.status || (result.notFound ? 404 : (result.needsConfirmation ? 403 : 400));
|
|
155
|
+
const error = result.needsConfirmation
|
|
156
|
+
? `Switching a task to the ${normalizeTaskType(body.type)} type over HTTP requires the ${SCRIPT_CONFIRM_HEADER}: script header`
|
|
157
|
+
: result.error;
|
|
158
|
+
sendJson(res, status, {
|
|
159
|
+
ok: false,
|
|
160
|
+
error,
|
|
161
|
+
...(result.unknownChannels ? { unknownChannels: result.unknownChannels } : {}),
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
sendJson(res, 200, { ok: true, task: redactTaskSecrets(result.task) });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
export function deleteTask({ store, scheduler, res, id }) {
|
|
170
|
+
const current = store.get(id);
|
|
171
|
+
if (current && current.managedBy === MANAGED_BY_CONFIG) {
|
|
172
|
+
refuseConfigOwned(res, id);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (current) {
|
|
176
|
+
if (scheduler && typeof scheduler.removeTask === 'function') {
|
|
177
|
+
scheduler.removeTask(id);
|
|
178
|
+
} else if (scheduler) {
|
|
179
|
+
scheduler.pauseTask(id);
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
store.delete(id);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
sendJson(res, 500, { ok: false, error: 'Failed to delete task: ' + err.message });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
sendJson(res, 200, { ok: true });
|
|
189
|
+
}
|
|
190
|
+
|