@blocksdiy/react-common 1.51.0 → 1.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,14 @@
1
1
  import { type Message, ToolMessage, type UserMessage } from "@ag-ui/client";
2
2
  import { useRenderToolCall } from "@copilotkit/react-core/v2";
3
3
  import { ReactNode, SetStateAction } from "react";
4
+ import { type ApprovalCardView, type ApprovalDecision } from "../utils/agui-interrupts.js";
4
5
  import { type RenderProp } from "../utils/compose-render.js";
5
6
  import { type ComposerMessageMeta } from "../utils/composer-message.js";
6
7
  import { type MessageRunIds, type RunStartedAt } from "../utils/message-run-ids.js";
7
8
  export { useTextGrowthAnimating } from "../hooks/text-growth-animation.js";
8
9
  export { type ComposerMessageMeta, FEEDBACK_MESSAGE_NAME, type FeedbackDraft, type FeedbackRate, feedbackComposerMeta, feedbackDraftOf, feedbackMessage, feedbackThreadId, isFeedbackMessage, isFeedbackTag, parseFeedbackContent, parseFeedbackTag, type ParsedFeedback, } from "../utils/composer-message.js";
9
10
  export { MESSAGE_RUN_IDS_EVENT_NAME, messageIdsOfRun, type MessageRunIds, type RunStartedAt, } from "../utils/message-run-ids.js";
11
+ export { type ApprovalCardView, type ApprovalDecision, toolStepVisibleBelowCards } from "../utils/agui-interrupts.js";
10
12
  export { type Message, type AssistantMessage, type UserMessage } from "@ag-ui/client";
11
13
  export { useRenderTool, useDefaultRenderTool, useHumanInTheLoop, useFrontendTool, ToolCallStatus, } from "@copilotkit/react-core/v2";
12
14
  export interface Attachment {
@@ -78,11 +80,20 @@ export interface AgentChatContextValue {
78
80
  sendMessage: (message: Omit<UserMessage, "id" | "role">) => Promise<void>;
79
81
  sendFromInputs: () => Promise<void>;
80
82
  reloadMessages: () => Promise<void>;
83
+ /** @deprecated Answer with `answerInterrupt`; kept for the frozen-copy contract. */
81
84
  resolveInterrupt: (resume: unknown) => Promise<boolean>;
82
85
  answerInterrupt: (answer: {
83
86
  toolCallId: string;
84
87
  answer: unknown;
85
88
  }) => Promise<void>;
89
+ /** Approve or reject one waiting request; resumes once every request of the pause has an answer. */
90
+ decideApproval?: (decision: {
91
+ interruptId: string;
92
+ index: number;
93
+ decision: ApprovalDecision;
94
+ }) => Promise<void>;
95
+ /** The approval cards on a transcript tool call: waiting, decided here, or recorded. */
96
+ approvalCardsFor?: (toolCallId: string) => ApprovalCardView[];
86
97
  stopGeneration: () => void;
87
98
  addAttachments: (attachments: Attachment[]) => void;
88
99
  removeAttachment: (url: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"new-agent-chat.d.ts","sourceRoot":"","sources":["../../src/components/new-agent-chat.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"new-agent-chat.d.ts","sourceRoot":"","sources":["../../src/components/new-agent-chat.tsx"],"names":[],"mappings":"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"}
@@ -5,13 +5,16 @@ import { CopilotKit, useCopilotChatInternal } from "@copilotkit/react-core";
5
5
  import { useCopilotKit, useRenderToolCall } from "@copilotkit/react-core/v2";
6
6
  import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
7
7
  import { useStickToBottom } from "use-stick-to-bottom";
8
+ import { AgentChatOwnRunsContext } from "../hooks/agent-chat-own-runs.js";
8
9
  import { readAgentAdminRoles } from "../utils/agent-admin.js";
10
+ import { answerRouteOf, approvalCardsForToolCall, buildResumeEntries, cancelledResumeEntries, dropStamps, forgetOpenInterrupts, INTERRUPT_NOT_OPEN_ERROR, isApprovalRequest, localStampsByAnchor, mergeStamps, needsReconnectAfter, requestKey, requestsOf, respondMessageOf, singleInterruptResume, stampsAfterInterruptReset, toolCallOf, } from "../utils/agui-interrupts.js";
9
11
  import { ComposeRender } from "../utils/compose-render.js";
10
12
  import { buildArmedMessageContent, userTextOfContent } from "../utils/composer-message.js";
11
13
  import { claimRunInput, EMPTY_MESSAGE_RUN_IDS, EMPTY_RUN_STARTED_AT, findMessageGroupElement, mergeEntries, MESSAGE_IDS_ATTRIBUTE, messageIdsOfRun, readMessageRunIdsEvent, readRunStartedAtEvent, } from "../utils/message-run-ids.js";
12
14
  export { useTextGrowthAnimating } from "../hooks/text-growth-animation.js";
13
15
  export { FEEDBACK_MESSAGE_NAME, feedbackComposerMeta, feedbackDraftOf, feedbackMessage, feedbackThreadId, isFeedbackMessage, isFeedbackTag, parseFeedbackContent, parseFeedbackTag, } from "../utils/composer-message.js";
14
16
  export { MESSAGE_RUN_IDS_EVENT_NAME, messageIdsOfRun, } from "../utils/message-run-ids.js";
17
+ export { toolStepVisibleBelowCards } from "../utils/agui-interrupts.js";
15
18
  export { useRenderTool, useDefaultRenderTool, useHumanInTheLoop, useFrontendTool, ToolCallStatus, } from "@copilotkit/react-core/v2";
16
19
  // Client-only `useLayoutEffect`, falls back to `useEffect` during SSR.
17
20
  // Needed so the `agent.threadId` mirror commits before CopilotKit's
@@ -53,35 +56,6 @@ const _collectPendingToolCalls = (messages) => {
53
56
  }
54
57
  return pending;
55
58
  };
56
- // Message order IS the order of the interrupt's `action_requests`, so decisions
57
- // pair with them by index and need no ids. Matched by name rather than by "has no
58
- // local handler", so a backend tool merely mid-flight is never taken for a pause.
59
- const _SERVER_PAUSE_TOOL_NAME = "ask_user";
60
- const _SERVER_PAUSE_TOOL_PREFIX = "ask-component-";
61
- const _isServerPause = (toolCall) => toolCall.function.name === _SERVER_PAUSE_TOOL_NAME || toolCall.function.name.startsWith(_SERVER_PAUSE_TOOL_PREFIX);
62
- // ONE assistant message's pauses, never the transcript's: the server batches a
63
- // turn's pauses into a single interrupt requiring exactly one decision each, and a
64
- // transcript can also hold pauses from a run that died before parking. Counting
65
- // those raises the count check — permanently, since the checkpoint never advances.
66
- const _collectServerPauses = (messages) => {
67
- const answered = new Set();
68
- for (const m of messages) {
69
- if (m.role === "tool" && "toolCallId" in m && typeof m.toolCallId === "string") {
70
- answered.add(m.toolCallId);
71
- }
72
- }
73
- for (let i = messages.length - 1; i >= 0; i--) {
74
- const m = messages[i];
75
- if (m.role !== "assistant" || !("toolCalls" in m) || !m.toolCalls) {
76
- continue;
77
- }
78
- const pauses = m.toolCalls.filter((toolCall) => !answered.has(toolCall.id) && _isServerPause(toolCall));
79
- if (pauses.length > 0) {
80
- return pauses;
81
- }
82
- }
83
- return [];
84
- };
85
59
  // CopilotKit 1.60.x: `connectAgent()` awaits every replayed HITL tool call's
86
60
  // handler before resolving `isAvailable`, and a `useHumanInTheLoop` handler
87
61
  // only resolves on `respond()` — so a transcript replayed with an unanswered
@@ -91,6 +65,14 @@ const _collectServerPauses = (messages) => {
91
65
  const isConnectParkedOnPendingToolCall = (messages) => {
92
66
  return messages.length > 0 && _collectPendingToolCalls(messages).length > 0;
93
67
  };
68
+ const EMPTY_INTERRUPTS = [];
69
+ const EMPTY_ANSWERS = new Map();
70
+ const EMPTY_STAMPS = new Map();
71
+ const EMPTY_REQUESTS = [];
72
+ // What @ag-ui/client tracks as open: the source of truth for what a send must
73
+ // address. The chat writes the field only through `resetInterrupts`, its one writer:
74
+ // on a thread swap and before every connect we start (CopilotKit 1.70.2 never clears it).
75
+ const openInterruptsOf = (agent) => agent?.pendingInterrupts ?? EMPTY_INTERRUPTS;
94
76
  const validateAndConvertFiles = (files) => {
95
77
  if (files.length === 0) {
96
78
  return null;
@@ -318,6 +300,29 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
318
300
  const [runStartedAt, setRunStartedAt] = useState(EMPTY_RUN_STARTED_AT);
319
301
  const liveRunRef = useRef(null);
320
302
  const ownRunIdsRef = useRef(new Set());
303
+ // Paired once, when the pause arrives: while the next run streams, a sub-agent's own
304
+ // calls arrive as the parent's and would re-pair the request with them -- a second
305
+ // card on the parent's call.
306
+ const [interruptRequests, setInterruptRequests] = useState(EMPTY_REQUESTS);
307
+ // One answer per request (`requestKey`). The ref is the truth (two clicks in one
308
+ // tick); the state copy re-renders the cards.
309
+ const keyedAnswersRef = useRef(new Map());
310
+ const [interruptAnswers, setInterruptAnswers] = useState(EMPTY_ANSWERS);
311
+ const [localApprovals, setLocalApprovals] = useState(EMPTY_STAMPS);
312
+ // The agent's own `pendingInterrupts` too: CopilotKit 1.70.2 keeps it across a
313
+ // thread swap, and @ag-ui/client 0.0.59 would refuse the next connect.
314
+ // ONE source: @ag-ui/client writes the field on RUN_FINISHED and
315
+ // `onRunFinalized` pairs its requests into `interruptRequests`; this is the only place
316
+ // the chat writes it, and it clears the pairing in the same breath.
317
+ const resetInterrupts = useCallback(({ keepStamps = false } = {}) => {
318
+ forgetOpenInterrupts(copilotAgent);
319
+ setInterruptRequests(EMPTY_REQUESTS);
320
+ keyedAnswersRef.current.clear();
321
+ setInterruptAnswers(EMPTY_ANSWERS);
322
+ setLocalApprovals((previous) => stampsAfterInterruptReset(previous, { keepStamps }));
323
+ }, [copilotAgent]);
324
+ // Runs this chat started that no send accounted for (AgentChatOwnRunsContext).
325
+ const [ownRunsWithoutSend, setOwnRunsWithoutSend] = useState(0);
321
326
  useEffect(() => {
322
327
  if (!copilotAgent) {
323
328
  return;
@@ -349,6 +354,9 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
349
354
  onToolCallStartEvent: ({ event }) => claimLive(event.parentMessageId ?? event.toolCallId),
350
355
  onRunFinalized: () => {
351
356
  liveRunRef.current = null;
357
+ // The agent's own field, which @ag-ui/client has set on RUN_FINISHED by now (pinned
358
+ // in agui-interrupts.spec) -- live runs and connects alike; a RUN_ERROR leaves it.
359
+ setInterruptRequests(requestsOf(openInterruptsOf(copilotAgent), copilotAgent.messages));
352
360
  },
353
361
  onCustomEvent: ({ event }) => {
354
362
  const runIds = readMessageRunIdsEvent(event);
@@ -366,7 +374,6 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
366
374
  liveRunRef.current = null;
367
375
  };
368
376
  }, [copilotAgent]);
369
- const interruptAnswersRef = useRef(new Map());
370
377
  // Mirror `currentThreadId` onto the agent instance — the `<CopilotKit>`
371
378
  // prop only feeds React context, but `agent.threadId` is what hits the
372
379
  // wire and the LangGraph checkpoint. `<CopilotChat>` does this for you;
@@ -380,8 +387,8 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
380
387
  }
381
388
  // Carrying answers across a thread swap would pair them with another
382
389
  // conversation's pause.
383
- interruptAnswersRef.current.clear();
384
- }, [copilotAgent, currentThreadId]);
390
+ resetInterrupts();
391
+ }, [copilotAgent, currentThreadId, resetInterrupts]);
385
392
  const [prompt, setPrompt] = useState("");
386
393
  const [attachments, setAttachments] = useState([]);
387
394
  const [composerMessageMeta, setComposerMessageMeta] = useState(null);
@@ -404,6 +411,7 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
404
411
  setPrompt("");
405
412
  setAttachments([]);
406
413
  setComposerMessageMeta(null);
414
+ resetInterrupts();
407
415
  setOwnRunInFlight(false);
408
416
  const detachable = copilotAgent;
409
417
  const drained = detachable?.detachActiveRun?.();
@@ -427,18 +435,25 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
427
435
  }
428
436
  copilotAgent.setState?.({});
429
437
  copilotAgent.clearReplayCursor?.(currentThreadId);
438
+ // Answers and decided-here stamps too; the connect re-sends what is open.
439
+ resetInterrupts();
430
440
  void copilotkit.connectAgent({ agent: copilotAgent }).catch(() => undefined);
431
- }, [chatId, currentThreadId, copilotAgent, copilotkit]);
441
+ }, [chatId, currentThreadId, copilotAgent, copilotkit, resetInterrupts]);
432
442
  const [isDraggingFiles, setIsDraggingFiles] = useState(false);
