@thenewlabs/entangle-serve 2.6.2 → 2.7.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.d.ts +23 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +245 -0
- package/dist/daemon.js.map +1 -0
- package/dist/host-session.d.ts +99 -0
- package/dist/host-session.d.ts.map +1 -0
- package/dist/host-session.js +83 -0
- package/dist/host-session.js.map +1 -0
- package/dist/host-terminal.d.ts +7 -12
- package/dist/host-terminal.d.ts.map +1 -1
- package/dist/host-terminal.js +95 -103
- package/dist/host-terminal.js.map +1 -1
- package/dist/index.js +375 -112
- package/dist/index.js.map +1 -1
- package/dist/ipc.d.ts +103 -0
- package/dist/ipc.d.ts.map +1 -0
- package/dist/ipc.js +115 -0
- package/dist/ipc.js.map +1 -0
- package/dist/remote-host-session.d.ts +72 -0
- package/dist/remote-host-session.d.ts.map +1 -0
- package/dist/remote-host-session.js +170 -0
- package/dist/remote-host-session.js.map +1 -0
- package/dist/session-registry.d.ts +51 -0
- package/dist/session-registry.d.ts.map +1 -0
- package/dist/session-registry.js +167 -0
- package/dist/session-registry.js.map +1 -0
- package/package.json +4 -4
package/dist/daemon.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DAEMON half of the tmux-style detach/reattach split for `entangle serve`.
|
|
3
|
+
*
|
|
4
|
+
* Runs headless (no TTY): it owns a {@link SharedWorkspace} + {@link LocalHostSession}
|
|
5
|
+
* and connects to the relay via {@link startAgent}, serving the pinned capability
|
|
6
|
+
* handed to it. It listens on a local unix socket where zero or more terminal
|
|
7
|
+
* CLIENTS ({@link RemoteHostSession}) attach; it fans the session's events out to
|
|
8
|
+
* every attached client and applies each client's input/resize/window ops to the
|
|
9
|
+
* one shared session. Detaching a client leaves the daemon (and the session)
|
|
10
|
+
* running; the daemon exits only when the workspace's last shell exits or it is
|
|
11
|
+
* signalled.
|
|
12
|
+
*
|
|
13
|
+
* Config comes entirely from the environment (set by the index wiring that spawns
|
|
14
|
+
* the daemon):
|
|
15
|
+
*
|
|
16
|
+
* ENTANGLE_DAEMON_SESSION session name (registry key + log/socket basename)
|
|
17
|
+
* ENTANGLE_DAEMON_SOCKET absolute path of the unix socket to listen on
|
|
18
|
+
* ENTANGLE_DAEMON_SERVER relay server URL to register with
|
|
19
|
+
* ENTANGLE_DAEMON_CAP JSON-encoded CapabilityInfo ({capId,S,policy}) to pin
|
|
20
|
+
* ENTANGLE_DAEMON_PASSWORD optional connect password
|
|
21
|
+
*/
|
|
22
|
+
export declare function runDaemon(): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAkK/C"}
|
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { OutputHandler, parseOutputMode } from '@thenewlabs/entangle-utils';
|
|
4
|
+
import { startAgent } from './agent.js';
|
|
5
|
+
import { SharedWorkspace } from './shared-workspace.js';
|
|
6
|
+
import { LocalHostSession } from './host-session.js';
|
|
7
|
+
import { addSession, logPath, removeSession } from './session-registry.js';
|
|
8
|
+
import { createMessageReader, decodeChunk, encodeChunk, writeMessage, } from './ipc.js';
|
|
9
|
+
/**
|
|
10
|
+
* The DAEMON half of the tmux-style detach/reattach split for `entangle serve`.
|
|
11
|
+
*
|
|
12
|
+
* Runs headless (no TTY): it owns a {@link SharedWorkspace} + {@link LocalHostSession}
|
|
13
|
+
* and connects to the relay via {@link startAgent}, serving the pinned capability
|
|
14
|
+
* handed to it. It listens on a local unix socket where zero or more terminal
|
|
15
|
+
* CLIENTS ({@link RemoteHostSession}) attach; it fans the session's events out to
|
|
16
|
+
* every attached client and applies each client's input/resize/window ops to the
|
|
17
|
+
* one shared session. Detaching a client leaves the daemon (and the session)
|
|
18
|
+
* running; the daemon exits only when the workspace's last shell exits or it is
|
|
19
|
+
* signalled.
|
|
20
|
+
*
|
|
21
|
+
* Config comes entirely from the environment (set by the index wiring that spawns
|
|
22
|
+
* the daemon):
|
|
23
|
+
*
|
|
24
|
+
* ENTANGLE_DAEMON_SESSION session name (registry key + log/socket basename)
|
|
25
|
+
* ENTANGLE_DAEMON_SOCKET absolute path of the unix socket to listen on
|
|
26
|
+
* ENTANGLE_DAEMON_SERVER relay server URL to register with
|
|
27
|
+
* ENTANGLE_DAEMON_CAP JSON-encoded CapabilityInfo ({capId,S,policy}) to pin
|
|
28
|
+
* ENTANGLE_DAEMON_PASSWORD optional connect password
|
|
29
|
+
*/
|
|
30
|
+
export async function runDaemon() {
|
|
31
|
+
const name = requireEnv('ENTANGLE_DAEMON_SESSION');
|
|
32
|
+
const socketPathValue = requireEnv('ENTANGLE_DAEMON_SOCKET');
|
|
33
|
+
const serverUrl = requireEnv('ENTANGLE_DAEMON_SERVER');
|
|
34
|
+
const cap = parseCap(requireEnv('ENTANGLE_DAEMON_CAP'));
|
|
35
|
+
const password = process.env.ENTANGLE_DAEMON_PASSWORD;
|
|
36
|
+
const output = new OutputHandler({ mode: parseOutputMode('text') });
|
|
37
|
+
// A sane default size; the first attaching client resizes the session to its
|
|
38
|
+
// real terminal via its `hello`.
|
|
39
|
+
const workspace = new SharedWorkspace(output, { cols: 80, rows: 24 });
|
|
40
|
+
// Installs the log sink (agent logs → ring buffer, fanned out to clients as
|
|
41
|
+
// `log` frames) and owns the session URL via setUrl().
|
|
42
|
+
const localSession = new LocalHostSession(workspace, output);
|
|
43
|
+
const createdAt = Date.now();
|
|
44
|
+
const register = (url) => {
|
|
45
|
+
const info = {
|
|
46
|
+
name,
|
|
47
|
+
socket: socketPathValue,
|
|
48
|
+
logFile: logPath(name),
|
|
49
|
+
pid: process.pid,
|
|
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 and fan each event out to every client.
|
|
103
|
+
localSession.onHostData((chunk) => broadcast({ t: 'data', chunk: encodeChunk(chunk) }));
|
|
104
|
+
localSession.onWindowState((state) => broadcast({ t: 'window-state', state }));
|
|
105
|
+
localSession.onViewersChange((n) => broadcast({ t: 'viewers', n }));
|
|
106
|
+
localSession.onLog((line) => broadcast({ t: 'log', line }));
|
|
107
|
+
localSession.onUrl((url) => broadcast({ t: 'url', url }));
|
|
108
|
+
// Workspace ended (last window's shell exited) → tear the daemon down.
|
|
109
|
+
localSession.onExit((code) => shutdown(code));
|
|
110
|
+
// --- inbound client handling ---------------------------------------------
|
|
111
|
+
const handleClientMessage = (msg, socket) => {
|
|
112
|
+
switch (msg.t) {
|
|
113
|
+
case 'hello':
|
|
114
|
+
localSession.resize(msg.cols, msg.rows);
|
|
115
|
+
break;
|
|
116
|
+
case 'input':
|
|
117
|
+
localSession.write(decodeChunk(msg.data));
|
|
118
|
+
break;
|
|
119
|
+
case 'resize':
|
|
120
|
+
localSession.resize(msg.cols, msg.rows);
|
|
121
|
+
break;
|
|
122
|
+
case 'win':
|
|
123
|
+
switch (msg.op) {
|
|
124
|
+
case 'new':
|
|
125
|
+
localSession.newWindow();
|
|
126
|
+
break;
|
|
127
|
+
case 'next':
|
|
128
|
+
localSession.nextWindow();
|
|
129
|
+
break;
|
|
130
|
+
case 'prev':
|
|
131
|
+
localSession.prevWindow();
|
|
132
|
+
break;
|
|
133
|
+
case 'select':
|
|
134
|
+
if (msg.index !== undefined)
|
|
135
|
+
localSession.selectWindow(msg.index);
|
|
136
|
+
break;
|
|
137
|
+
case 'close':
|
|
138
|
+
if (msg.index !== undefined)
|
|
139
|
+
localSession.closeWindow(msg.index);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
case 'detach':
|
|
144
|
+
// Drop just this client; the daemon (and session) keep running.
|
|
145
|
+
clients.delete(socket);
|
|
146
|
+
try {
|
|
147
|
+
socket.end();
|
|
148
|
+
}
|
|
149
|
+
catch { /* already ending */ }
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const onConnection = (socket) => {
|
|
154
|
+
clients.add(socket);
|
|
155
|
+
const remove = () => { clients.delete(socket); };
|
|
156
|
+
socket.on('close', remove);
|
|
157
|
+
socket.on('error', remove);
|
|
158
|
+
createMessageReader(socket, (msg) => {
|
|
159
|
+
// Only ClientToDaemon frames are expected inbound; a single bad message
|
|
160
|
+
// must not take down the daemon.
|
|
161
|
+
try {
|
|
162
|
+
handleClientMessage(msg, socket);
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
output.warn('Client message failed', err instanceof Error ? err.message : String(err));
|
|
166
|
+
}
|
|
167
|
+
}, () => { remove(); try {
|
|
168
|
+
socket.destroy();
|
|
169
|
+
}
|
|
170
|
+
catch { /* already gone */ } });
|
|
171
|
+
// Push the current state immediately so the client's UI populates fast.
|
|
172
|
+
try {
|
|
173
|
+
const url = localSession.getUrl();
|
|
174
|
+
if (url)
|
|
175
|
+
writeMessage(socket, { t: 'url', url });
|
|
176
|
+
writeMessage(socket, { t: 'window-state', state: localSession.windowState() });
|
|
177
|
+
writeMessage(socket, { t: 'viewers', n: localSession.viewerCount() });
|
|
178
|
+
for (const line of localSession.getLogBuffer())
|
|
179
|
+
writeMessage(socket, { t: 'log', line });
|
|
180
|
+
writeMessage(socket, { t: 'replay', chunk: encodeChunk(localSession.getReplay()) });
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
remove();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
// --- listen ---------------------------------------------------------------
|
|
187
|
+
try {
|
|
188
|
+
fs.unlinkSync(socketPathValue);
|
|
189
|
+
}
|
|
190
|
+
catch { /* no stale socket to remove */ }
|
|
191
|
+
server = net.createServer(onConnection);
|
|
192
|
+
server.on('error', (err) => output.error('Daemon socket server error', err.message));
|
|
193
|
+
await new Promise((resolve, reject) => {
|
|
194
|
+
const onError = (err) => reject(err);
|
|
195
|
+
server.once('error', onError);
|
|
196
|
+
server.listen(socketPathValue, () => {
|
|
197
|
+
server.off('error', onError);
|
|
198
|
+
resolve();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
try {
|
|
202
|
+
fs.chmodSync(socketPathValue, 0o600);
|
|
203
|
+
}
|
|
204
|
+
catch { /* best-effort tightening */ }
|
|
205
|
+
// Register now (url filled in once the relay assigns it).
|
|
206
|
+
register('');
|
|
207
|
+
// Connect to the relay and serve the pinned capability. onCapabilityReady sets
|
|
208
|
+
// the session URL (fanned out as a `url` frame) and records it in the registry.
|
|
209
|
+
await startAgent({
|
|
210
|
+
serverUrl,
|
|
211
|
+
sharedWorkspace: workspace,
|
|
212
|
+
onCapabilityReady: ({ link }) => {
|
|
213
|
+
localSession.setUrl(link);
|
|
214
|
+
register(link);
|
|
215
|
+
},
|
|
216
|
+
...(password ? { password } : {}),
|
|
217
|
+
pinnedCapability: cap,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/** Read a required env var or throw a clear configuration error. */
|
|
221
|
+
function requireEnv(key) {
|
|
222
|
+
const value = process.env[key];
|
|
223
|
+
if (!value)
|
|
224
|
+
throw new Error(`Daemon misconfigured: ${key} is not set`);
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
/** Parse the pinned capability JSON from the environment. */
|
|
228
|
+
function parseCap(raw) {
|
|
229
|
+
let parsed;
|
|
230
|
+
try {
|
|
231
|
+
parsed = JSON.parse(raw);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
throw new Error('Daemon misconfigured: ENTANGLE_DAEMON_CAP is not valid JSON');
|
|
235
|
+
}
|
|
236
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
237
|
+
throw new Error('Daemon misconfigured: ENTANGLE_DAEMON_CAP is not a capability object');
|
|
238
|
+
}
|
|
239
|
+
const c = parsed;
|
|
240
|
+
if (typeof c.capId !== 'string' || typeof c.S !== 'string' || typeof c.policy !== 'object' || c.policy === null) {
|
|
241
|
+
throw new Error('Daemon misconfigured: ENTANGLE_DAEMON_CAP is missing capId/S/policy');
|
|
242
|
+
}
|
|
243
|
+
return parsed;
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { OutputHandler } from '@thenewlabs/entangle-utils';
|
|
2
|
+
import type { WindowStateBody } from '@thenewlabs/entangle-protocol';
|
|
3
|
+
import type { SharedWorkspace } from './shared-workspace.js';
|
|
4
|
+
/**
|
|
5
|
+
* The data source the host terminal UI renders against, decoupled from the
|
|
6
|
+
* concrete {@link SharedWorkspace}/{@link OutputHandler} it runs on today.
|
|
7
|
+
*
|
|
8
|
+
* The blue-bar host UI (see host-terminal.ts) only ever touches this surface:
|
|
9
|
+
* the active window's byte stream, the window set + operations, the viewer
|
|
10
|
+
* count, the captured agent-log tail (debug tab), the session URL, and the
|
|
11
|
+
* lifecycle exit. Keeping the UI on this interface lets the SAME UI later run
|
|
12
|
+
* against a socket-backed session (a daemon/client split) instead of the
|
|
13
|
+
* in-process workspace, with no UI changes.
|
|
14
|
+
*/
|
|
15
|
+
export interface HostSession {
|
|
16
|
+
/** Register the callback fired with the active window's output chunks. */
|
|
17
|
+
onHostData(cb: (chunk: Buffer) => void): void;
|
|
18
|
+
/** Merge input (host keystrokes) into the active window. */
|
|
19
|
+
write(data: Uint8Array | string): void;
|
|
20
|
+
/** Resize the session (host is authoritative) to `cols` x `rows`. */
|
|
21
|
+
resize(cols: number, rows: number): void;
|
|
22
|
+
/** Recent-output snapshot of the active window (initial/repaint screen). */
|
|
23
|
+
getReplay(): Uint8Array;
|
|
24
|
+
/** Register the callback fired whenever the window set changes. */
|
|
25
|
+
onWindowState(cb: (s: WindowStateBody) => void): void;
|
|
26
|
+
/** Current window state (windows + active index) for the tab bar. */
|
|
27
|
+
windowState(): WindowStateBody;
|
|
28
|
+
/** Create a new window and switch to it. */
|
|
29
|
+
newWindow(): void;
|
|
30
|
+
/** Switch to the next window (wraps). */
|
|
31
|
+
nextWindow(): void;
|
|
32
|
+
/** Switch to the previous window (wraps). */
|
|
33
|
+
prevWindow(): void;
|
|
34
|
+
/** Switch the active window to `i` (no-op if out of range). */
|
|
35
|
+
selectWindow(i: number): void;
|
|
36
|
+
/** Close the window at `i`. */
|
|
37
|
+
closeWindow(i: number): void;
|
|
38
|
+
/** Register the callback fired whenever the attached-viewer count changes. */
|
|
39
|
+
onViewersChange(cb: (n: number) => void): void;
|
|
40
|
+
/** Number of attached client viewers. */
|
|
41
|
+
viewerCount(): number;
|
|
42
|
+
/** Register a callback fired with each newly captured `[level] message` line. */
|
|
43
|
+
onLog(cb: (line: string) => void): void;
|
|
44
|
+
/** The captured agent-log ring buffer (tail of which the debug tab shows). */
|
|
45
|
+
getLogBuffer(): readonly string[];
|
|
46
|
+
/** The session URL once the relay assigns it, else null. */
|
|
47
|
+
getUrl(): string | null;
|
|
48
|
+
/** Register a callback fired when the session URL is set (or refreshed). */
|
|
49
|
+
onUrl(cb: (url: string) => void): void;
|
|
50
|
+
/** Register the callback fired once when the session ends. */
|
|
51
|
+
onExit(cb: (code: number | null, signal: string | null) => void): void;
|
|
52
|
+
/** Tear down any owned resources (e.g. release the captured-log sink). */
|
|
53
|
+
dispose(): void;
|
|
54
|
+
/** Detach without ending the session (unused for now). */
|
|
55
|
+
detach?(): void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The in-process {@link HostSession}: a thin adapter over a {@link SharedWorkspace}
|
|
59
|
+
* (for terminal/window/viewer/exit) that additionally OWNS the two sources the
|
|
60
|
+
* workspace doesn't provide:
|
|
61
|
+
*
|
|
62
|
+
* - the captured agent logs — it installs an {@link OutputHandler} log sink and
|
|
63
|
+
* appends `[level] message` lines to a capped ring buffer for the debug tab
|
|
64
|
+
* (this responsibility used to live in host-terminal); {@link dispose} clears
|
|
65
|
+
* the sink so late logs print to stdout normally again;
|
|
66
|
+
* - the session URL — {@link setUrl} stores it and notifies {@link onUrl}
|
|
67
|
+
* subscribers (the relay assigns it only after attach).
|
|
68
|
+
*/
|
|
69
|
+
export declare class LocalHostSession implements HostSession {
|
|
70
|
+
private readonly workspace;
|
|
71
|
+
private readonly logBuf;
|
|
72
|
+
private readonly logCbs;
|
|
73
|
+
private readonly urlCbs;
|
|
74
|
+
private url;
|
|
75
|
+
constructor(workspace: SharedWorkspace, _output: OutputHandler);
|
|
76
|
+
private pushLog;
|
|
77
|
+
onHostData(cb: (chunk: Buffer) => void): void;
|
|
78
|
+
write(data: Uint8Array | string): void;
|
|
79
|
+
resize(cols: number, rows: number): void;
|
|
80
|
+
getReplay(): Uint8Array;
|
|
81
|
+
onWindowState(cb: (s: WindowStateBody) => void): void;
|
|
82
|
+
windowState(): WindowStateBody;
|
|
83
|
+
newWindow(): void;
|
|
84
|
+
nextWindow(): void;
|
|
85
|
+
prevWindow(): void;
|
|
86
|
+
selectWindow(i: number): void;
|
|
87
|
+
closeWindow(i: number): void;
|
|
88
|
+
onViewersChange(cb: (n: number) => void): void;
|
|
89
|
+
viewerCount(): number;
|
|
90
|
+
onLog(cb: (line: string) => void): void;
|
|
91
|
+
getLogBuffer(): readonly string[];
|
|
92
|
+
getUrl(): string | null;
|
|
93
|
+
onUrl(cb: (url: string) => void): void;
|
|
94
|
+
/** Store the session URL (once the relay assigns it) and notify subscribers. */
|
|
95
|
+
setUrl(link: string): void;
|
|
96
|
+
onExit(cb: (code: number | null, signal: string | null) => void): void;
|
|
97
|
+
dispose(): void;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=host-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-session.d.ts","sourceRoot":"","sources":["../src/host-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { OutputHandler } from '@thenewlabs/entangle-utils';
|
|
2
|
+
/** Cap on the host's captured-log ring buffer backing the debug tab. */
|
|
3
|
+
const DEBUG_MAX_LINES = 1000;
|
|
4
|
+
/**
|
|
5
|
+
* The in-process {@link HostSession}: a thin adapter over a {@link SharedWorkspace}
|
|
6
|
+
* (for terminal/window/viewer/exit) that additionally OWNS the two sources the
|
|
7
|
+
* workspace doesn't provide:
|
|
8
|
+
*
|
|
9
|
+
* - the captured agent logs — it installs an {@link OutputHandler} log sink and
|
|
10
|
+
* appends `[level] message` lines to a capped ring buffer for the debug tab
|
|
11
|
+
* (this responsibility used to live in host-terminal); {@link dispose} clears
|
|
12
|
+
* the sink so late logs print to stdout normally again;
|
|
13
|
+
* - the session URL — {@link setUrl} stores it and notifies {@link onUrl}
|
|
14
|
+
* subscribers (the relay assigns it only after attach).
|
|
15
|
+
*/
|
|
16
|
+
export class LocalHostSession {
|
|
17
|
+
workspace;
|
|
18
|
+
logBuf = [];
|
|
19
|
+
logCbs = [];
|
|
20
|
+
urlCbs = [];
|
|
21
|
+
url = null;
|
|
22
|
+
// `output` is accepted for symmetry with the workspace's construction (and so
|
|
23
|
+
// a future session variant can log through it); the log sink it installs is a
|
|
24
|
+
// process-global static, so the instance itself isn't retained.
|
|
25
|
+
constructor(workspace, _output) {
|
|
26
|
+
this.workspace = workspace;
|
|
27
|
+
// Redirect all agent text-mode logs into the ring buffer instead of stdout
|
|
28
|
+
// (which the host UI owns) so they don't trample the terminal, and feed the
|
|
29
|
+
// debug tab. Cleared again in dispose().
|
|
30
|
+
OutputHandler.setLogSink((level, message, data) => this.pushLog(level, message, data));
|
|
31
|
+
}
|
|
32
|
+
pushLog(level, message, data) {
|
|
33
|
+
let line = `[${level}] ${message}`;
|
|
34
|
+
if (data !== undefined && data !== null) {
|
|
35
|
+
let extra;
|
|
36
|
+
try {
|
|
37
|
+
extra = typeof data === 'string' ? data : JSON.stringify(data);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
extra = String(data);
|
|
41
|
+
}
|
|
42
|
+
if (extra)
|
|
43
|
+
line += ` ${extra}`;
|
|
44
|
+
}
|
|
45
|
+
this.logBuf.push(line);
|
|
46
|
+
if (this.logBuf.length > DEBUG_MAX_LINES)
|
|
47
|
+
this.logBuf.splice(0, this.logBuf.length - DEBUG_MAX_LINES);
|
|
48
|
+
for (const cb of this.logCbs)
|
|
49
|
+
cb(line);
|
|
50
|
+
}
|
|
51
|
+
// --- terminal ------------------------------------------------------------
|
|
52
|
+
onHostData(cb) { this.workspace.onHostData(cb); }
|
|
53
|
+
write(data) { this.workspace.write(data); }
|
|
54
|
+
resize(cols, rows) { this.workspace.resize(cols, rows); }
|
|
55
|
+
getReplay() { return this.workspace.getReplay(); }
|
|
56
|
+
// --- windows -------------------------------------------------------------
|
|
57
|
+
onWindowState(cb) { this.workspace.onWindowState(cb); }
|
|
58
|
+
windowState() { return this.workspace.windowState(); }
|
|
59
|
+
newWindow() { this.workspace.newWindow(); }
|
|
60
|
+
nextWindow() { this.workspace.nextWindow(); }
|
|
61
|
+
prevWindow() { this.workspace.prevWindow(); }
|
|
62
|
+
selectWindow(i) { this.workspace.selectWindow(i); }
|
|
63
|
+
closeWindow(i) { this.workspace.closeWindow(i); }
|
|
64
|
+
// --- viewers -------------------------------------------------------------
|
|
65
|
+
onViewersChange(cb) { this.workspace.onViewersChange(cb); }
|
|
66
|
+
viewerCount() { return this.workspace.viewerCount(); }
|
|
67
|
+
// --- logs ----------------------------------------------------------------
|
|
68
|
+
onLog(cb) { this.logCbs.push(cb); }
|
|
69
|
+
getLogBuffer() { return this.logBuf; }
|
|
70
|
+
// --- url -----------------------------------------------------------------
|
|
71
|
+
getUrl() { return this.url; }
|
|
72
|
+
onUrl(cb) { this.urlCbs.push(cb); }
|
|
73
|
+
/** Store the session URL (once the relay assigns it) and notify subscribers. */
|
|
74
|
+
setUrl(link) {
|
|
75
|
+
this.url = link;
|
|
76
|
+
for (const cb of this.urlCbs)
|
|
77
|
+
cb(link);
|
|
78
|
+
}
|
|
79
|
+
// --- lifecycle -----------------------------------------------------------
|
|
80
|
+
onExit(cb) { this.workspace.onExit(cb); }
|
|
81
|
+
dispose() { OutputHandler.setLogSink(null); }
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=host-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-session.js","sourceRoot":"","sources":["../src/host-session.ts"],"names":[],"mappings":"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"}
|
package/dist/host-terminal.d.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import { OutputHandler } from '@thenewlabs/entangle-utils';
|
|
2
|
-
import type {
|
|
2
|
+
import type { HostSession } from './host-session.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* once the relay assigns the capability, so index.ts feeds it in later via
|
|
6
|
-
* {@link HostTerminalHandle.setUrl}; until then the host shows a brief
|
|
7
|
-
* "connecting…" screen and only goes live once the URL arrives.
|
|
8
|
-
*/
|
|
9
|
-
export interface HostTerminalHandle {
|
|
10
|
-
setUrl(link: string): void;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Wire the host's own terminal to the shared workspace.
|
|
4
|
+
* Wire the host's own terminal to the {@link HostSession}.
|
|
14
5
|
*
|
|
15
6
|
* On a real, big-enough terminal the host sees their shell rendered RAW at full
|
|
16
7
|
* width, using rows 1..(rows-1), with a blue tmux-style status bar pinned to the
|
|
@@ -18,6 +9,10 @@ export interface HostTerminalHandle {
|
|
|
18
9
|
* region keeps its scrolling clear of the bar; because output is passed through
|
|
19
10
|
* byte-for-byte (no VtGrid), resizes stay clean. Elsewhere (too small, or not a
|
|
20
11
|
* TTY) it falls back to raw pass-through.
|
|
12
|
+
*
|
|
13
|
+
* The session URL is only known once the relay assigns the capability, so the
|
|
14
|
+
* host subscribes to {@link HostSession.onUrl} and shows a brief "connecting…"
|
|
15
|
+
* screen until it arrives (index.ts sets it on the session).
|
|
21
16
|
*/
|
|
22
|
-
export declare function attachHostTerminal(
|
|
17
|
+
export declare function attachHostTerminal(session: HostSession, output: OutputHandler): void;
|
|
23
18
|
//# sourceMappingURL=host-terminal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-terminal.d.ts","sourceRoot":"","sources":["../src/host-terminal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"host-terminal.d.ts","sourceRoot":"","sources":["../src/host-terminal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAU3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkBrD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAWpF"}
|