(props: AppProps)
| 72 | } |
| 73 | |
| 74 | export function App(props: AppProps): React.ReactElement { |
| 75 | const { exit } = useApp(); |
| 76 | // Pre-compute resumed-session metadata for the welcome banner |
| 77 | const resumedMeta = (() => { |
| 78 | if (!props.resumeSessionId) return undefined; |
| 79 | try { |
| 80 | const loaded = getSessionStore().loadSession(props.resumeSessionId); |
| 81 | if (loaded) return { id: props.resumeSessionId, turnCount: loaded.meta.turn_count }; |
| 82 | } catch { /* ignore */ } |
| 83 | return undefined; |
| 84 | })(); |
| 85 | const [history, setHistory] = useState<HistoryItem[]>(() => { |
| 86 | // On resume, repaint the prior Q&A so the user sees the conversation they're |
| 87 | // continuing — not a blank screen. (The model's context is rehydrated separately |
| 88 | // via `messages` below; this is purely the on-screen transcript.) |
| 89 | if (props.resumeSessionId) { |
| 90 | try { |
| 91 | const loaded = getSessionStore().loadSession(props.resumeSessionId); |
| 92 | if (loaded) return messagesToHistory(loaded.messages); |
| 93 | } catch { /* ignore — fall back to empty */ } |
| 94 | } |
| 95 | return []; |
| 96 | }); |
| 97 | const [input, setInput] = useState(''); |
| 98 | // Type-ahead queue: prompts the user submits while a turn is running. They run |
| 99 | // one at a time as soon as the agent goes idle (drained by an effect below), so |
| 100 | // the input box stays usable mid-task — like Claude Code's queued input. |
| 101 | const [queued, setQueued] = useState<string[]>([]); |
| 102 | // Bumped after each queued prompt finishes to re-trigger the drain effect even |
| 103 | // when the prompt didn't flip `busy` (e.g. an instant slash command). |
| 104 | const [drainTick, setDrainTick] = useState(0); |
| 105 | const dispatchingRef = useRef(false); |
| 106 | // Up/Down-arrow recall of previously submitted prompts (shell-style history), |
| 107 | // consumed by ChatInput. |
| 108 | const promptHistoryRef = useRef<string[]>([]); |
| 109 | const [busy, setBusy] = useState(false); |
| 110 | const [streamingText, setStreamingText] = useState(''); |
| 111 | const [activeTools, setActiveTools] = useState<Array<{ id: string; name: string; partialArgs: string }>>([]); |
| 112 | const [pendingPrompt, setPendingPrompt] = useState<PendingPrompt | null>(null); |
| 113 | const [sessionId, setSessionId] = useState<string>(() => { |
| 114 | const store = getSessionStore(); |
| 115 | if (props.resumeSessionId) { |
| 116 | const loaded = store.loadSession(props.resumeSessionId); |
| 117 | if (loaded) return props.resumeSessionId; |
| 118 | } |
| 119 | return store.createSession(props.cwd, props.config.defaults.model); |
| 120 | }); |
| 121 | const [messages, setMessages] = useState<Message[]>(() => { |
| 122 | if (props.resumeSessionId) { |
| 123 | const loaded = getSessionStore().loadSession(props.resumeSessionId); |
| 124 | if (loaded) return loaded.messages; |
| 125 | } |
| 126 | return []; |
| 127 | }); |
| 128 | const [mode, setMode] = useState<'normal' | 'plan'>('normal'); |
| 129 | // Gates the main UI behind the animated boot splash. Flips to true when the splash |
| 130 | // finishes (or immediately when motion is disabled / not a TTY). |
| 131 | const [booted, setBooted] = useState(false); |
nothing calls this directly
no test coverage detected