@nuvin/session 0.1.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/chunk-3IPZO7LP.js +66 -0
  2. package/dist/chunk-7G3R25NS.js +6 -0
  3. package/dist/chunk-DGC7JSCG.js +0 -0
  4. package/dist/chunk-FR3R66RD.js +678 -0
  5. package/dist/chunk-UVJIG4DT.js +17 -0
  6. package/dist/client/directory.d.ts +141 -0
  7. package/dist/client/directory.d.ts.map +1 -0
  8. package/dist/client/endpoint.d.ts +33 -0
  9. package/dist/client/endpoint.d.ts.map +1 -0
  10. package/dist/client/index.d.ts +8 -0
  11. package/dist/client/index.d.ts.map +1 -0
  12. package/dist/client/index.js +1060 -0
  13. package/dist/client/session-client.d.ts +60 -0
  14. package/dist/client/session-client.d.ts.map +1 -0
  15. package/dist/client/socket.d.ts +38 -0
  16. package/dist/client/socket.d.ts.map +1 -0
  17. package/dist/client/transport.d.ts +9 -0
  18. package/dist/client/transport.d.ts.map +1 -0
  19. package/dist/client/uds-socket.d.ts +23 -0
  20. package/dist/client/uds-socket.d.ts.map +1 -0
  21. package/dist/client/websocket.d.ts +54 -0
  22. package/dist/client/websocket.d.ts.map +1 -0
  23. package/dist/controller/agent-channel.d.ts +58 -0
  24. package/dist/controller/agent-channel.d.ts.map +1 -0
  25. package/dist/controller/index.d.ts +3 -0
  26. package/dist/controller/index.d.ts.map +1 -0
  27. package/dist/controller/index.js +441 -0
  28. package/dist/controller/session-controller.d.ts +147 -0
  29. package/dist/controller/session-controller.d.ts.map +1 -0
  30. package/dist/controller/test-utils.d.ts +15 -0
  31. package/dist/controller/test-utils.d.ts.map +1 -0
  32. package/dist/grant/index.d.ts +33 -0
  33. package/dist/grant/index.d.ts.map +1 -0
  34. package/dist/grant/index.js +12 -0
  35. package/dist/protocol/index.d.ts +3 -0
  36. package/dist/protocol/index.d.ts.map +1 -0
  37. package/dist/protocol/index.js +7 -0
  38. package/dist/protocol/types.d.ts +855 -0
  39. package/dist/protocol/types.d.ts.map +1 -0
  40. package/dist/protocol/version.d.ts +8 -0
  41. package/dist/protocol/version.d.ts.map +1 -0
  42. package/dist/state/approvals.d.ts +34 -0
  43. package/dist/state/approvals.d.ts.map +1 -0
  44. package/dist/state/dir-access.d.ts +9 -0
  45. package/dist/state/dir-access.d.ts.map +1 -0
  46. package/dist/state/index.d.ts +6 -0
  47. package/dist/state/index.d.ts.map +1 -0
  48. package/dist/state/index.js +48 -0
  49. package/dist/state/json.d.ts +7 -0
  50. package/dist/state/json.d.ts.map +1 -0
  51. package/dist/state/messages.d.ts +105 -0
  52. package/dist/state/messages.d.ts.map +1 -0
  53. package/dist/state/session.d.ts +9 -0
  54. package/dist/state/session.d.ts.map +1 -0
  55. package/dist/state/tool-preview.d.ts +31 -0
  56. package/dist/state/tool-preview.d.ts.map +1 -0
  57. package/dist/state/tool-preview.js +300 -0
  58. package/dist/state/workflow-view.d.ts +10 -0
  59. package/dist/state/workflow-view.d.ts.map +1 -0
  60. package/dist/test-utils/fake-relay.d.ts +24 -0
  61. package/dist/test-utils/fake-relay.d.ts.map +1 -0
  62. package/dist/test-utils/index.d.ts +2 -0
  63. package/dist/test-utils/index.d.ts.map +1 -0
  64. package/dist/test-utils/index.js +250 -0
  65. package/dist/ui/history-grouping.d.ts +36 -0
  66. package/dist/ui/history-grouping.d.ts.map +1 -0
  67. package/dist/ui/index.d.ts +3 -0
  68. package/dist/ui/index.d.ts.map +1 -0
  69. package/dist/ui/index.js +135 -0
  70. package/dist/ui/picker-rows.d.ts +62 -0
  71. package/dist/ui/picker-rows.d.ts.map +1 -0
  72. package/package.json +70 -0
