@cruxy/cli 0.12.0 → 0.13.0
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/cli/commands/run.js +19 -1
- package/dist/cli/session-factory.d.ts +2 -1
- package/dist/cli/session-factory.js +10 -3
- package/dist/config/schema.d.ts +123 -0
- package/dist/config/schema.js +40 -0
- package/dist/errors/constructors.d.ts +21 -0
- package/dist/errors/constructors.js +58 -0
- package/dist/errors/types.d.ts +5 -0
- package/dist/errors/types.js +11 -0
- package/dist/sandbox/detect.d.ts +22 -0
- package/dist/sandbox/detect.js +67 -0
- package/dist/sandbox/docker-runtime.d.ts +32 -0
- package/dist/sandbox/docker-runtime.js +263 -0
- package/dist/sandbox/index.d.ts +7 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/policy.d.ts +17 -0
- package/dist/sandbox/policy.js +90 -0
- package/dist/sandbox/service.d.ts +57 -0
- package/dist/sandbox/service.js +64 -0
- package/dist/sandbox/types.d.ts +114 -0
- package/dist/sandbox/types.js +17 -0
- package/dist/subagent/orchestrator.d.ts +7 -0
- package/dist/subagent/orchestrator.js +1 -0
- package/dist/testing/run-tests-tool.d.ts +5 -1
- package/dist/testing/run-tests-tool.js +8 -1
- package/dist/testing/sandbox-runner.d.ts +16 -0
- package/dist/testing/sandbox-runner.js +47 -0
- package/dist/tools/shell/run-command.js +35 -1
- package/dist/tools/types.d.ts +10 -0
- package/package.json +1 -1
|
@@ -32,7 +32,11 @@ declare const parameters: z.ZodObject<{
|
|
|
32
32
|
command?: string | undefined;
|
|
33
33
|
}>;
|
|
34
34
|
export interface RunTestsToolDeps {
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Execution seam (tests inject a fake). When unset, the runner is chosen per
|
|
37
|
+
* run from `ctx.sandbox`: present → {@link SandboxTestRunner} (run in the box,
|
|
38
|
+
* C.16), absent → host {@link CommandTestRunner} — unchanged.
|
|
39
|
+
*/
|
|
36
40
|
runner?: TestRunner;
|
|
37
41
|
/** Detection seam (defaults to config + package.json detection). */
|
|
38
42
|
detect?: (ctx: ToolContext) => TestCommand | null;
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { ErrorCode } from "../errors/index.js";
|
|
3
3
|
import { detectTestCommand } from "./detect.js";
|
|
4
4
|
import { CommandTestRunner } from "./runner.js";
|
|
5
|
+
import { SandboxTestRunner } from "./sandbox-runner.js";
|
|
5
6
|
/**
|
|
6
7
|
* The `run_tests` tool (C.13): execute the project's test suite and return a
|
|
7
8
|
* structured result the model can iterate on (edit → re-run → repeat). The
|
|
@@ -58,7 +59,6 @@ function renderResult(result, command, iteration) {
|
|
|
58
59
|
}
|
|
59
60
|
/** Build the `run_tests` tool. One instance = one session's iteration budget. */
|
|
60
61
|
export function makeRunTestsTool(deps = {}) {
|
|
61
|
-
const runner = deps.runner ?? new CommandTestRunner();
|
|
62
62
|
const detect = deps.detect ??
|
|
63
63
|
((ctx) => detectTestCommand(ctx.cwd, ctx.config));
|
|
64
64
|
const budget = new TestIterationBudget();
|
|
@@ -104,6 +104,13 @@ export function makeRunTestsTool(deps = {}) {
|
|
|
104
104
|
error: decision.feedback ?? "test run denied by the user",
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
|
+
// Substrate chosen by ctx.sandbox (C.16): in the box when enabled, else
|
|
108
|
+
// host — identical result shape either way. An explicit deps.runner
|
|
109
|
+
// (tests) always wins. A sandbox that can't run throws (fail loud).
|
|
110
|
+
const runner = deps.runner ??
|
|
111
|
+
(ctx.sandbox
|
|
112
|
+
? new SandboxTestRunner(ctx.sandbox)
|
|
113
|
+
: new CommandTestRunner());
|
|
107
114
|
const result = await runner.run(resolved.command, {
|
|
108
115
|
cwd: ctx.cwd,
|
|
109
116
|
timeoutMs: ctx.config.shell.timeoutMs,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SandboxService } from "../sandbox/index.js";
|
|
2
|
+
import type { TestRunner, TestRunOptions, TestRunResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* A {@link TestRunner} that runs the suite inside the C.16 sandbox instead of
|
|
5
|
+
* on the host. It maps the neutral sandbox {@link ExecResult} onto the exact
|
|
6
|
+
* same {@link TestRunResult} the host {@link CommandTestRunner} produces — so
|
|
7
|
+
* honest-green (`passed = exitCode === 0`), tail-biased capture, and the
|
|
8
|
+
* timeout note all hold identically inside the box. A container-start failure
|
|
9
|
+
* is NOT swallowed into a result: `sandbox.exec` throws a coded error that
|
|
10
|
+
* propagates (fail loud), never a fabricated pass.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SandboxTestRunner implements TestRunner {
|
|
13
|
+
private readonly sandbox;
|
|
14
|
+
constructor(sandbox: SandboxService);
|
|
15
|
+
run(command: string, opts: TestRunOptions): Promise<TestRunResult>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { parseFailures } from "./parse.js";
|
|
2
|
+
/**
|
|
3
|
+
* A {@link TestRunner} that runs the suite inside the C.16 sandbox instead of
|
|
4
|
+
* on the host. It maps the neutral sandbox {@link ExecResult} onto the exact
|
|
5
|
+
* same {@link TestRunResult} the host {@link CommandTestRunner} produces — so
|
|
6
|
+
* honest-green (`passed = exitCode === 0`), tail-biased capture, and the
|
|
7
|
+
* timeout note all hold identically inside the box. A container-start failure
|
|
8
|
+
* is NOT swallowed into a result: `sandbox.exec` throws a coded error that
|
|
9
|
+
* propagates (fail loud), never a fabricated pass.
|
|
10
|
+
*/
|
|
11
|
+
export class SandboxTestRunner {
|
|
12
|
+
sandbox;
|
|
13
|
+
constructor(sandbox) {
|
|
14
|
+
this.sandbox = sandbox;
|
|
15
|
+
}
|
|
16
|
+
async run(command, opts) {
|
|
17
|
+
const result = await this.sandbox.exec(command, {
|
|
18
|
+
cwd: opts.cwd,
|
|
19
|
+
timeoutMs: opts.timeoutMs,
|
|
20
|
+
maxOutputBytes: opts.captureBytes,
|
|
21
|
+
capture: "tail", // failures live at the end of test output
|
|
22
|
+
});
|
|
23
|
+
if (result.timedOut) {
|
|
24
|
+
return {
|
|
25
|
+
passed: false,
|
|
26
|
+
exitCode: null,
|
|
27
|
+
durationMs: result.durationMs,
|
|
28
|
+
failures: [],
|
|
29
|
+
output: result.output +
|
|
30
|
+
`\n… [test run timed out after ${opts.timeoutMs}ms and was killed]`,
|
|
31
|
+
outputTruncated: result.outputTruncated,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// THE honest-green rule, unchanged by the substrate: exit 0 means passed.
|
|
35
|
+
const passed = result.exitCode === 0;
|
|
36
|
+
const parsed = passed ? { failures: [] } : parseFailures(result.output);
|
|
37
|
+
return {
|
|
38
|
+
passed,
|
|
39
|
+
exitCode: result.exitCode,
|
|
40
|
+
durationMs: result.durationMs,
|
|
41
|
+
...(parsed.total !== undefined ? { total: parsed.total } : {}),
|
|
42
|
+
failures: parsed.failures,
|
|
43
|
+
output: result.output,
|
|
44
|
+
outputTruncated: result.outputTruncated,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -26,9 +26,43 @@ export const runCommandTool = {
|
|
|
26
26
|
error: decision.feedback ?? "command denied by the user",
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
// Substrate is chosen SOLELY by ctx.sandbox: present → run in the box (C.16),
|
|
30
|
+
// never on the host; absent → host, unchanged. There is no fallback path — a
|
|
31
|
+
// sandbox that can't run throws a coded error (see runSandboxed) rather than
|
|
32
|
+
// silently reaching runBounded.
|
|
33
|
+
return ctx.sandbox
|
|
34
|
+
? runSandboxed(input.command, ctx)
|
|
35
|
+
: runBounded(input.command, ctx);
|
|
30
36
|
},
|
|
31
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Run inside the sandbox and map the neutral ExecResult onto the IDENTICAL
|
|
40
|
+
* ToolResult the host path produces — same "exit code N" framing, same
|
|
41
|
+
* truncation note, same timeout message — so the tool is substrate-agnostic.
|
|
42
|
+
* A container-start / image failure throws a coded CruxyError from
|
|
43
|
+
* `sandbox.exec` and propagates; we deliberately do not catch it (fail loud).
|
|
44
|
+
*/
|
|
45
|
+
function runSandboxed(command, ctx) {
|
|
46
|
+
const { timeoutMs, maxOutputBytes } = ctx.config.shell;
|
|
47
|
+
return ctx
|
|
48
|
+
.sandbox.exec(command, {
|
|
49
|
+
cwd: ctx.cwd,
|
|
50
|
+
timeoutMs,
|
|
51
|
+
maxOutputBytes,
|
|
52
|
+
capture: "head",
|
|
53
|
+
})
|
|
54
|
+
.then((result) => {
|
|
55
|
+
if (result.timedOut) {
|
|
56
|
+
return { ok: false, error: `timed out after ${timeoutMs}ms` };
|
|
57
|
+
}
|
|
58
|
+
const exit = result.exitCode ?? "unknown";
|
|
59
|
+
let output = `exit code ${exit}\n${result.output}`;
|
|
60
|
+
if (result.outputTruncated) {
|
|
61
|
+
output += `\n… [output truncated at ${maxOutputBytes} bytes]`;
|
|
62
|
+
}
|
|
63
|
+
return { ok: true, output };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
32
66
|
/** Spawn the command, capture bounded output, and enforce the timeout. */
|
|
33
67
|
function runBounded(command, ctx) {
|
|
34
68
|
const { timeoutMs, maxOutputBytes } = ctx.config.shell;
|
package/dist/tools/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { z, ZodTypeAny } from "zod";
|
|
2
2
|
import type { CruxyConfig } from "../config/index.js";
|
|
3
3
|
import type { ApprovalDecision } from "../approval/types.js";
|
|
4
|
+
import type { SandboxService } from "../sandbox/index.js";
|
|
4
5
|
import type { logger } from "../utils/logger.js";
|
|
5
6
|
/** The leveled logger instance shared across the CLI. */
|
|
6
7
|
type Logger = typeof logger;
|
|
@@ -131,6 +132,15 @@ export interface ToolContext {
|
|
|
131
132
|
* tools never call this.
|
|
132
133
|
*/
|
|
133
134
|
requestApproval(action: ApproveAction): Promise<ApprovalDecision>;
|
|
135
|
+
/**
|
|
136
|
+
* Isolation substrate for the shell + test tools (C.16). Present ONLY when
|
|
137
|
+
* the sandbox is enabled; when set, `run_command`/`run_tests` execute the
|
|
138
|
+
* approved command inside the container and NEVER on the host. Its mere
|
|
139
|
+
* presence is the switch — there is no host fallback once it is set (an
|
|
140
|
+
* unavailable runtime fails loud at construction, before this is ever
|
|
141
|
+
* populated). Absent → host execution, unchanged.
|
|
142
|
+
*/
|
|
143
|
+
sandbox?: SandboxService;
|
|
134
144
|
}
|
|
135
145
|
/**
|
|
136
146
|
* The one interface every tool implements. `parameters` is a zod schema; it both
|