skydive-cli 0.1.0-beta.189 → 0.1.0-beta.218
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.218";
|
|
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-CIdJ5Als.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-CIdJ5Als.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);
|
|
@@ -3919,6 +3921,10 @@ const keybindGroups = [
|
|
|
3919
3921
|
keys: "ctrl+c ctrl+c",
|
|
3920
3922
|
action: "quit"
|
|
3921
3923
|
},
|
|
3924
|
+
{
|
|
3925
|
+
keys: "exit",
|
|
3926
|
+
action: "quit (type it as a message)"
|
|
3927
|
+
},
|
|
3922
3928
|
{
|
|
3923
3929
|
keys: "ctrl+p",
|
|
3924
3930
|
action: "switch model"
|
|
@@ -5034,6 +5040,7 @@ function routeInput(raw) {
|
|
|
5034
5040
|
kind: "sandbox-exec",
|
|
5035
5041
|
command: sandbox
|
|
5036
5042
|
} : { kind: "sandbox-pty" };
|
|
5043
|
+
if (line.trim().toLowerCase() === "exit") return { kind: "quit" };
|
|
5037
5044
|
return {
|
|
5038
5045
|
kind: "message",
|
|
5039
5046
|
content: raw
|
|
@@ -5071,6 +5078,203 @@ function formatShellContext(command, output, exitCode) {
|
|
|
5071
5078
|
].join("\n");
|
|
5072
5079
|
}
|
|
5073
5080
|
|
|
5081
|
+
//#endregion
|
|
5082
|
+
//#region src/chat/sandbox/client.ts
|
|
5083
|
+
/**
|
|
5084
|
+
* Client for the api's `/api/v1/sandbox/stream` WebSocket: relays a `/sandbox`
|
|
5085
|
+
* session into the agent's own sandbox — a live interactive PTY or a one-shot
|
|
5086
|
+
* streamed exec. Mirrors the transport shape of `portal/client.ts`, but dials a
|
|
5087
|
+
* different endpoint and speaks the sandbox-stream frame protocol (see the
|
|
5088
|
+
* server's sandbox-stream-ws.ts).
|
|
5089
|
+
*/
|
|
5090
|
+
const T = {
|
|
5091
|
+
DATA: 1,
|
|
5092
|
+
EXIT: 2,
|
|
5093
|
+
ERROR: 3,
|
|
5094
|
+
INPUT: 16,
|
|
5095
|
+
RESIZE: 17
|
|
5096
|
+
};
|
|
5097
|
+
function wsBase(appUrl) {
|
|
5098
|
+
const base = appUrl.replace(/\/+$/, "");
|
|
5099
|
+
if (base.startsWith("https://")) return `wss://${base.slice(8)}`;
|
|
5100
|
+
if (base.startsWith("http://")) return `ws://${base.slice(7)}`;
|
|
5101
|
+
return `wss://${base}`;
|
|
5102
|
+
}
|
|
5103
|
+
/**
|
|
5104
|
+
* A connected sandbox-stream session. Construct via `openSandboxStream`. Carries
|
|
5105
|
+
* the write side (keystrokes / resize for pty mode) and teardown.
|
|
5106
|
+
*/
|
|
5107
|
+
var SandboxStream = class SandboxStream {
|
|
5108
|
+
ws;
|
|
5109
|
+
closed = false;
|
|
5110
|
+
constructor(ws, onEvent) {
|
|
5111
|
+
this.ws = ws;
|
|
5112
|
+
ws.on("message", (data, isBinary) => {
|
|
5113
|
+
if (!isBinary) return;
|
|
5114
|
+
const buf = toBuffer(data);
|
|
5115
|
+
if (buf.length === 0) return;
|
|
5116
|
+
const body = buf.subarray(1);
|
|
5117
|
+
switch (buf[0]) {
|
|
5118
|
+
case T.DATA:
|
|
5119
|
+
onEvent({
|
|
5120
|
+
type: "data",
|
|
5121
|
+
bytes: new Uint8Array(body)
|
|
5122
|
+
});
|
|
5123
|
+
break;
|
|
5124
|
+
case T.EXIT:
|
|
5125
|
+
onEvent({
|
|
5126
|
+
type: "exit",
|
|
5127
|
+
code: body.length >= 4 ? body.readInt32BE(0) : 0
|
|
5128
|
+
});
|
|
5129
|
+
break;
|
|
5130
|
+
case T.ERROR:
|
|
5131
|
+
onEvent({
|
|
5132
|
+
type: "error",
|
|
5133
|
+
message: body.toString("utf8")
|
|
5134
|
+
});
|
|
5135
|
+
break;
|
|
5136
|
+
}
|
|
5137
|
+
});
|
|
5138
|
+
ws.on("close", () => {
|
|
5139
|
+
this.closed = true;
|
|
5140
|
+
onEvent({ type: "close" });
|
|
5141
|
+
});
|
|
5142
|
+
ws.on("error", () => {});
|
|
5143
|
+
}
|
|
5144
|
+
/** Feed keystroke bytes to the pty stdin. */
|
|
5145
|
+
sendInput(data) {
|
|
5146
|
+
if (this.closed || this.ws.readyState !== WebSocket.OPEN) return;
|
|
5147
|
+
this.ws.send(Buffer.concat([Buffer.from([T.INPUT]), Buffer.from(data)]));
|
|
5148
|
+
}
|
|
5149
|
+
/** Notify the pty of a terminal resize. */
|
|
5150
|
+
resize(cols, rows) {
|
|
5151
|
+
if (this.closed || this.ws.readyState !== WebSocket.OPEN) return;
|
|
5152
|
+
const b = Buffer.allocUnsafe(5);
|
|
5153
|
+
b[0] = T.RESIZE;
|
|
5154
|
+
b.writeUInt16BE(cols & 65535, 1);
|
|
5155
|
+
b.writeUInt16BE(rows & 65535, 3);
|
|
5156
|
+
this.ws.send(b);
|
|
5157
|
+
}
|
|
5158
|
+
close() {
|
|
5159
|
+
this.closed = true;
|
|
5160
|
+
this.ws.close();
|
|
5161
|
+
}
|
|
5162
|
+
/** Open a stream. `mode` is 'pty' (interactive) or 'exec' (one-shot). */
|
|
5163
|
+
static open(opts) {
|
|
5164
|
+
const url = new URL(`${wsBase(opts.appUrl)}/api/v1/sandbox/stream`);
|
|
5165
|
+
url.searchParams.set("agentId", opts.agentId);
|
|
5166
|
+
url.searchParams.set("mode", opts.mode);
|
|
5167
|
+
if (opts.mode === "pty") {
|
|
5168
|
+
url.searchParams.set("cols", String(opts.cols));
|
|
5169
|
+
url.searchParams.set("rows", String(opts.rows));
|
|
5170
|
+
} else url.searchParams.set("command", opts.command);
|
|
5171
|
+
return new SandboxStream(new WebSocket(url.toString(), { headers: { authorization: `Bearer ${opts.sessionToken}` } }), opts.onEvent);
|
|
5172
|
+
}
|
|
5173
|
+
};
|
|
5174
|
+
function toBuffer(data) {
|
|
5175
|
+
if (Buffer.isBuffer(data)) return data;
|
|
5176
|
+
if (Array.isArray(data)) return Buffer.concat(data);
|
|
5177
|
+
return Buffer.from(data);
|
|
5178
|
+
}
|
|
5179
|
+
|
|
5180
|
+
//#endregion
|
|
5181
|
+
//#region src/chat/sandbox/pty-session.ts
|
|
5182
|
+
/**
|
|
5183
|
+
* Runs a live interactive PTY into the agent's sandbox as a full-screen raw
|
|
5184
|
+
* terminal, by SUSPENDING the opentui renderer for the duration and doing a
|
|
5185
|
+
* direct byte passthrough:
|
|
5186
|
+
*
|
|
5187
|
+
* local stdin → INPUT frames → sandbox pty
|
|
5188
|
+
* sandbox pty → DATA frames → local stdout
|
|
5189
|
+
*
|
|
5190
|
+
* This is the correct shape for a real terminal: we don't reimplement a
|
|
5191
|
+
* terminal emulator, we hand the actual TTY to the remote shell. On exit
|
|
5192
|
+
* (the shell exits, the socket drops, or the user hits the detach key) we
|
|
5193
|
+
* restore the terminal and resume the TUI.
|
|
5194
|
+
*
|
|
5195
|
+
* Detach key: Ctrl-] (0x1d), the classic telnet/ssh escape — leaves the shell
|
|
5196
|
+
* running server-side is NOT a goal here; detaching closes the session.
|
|
5197
|
+
*/
|
|
5198
|
+
const DETACH_BYTE = 29;
|
|
5199
|
+
async function runPtySession(opts) {
|
|
5200
|
+
const { renderer } = opts;
|
|
5201
|
+
const stdin = renderer.stdin;
|
|
5202
|
+
const stdout = process.stdout;
|
|
5203
|
+
const size = () => ({
|
|
5204
|
+
cols: stdout.columns ?? 80,
|
|
5205
|
+
rows: stdout.rows ?? 24
|
|
5206
|
+
});
|
|
5207
|
+
renderer.suspend();
|
|
5208
|
+
return await new Promise((resolve) => {
|
|
5209
|
+
let settled = false;
|
|
5210
|
+
const initial = size();
|
|
5211
|
+
const stream = SandboxStream.open({
|
|
5212
|
+
mode: "pty",
|
|
5213
|
+
appUrl: opts.appUrl,
|
|
5214
|
+
sessionToken: opts.sessionToken,
|
|
5215
|
+
agentId: opts.agentId,
|
|
5216
|
+
cols: initial.cols,
|
|
5217
|
+
rows: initial.rows,
|
|
5218
|
+
onEvent: (e) => {
|
|
5219
|
+
switch (e.type) {
|
|
5220
|
+
case "data":
|
|
5221
|
+
stdout.write(e.bytes);
|
|
5222
|
+
break;
|
|
5223
|
+
case "error":
|
|
5224
|
+
stdout.write(`\r\n\x1b[31m${e.message}\x1b[0m\r\n`);
|
|
5225
|
+
finish({
|
|
5226
|
+
reason: "error",
|
|
5227
|
+
code: 1
|
|
5228
|
+
});
|
|
5229
|
+
break;
|
|
5230
|
+
case "exit":
|
|
5231
|
+
finish({
|
|
5232
|
+
reason: "exit",
|
|
5233
|
+
code: e.code
|
|
5234
|
+
});
|
|
5235
|
+
break;
|
|
5236
|
+
case "close":
|
|
5237
|
+
finish({
|
|
5238
|
+
reason: "exit",
|
|
5239
|
+
code: 0
|
|
5240
|
+
});
|
|
5241
|
+
break;
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
});
|
|
5245
|
+
const onStdin = (chunk) => {
|
|
5246
|
+
if (chunk.length === 1 && chunk[0] === DETACH_BYTE) {
|
|
5247
|
+
finish({
|
|
5248
|
+
reason: "detach",
|
|
5249
|
+
code: 0
|
|
5250
|
+
});
|
|
5251
|
+
return;
|
|
5252
|
+
}
|
|
5253
|
+
stream.sendInput(new Uint8Array(chunk));
|
|
5254
|
+
};
|
|
5255
|
+
const onResize = () => {
|
|
5256
|
+
const s = size();
|
|
5257
|
+
stream.resize(s.cols, s.rows);
|
|
5258
|
+
};
|
|
5259
|
+
const wasRaw = stdin.isRaw ?? false;
|
|
5260
|
+
stdin.setRawMode?.(true);
|
|
5261
|
+
stdin.resume();
|
|
5262
|
+
stdin.on("data", onStdin);
|
|
5263
|
+
stdout.on("resize", onResize);
|
|
5264
|
+
stream.resize(initial.cols, initial.rows);
|
|
5265
|
+
function finish(result) {
|
|
5266
|
+
if (settled) return;
|
|
5267
|
+
settled = true;
|
|
5268
|
+
stdin.off("data", onStdin);
|
|
5269
|
+
stdout.off("resize", onResize);
|
|
5270
|
+
stdin.setRawMode?.(wasRaw);
|
|
5271
|
+
stream.close();
|
|
5272
|
+
renderer.resume();
|
|
5273
|
+
resolve(result);
|
|
5274
|
+
}
|
|
5275
|
+
});
|
|
5276
|
+
}
|
|
5277
|
+
|
|
5074
5278
|
//#endregion
|
|
5075
5279
|
//#region src/chat/tui/screens/chat.tsx
|
|
5076
5280
|
/**
|
|
@@ -5133,6 +5337,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5133
5337
|
const rest = useStore((s) => s.rest);
|
|
5134
5338
|
const history = usePromptHistory({ filePath: useStore((s) => s.promptHistoryPath) });
|
|
5135
5339
|
const appUrl = useStore((s) => s.appUrl);
|
|
5340
|
+
const sessionToken = useStore((s) => s.sessionToken);
|
|
5136
5341
|
const goTo = useStore((s) => s.goTo);
|
|
5137
5342
|
const setChatTitle = useStore((s) => s.setChatTitle);
|
|
5138
5343
|
const portal = useStore((s) => s.portal);
|
|
@@ -5591,6 +5796,74 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5591
5796
|
}
|
|
5592
5797
|
sendContent(formatShellContext(command, captured, exitCode), [], { echo: false });
|
|
5593
5798
|
}, [sendContent]);
|
|
5799
|
+
const runSandboxExec = useCallback((command) => {
|
|
5800
|
+
if (!appUrl || !sessionToken) return;
|
|
5801
|
+
const id = crypto.randomUUID();
|
|
5802
|
+
setItems((prev) => [...prev, {
|
|
5803
|
+
kind: "shell",
|
|
5804
|
+
id,
|
|
5805
|
+
command: `☁ ${command}`,
|
|
5806
|
+
output: "",
|
|
5807
|
+
exit: null
|
|
5808
|
+
}]);
|
|
5809
|
+
const dec = new TextDecoder();
|
|
5810
|
+
const append = (chunk) => setItems((prev) => prev.map((m) => m.id === id && m.kind === "shell" ? {
|
|
5811
|
+
...m,
|
|
5812
|
+
output: m.output + chunk
|
|
5813
|
+
} : m));
|
|
5814
|
+
const stamp = (exit) => setItems((prev) => prev.map((m) => m.id === id && m.kind === "shell" ? {
|
|
5815
|
+
...m,
|
|
5816
|
+
exit
|
|
5817
|
+
} : m));
|
|
5818
|
+
SandboxStream.open({
|
|
5819
|
+
mode: "exec",
|
|
5820
|
+
appUrl,
|
|
5821
|
+
sessionToken,
|
|
5822
|
+
agentId: agent.id,
|
|
5823
|
+
command,
|
|
5824
|
+
onEvent: (e) => {
|
|
5825
|
+
if (e.type === "data") append(dec.decode(e.bytes));
|
|
5826
|
+
else if (e.type === "error") {
|
|
5827
|
+
append(`\n${e.message}`);
|
|
5828
|
+
stamp(1);
|
|
5829
|
+
} else if (e.type === "exit") stamp(e.code);
|
|
5830
|
+
}
|
|
5831
|
+
});
|
|
5832
|
+
}, [
|
|
5833
|
+
appUrl,
|
|
5834
|
+
sessionToken,
|
|
5835
|
+
agent.id
|
|
5836
|
+
]);
|
|
5837
|
+
const runSandboxPty = useCallback(async () => {
|
|
5838
|
+
if (!appUrl || !sessionToken) return;
|
|
5839
|
+
const result = await runPtySession({
|
|
5840
|
+
renderer,
|
|
5841
|
+
appUrl,
|
|
5842
|
+
sessionToken,
|
|
5843
|
+
agentId: agent.id
|
|
5844
|
+
});
|
|
5845
|
+
setItems((prev) => [...prev, {
|
|
5846
|
+
kind: "shell",
|
|
5847
|
+
id: crypto.randomUUID(),
|
|
5848
|
+
command: "☁ sandbox terminal",
|
|
5849
|
+
output: result.reason === "error" ? "terminal ended with an error" : result.reason === "detach" ? "detached" : "session ended",
|
|
5850
|
+
exit: result.code
|
|
5851
|
+
}]);
|
|
5852
|
+
}, [
|
|
5853
|
+
appUrl,
|
|
5854
|
+
sessionToken,
|
|
5855
|
+
agent.id,
|
|
5856
|
+
renderer
|
|
5857
|
+
]);
|
|
5858
|
+
const quit = useCallback(() => {
|
|
5859
|
+
portalClient?.dispose();
|
|
5860
|
+
agentHost.dispose();
|
|
5861
|
+
renderer.destroy();
|
|
5862
|
+
}, [
|
|
5863
|
+
portalClient,
|
|
5864
|
+
agentHost,
|
|
5865
|
+
renderer
|
|
5866
|
+
]);
|
|
5594
5867
|
const submit = useCallback(() => {
|
|
5595
5868
|
const content = input.trim();
|
|
5596
5869
|
const attachments = pending;
|
|
@@ -5602,11 +5875,9 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5602
5875
|
composerRef.current?.clear();
|
|
5603
5876
|
setComposerRows(1);
|
|
5604
5877
|
if (routed.kind === "local-shell") runLocalShell(routed.command);
|
|
5605
|
-
else if (routed.kind === "sandbox-exec"
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
text: "/sandbox is coming soon — run local commands with ! for now."
|
|
5609
|
-
}]);
|
|
5878
|
+
else if (routed.kind === "sandbox-exec") runSandboxExec(routed.command);
|
|
5879
|
+
else if (routed.kind === "sandbox-pty") runSandboxPty();
|
|
5880
|
+
else if (routed.kind === "quit") quit();
|
|
5610
5881
|
return;
|
|
5611
5882
|
}
|
|
5612
5883
|
history.append(content);
|
|
@@ -5620,7 +5891,10 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5620
5891
|
pending,
|
|
5621
5892
|
sendContent,
|
|
5622
5893
|
history,
|
|
5623
|
-
runLocalShell
|
|
5894
|
+
runLocalShell,
|
|
5895
|
+
runSandboxExec,
|
|
5896
|
+
runSandboxPty,
|
|
5897
|
+
quit
|
|
5624
5898
|
]);
|
|
5625
5899
|
const handleComposerChange = useCallback(() => {
|
|
5626
5900
|
const composer = composerRef.current;
|
|
@@ -5907,9 +6181,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5907
6181
|
return;
|
|
5908
6182
|
}
|
|
5909
6183
|
if (ctrlCArmed) {
|
|
5910
|
-
|
|
5911
|
-
agentHost.dispose();
|
|
5912
|
-
renderer.destroy();
|
|
6184
|
+
quit();
|
|
5913
6185
|
return;
|
|
5914
6186
|
}
|
|
5915
6187
|
setCtrlCArmed(true);
|
|
@@ -6311,6 +6583,7 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
|
|
|
6311
6583
|
});
|
|
6312
6584
|
setClients({
|
|
6313
6585
|
appUrl,
|
|
6586
|
+
sessionToken,
|
|
6314
6587
|
rest,
|
|
6315
6588
|
promptHistoryPath
|
|
6316
6589
|
});
|