switchroom 0.18.17 → 0.18.19

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 (67) hide show
  1. package/dist/agent-scheduler/index.js +13 -0
  2. package/dist/auth-broker/index.js +13 -0
  3. package/dist/cli/notion-write-pretool.mjs +13 -0
  4. package/dist/cli/switchroom.js +605 -479
  5. package/dist/host-control/main.js +17 -1
  6. package/dist/vault/approvals/kernel-server.js +13 -0
  7. package/dist/vault/broker/server.js +13 -0
  8. package/package.json +1 -1
  9. package/telegram-plugin/bridge/bridge.ts +7 -1
  10. package/telegram-plugin/dist/bridge/bridge.js +26 -1
  11. package/telegram-plugin/dist/gateway/gateway.js +1544 -619
  12. package/telegram-plugin/dist/server.js +32 -1
  13. package/telegram-plugin/fleet-fallback-resume.ts +26 -3
  14. package/telegram-plugin/format.ts +137 -213
  15. package/telegram-plugin/gateway/approval-hold.ts +49 -0
  16. package/telegram-plugin/gateway/bridge-dead-watchdog.ts +61 -18
  17. package/telegram-plugin/gateway/gateway.ts +399 -85
  18. package/telegram-plugin/gateway/linear-activity.ts +20 -4
  19. package/telegram-plugin/gateway/outbound-send-path.ts +9 -7
  20. package/telegram-plugin/gateway/premium-recovery-wiring.ts +122 -0
  21. package/telegram-plugin/gateway/session-model-file.ts +103 -0
  22. package/telegram-plugin/gateway/tier-downgrade-wiring.ts +121 -0
  23. package/telegram-plugin/gateway/unhandled-rejection-policy.ts +14 -1
  24. package/telegram-plugin/llm-error-present.ts +474 -0
  25. package/telegram-plugin/operator-events.ts +7 -1
  26. package/telegram-plugin/permission-title.ts +172 -10
  27. package/telegram-plugin/premium-recovery.ts +101 -0
  28. package/telegram-plugin/raw-error-scrub.ts +73 -0
  29. package/telegram-plugin/retry-api-call.ts +8 -2
  30. package/telegram-plugin/send-gate-degraded.test.ts +152 -1
  31. package/telegram-plugin/send-gate-observability.test.ts +140 -0
  32. package/telegram-plugin/send-gate-observability.ts +65 -20
  33. package/telegram-plugin/send-gate.test.ts +143 -1
  34. package/telegram-plugin/send-gate.ts +212 -19
  35. package/telegram-plugin/session-tail.ts +16 -0
  36. package/telegram-plugin/shared/local-time.ts +69 -0
  37. package/telegram-plugin/stream-reply-handler.ts +5 -14
  38. package/telegram-plugin/tests/approval-hold-harness.ts +6 -6
  39. package/telegram-plugin/tests/approval-hold-outcome.test.ts +10 -2
  40. package/telegram-plugin/tests/bridge-dead-watchdog.test.ts +61 -0
  41. package/telegram-plugin/tests/fleet-fallback-resume.test.ts +39 -0
  42. package/telegram-plugin/tests/flood-windows-persistence.test.ts +3 -2
  43. package/telegram-plugin/tests/format-consistency.test.ts +68 -53
  44. package/telegram-plugin/tests/formatting-parse-regression.test.ts +5 -6
  45. package/telegram-plugin/tests/formatting-torture-set.ts +1 -1
  46. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +26 -0
  47. package/telegram-plugin/tests/linear-create-issue.test.ts +30 -2
  48. package/telegram-plugin/tests/llm-error-present.test.ts +481 -0
  49. package/telegram-plugin/tests/outbound-send-path.test.ts +4 -3
  50. package/telegram-plugin/tests/paragraph-normalizer.test.ts +42 -100
  51. package/telegram-plugin/tests/permission-title.test.ts +167 -4
  52. package/telegram-plugin/tests/premium-recovery-wiring.test.ts +150 -0
  53. package/telegram-plugin/tests/premium-recovery.test.ts +165 -0
  54. package/telegram-plugin/tests/reaction-gate-routing.test.ts +6 -1
  55. package/telegram-plugin/tests/retry-api-call.test.ts +21 -0
  56. package/telegram-plugin/tests/stream-reply-handler.test.ts +9 -12
  57. package/telegram-plugin/tests/telegram-format.test.ts +86 -31
  58. package/telegram-plugin/tests/tier-downgrade-wiring.test.ts +165 -0
  59. package/telegram-plugin/tests/tier-downgrade.test.ts +141 -0
  60. package/telegram-plugin/tests/turn-flush-safety.test.ts +17 -21
  61. package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +27 -1
  62. package/telegram-plugin/tests/worker-activity-feed.test.ts +5 -2
  63. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +492 -0
  64. package/telegram-plugin/tier-downgrade.ts +198 -0
  65. package/telegram-plugin/tool-activity-summary.ts +99 -0
  66. package/telegram-plugin/turn-flush-safety.ts +4 -3
  67. package/telegram-plugin/worker-activity-feed.ts +509 -409
