@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.
Files changed (53) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/content/actions/list-document-versions.ts +102 -2
  3. package/corpus/templates/content/actions/restore-document-version.ts +50 -2
  4. package/corpus/templates/content/app/components/layout/Layout.tsx +6 -1
  5. package/corpus/templates/content/changelog/2026-09-25-chat-messages-can-be-reverted-to-their-autosaved-version-inc.md +6 -0
  6. package/corpus/templates/content/server/lib/document-history.ts +59 -8
  7. package/corpus/templates/content/server/lib/document-version-context.ts +5 -0
  8. package/corpus/templates/content/server/plugins/agent-chat.ts +164 -0
  9. package/corpus/templates/design/actions/list-design-versions.ts +3 -1
  10. package/corpus/templates/design/app/components/layout/Layout.tsx +17 -2
  11. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +10 -3
  12. package/corpus/templates/design/changelog/2026-09-25-chat-messages-can-be-reverted-to-their-autosaved-version-inc.md +6 -0
  13. package/corpus/templates/design/server/lib/design-versions.ts +136 -20
  14. package/corpus/templates/design/server/plugins/agent-chat.ts +18 -2
  15. package/corpus/templates/slides/actions/list-deck-versions.ts +62 -10
  16. package/corpus/templates/slides/app/components/layout/Layout.tsx +10 -2
  17. package/corpus/templates/slides/changelog/2026-09-25-chat-messages-can-be-reverted-to-their-autosaved-version-inc.md +6 -0
  18. package/corpus/templates/slides/server/lib/deck-versions.ts +36 -1
  19. package/corpus/templates/slides/server/plugins/agent-chat.ts +26 -2
  20. package/dist/automation/index.d.ts +1 -1
  21. package/dist/client/AssistantChat.js +112 -35
  22. package/dist/client/chat/assistant-chat-history-version.d.ts +1 -0
  23. package/dist/client/chat/message-components.d.ts +8 -1
  24. package/dist/client/chat/message-components.js +52 -11
  25. package/dist/client/resources/mcp-integration-catalog.js +1 -1
  26. package/dist/client/resources/mcp-integration-logos.js +29 -30
  27. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  28. package/dist/localization/core-messages/ar-SA.js +2 -0
  29. package/dist/localization/core-messages/de-DE.js +2 -0
  30. package/dist/localization/core-messages/en-US.d.ts +2 -0
  31. package/dist/localization/core-messages/en-US.js +2 -0
  32. package/dist/localization/core-messages/es-ES.js +2 -0
  33. package/dist/localization/core-messages/fr-FR.js +2 -0
  34. package/dist/localization/core-messages/hi-IN.js +2 -0
  35. package/dist/localization/core-messages/ja-JP.js +2 -0
  36. package/dist/localization/core-messages/ko-KR.js +2 -0
  37. package/dist/localization/core-messages/pt-BR.js +2 -0
  38. package/dist/localization/core-messages/zh-CN.js +2 -0
  39. package/dist/localization/core-messages/zh-TW.js +2 -0
  40. package/dist/localization/core-messages.d.ts +2 -0
  41. package/dist/notifications/routes.d.ts +3 -3
  42. package/dist/observability/routes.d.ts +4 -4
  43. package/dist/org/handlers.d.ts +1 -1
  44. package/dist/resources/handlers.d.ts +1 -1
  45. package/dist/secrets/routes.d.ts +6 -6
  46. package/dist/server/agent-chat/plugin-options.d.ts +2 -0
  47. package/dist/server/agent-chat-plugin.d.ts +1 -0
  48. package/dist/server/agent-chat-plugin.js +26 -0
  49. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  50. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  51. package/dist/server/onboarding-html.js +28 -5
  52. package/docs/content/deployment.mdx +12 -1
  53. 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, { enabled: chatHistory !== undefined });
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
- await restoreAssistantChatHistoryVersion({
1997
- history: chatHistory,
1998
- version,
1999
- restore: restoreHistory,
2000
- refetch: refetchChatHistory,
2001
- onRefetchError: (error) => captureError(error, {
2002
- tags: {
2003
- source: "agent-chat-client",
2004
- phase: "chat-history-refetch-after-restore",
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, [chatHistory, chatHistoryVersions, restoreChatHistoryVersion]);
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
- }, [appendThreadMessage, applyLocalQueuedMessages, threadId, threadRuntime]);
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: _jsx(AssistantChatHistoryContext.Provider, { value: chatHistoryContext, children: _jsx(ThreadPrimitive.Messages
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
- components: {
5002
- UserMessage: AssistantChatUserMessageItem,
5003
- AssistantMessage: AssistantChatAssistantMessageItem,
5004
- } }) }) }) }) }) }), visibleLoopLimit && !showRunningInUI && (_jsx(MessageScrollerItem, { children: _jsx(LoopLimitContinueCard, { info: visibleLoopLimit, onContinue: () => {
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 || showMissingKeySetup, placeholder: showMissingKeySetup
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 })] }) }) })] })] }) }) }) }) }) }) }) }) }) }) }));
@@ -7,6 +7,7 @@ export interface AssistantChatHistoryVersion {
7
7
  threadId?: string;
8
8
  runId?: string;
9
9
  turnId?: string;
10
+ phase?: "start" | "end";
10
11
  };
11
12
  }
12
13
  export declare function coerceAssistantChatHistoryDate(value: unknown): Date | null;
@@ -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.POSITIVE_INFINITY;
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 >= matchTime)
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 (!nextOpen && state === "restoring")
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
- }, [state]);
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.restoreQuestion") }), _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", onClick: () => void handleRestore(), className: "rounded-md bg-destructive px-2 py-1 text-xs font-medium text-destructive-foreground hover:bg-destructive/90", children: 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") })] })) })] }));
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("atlassian"),
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"],