@camerontaylor/paseo-client 0.8.0-fork.1 → 0.8.0-fork.3
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/connection/index.d.ts +27 -21
- package/dist/connection/index.js +89 -85
- package/dist/connection/legacy-workspaces.d.ts +19 -0
- package/dist/connection/legacy-workspaces.js +160 -0
- package/dist/connection/legacy.d.ts +31 -0
- package/dist/connection/legacy.js +245 -0
- package/dist/connection/owned.d.ts +36 -0
- package/dist/connection/owned.js +210 -0
- package/dist/daemon-client.d.ts +88 -57
- package/dist/daemon-client.js +259 -392
- package/dist/index.d.ts +39 -10
- package/dist/index.js +254 -125
- package/dist/provider-snapshots/index.d.ts +1 -3
- package/dist/provider-snapshots/index.js +23 -27
- package/dist/terminal-stream-router.d.ts +7 -10
- package/dist/terminal-stream-router.js +41 -88
- package/dist/timeline-subscription/index.d.ts +25 -0
- package/dist/timeline-subscription/index.js +56 -0
- package/package.json +7 -3
- package/dist/creation/index.d.ts +0 -47
- package/dist/creation/index.js +0 -216
package/dist/daemon-client.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import { subscribeTimeline } from "./timeline-subscription/index.js";
|
|
1
2
|
import { ProviderSnapshotUpdates } from "./provider-snapshots/index.js";
|
|
2
3
|
import { ConnectionSubscriptions, DEFAULT_CLIENT_CAPABILITIES, } from "./connection/index.js";
|
|
3
|
-
import { CreationClient } from "./creation/index.js";
|
|
4
4
|
import { CLIENT_CAPS } from "@camerontaylor/paseo-protocol/client-capabilities";
|
|
5
5
|
import { parsePluginSourceReference } from "@camerontaylor/paseo-protocol/plugin-source-reference";
|
|
6
6
|
import { AgentCreateFailedStatusPayloadSchema, AgentCreatedStatusPayloadSchema, AgentRefreshedStatusPayloadSchema, AgentResumedStatusPayloadSchema, parseServerInfoStatusPayload, RestartRequestedStatusPayloadSchema, ShutdownRequestedStatusPayloadSchema, DaemonUpdateResponseSchema, SessionInboundMessageSchema, } from "@camerontaylor/paseo-protocol/messages";
|
|
7
7
|
import { validateWSOutboundMessage } from "@camerontaylor/paseo-protocol/validation/ws-outbound";
|
|
8
8
|
import { isRelayClientWebSocketUrl } from "@camerontaylor/paseo-protocol/daemon-endpoints";
|
|
9
|
-
import { terminalSubscriptionKey } from "@camerontaylor/paseo-protocol/terminal-subscription-key";
|
|
10
9
|
import { asUint8Array, decodeFileTransferFrame, encodeFileTransferFrame, decodeTerminalStreamFrame, FileTransferOpcode, TerminalStreamOpcode, } from "@camerontaylor/paseo-protocol/binary-frames/index";
|
|
11
10
|
import { createRelayE2eeTransportFactory, createWebSocketTransportFactory, decodeMessageData, defaultWebSocketFactory, describeTransportClose, describeTransportError, } from "./daemon-client-transport.js";
|
|
12
11
|
import { DaemonClientRuntimeMetrics } from "./daemon-client-runtime-metrics.js";
|
|
@@ -76,6 +75,13 @@ function normalizeListCommandsOptions(input, legacyOptions) {
|
|
|
76
75
|
}
|
|
77
76
|
return { agentId: input, ...legacyOptions };
|
|
78
77
|
}
|
|
78
|
+
export class DaemonConnectionError extends Error {
|
|
79
|
+
constructor(message, code = "DAEMON_CONNECTION_LOST") {
|
|
80
|
+
super(message);
|
|
81
|
+
this.code = code;
|
|
82
|
+
this.name = "DaemonConnectionError";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
79
85
|
class DaemonRpcError extends Error {
|
|
80
86
|
constructor(params) {
|
|
81
87
|
const parts = [params.error];
|
|
@@ -232,14 +238,29 @@ export class DaemonClient {
|
|
|
232
238
|
constructor(config) {
|
|
233
239
|
this.config = config;
|
|
234
240
|
this.providerSnapshotUpdates = new ProviderSnapshotUpdates({
|
|
241
|
+
active: (message) => this.owned.owns(message),
|
|
235
242
|
fetch: (cwd) => this.requestProvidersSnapshot({ cwd }),
|
|
236
243
|
emit: (message) => this.deliverSessionMessage(message),
|
|
237
244
|
failed: (error) => this.logger.error({ err: error }, "Failed to resolve provider snapshot"),
|
|
238
245
|
});
|
|
239
|
-
this.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
246
|
+
this.owned = new ConnectionSubscriptions({
|
|
247
|
+
send: (message) => this.sendSessionMessageOrThrow(SessionInboundMessageSchema.parse({ ...message, requestId: this.createRequestId() })),
|
|
248
|
+
release: async (subscriptionId) => {
|
|
249
|
+
if (!this.isConnected)
|
|
250
|
+
return;
|
|
251
|
+
try {
|
|
252
|
+
await this.sendCorrelatedSessionRequest({
|
|
253
|
+
message: { type: "subscription.release.request", subscriptionId },
|
|
254
|
+
responseType: "subscription.release.response",
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
this.disposeTransport(1001, "Subscription release failed");
|
|
259
|
+
this.scheduleReconnect({ reason: "Subscription release failed" });
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
failed: (error) => this.logger.error({ err: error }, "Subscription failed"),
|
|
243
264
|
});
|
|
244
265
|
this.transport = null;
|
|
245
266
|
this.transportCleanup = [];
|
|
@@ -259,9 +280,6 @@ export class DaemonClient {
|
|
|
259
280
|
this.connectReject = null;
|
|
260
281
|
this.lastErrorValue = null;
|
|
261
282
|
this.connectionState = { status: "idle" };
|
|
262
|
-
this.checkoutDiffSubscriptions = new Map();
|
|
263
|
-
this.terminalDirectorySubscriptions = new Map();
|
|
264
|
-
this.fileSubscriptions = new Map();
|
|
265
283
|
this.terminalStreams = new TerminalStreamRouter();
|
|
266
284
|
this.pendingBinaryFileReads = new Map();
|
|
267
285
|
this.activeBinaryFileTransfers = new Map();
|
|
@@ -274,39 +292,6 @@ export class DaemonClient {
|
|
|
274
292
|
this.livenessHeartbeatTimer = null;
|
|
275
293
|
this.lastLivenessRttMs = null;
|
|
276
294
|
this.consecutiveLivenessFailures = 0;
|
|
277
|
-
// ============================================================================
|
|
278
|
-
// Agent Lifecycle
|
|
279
|
-
// ============================================================================
|
|
280
|
-
this.creations = new CreationClient({
|
|
281
|
-
supports: () => this.lastServerInfoMessage?.features?.creationLifecycle === true,
|
|
282
|
-
connected: () => this.connectionState.status === "connected",
|
|
283
|
-
requestId: () => this.createRequestId(),
|
|
284
|
-
request: (kind, input) => kind === "workspace"
|
|
285
|
-
? this.sendCorrelatedSessionRequest({
|
|
286
|
-
requestId: input.requestId,
|
|
287
|
-
message: { ...input, type: "workspace.create.request" },
|
|
288
|
-
responseType: "workspace.create.response",
|
|
289
|
-
timeout: 0,
|
|
290
|
-
})
|
|
291
|
-
: this.sendCorrelatedSessionRequest({
|
|
292
|
-
requestId: input.requestId,
|
|
293
|
-
message: { ...input, type: "agent.create.request" },
|
|
294
|
-
responseType: "agent.create.response",
|
|
295
|
-
timeout: 0,
|
|
296
|
-
}),
|
|
297
|
-
subscribe: async (kind, idempotencyKey, subscribe = true) => {
|
|
298
|
-
const result = await this.sendCorrelatedSessionRequest({
|
|
299
|
-
message: { type: "creation.subscribe.request", kind, idempotencyKey, subscribe },
|
|
300
|
-
responseType: "creation.subscribe.response",
|
|
301
|
-
});
|
|
302
|
-
if (result.error)
|
|
303
|
-
throw new Error(result.error);
|
|
304
|
-
return result.snapshot;
|
|
305
|
-
},
|
|
306
|
-
legacyAgent: (input) => this.createLegacyAgent(input),
|
|
307
|
-
legacyWorkspace: (input) => this.createLegacyWorkspace(input, input.requestId),
|
|
308
|
-
sendMessage: (id, text, options) => this.sendMessage(id, text, options),
|
|
309
|
-
});
|
|
310
295
|
this.logger = config.logger ?? consoleLogger;
|
|
311
296
|
this.logConnectionPath = isRelayClientWebSocketUrl(this.config.url) ? "relay" : "direct";
|
|
312
297
|
let parsedUrlForLog = null;
|
|
@@ -494,7 +479,10 @@ export class DaemonClient {
|
|
|
494
479
|
reasonCode: "transport_error",
|
|
495
480
|
});
|
|
496
481
|
}),
|
|
497
|
-
transport.onMessage((data) =>
|
|
482
|
+
transport.onMessage((data) => {
|
|
483
|
+
if (this.transport === transport)
|
|
484
|
+
this.handleTransportMessage(data);
|
|
485
|
+
}),
|
|
498
486
|
];
|
|
499
487
|
}
|
|
500
488
|
catch (error) {
|
|
@@ -530,7 +518,6 @@ export class DaemonClient {
|
|
|
530
518
|
return;
|
|
531
519
|
}
|
|
532
520
|
this.shouldReconnect = false;
|
|
533
|
-
this.subscriptions.close();
|
|
534
521
|
this.connectPromise = null;
|
|
535
522
|
this.connectResolve = null;
|
|
536
523
|
this.connectReject = null;
|
|
@@ -542,11 +529,10 @@ export class DaemonClient {
|
|
|
542
529
|
this.disposeTransport(1000, "Client closed");
|
|
543
530
|
this.providerSnapshotUpdates.clear();
|
|
544
531
|
this.clearWaiters(new Error("Daemon client closed"));
|
|
545
|
-
this.
|
|
532
|
+
await this.owned.close();
|
|
546
533
|
this.rejectPendingSendQueue(new Error("Daemon client closed"));
|
|
547
534
|
this.rejectPingProbe(new Error("Daemon client closed"));
|
|
548
535
|
this.terminalStreams.clearSlots();
|
|
549
|
-
this.fileSubscriptions.clear();
|
|
550
536
|
this.lastServerInfoMessage = null;
|
|
551
537
|
if (this.runtimeMetricsInterval) {
|
|
552
538
|
clearInterval(this.runtimeMetricsInterval);
|
|
@@ -604,10 +590,8 @@ export class DaemonClient {
|
|
|
604
590
|
// ============================================================================
|
|
605
591
|
subscribe(handler) {
|
|
606
592
|
this.eventListeners.add(handler);
|
|
607
|
-
this.updateEventSubscriptions();
|
|
608
593
|
return () => {
|
|
609
594
|
this.eventListeners.delete(handler);
|
|
610
|
-
this.updateEventSubscriptions();
|
|
611
595
|
};
|
|
612
596
|
}
|
|
613
597
|
subscribeRawMessages(handler) {
|
|
@@ -626,7 +610,6 @@ export class DaemonClient {
|
|
|
626
610
|
this.messageHandlers.set(type, new Set());
|
|
627
611
|
}
|
|
628
612
|
this.messageHandlers.get(type).add(handler);
|
|
629
|
-
this.updateEventSubscriptions();
|
|
630
613
|
return () => {
|
|
631
614
|
const handlers = this.messageHandlers.get(type);
|
|
632
615
|
if (!handlers) {
|
|
@@ -636,7 +619,6 @@ export class DaemonClient {
|
|
|
636
619
|
if (handlers.size === 0) {
|
|
637
620
|
this.messageHandlers.delete(type);
|
|
638
621
|
}
|
|
639
|
-
this.updateEventSubscriptions();
|
|
640
622
|
};
|
|
641
623
|
}
|
|
642
624
|
onAgentAttentionRequired(handler) {
|
|
@@ -690,7 +672,7 @@ export class DaemonClient {
|
|
|
690
672
|
}
|
|
691
673
|
sendTransportFrame(frame) {
|
|
692
674
|
if (!this.transport) {
|
|
693
|
-
throw new
|
|
675
|
+
throw new DaemonConnectionError("Transport not connected");
|
|
694
676
|
}
|
|
695
677
|
const isOpen = this.beginTraceSection("paseo.ws.frame.outbound", {
|
|
696
678
|
kind: typeof frame === "string" ? "text" : "binary",
|
|
@@ -770,13 +752,13 @@ export class DaemonClient {
|
|
|
770
752
|
if (idx !== -1) {
|
|
771
753
|
this.pendingSendQueue.splice(idx, 1);
|
|
772
754
|
}
|
|
773
|
-
reject(new
|
|
755
|
+
reject(new DaemonConnectionError("Timed out waiting for connection to send message", "DAEMON_REQUEST_TIMEOUT"));
|
|
774
756
|
}, DEFAULT_SEND_QUEUE_TIMEOUT_MS);
|
|
775
757
|
this.pendingSendQueue.push({ message, resolve, reject, timeoutHandle });
|
|
776
758
|
});
|
|
777
759
|
}
|
|
778
760
|
// Not connected and not connecting - fail immediately
|
|
779
|
-
return Promise.reject(new
|
|
761
|
+
return Promise.reject(new DaemonConnectionError(`Transport not connected (status: ${status})`));
|
|
780
762
|
}
|
|
781
763
|
/**
|
|
782
764
|
* Flush pending send queue - called when connection is established.
|
|
@@ -793,7 +775,7 @@ export class DaemonClient {
|
|
|
793
775
|
pending.resolve();
|
|
794
776
|
}
|
|
795
777
|
else {
|
|
796
|
-
pending.reject(new
|
|
778
|
+
pending.reject(new DaemonConnectionError("Connection lost before message could be sent"));
|
|
797
779
|
}
|
|
798
780
|
}
|
|
799
781
|
catch (error) {
|
|
@@ -813,6 +795,7 @@ export class DaemonClient {
|
|
|
813
795
|
}
|
|
814
796
|
}
|
|
815
797
|
async sendRequest(params) {
|
|
798
|
+
const wire = this.owned.prepareRequest(params.message);
|
|
816
799
|
const timeout = params.timeout ?? DEFAULT_SESSION_RPC_TIMEOUT_MS;
|
|
817
800
|
const { promise, cancel } = this.waitForWithCancel((msg) => {
|
|
818
801
|
if (msg.type === "rpc_error" && msg.payload.requestId === params.requestId) {
|
|
@@ -826,14 +809,14 @@ export class DaemonClient {
|
|
|
826
809
|
}),
|
|
827
810
|
};
|
|
828
811
|
}
|
|
829
|
-
const value = params.select(msg);
|
|
812
|
+
const value = params.select(wire.receive(msg));
|
|
830
813
|
if (value === null) {
|
|
831
814
|
return null;
|
|
832
815
|
}
|
|
833
816
|
return { kind: "ok", value };
|
|
834
817
|
}, timeout, { ...params.options, requestId: params.requestId });
|
|
835
818
|
try {
|
|
836
|
-
await this.sendSessionMessageOrThrow(
|
|
819
|
+
await this.sendSessionMessageOrThrow(wire.message);
|
|
837
820
|
}
|
|
838
821
|
catch (error) {
|
|
839
822
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
@@ -841,11 +824,15 @@ export class DaemonClient {
|
|
|
841
824
|
void promise.catch(() => undefined);
|
|
842
825
|
throw err;
|
|
843
826
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
827
|
+
try {
|
|
828
|
+
const result = await promise;
|
|
829
|
+
if (result.kind === "error")
|
|
830
|
+
throw result.error;
|
|
831
|
+
return result.value;
|
|
832
|
+
}
|
|
833
|
+
finally {
|
|
834
|
+
await wire.finish();
|
|
847
835
|
}
|
|
848
|
-
return result.value;
|
|
849
836
|
}
|
|
850
837
|
async sendCorrelatedRequest(params) {
|
|
851
838
|
return this.sendRequest({
|
|
@@ -893,7 +880,7 @@ export class DaemonClient {
|
|
|
893
880
|
}
|
|
894
881
|
sendSessionMessageStrict(message) {
|
|
895
882
|
if (!this.transport || this.connectionState.status !== "connected") {
|
|
896
|
-
throw new
|
|
883
|
+
throw new DaemonConnectionError("Transport not connected");
|
|
897
884
|
}
|
|
898
885
|
const payload = SessionInboundMessageSchema.parse(message);
|
|
899
886
|
try {
|
|
@@ -1107,7 +1094,69 @@ export class DaemonClient {
|
|
|
1107
1094
|
// ============================================================================
|
|
1108
1095
|
// Agent RPCs (requestId-correlated)
|
|
1109
1096
|
// ============================================================================
|
|
1097
|
+
observe(responseType, message, options) {
|
|
1098
|
+
let resetSource = false;
|
|
1099
|
+
return this.owned.observeRequest(message, async (query, accept) => {
|
|
1100
|
+
resetSource = false;
|
|
1101
|
+
const requestId = this.createRequestId(options?.requestId);
|
|
1102
|
+
const request = SessionInboundMessageSchema.parse({ ...query, requestId });
|
|
1103
|
+
try {
|
|
1104
|
+
return await this.sendCorrelatedRequest({
|
|
1105
|
+
message: request,
|
|
1106
|
+
responseType,
|
|
1107
|
+
requestId,
|
|
1108
|
+
timeout: options?.timeout,
|
|
1109
|
+
options: { skipQueue: true },
|
|
1110
|
+
selectPayload: (payload) => {
|
|
1111
|
+
if ("error" in payload && typeof payload.error === "string")
|
|
1112
|
+
throw new DaemonRpcError({
|
|
1113
|
+
requestId: payload.requestId,
|
|
1114
|
+
requestType: message.type,
|
|
1115
|
+
error: payload.error,
|
|
1116
|
+
});
|
|
1117
|
+
accept(payload);
|
|
1118
|
+
return payload;
|
|
1119
|
+
},
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
catch (error) {
|
|
1123
|
+
resetSource = !(error instanceof DaemonRpcError) && this.isConnected;
|
|
1124
|
+
throw error;
|
|
1125
|
+
}
|
|
1126
|
+
}, options?.signal, () => {
|
|
1127
|
+
// Reject and release this handle before closing a source with an unknown bootstrap outcome.
|
|
1128
|
+
// Closing first would classify the failure as a reconnect and replay the failed request.
|
|
1129
|
+
if (resetSource) {
|
|
1130
|
+
this.disposeTransport(1001, "Subscription request failed");
|
|
1131
|
+
this.scheduleReconnect({ reason: "Subscription request failed" });
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
registerBrowserHost(registration, options) {
|
|
1136
|
+
return this.observe("browser.host.register.response", { type: "browser.host.register.request", ...registration }, options);
|
|
1137
|
+
}
|
|
1138
|
+
observeEvents(events, options) {
|
|
1139
|
+
return this.observe("session.events.set_subscription.response", {
|
|
1140
|
+
type: "session.events.set_subscription.request",
|
|
1141
|
+
events,
|
|
1142
|
+
notifications: options?.notifications,
|
|
1143
|
+
}, options);
|
|
1144
|
+
}
|
|
1145
|
+
observeAgents(options = {}) {
|
|
1146
|
+
const { signal, requestId, timeout, ...query } = options;
|
|
1147
|
+
return this.observe("fetch_agents_response", { ...query, type: "fetch_agents_request", subscribe: {} }, { signal, requestId, timeout });
|
|
1148
|
+
}
|
|
1149
|
+
observeWorkspaces(options = {}) {
|
|
1150
|
+
const { signal, requestId, ...query } = options;
|
|
1151
|
+
return this.observe("fetch_workspaces_response", { ...query, type: "fetch_workspaces_request", subscribe: {} }, { signal, requestId });
|
|
1152
|
+
}
|
|
1110
1153
|
async fetchAgents(options) {
|
|
1154
|
+
if (options?.subscribe) {
|
|
1155
|
+
if (options.subscribe.subscriptionId !== undefined)
|
|
1156
|
+
throw new Error("Subscription IDs are assigned by the host");
|
|
1157
|
+
const subscription = this.observeAgents(options);
|
|
1158
|
+
return { ...(await subscription.ready), subscription };
|
|
1159
|
+
}
|
|
1111
1160
|
const resolvedRequestId = this.createRequestId(options?.requestId);
|
|
1112
1161
|
const message = SessionInboundMessageSchema.parse({
|
|
1113
1162
|
type: "fetch_agents_request",
|
|
@@ -1187,6 +1236,12 @@ export class DaemonClient {
|
|
|
1187
1236
|
});
|
|
1188
1237
|
}
|
|
1189
1238
|
async fetchWorkspaces(options) {
|
|
1239
|
+
if (options?.subscribe) {
|
|
1240
|
+
if (options.subscribe.subscriptionId !== undefined)
|
|
1241
|
+
throw new Error("Subscription IDs are assigned by the host");
|
|
1242
|
+
const subscription = this.observeWorkspaces(options);
|
|
1243
|
+
return { ...(await subscription.ready), subscription };
|
|
1244
|
+
}
|
|
1190
1245
|
const resolvedRequestId = this.createRequestId(options?.requestId);
|
|
1191
1246
|
const message = SessionInboundMessageSchema.parse({
|
|
1192
1247
|
type: "fetch_workspaces_request",
|
|
@@ -1212,16 +1267,18 @@ export class DaemonClient {
|
|
|
1212
1267
|
},
|
|
1213
1268
|
});
|
|
1214
1269
|
}
|
|
1215
|
-
listWorkspaceLabels(options) {
|
|
1270
|
+
listWorkspaceLabels(options = {}) {
|
|
1216
1271
|
return this.sendNamespacedCorrelatedSessionRequest({
|
|
1217
1272
|
requestId: options.requestId,
|
|
1218
1273
|
message: {
|
|
1219
1274
|
type: "workspace.label.list.request",
|
|
1220
|
-
subscribe: { subscriptionId: options.subscriptionId },
|
|
1221
1275
|
...(options.sync ? { sync: options.sync } : {}),
|
|
1222
1276
|
},
|
|
1223
1277
|
});
|
|
1224
1278
|
}
|
|
1279
|
+
observeWorkspaceLabels(options) {
|
|
1280
|
+
return this.observe("workspace.label.list.response", { type: "workspace.label.list.request", subscribe: {} }, options);
|
|
1281
|
+
}
|
|
1225
1282
|
setWorkspaceLabel(options) {
|
|
1226
1283
|
return this.sendNamespacedCorrelatedSessionRequest({
|
|
1227
1284
|
requestId: options.requestId,
|
|
@@ -1422,60 +1479,10 @@ export class DaemonClient {
|
|
|
1422
1479
|
}
|
|
1423
1480
|
return { agent: payload.agent, project: payload.project ?? null };
|
|
1424
1481
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
}
|
|
1429
|
-
for (const [subscriptionId, subscription] of this.checkoutDiffSubscriptions) {
|
|
1430
|
-
const message = SessionInboundMessageSchema.parse({
|
|
1431
|
-
type: "subscribe_checkout_diff_request",
|
|
1432
|
-
subscriptionId,
|
|
1433
|
-
cwd: subscription.cwd,
|
|
1434
|
-
compare: subscription.compare,
|
|
1435
|
-
requestId: this.createRequestId(),
|
|
1436
|
-
});
|
|
1437
|
-
this.sendSessionMessage(message);
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
resubscribeTerminalDirectorySubscriptions() {
|
|
1441
|
-
if (this.terminalDirectorySubscriptions.size === 0) {
|
|
1442
|
-
return;
|
|
1443
|
-
}
|
|
1444
|
-
for (const subscription of this.terminalDirectorySubscriptions.values()) {
|
|
1445
|
-
this.sendSessionMessage({
|
|
1446
|
-
type: "subscribe_terminals_request",
|
|
1447
|
-
cwd: subscription.cwd,
|
|
1448
|
-
...(subscription.workspaceId !== undefined
|
|
1449
|
-
? { workspaceId: subscription.workspaceId }
|
|
1450
|
-
: {}),
|
|
1451
|
-
});
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
resubscribeFileSubscriptions() {
|
|
1455
|
-
for (const [subscriptionId, subscription] of this.fileSubscriptions) {
|
|
1456
|
-
void this.sendCorrelatedSessionRequest({
|
|
1457
|
-
message: {
|
|
1458
|
-
type: "fs.file.subscribe.request",
|
|
1459
|
-
cwd: subscription.cwd,
|
|
1460
|
-
path: subscription.path,
|
|
1461
|
-
subscriptionId,
|
|
1462
|
-
},
|
|
1463
|
-
responseType: "fs.file.subscribe.response",
|
|
1464
|
-
})
|
|
1465
|
-
.then((payload) => subscription.onUpdate(payload.initial))
|
|
1466
|
-
.catch(() => undefined);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1482
|
+
// ============================================================================
|
|
1483
|
+
// Agent Lifecycle
|
|
1484
|
+
// ============================================================================
|
|
1469
1485
|
async createAgent(options) {
|
|
1470
|
-
const result = await this.creations.createAgent({
|
|
1471
|
-
...options,
|
|
1472
|
-
config: resolveAgentConfig(options),
|
|
1473
|
-
});
|
|
1474
|
-
if (result.error || !result.agent)
|
|
1475
|
-
throw new Error(result.error ?? "Agent creation failed");
|
|
1476
|
-
return result.agent;
|
|
1477
|
-
}
|
|
1478
|
-
async createLegacyAgent(options) {
|
|
1479
1486
|
if (options.idempotencyKey !== undefined)
|
|
1480
1487
|
this.requireAgentRequestReceipts();
|
|
1481
1488
|
const requestId = this.createRequestId(options.requestId);
|
|
@@ -1902,6 +1909,9 @@ export class DaemonClient {
|
|
|
1902
1909
|
}
|
|
1903
1910
|
return payload;
|
|
1904
1911
|
}
|
|
1912
|
+
observeTimeline(agentIds, options) {
|
|
1913
|
+
return this.observe("agent.timeline.set_subscription.response", { type: "agent.timeline.set_subscription.request", agentIds }, options);
|
|
1914
|
+
}
|
|
1905
1915
|
async askSideConversation(parentAgentId, threadId, question, options = {}) {
|
|
1906
1916
|
this.requireSideConversationSupport();
|
|
1907
1917
|
const requestId = this.createRequestId(options.requestId);
|
|
@@ -1971,77 +1981,8 @@ export class DaemonClient {
|
|
|
1971
1981
|
throw new Error(payload.error);
|
|
1972
1982
|
return payload;
|
|
1973
1983
|
}
|
|
1974
|
-
setAgentTimelineSubscription(agentIds) {
|
|
1975
|
-
return this.subscriptions.setViewed(agentIds);
|
|
1976
|
-
}
|
|
1977
1984
|
subscribeAgentTimeline(agentId, handler) {
|
|
1978
|
-
|
|
1979
|
-
if (message.payload.agentId === agentId)
|
|
1980
|
-
handler(message);
|
|
1981
|
-
});
|
|
1982
|
-
const replacement = this.on("agent.timeline.replacement", (message) => {
|
|
1983
|
-
if (message.payload.agentId === agentId)
|
|
1984
|
-
handler(message);
|
|
1985
|
-
});
|
|
1986
|
-
const release = this.subscriptions.observeTimeline(agentId);
|
|
1987
|
-
return Object.assign(() => {
|
|
1988
|
-
stream();
|
|
1989
|
-
replacement();
|
|
1990
|
-
release();
|
|
1991
|
-
}, { ready: release.ready });
|
|
1992
|
-
}
|
|
1993
|
-
updateEventSubscriptions() {
|
|
1994
|
-
const events = [
|
|
1995
|
-
"project.update",
|
|
1996
|
-
"providers_snapshot_update",
|
|
1997
|
-
"agent_attention_required",
|
|
1998
|
-
"agent_permission_request",
|
|
1999
|
-
"agent_permission_resolved",
|
|
2000
|
-
];
|
|
2001
|
-
if (this.eventListeners.size === 0 && !this.messageHandlers.has("providers_snapshot_update")) {
|
|
2002
|
-
this.providerSnapshotUpdates.clear();
|
|
2003
|
-
}
|
|
2004
|
-
this.subscriptions.setEvents(events.filter((type) => this.eventListeners.size > 0 || (this.messageHandlers.get(type)?.size ?? 0) > 0));
|
|
2005
|
-
}
|
|
2006
|
-
async sendEventSubscription(events) {
|
|
2007
|
-
if (this.connectionState.status !== "connected")
|
|
2008
|
-
return;
|
|
2009
|
-
// COMPAT(explicitEventSubscriptions): added in v0.8.0, remove legacy broadcast handling after 2027-03-08.
|
|
2010
|
-
if (!this.lastServerInfoMessage?.features?.explicitEventSubscriptions ||
|
|
2011
|
-
this.config.capabilities?.[CLIENT_CAPS.explicitEventSubscriptions] === false)
|
|
2012
|
-
return;
|
|
2013
|
-
await this.sendCorrelatedSessionRequest({
|
|
2014
|
-
message: { type: "session.events.set_subscription.request", events },
|
|
2015
|
-
responseType: "session.events.set_subscription.response",
|
|
2016
|
-
});
|
|
2017
|
-
}
|
|
2018
|
-
async sendTimelineSubscription(agentIds) {
|
|
2019
|
-
if (this.connectionState.status !== "connected")
|
|
2020
|
-
return;
|
|
2021
|
-
// COMPAT(selectiveAgentTimeline): added in v0.1.106. Old daemons keep their
|
|
2022
|
-
// legacy global stream and do not understand this RPC. Remove after
|
|
2023
|
-
// 2027-01-12 once the supported daemon floor is >= v0.1.106.
|
|
2024
|
-
if (!this.lastServerInfoMessage?.features?.selectiveAgentTimeline) {
|
|
2025
|
-
return;
|
|
2026
|
-
}
|
|
2027
|
-
const requestId = this.createRequestId();
|
|
2028
|
-
const normalizedAgentIds = [...new Set(agentIds)].sort();
|
|
2029
|
-
const message = SessionInboundMessageSchema.parse({
|
|
2030
|
-
type: "agent.timeline.set_subscription.request",
|
|
2031
|
-
agentIds: normalizedAgentIds,
|
|
2032
|
-
requestId,
|
|
2033
|
-
});
|
|
2034
|
-
await this.sendRequest({
|
|
2035
|
-
requestId,
|
|
2036
|
-
message,
|
|
2037
|
-
options: { skipQueue: true },
|
|
2038
|
-
select: (response) => {
|
|
2039
|
-
if (response.type !== "agent.timeline.set_subscription.response") {
|
|
2040
|
-
return null;
|
|
2041
|
-
}
|
|
2042
|
-
return response.payload.requestId === requestId ? response.payload : null;
|
|
2043
|
-
},
|
|
2044
|
-
});
|
|
1985
|
+
return subscribeTimeline(agentId, this.observeTimeline([agentId]), handler, (error) => this.logger.error({ err: error }, "Timeline observation failed"));
|
|
2045
1986
|
}
|
|
2046
1987
|
async buildAgentForkContext(agentId, options = {}) {
|
|
2047
1988
|
const resolvedRequestId = this.createRequestId(options.requestId);
|
|
@@ -2303,7 +2244,7 @@ export class DaemonClient {
|
|
|
2303
2244
|
}
|
|
2304
2245
|
return payload.notice ?? null;
|
|
2305
2246
|
}
|
|
2306
|
-
async restartServer(reason, requestId) {
|
|
2247
|
+
async restartServer(reason, requestId, options) {
|
|
2307
2248
|
const resolvedRequestId = this.createRequestId(requestId);
|
|
2308
2249
|
const message = SessionInboundMessageSchema.parse({
|
|
2309
2250
|
type: "restart_server_request",
|
|
@@ -2313,6 +2254,7 @@ export class DaemonClient {
|
|
|
2313
2254
|
return this.sendRequest({
|
|
2314
2255
|
requestId: resolvedRequestId,
|
|
2315
2256
|
message,
|
|
2257
|
+
timeout: options?.timeout,
|
|
2316
2258
|
options: { skipQueue: true },
|
|
2317
2259
|
select: (msg) => {
|
|
2318
2260
|
if (msg.type !== "status") {
|
|
@@ -2638,75 +2580,22 @@ export class DaemonClient {
|
|
|
2638
2580
|
: { mode: "base", baseRef: trimmedBaseRef };
|
|
2639
2581
|
}
|
|
2640
2582
|
async getCheckoutDiff(cwd, compare, requestId) {
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
}
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
files: payload.files,
|
|
2650
|
-
error: payload.error,
|
|
2651
|
-
diffTooLarge: payload.diffTooLarge,
|
|
2652
|
-
requestId: payload.requestId,
|
|
2653
|
-
};
|
|
2654
|
-
}
|
|
2655
|
-
finally {
|
|
2656
|
-
try {
|
|
2657
|
-
this.unsubscribeCheckoutDiff(oneShotSubscriptionId);
|
|
2658
|
-
}
|
|
2659
|
-
catch {
|
|
2660
|
-
// Ignore disconnect races during one-shot cleanup.
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
async subscribeCheckoutDiff(cwd, compare, options) {
|
|
2665
|
-
const subscriptionId = options?.subscriptionId ?? crypto.randomUUID();
|
|
2666
|
-
const normalizedCompare = this.normalizeCheckoutDiffCompare(compare);
|
|
2667
|
-
const previousSubscription = this.checkoutDiffSubscriptions.get(subscriptionId) ?? null;
|
|
2668
|
-
this.checkoutDiffSubscriptions.set(subscriptionId, {
|
|
2669
|
-
cwd,
|
|
2670
|
-
compare: normalizedCompare,
|
|
2583
|
+
return this.sendCorrelatedSessionRequest({
|
|
2584
|
+
message: {
|
|
2585
|
+
type: "checkout.diff.get.request",
|
|
2586
|
+
cwd,
|
|
2587
|
+
compare: this.normalizeCheckoutDiffCompare(compare),
|
|
2588
|
+
},
|
|
2589
|
+
responseType: "checkout.diff.get.response",
|
|
2590
|
+
requestId,
|
|
2671
2591
|
});
|
|
2672
|
-
|
|
2673
|
-
|
|
2592
|
+
}
|
|
2593
|
+
observeCheckoutDiff(cwd, compare, options) {
|
|
2594
|
+
return this.observe("subscribe_checkout_diff_response", {
|
|
2674
2595
|
type: "subscribe_checkout_diff_request",
|
|
2675
|
-
subscriptionId,
|
|
2676
2596
|
cwd,
|
|
2677
|
-
compare:
|
|
2678
|
-
|
|
2679
|
-
});
|
|
2680
|
-
try {
|
|
2681
|
-
return await this.sendCorrelatedRequest({
|
|
2682
|
-
requestId: resolvedRequestId,
|
|
2683
|
-
message,
|
|
2684
|
-
responseType: "subscribe_checkout_diff_response",
|
|
2685
|
-
options: { skipQueue: true },
|
|
2686
|
-
selectPayload: (payload) => {
|
|
2687
|
-
if (payload.subscriptionId !== subscriptionId) {
|
|
2688
|
-
return null;
|
|
2689
|
-
}
|
|
2690
|
-
return payload;
|
|
2691
|
-
},
|
|
2692
|
-
});
|
|
2693
|
-
}
|
|
2694
|
-
catch (error) {
|
|
2695
|
-
if (previousSubscription) {
|
|
2696
|
-
this.checkoutDiffSubscriptions.set(subscriptionId, previousSubscription);
|
|
2697
|
-
}
|
|
2698
|
-
else {
|
|
2699
|
-
this.checkoutDiffSubscriptions.delete(subscriptionId);
|
|
2700
|
-
}
|
|
2701
|
-
throw error;
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
unsubscribeCheckoutDiff(subscriptionId) {
|
|
2705
|
-
this.checkoutDiffSubscriptions.delete(subscriptionId);
|
|
2706
|
-
this.sendSessionMessage({
|
|
2707
|
-
type: "unsubscribe_checkout_diff_request",
|
|
2708
|
-
subscriptionId,
|
|
2709
|
-
});
|
|
2597
|
+
compare: this.normalizeCheckoutDiffCompare(compare),
|
|
2598
|
+
}, options);
|
|
2710
2599
|
}
|
|
2711
2600
|
async checkoutCommit(cwd, input, requestId) {
|
|
2712
2601
|
return this.sendCorrelatedSessionRequest({
|
|
@@ -3003,34 +2892,11 @@ export class DaemonClient {
|
|
|
3003
2892
|
});
|
|
3004
2893
|
}
|
|
3005
2894
|
async createWorkspace(input, requestId) {
|
|
3006
|
-
const resolvedRequestId = this.createRequestId(requestId ?? input.requestId);
|
|
3007
|
-
const result = await this.creations.createWorkspace({
|
|
3008
|
-
...input,
|
|
3009
|
-
requestId: resolvedRequestId,
|
|
3010
|
-
...(input.agent
|
|
3011
|
-
? { agent: { ...input.agent, config: resolveAgentConfig(input.agent) } }
|
|
3012
|
-
: {}),
|
|
3013
|
-
});
|
|
3014
|
-
return {
|
|
3015
|
-
...result,
|
|
3016
|
-
workspace: result.workspace ?? null,
|
|
3017
|
-
agent: result.agent ?? undefined,
|
|
3018
|
-
setupTerminalId: result.setupTerminalId ?? null,
|
|
3019
|
-
requestId: result.requestId ?? resolvedRequestId,
|
|
3020
|
-
};
|
|
3021
|
-
}
|
|
3022
|
-
async createLegacyWorkspace(input, requestId) {
|
|
3023
|
-
// COMPAT(workspaceRequestReceipts): added in v0.8.0; remove gate after 2027-03-07.
|
|
3024
|
-
if (input.idempotencyKey !== undefined &&
|
|
3025
|
-
!this.lastServerInfoMessage?.features?.workspaceRequestReceipts) {
|
|
3026
|
-
throw new Error("Update the host to use retry-safe workspace creation.");
|
|
3027
|
-
}
|
|
3028
2895
|
return this.sendCorrelatedSessionRequest({
|
|
3029
2896
|
requestId,
|
|
3030
2897
|
message: {
|
|
3031
2898
|
type: "workspace.create.request",
|
|
3032
2899
|
source: input.source,
|
|
3033
|
-
...(input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
3034
2900
|
...(input.title !== undefined ? { title: input.title } : {}),
|
|
3035
2901
|
...(input.firstAgentContext !== undefined
|
|
3036
2902
|
? { firstAgentContext: input.firstAgentContext }
|
|
@@ -3156,35 +3022,26 @@ export class DaemonClient {
|
|
|
3156
3022
|
this.activeBinaryFileTransfers.delete(resolvedRequestId);
|
|
3157
3023
|
}
|
|
3158
3024
|
}
|
|
3025
|
+
observeFile(input) {
|
|
3026
|
+
return this.observe("fs.file.subscribe.response", { type: "fs.file.subscribe.request", cwd: input.cwd, path: input.path }, { signal: input.signal });
|
|
3027
|
+
}
|
|
3159
3028
|
async subscribeFile(input, onUpdate) {
|
|
3160
|
-
const
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
return;
|
|
3177
|
-
void this.sendCorrelatedSessionRequest({
|
|
3178
|
-
message: { type: "fs.file.unsubscribe.request", subscriptionId },
|
|
3179
|
-
responseType: "fs.file.unsubscribe.response",
|
|
3180
|
-
}).catch(() => undefined);
|
|
3181
|
-
},
|
|
3182
|
-
};
|
|
3183
|
-
}
|
|
3184
|
-
catch (error) {
|
|
3185
|
-
this.fileSubscriptions.delete(subscriptionId);
|
|
3186
|
-
throw error;
|
|
3187
|
-
}
|
|
3029
|
+
const subscription = this.observeFile(input);
|
|
3030
|
+
let initial = true;
|
|
3031
|
+
subscription.subscribe({
|
|
3032
|
+
snapshot: (snapshot) => {
|
|
3033
|
+
// The initial version is returned below. Subsequent snapshots repair reconnects.
|
|
3034
|
+
if (!initial)
|
|
3035
|
+
onUpdate(snapshot.initial);
|
|
3036
|
+
initial = false;
|
|
3037
|
+
},
|
|
3038
|
+
update: (message) => {
|
|
3039
|
+
if (message.type === "fs.file.update")
|
|
3040
|
+
onUpdate(message.payload.version);
|
|
3041
|
+
},
|
|
3042
|
+
});
|
|
3043
|
+
const snapshot = await subscription.ready;
|
|
3044
|
+
return { initial: snapshot.initial, subscription, unsubscribe: subscription.release };
|
|
3188
3045
|
}
|
|
3189
3046
|
async writeFile(input) {
|
|
3190
3047
|
const payload = await this.sendCorrelatedSessionRequest({
|
|
@@ -3364,6 +3221,11 @@ export class DaemonClient {
|
|
|
3364
3221
|
});
|
|
3365
3222
|
}
|
|
3366
3223
|
async getDaemonStatus(options) {
|
|
3224
|
+
if (!this.lastServerInfoMessage)
|
|
3225
|
+
throw new DaemonConnectionError("Transport not connected");
|
|
3226
|
+
if (this.lastServerInfoMessage?.features?.daemonStatusRpc !== true) {
|
|
3227
|
+
throw new Error("Update the host to read daemon status.");
|
|
3228
|
+
}
|
|
3367
3229
|
return this.sendCorrelatedSessionRequest({
|
|
3368
3230
|
requestId: options?.requestId,
|
|
3369
3231
|
message: {
|
|
@@ -3840,30 +3702,9 @@ export class DaemonClient {
|
|
|
3840
3702
|
// ============================================================================
|
|
3841
3703
|
// Terminals
|
|
3842
3704
|
// ============================================================================
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
workspaceId: input.workspaceId,
|
|
3847
|
-
});
|
|
3848
|
-
if (!this.transport || this.connectionState.status !== "connected") {
|
|
3849
|
-
return;
|
|
3850
|
-
}
|
|
3851
|
-
this.sendSessionMessage({
|
|
3852
|
-
type: "subscribe_terminals_request",
|
|
3853
|
-
cwd: input.cwd,
|
|
3854
|
-
...(input.workspaceId !== undefined ? { workspaceId: input.workspaceId } : {}),
|
|
3855
|
-
});
|
|
3856
|
-
}
|
|
3857
|
-
unsubscribeTerminals(input) {
|
|
3858
|
-
this.terminalDirectorySubscriptions.delete(terminalSubscriptionKey(input.cwd, input.workspaceId));
|
|
3859
|
-
if (!this.transport || this.connectionState.status !== "connected") {
|
|
3860
|
-
return;
|
|
3861
|
-
}
|
|
3862
|
-
this.sendSessionMessage({
|
|
3863
|
-
type: "unsubscribe_terminals_request",
|
|
3864
|
-
cwd: input.cwd,
|
|
3865
|
-
...(input.workspaceId !== undefined ? { workspaceId: input.workspaceId } : {}),
|
|
3866
|
-
});
|
|
3705
|
+
observeTerminals(input) {
|
|
3706
|
+
const { signal, ...query } = input;
|
|
3707
|
+
return this.observe("terminals_changed", { type: "subscribe_terminals_request", ...query }, { signal });
|
|
3867
3708
|
}
|
|
3868
3709
|
async listTerminals(cwd, requestId, options) {
|
|
3869
3710
|
const resolvedRequestId = this.createRequestId(requestId);
|
|
@@ -3911,45 +3752,72 @@ export class DaemonClient {
|
|
|
3911
3752
|
responseType: "terminal.rename.response",
|
|
3912
3753
|
});
|
|
3913
3754
|
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3755
|
+
/**
|
|
3756
|
+
* Snapshot/restore and output arrive in receive. Subscribe to the returned handle
|
|
3757
|
+
* for terminal_stream_exit: payload.error means observation failure, not PTY exit.
|
|
3758
|
+
* Either outcome detaches this slot and releases the handle automatically.
|
|
3759
|
+
*/
|
|
3760
|
+
observeTerminal(terminalId, receive, options) {
|
|
3761
|
+
options?.signal?.throwIfAborted();
|
|
3762
|
+
const observation = this.observe("subscribe_terminal_response", {
|
|
3919
3763
|
type: "subscribe_terminal_request",
|
|
3920
3764
|
terminalId,
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
}
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
options
|
|
3765
|
+
...(options?.restore ? { restore: options.restore } : {}),
|
|
3766
|
+
}, options);
|
|
3767
|
+
let detach = () => { };
|
|
3768
|
+
let released = false;
|
|
3769
|
+
const release = () => {
|
|
3770
|
+
released = true;
|
|
3771
|
+
detach();
|
|
3772
|
+
options?.signal?.removeEventListener("abort", abort);
|
|
3773
|
+
return observation.release();
|
|
3774
|
+
};
|
|
3775
|
+
const abort = () => {
|
|
3776
|
+
void release().catch((error) => this.logger.error({ err: error }, "Terminal release failed"));
|
|
3777
|
+
};
|
|
3778
|
+
options?.signal?.addEventListener("abort", abort, { once: true });
|
|
3779
|
+
observation.subscribe({
|
|
3780
|
+
snapshot: (payload) => {
|
|
3781
|
+
detach();
|
|
3782
|
+
if (released || options?.signal?.aborted || payload.error !== null)
|
|
3783
|
+
return;
|
|
3784
|
+
const subscriptionId = payload.subscriptionId;
|
|
3785
|
+
detach = this.terminalStreams.attach(subscriptionId, terminalId, payload.slot, (event) => {
|
|
3786
|
+
if (released || observation.subscriptionId !== subscriptionId)
|
|
3787
|
+
return;
|
|
3788
|
+
try {
|
|
3789
|
+
receive(event);
|
|
3790
|
+
}
|
|
3791
|
+
catch (error) {
|
|
3792
|
+
this.logger.error({ err: error }, "Terminal observer failed");
|
|
3793
|
+
}
|
|
3794
|
+
});
|
|
3795
|
+
},
|
|
3796
|
+
update: (message) => {
|
|
3797
|
+
if (message.type === "terminal_stream_exit") {
|
|
3798
|
+
detach();
|
|
3799
|
+
queueMicrotask(abort);
|
|
3800
|
+
}
|
|
3801
|
+
},
|
|
3802
|
+
error: () => {
|
|
3803
|
+
detach();
|
|
3804
|
+
},
|
|
3929
3805
|
});
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3806
|
+
return {
|
|
3807
|
+
get subscriptionId() {
|
|
3808
|
+
return observation.subscriptionId;
|
|
3809
|
+
},
|
|
3810
|
+
ready: observation.ready,
|
|
3811
|
+
subscribe: observation.subscribe,
|
|
3812
|
+
release,
|
|
3813
|
+
};
|
|
3934
3814
|
}
|
|
3935
|
-
|
|
3936
|
-
this.
|
|
3937
|
-
|
|
3938
|
-
type: "unsubscribe_terminal_request",
|
|
3939
|
-
terminalId,
|
|
3940
|
-
});
|
|
3815
|
+
async subscribeTerminal(terminalId, options) {
|
|
3816
|
+
const subscription = this.observeTerminal(terminalId, () => { }, options);
|
|
3817
|
+
return { ...(await subscription.ready), subscription };
|
|
3941
3818
|
}
|
|
3942
3819
|
sendTerminalInput(terminalId, message) {
|
|
3943
|
-
|
|
3944
|
-
if (frame) {
|
|
3945
|
-
this.sendBinaryFrame(frame);
|
|
3946
|
-
return;
|
|
3947
|
-
}
|
|
3948
|
-
this.sendSessionMessage({
|
|
3949
|
-
type: "terminal_input",
|
|
3950
|
-
terminalId,
|
|
3951
|
-
message,
|
|
3952
|
-
});
|
|
3820
|
+
this.sendSessionMessage({ type: "terminal_input", terminalId, message });
|
|
3953
3821
|
}
|
|
3954
3822
|
async killTerminal(terminalId, requestId) {
|
|
3955
3823
|
const resolvedRequestId = this.createRequestId(requestId);
|
|
@@ -4182,7 +4050,8 @@ export class DaemonClient {
|
|
|
4182
4050
|
}
|
|
4183
4051
|
}
|
|
4184
4052
|
disposeTransport(code = 1001, reason = "Reconnecting") {
|
|
4185
|
-
this.
|
|
4053
|
+
this.owned.disconnected();
|
|
4054
|
+
this.providerSnapshotUpdates.clear();
|
|
4186
4055
|
this.stopLivenessHeartbeat();
|
|
4187
4056
|
this.cleanupTransport();
|
|
4188
4057
|
if (this.transport) {
|
|
@@ -4223,10 +4092,12 @@ export class DaemonClient {
|
|
|
4223
4092
|
if (typeof Blob !== "undefined" &&
|
|
4224
4093
|
rawData instanceof Blob &&
|
|
4225
4094
|
typeof rawData.arrayBuffer === "function") {
|
|
4095
|
+
const transport = this.transport;
|
|
4226
4096
|
void rawData
|
|
4227
4097
|
.arrayBuffer()
|
|
4228
4098
|
.then((buffer) => {
|
|
4229
|
-
this.
|
|
4099
|
+
if (this.transport === transport)
|
|
4100
|
+
this.handleTransportMessage(buffer);
|
|
4230
4101
|
return;
|
|
4231
4102
|
})
|
|
4232
4103
|
.catch(() => {
|
|
@@ -4455,12 +4326,13 @@ export class DaemonClient {
|
|
|
4455
4326
|
if (typeof reason === "string" && reason.trim().length > 0) {
|
|
4456
4327
|
this.lastErrorValue = reason.trim();
|
|
4457
4328
|
}
|
|
4458
|
-
this.
|
|
4329
|
+
this.owned.disconnected();
|
|
4330
|
+
this.providerSnapshotUpdates.clear();
|
|
4459
4331
|
// Clear all pending waiters and queued sends since the connection was lost
|
|
4460
4332
|
// and responses from the previous connection will never arrive.
|
|
4461
|
-
this.clearWaiters(new
|
|
4462
|
-
this.rejectPendingSendQueue(new
|
|
4463
|
-
this.rejectPingProbe(new
|
|
4333
|
+
this.clearWaiters(new DaemonConnectionError(reason ?? "Connection lost"));
|
|
4334
|
+
this.rejectPendingSendQueue(new DaemonConnectionError(reason ?? "Connection lost"));
|
|
4335
|
+
this.rejectPingProbe(new DaemonConnectionError(reason ?? "Connection lost"));
|
|
4464
4336
|
this.terminalStreams.clearSlots();
|
|
4465
4337
|
this.lastServerInfoMessage = null;
|
|
4466
4338
|
if (wasDisposed) {
|
|
@@ -4539,13 +4411,13 @@ export class DaemonClient {
|
|
|
4539
4411
|
});
|
|
4540
4412
|
}
|
|
4541
4413
|
handleSessionMessage(msg) {
|
|
4414
|
+
msg = this.owned.normalize(msg);
|
|
4542
4415
|
if (msg.type === "providers_snapshot_update" &&
|
|
4543
4416
|
this.config.providerSnapshots !== "wire" &&
|
|
4544
4417
|
msg.payload.snapshotHash &&
|
|
4545
4418
|
!msg.payload.compactSnapshot) {
|
|
4546
|
-
if (this.
|
|
4419
|
+
if (this.owned.owns(msg))
|
|
4547
4420
|
this.providerSnapshotUpdates.receive(msg);
|
|
4548
|
-
}
|
|
4549
4421
|
return;
|
|
4550
4422
|
}
|
|
4551
4423
|
this.deliverSessionMessage(msg);
|
|
@@ -4561,25 +4433,15 @@ export class DaemonClient {
|
|
|
4561
4433
|
this.reconnectAttempt = 0;
|
|
4562
4434
|
this.updateConnectionState({ status: "connected" }, { event: "HELLO_SERVER_INFO" });
|
|
4563
4435
|
this.startLivenessHeartbeat();
|
|
4564
|
-
this.
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
this.resubscribeFileSubscriptions();
|
|
4569
|
-
this.creations.reconnect();
|
|
4436
|
+
this.owned.restore(serverInfo, {
|
|
4437
|
+
...DEFAULT_CLIENT_CAPABILITIES,
|
|
4438
|
+
...this.config.capabilities,
|
|
4439
|
+
});
|
|
4570
4440
|
this.flushPendingSendQueue();
|
|
4571
4441
|
this.resolveConnect();
|
|
4572
4442
|
}
|
|
4573
4443
|
}
|
|
4574
4444
|
}
|
|
4575
|
-
if (consumerMessage.type === "terminal_stream_exit") {
|
|
4576
|
-
this.terminalStreams.removeTerminal(consumerMessage.payload.terminalId);
|
|
4577
|
-
}
|
|
4578
|
-
if (consumerMessage.type === "fs.file.update") {
|
|
4579
|
-
this.fileSubscriptions
|
|
4580
|
-
.get(consumerMessage.payload.subscriptionId)
|
|
4581
|
-
?.onUpdate(consumerMessage.payload.version);
|
|
4582
|
-
}
|
|
4583
4445
|
if (this.rawMessageListeners.size > 0) {
|
|
4584
4446
|
for (const handler of this.rawMessageListeners) {
|
|
4585
4447
|
try {
|
|
@@ -4607,21 +4469,26 @@ export class DaemonClient {
|
|
|
4607
4469
|
handler(event);
|
|
4608
4470
|
}
|
|
4609
4471
|
}
|
|
4610
|
-
if (consumerMessage.type === "workspace.create.update" ||
|
|
4611
|
-
consumerMessage.type === "agent.create.update")
|
|
4612
|
-
this.creations.receive(consumerMessage.payload);
|
|
4613
4472
|
this.resolveWaiters(consumerMessage);
|
|
4473
|
+
this.owned.receive(consumerMessage);
|
|
4614
4474
|
}
|
|
4615
4475
|
resolveWaiters(msg) {
|
|
4616
4476
|
for (const waiter of Array.from(this.waiters)) {
|
|
4617
|
-
|
|
4618
|
-
|
|
4477
|
+
try {
|
|
4478
|
+
const result = waiter.predicate(msg);
|
|
4479
|
+
if (result === null)
|
|
4480
|
+
continue;
|
|
4619
4481
|
this.waiters.delete(waiter);
|
|
4620
|
-
if (waiter.timeoutHandle)
|
|
4482
|
+
if (waiter.timeoutHandle)
|
|
4621
4483
|
clearTimeout(waiter.timeoutHandle);
|
|
4622
|
-
}
|
|
4623
4484
|
waiter.resolve(result);
|
|
4624
4485
|
}
|
|
4486
|
+
catch (error) {
|
|
4487
|
+
this.waiters.delete(waiter);
|
|
4488
|
+
if (waiter.timeoutHandle)
|
|
4489
|
+
clearTimeout(waiter.timeoutHandle);
|
|
4490
|
+
waiter.reject(error instanceof Error ? error : new Error(String(error)));
|
|
4491
|
+
}
|
|
4625
4492
|
}
|
|
4626
4493
|
}
|
|
4627
4494
|
rejectWaitersForRequestId(requestId, error) {
|
|
@@ -4704,7 +4571,7 @@ export class DaemonClient {
|
|
|
4704
4571
|
}
|
|
4705
4572
|
waitForWithCancel(predicate, timeout = 30000, options) {
|
|
4706
4573
|
// Capture stack trace at call site, not inside setTimeout
|
|
4707
|
-
const timeoutError = new
|
|
4574
|
+
const timeoutError = new DaemonConnectionError(`Timeout waiting for message (${timeout}ms)`, "DAEMON_REQUEST_TIMEOUT");
|
|
4708
4575
|
let waiter = null;
|
|
4709
4576
|
let settled = false;
|
|
4710
4577
|
let rejectFn = null;
|
|
@@ -4764,7 +4631,7 @@ export class DaemonClient {
|
|
|
4764
4631
|
}
|
|
4765
4632
|
}
|
|
4766
4633
|
function resolveAgentConfig(options) {
|
|
4767
|
-
const { config, provider, cwd,
|
|
4634
|
+
const { config, provider, cwd, env: _env, workspaceId: _workspaceId, initialPrompt: _initialPrompt, images: _images, git: _git, worktreeName: _worktreeName, requestId: _requestId, labels: _labels, ...overrides } = options;
|
|
4768
4635
|
const baseConfig = {
|
|
4769
4636
|
...(provider ? { provider } : {}),
|
|
4770
4637
|
...(cwd ? { cwd } : {}),
|