433
443
  const attachmentsEnabled = agentChat ? agentChat.disableAttachments !== true : false;
434
444
  const canSendMessage = Boolean(agentChatBlock && agentChat && agent && isConfigLoaded && isAgentReady && !isAgentRunning && !isRunningElsewhere);
435
445
  const isFetchingMessages = !isConfigLoaded || (!isAgentReady && copilotMessages.length === 0);
436
446
  const isThinking = (isAgentRunning || isRunningElsewhere) && !isFetchingMessages && isAgentReady;
437
- const runAgentWithRetry = useCallback(async (agentToRun, messageCountAtRunStart, forwardedProps) => {
447
+ const runAgentWithRetry = useCallback(async (agentToRun, messageCountAtRunStart, forwardedProps, resume, withoutSend = false) => {
438
448
  // A run elsewhere is on this conversation's checkpoint too: ours would race it.
439
449
  if (agentToRun.isRunning || isRunningElsewhereRef.current) {
440
450
  return false;
441
451
  }
452
+ // A turn on the server like any run; a send accounts for its own (onUserMessageSent).
453
+ if (withoutSend) {
454
+ setOwnRunsWithoutSend((count) => count + 1);
455
+ }
456
+ let finished = false;
442
457
  const runAgentAndThrowError = async () => {
443
458
  let runEventError;
444
459
  const { unsubscribe } = agentToRun.subscribe({
@@ -448,9 +463,12 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
448
463
  onRunErrorEvent: (event) => {
449
464
  runEventError = event;
450
465
  },
466
+ onRunFinishedEvent: () => {
467
+ finished = true;
468
+ },
451
469
  });
452
470
  try {
453
- await copilotkit.runAgent({ agent: agentToRun, forwardedProps });
471
+ await copilotkit.runAgent({ agent: agentToRun, forwardedProps, ...(resume ? { resume } : {}) });
454
472
  if (runEventError) {
455
473
  throw runEventError;
456
474
  }
@@ -462,26 +480,41 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
462
480
  // In flight until the run ends, whichever way (see `runningElsewhereRunId`) — unless
463
481
  // the conversation switched under it, which resets this itself.
464
482
  const ranOn = prevConversationKeyRef.current;
483
+ let reconnected = false;
465
484
  setOwnRunInFlight(true);
466
485
  try {
467
486
  await runAgentAndThrowError();
468
- return true;
469
487
  }
470
488
  catch (error) {
471
489
  const hasAgentStartedResponding = agentToRun.messages.length > messageCountAtRunStart;
472
490
  const status = getAgentRunErrorStatus(error);
473
- if (status !== undefined && status >= 500 && status < 600 && !hasAgentStartedResponding) {
491
+ // Never re-sent blind when it carried a resume: the server may already have consumed
492
+ // it. The reconnect below re-reads what is open instead.
493
+ if (!resume && status !== undefined && status >= 500 && status < 600 && !hasAgentStartedResponding) {
474
494
  await runAgentAndThrowError();
475
- return true;
476
495
  }
477
- throw error;
496
+ else {
497
+ throw error;
498
+ }
478
499
  }
479
500
  finally {
480
- if (prevConversationKeyRef.current === ranOn) {
501
+ const stillHere = prevConversationKeyRef.current === ranOn;
502
+ if (stillHere) {
481
503
  setOwnRunInFlight(false);
482
504
  }
505
+ if (stillHere && needsReconnectAfter(Boolean(resume), finished)) {
506
+ // A RUN_ERROR, a stop or a dropped stream leaves @ag-ui/client's
507
+ // `pendingInterrupts` as they were, possibly consumed: every later send would be
508
+ // refused. Forget them and reconnect; the connect re-sends what is really open.
509
+ resetInterrupts();
510
+ agentToRun.clearReplayCursor?.(currentThreadId);
511
+ await copilotkit.connectAgent({ agent: agentToRun }).catch(() => undefined);
512
+ reconnected = true;
513
+ }
483
514
  }
484
- }, [copilotkit]);
515
+ // A reconnected run did not take effect as sent: callers must not record it as done.
516
+ return !reconnected;
517
+ }, [copilotkit, currentThreadId, resetInterrupts]);
485
518
  const sendMessage = useCallback(async (message) => {
486
519
  if (!canSendMessage || !copilotAgent || copilotAgent.isRunning || isRunningElsewhereRef.current) {
487
520
  return;
@@ -490,71 +523,157 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
490
523
  if (onUserMessageSent) {
491
524
  onUserMessageSent(userTextOfContent(message.content), message);
492
525
  }
493
- await runAgentWithRetry(copilotAgent, copilotAgent.messages.length);
526
+ // Typing while the turn is parked sends the message
527
+ // (already added above, with its own id and attachments) as a normal message,
528
+ // plus a payload-less `cancelled` entry for every open interrupt --
529
+ // @ag-ui/client refuses to send at all unless every open interrupt is
530
+ // addressed. The server rejects every waiting approval (one approved here but
531
+ // not yet sent included), skips every question with one fixed marker, and
532
+ // keeps the message after them. A send that fails reconnects (runAgentWithRetry).
533
+ const interrupts = openInterruptsOf(copilotAgent);
534
+ if (interrupts.length === 0) {
535
+ await runAgentWithRetry(copilotAgent, copilotAgent.messages.length);
536
+ return;
537
+ }
538
+ const requests = requestsOf(interrupts, copilotAgent.messages);
539
+ const rejected = new Map(requests.filter(isApprovalRequest).map((request) => [request.key, { type: "reject" }]));
540
+ // Shown as decided for the whole run, as a click is (see submitInterruptAnswers):
541
+ // approvals rejected, questions closed too (they get the skip marker), so no card
542
+ // reads "waiting for your decision on the other request". The ref stays empty so
543
+ // no card can send.
544
+ keyedAnswersRef.current.clear();
545
+ setInterruptAnswers(new Map(requests.map((request) => [request.key, rejected.get(request.key) ?? { type: "respond", message: null }])));
546
+ const decided = localStampsByAnchor(requests, rejected);
547
+ setLocalApprovals((previous) => mergeStamps(previous, decided));
548
+ let resumed = false;
549
+ try {
550
+ resumed = await runAgentWithRetry(copilotAgent, copilotAgent.messages.length, undefined, cancelledResumeEntries(interrupts));
551
+ }
552
+ finally {
553
+ if (!resumed) {
554
+ setLocalApprovals((previous) => dropStamps(previous, decided));
555
+ }
556
+ setInterruptAnswers(EMPTY_ANSWERS);
557
+ }
494
558
  }, [canSendMessage, copilotAgent, runAgentWithRetry, onUserMessageSent]);
495
559
  const reloadMessages = useCallback(async () => {
496
560
  if (!copilotAgent || copilotAgent.isRunning) {
497
561
  return;
498
562
  }
499
563
  copilotAgent.clearReplayCursor?.(currentThreadId);
564
+ // Answers too; the connect re-sends what is open. The decided-here stamps stay: on the
565
+ // same thread they bridge the card until the connect's snapshot brings the record.
566
+ resetInterrupts({ keepStamps: true });
500
567
  await copilotkit.connectAgent({ agent: copilotAgent }).catch(() => undefined);
501
- }, [copilotAgent, copilotkit, currentThreadId]);
502
- // Not sourced from `useInterrupt`: that hook only hands `resolve` to its own
503
- // `render`, which it skips whenever nothing is pending — so after a reload there
504
- // is no way to obtain it. `runAgentWithRetry`'s `isRunning` guard makes a
505
- // double-click a no-op rather than a duplicate resume.
568
+ }, [copilotAgent, copilotkit, currentThreadId, resetInterrupts]);
569
+ // Deprecated: no renderer calls it, and ChatComponentPreview stubs
570
+ // it. The context field stays for the frozen-copy contract; a `{decisions}` payload now
571
+ // goes to the ONE open interrupt through the standard `resume[]`.
506
572
  const resolveInterrupt = useCallback(async (resume) => {
507
573
  if (!copilotAgent) {
508
574
  return false;
509
575
  }
510
- return runAgentWithRetry(copilotAgent, copilotAgent.messages.length, { command: { resume } });
576
+ const entries = singleInterruptResume(openInterruptsOf(copilotAgent), resume);
577
+ if (!entries) {
578
+ throw new Error(INTERRUPT_NOT_OPEN_ERROR);
579
+ }
580
+ return runAgentWithRetry(copilotAgent, copilotAgent.messages.length, undefined, entries, true);
511
581
  }, [copilotAgent, runAgentWithRetry]);
512
- // Resumes only once EVERY pause of the turn has an answer: a short decisions list
513
- // fails `HumanInTheLoopMiddleware`'s count check, and the checkpoint never
514
- // advances, so every later answer fails identically.
515
- const answerInterrupt = useCallback(async ({ toolCallId, answer }) => {
582
+ // Resumes once EVERY request of every open interrupt has an answer: the server
583
+ // refuses a partial resume.
584
+ const submitInterruptAnswers = useCallback(async () => {
516
585
  if (!copilotAgent) {
517
586
  return;
518
587
  }
519
- const pauses = _collectServerPauses(copilotAgent.messages);
520
- // Thrown, never returned: a caller resets its submitting state on a rejection,
521
- // while a silent return leaves the card locked with the answer discarded.
522
- if (!pauses.some((toolCall) => toolCall.id === toolCallId)) {
523
- throw new Error(`answerInterrupt: ${toolCallId} is not a pending pause of this turn`);
524
- }
525
- const answers = interruptAnswersRef.current;
526
- answers.set(toolCallId, answer);
527
- const unanswered = pauses.filter((toolCall) => !answers.has(toolCall.id));
528
- if (unanswered.length > 0) {
588
+ const interrupts = openInterruptsOf(copilotAgent);
589
+ const requests = requestsOf(interrupts, copilotAgent.messages);
590
+ const answers = keyedAnswersRef.current;
591
+ const resume = buildResumeEntries(interrupts, requests, answers);
592
+ if (!resume) {
529
593
  return;
530
594
  }
531
- const decisions = pauses.map((toolCall) => ({
532
- type: "respond",
533
- // `?? null` because a component may call `respond()` with no payload, and
534
- // `JSON.stringify(undefined)` drops the key, which raises server-side.
535
- message: JSON.stringify(answers.get(toolCall.id) ?? null),
536
- }));
537
595
  const sent = new Map(answers);
538
596
  answers.clear();
539
- const restore = () => sent.forEach((value, id) => answers.set(id, value));
597
+ // Stamped before the run: once the run streams, the call stops waiting and the live
598
+ // request no longer pairs with it, so without the stamp the card vanishes until the
599
+ // run ends and then comes back lower.
600
+ const decided = localStampsByAnchor(requests, sent);
601
+ setLocalApprovals((previous) => mergeStamps(previous, decided));
540
602
  let resumed = false;
541
603
  try {
542
- // Every answer goes back, not just this call's, or a resume that never
543
- // happened discards the rest of the turn's cards with it.
544
- resumed = await resolveInterrupt({ decisions });
604
+ resumed = await runAgentWithRetry(copilotAgent, copilotAgent.messages.length, undefined, resume, true);
545
605
  }
546
606
  catch (error) {
547
- restore();
607
+ // Sent and failed (a refused resume, a server error): runAgentWithRetry already
608
+ // reconnected, so the cards show what is really open.
609
+ // Restoring the answers would show "Approved" for a decision that never took
610
+ // effect.
611
+ setLocalApprovals((previous) => dropStamps(previous, decided));
612
+ setInterruptAnswers(EMPTY_ANSWERS);
548
613
  throw error;
549
614
  }
550
615
  if (!resumed) {
551
- // A run was already in flight, so nothing was sent: `isThinking` lags
552
- // `agent.isRunning` by a commit, which leaves `respond` live for a moment
553
- // after a run starts.
554
- restore();
555
- throw new Error("answerInterrupt: a run was already in flight; answer kept for retry");
616
+ // Nothing was sent (a run already in flight), or the run did not finish and the
617
+ // chat reloaded what is open. Clear the answers, as on an error: restoring them
618
+ // would lock every card on "Approved"/"Rejected" with no way to resend until the
619
+ // user typed or reloaded. The cards go back to waiting, and the next click sends.
620
+ setLocalApprovals((previous) => dropStamps(previous, decided));
621
+ setInterruptAnswers(EMPTY_ANSWERS);
622
+ throw new Error("answerInterrupt: the answer did not go through; the chat shows what is still open");
623
+ }
624
+ setInterruptAnswers(EMPTY_ANSWERS);
625
+ }, [copilotAgent, runAgentWithRetry]);
626
+ const decideApproval = useCallback(async ({ interruptId, index, decision }) => {
627
+ keyedAnswersRef.current.set(requestKey(interruptId, index), { type: decision });
628
+ setInterruptAnswers(new Map(keyedAnswersRef.current));
629
+ await submitInterruptAnswers();
630
+ }, [submitInterruptAnswers]);
631
+ const approvalCardsFor = useCallback((toolCallId) => {
632
+ const call = toolCallOf(copilotMessages, toolCallId);
633
+ const toolMessage = toolMessages.find((message) => message.toolCallId === toolCallId);
634
+ return approvalCardsForToolCall({
635
+ toolCallId,
636
+ toolName: call?.name ?? "",
637
+ toolArgs: call?.args,
638
+ toolResult: typeof toolMessage?.content === "string" ? toolMessage.content : null,
639
+ toolMessage,
640
+ requests: interruptRequests,
641
+ answers: interruptAnswers,
642
+ localStamps: localApprovals,
643
+ });
644
+ }, [copilotMessages, toolMessages, interruptRequests, interruptAnswers, localApprovals]);
645
+ // One path for every question card. Every renderer -- the frozen registry copies
646
+ // included -- calls this with the same signature. The answer goes to the open request its
647
+ // card pairs with (by order) and resumes through `resume[]` once every request has one.
648
+ // The welcome seed, which has no interrupt,
649
+ // becomes its call's tool result and a normal run. Anything else throws, never falls
650
+ // back: the card resets and stays waiting, and typing still works.
651
+ const answerInterrupt = useCallback(async ({ toolCallId, answer }) => {
652
+ if (!copilotAgent) {
653
+ return;
654
+ }
655
+ const route = answerRouteOf(openInterruptsOf(copilotAgent), copilotAgent.messages, toolCallId);
656
+ if (route.kind === "respond") {
657
+ keyedAnswersRef.current.set(route.request.key, { type: "respond", message: answer });
658
+ setInterruptAnswers(new Map(keyedAnswersRef.current));
659
+ await submitInterruptAnswers();
660
+ return;
556
661
  }
557
- }, [copilotAgent, resolveInterrupt]);
662
+ if (route.kind === "refuse") {
663
+ throw new Error(INTERRUPT_NOT_OPEN_ERROR);
664
+ }
665
+ // The welcome seed: the call's tool result, then an ordinary run --
666
+ // no resume, nothing parked on the server. A run that fails keeps the answer, like
667
+ // a typed message whose run failed.
668
+ const seedAnswerId = generateId();
669
+ copilotAgent.addMessage({ id: seedAnswerId, role: "tool", toolCallId, content: respondMessageOf(answer) });
670
+ const sent = await runAgentWithRetry(copilotAgent, copilotAgent.messages.length, undefined, undefined, true);
671
+ if (!sent) {
672
+ // Nothing went out (a run already in flight): take the answer back so the card can send again.
673
+ copilotAgent.setMessages?.(copilotAgent.messages.filter((message) => message.id !== seedAnswerId));
674
+ throw new Error("answerInterrupt: a run was already in flight; answer not sent");
675
+ }
676
+ }, [copilotAgent, runAgentWithRetry, submitInterruptAnswers]);
558
677
  const stopGeneration = useCallback(() => {
559
678
  copilotStopGeneration();
560
679
  copilotAgent?.abortRun?.();
@@ -825,6 +944,8 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
825
944
  reloadMessages,
826
945
  resolveInterrupt,
827
946
  answerInterrupt,
947
+ decideApproval,
948
+ approvalCardsFor,
828
949
  stopGeneration,
829
950
  addAttachments,
830
951
  removeAttachment,
@@ -845,7 +966,7 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
845
966
  components: components ?? [],
846
967
  componentsLoading,
847
968
  currentThreadId,
848
- }, children: children }));
969
+ }, children: _jsx(AgentChatOwnRunsContext.Provider, { value: ownRunsWithoutSend, children: children }) }));
849
970
  };
