@nuvin/session 0.1.0-rc.10
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/LICENSE +202 -0
- package/dist/chunk-2LIGD6UP.js +11 -0
- package/dist/chunk-3IPZO7LP.js +66 -0
- package/dist/chunk-3S4DZMX3.js +681 -0
- package/dist/chunk-HJ666YLF.js +6 -0
- package/dist/chunk-UVJIG4DT.js +17 -0
- package/dist/client/daemon-client.d.ts +50 -0
- package/dist/client/daemon-client.d.ts.map +1 -0
- package/dist/client/data-client.d.ts +79 -0
- package/dist/client/data-client.d.ts.map +1 -0
- package/dist/client/directory.d.ts +99 -0
- package/dist/client/directory.d.ts.map +1 -0
- package/dist/client/endpoint.d.ts +39 -0
- package/dist/client/endpoint.d.ts.map +1 -0
- package/dist/client/http-data-client.d.ts +23 -0
- package/dist/client/http-data-client.d.ts.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +1418 -0
- package/dist/client/session-client.d.ts +76 -0
- package/dist/client/session-client.d.ts.map +1 -0
- package/dist/client/socket.d.ts +38 -0
- package/dist/client/socket.d.ts.map +1 -0
- package/dist/client/transport.d.ts +9 -0
- package/dist/client/transport.d.ts.map +1 -0
- package/dist/client/uds-socket.d.ts +23 -0
- package/dist/client/uds-socket.d.ts.map +1 -0
- package/dist/client/websocket.d.ts +54 -0
- package/dist/client/websocket.d.ts.map +1 -0
- package/dist/controller/agent-channel.d.ts +58 -0
- package/dist/controller/agent-channel.d.ts.map +1 -0
- package/dist/controller/index.d.ts +3 -0
- package/dist/controller/index.d.ts.map +1 -0
- package/dist/controller/index.js +447 -0
- package/dist/controller/session-controller.d.ts +158 -0
- package/dist/controller/session-controller.d.ts.map +1 -0
- package/dist/controller/test-utils.d.ts +15 -0
- package/dist/controller/test-utils.d.ts.map +1 -0
- package/dist/grant/index.d.ts +33 -0
- package/dist/grant/index.d.ts.map +1 -0
- package/dist/grant/index.js +12 -0
- package/dist/protocol/daemon-directory.d.ts +30 -0
- package/dist/protocol/daemon-directory.d.ts.map +1 -0
- package/dist/protocol/data-rpc.d.ts +122 -0
- package/dist/protocol/data-rpc.d.ts.map +1 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +10 -0
- package/dist/protocol/types.d.ts +971 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/version.d.ts +8 -0
- package/dist/protocol/version.d.ts.map +1 -0
- package/dist/state/approvals.d.ts +34 -0
- package/dist/state/approvals.d.ts.map +1 -0
- package/dist/state/dir-access.d.ts +9 -0
- package/dist/state/dir-access.d.ts.map +1 -0
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +48 -0
- package/dist/state/json.d.ts +7 -0
- package/dist/state/json.d.ts.map +1 -0
- package/dist/state/messages.d.ts +105 -0
- package/dist/state/messages.d.ts.map +1 -0
- package/dist/state/session.d.ts +9 -0
- package/dist/state/session.d.ts.map +1 -0
- package/dist/state/tool-preview.d.ts +31 -0
- package/dist/state/tool-preview.d.ts.map +1 -0
- package/dist/state/tool-preview.js +300 -0
- package/dist/state/workflow-view.d.ts +10 -0
- package/dist/state/workflow-view.d.ts.map +1 -0
- package/dist/test-utils/fake-relay.d.ts +24 -0
- package/dist/test-utils/fake-relay.d.ts.map +1 -0
- package/dist/test-utils/index.d.ts +2 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/index.js +250 -0
- package/dist/ui/history-grouping.d.ts +36 -0
- package/dist/ui/history-grouping.d.ts.map +1 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +135 -0
- package/dist/ui/picker-rows.d.ts +62 -0
- package/dist/ui/picker-rows.d.ts.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSessionViewState,
|
|
3
|
+
reduceSessionEvent
|
|
4
|
+
} from "../chunk-3S4DZMX3.js";
|
|
5
|
+
import {
|
|
6
|
+
PROTOCOL_VERSION
|
|
7
|
+
} from "../chunk-HJ666YLF.js";
|
|
8
|
+
|
|
9
|
+
// src/client/endpoint.ts
|
|
10
|
+
var TerminalEndpointError = class extends Error {
|
|
11
|
+
reason;
|
|
12
|
+
constructor(reason, message) {
|
|
13
|
+
super(message ?? reason);
|
|
14
|
+
this.name = "TerminalEndpointError";
|
|
15
|
+
this.reason = reason;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/client/http-data-client.ts
|
|
20
|
+
function httpBaseFromWsUrl(wsUrl) {
|
|
21
|
+
const u = new URL(wsUrl);
|
|
22
|
+
const scheme = u.protocol === "wss:" ? "https:" : "http:";
|
|
23
|
+
return `${scheme}//${u.host}`;
|
|
24
|
+
}
|
|
25
|
+
function queryString(params) {
|
|
26
|
+
const q = new URLSearchParams();
|
|
27
|
+
for (const [key, value] of Object.entries(params)) {
|
|
28
|
+
if (value !== void 0) q.set(key, String(value));
|
|
29
|
+
}
|
|
30
|
+
const s = q.toString();
|
|
31
|
+
return s ? `?${s}` : "";
|
|
32
|
+
}
|
|
33
|
+
async function throwFromResponse(response, fallback, terminal, onUnauthorized) {
|
|
34
|
+
const body = await response.json().catch(() => null);
|
|
35
|
+
if (response.status === 401 || body?.code === "unauthorized") {
|
|
36
|
+
onUnauthorized?.();
|
|
37
|
+
throw new TerminalEndpointError(
|
|
38
|
+
"unauthorized",
|
|
39
|
+
typeof body?.message === "string" ? body.message : "Authentication required."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const mapped = terminal?.[response.status];
|
|
43
|
+
if (mapped) {
|
|
44
|
+
throw new TerminalEndpointError(
|
|
45
|
+
mapped.reason,
|
|
46
|
+
typeof body?.message === "string" ? body.message : mapped.message
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
throw new Error(
|
|
50
|
+
typeof body?.message === "string" ? body.message : `${fallback} (HTTP ${response.status}).`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function createHttpDataClient(options = {}) {
|
|
54
|
+
const fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
|
|
55
|
+
const baseUrl = (options.baseUrl ?? "").replace(/\/+$/, "");
|
|
56
|
+
const rows = /* @__PURE__ */ new Map();
|
|
57
|
+
const resolveToken = async (id) => {
|
|
58
|
+
const endpoint = rows.get(id)?.endpoint;
|
|
59
|
+
if (endpoint) return endpoint.token;
|
|
60
|
+
return options.getToken ? await options.getToken() : null;
|
|
61
|
+
};
|
|
62
|
+
const resolveBase = (id) => {
|
|
63
|
+
const endpoint = rows.get(id)?.endpoint;
|
|
64
|
+
return endpoint ? httpBaseFromWsUrl(endpoint.wsUrl) : baseUrl;
|
|
65
|
+
};
|
|
66
|
+
const daemonFetch = async (id, suffix, init = {}) => {
|
|
67
|
+
const token = await resolveToken(id);
|
|
68
|
+
const response = await fetchImpl(
|
|
69
|
+
`${resolveBase(id)}/api/daemons/${encodeURIComponent(id)}${suffix}`,
|
|
70
|
+
{
|
|
71
|
+
...init,
|
|
72
|
+
cache: "no-store",
|
|
73
|
+
headers: {
|
|
74
|
+
...init.headers ?? {},
|
|
75
|
+
...token !== null ? { Authorization: `Bearer ${token}` } : {}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
if (response.status === 401) {
|
|
80
|
+
await throwFromResponse(
|
|
81
|
+
response,
|
|
82
|
+
"Authentication required",
|
|
83
|
+
void 0,
|
|
84
|
+
options.onUnauthorized
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return response;
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
async listDaemons() {
|
|
91
|
+
const token = options.getToken ? await options.getToken() : null;
|
|
92
|
+
const response = await fetchImpl(`${baseUrl}/api/daemons`, {
|
|
93
|
+
cache: "no-store",
|
|
94
|
+
headers: token !== null ? { Authorization: `Bearer ${token}` } : {}
|
|
95
|
+
});
|
|
96
|
+
if (response.status === 401) {
|
|
97
|
+
options.onUnauthorized?.();
|
|
98
|
+
throw new TerminalEndpointError("unauthorized", "Signed out \u2014 authentication required.");
|
|
99
|
+
}
|
|
100
|
+
if (!response.ok) await throwFromResponse(response, "Failed to fetch the daemon list");
|
|
101
|
+
const body = await response.json();
|
|
102
|
+
rows.clear();
|
|
103
|
+
for (const row of body.daemons) rows.set(row.id, row);
|
|
104
|
+
return {
|
|
105
|
+
daemons: body.daemons,
|
|
106
|
+
...body.defaultSessionId !== void 0 ? { defaultSessionId: body.defaultSessionId } : {}
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
async acquireEndpoint(daemonId) {
|
|
110
|
+
const endpoint = rows.get(daemonId)?.endpoint;
|
|
111
|
+
if (endpoint) {
|
|
112
|
+
return { url: endpoint.wsUrl, credential: { token: endpoint.token } };
|
|
113
|
+
}
|
|
114
|
+
const request = (token) => fetchImpl(`${baseUrl}/api/daemons/${encodeURIComponent(daemonId)}/connect`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
cache: "no-store",
|
|
117
|
+
headers: token !== null ? { Authorization: `Bearer ${token}` } : {}
|
|
118
|
+
});
|
|
119
|
+
const throwUnauthorized = () => {
|
|
120
|
+
options.onUnauthorized?.();
|
|
121
|
+
throw new TerminalEndpointError("unauthorized", "Signed out \u2014 authentication required.");
|
|
122
|
+
};
|
|
123
|
+
const failedToken = options.getToken ? await options.getToken() : null;
|
|
124
|
+
let response = await request(failedToken);
|
|
125
|
+
if (response.status === 401 && options.refreshAccessToken) {
|
|
126
|
+
let retryToken = null;
|
|
127
|
+
try {
|
|
128
|
+
const currentToken = options.getToken ? await options.getToken() : null;
|
|
129
|
+
retryToken = currentToken && currentToken !== failedToken ? currentToken : null;
|
|
130
|
+
if (!retryToken) retryToken = await options.refreshAccessToken();
|
|
131
|
+
} catch {
|
|
132
|
+
throwUnauthorized();
|
|
133
|
+
}
|
|
134
|
+
if (!retryToken) {
|
|
135
|
+
throwUnauthorized();
|
|
136
|
+
}
|
|
137
|
+
response = await request(retryToken);
|
|
138
|
+
}
|
|
139
|
+
if (response.status === 401) {
|
|
140
|
+
throwUnauthorized();
|
|
141
|
+
}
|
|
142
|
+
if (response.status === 404) {
|
|
143
|
+
throw new TerminalEndpointError(
|
|
144
|
+
"daemon-revoked",
|
|
145
|
+
`Daemon "${daemonId}" is no longer registered.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (!response.ok)
|
|
149
|
+
await throwFromResponse(
|
|
150
|
+
response,
|
|
151
|
+
response.status === 409 ? `Daemon "${daemonId}" is offline` : `Connect failed for "${daemonId}"`
|
|
152
|
+
);
|
|
153
|
+
const body = await response.json();
|
|
154
|
+
return { url: body.wsUrl, credential: { grant: body.grant } };
|
|
155
|
+
},
|
|
156
|
+
async fetchTranscript(daemonId, historyId, opts) {
|
|
157
|
+
const suffix = `/transcript/${encodeURIComponent(historyId)}${queryString({
|
|
158
|
+
workspace: opts?.workspace,
|
|
159
|
+
profile: opts?.profile
|
|
160
|
+
})}`;
|
|
161
|
+
const response = await daemonFetch(daemonId, suffix);
|
|
162
|
+
if (response.status === 404) {
|
|
163
|
+
throw new TerminalEndpointError(
|
|
164
|
+
"transcript-not-found",
|
|
165
|
+
"This session's transcript is no longer available."
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (!response.ok) await throwFromResponse(response, "Failed to load the transcript");
|
|
169
|
+
return await response.json();
|
|
170
|
+
},
|
|
171
|
+
async fetchHistory(daemonId, opts) {
|
|
172
|
+
const suffix = `/history${queryString({
|
|
173
|
+
profile: opts?.profile,
|
|
174
|
+
cwd: opts?.cwd,
|
|
175
|
+
limit: opts?.limit,
|
|
176
|
+
cursor: opts?.cursor
|
|
177
|
+
})}`;
|
|
178
|
+
const response = await daemonFetch(daemonId, suffix);
|
|
179
|
+
if (!response.ok)
|
|
180
|
+
await throwFromResponse(response, "Failed to load history", {
|
|
181
|
+
404: { reason: "daemon-revoked", message: "This daemon is no longer registered." }
|
|
182
|
+
});
|
|
183
|
+
return await response.json();
|
|
184
|
+
},
|
|
185
|
+
async listWorkspaces(daemonId, opts) {
|
|
186
|
+
const suffix = `/workspaces${queryString({ profile: opts?.profile })}`;
|
|
187
|
+
const response = await daemonFetch(daemonId, suffix);
|
|
188
|
+
if (!response.ok)
|
|
189
|
+
await throwFromResponse(response, "Failed to load workspaces", {
|
|
190
|
+
404: { reason: "daemon-revoked", message: "This daemon is no longer registered." }
|
|
191
|
+
});
|
|
192
|
+
return await response.json();
|
|
193
|
+
},
|
|
194
|
+
async createWorkspace(daemonId, opts) {
|
|
195
|
+
const response = await daemonFetch(daemonId, "/workspaces", {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: { "Content-Type": "application/json" },
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
name: opts.name,
|
|
200
|
+
...opts.root !== void 0 ? { root: opts.root } : {},
|
|
201
|
+
...opts.profile !== void 0 ? { profile: opts.profile } : {}
|
|
202
|
+
})
|
|
203
|
+
});
|
|
204
|
+
if (!response.ok) await throwFromResponse(response, "Failed to create the workspace");
|
|
205
|
+
return await response.json();
|
|
206
|
+
},
|
|
207
|
+
async deleteSession(daemonId, historyId, opts) {
|
|
208
|
+
const suffix = `/sessions/${encodeURIComponent(historyId)}${queryString({
|
|
209
|
+
workspace: opts?.workspace,
|
|
210
|
+
profile: opts?.profile
|
|
211
|
+
})}`;
|
|
212
|
+
const response = await daemonFetch(daemonId, suffix, { method: "DELETE" });
|
|
213
|
+
if (response.status === 404) {
|
|
214
|
+
throw new TerminalEndpointError("session-not-found", "This session no longer exists.");
|
|
215
|
+
}
|
|
216
|
+
if (!response.ok) await throwFromResponse(response, "Failed to delete the session");
|
|
217
|
+
},
|
|
218
|
+
async listProfiles(daemonId) {
|
|
219
|
+
const response = await daemonFetch(daemonId, "/profiles");
|
|
220
|
+
if (!response.ok) {
|
|
221
|
+
await throwFromResponse(response, "Failed to load profiles", {
|
|
222
|
+
404: { reason: "daemon-revoked", message: "This daemon is no longer registered." }
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return await response.json();
|
|
226
|
+
},
|
|
227
|
+
async listProviders(daemonId, opts) {
|
|
228
|
+
const suffix = `/providers${queryString({ profile: opts?.profile })}`;
|
|
229
|
+
const response = await daemonFetch(daemonId, suffix);
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
await throwFromResponse(response, "Failed to load providers", {
|
|
232
|
+
404: { reason: "daemon-revoked", message: "This daemon is no longer registered." }
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return await response.json();
|
|
236
|
+
},
|
|
237
|
+
async addProvider(daemonId, input) {
|
|
238
|
+
const response = await daemonFetch(daemonId, "/providers", {
|
|
239
|
+
method: "POST",
|
|
240
|
+
headers: { "Content-Type": "application/json" },
|
|
241
|
+
body: JSON.stringify(input)
|
|
242
|
+
});
|
|
243
|
+
if (!response.ok) await throwFromResponse(response, "Failed to add provider");
|
|
244
|
+
return await response.json();
|
|
245
|
+
},
|
|
246
|
+
async removeProvider(daemonId, providerId, opts) {
|
|
247
|
+
const response = await daemonFetch(daemonId, `/providers/${encodeURIComponent(providerId)}`, {
|
|
248
|
+
method: "DELETE",
|
|
249
|
+
headers: { "Content-Type": "application/json" },
|
|
250
|
+
body: JSON.stringify(opts?.profile !== void 0 ? { profile: opts.profile } : {})
|
|
251
|
+
});
|
|
252
|
+
if (!response.ok) await throwFromResponse(response, "Failed to remove provider");
|
|
253
|
+
return await response.json();
|
|
254
|
+
},
|
|
255
|
+
async setDefaultProvider(daemonId, providerId, opts) {
|
|
256
|
+
const response = await daemonFetch(daemonId, "/providers/default", {
|
|
257
|
+
method: "PUT",
|
|
258
|
+
headers: { "Content-Type": "application/json" },
|
|
259
|
+
body: JSON.stringify({
|
|
260
|
+
providerId,
|
|
261
|
+
...opts?.profile !== void 0 ? { profile: opts.profile } : {}
|
|
262
|
+
})
|
|
263
|
+
});
|
|
264
|
+
if (!response.ok) await throwFromResponse(response, "Failed to set default provider");
|
|
265
|
+
return await response.json();
|
|
266
|
+
},
|
|
267
|
+
async setProviderAuth(daemonId, providerId, credential, opts) {
|
|
268
|
+
const response = await daemonFetch(
|
|
269
|
+
daemonId,
|
|
270
|
+
`/providers/${encodeURIComponent(providerId)}/credential`,
|
|
271
|
+
{
|
|
272
|
+
method: "PUT",
|
|
273
|
+
headers: { "Content-Type": "application/json" },
|
|
274
|
+
body: JSON.stringify({
|
|
275
|
+
credential,
|
|
276
|
+
...opts?.profile !== void 0 ? { profile: opts.profile } : {}
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
if (!response.ok) await throwFromResponse(response, "Failed to update provider auth");
|
|
281
|
+
return await response.json();
|
|
282
|
+
},
|
|
283
|
+
async clearProviderAuth(daemonId, providerId, opts) {
|
|
284
|
+
const response = await daemonFetch(
|
|
285
|
+
daemonId,
|
|
286
|
+
`/providers/${encodeURIComponent(providerId)}/credential`,
|
|
287
|
+
{
|
|
288
|
+
method: "DELETE",
|
|
289
|
+
headers: { "Content-Type": "application/json" },
|
|
290
|
+
body: JSON.stringify(opts?.profile !== void 0 ? { profile: opts.profile } : {})
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
if (!response.ok) await throwFromResponse(response, "Failed to clear provider auth");
|
|
294
|
+
return await response.json();
|
|
295
|
+
},
|
|
296
|
+
async updateWorkspace(daemonId, name, patch) {
|
|
297
|
+
const response = await daemonFetch(daemonId, `/workspaces/${encodeURIComponent(name)}`, {
|
|
298
|
+
method: "PATCH",
|
|
299
|
+
headers: { "Content-Type": "application/json" },
|
|
300
|
+
body: JSON.stringify(patch)
|
|
301
|
+
});
|
|
302
|
+
if (!response.ok) await throwFromResponse(response, "Failed to update workspace");
|
|
303
|
+
return await response.json();
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/client/session-client.ts
|
|
309
|
+
var EMPTY_MODEL_COMPLETION = {
|
|
310
|
+
providers: [],
|
|
311
|
+
perProvider: {},
|
|
312
|
+
recentModels: []
|
|
313
|
+
};
|
|
314
|
+
function toModelCompletion(result) {
|
|
315
|
+
if (typeof result !== "object" || result === null || Array.isArray(result)) {
|
|
316
|
+
return EMPTY_MODEL_COMPLETION;
|
|
317
|
+
}
|
|
318
|
+
const record = result;
|
|
319
|
+
const providers = Array.isArray(record.providers) ? record.providers.filter((p) => typeof p === "string") : [];
|
|
320
|
+
const perProvider = {};
|
|
321
|
+
if (typeof record.perProvider === "object" && record.perProvider !== null) {
|
|
322
|
+
for (const [provider, models] of Object.entries(
|
|
323
|
+
record.perProvider
|
|
324
|
+
)) {
|
|
325
|
+
if (!Array.isArray(models)) continue;
|
|
326
|
+
perProvider[provider] = models.flatMap((entry) => {
|
|
327
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
|
|
328
|
+
const id = entry.id;
|
|
329
|
+
if (typeof id !== "string") return [];
|
|
330
|
+
const name = entry.name;
|
|
331
|
+
return [typeof name === "string" ? { id, name } : { id }];
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const recentModels = Array.isArray(record.recentModels) ? record.recentModels.flatMap((entry) => {
|
|
336
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
|
|
337
|
+
const provider = entry.provider;
|
|
338
|
+
const model = entry.model;
|
|
339
|
+
return typeof provider === "string" && typeof model === "string" ? [{ provider, model }] : [];
|
|
340
|
+
}) : [];
|
|
341
|
+
return { providers, perProvider, recentModels };
|
|
342
|
+
}
|
|
343
|
+
function createSessionClient(transport) {
|
|
344
|
+
let state = createSessionViewState();
|
|
345
|
+
let seq = 0;
|
|
346
|
+
let closed = false;
|
|
347
|
+
let notifyScheduled = false;
|
|
348
|
+
let commandCounter = 0;
|
|
349
|
+
let serverCommandList = [];
|
|
350
|
+
const stateListeners = /* @__PURE__ */ new Set();
|
|
351
|
+
const eventListeners = /* @__PURE__ */ new Set();
|
|
352
|
+
const pending = /* @__PURE__ */ new Map();
|
|
353
|
+
const scheduleNotify = () => {
|
|
354
|
+
if (notifyScheduled) return;
|
|
355
|
+
notifyScheduled = true;
|
|
356
|
+
queueMicrotask(() => {
|
|
357
|
+
notifyScheduled = false;
|
|
358
|
+
for (const listener of stateListeners) {
|
|
359
|
+
try {
|
|
360
|
+
listener();
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
const unsubscribe = transport.onFrame((frame) => {
|
|
367
|
+
if (frame.type === "snapshot") {
|
|
368
|
+
state = frame.snapshot.state;
|
|
369
|
+
seq = frame.snapshot.seq;
|
|
370
|
+
serverCommandList = frame.snapshot.serverCommands;
|
|
371
|
+
scheduleNotify();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (frame.type === "ack") {
|
|
375
|
+
const entry = pending.get(frame.id);
|
|
376
|
+
if (!entry) return;
|
|
377
|
+
pending.delete(frame.id);
|
|
378
|
+
if (frame.ok) {
|
|
379
|
+
entry.resolve(frame.result);
|
|
380
|
+
} else {
|
|
381
|
+
entry.reject(new Error(frame.error?.message ?? "Command failed."));
|
|
382
|
+
}
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
state = reduceSessionEvent(state, frame);
|
|
386
|
+
seq = frame.seq;
|
|
387
|
+
for (const listener of eventListeners) {
|
|
388
|
+
try {
|
|
389
|
+
listener(frame);
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
scheduleNotify();
|
|
394
|
+
});
|
|
395
|
+
const sendCommand = (build) => {
|
|
396
|
+
if (closed) {
|
|
397
|
+
return Promise.reject(new Error("SessionClient is closed."));
|
|
398
|
+
}
|
|
399
|
+
commandCounter += 1;
|
|
400
|
+
const id = `cmd-${commandCounter}`;
|
|
401
|
+
return new Promise((resolve, reject) => {
|
|
402
|
+
pending.set(id, { resolve, reject });
|
|
403
|
+
transport.send(build(id));
|
|
404
|
+
});
|
|
405
|
+
};
|
|
406
|
+
return {
|
|
407
|
+
getState() {
|
|
408
|
+
return state;
|
|
409
|
+
},
|
|
410
|
+
getSeq() {
|
|
411
|
+
return seq;
|
|
412
|
+
},
|
|
413
|
+
subscribe(listener) {
|
|
414
|
+
stateListeners.add(listener);
|
|
415
|
+
return () => stateListeners.delete(listener);
|
|
416
|
+
},
|
|
417
|
+
onEvent(listener) {
|
|
418
|
+
eventListeners.add(listener);
|
|
419
|
+
return () => eventListeners.delete(listener);
|
|
420
|
+
},
|
|
421
|
+
submit(input, opts) {
|
|
422
|
+
return sendCommand((id) => ({
|
|
423
|
+
id,
|
|
424
|
+
type: "submit",
|
|
425
|
+
input,
|
|
426
|
+
displayText: opts.displayText,
|
|
427
|
+
...opts.attachmentLabels?.length ? { attachmentLabels: opts.attachmentLabels } : {}
|
|
428
|
+
})).then(() => {
|
|
429
|
+
});
|
|
430
|
+
},
|
|
431
|
+
decideApproval(toolCallId, decision, comment, grantDir) {
|
|
432
|
+
return sendCommand((id) => ({
|
|
433
|
+
id,
|
|
434
|
+
type: "decide-approval",
|
|
435
|
+
toolCallId,
|
|
436
|
+
decision,
|
|
437
|
+
...comment !== void 0 ? { comment } : {},
|
|
438
|
+
...grantDir !== void 0 ? { grantDir } : {}
|
|
439
|
+
})).then(() => {
|
|
440
|
+
});
|
|
441
|
+
},
|
|
442
|
+
answerQuestion(questionId, answers) {
|
|
443
|
+
return sendCommand((id) => ({ id, type: "answer-question", questionId, answers })).then(
|
|
444
|
+
() => {
|
|
445
|
+
}
|
|
446
|
+
);
|
|
447
|
+
},
|
|
448
|
+
abort() {
|
|
449
|
+
return sendCommand((id) => ({ id, type: "abort" })).then(() => {
|
|
450
|
+
});
|
|
451
|
+
},
|
|
452
|
+
dequeueMessage(queuedId) {
|
|
453
|
+
return sendCommand((id) => ({ id, type: "dequeue", queuedId })).then(() => {
|
|
454
|
+
});
|
|
455
|
+
},
|
|
456
|
+
runSlashCommand(raw) {
|
|
457
|
+
return sendCommand((id) => ({ id, type: "slash-command", raw })).then(() => {
|
|
458
|
+
});
|
|
459
|
+
},
|
|
460
|
+
loadHistory(historyId) {
|
|
461
|
+
return sendCommand((id) => ({ id, type: "load-history", historyId })).then(() => {
|
|
462
|
+
});
|
|
463
|
+
},
|
|
464
|
+
cancelAuthFlow() {
|
|
465
|
+
return sendCommand((id) => ({ id, type: "cancel-auth-flow" })).then(() => {
|
|
466
|
+
});
|
|
467
|
+
},
|
|
468
|
+
mcpAction(action, serverName) {
|
|
469
|
+
return sendCommand((id) => ({ id, type: "mcp-action", action, serverName })).then(
|
|
470
|
+
(result) => result
|
|
471
|
+
);
|
|
472
|
+
},
|
|
473
|
+
serverCommands() {
|
|
474
|
+
return serverCommandList;
|
|
475
|
+
},
|
|
476
|
+
completePath(prefix) {
|
|
477
|
+
return sendCommand((id) => ({ id, type: "complete-path", prefix })).then(
|
|
478
|
+
(result) => Array.isArray(result) ? result.filter((entry) => typeof entry === "string") : []
|
|
479
|
+
);
|
|
480
|
+
},
|
|
481
|
+
completeModels() {
|
|
482
|
+
return sendCommand((id) => ({ id, type: "complete-models" })).then(toModelCompletion);
|
|
483
|
+
},
|
|
484
|
+
setConfig(patches) {
|
|
485
|
+
return sendCommand((id) => ({ id, type: "set-config", patches })).then(
|
|
486
|
+
(result) => result
|
|
487
|
+
);
|
|
488
|
+
},
|
|
489
|
+
listSkills() {
|
|
490
|
+
return sendCommand((id) => ({ id, type: "list-skills" })).then(
|
|
491
|
+
(result) => Array.isArray(result) ? result : []
|
|
492
|
+
);
|
|
493
|
+
},
|
|
494
|
+
getSkill(skillId) {
|
|
495
|
+
return sendCommand((id) => ({ id, type: "get-skill", skillId })).then(
|
|
496
|
+
(result) => result
|
|
497
|
+
);
|
|
498
|
+
},
|
|
499
|
+
listAgents() {
|
|
500
|
+
return sendCommand((id) => ({ id, type: "list-agents" })).then(
|
|
501
|
+
(result) => Array.isArray(result) ? result : []
|
|
502
|
+
);
|
|
503
|
+
},
|
|
504
|
+
getAgent(agentId) {
|
|
505
|
+
return sendCommand((id) => ({ id, type: "get-agent", agentId })).then(
|
|
506
|
+
(result) => result
|
|
507
|
+
);
|
|
508
|
+
},
|
|
509
|
+
getCommand(name) {
|
|
510
|
+
return sendCommand((id) => ({ id, type: "get-command", name })).then(
|
|
511
|
+
(result) => result
|
|
512
|
+
);
|
|
513
|
+
},
|
|
514
|
+
listMcpServers() {
|
|
515
|
+
return sendCommand((id) => ({ id, type: "list-mcp-servers" })).then(
|
|
516
|
+
(result) => Array.isArray(result) ? result : []
|
|
517
|
+
);
|
|
518
|
+
},
|
|
519
|
+
close() {
|
|
520
|
+
if (closed) return;
|
|
521
|
+
closed = true;
|
|
522
|
+
unsubscribe();
|
|
523
|
+
const error = new Error("SessionClient closed before the command was acknowledged.");
|
|
524
|
+
for (const { reject } of pending.values()) {
|
|
525
|
+
reject(error);
|
|
526
|
+
}
|
|
527
|
+
pending.clear();
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/client/socket.ts
|
|
533
|
+
function adaptWhatwgSocket(socket) {
|
|
534
|
+
const on = (event, listener) => {
|
|
535
|
+
switch (event) {
|
|
536
|
+
case "open":
|
|
537
|
+
socket.addEventListener("open", () => listener(void 0));
|
|
538
|
+
return;
|
|
539
|
+
case "message":
|
|
540
|
+
socket.addEventListener("message", (messageEvent) => listener(messageEvent.data));
|
|
541
|
+
return;
|
|
542
|
+
case "close":
|
|
543
|
+
socket.addEventListener("close", () => listener(void 0));
|
|
544
|
+
return;
|
|
545
|
+
case "error":
|
|
546
|
+
socket.addEventListener("error", (errorEvent) => listener(errorEvent));
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
return {
|
|
551
|
+
on,
|
|
552
|
+
send: (data) => socket.send(data),
|
|
553
|
+
close: () => socket.close()
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function globalWebSocketConstructor() {
|
|
557
|
+
const candidate = globalThis.WebSocket;
|
|
558
|
+
if (typeof candidate !== "function") {
|
|
559
|
+
throw new Error(
|
|
560
|
+
"No global WebSocket constructor is available. Pass `socketFactory` (node < 22 callers can wrap the 'ws' package)."
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
return candidate;
|
|
564
|
+
}
|
|
565
|
+
function defaultSocketFactory(url) {
|
|
566
|
+
const WebSocketCtor = globalWebSocketConstructor();
|
|
567
|
+
return adaptWhatwgSocket(new WebSocketCtor(url));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/client/uds-socket.ts
|
|
571
|
+
import { WebSocket } from "ws";
|
|
572
|
+
function udsUrl(socketPath) {
|
|
573
|
+
return `ws+unix://${socketPath}:/`;
|
|
574
|
+
}
|
|
575
|
+
function ensureLocalhostNoProxy() {
|
|
576
|
+
const ga = globalThis.GLOBAL_AGENT;
|
|
577
|
+
if (!ga) return;
|
|
578
|
+
const current = ga.NO_PROXY ?? "";
|
|
579
|
+
const entries = current.split(",").map((s) => s.trim()).filter(Boolean);
|
|
580
|
+
let changed = false;
|
|
581
|
+
for (const host of ["localhost", "127.0.0.1"]) {
|
|
582
|
+
if (!entries.includes(host)) {
|
|
583
|
+
entries.push(host);
|
|
584
|
+
changed = true;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (changed) {
|
|
588
|
+
ga.NO_PROXY = entries.join(",");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function udsSocketFactory(socketPath) {
|
|
592
|
+
const target = udsUrl(socketPath);
|
|
593
|
+
return (_url) => {
|
|
594
|
+
ensureLocalhostNoProxy();
|
|
595
|
+
return new WebSocket(target);
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// src/client/websocket.ts
|
|
600
|
+
var toText = (data) => {
|
|
601
|
+
if (typeof data === "string") return data;
|
|
602
|
+
if (data instanceof Buffer) return data.toString("utf8");
|
|
603
|
+
if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf8");
|
|
604
|
+
if (Array.isArray(data) && data.every((chunk) => chunk instanceof Buffer)) {
|
|
605
|
+
return Buffer.concat(data).toString("utf8");
|
|
606
|
+
}
|
|
607
|
+
return null;
|
|
608
|
+
};
|
|
609
|
+
var parseMessage = (data) => {
|
|
610
|
+
const text = toText(data);
|
|
611
|
+
if (text === null) return null;
|
|
612
|
+
try {
|
|
613
|
+
const parsed = JSON.parse(text);
|
|
614
|
+
return typeof parsed === "object" && parsed !== null && typeof parsed.type === "string" ? parsed : null;
|
|
615
|
+
} catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
function createWebSocketTransport(options) {
|
|
620
|
+
if (!options.getEndpoint && (options.url === void 0 || options.token === void 0)) {
|
|
621
|
+
throw new Error("createWebSocketTransport requires either url+token or getEndpoint.");
|
|
622
|
+
}
|
|
623
|
+
const socketFactory = options.socketFactory ?? defaultSocketFactory;
|
|
624
|
+
const backoffInitialMs = options.backoffInitialMs ?? 500;
|
|
625
|
+
const backoffMaxMs = options.backoffMaxMs ?? 3e4;
|
|
626
|
+
const backoffFactor = options.backoffFactor ?? 2;
|
|
627
|
+
const frameListeners = /* @__PURE__ */ new Set();
|
|
628
|
+
const stateListeners = /* @__PURE__ */ new Set();
|
|
629
|
+
let state = "reconnecting";
|
|
630
|
+
let phase = "awaiting-welcome";
|
|
631
|
+
let socket = null;
|
|
632
|
+
let lastSeq = null;
|
|
633
|
+
let attempts = 0;
|
|
634
|
+
let attachCounter = 0;
|
|
635
|
+
let reconnectTimer = null;
|
|
636
|
+
let closed = false;
|
|
637
|
+
const setState = (next) => {
|
|
638
|
+
if (state === next) return;
|
|
639
|
+
state = next;
|
|
640
|
+
for (const listener of stateListeners) {
|
|
641
|
+
try {
|
|
642
|
+
listener(next);
|
|
643
|
+
} catch {
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
const deliver = (frame) => {
|
|
648
|
+
for (const listener of frameListeners) {
|
|
649
|
+
try {
|
|
650
|
+
listener(frame);
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
const terminate = () => {
|
|
656
|
+
closed = true;
|
|
657
|
+
if (reconnectTimer !== null) {
|
|
658
|
+
clearTimeout(reconnectTimer);
|
|
659
|
+
reconnectTimer = null;
|
|
660
|
+
}
|
|
661
|
+
const current = socket;
|
|
662
|
+
socket = null;
|
|
663
|
+
current?.close();
|
|
664
|
+
setState("closed");
|
|
665
|
+
};
|
|
666
|
+
const scheduleReconnect = () => {
|
|
667
|
+
if (closed || reconnectTimer !== null) return;
|
|
668
|
+
setState("reconnecting");
|
|
669
|
+
const delay = Math.min(backoffInitialMs * backoffFactor ** attempts, backoffMaxMs);
|
|
670
|
+
attempts += 1;
|
|
671
|
+
reconnectTimer = setTimeout(() => {
|
|
672
|
+
reconnectTimer = null;
|
|
673
|
+
connect();
|
|
674
|
+
}, delay);
|
|
675
|
+
};
|
|
676
|
+
const completeAttach = () => {
|
|
677
|
+
phase = "attached";
|
|
678
|
+
attempts = 0;
|
|
679
|
+
setState("connected");
|
|
680
|
+
};
|
|
681
|
+
const handleMessage = (message) => {
|
|
682
|
+
if (message.type === "rejected") {
|
|
683
|
+
terminate();
|
|
684
|
+
options.onRejected?.(message.reason);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (message.type === "detached") {
|
|
688
|
+
terminate();
|
|
689
|
+
options.onDetached?.(message.reason);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (phase === "awaiting-welcome") {
|
|
693
|
+
if (message.type !== "welcome") return;
|
|
694
|
+
phase = "awaiting-attach";
|
|
695
|
+
setState("replaying");
|
|
696
|
+
attachCounter += 1;
|
|
697
|
+
socket?.send(
|
|
698
|
+
JSON.stringify({
|
|
699
|
+
id: `attach-${attachCounter}`,
|
|
700
|
+
type: "attach",
|
|
701
|
+
sessionId: options.sessionId,
|
|
702
|
+
...lastSeq !== null ? { lastSeq } : {}
|
|
703
|
+
})
|
|
704
|
+
);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (phase === "awaiting-attach") {
|
|
708
|
+
if (message.type === "replay") {
|
|
709
|
+
for (const event of message.events) {
|
|
710
|
+
lastSeq = event.seq;
|
|
711
|
+
deliver(event);
|
|
712
|
+
}
|
|
713
|
+
completeAttach();
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (message.type === "snapshot") {
|
|
717
|
+
lastSeq = message.snapshot.seq;
|
|
718
|
+
deliver(message);
|
|
719
|
+
completeAttach();
|
|
720
|
+
}
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (message.type === "ack") {
|
|
724
|
+
deliver(message);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (message.type === "snapshot") {
|
|
728
|
+
lastSeq = message.snapshot.seq;
|
|
729
|
+
deliver(message);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (message.type === "welcome" || message.type === "replay" || message.type === "session-list" || message.type === "session-created") {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
lastSeq = message.seq;
|
|
736
|
+
deliver(message);
|
|
737
|
+
};
|
|
738
|
+
const openSocket = (endpoint) => {
|
|
739
|
+
if (closed) return;
|
|
740
|
+
phase = "awaiting-welcome";
|
|
741
|
+
const current = socketFactory(endpoint.url);
|
|
742
|
+
socket = current;
|
|
743
|
+
current.on("open", () => {
|
|
744
|
+
if (closed || socket !== current) return;
|
|
745
|
+
current.send(
|
|
746
|
+
JSON.stringify({
|
|
747
|
+
type: "hello",
|
|
748
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
749
|
+
...endpoint.credential
|
|
750
|
+
})
|
|
751
|
+
);
|
|
752
|
+
});
|
|
753
|
+
current.on("message", (data) => {
|
|
754
|
+
if (closed || socket !== current) return;
|
|
755
|
+
const message = parseMessage(data);
|
|
756
|
+
if (message) handleMessage(message);
|
|
757
|
+
});
|
|
758
|
+
current.on("error", () => {
|
|
759
|
+
});
|
|
760
|
+
current.on("close", () => {
|
|
761
|
+
if (socket !== current) return;
|
|
762
|
+
socket = null;
|
|
763
|
+
if (closed) return;
|
|
764
|
+
scheduleReconnect();
|
|
765
|
+
});
|
|
766
|
+
};
|
|
767
|
+
const connect = () => {
|
|
768
|
+
if (closed) return;
|
|
769
|
+
const getEndpoint = options.getEndpoint;
|
|
770
|
+
if (!getEndpoint) {
|
|
771
|
+
openSocket({ url: options.url, credential: { token: options.token } });
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
void getEndpoint().then(
|
|
775
|
+
(endpoint) => {
|
|
776
|
+
if (closed) return;
|
|
777
|
+
openSocket(endpoint);
|
|
778
|
+
},
|
|
779
|
+
(error) => {
|
|
780
|
+
if (closed) return;
|
|
781
|
+
if (error instanceof TerminalEndpointError) {
|
|
782
|
+
terminate();
|
|
783
|
+
options.onDetached?.(error.reason);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
scheduleReconnect();
|
|
787
|
+
}
|
|
788
|
+
);
|
|
789
|
+
};
|
|
790
|
+
connect();
|
|
791
|
+
return {
|
|
792
|
+
send(command) {
|
|
793
|
+
if (closed || phase !== "attached" || socket === null) return;
|
|
794
|
+
socket.send(JSON.stringify(command));
|
|
795
|
+
},
|
|
796
|
+
onFrame(listener) {
|
|
797
|
+
frameListeners.add(listener);
|
|
798
|
+
return () => {
|
|
799
|
+
frameListeners.delete(listener);
|
|
800
|
+
};
|
|
801
|
+
},
|
|
802
|
+
close() {
|
|
803
|
+
if (closed) return;
|
|
804
|
+
terminate();
|
|
805
|
+
frameListeners.clear();
|
|
806
|
+
stateListeners.clear();
|
|
807
|
+
},
|
|
808
|
+
connectionState() {
|
|
809
|
+
return state;
|
|
810
|
+
},
|
|
811
|
+
onConnectionState(listener) {
|
|
812
|
+
stateListeners.add(listener);
|
|
813
|
+
return () => {
|
|
814
|
+
stateListeners.delete(listener);
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/client/daemon-client.ts
|
|
821
|
+
var CLOSED_ERROR = "DaemonClient is closed.";
|
|
822
|
+
function throwCollectedErrors(errors, message) {
|
|
823
|
+
if (errors.length === 1) throw errors[0];
|
|
824
|
+
if (errors.length > 1) throw new AggregateError(errors, message);
|
|
825
|
+
}
|
|
826
|
+
function bindDaemonDataClient(client, daemonId) {
|
|
827
|
+
return {
|
|
828
|
+
fetchTranscript: (historyId, opts) => client.fetchTranscript(daemonId, historyId, opts),
|
|
829
|
+
fetchHistory: (opts) => client.fetchHistory(daemonId, opts),
|
|
830
|
+
listWorkspaces: (opts) => client.listWorkspaces(daemonId, opts),
|
|
831
|
+
createWorkspace: (opts) => client.createWorkspace(daemonId, opts),
|
|
832
|
+
deleteSession: (historyId, opts) => client.deleteSession(daemonId, historyId, opts),
|
|
833
|
+
listProfiles: () => client.listProfiles(daemonId),
|
|
834
|
+
listProviders: (opts) => client.listProviders(daemonId, opts),
|
|
835
|
+
addProvider: (input) => client.addProvider(daemonId, input),
|
|
836
|
+
removeProvider: (providerId, opts) => client.removeProvider(daemonId, providerId, opts),
|
|
837
|
+
setDefaultProvider: (providerId, opts) => client.setDefaultProvider(daemonId, providerId, opts),
|
|
838
|
+
setProviderAuth: (providerId, credential, opts) => client.setProviderAuth(daemonId, providerId, credential, opts),
|
|
839
|
+
clearProviderAuth: (providerId, opts) => client.clearProviderAuth(daemonId, providerId, opts),
|
|
840
|
+
updateWorkspace: (name, patch) => client.updateWorkspace(daemonId, name, patch)
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function createDaemonClient(target, deps = {}) {
|
|
844
|
+
const dataClient = (() => {
|
|
845
|
+
switch (target.kind) {
|
|
846
|
+
case "local": {
|
|
847
|
+
if (!deps.udsFetch) {
|
|
848
|
+
throw new Error("A local DaemonClient requires deps.udsFetch.");
|
|
849
|
+
}
|
|
850
|
+
return createHttpDataClient({
|
|
851
|
+
baseUrl: "http://daemon",
|
|
852
|
+
getToken: async () => target.token,
|
|
853
|
+
fetchImpl: deps.udsFetch(target.socketPath)
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
case "direct":
|
|
857
|
+
return createHttpDataClient({
|
|
858
|
+
baseUrl: httpBaseFromWsUrl(target.wsUrl),
|
|
859
|
+
getToken: async () => target.token,
|
|
860
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
861
|
+
});
|
|
862
|
+
case "relay":
|
|
863
|
+
return createHttpDataClient({
|
|
864
|
+
baseUrl: target.apiBaseUrl,
|
|
865
|
+
getToken: target.getAccessToken,
|
|
866
|
+
refreshAccessToken: target.refreshAccessToken,
|
|
867
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
})();
|
|
871
|
+
const connections = /* @__PURE__ */ new Set();
|
|
872
|
+
const directorySockets = /* @__PURE__ */ new Set();
|
|
873
|
+
let closed = false;
|
|
874
|
+
const socketFactory = target.kind === "local" ? deps.socketFactory ?? udsSocketFactory(target.socketPath) : deps.socketFactory ?? defaultSocketFactory;
|
|
875
|
+
const requireOpen = () => {
|
|
876
|
+
if (closed) throw new Error(CLOSED_ERROR);
|
|
877
|
+
};
|
|
878
|
+
const directorySocketFactory = (url) => {
|
|
879
|
+
requireOpen();
|
|
880
|
+
const socket = socketFactory(url);
|
|
881
|
+
if (closed) {
|
|
882
|
+
socket.close();
|
|
883
|
+
throw new Error(CLOSED_ERROR);
|
|
884
|
+
}
|
|
885
|
+
let closeRequested = false;
|
|
886
|
+
let closeDelivered = false;
|
|
887
|
+
const closeListeners = /* @__PURE__ */ new Set();
|
|
888
|
+
let owned;
|
|
889
|
+
const deliverClose = () => {
|
|
890
|
+
if (closeDelivered) return;
|
|
891
|
+
closeDelivered = true;
|
|
892
|
+
closeRequested = true;
|
|
893
|
+
directorySockets.delete(owned);
|
|
894
|
+
const listeners = [...closeListeners];
|
|
895
|
+
closeListeners.clear();
|
|
896
|
+
const errors = [];
|
|
897
|
+
for (const listener of listeners) {
|
|
898
|
+
try {
|
|
899
|
+
listener();
|
|
900
|
+
} catch (error) {
|
|
901
|
+
errors.push(error);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
throwCollectedErrors(errors, "Failed to deliver directory socket close listeners.");
|
|
905
|
+
};
|
|
906
|
+
owned = {
|
|
907
|
+
on(event, listener) {
|
|
908
|
+
if (event === "close") {
|
|
909
|
+
closeListeners.add(listener);
|
|
910
|
+
return owned;
|
|
911
|
+
}
|
|
912
|
+
return socket.on(event, listener);
|
|
913
|
+
},
|
|
914
|
+
send: (data) => socket.send(data),
|
|
915
|
+
close() {
|
|
916
|
+
if (closeRequested) return;
|
|
917
|
+
closeRequested = true;
|
|
918
|
+
directorySockets.delete(owned);
|
|
919
|
+
const errors = [];
|
|
920
|
+
try {
|
|
921
|
+
socket.close();
|
|
922
|
+
} catch (error) {
|
|
923
|
+
errors.push(error);
|
|
924
|
+
} finally {
|
|
925
|
+
try {
|
|
926
|
+
deliverClose();
|
|
927
|
+
} catch (error) {
|
|
928
|
+
errors.push(error);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
throwCollectedErrors(errors, "Failed to close directory socket.");
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
directorySockets.add(owned);
|
|
935
|
+
socket.on("close", deliverClose);
|
|
936
|
+
return owned;
|
|
937
|
+
};
|
|
938
|
+
const acquireEndpoint = async () => {
|
|
939
|
+
requireOpen();
|
|
940
|
+
const endpoint = await dataClient.acquireEndpoint(target.id);
|
|
941
|
+
requireOpen();
|
|
942
|
+
return endpoint;
|
|
943
|
+
};
|
|
944
|
+
const endpointConfig = (selectedSocketFactory) => {
|
|
945
|
+
switch (target.kind) {
|
|
946
|
+
case "local":
|
|
947
|
+
return {
|
|
948
|
+
url: udsUrl(target.socketPath),
|
|
949
|
+
token: target.token,
|
|
950
|
+
socketFactory: selectedSocketFactory
|
|
951
|
+
};
|
|
952
|
+
case "direct":
|
|
953
|
+
return {
|
|
954
|
+
url: target.wsUrl,
|
|
955
|
+
token: target.token,
|
|
956
|
+
socketFactory: selectedSocketFactory
|
|
957
|
+
};
|
|
958
|
+
case "relay":
|
|
959
|
+
return {
|
|
960
|
+
getEndpoint: acquireEndpoint,
|
|
961
|
+
socketFactory: selectedSocketFactory
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
return {
|
|
966
|
+
target,
|
|
967
|
+
data: bindDaemonDataClient(dataClient, target.id),
|
|
968
|
+
directoryEndpoint() {
|
|
969
|
+
requireOpen();
|
|
970
|
+
return { name: target.name, ...endpointConfig(directorySocketFactory) };
|
|
971
|
+
},
|
|
972
|
+
openSession(sessionId, options = {}) {
|
|
973
|
+
requireOpen();
|
|
974
|
+
const transport = createWebSocketTransport({
|
|
975
|
+
...endpointConfig(socketFactory),
|
|
976
|
+
sessionId,
|
|
977
|
+
...options
|
|
978
|
+
});
|
|
979
|
+
const client = createSessionClient(transport);
|
|
980
|
+
let connectionClosed = false;
|
|
981
|
+
const connection = {
|
|
982
|
+
client,
|
|
983
|
+
transport,
|
|
984
|
+
close() {
|
|
985
|
+
if (connectionClosed) return;
|
|
986
|
+
connectionClosed = true;
|
|
987
|
+
connections.delete(connection);
|
|
988
|
+
const errors = [];
|
|
989
|
+
try {
|
|
990
|
+
client.close();
|
|
991
|
+
} catch (error) {
|
|
992
|
+
errors.push(error);
|
|
993
|
+
}
|
|
994
|
+
try {
|
|
995
|
+
transport.close();
|
|
996
|
+
} catch (error) {
|
|
997
|
+
errors.push(error);
|
|
998
|
+
}
|
|
999
|
+
throwCollectedErrors(errors, "Failed to close session connection.");
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
connections.add(connection);
|
|
1003
|
+
return connection;
|
|
1004
|
+
},
|
|
1005
|
+
close() {
|
|
1006
|
+
if (closed) return;
|
|
1007
|
+
closed = true;
|
|
1008
|
+
const errors = [];
|
|
1009
|
+
for (const connection of [...connections]) {
|
|
1010
|
+
try {
|
|
1011
|
+
connection.close();
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
errors.push(error);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
for (const socket of [...directorySockets]) {
|
|
1017
|
+
directorySockets.delete(socket);
|
|
1018
|
+
try {
|
|
1019
|
+
socket.close();
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
errors.push(error);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
connections.clear();
|
|
1025
|
+
directorySockets.clear();
|
|
1026
|
+
throwCollectedErrors(errors, "Failed to close DaemonClient resources.");
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// src/client/directory.ts
|
|
1032
|
+
var toText2 = (data) => {
|
|
1033
|
+
if (typeof data === "string") return data;
|
|
1034
|
+
if (data instanceof Buffer) return data.toString("utf8");
|
|
1035
|
+
if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf8");
|
|
1036
|
+
if (Array.isArray(data) && data.every((chunk) => chunk instanceof Buffer)) {
|
|
1037
|
+
return Buffer.concat(data).toString("utf8");
|
|
1038
|
+
}
|
|
1039
|
+
return null;
|
|
1040
|
+
};
|
|
1041
|
+
var parseMessage2 = (data) => {
|
|
1042
|
+
const text = toText2(data);
|
|
1043
|
+
if (text === null) return null;
|
|
1044
|
+
try {
|
|
1045
|
+
const parsed = JSON.parse(text);
|
|
1046
|
+
return typeof parsed === "object" && parsed !== null && typeof parsed.type === "string" ? parsed : null;
|
|
1047
|
+
} catch {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
function createServerDirectory(options) {
|
|
1052
|
+
const socketFactory = options.socketFactory ?? defaultSocketFactory;
|
|
1053
|
+
const connectTimeoutMs = options.connectTimeoutMs ?? 5e3;
|
|
1054
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1055
|
+
const links = /* @__PURE__ */ new Map();
|
|
1056
|
+
let closed = false;
|
|
1057
|
+
let view = { servers: [] };
|
|
1058
|
+
const rebuildView = () => {
|
|
1059
|
+
view = {
|
|
1060
|
+
servers: Array.from(links.values(), (link) => ({
|
|
1061
|
+
name: link.config.name,
|
|
1062
|
+
url: link.resolvedUrl ?? link.config.url ?? "",
|
|
1063
|
+
status: link.status
|
|
1064
|
+
}))
|
|
1065
|
+
};
|
|
1066
|
+
for (const listener of listeners) {
|
|
1067
|
+
try {
|
|
1068
|
+
listener();
|
|
1069
|
+
} catch {
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
const clearTimer = (link) => {
|
|
1074
|
+
if (link.timer !== null) {
|
|
1075
|
+
clearTimeout(link.timer);
|
|
1076
|
+
link.timer = null;
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
const rejectPending = (link, message) => {
|
|
1080
|
+
const error = new Error(message);
|
|
1081
|
+
for (const entry of link.pending.values()) {
|
|
1082
|
+
if (entry.kind !== "list") entry.reject(error);
|
|
1083
|
+
}
|
|
1084
|
+
link.pending.clear();
|
|
1085
|
+
};
|
|
1086
|
+
const markOffline = (link, message) => {
|
|
1087
|
+
clearTimer(link);
|
|
1088
|
+
link.ready = false;
|
|
1089
|
+
const current = link.socket;
|
|
1090
|
+
link.socket = null;
|
|
1091
|
+
current?.close();
|
|
1092
|
+
rejectPending(link, message);
|
|
1093
|
+
link.status = { state: "offline", error: message };
|
|
1094
|
+
rebuildView();
|
|
1095
|
+
};
|
|
1096
|
+
const sendList = (link) => {
|
|
1097
|
+
if (!link.ready || link.socket === null) return;
|
|
1098
|
+
link.commandCounter += 1;
|
|
1099
|
+
const id = `dir-${link.commandCounter}`;
|
|
1100
|
+
link.pending.set(id, { kind: "list" });
|
|
1101
|
+
link.socket.send(JSON.stringify({ id, type: "list-sessions" }));
|
|
1102
|
+
};
|
|
1103
|
+
const handleMessage = (link, message) => {
|
|
1104
|
+
if (message.type === "rejected") {
|
|
1105
|
+
markOffline(
|
|
1106
|
+
link,
|
|
1107
|
+
message.reason === "auth" ? `Token rejected by "${link.config.name}" \u2014 check the configured token.` : `Protocol version mismatch with "${link.config.name}" \u2014 upgrade the daemon or this client.`
|
|
1108
|
+
);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (message.type === "welcome") {
|
|
1112
|
+
link.ready = true;
|
|
1113
|
+
clearTimer(link);
|
|
1114
|
+
rebuildView();
|
|
1115
|
+
sendList(link);
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (message.type === "session-list") {
|
|
1119
|
+
const entry = link.pending.get(message.id);
|
|
1120
|
+
if (entry?.kind !== "list") return;
|
|
1121
|
+
link.pending.delete(message.id);
|
|
1122
|
+
link.status = { state: "online", sessions: message.sessions };
|
|
1123
|
+
rebuildView();
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
if (message.type === "session-created") {
|
|
1127
|
+
const entry = link.pending.get(message.id);
|
|
1128
|
+
if (entry?.kind !== "create" && entry?.kind !== "resume") return;
|
|
1129
|
+
link.pending.delete(message.id);
|
|
1130
|
+
entry.resolve(message.session);
|
|
1131
|
+
if (link.status.state === "online") {
|
|
1132
|
+
const sessions = link.status.sessions.some((existing) => existing.id === message.session.id) ? link.status.sessions : [...link.status.sessions, message.session];
|
|
1133
|
+
link.status = { state: "online", sessions };
|
|
1134
|
+
rebuildView();
|
|
1135
|
+
}
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
if (message.type === "ack") {
|
|
1139
|
+
const entry = link.pending.get(message.id);
|
|
1140
|
+
if (!entry || entry.kind === "list") return;
|
|
1141
|
+
link.pending.delete(message.id);
|
|
1142
|
+
if (!message.ok) {
|
|
1143
|
+
entry.reject(new Error(message.error?.message ?? "Command failed."));
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (entry.kind === "kill") {
|
|
1147
|
+
entry.resolve();
|
|
1148
|
+
sendList(link);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (entry.kind === "complete") {
|
|
1152
|
+
entry.resolve(
|
|
1153
|
+
Array.isArray(message.result) ? message.result.filter((item) => typeof item === "string") : []
|
|
1154
|
+
);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
entry.reject(new Error("Unexpected ack for create-session / resume-session."));
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
const openLinkSocket = (link, endpoint) => {
|
|
1162
|
+
link.resolvedUrl = endpoint.url;
|
|
1163
|
+
const socket = (link.config.socketFactory ?? socketFactory)(endpoint.url);
|
|
1164
|
+
link.socket = socket;
|
|
1165
|
+
socket.on("open", () => {
|
|
1166
|
+
if (closed || link.socket !== socket) return;
|
|
1167
|
+
socket.send(
|
|
1168
|
+
JSON.stringify({
|
|
1169
|
+
type: "hello",
|
|
1170
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
1171
|
+
...endpoint.credential
|
|
1172
|
+
})
|
|
1173
|
+
);
|
|
1174
|
+
});
|
|
1175
|
+
socket.on("message", (data) => {
|
|
1176
|
+
if (closed || link.socket !== socket) return;
|
|
1177
|
+
const message = parseMessage2(data);
|
|
1178
|
+
if (message) handleMessage(link, message);
|
|
1179
|
+
});
|
|
1180
|
+
socket.on("error", () => {
|
|
1181
|
+
});
|
|
1182
|
+
socket.on("close", () => {
|
|
1183
|
+
if (link.socket !== socket) return;
|
|
1184
|
+
link.socket = null;
|
|
1185
|
+
if (closed) return;
|
|
1186
|
+
if (link.status.state !== "offline") {
|
|
1187
|
+
markOffline(link, `Connection to "${link.config.name}" closed.`);
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
};
|
|
1191
|
+
const connect = (link) => {
|
|
1192
|
+
if (closed) return;
|
|
1193
|
+
link.ready = false;
|
|
1194
|
+
link.status = { state: "connecting" };
|
|
1195
|
+
clearTimer(link);
|
|
1196
|
+
link.timer = setTimeout(() => {
|
|
1197
|
+
link.timer = null;
|
|
1198
|
+
if (!link.ready) {
|
|
1199
|
+
markOffline(link, `Timed out connecting to "${link.config.name}".`);
|
|
1200
|
+
}
|
|
1201
|
+
}, connectTimeoutMs);
|
|
1202
|
+
const getEndpoint = link.config.getEndpoint;
|
|
1203
|
+
if (!getEndpoint) {
|
|
1204
|
+
openLinkSocket(link, {
|
|
1205
|
+
url: link.config.url,
|
|
1206
|
+
credential: { token: link.config.token }
|
|
1207
|
+
});
|
|
1208
|
+
rebuildView();
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
void getEndpoint().then(
|
|
1212
|
+
(endpoint) => {
|
|
1213
|
+
if (closed || link.status.state !== "connecting" || link.socket !== null) return;
|
|
1214
|
+
openLinkSocket(link, endpoint);
|
|
1215
|
+
rebuildView();
|
|
1216
|
+
},
|
|
1217
|
+
(error) => {
|
|
1218
|
+
if (closed || link.status.state !== "connecting") return;
|
|
1219
|
+
markOffline(
|
|
1220
|
+
link,
|
|
1221
|
+
`Endpoint for "${link.config.name}" unavailable: ${error instanceof Error ? error.message : String(error)}`
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
);
|
|
1225
|
+
rebuildView();
|
|
1226
|
+
};
|
|
1227
|
+
const linkFor = (server) => {
|
|
1228
|
+
const link = links.get(server);
|
|
1229
|
+
if (!link) return new Error(`Unknown server "${server}".`);
|
|
1230
|
+
if (closed || !link.ready || link.socket === null) {
|
|
1231
|
+
return new Error(`Server "${server}" is not connected.`);
|
|
1232
|
+
}
|
|
1233
|
+
return link;
|
|
1234
|
+
};
|
|
1235
|
+
for (const config of options.servers) {
|
|
1236
|
+
if (!config.getEndpoint && (config.url === void 0 || config.token === void 0)) {
|
|
1237
|
+
throw new Error(`Server "${config.name}" needs either url+token or a getEndpoint provider.`);
|
|
1238
|
+
}
|
|
1239
|
+
links.set(config.name, {
|
|
1240
|
+
config,
|
|
1241
|
+
status: { state: "connecting" },
|
|
1242
|
+
socket: null,
|
|
1243
|
+
ready: false,
|
|
1244
|
+
pending: /* @__PURE__ */ new Map(),
|
|
1245
|
+
timer: null,
|
|
1246
|
+
commandCounter: 0
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
rebuildView();
|
|
1250
|
+
for (const link of links.values()) {
|
|
1251
|
+
connect(link);
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
getView() {
|
|
1255
|
+
return view;
|
|
1256
|
+
},
|
|
1257
|
+
subscribe(listener) {
|
|
1258
|
+
listeners.add(listener);
|
|
1259
|
+
return () => {
|
|
1260
|
+
listeners.delete(listener);
|
|
1261
|
+
};
|
|
1262
|
+
},
|
|
1263
|
+
refresh() {
|
|
1264
|
+
if (closed) return;
|
|
1265
|
+
for (const link of links.values()) {
|
|
1266
|
+
if (link.status.state === "offline") {
|
|
1267
|
+
connect(link);
|
|
1268
|
+
} else if (link.ready) {
|
|
1269
|
+
sendList(link);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
setServers(configs) {
|
|
1274
|
+
if (closed) return;
|
|
1275
|
+
for (const config of configs) {
|
|
1276
|
+
if (!links.has(config.name) && !config.getEndpoint) {
|
|
1277
|
+
if (config.url === void 0 || config.token === void 0) {
|
|
1278
|
+
throw new Error(
|
|
1279
|
+
`Server "${config.name}" needs either url+token or a getEndpoint provider.`
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
const nextNames = new Set(configs.map((config) => config.name));
|
|
1285
|
+
for (const [name, link] of links) {
|
|
1286
|
+
if (nextNames.has(name)) continue;
|
|
1287
|
+
clearTimer(link);
|
|
1288
|
+
rejectPending(link, `Server "${name}" was removed from the directory.`);
|
|
1289
|
+
const socket = link.socket;
|
|
1290
|
+
link.socket = null;
|
|
1291
|
+
socket?.close();
|
|
1292
|
+
links.delete(name);
|
|
1293
|
+
}
|
|
1294
|
+
const added = [];
|
|
1295
|
+
for (const config of configs) {
|
|
1296
|
+
const existing = links.get(config.name);
|
|
1297
|
+
if (existing) {
|
|
1298
|
+
existing.config = config;
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
const link = {
|
|
1302
|
+
config,
|
|
1303
|
+
status: { state: "connecting" },
|
|
1304
|
+
socket: null,
|
|
1305
|
+
ready: false,
|
|
1306
|
+
pending: /* @__PURE__ */ new Map(),
|
|
1307
|
+
timer: null,
|
|
1308
|
+
commandCounter: 0
|
|
1309
|
+
};
|
|
1310
|
+
links.set(config.name, link);
|
|
1311
|
+
added.push(link);
|
|
1312
|
+
}
|
|
1313
|
+
rebuildView();
|
|
1314
|
+
for (const link of added) {
|
|
1315
|
+
connect(link);
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
createSession(server, createOptions) {
|
|
1319
|
+
return new Promise((resolve, reject) => {
|
|
1320
|
+
const link = linkFor(server);
|
|
1321
|
+
if (link instanceof Error) {
|
|
1322
|
+
reject(link);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
link.commandCounter += 1;
|
|
1326
|
+
const id = `dir-${link.commandCounter}`;
|
|
1327
|
+
link.pending.set(id, { kind: "create", resolve, reject });
|
|
1328
|
+
link.socket?.send(
|
|
1329
|
+
JSON.stringify({
|
|
1330
|
+
id,
|
|
1331
|
+
type: "create-session",
|
|
1332
|
+
cwd: createOptions.cwd,
|
|
1333
|
+
...createOptions.name !== void 0 ? { name: createOptions.name } : {},
|
|
1334
|
+
...createOptions.workspace !== void 0 ? { workspace: createOptions.workspace } : {},
|
|
1335
|
+
...createOptions.profile !== void 0 ? { profile: createOptions.profile } : {}
|
|
1336
|
+
})
|
|
1337
|
+
);
|
|
1338
|
+
});
|
|
1339
|
+
},
|
|
1340
|
+
resumeSession(server, historyId, resumeOptions) {
|
|
1341
|
+
return new Promise((resolve, reject) => {
|
|
1342
|
+
const link = linkFor(server);
|
|
1343
|
+
if (link instanceof Error) {
|
|
1344
|
+
reject(link);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
link.commandCounter += 1;
|
|
1348
|
+
const id = `dir-${link.commandCounter}`;
|
|
1349
|
+
link.pending.set(id, { kind: "resume", resolve, reject });
|
|
1350
|
+
link.socket?.send(
|
|
1351
|
+
JSON.stringify({
|
|
1352
|
+
id,
|
|
1353
|
+
type: "resume-session",
|
|
1354
|
+
historyId,
|
|
1355
|
+
cwd: resumeOptions.cwd,
|
|
1356
|
+
...resumeOptions.name !== void 0 ? { name: resumeOptions.name } : {},
|
|
1357
|
+
...resumeOptions.workspace !== void 0 ? { workspace: resumeOptions.workspace } : {},
|
|
1358
|
+
...resumeOptions.profile !== void 0 ? { profile: resumeOptions.profile } : {}
|
|
1359
|
+
})
|
|
1360
|
+
);
|
|
1361
|
+
});
|
|
1362
|
+
},
|
|
1363
|
+
killSession(server, sessionId, options2) {
|
|
1364
|
+
return new Promise((resolve, reject) => {
|
|
1365
|
+
const link = linkFor(server);
|
|
1366
|
+
if (link instanceof Error) {
|
|
1367
|
+
reject(link);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
link.commandCounter += 1;
|
|
1371
|
+
const id = `dir-${link.commandCounter}`;
|
|
1372
|
+
link.pending.set(id, { kind: "kill", resolve, reject });
|
|
1373
|
+
link.socket?.send(
|
|
1374
|
+
JSON.stringify({ id, type: "kill-session", sessionId, delete: options2?.delete })
|
|
1375
|
+
);
|
|
1376
|
+
});
|
|
1377
|
+
},
|
|
1378
|
+
completePath(server, prefix) {
|
|
1379
|
+
return new Promise((resolve, reject) => {
|
|
1380
|
+
const link = linkFor(server);
|
|
1381
|
+
if (link instanceof Error) {
|
|
1382
|
+
reject(link);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
link.commandCounter += 1;
|
|
1386
|
+
const id = `dir-${link.commandCounter}`;
|
|
1387
|
+
link.pending.set(id, { kind: "complete", resolve, reject });
|
|
1388
|
+
link.socket?.send(JSON.stringify({ id, type: "complete-path", prefix }));
|
|
1389
|
+
});
|
|
1390
|
+
},
|
|
1391
|
+
close() {
|
|
1392
|
+
if (closed) return;
|
|
1393
|
+
closed = true;
|
|
1394
|
+
for (const link of links.values()) {
|
|
1395
|
+
clearTimer(link);
|
|
1396
|
+
rejectPending(link, "ServerDirectory closed.");
|
|
1397
|
+
const socket = link.socket;
|
|
1398
|
+
link.socket = null;
|
|
1399
|
+
socket?.close();
|
|
1400
|
+
}
|
|
1401
|
+
listeners.clear();
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
export {
|
|
1406
|
+
TerminalEndpointError,
|
|
1407
|
+
adaptWhatwgSocket,
|
|
1408
|
+
bindDaemonDataClient,
|
|
1409
|
+
createDaemonClient,
|
|
1410
|
+
createHttpDataClient,
|
|
1411
|
+
createServerDirectory,
|
|
1412
|
+
createSessionClient,
|
|
1413
|
+
createWebSocketTransport,
|
|
1414
|
+
defaultSocketFactory,
|
|
1415
|
+
httpBaseFromWsUrl,
|
|
1416
|
+
udsSocketFactory,
|
|
1417
|
+
udsUrl
|
|
1418
|
+
};
|