voratiq 0.1.0-beta.0 → 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/auth/providers/gemini.js +4 -3
- package/dist/bin.js +2 -1
- package/dist/commands/run/agents/lifecycle.js +22 -1
- package/dist/commands/run/agents/run-context.d.ts +3 -0
- package/dist/commands/run/agents/run-context.js +14 -1
- package/dist/commands/run/agents/sandbox-launcher.d.ts +6 -1
- package/dist/commands/run/agents/sandbox-launcher.js +4 -2
- package/dist/commands/run/agents/watchdog.d.ts +6 -2
- package/dist/commands/run/agents/watchdog.js +66 -2
- package/dist/commands/run/sandbox.d.ts +22 -0
- package/dist/commands/run/sandbox.js +109 -0
- package/dist/configs/sandbox/loader.d.ts +1 -1
- package/dist/configs/sandbox/loader.js +30 -0
- package/dist/configs/sandbox/schemas.d.ts +25 -0
- package/dist/configs/sandbox/schemas.js +11 -0
- package/dist/configs/sandbox/types.d.ts +9 -0
- package/dist/records/types.d.ts +17 -0
- package/dist/records/types.js +30 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Voratiq
|
|
2
2
|
|
|
3
|
-
Run
|
|
3
|
+
Run agents in parallel, compare results, and apply the best solution.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
@@ -3,7 +3,7 @@ import { assertSandboxDestination } from "../staging.js";
|
|
|
3
3
|
import { buildAuthFailedMessage } from "./messages.js";
|
|
4
4
|
import { disposeHandles, registerSandboxSecrets, stageSecretFile, } from "./secret-staging.js";
|
|
5
5
|
import { teardownAuthProvider } from "./teardown.js";
|
|
6
|
-
import { assertReadableFileOrThrow, composeSandboxEnvResult,
|
|
6
|
+
import { assertReadableFileOrThrow, composeSandboxEnvResult, copyOptionalFileWithPermissions, createSandboxPaths, ensureDirectories, resolveChildPath, } from "./utils.js";
|
|
7
7
|
const GEMINI_PROVIDER_ID = "gemini";
|
|
8
8
|
const GEMINI_LOGIN_HINT = buildAuthFailedMessage("Gemini");
|
|
9
9
|
const GEMINI_REQUIRED_FILES = [
|
|
@@ -124,7 +124,8 @@ async function stageOptionalFiles(geminiHome, sandboxGeminiDir, sandboxHome) {
|
|
|
124
124
|
return new GeminiAuthProviderError(GEMINI_LOGIN_HINT, { cause });
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
// Create empty tmp directory (don't copy contents to avoid polluting
|
|
128
|
+
// chat transcripts with historical sessions from previous runs)
|
|
128
129
|
const sandboxTmpDestination = resolveChildPath(sandboxGeminiDir, "tmp");
|
|
129
130
|
assertSandboxDestination({
|
|
130
131
|
sandboxHome,
|
|
@@ -132,7 +133,7 @@ async function stageOptionalFiles(geminiHome, sandboxGeminiDir, sandboxHome) {
|
|
|
132
133
|
providerId: GEMINI_PROVIDER_ID,
|
|
133
134
|
fileLabel: "tmp",
|
|
134
135
|
});
|
|
135
|
-
await
|
|
136
|
+
await ensureDirectories([sandboxTmpDestination]);
|
|
136
137
|
}
|
|
137
138
|
async function validateSettingsFile(settingsPath) {
|
|
138
139
|
let raw;
|
package/dist/bin.js
CHANGED
|
@@ -82,7 +82,8 @@ export async function runCli(argv = process.argv) {
|
|
|
82
82
|
.description("Voratiq CLI")
|
|
83
83
|
.version(getVoratiqVersion(), "-v, --version", "print the Voratiq version")
|
|
84
84
|
.exitOverride()
|
|
85
|
-
.showHelpAfterError()
|
|
85
|
+
.showHelpAfterError()
|
|
86
|
+
.helpCommand(false);
|
|
86
87
|
program.addCommand(createInitCommand());
|
|
87
88
|
program.addCommand(createListCommand());
|
|
88
89
|
program.addCommand(createRunCommand());
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { loadSandboxProviderConfig } from "../../../configs/sandbox/loader.js";
|
|
1
2
|
import { toErrorMessage } from "../../../utils/errors.js";
|
|
2
3
|
import { GIT_AUTHOR_EMAIL, GIT_AUTHOR_NAME } from "../../../utils/git.js";
|
|
3
4
|
import { AgentProcessError, GitOperationError, RunCommandError, } from "../errors.js";
|
|
5
|
+
import { DEFAULT_DENIAL_BACKOFF, } from "../sandbox.js";
|
|
4
6
|
import { teardownRegisteredSandboxContext } from "../sandbox-registry.js";
|
|
5
7
|
import { captureAgentChatTranscripts } from "./chat-preserver.js";
|
|
6
8
|
import { runPostProcessingAndEvaluations } from "./eval-runner.js";
|
|
@@ -29,18 +31,33 @@ export async function executeAgentLifecycle(execution) {
|
|
|
29
31
|
await execution.progress.onRunning(buildRunningAgentRecord(execution, agentContext));
|
|
30
32
|
}
|
|
31
33
|
// Create watchdog trigger callback for immediate UI surfacing
|
|
32
|
-
const onWatchdogTrigger = (trigger, reason) => {
|
|
34
|
+
const onWatchdogTrigger = (trigger, reason, failFast) => {
|
|
33
35
|
// Update watchdog metadata with trigger
|
|
34
36
|
agentContext.setWatchdogMetadata({
|
|
35
37
|
...initialWatchdog,
|
|
36
38
|
trigger,
|
|
37
39
|
});
|
|
40
|
+
if (failFast) {
|
|
41
|
+
agentContext.setFailFastTriggered(failFast);
|
|
42
|
+
}
|
|
38
43
|
// Fire early failure callback for immediate UI update
|
|
39
44
|
if (execution.progress?.onEarlyFailure) {
|
|
40
45
|
const earlyRecord = agentContext.buildEarlyFailureRecord(reason);
|
|
41
46
|
void execution.progress.onEarlyFailure(earlyRecord);
|
|
42
47
|
}
|
|
43
48
|
};
|
|
49
|
+
let denialBackoff = DEFAULT_DENIAL_BACKOFF;
|
|
50
|
+
try {
|
|
51
|
+
const sandboxProviderConfig = loadSandboxProviderConfig({
|
|
52
|
+
root,
|
|
53
|
+
providerId: agent.provider,
|
|
54
|
+
});
|
|
55
|
+
denialBackoff = sandboxProviderConfig.denialBackoff;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// If sandbox.yaml is missing or invalid, fall back to defaults rather than
|
|
59
|
+
// failing the entire agent lifecycle.
|
|
60
|
+
}
|
|
44
61
|
const processResult = await runAgentProcess({
|
|
45
62
|
runtimeManifestPath,
|
|
46
63
|
agentRoot: workspacePaths.agentRoot,
|
|
@@ -49,11 +66,15 @@ export async function executeAgentLifecycle(execution) {
|
|
|
49
66
|
sandboxSettingsPath: workspacePaths.sandboxSettingsPath,
|
|
50
67
|
providerId: agent.provider,
|
|
51
68
|
onWatchdogTrigger,
|
|
69
|
+
denialBackoff,
|
|
52
70
|
});
|
|
53
71
|
// Update watchdog metadata from process result (in case trigger came via watchdog)
|
|
54
72
|
if (processResult.watchdog) {
|
|
55
73
|
agentContext.setWatchdogMetadata(processResult.watchdog);
|
|
56
74
|
}
|
|
75
|
+
if (processResult.failFast) {
|
|
76
|
+
agentContext.setFailFastTriggered(processResult.failFast);
|
|
77
|
+
}
|
|
57
78
|
if (processResult.exitCode !== 0 || processResult.errorMessage) {
|
|
58
79
|
// Use watchdog error message if available, otherwise detect from logs
|
|
59
80
|
const failureDetail = processResult.watchdog?.trigger && processResult.errorMessage
|
|
@@ -5,6 +5,7 @@ import type { ArtifactCollectionResult } from "../../../workspace/agents.js";
|
|
|
5
5
|
import type { ChatArtifactFormat } from "../../../workspace/chat/types.js";
|
|
6
6
|
import type { RunCommandError } from "../errors.js";
|
|
7
7
|
import { type AgentExecutionResult, type AgentExecutionState } from "../reports.js";
|
|
8
|
+
import type { SandboxFailFastInfo } from "../sandbox.js";
|
|
8
9
|
export declare class AgentRunContext {
|
|
9
10
|
readonly state: AgentExecutionState;
|
|
10
11
|
status: AgentStatus;
|
|
@@ -12,6 +13,7 @@ export declare class AgentRunContext {
|
|
|
12
13
|
evalResults: AgentEvalResult[];
|
|
13
14
|
errorMessage: string | undefined;
|
|
14
15
|
watchdogMetadata: WatchdogMetadata | undefined;
|
|
16
|
+
private failFast;
|
|
15
17
|
private completedAt;
|
|
16
18
|
private startedAt;
|
|
17
19
|
private readonly evalPlan;
|
|
@@ -36,6 +38,7 @@ export declare class AgentRunContext {
|
|
|
36
38
|
markChatArtifact(format: ChatArtifactFormat): void;
|
|
37
39
|
recordEvalWarnings(warnings: readonly string[]): void;
|
|
38
40
|
setWatchdogMetadata(metadata: WatchdogMetadata): void;
|
|
41
|
+
setFailFastTriggered(info: SandboxFailFastInfo): void;
|
|
39
42
|
finalize(): AgentExecutionResult;
|
|
40
43
|
/**
|
|
41
44
|
* Build an early failure record for immediate UI surfacing when watchdog triggers.
|
|
@@ -11,6 +11,7 @@ export class AgentRunContext {
|
|
|
11
11
|
evalResults;
|
|
12
12
|
errorMessage;
|
|
13
13
|
watchdogMetadata;
|
|
14
|
+
failFast;
|
|
14
15
|
completedAt;
|
|
15
16
|
startedAt;
|
|
16
17
|
evalPlan;
|
|
@@ -113,6 +114,9 @@ export class AgentRunContext {
|
|
|
113
114
|
setWatchdogMetadata(metadata) {
|
|
114
115
|
this.watchdogMetadata = metadata;
|
|
115
116
|
}
|
|
117
|
+
setFailFastTriggered(info) {
|
|
118
|
+
this.failFast = info;
|
|
119
|
+
}
|
|
116
120
|
finalize() {
|
|
117
121
|
this.setCompleted();
|
|
118
122
|
const record = buildAgentRecord({
|
|
@@ -127,6 +131,7 @@ export class AgentRunContext {
|
|
|
127
131
|
warnings: this.evalWarnings,
|
|
128
132
|
diffStatistics: this.state.diffStatistics,
|
|
129
133
|
watchdog: this.watchdogMetadata,
|
|
134
|
+
failFast: this.failFast,
|
|
130
135
|
});
|
|
131
136
|
return finalizeAgentResult(this.runId, record, this.state);
|
|
132
137
|
}
|
|
@@ -147,6 +152,7 @@ export class AgentRunContext {
|
|
|
147
152
|
warnings: this.evalWarnings,
|
|
148
153
|
diffStatistics: undefined,
|
|
149
154
|
watchdog: this.watchdogMetadata,
|
|
155
|
+
failFast: this.failFast,
|
|
150
156
|
});
|
|
151
157
|
}
|
|
152
158
|
}
|
|
@@ -158,7 +164,7 @@ export function buildDefaultEvalResults(definitions) {
|
|
|
158
164
|
}));
|
|
159
165
|
}
|
|
160
166
|
function buildAgentRecord(options) {
|
|
161
|
-
const { agent, commitSha, completedAt, errorMessage, startedAt, status, artifacts, evalResults, warnings, diffStatistics, watchdog, } = options;
|
|
167
|
+
const { agent, commitSha, completedAt, errorMessage, startedAt, status, artifacts, evalResults, warnings, diffStatistics, watchdog, failFast, } = options;
|
|
162
168
|
const snapshots = toEvalSnapshots(evalResults);
|
|
163
169
|
const artifactState = Object.keys(artifacts).length > 0 ? artifacts : undefined;
|
|
164
170
|
const normalizedWarnings = warnings.length > 0 ? Array.from(new Set(warnings)) : undefined;
|
|
@@ -175,6 +181,13 @@ function buildAgentRecord(options) {
|
|
|
175
181
|
error: errorMessage,
|
|
176
182
|
warnings: normalizedWarnings,
|
|
177
183
|
watchdog,
|
|
184
|
+
...(failFast
|
|
185
|
+
? {
|
|
186
|
+
failFastTriggered: true,
|
|
187
|
+
failFastTarget: failFast.target,
|
|
188
|
+
failFastOperation: failFast.operation,
|
|
189
|
+
}
|
|
190
|
+
: {}),
|
|
178
191
|
};
|
|
179
192
|
if (normalizedDiffStatistics) {
|
|
180
193
|
record.diffStatistics = normalizedDiffStatistics;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { DenialBackoffConfig } from "../../../configs/sandbox/types.js";
|
|
1
2
|
import type { WatchdogMetadata } from "../../../records/types.js";
|
|
2
3
|
import type { AgentWorkspacePaths } from "../../../workspace/layout.js";
|
|
4
|
+
import { type SandboxFailFastInfo } from "../sandbox.js";
|
|
3
5
|
import { type WatchdogTrigger } from "./watchdog.js";
|
|
4
6
|
export interface AgentProcessOptions {
|
|
5
7
|
runtimeManifestPath: string;
|
|
@@ -7,11 +9,12 @@ export interface AgentProcessOptions {
|
|
|
7
9
|
stdoutPath: string;
|
|
8
10
|
stderrPath: string;
|
|
9
11
|
sandboxSettingsPath: string;
|
|
12
|
+
denialBackoff?: DenialBackoffConfig;
|
|
10
13
|
resolveRunInvocation?: RunInvocationResolver;
|
|
11
14
|
/** Provider ID for watchdog fatal pattern matching. */
|
|
12
15
|
providerId?: string;
|
|
13
16
|
/** Callback fired immediately when watchdog triggers, before process exits. */
|
|
14
|
-
onWatchdogTrigger?: (trigger: WatchdogTrigger, reason: string) => void;
|
|
17
|
+
onWatchdogTrigger?: (trigger: WatchdogTrigger, reason: string, failFast?: SandboxFailFastInfo) => void;
|
|
15
18
|
}
|
|
16
19
|
export interface AgentProcessResult {
|
|
17
20
|
exitCode: number;
|
|
@@ -19,6 +22,8 @@ export interface AgentProcessResult {
|
|
|
19
22
|
signal?: NodeJS.Signals | null;
|
|
20
23
|
/** Watchdog metadata showing enforced limits and trigger reason. */
|
|
21
24
|
watchdog?: WatchdogMetadata;
|
|
25
|
+
/** Sandbox fail-fast metadata when repeated denials trigger an abort. */
|
|
26
|
+
failFast?: SandboxFailFastInfo;
|
|
22
27
|
}
|
|
23
28
|
export interface RunInvocationContext {
|
|
24
29
|
agentRoot: string;
|
|
@@ -61,7 +61,7 @@ async function defaultResolveRunInvocation(context) {
|
|
|
61
61
|
return { command, args };
|
|
62
62
|
}
|
|
63
63
|
export async function runAgentProcess(options) {
|
|
64
|
-
const { runtimeManifestPath, agentRoot, stdoutPath, stderrPath, sandboxSettingsPath, resolveRunInvocation, providerId = "", onWatchdogTrigger, } = options;
|
|
64
|
+
const { runtimeManifestPath, agentRoot, stdoutPath, stderrPath, sandboxSettingsPath, denialBackoff, resolveRunInvocation, providerId = "", onWatchdogTrigger, } = options;
|
|
65
65
|
const stdoutStream = createWriteStream(stdoutPath, { flags: "w" });
|
|
66
66
|
const stderrStream = createWriteStream(stderrPath, { flags: "w" });
|
|
67
67
|
const shimEntryPath = resolveShimEntryPath();
|
|
@@ -105,6 +105,7 @@ export async function runAgentProcess(options) {
|
|
|
105
105
|
watchdogController = createWatchdog(child, stderrStream, {
|
|
106
106
|
providerId,
|
|
107
107
|
onWatchdogTrigger,
|
|
108
|
+
denialBackoff,
|
|
108
109
|
});
|
|
109
110
|
// Bridge watchdog's abort signal to our shared abort controller
|
|
110
111
|
abortSignalHandler = () => forceAbortController.abort();
|
|
@@ -135,6 +136,7 @@ export async function runAgentProcess(options) {
|
|
|
135
136
|
}
|
|
136
137
|
const watchdogState = watchdogController?.getState();
|
|
137
138
|
const watchdogTrigger = watchdogState?.triggered ?? undefined;
|
|
139
|
+
const failFast = watchdogState?.sandboxFailFast;
|
|
138
140
|
let errorMessage;
|
|
139
141
|
if (watchdogTrigger && watchdogState?.triggeredReason) {
|
|
140
142
|
errorMessage = aborted
|
|
@@ -152,7 +154,7 @@ export async function runAgentProcess(options) {
|
|
|
152
154
|
wallClockCapMs: WATCHDOG_DEFAULTS.wallClockCapMs,
|
|
153
155
|
trigger: watchdogTrigger,
|
|
154
156
|
};
|
|
155
|
-
return { exitCode, errorMessage, signal, watchdog };
|
|
157
|
+
return { exitCode, errorMessage, signal, watchdog, failFast };
|
|
156
158
|
}
|
|
157
159
|
export async function stageManifestForSandbox(options) {
|
|
158
160
|
const { runtimeManifestPath } = options;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { ChildProcess } from "node:child_process";
|
|
2
2
|
import type { Writable } from "node:stream";
|
|
3
|
+
import type { DenialBackoffConfig } from "../../../configs/sandbox/types.js";
|
|
4
|
+
import { type SandboxFailFastInfo } from "../sandbox.js";
|
|
3
5
|
/**
|
|
4
6
|
* Watchdog types and constants for enforcing per-agent process timeouts.
|
|
5
7
|
*
|
|
6
8
|
* Watchdog enforcement prevents hung agent binaries from blocking the entire
|
|
7
9
|
* voratiq run pipeline by enforcing silence, wall-clock, and fatal pattern limits.
|
|
8
10
|
*/
|
|
9
|
-
export type WatchdogTrigger = "silence" | "wall-clock" | "fatal-pattern";
|
|
11
|
+
export type WatchdogTrigger = "silence" | "wall-clock" | "fatal-pattern" | "sandbox-denial";
|
|
10
12
|
export declare const WATCHDOG_DEFAULTS: {
|
|
11
13
|
readonly silenceTimeoutMs: number;
|
|
12
14
|
readonly wallClockCapMs: number;
|
|
@@ -24,7 +26,8 @@ export interface WatchdogResult {
|
|
|
24
26
|
}
|
|
25
27
|
export interface WatchdogOptions {
|
|
26
28
|
providerId: string;
|
|
27
|
-
|
|
29
|
+
denialBackoff?: DenialBackoffConfig;
|
|
30
|
+
onWatchdogTrigger?: (trigger: WatchdogTrigger, reason: string, failFast?: SandboxFailFastInfo) => void;
|
|
28
31
|
}
|
|
29
32
|
export declare function createWatchdog(child: ChildProcess, stderrStream: Writable, options: WatchdogOptions): WatchdogController;
|
|
30
33
|
export interface WatchdogController {
|
|
@@ -33,6 +36,7 @@ export interface WatchdogController {
|
|
|
33
36
|
getState: () => {
|
|
34
37
|
triggered: WatchdogTrigger | null;
|
|
35
38
|
triggeredReason: string | null;
|
|
39
|
+
sandboxFailFast?: SandboxFailFastInfo;
|
|
36
40
|
};
|
|
37
41
|
/** AbortSignal that fires after watchdog triggers and hard abort timeout passes. */
|
|
38
42
|
abortSignal: AbortSignal;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DenialBackoffTracker, parseSandboxDenialLine, resolveDenialBackoffConfig, } from "../sandbox.js";
|
|
1
2
|
export const WATCHDOG_DEFAULTS = {
|
|
2
3
|
silenceTimeoutMs: 15 * 60 * 1000,
|
|
3
4
|
wallClockCapMs: 120 * 60 * 1000,
|
|
@@ -12,6 +13,7 @@ export const FATAL_PATTERNS = new Map([
|
|
|
12
13
|
]);
|
|
13
14
|
export function createWatchdog(child, stderrStream, options) {
|
|
14
15
|
const { silenceTimeoutMs, wallClockCapMs, killGraceMs, hardAbortMs } = WATCHDOG_DEFAULTS;
|
|
16
|
+
const denialBackoff = resolveDenialBackoffConfig(options.denialBackoff);
|
|
15
17
|
const state = {
|
|
16
18
|
silenceTimer: null,
|
|
17
19
|
wallClockTimer: null,
|
|
@@ -20,6 +22,10 @@ export function createWatchdog(child, stderrStream, options) {
|
|
|
20
22
|
fatalPatternFirstSeen: null,
|
|
21
23
|
triggered: null,
|
|
22
24
|
triggeredReason: null,
|
|
25
|
+
sandboxFailFast: null,
|
|
26
|
+
denialBackoff: new DenialBackoffTracker(denialBackoff),
|
|
27
|
+
lineBuffer: "",
|
|
28
|
+
delayInProgress: false,
|
|
23
29
|
abortController: new AbortController(),
|
|
24
30
|
};
|
|
25
31
|
const fatalPatterns = FATAL_PATTERNS.get(options.providerId) ?? [];
|
|
@@ -54,17 +60,20 @@ export function createWatchdog(child, stderrStream, options) {
|
|
|
54
60
|
state.hardAbortTimer = null;
|
|
55
61
|
}
|
|
56
62
|
};
|
|
57
|
-
const triggerWatchdog = (trigger, reason) => {
|
|
63
|
+
const triggerWatchdog = (trigger, reason, failFast) => {
|
|
58
64
|
if (state.triggered) {
|
|
59
65
|
return;
|
|
60
66
|
}
|
|
61
67
|
state.triggered = trigger;
|
|
62
68
|
state.triggeredReason = reason;
|
|
69
|
+
if (failFast) {
|
|
70
|
+
state.sandboxFailFast = failFast;
|
|
71
|
+
}
|
|
63
72
|
clearAllTimers();
|
|
64
73
|
const banner = formatWatchdogBanner(trigger, reason);
|
|
65
74
|
stderrStream.write(banner);
|
|
66
75
|
if (options.onWatchdogTrigger) {
|
|
67
|
-
options.onWatchdogTrigger(trigger, reason);
|
|
76
|
+
options.onWatchdogTrigger(trigger, reason, failFast);
|
|
68
77
|
}
|
|
69
78
|
terminateProcess(child, state, { killGraceMs, hardAbortMs });
|
|
70
79
|
};
|
|
@@ -91,6 +100,60 @@ export function createWatchdog(child, stderrStream, options) {
|
|
|
91
100
|
resetSilenceTimer();
|
|
92
101
|
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
93
102
|
checkFatalPattern(text);
|
|
103
|
+
handleSandboxDenialText(text);
|
|
104
|
+
};
|
|
105
|
+
const handleSandboxDenialText = (text) => {
|
|
106
|
+
if (state.triggered) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
state.lineBuffer += text;
|
|
110
|
+
const lines = state.lineBuffer.split("\n");
|
|
111
|
+
state.lineBuffer = lines.pop() ?? "";
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
if (state.triggered) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (line.startsWith("Running: ")) {
|
|
117
|
+
state.denialBackoff.resetAll();
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const denial = parseSandboxDenialLine(line);
|
|
121
|
+
if (!denial) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const decision = state.denialBackoff.register(denial);
|
|
125
|
+
if (decision.action === "warn") {
|
|
126
|
+
stderrStream.write(`\n[SandboxBackoff: WARN] Repeated denial to ${denial.target} (count=${decision.count}).\n`);
|
|
127
|
+
}
|
|
128
|
+
else if (decision.action === "delay") {
|
|
129
|
+
stderrStream.write(`\n[SandboxBackoff: ERROR] Repeated denial to ${denial.target} (count=${decision.count}); delaying ${denialBackoff.delayMs}ms.\n`);
|
|
130
|
+
void applyBackoffDelay(child, state, denialBackoff);
|
|
131
|
+
}
|
|
132
|
+
else if (decision.action === "fail-fast") {
|
|
133
|
+
triggerWatchdog("sandbox-denial", `Sandbox: repeated denial to ${denial.target}, aborting to prevent resource exhaustion`, denial);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const applyBackoffDelay = async (child, state, config) => {
|
|
139
|
+
if (state.delayInProgress || state.triggered) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const pid = child.pid;
|
|
143
|
+
if (pid === undefined) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
state.delayInProgress = true;
|
|
147
|
+
const delayMs = config.delayMs;
|
|
148
|
+
killProcessGroup(pid, "SIGSTOP");
|
|
149
|
+
await new Promise((resolve) => {
|
|
150
|
+
const timer = setTimeout(resolve, delayMs);
|
|
151
|
+
timer.unref();
|
|
152
|
+
});
|
|
153
|
+
if (!state.triggered) {
|
|
154
|
+
killProcessGroup(pid, "SIGCONT");
|
|
155
|
+
}
|
|
156
|
+
state.delayInProgress = false;
|
|
94
157
|
};
|
|
95
158
|
resetSilenceTimer();
|
|
96
159
|
const wallClockMinutes = Math.round(wallClockCapMs / 60000);
|
|
@@ -115,6 +178,7 @@ export function createWatchdog(child, stderrStream, options) {
|
|
|
115
178
|
getState: () => ({
|
|
116
179
|
triggered: state.triggered,
|
|
117
180
|
triggeredReason: state.triggeredReason,
|
|
181
|
+
sandboxFailFast: state.sandboxFailFast ?? undefined,
|
|
118
182
|
}),
|
|
119
183
|
/** AbortSignal that fires after watchdog triggers and hard abort timeout passes. */
|
|
120
184
|
abortSignal: state.abortController.signal,
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { SandboxRuntimeConfig } from "@voratiq/sandbox-runtime";
|
|
2
|
+
import type { DenialBackoffConfig } from "../../configs/sandbox/types.js";
|
|
2
3
|
export type SandboxSettings = SandboxRuntimeConfig;
|
|
4
|
+
export type DenialOperationType = "network-connect" | "file-read" | "file-write";
|
|
5
|
+
export interface SandboxFailFastInfo {
|
|
6
|
+
operation: DenialOperationType;
|
|
7
|
+
target: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const DEFAULT_DENIAL_BACKOFF: DenialBackoffConfig;
|
|
3
10
|
export interface SandboxSettingsOptions {
|
|
4
11
|
sandboxHomePath: string;
|
|
5
12
|
workspacePath: string;
|
|
@@ -11,6 +18,21 @@ export interface SandboxSettingsOptions {
|
|
|
11
18
|
evalsPath: string;
|
|
12
19
|
}
|
|
13
20
|
export declare function generateSandboxSettings(options: SandboxSettingsOptions): SandboxSettings;
|
|
21
|
+
export declare function resolveDenialBackoffConfig(config: DenialBackoffConfig | undefined): DenialBackoffConfig;
|
|
22
|
+
export declare function parseSandboxDenialLine(line: string): SandboxFailFastInfo | undefined;
|
|
23
|
+
export type DenialBackoffAction = "none" | "warn" | "delay" | "fail-fast";
|
|
24
|
+
export interface DenialBackoffDecision {
|
|
25
|
+
action: DenialBackoffAction;
|
|
26
|
+
count: number;
|
|
27
|
+
info: SandboxFailFastInfo;
|
|
28
|
+
}
|
|
29
|
+
export declare class DenialBackoffTracker {
|
|
30
|
+
private readonly config;
|
|
31
|
+
private readonly byTarget;
|
|
32
|
+
constructor(config: DenialBackoffConfig);
|
|
33
|
+
resetAll(): void;
|
|
34
|
+
register(info: SandboxFailFastInfo, now?: number): DenialBackoffDecision;
|
|
35
|
+
}
|
|
14
36
|
export declare function writeSandboxSettings(sandboxSettingsPath: string, settings: SandboxSettings): Promise<void>;
|
|
15
37
|
export declare function resolveSrtBinary(cliRoot: string): string;
|
|
16
38
|
export declare function checkPlatformSupport(): void;
|
|
@@ -2,6 +2,14 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, isAbsolute } from "node:path";
|
|
3
3
|
import { loadSandboxProviderConfig } from "../../configs/sandbox/loader.js";
|
|
4
4
|
import { resolvePath } from "../../utils/path.js";
|
|
5
|
+
export const DEFAULT_DENIAL_BACKOFF = {
|
|
6
|
+
enabled: true,
|
|
7
|
+
warningThreshold: 2,
|
|
8
|
+
delayThreshold: 3,
|
|
9
|
+
delayMs: 5000,
|
|
10
|
+
failFastThreshold: 4,
|
|
11
|
+
windowMs: 120000,
|
|
12
|
+
};
|
|
5
13
|
export function generateSandboxSettings(options) {
|
|
6
14
|
const { sandboxHomePath, workspacePath, provider, root, sandboxSettingsPath, runtimePath, artifactsPath, evalsPath, } = options;
|
|
7
15
|
const providerConfig = loadSandboxProviderConfig({
|
|
@@ -41,6 +49,107 @@ export function generateSandboxSettings(options) {
|
|
|
41
49
|
},
|
|
42
50
|
};
|
|
43
51
|
}
|
|
52
|
+
export function resolveDenialBackoffConfig(config) {
|
|
53
|
+
if (!config) {
|
|
54
|
+
return { ...DEFAULT_DENIAL_BACKOFF };
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
enabled: typeof config.enabled === "boolean"
|
|
58
|
+
? config.enabled
|
|
59
|
+
: DEFAULT_DENIAL_BACKOFF.enabled,
|
|
60
|
+
warningThreshold: typeof config.warningThreshold === "number"
|
|
61
|
+
? config.warningThreshold
|
|
62
|
+
: DEFAULT_DENIAL_BACKOFF.warningThreshold,
|
|
63
|
+
delayThreshold: typeof config.delayThreshold === "number"
|
|
64
|
+
? config.delayThreshold
|
|
65
|
+
: DEFAULT_DENIAL_BACKOFF.delayThreshold,
|
|
66
|
+
delayMs: typeof config.delayMs === "number"
|
|
67
|
+
? config.delayMs
|
|
68
|
+
: DEFAULT_DENIAL_BACKOFF.delayMs,
|
|
69
|
+
failFastThreshold: typeof config.failFastThreshold === "number"
|
|
70
|
+
? config.failFastThreshold
|
|
71
|
+
: DEFAULT_DENIAL_BACKOFF.failFastThreshold,
|
|
72
|
+
windowMs: typeof config.windowMs === "number"
|
|
73
|
+
? config.windowMs
|
|
74
|
+
: DEFAULT_DENIAL_BACKOFF.windowMs,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function parseSandboxDenialLine(line) {
|
|
78
|
+
const trimmed = line.trim();
|
|
79
|
+
if (trimmed.length === 0) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const debugNetworkDeny = trimmed.match(/^\[SandboxDebug\]\s+(?:Denied by config rule|No matching config rule, denying|User denied):\s+([^\s]+)$/u);
|
|
83
|
+
if (debugNetworkDeny?.[1]) {
|
|
84
|
+
return { operation: "network-connect", target: debugNetworkDeny[1] };
|
|
85
|
+
}
|
|
86
|
+
const macosKernelDeny = trimmed.match(/\bSandbox:\s+deny(?:\(\d+\))?\s+([^\s]+)\s+(.+)$/u);
|
|
87
|
+
if (macosKernelDeny?.[1] && macosKernelDeny[2]) {
|
|
88
|
+
const op = macosKernelDeny[1].toLowerCase();
|
|
89
|
+
const target = macosKernelDeny[2].trim();
|
|
90
|
+
if (op.includes("network")) {
|
|
91
|
+
return { operation: "network-connect", target };
|
|
92
|
+
}
|
|
93
|
+
if (op.includes("file-read")) {
|
|
94
|
+
return { operation: "file-read", target };
|
|
95
|
+
}
|
|
96
|
+
if (op.includes("file-write")) {
|
|
97
|
+
return { operation: "file-write", target };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
export class DenialBackoffTracker {
|
|
103
|
+
config;
|
|
104
|
+
byTarget = new Map();
|
|
105
|
+
constructor(config) {
|
|
106
|
+
this.config = resolveDenialBackoffConfig(config);
|
|
107
|
+
}
|
|
108
|
+
resetAll() {
|
|
109
|
+
this.byTarget.clear();
|
|
110
|
+
}
|
|
111
|
+
register(info, now = Date.now()) {
|
|
112
|
+
const key = `${info.operation}:${info.target}`;
|
|
113
|
+
const windowMs = this.config.windowMs;
|
|
114
|
+
const existing = this.byTarget.get(key) ?? [];
|
|
115
|
+
const last = existing.length > 0 ? existing[existing.length - 1] : undefined;
|
|
116
|
+
const timestamps = last !== undefined && now - last > windowMs ? [] : [...existing];
|
|
117
|
+
timestamps.push(now);
|
|
118
|
+
const maxKept = Math.max(1, this.config.failFastThreshold);
|
|
119
|
+
while (timestamps.length > maxKept) {
|
|
120
|
+
timestamps.shift();
|
|
121
|
+
}
|
|
122
|
+
this.byTarget.set(key, timestamps);
|
|
123
|
+
const countInWindow = countWithinMs(timestamps, now, windowMs);
|
|
124
|
+
const countIn60 = countWithinMs(timestamps, now, 60_000);
|
|
125
|
+
const countIn30 = countWithinMs(timestamps, now, 30_000);
|
|
126
|
+
let action = "none";
|
|
127
|
+
if (this.config.enabled && countInWindow >= this.config.failFastThreshold) {
|
|
128
|
+
action = "fail-fast";
|
|
129
|
+
}
|
|
130
|
+
else if (this.config.enabled &&
|
|
131
|
+
countIn60 === this.config.delayThreshold) {
|
|
132
|
+
action = "delay";
|
|
133
|
+
}
|
|
134
|
+
else if (this.config.enabled &&
|
|
135
|
+
countIn30 === this.config.warningThreshold) {
|
|
136
|
+
action = "warn";
|
|
137
|
+
}
|
|
138
|
+
return { action, count: countInWindow, info };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function countWithinMs(timestamps, now, windowMs) {
|
|
142
|
+
let count = 0;
|
|
143
|
+
for (let i = timestamps.length - 1; i >= 0; i -= 1) {
|
|
144
|
+
if (now - timestamps[i] <= windowMs) {
|
|
145
|
+
count += 1;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return count;
|
|
152
|
+
}
|
|
44
153
|
function getDefaultSandboxWritePaths() {
|
|
45
154
|
return [];
|
|
46
155
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LoadSandboxConfigurationOptions, LoadSandboxProviderConfigOptions, SandboxConfig, SandboxProviderConfig } from "./types.js";
|
|
2
|
-
export type { LoadSandboxConfigurationOptions, LoadSandboxNetworkConfigOptions, LoadSandboxProviderConfigOptions, SandboxConfig, SandboxFilesystemConfig, SandboxNetworkConfig, SandboxProviderConfig, } from "./types.js";
|
|
2
|
+
export type { DenialBackoffConfig, LoadSandboxConfigurationOptions, LoadSandboxNetworkConfigOptions, LoadSandboxProviderConfigOptions, SandboxConfig, SandboxFilesystemConfig, SandboxNetworkConfig, SandboxProviderConfig, } from "./types.js";
|
|
3
3
|
export declare function loadSandboxConfiguration(options?: LoadSandboxConfigurationOptions): SandboxConfig;
|
|
4
4
|
export declare function loadSandboxProviderConfig(options: LoadSandboxProviderConfigOptions, providerIdOverride?: string): SandboxProviderConfig;
|
|
5
5
|
export declare function enableSandboxLoaderTestHooks(): void;
|
|
@@ -15,6 +15,14 @@ const DEFAULT_FILESYSTEM_CONFIG = {
|
|
|
15
15
|
denyRead: [],
|
|
16
16
|
denyWrite: [],
|
|
17
17
|
};
|
|
18
|
+
const DEFAULT_DENIAL_BACKOFF = {
|
|
19
|
+
enabled: true,
|
|
20
|
+
warningThreshold: 2,
|
|
21
|
+
delayThreshold: 3,
|
|
22
|
+
delayMs: 5000,
|
|
23
|
+
failFastThreshold: 4,
|
|
24
|
+
windowMs: 120000,
|
|
25
|
+
};
|
|
18
26
|
const sandboxConfigLoader = createConfigLoader({
|
|
19
27
|
resolveFilePath: (root, options) => resolveSandboxFilePath(root, options),
|
|
20
28
|
selectReadFile: (options) => options.readFile,
|
|
@@ -48,6 +56,7 @@ const sandboxConfigLoader = createConfigLoader({
|
|
|
48
56
|
providerId: canonical.id,
|
|
49
57
|
network: providerNetwork,
|
|
50
58
|
filesystem: providerFilesystem,
|
|
59
|
+
denialBackoff: mergeDenialBackoffConfig(DEFAULT_DENIAL_BACKOFF, override?.denialBackoff),
|
|
51
60
|
};
|
|
52
61
|
}
|
|
53
62
|
return {
|
|
@@ -57,6 +66,25 @@ const sandboxConfigLoader = createConfigLoader({
|
|
|
57
66
|
};
|
|
58
67
|
},
|
|
59
68
|
});
|
|
69
|
+
function mergeDenialBackoffConfig(base, override) {
|
|
70
|
+
if (!override) {
|
|
71
|
+
return { ...base };
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
enabled: typeof override.enabled === "boolean" ? override.enabled : base.enabled,
|
|
75
|
+
warningThreshold: typeof override.warningThreshold === "number"
|
|
76
|
+
? override.warningThreshold
|
|
77
|
+
: base.warningThreshold,
|
|
78
|
+
delayThreshold: typeof override.delayThreshold === "number"
|
|
79
|
+
? override.delayThreshold
|
|
80
|
+
: base.delayThreshold,
|
|
81
|
+
delayMs: typeof override.delayMs === "number" ? override.delayMs : base.delayMs,
|
|
82
|
+
failFastThreshold: typeof override.failFastThreshold === "number"
|
|
83
|
+
? override.failFastThreshold
|
|
84
|
+
: base.failFastThreshold,
|
|
85
|
+
windowMs: typeof override.windowMs === "number" ? override.windowMs : base.windowMs,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
60
88
|
function clearSandboxConfigurationCache() {
|
|
61
89
|
configCache.clear();
|
|
62
90
|
}
|
|
@@ -101,6 +129,7 @@ export function loadSandboxProviderConfig(options, providerIdOverride) {
|
|
|
101
129
|
providerId: providerConfig.providerId,
|
|
102
130
|
network: cloneNetworkConfig(providerConfig.network),
|
|
103
131
|
filesystem: cloneFilesystemConfig(providerConfig.filesystem),
|
|
132
|
+
denialBackoff: { ...providerConfig.denialBackoff },
|
|
104
133
|
};
|
|
105
134
|
}
|
|
106
135
|
const SANDBOX_LOADER_TEST_HOOKS = Symbol.for("voratiq.configs.sandbox.loader.testHooks");
|
|
@@ -162,6 +191,7 @@ function cloneSandboxConfig(config) {
|
|
|
162
191
|
providerId,
|
|
163
192
|
network: cloneNetworkConfig(providerConfig.network),
|
|
164
193
|
filesystem: cloneFilesystemConfig(providerConfig.filesystem),
|
|
194
|
+
denialBackoff: { ...providerConfig.denialBackoff },
|
|
165
195
|
};
|
|
166
196
|
}
|
|
167
197
|
return {
|
|
@@ -6,6 +6,14 @@ export declare const networkOverrideSchema: z.ZodObject<{
|
|
|
6
6
|
allowUnixSockets: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
7
7
|
allowAllUnixSockets: z.ZodOptional<z.ZodBoolean>;
|
|
8
8
|
}, z.core.$strict>;
|
|
9
|
+
export declare const denialBackoffOverrideSchema: z.ZodObject<{
|
|
10
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
warningThreshold: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
delayThreshold: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
delayMs: z.ZodOptional<z.ZodNumber>;
|
|
14
|
+
failFastThreshold: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
windowMs: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
}, z.core.$strict>;
|
|
9
17
|
export declare const filesystemOverrideSchema: z.ZodObject<{
|
|
10
18
|
allowWrite: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
11
19
|
denyRead: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
@@ -24,6 +32,14 @@ export declare const providerOverrideSchema: z.ZodObject<{
|
|
|
24
32
|
denyRead: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
25
33
|
denyWrite: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
26
34
|
}, z.core.$strict>>;
|
|
35
|
+
denialBackoff: z.ZodOptional<z.ZodObject<{
|
|
36
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
warningThreshold: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
delayThreshold: z.ZodOptional<z.ZodNumber>;
|
|
39
|
+
delayMs: z.ZodOptional<z.ZodNumber>;
|
|
40
|
+
failFastThreshold: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
windowMs: z.ZodOptional<z.ZodNumber>;
|
|
42
|
+
}, z.core.$strict>>;
|
|
27
43
|
allowedDomains: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
28
44
|
deniedDomains: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
29
45
|
allowLocalBinding: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -44,6 +60,14 @@ export declare const sandboxConfigSchema: z.ZodObject<{
|
|
|
44
60
|
denyRead: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
45
61
|
denyWrite: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
46
62
|
}, z.core.$strict>>;
|
|
63
|
+
denialBackoff: z.ZodOptional<z.ZodObject<{
|
|
64
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
65
|
+
warningThreshold: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
delayThreshold: z.ZodOptional<z.ZodNumber>;
|
|
67
|
+
delayMs: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
failFastThreshold: z.ZodOptional<z.ZodNumber>;
|
|
69
|
+
windowMs: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
}, z.core.$strict>>;
|
|
47
71
|
allowedDomains: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
48
72
|
deniedDomains: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
49
73
|
allowLocalBinding: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -52,6 +76,7 @@ export declare const sandboxConfigSchema: z.ZodObject<{
|
|
|
52
76
|
}, z.core.$strict>>;
|
|
53
77
|
}, z.core.$strip>;
|
|
54
78
|
export type NetworkOverride = z.infer<typeof networkOverrideSchema>;
|
|
79
|
+
export type DenialBackoffOverride = z.infer<typeof denialBackoffOverrideSchema>;
|
|
55
80
|
export type FilesystemOverride = z.infer<typeof filesystemOverrideSchema>;
|
|
56
81
|
export type ProviderOverride = z.infer<typeof providerOverrideSchema>;
|
|
57
82
|
export type SandboxOverrideDocument = z.infer<typeof sandboxConfigSchema>;
|
|
@@ -31,6 +31,16 @@ const networkOverrideShape = {
|
|
|
31
31
|
allowAllUnixSockets: z.boolean().optional(),
|
|
32
32
|
};
|
|
33
33
|
export const networkOverrideSchema = z.object(networkOverrideShape).strict();
|
|
34
|
+
export const denialBackoffOverrideSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
enabled: z.boolean().optional(),
|
|
37
|
+
warningThreshold: z.number().int().positive().optional(),
|
|
38
|
+
delayThreshold: z.number().int().positive().optional(),
|
|
39
|
+
delayMs: z.number().int().nonnegative().optional(),
|
|
40
|
+
failFastThreshold: z.number().int().positive().optional(),
|
|
41
|
+
windowMs: z.number().int().positive().optional(),
|
|
42
|
+
})
|
|
43
|
+
.strict();
|
|
34
44
|
export const filesystemOverrideSchema = z
|
|
35
45
|
.object({
|
|
36
46
|
allowWrite: z
|
|
@@ -48,6 +58,7 @@ export const providerOverrideSchema = z
|
|
|
48
58
|
...networkOverrideShape,
|
|
49
59
|
network: networkOverrideSchema.optional(),
|
|
50
60
|
filesystem: filesystemOverrideSchema.optional(),
|
|
61
|
+
denialBackoff: denialBackoffOverrideSchema.optional(),
|
|
51
62
|
})
|
|
52
63
|
.strict();
|
|
53
64
|
export const sandboxConfigSchema = z.object({
|
|
@@ -5,6 +5,14 @@ export interface SandboxNetworkConfig {
|
|
|
5
5
|
allowUnixSockets?: string[];
|
|
6
6
|
allowAllUnixSockets?: boolean;
|
|
7
7
|
}
|
|
8
|
+
export interface DenialBackoffConfig {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
warningThreshold: number;
|
|
11
|
+
delayThreshold: number;
|
|
12
|
+
delayMs: number;
|
|
13
|
+
failFastThreshold: number;
|
|
14
|
+
windowMs: number;
|
|
15
|
+
}
|
|
8
16
|
export interface SandboxFilesystemConfig {
|
|
9
17
|
allowWrite: string[];
|
|
10
18
|
denyRead: string[];
|
|
@@ -14,6 +22,7 @@ export interface SandboxProviderConfig {
|
|
|
14
22
|
providerId: string;
|
|
15
23
|
network: SandboxNetworkConfig;
|
|
16
24
|
filesystem: SandboxFilesystemConfig;
|
|
25
|
+
denialBackoff: DenialBackoffConfig;
|
|
17
26
|
}
|
|
18
27
|
export interface SandboxConfig {
|
|
19
28
|
filePath: string;
|
package/dist/records/types.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export declare const watchdogMetadataSchema: z.ZodObject<{
|
|
|
24
24
|
silence: "silence";
|
|
25
25
|
"wall-clock": "wall-clock";
|
|
26
26
|
"fatal-pattern": "fatal-pattern";
|
|
27
|
+
"sandbox-denial": "sandbox-denial";
|
|
27
28
|
}>>;
|
|
28
29
|
}, z.core.$strip>;
|
|
29
30
|
export type WatchdogMetadata = z.infer<typeof watchdogMetadataSchema>;
|
|
@@ -107,8 +108,16 @@ export declare const agentInvocationRecordSchema: z.ZodObject<{
|
|
|
107
108
|
silence: "silence";
|
|
108
109
|
"wall-clock": "wall-clock";
|
|
109
110
|
"fatal-pattern": "fatal-pattern";
|
|
111
|
+
"sandbox-denial": "sandbox-denial";
|
|
110
112
|
}>>;
|
|
111
113
|
}, z.core.$strip>>;
|
|
114
|
+
failFastTriggered: z.ZodOptional<z.ZodBoolean>;
|
|
115
|
+
failFastTarget: z.ZodOptional<z.ZodString>;
|
|
116
|
+
failFastOperation: z.ZodOptional<z.ZodEnum<{
|
|
117
|
+
"network-connect": "network-connect";
|
|
118
|
+
"file-read": "file-read";
|
|
119
|
+
"file-write": "file-write";
|
|
120
|
+
}>>;
|
|
112
121
|
}, z.core.$strip>;
|
|
113
122
|
export type AgentInvocationRecord = z.infer<typeof agentInvocationRecordSchema>;
|
|
114
123
|
export declare const applyStatusSchema: z.ZodObject<{
|
|
@@ -189,8 +198,16 @@ export declare const runRecordSchema: z.ZodObject<{
|
|
|
189
198
|
silence: "silence";
|
|
190
199
|
"wall-clock": "wall-clock";
|
|
191
200
|
"fatal-pattern": "fatal-pattern";
|
|
201
|
+
"sandbox-denial": "sandbox-denial";
|
|
192
202
|
}>>;
|
|
193
203
|
}, z.core.$strip>>;
|
|
204
|
+
failFastTriggered: z.ZodOptional<z.ZodBoolean>;
|
|
205
|
+
failFastTarget: z.ZodOptional<z.ZodString>;
|
|
206
|
+
failFastOperation: z.ZodOptional<z.ZodEnum<{
|
|
207
|
+
"network-connect": "network-connect";
|
|
208
|
+
"file-read": "file-read";
|
|
209
|
+
"file-write": "file-write";
|
|
210
|
+
}>>;
|
|
194
211
|
}, z.core.$strip>>;
|
|
195
212
|
applyStatus: z.ZodOptional<z.ZodObject<{
|
|
196
213
|
agentId: z.ZodString;
|
package/dist/records/types.js
CHANGED
|
@@ -42,7 +42,17 @@ const CHAT_ARTIFACT_FORMATS = [
|
|
|
42
42
|
"json",
|
|
43
43
|
"jsonl",
|
|
44
44
|
];
|
|
45
|
-
const WATCHDOG_TRIGGERS = [
|
|
45
|
+
const WATCHDOG_TRIGGERS = [
|
|
46
|
+
"silence",
|
|
47
|
+
"wall-clock",
|
|
48
|
+
"fatal-pattern",
|
|
49
|
+
"sandbox-denial",
|
|
50
|
+
];
|
|
51
|
+
const FAIL_FAST_OPERATIONS = [
|
|
52
|
+
"network-connect",
|
|
53
|
+
"file-read",
|
|
54
|
+
"file-write",
|
|
55
|
+
];
|
|
46
56
|
export const watchdogMetadataSchema = z.object({
|
|
47
57
|
/** Silence timeout in milliseconds that was enforced. */
|
|
48
58
|
silenceTimeoutMs: z.number(),
|
|
@@ -82,6 +92,9 @@ export const agentInvocationRecordSchema = z
|
|
|
82
92
|
warnings: z.array(z.string()).optional(),
|
|
83
93
|
diffStatistics: z.string().optional(),
|
|
84
94
|
watchdog: watchdogMetadataSchema.optional(),
|
|
95
|
+
failFastTriggered: z.boolean().optional(),
|
|
96
|
+
failFastTarget: z.string().optional(),
|
|
97
|
+
failFastOperation: z.enum(FAIL_FAST_OPERATIONS).optional(),
|
|
85
98
|
})
|
|
86
99
|
.superRefine((data, ctx) => {
|
|
87
100
|
if (IN_PROGRESS_AGENT_STATUSES.includes(data.status)) {
|
|
@@ -110,6 +123,22 @@ export const agentInvocationRecordSchema = z
|
|
|
110
123
|
});
|
|
111
124
|
}
|
|
112
125
|
}
|
|
126
|
+
if (data.failFastTriggered) {
|
|
127
|
+
if (!data.failFastTarget) {
|
|
128
|
+
ctx.addIssue({
|
|
129
|
+
code: z.ZodIssueCode.custom,
|
|
130
|
+
path: ["failFastTarget"],
|
|
131
|
+
message: "failFastTarget is required when failFastTriggered is true",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (!data.failFastOperation) {
|
|
135
|
+
ctx.addIssue({
|
|
136
|
+
code: z.ZodIssueCode.custom,
|
|
137
|
+
path: ["failFastOperation"],
|
|
138
|
+
message: "failFastOperation is required when failFastTriggered is true",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
113
142
|
});
|
|
114
143
|
export const applyStatusSchema = z.object({
|
|
115
144
|
agentId: agentIdSchema,
|