Gate watchdog state callout behind task watchdog flag

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-06-24 21:49:32 +00:00
1 parent 78e0f80358
commit fc2af3841b
2 files changed
+67 -1

No files matched your search

+65
View File
@@ -469,6 +469,28 @@ function createIssue(overrides: Partial<Issue> = {}): Issue {
} as Issue;
}
function createWatchdogSummary(
overrides: Partial<NonNullable<Issue["watchdog"]>> = {},
): NonNullable<Issue["watchdog"]> {
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<IssueAttachment> & { 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(
<QueryClientProvider client={queryClient}>
<IssueDetail />
</QueryClientProvider>,
);
});
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(
<QueryClientProvider client={queryClient}>
<IssueDetail />
</QueryClientProvider>,
);
});
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");
+2 -1
View File
@@ -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}