pi-harness-runtime 1.1.376 → 1.1.377

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 (123) hide show
  1. package/package.json +1 -1
  2. package/packages/a2a-adapter/package.json +1 -1
  3. package/packages/agent-policy/package.json +1 -1
  4. package/packages/architecture-generator/package.json +1 -1
  5. package/packages/auth/package.json +1 -1
  6. package/packages/autonomous-refactor/package.json +1 -1
  7. package/packages/autonomous-runtime/package.json +1 -1
  8. package/packages/cache-strategy/package.json +1 -1
  9. package/packages/capability-registry/package.json +1 -1
  10. package/packages/checkpoint/package.json +1 -1
  11. package/packages/cli-plugin-sdk/package.json +1 -1
  12. package/packages/clipboard/package.json +1 -1
  13. package/packages/clipboard-plugin/package.json +1 -1
  14. package/packages/code-generation/package.json +1 -1
  15. package/packages/code-review/package.json +1 -1
  16. package/packages/codex-adapter/package.json +1 -1
  17. package/packages/codex-watcher/package.json +1 -1
  18. package/packages/config-capture/package.json +1 -1
  19. package/packages/connection-runtime/package.json +1 -1
  20. package/packages/context-compiler/package.json +1 -1
  21. package/packages/context-discovery/package.json +1 -1
  22. package/packages/context-manager/package.json +1 -1
  23. package/packages/contracts/package.json +1 -1
  24. package/packages/cookie-sanitizer/package.json +1 -1
  25. package/packages/cost-optimizer/package.json +1 -1
  26. package/packages/dependency-analyzer/package.json +1 -1
  27. package/packages/django-plugin/package.json +1 -1
  28. package/packages/doc-generator/package.json +1 -1
  29. package/packages/durable-review/package.json +1 -1
  30. package/packages/e2e-tester/package.json +1 -1
  31. package/packages/evaluation-engine/package.json +1 -1
  32. package/packages/evaluation-runner/package.json +1 -1
  33. package/packages/event-bus/package.json +1 -1
  34. package/packages/event-sink/package.json +1 -1
  35. package/packages/event-store/package.json +1 -1
  36. package/packages/experience-replay/package.json +1 -1
  37. package/packages/feedback-collector/package.json +1 -1
  38. package/packages/file-copy-helper/package.json +1 -1
  39. package/packages/framework-detector/package.json +1 -1
  40. package/packages/framework-plugin-sdk/package.json +1 -1
  41. package/packages/frappe-plugin/package.json +1 -1
  42. package/packages/generic-web-plugin/package.json +1 -1
  43. package/packages/health-monitor/package.json +1 -1
  44. package/packages/intent-analyzer/package.json +1 -1
  45. package/packages/jev-judge/package.json +1 -1
  46. package/packages/knowledge-graph/package.json +1 -1
  47. package/packages/knowledge-retrieval/package.json +1 -1
  48. package/packages/laravel-plugin/package.json +1 -1
  49. package/packages/learning-engine/package.json +1 -1
  50. package/packages/mcp-adapter/package.json +1 -1
  51. package/packages/memory-engine/package.json +1 -1
  52. package/packages/milestone-manager/package.json +1 -1
  53. package/packages/model-registry/package.json +1 -1
  54. package/packages/nextjs-plugin/package.json +1 -1
  55. package/packages/notification/package.json +1 -1
  56. package/packages/observability/package.json +1 -1
  57. package/packages/okf-indexer/package.json +1 -1
  58. package/packages/paperclip-integration/package.json +1 -1
  59. package/packages/performance-optimizer/package.json +1 -1
  60. package/packages/permission-mode/package.json +1 -1
  61. package/packages/privilege-broker/package.json +1 -1
  62. package/packages/project-analyzer/package.json +1 -1
  63. package/packages/project-bootstrap/package.json +1 -1
  64. package/packages/projection-engine/package.json +1 -1
  65. package/packages/prompt-compiler/package.json +1 -1
  66. package/packages/prompt-versioning/package.json +1 -1
  67. package/packages/provider-adapter-sdk/package.json +1 -1
  68. package/packages/provider-router/package.json +1 -1
  69. package/packages/provider-selector/package.json +1 -1
  70. package/packages/providers/package.json +1 -1
  71. package/packages/qdrant-skills/package.json +1 -1
  72. package/packages/quota-manager/package.json +1 -1
  73. package/packages/rate-limiter/package.json +1 -1
  74. package/packages/react-vite-plugin/package.json +1 -1
  75. package/packages/relay/dist/client.d.ts +56 -35
  76. package/packages/relay/dist/client.js +278 -71
  77. package/packages/relay/dist/index.d.ts +12 -5
  78. package/packages/relay/dist/index.js +12 -4
  79. package/packages/relay/dist/server.d.ts +58 -20
  80. package/packages/relay/dist/server.js +344 -49
  81. package/packages/relay/dist/types.d.ts +137 -1
  82. package/packages/relay/dist/types.js +21 -2
  83. package/packages/relay/package.json +1 -1
  84. package/packages/relay/src/client.ts +375 -81
  85. package/packages/relay/src/index.ts +42 -5
  86. package/packages/relay/src/server.ts +452 -54
  87. package/packages/relay/src/types.ts +174 -1
  88. package/packages/release-manager/package.json +1 -1
  89. package/packages/requirement-compiler/package.json +1 -1
  90. package/packages/runtime/package.json +1 -1
  91. package/packages/scheduler/package.json +1 -1
  92. package/packages/scheduler-adapter/package.json +1 -1
  93. package/packages/session/package.json +1 -1
  94. package/packages/session-api/package.json +1 -1
  95. package/packages/session-export/package.json +1 -1
  96. package/packages/shared-context/package.json +1 -1
  97. package/packages/skill-mcp-client/package.json +1 -1
  98. package/packages/skill-registry/package.json +1 -1
  99. package/packages/skills/package.json +1 -1
  100. package/packages/sprint-planner/package.json +1 -1
  101. package/packages/subscription-engine/package.json +1 -1
  102. package/packages/task-compiler/package.json +1 -1
  103. package/packages/telegram/package.json +1 -1
  104. package/packages/tencentdb-memory/package.json +1 -1
  105. package/packages/tencentdb-sync/package.json +1 -1
  106. package/packages/test-data-generator/package.json +1 -1
  107. package/packages/test-generator/package.json +1 -1
  108. package/packages/todo-bd-sync/package.json +1 -1
  109. package/packages/token-estimation/package.json +1 -1
  110. package/packages/token-optimizer/package.json +1 -1
  111. package/packages/trajectory/package.json +1 -1
  112. package/packages/tui/package.json +1 -1
  113. package/packages/types/package.json +1 -1
  114. package/packages/voice-api/package.json +1 -1
  115. package/packages/webmcp-tester/package.json +1 -1
  116. package/packages/wiki/package.json +1 -1
  117. package/packages/workflow-events/package.json +1 -1
  118. package/packages/workspace-scanner/package.json +1 -1
  119. package/packages/worktree/package.json +1 -1
  120. package/packages/write-review/package.json +1 -1
  121. package/scripts/relay-client.ts +105 -96
  122. package/scripts/unified-webhook-server.ts +145 -143
  123. package/scripts/ws-server.ts +81 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/provider-router",
