omnara 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/bin/omnara.js +8 -3
  2. package/dist/auth/auth-provider.d.ts +2 -2
  3. package/dist/auth/auth-provider.js +13 -15
  4. package/dist/auth/auth-provider.js.map +1 -1
  5. package/dist/auth/cli-auth-service.d.ts +1 -1
  6. package/dist/auth/cli-auth-service.js +3 -6
  7. package/dist/auth/cli-auth-service.js.map +1 -1
  8. package/dist/auth/credential-manager.d.ts +1 -1
  9. package/dist/auth/credential-manager.js +7 -9
  10. package/dist/auth/credential-manager.js.map +1 -1
  11. package/dist/auth/credentials-store.js +14 -20
  12. package/dist/auth/credentials-store.js.map +1 -1
  13. package/dist/auth/login-flow.d.ts +2 -2
  14. package/dist/auth/login-flow.js +23 -29
  15. package/dist/auth/login-flow.js.map +1 -1
  16. package/dist/auth/loopback-listener.js +11 -15
  17. package/dist/auth/loopback-listener.js.map +1 -1
  18. package/dist/auth/pkce.js +4 -7
  19. package/dist/auth/pkce.js.map +1 -1
  20. package/dist/auth/supabase-client.d.ts +1 -1
  21. package/dist/auth/supabase-client.js +3 -6
  22. package/dist/auth/supabase-client.js.map +1 -1
  23. package/dist/auth/supabase-config.js +3 -6
  24. package/dist/auth/supabase-config.js.map +1 -1
  25. package/dist/cli.js +26 -25
  26. package/dist/cli.js.map +1 -1
  27. package/dist/commands/agent.js +36 -38
  28. package/dist/commands/agent.js.map +1 -1
  29. package/dist/commands/auth.js +13 -16
  30. package/dist/commands/auth.js.map +1 -1
  31. package/dist/commands/index.d.ts +2 -2
  32. package/dist/commands/index.js +2 -8
  33. package/dist/commands/index.js.map +1 -1
  34. package/dist/http/api-client.js +7 -11
  35. package/dist/http/api-client.js.map +1 -1
  36. package/dist/http/user-sessions.d.ts +13 -0
  37. package/dist/http/user-sessions.js +9 -0
  38. package/dist/http/user-sessions.js.map +1 -0
  39. package/dist/orchestrator/agent-orchestrator.d.ts +11 -6
  40. package/dist/orchestrator/agent-orchestrator.js +226 -57
  41. package/dist/orchestrator/agent-orchestrator.js.map +1 -1
  42. package/dist/orchestrator/ai-client.d.ts +5 -4
  43. package/dist/orchestrator/ai-client.js +17 -14
  44. package/dist/orchestrator/ai-client.js.map +1 -1
  45. package/dist/orchestrator/orchestrator-prompt.d.ts +2 -7
  46. package/dist/orchestrator/orchestrator-prompt.js +7 -154
  47. package/dist/orchestrator/orchestrator-prompt.js.map +1 -1
  48. package/dist/orchestrator/orchestrator-prompt.md +405 -0
  49. package/dist/orchestrator/session.d.ts +7 -3
  50. package/dist/orchestrator/session.js +56 -34
  51. package/dist/orchestrator/session.js.map +1 -1
  52. package/dist/orchestrator/tools/worker-status-tool.d.ts +7 -0
  53. package/dist/orchestrator/tools/worker-status-tool.js +41 -0
  54. package/dist/orchestrator/tools/worker-status-tool.js.map +1 -0
  55. package/dist/orchestrator/tools/worker-tools.d.ts +1 -1
  56. package/dist/orchestrator/tools/worker-tools.js +73 -89
  57. package/dist/orchestrator/tools/worker-tools.js.map +1 -1
  58. package/dist/orchestrator/types.d.ts +19 -29
  59. package/dist/orchestrator/types.js +22 -25
  60. package/dist/orchestrator/types.js.map +1 -1
  61. package/dist/orchestrator/utils/async-queue.d.ts +26 -0
  62. package/dist/orchestrator/utils/async-queue.js +70 -0
  63. package/dist/orchestrator/utils/async-queue.js.map +1 -0
  64. package/dist/orchestrator/utils/session-logger.d.ts +1 -1
  65. package/dist/orchestrator/utils/session-logger.js +8 -14
  66. package/dist/orchestrator/utils/session-logger.js.map +1 -1
  67. package/dist/orchestrator/worker-manager.d.ts +31 -15
  68. package/dist/orchestrator/worker-manager.js +186 -18
  69. package/dist/orchestrator/worker-manager.js.map +1 -1
  70. package/dist/relay/relay-bridge.d.ts +24 -3
  71. package/dist/relay/relay-bridge.js +382 -90
  72. package/dist/relay/relay-bridge.js.map +1 -1
  73. package/dist/relay/relay-service.d.ts +4 -7
  74. package/dist/relay/relay-service.js +5 -11
  75. package/dist/relay/relay-service.js.map +1 -1
  76. package/dist/repl/terminal-repl.js +34 -30
  77. package/dist/repl/terminal-repl.js.map +1 -1
  78. package/dist/schemas/generated.d.ts +244 -0
  79. package/dist/schemas/generated.js +5 -0
  80. package/dist/schemas/generated.js.map +1 -0
  81. package/dist/schemas/index.d.ts +2 -0
  82. package/dist/schemas/index.js +2 -0
  83. package/dist/schemas/index.js.map +1 -0
  84. package/dist/scripts/generate-schema-types.js +53 -0
  85. package/dist/scripts/generate-schema-types.js.map +1 -0
  86. package/dist/scripts/sync-schemas.d.ts +1 -0
  87. package/dist/scripts/sync-schemas.js +28 -0
  88. package/dist/scripts/sync-schemas.js.map +1 -0
  89. package/dist/utils/prompt.js +4 -10
  90. package/dist/utils/prompt.js.map +1 -1
  91. package/dist/voice/voice-service.d.ts +1 -1
  92. package/dist/voice/voice-service.js +2 -4
  93. package/dist/voice/voice-service.js.map +1 -1
  94. package/package.json +6 -2
  95. package/dist/commands/hello.d.ts +0 -2
  96. package/dist/commands/hello.js +0 -12
  97. package/dist/commands/hello.js.map +0 -1
  98. package/dist/generated/relay.d.ts +0 -240
  99. package/dist/generated/relay.js +0 -2376
  100. package/dist/generated/relay.js.map +0 -1
  101. package/dist/messaging/proto-codec.d.ts +0 -5
  102. package/dist/messaging/proto-codec.js +0 -387
  103. package/dist/messaging/proto-codec.js.map +0 -1
  104. package/dist/server/agent-server.d.ts +0 -46
  105. package/dist/server/agent-server.js +0 -429
  106. package/dist/server/agent-server.js.map +0 -1
  107. package/dist/server/ai-client.d.ts +0 -16
  108. package/dist/server/ai-client.js +0 -74
  109. package/dist/server/ai-client.js.map +0 -1
  110. package/dist/server/session.d.ts +0 -35
  111. package/dist/server/session.js +0 -272
  112. package/dist/server/session.js.map +0 -1
  113. package/dist/server/subagents.d.ts +0 -19
  114. package/dist/server/subagents.js +0 -42
  115. package/dist/server/subagents.js.map +0 -1
  116. package/dist/server/tools/worker-tools.d.ts +0 -2
  117. package/dist/server/tools/worker-tools.js +0 -37
  118. package/dist/server/tools/worker-tools.js.map +0 -1
  119. package/dist/server/types.d.ts +0 -120
  120. package/dist/server/types.js +0 -29
  121. package/dist/server/types.js.map +0 -1
  122. package/dist/server/utils/session-logger.d.ts +0 -7
  123. package/dist/server/utils/session-logger.js +0 -53
  124. package/dist/server/utils/session-logger.js.map +0 -1
  125. package/dist/server/worker-manager.d.ts +0 -67
  126. package/dist/server/worker-manager.js +0 -207
  127. package/dist/server/worker-manager.js.map +0 -1
  128. package/dist/test-agent.js +0 -63
  129. package/dist/test-agent.js.map +0 -1
  130. package/dist/utils/base64url.d.ts +0 -1
  131. package/dist/utils/base64url.js +0 -12
  132. package/dist/utils/base64url.js.map +0 -1
  133. package/dist/utils/random.d.ts +0 -1
  134. package/dist/utils/random.js +0 -9
  135. package/dist/utils/random.js.map +0 -1
  136. /package/dist/{test-agent.d.ts → scripts/generate-schema-types.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import EventEmitter from 'events';
2
- import { AgentOrchestrator } from '../orchestrator/agent-orchestrator';
3
- import { CredentialsStore } from '../auth/credentials-store';
2
+ import { AgentOrchestrator } from '../orchestrator/agent-orchestrator.js';
3
+ import { CredentialsStore } from '../auth/credentials-store.js';
4
4
  export interface RelayBridgeOptions {
5
5
  store?: CredentialsStore;
6
6
  logger?: Console;
@@ -20,15 +20,36 @@ export declare class RelayBridge extends EventEmitter {
20
20
  private pingTimer;
21
21
  private heartbeatIntervalMs;
22
22
  private authProviderPromise;
23
- private currentSessionId;
23
+ private userSessionId;
24
+ private currentAgentSessionId;
24
25
  constructor(orchestrator: AgentOrchestrator, options: RelayBridgeOptions);
25
26
  start(): Promise<void>;
26
27
  stop(): Promise<void>;
27
28
  private establishConnection;
28
29
  private getAuthProvider;
30
+ private ensureAgentSessionExists;
31
+ private ensureUserSession;
32
+ private sendPrimarySessionInit;
33
+ private sendFrame;
29
34
  private handleWebsocketLifecycle;
30
35
  private handleOutbound;
31
36
  private handleInbound;
37
+ private toRelayFrame;
38
+ private tryHandleControlMessage;
39
+ private tryHandleMessageEnvelope;
40
+ private handleSessionBootstrap;
41
+ private handleSessionInitAck;
42
+ private handleSessionAnnounce;
43
+ private handleMessageEnvelope;
44
+ private isTextContent;
45
+ private buildTextEnvelope;
46
+ private buildToolCallEnvelope;
47
+ private normalizeToolArguments;
48
+ private buildToolResultEnvelope;
49
+ private buildAgentCompleteEnvelope;
50
+ private buildProgressEnvelope;
51
+ private buildEnvelope;
52
+ private isMessageEnvelope;
32
53
  private enqueue;
33
54
  private flushQueue;
34
55
  private startPingTimer;
@@ -1,47 +1,35 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.RelayBridge = void 0;
7
- const events_1 = __importDefault(require("events"));
8
- const ws_1 = __importDefault(require("ws"));
9
- const types_1 = require("../orchestrator/types");
10
- const credentials_store_1 = require("../auth/credentials-store");
11
- const api_client_1 = require("../http/api-client");
12
- const relay_service_1 = require("./relay-service");
13
- const auth_provider_1 = require("../auth/auth-provider");
1
+ import { randomUUID } from 'crypto';
2
+ import EventEmitter from 'events';
3
+ import WebSocket from 'ws';
4
+ import { AgentOrchestrator, } from '../orchestrator/agent-orchestrator.js';
5
+ import { CredentialsStore } from '../auth/credentials-store.js';
6
+ import { ApiClient, normalizeBaseUrl } from '../http/api-client.js';
7
+ import { registerRelaySession } from './relay-service.js';
8
+ import { createAuthProvider } from '../auth/auth-provider.js';
9
+ import { createUserSession } from '../http/user-sessions.js';
10
+ import { validateControlMessage, validateMessageEnvelope, } from '../schemas/index.js';
14
11
  const MAX_QUEUE_SIZE = 500;
15
- const OUTBOUND_MESSAGE_TYPES = new Set([
16
- 'session_created',
17
- 'session_joined',
18
- 'session_init',
19
- 'session_initialized',
20
- 'user_text',
21
- 'assistant_text',
22
- 'assistant_complete',
23
- 'tool_call',
24
- 'tool_result',
25
- 'system',
26
- 'error',
27
- 'worker_update',
28
- 'pong',
29
- ]);
30
- class RelayBridge extends events_1.default {
12
+ export class RelayBridge extends EventEmitter {
13
+ orchestrator;
14
+ store;
15
+ logger;
16
+ backendUrl;
17
+ frontendUrl;
18
+ connectionHandle = null;
19
+ ws = null;
20
+ stopped = false;
21
+ pendingOutbound = [];
22
+ pingTimer = null;
23
+ heartbeatIntervalMs = 30_000;
24
+ authProviderPromise = null;
25
+ userSessionId = null;
26
+ currentAgentSessionId = null;
31
27
  constructor(orchestrator, options) {
32
28
  super();
33
- this.connectionHandle = null;
34
- this.ws = null;
35
- this.stopped = false;
36
- this.pendingOutbound = [];
37
- this.pingTimer = null;
38
- this.heartbeatIntervalMs = 30000;
39
- this.authProviderPromise = null;
40
- this.currentSessionId = null;
41
29
  this.orchestrator = orchestrator;
42
- this.store = options.store ?? new credentials_store_1.CredentialsStore();
30
+ this.store = options.store ?? new CredentialsStore();
43
31
  this.logger = options.logger ?? console;
44
- this.backendUrl = (0, api_client_1.normalizeBaseUrl)(options.backendUrl);
32
+ this.backendUrl = normalizeBaseUrl(options.backendUrl);
45
33
  this.frontendUrl = options.frontendUrl;
46
34
  }
47
35
  async start() {
@@ -49,16 +37,17 @@ class RelayBridge extends events_1.default {
49
37
  throw new Error('Relay bridge already started');
50
38
  }
51
39
  this.connectionHandle = this.orchestrator.addClient({
52
- canSend: () => this.ws?.readyState === ws_1.default.OPEN,
40
+ canSend: () => this.ws?.readyState === WebSocket.OPEN,
53
41
  send: (message) => {
54
42
  this.handleOutbound(message);
55
43
  },
56
44
  });
57
- let backoffMs = 1000;
45
+ this.ensureAgentSessionExists();
46
+ let backoffMs = 1_000;
58
47
  while (!this.stopped) {
59
48
  try {
60
49
  await this.establishConnection();
61
- backoffMs = 1000;
50
+ backoffMs = 1_000;
62
51
  }
63
52
  catch (error) {
64
53
  const aborted = isAbortError(error);
@@ -70,13 +59,13 @@ class RelayBridge extends events_1.default {
70
59
  }
71
60
  this.logger.error(`Relay connection failed: ${extractErrorMessage(error)}. Retrying in ${Math.round(backoffMs / 1000)}s.`);
72
61
  await delay(backoffMs);
73
- backoffMs = Math.min(backoffMs * 2, 30000);
62
+ backoffMs = Math.min(backoffMs * 2, 30_000);
74
63
  }
75
64
  }
76
65
  }
77
66
  async stop() {
78
67
  this.stopped = true;
79
- this.currentSessionId = null;
68
+ this.currentAgentSessionId = null;
80
69
  if (this.pingTimer) {
81
70
  clearInterval(this.pingTimer);
82
71
  this.pingTimer = null;
@@ -85,36 +74,41 @@ class RelayBridge extends events_1.default {
85
74
  this.connectionHandle.disconnect();
86
75
  this.connectionHandle = null;
87
76
  }
88
- if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
77
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
89
78
  this.ws.close(1000, 'CLI shutting down');
90
79
  }
91
80
  this.ws = null;
92
81
  }
93
82
  async establishConnection() {
94
83
  const authProvider = await this.getAuthProvider();
95
- const client = new api_client_1.ApiClient({ baseUrl: this.backendUrl });
84
+ const client = new ApiClient({ baseUrl: this.backendUrl });
96
85
  const accessToken = await authProvider.getAccessToken();
97
- const registration = await (0, relay_service_1.registerRelaySession)(client, accessToken, this.currentSessionId ? { sessionId: this.currentSessionId } : undefined);
98
- this.currentSessionId = registration.sessionId;
99
- this.orchestrator.ensureSessionExists(this.currentSessionId);
86
+ const userSessionId = await this.ensureUserSession(client, accessToken);
87
+ const registration = await registerRelaySession(client, accessToken, {
88
+ userSessionId,
89
+ clientKind: 'cli',
90
+ });
91
+ this.userSessionId = registration.userSessionId;
92
+ const agentSessionId = this.ensureAgentSessionExists();
100
93
  const url = buildRelayWebsocketUrl(this.backendUrl, registration.relayWsPath);
101
- const ws = new ws_1.default(url, {
94
+ const ws = new WebSocket(url, {
102
95
  headers: {
103
96
  authorization: `Bearer ${accessToken}`,
104
97
  'x-relay-token': registration.relayToken,
105
98
  },
106
99
  });
107
100
  await waitForOpen(ws);
108
- this.logger.log(`Connected to relay session ${registration.sessionId}`);
101
+ this.logger.log(`Connected to relay (user_session=${registration.userSessionId}, agent_session=${agentSessionId})`);
109
102
  this.ws = ws;
110
103
  this.heartbeatIntervalMs = registration.relayHeartbeatIntervalSec * 1000;
111
104
  this.startPingTimer();
105
+ await this.sendPrimarySessionInit(ws);
112
106
  this.flushQueue();
113
107
  await this.handleWebsocketLifecycle(ws);
114
108
  }
115
109
  async getAuthProvider() {
116
110
  if (!this.authProviderPromise) {
117
- this.authProviderPromise = (0, auth_provider_1.createAuthProvider)({
111
+ this.authProviderPromise = createAuthProvider({
118
112
  store: this.store,
119
113
  backendUrl: this.backendUrl,
120
114
  frontendUrl: this.frontendUrl,
@@ -123,6 +117,64 @@ class RelayBridge extends events_1.default {
123
117
  }
124
118
  return this.authProviderPromise;
125
119
  }
120
+ ensureAgentSessionExists() {
121
+ if (!this.currentAgentSessionId) {
122
+ this.currentAgentSessionId = randomUUID();
123
+ }
124
+ this.orchestrator.ensureSessionExists(this.currentAgentSessionId);
125
+ return this.currentAgentSessionId;
126
+ }
127
+ async ensureUserSession(client, accessToken) {
128
+ if (this.userSessionId) {
129
+ return this.userSessionId;
130
+ }
131
+ const descriptor = await createUserSession(client, accessToken, {
132
+ metadata: { source: 'cli' },
133
+ });
134
+ this.userSessionId = descriptor.session_id;
135
+ this.logger.log(`Created user session ${this.userSessionId} for CLI orchestrator`);
136
+ return this.userSessionId;
137
+ }
138
+ async sendPrimarySessionInit(ws) {
139
+ if (!this.userSessionId) {
140
+ throw new Error('Cannot send session_init before user session is available.');
141
+ }
142
+ const agentSessionId = this.ensureAgentSessionExists();
143
+ const initFrame = {
144
+ type: 'session_init',
145
+ session_id: agentSessionId,
146
+ user_session_id: this.userSessionId,
147
+ session_type: 'CODE',
148
+ parent_session_id: null,
149
+ };
150
+ this.sendFrame(initFrame, ws);
151
+ }
152
+ sendFrame(frame, socket) {
153
+ if (this.isMessageEnvelope(frame)) {
154
+ if (!validateMessageEnvelope(frame)) {
155
+ this.logger.error('[RelayBridge] Message envelope validation failed before sending', {
156
+ frame: JSON.stringify(frame),
157
+ errors: validateMessageEnvelope.errors,
158
+ });
159
+ return;
160
+ }
161
+ }
162
+ const serialized = JSON.stringify(frame);
163
+ const target = socket ?? this.ws;
164
+ if (target && target.readyState === WebSocket.OPEN) {
165
+ try {
166
+ target.send(serialized);
167
+ return;
168
+ }
169
+ catch (error) {
170
+ this.logger.error(`Failed to send relay frame: ${extractErrorMessage(error)}`);
171
+ if (target === this.ws) {
172
+ this.closeCurrentSocket();
173
+ }
174
+ }
175
+ }
176
+ this.enqueue(serialized);
177
+ }
126
178
  async handleWebsocketLifecycle(ws) {
127
179
  await new Promise((resolve) => {
128
180
  const onMessage = (data) => {
@@ -160,20 +212,11 @@ class RelayBridge extends events_1.default {
160
212
  });
161
213
  }
162
214
  handleOutbound(message) {
163
- const payload = JSON.stringify(message);
164
- if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
165
- try {
166
- this.ws.send(payload);
167
- }
168
- catch (error) {
169
- this.logger.error(`Failed to send relay message: ${extractErrorMessage(error)}`);
170
- this.enqueue(payload);
171
- this.closeCurrentSocket();
172
- }
173
- }
174
- else {
175
- this.enqueue(payload);
215
+ const frame = this.toRelayFrame(message);
216
+ if (!frame) {
217
+ return;
176
218
  }
219
+ this.sendFrame(frame);
177
220
  }
178
221
  handleInbound(data) {
179
222
  let parsed;
@@ -185,16 +228,273 @@ class RelayBridge extends events_1.default {
185
228
  this.logger.error(`Received invalid JSON from relay: ${extractErrorMessage(error)}`);
186
229
  return;
187
230
  }
188
- const result = types_1.inboundMessageSchema.safeParse(parsed);
189
- if (result.success) {
190
- this.connectionHandle?.dispatch(result.data);
231
+ if (this.tryHandleControlMessage(parsed)) {
191
232
  return;
192
233
  }
193
- if (isOutboundMessage(parsed)) {
194
- // Relay echoed a message originally produced by the orchestrator; ignore.
234
+ if (this.tryHandleMessageEnvelope(parsed)) {
195
235
  return;
196
236
  }
197
- this.logger.error('Received invalid relay message payload.', parsed);
237
+ this.logger.error('Received invalid relay message payload.', { payload: parsed });
238
+ }
239
+ toRelayFrame(message) {
240
+ switch (message.type) {
241
+ case 'session_init': {
242
+ if (!this.userSessionId) {
243
+ this.logger.warn('Dropping session_init frame: user session is not initialised yet.');
244
+ return null;
245
+ }
246
+ const frame = {
247
+ type: 'session_init',
248
+ session_id: message.sessionId,
249
+ user_session_id: this.userSessionId,
250
+ session_type: 'CODE',
251
+ parent_session_id: message.parentSessionId ?? null,
252
+ };
253
+ if (message.metadata) {
254
+ frame.metadata = message.metadata;
255
+ }
256
+ return frame;
257
+ }
258
+ case 'assistant_text':
259
+ return this.buildTextEnvelope(message);
260
+ case 'tool_call':
261
+ return this.buildToolCallEnvelope(message);
262
+ case 'tool_result':
263
+ return this.buildToolResultEnvelope(message);
264
+ case 'agent_complete':
265
+ return this.buildAgentCompleteEnvelope(message);
266
+ case 'agent_progress':
267
+ return this.buildProgressEnvelope(message);
268
+ default:
269
+ return null;
270
+ }
271
+ }
272
+ tryHandleControlMessage(payload) {
273
+ if (!validateControlMessage(payload)) {
274
+ return false;
275
+ }
276
+ const control = payload;
277
+ const type = control.type;
278
+ switch (type) {
279
+ case 'session_bootstrap':
280
+ this.handleSessionBootstrap(control);
281
+ break;
282
+ case 'session_init_ack':
283
+ this.handleSessionInitAck(control);
284
+ break;
285
+ case 'session_announce':
286
+ this.handleSessionAnnounce(control);
287
+ break;
288
+ case 'session_closed':
289
+ this.logger.info(`Session closed: ${control.session_id} -> status ${control.status}`);
290
+ break;
291
+ case 'heartbeat':
292
+ if (control.mode === 'ping') {
293
+ const response = {
294
+ type: 'heartbeat',
295
+ mode: 'pong',
296
+ sent_at: new Date().toISOString(),
297
+ };
298
+ this.sendFrame(response);
299
+ }
300
+ break;
301
+ default:
302
+ this.logger.debug(`Ignoring control message ${type ?? 'unknown'}`);
303
+ }
304
+ return true;
305
+ }
306
+ tryHandleMessageEnvelope(payload) {
307
+ if (!validateMessageEnvelope(payload)) {
308
+ return false;
309
+ }
310
+ const envelope = payload;
311
+ this.handleMessageEnvelope(envelope);
312
+ return true;
313
+ }
314
+ handleSessionBootstrap(control) {
315
+ const userSessionId = control.root.session_id;
316
+ if (!this.userSessionId) {
317
+ this.userSessionId = userSessionId;
318
+ }
319
+ if (this.userSessionId !== userSessionId) {
320
+ this.logger.warn(`Bootstrap user session mismatch (expected ${this.userSessionId}, got ${userSessionId})`);
321
+ }
322
+ for (const agent of control.agent_sessions ?? []) {
323
+ this.orchestrator.ensureSessionExists(agent.session_id);
324
+ }
325
+ }
326
+ handleSessionInitAck(control) {
327
+ const sessionId = control.session_id;
328
+ if (!this.currentAgentSessionId) {
329
+ this.currentAgentSessionId = sessionId;
330
+ }
331
+ if (this.currentAgentSessionId !== sessionId) {
332
+ this.logger.warn(`Session init ack for unexpected session: ${sessionId} (current=${this.currentAgentSessionId})`);
333
+ }
334
+ }
335
+ handleSessionAnnounce(control) {
336
+ if (control.session.kind === 'agent_session') {
337
+ this.orchestrator.ensureSessionExists(control.session.session_id);
338
+ }
339
+ }
340
+ handleMessageEnvelope(envelope) {
341
+ const content = envelope.payload.content;
342
+ if (this.isTextContent(content) && envelope.sender.kind === 'user') {
343
+ this.connectionHandle?.dispatch({
344
+ type: 'chat',
345
+ sessionId: envelope.session_id,
346
+ text: content.text,
347
+ });
348
+ return;
349
+ }
350
+ const label = content.type ?? 'unknown';
351
+ this.logger.debug(`Ignoring inbound envelope with content type ${label}`);
352
+ }
353
+ isTextContent(content) {
354
+ return typeof content.text === 'string';
355
+ }
356
+ buildTextEnvelope(message) {
357
+ const content = {
358
+ type: 'text',
359
+ text: message.text,
360
+ };
361
+ const payloadMetadata = message.workerId || message.workerTask
362
+ ? {
363
+ worker_id: message.workerId,
364
+ worker_task: message.workerTask,
365
+ }
366
+ : undefined;
367
+ const senderSessionId = message.workerId ?? message.sessionId;
368
+ return this.buildEnvelope({
369
+ sessionId: message.sessionId,
370
+ senderSessionId,
371
+ content,
372
+ payloadMetadata,
373
+ messageId: message.messageId,
374
+ });
375
+ }
376
+ buildToolCallEnvelope(message) {
377
+ const args = this.normalizeToolArguments(message.input);
378
+ const content = {
379
+ type: 'tool_call',
380
+ name: message.toolName,
381
+ tool_use_id: message.toolUseId,
382
+ arguments: args,
383
+ };
384
+ const payloadMetadata = message.workerId || message.workerTask
385
+ ? {
386
+ worker_id: message.workerId,
387
+ worker_task: message.workerTask,
388
+ }
389
+ : undefined;
390
+ const senderSessionId = message.workerId ?? message.sessionId;
391
+ return this.buildEnvelope({
392
+ sessionId: message.sessionId,
393
+ senderSessionId,
394
+ content,
395
+ payloadMetadata,
396
+ messageId: message.messageId,
397
+ });
398
+ }
399
+ normalizeToolArguments(input) {
400
+ if (input && typeof input === 'object' && !Array.isArray(input)) {
401
+ return input;
402
+ }
403
+ return { value: input };
404
+ }
405
+ buildToolResultEnvelope(message) {
406
+ const output = typeof message.content === 'object' && message.content !== null
407
+ ? message.content
408
+ : { value: message.content };
409
+ const content = {
410
+ type: 'tool_result',
411
+ tool_use_id: message.toolUseId,
412
+ output,
413
+ is_error: Boolean(message.isError),
414
+ };
415
+ const payloadMetadata = message.workerId || message.workerTask
416
+ ? {
417
+ worker_id: message.workerId,
418
+ worker_task: message.workerTask,
419
+ }
420
+ : undefined;
421
+ const senderSessionId = message.workerId ?? message.sessionId;
422
+ return this.buildEnvelope({
423
+ sessionId: message.sessionId,
424
+ senderSessionId,
425
+ content,
426
+ payloadMetadata,
427
+ messageId: message.messageId,
428
+ });
429
+ }
430
+ buildAgentCompleteEnvelope(message) {
431
+ const content = {
432
+ type: 'agent_complete',
433
+ success: message.success,
434
+ duration_ms: message.durationMs ?? null,
435
+ cost_usd: message.costUsd ?? null,
436
+ message: message.message ?? null,
437
+ };
438
+ if (message.metadata) {
439
+ content.metadata = message.metadata;
440
+ }
441
+ const metadataAgent = typeof message.metadata?.agent_session_id === 'string'
442
+ ? message.metadata.agent_session_id
443
+ : undefined;
444
+ const senderSessionId = metadataAgent ?? this.currentAgentSessionId ?? message.sessionId;
445
+ return this.buildEnvelope({
446
+ sessionId: message.sessionId,
447
+ senderSessionId,
448
+ content,
449
+ messageId: message.messageId,
450
+ });
451
+ }
452
+ buildProgressEnvelope(message) {
453
+ const content = {
454
+ type: 'agent_progress',
455
+ status: message.status,
456
+ };
457
+ if (message.detail) {
458
+ content.detail = message.detail;
459
+ }
460
+ if (message.metadata) {
461
+ content.metadata = message.metadata;
462
+ }
463
+ const metadataAgent = typeof message.metadata?.agent_session_id === 'string'
464
+ ? message.metadata.agent_session_id
465
+ : undefined;
466
+ const sessionId = message.sessionId ?? this.currentAgentSessionId ?? this.ensureAgentSessionExists();
467
+ const senderSessionId = metadataAgent ?? this.currentAgentSessionId ?? sessionId;
468
+ return this.buildEnvelope({
469
+ sessionId,
470
+ senderSessionId,
471
+ content,
472
+ messageId: message.messageId,
473
+ });
474
+ }
475
+ buildEnvelope(options) {
476
+ const payload = {
477
+ version: 0,
478
+ content: options.content,
479
+ };
480
+ if (options.payloadMetadata && Object.keys(options.payloadMetadata).length > 0) {
481
+ payload.metadata = options.payloadMetadata;
482
+ }
483
+ const sender = {
484
+ kind: 'agent_session',
485
+ id: options.senderSessionId ?? options.sessionId,
486
+ };
487
+ const envelope = {
488
+ message_id: options.messageId ?? randomUUID(),
489
+ session_id: options.sessionId,
490
+ sender,
491
+ payload,
492
+ created_at: new Date().toISOString(),
493
+ };
494
+ return envelope;
495
+ }
496
+ isMessageEnvelope(frame) {
497
+ return frame.payload !== undefined;
198
498
  }
199
499
  enqueue(serialized) {
200
500
  if (this.pendingOutbound.length >= MAX_QUEUE_SIZE) {
@@ -203,7 +503,7 @@ class RelayBridge extends events_1.default {
203
503
  this.pendingOutbound.push(serialized);
204
504
  }
205
505
  flushQueue() {
206
- if (!this.ws || this.ws.readyState !== ws_1.default.OPEN) {
506
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
207
507
  return;
208
508
  }
209
509
  while (this.pendingOutbound.length > 0) {
@@ -226,27 +526,26 @@ class RelayBridge extends events_1.default {
226
526
  if (this.pingTimer) {
227
527
  clearInterval(this.pingTimer);
228
528
  }
229
- const interval = Math.max(this.heartbeatIntervalMs, 5000);
529
+ const interval = Math.max(this.heartbeatIntervalMs, 5_000);
230
530
  this.pingTimer = setInterval(() => {
231
- if (!this.ws || this.ws.readyState !== ws_1.default.OPEN) {
531
+ const socket = this.ws;
532
+ if (!socket || socket.readyState !== WebSocket.OPEN) {
232
533
  return;
233
534
  }
234
- try {
235
- this.ws.send(JSON.stringify({ type: 'ping' }));
236
- }
237
- catch (error) {
238
- this.logger.error(`Failed to send relay ping: ${extractErrorMessage(error)}`);
239
- this.closeCurrentSocket();
240
- }
535
+ const frame = {
536
+ type: 'heartbeat',
537
+ mode: 'ping',
538
+ sent_at: new Date().toISOString(),
539
+ };
540
+ this.sendFrame(frame, socket);
241
541
  }, interval);
242
542
  }
243
543
  closeCurrentSocket() {
244
- if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
544
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
245
545
  this.ws.close(1012, 'Reconnecting');
246
546
  }
247
547
  }
248
548
  }
249
- exports.RelayBridge = RelayBridge;
250
549
  function buildRelayWebsocketUrl(baseUrl, path) {
251
550
  const url = new URL(baseUrl);
252
551
  url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -270,13 +569,6 @@ function extractErrorMessage(error) {
270
569
  }
271
570
  return String(error);
272
571
  }
273
- function isOutboundMessage(value) {
274
- if (!value || typeof value !== 'object') {
275
- return false;
276
- }
277
- const type = value.type;
278
- return typeof type === 'string' && OUTBOUND_MESSAGE_TYPES.has(type);
279
- }
280
572
  function isAbortError(error) {
281
573
  if (!(error instanceof Error)) {
282
574
  return false;