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.
- package/bin/omnara.js +8 -3
- package/dist/auth/auth-provider.d.ts +2 -2
- package/dist/auth/auth-provider.js +13 -15
- package/dist/auth/auth-provider.js.map +1 -1
- package/dist/auth/cli-auth-service.d.ts +1 -1
- package/dist/auth/cli-auth-service.js +3 -6
- package/dist/auth/cli-auth-service.js.map +1 -1
- package/dist/auth/credential-manager.d.ts +1 -1
- package/dist/auth/credential-manager.js +7 -9
- package/dist/auth/credential-manager.js.map +1 -1
- package/dist/auth/credentials-store.js +14 -20
- package/dist/auth/credentials-store.js.map +1 -1
- package/dist/auth/login-flow.d.ts +2 -2
- package/dist/auth/login-flow.js +23 -29
- package/dist/auth/login-flow.js.map +1 -1
- package/dist/auth/loopback-listener.js +11 -15
- package/dist/auth/loopback-listener.js.map +1 -1
- package/dist/auth/pkce.js +4 -7
- package/dist/auth/pkce.js.map +1 -1
- package/dist/auth/supabase-client.d.ts +1 -1
- package/dist/auth/supabase-client.js +3 -6
- package/dist/auth/supabase-client.js.map +1 -1
- package/dist/auth/supabase-config.js +3 -6
- package/dist/auth/supabase-config.js.map +1 -1
- package/dist/cli.js +26 -25
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.js +36 -38
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/auth.js +13 -16
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/index.d.ts +2 -2
- package/dist/commands/index.js +2 -8
- package/dist/commands/index.js.map +1 -1
- package/dist/http/api-client.js +7 -11
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/user-sessions.d.ts +13 -0
- package/dist/http/user-sessions.js +9 -0
- package/dist/http/user-sessions.js.map +1 -0
- package/dist/orchestrator/agent-orchestrator.d.ts +11 -6
- package/dist/orchestrator/agent-orchestrator.js +226 -57
- package/dist/orchestrator/agent-orchestrator.js.map +1 -1
- package/dist/orchestrator/ai-client.d.ts +5 -4
- package/dist/orchestrator/ai-client.js +17 -14
- package/dist/orchestrator/ai-client.js.map +1 -1
- package/dist/orchestrator/orchestrator-prompt.d.ts +2 -7
- package/dist/orchestrator/orchestrator-prompt.js +7 -154
- package/dist/orchestrator/orchestrator-prompt.js.map +1 -1
- package/dist/orchestrator/orchestrator-prompt.md +405 -0
- package/dist/orchestrator/session.d.ts +7 -3
- package/dist/orchestrator/session.js +56 -34
- package/dist/orchestrator/session.js.map +1 -1
- package/dist/orchestrator/tools/worker-status-tool.d.ts +7 -0
- package/dist/orchestrator/tools/worker-status-tool.js +41 -0
- package/dist/orchestrator/tools/worker-status-tool.js.map +1 -0
- package/dist/orchestrator/tools/worker-tools.d.ts +1 -1
- package/dist/orchestrator/tools/worker-tools.js +73 -89
- package/dist/orchestrator/tools/worker-tools.js.map +1 -1
- package/dist/orchestrator/types.d.ts +19 -29
- package/dist/orchestrator/types.js +22 -25
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/orchestrator/utils/async-queue.d.ts +26 -0
- package/dist/orchestrator/utils/async-queue.js +70 -0
- package/dist/orchestrator/utils/async-queue.js.map +1 -0
- package/dist/orchestrator/utils/session-logger.d.ts +1 -1
- package/dist/orchestrator/utils/session-logger.js +8 -14
- package/dist/orchestrator/utils/session-logger.js.map +1 -1
- package/dist/orchestrator/worker-manager.d.ts +31 -15
- package/dist/orchestrator/worker-manager.js +186 -18
- package/dist/orchestrator/worker-manager.js.map +1 -1
- package/dist/relay/relay-bridge.d.ts +24 -3
- package/dist/relay/relay-bridge.js +382 -90
- package/dist/relay/relay-bridge.js.map +1 -1
- package/dist/relay/relay-service.d.ts +4 -7
- package/dist/relay/relay-service.js +5 -11
- package/dist/relay/relay-service.js.map +1 -1
- package/dist/repl/terminal-repl.js +34 -30
- package/dist/repl/terminal-repl.js.map +1 -1
- package/dist/schemas/generated.d.ts +244 -0
- package/dist/schemas/generated.js +5 -0
- package/dist/schemas/generated.js.map +1 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/scripts/generate-schema-types.js +53 -0
- package/dist/scripts/generate-schema-types.js.map +1 -0
- package/dist/scripts/sync-schemas.d.ts +1 -0
- package/dist/scripts/sync-schemas.js +28 -0
- package/dist/scripts/sync-schemas.js.map +1 -0
- package/dist/utils/prompt.js +4 -10
- package/dist/utils/prompt.js.map +1 -1
- package/dist/voice/voice-service.d.ts +1 -1
- package/dist/voice/voice-service.js +2 -4
- package/dist/voice/voice-service.js.map +1 -1
- package/package.json +6 -2
- package/dist/commands/hello.d.ts +0 -2
- package/dist/commands/hello.js +0 -12
- package/dist/commands/hello.js.map +0 -1
- package/dist/generated/relay.d.ts +0 -240
- package/dist/generated/relay.js +0 -2376
- package/dist/generated/relay.js.map +0 -1
- package/dist/messaging/proto-codec.d.ts +0 -5
- package/dist/messaging/proto-codec.js +0 -387
- package/dist/messaging/proto-codec.js.map +0 -1
- package/dist/server/agent-server.d.ts +0 -46
- package/dist/server/agent-server.js +0 -429
- package/dist/server/agent-server.js.map +0 -1
- package/dist/server/ai-client.d.ts +0 -16
- package/dist/server/ai-client.js +0 -74
- package/dist/server/ai-client.js.map +0 -1
- package/dist/server/session.d.ts +0 -35
- package/dist/server/session.js +0 -272
- package/dist/server/session.js.map +0 -1
- package/dist/server/subagents.d.ts +0 -19
- package/dist/server/subagents.js +0 -42
- package/dist/server/subagents.js.map +0 -1
- package/dist/server/tools/worker-tools.d.ts +0 -2
- package/dist/server/tools/worker-tools.js +0 -37
- package/dist/server/tools/worker-tools.js.map +0 -1
- package/dist/server/types.d.ts +0 -120
- package/dist/server/types.js +0 -29
- package/dist/server/types.js.map +0 -1
- package/dist/server/utils/session-logger.d.ts +0 -7
- package/dist/server/utils/session-logger.js +0 -53
- package/dist/server/utils/session-logger.js.map +0 -1
- package/dist/server/worker-manager.d.ts +0 -67
- package/dist/server/worker-manager.js +0 -207
- package/dist/server/worker-manager.js.map +0 -1
- package/dist/test-agent.js +0 -63
- package/dist/test-agent.js.map +0 -1
- package/dist/utils/base64url.d.ts +0 -1
- package/dist/utils/base64url.js +0 -12
- package/dist/utils/base64url.js.map +0 -1
- package/dist/utils/random.d.ts +0 -1
- package/dist/utils/random.js +0 -9
- package/dist/utils/random.js.map +0 -1
- /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
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
30
|
+
this.store = options.store ?? new CredentialsStore();
|
|
43
31
|
this.logger = options.logger ?? console;
|
|
44
|
-
this.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 ===
|
|
40
|
+
canSend: () => this.ws?.readyState === WebSocket.OPEN,
|
|
53
41
|
send: (message) => {
|
|
54
42
|
this.handleOutbound(message);
|
|
55
43
|
},
|
|
56
44
|
});
|
|
57
|
-
|
|
45
|
+
this.ensureAgentSessionExists();
|
|
46
|
+
let backoffMs = 1_000;
|
|
58
47
|
while (!this.stopped) {
|
|
59
48
|
try {
|
|
60
49
|
await this.establishConnection();
|
|
61
|
-
backoffMs =
|
|
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,
|
|
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.
|
|
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 ===
|
|
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
|
|
84
|
+
const client = new ApiClient({ baseUrl: this.backendUrl });
|
|
96
85
|
const accessToken = await authProvider.getAccessToken();
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
164
|
-
if (
|
|
165
|
-
|
|
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
|
-
|
|
189
|
-
if (result.success) {
|
|
190
|
-
this.connectionHandle?.dispatch(result.data);
|
|
231
|
+
if (this.tryHandleControlMessage(parsed)) {
|
|
191
232
|
return;
|
|
192
233
|
}
|
|
193
|
-
if (
|
|
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 !==
|
|
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,
|
|
529
|
+
const interval = Math.max(this.heartbeatIntervalMs, 5_000);
|
|
230
530
|
this.pingTimer = setInterval(() => {
|
|
231
|
-
|
|
531
|
+
const socket = this.ws;
|
|
532
|
+
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
232
533
|
return;
|
|
233
534
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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 ===
|
|
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;
|