@praxis-ai/praxis 0.1.1 → 0.1.2

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.
Files changed (48) hide show
  1. package/dist/agentCore/index.d.ts +45 -6
  2. package/dist/agentCore/index.js +14 -2
  3. package/dist/applicationLayer/applicationContract.d.ts +2 -0
  4. package/dist/applicationLayer/applicationRuntime.d.ts +6 -1
  5. package/dist/applicationLayer/applicationRuntime.js +37 -3
  6. package/dist/applicationLayer/index.d.ts +1 -0
  7. package/dist/basetool/core/shellRun.js +6 -1
  8. package/dist/rax_packageManager/raxCli.js +42 -1
  9. package/dist/runtimeImplementation/praxisRuntimeKernel.d.ts +6 -0
  10. package/dist/runtimeImplementation/praxisRuntimeKernel.js +165 -14
  11. package/dist/runtimeImplementation/runtime.componentPlane/runtimeComponentRegistry.d.ts +1 -1
  12. package/dist/runtimeImplementation/runtime.componentPlane/runtimeComponentRegistry.js +2 -2
  13. package/dist/runtimeImplementation/runtime.dependencyPlane/dependencySourceRegistry.d.ts +1 -1
  14. package/dist/runtimeImplementation/runtime.dependencyPlane/dependencySourceRegistry.js +12 -0
  15. package/dist/runtimeImplementation/runtime.dependencyPlane/dependencyTypes.js +2 -0
  16. package/dist/runtimeImplementation/runtime.execEngine/baseToolExecutorPortFactory.d.ts +3 -0
  17. package/dist/runtimeImplementation/runtime.execEngine/baseToolExecutorPortFactory.js +45 -7
  18. package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.js +56 -0
  19. package/dist/runtimeImplementation/runtime.mcpPlane/index.d.ts +114 -0
  20. package/dist/runtimeImplementation/runtime.mcpPlane/index.js +167 -0
  21. package/dist/runtimeImplementation/runtime.sandboxPlane/baseToolSandboxPlanner.js +0 -2
  22. package/dist/runtimeImplementation/runtime.sandboxPlane/raxcellSandboxProvider.d.ts +19 -0
  23. package/dist/runtimeImplementation/runtime.sandboxPlane/raxcellSandboxProvider.js +172 -0
  24. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxCommandRunner.d.ts +13 -1
  25. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxCommandRunner.js +230 -186
  26. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxPolicyMiddleware.d.ts +175 -0
  27. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxPolicyMiddleware.js +142 -0
  28. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxRuntimeProvider.d.ts +9 -0
  29. package/dist/runtimeImplementation/runtime.sandboxPlane/sandboxRuntimeProvider.js +115 -205
  30. package/dist/runtimeImplementation/runtimeAgentManifest.js +7 -3
  31. package/package.json +3 -1
  32. package/raxode-tui/dist/raxode-cli/backend/agents/codingAgent/agent.js +3 -3
  33. package/raxode-tui/dist/raxode-cli/backend/application/backendModuleInventory.js +3 -3
  34. package/raxode-tui/dist/raxode-cli/backend/application/localReadinessProbe.d.ts +1 -0
  35. package/raxode-tui/dist/raxode-cli/backend/application/localReadinessProbe.js +50 -4
  36. package/raxode-tui/dist/raxode-cli/backend/application/raxcellSandboxProvider.d.ts +12 -0
  37. package/raxode-tui/dist/raxode-cli/backend/application/raxcellSandboxProvider.js +58 -0
  38. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.d.ts +1 -0
  39. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.js +3 -1
  40. package/raxode-tui/dist/raxode-cli/backend/application/stdioApplicationServer.d.ts +2 -0
  41. package/raxode-tui/dist/raxode-cli/backend/application/stdioApplicationServer.js +7 -0
  42. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.d.ts +2 -0
  43. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.js +21 -1
  44. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.d.ts +1 -1
  45. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.js +8 -0
  46. package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.js +19 -1
  47. package/raxode-tui/package.json +2 -1
  48. package/tsconfig.json +16 -1
@@ -4,9 +4,10 @@
4
4
  * 边界:工具 handler 不感知本文件;executor port 负责把真实进程执行交给本 runner。
5
5
  */
