teamshare-bridge 0.22.8 → 0.22.10
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/bridge/auto-upgrade.d.ts +92 -0
- package/dist/bridge/auto-upgrade.js +159 -6
- package/dist/bridge/auto-upgrade.js.map +1 -1
- package/dist/bridge/daemon-instance.d.ts +17 -0
- package/dist/bridge/daemon-instance.js +58 -0
- package/dist/bridge/daemon-instance.js.map +1 -1
- package/dist/bridge/daemon-support.js +25 -5
- package/dist/bridge/daemon-support.js.map +1 -1
- package/dist/bridge/daemon.js +254 -83
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/doctor.js +42 -5
- package/dist/bridge/doctor.js.map +1 -1
- package/dist/bridge/index.js +42 -3
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/local-server.d.ts +2 -0
- package/dist/bridge/local-server.js +158 -19
- package/dist/bridge/local-server.js.map +1 -1
- package/dist/bridge/protocol.js +3 -12
- package/dist/bridge/protocol.js.map +1 -1
- package/dist/bridge/session-relay.d.ts +8 -0
- package/dist/bridge/session-relay.js +56 -5
- package/dist/bridge/session-relay.js.map +1 -1
- package/dist/bridge/upgrade-marker.d.ts +24 -2
- package/dist/bridge/upgrade-marker.js +30 -2
- package/dist/bridge/upgrade-marker.js.map +1 -1
- package/dist/bridge/upgrade-preflight.d.ts +49 -0
- package/dist/bridge/upgrade-preflight.js +164 -0
- package/dist/bridge/upgrade-preflight.js.map +1 -0
- package/dist/cli/commands/board.js +10 -1
- package/dist/cli/commands/board.js.map +1 -1
- package/dist/cli/commands/browse.js +10 -6
- package/dist/cli/commands/browse.js.map +1 -1
- package/dist/cli/commands/browser.js +6 -7
- package/dist/cli/commands/browser.js.map +1 -1
- package/dist/cli/commands/build.js +28 -5
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/chat.js +10 -1
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/cleanup.js +1 -11
- package/dist/cli/commands/cleanup.js.map +1 -1
- package/dist/cli/commands/connect.js +2 -8
- package/dist/cli/commands/connect.js.map +1 -1
- package/dist/cli/commands/disconnect.js +4 -10
- package/dist/cli/commands/disconnect.js.map +1 -1
- package/dist/cli/commands/doc.js +10 -1
- package/dist/cli/commands/doc.js.map +1 -1
- package/dist/cli/commands/planner.js +10 -1
- package/dist/cli/commands/planner.js.map +1 -1
- package/dist/cli/commands/research.js +21 -4
- package/dist/cli/commands/research.js.map +1 -1
- package/dist/cli/commands/run.js +10 -1
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/lib/brief.js +18 -1
- package/dist/lib/brief.js.map +1 -1
- package/dist/lib/browser/browse-stealth.js +37 -6
- package/dist/lib/browser/browse-stealth.js.map +1 -1
- package/dist/lib/browser/profile-guard.d.ts +23 -0
- package/dist/lib/browser/profile-guard.js +72 -0
- package/dist/lib/browser/profile-guard.js.map +1 -0
- package/dist/lib/chat/daemon-reporter.js +8 -1
- package/dist/lib/chat/daemon-reporter.js.map +1 -1
- package/dist/lib/git/git-discovery.d.ts +23 -4
- package/dist/lib/git/git-discovery.js +67 -19
- package/dist/lib/git/git-discovery.js.map +1 -1
- package/dist/lib/git/git-finalize.js +53 -1
- package/dist/lib/git/git-finalize.js.map +1 -1
- package/dist/lib/git/index-recovery.js +91 -1
- package/dist/lib/git/index-recovery.js.map +1 -1
- package/dist/lib/harness/agy-pty.d.ts +1 -1
- package/dist/lib/harness/agy-pty.js +20 -4
- package/dist/lib/harness/agy-pty.js.map +1 -1
- package/dist/lib/harness/spawn.js +98 -24
- package/dist/lib/harness/spawn.js.map +1 -1
- package/dist/lib/lock.d.ts +6 -0
- package/dist/lib/lock.js +108 -15
- package/dist/lib/lock.js.map +1 -1
- package/dist/lib/orchestrator/handlers/browser.js +9 -8
- package/dist/lib/orchestrator/handlers/browser.js.map +1 -1
- package/dist/lib/orchestrator/handlers/git.js +88 -6
- package/dist/lib/orchestrator/handlers/git.js.map +1 -1
- package/dist/lib/orchestrator/worktree.d.ts +10 -0
- package/dist/lib/orchestrator/worktree.js +243 -101
- package/dist/lib/orchestrator/worktree.js.map +1 -1
- package/dist/lib/tail-file.d.ts +4 -0
- package/dist/lib/tail-file.js +73 -0
- package/dist/lib/tail-file.js.map +1 -0
- package/dist/lib/tools/browser-tool.js +13 -2
- package/dist/lib/tools/browser-tool.js.map +1 -1
- package/dist/lib/wake-claim.d.ts +4 -3
- package/dist/lib/wake-claim.js +81 -20
- package/dist/lib/wake-claim.js.map +1 -1
- package/dist/lib/workspace/bus.d.ts +32 -0
- package/dist/lib/workspace/bus.js +48 -1
- package/dist/lib/workspace/bus.js.map +1 -1
- package/dist/lib/workspace/mirror-lease.js +30 -1
- package/dist/lib/workspace/mirror-lease.js.map +1 -1
- package/dist/lib/workspace/mirror.js +22 -1
- package/dist/lib/workspace/mirror.js.map +1 -1
- package/dist/lib/workspace/presence.js +38 -14
- package/dist/lib/workspace/presence.js.map +1 -1
- package/dist/lib/workspace/repo-lock.d.ts +21 -1
- package/dist/lib/workspace/repo-lock.js +146 -21
- package/dist/lib/workspace/repo-lock.js.map +1 -1
- package/dist/lib/workspace/stop-save.js +10 -0
- package/dist/lib/workspace/stop-save.js.map +1 -1
- package/package.json +1 -1
- package/tools/workspace-arbitration-check.mjs +39 -33
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-support.js","sourceRoot":"","sources":["../../src/bridge/daemon-support.ts"],"names":[],"mappings":";;;AAwCA,wDAIC;AAGD,0DAKC;AAED,wDAIC;AAGD,wDAEC;AAgBD,
|
|
1
|
+
{"version":3,"file":"daemon-support.js","sourceRoot":"","sources":["../../src/bridge/daemon-support.ts"],"names":[],"mappings":"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"}
|
package/dist/bridge/daemon.js
CHANGED
|
@@ -15,12 +15,26 @@ exports.runDaemon = runDaemon;
|
|
|
15
15
|
*/
|
|
16
16
|
const node_child_process_1 = require("node:child_process");
|
|
17
17
|
const node_fs_1 = require("node:fs");
|
|
18
|
+
const tail_file_1 = require("../lib/tail-file");
|
|
18
19
|
const process_tree_1 = require("../lib/process-tree");
|
|
19
20
|
const git_runner_1 = require("../lib/git/git-runner");
|
|
20
21
|
const mirror_1 = require("../lib/workspace/mirror");
|
|
21
22
|
const stop_save_1 = require("../lib/workspace/stop-save");
|
|
22
23
|
const bridge_notice_1 = require("../lib/bridge-notice");
|
|
23
24
|
const wake_outbox_1 = require("./wake-outbox");
|
|
25
|
+
/**
|
|
26
|
+
* Outbox depth that cannot throw. Read for the upgrade diagnostic, so an
|
|
27
|
+
* unreadable queue must degrade to "0" rather than take the daemon's upgrade
|
|
28
|
+
* scheduler down with it.
|
|
29
|
+
*/
|
|
30
|
+
const safeOutboxDepth = (agentId) => {
|
|
31
|
+
try {
|
|
32
|
+
return (0, wake_outbox_1.outboxDepth)(agentId);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
24
38
|
const wake_ack_ledger_1 = require("./wake-ack-ledger");
|
|
25
39
|
/**
|
|
26
40
|
* Wake types a "Stop work" DISCARDS from the outbox (2026-10).
|
|
@@ -315,6 +329,14 @@ async function runDaemon(portOverride) {
|
|
|
315
329
|
}
|
|
316
330
|
/** How long to ask the hub to wait before re-offering a deferred wake. */
|
|
317
331
|
const DEFER_RETRY_MS = 60_000;
|
|
332
|
+
/**
|
|
333
|
+
* Local attempts a single outbox-lane incarnation gets before its copy is
|
|
334
|
+
* dropped. Matches the hub `pending_wakes` bound so the two queues agree on
|
|
335
|
+
* what "giving up" means. At `LOCK_POLL_MS` that is ~2 minutes of local
|
|
336
|
+
* retries; after that the hub's `retryAfterMs` re-offers the row and the lane
|
|
337
|
+
* starts over with a fresh budget.
|
|
338
|
+
*/
|
|
339
|
+
const OUTBOX_MAX_ATTEMPTS = 12;
|
|
318
340
|
/**
|
|
319
341
|
* Report every lane this agent still owes a terminal report for, and clear the
|
|
320
342
|
* local debt (2026-10).
|
|
@@ -478,8 +500,15 @@ async function runDaemon(portOverride) {
|
|
|
478
500
|
// foreground (only detached `start` used to leave a pid file behind).
|
|
479
501
|
(0, daemon_support_1.writeDaemonPidFile)();
|
|
480
502
|
// IMP-950: publish the probe port so shims + CLI tools can find the
|
|
481
|
-
// arbiter endpoints without scanning candidate ports.
|
|
482
|
-
|
|
503
|
+
// arbiter endpoints without scanning candidate ports. The probe token rides
|
|
504
|
+
// along: every non-public endpoint now requires it (2026-10), and callers
|
|
505
|
+
// already read this file for the port.
|
|
506
|
+
(0, bus_1.writeRuntimeFile)({
|
|
507
|
+
port: boundPort,
|
|
508
|
+
pid: process.pid,
|
|
509
|
+
startedAt: new Date().toISOString(),
|
|
510
|
+
...((0, local_server_1.getProbeToken)() ? { probeToken: (0, local_server_1.getProbeToken)() } : {}),
|
|
511
|
+
});
|
|
483
512
|
if (config.agents.length === 0) {
|
|
484
513
|
(0, daemon_support_1.log)('no agents configured - run: teamshare-agent connect --agent <id> --key ts_...');
|
|
485
514
|
}
|
|
@@ -498,64 +527,87 @@ async function runDaemon(portOverride) {
|
|
|
498
527
|
const startedAt = Date.now();
|
|
499
528
|
const timer = setTimeout(() => {
|
|
500
529
|
spawnWatchTimers.delete(agentId);
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
530
|
+
// ASYNC FORENSICS (2026-10).
|
|
531
|
+
//
|
|
532
|
+
// This body used to run synchronously on the DAEMON'S event loop:
|
|
533
|
+
// `readdirSync` over `~/.teamshare/sessions` (every session transcript
|
|
534
|
+
// ever written, plus `launch-*.vbs`), a `statSync` per entry, then
|
|
535
|
+
// `readFileSync` of the ENTIRE newest `spawn-*.log` and
|
|
536
|
+
// `content.split(/\r?\n/)` before taking the last 15 lines. That log is
|
|
537
|
+
// an unbounded append of the child's stdout+stderr, so on a machine that
|
|
538
|
+
// has run a long build it is hundreds of MB - a multi-second freeze in
|
|
539
|
+
// which the 2-minute heartbeat, the 10s outbox drain and the
|
|
540
|
+
// bridge-state push all miss their cadence.
|
|
541
|
+
void (async () => {
|
|
542
|
+
try {
|
|
543
|
+
if (sessionRelay.current(agentId))
|
|
544
|
+
return; // session came up
|
|
545
|
+
const lock = (0, lock_1.readLock)(agentId);
|
|
546
|
+
if (lock?.pid) {
|
|
547
|
+
try {
|
|
548
|
+
process.kill(lock.pid, 0);
|
|
549
|
+
return; // a live session process holds the lock - assume running
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
/* dead pid - fall through to forensics */
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// No session, no live lock: look for a capture file written by this
|
|
556
|
+
// spawn (hidden-launch creates it only if the chain actually ran).
|
|
557
|
+
let launched = false;
|
|
558
|
+
let captureInfo = 'no capture file - the hidden launcher never ran (Windows Script Host/wscript.exe may be disabled or blocked by antivirus)';
|
|
559
|
+
const dir = (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'sessions');
|
|
560
|
+
let names = [];
|
|
506
561
|
try {
|
|
507
|
-
|
|
508
|
-
return; // a live session process holds the lock - assume running
|
|
562
|
+
names = await node_fs_1.promises.readdir(dir);
|
|
509
563
|
}
|
|
510
564
|
catch {
|
|
511
|
-
|
|
565
|
+
names = []; // folder never created, or cleaned up
|
|
512
566
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
567
|
+
const stamped = await Promise.all(names
|
|
568
|
+
.filter((f) => f.startsWith('spawn-') && f.endsWith('.log'))
|
|
569
|
+
.map(async (f) => {
|
|
570
|
+
try {
|
|
571
|
+
const st = await node_fs_1.promises.stat((0, node_path_1.join)(dir, f));
|
|
572
|
+
return { f, mtime: st.mtimeMs };
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
}));
|
|
578
|
+
let newest = null;
|
|
579
|
+
for (const entry of stamped) {
|
|
580
|
+
if (!entry)
|
|
581
|
+
continue;
|
|
582
|
+
if (entry.mtime >= startedAt - 2_000 && (!newest || entry.mtime > newest.mtime)) {
|
|
583
|
+
newest = { file: entry.f, mtime: entry.mtime };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (newest) {
|
|
587
|
+
launched = true;
|
|
588
|
+
// Tail-read: bounded no matter how much the child printed.
|
|
589
|
+
const tail = await (0, tail_file_1.readTailLines)((0, node_path_1.join)(dir, newest.file), 15);
|
|
590
|
+
captureInfo = tail
|
|
591
|
+
? `capture ${newest.file} tail:\n${tail}`
|
|
592
|
+
: `capture ${newest.file} exists but is empty - the CLI exited before logging anything (native-module load failure?)`;
|
|
529
593
|
}
|
|
594
|
+
(0, daemon_support_1.log)(`agent ${agentId}: spawn watch - no session started ${Math.round((Date.now() - startedAt) / 1000)}s after ${wakeType} wake`);
|
|
595
|
+
(0, diagnostics_1.reportDiagnosticSafe)({
|
|
596
|
+
kind: 'spawn_failure',
|
|
597
|
+
severity: 'error',
|
|
598
|
+
agentId,
|
|
599
|
+
message: `Session never started after a ${wakeType} wake (${launched ? 'CLI exited before session:start' : 'hidden launcher never ran'})`,
|
|
600
|
+
errorCode: launched
|
|
601
|
+
? 'SESSION_PREMATURE_EXIT'
|
|
602
|
+
: 'SESSION_NEVER_LAUNCHED',
|
|
603
|
+
context: { wakeType, capture: captureInfo.slice(0, 4_000) },
|
|
604
|
+
includeDoctor: true,
|
|
605
|
+
});
|
|
530
606
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, newest.file), 'utf8');
|
|
534
|
-
const tail = content
|
|
535
|
-
.split(/\r?\n/)
|
|
536
|
-
.filter(Boolean)
|
|
537
|
-
.slice(-15)
|
|
538
|
-
.join('\n');
|
|
539
|
-
captureInfo = tail
|
|
540
|
-
? `capture ${newest.file} tail:\n${tail}`
|
|
541
|
-
: `capture ${newest.file} exists but is empty - the CLI exited before logging anything (native-module load failure?)`;
|
|
607
|
+
catch (err) {
|
|
608
|
+
(0, daemon_support_1.log)(`agent ${agentId}: spawn watch failed: ${(0, config_1.msg)(err)}`);
|
|
542
609
|
}
|
|
543
|
-
|
|
544
|
-
(0, diagnostics_1.reportDiagnosticSafe)({
|
|
545
|
-
kind: 'spawn_failure',
|
|
546
|
-
severity: 'error',
|
|
547
|
-
agentId,
|
|
548
|
-
message: `Session never started after a ${wakeType} wake (${launched ? 'CLI exited before session:start' : 'hidden launcher never ran'})`,
|
|
549
|
-
errorCode: launched
|
|
550
|
-
? 'SESSION_PREMATURE_EXIT'
|
|
551
|
-
: 'SESSION_NEVER_LAUNCHED',
|
|
552
|
-
context: { wakeType, capture: captureInfo.slice(0, 4_000) },
|
|
553
|
-
includeDoctor: true,
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
catch (err) {
|
|
557
|
-
(0, daemon_support_1.log)(`agent ${agentId}: spawn watch failed: ${(0, config_1.msg)(err)}`);
|
|
558
|
-
}
|
|
610
|
+
})();
|
|
559
611
|
}, 20_000);
|
|
560
612
|
timer.unref();
|
|
561
613
|
spawnWatchTimers.set(agentId, timer);
|
|
@@ -764,6 +816,8 @@ async function runDaemon(portOverride) {
|
|
|
764
816
|
const config = getConfig;
|
|
765
817
|
const agentTimers = [];
|
|
766
818
|
let timersCreated = false;
|
|
819
|
+
/** At most one pending staggered first-beat; see the `connect` handler. */
|
|
820
|
+
let firstBeatTimer = null;
|
|
767
821
|
/** Consecutive 401 "Invalid API key" heartbeats before we stop retrying. */
|
|
768
822
|
let authFailures = 0;
|
|
769
823
|
const isAuthError = (err) => err instanceof api_1.ApiError
|
|
@@ -913,10 +967,23 @@ async function runDaemon(portOverride) {
|
|
|
913
967
|
// is safe. Later beats ride the 2-min heartbeat + 5-min push cadence.
|
|
914
968
|
const seq = (0, daemon_support_1.nextAgentLoopSeq)();
|
|
915
969
|
const firstBeatMs = Math.round(daemon_support_1.HEARTBEAT_MS * (0.25 + 0.5 * ((seq % 6) / 6)));
|
|
916
|
-
|
|
970
|
+
// ONE pending first-beat at a time. This block runs on EVERY `connect`,
|
|
971
|
+
// and a flapping socket used to push another `setTimeout` each time: the
|
|
972
|
+
// array grew for the life of the loop AND every stale timer still fired
|
|
973
|
+
// its own heartbeat + pushState, so a reconnect storm turned into a burst
|
|
974
|
+
// of duplicate beats instead of one.
|
|
975
|
+
if (firstBeatTimer) {
|
|
976
|
+
clearTimeout(firstBeatTimer);
|
|
977
|
+
const staleIdx = agentTimers.indexOf(firstBeatTimer);
|
|
978
|
+
if (staleIdx >= 0)
|
|
979
|
+
agentTimers.splice(staleIdx, 1);
|
|
980
|
+
}
|
|
981
|
+
firstBeatTimer = setTimeout(() => {
|
|
982
|
+
firstBeatTimer = null;
|
|
917
983
|
heartbeat();
|
|
918
984
|
pushState();
|
|
919
|
-
}, firstBeatMs)
|
|
985
|
+
}, firstBeatMs);
|
|
986
|
+
agentTimers.push(firstBeatTimer);
|
|
920
987
|
// Only create persistent intervals on the first connect — reconnects
|
|
921
988
|
// reuse the existing timers (they survive socket disconnects).
|
|
922
989
|
if (!timersCreated) {
|
|
@@ -1157,7 +1224,34 @@ async function runDaemon(portOverride) {
|
|
|
1157
1224
|
orchestratorClient,
|
|
1158
1225
|
});
|
|
1159
1226
|
reportWakeOutcome(agent.agentId, socket, event, outcome);
|
|
1160
|
-
})().catch((err) =>
|
|
1227
|
+
})().catch((err) => {
|
|
1228
|
+
(0, daemon_support_1.log)(`agent ${agent.agentId}: wake failed: ${(0, config_1.msg)(err)}`);
|
|
1229
|
+
// The receipt (`wake.received` / noteAck) was already emitted before
|
|
1230
|
+
// `handleWake` ran, so without this the hub row sat `acknowledged`
|
|
1231
|
+
// forever with no `wake.settled` and no `wake.defer` - the human saw
|
|
1232
|
+
// "Wake received" and no work, with nothing in the run history to
|
|
1233
|
+
// explain it. A deferred report is the negative ack that lets the hub's
|
|
1234
|
+
// retry clock take over, and a diagnostic records WHY.
|
|
1235
|
+
try {
|
|
1236
|
+
(0, diagnostics_1.reportDiagnosticSafe)({
|
|
1237
|
+
kind: 'wake_failure',
|
|
1238
|
+
severity: 'error',
|
|
1239
|
+
agentId: agent.agentId,
|
|
1240
|
+
errorCode: 'WAKE_HANDLER_THREW',
|
|
1241
|
+
message: `${String(event.type)}: ${(0, config_1.msg)(err)}`,
|
|
1242
|
+
includeDoctor: true,
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
catch {
|
|
1246
|
+
/* reporting must never mask the wake failure */
|
|
1247
|
+
}
|
|
1248
|
+
try {
|
|
1249
|
+
reportWakeOutcome(agent.agentId, socket, event, 'deferred');
|
|
1250
|
+
}
|
|
1251
|
+
catch {
|
|
1252
|
+
/* socket already gone - the hub's reclaim sweep is the backstop */
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1161
1255
|
});
|
|
1162
1256
|
sockets.push(socket);
|
|
1163
1257
|
// 2026-09: restore this agent's queued wakes from disk instead of wiping
|
|
@@ -1359,6 +1453,9 @@ async function runDaemon(portOverride) {
|
|
|
1359
1453
|
const res = await fetch(`${apiBase}/agents/${agentId}/keys/rotate`, {
|
|
1360
1454
|
method: 'POST',
|
|
1361
1455
|
headers: { Authorization: `Bearer ${h.apiKey}` },
|
|
1456
|
+
// Node's fetch has NO default timeout, so one hung request stalled
|
|
1457
|
+
// this `for` loop and every agent queued behind it never rotated.
|
|
1458
|
+
signal: AbortSignal.timeout(30_000),
|
|
1362
1459
|
});
|
|
1363
1460
|
if (!res.ok)
|
|
1364
1461
|
continue;
|
|
@@ -1427,7 +1524,33 @@ async function runDaemon(portOverride) {
|
|
|
1427
1524
|
(0, daemon_support_1.log)(`agent ${agentId}: outbox drain paused after a lane took the lock`);
|
|
1428
1525
|
return;
|
|
1429
1526
|
}
|
|
1430
|
-
|
|
1527
|
+
let outcome;
|
|
1528
|
+
try {
|
|
1529
|
+
outcome = await (0, wake_handler_1.handleWake)(api_1.TeamshareApi.forAgent(apiBase, agent), agentId, agent.apiKey, lane.event, getConfig(), { wakeQueue, settings, recentSpawns, orchestratorClient });
|
|
1530
|
+
}
|
|
1531
|
+
catch (err) {
|
|
1532
|
+
// Per-lane, not per-tick: a throw anywhere in the loop used to be
|
|
1533
|
+
// caught only by the IIFE below, which logged a line and never
|
|
1534
|
+
// called `reportWakeOutcome` - so the row stayed `acknowledged` on
|
|
1535
|
+
// the hub AND every remaining lane for this agent was abandoned for
|
|
1536
|
+
// the tick. Convert it to the negative ack the rest of this body
|
|
1537
|
+
// already knows how to send.
|
|
1538
|
+
(0, daemon_support_1.log)(`agent ${agentId}: outbox lane ${lane.lane} threw: ${(0, config_1.msg)(err)}`);
|
|
1539
|
+
try {
|
|
1540
|
+
(0, diagnostics_1.reportDiagnosticSafe)({
|
|
1541
|
+
kind: 'wake_failure',
|
|
1542
|
+
severity: 'error',
|
|
1543
|
+
agentId,
|
|
1544
|
+
errorCode: 'WAKE_HANDLER_THREW',
|
|
1545
|
+
message: `${String(lane.event.type)}: ${(0, config_1.msg)(err)}`,
|
|
1546
|
+
includeDoctor: true,
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
catch {
|
|
1550
|
+
/* reporting must never mask the failure */
|
|
1551
|
+
}
|
|
1552
|
+
outcome = 'deferred';
|
|
1553
|
+
}
|
|
1431
1554
|
// Anything that is NOT finished with the lane stays queued. That covers
|
|
1432
1555
|
// 'queued', 'deferred' and 'blocked' - the three outcomes that mean
|
|
1433
1556
|
// "ask again later" - plus a thrown error handled below.
|
|
@@ -1465,48 +1588,87 @@ async function runDaemon(portOverride) {
|
|
|
1465
1588
|
// report for a deliberately failed 'blocked') is what stops the row
|
|
1466
1589
|
// sitting `acknowledged` on the hub while the outbox holds it.
|
|
1467
1590
|
reportWakeOutcome(agentId, h.socket, lane.event, outcome);
|
|
1591
|
+
// Bound the LOCAL retry (2026-10).
|
|
1592
|
+
//
|
|
1593
|
+
// `noteAttempt` has been exported since the outbox was written and
|
|
1594
|
+
// had ZERO call sites, so `OutboxLane.attempts` was persisted on
|
|
1595
|
+
// every enqueue and never incremented: a lane that could not make
|
|
1596
|
+
// progress was retried every LOCK_POLL_MS forever, each tick costing
|
|
1597
|
+
// an authenticated `GET /agents/:id/settings` on top of the full
|
|
1598
|
+
// `handleWake` pass. This is the same "wired but zero readers"
|
|
1599
|
+
// failure `AGENTS.md` records for Jira's `markPulled`/`touchSynced`.
|
|
1600
|
+
//
|
|
1601
|
+
// Dropping the local copy is safe BECAUSE the report above sent
|
|
1602
|
+
// `retryAfterMs`: the hub re-offers the row in 60 s, which enqueues
|
|
1603
|
+
// a FRESH lane with `attempts` back at 0. So the hub - not this file
|
|
1604
|
+
// - is the retry clock, and the cap only stops one incarnation of a
|
|
1605
|
+
// stuck lane from spinning.
|
|
1606
|
+
const kept = (0, wake_outbox_1.noteAttempt)(agentId, lane.lane, OUTBOX_MAX_ATTEMPTS);
|
|
1607
|
+
if (!kept) {
|
|
1608
|
+
(0, daemon_support_1.log)(`agent ${agentId}: outbox lane ${lane.lane} gave up after ` +
|
|
1609
|
+
`${OUTBOX_MAX_ATTEMPTS} attempts (${outcome}) - local copy dropped; ` +
|
|
1610
|
+
`the hub's PendingWake row (retryAfterMs) is the recovery path`);
|
|
1611
|
+
}
|
|
1468
1612
|
}
|
|
1469
1613
|
}
|
|
1470
1614
|
})().catch((err) => {
|
|
1615
|
+
// Only `freshSettings` (above the loop) can reject now - each lane's
|
|
1616
|
+
// `handleWake` is caught per-lane. Without a report here the settings
|
|
1617
|
+
// fetch failing left the row `acknowledged` indefinitely.
|
|
1471
1618
|
(0, daemon_support_1.log)(`agent ${agentId}: outbox drain failed: ${(0, config_1.msg)(err)}`);
|
|
1619
|
+
try {
|
|
1620
|
+
(0, diagnostics_1.reportDiagnosticSafe)({
|
|
1621
|
+
kind: 'wake_failure',
|
|
1622
|
+
severity: 'error',
|
|
1623
|
+
agentId,
|
|
1624
|
+
errorCode: 'OUTBOX_DRAIN_THREW',
|
|
1625
|
+
message: (0, config_1.msg)(err),
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
catch {
|
|
1629
|
+
/* best-effort */
|
|
1630
|
+
}
|
|
1472
1631
|
});
|
|
1473
1632
|
}
|
|
1474
1633
|
}, lock_1.LOCK_POLL_MS);
|
|
1475
1634
|
timers.push(flushInterval);
|
|
1476
|
-
timers.push(flushInterval);
|
|
1477
1635
|
// ── Silent auto-upgrade (2026-09) ──────────────────────────────────────
|
|
1478
1636
|
// The daemon installs the newest published `teamshare-bridge` on its own
|
|
1479
|
-
// once THIS machine
|
|
1480
|
-
//
|
|
1481
|
-
// minutes. Opt out with `teamshare-bridge auto-upgrade off` (config
|
|
1482
|
-
// autoUpgrade).
|
|
1637
|
+
// once THIS machine is not EXECUTING anything. Opt out with
|
|
1638
|
+
// `teamshare-bridge auto-upgrade off` (config autoUpgrade).
|
|
1483
1639
|
//
|
|
1484
|
-
//
|
|
1485
|
-
//
|
|
1486
|
-
//
|
|
1487
|
-
//
|
|
1488
|
-
const WAKE_QUIET_MS = 2 * 60_000;
|
|
1640
|
+
// WHAT COUNTS AS "BUSY" lives in `upgradeBlockingReason` (auto-upgrade.ts),
|
|
1641
|
+
// not here: it is a pure exported function so the queued-vs-executing rule
|
|
1642
|
+
// can be regression-tested instead of trusted. This loop only collects the
|
|
1643
|
+
// observations.
|
|
1489
1644
|
const machineBusy = () => {
|
|
1645
|
+
const now = Date.now();
|
|
1490
1646
|
for (const agent of (0, config_1.loadConfig)().agents) {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
const ago = Date.now() - Date.parse(lastWake);
|
|
1503
|
-
if (Number.isFinite(ago) && ago < WAKE_QUIET_MS) {
|
|
1504
|
-
return `a wake landed ${Math.round(ago / 1000)}s ago`;
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1647
|
+
const reason = (0, auto_upgrade_1.upgradeBlockingReason)({
|
|
1648
|
+
agentId: agent.agentId,
|
|
1649
|
+
lockBusy: (0, lock_1.isLockBusy)(agent.agentId),
|
|
1650
|
+
liveSession: sessionRelay.current(agent.agentId) !== undefined &&
|
|
1651
|
+
sessionRelay.current(agent.agentId) !== null,
|
|
1652
|
+
lastWakeAt: lastWakeByAgent.get(agent.agentId) ?? null,
|
|
1653
|
+
queuedWakes: safeOutboxDepth(agent.agentId),
|
|
1654
|
+
now,
|
|
1655
|
+
});
|
|
1656
|
+
if (reason)
|
|
1657
|
+
return reason;
|
|
1507
1658
|
}
|
|
1508
1659
|
return null;
|
|
1509
1660
|
};
|
|
1661
|
+
/**
|
|
1662
|
+
* Total durable queued wakes across every agent. Diagnostics only - it is
|
|
1663
|
+
* deliberately NOT part of the upgrade gate (see `upgradeBlockingReason`).
|
|
1664
|
+
*/
|
|
1665
|
+
const queuedWakes = () => {
|
|
1666
|
+
let total = 0;
|
|
1667
|
+
for (const agent of (0, config_1.loadConfig)().agents) {
|
|
1668
|
+
total += safeOutboxDepth(agent.agentId);
|
|
1669
|
+
}
|
|
1670
|
+
return total;
|
|
1671
|
+
};
|
|
1510
1672
|
let stopAutoUpgrade = () => { };
|
|
1511
1673
|
stopAutoUpgrade = (0, auto_upgrade_1.startAutoUpgrade)({
|
|
1512
1674
|
log: daemon_support_1.log,
|
|
@@ -1515,6 +1677,9 @@ async function runDaemon(portOverride) {
|
|
|
1515
1677
|
version: (0, version_check_1.currentVersion)(),
|
|
1516
1678
|
port: boundPort,
|
|
1517
1679
|
shutdown: () => shutdown(),
|
|
1680
|
+
// Reported once per state change, not per 30s sample, so the hub shows
|
|
1681
|
+
// "update pending, waiting for idle" instead of a repeating trickle.
|
|
1682
|
+
describeQueue: queuedWakes,
|
|
1518
1683
|
report: async (severity, message, context) => {
|
|
1519
1684
|
await (0, diagnostics_1.reportDiagnostic)({
|
|
1520
1685
|
kind: 'auto_upgrade',
|
|
@@ -1546,6 +1711,12 @@ async function runDaemon(portOverride) {
|
|
|
1546
1711
|
catch { /* best-effort */ }
|
|
1547
1712
|
for (const t of timers)
|
|
1548
1713
|
clearInterval(t);
|
|
1714
|
+
// Pending spawn-watchdog timers. They are `.unref()`'d so they never hold
|
|
1715
|
+
// the process open, but they are created outside `timers` (one per agent,
|
|
1716
|
+
// rescheduled on every spawn) and so were never cleared here.
|
|
1717
|
+
for (const t of spawnWatchTimers.values())
|
|
1718
|
+
clearTimeout(t);
|
|
1719
|
+
spawnWatchTimers.clear();
|
|
1549
1720
|
// Graceful shutdown SETTLES what this boot owes (2026-10). Sockets are still
|
|
1550
1721
|
// open here, so the reports land — and settling is honest on a clean stop,
|
|
1551
1722
|
// unlike the crash path, which deliberately reports nothing so the hub can
|