@ai-dossier/sched 0.2.0 → 0.3.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.
Files changed (58) hide show
  1. package/README.md +85 -17
  2. package/dist/dispatch.d.ts +128 -0
  3. package/dist/dispatch.d.ts.map +1 -0
  4. package/dist/dispatch.js +300 -0
  5. package/dist/dispatch.js.map +1 -0
  6. package/dist/engine.d.ts +111 -0
  7. package/dist/engine.d.ts.map +1 -0
  8. package/dist/engine.js +936 -0
  9. package/dist/engine.js.map +1 -0
  10. package/dist/enqueue.d.ts +38 -0
  11. package/dist/enqueue.d.ts.map +1 -0
  12. package/dist/enqueue.js +222 -0
  13. package/dist/enqueue.js.map +1 -0
  14. package/dist/groundtruth.d.ts +135 -0
  15. package/dist/groundtruth.d.ts.map +1 -0
  16. package/dist/groundtruth.js +259 -0
  17. package/dist/groundtruth.js.map +1 -0
  18. package/dist/index.d.ts +14 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +101 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/journal.d.ts +32 -0
  23. package/dist/journal.d.ts.map +1 -0
  24. package/dist/journal.js +113 -0
  25. package/dist/journal.js.map +1 -0
  26. package/dist/persist.d.ts +54 -0
  27. package/dist/persist.d.ts.map +1 -0
  28. package/dist/persist.js +348 -0
  29. package/dist/persist.js.map +1 -0
  30. package/dist/project.d.ts +41 -0
  31. package/dist/project.d.ts.map +1 -0
  32. package/dist/project.js +124 -0
  33. package/dist/project.js.map +1 -0
  34. package/dist/readiness.d.ts +43 -0
  35. package/dist/readiness.d.ts.map +1 -0
  36. package/dist/readiness.js +102 -0
  37. package/dist/readiness.js.map +1 -0
  38. package/dist/scheduler.d.ts +78 -0
  39. package/dist/scheduler.d.ts.map +1 -0
  40. package/dist/scheduler.js +191 -0
  41. package/dist/scheduler.js.map +1 -0
  42. package/dist/state.d.ts +40 -0
  43. package/dist/state.d.ts.map +1 -0
  44. package/dist/state.js +391 -0
  45. package/dist/state.js.map +1 -0
  46. package/dist/status.d.ts +44 -0
  47. package/dist/status.d.ts.map +1 -0
  48. package/dist/status.js +82 -0
  49. package/dist/status.js.map +1 -0
  50. package/dist/teardown.d.ts +57 -0
  51. package/dist/teardown.d.ts.map +1 -0
  52. package/dist/teardown.js +242 -0
  53. package/dist/teardown.js.map +1 -0
  54. package/dist/types.d.ts +253 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +91 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +1 -1
