@iaforged/context-code 1.1.9 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +24 -0
  2. package/dist/src/commands/model/model.js +9 -5
  3. package/dist/src/commands/timeline/index.js +8 -0
  4. package/dist/src/commands/timeline/timeline.js +194 -0
  5. package/dist/src/commands.js +2 -0
  6. package/dist/src/components/AgentActivitySidebar.js +50 -0
  7. package/dist/src/components/AgentProgressLine.js +5 -5
  8. package/dist/src/components/ModelPicker.js +252 -441
  9. package/dist/src/components/PromptInput/PromptInputFooter.js +10 -29
  10. package/dist/src/components/Spinner/TeammateSpinnerLine.js +20 -62
  11. package/dist/src/components/Spinner/TeammateSpinnerTree.js +16 -258
  12. package/dist/src/components/Spinner/teammateSelectHint.js +1 -1
  13. package/dist/src/components/Spinner/utils.js +3 -6
  14. package/dist/src/components/ThemeBrowser.js +120 -0
  15. package/dist/src/components/ThemePicker.js +113 -321
  16. package/dist/src/components/design-system/ThemeProvider.js +3 -0
  17. package/dist/src/components/mcp/MCPListPanel.js +138 -444
  18. package/dist/src/components/permissions/SandboxPermissionRequest.js +5 -5
  19. package/dist/src/components/teams/TeamStatus.js +7 -71
  20. package/dist/src/constants/spinnerVerbs.js +80 -180
  21. package/dist/src/context/modalStackContext.js +12 -0
  22. package/dist/src/hooks/useTextInput.js +28 -18
  23. package/dist/src/main.js +12 -0
  24. package/dist/src/screens/REPL.js +386 -320
  25. package/dist/src/skills/loadSkillsDir.js +1 -0
  26. package/dist/src/tools/AgentTool/UI.js +8 -8
  27. package/dist/src/tools/BashTool/bashSecurity.js +1 -1
  28. package/dist/src/utils/handlePromptSubmit.js +12 -2
  29. package/dist/src/utils/processUserInput/processSlashCommand.js +9 -5
  30. package/dist/src/utils/sembleMcp/common.js +5 -0
  31. package/dist/src/utils/sembleMcp/setup.js +119 -0
  32. package/dist/src/utils/theme.js +24 -3
  33. package/dist/src/utils/themes/bootstrap.js +109 -0
  34. package/dist/src/utils/themes/builtin/opencode/_index.json +41 -0
  35. package/dist/src/utils/themes/builtin/opencode/amoled.json +49 -0
  36. package/dist/src/utils/themes/builtin/opencode/aura.json +51 -0
  37. package/dist/src/utils/themes/builtin/opencode/ayu.json +51 -0
  38. package/dist/src/utils/themes/builtin/opencode/carbonfox.json +53 -0
  39. package/dist/src/utils/themes/builtin/opencode/catppuccin-frappe.json +85 -0
  40. package/dist/src/utils/themes/builtin/opencode/catppuccin-macchiato.json +85 -0
  41. package/dist/src/utils/themes/builtin/opencode/catppuccin.json +45 -0
  42. package/dist/src/utils/themes/builtin/opencode/cobalt2.json +87 -0
  43. package/dist/src/utils/themes/builtin/opencode/cursor.json +91 -0
  44. package/dist/src/utils/themes/builtin/opencode/dracula.json +49 -0
  45. package/dist/src/utils/themes/builtin/opencode/everforest.json +89 -0
  46. package/dist/src/utils/themes/builtin/opencode/flexoki.json +86 -0
  47. package/dist/src/utils/themes/builtin/opencode/github.json +85 -0
  48. package/dist/src/utils/themes/builtin/opencode/gruvbox.json +45 -0
  49. package/dist/src/utils/themes/builtin/opencode/kanagawa.json +89 -0
  50. package/dist/src/utils/themes/builtin/opencode/lucent-orng.json +87 -0
  51. package/dist/src/utils/themes/builtin/opencode/material.json +87 -0
  52. package/dist/src/utils/themes/builtin/opencode/matrix.json +91 -0
  53. package/dist/src/utils/themes/builtin/opencode/mercury.json +86 -0
  54. package/dist/src/utils/themes/builtin/opencode/monokai.json +49 -0
  55. package/dist/src/utils/themes/builtin/opencode/nightowl.json +46 -0
  56. package/dist/src/utils/themes/builtin/opencode/nord.json +46 -0
  57. package/dist/src/utils/themes/builtin/opencode/oc-2.json +88 -0
  58. package/dist/src/utils/themes/builtin/opencode/one-dark.json +89 -0
  59. package/dist/src/utils/themes/builtin/opencode/onedarkpro.json +45 -0
  60. package/dist/src/utils/themes/builtin/opencode/opencode.json +89 -0
  61. package/dist/src/utils/themes/builtin/opencode/orng.json +87 -0
  62. package/dist/src/utils/themes/builtin/opencode/osaka-jade.json +88 -0
  63. package/dist/src/utils/themes/builtin/opencode/palenight.json +85 -0
  64. package/dist/src/utils/themes/builtin/opencode/rosepine.json +85 -0
  65. package/dist/src/utils/themes/builtin/opencode/shadesofpurple.json +51 -0
  66. package/dist/src/utils/themes/builtin/opencode/solarized.json +49 -0
  67. package/dist/src/utils/themes/builtin/opencode/synthwave84.json +87 -0
  68. package/dist/src/utils/themes/builtin/opencode/tokyonight.json +47 -0
  69. package/dist/src/utils/themes/builtin/opencode/vercel.json +90 -0
  70. package/dist/src/utils/themes/builtin/opencode/vesper.json +51 -0
  71. package/dist/src/utils/themes/builtin/opencode/zenburn.json +87 -0
  72. package/dist/src/utils/themes/index.js +4 -0
  73. package/dist/src/utils/themes/loader.js +147 -0
  74. package/dist/src/utils/themes/opencodeMapper.js +124 -0
  75. package/dist/src/utils/themes/resolver.js +66 -0
  76. package/dist/src/utils/themes/types.js +1 -0
  77. package/docs/MCP_SERVERS.md +27 -1
  78. package/package.json +1 -1
@@ -25,6 +25,7 @@ import { IdleReturnDialog } from '../components/IdleReturnDialog.js';
25
25
  import * as React from 'react';
26
26
  import { useEffect, useMemo, useRef, useState, useCallback, useDeferredValue, useLayoutEffect } from 'react';
27
27
  import { useNotifications } from '../context/notifications.js';
28
+ import { ModalStackContext } from '../context/modalStackContext.js';
28
29
  import { sendNotification } from '../services/notifier.js';
29
30
  import { startPreventSleep, stopPreventSleep } from '../services/preventSleep.js';
30
31
  import { useTerminalNotification } from '../ink/useTerminalNotification.js';
@@ -828,6 +829,24 @@ export function REPL({ commands: initialCommands, debug, initialTools, initialMe
828
829
  // eslint-disable-next-line react-hooks/exhaustive-deps
829
830
  }, []);
830
831
  const [toolJSX, setToolJSXInternal] = useState(null);
832
+ const [modalStack, setModalStack] = useState([]);
833
+ const pushModal = useCallback((item) => {
834
+ setModalStack(prev => [...prev, item]);
835
+ }, []);
836
+ const replaceModal = useCallback((item) => {
837
+ setModalStack(prev => {
838
+ if (prev.length === 0) {
839
+ return [item];
840
+ }
841
+ return [...prev.slice(0, -1), item];
842
+ });
843
+ }, []);
844
+ const popModal = useCallback(() => {
845
+ setModalStack(prev => prev.slice(0, -1));
846
+ }, []);
847
+ const clearModals = useCallback(() => {
848
+ setModalStack([]);
849
+ }, []);
831
850
  // Track local JSX commands separately so tools can't overwrite them.
832
851
  // This enables "immediate" commands (like /btw) to persist while Claude is processing.
833
852
  const localJSXCommandRef = useRef(null);
