@webpresso/agent-kit 3.3.1 → 3.3.3

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.
Files changed (87) hide show
  1. package/catalog/AGENTS.md.tpl +3 -5
  2. package/catalog/agent/rules/pre-implementation.md +15 -8
  3. package/catalog/agent/skills/ultragoal/SKILL.md +5 -18
  4. package/catalog/agent/skills/verify/SKILL.md +12 -12
  5. package/catalog/compose/admin-blocks-registry.json +659 -0
  6. package/dist/esm/blueprint/core/parser.js +2 -4
  7. package/dist/esm/blueprint/core/validation/criteria.js +2 -1
  8. package/dist/esm/blueprint/core/validation/task-blocks.js +7 -1
  9. package/dist/esm/blueprint/lifecycle/review-provenance.js +85 -30
  10. package/dist/esm/blueprint/markdown/task-heading.d.ts +21 -0
  11. package/dist/esm/blueprint/markdown/task-heading.js +25 -0
  12. package/dist/esm/blueprint/trust/command-runner.d.ts +40 -0
  13. package/dist/esm/blueprint/trust/command-runner.js +265 -41
  14. package/dist/esm/build/cli-mcp-parity.js +6 -0
  15. package/dist/esm/build/package-manifest.js +7 -2
  16. package/dist/esm/ci/native-session-memory-cache.d.ts +2 -1
  17. package/dist/esm/ci/native-session-memory-cache.js +2 -1
  18. package/dist/esm/ci/release-progress.d.ts +37 -0
  19. package/dist/esm/ci/release-progress.js +139 -0
  20. package/dist/esm/cli/cli.d.ts +1 -1
  21. package/dist/esm/cli/cli.js +9 -0
  22. package/dist/esm/cli/commands/audit-core.js +9 -1
  23. package/dist/esm/cli/commands/audit.js +16 -5
  24. package/dist/esm/cli/commands/blueprint/mutations.js +2 -3
  25. package/dist/esm/cli/commands/init/mcp-spec.d.ts +18 -3
  26. package/dist/esm/cli/commands/init/mcp-spec.js +54 -6
  27. package/dist/esm/cli/commands/init/scaffold-agents-md.js +5 -7
  28. package/dist/esm/cli/commands/package-manager.js +8 -2
  29. package/dist/esm/cli/commands/release-progress.d.ts +53 -0
  30. package/dist/esm/cli/commands/release-progress.js +155 -0
  31. package/dist/esm/cli/commands/review.js +74 -11
  32. package/dist/esm/cli/utils.d.ts +8 -1
  33. package/dist/esm/cli/utils.js +12 -2
  34. package/dist/esm/compose/compose.d.ts +38 -0
  35. package/dist/esm/compose/compose.js +143 -0
  36. package/dist/esm/compose/escape-html.d.ts +5 -0
  37. package/dist/esm/compose/escape-html.js +12 -0
  38. package/dist/esm/compose/ops-report-render.d.ts +5 -0
  39. package/dist/esm/compose/ops-report-render.js +71 -0
  40. package/dist/esm/compose/ops-report-schema.d.ts +42 -0
  41. package/dist/esm/compose/ops-report-schema.js +46 -0
  42. package/dist/esm/compose/preview.d.ts +14 -0
  43. package/dist/esm/compose/preview.js +32 -0
  44. package/dist/esm/compose/registry-resolve.d.ts +47 -0
  45. package/dist/esm/compose/registry-resolve.js +133 -0
  46. package/dist/esm/git/changed-files.d.ts +20 -0
  47. package/dist/esm/git/changed-files.js +63 -0
  48. package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -2
  49. package/dist/esm/hooks/stop/qa-changed-files.js +6 -1
  50. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +2 -1
  51. package/dist/esm/mcp/blueprint/handlers/task-advance.js +2 -12
  52. package/dist/esm/mcp/blueprint/handlers/validate.js +6 -0
  53. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  54. package/dist/esm/mcp/tools/_names.js +4 -0
  55. package/dist/esm/mcp/tools/_registry.js +8 -0
  56. package/dist/esm/mcp/tools/release-progress.d.ts +9 -0
  57. package/dist/esm/mcp/tools/release-progress.js +110 -0
  58. package/dist/esm/mcp/tools/wp-ui-blocks-list.d.ts +3 -0
  59. package/dist/esm/mcp/tools/wp-ui-blocks-list.js +43 -0
  60. package/dist/esm/mcp/tools/wp-ui-compose.d.ts +3 -0
  61. package/dist/esm/mcp/tools/wp-ui-compose.js +62 -0
  62. package/dist/esm/mcp/tools/wp-ui-preview.d.ts +3 -0
  63. package/dist/esm/mcp/tools/wp-ui-preview.js +52 -0
  64. package/dist/esm/package.json +2 -0
  65. package/dist/esm/review/delivery-verifier.d.ts +33 -2
  66. package/dist/esm/review/delivery-verifier.js +130 -24
  67. package/dist/esm/review/execution/review-checkout.d.ts +1 -0
  68. package/dist/esm/review/execution/review-checkout.js +386 -23
  69. package/dist/esm/review/execution/sandbox/adapter.d.ts +37 -0
  70. package/dist/esm/review/execution/sandbox/adapter.js +39 -0
  71. package/dist/esm/review/execution/sandbox/env.d.ts +12 -0
  72. package/dist/esm/review/execution/sandbox/env.js +37 -0
  73. package/dist/esm/review/execution/sandbox/index.d.ts +5 -0
  74. package/dist/esm/review/execution/sandbox/index.js +4 -0
  75. package/dist/esm/review/execution/sandbox/policy.d.ts +19 -0
  76. package/dist/esm/review/execution/sandbox/policy.js +95 -0
  77. package/dist/esm/review/execution/sandbox/probe.d.ts +20 -0
  78. package/dist/esm/review/execution/sandbox/probe.js +97 -0
  79. package/dist/esm/review/execution/sandbox/shell-quote.d.ts +14 -0
  80. package/dist/esm/review/execution/sandbox/shell-quote.js +18 -0
  81. package/dist/esm/review/execution/sandbox/tmpdir.d.ts +9 -0
  82. package/dist/esm/review/execution/sandbox/tmpdir.js +36 -0
  83. package/dist/esm/review/execution/sandbox/types.d.ts +31 -0
  84. package/dist/esm/review/execution/sandbox/types.js +6 -0
  85. package/dist/esm/review/execution/supervisor.js +44 -5
  86. package/dist/esm/review/execution/types.d.ts +2 -0
  87. package/package.json +24 -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
