@webpresso/agent-kit 3.3.1 → 3.3.2
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/catalog/agent/rules/pre-implementation.md +15 -8
- package/catalog/agent/skills/ultragoal/SKILL.md +5 -18
- package/dist/esm/blueprint/core/validation/task-blocks.js +7 -1
- package/dist/esm/blueprint/markdown/task-heading.d.ts +21 -0
- package/dist/esm/blueprint/markdown/task-heading.js +25 -0
- package/dist/esm/blueprint/trust/command-runner.d.ts +40 -0
- package/dist/esm/blueprint/trust/command-runner.js +265 -41
- package/dist/esm/build/cli-mcp-parity.js +5 -0
- package/dist/esm/ci/native-session-memory-cache.d.ts +2 -1
- package/dist/esm/ci/native-session-memory-cache.js +2 -1
- package/dist/esm/ci/release-progress.d.ts +37 -0
- package/dist/esm/ci/release-progress.js +139 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +9 -0
- package/dist/esm/cli/commands/blueprint/mutations.js +2 -3
- package/dist/esm/cli/commands/release-progress.d.ts +53 -0
- package/dist/esm/cli/commands/release-progress.js +155 -0
- package/dist/esm/cli/commands/review.js +74 -11
- package/dist/esm/cli/utils.d.ts +8 -1
- package/dist/esm/cli/utils.js +12 -2
- package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -2
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +2 -1
- package/dist/esm/mcp/blueprint/handlers/task-advance.js +2 -12
- package/dist/esm/review/delivery-verifier.d.ts +33 -2
- package/dist/esm/review/delivery-verifier.js +130 -24
- package/dist/esm/review/execution/review-checkout.d.ts +1 -0
- package/dist/esm/review/execution/review-checkout.js +386 -23
- package/dist/esm/review/execution/sandbox/adapter.d.ts +37 -0
- package/dist/esm/review/execution/sandbox/adapter.js +39 -0
- package/dist/esm/review/execution/sandbox/env.d.ts +12 -0
- package/dist/esm/review/execution/sandbox/env.js +37 -0
- package/dist/esm/review/execution/sandbox/index.d.ts +5 -0
- package/dist/esm/review/execution/sandbox/index.js +4 -0
- package/dist/esm/review/execution/sandbox/policy.d.ts +19 -0
- package/dist/esm/review/execution/sandbox/policy.js +95 -0
- package/dist/esm/review/execution/sandbox/probe.d.ts +20 -0
- package/dist/esm/review/execution/sandbox/probe.js +97 -0
- package/dist/esm/review/execution/sandbox/shell-quote.d.ts +14 -0
- package/dist/esm/review/execution/sandbox/shell-quote.js +18 -0
- package/dist/esm/review/execution/sandbox/tmpdir.d.ts +9 -0
- package/dist/esm/review/execution/sandbox/tmpdir.js +36 -0
- package/dist/esm/review/execution/sandbox/types.d.ts +31 -0
- package/dist/esm/review/execution/sandbox/types.js +6 -0
- package/dist/esm/review/execution/supervisor.js +44 -5
- package/dist/esm/review/execution/types.d.ts +2 -0
- package/package.json +13 -12
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readdirSync, realpathSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* srt always unions these host-global persistent paths into any custom policy
|
|
5
|
+
* and redirects the child `TMPDIR` to `/tmp/claude`. They are shared across
|
|
6
|
+
* concurrent runs (a corruption surface), so we deny them: `denyWrite` takes
|
|
7
|
+
* precedence over `allowWrite`, removing them from the child's writable set.
|
|
8
|
+
* The child's `TMPDIR` is redirected into the per-run dir via
|
|
9
|
+
* `CLAUDE_CODE_TMPDIR` (see `withSandboxTmpdir`).
|
|
10
|
+
*/
|
|
11
|
+
const SRT_DEFAULT_WRITABLE_DENY = [
|
|
12
|
+
"/tmp/claude",
|
|
13
|
+
"/private/tmp/claude",
|
|
14
|
+
"~/.npm/_logs",
|
|
15
|
+
"~/.claude/debug",
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Defense-in-depth credential-store read denies (srt expands `~`). Reads
|
|
19
|
+
* elsewhere stay allowed (default srt semantics); network-deny is the hard
|
|
20
|
+
* exfiltration barrier. The caller repo root and the pnpm store are
|
|
21
|
+
* deliberately NOT denied — the reconstruction and the gate toolchain must read
|
|
22
|
+
* dependencies from them.
|
|
23
|
+
*/
|
|
24
|
+
const CREDENTIAL_DENY_READ = [
|
|
25
|
+
"~/.ssh",
|
|
26
|
+
"~/.aws",
|
|
27
|
+
"~/.netrc",
|
|
28
|
+
"~/.npmrc",
|
|
29
|
+
"~/.docker",
|
|
30
|
+
"~/.kube",
|
|
31
|
+
"~/.azure",
|
|
32
|
+
"~/.gnupg",
|
|
33
|
+
"~/.config/gh",
|
|
34
|
+
"~/.config/gcloud",
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Common `.env*` names denied under the caller root even if the directory
|
|
38
|
+
* cannot be enumerated. srt takes no globs, so `callerEnvDenyReads` expands the
|
|
39
|
+
* real `.env*` set at build time; this is only the fallback floor.
|
|
40
|
+
*/
|
|
41
|
+
const CALLER_ENV_FILES = [
|
|
42
|
+
".env",
|
|
43
|
+
".env.local",
|
|
44
|
+
".env.development",
|
|
45
|
+
".env.production",
|
|
46
|
+
".env.test",
|
|
47
|
+
];
|
|
48
|
+
// Literally `.env*`: `.env`, `.env.local`, but ALSO `.envrc`, `.environment`,
|
|
49
|
+
// and any other secret-bearing name that begins `.env`.
|
|
50
|
+
const ENV_FILE_PATTERN = /^\.env/u;
|
|
51
|
+
/**
|
|
52
|
+
* Deny-read every `.env*` file actually present in the caller root — not just a
|
|
53
|
+
* literal five — because a hostile gate could otherwise read `.env.staging`,
|
|
54
|
+
* `.env.secret`, `.env.production.local`, etc. Both the lexical path and (for a
|
|
55
|
+
* symlinked secret) its realpath target are denied. Falls back to the common
|
|
56
|
+
* literal set if the caller root cannot be listed.
|
|
57
|
+
*/
|
|
58
|
+
function callerEnvDenyReads(projectRoot) {
|
|
59
|
+
const denied = new Set(CALLER_ENV_FILES.map((file) => join(projectRoot, file)));
|
|
60
|
+
try {
|
|
61
|
+
for (const entry of readdirSync(projectRoot, { withFileTypes: true })) {
|
|
62
|
+
if (!ENV_FILE_PATTERN.test(entry.name))
|
|
63
|
+
continue;
|
|
64
|
+
const lexical = join(projectRoot, entry.name);
|
|
65
|
+
denied.add(lexical);
|
|
66
|
+
try {
|
|
67
|
+
denied.add(realpathSync(lexical));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Broken/unreadable link — the lexical deny still applies.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Caller root not enumerable — the literal fallback floor still applies.
|
|
76
|
+
}
|
|
77
|
+
return [...denied];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Builds the srt filesystem/network policy for one gate: writes confined to the
|
|
81
|
+
* checkout + per-run tmp + the shared per-process scratch dir; the checkout
|
|
82
|
+
* `.git` and srt's default-writable set denied; network fully denied.
|
|
83
|
+
*/
|
|
84
|
+
export function buildGateSandboxConfig(policy) {
|
|
85
|
+
return {
|
|
86
|
+
network: { allowedDomains: [], deniedDomains: [] },
|
|
87
|
+
filesystem: {
|
|
88
|
+
// The per-run sandbox temp dir (CLAUDE_CODE_TMPDIR) lives under runTmpDir,
|
|
89
|
+
// so allowing the run dir covers it — no process-global shared scratch.
|
|
90
|
+
allowWrite: [policy.checkoutRoot, policy.runTmpDir],
|
|
91
|
+
denyWrite: [join(policy.checkoutRoot, ".git"), ...SRT_DEFAULT_WRITABLE_DENY],
|
|
92
|
+
denyRead: [...CREDENTIAL_DENY_READ, ...callerEnvDenyReads(policy.projectRoot)],
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { type SrtModule } from "#review/execution/sandbox/adapter.js";
|
|
3
|
+
import type { SandboxAvailability } from "#review/execution/sandbox/types.js";
|
|
4
|
+
export type ProbeOptions = {
|
|
5
|
+
readonly platform?: NodeJS.Platform;
|
|
6
|
+
readonly loadSrtModule?: () => Promise<SrtModule>;
|
|
7
|
+
readonly spawn?: typeof spawnSync;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Probes the host for a usable OS sandbox backend. Fail-closed: any missing
|
|
11
|
+
* prerequisite yields `{ kind: "none", reason }` with operator remediation —
|
|
12
|
+
* never a silent unsandboxed pass. Memoized per process unless `options` are
|
|
13
|
+
* supplied (tests). Order: platform gate → srt resolvable → srt platform
|
|
14
|
+
* support → dependency check → a real smoke-run of a wrapped `/usr/bin/true`
|
|
15
|
+
* (catches the Ubuntu 24.04 AppArmor userns restriction that the dependency
|
|
16
|
+
* check cannot see), with `cleanupAfterCommand` in a `finally`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveSandboxAvailability(options?: ProbeOptions): Promise<SandboxAvailability>;
|
|
19
|
+
/** Test-only: clears the per-process memo. */
|
|
20
|
+
export declare function resetSandboxAvailabilityCache(): void;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { loadSrt } from "#review/execution/sandbox/adapter.js";
|
|
6
|
+
import { buildGateSandboxConfig } from "#review/execution/sandbox/policy.js";
|
|
7
|
+
import { withSandboxTmpdir } from "#review/execution/sandbox/tmpdir.js";
|
|
8
|
+
const LINUX_REMEDIATION = "install bubblewrap, socat, and ripgrep, and (on Ubuntu 24.04+) allow unprivileged user namespaces: `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`";
|
|
9
|
+
const MODULE_REMEDIATION = "run `wp review` from the source or an installed runtime (not the bun-compiled binary), where @anthropic-ai/sandbox-runtime resolves";
|
|
10
|
+
const OVERRIDE_NOTE = "or set WP_REVIEW_UNSANDBOXED_GATES=1 to run gates unsandboxed (recorded as disabled-by-operator)";
|
|
11
|
+
let cached;
|
|
12
|
+
/**
|
|
13
|
+
* Probes the host for a usable OS sandbox backend. Fail-closed: any missing
|
|
14
|
+
* prerequisite yields `{ kind: "none", reason }` with operator remediation —
|
|
15
|
+
* never a silent unsandboxed pass. Memoized per process unless `options` are
|
|
16
|
+
* supplied (tests). Order: platform gate → srt resolvable → srt platform
|
|
17
|
+
* support → dependency check → a real smoke-run of a wrapped `/usr/bin/true`
|
|
18
|
+
* (catches the Ubuntu 24.04 AppArmor userns restriction that the dependency
|
|
19
|
+
* check cannot see), with `cleanupAfterCommand` in a `finally`.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveSandboxAvailability(options) {
|
|
22
|
+
if (options === undefined && cached !== undefined)
|
|
23
|
+
return cached;
|
|
24
|
+
const result = await probe(options);
|
|
25
|
+
if (options === undefined)
|
|
26
|
+
cached = result;
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
/** Test-only: clears the per-process memo. */
|
|
30
|
+
export function resetSandboxAvailabilityCache() {
|
|
31
|
+
cached = undefined;
|
|
32
|
+
}
|
|
33
|
+
async function probe(options) {
|
|
34
|
+
const platform = options?.platform ?? process.platform;
|
|
35
|
+
if (platform !== "darwin" && platform !== "linux") {
|
|
36
|
+
return none(`OS sandbox is only supported on macOS and Linux (host platform: ${platform}); ${OVERRIDE_NOTE}`);
|
|
37
|
+
}
|
|
38
|
+
let srt;
|
|
39
|
+
try {
|
|
40
|
+
srt = options?.loadSrtModule ? await options.loadSrtModule() : await loadSrt();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return none(`@anthropic-ai/sandbox-runtime is not loadable (${describe(error)}); ${MODULE_REMEDIATION}, ${OVERRIDE_NOTE}`);
|
|
44
|
+
}
|
|
45
|
+
const { SandboxManager } = srt;
|
|
46
|
+
if (!SandboxManager.isSupportedPlatform()) {
|
|
47
|
+
return none(`the sandbox runtime reports this platform unsupported; ${OVERRIDE_NOTE}`);
|
|
48
|
+
}
|
|
49
|
+
const deps = SandboxManager.checkDependencies();
|
|
50
|
+
if (deps.errors.length > 0) {
|
|
51
|
+
return none(`sandbox dependencies missing (${deps.errors.join("; ")}); ${LINUX_REMEDIATION}, ${OVERRIDE_NOTE}`);
|
|
52
|
+
}
|
|
53
|
+
return smokeTest(SandboxManager, options?.spawn ?? spawnSync);
|
|
54
|
+
}
|
|
55
|
+
async function smokeTest(manager, spawn) {
|
|
56
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "wp-sandbox-probe-")));
|
|
57
|
+
const outcome = await runSmoke(manager, spawn, dir);
|
|
58
|
+
// Cleanup is part of the backend's health: if it cannot balance its own
|
|
59
|
+
// active-sandbox counter / mounts, the backend is NOT usable. Fail the probe
|
|
60
|
+
// (kind: none) rather than caching a leaky backend that silently litters.
|
|
61
|
+
let result = outcome;
|
|
62
|
+
try {
|
|
63
|
+
await manager.cleanupAfterCommand();
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
result = none(`the sandbox backend cleanup did not complete (${describe(error)}); ${OVERRIDE_NOTE}`);
|
|
67
|
+
}
|
|
68
|
+
rmSync(dir, { recursive: true, force: true });
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
async function runSmoke(manager, spawn, dir) {
|
|
72
|
+
try {
|
|
73
|
+
const config = buildGateSandboxConfig({
|
|
74
|
+
checkoutRoot: dir,
|
|
75
|
+
runTmpDir: dir,
|
|
76
|
+
projectRoot: dir,
|
|
77
|
+
});
|
|
78
|
+
const wrapped = await withSandboxTmpdir(dir, () => manager.wrapWithSandboxArgv("/usr/bin/true", undefined, config));
|
|
79
|
+
const [command, ...args] = wrapped.argv;
|
|
80
|
+
if (command === undefined)
|
|
81
|
+
return none("sandbox wrap returned an empty argv during the probe");
|
|
82
|
+
const result = spawn(command, args, { timeout: 10_000, stdio: "ignore" });
|
|
83
|
+
if (result.error || result.status !== 0) {
|
|
84
|
+
return none(`the sandbox smoke-run failed (exit ${result.status ?? "null"}${result.error ? `, ${result.error.message}` : ""}); ${LINUX_REMEDIATION}, ${OVERRIDE_NOTE}`);
|
|
85
|
+
}
|
|
86
|
+
return { kind: "srt" };
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return none(`the sandbox smoke-run threw (${describe(error)}); ${OVERRIDE_NOTE}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function none(reason) {
|
|
93
|
+
return { kind: "none", reason };
|
|
94
|
+
}
|
|
95
|
+
function describe(error) {
|
|
96
|
+
return error instanceof Error ? error.message : String(error);
|
|
97
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lossless POSIX shell serialization of a structured argv.
|
|
3
|
+
*
|
|
4
|
+
* `SandboxManager.wrapWithSandboxArgv` accepts a command *string* and returns a
|
|
5
|
+
* `shell -c <string>` wrapper, but the verifier owns a structured argv. Naively
|
|
6
|
+
* joining with spaces would let a shell-significant character in an argument
|
|
7
|
+
* (space, quote, glob, `#`, `$`, backtick, newline) change the command's
|
|
8
|
+
* meaning after wrapping. Single-quote wrapping makes every byte literal — the
|
|
9
|
+
* only escape needed is the single quote itself, closed and reopened around an
|
|
10
|
+
* escaped literal quote (`'\''`). This round-trips byte-identically even
|
|
11
|
+
* through srt's nested `bash -c "... sh -c '<here>'"` wrapping.
|
|
12
|
+
*/
|
|
13
|
+
export declare function shellQuoteArg(arg: string): string;
|
|
14
|
+
export declare function shellQuoteArgv(argv: readonly string[]): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lossless POSIX shell serialization of a structured argv.
|
|
3
|
+
*
|
|
4
|
+
* `SandboxManager.wrapWithSandboxArgv` accepts a command *string* and returns a
|
|
5
|
+
* `shell -c <string>` wrapper, but the verifier owns a structured argv. Naively
|
|
6
|
+
* joining with spaces would let a shell-significant character in an argument
|
|
7
|
+
* (space, quote, glob, `#`, `$`, backtick, newline) change the command's
|
|
8
|
+
* meaning after wrapping. Single-quote wrapping makes every byte literal — the
|
|
9
|
+
* only escape needed is the single quote itself, closed and reopened around an
|
|
10
|
+
* escaped literal quote (`'\''`). This round-trips byte-identically even
|
|
11
|
+
* through srt's nested `bash -c "... sh -c '<here>'"` wrapping.
|
|
12
|
+
*/
|
|
13
|
+
export function shellQuoteArg(arg) {
|
|
14
|
+
return `'${arg.replace(/'/gu, "'\\''")}'`;
|
|
15
|
+
}
|
|
16
|
+
export function shellQuoteArgv(argv) {
|
|
17
|
+
return argv.map(shellQuoteArg).join(" ");
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `fn` (a wrap call) with `process.env.CLAUDE_CODE_TMPDIR` pointed at a
|
|
3
|
+
* PER-RUN sandbox temp dir under the verification's disposable `runTmpDir`,
|
|
4
|
+
* serialized against other callers and restored afterward. srt redirects the
|
|
5
|
+
* sandboxed child's TMPDIR here, so temp writes land in the caller's own
|
|
6
|
+
* disposable tree — never a process-global shared dir another review in the same
|
|
7
|
+
* process could read, modify, or recover.
|
|
8
|
+
*/
|
|
9
|
+
export declare function withSandboxTmpdir<T>(runTmpDir: string, fn: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
// Serializes the brief window in which CLAUDE_CODE_TMPDIR is set process-wide.
|
|
4
|
+
// srt reads that variable from the environment when it bakes the sandboxed
|
|
5
|
+
// child's TMPDIR into the wrapped argv, so two concurrent wraps must not clobber
|
|
6
|
+
// each other's value. Only the (fast, synchronous-ish) wrap call is serialized;
|
|
7
|
+
// the actual gate execution runs outside this mutex with the value already baked
|
|
8
|
+
// into its argv.
|
|
9
|
+
let sandboxTmpdirChain = Promise.resolve();
|
|
10
|
+
/**
|
|
11
|
+
* Runs `fn` (a wrap call) with `process.env.CLAUDE_CODE_TMPDIR` pointed at a
|
|
12
|
+
* PER-RUN sandbox temp dir under the verification's disposable `runTmpDir`,
|
|
13
|
+
* serialized against other callers and restored afterward. srt redirects the
|
|
14
|
+
* sandboxed child's TMPDIR here, so temp writes land in the caller's own
|
|
15
|
+
* disposable tree — never a process-global shared dir another review in the same
|
|
16
|
+
* process could read, modify, or recover.
|
|
17
|
+
*/
|
|
18
|
+
export function withSandboxTmpdir(runTmpDir, fn) {
|
|
19
|
+
const dir = join(runTmpDir, "sandbox-tmp");
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
const run = sandboxTmpdirChain.then(async () => {
|
|
22
|
+
const previous = process.env["CLAUDE_CODE_TMPDIR"];
|
|
23
|
+
process.env["CLAUDE_CODE_TMPDIR"] = dir;
|
|
24
|
+
try {
|
|
25
|
+
return await fn();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
if (previous === undefined)
|
|
29
|
+
delete process.env["CLAUDE_CODE_TMPDIR"];
|
|
30
|
+
else
|
|
31
|
+
process.env["CLAUDE_CODE_TMPDIR"] = previous;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
sandboxTmpdirChain = run.then(() => undefined, () => undefined);
|
|
35
|
+
return run;
|
|
36
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS-sandbox contract for the delivery-review verifier's untrusted gate
|
|
3
|
+
* execution. Backed by @anthropic-ai/sandbox-runtime (Seatbelt on macOS,
|
|
4
|
+
* bubblewrap on Linux) via the no-initialize wrap pattern.
|
|
5
|
+
*/
|
|
6
|
+
/** Result of probing the host for a usable OS sandbox backend. */
|
|
7
|
+
export type SandboxAvailability = {
|
|
8
|
+
readonly kind: "srt";
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: "none";
|
|
11
|
+
readonly reason: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Per-verification inputs the sandbox policy is built from. All paths are
|
|
15
|
+
* realpath-resolved by the caller so srt does not silently drop
|
|
16
|
+
* boundary-crossing symlink entries on Linux.
|
|
17
|
+
*/
|
|
18
|
+
export type GateSandboxPolicy = {
|
|
19
|
+
/** The disposable review checkout root (realpath'd). Writable. */
|
|
20
|
+
readonly checkoutRoot: string;
|
|
21
|
+
/** A per-run scratch dir outside the checkout (realpath'd). Writable. */
|
|
22
|
+
readonly runTmpDir: string;
|
|
23
|
+
/** The caller repository root, used to derive `.env*` read-denies. */
|
|
24
|
+
readonly projectRoot: string;
|
|
25
|
+
};
|
|
26
|
+
/** A concrete spawnable invocation (post-sandbox-wrap). */
|
|
27
|
+
export type SandboxedInvocation = {
|
|
28
|
+
readonly command: string;
|
|
29
|
+
readonly args: readonly string[];
|
|
30
|
+
readonly env: NodeJS.ProcessEnv;
|
|
31
|
+
};
|
|
@@ -126,11 +126,23 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
126
126
|
const droppedEventWarnings = [];
|
|
127
127
|
const recordDroppedProviderEvent = (reason) => {
|
|
128
128
|
droppedEventWarnings.push(reason);
|
|
129
|
+
if (reason.startsWith("provider-event-too-large")) {
|
|
130
|
+
oversizedEventsSkipped += 1;
|
|
131
|
+
}
|
|
129
132
|
};
|
|
130
133
|
const decoder = new StringDecoder("utf8");
|
|
131
134
|
const diagnosticDecoder = new StringDecoder("utf8");
|
|
132
135
|
let buffered = "";
|
|
133
136
|
let diagnosticBuffered = "";
|
|
137
|
+
// When a single NDJSON event exceeds MAX_PROVIDER_EVENT_BYTES we DROP that one
|
|
138
|
+
// event (bounding memory) and keep processing toward the terminal verdict,
|
|
139
|
+
// rather than discarding an otherwise-complete review. A reviewer that reads a
|
|
140
|
+
// large file (e.g. a big lockfile) legitimately emits one oversized tool event;
|
|
141
|
+
// that must not sink the whole review. These flags skip the remainder of an
|
|
142
|
+
// oversized line up to its next newline.
|
|
143
|
+
let skippingOversizedEvent = false;
|
|
144
|
+
let skippingOversizedDiagnostic = false;
|
|
145
|
+
let oversizedEventsSkipped = 0;
|
|
134
146
|
let finalized = false;
|
|
135
147
|
let terminationPromise;
|
|
136
148
|
let resolveSummary;
|
|
@@ -332,20 +344,34 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
332
344
|
}
|
|
333
345
|
};
|
|
334
346
|
const consume = (chunk) => {
|
|
335
|
-
|
|
347
|
+
if (status !== null)
|
|
348
|
+
return;
|
|
349
|
+
let text = decoder.write(chunk);
|
|
350
|
+
if (skippingOversizedEvent) {
|
|
351
|
+
const newline = text.indexOf("\n");
|
|
352
|
+
if (newline === -1)
|
|
353
|
+
return; // still inside the oversized event; drop this chunk
|
|
354
|
+
skippingOversizedEvent = false;
|
|
355
|
+
text = text.slice(newline + 1); // resume after the oversized event's newline
|
|
356
|
+
}
|
|
357
|
+
buffered += text;
|
|
336
358
|
const lines = buffered.split("\n");
|
|
337
359
|
buffered = lines.pop() ?? "";
|
|
338
360
|
for (const line of lines)
|
|
339
361
|
processLine(line);
|
|
340
362
|
if (status === null && Buffer.byteLength(buffered, "utf8") > MAX_PROVIDER_EVENT_BYTES) {
|
|
341
|
-
|
|
363
|
+
// An in-progress event already exceeds the bound: drop the rest of it (skip
|
|
364
|
+
// to its next newline) instead of discarding the whole review.
|
|
365
|
+
buffered = "";
|
|
366
|
+
skippingOversizedEvent = true;
|
|
367
|
+
oversizedEventsSkipped += 1;
|
|
342
368
|
}
|
|
343
369
|
};
|
|
344
370
|
const processDiagnosticLine = (line) => {
|
|
345
371
|
if (line.trim().length === 0 || status !== null || !adapter.parseDiagnostic)
|
|
346
372
|
return;
|
|
347
373
|
if (Buffer.byteLength(line, "utf8") > MAX_PROVIDER_EVENT_BYTES) {
|
|
348
|
-
|
|
374
|
+
oversizedEventsSkipped += 1;
|
|
349
375
|
return;
|
|
350
376
|
}
|
|
351
377
|
const event = adapter.parseDiagnostic(line);
|
|
@@ -358,14 +384,26 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
358
384
|
}
|
|
359
385
|
};
|
|
360
386
|
const consumeDiagnostic = (chunk) => {
|
|
361
|
-
|
|
387
|
+
if (status !== null)
|
|
388
|
+
return;
|
|
389
|
+
let text = diagnosticDecoder.write(chunk);
|
|
390
|
+
if (skippingOversizedDiagnostic) {
|
|
391
|
+
const newline = text.indexOf("\n");
|
|
392
|
+
if (newline === -1)
|
|
393
|
+
return;
|
|
394
|
+
skippingOversizedDiagnostic = false;
|
|
395
|
+
text = text.slice(newline + 1);
|
|
396
|
+
}
|
|
397
|
+
diagnosticBuffered += text;
|
|
362
398
|
const lines = diagnosticBuffered.split("\n");
|
|
363
399
|
diagnosticBuffered = lines.pop() ?? "";
|
|
364
400
|
for (const line of lines)
|
|
365
401
|
processDiagnosticLine(line);
|
|
366
402
|
if (status === null &&
|
|
367
403
|
Buffer.byteLength(diagnosticBuffered, "utf8") > MAX_PROVIDER_EVENT_BYTES) {
|
|
368
|
-
|
|
404
|
+
diagnosticBuffered = "";
|
|
405
|
+
skippingOversizedDiagnostic = true;
|
|
406
|
+
oversizedEventsSkipped += 1;
|
|
369
407
|
}
|
|
370
408
|
};
|
|
371
409
|
const summaryPromise = new Promise((resolve) => {
|
|
@@ -451,6 +489,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
451
489
|
finishedAt: now().toISOString(),
|
|
452
490
|
lastProgressAt: lastProgressAt.toISOString(),
|
|
453
491
|
semanticProgressCount,
|
|
492
|
+
...(oversizedEventsSkipped > 0 ? { oversizedEventsSkipped } : {}),
|
|
454
493
|
exitCode,
|
|
455
494
|
markerMatched,
|
|
456
495
|
...(result === undefined ? {} : { result }),
|
|
@@ -73,6 +73,8 @@ export interface ReviewRunSummary {
|
|
|
73
73
|
readonly finishedAt: string;
|
|
74
74
|
readonly lastProgressAt: string;
|
|
75
75
|
readonly semanticProgressCount: number;
|
|
76
|
+
/** Count of single provider/diagnostic events dropped for exceeding the byte bound. */
|
|
77
|
+
readonly oversizedEventsSkipped?: number;
|
|
76
78
|
readonly exitCode: number | null;
|
|
77
79
|
readonly markerMatched: boolean | null;
|
|
78
80
|
readonly result?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpresso/agent-kit",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "TypeScript-first agent harness for guarded develop/deploy workflows: wp CLI gates, MCP tools, hooks, memory, worktrees, secrets, audits, and evidence checks.",
|
|
6
6
|
"keywords": [
|
|
@@ -430,6 +430,7 @@
|
|
|
430
430
|
"desktop:build": "pnpm --filter @webpresso/desktop exec tsc --noEmit && pnpm --filter @webpresso/desktop exec vite build"
|
|
431
431
|
},
|
|
432
432
|
"dependencies": {
|
|
433
|
+
"@anthropic-ai/sandbox-runtime": "0.0.67",
|
|
433
434
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
434
435
|
"@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
|
|
435
436
|
"@typescript/typescript6": "^6.0.2",
|
|
@@ -507,16 +508,16 @@
|
|
|
507
508
|
"setupWpActionRef": "c2c71a7a4be446fc6858e6b57bf55a11ccfa2d88"
|
|
508
509
|
},
|
|
509
510
|
"optionalDependencies": {
|
|
510
|
-
"@webpresso/agent-kit-runtime-darwin-arm64": "3.3.
|
|
511
|
-
"@webpresso/agent-kit-runtime-darwin-x64": "3.3.
|
|
512
|
-
"@webpresso/agent-kit-runtime-linux-x64": "3.3.
|
|
513
|
-
"@webpresso/agent-kit-runtime-linux-arm64": "3.3.
|
|
514
|
-
"@webpresso/agent-kit-runtime-windows-x64": "3.3.
|
|
515
|
-
"@webpresso/agent-kit-session-memory-darwin-x64": "3.3.
|
|
516
|
-
"@webpresso/agent-kit-session-memory-darwin-arm64": "3.3.
|
|
517
|
-
"@webpresso/agent-kit-session-memory-linux-x64": "3.3.
|
|
518
|
-
"@webpresso/agent-kit-session-memory-linux-arm64": "3.3.
|
|
519
|
-
"@webpresso/agent-kit-session-memory-win32-x64": "3.3.
|
|
520
|
-
"@webpresso/agent-kit-session-memory-win32-arm64": "3.3.
|
|
511
|
+
"@webpresso/agent-kit-runtime-darwin-arm64": "3.3.2",
|
|
512
|
+
"@webpresso/agent-kit-runtime-darwin-x64": "3.3.2",
|
|
513
|
+
"@webpresso/agent-kit-runtime-linux-x64": "3.3.2",
|
|
514
|
+
"@webpresso/agent-kit-runtime-linux-arm64": "3.3.2",
|
|
515
|
+
"@webpresso/agent-kit-runtime-windows-x64": "3.3.2",
|
|
516
|
+
"@webpresso/agent-kit-session-memory-darwin-x64": "3.3.2",
|
|
517
|
+
"@webpresso/agent-kit-session-memory-darwin-arm64": "3.3.2",
|
|
518
|
+
"@webpresso/agent-kit-session-memory-linux-x64": "3.3.2",
|
|
519
|
+
"@webpresso/agent-kit-session-memory-linux-arm64": "3.3.2",
|
|
520
|
+
"@webpresso/agent-kit-session-memory-win32-x64": "3.3.2",
|
|
521
|
+
"@webpresso/agent-kit-session-memory-win32-arm64": "3.3.2"
|
|
521
522
|
}
|
|
522
523
|
}
|