@miller-tech/uap 1.161.23 → 1.162.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.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 +122 -8
- package/dist/cli/deliver.js.map +1 -1
- package/dist/delivery/agentic-executor.d.ts +9 -0
- package/dist/delivery/agentic-executor.d.ts.map +1 -1
- package/dist/delivery/agentic-executor.js +30 -0
- package/dist/delivery/agentic-executor.js.map +1 -1
- package/dist/delivery/decompose.d.ts +13 -0
- package/dist/delivery/decompose.d.ts.map +1 -1
- package/dist/delivery/decompose.js +36 -3
- package/dist/delivery/decompose.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';
|
|
@@ -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,6 +1054,11 @@ 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,
|
|
@@ -1286,7 +1371,12 @@ async function runDeliver(instruction, options) {
|
|
|
1286
1371
|
errorTail),
|
|
1287
1372
|
})
|
|
1288
1373
|
: undefined;
|
|
1289
|
-
return composeIterationHooks((record) => printProgress(record), (
|
|
1374
|
+
return composeIterationHooks((record) => printProgress(record), (_record) => {
|
|
1375
|
+
// P0 wedge watchdog: each iteration is real progress — refresh the
|
|
1376
|
+
// heartbeat so the watchdog + lock wedge-reclaim only fire on a true stall.
|
|
1377
|
+
updateDeliverHeartbeat(projectRoot);
|
|
1378
|
+
return undefined;
|
|
1379
|
+
}, (record) => {
|
|
1290
1380
|
// Feed the acceptance breaker's zero-diff guard.
|
|
1291
1381
|
specs.recordWrites(evidenceRoot, record.filesApplied.length);
|
|
1292
1382
|
return undefined;
|
|
@@ -1822,7 +1912,31 @@ async function runDeliver(instruction, options) {
|
|
|
1822
1912
|
ledgerInit: (items) => initLedger(projectRoot, instruction, items.map((i) => ({ ...i, kind: 'epic' }))),
|
|
1823
1913
|
ledgerMark: (id, status, noteText) => markItem(projectRoot, id, status, noteText),
|
|
1824
1914
|
lockedContracts: () => [...contractLock],
|
|
1825
|
-
lockContracts: (files) =>
|
|
1915
|
+
lockContracts: (files) => {
|
|
1916
|
+
const locked = lockContractFiles(contractLock, projectRoot, files);
|
|
1917
|
+
for (const p of locked)
|
|
1918
|
+
if (!contractPaths.includes(p))
|
|
1919
|
+
contractPaths.push(p);
|
|
1920
|
+
return locked;
|
|
1921
|
+
},
|
|
1922
|
+
// P1: concatenate the locked contract files' CONTENTS (original-cased
|
|
1923
|
+
// paths) for verbatim injection into later epics (build against the EXACT
|
|
1924
|
+
// shared surface). Reads are contained to the project root.
|
|
1925
|
+
readContractFiles: () => {
|
|
1926
|
+
if (contractPaths.length === 0)
|
|
1927
|
+
return null;
|
|
1928
|
+
const parts = [];
|
|
1929
|
+
for (const rel of contractPaths) {
|
|
1930
|
+
const abs = resolve(projectRoot, rel);
|
|
1931
|
+
if (relative(projectRoot, abs).startsWith('..'))
|
|
1932
|
+
continue; // never read outside the project
|
|
1933
|
+
try {
|
|
1934
|
+
parts.push(`// ===== ${rel} =====\n${readFileSync(abs, 'utf8')}`);
|
|
1935
|
+
}
|
|
1936
|
+
catch { /* a locked path that vanished — skip it */ }
|
|
1937
|
+
}
|
|
1938
|
+
return parts.length > 0 ? parts.join('\n\n') : null;
|
|
1939
|
+
},
|
|
1826
1940
|
maxAttemptsPerEpic: Number(process.env.UAP_DELIVER_EPIC_ATTEMPTS ?? 3), // (#4b) 2→3
|
|
1827
1941
|
// (#4c) Recursive split depth: a huge epic that still can't land after
|
|
1828
1942
|
// a split is split again, one level shallower — bounded.
|
|
@@ -1894,7 +2008,7 @@ async function runDeliver(instruction, options) {
|
|
|
1894
2008
|
redetectRungs: true,
|
|
1895
2009
|
redetectFilter: loopConfig.redetectFilter,
|
|
1896
2010
|
protectTests: options.protectTests,
|
|
1897
|
-
onIteration: (record) => printProgress(record),
|
|
2011
|
+
onIteration: (record) => { updateDeliverHeartbeat(projectRoot); printProgress(record); },
|
|
1898
2012
|
}, executor, seams);
|
|
1899
2013
|
const lazyResult = await lazyLoop.deliver(instruction);
|
|
1900
2014
|
if (lazyResult.success) {
|