@@ -0,0 +1,1060 @@
1
+ import {
2
+ createSessionViewState,
3
+ reduceSessionEvent
4
+ } from "../chunk-FR3R66RD.js";
5
+ import {
6
+ PROTOCOL_VERSION
7
+ } from "../chunk-7G3R25NS.js";
8
+
9
+ // src/client/socket.ts
10
+ function adaptWhatwgSocket(socket) {
11
+ const on = (event, listener) => {
12
+ switch (event) {
13
+ case "open":
14
+ socket.addEventListener("open", () => listener(void 0));
15
+ return;
16
+ case "message":
17
+ socket.addEventListener("message", (messageEvent) => listener(messageEvent.data));
18
+ return;
19
+ case "close":
20
+ socket.addEventListener("close", () => listener(void 0));
21
+ return;
22
+ case "error":
23
+ socket.addEventListener("error", (errorEvent) => listener(errorEvent));
24
+ return;
25
+ }
26
+ };
27
+ return {
28
+ on,
29
+ send: (data) => socket.send(data),
30
+ close: () => socket.close()
31
+ };
32
+ }
33
+ function globalWebSocketConstructor() {
34
+ const candidate = globalThis.WebSocket;
35
+ if (typeof candidate !== "function") {
36
+ throw new Error(
37
+ "No global WebSocket constructor is available. Pass `socketFactory` (node < 22 callers can wrap the 'ws' package)."
38
+ );
39
+ }
40
+ return candidate;
41
+ }
42
+ function defaultSocketFactory(url) {
43
+ const WebSocketCtor = globalWebSocketConstructor();
44
+ return adaptWhatwgSocket(new WebSocketCtor(url));
45
+ }
46
+
47
+ // src/client/directory.ts
48
+ var toText = (data) => {
49
+ if (typeof data === "string") return data;
50
+ if (data instanceof Buffer) return data.toString("utf8");
51
+ if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf8");
52
+ if (Array.isArray(data) && data.every((chunk) => chunk instanceof Buffer)) {
53
+ return Buffer.concat(data).toString("utf8");
54
+ }
55
+ return null;
56
+ };
57
+ var parseMessage = (data) => {
58
+ const text = toText(data);
59
+ if (text === null) return null;
60
+ try {
61
+ const parsed = JSON.parse(text);
62
+ return typeof parsed === "object" && parsed !== null && typeof parsed.type === "string" ? parsed : null;
63
+ } catch {
64
+ return null;
65
+ }
66
+ };
67
+ function createServerDirectory(options) {
68
+ const socketFactory = options.socketFactory ?? defaultSocketFactory;
69
+ const connectTimeoutMs = options.connectTimeoutMs ?? 5e3;
70
+ const listeners = /* @__PURE__ */ new Set();
71
+ const links = /* @__PURE__ */ new Map();
72
+ let closed = false;
73
+ let view = { servers: [] };
74
+ const rebuildView = () => {
75
+ view = {
76
+ servers: Array.from(links.values(), (link) => ({
77
+ name: link.config.name,
78
+ url: link.resolvedUrl ?? link.config.url ?? "",
79
+ status: link.status
80
+ }))
81
+ };
82
+ for (const listener of listeners) {
83
+ try {
84
+ listener();
85
+ } catch {
86
+ }
87
+ }
88
+ };
89
+ const clearTimer = (link) => {
90
+ if (link.timer !== null) {
91
+ clearTimeout(link.timer);
92
+ link.timer = null;
93
+ }
94
+ };
95
+ const rejectPending = (link, message) => {
96
+ const error = new Error(message);
97
+ for (const entry of link.pending.values()) {
98
+ if (entry.kind !== "list") entry.reject(error);
99
+ }
100
+ link.pending.clear();
101
+ };
102
+ const markOffline = (link, message) => {
103
+ clearTimer(link);
104
+ link.ready = false;
105
+ const current = link.socket;
106
+ link.socket = null;
107
+ current?.close();
108
+ rejectPending(link, message);
109
+ link.status = { state: "offline", error: message };
110
+ rebuildView();
111
+ };
112
+ const sendList = (link) => {
113
+ if (!link.ready || link.socket === null) return;
114
+ link.commandCounter += 1;
115
+ const id = `dir-${link.commandCounter}`;
116
+ link.pending.set(id, { kind: "list" });
117
+ link.socket.send(JSON.stringify({ id, type: "list-sessions" }));
118
+ };
119
+ const handleMessage = (link, message) => {
120
+ if (message.type === "rejected") {
121
+ markOffline(
122
+ link,
123
+ 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.`
124
+ );
125
+ return;
126
+ }
127
+ if (message.type === "welcome") {
128
+ link.ready = true;
129
+ clearTimer(link);
130
+ rebuildView();
131
+ sendList(link);
132
+ return;
133
+ }
134
+ if (message.type === "session-list") {
135
+ const entry = link.pending.get(message.id);
136
+ if (entry?.kind !== "list") return;
137
+ link.pending.delete(message.id);
138
+ link.status = { state: "online", sessions: message.sessions };
139
+ rebuildView();
140
+ return;
141
+ }
142
+ if (message.type === "history-list") {
143
+ const entry = link.pending.get(message.id);
144
+ if (entry?.kind !== "history") return;
145
+ link.pending.delete(message.id);
146
+ entry.resolve({ sessions: message.sessions, currentWorkspace: message.currentWorkspace });
147
+ return;
148
+ }
149
+ if (message.type === "session-created") {
150
+ const entry = link.pending.get(message.id);
151
+ if (entry?.kind !== "create" && entry?.kind !== "resume") return;
152
+ link.pending.delete(message.id);
153
+ entry.resolve(message.session);
154
+ if (link.status.state === "online") {
155
+ const sessions = link.status.sessions.some((existing) => existing.id === message.session.id) ? link.status.sessions : [...link.status.sessions, message.session];
156
+ link.status = { state: "online", sessions };
157
+ rebuildView();
158
+ }
159
+ return;
160
+ }
161
+ if (message.type === "ack") {
162
+ const entry = link.pending.get(message.id);
163
+ if (!entry || entry.kind === "list") return;
164
+ link.pending.delete(message.id);
165
+ if (!message.ok) {
166
+ entry.reject(new Error(message.error?.message ?? "Command failed."));
167
+ return;
168
+ }
169
+ if (entry.kind === "kill") {
170
+ entry.resolve();
171
+ sendList(link);
172
+ return;
173
+ }
174
+ if (entry.kind === "delete-history") {
175
+ entry.resolve();
176
+ sendList(link);
177
+ return;
178
+ }
179
+ if (entry.kind === "complete") {
180
+ entry.resolve(
181
+ Array.isArray(message.result) ? message.result.filter((item) => typeof item === "string") : []
182
+ );
183
+ return;
184
+ }
185
+ if (entry.kind === "list-workspaces") {
186
+ entry.resolve(Array.isArray(message.result) ? message.result : []);
187
+ return;
188
+ }
189
+ if (entry.kind === "create-workspace") {
190
+ entry.resolve(message.result);
191
+ return;
192
+ }
193
+ if (entry.kind === "list-profiles") {
194
+ entry.resolve(Array.isArray(message.result) ? message.result : []);
195
+ return;
196
+ }
197
+ if (entry.kind === "list-providers") {
198
+ entry.resolve(Array.isArray(message.result) ? message.result : []);
199
+ return;
200
+ }
201
+ if (entry.kind === "provider-mutation") {
202
+ entry.resolve();
203
+ return;
204
+ }
205
+ entry.reject(new Error("Unexpected ack for create-session / resume-session."));
206
+ return;
207
+ }
208
+ };
209
+ const openLinkSocket = (link, endpoint) => {
210
+ link.resolvedUrl = endpoint.url;
211
+ const socket = (link.config.socketFactory ?? socketFactory)(endpoint.url);
212
+ link.socket = socket;
213
+ socket.on("open", () => {
214
+ if (closed || link.socket !== socket) return;
215
+ socket.send(
216
+ JSON.stringify({
217
+ type: "hello",
218
+ protocolVersion: PROTOCOL_VERSION,
219
+ ...endpoint.credential
220
+ })
221
+ );
222
+ });
223
+ socket.on("message", (data) => {
224
+ if (closed || link.socket !== socket) return;
225
+ const message = parseMessage(data);
226
+ if (message) handleMessage(link, message);
227
+ });
228
+ socket.on("error", () => {
229
+ });
230
+ socket.on("close", () => {
231
+ if (link.socket !== socket) return;
232
+ link.socket = null;
233
+ if (closed) return;
234
+ if (link.status.state !== "offline") {
235
+ markOffline(link, `Connection to "${link.config.name}" closed.`);
236
+ }
237
+ });
238
+ };
239
+ const connect = (link) => {
240
+ if (closed) return;
241
+ link.ready = false;
242
+ link.status = { state: "connecting" };
243
+ clearTimer(link);
244
+ link.timer = setTimeout(() => {
245
+ link.timer = null;
246
+ if (!link.ready) {
247
+ markOffline(link, `Timed out connecting to "${link.config.name}".`);
248
+ }
249
+ }, connectTimeoutMs);
250
+ const getEndpoint = link.config.getEndpoint;
251
+ if (!getEndpoint) {
252
+ openLinkSocket(link, {
253
+ url: link.config.url,
254
+ credential: { token: link.config.token }
255
+ });
256
+ rebuildView();
257
+ return;
258
+ }
259
+ void getEndpoint().then(
260
+ (endpoint) => {
261
+ if (closed || link.status.state !== "connecting" || link.socket !== null) return;
262
+ openLinkSocket(link, endpoint);
263
+ rebuildView();
264
+ },
265
+ (error) => {
266
+ if (closed || link.status.state !== "connecting") return;
267
+ markOffline(
268
+ link,
269
+ `Endpoint for "${link.config.name}" unavailable: ${error instanceof Error ? error.message : String(error)}`
270
+ );
271
+ }
272
+ );
273
+ rebuildView();
274
+ };
275
+ const linkFor = (server) => {
276
+ const link = links.get(server);
277
+ if (!link) return new Error(`Unknown server "${server}".`);
278
+ if (closed || !link.ready || link.socket === null) {
279
+ return new Error(`Server "${server}" is not connected.`);
280
+ }
281
+ return link;
282
+ };
283
+ for (const config of options.servers) {
284
+ if (!config.getEndpoint && (config.url === void 0 || config.token === void 0)) {
285
+ throw new Error(`Server "${config.name}" needs either url+token or a getEndpoint provider.`);
286
+ }
287
+ links.set(config.name, {
288
+ config,
289
+ status: { state: "connecting" },
290
+ socket: null,
291
+ ready: false,
292
+ pending: /* @__PURE__ */ new Map(),
293
+ timer: null,
294
+ commandCounter: 0
295
+ });
296
+ }
297
+ rebuildView();
298
+ for (const link of links.values()) {
299
+ connect(link);
300
+ }
301
+ return {
302
+ getView() {
303
+ return view;
304
+ },
305
+ subscribe(listener) {
306
+ listeners.add(listener);
307
+ return () => {
308
+ listeners.delete(listener);
309
+ };
310
+ },
311
+ refresh() {
312
+ if (closed) return;
313
+ for (const link of links.values()) {
314
+ if (link.status.state === "offline") {
315
+ connect(link);
316
+ } else if (link.ready) {
317
+ sendList(link);
318
+ }
319
+ }
320
+ },
321
+ setServers(configs) {
322
+ if (closed) return;
323
+ for (const config of configs) {
324
+ if (!links.has(config.name) && !config.getEndpoint) {
325
+ if (config.url === void 0 || config.token === void 0) {
326
+ throw new Error(
327
+ `Server "${config.name}" needs either url+token or a getEndpoint provider.`
328
+ );
329
+ }
330
+ }
331
+ }
332
+ const nextNames = new Set(configs.map((config) => config.name));
333
+ for (const [name, link] of links) {
334
+ if (nextNames.has(name)) continue;
335
+ clearTimer(link);
336
+ rejectPending(link, `Server "${name}" was removed from the directory.`);
337
+ const socket = link.socket;
338
+ link.socket = null;
339
+ socket?.close();
340
+ links.delete(name);
341
+ }
342
+ const added = [];
343
+ for (const config of configs) {
344
+ const existing = links.get(config.name);
345
+ if (existing) {
346
+ existing.config = config;
347
+ continue;
348
+ }
349
+ const link = {
350
+ config,
351
+ status: { state: "connecting" },
352
+ socket: null,
353
+ ready: false,
354
+ pending: /* @__PURE__ */ new Map(),
355
+ timer: null,
356
+ commandCounter: 0
357
+ };
358
+ links.set(config.name, link);
359
+ added.push(link);
360
+ }
361
+ rebuildView();
362
+ for (const link of added) {
363
+ connect(link);
364
+ }
365
+ },
366
+ createSession(server, createOptions) {
367
+ return new Promise((resolve, reject) => {
368
+ const link = linkFor(server);
369
+ if (link instanceof Error) {
370
+ reject(link);
371
+ return;
372
+ }
373
+ link.commandCounter += 1;
374
+ const id = `dir-${link.commandCounter}`;
375
+ link.pending.set(id, { kind: "create", resolve, reject });
376
+ link.socket?.send(
377
+ JSON.stringify({
378
+ id,
379
+ type: "create-session",
380
+ cwd: createOptions.cwd,
381
+ ...createOptions.name !== void 0 ? { name: createOptions.name } : {},
382
+ ...createOptions.workspace !== void 0 ? { workspace: createOptions.workspace } : {},
383
+ ...createOptions.profile !== void 0 ? { profile: createOptions.profile } : {}
384
+ })
385
+ );
386
+ });
387
+ },
388
+ resumeSession(server, historyId, resumeOptions) {
389
+ return new Promise((resolve, reject) => {
390
+ const link = linkFor(server);
391
+ if (link instanceof Error) {
392
+ reject(link);
393
+ return;
394
+ }
395
+ link.commandCounter += 1;
396
+ const id = `dir-${link.commandCounter}`;
397
+ link.pending.set(id, { kind: "resume", resolve, reject });
398
+ link.socket?.send(
399
+ JSON.stringify({
400
+ id,
401
+ type: "resume-session",
402
+ historyId,
403
+ cwd: resumeOptions.cwd,
404
+ ...resumeOptions.name !== void 0 ? { name: resumeOptions.name } : {},
405
+ ...resumeOptions.workspace !== void 0 ? { workspace: resumeOptions.workspace } : {},
406
+ ...resumeOptions.profile !== void 0 ? { profile: resumeOptions.profile } : {}
407
+ })
408
+ );
409
+ });
410
+ },
411
+ killSession(server, sessionId, options2) {
412
+ return new Promise((resolve, reject) => {
413
+ const link = linkFor(server);
414
+ if (link instanceof Error) {
415
+ reject(link);
416
+ return;
417
+ }
418
+ link.commandCounter += 1;
419
+ const id = `dir-${link.commandCounter}`;
420
+ link.pending.set(id, { kind: "kill", resolve, reject });
421
+ link.socket?.send(
422
+ JSON.stringify({ id, type: "kill-session", sessionId, delete: options2?.delete })
423
+ );
424
+ });
425
+ },
426
+ completePath(server, prefix) {
427
+ return new Promise((resolve, reject) => {
428
+ const link = linkFor(server);
429
+ if (link instanceof Error) {
430
+ reject(link);
431
+ return;
432
+ }
433
+ link.commandCounter += 1;
434
+ const id = `dir-${link.commandCounter}`;
435
+ link.pending.set(id, { kind: "complete", resolve, reject });
436
+ link.socket?.send(JSON.stringify({ id, type: "complete-path", prefix }));
437
+ });
438
+ },
439
+ listWorkspaces(server, opts) {
440
+ return new Promise((resolve, reject) => {
441
+ const link = linkFor(server);
442
+ if (link instanceof Error) {
443
+ reject(link);
444
+ return;
445
+ }
446
+ link.commandCounter += 1;
447
+ const id = `dir-${link.commandCounter}`;
448
+ link.pending.set(id, { kind: "list-workspaces", resolve, reject });
449
+ link.socket?.send(
450
+ JSON.stringify({
451
+ id,
452
+ type: "list-workspaces",
453
+ ...opts?.profile !== void 0 ? { profile: opts.profile } : {}
454
+ })
455
+ );
456
+ });
457
+ },
458
+ createWorkspace(server, opts) {
459
+ return new Promise((resolve, reject) => {
460
+ const link = linkFor(server);
461
+ if (link instanceof Error) {
462
+ reject(link);
463
+ return;
464
+ }
465
+ link.commandCounter += 1;
466
+ const id = `dir-${link.commandCounter}`;
467
+ link.pending.set(id, { kind: "create-workspace", resolve, reject });
468
+ link.socket?.send(
469
+ JSON.stringify({
470
+ id,
471
+ type: "create-workspace",
472
+ name: opts.name,
473
+ ...opts.root !== void 0 ? { root: opts.root } : {},
474
+ ...opts.profile !== void 0 ? { profile: opts.profile } : {}
475
+ })
476
+ );
477
+ });
478
+ },
479
+ listProfiles(server) {
480
+ return new Promise((resolve, reject) => {
481
+ const link = linkFor(server);
482
+ if (link instanceof Error) {
483
+ reject(link);
484
+ return;
485
+ }
486
+ link.commandCounter += 1;
487
+ const id = `dir-${link.commandCounter}`;
488
+ link.pending.set(id, { kind: "list-profiles", resolve, reject });
489
+ link.socket?.send(JSON.stringify({ id, type: "list-profiles" }));
490
+ });
491
+ },
492
+ listProviders(server) {
493
+ return new Promise((resolve, reject) => {
494
+ const link = linkFor(server);
495
+ if (link instanceof Error) {
496
+ reject(link);
497
+ return;
498
+ }
499
+ link.commandCounter += 1;
500
+ const id = `dir-${link.commandCounter}`;
501
+ link.pending.set(id, { kind: "list-providers", resolve, reject });
502
+ link.socket?.send(JSON.stringify({ id, type: "list-providers" }));
503
+ });
504
+ },
505
+ setDefaultProvider(server, providerId) {
506
+ return new Promise((resolve, reject) => {
507
+ const link = linkFor(server);
508
+ if (link instanceof Error) {
509
+ reject(link);
510
+ return;
511
+ }
512
+ link.commandCounter += 1;
513
+ const id = `dir-${link.commandCounter}`;
514
+ link.pending.set(id, { kind: "provider-mutation", resolve, reject });
515
+ link.socket?.send(JSON.stringify({ id, type: "set-default-provider", providerId }));
516
+ });
517
+ },
518
+ setProviderAuth(server, providerId, credential) {
519
+ return new Promise((resolve, reject) => {
520
+ const link = linkFor(server);
521
+ if (link instanceof Error) {
522
+ reject(link);
523
+ return;
524
+ }
525
+ link.commandCounter += 1;
526
+ const id = `dir-${link.commandCounter}`;
527
+ link.pending.set(id, { kind: "provider-mutation", resolve, reject });
528
+ link.socket?.send(
529
+ JSON.stringify({ id, type: "set-provider-auth", providerId, credential })
530
+ );
531
+ });
532
+ },
533
+ clearProviderAuth(server, providerId) {
534
+ return new Promise((resolve, reject) => {
535
+ const link = linkFor(server);
536
+ if (link instanceof Error) {
537
+ reject(link);
538
+ return;
539
+ }
540
+ link.commandCounter += 1;
541
+ const id = `dir-${link.commandCounter}`;
542
+ link.pending.set(id, { kind: "provider-mutation", resolve, reject });
543
+ link.socket?.send(JSON.stringify({ id, type: "clear-provider-auth", providerId }));
544
+ });
545
+ },
546
+ listHistory(server, opts) {
547
+ return new Promise(
548
+ (resolve, reject) => {
549
+ const link = linkFor(server);
550
+ if (link instanceof Error) {
551
+ reject(link);
552
+ return;
553
+ }
554
+ link.commandCounter += 1;
555
+ const id = `dir-${link.commandCounter}`;
556
+ link.pending.set(id, { kind: "history", resolve, reject });
557
+ link.socket?.send(
558
+ JSON.stringify({
559
+ id,
560
+ type: "list-history",
561
+ ...opts?.profile !== void 0 ? { profile: opts.profile } : {},
562
+ ...opts?.cwd !== void 0 ? { cwd: opts.cwd } : {}
563
+ })
564
+ );
565
+ }
566
+ );
567
+ },
568
+ deleteHistory(server, historyId, opts) {
569
+ return new Promise((resolve, reject) => {
570
+ const link = linkFor(server);
571
+ if (link instanceof Error) {
572
+ reject(link);
573
+ return;
574
+ }
575
+ link.commandCounter += 1;
576
+ const id = `dir-${link.commandCounter}`;
577
+ link.pending.set(id, { kind: "delete-history", resolve, reject });
578
+ link.socket?.send(
579
+ JSON.stringify({
580
+ id,
581
+ type: "delete-history",
582
+ historyId,
583
+ workspace: opts.workspace,
584
+ ...opts.profile !== void 0 ? { profile: opts.profile } : {}
585
+ })
586
+ );
587
+ });
588
+ },
589
+ close() {
590
+ if (closed) return;
591
+ closed = true;
592
+ for (const link of links.values()) {
593
+ clearTimer(link);
594
+ rejectPending(link, "ServerDirectory closed.");
595
+ const socket = link.socket;
596
+ link.socket = null;
597
+ socket?.close();
598
+ }
599
+ listeners.clear();
600
+ }
601
+ };
602
+ }
603
+
604
+ // src/client/endpoint.ts
605
+ var TerminalEndpointError = class extends Error {
606
+ reason;
607
+ constructor(reason, message) {
608
+ super(message ?? reason);
609
+ this.name = "TerminalEndpointError";
610
+ this.reason = reason;
611
+ }
612
+ };
613
+
614
+ // src/client/session-client.ts
615
+ var EMPTY_MODEL_COMPLETION = {
616
+ providers: [],
617
+ perProvider: {},
618
+ recentModels: []
619
+ };
620
+ function toModelCompletion(result) {
621
+ if (typeof result !== "object" || result === null || Array.isArray(result)) {
622
+ return EMPTY_MODEL_COMPLETION;
623
+ }
624
+ const record = result;
625
+ const providers = Array.isArray(record.providers) ? record.providers.filter((p) => typeof p === "string") : [];
626
+ const perProvider = {};
627
+ if (typeof record.perProvider === "object" && record.perProvider !== null) {
628
+ for (const [provider, models] of Object.entries(
629
+ record.perProvider
630
+ )) {
631
+ if (!Array.isArray(models)) continue;
632
+ perProvider[provider] = models.flatMap((entry) => {
633
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
634
+ const id = entry.id;
635
+ if (typeof id !== "string") return [];
636
+ const name = entry.name;
637
+ return [typeof name === "string" ? { id, name } : { id }];
638
+ });
639
+ }
640
+ }
641
+ const recentModels = Array.isArray(record.recentModels) ? record.recentModels.flatMap((entry) => {
642
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
643
+ const provider = entry.provider;
644
+ const model = entry.model;
645
+ return typeof provider === "string" && typeof model === "string" ? [{ provider, model }] : [];
646
+ }) : [];
647
+ return { providers, perProvider, recentModels };
648
+ }
649
+ function createSessionClient(transport) {
650
+ let state = createSessionViewState();
651
+ let seq = 0;
652
+ let closed = false;
653
+ let notifyScheduled = false;
654
+ let commandCounter = 0;
655
+ let serverCommandList = [];
656
+ const stateListeners = /* @__PURE__ */ new Set();
657
+ const eventListeners = /* @__PURE__ */ new Set();
658
+ const pending = /* @__PURE__ */ new Map();
659
+ const scheduleNotify = () => {
660
+ if (notifyScheduled) return;
661
+ notifyScheduled = true;
662
+ queueMicrotask(() => {
663
+ notifyScheduled = false;
664
+ for (const listener of stateListeners) {
665
+ try {
666
+ listener();
667
+ } catch {
668
+ }
669
+ }
670
+ });
671
+ };
672
+ const unsubscribe = transport.onFrame((frame) => {
673
+ if (frame.type === "snapshot") {
674
+ state = frame.snapshot.state;
675
+ seq = frame.snapshot.seq;
676
+ serverCommandList = frame.snapshot.serverCommands;
677
+ scheduleNotify();
678
+ return;
679
+ }
680
+ if (frame.type === "ack") {
681
+ const entry = pending.get(frame.id);
682
+ if (!entry) return;
683
+ pending.delete(frame.id);
684
+ if (frame.ok) {
685
+ entry.resolve(frame.result);
686
+ } else {
687
+ entry.reject(new Error(frame.error?.message ?? "Command failed."));
688
+ }
689
+ return;
690
+ }
691
+ state = reduceSessionEvent(state, frame);
692
+ seq = frame.seq;
693
+ for (const listener of eventListeners) {
694
+ try {
695
+ listener(frame);
696
+ } catch {
697
+ }
698
+ }
699
+ scheduleNotify();
700
+ });
701
+ const sendCommand = (build) => {
702
+ if (closed) {
703
+ return Promise.reject(new Error("SessionClient is closed."));
704
+ }
705
+ commandCounter += 1;
706
+ const id = `cmd-${commandCounter}`;
707
+ return new Promise((resolve, reject) => {
708
+ pending.set(id, { resolve, reject });
709
+ transport.send(build(id));
710
+ });
711
+ };
712
+ return {
713
+ getState() {
714
+ return state;
715
+ },
716
+ getSeq() {
717
+ return seq;
718
+ },
719
+ subscribe(listener) {
720
+ stateListeners.add(listener);
721
+ return () => stateListeners.delete(listener);
722
+ },
723
+ onEvent(listener) {
724
+ eventListeners.add(listener);
725
+ return () => eventListeners.delete(listener);
726
+ },
727
+ submit(input, opts) {
728
+ return sendCommand((id) => ({
729
+ id,
730
+ type: "submit",
731
+ input,
732
+ displayText: opts.displayText,
733
+ ...opts.attachmentLabels?.length ? { attachmentLabels: opts.attachmentLabels } : {}
734
+ })).then(() => {
735
+ });
736
+ },
737
+ decideApproval(toolCallId, decision, comment, grantDir) {
738
+ return sendCommand((id) => ({
739
+ id,
740
+ type: "decide-approval",
741
+ toolCallId,
742
+ decision,
743
+ ...comment !== void 0 ? { comment } : {},
744
+ ...grantDir !== void 0 ? { grantDir } : {}
745
+ })).then(() => {
746
+ });
747
+ },
748
+ answerQuestion(questionId, answers) {
749
+ return sendCommand((id) => ({ id, type: "answer-question", questionId, answers })).then(
750
+ () => {
751
+ }
752
+ );
753
+ },
754
+ abort() {
755
+ return sendCommand((id) => ({ id, type: "abort" })).then(() => {
756
+ });
757
+ },
758
+ dequeueMessage(queuedId) {
759
+ return sendCommand((id) => ({ id, type: "dequeue", queuedId })).then(() => {
760
+ });
761
+ },
762
+ runSlashCommand(raw) {
763
+ return sendCommand((id) => ({ id, type: "slash-command", raw })).then(() => {
764
+ });
765
+ },
766
+ loadHistory(historyId) {
767
+ return sendCommand((id) => ({ id, type: "load-history", historyId })).then(() => {
768
+ });
769
+ },
770
+ reloadConfig() {
771
+ return sendCommand((id) => ({ id, type: "reload-config" })).then(() => {
772
+ });
773
+ },
774
+ cancelAuthFlow() {
775
+ return sendCommand((id) => ({ id, type: "cancel-auth-flow" })).then(() => {
776
+ });
777
+ },
778
+ serverCommands() {
779
+ return serverCommandList;
780
+ },
781
+ completePath(prefix) {
782
+ return sendCommand((id) => ({ id, type: "complete-path", prefix })).then(
783
+ (result) => Array.isArray(result) ? result.filter((entry) => typeof entry === "string") : []
784
+ );
785
+ },
786
+ completeModels() {
787
+ return sendCommand((id) => ({ id, type: "complete-models" })).then(toModelCompletion);
788
+ },
789
+ close() {
790
+ if (closed) return;
791
+ closed = true;
792
+ unsubscribe();
793
+ const error = new Error("SessionClient closed before the command was acknowledged.");
794
+ for (const { reject } of pending.values()) {
795
+ reject(error);
796
+ }
797
+ pending.clear();
798
+ }
799
+ };
800
+ }
801
+
802
+ // src/client/uds-socket.ts
803
+ import { WebSocket } from "ws";
804
+ function udsUrl(socketPath) {
805
+ return `ws+unix://${socketPath}:/`;
806
+ }
807
+ function ensureLocalhostNoProxy() {
808
+ const ga = globalThis.GLOBAL_AGENT;
809
+ if (!ga) return;
810
+ const current = ga.NO_PROXY ?? "";
811
+ const entries = current.split(",").map((s) => s.trim()).filter(Boolean);
812
+ let changed = false;
813
+ for (const host of ["localhost", "127.0.0.1"]) {
814
+ if (!entries.includes(host)) {
815
+ entries.push(host);
816
+ changed = true;
817
+ }
818
+ }
819
+ if (changed) {
820
+ ga.NO_PROXY = entries.join(",");
821
+ }
822
+ }
823
+ function udsSocketFactory(socketPath) {
824
+ const target = udsUrl(socketPath);
825
+ return (_url) => {
826
+ ensureLocalhostNoProxy();
827
+ return new WebSocket(target);
828
+ };
829
+ }
830
+
831
+ // src/client/websocket.ts
832
+ var toText2 = (data) => {
833
+ if (typeof data === "string") return data;
834
+ if (data instanceof Buffer) return data.toString("utf8");
835
+ if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf8");
836
+ if (Array.isArray(data) && data.every((chunk) => chunk instanceof Buffer)) {
837
+ return Buffer.concat(data).toString("utf8");
838
+ }
839
+ return null;
840
+ };
841
+ var parseMessage2 = (data) => {
842
+ const text = toText2(data);
843
+ if (text === null) return null;
844
+ try {
845
+ const parsed = JSON.parse(text);
846
+ return typeof parsed === "object" && parsed !== null && typeof parsed.type === "string" ? parsed : null;
847
+ } catch {
848
+ return null;
849
+ }
850
+ };
851
+ function createWebSocketTransport(options) {
852
+ if (!options.getEndpoint && (options.url === void 0 || options.token === void 0)) {
853
+ throw new Error("createWebSocketTransport requires either url+token or getEndpoint.");
854
+ }
855
+ const socketFactory = options.socketFactory ?? defaultSocketFactory;
856
+ const backoffInitialMs = options.backoffInitialMs ?? 500;
857
+ const backoffMaxMs = options.backoffMaxMs ?? 3e4;
858
+ const backoffFactor = options.backoffFactor ?? 2;
859
+ const frameListeners = /* @__PURE__ */ new Set();
860
+ const stateListeners = /* @__PURE__ */ new Set();
861
+ let state = "reconnecting";
862
+ let phase = "awaiting-welcome";
863
+ let socket = null;
864
+ let lastSeq = null;
865
+ let attempts = 0;
866
+ let attachCounter = 0;
867
+ let reconnectTimer = null;
868
+ let closed = false;
869
+ const setState = (next) => {
870
+ if (state === next) return;
871
+ state = next;
872
+ for (const listener of stateListeners) {
873
+ try {
874
+ listener(next);
875
+ } catch {
876
+ }
877
+ }
878
+ };
879
+ const deliver = (frame) => {
880
+ for (const listener of frameListeners) {
881
+ try {
882
+ listener(frame);
883
+ } catch {
884
+ }
885
+ }
886
+ };
887
+ const terminate = () => {
888
+ closed = true;
889
+ if (reconnectTimer !== null) {
890
+ clearTimeout(reconnectTimer);
891
+ reconnectTimer = null;
892
+ }
893
+ const current = socket;
894
+ socket = null;
895
+ current?.close();
896
+ setState("closed");
897
+ };
898
+ const scheduleReconnect = () => {
899
+ if (closed || reconnectTimer !== null) return;
900
+ setState("reconnecting");
901
+ const delay = Math.min(backoffInitialMs * backoffFactor ** attempts, backoffMaxMs);
902
+ attempts += 1;
903
+ reconnectTimer = setTimeout(() => {
904
+ reconnectTimer = null;
905
+ connect();
906
+ }, delay);
907
+ };
908
+ const completeAttach = () => {
909
+ phase = "attached";
910
+ attempts = 0;
911
+ setState("connected");
912
+ };
913
+ const handleMessage = (message) => {
914
+ if (message.type === "rejected") {
915
+ terminate();
916
+ options.onRejected?.(message.reason);
917
+ return;
918
+ }
919
+ if (message.type === "detached") {
920
+ terminate();
921
+ options.onDetached?.(message.reason);
922
+ return;
923
+ }
924
+ if (phase === "awaiting-welcome") {
925
+ if (message.type !== "welcome") return;
926
+ phase = "awaiting-attach";
927
+ setState("replaying");
928
+ attachCounter += 1;
929
+ socket?.send(
930
+ JSON.stringify({
931
+ id: `attach-${attachCounter}`,
932
+ type: "attach",
933
+ sessionId: options.sessionId,
934
+ ...lastSeq !== null ? { lastSeq } : {}
935
+ })
936
+ );
937
+ return;
938
+ }
939
+ if (phase === "awaiting-attach") {
940
+ if (message.type === "replay") {
941
+ for (const event of message.events) {
942
+ lastSeq = event.seq;
943
+ deliver(event);
944
+ }
945
+ completeAttach();
946
+ return;
947
+ }
948
+ if (message.type === "snapshot") {
949
+ lastSeq = message.snapshot.seq;
950
+ deliver(message);
951
+ completeAttach();
952
+ }
953
+ return;
954
+ }
955
+ if (message.type === "ack") {
956
+ deliver(message);
957
+ return;
958
+ }
959
+ if (message.type === "snapshot") {
960
+ lastSeq = message.snapshot.seq;
961
+ deliver(message);
962
+ return;
963
+ }
964
+ if (message.type === "welcome" || message.type === "replay" || message.type === "session-list" || message.type === "session-created" || message.type === "history-list") {
965
+ return;
966
+ }
967
+ lastSeq = message.seq;
968
+ deliver(message);
969
+ };
970
+ const openSocket = (endpoint) => {
971
+ if (closed) return;
972
+ phase = "awaiting-welcome";
973
+ const current = socketFactory(endpoint.url);
974
+ socket = current;
975
+ current.on("open", () => {
976
+ if (closed || socket !== current) return;
977
+ current.send(
978
+ JSON.stringify({
979
+ type: "hello",
980
+ protocolVersion: PROTOCOL_VERSION,
981
+ ...endpoint.credential
982
+ })
983
+ );
984
+ });
985
+ current.on("message", (data) => {
986
+ if (closed || socket !== current) return;
987
+ const message = parseMessage2(data);
988
+ if (message) handleMessage(message);
989
+ });
990
+ current.on("error", () => {
991
+ });
992
+ current.on("close", () => {
993
+ if (socket !== current) return;
994
+ socket = null;
995
+ if (closed) return;
996
+ scheduleReconnect();
997
+ });
998
+ };
999
+ const connect = () => {
1000
+ if (closed) return;
1001
+ const getEndpoint = options.getEndpoint;
1002
+ if (!getEndpoint) {
1003
+ openSocket({ url: options.url, credential: { token: options.token } });
1004
+ return;
1005
+ }
1006
+ void getEndpoint().then(
1007
+ (endpoint) => {
1008
+ if (closed) return;
1009
+ openSocket(endpoint);
1010
+ },
1011
+ (error) => {
1012
+ if (closed) return;
1013
+ if (error instanceof TerminalEndpointError) {
1014
+ terminate();
1015
+ options.onDetached?.(error.reason);
1016
+ return;
1017
+ }
1018
+ scheduleReconnect();
1019
+ }
1020
+ );
1021
+ };
1022
+ connect();
1023
+ return {
1024
+ send(command) {
1025
+ if (closed || phase !== "attached" || socket === null) return;
1026
+ socket.send(JSON.stringify(command));
1027
+ },
1028
+ onFrame(listener) {
1029
+ frameListeners.add(listener);
1030
+ return () => {
1031
+ frameListeners.delete(listener);
1032
+ };
1033
+ },
1034
+ close() {
1035
+ if (closed) return;
1036
+ terminate();
1037
+ frameListeners.clear();
1038
+ stateListeners.clear();
1039
+ },
1040
+ connectionState() {
1041
+ return state;
1042
+ },
1043
+ onConnectionState(listener) {
1044
+ stateListeners.add(listener);
1045
+ return () => {
1046
+ stateListeners.delete(listener);
1047
+ };
1048
+ }
1049
+ };
1050
+ }
1051
+ export {
1052
+ TerminalEndpointError,
1053
+ adaptWhatwgSocket,
1054
+ createServerDirectory,
1055
+ createSessionClient,
1056
+ createWebSocketTransport,
1057
+ defaultSocketFactory,
1058
+ udsSocketFactory,
1059
+ udsUrl
1060
+ };