@ai-dossier/sched 0.33.0 → 0.35.0
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 +26 -2
- package/dist/batch-dispatch.d.ts.map +1 -1
- package/dist/batch-dispatch.js +116 -19
- package/dist/batch-dispatch.js.map +1 -1
- package/dist/dispatch.d.ts +34 -2
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +115 -4
- package/dist/dispatch.js.map +1 -1
- package/dist/enqueue.d.ts +12 -0
- package/dist/enqueue.d.ts.map +1 -1
- package/dist/enqueue.js +25 -2
- package/dist/enqueue.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/persist.d.ts.map +1 -1
- package/dist/persist.js +59 -0
- package/dist/persist.js.map +1 -1
- package/dist/recovery.d.ts +2 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +5 -1
- package/dist/recovery.js.map +1 -1
- package/dist/state.d.ts +9 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +27 -0
- package/dist/state.js.map +1 -1
- package/dist/status.d.ts +6 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +7 -1
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +51 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -2
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,8 +89,8 @@ where every mechanical supervision decision is code, not remembered prose:
|
|
|
89
89
|
|
|
90
90
|
(`strong` sets only `model`, so its command falls back to the default claude template.)
|
|
91
91
|
What an engine is ACTUALLY running is always visible in two places, added in #680: the
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
`Dispatch (default): …` line in `sched status` (and its `--json` `dispatch` field), and the
|
|
93
|
+
`▶ sched dispatch (default): …` banner `sched start` prints once at startup — both show
|
|
94
94
|
`tier=agent/model` resolved exactly as spawns resolve. Note the inverse trap: running
|
|
95
95
|
the batch FROM opencode/GLM does not make it run ON opencode/GLM — without a
|
|
96
96
|
`dispatch.tiers` override every tier dispatches the default claude template regardless
|
|
@@ -283,6 +283,30 @@ Config schema moves to 1.8.0 (#565): a new top-level `default_batch_priority` ke
|
|
|
283
283
|
invalid value degrades the whole config file to built-in defaults, same contract as every
|
|
284
284
|
other field.
|
|
285
285
|
|
|
286
|
+
Config schema moves to 1.9.0 (#707): `dispatch.dispatch_profiles` names complete
|
|
287
|
+
dispatch configurations for batch runs. A profile has the same `command`, `tier_models`,
|
|
288
|
+
`prompt`, and optional per-tier `tiers` overrides as `dispatch`; the batch records the
|
|
289
|
+
profile name at enqueue time, and every member, tail, report, and recovery agent resolves
|
|
290
|
+
through that profile. For example:
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"dispatch": {
|
|
295
|
+
"dispatch_profiles": {
|
|
296
|
+
"claude": { "command": ["claude", "-p", "--model", "{model}"], "tier_models": { "mechanical": "haiku", "mid": "sonnet", "strong": "opus" } },
|
|
297
|
+
"glm": { "command": ["opencode", "run", "-m", "{model}", "--"], "tier_models": { "mechanical": "glm-flash", "mid": "glm", "strong": "glm-strong" } }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Use `sched enqueue --mode slot --batch <id> --dispatch glm` to select a profile
|
|
304
|
+
explicitly. Without `--dispatch`, a new batch detects Claude Code from `CLAUDECODE` or a
|
|
305
|
+
configured agent binary in its parent process chain. Detection is convenience only: if
|
|
306
|
+
profiles exist and detection is inconclusive, enqueue fails and names the available
|
|
307
|
+
profiles rather than silently choosing the default. `--dispatch` is intentionally rejected
|
|
308
|
+
for full-cycle entries; no profiles means legacy dispatch behavior remains unchanged.
|
|
309
|
+
|
|
286
310
|
Two engine-safety policies were explicit product decisions on #464:
|
|
287
311
|
|
|
288
312
|
- **Pid identity is hybrid-verified (decision 1, option C).** Every spawn records the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAmBH,OAAO,
|
|
1
|
+
{"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAmBH,OAAO,EAYL,KAAK,gBAAgB,EAGrB,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,KAAK,WAAW,EAQjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,OAAO,EAAa,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAUL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAoBpB,OAAO,EAAE,KAAK,QAAQ,EAA2D,MAAM,YAAY,CAAC;AACpG,OAAO,KAAK,EAEV,UAAU,EAEV,oBAAoB,EAMpB,WAAW,EAGZ,MAAM,SAAS,CAAC;AAYjB,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sGAAsG;IACtG,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,WAAW,CAAC;IAC5C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAugBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3F;AA0uCD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,IAAI,EACT,MAAM,EAAE,eAAe,GACtB,IAAI,CA+BN;AAiSD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,IAAI,GACR;IACD,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,cAAc,GAAG,qBAAqB,CAAC;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CA2KA;AA+gCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,GACzB,eAAe,CAgOjB"}
|
package/dist/batch-dispatch.js
CHANGED
|
@@ -1374,7 +1374,10 @@ function runValidate(deps, config, dispatch, batchId, now, result) {
|
|
|
1374
1374
|
return;
|
|
1375
1375
|
}
|
|
1376
1376
|
const offender = outcome.offenders[0];
|
|
1377
|
-
const { state: fixing, dispatch: fixDispatch } = (0, recovery_1.beginFixAttempt)(attributed, batchId, offender, rDeps,
|
|
1377
|
+
const { state: fixing, dispatch: fixDispatch } = (0, recovery_1.beginFixAttempt)(attributed, batchId, offender, rDeps,
|
|
1378
|
+
// #707: `dispatch` is the batch's own (profile-resolved) dispatch — the
|
|
1379
|
+
// fix agent rides the same family the members ran on.
|
|
1380
|
+
{ config, tests: outcome.attributed.get(offender) ?? [], dispatch });
|
|
1378
1381
|
deps.store.withLock((s) => ({
|
|
1379
1382
|
state: applyBatchAndIssues(s, fixing, batchId, []),
|
|
1380
1383
|
result: undefined,
|
|
@@ -1804,6 +1807,11 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
|
|
|
1804
1807
|
const batch = (0, state_1.findBatch)(state, batchId);
|
|
1805
1808
|
if (!batch)
|
|
1806
1809
|
throw new types_1.SchedNotFoundError(`Batch not found: ${batchId}`);
|
|
1810
|
+
// A gate recheck can immediately advance to the next provider dispatch, so
|
|
1811
|
+
// it must use the batch's recorded profile rather than the engine default.
|
|
1812
|
+
const batchDispatch = batch.dispatch_profile === null
|
|
1813
|
+
? dispatch
|
|
1814
|
+
: (0, dispatch_1.resolveProfiledDispatch)(config, batch.dispatch_profile);
|
|
1807
1815
|
if (batch.status !== 'blocked' || !batch.blocked_reason?.startsWith('gate-inconclusive:')) {
|
|
1808
1816
|
// #677 review: the landing-failure blocks have no resume verb (same as
|
|
1809
1817
|
// `suite-unreadable`) — name the actual reason and the operator path
|
|
@@ -1852,7 +1860,7 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
|
|
|
1852
1860
|
outputTail: recheck.outputTail,
|
|
1853
1861
|
durationMs: recheck.durationMs,
|
|
1854
1862
|
}, now);
|
|
1855
|
-
completeMemberGate(deps, config,
|
|
1863
|
+
completeMemberGate(deps, config, batchDispatch, batchId, batch, memberIssue, now, result);
|
|
1856
1864
|
return { outcome: 'skipped', capability: capabilityId, detail: excerpt };
|
|
1857
1865
|
}
|
|
1858
1866
|
recordMemberGate(deps, batchId, memberIssue, { id: capabilityId, ...recheck }, now);
|
|
@@ -1900,7 +1908,7 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
|
|
|
1900
1908
|
if (recheck.outcome === 'task-failed') {
|
|
1901
1909
|
const reason = `incremental-gate-failed:${capabilityId}`;
|
|
1902
1910
|
writeGateLog(deps, batchId, capabilityId, memberIssue, recheck.outputTail);
|
|
1903
|
-
evictMemberAndContinue(deps, config,
|
|
1911
|
+
evictMemberAndContinue(deps, config, batchDispatch, batchId, batch, memberIssue, {
|
|
1904
1912
|
reason,
|
|
1905
1913
|
detail: withExcerpt(`sched resume --batch: cap run ${capabilityId} reported task-failed on recheck`, excerpt),
|
|
1906
1914
|
}, now, result);
|
|
@@ -1910,7 +1918,7 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
|
|
|
1910
1918
|
issue: memberIssue,
|
|
1911
1919
|
detail: `sched resume --batch: cap run ${capabilityId} reported ok on recheck`,
|
|
1912
1920
|
});
|
|
1913
|
-
completeMemberGate(deps, config,
|
|
1921
|
+
completeMemberGate(deps, config, batchDispatch, batchId, batch, memberIssue, now, result);
|
|
1914
1922
|
return { outcome: 'completed', capability: capabilityId };
|
|
1915
1923
|
}
|
|
1916
1924
|
/**
|
|
@@ -2791,6 +2799,81 @@ function teardownBatch(deps, batchId) {
|
|
|
2791
2799
|
function runBatchTick(deps, config, dispatch) {
|
|
2792
2800
|
const result = emptyResult();
|
|
2793
2801
|
const now = deps.now();
|
|
2802
|
+
// #707: each batch dispatches through ITS OWN recorded profile, not the
|
|
2803
|
+
// engine's default. Successful resolution is cached per tick (per
|
|
2804
|
+
// runBatchTick call); missing profiles are handled per batch. A profile that
|
|
2805
|
+
// no longer resolves (config edited
|
|
2806
|
+
// mid-run) never silently falls back — the silent fallback is precisely
|
|
2807
|
+
// the #680 incident shape (a run that looked like one agent family,
|
|
2808
|
+
// executed as another). The reaction depends on how far the batch got:
|
|
2809
|
+
// - pre-merge (`dissolving` is a legal edge): DISSOLVE with reason
|
|
2810
|
+
// `dispatch-profile-missing:<name>` — loud, deterministic, one-shot, and
|
|
2811
|
+
// the members requeue as full-cycle units on the config default, which is
|
|
2812
|
+
// a coherent recovery rather than a mislabelled continuation.
|
|
2813
|
+
// - post-merge (PR parked/merged — the product already shipped): dissolving
|
|
2814
|
+
// would discard a landed PR's bookkeeping, so the remaining dispatch (the
|
|
2815
|
+
// report agent) runs on the default and journals
|
|
2816
|
+
// `dispatch-profile-missing` once while it holds the decision open — a
|
|
2817
|
+
// bounded phase (deployed → reported), with the journal acting as the
|
|
2818
|
+
// durable deduplication record across ticks and process restarts.
|
|
2819
|
+
const profileCache = new Map();
|
|
2820
|
+
const missingProfileBatches = new Set();
|
|
2821
|
+
const missingProfileNotices = new Set(deps.journal
|
|
2822
|
+
.read()
|
|
2823
|
+
.filter((event) => event.event === 'dispatch-profile-missing' && event.unit !== undefined)
|
|
2824
|
+
.map((event) => event.unit));
|
|
2825
|
+
const dispatchFor = (batch) => {
|
|
2826
|
+
if (batch.dispatch_profile === null)
|
|
2827
|
+
return dispatch;
|
|
2828
|
+
const cached = profileCache.get(batch.dispatch_profile);
|
|
2829
|
+
if (cached !== undefined)
|
|
2830
|
+
return cached;
|
|
2831
|
+
if (missingProfileBatches.has(batch.id))
|
|
2832
|
+
return dispatch;
|
|
2833
|
+
try {
|
|
2834
|
+
const resolved = (0, dispatch_1.resolveProfiledDispatch)(config, batch.dispatch_profile);
|
|
2835
|
+
profileCache.set(batch.dispatch_profile, resolved);
|
|
2836
|
+
return resolved;
|
|
2837
|
+
}
|
|
2838
|
+
catch (err) {
|
|
2839
|
+
if (!(err instanceof dispatch_1.DispatchProfileError))
|
|
2840
|
+
throw err;
|
|
2841
|
+
missingProfileBatches.add(batch.id);
|
|
2842
|
+
const current = deps.store.load();
|
|
2843
|
+
const fresh = (0, state_1.findBatch)(current, batch.id);
|
|
2844
|
+
const dissolvable = fresh !== undefined && (0, state_1.allowedBatchTransitions)(fresh.status).includes('dissolving');
|
|
2845
|
+
if (dissolvable) {
|
|
2846
|
+
const liveSlot = slotFor(current, batch.id);
|
|
2847
|
+
if (liveSlot !== undefined &&
|
|
2848
|
+
liveSlot.pid !== null &&
|
|
2849
|
+
deps.spawnDeps.isAlive(liveSlot.pid, liveSlot.pid_start ?? undefined)) {
|
|
2850
|
+
deps.spawnDeps.kill(liveSlot.pid, liveSlot.pid_start ?? undefined);
|
|
2851
|
+
}
|
|
2852
|
+
const outcome = (0, recovery_1.dissolveBatch)(current, batch.id, { strategy: 'full', reason: `dispatch-profile-missing:${batch.dispatch_profile}` }, recoveryDeps(deps, config, fresh, now));
|
|
2853
|
+
deps.store.withLock((s) => ({
|
|
2854
|
+
state: applyBatchAndIssues(s, outcome.state, batch.id, outcome.requeued),
|
|
2855
|
+
result: undefined,
|
|
2856
|
+
}));
|
|
2857
|
+
teardownBatch(deps, batch.id);
|
|
2858
|
+
result.failed.push(unit(batch.id));
|
|
2859
|
+
// Resolution failed: clean up any batch/member worktrees, then skip
|
|
2860
|
+
// this batch for the rest of the pass. No further arm can claim it.
|
|
2861
|
+
// Do not cache a missing profile: every batch must receive its own
|
|
2862
|
+
// dissolve/journal reaction rather than inheriting a cached default.
|
|
2863
|
+
return null;
|
|
2864
|
+
}
|
|
2865
|
+
else {
|
|
2866
|
+
const batchUnit = unit(batch.id);
|
|
2867
|
+
if (!missingProfileNotices.has(batchUnit)) {
|
|
2868
|
+
journalEvent(deps, 'dispatch-profile-missing', batchUnit, {
|
|
2869
|
+
detail: `profile '${batch.dispatch_profile}' is no longer configured; the post-merge tail runs on the default dispatch`,
|
|
2870
|
+
});
|
|
2871
|
+
missingProfileNotices.add(batchUnit);
|
|
2872
|
+
}
|
|
2873
|
+
return dispatch;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
};
|
|
2794
2877
|
// #565: when more ready batches exist than free capacity, claim them in
|
|
2795
2878
|
// the same priority order `runnableUnits` would (desc priority → asc
|
|
2796
2879
|
// readiness age → anchor) — this loop never goes through
|
|
@@ -2822,7 +2905,12 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2822
2905
|
const state = deps.store.load();
|
|
2823
2906
|
const batch = (0, state_1.findBatch)(state, batchId);
|
|
2824
2907
|
if (batch && batch.status === 'ready' && slotFor(state, batchId) === undefined) {
|
|
2825
|
-
|
|
2908
|
+
// #707: batch-setup dispatches through the batch's own profile too — it
|
|
2909
|
+
// is the batch's first agent, not an engine-internal step.
|
|
2910
|
+
const batchDispatch = dispatchFor(batch);
|
|
2911
|
+
if (batchDispatch !== null) {
|
|
2912
|
+
claimAndSetup(deps, config, batchDispatch, batchId, now, result);
|
|
2913
|
+
}
|
|
2826
2914
|
}
|
|
2827
2915
|
}
|
|
2828
2916
|
for (const batch of deps.store.load().batches) {
|
|
@@ -2837,20 +2925,29 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2837
2925
|
// `abandonBatch` that stops one from being created: it also REPAIRS state
|
|
2838
2926
|
// files that already carry a leaked slot, which is the only way an
|
|
2839
2927
|
// operator gets those three hours of held capacity back.
|
|
2840
|
-
if (
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
slot
|
|
2847
|
-
|
|
2848
|
-
|
|
2928
|
+
if (types_1.TERMINAL_BATCH_STATUSES.has(batch.status)) {
|
|
2929
|
+
if (slot) {
|
|
2930
|
+
deps.store.withLock((s) => ({
|
|
2931
|
+
state: (0, state_1.releaseBatchSlot)(s, batch.id, now),
|
|
2932
|
+
result: undefined,
|
|
2933
|
+
}));
|
|
2934
|
+
journalEvent(deps, 'slot-released', unit(batch.id), {
|
|
2935
|
+
slot: slot.id,
|
|
2936
|
+
detail: `terminal batch (${batch.status}) held slot ${slot.id}`,
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2849
2939
|
continue;
|
|
2850
2940
|
}
|
|
2941
|
+
// #707: resolve this batch's profile once per pass — or dissolve/fall
|
|
2942
|
+
// back loudly when it no longer resolves (see `dispatchFor` above).
|
|
2943
|
+
// Terminal batches are skipped before this call so a removed profile does
|
|
2944
|
+
// not generate a missing-profile event on every later tick.
|
|
2945
|
+
const batchDispatch = dispatchFor(batch);
|
|
2946
|
+
if (batchDispatch === null)
|
|
2947
|
+
continue;
|
|
2851
2948
|
if (slot && (slot.status === 'running' || slot.status === 'assigned')) {
|
|
2852
2949
|
if (batch.status === 'executing') {
|
|
2853
|
-
reconcileMemberSlot(deps, config,
|
|
2950
|
+
reconcileMemberSlot(deps, config, batchDispatch, batch.id, slot, now, result);
|
|
2854
2951
|
}
|
|
2855
2952
|
else if (batch.status === 'fixing') {
|
|
2856
2953
|
reconcileFixSlot(deps, config, batch.id, slot, now, result);
|
|
@@ -2867,7 +2964,7 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2867
2964
|
continue; // live but neither running/assigned (e.g. mid-verify) — next tick
|
|
2868
2965
|
if (batch.status === 'validating') {
|
|
2869
2966
|
// A local suite run, not a provider dispatch — unaffected by `paused`.
|
|
2870
|
-
runValidate(deps, config,
|
|
2967
|
+
runValidate(deps, config, batchDispatch, batch.id, now, result);
|
|
2871
2968
|
continue;
|
|
2872
2969
|
}
|
|
2873
2970
|
// #629: every branch below spawns a provider agent — hold the wedge
|
|
@@ -2876,7 +2973,7 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2876
2973
|
if (paused)
|
|
2877
2974
|
continue;
|
|
2878
2975
|
if (batch.status === 'deployed') {
|
|
2879
|
-
spawnReportAgent(deps, config,
|
|
2976
|
+
spawnReportAgent(deps, config, batchDispatch, batch.id, now, result);
|
|
2880
2977
|
}
|
|
2881
2978
|
else if (batch.status === 'executing') {
|
|
2882
2979
|
// A prior spawn threw, or `claimAndSpawn` found zero free capacity —
|
|
@@ -2887,11 +2984,11 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2887
2984
|
// pre-checks BEFORE any pool/git work, so a full scheduler (or stale
|
|
2888
2985
|
// member context) exits here without exec'ing, and `claimAndSpawn`
|
|
2889
2986
|
// remains the authoritative capacity gate.
|
|
2890
|
-
spawnMemberContinuation(deps, config,
|
|
2987
|
+
spawnMemberContinuation(deps, config, batchDispatch, batch.id, now, result);
|
|
2891
2988
|
}
|
|
2892
2989
|
else if (batch.status === 'reviewing' || batch.status === 'shipping') {
|
|
2893
2990
|
// Same wedge, for a dead-or-never-claimed tail agent.
|
|
2894
|
-
spawnTailAgent(deps, config,
|
|
2991
|
+
spawnTailAgent(deps, config, batchDispatch, batch.id, now, result);
|
|
2895
2992
|
}
|
|
2896
2993
|
else if (batch.status === 'fixing') {
|
|
2897
2994
|
// `beginFixAttempt` already recorded this member's ONE attempt as
|