pi-braid-telegram 0.0.0-stage → 0.1.4

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.
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Watchdog Tunnel Bridge — cross-package seam (telegram → core).
3
+ *
4
+ * Telegram cannot import pi-braid core (standalone package, no core dep) and
5
+ * core cannot import telegram, so tunnel liveness crosses packages through a
6
+ * deliberately narrow process-global slot — the same seam pattern as the
7
+ * providers key-ring feedback (KEY_RING_FEEDBACK_KEY, packages/providers/
8
+ * ring-lifecycle.ts ↔ packages/core/src/lib/state/global-state.ts).
9
+ *
10
+ * WATCHDOG_TUNNEL_KEY MUST stay in sync with core's
11
+ * packages/core/src/lib/state/global-state.ts (same literal), and the hub
12
+ * protocol below MUST match core's WatchdogTunnelHub exactly: both sides
13
+ * get-or-create the hub at the same slot, telegram publishes, core
14
+ * subscribes. If the key literal or the protocol drifts, tunnel-down/up
15
+ * signals silently stop reaching the watchdog — keep them in sync, like the
16
+ * KEY_RING_FEEDBACK_KEY comment demands.
17
+ *
18
+ * One emitter per event: only inbound-poller.ts (via the poller's injectable
19
+ * `onTunnelStatusChange`, wired by telegram/index.ts) calls publishWatchdogTunnel.
20
+ * Core's watchdog is the single consumer that surfaces the two whispers.
21
+ */
22
+
23
+ import type { WatchdogTunnelStatusEvent } from "./inbound-poller.js";
24
+
25
+ /** Process-global slot key — MUST match core's WATCHDOG_TUNNEL_KEY literal. */
26
+ export const WATCHDOG_TUNNEL_KEY = "__braid_watchdog_tunnel";
27
+
28
+ export type WatchdogTunnelStatus = "up" | "down" | "unknown";
29
+
30
+ /**
31
+ * Hub protocol mirrored verbatim by core (global-state.ts). The hub object
32
+ * lives at globalThis[WATCHDOG_TUNNEL_KEY]; whichever side runs first
33
+ * (telegram publish or core subscribe) get-or-creates it with this shape.
34
+ */
35
+ export interface WatchdogTunnelHub {
36
+ /** Latest published status ("unknown" until the first publish). */
37
+ status: WatchdogTunnelStatus;
38
+ /** Consecutive-failure count of the latest transition (0 when up/unknown). */
39
+ failures: number;
40
+ /** Last getUpdates error text seen (present after a down transition). */
41
+ lastError?: string;
42
+ /** Record a transition and notify every subscriber (synchronous). */
43
+ publish(event: WatchdogTunnelStatusEvent): void;
44
+ /** Register a listener; returns an unsubscribe function. */
45
+ subscribe(fn: (event: WatchdogTunnelStatusEvent) => void): () => void;
46
+ }
47
+
48
+ /** Build the hub object (get-or-create at the global slot). */
49
+ function getOrCreateTunnelHub(): WatchdogTunnelHub {
50
+ const g = globalThis as Record<string, unknown>;
51
+ const existing = g[WATCHDOG_TUNNEL_KEY] as WatchdogTunnelHub | undefined;
52
+ if (existing && typeof existing?.publish === "function") return existing;
53
+
54
+ const subs = new Set<(event: WatchdogTunnelStatusEvent) => void>();
55
+ let status: WatchdogTunnelStatus = "unknown";
56
+ let failures = 0;
57
+ let lastError: string | undefined;
58
+
59
+ const hub: WatchdogTunnelHub = {
60
+ get status() {
61
+ return status;
62
+ },
63
+ get failures() {
64
+ return failures;
65
+ },
66
+ get lastError() {
67
+ return lastError;
68
+ },
69
+ publish(event) {
70
+ status = event.status;
71
+ failures = event.failures;
72
+ if (event.lastError !== undefined) lastError = event.lastError;
73
+ for (const fn of [...subs]) {
74
+ try {
75
+ fn(event);
76
+ } catch {
77
+ // A subscriber must never break the bus — the canary keeps running.
78
+ }
79
+ }
80
+ },
81
+ subscribe(fn) {
82
+ subs.add(fn);
83
+ return () => {
84
+ subs.delete(fn);
85
+ };
86
+ },
87
+ };
88
+ g[WATCHDOG_TUNNEL_KEY] = hub;
89
+ return hub;
90
+ }
91
+
92
+ /**
93
+ * Publish a tunnel liveness transition into the shared watchdog hub.
94
+ * Get-or-create so a first publish before core's watchdog subscribes is never
95
+ * lost for later subscribers (they receive the current status on subscribe —
96
+ * subscribers are called on every subsequent publish; the core watchdog reads
97
+ * the latest status on session_start via getWatchdogTunnelStatus).
98
+ */
99
+ export function publishWatchdogTunnel(event: WatchdogTunnelStatusEvent): void {
100
+ const hub = getOrCreateTunnelHub();
101
+ hub.publish(event);
102
+ }