voratiq 0.1.0-beta.23 → 0.1.0-beta.24
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 +1 -1
- package/dist/agents/runtime/harness.js +10 -1
- package/dist/agents/runtime/launcher.d.ts +2 -0
- package/dist/agents/runtime/launcher.js +2 -1
- package/dist/agents/runtime/operator-access.d.ts +68 -0
- package/dist/agents/runtime/operator-access.js +95 -0
- package/dist/agents/runtime/policy.d.ts +2 -1
- package/dist/agents/runtime/policy.js +15 -42
- package/dist/agents/runtime/registry.d.ts +4 -0
- package/dist/agents/runtime/registry.js +132 -0
- package/dist/bin.js +2 -71
- package/dist/cli/contract.d.ts +2 -32
- package/dist/cli/contract.js +0 -54
- package/dist/cli/operator-envelope.d.ts +0 -7
- package/dist/cli/operator-envelope.js +0 -22
- package/dist/commands/apply/command.js +7 -1
- package/dist/commands/fetch.d.ts +0 -1
- package/dist/commands/fetch.js +1 -5
- package/dist/commands/interactive/lifecycle.js +14 -0
- package/dist/commands/list/command.js +1 -4
- package/dist/commands/list/normalization.js +3 -0
- package/dist/commands/message/command.js +36 -29
- package/dist/commands/message/lifecycle.d.ts +2 -0
- package/dist/commands/message/lifecycle.js +141 -36
- package/dist/commands/reduce/command.js +115 -62
- package/dist/commands/reduce/lifecycle.d.ts +16 -0
- package/dist/commands/reduce/lifecycle.js +219 -0
- package/dist/commands/reduce/targets.js +7 -10
- package/dist/commands/run/command.js +115 -25
- package/dist/commands/run/lifecycle.d.ts +4 -1
- package/dist/commands/run/lifecycle.js +183 -84
- package/dist/commands/run/record-init.js +0 -1
- package/dist/commands/shared/teardown-registry.d.ts +12 -0
- package/dist/commands/shared/teardown-registry.js +35 -0
- package/dist/commands/spec/command.js +132 -115
- package/dist/commands/spec/lifecycle.d.ts +16 -0
- package/dist/commands/spec/lifecycle.js +205 -0
- package/dist/commands/verify/command.js +36 -29
- package/dist/commands/verify/lifecycle.d.ts +2 -0
- package/dist/commands/verify/lifecycle.js +166 -44
- package/dist/commands/verify/targets.js +3 -6
- package/dist/competition/shared/prompt-helpers.d.ts +7 -3
- package/dist/competition/shared/prompt-helpers.js +41 -8
- package/dist/competition/shared/sandbox-policy.d.ts +13 -3
- package/dist/competition/shared/sandbox-policy.js +215 -4
- package/dist/competition/shared/teardown.d.ts +12 -0
- package/dist/competition/shared/teardown.js +60 -4
- package/dist/domain/interactive/prompt.d.ts +1 -1
- package/dist/domain/interactive/prompt.js +1 -1
- package/dist/domain/message/competition/adapter.js +11 -1
- package/dist/domain/message/competition/prompt.js +3 -2
- package/dist/domain/message/model/mutators.js +11 -6
- package/dist/domain/reduce/competition/adapter.d.ts +2 -0
- package/dist/domain/reduce/competition/adapter.js +16 -8
- package/dist/domain/reduce/competition/prompt.d.ts +1 -1
- package/dist/domain/reduce/competition/prompt.js +4 -3
- package/dist/domain/run/competition/agent-preparation.js +18 -10
- package/dist/domain/run/competition/agents/artifacts.js +27 -5
- package/dist/domain/run/competition/agents/lifecycle.js +12 -2
- package/dist/domain/run/competition/agents/preparation.js +2 -0
- package/dist/domain/run/competition/agents/types.d.ts +1 -0
- package/dist/domain/run/competition/prompt.d.ts +1 -0
- package/dist/domain/run/competition/prompt.js +4 -3
- package/dist/domain/run/model/enhanced.d.ts +0 -1
- package/dist/domain/run/model/enhanced.js +0 -3
- package/dist/domain/run/model/mutators.js +11 -9
- package/dist/domain/run/model/types.d.ts +8 -10
- package/dist/domain/run/model/types.js +5 -4
- package/dist/domain/run/persistence/adapter.d.ts +0 -1
- package/dist/domain/run/persistence/adapter.js +2 -2
- package/dist/domain/spec/competition/adapter.d.ts +2 -0
- package/dist/domain/spec/competition/adapter.js +20 -11
- package/dist/domain/spec/competition/prompt.js +3 -2
- package/dist/domain/spec/model/mutators.js +12 -7
- package/dist/domain/verify/competition/adapter.js +20 -16
- package/dist/domain/verify/competition/blinding.d.ts +4 -0
- package/dist/domain/verify/competition/blinding.js +7 -0
- package/dist/domain/verify/competition/prompt.js +21 -2
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +3 -38
- package/dist/render/transcripts/run.d.ts +3 -2
- package/dist/render/transcripts/stage-progress.d.ts +1 -1
- package/dist/render/utils/transcript-shell.js +3 -3
- package/dist/status/colors.js +0 -1
- package/dist/status/index.d.ts +1 -2
- package/dist/status/index.js +0 -1
- package/dist/utils/git.d.ts +6 -0
- package/dist/utils/git.js +15 -0
- package/dist/utils/slug.d.ts +0 -1
- package/dist/utils/slug.js +0 -4
- package/dist/workspace/agents.js +1 -0
- package/dist/workspace/cleanup.d.ts +8 -0
- package/dist/workspace/cleanup.js +28 -0
- package/dist/workspace/dependencies.d.ts +11 -0
- package/dist/workspace/dependencies.js +150 -13
- package/package.json +2 -1
- package/dist/cli/prune.d.ts +0 -18
- package/dist/cli/prune.js +0 -99
- package/dist/commands/prune/command.d.ts +0 -3
- package/dist/commands/prune/command.js +0 -391
- package/dist/commands/prune/errors.d.ts +0 -17
- package/dist/commands/prune/errors.js +0 -33
- package/dist/commands/prune/types.d.ts +0 -63
- package/dist/commands/prune/types.js +0 -1
- package/dist/competition/shared/prune.d.ts +0 -1
- package/dist/competition/shared/prune.js +0 -4
- package/dist/render/transcripts/prune.d.ts +0 -21
- package/dist/render/transcripts/prune.js +0 -97
- package/dist/workspace/prune.d.ts +0 -8
- package/dist/workspace/prune.js +0 -29
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npm install -g voratiq
|
|
|
15
15
|
|
|
16
16
|
- Node 20+
|
|
17
17
|
- git
|
|
18
|
-
- 1+ AI coding agent (Claude [>=2.1.111](https://github.com/anthropics/claude-code?tab=readme-ov-file#get-started), Codex [>=0.
|
|
18
|
+
- 1+ AI coding agent (Claude [>=2.1.111](https://github.com/anthropics/claude-code?tab=readme-ov-file#get-started), Codex [>=0.122.0](https://github.com/openai/codex?tab=readme-ov-file#quickstart), or Gemini [>=0.31.0](https://github.com/google-gemini/gemini-cli?tab=readme-ov-file#quick-install))
|
|
19
19
|
- macOS: `ripgrep`
|
|
20
20
|
- Linux (Debian/Ubuntu): `bubblewrap`, `socat`, `ripgrep`
|
|
21
21
|
|
|
@@ -8,7 +8,7 @@ import { captureAgentChatArtifacts } from "./chat.js";
|
|
|
8
8
|
import { AgentRuntimeError, AgentRuntimeProcessError, AgentRuntimeSandboxError, } from "./errors.js";
|
|
9
9
|
import { configureSandboxSettings, runAgentProcess } from "./launcher.js";
|
|
10
10
|
import { writeAgentManifest } from "./manifest.js";
|
|
11
|
-
import { registerStagedAuthContext, teardownRegisteredAuthContext, } from "./registry.js";
|
|
11
|
+
import { registerSessionProcess, registerStagedAuthContext, teardownRegisteredAuthContext, unregisterSessionProcess, } from "./registry.js";
|
|
12
12
|
import { DEFAULT_DENIAL_BACKOFF } from "./sandbox.js";
|
|
13
13
|
export async function runSandboxedAgent(input) {
|
|
14
14
|
const { root, agent, prompt, environment, paths, sandboxStageId = "run", sessionId, sandboxProviderId, sandboxPolicyOverrides, extraWriteProtectedPaths, extraReadProtectedPaths, captureChat = true, teardownAuthOnExit = true, onWatchdogTrigger, } = input;
|
|
@@ -25,6 +25,7 @@ export async function runSandboxedAgent(input) {
|
|
|
25
25
|
prompt,
|
|
26
26
|
});
|
|
27
27
|
let authContext;
|
|
28
|
+
let spawnedProcess;
|
|
28
29
|
try {
|
|
29
30
|
const staged = await stageAgentAuth({
|
|
30
31
|
agent,
|
|
@@ -71,6 +72,10 @@ export async function runSandboxedAgent(input) {
|
|
|
71
72
|
providerId,
|
|
72
73
|
denialBackoff,
|
|
73
74
|
onWatchdogTrigger,
|
|
75
|
+
onSpawnedProcess: (child) => {
|
|
76
|
+
spawnedProcess = child;
|
|
77
|
+
registerSessionProcess(sessionId, child);
|
|
78
|
+
},
|
|
74
79
|
});
|
|
75
80
|
const chat = captureChat
|
|
76
81
|
? await captureAgentChatArtifacts({
|
|
@@ -96,6 +101,10 @@ export async function runSandboxedAgent(input) {
|
|
|
96
101
|
throw new AgentRuntimeProcessError(error instanceof Error ? error.message : toErrorMessage(error));
|
|
97
102
|
}
|
|
98
103
|
finally {
|
|
104
|
+
if (spawnedProcess &&
|
|
105
|
+
(spawnedProcess.exitCode !== null || spawnedProcess.signalCode !== null)) {
|
|
106
|
+
unregisterSessionProcess(sessionId, spawnedProcess);
|
|
107
|
+
}
|
|
99
108
|
await rm(promptPath, { force: true }).catch(() => { });
|
|
100
109
|
if (teardownAuthOnExit || !sessionId) {
|
|
101
110
|
await teardownRegisteredAuthContext(sessionId ?? "runtime", authContext).catch(() => { });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
1
2
|
import type { DenialBackoffConfig } from "../../configs/sandbox/types.js";
|
|
2
3
|
import type { WatchdogMetadata } from "../../domain/run/model/types.js";
|
|
3
4
|
import type { SandboxStageId } from "./policy.js";
|
|
@@ -16,6 +17,7 @@ export interface AgentProcessOptions {
|
|
|
16
17
|
providerId?: string;
|
|
17
18
|
/** Callback fired immediately when watchdog triggers, before process exits. */
|
|
18
19
|
onWatchdogTrigger?: (trigger: WatchdogTrigger, reason: string, failFast?: SandboxFailFastInfo) => void;
|
|
20
|
+
onSpawnedProcess?: (child: ChildProcess) => void;
|
|
19
21
|
}
|
|
20
22
|
export interface AgentProcessResult {
|
|
21
23
|
exitCode: number;
|
|
@@ -50,7 +50,7 @@ async function defaultResolveRunInvocation(context) {
|
|
|
50
50
|
return { command, args };
|
|
51
51
|
}
|
|
52
52
|
export async function runAgentProcess(options) {
|
|
53
|
-
const { runtimeManifestPath, agentRoot, stdoutPath, stderrPath, sandboxSettingsPath, denialBackoff, resolveRunInvocation, providerId = "", onWatchdogTrigger, } = options;
|
|
53
|
+
const { runtimeManifestPath, agentRoot, stdoutPath, stderrPath, sandboxSettingsPath, denialBackoff, resolveRunInvocation, providerId = "", onWatchdogTrigger, onSpawnedProcess, } = options;
|
|
54
54
|
const stdoutStream = createWriteStream(stdoutPath, { flags: "w" });
|
|
55
55
|
const stderrStream = createWriteStream(stderrPath, { flags: "w" });
|
|
56
56
|
const shimEntryPath = resolveShimEntryPath();
|
|
@@ -87,6 +87,7 @@ export async function runAgentProcess(options) {
|
|
|
87
87
|
stderr: { writable: stderrStream },
|
|
88
88
|
detached: true,
|
|
89
89
|
onSpawn: (child) => {
|
|
90
|
+
onSpawnedProcess?.(child);
|
|
90
91
|
watchdogController = createWatchdog(child, stderrStream, {
|
|
91
92
|
providerId,
|
|
92
93
|
onWatchdogTrigger,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export declare const SANDBOX_STAGE_IDS: readonly ["spec", "run", "reduce", "verify", "message"];
|
|
2
|
+
export type SandboxStageId = (typeof SANDBOX_STAGE_IDS)[number];
|
|
3
|
+
export type SandboxGitAccessLevel = "deny-read" | "deny-read-write";
|
|
4
|
+
export type SandboxReadRoot = "repo-root" | "workspace-root";
|
|
5
|
+
export type SandboxWriteRoot = "workspace-root";
|
|
6
|
+
export type OperatorAccessProtectedOperatorDirKey = "spec-dir" | "run-dir" | "reduce-dir" | "verify-dir" | "message-dir";
|
|
7
|
+
export type OperatorAccessProtectedMetadataPathKey = "run-index" | "run-history-lock";
|
|
8
|
+
export type ReadonlyWorkspaceMountKey = "context" | "inputs" | "reference_repo";
|
|
9
|
+
export interface OperatorAccessProfile {
|
|
10
|
+
readonly readRoot: SandboxReadRoot;
|
|
11
|
+
readonly writeRoot: SandboxWriteRoot;
|
|
12
|
+
readonly gitAccess: SandboxGitAccessLevel;
|
|
13
|
+
readonly protectedOperatorDirs: readonly OperatorAccessProtectedOperatorDirKey[];
|
|
14
|
+
readonly protectedMetadataPaths: readonly OperatorAccessProtectedMetadataPathKey[];
|
|
15
|
+
readonly restrictRepositoryReads: boolean;
|
|
16
|
+
readonly readonlyWorkspaceMounts: readonly ReadonlyWorkspaceMountKey[];
|
|
17
|
+
}
|
|
18
|
+
export declare const OPERATOR_ACCESS_PROFILES: {
|
|
19
|
+
readonly spec: {
|
|
20
|
+
readonly readRoot: "repo-root";
|
|
21
|
+
readonly writeRoot: "workspace-root";
|
|
22
|
+
readonly gitAccess: "deny-read";
|
|
23
|
+
readonly protectedOperatorDirs: readonly ["run-dir", "reduce-dir", "verify-dir", "message-dir"];
|
|
24
|
+
readonly protectedMetadataPaths: readonly [];
|
|
25
|
+
readonly restrictRepositoryReads: false;
|
|
26
|
+
readonly readonlyWorkspaceMounts: readonly [];
|
|
27
|
+
};
|
|
28
|
+
readonly run: {
|
|
29
|
+
readonly readRoot: "workspace-root";
|
|
30
|
+
readonly writeRoot: "workspace-root";
|
|
31
|
+
readonly gitAccess: "deny-read-write";
|
|
32
|
+
readonly protectedOperatorDirs: readonly ["spec-dir", "reduce-dir", "verify-dir", "message-dir"];
|
|
33
|
+
readonly protectedMetadataPaths: readonly ["run-index", "run-history-lock"];
|
|
34
|
+
readonly restrictRepositoryReads: true;
|
|
35
|
+
readonly readonlyWorkspaceMounts: readonly [];
|
|
36
|
+
};
|
|
37
|
+
readonly reduce: {
|
|
38
|
+
readonly readRoot: "workspace-root";
|
|
39
|
+
readonly writeRoot: "workspace-root";
|
|
40
|
+
readonly gitAccess: "deny-read";
|
|
41
|
+
readonly protectedOperatorDirs: readonly ["spec-dir", "run-dir", "verify-dir", "message-dir"];
|
|
42
|
+
readonly protectedMetadataPaths: readonly [];
|
|
43
|
+
readonly restrictRepositoryReads: true;
|
|
44
|
+
readonly readonlyWorkspaceMounts: readonly [];
|
|
45
|
+
};
|
|
46
|
+
readonly verify: {
|
|
47
|
+
readonly readRoot: "workspace-root";
|
|
48
|
+
readonly writeRoot: "workspace-root";
|
|
49
|
+
readonly gitAccess: "deny-read";
|
|
50
|
+
readonly protectedOperatorDirs: readonly ["spec-dir", "run-dir", "reduce-dir", "message-dir"];
|
|
51
|
+
readonly protectedMetadataPaths: readonly [];
|
|
52
|
+
readonly restrictRepositoryReads: true;
|
|
53
|
+
readonly readonlyWorkspaceMounts: readonly ["context", "inputs", "reference_repo"];
|
|
54
|
+
};
|
|
55
|
+
readonly message: {
|
|
56
|
+
readonly readRoot: "repo-root";
|
|
57
|
+
readonly writeRoot: "workspace-root";
|
|
58
|
+
readonly gitAccess: "deny-read";
|
|
59
|
+
readonly protectedOperatorDirs: readonly ["spec-dir", "run-dir", "reduce-dir", "verify-dir"];
|
|
60
|
+
readonly protectedMetadataPaths: readonly [];
|
|
61
|
+
readonly restrictRepositoryReads: false;
|
|
62
|
+
readonly readonlyWorkspaceMounts: readonly [];
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
export declare function getOperatorAccessProfile(stageId: SandboxStageId): OperatorAccessProfile;
|
|
66
|
+
export declare function resolveProtectedOperatorDir(root: string, key: OperatorAccessProtectedOperatorDirKey): string;
|
|
67
|
+
export declare function resolveProtectedMetadataPath(root: string, key: OperatorAccessProtectedMetadataPathKey): string;
|
|
68
|
+
export declare function resolveRepositoryGitPath(root: string): string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { resolve as resolveAbsolute } from "node:path";
|
|
2
|
+
import { VORATIQ_HISTORY_LOCK_FILENAME, VORATIQ_MESSAGE_DIR, VORATIQ_REDUCTION_DIR, VORATIQ_RUN_DIR, VORATIQ_RUN_FILE, VORATIQ_SPEC_DIR, VORATIQ_VERIFICATION_DIR, } from "../../workspace/constants.js";
|
|
3
|
+
import { resolveWorkspacePath } from "../../workspace/path-resolvers.js";
|
|
4
|
+
export const SANDBOX_STAGE_IDS = [
|
|
5
|
+
"spec",
|
|
6
|
+
"run",
|
|
7
|
+
"reduce",
|
|
8
|
+
"verify",
|
|
9
|
+
"message",
|
|
10
|
+
];
|
|
11
|
+
export const OPERATOR_ACCESS_PROFILES = {
|
|
12
|
+
spec: {
|
|
13
|
+
readRoot: "repo-root",
|
|
14
|
+
writeRoot: "workspace-root",
|
|
15
|
+
gitAccess: "deny-read",
|
|
16
|
+
protectedOperatorDirs: [
|
|
17
|
+
"run-dir",
|
|
18
|
+
"reduce-dir",
|
|
19
|
+
"verify-dir",
|
|
20
|
+
"message-dir",
|
|
21
|
+
],
|
|
22
|
+
protectedMetadataPaths: [],
|
|
23
|
+
restrictRepositoryReads: false,
|
|
24
|
+
readonlyWorkspaceMounts: [],
|
|
25
|
+
},
|
|
26
|
+
run: {
|
|
27
|
+
readRoot: "workspace-root",
|
|
28
|
+
writeRoot: "workspace-root",
|
|
29
|
+
gitAccess: "deny-read-write",
|
|
30
|
+
protectedOperatorDirs: [
|
|
31
|
+
"spec-dir",
|
|
32
|
+
"reduce-dir",
|
|
33
|
+
"verify-dir",
|
|
34
|
+
"message-dir",
|
|
35
|
+
],
|
|
36
|
+
protectedMetadataPaths: ["run-index", "run-history-lock"],
|
|
37
|
+
restrictRepositoryReads: true,
|
|
38
|
+
readonlyWorkspaceMounts: [],
|
|
39
|
+
},
|
|
40
|
+
reduce: {
|
|
41
|
+
readRoot: "workspace-root",
|
|
42
|
+
writeRoot: "workspace-root",
|
|
43
|
+
gitAccess: "deny-read",
|
|
44
|
+
protectedOperatorDirs: ["spec-dir", "run-dir", "verify-dir", "message-dir"],
|
|
45
|
+
protectedMetadataPaths: [],
|
|
46
|
+
restrictRepositoryReads: true,
|
|
47
|
+
readonlyWorkspaceMounts: [],
|
|
48
|
+
},
|
|
49
|
+
verify: {
|
|
50
|
+
readRoot: "workspace-root",
|
|
51
|
+
writeRoot: "workspace-root",
|
|
52
|
+
gitAccess: "deny-read",
|
|
53
|
+
protectedOperatorDirs: ["spec-dir", "run-dir", "reduce-dir", "message-dir"],
|
|
54
|
+
protectedMetadataPaths: [],
|
|
55
|
+
restrictRepositoryReads: true,
|
|
56
|
+
readonlyWorkspaceMounts: ["context", "inputs", "reference_repo"],
|
|
57
|
+
},
|
|
58
|
+
message: {
|
|
59
|
+
readRoot: "repo-root",
|
|
60
|
+
writeRoot: "workspace-root",
|
|
61
|
+
gitAccess: "deny-read",
|
|
62
|
+
protectedOperatorDirs: ["spec-dir", "run-dir", "reduce-dir", "verify-dir"],
|
|
63
|
+
protectedMetadataPaths: [],
|
|
64
|
+
restrictRepositoryReads: false,
|
|
65
|
+
readonlyWorkspaceMounts: [],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
export function getOperatorAccessProfile(stageId) {
|
|
69
|
+
return OPERATOR_ACCESS_PROFILES[stageId];
|
|
70
|
+
}
|
|
71
|
+
export function resolveProtectedOperatorDir(root, key) {
|
|
72
|
+
switch (key) {
|
|
73
|
+
case "spec-dir":
|
|
74
|
+
return resolveWorkspacePath(root, VORATIQ_SPEC_DIR);
|
|
75
|
+
case "run-dir":
|
|
76
|
+
return resolveWorkspacePath(root, VORATIQ_RUN_DIR);
|
|
77
|
+
case "reduce-dir":
|
|
78
|
+
return resolveWorkspacePath(root, VORATIQ_REDUCTION_DIR);
|
|
79
|
+
case "verify-dir":
|
|
80
|
+
return resolveWorkspacePath(root, VORATIQ_VERIFICATION_DIR);
|
|
81
|
+
case "message-dir":
|
|
82
|
+
return resolveWorkspacePath(root, VORATIQ_MESSAGE_DIR);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function resolveProtectedMetadataPath(root, key) {
|
|
86
|
+
switch (key) {
|
|
87
|
+
case "run-index":
|
|
88
|
+
return resolveWorkspacePath(root, VORATIQ_RUN_FILE);
|
|
89
|
+
case "run-history-lock":
|
|
90
|
+
return resolveWorkspacePath(root, VORATIQ_RUN_DIR, VORATIQ_HISTORY_LOCK_FILENAME);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function resolveRepositoryGitPath(root) {
|
|
94
|
+
return resolveAbsolute(root, ".git");
|
|
95
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SandboxFilesystemConfig, SandboxNetworkConfig } from "../../configs/sandbox/types.js";
|
|
2
|
+
import { type SandboxStageId } from "./operator-access.js";
|
|
2
3
|
import type { SandboxPolicyOverrides } from "./types.js";
|
|
3
|
-
export type SandboxStageId
|
|
4
|
+
export type { SandboxStageId } from "./operator-access.js";
|
|
4
5
|
export interface BuildSandboxPolicyInput {
|
|
5
6
|
stageId: SandboxStageId;
|
|
6
7
|
root: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isAbsolute, relative, resolve as resolveAbsolute } from "node:path";
|
|
2
|
-
import { VORATIQ_AGENTS_FILE, VORATIQ_ENVIRONMENT_FILE,
|
|
2
|
+
import { VORATIQ_AGENTS_FILE, VORATIQ_ENVIRONMENT_FILE, VORATIQ_ORCHESTRATION_FILE, VORATIQ_SANDBOX_FILE, } from "../../workspace/constants.js";
|
|
3
3
|
import { resolveWorkspacePath } from "../../workspace/path-resolvers.js";
|
|
4
|
+
import { getOperatorAccessProfile, resolveProtectedMetadataPath, resolveProtectedOperatorDir, resolveRepositoryGitPath, } from "./operator-access.js";
|
|
4
5
|
export function buildSandboxPolicy(input) {
|
|
5
6
|
const { stageId, root, workspacePath, sandboxHomePath, sandboxSettingsPath, runtimePath, artifactsPath, repoRootPath, providerFilesystem, providerNetwork, policyOverrides, stageDenyWritePaths = [], stageDenyReadPaths = [], } = input;
|
|
6
7
|
const baseline = buildBaselineFilesystemPolicy({
|
|
@@ -116,55 +117,27 @@ function buildBaselineFilesystemPolicy(options) {
|
|
|
116
117
|
resolveWorkspacePath(root, VORATIQ_ORCHESTRATION_FILE),
|
|
117
118
|
resolveWorkspacePath(root, VORATIQ_SANDBOX_FILE),
|
|
118
119
|
];
|
|
119
|
-
const
|
|
120
|
+
const stagePaths = resolveStageProtectedPaths(stageId, root);
|
|
120
121
|
// Default deny rules stay symmetric; read-only divergences are explicit.
|
|
121
|
-
const symmetricDeny = [...commonSensitivePaths, ...
|
|
122
|
+
const symmetricDeny = [...commonSensitivePaths, ...stagePaths.symmetric];
|
|
122
123
|
return {
|
|
123
124
|
allowWrite: [],
|
|
124
|
-
denyRead: [...symmetricDeny, ...
|
|
125
|
+
denyRead: [...symmetricDeny, ...stagePaths.readOnly],
|
|
125
126
|
denyWrite: [...symmetricDeny],
|
|
126
127
|
};
|
|
127
128
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
],
|
|
137
|
-
readOnly: [],
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
if (stageId === "spec") {
|
|
141
|
-
return {
|
|
142
|
-
symmetric: [
|
|
143
|
-
resolveWorkspacePath(root, VORATIQ_RUN_DIR),
|
|
144
|
-
resolveWorkspacePath(root, VORATIQ_VERIFICATION_DIR),
|
|
145
|
-
resolveWorkspacePath(root, VORATIQ_REDUCTION_DIR),
|
|
146
|
-
],
|
|
147
|
-
readOnly: [],
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
if (stageId === "verify") {
|
|
151
|
-
return {
|
|
152
|
-
symmetric: [
|
|
153
|
-
resolveWorkspacePath(root, VORATIQ_RUN_DIR),
|
|
154
|
-
resolveWorkspacePath(root, VORATIQ_SPEC_DIR),
|
|
155
|
-
resolveWorkspacePath(root, VORATIQ_REDUCTION_DIR),
|
|
156
|
-
],
|
|
157
|
-
// Verification agents should never inspect repository metadata during
|
|
158
|
-
// blinded review.
|
|
159
|
-
readOnly: [resolveAbsolute(root, ".git")],
|
|
160
|
-
};
|
|
129
|
+
function resolveStageProtectedPaths(stageId, root) {
|
|
130
|
+
const profile = getOperatorAccessProfile(stageId);
|
|
131
|
+
const symmetric = [
|
|
132
|
+
...profile.protectedOperatorDirs.map((key) => resolveProtectedOperatorDir(root, key)),
|
|
133
|
+
...profile.protectedMetadataPaths.map((key) => resolveProtectedMetadataPath(root, key)),
|
|
134
|
+
];
|
|
135
|
+
if (profile.gitAccess === "deny-read-write") {
|
|
136
|
+
symmetric.unshift(resolveRepositoryGitPath(root));
|
|
161
137
|
}
|
|
162
138
|
return {
|
|
163
|
-
symmetric
|
|
164
|
-
|
|
165
|
-
resolveWorkspacePath(root, VORATIQ_SPEC_DIR),
|
|
166
|
-
],
|
|
167
|
-
readOnly: [resolveAbsolute(root, ".git")],
|
|
139
|
+
symmetric,
|
|
140
|
+
readOnly: profile.gitAccess === "deny-read" ? [resolveRepositoryGitPath(root)] : [],
|
|
168
141
|
};
|
|
169
142
|
}
|
|
170
143
|
function resolveFilesystemOverrides(overrides, workspacePath) {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
1
2
|
import type { StagedAuthContext } from "./auth.js";
|
|
2
3
|
export declare function registerStagedAuthContext(sessionId: string, context: StagedAuthContext): void;
|
|
3
4
|
export declare function teardownRegisteredAuthContext(sessionId: string, context: StagedAuthContext | undefined): Promise<void>;
|
|
4
5
|
export declare function teardownSessionAuth(sessionId: string | undefined): Promise<void>;
|
|
6
|
+
export declare function registerSessionProcess(sessionId: string | undefined, child: ChildProcess | undefined): void;
|
|
7
|
+
export declare function unregisterSessionProcess(sessionId: string | undefined, child: ChildProcess | undefined): void;
|
|
8
|
+
export declare function terminateSessionProcesses(sessionId: string | undefined): Promise<void>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { teardownAuthContext } from "./auth.js";
|
|
2
2
|
const registry = new Map();
|
|
3
|
+
const processRegistry = new Map();
|
|
4
|
+
const PROCESS_EXIT_WAIT_MS = 2_000;
|
|
3
5
|
export function registerStagedAuthContext(sessionId, context) {
|
|
4
6
|
const existing = registry.get(sessionId);
|
|
5
7
|
if (existing) {
|
|
@@ -42,6 +44,48 @@ export async function teardownSessionAuth(sessionId) {
|
|
|
42
44
|
throw new AggregateError(failures, `Failed to teardown ${failures.length} auth contexts for session ${sessionId}`);
|
|
43
45
|
}
|
|
44
46
|
}
|
|
47
|
+
export function registerSessionProcess(sessionId, child) {
|
|
48
|
+
if (!sessionId || !child || child.pid === undefined) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const existing = processRegistry.get(sessionId);
|
|
52
|
+
if (existing) {
|
|
53
|
+
existing.add(child);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
processRegistry.set(sessionId, new Set([child]));
|
|
57
|
+
}
|
|
58
|
+
export function unregisterSessionProcess(sessionId, child) {
|
|
59
|
+
if (!sessionId || !child) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
removeProcess(sessionId, child);
|
|
63
|
+
}
|
|
64
|
+
export async function terminateSessionProcesses(sessionId) {
|
|
65
|
+
if (!sessionId) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const children = processRegistry.get(sessionId);
|
|
69
|
+
if (!children || children.size === 0) {
|
|
70
|
+
processRegistry.delete(sessionId);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const failures = [];
|
|
74
|
+
await Promise.all([...children].map(async (child) => {
|
|
75
|
+
try {
|
|
76
|
+
await terminateRegisteredProcess(sessionId, child);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
failures.push(error instanceof Error ? error : new Error(String(error)));
|
|
80
|
+
}
|
|
81
|
+
}));
|
|
82
|
+
if (failures.length === 1) {
|
|
83
|
+
throw failures[0];
|
|
84
|
+
}
|
|
85
|
+
if (failures.length > 1) {
|
|
86
|
+
throw new AggregateError(failures, `Failed to terminate ${failures.length} session processes for ${sessionId}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
45
89
|
function removeContext(sessionId, context) {
|
|
46
90
|
const contexts = registry.get(sessionId);
|
|
47
91
|
if (!contexts) {
|
|
@@ -52,3 +96,91 @@ function removeContext(sessionId, context) {
|
|
|
52
96
|
registry.delete(sessionId);
|
|
53
97
|
}
|
|
54
98
|
}
|
|
99
|
+
function removeProcess(sessionId, child) {
|
|
100
|
+
const children = processRegistry.get(sessionId);
|
|
101
|
+
if (!children) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
children.delete(child);
|
|
105
|
+
if (children.size === 0) {
|
|
106
|
+
processRegistry.delete(sessionId);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function terminateRegisteredProcess(sessionId, child) {
|
|
110
|
+
if (hasExited(child) || child.pid === undefined) {
|
|
111
|
+
removeProcess(sessionId, child);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (tryTerminateProcessGroup(child.pid, "SIGTERM")) {
|
|
115
|
+
const exitedOnTerm = await waitForProcessExit(child, PROCESS_EXIT_WAIT_MS);
|
|
116
|
+
if (exitedOnTerm) {
|
|
117
|
+
removeProcess(sessionId, child);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
removeProcess(sessionId, child);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (tryTerminateProcessGroup(child.pid, "SIGKILL")) {
|
|
126
|
+
const exitedOnKill = await waitForProcessExit(child, PROCESS_EXIT_WAIT_MS);
|
|
127
|
+
if (exitedOnKill) {
|
|
128
|
+
removeProcess(sessionId, child);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
removeProcess(sessionId, child);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`Detached agent process ${child.pid} did not exit after SIGTERM and SIGKILL.`);
|
|
137
|
+
}
|
|
138
|
+
function hasExited(child) {
|
|
139
|
+
return child.exitCode !== null || child.signalCode !== null;
|
|
140
|
+
}
|
|
141
|
+
function tryTerminateProcessGroup(pid, signal) {
|
|
142
|
+
try {
|
|
143
|
+
process.kill(-pid, signal);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
try {
|
|
148
|
+
process.kill(pid, signal);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function waitForProcessExit(child, timeoutMs) {
|
|
157
|
+
if (hasExited(child)) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
if (typeof child.once !== "function") {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
return await new Promise((resolve) => {
|
|
164
|
+
let settled = false;
|
|
165
|
+
const finish = (exited) => {
|
|
166
|
+
if (settled) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
settled = true;
|
|
170
|
+
if (typeof child.removeListener === "function") {
|
|
171
|
+
child.removeListener("exit", handleExit);
|
|
172
|
+
child.removeListener("close", handleExit);
|
|
173
|
+
}
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
resolve(exited);
|
|
176
|
+
};
|
|
177
|
+
const handleExit = () => {
|
|
178
|
+
finish(true);
|
|
179
|
+
};
|
|
180
|
+
const timer = setTimeout(() => {
|
|
181
|
+
finish(hasExited(child));
|
|
182
|
+
}, timeoutMs);
|
|
183
|
+
child.once("exit", handleExit);
|
|
184
|
+
child.once("close", handleExit);
|
|
185
|
+
});
|
|
186
|
+
}
|
package/dist/bin.js
CHANGED
|
@@ -108,73 +108,9 @@ async function flushPendingHistory() {
|
|
|
108
108
|
console.warn(`[voratiq] Failed to flush interactive history buffers: ${error.message}`);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
async function terminateActiveRunSafe(status, context) {
|
|
112
|
-
try {
|
|
113
|
-
const { terminateActiveRun } = await import("./commands/run/lifecycle.js");
|
|
114
|
-
await terminateActiveRun(status);
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
const { toErrorMessage } = await import("./utils/errors.js");
|
|
119
|
-
const normalizedError = error instanceof Error ? error : new Error(toErrorMessage(error));
|
|
120
|
-
console.error(`[voratiq] Failed to teardown run after ${context}: ${toErrorMessage(error)}`);
|
|
121
|
-
return normalizedError;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
async function terminateActiveVerificationSafe(status, context) {
|
|
125
|
-
try {
|
|
126
|
-
const { terminateActiveVerification } = await import("./commands/verify/lifecycle.js");
|
|
127
|
-
await terminateActiveVerification(status);
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
const { toErrorMessage } = await import("./utils/errors.js");
|
|
132
|
-
const normalizedError = error instanceof Error ? error : new Error(toErrorMessage(error));
|
|
133
|
-
console.error(`[voratiq] Failed to teardown verification after ${context}: ${toErrorMessage(error)}`);
|
|
134
|
-
return normalizedError;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
async function terminateActiveInteractiveSafe(status, context) {
|
|
138
|
-
try {
|
|
139
|
-
const { terminateActiveInteractive } = await import("./commands/interactive/lifecycle.js");
|
|
140
|
-
await terminateActiveInteractive(status, context);
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
const { toErrorMessage } = await import("./utils/errors.js");
|
|
145
|
-
const normalizedError = error instanceof Error ? error : new Error(toErrorMessage(error));
|
|
146
|
-
console.error(`[voratiq] Failed to teardown interactive session after ${context}: ${toErrorMessage(error)}`);
|
|
147
|
-
return normalizedError;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
async function terminateActiveMessageSafe(status, context) {
|
|
151
|
-
try {
|
|
152
|
-
const { terminateActiveMessage } = await import("./commands/message/lifecycle.js");
|
|
153
|
-
await terminateActiveMessage(status);
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
const { toErrorMessage } = await import("./utils/errors.js");
|
|
158
|
-
const normalizedError = error instanceof Error ? error : new Error(toErrorMessage(error));
|
|
159
|
-
console.error(`[voratiq] Failed to teardown message after ${context}: ${toErrorMessage(error)}`);
|
|
160
|
-
return normalizedError;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
111
|
async function terminateActiveSessionsSafe(status, context) {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
const interactiveError = await terminateActiveInteractiveSafe(status, context);
|
|
167
|
-
const messageError = await terminateActiveMessageSafe(status, context);
|
|
168
|
-
const errors = [
|
|
169
|
-
runError,
|
|
170
|
-
verificationError,
|
|
171
|
-
interactiveError,
|
|
172
|
-
messageError,
|
|
173
|
-
].filter((error) => error instanceof Error);
|
|
174
|
-
if (errors.length > 1) {
|
|
175
|
-
return new AggregateError(errors, `Failed to teardown active sessions after ${context}`);
|
|
176
|
-
}
|
|
177
|
-
return errors[0] ?? null;
|
|
112
|
+
const { terminateRegisteredActiveSessions } = await import("./commands/shared/teardown-registry.js");
|
|
113
|
+
return await terminateRegisteredActiveSessions(status, context);
|
|
178
114
|
}
|
|
179
115
|
function renderRootLauncherGitGuidance(options) {
|
|
180
116
|
const lines = [];
|
|
@@ -359,7 +295,6 @@ async function registerCommands(program, argv) {
|
|
|
359
295
|
"auto",
|
|
360
296
|
"apply",
|
|
361
297
|
"list",
|
|
362
|
-
"prune",
|
|
363
298
|
"doctor",
|
|
364
299
|
"mcp",
|
|
365
300
|
]);
|
|
@@ -378,7 +313,6 @@ async function registerCommands(program, argv) {
|
|
|
378
313
|
program.addCommand((await import("./cli/auto.js")).createAutoCommand());
|
|
379
314
|
program.addCommand((await import("./cli/apply.js")).createApplyCommand());
|
|
380
315
|
program.addCommand((await import("./cli/list.js")).createListCommand());
|
|
381
|
-
program.addCommand((await import("./cli/prune.js")).createPruneCommand());
|
|
382
316
|
program.addCommand((await import("./cli/doctor.js")).createDoctorCommand());
|
|
383
317
|
program.addCommand((await import("./cli/mcp.js")).createMcpCommand());
|
|
384
318
|
return;
|
|
@@ -408,9 +342,6 @@ async function registerCommands(program, argv) {
|
|
|
408
342
|
case "list":
|
|
409
343
|
program.addCommand((await import("./cli/list.js")).createListCommand());
|
|
410
344
|
break;
|
|
411
|
-
case "prune":
|
|
412
|
-
program.addCommand((await import("./cli/prune.js")).createPruneCommand());
|
|
413
|
-
break;
|
|
414
345
|
case "doctor":
|
|
415
346
|
program.addCommand((await import("./cli/doctor.js")).createDoctorCommand());
|
|
416
347
|
break;
|