3
- "version": "1.1.376",
3
+ "version": "1.1.377",
4
4
  "description": "Enhanced Provider Router for pi-harness-runtime — intelligent routing with capabilities, costs, and quotas",
5
5
  "type": "module",
6
6
  "main": "./dist/provider-router.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/provider-selector",
3
- "version": "1.1.376",
3
+ "version": "1.1.377",
4
4
  "description": "Multi-Provider Selector — route requests by cost, quality, and latency (RFC-0012)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/providers",
3
- "version": "1.1.376",
3
+ "version": "1.1.377",
4
4
  "description": "pi-harness-runtime — providers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/qdrant-skills",
3
- "version": "1.1.376",
3
+ "version": "1.1.377",
4
4
  "description": "Qdrant vector search integration for Hermes-style skill system",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/quota-manager",
3
- "version": "1.1.376",
3
+ "version": "1.1.377",
4
4
  "description": "pi-harness-runtime — quota-manager",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/rate-limiter",
3
- "version": "1.1.376",
3
+ "version": "1.1.377",
4
4
  "type": "module",
5
5
  "description": "Rate limiting with token bucket algorithm (RFC-0023)",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/react-vite-plugin",
3
- "version": "1.1.376",
3
+ "version": "1.1.377",
4
4
  "description": "React/Vite framework analysis — components, config, TypeScript setup",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,53 +1,74 @@
