218 lines
7.2 KiB
JavaScript
218 lines
7.2 KiB
JavaScript
// 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),
|
||
};
|
||
}
|