@agentqueue/worker 0.0.0 → 0.3.1-ci.g02c90ca5cd192c5a75a7d1d19329683cefb22d6e

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 (109) hide show
  1. package/README.md +159 -0
  2. package/THIRD_PARTY_NOTICES.md +27 -0
  3. package/dist/bin/aq-askpass.d.ts +2 -0
  4. package/dist/bin/aq-askpass.js +5 -0
  5. package/dist/bin/aq-askpass.js.map +1 -0
  6. package/dist/bin/aq.d.ts +2 -0
  7. package/dist/bin/aq.js +9 -0
  8. package/dist/bin/aq.js.map +1 -0
  9. package/dist/release.json +16 -0
  10. package/dist/src/app-server/checkpoint-codec.d.ts +4 -0
  11. package/dist/src/app-server/checkpoint-codec.js +31 -0
  12. package/dist/src/app-server/checkpoint-codec.js.map +1 -0
  13. package/dist/src/app-server/codex-token-snapshot.d.ts +11 -0
  14. package/dist/src/app-server/codex-token-snapshot.js +45 -0
  15. package/dist/src/app-server/codex-token-snapshot.js.map +1 -0
  16. package/dist/src/app-server/index.d.ts +70 -0
  17. package/dist/src/app-server/index.js +319 -0
  18. package/dist/src/app-server/index.js.map +1 -0
  19. package/dist/src/app-server/native-checkpoint.d.ts +12 -0
  20. package/dist/src/app-server/native-checkpoint.js +179 -0
  21. package/dist/src/app-server/native-checkpoint.js.map +1 -0
  22. package/dist/src/app-server/process-group.d.ts +6 -0
  23. package/dist/src/app-server/process-group.js +122 -0
  24. package/dist/src/app-server/process-group.js.map +1 -0
  25. package/dist/src/app-server/transports.d.ts +68 -0
  26. package/dist/src/app-server/transports.js +228 -0
  27. package/dist/src/app-server/transports.js.map +1 -0
  28. package/dist/src/app-server/worker-checkpoint.d.ts +15 -0
  29. package/dist/src/app-server/worker-checkpoint.js +76 -0
  30. package/dist/src/app-server/worker-checkpoint.js.map +1 -0
  31. package/dist/src/broker.d.ts +4 -0
  32. package/dist/src/broker.js +104 -0
  33. package/dist/src/broker.js.map +1 -0
  34. package/dist/src/cli.d.ts +14 -0
  35. package/dist/src/cli.js +212 -0
  36. package/dist/src/cli.js.map +1 -0
  37. package/dist/src/client.d.ts +19 -0
  38. package/dist/src/client.js +116 -0
  39. package/dist/src/client.js.map +1 -0
  40. package/dist/src/codex-owner-auth.d.ts +14 -0
  41. package/dist/src/codex-owner-auth.js +155 -0
  42. package/dist/src/codex-owner-auth.js.map +1 -0
  43. package/dist/src/codex-runtime.d.ts +32 -0
  44. package/dist/src/codex-runtime.js +383 -0
  45. package/dist/src/codex-runtime.js.map +1 -0
  46. package/dist/src/completion-outbox.d.ts +38 -0
  47. package/dist/src/completion-outbox.js +90 -0
  48. package/dist/src/completion-outbox.js.map +1 -0
  49. package/dist/src/config.d.ts +16 -0
  50. package/dist/src/config.js +57 -0
  51. package/dist/src/config.js.map +1 -0
  52. package/dist/src/diagnostics.d.ts +3 -0
  53. package/dist/src/diagnostics.js +40 -0
  54. package/dist/src/diagnostics.js.map +1 -0
  55. package/dist/src/isolation.d.ts +34 -0
  56. package/dist/src/isolation.js +690 -0
  57. package/dist/src/isolation.js.map +1 -0
  58. package/dist/src/planning-dev/analysis-runtime.d.ts +6 -0
  59. package/dist/src/planning-dev/analysis-runtime.js +65 -0
  60. package/dist/src/planning-dev/analysis-runtime.js.map +1 -0
  61. package/dist/src/planning-dev/index.d.ts +35 -0
  62. package/dist/src/planning-dev/index.js +244 -0
  63. package/dist/src/planning-dev/index.js.map +1 -0
  64. package/dist/src/planning-dev/native-binary.d.ts +1 -0
  65. package/dist/src/planning-dev/native-binary.js +67 -0
  66. package/dist/src/planning-dev/native-binary.js.map +1 -0
  67. package/dist/src/planning-dev/protocol.d.ts +30 -0
  68. package/dist/src/planning-dev/protocol.js +43 -0
  69. package/dist/src/planning-dev/protocol.js.map +1 -0
  70. package/dist/src/planning-dev/runtime.d.ts +11 -0
  71. package/dist/src/planning-dev/runtime.js +437 -0
  72. package/dist/src/planning-dev/runtime.js.map +1 -0
  73. package/dist/src/process-lock.d.ts +4 -0
  74. package/dist/src/process-lock.js +44 -0
  75. package/dist/src/process-lock.js.map +1 -0
  76. package/dist/src/provider-status.d.ts +21 -0
  77. package/dist/src/provider-status.js +73 -0
  78. package/dist/src/provider-status.js.map +1 -0
  79. package/dist/src/readiness.d.ts +15 -0
  80. package/dist/src/readiness.js +145 -0
  81. package/dist/src/readiness.js.map +1 -0
  82. package/dist/src/register.d.ts +5 -0
  83. package/dist/src/register.js +178 -0
  84. package/dist/src/register.js.map +1 -0
  85. package/dist/src/runtime.d.ts +100 -0
  86. package/dist/src/runtime.js +2288 -0
  87. package/dist/src/runtime.js.map +1 -0
  88. package/dist/src/service.d.ts +11 -0
  89. package/dist/src/service.js +119 -0
  90. package/dist/src/service.js.map +1 -0
  91. package/dist/src/supervisor.d.ts +9 -0
  92. package/dist/src/supervisor.js +157 -0
  93. package/dist/src/supervisor.js.map +1 -0
  94. package/dist/src/turn-checkpoint.d.ts +58 -0
  95. package/dist/src/turn-checkpoint.js +314 -0
  96. package/dist/src/turn-checkpoint.js.map +1 -0
  97. package/dist/src/types.d.ts +168 -0
  98. package/dist/src/types.js +2 -0
  99. package/dist/src/types.js.map +1 -0
  100. package/dist/src/usage-limit.d.ts +13 -0
  101. package/dist/src/usage-limit.js +75 -0
  102. package/dist/src/usage-limit.js.map +1 -0
  103. package/dist/src/version.d.ts +6 -0
  104. package/dist/src/version.js +49 -0
  105. package/dist/src/version.js.map +1 -0
  106. package/dist/src/worker-types.d.ts +12 -0
  107. package/dist/src/worker-types.js +2 -0
  108. package/dist/src/worker-types.js.map +1 -0
  109. package/package.json +56 -4
