@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
|
@@ -2,18 +2,158 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import {
|
|
5
|
+
import { nodeEntryArgs } from './control/executor.js';
|
|
6
|
+
import { containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxRuntimeCapabilityLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel, worktreeDirCandidates } from './constants.js';
|
|
6
7
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
7
8
|
import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
|
|
8
9
|
import { toEnginePath } from './engines/wsl2-paths.js';
|
|
9
10
|
import { sandboxCoreBindMounts } from './mounts.js';
|
|
10
11
|
import { assertSandboxTaskSource, sandboxControlPaths, sandboxWorkspaceViewPaths } from './workspace-view.js';
|
|
11
12
|
import { parseSandboxWorkspaceIdentity, sameSandboxWorkspaceIdentity } from './workspace-identity.js';
|
|
12
|
-
import {
|
|
13
|
+
import { inspectWorktree } from './worktree-safety.js';
|
|
14
|
+
import { commandForEngine, runEngine, runOkEngine, runProbe, runVerboseEngine } from './shell.js';
|
|
13
15
|
import { declaredTmpfsSeedEntries, resolveTools, toolConfigDirCandidates } from './tools.js';
|
|
14
16
|
import { fetchSandboxRows, selectSandboxContainer, startSandboxContainer } from './commands/list-running.js';
|
|
15
|
-
import { processIdentityMatches } from '../server/process-state.js';
|
|
16
|
-
|
|
17
|
+
import { getProcessStartTime, processIdentityMatches, removePidFileIfMatches } from '../server/process-state.js';
|
|
18
|
+
import { acquireSandboxControlBrokerStartup, garbageCollectSandboxControlRoot, isSandboxControlRootQuiescing, readSandboxControlManifest } from './control/lifecycle.js';
|
|
19
|
+
import { appendSandboxControlAudit, readSandboxControlStatus } from './control/state.js';
|
|
20
|
+
import { inspectSandboxControlContainer } from './control/container-identity.js';
|
|
21
|
+
import { SANDBOX_CONTROL_FUTURE_SKEW_MS, SANDBOX_CONTROL_STATUS_STALE_MS } from './control/protocol.js';
|
|
22
|
+
export function worktreeProbeForEngine(engine, probe = runProbe) {
|
|
23
|
+
return (cmd, args, opts = {}) => {
|
|
24
|
+
const engineArgs = [...args];
|
|
25
|
+
if (engine === 'wsl2' && cmd === 'git') {
|
|
26
|
+
for (let index = 0; index < engineArgs.length - 1; index += 1) {
|
|
27
|
+
if (engineArgs[index] === '-C') {
|
|
28
|
+
engineArgs[index + 1] = toEnginePath(engine, engineArgs[index + 1]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const command = commandForEngine(engine, cmd, engineArgs);
|
|
33
|
+
return probe(command.cmd, command.args, opts);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function waitForSandboxControlBrokerStatus(params, timeoutMs = 5_000) {
|
|
37
|
+
const deadline = Date.now() + timeoutMs;
|
|
38
|
+
while (Date.now() < deadline) {
|
|
39
|
+
if (isSandboxControlRootQuiescing(params.root))
|
|
40
|
+
throw new Error('SANDBOX_CONTROL_QUIESCING');
|
|
41
|
+
try {
|
|
42
|
+
const status = readSandboxControlStatus(params.statusDir);
|
|
43
|
+
if (status.generation === params.generation && status.broker.pid === params.pid
|
|
44
|
+
&& (!params.startTime || status.broker.startTime === params.startTime)
|
|
45
|
+
&& Date.now() - status.updatedAt <= SANDBOX_CONTROL_STATUS_STALE_MS
|
|
46
|
+
&& status.updatedAt <= Date.now() + SANDBOX_CONTROL_FUTURE_SKEW_MS)
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// The broker may still be publishing its initial atomic status.
|
|
51
|
+
}
|
|
52
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
53
|
+
}
|
|
54
|
+
throw new Error('SANDBOX_CONTROL_BROKER_START_TIMEOUT');
|
|
55
|
+
}
|
|
56
|
+
export async function startSandboxControlBroker(repoRoot, manifestPath) {
|
|
57
|
+
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
58
|
+
const extension = path.extname(fileURLToPath(import.meta.url));
|
|
59
|
+
const internalCli = path.resolve(directory, '..', '..', 'bin', `internal-cli${extension}`);
|
|
60
|
+
const manifest = readSandboxControlManifest(manifestPath);
|
|
61
|
+
const root = path.dirname(manifestPath);
|
|
62
|
+
if (isSandboxControlRootQuiescing(root))
|
|
63
|
+
throw new Error('SANDBOX_CONTROL_QUIESCING');
|
|
64
|
+
const brokerPath = path.join(root, 'broker.json');
|
|
65
|
+
let brokerSnapshot = null;
|
|
66
|
+
let replacedBrokerRecord = false;
|
|
67
|
+
try {
|
|
68
|
+
brokerSnapshot = fs.readFileSync(brokerPath, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.code !== 'ENOENT')
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
if (brokerSnapshot !== null) {
|
|
75
|
+
let broker = null;
|
|
76
|
+
try {
|
|
77
|
+
broker = JSON.parse(brokerSnapshot);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Malformed owner records are stale and replaced below.
|
|
81
|
+
}
|
|
82
|
+
const owner = broker !== null && broker.version === 3 && typeof broker.pid === 'number'
|
|
83
|
+
&& Number.isSafeInteger(broker.pid) && broker.pid > 0 && typeof broker.startTime === 'number'
|
|
84
|
+
&& Number.isSafeInteger(broker.startTime) && typeof broker.brokerId === 'string'
|
|
85
|
+
&& broker.brokerId.length > 0 && typeof broker.token === 'string'
|
|
86
|
+
&& typeof broker.generation === 'string'
|
|
87
|
+
? broker
|
|
88
|
+
: null;
|
|
89
|
+
if (broker === null || broker.version !== 3 || owner === null) {
|
|
90
|
+
throw new Error('SANDBOX_CONTROL_BROKER_VERSION_INVALID: rebuild the sandbox container before starting a new broker');
|
|
91
|
+
}
|
|
92
|
+
const live = owner !== null && processIdentityMatches({ pid: owner.pid, startTime: owner.startTime });
|
|
93
|
+
if (live
|
|
94
|
+
&& owner !== null
|
|
95
|
+
&& broker !== null
|
|
96
|
+
&& broker.version === 3
|
|
97
|
+
&& broker.token === manifest.token
|
|
98
|
+
&& broker.generation === manifest.generation) {
|
|
99
|
+
await waitForSandboxControlBrokerStatus({
|
|
100
|
+
root,
|
|
101
|
+
statusDir: manifest.publicStatusDir,
|
|
102
|
+
generation: manifest.generation,
|
|
103
|
+
pid: owner.pid,
|
|
104
|
+
startTime: owner.startTime
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const startupStartTime = getProcessStartTime(process.pid);
|
|
110
|
+
if (!startupStartTime)
|
|
111
|
+
throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
112
|
+
const releaseStartup = await acquireSandboxControlBrokerStartup(root, {
|
|
113
|
+
pid: process.pid,
|
|
114
|
+
startTime: startupStartTime
|
|
115
|
+
});
|
|
116
|
+
let child;
|
|
117
|
+
try {
|
|
118
|
+
const currentBroker = fs.existsSync(brokerPath) ? fs.readFileSync(brokerPath, 'utf8') : null;
|
|
119
|
+
if (currentBroker !== brokerSnapshot)
|
|
120
|
+
return startSandboxControlBroker(repoRoot, manifestPath);
|
|
121
|
+
if (brokerSnapshot !== null) {
|
|
122
|
+
replacedBrokerRecord = removePidFileIfMatches(brokerPath, brokerSnapshot);
|
|
123
|
+
if (!replacedBrokerRecord && fs.existsSync(brokerPath)) {
|
|
124
|
+
throw new Error('SANDBOX_CONTROL_BROKER_OWNER_TRANSITION');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (replacedBrokerRecord) {
|
|
128
|
+
appendSandboxControlAudit(manifest, 'broker-observed-crash');
|
|
129
|
+
appendSandboxControlAudit(manifest, 'broker-restart');
|
|
130
|
+
}
|
|
131
|
+
child = spawn(process.execPath, nodeEntryArgs(internalCli, ['sandbox-control', 'serve', '--manifest', manifestPath]), { cwd: repoRoot, detached: true, stdio: 'ignore' });
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
releaseStartup();
|
|
135
|
+
}
|
|
136
|
+
let spawnError = null;
|
|
137
|
+
child.once('error', (error) => { spawnError = error; });
|
|
138
|
+
child.unref();
|
|
139
|
+
if (!child.pid)
|
|
140
|
+
throw new Error('SANDBOX_CONTROL_BROKER_START_FAILED');
|
|
141
|
+
try {
|
|
142
|
+
await waitForSandboxControlBrokerStatus({
|
|
143
|
+
root,
|
|
144
|
+
statusDir: manifest.publicStatusDir,
|
|
145
|
+
generation: manifest.generation,
|
|
146
|
+
pid: child.pid
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
child.kill('SIGTERM');
|
|
151
|
+
if (spawnError)
|
|
152
|
+
throw spawnError;
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function ensureSandboxControlBroker(params) {
|
|
17
157
|
const control = sandboxControlPaths({
|
|
18
158
|
base: params.config.controlBase ?? path.join(params.config.home, '.agent-infra', 'sandbox-control'),
|
|
19
159
|
project: params.config.project,
|
|
@@ -22,31 +162,55 @@ function ensureSandboxControlBroker(params) {
|
|
|
22
162
|
});
|
|
23
163
|
if (!fs.existsSync(control.manifestPath))
|
|
24
164
|
return;
|
|
25
|
-
const
|
|
165
|
+
const validatedManifest = readSandboxControlManifest(control.manifestPath);
|
|
166
|
+
const containerObservation = await inspectSandboxControlContainer(validatedManifest);
|
|
167
|
+
if (containerObservation.state === 'unknown') {
|
|
168
|
+
throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${containerObservation.reason}`);
|
|
169
|
+
}
|
|
170
|
+
if (containerObservation.state === 'absent') {
|
|
171
|
+
await garbageCollectSandboxControlRoot(control.root, {
|
|
172
|
+
inspectContainer: (timeoutMs) => inspectSandboxControlContainer(validatedManifest, { timeoutMs })
|
|
173
|
+
});
|
|
174
|
+
throw new Error('SANDBOX_CONTROL_CONTAINER_ABSENT: recreate the sandbox container before broker recovery');
|
|
175
|
+
}
|
|
26
176
|
const brokerPath = path.join(control.root, 'broker.json');
|
|
27
177
|
try {
|
|
28
178
|
const broker = JSON.parse(fs.readFileSync(brokerPath, 'utf8'));
|
|
29
|
-
if (broker.version ===
|
|
179
|
+
if (broker.version === 3
|
|
30
180
|
&& typeof broker.pid === 'number'
|
|
31
|
-
&&
|
|
32
|
-
&& broker.
|
|
33
|
-
&&
|
|
181
|
+
&& Number.isSafeInteger(broker.pid)
|
|
182
|
+
&& typeof broker.startTime === 'number'
|
|
183
|
+
&& Number.isSafeInteger(broker.startTime)
|
|
184
|
+
&& typeof broker.brokerId === 'string'
|
|
185
|
+
&& broker.brokerId.length > 0
|
|
186
|
+
&& broker.token === validatedManifest.token
|
|
187
|
+
&& broker.generation === validatedManifest.generation
|
|
188
|
+
&& processIdentityMatches({ pid: broker.pid, startTime: broker.startTime })) {
|
|
189
|
+
try {
|
|
190
|
+
const status = readSandboxControlStatus(control.statusDir);
|
|
191
|
+
if (status.generation === validatedManifest.generation && status.broker.pid === broker.pid
|
|
192
|
+
&& status.broker.startTime === broker.startTime
|
|
193
|
+
&& Date.now() - status.updatedAt <= SANDBOX_CONTROL_STATUS_STALE_MS
|
|
194
|
+
&& status.updatedAt <= Date.now() + SANDBOX_CONTROL_FUTURE_SKEW_MS)
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// A verified live owner may still be publishing its initial status.
|
|
199
|
+
}
|
|
200
|
+
await waitForSandboxControlBrokerStatus({
|
|
201
|
+
root: control.root,
|
|
202
|
+
statusDir: control.statusDir,
|
|
203
|
+
generation: validatedManifest.generation,
|
|
204
|
+
pid: broker.pid,
|
|
205
|
+
startTime: broker.startTime
|
|
206
|
+
});
|
|
34
207
|
return;
|
|
208
|
+
}
|
|
35
209
|
}
|
|
36
210
|
catch {
|
|
37
211
|
// A missing, stale, or malformed broker record is replaced below.
|
|
38
212
|
}
|
|
39
|
-
|
|
40
|
-
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
41
|
-
const extension = path.extname(fileURLToPath(import.meta.url));
|
|
42
|
-
const internalCli = path.resolve(directory, '..', '..', 'bin', `internal-cli${extension}`);
|
|
43
|
-
const child = spawn(process.execPath, [internalCli, 'sandbox-control', 'serve', '--manifest', control.manifestPath], { cwd: params.config.repoRoot, detached: true, stdio: 'ignore' });
|
|
44
|
-
child.once('error', () => {
|
|
45
|
-
// Readiness remains fail-closed; a later control request reports the
|
|
46
|
-
// unavailable broker without turning this detached spawn into an
|
|
47
|
-
// unhandled process error.
|
|
48
|
-
});
|
|
49
|
-
child.unref();
|
|
213
|
+
await startSandboxControlBroker(params.config.repoRoot, control.manifestPath);
|
|
50
214
|
}
|
|
51
215
|
function findingKey(finding) {
|
|
52
216
|
return `${finding.repairKind}:${finding.path ?? finding.seed?.targetPath ?? finding.message}`;
|
|
@@ -54,9 +218,22 @@ function findingKey(finding) {
|
|
|
54
218
|
export function classifySandboxRecovery(snapshot) {
|
|
55
219
|
const findings = [];
|
|
56
220
|
if (!snapshot.identityOk) {
|
|
221
|
+
const branchOnly = snapshot.containerIdValid
|
|
222
|
+
&& snapshot.actualBranch === snapshot.expectedBranch
|
|
223
|
+
&& snapshot.expectedWorkspace.mode === 'task-bound'
|
|
224
|
+
&& snapshot.actualWorkspace.mode === 'branch-only';
|
|
225
|
+
const expectedIdentity = snapshot.expectedWorkspace.mode === 'task-bound'
|
|
226
|
+
? `task-bound:${snapshot.expectedWorkspace.taskId}`
|
|
227
|
+
: snapshot.expectedWorkspace.mode;
|
|
228
|
+
const actualIdentity = snapshot.actualWorkspace.mode === 'task-bound'
|
|
229
|
+
? `task-bound:${snapshot.actualWorkspace.taskId}`
|
|
230
|
+
: snapshot.actualWorkspace.mode;
|
|
57
231
|
findings.push({
|
|
58
232
|
repairKind: 'hard-failure',
|
|
59
|
-
|
|
233
|
+
code: branchOnly ? 'SANDBOX_CONTROL_BRANCH_ONLY' : 'SANDBOX_WORKSPACE_IDENTITY_CONFLICT',
|
|
234
|
+
message: branchOnly
|
|
235
|
+
? `Container is branch-only but the requested sandbox is ${expectedIdentity}.`
|
|
236
|
+
: `Container identity ${actualIdentity} on branch ${snapshot.actualBranch ?? 'unknown'} does not match requested ${expectedIdentity} on branch ${snapshot.expectedBranch}.`
|
|
60
237
|
});
|
|
61
238
|
}
|
|
62
239
|
if (snapshot.runtimeCapabilityOk === false) {
|
|
@@ -211,6 +388,7 @@ function expectedMounts(params) {
|
|
|
211
388
|
const core = sandboxCoreBindMounts(config, branch, {
|
|
212
389
|
workspaceViewRoot: view.root,
|
|
213
390
|
controlDir: control.channelDir,
|
|
391
|
+
controlStatusDir: control.statusDir,
|
|
214
392
|
...(params.workspace.mode === 'task-bound'
|
|
215
393
|
? {
|
|
216
394
|
taskSource: assertSandboxTaskSource(config.repoRoot, params.workspace.taskId),
|
|
@@ -379,8 +557,12 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
379
557
|
};
|
|
380
558
|
});
|
|
381
559
|
return {
|
|
382
|
-
|
|
383
|
-
|
|
560
|
+
containerIdValid: typeof inspection.Id === 'string' && inspection.Id.length > 0,
|
|
561
|
+
expectedBranch: params.branch,
|
|
562
|
+
actualBranch: typeof branchLabel === 'string' ? branchLabel : null,
|
|
563
|
+
expectedWorkspace: workspace,
|
|
564
|
+
actualWorkspace: containerWorkspace,
|
|
565
|
+
identityOk: typeof inspection.Id === 'string' && inspection.Id.length > 0
|
|
384
566
|
&& branchLabel === params.branch
|
|
385
567
|
&& sameSandboxWorkspaceIdentity(containerWorkspace, workspace),
|
|
386
568
|
runtimeCapabilityOk: enforceCanonicalPlan
|
|
@@ -487,7 +669,7 @@ function repairFindings(params) {
|
|
|
487
669
|
}
|
|
488
670
|
}
|
|
489
671
|
function describeFindings(findings) {
|
|
490
|
-
return findings.map((finding) => finding.message).join(' ');
|
|
672
|
+
return findings.map((finding) => finding.code ? `${finding.code}: ${finding.message}` : finding.message).join(' ');
|
|
491
673
|
}
|
|
492
674
|
function assess(params) {
|
|
493
675
|
const snapshot = collectSandboxRecoverySnapshot(params);
|
|
@@ -547,12 +729,20 @@ export async function ensureSandboxReady(params) {
|
|
|
547
729
|
const startFn = deps?.start ?? startSandboxContainer;
|
|
548
730
|
const warnings = [];
|
|
549
731
|
let failure = null;
|
|
550
|
-
ensureSandboxControlBroker({
|
|
551
|
-
config: params.config,
|
|
552
|
-
container: params.row.name,
|
|
553
|
-
workspace: params.workspace ?? { mode: 'branch-only' }
|
|
554
|
-
});
|
|
555
732
|
try {
|
|
733
|
+
if (deps?.ensureControlBroker) {
|
|
734
|
+
await deps.ensureControlBroker();
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
await ensureSandboxControlBroker({
|
|
738
|
+
config: params.config,
|
|
739
|
+
container: params.row.name,
|
|
740
|
+
workspace: params.workspace ?? { mode: 'branch-only' }
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
if (params.forceRecreate) {
|
|
744
|
+
throw new Error('Explicit container recreation requested.');
|
|
745
|
+
}
|
|
556
746
|
if (!params.row.running) {
|
|
557
747
|
startFn(params.engine, params.row.name);
|
|
558
748
|
const initial = assess({
|
|
@@ -668,34 +858,78 @@ export async function ensureSandboxReady(params) {
|
|
|
668
858
|
failure = error instanceof Error ? error : new Error(String(error));
|
|
669
859
|
}
|
|
670
860
|
const dataBoundary = 'The existing container writable layer, ordinary /tmp data, processes, tmux sessions, and RAM state may be lost by replacement; worktree and host-managed sandbox data are preserved.';
|
|
861
|
+
const recoveryTarget = params.workspace?.mode === 'task-bound'
|
|
862
|
+
? params.workspace.taskId
|
|
863
|
+
: params.branch;
|
|
671
864
|
if (!params.allowRecreate || !params.recreate) {
|
|
672
|
-
throw new Error(
|
|
865
|
+
throw new Error([
|
|
866
|
+
`Sandbox recovery failed. Run 'ai sandbox start --recreate ${recoveryTarget}' on the host to authorize container-only replacement.`,
|
|
867
|
+
dataBoundary,
|
|
868
|
+
`Details: ${failure.message}`
|
|
869
|
+
].join('\n'));
|
|
870
|
+
}
|
|
871
|
+
const existingWorktrees = worktreeDirCandidates(params.config, params.branch).filter((candidate) => fs.existsSync(candidate));
|
|
872
|
+
if (existingWorktrees.length !== 1) {
|
|
873
|
+
const remediation = existingWorktrees.length === 0
|
|
874
|
+
? `Restore the worktree for '${params.branch}' before recovering the container.`
|
|
875
|
+
: `Found ${existingWorktrees.map((candidate) => JSON.stringify(candidate)).join(', ')}; `
|
|
876
|
+
+ 'keep the worktree in use, remove the stale directory, then retry.';
|
|
877
|
+
throw new Error(`SANDBOX_RECOVERY_WORKTREE_SNAPSHOT_INVALID: expected exactly one existing worktree for '${params.branch}', found ${existingWorktrees.length}. ${remediation}`);
|
|
673
878
|
}
|
|
674
|
-
const
|
|
879
|
+
const worktreeProbe = worktreeProbeForEngine(params.engine, deps?.probe);
|
|
880
|
+
const beforeInspection = inspectWorktree(existingWorktrees[0], { probe: worktreeProbe });
|
|
881
|
+
if (beforeInspection.status === 'failed') {
|
|
882
|
+
throw new Error(`SANDBOX_RECOVERY_WORKTREE_SNAPSHOT_INVALID: ${beforeInspection.message}`);
|
|
883
|
+
}
|
|
884
|
+
const before = beforeInspection.snapshot;
|
|
885
|
+
if (before.branch !== params.branch) {
|
|
886
|
+
throw new Error(`SANDBOX_RECOVERY_WORKTREE_SNAPSHOT_INVALID: expected branch '${params.branch}', found '${before.branch}'. `
|
|
887
|
+
+ `Run 'git -C ${JSON.stringify(before.worktree)} checkout ${params.branch}' to restore the expected branch, then retry.`);
|
|
888
|
+
}
|
|
889
|
+
const warning = params.forceRecreate
|
|
890
|
+
? `Explicit sandbox recreation requested. Replacing only the container. ${dataBoundary}`
|
|
891
|
+
: `Sandbox recovery failed in place. Replacing only the container. ${dataBoundary}`;
|
|
675
892
|
warnings.push(warning);
|
|
676
893
|
(params.writeWarning ?? ((message) => process.stderr.write(`${message}\n`)))(warning);
|
|
677
894
|
const runVerboseFn = deps?.runVerbose ?? runVerboseEngine;
|
|
678
|
-
|
|
679
|
-
|
|
895
|
+
let replacementResult = null;
|
|
896
|
+
let replacementFailure = null;
|
|
897
|
+
try {
|
|
898
|
+
if (params.row.running) {
|
|
899
|
+
runVerboseFn(params.engine, 'docker', ['stop', params.row.name]);
|
|
900
|
+
}
|
|
901
|
+
runVerboseFn(params.engine, 'docker', ['rm', params.row.name]);
|
|
902
|
+
await params.recreate(params.branch);
|
|
903
|
+
const rows = (deps?.fetchRows ?? fetchSandboxRows)(params.engine, sandboxLabel(params.config), sandboxBranchLabel(params.config));
|
|
904
|
+
const replacement = selectSandboxContainer([...rows.running, ...rows.nonRunning], containerNameCandidates(params.config, params.branch));
|
|
905
|
+
if (!replacement?.running) {
|
|
906
|
+
throw new Error('Replacement sandbox container was not found in a running state.');
|
|
907
|
+
}
|
|
908
|
+
const final = assess({
|
|
909
|
+
config: params.config,
|
|
910
|
+
engine: params.engine,
|
|
911
|
+
branch: params.branch,
|
|
912
|
+
workspace: params.workspace,
|
|
913
|
+
container: replacement.name,
|
|
914
|
+
deps
|
|
915
|
+
});
|
|
916
|
+
if (final.findings.length > 0) {
|
|
917
|
+
throw new Error(`Replacement sandbox readiness check failed: ${describeFindings(final.findings)}`);
|
|
918
|
+
}
|
|
919
|
+
replacementResult = { container: replacement.name, path: 'recreated', warnings };
|
|
680
920
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const rows = (deps?.fetchRows ?? fetchSandboxRows)(params.engine, sandboxLabel(params.config), sandboxBranchLabel(params.config));
|
|
684
|
-
const replacement = selectSandboxContainer([...rows.running, ...rows.nonRunning], containerNameCandidates(params.config, params.branch));
|
|
685
|
-
if (!replacement?.running) {
|
|
686
|
-
throw new Error('Replacement sandbox container was not found in a running state.');
|
|
921
|
+
catch (error) {
|
|
922
|
+
replacementFailure = error;
|
|
687
923
|
}
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
deps
|
|
695
|
-
});
|
|
696
|
-
if (final.findings.length > 0) {
|
|
697
|
-
throw new Error(`Replacement sandbox readiness check failed: ${describeFindings(final.findings)}`);
|
|
924
|
+
const afterInspection = inspectWorktree(before.worktree, { probe: worktreeProbe });
|
|
925
|
+
if (afterInspection.status === 'failed' || afterInspection.snapshot.identity !== before.identity) {
|
|
926
|
+
const detail = afterInspection.status === 'failed'
|
|
927
|
+
? afterInspection.message
|
|
928
|
+
: `snapshot changed from ${before.identity} to ${afterInspection.snapshot.identity}`;
|
|
929
|
+
throw new Error(`SANDBOX_RECOVERY_WORKTREE_CHANGED: ${detail}`, replacementFailure === null ? undefined : { cause: replacementFailure });
|
|
698
930
|
}
|
|
699
|
-
|
|
931
|
+
if (replacementFailure !== null)
|
|
932
|
+
throw replacementFailure;
|
|
933
|
+
return replacementResult;
|
|
700
934
|
}
|
|
701
935
|
//# sourceMappingURL=recovery.js.map
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
export const SANDBOX_WORKSPACE_VIEW_STATES = Object.freeze([
|
|
5
|
+
'active',
|
|
6
|
+
'completed',
|
|
7
|
+
'blocked',
|
|
8
|
+
'archive'
|
|
9
|
+
]);
|
|
10
|
+
export function sandboxWorkspaceViewStatePaths(root) {
|
|
11
|
+
return SANDBOX_WORKSPACE_VIEW_STATES.map((state) => ({
|
|
12
|
+
state,
|
|
13
|
+
hostPath: path.join(root, state)
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
4
16
|
export function sandboxWorkspaceViewPaths(params) {
|
|
5
17
|
const identityKey = params.identity.mode === 'task-bound'
|
|
6
18
|
? `task-bound:${params.identity.taskId}`
|
|
@@ -21,7 +33,13 @@ export function sandboxControlPaths(params) {
|
|
|
21
33
|
: 'branch-only';
|
|
22
34
|
const digest = createHash('sha256').update(identityKey).digest('hex').slice(0, 16);
|
|
23
35
|
const root = path.resolve(params.base, params.project, params.container, digest);
|
|
24
|
-
return {
|
|
36
|
+
return {
|
|
37
|
+
root,
|
|
38
|
+
channelDir: path.join(root, 'channel'),
|
|
39
|
+
statusDir: path.join(root, 'public'),
|
|
40
|
+
processingDir: path.join(root, 'processing'),
|
|
41
|
+
manifestPath: path.join(root, 'manifest.json')
|
|
42
|
+
};
|
|
25
43
|
}
|
|
26
44
|
function assertSafeDirectory(directory, expectedBase) {
|
|
27
45
|
const relative = path.relative(expectedBase, directory);
|
|
@@ -47,17 +65,53 @@ export function assertSandboxTaskSource(repoRoot, taskId) {
|
|
|
47
65
|
}
|
|
48
66
|
return canonical;
|
|
49
67
|
}
|
|
68
|
+
export function prepareSandboxWorkspaceMountTarget(worktreeRoot) {
|
|
69
|
+
const worktree = path.resolve(worktreeRoot);
|
|
70
|
+
const canonicalWorktree = fs.realpathSync.native(worktree);
|
|
71
|
+
const agentsRoot = path.join(worktree, '.agents');
|
|
72
|
+
if (fs.existsSync(agentsRoot)) {
|
|
73
|
+
const agentsStat = fs.lstatSync(agentsRoot);
|
|
74
|
+
if (!agentsStat.isDirectory() || agentsStat.isSymbolicLink()) {
|
|
75
|
+
throw new Error(`Sandbox workspace mount ancestor must be a real directory: ${agentsRoot}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
fs.mkdirSync(agentsRoot, { mode: 0o700 });
|
|
80
|
+
}
|
|
81
|
+
const canonicalAgents = fs.realpathSync.native(agentsRoot);
|
|
82
|
+
if (path.dirname(canonicalAgents) !== canonicalWorktree) {
|
|
83
|
+
throw new Error(`Sandbox workspace mount ancestor escapes the worktree: ${agentsRoot}`);
|
|
84
|
+
}
|
|
85
|
+
const target = path.join(agentsRoot, 'workspace');
|
|
86
|
+
if (fs.existsSync(target)) {
|
|
87
|
+
const stat = fs.lstatSync(target);
|
|
88
|
+
if (stat.isSymbolicLink()) {
|
|
89
|
+
throw new Error(`Sandbox workspace mount target must not be a symbolic link: ${target}`);
|
|
90
|
+
}
|
|
91
|
+
if (!stat.isDirectory()) {
|
|
92
|
+
throw new Error(`Sandbox workspace mount target must be a directory: ${target}`);
|
|
93
|
+
}
|
|
94
|
+
if (path.dirname(fs.realpathSync.native(target)) !== canonicalAgents) {
|
|
95
|
+
throw new Error(`Sandbox workspace mount target escapes the worktree: ${target}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
fs.mkdirSync(target, { mode: 0o700 });
|
|
100
|
+
}
|
|
101
|
+
fs.chmodSync(target, 0o700);
|
|
102
|
+
fs.accessSync(target, fs.constants.W_OK);
|
|
103
|
+
return target;
|
|
104
|
+
}
|
|
50
105
|
export function materializeSandboxWorkspaceView(params) {
|
|
51
106
|
const projectRoot = path.resolve(params.base, params.project);
|
|
52
107
|
const { root, taskMountPath } = sandboxWorkspaceViewPaths(params);
|
|
53
108
|
assertSafeDirectory(projectRoot, path.resolve(params.base));
|
|
54
109
|
assertSafeDirectory(root, projectRoot);
|
|
55
110
|
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
56
|
-
for (const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
fs.
|
|
60
|
-
fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
|
111
|
+
for (const { hostPath } of sandboxWorkspaceViewStatePaths(root)) {
|
|
112
|
+
assertSafeDirectory(hostPath, root);
|
|
113
|
+
fs.rmSync(hostPath, { recursive: true, force: true });
|
|
114
|
+
fs.mkdirSync(hostPath, { recursive: true, mode: 0o700 });
|
|
61
115
|
}
|
|
62
116
|
const active = path.join(root, 'active');
|
|
63
117
|
const registry = {
|
|
@@ -77,12 +131,16 @@ export function materializeSandboxWorkspaceView(params) {
|
|
|
77
131
|
return { root, taskMountPath };
|
|
78
132
|
}
|
|
79
133
|
export function materializeSandboxControl(params) {
|
|
80
|
-
const { root, channelDir, manifestPath } = sandboxControlPaths(params);
|
|
134
|
+
const { root, channelDir, statusDir, processingDir, manifestPath } = sandboxControlPaths(params);
|
|
81
135
|
assertSafeDirectory(root, path.resolve(params.base));
|
|
82
136
|
const consumedDir = path.join(root, 'consumed');
|
|
83
137
|
assertSafeDirectory(consumedDir, root);
|
|
84
138
|
fs.rmSync(consumedDir, { recursive: true, force: true });
|
|
85
139
|
fs.mkdirSync(consumedDir, { recursive: true, mode: 0o700 });
|
|
140
|
+
for (const directory of [statusDir, processingDir]) {
|
|
141
|
+
assertSafeDirectory(directory, root);
|
|
142
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
143
|
+
}
|
|
86
144
|
for (const queue of ['requests', 'responses']) {
|
|
87
145
|
const directory = path.join(channelDir, queue);
|
|
88
146
|
assertSafeDirectory(directory, root);
|
|
@@ -90,20 +148,42 @@ export function materializeSandboxControl(params) {
|
|
|
90
148
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
91
149
|
}
|
|
92
150
|
const token = randomBytes(32).toString('hex');
|
|
151
|
+
const generation = randomBytes(16).toString('hex');
|
|
93
152
|
const repoRoot = fs.realpathSync.native(params.repoRoot);
|
|
94
153
|
const manifest = {
|
|
95
|
-
version:
|
|
154
|
+
version: 4,
|
|
155
|
+
engine: params.engine ?? 'docker',
|
|
96
156
|
repoRoot,
|
|
97
157
|
worktreeRoot: fs.realpathSync.native(params.worktreeRoot),
|
|
98
158
|
project: params.project,
|
|
99
159
|
container: params.container,
|
|
160
|
+
containerIdentity: { id: '', labels: {} },
|
|
100
161
|
branch: params.branch,
|
|
101
162
|
mode: params.identity.mode,
|
|
102
163
|
taskId: params.identity.mode === 'task-bound' ? params.identity.taskId : null,
|
|
103
164
|
token,
|
|
104
|
-
|
|
165
|
+
generation,
|
|
166
|
+
channelDir,
|
|
167
|
+
publicStatusDir: statusDir,
|
|
168
|
+
processingDir
|
|
105
169
|
};
|
|
106
170
|
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest)}\n`, { mode: 0o600 });
|
|
107
|
-
return { channelDir, manifestPath, token };
|
|
171
|
+
return { root, channelDir, statusDir, manifestPath, token, generation };
|
|
172
|
+
}
|
|
173
|
+
export function finalizeSandboxControlManifest(setup, identity) {
|
|
174
|
+
if (!identity.engine || !identity.id)
|
|
175
|
+
throw new Error('SANDBOX_CONTROL_CONTAINER_ID_INVALID');
|
|
176
|
+
const manifest = JSON.parse(fs.readFileSync(setup.manifestPath, 'utf8'));
|
|
177
|
+
manifest.version = 4;
|
|
178
|
+
manifest.engine = identity.engine;
|
|
179
|
+
manifest.containerIdentity = { id: identity.id, labels: { ...identity.labels } };
|
|
180
|
+
const temporary = `${setup.manifestPath}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
181
|
+
try {
|
|
182
|
+
fs.writeFileSync(temporary, `${JSON.stringify(manifest)}\n`, { mode: 0o600, flag: 'wx' });
|
|
183
|
+
fs.renameSync(temporary, setup.manifestPath);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
fs.rmSync(temporary, { force: true });
|
|
187
|
+
}
|
|
108
188
|
}
|
|
109
189
|
//# sourceMappingURL=workspace-view.js.map
|