teamshare-bridge 0.22.9 → 0.22.11

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.
Files changed (107) hide show
  1. package/dist/bridge/daemon-support.js +25 -5
  2. package/dist/bridge/daemon-support.js.map +1 -1
  3. package/dist/bridge/daemon.js +209 -58
  4. package/dist/bridge/daemon.js.map +1 -1
  5. package/dist/bridge/doctor.js +12 -5
  6. package/dist/bridge/doctor.js.map +1 -1
  7. package/dist/bridge/index.js +5 -0
  8. package/dist/bridge/index.js.map +1 -1
  9. package/dist/bridge/local-server.d.ts +2 -0
  10. package/dist/bridge/local-server.js +158 -19
  11. package/dist/bridge/local-server.js.map +1 -1
  12. package/dist/bridge/protocol.js +3 -12
  13. package/dist/bridge/protocol.js.map +1 -1
  14. package/dist/bridge/session-relay.d.ts +8 -0
  15. package/dist/bridge/session-relay.js +56 -5
  16. package/dist/bridge/session-relay.js.map +1 -1
  17. package/dist/bridge/wake-handler.js +18 -4
  18. package/dist/bridge/wake-handler.js.map +1 -1
  19. package/dist/bridge/wake-routing.d.ts +6 -0
  20. package/dist/bridge/wake-routing.js +24 -6
  21. package/dist/bridge/wake-routing.js.map +1 -1
  22. package/dist/cli/commands/board.js +10 -1
  23. package/dist/cli/commands/board.js.map +1 -1
  24. package/dist/cli/commands/browse.js +10 -6
  25. package/dist/cli/commands/browse.js.map +1 -1
  26. package/dist/cli/commands/browser.js +6 -7
  27. package/dist/cli/commands/browser.js.map +1 -1
  28. package/dist/cli/commands/build.js +28 -5
  29. package/dist/cli/commands/build.js.map +1 -1
  30. package/dist/cli/commands/chat.js +10 -1
  31. package/dist/cli/commands/chat.js.map +1 -1
  32. package/dist/cli/commands/cleanup.js +1 -11
  33. package/dist/cli/commands/cleanup.js.map +1 -1
  34. package/dist/cli/commands/connect.js +2 -8
  35. package/dist/cli/commands/connect.js.map +1 -1
  36. package/dist/cli/commands/disconnect.js +4 -10
  37. package/dist/cli/commands/disconnect.js.map +1 -1
  38. package/dist/cli/commands/doc.js +10 -1
  39. package/dist/cli/commands/doc.js.map +1 -1
  40. package/dist/cli/commands/planner.js +10 -1
  41. package/dist/cli/commands/planner.js.map +1 -1
  42. package/dist/cli/commands/research.js +21 -4
  43. package/dist/cli/commands/research.js.map +1 -1
  44. package/dist/cli/commands/run.js +10 -1
  45. package/dist/cli/commands/run.js.map +1 -1
  46. package/dist/lib/api-content.d.ts +1 -1
  47. package/dist/lib/api-content.js +7 -1
  48. package/dist/lib/api-content.js.map +1 -1
  49. package/dist/lib/api-types.d.ts +7 -0
  50. package/dist/lib/brief.js +77 -1
  51. package/dist/lib/brief.js.map +1 -1
  52. package/dist/lib/browser/browse-stealth.js +37 -6
  53. package/dist/lib/browser/browse-stealth.js.map +1 -1
  54. package/dist/lib/browser/profile-guard.d.ts +23 -0
  55. package/dist/lib/browser/profile-guard.js +72 -0
  56. package/dist/lib/browser/profile-guard.js.map +1 -0
  57. package/dist/lib/chat/daemon-reporter.js +8 -1
  58. package/dist/lib/chat/daemon-reporter.js.map +1 -1
  59. package/dist/lib/git/git-discovery.d.ts +23 -4
  60. package/dist/lib/git/git-discovery.js +67 -19
  61. package/dist/lib/git/git-discovery.js.map +1 -1
  62. package/dist/lib/git/git-finalize.js +53 -1
  63. package/dist/lib/git/git-finalize.js.map +1 -1
  64. package/dist/lib/git/index-recovery.js +91 -1
  65. package/dist/lib/git/index-recovery.js.map +1 -1
  66. package/dist/lib/harness/agy-pty.d.ts +1 -1
  67. package/dist/lib/harness/agy-pty.js +20 -4
  68. package/dist/lib/harness/agy-pty.js.map +1 -1
  69. package/dist/lib/harness/spawn.js +144 -25
  70. package/dist/lib/harness/spawn.js.map +1 -1
  71. package/dist/lib/llm/llm-tools.d.ts +17 -2
  72. package/dist/lib/llm/llm-tools.js +164 -36
  73. package/dist/lib/llm/llm-tools.js.map +1 -1
  74. package/dist/lib/lock.d.ts +6 -0
  75. package/dist/lib/lock.js +108 -15
  76. package/dist/lib/lock.js.map +1 -1
  77. package/dist/lib/orchestrator/handlers/browser.js +9 -8
  78. package/dist/lib/orchestrator/handlers/browser.js.map +1 -1
  79. package/dist/lib/orchestrator/handlers/git.js +88 -6
  80. package/dist/lib/orchestrator/handlers/git.js.map +1 -1
  81. package/dist/lib/orchestrator/worktree.d.ts +10 -0
  82. package/dist/lib/orchestrator/worktree.js +243 -101
  83. package/dist/lib/orchestrator/worktree.js.map +1 -1
  84. package/dist/lib/tail-file.d.ts +4 -0
  85. package/dist/lib/tail-file.js +73 -0
  86. package/dist/lib/tail-file.js.map +1 -0
  87. package/dist/lib/tools/browser-tool.js +13 -2
  88. package/dist/lib/tools/browser-tool.js.map +1 -1
  89. package/dist/lib/wake-claim.d.ts +4 -3
  90. package/dist/lib/wake-claim.js +81 -20
  91. package/dist/lib/wake-claim.js.map +1 -1
  92. package/dist/lib/workspace/bus.d.ts +32 -0
  93. package/dist/lib/workspace/bus.js +48 -1
  94. package/dist/lib/workspace/bus.js.map +1 -1
  95. package/dist/lib/workspace/mirror-lease.js +30 -1
  96. package/dist/lib/workspace/mirror-lease.js.map +1 -1
  97. package/dist/lib/workspace/mirror.js +22 -1
  98. package/dist/lib/workspace/mirror.js.map +1 -1
  99. package/dist/lib/workspace/presence.js +38 -14
  100. package/dist/lib/workspace/presence.js.map +1 -1
  101. package/dist/lib/workspace/repo-lock.d.ts +21 -1
  102. package/dist/lib/workspace/repo-lock.js +146 -21
  103. package/dist/lib/workspace/repo-lock.js.map +1 -1
  104. package/dist/lib/workspace/stop-save.js +10 -0
  105. package/dist/lib/workspace/stop-save.js.map +1 -1
  106. package/package.json +1 -1
  107. 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
