@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
|
@@ -975,16 +975,20 @@ class GatewayExecutor {
|
|
|
975
975
|
: '';
|
|
976
976
|
return text && text.trim() !== 'ANNOUNCE_SKIP' ? text : undefined;
|
|
977
977
|
}
|
|
978
|
-
/**
|
|
978
|
+
/**
|
|
979
|
+
* Check history messages for timeout detection.
|
|
980
|
+
*
|
|
981
|
+
* Does NOT determine completion — session completion is authoritative
|
|
982
|
+
* (via sessions_list). History stop reasons can false-positive on
|
|
983
|
+
* sessions_yield artifacts (#200).
|
|
984
|
+
*/
|
|
979
985
|
static checkHistoryCompletion(messages) {
|
|
980
986
|
if (messages.length === 0)
|
|
981
|
-
return {
|
|
987
|
+
return { timedOut: false };
|
|
982
988
|
const last = messages[messages.length - 1];
|
|
983
989
|
if (last.role !== 'assistant' || !last.stopReason)
|
|
984
|
-
return {
|
|
985
|
-
|
|
986
|
-
return { done: false, timedOut: false };
|
|
987
|
-
return { done: true, timedOut: last.stopReason === 'timeout' };
|
|
990
|
+
return { timedOut: false };
|
|
991
|
+
return { timedOut: last.stopReason === 'timeout' };
|
|
988
992
|
}
|
|
989
993
|
/** Invoke a gateway tool via the /tools/invoke HTTP endpoint. */
|
|
990
994
|
async invoke(tool, args, sessionKey) {
|
|
@@ -1147,12 +1151,14 @@ class GatewayExecutor {
|
|
|
1147
1151
|
historyResult.result?.messages ??
|
|
1148
1152
|
[];
|
|
1149
1153
|
const msgArray = messages;
|
|
1150
|
-
// Check 1:
|
|
1151
|
-
const {
|
|
1154
|
+
// Check 1: history-based timeout detection (stop reason)
|
|
1155
|
+
const { timedOut: historyTimedOut } = GatewayExecutor.checkHistoryCompletion(msgArray);
|
|
1152
1156
|
// Check 2: session completion status via sessions_list
|
|
1157
|
+
// Gate on session completion only — history stop reasons can
|
|
1158
|
+
// false-positive on sessions_yield artifacts (#200).
|
|
1153
1159
|
const sessionInfo = await this.getSessionInfo(sessionKey);
|
|
1154
1160
|
const timedOut = sessionInfo.timedOut || historyTimedOut;
|
|
1155
|
-
if (
|
|
1161
|
+
if (sessionInfo.completed) {
|
|
1156
1162
|
const tokens = sessionInfo.tokens;
|
|
1157
1163
|
// Gateway-side timeout detected — check staging file for recovery
|
|
1158
1164
|
if (timedOut) {
|
|
@@ -3167,36 +3173,33 @@ class ProgressReporter {
|
|
|
3167
3173
|
}
|
|
3168
3174
|
|
|
3169
3175
|
/**
|
|
3170
|
-
*
|
|
3176
|
+
* Synthesis queue.
|
|
3171
3177
|
*
|
|
3172
3178
|
* Layer 1: Current — the single item currently executing (at most one).
|
|
3173
|
-
* Layer 2:
|
|
3174
|
-
*
|
|
3175
|
-
* Layer 3: Automatic — computed on read, not persisted. All
|
|
3176
|
-
* pending phase, ranked by scheduler priority.
|
|
3177
|
-
*
|
|
3178
|
-
* Legacy: `pending` array is the union of overrides + automatic.
|
|
3179
|
+
* Layer 2: Pending — items enqueued via POST /synthesize (targeted) or
|
|
3180
|
+
* scheduler tick (automatic). FIFO, ahead of automatic candidates.
|
|
3181
|
+
* Layer 3: Automatic — computed on read (GET /queue), not persisted. All
|
|
3182
|
+
* metas with a pending phase, ranked by scheduler priority.
|
|
3179
3183
|
*
|
|
3180
3184
|
* @module queue
|
|
3181
3185
|
*/
|
|
3182
3186
|
const DEPTH_WARNING_THRESHOLD = 3;
|
|
3187
|
+
/** Strip trailing .meta suffix for consistent path comparison. */
|
|
3188
|
+
function normQueuePath(p) {
|
|
3189
|
+
return p.endsWith('.meta') ? p.slice(0, -5).replace(/[/\\]$/, '') : p;
|
|
3190
|
+
}
|
|
3183
3191
|
/**
|
|
3184
|
-
*
|
|
3192
|
+
* Synthesis queue.
|
|
3185
3193
|
*
|
|
3186
|
-
* Only one synthesis runs at a time.
|
|
3187
|
-
* take priority over automatic candidates.
|
|
3194
|
+
* Only one synthesis runs at a time. Explicitly enqueued items
|
|
3195
|
+
* take priority over automatic (computed-on-read) candidates.
|
|
3188
3196
|
*/
|
|
3189
3197
|
class SynthesisQueue {
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
currentItem = null;
|
|
3198
|
+
entries = [];
|
|
3199
|
+
currentPhaseItem = null;
|
|
3193
3200
|
processing = false;
|
|
3194
3201
|
logger;
|
|
3195
3202
|
onEnqueueCallback = null;
|
|
3196
|
-
/** Explicit override entries (3-layer model). */
|
|
3197
|
-
overrideEntries = [];
|
|
3198
|
-
/** Currently executing item with phase info (3-layer model). */
|
|
3199
|
-
currentPhaseItem = null;
|
|
3200
3203
|
constructor(logger) {
|
|
3201
3204
|
this.logger = logger;
|
|
3202
3205
|
}
|
|
@@ -3204,52 +3207,67 @@ class SynthesisQueue {
|
|
|
3204
3207
|
onEnqueue(callback) {
|
|
3205
3208
|
this.onEnqueueCallback = callback;
|
|
3206
3209
|
}
|
|
3207
|
-
// ──
|
|
3210
|
+
// ── Enqueue / dequeue ──────────────────────────────────────────────
|
|
3208
3211
|
/**
|
|
3209
|
-
* Add an
|
|
3212
|
+
* Add an entry to the queue.
|
|
3210
3213
|
* Deduped by path. Returns position and whether already queued.
|
|
3211
3214
|
*/
|
|
3212
|
-
|
|
3215
|
+
enqueue(path) {
|
|
3216
|
+
const norm = normQueuePath(path);
|
|
3213
3217
|
// Check if currently executing
|
|
3214
|
-
if (this.currentPhaseItem
|
|
3215
|
-
this.
|
|
3218
|
+
if (this.currentPhaseItem &&
|
|
3219
|
+
normQueuePath(this.currentPhaseItem.path) === norm) {
|
|
3216
3220
|
return { position: 0, alreadyQueued: true };
|
|
3217
3221
|
}
|
|
3218
|
-
// Check if already in
|
|
3219
|
-
const existing = this.
|
|
3222
|
+
// Check if already in queue
|
|
3223
|
+
const existing = this.entries.findIndex((e) => normQueuePath(e.path) === norm);
|
|
3220
3224
|
if (existing !== -1) {
|
|
3221
3225
|
return { position: existing, alreadyQueued: true };
|
|
3222
3226
|
}
|
|
3223
|
-
this.
|
|
3227
|
+
this.entries.push({
|
|
3224
3228
|
path,
|
|
3225
3229
|
enqueuedAt: new Date().toISOString(),
|
|
3226
3230
|
});
|
|
3227
|
-
const position = this.
|
|
3228
|
-
if (this.
|
|
3229
|
-
this.logger.warn({ depth: this.
|
|
3231
|
+
const position = this.entries.length - 1;
|
|
3232
|
+
if (this.entries.length > DEPTH_WARNING_THRESHOLD) {
|
|
3233
|
+
this.logger.warn({ depth: this.entries.length }, 'Queue depth exceeds threshold');
|
|
3230
3234
|
}
|
|
3231
3235
|
this.onEnqueueCallback?.();
|
|
3232
3236
|
return { position, alreadyQueued: false };
|
|
3233
3237
|
}
|
|
3234
|
-
/** Dequeue the next
|
|
3235
|
-
|
|
3236
|
-
return this.
|
|
3238
|
+
/** Dequeue the next entry, or undefined if empty. */
|
|
3239
|
+
dequeue() {
|
|
3240
|
+
return this.entries.shift();
|
|
3241
|
+
}
|
|
3242
|
+
/** Get all queued entries (shallow copy). */
|
|
3243
|
+
get items() {
|
|
3244
|
+
return [...this.entries];
|
|
3237
3245
|
}
|
|
3238
|
-
/**
|
|
3239
|
-
get
|
|
3240
|
-
return
|
|
3246
|
+
/** Number of items waiting in the queue (excludes current). */
|
|
3247
|
+
get depth() {
|
|
3248
|
+
return this.entries.length;
|
|
3241
3249
|
}
|
|
3242
|
-
/**
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3250
|
+
/**
|
|
3251
|
+
* Remove all pending items from the queue.
|
|
3252
|
+
* Does not affect the currently-running item.
|
|
3253
|
+
*
|
|
3254
|
+
* @returns The number of items removed.
|
|
3255
|
+
*/
|
|
3256
|
+
clear() {
|
|
3257
|
+
const count = this.entries.length;
|
|
3258
|
+
this.entries = [];
|
|
3246
3259
|
return count;
|
|
3247
3260
|
}
|
|
3248
|
-
/** Check
|
|
3249
|
-
|
|
3250
|
-
|
|
3261
|
+
/** Check whether a path is in the queue or currently being synthesized. */
|
|
3262
|
+
has(path) {
|
|
3263
|
+
const norm = normQueuePath(path);
|
|
3264
|
+
if (this.currentPhaseItem &&
|
|
3265
|
+
normQueuePath(this.currentPhaseItem.path) === norm) {
|
|
3266
|
+
return true;
|
|
3267
|
+
}
|
|
3268
|
+
return this.entries.some((e) => normQueuePath(e.path) === norm);
|
|
3251
3269
|
}
|
|
3252
|
-
// ── Current-item tracking
|
|
3270
|
+
// ── Current-item tracking ──────────────────────────────────────────
|
|
3253
3271
|
/** Set the currently executing phase item. */
|
|
3254
3272
|
setCurrentPhase(path, phase) {
|
|
3255
3273
|
this.currentPhaseItem = {
|
|
@@ -3266,129 +3284,21 @@ class SynthesisQueue {
|
|
|
3266
3284
|
get currentPhase() {
|
|
3267
3285
|
return this.currentPhaseItem;
|
|
3268
3286
|
}
|
|
3269
|
-
// ──
|
|
3270
|
-
/**
|
|
3271
|
-
* Add a path to the synthesis queue.
|
|
3272
|
-
*
|
|
3273
|
-
* @param path - Meta path to synthesize.
|
|
3274
|
-
* @param priority - If true, insert at front of queue.
|
|
3275
|
-
* @returns Position and whether the path was already queued.
|
|
3276
|
-
*/
|
|
3277
|
-
enqueue(path, priority = false) {
|
|
3278
|
-
if (this.currentItem?.path === path) {
|
|
3279
|
-
return { position: 0, alreadyQueued: true };
|
|
3280
|
-
}
|
|
3281
|
-
const existingIndex = this.queue.findIndex((item) => item.path === path);
|
|
3282
|
-
if (existingIndex !== -1) {
|
|
3283
|
-
return { position: existingIndex, alreadyQueued: true };
|
|
3284
|
-
}
|
|
3285
|
-
const item = {
|
|
3286
|
-
path,
|
|
3287
|
-
priority,
|
|
3288
|
-
enqueuedAt: new Date().toISOString(),
|
|
3289
|
-
};
|
|
3290
|
-
if (priority) {
|
|
3291
|
-
this.queue.unshift(item);
|
|
3292
|
-
}
|
|
3293
|
-
else {
|
|
3294
|
-
this.queue.push(item);
|
|
3295
|
-
}
|
|
3296
|
-
if (this.queue.length > DEPTH_WARNING_THRESHOLD) {
|
|
3297
|
-
this.logger.warn({ depth: this.queue.length }, 'Queue depth exceeds threshold');
|
|
3298
|
-
}
|
|
3299
|
-
const position = this.queue.findIndex((i) => i.path === path);
|
|
3300
|
-
this.onEnqueueCallback?.();
|
|
3301
|
-
return { position, alreadyQueued: false };
|
|
3302
|
-
}
|
|
3303
|
-
/** Remove and return the next item from the queue. */
|
|
3304
|
-
dequeue() {
|
|
3305
|
-
const item = this.queue.shift();
|
|
3306
|
-
if (item) {
|
|
3307
|
-
this.currentItem = item;
|
|
3308
|
-
}
|
|
3309
|
-
return item;
|
|
3310
|
-
}
|
|
3311
|
-
/** Mark the currently-running synthesis as complete. */
|
|
3312
|
-
complete() {
|
|
3313
|
-
this.currentItem = null;
|
|
3314
|
-
}
|
|
3315
|
-
/** Number of items waiting in the queue (excludes current). */
|
|
3316
|
-
get depth() {
|
|
3317
|
-
return this.queue.length;
|
|
3318
|
-
}
|
|
3319
|
-
/** The item currently being synthesized, or null. */
|
|
3320
|
-
get current() {
|
|
3321
|
-
return this.currentItem;
|
|
3322
|
-
}
|
|
3323
|
-
/** A shallow copy of the queued items. */
|
|
3324
|
-
get items() {
|
|
3325
|
-
return [...this.queue];
|
|
3326
|
-
}
|
|
3327
|
-
/** A shallow copy of the pending items (alias for items). */
|
|
3328
|
-
get pending() {
|
|
3329
|
-
return [...this.queue];
|
|
3330
|
-
}
|
|
3331
|
-
/**
|
|
3332
|
-
* Remove all pending items from the queue.
|
|
3333
|
-
* Does not affect the currently-running item.
|
|
3334
|
-
*
|
|
3335
|
-
* @returns The number of items removed.
|
|
3336
|
-
*/
|
|
3337
|
-
clear() {
|
|
3338
|
-
const count = this.queue.length;
|
|
3339
|
-
this.queue = [];
|
|
3340
|
-
return count;
|
|
3341
|
-
}
|
|
3342
|
-
/** Check whether a path is in the queue or currently being synthesized. */
|
|
3343
|
-
has(path) {
|
|
3344
|
-
if (this.currentItem?.path === path)
|
|
3345
|
-
return true;
|
|
3346
|
-
if (this.currentPhaseItem?.path === path)
|
|
3347
|
-
return true;
|
|
3348
|
-
return (this.queue.some((item) => item.path === path) ||
|
|
3349
|
-
this.overrideEntries.some((e) => e.path === path));
|
|
3350
|
-
}
|
|
3351
|
-
/** Get the 0-indexed position of a path in the queue. */
|
|
3352
|
-
getPosition(path) {
|
|
3353
|
-
// Check overrides first
|
|
3354
|
-
const overrideIdx = this.overrideEntries.findIndex((e) => e.path === path);
|
|
3355
|
-
if (overrideIdx !== -1)
|
|
3356
|
-
return overrideIdx;
|
|
3357
|
-
const index = this.queue.findIndex((item) => item.path === path);
|
|
3358
|
-
return index === -1 ? null : index;
|
|
3359
|
-
}
|
|
3360
|
-
/** Dequeue the next item: overrides first, then legacy queue. */
|
|
3361
|
-
nextItem() {
|
|
3362
|
-
const override = this.dequeueOverride();
|
|
3363
|
-
if (override)
|
|
3364
|
-
return { path: override.path, source: 'override' };
|
|
3365
|
-
const item = this.dequeue();
|
|
3366
|
-
if (item)
|
|
3367
|
-
return { path: item.path, source: 'legacy' };
|
|
3368
|
-
return undefined;
|
|
3369
|
-
}
|
|
3287
|
+
// ── Queue state ────────────────────────────────────────────────────
|
|
3370
3288
|
/** Return a snapshot of queue state for the /status endpoint. */
|
|
3371
3289
|
getState() {
|
|
3372
3290
|
return {
|
|
3373
|
-
depth: this.
|
|
3374
|
-
items:
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
enqueuedAt: e.enqueuedAt,
|
|
3379
|
-
})),
|
|
3380
|
-
...this.queue.map((item) => ({
|
|
3381
|
-
path: item.path,
|
|
3382
|
-
priority: item.priority,
|
|
3383
|
-
enqueuedAt: item.enqueuedAt,
|
|
3384
|
-
})),
|
|
3385
|
-
],
|
|
3291
|
+
depth: this.entries.length,
|
|
3292
|
+
items: this.entries.map((e) => ({
|
|
3293
|
+
path: e.path,
|
|
3294
|
+
enqueuedAt: e.enqueuedAt,
|
|
3295
|
+
})),
|
|
3386
3296
|
};
|
|
3387
3297
|
}
|
|
3298
|
+
// ── Processing ─────────────────────────────────────────────────────
|
|
3388
3299
|
/**
|
|
3389
|
-
* Process queued items one at a time until
|
|
3300
|
+
* Process queued items one at a time until the queue is empty.
|
|
3390
3301
|
*
|
|
3391
|
-
* Override entries are processed first (FIFO), then legacy queue items.
|
|
3392
3302
|
* Re-entry is prevented: if already processing, the call returns
|
|
3393
3303
|
* immediately. Errors are logged and do not block subsequent items.
|
|
3394
3304
|
*
|
|
@@ -3399,7 +3309,7 @@ class SynthesisQueue {
|
|
|
3399
3309
|
return;
|
|
3400
3310
|
this.processing = true;
|
|
3401
3311
|
try {
|
|
3402
|
-
let next = this.
|
|
3312
|
+
let next = this.dequeue();
|
|
3403
3313
|
while (next) {
|
|
3404
3314
|
try {
|
|
3405
3315
|
await synthesizeFn(next.path);
|
|
@@ -3408,9 +3318,7 @@ class SynthesisQueue {
|
|
|
3408
3318
|
this.logger.error({ path: next.path, err }, 'Synthesis failed');
|
|
3409
3319
|
}
|
|
3410
3320
|
this.clearCurrentPhase();
|
|
3411
|
-
|
|
3412
|
-
this.complete();
|
|
3413
|
-
next = this.nextItem();
|
|
3321
|
+
next = this.dequeue();
|
|
3414
3322
|
}
|
|
3415
3323
|
}
|
|
3416
3324
|
finally {
|
|
@@ -3964,7 +3872,6 @@ class Scheduler {
|
|
|
3964
3872
|
this.logger.debug({ backoffMultiplier: this.backoffMultiplier }, 'No ready phases found, increasing backoff');
|
|
3965
3873
|
return;
|
|
3966
3874
|
}
|
|
3967
|
-
// Enqueue using the legacy queue path (backward compat with processQueue)
|
|
3968
3875
|
this.queue.enqueue(candidate.path);
|
|
3969
3876
|
this.logger.info({ path: candidate.path, phase: candidate.phase, band: candidate.band }, 'Enqueued phase candidate');
|
|
3970
3877
|
// Opportunistic watcher restart detection
|
|
@@ -4563,8 +4470,8 @@ function registerPreviewRoute(app, deps) {
|
|
|
4563
4470
|
/**
|
|
4564
4471
|
* Queue management and abort routes.
|
|
4565
4472
|
*
|
|
4566
|
-
* - GET /queue — 3-layer queue model (current,
|
|
4567
|
-
* - POST /queue/clear — remove
|
|
4473
|
+
* - GET /queue — 3-layer queue model (current, pending, automatic)
|
|
4474
|
+
* - POST /queue/clear — remove pending entries
|
|
4568
4475
|
* - POST /synthesize/abort — abort the current synthesis
|
|
4569
4476
|
*
|
|
4570
4477
|
* @module routes/queue
|
|
@@ -4574,24 +4481,24 @@ function registerQueueRoutes(app, deps) {
|
|
|
4574
4481
|
const { queue } = deps;
|
|
4575
4482
|
app.get(getEndpoint('queue').path, async () => {
|
|
4576
4483
|
const currentPhase = queue.currentPhase;
|
|
4577
|
-
const
|
|
4578
|
-
// Compute owedPhase for each
|
|
4579
|
-
const
|
|
4484
|
+
const pending = queue.items;
|
|
4485
|
+
// Compute owedPhase for each pending entry by reading meta state
|
|
4486
|
+
const enrichedPending = await Promise.all(pending.map(async (entry) => {
|
|
4580
4487
|
try {
|
|
4581
|
-
const metaDir = resolveMetaDir(
|
|
4488
|
+
const metaDir = resolveMetaDir(entry.path);
|
|
4582
4489
|
const meta = await readMetaJson(metaDir);
|
|
4583
4490
|
const ps = derivePhaseState(meta);
|
|
4584
4491
|
return {
|
|
4585
|
-
path:
|
|
4492
|
+
path: entry.path,
|
|
4586
4493
|
owedPhase: getOwedPhase(ps),
|
|
4587
|
-
enqueuedAt:
|
|
4494
|
+
enqueuedAt: entry.enqueuedAt,
|
|
4588
4495
|
};
|
|
4589
4496
|
}
|
|
4590
4497
|
catch {
|
|
4591
4498
|
return {
|
|
4592
|
-
path:
|
|
4499
|
+
path: entry.path,
|
|
4593
4500
|
owedPhase: null,
|
|
4594
|
-
enqueuedAt:
|
|
4501
|
+
enqueuedAt: entry.enqueuedAt,
|
|
4595
4502
|
};
|
|
4596
4503
|
}
|
|
4597
4504
|
}));
|
|
@@ -4612,21 +4519,6 @@ function registerQueueRoutes(app, deps) {
|
|
|
4612
4519
|
catch {
|
|
4613
4520
|
// If listing fails, automatic stays empty
|
|
4614
4521
|
}
|
|
4615
|
-
// Legacy: pending is the union of overrides + automatic + legacy queue items
|
|
4616
|
-
const pendingItems = [
|
|
4617
|
-
...enrichedOverrides.map((o) => ({
|
|
4618
|
-
path: o.path,
|
|
4619
|
-
owedPhase: o.owedPhase,
|
|
4620
|
-
})),
|
|
4621
|
-
...automatic.map((a) => ({
|
|
4622
|
-
path: a.path,
|
|
4623
|
-
owedPhase: a.owedPhase,
|
|
4624
|
-
})),
|
|
4625
|
-
...queue.pending.map((item) => ({
|
|
4626
|
-
path: item.path,
|
|
4627
|
-
owedPhase: null,
|
|
4628
|
-
})),
|
|
4629
|
-
];
|
|
4630
4522
|
return {
|
|
4631
4523
|
current: currentPhase
|
|
4632
4524
|
? {
|
|
@@ -4634,58 +4526,45 @@ function registerQueueRoutes(app, deps) {
|
|
|
4634
4526
|
phase: currentPhase.phase,
|
|
4635
4527
|
startedAt: currentPhase.startedAt,
|
|
4636
4528
|
}
|
|
4637
|
-
:
|
|
4638
|
-
|
|
4639
|
-
path: queue.current.path,
|
|
4640
|
-
phase: null,
|
|
4641
|
-
startedAt: queue.current.enqueuedAt,
|
|
4642
|
-
}
|
|
4643
|
-
: null,
|
|
4644
|
-
overrides: enrichedOverrides,
|
|
4529
|
+
: null,
|
|
4530
|
+
pending: enrichedPending,
|
|
4645
4531
|
automatic,
|
|
4646
|
-
pending: pendingItems,
|
|
4647
|
-
// Legacy state
|
|
4648
|
-
state: queue.getState(),
|
|
4649
4532
|
};
|
|
4650
4533
|
});
|
|
4651
4534
|
app.post(getEndpoint('queueClear').path, () => {
|
|
4652
|
-
const removed = queue.
|
|
4535
|
+
const removed = queue.clear();
|
|
4653
4536
|
return { cleared: removed };
|
|
4654
4537
|
});
|
|
4655
4538
|
app.post(getEndpoint('abort').path, async (_request, reply) => {
|
|
4656
|
-
// Check 3-layer current first
|
|
4657
4539
|
const currentPhase = queue.currentPhase;
|
|
4658
|
-
|
|
4659
|
-
if (!current) {
|
|
4540
|
+
if (!currentPhase) {
|
|
4660
4541
|
return reply.status(200).send({ status: 'idle' });
|
|
4661
4542
|
}
|
|
4662
4543
|
// Abort the executor
|
|
4663
4544
|
deps.executor?.abort();
|
|
4664
|
-
const metaDir = resolveMetaDir(
|
|
4665
|
-
const phase = currentPhase
|
|
4545
|
+
const metaDir = resolveMetaDir(currentPhase.path);
|
|
4546
|
+
const { phase } = currentPhase;
|
|
4666
4547
|
// Transition running phase to failed and write _error to meta.json
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
// Best-effort — meta may be unreadable
|
|
4688
|
-
}
|
|
4548
|
+
try {
|
|
4549
|
+
const meta = await readMetaJson(metaDir);
|
|
4550
|
+
let ps = derivePhaseState(meta);
|
|
4551
|
+
ps = phaseFailed(ps, phase);
|
|
4552
|
+
const updated = {
|
|
4553
|
+
...meta,
|
|
4554
|
+
_phaseState: ps,
|
|
4555
|
+
_error: {
|
|
4556
|
+
step: phase,
|
|
4557
|
+
code: 'ABORT',
|
|
4558
|
+
message: 'Aborted by operator',
|
|
4559
|
+
},
|
|
4560
|
+
};
|
|
4561
|
+
const lockPath = join(metaDir, '.lock');
|
|
4562
|
+
const metaJsonPath = join(metaDir, 'meta.json');
|
|
4563
|
+
await writeFile(lockPath, JSON.stringify(updated, null, 2) + '\n');
|
|
4564
|
+
await copyFile(lockPath, metaJsonPath);
|
|
4565
|
+
}
|
|
4566
|
+
catch {
|
|
4567
|
+
// Best-effort — meta may be unreadable
|
|
4689
4568
|
}
|
|
4690
4569
|
// Release the lock for the current meta path
|
|
4691
4570
|
try {
|
|
@@ -4694,11 +4573,11 @@ function registerQueueRoutes(app, deps) {
|
|
|
4694
4573
|
catch {
|
|
4695
4574
|
// Lock may already be released
|
|
4696
4575
|
}
|
|
4697
|
-
deps.logger.info({ path:
|
|
4576
|
+
deps.logger.info({ path: currentPhase.path }, 'Synthesis aborted');
|
|
4698
4577
|
return {
|
|
4699
4578
|
status: 'aborted',
|
|
4700
|
-
path:
|
|
4701
|
-
|
|
4579
|
+
path: currentPhase.path,
|
|
4580
|
+
phase,
|
|
4702
4581
|
};
|
|
4703
4582
|
});
|
|
4704
4583
|
}
|
|
@@ -4809,9 +4688,9 @@ async function checkWatcher(url) {
|
|
|
4809
4688
|
function deriveServiceState(deps) {
|
|
4810
4689
|
if (deps.shuttingDown)
|
|
4811
4690
|
return 'stopping';
|
|
4812
|
-
if (deps.queue.
|
|
4691
|
+
if (deps.queue.currentPhase)
|
|
4813
4692
|
return 'synthesizing';
|
|
4814
|
-
if (deps.queue.depth > 0
|
|
4693
|
+
if (deps.queue.depth > 0)
|
|
4815
4694
|
return 'waiting';
|
|
4816
4695
|
return 'idle';
|
|
4817
4696
|
}
|
|
@@ -4863,7 +4742,7 @@ function registerStatusRoute(app, deps) {
|
|
|
4863
4742
|
}
|
|
4864
4743
|
return {
|
|
4865
4744
|
serviceState: deriveServiceState(deps),
|
|
4866
|
-
currentTarget: queue.
|
|
4745
|
+
currentTarget: queue.currentPhase?.path ?? null,
|
|
4867
4746
|
queue: queue.getState(),
|
|
4868
4747
|
stats: {
|
|
4869
4748
|
totalSyntheses: stats.totalSyntheses,
|
|
@@ -4951,7 +4830,7 @@ function registerSynthesizeRoute(app, deps) {
|
|
|
4951
4830
|
alreadyQueued: false,
|
|
4952
4831
|
});
|
|
4953
4832
|
}
|
|
4954
|
-
const result = queue.
|
|
4833
|
+
const result = queue.enqueue(targetPath);
|
|
4955
4834
|
return reply.code(202).send({
|
|
4956
4835
|
status: 'queued',
|
|
4957
4836
|
path: targetPath,
|
|
@@ -4982,8 +4861,9 @@ function registerSynthesizeRoute(app, deps) {
|
|
|
4982
4861
|
const stalest = winner.node.metaPath;
|
|
4983
4862
|
const enqueueResult = queue.enqueue(stalest);
|
|
4984
4863
|
return reply.code(202).send({
|
|
4985
|
-
status: '
|
|
4864
|
+
status: 'queued',
|
|
4986
4865
|
path: stalest,
|
|
4866
|
+
owedPhase: winner.owedPhase,
|
|
4987
4867
|
queuePosition: enqueueResult.position,
|
|
4988
4868
|
alreadyQueued: enqueueResult.alreadyQueued,
|
|
4989
4869
|
});
|
|
@@ -5132,26 +5012,14 @@ function registerShutdownHandlers(deps) {
|
|
|
5132
5012
|
deps.logger.info('Scheduler stopped');
|
|
5133
5013
|
}
|
|
5134
5014
|
// 2. Release lock for in-progress synthesis
|
|
5135
|
-
const current = deps.queue.current;
|
|
5136
|
-
if (current) {
|
|
5137
|
-
try {
|
|
5138
|
-
releaseLock(current.path);
|
|
5139
|
-
deps.logger.info({ path: current.path }, 'Released lock for in-progress synthesis');
|
|
5140
|
-
}
|
|
5141
|
-
catch {
|
|
5142
|
-
deps.logger.warn({ path: current.path }, 'Failed to release lock during shutdown');
|
|
5143
|
-
}
|
|
5144
|
-
}
|
|
5145
|
-
// Release lock for in-progress override synthesis (only when it
|
|
5146
|
-
// differs from the legacy current item to avoid double-release)
|
|
5147
5015
|
const currentPhase = deps.queue.currentPhase;
|
|
5148
|
-
if (currentPhase
|
|
5016
|
+
if (currentPhase) {
|
|
5149
5017
|
try {
|
|
5150
5018
|
releaseLock(resolveMetaDir(currentPhase.path));
|
|
5151
|
-
deps.logger.info({ path: currentPhase.path }, 'Released lock for in-progress
|
|
5019
|
+
deps.logger.info({ path: currentPhase.path }, 'Released lock for in-progress synthesis');
|
|
5152
5020
|
}
|
|
5153
5021
|
catch {
|
|
5154
|
-
deps.logger.warn({ path: currentPhase.path }, 'Failed to release
|
|
5022
|
+
deps.logger.warn({ path: currentPhase.path }, 'Failed to release lock during shutdown');
|
|
5155
5023
|
}
|
|
5156
5024
|
}
|
|
5157
5025
|
// 3. Close server
|
|
@@ -40,7 +40,13 @@ export declare class GatewayExecutor implements MetaExecutor {
|
|
|
40
40
|
private readStagingFile;
|
|
41
41
|
/** Extract plain text from a message content field, skipping ANNOUNCE_SKIP sentinels. */
|
|
42
42
|
private static extractMessageText;
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Check history messages for timeout detection.
|
|
45
|
+
*
|
|
46
|
+
* Does NOT determine completion — session completion is authoritative
|
|
47
|
+
* (via sessions_list). History stop reasons can false-positive on
|
|
48
|
+
* sessions_yield artifacts (#200).
|
|
49
|
+
*/
|
|
44
50
|
private static checkHistoryCompletion;
|
|
45
51
|
/** Invoke a gateway tool via the /tools/invoke HTTP endpoint. */
|
|
46
52
|
private invoke;
|
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export { formatProgressEvent, type ProgressEvent, type ProgressPhase, ProgressRe
|
|
|
23
23
|
export { actualStaleness, computeEffectiveStaleness, hasSteerChanged, isArchitectTriggered, isStale, MAX_STALENESS_SECONDS, type StalenessCandidate, } from './scheduling/index.js';
|
|
24
24
|
export { type MetaConfig, metaConfigSchema, type MetaError, metaErrorSchema, type MetaJson, metaJsonSchema, type ServiceConfig, serviceConfigSchema, } from './schema/index.js';
|
|
25
25
|
export { Scheduler } from './scheduler/index.js';
|
|
26
|
-
export { type EnqueueResult, type
|
|
26
|
+
export { type CurrentItem, type EnqueueResult, type QueueEntry, type QueueState, SynthesisQueue, } from './queue/index.js';
|
|
27
27
|
export { registerRoutes, type RouteDeps, type ServiceStats, } from './routes/index.js';
|
|
28
28
|
export { RuleRegistrar } from './rules/index.js';
|
|
29
29
|
export { verifyRuleApplication } from './rules/verify.js';
|