adhdev 0.8.50 → 0.8.52

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 (41) hide show
  1. package/dist/cli/index.js +30 -5
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/index.js +30 -5
  4. package/dist/index.js.map +1 -1
  5. package/package.json +3 -2
  6. package/vendor/terminal-mux-cli/index.d.mts +1 -0
  7. package/vendor/terminal-mux-cli/index.d.ts +1 -0
  8. package/vendor/terminal-mux-cli/index.js +2056 -0
  9. package/vendor/terminal-mux-cli/index.mjs +2048 -0
  10. package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.d.mts +427 -0
  11. package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.d.ts +427 -0
  12. package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.js +617 -0
  13. package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.js.map +1 -0
  14. package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.mjs +573 -0
  15. package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.mjs.map +1 -0
  16. package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/package.json +7 -0
  17. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.d.mts +16 -0
  18. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.d.ts +16 -0
  19. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.js +206 -0
  20. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.mjs +17 -0
  21. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/chunk-7RNMRPVZ.mjs +183 -0
  22. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/chunk-R4EFW6W3.mjs +46 -0
  23. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/chunk-XZWWVN5W.mjs +164 -0
  24. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.d.mts +35 -0
  25. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.d.ts +35 -0
  26. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.js +219 -0
  27. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.mjs +13 -0
  28. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.d.mts +5 -0
  29. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.d.ts +5 -0
  30. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.js +427 -0
  31. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.mjs +34 -0
  32. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/package.json +33 -0
  33. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.d.mts +49 -0
  34. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.d.ts +49 -0
  35. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.js +222 -0
  36. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.mjs +16 -0
  37. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.d.mts +162 -0
  38. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.d.ts +162 -0
  39. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.js +985 -0
  40. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.mjs +948 -0
  41. package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/package.json +7 -0