+ };
@@ -0,0 +1,6 @@
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
+ export {};
@@ -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
- buffered += decoder.write(chunk);
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
- fail("protocol-unsupported", "provider-event-too-large");
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
- fail("protocol-unsupported", "provider-diagnostic-too-large");
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
- diagnosticBuffered += diagnosticDecoder.write(chunk);
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
- fail("protocol-unsupported", "provider-diagnostic-too-large");
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.1",
3
+ "version": "3.3.3",
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": [
@@ -78,6 +78,8 @@
78
78
  "#worktrees/*": "./src/worktrees/*.ts",
79
79
  "#fleet/*.js": "./src/fleet/*.ts",
80
80
  "#fleet/*": "./src/fleet/*.ts",
81
+ "#compose/*.js": "./src/compose/*.ts",
82
+ "#compose/*": "./src/compose/*.ts",
81
83
  "#cli/auto-update/*.js": "./src/cli/auto-update/*.ts",
82
84
  "#cli/auto-update/*": "./src/cli/auto-update/*.ts",
83
85
  "#cli/*.js": "./src/cli/*.ts",
@@ -430,6 +432,7 @@
430
432
  "desktop:build": "pnpm --filter @webpresso/desktop exec tsc --noEmit && pnpm --filter @webpresso/desktop exec vite build"
431
433
  },
432
434
  "dependencies": {
435
+ "@anthropic-ai/sandbox-runtime": "0.0.67",
433
436
  "@modelcontextprotocol/sdk": "^1.29.0",
434
437
  "@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
435
438
  "@typescript/typescript6": "^6.0.2",
@@ -504,19 +507,28 @@
504
507
  "packageManager": "pnpm@11.10.0",
505
508
  "webpresso": {
506
509
  "agentConfigVersion": "0.1.5",
510
+ "dependencyFreshnessExceptions": [
511
+ {
512
+ "package": "@xterm/xterm",
513
+ "reason": "Pinned to the 5.5 major the desktop-pty-loadtest spike actually measured. The spike's GUI performance gate for the terminal pane is still recorded as pending, so adopting the 6.0 renderer major would make that structural evidence non-transferable and leave apps/desktop with no supporting measurement at all. Upgrading is a separate change that must carry its own measurement.",
514
+ "owner": "ozby",
515
+ "expiry": "2026-10-31",
516
+ "linkedIssue": "https://github.com/webpresso/agent-kit/pull/889"
517
+ }
518
+ ],
507
519
  "setupWpActionRef": "c2c71a7a4be446fc6858e6b57bf55a11ccfa2d88"
508
520
  },
509
521
  "optionalDependencies": {
510
- "@webpresso/agent-kit-runtime-darwin-arm64": "3.3.1",
511
- "@webpresso/agent-kit-runtime-darwin-x64": "3.3.1",
512
- "@webpresso/agent-kit-runtime-linux-x64": "3.3.1",
513
- "@webpresso/agent-kit-runtime-linux-arm64": "3.3.1",
514
- "@webpresso/agent-kit-runtime-windows-x64": "3.3.1",
515
- "@webpresso/agent-kit-session-memory-darwin-x64": "3.3.1",
516
- "@webpresso/agent-kit-session-memory-darwin-arm64": "3.3.1",
517
- "@webpresso/agent-kit-session-memory-linux-x64": "3.3.1",
518
- "@webpresso/agent-kit-session-memory-linux-arm64": "3.3.1",
519
- "@webpresso/agent-kit-session-memory-win32-x64": "3.3.1",
520
- "@webpresso/agent-kit-session-memory-win32-arm64": "3.3.1"
522
+ "@webpresso/agent-kit-runtime-darwin-arm64": "3.3.3",
523
+ "@webpresso/agent-kit-runtime-darwin-x64": "3.3.3",
524
+ "@webpresso/agent-kit-runtime-linux-x64": "3.3.3",
525
+ "@webpresso/agent-kit-runtime-linux-arm64": "3.3.3",
526
+ "@webpresso/agent-kit-runtime-windows-x64": "3.3.3",
527
+ "@webpresso/agent-kit-session-memory-darwin-x64": "3.3.3",
528
+ "@webpresso/agent-kit-session-memory-darwin-arm64": "3.3.3",
529
+ "@webpresso/agent-kit-session-memory-linux-x64": "3.3.3",
530
+ "@webpresso/agent-kit-session-memory-linux-arm64": "3.3.3",
531
+ "@webpresso/agent-kit-session-memory-win32-x64": "3.3.3",
532
+ "@webpresso/agent-kit-session-memory-win32-arm64": "3.3.3"
521
533
  }
522
534
  }