| ... | @@ -39,7 +39,6 @@ import { feedbackBannerItem } from "./chat/ComposerFeedback"; | ... | @@ -39,7 +39,6 @@ import { feedbackBannerItem } from "./chat/ComposerFeedback"; |
| 39 | import { usageLimitsBannerItem } from "./chat/ComposerUsageLimits"; | 39 | import { usageLimitsBannerItem } from "./chat/ComposerUsageLimits"; |
| 40 | import { getTerminalLabel } from "@t3tools/shared/terminalLabels"; | 40 | import { getTerminalLabel } from "@t3tools/shared/terminalLabels"; |
| 41 | import * as Schema from "effect/Schema"; | 41 | import * as Schema from "effect/Schema"; |
| 42 | import { Minimize2Icon } from "lucide-react"; | | |
| 43 | import { | 42 | import { |
| 44 | questionAttachmentDraftId, | 43 | questionAttachmentDraftId, |
| 45 | questionAttachmentDraftPrefix, | 44 | questionAttachmentDraftPrefix, |
| ... | @@ -486,6 +485,8 @@ import { | ... | @@ -486,6 +485,8 @@ import { |
| 486 | } from "./chat/QueuedRunsControl"; | 485 | } from "./chat/QueuedRunsControl"; |
| 487 | import { useLinkedThreadPullRequest } from "./ThreadStatusIndicators"; | 486 | import { useLinkedThreadPullRequest } from "./ThreadStatusIndicators"; |
| 488 | import type { ComposerBannerStackItem } from "./chat/ComposerBannerStack"; | 487 | import type { ComposerBannerStackItem } from "./chat/ComposerBannerStack"; |
| | 488 | import { ThreadStatusLine } from "./chat/ThreadStatusLine"; |
| | 489 | import { formatRelativeTimeLabel, formatRelativeTimeUntilLabel } from "../timestampFormat"; |
| 489 | import { ComposerSurface } from "./chat/ComposerSurface"; | 490 | import { ComposerSurface } from "./chat/ComposerSurface"; |
| 490 | import { resolveThreadSyncPhase } from "../threadSync"; | 491 | import { resolveThreadSyncPhase } from "../threadSync"; |
| 491 | import { | 492 | import { |
| ... | @@ -493,7 +494,7 @@ import { | ... | @@ -493,7 +494,7 @@ import { |
| 493 | hasDismissedResumeCompaction, | 494 | hasDismissedResumeCompaction, |
| 494 | shouldOfferResumeCompaction, | 495 | shouldOfferResumeCompaction, |
| 495 | } from "./chat/ContextWindowMeter.logic"; | 496 | } from "./chat/ContextWindowMeter.logic"; |
| 496 | import { deriveLatestContextWindowSnapshot, formatContextWindowTokens } from "../lib/contextWindow"; | 497 | import { deriveLatestContextWindowSnapshot } from "../lib/contextWindow"; |
| 497 | import { | 498 | import { |
| 498 | DRAFT_HERO_TRANSITION_ANIMATION_ID, | 499 | DRAFT_HERO_TRANSITION_ANIMATION_ID, |
| 499 | DRAFT_HERO_TRANSITION_EASING, | 500 | DRAFT_HERO_TRANSITION_EASING, |
| ... | @@ -7563,73 +7564,61 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7563,73 +7564,61 @@ export default function ChatView(props: ChatViewProps) { |
| 7563 | isStoppingBackgroundWork, | 7564 | isStoppingBackgroundWork, |
| 7564 | onOpenRelatedThread, | 7565 | onOpenRelatedThread, |
| 7565 | ]); | 7566 | ]); |
| 7566 | // A woken thread announces itself in the open view, not just the sidebar | 7567 | // Settled, snoozed, and woke are thread state, not composer actions: each |
| 7567 | // pill. Dismissing marks the wake as seen (same acknowledgment as the | 7568 | // gets one quiet line after the last message instead of a banner. A woken |
| 7568 | // pill); sending a message clears it as a side effect of the send path. | 7569 | // thread announces itself here, not just in the sidebar pill. Dismissing |
| 7569 | const wokeThreadBannerItem = useMemo<ComposerBannerStackItem | null>(() => { | 7570 | // marks the wake as seen (same acknowledgment as the pill); sending a |
| 7570 | if (!activeThreadWokeVisible) { | 7571 | // message clears it as a side effect of the send path. |
| 7571 | return null; | 7572 | // Memoized: it is the timeline's list footer, and a new element re-renders that footer. |
| 7572 | } | 7573 | // nowMinute keeps the relative time fresh. |
| 7573 | return { | 7574 | const threadStatusLine = useMemo(() => { |
| 7574 | id: `thread-woke:${activeThread?.id ?? "unknown"}`, | 7575 | void nowMinute; |
| 7575 | variant: "info", | 7576 | return activeThreadSnoozed ? ( |
| 7576 | icon: <AlarmClockIcon />, | 7577 | <ThreadStatusLine |
| 7577 | title: "Thread woke from snooze", | 7578 | icon={<AlarmClockIcon />} |
| 7578 | description: "Send a message to continue", | 7579 | label={ |
| 7579 | dismissLabel: "Dismiss Woke notification", | 7580 | activeThreadShell?.snoozedUntil |
| 7580 | onDismiss: acknowledgeActiveThreadWoke, | 7581 | ? `Snoozed, ${formatRelativeTimeUntilLabel(activeThreadShell.snoozedUntil)}` |
| 7581 | }; | 7582 | : "Snoozed" |
| 7582 | }, [acknowledgeActiveThreadWoke, activeThread?.id, activeThreadWokeVisible]); | 7583 | } |
| 7583 | const parkedThreadBannerItem = useMemo<ComposerBannerStackItem | null>(() => { | 7584 | actionLabel={isUnsnoozing ? "Waking..." : "Wake now"} |
| 7584 | if (!activeThreadSnoozed && !activeThreadSettled) { | 7585 | actionDisabled={!canOperateThread || isUnsnoozing} |
| 7585 | return null; | 7586 | onAction={() => void handleUnsnoozeActiveThread()} |
| 7586 | } | 7587 | /> |
| 7587 | const isSnoozed = activeThreadSnoozed; | 7588 | ) : activeThreadSettled ? ( |
| 7588 | return { | 7589 | <ThreadStatusLine |
| 7589 | id: `thread-${isSnoozed ? "snoozed" : "settled"}:${activeThread?.id ?? "unknown"}`, | 7590 | icon={<CheckCircle2Icon />} |
| 7590 | variant: "info", | 7591 | label={ |
| 7591 | icon: isSnoozed ? <AlarmClockIcon /> : <CheckCircle2Icon />, | 7592 | activeThreadShell?.settledAt |
| 7592 | title: `This thread is ${isSnoozed ? "snoozed" : "settled"}`, | 7593 | ? `Settled ${formatRelativeTimeLabel(activeThreadShell.settledAt)}` |
| 7593 | description: `Send a message to ${isSnoozed ? "wake" : "unsettle"}`, | 7594 | : "Settled" |
| 7594 | actions: ( | 7595 | } |
| 7595 | <Button | 7596 | actionLabel={isUnsettling ? "Un-settling..." : "Un-settle"} |
| 7596 | size="xs" | 7597 | actionDisabled={!canOperateThread || isUnsettling} |
| 7597 | variant="ghost" | 7598 | onAction={() => void handleUnsettleActiveThread()} |
| 7598 | disabled={!canOperateThread || (isSnoozed ? isUnsnoozing : isUnsettling)} | 7599 | /> |
| 7599 | onClick={() => | 7600 | ) : activeThreadWokeVisible ? ( |
| 7600 | void (isSnoozed ? handleUnsnoozeActiveThread() : handleUnsettleActiveThread()) | 7601 | <ThreadStatusLine |
| 7601 | } | 7602 | icon={<AlarmClockIcon />} |
| 7602 | > | 7603 | label="Woke from snooze" |
| 7603 | {isSnoozed | 7604 | actionLabel="Dismiss" |
| 7604 | ? isUnsnoozing | 7605 | onAction={acknowledgeActiveThreadWoke} |
| 7605 | ? "Waking..." | 7606 | /> |
| 7606 | : "Wake now" | 7607 | ) : null; |
| 7607 | : isUnsettling | | |
| 7608 | ? "Un-settling..." | | |
| 7609 | : "Un-settle"} | | |
| 7610 | </Button> | | |
| 7611 | ), | | |
| 7612 | }; | | |
| 7613 | }, [ | 7608 | }, [ |
| 7614 | activeThread?.id, | 7609 | acknowledgeActiveThreadWoke, |
| 7615 | activeThreadSettled, | 7610 | activeThreadSettled, |
| | 7611 | activeThreadShell?.settledAt, |
| | 7612 | activeThreadShell?.snoozedUntil, |
| 7616 | activeThreadSnoozed, | 7613 | activeThreadSnoozed, |
| | 7614 | activeThreadWokeVisible, |
| 7617 | canOperateThread, | 7615 | canOperateThread, |
| 7618 | handleUnsnoozeActiveThread, | | |
| 7619 | handleUnsettleActiveThread, | 7616 | handleUnsettleActiveThread, |
| 7620 | isUnsnoozing, | 7617 | handleUnsnoozeActiveThread, |
| 7621 | isUnsettling, | 7618 | isUnsettling, |
| | 7619 | isUnsnoozing, |
| | 7620 | nowMinute, |
| 7622 | ]); | 7621 | ]); |
| 7623 | // Session-scoped dismissals, one key per (thread, snapshot). A set rather | | |
| 7624 | // than a single slot so dismissing the banner on one thread does not | | |
| 7625 | // resurface it on another thread dismissed earlier. | | |
| 7626 | const [dismissedResumeCompactionKeys, setDismissedResumeCompactionKeys] = useState< | | |
| 7627 | ReadonlySet<string> | | |
| 7628 | >(new Set()); | | |
| 7629 | const resumeCompactionKey = | | |
| 7630 | activeThread && activeContextWindow | | |
| 7631 | ? `${activeThread.id}:${activeContextWindow.updatedAt}` | | |
| 7632 | : null; | | |
| 7633 | const activeThreadHasCompactableConversation = serverVisibleTurnItems.some( | 7622 | const activeThreadHasCompactableConversation = serverVisibleTurnItems.some( |
| 7634 | ({ item }) => | 7623 | ({ item }) => |
| 7635 | item.type === "user_message" && | 7624 | item.type === "user_message" && |
| ... | @@ -7661,73 +7650,36 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7661,73 +7650,36 @@ export default function ChatView(props: ChatViewProps) { |
| 7661 | ? "Compaction is unavailable for this provider" | 7650 | ? "Compaction is unavailable for this provider" |
| 7662 | : "Compacting is unavailable right now" | 7651 | : "Compacting is unavailable right now" |
| 7663 | : null; | 7652 | : null; |
| 7664 | const resumeCompactionBannerItem = useMemo<ComposerBannerStackItem | null>(() => { | 7653 | // Tokens a stale Claude session would re-read on its next turn. While set, |
| 7665 | if ( | 7654 | // Enter compacts first and the composer's send button says so; "Send with |
| 7666 | !activeThread || | 7655 | // full history" in its menu skips that once. Held queues and multi-model |
| 7667 | !activeContextWindow || | 7656 | // sends never compact first, so the offer hides for them. |
| 7668 | resumeCompactionKey === null || | 7657 | const resumeCompactionTokens = |
| 7669 | dismissedResumeCompactionKeys.has(resumeCompactionKey) || | 7658 | activeContextWindow && |
| 7670 | resumeCompactionPermanentlyDismissed || | 7659 | !resumeCompactionPermanentlyDismissed && |
| 7671 | nativeResumeCompactionDismissed || | 7660 | !nativeResumeCompactionDismissed && |
| 7672 | pendingUserInputs.length > 0 || | 7661 | !compactDisabled && |
| 7673 | phase === "running" || | 7662 | !hasHeldQueuedRuns && |
| 7674 | !shouldOfferResumeCompaction({ | 7663 | multipleModelSelections === null && |
| 7675 | provider: selectedProvider, | 7664 | shouldOfferResumeCompaction({ |
| 7676 | usedTokens: activeContextWindow.usedTokens, | 7665 | provider: selectedProvider, |
| 7677 | updatedAt: activeContextWindow.updatedAt, | 7666 | usedTokens: activeContextWindow.usedTokens, |
| 7678 | now: `${nowMinute}:00.000Z`, | 7667 | updatedAt: activeContextWindow.updatedAt, |
| 7679 | }) | 7668 | now: `${nowMinute}:00.000Z`, |
| 7680 | ) { | 7669 | }) |
| 7681 | return null; | 7670 | ? activeContextWindow.usedTokens |
| | 7671 | : null; |
| | 7672 | // Set only for the synchronous span of a "Send with full history" submit; |
| | 7673 | // onSend reads it before its first await. |
| | 7674 | const keepFullHistoryOnceRef = useRef(false); |
| | 7675 | const sendWithFullHistory = useCallback((send: () => void) => { |
| | 7676 | keepFullHistoryOnceRef.current = true; |
| | 7677 | try { |
| | 7678 | send(); |
| | 7679 | } finally { |
| | 7680 | keepFullHistoryOnceRef.current = false; |
| 7682 | } | 7681 | } |
| 7683 | | 7682 | }, []); |
| 7684 | const dismiss = () => | | |
| 7685 | setDismissedResumeCompactionKeys((keys) => new Set(keys).add(resumeCompactionKey)); | | |
| 7686 | const compactAction = ( | | |
| 7687 | <Button | | |
| 7688 | size="xs" | | |
| 7689 | variant="ghost" | | |
| 7690 | disabled={compactDisabled} | | |
| 7691 | onClick={() => { | | |
| 7692 | if (compactDisabled) return; | | |
| 7693 | composerRef.current?.compactContext(); | | |
| 7694 | }} | | |
| 7695 | > | | |
| 7696 | Compact | | |
| 7697 | </Button> | | |
| 7698 | ); | | |
| 7699 | return { | | |
| 7700 | id: `resume-compaction:${resumeCompactionKey}`, | | |
| 7701 | variant: "info", | | |
| 7702 | icon: <Minimize2Icon />, | | |
| 7703 | title: "Resume with less context", | | |
| 7704 | description: `${formatContextWindowTokens(activeContextWindow.usedTokens)} tokens from earlier`, | | |
| 7705 | actions: compactDisabledReason ? ( | | |
| 7706 | <Tooltip> | | |
| 7707 | <TooltipTrigger render={<span className="inline-flex">{compactAction}</span>} /> | | |
| 7708 | <TooltipPopup side="top">{compactDisabledReason}</TooltipPopup> | | |
| 7709 | </Tooltip> | | |
| 7710 | ) : ( | | |
| 7711 | compactAction | | |
| 7712 | ), | | |
| 7713 | dismissLabel: "Keep full history", | | |
| 7714 | onDismiss: dismiss, | | |
| 7715 | }; | | |
| 7716 | }, [ | | |
| 7717 | activeContextWindow, | | |
| 7718 | activeThread, | | |
| 7719 | compactDisabled, | | |
| 7720 | compactDisabledReason, | | |
| 7721 | composerRef, | | |
| 7722 | dismissedResumeCompactionKeys, | | |
| 7723 | nativeResumeCompactionDismissed, | | |
| 7724 | nowMinute, | | |
| 7725 | pendingUserInputs.length, | | |
| 7726 | phase, | | |
| 7727 | resumeCompactionKey, | | |
| 7728 | resumeCompactionPermanentlyDismissed, | | |
| 7729 | selectedProvider, | | |
| 7730 | ]); | | |
| 7731 | const handleRestoreThreadBranch = useCallback(() => { | 7683 | const handleRestoreThreadBranch = useCallback(() => { |
| 7732 | if (!canWriteSourceControl) return; | 7684 | if (!canWriteSourceControl) return; |
| 7733 | if (gitStatusQuery.data?.hasWorkingTreeChanges) { | 7685 | if (gitStatusQuery.data?.hasWorkingTreeChanges) { |
| ... | @@ -7779,10 +7731,6 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7779,10 +7731,6 @@ export default function ChatView(props: ChatViewProps) { |
| 7779 | const backgroundWorkItems = [goalBannerItem, backgroundWorkBannerItem].filter( | 7731 | const backgroundWorkItems = [goalBannerItem, backgroundWorkBannerItem].filter( |
| 7780 | (item) => item !== null, | 7732 | (item) => item !== null, |
| 7781 | ); | 7733 | ); |
| 7782 | const resumeCompactionItems = | | |
| 7783 | resumeCompactionBannerItem === null ? [] : [resumeCompactionBannerItem]; | | |
| 7784 | const wokeThreadItems = wokeThreadBannerItem === null ? [] : [wokeThreadBannerItem]; | | |
| 7785 | const parkedThreadItems = parkedThreadBannerItem === null ? [] : [parkedThreadBannerItem]; | | |
| 7786 | // The user asked for this one, so it leads the notice tier instead of trailing it. | 7734 | // The user asked for this one, so it leads the notice tier instead of trailing it. |
| 7787 | const usageLimitsItems = usageLimitsBanner === null ? [] : [usageLimitsBanner]; | 7735 | const usageLimitsItems = usageLimitsBanner === null ? [] : [usageLimitsBanner]; |
| 7788 | const projectCloneItems = projectCloneBannerItem === null ? [] : [projectCloneBannerItem]; | 7736 | const projectCloneItems = projectCloneBannerItem === null ? [] : [projectCloneBannerItem]; |
| ... | @@ -7794,9 +7742,6 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7794,9 +7742,6 @@ export default function ChatView(props: ChatViewProps) { |
| 7794 | ...projectCloneItems, | 7742 | ...projectCloneItems, |
| 7795 | ...systemComposerBannerItems, | 7743 | ...systemComposerBannerItems, |
| 7796 | ...backgroundWorkItems, | 7744 | ...backgroundWorkItems, |
| 7797 | ...resumeCompactionItems, | | |
| 7798 | ...wokeThreadItems, | | |
| 7799 | ...parkedThreadItems, | | |
| 7800 | ]; | 7745 | ]; |
| 7801 | } | 7746 | } |
| 7802 | return [ | 7747 | return [ |
| ... | @@ -7806,8 +7751,6 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7806,8 +7751,6 @@ export default function ChatView(props: ChatViewProps) { |
| 7806 | ...projectCloneItems, | 7751 | ...projectCloneItems, |
| 7807 | ...systemComposerBannerItems, | 7752 | ...systemComposerBannerItems, |
| 7808 | ...backgroundWorkItems, | 7753 | ...backgroundWorkItems, |
| 7809 | ...resumeCompactionItems, | | |
| 7810 | ...wokeThreadItems, | | |
| 7811 | { | 7754 | { |
| 7812 | id: `branch-mismatch:${activeBranchMismatchKey}`, | 7755 | id: `branch-mismatch:${activeBranchMismatchKey}`, |
| 7813 | variant: "info", | 7756 | variant: "info", |
| ... | @@ -7846,7 +7789,6 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7846,7 +7789,6 @@ export default function ChatView(props: ChatViewProps) { |
| 7846 | setBranchMismatchDismissTick((tick) => tick + 1); | 7789 | setBranchMismatchDismissTick((tick) => tick + 1); |
| 7847 | }, | 7790 | }, |
| 7848 | }, | 7791 | }, |
| 7849 | ...parkedThreadItems, | | |
| 7850 | ]; | 7792 | ]; |
| 7851 | }, [ | 7793 | }, [ |
| 7852 | activeBranchMismatchKey, | 7794 | activeBranchMismatchKey, |
| ... | @@ -7860,13 +7802,10 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7860,13 +7802,10 @@ export default function ChatView(props: ChatViewProps) { |
| 7860 | backgroundWorkBannerItem, | 7802 | backgroundWorkBannerItem, |
| 7861 | goalBannerItem, | 7803 | goalBannerItem, |
| 7862 | localCheckoutBranchMismatch, | 7804 | localCheckoutBranchMismatch, |
| 7863 | parkedThreadBannerItem, | | |
| 7864 | projectCloneBannerItem, | 7805 | projectCloneBannerItem, |
| 7865 | resumeCompactionBannerItem, | | |
| 7866 | showBranchMismatchBanner, | 7806 | showBranchMismatchBanner, |
| 7867 | systemComposerBannerItems, | 7807 | systemComposerBannerItems, |
| 7868 | usageLimitsBanner, | 7808 | usageLimitsBanner, |
| 7869 | wokeThreadBannerItem, | | |
| 7870 | ]); | 7809 | ]); |
| 7871 | | 7810 | |
| 7872 | useEffect(() => { | 7811 | useEffect(() => { |
| ... | @@ -8692,6 +8631,7 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -8692,6 +8631,7 @@ export default function ChatView(props: ChatViewProps) { |
| 8692 | }, | 8631 | }, |
| 8693 | ) => { | 8632 | ) => { |
| 8694 | e?.preventDefault(); | 8633 | e?.preventDefault(); |
| | 8634 | const keepFullHistory = keepFullHistoryOnceRef.current; |
| 8695 | // Typed out in full rather than picked from the menu. Attachments or contexts | 8635 | // Typed out in full rather than picked from the menu. Attachments or contexts |
| 8696 | // mean the user is sending a prompt, so those go through as usual. | 8636 | // mean the user is sending a prompt, so those go through as usual. |
| 8697 | if ( | 8637 | if ( |
| ... | @@ -9216,7 +9156,16 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -9216,7 +9156,16 @@ export default function ChatView(props: ChatViewProps) { |
| 9216 | ); | 9156 | ); |
| 9217 | const messageIdForSend = newMessageId(); | 9157 | const messageIdForSend = newMessageId(); |
| 9218 | const messageCreatedAt = new Date().toISOString(); | 9158 | const messageCreatedAt = new Date().toISOString(); |
| 9219 | const shouldQueueBehindActiveRun = phase === "running" && dispatchMode === "queue"; | 9159 | // A stale Claude session compacts before this message so the turn does not re-read the |
| | 9160 | // old history. The message queues behind the /compact run, since steering into it is |
| | 9161 | // rejected. |
| | 9162 | const compactBeforeSend = |
| | 9163 | resumeCompactionTokens !== null && |
| | 9164 | !keepFullHistory && |
| | 9165 | messageTextForSend.toLowerCase() !== "/compact"; |
| | 9166 | const turnDispatchMode = compactBeforeSend ? "queue" : dispatchMode; |
| | 9167 | const shouldQueueBehindActiveRun = |
| | 9168 | compactBeforeSend || (phase === "running" && dispatchMode === "queue"); |
| 9220 | const outgoingMessageText = formatOutgoingPrompt({ | 9169 | const outgoingMessageText = formatOutgoingPrompt({ |
| 9221 | provider: ctxSelectedProvider, | 9170 | provider: ctxSelectedProvider, |
| 9222 | model: ctxSelectedModel, | 9171 | model: ctxSelectedModel, |
| ... | @@ -9799,6 +9748,22 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -9799,6 +9748,22 @@ export default function ChatView(props: ChatViewProps) { |
| 9799 | failure = turnAttachmentsResult; | 9748 | failure = turnAttachmentsResult; |
| 9800 | } | 9749 | } |
| 9801 | | 9750 | |
| | 9751 | if (failure === null && compactBeforeSend) { |
| | 9752 | const compactResult = await startThreadTurn({ |
| | 9753 | environmentId, |
| | 9754 | input: { |
| | 9755 | threadId: threadIdForSend, |
| | 9756 | message: { messageId: newMessageId(), role: "user", text: "/compact", attachments: [] }, |
| | 9757 | modelSelection: ctxSelectedModelSelection, |
| | 9758 | runtimeMode, |
| | 9759 | interactionMode: sendInteractionMode, |
| | 9760 | }, |
| | 9761 | }); |
| | 9762 | if (compactResult._tag === "Failure") { |
| | 9763 | failure = compactResult; |
| | 9764 | } |
| | 9765 | } |
| | 9766 | |
| 9802 | let backgroundDraftOpened = false; | 9767 | let backgroundDraftOpened = false; |
| 9803 | let turnStartSucceeded = false; | 9768 | let turnStartSucceeded = false; |
| 9804 | if (failure === null && turnAttachmentsResult._tag === "Success") { | 9769 | if (failure === null && turnAttachmentsResult._tag === "Success") { |
| ... | @@ -9876,7 +9841,7 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -9876,7 +9841,7 @@ export default function ChatView(props: ChatViewProps) { |
| 9876 | titleSeed: title, | 9841 | titleSeed: title, |
| 9877 | runtimeMode, | 9842 | runtimeMode, |
| 9878 | interactionMode: sendInteractionMode, | 9843 | interactionMode: sendInteractionMode, |
| 9879 | dispatchMode, | 9844 | dispatchMode: turnDispatchMode, |
| 9880 | ...(bootstrap ? { bootstrap } : {}), | 9845 | ...(bootstrap ? { bootstrap } : {}), |
| 9881 | createdAt: messageCreatedAt, | 9846 | createdAt: messageCreatedAt, |
| 9882 | }, | 9847 | }, |
| ... | @@ -11324,6 +11289,7 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -11324,6 +11289,7 @@ export default function ChatView(props: ChatViewProps) { |
| 11324 | {...(draftId ? { onWorktreeSetupWorkLocally } : {})} | 11289 | {...(draftId ? { onWorktreeSetupWorkLocally } : {})} |
| 11325 | {...(onOpenWorktreeSetupTerminal ? { onOpenWorktreeSetupTerminal } : {})} | 11290 | {...(onOpenWorktreeSetupTerminal ? { onOpenWorktreeSetupTerminal } : {})} |
| 11326 | isPreparingWorktree={!paintOnlyDisplayedTimeline && isPreparingWorktree} | 11291 | isPreparingWorktree={!paintOnlyDisplayedTimeline && isPreparingWorktree} |
| | 11292 | footer={paintOnlyDisplayedTimeline ? null : threadStatusLine} |
| 11327 | listRef={legendListRef} | 11293 | listRef={legendListRef} |
| 11328 | timelineEntries={displayedTimeline.entries} | 11294 | timelineEntries={displayedTimeline.entries} |
| 11329 | providerStatuses={ | 11295 | providerStatuses={ |
| ... | @@ -11575,6 +11541,8 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -11575,6 +11541,8 @@ export default function ChatView(props: ChatViewProps) { |
| 11575 | ) : null | 11541 | ) : null |
| 11576 | } | 11542 | } |
| 11577 | bannerItems={composerBannerItems} | 11543 | bannerItems={composerBannerItems} |
| | 11544 | resumeCompactionTokens={resumeCompactionTokens} |
| | 11545 | onSendWithFullHistory={sendWithFullHistory} |
| 11578 | // With attachments or contexts aboard the pick just inserts the | 11546 | // With attachments or contexts aboard the pick just inserts the |
| 11579 | // text, so it sends as a prompt like the typed path would. | 11547 | // text, so it sends as a prompt like the typed path would. |
| 11580 | onUsageLimitsCommand={ | 11548 | onUsageLimitsCommand={ |