@fitlab-ai/agent-infra 0.9.6 → 0.9.8
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 +10 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
- package/dist/lib/decide.js +68 -14
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +1 -3
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-ledger.js +59 -5
- package/dist/lib/internal/task-lifecycle.js +59 -8
- package/dist/lib/internal/task-orchestration.js +48 -8
- package/dist/lib/internal/task-override.js +131 -0
- package/dist/lib/internal/task-review.js +64 -6
- package/dist/lib/internal/task-verify.js +65 -3
- package/dist/lib/internal/task-warning.js +57 -4
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/pr-summary.js +5 -3
- 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/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +394 -264
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +23 -9
- package/dist/lib/sandbox/control/lifecycle.js +590 -61
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +51 -25
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/mounts.js +7 -0
- package/dist/lib/sandbox/recovery.js +135 -50
- package/dist/lib/sandbox/workspace-view.js +59 -20
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +107 -20
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +120 -17
- package/dist/lib/task/events.js +148 -7
- package/dist/lib/task/guard-override.js +31 -0
- package/dist/lib/task/human-override.js +1240 -0
- package/dist/lib/task/ledger-intents.js +6 -3
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/orchestration.js +293 -18
- package/dist/lib/task/review-finalization.js +3 -2
- 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 +80 -22
- package/dist/lib/task/verification.js +13 -1
- package/dist/lib/task/workflow-warning-intents.js +4 -2
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
- package/lib/decide.ts +59 -14
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +1 -3
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +43 -8
- package/lib/internal/task-override.ts +138 -0
- package/lib/internal/task-review.ts +51 -6
- package/lib/internal/task-verify.ts +52 -3
- package/lib/internal/task-warning.ts +44 -3
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/pr-summary.ts +10 -3
- 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/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +139 -6
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +24 -12
- package/lib/sandbox/control/lifecycle.ts +685 -67
- package/lib/sandbox/control/protocol.ts +35 -6
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +67 -33
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/mounts.ts +8 -0
- package/lib/sandbox/recovery.ts +159 -49
- package/lib/sandbox/workspace-view.ts +73 -22
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +129 -27
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +205 -25
- package/lib/task/events.ts +132 -6
- package/lib/task/guard-override.ts +50 -0
- package/lib/task/human-override.ts +1503 -0
- package/lib/task/ledger-intents.ts +6 -3
- package/lib/task/ledger.ts +16 -1
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/orchestration.ts +443 -22
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -32
- package/lib/task/verification.ts +15 -1
- package/lib/task/workflow-warning-intents.ts +4 -2
- package/lib/task/write.ts +4 -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 +33 -4
- package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
- 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/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- 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 +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -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 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -5,6 +5,13 @@ export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
|
|
|
5
5
|
export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
|
|
6
6
|
export const SANDBOX_CONTROL_FUTURE_SKEW_MS = 1_000;
|
|
7
7
|
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-orchestration', 'task-create'];
|
|
8
|
+
export const DEFAULT_SANDBOX_CONTROL_TIMING = Object.freeze({
|
|
9
|
+
controlTickMs: 250,
|
|
10
|
+
parkedBindingInitialMs: 1_000,
|
|
11
|
+
slowCheckMs: 5_000,
|
|
12
|
+
containerHeartbeatMs: 5_000,
|
|
13
|
+
quiesceDeadlineMs: 7_000
|
|
14
|
+
});
|
|
8
15
|
export class SandboxControlProtocolError extends Error {
|
|
9
16
|
code;
|
|
10
17
|
retryable;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import { getProcessStartTime } from '../../server/process-state.js';
|
|
4
5
|
import { assertGitWorktreeBinding } from '../../git/worktree-identity.js';
|
|
5
|
-
import { controlError,
|
|
6
|
+
import { controlError, DEFAULT_SANDBOX_CONTROL_TIMING } from './protocol.js';
|
|
6
7
|
import { prepareSandboxControlExecution } from './executor.js';
|
|
7
8
|
import { appendSandboxControlAudit, atomicWriteJson, cleanupStaleSandboxControlLease, executionPath, readActiveLease, readExecution, terminateSandboxControlExecution, writeSandboxControlStatus } from './state.js';
|
|
8
9
|
import { validateSandboxControlRequest } from './protocol.js';
|
|
9
|
-
import {
|
|
10
|
+
import { inspectSandboxControlContainer } from './container-identity.js';
|
|
11
|
+
import { acquireSandboxControlBrokerStartup, garbageCollectSandboxControlRoot, isSandboxControlRootQuiescing, readSandboxControlManifest } from './lifecycle.js';
|
|
12
|
+
import { nextSandboxControlBackoff } from './timing.js';
|
|
10
13
|
function assertRealDirectory(directory, parent) {
|
|
11
14
|
const stat = fs.lstatSync(directory);
|
|
12
15
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
@@ -73,10 +76,12 @@ function bindingReason(manifest) {
|
|
|
73
76
|
return 'SANDBOX_WORKTREE_BINDING_LOST';
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
|
-
function recoverProcessing(manifest) {
|
|
79
|
+
function recoverProcessing(manifest, brokerOwns) {
|
|
77
80
|
for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
|
|
78
81
|
if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name))
|
|
79
82
|
continue;
|
|
83
|
+
if (!brokerOwns())
|
|
84
|
+
return false;
|
|
80
85
|
const descriptor = executionPath(manifest, entry.name);
|
|
81
86
|
const existingResponse = responsePath(manifest, entry.name);
|
|
82
87
|
if (fs.existsSync(descriptor)) {
|
|
@@ -84,6 +89,8 @@ function recoverProcessing(manifest) {
|
|
|
84
89
|
if (!terminateSandboxControlExecution(execution)) {
|
|
85
90
|
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
86
91
|
}
|
|
92
|
+
if (!brokerOwns())
|
|
93
|
+
return false;
|
|
87
94
|
appendSandboxControlAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
88
95
|
let terminal = false;
|
|
89
96
|
if (fs.existsSync(existingResponse)) {
|
|
@@ -96,14 +103,22 @@ function recoverProcessing(manifest) {
|
|
|
96
103
|
terminal = false;
|
|
97
104
|
}
|
|
98
105
|
}
|
|
99
|
-
if (!terminal)
|
|
106
|
+
if (!terminal) {
|
|
107
|
+
if (!brokerOwns())
|
|
108
|
+
return false;
|
|
100
109
|
writeResponse(manifest, unknown(entry.name));
|
|
110
|
+
}
|
|
101
111
|
}
|
|
102
112
|
else {
|
|
113
|
+
if (!brokerOwns())
|
|
114
|
+
return false;
|
|
103
115
|
writeResponse(manifest, notExecuted(entry.name));
|
|
104
116
|
}
|
|
117
|
+
if (!brokerOwns())
|
|
118
|
+
return false;
|
|
105
119
|
fs.rmSync(path.join(manifest.processingDir, entry.name), { recursive: true, force: true });
|
|
106
120
|
}
|
|
121
|
+
return true;
|
|
107
122
|
}
|
|
108
123
|
function delay(ms) {
|
|
109
124
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -111,19 +126,38 @@ function delay(ms) {
|
|
|
111
126
|
export function sandboxControlSafeEnv(env = process.env) {
|
|
112
127
|
return Object.fromEntries(Object.entries(env).filter(([key]) => !key.toUpperCase().startsWith('AGENT_INFRA_CONTROL_')));
|
|
113
128
|
}
|
|
114
|
-
export async function serveSandboxControl(manifestPath, signal = new AbortController().signal) {
|
|
129
|
+
export async function serveSandboxControl(manifestPath, signal = new AbortController().signal, options = {}) {
|
|
130
|
+
const timing = options.timing ?? DEFAULT_SANDBOX_CONTROL_TIMING;
|
|
131
|
+
const inspectContainer = options.inspectContainer ?? ((value) => inspectSandboxControlContainer(value));
|
|
132
|
+
const bindingCheck = options.bindingCheck ?? bindingReason;
|
|
115
133
|
const manifest = readSandboxControlManifest(manifestPath);
|
|
116
134
|
const root = path.dirname(manifestPath);
|
|
117
135
|
const startTime = getProcessStartTime(process.pid);
|
|
118
136
|
if (!startTime)
|
|
119
137
|
throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
120
|
-
const
|
|
138
|
+
const brokerId = randomUUID();
|
|
139
|
+
const releaseStartup = await acquireSandboxControlBrokerStartup(root, { pid: process.pid, startTime, brokerId });
|
|
121
140
|
const requestsDir = path.join(manifest.channelDir, 'requests');
|
|
122
141
|
const responsesDir = path.join(manifest.channelDir, 'responses');
|
|
123
142
|
const consumedDir = path.join(root, 'consumed');
|
|
124
|
-
const broker = {
|
|
143
|
+
const broker = {
|
|
144
|
+
version: 3,
|
|
145
|
+
pid: process.pid,
|
|
146
|
+
startTime,
|
|
147
|
+
brokerId,
|
|
148
|
+
token: manifest.token,
|
|
149
|
+
generation: manifest.generation
|
|
150
|
+
};
|
|
125
151
|
const brokerPath = path.join(root, 'broker.json');
|
|
126
|
-
const brokerRecord = `${JSON.stringify(
|
|
152
|
+
const brokerRecord = `${JSON.stringify(broker)}\n`;
|
|
153
|
+
const brokerOwns = () => {
|
|
154
|
+
try {
|
|
155
|
+
return fs.readFileSync(brokerPath, 'utf8') === brokerRecord;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
127
161
|
try {
|
|
128
162
|
for (const directory of [manifest.channelDir, requestsDir, responsesDir, consumedDir, manifest.publicStatusDir, manifest.processingDir]) {
|
|
129
163
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
@@ -147,11 +181,24 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
147
181
|
let active = null;
|
|
148
182
|
let lastState = '';
|
|
149
183
|
let lastStatusAt = 0;
|
|
184
|
+
let nextBindingCheckAt = 0;
|
|
185
|
+
let nextContainerHeartbeatAt = Date.now() + timing.containerHeartbeatMs;
|
|
186
|
+
let containerReasonCode = null;
|
|
187
|
+
let containerBackoffMs = timing.parkedBindingInitialMs;
|
|
188
|
+
let bindingReasonCode = null;
|
|
189
|
+
let bindingBackoffMs = timing.parkedBindingInitialMs;
|
|
150
190
|
try {
|
|
191
|
+
if (!brokerOwns())
|
|
192
|
+
return;
|
|
151
193
|
writeSandboxControlStatus(manifest, broker, 'starting', null, null);
|
|
194
|
+
if (!brokerOwns())
|
|
195
|
+
return;
|
|
152
196
|
appendSandboxControlAudit(manifest, 'broker-start', { pid: broker.pid });
|
|
153
|
-
recoverProcessing(manifest)
|
|
197
|
+
if (!recoverProcessing(manifest, brokerOwns))
|
|
198
|
+
return;
|
|
154
199
|
while (!signal.aborted) {
|
|
200
|
+
if (!brokerOwns())
|
|
201
|
+
break;
|
|
155
202
|
let current;
|
|
156
203
|
try {
|
|
157
204
|
current = readSandboxControlManifest(manifestPath);
|
|
@@ -161,22 +208,78 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
161
208
|
}
|
|
162
209
|
if (current.token !== manifest.token || current.generation !== manifest.generation)
|
|
163
210
|
break;
|
|
211
|
+
const heartbeatNow = Date.now();
|
|
212
|
+
if (heartbeatNow >= nextContainerHeartbeatAt) {
|
|
213
|
+
let observation;
|
|
214
|
+
try {
|
|
215
|
+
observation = await inspectContainer(manifest);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
observation = { state: 'unknown', reason: controlError(error).code };
|
|
219
|
+
}
|
|
220
|
+
if (!brokerOwns())
|
|
221
|
+
break;
|
|
222
|
+
if (observation.state === 'found') {
|
|
223
|
+
containerReasonCode = null;
|
|
224
|
+
containerBackoffMs = timing.parkedBindingInitialMs;
|
|
225
|
+
nextContainerHeartbeatAt = Date.now() + timing.containerHeartbeatMs;
|
|
226
|
+
}
|
|
227
|
+
else if (observation.state === 'unknown') {
|
|
228
|
+
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_UNKNOWN';
|
|
229
|
+
appendSandboxControlAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
|
|
230
|
+
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
231
|
+
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
232
|
+
containerBackoffMs = backoff.nextDelayMs;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
try {
|
|
236
|
+
await garbageCollectSandboxControlRoot(root, {
|
|
237
|
+
timeoutMs: timing.quiesceDeadlineMs,
|
|
238
|
+
inspectContainer: (timeoutMs) => inspectContainer(manifest),
|
|
239
|
+
selfOwner: broker
|
|
240
|
+
});
|
|
241
|
+
active = null;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
if (!brokerOwns())
|
|
246
|
+
break;
|
|
247
|
+
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_ABSENT_GC_FAILED';
|
|
248
|
+
appendSandboxControlAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
|
|
249
|
+
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
250
|
+
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
251
|
+
containerBackoffMs = backoff.nextDelayMs;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
164
255
|
if (active?.settled) {
|
|
256
|
+
if (!brokerOwns())
|
|
257
|
+
break;
|
|
165
258
|
if (active.result) {
|
|
259
|
+
if (!brokerOwns())
|
|
260
|
+
break;
|
|
166
261
|
writeResponse(manifest, {
|
|
167
262
|
version: 2, id: active.request.id, phase: 'completed', exitCode: active.result.exitCode,
|
|
168
263
|
stdout: active.result.stdout, stderr: active.result.stderr, error: null
|
|
169
264
|
});
|
|
170
265
|
}
|
|
171
266
|
else {
|
|
267
|
+
if (!brokerOwns())
|
|
268
|
+
break;
|
|
172
269
|
writeResponse(manifest, unknown(active.request.id));
|
|
173
270
|
}
|
|
271
|
+
if (!brokerOwns())
|
|
272
|
+
break;
|
|
174
273
|
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
175
274
|
active = null;
|
|
176
275
|
}
|
|
177
276
|
let reasonCode = null;
|
|
178
277
|
try {
|
|
278
|
+
if (!brokerOwns())
|
|
279
|
+
break;
|
|
179
280
|
if (cleanupStaleSandboxControlLease(manifest)) {
|
|
281
|
+
if (!brokerOwns())
|
|
282
|
+
break;
|
|
180
283
|
appendSandboxControlAudit(manifest, 'lease-stale-cleanup');
|
|
181
284
|
}
|
|
182
285
|
if (readActiveLease(manifest))
|
|
@@ -185,18 +288,32 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
185
288
|
catch {
|
|
186
289
|
reasonCode = 'SANDBOX_CONTROL_HANDOFF_ACTIVE';
|
|
187
290
|
}
|
|
188
|
-
|
|
291
|
+
const now = Date.now();
|
|
292
|
+
if (now >= nextBindingCheckAt) {
|
|
293
|
+
bindingReasonCode = bindingCheck(manifest);
|
|
294
|
+
if (bindingReasonCode) {
|
|
295
|
+
const backoff = nextSandboxControlBackoff(bindingBackoffMs, timing.slowCheckMs);
|
|
296
|
+
nextBindingCheckAt = now + backoff.delayMs;
|
|
297
|
+
bindingBackoffMs = backoff.nextDelayMs;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
nextBindingCheckAt = now + timing.slowCheckMs;
|
|
301
|
+
bindingBackoffMs = timing.parkedBindingInitialMs;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
reasonCode ??= containerReasonCode;
|
|
305
|
+
reasonCode ??= bindingReasonCode;
|
|
189
306
|
const state = reasonCode ? 'parked' : active ? 'busy' : 'healthy';
|
|
190
307
|
const stateKey = `${state}:${reasonCode ?? ''}:${active?.request.id ?? ''}`;
|
|
191
|
-
|
|
192
|
-
if (stateKey !== lastState || now - lastStatusAt >= SANDBOX_CONTROL_STATUS_INTERVAL_MS) {
|
|
308
|
+
if (brokerOwns() && (stateKey !== lastState || now - lastStatusAt >= timing.controlTickMs)) {
|
|
193
309
|
writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now);
|
|
194
310
|
lastStatusAt = now;
|
|
195
311
|
}
|
|
196
|
-
if (stateKey !== lastState) {
|
|
312
|
+
if (brokerOwns() && stateKey !== lastState) {
|
|
197
313
|
appendSandboxControlAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
|
|
198
314
|
lastState = stateKey;
|
|
199
315
|
}
|
|
316
|
+
let retiring = false;
|
|
200
317
|
for (const name of fs.readdirSync(requestsDir).sort()) {
|
|
201
318
|
if (!/^[a-f0-9-]{16,64}\.json$/.test(name))
|
|
202
319
|
continue;
|
|
@@ -204,14 +321,26 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
204
321
|
const source = path.join(requestsDir, name);
|
|
205
322
|
let claimed = null;
|
|
206
323
|
try {
|
|
324
|
+
if (!brokerOwns()) {
|
|
325
|
+
retiring = true;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
207
328
|
claimed = claimRequest(manifest, source, id);
|
|
329
|
+
if (!brokerOwns()) {
|
|
330
|
+
retiring = true;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
208
333
|
consumeRequest(consumedDir, id);
|
|
334
|
+
if (!brokerOwns()) {
|
|
335
|
+
retiring = true;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
209
338
|
if (reasonCode)
|
|
210
339
|
throw new Error(reasonCode);
|
|
211
340
|
if (active)
|
|
212
341
|
throw new Error('SANDBOX_CONTROL_BUSY');
|
|
213
342
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
214
|
-
if (
|
|
343
|
+
if (bindingCheck(manifest))
|
|
215
344
|
throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
|
|
216
345
|
if (readActiveLease(manifest))
|
|
217
346
|
throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
@@ -221,43 +350,90 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
221
350
|
const execution = { request, prepared, result: null, failure: null, settled: false };
|
|
222
351
|
active = execution;
|
|
223
352
|
prepared.completion.then((result) => { execution.result = result; execution.settled = true; }, (error) => { execution.failure = error; execution.settled = true; });
|
|
353
|
+
if (!brokerOwns()) {
|
|
354
|
+
prepared.terminate(false);
|
|
355
|
+
active = null;
|
|
356
|
+
retiring = true;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (!brokerOwns()) {
|
|
360
|
+
prepared.terminate(false);
|
|
361
|
+
active = null;
|
|
362
|
+
retiring = true;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
224
365
|
writeResponse(manifest, {
|
|
225
366
|
version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
|
|
226
367
|
});
|
|
368
|
+
if (!brokerOwns()) {
|
|
369
|
+
prepared.terminate(false);
|
|
370
|
+
active = null;
|
|
371
|
+
retiring = true;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
227
374
|
try {
|
|
228
|
-
prepared.start();
|
|
375
|
+
prepared.start(brokerOwns);
|
|
229
376
|
}
|
|
230
377
|
catch (error) {
|
|
231
|
-
|
|
378
|
+
const owned = brokerOwns();
|
|
379
|
+
prepared.terminate(owned);
|
|
380
|
+
if (!owned || !brokerOwns()) {
|
|
381
|
+
active = null;
|
|
382
|
+
retiring = true;
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
232
385
|
writeResponse(manifest, unknown(id));
|
|
386
|
+
if (!brokerOwns()) {
|
|
387
|
+
active = null;
|
|
388
|
+
retiring = true;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
233
391
|
fs.rmSync(path.join(manifest.processingDir, id), { recursive: true, force: true });
|
|
234
392
|
active = null;
|
|
235
393
|
claimed = null;
|
|
394
|
+
if (!brokerOwns()) {
|
|
395
|
+
retiring = true;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
236
398
|
appendSandboxControlAudit(manifest, 'executor-gate-failed', { requestId: id });
|
|
237
399
|
continue;
|
|
238
400
|
}
|
|
239
401
|
}
|
|
240
402
|
catch (error) {
|
|
403
|
+
if (!brokerOwns()) {
|
|
404
|
+
retiring = true;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
241
407
|
writeResponse(manifest, rejected(id, error));
|
|
408
|
+
if (!brokerOwns()) {
|
|
409
|
+
retiring = true;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
242
412
|
if (claimed)
|
|
243
413
|
fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
|
|
244
414
|
}
|
|
245
415
|
}
|
|
246
|
-
|
|
416
|
+
if (retiring || !brokerOwns())
|
|
417
|
+
break;
|
|
418
|
+
await delay(timing.controlTickMs);
|
|
247
419
|
}
|
|
248
420
|
}
|
|
249
421
|
finally {
|
|
250
422
|
if (active) {
|
|
251
|
-
|
|
252
|
-
|
|
423
|
+
const owned = brokerOwns();
|
|
424
|
+
active.prepared.terminate(owned);
|
|
425
|
+
if (owned && brokerOwns())
|
|
426
|
+
writeResponse(manifest, unknown(active.request.id));
|
|
253
427
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
fs.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
428
|
+
if (brokerOwns()) {
|
|
429
|
+
appendSandboxControlAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
|
|
430
|
+
try {
|
|
431
|
+
if (fs.readFileSync(brokerPath, 'utf8') === brokerRecord)
|
|
432
|
+
fs.unlinkSync(brokerPath);
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
// A newer owner or recovery path owns the record.
|
|
436
|
+
}
|
|
261
437
|
}
|
|
262
438
|
}
|
|
263
439
|
}
|
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { buildProcessTreeStopCommand } from '../../server/process-control.js';
|
|
6
|
-
import {
|
|
6
|
+
import { getProcessIdentityState } from '../../server/process-state.js';
|
|
7
7
|
export const SANDBOX_CONTROL_AUDIT_MAX_BYTES = 1024 * 1024;
|
|
8
8
|
export const SANDBOX_CONTROL_EXECUTION_STOP_MS = 2_000;
|
|
9
9
|
function assertRegularFile(filePath) {
|
|
@@ -32,15 +32,17 @@ export function statusPath(manifest) {
|
|
|
32
32
|
}
|
|
33
33
|
export function writeSandboxControlStatus(manifest, broker, state, reasonCode, activeRequestId, now = Date.now()) {
|
|
34
34
|
const status = {
|
|
35
|
-
version:
|
|
35
|
+
version: 2, generation: manifest.generation, broker, state, reasonCode, activeRequestId, updatedAt: now
|
|
36
36
|
};
|
|
37
37
|
atomicWriteJson(statusPath(manifest), status, 0o600, false);
|
|
38
38
|
return status;
|
|
39
39
|
}
|
|
40
40
|
export function parseSandboxControlStatus(value) {
|
|
41
41
|
const status = value;
|
|
42
|
-
if (!status || status.version !==
|
|
43
|
-
|| !status.broker || !Number.isSafeInteger(status.broker.pid) || typeof status.broker.startTime !== '
|
|
42
|
+
if (!status || status.version !== 2 || typeof status.generation !== 'string'
|
|
43
|
+
|| !status.broker || !Number.isSafeInteger(status.broker.pid) || typeof status.broker.startTime !== 'number'
|
|
44
|
+
|| !Number.isSafeInteger(status.broker.startTime) || typeof status.broker.brokerId !== 'string'
|
|
45
|
+
|| status.broker.brokerId.length === 0
|
|
44
46
|
|| !['starting', 'healthy', 'busy', 'parked'].includes(status.state ?? '')
|
|
45
47
|
|| (status.reasonCode !== null && typeof status.reasonCode !== 'string')
|
|
46
48
|
|| (status.activeRequestId !== null && typeof status.activeRequestId !== 'string')
|
|
@@ -56,9 +58,10 @@ export function readActiveLease(manifest, now = Date.now()) {
|
|
|
56
58
|
if (!fs.existsSync(filePath))
|
|
57
59
|
return null;
|
|
58
60
|
const lease = readJsonFile(filePath);
|
|
59
|
-
if (!lease || lease.version !==
|
|
61
|
+
if (!lease || lease.version !== 2
|
|
60
62
|
|| typeof lease.nonce !== 'string' || !lease.owner || !Number.isSafeInteger(lease.owner.pid)
|
|
61
|
-
|| typeof lease.owner.startTime !== '
|
|
63
|
+
|| typeof lease.owner.startTime !== 'number' || !Number.isSafeInteger(lease.owner.startTime)
|
|
64
|
+
|| !Number.isSafeInteger(lease.issuedAt)
|
|
62
65
|
|| !Number.isSafeInteger(lease.expiresAt) || typeof lease.generation !== 'string'
|
|
63
66
|
|| (lease.taskId !== null && typeof lease.taskId !== 'string')
|
|
64
67
|
|| typeof lease.branch !== 'string' || typeof lease.reason !== 'string') {
|
|
@@ -69,24 +72,28 @@ export function readActiveLease(manifest, now = Date.now()) {
|
|
|
69
72
|
return null;
|
|
70
73
|
return lease;
|
|
71
74
|
}
|
|
72
|
-
export function cleanupStaleSandboxControlLease(manifest, now = Date.now()) {
|
|
75
|
+
export function cleanupStaleSandboxControlLease(manifest, now = Date.now(), options = {}) {
|
|
73
76
|
const filePath = path.join(path.dirname(manifest.publicStatusDir), 'lease.json');
|
|
74
77
|
if (!fs.existsSync(filePath))
|
|
75
78
|
return false;
|
|
76
79
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
77
80
|
const lease = JSON.parse(raw);
|
|
78
|
-
if (!lease || lease.version !==
|
|
81
|
+
if (!lease || lease.version !== 2
|
|
79
82
|
|| typeof lease.nonce !== 'string' || !lease.owner || !Number.isSafeInteger(lease.owner.pid)
|
|
80
|
-
|| typeof lease.owner.startTime !== '
|
|
83
|
+
|| typeof lease.owner.startTime !== 'number' || !Number.isSafeInteger(lease.owner.startTime)
|
|
84
|
+
|| !Number.isSafeInteger(lease.issuedAt)
|
|
81
85
|
|| !Number.isSafeInteger(lease.expiresAt) || typeof lease.generation !== 'string'
|
|
82
86
|
|| (lease.taskId !== null && typeof lease.taskId !== 'string')
|
|
83
87
|
|| typeof lease.branch !== 'string' || typeof lease.reason !== 'string') {
|
|
84
88
|
throw new Error('SANDBOX_CONTROL_LEASE_INVALID');
|
|
85
89
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
version: 1, pid: lease.owner.pid, startTime: lease.owner.startTime
|
|
90
|
+
const ownerState = (options.identityProbe ?? getProcessIdentityState)({
|
|
91
|
+
pid: lease.owner.pid, startTime: lease.owner.startTime
|
|
89
92
|
});
|
|
93
|
+
if (ownerState === 'unknown')
|
|
94
|
+
throw new Error('SANDBOX_CONTROL_LEASE_OWNER_UNAVAILABLE');
|
|
95
|
+
const stale = lease.generation !== manifest.generation || lease.taskId !== manifest.taskId
|
|
96
|
+
|| lease.branch !== manifest.branch || lease.expiresAt <= now || ownerState === 'dead';
|
|
90
97
|
if (!stale)
|
|
91
98
|
return false;
|
|
92
99
|
if (fs.readFileSync(filePath, 'utf8') !== raw)
|
|
@@ -109,9 +116,10 @@ export function executionPath(manifest, requestId) {
|
|
|
109
116
|
}
|
|
110
117
|
export function readExecution(filePath) {
|
|
111
118
|
const execution = readJsonFile(filePath);
|
|
112
|
-
if (!execution || execution.version !==
|
|
119
|
+
if (!execution || execution.version !== 2 || typeof execution.generation !== 'string'
|
|
113
120
|
|| typeof execution.requestId !== 'string' || typeof execution.nonce !== 'string'
|
|
114
|
-
|| !execution.child || !Number.isSafeInteger(execution.child.pid) || typeof execution.child.startTime !== '
|
|
121
|
+
|| !execution.child || !Number.isSafeInteger(execution.child.pid) || typeof execution.child.startTime !== 'number'
|
|
122
|
+
|| !Number.isSafeInteger(execution.child.startTime)
|
|
115
123
|
|| !['prepared', 'running', 'terminating'].includes(execution.phase ?? '')
|
|
116
124
|
|| !Number.isSafeInteger(execution.updatedAt))
|
|
117
125
|
throw new Error('SANDBOX_CONTROL_EXECUTION_INVALID');
|
|
@@ -120,29 +128,42 @@ export function readExecution(filePath) {
|
|
|
120
128
|
export function terminateSandboxControlExecution(execution, options = {}) {
|
|
121
129
|
const platform = options.platform ?? process.platform;
|
|
122
130
|
const processGroupId = execution.child.processGroupId;
|
|
123
|
-
const
|
|
131
|
+
const processGroupState = (groupId) => {
|
|
124
132
|
try {
|
|
125
133
|
const rows = execFileSync('ps', ['-axo', 'pgid=,stat='], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
126
134
|
return rows.split('\n').some((row) => {
|
|
127
135
|
const match = row.trim().match(/^(\d+)\s+(\S+)/);
|
|
128
136
|
return Number(match?.[1]) === groupId && !match?.[2]?.startsWith('Z');
|
|
129
|
-
});
|
|
137
|
+
}) ? 'alive' : 'dead';
|
|
130
138
|
}
|
|
131
139
|
catch {
|
|
132
140
|
try {
|
|
133
141
|
process.kill(-groupId, 0);
|
|
134
|
-
return
|
|
142
|
+
return 'alive';
|
|
135
143
|
}
|
|
136
144
|
catch (error) {
|
|
137
|
-
|
|
145
|
+
const code = error.code;
|
|
146
|
+
if (code === 'ESRCH')
|
|
147
|
+
return 'dead';
|
|
148
|
+
if (code === 'EPERM')
|
|
149
|
+
return 'alive';
|
|
150
|
+
return 'unknown';
|
|
138
151
|
}
|
|
139
152
|
}
|
|
140
153
|
};
|
|
141
|
-
const
|
|
154
|
+
const treeState = () => {
|
|
142
155
|
if (platform === 'win32' || !processGroupId) {
|
|
143
|
-
return
|
|
156
|
+
return (options.identityProbe ?? getProcessIdentityState)({
|
|
157
|
+
pid: execution.child.pid, startTime: execution.child.startTime
|
|
158
|
+
});
|
|
144
159
|
}
|
|
145
|
-
return
|
|
160
|
+
return processGroupState(processGroupId);
|
|
161
|
+
};
|
|
162
|
+
const treeAlive = () => {
|
|
163
|
+
const state = treeState();
|
|
164
|
+
if (state === 'unknown')
|
|
165
|
+
throw new Error('SANDBOX_CONTROL_EXECUTION_OWNER_UNAVAILABLE');
|
|
166
|
+
return state === 'alive';
|
|
146
167
|
};
|
|
147
168
|
if (!treeAlive())
|
|
148
169
|
return true;
|
|
@@ -156,20 +177,25 @@ export function terminateSandboxControlExecution(execution, options = {}) {
|
|
|
156
177
|
catch {
|
|
157
178
|
// The identity check below distinguishes an already-exited tree from a failed termination.
|
|
158
179
|
}
|
|
159
|
-
const deadline = Date.now() + (options.timeoutMs ?? SANDBOX_CONTROL_EXECUTION_STOP_MS);
|
|
160
|
-
|
|
180
|
+
const deadline = options.deadlineAt ?? (Date.now() + (options.timeoutMs ?? SANDBOX_CONTROL_EXECUTION_STOP_MS));
|
|
181
|
+
const forceAt = options.forceAt ?? (options.deadlineAt === undefined
|
|
182
|
+
? deadline
|
|
183
|
+
: Math.min(deadline, Date.now() + Math.floor(Math.max(0, deadline - Date.now()) / 2)));
|
|
184
|
+
while (Date.now() < forceAt) {
|
|
161
185
|
if (!treeAlive())
|
|
162
186
|
return true;
|
|
163
187
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
164
188
|
}
|
|
165
|
-
if (platform !== 'win32'
|
|
189
|
+
if (platform !== 'win32' && options.allowForce !== false
|
|
190
|
+
&& (options.deadlineAt === undefined || Date.now() < deadline)) {
|
|
166
191
|
try {
|
|
167
192
|
process.kill(processGroupId ? -processGroupId : execution.child.pid, 'SIGKILL');
|
|
168
193
|
}
|
|
169
194
|
catch {
|
|
170
195
|
// The final identity check handles an already-exited tree.
|
|
171
196
|
}
|
|
172
|
-
const killDeadline =
|
|
197
|
+
const killDeadline = options.deadlineAt
|
|
198
|
+
?? (Date.now() + (options.timeoutMs ?? SANDBOX_CONTROL_EXECUTION_STOP_MS));
|
|
173
199
|
while (Date.now() < killDeadline) {
|
|
174
200
|
if (!treeAlive())
|
|
175
201
|
return true;
|
|
@@ -14,6 +14,12 @@ const PLATFORM_DEFAULTS = Object.freeze({
|
|
|
14
14
|
darwin: ENGINES.COLIMA,
|
|
15
15
|
win32: ENGINES.WSL2
|
|
16
16
|
});
|
|
17
|
+
function isPerPlatformEngine(engine) {
|
|
18
|
+
return typeof engine === 'object' && engine !== null;
|
|
19
|
+
}
|
|
20
|
+
function resolveEngineForPlatform(engine, os) {
|
|
21
|
+
return isPerPlatformEngine(engine) ? engine[os] : engine;
|
|
22
|
+
}
|
|
17
23
|
function runFns({ runFn = run, runOkFn = runOk, runSafeFn = runSafe, runVerboseFn = runVerbose } = {}) {
|
|
18
24
|
return {
|
|
19
25
|
run: runFn,
|
|
@@ -56,23 +62,24 @@ function ensureBuildKit(engine, runOkEngineFn) {
|
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
64
|
export function validateSandboxEngine(engine, { platformFn = platform } = {}) {
|
|
59
|
-
|
|
65
|
+
const os = platformFn();
|
|
66
|
+
const resolved = resolveEngineForPlatform(engine, os);
|
|
67
|
+
if (resolved === null || resolved === undefined) {
|
|
60
68
|
return null;
|
|
61
69
|
}
|
|
62
|
-
|
|
63
|
-
if (!(engine in ADAPTERS)) {
|
|
70
|
+
if (!(resolved in ADAPTERS)) {
|
|
64
71
|
const known = Object.keys(ADAPTERS).join(', ');
|
|
65
|
-
throw new Error(`sandbox: invalid "sandbox.engine" value "${
|
|
72
|
+
throw new Error(`sandbox: invalid "sandbox.engine" value "${resolved}" (unknown sandbox engine). `
|
|
66
73
|
+ `Valid engines: ${known}.`);
|
|
67
74
|
}
|
|
68
|
-
const adapter = ADAPTERS[
|
|
75
|
+
const adapter = ADAPTERS[resolved];
|
|
69
76
|
if (!adapter.supportedPlatforms.includes(os)) {
|
|
70
77
|
const supported = enginesForPlatform(os);
|
|
71
78
|
const supportedList = supported.length > 0 ? supported.join(', ') : 'none';
|
|
72
|
-
throw new Error(`sandbox: "sandbox.engine" value "${
|
|
79
|
+
throw new Error(`sandbox: "sandbox.engine" value "${resolved}" is not supported on ${os}. `
|
|
73
80
|
+ `Supported engines on ${os}: ${supportedList}.`);
|
|
74
81
|
}
|
|
75
|
-
return
|
|
82
|
+
return resolved;
|
|
76
83
|
}
|
|
77
84
|
export function detectEngine(config = {}, { platformFn = platform } = {}) {
|
|
78
85
|
const configured = validateSandboxEngine(config.engine, { platformFn });
|
|
@@ -55,6 +55,13 @@ export function sandboxCoreBindMounts(config, branch, overrides) {
|
|
|
55
55
|
containerPath: '/run/agent-infra/control',
|
|
56
56
|
readOnly: false
|
|
57
57
|
});
|
|
58
|
+
if (taskBound && overrides.runtimeDir) {
|
|
59
|
+
mounts.push({
|
|
60
|
+
hostPaths: [overrides.runtimeDir],
|
|
61
|
+
containerPath: '/run/agent-infra/runtime',
|
|
62
|
+
readOnly: false
|
|
63
|
+
});
|
|
64
|
+
}
|
|
58
65
|
return mounts;
|
|
59
66
|
}
|
|
60
67
|
//# sourceMappingURL=mounts.js.map
|