@rivet-dev/agentos-core 0.0.0-agentos-dylib-plugin.473700e
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/README.md +204 -0
- package/dist/agent-os.d.ts +560 -0
- package/dist/agent-os.js +3624 -0
- package/dist/agent-session-types.d.ts +67 -0
- package/dist/agent-session-types.js +1 -0
- package/dist/agents.d.ts +63 -0
- package/dist/agents.js +42 -0
- package/dist/base-filesystem.d.ts +34 -0
- package/dist/base-filesystem.js +173 -0
- package/dist/cron/cron-manager.d.ts +22 -0
- package/dist/cron/cron-manager.js +160 -0
- package/dist/cron/index.d.ts +5 -0
- package/dist/cron/index.js +3 -0
- package/dist/cron/parse-schedule.d.ts +25 -0
- package/dist/cron/parse-schedule.js +66 -0
- package/dist/cron/schedule-driver.d.ts +19 -0
- package/dist/cron/schedule-driver.js +1 -0
- package/dist/cron/timer-driver.d.ts +17 -0
- package/dist/cron/timer-driver.js +61 -0
- package/dist/cron/types.d.ts +55 -0
- package/dist/cron/types.js +1 -0
- package/dist/filesystem-snapshot.d.ts +14 -0
- package/dist/filesystem-snapshot.js +105 -0
- package/dist/host-dir-mount.d.ts +38 -0
- package/dist/host-dir-mount.js +34 -0
- package/dist/host-tools-zod.d.ts +7 -0
- package/dist/host-tools-zod.js +243 -0
- package/dist/host-tools.d.ts +45 -0
- package/dist/host-tools.js +26 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/js-bridge.d.ts +3 -0
- package/dist/js-bridge.js +2 -0
- package/dist/json-rpc.d.ts +50 -0
- package/dist/json-rpc.js +22 -0
- package/dist/layers.d.ts +51 -0
- package/dist/layers.js +185 -0
- package/dist/overlay-filesystem.d.ts +19 -0
- package/dist/overlay-filesystem.js +645 -0
- package/dist/packages.d.ts +127 -0
- package/dist/packages.js +335 -0
- package/dist/runtime-compat.d.ts +471 -0
- package/dist/runtime-compat.js +2009 -0
- package/dist/runtime.d.ts +336 -0
- package/dist/runtime.js +1 -0
- package/dist/sidecar/agent-os-protocol.d.ts +219 -0
- package/dist/sidecar/agent-os-protocol.js +594 -0
- package/dist/sidecar/binary.d.ts +9 -0
- package/dist/sidecar/binary.js +30 -0
- package/dist/sidecar/cargo.d.ts +2 -0
- package/dist/sidecar/cargo.js +142 -0
- package/dist/sidecar/native-process-client.d.ts +2 -0
- package/dist/sidecar/native-process-client.js +1 -0
- package/dist/sidecar/permissions.d.ts +3 -0
- package/dist/sidecar/permissions.js +54 -0
- package/dist/sidecar/process.d.ts +6 -0
- package/dist/sidecar/process.js +8 -0
- package/dist/sidecar/rpc-client.d.ts +244 -0
- package/dist/sidecar/rpc-client.js +2028 -0
- package/dist/test/docker.d.ts +133 -0
- package/dist/test/docker.js +336 -0
- package/dist/test/file-system.d.ts +39 -0
- package/dist/test/file-system.js +562 -0
- package/dist/test/mock-s3.d.ts +42 -0
- package/dist/test/mock-s3.js +292 -0
- package/dist/test/runtime.d.ts +11 -0
- package/dist/test/runtime.js +9 -0
- package/dist/test/sandbox-agent.d.ts +9 -0
- package/dist/test/sandbox-agent.js +395 -0
- package/dist/test/terminal-harness.d.ts +27 -0
- package/dist/test/terminal-harness.js +122 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.js +1 -0
- package/fixtures/AGENTOS_SYSTEM_PROMPT.md +21 -0
- package/package.json +111 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { once } from "node:events";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { mkdtemp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, relative, resolve } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
function json(response, status, value) {
|
|
9
|
+
const body = Buffer.from(JSON.stringify(value));
|
|
10
|
+
response.writeHead(status, {
|
|
11
|
+
"content-length": String(body.length),
|
|
12
|
+
"content-type": "application/json",
|
|
13
|
+
});
|
|
14
|
+
response.end(body);
|
|
15
|
+
}
|
|
16
|
+
function problem(response, status, detail) {
|
|
17
|
+
json(response, status, {
|
|
18
|
+
type: "about:blank",
|
|
19
|
+
title: status === 404 ? "Not Found" : "Bad Request",
|
|
20
|
+
status,
|
|
21
|
+
detail,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function readBody(request) {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
for await (const chunk of request) {
|
|
27
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
28
|
+
}
|
|
29
|
+
return Buffer.concat(chunks);
|
|
30
|
+
}
|
|
31
|
+
function decodePath(rootDir, rawPath) {
|
|
32
|
+
const candidate = rawPath && rawPath.length > 0 ? rawPath : rootDir;
|
|
33
|
+
const direct = resolve(candidate.startsWith("/") ? candidate : resolve(rootDir, candidate));
|
|
34
|
+
const directRel = relative(rootDir, direct);
|
|
35
|
+
if (!(directRel.startsWith("..") || directRel === "..")) {
|
|
36
|
+
return direct;
|
|
37
|
+
}
|
|
38
|
+
const mapped = resolve(rootDir, candidate.replace(/^\/+/, ""));
|
|
39
|
+
const mappedRel = relative(rootDir, mapped);
|
|
40
|
+
if (mappedRel.startsWith("..") || mappedRel === "..") {
|
|
41
|
+
throw new Error(`Path escapes mock sandbox root: ${candidate}`);
|
|
42
|
+
}
|
|
43
|
+
return mapped;
|
|
44
|
+
}
|
|
45
|
+
function mapProcessPath(rootDir, value) {
|
|
46
|
+
const direct = resolve(value);
|
|
47
|
+
const directRel = relative(rootDir, direct);
|
|
48
|
+
if (!(directRel.startsWith("..") || directRel === "..")) {
|
|
49
|
+
return direct;
|
|
50
|
+
}
|
|
51
|
+
if (!value.startsWith("/")) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
return decodePath(rootDir, value);
|
|
55
|
+
}
|
|
56
|
+
function processInfo(proc) {
|
|
57
|
+
return {
|
|
58
|
+
id: proc.id,
|
|
59
|
+
command: proc.command,
|
|
60
|
+
args: proc.args,
|
|
61
|
+
status: proc.status,
|
|
62
|
+
pid: proc.pid,
|
|
63
|
+
exitCode: proc.exitCode,
|
|
64
|
+
cwd: proc.cwd,
|
|
65
|
+
createdAtMs: proc.createdAtMs,
|
|
66
|
+
exitedAtMs: proc.exitedAtMs,
|
|
67
|
+
interactive: proc.interactive,
|
|
68
|
+
tty: proc.tty,
|
|
69
|
+
owner: "user",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function appendLog(proc, stream, chunk) {
|
|
73
|
+
proc.sequence += 1;
|
|
74
|
+
proc.logs.push({
|
|
75
|
+
data: chunk.toString("base64"),
|
|
76
|
+
encoding: "base64",
|
|
77
|
+
sequence: proc.sequence,
|
|
78
|
+
stream,
|
|
79
|
+
timestampMs: Date.now(),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function waitForProcessExit(proc, timeoutMs = 2_000) {
|
|
83
|
+
if (proc.status === "exited") {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
await Promise.race([
|
|
87
|
+
once(proc.child, "close").then(() => undefined),
|
|
88
|
+
new Promise((resolveTimeout) => {
|
|
89
|
+
setTimeout(resolveTimeout, timeoutMs);
|
|
90
|
+
}),
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
async function runCommand(request) {
|
|
94
|
+
const startedAt = Date.now();
|
|
95
|
+
return await new Promise((resolveRun) => {
|
|
96
|
+
let stdout = "";
|
|
97
|
+
let stderr = "";
|
|
98
|
+
let settled = false;
|
|
99
|
+
let timedOut = false;
|
|
100
|
+
const child = spawn(request.command, request.args?.map((value) => mapProcessPath(request.rootDir, value)) ?? [], {
|
|
101
|
+
cwd: request.cwd ? mapProcessPath(request.rootDir, request.cwd) : undefined,
|
|
102
|
+
env: request.env ? { ...process.env, ...request.env } : process.env,
|
|
103
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
104
|
+
});
|
|
105
|
+
const finish = (value) => {
|
|
106
|
+
if (settled) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
settled = true;
|
|
110
|
+
resolveRun(value);
|
|
111
|
+
};
|
|
112
|
+
const timeout = request.timeoutMs && request.timeoutMs > 0
|
|
113
|
+
? setTimeout(() => {
|
|
114
|
+
timedOut = true;
|
|
115
|
+
child.kill("SIGKILL");
|
|
116
|
+
}, request.timeoutMs)
|
|
117
|
+
: null;
|
|
118
|
+
child.stdout.on("data", (chunk) => {
|
|
119
|
+
stdout += chunk.toString("utf8");
|
|
120
|
+
});
|
|
121
|
+
child.stderr.on("data", (chunk) => {
|
|
122
|
+
stderr += chunk.toString("utf8");
|
|
123
|
+
});
|
|
124
|
+
child.on("error", (error) => {
|
|
125
|
+
if (timeout) {
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
}
|
|
128
|
+
finish({
|
|
129
|
+
durationMs: Date.now() - startedAt,
|
|
130
|
+
exitCode: 127,
|
|
131
|
+
stderr: error.message,
|
|
132
|
+
stderrTruncated: false,
|
|
133
|
+
stdout: "",
|
|
134
|
+
stdoutTruncated: false,
|
|
135
|
+
timedOut,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
child.on("close", (code) => {
|
|
139
|
+
if (timeout) {
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
}
|
|
142
|
+
finish({
|
|
143
|
+
durationMs: Date.now() - startedAt,
|
|
144
|
+
exitCode: code,
|
|
145
|
+
stderr,
|
|
146
|
+
stderrTruncated: false,
|
|
147
|
+
stdout,
|
|
148
|
+
stdoutTruncated: false,
|
|
149
|
+
timedOut,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
export async function startMockSandboxAgent() {
|
|
155
|
+
const rootDir = await mkdtemp(resolve(tmpdir(), "agent-os-sandbox-agent-"));
|
|
156
|
+
const processes = new Map();
|
|
157
|
+
const server = createServer(async (request, response) => {
|
|
158
|
+
try {
|
|
159
|
+
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
160
|
+
const method = request.method ?? "GET";
|
|
161
|
+
if (method === "GET" && url.pathname === "/") {
|
|
162
|
+
json(response, 200, { ok: true });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (method === "GET" && url.pathname === "/v1/health") {
|
|
166
|
+
json(response, 200, { status: "ok" });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (method === "GET" && url.pathname === "/v1/fs/entries") {
|
|
170
|
+
const target = decodePath(rootDir, url.searchParams.get("path"));
|
|
171
|
+
const entries = await readdir(target, { withFileTypes: true });
|
|
172
|
+
const payload = await Promise.all(entries.map(async (entry) => {
|
|
173
|
+
const entryPath = resolve(target, entry.name);
|
|
174
|
+
const metadata = await stat(entryPath);
|
|
175
|
+
return {
|
|
176
|
+
name: entry.name,
|
|
177
|
+
path: entryPath,
|
|
178
|
+
entryType: entry.isDirectory() ? "directory" : "file",
|
|
179
|
+
size: metadata.size,
|
|
180
|
+
modified: null,
|
|
181
|
+
};
|
|
182
|
+
}));
|
|
183
|
+
payload.sort((left, right) => left.name.localeCompare(right.name));
|
|
184
|
+
json(response, 200, payload);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (method === "GET" && url.pathname === "/v1/fs/file") {
|
|
188
|
+
const target = decodePath(rootDir, url.searchParams.get("path"));
|
|
189
|
+
const bytes = await readFile(target);
|
|
190
|
+
response.writeHead(200, {
|
|
191
|
+
"content-length": String(bytes.length),
|
|
192
|
+
"content-type": "application/octet-stream",
|
|
193
|
+
});
|
|
194
|
+
response.end(bytes);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (method === "PUT" && url.pathname === "/v1/fs/file") {
|
|
198
|
+
const target = decodePath(rootDir, url.searchParams.get("path"));
|
|
199
|
+
await mkdir(dirname(target), { recursive: true });
|
|
200
|
+
const body = await readBody(request);
|
|
201
|
+
await writeFile(target, body);
|
|
202
|
+
json(response, 200, {
|
|
203
|
+
path: target,
|
|
204
|
+
bytesWritten: body.length,
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (method === "DELETE" && url.pathname === "/v1/fs/entry") {
|
|
209
|
+
const target = decodePath(rootDir, url.searchParams.get("path"));
|
|
210
|
+
await rm(target, {
|
|
211
|
+
force: true,
|
|
212
|
+
recursive: url.searchParams.get("recursive") === "true",
|
|
213
|
+
});
|
|
214
|
+
json(response, 200, { path: target });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (method === "POST" && url.pathname === "/v1/fs/mkdir") {
|
|
218
|
+
const target = decodePath(rootDir, url.searchParams.get("path"));
|
|
219
|
+
await mkdir(target, { recursive: true });
|
|
220
|
+
json(response, 200, { path: target });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (method === "POST" && url.pathname === "/v1/fs/move") {
|
|
224
|
+
const body = JSON.parse((await readBody(request)).toString("utf8"));
|
|
225
|
+
const from = decodePath(rootDir, body.from);
|
|
226
|
+
const to = decodePath(rootDir, body.to);
|
|
227
|
+
await mkdir(dirname(to), { recursive: true });
|
|
228
|
+
await rename(from, to);
|
|
229
|
+
json(response, 200, { from, to });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (method === "GET" && url.pathname === "/v1/fs/stat") {
|
|
233
|
+
const target = decodePath(rootDir, url.searchParams.get("path"));
|
|
234
|
+
const metadata = await stat(target);
|
|
235
|
+
json(response, 200, {
|
|
236
|
+
path: target,
|
|
237
|
+
entryType: metadata.isDirectory() ? "directory" : "file",
|
|
238
|
+
size: metadata.size,
|
|
239
|
+
modified: null,
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (method === "POST" && url.pathname === "/v1/processes/run") {
|
|
244
|
+
const requestBody = JSON.parse((await readBody(request)).toString("utf8"));
|
|
245
|
+
json(response, 200, await runCommand({
|
|
246
|
+
...requestBody,
|
|
247
|
+
rootDir,
|
|
248
|
+
cwd: requestBody.cwd ?? undefined,
|
|
249
|
+
}));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (method === "POST" && url.pathname === "/v1/processes") {
|
|
253
|
+
const requestBody = JSON.parse((await readBody(request)).toString("utf8"));
|
|
254
|
+
const child = spawn(requestBody.command, requestBody.args?.map((value) => mapProcessPath(rootDir, value)) ?? [], {
|
|
255
|
+
cwd: requestBody.cwd
|
|
256
|
+
? mapProcessPath(rootDir, requestBody.cwd)
|
|
257
|
+
: undefined,
|
|
258
|
+
env: requestBody.env
|
|
259
|
+
? { ...process.env, ...requestBody.env }
|
|
260
|
+
: process.env,
|
|
261
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
262
|
+
});
|
|
263
|
+
const proc = {
|
|
264
|
+
id: randomUUID(),
|
|
265
|
+
command: requestBody.command,
|
|
266
|
+
args: requestBody.args ?? [],
|
|
267
|
+
child,
|
|
268
|
+
createdAtMs: Date.now(),
|
|
269
|
+
cwd: requestBody.cwd ?? null,
|
|
270
|
+
exitCode: null,
|
|
271
|
+
exitedAtMs: null,
|
|
272
|
+
interactive: requestBody.interactive === true,
|
|
273
|
+
logs: [],
|
|
274
|
+
pid: child.pid ?? null,
|
|
275
|
+
sequence: 0,
|
|
276
|
+
status: "running",
|
|
277
|
+
tty: requestBody.tty === true,
|
|
278
|
+
};
|
|
279
|
+
processes.set(proc.id, proc);
|
|
280
|
+
child.stdout.on("data", (chunk) => appendLog(proc, "stdout", chunk));
|
|
281
|
+
child.stderr.on("data", (chunk) => appendLog(proc, "stderr", chunk));
|
|
282
|
+
child.on("close", (code) => {
|
|
283
|
+
proc.status = "exited";
|
|
284
|
+
proc.exitCode = code;
|
|
285
|
+
proc.exitedAtMs = Date.now();
|
|
286
|
+
});
|
|
287
|
+
child.on("error", (error) => {
|
|
288
|
+
appendLog(proc, "stderr", Buffer.from(error.message, "utf8"));
|
|
289
|
+
proc.status = "exited";
|
|
290
|
+
proc.exitCode = 127;
|
|
291
|
+
proc.exitedAtMs = Date.now();
|
|
292
|
+
});
|
|
293
|
+
json(response, 200, processInfo(proc));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (method === "GET" && url.pathname === "/v1/processes") {
|
|
297
|
+
json(response, 200, {
|
|
298
|
+
processes: Array.from(processes.values()).map(processInfo),
|
|
299
|
+
});
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const processMatch = url.pathname.match(/^\/v1\/processes\/([^/]+)(?:\/(stop|kill|logs|input))?$/);
|
|
303
|
+
if (processMatch) {
|
|
304
|
+
const [, rawId, action] = processMatch;
|
|
305
|
+
const proc = processes.get(decodeURIComponent(rawId));
|
|
306
|
+
if (!proc) {
|
|
307
|
+
problem(response, 404, `Unknown process: ${rawId}`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (method === "POST" && action === "stop") {
|
|
311
|
+
if (proc.status === "running") {
|
|
312
|
+
proc.child.kill("SIGTERM");
|
|
313
|
+
await waitForProcessExit(proc);
|
|
314
|
+
}
|
|
315
|
+
json(response, 200, processInfo(proc));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (method === "POST" && action === "kill") {
|
|
319
|
+
if (proc.status === "running") {
|
|
320
|
+
proc.child.kill("SIGKILL");
|
|
321
|
+
await waitForProcessExit(proc);
|
|
322
|
+
}
|
|
323
|
+
json(response, 200, processInfo(proc));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (method === "GET" && action === "logs") {
|
|
327
|
+
const tail = Number(url.searchParams.get("tail") ?? "0");
|
|
328
|
+
const stream = (url.searchParams.get("stream") ?? "combined");
|
|
329
|
+
let entries = proc.logs;
|
|
330
|
+
if (stream !== "combined") {
|
|
331
|
+
entries = entries.filter((entry) => entry.stream === stream);
|
|
332
|
+
}
|
|
333
|
+
if (Number.isFinite(tail) && tail > 0) {
|
|
334
|
+
entries = entries.slice(-tail);
|
|
335
|
+
}
|
|
336
|
+
json(response, 200, {
|
|
337
|
+
processId: proc.id,
|
|
338
|
+
stream,
|
|
339
|
+
entries,
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (method === "POST" && action === "input") {
|
|
344
|
+
const body = JSON.parse((await readBody(request)).toString("utf8"));
|
|
345
|
+
const bytes = body.encoding === "base64"
|
|
346
|
+
? Buffer.from(body.data, "base64")
|
|
347
|
+
: Buffer.from(body.data, "utf8");
|
|
348
|
+
proc.child.stdin.write(bytes);
|
|
349
|
+
json(response, 200, { bytesWritten: bytes.length });
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
problem(response, 404, `Unhandled mock sandbox-agent route: ${method} ${url.pathname}`);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
problem(response, 400, error instanceof Error ? error.message : String(error));
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
server.listen(0, "127.0.0.1");
|
|
360
|
+
await once(server, "listening");
|
|
361
|
+
const address = server.address();
|
|
362
|
+
if (!address || typeof address === "string") {
|
|
363
|
+
throw new Error("Mock sandbox-agent failed to bind to a TCP port");
|
|
364
|
+
}
|
|
365
|
+
const baseUrl = `http://127.0.0.1:${address.port}`;
|
|
366
|
+
const { SandboxAgent } = await import("sandbox-agent");
|
|
367
|
+
const client = await SandboxAgent.connect({
|
|
368
|
+
baseUrl,
|
|
369
|
+
waitForHealth: { timeoutMs: 5_000 },
|
|
370
|
+
});
|
|
371
|
+
return {
|
|
372
|
+
baseUrl,
|
|
373
|
+
client,
|
|
374
|
+
rootDir,
|
|
375
|
+
path: (...segments) => resolve(rootDir, ...segments),
|
|
376
|
+
stop: async () => {
|
|
377
|
+
for (const proc of processes.values()) {
|
|
378
|
+
if (proc.status === "running") {
|
|
379
|
+
proc.child.kill("SIGKILL");
|
|
380
|
+
await waitForProcessExit(proc);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
await new Promise((resolveClose, rejectClose) => {
|
|
384
|
+
server.close((error) => {
|
|
385
|
+
if (error) {
|
|
386
|
+
rejectClose(error);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
resolveClose();
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
await rm(rootDir, { force: true, recursive: true });
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TerminalHarness wires openShell() to a headless xterm Terminal so tests can
|
|
3
|
+
* assert against deterministic terminal screen state.
|
|
4
|
+
*/
|
|
5
|
+
import { Terminal } from "@xterm/headless";
|
|
6
|
+
import type { Kernel } from "../runtime-compat.js";
|
|
7
|
+
type ShellHandle = ReturnType<Kernel["openShell"]>;
|
|
8
|
+
export declare class TerminalHarness {
|
|
9
|
+
readonly term: Terminal;
|
|
10
|
+
readonly shell: ShellHandle;
|
|
11
|
+
private typing;
|
|
12
|
+
private disposed;
|
|
13
|
+
constructor(kernel: Kernel, options?: {
|
|
14
|
+
cols?: number;
|
|
15
|
+
rows?: number;
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
});
|
|
19
|
+
type(input: string): Promise<void>;
|
|
20
|
+
private typeInternal;
|
|
21
|
+
screenshotTrimmed(): string;
|
|
22
|
+
line(row: number): string;
|
|
23
|
+
waitFor(text: string, occurrence?: number, timeoutMs?: number): Promise<void>;
|
|
24
|
+
exit(): Promise<number>;
|
|
25
|
+
dispose(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TerminalHarness wires openShell() to a headless xterm Terminal so tests can
|
|
3
|
+
* assert against deterministic terminal screen state.
|
|
4
|
+
*/
|
|
5
|
+
import { Terminal } from "@xterm/headless";
|
|
6
|
+
const SETTLE_MS = 50;
|
|
7
|
+
const POLL_MS = 20;
|
|
8
|
+
const DEFAULT_WAIT_TIMEOUT_MS = 5_000;
|
|
9
|
+
export class TerminalHarness {
|
|
10
|
+
term;
|
|
11
|
+
shell;
|
|
12
|
+
typing = false;
|
|
13
|
+
disposed = false;
|
|
14
|
+
constructor(kernel, options) {
|
|
15
|
+
const cols = options?.cols ?? 80;
|
|
16
|
+
const rows = options?.rows ?? 24;
|
|
17
|
+
this.term = new Terminal({ cols, rows, allowProposedApi: true });
|
|
18
|
+
this.shell = kernel.openShell({
|
|
19
|
+
cols,
|
|
20
|
+
rows,
|
|
21
|
+
env: options?.env,
|
|
22
|
+
cwd: options?.cwd,
|
|
23
|
+
onStderr: (data) => {
|
|
24
|
+
this.term.write(data);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
this.shell.onData = (data) => {
|
|
28
|
+
this.term.write(data);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async type(input) {
|
|
32
|
+
if (this.typing) {
|
|
33
|
+
throw new Error("TerminalHarness.type() called while previous type() is still in-flight");
|
|
34
|
+
}
|
|
35
|
+
this.typing = true;
|
|
36
|
+
try {
|
|
37
|
+
await this.typeInternal(input);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
this.typing = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
typeInternal(input) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
let timer = null;
|
|
46
|
+
const originalOnData = this.shell.onData;
|
|
47
|
+
const resetTimer = () => {
|
|
48
|
+
if (timer !== null)
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
timer = setTimeout(() => {
|
|
51
|
+
this.shell.onData = originalOnData;
|
|
52
|
+
resolve();
|
|
53
|
+
}, SETTLE_MS);
|
|
54
|
+
};
|
|
55
|
+
this.shell.onData = (data) => {
|
|
56
|
+
this.term.write(data);
|
|
57
|
+
resetTimer();
|
|
58
|
+
};
|
|
59
|
+
resetTimer();
|
|
60
|
+
this.shell.write(input);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
screenshotTrimmed() {
|
|
64
|
+
const buf = this.term.buffer.active;
|
|
65
|
+
const lines = [];
|
|
66
|
+
for (let row = 0; row < this.term.rows; row++) {
|
|
67
|
+
const line = buf.getLine(buf.viewportY + row);
|
|
68
|
+
lines.push(line ? line.translateToString(true) : "");
|
|
69
|
+
}
|
|
70
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
71
|
+
lines.pop();
|
|
72
|
+
}
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
line(row) {
|
|
76
|
+
const buf = this.term.buffer.active;
|
|
77
|
+
const line = buf.getLine(buf.viewportY + row);
|
|
78
|
+
return line ? line.translateToString(true) : "";
|
|
79
|
+
}
|
|
80
|
+
async waitFor(text, occurrence = 1, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
|
|
81
|
+
const deadline = Date.now() + timeoutMs;
|
|
82
|
+
while (true) {
|
|
83
|
+
const screen = this.screenshotTrimmed();
|
|
84
|
+
let count = 0;
|
|
85
|
+
let idx = -1;
|
|
86
|
+
while (true) {
|
|
87
|
+
idx = screen.indexOf(text, idx + 1);
|
|
88
|
+
if (idx === -1)
|
|
89
|
+
break;
|
|
90
|
+
count++;
|
|
91
|
+
if (count >= occurrence)
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (Date.now() >= deadline) {
|
|
95
|
+
throw new Error(`waitFor("${text}", ${occurrence}) timed out after ${timeoutMs}ms.\n` +
|
|
96
|
+
`Expected: "${text}" (occurrence ${occurrence})\n` +
|
|
97
|
+
`Screen:\n${screen}`);
|
|
98
|
+
}
|
|
99
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async exit() {
|
|
103
|
+
this.shell.write("\x04");
|
|
104
|
+
return this.shell.wait();
|
|
105
|
+
}
|
|
106
|
+
async dispose() {
|
|
107
|
+
if (this.disposed)
|
|
108
|
+
return;
|
|
109
|
+
this.disposed = true;
|
|
110
|
+
try {
|
|
111
|
+
this.shell.kill();
|
|
112
|
+
await Promise.race([
|
|
113
|
+
this.shell.wait(),
|
|
114
|
+
new Promise((resolve) => setTimeout(resolve, 500)),
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Shell may already be gone.
|
|
119
|
+
}
|
|
120
|
+
this.term.dispose();
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { AgentCapabilities, AgentInfo, AgentOsCreateSidecarOptions, AgentOsLimits, AgentOsOptions, AgentOsSharedSidecarOptions, AgentOsSidecarConfig, AgentOsSidecarDescription, AgentRegistryEntry, BatchReadResult, BatchWriteEntry, BatchWriteResult, CreateSessionOptions, DirEntry, McpServerConfig, McpServerConfigLocal, McpServerConfigRemote, MountConfig, MountConfigJsonObject, MountConfigJsonPrimitive, MountConfigJsonValue, NativeMountConfig, NativeMountPluginDescriptor, OverlayMountConfig, PermissionReply, PermissionRequest, PermissionRequestHandler, PlainMountConfig, ProcessTreeNode, PromptResult, ReaddirRecursiveOptions, ResumeSessionOptions, ResumeSessionResult, RootFilesystemConfig, RootLowerInput, SessionConfigOption, SessionEventHandler, SessionInfo, SessionInitData, SessionMode, SessionModeState, SpawnedProcessInfo, } from "./agent-os.js";
|
|
2
|
+
export type { AgentConfig, AgentType } from "./agents.js";
|
|
3
|
+
export type { CronAction, CronEvent, CronEventHandler, CronJob, CronJobInfo, CronJobOptions, ScheduleDriver, ScheduleEntry, ScheduleHandle, } from "./cron/index.js";
|
|
4
|
+
export type { HostDirBackendOptions, HostDirMountPluginConfig, NodeModulesMountConfig, } from "./host-dir-mount.js";
|
|
5
|
+
export type { HostTool, ToolExample, ToolKit } from "./host-tools.js";
|
|
6
|
+
export type { AcpTimeoutErrorData, JsonRpcError, JsonRpcErrorData, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, UnknownSessionErrorData, } from "./json-rpc.js";
|
|
7
|
+
export type { FilesystemSnapshotExport, LayerHandle, LayerStore, OverlayFilesystemMode, RootSnapshotExport, SnapshotImportSource, SnapshotLayerHandle, WritableLayerHandle, } from "./layers.js";
|
|
8
|
+
export type { AgentSoftwareDescriptor, AnySoftwareDescriptor, SoftwareContext, SoftwareDescriptor, SoftwareInput, SoftwareRoot, ToolSoftwareDescriptor, WasmCommandDirDescriptor, WasmCommandSoftwareDescriptor, } from "./packages.js";
|
|
9
|
+
export type { ChildProcessPermissions, ConnectTerminalOptions, EnvPermissions, ExecOptions, ExecResult, FsPermissionRule, FsPermissions, KernelExecOptions, KernelExecResult, KernelSpawnOptions, NetworkAccessRequest, NetworkPermissions, OpenShellOptions, PatternPermissionRule, PermissionDecision, PermissionMode, Permissions, ProcessInfo, RulePermissions, StdioChannel, TimingMitigation, VirtualDirEntry, VirtualFileSystem, VirtualStat, } from "./runtime.js";
|
|
10
|
+
export type { PromptCapabilities } from "./agent-session-types.js";
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# agentOS
|
|
2
|
+
|
|
3
|
+
You are running inside agentOS, a Linux-like operating system for coding agents.
|
|
4
|
+
|
|
5
|
+
Known limitations:
|
|
6
|
+
|
|
7
|
+
- Binaries inside agentOS are special. You cannot download and install arbitrary binaries (e.g. via curl or apt).
|
|
8
|
+
- No file watching (inotify, fs.watch).
|
|
9
|
+
|
|
10
|
+
## Bug Reports
|
|
11
|
+
|
|
12
|
+
If you find a bug in something that does not behave like you would expect in agentOS, generate a bug report and indicate the user should file it under: https://github.com/rivet-dev/rivet/issues (This repository holds agentOS inside of it.)
|
|
13
|
+
|
|
14
|
+
## Tools CLI
|
|
15
|
+
|
|
16
|
+
Tools are available as CLI commands:
|
|
17
|
+
|
|
18
|
+
- `agentos list-tools` — list all available toolkits and tools
|
|
19
|
+
- `agentos-{toolkit} {tool} --help` — show usage for a specific tool
|
|
20
|
+
- `agentos-{toolkit} {tool} --flag value` — invoke a tool
|
|
21
|
+
|