package/README.md CHANGED
@@ -3,11 +3,14 @@
3
3
  [![npm](https://img.shields.io/npm/v/@ai-dossier/sched.svg)](https://www.npmjs.com/package/@ai-dossier/sched)
4
4
 
5
5
  Deterministic scheduler core for dossier batch cycles — queue, worker slots, typed state
6
- machines, crash-safe persistence, and (since #464) the **dispatch engine**: spawning agent
7
- processes, verifying their completion against ground truth, and mechanizing the
8
- stall/escalation ladder. The scheduler itself **never invokes an LLM** — it spawns the
9
- agent process the operator configured and reconciles the durable record
10
- (`ai-dossier runstate` / `gh` / `git`) that the spawned run leaves behind.
6
+ machines, crash-safe persistence, the **dispatch engine** (#464: spawning agent
7
+ processes, verifying their completion against ground truth, mechanizing the
8
+ stall/escalation ladder), and since #468 the **PR watcher + tail work** (parked-PR
9
+ watching, script-based teardown, cheap-tier report dispatch retiring the fleet
10
+ pattern of re-dispatching a full-cycle run for the tail). The scheduler itself **never
11
+ invokes an LLM** — it spawns the agent process the operator configured and reconciles
12
+ the durable record (`ai-dossier runstate` / `gh` / `git`) that the spawned run leaves
13
+ behind.
11
14
 
12
15
  Design: RFC-0001 *Batch Cycles* §B/C.1/D (the RFC lives on branch `docs/batch-cycles-rfc`,
13
16
  not yet merged to `main`; the §D state machines are frozen verbatim into the types below).
@@ -17,14 +20,14 @@ this state machine makes impossible to forget.
17
20
 
18
21
  ## CLI surface
19
22
 
20
- Consumed through the monorepo CLI (`@ai-dossier/cli` ≥ 0.18.0):
23
+ Consumed through the monorepo CLI (`@ai-dossier/cli` ≥ 0.19.0):
21
24
 
22
25
  ```bash
23
26
  ai-dossier sched enqueue --issues 101,105..109 --deps 100 --tier strong # flags
24
27
  ai-dossier sched enqueue --from-manifest batch-prep.json # batch-prep output
25
- ai-dossier sched start # the dispatch engine: spawn, verify, escalate (Ctrl-C stops it)
28
+ ai-dossier sched start # the dispatch engine: spawn, verify, escalate, watch parked PRs (Ctrl-C stops it)
26
29
  ai-dossier sched start --once # a single reconcile+refill tick (cron-style)
27
- ai-dossier sched status # queue, slots (pid/phase/last-progress), batches, blocked/failed
30
+ ai-dossier sched status # queue (+pr/cleanup), parked PRs, slots, batches, blocked/failed
28
31
  ai-dossier sched pause # stop NEW assignments; live units keep running
29
32
  ai-dossier sched resume
30
33
  ai-dossier sched abandon --issue 42 --reason "operator abort"
@@ -91,13 +94,26 @@ import {
91
94
  parseManifest, // batch-prep JSON → EnqueueInput[]
92
95
  computeAssignments, // pure: fill idle slots with runnable units, bounded by max_slots
93
96
  runnableUnits, // pure: which units may run right now (dep-gated)
94
- tick, // one engine cycle: reconcile + verify + refill + spawn
97
+ tick, // one engine cycle: reconcile + verify + refill + spawn,
98
+ // and since #468: park-watch, teardown, report dispatch
95
99
  runLoop, // the sched start loop (tick, sleep, repeat)
96
- type TickResult, // what one tick did (spawned/completed/redispatched/failed/blocked)
97
- type EngineDeps, // inject everything the engine touches (store/journal/spawn/ground truth/clock)
100
+ type TickResult, // what one tick did (spawned/parked/merge-accepted/report-dispatched/
101
+ // teardown/completed/redispatched/failed/blocked)
102
+ type EngineDeps, // inject everything the engine touches (store/journal/spawn/ground
103
+ // truth/clock/repoDir/teardownExec)
98
104
  createSpawnDeps, // real detached-spawn process I/O
99
- createExecGroundTruth, // runstate/gh/git ground truth via subprocesses (injectable exec)
100
- resolveDispatch, // config resolved command/prompt/tier-models/timers
105
+ createExecGroundTruth, // runstate/gh/git ground truth via subprocesses (injectable exec);
106
+ // since #468 also gh pr view PR state + setup info from comments
107
+ resolveDispatch, // config → resolved command/prompt/report-prompt/tier-models/timers
108
+ buildReportPrompt, // report-agent prompt ({issue}/{pr}/{cleanup} substituted)
109
+ reportTierFor, // report (re)dispatch tier after N escalations
110
+ isParkedMilestone, // ship-phase awaiting-merge + pr= → the park signal
111
+ prOfMilestone, // a milestone's pr= key as a positive integer
112
+ parsePrViewJson, // gh pr view --json → PR truth (mergedAt/mergeable/blocked label)
113
+ parseSetupInfo, // gh issue view --json comments → teardown inputs
114
+ runTeardown, // #468 script teardown for a merged unit (pool return / worktree remove)
115
+ isSafeWorktree, // worktree-path containment check (CWE-22)
116
+ TEARDOWN_TIMEOUT_MS, // teardown subprocess timeout (120 s)
101
117
  Journal, // append-only events.jsonl
102
118
  transitionIssue, transitionBatch, transitionSlot, // typed §D transitions
103
119
  TRANSITIONS, // the transition tables themselves (for previews)
@@ -119,7 +135,8 @@ fake agents and stub ground truth; no LLM calls anywhere.
119
135
  ```
120
136
  ~/.dossier/sched/<project>/
121
137
  ├── state.json # hot operational truth — atomic tmp+fsync+rename writes;
122
- ├── config.json # durable intent: max_slots, stall_timeout_ms, reconcile_interval_ms, dispatch
138
+ ├── config.json # durable intent: max_slots, stall_timeout_ms, reconcile_interval_ms,
139
+ │ # pr_poll_interval_ms, dispatch (incl. report_prompt)
123
140
  ├── events.jsonl # append-only event journal (the operator's flight recorder)
124
141
  ├── runs/ # per-unit agent output logs (issue-<n>.log)
125
142
  └── .sched-lock/ # cross-process directory mutex (pid; stolen from dead holders)
@@ -133,14 +150,63 @@ fake agents and stub ground truth; no LLM calls anywhere.
133
150
  - **Corrupt state is loud**: `load()` throws `CorruptStateError` naming the file —
134
151
  never a silent queue reset. `state.json` is deletable and rebuildable from GitHub,
135
152
  which remains the system of record.
136
- - **Schema**: state/config files from #460 (schema 1.0.0) load and migrate to 1.1.0
137
- automatically (slot `branch`/`last_head` backfill to null).
153
+ - **Schema**: state/config files from #460 (schema 1.0.0) and #464 (1.1.0) load and
154
+ migrate to 1.2.0 automatically (slot `branch`/`last_head`, entry `pr`/`cleanup`,
155
+ and state-level `last_pr_poll_at` backfill to null).
138
156
  - **`max_slots`** bounds live units (`assigned | running | recovering`); dependency
139
157
  edges gate readiness — an issue with an unmerged dependency, and a batch behind an
140
158
  unmerged batch, are never runnable.
141
159
  - **Pause** stops new assignments only; abandon routes through the typed failure rails
142
160
  (`evicted → requeued{full}` for batch members — nothing green is discarded).
143
161
 
162
+ ## The PR watcher + tail work (#468)
163
+
164
+ Dispatched runs park their PR on `auto-merge` (detached ship mode — the default
165
+ prompt instructs it) and exit. The engine owns everything after the park:
166
+
167
+ 1. **Park detection (AC1)** — an agent exit whose latest milestone is the ship
168
+ phase's `awaiting-merge` (with `pr=`) is a VERIFIED park, not an unverified
169
+ exit: the entry moves to `parked`, the slot is released (a waiting unit
170
+ consumes zero slots), and the watcher takes over.
171
+ 2. **PR watching (AC1)** — parked PRs are polled every `pr_poll_interval_ms`
172
+ (default 150 s — "every 2–3 min", persisted `last_pr_poll_at` so a restart
173
+ honors the cadence; checked on each reconcile tick when due, so a
174
+ `reconcile_interval_ms` longer than the interval slows the effective cadence)
175
+ via `gh pr view --json state,mergedAt,mergeable,labels`.
176
+ A merge is accepted only when state is MERGED **and** `mergedAt` is non-null
177
+ **and** the issue is closed — never inferred from an agent exit. An
178
+ unreachable poll pauses the watcher (decision 2, option A).
179
+ 3. **Failure states (AC3)** — `CONFLICTING`, closed-unmerged, or the
180
+ `auto-merge-blocked` label fail the unit with the reason and block its
181
+ TRANSITIVE dependents. The engine never merges anything itself.
182
+ 4. **Gating on MERGE, not park (AC4)** — `parked` is not a satisfied status:
183
+ dependents stay blocked until the merge lands (`parked → shipped`).
184
+ 5. **Teardown as a script (AC2)** — on merge, the run's setup milestone
185
+ (recovered once from the issue's comments — collaborator-authored only, and
186
+ the worktree path must pass a containment check before any destructive
187
+ subprocess) chooses the script: pool-claimed worktrees run
188
+ `worktree-pool return --path <wt> --json` (the pool's own self-check is the
189
+ verification); cold worktrees run
190
+ `git worktree remove --force <wt>` with a path-gone check. Both are
191
+ verify-first idempotent; a failed step records `cleanup=failed-<step>` on
192
+ the entry and in the journal — degradation, never unit failure.
193
+ 6. **Report dispatch (AC2)** — once teardown is recorded (when a slot is
194
+ free — a waiting report consumes zero slots), a **mechanical-tier** report
195
+ agent is spawned with the report-phase prompt (`dispatch.report_prompt`;
196
+ `{issue}`/`{pr}`/`{cleanup}` substituted — the cleanup status rides into
197
+ the report). It completes like any agent; a report that stalls climbs the
198
+ same ladder (mechanical → mid → strong, cap 2), and at the cap the unit
199
+ completes (`done`, reason `report-escalation-cap`) with a `report-failed`
200
+ journal event — the work is merged, so dependents are never re-blocked.
201
+ The full-cycle tail-run pattern (re-dispatching a whole run for
202
+ teardown+report) is retired.
203
+
204
+ `sched status` shows parked PRs (zero slots, with the last poll's age), a
205
+ `pr` column and a `cleanup` column on the queue; every watcher decision lands
206
+ in `events.jsonl` (`pr-parked`, `merge-accepted`, `pr-watch-failed`,
207
+ `pr-watch-waiting`, `teardown-done`/`teardown-failed`, `report-dispatched`,
208
+ `report-failed`, `ground-truth-unreachable`).
209
+
144
210
  ## Development
145
211
 
146
212
  ```bash
@@ -152,4 +218,6 @@ npm test # vitest — state machines, persistence, crash/restart, engine
152
218
 
153
219
  No network, no GitHub in unit tests — persistence tests run on temp directories; the
154
220
  integration tests spawn fake-agent fixtures against a scratch git repo with stubbed
155
- ground truth.
221
+ ground truth. The #468 integration tests run the full detached-ship tail
222
+ (park → watch → merge → REAL worktree removal → report) end-to-end, including a
223
+ sched restart mid-watch.
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Agent dispatch machinery (#464, AC1 — "a runnable queue unit is dispatched
3
+ * as a spawned agent process … with `--model` per its tier; pid + phase +
4
+ * last-progress timestamp tracked in state.json"; RFC-0001 §C.1 "spawns agent
5
+ * processes via the existing `run` machinery").
6
+ *
7
+ * The command is a template (default `claude -p --output-format json --model
8
+ * {model}` — the same headless invocation `ai-dossier run` builds in
9
+ * cli/src/helpers.ts) with `{model}`/`{issue}` placeholders; the prompt
10
+ * travels on stdin exactly like the run machinery's headless path. Children
11
+ * spawn DETACHED and unref'd: an agent must survive a sched crash (RFC F.10 —
12
+ * restart reconciles by pid, it never owns the agent's lifetime).
13
+ *
14
+ * All process I/O is injectable (`SpawnDeps`) so tests spawn fake agents, and
15
+ * the package itself never invokes an LLM — it spawns a process the operator
16
+ * configured.
17
+ */
18
+ import { type ModelTier, type SchedConfig } from './types';
19
+ /** Default tier → model mapping (claude aliases; override in config.json). */
20
+ export declare const DEFAULT_TIER_MODELS: Readonly<Record<ModelTier, string>>;
21
+ /** Default headless agent command template (claude, the run machinery's first choice). */
22
+ export declare const DEFAULT_DISPATCH_COMMAND: readonly string[];
23
+ /** opencode equivalent (the run machinery's second agent, #459). */
24
+ export declare const OPENCODE_DISPATCH_COMMAND: readonly string[];
25
+ /**
26
+ * Default prompt sent on the child's stdin. Detached ship mode (#468): the
27
+ * agent parks the PR on `auto-merge` and STOPS — the scheduler's PR watcher
28
+ * owns the merge wait and dispatches teardown + report as tail work. The
29
+ * fleet pattern of re-dispatching a full-cycle run for the tail is retired.
30
+ * Operators wanting attached runs (agent drives to the final report itself)
31
+ * override `dispatch.prompt` in config.json.
32
+ */
33
+ export declare const DEFAULT_PROMPT_TEMPLATE: string;
34
+ /**
35
+ * Default prompt for the report agent dispatched after a merged PR (#468
36
+ * AC2) — a cheap-tier run of the report phase only, never a full cycle.
37
+ */
38
+ export declare const DEFAULT_REPORT_PROMPT_TEMPLATE: string;
39
+ /** Fully-resolved dispatch settings the engine runs with. */
40
+ export interface ResolvedDispatch {
41
+ /** Command template with `{model}`/`{issue}` placeholders. */
42
+ command: string[];
43
+ /** Prompt template with `{issue}` placeholder. */
44
+ prompt: string;
45
+ /** Report-agent prompt template with `{issue}`/`{pr}`/`{cleanup}` placeholders (#468). */
46
+ reportPrompt: string;
47
+ /** Model per tier; null means "no model flag" (the command's `--model {model}` pair drops). */
48
+ tierModels: Record<ModelTier, string | null>;
49
+ stallTimeoutMs: number;
50
+ reconcileIntervalMs: number;
51
+ /** Parked-PR poll interval (#468 AC1, default 150 s — "every 2–3 min"). */
52
+ prPollIntervalMs: number;
53
+ }
54
+ /** Resolve engine dispatch settings from the (possibly sparse) config. */
55
+ export declare function resolveDispatch(config: SchedConfig): ResolvedDispatch;
56
+ /**
57
+ * Build the concrete agent argv for one dispatch: substitute `{issue}` and
58
+ * `{model}`. When the tier's model is null, the `{model}` item AND its
59
+ * immediately-preceding flag (e.g. `--model`) drop together — a command never
60
+ * carries a flag whose value is missing.
61
+ */
62
+ export declare function buildAgentCommand(template: readonly string[], tier: ModelTier, issue: number, tierModels: Readonly<Record<ModelTier, string | null>>): string[];
63
+ /** Build the child's stdin prompt for one dispatch. */
64
+ export declare function buildPrompt(template: string, issue: number): string;
65
+ /** Build the report agent's stdin prompt (#468): `{issue}`/`{pr}`/`{cleanup}` substituted. */
66
+ export declare function buildReportPrompt(template: string, issue: number, pr: number, cleanup: string): string;
67
+ /** One tier stronger on the ladder, or null at the top (RFC-0001 §C.1). */
68
+ export declare function escalateTier(tier: ModelTier): ModelTier | null;
69
+ /**
70
+ * The tier for a report-agent (re)dispatch after `recoveries` escalations
71
+ * (#468): reports start cheap (mechanical) and climb the same ladder —
72
+ * mechanical → mid → strong — with null past the top. The engine's
73
+ * `ESCALATION_CAP` check fails the unit before that null is reached.
74
+ */
75
+ export declare function reportTierFor(recoveries: number): ModelTier | null;
76
+ export interface SpawnDeps {
77
+ /**
78
+ * Spawn a detached agent process and return its pid. `logFile` receives the
79
+ * child's combined stdout/stderr (agents outlive sched, so their output
80
+ * cannot stay in this process's pipes). Throws synchronously when the
81
+ * process cannot be spawned (missing binary, unwritable log dir).
82
+ */
83
+ spawn(cmd: string[], prompt: string, logFile: string): number;
84
+ /**
85
+ * Signal a pid; returns false when it was already dead (or not ours).
86
+ * `expectedStart` (the persisted `/proc` start-time) enables the pid-reuse
87
+ * guard: a pid whose start-time no longer matches was reused by an
88
+ * unrelated process and is never signalled.
89
+ */
90
+ kill(pid: number, expectedStart?: number): boolean;
91
+ /**
92
+ * Whether a pid is alive (best-effort). `expectedStart` applies the same
93
+ * pid-reuse guard as `kill`.
94
+ */
95
+ isAlive(pid: number, expectedStart?: number): boolean;
96
+ /**
97
+ * `/proc/<pid>/stat` start-time (field 22) of a pid, when the platform
98
+ * exposes it (Linux); null elsewhere. The engine persists this at spawn so
99
+ * pid identity survives engine restarts (decision 1, option C).
100
+ */
101
+ processStart(pid: number): number | null;
102
+ }
103
+ /** `issue:464` → `issue-464` (filesystem-safe unit ids for log file names). */
104
+ export declare function unitLogName(unit: string): string;
105
+ /** Poll cadence bounds for `sleep`'s stop-check interval (engine loop). */
106
+ export declare const STOP_POLL_MIN_MS = 100;
107
+ export declare const STOP_POLL_MAX_MS = 1000;
108
+ /**
109
+ * `/proc/<pid>/stat` start-time (field 22, clock ticks since boot) — stable
110
+ * process identity for the lifetime of the pid, so a recycled pid is
111
+ * detectable. Null when /proc is unavailable (macOS/Windows) or the process
112
+ * is gone.
113
+ */
114
+ export declare function procStartTime(pid: number): number | null;
115
+ /**
116
+ * Real process I/O: detached spawn with output to a log file, unref'd.
117
+ *
118
+ * Pid-reuse guard (decision 1, option C — hybrid): every pid this instance
119
+ * spawns is recorded with its `/proc` start-time; `kill`/`isAlive` accept the
120
+ * start-time persisted in state.json and refuse a pid whose current
121
+ * start-time no longer matches (it was reused by an unrelated process — the
122
+ * agent we spawned is already dead, so skipping the signal loses nothing).
123
+ * On platforms without /proc the guard degrades to best-effort, and pids
124
+ * without a recorded start-time (e.g. from pre-decision state files) stay
125
+ * best-effort everywhere.
126
+ */
127
+ export declare function createSpawnDeps(cwd?: string): SpawnDeps;
128
+ //# sourceMappingURL=dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+ /**
3
+ * Agent dispatch machinery (#464, AC1 — "a runnable queue unit is dispatched
4
+ * as a spawned agent process … with `--model` per its tier; pid + phase +
5
+ * last-progress timestamp tracked in state.json"; RFC-0001 §C.1 "spawns agent
6
+ * processes via the existing `run` machinery").
7
+ *
8
+ * The command is a template (default `claude -p --output-format json --model
9
+ * {model}` — the same headless invocation `ai-dossier run` builds in
10
+ * cli/src/helpers.ts) with `{model}`/`{issue}` placeholders; the prompt
11
+ * travels on stdin exactly like the run machinery's headless path. Children
12
+ * spawn DETACHED and unref'd: an agent must survive a sched crash (RFC F.10 —
13
+ * restart reconciles by pid, it never owns the agent's lifetime).
14
+ *
15
+ * All process I/O is injectable (`SpawnDeps`) so tests spawn fake agents, and
16
+ * the package itself never invokes an LLM — it spawns a process the operator
17
+ * configured.
18
+ */
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.STOP_POLL_MAX_MS = exports.STOP_POLL_MIN_MS = exports.DEFAULT_REPORT_PROMPT_TEMPLATE = exports.DEFAULT_PROMPT_TEMPLATE = exports.OPENCODE_DISPATCH_COMMAND = exports.DEFAULT_DISPATCH_COMMAND = exports.DEFAULT_TIER_MODELS = void 0;
54
+ exports.resolveDispatch = resolveDispatch;
55
+ exports.buildAgentCommand = buildAgentCommand;
56
+ exports.buildPrompt = buildPrompt;
57
+ exports.buildReportPrompt = buildReportPrompt;
58
+ exports.escalateTier = escalateTier;
59
+ exports.reportTierFor = reportTierFor;
60
+ exports.unitLogName = unitLogName;
61
+ exports.procStartTime = procStartTime;
62
+ exports.createSpawnDeps = createSpawnDeps;
63
+ const node_child_process_1 = require("node:child_process");
64
+ const fs = __importStar(require("node:fs"));
65
+ const path = __importStar(require("node:path"));
66
+ const project_1 = require("./project");
67
+ const types_1 = require("./types");
68
+ /** Default tier → model mapping (claude aliases; override in config.json). */
69
+ exports.DEFAULT_TIER_MODELS = {
70
+ mechanical: 'haiku',
71
+ mid: 'sonnet',
72
+ strong: 'opus',
73
+ };
74
+ /** Default headless agent command template (claude, the run machinery's first choice). */
75
+ exports.DEFAULT_DISPATCH_COMMAND = [
76
+ 'claude',
77
+ '-p',
78
+ '--output-format',
79
+ 'json',
80
+ '--model',
81
+ '{model}',
82
+ ];
83
+ /** opencode equivalent (the run machinery's second agent, #459). */
84
+ exports.OPENCODE_DISPATCH_COMMAND = [
85
+ 'opencode',
86
+ 'run',
87
+ '--format',
88
+ 'json',
89
+ '--model',
90
+ '{model}',
91
+ ];
92
+ /**
93
+ * Default prompt sent on the child's stdin. Detached ship mode (#468): the
94
+ * agent parks the PR on `auto-merge` and STOPS — the scheduler's PR watcher
95
+ * owns the merge wait and dispatches teardown + report as tail work. The
96
+ * fleet pattern of re-dispatching a full-cycle run for the tail is retired.
97
+ * Operators wanting attached runs (agent drives to the final report itself)
98
+ * override `dispatch.prompt` in config.json.
99
+ */
100
+ exports.DEFAULT_PROMPT_TEMPLATE = 'Run the full-cycle-issue workflow for GitHub issue #{issue} in this repository.\n\n' +
101
+ 'Begin by fetching the workflow: ai-dossier run imboard-ai/git/full-cycle-issue --pull\n\n' +
102
+ 'Then execute it for issue #{issue} in detached ship mode (ship_mode=detached), following every ' +
103
+ 'phase (gate, setup, plan, implement, review) without asking questions, until Phase 5 parks the ' +
104
+ 'PR: apply the auto-merge label, post the awaiting-merge milestone, and STOP. Do not wait for ' +
105
+ 'the merge, do not run teardown or report — the scheduler watches the PR and dispatches those.';
106
+ /**
107
+ * Default prompt for the report agent dispatched after a merged PR (#468
108
+ * AC2) — a cheap-tier run of the report phase only, never a full cycle.
109
+ */
110
+ exports.DEFAULT_REPORT_PROMPT_TEMPLATE = 'Run the report phase for GitHub issue #{issue} in this repository.\n\n' +
111
+ 'Begin by fetching the workflow: ai-dossier run imboard-ai/git/report-issue --pull\n\n' +
112
+ 'The work is DONE: pull request #{pr} is merged (merge commit via `gh pr view {pr}`), the issue ' +
113
+ 'is closed, and the worktree is already torn down (cleanup status: {cleanup}). Do not ' +
114
+ 're-implement, re-review, or re-ship anything — produce the final report for issue #{issue} and ' +
115
+ 'post its runstate milestone.';
116
+ /** Resolve engine dispatch settings from the (possibly sparse) config. */
117
+ function resolveDispatch(config) {
118
+ const dispatch = config.dispatch ?? {};
119
+ const tierModels = {
120
+ mechanical: dispatch.tier_models?.mechanical ?? exports.DEFAULT_TIER_MODELS.mechanical,
121
+ mid: dispatch.tier_models?.mid ?? exports.DEFAULT_TIER_MODELS.mid,
122
+ strong: dispatch.tier_models?.strong ?? exports.DEFAULT_TIER_MODELS.strong,
123
+ };
124
+ return {
125
+ command: dispatch.command ?? [...exports.DEFAULT_DISPATCH_COMMAND],
126
+ prompt: dispatch.prompt ?? exports.DEFAULT_PROMPT_TEMPLATE,
127
+ reportPrompt: dispatch.report_prompt ?? exports.DEFAULT_REPORT_PROMPT_TEMPLATE,
128
+ tierModels,
129
+ stallTimeoutMs: config.stall_timeout_ms ?? types_1.DEFAULT_STALL_TIMEOUT_MS,
130
+ reconcileIntervalMs: config.reconcile_interval_ms ?? types_1.DEFAULT_RECONCILE_INTERVAL_MS,
131
+ prPollIntervalMs: config.pr_poll_interval_ms ?? types_1.DEFAULT_PR_POLL_INTERVAL_MS,
132
+ };
133
+ }
134
+ /**
135
+ * Build the concrete agent argv for one dispatch: substitute `{issue}` and
136
+ * `{model}`. When the tier's model is null, the `{model}` item AND its
137
+ * immediately-preceding flag (e.g. `--model`) drop together — a command never
138
+ * carries a flag whose value is missing.
139
+ */
140
+ function buildAgentCommand(template, tier, issue, tierModels) {
141
+ const model = tierModels[tier] ?? null;
142
+ const argv = [];
143
+ for (const item of template) {
144
+ if (item === '{model}') {
145
+ if (model === null) {
146
+ // Drop the preceding flag along with the placeholder.
147
+ if (argv.length > 0 && argv[argv.length - 1].startsWith('--'))
148
+ argv.pop();
149
+ continue;
150
+ }
151
+ argv.push(model);
152
+ continue;
153
+ }
154
+ argv.push(item.replaceAll('{issue}', String(issue)));
155
+ }
156
+ return argv;
157
+ }
158
+ /** Build the child's stdin prompt for one dispatch. */
159
+ function buildPrompt(template, issue) {
160
+ return template.replaceAll('{issue}', String(issue));
161
+ }
162
+ /** Build the report agent's stdin prompt (#468): `{issue}`/`{pr}`/`{cleanup}` substituted. */
163
+ function buildReportPrompt(template, issue, pr, cleanup) {
164
+ return template
165
+ .replaceAll('{issue}', String(issue))
166
+ .replaceAll('{pr}', String(pr))
167
+ .replaceAll('{cleanup}', cleanup);
168
+ }
169
+ /** One tier stronger on the ladder, or null at the top (RFC-0001 §C.1). */
170
+ function escalateTier(tier) {
171
+ return types_1.TIER_LADDER[tier];
172
+ }
173
+ /**
174
+ * The tier for a report-agent (re)dispatch after `recoveries` escalations
175
+ * (#468): reports start cheap (mechanical) and climb the same ladder —
176
+ * mechanical → mid → strong — with null past the top. The engine's
177
+ * `ESCALATION_CAP` check fails the unit before that null is reached.
178
+ */
179
+ function reportTierFor(recoveries) {
180
+ return types_1.TIER_ORDER[recoveries] ?? null;
181
+ }
182
+ /** `issue:464` → `issue-464` (filesystem-safe unit ids for log file names). */
183
+ function unitLogName(unit) {
184
+ return (0, project_1.sanitizeSlug)(unit);
185
+ }
186
+ /** Poll cadence bounds for `sleep`'s stop-check interval (engine loop). */
187
+ exports.STOP_POLL_MIN_MS = 100;
188
+ exports.STOP_POLL_MAX_MS = 1000;
189
+ /**
190
+ * `/proc/<pid>/stat` start-time (field 22, clock ticks since boot) — stable
191
+ * process identity for the lifetime of the pid, so a recycled pid is
192
+ * detectable. Null when /proc is unavailable (macOS/Windows) or the process
193
+ * is gone.
194
+ */
195
+ function procStartTime(pid) {
196
+ try {
197
+ const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');
198
+ // comm (field 2) is parenthesized and may contain spaces — everything
199
+ // after the LAST ')' is fields 3..N, space-separated.
200
+ const close = stat.lastIndexOf(')');
201
+ if (close === -1)
202
+ return null;
203
+ const fields = stat.slice(close + 2).split(' ');
204
+ // field 22 (starttime) -> index 19 in the post-comm array (field 3 = index 0)
205
+ const start = Number.parseInt(fields[19] ?? '', 10);
206
+ return Number.isInteger(start) && start >= 0 ? start : null;
207
+ }
208
+ catch {
209
+ return null;
210
+ }
211
+ }
212
+ /**
213
+ * Real process I/O: detached spawn with output to a log file, unref'd.
214
+ *
215
+ * Pid-reuse guard (decision 1, option C — hybrid): every pid this instance
216
+ * spawns is recorded with its `/proc` start-time; `kill`/`isAlive` accept the
217
+ * start-time persisted in state.json and refuse a pid whose current
218
+ * start-time no longer matches (it was reused by an unrelated process — the
219
+ * agent we spawned is already dead, so skipping the signal loses nothing).
220
+ * On platforms without /proc the guard degrades to best-effort, and pids
221
+ * without a recorded start-time (e.g. from pre-decision state files) stay
222
+ * best-effort everywhere.
223
+ */
224
+ function createSpawnDeps(cwd) {
225
+ /** Pids spawned by THIS instance -> their /proc start-time (Linux). */
226
+ const spawnedStarts = new Map();
227
+ /** True when `pid` plausibly still names the process we recorded. */
228
+ function matchesRecordedStart(pid, expectedStart) {
229
+ const expected = expectedStart ?? spawnedStarts.get(pid);
230
+ if (expected === undefined)
231
+ return true; // no recorded identity — best-effort
232
+ const current = procStartTime(pid);
233
+ if (current === null)
234
+ return true; // /proc unavailable (non-Linux) or a race — best-effort
235
+ return current === expected;
236
+ }
237
+ return {
238
+ spawn(cmd, prompt, logFile) {
239
+ fs.mkdirSync(path.dirname(logFile), { recursive: true, mode: 0o700 });
240
+ const out = fs.openSync(logFile, 'a', 0o600);
241
+ try {
242
+ const child = (0, node_child_process_1.spawn)(cmd[0], cmd.slice(1), {
243
+ ...(cwd ? { cwd } : {}),
244
+ detached: true,
245
+ stdio: ['pipe', out, out],
246
+ });
247
+ // Spawn failures surface synchronously via the pid check below; the
248
+ // async 'error' event must never crash the engine (ENOENT), and an
249
+ // agent exiting before reading stdin must never crash it either (EPIPE).
250
+ child.on('error', (err) => {
251
+ process.stderr.write(`⚠ sched: spawn '${cmd[0]}' failed: ${err.message}\n`);
252
+ });
253
+ if (child.stdin !== null) {
254
+ child.stdin.on('error', () => { });
255
+ child.stdin.write(prompt);
256
+ child.stdin.end();
257
+ }
258
+ child.unref();
259
+ if (child.pid === undefined) {
260
+ throw new Error(`failed to spawn '${cmd[0]}' — is it on PATH? (command: ${cmd.join(' ')})`);
261
+ }
262
+ const start = procStartTime(child.pid);
263
+ if (start !== null)
264
+ spawnedStarts.set(child.pid, start);
265
+ return child.pid;
266
+ }
267
+ finally {
268
+ // The child holds its own dups of the fd; the parent's copy must close.
269
+ fs.closeSync(out);
270
+ }
271
+ },
272
+ kill(pid, expectedStart) {
273
+ if (!matchesRecordedStart(pid, expectedStart)) {
274
+ spawnedStarts.delete(pid);
275
+ return false; // reused pid — the agent we spawned is already gone
276
+ }
277
+ try {
278
+ process.kill(pid, 'SIGTERM');
279
+ return true;
280
+ }
281
+ catch {
282
+ spawnedStarts.delete(pid);
283
+ return false;
284
+ }
285
+ },
286
+ isAlive(pid, expectedStart) {
287
+ try {
288
+ process.kill(pid, 0);
289
+ return matchesRecordedStart(pid, expectedStart);
290
+ }
291
+ catch (err) {
292
+ return err.code === 'EPERM';
293
+ }
294
+ },
295
+ processStart(pid) {
296
+ return procStartTime(pid);
297
+ },
298
+ };
299
+ }
300
+ //# sourceMappingURL=dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}