@ai-dossier/sched 0.39.0 → 0.41.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 +11 -1
- package/dist/batch-dispatch.d.ts +2 -0
- package/dist/batch-dispatch.d.ts.map +1 -1
- package/dist/batch-dispatch.js +106 -72
- package/dist/batch-dispatch.js.map +1 -1
- package/dist/engine.d.ts +2 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +69 -5
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +4 -1
- package/dist/recovery.js.map +1 -1
- package/dist/scheduler.d.ts +19 -0
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +62 -0
- package/dist/scheduler.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +29 -16
- package/dist/state.js.map +1 -1
- package/dist/status.d.ts +3 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +7 -0
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +12 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +14 -3
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,8 +29,10 @@ ai-dossier sched enqueue --from-manifest batch-prep.json # batc
|
|
|
29
29
|
ai-dossier sched start # the dispatch engine: spawn, verify, escalate, watch parked PRs (Ctrl-C stops it)
|
|
30
30
|
ai-dossier sched start --once # a single reconcile+refill tick (cron-style)
|
|
31
31
|
ai-dossier sched status # queue (+pr/cleanup), parked PRs, slots, batches, blocked/failed
|
|
32
|
-
ai-dossier sched pause #
|
|
32
|
+
ai-dossier sched pause # prevent every new agent process; live units keep running
|
|
33
33
|
ai-dossier sched resume
|
|
34
|
+
ai-dossier sched stop --issue 42 # terminate one full-cycle agent and record it stopped (no recovery)
|
|
35
|
+
ai-dossier sched stop --batch b1 # terminate the batch process and stop unfinished members
|
|
34
36
|
ai-dossier sched abandon --issue 42 --reason "operator abort"
|
|
35
37
|
ai-dossier sched abandon --batch b1 # dissolve; members requeue as full-cycle
|
|
36
38
|
ai-dossier sched stats --issues 4..9 # per-issue tokens/cost from ~/.dossier/runs.jsonl (#524)
|
|
@@ -47,6 +49,14 @@ every other subcommand and reads that project's `~/.dossier/sched/<project>/runs
|
|
|
47
49
|
directory directly, reconstructing costs from the raw dispatch logs rather than
|
|
48
50
|
`runs.jsonl` (#564) — see "Batch members (#564)" below.
|
|
49
51
|
|
|
52
|
+
`pause` prevents every new agent process, including escalation and same-tier recovery
|
|
53
|
+
takeovers; it does not terminate agents that are already running. `stop --issue <n>` is the
|
|
54
|
+
single-command stop path: it PID-start-safely terminates that issue's live agent, releases its
|
|
55
|
+
slot, and records a terminal `stopped` outcome that will not recover or escalate. An active
|
|
56
|
+
slot-mode member must instead be stopped through `stop --batch <id>`, which terminates the batch
|
|
57
|
+
process and atomically stops the batch and its unfinished members. `abandon` only records failure
|
|
58
|
+
and releases the slot; it intentionally does not terminate its process.
|
|
59
|
+
|
|
50
60
|
Since #507, `enqueue` additionally reads each candidate issue's live GitHub labels (one
|
|
51
61
|
`gh issue view --json labels` call per issue, resolved against the current directory's repo
|
|
52
62
|
unless `--repo <owner/name>` is passed) and lands an issue carrying `decision-pending` /
|
package/dist/batch-dispatch.d.ts
CHANGED
|
@@ -125,6 +125,8 @@ export interface BatchTickResult {
|
|
|
125
125
|
parked: string[];
|
|
126
126
|
mergeAccepted: string[];
|
|
127
127
|
failed: string[];
|
|
128
|
+
/** Per-batch exceptions contained so unrelated units can continue this tick (#635). */
|
|
129
|
+
reconciliation_errors: string[];
|
|
128
130
|
/** Issue numbers requeued full-cycle by a dissolve — matches `TickResult.blocked`'s shape. */
|
|
129
131
|
blocked: number[];
|
|
130
132
|
}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"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"}
|
package/dist/batch-dispatch.js
CHANGED
|
@@ -116,7 +116,15 @@ const state_1 = require("./state");
|
|
|
116
116
|
const teardown_1 = require("./teardown");
|
|
117
117
|
const types_1 = require("./types");
|
|
118
118
|
function emptyResult() {
|
|
119
|
-
return {
|
|
119
|
+
return {
|
|
120
|
+
spawned: [],
|
|
121
|
+
completed: [],
|
|
122
|
+
parked: [],
|
|
123
|
+
mergeAccepted: [],
|
|
124
|
+
failed: [],
|
|
125
|
+
reconciliation_errors: [],
|
|
126
|
+
blocked: [],
|
|
127
|
+
};
|
|
120
128
|
}
|
|
121
129
|
function unit(batchId) {
|
|
122
130
|
return `batch:${batchId}`;
|
|
@@ -2902,6 +2910,22 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2902
2910
|
}
|
|
2903
2911
|
}
|
|
2904
2912
|
};
|
|
2913
|
+
const containFailure = (batchId, err) => {
|
|
2914
|
+
const detail = `${err.name}: ${err.message}`;
|
|
2915
|
+
// A corrupt unit must not turn into a project-wide scheduler outage.
|
|
2916
|
+
// Keep the failed batch inspectable, release its slot, and continue.
|
|
2917
|
+
deps.store.withLock((state) => {
|
|
2918
|
+
const fresh = (0, state_1.findBatch)(state, batchId);
|
|
2919
|
+
if (!fresh || types_1.TERMINAL_BATCH_STATUSES.has(fresh.status) || fresh.status === 'blocked') {
|
|
2920
|
+
return { state, result: undefined };
|
|
2921
|
+
}
|
|
2922
|
+
const blocked = (0, state_1.transitionBatch)(state, batchId, 'blocked', { blocked_reason: 'reconcile-error' }, now);
|
|
2923
|
+
return { state: (0, state_1.releaseBatchSlot)(blocked, batchId, now), result: undefined };
|
|
2924
|
+
});
|
|
2925
|
+
journalEvent(deps, 'unit-failed', unit(batchId), { reason: 'reconcile-error', detail });
|
|
2926
|
+
result.failed.push(unit(batchId));
|
|
2927
|
+
result.reconciliation_errors.push(`${unit(batchId)}: ${detail}`);
|
|
2928
|
+
};
|
|
2905
2929
|
// #565: when more ready batches exist than free capacity, claim them in
|
|
2906
2930
|
// the same priority order `runnableUnits` would (desc priority → asc
|
|
2907
2931
|
// readiness age → anchor) — this loop never goes through
|
|
@@ -2933,11 +2957,16 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2933
2957
|
const state = deps.store.load();
|
|
2934
2958
|
const batch = (0, state_1.findBatch)(state, batchId);
|
|
2935
2959
|
if (batch && batch.status === 'ready' && slotFor(state, batchId) === undefined) {
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2960
|
+
try {
|
|
2961
|
+
// #707: batch-setup dispatches through the batch's own profile too — it
|
|
2962
|
+
// is the batch's first agent, not an engine-internal step.
|
|
2963
|
+
const batchDispatch = dispatchFor(batch);
|
|
2964
|
+
if (batchDispatch !== null) {
|
|
2965
|
+
claimAndSetup(deps, config, batchDispatch, batchId, now, result);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
catch (err) {
|
|
2969
|
+
containFailure(batchId, err);
|
|
2941
2970
|
}
|
|
2942
2971
|
}
|
|
2943
2972
|
}
|
|
@@ -2966,78 +2995,83 @@ function runBatchTick(deps, config, dispatch) {
|
|
|
2966
2995
|
}
|
|
2967
2996
|
continue;
|
|
2968
2997
|
}
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
if (
|
|
2978
|
-
|
|
2998
|
+
try {
|
|
2999
|
+
// #707: resolve this batch's profile once per pass — or dissolve/fall
|
|
3000
|
+
// back loudly when it no longer resolves (see `dispatchFor` above).
|
|
3001
|
+
// Terminal batches are skipped before this call so a removed profile does
|
|
3002
|
+
// not generate a missing-profile event on every later tick.
|
|
3003
|
+
const batchDispatch = dispatchFor(batch);
|
|
3004
|
+
if (batchDispatch === null)
|
|
3005
|
+
continue;
|
|
3006
|
+
if (slot && (slot.status === 'running' || slot.status === 'assigned')) {
|
|
3007
|
+
if (batch.status === 'executing') {
|
|
3008
|
+
reconcileMemberSlot(deps, config, batchDispatch, batch.id, slot, now, result);
|
|
3009
|
+
}
|
|
3010
|
+
else if (batch.status === 'fixing') {
|
|
3011
|
+
reconcileFixSlot(deps, config, batch.id, slot, now, result);
|
|
3012
|
+
}
|
|
3013
|
+
else if (batch.status === 'reviewing' || batch.status === 'shipping') {
|
|
3014
|
+
reconcileTailSlot(deps, batch.id, slot, now, result);
|
|
3015
|
+
}
|
|
3016
|
+
else if (batch.status === 'deployed') {
|
|
3017
|
+
reconcileReportSlot(deps, batch.id, slot, now, result);
|
|
3018
|
+
}
|
|
3019
|
+
continue;
|
|
2979
3020
|
}
|
|
2980
|
-
|
|
2981
|
-
|
|
3021
|
+
if (slot)
|
|
3022
|
+
continue; // live but neither running/assigned (e.g. mid-verify) — next tick
|
|
3023
|
+
if (batch.status === 'validating') {
|
|
3024
|
+
// A local suite run, not a provider dispatch — unaffected by `paused`.
|
|
3025
|
+
runValidate(deps, config, batchDispatch, batch.id, now, result);
|
|
3026
|
+
continue;
|
|
3027
|
+
}
|
|
3028
|
+
// #629: every branch below spawns a provider agent — hold the wedge
|
|
3029
|
+
// until `sched resume` (see the top of this function for why `paused`
|
|
3030
|
+
// is read once, up front, rather than per iteration).
|
|
3031
|
+
if (paused)
|
|
3032
|
+
continue;
|
|
3033
|
+
if (batch.status === 'deployed') {
|
|
3034
|
+
spawnReportAgent(deps, config, batchDispatch, batch.id, now, result);
|
|
3035
|
+
}
|
|
3036
|
+
else if (batch.status === 'executing') {
|
|
3037
|
+
// A prior spawn threw, or `claimAndSpawn` found zero free capacity —
|
|
3038
|
+
// either way the batch is stuck mid-member with no slot and nothing
|
|
3039
|
+
// else will ever retry it (Conformance review AC5 caveat; Supportability
|
|
3040
|
+
// review #12). Retrying every tick is cheap-by-contract (#677 review):
|
|
3041
|
+
// `spawnMemberContinuation` runs the free-capacity and member-identity
|
|
3042
|
+
// pre-checks BEFORE any pool/git work, so a full scheduler (or stale
|
|
3043
|
+
// member context) exits here without exec'ing, and `claimAndSpawn`
|
|
3044
|
+
// remains the authoritative capacity gate.
|
|
3045
|
+
spawnMemberContinuation(deps, config, batchDispatch, batch.id, now, result);
|
|
2982
3046
|
}
|
|
2983
3047
|
else if (batch.status === 'reviewing' || batch.status === 'shipping') {
|
|
2984
|
-
|
|
3048
|
+
// Same wedge, for a dead-or-never-claimed tail agent.
|
|
3049
|
+
spawnTailAgent(deps, config, batchDispatch, batch.id, now, result);
|
|
2985
3050
|
}
|
|
2986
|
-
else if (batch.status === '
|
|
2987
|
-
|
|
3051
|
+
else if (batch.status === 'fixing') {
|
|
3052
|
+
// `beginFixAttempt` already recorded this member's ONE attempt as
|
|
3053
|
+
// `dispatched` before `claimAndSpawn` could find capacity — retrying the
|
|
3054
|
+
// exact same dispatch isn't reconstructible from persisted state (only
|
|
3055
|
+
// the outcome is persisted, not the command/prompt), so resolve it
|
|
3056
|
+
// `red` (conservatively: the member loses its one attempt and evicts on
|
|
3057
|
+
// the next validate pass, which is safe — never a permanent wedge).
|
|
3058
|
+
const state = deps.store.load();
|
|
3059
|
+
const b = (0, state_1.findBatch)(state, batch.id);
|
|
3060
|
+
const offenderRecord = b
|
|
3061
|
+
? [...b.fix_attempts].reverse().find((a) => a.outcome === 'dispatched')
|
|
3062
|
+
: undefined;
|
|
3063
|
+
if (b && offenderRecord) {
|
|
3064
|
+
const rDeps = recoveryDeps(deps, config, b, now);
|
|
3065
|
+
const { state: resolved } = (0, recovery_1.resolveFixAttempt)(state, batch.id, offenderRecord.issue, 'red', rDeps);
|
|
3066
|
+
deps.store.withLock((s) => ({
|
|
3067
|
+
state: applyBatchAndIssues(s, resolved, batch.id, []),
|
|
3068
|
+
result: undefined,
|
|
3069
|
+
}));
|
|
3070
|
+
}
|
|
2988
3071
|
}
|
|
2989
|
-
continue;
|
|
2990
3072
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
if (batch.status === 'validating') {
|
|
2994
|
-
// A local suite run, not a provider dispatch — unaffected by `paused`.
|
|
2995
|
-
runValidate(deps, config, batchDispatch, batch.id, now, result);
|
|
2996
|
-
continue;
|
|
2997
|
-
}
|
|
2998
|
-
// #629: every branch below spawns a provider agent — hold the wedge
|
|
2999
|
-
// until `sched resume` (see the top of this function for why `paused`
|
|
3000
|
-
// is read once, up front, rather than per iteration).
|
|
3001
|
-
if (paused)
|
|
3002
|
-
continue;
|
|
3003
|
-
if (batch.status === 'deployed') {
|
|
3004
|
-
spawnReportAgent(deps, config, batchDispatch, batch.id, now, result);
|
|
3005
|
-
}
|
|
3006
|
-
else if (batch.status === 'executing') {
|
|
3007
|
-
// A prior spawn threw, or `claimAndSpawn` found zero free capacity —
|
|
3008
|
-
// either way the batch is stuck mid-member with no slot and nothing
|
|
3009
|
-
// else will ever retry it (Conformance review AC5 caveat; Supportability
|
|
3010
|
-
// review #12). Retrying every tick is cheap-by-contract (#677 review):
|
|
3011
|
-
// `spawnMemberContinuation` runs the free-capacity and member-identity
|
|
3012
|
-
// pre-checks BEFORE any pool/git work, so a full scheduler (or stale
|
|
3013
|
-
// member context) exits here without exec'ing, and `claimAndSpawn`
|
|
3014
|
-
// remains the authoritative capacity gate.
|
|
3015
|
-
spawnMemberContinuation(deps, config, batchDispatch, batch.id, now, result);
|
|
3016
|
-
}
|
|
3017
|
-
else if (batch.status === 'reviewing' || batch.status === 'shipping') {
|
|
3018
|
-
// Same wedge, for a dead-or-never-claimed tail agent.
|
|
3019
|
-
spawnTailAgent(deps, config, batchDispatch, batch.id, now, result);
|
|
3020
|
-
}
|
|
3021
|
-
else if (batch.status === 'fixing') {
|
|
3022
|
-
// `beginFixAttempt` already recorded this member's ONE attempt as
|
|
3023
|
-
// `dispatched` before `claimAndSpawn` could find capacity — retrying the
|
|
3024
|
-
// exact same dispatch isn't reconstructible from persisted state (only
|
|
3025
|
-
// the outcome is persisted, not the command/prompt), so resolve it
|
|
3026
|
-
// `red` (conservatively: the member loses its one attempt and evicts on
|
|
3027
|
-
// the next validate pass, which is safe — never a permanent wedge).
|
|
3028
|
-
const state = deps.store.load();
|
|
3029
|
-
const b = (0, state_1.findBatch)(state, batch.id);
|
|
3030
|
-
const offenderRecord = b
|
|
3031
|
-
? [...b.fix_attempts].reverse().find((a) => a.outcome === 'dispatched')
|
|
3032
|
-
: undefined;
|
|
3033
|
-
if (b && offenderRecord) {
|
|
3034
|
-
const rDeps = recoveryDeps(deps, config, b, now);
|
|
3035
|
-
const { state: resolved } = (0, recovery_1.resolveFixAttempt)(state, batch.id, offenderRecord.issue, 'red', rDeps);
|
|
3036
|
-
deps.store.withLock((s) => ({
|
|
3037
|
-
state: applyBatchAndIssues(s, resolved, batch.id, []),
|
|
3038
|
-
result: undefined,
|
|
3039
|
-
}));
|
|
3040
|
-
}
|
|
3073
|
+
catch (err) {
|
|
3074
|
+
containFailure(batch.id, err);
|
|
3041
3075
|
}
|
|
3042
3076
|
}
|
|
3043
3077
|
reconcilePrWatch(deps, now, result);
|