6
6
  import { spawn } from "node:child_process";
7
- import { mkdir, mkdtemp, readdir, rm, stat, writeFile } from "node:fs/promises";
8
- import os from "node:os";
9
7
  import path from "node:path";
8
+ import { createRaxcellSandboxProvider } from "./raxcellSandboxProvider.js";
9
+ import { resolveRaxcellBinaryPath } from "./sandboxRuntimeProvider.js";
10
+ import { runSandboxPolicyMiddleware, } from "./sandboxPolicyMiddleware.js";
10
11
  import { createWorkspaceRollbackSandboxPlan, createWorkspaceRollbackSnapshot, finalizeWorkspaceRollbackSnapshot, restoreWorkspaceRollbackSnapshot, } from "./workspaceRollbackSandbox.js";
11
12
  export const sandboxCommandRunnerDescriptor = {
12
13
  surface: "runtime.sandboxPlane.sandboxCommandRunner",
@@ -31,8 +32,6 @@ function truncate(value, maxBytes) {
31
32
  return output;
32
33
  }
33
34
  function defaultModeFor(profile) {
34
- if (profile === "bapr")
35
- return "none";
36
35
  if (profile === "yolo")
37
36
  return "workspace-rollback";
38
37
  return "isolated";
@@ -80,191 +79,10 @@ function sandboxProviderUnavailable(input, providerFamily) {
80
79
  const preparedStatus = input.preparedSandbox?.probe.status ?? "not-prepared";
81
80
  return new Error(`${providerFamily} sandbox is not ready for isolated execution (${preparedStatus})`);
82
81
  }
83
- function secretMaskScript(filesystem) {
84
- if (!filesystem.protectSecrets)
85
- return "";
86
- const patterns = filesystem.secretGlobs.map((glob) => `-name '${glob.replaceAll("'", "'\\''")}'`).join(" -o ");
87
- return `find /workspace -maxdepth 3 -type f \\( ${patterns} \\) -print0 2>/dev/null | while IFS= read -r -d '' file; do :; done`;
88
- }
89
- function secretPatternMatches(name) {
90
- return name === ".env" || name.startsWith(".env.");
91
- }
92
- async function collectSecretFiles(root, current = root, depth = 0, out = []) {
93
- if (depth > 4)
94
- return out;
95
- let entries;
96
- try {
97
- entries = await readdir(current, { withFileTypes: true });
98
- }
99
- catch {
100
- return out;
101
- }
102
- for (const entry of entries) {
103
- const absolute = path.join(current, entry.name);
104
- if (entry.isDirectory()) {
105
- if (entry.name === ".git" || entry.name === "node_modules" || entry.name === ".rax_workspace")
106
- continue;
107
- await collectSecretFiles(root, absolute, depth + 1, out);
108
- continue;
109
- }
110
- if (!entry.isFile() || !secretPatternMatches(entry.name))
111
- continue;
112
- try {
113
- const info = await stat(absolute);
114
- if (info.isFile())
115
- out.push(path.relative(root, absolute).split(path.sep).join("/"));
116
- }
117
- catch {
118
- // Ignore files that disappeared between directory scan and stat.
119
- }
120
- }
121
- return out;
122
- }
123
82
  function isInsidePath(root, candidate) {
124
83
  const relative = path.relative(root, candidate);
125
84
  return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
126
85
  }
127
- function sandboxPathForRoot(root, workspaceRoot) {
128
- if (root === workspaceRoot)
129
- return "/workspace";
130
- if (isInsidePath(workspaceRoot, root)) {
131
- return path.posix.join("/workspace", path.relative(workspaceRoot, root).split(path.sep).join("/"));
132
- }
133
- return root;
134
- }
135
- function sandboxCwdForHost(hostCwd, filesystem) {
136
- const roots = [...new Set([
137
- filesystem.workspaceRoot,
138
- ...filesystem.allowedReadRoots,
139
- ...filesystem.allowedWriteRoots,
140
- ])].sort((left, right) => right.length - left.length);
141
- for (const root of roots) {
142
- if (!isInsidePath(root, hostCwd))
143
- continue;
144
- const rootDest = sandboxPathForRoot(root, filesystem.workspaceRoot);
145
- const relative = path.relative(root, hostCwd);
146
- if (relative === "")
147
- return rootDest;
148
- return path.posix.join(rootDest, relative.split(path.sep).join("/"));
149
- }
150
- throw new Error(`sandbox cwd ${hostCwd} is outside sandbox filesystem roots`);
151
- }
152
- function sandboxPathParentDirs(target) {
153
- const normalized = path.posix.resolve(target.split(path.sep).join(path.posix.sep));
154
- if (normalized === "/workspace" || normalized.startsWith("/workspace/"))
155
- return [];
156
- const segments = normalized.split("/").filter(Boolean);
157
- const dirs = [];
158
- for (let index = 0; index < segments.length - 1; index += 1) {
159
- const dir = `/${segments.slice(0, index + 1).join("/")}`;
160
- if (dir === "/tmp" || dir === "/dev" || dir === "/proc" || dir === "/usr" || dir === "/bin" || dir === "/lib" || dir === "/lib64" || dir === "/etc")
161
- continue;
162
- dirs.push(dir);
163
- }
164
- return dirs;
165
- }
166
- async function linuxBubblewrapPlan(input, base) {
167
- if (process.platform !== "linux") {
168
- return {
169
- program: input.command,
170
- args: input.args ?? [],
171
- metadata: { ...base.metadata, providerUnavailable: "linux-bubblewrap is only executable on Linux" },
172
- };
173
- }
174
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "praxis-bwrap-"));
175
- const home = path.join(tempDir, "home");
176
- const tmp = path.join(tempDir, "tmp");
177
- await Promise.all([mkdir(home, { recursive: true }), mkdir(tmp, { recursive: true })]);
178
- const command = [input.command, ...(input.args ?? [])];
179
- const networkArgs = base.network === "allow" ? ["--share-net"] : ["--unshare-net"];
180
- const sandboxCwd = sandboxCwdForHost(base.cwd, base.filesystem);
181
- const mountArgs = [
182
- "--unshare-pid",
183
- "--unshare-ipc",
184
- "--unshare-uts",
185
- ...networkArgs,
186
- "--die-with-parent",
187
- "--new-session",
188
- "--ro-bind-try",
189
- "/usr",
190
- "/usr",
191
- "--ro-bind-try",
192
- "/bin",
193
- "/bin",
194
- "--ro-bind-try",
195
- "/lib",
196
- "/lib",
197
- "--ro-bind-try",
198
- "/lib64",
199
- "/lib64",
200
- "--ro-bind-try",
201
- "/etc",
202
- "/etc",
203
- "--dev",
204
- "/dev",
205
- "--proc",
206
- "/proc",
207
- "--tmpfs",
208
- "/tmp",
209
- "--bind",
210
- home,
211
- "/sandbox-home",
212
- "--setenv",
213
- "HOME",
214
- "/sandbox-home",
215
- "--setenv",
216
- "TMPDIR",
217
- "/tmp",
218
- "--setenv",
219
- "PRAXIS_SANDBOX",
220
- "linux-bubblewrap",
221
- "--chdir",
222
- sandboxCwd,
223
- ];
224
- const workspaceMountFlag = base.filesystem.readonlyRoot ? "--ro-bind" : "--bind";
225
- mountArgs.push(workspaceMountFlag, base.filesystem.workspaceRoot, "/workspace");
226
- const mountedReadTargets = new Set(["/workspace"]);
227
- const mountedWriteTargets = new Set();
228
- const writableDestinations = new Set(base.filesystem.allowedWriteRoots.map((root) => sandboxPathForRoot(root, base.filesystem.workspaceRoot)));
229
- for (const readRoot of base.filesystem.allowedReadRoots) {
230
- const dest = sandboxPathForRoot(readRoot, base.filesystem.workspaceRoot);
231
- if (writableDestinations.has(dest))
232
- continue;
233
- if (mountedReadTargets.has(dest) || mountedWriteTargets.has(dest))
234
- continue;
235
- if (dest !== "/workspace") {
236
- for (const parent of sandboxPathParentDirs(dest))
237
- mountArgs.push("--dir", parent);
238
- mountArgs.push("--ro-bind-try", readRoot, dest);
239
- }
240
- mountedReadTargets.add(dest);
241
- }
242
- for (const writeRoot of base.filesystem.allowedWriteRoots) {
243
- if (writeRoot === base.filesystem.workspaceRoot)
244
- continue;
245
- const dest = sandboxPathForRoot(writeRoot, base.filesystem.workspaceRoot);
246
- if (mountedWriteTargets.has(dest))
247
- continue;
248
- for (const parent of sandboxPathParentDirs(dest))
249
- mountArgs.push("--dir", parent);
250
- mountArgs.push("--bind-try", writeRoot, dest);
251
- mountedWriteTargets.add(dest);
252
- }
253
- if (base.filesystem.protectSecrets) {
254
- const mask = path.join(tempDir, "secret-mask");
255
- await writeFile(mask, "", { mode: 0o000 });
256
- const secretFiles = await collectSecretFiles(base.filesystem.workspaceRoot);
257
- for (const name of secretFiles)
258
- mountArgs.push("--ro-bind-try", mask, path.posix.join("/workspace", name));
259
- }
260
- mountArgs.push("/usr/bin/env", ...command);
261
- return {
262
- program: "bwrap",
263
- args: mountArgs,
264
- cleanup: async () => { await rm(tempDir, { recursive: true, force: true }); },
265
- metadata: { ...base.metadata, tempDir, secretMaskScript: secretMaskScript(base.filesystem) },
266
- };
267
- }
268
86
  function seatbeltString(value) {
269
87
  return value
270
88
  .replace(/\\/gu, "\\\\")
@@ -352,7 +170,7 @@ export async function createSandboxCommandPlan(input) {
352
170
  if (providerFamily === "linux-bubblewrap") {
353
171
  if (input.preparedSandbox?.ready !== true)
354
172
  throw sandboxProviderUnavailable(input, providerFamily);
355
- plan = { ...base, ...(await linuxBubblewrapPlan(input, base)) };
173
+ plan = { ...base, program: input.command, args: input.args ?? [] };
356
174
  }
357
175
  else if (providerFamily === "macos-containerization") {
358
176
  if (input.preparedSandbox !== undefined && input.preparedSandbox.ready !== true)
@@ -412,6 +230,227 @@ function parseSandboxDenial(plan, stderr, exitCode) {
412
230
  }
413
231
  return undefined;
414
232
  }
233
+ function providerFromOptions(options) {
234
+ if (options.sandboxProvider !== undefined)
235
+ return options.sandboxProvider;
236
+ const binaryPath = resolveRaxcellBinaryPath();
237
+ return binaryPath === undefined
238
+ ? undefined
239
+ : createRaxcellSandboxProvider({ binaryPath });
240
+ }
241
+ function toProviderRunRequest(input, plan) {
242
+ return {
243
+ kind: "runtime.sandboxPlane.provider.runRequest",
244
+ action: {
245
+ actionId: input.invocationId,
246
+ runtimeId: input.runtimeId,
247
+ sessionId: input.sessionId,
248
+ toolId: input.toolId,
249
+ ownerRuntime: "praxis",
250
+ intentLabel: `${input.toolId} command`,
251
+ metadata: input.metadata ?? {},
252
+ },
253
+ command: {
254
+ argv: [input.command, ...(input.args ?? [])],
255
+ cwd: plan.cwd,
256
+ env: plan.env,
257
+ stdin: null,
258
+ },
259
+ policy: {
260
+ profile: input.policyProfile,
261
+ sandboxId: input.sandbox.sandboxId,
262
+ sandboxMode: plan.mode,
263
+ network: plan.network,
264
+ process: { spawn: true },
265
+ resources: {
266
+ timeoutMs: input.timeoutMs ?? input.sandbox.resourceLimits.timeoutMs,
267
+ maxOutputBytes: input.maxOutputBytes ?? input.sandbox.resourceLimits.maxOutputBytes,
268
+ maxProcesses: input.sandbox.resourceLimits.maxProcesses,
269
+ },
270
+ },
271
+ filesystem: {
272
+ workspaceRoot: plan.filesystem.workspaceRoot,
273
+ read: plan.filesystem.allowedReadRoots,
274
+ write: plan.filesystem.allowedWriteRoots,
275
+ readonlyRoot: plan.filesystem.readonlyRoot,
276
+ protectSecrets: plan.filesystem.protectSecrets,
277
+ },
278
+ policyGrants: input.policyGrants ?? [],
279
+ fallback: { mode: "none" },
280
+ metadata: {
281
+ providerFamily: plan.providerFamily,
282
+ requestedProviderFamily: plan.requestedProviderFamily ?? "",
283
+ policyProfile: plan.policyProfile,
284
+ secretGlobs: plan.filesystem.secretGlobs,
285
+ protectSecrets: plan.filesystem.protectSecrets,
286
+ approvalAccepted: input.approval?.accepted === true,
287
+ approvalGrantedBy: input.approval?.grantedBy ?? null,
288
+ },
289
+ };
290
+ }
291
+ function grantAccessForGap(gap) {
292
+ const access = [...new Set((gap.required ?? [])
293
+ .map((value) => value.toLowerCase())
294
+ .filter((value) => value === "read" || value === "write"))];
295
+ return access.length > 0 ? access : ["read"];
296
+ }
297
+ function resolvePraxisDynamicShellPath(request, rawPath) {
298
+ const home = request.command.env.HOME ?? process.env.HOME;
299
+ if (home === undefined || home.trim().length === 0)
300
+ return undefined;
301
+ if (rawPath === "$HOME" || rawPath === "${HOME}" || rawPath === "~")
302
+ return path.resolve(home);
303
+ for (const prefix of ["$HOME/", "${HOME}/", "~/"]) {
304
+ if (rawPath.startsWith(prefix))
305
+ return path.resolve(home, rawPath.slice(prefix.length));
306
+ }
307
+ return undefined;
308
+ }
309
+ function replaceShellPathToken(value, rawPath, resolvedPath) {
310
+ return value.split(rawPath).join(resolvedPath);
311
+ }
312
+ function rewriteDynamicShellPathRequest(context) {
313
+ const rawPath = context.gap.path;
314
+ if (rawPath.trim().length === 0)
315
+ return undefined;
316
+ const resolvedPath = resolvePraxisDynamicShellPath(context.request, rawPath);
317
+ if (resolvedPath === undefined)
318
+ return undefined;
319
+ return {
320
+ ...context.request,
321
+ command: {
322
+ ...context.request.command,
323
+ argv: context.request.command.argv.map((value) => replaceShellPathToken(value, rawPath, resolvedPath)),
324
+ },
325
+ policyGrants: [
326
+ ...context.request.policyGrants,
327
+ {
328
+ reason: context.gap.reason,
329
+ path: resolvedPath,
330
+ access: grantAccessForGap(context.gap),
331
+ grantedBy: typeof context.request.metadata.approvalGrantedBy === "string"
332
+ ? context.request.metadata.approvalGrantedBy
333
+ : "praxis-human-approval",
334
+ },
335
+ ],
336
+ };
337
+ }
338
+ function defaultEnvironmentGapDecision(context) {
339
+ const gap = context.environmentGap;
340
+ if (context.request.metadata.approvalAccepted === true && gap.reason === "path-outside-declared-roots") {
341
+ return {
342
+ type: "grant",
343
+ grants: [{
344
+ reason: gap.reason,
345
+ path: gap.path,
346
+ access: grantAccessForGap(gap),
347
+ grantedBy: typeof context.request.metadata.approvalGrantedBy === "string"
348
+ ? context.request.metadata.approvalGrantedBy
349
+ : "praxis-human-approval",
350
+ }],
351
+ };
352
+ }
353
+ if (context.request.metadata.approvalAccepted === true && gap.reason === "shell-dynamic-path-unresolved") {
354
+ const request = rewriteDynamicShellPathRequest({ request: context.request, gap });
355
+ if (request !== undefined) {
356
+ return { type: "rewrite", request, reason: "Praxis resolved approved dynamic shell path before sandbox lowering" };
357
+ }
358
+ }
359
+ if (gap.reason !== "cwd-outside-declared-roots") {
360
+ return { type: "deny", reason: gap.publicSafeMessage };
361
+ }
362
+ const cwd = path.resolve(gap.path);
363
+ const writeRoot = context.request.filesystem.write.find((root) => isInsidePath(root, cwd));
364
+ if (writeRoot !== undefined) {
365
+ return {
366
+ type: "grant",
367
+ grants: [{ reason: gap.reason, path: gap.path, access: ["write"], grantedBy: "praxis-policy" }],
368
+ };
369
+ }
370
+ const readRoot = context.request.filesystem.read.find((root) => isInsidePath(root, cwd));
371
+ if (readRoot !== undefined) {
372
+ return {
373
+ type: "grant",
374
+ grants: [{ reason: gap.reason, path: gap.path, access: ["read"], grantedBy: "praxis-policy" }],
375
+ };
376
+ }
377
+ return { type: "deny", reason: gap.publicSafeMessage };
378
+ }
379
+ async function runProviderSandboxCommand(input, plan, options) {
380
+ const provider = providerFromOptions(options);
381
+ if (provider === undefined) {
382
+ return {
383
+ ok: false,
384
+ plan,
385
+ error: {
386
+ code: "SANDBOX_DENIED",
387
+ message: "Raxcell sandbox provider is not configured",
388
+ publicSafe: true,
389
+ denial: {
390
+ kind: "runtime.sandboxPlane.command.denial",
391
+ code: "SANDBOX_PROVIDER_UNAVAILABLE",
392
+ message: "Raxcell sandbox provider is not configured",
393
+ needsApproval: false,
394
+ publicSafe: true,
395
+ },
396
+ },
397
+ events: [...plan.events, "runtime.sandboxPlane.command.denied"],
398
+ metadata: { providerFamily: plan.providerFamily, providerUnavailable: "raxcell provider is not configured" },
399
+ };
400
+ }
401
+ const middlewareResult = await runSandboxPolicyMiddleware({
402
+ provider,
403
+ request: toProviderRunRequest(input, plan),
404
+ decideEnvironmentGap: async ({ request, environmentGap }) => options.decideEnvironmentGap?.({ request, environmentGap }) ?? defaultEnvironmentGapDecision({ request, environmentGap }),
405
+ audit: options.audit,
406
+ });
407
+ if (!middlewareResult.ok) {
408
+ return {
409
+ ok: false,
410
+ plan,
411
+ error: {
412
+ code: "SANDBOX_DENIED",
413
+ message: middlewareResult.error.message,
414
+ publicSafe: true,
415
+ denial: {
416
+ kind: "runtime.sandboxPlane.command.denial",
417
+ code: middlewareResult.error.code === "SANDBOX_PREPARE_FAILED" ? "SANDBOX_PROVIDER_UNAVAILABLE" : "SANDBOX_PERMISSION_DENIED",
418
+ message: middlewareResult.error.message,
419
+ needsApproval: false,
420
+ publicSafe: true,
421
+ },
422
+ },
423
+ stdout: "",
424
+ stderr: "",
425
+ events: [...plan.events, ...middlewareResult.events, "runtime.sandboxPlane.command.denied"],
426
+ metadata: { providerFamily: plan.providerFamily, middlewareError: middlewareResult.error.code },
427
+ };
428
+ }
429
+ return {
430
+ ok: true,
431
+ plan: {
432
+ ...plan,
433
+ metadata: {
434
+ ...plan.metadata,
435
+ providerPrepare: middlewareResult.prepared,
436
+ providerRequest: middlewareResult.request,
437
+ },
438
+ },
439
+ exitCode: middlewareResult.result.exitCode ?? 0,
440
+ stdout: middlewareResult.result.stdout,
441
+ stderr: middlewareResult.result.stderr,
442
+ events: [...plan.events, ...middlewareResult.events, "runtime.sandboxPlane.command.completed"],
443
+ metadata: {
444
+ providerFamily: plan.providerFamily,
445
+ providerId: provider.providerId,
446
+ ...(middlewareResult.result.timedOut ? { timedOut: true } : {}),
447
+ providerRun: {
448
+ denial: middlewareResult.result.denial ?? null,
449
+ filesystemLowering: middlewareResult.result.filesystemLowering ?? null,
450
+ },
451
+ },
452
+ };
453
+ }
415
454
  function spawnPlan(plan, input) {
416
455
  return new Promise((resolve, reject) => {
417
456
  const child = spawn(plan.program, [...plan.args], {
@@ -505,6 +544,11 @@ export async function runSandboxCommand(input, options = {}) {
505
544
  metadata: { providerFamily: plan.providerFamily, providerUnavailable: "remote-worker adapter is not configured" },
506
545
  };
507
546
  }
547
+ if (plan.providerFamily === "linux-bubblewrap") {
548
+ const result = await runProviderSandboxCommand(input, plan, options);
549
+ await plan.cleanup?.();
550
+ return result;
551
+ }
508
552
  const remote = plan.providerFamily === "remote-worker"
509
553
  ? await options.remoteWorker?.(input, plan)
510
554
  : undefined;
@@ -0,0 +1,175 @@
1
+ import type { BaseToolPolicyProfile } from "../runtimeAgentManifest.js";
2
+ import type { SandboxCommandNetworkPolicy } from "./sandboxCommandRunner.js";
3
+ export type SandboxProviderFamily = "host-observed" | "workspace-policy" | "workspace-rollback" | "linux-bubblewrap" | "macos-containerization" | "windows-sandbox" | "remote-worker" | "external";
4
+ export type SandboxProviderPolicyGrant = {
5
+ reason: string;
6
+ path: string;
7
+ access?: readonly string[];
8
+ grantedBy?: string | null;
9
+ };
10
+ export type SandboxProviderFilesystemLoweredRoot = {
11
+ path: string;
12
+ access: "read" | "write" | "runtime" | "scratch" | "runtime-link";
13
+ source: "declared" | "backend-runtime" | "policy-grant";
14
+ };
15
+ export type SandboxProviderFilesystemLoweringReport = {
16
+ declaredRoots: readonly SandboxProviderFilesystemLoweredRoot[];
17
+ runtimeRoots: readonly SandboxProviderFilesystemLoweredRoot[];
18
+ policyGrants: readonly SandboxProviderPolicyGrant[];
19
+ warnings: readonly {
20
+ code: string;
21
+ message: string;
22
+ }[];
23
+ effects?: readonly {
24
+ path?: string;
25
+ pattern?: string;
26
+ rawToken: string;
27
+ access: "read" | "write" | "readwrite";
28
+ command: string;
29
+ reason: string;
30
+ confidence: "high" | "medium" | "low";
31
+ warning?: string;
32
+ }[];
33
+ };
34
+ export type SandboxProviderBackendArtifact = {
35
+ backend: SandboxProviderFamily;
36
+ format: string;
37
+ arguments: readonly string[];
38
+ data: Readonly<Record<string, unknown>>;
39
+ warnings: readonly {
40
+ code: string;
41
+ message: string;
42
+ }[];
43
+ };
44
+ export type SandboxProviderEnvironmentGap = {
45
+ reason: string;
46
+ path: string;
47
+ required?: readonly string[];
48
+ publicSafeMessage: string;
49
+ };
50
+ export type SandboxProviderDenial = {
51
+ code: string;
52
+ message: string;
53
+ publicSafe: true;
54
+ };
55
+ export type SandboxProviderRunRequest = {
56
+ kind: "runtime.sandboxPlane.provider.runRequest";
57
+ action: {
58
+ actionId: string;
59
+ runtimeId: string;
60
+ sessionId: string;
61
+ toolId: string;
62
+ ownerRuntime: "praxis" | string;
63
+ intentLabel: string;
64
+ metadata: Readonly<Record<string, unknown>>;
65
+ };
66
+ command: {
67
+ argv: readonly string[];
68
+ cwd: string;
69
+ env: Readonly<Record<string, string | undefined>>;
70
+ stdin: string | null;
71
+ };
72
+ policy: {
73
+ profile: BaseToolPolicyProfile;
74
+ sandboxId: string;
75
+ sandboxMode: "none" | "workspace-rollback" | "isolated";
76
+ network: SandboxCommandNetworkPolicy;
77
+ process: Readonly<Record<string, unknown>>;
78
+ resources: Readonly<Record<string, unknown>>;
79
+ };
80
+ filesystem: {
81
+ workspaceRoot: string;
82
+ read: readonly string[];
83
+ write: readonly string[];
84
+ readonlyRoot: boolean;
85
+ protectSecrets: boolean;
86
+ };
87
+ policyGrants: readonly SandboxProviderPolicyGrant[];
88
+ fallback: {
89
+ mode: string;
90
+ };
91
+ metadata: Readonly<Record<string, unknown>>;
92
+ };
93
+ export type SandboxProviderPrepareRunResult = {
94
+ kind: "runtime.sandboxPlane.provider.prepareRunResult";
95
+ ok: boolean;
96
+ providerFamily: SandboxProviderFamily;
97
+ denial?: SandboxProviderDenial | null;
98
+ environmentGap?: SandboxProviderEnvironmentGap | null;
99
+ filesystemLowering?: SandboxProviderFilesystemLoweringReport | null;
100
+ backendArtifacts: readonly SandboxProviderBackendArtifact[];
101
+ metadata: Readonly<Record<string, unknown>>;
102
+ };
103
+ export type SandboxProviderRunResult = {
104
+ kind: "runtime.sandboxPlane.provider.runResult";
105
+ ok: boolean;
106
+ providerFamily: SandboxProviderFamily;
107
+ exitCode: number | null;
108
+ stdout: string;
109
+ stderr: string;
110
+ timedOut: boolean;
111
+ denial?: SandboxProviderDenial | null;
112
+ environmentGap?: SandboxProviderEnvironmentGap | null;
113
+ filesystemLowering?: SandboxProviderFilesystemLoweringReport | null;
114
+ metadata: Readonly<Record<string, unknown>>;
115
+ };
116
+ export type SandboxExecutionProviderPort = {
117
+ providerId: string;
118
+ providerFamily: SandboxProviderFamily;
119
+ prepareRun(request: SandboxProviderRunRequest): Promise<SandboxProviderPrepareRunResult>;
120
+ run(request: SandboxProviderRunRequest): Promise<SandboxProviderRunResult>;
121
+ };
122
+ export type SandboxPolicyMiddlewareEnvironmentGapDecision = {
123
+ type: "grant";
124
+ grants: readonly SandboxProviderPolicyGrant[];
125
+ } | {
126
+ type: "rewrite";
127
+ request: SandboxProviderRunRequest;
128
+ reason: string;
129
+ } | {
130
+ type: "deny";
131
+ reason: string;
132
+ };
133
+ export type SandboxPolicyMiddlewareResult = {
134
+ ok: true;
135
+ request: SandboxProviderRunRequest;
136
+ prepared: SandboxProviderPrepareRunResult;
137
+ result: SandboxProviderRunResult;
138
+ events: readonly string[];
139
+ } | {
140
+ ok: false;
141
+ request: SandboxProviderRunRequest;
142
+ prepared?: SandboxProviderPrepareRunResult;
143
+ error: {
144
+ code: "SANDBOX_PREPARE_FAILED" | "SANDBOX_DENIED" | "SANDBOX_RUN_FAILED";
145
+ message: string;
146
+ publicSafe: true;
147
+ denial?: SandboxProviderDenial | null;
148
+ };
149
+ events: readonly string[];
150
+ };
151
+ export type SandboxPolicyMiddlewareAuditEvent = {
152
+ type: string;
153
+ actionId: string;
154
+ sessionId: string;
155
+ toolId: string;
156
+ providerId: string;
157
+ providerFamily: SandboxProviderFamily;
158
+ payload: Readonly<Record<string, unknown>>;
159
+ };
160
+ export declare const sandboxPolicyMiddlewareDescriptor: {
161
+ readonly surface: "runtime.sandboxPlane.sandboxPolicyMiddleware";
162
+ readonly policyOwner: "praxis";
163
+ readonly providerRole: "environment-and-execution";
164
+ readonly publicSafe: true;
165
+ };
166
+ export declare function runSandboxPolicyMiddleware(input: {
167
+ provider: SandboxExecutionProviderPort;
168
+ request: SandboxProviderRunRequest;
169
+ decideEnvironmentGap?: (context: {
170
+ request: SandboxProviderRunRequest;
171
+ prepared: SandboxProviderPrepareRunResult;
172
+ environmentGap: SandboxProviderEnvironmentGap;
173
+ }) => Promise<SandboxPolicyMiddlewareEnvironmentGapDecision> | SandboxPolicyMiddlewareEnvironmentGapDecision;
174
+ audit?: (event: SandboxPolicyMiddlewareAuditEvent) => Promise<void> | void;
175
+ }): Promise<SandboxPolicyMiddlewareResult>;