@@ -0,0 +1,985 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ GhosttyTerminalSurface: () => GhosttyTerminalSurface,
24
+ SessionHostMuxClient: () => SessionHostMuxClient,
25
+ applyMuxLayoutPreset: () => applyMuxLayoutPreset,
26
+ createMuxWorkspace: () => createMuxWorkspace,
27
+ focusMuxPane: () => focusMuxPane,
28
+ rebalanceMuxLayout: () => rebalanceMuxLayout,
29
+ removeMuxPane: () => removeMuxPane,
30
+ resizeMuxPane: () => resizeMuxPane,
31
+ serializeWorkspace: () => serializeWorkspace,
32
+ splitMuxPane: () => splitMuxPane,
33
+ swapMuxPanePositions: () => swapMuxPanePositions,
34
+ toggleMuxPaneZoom: () => toggleMuxPaneZoom,
35
+ updateMuxPane: () => updateMuxPane
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/layout.ts
40
+ var import_crypto = require("crypto");
41
+ function cloneNode(node) {
42
+ if (node.type === "pane") {
43
+ return { ...node };
44
+ }
45
+ return {
46
+ ...node,
47
+ first: cloneNode(node.first),
48
+ second: cloneNode(node.second)
49
+ };
50
+ }
51
+ function replaceNode(node, paneId, next) {
52
+ if (node.type === "pane") {
53
+ return node.paneId === paneId ? next : node;
54
+ }
55
+ return {
56
+ ...node,
57
+ first: replaceNode(node.first, paneId, next),
58
+ second: replaceNode(node.second, paneId, next)
59
+ };
60
+ }
61
+ function removeNode(node, paneId) {
62
+ if (node.type === "pane") {
63
+ return node.paneId === paneId ? null : node;
64
+ }
65
+ const first = removeNode(node.first, paneId);
66
+ const second = removeNode(node.second, paneId);
67
+ if (!first && !second) return null;
68
+ if (!first) return second;
69
+ if (!second) return first;
70
+ return {
71
+ ...node,
72
+ first,
73
+ second
74
+ };
75
+ }
76
+ function cloneWorkspace(workspace) {
77
+ return {
78
+ ...workspace,
79
+ root: cloneNode(workspace.root),
80
+ panes: Object.fromEntries(
81
+ Object.entries(workspace.panes).map(([paneId, pane]) => [paneId, { ...pane, viewport: { ...pane.viewport } }])
82
+ )
83
+ };
84
+ }
85
+ function nodeContainsPane(node, paneId) {
86
+ if (node.type === "pane") {
87
+ return node.paneId === paneId;
88
+ }
89
+ return nodeContainsPane(node.first, paneId) || nodeContainsPane(node.second, paneId);
90
+ }
91
+ function clampRatio(value) {
92
+ return Math.max(0.15, Math.min(0.85, value));
93
+ }
94
+ function adjustNodeForPane(node, paneId, axis, delta) {
95
+ if (node.type === "pane") {
96
+ return { node, changed: false };
97
+ }
98
+ const firstContains = nodeContainsPane(node.first, paneId);
99
+ const secondContains = !firstContains && nodeContainsPane(node.second, paneId);
100
+ let changed = false;
101
+ let nextNode = node;
102
+ if (node.axis === axis && (firstContains || secondContains)) {
103
+ const directionDelta = firstContains ? delta : -delta;
104
+ nextNode = {
105
+ ...node,
106
+ ratio: clampRatio(node.ratio + directionDelta)
107
+ };
108
+ changed = true;
109
+ }
110
+ if (firstContains) {
111
+ const adjusted = adjustNodeForPane(nextNode.first, paneId, axis, delta);
112
+ if (adjusted.changed) {
113
+ nextNode = {
114
+ ...nextNode,
115
+ first: adjusted.node
116
+ };
117
+ changed = true;
118
+ }
119
+ } else if (secondContains) {
120
+ const adjusted = adjustNodeForPane(nextNode.second, paneId, axis, delta);
121
+ if (adjusted.changed) {
122
+ nextNode = {
123
+ ...nextNode,
124
+ second: adjusted.node
125
+ };
126
+ changed = true;
127
+ }
128
+ }
129
+ return { node: nextNode, changed };
130
+ }
131
+ function rebalanceNode(node) {
132
+ if (node.type === "pane") return node;
133
+ return {
134
+ ...node,
135
+ ratio: 0.5,
136
+ first: rebalanceNode(node.first),
137
+ second: rebalanceNode(node.second)
138
+ };
139
+ }
140
+ function mapPaneIds(node, firstPaneId, secondPaneId) {
141
+ if (node.type === "pane") {
142
+ if (node.paneId === firstPaneId) return { ...node, paneId: secondPaneId };
143
+ if (node.paneId === secondPaneId) return { ...node, paneId: firstPaneId };
144
+ return node;
145
+ }
146
+ return {
147
+ ...node,
148
+ first: mapPaneIds(node.first, firstPaneId, secondPaneId),
149
+ second: mapPaneIds(node.second, firstPaneId, secondPaneId)
150
+ };
151
+ }
152
+ function collectPaneIds(node, acc = []) {
153
+ if (node.type === "pane") {
154
+ acc.push(node.paneId);
155
+ return acc;
156
+ }
157
+ collectPaneIds(node.first, acc);
158
+ collectPaneIds(node.second, acc);
159
+ return acc;
160
+ }
161
+ function buildEvenLayout(paneIds, axis = "vertical") {
162
+ if (paneIds.length === 0) throw new Error("Cannot build layout without panes");
163
+ if (paneIds.length === 1) return { type: "pane", paneId: paneIds[0] };
164
+ const midpoint = Math.ceil(paneIds.length / 2);
165
+ const nextAxis = axis === "vertical" ? "horizontal" : "vertical";
166
+ return {
167
+ type: "split",
168
+ axis,
169
+ ratio: 0.5,
170
+ first: buildEvenLayout(paneIds.slice(0, midpoint), nextAxis),
171
+ second: buildEvenLayout(paneIds.slice(midpoint), nextAxis)
172
+ };
173
+ }
174
+ function buildStackLayout(paneIds, axis) {
175
+ if (paneIds.length === 0) throw new Error("Cannot build stack layout without panes");
176
+ if (paneIds.length === 1) return { type: "pane", paneId: paneIds[0] };
177
+ const [first, ...rest] = paneIds;
178
+ return {
179
+ type: "split",
180
+ axis,
181
+ ratio: 0.5,
182
+ first: { type: "pane", paneId: first },
183
+ second: buildStackLayout(rest, axis)
184
+ };
185
+ }
186
+ function buildMainLayout(paneIds, rootAxis) {
187
+ if (paneIds.length === 0) throw new Error("Cannot build layout without panes");
188
+ if (paneIds.length === 1) return { type: "pane", paneId: paneIds[0] };
189
+ const [primary, ...rest] = paneIds;
190
+ const stackAxis = rootAxis === "vertical" ? "horizontal" : "vertical";
191
+ return {
192
+ type: "split",
193
+ axis: rootAxis,
194
+ ratio: rest.length === 1 ? 0.5 : 0.62,
195
+ first: { type: "pane", paneId: primary },
196
+ second: buildStackLayout(rest, stackAxis)
197
+ };
198
+ }
199
+ function createMuxWorkspace(initialPane, options = {}) {
200
+ return {
201
+ workspaceId: options.workspaceId || (0, import_crypto.randomUUID)(),
202
+ title: options.title || initialPane.displayName,
203
+ root: {
204
+ type: "pane",
205
+ paneId: initialPane.paneId
206
+ },
207
+ focusedPaneId: initialPane.paneId,
208
+ zoomedPaneId: null,
209
+ panes: {
210
+ [initialPane.paneId]: initialPane
211
+ }
212
+ };
213
+ }
214
+ function splitMuxPane(workspace, targetPaneId, axis, nextPane) {
215
+ if (!workspace.panes[targetPaneId]) {
216
+ throw new Error(`Unknown pane: ${targetPaneId}`);
217
+ }
218
+ const next = cloneWorkspace(workspace);
219
+ next.root = replaceNode(next.root, targetPaneId, {
220
+ type: "split",
221
+ axis,
222
+ ratio: 0.5,
223
+ first: { type: "pane", paneId: targetPaneId },
224
+ second: { type: "pane", paneId: nextPane.paneId }
225
+ });
226
+ next.panes[nextPane.paneId] = nextPane;
227
+ next.focusedPaneId = nextPane.paneId;
228
+ return next;
229
+ }
230
+ function removeMuxPane(workspace, paneId) {
231
+ if (!workspace.panes[paneId]) return workspace;
232
+ const root = removeNode(workspace.root, paneId);
233
+ if (!root) return null;
234
+ const next = cloneWorkspace(workspace);
235
+ delete next.panes[paneId];
236
+ next.root = root;
237
+ if (next.focusedPaneId === paneId) {
238
+ next.focusedPaneId = Object.keys(next.panes)[0] || "";
239
+ }
240
+ if (next.zoomedPaneId === paneId) {
241
+ next.zoomedPaneId = null;
242
+ }
243
+ return next;
244
+ }
245
+ function focusMuxPane(workspace, paneId) {
246
+ if (!workspace.panes[paneId]) {
247
+ throw new Error(`Unknown pane: ${paneId}`);
248
+ }
249
+ return {
250
+ ...workspace,
251
+ focusedPaneId: paneId
252
+ };
253
+ }
254
+ function updateMuxPane(workspace, pane) {
255
+ if (!workspace.panes[pane.paneId]) return workspace;
256
+ return {
257
+ ...workspace,
258
+ panes: {
259
+ ...workspace.panes,
260
+ [pane.paneId]: pane
261
+ }
262
+ };
263
+ }
264
+ function toggleMuxPaneZoom(workspace, paneId) {
265
+ if (!workspace.panes[paneId]) {
266
+ throw new Error(`Unknown pane: ${paneId}`);
267
+ }
268
+ return {
269
+ ...workspace,
270
+ focusedPaneId: paneId,
271
+ zoomedPaneId: workspace.zoomedPaneId === paneId ? null : paneId
272
+ };
273
+ }
274
+ function resizeMuxPane(workspace, paneId, direction, amount = 0.05) {
275
+ if (!workspace.panes[paneId]) {
276
+ throw new Error(`Unknown pane: ${paneId}`);
277
+ }
278
+ const axis = direction === "left" || direction === "right" ? "vertical" : "horizontal";
279
+ const delta = direction === "left" || direction === "up" ? -Math.abs(amount) : Math.abs(amount);
280
+ const adjusted = adjustNodeForPane(workspace.root, paneId, axis, delta);
281
+ if (!adjusted.changed) {
282
+ return workspace;
283
+ }
284
+ return {
285
+ ...workspace,
286
+ root: adjusted.node
287
+ };
288
+ }
289
+ function rebalanceMuxLayout(workspace) {
290
+ return {
291
+ ...workspace,
292
+ root: rebalanceNode(workspace.root)
293
+ };
294
+ }
295
+ function swapMuxPanePositions(workspace, firstPaneId, secondPaneId) {
296
+ if (firstPaneId === secondPaneId) return workspace;
297
+ if (!workspace.panes[firstPaneId]) throw new Error(`Unknown pane: ${firstPaneId}`);
298
+ if (!workspace.panes[secondPaneId]) throw new Error(`Unknown pane: ${secondPaneId}`);
299
+ return {
300
+ ...workspace,
301
+ root: mapPaneIds(workspace.root, firstPaneId, secondPaneId),
302
+ focusedPaneId: workspace.focusedPaneId === firstPaneId ? secondPaneId : workspace.focusedPaneId === secondPaneId ? firstPaneId : workspace.focusedPaneId,
303
+ zoomedPaneId: workspace.zoomedPaneId === firstPaneId ? secondPaneId : workspace.zoomedPaneId === secondPaneId ? firstPaneId : workspace.zoomedPaneId
304
+ };
305
+ }
306
+ function applyMuxLayoutPreset(workspace, preset) {
307
+ const paneIds = collectPaneIds(workspace.root);
308
+ const orderedPaneIds = [workspace.focusedPaneId, ...paneIds.filter((paneId) => paneId !== workspace.focusedPaneId)];
309
+ let root;
310
+ switch (preset) {
311
+ case "main-vertical":
312
+ root = buildMainLayout(orderedPaneIds, "vertical");
313
+ break;
314
+ case "main-horizontal":
315
+ root = buildMainLayout(orderedPaneIds, "horizontal");
316
+ break;
317
+ case "tiled":
318
+ case "even":
319
+ default:
320
+ root = buildEvenLayout(orderedPaneIds, "vertical");
321
+ break;
322
+ }
323
+ return { ...workspace, root };
324
+ }
325
+
326
+ // src/ghostty-terminal-surface.ts
327
+ var import_module = require("module");
328
+ var import_meta = {};
329
+ var require2 = (0, import_module.createRequire)(
330
+ typeof __filename === "string" ? __filename : import_meta.url
331
+ );
332
+ var ghosttyBinding = require2("@adhdev/ghostty-vt-node");
333
+ var GhosttyTerminalSurface = class {
334
+ terminal;
335
+ cols;
336
+ rows;
337
+ snapshotSeq = 0;
338
+ constructor(options = {}) {
339
+ this.cols = Math.max(1, options.cols ?? 120);
340
+ this.rows = Math.max(1, options.rows ?? 36);
341
+ const terminalOptions = {
342
+ cols: this.cols,
343
+ rows: this.rows,
344
+ scrollback: Math.max(1024, options.scrollback ?? 32768)
345
+ };
346
+ this.terminal = ghosttyBinding.createTerminal(terminalOptions);
347
+ }
348
+ resetFromText(text, snapshotSeq = 0) {
349
+ this.terminal.dispose();
350
+ this.terminal = ghosttyBinding.createTerminal({
351
+ cols: this.cols,
352
+ rows: this.rows,
353
+ scrollback: 32768
354
+ });
355
+ if (text) {
356
+ this.terminal.write(text);
357
+ }
358
+ this.snapshotSeq = snapshotSeq;
359
+ }
360
+ write(data, snapshotSeq) {
361
+ if (data) {
362
+ this.terminal.write(data);
363
+ }
364
+ if (typeof snapshotSeq === "number") {
365
+ this.snapshotSeq = snapshotSeq;
366
+ }
367
+ }
368
+ resize(cols, rows) {
369
+ this.cols = Math.max(1, cols | 0);
370
+ this.rows = Math.max(1, rows | 0);
371
+ this.terminal.resize(this.cols, this.rows);
372
+ }
373
+ getViewportState() {
374
+ return {
375
+ cols: this.cols,
376
+ rows: this.rows,
377
+ snapshotSeq: this.snapshotSeq,
378
+ text: this.terminal.formatPlainText({ trim: true }) || ""
379
+ };
380
+ }
381
+ dispose() {
382
+ this.terminal.dispose();
383
+ }
384
+ };
385
+
386
+ // src/session-host-mux-client.ts
387
+ var import_crypto2 = require("crypto");
388
+ var import_session_host_core = require("@adhdev/session-host-core");
389
+
390
+ // src/workspace-persistence.ts
391
+ function serializeWorkspace(workspace) {
392
+ return {
393
+ workspaceId: workspace.workspaceId,
394
+ title: workspace.title,
395
+ focusedPaneId: workspace.focusedPaneId,
396
+ zoomedPaneId: workspace.zoomedPaneId || null,
397
+ root: workspace.root,
398
+ panes: Object.fromEntries(
399
+ Object.entries(workspace.panes).map(([paneId, pane]) => [
400
+ paneId,
401
+ {
402
+ runtimeId: pane.runtimeId,
403
+ runtimeKey: pane.runtimeKey,
404
+ paneKind: pane.paneKind,
405
+ accessMode: pane.accessMode
406
+ }
407
+ ])
408
+ )
409
+ };
410
+ }
411
+
412
+ // src/session-host-mux-client.ts
413
+ function paneFromRecord(paneId, record, surface, paneKind, accessMode) {
414
+ return {
415
+ paneId,
416
+ paneKind,
417
+ runtimeId: record.sessionId,
418
+ runtimeKey: record.runtimeKey,
419
+ displayName: record.displayName,
420
+ workspaceLabel: record.workspaceLabel,
421
+ accessMode,
422
+ lifecycle: record.lifecycle,
423
+ writeOwner: record.writeOwner,
424
+ attachedClients: record.attachedClients,
425
+ viewport: surface.getViewportState()
426
+ };
427
+ }
428
+ var SessionHostMuxClient = class {
429
+ clientId;
430
+ clientType = "local-terminal";
431
+ client;
432
+ paneById = /* @__PURE__ */ new Map();
433
+ paneIdsByRuntime = /* @__PURE__ */ new Map();
434
+ workspaceById = /* @__PURE__ */ new Map();
435
+ listeners = /* @__PURE__ */ new Set();
436
+ unsubEvents = null;
437
+ constructor(options = {}) {
438
+ this.client = new import_session_host_core.SessionHostClient(options);
439
+ this.clientId = options.clientId || `terminal-ui-${(0, import_crypto2.randomUUID)()}`;
440
+ }
441
+ async connect() {
442
+ await this.client.connect();
443
+ if (!this.unsubEvents) {
444
+ this.unsubEvents = this.client.onEvent((event) => {
445
+ void this.handleHostEvent(event);
446
+ });
447
+ }
448
+ }
449
+ onEvent(listener) {
450
+ this.listeners.add(listener);
451
+ return () => {
452
+ this.listeners.delete(listener);
453
+ };
454
+ }
455
+ async createWorkspace(target, options = {}) {
456
+ const pane = await this.openRuntime(target, options);
457
+ const workspace = createMuxWorkspace(pane, {
458
+ workspaceId: options.workspaceId,
459
+ title: options.title
460
+ });
461
+ this.workspaceById.set(workspace.workspaceId, workspace);
462
+ this.emit({ kind: "workspace", workspace });
463
+ return workspace;
464
+ }
465
+ async splitWorkspacePane(workspaceId, targetPaneId, runtimeTarget, options) {
466
+ const workspace = this.requireWorkspace(workspaceId);
467
+ const nextPane = await this.openRuntime(runtimeTarget, options);
468
+ const updated = splitMuxPane(workspace, targetPaneId, options.axis, nextPane);
469
+ this.workspaceById.set(workspaceId, updated);
470
+ this.emit({ kind: "workspace", workspace: updated });
471
+ return updated;
472
+ }
473
+ async splitWorkspaceMirror(workspaceId, targetPaneId, sourcePaneId, axis) {
474
+ const workspace = this.requireWorkspace(workspaceId);
475
+ const source = this.requirePane(sourcePaneId);
476
+ const paneId = (0, import_crypto2.randomUUID)();
477
+ const viewport = source.surface.getViewportState();
478
+ const surface = new GhosttyTerminalSurface({
479
+ cols: viewport.cols,
480
+ rows: viewport.rows
481
+ });
482
+ surface.resetFromText(viewport.text, viewport.snapshotSeq);
483
+ const paneState = paneFromRecord(paneId, source.record, surface, "mirror", "read-only");
484
+ this.paneById.set(paneId, {
485
+ record: source.record,
486
+ surface,
487
+ paneKind: "mirror",
488
+ accessMode: "read-only",
489
+ requestedReadOnly: true
490
+ });
491
+ const paneIds = this.paneIdsByRuntime.get(source.record.sessionId) || /* @__PURE__ */ new Set();
492
+ paneIds.add(paneId);
493
+ this.paneIdsByRuntime.set(source.record.sessionId, paneIds);
494
+ const updated = splitMuxPane(workspace, targetPaneId, axis, paneState);
495
+ this.workspaceById.set(workspaceId, updated);
496
+ this.emit({ kind: "runtime", pane: paneState });
497
+ this.emit({ kind: "workspace", workspace: updated });
498
+ return updated;
499
+ }
500
+ async replacePaneRuntime(workspaceId, paneId, runtimeTarget, options = {}) {
501
+ const workspace = this.requireWorkspace(workspaceId);
502
+ const existing = this.requirePane(paneId);
503
+ const previousRuntimeId = existing.record.sessionId;
504
+ const replacement = await this.openRuntime(runtimeTarget, {
505
+ ...options,
506
+ paneId
507
+ });
508
+ existing.surface.dispose();
509
+ if (previousRuntimeId !== replacement.runtimeId) {
510
+ const existingPaneIds = this.paneIdsByRuntime.get(previousRuntimeId);
511
+ existingPaneIds?.delete(paneId);
512
+ if (existingPaneIds && existingPaneIds.size === 0) {
513
+ this.paneIdsByRuntime.delete(previousRuntimeId);
514
+ await this.client.request({
515
+ type: "detach_session",
516
+ payload: {
517
+ sessionId: previousRuntimeId,
518
+ clientId: this.clientId
519
+ }
520
+ });
521
+ }
522
+ }
523
+ const updated = updateMuxPane(workspace, replacement);
524
+ this.workspaceById.set(workspaceId, updated);
525
+ this.emit({ kind: "workspace", workspace: updated });
526
+ return updated;
527
+ }
528
+ async restoreWorkspace(snapshot) {
529
+ await this.connect();
530
+ const panes = Object.entries(snapshot.panes);
531
+ if (panes.length === 0) {
532
+ throw new Error(`Workspace ${snapshot.workspaceId} has no panes`);
533
+ }
534
+ const orderedPanes = panes.sort(([, left], [, right]) => {
535
+ const leftKind = left.paneKind || "runtime";
536
+ const rightKind = right.paneKind || "runtime";
537
+ if (leftKind === rightKind) return 0;
538
+ return leftKind === "runtime" ? -1 : 1;
539
+ });
540
+ const restoredPanes = [];
541
+ for (const [paneId, pane] of orderedPanes) {
542
+ const paneKind = pane.paneKind || "runtime";
543
+ const opened = paneKind === "mirror" ? await this.openMirrorRuntime(pane.runtimeId || pane.runtimeKey, { paneId }) : await this.openRuntime(pane.runtimeId || pane.runtimeKey, {
544
+ paneId,
545
+ readOnly: pane.accessMode === "read-only",
546
+ takeover: false
547
+ });
548
+ restoredPanes.push([paneId, opened]);
549
+ }
550
+ const workspace = {
551
+ workspaceId: snapshot.workspaceId,
552
+ title: snapshot.title,
553
+ root: snapshot.root,
554
+ focusedPaneId: snapshot.focusedPaneId in snapshot.panes ? snapshot.focusedPaneId : restoredPanes[0][0],
555
+ zoomedPaneId: snapshot.zoomedPaneId && snapshot.zoomedPaneId in snapshot.panes ? snapshot.zoomedPaneId : null,
556
+ panes: Object.fromEntries(restoredPanes)
557
+ };
558
+ this.workspaceById.set(workspace.workspaceId, workspace);
559
+ this.emit({ kind: "workspace", workspace });
560
+ return workspace;
561
+ }
562
+ async closePane(workspaceId, paneId) {
563
+ const workspace = this.requireWorkspace(workspaceId);
564
+ const next = removeMuxPane(workspace, paneId);
565
+ const pane = this.paneById.get(paneId);
566
+ if (pane) {
567
+ pane.surface.dispose();
568
+ this.paneById.delete(paneId);
569
+ const paneIds = this.paneIdsByRuntime.get(pane.record.sessionId);
570
+ paneIds?.delete(paneId);
571
+ if (paneIds && paneIds.size === 0) {
572
+ this.paneIdsByRuntime.delete(pane.record.sessionId);
573
+ await this.client.request({
574
+ type: "detach_session",
575
+ payload: {
576
+ sessionId: pane.record.sessionId,
577
+ clientId: this.clientId
578
+ }
579
+ });
580
+ }
581
+ }
582
+ if (!next) {
583
+ this.workspaceById.delete(workspaceId);
584
+ return null;
585
+ }
586
+ this.workspaceById.set(workspaceId, next);
587
+ this.emit({ kind: "workspace", workspace: next });
588
+ return next;
589
+ }
590
+ async focusPane(workspaceId, paneId) {
591
+ const workspace = this.requireWorkspace(workspaceId);
592
+ const next = focusMuxPane(workspace, paneId);
593
+ this.workspaceById.set(workspaceId, next);
594
+ this.emit({ kind: "workspace", workspace: next });
595
+ return next;
596
+ }
597
+ async resizeLayoutPane(workspaceId, paneId, direction, amount = 0.05) {
598
+ const workspace = this.requireWorkspace(workspaceId);
599
+ const next = resizeMuxPane(workspace, paneId, direction, amount);
600
+ this.workspaceById.set(workspaceId, next);
601
+ this.emit({ kind: "workspace", workspace: next });
602
+ return next;
603
+ }
604
+ async rebalanceWorkspaceLayout(workspaceId) {
605
+ const workspace = this.requireWorkspace(workspaceId);
606
+ const next = rebalanceMuxLayout(workspace);
607
+ this.workspaceById.set(workspaceId, next);
608
+ this.emit({ kind: "workspace", workspace: next });
609
+ return next;
610
+ }
611
+ async applyLayoutPreset(workspaceId, preset) {
612
+ const workspace = this.requireWorkspace(workspaceId);
613
+ const next = applyMuxLayoutPreset(workspace, preset);
614
+ this.workspaceById.set(workspaceId, next);
615
+ this.emit({ kind: "workspace", workspace: next });
616
+ return next;
617
+ }
618
+ async swapPanePositions(workspaceId, firstPaneId, secondPaneId) {
619
+ const workspace = this.requireWorkspace(workspaceId);
620
+ const next = swapMuxPanePositions(workspace, firstPaneId, secondPaneId);
621
+ this.workspaceById.set(workspaceId, next);
622
+ this.emit({ kind: "workspace", workspace: next });
623
+ return next;
624
+ }
625
+ async togglePaneZoom(workspaceId, paneId) {
626
+ const workspace = this.requireWorkspace(workspaceId);
627
+ const next = toggleMuxPaneZoom(workspace, paneId);
628
+ this.workspaceById.set(workspaceId, next);
629
+ this.emit({ kind: "workspace", workspace: next });
630
+ return next;
631
+ }
632
+ async sendInput(paneId, data) {
633
+ const pane = this.requirePane(paneId);
634
+ if (pane.accessMode === "read-only" && !pane.requestedReadOnly && pane.paneKind === "runtime") {
635
+ await this.takeoverPane(paneId);
636
+ }
637
+ if (pane.accessMode === "read-only") {
638
+ throw new Error(`Pane ${paneId} is read-only`);
639
+ }
640
+ const response = await this.client.request({
641
+ type: "send_input",
642
+ payload: {
643
+ sessionId: pane.record.sessionId,
644
+ clientId: this.clientId,
645
+ data
646
+ }
647
+ });
648
+ if (!response.success) {
649
+ if ((response.error || "").includes("Write owned by") && !pane.requestedReadOnly && pane.paneKind === "runtime") {
650
+ await this.takeoverPane(paneId);
651
+ const retry = await this.client.request({
652
+ type: "send_input",
653
+ payload: {
654
+ sessionId: pane.record.sessionId,
655
+ clientId: this.clientId,
656
+ data
657
+ }
658
+ });
659
+ if (!retry.success) {
660
+ throw new Error(retry.error || `Failed to send input to pane ${paneId}`);
661
+ }
662
+ return;
663
+ }
664
+ throw new Error(response.error || `Failed to send input to pane ${paneId}`);
665
+ }
666
+ }
667
+ async resizePane(paneId, cols, rows) {
668
+ const pane = this.requirePane(paneId);
669
+ pane.surface.resize(cols, rows);
670
+ await this.client.request({
671
+ type: "resize_session",
672
+ payload: {
673
+ sessionId: pane.record.sessionId,
674
+ cols,
675
+ rows
676
+ }
677
+ });
678
+ this.publishPaneUpdate(paneId);
679
+ }
680
+ async takeoverPane(paneId) {
681
+ const pane = this.requirePane(paneId);
682
+ const response = await this.client.request({
683
+ type: "acquire_write",
684
+ payload: {
685
+ sessionId: pane.record.sessionId,
686
+ clientId: this.clientId,
687
+ ownerType: "user",
688
+ force: true
689
+ }
690
+ });
691
+ if (!response.success || !response.result) {
692
+ throw new Error(response.error || "Failed to acquire write owner");
693
+ }
694
+ pane.record = response.result;
695
+ pane.requestedReadOnly = false;
696
+ pane.accessMode = "interactive";
697
+ this.publishPaneUpdate(paneId);
698
+ }
699
+ async releasePane(paneId) {
700
+ const pane = this.requirePane(paneId);
701
+ const response = await this.client.request({
702
+ type: "release_write",
703
+ payload: {
704
+ sessionId: pane.record.sessionId,
705
+ clientId: this.clientId
706
+ }
707
+ });
708
+ if (!response.success || !response.result) {
709
+ throw new Error(response.error || "Failed to release write owner");
710
+ }
711
+ pane.record = response.result;
712
+ pane.requestedReadOnly = false;
713
+ pane.accessMode = this.computeAccessMode(pane.record, false, pane.paneKind);
714
+ this.publishPaneUpdate(paneId);
715
+ }
716
+ listWorkspaces() {
717
+ return Array.from(this.workspaceById.values());
718
+ }
719
+ async listRuntimes() {
720
+ await this.connect();
721
+ const list = await this.client.request({ type: "list_sessions" });
722
+ if (!list.success || !list.result) {
723
+ throw new Error(list.error || "Failed to list runtimes");
724
+ }
725
+ return list.result;
726
+ }
727
+ async resumeRuntime(target) {
728
+ await this.connect();
729
+ const record = (0, import_session_host_core.resolveRuntimeRecord)(await this.listRuntimes(), target);
730
+ const response = await this.client.request({
731
+ type: "resume_session",
732
+ payload: {
733
+ sessionId: record.sessionId
734
+ }
735
+ });
736
+ if (!response.success || !response.result) {
737
+ throw new Error(response.error || `Failed to resume runtime ${target}`);
738
+ }
739
+ for (const [paneId, pane] of this.paneById) {
740
+ if (pane.record.sessionId !== record.sessionId) continue;
741
+ pane.record = response.result;
742
+ this.publishPaneUpdate(paneId);
743
+ }
744
+ return response.result;
745
+ }
746
+ serializeWorkspace(workspaceId) {
747
+ return serializeWorkspace(this.requireWorkspace(workspaceId));
748
+ }
749
+ async close() {
750
+ for (const [paneId, pane] of this.paneById) {
751
+ try {
752
+ if (pane.record.writeOwner?.clientId === this.clientId) {
753
+ await this.client.request({
754
+ type: "release_write",
755
+ payload: {
756
+ sessionId: pane.record.sessionId,
757
+ clientId: this.clientId
758
+ }
759
+ });
760
+ }
761
+ const siblings = this.paneIdsByRuntime.get(pane.record.sessionId);
762
+ if (siblings?.size === 1) {
763
+ await this.client.request({
764
+ type: "detach_session",
765
+ payload: {
766
+ sessionId: pane.record.sessionId,
767
+ clientId: this.clientId
768
+ }
769
+ });
770
+ }
771
+ } catch {
772
+ }
773
+ pane.surface.dispose();
774
+ this.paneById.delete(paneId);
775
+ }
776
+ this.paneIdsByRuntime.clear();
777
+ this.workspaceById.clear();
778
+ this.unsubEvents?.();
779
+ this.unsubEvents = null;
780
+ await this.client.close();
781
+ }
782
+ async openRuntime(target, options) {
783
+ await this.connect();
784
+ let record = (0, import_session_host_core.resolveRuntimeRecord)(await this.listRuntimes(), target);
785
+ if (record.lifecycle === "interrupted") {
786
+ try {
787
+ record = await this.resumeRuntime(target);
788
+ } catch {
789
+ }
790
+ }
791
+ const readOnly = options.takeover ? false : !!options.readOnly || !!(record.writeOwner && record.writeOwner.clientId !== this.clientId);
792
+ const attachResponse = await this.client.request({
793
+ type: "attach_session",
794
+ payload: {
795
+ sessionId: record.sessionId,
796
+ clientId: this.clientId,
797
+ clientType: this.clientType,
798
+ readOnly
799
+ }
800
+ });
801
+ if (!attachResponse.success || !attachResponse.result) {
802
+ throw new Error(attachResponse.error || `Failed to attach runtime ${target}`);
803
+ }
804
+ record = attachResponse.result;
805
+ if (options.takeover) {
806
+ const takeoverResponse = await this.client.request({
807
+ type: "acquire_write",
808
+ payload: {
809
+ sessionId: record.sessionId,
810
+ clientId: this.clientId,
811
+ ownerType: "user",
812
+ force: true
813
+ }
814
+ });
815
+ if (!takeoverResponse.success || !takeoverResponse.result) {
816
+ throw new Error(takeoverResponse.error || `Failed to acquire runtime ${target}`);
817
+ }
818
+ record = takeoverResponse.result;
819
+ }
820
+ const snapshot = await this.client.request({
821
+ type: "get_snapshot",
822
+ payload: {
823
+ sessionId: record.sessionId
824
+ }
825
+ });
826
+ if (!snapshot.success || !snapshot.result) {
827
+ throw new Error(snapshot.error || "Failed to get runtime snapshot");
828
+ }
829
+ const paneId = options.paneId || (0, import_crypto2.randomUUID)();
830
+ const surface = new GhosttyTerminalSurface({
831
+ cols: options.cols ?? 120,
832
+ rows: options.rows ?? 36
833
+ });
834
+ surface.resetFromText(snapshot.result.text, snapshot.result.seq);
835
+ const runtimeRecord = {
836
+ ...record,
837
+ attachedClients: record.attachedClients
838
+ };
839
+ const accessMode = this.computeAccessMode(runtimeRecord, readOnly, "runtime");
840
+ const paneState = paneFromRecord(paneId, runtimeRecord, surface, "runtime", accessMode);
841
+ this.paneById.set(paneId, {
842
+ record: runtimeRecord,
843
+ surface,
844
+ paneKind: "runtime",
845
+ accessMode,
846
+ requestedReadOnly: readOnly
847
+ });
848
+ const paneIds = this.paneIdsByRuntime.get(runtimeRecord.sessionId) || /* @__PURE__ */ new Set();
849
+ paneIds.add(paneId);
850
+ this.paneIdsByRuntime.set(runtimeRecord.sessionId, paneIds);
851
+ this.emit({ kind: "runtime", pane: paneState });
852
+ return paneState;
853
+ }
854
+ async openMirrorRuntime(target, options = {}) {
855
+ const record = (0, import_session_host_core.resolveRuntimeRecord)(await this.listRuntimes(), target);
856
+ const runtimePane = Array.from(this.paneById.values()).find((pane) => pane.record.sessionId === record.sessionId);
857
+ if (!runtimePane) {
858
+ throw new Error(`Cannot mirror runtime ${record.runtimeKey} before it is attached`);
859
+ }
860
+ const paneId = options.paneId || (0, import_crypto2.randomUUID)();
861
+ const viewport = runtimePane.surface.getViewportState();
862
+ const surface = new GhosttyTerminalSurface({
863
+ cols: viewport.cols,
864
+ rows: viewport.rows
865
+ });
866
+ surface.resetFromText(viewport.text, viewport.snapshotSeq);
867
+ const paneState = paneFromRecord(paneId, record, surface, "mirror", "read-only");
868
+ this.paneById.set(paneId, {
869
+ record,
870
+ surface,
871
+ paneKind: "mirror",
872
+ accessMode: "read-only",
873
+ requestedReadOnly: true
874
+ });
875
+ const paneIds = this.paneIdsByRuntime.get(record.sessionId) || /* @__PURE__ */ new Set();
876
+ paneIds.add(paneId);
877
+ this.paneIdsByRuntime.set(record.sessionId, paneIds);
878
+ this.emit({ kind: "runtime", pane: paneState });
879
+ return paneState;
880
+ }
881
+ async handleHostEvent(event) {
882
+ const sessionId = "sessionId" in event ? event.sessionId : event.type === "runtime_transition" ? event.transition.sessionId : event.type === "request_trace" ? event.trace.sessionId : event.entry.sessionId;
883
+ if (!sessionId) return;
884
+ const paneIds = this.paneIdsByRuntime.get(sessionId);
885
+ if (!paneIds || paneIds.size === 0) return;
886
+ for (const paneId of paneIds) {
887
+ const pane = this.paneById.get(paneId);
888
+ if (!pane) continue;
889
+ switch (event.type) {
890
+ case "session_output":
891
+ pane.surface.write(event.data, event.seq);
892
+ break;
893
+ case "session_started":
894
+ case "session_resumed":
895
+ pane.record = { ...pane.record, lifecycle: "running", osPid: event.pid ?? pane.record.osPid };
896
+ break;
897
+ case "session_exit":
898
+ pane.record = { ...pane.record, lifecycle: event.exitCode === 0 ? "stopped" : "failed" };
899
+ break;
900
+ case "session_stopped":
901
+ pane.record = { ...pane.record, lifecycle: "stopped" };
902
+ break;
903
+ case "session_resized":
904
+ pane.surface.resize(event.cols, event.rows);
905
+ break;
906
+ case "write_owner_changed":
907
+ pane.record = { ...pane.record, writeOwner: event.owner };
908
+ pane.accessMode = this.computeAccessMode(pane.record, pane.requestedReadOnly, pane.paneKind);
909
+ break;
910
+ case "client_attached":
911
+ pane.record = {
912
+ ...pane.record,
913
+ attachedClients: [
914
+ ...pane.record.attachedClients.filter((client) => client.clientId !== event.client.clientId),
915
+ event.client
916
+ ]
917
+ };
918
+ pane.accessMode = this.computeAccessMode(pane.record, pane.requestedReadOnly, pane.paneKind);
919
+ break;
920
+ case "client_detached":
921
+ pane.record = {
922
+ ...pane.record,
923
+ attachedClients: pane.record.attachedClients.filter((client) => client.clientId !== event.clientId)
924
+ };
925
+ pane.accessMode = this.computeAccessMode(pane.record, pane.requestedReadOnly, pane.paneKind);
926
+ break;
927
+ case "session_created":
928
+ pane.record = event.record;
929
+ pane.accessMode = this.computeAccessMode(pane.record, pane.requestedReadOnly, pane.paneKind);
930
+ break;
931
+ }
932
+ this.publishPaneUpdate(paneId, event);
933
+ }
934
+ }
935
+ publishPaneUpdate(paneId, event) {
936
+ const pane = this.requirePane(paneId);
937
+ const paneState = paneFromRecord(paneId, pane.record, pane.surface, pane.paneKind, pane.accessMode);
938
+ this.emit({ kind: "runtime", pane: paneState, event });
939
+ for (const [workspaceId, workspace] of this.workspaceById) {
940
+ if (!workspace.panes[paneId]) continue;
941
+ const updated = updateMuxPane(workspace, paneState);
942
+ this.workspaceById.set(workspaceId, updated);
943
+ this.emit({ kind: "workspace", workspace: updated });
944
+ }
945
+ }
946
+ emit(event) {
947
+ for (const listener of this.listeners) {
948
+ listener(event);
949
+ }
950
+ }
951
+ requireWorkspace(workspaceId) {
952
+ const workspace = this.workspaceById.get(workspaceId);
953
+ if (!workspace) throw new Error(`Unknown workspace: ${workspaceId}`);
954
+ return workspace;
955
+ }
956
+ requirePane(paneId) {
957
+ const pane = this.paneById.get(paneId);
958
+ if (!pane) throw new Error(`Unknown pane: ${paneId}`);
959
+ return pane;
960
+ }
961
+ computeAccessMode(record, requestedReadOnly, paneKind) {
962
+ if (paneKind === "mirror") return "read-only";
963
+ if (requestedReadOnly) return "read-only";
964
+ if (record.writeOwner && record.writeOwner.clientId !== this.clientId) return "read-only";
965
+ const attachedClient = record.attachedClients.find((client) => client.clientId === this.clientId);
966
+ if (attachedClient?.readOnly) return "read-only";
967
+ return "interactive";
968
+ }
969
+ };
970
+ // Annotate the CommonJS export names for ESM import in node:
971
+ 0 && (module.exports = {
972
+ GhosttyTerminalSurface,
973
+ SessionHostMuxClient,
974
+ applyMuxLayoutPreset,
975
+ createMuxWorkspace,
976
+ focusMuxPane,
977
+ rebalanceMuxLayout,
978
+ removeMuxPane,
979
+ resizeMuxPane,
980
+ serializeWorkspace,
981
+ splitMuxPane,
982
+ swapMuxPanePositions,
983
+ toggleMuxPaneZoom,
984
+ updateMuxPane
985
+ });