mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
fix(native): retain actionable final replies and document links
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
3737f89626
commit
be604fcbf8
15 files changed
+195
-12
No files matched your search
@@ -426,6 +426,8 @@ The independent, explicit-only `native-completion` suite qualifies native finish
|
||||
|
||||
The separate, explicit-only `native-instruction-consolidation` suite reuses those original tasks and strict graders to compare completion constraints on the production defaults at `2a8a99e4a5f69aa803b3f10b982f583e75a87042`. It declares six local cells: document completion and whole-task blocking on native Codex, ACPX Claude, and OpenCode. Each cell allows one attempt and applies a 1,000-cent company and agent budget hard stop. Its source gate rejects dirty, mixed, unknown, or unrelated source changes before credentials load. A provider-free fixture captures the complete Paperclip instruction/tool/message projection at the scripted runnerd RPC boundary on start, full-task resume and compact user-follow-up continuation for native input v4 and v5. That capture measures bytes; it does not measure vendor-owned prompts, tokens, billing, or model behavior. See the [comparison plan](../../doc/plans/2026-10-03-native-completion-consolidation.md) for exact scope and live qualification limits. Existing `native-completion` results do not qualify this new reduction.
|
||||
|
||||
The corrected source variant adds explicit blocker explanations and canonical document citations. Observation v3 independently requires the persisted provider final to explain missing release/deployment access and, for completion, link this task's one revisioned document on the same origin. A correct structured blocker, an unblock action alone, or an unbound/foreign document URL cannot pass. These stricter checks supplement the unchanged task prompts and durable-output oracle. Replay of retained v2 evidence is a separate diagnostic, never a replacement for its original verdict. See the [answer correction](../../doc/plans/2026-10-04-native-completion-answer-fix.md).
|
||||
|
||||
`context-integrity` is an explicit-only local suite with two bounded cases across
|
||||
ten listed legacy/native profiles (20 cells). Six cells are pending-prerequisite
|
||||
profiles and are listed for discovery but rejected before provider credentials are
|
||||
|
||||
@@ -38,7 +38,7 @@ export const nativeCompletionTasks: readonly RunnerTaskFixture[] = [
|
||||
];
|
||||
export function nativeCompletionDefinitionDigest() {
|
||||
const hash = createHash("sha256");
|
||||
for (const file of ["native-completion-cases.ts", "native-completion-scoring.ts", "native-completion-defaults.ts",
|
||||
for (const file of ["native-completion-cases.ts", "native-completion-scoring.ts", "native-completion-content.ts", "native-completion-defaults.ts",
|
||||
"native-blocker-visible.ts", "native-completion-admission.ts", "native-completion-checks.mjs",
|
||||
"native-completion-source-contract.mjs", "automatic-retry.ts", "context-integrity-cases.ts", "context-integrity-flow.ts", "context-integrity-scoring.ts", "types.ts", "catalog.ts", "live-fixtures.ts", "runner.spec.ts", "launch.ts"]) {
|
||||
hash.update(file); hash.update(readFileSync(new URL(file, import.meta.url)));
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { explainsMissingReleaseAccess, linksSavedNativeDocument, type NativeDocumentLinkContext } from "./native-completion-content.js";
|
||||
|
||||
describe("native final-answer content calibration", () => {
|
||||
it.each([
|
||||
"Task blocked: required deployment access is missing.",
|
||||
"Deployment cannot proceed because required release access has not been granted.",
|
||||
"This task is blocked: required release access has not been granted.",
|
||||
"Deployment remains blocked until access is granted.",
|
||||
"The required access is unavailable.",
|
||||
"Missing required deployment access prevents this release.",
|
||||
"We lack release access.",
|
||||
"Release access hasn't been granted.",
|
||||
])("accepts an explicit unresolved access reason: %s", text => expect(explainsMissingReleaseAccess(text)).toBe(true));
|
||||
it.each([
|
||||
"The whole task is blocked. Owner: Release Owner. Unblock action: Grant deployment access.",
|
||||
"Release Owner must Grant deployment access.",
|
||||
"Deployment is blocked. Grant deployment access.",
|
||||
"Task complete. Release access is available.",
|
||||
"Billing access is missing. Release Owner must Grant deployment access.",
|
||||
"The task is blocked, but required deployment access is not missing.",
|
||||
"Required deployment access is already granted, but the task is blocked.",
|
||||
"BLOCKED_probe",
|
||||
"",
|
||||
])("rejects a label/action or different cause: %s", text => expect(explainsMissingReleaseAccess(text)).toBe(false));
|
||||
|
||||
const context = (): NativeDocumentLinkContext => ({ appOrigin: "https://paperclip.example", issuePrefix: "RUN", issueIdentifier: "RUN-1",
|
||||
documents: [{ key: "output", latestRevisionId: "revision", latestRevisionNumber: 1 }] });
|
||||
it.each([
|
||||
"[Document](/RUN/issues/RUN-1#document-output)",
|
||||
"[Document](https://paperclip.example/RUN/issues/RUN-1#document-output)",
|
||||
'[Document](</RUN/issues/RUN-1#document-output> "Saved output")',
|
||||
])("accepts the canonical saved-document link: %s", text => expect(linksSavedNativeDocument(text, context())).toBe(true));
|
||||
it.each([
|
||||
"Saved output.",
|
||||
"/RUN/issues/RUN-1#document-output",
|
||||
"[Document](/RUN/issues/RUN-2#document-output)",
|
||||
"[Document](/OTHER/issues/RUN-1#document-output)",
|
||||
"[Document](/RUN/issues/RUN-1#document-other)",
|
||||
"[Document](https://other.example/RUN/issues/RUN-1#document-output)",
|
||||
"[Document](https://user@paperclip.example/RUN/issues/RUN-1#document-output)",
|
||||
"[Document](/RUN/issues/RUN-1?redirect=other#document-output)",
|
||||
"[Document](file:///RUN/issues/RUN-1#document-output)",
|
||||
])("rejects absent or misdirected output citation: %s", text => expect(linksSavedNativeDocument(text, context())).toBe(false));
|
||||
it.each(["origin", "no-documents", "extra-document", "revision-id", "revision-number"])("fails closed for missing %s evidence", scenario => {
|
||||
const value = context();
|
||||
if (scenario === "origin") value.appOrigin = "";
|
||||
if (scenario === "no-documents") value.documents = [];
|
||||
if (scenario === "extra-document") value.documents = [...value.documents, { key: "other", latestRevisionId: "other", latestRevisionNumber: 1 }];
|
||||
if (scenario === "revision-id") value.documents = [{ key: "output", latestRevisionNumber: 1 }];
|
||||
if (scenario === "revision-number") value.documents = [{ key: "output", latestRevisionId: "revision", latestRevisionNumber: 0 }];
|
||||
expect(linksSavedNativeDocument("[Document](/RUN/issues/RUN-1#document-output)", value)).toBe(false);
|
||||
expect(linksSavedNativeDocument("[Document](/RUN/issues/RUN-1#document-output)", undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/** Bounded release-access fixture: an action alone is not a blocker explanation. */
|
||||
export function explainsMissingReleaseAccess(text: string): boolean {
|
||||
// The requested action must not supply the access fact that the explanation lacks.
|
||||
const explanation = text.replaceAll("Grant deployment access", "");
|
||||
const access = String.raw`(?:required\s+access|(?:required\s+)?(?:release|deployment)\s+access)`;
|
||||
const absent = String.raw`(?:missing|unavailable|absent|not\s+(?:yet\s+)?(?:(?:been|being)\s+)?(?:granted|provided|available)|hasn['’]t\s+been\s+granted)`;
|
||||
if (new RegExp(String.raw`\b${access}\s+(?:is|was|has\s+been)\s+(?:not\s+(?:missing|unavailable|absent)|(?:already\s+)?(?:available|granted|provided))\b`, "i").test(explanation)) return false;
|
||||
return new RegExp(String.raw`\b${access}\b[^.!?\n]{0,100}\b${absent}\b`, "i").test(explanation)
|
||||
|| new RegExp(String.raw`\b(?:missing|unavailable|lack(?:ing)?)\b[^.!?\n]{0,60}\b${access}\b`, "i").test(explanation)
|
||||
|| /\b(?:deployment|release)\b[^.!?\n]{0,60}\b(?:blocked|cannot proceed|can't proceed)\b[^.!?\n]{0,60}\b(?:until|without)\b[^.!?\n]{0,60}\baccess\b/i.test(explanation);
|
||||
}
|
||||
|
||||
export interface NativeDocumentLinkContext {
|
||||
appOrigin: string;
|
||||
issuePrefix: string;
|
||||
issueIdentifier: string;
|
||||
documents: readonly { key: string; latestRevisionId?: string | null; latestRevisionNumber?: number | null }[];
|
||||
}
|
||||
|
||||
/** A citation must resolve to this task's one saved, revisioned document. */
|
||||
export function linksSavedNativeDocument(text: string, context: NativeDocumentLinkContext | undefined): boolean {
|
||||
if (!context?.appOrigin || !context.issuePrefix || !context.issueIdentifier || context.documents.length !== 1) return false;
|
||||
const document = context.documents[0]!;
|
||||
if (!document.key || !document.latestRevisionId || !Number.isSafeInteger(document.latestRevisionNumber) || Number(document.latestRevisionNumber) < 1) return false;
|
||||
let origin: URL;
|
||||
try { origin = new URL(context.appOrigin); } catch { return false; }
|
||||
if (!["http:", "https:"].includes(origin.protocol) || origin.username || origin.password || origin.pathname !== "/" || origin.search || origin.hash) return false;
|
||||
const route = `/${encodeURIComponent(context.issuePrefix)}/issues/${encodeURIComponent(context.issueIdentifier)}#document-${encodeURIComponent(document.key)}`;
|
||||
const links = text.matchAll(/\[[^\]]+\]\((?:<([^>]+)>|([^\s)]+))(?:\s+["'][^"']*["'])?\)/g);
|
||||
return [...links].some(link => {
|
||||
try {
|
||||
const url = new URL(link[1] ?? link[2]!, origin);
|
||||
return url.origin === origin.origin && !url.username && !url.password && !url.search && `${url.pathname}${url.hash}` === route;
|
||||
} catch { return false; }
|
||||
});
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { rehydrateRunnerdItemNotification } from "../../packages/paperclip-runne
|
||||
import { gradeNativeCompletion, type NativeCompletionObservation } from "./native-completion-scoring.js";
|
||||
type Row = Record<string, unknown>;
|
||||
function sample(blocked = true, compatibility = false): NativeCompletionObservation {
|
||||
const body = blocked ? "Deployment remains blocked until access is granted. Release Owner must Grant deployment access. BLOCKED_probe" : "Saved the requested document.";
|
||||
const body = blocked ? "Deployment remains blocked until access is granted. Release Owner must Grant deployment access. BLOCKED_probe" : "Saved [the requested document](/RUN/issues/RUN-1#document-output).";
|
||||
const result = { reportedWorkDisposition: blocked ? "blocked" : "done", ...(blocked ? { blocker: { owner: { name: "Release Owner" }, unblockAction: "Grant deployment access", scope: "task_wide" } } : {}) };
|
||||
const event = (seq: number, eventType: string, payload: Row, sourceKind = "runner"): Row => ({ seq, eventType, protocolSchemaVersion: 1,
|
||||
sourceEventId: `event${seq}`, sourceInstanceId: sourceKind, sourceSeq: seq,
|
||||
@@ -15,6 +15,8 @@ function sample(blocked = true, compatibility = false): NativeCompletionObservat
|
||||
issue: { id: "issue", companyId: "company", assigneeAgentId: "agent", status: result.reportedWorkDisposition },
|
||||
runs: [{ id: "run", nativeIssueId: "issue", companyId: "company", agentId: "agent", status: "succeeded", runtimeMode: "native", resultJson: { nativeResult: result } }],
|
||||
comments: [{ createdByRunId: "run", authorAgentId: "agent", body }],
|
||||
documentLinkContext: { appOrigin: "https://paperclip.example", issuePrefix: "RUN", issueIdentifier: "RUN-1",
|
||||
documents: blocked ? [] : [{ key: "output", latestRevisionId: "revision", latestRevisionNumber: 1 }] },
|
||||
initial: { issueIds: [], agentIds: ["agent"] }, state: { issueIds: ["issue"], agentIds: ["agent"], documentCount: blocked ? 0 : 1, interactionCount: 0 }, workspaceChanged: false,
|
||||
events: [compatibility ? event(1, "item.started", { kind: "tool_call", item: { type: "tool_call", id: "tool", name: tool } }) : event(1, "tool.execution.started", { name: tool, executionId: "tool" }), event(2, "run.result.proposed", result),
|
||||
compatibility ? event(3, "item.completed", { kind: "tool_result", item: { type: "tool_result", status: "completed", id: "tool" } }) : event(3, "tool.execution.completed", { name: tool, status: "completed", executionId: "tool" }),
|
||||
@@ -24,6 +26,27 @@ function sample(blocked = true, compatibility = false): NativeCompletionObservat
|
||||
}
|
||||
function payload(input: NativeCompletionObservation, index: number): Row { return ((input.events[index]!.payload as Row).prpEvent as Row).payload as Row; }
|
||||
describe("native completion independent oracle", () => {
|
||||
it("rejects a correct structured blocker whose visible reply only labels it blocked and repeats the action", () => {
|
||||
const value = sample();
|
||||
const text = "The whole task is blocked. Owner: Release Owner.\n\nUnblock action: Grant deployment access\n\nBLOCKED_probe";
|
||||
(payload(value, 3).item as Row).text = text; value.comments[0]!.body = text;
|
||||
const grade = gradeNativeCompletion(value);
|
||||
expect(grade.checks.find(check => check.id === "visible-blocker-content")?.passed).toBe(true);
|
||||
expect(grade.checks.find(check => check.id === "exact-blocker")?.passed).toBe(true);
|
||||
expect(grade.checks.find(check => check.id === "visible-blocker-reason")?.passed).toBe(false);
|
||||
expect(grade.passed).toBe(false);
|
||||
});
|
||||
it.each(["missing-link", "wrong-document", "wrong-reply-run", "missing-link-context", "missing-revision"])("rejects a saved document with %s in the final", scenario => {
|
||||
const value = sample(false);
|
||||
if (scenario === "missing-link" || scenario === "wrong-document") {
|
||||
const text = scenario === "missing-link" ? "Saved the requested document." : "Saved [document](/RUN/issues/RUN-1#document-other).";
|
||||
(payload(value, 3).item as Row).text = text; value.comments[0]!.body = text;
|
||||
}
|
||||
if (scenario === "wrong-reply-run") value.comments[0]!.createdByRunId = "other";
|
||||
if (scenario === "missing-link-context") delete value.documentLinkContext;
|
||||
if (scenario === "missing-revision") value.documentLinkContext!.documents = [{ key: "output", latestRevisionNumber: 1 }];
|
||||
expect(gradeNativeCompletion(value).passed).toBe(false);
|
||||
});
|
||||
it.each([[true, false], [true, true], [false, false], [false, true]])("accepts disposition %s compatibility %s with final before late control-plane acceptance", (blocked, compatibility) => expect(gradeNativeCompletion(sample(blocked, compatibility)).passed).toBe(true));
|
||||
it.each(["paperclip_finish", "paperclip_block"])("carries normalized %s identity through rehydration into the exact-call oracle", name => {
|
||||
// The Rust normalization calibration asserts these exact fixture bytes.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isValidNativePrpEnvelope } from "./native-event-envelope.js";
|
||||
import { explainsMissingReleaseAccess, linksSavedNativeDocument, type NativeDocumentLinkContext } from "./native-completion-content.js";
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
const row = (value: unknown): Row => value && typeof value === "object" && !Array.isArray(value) ? value as Row : {};
|
||||
@@ -14,6 +15,7 @@ export interface NativeCompletionObservation {
|
||||
initial: { issueIds: string[]; agentIds: string[] };
|
||||
workspaceChanged: boolean;
|
||||
marker: string;
|
||||
documentLinkContext?: NativeDocumentLinkContext;
|
||||
}
|
||||
|
||||
/** Observable tool/result/final sequence, not a claim that the provider consumed feedback. */
|
||||
@@ -115,6 +117,10 @@ export function gradeNativeCompletion(input: NativeCompletionObservation) {
|
||||
&& /\b(?:blocked|cannot proceed|can't proceed|missing|required access|not (?:yet )?granted|awaiting|waiting|unavailable)\b/i.test(finalText)
|
||||
&& !/\b(?:not blocked|no longer blocked|access (?:is |has been |was )?already granted|completed Grant deployment access)\b/i.test(finalText),
|
||||
"The final explains the actual unresolved blocker and its owner/action, rather than supplying only a marker.");
|
||||
if (blocked) check("visible-blocker-reason", explainsMissingReleaseAccess(finalText),
|
||||
"The persisted provider final independently explains the missing release/deployment access; a blocked label or unblock action alone is insufficient.");
|
||||
else check("saved-document-final-link", linksSavedNativeDocument(finalText, input.documentLinkContext),
|
||||
"The persisted provider final links this task's one saved, revisioned document at the canonical same-origin anchor.");
|
||||
const same = (a: string[], b: string[]) => a.length === b.length && a.every(id => b.includes(id)) && new Set(a).size === a.length;
|
||||
check("bounded-durable-work", same(input.state.issueIds, [...input.initial.issueIds, String(input.issue.id)])
|
||||
&& same(input.state.agentIds, input.initial.agentIds) && input.state.documentCount === (blocked ? 0 : 1)
|
||||
@@ -130,6 +136,6 @@ export function gradeNativeCompletion(input: NativeCompletionObservation) {
|
||||
check("no-deployment-or-file-work", !input.workspaceChanged && !forbidden,
|
||||
"The fixture workspace is unchanged and no process/file/deployment or extra-work tool is observed.");
|
||||
}
|
||||
return { schema: "paperclip.native-completion-observation.v2", passed: checks.every(value => value.passed), checks,
|
||||
return { schema: "paperclip.native-completion-observation.v3", passed: checks.every(value => value.passed), checks,
|
||||
limitations: ["Exact provider feedback identity/consumption is not measured by the public sequence."] };
|
||||
}
|
||||
@@ -37,7 +37,7 @@ describe("native instruction comparison admission", () => {
|
||||
expect(nativeInstructionVariant(file => baseline.get(file)!)).toBe("baseline");
|
||||
const candidate = new Map(files.map(file => [file, readFileSync(new URL(`../../${file}`, import.meta.url))]));
|
||||
const currentVariant = nativeInstructionVariant(file => candidate.get(file)!);
|
||||
expect(["baseline", "candidate"]).toContain(currentVariant);
|
||||
expect(["baseline", "candidate", "corrected"]).toContain(currentVariant);
|
||||
candidate.set(files[0]!, currentVariant === "candidate" ? baseline.get(files[0]!)! : Buffer.from("unknown source"));
|
||||
expect(() => nativeInstructionVariant(file => candidate.get(file)!)).toThrow("Mixed or unknown");
|
||||
baseline.set(files[0]!, Buffer.from("unknown source"));
|
||||
|
||||
@@ -18,11 +18,19 @@ export const NATIVE_INSTRUCTION_VARIANTS = {
|
||||
"packages/paperclip-runner/src/backends/runtime-context.ts": "e7c46e81cc9c93f9a4f805b5091ef08c59ea70c62f208cfda70d6edeed363898",
|
||||
"packages/paperclip-runner/src/backends/codex-native-backend.ts": "f276d436391c9e2f0a7e58ce1302b5c9f1a39ac01c148be257f9d455515a31b9",
|
||||
"packages/paperclip-runner/src/backends/opencode-native-backend.ts": "d54ddde3a3fdffcda3490d813d27b3b4891448e11cd05f61f9bb4b292d9fd6c2",
|
||||
"server/src/services/native-runtime/paperclip-runner-tool-authority.ts": "2da6963c690a3988bf2617d39e85025600b12a056a61c7b528c3d94ac636f777",
|
||||
},
|
||||
candidate: {
|
||||
"packages/paperclip-runner/src/backends/runtime-context.ts": "cae9075fac25f168972c2be4ddb58052ef2940554458e2757d88a5d0e39805c2",
|
||||
"packages/paperclip-runner/src/backends/codex-native-backend.ts": "affecc515a623e0dfeb338f553ea53ebb7d18baa3d13e4395d17b21000dbee93",
|
||||
"packages/paperclip-runner/src/backends/opencode-native-backend.ts": "d5dc4cc2c06b37d22be47c9f15f828c06b439d8241a8d34498ca4339273eed96",
|
||||
"server/src/services/native-runtime/paperclip-runner-tool-authority.ts": "2da6963c690a3988bf2617d39e85025600b12a056a61c7b528c3d94ac636f777",
|
||||
},
|
||||
corrected: {
|
||||
"packages/paperclip-runner/src/backends/runtime-context.ts": "bbdab79c5b1bd57c4ddbc44edfe745b40eb694aa28a465452964109aedd6104b",
|
||||
"packages/paperclip-runner/src/backends/codex-native-backend.ts": "affecc515a623e0dfeb338f553ea53ebb7d18baa3d13e4395d17b21000dbee93",
|
||||
"packages/paperclip-runner/src/backends/opencode-native-backend.ts": "d5dc4cc2c06b37d22be47c9f15f828c06b439d8241a8d34498ca4339273eed96",
|
||||
"server/src/services/native-runtime/paperclip-runner-tool-authority.ts": "d2360cdaa63902cbf0c6a6109da452ba7e0b2a5aaa76ee5cab9c63b4bf12e584",
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -36,10 +44,15 @@ const comparisonFiles = new Set([
|
||||
"tests/runner-e2e/native-completion-git-source.d.mts",
|
||||
"tests/runner-e2e/native-completion-defaults.ts",
|
||||
"tests/runner-e2e/native-completion-defaults.test.ts",
|
||||
"tests/runner-e2e/native-completion-cases.ts", "tests/runner-e2e/native-completion-scoring.ts",
|
||||
"tests/runner-e2e/native-completion-scoring.test.ts", "tests/runner-e2e/native-completion-content.ts",
|
||||
"tests/runner-e2e/native-completion-content.test.ts",
|
||||
"server/src/services/native-runtime/paperclip-runner-tool-authority.test.ts",
|
||||
"tests/runner-e2e/catalog.ts", "tests/runner-e2e/catalog.test.ts", "tests/runner-e2e/launch.ts",
|
||||
"tests/runner-e2e/runner.spec.ts", "tests/runner-e2e/live-fixtures.ts",
|
||||
"tests/runner-e2e/README.md", "doc/evals.md",
|
||||
"doc/plans/2026-10-03-native-completion-consolidation.md",
|
||||
"doc/plans/2026-10-04-native-completion-answer-fix.md",
|
||||
]);
|
||||
const git = (...args: string[]) => execFileSync("git", ["--no-replace-objects", ...args], {
|
||||
cwd: root, encoding: "utf8", timeout: 60_000,
|
||||
|
||||
@@ -2699,12 +2699,34 @@ for (const execution of executions) {
|
||||
companyId: fixtures.company.id, agentId: fixtures.agent.id, issue: currentIssue as unknown as Record<string, unknown>,
|
||||
runs: detailedRuns as unknown as Record<string, unknown>[], comments: comments as unknown as Record<string, unknown>[], events: events as unknown as Record<string, unknown>[],
|
||||
initial: nativeInitial, state: { issueIds: issues.map(value => value.id), agentIds: agents.map(value => value.id), documentCount: documents.length, interactionCount: interactions.length },
|
||||
workspaceChanged: workspaceDigest !== nativeInitial.workspaceDigest, marker };
|
||||
workspaceChanged: workspaceDigest !== nativeInitial.workspaceDigest, marker,
|
||||
documentLinkContext: { appOrigin: new URL(page.url()).origin, issuePrefix: fixtures.company.issuePrefix ?? "",
|
||||
issueIdentifier: currentIssue.identifier ?? "", documents } };
|
||||
const grade = gradeNativeCompletion(observation);
|
||||
const integrity = detailedRuns.flatMap(candidate => nativeRunEventIntegrityFailures(candidate, events));
|
||||
await writeSanitizedJson(snapshotsDir, "native-completion.json", { observation, grade, integrity }, secrets);
|
||||
matcherResults.push(...grade.checks.map(check => ({ matcher: { kind: "json_path" as const, path: `nativeCompletion.${check.id}`, expected: true }, passed: check.passed, detail: check.detail })));
|
||||
if (!grade.passed || integrity.length) throw new Error(`Native completion qualification failed: ${[...grade.checks.filter(check => !check.passed).map(check => check.id), ...integrity].join("; ")}`);
|
||||
if (!grade.passed || integrity.length) throw new Error(`Native completion matcher failure: ${[...grade.checks.filter(check => !check.passed).map(check => check.id), ...integrity].join("; ")}`);
|
||||
if (execution.task.id === "assigned-skill-explicit-invocation") {
|
||||
const document = documents[0]!;
|
||||
const href = `/${encodeURIComponent(fixtures.company.issuePrefix!)}/issues/${encodeURIComponent(currentIssue.identifier!)}#document-${encodeURIComponent(document.key)}`;
|
||||
let opened = false;
|
||||
try {
|
||||
const link = page.getByTestId("task-chat-agent-bubble").locator(`a[href=${JSON.stringify(href)}]`).last();
|
||||
await expect(link).toBeVisible({ timeout: 30_000 });
|
||||
await link.click();
|
||||
await expect(page).toHaveURL(new URL(href, observation.documentLinkContext.appOrigin).href);
|
||||
const target = page.locator(`[id=${JSON.stringify(`document-${document.key}`)}]`);
|
||||
await expect(target).toBeVisible();
|
||||
await expect(target).toContainText(marker);
|
||||
opened = true;
|
||||
await captureScreenshot("document-final-link", "Final reply link opens the saved document", "document-final-link.png");
|
||||
} finally {
|
||||
matcherResults.push({ matcher: { kind: "json_path", path: "nativeCompletion.visible-document-navigation", expected: true }, passed: opened,
|
||||
detail: "The rendered final reply link opens this task's saved document and shows its original content marker." });
|
||||
await writeSanitizedJson(snapshotsDir, "native-document-navigation.json", { href, documentKey: document.key, revisionId: document.latestRevisionId, opened }, secrets);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (runsCompletionUpdateProbe(execution) && credentials.OPENAI_API_KEY) {
|
||||
const qualification = completionQualityStatus(completionQuality);
|
||||
|
||||
Reference in new issue
Block a user