chat: unify HITL approval UX behind a single paginated card and harden timeline supersede.

This commit is contained in:
CREDO23 2026-05-09 21:44:54 +02:00
parent 89e4953800
commit 2e132513be
25 changed files with 604 additions and 1157 deletions

View file

@ -1,44 +1,31 @@
import { useCallback } from "react";
import { useHitlBundle, useToolCallIdContext } from "./bundle/bundle-context";
import { useHitlApproval } from "./approval/approval-context";
import type { HitlDecision } from "./types";
/**
* Dispatches a HITL decision from inside an approval card.
*
* Behavior:
* - **Bundle active** (N2 parallel interrupts) AND this card's
* ``toolCallId`` is in the bundle: stage the (single) decision
* against this ``toolCallId`` so the bundle can submit one ordered
* N-payload when every card has decided. Multi-decision dispatches
* in this path are a programming error: only ``decisions[0]`` is
* staged; a dev warning fires for the rest.
* - **Otherwise (N=1 or no bundle):** dispatch the ``hitl-decision``
* window event directly with the full ``decisions`` array. The host
* page's listener calls ``runtime.resume`` with the same array.
*
* Cards always call ``dispatch([decision])`` and don't need to know
* which path they're on.
* Per-tool components always call ``dispatch([decision])``. We route
* through ``HitlApprovalContext`` when mounted inside an approval
* card (so multi-approval can stage and pager-navigate), and fall
* back to the ``hitl-decision`` window event for standalone callers.
*/
export function useHitlDecision() {
const bundle = useHitlBundle();
const toolCallId = useToolCallIdContext();
const approval = useHitlApproval();
const dispatch = useCallback(
(decisions: HitlDecision[]) => {
if (bundle && toolCallId && bundle.isInBundle(toolCallId) && decisions.length > 0) {
if (approval && decisions.length > 0) {
if (decisions.length > 1 && process.env.NODE_ENV !== "production") {
console.warn(
"[hitl] dispatch received %d decisions inside an active bundle; only [0] will be staged for %s",
decisions.length,
toolCallId
"[hitl] dispatch received %d decisions inside an approval card; only [0] will be staged",
decisions.length
);
}
bundle.stage(toolCallId, decisions[0]);
approval.stage(decisions[0]);
return;
}
window.dispatchEvent(new CustomEvent("hitl-decision", { detail: { decisions } }));
},
[bundle, toolCallId]
[approval]
);
return { dispatch };