- /* best-effort */
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
- /* corrupt state file - just remove it */
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
- /* best-effort */
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":"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"}
1
+ {"version":3,"file":"daemon-support.js","sourceRoot":"","sources":["../../src/bridge/daemon-support.ts"],"names":[],"mappings":"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"}
@@ -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
- (0, bus_1.writeRuntimeFile)({ port: boundPort, pid: process.pid, startedAt: new Date().toISOString() });
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
- try {
515
- if (sessionRelay.current(agentId))
516
- return; // session came up
517
- const lock = (0, lock_1.readLock)(agentId);
518
- if (lock?.pid) {
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
- process.kill(lock.pid, 0);
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
- /* dead pid - fall through to forensics */
565
+ names = []; // folder never created, or cleaned up
525
566
  }
526
- }
527
- // No session, no live lock: look for a capture file written by this
528
- // spawn (hidden-launch creates it only if the chain actually ran).
529
- let launched = false;
530
- let captureInfo = 'no capture file - the hidden launcher never ran (Windows Script Host/wscript.exe may be disabled or blocked by antivirus)';
531
- const dir = (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'sessions');
532
- let newest = null;
533
- for (const f of (0, node_fs_1.readdirSync)(dir)) {
534
- if (!f.startsWith('spawn-') || !f.endsWith('.log'))
535
- continue;
536
- const st = (0, node_fs_1.statSync)((0, node_path_1.join)(dir, f), { throwIfNoEntry: false });
537
- if (!st)
538
- continue;
539
- if (st.mtimeMs >= startedAt - 2_000 &&
540
- (!newest || st.mtimeMs > newest.mtime)) {
541
- newest = { file: f, mtime: st.mtimeMs };
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
- if (newest) {
545
- launched = true;
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
- (0, daemon_support_1.log)(`agent ${agentId}: spawn watch - no session started ${Math.round((Date.now() - startedAt) / 1000)}s after ${wakeType} wake`);
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
- agentTimers.push(setTimeout(() => {
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) => (0, daemon_support_1.log)(`agent ${agent.agentId}: wake failed: ${(0, config_1.msg)(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
- const outcome = await (0, wake_handler_1.handleWake)(api_1.TeamshareApi.forAgent(apiBase, agent), agentId, agent.apiKey, lane.event, getConfig(), { wakeQueue, settings, recentSpawns, orchestratorClient });
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