@nebutra/sandbox-runtime 0.1.0 → 0.1.1

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.
@@ -1 +1 @@
1
- {"at":"2026-05-17T16:51:10.543Z","type":"exec","provider":"local_mac","command":"echo sandbox ok","result":{"exitCode":0,"aggregatedOutput":"sandbox ok\n","executedOn":"local_mac"}}
1
+ {"at":"2026-05-19T08:15:00.536Z","type":"exec","provider":"local_mac","command":"echo sandbox ok","result":{"exitCode":0,"aggregatedOutput":"sandbox ok\n","executedOn":"local_mac"}}
@@ -1,5 +1,5 @@
1
1
 
2
- > @nebutra/sandbox-runtime@0.1.0 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/sandbox-runtime
2
+ > @nebutra/sandbox-runtime@0.1.1 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/sandbox-runtime
3
3
  > tsup
4
4
 
5
5
  CLI Building entry: src/cli.ts, src/index.ts
@@ -11,12 +11,12 @@
11
11
  ESM Build start
12
12
  ESM dist/cli.js 882.00 B
13
13
  ESM dist/index.js 302.00 B
14
- ESM dist/chunk-UGJKHUL3.js 4.95 KB
14
+ ESM dist/chunk-5TXS6ZSQ.js 4.66 KB
15
15
  ESM dist/cli.js.map 1.56 KB
16
16
  ESM dist/index.js.map 71.00 B
17
- ESM dist/chunk-UGJKHUL3.js.map 10.02 KB
17
+ ESM dist/chunk-5TXS6ZSQ.js.map 9.32 KB
18
18
  ESM ⚡️ Build success in 81ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 10211ms
20
+ DTS ⚡️ Build success in 10574ms
21
21
  DTS dist/cli.d.ts 13.00 B
22
22
  DTS dist/index.d.ts 1.83 KB
@@ -1,14 +1,14 @@
1
1
 
2
- > @nebutra/sandbox-runtime@0.1.0 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/sandbox-runtime
2
+ > @nebutra/sandbox-runtime@0.1.1 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/sandbox-runtime
3
3
  > vitest run
4
4
 
5
5
 
6
6
   RUN  v4.1.4 /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/sandbox-runtime
7
7
 
8
- ✓ src/index.test.ts (2 tests) 194ms
8
+ ✓ src/index.test.ts (2 tests) 263ms
9
9
 
10
10
   Test Files  1 passed (1)
11
11
   Tests  2 passed (2)
12
-  Start at  16:51:09
13
-  Duration  1.08s (transform 357ms, setup 0ms, import 432ms, tests 194ms, environment 0ms)
12
+  Start at  08:14:59
13
+  Duration  1.35s (transform 376ms, setup 0ms, import 442ms, tests 263ms, environment 0ms)
14
14
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @nebutra/sandbox-runtime@0.1.0 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/sandbox-runtime
2
+ > @nebutra/sandbox-runtime@0.1.1 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/sandbox-runtime
3
3
  > tsc --noEmit
4
4
 
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # @nebutra/sandbox-runtime
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Publish registry package metadata under the MIT license.
8
+
9
+ - Updated dependencies []:
10
+ - @nebutra/capability-kit@0.2.1
11
+ - @nebutra/errors@0.1.1
@@ -1,17 +1,15 @@
1
1
  // src/index.ts
2
- import { mkdir, readFile, writeFile } from "fs/promises";
3
- import { dirname, join } from "path";
2
+ import {
3
+ appendCapabilityDebug,
4
+ capabilityDebugPath,
5
+ readCapabilityDebug
6
+ } from "@nebutra/capability-kit/debug";
4
7
  import { CapabilityError } from "@nebutra/errors";
5
8
  function sandboxDebugPath() {
6
- return join(process.cwd(), ".nebutra", "debug", "sandbox-runtime.jsonl");
9
+ return capabilityDebugPath("sandbox-runtime");
7
10
  }
