@getpaseo/server 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/dist/server/client/daemon-client-relay-e2ee-transport.d.ts +8 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.d.ts.map +1 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.js +161 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.js.map +1 -0
- package/dist/server/client/daemon-client-terminal-stream-manager.d.ts +43 -0
- package/dist/server/client/daemon-client-terminal-stream-manager.d.ts.map +1 -0
- package/dist/server/client/daemon-client-terminal-stream-manager.js +130 -0
- package/dist/server/client/daemon-client-terminal-stream-manager.js.map +1 -0
- package/dist/server/client/daemon-client-transport-types.d.ts +34 -0
- package/dist/server/client/daemon-client-transport-types.d.ts.map +1 -0
- package/dist/server/client/daemon-client-transport-types.js +2 -0
- package/dist/server/client/daemon-client-transport-types.js.map +1 -0
- package/dist/server/client/daemon-client-transport-utils.d.ts +9 -0
- package/dist/server/client/daemon-client-transport-utils.d.ts.map +1 -0
- package/dist/server/client/daemon-client-transport-utils.js +121 -0
- package/dist/server/client/daemon-client-transport-utils.js.map +1 -0
- package/dist/server/client/daemon-client-transport.d.ts +5 -0
- package/dist/server/client/daemon-client-transport.d.ts.map +1 -0
- package/dist/server/client/daemon-client-transport.js +4 -0
- package/dist/server/client/daemon-client-transport.js.map +1 -0
- package/dist/server/client/daemon-client-websocket-transport.d.ts +7 -0
- package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -0
- package/dist/server/client/daemon-client-websocket-transport.js +64 -0
- package/dist/server/client/daemon-client-websocket-transport.js.map +1 -0
- package/dist/server/client/daemon-client.d.ts +44 -32
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +463 -774
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +45 -0
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +197 -9
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +4 -4
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +120 -6
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts +7 -0
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +83 -9
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +25 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/stt-manager.d.ts +3 -2
- package/dist/server/server/agent/stt-manager.d.ts.map +1 -1
- package/dist/server/server/agent/stt-manager.js +5 -3
- package/dist/server/server/agent/stt-manager.js.map +1 -1
- package/dist/server/server/agent/timeline-projection.d.ts +19 -0
- package/dist/server/server/agent/timeline-projection.d.ts.map +1 -0
- package/dist/server/server/agent/timeline-projection.js +142 -0
- package/dist/server/server/agent/timeline-projection.js.map +1 -0
- package/dist/server/server/agent/tts-manager.d.ts +3 -2
- package/dist/server/server/agent/tts-manager.d.ts.map +1 -1
- package/dist/server/server/agent/tts-manager.js +5 -3
- package/dist/server/server/agent/tts-manager.js.map +1 -1
- package/dist/server/server/agent-attention-policy.d.ts +20 -0
- package/dist/server/server/agent-attention-policy.d.ts.map +1 -0
- package/dist/server/server/agent-attention-policy.js +40 -0
- package/dist/server/server/agent-attention-policy.js.map +1 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +13 -18
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/dictation/dictation-stream-manager.d.ts +10 -2
- package/dist/server/server/dictation/dictation-stream-manager.d.ts.map +1 -1
- package/dist/server/server/dictation/dictation-stream-manager.js +81 -14
- package/dist/server/server/dictation/dictation-stream-manager.js.map +1 -1
- package/dist/server/server/exports.d.ts +1 -1
- package/dist/server/server/exports.d.ts.map +1 -1
- package/dist/server/server/persisted-config.d.ts +4 -4
- package/dist/server/server/relay-transport.d.ts +3 -2
- package/dist/server/server/relay-transport.d.ts.map +1 -1
- package/dist/server/server/relay-transport.js +21 -5
- package/dist/server/server/relay-transport.js.map +1 -1
- package/dist/server/server/session.d.ts +32 -8
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +499 -79
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/speech/provider-resolver.d.ts +3 -0
- package/dist/server/server/speech/provider-resolver.d.ts.map +1 -0
- package/dist/server/server/speech/provider-resolver.js +7 -0
- package/dist/server/server/speech/provider-resolver.js.map +1 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts +1 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/runtime.js +4 -3
- package/dist/server/server/speech/providers/local/runtime.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +3 -66
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js.map +1 -1
- package/dist/server/server/speech/speech-runtime.d.ts +26 -3
- package/dist/server/server/speech/speech-runtime.d.ts.map +1 -1
- package/dist/server/server/speech/speech-runtime.js +466 -112
- package/dist/server/server/speech/speech-runtime.js.map +1 -1
- package/dist/server/server/websocket-server.d.ts +23 -7
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +288 -64
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/shared/binary-mux.d.ts +31 -0
- package/dist/server/shared/binary-mux.d.ts.map +1 -0
- package/dist/server/shared/binary-mux.js +101 -0
- package/dist/server/shared/binary-mux.js.map +1 -0
- package/dist/server/shared/messages.d.ts +4655 -3615
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +184 -26
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/shared/terminal-key-input.d.ts +9 -0
- package/dist/server/shared/terminal-key-input.d.ts.map +1 -0
- package/dist/server/shared/terminal-key-input.js +132 -0
- package/dist/server/shared/terminal-key-input.js.map +1 -0
- package/dist/server/terminal/terminal.d.ts +17 -0
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +89 -0
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +4 -0
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +92 -0
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +33 -4
- package/dist/server/utils/worktree.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { AgentCreateFailedStatusPayloadSchema, AgentCreatedStatusPayloadSchema, AgentRefreshedStatusPayloadSchema, AgentResumedStatusPayloadSchema, RestartRequestedStatusPayloadSchema, SessionInboundMessageSchema, WSOutboundMessageSchema, } from "../shared/messages.js";
|
|
2
2
|
import { getAgentProviderDefinition } from "../server/agent/provider-manifest.js";
|
|
3
|
-
import { createClientChannel, } from "@getpaseo/relay/e2ee";
|
|
4
3
|
import { isRelayClientWebSocketUrl } from "../shared/daemon-endpoints.js";
|
|
4
|
+
import { asUint8Array, decodeBinaryMuxFrame, encodeBinaryMuxFrame, BinaryMuxChannel, TerminalBinaryFlags, TerminalBinaryMessageType, } from "../shared/binary-mux.js";
|
|
5
|
+
import { encodeTerminalKeyInput, } from "../shared/terminal-key-input.js";
|
|
6
|
+
import { TerminalStreamManager, } from "./daemon-client-terminal-stream-manager.js";
|
|
7
|
+
import { createRelayE2eeTransportFactory, createWebSocketTransportFactory, decodeMessageData, defaultWebSocketFactory, describeTransportClose, describeTransportError, encodeUtf8String, safeRandomId, } from "./daemon-client-transport.js";
|
|
5
8
|
const consoleLogger = {
|
|
6
9
|
debug: (obj, msg) => console.debug(msg, obj),
|
|
7
10
|
info: (obj, msg) => console.info(msg, obj),
|
|
@@ -21,6 +24,12 @@ const DEFAULT_RECONNECT_BASE_DELAY_MS = 1500;
|
|
|
21
24
|
const DEFAULT_RECONNECT_MAX_DELAY_MS = 30000;
|
|
22
25
|
/** Default timeout for waiting for connection before sending queued messages */
|
|
23
26
|
const DEFAULT_SEND_QUEUE_TIMEOUT_MS = 10000;
|
|
27
|
+
const DEFAULT_DICTATION_FINISH_ACCEPT_TIMEOUT_MS = 15000;
|
|
28
|
+
const DEFAULT_DICTATION_FINISH_FALLBACK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
29
|
+
const DEFAULT_DICTATION_FINISH_TIMEOUT_GRACE_MS = 5000;
|
|
30
|
+
function isWaiterTimeoutError(error) {
|
|
31
|
+
return (error instanceof Error && error.message.startsWith("Timeout waiting for message"));
|
|
32
|
+
}
|
|
24
33
|
export class DaemonClient {
|
|
25
34
|
constructor(config) {
|
|
26
35
|
this.config = config;
|
|
@@ -46,6 +55,17 @@ export class DaemonClient {
|
|
|
46
55
|
this.pendingSendQueue = [];
|
|
47
56
|
this.relayClientId = null;
|
|
48
57
|
this.logger = config.logger ?? consoleLogger;
|
|
58
|
+
this.terminalStreams = new TerminalStreamManager({
|
|
59
|
+
sendAck: (ack) => {
|
|
60
|
+
this.sendBinaryFrame({
|
|
61
|
+
channel: BinaryMuxChannel.Terminal,
|
|
62
|
+
messageType: TerminalBinaryMessageType.Ack,
|
|
63
|
+
streamId: ack.streamId,
|
|
64
|
+
offset: ack.offset,
|
|
65
|
+
payload: new Uint8Array(0),
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
});
|
|
49
69
|
// Relay requires a clientId so the daemon can create an independent
|
|
50
70
|
// socket + E2EE channel per connected client. Generate one per DaemonClient
|
|
51
71
|
// instance (stable across reconnects in this tab/app session).
|
|
@@ -235,6 +255,7 @@ export class DaemonClient {
|
|
|
235
255
|
}
|
|
236
256
|
this.disposeTransport(1000, "Client closed");
|
|
237
257
|
this.clearWaiters(new Error("Daemon client closed"));
|
|
258
|
+
this.terminalStreams.clearAll();
|
|
238
259
|
this.updateConnectionState({
|
|
239
260
|
status: "disconnected",
|
|
240
261
|
reason: "client_closed",
|
|
@@ -329,6 +350,23 @@ export class DaemonClient {
|
|
|
329
350
|
throw error instanceof Error ? error : new Error(String(error));
|
|
330
351
|
}
|
|
331
352
|
}
|
|
353
|
+
sendBinaryFrame(frame) {
|
|
354
|
+
if (!this.transport || this.connectionState.status !== "connected") {
|
|
355
|
+
if (this.config.suppressSendErrors) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
throw new Error(`Transport not connected (status: ${this.connectionState.status})`);
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
this.transport.send(encodeBinaryMuxFrame(frame));
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
if (this.config.suppressSendErrors) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
332
370
|
/**
|
|
333
371
|
* Send a session message for RPC methods that create waiters.
|
|
334
372
|
* If the connection is still being established ("connecting"), the message
|
|
@@ -428,6 +466,43 @@ export class DaemonClient {
|
|
|
428
466
|
}
|
|
429
467
|
return result.value;
|
|
430
468
|
}
|
|
469
|
+
async sendCorrelatedRequest(params) {
|
|
470
|
+
return this.sendRequest({
|
|
471
|
+
requestId: params.requestId,
|
|
472
|
+
message: params.message,
|
|
473
|
+
timeout: params.timeout,
|
|
474
|
+
options: params.options,
|
|
475
|
+
select: (msg) => {
|
|
476
|
+
const correlated = msg;
|
|
477
|
+
if (correlated.type !== params.responseType) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
const payload = correlated.payload;
|
|
481
|
+
if (payload.requestId !== params.requestId) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
if (!params.selectPayload) {
|
|
485
|
+
return payload;
|
|
486
|
+
}
|
|
487
|
+
return params.selectPayload(payload);
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
sendCorrelatedSessionRequest(params) {
|
|
492
|
+
const resolvedRequestId = this.createRequestId(params.requestId);
|
|
493
|
+
const message = SessionInboundMessageSchema.parse({
|
|
494
|
+
...params.message,
|
|
495
|
+
requestId: resolvedRequestId,
|
|
496
|
+
});
|
|
497
|
+
return this.sendCorrelatedRequest({
|
|
498
|
+
requestId: resolvedRequestId,
|
|
499
|
+
message,
|
|
500
|
+
responseType: params.responseType,
|
|
501
|
+
timeout: params.timeout,
|
|
502
|
+
options: { skipQueue: true },
|
|
503
|
+
...(params.selectPayload ? { selectPayload: params.selectPayload } : {}),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
431
506
|
sendSessionMessageStrict(message) {
|
|
432
507
|
if (!this.transport || this.connectionState.status !== "connected") {
|
|
433
508
|
throw new Error("Transport not connected");
|
|
@@ -783,20 +858,24 @@ export class DaemonClient {
|
|
|
783
858
|
},
|
|
784
859
|
});
|
|
785
860
|
}
|
|
786
|
-
async
|
|
787
|
-
const resolvedRequestId = this.createRequestId(requestId);
|
|
861
|
+
async fetchAgentTimeline(agentId, options = {}) {
|
|
862
|
+
const resolvedRequestId = this.createRequestId(options.requestId);
|
|
788
863
|
const message = SessionInboundMessageSchema.parse({
|
|
789
|
-
type: "
|
|
864
|
+
type: "fetch_agent_timeline_request",
|
|
790
865
|
agentId,
|
|
791
866
|
requestId: resolvedRequestId,
|
|
867
|
+
...(options.direction ? { direction: options.direction } : {}),
|
|
868
|
+
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
869
|
+
...(typeof options.limit === "number" ? { limit: options.limit } : {}),
|
|
870
|
+
...(options.projection ? { projection: options.projection } : {}),
|
|
792
871
|
});
|
|
793
872
|
const payload = await this.sendRequest({
|
|
794
873
|
requestId: resolvedRequestId,
|
|
795
874
|
message,
|
|
796
|
-
timeout:
|
|
875
|
+
timeout: 15000,
|
|
797
876
|
options: { skipQueue: true },
|
|
798
877
|
select: (msg) => {
|
|
799
|
-
if (msg.type !== "
|
|
878
|
+
if (msg.type !== "fetch_agent_timeline_response") {
|
|
800
879
|
return null;
|
|
801
880
|
}
|
|
802
881
|
if (msg.payload.requestId !== resolvedRequestId) {
|
|
@@ -808,11 +887,7 @@ export class DaemonClient {
|
|
|
808
887
|
if (payload.error) {
|
|
809
888
|
throw new Error(payload.error);
|
|
810
889
|
}
|
|
811
|
-
|
|
812
|
-
if (!agent) {
|
|
813
|
-
throw new Error(`Agent not found after initialize: ${agentId}`);
|
|
814
|
-
}
|
|
815
|
-
return agent;
|
|
890
|
+
return payload;
|
|
816
891
|
}
|
|
817
892
|
// ============================================================================
|
|
818
893
|
// Agent Interaction
|
|
@@ -972,7 +1047,7 @@ export class DaemonClient {
|
|
|
972
1047
|
...(agentId ? { agentId } : {}),
|
|
973
1048
|
requestId,
|
|
974
1049
|
});
|
|
975
|
-
|
|
1050
|
+
const response = await this.sendRequest({
|
|
976
1051
|
requestId,
|
|
977
1052
|
message,
|
|
978
1053
|
timeout: 10000,
|
|
@@ -986,11 +1061,18 @@ export class DaemonClient {
|
|
|
986
1061
|
return msg.payload;
|
|
987
1062
|
},
|
|
988
1063
|
});
|
|
1064
|
+
if (!response.accepted) {
|
|
1065
|
+
const codeSuffix = typeof response.reasonCode === "string" && response.reasonCode.trim().length > 0
|
|
1066
|
+
? ` (${response.reasonCode})`
|
|
1067
|
+
: "";
|
|
1068
|
+
throw new Error((response.error ?? "Failed to set voice mode") + codeSuffix);
|
|
1069
|
+
}
|
|
1070
|
+
return response;
|
|
989
1071
|
}
|
|
990
1072
|
async sendVoiceAudioChunk(audio, format, isLast) {
|
|
991
1073
|
this.sendSessionMessage({ type: "voice_audio_chunk", audio, format, isLast });
|
|
992
1074
|
}
|
|
993
|
-
startDictationStream(dictationId, format) {
|
|
1075
|
+
async startDictationStream(dictationId, format) {
|
|
994
1076
|
const ack = this.waitForWithCancel((msg) => {
|
|
995
1077
|
if (msg.type !== "dictation_stream_ack") {
|
|
996
1078
|
return null;
|
|
@@ -1016,23 +1098,22 @@ export class DaemonClient {
|
|
|
1016
1098
|
const errorPromise = streamError.promise.then((payload) => {
|
|
1017
1099
|
throw new Error(payload.error);
|
|
1018
1100
|
});
|
|
1101
|
+
const cleanupError = new Error("Cancelled dictation start waiter");
|
|
1019
1102
|
try {
|
|
1020
1103
|
this.sendSessionMessageStrict({ type: "dictation_stream_start", dictationId, format });
|
|
1104
|
+
await Promise.race([ackPromise, errorPromise]);
|
|
1021
1105
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
streamError.cancel(err);
|
|
1106
|
+
finally {
|
|
1107
|
+
ack.cancel(cleanupError);
|
|
1108
|
+
streamError.cancel(cleanupError);
|
|
1026
1109
|
void ackPromise.catch(() => undefined);
|
|
1027
1110
|
void errorPromise.catch(() => undefined);
|
|
1028
|
-
throw err;
|
|
1029
1111
|
}
|
|
1030
|
-
return Promise.race([ackPromise, errorPromise]);
|
|
1031
1112
|
}
|
|
1032
1113
|
sendDictationStreamChunk(dictationId, seq, audio, format) {
|
|
1033
1114
|
this.sendSessionMessageStrict({ type: "dictation_stream_chunk", dictationId, seq, audio, format });
|
|
1034
1115
|
}
|
|
1035
|
-
finishDictationStream(dictationId, finalSeq) {
|
|
1116
|
+
async finishDictationStream(dictationId, finalSeq) {
|
|
1036
1117
|
const final = this.waitForWithCancel((msg) => {
|
|
1037
1118
|
if (msg.type !== "dictation_stream_final") {
|
|
1038
1119
|
return null;
|
|
@@ -1041,7 +1122,7 @@ export class DaemonClient {
|
|
|
1041
1122
|
return null;
|
|
1042
1123
|
}
|
|
1043
1124
|
return msg.payload;
|
|
1044
|
-
},
|
|
1125
|
+
}, 0, { skipQueue: true });
|
|
1045
1126
|
const streamError = this.waitForWithCancel((msg) => {
|
|
1046
1127
|
if (msg.type !== "dictation_stream_error") {
|
|
1047
1128
|
return null;
|
|
@@ -1050,23 +1131,96 @@ export class DaemonClient {
|
|
|
1050
1131
|
return null;
|
|
1051
1132
|
}
|
|
1052
1133
|
return msg.payload;
|
|
1053
|
-
},
|
|
1134
|
+
}, 0, { skipQueue: true });
|
|
1135
|
+
const finishAccepted = this.waitForWithCancel((msg) => {
|
|
1136
|
+
if (msg.type !== "dictation_stream_finish_accepted") {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
if (msg.payload.dictationId !== dictationId) {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
return msg.payload;
|
|
1143
|
+
}, DEFAULT_DICTATION_FINISH_ACCEPT_TIMEOUT_MS, { skipQueue: true });
|
|
1054
1144
|
const finalPromise = final.promise;
|
|
1055
1145
|
const errorPromise = streamError.promise.then((payload) => {
|
|
1056
1146
|
throw new Error(payload.error);
|
|
1057
1147
|
});
|
|
1148
|
+
const finishAcceptedPromise = finishAccepted.promise;
|
|
1149
|
+
const finalOutcomePromise = finalPromise.then((payload) => ({
|
|
1150
|
+
kind: "final",
|
|
1151
|
+
payload,
|
|
1152
|
+
}));
|
|
1153
|
+
const errorOutcomePromise = errorPromise.then(() => ({
|
|
1154
|
+
kind: "error",
|
|
1155
|
+
error: new Error("Unexpected dictation stream error state"),
|
|
1156
|
+
}), (error) => ({
|
|
1157
|
+
kind: "error",
|
|
1158
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1159
|
+
}));
|
|
1160
|
+
const finishAcceptedOutcomePromise = finishAcceptedPromise.then((payload) => ({ kind: "accepted", payload }), (error) => {
|
|
1161
|
+
if (isWaiterTimeoutError(error)) {
|
|
1162
|
+
return { kind: "accepted_timeout" };
|
|
1163
|
+
}
|
|
1164
|
+
return {
|
|
1165
|
+
kind: "accepted_error",
|
|
1166
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1167
|
+
};
|
|
1168
|
+
});
|
|
1169
|
+
const waitForFinalResult = async (timeoutMs) => {
|
|
1170
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
1171
|
+
const outcome = await Promise.race([finalOutcomePromise, errorOutcomePromise]);
|
|
1172
|
+
if (outcome.kind === "error") {
|
|
1173
|
+
throw outcome.error;
|
|
1174
|
+
}
|
|
1175
|
+
return outcome.payload;
|
|
1176
|
+
}
|
|
1177
|
+
let timeoutHandle = null;
|
|
1178
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
1179
|
+
timeoutHandle = setTimeout(() => resolve({ kind: "timeout" }), timeoutMs);
|
|
1180
|
+
});
|
|
1181
|
+
const outcome = await Promise.race([
|
|
1182
|
+
finalOutcomePromise,
|
|
1183
|
+
errorOutcomePromise,
|
|
1184
|
+
timeoutPromise,
|
|
1185
|
+
]);
|
|
1186
|
+
if (timeoutHandle) {
|
|
1187
|
+
clearTimeout(timeoutHandle);
|
|
1188
|
+
}
|
|
1189
|
+
if (outcome.kind === "timeout") {
|
|
1190
|
+
throw new Error(`Timeout waiting for dictation finalization (${timeoutMs}ms)`);
|
|
1191
|
+
}
|
|
1192
|
+
if (outcome.kind === "error") {
|
|
1193
|
+
throw outcome.error;
|
|
1194
|
+
}
|
|
1195
|
+
return outcome.payload;
|
|
1196
|
+
};
|
|
1197
|
+
const cleanupError = new Error("Cancelled dictation finish waiter");
|
|
1058
1198
|
try {
|
|
1059
1199
|
this.sendSessionMessageStrict({ type: "dictation_stream_finish", dictationId, finalSeq });
|
|
1200
|
+
const firstOutcome = await Promise.race([
|
|
1201
|
+
finalOutcomePromise,
|
|
1202
|
+
errorOutcomePromise,
|
|
1203
|
+
finishAcceptedOutcomePromise,
|
|
1204
|
+
]);
|
|
1205
|
+
if (firstOutcome.kind === "final") {
|
|
1206
|
+
return firstOutcome.payload;
|
|
1207
|
+
}
|
|
1208
|
+
if (firstOutcome.kind === "error") {
|
|
1209
|
+
throw firstOutcome.error;
|
|
1210
|
+
}
|
|
1211
|
+
if (firstOutcome.kind === "accepted") {
|
|
1212
|
+
return await waitForFinalResult(firstOutcome.payload.timeoutMs + DEFAULT_DICTATION_FINISH_TIMEOUT_GRACE_MS);
|
|
1213
|
+
}
|
|
1214
|
+
return await waitForFinalResult(DEFAULT_DICTATION_FINISH_FALLBACK_TIMEOUT_MS);
|
|
1060
1215
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1216
|
+
finally {
|
|
1217
|
+
final.cancel(cleanupError);
|
|
1218
|
+
streamError.cancel(cleanupError);
|
|
1219
|
+
finishAccepted.cancel(cleanupError);
|
|
1065
1220
|
void finalPromise.catch(() => undefined);
|
|
1066
1221
|
void errorPromise.catch(() => undefined);
|
|
1067
|
-
|
|
1222
|
+
void finishAcceptedPromise.catch(() => undefined);
|
|
1068
1223
|
}
|
|
1069
|
-
return Promise.race([finalPromise, errorPromise]);
|
|
1070
1224
|
}
|
|
1071
1225
|
cancelDictationStream(dictationId) {
|
|
1072
1226
|
this.sendSessionMessageStrict({ type: "dictation_stream_cancel", dictationId });
|
|
@@ -1171,22 +1325,17 @@ export class DaemonClient {
|
|
|
1171
1325
|
requestId: resolvedRequestId,
|
|
1172
1326
|
});
|
|
1173
1327
|
try {
|
|
1174
|
-
return await this.
|
|
1328
|
+
return await this.sendCorrelatedRequest({
|
|
1175
1329
|
requestId: resolvedRequestId,
|
|
1176
1330
|
message,
|
|
1331
|
+
responseType: "subscribe_checkout_diff_response",
|
|
1177
1332
|
timeout: 60000,
|
|
1178
1333
|
options: { skipQueue: true },
|
|
1179
|
-
|
|
1180
|
-
if (
|
|
1181
|
-
return null;
|
|
1182
|
-
}
|
|
1183
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1184
|
-
return null;
|
|
1185
|
-
}
|
|
1186
|
-
if (msg.payload.subscriptionId !== subscriptionId) {
|
|
1334
|
+
selectPayload: (payload) => {
|
|
1335
|
+
if (payload.subscriptionId !== subscriptionId) {
|
|
1187
1336
|
return null;
|
|
1188
1337
|
}
|
|
1189
|
-
return
|
|
1338
|
+
return payload;
|
|
1190
1339
|
},
|
|
1191
1340
|
});
|
|
1192
1341
|
}
|
|
@@ -1208,441 +1357,238 @@ export class DaemonClient {
|
|
|
1208
1357
|
});
|
|
1209
1358
|
}
|
|
1210
1359
|
async checkoutCommit(cwd, input, requestId) {
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
});
|
|
1219
|
-
return this.sendRequest({
|
|
1220
|
-
requestId: resolvedRequestId,
|
|
1221
|
-
message,
|
|
1222
|
-
timeout: 60000,
|
|
1223
|
-
options: { skipQueue: true },
|
|
1224
|
-
select: (msg) => {
|
|
1225
|
-
if (msg.type !== "checkout_commit_response") {
|
|
1226
|
-
return null;
|
|
1227
|
-
}
|
|
1228
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1229
|
-
return null;
|
|
1230
|
-
}
|
|
1231
|
-
return msg.payload;
|
|
1360
|
+
return this.sendCorrelatedSessionRequest({
|
|
1361
|
+
requestId,
|
|
1362
|
+
message: {
|
|
1363
|
+
type: "checkout_commit_request",
|
|
1364
|
+
cwd,
|
|
1365
|
+
message: input.message,
|
|
1366
|
+
addAll: input.addAll,
|
|
1232
1367
|
},
|
|
1368
|
+
responseType: "checkout_commit_response",
|
|
1369
|
+
timeout: 60000,
|
|
1233
1370
|
});
|
|
1234
1371
|
}
|
|
1235
1372
|
async checkoutMerge(cwd, input, requestId) {
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
});
|
|
1245
|
-
return this.sendRequest({
|
|
1246
|
-
requestId: resolvedRequestId,
|
|
1247
|
-
message,
|
|
1248
|
-
timeout: 60000,
|
|
1249
|
-
options: { skipQueue: true },
|
|
1250
|
-
select: (msg) => {
|
|
1251
|
-
if (msg.type !== "checkout_merge_response") {
|
|
1252
|
-
return null;
|
|
1253
|
-
}
|
|
1254
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1255
|
-
return null;
|
|
1256
|
-
}
|
|
1257
|
-
return msg.payload;
|
|
1373
|
+
return this.sendCorrelatedSessionRequest({
|
|
1374
|
+
requestId,
|
|
1375
|
+
message: {
|
|
1376
|
+
type: "checkout_merge_request",
|
|
1377
|
+
cwd,
|
|
1378
|
+
baseRef: input.baseRef,
|
|
1379
|
+
strategy: input.strategy,
|
|
1380
|
+
requireCleanTarget: input.requireCleanTarget,
|
|
1258
1381
|
},
|
|
1382
|
+
responseType: "checkout_merge_response",
|
|
1383
|
+
timeout: 60000,
|
|
1259
1384
|
});
|
|
1260
1385
|
}
|
|
1261
1386
|
async checkoutMergeFromBase(cwd, input, requestId) {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
});
|
|
1270
|
-
return this.sendRequest({
|
|
1271
|
-
requestId: resolvedRequestId,
|
|
1272
|
-
message,
|
|
1273
|
-
timeout: 60000,
|
|
1274
|
-
options: { skipQueue: true },
|
|
1275
|
-
select: (msg) => {
|
|
1276
|
-
if (msg.type !== "checkout_merge_from_base_response") {
|
|
1277
|
-
return null;
|
|
1278
|
-
}
|
|
1279
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1280
|
-
return null;
|
|
1281
|
-
}
|
|
1282
|
-
return msg.payload;
|
|
1387
|
+
return this.sendCorrelatedSessionRequest({
|
|
1388
|
+
requestId,
|
|
1389
|
+
message: {
|
|
1390
|
+
type: "checkout_merge_from_base_request",
|
|
1391
|
+
cwd,
|
|
1392
|
+
baseRef: input.baseRef,
|
|
1393
|
+
requireCleanTarget: input.requireCleanTarget,
|
|
1283
1394
|
},
|
|
1395
|
+
responseType: "checkout_merge_from_base_response",
|
|
1396
|
+
timeout: 60000,
|
|
1284
1397
|
});
|
|
1285
1398
|
}
|
|
1286
1399
|
async checkoutPush(cwd, requestId) {
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
});
|
|
1293
|
-
return this.sendRequest({
|
|
1294
|
-
requestId: resolvedRequestId,
|
|
1295
|
-
message,
|
|
1296
|
-
timeout: 60000,
|
|
1297
|
-
options: { skipQueue: true },
|
|
1298
|
-
select: (msg) => {
|
|
1299
|
-
if (msg.type !== "checkout_push_response") {
|
|
1300
|
-
return null;
|
|
1301
|
-
}
|
|
1302
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1303
|
-
return null;
|
|
1304
|
-
}
|
|
1305
|
-
return msg.payload;
|
|
1400
|
+
return this.sendCorrelatedSessionRequest({
|
|
1401
|
+
requestId,
|
|
1402
|
+
message: {
|
|
1403
|
+
type: "checkout_push_request",
|
|
1404
|
+
cwd,
|
|
1306
1405
|
},
|
|
1406
|
+
responseType: "checkout_push_response",
|
|
1407
|
+
timeout: 60000,
|
|
1307
1408
|
});
|
|
1308
1409
|
}
|
|
1309
1410
|
async checkoutPrCreate(cwd, input, requestId) {
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
});
|
|
1319
|
-
return this.sendRequest({
|
|
1320
|
-
requestId: resolvedRequestId,
|
|
1321
|
-
message,
|
|
1322
|
-
timeout: 60000,
|
|
1323
|
-
options: { skipQueue: true },
|
|
1324
|
-
select: (msg) => {
|
|
1325
|
-
if (msg.type !== "checkout_pr_create_response") {
|
|
1326
|
-
return null;
|
|
1327
|
-
}
|
|
1328
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1329
|
-
return null;
|
|
1330
|
-
}
|
|
1331
|
-
return msg.payload;
|
|
1411
|
+
return this.sendCorrelatedSessionRequest({
|
|
1412
|
+
requestId,
|
|
1413
|
+
message: {
|
|
1414
|
+
type: "checkout_pr_create_request",
|
|
1415
|
+
cwd,
|
|
1416
|
+
title: input.title,
|
|
1417
|
+
body: input.body,
|
|
1418
|
+
baseRef: input.baseRef,
|
|
1332
1419
|
},
|
|
1420
|
+
responseType: "checkout_pr_create_response",
|
|
1421
|
+
timeout: 60000,
|
|
1333
1422
|
});
|
|
1334
1423
|
}
|
|
1335
1424
|
async checkoutPrStatus(cwd, requestId) {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
});
|
|
1342
|
-
return this.sendRequest({
|
|
1343
|
-
requestId: resolvedRequestId,
|
|
1344
|
-
message,
|
|
1345
|
-
timeout: 60000,
|
|
1346
|
-
options: { skipQueue: true },
|
|
1347
|
-
select: (msg) => {
|
|
1348
|
-
if (msg.type !== "checkout_pr_status_response") {
|
|
1349
|
-
return null;
|
|
1350
|
-
}
|
|
1351
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1352
|
-
return null;
|
|
1353
|
-
}
|
|
1354
|
-
return msg.payload;
|
|
1425
|
+
return this.sendCorrelatedSessionRequest({
|
|
1426
|
+
requestId,
|
|
1427
|
+
message: {
|
|
1428
|
+
type: "checkout_pr_status_request",
|
|
1429
|
+
cwd,
|
|
1355
1430
|
},
|
|
1431
|
+
responseType: "checkout_pr_status_response",
|
|
1432
|
+
timeout: 60000,
|
|
1356
1433
|
});
|
|
1357
1434
|
}
|
|
1358
1435
|
async getPaseoWorktreeList(input, requestId) {
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
});
|
|
1366
|
-
return this.sendRequest({
|
|
1367
|
-
requestId: resolvedRequestId,
|
|
1368
|
-
message,
|
|
1369
|
-
timeout: 60000,
|
|
1370
|
-
options: { skipQueue: true },
|
|
1371
|
-
select: (msg) => {
|
|
1372
|
-
if (msg.type !== "paseo_worktree_list_response") {
|
|
1373
|
-
return null;
|
|
1374
|
-
}
|
|
1375
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1376
|
-
return null;
|
|
1377
|
-
}
|
|
1378
|
-
return msg.payload;
|
|
1436
|
+
return this.sendCorrelatedSessionRequest({
|
|
1437
|
+
requestId,
|
|
1438
|
+
message: {
|
|
1439
|
+
type: "paseo_worktree_list_request",
|
|
1440
|
+
cwd: input.cwd,
|
|
1441
|
+
repoRoot: input.repoRoot,
|
|
1379
1442
|
},
|
|
1443
|
+
responseType: "paseo_worktree_list_response",
|
|
1444
|
+
timeout: 60000,
|
|
1380
1445
|
});
|
|
1381
1446
|
}
|
|
1382
1447
|
async archivePaseoWorktree(input, requestId) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
});
|
|
1391
|
-
return this.sendRequest({
|
|
1392
|
-
requestId: resolvedRequestId,
|
|
1393
|
-
message,
|
|
1394
|
-
timeout: 20000,
|
|
1395
|
-
options: { skipQueue: true },
|
|
1396
|
-
select: (msg) => {
|
|
1397
|
-
if (msg.type !== "paseo_worktree_archive_response") {
|
|
1398
|
-
return null;
|
|
1399
|
-
}
|
|
1400
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1401
|
-
return null;
|
|
1402
|
-
}
|
|
1403
|
-
return msg.payload;
|
|
1448
|
+
return this.sendCorrelatedSessionRequest({
|
|
1449
|
+
requestId,
|
|
1450
|
+
message: {
|
|
1451
|
+
type: "paseo_worktree_archive_request",
|
|
1452
|
+
worktreePath: input.worktreePath,
|
|
1453
|
+
repoRoot: input.repoRoot,
|
|
1454
|
+
branchName: input.branchName,
|
|
1404
1455
|
},
|
|
1456
|
+
responseType: "paseo_worktree_archive_response",
|
|
1457
|
+
timeout: 20000,
|
|
1405
1458
|
});
|
|
1406
1459
|
}
|
|
1407
1460
|
async validateBranch(options, requestId) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
requestId: resolvedRequestId,
|
|
1417
|
-
message,
|
|
1461
|
+
return this.sendCorrelatedSessionRequest({
|
|
1462
|
+
requestId,
|
|
1463
|
+
message: {
|
|
1464
|
+
type: "validate_branch_request",
|
|
1465
|
+
cwd: options.cwd,
|
|
1466
|
+
branchName: options.branchName,
|
|
1467
|
+
},
|
|
1468
|
+
responseType: "validate_branch_response",
|
|
1418
1469
|
timeout: 10000,
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
async getBranchSuggestions(options, requestId) {
|
|
1473
|
+
return this.sendCorrelatedSessionRequest({
|
|
1474
|
+
requestId,
|
|
1475
|
+
message: {
|
|
1476
|
+
type: "branch_suggestions_request",
|
|
1477
|
+
cwd: options.cwd,
|
|
1478
|
+
query: options.query,
|
|
1479
|
+
limit: options.limit,
|
|
1428
1480
|
},
|
|
1481
|
+
responseType: "branch_suggestions_response",
|
|
1482
|
+
timeout: 10000,
|
|
1429
1483
|
});
|
|
1430
1484
|
}
|
|
1431
1485
|
// ============================================================================
|
|
1432
1486
|
// File Explorer
|
|
1433
1487
|
// ============================================================================
|
|
1434
1488
|
async exploreFileSystem(agentId, path, mode = "list", requestId) {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
message,
|
|
1446
|
-
timeout: 10000,
|
|
1447
|
-
options: { skipQueue: true },
|
|
1448
|
-
select: (msg) => {
|
|
1449
|
-
if (msg.type !== "file_explorer_response") {
|
|
1450
|
-
return null;
|
|
1451
|
-
}
|
|
1452
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1453
|
-
return null;
|
|
1454
|
-
}
|
|
1455
|
-
return msg.payload;
|
|
1456
|
-
},
|
|
1489
|
+
return this.sendCorrelatedSessionRequest({
|
|
1490
|
+
requestId,
|
|
1491
|
+
message: {
|
|
1492
|
+
type: "file_explorer_request",
|
|
1493
|
+
agentId,
|
|
1494
|
+
path,
|
|
1495
|
+
mode,
|
|
1496
|
+
},
|
|
1497
|
+
responseType: "file_explorer_response",
|
|
1498
|
+
timeout: 10000,
|
|
1457
1499
|
});
|
|
1458
1500
|
}
|
|
1459
1501
|
async requestDownloadToken(agentId, path, requestId) {
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
});
|
|
1467
|
-
return this.sendRequest({
|
|
1468
|
-
requestId: resolvedRequestId,
|
|
1469
|
-
message,
|
|
1470
|
-
timeout: 10000,
|
|
1471
|
-
options: { skipQueue: true },
|
|
1472
|
-
select: (msg) => {
|
|
1473
|
-
if (msg.type !== "file_download_token_response") {
|
|
1474
|
-
return null;
|
|
1475
|
-
}
|
|
1476
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1477
|
-
return null;
|
|
1478
|
-
}
|
|
1479
|
-
return msg.payload;
|
|
1502
|
+
return this.sendCorrelatedSessionRequest({
|
|
1503
|
+
requestId,
|
|
1504
|
+
message: {
|
|
1505
|
+
type: "file_download_token_request",
|
|
1506
|
+
agentId,
|
|
1507
|
+
path,
|
|
1480
1508
|
},
|
|
1509
|
+
responseType: "file_download_token_response",
|
|
1510
|
+
timeout: 10000,
|
|
1481
1511
|
});
|
|
1482
1512
|
}
|
|
1483
1513
|
async requestProjectIcon(cwd, requestId) {
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
});
|
|
1490
|
-
return this.sendRequest({
|
|
1491
|
-
requestId: resolvedRequestId,
|
|
1492
|
-
message,
|
|
1493
|
-
timeout: 10000,
|
|
1494
|
-
options: { skipQueue: true },
|
|
1495
|
-
select: (msg) => {
|
|
1496
|
-
if (msg.type !== "project_icon_response") {
|
|
1497
|
-
return null;
|
|
1498
|
-
}
|
|
1499
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1500
|
-
return null;
|
|
1501
|
-
}
|
|
1502
|
-
return msg.payload;
|
|
1514
|
+
return this.sendCorrelatedSessionRequest({
|
|
1515
|
+
requestId,
|
|
1516
|
+
message: {
|
|
1517
|
+
type: "project_icon_request",
|
|
1518
|
+
cwd,
|
|
1503
1519
|
},
|
|
1520
|
+
responseType: "project_icon_response",
|
|
1521
|
+
timeout: 10000,
|
|
1504
1522
|
});
|
|
1505
1523
|
}
|
|
1506
1524
|
// ============================================================================
|
|
1507
1525
|
// Provider Models / Commands
|
|
1508
1526
|
// ============================================================================
|
|
1509
1527
|
async listProviderModels(provider, options) {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
});
|
|
1517
|
-
return this.sendRequest({
|
|
1518
|
-
requestId: resolvedRequestId,
|
|
1519
|
-
message,
|
|
1520
|
-
timeout: 30000,
|
|
1521
|
-
options: { skipQueue: true },
|
|
1522
|
-
select: (msg) => {
|
|
1523
|
-
if (msg.type !== "list_provider_models_response") {
|
|
1524
|
-
return null;
|
|
1525
|
-
}
|
|
1526
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1527
|
-
return null;
|
|
1528
|
-
}
|
|
1529
|
-
return msg.payload;
|
|
1528
|
+
return this.sendCorrelatedSessionRequest({
|
|
1529
|
+
requestId: options?.requestId,
|
|
1530
|
+
message: {
|
|
1531
|
+
type: "list_provider_models_request",
|
|
1532
|
+
provider,
|
|
1533
|
+
cwd: options?.cwd,
|
|
1530
1534
|
},
|
|
1535
|
+
responseType: "list_provider_models_response",
|
|
1536
|
+
timeout: 30000,
|
|
1531
1537
|
});
|
|
1532
1538
|
}
|
|
1533
1539
|
async listAvailableProviders(options) {
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
});
|
|
1539
|
-
return this.sendRequest({
|
|
1540
|
-
requestId: resolvedRequestId,
|
|
1541
|
-
message,
|
|
1542
|
-
timeout: 30000,
|
|
1543
|
-
options: { skipQueue: true },
|
|
1544
|
-
select: (msg) => {
|
|
1545
|
-
if (msg.type !== "list_available_providers_response") {
|
|
1546
|
-
return null;
|
|
1547
|
-
}
|
|
1548
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1549
|
-
return null;
|
|
1550
|
-
}
|
|
1551
|
-
return msg.payload;
|
|
1540
|
+
return this.sendCorrelatedSessionRequest({
|
|
1541
|
+
requestId: options?.requestId,
|
|
1542
|
+
message: {
|
|
1543
|
+
type: "list_available_providers_request",
|
|
1552
1544
|
},
|
|
1545
|
+
responseType: "list_available_providers_response",
|
|
1546
|
+
timeout: 30000,
|
|
1553
1547
|
});
|
|
1554
1548
|
}
|
|
1555
1549
|
async listSpeechModels(requestId) {
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
});
|
|
1561
|
-
return this.sendRequest({
|
|
1562
|
-
requestId: resolvedRequestId,
|
|
1563
|
-
message,
|
|
1564
|
-
timeout: 30000,
|
|
1565
|
-
options: { skipQueue: true },
|
|
1566
|
-
select: (msg) => {
|
|
1567
|
-
if (msg.type !== "speech_models_list_response") {
|
|
1568
|
-
return null;
|
|
1569
|
-
}
|
|
1570
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1571
|
-
return null;
|
|
1572
|
-
}
|
|
1573
|
-
return msg.payload;
|
|
1550
|
+
return this.sendCorrelatedSessionRequest({
|
|
1551
|
+
requestId,
|
|
1552
|
+
message: {
|
|
1553
|
+
type: "speech_models_list_request",
|
|
1574
1554
|
},
|
|
1555
|
+
responseType: "speech_models_list_response",
|
|
1556
|
+
timeout: 30000,
|
|
1575
1557
|
});
|
|
1576
1558
|
}
|
|
1577
1559
|
async downloadSpeechModels(options) {
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
});
|
|
1584
|
-
return this.sendRequest({
|
|
1585
|
-
requestId: resolvedRequestId,
|
|
1586
|
-
message,
|
|
1587
|
-
timeout: 30 * 60 * 1000,
|
|
1588
|
-
options: { skipQueue: true },
|
|
1589
|
-
select: (msg) => {
|
|
1590
|
-
if (msg.type !== "speech_models_download_response") {
|
|
1591
|
-
return null;
|
|
1592
|
-
}
|
|
1593
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1594
|
-
return null;
|
|
1595
|
-
}
|
|
1596
|
-
return msg.payload;
|
|
1560
|
+
return this.sendCorrelatedSessionRequest({
|
|
1561
|
+
requestId: options?.requestId,
|
|
1562
|
+
message: {
|
|
1563
|
+
type: "speech_models_download_request",
|
|
1564
|
+
modelIds: options?.modelIds,
|
|
1597
1565
|
},
|
|
1566
|
+
responseType: "speech_models_download_response",
|
|
1567
|
+
timeout: 30 * 60 * 1000,
|
|
1598
1568
|
});
|
|
1599
1569
|
}
|
|
1600
1570
|
async listCommands(agentId, requestId) {
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
});
|
|
1607
|
-
return this.sendRequest({
|
|
1608
|
-
requestId: resolvedRequestId,
|
|
1609
|
-
message,
|
|
1610
|
-
timeout: 30000,
|
|
1611
|
-
options: { skipQueue: true },
|
|
1612
|
-
select: (msg) => {
|
|
1613
|
-
if (msg.type !== "list_commands_response") {
|
|
1614
|
-
return null;
|
|
1615
|
-
}
|
|
1616
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1617
|
-
return null;
|
|
1618
|
-
}
|
|
1619
|
-
return msg.payload;
|
|
1571
|
+
return this.sendCorrelatedSessionRequest({
|
|
1572
|
+
requestId,
|
|
1573
|
+
message: {
|
|
1574
|
+
type: "list_commands_request",
|
|
1575
|
+
agentId,
|
|
1620
1576
|
},
|
|
1577
|
+
responseType: "list_commands_response",
|
|
1578
|
+
timeout: 30000,
|
|
1621
1579
|
});
|
|
1622
1580
|
}
|
|
1623
1581
|
async executeCommand(agentId, commandName, args, requestId) {
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
});
|
|
1632
|
-
return this.sendRequest({
|
|
1633
|
-
requestId: resolvedRequestId,
|
|
1634
|
-
message,
|
|
1635
|
-
timeout: 30000,
|
|
1636
|
-
options: { skipQueue: true },
|
|
1637
|
-
select: (msg) => {
|
|
1638
|
-
if (msg.type !== "execute_command_response") {
|
|
1639
|
-
return null;
|
|
1640
|
-
}
|
|
1641
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1642
|
-
return null;
|
|
1643
|
-
}
|
|
1644
|
-
return msg.payload;
|
|
1582
|
+
return this.sendCorrelatedSessionRequest({
|
|
1583
|
+
requestId,
|
|
1584
|
+
message: {
|
|
1585
|
+
type: "execute_command_request",
|
|
1586
|
+
agentId,
|
|
1587
|
+
commandName,
|
|
1588
|
+
args,
|
|
1645
1589
|
},
|
|
1590
|
+
responseType: "execute_command_response",
|
|
1591
|
+
timeout: 30000,
|
|
1646
1592
|
});
|
|
1647
1593
|
}
|
|
1648
1594
|
// ============================================================================
|
|
@@ -1704,20 +1650,12 @@ export class DaemonClient {
|
|
|
1704
1650
|
agentId,
|
|
1705
1651
|
timeoutMs: timeout,
|
|
1706
1652
|
});
|
|
1707
|
-
const payload = await this.
|
|
1653
|
+
const payload = await this.sendCorrelatedRequest({
|
|
1708
1654
|
requestId,
|
|
1709
1655
|
message,
|
|
1656
|
+
responseType: "wait_for_finish_response",
|
|
1710
1657
|
timeout: timeout + 5000,
|
|
1711
1658
|
options: { skipQueue: true },
|
|
1712
|
-
select: (msg) => {
|
|
1713
|
-
if (msg.type !== "wait_for_finish_response") {
|
|
1714
|
-
return null;
|
|
1715
|
-
}
|
|
1716
|
-
if (msg.payload.requestId !== requestId) {
|
|
1717
|
-
return null;
|
|
1718
|
-
}
|
|
1719
|
-
return msg.payload;
|
|
1720
|
-
},
|
|
1721
1659
|
});
|
|
1722
1660
|
return {
|
|
1723
1661
|
status: payload.status,
|
|
@@ -1736,20 +1674,12 @@ export class DaemonClient {
|
|
|
1736
1674
|
cwd,
|
|
1737
1675
|
requestId: resolvedRequestId,
|
|
1738
1676
|
});
|
|
1739
|
-
return this.
|
|
1677
|
+
return this.sendCorrelatedRequest({
|
|
1740
1678
|
requestId: resolvedRequestId,
|
|
1741
1679
|
message,
|
|
1680
|
+
responseType: "list_terminals_response",
|
|
1742
1681
|
timeout: 10000,
|
|
1743
1682
|
options: { skipQueue: true },
|
|
1744
|
-
select: (msg) => {
|
|
1745
|
-
if (msg.type !== "list_terminals_response") {
|
|
1746
|
-
return null;
|
|
1747
|
-
}
|
|
1748
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1749
|
-
return null;
|
|
1750
|
-
}
|
|
1751
|
-
return msg.payload;
|
|
1752
|
-
},
|
|
1753
1683
|
});
|
|
1754
1684
|
}
|
|
1755
1685
|
async createTerminal(cwd, name, requestId) {
|
|
@@ -1760,20 +1690,12 @@ export class DaemonClient {
|
|
|
1760
1690
|
name,
|
|
1761
1691
|
requestId: resolvedRequestId,
|
|
1762
1692
|
});
|
|
1763
|
-
return this.
|
|
1693
|
+
return this.sendCorrelatedRequest({
|
|
1764
1694
|
requestId: resolvedRequestId,
|
|
1765
1695
|
message,
|
|
1696
|
+
responseType: "create_terminal_response",
|
|
1766
1697
|
timeout: 10000,
|
|
1767
1698
|
options: { skipQueue: true },
|
|
1768
|
-
select: (msg) => {
|
|
1769
|
-
if (msg.type !== "create_terminal_response") {
|
|
1770
|
-
return null;
|
|
1771
|
-
}
|
|
1772
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1773
|
-
return null;
|
|
1774
|
-
}
|
|
1775
|
-
return msg.payload;
|
|
1776
|
-
},
|
|
1777
1699
|
});
|
|
1778
1700
|
}
|
|
1779
1701
|
async subscribeTerminal(terminalId, requestId) {
|
|
@@ -1783,20 +1705,12 @@ export class DaemonClient {
|
|
|
1783
1705
|
terminalId,
|
|
1784
1706
|
requestId: resolvedRequestId,
|
|
1785
1707
|
});
|
|
1786
|
-
return this.
|
|
1708
|
+
return this.sendCorrelatedRequest({
|
|
1787
1709
|
requestId: resolvedRequestId,
|
|
1788
1710
|
message,
|
|
1711
|
+
responseType: "subscribe_terminal_response",
|
|
1789
1712
|
timeout: 10000,
|
|
1790
1713
|
options: { skipQueue: true },
|
|
1791
|
-
select: (msg) => {
|
|
1792
|
-
if (msg.type !== "subscribe_terminal_response") {
|
|
1793
|
-
return null;
|
|
1794
|
-
}
|
|
1795
|
-
if (msg.payload.requestId !== resolvedRequestId) {
|
|
1796
|
-
return null;
|
|
1797
|
-
}
|
|
1798
|
-
return msg.payload;
|
|
1799
|
-
},
|
|
1800
1714
|
});
|
|
1801
1715
|
}
|
|
1802
1716
|
unsubscribeTerminal(terminalId) {
|
|
@@ -1819,20 +1733,94 @@ export class DaemonClient {
|
|
|
1819
1733
|
terminalId,
|
|
1820
1734
|
requestId: resolvedRequestId,
|
|
1821
1735
|
});
|
|
1822
|
-
return this.
|
|
1736
|
+
return this.sendCorrelatedRequest({
|
|
1823
1737
|
requestId: resolvedRequestId,
|
|
1824
1738
|
message,
|
|
1739
|
+
responseType: "kill_terminal_response",
|
|
1825
1740
|
timeout: 10000,
|
|
1826
1741
|
options: { skipQueue: true },
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
async attachTerminalStream(terminalId, options, requestId) {
|
|
1745
|
+
const resolvedRequestId = this.createRequestId(requestId);
|
|
1746
|
+
const message = SessionInboundMessageSchema.parse({
|
|
1747
|
+
type: "attach_terminal_stream_request",
|
|
1748
|
+
terminalId,
|
|
1749
|
+
requestId: resolvedRequestId,
|
|
1750
|
+
...(options?.resumeOffset !== undefined ? { resumeOffset: options.resumeOffset } : {}),
|
|
1751
|
+
...(options?.rows !== undefined ? { rows: options.rows } : {}),
|
|
1752
|
+
...(options?.cols !== undefined ? { cols: options.cols } : {}),
|
|
1753
|
+
});
|
|
1754
|
+
return this.sendCorrelatedRequest({
|
|
1755
|
+
requestId: resolvedRequestId,
|
|
1756
|
+
message,
|
|
1757
|
+
responseType: "attach_terminal_stream_response",
|
|
1758
|
+
timeout: 10000,
|
|
1759
|
+
options: { skipQueue: true },
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
async detachTerminalStream(streamId, requestId) {
|
|
1763
|
+
const resolvedRequestId = this.createRequestId(requestId);
|
|
1764
|
+
const message = SessionInboundMessageSchema.parse({
|
|
1765
|
+
type: "detach_terminal_stream_request",
|
|
1766
|
+
streamId,
|
|
1767
|
+
requestId: resolvedRequestId,
|
|
1768
|
+
});
|
|
1769
|
+
const payload = await this.sendCorrelatedRequest({
|
|
1770
|
+
requestId: resolvedRequestId,
|
|
1771
|
+
message,
|
|
1772
|
+
responseType: "detach_terminal_stream_response",
|
|
1773
|
+
timeout: 10000,
|
|
1774
|
+
options: { skipQueue: true },
|
|
1775
|
+
});
|
|
1776
|
+
this.terminalStreams.clearStream({ streamId });
|
|
1777
|
+
return payload;
|
|
1778
|
+
}
|
|
1779
|
+
onTerminalStreamData(streamId, handler) {
|
|
1780
|
+
return this.terminalStreams.subscribe({ streamId, handler });
|
|
1781
|
+
}
|
|
1782
|
+
async waitForTerminalStreamData(streamId, predicate, timeout = 5000) {
|
|
1783
|
+
return new Promise((resolve, reject) => {
|
|
1784
|
+
const timeoutHandle = setTimeout(() => {
|
|
1785
|
+
unsubscribe();
|
|
1786
|
+
reject(new Error(`Timeout waiting for terminal stream data (${timeout}ms)`));
|
|
1787
|
+
}, timeout);
|
|
1788
|
+
const unsubscribe = this.onTerminalStreamData(streamId, (chunk) => {
|
|
1789
|
+
if (!predicate(chunk)) {
|
|
1790
|
+
return;
|
|
1833
1791
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1792
|
+
clearTimeout(timeoutHandle);
|
|
1793
|
+
unsubscribe();
|
|
1794
|
+
resolve(chunk);
|
|
1795
|
+
});
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
sendTerminalStreamInput(streamId, data) {
|
|
1799
|
+
const payload = typeof data === "string" ? encodeUtf8String(data) : data;
|
|
1800
|
+
this.sendBinaryFrame({
|
|
1801
|
+
channel: BinaryMuxChannel.Terminal,
|
|
1802
|
+
messageType: TerminalBinaryMessageType.InputUtf8,
|
|
1803
|
+
streamId,
|
|
1804
|
+
offset: 0,
|
|
1805
|
+
payload,
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
sendTerminalStreamKey(streamId, input) {
|
|
1809
|
+
const encoded = encodeTerminalKeyInput(input);
|
|
1810
|
+
if (!encoded) {
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
this.sendTerminalStreamInput(streamId, encoded);
|
|
1814
|
+
}
|
|
1815
|
+
sendTerminalStreamAck(streamId, offset) {
|
|
1816
|
+
const normalizedOffset = Math.max(0, Math.floor(offset));
|
|
1817
|
+
this.terminalStreams.noteAck({ streamId, offset: normalizedOffset });
|
|
1818
|
+
this.sendBinaryFrame({
|
|
1819
|
+
channel: BinaryMuxChannel.Terminal,
|
|
1820
|
+
messageType: TerminalBinaryMessageType.Ack,
|
|
1821
|
+
streamId,
|
|
1822
|
+
offset: normalizedOffset,
|
|
1823
|
+
payload: new Uint8Array(0),
|
|
1836
1824
|
});
|
|
1837
1825
|
}
|
|
1838
1826
|
async waitForTerminalOutput(terminalId, timeout = 5000) {
|
|
@@ -1883,6 +1871,14 @@ export class DaemonClient {
|
|
|
1883
1871
|
const rawData = data && typeof data === "object" && "data" in data
|
|
1884
1872
|
? data.data
|
|
1885
1873
|
: data;
|
|
1874
|
+
const rawBytes = asUint8Array(rawData);
|
|
1875
|
+
if (rawBytes) {
|
|
1876
|
+
const frame = decodeBinaryMuxFrame(rawBytes);
|
|
1877
|
+
if (frame) {
|
|
1878
|
+
this.handleBinaryFrame(frame);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1886
1882
|
const payload = decodeMessageData(rawData);
|
|
1887
1883
|
if (!payload) {
|
|
1888
1884
|
return;
|
|
@@ -1905,6 +1901,20 @@ export class DaemonClient {
|
|
|
1905
1901
|
}
|
|
1906
1902
|
this.handleSessionMessage(parsed.data.message);
|
|
1907
1903
|
}
|
|
1904
|
+
handleBinaryFrame(frame) {
|
|
1905
|
+
if (frame.channel === BinaryMuxChannel.Terminal &&
|
|
1906
|
+
frame.messageType === TerminalBinaryMessageType.OutputUtf8) {
|
|
1907
|
+
const chunk = {
|
|
1908
|
+
streamId: frame.streamId,
|
|
1909
|
+
offset: frame.offset,
|
|
1910
|
+
endOffset: frame.offset + (frame.payload?.byteLength ?? 0),
|
|
1911
|
+
replay: Boolean((frame.flags ?? 0) & TerminalBinaryFlags.Replay),
|
|
1912
|
+
data: frame.payload ?? new Uint8Array(0),
|
|
1913
|
+
};
|
|
1914
|
+
this.terminalStreams.receiveChunk({ chunk });
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1908
1918
|
updateConnectionState(next) {
|
|
1909
1919
|
this.connectionState = next;
|
|
1910
1920
|
for (const listener of this.connectionListeners) {
|
|
@@ -1937,6 +1947,7 @@ export class DaemonClient {
|
|
|
1937
1947
|
// and responses from the previous connection will never arrive.
|
|
1938
1948
|
this.clearWaiters(new Error(reason ?? "Connection lost"));
|
|
1939
1949
|
this.rejectPendingSendQueue(new Error(reason ?? "Connection lost"));
|
|
1950
|
+
this.terminalStreams.clearAll();
|
|
1940
1951
|
this.updateConnectionState({
|
|
1941
1952
|
status: "disconnected",
|
|
1942
1953
|
...(reason ? { reason } : {}),
|
|
@@ -1950,6 +1961,9 @@ export class DaemonClient {
|
|
|
1950
1961
|
}, delay);
|
|
1951
1962
|
}
|
|
1952
1963
|
handleSessionMessage(msg) {
|
|
1964
|
+
if (msg.type === "terminal_stream_exit") {
|
|
1965
|
+
this.terminalStreams.clearStream({ streamId: msg.payload.streamId });
|
|
1966
|
+
}
|
|
1953
1967
|
if (this.rawMessageListeners.size > 0) {
|
|
1954
1968
|
for (const handler of this.rawMessageListeners) {
|
|
1955
1969
|
try {
|
|
@@ -2016,6 +2030,8 @@ export class DaemonClient {
|
|
|
2016
2030
|
agentId: msg.payload.agentId,
|
|
2017
2031
|
event: msg.payload.event,
|
|
2018
2032
|
timestamp: msg.payload.timestamp,
|
|
2033
|
+
...(typeof msg.payload.seq === "number" ? { seq: msg.payload.seq } : {}),
|
|
2034
|
+
...(typeof msg.payload.epoch === "string" ? { epoch: msg.payload.epoch } : {}),
|
|
2019
2035
|
};
|
|
2020
2036
|
case "status":
|
|
2021
2037
|
return { type: "status", payload: msg.payload };
|
|
@@ -2101,333 +2117,6 @@ export class DaemonClient {
|
|
|
2101
2117
|
return { promise, cancel };
|
|
2102
2118
|
}
|
|
2103
2119
|
}
|
|
2104
|
-
// ============================================================================
|
|
2105
|
-
// Helpers
|
|
2106
|
-
// ============================================================================
|
|
2107
|
-
function defaultWebSocketFactory(url, _options) {
|
|
2108
|
-
const globalWs = globalThis.WebSocket;
|
|
2109
|
-
if (!globalWs) {
|
|
2110
|
-
throw new Error("WebSocket is not available in this runtime");
|
|
2111
|
-
}
|
|
2112
|
-
return new globalWs(url);
|
|
2113
|
-
}
|
|
2114
|
-
function createWebSocketTransportFactory(factory) {
|
|
2115
|
-
return ({ url, headers }) => {
|
|
2116
|
-
const ws = factory(url, { headers });
|
|
2117
|
-
return {
|
|
2118
|
-
send: (data) => {
|
|
2119
|
-
if (typeof ws.readyState === "number" && ws.readyState !== 1) {
|
|
2120
|
-
throw new Error(`WebSocket not open (readyState=${ws.readyState})`);
|
|
2121
|
-
}
|
|
2122
|
-
ws.send(data);
|
|
2123
|
-
},
|
|
2124
|
-
close: (code, reason) => ws.close(code, reason),
|
|
2125
|
-
onOpen: (handler) => bindWsHandler(ws, "open", handler),
|
|
2126
|
-
onClose: (handler) => bindWsHandler(ws, "close", handler),
|
|
2127
|
-
onError: (handler) => bindWsHandler(ws, "error", handler),
|
|
2128
|
-
onMessage: (handler) => bindWsHandler(ws, "message", handler),
|
|
2129
|
-
};
|
|
2130
|
-
};
|
|
2131
|
-
}
|
|
2132
|
-
function createRelayE2eeTransportFactory(args) {
|
|
2133
|
-
return ({ url, headers }) => {
|
|
2134
|
-
const base = args.baseFactory({ url, headers });
|
|
2135
|
-
return createEncryptedTransport(base, args.daemonPublicKeyB64, args.logger);
|
|
2136
|
-
};
|
|
2137
|
-
}
|
|
2138
|
-
function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
|
|
2139
|
-
let channel = null;
|
|
2140
|
-
let opened = false;
|
|
2141
|
-
let closed = false;
|
|
2142
|
-
const openHandlers = new Set();
|
|
2143
|
-
const closeHandlers = new Set();
|
|
2144
|
-
const errorHandlers = new Set();
|
|
2145
|
-
const messageHandlers = new Set();
|
|
2146
|
-
const emitOpen = () => {
|
|
2147
|
-
if (opened || closed)
|
|
2148
|
-
return;
|
|
2149
|
-
opened = true;
|
|
2150
|
-
for (const handler of openHandlers) {
|
|
2151
|
-
try {
|
|
2152
|
-
handler();
|
|
2153
|
-
}
|
|
2154
|
-
catch {
|
|
2155
|
-
// no-op
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
};
|
|
2159
|
-
const emitClose = (event) => {
|
|
2160
|
-
if (closed)
|
|
2161
|
-
return;
|
|
2162
|
-
closed = true;
|
|
2163
|
-
for (const handler of closeHandlers) {
|
|
2164
|
-
try {
|
|
2165
|
-
handler(event);
|
|
2166
|
-
}
|
|
2167
|
-
catch {
|
|
2168
|
-
// no-op
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
};
|
|
2172
|
-
const emitError = (event) => {
|
|
2173
|
-
if (closed)
|
|
2174
|
-
return;
|
|
2175
|
-
for (const handler of errorHandlers) {
|
|
2176
|
-
try {
|
|
2177
|
-
handler(event);
|
|
2178
|
-
}
|
|
2179
|
-
catch {
|
|
2180
|
-
// no-op
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
};
|
|
2184
|
-
const emitMessage = (data) => {
|
|
2185
|
-
if (closed)
|
|
2186
|
-
return;
|
|
2187
|
-
for (const handler of messageHandlers) {
|
|
2188
|
-
try {
|
|
2189
|
-
handler(data);
|
|
2190
|
-
}
|
|
2191
|
-
catch {
|
|
2192
|
-
// no-op
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
};
|
|
2196
|
-
const relayTransport = {
|
|
2197
|
-
send: (data) => {
|
|
2198
|
-
if (typeof data === "string") {
|
|
2199
|
-
base.send(data);
|
|
2200
|
-
return;
|
|
2201
|
-
}
|
|
2202
|
-
if (data instanceof ArrayBuffer) {
|
|
2203
|
-
if (typeof TextDecoder !== "undefined") {
|
|
2204
|
-
base.send(new TextDecoder().decode(data));
|
|
2205
|
-
return;
|
|
2206
|
-
}
|
|
2207
|
-
if (typeof Buffer !== "undefined") {
|
|
2208
|
-
base.send(Buffer.from(data).toString("utf8"));
|
|
2209
|
-
return;
|
|
2210
|
-
}
|
|
2211
|
-
base.send(String(data));
|
|
2212
|
-
return;
|
|
2213
|
-
}
|
|
2214
|
-
base.send(String(data));
|
|
2215
|
-
},
|
|
2216
|
-
close: (code, reason) => base.close(code, reason),
|
|
2217
|
-
onmessage: null,
|
|
2218
|
-
onclose: null,
|
|
2219
|
-
onerror: null,
|
|
2220
|
-
};
|
|
2221
|
-
const startHandshake = async () => {
|
|
2222
|
-
try {
|
|
2223
|
-
channel = await createClientChannel(relayTransport, daemonPublicKeyB64, {
|
|
2224
|
-
onopen: emitOpen,
|
|
2225
|
-
onmessage: (data) => emitMessage(data),
|
|
2226
|
-
onclose: (code, reason) => emitClose({ code, reason }),
|
|
2227
|
-
onerror: (error) => emitError(error),
|
|
2228
|
-
});
|
|
2229
|
-
}
|
|
2230
|
-
catch (error) {
|
|
2231
|
-
logger.warn({ err: error }, "relay_e2ee_handshake_failed");
|
|
2232
|
-
emitError(error);
|
|
2233
|
-
base.close(1011, "E2EE handshake failed");
|
|
2234
|
-
}
|
|
2235
|
-
};
|
|
2236
|
-
base.onOpen(() => {
|
|
2237
|
-
void startHandshake();
|
|
2238
|
-
});
|
|
2239
|
-
base.onMessage((event) => {
|
|
2240
|
-
relayTransport.onmessage?.(extractRelayMessageData(event));
|
|
2241
|
-
});
|
|
2242
|
-
base.onClose((event) => {
|
|
2243
|
-
const record = event;
|
|
2244
|
-
relayTransport.onclose?.(record?.code ?? 0, record?.reason ?? "");
|
|
2245
|
-
emitClose(event);
|
|
2246
|
-
});
|
|
2247
|
-
base.onError((event) => {
|
|
2248
|
-
relayTransport.onerror?.(event instanceof Error ? event : new Error(String(event)));
|
|
2249
|
-
emitError(event);
|
|
2250
|
-
});
|
|
2251
|
-
return {
|
|
2252
|
-
send: (data) => {
|
|
2253
|
-
if (!channel) {
|
|
2254
|
-
throw new Error("Encrypted channel not ready");
|
|
2255
|
-
}
|
|
2256
|
-
void channel.send(data).catch((error) => {
|
|
2257
|
-
emitError(error);
|
|
2258
|
-
});
|
|
2259
|
-
},
|
|
2260
|
-
close: (code, reason) => {
|
|
2261
|
-
if (channel) {
|
|
2262
|
-
channel.close(code, reason);
|
|
2263
|
-
}
|
|
2264
|
-
else {
|
|
2265
|
-
base.close(code, reason);
|
|
2266
|
-
}
|
|
2267
|
-
emitClose({ code, reason });
|
|
2268
|
-
},
|
|
2269
|
-
onMessage: (handler) => {
|
|
2270
|
-
messageHandlers.add(handler);
|
|
2271
|
-
return () => messageHandlers.delete(handler);
|
|
2272
|
-
},
|
|
2273
|
-
onOpen: (handler) => {
|
|
2274
|
-
openHandlers.add(handler);
|
|
2275
|
-
if (opened) {
|
|
2276
|
-
try {
|
|
2277
|
-
handler();
|
|
2278
|
-
}
|
|
2279
|
-
catch {
|
|
2280
|
-
// no-op
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
return () => openHandlers.delete(handler);
|
|
2284
|
-
},
|
|
2285
|
-
onClose: (handler) => {
|
|
2286
|
-
closeHandlers.add(handler);
|
|
2287
|
-
if (closed) {
|
|
2288
|
-
try {
|
|
2289
|
-
handler();
|
|
2290
|
-
}
|
|
2291
|
-
catch {
|
|
2292
|
-
// no-op
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
return () => closeHandlers.delete(handler);
|
|
2296
|
-
},
|
|
2297
|
-
onError: (handler) => {
|
|
2298
|
-
errorHandlers.add(handler);
|
|
2299
|
-
return () => errorHandlers.delete(handler);
|
|
2300
|
-
},
|
|
2301
|
-
};
|
|
2302
|
-
}
|
|
2303
|
-
function extractRelayMessageData(event) {
|
|
2304
|
-
const raw = event && typeof event === "object" && "data" in event
|
|
2305
|
-
? event.data
|
|
2306
|
-
: event;
|
|
2307
|
-
if (typeof raw === "string")
|
|
2308
|
-
return raw;
|
|
2309
|
-
if (raw instanceof ArrayBuffer)
|
|
2310
|
-
return raw;
|
|
2311
|
-
if (ArrayBuffer.isView(raw)) {
|
|
2312
|
-
const view = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
2313
|
-
const out = new Uint8Array(view.byteLength);
|
|
2314
|
-
out.set(view);
|
|
2315
|
-
return out.buffer;
|
|
2316
|
-
}
|
|
2317
|
-
return String(raw ?? "");
|
|
2318
|
-
}
|
|
2319
|
-
function bindWsHandler(ws, event, handler) {
|
|
2320
|
-
if (typeof ws.addEventListener === "function") {
|
|
2321
|
-
ws.addEventListener(event, handler);
|
|
2322
|
-
return () => {
|
|
2323
|
-
if (typeof ws.removeEventListener === "function") {
|
|
2324
|
-
ws.removeEventListener(event, handler);
|
|
2325
|
-
}
|
|
2326
|
-
};
|
|
2327
|
-
}
|
|
2328
|
-
if (typeof ws.on === "function") {
|
|
2329
|
-
ws.on(event, handler);
|
|
2330
|
-
return () => {
|
|
2331
|
-
if (typeof ws.off === "function") {
|
|
2332
|
-
ws.off(event, handler);
|
|
2333
|
-
return;
|
|
2334
|
-
}
|
|
2335
|
-
if (typeof ws.removeListener === "function") {
|
|
2336
|
-
ws.removeListener(event, handler);
|
|
2337
|
-
}
|
|
2338
|
-
};
|
|
2339
|
-
}
|
|
2340
|
-
const prop = `on${event}`;
|
|
2341
|
-
const previous = ws[prop];
|
|
2342
|
-
ws[prop] = handler;
|
|
2343
|
-
return () => {
|
|
2344
|
-
if (ws[prop] === handler) {
|
|
2345
|
-
ws[prop] = previous ?? null;
|
|
2346
|
-
}
|
|
2347
|
-
};
|
|
2348
|
-
}
|
|
2349
|
-
function describeTransportClose(event) {
|
|
2350
|
-
if (!event) {
|
|
2351
|
-
return "Transport closed";
|
|
2352
|
-
}
|
|
2353
|
-
if (event instanceof Error) {
|
|
2354
|
-
return event.message;
|
|
2355
|
-
}
|
|
2356
|
-
if (typeof event === "string") {
|
|
2357
|
-
return event;
|
|
2358
|
-
}
|
|
2359
|
-
if (typeof event === "object") {
|
|
2360
|
-
const record = event;
|
|
2361
|
-
if (typeof record.reason === "string" && record.reason.trim().length > 0) {
|
|
2362
|
-
return record.reason.trim();
|
|
2363
|
-
}
|
|
2364
|
-
if (typeof record.message === "string" && record.message.trim().length > 0) {
|
|
2365
|
-
return record.message.trim();
|
|
2366
|
-
}
|
|
2367
|
-
if (typeof record.code === "number") {
|
|
2368
|
-
return `Transport closed (code ${record.code})`;
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
return "Transport closed";
|
|
2372
|
-
}
|
|
2373
|
-
function describeTransportError(event) {
|
|
2374
|
-
if (!event) {
|
|
2375
|
-
return "Transport error";
|
|
2376
|
-
}
|
|
2377
|
-
if (event instanceof Error) {
|
|
2378
|
-
return event.message;
|
|
2379
|
-
}
|
|
2380
|
-
if (typeof event === "string") {
|
|
2381
|
-
return event;
|
|
2382
|
-
}
|
|
2383
|
-
if (typeof event === "object") {
|
|
2384
|
-
const record = event;
|
|
2385
|
-
if (typeof record.message === "string" && record.message.trim().length > 0) {
|
|
2386
|
-
return record.message.trim();
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
return "Transport error";
|
|
2390
|
-
}
|
|
2391
|
-
function safeRandomId() {
|
|
2392
|
-
try {
|
|
2393
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
2394
|
-
return crypto.randomUUID();
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
catch {
|
|
2398
|
-
// ignore
|
|
2399
|
-
}
|
|
2400
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
2401
|
-
}
|
|
2402
|
-
function decodeMessageData(data) {
|
|
2403
|
-
if (data === null || data === undefined) {
|
|
2404
|
-
return null;
|
|
2405
|
-
}
|
|
2406
|
-
if (typeof data === "string") {
|
|
2407
|
-
return data;
|
|
2408
|
-
}
|
|
2409
|
-
if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) {
|
|
2410
|
-
if (typeof Buffer !== "undefined") {
|
|
2411
|
-
return Buffer.from(data).toString("utf8");
|
|
2412
|
-
}
|
|
2413
|
-
if (typeof TextDecoder !== "undefined") {
|
|
2414
|
-
return new TextDecoder().decode(data);
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
if (ArrayBuffer.isView(data)) {
|
|
2418
|
-
const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
2419
|
-
if (typeof Buffer !== "undefined") {
|
|
2420
|
-
return Buffer.from(view).toString("utf8");
|
|
2421
|
-
}
|
|
2422
|
-
if (typeof TextDecoder !== "undefined") {
|
|
2423
|
-
return new TextDecoder().decode(view);
|
|
2424
|
-
}
|
|
2425
|
-
}
|
|
2426
|
-
if (typeof data.toString === "function") {
|
|
2427
|
-
return data.toString();
|
|
2428
|
-
}
|
|
2429
|
-
return null;
|
|
2430
|
-
}
|
|
2431
2120
|
function resolveAgentConfig(options) {
|
|
2432
2121
|
const { config, provider, cwd, initialPrompt: _initialPrompt, images: _images, git: _git, worktreeName: _worktreeName, requestId: _requestId, labels: _labels, ...overrides } = options;
|
|
2433
2122
|
const baseConfig = {
|