@@ -32,11 +32,15 @@
32
32
  * claude itself down — where a bounce is either premature or
33
33
  * futile); the timer re-arms so a slow-booting claude gets the full
34
34
  * grace window again once it appears;
35
- * - cron-session bridges (`<agent>-cron`) and anonymous IPC clients
36
- * (recall.py one-shots, pre-handshake connects) neither satisfy nor
37
- * re-arm the watchdog the gating lives INSIDE noteBridge* so a
38
- * gateway handler refactor can't silently reintroduce the
39
- * bounce-after-cron-fire false positive;
35
+ * - cron-session bridges (`<agent>-cron`), anonymous IPC clients
36
+ * (recall.py one-shots, pre-handshake connects), AND secondary/relay
37
+ * clients that register a DIFFERENT agent name than this gateway serves
38
+ * (e.g. an `overlord-relay` connecting into another agent's gateway
39
+ * socket — #3086) neither satisfy nor re-arm the watchdog: only the
40
+ * gateway's OWN primary bridge (agentName === $SWITCHROOM_AGENT_NAME)
41
+ * counts. The gating lives INSIDE noteBridge* so a gateway handler
42
+ * refactor can't silently reintroduce the bounce-after-cron-fire or the
43
+ * bounce-after-relay-disconnect false positive;
40
44
  * - loud, structured escalation log line including a fresh
41
45
  * bridge-crash.log tail (PR #3037 breadcrumbs) so the operator sees
42
46
  * WHY from the supervisor log alone.
@@ -352,6 +356,16 @@ export interface BridgeDeadWatchdogOpts {
352
356
  * unref (the watchdog must never keep the gateway process alive). */
353
357
  setTimer?: (fn: () => void, ms: number) => unknown
354
358
  clearTimer?: (handle: unknown) => void
359
+ /** The agent identity THIS gateway serves ($SWITCHROOM_AGENT_NAME).
360
+ * Only a bridge client registering under this exact name is the
361
+ * primary bridge whose presence/absence drives the watchdog. Secondary
362
+ * or relay clients (a different named identity connecting into this
363
+ * gateway's socket — e.g. `overlord-relay`, #3086) are ignored on both
364
+ * the register and disconnect paths. When unset/empty (a misconfigured
365
+ * gateway with no agent name), the watchdog falls back to the pre-#3086
366
+ * test — any named non-cron client counts — so a genuine bridge death is
367
+ * still caught rather than silently un-guarded. */
368
+ selfAgentName?: string
355
369
  /** Injectable clock (marker ts + crash-log freshness). */
356
370
  nowMs?: () => number
357
371
  /** Injectable marker writer (tests avoid real fs). */
@@ -365,15 +379,18 @@ export interface BridgeDeadWatchdog {
365
379
  * when the cross-boot streak cap is already reached (stands down with
366
380
  * an audit line instead). */
367
381
  arm: () => void
368
- /** A bridge client registered. Only a REAL bridge (named, non-cron)
369
- * satisfies the watchdog cron sessions (`<agent>-cron`) and
370
- * anonymous clients (agentName null) are ignored HERE so a gateway
371
- * handler refactor can't reorder the gating away (review finding 5). */
382
+ /** A bridge client registered. Only THIS gateway's OWN primary bridge
383
+ * (agentName === selfAgentName, named, non-cron) satisfies the watchdog
384
+ * — cron sessions (`<agent>-cron`), anonymous clients (agentName null),
385
+ * and secondary/relay clients registering a different name (#3086) are
386
+ * ignored HERE so a gateway handler refactor can't reorder the gating
387
+ * away (review finding 5). */
372
388
  noteBridgeRegistered: (agentName: string | null | undefined) => void
373
- /** A bridge client disconnected. Re-arms the grace window only for the
374
- * real bridge (same internal gating), so a bridge that dies AFTER boot
375
- * and never reconnects also escalates. Still capped by the
376
- * once-per-boot fuse. */
389
+ /** A bridge client disconnected. Re-arms the grace window only for THIS
390
+ * gateway's own primary bridge (same internal gating), so a bridge that
391
+ * dies AFTER boot and never reconnects also escalates while a
392
+ * transient relay/secondary client's disconnect (#3086) is a no-op.
393
+ * Still capped by the once-per-boot fuse. */
377
394
  noteBridgeDisconnected: (agentName: string | null | undefined) => void
378
395
  /** Evaluate now (the timer body — exposed for tests). Returns the
379
396
  * decision taken. */
@@ -384,9 +401,34 @@ export interface BridgeDeadWatchdog {
384
401
  hasEscalated: () => boolean
385
402
  }
386
403
 
387
- /** Is this client identity the REAL main-agent bridge (named, non-cron)? */
388
- function isRealBridgeIdentity(agentName: string | null | undefined): boolean {
389
- return agentName != null && agentName.length > 0 && !isCronIdentity(agentName)
404
+ /**
405
+ * Is this client identity THIS gateway's OWN primary main-agent bridge?
406
+ *
407
+ * A named, non-cron client whose name matches the gateway's own agent
408
+ * identity (`selfAgentName` = $SWITCHROOM_AGENT_NAME). A switchroom gateway
409
+ * serves exactly one agent, and its real bridge registers under exactly that
410
+ * name (see bridge/bridge.ts — `agentName: AGENT_NAME`). Secondary or relay
411
+ * clients (e.g. an `overlord-relay` that connects into another agent's
412
+ * gateway socket, #3086) register a DIFFERENT name; they are named and
413
+ * non-cron but are NOT this gateway's primary bridge, so their register /
414
+ * disconnect must neither satisfy nor re-arm the watchdog — otherwise a
415
+ * transient relay disconnect marks the (still-alive) primary bridge dead and
416
+ * bounces a healthy container.
417
+ *
418
+ * When `selfAgentName` is unset/empty (misconfigured gateway) we cannot
419
+ * identity-match, so fall back to the pre-#3086 test — any named non-cron
420
+ * client counts — keeping the watchdog protective rather than un-guarded.
421
+ */
422
+ function isRealBridgeIdentity(
423
+ agentName: string | null | undefined,
424
+ selfAgentName: string | null | undefined,
425
+ ): boolean {
426
+ if (agentName == null || agentName.length === 0) return false
427
+ if (isCronIdentity(agentName)) return false
428
+ if (selfAgentName != null && selfAgentName.length > 0) {
429
+ return agentName === selfAgentName
430
+ }
431
+ return true
390
432
  }
391
433
 
392
434
  export function createBridgeDeadWatchdog(opts: BridgeDeadWatchdogOpts): BridgeDeadWatchdog {
@@ -404,6 +446,7 @@ export function createBridgeDeadWatchdog(opts: BridgeDeadWatchdogOpts): BridgeDe
404
446
  opts.readCrashTail ?? ((p: string, t: number) => readFreshCrashLogTail(p, { nowMs: t }))
405
447
  const priorStreak = opts.priorStreak ?? 0
406
448
  const maxConsecutive = opts.maxConsecutive ?? MAX_CONSECUTIVE_ESCALATIONS
449
+ const selfAgentName = opts.selfAgentName
407
450
 
408
451
  let timer: unknown = null
409
452
  let bridgeRegistered = false
@@ -528,13 +571,13 @@ export function createBridgeDeadWatchdog(opts: BridgeDeadWatchdogOpts): BridgeDe
528
571
  return {
529
572
  arm: armInternal,
530
573
  noteBridgeRegistered: (agentName) => {
531
- if (!isRealBridgeIdentity(agentName)) return
574
+ if (!isRealBridgeIdentity(agentName, selfAgentName)) return
532
575
  bridgeRegistered = true
533
576
  bridgeEverRegistered = true
534
577
  cancel()
535
578
  },
536
579
  noteBridgeDisconnected: (agentName) => {
537
- if (!isRealBridgeIdentity(agentName)) return
580
+ if (!isRealBridgeIdentity(agentName, selfAgentName)) return
538
581
  bridgeRegistered = false
539
582
  armInternal()
540
583
  },