@bridge4dev/runner 0.45.0 → 0.45.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/supervisor.d.ts +26 -0
- package/dist/supervisor.js +74 -24
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/supervisor.d.ts
CHANGED
|
@@ -53,6 +53,15 @@ export interface SupervisorOptions {
|
|
|
53
53
|
* announced and no rewind can be started.
|
|
54
54
|
*/
|
|
55
55
|
checkpointsEnabled?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* How long a turn that produced nothing is held before it counts (#300).
|
|
58
|
+
*
|
|
59
|
+
* A test seam, like `proposeCommitMessage` above: the real window is 25
|
|
60
|
+
* seconds and no suite can wait that out, so the behaviour would go
|
|
61
|
+
* unverified — which is exactly how the independent review found the hold
|
|
62
|
+
* firing over a working agent (QA-2026-08-16 M-4). Never set in production.
|
|
63
|
+
*/
|
|
64
|
+
emptyTurnSettleMs?: number;
|
|
56
65
|
}
|
|
57
66
|
export declare class Supervisor {
|
|
58
67
|
private readonly ws;
|
|
@@ -78,6 +87,8 @@ export declare class Supervisor {
|
|
|
78
87
|
* agent mid-thought.
|
|
79
88
|
*/
|
|
80
89
|
private static readonly EMPTY_TURN_SETTLE_MS;
|
|
90
|
+
/** The window actually used — the constant, or a test's own shorter one. */
|
|
91
|
+
private readonly emptyTurnSettleMs;
|
|
81
92
|
/** A finished session's journal is kept this long for a late reconnect. */
|
|
82
93
|
private static readonly JOURNAL_TTL_MS;
|
|
83
94
|
/** Backstop: events the API will never accept must not pile up forever. */
|
|
@@ -351,6 +362,21 @@ export declare class Supervisor {
|
|
|
351
362
|
* phantom can never fire after the turn it belonged to has closed properly.
|
|
352
363
|
*/
|
|
353
364
|
private clearEmptyTurn;
|
|
365
|
+
/**
|
|
366
|
+
* Record how many subagents are alive, and say so when it matters (#236).
|
|
367
|
+
*
|
|
368
|
+
* One place, because there are now two callers with opposite news — a frame
|
|
369
|
+
* from the adapter, and the process going away — and «is it the human's turn»
|
|
370
|
+
* must be answered the same way by both.
|
|
371
|
+
*
|
|
372
|
+
* The report on reaching zero is the whole point: the agent is already
|
|
373
|
+
* sitting in a resting status, so nothing else will ever tell the API that
|
|
374
|
+
* the session finally became the person's. `REVIEW` counts as well as
|
|
375
|
+
* `WAITING_INPUT` — a TICKET session waiting on its own subagent is in the
|
|
376
|
+
* same position, and leaving it out was how one of the two statuses kept its
|
|
377
|
+
* notification suppressed for good (QA-2026-08-16 M-5).
|
|
378
|
+
*/
|
|
379
|
+
private setBackgroundTasks;
|
|
354
380
|
private isParkable;
|
|
355
381
|
/**
|
|
356
382
|
* Stop the agent process but keep the session resumable.
|
package/dist/supervisor.js
CHANGED
|
@@ -77,6 +77,8 @@ export class Supervisor {
|
|
|
77
77
|
* agent mid-thought.
|
|
78
78
|
*/
|
|
79
79
|
static EMPTY_TURN_SETTLE_MS = 25_000;
|
|
80
|
+
/** The window actually used — the constant, or a test's own shorter one. */
|
|
81
|
+
emptyTurnSettleMs;
|
|
80
82
|
/** A finished session's journal is kept this long for a late reconnect. */
|
|
81
83
|
static JOURNAL_TTL_MS = 72 * 3_600_000;
|
|
82
84
|
/** Backstop: events the API will never accept must not pile up forever. */
|
|
@@ -114,6 +116,7 @@ export class Supervisor {
|
|
|
114
116
|
this.ws = ws;
|
|
115
117
|
this.opts = opts;
|
|
116
118
|
this.journals = opts.journals ?? new JournalStore();
|
|
119
|
+
this.emptyTurnSettleMs = opts.emptyTurnSettleMs ?? Supervisor.EMPTY_TURN_SETTLE_MS;
|
|
117
120
|
this.verify = new VerifyRunner({
|
|
118
121
|
enabled: opts.verifyEnabled !== false,
|
|
119
122
|
onReport: (report) => {
|
|
@@ -376,7 +379,7 @@ export class Supervisor {
|
|
|
376
379
|
if (orphaned) {
|
|
377
380
|
this.orphanMessages.delete(descriptor.id);
|
|
378
381
|
for (const message of orphaned) {
|
|
379
|
-
this.acceptUserMessage(descriptor.id, message.text, message.attachments);
|
|
382
|
+
this.acceptUserMessage(descriptor.id, message.text, message.attachments, message.messageId);
|
|
380
383
|
}
|
|
381
384
|
}
|
|
382
385
|
try {
|
|
@@ -1063,6 +1066,20 @@ export class Supervisor {
|
|
|
1063
1066
|
// Idle process ended (parked or died between turns) — stay resumable.
|
|
1064
1067
|
running.session = null;
|
|
1065
1068
|
running.parkRequested = false;
|
|
1069
|
+
/**
|
|
1070
|
+
* The subagents died with it (QA-2026-08-16 M-5).
|
|
1071
|
+
*
|
|
1072
|
+
* Here rather than in `park()`, because parking only ASKS the process to
|
|
1073
|
+
* stop and this is where it actually went. The adapter stops publishing
|
|
1074
|
+
* the moment it is told to stop, so without this the last frame's count
|
|
1075
|
+
* stands for ever: the strip says «Working in background» over a parked
|
|
1076
|
+
* session and — far worse — the API goes on suppressing that session's
|
|
1077
|
+
* «your turn» notification for the rest of its life, because the number
|
|
1078
|
+
* it stored never reaches zero again.
|
|
1079
|
+
*
|
|
1080
|
+
* Before the `reportStatus` just below, so the zero rides out on it.
|
|
1081
|
+
*/
|
|
1082
|
+
running.backgroundTasks = 0;
|
|
1066
1083
|
running.costBaseUsd = running.costUsd; // next process starts from here
|
|
1067
1084
|
// Persist the clock: a parked session can sit for hours and the runner
|
|
1068
1085
|
// may be restarted before it ever runs again.
|
|
@@ -1301,6 +1318,11 @@ export class Supervisor {
|
|
|
1301
1318
|
if (event.ok && event.produced !== true && this.holdEmptyTurn(running, descriptor, event)) {
|
|
1302
1319
|
return;
|
|
1303
1320
|
}
|
|
1321
|
+
// Not held — so any phantom still waiting from an earlier turn is stale and
|
|
1322
|
+
// must not outlive this ending. Cleared HERE and not before the decision
|
|
1323
|
+
// above, or `holdEmptyTurn`'s «two in a row is a quiet agent, not two
|
|
1324
|
+
// phantoms» rule could never see the first one (QA-2026-08-16 M-6).
|
|
1325
|
+
this.clearEmptyTurn(running);
|
|
1304
1326
|
this.settleTurnStatus(running, descriptor, event);
|
|
1305
1327
|
}
|
|
1306
1328
|
/**
|
|
@@ -1364,7 +1386,7 @@ export class Supervisor {
|
|
|
1364
1386
|
return false;
|
|
1365
1387
|
log.info('supervisor: holding a turn that produced nothing', {
|
|
1366
1388
|
sessionId: descriptor.id,
|
|
1367
|
-
settleMs:
|
|
1389
|
+
settleMs: this.emptyTurnSettleMs,
|
|
1368
1390
|
});
|
|
1369
1391
|
const timer = setTimeout(() => {
|
|
1370
1392
|
running.emptyTurnTimer = undefined;
|
|
@@ -1374,7 +1396,7 @@ export class Supervisor {
|
|
|
1374
1396
|
return;
|
|
1375
1397
|
log.info('supervisor: the empty turn was real after all', { sessionId: descriptor.id });
|
|
1376
1398
|
this.settleTurnStatus(running, descriptor, event);
|
|
1377
|
-
},
|
|
1399
|
+
}, this.emptyTurnSettleMs);
|
|
1378
1400
|
timer.unref();
|
|
1379
1401
|
running.emptyTurnTimer = timer;
|
|
1380
1402
|
return true;
|
|
@@ -1392,6 +1414,35 @@ export class Supervisor {
|
|
|
1392
1414
|
clearTimeout(running.emptyTurnTimer);
|
|
1393
1415
|
running.emptyTurnTimer = undefined;
|
|
1394
1416
|
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Record how many subagents are alive, and say so when it matters (#236).
|
|
1419
|
+
*
|
|
1420
|
+
* One place, because there are now two callers with opposite news — a frame
|
|
1421
|
+
* from the adapter, and the process going away — and «is it the human's turn»
|
|
1422
|
+
* must be answered the same way by both.
|
|
1423
|
+
*
|
|
1424
|
+
* The report on reaching zero is the whole point: the agent is already
|
|
1425
|
+
* sitting in a resting status, so nothing else will ever tell the API that
|
|
1426
|
+
* the session finally became the person's. `REVIEW` counts as well as
|
|
1427
|
+
* `WAITING_INPUT` — a TICKET session waiting on its own subagent is in the
|
|
1428
|
+
* same position, and leaving it out was how one of the two statuses kept its
|
|
1429
|
+
* notification suppressed for good (QA-2026-08-16 M-5).
|
|
1430
|
+
*/
|
|
1431
|
+
setBackgroundTasks(running, live) {
|
|
1432
|
+
if (live === running.backgroundTasks)
|
|
1433
|
+
return;
|
|
1434
|
+
const finished = running.backgroundTasks > 0 && live === 0;
|
|
1435
|
+
running.backgroundTasks = live;
|
|
1436
|
+
const resting = running.lastReported === 'WAITING_INPUT' || running.lastReported === 'REVIEW'
|
|
1437
|
+
? running.lastReported
|
|
1438
|
+
: null;
|
|
1439
|
+
if (finished && resting) {
|
|
1440
|
+
this.reportStatus(running.descriptor.id, resting, {
|
|
1441
|
+
costUsd: running.costUsd,
|
|
1442
|
+
activeMs: Supervisor.spentMs(running),
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1395
1446
|
isParkable(running) {
|
|
1396
1447
|
return ((running.lastReported === 'REVIEW' || running.lastReported === 'WAITING_INPUT') &&
|
|
1397
1448
|
running.openQuestions.size === 0 &&
|
|
@@ -1522,11 +1573,6 @@ export class Supervisor {
|
|
|
1522
1573
|
// when it finally succeeds or finally gives up.
|
|
1523
1574
|
if (!event.ok && this.armApiRetry(running, descriptor, event))
|
|
1524
1575
|
return;
|
|
1525
|
-
// A previous turn's phantom, if any, is over: this turn has ended for
|
|
1526
|
-
// real, and a timer still holding its predecessor would report a status
|
|
1527
|
-
// for a turn that is two turns old (#300 — the hold itself lives in
|
|
1528
|
-
// `completeTurn`, where the decision belongs).
|
|
1529
|
-
this.clearEmptyTurn(running);
|
|
1530
1576
|
this.completeTurn(running, descriptor, event);
|
|
1531
1577
|
return;
|
|
1532
1578
|
}
|
|
@@ -1757,21 +1803,7 @@ export class Supervisor {
|
|
|
1757
1803
|
* turn (`endTaskTurn` keeps the live set).
|
|
1758
1804
|
*/
|
|
1759
1805
|
const live = event.tasks.filter((task) => task.status === 'running').length;
|
|
1760
|
-
|
|
1761
|
-
const wasWaiting = running.backgroundTasks > 0 && live === 0;
|
|
1762
|
-
running.backgroundTasks = live;
|
|
1763
|
-
// The moment the last subagent finishes is the moment the session
|
|
1764
|
-
// really does become the human's — and the agent is sitting in
|
|
1765
|
-
// WAITING_INPUT, so nothing else will ever say so. `reportStatus`
|
|
1766
|
-
// carries the new number and the API turns it into the notification
|
|
1767
|
-
// it withheld earlier.
|
|
1768
|
-
if (wasWaiting && running.lastReported === 'WAITING_INPUT') {
|
|
1769
|
-
this.reportStatus(running.descriptor.id, 'WAITING_INPUT', {
|
|
1770
|
-
costUsd: running.costUsd,
|
|
1771
|
-
activeMs: Supervisor.spentMs(running),
|
|
1772
|
-
});
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1806
|
+
this.setBackgroundTasks(running, live);
|
|
1775
1807
|
return;
|
|
1776
1808
|
}
|
|
1777
1809
|
case 'notice': {
|
|
@@ -1884,7 +1916,20 @@ export class Supervisor {
|
|
|
1884
1916
|
// instead of dropping it silently.
|
|
1885
1917
|
log.warn('supervisor: message for unknown session — requesting descriptor', { sessionId });
|
|
1886
1918
|
const queued = this.orphanMessages.get(sessionId) ?? [];
|
|
1887
|
-
|
|
1919
|
+
/**
|
|
1920
|
+
* The name travels with the words (QA-2026-08-16 M-2).
|
|
1921
|
+
*
|
|
1922
|
+
* Without it the replay below took the message a SECOND time under no
|
|
1923
|
+
* name, and the API's redelivery of the row it queued for the same
|
|
1924
|
+
* `unknown_session` then looked brand new to `deliveredMessageIds` — the
|
|
1925
|
+
* agent got one instruction twice, in the one scenario that reliably
|
|
1926
|
+
* produces both copies.
|
|
1927
|
+
*/
|
|
1928
|
+
queued.push({
|
|
1929
|
+
text,
|
|
1930
|
+
...(attachments?.length ? { attachments } : {}),
|
|
1931
|
+
...(messageId ? { messageId } : {}),
|
|
1932
|
+
});
|
|
1888
1933
|
this.orphanMessages.set(sessionId, queued.slice(-Supervisor.ORPHAN_MESSAGE_CAP));
|
|
1889
1934
|
this.ws.send({ type: 'session_unknown', sessionId });
|
|
1890
1935
|
return 'unknown_session';
|
|
@@ -2202,6 +2247,11 @@ export class Supervisor {
|
|
|
2202
2247
|
this.sendEvent(running, 'message_delivered', { targetSeqs: delivered });
|
|
2203
2248
|
}
|
|
2204
2249
|
};
|
|
2250
|
+
// M-4 (QA-2026-08-16): a new turn is starting, so a phantom held from the
|
|
2251
|
+
// previous one must not fire 25 seconds from now and report WAITING_INPUT
|
|
2252
|
+
// over an agent that is working — the very lie #300 is about, and this time
|
|
2253
|
+
// with an irreversible notification behind it.
|
|
2254
|
+
this.clearEmptyTurn(running);
|
|
2205
2255
|
if (running.session && !running.parkRequested) {
|
|
2206
2256
|
// #252: the words this turn is actually running. `lastPrompt` deliberately
|
|
2207
2257
|
// stays put — three older latches relaunch a PROCESS with it, and handing
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const RUNNER_VERSION = "0.45.
|
|
1
|
+
export declare const RUNNER_VERSION = "0.45.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED