@goodandready/dsh-cron 0.2.4 → 0.2.6

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.
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Failure inspector (#43): after a run fails, ask the model what went wrong and
3
+ * what to change in the task.
4
+ *
5
+ * The value is speed: instead of reading a stack trace or a wall of logs, the
6
+ * history entry carries a short diagnosis and a proposed prompt change the user
7
+ * can apply with one click. Nothing is applied automatically.
8
+ *
9
+ * Fail-open like the silent rule: no flag, no model, a failed call or an
10
+ * unreadable answer leave the run exactly as it was.
11
+ */
12
+
13
+ import { askModel, parseJsonAnswer, resolveAskTarget } from './llm-ask.js';
14
+
15
+ /** Agent-mediated tasks have a prompt worth revising. */
16
+ export const INSPECTABLE_TASK_TYPES = ['llm', 'skill', 'workflow'];
17
+
18
+ export function supportsInspection(task) {
19
+ const type = String((task && task.type) || 'llm');
20
+ return INSPECTABLE_TASK_TYPES.includes(type);
21
+ }
22
+
23
+ const SYSTEM_PROMPT = [
24
+ 'You diagnose a failed scheduled agent task.',
25
+ 'Answer with a single JSON object and nothing else:',
26
+ '{"diagnosis": "what went wrong, in one or two sentences", "suggestion": "a concrete improved prompt or configuration change", "confidence": "low"|"medium"|"high"}',
27
+ 'Be specific and short. If the cause is unclear from the material, say so and set confidence to low.',
28
+ ].join(' ');
29
+
30
+ export function buildInspectPrompt(task, runInfo) {
31
+ const error = String((runInfo && runInfo.error) || '').slice(0, 2000);
32
+ const output = String((runInfo && runInfo.output) || '').slice(0, 2000);
33
+ return [
34
+ `Task: ${task.title || 'scheduled task'} (type: ${task.type || 'llm'})`,
35
+ `Schedule: ${task.scheduleText || task.schedule || ''}`,
36
+ task.model ? `Model: ${task.model}` : '',
37
+ '',
38
+ 'Current prompt:',
39
+ '```',
40
+ String(task.prompt || '').slice(0, 2000),
41
+ '```',
42
+ '',
43
+ `Result: ${(runInfo && runInfo.status) || 'error'}`,
44
+ 'Error:',
45
+ '```',
46
+ error || '(no error text)',
47
+ '```',
48
+ output ? 'Last output:' : '',
49
+ output ? '```' : '',
50
+ output,
51
+ output ? '```' : '',
52
+ ].filter((line) => line !== '').join('\n');
53
+ }
54
+
55
+ /** Read a diagnosis out of a model answer; anything unusable is rejected. */
56
+ export function readDiagnosis(text) {
57
+ const parsed = parseJsonAnswer(text);
58
+ if (!parsed || !parsed.diagnosis) return { ok: false, error: 'the model did not answer with a diagnosis' };
59
+ const confidence = ['low', 'medium', 'high'].includes(String(parsed.confidence || '').toLowerCase())
60
+ ? String(parsed.confidence).toLowerCase()
61
+ : 'low';
62
+ return {
63
+ ok: true,
64
+ diagnosis: String(parsed.diagnosis).slice(0, 1000),
65
+ suggestion: String(parsed.suggestion || '').slice(0, 2000),
66
+ confidence,
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Inspect a failed run. Returns `{ inspected: true, diagnosis, suggestion,
72
+ * confidence }` or `{ inspected: false, error }` — never throws.
73
+ */
74
+ export async function inspectFailure({ task, runInfo, ask, preferredModel, timeoutMs }) {
75
+ // Order matters: an inspection only ever concerns failed runs, so a healthy
76
+ // run must not produce a "no model available" warning.
77
+ const status = (runInfo && runInfo.status) || '';
78
+ if (status !== 'error' && status !== 'timeout') return { inspected: false };
79
+ if (!task || !task.inspectOnFailure) return { inspected: false };
80
+ if (!supportsInspection(task)) return { inspected: false, error: 'this task type has no prompt to revise' };
81
+ if (typeof ask !== 'function') return { inspected: false, error: 'no model is available for the inspection' };
82
+
83
+ try {
84
+ const result = await ask({
85
+ task,
86
+ prompt: buildInspectPrompt(task, runInfo),
87
+ system: SYSTEM_PROMPT,
88
+ preferredModel,
89
+ maxTokens: 400,
90
+ timeoutMs,
91
+ });
92
+ if (!result || !result.ok) return { inspected: false, error: (result && result.error) || 'the model call failed' };
93
+ const diagnosis = readDiagnosis(result.text);
94
+ if (!diagnosis.ok) return { inspected: false, error: diagnosis.error };
95
+ return { inspected: true, diagnosis: diagnosis.diagnosis, suggestion: diagnosis.suggestion, confidence: diagnosis.confidence };
96
+ } catch (err) {
97
+ return { inspected: false, error: (err && err.message) || String(err) };
98
+ }
99
+ }
100
+
101
+ /** Ask helper bound to a context, used by the scheduler. */
102
+ export function makeInspectAsk(ctx) {
103
+ return async ({ prompt, system, preferredModel, maxTokens, timeoutMs, task }) => {
104
+ const target = resolveAskTarget(ctx, task || {}, preferredModel);
105
+ return askModel(ctx, { ...target, prompt, system, maxTokens, timeoutMs });
106
+ };
107
+ }
package/lib/http-utils.js CHANGED
@@ -85,3 +85,27 @@ export function pickPatchableFields(body, allowed) {
85
85
  }
86
86
  return patch;
87
87
  }
88
+
89
+ export function sendJson(res, statusCode, data) {
90
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
91
+ res.end(JSON.stringify(data));
92
+ }
93
+
94
+ export function rejectCrossOrigin(req, res) {
95
+ if (isCrossOrigin(req)) {
96
+ sendJson(res, 403, { ok: false, error: 'Cross-origin request rejected' });
97
+ return true;
98
+ }
99
+ return false;
100
+ }
101
+
102
+ export async function readBody(req, res) {
103
+ try {
104
+ return { body: await parseJsonBody(req) };
105
+ } catch (err) {
106
+ sendJson(res, err.statusCode || 400, { ok: false, error: err.message });
107
+ return { error: true };
108
+ }
109
+ }
110
+
111
+ /** Settings keys synchronized between the store and the settings service. */