From fc2af3841b20f372c7e3b52f5cd6e619d20194e3 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 24 Jun 2026 19:42:58 +0000 Subject: [PATCH] Gate watchdog state callout behind task watchdog flag Co-Authored-By: Paperclip --- ui/src/pages/IssueDetail.test.tsx | 65 +++++++++++++++++++++++++++++++ ui/src/pages/IssueDetail.tsx | 3 +- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/ui/src/pages/IssueDetail.test.tsx b/ui/src/pages/IssueDetail.test.tsx index 43c894c9a87..3e66e2f8179 100644 --- a/ui/src/pages/IssueDetail.test.tsx +++ b/ui/src/pages/IssueDetail.test.tsx @@ -469,6 +469,28 @@ function createIssue(overrides: Partial = {}): Issue { } as Issue; } +function createWatchdogSummary( + overrides: Partial> = {}, +): NonNullable { + return { + id: "watchdog-1", + companyId: "company-1", + issueId: "issue-1", + watchdogAgentId: "agent-1", + instructions: "Watch for stalled proof obligations.", + status: "active", + watchdogIssueId: "watchdog-issue-1", + lastObservedFingerprint: "task_watchdog_stop:observed", + lastReviewedFingerprint: "task_watchdog_stop:reviewed", + lastTriggeredAt: null, + lastCompletedAt: null, + triggerCount: 1, + createdAt: new Date("2026-04-21T00:00:00.000Z"), + updatedAt: new Date("2026-04-21T00:00:00.000Z"), + ...overrides, + }; +} + function createAttachment(overrides: Partial & { id: string }): IssueAttachment { const { id, ...attachmentOverrides } = overrides; return { @@ -1655,6 +1677,49 @@ describe("IssueDetail", () => { expect(container.textContent).toContain("Ask mode"); }); + it("does not pass watchdog state to the issue chat thread when task watchdogs are disabled", async () => { + mockIssuesApi.get.mockResolvedValue(createIssue({ watchdog: createWatchdogSummary() })); + + await act(async () => { + root.render( + + + , + ); + }); + await flushReact(); + + expect(mockIssueChatThreadRender.mock.calls.at(-1)?.[0]).toMatchObject({ + watchdog: null, + }); + }); + + it("passes watchdog state to the issue chat thread when task watchdogs are enabled", async () => { + const watchdog = createWatchdogSummary(); + mockInstanceSettingsApi.getExperimental.mockResolvedValue({ + enableIssuePlanDecompositions: false, + enableExperimentalFileViewer: false, + enableExternalObjects: false, + enableTaskWatchdogs: true, + }); + mockIssuesApi.get.mockResolvedValue(createIssue({ watchdog })); + + await act(async () => { + root.render( + + + , + ); + }); + await flushReact(); + + await vi.waitFor(() => { + expect(mockIssueChatThreadRender.mock.calls.at(-1)?.[0]).toMatchObject({ + watchdog, + }); + }); + }); + it("falls back to execCommand when copying the task from an insecure context", async () => { const clipboardWrite = vi.fn(async () => { throw new Error("Clipboard API blocked"); diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index b63c5616700..71b066b4a12 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -1563,6 +1563,7 @@ export function IssueDetail() { const showPlanDecompositionsSection = instanceExperimentalSettings?.enableIssuePlanDecompositions === true; const fileViewerEnabled = instanceExperimentalSettings?.enableExperimentalFileViewer === true; + const taskWatchdogsEnabled = instanceExperimentalSettings?.enableTaskWatchdogs === true; const { orderedProjects } = useProjectOrder({ projects: projects ?? [], companyId: selectedCompanyId, @@ -4234,7 +4235,7 @@ export function IssueDetail() { successfulRunHandoff={issue.successfulRunHandoff ?? null} scheduledRetry={issue.scheduledRetry ?? null} recoveryAction={issue.activeRecoveryAction ?? null} - watchdog={issue.watchdog ?? null} + watchdog={taskWatchdogsEnabled ? issue.watchdog ?? null : null} monitorNextCheckAt={issue.monitorNextCheckAt ?? null} onResolveRecoveryAction={handleResolveRecoveryAction} canFalsePositiveRecoveryAction={canResolveBoardRecoveryAction}