@goodandready/dsh-cron 0.3.12 → 0.3.13
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/README.md +12 -0
- package/README.ru.md +12 -0
- package/README.zh.md +12 -0
- package/lib/api.js +117 -5
- package/lib/branching.js +341 -0
- package/lib/client.js +870 -758
- package/lib/dag.js +88 -18
- package/lib/doctor.js +431 -0
- package/lib/scheduler-execution.js +59 -6
- package/lib/task-create.js +9 -0
- package/lib/task-patch.js +18 -1
- package/lib/task-transfer.js +7 -0
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveBranchTargets } from './branching.js';
|
|
2
|
+
import { shouldAutoHeal, applyDoctorFix } from './doctor.js';
|
|
1
3
|
import { recordPromptRunOutcome } from './prompt-evolver.js';
|
|
2
4
|
import { evaluateAdaptiveRun, calculateNextAdaptiveState } from './adaptive.js';
|
|
3
5
|
import { approvalManager } from './approval.js';
|
|
@@ -441,14 +443,42 @@ export async function applySilentRuleToRun(task, runInfo, { askModel, readSettin
|
|
|
441
443
|
/**
|
|
442
444
|
* Run the model-assisted steps for a finished run.
|
|
443
445
|
*/
|
|
444
|
-
export async function applyModelAssists(task, runInfo, options) {
|
|
446
|
+
export async function applyModelAssists(task, runInfo, options = {}) {
|
|
445
447
|
const inspection = await inspectFailureRun(task, runInfo, options);
|
|
446
448
|
if (inspection.inspected) {
|
|
447
449
|
runInfo.diagnosis = inspection.diagnosis;
|
|
448
450
|
runInfo.suggestion = inspection.suggestion;
|
|
449
451
|
runInfo.confidence = inspection.confidence;
|
|
452
|
+
runInfo.doctorReport = inspection.doctorReport || {
|
|
453
|
+
category: inspection.category || 'runtime_error',
|
|
454
|
+
cause: inspection.diagnosis,
|
|
455
|
+
suggestedFix: inspection.suggestion,
|
|
456
|
+
confidence: inspection.confidence,
|
|
457
|
+
diff: inspection.diff || '',
|
|
458
|
+
timestamp: Date.now(),
|
|
459
|
+
applied: false,
|
|
460
|
+
autoHealed: false,
|
|
461
|
+
};
|
|
450
462
|
logger.info(`[dsh-cron] task "${task.title}" failure diagnosed (${inspection.confidence}): ${inspection.diagnosis}`);
|
|
451
|
-
|
|
463
|
+
|
|
464
|
+
// Safe Auto-Heal Check (#353)
|
|
465
|
+
if (shouldAutoHeal(task, runInfo.doctorReport)) {
|
|
466
|
+
try {
|
|
467
|
+
const healResult = await applyDoctorFix(task, runInfo.doctorReport, {
|
|
468
|
+
store: options.store,
|
|
469
|
+
user: 'scheduler:auto-heal',
|
|
470
|
+
isAutoHeal: true,
|
|
471
|
+
});
|
|
472
|
+
if (healResult.ok) {
|
|
473
|
+
logger.info(`[dsh-cron] task "${task.title}" auto-healed successfully with doctor fix!`);
|
|
474
|
+
runInfo.autoHealed = true;
|
|
475
|
+
if (runInfo.doctorReport) runInfo.doctorReport.autoHealed = true;
|
|
476
|
+
}
|
|
477
|
+
} catch (autoHealErr) {
|
|
478
|
+
logger.warn(`[dsh-cron] failed to auto-heal task "${task.title}":`, autoHealErr.message);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} else if (inspection.error && (task.inspectOnFailure || task.autoDiagnose || task.autoHeal)) {
|
|
452
482
|
logger.warn(`[dsh-cron] failure inspection for "${task.title}" not applied: ${inspection.error}`);
|
|
453
483
|
}
|
|
454
484
|
|
|
@@ -769,8 +799,14 @@ export async function handleCompleteRun(scheduler, task, taskId, currentRun, out
|
|
|
769
799
|
runInfo.artifacts = scheduler.store.getArtifactsForRun(runInfo.runId);
|
|
770
800
|
}
|
|
771
801
|
|
|
772
|
-
// Chain Checkpoints (#319, #328)
|
|
773
|
-
const
|
|
802
|
+
// Chain Checkpoints & Decision Nodes (#319, #328, #352)
|
|
803
|
+
const branchResult = resolveBranchTargets(task, outcome, runInfo);
|
|
804
|
+
if (branchResult && branchResult.decision) {
|
|
805
|
+
runInfo.branchDecision = branchResult.decision;
|
|
806
|
+
}
|
|
807
|
+
const chainedTargets = branchResult
|
|
808
|
+
? branchResult.targets
|
|
809
|
+
: parseChainTargets(outcome.status === 'success' ? task.onSuccess : task.onFailure);
|
|
774
810
|
let hasDependents = false;
|
|
775
811
|
if (scheduler.store?.tasks) {
|
|
776
812
|
for (const t of Object.values(scheduler.store.tasks)) {
|
|
@@ -888,10 +924,27 @@ export async function handleCompleteRun(scheduler, task, taskId, currentRun, out
|
|
|
888
924
|
}
|
|
889
925
|
}
|
|
890
926
|
|
|
891
|
-
// 2. Task Chaining (#137, #328 Fan-Out / Fan-In)
|
|
892
|
-
const targets =
|
|
927
|
+
// 2. Task Chaining & Decision Nodes (#137, #328, #352 Fan-Out / Fan-In)
|
|
928
|
+
const targets = branchResult
|
|
929
|
+
? branchResult.targets
|
|
930
|
+
: parseChainTargets(isSuccess ? task.onSuccess : (isFailure ? task.onFailure : null));
|
|
893
931
|
const currentDepth = options.chainDepth || 0;
|
|
894
932
|
|
|
933
|
+
if (branchResult && branchResult.decision) {
|
|
934
|
+
logger.info(`[dsh-cron] decision node in "${task.title}": routed to ${targets.join(', ') || 'none'} (${branchResult.decision.isElse ? 'fallback else' : (branchResult.decision.matched ? `rule #${branchResult.decision.matchedIndex + 1}` : 'unmatched')})`);
|
|
935
|
+
if (chainRunId && scheduler.store.chainCheckpointManager && Array.isArray(task.branchRules)) {
|
|
936
|
+
for (const rule of task.branchRules) {
|
|
937
|
+
if (rule && rule.targetTask && !targets.includes(rule.targetTask)) {
|
|
938
|
+
scheduler.store.chainCheckpointManager.recordStepSnapshot(chainRunId, rule.targetTask, {
|
|
939
|
+
status: 'skipped',
|
|
940
|
+
skippedReason: 'condition not met',
|
|
941
|
+
durationMs: 0,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
895
948
|
if (currentDepth < 4) {
|
|
896
949
|
// 2.1 Trigger explicit targets (Fan-Out)
|
|
897
950
|
for (const targetId of targets) {
|
package/lib/task-create.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeBranchRules } from './branching.js';
|
|
1
2
|
import { sanitizePromptHistory } from './prompt-evolver.js';
|
|
2
3
|
import { sanitizeAdaptiveConfig } from './adaptive.js';
|
|
3
4
|
import { sanitizeValidators } from './validators.js';
|
|
@@ -55,6 +56,14 @@ export function executeCreateTask(store, scheduler, args) {
|
|
|
55
56
|
webhookDebounceMs: args.webhookDebounceMs !== undefined ? Number(args.webhookDebounceMs) : (args.webhookTrigger?.debounceMs !== undefined ? Number(args.webhookTrigger.debounceMs) : undefined),
|
|
56
57
|
onSuccess: args.onSuccess ? String(args.onSuccess).trim() : '',
|
|
57
58
|
onFailure: args.onFailure ? String(args.onFailure).trim() : '',
|
|
59
|
+
branchRules: args.branchRules !== undefined ? sanitizeBranchRules(args.branchRules) : undefined,
|
|
60
|
+
elseTask: args.elseTask ? String(args.elseTask).trim() : undefined,
|
|
61
|
+
autoHeal: args.autoHeal !== undefined ? Boolean(args.autoHeal) : undefined,
|
|
62
|
+
selfHealing: args.selfHealing && typeof args.selfHealing === 'object' ? {
|
|
63
|
+
autoApply: Boolean(args.selfHealing.autoApply),
|
|
64
|
+
lastPatchedAt: Number(args.selfHealing.lastPatchedAt) || 0,
|
|
65
|
+
autoPatchesCount: Number(args.selfHealing.autoPatchesCount) || 0,
|
|
66
|
+
} : undefined,
|
|
58
67
|
dependsOn: args.dependsOn !== undefined ? sanitizeDependsOn(args.dependsOn) : undefined,
|
|
59
68
|
joinPolicy: args.joinPolicy !== undefined ? sanitizeJoinPolicy(args.joinPolicy) : undefined,
|
|
60
69
|
adaptive: args.adaptive !== undefined ? sanitizeAdaptiveConfig(args.adaptive) : undefined,
|
package/lib/task-patch.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeBranchRules } from './branching.js';
|
|
1
2
|
import { trackPromptEdit, sanitizePromptHistory } from './prompt-evolver.js';
|
|
2
3
|
import { sanitizeAdaptiveConfig } from './adaptive.js';
|
|
3
4
|
import { sanitizeValidators } from './validators.js';
|
|
@@ -121,6 +122,22 @@ export function buildTaskPatch(current, body) {
|
|
|
121
122
|
if (patch.validators !== undefined) {
|
|
122
123
|
patch.validators = Array.isArray(patch.validators) ? sanitizeValidators(patch.validators) : [];
|
|
123
124
|
}
|
|
125
|
+
if (patch.branchRules !== undefined) {
|
|
126
|
+
patch.branchRules = sanitizeBranchRules(patch.branchRules);
|
|
127
|
+
}
|
|
128
|
+
if (patch.elseTask !== undefined) {
|
|
129
|
+
patch.elseTask = patch.elseTask ? String(patch.elseTask).trim() : null;
|
|
130
|
+
}
|
|
131
|
+
if (patch.autoHeal !== undefined) {
|
|
132
|
+
patch.autoHeal = Boolean(patch.autoHeal);
|
|
133
|
+
}
|
|
134
|
+
if (patch.selfHealing !== undefined && patch.selfHealing && typeof patch.selfHealing === 'object') {
|
|
135
|
+
patch.selfHealing = {
|
|
136
|
+
autoApply: Boolean(patch.selfHealing.autoApply),
|
|
137
|
+
lastPatchedAt: Number(patch.selfHealing.lastPatchedAt) || 0,
|
|
138
|
+
autoPatchesCount: Number(patch.selfHealing.autoPatchesCount) || 0,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
124
141
|
if (patch.prompt !== undefined && current && typeof current.prompt === 'string') {
|
|
125
142
|
const edit = trackPromptEdit(current, patch.prompt, body && body.promptNote ? body.promptNote : 'Manual edit');
|
|
126
143
|
patch.prompt = edit.prompt;
|
|
@@ -181,7 +198,7 @@ export function applyTaskPatch({ store, scheduler, id, body, allowCodeSwitch = f
|
|
|
181
198
|
}
|
|
182
199
|
const built = buildTaskPatch(current, body);
|
|
183
200
|
if (!built.ok) return built;
|
|
184
|
-
if (built.patch.dependsOn !== undefined || built.patch.onSuccess !== undefined) {
|
|
201
|
+
if (built.patch.dependsOn !== undefined || built.patch.onSuccess !== undefined || built.patch.branchRules !== undefined || built.patch.elseTask !== undefined) {
|
|
185
202
|
const dagCheck = validateDAG(store?.tasks || {}, { ...current, ...built.patch, id });
|
|
186
203
|
if (!dagCheck.ok) {
|
|
187
204
|
return { ok: false, error: dagCheck.error };
|
package/lib/task-transfer.js
CHANGED
|
@@ -89,6 +89,13 @@ export const PATCHABLE_TASK_FIELDS = [
|
|
|
89
89
|
'adaptiveState',
|
|
90
90
|
'promptVersion',
|
|
91
91
|
'promptHistory',
|
|
92
|
+
'branchRules',
|
|
93
|
+
'elseTask',
|
|
94
|
+
'autoHeal',
|
|
95
|
+
'selfHealing',
|
|
96
|
+
'revisions',
|
|
97
|
+
'previousPrompt',
|
|
98
|
+
'previousScript',
|
|
92
99
|
];
|
|
93
100
|
|
|
94
101
|
/** Fields a duplicate inherits; anything else is runtime state, not config. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-cron",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.13",
|
|
4
4
|
"description": "Background automation runner for DSH: isolated agent runs, script/HTTP/SSH/Docker runtimes, cost guard, notifications, heartbeats.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|