agentic-engineering-harness 0.4.16 → 0.5.1

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 (76) hide show
  1. package/README.md +34 -5
  2. package/dist/agents/outputContracts.d.ts +1 -0
  3. package/dist/agents/outputContracts.js +12 -0
  4. package/dist/agents/outputContracts.js.map +1 -1
  5. package/dist/agents/parallelism.d.ts +1 -0
  6. package/dist/agents/parallelism.js +101 -14
  7. package/dist/agents/parallelism.js.map +1 -1
  8. package/dist/agents/structuredOutput.js +16 -4
  9. package/dist/agents/structuredOutput.js.map +1 -1
  10. package/dist/agents/waveExecutor.d.ts +41 -0
  11. package/dist/agents/waveExecutor.js +208 -0
  12. package/dist/agents/waveExecutor.js.map +1 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/cli.js.map +1 -1
  15. package/dist/core/config.js +23 -6
  16. package/dist/core/config.js.map +1 -1
  17. package/dist/core/controlPlane.d.ts +30 -0
  18. package/dist/core/controlPlane.js +131 -0
  19. package/dist/core/controlPlane.js.map +1 -0
  20. package/dist/core/init.js +12 -4
  21. package/dist/core/init.js.map +1 -1
  22. package/dist/core/run.d.ts +21 -0
  23. package/dist/core/run.js +104 -41
  24. package/dist/core/run.js.map +1 -1
  25. package/dist/core/types.d.ts +83 -1
  26. package/dist/distributed/queue.d.ts +11 -0
  27. package/dist/distributed/queue.js +140 -0
  28. package/dist/distributed/queue.js.map +1 -0
  29. package/dist/distributed/types.d.ts +34 -0
  30. package/dist/distributed/types.js +2 -0
  31. package/dist/distributed/types.js.map +1 -0
  32. package/dist/distributed/worker.d.ts +21 -0
  33. package/dist/distributed/worker.js +111 -0
  34. package/dist/distributed/worker.js.map +1 -0
  35. package/dist/entry.js +110 -46
  36. package/dist/entry.js.map +1 -1
  37. package/dist/evals/runner.js +2 -2
  38. package/dist/evals/runner.js.map +1 -1
  39. package/dist/evals/statistics.d.ts +38 -0
  40. package/dist/evals/statistics.js +88 -0
  41. package/dist/evals/statistics.js.map +1 -0
  42. package/dist/evidence/graph.d.ts +48 -0
  43. package/dist/evidence/graph.js +94 -0
  44. package/dist/evidence/graph.js.map +1 -0
  45. package/dist/mcp/benchmark.d.ts +24 -0
  46. package/dist/mcp/benchmark.js +82 -0
  47. package/dist/mcp/benchmark.js.map +1 -0
  48. package/dist/paseo/start.d.ts +47 -0
  49. package/dist/paseo/start.js +155 -0
  50. package/dist/paseo/start.js.map +1 -0
  51. package/dist/policy/bundles.d.ts +27 -0
  52. package/dist/policy/bundles.js +129 -0
  53. package/dist/policy/bundles.js.map +1 -0
  54. package/dist/security/sandbox.d.ts +12 -0
  55. package/dist/security/sandbox.js +64 -0
  56. package/dist/security/sandbox.js.map +1 -0
  57. package/dist/toolchain/resolve.js +51 -90
  58. package/dist/toolchain/resolve.js.map +1 -1
  59. package/dist/utils/process.d.ts +1 -0
  60. package/dist/utils/process.js +6 -1
  61. package/dist/utils/process.js.map +1 -1
  62. package/dist/validators/graphify.js +28 -6
  63. package/dist/validators/graphify.js.map +1 -1
  64. package/dist/workers/agentPrompt.d.ts +6 -1
  65. package/dist/workers/agentPrompt.js +118 -34
  66. package/dist/workers/agentPrompt.js.map +1 -1
  67. package/dist/workers/podman.js +30 -8
  68. package/dist/workers/podman.js.map +1 -1
  69. package/docs/V0.5.1.md +129 -0
  70. package/docs/V0.5.md +116 -0
  71. package/package.json +2 -2
  72. package/schemas/project.schema.json +27 -8
  73. package/skills/engineering-workflow/SKILL.md +9 -1
  74. package/templates/AGENTS.md +6 -0
  75. package/templates/project.yaml +70 -1
  76. package/templates/toolchain.yaml +8 -1
