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.
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/dist/client.d.ts +56 -35
- package/packages/relay/dist/client.js +278 -71
- package/packages/relay/dist/index.d.ts +12 -5
- package/packages/relay/dist/index.js +12 -4
- package/packages/relay/dist/server.d.ts +58 -20
- package/packages/relay/dist/server.js +344 -49
- package/packages/relay/dist/types.d.ts +137 -1
- package/packages/relay/dist/types.js +21 -2
- package/packages/relay/package.json +1 -1
- package/packages/relay/src/client.ts +375 -81
- package/packages/relay/src/index.ts +42 -5
- package/packages/relay/src/server.ts +452 -54
- package/packages/relay/src/types.ts +174 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/relay-client.ts +105 -96
- package/scripts/unified-webhook-server.ts +145 -143
- package/scripts/ws-server.ts +81 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/provider-router",
|
|
3
|
-
"version": "1.1.
|
|
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,53 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Relay Client -
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
14
|
-
|
|
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
|
|
26
|
-
private
|
|
27
|
-
private
|
|
28
|
-
|
|
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():
|
|
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
|
-
*
|
|
55
|
+
* Send a message through the WebSocket
|
|
51
56
|
*/
|
|
52
|
-
|
|
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 -
|
|
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
|
-
*
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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.
|
|
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.
|
|
67
|
+
if (this.isConnected())
|
|
39
68
|
return;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
this.
|
|
57
|
-
this.
|
|
115
|
+
disconnect() {
|
|
116
|
+
console.log("[RelayClient] Disconnecting...");
|
|
117
|
+
this.cleanup();
|
|
118
|
+
this.setState({ state: 'disconnected' });
|
|
119
|
+
this.callbacks.onDisconnected?.();
|
|
58
120
|
}
|
|
59
121
|
/**
|
|
60
|
-
*
|
|
122
|
+
* Send a message through the WebSocket
|
|
61
123
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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 -
|
|
2
|
+
* Cloud Relay - Two-way Streaming Communication
|
|
3
3
|
*
|
|
4
|
-
* Enables
|
|
5
|
-
* Based on T3Code's
|
|
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 {
|
|
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 -
|
|
2
|
+
* Cloud Relay - Two-way Streaming Communication
|
|
3
3
|
*
|
|
4
|
-
* Enables
|
|
5
|
-
* Based on T3Code's
|
|
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
|
-
|
|
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';
|