@karmaniverous/jeeves-meta 0.16.0 → 0.16.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/cli/jeeves-meta/index.js +127 -259
- package/dist/executor/GatewayExecutor.d.ts +7 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +127 -259
- package/dist/queue/index.d.ts +25 -66
- package/dist/routes/queue.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1509,16 +1509,20 @@ class GatewayExecutor {
|
|
|
1509
1509
|
: '';
|
|
1510
1510
|
return text && text.trim() !== 'ANNOUNCE_SKIP' ? text : undefined;
|
|
1511
1511
|
}
|
|
1512
|
-
/**
|
|
1512
|
+
/**
|
|
1513
|
+
* Check history messages for timeout detection.
|
|
1514
|
+
*
|
|
1515
|
+
* Does NOT determine completion — session completion is authoritative
|
|
1516
|
+
* (via sessions_list). History stop reasons can false-positive on
|
|
1517
|
+
* sessions_yield artifacts (#200).
|
|
1518
|
+
*/
|
|
1513
1519
|
static checkHistoryCompletion(messages) {
|
|
1514
1520
|
if (messages.length === 0)
|
|
1515
|
-
return {
|
|
1521
|
+
return { timedOut: false };
|
|
1516
1522
|
const last = messages[messages.length - 1];
|
|
1517
1523
|
if (last.role !== 'assistant' || !last.stopReason)
|
|
1518
|
-
return {
|
|
1519
|
-
|
|
1520
|
-
return { done: false, timedOut: false };
|
|
1521
|
-
return { done: true, timedOut: last.stopReason === 'timeout' };
|
|
1524
|
+
return { timedOut: false };
|
|
1525
|
+
return { timedOut: last.stopReason === 'timeout' };
|
|
1522
1526
|
}
|
|
1523
1527
|
/** Invoke a gateway tool via the /tools/invoke HTTP endpoint. */
|
|
1524
1528
|
async invoke(tool, args, sessionKey) {
|
|
@@ -1681,12 +1685,14 @@ class GatewayExecutor {
|
|
|
1681
1685
|
historyResult.result?.messages ??
|
|
1682
1686
|
[];
|
|
1683
1687
|
const msgArray = messages;
|
|
1684
|
-
// Check 1:
|
|
1685
|
-
const {
|
|
1688
|
+
// Check 1: history-based timeout detection (stop reason)
|
|
1689
|
+
const { timedOut: historyTimedOut } = GatewayExecutor.checkHistoryCompletion(msgArray);
|
|
1686
1690
|
// Check 2: session completion status via sessions_list
|
|
1691
|
+
// Gate on session completion only — history stop reasons can
|
|
1692
|
+
// false-positive on sessions_yield artifacts (#200).
|
|
1687
1693
|
const sessionInfo = await this.getSessionInfo(sessionKey);
|
|
1688
1694
|
const timedOut = sessionInfo.timedOut || historyTimedOut;
|
|
1689
|
-
if (
|
|
1695
|
+
if (sessionInfo.completed) {
|
|
1690
1696
|
const tokens = sessionInfo.tokens;
|
|
1691
1697
|
// Gateway-side timeout detected — check staging file for recovery
|
|
1692
1698
|
if (timedOut) {
|
|
@@ -3425,36 +3431,33 @@ class ProgressReporter {
|
|
|
3425
3431
|
}
|
|
3426
3432
|
|
|
3427
3433
|
/**
|
|
3428
|
-
*
|
|
3434
|
+
* Synthesis queue.
|
|
3429
3435
|
*
|
|
3430
3436
|
* Layer 1: Current — the single item currently executing (at most one).
|
|
3431
|
-
* Layer 2:
|
|
3432
|
-
*
|
|
3433
|
-
* Layer 3: Automatic — computed on read, not persisted. All
|
|
3434
|
-
* pending phase, ranked by scheduler priority.
|
|
3435
|
-
*
|
|
3436
|
-
* Legacy: `pending` array is the union of overrides + automatic.
|
|
3437
|
+
* Layer 2: Pending — items enqueued via POST /synthesize (targeted) or
|
|
3438
|
+
* scheduler tick (automatic). FIFO, ahead of automatic candidates.
|
|
3439
|
+
* Layer 3: Automatic — computed on read (GET /queue), not persisted. All
|
|
3440
|
+
* metas with a pending phase, ranked by scheduler priority.
|
|
3437
3441
|
*
|
|
3438
3442
|
* @module queue
|
|
3439
3443
|
*/
|
|
3440
3444
|
const DEPTH_WARNING_THRESHOLD = 3;
|
|
3445
|
+
/** Strip trailing .meta suffix for consistent path comparison. */
|
|
3446
|
+
function normQueuePath(p) {
|
|
3447
|
+
return p.endsWith('.meta') ? p.slice(0, -5).replace(/[/\\]$/, '') : p;
|
|
3448
|
+
}
|
|
3441
3449
|
/**
|
|
3442
|
-
*
|
|
3450
|
+
* Synthesis queue.
|
|
3443
3451
|
*
|
|
3444
|
-
* Only one synthesis runs at a time.
|
|
3445
|
-
* take priority over automatic candidates.
|
|
3452
|
+
* Only one synthesis runs at a time. Explicitly enqueued items
|
|
3453
|
+
* take priority over automatic (computed-on-read) candidates.
|
|
3446
3454
|
*/
|
|
3447
3455
|
class SynthesisQueue {
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
currentItem = null;
|
|
3456
|
+
entries = [];
|
|
3457
|
+
currentPhaseItem = null;
|
|
3451
3458
|
processing = false;
|
|
3452
3459
|
logger;
|
|
3453
3460
|
onEnqueueCallback = null;
|
|
3454
|
-
/** Explicit override entries (3-layer model). */
|
|
3455
|
-
overrideEntries = [];
|
|
3456
|
-
/** Currently executing item with phase info (3-layer model). */
|
|
3457
|
-
currentPhaseItem = null;
|
|
3458
3461
|
constructor(logger) {
|
|
3459
3462
|
this.logger = logger;
|
|
3460
3463
|
}
|
|
@@ -3462,52 +3465,67 @@ class SynthesisQueue {
|
|
|
3462
3465
|
onEnqueue(callback) {
|
|
3463
3466
|
this.onEnqueueCallback = callback;
|
|
3464
3467
|
}
|
|
3465
|
-
// ──
|
|
3468
|
+
// ── Enqueue / dequeue ──────────────────────────────────────────────
|
|
3466
3469
|
/**
|
|
3467
|
-
* Add an
|
|
3470
|
+
* Add an entry to the queue.
|
|
3468
3471
|
* Deduped by path. Returns position and whether already queued.
|
|
3469
3472
|
*/
|
|
3470
|
-
|
|
3473
|
+
enqueue(path) {
|
|
3474
|
+
const norm = normQueuePath(path);
|
|
3471
3475
|
// Check if currently executing
|
|
3472
|
-
if (this.currentPhaseItem
|
|
3473
|
-
this.
|
|
3476
|
+
if (this.currentPhaseItem &&
|
|
3477
|
+
normQueuePath(this.currentPhaseItem.path) === norm) {
|
|
3474
3478
|
return { position: 0, alreadyQueued: true };
|
|
3475
3479
|
}
|
|
3476
|
-
// Check if already in
|
|
3477
|
-
const existing = this.
|
|
3480
|
+
// Check if already in queue
|
|
3481
|
+
const existing = this.entries.findIndex((e) => normQueuePath(e.path) === norm);
|
|
3478
3482
|
if (existing !== -1) {
|
|
3479
3483
|
return { position: existing, alreadyQueued: true };
|
|
3480
3484
|
}
|
|
3481
|
-
this.
|
|
3485
|
+
this.entries.push({
|
|
3482
3486
|
path,
|
|
3483
3487
|
enqueuedAt: new Date().toISOString(),
|
|
3484
3488
|
});
|
|
3485
|
-
const position = this.
|
|
3486
|
-
if (this.
|
|
3487
|
-
this.logger.warn({ depth: this.
|
|
3489
|
+
const position = this.entries.length - 1;
|
|
3490
|
+
if (this.entries.length > DEPTH_WARNING_THRESHOLD) {
|
|
3491
|
+
this.logger.warn({ depth: this.entries.length }, 'Queue depth exceeds threshold');
|
|
3488
3492
|
}
|
|
3489
3493
|
this.onEnqueueCallback?.();
|
|
3490
3494
|
return { position, alreadyQueued: false };
|
|
3491
3495
|
}
|
|
3492
|
-
/** Dequeue the next
|
|
3493
|
-
|
|
3494
|
-
return this.
|
|
3496
|
+
/** Dequeue the next entry, or undefined if empty. */
|
|
3497
|
+
dequeue() {
|
|
3498
|
+
return this.entries.shift();
|
|
3499
|
+
}
|
|
3500
|
+
/** Get all queued entries (shallow copy). */
|
|
3501
|
+
get items() {
|
|
3502
|
+
return [...this.entries];
|
|
3495
3503
|
}
|
|
3496
|
-
/**
|
|
3497
|
-
get
|
|
3498
|
-
return
|
|
3504
|
+
/** Number of items waiting in the queue (excludes current). */
|
|
3505
|
+
get depth() {
|
|
3506
|
+
return this.entries.length;
|
|
3499
3507
|
}
|
|
3500
|
-
/**
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3508
|
+
/**
|
|
3509
|
+
* Remove all pending items from the queue.
|
|
3510
|
+
* Does not affect the currently-running item.
|
|
3511
|
+
*
|
|
3512
|
+
* @returns The number of items removed.
|
|
3513
|
+
*/
|
|
3514
|
+
clear() {
|
|
3515
|
+
const count = this.entries.length;
|
|
3516
|
+
this.entries = [];
|
|
3504
3517
|
return count;
|
|
3505
3518
|
}
|
|
3506
|
-
/** Check
|
|
3507
|
-
|
|
3508
|
-
|
|
3519
|
+
/** Check whether a path is in the queue or currently being synthesized. */
|
|
3520
|
+
has(path) {
|
|
3521
|
+
const norm = normQueuePath(path);
|
|
3522
|
+
if (this.currentPhaseItem &&
|
|
3523
|
+
normQueuePath(this.currentPhaseItem.path) === norm) {
|
|
3524
|
+
return true;
|
|
3525
|
+
}
|
|
3526
|
+
return this.entries.some((e) => normQueuePath(e.path) === norm);
|
|
3509
3527
|
}
|
|
3510
|
-
// ── Current-item tracking
|
|
3528
|
+
// ── Current-item tracking ──────────────────────────────────────────
|
|
3511
3529
|
/** Set the currently executing phase item. */
|
|
3512
3530
|
setCurrentPhase(path, phase) {
|
|
3513
3531
|
this.currentPhaseItem = {
|
|
@@ -3524,129 +3542,21 @@ class SynthesisQueue {
|
|
|
3524
3542
|
get currentPhase() {
|
|
3525
3543
|
return this.currentPhaseItem;
|
|
3526
3544
|
}
|
|
3527
|
-
// ──
|
|
3528
|
-
/**
|
|
3529
|
-
* Add a path to the synthesis queue.
|
|
3530
|
-
*
|
|
3531
|
-
* @param path - Meta path to synthesize.
|
|
3532
|
-
* @param priority - If true, insert at front of queue.
|
|
3533
|
-
* @returns Position and whether the path was already queued.
|
|
3534
|
-
*/
|
|
3535
|
-
enqueue(path, priority = false) {
|
|
3536
|
-
if (this.currentItem?.path === path) {
|
|
3537
|
-
return { position: 0, alreadyQueued: true };
|
|
3538
|
-
}
|
|
3539
|
-
const existingIndex = this.queue.findIndex((item) => item.path === path);
|
|
3540
|
-
if (existingIndex !== -1) {
|
|
3541
|
-
return { position: existingIndex, alreadyQueued: true };
|
|
3542
|
-
}
|
|
3543
|
-
const item = {
|
|
3544
|
-
path,
|
|
3545
|
-
priority,
|
|
3546
|
-
enqueuedAt: new Date().toISOString(),
|
|
3547
|
-
};
|
|
3548
|
-
if (priority) {
|
|
3549
|
-
this.queue.unshift(item);
|
|
3550
|
-
}
|
|
3551
|
-
else {
|
|
3552
|
-
this.queue.push(item);
|
|
3553
|
-
}
|
|
3554
|
-
if (this.queue.length > DEPTH_WARNING_THRESHOLD) {
|
|
3555
|
-
this.logger.warn({ depth: this.queue.length }, 'Queue depth exceeds threshold');
|
|
3556
|
-
}
|
|
3557
|
-
const position = this.queue.findIndex((i) => i.path === path);
|
|
3558
|
-
this.onEnqueueCallback?.();
|
|
3559
|
-
return { position, alreadyQueued: false };
|
|
3560
|
-
}
|
|
3561
|
-
/** Remove and return the next item from the queue. */
|
|
3562
|
-
dequeue() {
|
|
3563
|
-
const item = this.queue.shift();
|
|
3564
|
-
if (item) {
|
|
3565
|
-
this.currentItem = item;
|
|
3566
|
-
}
|
|
3567
|
-
return item;
|
|
3568
|
-
}
|
|
3569
|
-
/** Mark the currently-running synthesis as complete. */
|
|
3570
|
-
complete() {
|
|
3571
|
-
this.currentItem = null;
|
|
3572
|
-
}
|
|
3573
|
-
/** Number of items waiting in the queue (excludes current). */
|
|
3574
|
-
get depth() {
|
|
3575
|
-
return this.queue.length;
|
|
3576
|
-
}
|
|
3577
|
-
/** The item currently being synthesized, or null. */
|
|
3578
|
-
get current() {
|
|
3579
|
-
return this.currentItem;
|
|
3580
|
-
}
|
|
3581
|
-
/** A shallow copy of the queued items. */
|
|
3582
|
-
get items() {
|
|
3583
|
-
return [...this.queue];
|
|
3584
|
-
}
|
|
3585
|
-
/** A shallow copy of the pending items (alias for items). */
|
|
3586
|
-
get pending() {
|
|
3587
|
-
return [...this.queue];
|
|
3588
|
-
}
|
|
3589
|
-
/**
|
|
3590
|
-
* Remove all pending items from the queue.
|
|
3591
|
-
* Does not affect the currently-running item.
|
|
3592
|
-
*
|
|
3593
|
-
* @returns The number of items removed.
|
|
3594
|
-
*/
|
|
3595
|
-
clear() {
|
|
3596
|
-
const count = this.queue.length;
|
|
3597
|
-
this.queue = [];
|
|
3598
|
-
return count;
|
|
3599
|
-
}
|
|
3600
|
-
/** Check whether a path is in the queue or currently being synthesized. */
|
|
3601
|
-
has(path) {
|
|
3602
|
-
if (this.currentItem?.path === path)
|
|
3603
|
-
return true;
|
|
3604
|
-
if (this.currentPhaseItem?.path === path)
|
|
3605
|
-
return true;
|
|
3606
|
-
return (this.queue.some((item) => item.path === path) ||
|
|
3607
|
-
this.overrideEntries.some((e) => e.path === path));
|
|
3608
|
-
}
|
|
3609
|
-
/** Get the 0-indexed position of a path in the queue. */
|
|
3610
|
-
getPosition(path) {
|
|
3611
|
-
// Check overrides first
|
|
3612
|
-
const overrideIdx = this.overrideEntries.findIndex((e) => e.path === path);
|
|
3613
|
-
if (overrideIdx !== -1)
|
|
3614
|
-
return overrideIdx;
|
|
3615
|
-
const index = this.queue.findIndex((item) => item.path === path);
|
|
3616
|
-
return index === -1 ? null : index;
|
|
3617
|
-
}
|
|
3618
|
-
/** Dequeue the next item: overrides first, then legacy queue. */
|
|
3619
|
-
nextItem() {
|
|
3620
|
-
const override = this.dequeueOverride();
|
|
3621
|
-
if (override)
|
|
3622
|
-
return { path: override.path, source: 'override' };
|
|
3623
|
-
const item = this.dequeue();
|
|
3624
|
-
if (item)
|
|
3625
|
-
return { path: item.path, source: 'legacy' };
|
|
3626
|
-
return undefined;
|
|
3627
|
-
}
|
|
3545
|
+
// ── Queue state ────────────────────────────────────────────────────
|
|
3628
3546
|
/** Return a snapshot of queue state for the /status endpoint. */
|
|
3629
3547
|
getState() {
|
|
3630
3548
|
return {
|
|
3631
|
-
depth: this.
|
|
3632
|
-
items:
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
enqueuedAt: e.enqueuedAt,
|
|
3637
|
-
})),
|
|
3638
|
-
...this.queue.map((item) => ({
|
|
3639
|
-
path: item.path,
|
|
3640
|
-
priority: item.priority,
|
|
3641
|
-
enqueuedAt: item.enqueuedAt,
|
|
3642
|
-
})),
|
|
3643
|
-
],
|
|
3549
|
+
depth: this.entries.length,
|
|
3550
|
+
items: this.entries.map((e) => ({
|
|
3551
|
+
path: e.path,
|
|
3552
|
+
enqueuedAt: e.enqueuedAt,
|
|
3553
|
+
})),
|
|
3644
3554
|
};
|
|
3645
3555
|
}
|
|
3556
|
+
// ── Processing ─────────────────────────────────────────────────────
|
|
3646
3557
|
/**
|
|
3647
|
-
* Process queued items one at a time until
|
|
3558
|
+
* Process queued items one at a time until the queue is empty.
|
|
3648
3559
|
*
|
|
3649
|
-
* Override entries are processed first (FIFO), then legacy queue items.
|
|
3650
3560
|
* Re-entry is prevented: if already processing, the call returns
|
|
3651
3561
|
* immediately. Errors are logged and do not block subsequent items.
|
|
3652
3562
|
*
|
|
@@ -3657,7 +3567,7 @@ class SynthesisQueue {
|
|
|
3657
3567
|
return;
|
|
3658
3568
|
this.processing = true;
|
|
3659
3569
|
try {
|
|
3660
|
-
let next = this.
|
|
3570
|
+
let next = this.dequeue();
|
|
3661
3571
|
while (next) {
|
|
3662
3572
|
try {
|
|
3663
3573
|
await synthesizeFn(next.path);
|
|
@@ -3666,9 +3576,7 @@ class SynthesisQueue {
|
|
|
3666
3576
|
this.logger.error({ path: next.path, err }, 'Synthesis failed');
|
|
3667
3577
|
}
|
|
3668
3578
|
this.clearCurrentPhase();
|
|
3669
|
-
|
|
3670
|
-
this.complete();
|
|
3671
|
-
next = this.nextItem();
|
|
3579
|
+
next = this.dequeue();
|
|
3672
3580
|
}
|
|
3673
3581
|
}
|
|
3674
3582
|
finally {
|
|
@@ -4222,7 +4130,6 @@ class Scheduler {
|
|
|
4222
4130
|
this.logger.debug({ backoffMultiplier: this.backoffMultiplier }, 'No ready phases found, increasing backoff');
|
|
4223
4131
|
return;
|
|
4224
4132
|
}
|
|
4225
|
-
// Enqueue using the legacy queue path (backward compat with processQueue)
|
|
4226
4133
|
this.queue.enqueue(candidate.path);
|
|
4227
4134
|
this.logger.info({ path: candidate.path, phase: candidate.phase, band: candidate.band }, 'Enqueued phase candidate');
|
|
4228
4135
|
// Opportunistic watcher restart detection
|
|
@@ -4821,8 +4728,8 @@ function registerPreviewRoute(app, deps) {
|
|
|
4821
4728
|
/**
|
|
4822
4729
|
* Queue management and abort routes.
|
|
4823
4730
|
*
|
|
4824
|
-
* - GET /queue — 3-layer queue model (current,
|
|
4825
|
-
* - POST /queue/clear — remove
|
|
4731
|
+
* - GET /queue — 3-layer queue model (current, pending, automatic)
|
|
4732
|
+
* - POST /queue/clear — remove pending entries
|
|
4826
4733
|
* - POST /synthesize/abort — abort the current synthesis
|
|
4827
4734
|
*
|
|
4828
4735
|
* @module routes/queue
|
|
@@ -4832,24 +4739,24 @@ function registerQueueRoutes(app, deps) {
|
|
|
4832
4739
|
const { queue } = deps;
|
|
4833
4740
|
app.get(getEndpoint('queue').path, async () => {
|
|
4834
4741
|
const currentPhase = queue.currentPhase;
|
|
4835
|
-
const
|
|
4836
|
-
// Compute owedPhase for each
|
|
4837
|
-
const
|
|
4742
|
+
const pending = queue.items;
|
|
4743
|
+
// Compute owedPhase for each pending entry by reading meta state
|
|
4744
|
+
const enrichedPending = await Promise.all(pending.map(async (entry) => {
|
|
4838
4745
|
try {
|
|
4839
|
-
const metaDir = resolveMetaDir(
|
|
4746
|
+
const metaDir = resolveMetaDir(entry.path);
|
|
4840
4747
|
const meta = await readMetaJson(metaDir);
|
|
4841
4748
|
const ps = derivePhaseState(meta);
|
|
4842
4749
|
return {
|
|
4843
|
-
path:
|
|
4750
|
+
path: entry.path,
|
|
4844
4751
|
owedPhase: getOwedPhase(ps),
|
|
4845
|
-
enqueuedAt:
|
|
4752
|
+
enqueuedAt: entry.enqueuedAt,
|
|
4846
4753
|
};
|
|
4847
4754
|
}
|
|
4848
4755
|
catch {
|
|
4849
4756
|
return {
|
|
4850
|
-
path:
|
|
4757
|
+
path: entry.path,
|
|
4851
4758
|
owedPhase: null,
|
|
4852
|
-
enqueuedAt:
|
|
4759
|
+
enqueuedAt: entry.enqueuedAt,
|
|
4853
4760
|
};
|
|
4854
4761
|
}
|
|
4855
4762
|
}));
|
|
@@ -4870,21 +4777,6 @@ function registerQueueRoutes(app, deps) {
|
|
|
4870
4777
|
catch {
|
|
4871
4778
|
// If listing fails, automatic stays empty
|
|
4872
4779
|
}
|
|
4873
|
-
// Legacy: pending is the union of overrides + automatic + legacy queue items
|
|
4874
|
-
const pendingItems = [
|
|
4875
|
-
...enrichedOverrides.map((o) => ({
|
|
4876
|
-
path: o.path,
|
|
4877
|
-
owedPhase: o.owedPhase,
|
|
4878
|
-
})),
|
|
4879
|
-
...automatic.map((a) => ({
|
|
4880
|
-
path: a.path,
|
|
4881
|
-
owedPhase: a.owedPhase,
|
|
4882
|
-
})),
|
|
4883
|
-
...queue.pending.map((item) => ({
|
|
4884
|
-
path: item.path,
|
|
4885
|
-
owedPhase: null,
|
|
4886
|
-
})),
|
|
4887
|
-
];
|
|
4888
4780
|
return {
|
|
4889
4781
|
current: currentPhase
|
|
4890
4782
|
? {
|
|
@@ -4892,58 +4784,45 @@ function registerQueueRoutes(app, deps) {
|
|
|
4892
4784
|
phase: currentPhase.phase,
|
|
4893
4785
|
startedAt: currentPhase.startedAt,
|
|
4894
4786
|
}
|
|
4895
|
-
:
|
|
4896
|
-
|
|
4897
|
-
path: queue.current.path,
|
|
4898
|
-
phase: null,
|
|
4899
|
-
startedAt: queue.current.enqueuedAt,
|
|
4900
|
-
}
|
|
4901
|
-
: null,
|
|
4902
|
-
overrides: enrichedOverrides,
|
|
4787
|
+
: null,
|
|
4788
|
+
pending: enrichedPending,
|
|
4903
4789
|
automatic,
|
|
4904
|
-
pending: pendingItems,
|
|
4905
|
-
// Legacy state
|
|
4906
|
-
state: queue.getState(),
|
|
4907
4790
|
};
|
|
4908
4791
|
});
|
|
4909
4792
|
app.post(getEndpoint('queueClear').path, () => {
|
|
4910
|
-
const removed = queue.
|
|
4793
|
+
const removed = queue.clear();
|
|
4911
4794
|
return { cleared: removed };
|
|
4912
4795
|
});
|
|
4913
4796
|
app.post(getEndpoint('abort').path, async (_request, reply) => {
|
|
4914
|
-
// Check 3-layer current first
|
|
4915
4797
|
const currentPhase = queue.currentPhase;
|
|
4916
|
-
|
|
4917
|
-
if (!current) {
|
|
4798
|
+
if (!currentPhase) {
|
|
4918
4799
|
return reply.status(200).send({ status: 'idle' });
|
|
4919
4800
|
}
|
|
4920
4801
|
// Abort the executor
|
|
4921
4802
|
deps.executor?.abort();
|
|
4922
|
-
const metaDir = resolveMetaDir(
|
|
4923
|
-
const phase = currentPhase
|
|
4803
|
+
const metaDir = resolveMetaDir(currentPhase.path);
|
|
4804
|
+
const { phase } = currentPhase;
|
|
4924
4805
|
// Transition running phase to failed and write _error to meta.json
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
// Best-effort — meta may be unreadable
|
|
4946
|
-
}
|
|
4806
|
+
try {
|
|
4807
|
+
const meta = await readMetaJson(metaDir);
|
|
4808
|
+
let ps = derivePhaseState(meta);
|
|
4809
|
+
ps = phaseFailed(ps, phase);
|
|
4810
|
+
const updated = {
|
|
4811
|
+
...meta,
|
|
4812
|
+
_phaseState: ps,
|
|
4813
|
+
_error: {
|
|
4814
|
+
step: phase,
|
|
4815
|
+
code: 'ABORT',
|
|
4816
|
+
message: 'Aborted by operator',
|
|
4817
|
+
},
|
|
4818
|
+
};
|
|
4819
|
+
const lockPath = join(metaDir, '.lock');
|
|
4820
|
+
const metaJsonPath = join(metaDir, 'meta.json');
|
|
4821
|
+
await writeFile(lockPath, JSON.stringify(updated, null, 2) + '\n');
|
|
4822
|
+
await copyFile(lockPath, metaJsonPath);
|
|
4823
|
+
}
|
|
4824
|
+
catch {
|
|
4825
|
+
// Best-effort — meta may be unreadable
|
|
4947
4826
|
}
|
|
4948
4827
|
// Release the lock for the current meta path
|
|
4949
4828
|
try {
|
|
@@ -4952,11 +4831,11 @@ function registerQueueRoutes(app, deps) {
|
|
|
4952
4831
|
catch {
|
|
4953
4832
|
// Lock may already be released
|
|
4954
4833
|
}
|
|
4955
|
-
deps.logger.info({ path:
|
|
4834
|
+
deps.logger.info({ path: currentPhase.path }, 'Synthesis aborted');
|
|
4956
4835
|
return {
|
|
4957
4836
|
status: 'aborted',
|
|
4958
|
-
path:
|
|
4959
|
-
|
|
4837
|
+
path: currentPhase.path,
|
|
4838
|
+
phase,
|
|
4960
4839
|
};
|
|
4961
4840
|
});
|
|
4962
4841
|
}
|
|
@@ -5039,9 +4918,9 @@ async function checkWatcher(url) {
|
|
|
5039
4918
|
function deriveServiceState(deps) {
|
|
5040
4919
|
if (deps.shuttingDown)
|
|
5041
4920
|
return 'stopping';
|
|
5042
|
-
if (deps.queue.
|
|
4921
|
+
if (deps.queue.currentPhase)
|
|
5043
4922
|
return 'synthesizing';
|
|
5044
|
-
if (deps.queue.depth > 0
|
|
4923
|
+
if (deps.queue.depth > 0)
|
|
5045
4924
|
return 'waiting';
|
|
5046
4925
|
return 'idle';
|
|
5047
4926
|
}
|
|
@@ -5093,7 +4972,7 @@ function registerStatusRoute(app, deps) {
|
|
|
5093
4972
|
}
|
|
5094
4973
|
return {
|
|
5095
4974
|
serviceState: deriveServiceState(deps),
|
|
5096
|
-
currentTarget: queue.
|
|
4975
|
+
currentTarget: queue.currentPhase?.path ?? null,
|
|
5097
4976
|
queue: queue.getState(),
|
|
5098
4977
|
stats: {
|
|
5099
4978
|
totalSyntheses: stats.totalSyntheses,
|
|
@@ -5181,7 +5060,7 @@ function registerSynthesizeRoute(app, deps) {
|
|
|
5181
5060
|
alreadyQueued: false,
|
|
5182
5061
|
});
|
|
5183
5062
|
}
|
|
5184
|
-
const result = queue.
|
|
5063
|
+
const result = queue.enqueue(targetPath);
|
|
5185
5064
|
return reply.code(202).send({
|
|
5186
5065
|
status: 'queued',
|
|
5187
5066
|
path: targetPath,
|
|
@@ -5212,8 +5091,9 @@ function registerSynthesizeRoute(app, deps) {
|
|
|
5212
5091
|
const stalest = winner.node.metaPath;
|
|
5213
5092
|
const enqueueResult = queue.enqueue(stalest);
|
|
5214
5093
|
return reply.code(202).send({
|
|
5215
|
-
status: '
|
|
5094
|
+
status: 'queued',
|
|
5216
5095
|
path: stalest,
|
|
5096
|
+
owedPhase: winner.owedPhase,
|
|
5217
5097
|
queuePosition: enqueueResult.position,
|
|
5218
5098
|
alreadyQueued: enqueueResult.alreadyQueued,
|
|
5219
5099
|
});
|
|
@@ -5362,26 +5242,14 @@ function registerShutdownHandlers(deps) {
|
|
|
5362
5242
|
deps.logger.info('Scheduler stopped');
|
|
5363
5243
|
}
|
|
5364
5244
|
// 2. Release lock for in-progress synthesis
|
|
5365
|
-
const current = deps.queue.current;
|
|
5366
|
-
if (current) {
|
|
5367
|
-
try {
|
|
5368
|
-
releaseLock(current.path);
|
|
5369
|
-
deps.logger.info({ path: current.path }, 'Released lock for in-progress synthesis');
|
|
5370
|
-
}
|
|
5371
|
-
catch {
|
|
5372
|
-
deps.logger.warn({ path: current.path }, 'Failed to release lock during shutdown');
|
|
5373
|
-
}
|
|
5374
|
-
}
|
|
5375
|
-
// Release lock for in-progress override synthesis (only when it
|
|
5376
|
-
// differs from the legacy current item to avoid double-release)
|
|
5377
5245
|
const currentPhase = deps.queue.currentPhase;
|
|
5378
|
-
if (currentPhase
|
|
5246
|
+
if (currentPhase) {
|
|
5379
5247
|
try {
|
|
5380
5248
|
releaseLock(resolveMetaDir(currentPhase.path));
|
|
5381
|
-
deps.logger.info({ path: currentPhase.path }, 'Released lock for in-progress
|
|
5249
|
+
deps.logger.info({ path: currentPhase.path }, 'Released lock for in-progress synthesis');
|
|
5382
5250
|
}
|
|
5383
5251
|
catch {
|
|
5384
|
-
deps.logger.warn({ path: currentPhase.path }, 'Failed to release
|
|
5252
|
+
deps.logger.warn({ path: currentPhase.path }, 'Failed to release lock during shutdown');
|
|
5385
5253
|
}
|
|
5386
5254
|
}
|
|
5387
5255
|
// 3. Close server
|