@@ -1001,6 +1020,26 @@ export function REPL({ commands: initialCommands, debug, initialTools, initialMe
1001
1020
  }
1002
1021
  const [cursor, setCursor] = useState(null);
1003
1022
  const cursorNavRef = useRef(null);
1023
+ const jumpToMessage = useCallback((messageId) => {
1024
+ const exact = messagesRef.current.find(m => m.uuid === messageId)?.uuid;
1025
+ const prefix = messagesRef.current.find(m => m.uuid.slice(0, 24) === messageId.slice(0, 24))?.uuid;
1026
+ const resolved = exact ?? prefix;
1027
+ if (!resolved) {
1028
+ addNotification({
1029
+ key: 'timeline-jump-not-found',
1030
+ text: 'Message no longer available in active transcript',
1031
+ color: 'warning',
1032
+ priority: 'medium',
1033
+ timeoutMs: 3000
1034
+ });
1035
+ return;
1036
+ }
1037
+ setCursor({
1038
+ uuid: resolved,
1039
+ msgType: 'user',
1040
+ expanded: false
1041
+ });
1042
+ }, [addNotification]);
1004
1043
  // Memoized so Messages' React.memo holds.
1005
1044
  const unseenDivider = useMemo(() => computeUnseenDivider(messages, dividerIndex),
1006
1045
  // eslint-disable-next-line react-hooks/exhaustive-deps -- length change covers appends; useUnseenDivider's count-drop guard clears dividerIndex on replace/rewind
@@ -1783,6 +1822,12 @@ export function REPL({ commands: initialCommands, debug, initialTools, initialMe
1783
1822
  prevDialogRef.current = focusedInputDialog;
1784
1823
  }, [focusedInputDialog, repinScroll]);
1785
1824
  function onCancel() {
1825
+ if (modalStack.length > 0) {
1826
+ const topModal = modalStack[modalStack.length - 1];
1827
+ topModal?.onClose?.();
1828
+ popModal();
1829
+ return;
1830
+ }
1786
1831
  if (focusedInputDialog === 'elicitation') {
1787
1832
  // Elicitation dialog handles its own Escape, and closing it shouldn't affect any loading state.
1788
1833
  return;
@@ -2131,6 +2176,18 @@ export function REPL({ commands: initialCommands, debug, initialTools, initialMe
2131
2176
  setIsMessageSelectorVisible(true);
2132
2177
  }
2133
2178
  },
2179
+ openMessageSelectorAtMessage: (messageId) => {
2180
+ if (disabled)
2181
+ return;
2182
+ const exact = messages.find(m => m.uuid === messageId);
2183
+ const prefix = messages.find(m => m.uuid.slice(0, 24) === messageId.slice(0, 24));
2184
+ const raw = exact ?? prefix;
2185
+ if (raw && selectableUserMessagesFilter(raw)) {
2186
+ setMessageSelectorPreselect(raw);
2187
+ }
2188
+ setIsMessageSelectorVisible(true);
2189
+ },
2190
+ jumpToMessage,
2134
2191
  onChangeAPIKey: reverify,
2135
2192
  readFileState: readFileState.current,
2136
2193
  setToolJSX,
@@ -2184,7 +2241,7 @@ export function REPL({ commands: initialCommands, debug, initialTools, initialMe
2184
2241
  requestPrompt: feature('HOOK_PROMPTS') ? requestPrompt : undefined,
2185
2242
  contentReplacementState: contentReplacementStateRef.current
2186
2243
  };
2187
- }, [commands, combinedInitialTools, mainThreadAgentDefinition, debug, initialMcpClients, ideInstallationStatus, dynamicMcpConfig, theme, allowedAgentTypes, store, setAppState, reverify, addNotification, setMessages, onChangeDynamicMcpConfig, resume, requestPrompt, disabled, customSystemPrompt, appendSystemPrompt, setConversationId]);
2244
+ }, [commands, combinedInitialTools, mainThreadAgentDefinition, debug, initialMcpClients, ideInstallationStatus, dynamicMcpConfig, theme, allowedAgentTypes, store, setAppState, reverify, addNotification, setMessages, onChangeDynamicMcpConfig, resume, requestPrompt, disabled, customSystemPrompt, appendSystemPrompt, setConversationId, jumpToMessage, messages]);
2188
2245
  // Session backgrounding (Ctrl+B to background/foreground)
2189
2246
  const handleBackgroundQuery = useCallback(() => {
2190
2247
  // Stop the foreground query so the background one takes over
@@ -3125,7 +3182,8 @@ export function REPL({ commands: initialCommands, debug, initialTools, initialMe
3125
3182
  // Read via ref so streamMode can be dropped from onSubmit deps —
3126
3183
  // handlePromptSubmit only uses it for debug log + telemetry event.
3127
3184
  streamMode: streamModeRef.current,
3128
- hasInterruptibleToolInProgress: hasInterruptibleToolInProgressRef.current
3185
+ hasInterruptibleToolInProgress: hasInterruptibleToolInProgressRef.current,
3186
+ onJumpToMessage: jumpToMessage
3129
3187
  });
3130
3188
  // Restore stash that was deferred above. Two cases:
3131
3189
  // - Slash command: handlePromptSubmit awaited the full command execution
@@ -3481,9 +3539,10 @@ export function REPL({ commands: initialCommands, debug, initialTools, initialMe
3481
3539
  canUseTool,
3482
3540
  addNotification,
3483
3541
  setMessages,
3484
- queuedCommands
3542
+ queuedCommands,
3543
+ onJumpToMessage: jumpToMessage
3485
3544
  });
3486
- }, [queryGuard, commands, setToolJSX, getToolUseContext, messages, mainLoopModel, ideSelection, setUserInputOnProcessing, canUseTool, setAbortController, onQuery, addNotification, setAppState, onBeforeQuery]);
3545
+ }, [queryGuard, commands, setToolJSX, getToolUseContext, messages, mainLoopModel, ideSelection, setUserInputOnProcessing, canUseTool, setAbortController, onQuery, addNotification, setAppState, onBeforeQuery, jumpToMessage]);
3487
3546
  useQueueProcessor({
3488
3547
  executeQueuedInput,
3489
3548
  hasActiveLocalJsxUI: isShowingLocalJSXCommand,
@@ -4073,348 +4132,355 @@ export function REPL({ commands: initialCommands, debug, initialTools, initialMe
4073
4132
  // (immediate: /model, /mcp, /btw, ...) and scrollable (non-immediate:
4074
4133
  // /config, /theme, /diff, ...) both go here now.
4075
4134
  const toolJsxCentered = isFullscreenEnvEnabled() && toolJSX?.isLocalJSXCommand === true;
4076
- const centeredModal = toolJsxCentered ? toolJSX.jsx : null;
4135
+ const stackTopModal = modalStack.length > 0 ? modalStack[modalStack.length - 1]?.element : null;
4136
+ const centeredModal = stackTopModal ?? (toolJsxCentered ? toolJSX.jsx : null);
4077
4137
  // <AlternateScreen> at the root: everything below is inside its
4078
4138
  // <Box height={rows}>. Handlers/contexts are zero-height so ScrollBox's
4079
4139
  // flexGrow in FullscreenLayout resolves against this Box. The transcript
4080
4140
  // early return above wraps its virtual-scroll branch the same way; only
4081
4141
  // the 30-cap dump branch stays unwrapped for native terminal scrollback.
4082
- const mainReturn = _jsxs(KeybindingSetup, { children: [_jsx(AnimatedTerminalTitle, { isAnimating: titleIsAnimating, title: terminalTitle, disabled: titleDisabled, noPrefix: showStatusInTerminalTab }), _jsx(GlobalKeybindingHandlers, { ...globalKeybindingProps }), _jsx(VoiceKeybindingHandler, { voiceHandleKeyEvent: voice.handleKeyEvent, stripTrailing: voice.stripTrailing, resetAnchor: voice.resetAnchor, isActive: !toolJSX?.isLocalJSXCommand }), _jsx(CommandKeybindingHandlers, { onSubmit: onSubmit, isActive: !toolJSX?.isLocalJSXCommand }), _jsx(ScrollKeybindingHandler, { scrollRef: scrollRef, isActive: isFullscreenEnvEnabled() && (centeredModal != null || !focusedInputDialog || focusedInputDialog === 'tool-permission'), onScroll: centeredModal || toolPermissionOverlay || viewedAgentTask ? undefined : composedOnScroll }), feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? _jsx(MessageActionsKeybindings, { handlers: messageActionHandlers, isActive: cursor !== null }) : null, _jsx(CancelRequestHandler, { ...cancelRequestProps }), _jsx(MCPConnectionManager, { dynamicMcpConfig: dynamicMcpConfig, isStrictMcpConfig: strictMcpConfig, children: _jsx(FullscreenLayout, { scrollRef: scrollRef, overlay: toolPermissionOverlay, bottomFloat: feature('BUDDY') && companionVisible && !companionNarrow ? _jsx(CompanionFloatingBubble, {}) : undefined, modal: centeredModal, modalScrollRef: modalScrollRef, dividerYRef: dividerYRef, hidePill: !!viewedAgentTask, hideSticky: !!viewedTeammateTask, newMessageCount: unseenDivider?.count ?? 0, onPillClick: () => {
4083
- setCursor(null);
4084
- jumpToNew(scrollRef.current);
4085
- }, scrollable: _jsxs(_Fragment, { children: [_jsx(TeammateViewHeader, {}), _jsx(Messages, { messages: displayedMessages, tools: tools, commands: commands, verbose: verbose, toolJSX: toolJSX, toolUseConfirmQueue: toolUseConfirmQueue, inProgressToolUseIDs: viewedTeammateTask ? viewedTeammateTask.inProgressToolUseIDs ?? new Set() : inProgressToolUseIDs, isMessageSelectorVisible: isMessageSelectorVisible, conversationId: conversationId, screen: screen, streamingToolUses: streamingToolUses, showAllInTranscript: showAllInTranscript, agentDefinitions: agentDefinitions, onOpenRateLimitOptions: handleOpenRateLimitOptions, isLoading: isLoading, streamingText: isLoading && !viewedAgentTask ? visibleStreamingText : null, isBriefOnly: viewedAgentTask ? false : isBriefOnly, unseenDivider: viewedAgentTask ? undefined : unseenDivider, scrollRef: isFullscreenEnvEnabled() ? scrollRef : undefined, trackStickyPrompt: isFullscreenEnvEnabled() ? true : undefined, cursor: cursor, setCursor: setCursor, cursorNavRef: cursorNavRef }), _jsx(AwsAuthStatusBox, {}), !disabled && placeholderText && !centeredModal && _jsx(UserTextMessage, { param: {
4086
- text: placeholderText,
4087
- type: 'text'
4088
- }, addMargin: true, verbose: verbose }), toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && _jsx(Box, { flexDirection: "column", width: "100%", children: toolJSX.jsx }), "external" === 'ant' && _jsx(TungstenLiveMonitor, {}), feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && _jsx(WebBrowserPanelModule.WebBrowserPanel, {}) : null, _jsx(Box, { flexGrow: 1 }), showSpinner && _jsx(SpinnerWithVerb, { mode: streamMode, spinnerTip: spinnerTip, responseLengthRef: responseLengthRef, apiMetricsRef: apiMetricsRef, overrideMessage: spinnerMessage, spinnerSuffix: stopHookSpinnerSuffix, verbose: verbose, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, overrideColor: spinnerColor, overrideShimmerColor: spinnerShimmerColor, hasActiveTools: inProgressToolUseIDs.size > 0, leaderIsIdle: !isLoading }), !showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && _jsx(BriefIdleStatus, {}), isFullscreenEnvEnabled() && _jsx(PromptInputQueuedCommands, {})] }), bottom: _jsxs(Box, { flexDirection: feature('BUDDY') && companionNarrow ? 'column' : 'row', width: "100%", alignItems: feature('BUDDY') && companionNarrow ? undefined : 'flex-end', children: [feature('BUDDY') && companionNarrow && isFullscreenEnvEnabled() && companionVisible ? _jsx(CompanionSprite, {}) : null, _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [permissionStickyFooter, toolJSX?.isLocalJSXCommand && toolJSX.isImmediate && !toolJsxCentered && _jsx(Box, { flexDirection: "column", width: "100%", children: toolJSX.jsx }), !showSpinner && !toolJSX?.isLocalJSXCommand && showExpandedTodos && tasksV2 && tasksV2.length > 0 && _jsx(Box, { width: "100%", flexDirection: "column", children: _jsx(TaskListV2, { tasks: tasksV2, isStandalone: true }) }), focusedInputDialog === 'sandbox-permission' && _jsx(SandboxPermissionRequest, { hostPattern: sandboxPermissionRequestQueue[0].hostPattern, onUserResponse: (response) => {
4089
- const { allow, persistToSettings } = response;
4090
- const currentRequest = sandboxPermissionRequestQueue[0];
4091
- if (!currentRequest)
4092
- return;
4093
- const approvedHost = currentRequest.hostPattern.host;
4094
- if (persistToSettings) {
4095
- const update = {
4096
- type: 'addRules',
4097
- rules: [{
4098
- toolName: WEB_FETCH_TOOL_NAME,
4099
- ruleContent: `domain:${approvedHost}`
4100
- }],
4101
- behavior: (allow ? 'allow' : 'deny'),
4102
- destination: 'localSettings'
4103
- };
4104
- setAppState(prev => ({
4105
- ...prev,
4106
- toolPermissionContext: applyPermissionUpdate(prev.toolPermissionContext, update)
4107
- }));
4108
- persistPermissionUpdate(update);
4109
- // Immediately update sandbox in-memory config to prevent race conditions
4110
- // where pending requests slip through before settings change is detected
4111
- SandboxManager.refreshConfig();
4112
- }
4113
- // Resolve ALL pending requests for the same host (not just the first one)
4114
- // This handles the case where multiple parallel requests came in for the same domain
4115
- setSandboxPermissionRequestQueue(queue => {
4116
- queue.filter(item => item.hostPattern.host === approvedHost).forEach(item => item.resolvePromise(allow));
4117
- return queue.filter(item => item.hostPattern.host !== approvedHost);
4118
- });
4119
- // Clean up bridge subscriptions and cancel remote prompts
4120
- // for this host since the local user already responded.
4121
- const cleanups = sandboxBridgeCleanupRef.current.get(approvedHost);
4122
- if (cleanups) {
4123
- for (const fn of cleanups) {
4124
- fn();
4142
+ const mainReturn = _jsx(ModalStackContext.Provider, { value: {
4143
+ stack: modalStack,
4144
+ pushModal,
4145
+ replaceModal,
4146
+ popModal,
4147
+ clearModals
4148
+ }, children: _jsxs(KeybindingSetup, { children: [_jsx(AnimatedTerminalTitle, { isAnimating: titleIsAnimating, title: terminalTitle, disabled: titleDisabled, noPrefix: showStatusInTerminalTab }), _jsx(GlobalKeybindingHandlers, { ...globalKeybindingProps }), _jsx(VoiceKeybindingHandler, { voiceHandleKeyEvent: voice.handleKeyEvent, stripTrailing: voice.stripTrailing, resetAnchor: voice.resetAnchor, isActive: !toolJSX?.isLocalJSXCommand }), _jsx(CommandKeybindingHandlers, { onSubmit: onSubmit, isActive: !toolJSX?.isLocalJSXCommand }), _jsx(ScrollKeybindingHandler, { scrollRef: scrollRef, isActive: isFullscreenEnvEnabled() && (centeredModal != null || !focusedInputDialog || focusedInputDialog === 'tool-permission'), onScroll: centeredModal || toolPermissionOverlay || viewedAgentTask ? undefined : composedOnScroll }), feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? _jsx(MessageActionsKeybindings, { handlers: messageActionHandlers, isActive: cursor !== null }) : null, _jsx(CancelRequestHandler, { ...cancelRequestProps }), _jsx(MCPConnectionManager, { dynamicMcpConfig: dynamicMcpConfig, isStrictMcpConfig: strictMcpConfig, children: _jsx(FullscreenLayout, { scrollRef: scrollRef, overlay: toolPermissionOverlay, bottomFloat: feature('BUDDY') && companionVisible && !companionNarrow ? _jsx(CompanionFloatingBubble, {}) : undefined, modal: centeredModal, modalScrollRef: modalScrollRef, dividerYRef: dividerYRef, hidePill: !!viewedAgentTask, hideSticky: !!viewedTeammateTask, newMessageCount: unseenDivider?.count ?? 0, onPillClick: () => {
4149
+ setCursor(null);
4150
+ jumpToNew(scrollRef.current);
4151
+ }, scrollable: _jsxs(_Fragment, { children: [_jsx(TeammateViewHeader, {}), _jsx(Messages, { messages: displayedMessages, tools: tools, commands: commands, verbose: verbose, toolJSX: toolJSX, toolUseConfirmQueue: toolUseConfirmQueue, inProgressToolUseIDs: viewedTeammateTask ? viewedTeammateTask.inProgressToolUseIDs ?? new Set() : inProgressToolUseIDs, isMessageSelectorVisible: isMessageSelectorVisible, conversationId: conversationId, screen: screen, streamingToolUses: streamingToolUses, showAllInTranscript: showAllInTranscript, agentDefinitions: agentDefinitions, onOpenRateLimitOptions: handleOpenRateLimitOptions, isLoading: isLoading, streamingText: isLoading && !viewedAgentTask ? visibleStreamingText : null, isBriefOnly: viewedAgentTask ? false : isBriefOnly, unseenDivider: viewedAgentTask ? undefined : unseenDivider, scrollRef: isFullscreenEnvEnabled() ? scrollRef : undefined, trackStickyPrompt: isFullscreenEnvEnabled() ? true : undefined, cursor: cursor, setCursor: setCursor, cursorNavRef: cursorNavRef }), _jsx(AwsAuthStatusBox, {}), !disabled && placeholderText && !centeredModal && _jsx(UserTextMessage, { param: {
4152
+ text: placeholderText,
4153
+ type: 'text'
4154
+ }, addMargin: true, verbose: verbose }), toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && _jsx(Box, { flexDirection: "column", width: "100%", children: toolJSX.jsx }), "external" === 'ant' && _jsx(TungstenLiveMonitor, {}), feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && _jsx(WebBrowserPanelModule.WebBrowserPanel, {}) : null, _jsx(Box, { flexGrow: 1 }), showSpinner && _jsx(SpinnerWithVerb, { mode: streamMode, spinnerTip: spinnerTip, responseLengthRef: responseLengthRef, apiMetricsRef: apiMetricsRef, overrideMessage: spinnerMessage, spinnerSuffix: stopHookSpinnerSuffix, verbose: verbose, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, overrideColor: spinnerColor, overrideShimmerColor: spinnerShimmerColor, hasActiveTools: inProgressToolUseIDs.size > 0, leaderIsIdle: !isLoading }), !showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && _jsx(BriefIdleStatus, {}), isFullscreenEnvEnabled() && _jsx(PromptInputQueuedCommands, {})] }), bottom: _jsxs(Box, { flexDirection: feature('BUDDY') && companionNarrow ? 'column' : 'row', width: "100%", alignItems: feature('BUDDY') && companionNarrow ? undefined : 'flex-end', children: [feature('BUDDY') && companionNarrow && isFullscreenEnvEnabled() && companionVisible ? _jsx(CompanionSprite, {}) : null, _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [permissionStickyFooter, toolJSX?.isLocalJSXCommand && toolJSX.isImmediate && !toolJsxCentered && _jsx(Box, { flexDirection: "column", width: "100%", children: toolJSX.jsx }), !showSpinner && !toolJSX?.isLocalJSXCommand && showExpandedTodos && tasksV2 && tasksV2.length > 0 && _jsx(Box, { width: "100%", flexDirection: "column", children: _jsx(TaskListV2, { tasks: tasksV2, isStandalone: true }) }), focusedInputDialog === 'sandbox-permission' && _jsx(SandboxPermissionRequest, { hostPattern: sandboxPermissionRequestQueue[0].hostPattern, onUserResponse: (response) => {
4155
+ const { allow, persistToSettings } = response;
4156
+ const currentRequest = sandboxPermissionRequestQueue[0];
4157
+ if (!currentRequest)
4158
+ return;
4159
+ const approvedHost = currentRequest.hostPattern.host;
4160
+ if (persistToSettings) {
4161
+ const update = {
4162
+ type: 'addRules',
4163
+ rules: [{
4164
+ toolName: WEB_FETCH_TOOL_NAME,
4165
+ ruleContent: `domain:${approvedHost}`
4166
+ }],
4167
+ behavior: (allow ? 'allow' : 'deny'),
4168
+ destination: 'localSettings'
4169
+ };
4170
+ setAppState(prev => ({
4171
+ ...prev,
4172
+ toolPermissionContext: applyPermissionUpdate(prev.toolPermissionContext, update)
4173
+ }));
4174
+ persistPermissionUpdate(update);
4175
+ // Immediately update sandbox in-memory config to prevent race conditions
4176
+ // where pending requests slip through before settings change is detected
4177
+ SandboxManager.refreshConfig();
4125
4178
  }
4126
- sandboxBridgeCleanupRef.current.delete(approvedHost);
4127
- }
4128
- } }, sandboxPermissionRequestQueue[0].hostPattern.host), focusedInputDialog === 'prompt' && _jsx(PromptDialog, { title: promptQueue[0].title, toolInputSummary: promptQueue[0].toolInputSummary, request: promptQueue[0].request, onRespond: selectedKey => {
4129
- const item = promptQueue[0];
4130
- if (!item)
4131
- return;
4132
- item.resolve({
4133
- prompt_response: item.request.prompt,
4134
- selected: selectedKey
4135
- });
4136
- setPromptQueue(([, ...tail]) => tail);
4137
- }, onAbort: () => {
4138
- const item = promptQueue[0];
4139
- if (!item)
4140
- return;
4141
- item.reject(new Error('Prompt cancelled by user'));
4142
- setPromptQueue(([, ...tail]) => tail);
4143
- } }, promptQueue[0].request.prompt), pendingWorkerRequest && _jsx(WorkerPendingPermission, { toolName: pendingWorkerRequest.toolName, description: pendingWorkerRequest.description }), pendingSandboxRequest && _jsx(WorkerPendingPermission, { toolName: "Network Access", description: `Waiting for leader to approve network access to ${pendingSandboxRequest.host}` }), focusedInputDialog === 'worker-sandbox-permission' && _jsx(SandboxPermissionRequest, { hostPattern: {
4144
- host: workerSandboxPermissions.queue[0].host,
4145
- port: undefined
4146
- }, onUserResponse: (response) => {
4147
- const { allow, persistToSettings } = response;
4148
- const currentRequest = workerSandboxPermissions.queue[0];
4149
- if (!currentRequest)
4150
- return;
4151
- const approvedHost = currentRequest.host;
4152
- // Send response via mailbox to the worker
4153
- void sendSandboxPermissionResponseViaMailbox(currentRequest.workerName, currentRequest.requestId, approvedHost, allow, teamContext?.teamName);
4154
- if (persistToSettings && allow) {
4155
- const update = {
4156
- type: 'addRules',
4157
- rules: [{
4158
- toolName: WEB_FETCH_TOOL_NAME,
4159
- ruleContent: `domain:${approvedHost}`
4160
- }],
4161
- behavior: 'allow',
4162
- destination: 'localSettings'
4163
- };
4179
+ // Resolve ALL pending requests for the same host (not just the first one)
4180
+ // This handles the case where multiple parallel requests came in for the same domain
4181
+ setSandboxPermissionRequestQueue(queue => {
4182
+ queue.filter(item => item.hostPattern.host === approvedHost).forEach(item => item.resolvePromise(allow));
4183
+ return queue.filter(item => item.hostPattern.host !== approvedHost);
4184
+ });
4185
+ // Clean up bridge subscriptions and cancel remote prompts
4186
+ // for this host since the local user already responded.
4187
+ const cleanups = sandboxBridgeCleanupRef.current.get(approvedHost);
4188
+ if (cleanups) {
4189
+ for (const fn of cleanups) {
4190
+ fn();
4191
+ }
4192
+ sandboxBridgeCleanupRef.current.delete(approvedHost);
4193
+ }
4194
+ } }, sandboxPermissionRequestQueue[0].hostPattern.host), focusedInputDialog === 'prompt' && _jsx(PromptDialog, { title: promptQueue[0].title, toolInputSummary: promptQueue[0].toolInputSummary, request: promptQueue[0].request, onRespond: selectedKey => {
4195
+ const item = promptQueue[0];
4196
+ if (!item)
4197
+ return;
4198
+ item.resolve({
4199
+ prompt_response: item.request.prompt,
4200
+ selected: selectedKey
4201
+ });
4202
+ setPromptQueue(([, ...tail]) => tail);
4203
+ }, onAbort: () => {
4204
+ const item = promptQueue[0];
4205
+ if (!item)
4206
+ return;
4207
+ item.reject(new Error('Prompt cancelled by user'));
4208
+ setPromptQueue(([, ...tail]) => tail);
4209
+ } }, promptQueue[0].request.prompt), pendingWorkerRequest && _jsx(WorkerPendingPermission, { toolName: pendingWorkerRequest.toolName, description: pendingWorkerRequest.description }), pendingSandboxRequest && _jsx(WorkerPendingPermission, { toolName: "Network Access", description: `Waiting for leader to approve network access to ${pendingSandboxRequest.host}` }), focusedInputDialog === 'worker-sandbox-permission' && _jsx(SandboxPermissionRequest, { hostPattern: {
4210
+ host: workerSandboxPermissions.queue[0].host,
4211
+ port: undefined
4212
+ }, onUserResponse: (response) => {
4213
+ const { allow, persistToSettings } = response;
4214
+ const currentRequest = workerSandboxPermissions.queue[0];
4215
+ if (!currentRequest)
4216
+ return;
4217
+ const approvedHost = currentRequest.host;
4218
+ // Send response via mailbox to the worker
4219
+ void sendSandboxPermissionResponseViaMailbox(currentRequest.workerName, currentRequest.requestId, approvedHost, allow, teamContext?.teamName);
4220
+ if (persistToSettings && allow) {
4221
+ const update = {
4222
+ type: 'addRules',
4223
+ rules: [{
4224
+ toolName: WEB_FETCH_TOOL_NAME,
4225
+ ruleContent: `domain:${approvedHost}`
4226
+ }],
4227
+ behavior: 'allow',
4228
+ destination: 'localSettings'
4229
+ };
4230
+ setAppState(prev => ({
4231
+ ...prev,
4232
+ toolPermissionContext: applyPermissionUpdate(prev.toolPermissionContext, update)
4233
+ }));
4234
+ persistPermissionUpdate(update);
4235
+ SandboxManager.refreshConfig();
4236
+ }
4237
+ // Remove from queue
4164
4238
  setAppState(prev => ({
4165
4239
  ...prev,
4166
- toolPermissionContext: applyPermissionUpdate(prev.toolPermissionContext, update)
4240
+ workerSandboxPermissions: {
4241
+ ...prev.workerSandboxPermissions,
4242
+ queue: prev.workerSandboxPermissions.queue.slice(1)
4243
+ }
4167
4244
  }));
4168
- persistPermissionUpdate(update);
4169
- SandboxManager.refreshConfig();
4170
- }
4171
- // Remove from queue
4172
- setAppState(prev => ({
4173
- ...prev,
4174
- workerSandboxPermissions: {
4175
- ...prev.workerSandboxPermissions,
4176
- queue: prev.workerSandboxPermissions.queue.slice(1)
4245
+ } }, workerSandboxPermissions.queue[0].requestId), focusedInputDialog === 'elicitation' && _jsx(ElicitationDialog, { event: elicitation.queue[0], onResponse: (action, content) => {
4246
+ const currentRequest = elicitation.queue[0];
4247
+ if (!currentRequest)
4248
+ return;
4249
+ // Call respond callback to resolve Promise
4250
+ currentRequest.respond({
4251
+ action,
4252
+ content
4253
+ });
4254
+ // For URL accept, keep in queue for phase 2
4255
+ const isUrlAccept = currentRequest.params.mode === 'url' && action === 'accept';
4256
+ if (!isUrlAccept) {
4257
+ setAppState(prev => ({
4258
+ ...prev,
4259
+ elicitation: {
4260
+ queue: prev.elicitation.queue.slice(1)
4261
+ }
4262
+ }));
4177
4263
  }
4178
- }));
4179
- } }, workerSandboxPermissions.queue[0].requestId), focusedInputDialog === 'elicitation' && _jsx(ElicitationDialog, { event: elicitation.queue[0], onResponse: (action, content) => {
4180
- const currentRequest = elicitation.queue[0];
4181
- if (!currentRequest)
4182
- return;
4183
- // Call respond callback to resolve Promise
4184
- currentRequest.respond({
4185
- action,
4186
- content
4187
- });
4188
- // For URL accept, keep in queue for phase 2
4189
- const isUrlAccept = currentRequest.params.mode === 'url' && action === 'accept';
4190
- if (!isUrlAccept) {
4264
+ }, onWaitingDismiss: action => {
4265
+ const currentRequest = elicitation.queue[0];
4266
+ // Remove from queue
4191
4267
  setAppState(prev => ({
4192
4268
  ...prev,
4193
4269
  elicitation: {
4194
4270
  queue: prev.elicitation.queue.slice(1)
4195
4271
  }
4196
4272
  }));
4197
- }
4198
- }, onWaitingDismiss: action => {
4199
- const currentRequest = elicitation.queue[0];
4200
- // Remove from queue
4201
- setAppState(prev => ({
4202
- ...prev,
4203
- elicitation: {
4204
- queue: prev.elicitation.queue.slice(1)
4273
+ currentRequest?.onWaitingDismiss?.(action);
4274
+ } }, elicitation.queue[0].serverName + ':' + String(elicitation.queue[0].requestId)), focusedInputDialog === 'cost' && _jsx(CostThresholdDialog, { onDone: () => {
4275
+ setShowCostDialog(false);
4276
+ setHaveShownCostDialog(true);
4277
+ saveGlobalConfig(current => ({
4278
+ ...current,
4279
+ hasAcknowledgedCostThreshold: true
4280
+ }));
4281
+ logEvent('tengu_cost_threshold_acknowledged', {});
4282
+ } }), focusedInputDialog === 'idle-return' && idleReturnPending && _jsx(IdleReturnDialog, { idleMinutes: idleReturnPending.idleMinutes, totalInputTokens: getTotalInputTokens(), onDone: async (action) => {
4283
+ const pending = idleReturnPending;
4284
+ setIdleReturnPending(null);
4285
+ logEvent('tengu_idle_return_action', {
4286
+ action: action,
4287
+ idleMinutes: Math.round(pending.idleMinutes),
4288
+ messageCount: messagesRef.current.length,
4289
+ totalInputTokens: getTotalInputTokens()
4290
+ });
4291
+ if (action === 'dismiss') {
4292
+ setInputValue(pending.input);
4293
+ return;
4294
+ }
4295
+ if (action === 'never') {
4296
+ saveGlobalConfig(current => {
4297
+ if (current.idleReturnDismissed)
4298
+ return current;
4299
+ return {
4300
+ ...current,
4301
+ idleReturnDismissed: true
4302
+ };
4303
+ });
4304
+ }
4305
+ if (action === 'clear') {
4306
+ const { clearConversation } = await import('../commands/clear/conversation.js');
4307
+ await clearConversation({
4308
+ setMessages,
4309
+ readFileState: readFileState.current,
4310
+ discoveredSkillNames: discoveredSkillNamesRef.current,
4311
+ loadedNestedMemoryPaths: loadedNestedMemoryPathsRef.current,
4312
+ getAppState: () => store.getState(),
4313
+ setAppState,
4314
+ setConversationId
4315
+ });
4316
+ haikuTitleAttemptedRef.current = false;
4317
+ setHaikuTitle(undefined);
4318
+ bashTools.current.clear();
4319
+ bashToolsProcessedIdx.current = 0;
4320
+ }
4321
+ skipIdleCheckRef.current = true;
4322
+ void onSubmitRef.current(pending.input, {
4323
+ setCursorOffset: () => { },
4324
+ clearBuffer: () => { },
4325
+ resetHistory: () => { }
4326
+ });
4327
+ } }), focusedInputDialog === 'ide-onboarding' && _jsx(IdeOnboardingDialog, { onDone: () => setShowIdeOnboarding(false), installationStatus: ideInstallationStatus }), "external" === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && _jsx(AntModelSwitchCallout, { onDone: (selection, modelAlias) => {
4328
+ setShowModelSwitchCallout(false);
4329
+ if (selection === 'switch' && modelAlias) {
4330
+ setAppState(prev => ({
4331
+ ...prev,
4332
+ mainLoopModel: modelAlias,
4333
+ mainLoopModelForSession: null
4334
+ }));
4205
4335
  }
4206
- }));
4207
- currentRequest?.onWaitingDismiss?.(action);
4208
- } }, elicitation.queue[0].serverName + ':' + String(elicitation.queue[0].requestId)), focusedInputDialog === 'cost' && _jsx(CostThresholdDialog, { onDone: () => {
4209
- setShowCostDialog(false);
4210
- setHaveShownCostDialog(true);
4211
- saveGlobalConfig(current => ({
4212
- ...current,
4213
- hasAcknowledgedCostThreshold: true
4214
- }));
4215
- logEvent('tengu_cost_threshold_acknowledged', {});
4216
- } }), focusedInputDialog === 'idle-return' && idleReturnPending && _jsx(IdleReturnDialog, { idleMinutes: idleReturnPending.idleMinutes, totalInputTokens: getTotalInputTokens(), onDone: async (action) => {
4217
- const pending = idleReturnPending;
4218
- setIdleReturnPending(null);
4219
- logEvent('tengu_idle_return_action', {
4220
- action: action,
4221
- idleMinutes: Math.round(pending.idleMinutes),
4222
- messageCount: messagesRef.current.length,
4223
- totalInputTokens: getTotalInputTokens()
4224
- });
4225
- if (action === 'dismiss') {
4226
- setInputValue(pending.input);
4227
- return;
4228
- }
4229
- if (action === 'never') {
4230
- saveGlobalConfig(current => {
4231
- if (current.idleReturnDismissed)
4232
- return current;
4336
+ } }), "external" === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && _jsx(UndercoverAutoCallout, { onDone: () => setShowUndercoverCallout(false) }), focusedInputDialog === 'effort-callout' && _jsx(EffortCallout, { model: mainLoopModel, onDone: selection => {
4337
+ setShowEffortCallout(false);
4338
+ if (selection !== 'dismiss') {
4339
+ setAppState(prev => ({
4340
+ ...prev,
4341
+ effortValue: selection
4342
+ }));
4343
+ }
4344
+ } }), focusedInputDialog === 'remote-callout' && _jsx(RemoteCallout, { onDone: selection => {
4345
+ setAppState(prev => {
4346
+ if (!prev.showRemoteCallout)
4347
+ return prev;
4233
4348
  return {
4234
- ...current,
4235
- idleReturnDismissed: true
4349
+ ...prev,
4350
+ showRemoteCallout: false,
4351
+ ...(selection === 'enable' && {
4352
+ replBridgeEnabled: true,
4353
+ replBridgeExplicit: true,
4354
+ replBridgeOutboundOnly: false
4355
+ })
4236
4356
  };
4237
4357
  });
4238
- }
4239
- if (action === 'clear') {
4240
- const { clearConversation } = await import('../commands/clear/conversation.js');
4241
- await clearConversation({
4242
- setMessages,
4243
- readFileState: readFileState.current,
4244
- discoveredSkillNames: discoveredSkillNamesRef.current,
4245
- loadedNestedMemoryPaths: loadedNestedMemoryPathsRef.current,
4246
- getAppState: () => store.getState(),
4247
- setAppState,
4248
- setConversationId
4249
- });
4250
- haikuTitleAttemptedRef.current = false;
4251
- setHaikuTitle(undefined);
4252
- bashTools.current.clear();
4253
- bashToolsProcessedIdx.current = 0;
4254
- }
4255
- skipIdleCheckRef.current = true;
4256
- void onSubmitRef.current(pending.input, {
4257
- setCursorOffset: () => { },
4258
- clearBuffer: () => { },
4259
- resetHistory: () => { }
4260
- });
4261
- } }), focusedInputDialog === 'ide-onboarding' && _jsx(IdeOnboardingDialog, { onDone: () => setShowIdeOnboarding(false), installationStatus: ideInstallationStatus }), "external" === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && _jsx(AntModelSwitchCallout, { onDone: (selection, modelAlias) => {
4262
- setShowModelSwitchCallout(false);
4263
- if (selection === 'switch' && modelAlias) {
4264
- setAppState(prev => ({
4358
+ } }), exitFlow, focusedInputDialog === 'plugin-hint' && hintRecommendation && _jsx(PluginHintMenu, { pluginName: hintRecommendation.pluginName, pluginDescription: hintRecommendation.pluginDescription, marketplaceName: hintRecommendation.marketplaceName, sourceCommand: hintRecommendation.sourceCommand, onResponse: handleHintResponse }), focusedInputDialog === 'lsp-recommendation' && lspRecommendation && _jsx(LspRecommendationMenu, { pluginName: lspRecommendation.pluginName, pluginDescription: lspRecommendation.pluginDescription, fileExtension: lspRecommendation.fileExtension, onResponse: handleLspResponse }), focusedInputDialog === 'desktop-upsell' && _jsx(DesktopUpsellStartup, { onDone: () => setShowDesktopUpsellStartup(false) }), feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-choice' && ultraplanPendingChoice && _jsx(UltraplanChoiceDialog, { plan: ultraplanPendingChoice.plan, sessionId: ultraplanPendingChoice.sessionId, taskId: ultraplanPendingChoice.taskId, setMessages: setMessages, readFileState: readFileState.current, getAppState: () => store.getState(), setConversationId: setConversationId }) : null, feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-launch' && ultraplanLaunchPending && _jsx(UltraplanLaunchDialog, { onChoice: (choice, opts) => {
4359
+ const blurb = ultraplanLaunchPending.blurb;
4360
+ setAppState(prev => prev.ultraplanLaunchPending ? {
4265
4361
  ...prev,
4266
- mainLoopModel: modelAlias,
4267
- mainLoopModelForSession: null
4268
- }));
4269
- }
4270
- } }), "external" === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && _jsx(UndercoverAutoCallout, { onDone: () => setShowUndercoverCallout(false) }), focusedInputDialog === 'effort-callout' && _jsx(EffortCallout, { model: mainLoopModel, onDone: selection => {
4271
- setShowEffortCallout(false);
4272
- if (selection !== 'dismiss') {
4273
- setAppState(prev => ({
4274
- ...prev,
4275
- effortValue: selection
4276
- }));
4277
- }
4278
- } }), focusedInputDialog === 'remote-callout' && _jsx(RemoteCallout, { onDone: selection => {
4279
- setAppState(prev => {
4280
- if (!prev.showRemoteCallout)
4281
- return prev;
4282
- return {
4283
- ...prev,
4284
- showRemoteCallout: false,
4285
- ...(selection === 'enable' && {
4286
- replBridgeEnabled: true,
4287
- replBridgeExplicit: true,
4288
- replBridgeOutboundOnly: false
4289
- })
4362
+ ultraplanLaunchPending: undefined
4363
+ } : prev);
4364
+ if (choice === 'cancel')
4365
+ return;
4366
+ // Command's onDone used display:'skip', so add the
4367
+ // echo here — gives immediate feedback before the
4368
+ // ~5s teleportToRemote resolves.
4369
+ setMessages(prev => [...prev, createCommandInputMessage(formatCommandInputTags('ultraplan', blurb))]);
4370
+ const appendStdout = (msg) => setMessages(prev => [...prev, createCommandInputMessage(`<${LOCAL_COMMAND_STDOUT_TAG}>${escapeXml(msg)}</${LOCAL_COMMAND_STDOUT_TAG}>`)]);
4371
+ // Defer the second message if a query is mid-turn
4372
+ // so it lands after the assistant reply, not
4373
+ // between the user's prompt and the reply.
4374
+ const appendWhenIdle = (msg) => {
4375
+ if (!queryGuard.isActive) {
4376
+ appendStdout(msg);
4377
+ return;
4378
+ }
4379
+ const unsub = queryGuard.subscribe(() => {
4380
+ if (queryGuard.isActive)
4381
+ return;
4382
+ unsub();
4383
+ // Skip if the user stopped ultraplan while we
4384
+ // were waiting — avoids a stale "Monitoring
4385
+ // <url>" message for a session that's gone.
4386
+ if (!store.getState().ultraplanSessionUrl)
4387
+ return;
4388
+ appendStdout(msg);
4389
+ });
4290
4390
  };
4291
- });
4292
- } }), exitFlow, focusedInputDialog === 'plugin-hint' && hintRecommendation && _jsx(PluginHintMenu, { pluginName: hintRecommendation.pluginName, pluginDescription: hintRecommendation.pluginDescription, marketplaceName: hintRecommendation.marketplaceName, sourceCommand: hintRecommendation.sourceCommand, onResponse: handleHintResponse }), focusedInputDialog === 'lsp-recommendation' && lspRecommendation && _jsx(LspRecommendationMenu, { pluginName: lspRecommendation.pluginName, pluginDescription: lspRecommendation.pluginDescription, fileExtension: lspRecommendation.fileExtension, onResponse: handleLspResponse }), focusedInputDialog === 'desktop-upsell' && _jsx(DesktopUpsellStartup, { onDone: () => setShowDesktopUpsellStartup(false) }), feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-choice' && ultraplanPendingChoice && _jsx(UltraplanChoiceDialog, { plan: ultraplanPendingChoice.plan, sessionId: ultraplanPendingChoice.sessionId, taskId: ultraplanPendingChoice.taskId, setMessages: setMessages, readFileState: readFileState.current, getAppState: () => store.getState(), setConversationId: setConversationId }) : null, feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-launch' && ultraplanLaunchPending && _jsx(UltraplanLaunchDialog, { onChoice: (choice, opts) => {
4293
- const blurb = ultraplanLaunchPending.blurb;
4294
- setAppState(prev => prev.ultraplanLaunchPending ? {
4295
- ...prev,
4296
- ultraplanLaunchPending: undefined
4297
- } : prev);
4298
- if (choice === 'cancel')
4299
- return;
4300
- // Command's onDone used display:'skip', so add the
4301
- // echo here gives immediate feedback before the
4302
- // ~5s teleportToRemote resolves.
4303
- setMessages(prev => [...prev, createCommandInputMessage(formatCommandInputTags('ultraplan', blurb))]);
4304
- const appendStdout = (msg) => setMessages(prev => [...prev, createCommandInputMessage(`<${LOCAL_COMMAND_STDOUT_TAG}>${escapeXml(msg)}</${LOCAL_COMMAND_STDOUT_TAG}>`)]);
4305
- // Defer the second message if a query is mid-turn
4306
- // so it lands after the assistant reply, not
4307
- // between the user's prompt and the reply.
4308
- const appendWhenIdle = (msg) => {
4309
- if (!queryGuard.isActive) {
4310
- appendStdout(msg);
4391
+ void launchUltraplan({
4392
+ blurb,
4393
+ getAppState: () => store.getState(),
4394
+ setAppState,
4395
+ signal: createAbortController().signal,
4396
+ disconnectedBridge: opts?.disconnectedBridge,
4397
+ onSessionReady: appendWhenIdle
4398
+ }).then(appendStdout).catch(logError);
4399
+ } }) : null, mrRender(), !toolJSX?.shouldHidePromptInput && !focusedInputDialog && !isExiting && !disabled && !cursor && _jsxs(_Fragment, { children: [autoRunIssueReason && _jsx(AutoRunIssueNotification, { onRun: handleAutoRunIssue, onCancel: handleCancelAutoRunIssue, reason: getAutoRunIssueReasonText(autoRunIssueReason) }), postCompactSurvey.state !== 'closed' ? _jsx(FeedbackSurvey, { state: postCompactSurvey.state, lastResponse: postCompactSurvey.lastResponse, handleSelect: postCompactSurvey.handleSelect, inputValue: inputValue, setInputValue: setInputValue, onRequestFeedback: handleSurveyRequestFeedback }) : memorySurvey.state !== 'closed' ? _jsx(FeedbackSurvey, { state: memorySurvey.state, lastResponse: memorySurvey.lastResponse, handleSelect: memorySurvey.handleSelect, handleTranscriptSelect: memorySurvey.handleTranscriptSelect, inputValue: inputValue, setInputValue: setInputValue, onRequestFeedback: handleSurveyRequestFeedback, message: "How well did Claude use its memory? (optional)" }) : _jsx(FeedbackSurvey, { state: feedbackSurvey.state, lastResponse: feedbackSurvey.lastResponse, handleSelect: feedbackSurvey.handleSelect, handleTranscriptSelect: feedbackSurvey.handleTranscriptSelect, inputValue: inputValue, setInputValue: setInputValue, onRequestFeedback: didAutoRunIssueRef.current ? undefined : handleSurveyRequestFeedback }), frustrationDetection.state !== 'closed' && _jsx(FeedbackSurvey, { state: frustrationDetection.state, lastResponse: null, handleSelect: () => { }, handleTranscriptSelect: frustrationDetection.handleTranscriptSelect, inputValue: inputValue, setInputValue: setInputValue }), "external" === 'ant' && skillImprovementSurvey.suggestion && _jsx(SkillImprovementSurvey, { isOpen: skillImprovementSurvey.isOpen, skillName: skillImprovementSurvey.suggestion.skillName, updates: skillImprovementSurvey.suggestion.updates, handleSelect: skillImprovementSurvey.handleSelect, inputValue: inputValue, setInputValue: setInputValue }), showIssueFlagBanner && _jsx(IssueFlagBanner, {}), _jsx(PromptInput, { debug: debug, ideSelection: ideSelection, hasSuppressedDialogs: !!hasSuppressedDialogs, isLocalJSXCommandActive: isShowingLocalJSXCommand, getToolUseContext: getToolUseContext, toolPermissionContext: toolPermissionContext, setToolPermissionContext: setToolPermissionContext, apiKeyStatus: apiKeyStatus, commands: commands, agents: agentDefinitions.activeAgents, isLoading: isLoading, onExit: handleExit, verbose: verbose, messages: messages, onAutoUpdaterResult: setAutoUpdaterResult, autoUpdaterResult: autoUpdaterResult, input: inputValue, onInputChange: setInputValue, mode: inputMode, onModeChange: setInputMode, stashedPrompt: stashedPrompt, setStashedPrompt: setStashedPrompt, submitCount: submitCount, onShowMessageSelector: handleShowMessageSelector, onMessageActionsEnter:
4400
+ // Works during isLoading edit cancels first; uuid selection survives appends.
4401
+ feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? enterMessageActions : undefined, mcpClients: mcpClients, pastedContents: pastedContents, setPastedContents: setPastedContents, vimMode: vimMode, setVimMode: setVimMode, showBashesDialog: showBashesDialog, setShowBashesDialog: setShowBashesDialog, onSubmit: onSubmit, onAgentSubmit: onAgentSubmit, isSearchingHistory: isSearchingHistory, setIsSearchingHistory: setIsSearchingHistory, helpOpen: isHelpOpen, setHelpOpen: setIsHelpOpen, insertTextRef: insertTextRef, voiceInterimRange: voice.interimRange }), _jsx(SessionBackgroundHint, { onBackgroundSession: handleBackgroundSession, isLoading: isLoading })] }), cursor &&
4402
+ _jsx(MessageActionsBar, { cursor: cursor }), focusedInputDialog === 'message-selector' && _jsx(MessageSelector, { messages: messages, preselectedMessage: messageSelectorPreselect, onPreRestore: onCancel, onRestoreCode: async (message) => {
4403
+ await fileHistoryRewind((updater) => {
4404
+ setAppState(prev => ({
4405
+ ...prev,
4406
+ fileHistory: updater(prev.fileHistory)
4407
+ }));
4408
+ }, message.uuid);
4409
+ }, onSummarize: async (message, feedback, direction = 'from') => {
4410
+ // Project snipped messages so the compact model
4411
+ // doesn't summarize content that was intentionally removed.
4412
+ const compactMessages = getMessagesAfterCompactBoundary(messages);
4413
+ const messageIndex = compactMessages.indexOf(message);
4414
+ if (messageIndex === -1) {
4415
+ // Selected a snipped or pre-compact message that the
4416
+ // selector still shows (REPL keeps full history for
4417
+ // scrollback). Surface why nothing happened instead
4418
+ // of silently no-oping.
4419
+ setMessages(prev => [...prev, createSystemMessage('That message is no longer in the active context (snipped or pre-compact). Choose a more recent message.', 'warning')]);
4311
4420
  return;
4312
4421
  }
4313
- const unsub = queryGuard.subscribe(() => {
4314
- if (queryGuard.isActive)
4315
- return;
4316
- unsub();
4317
- // Skip if the user stopped ultraplan while we
4318
- // were waiting — avoids a stale "Monitoring
4319
- // <url>" message for a session that's gone.
4320
- if (!store.getState().ultraplanSessionUrl)
4321
- return;
4322
- appendStdout(msg);
4422
+ const newAbortController = createAbortController();
4423
+ const context = getToolUseContext(compactMessages, [], newAbortController, mainLoopModel);
4424
+ const appState = context.getAppState();
4425
+ const defaultSysPrompt = await getSystemPrompt(context.options.tools, context.options.mainLoopModel, Array.from(appState.toolPermissionContext.additionalWorkingDirectories.keys()), context.options.mcpClients);
4426
+ const systemPrompt = buildEffectiveSystemPrompt({
4427
+ mainThreadAgentDefinition: undefined,
4428
+ toolUseContext: context,
4429
+ customSystemPrompt: context.options.customSystemPrompt,
4430
+ defaultSystemPrompt: defaultSysPrompt,
4431
+ appendSystemPrompt: context.options.appendSystemPrompt
4323
4432
  });
4324
- };
4325
- void launchUltraplan({
4326
- blurb,
4327
- getAppState: () => store.getState(),
4328
- setAppState,
4329
- signal: createAbortController().signal,
4330
- disconnectedBridge: opts?.disconnectedBridge,
4331
- onSessionReady: appendWhenIdle
4332
- }).then(appendStdout).catch(logError);
4333
- } }) : null, mrRender(), !toolJSX?.shouldHidePromptInput && !focusedInputDialog && !isExiting && !disabled && !cursor && _jsxs(_Fragment, { children: [autoRunIssueReason && _jsx(AutoRunIssueNotification, { onRun: handleAutoRunIssue, onCancel: handleCancelAutoRunIssue, reason: getAutoRunIssueReasonText(autoRunIssueReason) }), postCompactSurvey.state !== 'closed' ? _jsx(FeedbackSurvey, { state: postCompactSurvey.state, lastResponse: postCompactSurvey.lastResponse, handleSelect: postCompactSurvey.handleSelect, inputValue: inputValue, setInputValue: setInputValue, onRequestFeedback: handleSurveyRequestFeedback }) : memorySurvey.state !== 'closed' ? _jsx(FeedbackSurvey, { state: memorySurvey.state, lastResponse: memorySurvey.lastResponse, handleSelect: memorySurvey.handleSelect, handleTranscriptSelect: memorySurvey.handleTranscriptSelect, inputValue: inputValue, setInputValue: setInputValue, onRequestFeedback: handleSurveyRequestFeedback, message: "How well did Claude use its memory? (optional)" }) : _jsx(FeedbackSurvey, { state: feedbackSurvey.state, lastResponse: feedbackSurvey.lastResponse, handleSelect: feedbackSurvey.handleSelect, handleTranscriptSelect: feedbackSurvey.handleTranscriptSelect, inputValue: inputValue, setInputValue: setInputValue, onRequestFeedback: didAutoRunIssueRef.current ? undefined : handleSurveyRequestFeedback }), frustrationDetection.state !== 'closed' && _jsx(FeedbackSurvey, { state: frustrationDetection.state, lastResponse: null, handleSelect: () => { }, handleTranscriptSelect: frustrationDetection.handleTranscriptSelect, inputValue: inputValue, setInputValue: setInputValue }), "external" === 'ant' && skillImprovementSurvey.suggestion && _jsx(SkillImprovementSurvey, { isOpen: skillImprovementSurvey.isOpen, skillName: skillImprovementSurvey.suggestion.skillName, updates: skillImprovementSurvey.suggestion.updates, handleSelect: skillImprovementSurvey.handleSelect, inputValue: inputValue, setInputValue: setInputValue }), showIssueFlagBanner && _jsx(IssueFlagBanner, {}), _jsx(PromptInput, { debug: debug, ideSelection: ideSelection, hasSuppressedDialogs: !!hasSuppressedDialogs, isLocalJSXCommandActive: isShowingLocalJSXCommand, getToolUseContext: getToolUseContext, toolPermissionContext: toolPermissionContext, setToolPermissionContext: setToolPermissionContext, apiKeyStatus: apiKeyStatus, commands: commands, agents: agentDefinitions.activeAgents, isLoading: isLoading, onExit: handleExit, verbose: verbose, messages: messages, onAutoUpdaterResult: setAutoUpdaterResult, autoUpdaterResult: autoUpdaterResult, input: inputValue, onInputChange: setInputValue, mode: inputMode, onModeChange: setInputMode, stashedPrompt: stashedPrompt, setStashedPrompt: setStashedPrompt, submitCount: submitCount, onShowMessageSelector: handleShowMessageSelector, onMessageActionsEnter:
4334
- // Works during isLoading edit cancels first; uuid selection survives appends.
4335
- feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? enterMessageActions : undefined, mcpClients: mcpClients, pastedContents: pastedContents, setPastedContents: setPastedContents, vimMode: vimMode, setVimMode: setVimMode, showBashesDialog: showBashesDialog, setShowBashesDialog: setShowBashesDialog, onSubmit: onSubmit, onAgentSubmit: onAgentSubmit, isSearchingHistory: isSearchingHistory, setIsSearchingHistory: setIsSearchingHistory, helpOpen: isHelpOpen, setHelpOpen: setIsHelpOpen, insertTextRef: insertTextRef, voiceInterimRange: voice.interimRange }), _jsx(SessionBackgroundHint, { onBackgroundSession: handleBackgroundSession, isLoading: isLoading })] }), cursor &&
4336
- _jsx(MessageActionsBar, { cursor: cursor }), focusedInputDialog === 'message-selector' && _jsx(MessageSelector, { messages: messages, preselectedMessage: messageSelectorPreselect, onPreRestore: onCancel, onRestoreCode: async (message) => {
4337
- await fileHistoryRewind((updater) => {
4338
- setAppState(prev => ({
4339
- ...prev,
4340
- fileHistory: updater(prev.fileHistory)
4341
- }));
4342
- }, message.uuid);
4343
- }, onSummarize: async (message, feedback, direction = 'from') => {
4344
- // Project snipped messages so the compact model
4345
- // doesn't summarize content that was intentionally removed.
4346
- const compactMessages = getMessagesAfterCompactBoundary(messages);
4347
- const messageIndex = compactMessages.indexOf(message);
4348
- if (messageIndex === -1) {
4349
- // Selected a snipped or pre-compact message that the
4350
- // selector still shows (REPL keeps full history for
4351
- // scrollback). Surface why nothing happened instead
4352
- // of silently no-oping.
4353
- setMessages(prev => [...prev, createSystemMessage('That message is no longer in the active context (snipped or pre-compact). Choose a more recent message.', 'warning')]);
4354
- return;
4355
- }
4356
- const newAbortController = createAbortController();
4357
- const context = getToolUseContext(compactMessages, [], newAbortController, mainLoopModel);
4358
- const appState = context.getAppState();
4359
- const defaultSysPrompt = await getSystemPrompt(context.options.tools, context.options.mainLoopModel, Array.from(appState.toolPermissionContext.additionalWorkingDirectories.keys()), context.options.mcpClients);
4360
- const systemPrompt = buildEffectiveSystemPrompt({
4361
- mainThreadAgentDefinition: undefined,
4362
- toolUseContext: context,
4363
- customSystemPrompt: context.options.customSystemPrompt,
4364
- defaultSystemPrompt: defaultSysPrompt,
4365
- appendSystemPrompt: context.options.appendSystemPrompt
4366
- });
4367
- const [userContext, systemContext] = await Promise.all([getUserContext(), getSystemContext()]);
4368
- const result = await partialCompactConversation(compactMessages, messageIndex, context, {
4369
- systemPrompt,
4370
- userContext,
4371
- systemContext,
4372
- toolUseContext: context,
4373
- forkContextMessages: compactMessages
4374
- }, feedback, direction);
4375
- const kept = result.messagesToKeep ?? [];
4376
- const ordered = direction === 'up_to' ? [...result.summaryMessages, ...kept] : [...kept, ...result.summaryMessages];
4377
- const postCompact = [result.boundaryMarker, ...ordered, ...result.attachments, ...result.hookResults];
4378
- // Fullscreen 'from' keeps scrollback; 'up_to' must not
4379
- // (old[0] unchanged + grown array means incremental
4380
- // useLogMessages path, so boundary never persisted).
4381
- // Find by uuid since old is raw REPL history and snipped
4382
- // entries can shift the projected messageIndex.
4383
- if (isFullscreenEnvEnabled() && direction === 'from') {
4384
- setMessages(old => {
4385
- const rawIdx = old.findIndex(m => m.uuid === message.uuid);
4386
- return [...old.slice(0, rawIdx === -1 ? 0 : rawIdx), ...postCompact];
4387
- });
4388
- }
4389
- else {
4390
- setMessages(postCompact);
4391
- }
4392
- // Partial compact bypasses handleMessageFromStream — clear
4393
- // the context-blocked flag so proactive ticks resume.
4394
- if (feature('PROACTIVE') || feature('KAIROS')) {
4395
- proactiveModule?.setContextBlocked(false);
4396
- }
4397
- setConversationId(randomUUID());
4398
- runPostCompactCleanup(context.options.querySource);
4399
- if (direction === 'from') {
4400
- const r = textForResubmit(message);
4401
- if (r) {
4402
- setInputValue(r.text);
4403
- setInputMode(r.mode);
4433
+ const [userContext, systemContext] = await Promise.all([getUserContext(), getSystemContext()]);
4434
+ const result = await partialCompactConversation(compactMessages, messageIndex, context, {
4435
+ systemPrompt,
4436
+ userContext,
4437
+ systemContext,
4438
+ toolUseContext: context,
4439
+ forkContextMessages: compactMessages
4440
+ }, feedback, direction);
4441
+ const kept = result.messagesToKeep ?? [];
4442
+ const ordered = direction === 'up_to' ? [...result.summaryMessages, ...kept] : [...kept, ...result.summaryMessages];
4443
+ const postCompact = [result.boundaryMarker, ...ordered, ...result.attachments, ...result.hookResults];
4444
+ // Fullscreen 'from' keeps scrollback; 'up_to' must not
4445
+ // (old[0] unchanged + grown array means incremental
4446
+ // useLogMessages path, so boundary never persisted).
4447
+ // Find by uuid since old is raw REPL history and snipped
4448
+ // entries can shift the projected messageIndex.
4449
+ if (isFullscreenEnvEnabled() && direction === 'from') {
4450
+ setMessages(old => {
4451
+ const rawIdx = old.findIndex(m => m.uuid === message.uuid);
4452
+ return [...old.slice(0, rawIdx === -1 ? 0 : rawIdx), ...postCompact];
4453
+ });
4454
+ }
4455
+ else {
4456
+ setMessages(postCompact);
4404
4457
  }
4405
- }
4406
- // Show notification with ctrl+o hint
4407
- const historyShortcut = getShortcutDisplay('app:toggleTranscript', 'Global', 'ctrl+o');
4408
- addNotification({
4409
- key: 'summarize-ctrl-o-hint',
4410
- text: `Conversation summarized (${historyShortcut} for history)`,
4411
- priority: 'medium',
4412
- timeoutMs: 8000
4413
- });
4414
- }, onRestoreMessage: handleRestoreMessage, onClose: () => {
4415
- setIsMessageSelectorVisible(false);
4416
- setMessageSelectorPreselect(undefined);
4417
- } }), "external" === 'ant' && _jsx(DevBar, {})] }), feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? _jsx(CompanionSprite, {}) : null] }) }) }, remountKey)] });
4458
+ // Partial compact bypasses handleMessageFromStream — clear
4459
+ // the context-blocked flag so proactive ticks resume.
4460
+ if (feature('PROACTIVE') || feature('KAIROS')) {
4461
+ proactiveModule?.setContextBlocked(false);
4462
+ }
4463
+ setConversationId(randomUUID());
4464
+ runPostCompactCleanup(context.options.querySource);
4465
+ if (direction === 'from') {
4466
+ const r = textForResubmit(message);
4467
+ if (r) {
4468
+ setInputValue(r.text);
4469
+ setInputMode(r.mode);
4470
+ }
4471
+ }
4472
+ // Show notification with ctrl+o hint
4473
+ const historyShortcut = getShortcutDisplay('app:toggleTranscript', 'Global', 'ctrl+o');
4474
+ addNotification({
4475
+ key: 'summarize-ctrl-o-hint',
4476
+ text: `Conversation summarized (${historyShortcut} for history)`,
4477
+ priority: 'medium',
4478
+ timeoutMs: 8000
4479
+ });
4480
+ }, onRestoreMessage: handleRestoreMessage, onClose: () => {
4481
+ setIsMessageSelectorVisible(false);
4482
+ setMessageSelectorPreselect(undefined);
4483
+ } }), "external" === 'ant' && _jsx(DevBar, {})] }), feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? _jsx(CompanionSprite, {}) : null] }) }) }, remountKey)] }) });
4418
4484
  if (isFullscreenEnvEnabled()) {
4419
4485
  return _jsx(AlternateScreen, { mouseTracking: isMouseTrackingEnabled(), children: mainReturn });
4420
4486
  }