open-managed-agents 0.1.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.
Files changed (197) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +248 -0
  3. package/bin/oma.mjs +499 -0
  4. package/dist/scripts/alpha-openai-compatible-fixture.mjs +134 -0
  5. package/dist/scripts/alpha-smoke-models.mjs +92 -0
  6. package/dist/scripts/alpha-smoke.mjs +581 -0
  7. package/dist/scripts/oma-doctor.js +230 -0
  8. package/dist/scripts/oma-models.js +299 -0
  9. package/dist/scripts/oma-workspaces.js +133 -0
  10. package/dist/src/control-plane/admin/auth.js +43 -0
  11. package/dist/src/control-plane/admin/routes.js +76 -0
  12. package/dist/src/control-plane/admin/service.js +102 -0
  13. package/dist/src/control-plane/admission.js +90 -0
  14. package/dist/src/control-plane/agents/routes.js +63 -0
  15. package/dist/src/control-plane/agents/service.js +687 -0
  16. package/dist/src/control-plane/agents/store.js +391 -0
  17. package/dist/src/control-plane/agents/types.js +1 -0
  18. package/dist/src/control-plane/api-constants.js +3 -0
  19. package/dist/src/control-plane/app.js +907 -0
  20. package/dist/src/control-plane/console/auth.js +68 -0
  21. package/dist/src/control-plane/console/static.js +112 -0
  22. package/dist/src/control-plane/deployment-runtime-config.js +328 -0
  23. package/dist/src/control-plane/deployment-runtime-event-coordinator.js +25 -0
  24. package/dist/src/control-plane/deployment-runtime-turn-guard.js +14 -0
  25. package/dist/src/control-plane/deployment-session-coordinator.js +25 -0
  26. package/dist/src/control-plane/deployment-session-output-coordinator.js +27 -0
  27. package/dist/src/control-plane/deployment-storage.js +354 -0
  28. package/dist/src/control-plane/egress/image.js +48 -0
  29. package/dist/src/control-plane/egress/policy.js +571 -0
  30. package/dist/src/control-plane/egress/presets.js +77 -0
  31. package/dist/src/control-plane/egress/proxy.js +72 -0
  32. package/dist/src/control-plane/egress/ssrf.js +106 -0
  33. package/dist/src/control-plane/egress/utils/debug.js +14 -0
  34. package/dist/src/control-plane/egress/vendor/http-proxy.js +335 -0
  35. package/dist/src/control-plane/egress/vendor/mitm-ca.js +226 -0
  36. package/dist/src/control-plane/egress/vendor/mitm-leaf.js +124 -0
  37. package/dist/src/control-plane/egress/vendor/parent-proxy.js +438 -0
  38. package/dist/src/control-plane/egress/vendor/request-filter.js +105 -0
  39. package/dist/src/control-plane/egress/vendor/sandbox-config.js +1 -0
  40. package/dist/src/control-plane/egress/vendor/tls-terminate-proxy.js +269 -0
  41. package/dist/src/control-plane/environments/routes.js +54 -0
  42. package/dist/src/control-plane/environments/service.js +112 -0
  43. package/dist/src/control-plane/environments/store.js +132 -0
  44. package/dist/src/control-plane/environments/types.js +1 -0
  45. package/dist/src/control-plane/errors.js +81 -0
  46. package/dist/src/control-plane/events/broadcaster.js +217 -0
  47. package/dist/src/control-plane/events/constants.js +2 -0
  48. package/dist/src/control-plane/events/persist.js +39 -0
  49. package/dist/src/control-plane/events/request.js +190 -0
  50. package/dist/src/control-plane/events/routes.js +159 -0
  51. package/dist/src/control-plane/events/runtime-helpers.js +134 -0
  52. package/dist/src/control-plane/events/service.js +1985 -0
  53. package/dist/src/control-plane/events/session-guards.js +29 -0
  54. package/dist/src/control-plane/events/sse.js +6 -0
  55. package/dist/src/control-plane/events/store.js +772 -0
  56. package/dist/src/control-plane/events/tool-persistence.js +102 -0
  57. package/dist/src/control-plane/events/types.js +21 -0
  58. package/dist/src/control-plane/files/routes.js +121 -0
  59. package/dist/src/control-plane/files/service.js +65 -0
  60. package/dist/src/control-plane/files/store-common.js +87 -0
  61. package/dist/src/control-plane/files/store-local.js +488 -0
  62. package/dist/src/control-plane/files/store-memory.js +270 -0
  63. package/dist/src/control-plane/files/store.js +2 -0
  64. package/dist/src/control-plane/files/types.js +7 -0
  65. package/dist/src/control-plane/http.js +25 -0
  66. package/dist/src/control-plane/ids.js +43 -0
  67. package/dist/src/control-plane/logging.js +263 -0
  68. package/dist/src/control-plane/models/auth-storage-backend.js +238 -0
  69. package/dist/src/control-plane/models/catalog.js +78 -0
  70. package/dist/src/control-plane/models/config-security.js +376 -0
  71. package/dist/src/control-plane/models/deployment-config.js +93 -0
  72. package/dist/src/control-plane/models/routes.js +32 -0
  73. package/dist/src/control-plane/models/service.js +138 -0
  74. package/dist/src/control-plane/observability/instruments.js +65 -0
  75. package/dist/src/control-plane/observability/metrics.js +125 -0
  76. package/dist/src/control-plane/observability/routes.js +95 -0
  77. package/dist/src/control-plane/openapi/document.js +282 -0
  78. package/dist/src/control-plane/openapi/routes.js +78 -0
  79. package/dist/src/control-plane/request-idempotency.js +85 -0
  80. package/dist/src/control-plane/secrets/envelope.js +132 -0
  81. package/dist/src/control-plane/secrets/master-key.js +44 -0
  82. package/dist/src/control-plane/secrets/routes.js +20 -0
  83. package/dist/src/control-plane/secrets/service.js +71 -0
  84. package/dist/src/control-plane/secrets/store.js +212 -0
  85. package/dist/src/control-plane/secrets/types.js +3 -0
  86. package/dist/src/control-plane/sessions/pi/custom-tools.js +146 -0
  87. package/dist/src/control-plane/sessions/pi/mcp/bridge.js +527 -0
  88. package/dist/src/control-plane/sessions/pi/mcp/client.js +157 -0
  89. package/dist/src/control-plane/sessions/pi/mcp/credential.js +28 -0
  90. package/dist/src/control-plane/sessions/pi/mcp/fetch.js +69 -0
  91. package/dist/src/control-plane/sessions/pi/mcp/probe.js +202 -0
  92. package/dist/src/control-plane/sessions/pi/mcp/runtime.js +13 -0
  93. package/dist/src/control-plane/sessions/pi/runner.js +1104 -0
  94. package/dist/src/control-plane/sessions/pi/sandbox/cma-glob.js +388 -0
  95. package/dist/src/control-plane/sessions/pi/sandbox/cma-grep.js +124 -0
  96. package/dist/src/control-plane/sessions/pi/sandbox/docker-egress.js +347 -0
  97. package/dist/src/control-plane/sessions/pi/sandbox/docker.js +1589 -0
  98. package/dist/src/control-plane/sessions/pi/sandbox/glob.js +48 -0
  99. package/dist/src/control-plane/sessions/pi/sandbox/image.js +7 -0
  100. package/dist/src/control-plane/sessions/pi/sandbox/microsandbox.js +1677 -0
  101. package/dist/src/control-plane/sessions/pi/sandbox/provider.js +561 -0
  102. package/dist/src/control-plane/sessions/pi/sandbox/selection.js +168 -0
  103. package/dist/src/control-plane/sessions/pi/span-normalizer.js +82 -0
  104. package/dist/src/control-plane/sessions/pi/tool-permissions.js +392 -0
  105. package/dist/src/control-plane/sessions/pi/translator.js +100 -0
  106. package/dist/src/control-plane/sessions/request.js +164 -0
  107. package/dist/src/control-plane/sessions/resources.js +63 -0
  108. package/dist/src/control-plane/sessions/routes.js +88 -0
  109. package/dist/src/control-plane/sessions/serialize.js +24 -0
  110. package/dist/src/control-plane/sessions/service.js +733 -0
  111. package/dist/src/control-plane/sessions/store.js +538 -0
  112. package/dist/src/control-plane/sessions/types.js +1 -0
  113. package/dist/src/control-plane/skills/archive.js +169 -0
  114. package/dist/src/control-plane/skills/routes.js +41 -0
  115. package/dist/src/control-plane/skills/service.js +29 -0
  116. package/dist/src/control-plane/skills/store.js +233 -0
  117. package/dist/src/control-plane/skills/types.js +5 -0
  118. package/dist/src/control-plane/sqlite-transaction.js +15 -0
  119. package/dist/src/control-plane/vaults/mcp-oauth-validate.js +165 -0
  120. package/dist/src/control-plane/vaults/oauth-refresh-ticker.js +43 -0
  121. package/dist/src/control-plane/vaults/oauth-refresh.js +448 -0
  122. package/dist/src/control-plane/vaults/routes.js +66 -0
  123. package/dist/src/control-plane/vaults/service.js +553 -0
  124. package/dist/src/control-plane/vaults/store.js +691 -0
  125. package/dist/src/control-plane/vaults/types.js +1 -0
  126. package/dist/src/control-plane/wake-loop.js +65 -0
  127. package/dist/src/control-plane/wiring.js +92 -0
  128. package/dist/src/control-plane/workspace.js +4 -0
  129. package/dist/src/control-plane/workspaces/store.js +158 -0
  130. package/dist/src/egress-proxy-main.js +103 -0
  131. package/dist/src/main.js +124 -0
  132. package/dist/src/types/agents.js +1 -0
  133. package/dist/src/types/common.js +1 -0
  134. package/dist/src/types/environments.js +1 -0
  135. package/dist/src/types/events.js +121 -0
  136. package/dist/src/types/files.js +1 -0
  137. package/dist/src/types/json.js +19 -0
  138. package/dist/src/types/sessions.js +1 -0
  139. package/dist/ui/managed-agents-console/README.md +52 -0
  140. package/dist/ui/managed-agents-console/docs/agents.md +23 -0
  141. package/dist/ui/managed-agents-console/docs/console.md +23 -0
  142. package/dist/ui/managed-agents-console/docs/dreams.md +5 -0
  143. package/dist/ui/managed-agents-console/docs/environments.md +27 -0
  144. package/dist/ui/managed-agents-console/docs/events.md +19 -0
  145. package/dist/ui/managed-agents-console/docs/files.md +17 -0
  146. package/dist/ui/managed-agents-console/docs/github.md +11 -0
  147. package/dist/ui/managed-agents-console/docs/integrations.md +19 -0
  148. package/dist/ui/managed-agents-console/docs/memory.md +11 -0
  149. package/dist/ui/managed-agents-console/docs/migration.md +17 -0
  150. package/dist/ui/managed-agents-console/docs/multiagent.md +11 -0
  151. package/dist/ui/managed-agents-console/docs/outcomes.md +11 -0
  152. package/dist/ui/managed-agents-console/docs/overview.md +41 -0
  153. package/dist/ui/managed-agents-console/docs/permissions.md +24 -0
  154. package/dist/ui/managed-agents-console/docs/quickstart.md +51 -0
  155. package/dist/ui/managed-agents-console/docs/reference.md +27 -0
  156. package/dist/ui/managed-agents-console/docs/sandbox-reference.md +19 -0
  157. package/dist/ui/managed-agents-console/docs/sandbox-security.md +15 -0
  158. package/dist/ui/managed-agents-console/docs/scheduled-deployments.md +5 -0
  159. package/dist/ui/managed-agents-console/docs/self-hosted-sandboxes.md +15 -0
  160. package/dist/ui/managed-agents-console/docs/session-operations.md +21 -0
  161. package/dist/ui/managed-agents-console/docs/sessions.md +21 -0
  162. package/dist/ui/managed-agents-console/docs/skills.md +15 -0
  163. package/dist/ui/managed-agents-console/docs/tools.md +23 -0
  164. package/dist/ui/managed-agents-console/docs/vaults.md +19 -0
  165. package/dist/ui/managed-agents-console/docs/webhooks.md +11 -0
  166. package/dist/ui/managed-agents-console/index.html +47 -0
  167. package/dist/ui/managed-agents-console/serve.mjs +116 -0
  168. package/dist/ui/managed-agents-console/src/agents-files.jsx +273 -0
  169. package/dist/ui/managed-agents-console/src/api.js +1043 -0
  170. package/dist/ui/managed-agents-console/src/app.jsx +633 -0
  171. package/dist/ui/managed-agents-console/src/auth.jsx +215 -0
  172. package/dist/ui/managed-agents-console/src/console.css +733 -0
  173. package/dist/ui/managed-agents-console/src/data.js +188 -0
  174. package/dist/ui/managed-agents-console/src/detail.jsx +719 -0
  175. package/dist/ui/managed-agents-console/src/docs.jsx +200 -0
  176. package/dist/ui/managed-agents-console/src/environments.jsx +365 -0
  177. package/dist/ui/managed-agents-console/src/forms.jsx +494 -0
  178. package/dist/ui/managed-agents-console/src/icons.jsx +48 -0
  179. package/dist/ui/managed-agents-console/src/skills.jsx +21 -0
  180. package/dist/ui/managed-agents-console/src/sse.js +185 -0
  181. package/dist/ui/managed-agents-console/src/states.jsx +90 -0
  182. package/dist/ui/managed-agents-console/src/tweaks-panel.jsx +541 -0
  183. package/dist/ui/managed-agents-console/src/ui.jsx +190 -0
  184. package/dist/ui/managed-agents-console/src/vaults-data.js +89 -0
  185. package/dist/ui/managed-agents-console/src/vaults.jsx +171 -0
  186. package/dist/ui/managed-agents-console/vendor/babel.min.js +4 -0
  187. package/dist/ui/managed-agents-console/vendor/react-dom.production.min.js +267 -0
  188. package/dist/ui/managed-agents-console/vendor/react.production.min.js +31 -0
  189. package/dist/ui/openapi-docs/VENDOR.md +27 -0
  190. package/dist/ui/openapi-docs/index.html +16 -0
  191. package/dist/ui/openapi-docs/swagger-initializer.js +14 -0
  192. package/dist/ui/openapi-docs/vendor/LICENSE +202 -0
  193. package/dist/ui/openapi-docs/vendor/NOTICE +2 -0
  194. package/dist/ui/openapi-docs/vendor/swagger-ui-bundle.js +2 -0
  195. package/dist/ui/openapi-docs/vendor/swagger-ui-standalone-preset.js +2 -0
  196. package/dist/ui/openapi-docs/vendor/swagger-ui.css +3 -0
  197. package/package.json +53 -0