850
971
  export const AgentChatRoot = ({ ...props
851
972
  // agentChatData,
@@ -1007,6 +1128,7 @@ export const AgentChatRoot = ({ ...props
1007
1128
  noPersistency,
1008
1129
  chatContext: chatContextStr,
1009
1130
  chatContextFiles,
1131
+ supportsInterruptOutcome: true,
1010
1132
  ...(selfEditEnabled ? { selfEditEnabled: true } : {}),
1011
1133
  ...(supportsServerInterrupt ? { supportsServerInterrupt: true } : {}),
1012
1134
  ...(serverInterruptComponentIds.length ? { serverInterruptComponentIds } : {}),
@@ -0,0 +1,3 @@
1
+ /** Runs this chat started that no send accounted for; null outside a chat root. */
2
+ export declare const AgentChatOwnRunsContext: import("react").Context<number | null>;
3
+ //# sourceMappingURL=agent-chat-own-runs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-chat-own-runs.d.ts","sourceRoot":"","sources":["../../src/hooks/agent-chat-own-runs.ts"],"names":[],"mappings":"AAQA,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,wCAAqC,CAAC"}
@@ -0,0 +1,8 @@
1
+ // The chat root → conversation bridge for runs no send accounts for. The server counts a turn
2
+ // per run, the chat's send seam (onUserMessageSent) one per send; a resume (an approval, an
3
+ // answered question) is a run with no send. The chat root provides how many such runs it has
4
+ // started; useAgentConversation accounts each as this client's own turn. Its own module so
5
+ // the threads entry does not pull in the chat runtime.
6
+ import { createContext } from "react";
7
+ /** Runs this chat started that no send accounted for; null outside a chat root. */
8
+ export const AgentChatOwnRunsContext = createContext(null);
@@ -67,6 +67,11 @@ export declare const accountedTurnsAfterRow: (accounted: AccountedTurns | null,
67
67
  * starts the count, so the row that lands for it is read as this send's, not a foreign turn.
68
68
  */
69
69
  export declare const accountedTurnsAfterOwnSend: (accounted: AccountedTurns | null, key: string, isDraft: boolean) => AccountedTurns | null;
70
+ /**
71
+ * The accounting after this client starts `runs` runs on `key` that no send accounted for (a
72
+ * resume: an approval, an answered question). Each is a turn on the server, as a send is.
73
+ */
74
+ export declare const accountedTurnsAfterOwnRuns: (accounted: AccountedTurns | null, key: string, isDraft: boolean, runs: number) => AccountedTurns | null;
70
75
  /** The chat runtime's side of the bridge — a subset of react-common's AgentChatContextValue. */
71
76
  export interface AgentConversationChat {
72
77
  isThinking: boolean;
@@ -97,7 +102,8 @@ export interface UseAgentConversationResult {
97
102
  /**
98
103
  * Keeps the index and the transcript agreeing for the open conversation:
99
104
  * - a run on it this client is not streaming (the listed row's `runningRunId`) reads as the chat's own;
100
- * - a turn this client did not send (`turnCount` ahead of what it accounted for) replays the transcript;
105
+ * - a turn this client did not make (`turnCount` ahead of what it accounted for: its sends, and
106
+ * the runs the chat root started without one) replays the transcript;
101
107
  * - a run mark past the stale ceiling asks the server again (the settle publishes no event);
102
108
  * - refocus re-walks the list (realtime has no replay), throttled.
103
109
  */
@@ -1 +1 @@
1
- {"version":3,"file":"use-agent-conversation.d.ts","sourceRoot":"","sources":["../../src/hooks/use-agent-conversation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-agent-conversation.d.ts","sourceRoot":"","sources":["../../src/hooks/use-agent-conversation.ts"],"names":[],"mappings":"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"}
@@ -2,8 +2,9 @@
2
2
  // (useAgentThreads) and the chat runtime (useAgentChat): which conversation is selected and
3
3
  // how a draft becomes a thread; the conversation's row; and the bridge that keeps the index
4
4
  // and the transcript agreeing. UI-free — the agent-layout registry item renders it.
5
- import { useCallback, useEffect, useRef, useState } from "react";
5
+ import { useCallback, useContext, useEffect, useRef, useState } from "react";
6
6
  import { FEEDBACK_MESSAGE_NAME } from "../utils/composer-message.js";
7
+ import { AgentChatOwnRunsContext } from "./agent-chat-own-runs.js";
7
8
  import { agentThreadKey } from "./agent-thread-live-update.js";
8
9
  export const newDraftSelection = (chatId, draftThreadId = crypto.randomUUID()) => ({
9
10
  conversation: { chatId, chatThreadId: draftThreadId },
@@ -106,10 +107,22 @@ export const accountedTurnsAfterOwnSend = (accounted, key, isDraft) => {
106
107
  }
107
108
  return isDraft ? { key, turns: 1 } : accounted;
108
109
  };
110
+ /**
111
+ * The accounting after this client starts `runs` runs on `key` that no send accounted for (a
112
+ * resume: an approval, an answered question). Each is a turn on the server, as a send is.
113
+ */
114
+ export const accountedTurnsAfterOwnRuns = (accounted, key, isDraft, runs) => {
115
+ let next = accounted;
116
+ for (let run = 0; run < runs; run++) {
117
+ next = accountedTurnsAfterOwnSend(next, key, isDraft);
118
+ }
119
+ return next;
120
+ };
109
121
  /**
110
122
  * Keeps the index and the transcript agreeing for the open conversation:
111
123
  * - a run on it this client is not streaming (the listed row's `runningRunId`) reads as the chat's own;
112
- * - a turn this client did not send (`turnCount` ahead of what it accounted for) replays the transcript;
124
+ * - a turn this client did not make (`turnCount` ahead of what it accounted for: its sends, and
125
+ * the runs the chat root started without one) replays the transcript;
113
126
  * - a run mark past the stale ceiling asks the server again (the settle publishes no event);
114
127
  * - refocus re-walks the list (realtime has no replay), throttled.
115
128
  */
@@ -177,9 +190,20 @@ export const useAgentConversation = ({ threads, agentId, conversation, isDraft,
177
190
  window.addEventListener("focus", onFocus);
178
191
  return () => window.removeEventListener("focus", onFocus);
179
192
  }, [refresh]);
180
- // Turns this client did not send (see accountedTurnsAfterRow). `isThinking` is a dependency,
193
+ // Turns this client did not make (see accountedTurnsAfterRow). `isThinking` is a dependency,
181
194
  // so this runs again the moment a run on the conversation settles.
182
195
  const accountedTurnsRef = useRef(null);
196
+ // The chat root's runs without a send, accounted before the row effect below reads a settle.
197
+ const ownRunsWithoutSend = useContext(AgentChatOwnRunsContext);
198
+ const seenOwnRunsRef = useRef(ownRunsWithoutSend);
199
+ useEffect(() => {
200
+ const seen = seenOwnRunsRef.current;
201
+ seenOwnRunsRef.current = ownRunsWithoutSend;
202
+ if (seen === null || ownRunsWithoutSend === null || ownRunsWithoutSend <= seen) {
203
+ return;
204
+ }
205
+ accountedTurnsRef.current = accountedTurnsAfterOwnRuns(accountedTurnsRef.current, conversationKey, isDraft, ownRunsWithoutSend - seen);
206
+ }, [ownRunsWithoutSend, conversationKey, isDraft]);
183
207
  useEffect(() => {
184
208
  if (!knownRow) {
185
209
  return;
@@ -0,0 +1,179 @@
1
+ /**
2
+ * What a parked agent turn waits on, and how the chat answers it.
3
+ *
4
+ * The server ends a parked run with the AG-UI standard `RUN_FINISHED.outcome`
5
+ * (`{type: "interrupt", interrupts}`), live and on connect. Each interrupt carries
6
+ * HumanInTheLoopMiddleware's requests in `metadata.langgraph.raw` -- name, args and
7
+ * description, no ids. One model step batches ask_user and approvals into ONE
8
+ * interrupt, and two paused sub-agents are two interrupts, so the chat collects one
9
+ * answer per request and resumes once with `resume[]`.
10
+ *
11
+ * Cards pair with tool calls BY ORDER, as ask_user cards already do:
12
+ * the middleware builds its requests in the waiting message's tool-call order, so
13
+ * the i-th request of a name is the i-th unanswered call of that name. A request
14
+ * with no such call belongs to a sub-agent, whose calls the parent transcript does
15
+ * not show: it hangs on the waiting `task` call. The server pairs the same way
16
+ * (wf-actions-py `hitl_pairing`). A wrong match can only misplace a card: the card
17
+ * draws the request's own name and args, and decisions stay positional.
18
+ *
19
+ * A past decision comes back on the tool message as `metadata["blocks.toolApprovals"]`.
20
+ * Who decided and when is a server-side audit record and is never shown.
21
+ */
22
+ import type { Interrupt, Message, ResumeEntry } from "@ag-ui/client";
23
+ export declare const TOOL_APPROVALS_METADATA_KEY = "blocks.toolApprovals";
24
+ export declare const APPROVAL_STATUS_LINE: {
25
+ readonly waiting: "Waiting for approval";
26
+ readonly approved: "Approved";
27
+ readonly rejected: "Rejected";
28
+ };
29
+ /** Appended to a decided card's status while another request of the pause is open. */
30
+ export declare const WAITING_FOR_OTHER_SUFFIX = " \u00B7 waiting for your decision on the other request";
31
+ export type ApprovalDecision = "approve" | "reject";
32
+ export type InterruptAnswer = {
33
+ type: "respond";
34
+ message: unknown;
35
+ } | {
36
+ type: ApprovalDecision;
37
+ };
38
+ export interface InterruptRequest {
39
+ interruptId: string;
40
+ index: number;
41
+ key: string;
42
+ name: string;
43
+ args: Record<string, unknown>;
44
+ allowedDecisions: string[];
45
+ /** The parent transcript call this request paired with; null for a sub-agent's request. */
46
+ toolCallId: string | null;
47
+ /** The transcript call the card hangs on: the call itself, or the waiting `task` call. */
48
+ anchorToolCallId: string | null;
49
+ /** The tool's describe name (`InsertItemsToTasksTable`); `name` is lowercased for the model. */
50
+ displayName: string | null;
51
+ }
52
+ export interface ToolApprovalStamp {
53
+ toolCallId: string;
54
+ decision: ApprovalDecision;
55
+ toolName: string;
56
+ /** Absent on records written before display names. */
57
+ displayName?: string | null;
58
+ args: Record<string, unknown>;
59
+ }
60
+ export interface ApprovalCardView {
61
+ key: string;
62
+ title: string;
63
+ /** The tool input as given; the cards show it as YAML. */
64
+ args: Record<string, unknown>;
65
+ status: "waiting" | "approved" | "rejected";
66
+ statusLine: string;
67
+ /** Set only while this card can still be decided. */
68
+ request: {
69
+ interruptId: string;
70
+ index: number;
71
+ } | null;
72
+ /** Decided here but not sent yet: another card of the same pause is still open. */
73
+ waitingForOthers: boolean;
74
+ }
75
+ export declare const requestKey: (interruptId: string, index: number) => string;
76
+ /**
77
+ * Every request of the open interrupts, in the order the server needs decisions,
78
+ * each paired with its transcript call by order.
79
+ */
80
+ export declare const requestsOf: (interrupts: readonly Interrupt[], messages: readonly Message[]) => InterruptRequest[];
81
+ export declare const isApprovalRequest: (request: InterruptRequest) => boolean;
82
+ /** Thrown with fixed text when an answer pairs with no open interrupt and is not the welcome seed. */
83
+ export declare const INTERRUPT_NOT_OPEN_ERROR = "answerInterrupt: nothing is waiting for this answer";
84
+ /**
85
+ * A question's answer as HumanInTheLoopMiddleware's `respond` message -- and as the welcome
86
+ * seed's tool result, so both read the same. `?? null`: JSON.stringify(undefined) drops the
87
+ * key, which the server refuses.
88
+ */
89
+ export declare const respondMessageOf: (answer: unknown) => string;
90
+ /**
91
+ * The welcome seed (`initialUserChoices`): connect writes an ask_user call that no run made,
92
+ * so nothing is parked behind it. Its signature is the one the server used
93
+ * (`answer_orphan_pause`): no user message yet, and the call still waits on the newest
94
+ * assistant message.
95
+ */
96
+ export declare const isWelcomeSeedCall: (messages: readonly Message[], toolCallId: string) => boolean;
97
+ type AnswerRoute = {
98
+ kind: "respond";
99
+ request: InterruptRequest;
100
+ } | {
101
+ kind: "seed";
102
+ } | {
103
+ kind: "refuse";
104
+ };
105
+ /**
106
+ * Where a question card's answer goes: the open request it pairs with by order; the seed's
107
+ * tool result when nothing is open; otherwise nowhere -- the caller
108
+ * throws INTERRUPT_NOT_OPEN_ERROR. Never `command.resume`.
109
+ */
110
+ export declare const answerRouteOf: (interrupts: readonly Interrupt[], messages: readonly Message[], toolCallId: string) => AnswerRoute;
111
+ /**
112
+ * The deprecated `resolveInterrupt(resume)`: a `{decisions}` payload for the ONE
113
+ * open interrupt, as a standard resolved entry. Null when it cannot be routed.
114
+ */
115
+ export declare const singleInterruptResume: (interrupts: readonly Interrupt[], payload: unknown) => ResumeEntry[] | null;
116
+ /**
117
+ * After a run that carried `resume[]`: anything but RUN_FINISHED (a RUN_ERROR such as a refused
118
+ * resume, a stop, a dropped stream) may have consumed the resume on the server, while
119
+ * @ag-ui/client changes `pendingInterrupts` only on RUN_FINISHED. The chat then forgets them and
120
+ * reconnects; the connect re-sends what is really open.
121
+ */
122
+ export declare const needsReconnectAfter: (sentResume: boolean, finished: boolean) => boolean;
123
+ /** One `resolved` entry per open interrupt, or null while any request is unanswered. */
124
+ export declare const buildResumeEntries: (interrupts: readonly Interrupt[], requests: readonly InterruptRequest[], answers: ReadonlyMap<string, InterruptAnswer>) => ResumeEntry[] | null;
125
+ /**
126
+ * Typing while the turn is parked: the typed text goes out as a normal
127
+ * message, and each open interrupt gets the protocol's own `cancelled` entry, with no payload.
128
+ * The server rejects every waiting approval with the standard text, skips every question with
129
+ * one fixed marker, and appends the message after them.
130
+ */
131
+ export declare const cancelledResumeEntries: (interrupts: readonly Interrupt[]) => ResumeEntry[];
132
+ /**
133
+ * Forgets the agent's open interrupts. CopilotKit 1.70.2 keeps
134
+ * `pendingInterrupts` across a thread swap and a reconnect, and @ag-ui/client 0.0.59
135
+ * then refuses the next run AND connect ("Thread has N pending interrupt(s) not
136
+ * addressed by resume"). Call it on a thread swap and right before every connect
137
+ * the chat starts; the connect's own RUN_FINISHED re-sends what is still open.
138
+ */
139
+ export declare const forgetOpenInterrupts: (agent: unknown) => void;
140
+ /** This browser's own approval decisions, by anchor, until a reload brings the record. */
141
+ export declare const localStampsByAnchor: (requests: readonly InterruptRequest[], answers: ReadonlyMap<string, InterruptAnswer>) => Map<string, ToolApprovalStamp[]>;
142
+ export declare const mergeStamps: (previous: ReadonlyMap<string, ToolApprovalStamp[]>, next: ReadonlyMap<string, ToolApprovalStamp[]>) => ReadonlyMap<string, ToolApprovalStamp[]>;
143
+ /**
144
+ * The decided-here stamps after the chat forgets what is open. A same-thread reload keeps them:
145
+ * the connect's snapshot brings the record, and until it lands nothing else draws the card. A
146
+ * thread or chat swap, or a reconnect after a failed run, clears them.
147
+ */
148
+ export declare const stampsAfterInterruptReset: (previous: ReadonlyMap<string, ToolApprovalStamp[]>, { keepStamps }: {
149
+ keepStamps: boolean;
150
+ }) => ReadonlyMap<string, ToolApprovalStamp[]>;
151
+ /** Takes back exactly the stamps `mergeStamps` added (by identity), when their send did not go through. */
152
+ export declare const dropStamps: (previous: ReadonlyMap<string, ToolApprovalStamp[]>, added: ReadonlyMap<string, ToolApprovalStamp[]>) => ReadonlyMap<string, ToolApprovalStamp[]>;
153
+ export declare const toolApprovalsOf: (message: unknown) => ToolApprovalStamp[];
154
+ /** `insert_items_in_orders` → `insert items in orders`. */
155
+ export declare const toolWords: (name: string) => string;
156
+ export declare const toolCallOf: (messages: readonly Message[], toolCallId: string) => {
157
+ name: string;
158
+ args: Record<string, unknown>;
159
+ } | null;
160
+ export declare const approvalCardsForToolCall: ({ toolCallId, toolName, toolArgs, toolResult, toolMessage, requests, answers, localStamps, }: {
161
+ toolCallId: string;
162
+ /** The transcript call the card hangs on (`task` for a sub-agent). */
163
+ toolName: string;
164
+ toolArgs: unknown;
165
+ toolResult: string | null;
166
+ toolMessage: unknown;
167
+ requests: readonly InterruptRequest[];
168
+ answers: ReadonlyMap<string, InterruptAnswer>;
169
+ localStamps: ReadonlyMap<string, readonly ToolApprovalStamp[]>;
170
+ }) => ApprovalCardView[];
171
+ /**
172
+ * Whether the tool's normal step renders under its cards: a sub-agent's `task` step
173
+ * always does; a parent call's only once approved AND sent (while waiting it has not
174
+ * run; a rejected call never runs; an approval waiting for the pause's other cards has
175
+ * not been sent yet).
176
+ */
177
+ export declare const toolStepVisibleBelowCards: (toolName: string, cards: readonly ApprovalCardView[]) => boolean;
178
+ export {};
179
+ //# sourceMappingURL=agui-interrupts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agui-interrupts.d.ts","sourceRoot":"","sources":["../../src/utils/agui-interrupts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,351 @@
1
+ export const TOOL_APPROVALS_METADATA_KEY = "blocks.toolApprovals";
2
+ const SUBAGENT_TOOL_NAME = "task";
3
+ /**
4
+ * A rejected call never ran; its result is the rejection. The server always uses
5
+ * HumanInTheLoopMiddleware's standard reject (the Reject button, typing, and a new
6
+ * Send Message). Used only when no record of the decision exists: a
7
+ * tool that failed also carries `error`.
8
+ */
9
+ const REJECTED_RESULT_PREFIXES = ["User rejected the tool call for `"];
10
+ export const APPROVAL_STATUS_LINE = {
11
+ waiting: "Waiting for approval",
12
+ approved: "Approved",
13
+ rejected: "Rejected",
14
+ };
15
+ /** Appended to a decided card's status while another request of the pause is open. */
16
+ export const WAITING_FOR_OTHER_SUFFIX = " · waiting for your decision on the other request";
17
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18
+ const isString = (value) => typeof value === "string";
19
+ const nonEmpty = (value) => (isString(value) && value ? value : null);
20
+ const parseArgs = (raw) => {
21
+ try {
22
+ const parsed = JSON.parse(raw || "{}");
23
+ return isRecord(parsed) ? parsed : {};
24
+ }
25
+ catch {
26
+ return {};
27
+ }
28
+ };
29
+ export const requestKey = (interruptId, index) => `${interruptId}#${index}`;
30
+ /** The name the model sees for a describe name (wf-actions-py `model_tool_name`). */
31
+ const modelToolName = (describeName) => describeName.replace(/[^a-zA-Z0-9_-]/g, "_").toLowerCase();
32
+ /**
33
+ * The server arms each approval with its tool's describe name as the request's
34
+ * `description`. Trusted only when it maps back to the request's name: a pause armed
35
+ * without one (and every pause parked before) carries the middleware's default text.
36
+ */
37
+ const displayNameOf = (name, description) => isString(description) && description && name && modelToolName(description) === name ? description : null;
38
+ const rawOf = (interrupt) => {
39
+ const metadata = isRecord(interrupt.metadata) ? interrupt.metadata : {};
40
+ const langgraph = isRecord(metadata.langgraph) ? metadata.langgraph : {};
41
+ return isRecord(langgraph.raw) ? langgraph.raw : {};
42
+ };
43
+ const allowedOf = (configs, index, name) => {
44
+ const read = (config) => isRecord(config) && Array.isArray(config.allowed_decisions) ? config.allowed_decisions.filter(isString) : [];
45
+ // The middleware appends one request and one config per gated call, in step.
46
+ const positional = configs[index];
47
+ if (isRecord(positional) && (!name || positional.action_name == null || positional.action_name === name)) {
48
+ return read(positional);
49
+ }
50
+ const byName = configs.find((config) => isRecord(config) && config.action_name === name);
51
+ return byName ? read(byName) : read(positional);
52
+ };
53
+ /** The newest assistant message's calls that have no result yet, in order. */
54
+ const waitingCalls = (messages) => {
55
+ const answered = new Set();
56
+ for (const message of messages) {
57
+ if (message.role === "tool" && "toolCallId" in message && isString(message.toolCallId)) {
58
+ answered.add(message.toolCallId);
59
+ }
60
+ }
61
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
62
+ const message = messages[i];
63
+ if (message.role !== "assistant") {
64
+ continue;
65
+ }
66
+ const calls = "toolCalls" in message && Array.isArray(message.toolCalls) ? message.toolCalls : [];
67
+ return calls
68
+ .filter((call) => !answered.has(call.id))
69
+ .map((call) => ({ id: call.id, name: call.function.name, args: parseArgs(call.function.arguments) }));
70
+ }
71
+ return [];
72
+ };
73
+ /**
74
+ * Every request of the open interrupts, in the order the server needs decisions,
75
+ * each paired with its transcript call by order.
76
+ */
77
+ export const requestsOf = (interrupts, messages) => {
78
+ const waiting = waitingCalls(messages);
79
+ const tasks = waiting.filter((call) => call.name === SUBAGENT_TOOL_NAME);
80
+ const own = waiting.filter((call) => call.name !== SUBAGENT_TOOL_NAME);
81
+ const host = tasks[0] ?? null;
82
+ const used = new Set();
83
+ const out = [];
84
+ for (const interrupt of interrupts) {
85
+ const raw = rawOf(interrupt);
86
+ const requests = Array.isArray(raw.action_requests) ? raw.action_requests : [];
87
+ const configs = Array.isArray(raw.review_configs) ? raw.review_configs : [];
88
+ requests.forEach((request, index) => {
89
+ const fields = isRecord(request) ? request : {};
90
+ const name = isString(fields.name) ? fields.name : "";
91
+ const match = own.find((call) => call.name === name && !used.has(call.id)) ?? null;
92
+ if (match) {
93
+ used.add(match.id);
94
+ }
95
+ out.push({
96
+ interruptId: interrupt.id,
97
+ index,
98
+ key: requestKey(interrupt.id, index),
99
+ name,
100
+ args: isRecord(fields.args) ? fields.args : {},
101
+ allowedDecisions: allowedOf(configs, index, name),
102
+ toolCallId: match?.id ?? null,
103
+ anchorToolCallId: match ? match.id : (host?.id ?? null),
104
+ displayName: displayNameOf(name, fields.description),
105
+ });
106
+ });
107
+ }
108
+ return out;
109
+ };
110
+ export const isApprovalRequest = (request) => request.allowedDecisions.includes("approve");
111
+ /** Thrown with fixed text when an answer pairs with no open interrupt and is not the welcome seed. */
112
+ export const INTERRUPT_NOT_OPEN_ERROR = "answerInterrupt: nothing is waiting for this answer";
113
+ /**
114
+ * A question's answer as HumanInTheLoopMiddleware's `respond` message -- and as the welcome
115
+ * seed's tool result, so both read the same. `?? null`: JSON.stringify(undefined) drops the
116
+ * key, which the server refuses.
117
+ */
118
+ export const respondMessageOf = (answer) => JSON.stringify(answer ?? null);
119
+ const decisionOf = (answer) => answer.type === "respond" ? { type: "respond", message: respondMessageOf(answer.message) } : { type: answer.type };
120
+ /**
121
+ * The welcome seed (`initialUserChoices`): connect writes an ask_user call that no run made,
122
+ * so nothing is parked behind it. Its signature is the one the server used
123
+ * (`answer_orphan_pause`): no user message yet, and the call still waits on the newest
124
+ * assistant message.
125
+ */
126
+ export const isWelcomeSeedCall = (messages, toolCallId) => !messages.some((message) => message.role === "user") && waitingCalls(messages).some((call) => call.id === toolCallId);
127
+ /**
128
+ * Where a question card's answer goes: the open request it pairs with by order; the seed's
129
+ * tool result when nothing is open; otherwise nowhere -- the caller
130
+ * throws INTERRUPT_NOT_OPEN_ERROR. Never `command.resume`.
131
+ */
132
+ export const answerRouteOf = (interrupts, messages, toolCallId) => {
133
+ const request = requestsOf(interrupts, messages).find((candidate) => candidate.toolCallId === toolCallId && !isApprovalRequest(candidate));
134
+ if (request) {
135
+ return { kind: "respond", request };
136
+ }
137
+ if (interrupts.length === 0 && isWelcomeSeedCall(messages, toolCallId)) {
138
+ return { kind: "seed" };
139
+ }
140
+ return { kind: "refuse" };
141
+ };
142
+ /**
143
+ * The deprecated `resolveInterrupt(resume)`: a `{decisions}` payload for the ONE
144
+ * open interrupt, as a standard resolved entry. Null when it cannot be routed.
145
+ */
146
+ export const singleInterruptResume = (interrupts, payload) => interrupts.length === 1 && isRecord(payload) && Array.isArray(payload.decisions)
147
+ ? [{ interruptId: interrupts[0].id, status: "resolved", payload: { decisions: payload.decisions } }]
148
+ : null;
149
+ /**
150
+ * After a run that carried `resume[]`: anything but RUN_FINISHED (a RUN_ERROR such as a refused
151
+ * resume, a stop, a dropped stream) may have consumed the resume on the server, while
152
+ * @ag-ui/client changes `pendingInterrupts` only on RUN_FINISHED. The chat then forgets them and
153
+ * reconnects; the connect re-sends what is really open.
154
+ */
155
+ export const needsReconnectAfter = (sentResume, finished) => sentResume && !finished;
156
+ /** One `resolved` entry per open interrupt, or null while any request is unanswered. */
157
+ export const buildResumeEntries = (interrupts, requests, answers) => {
158
+ if (interrupts.length === 0) {
159
+ return null;
160
+ }
161
+ const entries = [];
162
+ for (const interrupt of interrupts) {
163
+ const decisions = [];
164
+ for (const request of requests.filter((r) => r.interruptId === interrupt.id)) {
165
+ const answer = answers.get(request.key);
166
+ if (!answer) {
167
+ return null;
168
+ }
169
+ decisions.push(decisionOf(answer));
170
+ }
171
+ entries.push({ interruptId: interrupt.id, status: "resolved", payload: { decisions } });
172
+ }
173
+ return entries;
174
+ };
175
+ /**
176
+ * Typing while the turn is parked: the typed text goes out as a normal
177
+ * message, and each open interrupt gets the protocol's own `cancelled` entry, with no payload.
178
+ * The server rejects every waiting approval with the standard text, skips every question with
179
+ * one fixed marker, and appends the message after them.
180
+ */
181
+ export const cancelledResumeEntries = (interrupts) => interrupts.map((interrupt) => ({ interruptId: interrupt.id, status: "cancelled" }));
182
+ /**
183
+ * Forgets the agent's open interrupts. CopilotKit 1.70.2 keeps
184
+ * `pendingInterrupts` across a thread swap and a reconnect, and @ag-ui/client 0.0.59
185
+ * then refuses the next run AND connect ("Thread has N pending interrupt(s) not
186
+ * addressed by resume"). Call it on a thread swap and right before every connect
187
+ * the chat starts; the connect's own RUN_FINISHED re-sends what is still open.
188
+ */
189
+ export const forgetOpenInterrupts = (agent) => {
190
+ if (agent) {
191
+ agent.pendingInterrupts = [];
192
+ }
193
+ };
194
+ /** This browser's own approval decisions, by anchor, until a reload brings the record. */
195
+ export const localStampsByAnchor = (requests, answers) => {
196
+ const out = new Map();
197
+ for (const request of requests) {
198
+ const answer = answers.get(request.key);
199
+ if (!answer || answer.type === "respond" || !isApprovalRequest(request) || !request.anchorToolCallId) {
200
+ continue;
201
+ }
202
+ const list = out.get(request.anchorToolCallId) ?? [];
203
+ list.push({
204
+ // A sub-agent's call is not in this transcript; its request key stands in.
205
+ toolCallId: request.toolCallId ?? request.key,
206
+ decision: answer.type,
207
+ toolName: request.name,
208
+ displayName: request.displayName,
209
+ args: request.args,
210
+ });
211
+ out.set(request.anchorToolCallId, list);
212
+ }
213
+ return out;
214
+ };
215
+ export const mergeStamps = (previous, next) => {
216
+ if (next.size === 0) {
217
+ return previous;
218
+ }
219
+ const merged = new Map(previous);
220
+ next.forEach((stamps, anchor) => merged.set(anchor, [...(merged.get(anchor) ?? []), ...stamps]));
221
+ return merged;
222
+ };
223
+ /**
224
+ * The decided-here stamps after the chat forgets what is open. A same-thread reload keeps them:
225
+ * the connect's snapshot brings the record, and until it lands nothing else draws the card. A
226
+ * thread or chat swap, or a reconnect after a failed run, clears them.
227
+ */
228
+ export const stampsAfterInterruptReset = (previous, { keepStamps }) => (keepStamps || previous.size === 0 ? previous : new Map());
229
+ /** Takes back exactly the stamps `mergeStamps` added (by identity), when their send did not go through. */
230
+ export const dropStamps = (previous, added) => {
231
+ if (added.size === 0) {
232
+ return previous;
233
+ }
234
+ const kept = new Map();
235
+ previous.forEach((stamps, anchor) => {
236
+ const drop = new Set(added.get(anchor) ?? []);
237
+ const rest = stamps.filter((stamp) => !drop.has(stamp));
238
+ if (rest.length > 0) {
239
+ kept.set(anchor, rest);
240
+ }
241
+ });
242
+ return kept;
243
+ };
244
+ export const toolApprovalsOf = (message) => {
245
+ if (!isRecord(message) || !isRecord(message.metadata)) {
246
+ return [];
247
+ }
248
+ const raw = message.metadata[TOOL_APPROVALS_METADATA_KEY];
249
+ if (!Array.isArray(raw)) {
250
+ return [];
251
+ }
252
+ return raw.flatMap((item) => isRecord(item) &&
253
+ (item.decision === "approve" || item.decision === "reject") &&
254
+ isString(item.toolName) &&
255
+ isString(item.toolCallId)
256
+ ? [
257
+ {
258
+ toolCallId: item.toolCallId,
259
+ decision: item.decision,
260
+ toolName: item.toolName,
261
+ displayName: nonEmpty(item.displayName),
262
+ args: isRecord(item.args) ? item.args : {},
263
+ },
264
+ ]
265
+ : []);
266
+ };
267
+ const rejectedByResult = (result) => isString(result) && REJECTED_RESULT_PREFIXES.some((prefix) => result.startsWith(prefix));
268
+ /** `insert_items_in_orders` → `insert items in orders`. */
269
+ export const toolWords = (name) => name
270
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
271
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
272
+ .replace(/[_\s-]+/g, " ")
273
+ .trim()
274
+ .toLowerCase();
275
+ export const toolCallOf = (messages, toolCallId) => {
276
+ for (const message of messages) {
277
+ if (message.role !== "assistant" || !("toolCalls" in message) || !Array.isArray(message.toolCalls)) {
278
+ continue;
279
+ }
280
+ const call = message.toolCalls.find((candidate) => candidate.id === toolCallId);
281
+ if (call) {
282
+ return { name: call.function.name, args: parseArgs(call.function.arguments) };
283
+ }
284
+ }
285
+ return null;
286
+ };
287
+ const titleOf = (toolName) => {
288
+ const words = toolWords(toolName);
289
+ return words.charAt(0).toUpperCase() + words.slice(1);
290
+ };
291
+ const statusOf = (decision) => decision === "approve" ? "approved" : "rejected";
292
+ export const approvalCardsForToolCall = ({ toolCallId, toolName, toolArgs, toolResult, toolMessage, requests, answers, localStamps, }) => {
293
+ const live = requests.filter((request) => request.anchorToolCallId === toolCallId && isApprovalRequest(request));
294
+ if (live.length > 0) {
295
+ const othersOpen = requests.some((request) => !answers.has(request.key));
296
+ return live.map((request) => {
297
+ const answer = answers.get(request.key);
298
+ const status = answer && answer.type !== "respond" ? statusOf(answer.type) : "waiting";
299
+ const waitingForOthers = status !== "waiting" && othersOpen;
300
+ return {
301
+ key: request.key,
302
+ title: titleOf(request.displayName ?? request.name),
303
+ args: request.args,
304
+ status,
305
+ statusLine: waitingForOthers
306
+ ? `${APPROVAL_STATUS_LINE[status]}${WAITING_FOR_OTHER_SUFFIX}`
307
+ : APPROVAL_STATUS_LINE[status],
308
+ request: status === "waiting" ? { interruptId: request.interruptId, index: request.index } : null,
309
+ waitingForOthers,
310
+ };
311
+ });
312
+ }
313
+ // The server's record wins once a reload brought it; until then, this browser's own.
314
+ const recorded = toolApprovalsOf(toolMessage);
315
+ const decided = recorded.length > 0 ? recorded : (localStamps.get(toolCallId) ?? []);
316
+ if (decided.length > 0) {
317
+ return decided.map((stamp) => {
318
+ const status = statusOf(stamp.decision);
319
+ return {
320
+ key: `${toolCallId}#${stamp.toolCallId}`,
321
+ title: titleOf(stamp.displayName ?? stamp.toolName),
322
+ args: stamp.args,
323
+ status,
324
+ statusLine: APPROVAL_STATUS_LINE[status],
325
+ request: null,
326
+ waitingForOthers: false,
327
+ };
328
+ });
329
+ }
330
+ if (toolName !== SUBAGENT_TOOL_NAME && rejectedByResult(toolResult)) {
331
+ return [
332
+ {
333
+ key: `${toolCallId}#rejected`,
334
+ title: titleOf(toolName),
335
+ args: isRecord(toolArgs) ? toolArgs : {},
336
+ status: "rejected",
337
+ statusLine: APPROVAL_STATUS_LINE.rejected,
338
+ request: null,
339
+ waitingForOthers: false,
340
+ },
341
+ ];
342
+ }
343
+ return [];
344
+ };
345
+ /**
346
+ * Whether the tool's normal step renders under its cards: a sub-agent's `task` step
347
+ * always does; a parent call's only once approved AND sent (while waiting it has not
348
+ * run; a rejected call never runs; an approval waiting for the pause's other cards has
349
+ * not been sent yet).
350
+ */
351
+ export const toolStepVisibleBelowCards = (toolName, cards) => toolName === SUBAGENT_TOOL_NAME || cards.every((card) => card.status === "approved" && !card.waitingForOthers);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocksdiy/react-common",
3
- "version": "1.51.0",
3
+ "version": "1.52.1",
4
4
  "type": "module",
5
5
  "description": "React common",
6
6
  "keywords": [],
@@ -39,7 +39,7 @@
39
39
  "penpal": "^8.0.0",
40
40
  "react": "^19.2.8",
41
41
  "use-stick-to-bottom": "1.1.4",
42
- "@blocksdiy/blocks-client-api": "1.5.0"
42
+ "@blocksdiy/blocks-client-api": "1.25.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/react": "^19.2.18",