@miosa/sdk 3.2.4 → 3.3.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/README.md +121 -0
- package/dist/index.d.ts +872 -13
- package/dist/index.js +1325 -50
- package/dist/index.js.map +1 -1
- package/native/h3-runner/index.d.ts +61 -0
- package/native/h3-runner/index.js +60 -0
- package/native/h3-runner/npm/darwin-arm64/h3-runner-native.darwin-arm64.node +0 -0
- package/native/h3-runner/npm/darwin-x64/h3-runner-native.darwin-x64.node +0 -0
- package/native/h3-runner/npm/linux-arm64-gnu/h3-runner-native.linux-arm64-gnu.node +0 -0
- package/native/h3-runner/npm/linux-x64-gnu/h3-runner-native.linux-x64-gnu.node +0 -0
- package/native/h3-runner/package.json +9 -0
- package/package.json +17 -14
package/README.md
CHANGED
|
@@ -434,6 +434,127 @@ The SDK retries `429` and `5xx` automatically (3 retries, exponential backoff +
|
|
|
434
434
|
Sandbox `exec` requests are never retried, because a replayed command would run twice; see [Cancelling sandbox commands](#cancelling-sandbox-commands).
|
|
435
435
|
Every `MiosaError` carries `retryable` (server-supplied when present, otherwise `true` for 408/429/5xx and transport failures) and `requestId` for support correlation.
|
|
436
436
|
|
|
437
|
+
## Interactive sandbox terminals
|
|
438
|
+
|
|
439
|
+
Create a ticket, then use the versioned `miosa-terminal-v1` WebSocket protocol.
|
|
440
|
+
There is no typed WebSocket client: the complete wire contract is documented below.
|
|
441
|
+
The Docker container must already exist and be running inside the sandbox.
|
|
442
|
+
`command` and `args` launch the executable directly; do not combine them with `shell`.
|
|
443
|
+
|
|
444
|
+
The example imports [`ws`](https://www.npmjs.com/package/ws) directly, so install it as a direct dependency of your application.
|
|
445
|
+
The SDK's optional dependency does not make that import available under every package manager's dependency isolation rules.
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
npm install ws
|
|
449
|
+
npm install --save-dev @types/ws # TypeScript only
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
import WebSocket from "ws";
|
|
454
|
+
import { Miosa } from "@miosa/sdk";
|
|
455
|
+
|
|
456
|
+
const miosa = new Miosa({ apiKey: process.env.MIOSA_API_KEY! });
|
|
457
|
+
// An existing ready sandbox with the hackerai-agent container already running.
|
|
458
|
+
const sandboxId = process.env.MIOSA_SANDBOX_ID;
|
|
459
|
+
if (!sandboxId) throw new Error("MIOSA_SANDBOX_ID is required");
|
|
460
|
+
const sbx = await miosa.sandboxes.get(sandboxId);
|
|
461
|
+
|
|
462
|
+
const ticket = await sbx.terminal.create({
|
|
463
|
+
cols: 100,
|
|
464
|
+
rows: 30,
|
|
465
|
+
cwd: "/home/user",
|
|
466
|
+
command: "docker",
|
|
467
|
+
args: ["exec", "-it", "hackerai-agent", "bash"],
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const terminal = new WebSocket(ticket.wsUrl, "miosa-terminal-v1", {
|
|
471
|
+
headers: { Authorization: `Bearer ${ticket.streamAuth}` },
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
terminal.on("open", () => {
|
|
475
|
+
terminal.send(JSON.stringify({ type: "resize", cols: 120, rows: 40 }));
|
|
476
|
+
terminal.send(Buffer.from("pwd\n")); // stdin bytes
|
|
477
|
+
// terminal.send(Buffer.from([3])); // Ctrl-C
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
let exitCode: number | undefined;
|
|
481
|
+
terminal.on("message", (data: WebSocket.RawData, isBinary: boolean) => {
|
|
482
|
+
const bytes = Array.isArray(data)
|
|
483
|
+
? Buffer.concat(data)
|
|
484
|
+
: Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
485
|
+
if (isBinary) {
|
|
486
|
+
process.stdout.write(bytes); // PTY bytes, never re-decoded
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const event = JSON.parse(bytes.toString("utf8"));
|
|
490
|
+
if (event.type === "exit") exitCode = event.exit_code;
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
terminal.on("close", (code: number) => {
|
|
494
|
+
console.log(`closed ${code}, exit ${exitCode ?? "unknown"}`);
|
|
495
|
+
});
|
|
496
|
+
terminal.on("error", console.error);
|
|
497
|
+
|
|
498
|
+
// terminal.close(1000); // Deletes the remote PTY and its process.
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Write each server binary frame straight to the sink (`process.stdout.write(data)`).
|
|
502
|
+
Calling `data.toString()` per frame decodes every frame independently, so a UTF-8 sequence split across two PTY frames becomes replacement characters.
|
|
503
|
+
|
|
504
|
+
### Frames
|
|
505
|
+
|
|
506
|
+
Binary messages carry stdin or PTY output without a framing prefix.
|
|
507
|
+
A PTY combines stdout and stderr into the same output stream; they are not separate channels.
|
|
508
|
+
Neither direction is line-normalized.
|
|
509
|
+
The only client text frame with a meaning is resize, `{"type":"resize","cols":120,"rows":40}`; every other client text frame is UTF-8 stdin.
|
|
510
|
+
Standard ping and pong frames are supported.
|
|
511
|
+
|
|
512
|
+
### Authentication
|
|
513
|
+
|
|
514
|
+
`Authorization: Bearer <credential>` accepts either the ticket's scoped `streamAuth` or your account API key.
|
|
515
|
+
Browser clients that cannot set headers may pass the scoped token as `?token=<streamAuth>` on `wsUrl` instead.
|
|
516
|
+
When both are supplied the header wins, so a stale query token cannot override a valid header.
|
|
517
|
+
`ticket.streamAuthExpiresAt` and `ticket.expiresAt` are Unix epoch seconds, one hour after creation; after that the scoped token no longer authorizes a connection, while an API key still does.
|
|
518
|
+
|
|
519
|
+
### Exit and close
|
|
520
|
+
|
|
521
|
+
When the process exits, the server drains its remaining output, sends the text frame `{"type":"exit","exit_code":7}`, and then closes with `1000`.
|
|
522
|
+
That text frame is the only source of the exit code: a close code never carries it.
|
|
523
|
+
If the upstream stream fails without a confirmed process result, the server closes with `1011` and sends no exit frame, rather than inventing a successful exit code.
|
|
524
|
+
|
|
525
|
+
| Close code | Meaning |
|
|
526
|
+
|---|---|
|
|
527
|
+
| `1000` | Normal close: process exited, sandbox disconnected, or the client closed |
|
|
528
|
+
| `1011` | Upstream terminal failure with no confirmed process result |
|
|
529
|
+
| `4001` | Authentication failed |
|
|
530
|
+
| `4003` | Forbidden |
|
|
531
|
+
| `4004` | Sandbox not found |
|
|
532
|
+
| `4009` | Sandbox not running |
|
|
533
|
+
| `4013` | Tenant stream limit reached |
|
|
534
|
+
|
|
535
|
+
A client close with code `1000` (or with no status code) deletes the remote PTY and terminates its process.
|
|
536
|
+
`await sbx.terminal.delete(ticket.sessionId)` does the same and is idempotent.
|
|
537
|
+
|
|
538
|
+
### Reconnecting
|
|
539
|
+
|
|
540
|
+
Any other close, and a dropped transport, leaves the session alive for the guest's 30-second reconnect grace window; a network error is never treated as permission to delete a live PTY.
|
|
541
|
+
Reconnect by dialing the same `ticket.wsUrl` again, which already carries `session_id`, with a credential that is still valid.
|
|
542
|
+
After an interrupted transport or a transient `1011` without a confirmed process exit, reconnect only once the previous socket is fully closed (a session accepts one active connection at a time), and within those 30 seconds.
|
|
543
|
+
Reconnection is an attempt, not proof the process survived; after the grace window the PTY is gone and a new `terminal.create()` is required.
|
|
544
|
+
Do not reconnect a session after receiving its process exit event.
|
|
545
|
+
`4001`, `4003`, `4004`, `4009` and `4013` are terminal for that attempt: retrying them without fixing credentials, the sandbox state, or your concurrency simply fails again.
|
|
546
|
+
|
|
547
|
+
### Direct command launch
|
|
548
|
+
|
|
549
|
+
`command`/`args` are passed to process creation literally, with no implicit shell parsing.
|
|
550
|
+
`terminal.create()` rejects locally decidable misuse with a `MiosaError` carrying `INVALID_TERMINAL_OPTIONS`: `shell` combined with `command`, `args` without `command`, more than 256 arguments, or a non-string/NUL-bearing argument.
|
|
551
|
+
The server validates every option independently and answers `400 INVALID_TERMINAL_OPTIONS`, so client-side checks make failures earlier and cheaper, not authoritative.
|
|
552
|
+
Command launch requires both a platform and guest release that supports it.
|
|
553
|
+
Older control planes can ignore these options and return a ticket for the default shell, so a ticket alone does not prove the requested executable started.
|
|
554
|
+
Confirm that the platform supports direct command launch and verify the process or container through its output before relying on that execution context.
|
|
555
|
+
On a compatible platform, an older guest that cannot handle direct command launch rejects the request instead of silently substituting a default shell.
|
|
556
|
+
For a managed `docker exec` terminal, `cwd` and `env` configure the container exec, and `/home/user` is the documented shared path across the file and command API boundary.
|
|
557
|
+
|
|
437
558
|
## Configuration
|
|
438
559
|
|
|
439
560
|
| Option | Env var | Default |
|