mirror of
https://github.com/morluto/rea.git
synced 2026-10-23 06:12:29 -05:00
Pre-count ordered normalization stages before materializing output. Charge original and normalized terminal bytes independently, omit whole over-budget chunks and preserve exact raw counts plus explicit unknown final sizes. Bound rendered normalization, retain legacy capture compatibility, and remove temporary per-match output arrays. Validation: UTF-8 surrogate boundaries, shrinking/no-op/literal rules, capture validation and public MCP expansion/equal-size controls with verified cleanup. CPU, process-family memory and token changes are unmeasured; no performance speedup claimed. Nonterminal derived payload bounds remain separate work. Fixes #3168
233 lines
7.6 KiB
TypeScript
233 lines
7.6 KiB
TypeScript
import { fileURLToPath } from "node:url";
|
|
import { access } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
import { afterEach, expect, it } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { toolContract } from "../../../src/contracts/toolContracts.js";
|
|
import {
|
|
createCacheProvider,
|
|
createTestBinarySession,
|
|
} from "../../fixtures/binarySession.js";
|
|
import { createServer } from "../../../src/server/createServer.js";
|
|
import { silentLogger } from "../../../src/logger.js";
|
|
import { itWithCaptureCapability } from "./processCaptureCapability.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const processFixture = fileURLToPath(
|
|
new URL("../../fixtures/processFidelity.mjs", import.meta.url),
|
|
);
|
|
const resources: Array<{ close(): Promise<unknown> }> = [];
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(resources.splice(0).map((resource) => resource.close()));
|
|
});
|
|
|
|
it("rejects oversized initial and resize terminal buffers through MCP before launching", async () => {
|
|
const root = await createTestTempDirectory("rea-terminal-admission-");
|
|
const marker = join(root, "launched");
|
|
const session = createTestBinarySession(() => {
|
|
throw new Error("Terminal admission must not launch a binary provider");
|
|
});
|
|
const server = createServer(
|
|
{ kind: "session", session },
|
|
{ logger: silentLogger },
|
|
);
|
|
const client = new Client({ name: "terminal-admission", version: "1" });
|
|
resources.push(client, server, session);
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
for (const input of [
|
|
{ terminal: { columns: 1_000, rows: 1, scrollback: 1_000 } },
|
|
{ events: [{ type: "resize", at_ms: 10, columns: 1_000, rows: 1 }] },
|
|
] as const) {
|
|
const result = await client.callTool({
|
|
name: "capture_process_scenario",
|
|
arguments: {
|
|
executable: process.execPath,
|
|
arguments: [
|
|
"-e",
|
|
"require('node:fs').writeFileSync(process.argv[1], 'launched')",
|
|
marker,
|
|
],
|
|
...input,
|
|
},
|
|
});
|
|
expect(result.isError).toBe(true);
|
|
expect(result.content).toContainEqual(
|
|
expect.objectContaining({
|
|
type: "text",
|
|
text: expect.stringContaining("1000000"),
|
|
}),
|
|
);
|
|
await expect(access(marker)).rejects.toMatchObject({ code: "ENOENT" });
|
|
}
|
|
await client.ping();
|
|
});
|
|
|
|
itWithCaptureCapability(
|
|
"bounds normalized PTY text and preserves equal-size output through MCP",
|
|
async () => {
|
|
const root = await createTestTempDirectory(
|
|
"rea-process-normalized-budget-",
|
|
);
|
|
const session = createTestBinarySession(createCacheProvider([]));
|
|
const server = createServer(
|
|
{ kind: "session", session },
|
|
{ logger: silentLogger },
|
|
);
|
|
const client = new Client({
|
|
name: "process-normalized-budget",
|
|
version: "1",
|
|
});
|
|
resources.push(client, server, session);
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
|
|
const capture = async (replacement: string) => {
|
|
const result = await client.callTool({
|
|
name: "capture_process_scenario",
|
|
arguments: {
|
|
executable: process.execPath,
|
|
working_directory: root,
|
|
arguments: ["-e", 'process.stdout.write("a".repeat(100) + "\\n")'],
|
|
limits: { output_bytes: 1_000 },
|
|
normalization: {
|
|
paths: false,
|
|
pids: false,
|
|
ports: false,
|
|
patterns: [{ pattern: "a", replacement }],
|
|
},
|
|
settle_ms: 50,
|
|
},
|
|
});
|
|
expect(result.isError, JSON.stringify(result.content)).not.toBe(true);
|
|
return toolContract("capture_process_scenario").outputSchema.parse(
|
|
result.structuredContent,
|
|
).normalized_result;
|
|
};
|
|
|
|
const expanded = await capture("x".repeat(500));
|
|
expect(expanded.frames).toHaveLength(0);
|
|
expect(expanded.truncated).toBe(true);
|
|
expect(expanded.truncation_details.normalized_terminal).toMatchObject({
|
|
budget_bytes: 1_000,
|
|
retained_bytes: 0,
|
|
omitted_frames: expect.any(Number),
|
|
});
|
|
expect(
|
|
expanded.truncation_details.normalized_terminal?.omitted_frames,
|
|
).toBeGreaterThan(0);
|
|
expect(expanded.truncation_details.raw_terminal).toMatchObject({
|
|
budget_bytes: 1_000,
|
|
observed_bytes: expect.any(Number),
|
|
retained_bytes: 0,
|
|
observed_frames: expect.any(Number),
|
|
retained_frames: 0,
|
|
});
|
|
expect(
|
|
expanded.limitations.some((limitation) =>
|
|
limitation.includes(
|
|
"final normalized sizes for the latter omissions are unknown",
|
|
),
|
|
),
|
|
).toBe(true);
|
|
expect(expanded.cleanup).toMatchObject({
|
|
owned_process_group: "verified",
|
|
temporary_root: "removed",
|
|
});
|
|
|
|
const equalSize = await capture("b");
|
|
expect(equalSize.truncated).toBe(false);
|
|
expect(equalSize.frames.map(({ data }) => data).join("")).toBe(
|
|
`${"b".repeat(100)}\r\n`,
|
|
);
|
|
expect(equalSize.truncation_details.normalized_terminal).toMatchObject({
|
|
budget_bytes: 1_000,
|
|
retained_bytes: Buffer.byteLength(`${"b".repeat(100)}\r\n`),
|
|
omitted_frames: 0,
|
|
});
|
|
expect(equalSize.cleanup).toMatchObject({
|
|
owned_process_group: "verified",
|
|
temporary_root: "removed",
|
|
});
|
|
},
|
|
30_000,
|
|
);
|
|
|
|
itWithCaptureCapability(
|
|
"records process residuals linked to capture Evidence",
|
|
async () => {
|
|
const session = createTestBinarySession(createCacheProvider([]));
|
|
const server = createServer(
|
|
{ kind: "session", session },
|
|
{
|
|
logger: silentLogger,
|
|
},
|
|
);
|
|
const mcp = new Client({ name: "process-unknown", version: "1.0.0" });
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
resources.push(mcp, server);
|
|
await server.connect(serverTransport);
|
|
await mcp.connect(clientTransport);
|
|
|
|
const captured = await mcp.callTool({
|
|
name: "capture_process_scenario",
|
|
arguments: {
|
|
executable: process.execPath,
|
|
arguments: [processFixture, "partial"],
|
|
},
|
|
});
|
|
expect(captured.isError, JSON.stringify(captured.content)).not.toBe(true);
|
|
const contract = toolContract("capture_process_scenario");
|
|
const wire = (await mcp.listTools()).tools.find(
|
|
({ name }) => name === contract.name,
|
|
);
|
|
if (wire?.outputSchema === undefined)
|
|
throw new Error("Missing process capture output schema");
|
|
expect(
|
|
new Ajv2020({ strict: false, validateFormats: false }).validate(
|
|
z.record(z.string(), z.unknown()).parse(wire.outputSchema),
|
|
captured.structuredContent,
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
contract.outputSchema.safeParse(captured.structuredContent).success,
|
|
).toBe(true);
|
|
const listedUnknowns = await mcp.callTool({
|
|
name: "list_unknowns",
|
|
arguments: {},
|
|
});
|
|
expect(
|
|
listedUnknowns.isError,
|
|
JSON.stringify(listedUnknowns.content),
|
|
).not.toBe(true);
|
|
const listed = z
|
|
.object({
|
|
result: z.object({
|
|
items: z.array(
|
|
z.object({
|
|
question: z.string(),
|
|
domain: z.string(),
|
|
}),
|
|
),
|
|
}),
|
|
})
|
|
.parse(listedUnknowns.structuredContent).result.items;
|
|
expect(listed).toContainEqual(
|
|
expect.objectContaining({
|
|
question: "Was network behavior fully observed during capture?",
|
|
domain: "process-network",
|
|
}),
|
|
);
|
|
},
|
|
);
|