@bridge4dev/runner 0.55.0 → 0.56.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/adapters/claude-usage.d.ts +122 -1
- package/dist/adapters/claude-usage.js +310 -7
- package/dist/adapters/claude.js +68 -28
- package/dist/auth-relay.js +19 -0
- package/dist/checkpoints.d.ts +12 -1
- package/dist/checkpoints.js +272 -42
- package/dist/index.js +5 -5
- package/dist/session-cage.d.ts +6 -0
- package/dist/session-cage.js +28 -13
- package/dist/supervisor.d.ts +25 -10
- package/dist/supervisor.js +68 -37
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/supervisor.js
CHANGED
|
@@ -26,7 +26,7 @@ import { agentByDbValue } from './agent-registry.js';
|
|
|
26
26
|
import { invalidateAgentVersions, measureAgentVersions, } from './agent-versions.js';
|
|
27
27
|
import { rememberWorkspacePath } from './environment.js';
|
|
28
28
|
import { hostLoadChangedEnough, hostLoadHeartbeatDue, readHostLoad, HOST_LOAD_HEARTBEAT_MS, HOST_LOAD_SAMPLE_INTERVAL_MS, } from './host-load.js';
|
|
29
|
-
import { markScopeOomKillsSeen, readScopeMemoryStatus, sessionScopeUnitOf, } from './session-cage.js';
|
|
29
|
+
import { markScopeOomKillsSeen, readScopeMemoryStatus, sessionScopeUnitOf, stoppedProcesses, } from './session-cage.js';
|
|
30
30
|
import { composeMessageWithAttachments, saveAttachments, } from './attachments.js';
|
|
31
31
|
import { applyRewind, createCheckpoint, dropCheckpoints, listCheckpoints, MAX_BUSY_SESSIONS, previewRewind, pruneCheckpoints, } from './checkpoints.js';
|
|
32
32
|
import { DeliverMessageArgsSchema, QuestionAnswerArgsSchema } from './protocol.js';
|
|
@@ -144,6 +144,8 @@ export class Supervisor {
|
|
|
144
144
|
* which of the two is running.
|
|
145
145
|
*/
|
|
146
146
|
installInFlight = null;
|
|
147
|
+
/** A restore-point collection is running; a second reconnect must not start another (#388). */
|
|
148
|
+
checkpointGcInFlight = false;
|
|
147
149
|
/** Session 14: one project-recipe run per machine, and its verdict queue. */
|
|
148
150
|
verify;
|
|
149
151
|
verifyReports = new VerifyReportQueue();
|
|
@@ -715,18 +717,15 @@ export class Supervisor {
|
|
|
715
717
|
// where it was came from the slice over ALL sessions, and telling this
|
|
716
718
|
// session it went over its ceiling would be false (measured on this
|
|
717
719
|
// host: the killed neighbour read `max 0, oom 0, oom_kill 1`).
|
|
718
|
-
const
|
|
719
|
-
const
|
|
720
|
-
? 'a process'
|
|
721
|
-
: `${status.oomKills - base.oomKills} processes`;
|
|
720
|
+
const ownCeiling = status.ownLimitOom > base.ownOom;
|
|
721
|
+
const stopped = stoppedProcesses(status.oomKills - base.oomKills);
|
|
722
722
|
this.sendEvent(running, 'notice', {
|
|
723
723
|
level: 'warn',
|
|
724
|
-
text:
|
|
725
|
-
? `This session
|
|
726
|
-
'
|
|
727
|
-
: `The machine ran out of memory for agent sessions
|
|
728
|
-
|
|
729
|
-
'It is still running; the command that was in flight most likely died with it.',
|
|
724
|
+
text: ownCeiling
|
|
725
|
+
? `This session reached its${status.maxBytes === null ? '' : ` ${mb(status.maxBytes)}`} memory ceiling and ${stopped} — ` +
|
|
726
|
+
'check the output of the command that was running.'
|
|
727
|
+
: `The machine ran out of memory for agent sessions and ${stoppedProcesses(status.oomKills - base.oomKills, 'this one')} — ` +
|
|
728
|
+
'run fewer sessions at once, or give the machine more memory.',
|
|
730
729
|
});
|
|
731
730
|
// So a death minutes later is not blamed on a kill the feed already carries.
|
|
732
731
|
markScopeOomKillsSeen(running.descriptor.id, status.oomKills);
|
|
@@ -744,6 +743,10 @@ export class Supervisor {
|
|
|
744
743
|
*/
|
|
745
744
|
const throttled = status.highEvents > base.highEvents ||
|
|
746
745
|
(status.highBytes !== null && status.currentBytes > status.highBytes);
|
|
746
|
+
// `memory.high` reads `max` on a machine where the share never applied —
|
|
747
|
+
// then there is no number to name, and «its 0 MB share» is worse than no
|
|
748
|
+
// number at all.
|
|
749
|
+
const share = status.highBytes === null ? '' : ` ${mb(status.highBytes)}`;
|
|
747
750
|
let { brakedSince, warnedLong, calmTicks } = base;
|
|
748
751
|
if (throttled) {
|
|
749
752
|
calmTicks = 0;
|
|
@@ -752,18 +755,15 @@ export class Supervisor {
|
|
|
752
755
|
warnedLong = false;
|
|
753
756
|
this.sendEvent(running, 'notice', {
|
|
754
757
|
level: 'warn',
|
|
755
|
-
text: `This session
|
|
756
|
-
'Heavy commands will finish slower until memory is freed.',
|
|
758
|
+
text: `This session has hit its${share} memory share — commands run slower until it uses less memory.`,
|
|
757
759
|
});
|
|
758
760
|
}
|
|
759
761
|
else if (!warnedLong && now - brakedSince >= longMs) {
|
|
760
762
|
warnedLong = true;
|
|
761
763
|
this.sendEvent(running, 'notice', {
|
|
762
764
|
level: 'warn',
|
|
763
|
-
text: `
|
|
764
|
-
|
|
765
|
-
? ''
|
|
766
|
-
: ` The hard ceiling, where a process would be killed, is ${mb(status.maxBytes)}.`}`,
|
|
765
|
+
text: `This session has been over its${share} memory share for ${Math.round(longMs / 60_000)} min and is now using ${mb(status.currentBytes)} — ` +
|
|
766
|
+
'if a command is stuck rather than slow, stop the turn or pause the session.',
|
|
767
767
|
});
|
|
768
768
|
}
|
|
769
769
|
}
|
|
@@ -776,7 +776,7 @@ export class Supervisor {
|
|
|
776
776
|
warnedLong = false;
|
|
777
777
|
this.sendEvent(running, 'notice', {
|
|
778
778
|
level: 'info',
|
|
779
|
-
text: `
|
|
779
|
+
text: `This session is back under its${share} memory share — it runs at full speed again.`,
|
|
780
780
|
});
|
|
781
781
|
}
|
|
782
782
|
}
|
|
@@ -3403,9 +3403,20 @@ export class Supervisor {
|
|
|
3403
3403
|
* a second session opened. The neighbours are recorded on the point instead —
|
|
3404
3404
|
* the conversation can always be rewound to it, the files cannot.
|
|
3405
3405
|
*
|
|
3406
|
-
*
|
|
3407
|
-
*
|
|
3408
|
-
*
|
|
3406
|
+
* A refusal is audible when it is a refusal — when the person can do
|
|
3407
|
+
* something about it, or when a way back they might reach for is not there.
|
|
3408
|
+
* A restore point that was never taken is invisible until the day somebody
|
|
3409
|
+
* reaches for it, and «the button is not there» is not a sentence anybody can
|
|
3410
|
+
* act on.
|
|
3411
|
+
*
|
|
3412
|
+
* «This session was still answering» is the exception, and it is the only one
|
|
3413
|
+
* (#384). It is not a fault and not a state to act on: it is what a follow-up
|
|
3414
|
+
* note to a working agent looks like from in here, thirty times in a day on
|
|
3415
|
+
* one machine, and it costs almost nothing — the point in front of the turn
|
|
3416
|
+
* already stands, and rewinding to it takes back the files AND the
|
|
3417
|
+
* conversation, including the note. So it goes to the runner's own log, where
|
|
3418
|
+
* support can answer «why is there no point for that step», and not into the
|
|
3419
|
+
* feed, where it read as breakage.
|
|
3409
3420
|
*/
|
|
3410
3421
|
async captureCheckpoint(running, kind, messageSeq) {
|
|
3411
3422
|
const worktreePath = running.worktreePath;
|
|
@@ -3423,7 +3434,13 @@ export class Supervisor {
|
|
|
3423
3434
|
return;
|
|
3424
3435
|
}
|
|
3425
3436
|
if (kind === 'TURN' && this.isSessionMidTurn(running)) {
|
|
3426
|
-
|
|
3437
|
+
// Silent in the feed, on purpose (#384) — see the note above. The `return`
|
|
3438
|
+
// is not cosmetic and does not go: reading a tree the agent is writing
|
|
3439
|
+
// produces a restore point that restores half a file (gotcha 438).
|
|
3440
|
+
log.info('supervisor: no restore point — the session was mid-turn', {
|
|
3441
|
+
sessionId: running.descriptor.id,
|
|
3442
|
+
...(messageSeq === undefined ? {} : { messageSeq }),
|
|
3443
|
+
});
|
|
3427
3444
|
return;
|
|
3428
3445
|
}
|
|
3429
3446
|
if (await this.isRepoLocked(worktreePath)) {
|
|
@@ -3489,16 +3506,18 @@ export class Supervisor {
|
|
|
3489
3506
|
/**
|
|
3490
3507
|
* Say something once per BUSY PERIOD, not once per message (#310).
|
|
3491
3508
|
*
|
|
3492
|
-
* A
|
|
3493
|
-
*
|
|
3494
|
-
* message seq would have counted each of those as its own
|
|
3495
|
-
* same sentence three times — the noise the frequency
|
|
3496
|
-
* prevent. The set is cleared when the session next comes to
|
|
3497
|
-
* (`reportStatus`), which is exactly when the reason stops being true.
|
|
3509
|
+
* A repository held by another git command stays held for as long as that
|
|
3510
|
+
* command runs, and three follow-up notes can arrive inside one answer.
|
|
3511
|
+
* Keying this on the message seq would have counted each of those as its own
|
|
3512
|
+
* turn and said the same sentence three times — the noise the frequency
|
|
3513
|
+
* policy exists to prevent. The set is cleared when the session next comes to
|
|
3514
|
+
* rest (`reportStatus`), which is exactly when the reason stops being true.
|
|
3498
3515
|
*
|
|
3499
3516
|
* A SET of keys, not the last one said: two different reasons can both come
|
|
3500
3517
|
* up inside one period, and remembering only the most recent would let them
|
|
3501
|
-
* take turns re-announcing each other.
|
|
3518
|
+
* take turns re-announcing each other. One key uses this today — «another git
|
|
3519
|
+
* command holds this repository» — and the set stays a set for that reason,
|
|
3520
|
+
* not out of habit: the mid-turn key left when it stopped being said (#384).
|
|
3502
3521
|
*/
|
|
3503
3522
|
noticeOncePerTurn(running, key, text) {
|
|
3504
3523
|
running.noticesThisTurn ??= new Set();
|
|
@@ -4277,14 +4296,26 @@ export class Supervisor {
|
|
|
4277
4296
|
// switched off leaves restore points — a full copy of a working tree —
|
|
4278
4297
|
// with no row anywhere pointing at them, and nothing else on this machine
|
|
4279
4298
|
// would ever collect them.
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4299
|
+
//
|
|
4300
|
+
// One at a time (#388): reconnects come in runs, and since the collection
|
|
4301
|
+
// holds the store while it collects, a second one started on top of the
|
|
4302
|
+
// first would only queue — in front of the restore points of whoever is
|
|
4303
|
+
// working. The next reconnect collects whatever this pass leaves.
|
|
4304
|
+
if (!this.checkpointGcInFlight) {
|
|
4305
|
+
this.checkpointGcInFlight = true;
|
|
4306
|
+
void pruneCheckpoints({ liveSessionIds: known })
|
|
4307
|
+
.then((result) => {
|
|
4308
|
+
if (result.droppedRefs > 0) {
|
|
4309
|
+
log.info('supervisor: collected orphaned restore points', {
|
|
4310
|
+
sessions: result.droppedSessions.length,
|
|
4311
|
+
refs: result.droppedRefs,
|
|
4312
|
+
});
|
|
4313
|
+
}
|
|
4314
|
+
}, (error) => log.warn('supervisor: restore-point GC failed', { error: String(error) }))
|
|
4315
|
+
.finally(() => {
|
|
4316
|
+
this.checkpointGcInFlight = false;
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
4288
4319
|
for (const [sessionId, running] of [...this.sessions]) {
|
|
4289
4320
|
if (known.has(sessionId))
|
|
4290
4321
|
continue;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const RUNNER_VERSION = "0.56.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED