@rasensio/aidlc 1.63.0 → 1.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/autopilot/driver.d.ts +52 -1
- package/dist/autopilot/driver.d.ts.map +1 -1
- package/dist/autopilot/driver.js +170 -51
- package/dist/autopilot/driver.js.map +1 -1
- package/dist/autopilot/notify.d.ts +33 -0
- package/dist/autopilot/notify.d.ts.map +1 -0
- package/dist/autopilot/notify.js +66 -0
- package/dist/autopilot/notify.js.map +1 -0
- package/dist/autopilot/presets.d.ts +2 -0
- package/dist/autopilot/presets.d.ts.map +1 -1
- package/dist/autopilot/presets.js +20 -2
- package/dist/autopilot/presets.js.map +1 -1
- package/dist/autopilot/run.d.ts +14 -1
- package/dist/autopilot/run.d.ts.map +1 -1
- package/dist/autopilot/run.js +80 -9
- package/dist/autopilot/run.js.map +1 -1
- package/dist/autopilot/state.d.ts +24 -0
- package/dist/autopilot/state.d.ts.map +1 -1
- package/dist/autopilot/state.js +10 -1
- package/dist/autopilot/state.js.map +1 -1
- package/dist/autopilot/team.d.ts +2 -1
- package/dist/autopilot/team.d.ts.map +1 -1
- package/dist/autopilot/team.js +6 -1
- package/dist/autopilot/team.js.map +1 -1
- package/dist/autopilot/watch.d.ts +58 -0
- package/dist/autopilot/watch.d.ts.map +1 -0
- package/dist/autopilot/watch.js +165 -0
- package/dist/autopilot/watch.js.map +1 -0
- package/dist/commands/autopilot.d.ts +10 -4
- package/dist/commands/autopilot.d.ts.map +1 -1
- package/dist/commands/autopilot.js +42 -16
- package/dist/commands/autopilot.js.map +1 -1
- package/package.json +2 -2
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
*
|
|
16
16
|
* @module
|
|
17
17
|
*/
|
|
18
|
-
import type {
|
|
18
|
+
import type { Notice } from './notify.js';
|
|
19
|
+
import type { AutopilotConfig, AutopilotItem, AutopilotPaths, AutopilotState, ItemStep } from './state.js';
|
|
19
20
|
import { type PausePhase, type Stops } from './stops.js';
|
|
20
21
|
export interface RunResult {
|
|
21
22
|
status: number;
|
|
@@ -23,6 +24,16 @@ export interface RunResult {
|
|
|
23
24
|
stderr: string;
|
|
24
25
|
dry?: boolean;
|
|
25
26
|
}
|
|
27
|
+
/** How a spawned agent ended, as seen by the process that spawned it. */
|
|
28
|
+
export interface AgentExit {
|
|
29
|
+
/** null when a signal ended it. */
|
|
30
|
+
code: number | null;
|
|
31
|
+
signal: string | null;
|
|
32
|
+
/** From spawn to exit. */
|
|
33
|
+
ms: number;
|
|
34
|
+
/** At most 4 KB of what this launch wrote to the item's log — never an earlier launch's (codex-preset-removed-flag/AC-8). */
|
|
35
|
+
output: string;
|
|
36
|
+
}
|
|
26
37
|
export interface DriverCtx {
|
|
27
38
|
/** Primary checkout — where merges and releases happen. */
|
|
28
39
|
repo: string;
|
|
@@ -49,12 +60,22 @@ export interface DriverCtx {
|
|
|
49
60
|
boot: string | null;
|
|
50
61
|
ns: string | null;
|
|
51
62
|
} | null;
|
|
63
|
+
/**
|
|
64
|
+
* How `pid` ended, or null when this process did not spawn it or it has not ended.
|
|
65
|
+
* Absent is the same as null: the pass cannot tell (codex-preset-removed-flag/AC-11).
|
|
66
|
+
*/
|
|
67
|
+
exited?(pid: number): AgentExit | null;
|
|
52
68
|
kill(pid: number): void;
|
|
53
69
|
fs: {
|
|
54
70
|
read(path: string): string | null;
|
|
55
71
|
exists(path: string): boolean;
|
|
56
72
|
write(path: string, text: string): void;
|
|
57
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Tell a person something needs them or is done. Absent sends nothing: only a
|
|
76
|
+
* background run sets it (autopilot-live-progress/AC-20, AC-23).
|
|
77
|
+
*/
|
|
78
|
+
notify?(notice: Notice): void;
|
|
58
79
|
}
|
|
59
80
|
/** How many builds a run may still start, and what to tell it (autopilot-on-demand/AC-7). */
|
|
60
81
|
export interface Budget {
|
|
@@ -85,6 +106,20 @@ export declare function conflictPrompt(item: {
|
|
|
85
106
|
}): string;
|
|
86
107
|
/** The stops an item is held to: those recorded when it started (autopilot-stop-controls/AC-6). */
|
|
87
108
|
export declare function itemStops(item: AutopilotItem): Stops;
|
|
109
|
+
/**
|
|
110
|
+
* A non-zero exit sooner than this after launch is a launch failure, not a failed
|
|
111
|
+
* build. Argument and startup errors exit in well under a second; a session that
|
|
112
|
+
* fails later prints a banner first, which names nothing (codex-preset-removed-flag/AC-6, AC-10).
|
|
113
|
+
*/
|
|
114
|
+
export declare const LAUNCH_FAILURE_MS = 30000;
|
|
115
|
+
/**
|
|
116
|
+
* The first non-blank line of `text`, terminal escapes and control characters removed,
|
|
117
|
+
* at most 200 characters; null when there is none (codex-preset-removed-flag/AC-12).
|
|
118
|
+
* The first line, not `fail`'s last: for an argument error the last is the usage line.
|
|
119
|
+
*/
|
|
120
|
+
export declare function firstOutputLine(text: string): string | null;
|
|
121
|
+
/** The instance's `current_phase` in its worktree, or null when unreadable. Shared with the view. */
|
|
122
|
+
export declare function instancePhase(read: (path: string) => string | null, item: Pick<AutopilotItem, 'name' | 'worktree'>): string | null;
|
|
88
123
|
/**
|
|
89
124
|
* Return every item whose failure is resumable to the step it failed in, and
|
|
90
125
|
* clear the error. Called when a run starts: asking to run is asking to retry
|
|
@@ -112,5 +147,21 @@ export type ApproveOutcome = {
|
|
|
112
147
|
export declare function approveItem(state: AutopilotState, name: string): ApproveOutcome;
|
|
113
148
|
/** One pass. Returns the new state; the caller saves it (not in a dry run). */
|
|
114
149
|
export declare function tick(ctx: DriverCtx, state: AutopilotState, budget?: Budget): AutopilotState;
|
|
150
|
+
/** Each item's step by name, to compare against after a change (autopilot-live-progress/AC-11). */
|
|
151
|
+
export declare function stepsOf(state: AutopilotState): Map<string, ItemStep>;
|
|
152
|
+
/**
|
|
153
|
+
* Stamp `step_since` on every item whose step differs from `before`; a new item has
|
|
154
|
+
* no entry there and is stamped too. Diffing here, rather than at each of the
|
|
155
|
+
* fourteen places a step is set, means a new handler cannot forget it.
|
|
156
|
+
*/
|
|
157
|
+
export declare function markStepChanges(state: AutopilotState, before: Map<string, ItemStep>, now: number): void;
|
|
158
|
+
/**
|
|
159
|
+
* The driver's line, shared by `status` and the watch view.
|
|
160
|
+
*
|
|
161
|
+
* @param stopped - whether the STOP file exists.
|
|
162
|
+
* @param lastPass - how to show the last pass's time; `status` shows it as stored.
|
|
163
|
+
*/
|
|
164
|
+
export declare function driverLine(stopped: boolean, state: AutopilotState, now: number, lastPass?: (iso: string) => string): string;
|
|
165
|
+
export declare function queueLine(state: AutopilotState): string | null;
|
|
115
166
|
export declare function statusLines(ctx: Pick<DriverCtx, 'fs' | 'paths' | 'now'>, state: AutopilotState): string[];
|
|
116
167
|
//# sourceMappingURL=driver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/autopilot/driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/autopilot/driver.ts"],"names":[],"mappings":"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"}
|
package/dist/autopilot/driver.js
CHANGED
|
@@ -96,18 +96,49 @@ export function conflictPrompt(item) {
|
|
|
96
96
|
`\`git fetch origin main\` and \`git merge origin/main\` on this branch, resolve every conflict keeping ` +
|
|
97
97
|
`the intent of both sides, run the tests, commit the merge, and stop. Do not push, merge or release.`);
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Record `text` as the item's last event for the view. The same text again keeps
|
|
101
|
+
* the time it was first said: "waiting for checks to start, 12m ago"
|
|
102
|
+
* (autopilot-live-progress/AC-12).
|
|
103
|
+
*/
|
|
104
|
+
function record(ctx, item, text) {
|
|
105
|
+
if (item.last_event?.text === text)
|
|
106
|
+
return;
|
|
107
|
+
item.last_event = { at: new Date(ctx.now()).toISOString(), text };
|
|
108
|
+
}
|
|
109
|
+
/** Say a line about an item — the same line as ever — and record it. */
|
|
110
|
+
function tell(ctx, item, text) {
|
|
111
|
+
ctx.say(`${item.name}: ${text}`);
|
|
112
|
+
record(ctx, item, text);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Notices go out at a transition, never from an observation a pass repeats, so a
|
|
116
|
+
* queue halted for five hours notifies once with no record of what was sent
|
|
117
|
+
* (autopilot-live-progress/AC-16).
|
|
118
|
+
*/
|
|
119
|
+
function notice(ctx, title, message) {
|
|
120
|
+
ctx.notify?.({ title: `autopilot: ${title}`, message });
|
|
121
|
+
}
|
|
99
122
|
/**
|
|
100
123
|
* Put an item in `error`. `resume` names the step `aidlc autopilot run` returns
|
|
101
124
|
* it to; leave it out for a failure at or after the merge, which stays a
|
|
102
125
|
* person's call (autopilot-merge-lane-stalls-on-conflicts/AC-14, AC-16).
|
|
126
|
+
*
|
|
127
|
+
* Never reached for an item already in `error` — handlers are picked by step and
|
|
128
|
+
* `serveQueue` returns on an errored head — so this notifies once per failure
|
|
129
|
+
* (autopilot-live-progress/AC-15).
|
|
103
130
|
*/
|
|
104
|
-
function failItem(item, error, resume) {
|
|
131
|
+
function failItem(ctx, state, item, error, resume) {
|
|
105
132
|
item.step = 'error';
|
|
106
133
|
item.error = error;
|
|
107
134
|
if (resume)
|
|
108
135
|
item.resume_step = resume;
|
|
109
136
|
else
|
|
110
137
|
delete item.resume_step;
|
|
138
|
+
// Recorded, not said: half the callers print nothing today, and what is printed must not change.
|
|
139
|
+
record(ctx, item, error);
|
|
140
|
+
const title = error.startsWith('release failed') ? 'release failed' : state.queue.includes(item.name) ? 'queue halted' : 'item stopped';
|
|
141
|
+
notice(ctx, title, `${item.name}: ${error}`);
|
|
111
142
|
}
|
|
112
143
|
function dequeue(state, item) {
|
|
113
144
|
state.queue = state.queue.filter((n) => n !== item.name);
|
|
@@ -121,6 +152,37 @@ export function itemStops(item) {
|
|
|
121
152
|
return item.stops ?? LEGACY_STOPS;
|
|
122
153
|
}
|
|
123
154
|
const itemPauses = (item) => pauseEnv(itemStops(item), item.approved ?? []);
|
|
155
|
+
/**
|
|
156
|
+
* A non-zero exit sooner than this after launch is a launch failure, not a failed
|
|
157
|
+
* build. Argument and startup errors exit in well under a second; a session that
|
|
158
|
+
* fails later prints a banner first, which names nothing (codex-preset-removed-flag/AC-6, AC-10).
|
|
159
|
+
*/
|
|
160
|
+
export const LAUNCH_FAILURE_MS = 30_000;
|
|
161
|
+
const REASON_MAX = 200;
|
|
162
|
+
/**
|
|
163
|
+
* The first non-blank line of `text`, terminal escapes and control characters removed,
|
|
164
|
+
* at most 200 characters; null when there is none (codex-preset-removed-flag/AC-12).
|
|
165
|
+
* The first line, not `fail`'s last: for an argument error the last is the usage line.
|
|
166
|
+
*/
|
|
167
|
+
export function firstOutputLine(text) {
|
|
168
|
+
const plain = text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '').replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '');
|
|
169
|
+
const line = plain.split('\n').map((l) => l.trim()).find((l) => l !== '');
|
|
170
|
+
return line === undefined ? null : line.slice(0, REASON_MAX);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The park reason for an agent that failed to launch, or null when it did not or the
|
|
174
|
+
* pass cannot tell. Its first line, not its stderr: both streams share the log by
|
|
175
|
+
* design, and an argument error is printed before anything else
|
|
176
|
+
* (codex-preset-removed-flag/AC-6, AC-7, AC-11).
|
|
177
|
+
*/
|
|
178
|
+
function launchFailure(ctx, item) {
|
|
179
|
+
const exit = item.pid === null ? null : ctx.exited?.(item.pid) ?? null;
|
|
180
|
+
if (exit === null || exit.code === 0 || exit.ms >= LAUNCH_FAILURE_MS)
|
|
181
|
+
return null;
|
|
182
|
+
const how = exit.code === null ? `signal ${exit.signal ?? 'unknown'}` : `exit ${exit.code}`;
|
|
183
|
+
const line = firstOutputLine(exit.output);
|
|
184
|
+
return line === null ? `agent failed to launch (${how}) with no output` : `agent failed to launch (${how}): ${line}`;
|
|
185
|
+
}
|
|
124
186
|
function launchAgent(ctx, item, prompt) {
|
|
125
187
|
const command = ctx.config?.agent_command ?? [];
|
|
126
188
|
const argv = command.map((a) => (a === '{prompt}' ? prompt : a));
|
|
@@ -142,7 +204,7 @@ function launchAgent(ctx, item, prompt) {
|
|
|
142
204
|
item.pid_boot = id.boot;
|
|
143
205
|
item.pid_ns = id.ns;
|
|
144
206
|
}
|
|
145
|
-
ctx
|
|
207
|
+
tell(ctx, item, `agent started (pid ${item.pid})`);
|
|
146
208
|
}
|
|
147
209
|
function drop(state, item, budget) {
|
|
148
210
|
state.items = state.items.filter((i) => i !== item);
|
|
@@ -152,18 +214,23 @@ function drop(state, item, budget) {
|
|
|
152
214
|
function park(ctx, io, state, item, reason, budget) {
|
|
153
215
|
const r = io.cli(['roadmap', 'park', item.name, '--reason', reason]);
|
|
154
216
|
if (r.status !== 0) {
|
|
155
|
-
failItem(item, `park failed: ${fail(r)}`);
|
|
156
|
-
ctx
|
|
217
|
+
failItem(ctx, state, item, `park failed: ${fail(r)}`);
|
|
218
|
+
tell(ctx, item, `could not park — ${item.error}`);
|
|
157
219
|
return;
|
|
158
220
|
}
|
|
159
221
|
io.cli(['release', item.name]);
|
|
160
|
-
ctx
|
|
222
|
+
tell(ctx, item, `parked — ${reason}`);
|
|
223
|
+
notice(ctx, 'item parked', `${item.name}: ${reason}`);
|
|
161
224
|
drop(state, item, budget);
|
|
162
225
|
}
|
|
163
|
-
|
|
164
|
-
|
|
226
|
+
/** The instance's `current_phase` in its worktree, or null when unreadable. Shared with the view. */
|
|
227
|
+
export function instancePhase(read, item) {
|
|
228
|
+
const yaml = read(`${item.worktree}/.aidlc/state/${item.name}/instance.yaml`) ?? '';
|
|
165
229
|
return /^current_phase:\s*(\S+)/m.exec(yaml)?.[1] ?? null;
|
|
166
230
|
}
|
|
231
|
+
function currentPhase(ctx, item) {
|
|
232
|
+
return instancePhase(ctx.fs.read, item);
|
|
233
|
+
}
|
|
167
234
|
/** A phase file's own top-level status, not an artifact's indented one. */
|
|
168
235
|
const COMPLETE_RE = /^status:\s*complete\s*$/m;
|
|
169
236
|
function phaseComplete(ctx, item, phase) {
|
|
@@ -181,7 +248,8 @@ function awaitIfPaused(ctx, item) {
|
|
|
181
248
|
item.step = 'awaiting-review';
|
|
182
249
|
item.review = phase;
|
|
183
250
|
item.pid = null;
|
|
184
|
-
ctx
|
|
251
|
+
tell(ctx, item, `paused after ${phase} for your review — \`aidlc autopilot approve ${item.name}\` carries it on`);
|
|
252
|
+
notice(ctx, 'review needed', `${item.name}: paused after ${phase} — aidlc autopilot approve ${item.name}`);
|
|
185
253
|
return true;
|
|
186
254
|
}
|
|
187
255
|
function advanceBuilding(ctx, io, state, item, budget) {
|
|
@@ -205,18 +273,26 @@ function advanceBuilding(ctx, io, state, item, budget) {
|
|
|
205
273
|
return;
|
|
206
274
|
}
|
|
207
275
|
if (ctx.fs.exists(`${ctx.repo}/.aidlc/roadmap/hold/${item.file}`)) {
|
|
208
|
-
ctx
|
|
276
|
+
tell(ctx, item, 'the agent parked it');
|
|
277
|
+
notice(ctx, 'item parked', `${item.name}: parked by its agent — the reason is in the item's history`);
|
|
209
278
|
drop(state, item, budget);
|
|
210
279
|
return;
|
|
211
280
|
}
|
|
212
281
|
if (awaitIfPaused(ctx, item))
|
|
213
282
|
return;
|
|
283
|
+
// Before the phase: a relaunch that never started would otherwise push and open
|
|
284
|
+
// the pull request as if it had finished (codex-preset-removed-flag/AC-9).
|
|
285
|
+
const launch = launchFailure(ctx, item);
|
|
286
|
+
if (launch !== null) {
|
|
287
|
+
park(ctx, io, state, item, launch, budget);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
214
290
|
const phase = currentPhase(ctx, item);
|
|
215
291
|
if (phase !== 'deployment') {
|
|
216
292
|
park(ctx, io, state, item, `agent exited before the release boundary (phase ${phase ?? 'unknown'})`, budget);
|
|
217
293
|
return;
|
|
218
294
|
}
|
|
219
|
-
openPullRequest(ctx, io, item);
|
|
295
|
+
openPullRequest(ctx, io, state, item);
|
|
220
296
|
}
|
|
221
297
|
/**
|
|
222
298
|
* The pass after `aidlc autopilot approve`: wait again at the next completed pause,
|
|
@@ -228,7 +304,7 @@ function advanceApproved(ctx, io, state, item) {
|
|
|
228
304
|
return;
|
|
229
305
|
if (currentPhase(ctx, item) === 'deployment') {
|
|
230
306
|
item.step = 'building';
|
|
231
|
-
openPullRequest(ctx, io, item);
|
|
307
|
+
openPullRequest(ctx, io, state, item);
|
|
232
308
|
return;
|
|
233
309
|
}
|
|
234
310
|
// A relaunch is a build like any other: it waits for a slot, ahead of new items,
|
|
@@ -236,26 +312,26 @@ function advanceApproved(ctx, io, state, item) {
|
|
|
236
312
|
if (building(state) >= buildSlots(ctx))
|
|
237
313
|
return;
|
|
238
314
|
item.step = 'building';
|
|
239
|
-
ctx
|
|
315
|
+
tell(ctx, item, 'approved — relaunching the agent to carry on');
|
|
240
316
|
launchAgent(ctx, item, agentPrompt(item, itemPauses(item)));
|
|
241
317
|
}
|
|
242
|
-
function openPullRequest(ctx, io, item) {
|
|
318
|
+
function openPullRequest(ctx, io, state, item) {
|
|
243
319
|
const push = io.call('git', ['push', '-u', 'origin', item.branch], { cwd: item.worktree });
|
|
244
320
|
if (push.status !== 0) {
|
|
245
|
-
failItem(item, `push failed: ${fail(push)}`, 'building');
|
|
321
|
+
failItem(ctx, state, item, `push failed: ${fail(push)}`, 'building');
|
|
246
322
|
return;
|
|
247
323
|
}
|
|
248
324
|
if (!item.pr) {
|
|
249
325
|
const pr = io.call('gh', ['pr', 'create', '--base', 'main', '--head', item.branch, '--fill'], { cwd: item.worktree });
|
|
250
326
|
const n = /\/pull\/(\d+)/.exec(pr.stdout)?.[1];
|
|
251
327
|
if (pr.status !== 0 || (!n && !pr.dry)) {
|
|
252
|
-
failItem(item, `pr create failed: ${fail(pr)}`, 'building');
|
|
328
|
+
failItem(ctx, state, item, `pr create failed: ${fail(pr)}`, 'building');
|
|
253
329
|
return;
|
|
254
330
|
}
|
|
255
331
|
item.pr = n ? Number(n) : null;
|
|
256
332
|
}
|
|
257
333
|
item.step = 'pr-open';
|
|
258
|
-
ctx
|
|
334
|
+
tell(ctx, item, `pull request #${item.pr} open`);
|
|
259
335
|
}
|
|
260
336
|
/** `gh pr view`, or null when it cannot be read. Read-only, so a dry run makes it too. */
|
|
261
337
|
function prView(ctx, io, item) {
|
|
@@ -277,7 +353,7 @@ function prView(ctx, io, item) {
|
|
|
277
353
|
function droppedIfMerged(ctx, state, item, view, budget) {
|
|
278
354
|
if (view?.state !== 'MERGED')
|
|
279
355
|
return false;
|
|
280
|
-
ctx
|
|
356
|
+
tell(ctx, item, `pull request #${item.pr} was merged outside autopilot — no longer tracked`);
|
|
281
357
|
drop(state, item, budget);
|
|
282
358
|
return true;
|
|
283
359
|
}
|
|
@@ -292,7 +368,7 @@ function answeredByPerson(ctx, io, state, item, view, budget) {
|
|
|
292
368
|
return true;
|
|
293
369
|
}
|
|
294
370
|
if (view?.state === 'CLOSED') {
|
|
295
|
-
ctx
|
|
371
|
+
tell(ctx, item, `pull request #${item.pr} was closed without merging — no longer tracked`);
|
|
296
372
|
drop(state, item, budget);
|
|
297
373
|
return true;
|
|
298
374
|
}
|
|
@@ -307,31 +383,35 @@ function closeOut(ctx, io, state, item, budget) {
|
|
|
307
383
|
if (branch.stdout.trim() !== 'main' && !ctx.dryRun) {
|
|
308
384
|
// The merge is done; trying again next pass loses nothing, and an `error` here
|
|
309
385
|
// could only be cleared by `forget`, which skips the close-out altogether.
|
|
310
|
-
ctx
|
|
386
|
+
tell(ctx, item, `merged — closing it out waits for the primary checkout to be on main (it is on ${branch.stdout.trim()})`);
|
|
311
387
|
return;
|
|
312
388
|
}
|
|
313
389
|
const pull = io.call('git', ['pull', '--rebase', '--autostash'], { cwd: ctx.repo });
|
|
314
390
|
if (pull.status !== 0) {
|
|
315
|
-
|
|
316
|
-
ctx
|
|
391
|
+
const error = `pull failed: ${fail(pull)}`;
|
|
392
|
+
failItem(ctx, state, item, error);
|
|
393
|
+
tell(ctx, item, error);
|
|
317
394
|
return;
|
|
318
395
|
}
|
|
319
396
|
// Closed by hand already: nothing to transition, and a second close-out commit
|
|
320
397
|
// would fail on an empty index (autopilot-stop-controls/AC-22).
|
|
321
398
|
if (COMPLETE_RE.test(ctx.fs.read(`${ctx.repo}/.aidlc/state/${item.name}/phase-deployment.yaml`) ?? '')) {
|
|
322
|
-
ctx
|
|
399
|
+
tell(ctx, item, `pull request #${item.pr} merged, and already closed on main — no longer tracked`);
|
|
400
|
+
notice(ctx, 'item closed', `${item.name}: pull request #${item.pr} merged, and already closed on main`);
|
|
323
401
|
drop(state, item, budget);
|
|
324
402
|
return;
|
|
325
403
|
}
|
|
326
404
|
for (const [label, step] of closeSteps(ctx, io, item)) {
|
|
327
405
|
const r = step();
|
|
328
406
|
if (r.status !== 0) {
|
|
329
|
-
|
|
330
|
-
ctx
|
|
407
|
+
const error = `${label} failed: ${fail(r)}`;
|
|
408
|
+
failItem(ctx, state, item, error);
|
|
409
|
+
tell(ctx, item, error);
|
|
331
410
|
return;
|
|
332
411
|
}
|
|
333
412
|
}
|
|
334
|
-
ctx
|
|
413
|
+
tell(ctx, item, `pull request #${item.pr} merged by you, and closed`);
|
|
414
|
+
notice(ctx, 'item closed', `${item.name}: pull request #${item.pr} merged by you, and closed`);
|
|
335
415
|
drop(state, item, budget);
|
|
336
416
|
}
|
|
337
417
|
/** Close the instance on main. Shared by both close-outs so they cannot drift. */
|
|
@@ -368,7 +448,7 @@ function handleConflict(ctx, io, state, item, budget) {
|
|
|
368
448
|
item.conflict_fixes = tries + 1;
|
|
369
449
|
dequeue(state, item);
|
|
370
450
|
item.step = 'building';
|
|
371
|
-
ctx
|
|
451
|
+
tell(ctx, item, `conflicts with main — relaunching the agent to resolve it (attempt ${tries + 1} of ${CONFLICT_FIX_LIMIT})`);
|
|
372
452
|
launchAgent(ctx, item, conflictPrompt(item));
|
|
373
453
|
}
|
|
374
454
|
/**
|
|
@@ -385,14 +465,14 @@ function upToDateWithMain(ctx, io, state, item, resume, budget) {
|
|
|
385
465
|
const wt = { cwd: item.worktree };
|
|
386
466
|
const fetch = io.call('git', ['fetch', 'origin', 'main'], wt);
|
|
387
467
|
if (fetch.status !== 0) {
|
|
388
|
-
failItem(item, `update from main failed: ${fail(fetch)}`, resume);
|
|
468
|
+
failItem(ctx, state, item, `update from main failed: ${fail(fetch)}`, resume);
|
|
389
469
|
return false;
|
|
390
470
|
}
|
|
391
471
|
const contains = io.call('git', ['merge-base', '--is-ancestor', 'origin/main', 'HEAD'], wt);
|
|
392
472
|
if (contains.status === 0)
|
|
393
473
|
return true;
|
|
394
474
|
if (contains.status !== 1) {
|
|
395
|
-
failItem(item, `update from main failed: ${fail(contains)}`, resume);
|
|
475
|
+
failItem(ctx, state, item, `update from main failed: ${fail(contains)}`, resume);
|
|
396
476
|
return false;
|
|
397
477
|
}
|
|
398
478
|
const merge = io.call('git', ['merge', '--no-edit', 'origin/main'], wt);
|
|
@@ -405,11 +485,11 @@ function upToDateWithMain(ctx, io, state, item, resume, budget) {
|
|
|
405
485
|
const push = io.call('git', ['push', 'origin', item.branch], wt);
|
|
406
486
|
if (push.status !== 0) {
|
|
407
487
|
// The merge commit is local; the building step pushes it again on resume.
|
|
408
|
-
failItem(item, `push failed: ${fail(push)}`, 'building');
|
|
488
|
+
failItem(ctx, state, item, `push failed: ${fail(push)}`, 'building');
|
|
409
489
|
return false;
|
|
410
490
|
}
|
|
411
491
|
item.step = 'pr-open';
|
|
412
|
-
ctx
|
|
492
|
+
tell(ctx, item, 'brought up to date with main — waiting for CI on the new head');
|
|
413
493
|
return false;
|
|
414
494
|
}
|
|
415
495
|
function advancePr(ctx, io, state, item, budget) {
|
|
@@ -427,37 +507,43 @@ function advancePr(ctx, io, state, item, budget) {
|
|
|
427
507
|
return;
|
|
428
508
|
}
|
|
429
509
|
if (checks.length === 0) {
|
|
510
|
+
item.ci = 'waiting';
|
|
430
511
|
// GitHub runs no pull_request checks on a PR that conflicts, so "no checks
|
|
431
512
|
// yet" would otherwise be waited on forever (AC-12).
|
|
432
513
|
if (view?.mergeable === 'CONFLICTING') {
|
|
433
|
-
ctx
|
|
514
|
+
tell(ctx, item, `pull request #${item.pr} conflicts with main and has no checks`);
|
|
434
515
|
upToDateWithMain(ctx, io, state, item, 'pr-open', budget);
|
|
435
516
|
return;
|
|
436
517
|
}
|
|
437
|
-
ctx
|
|
518
|
+
tell(ctx, item, `pull request #${item.pr} — waiting for checks to start`);
|
|
438
519
|
return;
|
|
439
520
|
}
|
|
440
|
-
if (checks.some((c) => c.bucket === 'pending'))
|
|
521
|
+
if (checks.some((c) => c.bucket === 'pending')) {
|
|
522
|
+
item.ci = 'pending';
|
|
441
523
|
return;
|
|
524
|
+
}
|
|
442
525
|
const failing = checks.filter((c) => c.bucket === 'fail' || c.bucket === 'cancel').map((c) => c.name);
|
|
443
526
|
if (failing.length === 0) {
|
|
527
|
+
item.ci = 'passed';
|
|
444
528
|
if (atPr) {
|
|
445
529
|
item.step = 'awaiting-merge';
|
|
446
|
-
ctx
|
|
530
|
+
tell(ctx, item, `CI green — pull request #${item.pr} waits for you to merge it`);
|
|
531
|
+
notice(ctx, 'merge needed', `${item.name}: pull request #${item.pr} waits for you to merge it`);
|
|
447
532
|
return;
|
|
448
533
|
}
|
|
449
534
|
item.step = 'queued';
|
|
450
535
|
if (!state.queue.includes(item.name))
|
|
451
536
|
state.queue.push(item.name);
|
|
452
|
-
ctx
|
|
537
|
+
tell(ctx, item, 'CI green, queued for merge');
|
|
453
538
|
return;
|
|
454
539
|
}
|
|
540
|
+
item.ci = 'failed';
|
|
455
541
|
item.ci_failures = (item.ci_failures ?? 0) + 1;
|
|
456
542
|
if (item.ci_failures >= CI_FAILURE_LIMIT) {
|
|
457
543
|
park(ctx, io, state, item, `CI failed twice: ${failing.join(', ')}`, budget);
|
|
458
544
|
return;
|
|
459
545
|
}
|
|
460
|
-
ctx
|
|
546
|
+
tell(ctx, item, `CI failed (${failing.join(', ')}), relaunching the agent once`);
|
|
461
547
|
item.step = 'building';
|
|
462
548
|
launchAgent(ctx, item, fixPrompt(item, failing));
|
|
463
549
|
}
|
|
@@ -490,6 +576,7 @@ function serveQueue(ctx, io, state, budget) {
|
|
|
490
576
|
return;
|
|
491
577
|
}
|
|
492
578
|
if (item.step === 'error') {
|
|
579
|
+
// An observation repeated every pass: the failure that halted the queue already notified.
|
|
493
580
|
ctx.say(`queue halted: ${item.name} — ${item.error}`);
|
|
494
581
|
return;
|
|
495
582
|
}
|
|
@@ -499,7 +586,7 @@ function serveQueue(ctx, io, state, budget) {
|
|
|
499
586
|
try {
|
|
500
587
|
const status = JSON.parse(runs.stdout || '[]')[0]?.status;
|
|
501
588
|
if (status === 'queued' || status === 'in_progress') {
|
|
502
|
-
ctx
|
|
589
|
+
tell(ctx, item, 'waiting — a release is already in flight');
|
|
503
590
|
return;
|
|
504
591
|
}
|
|
505
592
|
}
|
|
@@ -508,7 +595,7 @@ function serveQueue(ctx, io, state, budget) {
|
|
|
508
595
|
}
|
|
509
596
|
const branch = io.call('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: ctx.repo });
|
|
510
597
|
if (branch.stdout.trim() !== 'main' && !ctx.dryRun) {
|
|
511
|
-
failItem(item, `primary checkout is on ${branch.stdout.trim()}, not main`, 'queued');
|
|
598
|
+
failItem(ctx, state, item, `primary checkout is on ${branch.stdout.trim()}, not main`, 'queued');
|
|
512
599
|
return;
|
|
513
600
|
}
|
|
514
601
|
if (!upToDateWithMain(ctx, io, state, item, 'queued', budget))
|
|
@@ -517,7 +604,7 @@ function serveQueue(ctx, io, state, budget) {
|
|
|
517
604
|
const head = io.call('git', ['rev-parse', 'HEAD'], { cwd: item.worktree });
|
|
518
605
|
const sha = head.stdout.trim();
|
|
519
606
|
if ((head.status !== 0 || !/^[0-9a-f]{7,64}$/.test(sha)) && !ctx.dryRun) {
|
|
520
|
-
failItem(item, `merge failed: cannot read the branch head (${fail(head)})`, 'queued');
|
|
607
|
+
failItem(ctx, state, item, `merge failed: cannot read the branch head (${fail(head)})`, 'queued');
|
|
521
608
|
return;
|
|
522
609
|
}
|
|
523
610
|
const release = ctx.config?.release_command;
|
|
@@ -540,12 +627,14 @@ function serveQueue(ctx, io, state, budget) {
|
|
|
540
627
|
if (r.status !== 0) {
|
|
541
628
|
// Halt. Only a failed merge resumes on the next `autopilot run`; from the
|
|
542
629
|
// pull on, the merge has happened and a half-done release is for a person (AC-16).
|
|
543
|
-
|
|
544
|
-
ctx
|
|
630
|
+
const error = `${label} failed: ${fail(r)}`;
|
|
631
|
+
failItem(ctx, state, item, error, label === 'merge' ? 'queued' : undefined);
|
|
632
|
+
tell(ctx, item, `${error} — queue halted`);
|
|
545
633
|
return;
|
|
546
634
|
}
|
|
547
635
|
}
|
|
548
|
-
ctx
|
|
636
|
+
tell(ctx, item, `merged${releases ? ', released' : ''} and closed`);
|
|
637
|
+
notice(ctx, releases ? 'item released' : 'item merged', `${item.name}: merged${releases ? ', released' : ''} and closed`);
|
|
549
638
|
drop(state, item, budget);
|
|
550
639
|
}
|
|
551
640
|
/**
|
|
@@ -563,6 +652,7 @@ function parkUnstartable(ctx, io, file, failure) {
|
|
|
563
652
|
return false;
|
|
564
653
|
}
|
|
565
654
|
ctx.say(`could not start ${file}: ${error} — parked`);
|
|
655
|
+
notice(ctx, 'item parked', `${file}: could not start: ${error}`);
|
|
566
656
|
return true;
|
|
567
657
|
}
|
|
568
658
|
function startBuilds(ctx, io, state, budget) {
|
|
@@ -700,6 +790,7 @@ export function tick(ctx, state, budget) {
|
|
|
700
790
|
return state;
|
|
701
791
|
}
|
|
702
792
|
const io = makeIO(ctx);
|
|
793
|
+
const before = stepsOf(state);
|
|
703
794
|
try {
|
|
704
795
|
for (const item of [...state.items]) {
|
|
705
796
|
// `awaiting-review` is passed over: nothing to read until a person approves.
|
|
@@ -724,9 +815,26 @@ export function tick(ctx, state, budget) {
|
|
|
724
815
|
state.backoff_until = new Date(ctx.now() + state.backoff_ms).toISOString();
|
|
725
816
|
ctx.say(`rate limited (${err.message}) — backing off until ${state.backoff_until}`);
|
|
726
817
|
}
|
|
818
|
+
markStepChanges(state, before, ctx.now());
|
|
727
819
|
state.last_tick = new Date(ctx.now()).toISOString();
|
|
728
820
|
return state;
|
|
729
821
|
}
|
|
822
|
+
/** Each item's step by name, to compare against after a change (autopilot-live-progress/AC-11). */
|
|
823
|
+
export function stepsOf(state) {
|
|
824
|
+
return new Map(state.items.map((i) => [i.name, i.step]));
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Stamp `step_since` on every item whose step differs from `before`; a new item has
|
|
828
|
+
* no entry there and is stamped too. Diffing here, rather than at each of the
|
|
829
|
+
* fourteen places a step is set, means a new handler cannot forget it.
|
|
830
|
+
*/
|
|
831
|
+
export function markStepChanges(state, before, now) {
|
|
832
|
+
const at = new Date(now).toISOString();
|
|
833
|
+
for (const item of state.items) {
|
|
834
|
+
if (before.get(item.name) !== item.step)
|
|
835
|
+
item.step_since = { step: item.step, at };
|
|
836
|
+
}
|
|
837
|
+
}
|
|
730
838
|
/** What a person is being waited on for, in words (autopilot-stop-controls/AC-14, AC-20). */
|
|
731
839
|
function waitingFor(i) {
|
|
732
840
|
if (i.step === 'awaiting-review') {
|
|
@@ -738,22 +846,33 @@ function waitingFor(i) {
|
|
|
738
846
|
return ` — waiting for you to merge pull request #${i.pr}`;
|
|
739
847
|
return '';
|
|
740
848
|
}
|
|
849
|
+
/**
|
|
850
|
+
* The driver's line, shared by `status` and the watch view.
|
|
851
|
+
*
|
|
852
|
+
* @param stopped - whether the STOP file exists.
|
|
853
|
+
* @param lastPass - how to show the last pass's time; `status` shows it as stored.
|
|
854
|
+
*/
|
|
855
|
+
export function driverLine(stopped, state, now, lastPass = (iso) => iso) {
|
|
856
|
+
if (stopped)
|
|
857
|
+
return 'driver: stopped (STOP file present)';
|
|
858
|
+
if (state.backoff_until && now < Date.parse(state.backoff_until))
|
|
859
|
+
return `driver: backing off until ${state.backoff_until}`;
|
|
860
|
+
return `driver: last pass ${state.last_tick ? lastPass(state.last_tick) : 'never'}`;
|
|
861
|
+
}
|
|
862
|
+
export function queueLine(state) {
|
|
863
|
+
return state.queue.length > 0 ? `merge queue: ${state.queue.join(' → ')}` : null;
|
|
864
|
+
}
|
|
741
865
|
export function statusLines(ctx, state) {
|
|
742
|
-
const lines = [];
|
|
743
|
-
if (ctx.fs.exists(ctx.paths.stop))
|
|
744
|
-
lines.push('driver: stopped (STOP file present)');
|
|
745
|
-
else if (state.backoff_until && ctx.now() < Date.parse(state.backoff_until))
|
|
746
|
-
lines.push(`driver: backing off until ${state.backoff_until}`);
|
|
747
|
-
else
|
|
748
|
-
lines.push(`driver: last pass ${state.last_tick ?? 'never'}`);
|
|
866
|
+
const lines = [driverLine(ctx.fs.exists(ctx.paths.stop), state, ctx.now())];
|
|
749
867
|
if (state.items.length === 0)
|
|
750
868
|
lines.push('no items in flight');
|
|
751
869
|
for (const i of state.items) {
|
|
752
870
|
lines.push(`${i.name}: ${i.step}${i.pr ? ` PR #${i.pr}` : ''} — CI failures ${i.ci_failures ?? 0}` +
|
|
753
871
|
`${waitingFor(i)}${i.error ? ` — last error: ${i.error}` : ''}`);
|
|
754
872
|
}
|
|
755
|
-
|
|
756
|
-
|
|
873
|
+
const queue = queueLine(state);
|
|
874
|
+
if (queue !== null)
|
|
875
|
+
lines.push(queue);
|
|
757
876
|
return lines;
|
|
758
877
|
}
|
|
759
878
|
//# sourceMappingURL=driver.js.map
|