@axiom-lattice/microsandbox-service 0.1.11 → 1.0.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/CHANGELOG.md +62 -0
- package/README.md +70 -0
- package/dist/{chunk-2GSPCUXK.mjs → chunk-2DPF6PW2.mjs} +441 -51
- package/dist/chunk-2DPF6PW2.mjs.map +1 -0
- package/dist/{chunk-7PYAXMHC.mjs → chunk-K6J5TLVU.mjs} +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/index.d.mts +82 -3
- package/dist/index.mjs +1 -1
- package/dist/server.mjs +2 -2
- package/package.json +1 -1
- package/src/__tests__/MicrosandboxRuntimeService.test.ts +101 -0
- package/src/__tests__/SandboxRegistry.test.ts +408 -1
- package/src/__tests__/app.test.ts +256 -2
- package/src/__tests__/fixtures/realSandboxConfig.ts +36 -0
- package/src/__tests__/proxy.test.ts +178 -0
- package/src/__tests__/schemas.test.ts +41 -0
- package/src/controllers/sandbox.ts +53 -0
- package/src/lib/proxy.ts +155 -0
- package/src/routes/sandbox.ts +32 -0
- package/src/schemas/sandbox.ts +14 -1
- package/src/services/MicrosandboxRuntimeService.ts +92 -12
- package/src/services/SandboxRegistry.ts +198 -7
- package/src/services/publishedPorts.test.ts +84 -0
- package/src/services/publishedPorts.ts +110 -0
- package/src/types/runtime-service.ts +2 -0
- package/dist/chunk-2GSPCUXK.mjs.map +0 -1
- /package/dist/{chunk-7PYAXMHC.mjs.map → chunk-K6J5TLVU.mjs.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# @axiom-lattice/microsandbox-service
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 726c98a: Consolidated release notes for all pending changesets. Each package takes the highest bump type among the merged entries.
|
|
8
|
+
|
|
9
|
+
## Sandbox shell-exec timeout unified on milliseconds (ADR-178, breaking)
|
|
10
|
+
|
|
11
|
+
Unify the sandbox shell-exec timeout on **milliseconds** across the framework.
|
|
12
|
+
|
|
13
|
+
Breaking changes:
|
|
14
|
+
|
|
15
|
+
- `SandboxShellService.execCommand` now takes `timeoutMs` (milliseconds) instead of `timeout` (seconds). Custom `SandboxInstance` implementations must rename the field and adjust units.
|
|
16
|
+
- `MicrosandboxShellExecInput.timeout` → `timeoutMs`; the `POST /api/shell/exec` wire field is now `timeoutMs` for both `microsandbox-service` and `opensandbox-gateway`.
|
|
17
|
+
- `PACK_DIRECTORY_TIMEOUT_SECONDS` → `PACK_DIRECTORY_TIMEOUT_MS` (public core export via `sandbox_lattice`).
|
|
18
|
+
- `sandbox-console` `SandboxExecInput.timeout` → `timeoutMs`.
|
|
19
|
+
|
|
20
|
+
Behavior:
|
|
21
|
+
|
|
22
|
+
- Provider adapters convert once at their boundary: Daytona / agent-infra (`remote`) / OpenSandbox convert ms→seconds; E2B and Local pass milliseconds directly.
|
|
23
|
+
- **Fix:** `opensandbox-gateway` no longer divides an already-seconds value by 1000 (which capped every command at 1s); it now converts `timeoutMs` → the SDK's `timeoutSeconds` (floored to at least 1s).
|
|
24
|
+
- **Fix:** `microsandbox-service` now honors the timeout by streaming the shell and killing it on expiry; previously `timeout` was accepted but ignored.
|
|
25
|
+
- LLM-facing tools `shell_exec` (`timeout`, seconds) and `code_eval` (`timeout_sec`, seconds) intentionally keep seconds and convert once; documented as exceptions.
|
|
26
|
+
- Sandbox lifetime `timeoutSeconds` (create/renew/idle) is unchanged and remains in seconds.
|
|
27
|
+
|
|
28
|
+
## Sandbox service base URL + missing-file semantics (ADR-174 / ADR-175)
|
|
29
|
+
|
|
30
|
+
Unify the sandbox service base-URL env and treat missing sandbox files as a structured not-found.
|
|
31
|
+
|
|
32
|
+
The resident plugin deployment runtime now reads `MICROSANDBOX_SERVICE_BASE_URL` only (dropping `MICROSANDBOX_SERVICE_URL`), so a provider-only config also enables `manage_plugin` deploy/undeploy/status; `sandbox-console` reads `VITE_MICROSANDBOX_SERVICE_BASE_URL`. `MICROSANDBOX_SERVICE_URL` no longer has any effect.
|
|
33
|
+
|
|
34
|
+
`opensandbox-gateway` now preserves the sandbox's upstream HTTP status (and surfaces execd's `{ code, message }`) instead of collapsing every failure to `500 "Download failed"`, and `MicrosandboxServiceClient` attaches the HTTP status to request errors; `microsandbox-service` `readFile`/`downloadFile` map missing files to `404 FILE_NOT_FOUND` (matching `deleteFile`). A missing sandbox file (e.g. the plugin-skill manifest) is therefore classified as not-found rather than a hard failure, fixing `Error loading skill content: ... Download failed`.
|
|
35
|
+
|
|
36
|
+
## Sandbox plugin result contract (ADR-177)
|
|
37
|
+
|
|
38
|
+
- `invokePluginTool` no longer returns `undefined` when a sandbox plugin handler reports `ok:true` without a `result` field. That `undefined` made the MCP App `tools/call` route (`/api/mcp-apps/rpc`) send an empty body (client `Response.json()` threw "Unexpected end of JSON input"). It now falls back to the handler's raw stdout via core's new `handlerResultText`; handlers that supply `result` are unchanged. `handlerResultText` is exported from `@axiom-lattice/core`.
|
|
39
|
+
- Sandbox plugin tool results no longer collapse to the string `"null"` when a handler reports `ok:true` but omits the `result` field. The normalized text now falls back to the handler's raw stdout via the new `handlerResultText` helper (used by `SandboxPluginCompiler`), and `manage_plugin test` returns that same normalized text so authors catch the missing `result` wrapper instead of seeing the raw envelope in `stdout` while the inline result is `"null"`.
|
|
40
|
+
- The sandbox plugin authoring skills now show an explicit ✅/❌ for the required `result` wrapper (payload must go under `result`, not the envelope top level), and the UI skill states that `ui/notifications/tool-result` is a `CallToolResult` read via `params.content[0].text` rather than the handler payload at the top level.
|
|
41
|
+
|
|
42
|
+
## Plugin Builder skills
|
|
43
|
+
|
|
44
|
+
- Plugin Builder's `skill` middleware now uses `readAll: true` instead of a sandbox-plugin-only whitelist, so built-in skills (e.g. `ptc`) are actually loadable rather than merely advertised in `<available_skills>` (loading them previously returned `RESOURCE_NOT_ALLOWED`).
|
|
45
|
+
- Close three Plugin Builder capability gaps: add `sandbox-plugins-consume-open` (how a handler calls the platform's Open MCP surface via the injected `API_GATEWAY_URL`/`OPEN_API_TOKEN` + `listTools()`/`callTool()`), add a "Scenarios → skills" routing table to `sandbox-plugins-patterns`, and document in `sandbox-plugins-ui` that a View `tools/call` response is the handler's `result` value directly (not a `CallToolResult` wrapper) plus a self-check/debug section. The Plugin Builder prompt now routes to the new skill.
|
|
46
|
+
|
|
47
|
+
## MCP App UI push semantics
|
|
48
|
+
|
|
49
|
+
Document the MCP App push semantics in `sandbox-plugins-ui`: `ui/notifications/tool-result` replays the tool result **recorded when the agent ran** (it does not re-execute the handler), whereas a View `tools/call` re-executes the handler live. This is why a stale/`"null"` push can coexist with a correct `tools/call` — re-run the tool for a fresh push instead of debugging the host.
|
|
50
|
+
|
|
51
|
+
## MCP App same-origin rendering
|
|
52
|
+
|
|
53
|
+
`McpAppRenderer` no longer refuses to render when the gateway shares the console origin. Single-origin deployments previously saw "MCP App unavailable" because the outer sandbox proxy iframe (which carries `allow-same-origin`) was required to be cross-origin per the MCP Apps spec. The renderer now only requires that a gateway origin can be derived from `client.config.baseURL`; `event.origin`/`event.source` validation is unchanged, and the untrusted View still runs in an opaque-origin inner frame. Cross-origin deployments keep the stronger containment and are unchanged; the trade-off is documented in ADR-117.
|
|
54
|
+
|
|
55
|
+
## 0.1.12
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- a19e0aa: Unify the sandbox service base-URL env and treat missing sandbox files as a structured not-found (ADR-174 / ADR-175).
|
|
60
|
+
|
|
61
|
+
The resident plugin deployment runtime now reads `MICROSANDBOX_SERVICE_BASE_URL` only (dropping `MICROSANDBOX_SERVICE_URL`), so a provider-only config also enables `manage_plugin` deploy/undeploy/status; `sandbox-console` reads `VITE_MICROSANDBOX_SERVICE_BASE_URL`. `MICROSANDBOX_SERVICE_URL` no longer has any effect.
|
|
62
|
+
|
|
63
|
+
`opensandbox-gateway` now preserves the sandbox's upstream HTTP status (and surfaces execd's `{ code, message }`) instead of collapsing every failure to `500 "Download failed"`, and `MicrosandboxServiceClient` attaches the HTTP status to request errors; `microsandbox-service` `readFile`/`downloadFile` map missing files to `404 FILE_NOT_FOUND` (matching `deleteFile`). A missing sandbox file (e.g. the plugin-skill manifest) is therefore classified as not-found rather than a hard failure, fixing `Error loading skill content: ... Download failed`.
|
|
64
|
+
|
|
3
65
|
## 0.1.11
|
|
4
66
|
|
|
5
67
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -54,6 +54,9 @@ Supported environment variables:
|
|
|
54
54
|
- `MICROSANDBOX_IDLE_TIMEOUT_SEC` default: `600` — auto-stop after N seconds of inactivity. Set to `0` to disable
|
|
55
55
|
- `MICROSANDBOX_VOLUME_QUOTA_MIB` default: `2048` — storage quota for named volumes
|
|
56
56
|
- `MICROSANDBOX_CLI_PATH` default: `~/.microsandbox/bin/msb` — path to the `msb` binary
|
|
57
|
+
- `MICROSANDBOX_API_KEY` — when set, every `/api/*` request must send `Authorization: Bearer <key>` (`/health` stays open)
|
|
58
|
+
- `MICROSANDBOX_HOST_PORT_MIN` default: `40000` — lowest host port used when publishing guest ports
|
|
59
|
+
- `MICROSANDBOX_HOST_PORT_MAX` default: `60000` — highest host port used when publishing guest ports
|
|
57
60
|
|
|
58
61
|
Example:
|
|
59
62
|
|
|
@@ -132,6 +135,73 @@ pnpm --filter @axiom-lattice/microsandbox-service dev
|
|
|
132
135
|
|
|
133
136
|
This command watches source files, rebuilds the package, and restarts the service automatically.
|
|
134
137
|
|
|
138
|
+
## HTTP API
|
|
139
|
+
|
|
140
|
+
Every response uses a JSON envelope: success is `{ "success": true, "data": ... }`, failure is
|
|
141
|
+
`{ "success": false, "error": { "code": "...", "message": "..." } }`. When `MICROSANDBOX_API_KEY`
|
|
142
|
+
is set, all `/api/*` requests require `Authorization: Bearer <key>` (`/health` stays open).
|
|
143
|
+
|
|
144
|
+
### Sandbox lifecycle
|
|
145
|
+
|
|
146
|
+
| Method | Path | Purpose |
|
|
147
|
+
|--------|------|---------|
|
|
148
|
+
| `GET` | `/api/sandboxes` | List sandboxes |
|
|
149
|
+
| `GET` | `/api/sandboxes/:name` | Inspect one sandbox |
|
|
150
|
+
| `PUT` | `/api/sandboxes/:name` | Create-or-reuse (`ensure`) |
|
|
151
|
+
| `POST` | `/api/sandboxes/:name/start` | Start / resume |
|
|
152
|
+
| `POST` | `/api/sandboxes/:name/stop` | Pause |
|
|
153
|
+
| `POST` | `/api/sandboxes/:name/kill` | Kill |
|
|
154
|
+
| `DELETE` | `/api/sandboxes/:name` | Delete |
|
|
155
|
+
| `GET` | `/api/sandboxes/:name/status` | Status |
|
|
156
|
+
| `POST` | `/api/sandboxes/:name/logs` | Logs |
|
|
157
|
+
| `GET` | `/api/sandboxes/:name/endpoints/:port` | Resolve a published guest port |
|
|
158
|
+
| `ANY` | `/api/sandboxes/:name/proxy/:port/*` | Stream to a published guest port |
|
|
159
|
+
|
|
160
|
+
File/shell operations are under `/api/files/*` and `/api/shell/exec`; image and named-volume
|
|
161
|
+
operations under `/api/images/*` and `/api/volumes/:name/fs/*`.
|
|
162
|
+
|
|
163
|
+
### Resident-service primitives
|
|
164
|
+
|
|
165
|
+
`PUT /api/sandboxes/:name` accepts three optional fields beyond image/resource/volume config:
|
|
166
|
+
|
|
167
|
+
| Field | Type | Meaning |
|
|
168
|
+
|-------|------|---------|
|
|
169
|
+
| `entrypoint` | `string[]` | Main process command; replaces the default `tail -f /dev/null`. Use this to run a long-lived service (e.g. a plugin host daemon). |
|
|
170
|
+
| `timeoutSeconds` | `number` \| `null` | Sandbox lifetime. `null` means no expiry. **Currently inert** in the registry — positive values are not yet applied and changes do not force a recreate (scheduled for Plan 3). |
|
|
171
|
+
| `exposePorts` | `number[]` | Guest ports to publish. Each is mapped to a deterministic host port within `[MICROSANDBOX_HOST_PORT_MIN, MICROSANDBOX_HOST_PORT_MAX]`. |
|
|
172
|
+
|
|
173
|
+
Create a resident service and resolve its endpoint:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
curl -X PUT http://127.0.0.1:4002/api/sandboxes/plugin-weather \
|
|
177
|
+
-H 'content-type: application/json' \
|
|
178
|
+
-d '{
|
|
179
|
+
"image": "kioko12520/sandbox:0.1.0",
|
|
180
|
+
"entrypoint": ["node", "/root/.agents/.plugin-host/server.js"],
|
|
181
|
+
"exposePorts": [8080]
|
|
182
|
+
}'
|
|
183
|
+
|
|
184
|
+
curl http://127.0.0.1:4002/api/sandboxes/plugin-weather/endpoints/8080
|
|
185
|
+
# { "success": true, "data": { "endpoint": "/api/sandboxes/plugin-weather/proxy/8080" } }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`GET /api/sandboxes/:name/endpoints/:port` responses:
|
|
189
|
+
|
|
190
|
+
- `200` — `{ "success": true, "data": { "endpoint": "/api/sandboxes/<name>/proxy/<port>" } }`
|
|
191
|
+
(a relative proxy path the caller composes with its configured service base URL; may also
|
|
192
|
+
include `headers` for proxied backends)
|
|
193
|
+
- `400 INVALID_REQUEST` — `:port` is not an integer in `1..65535`
|
|
194
|
+
- `404 ENDPOINT_NOT_PUBLISHED` — the port was not published for this sandbox
|
|
195
|
+
|
|
196
|
+
The guest→host mapping is recovered from the sandbox's persisted config, so it survives service
|
|
197
|
+
restarts. The loopback mapping stays internal; the returned path points at the proxy route below.
|
|
198
|
+
|
|
199
|
+
`ANY /api/sandboxes/:name/proxy/:port/*` streams the request to the sandbox's published guest
|
|
200
|
+
port (any method, unbuffered, so SSE and large payloads pass through). Authenticate to this
|
|
201
|
+
service with `Authorization: Bearer <MICROSANDBOX_API_KEY>`; the proxy **strips** that header
|
|
202
|
+
before forwarding, so the service key never reaches the (unauthenticated) sandbox host behind
|
|
203
|
+
the loopback-bound port.
|
|
204
|
+
|
|
135
205
|
## Operational Notes
|
|
136
206
|
|
|
137
207
|
- Invalid startup configuration exits non-zero.
|