@agent-native/core 0.188.0 → 0.189.0-nightly-20260925200527
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/corpus/README.md +1 -1
- package/corpus/templates/content/actions/list-document-versions.ts +102 -2
- package/corpus/templates/content/actions/restore-document-version.ts +50 -2
- package/corpus/templates/content/app/components/layout/Layout.tsx +6 -1
- package/corpus/templates/content/changelog/2026-09-25-chat-messages-can-be-reverted-to-their-autosaved-version-inc.md +6 -0
- package/corpus/templates/content/server/lib/document-history.ts +59 -8
- package/corpus/templates/content/server/lib/document-version-context.ts +5 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +164 -0
- package/corpus/templates/design/actions/list-design-versions.ts +3 -1
- package/corpus/templates/design/app/components/layout/Layout.tsx +17 -2
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +10 -3
- package/corpus/templates/design/changelog/2026-09-25-chat-messages-can-be-reverted-to-their-autosaved-version-inc.md +6 -0
- package/corpus/templates/design/server/lib/design-versions.ts +136 -20
- package/corpus/templates/design/server/plugins/agent-chat.ts +18 -2
- package/corpus/templates/slides/actions/list-deck-versions.ts +62 -10
- package/corpus/templates/slides/app/components/layout/Layout.tsx +10 -2
- package/corpus/templates/slides/changelog/2026-09-25-chat-messages-can-be-reverted-to-their-autosaved-version-inc.md +6 -0
- package/corpus/templates/slides/server/lib/deck-versions.ts +36 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +26 -2
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/AssistantChat.js +112 -35
- package/dist/client/chat/assistant-chat-history-version.d.ts +1 -0
- package/dist/client/chat/message-components.d.ts +8 -1
- package/dist/client/chat/message-components.js +52 -11
- package/dist/client/resources/mcp-integration-catalog.js +1 -1
- package/dist/client/resources/mcp-integration-logos.js +29 -30
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +2 -0
- package/dist/localization/core-messages/de-DE.js +2 -0
- package/dist/localization/core-messages/en-US.d.ts +2 -0
- package/dist/localization/core-messages/en-US.js +2 -0
- package/dist/localization/core-messages/es-ES.js +2 -0
- package/dist/localization/core-messages/fr-FR.js +2 -0
- package/dist/localization/core-messages/hi-IN.js +2 -0
- package/dist/localization/core-messages/ja-JP.js +2 -0
- package/dist/localization/core-messages/ko-KR.js +2 -0
- package/dist/localization/core-messages/pt-BR.js +2 -0
- package/dist/localization/core-messages/zh-CN.js +2 -0
- package/dist/localization/core-messages/zh-TW.js +2 -0
- package/dist/localization/core-messages.d.ts +2 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +4 -4
- package/dist/org/handlers.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat/plugin-options.d.ts +2 -0
- package/dist/server/agent-chat-plugin.d.ts +1 -0
- package/dist/server/agent-chat-plugin.js +26 -0
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/onboarding-html.js +28 -5
- package/docs/content/deployment.mdx +12 -1
- package/package.json +1 -1
|
@@ -23,7 +23,7 @@ import { AgentActivityTrace } from "./chat/agent-activity-trace.js";
|
|
|
23
23
|
import { DownscalingImageAttachmentAdapter, BinaryDocumentAttachmentAdapter, MAX_PDF_BYTES, MAX_ESTIMATED_BODY_BYTES, AGGRESSIVE_MAX_IMAGE_DIMENSION, AGGRESSIVE_JPEG_QUALITY, transcodeImageToDataURL, createAgentImageAttachments, serializeQueuedAttachments, getSubmittedPromptBodyStrings, measureJsonStringBytes, getAttachmentBodyStrings, } from "./chat/attachment-adapters.js";
|
|
24
24
|
import { readAssistantChatComposerDraft, writeAssistantChatComposerDraft, } from "./chat/composer-draft.js";
|
|
25
25
|
import { AgentTextStreamingProvider, ExternalTextStreamingContext, } from "./chat/markdown-renderer.js";
|
|
26
|
-
import { AssistantChatHistoryContext, assistantMessageHasCompletedSideEffect, findMatchingAssistantChatHistoryVersion, isAssistantChatHistoryVersion, CheckpointContext, MessageActionsContext, assistantMessageRunId, assistantMessageTurnId, UserMessage, AssistantMessage, ExternalUserStoppedRunContext, SelectionAttachedPill, displayableUserMessageText, isHiddenUserMessage, ServerRunActiveContext, UserStoppedRunContext, } from "./chat/message-components.js";
|
|
26
|
+
import { AssistantChatHistoryContext, AssistantChatHistoryBeginningRevertButton, assistantMessageHasCompletedSideEffect, findAssistantChatHistoryBeginningVersion, findMatchingAssistantChatHistoryVersion, isAssistantChatHistoryVersion, CheckpointContext, MessageActionsContext, assistantMessageRunId, assistantMessageTurnId, UserMessage, AssistantMessage, ExternalUserStoppedRunContext, SelectionAttachedPill, displayableUserMessageText, isHiddenUserMessage, ServerRunActiveContext, UserStoppedRunContext, } from "./chat/message-components.js";
|
|
27
27
|
import { repoHasAssistantMessage, getRepoMessages, getRepoMessage, shouldImportServerThreadData, dedupeRepoMessagesById, dropEmptyAssistantMessages, withLastAssistantRunDuration, } from "./chat/repo-helpers.js";
|
|
28
28
|
import { BuilderSetupCard, LoopLimitContinueCard, RunErrorRecoveryCard, PlanModeCallout, getLoopLimitMetadata, getRunErrorMetadata, getRequestModeMetadata, isBuilderReconnectRunError, runErrorKey, } from "./chat/run-recovery.js";
|
|
29
29
|
import { createAgentChatRuntimeAdapter, } from "./chat/runtime.js";
|
|
@@ -1321,6 +1321,7 @@ export function shouldShowAssistantChatSuggestions(visibility, hasAssistantMessa
|
|
|
1321
1321
|
return visibility === "always" || hasAssistantMessage;
|
|
1322
1322
|
}
|
|
1323
1323
|
export async function restoreAssistantChatHistoryVersion(options) {
|
|
1324
|
+
await options.history.restore.beforeRestore?.();
|
|
1324
1325
|
const args = await options.history.restore.args(options.version);
|
|
1325
1326
|
const restored = await options.restore(args);
|
|
1326
1327
|
let applicationFailed = false;
|
|
@@ -1976,7 +1977,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1976
1977
|
isRunningRef.current = isRunning;
|
|
1977
1978
|
const submissionInFlightRef = useRef(0);
|
|
1978
1979
|
const submissionTailRef = useRef(Promise.resolve());
|
|
1979
|
-
const chatHistoryListQuery = useActionQuery((chatHistory?.list.action ?? "list-resource-versions"), chatHistory?.list.args
|
|
1980
|
+
const chatHistoryListQuery = useActionQuery((chatHistory?.list.action ?? "list-resource-versions"), (typeof chatHistory?.list.args === "function"
|
|
1981
|
+
? chatHistory.list.args(threadId)
|
|
1982
|
+
: chatHistory?.list.args), { enabled: chatHistory !== undefined });
|
|
1980
1983
|
const chatHistoryVersions = useMemo(() => {
|
|
1981
1984
|
if (!chatHistory || chatHistoryListQuery.data == null)
|
|
1982
1985
|
return [];
|
|
@@ -1990,24 +1993,59 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1990
1993
|
const refetchChatHistory = chatHistoryListQuery.refetch;
|
|
1991
1994
|
const restoreHistory = chatHistoryRestoreMutation.mutateAsync;
|
|
1992
1995
|
const createHistoryVersion = chatHistoryCreateMutation.mutateAsync;
|
|
1996
|
+
const [isChatHistoryRestoring, setIsChatHistoryRestoring] = useState(false);
|
|
1997
|
+
const chatHistoryRestoreInFlightRef = useRef(false);
|
|
1998
|
+
const chatHistoryRestoreWaitRef = useRef(null);
|
|
1999
|
+
const waitForChatHistoryRestore = useCallback(async () => {
|
|
2000
|
+
let restoreWait = chatHistoryRestoreWaitRef.current;
|
|
2001
|
+
while (restoreWait) {
|
|
2002
|
+
await restoreWait;
|
|
2003
|
+
restoreWait = chatHistoryRestoreWaitRef.current;
|
|
2004
|
+
}
|
|
2005
|
+
}, []);
|
|
1993
2006
|
const restoreChatHistoryVersion = useCallback(async (version) => {
|
|
1994
2007
|
if (!chatHistory)
|
|
1995
2008
|
return;
|
|
1996
|
-
|
|
1997
|
-
history
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
},
|
|
2006
|
-
}),
|
|
2009
|
+
if (chatHistoryRestoreInFlightRef.current) {
|
|
2010
|
+
throw new Error("A chat history restore is already in progress.");
|
|
2011
|
+
}
|
|
2012
|
+
if (submissionInFlightRef.current > 0) {
|
|
2013
|
+
throw new Error("A chat submission is already in progress.");
|
|
2014
|
+
}
|
|
2015
|
+
let finishRestore;
|
|
2016
|
+
const restoreWait = new Promise((resolve) => {
|
|
2017
|
+
finishRestore = resolve;
|
|
2007
2018
|
});
|
|
2019
|
+
chatHistoryRestoreWaitRef.current = restoreWait;
|
|
2020
|
+
chatHistoryRestoreInFlightRef.current = true;
|
|
2021
|
+
setIsChatHistoryRestoring(true);
|
|
2022
|
+
try {
|
|
2023
|
+
await restoreAssistantChatHistoryVersion({
|
|
2024
|
+
history: chatHistory,
|
|
2025
|
+
version,
|
|
2026
|
+
restore: restoreHistory,
|
|
2027
|
+
refetch: refetchChatHistory,
|
|
2028
|
+
onRefetchError: (error) => captureError(error, {
|
|
2029
|
+
tags: {
|
|
2030
|
+
source: "agent-chat-client",
|
|
2031
|
+
phase: "chat-history-refetch-after-restore",
|
|
2032
|
+
},
|
|
2033
|
+
}),
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
finally {
|
|
2037
|
+
chatHistoryRestoreInFlightRef.current = false;
|
|
2038
|
+
setIsChatHistoryRestoring(false);
|
|
2039
|
+
if (chatHistoryRestoreWaitRef.current === restoreWait) {
|
|
2040
|
+
chatHistoryRestoreWaitRef.current = null;
|
|
2041
|
+
}
|
|
2042
|
+
finishRestore();
|
|
2043
|
+
}
|
|
2008
2044
|
}, [chatHistory, refetchChatHistory, restoreHistory]);
|
|
2009
2045
|
const chatHistoryContext = useMemo(() => chatHistory
|
|
2010
2046
|
? {
|
|
2047
|
+
beginningVersion: findAssistantChatHistoryBeginningVersion(chatHistoryVersions, threadId, chatHistory.isEditable),
|
|
2048
|
+
isRestoring: isChatHistoryRestoring,
|
|
2011
2049
|
findVersion: (message) => findMatchingAssistantChatHistoryVersion(chatHistoryVersions, message, {
|
|
2012
2050
|
isEditable: chatHistory.isEditable,
|
|
2013
2051
|
scope: chatHistory.scope ?? contextScope ?? undefined,
|
|
@@ -2015,7 +2053,13 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2015
2053
|
}),
|
|
2016
2054
|
restoreVersion: restoreChatHistoryVersion,
|
|
2017
2055
|
}
|
|
2018
|
-
: null, [
|
|
2056
|
+
: null, [
|
|
2057
|
+
chatHistory,
|
|
2058
|
+
chatHistoryVersions,
|
|
2059
|
+
isChatHistoryRestoring,
|
|
2060
|
+
restoreChatHistoryVersion,
|
|
2061
|
+
threadId,
|
|
2062
|
+
]);
|
|
2019
2063
|
const chatHistoryRunObservedRef = useRef(false);
|
|
2020
2064
|
const chatHistoryCreateKeyRef = useRef(null);
|
|
2021
2065
|
useEffect(() => {
|
|
@@ -2035,6 +2079,12 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2035
2079
|
.find((message) => message.role === "assistant");
|
|
2036
2080
|
if (!latestAssistantMessage ||
|
|
2037
2081
|
!assistantMessageHasCompletedSideEffect(latestAssistantMessage)) {
|
|
2082
|
+
void refetchChatHistory().catch((error) => captureError(error, {
|
|
2083
|
+
tags: {
|
|
2084
|
+
source: "agent-chat-client",
|
|
2085
|
+
phase: "chat-history-refetch-after-run",
|
|
2086
|
+
},
|
|
2087
|
+
}));
|
|
2038
2088
|
return;
|
|
2039
2089
|
}
|
|
2040
2090
|
const historyMessage = {
|
|
@@ -3736,6 +3786,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3736
3786
|
// Keep prompts visible while setup is missing. Dequeuing them would remove
|
|
3737
3787
|
// the only user-visible copy and immediately re-enter the provider failure.
|
|
3738
3788
|
if (isRestoring ||
|
|
3789
|
+
isChatHistoryRestoring ||
|
|
3739
3790
|
engineSetupRequired ||
|
|
3740
3791
|
isRunning ||
|
|
3741
3792
|
queuedMessages.length === 0) {
|
|
@@ -3762,7 +3813,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3762
3813
|
// complete. Starting the queued turn during that window can reconnect
|
|
3763
3814
|
// to the old run and replay the old answer under the new prompt.
|
|
3764
3815
|
const runCleared = await waitForThreadRunToClear(apiUrl, threadId);
|
|
3765
|
-
if (cancelled)
|
|
3816
|
+
if (cancelled || chatHistoryRestoreInFlightRef.current)
|
|
3766
3817
|
return;
|
|
3767
3818
|
if (!runCleared) {
|
|
3768
3819
|
// The server still owns this turn (including a deferred durable
|
|
@@ -3869,6 +3920,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3869
3920
|
apiUrl,
|
|
3870
3921
|
appendThreadMessage,
|
|
3871
3922
|
applyLocalQueuedMessages,
|
|
3923
|
+
isChatHistoryRestoring,
|
|
3872
3924
|
isRestoring,
|
|
3873
3925
|
isRunning,
|
|
3874
3926
|
engineSetupRequired,
|
|
@@ -4201,7 +4253,8 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4201
4253
|
}, [onStop, stopActiveRun]);
|
|
4202
4254
|
// The composer stop button uses the handler above; queued send-now keeps the
|
|
4203
4255
|
// active run alive and only promotes the selected message for later dequeue.
|
|
4204
|
-
const sendQueuedMessageNow = useCallback((id) => {
|
|
4256
|
+
const sendQueuedMessageNow = useCallback(async (id) => {
|
|
4257
|
+
await waitForChatHistoryRestore();
|
|
4205
4258
|
const message = queuedMessagesRef.current.find((candidate) => candidate.id === id);
|
|
4206
4259
|
if (!message || message.promoted)
|
|
4207
4260
|
return;
|
|
@@ -4235,9 +4288,18 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4235
4288
|
return;
|
|
4236
4289
|
}
|
|
4237
4290
|
applyLocalQueuedMessages((prev) => promoteQueuedMessage(prev, id));
|
|
4238
|
-
}, [
|
|
4291
|
+
}, [
|
|
4292
|
+
appendThreadMessage,
|
|
4293
|
+
applyLocalQueuedMessages,
|
|
4294
|
+
threadId,
|
|
4295
|
+
threadRuntime,
|
|
4296
|
+
waitForChatHistoryRestore,
|
|
4297
|
+
]);
|
|
4239
4298
|
const visibleQueuedMessages = useMemo(() => queuedMessages.filter((message) => !message.hideUserMessage && !message.promoted), [queuedMessages]);
|
|
4240
4299
|
const addToQueue = useCallback(async (text, images, references, attachments, requestMode, intent = "queued", recoveryAction, includeComposerContext = false, trackInRunsTray = false, preserveReconnectAutoRecoveryBudget = false, hideUserMessage = false, submitMessageId, approvedToolCalls, continuationTurnId, usageLabel, actionScope) => {
|
|
4300
|
+
if (isAgentChatSubmitCancelled(submitMessageId))
|
|
4301
|
+
return false;
|
|
4302
|
+
await waitForChatHistoryRestore();
|
|
4241
4303
|
if (isAgentChatSubmitCancelled(submitMessageId))
|
|
4242
4304
|
return false;
|
|
4243
4305
|
const wasSubmissionInFlight = submissionInFlightRef.current > 0;
|
|
@@ -4448,6 +4510,16 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4448
4510
|
]);
|
|
4449
4511
|
}
|
|
4450
4512
|
else {
|
|
4513
|
+
try {
|
|
4514
|
+
await chatHistory?.beforeStart?.();
|
|
4515
|
+
}
|
|
4516
|
+
catch (error) {
|
|
4517
|
+
setComposerError(String(error));
|
|
4518
|
+
reportAgentChatSubmitResult(submitMessageId, false, "editor-save-failed");
|
|
4519
|
+
return false;
|
|
4520
|
+
}
|
|
4521
|
+
if (isAgentChatSubmitCancelled(submitMessageId))
|
|
4522
|
+
return false;
|
|
4451
4523
|
markOptimisticRunning();
|
|
4452
4524
|
try {
|
|
4453
4525
|
appendThreadMessage({
|
|
@@ -4506,6 +4578,8 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4506
4578
|
t,
|
|
4507
4579
|
threadId,
|
|
4508
4580
|
updateComposerContextItems,
|
|
4581
|
+
waitForChatHistoryRestore,
|
|
4582
|
+
chatHistory,
|
|
4509
4583
|
]);
|
|
4510
4584
|
const mcpResumeTimerRef = useRef(null);
|
|
4511
4585
|
const scheduleMcpConnectionResume = useCallback((request) => {
|
|
@@ -4974,19 +5048,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4974
5048
|
if (engineSetupRequired)
|
|
4975
5049
|
return;
|
|
4976
5050
|
void addToQueue(suggestion);
|
|
4977
|
-
}, className: "agent-empty-suggestion w-full rounded-xl border border-border/70 bg-card/60 px-3 py-2.5 text-left text-[13px] text-muted-foreground shadow-sm transition-[border-color,background-color,color,transform] hover:-translate-y-px hover:border-border hover:bg-card hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: suggestion }, suggestion))) })) : null, showInlineEmptyThreadFooterSlot ? (_jsx("div", { className: "agent-thread-footer-slot agent-thread-footer-slot--empty", children: resolvedThreadFooterSlot })) : null, emptyStateFooter ? (_jsx("div", { className: "agent-empty-state-footer", children: emptyStateFooter })) : null] })) : (_jsxs(MessageScrollerContent, { className: "agent-thread-content gap-4 px-4 py-4", children: [resolvedThreadContentSlot ? (_jsx(MessageScrollerItem, { children: _jsx("div", { className: "agent-thread-content-slot", children: resolvedThreadContentSlot }) })) : null, threadRestoreErrorSurface ? (_jsx(MessageScrollerItem, { children: threadRestoreErrorSurface })) : null, _jsx(AssistantMessageListErrorBoundary, { resetKey: messageListResetKey, children: _jsx(UserStoppedRunContext.Provider, { value: wasUserStoppedRun, children: _jsx(ExternalUserStoppedRunContext.Provider, { value: externalUserStopped, children: _jsx(ExternalTextStreamingContext.Provider, { value: externalStreaming, children:
|
|
4978
|
-
// Deliberately NOT keyed on part structure. Doing that
|
|
4979
|
-
// remounted the whole transcript every time a tool call
|
|
4980
|
-
// started or a placeholder id was rewritten — a flash and a
|
|
4981
|
-
// lost scroll position in the middle of an answer. The
|
|
4982
|
-
// error boundary above is the mechanism for assistant-ui's
|
|
4983
|
-
// stale tap-resource errors: it catches them, clears, and
|
|
4984
|
-
// retries, and its retry signature includes the reset key so
|
|
4985
|
-
// a genuinely new structure always gets a fresh budget.
|
|
4986
|
-
// `assistant-ui-part-churn.spec.tsx` drives append, mutate,
|
|
4987
|
-
// rename and splice through both the import and streaming
|
|
4988
|
-
// paths and records that no such error occurs.
|
|
4989
|
-
, {
|
|
5051
|
+
}, className: "agent-empty-suggestion w-full rounded-xl border border-border/70 bg-card/60 px-3 py-2.5 text-left text-[13px] text-muted-foreground shadow-sm transition-[border-color,background-color,color,transform] hover:-translate-y-px hover:border-border hover:bg-card hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: suggestion }, suggestion))) })) : null, showInlineEmptyThreadFooterSlot ? (_jsx("div", { className: "agent-thread-footer-slot agent-thread-footer-slot--empty", children: resolvedThreadFooterSlot })) : null, emptyStateFooter ? (_jsx("div", { className: "agent-empty-state-footer", children: emptyStateFooter })) : null] })) : (_jsxs(MessageScrollerContent, { className: "agent-thread-content gap-4 px-4 py-4", children: [resolvedThreadContentSlot ? (_jsx(MessageScrollerItem, { children: _jsx("div", { className: "agent-thread-content-slot", children: resolvedThreadContentSlot }) })) : null, threadRestoreErrorSurface ? (_jsx(MessageScrollerItem, { children: threadRestoreErrorSurface })) : null, _jsx(AssistantMessageListErrorBoundary, { resetKey: messageListResetKey, children: _jsx(UserStoppedRunContext.Provider, { value: wasUserStoppedRun, children: _jsx(ExternalUserStoppedRunContext.Provider, { value: externalUserStopped, children: _jsx(ExternalTextStreamingContext.Provider, { value: externalStreaming, children: _jsxs(AssistantChatHistoryContext.Provider, { value: chatHistoryContext, children: [chatHistoryContext?.beginningVersion ? (_jsx(MessageScrollerItem, { children: _jsx(AssistantChatHistoryBeginningRevertButton, {}) })) : null, _jsx(ThreadPrimitive.Messages
|
|
4990
5052
|
// Deliberately NOT keyed on part structure. Doing that
|
|
4991
5053
|
// remounted the whole transcript every time a tool call
|
|
4992
5054
|
// started or a placeholder id was rewritten — a flash and a
|
|
@@ -4998,10 +5060,22 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4998
5060
|
// `assistant-ui-part-churn.spec.tsx` drives append, mutate,
|
|
4999
5061
|
// rename and splice through both the import and streaming
|
|
5000
5062
|
// paths and records that no such error occurs.
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5063
|
+
, {
|
|
5064
|
+
// Deliberately NOT keyed on part structure. Doing that
|
|
5065
|
+
// remounted the whole transcript every time a tool call
|
|
5066
|
+
// started or a placeholder id was rewritten — a flash and a
|
|
5067
|
+
// lost scroll position in the middle of an answer. The
|
|
5068
|
+
// error boundary above is the mechanism for assistant-ui's
|
|
5069
|
+
// stale tap-resource errors: it catches them, clears, and
|
|
5070
|
+
// retries, and its retry signature includes the reset key so
|
|
5071
|
+
// a genuinely new structure always gets a fresh budget.
|
|
5072
|
+
// `assistant-ui-part-churn.spec.tsx` drives append, mutate,
|
|
5073
|
+
// rename and splice through both the import and streaming
|
|
5074
|
+
// paths and records that no such error occurs.
|
|
5075
|
+
components: {
|
|
5076
|
+
UserMessage: AssistantChatUserMessageItem,
|
|
5077
|
+
AssistantMessage: AssistantChatAssistantMessageItem,
|
|
5078
|
+
} })] }) }) }) }) }), visibleLoopLimit && !showRunningInUI && (_jsx(MessageScrollerItem, { children: _jsx(LoopLimitContinueCard, { info: visibleLoopLimit, onContinue: () => {
|
|
5005
5079
|
setShowContinue(false);
|
|
5006
5080
|
setLoopLimitInfo(null);
|
|
5007
5081
|
const continuation = latestProtocolContinuationContext(messagesRef.current);
|
|
@@ -5089,7 +5163,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
5089
5163
|
? bounceMissingKeySetup
|
|
5090
5164
|
: undefined, children: _jsxs(_Fragment, { children: [_jsx(ComposerAttachmentPreviewStrip, {}), _jsx(TiptapComposer, { focusRef: tiptapRef, maxDocumentAttachmentBytes: MAX_PDF_BYTES, initialText: initialComposerText ?? undefined, initialTextKey: composerDraftScope, onTextChange: isActiveComposer
|
|
5091
5165
|
? handleComposerTextChange
|
|
5092
|
-
: undefined, disabled: isComposerDisabled ||
|
|
5166
|
+
: undefined, disabled: isComposerDisabled ||
|
|
5167
|
+
showMissingKeySetup ||
|
|
5168
|
+
isChatHistoryRestoring, placeholder: showMissingKeySetup
|
|
5093
5169
|
? t("agentChat.setup.connectPlaceholder")
|
|
5094
5170
|
: engineSetupRequired
|
|
5095
5171
|
? t("agentChat.setup.connectPlaceholder")
|
|
@@ -5119,6 +5195,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
5119
5195
|
}
|
|
5120
5196
|
}, willQueue: engineSetupRequired ||
|
|
5121
5197
|
isRunning ||
|
|
5198
|
+
isChatHistoryRestoring ||
|
|
5122
5199
|
submissionInFlightRef.current > 0, onSlashCommand: onSlashCommand, execMode: execMode, onExecModeChange: onExecModeChange, planModeDisabled: planModeDisabled, planModeDisabledReason: planModeDisabledReason, selectedModel: selectedModel ?? defaultModel, selectedEffort: selectedEffort, availableModels: availableModels, availableAgents: availableAgents, selectedAgent: selectedAgent, hostedHarness: hostedHarness, modelListLoading: modelListLoading, onModelChange: shouldShowAssistantChatModelSelector(showModelSelector)
|
|
5123
5200
|
? onModelChange
|
|
5124
5201
|
: undefined, onEffortChange: onEffortChange, onAgentChange: onAgentChange, imageModelMenu: imageModelMenu, onConnectProvider: onConnectProvider, onConnectLocalRuntime: onConnectLocalRuntime, toolbarSlot: composerToolbarSlot, contextItems: visibleComposerContextItems, onRemoveContextItem: removeComposerContextItem, plusMenuMode: plusMenuMode, layoutVariant: composerLayoutVariant, providerConnectStatusEnabled: providerStatusChecksEnabled, voiceEnabled: true, draftScope: composerDraftScope, interceptBuildRequestsForBuilder: true, onAttachmentError: setComposerError, extraActionButton: composerExtraActionButton, stopButton: showRunningInUI ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: handleComposerStop, "aria-label": t("agentChat.composer.stopResponse"), "data-agent-composer-slot": "stop-button", className: "shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: _jsx(IconPlayerStopFilled, { className: "h-3 w-3" }) }) }), _jsx(TooltipContent, { children: t("agentChat.composer.stopResponse") })] })) : undefined })] }) }) })] })] }) }) }) }) }) }) }) }) }) }) }));
|
|
@@ -57,14 +57,17 @@ export interface AssistantChatHistoryMessage {
|
|
|
57
57
|
hasCompletedSideEffect: boolean;
|
|
58
58
|
}
|
|
59
59
|
export interface AssistantChatHistoryConfig<TListResult = unknown, TVersion extends AssistantChatHistoryVersion = AssistantChatHistoryVersion, TRestoreResult = unknown> {
|
|
60
|
+
/** Flush host editor writes before an agent turn starts. */
|
|
61
|
+
beforeStart?: () => void | Promise<void>;
|
|
60
62
|
list: {
|
|
61
63
|
action: string;
|
|
62
|
-
args?: Record<string, unknown
|
|
64
|
+
args?: Record<string, unknown> | ((threadId?: string) => Record<string, unknown>);
|
|
63
65
|
getVersions: (result: TListResult) => readonly TVersion[];
|
|
64
66
|
};
|
|
65
67
|
restore: {
|
|
66
68
|
action: string;
|
|
67
69
|
args: (version: TVersion) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
70
|
+
beforeRestore?: () => void | Promise<void>;
|
|
68
71
|
onRestored?: (result: TRestoreResult, version: TVersion) => void | Promise<void>;
|
|
69
72
|
};
|
|
70
73
|
createVersion?: {
|
|
@@ -76,6 +79,8 @@ export interface AssistantChatHistoryConfig<TListResult = unknown, TVersion exte
|
|
|
76
79
|
matchVersion?: (version: TVersion, message: AssistantChatHistoryMessage) => boolean;
|
|
77
80
|
}
|
|
78
81
|
export interface AssistantChatHistoryContextValue {
|
|
82
|
+
beginningVersion: AssistantChatHistoryVersion | null;
|
|
83
|
+
isRestoring: boolean;
|
|
79
84
|
findVersion: (message: AssistantChatHistoryMessage) => AssistantChatHistoryVersion | null;
|
|
80
85
|
restoreVersion: (version: AssistantChatHistoryVersion) => Promise<void>;
|
|
81
86
|
}
|
|
@@ -93,6 +98,7 @@ export declare const MessageActionsContext: React.Context<{
|
|
|
93
98
|
export declare function isLocalDevelopmentHost(hostname: string): boolean;
|
|
94
99
|
export declare function assistantMessageHasCompletedSideEffect(message: unknown): boolean;
|
|
95
100
|
export declare function findMatchingAssistantChatHistoryVersion<TVersion extends AssistantChatHistoryVersion>(versions: readonly TVersion[], message: AssistantChatHistoryMessage, options?: Pick<AssistantChatHistoryConfig<unknown, TVersion>, "isEditable" | "matchVersion" | "scope">): TVersion | null;
|
|
101
|
+
export declare function findAssistantChatHistoryBeginningVersion<TVersion extends AssistantChatHistoryVersion>(versions: readonly TVersion[], threadId?: string, isEditable?: (version: TVersion) => boolean): TVersion | null;
|
|
96
102
|
/**
|
|
97
103
|
* Restore rewrites the working tree, so only offer it when the server actually
|
|
98
104
|
* has a checkpoint for this turn. Auto-checkpointing skips turns that started
|
|
@@ -129,6 +135,7 @@ export declare function MessageActionsMenu({ showRevert, onRevert, threadId, }?:
|
|
|
129
135
|
onRevert?: () => void;
|
|
130
136
|
threadId?: string;
|
|
131
137
|
}): React.JSX.Element;
|
|
138
|
+
export declare function AssistantChatHistoryBeginningRevertButton(): React.JSX.Element | null;
|
|
132
139
|
export declare function UserMessage(): React.JSX.Element | null;
|
|
133
140
|
export declare function messageTextFromContent(content: unknown): string;
|
|
134
141
|
export declare function isMissingFinalResponseWarningText(text: string): boolean;
|
|
@@ -221,7 +221,7 @@ export function findMatchingAssistantChatHistoryVersion(versions, message, optio
|
|
|
221
221
|
return null;
|
|
222
222
|
}
|
|
223
223
|
let match = null;
|
|
224
|
-
let matchTime = Number.
|
|
224
|
+
let matchTime = Number.NEGATIVE_INFINITY;
|
|
225
225
|
for (const version of versions) {
|
|
226
226
|
if (!isAssistantChatHistoryVersion(version))
|
|
227
227
|
continue;
|
|
@@ -229,6 +229,8 @@ export function findMatchingAssistantChatHistoryVersion(versions, message, optio
|
|
|
229
229
|
continue;
|
|
230
230
|
}
|
|
231
231
|
const chatContext = version.chatContext;
|
|
232
|
+
if (chatContext?.phase === "start")
|
|
233
|
+
continue;
|
|
232
234
|
const matchesChatTurn = Boolean(chatContext &&
|
|
233
235
|
((message.turnId && chatContext.turnId
|
|
234
236
|
? chatContext.turnId === message.turnId
|
|
@@ -244,13 +246,34 @@ export function findMatchingAssistantChatHistoryVersion(versions, message, optio
|
|
|
244
246
|
const matches = options.matchVersion
|
|
245
247
|
? options.matchVersion(version, message)
|
|
246
248
|
: true;
|
|
247
|
-
if (!matches || versionTime
|
|
249
|
+
if (!matches || versionTime <= matchTime)
|
|
248
250
|
continue;
|
|
249
251
|
match = version;
|
|
250
252
|
matchTime = versionTime;
|
|
251
253
|
}
|
|
252
254
|
return match;
|
|
253
255
|
}
|
|
256
|
+
export function findAssistantChatHistoryBeginningVersion(versions, threadId, isEditable) {
|
|
257
|
+
if (!threadId)
|
|
258
|
+
return null;
|
|
259
|
+
let beginning = null;
|
|
260
|
+
let beginningTime = Number.POSITIVE_INFINITY;
|
|
261
|
+
for (const version of versions) {
|
|
262
|
+
if (!isAssistantChatHistoryVersion(version) ||
|
|
263
|
+
version.editable === false ||
|
|
264
|
+
isEditable?.(version) === false ||
|
|
265
|
+
version.chatContext?.threadId !== threadId ||
|
|
266
|
+
version.chatContext.phase !== "start") {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const versionTime = coerceMessageDate(version.createdAt)?.getTime();
|
|
270
|
+
if (versionTime == null || versionTime >= beginningTime)
|
|
271
|
+
continue;
|
|
272
|
+
beginning = version;
|
|
273
|
+
beginningTime = versionTime;
|
|
274
|
+
}
|
|
275
|
+
return beginning;
|
|
276
|
+
}
|
|
254
277
|
/**
|
|
255
278
|
* Restore rewrites the working tree, so only offer it when the server actually
|
|
256
279
|
* has a checkpoint for this turn. Auto-checkpointing skips turns that started
|
|
@@ -486,13 +509,16 @@ export function MessageActionsMenu({ showRevert, onRevert, threadId = "", } = {}
|
|
|
486
509
|
? t("agentChat.recovery.copyFailed")
|
|
487
510
|
: t("agentChat.message.copyRequestId")] }), showRevert && (_jsxs(DropdownMenuItem, { onSelect: handleRevert, children: [_jsx(IconArrowBackUp, { className: "h-3.5 w-3.5" }), t("agentChat.message.revertHere")] })), timestamp && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { className: "px-2 py-1 text-[11px] font-normal text-muted-foreground", children: t("agentChat.message.sentAt", { time: timestamp.short }) })] }))] })] }));
|
|
488
511
|
}
|
|
489
|
-
function AssistantChatHistoryRevertButton({ onRestore, onRestored, }) {
|
|
512
|
+
function AssistantChatHistoryRevertButton({ onRestore, onRestored, label, persistent = false, }) {
|
|
490
513
|
const t = useT();
|
|
514
|
+
const chatRunning = React.useContext(ChatRunningContext);
|
|
515
|
+
const history = React.useContext(AssistantChatHistoryContext);
|
|
516
|
+
const restoreInProgress = chatRunning || Boolean(history?.isRestoring);
|
|
491
517
|
const [open, setOpen] = useState(false);
|
|
492
518
|
const [state, setState] = useState("confirming");
|
|
493
519
|
const [error, setError] = useState(null);
|
|
494
520
|
const handleOpenChange = useCallback((nextOpen) => {
|
|
495
|
-
if (
|
|
521
|
+
if (nextOpen && restoreInProgress)
|
|
496
522
|
return;
|
|
497
523
|
setOpen(nextOpen);
|
|
498
524
|
if (nextOpen) {
|
|
@@ -502,14 +528,20 @@ function AssistantChatHistoryRevertButton({ onRestore, onRestored, }) {
|
|
|
502
528
|
else {
|
|
503
529
|
setError(null);
|
|
504
530
|
}
|
|
505
|
-
}, [
|
|
531
|
+
}, [restoreInProgress]);
|
|
532
|
+
useEffect(() => {
|
|
533
|
+
if (restoreInProgress)
|
|
534
|
+
setOpen(false);
|
|
535
|
+
}, [restoreInProgress]);
|
|
506
536
|
const handleRestore = useCallback(async () => {
|
|
537
|
+
if (restoreInProgress)
|
|
538
|
+
return;
|
|
507
539
|
setState("restoring");
|
|
508
540
|
setError(null);
|
|
509
541
|
try {
|
|
510
542
|
await onRestore();
|
|
511
543
|
setOpen(false);
|
|
512
|
-
onRestored();
|
|
544
|
+
onRestored?.();
|
|
513
545
|
}
|
|
514
546
|
catch (restoreError) {
|
|
515
547
|
const status = restoreError?.status;
|
|
@@ -520,8 +552,17 @@ function AssistantChatHistoryRevertButton({ onRestore, onRestored, }) {
|
|
|
520
552
|
: t("agentChat.message.restoreRequestFailed")));
|
|
521
553
|
setState("error");
|
|
522
554
|
}
|
|
523
|
-
}, [onRestore, onRestored, t]);
|
|
524
|
-
return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsx(TooltipProvider, { delayDuration: 400, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("agentChat.message.revertHere"), className: cn("flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground/70 transition-colors duration-150 hover:bg-accent hover:text-foreground", messageFooterFadeClassName, open && "bg-accent text-foreground"), children: _jsx(IconArrowBackUp, { className: "h-3.5 w-3.5" }) }) }) }), _jsx(TooltipContent, { side: "top", className: "text-xs", children: t("agentChat.message.revertHere") })] }) }), _jsx(PopoverContent, { side: "top", align: "start", sideOffset: 6, className: "w-56 rounded-lg border-border p-3 shadow-xl", children: state === "confirming" ? (_jsxs("div", { className: "grid gap-2", children: [_jsx("p", { className: "text-xs font-medium text-foreground", children: t("agentChat.message.
|
|
555
|
+
}, [onRestore, onRestored, restoreInProgress, t]);
|
|
556
|
+
return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsx(TooltipProvider, { delayDuration: 400, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": label ?? t("agentChat.message.revertHere"), disabled: restoreInProgress, className: cn("flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground/70 transition-colors duration-150 hover:bg-accent hover:text-foreground", !persistent && messageFooterFadeClassName, open && "bg-accent text-foreground"), children: _jsx(IconArrowBackUp, { className: "h-3.5 w-3.5" }) }) }) }), _jsx(TooltipContent, { side: "top", className: "text-xs", children: label ?? t("agentChat.message.revertHere") })] }) }), _jsx(PopoverContent, { side: "top", align: "start", sideOffset: 6, className: "w-56 rounded-lg border-border p-3 shadow-xl", children: state === "confirming" ? (_jsxs("div", { className: "grid gap-2", children: [_jsx("p", { className: "text-xs font-medium text-foreground", children: t("agentChat.message.revertQuestion") }), _jsxs("div", { className: "flex justify-end gap-1.5", children: [_jsx("button", { type: "button", onClick: () => setOpen(false), className: "rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground", children: t("agentChat.common.cancel") }), _jsx("button", { type: "button", disabled: restoreInProgress, onClick: () => void handleRestore(), className: "rounded-md bg-destructive px-2 py-1 text-xs font-medium text-destructive-foreground hover:bg-destructive/90", children: label ?? t("agentChat.message.revertHere") })] })] })) : state === "restoring" ? (_jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(IconLoader2, { className: "h-3 w-3 animate-spin" }), t("agentChat.message.restoring")] })) : (_jsxs("div", { className: "grid gap-2", children: [_jsx("p", { className: "text-xs text-destructive", children: error ?? t("agentChat.message.restoreRequestFailed") }), _jsx("button", { type: "button", onClick: () => setOpen(false), className: "justify-self-end rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground", children: t("agentChat.common.dismiss") })] })) })] }));
|
|
557
|
+
}
|
|
558
|
+
export function AssistantChatHistoryBeginningRevertButton() {
|
|
559
|
+
const t = useT();
|
|
560
|
+
const history = React.useContext(AssistantChatHistoryContext);
|
|
561
|
+
const chatRunning = React.useContext(ChatRunningContext);
|
|
562
|
+
const version = history?.beginningVersion;
|
|
563
|
+
if (!history || !version || chatRunning)
|
|
564
|
+
return null;
|
|
565
|
+
return (_jsx("div", { className: "flex justify-end", children: _jsx(AssistantChatHistoryRevertButton, { label: t("agentChat.message.revertToBeginning"), onRestore: () => history.restoreVersion(version), persistent: true }) }));
|
|
525
566
|
}
|
|
526
567
|
// ─── UserMessage ──────────────────────────────────────────────────────────────
|
|
527
568
|
export function UserMessage() {
|
|
@@ -1213,12 +1254,12 @@ export function AssistantMessage() {
|
|
|
1213
1254
|
});
|
|
1214
1255
|
const historyContext = React.useContext(AssistantChatHistoryContext);
|
|
1215
1256
|
const historyVersion = React.useMemo(() => historyContext?.findVersion(historyMessage) ?? null, [historyContext, historyMessage]);
|
|
1216
|
-
const showHistoryRevert = isComplete && !historyReverted && historyVersion !== null;
|
|
1257
|
+
const showHistoryRevert = !chatRunning && isComplete && !historyReverted && historyVersion !== null;
|
|
1217
1258
|
const handleHistoryRestore = useCallback(async () => {
|
|
1218
|
-
if (!historyContext || !historyVersion)
|
|
1259
|
+
if (chatRunning || !historyContext || !historyVersion)
|
|
1219
1260
|
return;
|
|
1220
1261
|
await historyContext.restoreVersion(historyVersion);
|
|
1221
|
-
}, [historyContext, historyVersion]);
|
|
1262
|
+
}, [chatRunning, historyContext, historyVersion]);
|
|
1222
1263
|
const cpCtx = React.useContext(CheckpointContext);
|
|
1223
1264
|
useEffect(() => {
|
|
1224
1265
|
if (isLast && chatRunning) {
|
|
@@ -322,7 +322,7 @@ export const DEFAULT_MCP_INTEGRATIONS = [
|
|
|
322
322
|
connectionMode: "oauth",
|
|
323
323
|
availability: "provider-setup",
|
|
324
324
|
verification: "restricted",
|
|
325
|
-
logoUrl: mcpIntegrationLogo("
|
|
325
|
+
logoUrl: mcpIntegrationLogo("jira"),
|
|
326
326
|
docsUrl: "https://developer.atlassian.com/cloud/rovo-mcp/guides/getting-started/",
|
|
327
327
|
setupNoteKey: "mcpIntegrations.catalog.atlassian.setupNote",
|
|
328
328
|
brandAliases: ["Jira", "Confluence", "Rovo"],
|