@@ -0,0 +1,155 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { loadResolvedAgentTopology } from "../agents/config.js";
4
+ import { executionSelectionForAgent } from "../agents/routing.js";
5
+ import { setupToolchain } from "../toolchain/setup.js";
6
+ import { clearToolchainEnvCache, commandExists, runProcess } from "../utils/process.js";
7
+ export const PASEO_BOOTSTRAP_VERSION = 1;
8
+ const DEFAULT_DEPS = {
9
+ run: runProcess,
10
+ commandExists,
11
+ setupToolchain,
12
+ loadTopology: loadResolvedAgentTopology
13
+ };
14
+ export async function startPaseoHarness(root, config, options = {}, deps = DEFAULT_DEPS) {
15
+ const projectRoot = path.resolve(root);
16
+ const settings = config.orchestration?.interactive;
17
+ const autoSetup = options.autoSetup ?? settings?.autoSetup ?? true;
18
+ const webUi = options.webUi ?? settings?.webUi ?? true;
19
+ const forceNew = options.forceNew ?? false;
20
+ const reuseSession = settings?.reuseSession ?? true;
21
+ const stateDir = path.resolve(projectRoot, settings?.stateDir ?? ".harness/paseo");
22
+ const stateFile = path.join(stateDir, "lead-session.json");
23
+ const bootstrapFile = path.join(stateDir, "lead-bootstrap.md");
24
+ const topology = await deps.loadTopology(projectRoot, config, config.agents?.activeProfile);
25
+ const leadName = resolveLeadAgent(topology, options.leadAgent ?? settings?.leadAgent);
26
+ const lead = topology.agents[leadName];
27
+ const selection = executionSelectionForAgent(topology, leadName);
28
+ const runtimeCommand = runtimeExecutable(topology, leadName);
29
+ const missingBefore = await missingCommands(projectRoot, ["paseo", runtimeCommand], deps);
30
+ if (missingBefore.length && autoSetup) {
31
+ await deps.setupToolchain(projectRoot, config, { skipProjectDependencies: true });
32
+ clearToolchainEnvCache();
33
+ }
34
+ const missingAfter = await missingCommands(projectRoot, ["paseo", runtimeCommand], deps);
35
+ if (missingAfter.length)
36
+ throw new Error(`aeh start cannot launch Paseo because these managed commands are unavailable: ${missingAfter.join(", ")}. Run aeh setup or provide the required host prerequisite/credential.`);
37
+ let daemonStarted = false;
38
+ const daemonStatus = await deps.run("paseo daemon status --json", { cwd: projectRoot, timeoutMs: 30_000 });
39
+ if (daemonStatus.exitCode !== 0) {
40
+ const startCommand = webUi ? "paseo daemon start --web-ui" : "paseo daemon start";
41
+ const daemonStart = await deps.run(startCommand, { cwd: projectRoot, timeoutMs: 60_000 });
42
+ if (daemonStart.exitCode !== 0)
43
+ throw new Error(`Failed to start Paseo daemon: ${diagnostic(daemonStart)}`);
44
+ daemonStarted = true;
45
+ const verify = await deps.run("paseo daemon status --json", { cwd: projectRoot, timeoutMs: 30_000 });
46
+ if (verify.exitCode !== 0)
47
+ throw new Error(`Paseo daemon did not become ready after startup: ${diagnostic(verify)}`);
48
+ }
49
+ const provider = selection.paseoProvider;
50
+ const model = paseoModel(selection);
51
+ const title = options.title ?? settings?.title ?? `AEH Lead · ${config.project.name}`;
52
+ const aehCommand = options.aehCommand ?? "aeh";
53
+ const bootstrap = buildPaseoLeadBootstrap(config.project.name, projectRoot, aehCommand);
54
+ await fs.mkdir(stateDir, { recursive: true });
55
+ await fs.writeFile(bootstrapFile, `${bootstrap}\n`);
56
+ if (!forceNew && reuseSession) {
57
+ const existing = await loadState(stateFile);
58
+ if (existing && compatibleState(existing, { projectRoot, leadName, provider, model, title })) {
59
+ const probe = await deps.run(`paseo logs ${quote(existing.agentId)} --tail 1`, { cwd: projectRoot, timeoutMs: 30_000 });
60
+ if (probe.exitCode === 0)
61
+ return { daemonStarted, session: "reused", agentId: existing.agentId, title: existing.title, leadAgent: existing.leadAgent, provider: existing.provider, model: existing.model, stateFile, bootstrapFile };
62
+ }
63
+ }
64
+ const launch = await deps.run([
65
+ "paseo run --background --quiet",
66
+ `--title ${quote(title)}`,
67
+ `--provider ${quote(provider)}`,
68
+ `--model ${quote(model)}`,
69
+ quote(bootstrap)
70
+ ].join(" "), { cwd: projectRoot, timeoutMs: 60_000 });
71
+ if (launch.exitCode !== 0)
72
+ throw new Error(`Failed to create persistent AEH lead in Paseo: ${diagnostic(launch)}`);
73
+ const agentId = extractAgentId(launch.stdout);
74
+ if (!agentId)
75
+ throw new Error(`Paseo created no parseable agent id. Output: ${launch.stdout.trim() || "<empty>"}`);
76
+ const wait = await deps.run(`paseo wait ${quote(agentId)} --timeout 300`, { cwd: projectRoot, timeoutMs: 330_000 });
77
+ if (wait.exitCode !== 0)
78
+ throw new Error(`AEH lead bootstrap did not complete successfully: ${diagnostic(wait)}`);
79
+ const state = {
80
+ version: 1,
81
+ bootstrapVersion: PASEO_BOOTSTRAP_VERSION,
82
+ projectRoot,
83
+ projectName: config.project.name,
84
+ agentId,
85
+ title,
86
+ leadAgent: leadName,
87
+ provider,
88
+ model,
89
+ createdAt: new Date().toISOString()
90
+ };
91
+ await fs.writeFile(stateFile, `${JSON.stringify(state, null, 2)}\n`);
92
+ return { daemonStarted, session: "created", agentId, title, leadAgent: leadName, provider, model, stateFile, bootstrapFile };
93
+ }
94
+ export function buildPaseoLeadBootstrap(projectName, projectRoot, aehCommand) {
95
+ return `AEH persistent Paseo lead bootstrap v${PASEO_BOOTSTRAP_VERSION}.
96
+
97
+ You are the persistent top-level engineering lead for project ${JSON.stringify(projectName)} at ${JSON.stringify(projectRoot)}. This instruction governs every later user message in this Paseo conversation.
98
+
99
+ Before handling repository-changing work, read AGENTS.md and .harness/skills/engineering-workflow/SKILL.md when present. Treat every natural-language request that could modify the repository as input to the Agentic Engineering Harness workflow. The user must not have to mention AEH, QUICK, SPEC, SDD, TaskContract, validators, reviewers, Graphify, workers, or any Harness command.
100
+
101
+ Whenever the workflow skill says \`aeh\`, invoke the Harness through this command on this machine: \`${aehCommand}\`.
102
+
103
+ Required behavior for mutating engineering requests:
104
+ 1. inspect repository context and automatically build triage evidence;
105
+ 2. run deterministic QUICK/SPEC triage and obey the result without manual downgrade;
106
+ 3. create/validate the QuickContract or complete SDD/TaskContract as required;
107
+ 4. seal normative inputs before implementation;
108
+ 5. invoke the Harness run so routed workers, planner waves, deterministic validators, evidence graph, review convergence, remediation/escalation and lead acceptance execute under the frozen control plane;
109
+ 6. never implement directly as a shortcut around Harness execution;
110
+ 7. keep implementation/tool/review failures autonomous unless the Harness reaches a true human-on-exception state;
111
+ 8. surface concise progress and final acceptance/delivery state to the user.
112
+
113
+ Read-only questions may be answered directly, but must not mutate repository state. Existing GitHub issue requests must use the issue-driven Harness path. If a QUICK request discovers architecture/security/schema/public-API/cross-module or otherwise unsafe scope, escalate it to SPEC rather than broadening QUICK.
114
+
115
+ You remain the parent Paseo session. Harness-spawned agents are workers/subagents, not replacements for this lead. Do not ask the user to choose the internal workflow.
116
+
117
+ Initialize this role now by reading the project entry instructions. Do not start an engineering change until the user supplies one. When initialization is complete, respond with exactly: AEH READY`;
118
+ }
119
+ export function resolveLeadAgent(topology, configured) {
120
+ if (configured) {
121
+ const selected = topology.agents[configured];
122
+ if (!selected || selected.disabled)
123
+ throw new Error(`Configured interactive lead agent '${configured}' is unavailable.`);
124
+ return configured;
125
+ }
126
+ if (topology.agents.lead && !topology.agents.lead.disabled)
127
+ return "lead";
128
+ const orchestrator = Object.values(topology.agents).find((agent) => agent.role === "orchestrator" && !agent.disabled);
129
+ if (!orchestrator)
130
+ throw new Error("aeh start requires an enabled orchestrator/lead agent in the resolved topology.");
131
+ return orchestrator.name;
132
+ }
133
+ function runtimeExecutable(topology, leadName) {
134
+ const runtime = topology.agents[leadName].runtime;
135
+ return runtime.command?.trim().split(/\s+/, 1)[0] || runtime.adapter;
136
+ }
137
+ async function missingCommands(root, commands, deps) {
138
+ const result = [];
139
+ for (const command of [...new Set(commands.filter(Boolean))])
140
+ if (!(await deps.commandExists(command, root)))
141
+ result.push(command);
142
+ return result;
143
+ }
144
+ function paseoModel(selection) { return selection.runtimeAdapter === "codex" ? selection.modelName : selection.modelId; }
145
+ function extractAgentId(stdout) { return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).at(-1); }
146
+ async function loadState(file) { try {
147
+ return JSON.parse(await fs.readFile(file, "utf8"));
148
+ }
149
+ catch {
150
+ return undefined;
151
+ } }
152
+ function compatibleState(state, expected) { return state.version === 1 && state.bootstrapVersion === PASEO_BOOTSTRAP_VERSION && state.projectRoot === expected.projectRoot && state.leadAgent === expected.leadName && state.provider === expected.provider && state.model === expected.model && state.title === expected.title; }
153
+ function diagnostic(result) { return [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join("\n") || `exit code ${result.exitCode}`; }
154
+ function quote(value) { return `'${value.replaceAll("'", "'\\''")}'`; }
155
+ //# sourceMappingURL=start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/paseo/start.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { HarnessProjectConfig } from "../core/types.js";
2
+ export interface PolicyBundleFile {
3
+ path: string;
4
+ sha256: string;
5
+ }
6
+ export interface OrganizationPolicyBundleManifest {
7
+ version: 1;
8
+ name: string;
9
+ extends?: string[];
10
+ policyDirs?: string[];
11
+ files: PolicyBundleFile[];
12
+ }
13
+ export interface ResolvedPolicyBundle {
14
+ name: string;
15
+ root: string;
16
+ manifestSha256: string;
17
+ verifiedSignature: boolean;
18
+ policyDirs: string[];
19
+ extends: string[];
20
+ }
21
+ export interface PolicyBundleResolution {
22
+ bundles: ResolvedPolicyBundle[];
23
+ policyDirs: string[];
24
+ issues: string[];
25
+ }
26
+ export declare function resolveOrganizationPolicyBundles(root: string, config: HarnessProjectConfig): Promise<PolicyBundleResolution>;
27
+ export declare function withOrganizationPolicies(config: HarnessProjectConfig, resolution: PolicyBundleResolution): HarnessProjectConfig;
@@ -0,0 +1,129 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { commandExists, runProcess } from "../utils/process.js";
5
+ export async function resolveOrganizationPolicyBundles(root, config) {
6
+ const settings = config.organization?.policyBundles;
7
+ const sources = settings?.sources ?? [];
8
+ if (!sources.length)
9
+ return { bundles: [], policyDirs: [], issues: [] };
10
+ const cacheRoot = path.resolve(root, settings?.cacheDir ?? ".harness/policy-bundles");
11
+ await fs.mkdir(cacheRoot, { recursive: true });
12
+ const byName = new Map(sources.map((source) => [source.name, source]));
13
+ const resolved = new Map();
14
+ const issues = [];
15
+ const visiting = new Set();
16
+ const visit = async (name) => {
17
+ if (resolved.has(name))
18
+ return;
19
+ if (visiting.has(name))
20
+ throw new Error(`Organization policy bundle inheritance cycle at ${name}.`);
21
+ const source = byName.get(name);
22
+ if (!source)
23
+ throw new Error(`Organization policy bundle '${name}' is referenced but not configured.`);
24
+ visiting.add(name);
25
+ try {
26
+ const bundle = await materializeBundle(root, cacheRoot, source);
27
+ for (const parent of bundle.extends)
28
+ await visit(parent);
29
+ resolved.set(name, bundle);
30
+ }
31
+ catch (error) {
32
+ const message = `${name}: ${String(error)}`;
33
+ issues.push(message);
34
+ if (source.required !== false || settings?.required === true)
35
+ throw new Error(`Required organization policy bundle failed: ${message}`);
36
+ }
37
+ finally {
38
+ visiting.delete(name);
39
+ }
40
+ };
41
+ for (const source of sources)
42
+ await visit(source.name);
43
+ const bundles = [...resolved.values()];
44
+ return { bundles, policyDirs: [...new Set(bundles.flatMap((bundle) => bundle.policyDirs))], issues };
45
+ }
46
+ export function withOrganizationPolicies(config, resolution) {
47
+ if (!resolution.policyDirs.length)
48
+ return config;
49
+ const existing = config.validation?.opa?.policyDirs ?? [];
50
+ return {
51
+ ...config,
52
+ validation: {
53
+ ...config.validation,
54
+ opa: { ...config.validation?.opa, enabled: config.validation?.opa?.enabled ?? true, policyDirs: [...new Set([...existing, ...resolution.policyDirs])] }
55
+ }
56
+ };
57
+ }
58
+ async function materializeBundle(projectRoot, cacheRoot, source) {
59
+ const target = path.join(cacheRoot, safe(source.name));
60
+ await fs.rm(target, { recursive: true, force: true });
61
+ await fs.mkdir(target, { recursive: true });
62
+ const loaded = source.path ? await loadLocalManifest(projectRoot, source.path) : await loadRemoteManifest(source.url);
63
+ if (loaded.manifest.name !== source.name)
64
+ throw new Error(`manifest name '${loaded.manifest.name}' does not match configured source '${source.name}'.`);
65
+ const manifestHash = sha256(loaded.raw);
66
+ if (source.sha256 && source.sha256 !== manifestHash)
67
+ throw new Error(`manifest SHA-256 mismatch: expected ${source.sha256}, got ${manifestHash}.`);
68
+ const manifestFile = path.join(target, "bundle.json");
69
+ await fs.writeFile(manifestFile, loaded.raw);
70
+ let verifiedSignature = false;
71
+ if (source.signature || source.publicKey) {
72
+ if (!source.signature || !source.publicKey)
73
+ throw new Error("signed bundle requires both signature and publicKey.");
74
+ verifiedSignature = await verifySignature(projectRoot, manifestFile, source.signature, source.publicKey);
75
+ if (!verifiedSignature)
76
+ throw new Error("cosign signature verification failed.");
77
+ }
78
+ for (const file of loaded.manifest.files) {
79
+ if (file.path.includes("..") || path.isAbsolute(file.path))
80
+ throw new Error(`unsafe bundle file path ${file.path}.`);
81
+ const content = source.path ? await readLocalBundleFile(projectRoot, source.path, file.path) : await readRemoteBundleFile(source.url, file.path);
82
+ const actual = sha256(content);
83
+ if (actual !== file.sha256)
84
+ throw new Error(`${file.path} SHA-256 mismatch: expected ${file.sha256}, got ${actual}.`);
85
+ const destination = path.join(target, file.path);
86
+ await fs.mkdir(path.dirname(destination), { recursive: true });
87
+ await fs.writeFile(destination, content);
88
+ }
89
+ const policyDirs = (loaded.manifest.policyDirs?.length ? loaded.manifest.policyDirs : ["policies"]).map((relative) => path.relative(projectRoot, path.resolve(target, relative)).replaceAll("\\", "/"));
90
+ return { name: source.name, root: target, manifestSha256: manifestHash, verifiedSignature, policyDirs, extends: loaded.manifest.extends ?? [] };
91
+ }
92
+ async function loadLocalManifest(projectRoot, configuredPath) {
93
+ const source = path.resolve(projectRoot, configuredPath);
94
+ const stat = await fs.stat(source);
95
+ const manifestFile = stat.isDirectory() ? path.join(source, "bundle.json") : source;
96
+ const raw = await fs.readFile(manifestFile);
97
+ return { manifest: parseManifest(raw), raw };
98
+ }
99
+ async function loadRemoteManifest(url) {
100
+ const response = await fetch(url);
101
+ if (!response.ok)
102
+ throw new Error(`unable to fetch manifest ${url}: HTTP ${response.status}`);
103
+ const raw = Buffer.from(await response.arrayBuffer());
104
+ return { manifest: parseManifest(raw), raw };
105
+ }
106
+ function parseManifest(raw) {
107
+ const value = JSON.parse(raw.toString("utf8"));
108
+ if (value.version !== 1 || !value.name || !Array.isArray(value.files))
109
+ throw new Error("invalid organization policy bundle manifest.");
110
+ for (const file of value.files)
111
+ if (!file.path || !/^[a-f0-9]{64}$/.test(file.sha256))
112
+ throw new Error(`invalid bundle file declaration ${file.path ?? "<missing>"}.`);
113
+ return value;
114
+ }
115
+ async function readLocalBundleFile(projectRoot, configuredPath, relative) { const source = path.resolve(projectRoot, configuredPath); const stat = await fs.stat(source); const base = stat.isDirectory() ? source : path.dirname(source); return fs.readFile(path.resolve(base, relative)); }
116
+ async function readRemoteBundleFile(manifestUrl, relative) { const url = new URL(relative, manifestUrl).toString(); const response = await fetch(url); if (!response.ok)
117
+ throw new Error(`unable to fetch bundle file ${url}: HTTP ${response.status}`); return Buffer.from(await response.arrayBuffer()); }
118
+ async function verifySignature(root, manifestFile, signature, publicKey) {
119
+ if (!(await commandExists("cosign", root)))
120
+ throw new Error("cosign is required to verify the configured organization policy bundle signature.");
121
+ const signaturePath = path.resolve(root, signature);
122
+ const keyPath = path.resolve(root, publicKey);
123
+ const result = await runProcess(`cosign verify-blob --key ${quote(keyPath)} --signature ${quote(signaturePath)} ${quote(manifestFile)}`, { cwd: root, timeoutMs: 60_000 });
124
+ return result.exitCode === 0;
125
+ }
126
+ function sha256(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
127
+ function safe(value) { return value.replace(/[^A-Za-z0-9._-]/g, "-"); }
128
+ function quote(value) { return `'${value.replaceAll("'", "'\\''")}'`; }
129
+ //# sourceMappingURL=bundles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundles.js","sourceRoot":"","sources":["../../src/policy/bundles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { AgentExecutionSelection } from "../agents/types.js";
2
+ import type { HarnessProjectConfig, TaskRisk } from "../core/types.js";
3
+ export interface SandboxDecision {
4
+ required: boolean;
5
+ provider: string;
6
+ reasons: string[];
7
+ selection: AgentExecutionSelection;
8
+ }
9
+ export declare function enforceSandboxPolicy(selection: AgentExecutionSelection, config: HarnessProjectConfig, risk?: TaskRisk): SandboxDecision;
10
+ export declare function hardenedPodmanArgs(config: HarnessProjectConfig, selection: AgentExecutionSelection, writable: boolean): string[];
11
+ export declare function sandboxImage(config: HarnessProjectConfig): string;
12
+ export declare function allowedSandboxEnvironment(config: HarnessProjectConfig, source?: NodeJS.ProcessEnv): Record<string, string>;
@@ -0,0 +1,64 @@
1
+ export function enforceSandboxPolicy(selection, config, risk = "low") {
2
+ const sandbox = config.security?.sandbox;
3
+ const force = (sandbox?.forceForRisks ?? []).includes(risk);
4
+ const required = sandbox?.required === true || force;
5
+ const provider = sandbox?.provider ?? "podman";
6
+ if (!required)
7
+ return { required: false, provider, reasons: [], selection };
8
+ if (provider === "none")
9
+ throw new Error(`Sandbox is required for ${risk}-risk work but security.sandbox.provider is none.`);
10
+ if (selection.runtimeAdapter !== "opencode")
11
+ throw new Error(`Sandbox policy requires ${provider}, but runtime ${selection.runtimeAdapter} is not supported by the hardened worker sandbox.`);
12
+ if (!sandbox?.image)
13
+ throw new Error("Sandbox policy requires security.sandbox.image.");
14
+ return { required: true, provider, reasons: force ? [`risk:${risk}`] : ["security.sandbox.required"], selection: { ...selection, transport: provider === "podman" ? "podman" : selection.transport } };
15
+ }
16
+ export function hardenedPodmanArgs(config, selection, writable) {
17
+ const sandbox = config.security?.sandbox;
18
+ const args = ["--rm", "-i", "--userns=keep-id"];
19
+ if (sandbox?.readOnlyRoot !== false)
20
+ args.push("--read-only");
21
+ if (sandbox?.capDropAll !== false)
22
+ args.push("--cap-drop=ALL");
23
+ if (sandbox?.noNewPrivileges !== false)
24
+ args.push("--security-opt=no-new-privileges");
25
+ args.push(`--pids-limit=${sandbox?.pidsLimit ?? 512}`);
26
+ if (sandbox?.memory)
27
+ args.push(`--memory=${sandbox.memory}`);
28
+ if (sandbox?.cpus)
29
+ args.push(`--cpus=${sandbox.cpus}`);
30
+ if (sandbox?.network === false || selection.permissions.network === "deny")
31
+ args.push("--network=none");
32
+ const tmpfs = sandbox?.tmpfs ?? ["/tmp:rw,nosuid,nodev,noexec,size=1g"];
33
+ for (const mount of tmpfs)
34
+ args.push(`--tmpfs=${mount}`);
35
+ if (sandbox?.ephemeralHome !== false) {
36
+ args.push("--tmpfs=/home/aeh:rw,nosuid,nodev,size=256m");
37
+ args.push("--env=HOME=/home/aeh");
38
+ }
39
+ if (!writable)
40
+ args.push("--env=AEH_WORKSPACE_READ_ONLY=1");
41
+ for (const extra of sandbox?.extraArgs ?? [])
42
+ args.push(extra);
43
+ return args;
44
+ }
45
+ export function sandboxImage(config) {
46
+ const sandbox = config.security?.sandbox;
47
+ if (!sandbox?.image)
48
+ throw new Error("security.sandbox.image is required for Podman execution.");
49
+ if (!sandbox.imageDigest || sandbox.image.includes("@sha256:"))
50
+ return sandbox.image;
51
+ const digest = sandbox.imageDigest.startsWith("sha256:") ? sandbox.imageDigest : `sha256:${sandbox.imageDigest}`;
52
+ return `${sandbox.image.replace(/:[^/@]+$/, "")}@${digest}`;
53
+ }
54
+ export function allowedSandboxEnvironment(config, source = process.env) {
55
+ const sandbox = config.security?.sandbox;
56
+ const allowed = new Set(sandbox?.environmentAllowlist ?? []);
57
+ const credentials = new Set(sandbox?.credentialEnvAllowlist ?? []);
58
+ const result = {};
59
+ for (const name of [...allowed, ...credentials])
60
+ if (source[name] !== undefined)
61
+ result[name] = source[name];
62
+ return result;
63
+ }
64
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/security/sandbox.ts"],"names":[],"mappings":"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"}
@@ -4,10 +4,9 @@ import { loadResolvedAgentTopology } from "../agents/config.js";
4
4
  export async function resolveToolchain(root, project, toolchain, options = {}) {
5
5
  const profile = options.profile ?? "auto";
6
6
  const selected = new Map();
7
- if (profile !== "auto") {
7
+ if (profile !== "auto")
8
8
  for (const name of profileTools(toolchain, profile))
9
9
  select(selected, name, `profile:${profile}`);
10
- }
11
10
  else {
12
11
  const capabilities = await activeCapabilities(root, project);
13
12
  for (const [name, definition] of Object.entries(toolchain.tools)) {
@@ -22,35 +21,15 @@ export async function resolveToolchain(root, project, toolchain, options = {}) {
22
21
  expandDependencies(toolchain, selected);
23
22
  const versionOverrides = await projectVersionOverrides(root);
24
23
  const preferContainers = options.preferContainers ?? toolchain.strategy?.validators === "prefer-container";
25
- const tools = [...selected.entries()].map(([name, selectedBy]) => {
26
- const definition = requireTool(toolchain, name);
27
- const provisioning = definition.kind === "system" ? "system"
28
- : preferContainers && options.containerAvailable && definition.container ? "container"
29
- : "mise";
30
- return { name, ...definition, version: versionOverrides[name] ?? definition.version, selectedBy, provisioning };
31
- });
24
+ const tools = [...selected.entries()].map(([name, selectedBy]) => { const definition = requireTool(toolchain, name); const provisioning = definition.kind === "system" ? "system" : preferContainers && options.containerAvailable && definition.container ? "container" : "mise"; return { name, ...definition, version: versionOverrides[name] ?? definition.version, selectedBy, provisioning }; });
32
25
  tools.sort((a, b) => a.name.localeCompare(b.name));
33
26
  return { profile, tools };
34
27
  }
35
- export function profileTools(toolchain, profile) {
36
- const visiting = new Set();
37
- const result = new Set();
38
- const visit = (name) => {
39
- if (visiting.has(name))
40
- throw new Error(`Toolchain profile cycle detected at '${name}'.`);
41
- const value = toolchain.profiles?.[name];
42
- if (!value)
43
- throw new Error(`Unknown toolchain profile '${name}'.`);
44
- visiting.add(name);
45
- for (const parent of value.extends ?? [])
46
- visit(parent);
47
- for (const tool of value.tools ?? [])
48
- result.add(tool);
49
- visiting.delete(name);
50
- };
51
- visit(profile);
52
- return [...result];
53
- }
28
+ export function profileTools(toolchain, profile) { const visiting = new Set(); const result = new Set(); const visit = (name) => { if (visiting.has(name))
29
+ throw new Error(`Toolchain profile cycle detected at '${name}'.`); const value = toolchain.profiles?.[name]; if (!value)
30
+ throw new Error(`Unknown toolchain profile '${name}'.`); visiting.add(name); for (const parent of value.extends ?? [])
31
+ visit(parent); for (const tool of value.tools ?? [])
32
+ result.add(tool); visiting.delete(name); }; visit(profile); return [...result]; }
54
33
  async function activeCapabilities(root, project) {
55
34
  const result = new Set();
56
35
  if (project.agents) {
@@ -60,7 +39,7 @@ async function activeCapabilities(root, project) {
60
39
  if (!agent.disabled)
61
40
  result.add(`runtime:${agent.runtime.name}`);
62
41
  }
63
- catch { /* doctor/setup will surface topology problems independently */ }
42
+ catch { /* doctor/setup surfaces topology independently */ }
64
43
  }
65
44
  if (project.orchestration?.provider)
66
45
  result.add(`orchestration:${project.orchestration.provider}`);
@@ -76,6 +55,8 @@ async function activeCapabilities(root, project) {
76
55
  result.add("validation:opa");
77
56
  if (project.security?.sandbox?.provider)
78
57
  result.add(`sandbox:${project.security.sandbox.provider}`);
58
+ if ((project.organization?.policyBundles?.sources ?? []).some((source) => Boolean(source.signature || source.publicKey)))
59
+ result.add("organization:signed-policy");
79
60
  for (const name of project.security?.tools ?? [])
80
61
  result.add(`security-tool:${name}`);
81
62
  for (const validator of project.validation?.validators ?? [])
@@ -83,75 +64,55 @@ async function activeCapabilities(root, project) {
83
64
  await addProjectCapabilities(root, result);
84
65
  return result;
85
66
  }
86
- async function addProjectCapabilities(root, result) {
87
- const files = new Set(await fs.readdir(root).catch(() => []));
88
- if (files.has("package.json"))
89
- result.add("project:node");
90
- if (files.has("bun.lock") || files.has("bun.lockb"))
91
- result.add("project:bun");
92
- if (files.has("pnpm-lock.yaml"))
93
- result.add("project:pnpm");
94
- if (files.has("yarn.lock"))
95
- result.add("project:yarn");
96
- if (files.has("uv.lock") || files.has("pyproject.toml"))
97
- result.add("project:python");
98
- if ([...files].some((name) => name.endsWith(".sln") || name.endsWith(".slnx") || name.endsWith(".csproj")) || files.has("global.json"))
99
- result.add("project:dotnet");
100
- if (files.has("go.mod"))
101
- result.add("project:go");
102
- if (files.has("Cargo.toml"))
103
- result.add("project:rust");
104
- try {
105
- const pkg = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf8"));
106
- const manager = typeof pkg.packageManager === "string" ? pkg.packageManager.split("@")[0] : undefined;
107
- if (manager)
108
- result.add(`project:${manager}`);
109
- }
110
- catch { /* package.json is optional */ }
67
+ async function addProjectCapabilities(root, result) { const files = new Set(await fs.readdir(root).catch(() => [])); if (files.has("package.json"))
68
+ result.add("project:node"); if (files.has("bun.lock") || files.has("bun.lockb"))
69
+ result.add("project:bun"); if (files.has("pnpm-lock.yaml"))
70
+ result.add("project:pnpm"); if (files.has("yarn.lock"))
71
+ result.add("project:yarn"); if (files.has("uv.lock") || files.has("pyproject.toml"))
72
+ result.add("project:python"); if ([...files].some((name) => name.endsWith(".sln") || name.endsWith(".slnx") || name.endsWith(".csproj")) || files.has("global.json"))
73
+ result.add("project:dotnet"); if (files.has("go.mod"))
74
+ result.add("project:go"); if (files.has("Cargo.toml"))
75
+ result.add("project:rust"); try {
76
+ const pkg = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf8"));
77
+ const manager = typeof pkg.packageManager === "string" ? pkg.packageManager.split("@")[0] : undefined;
78
+ if (manager)
79
+ result.add(`project:${manager}`);
111
80
  }
112
- async function projectVersionOverrides(root) {
113
- const result = {};
114
- for (const name of [".node-version", ".nvmrc"]) {
115
- try {
116
- const value = (await fs.readFile(path.join(root, name), "utf8")).trim().replace(/^v/, "");
117
- if (value) {
118
- result.node = value;
119
- break;
120
- }
121
- }
122
- catch { /* optional */ }
123
- }
124
- try {
125
- const global = JSON.parse(await fs.readFile(path.join(root, "global.json"), "utf8"));
126
- if (typeof global.sdk?.version === "string" && global.sdk.version.trim())
127
- result.dotnet = global.sdk.version.trim();
128
- }
129
- catch { /* optional */ }
81
+ catch { /* optional */ } }
82
+ async function projectVersionOverrides(root) { const result = {}; for (const name of [".node-version", ".nvmrc"]) {
130
83
  try {
131
- const pkg = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf8"));
132
- if (typeof pkg.packageManager === "string") {
133
- const match = pkg.packageManager.match(/^bun@(.+)$/);
134
- if (match)
135
- result.bun = match[1];
84
+ const value = (await fs.readFile(path.join(root, name), "utf8")).trim().replace(/^v/, "");
85
+ if (value) {
86
+ result.node = value;
87
+ break;
136
88
  }
137
89
  }
138
90
  catch { /* optional */ }
139
- return result;
91
+ } try {
92
+ const global = JSON.parse(await fs.readFile(path.join(root, "global.json"), "utf8"));
93
+ if (typeof global.sdk?.version === "string" && global.sdk.version.trim())
94
+ result.dotnet = global.sdk.version.trim();
140
95
  }
141
- function expandDependencies(toolchain, selected) {
142
- let changed = true;
143
- while (changed) {
144
- changed = false;
145
- for (const name of [...selected.keys()]) {
146
- const tool = requireTool(toolchain, name);
147
- for (const dependency of tool.dependsOn ?? [])
148
- if (!selected.has(dependency)) {
149
- select(selected, dependency, `dependency:${name}`);
150
- changed = true;
151
- }
152
- }
96
+ catch { /* optional */ } try {
97
+ const pkg = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf8"));
98
+ if (typeof pkg.packageManager === "string") {
99
+ const match = pkg.packageManager.match(/^bun@(.+)$/);
100
+ if (match)
101
+ result.bun = match[1];
153
102
  }
154
103
  }
104
+ catch { /* optional */ } return result; }
105
+ function expandDependencies(toolchain, selected) { let changed = true; while (changed) {
106
+ changed = false;
107
+ for (const name of [...selected.keys()]) {
108
+ const tool = requireTool(toolchain, name);
109
+ for (const dependency of tool.dependsOn ?? [])
110
+ if (!selected.has(dependency)) {
111
+ select(selected, dependency, `dependency:${name}`);
112
+ changed = true;
113
+ }
114
+ }
115
+ } }
155
116
  function select(selected, name, reason) { const reasons = selected.get(name) ?? []; if (!reasons.includes(reason))
156
117
  reasons.push(reason); selected.set(name, reasons); }
157
118
  function requireTool(toolchain, name) { const tool = toolchain.tools[name]; if (!tool)