@fitlab-ai/agent-infra 0.9.5 → 0.9.7
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/dist/bin/internal-cli.js +8 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +171 -20
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +68 -8
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -4
- package/dist/lib/internal/codex-lifecycle.js +169 -3
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +108 -11
- package/dist/lib/internal/sandbox-control.js +43 -6
- package/dist/lib/internal/task-create.js +12 -4
- package/dist/lib/internal/task-orchestration.js +40 -7
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/issue-comments.js +2 -1
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +111 -60
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +372 -94
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/client.js +138 -22
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +142 -0
- package/dist/lib/sandbox/control/lifecycle.js +557 -0
- package/dist/lib/sandbox/control/protocol.js +69 -26
- package/dist/lib/sandbox/control/server.js +395 -99
- package/dist/lib/sandbox/control/state.js +191 -0
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +22 -19
- package/dist/lib/sandbox/recovery.js +284 -50
- package/dist/lib/sandbox/workspace-view.js +90 -10
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +17 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +87 -15
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +236 -0
- package/dist/lib/task/commands/status.js +3 -0
- package/dist/lib/task/commands/validate.js +289 -0
- package/dist/lib/task/commit-finalization.js +36 -9
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +109 -2
- package/dist/lib/task/events.js +149 -6
- package/dist/lib/task/finalization-artifacts.js +2 -1
- package/dist/lib/task/implementation-inputs.js +1 -7
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +365 -15
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +78 -14
- package/dist/lib/task/verification.js +1 -0
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +202 -18
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +83 -6
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -3
- package/lib/internal/codex-lifecycle.ts +175 -4
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +107 -11
- package/lib/internal/sandbox-control.ts +38 -6
- package/lib/internal/task-create.ts +16 -4
- package/lib/internal/task-orchestration.ts +34 -7
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/issue-comments.ts +2 -1
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +70 -25
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +423 -108
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/client.ts +152 -28
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +167 -0
- package/lib/sandbox/control/lifecycle.ts +573 -0
- package/lib/sandbox/control/protocol.ts +121 -68
- package/lib/sandbox/control/server.ts +398 -109
- package/lib/sandbox/control/state.ts +213 -0
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +16 -8
- package/lib/sandbox/recovery.ts +335 -65
- package/lib/sandbox/workspace-view.ts +102 -11
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +24 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +78 -14
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +319 -0
- package/lib/task/commands/status.ts +2 -0
- package/lib/task/commands/validate.ts +282 -0
- package/lib/task/commit-finalization.ts +52 -9
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +208 -7
- package/lib/task/events.ts +131 -5
- package/lib/task/finalization-artifacts.ts +2 -1
- package/lib/task/implementation-inputs.ts +1 -5
- package/lib/task/index.ts +8 -0
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +552 -18
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -24
- package/lib/task/verification.ts +2 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +72 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +25 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +69 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +69 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.claude/commands/run-manual-validation.en.md +9 -0
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
- package/templates/.codex/hooks.json +8 -8
- package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { buildProcessTreeStopCommand } from '../../server/process-control.js';
|
|
5
|
+
import { processIdentityMatches } from '../../server/process-state.js';
|
|
6
|
+
import { DEFAULT_SANDBOX_CONTROL_TIMING } from './protocol.js';
|
|
7
|
+
import { inspectSandboxControlContainer } from './container-identity.js';
|
|
8
|
+
import { parseSandboxControlStatus, readExecution, terminateSandboxControlExecution } from './state.js';
|
|
9
|
+
const DEFAULT_QUIESCE_TIMEOUT_MS = DEFAULT_SANDBOX_CONTROL_TIMING.quiesceDeadlineMs;
|
|
10
|
+
const QUIESCING_FILE = 'quiescing.json';
|
|
11
|
+
const BROKER_STARTING_FILE = 'broker-starting.json';
|
|
12
|
+
async function awaitWithDeadline(operation, deadlineAt, errorCode) {
|
|
13
|
+
const remaining = deadlineAt - Date.now();
|
|
14
|
+
if (remaining <= 0)
|
|
15
|
+
throw new Error(errorCode);
|
|
16
|
+
let timer;
|
|
17
|
+
try {
|
|
18
|
+
return await Promise.race([
|
|
19
|
+
operation(),
|
|
20
|
+
new Promise((_, reject) => {
|
|
21
|
+
timer = setTimeout(() => reject(new Error(errorCode)), remaining);
|
|
22
|
+
})
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
if (timer)
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function forceDeadline(deadlineAt, timeoutMs) {
|
|
31
|
+
return Math.max(Date.now(), deadlineAt - Math.max(1, Math.floor(timeoutMs / 2)));
|
|
32
|
+
}
|
|
33
|
+
function regularFile(filePath) {
|
|
34
|
+
try {
|
|
35
|
+
const stat = fs.lstatSync(filePath);
|
|
36
|
+
return stat.isFile() && !stat.isSymbolicLink();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code === 'ENOENT')
|
|
40
|
+
return false;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function isSandboxControlRootQuiescing(root) {
|
|
45
|
+
return fs.existsSync(path.join(path.resolve(root), QUIESCING_FILE));
|
|
46
|
+
}
|
|
47
|
+
function markSandboxControlRootQuiescing(root) {
|
|
48
|
+
const markerPath = path.join(root, QUIESCING_FILE);
|
|
49
|
+
try {
|
|
50
|
+
fs.writeFileSync(markerPath, `${JSON.stringify({ version: 1, startedAt: Date.now() })}\n`, {
|
|
51
|
+
mode: 0o600,
|
|
52
|
+
flag: 'wx'
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error.code !== 'EEXIST')
|
|
57
|
+
throw error;
|
|
58
|
+
if (!regularFile(markerPath))
|
|
59
|
+
throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function readStartupOwner(filePath) {
|
|
63
|
+
if (!fs.existsSync(filePath))
|
|
64
|
+
return null;
|
|
65
|
+
if (!regularFile(filePath))
|
|
66
|
+
throw new Error('SANDBOX_CONTROL_BROKER_START_TRANSITION');
|
|
67
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
68
|
+
try {
|
|
69
|
+
const record = JSON.parse(raw);
|
|
70
|
+
if (record.version !== 2 || !Number.isSafeInteger(record.pid) || (record.pid ?? 0) <= 0
|
|
71
|
+
|| typeof record.startTime !== 'number' || !Number.isSafeInteger(record.startTime))
|
|
72
|
+
return null;
|
|
73
|
+
return { raw, owner: { pid: record.pid, startTime: record.startTime } };
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function waitForStartupTransition(root, timeoutMs) {
|
|
80
|
+
const transitionPath = path.join(root, BROKER_STARTING_FILE);
|
|
81
|
+
const deadline = Date.now() + timeoutMs;
|
|
82
|
+
while (Date.now() < deadline) {
|
|
83
|
+
const transition = readStartupOwner(transitionPath);
|
|
84
|
+
if (!fs.existsSync(transitionPath))
|
|
85
|
+
return;
|
|
86
|
+
if (transition && !ownerLive(transition.owner)) {
|
|
87
|
+
try {
|
|
88
|
+
if (fs.readFileSync(transitionPath, 'utf8') === transition.raw)
|
|
89
|
+
fs.unlinkSync(transitionPath);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// A concurrent startup transition changed or removed the record.
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
97
|
+
}
|
|
98
|
+
throw new Error('SANDBOX_CONTROL_BROKER_START_TRANSITION');
|
|
99
|
+
}
|
|
100
|
+
export async function acquireSandboxControlBrokerStartup(root, owner, timeoutMs = 5_000) {
|
|
101
|
+
const resolvedRoot = path.resolve(root);
|
|
102
|
+
const transitionPath = path.join(resolvedRoot, BROKER_STARTING_FILE);
|
|
103
|
+
const record = `${JSON.stringify({ version: 2, ...owner })}\n`;
|
|
104
|
+
const deadline = Date.now() + timeoutMs;
|
|
105
|
+
while (Date.now() < deadline) {
|
|
106
|
+
if (isSandboxControlRootQuiescing(resolvedRoot))
|
|
107
|
+
throw new Error('SANDBOX_CONTROL_QUIESCING');
|
|
108
|
+
try {
|
|
109
|
+
fs.writeFileSync(transitionPath, record, { mode: 0o600, flag: 'wx' });
|
|
110
|
+
if (isSandboxControlRootQuiescing(resolvedRoot)) {
|
|
111
|
+
try {
|
|
112
|
+
if (fs.readFileSync(transitionPath, 'utf8') === record)
|
|
113
|
+
fs.unlinkSync(transitionPath);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// The deletion side owns the transition now.
|
|
117
|
+
}
|
|
118
|
+
throw new Error('SANDBOX_CONTROL_QUIESCING');
|
|
119
|
+
}
|
|
120
|
+
return () => {
|
|
121
|
+
try {
|
|
122
|
+
if (fs.readFileSync(transitionPath, 'utf8') === record)
|
|
123
|
+
fs.unlinkSync(transitionPath);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// A newer transition or deletion owns the path.
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error.code !== 'EEXIST')
|
|
132
|
+
throw error;
|
|
133
|
+
const transition = readStartupOwner(transitionPath);
|
|
134
|
+
if (transition && !ownerLive(transition.owner)) {
|
|
135
|
+
try {
|
|
136
|
+
if (fs.readFileSync(transitionPath, 'utf8') === transition.raw)
|
|
137
|
+
fs.unlinkSync(transitionPath);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// A concurrent transition changed the record.
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
throw new Error('SANDBOX_CONTROL_BROKER_START_TRANSITION');
|
|
148
|
+
}
|
|
149
|
+
export function readSandboxControlManifest(manifestPath) {
|
|
150
|
+
if (!regularFile(manifestPath))
|
|
151
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
152
|
+
let manifest;
|
|
153
|
+
try {
|
|
154
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
158
|
+
}
|
|
159
|
+
if (manifest.version !== 4) {
|
|
160
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_VERSION_INVALID: expected version 4; container-only recreation is required');
|
|
161
|
+
}
|
|
162
|
+
if (typeof manifest.engine !== 'string' || manifest.engine.length === 0
|
|
163
|
+
|| typeof manifest.repoRoot !== 'string' || typeof manifest.worktreeRoot !== 'string'
|
|
164
|
+
|| typeof manifest.project !== 'string' || typeof manifest.container !== 'string'
|
|
165
|
+
|| !manifest.containerIdentity || typeof manifest.containerIdentity.id !== 'string'
|
|
166
|
+
|| manifest.containerIdentity.id.length === 0 || !manifest.containerIdentity.labels
|
|
167
|
+
|| typeof manifest.containerIdentity.labels !== 'object'
|
|
168
|
+
|| Array.isArray(manifest.containerIdentity.labels)
|
|
169
|
+
|| Object.values(manifest.containerIdentity.labels).some((value) => typeof value !== 'string')
|
|
170
|
+
|| typeof manifest.branch !== 'string' || !['task-bound', 'branch-only'].includes(manifest.mode ?? '')
|
|
171
|
+
|| (manifest.taskId !== null && typeof manifest.taskId !== 'string')
|
|
172
|
+
|| typeof manifest.channelDir !== 'string' || typeof manifest.publicStatusDir !== 'string'
|
|
173
|
+
|| typeof manifest.processingDir !== 'string' || typeof manifest.token !== 'string'
|
|
174
|
+
|| typeof manifest.generation !== 'string')
|
|
175
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
176
|
+
const root = path.dirname(path.resolve(manifestPath));
|
|
177
|
+
if (path.resolve(manifest.channelDir) !== path.join(root, 'channel')
|
|
178
|
+
|| path.resolve(manifest.publicStatusDir) !== path.join(root, 'public')
|
|
179
|
+
|| path.resolve(manifest.processingDir) !== path.join(root, 'processing')) {
|
|
180
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
181
|
+
}
|
|
182
|
+
return manifest;
|
|
183
|
+
}
|
|
184
|
+
function readBrokerOwner(filePath) {
|
|
185
|
+
if (!fs.existsSync(filePath))
|
|
186
|
+
return null;
|
|
187
|
+
if (!regularFile(filePath))
|
|
188
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
189
|
+
let owner;
|
|
190
|
+
try {
|
|
191
|
+
owner = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
195
|
+
}
|
|
196
|
+
if (!owner || owner.version !== 3 || !Number.isSafeInteger(owner.pid) || (owner.pid ?? 0) <= 0
|
|
197
|
+
|| typeof owner.startTime !== 'number' || !Number.isSafeInteger(owner.startTime)
|
|
198
|
+
|| typeof owner.brokerId !== 'string' || owner.brokerId.length === 0
|
|
199
|
+
|| typeof owner.token !== 'string' || typeof owner.generation !== 'string')
|
|
200
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
201
|
+
return owner;
|
|
202
|
+
}
|
|
203
|
+
function readStatusOwner(filePath) {
|
|
204
|
+
if (!fs.existsSync(filePath))
|
|
205
|
+
return null;
|
|
206
|
+
if (!regularFile(filePath))
|
|
207
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
208
|
+
try {
|
|
209
|
+
return parseSandboxControlStatus(JSON.parse(fs.readFileSync(filePath, 'utf8')));
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function sameOwner(left, right) {
|
|
216
|
+
return left.pid === right.pid && left.startTime === right.startTime
|
|
217
|
+
&& (!left.brokerId || !right.brokerId || left.brokerId === right.brokerId);
|
|
218
|
+
}
|
|
219
|
+
function ownerLive(owner) {
|
|
220
|
+
return processIdentityMatches({ pid: owner.pid, startTime: owner.startTime });
|
|
221
|
+
}
|
|
222
|
+
function readExecutions(manifest) {
|
|
223
|
+
if (!fs.existsSync(manifest.processingDir))
|
|
224
|
+
return [];
|
|
225
|
+
const stat = fs.lstatSync(manifest.processingDir);
|
|
226
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
227
|
+
throw new Error('SANDBOX_CONTROL_EXECUTION_STILL_RUNNING');
|
|
228
|
+
const executions = [];
|
|
229
|
+
for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
|
|
230
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
231
|
+
continue;
|
|
232
|
+
const descriptor = path.join(manifest.processingDir, entry.name, 'execution.json');
|
|
233
|
+
if (!fs.existsSync(descriptor))
|
|
234
|
+
continue;
|
|
235
|
+
let execution;
|
|
236
|
+
try {
|
|
237
|
+
execution = readExecution(descriptor);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
241
|
+
}
|
|
242
|
+
if (execution.generation !== manifest.generation || execution.requestId !== entry.name) {
|
|
243
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
244
|
+
}
|
|
245
|
+
if (execution.child.pid <= 0 || (execution.child.processGroupId !== null
|
|
246
|
+
&& (!Number.isSafeInteger(execution.child.processGroupId) || execution.child.processGroupId <= 0))) {
|
|
247
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
248
|
+
}
|
|
249
|
+
executions.push(execution);
|
|
250
|
+
}
|
|
251
|
+
return executions;
|
|
252
|
+
}
|
|
253
|
+
function processGroupAlive(groupId) {
|
|
254
|
+
try {
|
|
255
|
+
const rows = execFileSync('ps', ['-axo', 'pgid=,stat='], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
256
|
+
return rows.split('\n').some((row) => {
|
|
257
|
+
const match = row.trim().match(/^(\d+)\s+(\S+)/);
|
|
258
|
+
return Number(match?.[1]) === groupId && !match?.[2]?.startsWith('Z');
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
try {
|
|
263
|
+
process.kill(-groupId, 0);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
return error.code === 'EPERM';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function executionAlive(execution, platform) {
|
|
272
|
+
if (platform !== 'win32' && execution.child.processGroupId) {
|
|
273
|
+
return processGroupAlive(execution.child.processGroupId);
|
|
274
|
+
}
|
|
275
|
+
return processIdentityMatches({ pid: execution.child.pid, startTime: execution.child.startTime });
|
|
276
|
+
}
|
|
277
|
+
async function waitForExit(owner, timeoutMs, deadlineAt) {
|
|
278
|
+
const deadline = deadlineAt ?? (Date.now() + timeoutMs);
|
|
279
|
+
while (Date.now() < deadline) {
|
|
280
|
+
if (!ownerLive(owner))
|
|
281
|
+
return true;
|
|
282
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
283
|
+
}
|
|
284
|
+
return !ownerLive(owner);
|
|
285
|
+
}
|
|
286
|
+
function signalOwner(owner, platform, force) {
|
|
287
|
+
try {
|
|
288
|
+
if (platform === 'win32') {
|
|
289
|
+
const command = buildProcessTreeStopCommand(owner.pid, platform);
|
|
290
|
+
if (command.kind === 'exec')
|
|
291
|
+
execFileSync(command.command, command.args, { stdio: 'ignore' });
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
process.kill(owner.pid, force ? 'SIGKILL' : 'SIGTERM');
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// The identity checks decide whether the owner actually exited.
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function mergeExecutions(...groups) {
|
|
301
|
+
const merged = new Map();
|
|
302
|
+
for (const execution of groups.flat())
|
|
303
|
+
merged.set(`${execution.requestId}:${execution.nonce}`, execution);
|
|
304
|
+
return [...merged.values()];
|
|
305
|
+
}
|
|
306
|
+
export async function quiesceSandboxControlRoot(root, options = {}) {
|
|
307
|
+
const resolvedRoot = path.resolve(root);
|
|
308
|
+
if (!fs.existsSync(resolvedRoot))
|
|
309
|
+
return 'missing';
|
|
310
|
+
const rootStat = fs.lstatSync(resolvedRoot);
|
|
311
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink())
|
|
312
|
+
throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
313
|
+
for (const directory of [path.join(resolvedRoot, 'public'), path.join(resolvedRoot, 'processing')]) {
|
|
314
|
+
if (!fs.existsSync(directory))
|
|
315
|
+
continue;
|
|
316
|
+
const stat = fs.lstatSync(directory);
|
|
317
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
318
|
+
throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
319
|
+
}
|
|
320
|
+
const timeoutMs = options.timeoutMs ?? options.timing?.quiesceDeadlineMs ?? DEFAULT_QUIESCE_TIMEOUT_MS;
|
|
321
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
322
|
+
const forceAt = forceDeadline(deadlineAt, timeoutMs);
|
|
323
|
+
markSandboxControlRootQuiescing(resolvedRoot);
|
|
324
|
+
await waitForStartupTransition(resolvedRoot, Math.max(0, forceAt - Date.now()));
|
|
325
|
+
const manifestPath = path.join(resolvedRoot, 'manifest.json');
|
|
326
|
+
const manifest = fs.existsSync(manifestPath) ? readSandboxControlManifest(manifestPath) : null;
|
|
327
|
+
const broker = readBrokerOwner(path.join(resolvedRoot, 'broker.json'));
|
|
328
|
+
const status = readStatusOwner(path.join(resolvedRoot, 'public', 'status.json'));
|
|
329
|
+
const statusOwner = status ? status.broker : null;
|
|
330
|
+
const brokerLive = broker ? ownerLive(broker) : false;
|
|
331
|
+
const statusLive = statusOwner ? ownerLive(statusOwner) : false;
|
|
332
|
+
if (manifest && broker && (broker.token !== manifest.token || broker.generation !== manifest.generation)) {
|
|
333
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
334
|
+
}
|
|
335
|
+
if (manifest && status && status.generation !== manifest.generation) {
|
|
336
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
337
|
+
}
|
|
338
|
+
if (broker && statusOwner && !sameOwner(broker, statusOwner) && (brokerLive || statusLive)) {
|
|
339
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
340
|
+
}
|
|
341
|
+
if (!manifest && brokerLive)
|
|
342
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
343
|
+
const platform = options.platform ?? process.platform;
|
|
344
|
+
let executions = manifest ? readExecutions(manifest) : [];
|
|
345
|
+
const owner = brokerLive ? broker : statusLive ? statusOwner : null;
|
|
346
|
+
if (!owner) {
|
|
347
|
+
if (manifest && !broker && !status)
|
|
348
|
+
throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_MISSING');
|
|
349
|
+
for (const execution of executions) {
|
|
350
|
+
terminateSandboxControlExecution(execution, { platform, timeoutMs: 0, allowForce: false });
|
|
351
|
+
}
|
|
352
|
+
const softDeadlineAt = Math.min(forceAt, Date.now() + Math.floor(Math.max(0, forceAt - Date.now()) / 2));
|
|
353
|
+
while (Date.now() < softDeadlineAt) {
|
|
354
|
+
if (!executions.some((execution) => executionAlive(execution, platform)))
|
|
355
|
+
break;
|
|
356
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
357
|
+
}
|
|
358
|
+
if (manifest)
|
|
359
|
+
executions = mergeExecutions(executions, readExecutions(manifest));
|
|
360
|
+
if (Date.now() >= deadlineAt)
|
|
361
|
+
throw new Error('SANDBOX_CONTROL_QUIESCE_DEADLINE_EXCEEDED');
|
|
362
|
+
for (const execution of executions) {
|
|
363
|
+
if (!terminateSandboxControlExecution(execution, {
|
|
364
|
+
platform, timeoutMs: Math.max(0, deadlineAt - Date.now()), deadlineAt, forceAt: Date.now()
|
|
365
|
+
})) {
|
|
366
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${execution.requestId}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (executions.some((execution) => executionAlive(execution, platform))) {
|
|
370
|
+
throw new Error('SANDBOX_CONTROL_EXECUTION_STILL_RUNNING');
|
|
371
|
+
}
|
|
372
|
+
return broker || status ? 'stale' : 'missing';
|
|
373
|
+
}
|
|
374
|
+
const remaining = () => Math.max(0, deadlineAt - Date.now());
|
|
375
|
+
signalOwner(owner, platform, false);
|
|
376
|
+
for (const execution of executions) {
|
|
377
|
+
terminateSandboxControlExecution(execution, { platform, timeoutMs: 0, allowForce: false });
|
|
378
|
+
}
|
|
379
|
+
const softDeadlineAt = Math.min(forceAt, Date.now() + Math.floor(Math.max(0, forceAt - Date.now()) / 2));
|
|
380
|
+
while (Date.now() < softDeadlineAt) {
|
|
381
|
+
if (!ownerLive(owner) && !executions.some((execution) => executionAlive(execution, platform)))
|
|
382
|
+
break;
|
|
383
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
384
|
+
}
|
|
385
|
+
if (!ownerLive(owner) && !executions.some((execution) => executionAlive(execution, platform))) {
|
|
386
|
+
return 'stopped';
|
|
387
|
+
}
|
|
388
|
+
if (manifest)
|
|
389
|
+
executions = mergeExecutions(executions, readExecutions(manifest));
|
|
390
|
+
if (Date.now() >= deadlineAt)
|
|
391
|
+
throw new Error('SANDBOX_CONTROL_QUIESCE_DEADLINE_EXCEEDED');
|
|
392
|
+
for (const execution of executions) {
|
|
393
|
+
terminateSandboxControlExecution(execution, { platform, timeoutMs: 0, deadlineAt, forceAt: Date.now() });
|
|
394
|
+
}
|
|
395
|
+
signalOwner(owner, platform, true);
|
|
396
|
+
while (Date.now() < deadlineAt) {
|
|
397
|
+
if (!ownerLive(owner) && !executions.some((execution) => executionAlive(execution, platform)))
|
|
398
|
+
return 'stopped';
|
|
399
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
400
|
+
}
|
|
401
|
+
if (ownerLive(owner))
|
|
402
|
+
throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
|
|
403
|
+
if (executions.some((execution) => executionAlive(execution, platform)))
|
|
404
|
+
throw new Error('SANDBOX_CONTROL_EXECUTION_STILL_RUNNING');
|
|
405
|
+
return 'stopped';
|
|
406
|
+
}
|
|
407
|
+
export async function removeSandboxControlRoot(root, options) {
|
|
408
|
+
const resolvedRoot = path.resolve(root);
|
|
409
|
+
if (!fs.existsSync(resolvedRoot))
|
|
410
|
+
return;
|
|
411
|
+
const stat = fs.lstatSync(resolvedRoot);
|
|
412
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
413
|
+
throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
414
|
+
const manifestPath = path.join(resolvedRoot, 'manifest.json');
|
|
415
|
+
const manifest = readSandboxControlManifest(manifestPath);
|
|
416
|
+
const inspectContainer = options.inspectContainer
|
|
417
|
+
?? ((timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }));
|
|
418
|
+
const timeoutMs = options.timeoutMs ?? options.timing?.quiesceDeadlineMs ?? DEFAULT_QUIESCE_TIMEOUT_MS;
|
|
419
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
420
|
+
const forceAt = forceDeadline(deadlineAt, timeoutMs);
|
|
421
|
+
const remaining = () => Math.max(0, deadlineAt - Date.now());
|
|
422
|
+
const observation = await awaitWithDeadline(() => inspectContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
|
|
423
|
+
if (observation.state === 'unknown')
|
|
424
|
+
throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${observation.reason}`);
|
|
425
|
+
if (options.requireAbsent && observation.state !== 'absent') {
|
|
426
|
+
throw new Error('SANDBOX_CONTROL_CONTAINER_REAPPEARED');
|
|
427
|
+
}
|
|
428
|
+
markSandboxControlRootQuiescing(resolvedRoot);
|
|
429
|
+
await waitForStartupTransition(resolvedRoot, Math.max(0, forceAt - Date.now()));
|
|
430
|
+
const brokerPath = path.join(resolvedRoot, 'broker.json');
|
|
431
|
+
const statusPath = path.join(resolvedRoot, 'public', 'status.json');
|
|
432
|
+
const broker = readBrokerOwner(brokerPath);
|
|
433
|
+
const status = readStatusOwner(statusPath);
|
|
434
|
+
if (broker && (broker.token !== manifest.token || broker.generation !== manifest.generation)) {
|
|
435
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
436
|
+
}
|
|
437
|
+
if (status && status.generation !== manifest.generation) {
|
|
438
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
439
|
+
}
|
|
440
|
+
if (broker && status && !sameOwner(broker, status.broker)
|
|
441
|
+
&& (ownerLive(broker) || ownerLive(status.broker))) {
|
|
442
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
443
|
+
}
|
|
444
|
+
const platform = options.platform ?? process.platform;
|
|
445
|
+
const owner = broker && ownerLive(broker) ? broker
|
|
446
|
+
: status && ownerLive(status.broker) ? status.broker : null;
|
|
447
|
+
const selfOwner = options.selfOwner;
|
|
448
|
+
const selfOwned = Boolean(selfOwner && broker && sameOwner(broker, selfOwner));
|
|
449
|
+
if (selfOwner && (!broker || !selfOwned || broker.token !== manifest.token || broker.generation !== manifest.generation)) {
|
|
450
|
+
throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
|
|
451
|
+
}
|
|
452
|
+
let executions = readExecutions(manifest);
|
|
453
|
+
if (owner && !selfOwned)
|
|
454
|
+
signalOwner(owner, platform, false);
|
|
455
|
+
for (const execution of executions) {
|
|
456
|
+
terminateSandboxControlExecution(execution, { platform, timeoutMs: 0, allowForce: false });
|
|
457
|
+
}
|
|
458
|
+
const softDeadlineAt = Math.min(forceAt, Date.now() + Math.floor(Math.max(0, forceAt - Date.now()) / 2));
|
|
459
|
+
while (Date.now() < softDeadlineAt) {
|
|
460
|
+
if ((!owner || selfOwned || !ownerLive(owner)) && !executions.some((execution) => executionAlive(execution, platform)))
|
|
461
|
+
break;
|
|
462
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
463
|
+
}
|
|
464
|
+
if (observation.state === 'found') {
|
|
465
|
+
if (Date.now() >= deadlineAt)
|
|
466
|
+
throw new Error('SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
|
|
467
|
+
await awaitWithDeadline(() => options.removeContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
|
|
468
|
+
const afterRemoval = await awaitWithDeadline(() => inspectContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
|
|
469
|
+
if (afterRemoval.state !== 'absent') {
|
|
470
|
+
throw new Error(afterRemoval.state === 'unknown'
|
|
471
|
+
? `SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterRemoval.reason}`
|
|
472
|
+
: 'SANDBOX_CONTROL_CONTAINER_STILL_EXISTS');
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const currentManifest = readSandboxControlManifest(manifestPath);
|
|
476
|
+
if (currentManifest.token !== manifest.token || currentManifest.generation !== manifest.generation
|
|
477
|
+
|| !isSandboxControlRootQuiescing(resolvedRoot)) {
|
|
478
|
+
throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
|
|
479
|
+
}
|
|
480
|
+
const currentBroker = readBrokerOwner(brokerPath);
|
|
481
|
+
if (selfOwned && (!currentBroker || !selfOwner || !sameOwner(selfOwner, currentBroker)
|
|
482
|
+
|| currentBroker.token !== manifest.token || currentBroker.generation !== manifest.generation)) {
|
|
483
|
+
throw new Error('SANDBOX_CONTROL_OWNER_REPLACED');
|
|
484
|
+
}
|
|
485
|
+
if (owner && currentBroker && !sameOwner(owner, currentBroker)) {
|
|
486
|
+
throw new Error('SANDBOX_CONTROL_OWNER_REPLACED');
|
|
487
|
+
}
|
|
488
|
+
if (!owner && currentBroker && ownerLive(currentBroker)) {
|
|
489
|
+
throw new Error('SANDBOX_CONTROL_OWNER_REPLACED');
|
|
490
|
+
}
|
|
491
|
+
if (owner && ownerLive(owner) && !currentBroker) {
|
|
492
|
+
throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_MISSING');
|
|
493
|
+
}
|
|
494
|
+
executions = mergeExecutions(executions, readExecutions(manifest));
|
|
495
|
+
if (Date.now() >= deadlineAt)
|
|
496
|
+
throw new Error('SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
|
|
497
|
+
for (const execution of executions) {
|
|
498
|
+
if (!terminateSandboxControlExecution(execution, {
|
|
499
|
+
platform, timeoutMs: remaining(), deadlineAt, forceAt: Date.now()
|
|
500
|
+
})) {
|
|
501
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${execution.requestId}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (owner && currentBroker && ownerLive(currentBroker) && !selfOwned) {
|
|
505
|
+
if (remaining() <= 0)
|
|
506
|
+
throw new Error('SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
|
|
507
|
+
signalOwner(currentBroker, platform, true);
|
|
508
|
+
if (remaining() <= 0 || !await waitForExit(currentBroker, remaining(), deadlineAt)) {
|
|
509
|
+
throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const finalExecutions = mergeExecutions(executions, readExecutions(manifest));
|
|
513
|
+
if (finalExecutions.some((execution) => executionAlive(execution, platform))) {
|
|
514
|
+
throw new Error('SANDBOX_CONTROL_EXECUTION_STILL_RUNNING');
|
|
515
|
+
}
|
|
516
|
+
const finalBrokerRaw = fs.existsSync(brokerPath) ? fs.readFileSync(brokerPath, 'utf8') : null;
|
|
517
|
+
if (finalBrokerRaw !== null) {
|
|
518
|
+
const finalBroker = readBrokerOwner(brokerPath);
|
|
519
|
+
if (!finalBroker)
|
|
520
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
521
|
+
const finalSelfOwner = Boolean(selfOwner && sameOwner(finalBroker, selfOwner)
|
|
522
|
+
&& finalBroker.token === manifest.token && finalBroker.generation === manifest.generation);
|
|
523
|
+
if (ownerLive(finalBroker) && !finalSelfOwner)
|
|
524
|
+
throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
|
|
525
|
+
if (fs.readFileSync(brokerPath, 'utf8') !== finalBrokerRaw) {
|
|
526
|
+
throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
|
|
527
|
+
}
|
|
528
|
+
fs.unlinkSync(brokerPath);
|
|
529
|
+
}
|
|
530
|
+
const finalStatusRaw = fs.existsSync(statusPath) ? fs.readFileSync(statusPath, 'utf8') : null;
|
|
531
|
+
if (finalStatusRaw !== null) {
|
|
532
|
+
const finalStatus = readStatusOwner(statusPath);
|
|
533
|
+
if (!finalStatus)
|
|
534
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
535
|
+
if (ownerLive(finalStatus.broker) && !(selfOwner && sameOwner(finalStatus.broker, selfOwner))) {
|
|
536
|
+
throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
|
|
537
|
+
}
|
|
538
|
+
if (fs.readFileSync(statusPath, 'utf8') !== finalStatusRaw) {
|
|
539
|
+
throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
|
|
540
|
+
}
|
|
541
|
+
fs.unlinkSync(statusPath);
|
|
542
|
+
}
|
|
543
|
+
if (fs.existsSync(brokerPath) || fs.existsSync(statusPath)) {
|
|
544
|
+
throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_REMAINS');
|
|
545
|
+
}
|
|
546
|
+
fs.rmSync(resolvedRoot, { recursive: true, force: true });
|
|
547
|
+
}
|
|
548
|
+
export async function garbageCollectSandboxControlRoot(root, options = {}) {
|
|
549
|
+
await removeSandboxControlRoot(path.resolve(root), {
|
|
550
|
+
...options,
|
|
551
|
+
requireAbsent: true,
|
|
552
|
+
removeContainer: async () => {
|
|
553
|
+
throw new Error('SANDBOX_CONTROL_CONTAINER_REAPPEARED');
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
//# sourceMappingURL=lifecycle.js.map
|