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.
- package/LICENSE +93 -0
- package/README.md +248 -0
- package/bin/oma.mjs +499 -0
- package/dist/scripts/alpha-openai-compatible-fixture.mjs +134 -0
- package/dist/scripts/alpha-smoke-models.mjs +92 -0
- package/dist/scripts/alpha-smoke.mjs +581 -0
- package/dist/scripts/oma-doctor.js +230 -0
- package/dist/scripts/oma-models.js +299 -0
- package/dist/scripts/oma-workspaces.js +133 -0
- package/dist/src/control-plane/admin/auth.js +43 -0
- package/dist/src/control-plane/admin/routes.js +76 -0
- package/dist/src/control-plane/admin/service.js +102 -0
- package/dist/src/control-plane/admission.js +90 -0
- package/dist/src/control-plane/agents/routes.js +63 -0
- package/dist/src/control-plane/agents/service.js +687 -0
- package/dist/src/control-plane/agents/store.js +391 -0
- package/dist/src/control-plane/agents/types.js +1 -0
- package/dist/src/control-plane/api-constants.js +3 -0
- package/dist/src/control-plane/app.js +907 -0
- package/dist/src/control-plane/console/auth.js +68 -0
- package/dist/src/control-plane/console/static.js +112 -0
- package/dist/src/control-plane/deployment-runtime-config.js +328 -0
- package/dist/src/control-plane/deployment-runtime-event-coordinator.js +25 -0
- package/dist/src/control-plane/deployment-runtime-turn-guard.js +14 -0
- package/dist/src/control-plane/deployment-session-coordinator.js +25 -0
- package/dist/src/control-plane/deployment-session-output-coordinator.js +27 -0
- package/dist/src/control-plane/deployment-storage.js +354 -0
- package/dist/src/control-plane/egress/image.js +48 -0
- package/dist/src/control-plane/egress/policy.js +571 -0
- package/dist/src/control-plane/egress/presets.js +77 -0
- package/dist/src/control-plane/egress/proxy.js +72 -0
- package/dist/src/control-plane/egress/ssrf.js +106 -0
- package/dist/src/control-plane/egress/utils/debug.js +14 -0
- package/dist/src/control-plane/egress/vendor/http-proxy.js +335 -0
- package/dist/src/control-plane/egress/vendor/mitm-ca.js +226 -0
- package/dist/src/control-plane/egress/vendor/mitm-leaf.js +124 -0
- package/dist/src/control-plane/egress/vendor/parent-proxy.js +438 -0
- package/dist/src/control-plane/egress/vendor/request-filter.js +105 -0
- package/dist/src/control-plane/egress/vendor/sandbox-config.js +1 -0
- package/dist/src/control-plane/egress/vendor/tls-terminate-proxy.js +269 -0
- package/dist/src/control-plane/environments/routes.js +54 -0
- package/dist/src/control-plane/environments/service.js +112 -0
- package/dist/src/control-plane/environments/store.js +132 -0
- package/dist/src/control-plane/environments/types.js +1 -0
- package/dist/src/control-plane/errors.js +81 -0
- package/dist/src/control-plane/events/broadcaster.js +217 -0
- package/dist/src/control-plane/events/constants.js +2 -0
- package/dist/src/control-plane/events/persist.js +39 -0
- package/dist/src/control-plane/events/request.js +190 -0
- package/dist/src/control-plane/events/routes.js +159 -0
- package/dist/src/control-plane/events/runtime-helpers.js +134 -0
- package/dist/src/control-plane/events/service.js +1985 -0
- package/dist/src/control-plane/events/session-guards.js +29 -0
- package/dist/src/control-plane/events/sse.js +6 -0
- package/dist/src/control-plane/events/store.js +772 -0
- package/dist/src/control-plane/events/tool-persistence.js +102 -0
- package/dist/src/control-plane/events/types.js +21 -0
- package/dist/src/control-plane/files/routes.js +121 -0
- package/dist/src/control-plane/files/service.js +65 -0
- package/dist/src/control-plane/files/store-common.js +87 -0
- package/dist/src/control-plane/files/store-local.js +488 -0
- package/dist/src/control-plane/files/store-memory.js +270 -0
- package/dist/src/control-plane/files/store.js +2 -0
- package/dist/src/control-plane/files/types.js +7 -0
- package/dist/src/control-plane/http.js +25 -0
- package/dist/src/control-plane/ids.js +43 -0
- package/dist/src/control-plane/logging.js +263 -0
- package/dist/src/control-plane/models/auth-storage-backend.js +238 -0
- package/dist/src/control-plane/models/catalog.js +78 -0
- package/dist/src/control-plane/models/config-security.js +376 -0
- package/dist/src/control-plane/models/deployment-config.js +93 -0
- package/dist/src/control-plane/models/routes.js +32 -0
- package/dist/src/control-plane/models/service.js +138 -0
- package/dist/src/control-plane/observability/instruments.js +65 -0
- package/dist/src/control-plane/observability/metrics.js +125 -0
- package/dist/src/control-plane/observability/routes.js +95 -0
- package/dist/src/control-plane/openapi/document.js +282 -0
- package/dist/src/control-plane/openapi/routes.js +78 -0
- package/dist/src/control-plane/request-idempotency.js +85 -0
- package/dist/src/control-plane/secrets/envelope.js +132 -0
- package/dist/src/control-plane/secrets/master-key.js +44 -0
- package/dist/src/control-plane/secrets/routes.js +20 -0
- package/dist/src/control-plane/secrets/service.js +71 -0
- package/dist/src/control-plane/secrets/store.js +212 -0
- package/dist/src/control-plane/secrets/types.js +3 -0
- package/dist/src/control-plane/sessions/pi/custom-tools.js +146 -0
- package/dist/src/control-plane/sessions/pi/mcp/bridge.js +527 -0
- package/dist/src/control-plane/sessions/pi/mcp/client.js +157 -0
- package/dist/src/control-plane/sessions/pi/mcp/credential.js +28 -0
- package/dist/src/control-plane/sessions/pi/mcp/fetch.js +69 -0
- package/dist/src/control-plane/sessions/pi/mcp/probe.js +202 -0
- package/dist/src/control-plane/sessions/pi/mcp/runtime.js +13 -0
- package/dist/src/control-plane/sessions/pi/runner.js +1104 -0
- package/dist/src/control-plane/sessions/pi/sandbox/cma-glob.js +388 -0
- package/dist/src/control-plane/sessions/pi/sandbox/cma-grep.js +124 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker-egress.js +347 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker.js +1589 -0
- package/dist/src/control-plane/sessions/pi/sandbox/glob.js +48 -0
- package/dist/src/control-plane/sessions/pi/sandbox/image.js +7 -0
- package/dist/src/control-plane/sessions/pi/sandbox/microsandbox.js +1677 -0
- package/dist/src/control-plane/sessions/pi/sandbox/provider.js +561 -0
- package/dist/src/control-plane/sessions/pi/sandbox/selection.js +168 -0
- package/dist/src/control-plane/sessions/pi/span-normalizer.js +82 -0
- package/dist/src/control-plane/sessions/pi/tool-permissions.js +392 -0
- package/dist/src/control-plane/sessions/pi/translator.js +100 -0
- package/dist/src/control-plane/sessions/request.js +164 -0
- package/dist/src/control-plane/sessions/resources.js +63 -0
- package/dist/src/control-plane/sessions/routes.js +88 -0
- package/dist/src/control-plane/sessions/serialize.js +24 -0
- package/dist/src/control-plane/sessions/service.js +733 -0
- package/dist/src/control-plane/sessions/store.js +538 -0
- package/dist/src/control-plane/sessions/types.js +1 -0
- package/dist/src/control-plane/skills/archive.js +169 -0
- package/dist/src/control-plane/skills/routes.js +41 -0
- package/dist/src/control-plane/skills/service.js +29 -0
- package/dist/src/control-plane/skills/store.js +233 -0
- package/dist/src/control-plane/skills/types.js +5 -0
- package/dist/src/control-plane/sqlite-transaction.js +15 -0
- package/dist/src/control-plane/vaults/mcp-oauth-validate.js +165 -0
- package/dist/src/control-plane/vaults/oauth-refresh-ticker.js +43 -0
- package/dist/src/control-plane/vaults/oauth-refresh.js +448 -0
- package/dist/src/control-plane/vaults/routes.js +66 -0
- package/dist/src/control-plane/vaults/service.js +553 -0
- package/dist/src/control-plane/vaults/store.js +691 -0
- package/dist/src/control-plane/vaults/types.js +1 -0
- package/dist/src/control-plane/wake-loop.js +65 -0
- package/dist/src/control-plane/wiring.js +92 -0
- package/dist/src/control-plane/workspace.js +4 -0
- package/dist/src/control-plane/workspaces/store.js +158 -0
- package/dist/src/egress-proxy-main.js +103 -0
- package/dist/src/main.js +124 -0
- package/dist/src/types/agents.js +1 -0
- package/dist/src/types/common.js +1 -0
- package/dist/src/types/environments.js +1 -0
- package/dist/src/types/events.js +121 -0
- package/dist/src/types/files.js +1 -0
- package/dist/src/types/json.js +19 -0
- package/dist/src/types/sessions.js +1 -0
- package/dist/ui/managed-agents-console/README.md +52 -0
- package/dist/ui/managed-agents-console/docs/agents.md +23 -0
- package/dist/ui/managed-agents-console/docs/console.md +23 -0
- package/dist/ui/managed-agents-console/docs/dreams.md +5 -0
- package/dist/ui/managed-agents-console/docs/environments.md +27 -0
- package/dist/ui/managed-agents-console/docs/events.md +19 -0
- package/dist/ui/managed-agents-console/docs/files.md +17 -0
- package/dist/ui/managed-agents-console/docs/github.md +11 -0
- package/dist/ui/managed-agents-console/docs/integrations.md +19 -0
- package/dist/ui/managed-agents-console/docs/memory.md +11 -0
- package/dist/ui/managed-agents-console/docs/migration.md +17 -0
- package/dist/ui/managed-agents-console/docs/multiagent.md +11 -0
- package/dist/ui/managed-agents-console/docs/outcomes.md +11 -0
- package/dist/ui/managed-agents-console/docs/overview.md +41 -0
- package/dist/ui/managed-agents-console/docs/permissions.md +24 -0
- package/dist/ui/managed-agents-console/docs/quickstart.md +51 -0
- package/dist/ui/managed-agents-console/docs/reference.md +27 -0
- package/dist/ui/managed-agents-console/docs/sandbox-reference.md +19 -0
- package/dist/ui/managed-agents-console/docs/sandbox-security.md +15 -0
- package/dist/ui/managed-agents-console/docs/scheduled-deployments.md +5 -0
- package/dist/ui/managed-agents-console/docs/self-hosted-sandboxes.md +15 -0
- package/dist/ui/managed-agents-console/docs/session-operations.md +21 -0
- package/dist/ui/managed-agents-console/docs/sessions.md +21 -0
- package/dist/ui/managed-agents-console/docs/skills.md +15 -0
- package/dist/ui/managed-agents-console/docs/tools.md +23 -0
- package/dist/ui/managed-agents-console/docs/vaults.md +19 -0
- package/dist/ui/managed-agents-console/docs/webhooks.md +11 -0
- package/dist/ui/managed-agents-console/index.html +47 -0
- package/dist/ui/managed-agents-console/serve.mjs +116 -0
- package/dist/ui/managed-agents-console/src/agents-files.jsx +273 -0
- package/dist/ui/managed-agents-console/src/api.js +1043 -0
- package/dist/ui/managed-agents-console/src/app.jsx +633 -0
- package/dist/ui/managed-agents-console/src/auth.jsx +215 -0
- package/dist/ui/managed-agents-console/src/console.css +733 -0
- package/dist/ui/managed-agents-console/src/data.js +188 -0
- package/dist/ui/managed-agents-console/src/detail.jsx +719 -0
- package/dist/ui/managed-agents-console/src/docs.jsx +200 -0
- package/dist/ui/managed-agents-console/src/environments.jsx +365 -0
- package/dist/ui/managed-agents-console/src/forms.jsx +494 -0
- package/dist/ui/managed-agents-console/src/icons.jsx +48 -0
- package/dist/ui/managed-agents-console/src/skills.jsx +21 -0
- package/dist/ui/managed-agents-console/src/sse.js +185 -0
- package/dist/ui/managed-agents-console/src/states.jsx +90 -0
- package/dist/ui/managed-agents-console/src/tweaks-panel.jsx +541 -0
- package/dist/ui/managed-agents-console/src/ui.jsx +190 -0
- package/dist/ui/managed-agents-console/src/vaults-data.js +89 -0
- package/dist/ui/managed-agents-console/src/vaults.jsx +171 -0
- package/dist/ui/managed-agents-console/vendor/babel.min.js +4 -0
- package/dist/ui/managed-agents-console/vendor/react-dom.production.min.js +267 -0
- package/dist/ui/managed-agents-console/vendor/react.production.min.js +31 -0
- package/dist/ui/openapi-docs/VENDOR.md +27 -0
- package/dist/ui/openapi-docs/index.html +16 -0
- package/dist/ui/openapi-docs/swagger-initializer.js +14 -0
- package/dist/ui/openapi-docs/vendor/LICENSE +202 -0
- package/dist/ui/openapi-docs/vendor/NOTICE +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui-bundle.js +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui-standalone-preset.js +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui.css +3 -0
- package/package.json +53 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { constants, existsSync, realpathSync } from "node:fs";
|
|
4
|
+
import { access, mkdir, open, opendir, readdir, readFile, stat, } from "node:fs/promises";
|
|
5
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { createBashToolDefinition, createEditToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { Type } from "typebox";
|
|
8
|
+
import { compileCmaGlob } from "./cma-glob.js";
|
|
9
|
+
import { CMA_GREP_MAX_OUTPUT_BYTES, CMA_GREP_MAX_RAW_BYTES, CMA_GREP_TIMEOUT_MS, CmaGrepInputError, assertCmaGrepContext, assertCmaGrepHeadLimit, assertCmaGrepPath, assertCmaGrepPattern, formatCmaGrepOutput, outputRelativeTo, } from "./cma-grep.js";
|
|
10
|
+
import { globMatcher, toPosix } from "./glob.js";
|
|
11
|
+
const toolExecutionContext = new AsyncLocalStorage();
|
|
12
|
+
export function createHostPassthroughSandboxProvider(opts) {
|
|
13
|
+
if (opts.unsafeAllowHostPassthrough !== true) {
|
|
14
|
+
throw new Error("Host passthrough provider requires explicit unsafe opt-in");
|
|
15
|
+
}
|
|
16
|
+
const workspaceRoot = resolve(opts.workspaceRoot);
|
|
17
|
+
const envAllowlist = new Set(opts.envAllowlist ?? []);
|
|
18
|
+
const invocations = createSandboxInvocationStats();
|
|
19
|
+
const disposed = { value: false };
|
|
20
|
+
const activeProcessGroups = new Set();
|
|
21
|
+
const readOps = {
|
|
22
|
+
access: async (absolutePath) => {
|
|
23
|
+
recordSandboxInvocation(invocations, disposed, "read");
|
|
24
|
+
await access(assertInsideWorkspace(absolutePath, workspaceRoot));
|
|
25
|
+
},
|
|
26
|
+
readFile: async (absolutePath) => {
|
|
27
|
+
recordSandboxInvocation(invocations, disposed, "read");
|
|
28
|
+
return readFile(assertInsideWorkspace(absolutePath, workspaceRoot));
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const writeOps = {
|
|
32
|
+
mkdir: async (dir) => {
|
|
33
|
+
recordSandboxInvocation(invocations, disposed, "write");
|
|
34
|
+
await mkdir(assertInsideWorkspace(dir, workspaceRoot), { recursive: true });
|
|
35
|
+
},
|
|
36
|
+
writeFile: async (absolutePath, content) => {
|
|
37
|
+
recordSandboxInvocation(invocations, disposed, "write");
|
|
38
|
+
await writeFileNoFollow(assertInsideWorkspace(absolutePath, workspaceRoot), content);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const editOps = {
|
|
42
|
+
access: async (absolutePath) => {
|
|
43
|
+
recordSandboxInvocation(invocations, disposed, "edit");
|
|
44
|
+
await access(assertInsideWorkspace(absolutePath, workspaceRoot));
|
|
45
|
+
},
|
|
46
|
+
readFile: async (absolutePath) => {
|
|
47
|
+
recordSandboxInvocation(invocations, disposed, "edit");
|
|
48
|
+
return readFile(assertInsideWorkspace(absolutePath, workspaceRoot));
|
|
49
|
+
},
|
|
50
|
+
writeFile: async (absolutePath, content) => {
|
|
51
|
+
recordSandboxInvocation(invocations, disposed, "edit");
|
|
52
|
+
await writeFileNoFollow(assertInsideWorkspace(absolutePath, workspaceRoot), content);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const findOps = {
|
|
56
|
+
exists: (absolutePath) => {
|
|
57
|
+
recordSandboxInvocation(invocations, disposed, "find");
|
|
58
|
+
return existsSync(assertInsideWorkspace(absolutePath, workspaceRoot));
|
|
59
|
+
},
|
|
60
|
+
glob: async (pattern, cwd, options) => {
|
|
61
|
+
recordSandboxInvocation(invocations, disposed, "find");
|
|
62
|
+
return globInsideWorkspace(pattern, assertInsideWorkspace(cwd, workspaceRoot), {
|
|
63
|
+
ignore: options.ignore,
|
|
64
|
+
limit: options.limit,
|
|
65
|
+
workspaceRoot,
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
const globOps = {
|
|
70
|
+
glob: async ({ pattern, cwd, signal, maxMatches, maxRawBytes, maxOutputBytes, outputBase, timeoutMs }) => {
|
|
71
|
+
recordSandboxInvocation(invocations, disposed, "glob");
|
|
72
|
+
return cmaGlobInsideWorkspace({
|
|
73
|
+
pattern,
|
|
74
|
+
cwd: assertInsideWorkspace(cwd, workspaceRoot),
|
|
75
|
+
workspaceRoot,
|
|
76
|
+
signal,
|
|
77
|
+
maxMatches,
|
|
78
|
+
maxRawBytes,
|
|
79
|
+
maxOutputBytes,
|
|
80
|
+
outputBase,
|
|
81
|
+
timeoutMs,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const grepOps = {
|
|
86
|
+
grep: async ({ pattern, cwd, signal, glob, headLimit, maxRawBytes, maxOutputBytes, outputBase, timeoutMs }) => {
|
|
87
|
+
recordSandboxInvocation(invocations, disposed, "grep");
|
|
88
|
+
return cmaGrepInsideWorkspace({
|
|
89
|
+
pattern,
|
|
90
|
+
cwd: assertInsideWorkspace(cwd, workspaceRoot),
|
|
91
|
+
workspaceRoot,
|
|
92
|
+
signal,
|
|
93
|
+
glob,
|
|
94
|
+
headLimit,
|
|
95
|
+
maxRawBytes,
|
|
96
|
+
maxOutputBytes,
|
|
97
|
+
outputBase,
|
|
98
|
+
timeoutMs,
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
const lsOps = {
|
|
103
|
+
exists: (absolutePath) => {
|
|
104
|
+
recordSandboxInvocation(invocations, disposed, "ls");
|
|
105
|
+
return existsSync(assertInsideWorkspace(absolutePath, workspaceRoot));
|
|
106
|
+
},
|
|
107
|
+
stat: async (absolutePath) => {
|
|
108
|
+
recordSandboxInvocation(invocations, disposed, "ls");
|
|
109
|
+
const result = await stat(assertInsideWorkspace(absolutePath, workspaceRoot));
|
|
110
|
+
return { isDirectory: () => result.isDirectory() };
|
|
111
|
+
},
|
|
112
|
+
readdir: async (absolutePath) => {
|
|
113
|
+
recordSandboxInvocation(invocations, disposed, "ls");
|
|
114
|
+
return readdir(assertInsideWorkspace(absolutePath, workspaceRoot));
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
const bashOps = {
|
|
118
|
+
exec: (command, cwd, options) => {
|
|
119
|
+
recordSandboxInvocation(invocations, disposed, "bash");
|
|
120
|
+
return execHostCommand(command, assertInsideWorkspace(cwd, workspaceRoot), {
|
|
121
|
+
activeProcessGroups,
|
|
122
|
+
env: filterEnv(options.env ?? {}, envAllowlist),
|
|
123
|
+
onData: options.onData,
|
|
124
|
+
signal: options.signal,
|
|
125
|
+
timeout: options.timeout,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
cwd: workspaceRoot,
|
|
131
|
+
invocations,
|
|
132
|
+
operations: {
|
|
133
|
+
bash: bashOps,
|
|
134
|
+
read: readOps,
|
|
135
|
+
write: writeOps,
|
|
136
|
+
edit: editOps,
|
|
137
|
+
find: findOps,
|
|
138
|
+
glob: globOps,
|
|
139
|
+
grep: grepOps,
|
|
140
|
+
ls: lsOps,
|
|
141
|
+
},
|
|
142
|
+
// Host passthrough is explicitly unsafe and is not a production isolation
|
|
143
|
+
// boundary. Do not expose model-facing grep here: its implementation uses
|
|
144
|
+
// JavaScript regexes and control-plane file reads, not the provider-owned
|
|
145
|
+
// in-guest POSIX grep contract Docker/microsandbox expose.
|
|
146
|
+
toolNames: new Set(["bash", "read", "write", "edit", "glob", "ls"]),
|
|
147
|
+
tools: createSandboxToolDefinitions(workspaceRoot, {
|
|
148
|
+
bash: bashOps,
|
|
149
|
+
read: readOps,
|
|
150
|
+
write: writeOps,
|
|
151
|
+
edit: editOps,
|
|
152
|
+
find: findOps,
|
|
153
|
+
glob: globOps,
|
|
154
|
+
grep: grepOps,
|
|
155
|
+
ls: lsOps,
|
|
156
|
+
}, invocations, disposed).filter((tool) => tool.name !== "grep"),
|
|
157
|
+
dispose: () => {
|
|
158
|
+
disposed.value = true;
|
|
159
|
+
for (const pid of activeProcessGroups) {
|
|
160
|
+
killProcessGroup(pid);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function assertInsideWorkspace(absolutePath, workspaceRoot) {
|
|
166
|
+
if (!isAbsolute(absolutePath)) {
|
|
167
|
+
throw new Error(`Sandbox path must be absolute: ${absolutePath}`);
|
|
168
|
+
}
|
|
169
|
+
const resolvedRoot = realpathSync(resolve(workspaceRoot));
|
|
170
|
+
const resolvedPath = resolve(absolutePath);
|
|
171
|
+
const existingAncestor = nearestExistingAncestor(resolvedPath);
|
|
172
|
+
const realAncestor = realpathSync(existingAncestor);
|
|
173
|
+
const realCandidate = resolve(realAncestor, relative(existingAncestor, resolvedPath));
|
|
174
|
+
if (isInsideOrEqual(realCandidate, resolvedRoot)) {
|
|
175
|
+
// Host passthrough is a guarded test/development provider, not real
|
|
176
|
+
// isolation. This realpath check plus O_NOFOLLOW writes reject existing
|
|
177
|
+
// symlink escapes and final-component write symlinks, but cannot close all
|
|
178
|
+
// intermediate-component TOCTOU races against a concurrent host process.
|
|
179
|
+
// Docker/remote providers must rely on their OS isolation boundary.
|
|
180
|
+
return realCandidate;
|
|
181
|
+
}
|
|
182
|
+
throw new Error(`Sandbox path escapes workspace: ${absolutePath}`);
|
|
183
|
+
}
|
|
184
|
+
function nearestExistingAncestor(absolutePath) {
|
|
185
|
+
let current = absolutePath;
|
|
186
|
+
while (!existsSync(current)) {
|
|
187
|
+
const parent = dirname(current);
|
|
188
|
+
if (parent === current)
|
|
189
|
+
return current;
|
|
190
|
+
current = parent;
|
|
191
|
+
}
|
|
192
|
+
return current;
|
|
193
|
+
}
|
|
194
|
+
function isInsideOrEqual(absolutePath, root) {
|
|
195
|
+
const rel = relative(root, absolutePath);
|
|
196
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
197
|
+
}
|
|
198
|
+
export function filterEnv(source, allowlist) {
|
|
199
|
+
const out = {};
|
|
200
|
+
for (const key of allowlist) {
|
|
201
|
+
if (source[key] !== undefined)
|
|
202
|
+
out[key] = source[key];
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
export function createSandboxInvocationStats() {
|
|
207
|
+
return {
|
|
208
|
+
total: 0,
|
|
209
|
+
byTool: emptyToolCounts(),
|
|
210
|
+
toolCallIds: emptyToolCallIds(),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
export function recordSandboxInvocation(invocations, disposed, toolName) {
|
|
214
|
+
if (disposed.value) {
|
|
215
|
+
throw new Error("Sandbox provider is disposed");
|
|
216
|
+
}
|
|
217
|
+
invocations.total += 1;
|
|
218
|
+
invocations.byTool[toolName] += 1;
|
|
219
|
+
const context = toolExecutionContext.getStore();
|
|
220
|
+
if (context?.toolName === toolName) {
|
|
221
|
+
invocations.toolCallIds[toolName].add(context.toolCallId);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const CMA_GLOB_MAX_MATCHES = 100;
|
|
225
|
+
const CMA_GLOB_MAX_RAW_BYTES = 1024 * 1024;
|
|
226
|
+
const CMA_GLOB_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
227
|
+
const CMA_GLOB_TIMEOUT_MS = 10_000;
|
|
228
|
+
function createCmaGlobToolDefinition(cwd, operations) {
|
|
229
|
+
return defineTool({
|
|
230
|
+
name: "glob",
|
|
231
|
+
label: "glob",
|
|
232
|
+
description: "Find files recursively using a glob pattern.",
|
|
233
|
+
parameters: Type.Object({
|
|
234
|
+
pattern: Type.String({ description: "Glob pattern to match" }),
|
|
235
|
+
path: Type.Optional(Type.String({ description: "Directory to search" })),
|
|
236
|
+
}, { additionalProperties: false }),
|
|
237
|
+
execute: async (_toolCallId, input, signal) => {
|
|
238
|
+
// Compile before provider execution so malformed/over-complex patterns do
|
|
239
|
+
// not start a sandbox command.
|
|
240
|
+
compileCmaGlob(input.pattern);
|
|
241
|
+
const suppliedPath = input.path;
|
|
242
|
+
const searchRoot = resolve(cwd, suppliedPath ?? ".");
|
|
243
|
+
const effectiveSignal = signal ?? new AbortController().signal;
|
|
244
|
+
const matches = await operations.glob({
|
|
245
|
+
pattern: input.pattern,
|
|
246
|
+
cwd: searchRoot,
|
|
247
|
+
signal: effectiveSignal,
|
|
248
|
+
maxMatches: CMA_GLOB_MAX_MATCHES,
|
|
249
|
+
maxRawBytes: CMA_GLOB_MAX_RAW_BYTES,
|
|
250
|
+
maxOutputBytes: CMA_GLOB_MAX_OUTPUT_BYTES,
|
|
251
|
+
outputBase: suppliedPath === undefined || !isAbsolute(suppliedPath) ? cwd : undefined,
|
|
252
|
+
timeoutMs: CMA_GLOB_TIMEOUT_MS,
|
|
253
|
+
});
|
|
254
|
+
const formatted = matches.map((match) => {
|
|
255
|
+
if (suppliedPath === undefined)
|
|
256
|
+
return toPosix(relative(cwd, match));
|
|
257
|
+
if (isAbsolute(suppliedPath))
|
|
258
|
+
return toPosix(match);
|
|
259
|
+
return toPosix(relative(cwd, match));
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
content: [{ type: "text", text: formatted.length === 0 ? "No files found" : formatted.join("\n") }],
|
|
263
|
+
details: undefined,
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
function createCmaGrepToolDefinition(_cwd, operations) {
|
|
269
|
+
return defineTool({
|
|
270
|
+
name: "grep",
|
|
271
|
+
label: "grep",
|
|
272
|
+
description: "Search file contents and return matching file paths.",
|
|
273
|
+
parameters: Type.Object({
|
|
274
|
+
pattern: Type.String({ description: "POSIX extended regular expression to search for" }),
|
|
275
|
+
path: Type.String({ description: "Absolute directory or file path to search" }),
|
|
276
|
+
glob: Type.Optional(Type.String({ description: "Optional glob filter for files to search" })),
|
|
277
|
+
context: Type.Optional(Type.Number({ description: "Accepted for CMA compatibility; output remains matching paths" })),
|
|
278
|
+
head_limit: Type.Optional(Type.Number({ description: "Maximum matching paths to return" })),
|
|
279
|
+
}, { additionalProperties: false }),
|
|
280
|
+
execute: async (_toolCallId, input, signal) => {
|
|
281
|
+
const pattern = assertCmaGrepPattern(input.pattern);
|
|
282
|
+
const path = assertCmaGrepPath(input.path);
|
|
283
|
+
const context = assertCmaGrepContext(input.context);
|
|
284
|
+
const headLimit = assertCmaGrepHeadLimit(input.head_limit);
|
|
285
|
+
let glob;
|
|
286
|
+
if (input.glob !== undefined) {
|
|
287
|
+
if (typeof input.glob !== "string" || input.glob.length === 0) {
|
|
288
|
+
throw new CmaGrepInputError("grep glob must be a non-empty string");
|
|
289
|
+
}
|
|
290
|
+
compileCmaGlob(input.glob);
|
|
291
|
+
glob = input.glob;
|
|
292
|
+
}
|
|
293
|
+
const effectiveSignal = signal ?? new AbortController().signal;
|
|
294
|
+
const matches = await operations.grep({
|
|
295
|
+
pattern,
|
|
296
|
+
cwd: path,
|
|
297
|
+
signal: effectiveSignal,
|
|
298
|
+
glob,
|
|
299
|
+
context,
|
|
300
|
+
headLimit,
|
|
301
|
+
maxRawBytes: CMA_GREP_MAX_RAW_BYTES,
|
|
302
|
+
maxOutputBytes: CMA_GREP_MAX_OUTPUT_BYTES,
|
|
303
|
+
timeoutMs: CMA_GREP_TIMEOUT_MS,
|
|
304
|
+
});
|
|
305
|
+
return {
|
|
306
|
+
content: [{ type: "text", text: formatCmaGrepOutput(matches) }],
|
|
307
|
+
details: undefined,
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
export function createSandboxToolDefinitions(cwd, operations, invocations, disposed) {
|
|
313
|
+
return [
|
|
314
|
+
withToolCallAccounting("bash", createBashToolDefinition(cwd, { operations: operations.bash }), invocations, disposed),
|
|
315
|
+
withToolCallAccounting("read", createReadToolDefinition(cwd, { operations: operations.read }), invocations, disposed),
|
|
316
|
+
withToolCallAccounting("write", createWriteToolDefinition(cwd, { operations: operations.write }), invocations, disposed),
|
|
317
|
+
withToolCallAccounting("edit", createEditToolDefinition(cwd, { operations: operations.edit }), invocations, disposed),
|
|
318
|
+
withToolCallAccounting("glob", createCmaGlobToolDefinition(cwd, operations.glob), invocations, disposed),
|
|
319
|
+
withToolCallAccounting("grep", createCmaGrepToolDefinition(cwd, operations.grep), invocations, disposed),
|
|
320
|
+
withToolCallAccounting("ls", createLsToolDefinition(cwd, { operations: operations.ls }), invocations, disposed),
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
function emptyToolCounts() {
|
|
324
|
+
return {
|
|
325
|
+
bash: 0,
|
|
326
|
+
read: 0,
|
|
327
|
+
write: 0,
|
|
328
|
+
edit: 0,
|
|
329
|
+
find: 0,
|
|
330
|
+
glob: 0,
|
|
331
|
+
grep: 0,
|
|
332
|
+
ls: 0,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function emptyToolCallIds() {
|
|
336
|
+
return {
|
|
337
|
+
bash: new Set(),
|
|
338
|
+
read: new Set(),
|
|
339
|
+
write: new Set(),
|
|
340
|
+
edit: new Set(),
|
|
341
|
+
find: new Set(),
|
|
342
|
+
glob: new Set(),
|
|
343
|
+
grep: new Set(),
|
|
344
|
+
ls: new Set(),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function withToolCallAccounting(toolName, tool, invocations, disposed) {
|
|
348
|
+
return {
|
|
349
|
+
...tool,
|
|
350
|
+
execute: async (toolCallId, params, signal, onUpdate, ctx) => {
|
|
351
|
+
if (disposed.value) {
|
|
352
|
+
throw new Error("Sandbox provider is disposed");
|
|
353
|
+
}
|
|
354
|
+
return toolExecutionContext.run({ toolName, toolCallId }, () => tool.execute(toolCallId, params, signal, onUpdate, ctx));
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
async function writeFileNoFollow(path, content) {
|
|
359
|
+
const handle = await open(path, constants.O_WRONLY |
|
|
360
|
+
constants.O_CREAT |
|
|
361
|
+
constants.O_TRUNC |
|
|
362
|
+
constants.O_NOFOLLOW, 0o666);
|
|
363
|
+
try {
|
|
364
|
+
await handle.writeFile(content, "utf8");
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
await handle.close();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async function execHostCommand(command, cwd, opts) {
|
|
371
|
+
return new Promise((resolvePromise, reject) => {
|
|
372
|
+
const child = spawn(command, {
|
|
373
|
+
cwd,
|
|
374
|
+
env: opts.env,
|
|
375
|
+
detached: true,
|
|
376
|
+
shell: true,
|
|
377
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
378
|
+
});
|
|
379
|
+
if (child.pid !== undefined) {
|
|
380
|
+
opts.activeProcessGroups.add(child.pid);
|
|
381
|
+
}
|
|
382
|
+
let settled = false;
|
|
383
|
+
let timer;
|
|
384
|
+
const settle = (fn) => {
|
|
385
|
+
if (settled)
|
|
386
|
+
return;
|
|
387
|
+
settled = true;
|
|
388
|
+
if (timer)
|
|
389
|
+
clearTimeout(timer);
|
|
390
|
+
if (child.pid !== undefined)
|
|
391
|
+
opts.activeProcessGroups.delete(child.pid);
|
|
392
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
393
|
+
fn();
|
|
394
|
+
};
|
|
395
|
+
const kill = () => {
|
|
396
|
+
if (child.pid === undefined)
|
|
397
|
+
return;
|
|
398
|
+
killProcessGroup(child.pid);
|
|
399
|
+
if (!child.killed)
|
|
400
|
+
child.kill();
|
|
401
|
+
};
|
|
402
|
+
const onAbort = () => {
|
|
403
|
+
kill();
|
|
404
|
+
settle(() => reject(new Error("Operation aborted")));
|
|
405
|
+
};
|
|
406
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
407
|
+
if (opts.timeout !== undefined && opts.timeout > 0) {
|
|
408
|
+
timer = setTimeout(() => {
|
|
409
|
+
kill();
|
|
410
|
+
settle(() => resolvePromise({ exitCode: null }));
|
|
411
|
+
}, opts.timeout * 1000);
|
|
412
|
+
}
|
|
413
|
+
child.stdout?.on("data", (chunk) => opts.onData(chunk));
|
|
414
|
+
child.stderr?.on("data", (chunk) => opts.onData(chunk));
|
|
415
|
+
child.on("error", (error) => settle(() => reject(error)));
|
|
416
|
+
child.on("close", (code) => settle(() => resolvePromise({ exitCode: code })));
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function killProcessGroup(pid) {
|
|
420
|
+
try {
|
|
421
|
+
process.kill(-pid);
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
// The process may already have exited. Callers may also kill the direct
|
|
425
|
+
// child as a fallback when they own the ChildProcess object.
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async function cmaGlobInsideWorkspace(opts) {
|
|
429
|
+
const matcher = compileCmaGlob(opts.pattern);
|
|
430
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
431
|
+
const matches = [];
|
|
432
|
+
let rawBytes = 0;
|
|
433
|
+
let outputBytes = 0;
|
|
434
|
+
const assertActive = () => {
|
|
435
|
+
if (opts.signal.aborted)
|
|
436
|
+
throw new Error("Operation aborted");
|
|
437
|
+
if (Date.now() >= deadline)
|
|
438
|
+
throw new Error("Glob operation timed out");
|
|
439
|
+
};
|
|
440
|
+
async function visit(dir) {
|
|
441
|
+
assertActive();
|
|
442
|
+
const handle = await opendir(dir);
|
|
443
|
+
for await (const entry of handle) {
|
|
444
|
+
assertActive();
|
|
445
|
+
if (matches.length >= opts.maxMatches)
|
|
446
|
+
return;
|
|
447
|
+
const fullPath = assertInsideWorkspace(`${dir}${sep}${entry.name}`, opts.workspaceRoot);
|
|
448
|
+
const relativePath = toPosix(relative(opts.cwd, fullPath));
|
|
449
|
+
if (entry.isDirectory()) {
|
|
450
|
+
await visit(fullPath);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
// Docker/microsandbox enumerate `find -type f`; host raw accounting
|
|
454
|
+
// therefore counts only equivalent emitted file records.
|
|
455
|
+
rawBytes += Buffer.byteLength(relativePath, "utf8") + 1;
|
|
456
|
+
if (rawBytes > opts.maxRawBytes) {
|
|
457
|
+
throw new Error(`Glob enumeration exceeds ${opts.maxRawBytes} raw bytes`);
|
|
458
|
+
}
|
|
459
|
+
if (!matcher.matches(relativePath))
|
|
460
|
+
continue;
|
|
461
|
+
const outputPath = opts.outputBase === undefined
|
|
462
|
+
? fullPath
|
|
463
|
+
: toPosix(relative(opts.outputBase, fullPath));
|
|
464
|
+
const addedBytes = Buffer.byteLength(outputPath, "utf8") +
|
|
465
|
+
(matches.length === 0 ? 0 : 1);
|
|
466
|
+
if (outputBytes + addedBytes > opts.maxOutputBytes) {
|
|
467
|
+
throw new Error(`Glob output exceeds ${opts.maxOutputBytes} bytes`);
|
|
468
|
+
}
|
|
469
|
+
outputBytes += addedBytes;
|
|
470
|
+
matches.push(fullPath);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
await visit(opts.cwd);
|
|
475
|
+
return matches;
|
|
476
|
+
}
|
|
477
|
+
async function cmaGrepInsideWorkspace(opts) {
|
|
478
|
+
let regex;
|
|
479
|
+
try {
|
|
480
|
+
regex = new RegExp(opts.pattern);
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
throw new Error(error instanceof Error ? error.message : "Invalid regex pattern");
|
|
484
|
+
}
|
|
485
|
+
const matcher = opts.glob === undefined ? undefined : compileCmaGlob(opts.glob);
|
|
486
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
487
|
+
const matches = [];
|
|
488
|
+
let rawBytes = 0;
|
|
489
|
+
let outputBytes = 0;
|
|
490
|
+
const assertActive = () => {
|
|
491
|
+
if (opts.signal.aborted)
|
|
492
|
+
throw new Error("Operation aborted");
|
|
493
|
+
if (Date.now() >= deadline)
|
|
494
|
+
throw new Error("Grep operation timed out");
|
|
495
|
+
};
|
|
496
|
+
async function visit(path) {
|
|
497
|
+
assertActive();
|
|
498
|
+
const stats = await stat(path);
|
|
499
|
+
if (stats.isDirectory()) {
|
|
500
|
+
const handle = await opendir(path);
|
|
501
|
+
for await (const entry of handle) {
|
|
502
|
+
assertActive();
|
|
503
|
+
if (matches.length >= opts.headLimit)
|
|
504
|
+
return;
|
|
505
|
+
await visit(assertInsideWorkspace(`${path}${sep}${entry.name}`, opts.workspaceRoot));
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (!stats.isFile())
|
|
510
|
+
return;
|
|
511
|
+
const relativePath = toPosix(relative(opts.cwd, path));
|
|
512
|
+
rawBytes += Buffer.byteLength(relativePath, "utf8") + 1;
|
|
513
|
+
if (rawBytes > opts.maxRawBytes) {
|
|
514
|
+
throw new Error(`Grep enumeration exceeds ${opts.maxRawBytes} raw bytes`);
|
|
515
|
+
}
|
|
516
|
+
if (matcher !== undefined && !matcher.matches(relativePath))
|
|
517
|
+
return;
|
|
518
|
+
const bytes = await readFile(path);
|
|
519
|
+
if (bytes.includes(0))
|
|
520
|
+
return;
|
|
521
|
+
if (!regex.test(bytes.toString("utf8")))
|
|
522
|
+
return;
|
|
523
|
+
const outputPath = opts.outputBase === undefined ? path : outputRelativeTo(opts.outputBase, path);
|
|
524
|
+
const addedBytes = Buffer.byteLength(outputPath, "utf8") +
|
|
525
|
+
(matches.length === 0 ? 0 : 1);
|
|
526
|
+
if (outputBytes + addedBytes > opts.maxOutputBytes) {
|
|
527
|
+
throw new Error(`Grep output exceeds ${opts.maxOutputBytes} bytes`);
|
|
528
|
+
}
|
|
529
|
+
outputBytes += addedBytes;
|
|
530
|
+
matches.push(path);
|
|
531
|
+
}
|
|
532
|
+
await visit(opts.cwd);
|
|
533
|
+
return matches;
|
|
534
|
+
}
|
|
535
|
+
async function globInsideWorkspace(pattern, cwd, opts) {
|
|
536
|
+
const matcher = globMatcher(pattern);
|
|
537
|
+
const ignores = opts.ignore.map(globMatcher);
|
|
538
|
+
const out = [];
|
|
539
|
+
const isIgnored = (rel) => ignores.some((ignore) => ignore(rel) || ignore(`${rel}/`));
|
|
540
|
+
async function visit(dir) {
|
|
541
|
+
if (out.length >= opts.limit)
|
|
542
|
+
return;
|
|
543
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
544
|
+
for (const entry of entries) {
|
|
545
|
+
if (out.length >= opts.limit)
|
|
546
|
+
return;
|
|
547
|
+
const fullPath = assertInsideWorkspace(`${dir}${sep}${entry.name}`, opts.workspaceRoot);
|
|
548
|
+
const rel = toPosix(relative(cwd, fullPath));
|
|
549
|
+
if (isIgnored(rel))
|
|
550
|
+
continue;
|
|
551
|
+
if (entry.isDirectory()) {
|
|
552
|
+
await visit(fullPath);
|
|
553
|
+
}
|
|
554
|
+
else if (matcher(rel)) {
|
|
555
|
+
out.push(fullPath);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
await visit(cwd);
|
|
560
|
+
return out;
|
|
561
|
+
}
|