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.
- package/dist/cli/index.js +30 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +30 -5
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/vendor/terminal-mux-cli/index.d.mts +1 -0
- package/vendor/terminal-mux-cli/index.d.ts +1 -0
- package/vendor/terminal-mux-cli/index.js +2056 -0
- package/vendor/terminal-mux-cli/index.mjs +2048 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.d.mts +427 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.d.ts +427 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.js +617 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.js.map +1 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.mjs +573 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.mjs.map +1 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/package.json +7 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.d.mts +16 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.d.ts +16 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.js +206 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.mjs +17 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/chunk-7RNMRPVZ.mjs +183 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/chunk-R4EFW6W3.mjs +46 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/chunk-XZWWVN5W.mjs +164 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.d.mts +35 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.d.ts +35 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.js +219 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.mjs +13 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.d.mts +5 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.d.ts +5 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.js +427 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.mjs +34 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/package.json +33 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.d.mts +49 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.d.ts +49 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.js +222 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.mjs +16 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.d.mts +162 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.d.ts +162 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.js +985 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.mjs +948 -0
- 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
|
+
});
|