diff --git a/surfsense_web/components/tool-ui/linear/create-linear-issue.tsx b/surfsense_web/components/tool-ui/linear/create-linear-issue.tsx index 1dec11c60..156cdb5ca 100644 --- a/surfsense_web/components/tool-ui/linear/create-linear-issue.tsx +++ b/surfsense_web/components/tool-ui/linear/create-linear-issue.tsx @@ -18,6 +18,8 @@ import { } from "@/components/ui/select"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useHitlPhase } from "@/hooks/use-hitl-phase"; +import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; +import type { InterruptResult, HitlDecision } from "@/lib/hitl"; interface LinearLabel { id: string; @@ -64,23 +66,9 @@ interface LinearWorkspace { auth_expired?: boolean; } -interface InterruptResult { - __interrupt__: true; - __decided__?: "approve" | "reject" | "edit"; - __completed__?: boolean; - action_requests: Array<{ - name: string; - args: Record; - }>; - review_configs: Array<{ - action_name: string; - allowed_decisions: Array<"approve" | "edit" | "reject">; - }>; - interrupt_type?: string; - context?: { - workspaces?: LinearWorkspace[]; - error?: string; - }; +interface LinearCreateIssueContext { + workspaces?: LinearWorkspace[]; + error?: string; } interface SuccessResult { @@ -103,16 +91,7 @@ interface AuthErrorResult { connector_type: string; } -type CreateLinearIssueResult = InterruptResult | SuccessResult | ErrorResult | AuthErrorResult; - -function isInterruptResult(result: unknown): result is InterruptResult { - return ( - typeof result === "object" && - result !== null && - "__interrupt__" in result && - (result as InterruptResult).__interrupt__ === true - ); -} +type CreateLinearIssueResult = InterruptResult | SuccessResult | ErrorResult | AuthErrorResult; function isErrorResult(result: unknown): result is ErrorResult { return ( @@ -138,12 +117,8 @@ function ApprovalCard({ onDecision, }: { args: { title: string; description?: string }; - interruptData: InterruptResult; - onDecision: (decision: { - type: "approve" | "reject" | "edit"; - message?: string; - edited_action?: { name: string; args: Record }; - }) => void; + interruptData: InterruptResult; + onDecision: (decision: HitlDecision) => void; }) { const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const [isPanelOpen, setIsPanelOpen] = useState(false); @@ -609,18 +584,16 @@ export const CreateLinearIssueToolUI = ({ args, result, }: ToolCallMessagePartProps<{ title: string; description?: string }, CreateLinearIssueResult>) => { + const { dispatch } = useHitlDecision(); + if (!result) return null; if (isInterruptResult(result)) { return ( { - window.dispatchEvent( - new CustomEvent("hitl-decision", { detail: { decisions: [decision] } }) - ); - }} + interruptData={result as InterruptResult} + onDecision={(decision) => dispatch([decision])} /> ); } diff --git a/surfsense_web/components/tool-ui/linear/delete-linear-issue.tsx b/surfsense_web/components/tool-ui/linear/delete-linear-issue.tsx index 18790f560..f033240de 100644 --- a/surfsense_web/components/tool-ui/linear/delete-linear-issue.tsx +++ b/surfsense_web/components/tool-ui/linear/delete-linear-issue.tsx @@ -7,32 +7,20 @@ import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { useHitlPhase } from "@/hooks/use-hitl-phase"; +import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; +import type { InterruptResult, HitlDecision } from "@/lib/hitl"; -interface InterruptResult { - __interrupt__: true; - __decided__?: "approve" | "reject"; - __completed__?: boolean; - action_requests: Array<{ - name: string; - args: Record; - }>; - review_configs: Array<{ - action_name: string; - allowed_decisions: Array<"approve" | "reject">; - }>; - interrupt_type?: string; - context?: { - workspace?: { id: number; organization_name: string }; - issue?: { - id: string; - identifier: string; - title: string; - state?: string; - document_id?: number; - indexed_at?: string; - }; - error?: string; +interface LinearDeleteIssueContext { + workspace?: { id: number; organization_name: string }; + issue?: { + id: string; + identifier: string; + title: string; + state?: string; + document_id?: number; + indexed_at?: string; }; + error?: string; } interface SuccessResult { @@ -65,22 +53,13 @@ interface AuthErrorResult { } type DeleteLinearIssueResult = - | InterruptResult + | InterruptResult | SuccessResult | ErrorResult | NotFoundResult | WarningResult | AuthErrorResult; -function isInterruptResult(result: unknown): result is InterruptResult { - return ( - typeof result === "object" && - result !== null && - "__interrupt__" in result && - (result as InterruptResult).__interrupt__ === true - ); -} - function isErrorResult(result: unknown): result is ErrorResult { return ( typeof result === "object" && @@ -123,12 +102,8 @@ function ApprovalCard({ interruptData, onDecision, }: { - interruptData: InterruptResult; - onDecision: (decision: { - type: "approve" | "reject"; - message?: string; - edited_action?: { name: string; args: Record }; - }) => void; + interruptData: InterruptResult; + onDecision: (decision: HitlDecision) => void; }) { const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const [deleteFromKb, setDeleteFromKb] = useState(false); @@ -366,18 +341,15 @@ export const DeleteLinearIssueToolUI = ({ { issue_ref: string; delete_from_kb?: boolean }, DeleteLinearIssueResult >) => { + const { dispatch } = useHitlDecision(); + if (!result) return null; if (isInterruptResult(result)) { return ( { - const event = new CustomEvent("hitl-decision", { - detail: { decisions: [decision] }, - }); - window.dispatchEvent(event); - }} + interruptData={result as InterruptResult} + onDecision={(decision) => dispatch([decision])} /> ); } diff --git a/surfsense_web/components/tool-ui/linear/update-linear-issue.tsx b/surfsense_web/components/tool-ui/linear/update-linear-issue.tsx index f15f04224..4ff5b6e75 100644 --- a/surfsense_web/components/tool-ui/linear/update-linear-issue.tsx +++ b/surfsense_web/components/tool-ui/linear/update-linear-issue.tsx @@ -18,6 +18,8 @@ import { } from "@/components/ui/select"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useHitlPhase } from "@/hooks/use-hitl-phase"; +import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; +import type { InterruptResult, HitlDecision } from "@/lib/hitl"; interface LinearLabel { id: string; @@ -45,45 +47,31 @@ interface LinearPriority { label: string; } -interface InterruptResult { - __interrupt__: true; - __decided__?: "approve" | "reject" | "edit"; - __completed__?: boolean; - action_requests: Array<{ - name: string; - args: Record; - }>; - review_configs: Array<{ - action_name: string; - allowed_decisions: Array<"approve" | "edit" | "reject">; - }>; - interrupt_type?: string; - context?: { - workspace?: { id: number; organization_name: string }; - priorities?: LinearPriority[]; - issue?: { - id: string; - identifier: string; - title: string; - description?: string; - priority: number; - url: string; - current_state?: LinearState; - current_assignee?: { id: string; name: string; email: string } | null; - current_labels?: LinearLabel[]; - team_id: string; - document_id: number; - }; - team?: { - id: string; - name: string; - key: string; - states: LinearState[]; - members: LinearMember[]; - labels: LinearLabel[]; - }; - error?: string; +interface LinearUpdateIssueContext { + workspace?: { id: number; organization_name: string }; + priorities?: LinearPriority[]; + issue?: { + id: string; + identifier: string; + title: string; + description?: string; + priority: number; + url: string; + current_state?: LinearState; + current_assignee?: { id: string; name: string; email: string } | null; + current_labels?: LinearLabel[]; + team_id: string; + document_id: number; }; + team?: { + id: string; + name: string; + key: string; + states: LinearState[]; + members: LinearMember[]; + labels: LinearLabel[]; + }; + error?: string; } interface SuccessResult { @@ -111,21 +99,12 @@ interface AuthErrorResult { } type UpdateLinearIssueResult = - | InterruptResult + | InterruptResult | SuccessResult | ErrorResult | NotFoundResult | AuthErrorResult; -function isInterruptResult(result: unknown): result is InterruptResult { - return ( - typeof result === "object" && - result !== null && - "__interrupt__" in result && - (result as InterruptResult).__interrupt__ === true - ); -} - function isErrorResult(result: unknown): result is ErrorResult { return ( typeof result === "object" && @@ -167,12 +146,8 @@ function ApprovalCard({ new_priority?: number; new_label_names?: string[]; }; - interruptData: InterruptResult; - onDecision: (decision: { - type: "approve" | "reject" | "edit"; - message?: string; - edited_action?: { name: string; args: Record }; - }) => void; + interruptData: InterruptResult; + onDecision: (decision: HitlDecision) => void; }) { const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); @@ -752,18 +727,16 @@ export const UpdateLinearIssueToolUI = ({ }, UpdateLinearIssueResult >) => { + const { dispatch } = useHitlDecision(); + if (!result) return null; if (isInterruptResult(result)) { return ( { - window.dispatchEvent( - new CustomEvent("hitl-decision", { detail: { decisions: [decision] } }) - ); - }} + interruptData={result as InterruptResult} + onDecision={(decision) => dispatch([decision])} /> ); } diff --git a/surfsense_web/components/tool-ui/notion/create-notion-page.tsx b/surfsense_web/components/tool-ui/notion/create-notion-page.tsx index 52ac363ee..9a05dbc94 100644 --- a/surfsense_web/components/tool-ui/notion/create-notion-page.tsx +++ b/surfsense_web/components/tool-ui/notion/create-notion-page.tsx @@ -16,41 +16,27 @@ import { SelectValue, } from "@/components/ui/select"; import { useHitlPhase } from "@/hooks/use-hitl-phase"; +import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; +import type { InterruptResult, HitlDecision } from "@/lib/hitl"; -interface InterruptResult { - __interrupt__: true; - __decided__?: "approve" | "reject" | "edit"; - __completed__?: boolean; - action_requests: Array<{ +interface NotionCreatePageContext { + accounts?: Array<{ + id: number; name: string; - args: Record; - description?: string; + workspace_id: string | null; + workspace_name: string; + workspace_icon: string; + auth_expired?: boolean; }>; - review_configs: Array<{ - action_name: string; - allowed_decisions: Array<"approve" | "edit" | "reject">; - }>; - interrupt_type?: string; - message?: string; - context?: { - accounts?: Array<{ - id: number; - name: string; - workspace_id: string | null; - workspace_name: string; - workspace_icon: string; - auth_expired?: boolean; - }>; - parent_pages?: Record< - number, - Array<{ - page_id: string; - title: string; - document_id: number; - }> - >; - error?: string; - }; + parent_pages?: Record< + number, + Array<{ + page_id: string; + title: string; + document_id: number; + }> + >; + error?: string; } interface SuccessResult { @@ -75,16 +61,7 @@ interface AuthErrorResult { connector_type: string; } -type CreateNotionPageResult = InterruptResult | SuccessResult | ErrorResult | AuthErrorResult; - -function isInterruptResult(result: unknown): result is InterruptResult { - return ( - typeof result === "object" && - result !== null && - "__interrupt__" in result && - (result as InterruptResult).__interrupt__ === true - ); -} +type CreateNotionPageResult = InterruptResult | SuccessResult | ErrorResult | AuthErrorResult; function isAuthErrorResult(result: unknown): result is AuthErrorResult { return ( @@ -110,12 +87,8 @@ function ApprovalCard({ onDecision, }: { args: Record; - interruptData: InterruptResult; - onDecision: (decision: { - type: "approve" | "reject" | "edit"; - message?: string; - edited_action?: { name: string; args: Record }; - }) => void; + interruptData: InterruptResult; + onDecision: (decision: HitlDecision) => void; }) { const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const [isPanelOpen, setIsPanelOpen] = useState(false); @@ -449,19 +422,16 @@ export const CreateNotionPageToolUI = ({ args, result, }: ToolCallMessagePartProps<{ title: string; content: string }, CreateNotionPageResult>) => { + const { dispatch } = useHitlDecision(); + if (!result) return null; if (isInterruptResult(result)) { return ( { - const event = new CustomEvent("hitl-decision", { - detail: { decisions: [decision] }, - }); - window.dispatchEvent(event); - }} + interruptData={result as InterruptResult} + onDecision={(decision) => dispatch([decision])} /> ); } diff --git a/surfsense_web/components/tool-ui/notion/delete-notion-page.tsx b/surfsense_web/components/tool-ui/notion/delete-notion-page.tsx index 792a3e8c4..5f30c7efd 100644 --- a/surfsense_web/components/tool-ui/notion/delete-notion-page.tsx +++ b/surfsense_web/components/tool-ui/notion/delete-notion-page.tsx @@ -7,36 +7,22 @@ import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { useHitlPhase } from "@/hooks/use-hitl-phase"; +import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; +import type { InterruptResult, HitlDecision } from "@/lib/hitl"; -interface InterruptResult { - __interrupt__: true; - __decided__?: "approve" | "reject"; - __completed__?: boolean; - action_requests: Array<{ +interface NotionDeletePageContext { + account?: { + id: number; name: string; - args: Record; - description?: string; - }>; - review_configs: Array<{ - action_name: string; - allowed_decisions: Array<"approve" | "reject">; - }>; - interrupt_type?: string; - message?: string; - context?: { - account?: { - id: number; - name: string; - workspace_id: string | null; - workspace_name: string; - workspace_icon: string; - }; - page_id?: string; - current_title?: string; - document_id?: number; - indexed_at?: string; - error?: string; + workspace_id: string | null; + workspace_name: string; + workspace_icon: string; }; + page_id?: string; + current_title?: string; + document_id?: number; + indexed_at?: string; + error?: string; } interface SuccessResult { @@ -73,22 +59,13 @@ interface AuthErrorResult { } type DeleteNotionPageResult = - | InterruptResult + | InterruptResult | SuccessResult | ErrorResult | InfoResult | WarningResult | AuthErrorResult; -function isInterruptResult(result: unknown): result is InterruptResult { - return ( - typeof result === "object" && - result !== null && - "__interrupt__" in result && - (result as InterruptResult).__interrupt__ === true - ); -} - function isErrorResult(result: unknown): result is ErrorResult { return ( typeof result === "object" && @@ -131,12 +108,8 @@ function ApprovalCard({ interruptData, onDecision, }: { - interruptData: InterruptResult; - onDecision: (decision: { - type: "approve" | "reject"; - message?: string; - edited_action?: { name: string; args: Record }; - }) => void; + interruptData: InterruptResult; + onDecision: (decision: HitlDecision) => void; }) { const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const [deleteFromKb, setDeleteFromKb] = useState(false); @@ -378,18 +351,15 @@ export const DeleteNotionPageToolUI = ({ { page_title: string; delete_from_kb?: boolean }, DeleteNotionPageResult >) => { + const { dispatch } = useHitlDecision(); + if (!result) return null; if (isInterruptResult(result)) { return ( { - const event = new CustomEvent("hitl-decision", { - detail: { decisions: [decision] }, - }); - window.dispatchEvent(event); - }} + interruptData={result as InterruptResult} + onDecision={(decision) => dispatch([decision])} /> ); } diff --git a/surfsense_web/components/tool-ui/notion/update-notion-page.tsx b/surfsense_web/components/tool-ui/notion/update-notion-page.tsx index 5b7ce7daa..d07c6592b 100644 --- a/surfsense_web/components/tool-ui/notion/update-notion-page.tsx +++ b/surfsense_web/components/tool-ui/notion/update-notion-page.tsx @@ -9,36 +9,22 @@ import { PlateEditor } from "@/components/editor/plate-editor"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; import { useHitlPhase } from "@/hooks/use-hitl-phase"; +import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; +import type { InterruptResult, HitlDecision } from "@/lib/hitl"; -interface InterruptResult { - __interrupt__: true; - __decided__?: "approve" | "reject" | "edit"; - __completed__?: boolean; - action_requests: Array<{ +interface NotionUpdatePageContext { + account?: { + id: number; name: string; - args: Record; - description?: string; - }>; - review_configs: Array<{ - action_name: string; - allowed_decisions: Array<"approve" | "edit" | "reject">; - }>; - interrupt_type?: string; - message?: string; - context?: { - account?: { - id: number; - name: string; - workspace_id: string | null; - workspace_name: string; - workspace_icon: string; - }; - page_id?: string; - current_title?: string; - document_id?: number; - indexed_at?: string; - error?: string; + workspace_id: string | null; + workspace_name: string; + workspace_icon: string; }; + page_id?: string; + current_title?: string; + document_id?: number; + indexed_at?: string; + error?: string; } interface SuccessResult { @@ -69,21 +55,12 @@ interface AuthErrorResult { } type UpdateNotionPageResult = - | InterruptResult + | InterruptResult | SuccessResult | ErrorResult | InfoResult | AuthErrorResult; -function isInterruptResult(result: unknown): result is InterruptResult { - return ( - typeof result === "object" && - result !== null && - "__interrupt__" in result && - (result as InterruptResult).__interrupt__ === true - ); -} - function isErrorResult(result: unknown): result is ErrorResult { return ( typeof result === "object" && @@ -117,12 +94,8 @@ function ApprovalCard({ onDecision, }: { args: Record; - interruptData: InterruptResult; - onDecision: (decision: { - type: "approve" | "reject" | "edit"; - message?: string; - edited_action?: { name: string; args: Record }; - }) => void; + interruptData: InterruptResult; + onDecision: (decision: HitlDecision) => void; }) { const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const [isPanelOpen, setIsPanelOpen] = useState(false); @@ -399,19 +372,16 @@ export const UpdateNotionPageToolUI = ({ args, result, }: ToolCallMessagePartProps<{ page_title: string; content: string }, UpdateNotionPageResult>) => { + const { dispatch } = useHitlDecision(); + if (!result) return null; if (isInterruptResult(result)) { return ( { - const event = new CustomEvent("hitl-decision", { - detail: { decisions: [decision] }, - }); - window.dispatchEvent(event); - }} + interruptData={result as InterruptResult} + onDecision={(decision) => dispatch([decision])} /> ); }