mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
test: advance the Rizin and build computer fixed waits instead of sleeping through them (#11026)
This commit is contained in:
1 parent
5cfaac0834
commit
f46cd6770c
5 files changed
+49
-17
No files matched your search
@@ -421,6 +421,11 @@ export class DesktopBuildComputers {
|
||||
this.#shared = { settle, secrets: this.#secrets };
|
||||
}
|
||||
|
||||
/** How often a computer's start, and a tool call waiting on one, reports progress. */
|
||||
get progressMs(): number {
|
||||
return this.startTiming.progressMs;
|
||||
}
|
||||
|
||||
/** How results show the secrets typed on this server's computers. */
|
||||
get redaction(): Pick<DesktopBuildSecrets, "redact" | "redactJson"> {
|
||||
return this.#secrets;
|
||||
|
||||
@@ -26,7 +26,7 @@ import { awaitHttpReply, type HttpReply } from "./HttpReply.js";
|
||||
const READY_TIMEOUT_MS = 180_000;
|
||||
const REQUEST_TIMEOUT_MS = 120_000;
|
||||
|
||||
/** How long a computer's first screenshot waits for the desktop to draw, and how often its start reports progress. */
|
||||
/** How long a computer's first screenshot waits for the desktop to draw, and how often its start and the tool calls waiting on it report progress. */
|
||||
export interface ComputerStartTiming {
|
||||
readonly drawnWaitMs: number;
|
||||
readonly progressMs: number;
|
||||
|
||||
@@ -37,16 +37,15 @@ const admit = <Schema extends z.ZodType>(
|
||||
);
|
||||
};
|
||||
|
||||
const HEARTBEAT_MS = 5_000;
|
||||
|
||||
/**
|
||||
* Report progress every few seconds while a long build step works, so a
|
||||
* Report progress every `intervalMs` while a long build step works, so a
|
||||
* client's request timeout can reset; the step reports on the same
|
||||
* reporter, whose notifications keep one order.
|
||||
*/
|
||||
const withHeartbeat = async <Value>(
|
||||
context: ServerContext,
|
||||
phase: string,
|
||||
intervalMs: number,
|
||||
run: (progress: ProgressReporter) => Promise<Value>,
|
||||
): Promise<Value> => {
|
||||
const progress = mcpProgressReporter(context);
|
||||
@@ -60,7 +59,7 @@ const withHeartbeat = async <Value>(
|
||||
message: `working (${String(Math.round((Date.now() - startedAt) / 1000))} s)`,
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}, HEARTBEAT_MS);
|
||||
}, intervalMs);
|
||||
try {
|
||||
return await run(progress);
|
||||
} finally {
|
||||
@@ -173,10 +172,15 @@ export const registerDesktopBuildTools = (
|
||||
server: EvidenceMcpServer,
|
||||
computers: Pick<
|
||||
DesktopBuildComputers,
|
||||
"start" | "act" | "close" | "steps" | "measure" | "redaction"
|
||||
"start" | "act" | "close" | "steps" | "measure" | "redaction" | "progressMs"
|
||||
>,
|
||||
logger: Logger,
|
||||
): void => {
|
||||
const heartbeat = <Value>(
|
||||
context: ServerContext,
|
||||
phase: string,
|
||||
run: (progress: ProgressReporter) => Promise<Value>,
|
||||
) => withHeartbeat(context, phase, computers.progressMs, run);
|
||||
const tool = <Name extends Parameters<typeof toolContract>[0]>(
|
||||
name: Name,
|
||||
) => ({
|
||||
@@ -191,38 +195,38 @@ export const registerDesktopBuildTools = (
|
||||
);
|
||||
// An action can wait for the call before it on its computer, and a reset for the relaunched app, which can take minutes.
|
||||
registerForwarded(tool("act_on_build_computer"), (input, context) =>
|
||||
withHeartbeat(context, "act_on_build_computer", () =>
|
||||
heartbeat(context, "act_on_build_computer", () =>
|
||||
computers.act(input, context.mcpReq.signal),
|
||||
),
|
||||
);
|
||||
registerForwarded(tool("start_build_capture"), (input, context) =>
|
||||
withHeartbeat(context, "start_build_capture", () =>
|
||||
heartbeat(context, "start_build_capture", () =>
|
||||
steps.startCapture(input, context.mcpReq.signal),
|
||||
),
|
||||
);
|
||||
registerPlain(tool("mark_build_checkpoint"), (input, context) =>
|
||||
withHeartbeat(context, "mark_build_checkpoint", () =>
|
||||
heartbeat(context, "mark_build_checkpoint", () =>
|
||||
steps.markCheckpoint(input, context.mcpReq.signal),
|
||||
),
|
||||
);
|
||||
registerPlain(tool("stop_build_capture"), (input, context) =>
|
||||
withHeartbeat(context, "stop_build_capture", () =>
|
||||
heartbeat(context, "stop_build_capture", () =>
|
||||
steps.stopCapture(input, context.mcpReq.signal),
|
||||
),
|
||||
);
|
||||
registerPlain(tool("export_build_environment"), (input, context) =>
|
||||
withHeartbeat(context, "export_build_environment", () =>
|
||||
heartbeat(context, "export_build_environment", () =>
|
||||
steps.exportEnvironment(input, context.mcpReq.signal),
|
||||
),
|
||||
);
|
||||
registerPlain(tool("check_build_task"), (input, context) =>
|
||||
withHeartbeat(context, "check_build_task", (progress) =>
|
||||
heartbeat(context, "check_build_task", (progress) =>
|
||||
steps.checkTask(input, context.mcpReq.signal, progress),
|
||||
),
|
||||
);
|
||||
// Solves run one at a time for minutes each, so progress keeps the client waiting.
|
||||
registerPlain(tool("measure_build_task"), (input, context) =>
|
||||
withHeartbeat(context, "measure_build_task", (progress) =>
|
||||
heartbeat(context, "measure_build_task", (progress) =>
|
||||
computers.measure(input, context.mcpReq.signal, progress),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
expectNoToken,
|
||||
startBuildComputer,
|
||||
} from "../../fixtures/buildComputerMcp.js";
|
||||
import { COMPUTER_START_TIMING } from "../../../src/desktop/AgentEnvComputer.js";
|
||||
import {
|
||||
FAKE_FRAME_PNG,
|
||||
type FakeApp,
|
||||
@@ -695,9 +696,14 @@ it("runs one computer's calls in the order they arrive and another computer's be
|
||||
});
|
||||
|
||||
it("sends progress while an action waits for the call before it on its computer", async () => {
|
||||
const app = slowApp(14);
|
||||
const app = slowApp(2);
|
||||
const { runner } = await fakes({ app });
|
||||
const client = await connect(runner.environment);
|
||||
// A short heartbeat lets the queued action report progress while the reset
|
||||
// before it is still polling the relaunched app.
|
||||
const client = await connect(runner.environment, undefined, undefined, {
|
||||
...COMPUTER_START_TIMING,
|
||||
progressMs: 100,
|
||||
});
|
||||
const id = await startBuildComputer(client, {
|
||||
kind: "bundle",
|
||||
path: await bundleFolder(),
|
||||
|
||||
@@ -287,9 +287,16 @@ it
|
||||
return launched;
|
||||
});
|
||||
try {
|
||||
// The real stop's detach command gets no reply from this fixture and
|
||||
// returns incomplete after its 1 s detach deadline without signalling
|
||||
// the child. Report the same uncertainty at once; the child stays alive
|
||||
// for the harness to reap either way.
|
||||
const stopProcess: typeof incompleteStop =
|
||||
stopMode === "is uncertain"
|
||||
? incompleteStop
|
||||
? async () => ({
|
||||
status: "incomplete",
|
||||
reason: "injected stop uncertainty",
|
||||
})
|
||||
: async () => ({ status: "stopped" });
|
||||
const manager = createManager(
|
||||
{ REA_RIZIN_COMMAND: provider },
|
||||
@@ -475,7 +482,17 @@ it.skipIf(process.platform === "win32")(
|
||||
const sessionId = started.value.session_id;
|
||||
injected.control.exitLeader();
|
||||
|
||||
const firstClose = await manager.close(sessionId);
|
||||
// The output-settlement grace is a fixed timer; advance it instead of
|
||||
// waiting it out. The open output keeps the close incomplete regardless.
|
||||
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
||||
let firstClose: Awaited<ReturnType<typeof manager.close>>;
|
||||
try {
|
||||
const closing = manager.close(sessionId);
|
||||
await vi.advanceTimersByTimeAsync(10_000);
|
||||
firstClose = await closing;
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
expect(firstClose.ok).toBe(false);
|
||||
if (!firstClose.ok) {
|
||||
expect(firstClose.error.cleanupIncomplete).toBe(true);
|
||||
|
||||
Reference in new issue
Block a user