diff --git a/tests/runner-e2e/chat-catalog-domains/integrator-skill-review-visible.test.ts b/tests/runner-e2e/chat-catalog-domains/integrator-skill-review-visible.test.ts
index be87eade060..a12afbbe2c8 100644
--- a/tests/runner-e2e/chat-catalog-domains/integrator-skill-review-visible.test.ts
+++ b/tests/runner-e2e/chat-catalog-domains/integrator-skill-review-visible.test.ts
@@ -1,6 +1,6 @@
// @vitest-environment jsdom
import { afterEach, beforeEach, expect, it, vi } from "vitest";
-import { readVisibleSkillReview, waitForVisibleSkillReview } from "./integrator-skill-update.js";
+import { readVisibleSkillReview, waitForVisibleSkillReview, collectVisibleSkillReviewFallback } from "./integrator-skill-update.js";
const input = { runId: "run", humanCommentId: "human" };
const thread = (body: string) => ``;
const response = '
Installed 0.4Regression tests; split forms into smaller steps. No update applied yet.
';
@@ -29,11 +29,16 @@ it("waits for eventual current response hydration within the original deadline o
document.body.innerHTML = thread("");
const evaluate = vi.fn(async () => readVisibleSkillReview(input));
const ctx = { observationDeadlineAt: 2_000, input: { page: { evaluate } } } as unknown as Parameters[0];
- const pending = waitForVisibleSkillReview(ctx, input);
+ const summary = "Installed 0.4. Regression tests and forms split into small steps.";
+ const base = { reply: "See the complete diff above. No update applied yet.", companyId: "company", leadId: "ceo",
+ runs: [{ id: "run", companyId: "company", agentId: "ceo", status: "succeeded", runtimeMode: "native", nativeIssueId: "source",
+ contextSnapshot: { issueId: "source", commentId: "human" }, resultJson: { summary, finalizationPhase: "committed", finalizationReasonCode: "conversation_turn_finished",
+ prpRunTerminalState: "succeeded", prpTurnTerminalState: "completed", nativeResult: { schema: "paperclip.run_result.v1", reportedWorkDisposition: "done", summary } } }] } as unknown as Parameters[1];
+ const pending = collectVisibleSkillReviewFallback(ctx, base, "source");
await vi.advanceTimersByTimeAsync(250);
document.body.innerHTML = thread(response);
await vi.advanceTimersByTimeAsync(250);
- expect(await pending).toContain("Installed 0.4");
+ expect((await pending)?.visibleText).toContain("Installed 0.4");
expect(evaluate).toHaveBeenCalledTimes(3);
expect(ctx.observationDeadlineAt).toBe(2_000);
document.body.innerHTML = thread(response.replace('run:turn:0', 'foreign:turn:0'));
diff --git a/tests/runner-e2e/chat-catalog-domains/integrator-skill-update.test.ts b/tests/runner-e2e/chat-catalog-domains/integrator-skill-update.test.ts
index fadeed53bfe..9f2e1eaebb3 100644
--- a/tests/runner-e2e/chat-catalog-domains/integrator-skill-update.test.ts
+++ b/tests/runner-e2e/chat-catalog-domains/integrator-skill-update.test.ts
@@ -1,7 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import { SkillSourceUpdateReadError } from "../api.js";
import { readFileSync } from "node:fs";
-import { accessibilityUpdatePins as pins, gradeIntegratorSkillUpdate, requireSkillUpdatePrerequisite, integratorSkillUpdateCases, acceptedSkillReviewRun } from "./integrator-skill-update.js";
+import { accessibilityUpdatePins as pins, gradeIntegratorSkillUpdate, requireSkillUpdatePrerequisite, integratorSkillUpdateCases, acceptedSkillReviewRun, collectVisibleSkillReviewFallback } from "./integrator-skill-update.js";
// Unmodified MIT-licensed source bytes, with LICENSE.md in the same fixture
// directory. Product setup still fetches the actual GitHub commit snapshots.
import { createHash } from "node:crypto";
@@ -46,6 +46,15 @@ describe("genuine immutable public skill update qualification", () => {
expect(gradeIntegratorSkillUpdate(changed).checks.find(check => check.id === "review-diff-before-update")?.passed).toBe(false);
});
+ it("keeps a complete persisted preview on the original path without any DOM read", async () => {
+ const first = valid(bytes.old, bytes.next).state.reviewCheckpoint;
+ const evaluate = vi.fn(async () => { throw new Error("No additional native bubble exists"); });
+ const ctx = { input: { page: { evaluate } } } as unknown as Parameters[0];
+ expect(await collectVisibleSkillReviewFallback(ctx, first, "source")).toBeUndefined();
+ expect(evaluate).not.toHaveBeenCalled();
+ expect(gradeIntegratorSkillUpdate(valid(bytes.old, bytes.next)).passed).toBe(true);
+ });
+
it("admits only a visible committed current-run review alongside the shorter final comment", () => {
const value = valid(bytes.old, bytes.next);
const first = value.state.reviewCheckpoint as typeof value.state.reviewCheckpoint & { visibleReview?: unknown };
diff --git a/tests/runner-e2e/chat-catalog-domains/integrator-skill-update.ts b/tests/runner-e2e/chat-catalog-domains/integrator-skill-update.ts
index 0a0399a3ef6..b538f2fa615 100644
--- a/tests/runner-e2e/chat-catalog-domains/integrator-skill-update.ts
+++ b/tests/runner-e2e/chat-catalog-domains/integrator-skill-update.ts
@@ -84,12 +84,7 @@ async function observeUpdate(ctx: ChatCatalogContext, state: Record !/^update /i.test(match[0]) || !/\bno\s+$/i.test(reply.slice(0, match.index)));
}
+function previewNarrativeCorrect(reply: string) {
+ return (reply.includes(accessibilityUpdatePins.oldCommit.slice(0, 7)) || /(?:installed|current|currently)[^\n]{0,80}\b0\.4\b|\b0\.4\b[^\n]{0,80}(?:installed|current)/i.test(reply))
+ && substantiveDiff(reply) && !claimsAppliedUpdate(reply);
+}
+export async function collectVisibleSkillReviewFallback(ctx: ChatCatalogContext, base: InternalObservation, issueId: string): Promise {
+ // A complete persisted review is already the ordinary user-facing answer.
+ // Its deduplicated native bubble need not exist for the original path to pass.
+ if (previewNarrativeCorrect(base.reply)) return undefined;
+ const run = acceptedSkillReviewRun(base, issueId);
+ if (!run) return undefined;
+ const humanCommentId = run.contextSnapshot!.commentId as string;
+ const visibleText = await waitForVisibleSkillReview(ctx, { runId: run.id, humanCommentId });
+ return { runId: run.id, issueId, humanCommentId, summary: run.resultJson!.summary as string, visibleText };
+}
function previewCorrect(o: UpdateObservation) {
const before = o.state.upstream as CompanySkillDetail;
const reply = skillReviewPreviewText(o);
return o.upstream.currentVersionId === before.currentVersionId && o.upstream.sourceRef === accessibilityUpdatePins.oldCommit && hash(o.upstream.markdown) === accessibilityUpdatePins.oldSha256
&& o.source.lastScanCommit === accessibilityUpdatePins.oldCommit && o.source.trackingRef === accessibilityUpdatePins.newCommit && o.source.automaticUpdates === false
&& o.updateStatus.supported && o.updateStatus.hasUpdate && o.updateStatus.currentRef === accessibilityUpdatePins.oldCommit && o.updateStatus.latestRef === accessibilityUpdatePins.newCommit
- && (reply.includes(accessibilityUpdatePins.oldCommit.slice(0, 7)) || /(?:installed|current|currently)[^\n]{0,80}\b0\.4\b|\b0\.4\b[^\n]{0,80}(?:installed|current)/i.test(reply))
- && substantiveDiff(reply) && !claimsAppliedUpdate(reply);
+ && previewNarrativeCorrect(reply);
}
export function gradeIntegratorSkillUpdate(value: unknown) {
const c = checks(); const o = updateObservation(value); c.add("real-tracked-source-and-saved-versions", !!o, "Requires actual public GitHub source tracking, installed snapshot versions and preserved editable fork."); if (!o) return c.result();