8
11
  async function appendSandboxDebug(entry) {
9
- const path = sandboxDebugPath();
10
- await mkdir(dirname(path), { recursive: true });
11
- await writeFile(path, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry })}
12
- `, {
13
- flag: "a"
14
- });
12
+ await appendCapabilityDebug("sandbox-runtime", entry);
15
13
  }
16
14
  function createLocalMacSandbox(options = {}) {
17
15
  const endpoint = options.endpoint ?? process.env.SANDBOX_RUNTIME_LOCAL_URL ?? "http://127.0.0.1:8020/api/v1/sandbox/exec";
@@ -133,12 +131,7 @@ var SandboxRuntime = class _SandboxRuntime {
133
131
  }
134
132
  };
135
133
  async function readSandboxDebug(limit = 10) {
136
- try {
137
- const raw = await readFile(sandboxDebugPath(), "utf8");
138
- return raw.trim().split("\n").filter(Boolean).slice(-limit).map((line) => JSON.parse(line));
139
- } catch {
140
- return [];
141
- }
134
+ return readCapabilityDebug("sandbox-runtime", { limit });
142
135
  }
143
136
 
144
137
  export {
@@ -148,4 +141,4 @@ export {
148
141
  SandboxRuntime,
149
142
  readSandboxDebug
150
143
  };
151
- //# sourceMappingURL=chunk-UGJKHUL3.js.map
144
+ //# sourceMappingURL=chunk-5TXS6ZSQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n appendCapabilityDebug,\n capabilityDebugPath,\n readCapabilityDebug,\n} from \"@nebutra/capability-kit/debug\";\nimport { CapabilityError } from \"@nebutra/errors\";\n\nexport interface ExecHints {\n readonly durationS?: number;\n readonly needsGpu?: boolean;\n readonly networkAccess?: boolean;\n}\n\nexport interface ExecRequest {\n readonly cmd: string;\n readonly filesIn?: Record<string, string>;\n readonly hints?: ExecHints;\n readonly tenantId?: string;\n readonly threadId?: string;\n}\n\nexport interface ExecResponse {\n readonly exitCode: number;\n readonly aggregatedOutput: string;\n readonly executedOn: string;\n}\n\nexport interface SandboxHealth {\n readonly provider: string;\n readonly ok: boolean;\n readonly suggestion?: string;\n}\n\nexport interface Sandbox {\n readonly id: string;\n exec(request: ExecRequest): Promise<ExecResponse>;\n doctor(): Promise<SandboxHealth>;\n}\n\nexport interface RouteRule {\n readonly when: Partial<ExecHints>;\n readonly provider: string;\n}\n\nexport interface SandboxRuntimeConfig {\n readonly providers?: readonly Sandbox[];\n readonly routes?: readonly RouteRule[];\n}\n\nexport function sandboxDebugPath(): string {\n return capabilityDebugPath(\"sandbox-runtime\");\n}\n\nasync function appendSandboxDebug(entry: Record<string, unknown>): Promise<void> {\n await appendCapabilityDebug(\"sandbox-runtime\", entry);\n}\n\nexport function createLocalMacSandbox(\n options: { endpoint?: string; fetch?: typeof fetch } = {},\n): Sandbox {\n const endpoint =\n options.endpoint ??\n process.env.SANDBOX_RUNTIME_LOCAL_URL ??\n \"http://127.0.0.1:8020/api/v1/sandbox/exec\";\n const fetchImpl = options.fetch ?? fetch;\n return {\n id: \"local_mac\",\n async exec(request) {\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n tenantId: request.tenantId ?? \"local\",\n threadId: request.threadId ?? \"local\",\n command: request.cmd,\n capabilityPolicy: {\n kind: \"external_sandbox\",\n network_access: request.hints?.networkAccess ?? false,\n },\n }),\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new CapabilityError(\n \"sandbox-runtime\",\n `Local sandbox refused execution (${response.status})`,\n {\n suggestion:\n \"Start the Rust sandbox service or route this task to a configured remote provider.\",\n metadata: { detail },\n statusCode: response.status,\n },\n );\n }\n const result = (await response.json()) as ExecResponse;\n await appendSandboxDebug({\n type: \"exec\",\n provider: \"local_mac\",\n command: request.cmd,\n result,\n });\n return result;\n },\n async doctor() {\n try {\n const result = await this.exec({ cmd: \"echo sandbox ok\" });\n return { provider: \"local_mac\", ok: result.exitCode === 0 };\n } catch (error) {\n return {\n provider: \"local_mac\",\n ok: false,\n suggestion:\n error instanceof Error ? error.message : \"Check local sandbox service reachability.\",\n };\n }\n },\n };\n}\n\nexport function createRemotePlaceholder(\n id: \"remote_code\" | \"remote_gpu\" | \"remote_workspace\",\n envKey: string,\n): Sandbox {\n return {\n id,\n async exec() {\n throw new CapabilityError(\"sandbox-runtime\", `${id} adapter is not configured`, {\n suggestion: `Set ${envKey} and wire the provider SDK before routing tasks to ${id}.`,\n statusCode: 503,\n });\n },\n async doctor() {\n return process.env[envKey]\n ? { provider: id, ok: true }\n : { provider: id, ok: false, suggestion: `Set ${envKey} to enable ${id}.` };\n },\n };\n}\n\nexport class SandboxRuntime {\n readonly #providers = new Map<string, Sandbox>();\n readonly #routes: readonly RouteRule[];\n\n constructor(config: SandboxRuntimeConfig = {}) {\n for (const provider of config.providers ?? [\n createLocalMacSandbox(),\n createRemotePlaceholder(\"remote_code\", \"SANDBOX_REMOTE_CODE_TOKEN\"),\n createRemotePlaceholder(\"remote_gpu\", \"SANDBOX_REMOTE_GPU_TOKEN\"),\n createRemotePlaceholder(\"remote_workspace\", \"SANDBOX_REMOTE_WORKSPACE_TOKEN\"),\n ]) {\n this.#providers.set(provider.id, provider);\n }\n this.#routes = config.routes ?? [\n { when: { needsGpu: true }, provider: \"remote_gpu\" },\n { when: { durationS: 30 }, provider: \"remote_code\" },\n { when: { needsGpu: false }, provider: \"local_mac\" },\n ];\n }\n\n static fromConfig(config: SandboxRuntimeConfig = {}): SandboxRuntime {\n return new SandboxRuntime(config);\n }\n\n plan(request: ExecRequest): { provider: string; reason: string } {\n const hints = request.hints ?? {};\n for (const rule of this.#routes) {\n const matches = Object.entries(rule.when).every(\n ([key, value]) => hints[key as keyof ExecHints] === value,\n );\n if (matches && this.#providers.has(rule.provider)) {\n return { provider: rule.provider, reason: \"matched static routing rule\" };\n }\n }\n return {\n provider: this.#providers.has(\"local_mac\")\n ? \"local_mac\"\n : (Array.from(this.#providers.keys())[0] ?? \"\"),\n reason: \"default route\",\n };\n }\n\n async exec(request: ExecRequest): Promise<ExecResponse> {\n const plan = this.plan(request);\n const provider = this.#providers.get(plan.provider);\n if (!provider) {\n throw new CapabilityError(\"sandbox-runtime\", \"No sandbox provider available\", {\n suggestion: \"Configure at least one sandbox provider before executing commands.\",\n metadata: plan,\n });\n }\n return provider.exec(request);\n }\n\n async doctor(): Promise<SandboxHealth[]> {\n return Promise.all(Array.from(this.#providers.values()).map((provider) => provider.doctor()));\n }\n}\n\nexport async function readSandboxDebug(limit = 10): Promise<unknown[]> {\n return readCapabilityDebug(\"sandbox-runtime\", { limit });\n}\n"],"mappings":"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","names":[]}
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SandboxRuntime,
3
3
  readSandboxDebug
4
- } from "./chunk-UGJKHUL3.js";
4
+ } from "./chunk-5TXS6ZSQ.js";
5
5
 
6
6
  // src/cli.ts
7
7
  var command = process.argv[2] ?? "doctor";
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  createRemotePlaceholder,
5
5
  readSandboxDebug,
6
6
  sandboxDebugPath
7
- } from "./chunk-UGJKHUL3.js";
7
+ } from "./chunk-5TXS6ZSQ.js";
8
8
  export {
9
9
  SandboxRuntime,
10
10
  createLocalMacSandbox,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebutra/sandbox-runtime",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Unified sandbox runtime router for local and remote execution providers",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -8,6 +8,7 @@
8
8
  "nebutra": {
9
9
  "featureId": "sandbox-runtime",
10
10
  "category": "ai",
11
+ "surface": "execution-router",
11
12
  "summary": "Sandbox trait, provider router, doctor, and debug tools"
12
13
  },
13
14
  "main": "./src/index.ts",
@@ -16,7 +17,8 @@
16
17
  ".": "./src/index.ts"
17
18
  },
18
19
  "dependencies": {
19
- "@nebutra/errors": "0.1.0"
20
+ "@nebutra/capability-kit": "0.2.1",
21
+ "@nebutra/errors": "0.1.1"
20
22
  },
21
23
  "devDependencies": {
22
24
  "@types/node": "^22.19.15",
package/src/index.ts CHANGED
@@ -1,5 +1,8 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
1
+ import {
2
+ appendCapabilityDebug,
3
+ capabilityDebugPath,
4
+ readCapabilityDebug,
5
+ } from "@nebutra/capability-kit/debug";
3
6
  import { CapabilityError } from "@nebutra/errors";
4
7
 
5
8
  export interface ExecHints {
@@ -45,15 +48,11 @@ export interface SandboxRuntimeConfig {
45
48
  }
46
49
 
47
50
  export function sandboxDebugPath(): string {
48
- return join(process.cwd(), ".nebutra", "debug", "sandbox-runtime.jsonl");
51
+ return capabilityDebugPath("sandbox-runtime");
49
52
  }
50
53
 
51
54
  async function appendSandboxDebug(entry: Record<string, unknown>): Promise<void> {
52
- const path = sandboxDebugPath();
53
- await mkdir(dirname(path), { recursive: true });
54
- await writeFile(path, `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\n`, {
55
- flag: "a",
56
- });
55
+ await appendCapabilityDebug("sandbox-runtime", entry);
57
56
  }
58
57
 
59
58
  export function createLocalMacSandbox(
@@ -198,15 +197,5 @@ export class SandboxRuntime {
198
197
  }
199
198
 
200
199
  export async function readSandboxDebug(limit = 10): Promise<unknown[]> {
201
- try {
202
- const raw = await readFile(sandboxDebugPath(), "utf8");
203
- return raw
204
- .trim()
205
- .split("\n")
206
- .filter(Boolean)
207
- .slice(-limit)
208
- .map((line) => JSON.parse(line) as unknown);
209
- } catch {
210
- return [];
211
- }
200
+ return readCapabilityDebug("sandbox-runtime", { limit });
212
201
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { CapabilityError } from \"@nebutra/errors\";\n\nexport interface ExecHints {\n readonly durationS?: number;\n readonly needsGpu?: boolean;\n readonly networkAccess?: boolean;\n}\n\nexport interface ExecRequest {\n readonly cmd: string;\n readonly filesIn?: Record<string, string>;\n readonly hints?: ExecHints;\n readonly tenantId?: string;\n readonly threadId?: string;\n}\n\nexport interface ExecResponse {\n readonly exitCode: number;\n readonly aggregatedOutput: string;\n readonly executedOn: string;\n}\n\nexport interface SandboxHealth {\n readonly provider: string;\n readonly ok: boolean;\n readonly suggestion?: string;\n}\n\nexport interface Sandbox {\n readonly id: string;\n exec(request: ExecRequest): Promise<ExecResponse>;\n doctor(): Promise<SandboxHealth>;\n}\n\nexport interface RouteRule {\n readonly when: Partial<ExecHints>;\n readonly provider: string;\n}\n\nexport interface SandboxRuntimeConfig {\n readonly providers?: readonly Sandbox[];\n readonly routes?: readonly RouteRule[];\n}\n\nexport function sandboxDebugPath(): string {\n return join(process.cwd(), \".nebutra\", \"debug\", \"sandbox-runtime.jsonl\");\n}\n\nasync function appendSandboxDebug(entry: Record<string, unknown>): Promise<void> {\n const path = sandboxDebugPath();\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\\n`, {\n flag: \"a\",\n });\n}\n\nexport function createLocalMacSandbox(\n options: { endpoint?: string; fetch?: typeof fetch } = {},\n): Sandbox {\n const endpoint =\n options.endpoint ??\n process.env.SANDBOX_RUNTIME_LOCAL_URL ??\n \"http://127.0.0.1:8020/api/v1/sandbox/exec\";\n const fetchImpl = options.fetch ?? fetch;\n return {\n id: \"local_mac\",\n async exec(request) {\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n tenantId: request.tenantId ?? \"local\",\n threadId: request.threadId ?? \"local\",\n command: request.cmd,\n capabilityPolicy: {\n kind: \"external_sandbox\",\n network_access: request.hints?.networkAccess ?? false,\n },\n }),\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new CapabilityError(\n \"sandbox-runtime\",\n `Local sandbox refused execution (${response.status})`,\n {\n suggestion:\n \"Start the Rust sandbox service or route this task to a configured remote provider.\",\n metadata: { detail },\n statusCode: response.status,\n },\n );\n }\n const result = (await response.json()) as ExecResponse;\n await appendSandboxDebug({\n type: \"exec\",\n provider: \"local_mac\",\n command: request.cmd,\n result,\n });\n return result;\n },\n async doctor() {\n try {\n const result = await this.exec({ cmd: \"echo sandbox ok\" });\n return { provider: \"local_mac\", ok: result.exitCode === 0 };\n } catch (error) {\n return {\n provider: \"local_mac\",\n ok: false,\n suggestion:\n error instanceof Error ? error.message : \"Check local sandbox service reachability.\",\n };\n }\n },\n };\n}\n\nexport function createRemotePlaceholder(\n id: \"remote_code\" | \"remote_gpu\" | \"remote_workspace\",\n envKey: string,\n): Sandbox {\n return {\n id,\n async exec() {\n throw new CapabilityError(\"sandbox-runtime\", `${id} adapter is not configured`, {\n suggestion: `Set ${envKey} and wire the provider SDK before routing tasks to ${id}.`,\n statusCode: 503,\n });\n },\n async doctor() {\n return process.env[envKey]\n ? { provider: id, ok: true }\n : { provider: id, ok: false, suggestion: `Set ${envKey} to enable ${id}.` };\n },\n };\n}\n\nexport class SandboxRuntime {\n readonly #providers = new Map<string, Sandbox>();\n readonly #routes: readonly RouteRule[];\n\n constructor(config: SandboxRuntimeConfig = {}) {\n for (const provider of config.providers ?? [\n createLocalMacSandbox(),\n createRemotePlaceholder(\"remote_code\", \"SANDBOX_REMOTE_CODE_TOKEN\"),\n createRemotePlaceholder(\"remote_gpu\", \"SANDBOX_REMOTE_GPU_TOKEN\"),\n createRemotePlaceholder(\"remote_workspace\", \"SANDBOX_REMOTE_WORKSPACE_TOKEN\"),\n ]) {\n this.#providers.set(provider.id, provider);\n }\n this.#routes = config.routes ?? [\n { when: { needsGpu: true }, provider: \"remote_gpu\" },\n { when: { durationS: 30 }, provider: \"remote_code\" },\n { when: { needsGpu: false }, provider: \"local_mac\" },\n ];\n }\n\n static fromConfig(config: SandboxRuntimeConfig = {}): SandboxRuntime {\n return new SandboxRuntime(config);\n }\n\n plan(request: ExecRequest): { provider: string; reason: string } {\n const hints = request.hints ?? {};\n for (const rule of this.#routes) {\n const matches = Object.entries(rule.when).every(\n ([key, value]) => hints[key as keyof ExecHints] === value,\n );\n if (matches && this.#providers.has(rule.provider)) {\n return { provider: rule.provider, reason: \"matched static routing rule\" };\n }\n }\n return {\n provider: this.#providers.has(\"local_mac\")\n ? \"local_mac\"\n : (Array.from(this.#providers.keys())[0] ?? \"\"),\n reason: \"default route\",\n };\n }\n\n async exec(request: ExecRequest): Promise<ExecResponse> {\n const plan = this.plan(request);\n const provider = this.#providers.get(plan.provider);\n if (!provider) {\n throw new CapabilityError(\"sandbox-runtime\", \"No sandbox provider available\", {\n suggestion: \"Configure at least one sandbox provider before executing commands.\",\n metadata: plan,\n });\n }\n return provider.exec(request);\n }\n\n async doctor(): Promise<SandboxHealth[]> {\n return Promise.all(Array.from(this.#providers.values()).map((provider) => provider.doctor()));\n }\n}\n\nexport async function readSandboxDebug(limit = 10): Promise<unknown[]> {\n try {\n const raw = await readFile(sandboxDebugPath(), \"utf8\");\n return raw\n .trim()\n .split(\"\\n\")\n .filter(Boolean)\n .slice(-limit)\n .map((line) => JSON.parse(line) as unknown);\n } catch {\n return [];\n }\n}\n"],"mappings":"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","names":[]}