teamshare-bridge 0.22.9 → 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/daemon-support.js +25 -5
- package/dist/bridge/daemon-support.js.map +1 -1
- package/dist/bridge/daemon.js +209 -58
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/doctor.js +12 -5
- package/dist/bridge/doctor.js.map +1 -1
- package/dist/bridge/index.js +5 -0
- 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/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
|
@@ -70,8 +70,11 @@ function writeAgentStopFile(agentId) {
|
|
|
70
70
|
(0, node_fs_1.mkdirSync)(exports.STOP_DIR, { recursive: true });
|
|
71
71
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(exports.STOP_DIR, `${agentId}.stop`), new Date().toISOString(), 'utf8');
|
|
72
72
|
}
|
|
73
|
-
catch {
|
|
74
|
-
|
|
73
|
+
catch (err) {
|
|
74
|
+
// NOT best-effort. This file is the only thing the build loop and the
|
|
75
|
+
// orchestrator worker read to know the human said stop - a failed write
|
|
76
|
+
// means the stop never reaches them and the loop keeps spawning tasks.
|
|
77
|
+
console.error(`[stop] could not write ${(0, node_path_1.join)(exports.STOP_DIR, `${agentId}.stop`)}: ${err.code ?? String(err)} - the build loop and worker will NOT see this stop`);
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
@@ -98,13 +101,30 @@ function clearAgentBuildState(agentId, api) {
|
|
|
98
101
|
remaining = Array.isArray(parsed.skipped) ? parsed.skipped.length : 0;
|
|
99
102
|
}
|
|
100
103
|
catch {
|
|
101
|
-
|
|
104
|
+
// Corrupt/locked state file: the removal below still runs, and a file
|
|
105
|
+
// that cannot be parsed has no projectId to report progress for.
|
|
106
|
+
console.warn(`[stop] build state ${full} was unreadable - removing it`);
|
|
102
107
|
}
|
|
108
|
+
// `force: true` suppresses ENOENT only - EPERM/EBUSY still throw, and
|
|
109
|
+
// they are routine here: this runs right after the session was
|
|
110
|
+
// force-killed (`taskkill /F`), so the child can still hold the file,
|
|
111
|
+
// and Defender pauses it routinely. Swallowing that and reporting
|
|
112
|
+
// `stopped` anyway is CORRECT about the outcome (the daemon wrote the
|
|
113
|
+
// stop file first, so the build IS stopping) but it must not be silent:
|
|
114
|
+
// a surviving state file is what `wake-handler` reads as
|
|
115
|
+
// `hasBuildState`, so the failure costs one wasted respawn on the next
|
|
116
|
+
// `question.answered`. Log it so that respawn has an explanation.
|
|
117
|
+
let removed = false;
|
|
103
118
|
try {
|
|
104
119
|
(0, node_fs_1.rmSync)(full, { force: true });
|
|
120
|
+
removed = !(0, node_fs_1.existsSync)(full);
|
|
105
121
|
}
|
|
106
|
-
catch {
|
|
107
|
-
|
|
122
|
+
catch (err) {
|
|
123
|
+
removed = false;
|
|
124
|
+
console.error(`[stop] could not clear build state ${full}: ${err.code ?? String(err)} - the next question.answered will re-read it (the stop file still makes that session exit immediately)`);
|
|
125
|
+
}
|
|
126
|
+
if (!removed && (0, node_fs_1.existsSync)(full)) {
|
|
127
|
+
console.error(`[stop] build state ${full} survived - see the message above`);
|
|
108
128
|
}
|
|
109
129
|
if (projectId) {
|
|
110
130
|
void api
|
|
@@ -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,6 +15,7 @@ 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");
|
|
@@ -328,6 +329,14 @@ async function runDaemon(portOverride) {
|
|
|
328
329
|
}
|
|
329
330
|
/** How long to ask the hub to wait before re-offering a deferred wake. */
|
|
330
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;
|
|
331
340
|
/**
|
|
332
341
|
* Report every lane this agent still owes a terminal report for, and clear the
|
|
333
342
|
* local debt (2026-10).
|
|
@@ -491,8 +500,15 @@ async function runDaemon(portOverride) {
|
|
|
491
500
|
// foreground (only detached `start` used to leave a pid file behind).
|
|
492
501
|
(0, daemon_support_1.writeDaemonPidFile)();
|
|
493
502
|
// IMP-950: publish the probe port so shims + CLI tools can find the
|
|
494
|
-
// arbiter endpoints without scanning candidate ports.
|
|
495
|
-
|
|
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
|
+
});
|
|
496
512
|
if (config.agents.length === 0) {
|
|
497
513
|
(0, daemon_support_1.log)('no agents configured - run: teamshare-agent connect --agent <id> --key ts_...');
|
|
498
514
|
}
|
|
@@ -511,64 +527,87 @@ async function runDaemon(portOverride) {
|
|
|
511
527
|
const startedAt = Date.now();
|
|
512
528
|
const timer = setTimeout(() => {
|
|
513
529
|
spawnWatchTimers.delete(agentId);
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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 = [];
|
|
519
561
|
try {
|
|
520
|
-
|
|
521
|
-
return; // a live session process holds the lock - assume running
|
|
562
|
+
names = await node_fs_1.promises.readdir(dir);
|
|
522
563
|
}
|
|
523
564
|
catch {
|
|
524
|
-
|
|
565
|
+
names = []; // folder never created, or cleaned up
|
|
525
566
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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?)`;
|
|
542
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
|
+
});
|
|
543
606
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, newest.file), 'utf8');
|
|
547
|
-
const tail = content
|
|
548
|
-
.split(/\r?\n/)
|
|
549
|
-
.filter(Boolean)
|
|
550
|
-
.slice(-15)
|
|
551
|
-
.join('\n');
|
|
552
|
-
captureInfo = tail
|
|
553
|
-
? `capture ${newest.file} tail:\n${tail}`
|
|
554
|
-
: `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)}`);
|
|
555
609
|
}
|
|
556
|
-
|
|
557
|
-
(0, diagnostics_1.reportDiagnosticSafe)({
|
|
558
|
-
kind: 'spawn_failure',
|
|
559
|
-
severity: 'error',
|
|
560
|
-
agentId,
|
|
561
|
-
message: `Session never started after a ${wakeType} wake (${launched ? 'CLI exited before session:start' : 'hidden launcher never ran'})`,
|
|
562
|
-
errorCode: launched
|
|
563
|
-
? 'SESSION_PREMATURE_EXIT'
|
|
564
|
-
: 'SESSION_NEVER_LAUNCHED',
|
|
565
|
-
context: { wakeType, capture: captureInfo.slice(0, 4_000) },
|
|
566
|
-
includeDoctor: true,
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
catch (err) {
|
|
570
|
-
(0, daemon_support_1.log)(`agent ${agentId}: spawn watch failed: ${(0, config_1.msg)(err)}`);
|
|
571
|
-
}
|
|
610
|
+
})();
|
|
572
611
|
}, 20_000);
|
|
573
612
|
timer.unref();
|
|
574
613
|
spawnWatchTimers.set(agentId, timer);
|
|
@@ -777,6 +816,8 @@ async function runDaemon(portOverride) {
|
|
|
777
816
|
const config = getConfig;
|
|
778
817
|
const agentTimers = [];
|
|
779
818
|
let timersCreated = false;
|
|
819
|
+
/** At most one pending staggered first-beat; see the `connect` handler. */
|
|
820
|
+
let firstBeatTimer = null;
|
|
780
821
|
/** Consecutive 401 "Invalid API key" heartbeats before we stop retrying. */
|
|
781
822
|
let authFailures = 0;
|
|
782
823
|
const isAuthError = (err) => err instanceof api_1.ApiError
|
|
@@ -926,10 +967,23 @@ async function runDaemon(portOverride) {
|
|
|
926
967
|
// is safe. Later beats ride the 2-min heartbeat + 5-min push cadence.
|
|
927
968
|
const seq = (0, daemon_support_1.nextAgentLoopSeq)();
|
|
928
969
|
const firstBeatMs = Math.round(daemon_support_1.HEARTBEAT_MS * (0.25 + 0.5 * ((seq % 6) / 6)));
|
|
929
|
-
|
|
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;
|
|
930
983
|
heartbeat();
|
|
931
984
|
pushState();
|
|
932
|
-
}, firstBeatMs)
|
|
985
|
+
}, firstBeatMs);
|
|
986
|
+
agentTimers.push(firstBeatTimer);
|
|
933
987
|
// Only create persistent intervals on the first connect — reconnects
|
|
934
988
|
// reuse the existing timers (they survive socket disconnects).
|
|
935
989
|
if (!timersCreated) {
|
|
@@ -1170,7 +1224,34 @@ async function runDaemon(portOverride) {
|
|
|
1170
1224
|
orchestratorClient,
|
|
1171
1225
|
});
|
|
1172
1226
|
reportWakeOutcome(agent.agentId, socket, event, outcome);
|
|
1173
|
-
})().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
|
+
});
|
|
1174
1255
|
});
|
|
1175
1256
|
sockets.push(socket);
|
|
1176
1257
|
// 2026-09: restore this agent's queued wakes from disk instead of wiping
|
|
@@ -1372,6 +1453,9 @@ async function runDaemon(portOverride) {
|
|
|
1372
1453
|
const res = await fetch(`${apiBase}/agents/${agentId}/keys/rotate`, {
|
|
1373
1454
|
method: 'POST',
|
|
1374
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),
|
|
1375
1459
|
});
|
|
1376
1460
|
if (!res.ok)
|
|
1377
1461
|
continue;
|
|
@@ -1440,7 +1524,33 @@ async function runDaemon(portOverride) {
|
|
|
1440
1524
|
(0, daemon_support_1.log)(`agent ${agentId}: outbox drain paused after a lane took the lock`);
|
|
1441
1525
|
return;
|
|
1442
1526
|
}
|
|
1443
|
-
|
|
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
|
+
}
|
|
1444
1554
|
// Anything that is NOT finished with the lane stays queued. That covers
|
|
1445
1555
|
// 'queued', 'deferred' and 'blocked' - the three outcomes that mean
|
|
1446
1556
|
// "ask again later" - plus a thrown error handled below.
|
|
@@ -1478,15 +1588,50 @@ async function runDaemon(portOverride) {
|
|
|
1478
1588
|
// report for a deliberately failed 'blocked') is what stops the row
|
|
1479
1589
|
// sitting `acknowledged` on the hub while the outbox holds it.
|
|
1480
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
|
+
}
|
|
1481
1612
|
}
|
|
1482
1613
|
}
|
|
1483
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.
|
|
1484
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
|
+
}
|
|
1485
1631
|
});
|
|
1486
1632
|
}
|
|
1487
1633
|
}, lock_1.LOCK_POLL_MS);
|
|
1488
1634
|
timers.push(flushInterval);
|
|
1489
|
-
timers.push(flushInterval);
|
|
1490
1635
|
// ── Silent auto-upgrade (2026-09) ──────────────────────────────────────
|
|
1491
1636
|
// The daemon installs the newest published `teamshare-bridge` on its own
|
|
1492
1637
|
// once THIS machine is not EXECUTING anything. Opt out with
|
|
@@ -1566,6 +1711,12 @@ async function runDaemon(portOverride) {
|
|
|
1566
1711
|
catch { /* best-effort */ }
|
|
1567
1712
|
for (const t of timers)
|
|
1568
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();
|
|
1569
1720
|
// Graceful shutdown SETTLES what this boot owes (2026-10). Sockets are still
|
|
1570
1721
|
// open here, so the reports land — and settling is honest on a clean stop,
|
|
1571
1722
|
// unlike the crash path, which deliberately reports nothing so the hub can
|