@ordewell/core 0.11.3 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS.md +3 -1
- package/dist/{ModeResolver-DyXeZ9ib.d.ts → ModeResolver-B64aUZXJ.d.ts} +1 -1
- package/dist/{ModeResolver-BDAgouuE.d.mts → ModeResolver-CA2fUU9h.d.mts} +1 -1
- package/dist/{Task-BL4Idm9r.d.mts → Task-CxGgN6I0.d.mts} +801 -39
- package/dist/{Task-BL4Idm9r.d.ts → Task-CxGgN6I0.d.ts} +801 -39
- package/dist/{chunk-YAFNYNFX.mjs → chunk-2IFKPYPO.mjs} +453 -17
- package/dist/chunk-2IFKPYPO.mjs.map +1 -0
- package/dist/{chunk-MYXMIVWA.mjs → chunk-FX33UKAD.mjs} +8 -6
- package/dist/chunk-FX33UKAD.mjs.map +1 -0
- package/dist/{chunk-QTZ5XTBY.mjs → chunk-PKJTN2PD.mjs} +458 -13
- package/dist/chunk-PKJTN2PD.mjs.map +1 -0
- package/dist/{chunk-7WNLN63J.mjs → chunk-RZ23BMVR.mjs} +599 -124
- package/dist/chunk-RZ23BMVR.mjs.map +1 -0
- package/dist/chunk-UDUATSCJ.mjs +48 -0
- package/dist/chunk-UDUATSCJ.mjs.map +1 -0
- package/dist/{IRunner-DOMtDTVt.d.ts → eventStore-CTCSKS9E.d.ts} +976 -145
- package/dist/{IRunner-ihalYfTo.d.mts → eventStore-CcB8yELZ.d.mts} +976 -145
- package/dist/index.d.mts +905 -164
- package/dist/index.d.ts +905 -164
- package/dist/index.js +12496 -3308
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10830 -2919
- 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 +277 -21
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +8 -5
- package/dist/plan-utils.d.ts +8 -5
- package/dist/plan-utils.js +262 -33
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +39 -3
- package/dist/{runSettingsText-DVOd7vKC.d.ts → runSettingsText-DFVpe6aU.d.mts} +1996 -895
- package/dist/{runSettingsText-BuLVBlXS.d.mts → runSettingsText-lKLDsyli.d.ts} +1996 -895
- package/dist/testing.d.mts +99 -4
- package/dist/testing.d.ts +99 -4
- package/dist/testing.js +213 -8
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +193 -8
- package/dist/testing.mjs.map +1 -1
- package/docker/task.Dockerfile +22 -0
- package/package.json +3 -1
- package/skills/diagnose/SKILL.md +2 -2
- package/skills/explore/SKILL.md +1 -1
- package/dist/chunk-7WNLN63J.mjs.map +0 -1
- package/dist/chunk-MYXMIVWA.mjs.map +0 -1
- package/dist/chunk-QTZ5XTBY.mjs.map +0 -1
- package/dist/chunk-YAFNYNFX.mjs.map +0 -1
|
@@ -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,398 @@ 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));
|
|
232
331
|
}
|
|
233
|
-
|
|
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, note) {
|
|
348
|
+
this.checkpointQuestions.delete(taskId);
|
|
349
|
+
this.resumeIdle(taskId);
|
|
350
|
+
this.toolCheckpoints.get(taskId)?.({ kind: "continue", ...note ? { note } : {} });
|
|
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, refuted) => this.signalComplete(task.id, gen, report, refuted));
|
|
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, refuted) {
|
|
419
|
+
if (this.generations.get(taskId) !== generation) return;
|
|
420
|
+
this.acceptVerdict(taskId, reportedVerdict(report, refuted));
|
|
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
|
+
}
|
|
615
|
+
}
|
|
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, refuted) {
|
|
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
|
+
...refuted ? [refuted] : []
|
|
631
|
+
];
|
|
632
|
+
const decidedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
633
|
+
const reported = report.status;
|
|
634
|
+
if (report.status === "done" && refuted) return { outcome: "fail", reason: refuted.detail, checks, decidedAt, reported: "failed" };
|
|
635
|
+
if (report.status === "done") {
|
|
636
|
+
return { outcome: "pass", reason: "Verified: the runner reported completion through task_complete. Task completed successfully.", checks, decidedAt, reported };
|
|
637
|
+
}
|
|
638
|
+
const why = report.reason?.trim() || report.summary?.trim().split(/\n\s*\n/)[0].trim() || "no reason given";
|
|
639
|
+
return { outcome: "fail", reason: `The runner reported the task ${report.status}: ${why}`, checks, decidedAt, reported };
|
|
234
640
|
}
|
|
235
641
|
|
|
236
642
|
// src/services/TaskRefs.ts
|
|
@@ -282,7 +688,7 @@ function resolveUserTaskRef(tasks, ref) {
|
|
|
282
688
|
// src/models/Task.ts
|
|
283
689
|
import { v4 as uuidv4 } from "uuid";
|
|
284
690
|
function isAwaitingReason(value) {
|
|
285
|
-
return value === "input" || value === "checkpoint" || value === "conflict" || value === "files-changed" || value === "usage-limit";
|
|
691
|
+
return value === "input" || value === "checkpoint" || value === "question-lost" || value === "conflict" || value === "files-changed" || value === "usage-limit";
|
|
286
692
|
}
|
|
287
693
|
function migrateLoadedTasks(tasks) {
|
|
288
694
|
for (const task of tasks) {
|
|
@@ -301,6 +707,31 @@ function migrateLoadedTasks(tasks) {
|
|
|
301
707
|
migrateLoadedTasks(task.subtasks ?? []);
|
|
302
708
|
}
|
|
303
709
|
}
|
|
710
|
+
function failInterruptedTasks(tasks, now) {
|
|
711
|
+
for (const task of flattenTasks(tasks)) {
|
|
712
|
+
if (task.type === "delegated" && task.awaitingReason === "checkpoint") task.awaitingReason = "question-lost";
|
|
713
|
+
if (task.status !== "in_progress") continue;
|
|
714
|
+
task.status = "failed";
|
|
715
|
+
task.verdict = interruptedVerdict(now);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function releaseMadeManual(tasks, live = () => false) {
|
|
719
|
+
for (const task of flattenTasks(tasks)) {
|
|
720
|
+
if (task.type !== "user" || live(task.id) || task.status !== "failed" && task.status !== "awaiting_user") continue;
|
|
721
|
+
task.status = "pending";
|
|
722
|
+
delete task.awaitingReason;
|
|
723
|
+
delete task.resumeAt;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function settleOwedRepairs(plan) {
|
|
727
|
+
const records = plan.isolation?.run.tasks;
|
|
728
|
+
if (!records) return;
|
|
729
|
+
for (const task of flattenTasks(plan.tasks)) {
|
|
730
|
+
if (task.status !== "pending" && task.status !== "approved" || records[task.id]?.status !== "conflict") continue;
|
|
731
|
+
task.status = "awaiting_user";
|
|
732
|
+
task.awaitingReason = "conflict";
|
|
733
|
+
}
|
|
734
|
+
}
|
|
304
735
|
function dropRetiredPlanFields(plan) {
|
|
305
736
|
const raw = plan;
|
|
306
737
|
delete raw.researchBudget;
|
|
@@ -440,7 +871,7 @@ function createTask(overrides = {}) {
|
|
|
440
871
|
assignedModel: overrides.assignedModel,
|
|
441
872
|
assignedRunner: overrides.assignedRunner ?? DEFAULT_RUNNERS[0],
|
|
442
873
|
// A script task runs in no mode (ADR-0035): one given it would read as a mode it runs in.
|
|
443
|
-
taskMode: overrides.taskMode ?? (type === "script" ? void 0 : "build"),
|
|
874
|
+
taskMode: overrides.taskMode ?? (type === "script" || type === "delegated" ? void 0 : "build"),
|
|
444
875
|
autonomy: overrides.autonomy,
|
|
445
876
|
sliceType: overrides.sliceType,
|
|
446
877
|
userStoriesCovered: overrides.userStoriesCovered,
|
|
@@ -450,6 +881,12 @@ function createTask(overrides = {}) {
|
|
|
450
881
|
...overrides.commands !== void 0 ? { commands: overrides.commands.map((argv) => [...argv]) } : {},
|
|
451
882
|
...overrides.script !== void 0 ? { script: overrides.script } : {},
|
|
452
883
|
...overrides.timeoutMs !== void 0 ? { timeoutMs: overrides.timeoutMs } : {},
|
|
884
|
+
...overrides.goal !== void 0 ? { goal: overrides.goal } : {},
|
|
885
|
+
...overrides.budget !== void 0 ? { budget: { ...overrides.budget } } : {},
|
|
886
|
+
...overrides.grants !== void 0 ? { grants: [...overrides.grants] } : {},
|
|
887
|
+
...overrides.protectedPaths !== void 0 ? { protectedPaths: [...overrides.protectedPaths] } : {},
|
|
888
|
+
...overrides.scope !== void 0 ? { scope: structuredClone(overrides.scope) } : {},
|
|
889
|
+
...overrides.readOnly ? { readOnly: true } : {},
|
|
453
890
|
...overrides.loop ? { loop: { body: [...overrides.loop.body], maxRounds: overrides.loop.maxRounds } } : {},
|
|
454
891
|
...overrides.loopRound !== void 0 ? { loopRound: overrides.loopRound } : {},
|
|
455
892
|
...overrides.research !== void 0 ? { research: overrides.research } : {}
|
|
@@ -472,7 +909,7 @@ function opsFlag(value, type = "ai") {
|
|
|
472
909
|
return value === true && type === "ai" ? true : void 0;
|
|
473
910
|
}
|
|
474
911
|
function runsAsOps(task) {
|
|
475
|
-
return task.type === "script" || opsFlag(task.ops, task.type) === true;
|
|
912
|
+
return task.type === "script" && !task.readOnly || opsFlag(task.ops, task.type) === true;
|
|
476
913
|
}
|
|
477
914
|
function inheritedOps(from, chosen) {
|
|
478
915
|
return chosen ?? from.every((t) => opsFlag(t.ops) === true);
|
|
@@ -599,6 +1036,7 @@ function keepExecutionState(current, rewrite) {
|
|
|
599
1036
|
...t,
|
|
600
1037
|
status: prior?.status ?? "pending",
|
|
601
1038
|
skipReason: prior?.skipReason,
|
|
1039
|
+
reverted: prior?.reverted,
|
|
602
1040
|
verdict: prior?.verdict,
|
|
603
1041
|
outputSummary: prior?.outputSummary,
|
|
604
1042
|
handoff: prior?.handoff,
|
|
@@ -609,6 +1047,7 @@ function keepExecutionState(current, rewrite) {
|
|
|
609
1047
|
attempted: prior?.attempted,
|
|
610
1048
|
attemptUsage: prior?.attemptUsage,
|
|
611
1049
|
loopRound: prior?.loopRound,
|
|
1050
|
+
waitsEnded: prior?.waitsEnded,
|
|
612
1051
|
// Where a task that has run ran is fixed, like its runner session (ADR-0020).
|
|
613
1052
|
...prior?.status === "failed" ? { ops: prior.ops } : {},
|
|
614
1053
|
forcedPastGate: prior?.forcedPastGate,
|
|
@@ -647,11 +1086,14 @@ export {
|
|
|
647
1086
|
absorbRemoval,
|
|
648
1087
|
layoutOf,
|
|
649
1088
|
changedReposOf,
|
|
650
|
-
|
|
1089
|
+
landedInRepo,
|
|
1090
|
+
taskIsolationsOf,
|
|
651
1091
|
handoffOf,
|
|
652
1092
|
migratePlanIsolation,
|
|
653
1093
|
migratePlanStateIsolation,
|
|
654
1094
|
isolationViewOfPlan,
|
|
1095
|
+
VerdictEngine,
|
|
1096
|
+
endedByExit,
|
|
655
1097
|
taskOrderLabel,
|
|
656
1098
|
taskRef,
|
|
657
1099
|
titledTaskRef,
|
|
@@ -661,6 +1103,9 @@ export {
|
|
|
661
1103
|
resolveUserTaskRef,
|
|
662
1104
|
isAwaitingReason,
|
|
663
1105
|
migrateLoadedTasks,
|
|
1106
|
+
failInterruptedTasks,
|
|
1107
|
+
releaseMadeManual,
|
|
1108
|
+
settleOwedRepairs,
|
|
664
1109
|
dropRetiredPlanFields,
|
|
665
1110
|
migrateLoadedPlan,
|
|
666
1111
|
isUserMessage,
|
|
@@ -689,4 +1134,4 @@ export {
|
|
|
689
1134
|
stampOrigin,
|
|
690
1135
|
keepExecutionState
|
|
691
1136
|
};
|
|
692
|
-
//# sourceMappingURL=chunk-
|
|
1137
|
+
//# sourceMappingURL=chunk-PKJTN2PD.mjs.map
|