@thenewlabs/entangle-serve 2.12.2 → 2.14.0
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/daemon-client.d.ts +30 -0
- package/dist/daemon-client.d.ts.map +1 -0
- package/dist/daemon-client.js +76 -0
- package/dist/daemon-client.js.map +1 -0
- package/dist/daemon-server.d.ts +57 -0
- package/dist/daemon-server.d.ts.map +1 -0
- package/dist/daemon-server.js +258 -0
- package/dist/daemon-server.js.map +1 -0
- package/dist/daemon.d.ts +6 -6
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +15 -230
- package/dist/daemon.js.map +1 -1
- package/dist/host-session.d.ts +14 -0
- package/dist/host-session.d.ts.map +1 -1
- package/dist/host-session.js +2 -1
- package/dist/host-session.js.map +1 -1
- package/dist/host-terminal.js +86 -6
- package/dist/host-terminal.js.map +1 -1
- package/dist/index.js +44 -67
- package/dist/index.js.map +1 -1
- package/dist/ipc.d.ts +8 -0
- package/dist/ipc.d.ts.map +1 -1
- package/dist/ipc.js.map +1 -1
- package/dist/remote-host-session.d.ts +6 -0
- package/dist/remote-host-session.d.ts.map +1 -1
- package/dist/remote-host-session.js +8 -0
- package/dist/remote-host-session.js.map +1 -1
- package/dist/session-registry.d.ts +22 -0
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +38 -1
- package/dist/session-registry.js.map +1 -1
- package/package.json +1 -1
package/dist/daemon.js
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import * as net from 'net';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
1
|
import { OutputHandler, parseOutputMode } from '@thenewlabs/entangle-utils';
|
|
4
2
|
import { startAgent } from './agent.js';
|
|
5
3
|
import { SharedWorkspace } from './shared-workspace.js';
|
|
6
4
|
import { LocalHostSession } from './host-session.js';
|
|
7
|
-
import {
|
|
8
|
-
import { createMessageReader, decodeChunk, encodeChunk, writeMessage, } from './ipc.js';
|
|
5
|
+
import { createDaemonServer } from './daemon-server.js';
|
|
9
6
|
/**
|
|
10
7
|
* The DAEMON half of the tmux-style detach/reattach split for `entangle serve`.
|
|
11
8
|
*
|
|
12
9
|
* Runs headless (no TTY): it owns a {@link SharedWorkspace} + {@link LocalHostSession}
|
|
13
10
|
* and connects to the relay via {@link startAgent}, serving the pinned capability
|
|
14
|
-
* handed to it.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* running; the daemon exits only when the workspace's last
|
|
19
|
-
* signalled.
|
|
11
|
+
* handed to it. The socket server that terminal CLIENTS ({@link RemoteHostSession})
|
|
12
|
+
* attach to — fan-out, per-client viewports, registry bookkeeping, shutdown — lives
|
|
13
|
+
* in daemon-server.ts (shared with embedders like Locus); this file is just the
|
|
14
|
+
* entangle-specific composition around it. Detaching a client leaves the daemon
|
|
15
|
+
* (and the session) running; the daemon exits only when the workspace's last
|
|
16
|
+
* shell exits or it is signalled.
|
|
20
17
|
*
|
|
21
18
|
* Config comes entirely from the environment (set by the index wiring that spawns
|
|
22
19
|
* the daemon):
|
|
@@ -40,232 +37,20 @@ export async function runDaemon() {
|
|
|
40
37
|
// Installs the log sink (agent logs → ring buffer, fanned out to clients as
|
|
41
38
|
// `log` frames) and owns the session URL via setUrl().
|
|
42
39
|
const localSession = new LocalHostSession(workspace, output);
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
capId: cap.capId,
|
|
51
|
-
url,
|
|
52
|
-
createdAt,
|
|
53
|
-
};
|
|
54
|
-
addSession(info);
|
|
55
|
-
};
|
|
56
|
-
// --- client set + fan-out -------------------------------------------------
|
|
57
|
-
const clients = new Set();
|
|
58
|
-
const broadcast = (msg) => {
|
|
59
|
-
for (const socket of clients) {
|
|
60
|
-
try {
|
|
61
|
-
writeMessage(socket, msg);
|
|
62
|
-
}
|
|
63
|
-
catch { /* dropped client; its close handler cleans up */ }
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
// --- lifecycle / cleanup (idempotent) ------------------------------------
|
|
67
|
-
let server;
|
|
68
|
-
let shuttingDown = false;
|
|
69
|
-
const shutdown = (code) => {
|
|
70
|
-
if (shuttingDown)
|
|
71
|
-
return;
|
|
72
|
-
shuttingDown = true;
|
|
73
|
-
broadcast({ t: 'exit', code });
|
|
74
|
-
try {
|
|
75
|
-
localSession.dispose();
|
|
76
|
-
}
|
|
77
|
-
catch { /* best-effort */ }
|
|
78
|
-
try {
|
|
79
|
-
workspace.kill();
|
|
80
|
-
}
|
|
81
|
-
catch { /* best-effort */ }
|
|
82
|
-
for (const socket of clients) {
|
|
83
|
-
try {
|
|
84
|
-
socket.end();
|
|
85
|
-
}
|
|
86
|
-
catch { /* already gone */ }
|
|
87
|
-
}
|
|
88
|
-
clients.clear();
|
|
89
|
-
try {
|
|
90
|
-
server?.close();
|
|
91
|
-
}
|
|
92
|
-
catch { /* not listening */ }
|
|
93
|
-
removeSession(name);
|
|
94
|
-
try {
|
|
95
|
-
fs.unlinkSync(socketPathValue);
|
|
96
|
-
}
|
|
97
|
-
catch { /* already gone */ }
|
|
98
|
-
process.exit(0);
|
|
99
|
-
};
|
|
100
|
-
process.on('SIGTERM', () => shutdown(0));
|
|
101
|
-
process.on('SIGINT', () => shutdown(0));
|
|
102
|
-
// Subscribe ONCE to the SESSION-GLOBAL streams and fan each out to every
|
|
103
|
-
// client. Terminal output and window-state are NOT global anymore: each client
|
|
104
|
-
// gets its own workspace viewport (see onConnection) so it can sit on its own
|
|
105
|
-
// active window, so we deliberately do NOT broadcast onHostData/onWindowState.
|
|
106
|
-
localSession.onViewersChange((n) => broadcast({ t: 'viewers', n }));
|
|
107
|
-
localSession.onLog((line) => broadcast({ t: 'log', line }));
|
|
108
|
-
localSession.onUrl((url) => broadcast({ t: 'url', url }));
|
|
109
|
-
// Workspace ended (last window's shell exited) → tear the daemon down.
|
|
110
|
-
localSession.onExit((code) => shutdown(code));
|
|
111
|
-
// --- inbound client handling ---------------------------------------------
|
|
112
|
-
// A client's messages drive ITS OWN workspace viewport (keyed by `vpId`), so
|
|
113
|
-
// input/window ops only move that client's active window. Sizing stays global
|
|
114
|
-
// (the host/workspace size is authoritative), so hello/resize resize the whole
|
|
115
|
-
// workspace as before.
|
|
116
|
-
const handleClientMessage = (msg, socket, vpId) => {
|
|
117
|
-
switch (msg.t) {
|
|
118
|
-
case 'hello':
|
|
119
|
-
workspace.resize(msg.cols, msg.rows);
|
|
120
|
-
break;
|
|
121
|
-
case 'input':
|
|
122
|
-
workspace.writeFromViewport(vpId, decodeChunk(msg.data));
|
|
123
|
-
break;
|
|
124
|
-
case 'resize':
|
|
125
|
-
workspace.resize(msg.cols, msg.rows);
|
|
126
|
-
break;
|
|
127
|
-
case 'win':
|
|
128
|
-
switch (msg.op) {
|
|
129
|
-
case 'new':
|
|
130
|
-
workspace.newWindowForViewport(vpId);
|
|
131
|
-
break;
|
|
132
|
-
case 'next':
|
|
133
|
-
workspace.nextWindowForViewport(vpId);
|
|
134
|
-
break;
|
|
135
|
-
case 'prev':
|
|
136
|
-
workspace.prevWindowForViewport(vpId);
|
|
137
|
-
break;
|
|
138
|
-
case 'select':
|
|
139
|
-
if (msg.index !== undefined)
|
|
140
|
-
workspace.selectWindowForViewport(vpId, msg.index);
|
|
141
|
-
break;
|
|
142
|
-
case 'close':
|
|
143
|
-
if (msg.index !== undefined)
|
|
144
|
-
workspace.closeWindowFromViewport(vpId, msg.index);
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
break;
|
|
148
|
-
case 'refresh':
|
|
149
|
-
// Serialize the viewport's active window NOW and send it back as a
|
|
150
|
-
// `replay` frame (getReplay()'s source client-side). Because this is a
|
|
151
|
-
// full IPC round-trip after the client's own bytes were fed to the
|
|
152
|
-
// emulator, the serialize reflects the window's live screen — this is
|
|
153
|
-
// what lets a host repaint (e.g. after a full-screen app quits) paint
|
|
154
|
-
// the CURRENT primary instead of the stale attach-time cache.
|
|
155
|
-
try {
|
|
156
|
-
const frame = workspace.snapshotForViewport(vpId, msg.scrollback !== undefined ? { scrollback: msg.scrollback } : undefined);
|
|
157
|
-
writeMessage(socket, { t: 'replay', chunk: encodeChunk(frame) });
|
|
158
|
-
}
|
|
159
|
-
catch { /* dropped client; its close handler cleans up */ }
|
|
160
|
-
break;
|
|
161
|
-
case 'scrollback':
|
|
162
|
-
// Serialize the viewport's active window buffer to plain-text lines NOW
|
|
163
|
-
// and send them back for the client's copy-mode pager.
|
|
164
|
-
try {
|
|
165
|
-
writeMessage(socket, { t: 'scrollback', lines: workspace.scrollbackLinesForViewport(vpId) });
|
|
166
|
-
}
|
|
167
|
-
catch { /* dropped client; its close handler cleans up */ }
|
|
168
|
-
break;
|
|
169
|
-
case 'detach':
|
|
170
|
-
// Drop just this client's viewport; the daemon (and session) keep running.
|
|
171
|
-
clients.delete(socket);
|
|
172
|
-
workspace.detachViewport(vpId);
|
|
173
|
-
try {
|
|
174
|
-
socket.end();
|
|
175
|
-
}
|
|
176
|
-
catch { /* already ending */ }
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
let nextVpId = 0;
|
|
181
|
-
const onConnection = (socket) => {
|
|
182
|
-
// Each connection is its own workspace viewport with an independent active
|
|
183
|
-
// window; reuse the connection counter as the viewport key.
|
|
184
|
-
const vpId = `client-${nextVpId++}`;
|
|
185
|
-
clients.add(socket);
|
|
186
|
-
const remove = () => {
|
|
187
|
-
clients.delete(socket);
|
|
188
|
-
workspace.detachViewport(vpId);
|
|
189
|
-
};
|
|
190
|
-
socket.on('close', remove);
|
|
191
|
-
socket.on('error', remove);
|
|
192
|
-
// Attach this client's viewport: the workspace multiplexes ITS active
|
|
193
|
-
// window's output onto this socket and pushes ITS own window-state.
|
|
194
|
-
const { replay } = workspace.attachViewport({
|
|
195
|
-
sid: vpId,
|
|
196
|
-
onData: (chunk) => { try {
|
|
197
|
-
writeMessage(socket, { t: 'data', chunk: encodeChunk(chunk) });
|
|
198
|
-
}
|
|
199
|
-
catch { /* dropped */ } },
|
|
200
|
-
onWindowState: (state) => { try {
|
|
201
|
-
writeMessage(socket, { t: 'window-state', state });
|
|
202
|
-
}
|
|
203
|
-
catch { /* dropped */ } },
|
|
204
|
-
onExit: (code) => { try {
|
|
205
|
-
writeMessage(socket, { t: 'exit', code });
|
|
206
|
-
}
|
|
207
|
-
catch { /* dropped */ } },
|
|
208
|
-
});
|
|
209
|
-
createMessageReader(socket, (msg) => {
|
|
210
|
-
// Only ClientToDaemon frames are expected inbound; a single bad message
|
|
211
|
-
// must not take down the daemon.
|
|
212
|
-
try {
|
|
213
|
-
handleClientMessage(msg, socket, vpId);
|
|
214
|
-
}
|
|
215
|
-
catch (err) {
|
|
216
|
-
output.warn('Client message failed', err instanceof Error ? err.message : String(err));
|
|
217
|
-
}
|
|
218
|
-
}, () => { remove(); try {
|
|
219
|
-
socket.destroy();
|
|
220
|
-
}
|
|
221
|
-
catch { /* already gone */ } });
|
|
222
|
-
// Push this viewport's current state immediately so the client's UI
|
|
223
|
-
// populates fast. Sent synchronously (no await) right after attach, so live
|
|
224
|
-
// onData frames can't slip in before the replay.
|
|
225
|
-
try {
|
|
226
|
-
const url = localSession.getUrl();
|
|
227
|
-
if (url)
|
|
228
|
-
writeMessage(socket, { t: 'url', url });
|
|
229
|
-
writeMessage(socket, { t: 'window-state', state: workspace.windowStateForViewport(vpId) });
|
|
230
|
-
writeMessage(socket, { t: 'viewers', n: workspace.viewerCount() });
|
|
231
|
-
for (const line of localSession.getLogBuffer())
|
|
232
|
-
writeMessage(socket, { t: 'log', line });
|
|
233
|
-
writeMessage(socket, { t: 'replay', chunk: encodeChunk(replay) });
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
remove();
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
// --- listen ---------------------------------------------------------------
|
|
240
|
-
try {
|
|
241
|
-
fs.unlinkSync(socketPathValue);
|
|
242
|
-
}
|
|
243
|
-
catch { /* no stale socket to remove */ }
|
|
244
|
-
server = net.createServer(onConnection);
|
|
245
|
-
server.on('error', (err) => output.error('Daemon socket server error', err.message));
|
|
246
|
-
await new Promise((resolve, reject) => {
|
|
247
|
-
const onError = (err) => reject(err);
|
|
248
|
-
server.once('error', onError);
|
|
249
|
-
server.listen(socketPathValue, () => {
|
|
250
|
-
server.off('error', onError);
|
|
251
|
-
resolve();
|
|
252
|
-
});
|
|
40
|
+
const server = await createDaemonServer({
|
|
41
|
+
name,
|
|
42
|
+
socketPath: socketPathValue,
|
|
43
|
+
workspace,
|
|
44
|
+
session: localSession,
|
|
45
|
+
output,
|
|
46
|
+
registry: { capId: cap.capId, kind: 'entangle' },
|
|
253
47
|
});
|
|
254
|
-
try {
|
|
255
|
-
fs.chmodSync(socketPathValue, 0o600);
|
|
256
|
-
}
|
|
257
|
-
catch { /* best-effort tightening */ }
|
|
258
|
-
// Register now (url filled in once the relay assigns it).
|
|
259
|
-
register('');
|
|
260
48
|
// Connect to the relay and serve the pinned capability. onCapabilityReady sets
|
|
261
49
|
// the session URL (fanned out as a `url` frame) and records it in the registry.
|
|
262
50
|
await startAgent({
|
|
263
51
|
serverUrl,
|
|
264
52
|
sharedWorkspace: workspace,
|
|
265
|
-
onCapabilityReady: ({ link }) =>
|
|
266
|
-
localSession.setUrl(link);
|
|
267
|
-
register(link);
|
|
268
|
-
},
|
|
53
|
+
onCapabilityReady: ({ link }) => server.setUrl(link),
|
|
269
54
|
...(password ? { password } : {}),
|
|
270
55
|
pinnedCapability: cap,
|
|
271
56
|
});
|
package/dist/daemon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
package/dist/host-session.d.ts
CHANGED
|
@@ -82,8 +82,21 @@ export interface HostSession {
|
|
|
82
82
|
onExit(cb: (code: number | null, signal: string | null) => void): void;
|
|
83
83
|
/** Tear down any owned resources (e.g. release the captured-log sink). */
|
|
84
84
|
dispose(): void;
|
|
85
|
+
/**
|
|
86
|
+
* Release just the captured-log sink (if this session owns one) so agent logs
|
|
87
|
+
* print to stdout — a lighter cousin of {@link dispose} for UIs with no debug
|
|
88
|
+
* tab to feed. Unlike dispose it must NOT tear the session down (a daemon-
|
|
89
|
+
* backed session's dispose ends its socket).
|
|
90
|
+
*/
|
|
91
|
+
releaseLogSink?(): void;
|
|
85
92
|
/** Detach without ending the session (unused for now). */
|
|
86
93
|
detach?(): void;
|
|
94
|
+
/**
|
|
95
|
+
* End the WHOLE session — daemon shutdown, every client exits. Present only
|
|
96
|
+
* on daemon-backed sessions (the host UI's Ctrl-B q binding needs it; an
|
|
97
|
+
* in-process session ends by exiting its shells or Ctrl-C instead).
|
|
98
|
+
*/
|
|
99
|
+
kill?(): void;
|
|
87
100
|
}
|
|
88
101
|
/**
|
|
89
102
|
* The in-process {@link HostSession}: a thin adapter over a {@link SharedWorkspace}
|
|
@@ -135,6 +148,7 @@ export declare class LocalHostSession implements HostSession {
|
|
|
135
148
|
/** Store the session URL (once the relay assigns it) and notify subscribers. */
|
|
136
149
|
setUrl(link: string): void;
|
|
137
150
|
onExit(cb: (code: number | null, signal: string | null) => void): void;
|
|
151
|
+
releaseLogSink(): void;
|
|
138
152
|
dispose(): void;
|
|
139
153
|
}
|
|
140
154
|
//# sourceMappingURL=host-session.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-session.d.ts","sourceRoot":"","sources":["../src/host-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAK7D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IAE1B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9C,4DAA4D;IAC5D,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,qEAAqE;IACrE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC;;;;;;;;;OASG;IACH,SAAS,CAAC,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,UAAU,CAAC;IACtD;;;;;OAKG;IACH,YAAY,CAAC,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnD,0FAA0F;IAC1F,OAAO,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/C;;;;;OAKG;IACH,iBAAiB,IAAI,IAAI,CAAC;IAC1B,iGAAiG;IACjG,YAAY,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAGlD,mEAAmE;IACnE,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAC;IACtD,qEAAqE;IACrE,WAAW,IAAI,eAAe,CAAC;IAC/B,4CAA4C;IAC5C,SAAS,IAAI,IAAI,CAAC;IAClB,yCAAyC;IACzC,UAAU,IAAI,IAAI,CAAC;IACnB,6CAA6C;IAC7C,UAAU,IAAI,IAAI,CAAC;IACnB,+DAA+D;IAC/D,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,+BAA+B;IAC/B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG7B,8EAA8E;IAC9E,eAAe,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/C,yCAAyC;IACzC,WAAW,IAAI,MAAM,CAAC;IAGtB,iFAAiF;IACjF,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACxC,8EAA8E;IAC9E,YAAY,IAAI,SAAS,MAAM,EAAE,CAAC;IAGlC,4DAA4D;IAC5D,MAAM,IAAI,MAAM,GAAG,IAAI,CAAC;IACxB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAGvC,8DAA8D;IAC9D,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;IACvE,0EAA0E;IAC1E,OAAO,IAAI,IAAI,CAAC;IAChB,0DAA0D;IAC1D,MAAM,CAAC,IAAI,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"host-session.d.ts","sourceRoot":"","sources":["../src/host-session.ts"],"names":[],"mappings":"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"}
|
package/dist/host-session.js
CHANGED
|
@@ -88,6 +88,7 @@ export class LocalHostSession {
|
|
|
88
88
|
}
|
|
89
89
|
// --- lifecycle -----------------------------------------------------------
|
|
90
90
|
onExit(cb) { this.workspace.onExit(cb); }
|
|
91
|
-
|
|
91
|
+
releaseLogSink() { OutputHandler.setLogSink(null); }
|
|
92
|
+
dispose() { this.releaseLogSink(); }
|
|
92
93
|
}
|
|
93
94
|
//# sourceMappingURL=host-session.js.map
|
package/dist/host-session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-session.js","sourceRoot":"","sources":["../src/host-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAI3D,wEAAwE;AACxE,MAAM,eAAe,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"host-session.js","sourceRoot":"","sources":["../src/host-session.ts"],"names":[],"mappings":"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"}
|
package/dist/host-terminal.js
CHANGED
|
@@ -251,7 +251,7 @@ function attachBarTerminal(session, output, initialCols, initialRows) {
|
|
|
251
251
|
`Share this live session:`,
|
|
252
252
|
` ${BAR_FG}${link ?? 'connecting…'}${RESET}`,
|
|
253
253
|
``,
|
|
254
|
-
`Ctrl-B [=scroll d=detach l=logs c=new n/p=win 1-9=select x=close`,
|
|
254
|
+
`Ctrl-B [=scroll d=detach l=logs c=new n/p=win 1-9=select x=close q=quit`,
|
|
255
255
|
];
|
|
256
256
|
// Render the welcome/log view: a PINNED HEADER (brand, intro, share URL, key
|
|
257
257
|
// hint) at the top so the URL is always findable, a "Recent activity:"
|
|
@@ -628,9 +628,19 @@ function attachBarTerminal(session, output, initialCols, initialRows) {
|
|
|
628
628
|
// Detach fires the session's onExit path, which runs restore()+exit below;
|
|
629
629
|
// don't force an exit here that would race it.
|
|
630
630
|
if (ch === 'd') {
|
|
631
|
+
detaching = true;
|
|
631
632
|
session.detach?.();
|
|
632
633
|
return;
|
|
633
634
|
}
|
|
635
|
+
// Quit = END the whole session (daemon shutdown), guarded by a y/N confirm
|
|
636
|
+
// on the bar row. Only offered for sessions that support it (daemon-backed):
|
|
637
|
+
// a persistent workspace respawns its shell on close, so without this there
|
|
638
|
+
// is no way out of the session from inside the terminal.
|
|
639
|
+
if (ch === 'q' && session.kill) {
|
|
640
|
+
pendingKill = true;
|
|
641
|
+
write(`\x1b7\x1b[${rows};1H\x1b[2K${BAR_BG}${BAR_FG} End this session for everyone? y = yes, any other key = cancel ${RESET}\x1b8`);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
634
644
|
if (ch === 'l') {
|
|
635
645
|
enterDebug();
|
|
636
646
|
return;
|
|
@@ -699,6 +709,8 @@ function attachBarTerminal(session, output, initialCols, initialRows) {
|
|
|
699
709
|
// other bytes forward to the active window, flushed around each prefix or mouse
|
|
700
710
|
// boundary so ordering across a window switch is preserved.
|
|
701
711
|
let pendingPrefix = false;
|
|
712
|
+
let pendingKill = false; // armed by Ctrl-B q; the next byte confirms or cancels
|
|
713
|
+
let detaching = false; // set by Ctrl-B d so the exit path can print a reattach hint
|
|
702
714
|
const onInput = (d) => {
|
|
703
715
|
// In the pager, keystrokes drive scrolling and are never sent to the shell.
|
|
704
716
|
if (viewMode === 'scroll') {
|
|
@@ -708,6 +720,20 @@ function attachBarTerminal(session, output, initialCols, initialRows) {
|
|
|
708
720
|
let start = 0;
|
|
709
721
|
for (let i = 0; i < d.length;) {
|
|
710
722
|
const byte = d[i];
|
|
723
|
+
// Kill-confirm consumes exactly one byte and never forwards it: y ends the
|
|
724
|
+
// session (the daemon's exit broadcast runs the normal exit path below),
|
|
725
|
+
// anything else cancels and repaints the bar over the prompt.
|
|
726
|
+
if (pendingKill) {
|
|
727
|
+
pendingKill = false;
|
|
728
|
+
if (byte === 0x79 /* y */ || byte === 0x59 /* Y */) {
|
|
729
|
+
session.kill();
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
drawBar();
|
|
733
|
+
i++;
|
|
734
|
+
start = i;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
711
737
|
if (pendingPrefix) {
|
|
712
738
|
pendingPrefix = false;
|
|
713
739
|
if (byte === PREFIX)
|
|
@@ -810,7 +836,17 @@ function attachBarTerminal(session, output, initialCols, initialRows) {
|
|
|
810
836
|
session.onExit((code) => {
|
|
811
837
|
restore();
|
|
812
838
|
write('\n');
|
|
813
|
-
|
|
839
|
+
if (detaching) {
|
|
840
|
+
// Print the FULL session URL on the primary screen: the status bar
|
|
841
|
+
// truncates it to the terminal width, so this is the copyable source.
|
|
842
|
+
output.info('Detached — the session keeps running.');
|
|
843
|
+
const url = session.getUrl();
|
|
844
|
+
if (url)
|
|
845
|
+
output.info(`View URL: ${url}`);
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
output.info('Shared session ended.');
|
|
849
|
+
}
|
|
814
850
|
process.exit(code ?? 0);
|
|
815
851
|
});
|
|
816
852
|
process.on('exit', restore);
|
|
@@ -853,9 +889,11 @@ function attachRawTerminal(session, output) {
|
|
|
853
889
|
const stdin = process.stdin;
|
|
854
890
|
const stdout = process.stdout;
|
|
855
891
|
// No status bar and no debug tab here, so there's nowhere to surface captured
|
|
856
|
-
// logs — release the session's log sink so agent logs print
|
|
857
|
-
// did before this UI existed (the bar path keeps the sink
|
|
858
|
-
|
|
892
|
+
// logs — release the session's log sink (if it owns one) so agent logs print
|
|
893
|
+
// to stdout as they did before this UI existed (the bar path keeps the sink
|
|
894
|
+
// for its debug tab). NOT dispose(): a daemon-backed session's dispose ends
|
|
895
|
+
// its socket, which would kill the attach right here.
|
|
896
|
+
session.releaseLogSink?.();
|
|
859
897
|
const initial = session.getReplay();
|
|
860
898
|
if (initial.length > 0) {
|
|
861
899
|
try {
|
|
@@ -863,6 +901,13 @@ function attachRawTerminal(session, output) {
|
|
|
863
901
|
}
|
|
864
902
|
catch { }
|
|
865
903
|
}
|
|
904
|
+
// A daemon-backed session delivers its post-attach replay (and any refreshed
|
|
905
|
+
// frames) asynchronously via onFrame — paint those too, so a raw reattach
|
|
906
|
+
// shows the window's current screen instead of starting blank.
|
|
907
|
+
session.onFrame((frame) => { try {
|
|
908
|
+
stdout.write(Buffer.from(frame));
|
|
909
|
+
}
|
|
910
|
+
catch { } });
|
|
866
911
|
session.onHostData((chunk) => { try {
|
|
867
912
|
stdout.write(chunk);
|
|
868
913
|
}
|
|
@@ -871,7 +916,42 @@ function attachRawTerminal(session, output) {
|
|
|
871
916
|
if (stdin.isTTY)
|
|
872
917
|
stdin.setRawMode(true);
|
|
873
918
|
stdin.resume();
|
|
874
|
-
|
|
919
|
+
// A daemon-backed session gets a detach key even without the bar: Ctrl-B d
|
|
920
|
+
// detaches (Ctrl-B Ctrl-B sends a literal Ctrl-B; any other command byte is
|
|
921
|
+
// swallowed, like the bar's prefix), otherwise a raw attach could only be
|
|
922
|
+
// left by killing the terminal. Interactive TTYs only — piped stdin stays
|
|
923
|
+
// pure byte-for-byte passthrough.
|
|
924
|
+
const canDetach = !!stdin.isTTY && typeof session.detach === 'function';
|
|
925
|
+
let pendingPrefix = false;
|
|
926
|
+
const onInput = (d) => {
|
|
927
|
+
if (!canDetach) {
|
|
928
|
+
session.write(new Uint8Array(d));
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
let start = 0;
|
|
932
|
+
for (let i = 0; i < d.length; i++) {
|
|
933
|
+
const byte = d[i];
|
|
934
|
+
if (pendingPrefix) {
|
|
935
|
+
pendingPrefix = false;
|
|
936
|
+
if (byte === PREFIX)
|
|
937
|
+
session.write(new Uint8Array([PREFIX]));
|
|
938
|
+
else if (byte === 0x64 /* d */) {
|
|
939
|
+
session.detach();
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
start = i + 1;
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
if (byte === PREFIX) {
|
|
946
|
+
if (i > start)
|
|
947
|
+
session.write(new Uint8Array(d.subarray(start, i)));
|
|
948
|
+
pendingPrefix = true;
|
|
949
|
+
start = i + 1;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (d.length > start)
|
|
953
|
+
session.write(new Uint8Array(d.subarray(start)));
|
|
954
|
+
};
|
|
875
955
|
stdin.on('data', onInput);
|
|
876
956
|
const onResize = () => session.resize(stdout.columns || 80, stdout.rows || 24);
|
|
877
957
|
stdout.on('resize', onResize);
|