@mobrienv/autoloop-harness 0.8.0 → 0.9.2

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 (116) hide show
  1. package/dist/acceptance.d.ts +31 -0
  2. package/dist/acceptance.js +69 -0
  3. package/dist/acceptance.js.map +1 -0
  4. package/dist/ask.d.ts +16 -0
  5. package/dist/ask.js +47 -0
  6. package/dist/ask.js.map +1 -0
  7. package/dist/backend/types.d.ts +3 -0
  8. package/dist/backend/types.js +3 -0
  9. package/dist/backend/types.js.map +1 -1
  10. package/dist/circuit-breaker.d.ts +27 -0
  11. package/dist/circuit-breaker.js +41 -0
  12. package/dist/circuit-breaker.js.map +1 -0
  13. package/dist/completion-lint.d.ts +29 -0
  14. package/dist/completion-lint.js +51 -0
  15. package/dist/completion-lint.js.map +1 -0
  16. package/dist/config-helpers.js +141 -8
  17. package/dist/config-helpers.js.map +1 -1
  18. package/dist/control/command-adapter.d.ts +19 -0
  19. package/dist/control/command-adapter.js +57 -0
  20. package/dist/control/command-adapter.js.map +1 -0
  21. package/dist/control/dispatch.js +14 -0
  22. package/dist/control/dispatch.js.map +1 -1
  23. package/dist/control/types.d.ts +11 -2
  24. package/dist/display.d.ts +9 -2
  25. package/dist/display.js +37 -1
  26. package/dist/display.js.map +1 -1
  27. package/dist/emit.d.ts +27 -0
  28. package/dist/emit.js +321 -2
  29. package/dist/emit.js.map +1 -1
  30. package/dist/events.d.ts +36 -3
  31. package/dist/fanout-runner.d.ts +25 -0
  32. package/dist/fanout-runner.js +54 -0
  33. package/dist/fanout-runner.js.map +1 -0
  34. package/dist/file-mod-audit.d.ts +29 -0
  35. package/dist/file-mod-audit.js +63 -0
  36. package/dist/file-mod-audit.js.map +1 -0
  37. package/dist/git-diff.d.ts +19 -0
  38. package/dist/git-diff.js +74 -0
  39. package/dist/git-diff.js.map +1 -0
  40. package/dist/hooks.d.ts +78 -0
  41. package/dist/hooks.js +239 -0
  42. package/dist/hooks.js.map +1 -0
  43. package/dist/index.d.ts +15 -2
  44. package/dist/index.js +181 -5
  45. package/dist/index.js.map +1 -1
  46. package/dist/intent.d.ts +45 -0
  47. package/dist/intent.js +127 -0
  48. package/dist/intent.js.map +1 -0
  49. package/dist/iteration-diff.d.ts +32 -0
  50. package/dist/iteration-diff.js +92 -0
  51. package/dist/iteration-diff.js.map +1 -0
  52. package/dist/iteration.d.ts +20 -0
  53. package/dist/iteration.js +383 -16
  54. package/dist/iteration.js.map +1 -1
  55. package/dist/metareview.d.ts +9 -1
  56. package/dist/metareview.js +68 -30
  57. package/dist/metareview.js.map +1 -1
  58. package/dist/notify.d.ts +9 -2
  59. package/dist/notify.js +16 -4
  60. package/dist/notify.js.map +1 -1
  61. package/dist/parallel.d.ts +16 -0
  62. package/dist/parallel.js +28 -1
  63. package/dist/parallel.js.map +1 -1
  64. package/dist/postconditions.d.ts +25 -0
  65. package/dist/postconditions.js +107 -0
  66. package/dist/postconditions.js.map +1 -0
  67. package/dist/postfire-verify.d.ts +31 -0
  68. package/dist/postfire-verify.js +64 -0
  69. package/dist/postfire-verify.js.map +1 -0
  70. package/dist/premature-quit.d.ts +25 -0
  71. package/dist/premature-quit.js +54 -0
  72. package/dist/premature-quit.js.map +1 -0
  73. package/dist/progress.d.ts +20 -0
  74. package/dist/progress.js +56 -0
  75. package/dist/progress.js.map +1 -0
  76. package/dist/prompt.js +8 -2
  77. package/dist/prompt.js.map +1 -1
  78. package/dist/provisional.d.ts +42 -0
  79. package/dist/provisional.js +92 -0
  80. package/dist/provisional.js.map +1 -0
  81. package/dist/registry-bridge.js +25 -0
  82. package/dist/registry-bridge.js.map +1 -1
  83. package/dist/resume.d.ts +61 -0
  84. package/dist/resume.js +226 -0
  85. package/dist/resume.js.map +1 -0
  86. package/dist/scratchpad.js +27 -8
  87. package/dist/scratchpad.js.map +1 -1
  88. package/dist/stage.d.ts +21 -0
  89. package/dist/stage.js +131 -0
  90. package/dist/stage.js.map +1 -0
  91. package/dist/stop.d.ts +29 -2
  92. package/dist/stop.js +106 -0
  93. package/dist/stop.js.map +1 -1
  94. package/dist/suspend-state.d.ts +32 -0
  95. package/dist/suspend-state.js +104 -0
  96. package/dist/suspend-state.js.map +1 -0
  97. package/dist/tamper.d.ts +21 -0
  98. package/dist/tamper.js +88 -0
  99. package/dist/tamper.js.map +1 -0
  100. package/dist/types.d.ts +247 -2
  101. package/dist/types.js +91 -1
  102. package/dist/types.js.map +1 -1
  103. package/dist/wave/finalize-wave.d.ts +15 -5
  104. package/dist/wave/finalize-wave.js +56 -2
  105. package/dist/wave/finalize-wave.js.map +1 -1
  106. package/dist/wave/launch-branches.d.ts +23 -2
  107. package/dist/wave/launch-branches.js +56 -3
  108. package/dist/wave/launch-branches.js.map +1 -1
  109. package/dist/wave/stage-branch-runner.d.ts +12 -0
  110. package/dist/wave/stage-branch-runner.js +130 -0
  111. package/dist/wave/stage-branch-runner.js.map +1 -0
  112. package/dist/wave/types.d.ts +40 -2
  113. package/dist/wave.d.ts +32 -1
  114. package/dist/wave.js +163 -34
  115. package/dist/wave.js.map +1 -1
  116. package/package.json +83 -3
