voratiq 0.1.0-beta.23 → 0.1.0-beta.25
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 +52 -77
- package/dist/cli/contract.d.ts +19 -53
- package/dist/cli/contract.js +33 -63
- package/dist/cli/list.d.ts +1 -0
- package/dist/cli/list.js +15 -12
- 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/auto/command.d.ts +1 -1
- package/dist/commands/auto/command.js +39 -8
- 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.d.ts +1 -0
- package/dist/commands/list/command.js +19 -14
- 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 +14 -19
- package/dist/commands/run/command.js +122 -26
- 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/run/spec-path.d.ts +10 -0
- package/dist/commands/run/spec-path.js +20 -0
- 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/configs/agents/defaults.js +17 -0
- package/dist/contracts/list.d.ts +6 -12
- package/dist/contracts/list.js +4 -6
- 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 +19 -9
- 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.d.ts +1 -0
- package/dist/domain/run/competition/agents/artifacts.js +71 -35
- 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/run-context.d.ts +1 -0
- package/dist/domain/run/competition/agents/run-context.js +8 -1
- 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 +12 -11
- package/dist/domain/run/model/types.js +6 -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/domain/verify/model/mutators.js +1 -0
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +97 -61
- package/dist/render/transcripts/auto.d.ts +1 -1
- package/dist/render/transcripts/auto.js +6 -2
- package/dist/render/transcripts/interactive.d.ts +1 -0
- package/dist/render/transcripts/interactive.js +7 -1
- package/dist/render/transcripts/message.d.ts +1 -0
- package/dist/render/transcripts/message.js +5 -2
- package/dist/render/transcripts/reduce.d.ts +1 -0
- package/dist/render/transcripts/reduce.js +5 -2
- package/dist/render/transcripts/run.d.ts +4 -2
- package/dist/render/transcripts/run.js +2 -1
- package/dist/render/transcripts/spec.d.ts +1 -0
- package/dist/render/transcripts/spec.js +5 -2
- package/dist/render/transcripts/stage-progress.d.ts +1 -1
- package/dist/render/transcripts/verify.d.ts +1 -0
- package/dist/render/transcripts/verify.js +5 -2
- 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 +200 -14
- package/dist/workspace/run.d.ts +9 -0
- package/dist/workspace/run.js +28 -2
- 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
|
@@ -8,24 +8,68 @@ const SIGNAL_EXIT_CODES = {
|
|
|
8
8
|
SIGINT: 130,
|
|
9
9
|
SIGTERM: 143,
|
|
10
10
|
};
|
|
11
|
+
const PROCESS_GUARD_SIGNALS = ["SIGHUP", "SIGINT", "SIGTERM"];
|
|
11
12
|
let activeJsonEnvelopeOperator;
|
|
12
13
|
let processGuardsInstalled = false;
|
|
14
|
+
let activeShutdownState;
|
|
13
15
|
function installProcessGuards() {
|
|
14
16
|
if (processGuardsInstalled) {
|
|
15
17
|
return;
|
|
16
18
|
}
|
|
17
19
|
processGuardsInstalled = true;
|
|
18
|
-
for (const signal of
|
|
19
|
-
|
|
20
|
-
void handleSignal(signal);
|
|
21
|
-
});
|
|
20
|
+
for (const signal of PROCESS_GUARD_SIGNALS) {
|
|
21
|
+
installSignalGuard(signal);
|
|
22
22
|
}
|
|
23
23
|
process.on("uncaughtException", (error) => {
|
|
24
|
-
void
|
|
24
|
+
void beginFatalShutdown("uncaught exception", error);
|
|
25
25
|
});
|
|
26
26
|
process.on("unhandledRejection", (reason) => {
|
|
27
|
-
void
|
|
27
|
+
void beginFatalShutdown("unhandled rejection", reason);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function installSignalGuard(signal) {
|
|
31
|
+
const handler = () => {
|
|
32
|
+
// Re-arm the listener before awaiting teardown so repeated interrupts stay
|
|
33
|
+
// inside the coordinated abort path instead of falling through to the
|
|
34
|
+
// process default signal exit.
|
|
35
|
+
process.once(signal, handler);
|
|
36
|
+
void beginSignalShutdown(signal);
|
|
37
|
+
};
|
|
38
|
+
process.once(signal, handler);
|
|
39
|
+
}
|
|
40
|
+
async function beginFatalShutdown(context, error) {
|
|
41
|
+
if (activeShutdownState) {
|
|
42
|
+
if (activeShutdownState.kind === "signal") {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
await activeShutdownState.promise;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const promise = handleFatalError(context, error).finally(() => {
|
|
49
|
+
if (activeShutdownState?.promise === promise) {
|
|
50
|
+
activeShutdownState = undefined;
|
|
51
|
+
}
|
|
28
52
|
});
|
|
53
|
+
activeShutdownState = { kind: "fatal", promise };
|
|
54
|
+
await promise;
|
|
55
|
+
}
|
|
56
|
+
async function beginSignalShutdown(signal) {
|
|
57
|
+
if (activeShutdownState) {
|
|
58
|
+
if (activeShutdownState.kind === "signal") {
|
|
59
|
+
process.exitCode = activeShutdownState.exitCode;
|
|
60
|
+
await activeShutdownState.promise;
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const exitCode = SIGNAL_EXIT_CODES[signal] ?? 1;
|
|
65
|
+
process.exitCode = exitCode;
|
|
66
|
+
const promise = handleSignal(signal).finally(() => {
|
|
67
|
+
if (activeShutdownState?.promise === promise) {
|
|
68
|
+
activeShutdownState = undefined;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
activeShutdownState = { kind: "signal", exitCode, promise };
|
|
72
|
+
await promise;
|
|
29
73
|
}
|
|
30
74
|
async function handleFatalError(context, error) {
|
|
31
75
|
await terminateActiveSessionsSafe("failed", context);
|
|
@@ -108,73 +152,9 @@ async function flushPendingHistory() {
|
|
|
108
152
|
console.warn(`[voratiq] Failed to flush interactive history buffers: ${error.message}`);
|
|
109
153
|
}
|
|
110
154
|
}
|
|
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
155
|
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;
|
|
156
|
+
const { terminateRegisteredActiveSessions } = await import("./commands/shared/teardown-registry.js");
|
|
157
|
+
return await terminateRegisteredActiveSessions(status, context);
|
|
178
158
|
}
|
|
179
159
|
function renderRootLauncherGitGuidance(options) {
|
|
180
160
|
const lines = [];
|
|
@@ -359,7 +339,6 @@ async function registerCommands(program, argv) {
|
|
|
359
339
|
"auto",
|
|
360
340
|
"apply",
|
|
361
341
|
"list",
|
|
362
|
-
"prune",
|
|
363
342
|
"doctor",
|
|
364
343
|
"mcp",
|
|
365
344
|
]);
|
|
@@ -378,7 +357,6 @@ async function registerCommands(program, argv) {
|
|
|
378
357
|
program.addCommand((await import("./cli/auto.js")).createAutoCommand());
|
|
379
358
|
program.addCommand((await import("./cli/apply.js")).createApplyCommand());
|
|
380
359
|
program.addCommand((await import("./cli/list.js")).createListCommand());
|
|
381
|
-
program.addCommand((await import("./cli/prune.js")).createPruneCommand());
|
|
382
360
|
program.addCommand((await import("./cli/doctor.js")).createDoctorCommand());
|
|
383
361
|
program.addCommand((await import("./cli/mcp.js")).createMcpCommand());
|
|
384
362
|
return;
|
|
@@ -408,9 +386,6 @@ async function registerCommands(program, argv) {
|
|
|
408
386
|
case "list":
|
|
409
387
|
program.addCommand((await import("./cli/list.js")).createListCommand());
|
|
410
388
|
break;
|
|
411
|
-
case "prune":
|
|
412
|
-
program.addCommand((await import("./cli/prune.js")).createPruneCommand());
|
|
413
|
-
break;
|
|
414
389
|
case "doctor":
|
|
415
390
|
program.addCommand((await import("./cli/doctor.js")).createDoctorCommand());
|
|
416
391
|
break;
|