@aiwg/cli 2026.10.8 → 2026.10.10
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/agentic/code/providers/model-capabilities.v1.json +197 -3
- package/bin/aiwg.mjs +8 -0
- package/dist/src/artifacts/cli.js +9 -5
- package/dist/src/auth/config.js +3 -8
- package/dist/src/auth/resource-credentials.js +35 -2
- package/dist/src/cli/handlers/diagnose.js +13 -0
- package/dist/src/cli/handlers/mc-admission.js +39 -0
- package/dist/src/cli/handlers/mc.js +2 -31
- package/dist/src/cli/handlers/models-help.js +52 -0
- package/dist/src/cli/handlers/models.js +11 -81
- package/dist/src/cli/handlers/resource-versions.js +8 -5
- package/dist/src/cli/handlers/script-runner.js +13 -1
- package/dist/src/cli/handlers/use-addon-profile.js +72 -0
- package/dist/src/cli/handlers/use.js +209 -233
- package/dist/src/cli/ui.js +1 -0
- package/dist/src/config/aiwg-config.js +38 -184
- package/dist/src/config/cli-help.js +54 -0
- package/dist/src/config/cli.js +5 -69
- package/dist/src/config/config-normalization.js +10 -0
- package/dist/src/config/worker-activation-gate.js +26 -0
- package/dist/src/config/worker-adoption-cli.js +102 -0
- package/dist/src/config/worker-adoption.js +368 -0
- package/dist/src/config/worker-config-digest.js +14 -0
- package/dist/src/config/worker-group-templates.js +150 -0
- package/dist/src/config/worker-guidance-warning.js +28 -0
- package/dist/src/config/worker-owner-lock.js +152 -0
- package/dist/src/config/worker-parallelism.js +154 -0
- package/dist/src/config/worker-write.js +75 -0
- package/dist/src/decision/telemetry/exporter.js +2 -1
- package/dist/src/decision/telemetry/index.js +1 -0
- package/dist/src/decision/telemetry/mapping.js +43 -0
- package/dist/src/decision/telemetry/otlp-http.js +5 -2
- package/dist/src/decision/telemetry/redaction.js +6 -1
- package/dist/src/decision/telemetry/types.js +3 -0
- package/dist/src/decision/telemetry/worker.js +69 -0
- package/dist/src/lint/rule-activation.js +15 -7
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/server.mjs +2 -11
- package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
- package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
- package/dist/src/mcp/tools/worker-invoke.mjs +6 -6
- package/dist/src/mission-protocol/codecs.js +8 -38
- package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
- package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
- package/dist/src/models/conductor-data-policy.d.mts +8 -0
- package/dist/src/models/conductor-data-policy.mjs +190 -0
- package/dist/src/models/config-loader.js +19 -3
- package/dist/src/models/config-writer.js +27 -0
- package/dist/src/models/model-capabilities.v1.json +197 -3
- package/dist/src/models/provider-policy.js +7 -155
- package/dist/src/models/resolver.js +2 -293
- package/dist/src/models/worker-artifact-binding.mjs +12 -0
- package/dist/src/models/worker-direct-readiness-policy.js +155 -0
- package/dist/src/models/worker-direct-readiness.js +9 -218
- package/dist/src/models/worker-health-readiness.js +105 -26
- package/dist/src/models/worker-installation.mjs +12 -14
- package/dist/src/models/worker-member-references.js +64 -0
- package/dist/src/models/worker-plan.js +317 -0
- package/dist/src/models/worker-selection-policy.js +217 -0
- package/dist/src/models/wrapper-deployment.js +1 -1
- package/dist/src/resources/web-release.js +76 -65
- package/dist/src/serve/shared-host-admission-checks.js +116 -0
- package/dist/src/serve/shared-host-scheduler.js +25 -107
- package/dist/src/serve/shared-host-starvation.js +73 -0
- package/dist/src/serve/worker-artifacts.d.mts +4 -0
- package/dist/src/serve/worker-artifacts.mjs +56 -0
- package/dist/src/serve/worker-command-request.js +68 -0
- package/dist/src/serve/worker-invocation-paths.js +144 -0
- package/dist/src/serve/worker-invocation-preparation.js +169 -0
- package/dist/src/serve/worker-invocation.js +24 -291
- package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
- package/dist/src/serve/worker-stdin-frame.mjs +6 -6
- package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
- package/dist/src/storage/backends/fortemi.js +33 -29
- package/dist/src/storage/backends/logseq.js +16 -3
- package/dist/src/storage/backends/postgres.js +74 -33
- package/dist/src/storage/backends/postgrest.js +37 -13
- package/dist/src/storage/config.js +6 -1
- package/dist/src/uhp/config.js +4 -0
- package/package.json +9 -1
- package/schemas/mission-v1.schema.json +20 -19
- package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
- package/tools/agents/providers/claude-post-deploy.mjs +35 -0
- package/tools/agents/providers/claude.mjs +3 -30
- package/tools/agents/providers/codex-post-deploy.mjs +13 -0
- package/tools/agents/providers/codex.mjs +2 -8
- package/tools/agents/providers/conductor-data-guard.mjs +61 -0
- package/tools/agents/providers/rule-delivery.mjs +3 -16
- package/tools/agents/providers/rule-presentation.mjs +38 -0
- package/tools/plugin/package-plugins.mjs +1 -1
- package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
- package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
- package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
- package/tools/ralph-external/lib/session-attempt.mjs +85 -0
- package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
- package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
- package/tools/ralph-external/lib/worker-final.mjs +143 -0
- package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +2 -44
- package/tools/ralph-external/lib/worker-launch-cli.mjs +47 -0
- package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
- package/tools/ralph-external/session-launcher.mjs +73 -217
- package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Read bounded worker inputs through pinned, no-follow directory handles.
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { open } from 'node:fs/promises';
|
|
4
|
+
import { isAbsolute } from 'node:path';
|
|
5
|
+
|
|
6
|
+
// Linux directory handles pin each ancestor. O_NOFOLLOW on a pathname alone
|
|
7
|
+
// cannot protect an ancestor that the worker replaces after inspection.
|
|
8
|
+
export async function withWorkerPath(filename, operation) {
|
|
9
|
+
if (!isAbsolute(filename) || filename.includes('\0') || filename.split('/').includes('..')) throw new Error('Invalid worker path');
|
|
10
|
+
const components = filename.split('/').filter(Boolean);
|
|
11
|
+
const leaf = components.pop();
|
|
12
|
+
let directory = await open('/', constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
13
|
+
try {
|
|
14
|
+
for (const component of components) {
|
|
15
|
+
const next = await open(`/proc/self/fd/${directory.fd}/${component}`, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
16
|
+
await directory.close(); directory = next;
|
|
17
|
+
}
|
|
18
|
+
return await operation(`/proc/self/fd/${directory.fd}/${leaf ?? '.'}`);
|
|
19
|
+
} finally { await directory.close(); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// #2915 review 4 (finding 4): `privateOutput: true` REPAIRS a loose mode
|
|
23
|
+
// (chmod 0600) rather than rejecting it — correct for the worker's own
|
|
24
|
+
// final-output file, which the launcher itself created and is free to
|
|
25
|
+
// re-tighten. `rejectInsecureMode: true` additionally REFUSES a mode other
|
|
26
|
+
// than exactly 0600 instead of repairing it, for evidence this launcher
|
|
27
|
+
// did NOT create moments ago (retry predecessor metadata) — a 0666 file
|
|
28
|
+
// there is itself a signal worth refusing on, not silently normalizing
|
|
29
|
+
// away.
|
|
30
|
+
export async function boundedPrivateRead(filename, limit, inspect, privateOutput = false, rejectInsecureMode = false) {
|
|
31
|
+
if (!await inspect(filename, 'file')) throw new Error('Required worker input is missing');
|
|
32
|
+
const handle = await withWorkerPath(filename, target => open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK));
|
|
33
|
+
try {
|
|
34
|
+
const stat = await handle.stat();
|
|
35
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > limit) throw new Error('Worker input must be a bounded unaliased regular file');
|
|
36
|
+
if (privateOutput) {
|
|
37
|
+
if (stat.uid !== process.getuid()) throw new Error('Worker final must be conductor-owned');
|
|
38
|
+
if (rejectInsecureMode) {
|
|
39
|
+
if ((stat.mode & 0o777) !== 0o600) throw new Error('Worker input mode must already be exactly 0600; insecure modes are refused, not repaired');
|
|
40
|
+
} else {
|
|
41
|
+
await handle.chmod(0o600);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const buffer = Buffer.alloc(limit + 1);
|
|
45
|
+
let length = 0;
|
|
46
|
+
while (length < buffer.length) {
|
|
47
|
+
const { bytesRead } = await handle.read(buffer, length, buffer.length - length, length);
|
|
48
|
+
if (!bytesRead) break;
|
|
49
|
+
length += bytesRead;
|
|
50
|
+
}
|
|
51
|
+
if (length > limit) throw new Error('Worker input exceeds bound');
|
|
52
|
+
return buffer.subarray(0, length);
|
|
53
|
+
} finally { await handle.close(); }
|
|
54
|
+
}
|
|
55
|
+
|
|
@@ -42,50 +42,8 @@ import { buildSystemdClientEnv, resolveSystemdClient, resolveSystemdRun, CONNECT
|
|
|
42
42
|
|
|
43
43
|
const UNIT_SAFE = /[^A-Za-z0-9:_.@-]/g;
|
|
44
44
|
const transportBootstrapVariables = ['NODE_OPTIONS', 'NODE_PATH', 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'LD_AUDIT', 'LD_DEBUG', 'LD_DEBUG_OUTPUT'];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
export const NATIVE_SANDBOX_UNAVAILABLE = 'OG_NATIVE_SANDBOX_UNAVAILABLE';
|
|
48
|
-
|
|
49
|
-
export function workerNeedsNativeNamespaces(adapter, permissions = {}, platform = process.platform) {
|
|
50
|
-
return platform === 'linux' && adapter?.provider === 'codex'
|
|
51
|
-
&& (permissions.transport ?? 'shell') === 'shell'
|
|
52
|
-
&& ['read-only', 'workspace-write'].includes(permissions.sandbox ?? 'workspace-write');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function nativeSandboxFailure(text) {
|
|
56
|
-
return /loopback: Failed RTM_NEWADDR: Operation not permitted|uid_map[^\n]*Operation not permitted/i.test(text);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function nativeSandboxProbeMessage(result) {
|
|
60
|
-
return `${NATIVE_SANDBOX_UNAVAILABLE}: Native sandbox unavailable: restricted unprivileged user namespaces inside systemd user units (host AppArmor policy may restrict userns). Probe: ${String(result.detail ?? 'failed').replace(/[\x00-\x1f\x7f]/g, ' ').slice(0, 512)}. Select parallelism.groups.<group>.execution.launchMode="native-sandbox" with a verified enforcing CLI sandbox, or configure a host AppArmor allowance for the systemd worker launch path.`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** No inference: exercise user/network namespaces through the worker's unit and client environment path. */
|
|
64
|
-
export function probeWorkerNativeSandbox({ spawn = spawnSync, env = process.env, envNames = ['PATH', 'HOME'], cwd,
|
|
65
|
-
cache = spawn === spawnSync ? nativeSandboxProbes : undefined } = {}) {
|
|
66
|
-
const key = JSON.stringify([cwd, [...envNames].sort()]);
|
|
67
|
-
if (cache?.has(key)) return cache.get(key);
|
|
68
|
-
let result;
|
|
69
|
-
try {
|
|
70
|
-
const managerEnvNames = resolveManagerEnvNames({ spawn });
|
|
71
|
-
const command = process.execPath;
|
|
72
|
-
const args = [fileURLToPath(import.meta.url), '--worker-environment', JSON.stringify(unitEnvironmentNames(envNames)),
|
|
73
|
-
'--', '/usr/bin/unshare', '--user', '--map-root-user', '--net', '/usr/bin/true'];
|
|
74
|
-
const argv = buildWorkerIsolationArgs({ unit: proposeWorkerUnitName(`sandbox-probe-${process.pid}-${++nativeSandboxProbeSequence}`),
|
|
75
|
-
command, args, cwd, env, envNames, managerEnvNames, memoryMaxGb: 0.25, attached: true,
|
|
76
|
-
extraProperties: ['RuntimeMaxSec=5s', 'TimeoutStopSec=1s'] });
|
|
77
|
-
const observed = spawn(resolveSystemdRun(), ['--collect', ...argv.slice(1)], {
|
|
78
|
-
encoding: 'utf8', timeout: 8000, maxBuffer: 4096, env: buildSystemdClientEnv({ env, envNames }) });
|
|
79
|
-
result = observed.status === 0 && !observed.error ? { ok: true, mode: 'systemd-unit' }
|
|
80
|
-
: { ok: false, mode: 'systemd-unit', code: NATIVE_SANDBOX_UNAVAILABLE,
|
|
81
|
-
detail: String(observed.error?.message ?? (String(observed.stderr ?? '').trim() || `exit ${observed.status}`)).replace(/[\x00-\x1f\x7f]/g, ' ').slice(0, 512) };
|
|
82
|
-
} catch (error) {
|
|
83
|
-
result = { ok: false, mode: 'systemd-unit', code: NATIVE_SANDBOX_UNAVAILABLE,
|
|
84
|
-
detail: String(error.message).replace(/[\x00-\x1f\x7f]/g, ' ').slice(0, 512) };
|
|
85
|
-
}
|
|
86
|
-
cache?.set(key, result);
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
45
|
+
export { NATIVE_SANDBOX_UNAVAILABLE, workerNeedsNativeNamespaces, nativeSandboxFailure,
|
|
46
|
+
nativeSandboxProbeMessage, probeWorkerNativeSandbox } from './worker-native-sandbox.mjs';
|
|
89
47
|
|
|
90
48
|
/** Derive a unit-safe `aiwg-worker-<name>-<epoch>` unit name. */
|
|
91
49
|
export function proposeWorkerUnitName(name, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Parse one-shot launch options and derive retry IDs from launcher records.
|
|
2
|
+
import { join, isAbsolute } from 'node:path';
|
|
3
|
+
import { boundedPrivateRead } from './worker-inputs.mjs';
|
|
4
|
+
|
|
5
|
+
export function parseWorkerLaunchArgs(args) {
|
|
6
|
+
const result = {};
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
let retryOfInvocationId;
|
|
9
|
+
let retryOfRunDir;
|
|
10
|
+
for (let i = 0; i < args.length; i++) {
|
|
11
|
+
const flag = args[i];
|
|
12
|
+
if (seen.has(flag)) throw new Error(`Duplicate worker launch option: ${flag}`);
|
|
13
|
+
seen.add(flag);
|
|
14
|
+
if (flag === '--launch-worker') continue;
|
|
15
|
+
if (flag === '--allow-degraded-worker') { result.allowDegraded = true; continue; }
|
|
16
|
+
// A run directory declares a retry; its launcher ID is derived before validation.
|
|
17
|
+
const key = { '--invocation': 'invocationPath', '--run-dir': 'runDir',
|
|
18
|
+
'--retry-of': '__retryOfInvocationId', '--retry-of-run-dir': '__retryOfRunDir' }[flag];
|
|
19
|
+
if (!key) throw new Error(`Unsupported worker launch option: ${flag}`);
|
|
20
|
+
const value = args[++i];
|
|
21
|
+
if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
|
|
22
|
+
if (key === '__retryOfInvocationId') retryOfInvocationId = value;
|
|
23
|
+
else if (key === '__retryOfRunDir') retryOfRunDir = value;
|
|
24
|
+
else result[key] = value;
|
|
25
|
+
}
|
|
26
|
+
if (!seen.has('--launch-worker') || !result.invocationPath || !result.runDir) {
|
|
27
|
+
throw new Error('Usage: ralph-external --launch-worker --invocation FILE --run-dir DIR [--allow-degraded-worker] [--retry-of-run-dir DIR [--retry-of ID]]');
|
|
28
|
+
}
|
|
29
|
+
if (retryOfInvocationId !== undefined && retryOfRunDir === undefined) {
|
|
30
|
+
throw new Error('--retry-of and --retry-of-run-dir must be supplied together when an explicit ID is used');
|
|
31
|
+
}
|
|
32
|
+
if (retryOfRunDir !== undefined) result.retryOf = { runDir: retryOfRunDir, ...(retryOfInvocationId === undefined ? {} : { invocationId: retryOfInvocationId }) };
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function resolveRetryReference(retryOf, { inspect }) {
|
|
37
|
+
if (!retryOf || typeof retryOf !== 'object' || retryOf.invocationId !== undefined) return retryOf;
|
|
38
|
+
if (!isAbsolute(retryOf.runDir ?? '')) throw new Error('Retry run directory must be absolute');
|
|
39
|
+
const bytes = await boundedPrivateRead(join(retryOf.runDir, 'invocation.json'), 1048576, inspect, true, true);
|
|
40
|
+
const record = JSON.parse(bytes.toString('utf8'));
|
|
41
|
+
if (record.origin !== 'launcher' || typeof record.invocationId !== 'string' || !record.invocationId) {
|
|
42
|
+
throw new Error('Retry predecessor invocation.json must contain a launcher invocationId');
|
|
43
|
+
}
|
|
44
|
+
// Derivation is not acceptance: verifyRetryPredecessor still checks ownership,
|
|
45
|
+
// task-chain identity, matching records, writable roots and confirmed stop.
|
|
46
|
+
return { ...retryOf, invocationId: record.invocationId };
|
|
47
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** Probe each CLI's native sandbox mechanism through the worker's transient unit. */
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { accessSync, constants, statSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { buildWorkerIsolationArgs, proposeWorkerUnitName, resolveManagerEnvNames } from './worker-isolation.mjs';
|
|
7
|
+
import { buildSystemdClientEnv, resolveSystemdRun, unitEnvironmentNames } from './systemd-client.mjs';
|
|
8
|
+
|
|
9
|
+
const nativeSandboxProbes = new Map();
|
|
10
|
+
let sequence = 0;
|
|
11
|
+
export const NATIVE_SANDBOX_UNAVAILABLE = 'OG_NATIVE_SANDBOX_UNAVAILABLE';
|
|
12
|
+
|
|
13
|
+
/** Prefer the system bwrap allowed by Ubuntu's executable-specific AppArmor policy. */
|
|
14
|
+
export function resolveWorkerBwrap({ env = process.env, executable = candidate => {
|
|
15
|
+
try { accessSync(candidate, constants.X_OK); return statSync(candidate).isFile(); } catch { return false; }
|
|
16
|
+
} } = {}) {
|
|
17
|
+
for (const candidate of ['/usr/bin/bwrap', ...(env.PATH ?? '').split(path.delimiter)
|
|
18
|
+
.filter(directory => path.isAbsolute(directory)).map(directory => path.join(directory, 'bwrap'))]) {
|
|
19
|
+
if (executable(candidate)) return candidate;
|
|
20
|
+
}
|
|
21
|
+
throw new Error('bwrap is unavailable at /usr/bin/bwrap or on the worker PATH');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const bwrapProbe = options => ({ mechanism: 'bwrap', command: resolveWorkerBwrap(options),
|
|
25
|
+
args: ['--ro-bind', '/', '/', '--unshare-user', '--unshare-net', '--', '/usr/bin/true'] });
|
|
26
|
+
|
|
27
|
+
// Hooks are owned by launcher code, never loaded from project configuration.
|
|
28
|
+
export const workerNativeSandboxProbeHooks = { codex: bwrapProbe, muse: bwrapProbe };
|
|
29
|
+
|
|
30
|
+
// CLIs without their own hook keep a generic user/network namespace probe: bwrap when the host
|
|
31
|
+
// has it (allowed by Ubuntu's bwrap profile), else the previous unshare probe.
|
|
32
|
+
const genericProbe = options => {
|
|
33
|
+
try { return bwrapProbe(options); }
|
|
34
|
+
catch { return { mechanism: 'unshare', command: '/usr/bin/unshare', args: ['--user', '--map-root-user', '--net', '/usr/bin/true'] }; }
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function workerNeedsNativeNamespaces(adapter, permissions = {}, platform = process.platform) {
|
|
38
|
+
return platform === 'linux' && Object.hasOwn(workerNativeSandboxProbeHooks, adapter?.provider ?? '')
|
|
39
|
+
&& (permissions.transport ?? 'shell') === 'shell'
|
|
40
|
+
&& ['read-only', 'workspace-write'].includes(permissions.sandbox ?? 'workspace-write');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function nativeSandboxFailure(text) {
|
|
44
|
+
return /loopback: Failed RTM_NEWADDR: Operation not permitted|uid_map[^\n]*Operation not permitted/i.test(text);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const bounded = detail => String(detail).replace(/[\x00-\x1f\x7f]/g, ' ').slice(0, 512);
|
|
48
|
+
|
|
49
|
+
export function nativeSandboxProbeMessage(result) {
|
|
50
|
+
return `${NATIVE_SANDBOX_UNAVAILABLE}: Native sandbox unavailable: ${bounded(result.mechanism ?? 'CLI sandbox')} probe failed inside a systemd user unit. Probe: ${bounded(result.detail ?? 'failed')}. Verify that the named sandbox executable is installed and can create its sandbox in the worker unit environment.`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Hooks return { mechanism, command, args }; the unit and environment path stay shared. */
|
|
54
|
+
export function probeWorkerNativeSandbox({ adapter = { provider: 'codex' }, probeHooks = workerNativeSandboxProbeHooks,
|
|
55
|
+
spawn = spawnSync, env = process.env, envNames = ['PATH', 'HOME'], cwd,
|
|
56
|
+
cache = spawn === spawnSync ? nativeSandboxProbes : undefined } = {}) {
|
|
57
|
+
let mechanism = ['codex', 'muse'].includes(adapter.provider) ? 'bwrap' : `${adapter.provider} sandbox`;
|
|
58
|
+
let key;
|
|
59
|
+
let result;
|
|
60
|
+
try {
|
|
61
|
+
const hook = Object.hasOwn(probeHooks, adapter.provider) ? probeHooks[adapter.provider] : genericProbe;
|
|
62
|
+
const probe = hook({ env, cwd, adapter });
|
|
63
|
+
mechanism = probe.mechanism;
|
|
64
|
+
key = JSON.stringify([adapter.provider, probe, cwd, [...envNames].sort().map(name => [name, env[name]])]);
|
|
65
|
+
if (cache?.has(key)) return cache.get(key);
|
|
66
|
+
const managerEnvNames = resolveManagerEnvNames({ spawn });
|
|
67
|
+
const command = process.execPath;
|
|
68
|
+
const args = [fileURLToPath(new URL('./worker-isolation.mjs', import.meta.url)), '--worker-environment',
|
|
69
|
+
JSON.stringify(unitEnvironmentNames(envNames)), '--', probe.command, ...probe.args];
|
|
70
|
+
const argv = buildWorkerIsolationArgs({ unit: proposeWorkerUnitName(`sandbox-probe-${process.pid}-${++sequence}`),
|
|
71
|
+
command, args, cwd, env, envNames, managerEnvNames, memoryMaxGb: 0.25, attached: true,
|
|
72
|
+
extraProperties: ['RuntimeMaxSec=5s', 'TimeoutStopSec=1s'] });
|
|
73
|
+
const observed = spawn(resolveSystemdRun(), ['--collect', ...argv.slice(1)], {
|
|
74
|
+
encoding: 'utf8', timeout: 8000, maxBuffer: 4096, env: buildSystemdClientEnv({ env, envNames }) });
|
|
75
|
+
result = observed.status === 0 && !observed.error ? { ok: true, mode: 'systemd-unit', mechanism }
|
|
76
|
+
: { ok: false, mode: 'systemd-unit', mechanism, code: NATIVE_SANDBOX_UNAVAILABLE,
|
|
77
|
+
detail: bounded(observed.error?.message ?? (String(observed.stderr ?? '').trim() || `exit ${observed.status}`)) };
|
|
78
|
+
} catch (error) {
|
|
79
|
+
result = { ok: false, mode: 'systemd-unit', mechanism, code: NATIVE_SANDBOX_UNAVAILABLE, detail: bounded(error.message) };
|
|
80
|
+
}
|
|
81
|
+
if (key) cache?.set(key, result);
|
|
82
|
+
return result;
|
|
83
|
+
}
|