1
1
  /**
2
- * Relay Client - Connect to relay server for remote access
3
- */
4
- import type { RelayConfig, RelayTunnel, RelayMessage } from './types.js';
5
- /**
6
- * Relay Client options
2
+ * Relay Client - Two-way streaming WebSocket client
3
+ *
4
+ * Implements t3code-style streaming with:
5
+ * - Connection state machine
6
+ * - Snapshot + live event handling
7
+ * - Typed message protocol
8
+ * - Automatic reconnection with backoff
7
9
  */
8
- export interface RelayClientOptions {
9
- /** Called when connected */
10
- onConnected?: () => void;
10
+ import { type RelayConfig, type ConnectionState, type ConnectionStateChange, type CallbackPayload, type ClientMessage, type SnapshotMessage, type StatusMessage } from './types.js';
11
+ export interface RelayClientCallbacks {
12
+ /** Called when connected and authenticated */
13
+ onConnected?: (sessionId: string) => void;
11
14
  /** Called when disconnected */
12
15
  onDisconnected?: () => void;
13
- /** Called when tunnel is established */
14
- onTunnel?: (tunnel: RelayTunnel) => void;
16
+ /** Called when snapshot is received (initial state) */
17
+ onSnapshot?: (snapshot: SnapshotMessage['payload']) => void;
18
+ /** Called when synchronized marker received (live events start) */
19
+ onSynchronized?: () => void;
20
+ /** Called when a callback event is received */
21
+ onCallback?: (callback: CallbackPayload) => void;
22
+ /** Called when a status message is received */
23
+ onStatus?: (status: StatusMessage['payload']) => void;
15
24
  /** Called on error */
16
25
  onError?: (error: Error) => void;
26
+ /** Called when connection state changes */
27
+ onStateChange?: (change: ConnectionStateChange) => void;
17
28
  }
18
- /**
19
- * Relay Client
20
- *
21
- * Connects to a relay server and establishes tunnels for remote access.
22
- */
23
29
  export declare class RelayClient {
30
+ private ws;
24
31
  private config;
25
- private options;
26
- private connected;
27
- private tunnel?;
28
- constructor(config: RelayConfig, options: RelayClientOptions);
32
+ private callbacks;
33
+ private state;
34
+ private sessionId;
35
+ private clientId;
36
+ private reconnectAttempt;
37
+ private reconnectDelay;
38
+ private reconnectTimer;
39
+ private connectTimeoutTimer;
40
+ private heartbeatTimer;
41
+ private snapshotReceived;
42
+ private synchronizedReceived;
43
+ constructor(config: RelayConfig, callbacks?: RelayClientCallbacks);
44
+ private setState;
45
+ private isConnected;
29
46
  /**
30
- * Connect to relay server
47
+ * Connect to relay server with automatic authentication
31
48
  */
32
49
  connect(): Promise<void>;
33
50
  /**
34
51
  * Disconnect from relay server
35
52
  */
36
- disconnect(): Promise<void>;
37
- /**
38
- * Create a tunnel
39
- */
40
- createTunnel(localPort: number): Promise<RelayTunnel>;
41
- /**
42
- * Send a message through the tunnel
43
- */
44
- send(_message: RelayMessage): Promise<void>;
45
- /**
46
- * Check if connected
47
- */
48
- isConnected(): boolean;
53
+ disconnect(): void;
49
54
  /**
50
- * Get current tunnel
55
+ * Send a message through the WebSocket
51
56
  */
52
- getTunnel(): RelayTunnel | undefined;
57
+ send(message: ClientMessage): void;
58
+ private handleMessage;
59
+ private handleConnected;
60
+ private handleAuth;
61
+ private handleSnapshot;
62
+ private handleSynchronized;
63
+ private handleCallback;
64
+ private handleStatus;
65
+ private handleDisconnect;
66
+ private scheduleReconnect;
67
+ private startHeartbeat;
68
+ private stopHeartbeat;
69
+ private cleanup;
70
+ getState(): ConnectionState;
71
+ isReady(): boolean;
72
+ getSessionId(): string | null;
73
+ getClientId(): string | null;
53
74
  }
@@ -1,95 +1,302 @@
1
1
  /**
2
- * Relay Client - Connect to relay server for remote access
3
- */
4
- /**
5
- * Generate a UUID v4
6
- */
7
- function generateUUID() {
8
- // Use crypto.randomUUID if available (Node.js 14.17+)
9
- const crypto = globalThis;
10
- if (crypto.crypto?.randomUUID) {
11
- return crypto.crypto.randomUUID();
12
- }
13
- // Fallback: simple UUID generation
14
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
15
- const r = (Math.random() * 16) | 0;
16
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
17
- return v.toString(16);
18
- });
19
- }
20
- /**
21
- * Relay Client
2
+ * Relay Client - Two-way streaming WebSocket client
22
3
  *
23
- * Connects to a relay server and establishes tunnels for remote access.
4
+ * Implements t3code-style streaming with:
5
+ * - Connection state machine
6
+ * - Snapshot + live event handling
7
+ * - Typed message protocol
8
+ * - Automatic reconnection with backoff
24
9
  */
