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,92 @@
1
+ export const DEFAULT_ALPHA_MODEL_PROVIDER = "anthropic";
2
+ export const DEFAULT_ALPHA_MODEL = "claude-sonnet-5";
3
+ export const LOCAL_ALPHA_MODEL_PROVIDER = "oma-local";
4
+ export const LOCAL_ALPHA_MODEL = "oma-smoke-model";
5
+ export const LOCAL_ALPHA_API_KEY = "oma-local-keyless";
6
+
7
+ export function resolveAlphaModel(env = process.env, localCompatible = false) {
8
+ if (localCompatible) {
9
+ return {
10
+ provider: LOCAL_ALPHA_MODEL_PROVIDER,
11
+ id: LOCAL_ALPHA_MODEL,
12
+ input: { provider: LOCAL_ALPHA_MODEL_PROVIDER, id: LOCAL_ALPHA_MODEL },
13
+ explicitProvider: true,
14
+ };
15
+ }
16
+
17
+ const rawProvider = env.OMA_ALPHA_MODEL_PROVIDER;
18
+ const provider = requiredNonEmpty(rawProvider, DEFAULT_ALPHA_MODEL_PROVIDER, "OMA_ALPHA_MODEL_PROVIDER");
19
+ const id = requiredNonEmpty(env.OMA_ALPHA_MODEL, DEFAULT_ALPHA_MODEL, "OMA_ALPHA_MODEL");
20
+ return {
21
+ provider,
22
+ id,
23
+ // Preserve the existing CMA-compatible string path when the provider is
24
+ // omitted. Once an operator names a provider, prove the explicit pair.
25
+ input: rawProvider === undefined ? id : { provider, id },
26
+ explicitProvider: rawProvider !== undefined,
27
+ };
28
+ }
29
+
30
+ export function createLocalCompatibleModelsConfig(baseUrl) {
31
+ const url = new URL(baseUrl);
32
+ if (url.protocol !== "http:" || (url.hostname !== "127.0.0.1" && url.hostname !== "localhost" && url.hostname !== "::1")) {
33
+ throw new Error("The alpha compatible-provider fixture must use a loopback HTTP URL");
34
+ }
35
+ return {
36
+ providers: {
37
+ [LOCAL_ALPHA_MODEL_PROVIDER]: {
38
+ name: "OMA local compatible fixture",
39
+ baseUrl: `${baseUrl.replace(/\/+$/, "")}/v1`,
40
+ api: "openai-completions",
41
+ apiKey: LOCAL_ALPHA_API_KEY,
42
+ compat: {
43
+ supportsDeveloperRole: false,
44
+ supportsReasoningEffort: false,
45
+ },
46
+ models: [{
47
+ id: LOCAL_ALPHA_MODEL,
48
+ name: "OMA deterministic smoke model",
49
+ reasoning: false,
50
+ input: ["text"],
51
+ contextWindow: 16_384,
52
+ maxTokens: 2_048,
53
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
54
+ }],
55
+ },
56
+ },
57
+ };
58
+ }
59
+
60
+ export const LIVE_PROVIDER_SMOKE_LANES = Object.freeze([
61
+ {
62
+ provider: "anthropic",
63
+ credentialEnv: "ANTHROPIC_API_KEY",
64
+ modelEnv: "OMA_ALPHA_ANTHROPIC_MODEL",
65
+ defaultModel: "claude-haiku-4-5",
66
+ },
67
+ {
68
+ provider: "openai",
69
+ credentialEnv: "OPENAI_API_KEY",
70
+ modelEnv: "OMA_ALPHA_OPENAI_MODEL",
71
+ defaultModel: "gpt-4.1-mini",
72
+ },
73
+ {
74
+ provider: "google",
75
+ credentialEnv: "GEMINI_API_KEY",
76
+ modelEnv: "OMA_ALPHA_GOOGLE_MODEL",
77
+ defaultModel: "gemini-2.5-flash",
78
+ },
79
+ {
80
+ provider: "openrouter",
81
+ credentialEnv: "OPENROUTER_API_KEY",
82
+ modelEnv: "OMA_ALPHA_OPENROUTER_MODEL",
83
+ defaultModel: "openai/gpt-4o-mini",
84
+ },
85
+ ]);
86
+
87
+ function requiredNonEmpty(value, fallback, label) {
88
+ if (value === undefined) return fallback;
89
+ const trimmed = value.trim();
90
+ if (trimmed.length === 0) throw new Error(`${label} must not be empty`);
91
+ return trimmed;
92
+ }
@@ -0,0 +1,581 @@
1
+ #!/usr/bin/env node
2
+ import { spawn, spawnSync } from "node:child_process";
3
+ import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { setTimeout as delay } from "node:timers/promises";
7
+ import { startAlphaOpenAICompatibleFixture } from "./alpha-openai-compatible-fixture.mjs";
8
+ import {
9
+ LOCAL_ALPHA_API_KEY,
10
+ createLocalCompatibleModelsConfig,
11
+ resolveAlphaModel,
12
+ } from "./alpha-smoke-models.mjs";
13
+
14
+ const BETA = "managed-agents-2026-04-01";
15
+ const SMOKE_TOKEN = "OMA_ALPHA_SMOKE_OK";
16
+ const EGRESS_COMMAND = [
17
+ "set -euo pipefail",
18
+ "work=/workspace/.oma-egress-smoke",
19
+ "trap 'rm -rf \"$work\"' EXIT",
20
+ "mkdir -p \"$work\"",
21
+ "npm view is-number version --registry=https://registry.npmjs.org >/dev/null",
22
+ "uv venv \"$work/venv\" >/dev/null",
23
+ "uv pip install --python \"$work/venv/bin/python\" packaging==25.0 >/dev/null",
24
+ "git ls-remote https://github.com/octocat/Hello-World.git HEAD >/dev/null",
25
+ "curl -fsSL --max-time 30 -o \"$work/source.zip\" https://github.com/octocat/Hello-World/archive/refs/heads/master.zip",
26
+ "if curl -fsS --max-time 5 https://example.com >/dev/null 2>&1; then echo unexpected-egress >&2; exit 93; fi",
27
+ `printf ${SMOKE_TOKEN}`,
28
+ ].join("\n");
29
+ const DEFAULT_SANDBOX_PROVIDER = "docker-local";
30
+ const DEFAULT_TIMEOUT_MS = 120_000;
31
+ const KEY_LINE = /x-api-key: (oma_[A-Za-z0-9_-]+)/;
32
+ const URL_LINE = /open-managed-agents listening on (http:\/\/[^\s]+)/;
33
+
34
+ const state = {
35
+ child: undefined,
36
+ fixture: undefined,
37
+ home: undefined,
38
+ logs: "",
39
+ baseUrl: undefined,
40
+ apiKey: undefined,
41
+ sessionId: undefined,
42
+ keepHome: process.env.OMA_ALPHA_KEEP_HOME === "1",
43
+ };
44
+
45
+ process.on("SIGINT", () => {
46
+ cleanup().finally(() => process.exit(130));
47
+ });
48
+ process.on("SIGTERM", () => {
49
+ cleanup().finally(() => process.exit(143));
50
+ });
51
+
52
+ try {
53
+ await main();
54
+ await cleanup();
55
+ } catch (error) {
56
+ console.error("");
57
+ fail(error instanceof Error ? error.message : String(error));
58
+ if (state.logs.trim()) console.error(`\nTemporary OMA logs:\n${redactLogs(state.logs.trim().slice(-12_000))}`);
59
+ await cleanup();
60
+ process.exit(1);
61
+ }
62
+
63
+ async function main() {
64
+ heading("OMA alpha smoke");
65
+ step("Checking local prerequisites");
66
+ checkNode();
67
+
68
+ const configuredBaseUrl = process.env.OMA_ALPHA_BASE_URL;
69
+ const configuredApiKey = process.env.OMA_ALPHA_API_KEY;
70
+ const localCompatible = process.env.OMA_ALPHA_LOCAL_COMPATIBLE === "1";
71
+ const egressSmoke = process.env.OMA_ALPHA_EGRESS_SMOKE === "1";
72
+ if (egressSmoke && !localCompatible) {
73
+ throw new Error("OMA_ALPHA_EGRESS_SMOKE requires the deterministic local-compatible model fixture");
74
+ }
75
+ if (localCompatible && (configuredBaseUrl || configuredApiKey)) {
76
+ throw new Error("OMA_ALPHA_LOCAL_COMPATIBLE cannot target an existing OMA server");
77
+ }
78
+ const model = resolveAlphaModel(process.env, localCompatible);
79
+ let baseUrl;
80
+ let apiKey;
81
+ let sandboxProvider = "existing-server";
82
+
83
+ if (configuredBaseUrl || configuredApiKey) {
84
+ if (!configuredBaseUrl || !configuredApiKey) {
85
+ throw new Error("Set both OMA_ALPHA_BASE_URL and OMA_ALPHA_API_KEY, or neither.");
86
+ }
87
+ baseUrl = trimTrailingSlash(configuredBaseUrl);
88
+ apiKey = configuredApiKey;
89
+ ok(`Using existing OMA server at ${baseUrl}`);
90
+ ok("Skipping local sandbox prerequisite check for existing-server mode");
91
+ } else {
92
+ sandboxProvider = process.env.OMA_ALPHA_SANDBOX_PROVIDER ?? DEFAULT_SANDBOX_PROVIDER;
93
+ checkSandboxPrerequisites(sandboxProvider);
94
+ if (localCompatible) {
95
+ state.fixture = await startAlphaOpenAICompatibleFixture({
96
+ apiKey: LOCAL_ALPHA_API_KEY,
97
+ modelId: model.id,
98
+ token: SMOKE_TOKEN,
99
+ ...(egressSmoke ? { command: EGRESS_COMMAND } : {}),
100
+ });
101
+ ok(`Started deterministic OpenAI-compatible fixture at ${state.fixture.baseUrl}`);
102
+ }
103
+ const started = await startTemporaryOma(sandboxProvider, model, state.fixture?.baseUrl, egressSmoke);
104
+ baseUrl = started.baseUrl;
105
+ apiKey = started.apiKey;
106
+ }
107
+
108
+ await waitForApi(baseUrl, apiKey);
109
+ await verifyModelCatalog(baseUrl, apiKey, model);
110
+
111
+ const prefix = `alpha-smoke-${Date.now().toString(36)}`;
112
+
113
+ step(`Creating agent (${model.provider}/${model.id})`);
114
+ const agent = await requestJson(baseUrl, apiKey, "/v1/agents", {
115
+ method: "POST",
116
+ body: {
117
+ name: `${prefix}-agent`,
118
+ model: model.input,
119
+ system: [
120
+ "You are running the Open Managed Agents alpha smoke test.",
121
+ "You must use the bash tool exactly once.",
122
+ `Run the exact bash command provided by the user. It prints ${SMOKE_TOKEN} only after every check passes.`,
123
+ `After the tool result, reply with the exact token ${SMOKE_TOKEN} and no extra prose.`,
124
+ ].join(" "),
125
+ tools: [
126
+ {
127
+ type: "agent_toolset_20260401",
128
+ default_config: { permission_policy: "always_allow" },
129
+ configs: [
130
+ { name: "web_fetch", enabled: false },
131
+ { name: "web_search", enabled: false },
132
+ ],
133
+ },
134
+ ],
135
+ metadata: { alpha_smoke: "true", prefix },
136
+ },
137
+ });
138
+ if (agent.model?.provider !== model.provider || agent.model?.id !== model.id) {
139
+ throw new Error(
140
+ `Agent persisted unexpected model ${JSON.stringify(agent.model)}; expected ${model.provider}/${model.id}`,
141
+ );
142
+ }
143
+ ok(`Agent ${agent.id} v${agent.version}`);
144
+
145
+ let networking = { type: "limited", allowed_hosts: [] };
146
+ if (egressSmoke) {
147
+ step("Resolving the GitHub + package registries preset");
148
+ const catalog = await requestJson(baseUrl, apiKey, "/v1/environments/networking-presets", { method:"GET" });
149
+ if (catalog.deployment?.egress_supported !== true) {
150
+ throw new Error(`Deployment did not report egress support: ${catalog.deployment?.reason ?? "unknown reason"}`);
151
+ }
152
+ const preset = catalog.presets?.find((candidate) => candidate.id === "github-packages-v1");
153
+ if (!preset) throw new Error("Networking catalog did not contain github-packages-v1");
154
+ networking = preset.networking;
155
+ ok(`Using ${preset.name} (${networking.allowed_hosts.length} allowed hosts)`);
156
+ }
157
+
158
+ step(egressSmoke ? "Creating registry-enabled environment" : "Creating default-deny environment");
159
+ const environment = await requestJson(baseUrl, apiKey, "/v1/environments", {
160
+ method: "POST",
161
+ body: {
162
+ name: `${prefix}-environment`,
163
+ config: { networking },
164
+ },
165
+ });
166
+ ok(`Environment ${environment.id}`);
167
+
168
+ step("Creating session");
169
+ const session = await requestJson(baseUrl, apiKey, "/v1/sessions", {
170
+ method: "POST",
171
+ body: {
172
+ agent: agent.id,
173
+ environment_id: environment.id,
174
+ title: "OMA alpha smoke",
175
+ metadata: { alpha_smoke: "true", prefix },
176
+ },
177
+ });
178
+ state.sessionId = session.id;
179
+ ok(`Session ${session.id}`);
180
+
181
+ step("Sending prompt");
182
+ await requestJson(baseUrl, apiKey, `/v1/sessions/${session.id}/events`, {
183
+ method: "POST",
184
+ body: {
185
+ events: [
186
+ {
187
+ type: "user.message",
188
+ content: [
189
+ {
190
+ type: "text",
191
+ text: egressSmoke
192
+ ? `Use bash exactly once and run this exact command:\n${EGRESS_COMMAND}`
193
+ : `Use bash exactly once and run: printf ${SMOKE_TOKEN}`,
194
+ },
195
+ ],
196
+ },
197
+ ],
198
+ },
199
+ });
200
+ ok("Prompt accepted");
201
+
202
+ step("Waiting for session result");
203
+ const timeoutMs = parsePositiveInt(process.env.OMA_ALPHA_TIMEOUT_MS, DEFAULT_TIMEOUT_MS);
204
+ const result = await waitForResult(baseUrl, apiKey, session.id, timeoutMs);
205
+ ok(`Observed ${result.eventCount} session events`);
206
+ if (result.sawBashToolUse) {
207
+ ok("Observed bash tool_use");
208
+ } else {
209
+ throw new Error(`Session completed without an agent.tool_use for bash. Last event types: ${result.eventTypes.join(", ")}`);
210
+ }
211
+ if (result.toolResultText.includes(SMOKE_TOKEN)) {
212
+ ok("Observed expected bash tool_result output");
213
+ } else {
214
+ throw new Error(
215
+ `Session completed, but no bash agent.tool_result included ${SMOKE_TOKEN}. Last tool result: ${JSON.stringify(result.toolResultText)}`,
216
+ );
217
+ }
218
+ if (result.agentText.includes(SMOKE_TOKEN)) {
219
+ ok("Received expected agent.message token");
220
+ } else {
221
+ throw new Error(
222
+ `Session completed, but the final agent.message did not include ${SMOKE_TOKEN}. Last message: ${JSON.stringify(result.agentText)}`,
223
+ );
224
+ }
225
+ state.fixture?.assertComplete();
226
+ if (state.fixture !== undefined) ok("Observed exact custom provider/model routing across both model requests");
227
+
228
+ step("Best-effort cleanup");
229
+ await bestEffort(() => requestJson(baseUrl, apiKey, `/v1/sessions/${session.id}`, { method: "DELETE" }));
230
+ await bestEffort(() => requestJson(baseUrl, apiKey, `/v1/agents/${agent.id}/archive`, { method: "POST" }));
231
+ if (egressSmoke) {
232
+ await assertNoSessionDockerResources(session.id);
233
+ ok("No session sandbox, sidecar, or internal network remained after delete");
234
+ }
235
+ ok("Cleanup attempted");
236
+
237
+ console.log("");
238
+ console.log("Alpha smoke passed.");
239
+ console.log(`Model: ${model.provider}/${model.id}`);
240
+ console.log(`Sandbox provider: ${sandboxProvider}`);
241
+ if (egressSmoke) console.log("Egress: npm, PyPI/uv, GitHub allowed; unrelated HTTPS denied");
242
+ console.log(`Console: ${baseUrl}/console`);
243
+ }
244
+
245
+ function checkNode() {
246
+ const [major = 0, minor = 0] = process.versions.node
247
+ .split(".")
248
+ .map((part) => Number.parseInt(part, 10));
249
+ if (major < 22 || (major === 22 && minor < 19)) {
250
+ throw new Error(`Node >= 22.19.0 is required; found ${process.versions.node}`);
251
+ }
252
+ ok(`Node ${process.versions.node}`);
253
+ }
254
+
255
+ function checkSandboxPrerequisites(provider) {
256
+ if (provider === "docker-local") {
257
+ checkDocker();
258
+ return;
259
+ }
260
+ if (provider === "microsandbox-local") {
261
+ checkMicrosandbox();
262
+ return;
263
+ }
264
+ throw new Error(
265
+ `Unsupported OMA_ALPHA_SANDBOX_PROVIDER ${JSON.stringify(provider)}. Use "docker-local" or "microsandbox-local".`,
266
+ );
267
+ }
268
+
269
+ function checkDocker() {
270
+ const result = spawnSync("docker", ["info"], { encoding: "utf8" });
271
+ if (result.status !== 0) {
272
+ const detail = (result.stderr || result.stdout || "docker info failed").trim();
273
+ throw new Error(`Docker is required for the alpha smoke. Start Docker and retry. Detail: ${detail}`);
274
+ }
275
+ ok("Docker daemon reachable");
276
+ }
277
+
278
+ function checkMicrosandbox() {
279
+ const command = process.env.OMA_MICROSANDBOX_COMMAND ?? "msb";
280
+ const result = spawnSync(command, ["--version"], { encoding: "utf8" });
281
+ if (result.status !== 0) {
282
+ const detail = (result.stderr || result.stdout || `${command} --version failed`).trim();
283
+ throw new Error(
284
+ `Microsandbox is required for OMA_ALPHA_SANDBOX_PROVIDER=microsandbox-local. Install msb or set OMA_MICROSANDBOX_COMMAND. Detail: ${detail}`,
285
+ );
286
+ }
287
+ ok(`Microsandbox CLI reachable (${command})`);
288
+ }
289
+
290
+ async function startTemporaryOma(sandboxProvider, model, localFixtureBaseUrl, egressSmoke) {
291
+ state.home = await mkdtemp(join(tmpdir(), "oma-alpha-smoke-"));
292
+ const modelEnv = {};
293
+ if (localFixtureBaseUrl !== undefined) {
294
+ const piRoot = join(state.home, "pi");
295
+ await mkdir(piRoot, { recursive: true, mode: 0o700 });
296
+ await chmod(state.home, 0o700);
297
+ const modelsPath = join(piRoot, "models.json");
298
+ await writeFile(
299
+ modelsPath,
300
+ `${JSON.stringify(createLocalCompatibleModelsConfig(localFixtureBaseUrl), null, 2)}\n`,
301
+ { mode: 0o600 },
302
+ );
303
+ await chmod(modelsPath, 0o600);
304
+ Object.assign(modelEnv, {
305
+ OMA_MODEL_PROVIDERS: model.provider,
306
+ OMA_DEFAULT_MODEL_PROVIDER: model.provider,
307
+ OMA_DEFAULT_MODEL: model.id,
308
+ OMA_PI_MODELS_FILE: modelsPath,
309
+ });
310
+ }
311
+ const child = spawn(
312
+ process.execPath,
313
+ ["bin/oma.mjs", "up", "--sandbox", sandboxProvider],
314
+ {
315
+ cwd: process.cwd(),
316
+ env: {
317
+ ...process.env,
318
+ OMA_HOME: state.home,
319
+ OMA_HOST: "127.0.0.1",
320
+ OMA_PORT: "0",
321
+ ...(egressSmoke ? { OMA_SANDBOX_OPERATION_TIMEOUT_MS:"120000" } : {}),
322
+ ...modelEnv,
323
+ },
324
+ stdio: ["ignore", "pipe", "pipe"],
325
+ },
326
+ );
327
+ state.child = child;
328
+
329
+ let logs = "";
330
+ let baseUrl;
331
+ let apiKey;
332
+ child.stdout.setEncoding("utf8");
333
+ child.stderr.setEncoding("utf8");
334
+ child.stdout.on("data", (chunk) => {
335
+ logs += chunk;
336
+ state.logs = logs;
337
+ baseUrl ??= URL_LINE.exec(logs)?.[1];
338
+ apiKey ??= KEY_LINE.exec(logs)?.[1];
339
+ });
340
+ child.stderr.on("data", (chunk) => {
341
+ logs += chunk;
342
+ state.logs = logs;
343
+ });
344
+
345
+ const startedAt = Date.now();
346
+ while (Date.now() - startedAt < 15_000) {
347
+ if (child.exitCode !== null) {
348
+ throw new Error(`OMA exited during startup with code ${child.exitCode}.\n${logs.trim()}`);
349
+ }
350
+ if (baseUrl && apiKey) {
351
+ state.baseUrl = trimTrailingSlash(baseUrl);
352
+ state.apiKey = apiKey;
353
+ ok(`Started temporary OMA at ${baseUrl}`);
354
+ ok(`Temporary sandbox provider: ${sandboxProvider}`);
355
+ ok(`Temporary data: ${state.home}`);
356
+ return { baseUrl: trimTrailingSlash(baseUrl), apiKey };
357
+ }
358
+ await delay(100);
359
+ }
360
+ throw new Error(`Timed out waiting for OMA startup logs.\n${logs.trim()}`);
361
+ }
362
+
363
+ async function assertNoSessionDockerResources(sessionId) {
364
+ const filters = ["container", "network"];
365
+ const deadline = Date.now() + 10_000;
366
+ for (;;) {
367
+ const remaining = [];
368
+ for (const resource of filters) {
369
+ const args = resource === "container"
370
+ ? ["ps", "-aq", "--filter", `label=open-managed-agents.session-id=${sessionId}`]
371
+ : ["network", "ls", "-q", "--filter", `label=open-managed-agents.session-id=${sessionId}`];
372
+ const result = spawnSync("docker", args, { encoding:"utf8" });
373
+ if (result.status !== 0) throw new Error(`Could not inspect Docker ${resource} cleanup: ${result.stderr || result.stdout}`);
374
+ if (result.stdout.trim() !== "") remaining.push(`${resource}: ${result.stdout.trim()}`);
375
+ }
376
+ if (remaining.length === 0) return;
377
+ if (Date.now() >= deadline) {
378
+ throw new Error(`Docker resources remain for ${sessionId}: ${remaining.join("; ")}`);
379
+ }
380
+ await delay(100);
381
+ }
382
+ }
383
+
384
+ async function waitForApi(baseUrl, apiKey) {
385
+ const startedAt = Date.now();
386
+ while (Date.now() - startedAt < 10_000) {
387
+ try {
388
+ const res = await fetch(`${baseUrl}/v1/agents?limit=1`, {
389
+ headers: headers(apiKey),
390
+ });
391
+ if (res.status === 200) {
392
+ ok("API authenticated");
393
+ return;
394
+ }
395
+ if (res.status === 401) {
396
+ throw new Error("API key was rejected by OMA.");
397
+ }
398
+ } catch (error) {
399
+ if (Date.now() - startedAt >= 10_000) throw error;
400
+ }
401
+ await delay(100);
402
+ }
403
+ throw new Error("Timed out waiting for OMA API readiness.");
404
+ }
405
+
406
+ async function verifyModelCatalog(baseUrl, apiKey, model) {
407
+ const page = await requestJson(
408
+ baseUrl,
409
+ apiKey,
410
+ `/v1/model-catalog?provider=${encodeURIComponent(model.provider)}&limit=100`,
411
+ { method: "GET" },
412
+ );
413
+ const entry = page.data?.find((candidate) => candidate.provider === model.provider && candidate.id === model.id);
414
+ if (!entry) throw new Error(`Model catalog did not contain ${model.provider}/${model.id}`);
415
+ if (entry.credentials_configured !== true) {
416
+ throw new Error(`Model catalog reported missing credentials for ${model.provider}/${model.id}`);
417
+ }
418
+ const serialized = JSON.stringify(page);
419
+ for (const forbidden of ["baseUrl", "headers", "authPath", "modelsPath", "apiKey", "Authorization", LOCAL_ALPHA_API_KEY]) {
420
+ if (serialized.includes(forbidden)) throw new Error(`Model catalog leaked forbidden field/value ${forbidden}`);
421
+ }
422
+ ok(`Model catalog reports ${model.provider}/${model.id} ready without configuration internals`);
423
+ }
424
+
425
+ async function waitForResult(baseUrl, apiKey, sessionId, timeoutMs) {
426
+ const startedAt = Date.now();
427
+ let lastTypes = [];
428
+ while (Date.now() - startedAt < timeoutMs) {
429
+ const page = await requestJson(
430
+ baseUrl,
431
+ apiKey,
432
+ `/v1/sessions/${sessionId}/events?order=asc&limit=100`,
433
+ { method: "GET" },
434
+ );
435
+ const events = Array.isArray(page.data) ? page.data : [];
436
+ lastTypes = events.map((event) => event.type);
437
+ const error = events.find((event) => event.type === "session.error");
438
+ if (error) {
439
+ throw new Error(`Session emitted session.error: ${JSON.stringify(error)}`);
440
+ }
441
+ const message = [...events].reverse().find((event) => event.type === "agent.message");
442
+ const bashToolUse = events.find(
443
+ (event) => event.type === "agent.tool_use" && event.name === "bash",
444
+ );
445
+ const toolResults = events.filter((event) => event.type === "agent.tool_result");
446
+ const idle = events.some((event) => event.type === "session.status_idle");
447
+ if (message && idle) {
448
+ return {
449
+ eventCount: events.length,
450
+ eventTypes: lastTypes,
451
+ sawBashToolUse: bashToolUse !== undefined,
452
+ toolResultText: toolResults.map((event) => contentText(event.content)).join("\n"),
453
+ agentText: contentText(message.content),
454
+ };
455
+ }
456
+ await delay(500);
457
+ }
458
+ throw new Error(`Timed out after ${timeoutMs}ms waiting for agent.message + idle. Last event types: ${lastTypes.join(", ") || "(none)"}`);
459
+ }
460
+
461
+ async function requestJson(baseUrl, apiKey, path, opts) {
462
+ const res = await fetch(`${baseUrl}${path}`, {
463
+ method: opts.method,
464
+ headers: {
465
+ ...headers(apiKey),
466
+ ...(opts.body === undefined ? {} : { "content-type": "application/json" }),
467
+ },
468
+ body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
469
+ });
470
+ const text = await res.text();
471
+ let body;
472
+ try {
473
+ body = text.length === 0 ? null : JSON.parse(text);
474
+ } catch {
475
+ body = text;
476
+ }
477
+ if (!res.ok) {
478
+ const message = body?.error?.message ?? text ?? res.statusText;
479
+ if (String(message).includes("not available on this deployment")) {
480
+ throw new Error(
481
+ `${message}\n\nSet OMA_ALPHA_MODEL to a model available in your Pi/model registry, or configure provider credentials before running the alpha smoke.`,
482
+ );
483
+ }
484
+ throw new Error(`${opts.method} ${path} failed with HTTP ${res.status}: ${message}`);
485
+ }
486
+ return body;
487
+ }
488
+
489
+ function headers(apiKey) {
490
+ return {
491
+ "anthropic-beta": BETA,
492
+ "x-api-key": apiKey,
493
+ };
494
+ }
495
+
496
+ function contentText(content) {
497
+ if (!Array.isArray(content)) return "";
498
+ return content
499
+ .filter((block) => block && block.type === "text" && typeof block.text === "string")
500
+ .map((block) => block.text)
501
+ .join("\n");
502
+ }
503
+
504
+ async function bestEffort(fn) {
505
+ try {
506
+ await fn();
507
+ } catch {
508
+ // The temporary appliance home is removed at the end of the default smoke.
509
+ }
510
+ }
511
+
512
+ async function cleanup() {
513
+ if (
514
+ state.child !== undefined &&
515
+ state.child.exitCode === null &&
516
+ state.baseUrl !== undefined &&
517
+ state.apiKey !== undefined &&
518
+ state.sessionId !== undefined
519
+ ) {
520
+ await bestEffort(() => requestJson(
521
+ state.baseUrl,
522
+ state.apiKey,
523
+ `/v1/sessions/${state.sessionId}`,
524
+ { method:"DELETE" },
525
+ ));
526
+ state.sessionId = undefined;
527
+ }
528
+ if (state.child !== undefined && state.child.exitCode === null) {
529
+ state.child.kill("SIGTERM");
530
+ await Promise.race([
531
+ new Promise((resolve) => state.child.once("exit", resolve)),
532
+ delay(3_000),
533
+ ]);
534
+ if (state.child.exitCode === null) state.child.kill("SIGKILL");
535
+ }
536
+ if (state.fixture !== undefined) {
537
+ await state.fixture.close();
538
+ state.fixture = undefined;
539
+ }
540
+ if (state.home !== undefined && !state.keepHome) {
541
+ await rm(state.home, { recursive: true, force: true });
542
+ } else if (state.home !== undefined) {
543
+ console.log(`Kept temporary OMA_HOME: ${state.home}`);
544
+ }
545
+ }
546
+
547
+ function redactLogs(value) {
548
+ return value.replace(/oma_[A-Za-z0-9_-]+/g, "[redacted-workspace-key]");
549
+ }
550
+
551
+ function parsePositiveInt(value, fallback) {
552
+ if (value === undefined) return fallback;
553
+ const parsed = Number.parseInt(value, 10);
554
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
555
+ throw new Error(`Expected positive integer, got ${JSON.stringify(value)}`);
556
+ }
557
+ return parsed;
558
+ }
559
+
560
+ function trimTrailingSlash(value) {
561
+ return value.replace(/\/+$/, "");
562
+ }
563
+
564
+ function heading(message) {
565
+ console.log("");
566
+ console.log(message);
567
+ console.log("=".repeat(message.length));
568
+ }
569
+
570
+ function step(message) {
571
+ console.log("");
572
+ console.log(`→ ${message}`);
573
+ }
574
+
575
+ function ok(message) {
576
+ console.log(`✓ ${message}`);
577
+ }
578
+
579
+ function fail(message) {
580
+ console.error(`✗ ${message}`);
581
+ }