@ordewell/core 0.11.3 → 0.12.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/AUTHORS.md +3 -1
- package/dist/{IRunner-ihalYfTo.d.mts → IRunner-BwzyUVIL.d.mts} +62 -18
- package/dist/{IRunner-DOMtDTVt.d.ts → IRunner-DjJkYwko.d.ts} +62 -18
- package/dist/{ModeResolver-DyXeZ9ib.d.ts → ModeResolver-CHsaoV7M.d.ts} +1 -1
- package/dist/{ModeResolver-BDAgouuE.d.mts → ModeResolver-b_7bPlBS.d.mts} +1 -1
- package/dist/{Task-BL4Idm9r.d.mts → Task-BTEH-ztX.d.mts} +301 -26
- package/dist/{Task-BL4Idm9r.d.ts → Task-BTEH-ztX.d.ts} +301 -26
- package/dist/{chunk-MYXMIVWA.mjs → chunk-BFLVOYNR.mjs} +3 -3
- package/dist/{chunk-YAFNYNFX.mjs → chunk-BKTDYGIP.mjs} +14 -2
- package/dist/chunk-BKTDYGIP.mjs.map +1 -0
- package/dist/{chunk-QTZ5XTBY.mjs → chunk-F6WS3JLQ.mjs} +446 -10
- package/dist/chunk-F6WS3JLQ.mjs.map +1 -0
- package/dist/{chunk-7WNLN63J.mjs → chunk-HP2LNKST.mjs} +315 -51
- package/dist/chunk-HP2LNKST.mjs.map +1 -0
- package/dist/index.d.mts +239 -78
- package/dist/index.d.ts +239 -78
- package/dist/index.js +5120 -1715
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4247 -1515
- package/dist/index.mjs.map +1 -1
- package/dist/parsing.d.mts +2 -2
- package/dist/parsing.d.ts +2 -2
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +4 -4
- package/dist/plan-utils.d.ts +4 -4
- package/dist/plan-utils.js +135 -19
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +25 -3
- package/dist/{runSettingsText-BuLVBlXS.d.mts → runSettingsText-CvZ40IPv.d.ts} +1083 -190
- package/dist/{runSettingsText-DVOd7vKC.d.ts → runSettingsText-x-YML2ay.d.mts} +1083 -190
- package/dist/testing.d.mts +18 -2
- package/dist/testing.d.ts +18 -2
- package/dist/testing.js +71 -7
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +68 -7
- package/dist/testing.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-7WNLN63J.mjs.map +0 -1
- package/dist/chunk-QTZ5XTBY.mjs.map +0 -1
- package/dist/chunk-YAFNYNFX.mjs.map +0 -1
- /package/dist/{chunk-MYXMIVWA.mjs.map → chunk-BFLVOYNR.mjs.map} +0 -0
|
@@ -146,7 +146,16 @@ var isKnownStatus = (status) => Object.prototype.hasOwnProperty.call(ISOLATION_S
|
|
|
146
146
|
function changedReposOf(record) {
|
|
147
147
|
return Object.entries(record.repos).filter(([, r]) => r.changed).map(([repoPath]) => repoPath);
|
|
148
148
|
}
|
|
149
|
-
function
|
|
149
|
+
function landedInRepo(run, record, repoPath) {
|
|
150
|
+
return !!record.repos[repoPath]?.changed || run.landings?.[record.taskId]?.[repoPath] !== void 0;
|
|
151
|
+
}
|
|
152
|
+
function revertible(record, run) {
|
|
153
|
+
if (record.inHead || record.status === "active" || record.status === "repairing" || !run?.carried || run.carried[record.taskId]) return false;
|
|
154
|
+
const landings = run?.landings?.[record.taskId];
|
|
155
|
+
const changed = record.status === "merged" ? changedReposOf(record) : [];
|
|
156
|
+
return (changed.length > 0 || landings !== void 0) && changed.every((repo) => record.repos[repo].landedTip || landings?.[repo]);
|
|
157
|
+
}
|
|
158
|
+
function taskIsolationOf(record, repairLimit, run) {
|
|
150
159
|
return {
|
|
151
160
|
state: ISOLATION_STATE[record.status],
|
|
152
161
|
branch: record.branch,
|
|
@@ -155,9 +164,20 @@ function taskIsolationOf(record, repairLimit) {
|
|
|
155
164
|
...record.conflictRepo ? { conflictRepo: record.conflictRepo } : {},
|
|
156
165
|
...record.conflictFiles?.length ? { conflictFiles: record.conflictFiles } : {},
|
|
157
166
|
...record.repairs && repairLimit !== void 0 ? { repair: { attempt: record.repairs, limit: repairLimit } } : {},
|
|
158
|
-
...record.repairedFiles?.length ? { repairedFiles: record.repairedFiles } : {}
|
|
167
|
+
...record.repairedFiles?.length ? { repairedFiles: record.repairedFiles } : {},
|
|
168
|
+
...revertible(record, run) ? { revertible: true } : {}
|
|
159
169
|
};
|
|
160
170
|
}
|
|
171
|
+
function taskIsolationsOf(run, repairLimit) {
|
|
172
|
+
const tasks = {};
|
|
173
|
+
for (const taskId of Object.keys(run.landings ?? {})) {
|
|
174
|
+
if (run.carried && !run.carried[taskId]) tasks[taskId] = { state: "none", revertible: true };
|
|
175
|
+
}
|
|
176
|
+
for (const [taskId, record] of Object.entries(run.tasks)) {
|
|
177
|
+
if (isKnownStatus(record.status)) tasks[taskId] = taskIsolationOf(record, repairLimit, run);
|
|
178
|
+
}
|
|
179
|
+
return tasks;
|
|
180
|
+
}
|
|
161
181
|
function handoffOf(run) {
|
|
162
182
|
const merged = Object.values(run.tasks).filter((r) => r.status === "merged" && !r.inHead).sort((a, b) => a.order - b.order);
|
|
163
183
|
const entry = (r) => ({
|
|
@@ -173,7 +193,7 @@ function handoffOf(run) {
|
|
|
173
193
|
baseRef: repo.baseRef,
|
|
174
194
|
...repo.baseBranch ? { baseBranch: repo.baseBranch } : {},
|
|
175
195
|
...repo.startedDetached ? { startedDetached: true } : {},
|
|
176
|
-
landed: merged.filter((r) => r
|
|
196
|
+
landed: merged.filter((r) => landedInRepo(run, r, repo.path)).map(entry)
|
|
177
197
|
})),
|
|
178
198
|
landed: merged.map(entry)
|
|
179
199
|
};
|
|
@@ -225,12 +245,396 @@ function carriesIsolation(plan) {
|
|
|
225
245
|
function isolationViewOfPlan(plan, repairLimit) {
|
|
226
246
|
if (!carriesIsolation(plan)) return null;
|
|
227
247
|
const { run } = migratePlanIsolation(plan.isolation);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
248
|
+
return { tasks: taskIsolationsOf(run, repairLimit), handoff: handoffOf(run) };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/services/VerdictEngine.ts
|
|
252
|
+
var IDLE_TIMEOUT_MS = 10 * 6e4;
|
|
253
|
+
var VerdictEngine = class {
|
|
254
|
+
pendingVerdicts = /* @__PURE__ */ new Map();
|
|
255
|
+
sessions = /* @__PURE__ */ new Map();
|
|
256
|
+
listeners = [];
|
|
257
|
+
checkpointListeners = [];
|
|
258
|
+
withdrawnListeners = [];
|
|
259
|
+
/** The open `checkpoint` tool call per task (ADR-0022, V5): settling it is how an answer reaches the runner. */
|
|
260
|
+
toolCheckpoints = /* @__PURE__ */ new Map();
|
|
261
|
+
/** The question a task's checkpoint asks, whole, for as long as it waits. */
|
|
262
|
+
checkpointQuestions = /* @__PURE__ */ new Map();
|
|
263
|
+
idleListeners = [];
|
|
264
|
+
/**
|
|
265
|
+
* Per-task generation. Replaced on every watch(), clear() and verdict.
|
|
266
|
+
* Stale callbacks (from a prior session whose generation doesn't match
|
|
267
|
+
* the current one) bail out instead of delivering a verdict for the
|
|
268
|
+
* wrong session.
|
|
269
|
+
*/
|
|
270
|
+
generations = /* @__PURE__ */ new Map();
|
|
271
|
+
/**
|
|
272
|
+
* Generations come from one counter that {@link reset} never rewinds: a
|
|
273
|
+
* per-task count restarted at 1 after a reset, and the next watch handed a
|
|
274
|
+
* session that outlived the reset the same generation as its successor.
|
|
275
|
+
*/
|
|
276
|
+
lastGeneration = 0;
|
|
277
|
+
idleTimers = /* @__PURE__ */ new Map();
|
|
278
|
+
idleSince = /* @__PURE__ */ new Map();
|
|
279
|
+
/** Tasks waiting on the user, whose silence is expected rather than a sign of a stuck runner. */
|
|
280
|
+
idlePaused = /* @__PURE__ */ new Set();
|
|
281
|
+
/**
|
|
282
|
+
* Open tool approvals per structured task. They arrive mid-turn and leave the
|
|
283
|
+
* status alone (ADR-0018, W1), but a task waiting on one is waiting on the
|
|
284
|
+
* user, so it is not idle either.
|
|
285
|
+
*/
|
|
286
|
+
openApprovals = /* @__PURE__ */ new Map();
|
|
287
|
+
onVerdict(listener) {
|
|
288
|
+
this.listeners.push(listener);
|
|
289
|
+
}
|
|
290
|
+
onCheckpoint(listener) {
|
|
291
|
+
this.checkpointListeners.push(listener);
|
|
292
|
+
}
|
|
293
|
+
onCheckpointWithdrawn(listener) {
|
|
294
|
+
this.withdrawnListeners.push(listener);
|
|
295
|
+
}
|
|
296
|
+
onIdleChange(listener) {
|
|
297
|
+
this.idleListeners.push(listener);
|
|
298
|
+
}
|
|
299
|
+
/** Advisory silence timestamp for a task, or null if it isn't idle. */
|
|
300
|
+
getIdleSince(taskId) {
|
|
301
|
+
return this.idleSince.get(taskId) ?? null;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Stop watching a task's silence while it waits on the user (ADR-0018, W1).
|
|
305
|
+
* Watching resumes when its next turn starts, or when a checkpoint is answered.
|
|
306
|
+
*/
|
|
307
|
+
pauseIdle(taskId) {
|
|
308
|
+
this.idlePaused.add(taskId);
|
|
309
|
+
this.clearIdle(taskId);
|
|
310
|
+
}
|
|
311
|
+
resumeIdle(taskId) {
|
|
312
|
+
const gen = this.generations.get(taskId);
|
|
313
|
+
if (!this.idlePaused.delete(taskId) || gen === void 0) return;
|
|
314
|
+
this.touchIdle(taskId, gen);
|
|
315
|
+
}
|
|
316
|
+
/** Restart the silence timer on fresh output; broadcasts the null transition if it was idle. */
|
|
317
|
+
touchIdle(taskId, gen) {
|
|
318
|
+
if (this.idlePaused.has(taskId) || this.openApprovals.get(taskId)?.size) return;
|
|
319
|
+
const existing = this.idleTimers.get(taskId);
|
|
320
|
+
if (existing) clearTimeout(existing);
|
|
321
|
+
if (this.idleSince.get(taskId)) {
|
|
322
|
+
this.idleSince.set(taskId, null);
|
|
323
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
324
|
+
}
|
|
325
|
+
this.idleTimers.set(taskId, setTimeout(() => {
|
|
326
|
+
if (this.generations.get(taskId) !== gen) return;
|
|
327
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
328
|
+
this.idleSince.set(taskId, now);
|
|
329
|
+
for (const l of this.idleListeners) l(taskId, now);
|
|
330
|
+
}, IDLE_TIMEOUT_MS));
|
|
331
|
+
}
|
|
332
|
+
/** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
|
|
333
|
+
clearIdle(taskId) {
|
|
334
|
+
const timer = this.idleTimers.get(taskId);
|
|
335
|
+
if (timer) clearTimeout(timer);
|
|
336
|
+
this.idleTimers.delete(taskId);
|
|
337
|
+
const wasIdle = this.idleSince.get(taskId);
|
|
338
|
+
this.idleSince.delete(taskId);
|
|
339
|
+
if (wasIdle) {
|
|
340
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/** What a task's waiting checkpoint asks, untruncated; undefined when none waits. */
|
|
344
|
+
getCheckpointQuestion(taskId) {
|
|
345
|
+
return this.checkpointQuestions.get(taskId);
|
|
346
|
+
}
|
|
347
|
+
approveCheckpoint(taskId) {
|
|
348
|
+
this.checkpointQuestions.delete(taskId);
|
|
349
|
+
this.resumeIdle(taskId);
|
|
350
|
+
this.toolCheckpoints.get(taskId)?.({ kind: "continue" });
|
|
351
|
+
}
|
|
352
|
+
rejectCheckpoint(taskId, reason) {
|
|
353
|
+
this.checkpointQuestions.delete(taskId);
|
|
354
|
+
this.resumeIdle(taskId);
|
|
355
|
+
this.toolCheckpoints.get(taskId)?.({ kind: "rejected", reason });
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* An attempt's runner is being started, with no session to watch yet. Its
|
|
359
|
+
* silence counts from here, so one whose start never resolves — stuck
|
|
360
|
+
* launching, or waiting for its tools to attach — is flagged quiet as well.
|
|
361
|
+
* {@link watch} starts the count again once the runner is up.
|
|
362
|
+
*/
|
|
363
|
+
starting(taskId) {
|
|
364
|
+
const gen = this.bumpGeneration(taskId);
|
|
365
|
+
this.idlePaused.delete(taskId);
|
|
366
|
+
this.openApprovals.delete(taskId);
|
|
367
|
+
this.touchIdle(taskId, gen);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Attach to a spawned session. The runner's `task_complete` call on the
|
|
371
|
+
* attempt's token is the only completion evidence (ADR-0022, V2); a session
|
|
372
|
+
* that exits before one gets a failed verdict. A script task's session has
|
|
373
|
+
* no runner to call it: its exit status is the evidence instead (ADR-0035).
|
|
374
|
+
*
|
|
375
|
+
* Returns the attempt's generation, what {@link signalComplete} is checked against.
|
|
376
|
+
*/
|
|
377
|
+
watch(task, session) {
|
|
378
|
+
const gen = this.bumpGeneration(task.id);
|
|
379
|
+
this.idlePaused.delete(task.id);
|
|
380
|
+
this.openApprovals.delete(task.id);
|
|
381
|
+
this.touchIdle(task.id, gen);
|
|
382
|
+
session.onOutput(() => {
|
|
383
|
+
if (this.generations.get(task.id) === gen) this.touchIdle(task.id, gen);
|
|
384
|
+
});
|
|
385
|
+
this.sessions.set(task.id, session);
|
|
386
|
+
session.onTurnEnd(() => {
|
|
387
|
+
if (this.generations.get(task.id) !== gen) return;
|
|
388
|
+
this.callWentAway(task.id, "the turn it was asked in has ended.");
|
|
389
|
+
const pending = this.pendingVerdicts.get(task.id);
|
|
390
|
+
this.pendingVerdicts.delete(task.id);
|
|
391
|
+
if (session.turnState() === "idle" && pending) this.publishVerdict(task.id, pending);
|
|
392
|
+
});
|
|
393
|
+
session.onEvent((event) => {
|
|
394
|
+
if (this.generations.get(task.id) !== gen) return;
|
|
395
|
+
if (event.type === "turn_start") {
|
|
396
|
+
this.supersede(task.id);
|
|
397
|
+
this.resumeIdle(task.id);
|
|
398
|
+
} else if (event.type === "message_delivered") this.supersede(task.id);
|
|
399
|
+
else if (event.type === "permission_request" && !event.decided) this.approvalOpened(task.id, event.id);
|
|
400
|
+
else if (event.type === "permission_decided" || event.type === "permission_withdrawn") this.approvalClosed(task.id, event.id, gen);
|
|
401
|
+
});
|
|
402
|
+
session.onTaskComplete((report) => this.signalComplete(task.id, gen, report));
|
|
403
|
+
session.onScriptExit((exit) => this.signalScriptExit(task.id, gen, exit));
|
|
404
|
+
session.onToolCheckpoint((question, signal) => this.raiseCheckpoint(task.id, gen, question, signal));
|
|
405
|
+
session.onExit((exitCode) => {
|
|
406
|
+
if (this.generations.get(task.id) !== gen) return;
|
|
407
|
+
this.forget(task.id);
|
|
408
|
+
const verdict = exitVerdict(exitCode);
|
|
409
|
+
for (const l of this.listeners) l(task.id, verdict);
|
|
410
|
+
});
|
|
411
|
+
return gen;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* The runner's own `task_complete` call (ADR-0022, V1/V3): settles the
|
|
415
|
+
* attempt, unless that attempt is no longer the task's current one or an
|
|
416
|
+
* earlier call already settled it.
|
|
417
|
+
*/
|
|
418
|
+
signalComplete(taskId, generation, report) {
|
|
419
|
+
if (this.generations.get(taskId) !== generation) return;
|
|
420
|
+
this.acceptVerdict(taskId, reportedVerdict(report));
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* A script task's exit status (ADR-0035): its one evidence, as a runner's
|
|
424
|
+
* `task_complete` call is a runner's. It settles the attempt unless that
|
|
425
|
+
* attempt is no longer the task's current one.
|
|
426
|
+
*/
|
|
427
|
+
signalScriptExit(taskId, generation, exit) {
|
|
428
|
+
if (this.generations.get(taskId) !== generation) return;
|
|
429
|
+
this.acceptVerdict(taskId, scriptVerdict(exit));
|
|
430
|
+
}
|
|
431
|
+
/** The runner was told something after its evidence so far, so only what it reports from here counts. */
|
|
432
|
+
supersede(taskId) {
|
|
433
|
+
this.pendingVerdicts.delete(taskId);
|
|
434
|
+
}
|
|
435
|
+
acceptVerdict(taskId, verdict) {
|
|
436
|
+
if (this.pendingVerdicts.has(taskId)) return;
|
|
437
|
+
if (this.sessions.get(taskId)?.queued().length) {
|
|
438
|
+
this.pendingVerdicts.set(taskId, verdict);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
this.publishVerdict(taskId, verdict);
|
|
442
|
+
}
|
|
443
|
+
publishVerdict(taskId, verdict) {
|
|
444
|
+
this.forget(taskId);
|
|
445
|
+
this.bumpGeneration(taskId);
|
|
446
|
+
for (const l of this.listeners) l(taskId, verdict);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* The runner's `checkpoint` call (ADR-0022, V5): the task waits on the
|
|
450
|
+
* user until it is settled by {@link approveCheckpoint} or {@link rejectCheckpoint}. It is
|
|
451
|
+
* withdrawn, never left hanging, once the attempt is over or the call goes.
|
|
452
|
+
*/
|
|
453
|
+
raiseCheckpoint(taskId, generation, question, signal) {
|
|
454
|
+
if (this.generations.get(taskId) !== generation || signal.aborted) {
|
|
455
|
+
return Promise.resolve({ kind: "withdrawn", why: "this attempt has ended." });
|
|
456
|
+
}
|
|
457
|
+
if (this.toolCheckpoints.has(taskId)) {
|
|
458
|
+
return Promise.resolve({ kind: "withdrawn", why: "another checkpoint is still waiting for an answer. Wait for it before asking again." });
|
|
459
|
+
}
|
|
460
|
+
return new Promise((resolve) => {
|
|
461
|
+
const settle = (answer) => {
|
|
462
|
+
signal.removeEventListener("abort", onAbort);
|
|
463
|
+
if (this.toolCheckpoints.get(taskId) === settle) {
|
|
464
|
+
this.toolCheckpoints.delete(taskId);
|
|
465
|
+
this.checkpointQuestions.delete(taskId);
|
|
466
|
+
}
|
|
467
|
+
resolve(answer);
|
|
468
|
+
};
|
|
469
|
+
const onAbort = () => {
|
|
470
|
+
settle({ kind: "withdrawn", why: "the call was cancelled." });
|
|
471
|
+
if (this.generations.get(taskId) !== generation) return;
|
|
472
|
+
this.resumeIdle(taskId);
|
|
473
|
+
for (const l of this.withdrawnListeners) l(taskId);
|
|
474
|
+
};
|
|
475
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
476
|
+
this.toolCheckpoints.set(taskId, settle);
|
|
477
|
+
this.checkpointQuestions.set(taskId, question.trim());
|
|
478
|
+
for (const l of this.checkpointListeners) l(taskId, question.trim());
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
/** The open `checkpoint` call can no longer be answered: refuse it, and let the task out of the wait. */
|
|
482
|
+
callWentAway(taskId, why) {
|
|
483
|
+
const open = this.toolCheckpoints.get(taskId);
|
|
484
|
+
if (!open) return;
|
|
485
|
+
open({ kind: "withdrawn", why });
|
|
486
|
+
this.resumeIdle(taskId);
|
|
487
|
+
for (const l of this.withdrawnListeners) l(taskId);
|
|
488
|
+
}
|
|
489
|
+
withdrawToolCheckpoint(taskId) {
|
|
490
|
+
this.toolCheckpoints.get(taskId)?.({ kind: "withdrawn", why: "this attempt has ended." });
|
|
491
|
+
}
|
|
492
|
+
approvalOpened(taskId, approvalId) {
|
|
493
|
+
const open = this.openApprovals.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
494
|
+
open.add(approvalId);
|
|
495
|
+
this.openApprovals.set(taskId, open);
|
|
496
|
+
this.clearIdle(taskId);
|
|
497
|
+
}
|
|
498
|
+
approvalClosed(taskId, approvalId, gen) {
|
|
499
|
+
const open = this.openApprovals.get(taskId);
|
|
500
|
+
if (!open?.delete(approvalId) || open.size) return;
|
|
501
|
+
this.openApprovals.delete(taskId);
|
|
502
|
+
this.touchIdle(taskId, gen);
|
|
503
|
+
}
|
|
504
|
+
bumpGeneration(taskId) {
|
|
505
|
+
this.lastGeneration += 1;
|
|
506
|
+
this.generations.set(taskId, this.lastGeneration);
|
|
507
|
+
return this.lastGeneration;
|
|
508
|
+
}
|
|
509
|
+
forget(taskId) {
|
|
510
|
+
this.pendingVerdicts.delete(taskId);
|
|
511
|
+
this.sessions.delete(taskId);
|
|
512
|
+
this.withdrawToolCheckpoint(taskId);
|
|
513
|
+
this.checkpointQuestions.delete(taskId);
|
|
514
|
+
this.idlePaused.delete(taskId);
|
|
515
|
+
this.openApprovals.delete(taskId);
|
|
516
|
+
this.clearIdle(taskId);
|
|
517
|
+
}
|
|
518
|
+
/** Manual "Mark complete" override: a pass verdict that bypasses evidence. */
|
|
519
|
+
markComplete(task) {
|
|
520
|
+
this.ceaseTracking(task.id);
|
|
521
|
+
return {
|
|
522
|
+
outcome: "pass",
|
|
523
|
+
reason: "Manually marked complete by user.",
|
|
524
|
+
checks: [
|
|
525
|
+
{
|
|
526
|
+
name: "manual",
|
|
527
|
+
passed: true,
|
|
528
|
+
skipped: false,
|
|
529
|
+
detail: "Task was manually marked complete by the user; no automatic verification was performed."
|
|
530
|
+
}
|
|
531
|
+
],
|
|
532
|
+
decidedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
/** Clear verification state for a task (used on retry). */
|
|
536
|
+
clear(task) {
|
|
537
|
+
this.ceaseTracking(task.id);
|
|
538
|
+
}
|
|
539
|
+
/** Drop a task's verification state and invalidate any callbacks still holding its generation. */
|
|
540
|
+
ceaseTracking(taskId) {
|
|
541
|
+
this.forget(taskId);
|
|
542
|
+
this.bumpGeneration(taskId);
|
|
543
|
+
}
|
|
544
|
+
/** Drop all tracking state (used on stop / loadPlan). */
|
|
545
|
+
reset() {
|
|
546
|
+
this.pendingVerdicts.clear();
|
|
547
|
+
this.sessions.clear();
|
|
548
|
+
for (const taskId of [...this.toolCheckpoints.keys()]) this.withdrawToolCheckpoint(taskId);
|
|
549
|
+
this.checkpointQuestions.clear();
|
|
550
|
+
for (const timer of this.idleTimers.values()) clearTimeout(timer);
|
|
551
|
+
this.idleTimers.clear();
|
|
552
|
+
this.idleSince.clear();
|
|
553
|
+
this.idlePaused.clear();
|
|
554
|
+
this.openApprovals.clear();
|
|
555
|
+
this.generations.clear();
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
var EXIT_CHECK = "exit_code";
|
|
559
|
+
function endedByExit(verdict) {
|
|
560
|
+
return verdict.checks.some((check) => check.name === EXIT_CHECK);
|
|
561
|
+
}
|
|
562
|
+
function exitVerdict(exitCode) {
|
|
563
|
+
const normalized = exitCode == null ? 0 : exitCode;
|
|
564
|
+
const exitOk = normalized === 0;
|
|
565
|
+
return {
|
|
566
|
+
outcome: "fail",
|
|
567
|
+
reason: exitOk ? "Failed verification: the runner exited cleanly but never called task_complete." : `Failed verification: the runner exited with code ${normalized} without calling task_complete.`,
|
|
568
|
+
checks: [
|
|
569
|
+
{ name: "task_complete", passed: false, skipped: false, detail: "the runner exited before calling task_complete" },
|
|
570
|
+
{ name: EXIT_CHECK, passed: exitOk, skipped: false, detail: exitOk ? "the runner exited cleanly (code 0)" : `the runner exited with code ${normalized}` }
|
|
571
|
+
],
|
|
572
|
+
decidedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function interruptedVerdict(decidedAt) {
|
|
576
|
+
return { outcome: "fail", reason: "It was interrupted when Ordewell stopped, and nothing is running it now.", checks: [], decidedAt };
|
|
577
|
+
}
|
|
578
|
+
function statusText(status) {
|
|
579
|
+
if (status.error !== void 0) return `could not start: ${status.error}`;
|
|
580
|
+
if (status.signal) return `was ended by ${status.signal}`;
|
|
581
|
+
return `exited with code ${status.code ?? "unknown"}`;
|
|
582
|
+
}
|
|
583
|
+
function withTail(reason, errorTail, fullOutput) {
|
|
584
|
+
const tail = errorTail.trim();
|
|
585
|
+
const quoted = tail ? `${reason}
|
|
586
|
+
|
|
587
|
+
The end of its error output:
|
|
588
|
+
${tail}` : reason;
|
|
589
|
+
if (!fullOutput) return quoted;
|
|
590
|
+
const cut = fullOutput.droppedChars > 0 ? `, except its last ${fullOutput.droppedChars} characters, which were not saved.` : "";
|
|
591
|
+
return `${quoted}
|
|
592
|
+
|
|
593
|
+
All of its output is in ${fullOutput.file}${cut}`;
|
|
594
|
+
}
|
|
595
|
+
function scriptVerdict(exit) {
|
|
596
|
+
const decidedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
597
|
+
const check = (passed, detail) => [{ name: "script_exit", passed, skipped: false, detail }];
|
|
598
|
+
const command = exit.outcome !== "passed" && exit.command !== void 0 ? `\`${exit.command}\`` : void 0;
|
|
599
|
+
switch (exit.outcome) {
|
|
600
|
+
case "passed":
|
|
601
|
+
return { outcome: "pass", reason: "Verified: every step of the script exited 0.", checks: check(true, "every step exited 0"), decidedAt, reported: "done" };
|
|
602
|
+
case "failed": {
|
|
603
|
+
const said = `${command ?? "it"} ${statusText(exit.status)}`;
|
|
604
|
+
const reported = exit.status.error !== void 0 ? "blocked" : "failed";
|
|
605
|
+
return { outcome: "fail", reason: withTail(`The script failed: ${said}.`, exit.errorTail, exit.fullOutput), checks: check(false, said), decidedAt, reported };
|
|
606
|
+
}
|
|
607
|
+
case "timed-out": {
|
|
608
|
+
const said = `the time limit of ${durationText(exit.timeoutMs)} ran out${command ? ` while ${command} ran` : ""}, so it was ended`;
|
|
609
|
+
return { outcome: "fail", reason: withTail(`The script failed: ${said}.`, exit.errorTail, exit.fullOutput), checks: check(false, said), decidedAt, reported: "failed" };
|
|
610
|
+
}
|
|
611
|
+
case "stopped": {
|
|
612
|
+
const said = `it was stopped${command ? ` while ${command} ran` : ""}`;
|
|
613
|
+
return { outcome: "fail", reason: withTail(`The script did not finish: ${said}.`, "", exit.fullOutput), checks: check(false, said), decidedAt };
|
|
614
|
+
}
|
|
232
615
|
}
|
|
233
|
-
|
|
616
|
+
}
|
|
617
|
+
function durationText(ms) {
|
|
618
|
+
if (ms % 6e4 === 0) return `${ms / 6e4} minute${ms === 6e4 ? "" : "s"}`;
|
|
619
|
+
const seconds = Math.round(ms / 100) / 10;
|
|
620
|
+
return `${seconds} second${seconds === 1 ? "" : "s"}`;
|
|
621
|
+
}
|
|
622
|
+
function reportedVerdict(report) {
|
|
623
|
+
const checks = [
|
|
624
|
+
{
|
|
625
|
+
name: "task_complete",
|
|
626
|
+
passed: report.status === "done",
|
|
627
|
+
skipped: false,
|
|
628
|
+
detail: `the runner called task_complete with status "${report.status}"`
|
|
629
|
+
}
|
|
630
|
+
];
|
|
631
|
+
const decidedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
632
|
+
const reported = report.status;
|
|
633
|
+
if (report.status === "done") {
|
|
634
|
+
return { outcome: "pass", reason: "Verified: the runner reported completion through task_complete. Task completed successfully.", checks, decidedAt, reported };
|
|
635
|
+
}
|
|
636
|
+
const why = report.reason?.trim() || report.summary?.trim().split(/\n\s*\n/)[0].trim() || "no reason given";
|
|
637
|
+
return { outcome: "fail", reason: `The runner reported the task ${report.status}: ${why}`, checks, decidedAt, reported };
|
|
234
638
|
}
|
|
235
639
|
|
|
236
640
|
// src/services/TaskRefs.ts
|
|
@@ -301,6 +705,30 @@ function migrateLoadedTasks(tasks) {
|
|
|
301
705
|
migrateLoadedTasks(task.subtasks ?? []);
|
|
302
706
|
}
|
|
303
707
|
}
|
|
708
|
+
function failInterruptedTasks(tasks, now) {
|
|
709
|
+
for (const task of flattenTasks(tasks)) {
|
|
710
|
+
if (task.status !== "in_progress") continue;
|
|
711
|
+
task.status = "failed";
|
|
712
|
+
task.verdict = interruptedVerdict(now);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function releaseMadeManual(tasks, live = () => false) {
|
|
716
|
+
for (const task of flattenTasks(tasks)) {
|
|
717
|
+
if (task.type !== "user" || live(task.id) || task.status !== "failed" && task.status !== "awaiting_user") continue;
|
|
718
|
+
task.status = "pending";
|
|
719
|
+
delete task.awaitingReason;
|
|
720
|
+
delete task.resumeAt;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function settleOwedRepairs(plan) {
|
|
724
|
+
const records = plan.isolation?.run.tasks;
|
|
725
|
+
if (!records) return;
|
|
726
|
+
for (const task of flattenTasks(plan.tasks)) {
|
|
727
|
+
if (task.status !== "pending" && task.status !== "approved" || records[task.id]?.status !== "conflict") continue;
|
|
728
|
+
task.status = "awaiting_user";
|
|
729
|
+
task.awaitingReason = "conflict";
|
|
730
|
+
}
|
|
731
|
+
}
|
|
304
732
|
function dropRetiredPlanFields(plan) {
|
|
305
733
|
const raw = plan;
|
|
306
734
|
delete raw.researchBudget;
|
|
@@ -599,6 +1027,7 @@ function keepExecutionState(current, rewrite) {
|
|
|
599
1027
|
...t,
|
|
600
1028
|
status: prior?.status ?? "pending",
|
|
601
1029
|
skipReason: prior?.skipReason,
|
|
1030
|
+
reverted: prior?.reverted,
|
|
602
1031
|
verdict: prior?.verdict,
|
|
603
1032
|
outputSummary: prior?.outputSummary,
|
|
604
1033
|
handoff: prior?.handoff,
|
|
@@ -609,6 +1038,7 @@ function keepExecutionState(current, rewrite) {
|
|
|
609
1038
|
attempted: prior?.attempted,
|
|
610
1039
|
attemptUsage: prior?.attemptUsage,
|
|
611
1040
|
loopRound: prior?.loopRound,
|
|
1041
|
+
waitsEnded: prior?.waitsEnded,
|
|
612
1042
|
// Where a task that has run ran is fixed, like its runner session (ADR-0020).
|
|
613
1043
|
...prior?.status === "failed" ? { ops: prior.ops } : {},
|
|
614
1044
|
forcedPastGate: prior?.forcedPastGate,
|
|
@@ -647,11 +1077,14 @@ export {
|
|
|
647
1077
|
absorbRemoval,
|
|
648
1078
|
layoutOf,
|
|
649
1079
|
changedReposOf,
|
|
650
|
-
|
|
1080
|
+
landedInRepo,
|
|
1081
|
+
taskIsolationsOf,
|
|
651
1082
|
handoffOf,
|
|
652
1083
|
migratePlanIsolation,
|
|
653
1084
|
migratePlanStateIsolation,
|
|
654
1085
|
isolationViewOfPlan,
|
|
1086
|
+
VerdictEngine,
|
|
1087
|
+
endedByExit,
|
|
655
1088
|
taskOrderLabel,
|
|
656
1089
|
taskRef,
|
|
657
1090
|
titledTaskRef,
|
|
@@ -661,6 +1094,9 @@ export {
|
|
|
661
1094
|
resolveUserTaskRef,
|
|
662
1095
|
isAwaitingReason,
|
|
663
1096
|
migrateLoadedTasks,
|
|
1097
|
+
failInterruptedTasks,
|
|
1098
|
+
releaseMadeManual,
|
|
1099
|
+
settleOwedRepairs,
|
|
664
1100
|
dropRetiredPlanFields,
|
|
665
1101
|
migrateLoadedPlan,
|
|
666
1102
|
isUserMessage,
|
|
@@ -689,4 +1125,4 @@ export {
|
|
|
689
1125
|
stampOrigin,
|
|
690
1126
|
keepExecutionState
|
|
691
1127
|
};
|
|
692
|
-
//# sourceMappingURL=chunk-
|
|
1128
|
+
//# sourceMappingURL=chunk-F6WS3JLQ.mjs.map
|