@@ -0,0 +1,61 @@
1
+ import type { RunRecord } from "@mobrienv/autoloop-core/registry/types";
2
+ import type { LoopContext, RunOptions, RunSummary } from "./types.js";
3
+ export interface ResumeOptions {
4
+ /** Additional iterations to grant beyond the resume point. */
5
+ addIterations?: number;
6
+ /** Backend override (parsed `-b` spec), applied via reloadLoop. */
7
+ backendOverride?: Record<string, unknown>;
8
+ /** Log level override (e.g. "debug" for -v). */
9
+ logLevel?: string | null;
10
+ /**
11
+ * Main repo state dir holding the registry the record was read from. Used
12
+ * to anchor registryFile and worktree cleanup. When omitted it is derived
13
+ * from the record (project_dir + state dir name).
14
+ */
15
+ baseStateDir?: string;
16
+ /** Abort signal (CLI owns process signal handling). */
17
+ signal?: AbortSignal;
18
+ /** Structured-event listener. */
19
+ onEvent?: RunOptions["onEvent"];
20
+ /**
21
+ * Force every fan-out stage branch to relaunch rather than reuse a
22
+ * journaled `stage.branch.finish` record from an interrupted prior attempt.
23
+ */
24
+ noResume?: boolean;
25
+ }
26
+ export interface ResumeResult extends RunSummary {
27
+ resumedFromIteration: number;
28
+ newMaxIterations: number;
29
+ }
30
+ /**
31
+ * Compute the iteration to resume at, given the terminating stop reason and
32
+ * the last iteration recorded in the registry.
33
+ *
34
+ * - `max_iterations`: the registry iteration is the last *completed* one; the
35
+ * blocked iteration is the next one (registryIteration + 1).
36
+ * - `backend_failed` / `backend_timeout`: the registry iteration is the one
37
+ * that failed mid-flight; retry it (registryIteration).
38
+ * - `interrupted` / `stopped` (or anything else): scan the journal — if an
39
+ * `iteration.finish` exists for registryIteration it completed, so resume at
40
+ * the next one; otherwise it was in-flight, so retry it.
41
+ */
42
+ export declare function determineResumeIteration(journalFile: string, runId: string, stopReason: string, registryIteration: number): number;
43
+ /**
44
+ * Build a LoopContext that reuses an existing run's identity (run_id, journal,
45
+ * memory, working files, worktree) instead of generating fresh ones. Mirrors
46
+ * `buildLoopContext` but skips run-id generation and worktree creation; the
47
+ * one-time setup that resume needs (loading config, deriving paths, filling
48
+ * topology/backend/limits) is delegated to `reloadLoop`.
49
+ */
50
+ export declare function buildResumeContext(record: RunRecord, options?: ResumeOptions): {
51
+ loop: LoopContext;
52
+ resumeIteration: number;
53
+ addIterations: number;
54
+ };
55
+ /**
56
+ * Resume a terminated run from where it left off. Validation (status, pid
57
+ * liveness, missing files) is the caller's responsibility (see the CLI
58
+ * command); this entry assumes the record is resumable and drives the same
59
+ * iteration loop `run()` uses.
60
+ */
61
+ export declare function resume(record: RunRecord, options?: ResumeOptions): Promise<ResumeResult>;
package/dist/resume.js ADDED
@@ -0,0 +1,226 @@
1
+ import { existsSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+ import { jsonField } from "@mobrienv/autoloop-core";
4
+ import * as config from "@mobrienv/autoloop-core/config";
5
+ import { appendEvent, extractIteration, extractTopic, readRunLines, } from "@mobrienv/autoloop-core/journal";
6
+ import { updateStatus as updateWorktreeStatus } from "@mobrienv/autoloop-core/worktree";
7
+ import { ensureLayout, initStore, installRuntimeTools, reloadLoop, } from "./config-helpers.js";
8
+ import { publishCapabilities } from "./control/dispatch.js";
9
+ import { log } from "./display.js";
10
+ import { buildControlAdapter, driveLoop } from "./index.js";
11
+ import { registryStart } from "./registry-bridge.js";
12
+ import { clearResumeRequest, clearSuspendState, readSuspendState, } from "./suspend-state.js";
13
+ /**
14
+ * Compute the iteration to resume at, given the terminating stop reason and
15
+ * the last iteration recorded in the registry.
16
+ *
17
+ * - `max_iterations`: the registry iteration is the last *completed* one; the
18
+ * blocked iteration is the next one (registryIteration + 1).
19
+ * - `backend_failed` / `backend_timeout`: the registry iteration is the one
20
+ * that failed mid-flight; retry it (registryIteration).
21
+ * - `interrupted` / `stopped` (or anything else): scan the journal — if an
22
+ * `iteration.finish` exists for registryIteration it completed, so resume at
23
+ * the next one; otherwise it was in-flight, so retry it.
24
+ */
25
+ export function determineResumeIteration(journalFile, runId, stopReason, registryIteration) {
26
+ if (stopReason === "max_iterations") {
27
+ return registryIteration + 1;
28
+ }
29
+ if (stopReason === "backend_failed" || stopReason === "backend_timeout") {
30
+ return registryIteration;
31
+ }
32
+ // interrupted / stopped / unknown: trust the journal.
33
+ const lines = readRunLines(journalFile, runId);
34
+ const finished = lines.some((line) => extractTopic(line) === "iteration.finish" &&
35
+ extractIteration(line) === String(registryIteration));
36
+ return finished ? registryIteration + 1 : registryIteration;
37
+ }
38
+ /**
39
+ * Build a LoopContext that reuses an existing run's identity (run_id, journal,
40
+ * memory, working files, worktree) instead of generating fresh ones. Mirrors
41
+ * `buildLoopContext` but skips run-id generation and worktree creation; the
42
+ * one-time setup that resume needs (loading config, deriving paths, filling
43
+ * topology/backend/limits) is delegated to `reloadLoop`.
44
+ */
45
+ export function buildResumeContext(record, options = {}) {
46
+ const projectDir = record.project_dir;
47
+ if (!projectDir) {
48
+ throw new Error(`run ${record.run_id} has no project_dir; cannot resume`);
49
+ }
50
+ // findRunByPrefix reads the registry directly, so a resumable run always
51
+ // carries the real state_dir that registryStart wrote (.autoloop/ for shared
52
+ // and worktree modes, .autoloop/runs/<id>/ for run-scoped). Never guess it
53
+ // from the journal path — for run-scoped runs the journal lives at the
54
+ // top-level .autoloop/ while the state dir is .autoloop/runs/<id>/, so a
55
+ // dirname() guess would point at the wrong directory.
56
+ const stateDir = record.state_dir;
57
+ if (!stateDir) {
58
+ throw new Error(`run ${record.run_id} has no state_dir; cannot resume`);
59
+ }
60
+ const journalFile = record.journal_file;
61
+ const isolationMode = record.isolation_mode || "run-scoped";
62
+ const worktreeMode = isolationMode === "worktree";
63
+ const workDir = worktreeMode
64
+ ? record.worktree_path || record.work_dir
65
+ : record.work_dir;
66
+ // Recompute config-anchored paths exactly as buildLoopContext does, but
67
+ // rooted in this run's existing state dir rather than a freshly-created one.
68
+ const stateDirName = basename(stateDir);
69
+ const presetFile = record.preset_file || "";
70
+ const cfg = presetFile
71
+ ? config.loadProjectFromFile(presetFile, {
72
+ presetName: record.preset || basename(projectDir),
73
+ workDir,
74
+ })
75
+ : config.loadProject(projectDir, {
76
+ presetName: record.preset || basename(projectDir),
77
+ workDir,
78
+ });
79
+ const memoryRel = config.get(cfg, "core.memory_file", join(stateDirName, "memory.jsonl"));
80
+ const memoryFile = join(workDir, memoryRel);
81
+ // baseStateDir is the main repo's state dir (registry + worktree metadata
82
+ // live here). Prefer the caller-supplied path (the registry the record was
83
+ // read from); fall back to deriving it. In worktree mode the per-run journal
84
+ // lives under the worktree tree, so the main state dir is the project root's.
85
+ const baseStateDir = options.baseStateDir ||
86
+ (worktreeMode ? join(record.project_dir, stateDirName) : stateDir);
87
+ const registryFile = join(baseStateDir, "registry.jsonl");
88
+ const backendOverride = options.backendOverride || {};
89
+ const logLevel = options.logLevel || config.get(cfg, "core.log_level", "info");
90
+ // A durable suspend (written by a `suspend`-policy hook) carries its own
91
+ // recorded resume point — prefer it over the stop-reason heuristic, since
92
+ // it reflects exactly where the hook engine intended to resume rather than
93
+ // a best-effort guess from the journal.
94
+ const suspendState = readSuspendState(stateDir);
95
+ const resumeIteration = suspendState
96
+ ? suspendState.resumeIteration
97
+ : determineResumeIteration(journalFile, record.run_id, record.stop_reason, record.iteration);
98
+ // Budget is additive from the resume point. Default add-iterations to the
99
+ // run's original max_iterations (so a max_iterations stop, by default, grants
100
+ // another full run's worth of budget beyond what was completed).
101
+ const addIterations = options.addIterations && options.addIterations > 0
102
+ ? options.addIterations
103
+ : record.max_iterations ||
104
+ config.getInt(cfg, "event_loop.max_iterations", 3);
105
+ const newMaxIterations = resumeIteration - 1 + addIterations;
106
+ // The budget must survive reloadLoop (called at the start of every
107
+ // iteration), which re-reads event_loop.max_iterations from config. Layer it
108
+ // in as a run config override so each reload sees the additive budget rather
109
+ // than the static config value.
110
+ const configOverride = config.put({}, "event_loop.max_iterations", String(newMaxIterations));
111
+ const seed = {
112
+ paths: {
113
+ projectDir,
114
+ workDir,
115
+ stateDir,
116
+ journalFile,
117
+ memoryFile,
118
+ runMemoryFile: join(stateDir, "memory.jsonl"),
119
+ tasksFile: join(stateDir, "tasks.jsonl"),
120
+ registryFile,
121
+ toolPath: join(stateDir, "autoloops"),
122
+ piAdapterPath: join(stateDir, "pi-adapter"),
123
+ baseStateDir,
124
+ mainProjectDir: projectDir,
125
+ worktreeBranch: worktreeMode ? record.worktree_name : "",
126
+ worktreePath: worktreeMode ? record.worktree_path : "",
127
+ worktreeMetaDir: worktreeMode
128
+ ? join(baseStateDir, "worktrees", record.run_id)
129
+ : "",
130
+ configWorkDir: workDir,
131
+ },
132
+ runtime: {
133
+ runId: record.run_id,
134
+ selfCommand: "",
135
+ promptOverride: null,
136
+ backendOverride,
137
+ configOverride,
138
+ logLevel,
139
+ branchMode: false,
140
+ isolationMode,
141
+ noResume: options.noResume ?? false,
142
+ },
143
+ launch: {
144
+ preset: record.preset || basename(projectDir),
145
+ trigger: record.trigger || "cli",
146
+ createdAt: record.created_at || new Date().toISOString(),
147
+ parentRunId: record.parent_run_id || "",
148
+ presetFile,
149
+ },
150
+ profiles: {
151
+ active: [],
152
+ fragments: new Map(),
153
+ warnings: [],
154
+ },
155
+ store: {},
156
+ };
157
+ let loop = reloadLoop(seed);
158
+ // Override the iteration budget: reloadLoop reads it fresh from config; resume
159
+ // needs the additive budget from the resume point.
160
+ loop = {
161
+ ...loop,
162
+ limits: { ...loop.limits, maxIterations: newMaxIterations },
163
+ };
164
+ return { loop, resumeIteration, addIterations };
165
+ }
166
+ /**
167
+ * Resume a terminated run from where it left off. Validation (status, pid
168
+ * liveness, missing files) is the caller's responsibility (see the CLI
169
+ * command); this entry assumes the record is resumable and drives the same
170
+ * iteration loop `run()` uses.
171
+ */
172
+ export async function resume(record, options = {}) {
173
+ const { loop: built, resumeIteration, addIterations, } = buildResumeContext(record, options);
174
+ let loop = built;
175
+ loop.onEvent = options.onEvent;
176
+ loop = initStore(loop);
177
+ ensureLayout(loop.paths.stateDir);
178
+ installRuntimeTools(loop);
179
+ // Append the resume marker before any iteration so the scratchpad and derive
180
+ // path see it. It's a system topic — routing ignores it.
181
+ appendEvent(loop.paths.journalFile, loop.runtime.runId, "", "loop.resume", jsonField("resumed_from_iteration", String(resumeIteration)) +
182
+ ", " +
183
+ jsonField("previous_stop_reason", record.stop_reason || "") +
184
+ ", " +
185
+ jsonField("add_iterations", String(addIterations)) +
186
+ ", " +
187
+ jsonField("new_max_iterations", String(loop.limits.maxIterations)));
188
+ // Registry: append a fresh record (status running, pid = this process). The
189
+ // registry is last-write-wins per run_id, so this transitions the run back to
190
+ // running exactly as the normal start path does.
191
+ registryStart(loop);
192
+ // Worktree mode: flip meta status back to running so the run isn't stuck as
193
+ // "failed" if it crashed mid-flight.
194
+ if (loop.paths.worktreeMetaDir && existsSync(loop.paths.worktreeMetaDir)) {
195
+ try {
196
+ updateWorktreeStatus(loop.paths.worktreeMetaDir, "running");
197
+ }
198
+ catch {
199
+ /* best-effort */
200
+ }
201
+ }
202
+ loop.controlAdapter = buildControlAdapter(loop);
203
+ if (loop.controlAdapter) {
204
+ publishCapabilities(loop.paths.stateDir, loop.controlAdapter);
205
+ }
206
+ // Clear durable suspend markers now that we're re-entering the loop at the
207
+ // recorded resume point — a stale suspend-state.json would otherwise make
208
+ // the next `run()` (or this resume, if invoked again) refuse to start.
209
+ clearSuspendState(loop.paths.stateDir);
210
+ clearResumeRequest(loop.paths.stateDir);
211
+ log(loop, "info", `resume run_id=${loop.runtime.runId} from_iteration=${resumeIteration} new_max_iterations=${loop.limits.maxIterations} (was: ${record.stop_reason || "unknown"})`);
212
+ const runOptions = {
213
+ workDir: loop.paths.workDir,
214
+ backendOverride: options.backendOverride,
215
+ logLevel: options.logLevel,
216
+ signal: options.signal,
217
+ onEvent: options.onEvent,
218
+ };
219
+ const summary = await driveLoop(loop, runOptions, resumeIteration);
220
+ return {
221
+ ...summary,
222
+ resumedFromIteration: resumeIteration,
223
+ newMaxIterations: loop.limits.maxIterations,
224
+ };
225
+ }
226
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.js","sourceRoot":"","sources":["../src/resume.ts"],"names":[],"mappings":"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"}
@@ -20,15 +20,23 @@ function collectScratchpadEntries(lines) {
20
20
  const entries = [];
21
21
  for (const line of lines) {
22
22
  const event = decodeEvent(line);
23
- if (!event ||
24
- event.shape !== "fields" ||
25
- event.topic !== "iteration.finish")
23
+ if (!event || event.shape !== "fields")
26
24
  continue;
27
- entries.push({
28
- iteration: event.iteration ?? "",
29
- exitCode: event.fields.exit_code ?? "",
30
- output: event.fields.output ?? "",
31
- });
25
+ if (event.topic === "iteration.finish") {
26
+ entries.push({
27
+ kind: "iteration",
28
+ iteration: event.iteration ?? "",
29
+ exitCode: event.fields.exit_code ?? "",
30
+ output: event.fields.output ?? "",
31
+ });
32
+ }
33
+ else if (event.topic === "loop.resume") {
34
+ entries.push({
35
+ kind: "resume",
36
+ previousStopReason: event.fields.previous_stop_reason ?? "",
37
+ addIterations: event.fields.add_iterations ?? "",
38
+ });
39
+ }
32
40
  }
33
41
  return entries;
34
42
  }
@@ -36,6 +44,9 @@ function renderScratchpadEntries(entries) {
36
44
  return entries.map(scratchpadEntryText).join("");
37
45
  }
38
46
  function scratchpadEntryText(entry) {
47
+ if (entry.kind === "resume") {
48
+ return `${resumeMarkerText(entry)}\n\n`;
49
+ }
39
50
  return (heading(2, `Iteration ${entry.iteration}`) +
40
51
  "\n\n" +
41
52
  "exit_code=" +
@@ -44,7 +55,15 @@ function scratchpadEntryText(entry) {
44
55
  entry.output +
45
56
  "\n");
46
57
  }
58
+ function resumeMarkerText(entry) {
59
+ const reason = entry.previousStopReason || "unknown";
60
+ const adding = entry.addIterations || "0";
61
+ return `--- resumed (was: ${reason}, adding ${adding} iterations) ---`;
62
+ }
47
63
  function compactScratchpadItem(entry) {
64
+ if (entry.kind === "resume") {
65
+ return resumeMarkerText(entry);
66
+ }
48
67
  return ("Iteration " +
49
68
  entry.iteration +
50
69
  " exit_code=" +
@@ -1 +1 @@
1
- {"version":3,"file":"scratchpad.js","sourceRoot":"","sources":["../src/scratchpad.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scratchpad.js","sourceRoot":"","sources":["../src/scratchpad.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { BranchResult as FanoutBranchResult, FanoutStage } from "@mobrienv/autoloop-core/fanout";
2
+ import type { IterationContext } from "./prompt.js";
3
+ import type { LoopContext, RunSummary } from "./types.js";
4
+ type IterateFn = (loop: LoopContext, iteration: number) => Promise<RunSummary>;
5
+ /**
6
+ * Reconstruct previously-completed branch results for `stageId` from the
7
+ * journal's `stage.branch.finish` records (any prior attempt in this run,
8
+ * across iterations — a stage that was mid-flight when the run was
9
+ * interrupted leaves these records behind). The latest record per branch id
10
+ * wins. Empty when resume is disabled or nothing was journaled yet.
11
+ */
12
+ export declare function loadResumedBranches(loop: LoopContext, stageId: string): Map<string, FanoutBranchResult>;
13
+ /**
14
+ * Run a fan-out stage triggered by `emittedTopic`, reduce its branch results
15
+ * to a routing decision, journal it, and continue the loop at the outcome
16
+ * event. `stage.branch.finish` records (real launches only — resumed/
17
+ * budget-skipped branches are not re-journaled) let a later attempt resume
18
+ * this exact stage without relaunching branches that already finished.
19
+ */
20
+ export declare function finishStageIteration(loop: LoopContext, iter: IterationContext, stage: FanoutStage, emittedTopic: string, iterate: IterateFn): Promise<RunSummary>;
21
+ export {};
package/dist/stage.js ADDED
@@ -0,0 +1,131 @@
1
+ // Wires fan-out `[[stage]]` execution into the iteration loop. Mirrors
2
+ // `wave.ts`'s `continueAfterParallelJoin`: run the stage to a reduced routing
3
+ // decision, journal it, emit the outcome event, and continue routing through
4
+ // the SAME topology handoff every other event uses — the stage's `onPass`/
5
+ // `onFail` are plain declared events, so the loop gains no special routing
6
+ // path once the stage has finished.
7
+ import { jsonField } from "@mobrienv/autoloop-core";
8
+ import { appendEvent, appendHarnessEvent, extractField, extractTopic, readRunLines, } from "@mobrienv/autoloop-core/journal";
9
+ import { runCostUsd } from "./display.js";
10
+ import { expandStageBranches, runFanoutStage } from "./fanout-runner.js";
11
+ import { buildStageBranchRunner } from "./wave/stage-branch-runner.js";
12
+ /**
13
+ * Best-effort advisory cost-per-branch estimate, when a budget ceiling is set:
14
+ * spend-so-far divided by iterations completed so far (floored at a small
15
+ * constant so a still-cheap early run does not admit an unbounded wave).
16
+ * Command backends that never journal `backend.usage` report zero spend, so
17
+ * the ceiling is a no-op for them — documented as advisory/best-effort.
18
+ */
19
+ function perBranchCostEstimate(loop, iteration) {
20
+ const spent = runCostUsd(loop);
21
+ if (spent <= 0)
22
+ return 0;
23
+ return Math.max(spent / Math.max(1, iteration), 0.01);
24
+ }
25
+ /**
26
+ * Which branches the budget ceiling admits this wave. When no ceiling is set
27
+ * (`maxCostUsd` is 0), every branch is admitted — width is bounded only by the
28
+ * concurrency semaphore. When a ceiling is set, branches are admitted up to
29
+ * what the remaining budget covers at the per-branch estimate; the rest are
30
+ * skipped (never launched) rather than launched and killed mid-flight —
31
+ * "stop admitting past the line, overshoot bounded by one wave".
32
+ */
33
+ function admitByBudget(loop, iter, specs) {
34
+ const ceiling = loop.limits.maxCostUsd ?? 0;
35
+ if (ceiling <= 0)
36
+ return new Set(specs.map((s) => s.branchId));
37
+ const spent = runCostUsd(loop);
38
+ const remaining = Math.max(ceiling - spent, 0);
39
+ const perBranch = perBranchCostEstimate(loop, iter.iteration);
40
+ if (perBranch <= 0)
41
+ return new Set(specs.map((s) => s.branchId));
42
+ const admitted = Math.max(0, Math.floor(remaining / perBranch));
43
+ return new Set(specs.slice(0, admitted).map((s) => s.branchId));
44
+ }
45
+ /**
46
+ * Reconstruct previously-completed branch results for `stageId` from the
47
+ * journal's `stage.branch.finish` records (any prior attempt in this run,
48
+ * across iterations — a stage that was mid-flight when the run was
49
+ * interrupted leaves these records behind). The latest record per branch id
50
+ * wins. Empty when resume is disabled or nothing was journaled yet.
51
+ */
52
+ export function loadResumedBranches(loop, stageId) {
53
+ const resumed = new Map();
54
+ if (loop.runtime.noResume)
55
+ return resumed;
56
+ const lines = readRunLines(loop.paths.journalFile, loop.runtime.runId);
57
+ for (const line of lines) {
58
+ if (extractTopic(line) !== "stage.branch.finish")
59
+ continue;
60
+ if (extractField(line, "stage_id") !== stageId)
61
+ continue;
62
+ const branchId = extractField(line, "branch_id");
63
+ if (!branchId)
64
+ continue;
65
+ const ok = extractField(line, "ok") === "true";
66
+ const rawData = extractField(line, "data");
67
+ let data;
68
+ if (ok && rawData) {
69
+ try {
70
+ data = JSON.parse(rawData);
71
+ }
72
+ catch {
73
+ data = undefined;
74
+ }
75
+ }
76
+ const error = extractField(line, "error");
77
+ resumed.set(branchId, {
78
+ branchId,
79
+ ok: ok && data !== undefined,
80
+ ...(data ? { data } : {}),
81
+ ...(error ? { error } : {}),
82
+ });
83
+ }
84
+ return resumed;
85
+ }
86
+ /**
87
+ * Run a fan-out stage triggered by `emittedTopic`, reduce its branch results
88
+ * to a routing decision, journal it, and continue the loop at the outcome
89
+ * event. `stage.branch.finish` records (real launches only — resumed/
90
+ * budget-skipped branches are not re-journaled) let a later attempt resume
91
+ * this exact stage without relaunching branches that already finished.
92
+ */
93
+ export async function finishStageIteration(loop, iter, stage, emittedTopic, iterate) {
94
+ appendEvent(loop.paths.journalFile, loop.runtime.runId, String(iter.iteration), "stage.start", jsonField("stage_id", stage.id) +
95
+ ", " +
96
+ jsonField("trigger_topic", emittedTopic) +
97
+ ", " +
98
+ jsonField("branch_count", String(expandStageBranches(stage).length)));
99
+ const resumed = loadResumedBranches(loop, stage.id);
100
+ const specs = expandStageBranches(stage);
101
+ const toAdmit = specs.filter((s) => !resumed.has(s.branchId));
102
+ const admitted = admitByBudget(loop, iter, toAdmit);
103
+ const baseRunner = buildStageBranchRunner(loop, iter, stage.id);
104
+ const runner = async (spec) => {
105
+ const cached = resumed.get(spec.branchId);
106
+ if (cached)
107
+ return cached;
108
+ if (!admitted.has(spec.branchId)) {
109
+ return {
110
+ branchId: spec.branchId,
111
+ ok: false,
112
+ error: "budget: not admitted this wave",
113
+ };
114
+ }
115
+ return baseRunner(spec);
116
+ };
117
+ const concurrency = Math.max(1, loop.stage?.concurrency || 1);
118
+ const outcome = await runFanoutStage(stage, runner, concurrency);
119
+ appendEvent(loop.paths.journalFile, loop.runtime.runId, String(iter.iteration), "stage.join", jsonField("stage_id", stage.id) +
120
+ ", " +
121
+ jsonField("event", outcome.event) +
122
+ ", " +
123
+ jsonField("passed", String(outcome.passed)) +
124
+ ", " +
125
+ jsonField("reason", outcome.reason) +
126
+ ", " +
127
+ jsonField("resumed_branches", String(resumed.size)));
128
+ appendHarnessEvent(loop.paths.journalFile, loop.runtime.runId, String(iter.iteration), outcome.event, outcome.reason);
129
+ return iterate(loop, iter.iteration + 1);
130
+ }
131
+ //# sourceMappingURL=stage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage.js","sourceRoot":"","sources":["../src/stage.ts"],"names":[],"mappings":"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"}
package/dist/stop.d.ts CHANGED
@@ -1,8 +1,35 @@
1
- import type { LoopContext, RunSummary } from "./types.js";
1
+ import type { LoopContext, RunSummary, StopReason } from "./types.js";
2
2
  export declare function stopMaxIterations(loop: LoopContext, iteration: number): RunSummary;
3
3
  export declare function stopBackendFailed(loop: LoopContext, iteration: number, output: string): RunSummary;
4
+ /**
5
+ * Stop with a typed backend-error reason (rate_limited / quota_exhausted /
6
+ * auth_failed / transient_error) instead of a generic backend_failed, so the
7
+ * disposition reflects an availability/auth/quota issue rather than an agent
8
+ * failure — and downstream (retry ladder, quarantine) can key off it.
9
+ */
10
+ export declare function stopBackendErrorClass(loop: LoopContext, iteration: number, reason: StopReason, output: string): RunSummary;
4
11
  export declare function stopBackendTimeout(loop: LoopContext, iteration: number, output: string): RunSummary;
5
12
  export declare function stopStalled(loop: LoopContext, completed: number, repeats: number): RunSummary;
6
13
  export declare function stopCostBudget(loop: LoopContext, completed: number, costUsd: number, maxCostUsd: number): RunSummary;
7
14
  export declare function stopMaxRuntime(loop: LoopContext, completed: number, elapsedMs: number, maxRuntimeMs: number, outputTail?: string): RunSummary;
8
- export declare function completeLoop(loop: LoopContext, iteration: number, reason: string): RunSummary;
15
+ /**
16
+ * Fail-closed stop for an UNKNOWN metareview verdict under `on_error = hold`.
17
+ * The reviewer gave no trustworthy signal, so the loop halts and raises
18
+ * attention (failure.diagnostic + notify-class "failed") instead of silently
19
+ * advancing — a human decides whether to resume.
20
+ */
21
+ export declare function stopReviewUnknown(loop: LoopContext, iteration: number, reasoning: string): RunSummary;
22
+ /**
23
+ * Escalate a premature quit that exhausted its re-arm budget: stop with a
24
+ * distinct reason and raise attention rather than silently accepting a
25
+ * false-finish that left authorized work undone.
26
+ */
27
+ export declare function stopPrematureQuit(loop: LoopContext, completed: number, reasons: string[]): RunSummary;
28
+ /**
29
+ * Stop for a `suspend`-policy hook that fired and could not (or was not
30
+ * configured to) block in-process. Durable suspend state has already been
31
+ * written by the hooks engine (`writeSuspendState`); this only journals the
32
+ * typed stop reason and registry status so `autoloop resume` can pick it up.
33
+ */
34
+ export declare function stopSuspended(loop: LoopContext, iteration: number, reason: string): RunSummary;
35
+ export declare function completeLoop(loop: LoopContext, iteration: number, reason: StopReason): RunSummary;