@@ -0,0 +1,68 @@
1
+ import { hashWorkspaceApiKey, } from "../workspaces/store.js";
2
+ export const CONSOLE_ADMIN_COOKIE = "oma_console_admin";
3
+ export const CONSOLE_WORKSPACE_COOKIE = "oma_console_workspace";
4
+ const WORKSPACE_SESSION_SECONDS = 30 * 24 * 60 * 60;
5
+ const ADMIN_SESSION_SECONDS = 8 * 60 * 60;
6
+ export function createConsoleSessionAuth(input) {
7
+ const workspaceExpiry = () => new Date(Date.now() + WORKSPACE_SESSION_SECONDS * 1000);
8
+ const adminExpiry = () => new Date(Date.now() + ADMIN_SESSION_SECONDS * 1000);
9
+ const workspaceToken = (workspaceId, credentialSha256, kind) => input.workspaces.mintConsoleSession({
10
+ kind,
11
+ workspaceId,
12
+ credentialSha256,
13
+ expiresAt: workspaceExpiry(),
14
+ }).plaintextToken;
15
+ const authenticateAdmin = (token) => {
16
+ const session = input.workspaces.getConsoleSession(token);
17
+ return session?.kind === "admin" && input.admin?.fingerprint() === session.credential_sha256;
18
+ };
19
+ return {
20
+ authenticateWorkspace(token) {
21
+ const session = input.workspaces.getConsoleSession(token);
22
+ if (session?.workspace_id === null || session === undefined)
23
+ return undefined;
24
+ if (session.kind === "workspace_key") {
25
+ return input.workspaces.authenticateKeySha256(session.credential_sha256) === session.workspace_id
26
+ ? session.workspace_id
27
+ : undefined;
28
+ }
29
+ return session.kind === "admin_workspace" && input.admin?.fingerprint() === session.credential_sha256
30
+ ? session.workspace_id
31
+ : undefined;
32
+ },
33
+ authenticateAdmin,
34
+ createWorkspaceSession(plaintextKey) {
35
+ const workspaceId = input.workspaces.authenticate(plaintextKey);
36
+ if (workspaceId === undefined)
37
+ return undefined;
38
+ return {
39
+ token: workspaceToken(workspaceId, hashWorkspaceApiKey(plaintextKey), "workspace_key"),
40
+ workspaceId,
41
+ };
42
+ },
43
+ createAdminSession(plaintextKey) {
44
+ if (!input.admin?.verify(plaintextKey))
45
+ return undefined;
46
+ return input.workspaces.mintConsoleSession({
47
+ kind: "admin",
48
+ credentialSha256: input.admin.fingerprint(),
49
+ expiresAt: adminExpiry(),
50
+ }).plaintextToken;
51
+ },
52
+ selectWorkspace(adminToken, workspaceId) {
53
+ if (!authenticateAdmin(adminToken) || !input.workspaces.getWorkspace(workspaceId)) {
54
+ return undefined;
55
+ }
56
+ return workspaceToken(workspaceId, input.admin.fingerprint(), "admin_workspace");
57
+ },
58
+ revokeWorkspaceSession(token) {
59
+ input.workspaces.revokeConsoleSession(token);
60
+ },
61
+ revokeAdminSession(token) {
62
+ input.workspaces.revokeConsoleSession(token);
63
+ },
64
+ workspaceName(workspaceId) {
65
+ return input.workspaces.getWorkspace(workspaceId)?.name;
66
+ },
67
+ };
68
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Static serving for the bundled operator console (plan 0120 §3.1).
3
+ *
4
+ * Hand-written rather than @hono/node-server's serveStatic because the
5
+ * console needs two things that helper cannot express: `.jsx` served as
6
+ * text/babel (hono's mime table has no jsx entry) and `cache-control:
7
+ * no-store` on every asset (its options have no header hook). The handler
8
+ * serves exactly one directory tree and nothing else — the traversal guard
9
+ * below is the one security-load-bearing piece.
10
+ *
11
+ * Guard shape (0120 review, Codex-adversarial finding): a bare
12
+ * `candidate.startsWith(root)` accepts prefix-sharing *siblings* such as
13
+ * `<root>-private/…`, so containment is checked against `root + sep`, and
14
+ * against the realpath so a symlink inside the tree cannot point out of it.
15
+ */
16
+ import { createReadStream, realpathSync, statSync } from "node:fs";
17
+ import { extname, resolve, sep } from "node:path";
18
+ import { Readable } from "node:stream";
19
+ export const CONSOLE_MOUNT = "/console";
20
+ // Everything the console ships today. Unknown extensions 404 rather than
21
+ // falling back to octet-stream: the tree is ours, so an unlisted type is a
22
+ // packaging mistake we want loud, not a download prompt.
23
+ const CONTENT_TYPES = new Map([
24
+ [".css", "text/css; charset=utf-8"],
25
+ [".html", "text/html; charset=utf-8"],
26
+ [".js", "text/javascript; charset=utf-8"],
27
+ [".jsx", "text/babel; charset=utf-8"],
28
+ [".json", "application/json; charset=utf-8"],
29
+ [".md", "text/markdown; charset=utf-8"],
30
+ [".png", "image/png"],
31
+ [".svg", "image/svg+xml; charset=utf-8"],
32
+ ]);
33
+ // unsafe-eval is the price of in-browser Babel; unsafe-inline (script) is the
34
+ // price of Babel executing transformed <script type="text/babel"> blocks.
35
+ // connect-src 'self' blocks fetch/XHR/WebSocket exfiltration of the keys in
36
+ // page memory; base-uri and form-action close the <base>-hijack and form-post
37
+ // channels. Top-level navigation remains uncoverable by CSP — this is
38
+ // defense-in-depth behind React's escaping, not a substitute for it.
39
+ const CONSOLE_CSP = [
40
+ "default-src 'self'",
41
+ "script-src 'self' 'unsafe-eval' 'unsafe-inline'",
42
+ "style-src 'self' 'unsafe-inline'",
43
+ "connect-src 'self'",
44
+ "img-src 'self' data:",
45
+ "base-uri 'self'",
46
+ "form-action 'self'",
47
+ "frame-ancestors 'none'",
48
+ ].join("; ");
49
+ export function registerConsoleRoutes(app, config) {
50
+ const root = resolve(config.root);
51
+ // Resolved once: symlink containment is checked against where the tree
52
+ // *really* lives, so a console dir that is itself a symlink still works.
53
+ const realRoot = realpathSync(root);
54
+ // The console's asset URLs are relative (src/api.js …); served at the bare
55
+ // mount they would resolve to /src/… and 404. Redirect instead of serving
56
+ // index.html so the browser's base URL is always the trailing-slash form.
57
+ app.get(CONSOLE_MOUNT, (c) => c.redirect(`${CONSOLE_MOUNT}/`, 301));
58
+ app.get(`${CONSOLE_MOUNT}/*`, (c) => {
59
+ const raw = c.req.path.slice(CONSOLE_MOUNT.length + 1);
60
+ const relative = raw === "" ? "index.html" : decodeOrNull(raw);
61
+ if (relative === null || relative.includes("\0"))
62
+ return notFound(c);
63
+ const candidate = resolve(realRoot, relative);
64
+ if (!contains(realRoot, candidate))
65
+ return notFound(c);
66
+ let file;
67
+ try {
68
+ file = realpathSync(candidate); // also the existence check
69
+ }
70
+ catch {
71
+ return notFound(c);
72
+ }
73
+ if (!contains(realRoot, file) || !statSync(file).isFile()) {
74
+ return notFound(c);
75
+ }
76
+ const contentType = CONTENT_TYPES.get(extname(file));
77
+ if (contentType === undefined)
78
+ return notFound(c);
79
+ setConsoleHeaders(c);
80
+ c.header("content-type", contentType);
81
+ return c.body(createReadableFileStream(file));
82
+ });
83
+ }
84
+ function contains(root, candidate) {
85
+ return candidate === root || candidate.startsWith(root + sep);
86
+ }
87
+ // Hono half-decodes (%2e -> "." but %2f stays); one more decode maps the
88
+ // remaining escapes to their filesystem form so ..%2f is seen as ../ by the
89
+ // containment check instead of sneaking past it as an opaque segment.
90
+ function decodeOrNull(value) {
91
+ try {
92
+ return decodeURIComponent(value);
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ }
98
+ function notFound(c) {
99
+ setConsoleHeaders(c);
100
+ return c.text("Not found\n", 404);
101
+ }
102
+ function setConsoleHeaders(c) {
103
+ c.header("cache-control", "no-store");
104
+ c.header("content-security-policy", CONSOLE_CSP);
105
+ c.header("x-frame-options", "DENY");
106
+ }
107
+ function createReadableFileStream(path) {
108
+ // Node's fs streams convert cleanly; Response accepts web ReadableStream.
109
+ // Files here are ≤3 MB (babel.min.js), so streaming keeps memory flat
110
+ // without any range/caching complexity the console does not need.
111
+ return Readable.toWeb(createReadStream(path));
112
+ }
@@ -0,0 +1,328 @@
1
+ import { PiSessionRunner } from "./sessions/pi/runner.js";
2
+ import { parseSandboxProviderSelection, resolveSandboxProviderFactory, } from "./sessions/pi/sandbox/selection.js";
3
+ export const DEPLOYMENT_RUNTIME_ENV_KEYS = [
4
+ "OMA_SANDBOX_PROVIDER",
5
+ "OMA_SANDBOX_ENV_ALLOWLIST",
6
+ "OMA_SANDBOX_OPERATION_TIMEOUT_MS",
7
+ "OMA_DOCKER_REAP_STALE_CONTAINERS_OLDER_THAN_MS",
8
+ "OMA_MICROSANDBOX_REAP_STALE_SANDBOXES_OLDER_THAN_MS",
9
+ "OMA_ALLOW_DOCKER_LOCAL",
10
+ "OMA_ALLOW_MICROSANDBOX_LOCAL",
11
+ "OMA_ALLOW_UNSAFE_HOST_PASSTHROUGH",
12
+ "OMA_UNSAFE_ALLOW_HOST_PASSTHROUGH",
13
+ "OMA_HOST_PASSTHROUGH_WORKSPACE_ROOT",
14
+ "OMA_ENABLE_EGRESS",
15
+ "OMA_EGRESS_SIDECAR_IMAGE",
16
+ "OMA_EGRESS_SIDECAR_REPO_MOUNT",
17
+ "OMA_ENABLE_MCP",
18
+ "OMA_MCP_OPERATION_TIMEOUT_MS",
19
+ ];
20
+ export const DEFAULT_DOCKER_REAP_STALE_CONTAINERS_OLDER_THAN_MS = 24 * 60 * 60 * 1000;
21
+ export const DEFAULT_MICROSANDBOX_REAP_STALE_SANDBOXES_OLDER_THAN_MS = 24 * 60 * 60 * 1000;
22
+ export function parseDeploymentRuntimeConfigFromEnv(env) {
23
+ // Provider-orthogonal: MCP is a control-plane capability, parsed once and
24
+ // merged whatever the sandbox branch below returns.
25
+ const mcp = mcpConfig(env);
26
+ const config = parseSandboxRuntimeConfigFromEnv(env);
27
+ return mcp === undefined ? config : { ...config, mcp };
28
+ }
29
+ function parseSandboxRuntimeConfigFromEnv(env) {
30
+ const provider = optionalString(env.OMA_SANDBOX_PROVIDER);
31
+ if (provider === undefined) {
32
+ rejectProviderSpecificEnv(env, "set OMA_SANDBOX_PROVIDER first");
33
+ return validateDeploymentRuntimeConfig({});
34
+ }
35
+ if (provider === "none") {
36
+ rejectProviderSpecificEnv(env, "OMA_SANDBOX_PROVIDER=none");
37
+ return validateDeploymentRuntimeConfig({
38
+ sandboxProviderSelection: { type: "none" },
39
+ });
40
+ }
41
+ if (provider === "docker-local") {
42
+ rejectHostPassthroughEnv(env, "OMA_SANDBOX_PROVIDER=docker-local");
43
+ rejectMicrosandboxEnv(env, "OMA_SANDBOX_PROVIDER=docker-local");
44
+ const selection = parseSandboxProviderSelection({
45
+ type: "docker-local",
46
+ ...envAllowlist(env),
47
+ ...operationTimeoutMs(env),
48
+ reapStaleContainersOlderThanMs: dockerReapStaleContainersOlderThanMs(env),
49
+ });
50
+ const egress = egressConfig(env);
51
+ return validateDeploymentRuntimeConfig({
52
+ sandboxProviderSelection: selection,
53
+ sandboxProviderSelectionOptions: {
54
+ allowDockerLocal: parseBoolean(env.OMA_ALLOW_DOCKER_LOCAL, {
55
+ defaultValue: false,
56
+ name: "OMA_ALLOW_DOCKER_LOCAL",
57
+ }),
58
+ },
59
+ ...(egress === undefined ? {} : { egress }),
60
+ });
61
+ }
62
+ if (provider === "microsandbox-local") {
63
+ rejectDockerEnv(env, "OMA_SANDBOX_PROVIDER=microsandbox-local");
64
+ rejectHostPassthroughEnv(env, "OMA_SANDBOX_PROVIDER=microsandbox-local");
65
+ rejectEnvAllowlist(env, "OMA_SANDBOX_PROVIDER=microsandbox-local");
66
+ rejectEgressEnv(env, "OMA_SANDBOX_PROVIDER=microsandbox-local");
67
+ const selection = parseSandboxProviderSelection({
68
+ type: "microsandbox-local",
69
+ ...operationTimeoutMs(env),
70
+ reapStaleSandboxesOlderThanMs: microsandboxReapStaleSandboxesOlderThanMs(env),
71
+ });
72
+ return validateDeploymentRuntimeConfig({
73
+ sandboxProviderSelection: selection,
74
+ sandboxProviderSelectionOptions: {
75
+ allowMicrosandboxLocal: parseBoolean(env.OMA_ALLOW_MICROSANDBOX_LOCAL, {
76
+ defaultValue: false,
77
+ name: "OMA_ALLOW_MICROSANDBOX_LOCAL",
78
+ }),
79
+ },
80
+ });
81
+ }
82
+ if (provider === "host-passthrough") {
83
+ rejectDockerEnv(env, "OMA_SANDBOX_PROVIDER=host-passthrough");
84
+ rejectMicrosandboxEnv(env, "OMA_SANDBOX_PROVIDER=host-passthrough");
85
+ rejectOperationTimeoutEnv(env, "OMA_SANDBOX_PROVIDER=host-passthrough");
86
+ rejectEgressEnv(env, "OMA_SANDBOX_PROVIDER=host-passthrough");
87
+ const unsafeAllowHostPassthrough = parseBoolean(env.OMA_UNSAFE_ALLOW_HOST_PASSTHROUGH, {
88
+ defaultValue: false,
89
+ name: "OMA_UNSAFE_ALLOW_HOST_PASSTHROUGH",
90
+ });
91
+ const selection = parseSandboxProviderSelection({
92
+ type: "host-passthrough",
93
+ unsafeAllowHostPassthrough,
94
+ ...envAllowlist(env),
95
+ });
96
+ return validateDeploymentRuntimeConfig({
97
+ sandboxProviderSelection: selection,
98
+ sandboxProviderSelectionOptions: {
99
+ allowUnsafeHostPassthrough: parseBoolean(env.OMA_ALLOW_UNSAFE_HOST_PASSTHROUGH, {
100
+ defaultValue: false,
101
+ name: "OMA_ALLOW_UNSAFE_HOST_PASSTHROUGH",
102
+ }),
103
+ hostPassthroughWorkspaceRoot: optionalString(env.OMA_HOST_PASSTHROUGH_WORKSPACE_ROOT),
104
+ },
105
+ });
106
+ }
107
+ throw new Error(`Unsupported OMA_SANDBOX_PROVIDER: ${provider}`);
108
+ }
109
+ export function validateDeploymentRuntimeConfig(config) {
110
+ rejectIgnoredResolverOptions(config);
111
+ if (config.egress !== undefined &&
112
+ config.sandboxProviderSelection?.type !== "docker-local") {
113
+ throw new Error("egress requires the docker-local sandbox provider");
114
+ }
115
+ resolveSandboxProviderFactory(config.sandboxProviderSelection, config.sandboxProviderSelectionOptions);
116
+ return config;
117
+ }
118
+ export function createDeploymentPiSessionRunner(config, opts = {}) {
119
+ const rawOpts = opts;
120
+ if (rawOpts.sandboxProviderFactory !== undefined ||
121
+ rawOpts.sandboxProviderSelection !== undefined ||
122
+ rawOpts.sandboxProviderSelectionOptions !== undefined) {
123
+ throw new Error("Deployment runner options cannot override sandbox provider config");
124
+ }
125
+ const { resolveEgressBundle, onSandboxesReaped, ...runnerOpts } = opts;
126
+ if (config.egress !== undefined && resolveEgressBundle === undefined) {
127
+ throw new Error("egress is enabled but no egress bundle resolver was provided");
128
+ }
129
+ const selectionOptions = {
130
+ ...(config.egress === undefined || resolveEgressBundle === undefined
131
+ ? config.sandboxProviderSelectionOptions
132
+ : {
133
+ ...config.sandboxProviderSelectionOptions,
134
+ egress: { ...config.egress, resolveEgressBundle },
135
+ }),
136
+ ...(onSandboxesReaped === undefined ? {} : { onReaped: onSandboxesReaped }),
137
+ };
138
+ return new PiSessionRunner({
139
+ ...runnerOpts,
140
+ sandboxProviderSelection: config.sandboxProviderSelection,
141
+ sandboxProviderSelectionOptions: selectionOptions,
142
+ });
143
+ }
144
+ function optionalString(value) {
145
+ if (value === undefined || value.trim() === "")
146
+ return undefined;
147
+ return value.trim();
148
+ }
149
+ function parseBoolean(value, opts) {
150
+ const parsed = optionalString(value);
151
+ if (parsed === undefined)
152
+ return opts.defaultValue;
153
+ if (parsed === "true")
154
+ return true;
155
+ if (parsed === "false")
156
+ return false;
157
+ throw new Error(`${opts.name} must be "true" or "false"`);
158
+ }
159
+ function envAllowlist(env) {
160
+ const raw = optionalString(env.OMA_SANDBOX_ENV_ALLOWLIST);
161
+ if (raw === undefined)
162
+ return {};
163
+ const values = raw.split(",").map((value) => value.trim());
164
+ if (values.some((value) => value.length === 0)) {
165
+ throw new Error("OMA_SANDBOX_ENV_ALLOWLIST must be comma-separated names");
166
+ }
167
+ return { envAllowlist: values };
168
+ }
169
+ function operationTimeoutMs(env) {
170
+ const raw = optionalString(env.OMA_SANDBOX_OPERATION_TIMEOUT_MS);
171
+ if (raw === undefined)
172
+ return {};
173
+ const value = parsePositiveInteger(raw, "OMA_SANDBOX_OPERATION_TIMEOUT_MS");
174
+ return { operationTimeoutMs: value };
175
+ }
176
+ // 0117e-3: egress requires BOTH the explicit boolean AND the image — setting
177
+ // an image alone must never silently change network posture, and (this
178
+ // file's idiom) partially-applied env is a loud startup error, not a warning.
179
+ function egressConfig(env) {
180
+ const enabled = parseBoolean(env.OMA_ENABLE_EGRESS, {
181
+ defaultValue: false,
182
+ name: "OMA_ENABLE_EGRESS",
183
+ });
184
+ const sidecarImage = optionalString(env.OMA_EGRESS_SIDECAR_IMAGE);
185
+ const sidecarRepoMount = optionalString(env.OMA_EGRESS_SIDECAR_REPO_MOUNT);
186
+ if (!enabled) {
187
+ if (sidecarImage !== undefined) {
188
+ throw new Error("OMA_EGRESS_SIDECAR_IMAGE is ignored without OMA_ENABLE_EGRESS=true");
189
+ }
190
+ if (sidecarRepoMount !== undefined) {
191
+ throw new Error("OMA_EGRESS_SIDECAR_REPO_MOUNT is ignored without OMA_ENABLE_EGRESS=true");
192
+ }
193
+ return undefined;
194
+ }
195
+ if (sidecarImage === undefined) {
196
+ throw new Error("OMA_ENABLE_EGRESS=true requires OMA_EGRESS_SIDECAR_IMAGE (the appliance cannot introspect its own image tag)");
197
+ }
198
+ return {
199
+ sidecarImage,
200
+ ...(sidecarRepoMount === undefined ? {} : { sidecarRepoMount }),
201
+ };
202
+ }
203
+ // Same partially-applied-env-is-a-loud-error idiom as egressConfig.
204
+ function mcpConfig(env) {
205
+ const enabled = parseBoolean(env.OMA_ENABLE_MCP, {
206
+ defaultValue: false,
207
+ name: "OMA_ENABLE_MCP",
208
+ });
209
+ const timeoutRaw = optionalString(env.OMA_MCP_OPERATION_TIMEOUT_MS);
210
+ if (!enabled) {
211
+ if (timeoutRaw !== undefined) {
212
+ throw new Error("OMA_MCP_OPERATION_TIMEOUT_MS is ignored without OMA_ENABLE_MCP=true");
213
+ }
214
+ return undefined;
215
+ }
216
+ if (timeoutRaw === undefined)
217
+ return {};
218
+ return {
219
+ operationTimeoutMs: parsePositiveInteger(timeoutRaw, "OMA_MCP_OPERATION_TIMEOUT_MS"),
220
+ };
221
+ }
222
+ function dockerReapStaleContainersOlderThanMs(env) {
223
+ const raw = optionalString(env.OMA_DOCKER_REAP_STALE_CONTAINERS_OLDER_THAN_MS);
224
+ return raw === undefined
225
+ ? DEFAULT_DOCKER_REAP_STALE_CONTAINERS_OLDER_THAN_MS
226
+ : parsePositiveInteger(raw, "OMA_DOCKER_REAP_STALE_CONTAINERS_OLDER_THAN_MS");
227
+ }
228
+ function microsandboxReapStaleSandboxesOlderThanMs(env) {
229
+ const raw = optionalString(env.OMA_MICROSANDBOX_REAP_STALE_SANDBOXES_OLDER_THAN_MS);
230
+ return raw === undefined
231
+ ? DEFAULT_MICROSANDBOX_REAP_STALE_SANDBOXES_OLDER_THAN_MS
232
+ : parsePositiveInteger(raw, "OMA_MICROSANDBOX_REAP_STALE_SANDBOXES_OLDER_THAN_MS");
233
+ }
234
+ function parsePositiveInteger(raw, name) {
235
+ const value = Number(raw);
236
+ if (!Number.isSafeInteger(value) || value <= 0 || String(value) !== raw) {
237
+ throw new Error(`${name} must be a positive integer`);
238
+ }
239
+ return value;
240
+ }
241
+ function rejectProviderSpecificEnv(env, context) {
242
+ rejectDockerEnv(env, context);
243
+ rejectHostPassthroughEnv(env, context);
244
+ rejectMicrosandboxEnv(env, context);
245
+ rejectEnvAllowlist(env, context);
246
+ rejectOperationTimeoutEnv(env, context);
247
+ rejectEgressEnv(env, context);
248
+ }
249
+ function rejectEgressEnv(env, context) {
250
+ for (const key of [
251
+ "OMA_ENABLE_EGRESS",
252
+ "OMA_EGRESS_SIDECAR_IMAGE",
253
+ "OMA_EGRESS_SIDECAR_REPO_MOUNT",
254
+ ]) {
255
+ if (env[key] !== undefined) {
256
+ throw new Error(`${key} is ignored by ${context}`);
257
+ }
258
+ }
259
+ }
260
+ function rejectIgnoredResolverOptions(config) {
261
+ const selection = config.sandboxProviderSelection;
262
+ const opts = config.sandboxProviderSelectionOptions ?? {};
263
+ if (selection === undefined || selection.type === "none") {
264
+ rejectDefinedOption(opts.allowDockerLocal, "allowDockerLocal", "no provider");
265
+ rejectDefinedOption(opts.allowMicrosandboxLocal, "allowMicrosandboxLocal", "no provider");
266
+ rejectDefinedOption(opts.allowUnsafeHostPassthrough, "allowUnsafeHostPassthrough", "no provider");
267
+ rejectDefinedOption(opts.hostPassthroughWorkspaceRoot, "hostPassthroughWorkspaceRoot", "no provider");
268
+ return;
269
+ }
270
+ if (selection.type === "docker-local") {
271
+ rejectDefinedOption(opts.allowMicrosandboxLocal, "allowMicrosandboxLocal", "docker-local");
272
+ rejectDefinedOption(opts.allowUnsafeHostPassthrough, "allowUnsafeHostPassthrough", "docker-local");
273
+ rejectDefinedOption(opts.hostPassthroughWorkspaceRoot, "hostPassthroughWorkspaceRoot", "docker-local");
274
+ return;
275
+ }
276
+ if (selection.type === "microsandbox-local") {
277
+ rejectDefinedOption(opts.allowDockerLocal, "allowDockerLocal", "microsandbox-local");
278
+ rejectDefinedOption(opts.allowUnsafeHostPassthrough, "allowUnsafeHostPassthrough", "microsandbox-local");
279
+ rejectDefinedOption(opts.hostPassthroughWorkspaceRoot, "hostPassthroughWorkspaceRoot", "microsandbox-local");
280
+ return;
281
+ }
282
+ if (selection.type === "host-passthrough") {
283
+ rejectDefinedOption(opts.allowDockerLocal, "allowDockerLocal", "host-passthrough");
284
+ rejectDefinedOption(opts.allowMicrosandboxLocal, "allowMicrosandboxLocal", "host-passthrough");
285
+ }
286
+ }
287
+ function rejectDefinedOption(value, optionName, context) {
288
+ if (value !== undefined) {
289
+ throw new Error(`${optionName} is ignored by ${context}`);
290
+ }
291
+ }
292
+ function rejectDockerEnv(env, context) {
293
+ if (env.OMA_ALLOW_DOCKER_LOCAL !== undefined) {
294
+ throw new Error(`OMA_ALLOW_DOCKER_LOCAL is ignored by ${context}`);
295
+ }
296
+ if (env.OMA_DOCKER_REAP_STALE_CONTAINERS_OLDER_THAN_MS !== undefined) {
297
+ throw new Error(`OMA_DOCKER_REAP_STALE_CONTAINERS_OLDER_THAN_MS is ignored by ${context}`);
298
+ }
299
+ }
300
+ function rejectHostPassthroughEnv(env, context) {
301
+ if (env.OMA_ALLOW_UNSAFE_HOST_PASSTHROUGH !== undefined) {
302
+ throw new Error(`OMA_ALLOW_UNSAFE_HOST_PASSTHROUGH is ignored by ${context}`);
303
+ }
304
+ if (env.OMA_UNSAFE_ALLOW_HOST_PASSTHROUGH !== undefined) {
305
+ throw new Error(`OMA_UNSAFE_ALLOW_HOST_PASSTHROUGH is ignored by ${context}`);
306
+ }
307
+ if (env.OMA_HOST_PASSTHROUGH_WORKSPACE_ROOT !== undefined) {
308
+ throw new Error(`OMA_HOST_PASSTHROUGH_WORKSPACE_ROOT is ignored by ${context}`);
309
+ }
310
+ }
311
+ function rejectMicrosandboxEnv(env, context) {
312
+ if (env.OMA_ALLOW_MICROSANDBOX_LOCAL !== undefined) {
313
+ throw new Error(`OMA_ALLOW_MICROSANDBOX_LOCAL is ignored by ${context}`);
314
+ }
315
+ if (env.OMA_MICROSANDBOX_REAP_STALE_SANDBOXES_OLDER_THAN_MS !== undefined) {
316
+ throw new Error(`OMA_MICROSANDBOX_REAP_STALE_SANDBOXES_OLDER_THAN_MS is ignored by ${context}`);
317
+ }
318
+ }
319
+ function rejectEnvAllowlist(env, context) {
320
+ if (env.OMA_SANDBOX_ENV_ALLOWLIST !== undefined) {
321
+ throw new Error(`OMA_SANDBOX_ENV_ALLOWLIST requires ${context}`);
322
+ }
323
+ }
324
+ function rejectOperationTimeoutEnv(env, context) {
325
+ if (env.OMA_SANDBOX_OPERATION_TIMEOUT_MS !== undefined) {
326
+ throw new Error(`OMA_SANDBOX_OPERATION_TIMEOUT_MS is ignored by ${context}`);
327
+ }
328
+ }
@@ -0,0 +1,25 @@
1
+ import { RuntimeTurnOwnershipLostError, } from "./events/types.js";
2
+ import { canCommitRuntimeTurn, } from "./deployment-runtime-turn-guard.js";
3
+ export function createSingleDatabaseRuntimeEventCoordinator(opts) {
4
+ return {
5
+ commitRuntimeEventsForTurn: (input) => opts.events.withTransaction(() => {
6
+ if (!canCommitRuntimeTurn(opts, input)) {
7
+ throw new RuntimeTurnOwnershipLostError(input.turnId);
8
+ }
9
+ opts.events.appendBatchWithRuntimeChangesInTransaction(input.events, input.changes);
10
+ }),
11
+ };
12
+ }
13
+ export function createBestEffortRuntimeEventCoordinator(opts) {
14
+ return {
15
+ commitRuntimeEventsForTurn(input) {
16
+ // In-memory/test deployments do not share one database transaction
17
+ // across sessions and events. Keep the same fence as durable mode, but
18
+ // the check is best-effort before the event/runtime mutation.
19
+ if (!canCommitRuntimeTurn(opts, input)) {
20
+ throw new RuntimeTurnOwnershipLostError(input.turnId);
21
+ }
22
+ opts.events.appendBatchWithRuntimeChanges(input.events, input.changes);
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,14 @@
1
+ export function canCommitRuntimeTurn(opts, input) {
2
+ // Cross-store session + runtime-turn predicate shared by deployment
3
+ // coordinators. Some legacy store updates still enforce only owner/generation
4
+ // in their WHERE clauses until their call sites get coordinator seams.
5
+ if (!opts.sessions.retrieve(input.workspaceId, input.sessionId))
6
+ return false;
7
+ const turn = opts.events
8
+ .listPendingRuntimeTurns(input.workspaceId)
9
+ .find((candidate) => candidate.session_id === input.sessionId &&
10
+ candidate.turn_id === input.turnId);
11
+ return (turn !== undefined &&
12
+ turn.owner_id === input.ownerId &&
13
+ turn.owner_generation === input.ownerGeneration);
14
+ }
@@ -0,0 +1,25 @@
1
+ export function createSingleDatabaseSessionCoordinator(opts) {
2
+ return {
3
+ deleteSessionRows: (workspaceId, sessionId) => opts.events.withTransaction(() => {
4
+ const row = opts.sessions.delete(workspaceId, sessionId);
5
+ if (!row)
6
+ return undefined;
7
+ opts.events.deleteForSession(workspaceId, sessionId);
8
+ const deletedSessionOutputFiles = opts.files.deleteSessionOutputRows(workspaceId, sessionId);
9
+ return { row, deletedSessionOutputFiles };
10
+ }),
11
+ };
12
+ }
13
+ export function createInMemorySessionCoordinator(opts) {
14
+ return {
15
+ deleteSessionRows: (workspaceId, sessionId) => {
16
+ const row = opts.sessions.delete(workspaceId, sessionId);
17
+ if (!row)
18
+ return undefined;
19
+ // The in-memory deployment stores are separate :memory: databases, so
20
+ // this cleanup is best-effort parity with durable mode, not atomic.
21
+ opts.events.deleteForSession(workspaceId, sessionId);
22
+ return { row };
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,27 @@
1
+ import { invalidRequest } from "./errors.js";
2
+ import { canCommitRuntimeTurn, } from "./deployment-runtime-turn-guard.js";
3
+ export function createSingleDatabaseSessionOutputCoordinator(opts) {
4
+ return {
5
+ replaceSessionOutputsForRuntimeTurn: (input) =>
6
+ // Local-object storage invokes this predicate inside its SQLite metadata
7
+ // transaction. A Postgres coordinator should preserve that check/write
8
+ // atomicity with database locks instead of a process-local callback.
9
+ opts.files.replaceSessionOutputsIfLive(input.workspaceId, input.sessionId, input.files, () => canCommitRuntimeTurn(opts, input)),
10
+ };
11
+ }
12
+ export function createBestEffortSessionOutputCoordinator(opts) {
13
+ return {
14
+ async replaceSessionOutputsForRuntimeTurn(input) {
15
+ // In-memory/test deployments do not have one database transaction across
16
+ // sessions, runtime turns, and file metadata. This keeps behavior aligned
17
+ // with durable mode, but it is a best-effort check before the write.
18
+ if (!canCommitRuntimeTurn(opts, input)) {
19
+ throw inactiveSessionOutputCommit(input.sessionId);
20
+ }
21
+ return opts.files.replaceSessionOutputs(input.workspaceId, input.sessionId, input.files);
22
+ },
23
+ };
24
+ }
25
+ function inactiveSessionOutputCommit(sessionId) {
26
+ return invalidRequest(`Session outputs cannot be committed for inactive session ${sessionId}`);
27
+ }