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.
Files changed (110) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/runtime/harness.js +10 -1
  3. package/dist/agents/runtime/launcher.d.ts +2 -0
  4. package/dist/agents/runtime/launcher.js +2 -1
  5. package/dist/agents/runtime/operator-access.d.ts +68 -0
  6. package/dist/agents/runtime/operator-access.js +95 -0
  7. package/dist/agents/runtime/policy.d.ts +2 -1
  8. package/dist/agents/runtime/policy.js +15 -42
  9. package/dist/agents/runtime/registry.d.ts +4 -0
  10. package/dist/agents/runtime/registry.js +132 -0
  11. package/dist/bin.js +2 -71
  12. package/dist/cli/contract.d.ts +2 -32
  13. package/dist/cli/contract.js +0 -54
  14. package/dist/cli/operator-envelope.d.ts +0 -7
  15. package/dist/cli/operator-envelope.js +0 -22
  16. package/dist/commands/apply/command.js +7 -1
  17. package/dist/commands/fetch.d.ts +0 -1
  18. package/dist/commands/fetch.js +1 -5
  19. package/dist/commands/interactive/lifecycle.js +14 -0
  20. package/dist/commands/list/command.js +1 -4
  21. package/dist/commands/list/normalization.js +3 -0
  22. package/dist/commands/message/command.js +36 -29
  23. package/dist/commands/message/lifecycle.d.ts +2 -0
  24. package/dist/commands/message/lifecycle.js +141 -36
  25. package/dist/commands/reduce/command.js +115 -62
  26. package/dist/commands/reduce/lifecycle.d.ts +16 -0
  27. package/dist/commands/reduce/lifecycle.js +219 -0
  28. package/dist/commands/reduce/targets.js +7 -10
  29. package/dist/commands/run/command.js +115 -25
  30. package/dist/commands/run/lifecycle.d.ts +4 -1
  31. package/dist/commands/run/lifecycle.js +183 -84
  32. package/dist/commands/run/record-init.js +0 -1
  33. package/dist/commands/shared/teardown-registry.d.ts +12 -0
  34. package/dist/commands/shared/teardown-registry.js +35 -0
  35. package/dist/commands/spec/command.js +132 -115
  36. package/dist/commands/spec/lifecycle.d.ts +16 -0
  37. package/dist/commands/spec/lifecycle.js +205 -0
  38. package/dist/commands/verify/command.js +36 -29
  39. package/dist/commands/verify/lifecycle.d.ts +2 -0
  40. package/dist/commands/verify/lifecycle.js +166 -44
  41. package/dist/commands/verify/targets.js +3 -6
  42. package/dist/competition/shared/prompt-helpers.d.ts +7 -3
  43. package/dist/competition/shared/prompt-helpers.js +41 -8
  44. package/dist/competition/shared/sandbox-policy.d.ts +13 -3
  45. package/dist/competition/shared/sandbox-policy.js +215 -4
  46. package/dist/competition/shared/teardown.d.ts +12 -0
  47. package/dist/competition/shared/teardown.js +60 -4
  48. package/dist/domain/interactive/prompt.d.ts +1 -1
  49. package/dist/domain/interactive/prompt.js +1 -1
  50. package/dist/domain/message/competition/adapter.js +11 -1
  51. package/dist/domain/message/competition/prompt.js +3 -2
  52. package/dist/domain/message/model/mutators.js +11 -6
  53. package/dist/domain/reduce/competition/adapter.d.ts +2 -0
  54. package/dist/domain/reduce/competition/adapter.js +16 -8
  55. package/dist/domain/reduce/competition/prompt.d.ts +1 -1
  56. package/dist/domain/reduce/competition/prompt.js +4 -3
  57. package/dist/domain/run/competition/agent-preparation.js +18 -10
  58. package/dist/domain/run/competition/agents/artifacts.js +27 -5
  59. package/dist/domain/run/competition/agents/lifecycle.js +12 -2
  60. package/dist/domain/run/competition/agents/preparation.js +2 -0
  61. package/dist/domain/run/competition/agents/types.d.ts +1 -0
  62. package/dist/domain/run/competition/prompt.d.ts +1 -0
  63. package/dist/domain/run/competition/prompt.js +4 -3
  64. package/dist/domain/run/model/enhanced.d.ts +0 -1
  65. package/dist/domain/run/model/enhanced.js +0 -3
  66. package/dist/domain/run/model/mutators.js +11 -9
  67. package/dist/domain/run/model/types.d.ts +8 -10
  68. package/dist/domain/run/model/types.js +5 -4
  69. package/dist/domain/run/persistence/adapter.d.ts +0 -1
  70. package/dist/domain/run/persistence/adapter.js +2 -2
  71. package/dist/domain/spec/competition/adapter.d.ts +2 -0
  72. package/dist/domain/spec/competition/adapter.js +20 -11
  73. package/dist/domain/spec/competition/prompt.js +3 -2
  74. package/dist/domain/spec/model/mutators.js +12 -7
  75. package/dist/domain/verify/competition/adapter.js +20 -16
  76. package/dist/domain/verify/competition/blinding.d.ts +4 -0
  77. package/dist/domain/verify/competition/blinding.js +7 -0
  78. package/dist/domain/verify/competition/prompt.js +21 -2
  79. package/dist/mcp/server.d.ts +2 -2
  80. package/dist/mcp/server.js +3 -38
  81. package/dist/render/transcripts/run.d.ts +3 -2
  82. package/dist/render/transcripts/stage-progress.d.ts +1 -1
  83. package/dist/render/utils/transcript-shell.js +3 -3
  84. package/dist/status/colors.js +0 -1
  85. package/dist/status/index.d.ts +1 -2
  86. package/dist/status/index.js +0 -1
  87. package/dist/utils/git.d.ts +6 -0
  88. package/dist/utils/git.js +15 -0
  89. package/dist/utils/slug.d.ts +0 -1
  90. package/dist/utils/slug.js +0 -4
  91. package/dist/workspace/agents.js +1 -0
  92. package/dist/workspace/cleanup.d.ts +8 -0
  93. package/dist/workspace/cleanup.js +28 -0
  94. package/dist/workspace/dependencies.d.ts +11 -0
  95. package/dist/workspace/dependencies.js +150 -13
  96. package/package.json +2 -1
  97. package/dist/cli/prune.d.ts +0 -18
  98. package/dist/cli/prune.js +0 -99
  99. package/dist/commands/prune/command.d.ts +0 -3
  100. package/dist/commands/prune/command.js +0 -391
  101. package/dist/commands/prune/errors.d.ts +0 -17
  102. package/dist/commands/prune/errors.js +0 -33
  103. package/dist/commands/prune/types.d.ts +0 -63
  104. package/dist/commands/prune/types.js +0 -1
  105. package/dist/competition/shared/prune.d.ts +0 -1
  106. package/dist/competition/shared/prune.js +0 -4
  107. package/dist/render/transcripts/prune.d.ts +0 -21
  108. package/dist/render/transcripts/prune.js +0 -97
  109. package/dist/workspace/prune.d.ts +0 -8
  110. 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.107.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))
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 = "spec" | "run" | "reduce" | "verify" | "message";
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, VORATIQ_HISTORY_LOCK_FILENAME, VORATIQ_ORCHESTRATION_FILE, VORATIQ_REDUCTION_DIR, VORATIQ_RUN_DIR, VORATIQ_RUN_FILE, VORATIQ_SANDBOX_FILE, VORATIQ_SPEC_DIR, VORATIQ_VERIFICATION_DIR, } from "../../workspace/constants.js";
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 stageRoots = resolveStageRoots(stageId, root);
120
+ const stagePaths = resolveStageProtectedPaths(stageId, root);
120
121
  // Default deny rules stay symmetric; read-only divergences are explicit.
121
- const symmetricDeny = [...commonSensitivePaths, ...stageRoots.symmetric];
122
+ const symmetricDeny = [...commonSensitivePaths, ...stagePaths.symmetric];
122
123
  return {
123
124
  allowWrite: [],
124
- denyRead: [...symmetricDeny, ...stageRoots.readOnly],
125
+ denyRead: [...symmetricDeny, ...stagePaths.readOnly],
125
126
  denyWrite: [...symmetricDeny],
126
127
  };
127
128
  }
128
- function resolveStageRoots(stageId, root) {
129
- if (stageId === "run") {
130
- return {
131
- symmetric: [
132
- resolveAbsolute(root, ".git"),
133
- resolveWorkspacePath(root, VORATIQ_RUN_FILE),
134
- resolveWorkspacePath(root, VORATIQ_RUN_DIR, VORATIQ_HISTORY_LOCK_FILENAME),
135
- resolveWorkspacePath(root, VORATIQ_VERIFICATION_DIR),
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
- resolveWorkspacePath(root, VORATIQ_RUN_DIR),
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 runError = await terminateActiveRunSafe(status, context);
165
- const verificationError = await terminateActiveVerificationSafe(status, context);
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;