@sentropic/remote-cli 0.0.3
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/attach-YI2AMS3B.js +22 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-E3GXAKJ3.js +258 -0
- package/dist/chunk-PG4CWJNC.js +197 -0
- package/dist/chunk-RLFQALHC.js +45 -0
- package/dist/chunk-VRTZ23J3.js +541 -0
- package/dist/chunk-Z277FK2S.js +294 -0
- package/dist/h2a-bridge-FPB5D3TB.js +20 -0
- package/dist/index.d.ts +730 -0
- package/dist/index.js +13966 -0
- package/dist/llm-gateway-runtime/index.d.ts +2 -0
- package/dist/llm-gateway-runtime/index.js +1295 -0
- package/dist/sync-status-WRQK4YRK.js +15 -0
- package/dist/workspace-O32VX2JG.js +38 -0
- package/dist/workspace-sync-incremental-W3SHAYHS.js +159 -0
- package/package.json +46 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_SESSION_TARGET,
|
|
3
|
+
authHeaders,
|
|
4
|
+
getTunnel,
|
|
5
|
+
resolveConfigPath
|
|
6
|
+
} from "./chunk-E3GXAKJ3.js";
|
|
7
|
+
|
|
8
|
+
// src/tunnel.ts
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
import {
|
|
11
|
+
existsSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
openSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
rmSync,
|
|
16
|
+
writeFileSync
|
|
17
|
+
} from "fs";
|
|
18
|
+
import { dirname, join } from "path";
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
function runtimeDir() {
|
|
21
|
+
return join(dirname(resolveConfigPath()), "run");
|
|
22
|
+
}
|
|
23
|
+
function pidFile() {
|
|
24
|
+
return join(runtimeDir(), "tunnel.pid");
|
|
25
|
+
}
|
|
26
|
+
function logFile() {
|
|
27
|
+
return join(runtimeDir(), "tunnel.log");
|
|
28
|
+
}
|
|
29
|
+
function expandHome(p) {
|
|
30
|
+
return p.startsWith("~") ? join(homedir(), p.slice(1)) : p;
|
|
31
|
+
}
|
|
32
|
+
async function isReachable(url, timeoutMs = 1500) {
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
35
|
+
try {
|
|
36
|
+
await fetch(`${url.replace(/\/$/, "")}/sessions`, {
|
|
37
|
+
signal: controller.signal,
|
|
38
|
+
headers: authHeaders()
|
|
39
|
+
});
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
} finally {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function tunnelPid() {
|
|
48
|
+
if (!existsSync(pidFile())) return void 0;
|
|
49
|
+
const pid = Number(readFileSync(pidFile(), "utf8").trim());
|
|
50
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
51
|
+
}
|
|
52
|
+
function tunnelAlive() {
|
|
53
|
+
const pid = tunnelPid();
|
|
54
|
+
if (pid === void 0) return false;
|
|
55
|
+
try {
|
|
56
|
+
process.kill(pid, 0);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function startTunnelProcess(tunnel) {
|
|
63
|
+
mkdirSync(runtimeDir(), { recursive: true });
|
|
64
|
+
const env = { ...process.env };
|
|
65
|
+
if (tunnel.kubeconfig) env.KUBECONFIG = expandHome(tunnel.kubeconfig);
|
|
66
|
+
const out = openSync(logFile(), "a");
|
|
67
|
+
const child = spawn(
|
|
68
|
+
"kubectl",
|
|
69
|
+
[
|
|
70
|
+
"-n",
|
|
71
|
+
tunnel.namespace,
|
|
72
|
+
"port-forward",
|
|
73
|
+
`svc/${tunnel.service}`,
|
|
74
|
+
`${tunnel.localPort}:${tunnel.remotePort}`
|
|
75
|
+
],
|
|
76
|
+
{ detached: true, stdio: ["ignore", out, out], env }
|
|
77
|
+
);
|
|
78
|
+
if (child.pid !== void 0) writeFileSync(pidFile(), String(child.pid));
|
|
79
|
+
child.unref();
|
|
80
|
+
}
|
|
81
|
+
function stopTunnel() {
|
|
82
|
+
const pid = tunnelPid();
|
|
83
|
+
rmSync(pidFile(), { force: true });
|
|
84
|
+
if (pid === void 0) return false;
|
|
85
|
+
try {
|
|
86
|
+
process.kill(pid);
|
|
87
|
+
return true;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function ensureConnected(url, stderr = process.stderr) {
|
|
93
|
+
const tunnel = getTunnel();
|
|
94
|
+
if (!tunnel) return;
|
|
95
|
+
if (await isReachable(url)) return;
|
|
96
|
+
const reopening = tunnelAlive();
|
|
97
|
+
stopTunnel();
|
|
98
|
+
stderr.write(
|
|
99
|
+
`[remote] control-plane unreachable \u2014 ${reopening ? "tunnel stale, reopening" : "opening tunnel"} (kubectl port-forward ${tunnel.service} :${tunnel.localPort})
|
|
100
|
+
`
|
|
101
|
+
);
|
|
102
|
+
startTunnelProcess(tunnel);
|
|
103
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
104
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
105
|
+
if (await isReachable(url)) {
|
|
106
|
+
stderr.write(`[remote] tunnel up
|
|
107
|
+
`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
throw new Error(
|
|
112
|
+
`tunnel started but ${url} is still unreachable after 15s \u2014 check the kubeconfig/namespace/service (logs: ${logFile()})`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/attach.ts
|
|
117
|
+
var DEFAULT_INPUT_RETRY = {
|
|
118
|
+
maxAttempts: 6,
|
|
119
|
+
baseDelayMs: 200,
|
|
120
|
+
maxDelayMs: 1600
|
|
121
|
+
};
|
|
122
|
+
function joinUrl(base, path) {
|
|
123
|
+
return `${base.replace(/\/$/, "")}${path}`;
|
|
124
|
+
}
|
|
125
|
+
function parseSseEvents(buffer) {
|
|
126
|
+
const events = [];
|
|
127
|
+
const chunks = buffer.split("\n\n");
|
|
128
|
+
const rest = chunks.pop() ?? "";
|
|
129
|
+
for (const chunk of chunks) {
|
|
130
|
+
if (!chunk.trim()) continue;
|
|
131
|
+
let eventName;
|
|
132
|
+
const dataLines = [];
|
|
133
|
+
for (const line of chunk.split("\n")) {
|
|
134
|
+
if (line.startsWith("event:")) eventName = line.slice(6).trim();
|
|
135
|
+
else if (line.startsWith("data:")) dataLines.push(line.slice(5).trim());
|
|
136
|
+
}
|
|
137
|
+
if (dataLines.length === 0) continue;
|
|
138
|
+
const entry = {
|
|
139
|
+
data: dataLines.join("\n")
|
|
140
|
+
};
|
|
141
|
+
if (eventName !== void 0) entry.event = eventName;
|
|
142
|
+
events.push(entry);
|
|
143
|
+
}
|
|
144
|
+
return { events, rest };
|
|
145
|
+
}
|
|
146
|
+
async function attach(options) {
|
|
147
|
+
const baseUrl = options.baseUrl;
|
|
148
|
+
const sessionId = options.sessionId;
|
|
149
|
+
const stdin = options.stdin ?? process.stdin;
|
|
150
|
+
const stdout = options.stdout ?? process.stdout;
|
|
151
|
+
const stderr = options.stderr ?? process.stderr;
|
|
152
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
153
|
+
const controller = new AbortController();
|
|
154
|
+
const sseResponse = await doFetch(
|
|
155
|
+
joinUrl(baseUrl, `/sessions/${sessionId}/events`),
|
|
156
|
+
{
|
|
157
|
+
headers: { accept: "text/event-stream", ...authHeaders() },
|
|
158
|
+
signal: controller.signal
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
if (!sseResponse.ok || !sseResponse.body) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`attach: SSE stream returned ${sseResponse.status} ${sseResponse.statusText}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
let reader = sseResponse.body.getReader();
|
|
167
|
+
const decoder = new TextDecoder("utf-8");
|
|
168
|
+
let finishedResolve;
|
|
169
|
+
const finished = new Promise((resolve) => {
|
|
170
|
+
finishedResolve = resolve;
|
|
171
|
+
});
|
|
172
|
+
const wasRaw = stdin.isTTY ? Boolean(stdin.isRaw) : false;
|
|
173
|
+
if (stdin.isTTY) stdin.setRawMode?.(true);
|
|
174
|
+
stdin.resume();
|
|
175
|
+
if (stdin.isTTY) {
|
|
176
|
+
stderr.write(
|
|
177
|
+
"[remote] press Ctrl+P Ctrl+Q to detach (session keeps running)\n"
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
const retry = { ...DEFAULT_INPUT_RETRY, ...options.inputRetry ?? {} };
|
|
181
|
+
let aborted = false;
|
|
182
|
+
let queueTail = Promise.resolve();
|
|
183
|
+
const postInput = async (data) => {
|
|
184
|
+
for (let attempt = 1; attempt <= retry.maxAttempts; attempt++) {
|
|
185
|
+
if (aborted) return;
|
|
186
|
+
try {
|
|
187
|
+
const response = await doFetch(
|
|
188
|
+
joinUrl(baseUrl, `/sessions/${sessionId}/terminal/input`),
|
|
189
|
+
{
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: { "content-type": "application/json", ...authHeaders() },
|
|
192
|
+
body: JSON.stringify({
|
|
193
|
+
terminalId: "operator",
|
|
194
|
+
data,
|
|
195
|
+
encoding: "utf8"
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
if (response.ok) return;
|
|
200
|
+
if (response.status < 500) {
|
|
201
|
+
stderr.write(
|
|
202
|
+
`[remote] input rejected (${response.status} ${response.statusText}); not retried
|
|
203
|
+
`
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (aborted) return;
|
|
209
|
+
if (attempt === retry.maxAttempts) {
|
|
210
|
+
stderr.write(
|
|
211
|
+
`[remote] input abandoned after ${retry.maxAttempts} attempts: ${String(error)}
|
|
212
|
+
`
|
|
213
|
+
);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (attempt < retry.maxAttempts) {
|
|
218
|
+
const delay = Math.min(
|
|
219
|
+
retry.maxDelayMs,
|
|
220
|
+
retry.baseDelayMs * 2 ** (attempt - 1)
|
|
221
|
+
);
|
|
222
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
stderr.write(
|
|
226
|
+
`[remote] input abandoned after ${retry.maxAttempts} attempts
|
|
227
|
+
`
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
const enqueueInput = (data) => {
|
|
231
|
+
queueTail = queueTail.then(() => postInput(data));
|
|
232
|
+
};
|
|
233
|
+
const sendResize = async () => {
|
|
234
|
+
const columns = Math.max(1, stdout.columns ?? 80);
|
|
235
|
+
const rows = Math.max(1, stdout.rows ?? 24);
|
|
236
|
+
try {
|
|
237
|
+
await doFetch(
|
|
238
|
+
joinUrl(baseUrl, `/sessions/${sessionId}/terminal/resize`),
|
|
239
|
+
{
|
|
240
|
+
method: "POST",
|
|
241
|
+
headers: { "content-type": "application/json", ...authHeaders() },
|
|
242
|
+
body: JSON.stringify({
|
|
243
|
+
terminalId: "operator",
|
|
244
|
+
columns,
|
|
245
|
+
rows
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
stderr.write(`[remote] resize failed: ${String(error)}
|
|
251
|
+
`);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const CTRL_P = 16;
|
|
255
|
+
const CTRL_Q = 17;
|
|
256
|
+
const DETACH_TIMEOUT_MS = 1500;
|
|
257
|
+
let detachPending = false;
|
|
258
|
+
let detachTimer = null;
|
|
259
|
+
const onStdin = (chunk) => {
|
|
260
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
261
|
+
const passthrough = [];
|
|
262
|
+
const flushPassthrough = () => {
|
|
263
|
+
if (passthrough.length === 0) return;
|
|
264
|
+
enqueueInput(Buffer.from(passthrough).toString("utf8"));
|
|
265
|
+
passthrough.length = 0;
|
|
266
|
+
};
|
|
267
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
268
|
+
const byte = bytes[i];
|
|
269
|
+
if (detachPending) {
|
|
270
|
+
if (byte === CTRL_Q) {
|
|
271
|
+
detachPending = false;
|
|
272
|
+
if (detachTimer) {
|
|
273
|
+
clearTimeout(detachTimer);
|
|
274
|
+
detachTimer = null;
|
|
275
|
+
}
|
|
276
|
+
flushPassthrough();
|
|
277
|
+
if (stdin.isTTY) stderr.write("\n[remote] detached\n");
|
|
278
|
+
void close();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
detachPending = false;
|
|
282
|
+
if (detachTimer) {
|
|
283
|
+
clearTimeout(detachTimer);
|
|
284
|
+
detachTimer = null;
|
|
285
|
+
}
|
|
286
|
+
passthrough.push(CTRL_P, byte);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (byte === CTRL_P) {
|
|
290
|
+
flushPassthrough();
|
|
291
|
+
detachPending = true;
|
|
292
|
+
detachTimer = setTimeout(() => {
|
|
293
|
+
if (!detachPending) return;
|
|
294
|
+
detachPending = false;
|
|
295
|
+
detachTimer = null;
|
|
296
|
+
enqueueInput("");
|
|
297
|
+
}, DETACH_TIMEOUT_MS);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
passthrough.push(byte);
|
|
301
|
+
}
|
|
302
|
+
flushPassthrough();
|
|
303
|
+
};
|
|
304
|
+
const onResize = () => {
|
|
305
|
+
void sendResize();
|
|
306
|
+
};
|
|
307
|
+
stdin.on("data", onStdin);
|
|
308
|
+
stdout.on?.("resize", onResize);
|
|
309
|
+
let closed = false;
|
|
310
|
+
const close = async () => {
|
|
311
|
+
if (closed) return;
|
|
312
|
+
closed = true;
|
|
313
|
+
if (detachTimer) {
|
|
314
|
+
clearTimeout(detachTimer);
|
|
315
|
+
detachTimer = null;
|
|
316
|
+
}
|
|
317
|
+
stdin.off?.("data", onStdin);
|
|
318
|
+
await Promise.race([
|
|
319
|
+
queueTail.catch(() => {
|
|
320
|
+
}),
|
|
321
|
+
new Promise((resolve) => setTimeout(resolve, 500).unref?.())
|
|
322
|
+
]);
|
|
323
|
+
aborted = true;
|
|
324
|
+
controller.abort();
|
|
325
|
+
stdout.off?.("resize", onResize);
|
|
326
|
+
if (stdin.isTTY) stdin.setRawMode?.(wasRaw);
|
|
327
|
+
stdin.pause();
|
|
328
|
+
finishedResolve();
|
|
329
|
+
};
|
|
330
|
+
(async () => {
|
|
331
|
+
let buffer = "";
|
|
332
|
+
for (; ; ) {
|
|
333
|
+
try {
|
|
334
|
+
for (; ; ) {
|
|
335
|
+
const { value, done } = await reader.read();
|
|
336
|
+
if (done) break;
|
|
337
|
+
buffer += decoder.decode(value, { stream: true });
|
|
338
|
+
const { events, rest } = parseSseEvents(buffer);
|
|
339
|
+
buffer = rest;
|
|
340
|
+
for (const ev of events) {
|
|
341
|
+
if (ev.event && ev.event !== "terminal.output" && ev.event !== "terminal.exited")
|
|
342
|
+
continue;
|
|
343
|
+
try {
|
|
344
|
+
const envelope = JSON.parse(ev.data);
|
|
345
|
+
if (envelope.type === "terminal.output") {
|
|
346
|
+
const payload = envelope.payload;
|
|
347
|
+
if (typeof payload.data === "string")
|
|
348
|
+
stdout.write(payload.data);
|
|
349
|
+
} else if (envelope.type === "terminal.exited") {
|
|
350
|
+
await close();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
359
|
+
if (closed) return;
|
|
360
|
+
stderr.write("\r\n[remote] connection lost \u2014 reconnecting\u2026\r\n");
|
|
361
|
+
buffer = "";
|
|
362
|
+
let reopened = false;
|
|
363
|
+
for (let attempt = 0; attempt < 120 && !closed; attempt++) {
|
|
364
|
+
try {
|
|
365
|
+
await ensureConnected(baseUrl, stderr);
|
|
366
|
+
const resp = await doFetch(
|
|
367
|
+
joinUrl(baseUrl, `/sessions/${sessionId}/events`),
|
|
368
|
+
{
|
|
369
|
+
headers: { accept: "text/event-stream", ...authHeaders() },
|
|
370
|
+
signal: controller.signal
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
if (resp.status === 404) {
|
|
374
|
+
stderr.write("[remote] session ended\r\n");
|
|
375
|
+
await close();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (resp.ok && resp.body) {
|
|
379
|
+
reader = resp.body.getReader();
|
|
380
|
+
reopened = true;
|
|
381
|
+
stderr.write("[remote] reconnected\r\n");
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
if (closed) return;
|
|
387
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
388
|
+
}
|
|
389
|
+
if (!reopened) {
|
|
390
|
+
await close();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
})();
|
|
395
|
+
return { close, finished };
|
|
396
|
+
}
|
|
397
|
+
async function listRemoteSessions(baseUrl, fetchImpl = fetch) {
|
|
398
|
+
const response = await fetchImpl(joinUrl(baseUrl, "/sessions"), {
|
|
399
|
+
headers: { ...authHeaders() }
|
|
400
|
+
});
|
|
401
|
+
if (!response.ok) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`listRemoteSessions: ${response.status} ${response.statusText}`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
const json = await response.json();
|
|
407
|
+
return json.sessions;
|
|
408
|
+
}
|
|
409
|
+
async function stopRemoteSession(baseUrl, sessionId, reason, fetchImpl = fetch) {
|
|
410
|
+
const body = {};
|
|
411
|
+
if (reason) body.reason = reason;
|
|
412
|
+
const response = await fetchImpl(
|
|
413
|
+
joinUrl(baseUrl, `/sessions/${sessionId}/stop`),
|
|
414
|
+
{
|
|
415
|
+
method: "POST",
|
|
416
|
+
headers: { "content-type": "application/json", ...authHeaders() },
|
|
417
|
+
body: JSON.stringify(body)
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
if (!response.ok) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
`stopRemoteSession: ${response.status} ${response.statusText}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
const json = await response.json();
|
|
426
|
+
return { accepted: json.accepted };
|
|
427
|
+
}
|
|
428
|
+
async function getRemoteSession(baseUrl, sessionId, fetchImpl = fetch) {
|
|
429
|
+
const response = await fetchImpl(joinUrl(baseUrl, `/sessions/${sessionId}`), {
|
|
430
|
+
headers: { ...authHeaders() }
|
|
431
|
+
});
|
|
432
|
+
if (!response.ok) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
`getRemoteSession: ${response.status} ${response.statusText}`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
return await response.json();
|
|
438
|
+
}
|
|
439
|
+
async function refreshRemoteSession(baseUrl, sessionId, credentials, fetchImpl = fetch, displayName) {
|
|
440
|
+
const path = `/sessions/${sessionId}/credentials`;
|
|
441
|
+
const url = displayName && displayName.length > 0 ? `${joinUrl(baseUrl, path)}?displayName=${encodeURIComponent(displayName)}` : joinUrl(baseUrl, path);
|
|
442
|
+
const response = await fetchImpl(url, {
|
|
443
|
+
method: "POST",
|
|
444
|
+
headers: { "content-type": "application/json", ...authHeaders() },
|
|
445
|
+
body: JSON.stringify(credentials)
|
|
446
|
+
});
|
|
447
|
+
if (!response.ok) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`refreshRemoteSession: ${response.status} ${response.statusText}`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const json = await response.json();
|
|
453
|
+
return { sessionId: json.sessionId, accepted: json.accepted };
|
|
454
|
+
}
|
|
455
|
+
async function renameRemoteSession(baseUrl, sessionId, displayName, fetchImpl = fetch) {
|
|
456
|
+
const response = await fetchImpl(joinUrl(baseUrl, `/sessions/${sessionId}`), {
|
|
457
|
+
method: "PATCH",
|
|
458
|
+
headers: { "content-type": "application/json", ...authHeaders() },
|
|
459
|
+
body: JSON.stringify({ displayName })
|
|
460
|
+
});
|
|
461
|
+
if (!response.ok) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
`renameRemoteSession: ${response.status} ${response.statusText}`
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
const json = await response.json();
|
|
467
|
+
return {
|
|
468
|
+
sessionId: json.sessionId,
|
|
469
|
+
displayName: json.displayName,
|
|
470
|
+
accepted: json.accepted
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
async function sessionTerminalHealth(baseUrl, sessionId, fetchImpl = fetch) {
|
|
474
|
+
try {
|
|
475
|
+
const response = await fetchImpl(
|
|
476
|
+
joinUrl(baseUrl, `/sessions/${sessionId}/terminal/input`),
|
|
477
|
+
{
|
|
478
|
+
method: "POST",
|
|
479
|
+
headers: { "content-type": "application/json", ...authHeaders() },
|
|
480
|
+
body: JSON.stringify({
|
|
481
|
+
terminalId: sessionId,
|
|
482
|
+
data: "",
|
|
483
|
+
encoding: "utf8"
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
);
|
|
487
|
+
if (response.status === 202) return "ready";
|
|
488
|
+
if (response.status === 503) return "agent-down";
|
|
489
|
+
return "unknown";
|
|
490
|
+
} catch {
|
|
491
|
+
return "unknown";
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async function createRemoteSession(baseUrl, body, fetchImpl = fetch) {
|
|
495
|
+
const payload = {
|
|
496
|
+
profile: body.profile,
|
|
497
|
+
target: body.target ?? DEFAULT_SESSION_TARGET
|
|
498
|
+
};
|
|
499
|
+
if (body.displayName) payload.displayName = body.displayName;
|
|
500
|
+
if (body.workspaceSync) payload.workspaceSync = true;
|
|
501
|
+
if (body.workspaceExport) payload.workspaceExport = true;
|
|
502
|
+
if (body.workspaceId) payload.workspaceId = body.workspaceId;
|
|
503
|
+
if (body.workspacePath) payload.workspacePath = body.workspacePath;
|
|
504
|
+
if (body.home) payload.home = body.home;
|
|
505
|
+
if (body.agentImage) payload.agentImage = body.agentImage;
|
|
506
|
+
if (body.resume !== void 0 || (body.startupArgs?.length ?? 0) > 0 || body.metadata !== void 0) {
|
|
507
|
+
const metadata = {
|
|
508
|
+
...body.metadata ?? {},
|
|
509
|
+
...body.resume !== void 0 ? { resume: body.resume } : {},
|
|
510
|
+
...body.startupArgs?.length ? { startup: { args: body.startupArgs } } : {}
|
|
511
|
+
};
|
|
512
|
+
payload.metadata = metadata;
|
|
513
|
+
}
|
|
514
|
+
if (body.credentials && Object.keys(body.credentials).length > 0)
|
|
515
|
+
payload.credentials = body.credentials;
|
|
516
|
+
const response = await fetchImpl(joinUrl(baseUrl, "/sessions"), {
|
|
517
|
+
method: "POST",
|
|
518
|
+
headers: { "content-type": "application/json", ...authHeaders() },
|
|
519
|
+
body: JSON.stringify(payload)
|
|
520
|
+
});
|
|
521
|
+
if (!response.ok) {
|
|
522
|
+
throw new Error(
|
|
523
|
+
`createRemoteSession: ${response.status} ${response.statusText}`
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
const json = await response.json();
|
|
527
|
+
return { id: json.session.id };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export {
|
|
531
|
+
stopTunnel,
|
|
532
|
+
ensureConnected,
|
|
533
|
+
attach,
|
|
534
|
+
listRemoteSessions,
|
|
535
|
+
stopRemoteSession,
|
|
536
|
+
getRemoteSession,
|
|
537
|
+
refreshRemoteSession,
|
|
538
|
+
renameRemoteSession,
|
|
539
|
+
sessionTerminalHealth,
|
|
540
|
+
createRemoteSession
|
|
541
|
+
};
|