connectonion 0.0.20 → 0.1.0

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.
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * @llm-note
3
- * Dependencies: imports from [src/connect/types, src/connect/endpoint, src/connect/auth, src/connect/handlers, src/address]
4
- * Data flow: input() → _connectAndSend() creates WS + PromiseattachWsHandlers() dispatches events → resolve on OUTPUT
5
- * State/Effects: owns WebSocket lifecycle + mutable _chatItems + _currentSession
3
+ * Dependencies: imports from [src/connect/types, src/connect/endpoint, src/connect/auth, src/connect/chat-item-mapper, src/address]
4
+ * Data flow: ensureConnected() opens persistent WS + INIT auth input() sends INPUT on existing WShandleMessage() dispatches events → resolves on OUTPUT
5
+ * State/Effects: owns persistent WebSocket + mutable _chatItems + _currentSession
6
6
  * Integration: public API consumed by connect() factory and React useAgentForHuman hook
7
7
  */
8
8
  import * as address from '../address';
9
- import { AgentStatus, ApprovalMode, ChatItem, ChatItemType, ConnectionState, ConnectOptions, ResolvedEndpoint, Response, SessionState, WebSocketCtor, WebSocketLike } from './types';
9
+ import { AgentStatus, ApprovalMode, ChatItem, ChatItemType, ConnectionState, ConnectOptions, ResolvedEndpoint, Response, SessionState, WebSocketCtor } from './types';
10
10
  export declare class RemoteAgent {
11
11
  readonly address: string;
12
12
  _keys?: address.AddressData;
@@ -19,16 +19,19 @@ export declare class RemoteAgent {
19
19
  _connectionState: ConnectionState;
20
20
  _currentSession: SessionState | null;
21
21
  _chatItems: ChatItem[];
22
- _activeWs: WebSocketLike | null;
23
- _pendingPrompt: string | null;
24
- _pendingInputId: string | null;
25
- _pendingSessionId: string | null;
26
- _lastPingTime: number;
27
- _healthCheckInterval: ReturnType<typeof setInterval> | null;
28
- _reconnectAttempts: number;
29
- _maxReconnectAttempts: number;
30
- _reconnectBaseDelay: number;
31
- _shouldReconnect: boolean;
22
+ _error: Error | null;
23
+ private _ws;
24
+ private _authenticated;
25
+ private _inputResolve;
26
+ private _inputReject;
27
+ private _inputTimer;
28
+ private _pendingRetry;
29
+ private _lastPingTime;
30
+ private _pingTimer;
31
+ private _connectResolve;
32
+ private _connectReject;
33
+ _onMessage: (() => void) | null;
34
+ set onMessage(fn: (() => void) | null);
32
35
  constructor(agentAddress: string, options?: ConnectOptions);
33
36
  get agentAddress(): string;
34
37
  get status(): AgentStatus;
@@ -37,43 +40,37 @@ export declare class RemoteAgent {
37
40
  get ui(): ChatItem[];
38
41
  get mode(): ApprovalMode;
39
42
  get error(): Error | null;
40
- _error: Error | null;
41
- _onMessage: (() => void) | null;
42
- set onMessage(fn: (() => void) | null);
43
43
  input(prompt: string, options?: {
44
44
  images?: string[];
45
45
  timeoutMs?: number;
46
46
  }): Promise<Response>;
47
- /** Reconnect to an existing session to receive pending output without adding duplicate UI items */
48
47
  reconnect(sessionId?: string): Promise<Response>;
49
- inputAsync(prompt: string, options?: {
50
- images?: string[];
51
- timeoutMs?: number;
52
- }): Promise<Response>;
48
+ send(message: Record<string, unknown>): void;
53
49
  setMode(mode: ApprovalMode, options?: {
54
50
  turns?: number;
55
51
  }): void;
56
52
  reset(): void;
57
53
  resetConversation(): void;
58
- send(message: Record<string, unknown>): void;
59
54
  signOnboard(options: {
60
55
  inviteCode?: string;
61
56
  payment?: number;
62
57
  }): Record<string, unknown>;
58
+ checkSessionStatus(sessionId: string): Promise<'executing' | 'suspended' | 'connected' | 'not_found'>;
63
59
  checkSession(sessionId?: string): Promise<'running' | 'done' | 'not_found'>;
64
- checkSessionStatus(sessionId: string): Promise<'running' | 'suspended' | 'completed' | 'not_found'>;
60
+ toString(): string;
65
61
  _addChatItem(event: Partial<ChatItem> & {
66
62
  type: ChatItemType;
67
63
  }): void;
68
64
  _clearPlaceholder(): void;
69
- _stopPingMonitor(): void;
70
- _attemptReconnect(resolve: (value: Response) => void, reject: (reason?: any) => void): void;
71
- _sendInput(ws: WebSocketLike, inputId: string, prompt: string, sessionId: string, isDirect: boolean, images?: string[]): void;
65
+ private _ensureConnected;
66
+ private _handleMessage;
67
+ private _handleConnectionLoss;
68
+ private _settleInput;
69
+ private _closeWs;
72
70
  private _startPingMonitor;
71
+ private _stopPingMonitor;
72
+ private _isDirect;
73
73
  private _resolveWsUrl;
74
74
  private _resolveEndpointOnce;
75
- private _connectAndSend;
76
- private _reconnect;
77
- toString(): string;
78
75
  }
79
76
  //# sourceMappingURL=remote-agent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote-agent.d.ts","sourceRoot":"","sources":["../../src/connect/remote-agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"remote-agent.d.ts","sourceRoot":"","sources":["../../src/connect/remote-agent.ts"],"names":[],"mappings":"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"}
@@ -3,25 +3,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RemoteAgent = void 0;
4
4
  const endpoint_1 = require("./endpoint");
5
5
  const auth_1 = require("./auth");
6
- const ws_handlers_1 = require("./ws-handlers");
6
+ const chat_item_mapper_1 = require("./chat-item-mapper");
7
7
  class RemoteAgent {
8
+ set onMessage(fn) { this._onMessage = fn; }
8
9
  constructor(agentAddress, options = {}) {
9
10
  this._endpointResolutionAttempted = false;
11
+ // Public reactive state
10
12
  this._status = 'idle';
11
13
  this._connectionState = 'disconnected';
12
14
  this._currentSession = null;
13
15
  this._chatItems = [];
14
- this._activeWs = null;
15
- this._pendingPrompt = null;
16
- this._pendingInputId = null;
17
- this._pendingSessionId = null;
18
- this._lastPingTime = 0;
19
- this._healthCheckInterval = null;
20
- this._reconnectAttempts = 0;
21
- this._maxReconnectAttempts = 3;
22
- this._reconnectBaseDelay = 1000;
23
- this._shouldReconnect = false;
24
16
  this._error = null;
17
+ // Persistent WebSocket
18
+ this._ws = null;
19
+ this._authenticated = false;
20
+ // Promise resolution for current input() call
21
+ this._inputResolve = null;
22
+ this._inputReject = null;
23
+ this._inputTimer = null;
24
+ // Pending retry after onboard
25
+ this._pendingRetry = null;
26
+ // PING/PONG health check
27
+ this._lastPingTime = 0;
28
+ this._pingTimer = null;
29
+ // Callback + promise for ensureConnected
30
+ this._connectResolve = null;
31
+ this._connectReject = null;
25
32
  this._onMessage = null;
26
33
  this.address = agentAddress;
27
34
  this._relayUrl = (0, endpoint_1.normalizeRelayUrl)(options.relayUrl || 'wss://oo.openonion.ai');
@@ -30,6 +37,7 @@ class RemoteAgent {
30
37
  if (options.keys)
31
38
  this._keys = options.keys;
32
39
  }
40
+ // --- Public getters ---
33
41
  get agentAddress() { return this.address; }
34
42
  get status() { return this._status; }
35
43
  get connectionState() { return this._connectionState; }
@@ -37,52 +45,84 @@ class RemoteAgent {
37
45
  get ui() { return this._chatItems; }
38
46
  get mode() { return this._currentSession?.mode || 'safe'; }
39
47
  get error() { return this._error || null; }
40
- set onMessage(fn) { this._onMessage = fn; }
41
48
  // --- Public API ---
42
49
  async input(prompt, options) {
43
- return this._connectAndSend(prompt, options?.timeoutMs ?? 600000, options?.images);
50
+ const timeoutMs = options?.timeoutMs ?? 600000;
51
+ this._addChatItem({ type: 'user', content: prompt, images: options?.images });
52
+ this._addChatItem({ type: 'thinking', id: '__optimistic__', status: 'running' });
53
+ this._status = 'working';
54
+ this._onMessage?.();
55
+ await this._ensureConnected();
56
+ const inputId = (0, endpoint_1.generateUUID)();
57
+ const isDirect = this._isDirect();
58
+ const msg = { type: 'INPUT', input_id: inputId, prompt };
59
+ if (options?.images?.length)
60
+ msg.images = options.images;
61
+ if (!isDirect)
62
+ msg.to = this.address;
63
+ this._ws.send(JSON.stringify(msg));
64
+ return new Promise((resolve, reject) => {
65
+ this._inputResolve = resolve;
66
+ this._inputReject = reject;
67
+ this._inputTimer = setTimeout(() => {
68
+ this._settleInput();
69
+ this._status = 'idle';
70
+ this._onMessage?.();
71
+ reject(new Error('Request timed out'));
72
+ }, timeoutMs);
73
+ });
44
74
  }
45
- /** Reconnect to an existing session to receive pending output without adding duplicate UI items */
46
75
  async reconnect(sessionId) {
47
76
  const sid = sessionId || this._currentSession?.session_id;
48
77
  if (!sid)
49
78
  throw new Error('No session to reconnect');
50
- this._keys = (0, auth_1.ensureKeys)(this._keys);
51
- await this._resolveEndpointOnce();
52
79
  if (!this._currentSession)
53
80
  this._currentSession = { session_id: sid };
54
81
  this._status = 'working';
55
- const inputId = (0, endpoint_1.generateUUID)();
82
+ this._onMessage?.();
83
+ // Force new connection for reconnect
84
+ this._closeWs();
85
+ this._keys = (0, auth_1.ensureKeys)(this._keys);
86
+ await this._resolveEndpointOnce();
56
87
  const { wsUrl, isDirect } = this._resolveWsUrl();
57
88
  const ws = new this._WS(wsUrl);
58
- this._activeWs = ws;
59
- this._shouldReconnect = true;
89
+ this._ws = ws;
90
+ this._connectionState = 'reconnecting';
91
+ this._onMessage?.();
60
92
  return new Promise((resolve, reject) => {
61
- const state = {
62
- settled: false,
63
- timer: setTimeout(() => {
64
- if (!state.settled) {
65
- state.settled = true;
66
- this._status = 'idle';
67
- this._shouldReconnect = false;
68
- this._connectionState = 'disconnected';
69
- ws.close();
70
- reject(new Error('Reconnect timed out'));
71
- }
72
- }, 60000),
73
- };
93
+ this._inputResolve = resolve;
94
+ this._inputReject = reject;
95
+ this._inputTimer = setTimeout(() => {
96
+ this._settleInput();
97
+ this._status = 'idle';
98
+ this._connectionState = 'disconnected';
99
+ this._onMessage?.();
100
+ reject(new Error('Reconnect timed out'));
101
+ }, 60000);
74
102
  ws.onopen = () => {
75
103
  this._connectionState = 'connected';
76
104
  this._lastPingTime = Date.now();
77
- this._startPingMonitor(ws, reject);
78
- // Send INPUT with existing session to trigger server reconnect path
79
- this._sendInput(ws, inputId, this._pendingPrompt || '', sid, isDirect);
105
+ this._startPingMonitor();
106
+ // Send CONNECT with session_id + session data
107
+ const payload = { timestamp: Math.floor(Date.now() / 1000) };
108
+ payload.to = this.address;
109
+ const signed = (0, auth_1.signPayload)(this._keys, payload);
110
+ const msg = { type: 'CONNECT', session_id: sid, ...signed };
111
+ if (!isDirect)
112
+ msg.to = this.address;
113
+ if (this._currentSession)
114
+ msg.session = { ...this._currentSession };
115
+ ws.send(JSON.stringify(msg));
80
116
  };
81
- (0, ws_handlers_1.attachWsHandlers)(this, ws, inputId, isDirect, state, resolve, reject);
117
+ ws.onmessage = (evt) => this._handleMessage(evt);
118
+ ws.onerror = () => this._handleConnectionLoss();
119
+ ws.onclose = () => this._handleConnectionLoss();
82
120
  });
83
121
  }
84
- async inputAsync(prompt, options) {
85
- return this.input(prompt, options);
122
+ send(message) {
123
+ if (!this._ws)
124
+ throw new Error('No active connection');
125
+ this._ws.send(JSON.stringify(message));
86
126
  }
87
127
  setMode(mode, options) {
88
128
  if (!this._currentSession) {
@@ -95,31 +135,24 @@ class RemoteAgent {
95
135
  this._currentSession.ulw_turns = options?.turns || 100;
96
136
  this._currentSession.ulw_turns_used = 0;
97
137
  }
98
- if (this._activeWs) {
138
+ if (this._ws) {
99
139
  const msg = { type: 'mode_change', mode };
100
140
  if (mode === 'ulw' && options?.turns)
101
141
  msg.turns = options.turns;
102
- this._activeWs.send(JSON.stringify(msg));
142
+ this._ws.send(JSON.stringify(msg));
103
143
  }
104
144
  }
105
145
  reset() {
106
- if (this._activeWs) {
107
- this._activeWs.close();
108
- this._activeWs = null;
109
- }
146
+ this._closeWs();
110
147
  this._currentSession = null;
111
148
  this._chatItems = [];
112
149
  this._status = 'idle';
113
150
  this._connectionState = 'disconnected';
114
- this._shouldReconnect = false;
115
- this._reconnectAttempts = 0;
151
+ this._error = null;
152
+ this._settleInput();
153
+ this._pendingRetry = null;
116
154
  }
117
155
  resetConversation() { this.reset(); }
118
- send(message) {
119
- if (!this._activeWs)
120
- throw new Error('No active connection');
121
- this._activeWs.send(JSON.stringify(message));
122
- }
123
156
  signOnboard(options) {
124
157
  const payload = { timestamp: Math.floor(Date.now() / 1000) };
125
158
  if (options.inviteCode)
@@ -128,21 +161,33 @@ class RemoteAgent {
128
161
  payload.payment = options.payment;
129
162
  return { type: 'ONBOARD_SUBMIT', ...(0, auth_1.signPayload)(this._keys, payload) };
130
163
  }
131
- async checkSession(sessionId) {
132
- const sid = sessionId || this._currentSession?.session_id;
133
- if (!sid)
134
- return 'not_found';
135
- await this._resolveEndpointOnce();
136
- const httpUrl = this._directUrl || this._resolvedEndpoint?.httpUrl;
137
- if (!httpUrl)
138
- return 'not_found';
139
- const res = await fetch(`${httpUrl}/sessions/${sid}`);
140
- if (!res.ok)
141
- return 'not_found';
142
- const data = await res.json();
143
- return data?.status === 'running' ? 'running' : 'done';
144
- }
145
164
  async checkSessionStatus(sessionId) {
165
+ // If we have a live WS, send SESSION_STATUS over it (no new connection needed)
166
+ if (this._ws && this._authenticated) {
167
+ return new Promise((resolve) => {
168
+ const timeout = setTimeout(() => resolve('not_found'), 5000);
169
+ // Temporarily intercept the next SESSION_STATUS response
170
+ const origHandler = this._ws.onmessage;
171
+ this._ws.onmessage = (evt) => {
172
+ const raw = typeof evt.data === 'string' ? evt.data : String(evt.data);
173
+ const data = JSON.parse(raw);
174
+ if (data?.type === 'SESSION_STATUS') {
175
+ clearTimeout(timeout);
176
+ this._ws.onmessage = origHandler;
177
+ resolve(data.status || 'not_found');
178
+ }
179
+ else {
180
+ // Not our response — pass to normal handler
181
+ this._handleMessage(evt);
182
+ }
183
+ };
184
+ this._ws.send(JSON.stringify({
185
+ type: 'SESSION_STATUS',
186
+ session: { session_id: sessionId },
187
+ }));
188
+ });
189
+ }
190
+ // No active connection — open a short-lived WS just for the check
146
191
  this._keys = (0, auth_1.ensureKeys)(this._keys);
147
192
  await this._resolveEndpointOnce();
148
193
  const { wsUrl, isDirect } = this._resolveWsUrl();
@@ -167,7 +212,25 @@ class RemoteAgent {
167
212
  ws.onerror = () => { clearTimeout(timeout); ws.close(); resolve('not_found'); };
168
213
  });
169
214
  }
170
- // --- Module-internal helpers (used by handlers.ts) ---
215
+ async checkSession(sessionId) {
216
+ const sid = sessionId || this._currentSession?.session_id;
217
+ if (!sid)
218
+ return 'not_found';
219
+ await this._resolveEndpointOnce();
220
+ const httpUrl = this._directUrl || this._resolvedEndpoint?.httpUrl;
221
+ if (!httpUrl)
222
+ return 'not_found';
223
+ const res = await fetch(`${httpUrl}/sessions/${sid}`).catch(() => null);
224
+ if (!res || !res.ok)
225
+ return 'not_found';
226
+ const data = await res.json().catch(() => null);
227
+ return data?.status === 'running' ? 'running' : 'done';
228
+ }
229
+ toString() {
230
+ const short = this.address.length > 12 ? this.address.slice(0, 12) + '...' : this.address;
231
+ return `RemoteAgent(${short})`;
232
+ }
233
+ // --- Internal helpers (used by useAgentForHuman) ---
171
234
  _addChatItem(event) {
172
235
  const id = event.id || (0, endpoint_1.generateUUID)();
173
236
  this._chatItems.push({ ...event, id });
@@ -177,56 +240,304 @@ class RemoteAgent {
177
240
  if (idx !== -1)
178
241
  this._chatItems.splice(idx, 1);
179
242
  }
180
- _stopPingMonitor() {
181
- if (this._healthCheckInterval) {
182
- clearInterval(this._healthCheckInterval);
183
- this._healthCheckInterval = null;
243
+ // --- Private: connection lifecycle ---
244
+ async _ensureConnected() {
245
+ if (this._ws && this._authenticated)
246
+ return;
247
+ this._keys = (0, auth_1.ensureKeys)(this._keys);
248
+ await this._resolveEndpointOnce();
249
+ const { wsUrl, isDirect } = this._resolveWsUrl();
250
+ const ws = new this._WS(wsUrl);
251
+ this._ws = ws;
252
+ // Wait for open
253
+ await new Promise((resolve, reject) => {
254
+ ws.onopen = () => {
255
+ this._connectionState = 'connected';
256
+ this._lastPingTime = Date.now();
257
+ this._startPingMonitor();
258
+ resolve();
259
+ };
260
+ ws.onerror = (err) => reject(new Error(`WebSocket connection failed: ${String(err)}`));
261
+ });
262
+ // Wire up persistent message handler
263
+ ws.onmessage = (evt) => this._handleMessage(evt);
264
+ ws.onerror = () => this._handleConnectionLoss();
265
+ ws.onclose = () => this._handleConnectionLoss();
266
+ // Send CONNECT with session (conversation history)
267
+ const payload = { timestamp: Math.floor(Date.now() / 1000) };
268
+ payload.to = this.address;
269
+ const signed = (0, auth_1.signPayload)(this._keys, payload);
270
+ const connectMsg = { type: 'CONNECT', ...signed };
271
+ if (!isDirect)
272
+ connectMsg.to = this.address;
273
+ if (this._currentSession?.session_id)
274
+ connectMsg.session_id = this._currentSession.session_id;
275
+ if (this._currentSession)
276
+ connectMsg.session = { ...this._currentSession };
277
+ ws.send(JSON.stringify(connectMsg));
278
+ // Wait for CONNECTED response
279
+ const connected = await new Promise((resolve, reject) => {
280
+ this._connectResolve = resolve;
281
+ this._connectReject = reject;
282
+ setTimeout(() => {
283
+ if (this._connectResolve) {
284
+ this._connectResolve = null;
285
+ this._connectReject = null;
286
+ reject(new Error('Authentication timed out'));
287
+ }
288
+ }, 30000);
289
+ });
290
+ this._authenticated = true;
291
+ // Update session from server (may include merged data)
292
+ const sid = connected.session_id;
293
+ if (sid) {
294
+ if (!this._currentSession) {
295
+ this._currentSession = { session_id: sid };
296
+ }
297
+ else {
298
+ this._currentSession.session_id = sid;
299
+ }
300
+ }
301
+ if (connected.server_newer && connected.session) {
302
+ this._currentSession = connected.session;
303
+ }
304
+ if (connected.server_newer && connected.chat_items && Array.isArray(connected.chat_items)) {
305
+ const userItems = this._chatItems.filter(item => item.type === 'user');
306
+ const serverNonUserItems = connected.chat_items.filter(item => item.type !== 'user');
307
+ this._chatItems = [...userItems, ...serverNonUserItems];
308
+ this._onMessage?.();
184
309
  }
185
310
  }
186
- _attemptReconnect(resolve, reject) {
187
- if (!this._currentSession?.session_id) {
188
- reject(new Error('No session to reconnect'));
311
+ _handleMessage(evt) {
312
+ const raw = typeof evt.data === 'string' ? evt.data : String(evt.data);
313
+ const data = JSON.parse(raw);
314
+ // PING/PONG keepalive
315
+ if (data?.type === 'PING') {
316
+ this._lastPingTime = Date.now();
317
+ this._ws?.send(JSON.stringify({ type: 'PONG' }));
318
+ return;
319
+ }
320
+ // CONNECTED — resolve ensureConnected() promise
321
+ if (data?.type === 'CONNECTED') {
322
+ if (this._connectResolve) {
323
+ const resolve = this._connectResolve;
324
+ this._connectResolve = null;
325
+ this._connectReject = null;
326
+ resolve(data);
327
+ this._onMessage?.();
328
+ return;
329
+ }
330
+ // CONNECTED during reconnect — update session and UI if server has newer data
331
+ if (data.server_newer && data.session) {
332
+ this._currentSession = data.session;
333
+ }
334
+ if (data.server_newer && data.chat_items && Array.isArray(data.chat_items)) {
335
+ // Server has newer chat items (e.g., agent finished while client was away)
336
+ // Keep user items from client, take everything else from server
337
+ const userItems = this._chatItems.filter(item => item.type === 'user');
338
+ const serverNonUserItems = data.chat_items.filter(item => item.type !== 'user');
339
+ this._chatItems = [...userItems, ...serverNonUserItems];
340
+ }
341
+ const reconnectSid = data.session_id;
342
+ if (reconnectSid && this._currentSession) {
343
+ this._currentSession.session_id = reconnectSid;
344
+ }
345
+ this._authenticated = true;
346
+ // If status is "connected" (idle), resolve immediately — session is alive, no events to wait for
347
+ if (data.status === 'connected' || data.status === 'new') {
348
+ this._status = 'idle';
349
+ const resolve = this._inputResolve;
350
+ this._settleInput();
351
+ resolve?.({ text: '', done: true });
352
+ }
353
+ // If status is "executing", events will stream in via _handleMessage — don't resolve yet
354
+ this._onMessage?.();
189
355
  return;
190
356
  }
191
- if (this._reconnectAttempts >= this._maxReconnectAttempts) {
192
- this._reconnectAttempts = 0;
193
- this._shouldReconnect = false;
357
+ // Session sync
358
+ if (data?.type === 'session_sync' && data.session) {
359
+ this._currentSession = data.session;
360
+ }
361
+ if (data?.type === 'RECONNECTED') {
362
+ // Server confirmed reconnect — events will follow
363
+ }
364
+ if (data?.type === 'SESSION_MERGED' && data.server_newer) {
365
+ // Server had newer session
366
+ }
367
+ if (data?.type === 'mode_changed' && data.mode) {
368
+ if (!this._currentSession) {
369
+ this._currentSession = { mode: data.mode };
370
+ }
371
+ else {
372
+ this._currentSession.mode = data.mode;
373
+ }
374
+ }
375
+ // ULW turns reached
376
+ if (data?.type === 'ulw_turns_reached') {
377
+ this._status = 'waiting';
378
+ if (this._currentSession) {
379
+ this._currentSession.ulw_turns_used = data.turns_used;
380
+ }
381
+ this._addChatItem({
382
+ type: 'ulw_turns_reached',
383
+ turns_used: data.turns_used,
384
+ max_turns: data.max_turns,
385
+ });
386
+ }
387
+ // Stream events → ChatItem mapping
388
+ if (data?.type === 'llm_call' || data?.type === 'llm_result' ||
389
+ data?.type === 'tool_call' || data?.type === 'tool_result' ||
390
+ data?.type === 'thinking' || data?.type === 'assistant' ||
391
+ data?.type === 'intent' || data?.type === 'eval' || data?.type === 'compact' ||
392
+ data?.type === 'tool_blocked') {
393
+ this._clearPlaceholder();
394
+ (0, chat_item_mapper_1.mapEventToChatItem)(this._chatItems, data, (item) => this._addChatItem(item));
395
+ if (data.session) {
396
+ this._currentSession = data.session;
397
+ }
398
+ }
399
+ // Interactive events
400
+ if (data?.type === 'ask_user') {
401
+ this._status = 'waiting';
402
+ this._addChatItem({ type: 'ask_user', text: data.text || '', options: data.options || [], multi_select: data.multi_select || false });
403
+ }
404
+ if (data?.type === 'approval_needed') {
405
+ this._status = 'waiting';
406
+ this._addChatItem({
407
+ type: 'approval_needed',
408
+ tool: data.tool,
409
+ arguments: data.arguments,
410
+ ...(data.description && { description: data.description }),
411
+ ...(data.batch_remaining && { batch_remaining: data.batch_remaining }),
412
+ });
413
+ }
414
+ if (data?.type === 'plan_review') {
415
+ this._status = 'waiting';
416
+ this._addChatItem({ type: 'plan_review', plan_content: data.plan_content });
417
+ }
418
+ // Onboard flow
419
+ if (data?.type === 'ONBOARD_REQUIRED') {
420
+ this._status = 'waiting';
421
+ this._pendingRetry = { prompt: data.prompt || '', inputId: (0, endpoint_1.generateUUID)() };
422
+ this._addChatItem({
423
+ type: 'onboard_required',
424
+ methods: (data.methods || []),
425
+ paymentAmount: data.payment_amount,
426
+ });
427
+ }
428
+ if (data?.type === 'ONBOARD_SUCCESS') {
429
+ this._addChatItem({
430
+ type: 'onboard_success',
431
+ level: data.level,
432
+ message: data.message,
433
+ });
434
+ if (this._pendingRetry && this._ws) {
435
+ this._status = 'working';
436
+ const retry = this._pendingRetry;
437
+ this._pendingRetry = null;
438
+ const isDirect = this._isDirect();
439
+ const msg = { type: 'INPUT', input_id: retry.inputId, prompt: retry.prompt };
440
+ if (!isDirect)
441
+ msg.to = this.address;
442
+ this._ws.send(JSON.stringify(msg));
443
+ }
444
+ }
445
+ // OUTPUT — resolve input() promise
446
+ if (data?.type === 'OUTPUT') {
447
+ this._clearPlaceholder();
448
+ this._status = 'idle';
449
+ if (data.session) {
450
+ this._currentSession = data.session;
451
+ }
452
+ if (data.server_newer && data.chat_items && Array.isArray(data.chat_items)) {
453
+ const userItems = this._chatItems.filter(item => item.type === 'user');
454
+ const serverNonUserItems = data.chat_items.filter(item => item.type !== 'user');
455
+ this._chatItems = [...userItems, ...serverNonUserItems];
456
+ }
457
+ const result = data.result || '';
458
+ if (result) {
459
+ const lastAgent = this._chatItems.filter((e) => e.type === 'agent').pop();
460
+ if (!lastAgent || lastAgent.content !== result) {
461
+ this._addChatItem({ type: 'agent', content: result });
462
+ }
463
+ }
464
+ // Don't close WS — keep it for next input()
465
+ const resolve = this._inputResolve;
466
+ this._settleInput();
467
+ resolve?.({ text: result, done: true });
468
+ }
469
+ // ERROR — reject input() promise
470
+ if (data?.type === 'ERROR') {
471
+ this._status = 'idle';
194
472
  this._connectionState = 'disconnected';
195
- reject(new Error('Max reconnection attempts reached'));
473
+ this._closeWs();
474
+ const reject = this._inputReject;
475
+ this._settleInput();
476
+ reject?.(new Error(`Agent error: ${String(data.message || data.error || 'Unknown error')}`));
477
+ }
478
+ this._onMessage?.();
479
+ }
480
+ _handleConnectionLoss() {
481
+ this._ws = null;
482
+ this._authenticated = false;
483
+ this._stopPingMonitor();
484
+ // Reject pending connect
485
+ if (this._connectReject) {
486
+ const reject = this._connectReject;
487
+ this._connectResolve = null;
488
+ this._connectReject = null;
489
+ reject(new Error('Connection lost during authentication'));
196
490
  return;
197
491
  }
198
- this._connectionState = 'reconnecting';
199
- this._reconnectAttempts++;
200
- const delay = Math.min(this._reconnectBaseDelay * Math.pow(2, this._reconnectAttempts - 1), 30000);
201
- console.log(`[ConnectOnion] Connection lost. Reconnecting in ${delay}ms (attempt ${this._reconnectAttempts}/${this._maxReconnectAttempts})...`);
202
- setTimeout(() => this._reconnect(resolve, reject), delay);
492
+ // Reject pending input only if there is one
493
+ if (this._inputReject) {
494
+ this._status = 'idle';
495
+ this._connectionState = 'disconnected';
496
+ const reject = this._inputReject;
497
+ this._settleInput();
498
+ reject(new Error('Connection closed before response'));
499
+ this._onMessage?.();
500
+ }
203
501
  }
204
- _sendInput(ws, inputId, prompt, sessionId, isDirect, images) {
205
- const payload = { prompt, timestamp: Math.floor(Date.now() / 1000) };
206
- payload.to = this.address;
207
- const signed = (0, auth_1.signPayload)(this._keys, payload);
208
- const msg = { type: 'INPUT', input_id: inputId, prompt, ...signed };
209
- if (images?.length)
210
- msg.images = images;
211
- if (!isDirect)
212
- msg.to = this.address;
213
- msg.session = this._currentSession
214
- ? { ...this._currentSession, session_id: sessionId }
215
- : { session_id: sessionId };
216
- console.log(`[ConnectOnion] Sending INPUT via ${isDirect ? 'direct' : 'relay'}, from: ${signed.from?.slice(0, 12)}...`);
217
- ws.send(JSON.stringify(msg));
502
+ _settleInput() {
503
+ if (this._inputTimer) {
504
+ clearTimeout(this._inputTimer);
505
+ this._inputTimer = null;
506
+ }
507
+ this._inputResolve = null;
508
+ this._inputReject = null;
218
509
  }
219
- // --- Private: connection lifecycle ---
220
- _startPingMonitor(ws, reject) {
510
+ _closeWs() {
511
+ this._stopPingMonitor();
512
+ if (this._ws) {
513
+ // Prevent close handler from firing during intentional close
514
+ this._ws.onerror = null;
515
+ this._ws.onclose = null;
516
+ this._ws.onmessage = null;
517
+ this._ws.close();
518
+ this._ws = null;
519
+ }
520
+ this._authenticated = false;
521
+ this._connectionState = 'disconnected';
522
+ }
523
+ _startPingMonitor() {
221
524
  this._stopPingMonitor();
222
- this._healthCheckInterval = setInterval(() => {
525
+ this._pingTimer = setInterval(() => {
223
526
  if (Date.now() - this._lastPingTime > 60000) {
224
527
  this._stopPingMonitor();
225
- ws.close();
226
- reject(new Error('Connection health check failed: No PING received for 60 seconds'));
528
+ this._ws?.close();
227
529
  }
228
530
  }, 10000);
229
531
  }
532
+ _stopPingMonitor() {
533
+ if (this._pingTimer) {
534
+ clearInterval(this._pingTimer);
535
+ this._pingTimer = null;
536
+ }
537
+ }
538
+ _isDirect() {
539
+ return !!this._directUrl || !!this._resolvedEndpoint;
540
+ }
230
541
  _resolveWsUrl() {
231
542
  if (this._directUrl) {
232
543
  const base = this._directUrl.replace(/^https?:\/\//, '');
@@ -247,75 +558,5 @@ class RemoteAgent {
247
558
  if (resolved)
248
559
  this._resolvedEndpoint = resolved;
249
560
  }
250
- async _connectAndSend(prompt, timeoutMs, images) {
251
- this._keys = (0, auth_1.ensureKeys)(this._keys);
252
- await this._resolveEndpointOnce();
253
- this._pendingPrompt = prompt;
254
- this._addChatItem({ type: 'user', content: prompt, images });
255
- this._addChatItem({ type: 'thinking', id: '__optimistic__', status: 'running' });
256
- this._status = 'working';
257
- const inputId = (0, endpoint_1.generateUUID)();
258
- const sessionId = this._currentSession?.session_id || (0, endpoint_1.generateUUID)();
259
- const { wsUrl, isDirect } = this._resolveWsUrl();
260
- const ws = new this._WS(wsUrl);
261
- this._activeWs = ws;
262
- this._shouldReconnect = true;
263
- return new Promise((resolve, reject) => {
264
- const state = {
265
- settled: false,
266
- timer: setTimeout(() => {
267
- if (!state.settled) {
268
- state.settled = true;
269
- this._status = 'idle';
270
- this._shouldReconnect = false;
271
- this._connectionState = 'disconnected';
272
- ws.close();
273
- reject(new Error('Connection timed out'));
274
- }
275
- }, timeoutMs),
276
- };
277
- ws.onopen = () => {
278
- this._connectionState = 'connected';
279
- this._lastPingTime = Date.now();
280
- this._startPingMonitor(ws, reject);
281
- this._sendInput(ws, inputId, prompt, sessionId, isDirect, images);
282
- };
283
- (0, ws_handlers_1.attachWsHandlers)(this, ws, inputId, isDirect, state, resolve, reject);
284
- });
285
- }
286
- _reconnect(resolve, reject) {
287
- const sessionId = this._currentSession?.session_id;
288
- if (!sessionId) {
289
- reject(new Error('No session to reconnect'));
290
- return;
291
- }
292
- const { wsUrl, isDirect } = this._resolveWsUrl();
293
- const ws = new this._WS(wsUrl);
294
- this._activeWs = ws;
295
- this._status = 'working';
296
- const state = {
297
- settled: false,
298
- timer: setTimeout(() => {
299
- if (!state.settled) {
300
- state.settled = true;
301
- this._status = 'idle';
302
- ws.close();
303
- this._attemptReconnect(resolve, reject);
304
- }
305
- }, 600000),
306
- };
307
- ws.onopen = () => {
308
- console.log('[ConnectOnion] Reconnected successfully');
309
- this._connectionState = 'connected';
310
- this._lastPingTime = Date.now();
311
- this._startPingMonitor(ws, reject);
312
- this._sendInput(ws, (0, endpoint_1.generateUUID)(), this._pendingPrompt || '', sessionId, isDirect);
313
- };
314
- (0, ws_handlers_1.attachWsHandlers)(this, ws, '', isDirect, state, resolve, reject);
315
- }
316
- toString() {
317
- const short = this.address.length > 12 ? this.address.slice(0, 12) + '...' : this.address;
318
- return `RemoteAgent(${short})`;
319
- }
320
561
  }
321
562
  exports.RemoteAgent = RemoteAgent;
@@ -35,16 +35,9 @@ export interface UseAgentForHumanReturn {
35
35
  * The caller decides when and how often to invoke this — no built-in interval.
36
36
  *
37
37
  * @param sessionId - Session UUID to probe
38
- * @returns 'running' | 'suspended' | 'completed' | 'not_found'
38
+ * @returns 'executing' | 'suspended' | 'connected' | 'not_found'
39
39
  */
40
- checkSessionStatus: (sessionId: string) => Promise<'running' | 'suspended' | 'completed' | 'not_found'>;
41
- /**
42
- * Lightweight HTTP check for the current session (no WebSocket relay required).
43
- * Prefer this over `checkSessionStatus` when you only need a quick alive/dead signal.
44
- *
45
- * @returns 'running' | 'done' | 'not_found'
46
- */
47
- checkSession: () => Promise<'running' | 'done' | 'not_found'>;
40
+ checkSessionStatus: (sessionId: string) => Promise<'executing' | 'suspended' | 'connected' | 'not_found'>;
48
41
  /** Current approval mode. Defaults to 'safe' when no session exists yet. */
49
42
  mode: ApprovalMode;
50
43
  /** Maximum turns before ULW mode pauses. null when mode is not 'ulw'. */
@@ -1 +1 @@
1
- {"version":3,"file":"use-agent-for-human.d.ts","sourceRoot":"","sources":["../../src/react/use-agent-for-human.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,QAAQ,EACR,WAAW,EACX,eAAe,EAEf,YAAY,EACZ,eAAe,EAChB,MAAM,YAAY,CAAC;AAGpB;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACrC,oEAAoE;IACpE,MAAM,EAAE,WAAW,CAAC;IAEpB;;;OAGG;IACH,eAAe,EAAE,eAAe,CAAC;IAEjC;;;;OAIG;IACH,EAAE,EAAE,QAAQ,EAAE,CAAC;IAEf,2FAA2F;IAC3F,SAAS,EAAE,MAAM,CAAC;IAElB,4EAA4E;IAC5E,YAAY,EAAE,OAAO,CAAC;IAEtB,+FAA+F;IAC/F,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAEpB;;;;;;OAMG;IACH,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC;IAExG;;;;;OAKG;IACH,YAAY,EAAE,MAAM,OAAO,CAAC,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;IAE9D,4EAA4E;IAC5E,IAAI,EAAE,YAAY,CAAC;IAEnB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,oFAAoF;IACpF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;;;;;;OAOG;IACH,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,KAAK,IAAI,CAAC;IAEjE;;;;OAIG;IACH,WAAW,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAEhD,qFAAqF;IACrF,WAAW,EAAE,CAAC,OAAO,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,eAAe,CAAC;IAErF;;;;;;;OAOG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAEpE,8DAA8D;IAC9D,SAAS,EAAE,MAAM,IAAI,CAAC;IAEtB,gFAAgF;IAChF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,sBAAsB,CAwJxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,EACvD,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,CAAC,GACN,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,CAExC;AAED,6CAA6C;AAC7C,eAAO,MAAM,WAAW,uBAAiB,CAAC"}
1
+ {"version":3,"file":"use-agent-for-human.d.ts","sourceRoot":"","sources":["../../src/react/use-agent-for-human.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,QAAQ,EACR,WAAW,EACX,eAAe,EAEf,YAAY,EACZ,eAAe,EAChB,MAAM,YAAY,CAAC;AAGpB;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACrC,oEAAoE;IACpE,MAAM,EAAE,WAAW,CAAC;IAEpB;;;OAGG;IACH,eAAe,EAAE,eAAe,CAAC;IAEjC;;;;OAIG;IACH,EAAE,EAAE,QAAQ,EAAE,CAAC;IAEf,2FAA2F;IAC3F,SAAS,EAAE,MAAM,CAAC;IAElB,4EAA4E;IAC5E,YAAY,EAAE,OAAO,CAAC;IAEtB,+FAA+F;IAC/F,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAEpB;;;;;;OAMG;IACH,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC;IAG1G,4EAA4E;IAC5E,IAAI,EAAE,YAAY,CAAC;IAEnB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,oFAAoF;IACpF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;;;;;;OAOG;IACH,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,KAAK,IAAI,CAAC;IAEjE;;;;OAIG;IACH,WAAW,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAEhD,qFAAqF;IACrF,WAAW,EAAE,CAAC,OAAO,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,eAAe,CAAC;IAErF;;;;;;;OAOG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAEpE,8DAA8D;IAC9D,SAAS,EAAE,MAAM,IAAI,CAAC;IAEtB,gFAAgF;IAChF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,sBAAsB,CAwJxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,EACvD,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,CAAC,GACN,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,CAExC;AAED,6CAA6C;AAC7C,eAAO,MAAM,WAAW,uBAAiB,CAAC"}
@@ -98,19 +98,19 @@ function useAgentForHuman(address, sessionId) {
98
98
  // eslint-disable-next-line react-hooks/exhaustive-deps
99
99
  }, [agent]);
100
100
  // Restore persisted session into the RemoteAgent on mount or when sessionId changes.
101
- // The agent holds session state in memory only; without this restore the server would
102
- // receive a sessionless request and start a brand-new conversation instead of resuming.
101
+ // Then auto-reconnect to sync with server (get newer data, resume executing agent, etc.)
103
102
  (0, react_1.useEffect)(() => {
104
103
  if (session) {
105
104
  agent._currentSession = { ...session, session_id: sessionId };
106
105
  agent._chatItems = [...ui];
107
106
  }
108
107
  else if (messages.length > 0) {
109
- // No full session snapshot yet, but we have raw messages — build a minimal session
110
- // so the server can reconstruct conversation history.
111
108
  agent._currentSession = { session_id: sessionId, messages };
112
109
  agent._chatItems = [...ui];
113
110
  }
111
+ // No auto-reconnect on mount. Show cached conversation from localStorage.
112
+ // When user sends next message, input() → _ensureConnected() → CONNECT
113
+ // will sync with server (session merge, server_newer, etc.).
114
114
  }, [sessionId]);
115
115
  const input = (prompt, options) => {
116
116
  setError(null);
@@ -172,7 +172,6 @@ function useAgentForHuman(address, sessionId) {
172
172
  isProcessing: status !== 'idle',
173
173
  error,
174
174
  checkSessionStatus: (sid) => agent.checkSessionStatus(sid),
175
- checkSession: () => agent.checkSession(sessionId),
176
175
  mode: session?.mode || 'safe',
177
176
  ulwTurns: session?.ulw_turns ?? null,
178
177
  ulwTurnsUsed: session?.ulw_turns_used ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectonion",
3
- "version": "0.0.20",
3
+ "version": "0.1.0",
4
4
  "description": "Connect to Python AI agents from TypeScript apps - Use powerful Python agents in your React, Next.js, Node.js, and Electron applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,14 +0,0 @@
1
- /**
2
- * @llm-note
3
- * Dependencies: imports from [src/connect/types, src/connect/stream-events, src/connect/endpoint, src/connect/remote-agent (type-only)]
4
- * Data flow: onmessage parses JSON → dispatches by type → mutates agent fields → resolves/rejects the input() Promise
5
- * State/Effects: mutates RemoteAgent fields (session, status, chatItems, connection state) via _ prefix access
6
- * Integration: attachWsHandlers() wires onmessage/onerror/onclose; called by RemoteAgent._connectAndSend and _reconnect
7
- */
8
- import type { RemoteAgent } from './remote-agent';
9
- import { Response, WebSocketLike } from './types';
10
- export declare function attachWsHandlers(agent: RemoteAgent, ws: WebSocketLike, inputId: string, isDirect: boolean, state: {
11
- settled: boolean;
12
- timer: ReturnType<typeof setTimeout>;
13
- }, resolve: (value: Response) => void, reject: (reason?: unknown) => void): void;
14
- //# sourceMappingURL=ws-handlers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ws-handlers.d.ts","sourceRoot":"","sources":["../../src/connect/ws-handlers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,EAAY,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE5D,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,aAAa,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;CAAE,EACjE,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,EAClC,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,GACjC,IAAI,CA+MN"}
@@ -1,188 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.attachWsHandlers = attachWsHandlers;
4
- const chat_item_mapper_1 = require("./chat-item-mapper");
5
- const endpoint_1 = require("./endpoint");
6
- function attachWsHandlers(agent, ws, inputId, isDirect, state, resolve, reject) {
7
- ws.onmessage = (evt) => {
8
- if (state.settled)
9
- return;
10
- const raw = typeof evt.data === 'string' ? evt.data : String(evt.data);
11
- const data = JSON.parse(raw);
12
- if (data?.type === 'PING') {
13
- agent._lastPingTime = Date.now();
14
- ws.send(JSON.stringify({ type: 'PONG' }));
15
- return;
16
- }
17
- if (data?.type === 'session_sync' && data.session) {
18
- agent._currentSession = data.session;
19
- }
20
- if (data?.type === 'RECONNECTED') {
21
- console.log('[RemoteAgent] Reconnected to session:', data.session_id);
22
- }
23
- if (data?.type === 'SESSION_MERGED' && data.server_newer) {
24
- console.log('[RemoteAgent] Server had newer session, merged');
25
- }
26
- if (data?.type === 'mode_changed' && data.mode) {
27
- if (!agent._currentSession) {
28
- agent._currentSession = { mode: data.mode };
29
- }
30
- else {
31
- agent._currentSession.mode = data.mode;
32
- }
33
- }
34
- if (data?.type === 'ulw_turns_reached') {
35
- agent._status = 'waiting';
36
- if (agent._currentSession) {
37
- agent._currentSession.ulw_turns_used = data.turns_used;
38
- }
39
- agent._addChatItem({
40
- type: 'ulw_turns_reached',
41
- turns_used: data.turns_used,
42
- max_turns: data.max_turns,
43
- });
44
- }
45
- if (data?.type === 'llm_call' || data?.type === 'llm_result' ||
46
- data?.type === 'tool_call' || data?.type === 'tool_result' ||
47
- data?.type === 'thinking' || data?.type === 'assistant' ||
48
- data?.type === 'intent' || data?.type === 'eval' || data?.type === 'compact' ||
49
- data?.type === 'tool_blocked') {
50
- agent._clearPlaceholder();
51
- (0, chat_item_mapper_1.mapEventToChatItem)(agent._chatItems, data, (item) => agent._addChatItem(item));
52
- if (data.session) {
53
- agent._currentSession = data.session;
54
- }
55
- }
56
- if (data?.type === 'ask_user') {
57
- agent._status = 'waiting';
58
- agent._addChatItem({ type: 'ask_user', text: data.text || '', options: data.options || [], multi_select: data.multi_select || false });
59
- }
60
- if (data?.type === 'approval_needed') {
61
- agent._status = 'waiting';
62
- agent._addChatItem({
63
- type: 'approval_needed',
64
- tool: data.tool,
65
- arguments: data.arguments,
66
- ...(data.description && { description: data.description }),
67
- ...(data.batch_remaining && { batch_remaining: data.batch_remaining }),
68
- });
69
- }
70
- if (data?.type === 'plan_review') {
71
- agent._status = 'waiting';
72
- agent._addChatItem({
73
- type: 'plan_review',
74
- plan_content: data.plan_content,
75
- });
76
- }
77
- if (data?.type === 'ONBOARD_REQUIRED') {
78
- agent._status = 'waiting';
79
- agent._pendingPrompt = data.prompt || '';
80
- agent._pendingInputId = inputId;
81
- agent._pendingSessionId = agent._currentSession?.session_id || null;
82
- agent._addChatItem({
83
- type: 'onboard_required',
84
- methods: (data.methods || []),
85
- paymentAmount: data.payment_amount,
86
- });
87
- }
88
- if (data?.type === 'ONBOARD_SUCCESS') {
89
- agent._addChatItem({
90
- type: 'onboard_success',
91
- level: data.level,
92
- message: data.message,
93
- });
94
- if (agent._pendingPrompt && agent._activeWs) {
95
- agent._status = 'working';
96
- const retryPrompt = agent._pendingPrompt;
97
- const retryInputId = agent._pendingInputId || (0, endpoint_1.generateUUID)();
98
- agent._pendingPrompt = null;
99
- agent._pendingInputId = null;
100
- agent._sendInput(agent._activeWs, retryInputId, retryPrompt, agent._pendingSessionId || (0, endpoint_1.generateUUID)(), isDirect);
101
- agent._pendingSessionId = null;
102
- }
103
- }
104
- if (data?.type === 'OUTPUT') {
105
- state.settled = true;
106
- clearTimeout(state.timer);
107
- agent._stopPingMonitor();
108
- agent._clearPlaceholder();
109
- agent._status = 'idle';
110
- agent._shouldReconnect = false;
111
- agent._connectionState = 'disconnected';
112
- agent._reconnectAttempts = 0;
113
- if (data.session) {
114
- agent._currentSession = data.session;
115
- }
116
- if (data.server_newer && data.chat_items && Array.isArray(data.chat_items)) {
117
- console.log('[RemoteAgent] Session was merged with newer server state');
118
- const userItems = agent._chatItems.filter(item => item.type === 'user');
119
- const serverNonUserItems = data.chat_items.filter(item => item.type !== 'user');
120
- agent._chatItems = [...userItems, ...serverNonUserItems];
121
- }
122
- const result = data.result || '';
123
- if (result) {
124
- const lastAgent = agent._chatItems.filter((e) => e.type === 'agent').pop();
125
- if (!lastAgent || lastAgent.content !== result) {
126
- agent._addChatItem({ type: 'agent', content: result });
127
- }
128
- }
129
- agent._activeWs = null;
130
- ws.close();
131
- resolve({ text: result, done: true });
132
- }
133
- if (data?.type === 'ERROR') {
134
- state.settled = true;
135
- clearTimeout(state.timer);
136
- agent._stopPingMonitor();
137
- agent._status = 'idle';
138
- agent._shouldReconnect = false;
139
- agent._connectionState = 'disconnected';
140
- agent._activeWs = null;
141
- ws.close();
142
- reject(new Error(`Agent error: ${String(data.message || data.error || 'Unknown error')}`));
143
- }
144
- agent._onMessage?.();
145
- };
146
- ws.onerror = async (err) => {
147
- if (state.settled)
148
- return;
149
- agent._stopPingMonitor();
150
- clearTimeout(state.timer);
151
- ws.close();
152
- if (isDirect && !agent._directUrl) {
153
- agent._resolvedEndpoint = undefined;
154
- agent._endpointResolutionAttempted = false;
155
- }
156
- if (agent._shouldReconnect && agent._reconnectAttempts < agent._maxReconnectAttempts) {
157
- agent._attemptReconnect(resolve, reject);
158
- }
159
- else {
160
- state.settled = true;
161
- agent._status = 'idle';
162
- agent._shouldReconnect = false;
163
- agent._connectionState = 'disconnected';
164
- reject(new Error(`WebSocket error: ${String(err)}`));
165
- }
166
- };
167
- ws.onclose = async () => {
168
- agent._activeWs = null;
169
- agent._stopPingMonitor();
170
- if (!state.settled) {
171
- clearTimeout(state.timer);
172
- if (isDirect && !agent._directUrl) {
173
- agent._resolvedEndpoint = undefined;
174
- agent._endpointResolutionAttempted = false;
175
- }
176
- if (agent._shouldReconnect && agent._reconnectAttempts < agent._maxReconnectAttempts) {
177
- agent._attemptReconnect(resolve, reject);
178
- }
179
- else {
180
- state.settled = true;
181
- agent._status = 'idle';
182
- agent._shouldReconnect = false;
183
- agent._connectionState = 'disconnected';
184
- reject(new Error('Connection closed before response'));
185
- }
186
- }
187
- };
188
- }