@blocksdiy/react-common 1.50.1 → 1.52.0
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.
- package/dist/components/new-agent-chat.d.ts +14 -0
- package/dist/components/new-agent-chat.d.ts.map +1 -1
- package/dist/components/new-agent-chat.js +204 -80
- package/dist/hooks/agent-chat-own-runs.d.ts +3 -0
- package/dist/hooks/agent-chat-own-runs.d.ts.map +1 -0
- package/dist/hooks/agent-chat-own-runs.js +8 -0
- package/dist/hooks/use-agent-conversation.d.ts +7 -1
- package/dist/hooks/use-agent-conversation.d.ts.map +1 -1
- package/dist/hooks/use-agent-conversation.js +27 -3
- package/dist/hooks/use-agent-threads.d.ts +1 -0
- package/dist/hooks/use-agent-threads.d.ts.map +1 -1
- package/dist/hooks/use-agent-threads.js +1 -0
- package/dist/utils/agent-admin.d.ts +28 -0
- package/dist/utils/agent-admin.d.ts.map +1 -0
- package/dist/utils/agent-admin.js +48 -0
- package/dist/utils/agui-interrupts.d.ts +179 -0
- package/dist/utils/agui-interrupts.d.ts.map +1 -0
- package/dist/utils/agui-interrupts.js +351 -0
- package/package.json +2 -2
|
@@ -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 {
|
|
@@ -44,6 +46,9 @@ export interface Agent {
|
|
|
44
46
|
photoUrl?: string;
|
|
45
47
|
voiceId?: string;
|
|
46
48
|
harness?: string;
|
|
49
|
+
/** Role names whose users are admins of this agent; unset or empty = every signed-in user. See `isAgentAdmin`. */
|
|
50
|
+
/** `undefined` = unset (no restriction); `null` = malformed (nobody is an admin). */
|
|
51
|
+
adminRoles?: string[] | null;
|
|
47
52
|
}
|
|
48
53
|
export type OnUserMessageSent = (text: string, message: Omit<UserMessage, "id" | "role">) => void;
|
|
49
54
|
export interface AgentChatContextValue {
|
|
@@ -75,11 +80,20 @@ export interface AgentChatContextValue {
|
|
|
75
80
|
sendMessage: (message: Omit<UserMessage, "id" | "role">) => Promise<void>;
|
|
76
81
|
sendFromInputs: () => Promise<void>;
|
|
77
82
|
reloadMessages: () => Promise<void>;
|
|
83
|
+
/** @deprecated Answer with `answerInterrupt`; kept for the frozen-copy contract. */
|
|
78
84
|
resolveInterrupt: (resume: unknown) => Promise<boolean>;
|
|
79
85
|
answerInterrupt: (answer: {
|
|
80
86
|
toolCallId: string;
|
|
81
87
|
answer: unknown;
|
|
82
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[];
|
|
83
97
|
stopGeneration: () => void;
|
|
84
98
|
addAttachments: (attachments: Attachment[]) => void;
|
|
85
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":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"new-agent-chat.d.ts","sourceRoot":"","sources":["../../src/components/new-agent-chat.tsx"],"names":[],"mappings":"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"}
|
|
@@ -5,12 +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";
|
|
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";
|
|
8
11
|
import { ComposeRender } from "../utils/compose-render.js";
|
|
9
12
|
import { buildArmedMessageContent, userTextOfContent } from "../utils/composer-message.js";
|
|
10
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";
|
|
11
14
|
export { useTextGrowthAnimating } from "../hooks/text-growth-animation.js";
|
|
12
15
|
export { FEEDBACK_MESSAGE_NAME, feedbackComposerMeta, feedbackDraftOf, feedbackMessage, feedbackThreadId, isFeedbackMessage, isFeedbackTag, parseFeedbackContent, parseFeedbackTag, } from "../utils/composer-message.js";
|
|
13
16
|
export { MESSAGE_RUN_IDS_EVENT_NAME, messageIdsOfRun, } from "../utils/message-run-ids.js";
|
|
17
|
+
export { toolStepVisibleBelowCards } from "../utils/agui-interrupts.js";
|
|
14
18
|
export { useRenderTool, useDefaultRenderTool, useHumanInTheLoop, useFrontendTool, ToolCallStatus, } from "@copilotkit/react-core/v2";
|
|
15
19
|
// Client-only `useLayoutEffect`, falls back to `useEffect` during SSR.
|
|
16
20
|
// Needed so the `agent.threadId` mirror commits before CopilotKit's
|
|
@@ -52,35 +56,6 @@ const _collectPendingToolCalls = (messages) => {
|
|
|
52
56
|
}
|
|
53
57
|
return pending;
|
|
54
58
|
};
|
|
55
|
-
// Message order IS the order of the interrupt's `action_requests`, so decisions
|
|
56
|
-
// pair with them by index and need no ids. Matched by name rather than by "has no
|
|
57
|
-
// local handler", so a backend tool merely mid-flight is never taken for a pause.
|
|
58
|
-
const _SERVER_PAUSE_TOOL_NAME = "ask_user";
|
|
59
|
-
const _SERVER_PAUSE_TOOL_PREFIX = "ask-component-";
|
|
60
|
-
const _isServerPause = (toolCall) => toolCall.function.name === _SERVER_PAUSE_TOOL_NAME || toolCall.function.name.startsWith(_SERVER_PAUSE_TOOL_PREFIX);
|
|
61
|
-
// ONE assistant message's pauses, never the transcript's: the server batches a
|
|
62
|
-
// turn's pauses into a single interrupt requiring exactly one decision each, and a
|
|
63
|
-
// transcript can also hold pauses from a run that died before parking. Counting
|
|
64
|
-
// those raises the count check — permanently, since the checkpoint never advances.
|
|
65
|
-
const _collectServerPauses = (messages) => {
|
|
66
|
-
const answered = new Set();
|
|
67
|
-
for (const m of messages) {
|
|
68
|
-
if (m.role === "tool" && "toolCallId" in m && typeof m.toolCallId === "string") {
|
|
69
|
-
answered.add(m.toolCallId);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
73
|
-
const m = messages[i];
|
|
74
|
-
if (m.role !== "assistant" || !("toolCalls" in m) || !m.toolCalls) {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const pauses = m.toolCalls.filter((toolCall) => !answered.has(toolCall.id) && _isServerPause(toolCall));
|
|
78
|
-
if (pauses.length > 0) {
|
|
79
|
-
return pauses;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return [];
|
|
83
|
-
};
|
|
84
59
|
// CopilotKit 1.60.x: `connectAgent()` awaits every replayed HITL tool call's
|
|
85
60
|
// handler before resolving `isAvailable`, and a `useHumanInTheLoop` handler
|
|
86
61
|
// only resolves on `respond()` — so a transcript replayed with an unanswered
|
|
@@ -90,6 +65,14 @@ const _collectServerPauses = (messages) => {
|
|
|
90
65
|
const isConnectParkedOnPendingToolCall = (messages) => {
|
|
91
66
|
return messages.length > 0 && _collectPendingToolCalls(messages).length > 0;
|
|
92
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;
|
|
93
76
|
const validateAndConvertFiles = (files) => {
|
|
94
77
|
if (files.length === 0) {
|
|
95
78
|
return null;
|
|
@@ -317,6 +300,29 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
317
300
|
const [runStartedAt, setRunStartedAt] = useState(EMPTY_RUN_STARTED_AT);
|
|
318
301
|
const liveRunRef = useRef(null);
|
|
319
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);
|
|
320
326
|
useEffect(() => {
|
|
321
327
|
if (!copilotAgent) {
|
|
322
328
|
return;
|
|
@@ -348,6 +354,9 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
348
354
|
onToolCallStartEvent: ({ event }) => claimLive(event.parentMessageId ?? event.toolCallId),
|
|
349
355
|
onRunFinalized: () => {
|
|
350
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));
|
|
351
360
|
},
|
|
352
361
|
onCustomEvent: ({ event }) => {
|
|
353
362
|
const runIds = readMessageRunIdsEvent(event);
|
|
@@ -365,7 +374,6 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
365
374
|
liveRunRef.current = null;
|
|
366
375
|
};
|
|
367
376
|
}, [copilotAgent]);
|
|
368
|
-
const interruptAnswersRef = useRef(new Map());
|
|
369
377
|
// Mirror `currentThreadId` onto the agent instance — the `<CopilotKit>`
|
|
370
378
|
// prop only feeds React context, but `agent.threadId` is what hits the
|
|
371
379
|
// wire and the LangGraph checkpoint. `<CopilotChat>` does this for you;
|
|
@@ -379,8 +387,8 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
379
387
|
}
|
|
380
388
|
// Carrying answers across a thread swap would pair them with another
|
|
381
389
|
// conversation's pause.
|
|
382
|
-
|
|
383
|
-
}, [copilotAgent, currentThreadId]);
|
|
390
|
+
resetInterrupts();
|
|
391
|
+
}, [copilotAgent, currentThreadId, resetInterrupts]);
|
|
384
392
|
const [prompt, setPrompt] = useState("");
|
|
385
393
|
const [attachments, setAttachments] = useState([]);
|
|
386
394
|
const [composerMessageMeta, setComposerMessageMeta] = useState(null);
|
|
@@ -403,6 +411,7 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
403
411
|
setPrompt("");
|
|
404
412
|
setAttachments([]);
|
|
405
413
|
setComposerMessageMeta(null);
|
|
414
|
+
resetInterrupts();
|
|
406
415
|
setOwnRunInFlight(false);
|
|
407
416
|
const detachable = copilotAgent;
|
|
408
417
|
const drained = detachable?.detachActiveRun?.();
|
|
@@ -426,18 +435,25 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
426
435
|
}
|
|
427
436
|
copilotAgent.setState?.({});
|
|
428
437
|
copilotAgent.clearReplayCursor?.(currentThreadId);
|
|
438
|
+
// Answers and decided-here stamps too; the connect re-sends what is open.
|
|
439
|
+
resetInterrupts();
|
|
429
440
|
void copilotkit.connectAgent({ agent: copilotAgent }).catch(() => undefined);
|
|
430
|
-
}, [chatId, currentThreadId, copilotAgent, copilotkit]);
|
|
441
|
+
}, [chatId, currentThreadId, copilotAgent, copilotkit, resetInterrupts]);
|
|
431
442
|
const [isDraggingFiles, setIsDraggingFiles] = useState(false);
|
|
432
443
|
const attachmentsEnabled = agentChat ? agentChat.disableAttachments !== true : false;
|
|
433
444
|
const canSendMessage = Boolean(agentChatBlock && agentChat && agent && isConfigLoaded && isAgentReady && !isAgentRunning && !isRunningElsewhere);
|
|
434
445
|
const isFetchingMessages = !isConfigLoaded || (!isAgentReady && copilotMessages.length === 0);
|
|
435
446
|
const isThinking = (isAgentRunning || isRunningElsewhere) && !isFetchingMessages && isAgentReady;
|
|
436
|
-
const runAgentWithRetry = useCallback(async (agentToRun, messageCountAtRunStart, forwardedProps) => {
|
|
447
|
+
const runAgentWithRetry = useCallback(async (agentToRun, messageCountAtRunStart, forwardedProps, resume, withoutSend = false) => {
|
|
437
448
|
// A run elsewhere is on this conversation's checkpoint too: ours would race it.
|
|
438
449
|
if (agentToRun.isRunning || isRunningElsewhereRef.current) {
|
|
439
450
|
return false;
|
|
440
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;
|
|
441
457
|
const runAgentAndThrowError = async () => {
|
|
442
458
|
let runEventError;
|
|
443
459
|
const { unsubscribe } = agentToRun.subscribe({
|
|
@@ -447,9 +463,12 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
447
463
|
onRunErrorEvent: (event) => {
|
|
448
464
|
runEventError = event;
|
|
449
465
|
},
|
|
466
|
+
onRunFinishedEvent: () => {
|
|
467
|
+
finished = true;
|
|
468
|
+
},
|
|
450
469
|
});
|
|
451
470
|
try {
|
|
452
|
-
await copilotkit.runAgent({ agent: agentToRun, forwardedProps });
|
|
471
|
+
await copilotkit.runAgent({ agent: agentToRun, forwardedProps, ...(resume ? { resume } : {}) });
|
|
453
472
|
if (runEventError) {
|
|
454
473
|
throw runEventError;
|
|
455
474
|
}
|
|
@@ -461,26 +480,41 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
461
480
|
// In flight until the run ends, whichever way (see `runningElsewhereRunId`) — unless
|
|
462
481
|
// the conversation switched under it, which resets this itself.
|
|
463
482
|
const ranOn = prevConversationKeyRef.current;
|
|
483
|
+
let reconnected = false;
|
|
464
484
|
setOwnRunInFlight(true);
|
|
465
485
|
try {
|
|
466
486
|
await runAgentAndThrowError();
|
|
467
|
-
return true;
|
|
468
487
|
}
|
|
469
488
|
catch (error) {
|
|
470
489
|
const hasAgentStartedResponding = agentToRun.messages.length > messageCountAtRunStart;
|
|
471
490
|
const status = getAgentRunErrorStatus(error);
|
|
472
|
-
|
|
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) {
|
|
473
494
|
await runAgentAndThrowError();
|
|
474
|
-
return true;
|
|
475
495
|
}
|
|
476
|
-
|
|
496
|
+
else {
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
477
499
|
}
|
|
478
500
|
finally {
|
|
479
|
-
|
|
501
|
+
const stillHere = prevConversationKeyRef.current === ranOn;
|
|
502
|
+
if (stillHere) {
|
|
480
503
|
setOwnRunInFlight(false);
|
|
481
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
|
+
}
|
|
482
514
|
}
|
|
483
|
-
|
|
515
|
+
// A reconnected run did not take effect as sent: callers must not record it as done.
|
|
516
|
+
return !reconnected;
|
|
517
|
+
}, [copilotkit, currentThreadId, resetInterrupts]);
|
|
484
518
|
const sendMessage = useCallback(async (message) => {
|
|
485
519
|
if (!canSendMessage || !copilotAgent || copilotAgent.isRunning || isRunningElsewhereRef.current) {
|
|
486
520
|
return;
|
|
@@ -489,71 +523,157 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
489
523
|
if (onUserMessageSent) {
|
|
490
524
|
onUserMessageSent(userTextOfContent(message.content), message);
|
|
491
525
|
}
|
|
492
|
-
|
|
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
|
+
}
|
|
493
558
|
}, [canSendMessage, copilotAgent, runAgentWithRetry, onUserMessageSent]);
|
|
494
559
|
const reloadMessages = useCallback(async () => {
|
|
495
560
|
if (!copilotAgent || copilotAgent.isRunning) {
|
|
496
561
|
return;
|
|
497
562
|
}
|
|
498
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 });
|
|
499
567
|
await copilotkit.connectAgent({ agent: copilotAgent }).catch(() => undefined);
|
|
500
|
-
}, [copilotAgent, copilotkit, currentThreadId]);
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
// 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[]`.
|
|
505
572
|
const resolveInterrupt = useCallback(async (resume) => {
|
|
506
573
|
if (!copilotAgent) {
|
|
507
574
|
return false;
|
|
508
575
|
}
|
|
509
|
-
|
|
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);
|
|
510
581
|
}, [copilotAgent, runAgentWithRetry]);
|
|
511
|
-
// Resumes
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
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 () => {
|
|
515
585
|
if (!copilotAgent) {
|
|
516
586
|
return;
|
|
517
587
|
}
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
const answers = interruptAnswersRef.current;
|
|
525
|
-
answers.set(toolCallId, answer);
|
|
526
|
-
const unanswered = pauses.filter((toolCall) => !answers.has(toolCall.id));
|
|
527
|
-
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) {
|
|
528
593
|
return;
|
|
529
594
|
}
|
|
530
|
-
const decisions = pauses.map((toolCall) => ({
|
|
531
|
-
type: "respond",
|
|
532
|
-
// `?? null` because a component may call `respond()` with no payload, and
|
|
533
|
-
// `JSON.stringify(undefined)` drops the key, which raises server-side.
|
|
534
|
-
message: JSON.stringify(answers.get(toolCall.id) ?? null),
|
|
535
|
-
}));
|
|
536
595
|
const sent = new Map(answers);
|
|
537
596
|
answers.clear();
|
|
538
|
-
|
|
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));
|
|
539
602
|
let resumed = false;
|
|
540
603
|
try {
|
|
541
|
-
|
|
542
|
-
// happened discards the rest of the turn's cards with it.
|
|
543
|
-
resumed = await resolveInterrupt({ decisions });
|
|
604
|
+
resumed = await runAgentWithRetry(copilotAgent, copilotAgent.messages.length, undefined, resume, true);
|
|
544
605
|
}
|
|
545
606
|
catch (error) {
|
|
546
|
-
|
|
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);
|
|
547
613
|
throw error;
|
|
548
614
|
}
|
|
549
615
|
if (!resumed) {
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
|
|
554
|
-
|
|
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;
|
|
555
661
|
}
|
|
556
|
-
|
|
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]);
|
|
557
677
|
const stopGeneration = useCallback(() => {
|
|
558
678
|
copilotStopGeneration();
|
|
559
679
|
copilotAgent?.abortRun?.();
|
|
@@ -591,6 +711,7 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
591
711
|
jobTitle: agentBlock?.data?.jobTitle,
|
|
592
712
|
photoUrl: agentBlock?.data?.avatar?.url || agentBlock?.data?.photo?.url,
|
|
593
713
|
voiceId: agentBlock?.data?.voiceId,
|
|
714
|
+
adminRoles: readAgentAdminRoles(agentBlock?.data?.adminRoles),
|
|
594
715
|
});
|
|
595
716
|
}
|
|
596
717
|
catch {
|
|
@@ -823,6 +944,8 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
823
944
|
reloadMessages,
|
|
824
945
|
resolveInterrupt,
|
|
825
946
|
answerInterrupt,
|
|
947
|
+
decideApproval,
|
|
948
|
+
approvalCardsFor,
|
|
826
949
|
stopGeneration,
|
|
827
950
|
addAttachments,
|
|
828
951
|
removeAttachment,
|
|
@@ -843,7 +966,7 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
843
966
|
components: components ?? [],
|
|
844
967
|
componentsLoading,
|
|
845
968
|
currentThreadId,
|
|
846
|
-
}, children: children }));
|
|
969
|
+
}, children: _jsx(AgentChatOwnRunsContext.Provider, { value: ownRunsWithoutSend, children: children }) }));
|
|
847
970
|
};
|
|
848
971
|
export const AgentChatRoot = ({ ...props
|
|
849
972
|
// agentChatData,
|
|
@@ -1005,6 +1128,7 @@ export const AgentChatRoot = ({ ...props
|
|
|
1005
1128
|
noPersistency,
|
|
1006
1129
|
chatContext: chatContextStr,
|
|
1007
1130
|
chatContextFiles,
|
|
1131
|
+
supportsInterruptOutcome: true,
|
|
1008
1132
|
...(selfEditEnabled ? { selfEditEnabled: true } : {}),
|
|
1009
1133
|
...(supportsServerInterrupt ? { supportsServerInterrupt: true } : {}),
|
|
1010
1134
|
...(serverInterruptComponentIds.length ? { serverInterruptComponentIds } : {}),
|
|
@@ -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
|
|
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":"
|
|
1
|
+
{"version":3,"file":"use-agent-conversation.d.ts","sourceRoot":"","sources":["../../src/hooks/use-agent-conversation.ts"],"names":[],"mappings":"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"}
|