10
+ import WebSocket from "ws";
11
+ import { PROTOCOL_VERSION, createTimestamp, } from './types.js';
12
+ // ============================================================================
13
+ // Constants
14
+ // ============================================================================
15
+ const DEFAULT_RECONNECT_DELAY_MS = 1000;
16
+ const MAX_RECONNECT_DELAY_MS = 30000;
17
+ const HEARTBEAT_INTERVAL_MS = 30000;
18
+ const CONNECT_TIMEOUT_MS = 10000;
19
+ // ============================================================================
20
+ // Relay Client - Two-way Streaming Implementation
21
+ // ============================================================================
25
22
  export class RelayClient {
23
+ ws = null;
26
24
  config;
27
- options;
28
- connected = false;
29
- tunnel;
30
- constructor(config, options) {
25
+ callbacks;
26
+ // State machine
27
+ state = { state: 'disconnected' };
28
+ sessionId = null;
29
+ clientId = null;
30
+ // Reconnection
31
+ reconnectAttempt = 0;
32
+ reconnectDelay = DEFAULT_RECONNECT_DELAY_MS;
33
+ reconnectTimer = null;
34
+ connectTimeoutTimer = null;
35
+ // Heartbeat
36
+ heartbeatTimer = null;
37
+ // Snapshot tracking (t3code pattern)
38
+ snapshotReceived = false;
39
+ synchronizedReceived = false;
40
+ constructor(config, callbacks = {}) {
31
41
  this.config = config;
32
- this.options = options;
42
+ this.callbacks = callbacks;
43
+ }
44
+ // ========================================================================
45
+ // Connection State Machine
46
+ // ========================================================================
47
+ setState(newState) {
48
+ const previous = this.state;
49
+ this.state = newState;
50
+ this.callbacks.onStateChange?.({
51
+ previous,
52
+ current: newState,
53
+ timestamp: createTimestamp(),
54
+ });
33
55
  }
56
+ isConnected() {
57
+ return this.ws?.readyState === WebSocket.OPEN &&
58
+ this.state.state === 'connected';
59
+ }
60
+ // ========================================================================
61
+ // Lifecycle
62
+ // ========================================================================
34
63
  /**
35
- * Connect to relay server
64
+ * Connect to relay server with automatic authentication
36
65
  */
37
66
  async connect() {
38
- if (this.connected)
67
+ if (this.isConnected())
39
68
  return;
40
- try {
41
- // In a real implementation, this would establish WebSocket connection
42
- // to the relay server and authenticate
43
- this.connected = true;
44
- this.options.onConnected?.();
45
- }
46
- catch (error) {
47
- this.options.onError?.(error);
48
- throw error;
49
- }
69
+ // Clear any existing connection
70
+ this.cleanup();
71
+ this.setState({ state: 'connecting' });
72
+ const url = this.config.serverUrl;
73
+ console.log(`[RelayClient] Connecting to ${url}...`);
74
+ return new Promise((resolve, reject) => {
75
+ // Set connect timeout
76
+ this.connectTimeoutTimer = setTimeout(() => {
77
+ if (this.state.state === 'connecting') {
78
+ this.ws?.close();
79
+ reject(new Error('Connection timeout'));
80
+ }
81
+ }, CONNECT_TIMEOUT_MS);
82
+ this.ws = new WebSocket(url);
83
+ this.ws.on("open", () => {
84
+ console.log("[RelayClient] WebSocket opened");
85
+ this.setState({ state: 'authenticating' });
86
+ // Send authentication
87
+ this.send({
88
+ version: PROTOCOL_VERSION,
89
+ type: 'auth',
90
+ payload: { token: this.config.token || '' },
91
+ timestamp: createTimestamp(),
92
+ });
93
+ });
94
+ this.ws.on("message", (data) => {
95
+ this.handleMessage(data.toString());
96
+ });
97
+ this.ws.on("close", () => {
98
+ console.log("[RelayClient] WebSocket closed");
99
+ this.handleDisconnect();
100
+ });
101
+ this.ws.on("error", (err) => {
102
+ console.error("[RelayClient] WebSocket error:", err.message);
103
+ this.callbacks.onError?.(err);
104
+ if (this.state.state === 'connecting' || this.state.state === 'authenticating') {
105
+ clearTimeout(this.connectTimeoutTimer);
106
+ reject(err);
107
+ }
108
+ this.handleDisconnect();
109
+ });
110
+ });
50
111
  }
51
112
  /**
52
113
  * Disconnect from relay server
53
114
  */
54
- async disconnect() {
55
- this.connected = false;
56
- this.tunnel = undefined;
57
- this.options.onDisconnected?.();
115
+ disconnect() {
116
+ console.log("[RelayClient] Disconnecting...");
117
+ this.cleanup();
118
+ this.setState({ state: 'disconnected' });
119
+ this.callbacks.onDisconnected?.();
58
120
  }
59
121
  /**
60
- * Create a tunnel
122
+ * Send a message through the WebSocket
61
123
  */
62
- async createTunnel(localPort) {
63
- const tunnel = {
64
- id: generateUUID(),
65
- url: `${this.config.serverUrl}/tunnel/${localPort}`,
66
- localPort,
67
- status: this.connected ? 'connected' : 'disconnected',
68
- createdAt: new Date().toISOString(),
69
- };
70
- this.tunnel = tunnel;
71
- this.options.onTunnel?.(tunnel);
72
- return tunnel;
124
+ send(message) {
125
+ if (this.isConnected()) {
126
+ this.ws.send(JSON.stringify(message));
127
+ }
73
128
  }
74
- /**
75
- * Send a message through the tunnel
76
- */
77
- async send(_message) {
78
- if (!this.connected) {
79
- throw new Error('Not connected to relay server');
129
+ // ========================================================================
130
+ // Message Handling
131
+ // ========================================================================
132
+ handleMessage(data) {
133
+ try {
134
+ const msg = JSON.parse(data);
135
+ switch (msg.type) {
136
+ case 'connected':
137
+ this.handleConnected(msg);
138
+ break;
139
+ case 'auth':
140
+ this.handleAuth(msg);
141
+ break;
142
+ case 'snapshot':
143
+ this.handleSnapshot(msg);
144
+ break;
145
+ case 'synchronized':
146
+ this.handleSynchronized(msg);
147
+ break;
148
+ case 'callback':
149
+ this.handleCallback(msg);
150
+ break;
151
+ case 'status':
152
+ this.handleStatus(msg);
153
+ break;
154
+ case 'pong':
155
+ // Heartbeat response - connection is alive
156
+ break;
157
+ default: {
158
+ const unknownMsg = msg;
159
+ console.warn(`[RelayClient] Unknown message type: ${unknownMsg.type}`);
160
+ }
161
+ }
162
+ }
163
+ catch (err) {
164
+ console.error("[RelayClient] Failed to parse message:", err);
165
+ this.callbacks.onError?.(err);
80
166
  }
81
- // In a real implementation, this would send the message over WebSocket
82
167
  }
83
- /**
84
- * Check if connected
85
- */
86
- isConnected() {
87
- return this.connected;
168
+ handleConnected(msg) {
169
+ this.clientId = msg.payload.clientId;
170
+ console.log(`[RelayClient] Connected, clientId: ${this.clientId}`);
88
171
  }
89
- /**
90
- * Get current tunnel
91
- */
92
- getTunnel() {
93
- return this.tunnel;
172
+ handleAuth(msg) {
173
+ if (msg.payload.success) {
174
+ this.sessionId = msg.payload.sessionId || null;
175
+ this.setState({ state: 'connected', sessionId: this.sessionId || '' });
176
+ // Reset reconnection state
177
+ this.reconnectAttempt = 0;
178
+ this.reconnectDelay = DEFAULT_RECONNECT_DELAY_MS;
179
+ // Start heartbeat
180
+ this.startHeartbeat();
181
+ // Notify callback
182
+ this.callbacks.onConnected?.(this.sessionId || '');
183
+ console.log(`[RelayClient] Authenticated, sessionId: ${this.sessionId}`);
184
+ }
185
+ else {
186
+ console.error(`[RelayClient] Auth failed: ${msg.payload.error}`);
187
+ this.callbacks.onError?.(new Error(msg.payload.error || 'Authentication failed'));
188
+ this.disconnect();
189
+ }
190
+ }
191
+ handleSnapshot(msg) {
192
+ console.log(`[RelayClient] Snapshot received: ${msg.payload.recentCallbacks.length} recent callbacks`);
193
+ this.snapshotReceived = true;
194
+ // Notify callback
195
+ this.callbacks.onSnapshot?.(msg.payload);
196
+ }
197
+ handleSynchronized(_msg) {
198
+ console.log("[RelayClient] Synchronized - live events starting");
199
+ this.synchronizedReceived = true;
200
+ // Notify callback
201
+ this.callbacks.onSynchronized?.();
202
+ }
203
+ handleCallback(msg) {
204
+ // Only process callbacks after synchronized
205
+ if (!this.synchronizedReceived) {
206
+ console.warn("[RelayClient] Ignoring callback before synchronized");
207
+ return;
208
+ }
209
+ this.callbacks.onCallback?.(msg.payload);
210
+ }
211
+ handleStatus(msg) {
212
+ console.log(`[RelayClient] Status [${msg.payload.kind}]: ${msg.payload.code} - ${msg.payload.message}`);
213
+ this.callbacks.onStatus?.(msg.payload);
214
+ }
215
+ // ========================================================================
216
+ // Disconnect & Reconnect
217
+ // ========================================================================
218
+ handleDisconnect() {
219
+ this.stopHeartbeat();
220
+ const wasConnected = this.state.state === 'connected';
221
+ this.setState({ state: 'disconnected' });
222
+ if (wasConnected) {
223
+ this.callbacks.onDisconnected?.();
224
+ this.scheduleReconnect();
225
+ }
226
+ }
227
+ scheduleReconnect() {
228
+ if (this.reconnectTimer)
229
+ return;
230
+ this.reconnectAttempt++;
231
+ this.setState({ state: 'reconnecting', attempt: this.reconnectAttempt });
232
+ console.log(`[RelayClient] Scheduling reconnect attempt ${this.reconnectAttempt} in ${this.reconnectDelay}ms`);
233
+ this.reconnectTimer = setTimeout(() => {
234
+ this.reconnectTimer = null;
235
+ this.connect().catch((err) => {
236
+ console.error(`[RelayClient] Reconnect failed: ${err.message}`);
237
+ });
238
+ }, this.reconnectDelay);
239
+ // Exponential backoff
240
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
241
+ }
242
+ // ========================================================================
243
+ // Heartbeat
244
+ // ========================================================================
245
+ startHeartbeat() {
246
+ this.stopHeartbeat();
247
+ this.heartbeatTimer = setInterval(() => {
248
+ if (this.isConnected()) {
249
+ this.send({
250
+ version: PROTOCOL_VERSION,
251
+ type: 'ping',
252
+ payload: {},
253
+ timestamp: createTimestamp(),
254
+ });
255
+ }
256
+ }, HEARTBEAT_INTERVAL_MS);
257
+ }
258
+ stopHeartbeat() {
259
+ if (this.heartbeatTimer) {
260
+ clearInterval(this.heartbeatTimer);
261
+ this.heartbeatTimer = null;
262
+ }
263
+ }
264
+ // ========================================================================
265
+ // Cleanup
266
+ // ========================================================================
267
+ cleanup() {
268
+ // Clear timers
269
+ if (this.reconnectTimer) {
270
+ clearTimeout(this.reconnectTimer);
271
+ this.reconnectTimer = null;
272
+ }
273
+ if (this.connectTimeoutTimer) {
274
+ clearTimeout(this.connectTimeoutTimer);
275
+ this.connectTimeoutTimer = null;
276
+ }
277
+ this.stopHeartbeat();
278
+ // Reset snapshot state
279
+ this.snapshotReceived = false;
280
+ this.synchronizedReceived = false;
281
+ // Close WebSocket
282
+ if (this.ws) {
283
+ this.ws.close();
284
+ this.ws = null;
285
+ }
286
+ }
287
+ // ========================================================================
288
+ // Public API
289
+ // ========================================================================
290
+ getState() {
291
+ return this.state;
292
+ }
293
+ isReady() {
294
+ return this.isConnected() && this.synchronizedReceived;
295
+ }
296
+ getSessionId() {
297
+ return this.sessionId;
298
+ }
299
+ getClientId() {
300
+ return this.clientId;
94
301
  }
95
302
  }
@@ -1,9 +1,16 @@
1
1
  /**
2
- * Cloud Relay - T3 Connect Style
2
+ * Cloud Relay - Two-way Streaming Communication
3
3
  *
4
- * Enables remote access to pi-harness without router/port forwarding.
5
- * Based on T3Code's T3 Connect architecture.
4
+ * Enables real-time two-way communication with pi-harness without router/port forwarding.
5
+ * Based on T3Code's streaming architecture:
6
+ * - Snapshot + Live event pattern
7
+ * - Typed message protocol
8
+ * - Connection state machine
9
+ * - Stream coalescing
6
10
  */
7
- export { RelayServer } from './server.js';
11
+ export { RelayServer, createRelayServer } from './server.js';
12
+ export type { RelayServerOptions } from './server.js';
8
13
  export { RelayClient } from './client.js';
9
- export { RelayConfig, RelayTunnel } from './types.js';
14
+ export type { RelayClientCallbacks } from './client.js';
15
+ export { PROTOCOL_VERSION, generateMessageId, createTimestamp, } from './types.js';
16
+ export type { RelayConfig, RelayTunnel, ConnectionState, ConnectionStateChange, BaseMessage, SnapshotMessage, SynchronizedMessage, CallbackPayload, CallbackMessage, StatusKind, StatusMessage, PingMessage, PongMessage, AuthRequestMessage, AuthResponseMessage, SubscribeMessage, ConnectedMessage, ServerMessage, ClientMessage, RelayMessage, } from './types.js';
@@ -1,8 +1,16 @@
1
1
  /**
2
- * Cloud Relay - T3 Connect Style
2
+ * Cloud Relay - Two-way Streaming Communication
3
3
  *
4
- * Enables remote access to pi-harness without router/port forwarding.
5
- * Based on T3Code's T3 Connect architecture.
4
+ * Enables real-time two-way communication with pi-harness without router/port forwarding.
5
+ * Based on T3Code's streaming architecture:
6
+ * - Snapshot + Live event pattern
7
+ * - Typed message protocol
8
+ * - Connection state machine
9
+ * - Stream coalescing
6
10
  */
7
- export { RelayServer } from './server.js';
11
+ // Server
12
+ export { RelayServer, createRelayServer } from './server.js';
13
+ // Client
8
14
  export { RelayClient } from './client.js';
15
+ // Types
16
+ export { PROTOCOL_VERSION, generateMessageId, createTimestamp, } from './types.js';