mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
b41d5293b2
* refactor(mantis): replace Telegram proof compiler with frozen visible replay * refactor(mantis): let Codex own Telegram proof scenarios * fix(mantis): isolate proof publication * fix(mantis): bind baseline cache to revision * chore(mantis): remove stale scenario-designer wording * fix(mantis): align readable worktrees with trusted proof * fix(mantis): register proof collector tooling * refactor(mantis): keep collector functions private * fix(mantis): publish agent-selected Telegram proof * fix(mantis): trim proof media to final turn * fix(mantis): fence lanes before evidence collection * fix(mantis): verify lane termination before unlock
180 lines
6.2 KiB
TypeScript
180 lines
6.2 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
loadEvidenceManifest,
|
|
renderEvidenceComment,
|
|
} from "../../scripts/mantis/publish-pr-evidence.mjs";
|
|
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
|
|
|
const SCRIPT = "scripts/mantis/telegram-visible-proof.mjs";
|
|
const BASELINE_SHA = "a".repeat(40);
|
|
const CANDIDATE_SHA = "b".repeat(40);
|
|
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
|
|
|
function temp(prefix: string) {
|
|
return tempDirs.make(prefix);
|
|
}
|
|
|
|
function writeMedia(file: string, header: string) {
|
|
const contents = Buffer.concat([Buffer.from(header), Buffer.alloc(12_000, 1)]);
|
|
writeFileSync(file, contents);
|
|
return {
|
|
bytes: contents.length,
|
|
file: path.basename(file),
|
|
sha256: createHash("sha256").update(contents).digest("hex"),
|
|
};
|
|
}
|
|
|
|
function writeLane(
|
|
root: string,
|
|
lane: "baseline" | "candidate",
|
|
sha: string,
|
|
events: unknown[],
|
|
invocations: unknown[],
|
|
) {
|
|
const published = path.join(root, "published", lane);
|
|
mkdirSync(published, { recursive: true });
|
|
const artifacts = {
|
|
previewGifCropped: writeMedia(path.join(published, `${lane}.gif`), "GIF89a"),
|
|
screenshot: writeMedia(path.join(published, `${lane}.png`), "PNG"),
|
|
trimmedVideoCropped: writeMedia(path.join(published, `${lane}.mp4`), "MP4"),
|
|
};
|
|
const facts = {
|
|
artifacts,
|
|
attempt: 2,
|
|
botApiRequests: [{ method: "sendMessage", payload: { text: `${lane}-payload` } }],
|
|
cleanupErrors: [],
|
|
invocations,
|
|
lane,
|
|
observation: { events, truncated: false, uptimeMs: 1_500 },
|
|
providerRequests: [{ input: `${lane}-provider-input` }],
|
|
schemaVersion: 2,
|
|
sendCount: 1,
|
|
status: "complete",
|
|
sutAttestation: { lane, sha },
|
|
};
|
|
const file = path.join(root, `${lane}.json`);
|
|
writeFileSync(file, `${JSON.stringify(facts)}\n`);
|
|
writeFileSync(path.join(published, "mantis-lane-facts.json"), `${JSON.stringify(facts)}\n`);
|
|
writeFileSync(path.join(published, "attempt-1-facts.json"), '{"status":"aborted"}\n');
|
|
return file;
|
|
}
|
|
|
|
function runCollector(options?: { candidateSha?: string }) {
|
|
const root = temp("mantis-open-proof-");
|
|
const output = path.join(root, "evidence");
|
|
const events = [
|
|
{ actor: "bot", kind: "typing", active: true },
|
|
{ actor: "user", kind: "reaction", emoji: "👍" },
|
|
{ actor: "bot", kind: "message", text: "done" },
|
|
];
|
|
const baselineFacts = writeLane(root, "baseline", BASELINE_SHA, events, [
|
|
{ command: "exec", args: { command: "replace every gateway setting" } },
|
|
]);
|
|
const candidateFacts = writeLane(root, "candidate", CANDIDATE_SHA, events, [
|
|
{ command: "desktop", args: { actionsFile: "different-experiment.json" } },
|
|
{ command: "restart", args: {} },
|
|
]);
|
|
const agentManifest = path.join(root, "agent-evidence.json");
|
|
writeFileSync(
|
|
agentManifest,
|
|
`${JSON.stringify({
|
|
schemaVersion: 2,
|
|
id: "telegram-visible-proof",
|
|
title: "Mantis Telegram proof — PASS",
|
|
summary: "The unrestricted experiment proved the repair.",
|
|
scenario: "Different adaptive experiments on main and candidate.",
|
|
comparison: {
|
|
baseline: {
|
|
expected: "Reproduce the defect.",
|
|
detail: "The defect reproduced.",
|
|
expectationMet: true,
|
|
},
|
|
candidate: {
|
|
expected: "Confirm the repair.",
|
|
detail: "The repair held.",
|
|
expectationMet: true,
|
|
},
|
|
differential: "The recorded Telegram and SUT evidence differs materially.",
|
|
outcome: "pass",
|
|
pass: true,
|
|
},
|
|
})}\n`,
|
|
);
|
|
execFileSync(
|
|
process.execPath,
|
|
[
|
|
SCRIPT,
|
|
"collect",
|
|
"--agent-manifest",
|
|
agentManifest,
|
|
"--baseline-facts",
|
|
baselineFacts,
|
|
"--baseline-sha",
|
|
BASELINE_SHA,
|
|
"--candidate-facts",
|
|
candidateFacts,
|
|
"--candidate-sha",
|
|
options?.candidateSha ?? CANDIDATE_SHA,
|
|
"--published-root",
|
|
path.join(root, "published"),
|
|
"--output-dir",
|
|
output,
|
|
],
|
|
{ stdio: "pipe" },
|
|
);
|
|
return { output, root };
|
|
}
|
|
|
|
describe("Mantis open-ended Telegram proof collector", () => {
|
|
it("accepts adaptive lane programs and preserves every recorded fact", () => {
|
|
const { output } = runCollector();
|
|
const manifest = JSON.parse(readFileSync(path.join(output, "mantis-evidence.json"), "utf8"));
|
|
const baseline = JSON.parse(
|
|
readFileSync(path.join(output, "baseline", "mantis-lane-facts.json"), "utf8"),
|
|
);
|
|
const candidate = JSON.parse(
|
|
readFileSync(path.join(output, "candidate", "mantis-lane-facts.json"), "utf8"),
|
|
);
|
|
|
|
expect(manifest.comparison.outcome).toBe("pass");
|
|
expect(manifest.artifacts.map((artifact: { path: string }) => artifact.path)).toContain(
|
|
"baseline/attempt-1-facts.json",
|
|
);
|
|
expect(baseline.observation.events.map((event: { kind: string }) => event.kind)).toEqual([
|
|
"typing",
|
|
"reaction",
|
|
"message",
|
|
]);
|
|
expect(candidate.invocations).not.toEqual(baseline.invocations);
|
|
expect(candidate.providerRequests).toHaveLength(1);
|
|
expect(candidate.botApiRequests).toHaveLength(1);
|
|
expect(
|
|
manifest.artifacts.find(
|
|
(artifact: { path: string }) => artifact.path === "baseline/baseline-previewGifCropped.gif",
|
|
),
|
|
).toMatchObject({ inline: true, kind: "timeline" });
|
|
|
|
const comment = renderEvidenceComment({
|
|
manifest: loadEvidenceManifest(path.join(output, "mantis-evidence.json")),
|
|
marker: "<!-- mantis-telegram-visible-proof -->",
|
|
rawBase: "https://qa.openclaw.ai/mantis/telegram-visible/run-1",
|
|
});
|
|
expect(comment).toContain(
|
|
'<img src="https://qa.openclaw.ai/mantis/telegram-visible/run-1/baseline/baseline-previewGifCropped.gif"',
|
|
);
|
|
expect(comment).not.toContain(
|
|
'<img src="https://qa.openclaw.ai/mantis/telegram-visible/run-1/baseline/baseline-screenshot.png"',
|
|
);
|
|
});
|
|
|
|
it("rejects evidence whose independently recorded revision does not match", () => {
|
|
expect(() => runCollector({ candidateSha: "c".repeat(40) })).toThrow(
|
|
"candidate SUT attestation does not match",
|
|
);
|
|
});
|
|
});
|