skydive-cli 0.1.0-beta.189 → 0.1.0-beta.199
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/js/bin.mjs
CHANGED
|
@@ -31,7 +31,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
31
31
|
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region package.json
|
|
34
|
-
var version$1 = "0.1.0-beta.
|
|
34
|
+
var version$1 = "0.1.0-beta.199";
|
|
35
35
|
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region src/types.ts
|
|
@@ -2152,7 +2152,7 @@ const chatCommand = {
|
|
|
2152
2152
|
printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
|
|
2153
2153
|
process.exit(1);
|
|
2154
2154
|
}
|
|
2155
|
-
const { runChat } = await import("./boot-
|
|
2155
|
+
const { runChat } = await import("./boot-DGdoNlG2.mjs");
|
|
2156
2156
|
await runChat({
|
|
2157
2157
|
appUrl,
|
|
2158
2158
|
sessionToken: session.value.sessionToken,
|
|
@@ -3330,7 +3330,7 @@ const switchCommand = {
|
|
|
3330
3330
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
3331
3331
|
process.exit(1);
|
|
3332
3332
|
}
|
|
3333
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
3333
|
+
const { runWorkspacePicker } = await import("./boot-DGdoNlG2.mjs");
|
|
3334
3334
|
await runWorkspacePicker(session);
|
|
3335
3335
|
return;
|
|
3336
3336
|
}
|
|
@@ -62,6 +62,7 @@ const useStore = create((set, get) => ({
|
|
|
62
62
|
themeId: DEFAULT_THEME_ID.dark,
|
|
63
63
|
themeLocked: false,
|
|
64
64
|
appUrl: null,
|
|
65
|
+
sessionToken: null,
|
|
65
66
|
workspaceName: null,
|
|
66
67
|
rest: null,
|
|
67
68
|
promptHistoryPath: null,
|
|
@@ -98,8 +99,9 @@ const useStore = create((set, get) => ({
|
|
|
98
99
|
themeLocked: true
|
|
99
100
|
});
|
|
100
101
|
},
|
|
101
|
-
setClients: ({ appUrl, rest, promptHistoryPath }) => set({
|
|
102
|
+
setClients: ({ appUrl, sessionToken, rest, promptHistoryPath }) => set({
|
|
102
103
|
appUrl,
|
|
104
|
+
sessionToken,
|
|
103
105
|
rest,
|
|
104
106
|
promptHistoryPath
|
|
105
107
|
}),
|
|
@@ -534,7 +536,7 @@ var PortalClient = class {
|
|
|
534
536
|
this.refreshDevice();
|
|
535
537
|
});
|
|
536
538
|
ws.on("message", (data, isBinary) => {
|
|
537
|
-
if (isBinary) jobs.handleFrame(toBuffer(data));
|
|
539
|
+
if (isBinary) jobs.handleFrame(toBuffer$1(data));
|
|
538
540
|
});
|
|
539
541
|
ws.on("error", (err) => {
|
|
540
542
|
this.error = errorMessage(err);
|
|
@@ -585,7 +587,7 @@ var PortalClient = class {
|
|
|
585
587
|
});
|
|
586
588
|
}
|
|
587
589
|
};
|
|
588
|
-
function toBuffer(data) {
|
|
590
|
+
function toBuffer$1(data) {
|
|
589
591
|
if (Buffer.isBuffer(data)) return data;
|
|
590
592
|
if (Array.isArray(data)) return Buffer.concat(data);
|
|
591
593
|
return Buffer.from(data);
|
|
@@ -5071,6 +5073,203 @@ function formatShellContext(command, output, exitCode) {
|
|
|
5071
5073
|
].join("\n");
|
|
5072
5074
|
}
|
|
5073
5075
|
|
|
5076
|
+
//#endregion
|
|
5077
|
+
//#region src/chat/sandbox/client.ts
|
|
5078
|
+
/**
|
|
5079
|
+
* Client for the api's `/api/v1/sandbox/stream` WebSocket: relays a `/sandbox`
|
|
5080
|
+
* session into the agent's own sandbox — a live interactive PTY or a one-shot
|
|
5081
|
+
* streamed exec. Mirrors the transport shape of `portal/client.ts`, but dials a
|
|
5082
|
+
* different endpoint and speaks the sandbox-stream frame protocol (see the
|
|
5083
|
+
* server's sandbox-stream-ws.ts).
|
|
5084
|
+
*/
|
|
5085
|
+
const T = {
|
|
5086
|
+
DATA: 1,
|
|
5087
|
+
EXIT: 2,
|
|
5088
|
+
ERROR: 3,
|
|
5089
|
+
INPUT: 16,
|
|
5090
|
+
RESIZE: 17
|
|
5091
|
+
};
|
|
5092
|
+
function wsBase(appUrl) {
|
|
5093
|
+
const base = appUrl.replace(/\/+$/, "");
|
|
5094
|
+
if (base.startsWith("https://")) return `wss://${base.slice(8)}`;
|
|
5095
|
+
if (base.startsWith("http://")) return `ws://${base.slice(7)}`;
|
|
5096
|
+
return `wss://${base}`;
|
|
5097
|
+
}
|
|
5098
|
+
/**
|
|
5099
|
+
* A connected sandbox-stream session. Construct via `openSandboxStream`. Carries
|
|
5100
|
+
* the write side (keystrokes / resize for pty mode) and teardown.
|
|
5101
|
+
*/
|
|
5102
|
+
var SandboxStream = class SandboxStream {
|
|
5103
|
+
ws;
|
|
5104
|
+
closed = false;
|
|
5105
|
+
constructor(ws, onEvent) {
|
|
5106
|
+
this.ws = ws;
|
|
5107
|
+
ws.on("message", (data, isBinary) => {
|
|
5108
|
+
if (!isBinary) return;
|
|
5109
|
+
const buf = toBuffer(data);
|
|
5110
|
+
if (buf.length === 0) return;
|
|
5111
|
+
const body = buf.subarray(1);
|
|
5112
|
+
switch (buf[0]) {
|
|
5113
|
+
case T.DATA:
|
|
5114
|
+
onEvent({
|
|
5115
|
+
type: "data",
|
|
5116
|
+
bytes: new Uint8Array(body)
|
|
5117
|
+
});
|
|
5118
|
+
break;
|
|
5119
|
+
case T.EXIT:
|
|
5120
|
+
onEvent({
|
|
5121
|
+
type: "exit",
|
|
5122
|
+
code: body.length >= 4 ? body.readInt32BE(0) : 0
|
|
5123
|
+
});
|
|
5124
|
+
break;
|
|
5125
|
+
case T.ERROR:
|
|
5126
|
+
onEvent({
|
|
5127
|
+
type: "error",
|
|
5128
|
+
message: body.toString("utf8")
|
|
5129
|
+
});
|
|
5130
|
+
break;
|
|
5131
|
+
}
|
|
5132
|
+
});
|
|
5133
|
+
ws.on("close", () => {
|
|
5134
|
+
this.closed = true;
|
|
5135
|
+
onEvent({ type: "close" });
|
|
5136
|
+
});
|
|
5137
|
+
ws.on("error", () => {});
|
|
5138
|
+
}
|
|
5139
|
+
/** Feed keystroke bytes to the pty stdin. */
|
|
5140
|
+
sendInput(data) {
|
|
5141
|
+
if (this.closed || this.ws.readyState !== WebSocket.OPEN) return;
|
|
5142
|
+
this.ws.send(Buffer.concat([Buffer.from([T.INPUT]), Buffer.from(data)]));
|
|
5143
|
+
}
|
|
5144
|
+
/** Notify the pty of a terminal resize. */
|
|
5145
|
+
resize(cols, rows) {
|
|
5146
|
+
if (this.closed || this.ws.readyState !== WebSocket.OPEN) return;
|
|
5147
|
+
const b = Buffer.allocUnsafe(5);
|
|
5148
|
+
b[0] = T.RESIZE;
|
|
5149
|
+
b.writeUInt16BE(cols & 65535, 1);
|
|
5150
|
+
b.writeUInt16BE(rows & 65535, 3);
|
|
5151
|
+
this.ws.send(b);
|
|
5152
|
+
}
|
|
5153
|
+
close() {
|
|
5154
|
+
this.closed = true;
|
|
5155
|
+
this.ws.close();
|
|
5156
|
+
}
|
|
5157
|
+
/** Open a stream. `mode` is 'pty' (interactive) or 'exec' (one-shot). */
|
|
5158
|
+
static open(opts) {
|
|
5159
|
+
const url = new URL(`${wsBase(opts.appUrl)}/api/v1/sandbox/stream`);
|
|
5160
|
+
url.searchParams.set("agentId", opts.agentId);
|
|
5161
|
+
url.searchParams.set("mode", opts.mode);
|
|
5162
|
+
if (opts.mode === "pty") {
|
|
5163
|
+
url.searchParams.set("cols", String(opts.cols));
|
|
5164
|
+
url.searchParams.set("rows", String(opts.rows));
|
|
5165
|
+
} else url.searchParams.set("command", opts.command);
|
|
5166
|
+
return new SandboxStream(new WebSocket(url.toString(), { headers: { authorization: `Bearer ${opts.sessionToken}` } }), opts.onEvent);
|
|
5167
|
+
}
|
|
5168
|
+
};
|
|
5169
|
+
function toBuffer(data) {
|
|
5170
|
+
if (Buffer.isBuffer(data)) return data;
|
|
5171
|
+
if (Array.isArray(data)) return Buffer.concat(data);
|
|
5172
|
+
return Buffer.from(data);
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5175
|
+
//#endregion
|
|
5176
|
+
//#region src/chat/sandbox/pty-session.ts
|
|
5177
|
+
/**
|
|
5178
|
+
* Runs a live interactive PTY into the agent's sandbox as a full-screen raw
|
|
5179
|
+
* terminal, by SUSPENDING the opentui renderer for the duration and doing a
|
|
5180
|
+
* direct byte passthrough:
|
|
5181
|
+
*
|
|
5182
|
+
* local stdin → INPUT frames → sandbox pty
|
|
5183
|
+
* sandbox pty → DATA frames → local stdout
|
|
5184
|
+
*
|
|
5185
|
+
* This is the correct shape for a real terminal: we don't reimplement a
|
|
5186
|
+
* terminal emulator, we hand the actual TTY to the remote shell. On exit
|
|
5187
|
+
* (the shell exits, the socket drops, or the user hits the detach key) we
|
|
5188
|
+
* restore the terminal and resume the TUI.
|
|
5189
|
+
*
|
|
5190
|
+
* Detach key: Ctrl-] (0x1d), the classic telnet/ssh escape — leaves the shell
|
|
5191
|
+
* running server-side is NOT a goal here; detaching closes the session.
|
|
5192
|
+
*/
|
|
5193
|
+
const DETACH_BYTE = 29;
|
|
5194
|
+
async function runPtySession(opts) {
|
|
5195
|
+
const { renderer } = opts;
|
|
5196
|
+
const stdin = renderer.stdin;
|
|
5197
|
+
const stdout = process.stdout;
|
|
5198
|
+
const size = () => ({
|
|
5199
|
+
cols: stdout.columns ?? 80,
|
|
5200
|
+
rows: stdout.rows ?? 24
|
|
5201
|
+
});
|
|
5202
|
+
renderer.suspend();
|
|
5203
|
+
return await new Promise((resolve) => {
|
|
5204
|
+
let settled = false;
|
|
5205
|
+
const initial = size();
|
|
5206
|
+
const stream = SandboxStream.open({
|
|
5207
|
+
mode: "pty",
|
|
5208
|
+
appUrl: opts.appUrl,
|
|
5209
|
+
sessionToken: opts.sessionToken,
|
|
5210
|
+
agentId: opts.agentId,
|
|
5211
|
+
cols: initial.cols,
|
|
5212
|
+
rows: initial.rows,
|
|
5213
|
+
onEvent: (e) => {
|
|
5214
|
+
switch (e.type) {
|
|
5215
|
+
case "data":
|
|
5216
|
+
stdout.write(e.bytes);
|
|
5217
|
+
break;
|
|
5218
|
+
case "error":
|
|
5219
|
+
stdout.write(`\r\n\x1b[31m${e.message}\x1b[0m\r\n`);
|
|
5220
|
+
finish({
|
|
5221
|
+
reason: "error",
|
|
5222
|
+
code: 1
|
|
5223
|
+
});
|
|
5224
|
+
break;
|
|
5225
|
+
case "exit":
|
|
5226
|
+
finish({
|
|
5227
|
+
reason: "exit",
|
|
5228
|
+
code: e.code
|
|
5229
|
+
});
|
|
5230
|
+
break;
|
|
5231
|
+
case "close":
|
|
5232
|
+
finish({
|
|
5233
|
+
reason: "exit",
|
|
5234
|
+
code: 0
|
|
5235
|
+
});
|
|
5236
|
+
break;
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
});
|
|
5240
|
+
const onStdin = (chunk) => {
|
|
5241
|
+
if (chunk.length === 1 && chunk[0] === DETACH_BYTE) {
|
|
5242
|
+
finish({
|
|
5243
|
+
reason: "detach",
|
|
5244
|
+
code: 0
|
|
5245
|
+
});
|
|
5246
|
+
return;
|
|
5247
|
+
}
|
|
5248
|
+
stream.sendInput(new Uint8Array(chunk));
|
|
5249
|
+
};
|
|
5250
|
+
const onResize = () => {
|
|
5251
|
+
const s = size();
|
|
5252
|
+
stream.resize(s.cols, s.rows);
|
|
5253
|
+
};
|
|
5254
|
+
const wasRaw = stdin.isRaw ?? false;
|
|
5255
|
+
stdin.setRawMode?.(true);
|
|
5256
|
+
stdin.resume();
|
|
5257
|
+
stdin.on("data", onStdin);
|
|
5258
|
+
stdout.on("resize", onResize);
|
|
5259
|
+
stream.resize(initial.cols, initial.rows);
|
|
5260
|
+
function finish(result) {
|
|
5261
|
+
if (settled) return;
|
|
5262
|
+
settled = true;
|
|
5263
|
+
stdin.off("data", onStdin);
|
|
5264
|
+
stdout.off("resize", onResize);
|
|
5265
|
+
stdin.setRawMode?.(wasRaw);
|
|
5266
|
+
stream.close();
|
|
5267
|
+
renderer.resume();
|
|
5268
|
+
resolve(result);
|
|
5269
|
+
}
|
|
5270
|
+
});
|
|
5271
|
+
}
|
|
5272
|
+
|
|
5074
5273
|
//#endregion
|
|
5075
5274
|
//#region src/chat/tui/screens/chat.tsx
|
|
5076
5275
|
/**
|
|
@@ -5133,6 +5332,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5133
5332
|
const rest = useStore((s) => s.rest);
|
|
5134
5333
|
const history = usePromptHistory({ filePath: useStore((s) => s.promptHistoryPath) });
|
|
5135
5334
|
const appUrl = useStore((s) => s.appUrl);
|
|
5335
|
+
const sessionToken = useStore((s) => s.sessionToken);
|
|
5136
5336
|
const goTo = useStore((s) => s.goTo);
|
|
5137
5337
|
const setChatTitle = useStore((s) => s.setChatTitle);
|
|
5138
5338
|
const portal = useStore((s) => s.portal);
|
|
@@ -5591,6 +5791,65 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5591
5791
|
}
|
|
5592
5792
|
sendContent(formatShellContext(command, captured, exitCode), [], { echo: false });
|
|
5593
5793
|
}, [sendContent]);
|
|
5794
|
+
const runSandboxExec = useCallback((command) => {
|
|
5795
|
+
if (!appUrl || !sessionToken) return;
|
|
5796
|
+
const id = crypto.randomUUID();
|
|
5797
|
+
setItems((prev) => [...prev, {
|
|
5798
|
+
kind: "shell",
|
|
5799
|
+
id,
|
|
5800
|
+
command: `☁ ${command}`,
|
|
5801
|
+
output: "",
|
|
5802
|
+
exit: null
|
|
5803
|
+
}]);
|
|
5804
|
+
const dec = new TextDecoder();
|
|
5805
|
+
const append = (chunk) => setItems((prev) => prev.map((m) => m.id === id && m.kind === "shell" ? {
|
|
5806
|
+
...m,
|
|
5807
|
+
output: m.output + chunk
|
|
5808
|
+
} : m));
|
|
5809
|
+
const stamp = (exit) => setItems((prev) => prev.map((m) => m.id === id && m.kind === "shell" ? {
|
|
5810
|
+
...m,
|
|
5811
|
+
exit
|
|
5812
|
+
} : m));
|
|
5813
|
+
SandboxStream.open({
|
|
5814
|
+
mode: "exec",
|
|
5815
|
+
appUrl,
|
|
5816
|
+
sessionToken,
|
|
5817
|
+
agentId: agent.id,
|
|
5818
|
+
command,
|
|
5819
|
+
onEvent: (e) => {
|
|
5820
|
+
if (e.type === "data") append(dec.decode(e.bytes));
|
|
5821
|
+
else if (e.type === "error") {
|
|
5822
|
+
append(`\n${e.message}`);
|
|
5823
|
+
stamp(1);
|
|
5824
|
+
} else if (e.type === "exit") stamp(e.code);
|
|
5825
|
+
}
|
|
5826
|
+
});
|
|
5827
|
+
}, [
|
|
5828
|
+
appUrl,
|
|
5829
|
+
sessionToken,
|
|
5830
|
+
agent.id
|
|
5831
|
+
]);
|
|
5832
|
+
const runSandboxPty = useCallback(async () => {
|
|
5833
|
+
if (!appUrl || !sessionToken) return;
|
|
5834
|
+
const result = await runPtySession({
|
|
5835
|
+
renderer,
|
|
5836
|
+
appUrl,
|
|
5837
|
+
sessionToken,
|
|
5838
|
+
agentId: agent.id
|
|
5839
|
+
});
|
|
5840
|
+
setItems((prev) => [...prev, {
|
|
5841
|
+
kind: "shell",
|
|
5842
|
+
id: crypto.randomUUID(),
|
|
5843
|
+
command: "☁ sandbox terminal",
|
|
5844
|
+
output: result.reason === "error" ? "terminal ended with an error" : result.reason === "detach" ? "detached" : "session ended",
|
|
5845
|
+
exit: result.code
|
|
5846
|
+
}]);
|
|
5847
|
+
}, [
|
|
5848
|
+
appUrl,
|
|
5849
|
+
sessionToken,
|
|
5850
|
+
agent.id,
|
|
5851
|
+
renderer
|
|
5852
|
+
]);
|
|
5594
5853
|
const submit = useCallback(() => {
|
|
5595
5854
|
const content = input.trim();
|
|
5596
5855
|
const attachments = pending;
|
|
@@ -5602,11 +5861,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5602
5861
|
composerRef.current?.clear();
|
|
5603
5862
|
setComposerRows(1);
|
|
5604
5863
|
if (routed.kind === "local-shell") runLocalShell(routed.command);
|
|
5605
|
-
else if (routed.kind === "sandbox-exec"
|
|
5606
|
-
|
|
5607
|
-
id: crypto.randomUUID(),
|
|
5608
|
-
text: "/sandbox is coming soon — run local commands with ! for now."
|
|
5609
|
-
}]);
|
|
5864
|
+
else if (routed.kind === "sandbox-exec") runSandboxExec(routed.command);
|
|
5865
|
+
else if (routed.kind === "sandbox-pty") runSandboxPty();
|
|
5610
5866
|
return;
|
|
5611
5867
|
}
|
|
5612
5868
|
history.append(content);
|
|
@@ -5620,7 +5876,9 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5620
5876
|
pending,
|
|
5621
5877
|
sendContent,
|
|
5622
5878
|
history,
|
|
5623
|
-
runLocalShell
|
|
5879
|
+
runLocalShell,
|
|
5880
|
+
runSandboxExec,
|
|
5881
|
+
runSandboxPty
|
|
5624
5882
|
]);
|
|
5625
5883
|
const handleComposerChange = useCallback(() => {
|
|
5626
5884
|
const composer = composerRef.current;
|
|
@@ -6311,6 +6569,7 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
|
|
|
6311
6569
|
});
|
|
6312
6570
|
setClients({
|
|
6313
6571
|
appUrl,
|
|
6572
|
+
sessionToken,
|
|
6314
6573
|
rest,
|
|
6315
6574
|
promptHistoryPath
|
|
6316
6575
|
});
|