paperclip/packages/paperclip-runner/scripts/public-eval-chat.mjs

218 lines
7.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Public replay is a new DTO, never a recursive copy of a provider artifact.
export const PUBLIC_CHAT_SCHEMA =
"paperclip.runner-protocol-eval.public-chat/v1";
export const PUBLIC_CHAT_NOTICE =
"Public replay of an isolated mock eval. Conversation text is scrubbed; provider identities, tool payloads, traces, and company-state snapshots are withheld. Full evidence remains in the access-controlled Actions artifact.";
export const SECRET_TEXT = [
/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/gu,
/\bsk-[A-Za-z0-9_-]{16,}\b/gu,
/\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{16,}\b/gu,
/\bBearer\s+[A-Za-z0-9._~+\/-]{8,}=*/giu,
/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gu,
/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu,
/\b(?:https?|file|s3):\/\/[^\s<>"')]+/giu,
/\barn:aws[^\s<>"')]+/gu,
/(?:\/(?:Users|home|tmp|private|var)\/|[A-Z]:\\)[^\s<>"')]+/gu,
/\b(?:api[_-]?key|access[_-]?token|secret|password|authorization|cookie)\s*[=:]\s*[^\s,;]+/giu,
];
export function publicText(value, privateValues = []) {
let text = typeof value === "string" ? value : "";
for (const secret of privateValues) {
if (typeof secret === "string" && secret.length >= 8)
text = text.replaceAll(secret, "[redacted]");
}
for (const pattern of SECRET_TEXT) text = text.replace(pattern, "[redacted]");
return text.length > 40_000 ? `${text.slice(0, 40_000)}\n[truncated]` : text;
}
function privateIdentities(value, found = new Set()) {
if (value && typeof value === "object") {
for (const [key, child] of Object.entries(value)) {
if (
/(?:session|profile|account|runtime|endpoint|memory|agentversion).*id$|arn$|token$|secret$|password$/i.test(
key,
) &&
typeof child === "string"
)
found.add(child);
else if (child && typeof child === "object")
privateIdentities(child, found);
}
}
return [...found];
}
function timestamp(value) {
return typeof value === "string" && /^\d{4}-\d\d-\d\dT[\d:.]+Z$/.test(value)
? value
: "1970-01-01T00:00:00.000Z";
}
function operation(value) {
return typeof value === "string" && /^[a-z][a-z_]{0,79}$/.test(value)
? value
: "unknown_operation";
}
export function publicChatView(artifact, evalCase) {
const privateValues = privateIdentities(artifact);
const scrub = (value) => publicText(value, privateValues);
const network = artifact.snapshot?.networkEvidence;
// Only the dedicated mock eval boundary can publish recorded conversation.
// Early infrastructure failures still receive a viewer with an honest notice.
const isolated =
network?.realPaperclipRequests === 0 &&
Array.isArray(network?.childPaperclipEnvironmentKeys) &&
network.childPaperclipEnvironmentKeys.length === 0;
const source =
isolated &&
artifact.issueThread?.schema === "paperclip.capability.issue-thread-view.v1"
? artifact.issueThread
: null;
const evidence = Object.fromEntries(
[
"tools",
"calls",
"authorization",
"control_plane",
"runner",
"state",
"traceability",
"parity",
].map((key) => [key, []]),
);
const turns = (source?.turns ?? []).map((turn, turnIndex) => {
const turnId = `public-turn-${turnIndex + 1}`;
const items = [];
for (const item of turn.items ?? []) {
const base = {
id: `public-item-${turnIndex + 1}-${items.length + 1}`,
at: timestamp(item.at),
};
if (
["user_message", "agent_message", "durable_comment"].includes(item.kind)
) {
items.push({
...base,
kind: item.kind === "user_message" ? "user_message" : "agent_message",
author: item.kind === "user_message" ? "You (eval prompt)" : "Agent",
body: scrub(item.body),
streaming: false,
});
} else if (item.kind === "tool_activity") {
const operationId = operation(item.operationId);
const status = ["ok", "denied", "running"].includes(item.status)
? item.status
: "running";
const result = {
outcome: status,
detail: "Tool payload withheld from public replay.",
};
const recordId = `public-call-${turnIndex + 1}-${items.length + 1}`;
items.push({
...base,
kind: "tool_activity",
operationId,
status,
summary: `${operationId}: ${status}`,
input: { detail: "Arguments withheld from public replay." },
result,
evidenceRef: { section: "calls", recordId },
});
if (status !== "running")
evidence.calls.push({
id: recordId,
turnId,
operationId,
version: 1,
providerRequest: operationId,
dispatchedCommand: operationId,
outcome: status,
result,
redactions: ["arguments", "result payload", "provider identities"],
threadAnchorId: base.id,
});
}
// Provider activity, reasoning, raw events, file refs and unrecognized
// future item kinds are deliberately not part of the public contract.
}
return {
id: turnId,
ordinal: turnIndex + 1,
mode: "replay",
toolCallCount: items.filter((item) => item.kind === "tool_activity")
.length,
at: timestamp(turn.at),
stoppedByUser: turn.stoppedByUser === true,
items,
};
});
if (!turns.some((turn) => turn.items.length > 0)) {
turns.length = 0;
turns.push({
id: "public-turn-1",
ordinal: 1,
mode: "replay",
toolCallCount: 0,
at: timestamp(artifact.snapshot?.createdAt),
stoppedByUser: false,
items: [
{
id: "public-notice",
at: timestamp(artifact.snapshot?.createdAt),
kind: "system_notice",
glyph: "",
text: "No publishable conversation was recorded for this attempt. See the checks and the access-controlled artifact for diagnostics.",
evidenceRef: { section: "runner", recordId: "public-notice" },
},
],
});
}
return {
schema: "paperclip.capability.issue-thread-view.v1",
sessionId: "public-report",
mode: "replay",
identity: {
agentLabel: "Recorded agent",
runnerLabel: "Recorded runner",
runnerAttached: false,
controlPlaneLabel: "Mock Paperclip",
controlPlaneTooltip: PUBLIC_CHAT_NOTICE,
replaySource: "live",
},
issue: {
identifier: "EVAL",
title: scrub(evalCase.title || evalCase.id),
status: [
"backlog",
"todo",
"in_progress",
"in_review",
"done",
"blocked",
"cancelled",
].includes(source?.issue?.status)
? source.issue.status
: "in_review",
priority: "medium",
assignee: null,
runState: "Read-only public replay",
scenarioId: evalCase.id,
fixtureProfile: evalCase.id,
},
turns,
composer: {
state: "disabled",
helper: null,
reason: "Read-only eval report",
pendingInteractionId: null,
},
evidence,
connection: { state: "closed", attempt: 0 },
replay: null,
renderedAt: timestamp(source?.renderedAt || artifact.snapshot?.createdAt),
};
}