@@ -0,0 +1,690 @@
1
+ import { accessSync, constants, existsSync, readFileSync, realpathSync, statSync } from "node:fs";
2
+ import { chmod, copyFile, lstat, mkdir, open, readFile, readdir, rm, writeFile } from "node:fs/promises";
3
+ import { homedir, tmpdir } from "node:os";
4
+ import { createRequire } from "node:module";
5
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
6
+ import { randomBytes } from "node:crypto";
7
+ import { execFile, spawn, spawnSync } from "node:child_process";
8
+ import { loadClaudeAuth, workerHome, workspacesPath } from "./config.js";
9
+ const SAFE_ENVIRONMENT = new Set(["LANG", "LANGUAGE", "LC_ALL", "LC_CTYPE", "TERM", "TZ"]);
10
+ const MODEL_ENVIRONMENT = {
11
+ codex: { openai: ["OPENAI_API_KEY"], azure: ["AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT"] },
12
+ opencode: {
13
+ openai: ["OPENAI_API_KEY"],
14
+ anthropic: ["ANTHROPIC_API_KEY"],
15
+ openrouter: ["OPENROUTER_API_KEY"],
16
+ google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
17
+ },
18
+ pi: {
19
+ openai: ["OPENAI_API_KEY"],
20
+ anthropic: ["ANTHROPIC_API_KEY"],
21
+ openrouter: ["OPENROUTER_API_KEY"],
22
+ google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
23
+ },
24
+ };
25
+ const AUTH_PATHS = {
26
+ codex: [".codex/auth.json"],
27
+ opencode: [".local/share/opencode/auth.json"],
28
+ pi: [".pi/agent/auth.json"],
29
+ };
30
+ function stringValues(value) {
31
+ if (typeof value === "string")
32
+ return value.length >= 8 ? [value] : [];
33
+ if (Array.isArray(value))
34
+ return value.flatMap(stringValues);
35
+ if (value && typeof value === "object")
36
+ return Object.values(value).flatMap(stringValues);
37
+ return [];
38
+ }
39
+ export function localCredentialValues(harnessId) {
40
+ const values = [];
41
+ if (harnessId === "claude") {
42
+ const auth = loadClaudeAuth();
43
+ if (auth)
44
+ values.push(auth.value);
45
+ }
46
+ for (const relative of AUTH_PATHS[harnessId] ?? []) {
47
+ try {
48
+ const raw = readFileSync(join(homedir(), relative), "utf8");
49
+ try {
50
+ values.push(...stringValues(JSON.parse(raw)));
51
+ }
52
+ catch {
53
+ if (raw.trim().length >= 8)
54
+ values.push(raw.trim());
55
+ }
56
+ }
57
+ catch {
58
+ continue;
59
+ }
60
+ }
61
+ return [...new Set(values)];
62
+ }
63
+ export function credentialValuesFromFiles(paths) {
64
+ const values = [];
65
+ for (const path of paths) {
66
+ try {
67
+ const raw = readFileSync(path, "utf8");
68
+ try {
69
+ values.push(...stringValues(JSON.parse(raw)));
70
+ }
71
+ catch {
72
+ if (raw.trim().length >= 8)
73
+ values.push(raw.trim());
74
+ }
75
+ }
76
+ catch {
77
+ continue;
78
+ }
79
+ }
80
+ return [...new Set(values)];
81
+ }
82
+ function executable(command) {
83
+ const candidates = isAbsolute(command)
84
+ ? [command]
85
+ : (process.env.PATH ?? "")
86
+ .split(":")
87
+ .filter(Boolean)
88
+ .map((entry) => join(entry, command));
89
+ for (const candidate of candidates) {
90
+ try {
91
+ accessSync(candidate, constants.X_OK);
92
+ return realpathSync(candidate);
93
+ }
94
+ catch {
95
+ continue;
96
+ }
97
+ }
98
+ throw new Error(`required executable ${command} is unavailable`);
99
+ }
100
+ function installationRoot(path) {
101
+ if (basename(dirname(path)) === ".bin")
102
+ return dirname(dirname(path));
103
+ const marker = `${process.platform === "win32" ? "\\" : "/"}bin${process.platform === "win32" ? "\\" : "/"}`;
104
+ const index = path.lastIndexOf(marker);
105
+ return index > 0 ? path.slice(0, index) : dirname(path);
106
+ }
107
+ export function installationRoots(path) {
108
+ const root = installationRoot(path);
109
+ const roots = new Set([root]);
110
+ const candidates = basename(root) === "node_modules" ? [] : [root, dirname(root)];
111
+ const pending = candidates.filter((directory) => existsSync(join(directory, "package.json"))).slice(0, 1);
112
+ const visited = new Set();
113
+ for (const directory of pending) {
114
+ const canonical = realpathSync(directory);
115
+ if (visited.has(canonical))
116
+ continue;
117
+ visited.add(canonical);
118
+ roots.add(canonical);
119
+ const manifestPath = join(canonical, "package.json");
120
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
121
+ const require = createRequire(manifestPath);
122
+ for (const dependency of Object.keys({ ...manifest.dependencies, ...manifest.optionalDependencies })) {
123
+ if (!/^(?:@[A-Za-z0-9_.-]+\/)?[A-Za-z0-9_.-]+$/.test(dependency))
124
+ continue;
125
+ for (const lookup of require.resolve.paths(dependency) ?? []) {
126
+ const candidate = join(lookup, dependency);
127
+ if (!existsSync(join(candidate, "package.json")))
128
+ continue;
129
+ roots.add(candidate);
130
+ pending.push(candidate);
131
+ break;
132
+ }
133
+ }
134
+ }
135
+ const marker = "/Cellar/";
136
+ const cellarIndex = root.indexOf(marker);
137
+ if (cellarIndex < 0)
138
+ return [...roots];
139
+ const prefix = root.slice(0, cellarIndex);
140
+ try {
141
+ const receipt = JSON.parse(readFileSync(join(root, "INSTALL_RECEIPT.json"), "utf8"));
142
+ for (const dependency of receipt.runtime_dependencies ?? []) {
143
+ if (typeof dependency.full_name !== "string")
144
+ continue;
145
+ const segments = dependency.full_name.split("/");
146
+ if (segments.length > 3 || segments.some((segment) => !/^[A-Za-z0-9][A-Za-z0-9@+_.-]*$/.test(segment))) {
147
+ continue;
148
+ }
149
+ const name = basename(dependency.full_name);
150
+ const candidate = join(prefix, "opt", name);
151
+ if (existsSync(candidate))
152
+ roots.add(candidate);
153
+ }
154
+ }
155
+ catch {
156
+ return [...roots];
157
+ }
158
+ return [...roots];
159
+ }
160
+ function macosDeveloperReadPaths() {
161
+ const selectionDirectory = "/private/var/select";
162
+ try {
163
+ const developer = realpathSync(join(selectionDirectory, "developer_dir"));
164
+ const marker = ".app/";
165
+ const appIndex = developer.indexOf(marker);
166
+ const toolchain = appIndex < 0 ? developer : developer.slice(0, appIndex + 4);
167
+ return [selectionDirectory, toolchain, join(tmpdir(), "xcrun_db")];
168
+ }
169
+ catch {
170
+ return [];
171
+ }
172
+ }
173
+ function shellValue(value) {
174
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
175
+ }
176
+ function workerPackage(path) {
177
+ const cli = realpathSync(path);
178
+ let root = dirname(cli);
179
+ for (;;) {
180
+ const manifest = join(root, "package.json");
181
+ if (existsSync(manifest)) {
182
+ try {
183
+ const value = JSON.parse(readFileSync(manifest, "utf8"));
184
+ if (value.name === "@agentqueue/worker")
185
+ return { cli, root };
186
+ }
187
+ catch {
188
+ return null;
189
+ }
190
+ }
191
+ const parent = dirname(root);
192
+ if (parent === root)
193
+ return null;
194
+ root = parent;
195
+ }
196
+ }
197
+ function workerCli() {
198
+ const candidates = [process.argv[1]];
199
+ try {
200
+ candidates.push(executable("aq"));
201
+ }
202
+ catch {
203
+ candidates.push(undefined);
204
+ }
205
+ for (const candidate of candidates) {
206
+ if (!candidate || !existsSync(candidate))
207
+ continue;
208
+ const resolved = workerPackage(candidate);
209
+ if (resolved)
210
+ return resolved;
211
+ }
212
+ throw new Error("Agent Queue broker CLI is unavailable");
213
+ }
214
+ function modelProvider(model) {
215
+ if (!model)
216
+ return undefined;
217
+ const provider = model.split("/", 1)[0]?.toLowerCase();
218
+ if (provider?.includes("claude"))
219
+ return "anthropic";
220
+ if (provider?.includes("gemini"))
221
+ return "google";
222
+ if (provider?.includes("gpt") || provider?.includes("o1") || provider?.includes("o3"))
223
+ return "openai";
224
+ return provider;
225
+ }
226
+ export function childEnvironment(workspace, harness, values) {
227
+ const environment = {
228
+ PATH: `${dirname(realpathSync(process.execPath))}:${process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin"}`,
229
+ HOME: join(workspace, ".aq-home"),
230
+ TMPDIR: join(workspace, ".aq-tmp"),
231
+ GIT_CONFIG_GLOBAL: "/dev/null",
232
+ GIT_CONFIG_NOSYSTEM: "1",
233
+ GIT_TERMINAL_PROMPT: "0",
234
+ };
235
+ for (const key of SAFE_ENVIRONMENT)
236
+ if (process.env[key])
237
+ environment[key] = process.env[key];
238
+ if (harness) {
239
+ if (harness.id === "claude") {
240
+ const auth = loadClaudeAuth();
241
+ if (auth)
242
+ environment[auth.name] = auth.value;
243
+ }
244
+ else {
245
+ let provider = modelProvider(harness.model);
246
+ if (!provider && harness.id === "codex")
247
+ provider = "openai";
248
+ const permitted = provider ? (MODEL_ENVIRONMENT[harness.id]?.[provider] ?? []) : [];
249
+ for (const key of permitted)
250
+ if (process.env[key])
251
+ environment[key] = process.env[key];
252
+ }
253
+ }
254
+ const result = { ...environment, ...values };
255
+ if (harness?.id === "claude")
256
+ result.CLAUDE_CODE_TMPDIR = result.TMPDIR;
257
+ return result;
258
+ }
259
+ function managerEnvironmentNames() {
260
+ const result = spawnSync("systemctl", ["--user", "show-environment"], { encoding: "utf8" });
261
+ if (result.status !== 0)
262
+ return Object.keys(process.env);
263
+ return result.stdout
264
+ .split("\n")
265
+ .map((entry) => entry.slice(0, entry.indexOf("=")))
266
+ .filter(Boolean);
267
+ }
268
+ function environmentFileValue(environment) {
269
+ return Object.entries(environment)
270
+ .filter((entry) => typeof entry[1] === "string")
271
+ .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
272
+ .join("\n");
273
+ }
274
+ export async function stopIsolatedUnit(unit) {
275
+ if (!/^aq-worker-[1-9][0-9]*-[0-9a-f]{12}$/.test(unit))
276
+ throw new Error("Invalid isolated worker unit identity");
277
+ const execute = (args) => new Promise((done) => {
278
+ execFile("systemctl", ["--user", ...args], { timeout: 10000, maxBuffer: 65536 }, (error, stdout) => done({ stdout, failed: Boolean(error) }));
279
+ });
280
+ await execute(["stop", unit]);
281
+ const status = await execute(["show", unit, "--property=ActiveState", "--property=LoadState"]);
282
+ if (!/^(?:ActiveState=(?:inactive|failed)|LoadState=not-found)$/m.test(status.stdout))
283
+ throw new Error("Isolated worker process cessation is unconfirmed");
284
+ }
285
+ async function watchIsolatedUnit(unit, marker) {
286
+ const environment = {
287
+ PATH: "/usr/local/bin:/usr/bin:/bin",
288
+ ...(process.env.XDG_RUNTIME_DIR ? { XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR } : {}),
289
+ ...(process.env.DBUS_SESSION_BUS_ADDRESS ? { DBUS_SESSION_BUS_ADDRESS: process.env.DBUS_SESSION_BUS_ADDRESS } : {}),
290
+ };
291
+ const monitor = spawn(executable("systemd-run"), [
292
+ "--user",
293
+ "--pipe",
294
+ "--wait",
295
+ "--collect",
296
+ "--quiet",
297
+ "--service-type=exec",
298
+ `--unit=${unit}-watchdog`,
299
+ "--property=KillMode=control-group",
300
+ "--property=TimeoutStopSec=5",
301
+ process.execPath,
302
+ "--input-type=module",
303
+ "-e",
304
+ `
305
+ import { execFile } from "node:child_process";
306
+ import { rmSync } from "node:fs";
307
+ process.stdin.resume();
308
+ process.stdin.once("end", () => {
309
+ rmSync(process.argv[2], { force: true });
310
+ execFile("systemctl", ["--user", "stop", process.argv[1]], { timeout: 10000 }, () => process.exit(0));
311
+ });
312
+ process.stdout.write("ready");
313
+ `,
314
+ unit,
315
+ marker,
316
+ ], { env: environment, stdio: ["pipe", "pipe", "ignore"] });
317
+ monitor.stdin.on("error", () => undefined);
318
+ let released = false;
319
+ monitor.once("exit", () => {
320
+ if (!released)
321
+ void rm(marker, { force: true })
322
+ .then(() => stopIsolatedUnit(unit))
323
+ .catch(() => undefined);
324
+ });
325
+ await new Promise((done, reject) => {
326
+ monitor.once("error", reject);
327
+ monitor.once("exit", () => reject(new Error("Worker process watchdog stopped before startup")));
328
+ monitor.stdout.once("data", () => done());
329
+ });
330
+ return async () => {
331
+ released = true;
332
+ monitor.stdin.end();
333
+ if (monitor.exitCode === null && monitor.signalCode === null)
334
+ await new Promise((done) => monitor.once("exit", () => done()));
335
+ };
336
+ }
337
+ async function saveIsolatedUnit(unit, cleanup, directory = join(workerHome(), "active-units")) {
338
+ await mkdir(directory, { recursive: true, mode: 0o700 });
339
+ const path = join(directory, `${unit}.json`);
340
+ const file = await open(path, "wx", 0o600);
341
+ try {
342
+ await file.writeFile(JSON.stringify({ unit, parentPid: process.pid, cleanup }));
343
+ await file.sync();
344
+ }
345
+ finally {
346
+ await file.close();
347
+ }
348
+ return path;
349
+ }
350
+ export async function recoverIsolatedUnits(options = {}) {
351
+ if (process.platform !== "linux")
352
+ return;
353
+ const directory = options.directory ?? join(workerHome(), "active-units");
354
+ const names = await readdir(directory).catch((error) => {
355
+ if (error.code === "ENOENT")
356
+ return [];
357
+ throw error;
358
+ });
359
+ for (const name of names) {
360
+ if (!/^aq-worker-[1-9][0-9]*-[0-9a-f]{12}\.json$/.test(name))
361
+ continue;
362
+ const path = join(directory, name);
363
+ const value = JSON.parse(await readFile(path, "utf8"));
364
+ if (value.unit !== name.slice(0, -5) || !Number.isSafeInteger(value.parentPid) || value.parentPid < 1)
365
+ throw new Error("Invalid isolated worker process journal");
366
+ try {
367
+ process.kill(value.parentPid, 0);
368
+ throw new Error("Another worker still owns an isolated process; stop it before starting this worker");
369
+ }
370
+ catch (error) {
371
+ if (error.code !== "ESRCH")
372
+ throw error;
373
+ }
374
+ if (!Array.isArray(value.cleanup) ||
375
+ value.cleanup.some((path) => typeof path !== "string" ||
376
+ !resolve(path).startsWith(`${resolve(options.workspaceRoot ?? workspacesPath())}/`)))
377
+ throw new Error("Invalid isolated worker cleanup paths");
378
+ await stopIsolatedUnit(value.unit);
379
+ await options.onCessation?.(value.unit);
380
+ for (const target of value.cleanup)
381
+ await rm(target, { recursive: true, force: true });
382
+ await rm(path, { force: true });
383
+ }
384
+ }
385
+ export function isolationStatus() {
386
+ if (process.platform === "linux") {
387
+ const result = spawnSync("systemd-run", [
388
+ "--user",
389
+ "--wait",
390
+ "--collect",
391
+ "--quiet",
392
+ "--property",
393
+ "PrivateUsers=yes",
394
+ "--property",
395
+ "ProtectHome=tmpfs",
396
+ "--property",
397
+ "ProtectSystem=strict",
398
+ "--property",
399
+ "TemporaryFileSystem=/run",
400
+ "/bin/true",
401
+ ], { stdio: "ignore" });
402
+ return {
403
+ ready: result.status === 0,
404
+ guidance: "A systemd user manager with filesystem sandboxing is required.",
405
+ };
406
+ }
407
+ if (process.platform === "darwin") {
408
+ try {
409
+ executable("sandbox-exec");
410
+ return { ready: true, guidance: "" };
411
+ }
412
+ catch {
413
+ return { ready: false, guidance: "macOS sandbox-exec is required." };
414
+ }
415
+ }
416
+ return { ready: false, guidance: `Workflow process isolation is unavailable on ${process.platform}.` };
417
+ }
418
+ function sandboxPathValues(paths) {
419
+ const resolved = paths.flatMap((path) => {
420
+ const lexical = resolve(path);
421
+ try {
422
+ return [lexical, realpathSync(lexical)];
423
+ }
424
+ catch {
425
+ return [lexical];
426
+ }
427
+ });
428
+ return [...new Set(resolved)];
429
+ }
430
+ function sandboxPaths(paths) {
431
+ return sandboxPathValues(paths)
432
+ .map((path) => `(subpath ${JSON.stringify(path)})`)
433
+ .join(" ");
434
+ }
435
+ export function macosSandboxProfile(readablePaths, writablePaths, skillRoot) {
436
+ const readable = sandboxPaths(readablePaths);
437
+ const writable = sandboxPaths(writablePaths);
438
+ return [
439
+ "(version 1)",
440
+ "(deny default)",
441
+ "(allow process-exec)",
442
+ "(allow process-fork)",
443
+ "(allow process-info* (target same-sandbox))",
444
+ "(allow signal (target same-sandbox))",
445
+ "(allow sysctl-read)",
446
+ '(allow sysctl-write (sysctl-name "kern.grade_cputype"))',
447
+ '(allow iokit-open (iokit-registry-entry-class "RootDomainUserClient"))',
448
+ "(allow ipc-posix-sem)",
449
+ '(allow ipc-posix-shm-read-data ipc-posix-shm-write-create ipc-posix-shm-write-unlink (ipc-posix-name-regex #"^/__KMP_REGISTERED_LIB_[0-9]+$"))',
450
+ "(allow pseudo-tty)",
451
+ '(allow file-read* file-write* file-ioctl (literal "/dev/ptmx"))',
452
+ '(allow file-read* file-write* (require-all (regex #"^/dev/ttys[0-9]+") (extension "com.apple.sandbox.pty")))',
453
+ '(allow file-ioctl (regex #"^/dev/ttys[0-9]+"))',
454
+ '(allow file-read* file-test-existence (literal "/dev/random") (literal "/dev/urandom"))',
455
+ '(allow file-read* file-test-existence file-write-data (literal "/dev/null") (literal "/dev/zero"))',
456
+ '(allow file-read-data file-test-existence file-write-data (subpath "/dev/fd"))',
457
+ '(allow file-read-data (literal "/"))',
458
+ '(allow file-read-metadata (literal "/etc"))',
459
+ '(allow file-read-metadata (literal "/tmp"))',
460
+ '(allow file-read-metadata (literal "/var"))',
461
+ "(allow mach-lookup)",
462
+ '(deny mach-lookup (global-name "com.apple.securityd") (global-name "com.apple.securityd.xpc"))',
463
+ "(allow network*)",
464
+ "(allow system-socket (require-all (socket-domain AF_SYSTEM) (socket-protocol 2)))",
465
+ `(allow file-read* file-test-existence ${readable})`,
466
+ `(allow file-map-executable ${readable})`,
467
+ `(allow file-write* ${writable})`,
468
+ ...sandboxPathValues([skillRoot]).map((path) => `(deny file-write* (subpath ${JSON.stringify(path)}))`),
469
+ ].join(" ");
470
+ }
471
+ async function privateCredentialDirectory(root, segments) {
472
+ const existing = await lstat(root).catch((error) => {
473
+ if (error.code === "ENOENT")
474
+ return null;
475
+ throw error;
476
+ });
477
+ if (existing?.isSymbolicLink() || (existing && !existing.isDirectory()))
478
+ throw new Error("Credential home must be a private directory");
479
+ await mkdir(root, { recursive: true, mode: 0o700 });
480
+ let directory = root;
481
+ for (const segment of segments) {
482
+ directory = join(directory, segment);
483
+ await mkdir(directory, { mode: 0o700 }).catch((error) => {
484
+ if (error.code !== "EEXIST")
485
+ throw error;
486
+ });
487
+ const info = await lstat(directory);
488
+ if (!info.isDirectory() || info.isSymbolicLink())
489
+ throw new Error("Credential directory contains a symlink");
490
+ }
491
+ return directory;
492
+ }
493
+ export async function isolate(command, args, workspace, workingDirectory, environment, harnessId, brokerAccess = true, writablePaths = [], copyModelAuth = true, isolationOptions = {}) {
494
+ const resolvedCommand = executable(command);
495
+ const sandboxHome = resolve(environment.HOME ?? join(workspace, ".aq-home"));
496
+ const sandboxTmp = resolve(environment.TMPDIR ?? join(workspace, ".aq-tmp"));
497
+ const skillRoot = join(sandboxHome, ".agents", "skills");
498
+ const broker = harnessId && brokerAccess ? workerCli() : null;
499
+ const brokerBin = broker ? join(sandboxHome, ".aq-bin") : null;
500
+ const credentialDirectory = resolve(workerHome());
501
+ const roots = new Set([...installationRoots(resolvedCommand), ...installationRoots(realpathSync(process.execPath))]);
502
+ for (const path of isolationOptions.readOnlyPaths ?? [])
503
+ roots.add(resolve(path));
504
+ if (broker) {
505
+ roots.add(broker.root);
506
+ for (const root of installationRoots(broker.cli))
507
+ roots.add(root);
508
+ }
509
+ const canonicalPaths = (paths) => paths.flatMap((path) => (existsSync(path) ? [resolve(path), realpathSync(path)] : [resolve(path)]));
510
+ const ownerHomes = canonicalPaths([homedir()]);
511
+ const privatePaths = canonicalPaths([
512
+ credentialDirectory,
513
+ workspacesPath(),
514
+ workspace,
515
+ ...Object.values(AUTH_PATHS).flatMap((paths) => paths.map((path) => dirname(join(homedir(), path)))),
516
+ ...[".ssh", ".aws", ".claude", ".config/gh", ".config/gcloud"].map((path) => join(homedir(), path)),
517
+ ]);
518
+ for (const root of roots) {
519
+ for (const path of new Set([resolve(root), realpathSync(root)])) {
520
+ if (["/", "/tmp", "/var/tmp", "/home", "/root", "/run"].includes(path) ||
521
+ path.startsWith("/run/") ||
522
+ ownerHomes.some((home) => home === path || home.startsWith(`${path}/`)) ||
523
+ privatePaths.some((privatePath) => privatePath === path || privatePath.startsWith(`${path}/`) || path.startsWith(`${privatePath}/`)))
524
+ throw new Error("Runtime installation must be separate from private worker data and workspaces");
525
+ }
526
+ }
527
+ const invocationEnvironment = { ...environment };
528
+ await Promise.all([
529
+ privateCredentialDirectory(sandboxHome, []),
530
+ privateCredentialDirectory(sandboxTmp, []),
531
+ ...(brokerBin ? [privateCredentialDirectory(sandboxHome, [".aq-bin"])] : []),
532
+ ]);
533
+ if (broker && brokerBin) {
534
+ await writeFile(join(brokerBin, "aq"), `#!/bin/sh\n[ "$1" = "broker" ] || exit 64\nshift\nexec ${shellValue(realpathSync(process.execPath))} ${shellValue(broker.cli)} broker "$@"\n`, { mode: 0o700 });
535
+ invocationEnvironment.PATH = `${brokerBin}:${invocationEnvironment.PATH ?? "/usr/local/bin:/usr/bin:/bin"}`;
536
+ }
537
+ const copiedAuth = [];
538
+ for (const relative of harnessId && copyModelAuth ? (AUTH_PATHS[harnessId] ?? []) : []) {
539
+ const source = join(homedir(), relative);
540
+ if (!existsSync(source) || statSync(source).isDirectory())
541
+ continue;
542
+ const target = harnessId === "codex" && invocationEnvironment.CODEX_HOME
543
+ ? join(invocationEnvironment.CODEX_HOME, "auth.json")
544
+ : join(sandboxHome, relative);
545
+ const credentialRoot = harnessId === "codex" && invocationEnvironment.CODEX_HOME ? invocationEnvironment.CODEX_HOME : sandboxHome;
546
+ const relativeParts = harnessId === "codex" && invocationEnvironment.CODEX_HOME ? [] : relative.split("/").slice(0, -1);
547
+ await privateCredentialDirectory(credentialRoot, relativeParts);
548
+ await rm(target, { force: true });
549
+ await copyFile(source, target, constants.COPYFILE_EXCL);
550
+ await chmod(target, 0o600);
551
+ copiedAuth.push(target);
552
+ }
553
+ if (process.platform === "linux") {
554
+ const systemdRun = executable("systemd-run");
555
+ const unit = `aq-worker-${process.pid}-${randomBytes(6).toString("hex")}`;
556
+ const environmentPath = join(dirname(workspace), `.${unit}.env`);
557
+ await writeFile(environmentPath, `${environmentFileValue(invocationEnvironment)}\n`, { mode: 0o600 });
558
+ const guardDirectory = join(dirname(workspace), `.${unit}.guard`);
559
+ const marker = join(guardDirectory, "owner");
560
+ await mkdir(guardDirectory, { mode: 0o700 });
561
+ await writeFile(marker, "alive", { mode: 0o600 });
562
+ const properties = [
563
+ "ProtectHome=tmpfs",
564
+ "ProtectSystem=strict",
565
+ "TemporaryFileSystem=/run",
566
+ ...(existsSync("/etc/resolv.conf") ? [`BindReadOnlyPaths=${realpathSync("/etc/resolv.conf")}`] : []),
567
+ `TemporaryFileSystem=${dirname(workspace)}`,
568
+ ...(isolationOptions.maskedDirectories ?? []).map((path) => `TemporaryFileSystem=${resolve(path)}`),
569
+ "KillMode=control-group",
570
+ "TimeoutStopSec=5",
571
+ "SendSIGKILL=yes",
572
+ `BindReadOnlyPaths=${guardDirectory}`,
573
+ `ExecStartPre=/usr/bin/test -e ${JSON.stringify(marker)}`,
574
+ `BindPaths=${workspace}`,
575
+ ...(sandboxHome.startsWith(`${workspace}/`) ? [] : [`BindPaths=${sandboxHome}`]),
576
+ ...(sandboxTmp.startsWith(`${workspace}/`) || sandboxTmp.startsWith(`${sandboxHome}/`)
577
+ ? []
578
+ : [`BindPaths=${sandboxTmp}`]),
579
+ ...writablePaths.map((path) => `BindPaths=${resolve(path)}`),
580
+ "PrivateTmp=yes",
581
+ "PrivateDevices=yes",
582
+ "PrivateUsers=yes",
583
+ "ProtectProc=invisible",
584
+ "ProcSubset=pid",
585
+ "NoNewPrivileges=yes",
586
+ "RestrictNamespaces=yes",
587
+ "ProtectKernelTunables=yes",
588
+ "ProtectKernelModules=yes",
589
+ "ProtectKernelLogs=yes",
590
+ "ProtectControlGroups=yes",
591
+ "ProtectClock=yes",
592
+ "RestrictSUIDSGID=yes",
593
+ "LockPersonality=yes",
594
+ "RemoveIPC=yes",
595
+ "CapabilityBoundingSet=",
596
+ "UMask=0077",
597
+ "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6",
598
+ "IPAddressDeny=link-local",
599
+ "IPAddressDeny=multicast",
600
+ `EnvironmentFile=${environmentPath}`,
601
+ `UnsetEnvironment=${managerEnvironmentNames()
602
+ .filter((name) => !(name in invocationEnvironment))
603
+ .join(" ")}`,
604
+ ];
605
+ for (const path of canonicalPaths([credentialDirectory, ...(isolationOptions.protectedPaths ?? [])]))
606
+ properties.push(`InaccessiblePaths=-${path}`);
607
+ for (const root of roots) {
608
+ const source = realpathSync(root);
609
+ const target = resolve(root);
610
+ properties.push(`BindReadOnlyPaths=${source === target ? source : `${source}:${target}`}`);
611
+ }
612
+ if (existsSync(skillRoot))
613
+ properties.push(`ReadOnlyPaths=${skillRoot}`);
614
+ const cleanupPaths = [environmentPath, guardDirectory, ...(brokerBin ? [brokerBin] : []), ...copiedAuth];
615
+ const journal = await saveIsolatedUnit(unit, cleanupPaths, isolationOptions.journalDirectory);
616
+ const stopWatchdog = await watchIsolatedUnit(unit, marker);
617
+ return {
618
+ executablePath: resolvedCommand,
619
+ command: systemdRun,
620
+ args: [
621
+ "--user",
622
+ "--wait",
623
+ "--pipe",
624
+ "--collect",
625
+ "--quiet",
626
+ `--unit=${unit}`,
627
+ `--working-directory=${workingDirectory}`,
628
+ ...properties.flatMap((property) => ["--property", property]),
629
+ resolvedCommand,
630
+ ...args,
631
+ ],
632
+ env: {
633
+ PATH: "/usr/local/bin:/usr/bin:/bin",
634
+ LANG: process.env.LANG ?? "C.UTF-8",
635
+ ...(process.env.XDG_RUNTIME_DIR ? { XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR } : {}),
636
+ ...(process.env.DBUS_SESSION_BUS_ADDRESS
637
+ ? { DBUS_SESSION_BUS_ADDRESS: process.env.DBUS_SESSION_BUS_ADDRESS }
638
+ : {}),
639
+ },
640
+ unit,
641
+ credentialFiles: copiedAuth,
642
+ cleanup: async () => {
643
+ await stopIsolatedUnit(unit);
644
+ await stopWatchdog();
645
+ await isolationOptions.onCessation?.(unit);
646
+ await rm(journal, { force: true });
647
+ await Promise.all([
648
+ rm(environmentPath, { force: true }),
649
+ rm(guardDirectory, { recursive: true, force: true }),
650
+ ...(brokerBin ? [rm(brokerBin, { recursive: true, force: true })] : []),
651
+ ...copiedAuth.map((path) => rm(path, { force: true })),
652
+ ]);
653
+ },
654
+ };
655
+ }
656
+ if (process.platform === "darwin") {
657
+ const sandboxExec = executable("sandbox-exec");
658
+ const reads = [
659
+ "/System",
660
+ "/usr",
661
+ "/bin",
662
+ "/sbin",
663
+ "/Library",
664
+ "/private/etc",
665
+ "/private/var/db",
666
+ workspace,
667
+ sandboxHome,
668
+ sandboxTmp,
669
+ ...roots,
670
+ ...macosDeveloperReadPaths(),
671
+ ...writablePaths.map((path) => resolve(path)),
672
+ ];
673
+ const profile = macosSandboxProfile(reads, [workspace, sandboxHome, sandboxTmp, "/private/tmp", ...writablePaths], skillRoot);
674
+ return {
675
+ executablePath: resolvedCommand,
676
+ command: sandboxExec,
677
+ args: ["-p", profile, resolvedCommand, ...args],
678
+ env: invocationEnvironment,
679
+ credentialFiles: copiedAuth,
680
+ cleanup: async () => {
681
+ await Promise.all([
682
+ ...(brokerBin ? [rm(brokerBin, { recursive: true, force: true })] : []),
683
+ ...copiedAuth.map((path) => rm(path, { force: true })),
684
+ ]);
685
+ },
686
+ };
687
+ }
688
+ throw new Error(`self-hosted workflow isolation is not supported on ${process.platform}`);
689
+ }
690
+ //# sourceMappingURL=isolation.js.map