@mobrienv/autoloop-harness 0.7.4 → 0.9.1
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/dist/acceptance.d.ts +31 -0
- package/dist/acceptance.js +69 -0
- package/dist/acceptance.js.map +1 -0
- package/dist/ask.d.ts +16 -0
- package/dist/ask.js +47 -0
- package/dist/ask.js.map +1 -0
- package/dist/backend/types.d.ts +16 -0
- package/dist/backend/types.js +17 -0
- package/dist/backend/types.js.map +1 -0
- package/dist/circuit-breaker.d.ts +27 -0
- package/dist/circuit-breaker.js +41 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/completion-lint.d.ts +29 -0
- package/dist/completion-lint.js +51 -0
- package/dist/completion-lint.js.map +1 -0
- package/dist/config-helpers.d.ts +10 -2
- package/dist/config-helpers.js +272 -30
- package/dist/config-helpers.js.map +1 -1
- package/dist/control/acp-adapter.d.ts +12 -0
- package/dist/control/acp-adapter.js +54 -0
- package/dist/control/acp-adapter.js.map +1 -0
- package/dist/control/adapter.d.ts +12 -0
- package/dist/control/adapter.js +2 -0
- package/dist/control/adapter.js.map +1 -0
- package/dist/control/capabilities.d.ts +9 -0
- package/dist/control/capabilities.js +27 -0
- package/dist/control/capabilities.js.map +1 -0
- package/dist/control/claude-sdk-adapter.d.ts +15 -0
- package/dist/control/claude-sdk-adapter.js +79 -0
- package/dist/control/claude-sdk-adapter.js.map +1 -0
- package/dist/control/command-adapter.d.ts +19 -0
- package/dist/control/command-adapter.js +57 -0
- package/dist/control/command-adapter.js.map +1 -0
- package/dist/control/dispatch.d.ts +14 -0
- package/dist/control/dispatch.js +55 -0
- package/dist/control/dispatch.js.map +1 -0
- package/dist/control/index.d.ts +11 -0
- package/dist/control/index.js +9 -0
- package/dist/control/index.js.map +1 -0
- package/dist/control/kiro-adapter.d.ts +2 -0
- package/dist/control/kiro-adapter.js +2 -0
- package/dist/control/kiro-adapter.js.map +1 -0
- package/dist/control/paths.d.ts +19 -0
- package/dist/control/paths.js +40 -0
- package/dist/control/paths.js.map +1 -0
- package/dist/control/pi-adapter.d.ts +15 -0
- package/dist/control/pi-adapter.js +76 -0
- package/dist/control/pi-adapter.js.map +1 -0
- package/dist/control/queue.d.ts +14 -0
- package/dist/control/queue.js +82 -0
- package/dist/control/queue.js.map +1 -0
- package/dist/control/render.d.ts +10 -0
- package/dist/control/render.js +53 -0
- package/dist/control/render.js.map +1 -0
- package/dist/control/types.d.ts +56 -0
- package/dist/control/types.js +9 -0
- package/dist/control/types.js.map +1 -0
- package/dist/display.d.ts +9 -2
- package/dist/display.js +37 -1
- package/dist/display.js.map +1 -1
- package/dist/emit.d.ts +27 -0
- package/dist/emit.js +327 -6
- package/dist/emit.js.map +1 -1
- package/dist/events.d.ts +36 -3
- package/dist/fanout-runner.d.ts +25 -0
- package/dist/fanout-runner.js +54 -0
- package/dist/fanout-runner.js.map +1 -0
- package/dist/file-mod-audit.d.ts +29 -0
- package/dist/file-mod-audit.js +63 -0
- package/dist/file-mod-audit.js.map +1 -0
- package/dist/git-diff.d.ts +19 -0
- package/dist/git-diff.js +74 -0
- package/dist/git-diff.js.map +1 -0
- package/dist/guards.d.ts +37 -0
- package/dist/guards.js +78 -0
- package/dist/guards.js.map +1 -0
- package/dist/hooks.d.ts +78 -0
- package/dist/hooks.js +239 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +359 -26
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +45 -0
- package/dist/intent.js +127 -0
- package/dist/intent.js.map +1 -0
- package/dist/iteration-diff.d.ts +32 -0
- package/dist/iteration-diff.js +92 -0
- package/dist/iteration-diff.js.map +1 -0
- package/dist/iteration.d.ts +20 -0
- package/dist/iteration.js +551 -30
- package/dist/iteration.js.map +1 -1
- package/dist/metareview.d.ts +9 -1
- package/dist/metareview.js +158 -34
- package/dist/metareview.js.map +1 -1
- package/dist/notify.d.ts +22 -0
- package/dist/notify.js +110 -0
- package/dist/notify.js.map +1 -0
- package/dist/parallel.d.ts +17 -0
- package/dist/parallel.js +38 -6
- package/dist/parallel.js.map +1 -1
- package/dist/postconditions.d.ts +25 -0
- package/dist/postconditions.js +107 -0
- package/dist/postconditions.js.map +1 -0
- package/dist/postfire-verify.d.ts +31 -0
- package/dist/postfire-verify.js +64 -0
- package/dist/postfire-verify.js.map +1 -0
- package/dist/premature-quit.d.ts +25 -0
- package/dist/premature-quit.js +54 -0
- package/dist/premature-quit.js.map +1 -0
- package/dist/progress.d.ts +20 -0
- package/dist/progress.js +56 -0
- package/dist/progress.js.map +1 -0
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.js +42 -2
- package/dist/prompt.js.map +1 -1
- package/dist/provisional.d.ts +42 -0
- package/dist/provisional.js +92 -0
- package/dist/provisional.js.map +1 -0
- package/dist/registry-bridge.js +32 -1
- package/dist/registry-bridge.js.map +1 -1
- package/dist/resume.d.ts +61 -0
- package/dist/resume.js +226 -0
- package/dist/resume.js.map +1 -0
- package/dist/scratchpad.js +27 -8
- package/dist/scratchpad.js.map +1 -1
- package/dist/stage.d.ts +21 -0
- package/dist/stage.js +131 -0
- package/dist/stage.js.map +1 -0
- package/dist/stop.d.ts +32 -2
- package/dist/stop.js +169 -0
- package/dist/stop.js.map +1 -1
- package/dist/suspend-state.d.ts +32 -0
- package/dist/suspend-state.js +104 -0
- package/dist/suspend-state.js.map +1 -0
- package/dist/tamper.d.ts +21 -0
- package/dist/tamper.js +88 -0
- package/dist/tamper.js.map +1 -0
- package/dist/types.d.ts +294 -3
- package/dist/types.js +91 -1
- package/dist/types.js.map +1 -1
- package/dist/wave/finalize-wave.d.ts +15 -5
- package/dist/wave/finalize-wave.js +56 -2
- package/dist/wave/finalize-wave.js.map +1 -1
- package/dist/wave/launch-branches.d.ts +23 -2
- package/dist/wave/launch-branches.js +58 -3
- package/dist/wave/launch-branches.js.map +1 -1
- package/dist/wave/stage-branch-runner.d.ts +12 -0
- package/dist/wave/stage-branch-runner.js +130 -0
- package/dist/wave/stage-branch-runner.js.map +1 -0
- package/dist/wave/types.d.ts +40 -2
- package/dist/wave.d.ts +32 -1
- package/dist/wave.js +163 -34
- package/dist/wave.js.map +1 -1
- package/package.json +131 -3
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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"}
|
package/dist/scratchpad.js
CHANGED
|
@@ -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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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=" +
|
package/dist/scratchpad.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scratchpad.js","sourceRoot":"","sources":["../src/scratchpad.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,WAAW,EACX,OAAO,EACP,OAAO,GACR,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"scratchpad.js","sourceRoot":"","sources":["../src/scratchpad.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,WAAW,EACX,OAAO,EACP,OAAO,GACR,MAAM,yBAAyB,CAAC;AAiBjC,MAAM,UAAU,uBAAuB,CAAC,KAAe;IACrD,MAAM,OAAO,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAChD,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,KAAe;IACvD,MAAM,OAAO,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAChD,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjE,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAE3C,OAAO,CACL,OAAO,CAAC,CAAC,EAAE,gCAAgC,CAAC;QAC5C,MAAM;QACN,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QAChD,MAAM;QACN,uBAAuB,CAAC,MAAM,CAAC,CAChC,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAe;IAC/C,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,QAAQ;YAAE,SAAS;QACjD,IAAI,KAAK,CAAC,KAAK,KAAK,kBAAkB,EAAE,CAAC;YACvC,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,WAAW;gBACjB,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE;gBAChC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE;gBACtC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE;aAClC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,KAAK,CAAC,KAAK,KAAK,aAAa,EAAE,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,QAAQ;gBACd,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAAC,oBAAoB,IAAI,EAAE;gBAC3D,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,cAAc,IAAI,EAAE;aACjD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,uBAAuB,CAAC,OAA0B;IACzD,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAsB;IACjD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,MAAM,CAAC;IAC1C,CAAC;IACD,OAAO,CACL,OAAO,CAAC,CAAC,EAAE,aAAa,KAAK,CAAC,SAAS,EAAE,CAAC;QAC1C,MAAM;QACN,YAAY;QACZ,KAAK,CAAC,QAAQ;QACd,MAAM;QACN,KAAK,CAAC,MAAM;QACZ,IAAI,CACL,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAmB;IAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,kBAAkB,IAAI,SAAS,CAAC;IACrD,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,IAAI,GAAG,CAAC;IAC1C,OAAO,qBAAqB,MAAM,YAAY,MAAM,kBAAkB,CAAC;AACzE,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAsB;IACnD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,CACL,YAAY;QACZ,KAAK,CAAC,SAAS;QACf,aAAa;QACb,KAAK,CAAC,QAAQ;QACd,KAAK;QACL,sBAAsB,CAAC,KAAK,CAAC,CAC9B,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAqB;IACnD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YACnB,OAAO,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC;QACzE,CAAC;IACH,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC"}
|
package/dist/stage.d.ts
ADDED
|
@@ -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,5 +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
|
-
export declare function
|
|
12
|
+
export declare function stopStalled(loop: LoopContext, completed: number, repeats: number): RunSummary;
|
|
13
|
+
export declare function stopCostBudget(loop: LoopContext, completed: number, costUsd: number, maxCostUsd: number): RunSummary;
|
|
14
|
+
export declare function stopMaxRuntime(loop: LoopContext, completed: number, elapsedMs: number, maxRuntimeMs: number, outputTail?: 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;
|