@enderfga/claw-orchestrator 5.1.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -26
- package/dist/bin/cli.js +107 -1
- package/dist/bin/cli.js.map +1 -1
- package/dist/src/acp-server.d.ts +5 -5
- package/dist/src/acp-server.js +3 -3
- package/dist/src/acp-server.js.map +1 -1
- package/dist/src/autoloop/dispatcher.d.ts +22 -0
- package/dist/src/autoloop/dispatcher.js +71 -13
- package/dist/src/autoloop/dispatcher.js.map +1 -1
- package/dist/src/autoloop/messages.d.ts +10 -0
- package/dist/src/autoloop/messages.js.map +1 -1
- package/dist/src/autoloop/runner.js +6 -0
- package/dist/src/autoloop/runner.js.map +1 -1
- package/dist/src/constants.d.ts +0 -6
- package/dist/src/constants.js +0 -6
- package/dist/src/constants.js.map +1 -1
- package/dist/src/council.d.ts +15 -0
- package/dist/src/council.js +48 -35
- package/dist/src/council.js.map +1 -1
- package/dist/src/dashboard/index.html +191 -6
- package/dist/src/embedded-server.js +132 -9
- package/dist/src/embedded-server.js.map +1 -1
- package/dist/src/fanout.d.ts +30 -1
- package/dist/src/fanout.js +32 -3
- package/dist/src/fanout.js.map +1 -1
- package/dist/src/index.js +359 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/agent-step.d.ts +59 -0
- package/dist/src/kernel/agent-step.js +100 -0
- package/dist/src/kernel/agent-step.js.map +1 -0
- package/dist/src/kernel/conditions.d.ts +11 -0
- package/dist/src/kernel/conditions.js +24 -0
- package/dist/src/kernel/conditions.js.map +1 -0
- package/dist/src/kernel/engine.d.ts +319 -0
- package/dist/src/kernel/engine.js +1047 -0
- package/dist/src/kernel/engine.js.map +1 -0
- package/dist/src/kernel/exec.d.ts +43 -0
- package/dist/src/kernel/exec.js +112 -0
- package/dist/src/kernel/exec.js.map +1 -0
- package/dist/src/kernel/file-lock.d.ts +50 -0
- package/dist/src/kernel/file-lock.js +135 -0
- package/dist/src/kernel/file-lock.js.map +1 -0
- package/dist/src/kernel/nodes/agent.d.ts +4 -0
- package/dist/src/kernel/nodes/agent.js +35 -0
- package/dist/src/kernel/nodes/agent.js.map +1 -0
- package/dist/src/kernel/nodes/autoloop.d.ts +78 -0
- package/dist/src/kernel/nodes/autoloop.js +75 -0
- package/dist/src/kernel/nodes/autoloop.js.map +1 -0
- package/dist/src/kernel/nodes/council.d.ts +12 -0
- package/dist/src/kernel/nodes/council.js +88 -0
- package/dist/src/kernel/nodes/council.js.map +1 -0
- package/dist/src/kernel/nodes/fanout.d.ts +11 -0
- package/dist/src/kernel/nodes/fanout.js +63 -0
- package/dist/src/kernel/nodes/fanout.js.map +1 -0
- package/dist/src/kernel/nodes/human-gate.d.ts +4 -0
- package/dist/src/kernel/nodes/human-gate.js +7 -0
- package/dist/src/kernel/nodes/human-gate.js.map +1 -0
- package/dist/src/kernel/nodes/index.d.ts +12 -0
- package/dist/src/kernel/nodes/index.js +21 -0
- package/dist/src/kernel/nodes/index.js.map +1 -0
- package/dist/src/kernel/nodes/router.d.ts +4 -0
- package/dist/src/kernel/nodes/router.js +12 -0
- package/dist/src/kernel/nodes/router.js.map +1 -0
- package/dist/src/kernel/nodes/subflow.d.ts +13 -0
- package/dist/src/kernel/nodes/subflow.js +38 -0
- package/dist/src/kernel/nodes/subflow.js.map +1 -0
- package/dist/src/kernel/nodes/ultraapp.d.ts +60 -0
- package/dist/src/kernel/nodes/ultraapp.js +62 -0
- package/dist/src/kernel/nodes/ultraapp.js.map +1 -0
- package/dist/src/kernel/nodes/verifier.d.ts +14 -0
- package/dist/src/kernel/nodes/verifier.js +84 -0
- package/dist/src/kernel/nodes/verifier.js.map +1 -0
- package/dist/src/kernel/projections.d.ts +42 -0
- package/dist/src/kernel/projections.js +133 -0
- package/dist/src/kernel/projections.js.map +1 -0
- package/dist/src/kernel/repo.d.ts +13 -0
- package/dist/src/kernel/repo.js +64 -0
- package/dist/src/kernel/repo.js.map +1 -0
- package/dist/src/kernel/secrets.d.ts +25 -0
- package/dist/src/kernel/secrets.js +48 -0
- package/dist/src/kernel/secrets.js.map +1 -0
- package/dist/src/kernel/store.d.ts +225 -0
- package/dist/src/kernel/store.js +838 -0
- package/dist/src/kernel/store.js.map +1 -0
- package/dist/src/kernel/templates/index.d.ts +140 -0
- package/dist/src/kernel/templates/index.js +266 -0
- package/dist/src/kernel/templates/index.js.map +1 -0
- package/dist/src/kernel/types.d.ts +326 -0
- package/dist/src/kernel/types.js +19 -0
- package/dist/src/kernel/types.js.map +1 -0
- package/dist/src/run-ledger.d.ts +57 -3
- package/dist/src/run-ledger.js +45 -2
- package/dist/src/run-ledger.js.map +1 -1
- package/dist/src/session-manager.d.ts +176 -129
- package/dist/src/session-manager.js +652 -603
- package/dist/src/session-manager.js.map +1 -1
- package/dist/src/types.d.ts +33 -3
- package/dist/src/ultraapp/build.d.ts +117 -3
- package/dist/src/ultraapp/build.js +319 -3
- package/dist/src/ultraapp/build.js.map +1 -1
- package/dist/src/ultraapp/contract.d.ts +52 -0
- package/dist/src/ultraapp/contract.js +83 -0
- package/dist/src/ultraapp/contract.js.map +1 -0
- package/dist/src/ultraapp/conventions.js +9 -2
- package/dist/src/ultraapp/conventions.js.map +1 -1
- package/dist/src/ultraapp/fix-on-failure.d.ts +21 -2
- package/dist/src/ultraapp/fix-on-failure.js +46 -62
- package/dist/src/ultraapp/fix-on-failure.js.map +1 -1
- package/dist/src/ultraapp/manager.d.ts +107 -2
- package/dist/src/ultraapp/manager.js +305 -86
- package/dist/src/ultraapp/manager.js.map +1 -1
- package/dist/src/verify/baseline.d.ts +73 -0
- package/dist/src/verify/baseline.js +186 -0
- package/dist/src/verify/baseline.js.map +1 -0
- package/dist/src/verify/contract.d.ts +116 -0
- package/dist/src/verify/contract.js +142 -0
- package/dist/src/verify/contract.js.map +1 -0
- package/dist/src/verify/evidence.d.ts +61 -0
- package/dist/src/verify/evidence.js +133 -0
- package/dist/src/verify/evidence.js.map +1 -0
- package/dist/src/verify/runner.d.ts +63 -0
- package/dist/src/verify/runner.js +317 -0
- package/dist/src/verify/runner.js.map +1 -0
- package/openclaw.plugin.json +8 -0
- package/package.json +2 -2
- package/skills/SKILL.md +120 -79
- package/skills/references/acp.md +17 -17
- package/skills/references/autoloop.md +139 -65
- package/skills/references/claude-cli-tracking.md +4 -4
- package/skills/references/cli.md +101 -59
- package/skills/references/council.md +109 -37
- package/skills/references/dashboard.md +34 -6
- package/skills/references/getting-started.md +13 -13
- package/skills/references/inbox.md +4 -4
- package/skills/references/mcp.md +39 -34
- package/skills/references/multi-engine.md +51 -47
- package/skills/references/observability.md +88 -28
- package/skills/references/openai-compat.md +39 -39
- package/skills/references/sessions.md +43 -25
- package/skills/references/tools.md +402 -309
- package/skills/references/ultra.md +45 -45
- package/skills/references/ultraapp.md +126 -50
- package/skills/references/verification.md +187 -0
- package/skills/references/workflow.md +362 -0
- package/dist/src/ultraapp/fix-on-failure-session.d.ts +0 -23
- package/dist/src/ultraapp/fix-on-failure-session.js +0 -51
- package/dist/src/ultraapp/fix-on-failure-session.js.map +0 -1
|
@@ -4,29 +4,293 @@
|
|
|
4
4
|
* Default concurrency = 1. Pending builds are FIFO. Subscribers receive every
|
|
5
5
|
* BuildEvent emitted by the worker for any currently-running build.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* The queue is durable as of 6.0.0. It used to say so in this comment: "Disk
|
|
8
|
+
* persistence (in-progress + pending lists) is NOT done in v0.2 — if the
|
|
9
|
+
* orchestrator restarts mid-build, the build is marked failed and the user can
|
|
10
|
+
* rerun." In practice a restart did not mark anything: the pending list simply
|
|
11
|
+
* vanished, along with any queued build the user was waiting on, with no record
|
|
12
|
+
* that it had ever been asked for.
|
|
13
|
+
*
|
|
14
|
+
* Now the pending list and the in-flight run id are written on every change and
|
|
15
|
+
* restored on construction. A build that was in flight when the process died is
|
|
16
|
+
* re-queued rather than resumed — each build starts from a fresh council
|
|
17
|
+
* worktree, so re-running is safe and resuming a half-built tree is not.
|
|
18
|
+
*
|
|
19
|
+
* Durability without ownership would be worse than neither: two processes each
|
|
20
|
+
* restoring the same file would each run the same builds, doing every side
|
|
21
|
+
* effect twice. So restoring takes a lease on the state file, held by pid and
|
|
22
|
+
* heartbeated. A queue that cannot take the lease starts empty and leaves the
|
|
23
|
+
* work to whoever owns it.
|
|
10
24
|
*/
|
|
25
|
+
import crypto from 'node:crypto';
|
|
11
26
|
import { EventEmitter } from 'node:events';
|
|
27
|
+
import fs from 'node:fs';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
import { withFileLock } from '../kernel/file-lock.js';
|
|
30
|
+
/** How long an owner may go without a heartbeat before the queue is up for grabs. */
|
|
31
|
+
const OWNER_TTL_MS = 60_000;
|
|
32
|
+
/** Refresh interval, comfortably inside the TTL. */
|
|
33
|
+
const HEARTBEAT_MS = 15_000;
|
|
34
|
+
function ownerIsLive(owner, selfId) {
|
|
35
|
+
if (!owner)
|
|
36
|
+
return false;
|
|
37
|
+
if (owner.ownerId === selfId)
|
|
38
|
+
return true;
|
|
39
|
+
const age = Date.now() - Date.parse(owner.renewedAt);
|
|
40
|
+
if (Number.isNaN(age) || age > OWNER_TTL_MS)
|
|
41
|
+
return false;
|
|
42
|
+
try {
|
|
43
|
+
process.kill(owner.pid, 0);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return err.code === 'EPERM';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
12
50
|
export class UltraappBuildQueue {
|
|
13
51
|
emitter = new EventEmitter();
|
|
14
52
|
pending = [];
|
|
15
53
|
currentRunId = null;
|
|
16
54
|
worker;
|
|
17
55
|
concurrency;
|
|
56
|
+
statePath;
|
|
57
|
+
heartbeat;
|
|
58
|
+
/** This queue instance's identity as an owner. Per instance, not per process. */
|
|
59
|
+
ownerId = `queue-${crypto.randomUUID()}`;
|
|
60
|
+
/** Consecutive failed dispatch claims, per build. Cleared once it starts. */
|
|
61
|
+
dispatchRetries = new Map();
|
|
18
62
|
idlePromise = Promise.resolve();
|
|
19
63
|
idleResolve = null;
|
|
20
64
|
constructor(opts) {
|
|
21
65
|
this.worker = opts.worker;
|
|
22
66
|
this.concurrency = opts.concurrency ?? 1;
|
|
67
|
+
this.statePath = opts.statePath;
|
|
68
|
+
const restored = this.restore();
|
|
69
|
+
if (this.statePath && !this.notOwner) {
|
|
70
|
+
// Independent of any build finishing: a worker that runs for ten minutes
|
|
71
|
+
// makes no state writes, and must not look abandoned for it.
|
|
72
|
+
this.heartbeat = setInterval(() => this.persist(), HEARTBEAT_MS);
|
|
73
|
+
if (typeof this.heartbeat.unref === 'function')
|
|
74
|
+
this.heartbeat.unref();
|
|
75
|
+
}
|
|
76
|
+
if (this.notOwner)
|
|
77
|
+
opts.onNotOwner?.(this.notOwner);
|
|
78
|
+
if (restored.length > 0) {
|
|
79
|
+
opts.onRestore?.(restored);
|
|
80
|
+
this.markBusy();
|
|
81
|
+
void this.tryDispatch();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Set when another live queue holds the claim. */
|
|
85
|
+
notOwner;
|
|
86
|
+
/**
|
|
87
|
+
* True when this process is executing the queue.
|
|
88
|
+
*
|
|
89
|
+
* Reads the state file rather than answering from the last decision, so a
|
|
90
|
+
* queue that has been taken over reports it even before its next write finds
|
|
91
|
+
* out. Advisory only — nothing acts on this answer; `enqueue` and dispatch
|
|
92
|
+
* re-check under the lock, because a lock-free read is a snapshot and a claim
|
|
93
|
+
* is not.
|
|
94
|
+
*/
|
|
95
|
+
ownsQueue() {
|
|
96
|
+
if (this.notOwner)
|
|
97
|
+
return false;
|
|
98
|
+
if (!this.statePath)
|
|
99
|
+
return true;
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
|
|
102
|
+
if (parsed?.owner && parsed.owner.ownerId !== this.ownerId && ownerIsLive(parsed.owner, this.ownerId)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// No state file yet, or unreadable: nothing says anyone else has it.
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/** Refuse an operation that only the owner may perform. */
|
|
112
|
+
assertOwner(action) {
|
|
113
|
+
if (!this.notOwner)
|
|
114
|
+
return;
|
|
115
|
+
throw new Error(`Cannot ${action}: this build queue is owned by ${this.notOwner.ownerId} (pid ${this.notOwner.pid}) ` +
|
|
116
|
+
`(last seen ${this.notOwner.renewedAt}). Running its builds here would execute them twice.`);
|
|
117
|
+
}
|
|
118
|
+
/** Release the claim so another process can pick the queue up promptly. */
|
|
119
|
+
stop() {
|
|
120
|
+
if (this.heartbeat)
|
|
121
|
+
clearInterval(this.heartbeat);
|
|
122
|
+
this.heartbeat = undefined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Read back a queue left by a previous process.
|
|
126
|
+
*
|
|
127
|
+
* A build that was in flight goes to the FRONT of the restored queue: it was
|
|
128
|
+
* asked for first, and the user has been waiting on it longest.
|
|
129
|
+
*/
|
|
130
|
+
/**
|
|
131
|
+
* Claim the queue and read back whatever the last owner left.
|
|
132
|
+
*
|
|
133
|
+
* Read-then-decide is not a claim. Two processes starting with no state file
|
|
134
|
+
* both saw "free", both concluded they owned it, and nothing wrote an owner
|
|
135
|
+
* until the first enqueue — by which time both were running the same builds.
|
|
136
|
+
* The read and the claim happen inside one `O_EXCL` critical section now.
|
|
137
|
+
*/
|
|
138
|
+
restore() {
|
|
139
|
+
if (!this.statePath)
|
|
140
|
+
return [];
|
|
141
|
+
let entered = false;
|
|
142
|
+
const ids = this.withLock(() => {
|
|
143
|
+
entered = true;
|
|
144
|
+
return this.claimLocked();
|
|
145
|
+
}, []);
|
|
146
|
+
if (!entered && !this.notOwner) {
|
|
147
|
+
// We never got inside the critical section — someone else is claiming
|
|
148
|
+
// right now, or the lock file is unusable. Either way we have not taken
|
|
149
|
+
// the queue, and acting as though we had is how both processes end up
|
|
150
|
+
// running the same builds.
|
|
151
|
+
this.notOwner = { ownerId: 'unknown', pid: -1, renewedAt: new Date().toISOString() };
|
|
152
|
+
}
|
|
153
|
+
return ids ?? [];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Run `fn` inside the queue's lock, waiting briefly for it.
|
|
157
|
+
*
|
|
158
|
+
* Shared with the run store, and for the same reason: a lock that is merely
|
|
159
|
+
* busy must not be reported the same way as a claim that has moved on.
|
|
160
|
+
*/
|
|
161
|
+
withLock(fn, onContended) {
|
|
162
|
+
if (!this.statePath)
|
|
163
|
+
return undefined;
|
|
164
|
+
const result = withFileLock(`${this.statePath}.lock`, fn, { staleMs: OWNER_TTL_MS, createParent: true });
|
|
165
|
+
return result.ok ? result.value : onContended;
|
|
166
|
+
}
|
|
167
|
+
/** The critical section of `restore`: inspect the owner and take it, or step aside. */
|
|
168
|
+
claimLocked() {
|
|
169
|
+
if (!this.statePath)
|
|
170
|
+
return [];
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
parsed = { pending: [], current: null };
|
|
177
|
+
}
|
|
178
|
+
if (ownerIsLive(parsed.owner, this.ownerId)) {
|
|
179
|
+
// Someone else is running these. Taking them would execute every build
|
|
180
|
+
// twice; the honest move is to start empty and say so.
|
|
181
|
+
this.notOwner = parsed.owner;
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
const ids = [
|
|
185
|
+
...(parsed.current ? [parsed.current] : []),
|
|
186
|
+
...(Array.isArray(parsed.pending) ? parsed.pending.filter((x) => typeof x === 'string') : []),
|
|
187
|
+
];
|
|
188
|
+
for (const runId of ids)
|
|
189
|
+
this.pending.push({ runId });
|
|
190
|
+
// Stake the claim before releasing the lock, so a racer that gets in next
|
|
191
|
+
// sees an owner rather than an empty file.
|
|
192
|
+
this.writeStateUnchecked();
|
|
193
|
+
return ids;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Persist the queue, if we still own it.
|
|
197
|
+
*
|
|
198
|
+
* Best-effort about I/O, not about ownership: the check happens inside the
|
|
199
|
+
* lock, in the same critical section as the write. Taking the claim at
|
|
200
|
+
* construction and then writing unconditionally forever was not ownership — a
|
|
201
|
+
* queue whose heartbeat had lapsed and been taken over would quietly stamp
|
|
202
|
+
* itself back in as owner on its next persist, clobbering the new owner's
|
|
203
|
+
* pending list and running its builds a second time.
|
|
204
|
+
*/
|
|
205
|
+
persist() {
|
|
206
|
+
if (!this.statePath)
|
|
207
|
+
return 'ephemeral';
|
|
208
|
+
if (this.notOwner)
|
|
209
|
+
return 'superseded';
|
|
210
|
+
return this.withLock(() => this.writeStateLocked(), 'blocked') ?? 'blocked';
|
|
211
|
+
}
|
|
212
|
+
/** Caller must hold the lock. Steps aside — permanently — if the claim moved on. */
|
|
213
|
+
writeStateLocked() {
|
|
214
|
+
if (!this.statePath)
|
|
215
|
+
return 'ephemeral';
|
|
216
|
+
let parsed;
|
|
217
|
+
try {
|
|
218
|
+
parsed = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
parsed = undefined;
|
|
222
|
+
}
|
|
223
|
+
if (parsed?.owner && parsed.owner.ownerId !== this.ownerId && ownerIsLive(parsed.owner, this.ownerId)) {
|
|
224
|
+
this.standDown(parsed.owner);
|
|
225
|
+
return 'superseded';
|
|
226
|
+
}
|
|
227
|
+
this.writeStateUnchecked();
|
|
228
|
+
return 'committed';
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Hand the queue over: stop heartbeating and drop the pending list.
|
|
232
|
+
*
|
|
233
|
+
* Keeping the list would be worse than losing it — every entry now belongs to
|
|
234
|
+
* the new owner, who has its own copy, and dispatching from ours is the double
|
|
235
|
+
* execution the claim exists to prevent. A build already in flight cannot be
|
|
236
|
+
* recalled; it finishes, and nothing further starts.
|
|
237
|
+
*/
|
|
238
|
+
standDown(owner) {
|
|
239
|
+
this.notOwner = owner;
|
|
240
|
+
this.stop();
|
|
241
|
+
this.pending.length = 0;
|
|
242
|
+
if (this.currentRunId === null)
|
|
243
|
+
this.markIdle();
|
|
244
|
+
}
|
|
245
|
+
writeStateUnchecked() {
|
|
246
|
+
if (!this.statePath)
|
|
247
|
+
return;
|
|
248
|
+
try {
|
|
249
|
+
fs.mkdirSync(path.dirname(this.statePath), { recursive: true });
|
|
250
|
+
const state = {
|
|
251
|
+
pending: this.pending.map((p) => p.runId),
|
|
252
|
+
current: this.currentRunId,
|
|
253
|
+
// Every write is also the heartbeat, so a queue that stops making
|
|
254
|
+
// progress stops holding the claim.
|
|
255
|
+
owner: { ownerId: this.ownerId, pid: process.pid, renewedAt: new Date().toISOString() },
|
|
256
|
+
};
|
|
257
|
+
const tmp = `${this.statePath}.tmp.${process.pid}.${crypto.randomBytes(4).toString('hex')}`;
|
|
258
|
+
fs.writeFileSync(tmp, JSON.stringify(state));
|
|
259
|
+
fs.renameSync(tmp, this.statePath);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// A queue that cannot be persisted still works for this process.
|
|
263
|
+
}
|
|
23
264
|
}
|
|
24
265
|
/**
|
|
25
266
|
* Enqueue a build. Resolves immediately after the run is appended to the
|
|
26
267
|
* pending list. Use {@link idle} to wait for the queue to drain.
|
|
27
268
|
*/
|
|
28
269
|
async enqueue(runId) {
|
|
270
|
+
// Refusing at construction is not enough: `ownsQueue()` returning false
|
|
271
|
+
// while `enqueue` cheerfully ran the build — and rewrote the owner to
|
|
272
|
+
// ourselves — is exactly the double execution the claim exists to stop.
|
|
273
|
+
this.assertOwner('enqueue');
|
|
29
274
|
this.pending.push({ runId });
|
|
275
|
+
// The claim is re-checked inside the same critical section the write happens
|
|
276
|
+
// in. Owning the queue at construction says nothing about owning it now: the
|
|
277
|
+
// heartbeat can have lapsed and someone else can have taken over, and an
|
|
278
|
+
// `enqueue` that only consulted the construction-time answer would run their
|
|
279
|
+
// builds a second time.
|
|
280
|
+
//
|
|
281
|
+
// Three outcomes, not two. `superseded` refuses. `blocked` — we could not
|
|
282
|
+
// even get into the critical section — also refuses, because an enqueue that
|
|
283
|
+
// cannot be recorded may be an enqueue into a queue somebody else now owns;
|
|
284
|
+
// the earlier version returned a bare `false` here and carried on, and ran a
|
|
285
|
+
// build whose queue state on disk already belonged to another process.
|
|
286
|
+
const persisted = this.persist();
|
|
287
|
+
if (persisted === 'superseded')
|
|
288
|
+
this.assertOwner('enqueue');
|
|
289
|
+
if (persisted === 'blocked') {
|
|
290
|
+
this.pending.pop();
|
|
291
|
+
throw new Error(`Cannot enqueue: the build queue state file is locked by another process, so this build cannot be ` +
|
|
292
|
+
`recorded. Retry in a moment.`);
|
|
293
|
+
}
|
|
30
294
|
this.markBusy();
|
|
31
295
|
const pos = this.position(runId);
|
|
32
296
|
if (pos > 0) {
|
|
@@ -38,6 +302,7 @@ export class UltraappBuildQueue {
|
|
|
38
302
|
const idx = this.pending.findIndex((p) => p.runId === runId);
|
|
39
303
|
if (idx >= 0) {
|
|
40
304
|
this.pending.splice(idx, 1);
|
|
305
|
+
this.persist();
|
|
41
306
|
this.emit({ type: 'build-cancelled', runId });
|
|
42
307
|
if (this.pending.length === 0 && this.currentRunId === null)
|
|
43
308
|
this.markIdle();
|
|
@@ -65,6 +330,8 @@ export class UltraappBuildQueue {
|
|
|
65
330
|
return this.idlePromise;
|
|
66
331
|
}
|
|
67
332
|
async tryDispatch() {
|
|
333
|
+
if (this.notOwner)
|
|
334
|
+
return;
|
|
68
335
|
if (this.currentRunId !== null)
|
|
69
336
|
return;
|
|
70
337
|
if (this.concurrency !== 1)
|
|
@@ -75,6 +342,25 @@ export class UltraappBuildQueue {
|
|
|
75
342
|
return;
|
|
76
343
|
}
|
|
77
344
|
this.currentRunId = next.runId;
|
|
345
|
+
// Same check before starting work as before recording it, and with the same
|
|
346
|
+
// three outcomes: only `committed` (or an ephemeral queue, which owns
|
|
347
|
+
// nothing and persists nothing) may dispatch. Dispatching on `blocked` is
|
|
348
|
+
// how a superseded queue ran a build whose state on disk already named
|
|
349
|
+
// another owner.
|
|
350
|
+
const persisted = this.persist();
|
|
351
|
+
if (persisted === 'superseded') {
|
|
352
|
+
this.currentRunId = null;
|
|
353
|
+
this.markIdle();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (persisted === 'blocked') {
|
|
357
|
+
// Transient: put the build back at the front and try again shortly.
|
|
358
|
+
this.currentRunId = null;
|
|
359
|
+
this.pending.unshift(next);
|
|
360
|
+
this._retryDispatch(next.runId);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
this.dispatchRetries.delete(next.runId);
|
|
78
364
|
try {
|
|
79
365
|
await this.worker(next.runId, (e) => this.emit(e));
|
|
80
366
|
}
|
|
@@ -88,9 +374,39 @@ export class UltraappBuildQueue {
|
|
|
88
374
|
}
|
|
89
375
|
finally {
|
|
90
376
|
this.currentRunId = null;
|
|
377
|
+
this.persist();
|
|
91
378
|
void this.tryDispatch();
|
|
92
379
|
}
|
|
93
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Re-attempt a dispatch that could not claim the state file.
|
|
383
|
+
*
|
|
384
|
+
* Bounded: a queue that cannot record what it is doing must eventually say so
|
|
385
|
+
* rather than spinning, and the build is failed with the reason instead of
|
|
386
|
+
* quietly never starting.
|
|
387
|
+
*/
|
|
388
|
+
_retryDispatch(runId) {
|
|
389
|
+
const attempts = (this.dispatchRetries.get(runId) ?? 0) + 1;
|
|
390
|
+
this.dispatchRetries.set(runId, attempts);
|
|
391
|
+
if (attempts > 8) {
|
|
392
|
+
this.dispatchRetries.delete(runId);
|
|
393
|
+
const idx = this.pending.findIndex((p) => p.runId === runId);
|
|
394
|
+
if (idx >= 0)
|
|
395
|
+
this.pending.splice(idx, 1);
|
|
396
|
+
this.emit({
|
|
397
|
+
type: 'build-failed',
|
|
398
|
+
runId,
|
|
399
|
+
phase: 'orchestrator',
|
|
400
|
+
reason: 'the build queue state file stayed locked by another process; the build was not started',
|
|
401
|
+
});
|
|
402
|
+
if (this.pending.length === 0 && this.currentRunId === null)
|
|
403
|
+
this.markIdle();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const timer = setTimeout(() => void this.tryDispatch(), 100 * attempts);
|
|
407
|
+
if (typeof timer.unref === 'function')
|
|
408
|
+
timer.unref();
|
|
409
|
+
}
|
|
94
410
|
emit(e) {
|
|
95
411
|
this.emitter.emit('event', e);
|
|
96
412
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/ultraapp/build.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/ultraapp/build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UltraApp's acceptance contracts — the only mode whose contract is on by default.
|
|
3
|
+
*
|
|
4
|
+
* It earns that because it already ran most of these commands, and because its
|
|
5
|
+
* own documentation has claimed two gates that the code never enforced:
|
|
6
|
+
*
|
|
7
|
+
* 1. `conventions.ts` §4 states "fix-on-failure runs `npm run smoke` and gates
|
|
8
|
+
* build-success on its passing". It did not — the step list was install /
|
|
9
|
+
* build / test / docker-build, with no smoke anywhere. The claim is now true.
|
|
10
|
+
* 2. `ultraapp.md` recorded, as a known limitation since 4.0.0, that the §7g
|
|
11
|
+
* frontend gate "relies on per-agent honesty about running the screenshot
|
|
12
|
+
* capture", with a follow-up promised to make it structural. The capture is
|
|
13
|
+
* now performed by the runtime against the deployed URL and stored as
|
|
14
|
+
* evidence, so whether it happened is no longer an agent's word.
|
|
15
|
+
*
|
|
16
|
+
* What §7g still does not do is judge the pixels. A human or an agent still
|
|
17
|
+
* decides whether the render is acceptable; the runtime guarantees only that the
|
|
18
|
+
* images exist and are of the deployed app. Calling that visual regression would
|
|
19
|
+
* be the same overclaim this module exists to remove.
|
|
20
|
+
*/
|
|
21
|
+
import type { AcceptanceContract } from '../verify/contract.js';
|
|
22
|
+
/** §7g's two viewports, unchanged from the convention text agents were given. */
|
|
23
|
+
export declare const UI_VIEWPORTS: {
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
label: string;
|
|
27
|
+
}[];
|
|
28
|
+
export interface BuildContractOptions {
|
|
29
|
+
useDocker: boolean;
|
|
30
|
+
fixRounds?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build-stage checks, run in the council's worktree. The order is the old
|
|
34
|
+
* `DEFAULT_STEPS` order, plus the smoke step the docs already promised.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ultraappBuildContract(opts: BuildContractOptions): AcceptanceContract;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a missing/failed screenshot capture should fail the deploy.
|
|
39
|
+
*
|
|
40
|
+
* Default is lenient: capture and store, record a failure, but do not sink a
|
|
41
|
+
* working app because the host has no Chrome. `CLAWO_ULTRAAPP_VISUAL_GATE=strict`
|
|
42
|
+
* makes it binding for setups that want §7g fully enforced.
|
|
43
|
+
*/
|
|
44
|
+
export declare function visualGateIsStrict(env?: NodeJS.ProcessEnv): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Deploy-stage checks, run against the live URL.
|
|
47
|
+
*
|
|
48
|
+
* No health probe here on purpose: `deployArtifact` already polls `/health` and
|
|
49
|
+
* refuses to report success without it, so re-probing would be a second source
|
|
50
|
+
* of truth for a question that is already settled.
|
|
51
|
+
*/
|
|
52
|
+
export declare function ultraappDeployContract(url: string, env?: NodeJS.ProcessEnv): AcceptanceContract;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UltraApp's acceptance contracts — the only mode whose contract is on by default.
|
|
3
|
+
*
|
|
4
|
+
* It earns that because it already ran most of these commands, and because its
|
|
5
|
+
* own documentation has claimed two gates that the code never enforced:
|
|
6
|
+
*
|
|
7
|
+
* 1. `conventions.ts` §4 states "fix-on-failure runs `npm run smoke` and gates
|
|
8
|
+
* build-success on its passing". It did not — the step list was install /
|
|
9
|
+
* build / test / docker-build, with no smoke anywhere. The claim is now true.
|
|
10
|
+
* 2. `ultraapp.md` recorded, as a known limitation since 4.0.0, that the §7g
|
|
11
|
+
* frontend gate "relies on per-agent honesty about running the screenshot
|
|
12
|
+
* capture", with a follow-up promised to make it structural. The capture is
|
|
13
|
+
* now performed by the runtime against the deployed URL and stored as
|
|
14
|
+
* evidence, so whether it happened is no longer an agent's word.
|
|
15
|
+
*
|
|
16
|
+
* What §7g still does not do is judge the pixels. A human or an agent still
|
|
17
|
+
* decides whether the render is acceptable; the runtime guarantees only that the
|
|
18
|
+
* images exist and are of the deployed app. Calling that visual regression would
|
|
19
|
+
* be the same overclaim this module exists to remove.
|
|
20
|
+
*/
|
|
21
|
+
/** §7g's two viewports, unchanged from the convention text agents were given. */
|
|
22
|
+
export const UI_VIEWPORTS = [
|
|
23
|
+
{ width: 1440, height: 900, label: 'desktop' },
|
|
24
|
+
{ width: 375, height: 812, label: 'mobile' },
|
|
25
|
+
];
|
|
26
|
+
const TEN_MIN = 10 * 60_000;
|
|
27
|
+
/**
|
|
28
|
+
* Build-stage checks, run in the council's worktree. The order is the old
|
|
29
|
+
* `DEFAULT_STEPS` order, plus the smoke step the docs already promised.
|
|
30
|
+
*/
|
|
31
|
+
export function ultraappBuildContract(opts) {
|
|
32
|
+
const checks = [
|
|
33
|
+
{ id: 'install', spec: { type: 'command', cmd: 'npm', args: ['install'], timeoutMs: TEN_MIN }, required: true },
|
|
34
|
+
{ id: 'build', spec: { type: 'command', cmd: 'npm', args: ['run', 'build'], timeoutMs: TEN_MIN }, required: true },
|
|
35
|
+
{ id: 'test', spec: { type: 'command', cmd: 'npm', args: ['test'], timeoutMs: TEN_MIN }, required: true },
|
|
36
|
+
];
|
|
37
|
+
if (opts.useDocker) {
|
|
38
|
+
checks.push({
|
|
39
|
+
id: 'docker-build',
|
|
40
|
+
spec: { type: 'command', cmd: 'docker', args: ['build', '-t', 'ultraapp-fix:test', '.'], timeoutMs: TEN_MIN },
|
|
41
|
+
required: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Required, because §4 of the conventions makes `scripts.smoke` mandatory and
|
|
45
|
+
// tells the council the gate is binding. A codebase without it has not met the
|
|
46
|
+
// brief it was given.
|
|
47
|
+
checks.push({
|
|
48
|
+
id: 'smoke',
|
|
49
|
+
spec: { type: 'command', cmd: 'npm', args: ['run', 'smoke'], timeoutMs: TEN_MIN },
|
|
50
|
+
required: true,
|
|
51
|
+
});
|
|
52
|
+
return { id: 'ultraapp-build', checks, fixOnFailureRounds: opts.fixRounds ?? 5 };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether a missing/failed screenshot capture should fail the deploy.
|
|
56
|
+
*
|
|
57
|
+
* Default is lenient: capture and store, record a failure, but do not sink a
|
|
58
|
+
* working app because the host has no Chrome. `CLAWO_ULTRAAPP_VISUAL_GATE=strict`
|
|
59
|
+
* makes it binding for setups that want §7g fully enforced.
|
|
60
|
+
*/
|
|
61
|
+
export function visualGateIsStrict(env = process.env) {
|
|
62
|
+
return (env.CLAWO_ULTRAAPP_VISUAL_GATE || '').toLowerCase() === 'strict';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Deploy-stage checks, run against the live URL.
|
|
66
|
+
*
|
|
67
|
+
* No health probe here on purpose: `deployArtifact` already polls `/health` and
|
|
68
|
+
* refuses to report success without it, so re-probing would be a second source
|
|
69
|
+
* of truth for a question that is already settled.
|
|
70
|
+
*/
|
|
71
|
+
export function ultraappDeployContract(url, env = process.env) {
|
|
72
|
+
return {
|
|
73
|
+
id: 'ultraapp-deploy',
|
|
74
|
+
checks: [
|
|
75
|
+
{
|
|
76
|
+
id: 'frontend-gate',
|
|
77
|
+
spec: { type: 'screenshot', url, viewports: UI_VIEWPORTS },
|
|
78
|
+
required: visualGateIsStrict(env),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../src/ultraapp/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE;IAC9C,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE;CAC7C,CAAC;AAEF,MAAM,OAAO,GAAG,EAAE,GAAG,MAAM,CAAC;AAO5B;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAA0B;IAC9D,MAAM,MAAM,GAAoB;QAC9B,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;QAC/G,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;QAClH,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;KAC1G,CAAC;IACF,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,cAAc;YAClB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,mBAAmB,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE;YAC7G,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;IACL,CAAC;IACD,8EAA8E;IAC9E,+EAA+E;IAC/E,sBAAsB;IACtB,MAAM,CAAC,IAAI,CAAC;QACV,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE;QACjF,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,OAAO,EAAE,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;AACnF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACrE,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC;AAC3E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW,EAAE,MAAyB,OAAO,CAAC,GAAG;IACtF,OAAO;QACL,EAAE,EAAE,iBAAiB;QACrB,MAAM,EAAE;YACN;gBACE,EAAE,EAAE,eAAe;gBACnB,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,YAAY,EAAE;gBAC1D,QAAQ,EAAE,kBAAkB,CAAC,GAAG,CAAC;aAClC;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -92,8 +92,9 @@ internally. ENTRYPOINT runs the production server. Build args:
|
|
|
92
92
|
Smoke test: package.json must expose "scripts.smoke" that drives ONE complete
|
|
93
93
|
job using AppSpec.inputs[].examples[0].ref files copied into the image at
|
|
94
94
|
build time under $DATA_DIR/_smoke/. The smoke test asserts pipeline completes and
|
|
95
|
-
output matches AppSpec.outputs[].type.
|
|
96
|
-
gates build-success on its
|
|
95
|
+
output matches AppSpec.outputs[].type. The orchestrator runs "npm run smoke"
|
|
96
|
+
itself as part of the build acceptance contract and gates build-success on its
|
|
97
|
+
exit code — a codebase without a working "scripts.smoke" does not build.
|
|
97
98
|
|
|
98
99
|
Smoke test must complete in < 90 seconds for the reference-trace inputs
|
|
99
100
|
provided. If your pipeline genuinely takes longer, design the smoke test to
|
|
@@ -205,6 +206,12 @@ tags, class names, and \`@media\` queries don't tell you what the page
|
|
|
205
206
|
actually looks like. You MUST capture screenshots and look at the pixels
|
|
206
207
|
before voting.
|
|
207
208
|
|
|
209
|
+
Note on who checks what: after deployment the orchestrator captures both
|
|
210
|
+
viewports against the live URL itself and stores the PNGs as run evidence,
|
|
211
|
+
so whether a capture happened is no longer taken on trust. It does not
|
|
212
|
+
judge the rendering — that is still your job, and it is why the steps
|
|
213
|
+
below are yours to perform in your own worktree before you vote.
|
|
214
|
+
|
|
208
215
|
Before voting [CONSENSUS: YES], EVERY agent MUST:
|
|
209
216
|
|
|
210
217
|
1. Start the dev/preview server in their worktree (\`npm run preview\` or
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../../src/ultraapp/conventions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG
|
|
1
|
+
{"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../../src/ultraapp/conventions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsPxC,CAAC,IAAI,EAAE,CAAC"}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Run the verification pipeline in a worktree, spawn a session to fix mechanical
|
|
3
|
+
* errors on red, retry up to N rounds.
|
|
4
|
+
*
|
|
5
|
+
* As of 6.0.0 this is an adapter over `src/verify/`, not its own runner. The
|
|
6
|
+
* logic it used to own — ordered steps, exit-code gating, byte-capped tails, the
|
|
7
|
+
* fix loop — moved out to become mode-agnostic, and came back with the two
|
|
8
|
+
* things it was missing: a per-step timeout (a wedged `npm test` used to hang
|
|
9
|
+
* the pipeline forever) and an honoured `required` flag (the field was declared
|
|
10
|
+
* here and never read, so every step was fatal).
|
|
4
11
|
*
|
|
5
12
|
* Not to be confused with src/autoloop/, which is a planner/coder/reviewer
|
|
6
13
|
* message-bus orchestrator for a different problem shape.
|
|
7
14
|
*/
|
|
15
|
+
import type { AcceptanceContract, CheckResult } from '../verify/contract.js';
|
|
8
16
|
export interface ShellResult {
|
|
9
17
|
ok: boolean;
|
|
10
18
|
stdout: string;
|
|
@@ -24,16 +32,27 @@ export interface FixOnFailureArgs {
|
|
|
24
32
|
maxRounds: number;
|
|
25
33
|
shell?: ShellRunner;
|
|
26
34
|
spawnFixer?: FixerSpawner;
|
|
35
|
+
/** Legacy step list. Each entry becomes a `command` check. */
|
|
27
36
|
steps?: Array<{
|
|
28
37
|
cmd: string;
|
|
29
38
|
args: string[];
|
|
30
39
|
required?: boolean;
|
|
40
|
+
timeoutMs?: number;
|
|
31
41
|
}>;
|
|
42
|
+
/** Full acceptance contract. Takes precedence over `steps`. */
|
|
43
|
+
contract?: AcceptanceContract;
|
|
44
|
+
/** Where screenshot-style checks drop files. Defaults to a temp dir. */
|
|
45
|
+
artifactDir?: string;
|
|
32
46
|
}
|
|
33
47
|
export interface FixOnFailureResult {
|
|
34
48
|
ok: boolean;
|
|
35
49
|
rounds: number;
|
|
36
50
|
lastError?: string;
|
|
37
51
|
failingCommand?: string;
|
|
52
|
+
/** Per-check outcomes, for an evidence bundle. Absent on stubbed results. */
|
|
53
|
+
results?: CheckResult[];
|
|
38
54
|
}
|
|
55
|
+
export declare const DEFAULT_STEPS: NonNullable<FixOnFailureArgs['steps']>;
|
|
56
|
+
/** Translate the legacy step list into contract checks, `required` included this time. */
|
|
57
|
+
export declare function stepsToContract(steps: NonNullable<FixOnFailureArgs['steps']>, fixOnFailureRounds: number): AcceptanceContract;
|
|
39
58
|
export declare function runFixOnFailure(args: FixOnFailureArgs): Promise<FixOnFailureResult>;
|