@miller-tech/uap 1.161.24 → 1.162.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli/deliver.d.ts +31 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +164 -9
- package/dist/cli/deliver.js.map +1 -1
- package/dist/delivery/agentic-executor.d.ts +18 -0
- package/dist/delivery/agentic-executor.d.ts.map +1 -1
- package/dist/delivery/agentic-executor.js +33 -0
- package/dist/delivery/agentic-executor.js.map +1 -1
- package/dist/delivery/epic-controller.d.ts +16 -0
- package/dist/delivery/epic-controller.d.ts.map +1 -1
- package/dist/delivery/epic-controller.js +26 -0
- package/dist/delivery/epic-controller.js.map +1 -1
- package/dist/delivery/epic-mission.d.ts +8 -0
- package/dist/delivery/epic-mission.d.ts.map +1 -1
- package/dist/delivery/epic-mission.js +10 -1
- package/dist/delivery/epic-mission.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/templates/hooks/deliver_autoroute.py +47 -4
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/tests/test_deliver_autoroute.py +53 -0
package/dist/cli/deliver.d.ts
CHANGED
|
@@ -217,6 +217,37 @@ export declare function hasExplicitAidFlags(options: DeliverOptions): boolean;
|
|
|
217
217
|
* filling only fields the user left unset. Exported for tests.
|
|
218
218
|
*/
|
|
219
219
|
export declare function applyAutoPlan(options: DeliverOptions, plan: AutoPlan): void;
|
|
220
|
+
/**
|
|
221
|
+
* Default wedge timeout (seconds). Override with UAP_DELIVER_WEDGE_TIMEOUT.
|
|
222
|
+
* Deliberately generous (30 min): the heartbeat only advances between
|
|
223
|
+
* convergence iterations, and one turn on a slow local model (generation + the
|
|
224
|
+
* full gate ladder) can legitimately take many minutes. The timeout must sit
|
|
225
|
+
* comfortably above the worst-case single-turn latency so a healthy-but-slow
|
|
226
|
+
* run is never mistaken for a wedged one and reclaimed out from under itself.
|
|
227
|
+
*/
|
|
228
|
+
export declare const DEFAULT_WEDGE_TIMEOUT_S = 1800;
|
|
229
|
+
/** Resolve the wedge timeout in seconds (env override, else the default). */
|
|
230
|
+
export declare function wedgeTimeoutS(): number;
|
|
231
|
+
/**
|
|
232
|
+
* Stamp the current epoch-seconds into `.uap/deliver.heartbeat`, ATOMICALLY
|
|
233
|
+
* (write a temp file then rename over the target). A plain truncate-in-place
|
|
234
|
+
* write leaves the file momentarily empty, and a concurrent reader would parse
|
|
235
|
+
* `""` as 0 (epoch 1970) and wrongly conclude the holder is wedged. Best-effort.
|
|
236
|
+
*/
|
|
237
|
+
export declare function updateDeliverHeartbeat(projectRoot: string): void;
|
|
238
|
+
/**
|
|
239
|
+
* Read the heartbeat epoch-seconds, or null if absent/unreadable/non-positive.
|
|
240
|
+
* Rejecting non-positive values guards against a torn read (empty file parses
|
|
241
|
+
* to 0) being treated as a real 1970 timestamp.
|
|
242
|
+
*/
|
|
243
|
+
export declare function readDeliverHeartbeat(projectRoot: string): number | null;
|
|
244
|
+
/**
|
|
245
|
+
* True when the lock holder is alive but WEDGED — its heartbeat is older than
|
|
246
|
+
* the wedge timeout. A MISSING heartbeat is not wedged (the holder may be
|
|
247
|
+
* starting up), so this returns false in that case and the caller keeps
|
|
248
|
+
* deferring to PID-liveness.
|
|
249
|
+
*/
|
|
250
|
+
export declare function isDeliverHolderWedged(projectRoot: string, nowS?: number): boolean;
|
|
220
251
|
/**
|
|
221
252
|
* Project-level deliver concurrency lock. Prevents a fan-out where an impatient
|
|
222
253
|
* caller (e.g. a model that launched `uap deliver` for some work, didn't wait
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/cli/deliver.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/cli/deliver.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/deliver.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { spawnSync } from 'child_process';
|
|
10
|
-
import { existsSync, mkdirSync, openSync, readFileSync, rmSync, writeSync, closeSync } from 'fs';
|
|
11
|
-
import { join, resolve } from 'path';
|
|
10
|
+
import { existsSync, mkdirSync, openSync, readFileSync, rmSync, writeSync, closeSync, renameSync } from 'fs';
|
|
11
|
+
import { join, resolve, relative } from 'path';
|
|
12
12
|
import { ConvergenceLoop, composeIterationHooks } from '../delivery/convergence-loop.js';
|
|
13
13
|
import { createModelJudge } from '../delivery/judge.js';
|
|
14
14
|
import { createModelCritic } from '../delivery/critic.js';
|
|
@@ -65,7 +65,7 @@ export function resolveEvaluatorPreset(opts) {
|
|
|
65
65
|
export { resolveAcceptanceVerdict } from '../delivery/mission-acceptance.js';
|
|
66
66
|
import { createAgenticExecutor, noopApplier, selectExecutorMode, lockContractFiles } from '../delivery/agentic-executor.js';
|
|
67
67
|
import { createRepairEscalation } from '../delivery/repair-escalation.js';
|
|
68
|
-
import { clearStop, isStopRequested, loadRunState, newRunId, saveRunState, MAX_PERSISTED_PHASES } from '../delivery/run-state.js';
|
|
68
|
+
import { clearStop, isStopRequested, requestStop, loadRunState, newRunId, saveRunState, MAX_PERSISTED_PHASES } from '../delivery/run-state.js';
|
|
69
69
|
import { planDeliveryPhases, shouldDecompose } from '../delivery/decompose.js';
|
|
70
70
|
import { initLedger, markItem } from '../delivery/completion-ledger.js';
|
|
71
71
|
import { loadUapConfigRaw } from '../utils/config-loader.js';
|
|
@@ -263,6 +263,73 @@ export function applyAutoPlan(options, plan) {
|
|
|
263
263
|
if (plan.deployDev && options.deployDev === undefined)
|
|
264
264
|
options.deployDev = true;
|
|
265
265
|
}
|
|
266
|
+
// ─── Deliver wedge detection (P0 reliability) ────────────────────────────────
|
|
267
|
+
// A deliver can wedge (alive PID, but stuck making no progress — e.g. a
|
|
268
|
+
// plan-check re-plan loop or a hung upstream). The lock alone can't tell a
|
|
269
|
+
// healthy long run from a wedged one, so the running deliver stamps a heartbeat
|
|
270
|
+
// on every iteration and the lock path reclaims a holder whose heartbeat has
|
|
271
|
+
// gone stale. Language-agnostic contract: `.uap/deliver.heartbeat` = one integer
|
|
272
|
+
// (unix epoch seconds), rewritten each turn; also read by deliver_autoroute.py.
|
|
273
|
+
/**
|
|
274
|
+
* Default wedge timeout (seconds). Override with UAP_DELIVER_WEDGE_TIMEOUT.
|
|
275
|
+
* Deliberately generous (30 min): the heartbeat only advances between
|
|
276
|
+
* convergence iterations, and one turn on a slow local model (generation + the
|
|
277
|
+
* full gate ladder) can legitimately take many minutes. The timeout must sit
|
|
278
|
+
* comfortably above the worst-case single-turn latency so a healthy-but-slow
|
|
279
|
+
* run is never mistaken for a wedged one and reclaimed out from under itself.
|
|
280
|
+
*/
|
|
281
|
+
export const DEFAULT_WEDGE_TIMEOUT_S = 1800;
|
|
282
|
+
/** Resolve the wedge timeout in seconds (env override, else the default). */
|
|
283
|
+
export function wedgeTimeoutS() {
|
|
284
|
+
const raw = process.env.UAP_DELIVER_WEDGE_TIMEOUT;
|
|
285
|
+
const n = raw !== undefined ? Number(raw) : NaN;
|
|
286
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_WEDGE_TIMEOUT_S;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Stamp the current epoch-seconds into `.uap/deliver.heartbeat`, ATOMICALLY
|
|
290
|
+
* (write a temp file then rename over the target). A plain truncate-in-place
|
|
291
|
+
* write leaves the file momentarily empty, and a concurrent reader would parse
|
|
292
|
+
* `""` as 0 (epoch 1970) and wrongly conclude the holder is wedged. Best-effort.
|
|
293
|
+
*/
|
|
294
|
+
export function updateDeliverHeartbeat(projectRoot) {
|
|
295
|
+
try {
|
|
296
|
+
const dir = join(projectRoot, '.uap');
|
|
297
|
+
mkdirSync(dir, { recursive: true });
|
|
298
|
+
const hbPath = join(dir, 'deliver.heartbeat');
|
|
299
|
+
const tmp = `${hbPath}.${process.pid}.tmp`;
|
|
300
|
+
const fd = openSync(tmp, 'w');
|
|
301
|
+
writeSync(fd, String(Math.floor(Date.now() / 1000)));
|
|
302
|
+
closeSync(fd);
|
|
303
|
+
renameSync(tmp, hbPath);
|
|
304
|
+
}
|
|
305
|
+
catch { /* non-fatal — lock path falls back to PID-liveness only */ }
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Read the heartbeat epoch-seconds, or null if absent/unreadable/non-positive.
|
|
309
|
+
* Rejecting non-positive values guards against a torn read (empty file parses
|
|
310
|
+
* to 0) being treated as a real 1970 timestamp.
|
|
311
|
+
*/
|
|
312
|
+
export function readDeliverHeartbeat(projectRoot) {
|
|
313
|
+
try {
|
|
314
|
+
const v = Number(readFileSync(join(projectRoot, '.uap', 'deliver.heartbeat'), 'utf8').trim());
|
|
315
|
+
return Number.isFinite(v) && v > 0 ? v : null;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* True when the lock holder is alive but WEDGED — its heartbeat is older than
|
|
323
|
+
* the wedge timeout. A MISSING heartbeat is not wedged (the holder may be
|
|
324
|
+
* starting up), so this returns false in that case and the caller keeps
|
|
325
|
+
* deferring to PID-liveness.
|
|
326
|
+
*/
|
|
327
|
+
export function isDeliverHolderWedged(projectRoot, nowS = Math.floor(Date.now() / 1000)) {
|
|
328
|
+
const hb = readDeliverHeartbeat(projectRoot);
|
|
329
|
+
if (hb === null)
|
|
330
|
+
return false;
|
|
331
|
+
return nowS - hb > wedgeTimeoutS();
|
|
332
|
+
}
|
|
266
333
|
/**
|
|
267
334
|
* Project-level deliver concurrency lock. Prevents a fan-out where an impatient
|
|
268
335
|
* caller (e.g. a model that launched `uap deliver` for some work, didn't wait
|
|
@@ -291,8 +358,9 @@ export function acquireDeliverLock(projectRoot) {
|
|
|
291
358
|
// Reclaim a stale lock (holder no longer alive).
|
|
292
359
|
if (existsSync(lockPath)) {
|
|
293
360
|
const held = Number((readFileSync(lockPath, 'utf8').split('|')[0] || '').trim());
|
|
294
|
-
|
|
295
|
-
|
|
361
|
+
// Defer only to a live holder that is NOT wedged; reclaim a stuck one.
|
|
362
|
+
if (held && held !== process.pid && pidAlive(held) && !isDeliverHolderWedged(projectRoot))
|
|
363
|
+
return null;
|
|
296
364
|
try {
|
|
297
365
|
rmSync(lockPath);
|
|
298
366
|
}
|
|
@@ -308,6 +376,10 @@ export function acquireDeliverLock(projectRoot) {
|
|
|
308
376
|
}
|
|
309
377
|
writeSync(fd, `${process.pid}|${new Date().toISOString()}`);
|
|
310
378
|
closeSync(fd);
|
|
379
|
+
// Stamp a fresh heartbeat immediately so this brand-new holder is never
|
|
380
|
+
// classified as wedged in the window before its first iteration (and so a
|
|
381
|
+
// reclaim overwrites the previous holder's stale heartbeat).
|
|
382
|
+
updateDeliverHeartbeat(projectRoot);
|
|
311
383
|
}
|
|
312
384
|
catch {
|
|
313
385
|
// Lock machinery unavailable — fail OPEN (don't block a legitimate run).
|
|
@@ -413,7 +485,7 @@ async function runDeliver(instruction, options) {
|
|
|
413
485
|
if (releaseLock === null) {
|
|
414
486
|
let holder = '';
|
|
415
487
|
try {
|
|
416
|
-
holder = readFileSync(join(projectRoot, '.uap', 'deliver.lock'), 'utf8').split('|')[0];
|
|
488
|
+
holder = stripControl(readFileSync(join(projectRoot, '.uap', 'deliver.lock'), 'utf8').split('|')[0]);
|
|
417
489
|
}
|
|
418
490
|
catch { /* gone already */ }
|
|
419
491
|
console.log(chalk.yellow(`↩ deliver already running for this project${holder ? ` (pid ${holder.trim()})` : ''} — ` +
|
|
@@ -424,6 +496,14 @@ async function runDeliver(instruction, options) {
|
|
|
424
496
|
}
|
|
425
497
|
// The lock is released via acquireDeliverLock's process-exit handler (the
|
|
426
498
|
// deliver CLI is a one-shot process), and explicitly on early returns below.
|
|
499
|
+
// ── Wedge handling (P0 reliability) ─────────────────────────────────────
|
|
500
|
+
// A stuck deliver (e.g. a plan-check re-plan loop) no longer blocks future
|
|
501
|
+
// runs: the heartbeat is refreshed on every iteration (see the loop hook
|
|
502
|
+
// below) and acquireDeliverLock PASSIVELY reclaims a holder whose heartbeat
|
|
503
|
+
// has gone stale past the wedge timeout. We deliberately do NOT self-abort via
|
|
504
|
+
// process.exit here — an abrupt teardown skips snapshot/worktree disposal
|
|
505
|
+
// (re-opening the known tmpfs-leak class), and a merely-slow turn must never
|
|
506
|
+
// kill a healthy run. Passive reclaim frees the lock without either hazard.
|
|
427
507
|
// Durable runs: `--resume <id|latest>` restores an interrupted mission —
|
|
428
508
|
// instruction, phase cursor, and the loop checkpoint — and continues it.
|
|
429
509
|
let resumeState = null;
|
|
@@ -974,10 +1054,16 @@ async function runDeliver(instruction, options) {
|
|
|
974
1054
|
// read-only for later epics. Mutable by reference — the epic controller
|
|
975
1055
|
// grows it between epics; the executor reads it live on every tool call.
|
|
976
1056
|
const contractLock = new Set();
|
|
1057
|
+
// ORIGINAL-cased locked contract paths (contractLock stores lowercased match
|
|
1058
|
+
// keys, which are NOT valid FS paths on a case-sensitive filesystem — reading
|
|
1059
|
+
// those back would silently drop PascalCase files like Config.ts). P1 reads
|
|
1060
|
+
// these for verbatim injection.
|
|
1061
|
+
const contractPaths = [];
|
|
977
1062
|
const executor = agentic
|
|
978
1063
|
? createAgenticExecutor(model, {
|
|
979
1064
|
projectRoot,
|
|
980
1065
|
endpoint: agenticEndpoint,
|
|
1066
|
+
onToolProgress: () => updateDeliverHeartbeat(projectRoot), // #2a per-tool-call heartbeat
|
|
981
1067
|
temperature,
|
|
982
1068
|
contextTokenBudget: sessionBudget,
|
|
983
1069
|
contractFiles: contractLock,
|
|
@@ -998,6 +1084,7 @@ async function runDeliver(instruction, options) {
|
|
|
998
1084
|
? createAgenticExecutor(repairModel, {
|
|
999
1085
|
projectRoot,
|
|
1000
1086
|
endpoint: agenticEndpoint,
|
|
1087
|
+
onToolProgress: () => updateDeliverHeartbeat(projectRoot), // #2a per-tool-call heartbeat
|
|
1001
1088
|
temperature: 0.2,
|
|
1002
1089
|
contextTokenBudget: sessionBudget,
|
|
1003
1090
|
contractFiles: contractLock,
|
|
@@ -1286,7 +1373,12 @@ async function runDeliver(instruction, options) {
|
|
|
1286
1373
|
errorTail),
|
|
1287
1374
|
})
|
|
1288
1375
|
: undefined;
|
|
1289
|
-
return composeIterationHooks((record) => printProgress(record), (
|
|
1376
|
+
return composeIterationHooks((record) => printProgress(record), (_record) => {
|
|
1377
|
+
// P0 wedge watchdog: each iteration is real progress — refresh the
|
|
1378
|
+
// heartbeat so the watchdog + lock wedge-reclaim only fire on a true stall.
|
|
1379
|
+
updateDeliverHeartbeat(projectRoot);
|
|
1380
|
+
return undefined;
|
|
1381
|
+
}, (record) => {
|
|
1290
1382
|
// Feed the acceptance breaker's zero-diff guard.
|
|
1291
1383
|
specs.recordWrites(evidenceRoot, record.filesApplied.length);
|
|
1292
1384
|
return undefined;
|
|
@@ -1363,6 +1455,32 @@ async function runDeliver(instruction, options) {
|
|
|
1363
1455
|
};
|
|
1364
1456
|
// ---- Durable run + optional mission decomposition (fable-style) ----
|
|
1365
1457
|
const runId = resumeState?.runId ?? newRunId();
|
|
1458
|
+
// #2b: safe cooperative wedge watchdog. If this run stops making tool progress
|
|
1459
|
+
// (heartbeat goes stale past the wedge timeout — #2a stamps it per tool call),
|
|
1460
|
+
// request a COOPERATIVE stop rather than process.exit: the convergence loop's
|
|
1461
|
+
// shouldStop picks it up at the next turn boundary and unwinds through the
|
|
1462
|
+
// normal finally (snapshot/worktree/lock cleanup all run). This lets a
|
|
1463
|
+
// genuinely-wedged run free its lock and exit instead of lingering as an idle
|
|
1464
|
+
// zombie, WITHOUT the abrupt-teardown resource leak the earlier process.exit
|
|
1465
|
+
// watchdog risked. Off-switch rides the existing UAP_DELIVER_NO_LOCK=1.
|
|
1466
|
+
let wedgeWatchdog = null;
|
|
1467
|
+
if (!options.dryRun && process.env.UAP_DELIVER_NO_LOCK !== '1') {
|
|
1468
|
+
// Fresh baseline at arm time so the watchdog never fires on a run that just
|
|
1469
|
+
// started but hasn't stamped yet — critically on --resume (which does NOT
|
|
1470
|
+
// acquire the lock, so acquireDeliverLock's stamp never ran and the on-disk
|
|
1471
|
+
// heartbeat still holds the PRIOR interrupted run's timestamp).
|
|
1472
|
+
updateDeliverHeartbeat(projectRoot);
|
|
1473
|
+
const wedgeS = wedgeTimeoutS();
|
|
1474
|
+
wedgeWatchdog = setInterval(() => {
|
|
1475
|
+
if (isDeliverHolderWedged(projectRoot) && !isStopRequested(projectRoot, runId)) {
|
|
1476
|
+
console.error(chalk.yellow(`⏱ wedge watchdog: no tool progress for ${wedgeS}s — requesting a clean stop so the lock is released.`));
|
|
1477
|
+
requestStop(projectRoot, runId);
|
|
1478
|
+
}
|
|
1479
|
+
}, Math.min(30_000, Math.max(1000, wedgeS * 1000)));
|
|
1480
|
+
wedgeWatchdog.unref();
|
|
1481
|
+
process.once('exit', () => { if (wedgeWatchdog)
|
|
1482
|
+
clearInterval(wedgeWatchdog); });
|
|
1483
|
+
}
|
|
1366
1484
|
let phases = resumeState?.phases;
|
|
1367
1485
|
let phaseIndex = resumeState?.phaseIndex ?? 0;
|
|
1368
1486
|
const phaseSummaries = [...(resumeState?.phaseSummaries ?? [])];
|
|
@@ -1645,6 +1763,12 @@ async function runDeliver(instruction, options) {
|
|
|
1645
1763
|
? createAgenticExecutor(model, {
|
|
1646
1764
|
projectRoot: root,
|
|
1647
1765
|
endpoint: agenticEndpoint,
|
|
1766
|
+
// #2a per-tool-call heartbeat. Stamp projectRoot (NOT the
|
|
1767
|
+
// per-task `root`): the deliver heartbeat is a project-global
|
|
1768
|
+
// wedge signal the watchdog + lock reclaim read at projectRoot;
|
|
1769
|
+
// stamping `root` here would leave projectRoot's advancing only
|
|
1770
|
+
// per-turn, re-opening the false-wedge window for isolated tasks.
|
|
1771
|
+
onToolProgress: () => updateDeliverHeartbeat(projectRoot),
|
|
1648
1772
|
temperature,
|
|
1649
1773
|
contextTokenBudget: sessionBudget,
|
|
1650
1774
|
contractFiles: contractLock,
|
|
@@ -1822,7 +1946,31 @@ async function runDeliver(instruction, options) {
|
|
|
1822
1946
|
ledgerInit: (items) => initLedger(projectRoot, instruction, items.map((i) => ({ ...i, kind: 'epic' }))),
|
|
1823
1947
|
ledgerMark: (id, status, noteText) => markItem(projectRoot, id, status, noteText),
|
|
1824
1948
|
lockedContracts: () => [...contractLock],
|
|
1825
|
-
lockContracts: (files) =>
|
|
1949
|
+
lockContracts: (files) => {
|
|
1950
|
+
const locked = lockContractFiles(contractLock, projectRoot, files);
|
|
1951
|
+
for (const p of locked)
|
|
1952
|
+
if (!contractPaths.includes(p))
|
|
1953
|
+
contractPaths.push(p);
|
|
1954
|
+
return locked;
|
|
1955
|
+
},
|
|
1956
|
+
// P1: concatenate the locked contract files' CONTENTS (original-cased
|
|
1957
|
+
// paths) for verbatim injection into later epics (build against the EXACT
|
|
1958
|
+
// shared surface). Reads are contained to the project root.
|
|
1959
|
+
readContractFiles: () => {
|
|
1960
|
+
if (contractPaths.length === 0)
|
|
1961
|
+
return null;
|
|
1962
|
+
const parts = [];
|
|
1963
|
+
for (const rel of contractPaths) {
|
|
1964
|
+
const abs = resolve(projectRoot, rel);
|
|
1965
|
+
if (relative(projectRoot, abs).startsWith('..'))
|
|
1966
|
+
continue; // never read outside the project
|
|
1967
|
+
try {
|
|
1968
|
+
parts.push(`// ===== ${rel} =====\n${readFileSync(abs, 'utf8')}`);
|
|
1969
|
+
}
|
|
1970
|
+
catch { /* a locked path that vanished — skip it */ }
|
|
1971
|
+
}
|
|
1972
|
+
return parts.length > 0 ? parts.join('\n\n') : null;
|
|
1973
|
+
},
|
|
1826
1974
|
maxAttemptsPerEpic: Number(process.env.UAP_DELIVER_EPIC_ATTEMPTS ?? 3), // (#4b) 2→3
|
|
1827
1975
|
// (#4c) Recursive split depth: a huge epic that still can't land after
|
|
1828
1976
|
// a split is split again, one level shallower — bounded.
|
|
@@ -1894,7 +2042,7 @@ async function runDeliver(instruction, options) {
|
|
|
1894
2042
|
redetectRungs: true,
|
|
1895
2043
|
redetectFilter: loopConfig.redetectFilter,
|
|
1896
2044
|
protectTests: options.protectTests,
|
|
1897
|
-
onIteration: (record) => printProgress(record),
|
|
2045
|
+
onIteration: (record) => { updateDeliverHeartbeat(projectRoot); printProgress(record); },
|
|
1898
2046
|
}, executor, seams);
|
|
1899
2047
|
const lazyResult = await lazyLoop.deliver(instruction);
|
|
1900
2048
|
if (lazyResult.success) {
|
|
@@ -2060,6 +2208,13 @@ async function runDeliver(instruction, options) {
|
|
|
2060
2208
|
// The loop is over: from here a normal exit is expected, so stop treating a
|
|
2061
2209
|
// signal as a mystery death.
|
|
2062
2210
|
disposeExitRecorder();
|
|
2211
|
+
// Stop the wedge watchdog on the normal completion path too (not only on
|
|
2212
|
+
// process exit) — avoids a stray requestStop on a finished runId and, for
|
|
2213
|
+
// repeated in-process callers, interval/listener accumulation.
|
|
2214
|
+
if (wedgeWatchdog) {
|
|
2215
|
+
clearInterval(wedgeWatchdog);
|
|
2216
|
+
wedgeWatchdog = null;
|
|
2217
|
+
}
|
|
2063
2218
|
}
|
|
2064
2219
|
// --keep-best: if deliver left the project worse than it started (by real
|
|
2065
2220
|
// gate score), roll back to the snapshot so the run is never a regression.
|