Skip to content

Commit ecc8998

Browse files
committed
fix(webapp): replay keeps the recorded watch confirmation; resolved wording follows the presented category
1 parent 7fca9f5 commit ecc8998

4 files changed

Lines changed: 62 additions & 3 deletions

File tree

apps/webapp/app/services/dashboardAgentWatches.server.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,6 @@ export async function submitDashboardAgentWatch(params: {
669669
return settle({
670670
confirmation: watchingConfirmation({
671671
watchId: recorded.watchId,
672-
watch: await getWatch(dashboardAgentDb, { id: recorded.watchId }),
673672
unavailable: recorded.unavailable,
674673
// Recorded, never re-decided: the confirmation already in the transcript is
675674
// append-once, so a second decision here would contradict it forever.

apps/webapp/test/dashboardAgentWatches.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,6 +2450,37 @@ describe("the watch card submit", () => {
24502450
expect(await storedMessages(seeded, "chat_1")).toEqual(transcript);
24512451
}
24522452
);
2453+
2454+
postgresTest(
2455+
"a replay repeats the recorded 'Watching' confirmation after the watch has fired",
2456+
async ({ prisma, postgresContainer }) => {
2457+
await boot(prisma, postgresContainer.getConnectionUri());
2458+
const seeded = await seed(prisma, "submit-replay-fired");
2459+
await seedChat(seeded);
2460+
2461+
const first = await submit({ seeded, chatId: "chat_1" });
2462+
expect(first.ok).toBe(true);
2463+
if (!first.ok || !first.watchId) return;
2464+
2465+
await transitionWatchCondition(ctx.agentDb, {
2466+
id: first.watchId,
2467+
resolution: "condition_met",
2468+
});
2469+
2470+
const retry = await submit({ seeded, chatId: "chat_1" });
2471+
2472+
expect(retry.ok).toBe(true);
2473+
if (!retry.ok) return;
2474+
expect(retry.repaired).toBe(true);
2475+
2476+
// The recorded outcome is replayed, never decided again: the append-once
2477+
// confirmation in the transcript says "Watching", so the answer has to as well.
2478+
const parts = retry.messages.at(-1)?.parts ?? [];
2479+
const block = (parts[0] as any).data.blocks[0];
2480+
expect(block.outcome).toBe("watching");
2481+
expect(block.headline).toContain("Watching");
2482+
}
2483+
);
24532484
});
24542485

24552486
describe("appendChatMessageOnce", () => {

internal-packages/dashboard-agent-contracts/src/watch-wording.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,10 +628,11 @@ export function watchResolvedBlockBody(args: { watchId: string; resolved: WatchR
628628
followUp: never[];
629629
watchId: string;
630630
} {
631+
const presented = presentResolvedWatch(args.resolved);
631632
return {
632633
type: "watch_result",
633-
outcome: args.resolved.resolution === "condition_met" ? "already_true" : "impossible",
634-
headline: presentResolvedWatch(args.resolved).headline,
634+
outcome: presented.category === "positive" ? "already_true" : "impossible",
635+
headline: presented.headline,
635636
lifetime: null,
636637
detail: null,
637638
followUp: [],

internal-packages/dashboard-agent-contracts/src/watch.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
type WatchKind,
2424
type WatchSpec,
2525
} from "./watch.js";
26+
import { watchResolvedBlockBody } from "./watch-wording.js";
2627

2728
const common = { maxHours: 6, note: "because I asked" };
2829

@@ -583,3 +584,30 @@ describe("resolveWatchResult", () => {
583584
).toBe("queue_drained");
584585
});
585586
});
587+
588+
describe("watchResolvedBlockBody", () => {
589+
const identity = watchIdentity(specs.run_failed as WatchSpec);
590+
591+
it("marks good news as already true even when the window merely ran out", () => {
592+
expect(
593+
watchResolvedBlockBody({
594+
watchId: "watch_1",
595+
resolved: { kind: "run_failed", identity, resolution: "window_completed" },
596+
}).outcome
597+
).toBe("already_true");
598+
});
599+
600+
it("does not put a success check on bad news the watch caught", () => {
601+
expect(
602+
watchResolvedBlockBody({
603+
watchId: "watch_1",
604+
resolved: {
605+
kind: "run_failed",
606+
identity,
607+
resolution: "condition_met",
608+
observed: watchObservedOutcomeSchema.parse({ kind: "run_failed" }),
609+
},
610+
}).outcome
611+
).toBe("impossible");
612+
});
613+
});

0 commit comments

Comments
 (0)