indusagi-coding-agent 0.1.47 → 0.1.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/command-line/session-picker.js +1 -1
- package/dist/command-line/session-picker.js.map +1 -1
- package/dist/interfaces/react-ink/adapters/command-router.d.ts +2 -0
- package/dist/interfaces/react-ink/adapters/command-router.d.ts.map +1 -1
- package/dist/interfaces/react-ink/adapters/command-router.js +47 -19
- package/dist/interfaces/react-ink/adapters/command-router.js.map +1 -1
- package/dist/interfaces/react-ink/adapters/session-events.d.ts.map +1 -1
- package/dist/interfaces/react-ink/adapters/session-events.js +9 -2
- package/dist/interfaces/react-ink/adapters/session-events.js.map +1 -1
- package/dist/interfaces/react-ink/adapters/session-history.d.ts +8 -2
- package/dist/interfaces/react-ink/adapters/session-history.d.ts.map +1 -1
- package/dist/interfaces/react-ink/adapters/session-history.js +19 -5
- package/dist/interfaces/react-ink/adapters/session-history.js.map +1 -1
- package/dist/interfaces/react-ink/adapters/tool-state.d.ts.map +1 -1
- package/dist/interfaces/react-ink/adapters/tool-state.js +1 -1
- package/dist/interfaces/react-ink/adapters/tool-state.js.map +1 -1
- package/dist/interfaces/react-ink/components/AppShell.d.ts +3 -2
- package/dist/interfaces/react-ink/components/AppShell.d.ts.map +1 -1
- package/dist/interfaces/react-ink/components/AppShell.js +914 -48
- package/dist/interfaces/react-ink/components/AppShell.js.map +1 -1
- package/dist/interfaces/react-ink/components/Header.d.ts +3 -2
- package/dist/interfaces/react-ink/components/Header.d.ts.map +1 -1
- package/dist/interfaces/react-ink/components/Header.js +5 -3
- package/dist/interfaces/react-ink/components/Header.js.map +1 -1
- package/dist/interfaces/react-ink/components/PromptInput.d.ts +15 -1
- package/dist/interfaces/react-ink/components/PromptInput.d.ts.map +1 -1
- package/dist/interfaces/react-ink/components/PromptInput.js +100 -12
- package/dist/interfaces/react-ink/components/PromptInput.js.map +1 -1
- package/dist/interfaces/react-ink/components/StartupDiagnosticsBlock.d.ts +9 -0
- package/dist/interfaces/react-ink/components/StartupDiagnosticsBlock.d.ts.map +1 -0
- package/dist/interfaces/react-ink/components/StartupDiagnosticsBlock.js +14 -0
- package/dist/interfaces/react-ink/components/StartupDiagnosticsBlock.js.map +1 -0
- package/dist/interfaces/react-ink/components/extensions/ExtensionComponentHost.d.ts +30 -0
- package/dist/interfaces/react-ink/components/extensions/ExtensionComponentHost.d.ts.map +1 -0
- package/dist/interfaces/react-ink/components/extensions/ExtensionComponentHost.js +106 -0
- package/dist/interfaces/react-ink/components/extensions/ExtensionComponentHost.js.map +1 -0
- package/dist/interfaces/react-ink/components/extensions/ExtensionDialogs.d.ts +20 -0
- package/dist/interfaces/react-ink/components/extensions/ExtensionDialogs.d.ts.map +1 -0
- package/dist/interfaces/react-ink/components/extensions/ExtensionDialogs.js +144 -0
- package/dist/interfaces/react-ink/components/extensions/ExtensionDialogs.js.map +1 -0
- package/dist/interfaces/react-ink/hooks/use-agent-session.d.ts +2 -2
- package/dist/interfaces/react-ink/hooks/use-agent-session.d.ts.map +1 -1
- package/dist/interfaces/react-ink/hooks/use-agent-session.js +6 -1
- package/dist/interfaces/react-ink/hooks/use-agent-session.js.map +1 -1
- package/dist/interfaces/react-ink/hooks/use-app-keybindings.d.ts +0 -2
- package/dist/interfaces/react-ink/hooks/use-app-keybindings.d.ts.map +1 -1
- package/dist/interfaces/react-ink/hooks/use-app-keybindings.js +2 -39
- package/dist/interfaces/react-ink/hooks/use-app-keybindings.js.map +1 -1
- package/dist/interfaces/react-ink/hooks/use-prompt-submit.d.ts +2 -0
- package/dist/interfaces/react-ink/hooks/use-prompt-submit.d.ts.map +1 -1
- package/dist/interfaces/react-ink/hooks/use-prompt-submit.js +14 -1
- package/dist/interfaces/react-ink/hooks/use-prompt-submit.js.map +1 -1
- package/dist/interfaces/react-ink/index.d.ts +1 -5
- package/dist/interfaces/react-ink/index.d.ts.map +1 -1
- package/dist/interfaces/react-ink/index.js +1 -5
- package/dist/interfaces/react-ink/index.js.map +1 -1
- package/dist/interfaces/react-ink/interactive-mode.d.ts +1 -0
- package/dist/interfaces/react-ink/interactive-mode.d.ts.map +1 -1
- package/dist/interfaces/react-ink/interactive-mode.js +8 -1
- package/dist/interfaces/react-ink/interactive-mode.js.map +1 -1
- package/dist/interfaces/react-ink/render-root.d.ts +3 -2
- package/dist/interfaces/react-ink/render-root.d.ts.map +1 -1
- package/dist/interfaces/react-ink/render-root.js +2 -2
- package/dist/interfaces/react-ink/render-root.js.map +1 -1
- package/dist/interfaces/react-ink/theme-adapter.d.ts +2 -8
- package/dist/interfaces/react-ink/theme-adapter.d.ts.map +1 -1
- package/dist/interfaces/react-ink/theme-adapter.js +2 -41
- package/dist/interfaces/react-ink/theme-adapter.js.map +1 -1
- package/dist/interfaces/react-ink/types.d.ts +4 -93
- package/dist/interfaces/react-ink/types.d.ts.map +1 -1
- package/dist/interfaces/react-ink/utils/key-data.d.ts +22 -0
- package/dist/interfaces/react-ink/utils/key-data.d.ts.map +1 -0
- package/dist/interfaces/react-ink/utils/key-data.js +123 -0
- package/dist/interfaces/react-ink/utils/key-data.js.map +1 -0
- package/dist/interfaces/react-ink/utils/session-actions.d.ts +2 -0
- package/dist/interfaces/react-ink/utils/session-actions.d.ts.map +1 -1
- package/dist/interfaces/react-ink/utils/session-actions.js +10 -0
- package/dist/interfaces/react-ink/utils/session-actions.js.map +1 -1
- package/dist/interfaces/react-ink/utils/startup-diagnostics.d.ts +7 -0
- package/dist/interfaces/react-ink/utils/startup-diagnostics.d.ts.map +1 -0
- package/dist/interfaces/react-ink/utils/startup-diagnostics.js +111 -0
- package/dist/interfaces/react-ink/utils/startup-diagnostics.js.map +1 -0
- package/dist/interfaces/theme/dark.json +1 -1
- package/dist/interfaces/theme/light.json +1 -1
- package/package.json +4 -4
|
@@ -1,33 +1,49 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
2
6
|
import { useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
3
7
|
import { Box, Text, useApp } from "ink";
|
|
4
|
-
import {
|
|
8
|
+
import { matchesKey } from "indusagi/tui";
|
|
9
|
+
import { Footer, LoginDialog, MessageList, ModelDialog, OAuthDialog, ScopedModelsDialog, SessionDialog, SettingsDialog, StatusLine, TaskPanel, ThemeDialog, TreeDialog, UserMessageDialog, } from "indusagi/react-ink";
|
|
10
|
+
import { APP_NAME, VERSION, getAuthPath } from "../../../config.js";
|
|
11
|
+
import { extensionForImageMimeType, readClipboardImage } from "../../../helpers/clipboard-image.js";
|
|
5
12
|
import { openAuthUrl } from "../../../helpers/open-auth-url.js";
|
|
13
|
+
import { KeybindingsManager } from "../../../runtime/keybindings.js";
|
|
6
14
|
import { resolveModelScope } from "../../../runtime/model-resolver.js";
|
|
7
|
-
import { getAvailableThemes, setTheme } from "../../theme/theme.js";
|
|
8
|
-
import { flattenSessionTree,
|
|
15
|
+
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getThemeByName, setTheme, setThemeInstance, theme as extensionTheme, Theme, } from "../../theme/theme.js";
|
|
16
|
+
import { flattenSessionTree, listAllSessions, listCurrentSessions, listUserMessages } from "../adapters/session-history.js";
|
|
9
17
|
import { uiReducer } from "../state/reducer.js";
|
|
10
18
|
import { createUiStore } from "../state/store.js";
|
|
11
19
|
import { createThemeAdapter } from "../theme-adapter.js";
|
|
20
|
+
import { deleteSessionAtPath, renameSessionAtPath } from "../utils/session-actions.js";
|
|
12
21
|
import { Header } from "./Header.js";
|
|
13
|
-
import { MessageList } from "./MessageList.js";
|
|
14
22
|
import { PromptInput } from "./PromptInput.js";
|
|
15
|
-
import { StatusLine } from "./StatusLine.js";
|
|
16
|
-
import { TaskPanel } from "./TaskPanel.js";
|
|
17
|
-
import { Footer } from "./Footer.js";
|
|
18
23
|
import { useAgentSession } from "../hooks/use-agent-session.js";
|
|
19
24
|
import { useAppKeybindings } from "../hooks/use-app-keybindings.js";
|
|
20
25
|
import { useFooterData } from "../hooks/use-footer-data.js";
|
|
21
26
|
import { usePromptSubmit } from "../hooks/use-prompt-submit.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
import { createExtensionTuiBridge, createHostedExtensionComponent, createHostedExtensionComponentFromParts, ExtensionComponentHost, } from "./extensions/ExtensionComponentHost.js";
|
|
28
|
+
import { ExtensionSelectDialog, ExtensionTextDialog } from "./extensions/ExtensionDialogs.js";
|
|
29
|
+
function truncateWidgetLines(lines, maxLines = 10) {
|
|
30
|
+
if (lines.length <= maxLines) {
|
|
31
|
+
return lines;
|
|
32
|
+
}
|
|
33
|
+
return [...lines.slice(0, maxLines), `... ${lines.length - maxLines} more line(s)`];
|
|
34
|
+
}
|
|
35
|
+
function renderTextLines(lines, prefix) {
|
|
36
|
+
if (lines.length === 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((line, index) => (_jsx(Text, { children: line.length > 0 ? line : " " }, `${prefix}:${index}`))) }));
|
|
40
|
+
}
|
|
41
|
+
function writeTerminalTitle(title) {
|
|
42
|
+
if (!process.stdout.isTTY) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
process.stdout.write(`\u001B]0;${title}\u0007`);
|
|
46
|
+
}
|
|
31
47
|
function buildSettingsItems(session, state, onRefresh, setError) {
|
|
32
48
|
const booleanValues = ["enabled", "disabled"];
|
|
33
49
|
const visibilityValues = ["visible", "hidden"];
|
|
@@ -204,15 +220,33 @@ function buildSettingsItems(session, state, onRefresh, setError) {
|
|
|
204
220
|
},
|
|
205
221
|
];
|
|
206
222
|
}
|
|
207
|
-
export function AppShell({ context, footerDataProvider, initialImages, initialMessage, initialMessages, startupChangelog, startupNotices, verbose, }) {
|
|
223
|
+
export function AppShell({ context, footerDataProvider, initialImages, initialMessage, initialMessages, startupChangelog, startupDiagnostics, startupNotices, verbose, }) {
|
|
208
224
|
const { exit } = useApp();
|
|
225
|
+
const keybindings = useMemo(() => KeybindingsManager.create(), []);
|
|
209
226
|
const [state, dispatch] = useReducer(uiReducer, context.session, createUiStore);
|
|
210
227
|
const overlayRef = useRef(state.overlay);
|
|
211
228
|
const oauthResolverRef = useRef(null);
|
|
212
229
|
const startupRanRef = useRef(false);
|
|
213
230
|
const displayBlockIdRef = useRef(0);
|
|
214
231
|
const [terminalRows, setTerminalRows] = useState(process.stdout.rows ?? 40);
|
|
232
|
+
const [terminalColumns, setTerminalColumns] = useState(process.stdout.columns ?? 80);
|
|
215
233
|
const [displayBlocks, setDisplayBlocks] = useState([]);
|
|
234
|
+
const [extensionHeader, setExtensionHeader] = useState(null);
|
|
235
|
+
const [extensionFooter, setExtensionFooter] = useState(null);
|
|
236
|
+
const [extensionWidgetsAbove, setExtensionWidgetsAbove] = useState(() => new Map());
|
|
237
|
+
const [extensionWidgetsBelow, setExtensionWidgetsBelow] = useState(() => new Map());
|
|
238
|
+
const [extensionOverlayState, setExtensionOverlayState] = useState({ kind: "none" });
|
|
239
|
+
const [extensionEditor, setExtensionEditor] = useState(null);
|
|
240
|
+
const [extensionWorkingMessage, setExtensionWorkingMessage] = useState();
|
|
241
|
+
const [toolOutputExpanded, setToolOutputExpanded] = useState(false);
|
|
242
|
+
const [compactionQueuedMessages, setCompactionQueuedMessages] = useState([]);
|
|
243
|
+
const [currentSessions, setCurrentSessions] = useState([]);
|
|
244
|
+
const [layoutEpoch, setLayoutEpoch] = useState(0);
|
|
245
|
+
const [startupDismissed, setStartupDismissed] = useState(context.session.messages.length > 0);
|
|
246
|
+
const extensionOverlayRef = useRef({ kind: "none" });
|
|
247
|
+
const extensionUiContextRef = useRef(undefined);
|
|
248
|
+
const inputRef = useRef(state.input);
|
|
249
|
+
const customTerminalTitleRef = useRef(null);
|
|
216
250
|
const setStatus = (text, kind = "info") => {
|
|
217
251
|
dispatch({ type: "set-status", status: { text, kind } });
|
|
218
252
|
};
|
|
@@ -232,6 +266,7 @@ export function AppShell({ context, footerDataProvider, initialImages, initialMe
|
|
|
232
266
|
const setInput = (value, cursorOffset) => {
|
|
233
267
|
dispatch({ type: "set-input", value, cursorOffset });
|
|
234
268
|
};
|
|
269
|
+
inputRef.current = state.input;
|
|
235
270
|
const appendDisplayBlock = (block) => {
|
|
236
271
|
const timestamp = Date.now();
|
|
237
272
|
displayBlockIdRef.current += 1;
|
|
@@ -244,26 +279,289 @@ export function AppShell({ context, footerDataProvider, initialImages, initialMe
|
|
|
244
279
|
},
|
|
245
280
|
]);
|
|
246
281
|
};
|
|
247
|
-
const resetSessionView = () => {
|
|
248
|
-
setDisplayBlocks([]);
|
|
249
|
-
dispatch({ type: "set-tool-executions", toolExecutions: {} });
|
|
250
|
-
refresh();
|
|
251
|
-
};
|
|
252
282
|
const handleExit = () => {
|
|
253
283
|
context.requestExit();
|
|
254
284
|
exit();
|
|
255
285
|
};
|
|
286
|
+
const isExtensionCommand = (text) => {
|
|
287
|
+
if (!text.startsWith("/")) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
const extensionRunner = context.session.extensionRunner;
|
|
291
|
+
if (!extensionRunner) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
const spaceIndex = text.indexOf(" ");
|
|
295
|
+
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
296
|
+
return Boolean(extensionRunner.getCommand(commandName));
|
|
297
|
+
};
|
|
298
|
+
const flushCompactionQueueRef = useRef(null);
|
|
256
299
|
const { snapshot, toolExecutions, refresh } = useAgentSession(context.session, (status) => {
|
|
257
300
|
dispatch({ type: "set-status", status });
|
|
301
|
+
}, (event) => {
|
|
302
|
+
if (event.type === "session_compact") {
|
|
303
|
+
void flushCompactionQueueRef.current?.();
|
|
304
|
+
}
|
|
305
|
+
if (event.type === "auto_compaction_end" && !event.aborted && event.result) {
|
|
306
|
+
void flushCompactionQueueRef.current?.();
|
|
307
|
+
}
|
|
258
308
|
});
|
|
259
309
|
useEffect(() => {
|
|
260
310
|
dispatch({ type: "set-tool-executions", toolExecutions });
|
|
261
311
|
}, [toolExecutions]);
|
|
312
|
+
const refreshSessionView = () => {
|
|
313
|
+
dispatch({ type: "set-tool-executions", toolExecutions: {} });
|
|
314
|
+
refresh();
|
|
315
|
+
};
|
|
316
|
+
const resetSessionView = () => {
|
|
317
|
+
setDisplayBlocks([]);
|
|
318
|
+
refreshSessionView();
|
|
319
|
+
};
|
|
320
|
+
const triggerFooterRefresh = () => {
|
|
321
|
+
dispatch({ type: "footer-tick" });
|
|
322
|
+
};
|
|
323
|
+
const pendingMessages = [
|
|
324
|
+
...context.session.getSteeringMessages().map((text, index) => ({
|
|
325
|
+
id: `session-steer-${index}-${text}`,
|
|
326
|
+
mode: "steer",
|
|
327
|
+
text,
|
|
328
|
+
source: "session",
|
|
329
|
+
})),
|
|
330
|
+
...context.session.getFollowUpMessages().map((text, index) => ({
|
|
331
|
+
id: `session-follow-${index}-${text}`,
|
|
332
|
+
mode: "followUp",
|
|
333
|
+
text,
|
|
334
|
+
source: "session",
|
|
335
|
+
})),
|
|
336
|
+
...compactionQueuedMessages.map((message) => ({
|
|
337
|
+
id: message.id,
|
|
338
|
+
mode: message.mode,
|
|
339
|
+
text: message.text,
|
|
340
|
+
source: "compaction",
|
|
341
|
+
})),
|
|
342
|
+
];
|
|
343
|
+
const restoreQueuedMessagesToPrompt = (options) => {
|
|
344
|
+
const { steering, followUp } = context.session.clearQueue();
|
|
345
|
+
const queuedMessages = [
|
|
346
|
+
...steering,
|
|
347
|
+
...followUp,
|
|
348
|
+
...compactionQueuedMessages.map((message) => message.text),
|
|
349
|
+
];
|
|
350
|
+
setCompactionQueuedMessages([]);
|
|
351
|
+
if (queuedMessages.length === 0) {
|
|
352
|
+
if (options?.abort) {
|
|
353
|
+
void context.session.abort();
|
|
354
|
+
}
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
const combinedText = [...queuedMessages, options?.currentText ?? state.input]
|
|
358
|
+
.filter((value) => value.trim().length > 0)
|
|
359
|
+
.join("\n\n");
|
|
360
|
+
setInput(combinedText);
|
|
361
|
+
if (options?.abort) {
|
|
362
|
+
void context.session.abort();
|
|
363
|
+
}
|
|
364
|
+
return queuedMessages.length;
|
|
365
|
+
};
|
|
366
|
+
const queueCompactionMessage = (text, mode) => {
|
|
367
|
+
setCompactionQueuedMessages((current) => [
|
|
368
|
+
...current,
|
|
369
|
+
{
|
|
370
|
+
id: `${mode}:${Date.now()}:${current.length}`,
|
|
371
|
+
text,
|
|
372
|
+
mode,
|
|
373
|
+
},
|
|
374
|
+
]);
|
|
375
|
+
};
|
|
376
|
+
const flushCompactionQueue = async () => {
|
|
377
|
+
if (compactionQueuedMessages.length === 0) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const queuedMessages = [...compactionQueuedMessages];
|
|
381
|
+
setCompactionQueuedMessages([]);
|
|
382
|
+
const restoreQueue = (error) => {
|
|
383
|
+
setCompactionQueuedMessages(queuedMessages);
|
|
384
|
+
setError(`Failed to send queued message${queuedMessages.length === 1 ? "" : "s"}: ${error instanceof Error ? error.message : String(error)}`);
|
|
385
|
+
};
|
|
386
|
+
try {
|
|
387
|
+
const firstPromptIndex = queuedMessages.findIndex((message) => !isExtensionCommand(message.text));
|
|
388
|
+
if (firstPromptIndex === -1) {
|
|
389
|
+
for (const message of queuedMessages) {
|
|
390
|
+
await context.session.prompt(message.text);
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
for (const message of queuedMessages.slice(0, firstPromptIndex)) {
|
|
395
|
+
await context.session.prompt(message.text);
|
|
396
|
+
}
|
|
397
|
+
const firstPrompt = queuedMessages[firstPromptIndex];
|
|
398
|
+
const rest = queuedMessages.slice(firstPromptIndex + 1);
|
|
399
|
+
await context.session.prompt(firstPrompt.text);
|
|
400
|
+
for (const message of rest) {
|
|
401
|
+
if (isExtensionCommand(message.text)) {
|
|
402
|
+
await context.session.prompt(message.text);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
await context.session.prompt(message.text, {
|
|
406
|
+
streamingBehavior: message.mode === "followUp" ? "followUp" : "steer",
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
setStatus(`Flushed ${queuedMessages.length} queued message${queuedMessages.length === 1 ? "" : "s"} after compaction.`, "success");
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
restoreQueue(error);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
flushCompactionQueueRef.current = flushCompactionQueue;
|
|
416
|
+
const cycleModel = async (direction) => {
|
|
417
|
+
try {
|
|
418
|
+
const result = await context.session.cycleModel(direction);
|
|
419
|
+
if (!result) {
|
|
420
|
+
setStatus(context.session.scopedModels.length > 0 ? "Only one model in scope." : "Only one model available.", "warning");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
refresh();
|
|
424
|
+
setStatus(`Switched to ${result.model.provider}/${result.model.id}.`, "success");
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
setError(error instanceof Error ? error.message : String(error));
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const toggleThinkingVisibility = () => {
|
|
431
|
+
const nextShowThinking = !state.showThinking;
|
|
432
|
+
context.session.settingsManager.setHideThinkingBlock(!nextShowThinking);
|
|
433
|
+
dispatch({ type: "set-show-thinking", value: nextShowThinking });
|
|
434
|
+
setStatus(`Thinking blocks ${nextShowThinking ? "visible" : "hidden"}.`, "success");
|
|
435
|
+
};
|
|
436
|
+
const toggleToolOutputExpansion = () => {
|
|
437
|
+
setToolOutputExpanded((current) => {
|
|
438
|
+
const next = !current;
|
|
439
|
+
setStatus(`Tool outputs ${next ? "expanded" : "collapsed"}.`, "success");
|
|
440
|
+
return next;
|
|
441
|
+
});
|
|
442
|
+
};
|
|
443
|
+
const openExternalEditor = async () => {
|
|
444
|
+
const editorCommand = process.env.VISUAL || process.env.EDITOR;
|
|
445
|
+
if (!editorCommand) {
|
|
446
|
+
setStatus("Set $VISUAL or $EDITOR to use the external editor shortcut.", "warning");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const tempFile = join(tmpdir(), `indusagi-editor-${Date.now()}.md`);
|
|
450
|
+
const currentText = state.input;
|
|
451
|
+
try {
|
|
452
|
+
writeFileSync(tempFile, currentText, "utf-8");
|
|
453
|
+
process.stdout.write("\u001B[?25h");
|
|
454
|
+
const [editor, ...editorArgs] = editorCommand.split(" ").filter(Boolean);
|
|
455
|
+
const result = spawnSync(editor, [...editorArgs, tempFile], {
|
|
456
|
+
stdio: "inherit",
|
|
457
|
+
});
|
|
458
|
+
if (result.status === 0) {
|
|
459
|
+
const nextText = readFileSync(tempFile, "utf-8").replace(/\n$/, "");
|
|
460
|
+
setInput(nextText, nextText.length);
|
|
461
|
+
setStatus("Updated prompt from external editor.", "success");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
setError(`External editor failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
466
|
+
}
|
|
467
|
+
finally {
|
|
468
|
+
try {
|
|
469
|
+
unlinkSync(tempFile);
|
|
470
|
+
}
|
|
471
|
+
catch { }
|
|
472
|
+
process.stdout.write("\u001B[?25l");
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
const dequeueQueuedMessages = () => {
|
|
476
|
+
const restored = restoreQueuedMessagesToPrompt();
|
|
477
|
+
if (restored === 0) {
|
|
478
|
+
setStatus("No queued messages to restore.", "warning");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
setStatus(`Restored ${restored} queued message${restored === 1 ? "" : "s"} to the prompt.`, "success");
|
|
482
|
+
};
|
|
483
|
+
const pasteClipboardImage = async () => {
|
|
484
|
+
try {
|
|
485
|
+
const image = await readClipboardImage();
|
|
486
|
+
if (!image) {
|
|
487
|
+
setStatus("No image was found in the clipboard.", "warning");
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const extension = extensionForImageMimeType(image.mimeType) ?? "png";
|
|
491
|
+
const filePath = join(tmpdir(), `indusagi-clipboard-${crypto.randomUUID()}.${extension}`);
|
|
492
|
+
writeFileSync(filePath, Buffer.from(image.bytes));
|
|
493
|
+
const nextValue = state.input.length > 0 && !/\s$/.test(state.input)
|
|
494
|
+
? `${state.input} ${filePath}`
|
|
495
|
+
: `${state.input}${filePath}`;
|
|
496
|
+
setInput(nextValue, nextValue.length);
|
|
497
|
+
setStatus("Inserted clipboard image path into the prompt.", "success");
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
setStatus(`Unable to paste clipboard image: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
const suspendTerminal = () => {
|
|
504
|
+
if (process.platform === "win32") {
|
|
505
|
+
setStatus("Suspend is not available on Windows terminals.", "warning");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
process.once("SIGCONT", () => {
|
|
509
|
+
refresh();
|
|
510
|
+
triggerFooterRefresh();
|
|
511
|
+
setStatus("Resumed terminal session.", "success");
|
|
512
|
+
});
|
|
513
|
+
process.stdout.write("\u001B[?25h");
|
|
514
|
+
process.kill(0, "SIGTSTP");
|
|
515
|
+
};
|
|
516
|
+
const restoreDefaultTerminalTitle = () => {
|
|
517
|
+
customTerminalTitleRef.current = null;
|
|
518
|
+
const sessionName = context.session.sessionManager.getSessionName();
|
|
519
|
+
writeTerminalTitle(sessionName ? `${APP_NAME} ${VERSION} - ${sessionName}` : `${APP_NAME} ${VERSION}`);
|
|
520
|
+
};
|
|
521
|
+
const cancelExtensionOverlay = () => {
|
|
522
|
+
const current = extensionOverlayRef.current;
|
|
523
|
+
switch (current.kind) {
|
|
524
|
+
case "select":
|
|
525
|
+
current.resolve(undefined);
|
|
526
|
+
break;
|
|
527
|
+
case "confirm":
|
|
528
|
+
current.resolve(false);
|
|
529
|
+
break;
|
|
530
|
+
case "input":
|
|
531
|
+
case "editor":
|
|
532
|
+
current.resolve(undefined);
|
|
533
|
+
break;
|
|
534
|
+
case "custom":
|
|
535
|
+
current.cancel();
|
|
536
|
+
break;
|
|
537
|
+
case "none":
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
setExtensionOverlayState({ kind: "none" });
|
|
541
|
+
};
|
|
542
|
+
const resetExtensionUi = () => {
|
|
543
|
+
cancelExtensionOverlay();
|
|
544
|
+
setExtensionEditor(null);
|
|
545
|
+
setExtensionHeader(null);
|
|
546
|
+
setExtensionFooter(null);
|
|
547
|
+
setExtensionWidgetsAbove(new Map());
|
|
548
|
+
setExtensionWidgetsBelow(new Map());
|
|
549
|
+
setExtensionWorkingMessage(undefined);
|
|
550
|
+
footerDataProvider.clearExtensionStatuses();
|
|
551
|
+
triggerFooterRefresh();
|
|
552
|
+
restoreDefaultTerminalTitle();
|
|
553
|
+
};
|
|
262
554
|
useEffect(() => {
|
|
263
555
|
overlayRef.current = state.overlay;
|
|
264
556
|
}, [state.overlay]);
|
|
265
557
|
useEffect(() => {
|
|
266
|
-
|
|
558
|
+
extensionOverlayRef.current = extensionOverlayState;
|
|
559
|
+
}, [extensionOverlayState]);
|
|
560
|
+
useEffect(() => {
|
|
561
|
+
const onResize = () => {
|
|
562
|
+
setTerminalRows(process.stdout.rows ?? 40);
|
|
563
|
+
setTerminalColumns(process.stdout.columns ?? 80);
|
|
564
|
+
};
|
|
267
565
|
process.stdout.on("resize", onResize);
|
|
268
566
|
return () => {
|
|
269
567
|
process.stdout.off("resize", onResize);
|
|
@@ -271,6 +569,8 @@ export function AppShell({ context, footerDataProvider, initialImages, initialMe
|
|
|
271
569
|
}, []);
|
|
272
570
|
const footerData = useFooterData(footerDataProvider, state.footerTick);
|
|
273
571
|
const theme = createThemeAdapter(state.themeName);
|
|
572
|
+
const showStartupDiagnostics = !startupDismissed;
|
|
573
|
+
const previousStartupDiagnosticsRef = useRef(showStartupDiagnostics);
|
|
274
574
|
function handleOAuthOverlayChange(partial) {
|
|
275
575
|
const current = overlayRef.current;
|
|
276
576
|
if (current.kind !== "oauth")
|
|
@@ -339,10 +639,492 @@ export function AppShell({ context, footerDataProvider, initialImages, initialMe
|
|
|
339
639
|
appendDisplayBlock,
|
|
340
640
|
startOAuthLogin,
|
|
341
641
|
resetSessionView,
|
|
642
|
+
refreshSessionView,
|
|
643
|
+
onBeforeReload: resetExtensionUi,
|
|
342
644
|
onExit: handleExit,
|
|
343
645
|
},
|
|
344
646
|
onAfterSubmit: refresh,
|
|
647
|
+
onQueueDuringCompaction: queueCompactionMessage,
|
|
648
|
+
onShouldRunDuringCompaction: isExtensionCommand,
|
|
345
649
|
});
|
|
650
|
+
const submitPromptRef = useRef(submitPrompt);
|
|
651
|
+
const submitPromptWithStartupDismiss = async (input, mode = "followUp") => {
|
|
652
|
+
if (!startupDismissed && input.trim().length > 0) {
|
|
653
|
+
setStartupDismissed(true);
|
|
654
|
+
}
|
|
655
|
+
await submitPrompt(input, mode);
|
|
656
|
+
};
|
|
657
|
+
submitPromptRef.current = submitPromptWithStartupDismiss;
|
|
658
|
+
const handleExtensionShortcutKeyData = async (data) => {
|
|
659
|
+
const extensionRunner = context.session.extensionRunner;
|
|
660
|
+
if (!extensionRunner) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
const shortcuts = extensionRunner.getShortcuts(keybindings.getEffectiveConfig());
|
|
664
|
+
if (shortcuts.size === 0) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
const shortcutContext = {
|
|
668
|
+
ui: extensionUiContextRef.current ?? {
|
|
669
|
+
select: async () => undefined,
|
|
670
|
+
confirm: async () => false,
|
|
671
|
+
input: async () => undefined,
|
|
672
|
+
notify: () => { },
|
|
673
|
+
setStatus: () => { },
|
|
674
|
+
setWorkingMessage: () => { },
|
|
675
|
+
setWidget: () => { },
|
|
676
|
+
setFooter: () => { },
|
|
677
|
+
setHeader: () => { },
|
|
678
|
+
setTitle: () => { },
|
|
679
|
+
custom: async () => undefined,
|
|
680
|
+
setEditorText: () => { },
|
|
681
|
+
getEditorText: () => inputRef.current,
|
|
682
|
+
editor: async () => undefined,
|
|
683
|
+
setEditorComponent: () => { },
|
|
684
|
+
get theme() {
|
|
685
|
+
return extensionTheme;
|
|
686
|
+
},
|
|
687
|
+
getAllThemes: () => [],
|
|
688
|
+
getTheme: () => undefined,
|
|
689
|
+
setTheme: () => ({ success: false, error: "Extension UI is not ready yet." }),
|
|
690
|
+
},
|
|
691
|
+
hasUI: true,
|
|
692
|
+
cwd: process.cwd(),
|
|
693
|
+
sessionManager: context.session.sessionManager,
|
|
694
|
+
modelRegistry: context.session.modelRegistry,
|
|
695
|
+
model: context.session.model,
|
|
696
|
+
isIdle: () => !context.session.isStreaming,
|
|
697
|
+
abort: () => {
|
|
698
|
+
void context.session.abort();
|
|
699
|
+
},
|
|
700
|
+
hasPendingMessages: () => context.session.pendingMessageCount > 0,
|
|
701
|
+
shutdown: handleExit,
|
|
702
|
+
getContextUsage: () => context.session.getContextUsage(),
|
|
703
|
+
compact: (options) => {
|
|
704
|
+
void (async () => {
|
|
705
|
+
try {
|
|
706
|
+
const result = await context.session.compact(options?.customInstructions);
|
|
707
|
+
refreshSessionView();
|
|
708
|
+
options?.onComplete?.(result);
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
const resolvedError = error instanceof Error ? error : new Error(String(error));
|
|
712
|
+
options?.onError?.(resolvedError);
|
|
713
|
+
}
|
|
714
|
+
})();
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
for (const [shortcutKey, shortcut] of shortcuts) {
|
|
718
|
+
if (!matchesKey(data, shortcutKey)) {
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
try {
|
|
722
|
+
await Promise.resolve(shortcut.handler(shortcutContext));
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
setError(`Shortcut handler error: ${error instanceof Error ? error.message : String(error)}`);
|
|
726
|
+
}
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
return false;
|
|
730
|
+
};
|
|
731
|
+
useEffect(() => {
|
|
732
|
+
if (customTerminalTitleRef.current) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
restoreDefaultTerminalTitle();
|
|
736
|
+
}, [snapshot.sessionName]);
|
|
737
|
+
useEffect(() => {
|
|
738
|
+
if (extensionOverlayState.kind === "none") {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const timeoutAt = extensionOverlayState.kind === "select" ||
|
|
742
|
+
extensionOverlayState.kind === "confirm" ||
|
|
743
|
+
extensionOverlayState.kind === "input" ||
|
|
744
|
+
extensionOverlayState.kind === "editor"
|
|
745
|
+
? extensionOverlayState.timeoutAt
|
|
746
|
+
: undefined;
|
|
747
|
+
if (!timeoutAt) {
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const remaining = timeoutAt - Date.now();
|
|
751
|
+
if (remaining <= 0) {
|
|
752
|
+
cancelExtensionOverlay();
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const timer = setTimeout(() => {
|
|
756
|
+
cancelExtensionOverlay();
|
|
757
|
+
}, remaining);
|
|
758
|
+
return () => {
|
|
759
|
+
clearTimeout(timer);
|
|
760
|
+
};
|
|
761
|
+
}, [extensionOverlayState]);
|
|
762
|
+
useEffect(() => {
|
|
763
|
+
if (!extensionEditor) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (extensionEditor.component.getText() !== state.input) {
|
|
767
|
+
extensionEditor.component.setText(state.input);
|
|
768
|
+
extensionEditor.bridge.requestRender?.();
|
|
769
|
+
}
|
|
770
|
+
}, [extensionEditor, state.input]);
|
|
771
|
+
useEffect(() => {
|
|
772
|
+
const buildExtensionUiContext = () => ({
|
|
773
|
+
select: (title, options, opts) => new Promise((resolve) => {
|
|
774
|
+
if (opts?.signal?.aborted) {
|
|
775
|
+
resolve(undefined);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const finalize = (() => {
|
|
779
|
+
let settled = false;
|
|
780
|
+
return (value) => {
|
|
781
|
+
if (settled) {
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
settled = true;
|
|
785
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
786
|
+
setExtensionOverlayState({ kind: "none" });
|
|
787
|
+
resolve(value);
|
|
788
|
+
};
|
|
789
|
+
})();
|
|
790
|
+
const onAbort = () => {
|
|
791
|
+
finalize(undefined);
|
|
792
|
+
};
|
|
793
|
+
cancelExtensionOverlay();
|
|
794
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
795
|
+
setExtensionOverlayState({
|
|
796
|
+
kind: "select",
|
|
797
|
+
title,
|
|
798
|
+
options,
|
|
799
|
+
timeoutAt: opts?.timeout ? Date.now() + opts.timeout : undefined,
|
|
800
|
+
resolve: finalize,
|
|
801
|
+
});
|
|
802
|
+
}),
|
|
803
|
+
confirm: (title, message, opts) => new Promise((resolve) => {
|
|
804
|
+
if (opts?.signal?.aborted) {
|
|
805
|
+
resolve(false);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const finalize = (() => {
|
|
809
|
+
let settled = false;
|
|
810
|
+
return (value) => {
|
|
811
|
+
if (settled) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
settled = true;
|
|
815
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
816
|
+
setExtensionOverlayState({ kind: "none" });
|
|
817
|
+
resolve(value);
|
|
818
|
+
};
|
|
819
|
+
})();
|
|
820
|
+
const onAbort = () => {
|
|
821
|
+
finalize(false);
|
|
822
|
+
};
|
|
823
|
+
cancelExtensionOverlay();
|
|
824
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
825
|
+
setExtensionOverlayState({
|
|
826
|
+
kind: "confirm",
|
|
827
|
+
title,
|
|
828
|
+
message,
|
|
829
|
+
timeoutAt: opts?.timeout ? Date.now() + opts.timeout : undefined,
|
|
830
|
+
resolve: finalize,
|
|
831
|
+
});
|
|
832
|
+
}),
|
|
833
|
+
input: (title, placeholder, opts) => new Promise((resolve) => {
|
|
834
|
+
if (opts?.signal?.aborted) {
|
|
835
|
+
resolve(undefined);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const finalize = (() => {
|
|
839
|
+
let settled = false;
|
|
840
|
+
return (value) => {
|
|
841
|
+
if (settled) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
settled = true;
|
|
845
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
846
|
+
setExtensionOverlayState({ kind: "none" });
|
|
847
|
+
resolve(value);
|
|
848
|
+
};
|
|
849
|
+
})();
|
|
850
|
+
const onAbort = () => {
|
|
851
|
+
finalize(undefined);
|
|
852
|
+
};
|
|
853
|
+
cancelExtensionOverlay();
|
|
854
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
855
|
+
setExtensionOverlayState({
|
|
856
|
+
kind: "input",
|
|
857
|
+
title,
|
|
858
|
+
placeholder,
|
|
859
|
+
timeoutAt: opts?.timeout ? Date.now() + opts.timeout : undefined,
|
|
860
|
+
resolve: finalize,
|
|
861
|
+
});
|
|
862
|
+
}),
|
|
863
|
+
notify: (message, type) => {
|
|
864
|
+
if (type === "error") {
|
|
865
|
+
setError(message);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (type === "warning") {
|
|
869
|
+
setStatus(message, "warning");
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
setStatus(message, "info");
|
|
873
|
+
},
|
|
874
|
+
setStatus: (key, text) => {
|
|
875
|
+
footerDataProvider.setExtensionStatus(key, text);
|
|
876
|
+
triggerFooterRefresh();
|
|
877
|
+
},
|
|
878
|
+
setWorkingMessage: (message) => {
|
|
879
|
+
setExtensionWorkingMessage(message);
|
|
880
|
+
},
|
|
881
|
+
setWidget: (key, content, options) => {
|
|
882
|
+
const placement = options?.placement ?? "aboveEditor";
|
|
883
|
+
const updateMap = placement === "belowEditor" ? setExtensionWidgetsBelow : setExtensionWidgetsAbove;
|
|
884
|
+
const clearOtherMap = placement === "belowEditor" ? setExtensionWidgetsAbove : setExtensionWidgetsBelow;
|
|
885
|
+
clearOtherMap((current) => {
|
|
886
|
+
if (!current.has(key)) {
|
|
887
|
+
return current;
|
|
888
|
+
}
|
|
889
|
+
const next = new Map(current);
|
|
890
|
+
next.delete(key);
|
|
891
|
+
return next;
|
|
892
|
+
});
|
|
893
|
+
updateMap((current) => {
|
|
894
|
+
const next = new Map(current);
|
|
895
|
+
if (content === undefined) {
|
|
896
|
+
next.delete(key);
|
|
897
|
+
return next;
|
|
898
|
+
}
|
|
899
|
+
if (Array.isArray(content)) {
|
|
900
|
+
next.set(key, { kind: "lines", lines: truncateWidgetLines(content) });
|
|
901
|
+
return next;
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
next.set(key, {
|
|
905
|
+
kind: "component",
|
|
906
|
+
hosted: createHostedExtensionComponent((tui) => content(tui, extensionTheme)),
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
catch (error) {
|
|
910
|
+
setError(`Failed to render extension widget "${key}": ${error instanceof Error ? error.message : String(error)}`);
|
|
911
|
+
}
|
|
912
|
+
return next;
|
|
913
|
+
});
|
|
914
|
+
},
|
|
915
|
+
setFooter: (factory) => {
|
|
916
|
+
if (!factory) {
|
|
917
|
+
setExtensionFooter(null);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
try {
|
|
921
|
+
setExtensionFooter(createHostedExtensionComponent((tui) => factory(tui, extensionTheme, footerDataProvider)));
|
|
922
|
+
}
|
|
923
|
+
catch (error) {
|
|
924
|
+
setError(`Failed to render extension footer: ${error instanceof Error ? error.message : String(error)}`);
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
setHeader: (factory) => {
|
|
928
|
+
if (!factory) {
|
|
929
|
+
setExtensionHeader(null);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
try {
|
|
933
|
+
setExtensionHeader(createHostedExtensionComponent((tui) => factory(tui, extensionTheme)));
|
|
934
|
+
}
|
|
935
|
+
catch (error) {
|
|
936
|
+
setError(`Failed to render extension header: ${error instanceof Error ? error.message : String(error)}`);
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
setTitle: (title) => {
|
|
940
|
+
customTerminalTitleRef.current = title;
|
|
941
|
+
writeTerminalTitle(title);
|
|
942
|
+
},
|
|
943
|
+
custom: async (factory, options) => new Promise((resolve, reject) => {
|
|
944
|
+
let overlayHandleHidden = false;
|
|
945
|
+
const finish = (() => {
|
|
946
|
+
let settled = false;
|
|
947
|
+
return (value) => {
|
|
948
|
+
if (settled) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
settled = true;
|
|
952
|
+
setExtensionOverlayState({ kind: "none" });
|
|
953
|
+
resolve(value);
|
|
954
|
+
};
|
|
955
|
+
})();
|
|
956
|
+
const cancel = () => {
|
|
957
|
+
setExtensionOverlayState({ kind: "none" });
|
|
958
|
+
resolve(undefined);
|
|
959
|
+
};
|
|
960
|
+
cancelExtensionOverlay();
|
|
961
|
+
const bridge = createExtensionTuiBridge();
|
|
962
|
+
Promise.resolve(factory(bridge, extensionTheme, keybindings, finish))
|
|
963
|
+
.then((component) => {
|
|
964
|
+
const hosted = createHostedExtensionComponentFromParts(component, bridge);
|
|
965
|
+
const handle = {
|
|
966
|
+
hide: () => {
|
|
967
|
+
overlayHandleHidden = true;
|
|
968
|
+
setExtensionOverlayState((current) => current.kind === "custom"
|
|
969
|
+
? { ...current, hidden: true }
|
|
970
|
+
: current);
|
|
971
|
+
},
|
|
972
|
+
setHidden: (hidden) => {
|
|
973
|
+
overlayHandleHidden = hidden;
|
|
974
|
+
setExtensionOverlayState((current) => current.kind === "custom"
|
|
975
|
+
? { ...current, hidden }
|
|
976
|
+
: current);
|
|
977
|
+
},
|
|
978
|
+
isHidden: () => overlayHandleHidden,
|
|
979
|
+
};
|
|
980
|
+
options?.onHandle?.(handle);
|
|
981
|
+
setExtensionOverlayState({
|
|
982
|
+
kind: "custom",
|
|
983
|
+
hosted,
|
|
984
|
+
mode: options?.overlay === true ? "overlay" : "inline",
|
|
985
|
+
hidden: false,
|
|
986
|
+
cancel: () => {
|
|
987
|
+
cancel();
|
|
988
|
+
},
|
|
989
|
+
});
|
|
990
|
+
})
|
|
991
|
+
.catch((error) => {
|
|
992
|
+
setExtensionOverlayState({ kind: "none" });
|
|
993
|
+
reject(error);
|
|
994
|
+
});
|
|
995
|
+
}),
|
|
996
|
+
setEditorText: (text) => {
|
|
997
|
+
setInput(text);
|
|
998
|
+
},
|
|
999
|
+
getEditorText: () => inputRef.current,
|
|
1000
|
+
editor: (title, prefill) => new Promise((resolve) => {
|
|
1001
|
+
const finalize = (() => {
|
|
1002
|
+
let settled = false;
|
|
1003
|
+
return (value) => {
|
|
1004
|
+
if (settled) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
settled = true;
|
|
1008
|
+
setExtensionOverlayState({ kind: "none" });
|
|
1009
|
+
resolve(value);
|
|
1010
|
+
};
|
|
1011
|
+
})();
|
|
1012
|
+
cancelExtensionOverlay();
|
|
1013
|
+
setExtensionOverlayState({
|
|
1014
|
+
kind: "editor",
|
|
1015
|
+
title,
|
|
1016
|
+
prefill,
|
|
1017
|
+
resolve: finalize,
|
|
1018
|
+
});
|
|
1019
|
+
}),
|
|
1020
|
+
setEditorComponent: (factory) => {
|
|
1021
|
+
if (!factory) {
|
|
1022
|
+
setExtensionEditor(null);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
try {
|
|
1026
|
+
const hosted = createHostedExtensionComponent((tui) => {
|
|
1027
|
+
const editor = factory(tui, getEditorTheme(), keybindings);
|
|
1028
|
+
editor.onSubmit = (text) => {
|
|
1029
|
+
void submitPromptRef.current(text, "steer");
|
|
1030
|
+
};
|
|
1031
|
+
editor.onChange = (text) => {
|
|
1032
|
+
dispatch({ type: "set-input", value: text, cursorOffset: text.length });
|
|
1033
|
+
};
|
|
1034
|
+
editor.setText(inputRef.current);
|
|
1035
|
+
return editor;
|
|
1036
|
+
});
|
|
1037
|
+
setExtensionEditor(hosted);
|
|
1038
|
+
}
|
|
1039
|
+
catch (error) {
|
|
1040
|
+
setError(`Failed to enable extension editor: ${error instanceof Error ? error.message : String(error)}`);
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
get theme() {
|
|
1044
|
+
return extensionTheme;
|
|
1045
|
+
},
|
|
1046
|
+
getAllThemes: () => getAvailableThemesWithPaths(),
|
|
1047
|
+
getTheme: (name) => getThemeByName(name),
|
|
1048
|
+
setTheme: (themeOrName) => {
|
|
1049
|
+
if (themeOrName instanceof Theme) {
|
|
1050
|
+
setThemeInstance(themeOrName);
|
|
1051
|
+
if (themeOrName.name) {
|
|
1052
|
+
context.session.settingsManager.setTheme(themeOrName.name);
|
|
1053
|
+
dispatch({ type: "set-theme-name", themeName: themeOrName.name });
|
|
1054
|
+
}
|
|
1055
|
+
triggerFooterRefresh();
|
|
1056
|
+
refresh();
|
|
1057
|
+
return { success: true };
|
|
1058
|
+
}
|
|
1059
|
+
const result = setTheme(themeOrName, true);
|
|
1060
|
+
if (result.success) {
|
|
1061
|
+
context.session.settingsManager.setTheme(themeOrName);
|
|
1062
|
+
dispatch({ type: "set-theme-name", themeName: themeOrName });
|
|
1063
|
+
triggerFooterRefresh();
|
|
1064
|
+
refresh();
|
|
1065
|
+
}
|
|
1066
|
+
return result;
|
|
1067
|
+
},
|
|
1068
|
+
});
|
|
1069
|
+
const uiContext = buildExtensionUiContext();
|
|
1070
|
+
extensionUiContextRef.current = uiContext;
|
|
1071
|
+
void context.session.bindExtensions({
|
|
1072
|
+
uiContext,
|
|
1073
|
+
commandContextActions: {
|
|
1074
|
+
waitForIdle: () => context.session.agent.waitForIdle(),
|
|
1075
|
+
newSession: async (options) => {
|
|
1076
|
+
const started = await context.session.newSession({ parentSession: options?.parentSession });
|
|
1077
|
+
if (!started) {
|
|
1078
|
+
return { cancelled: true };
|
|
1079
|
+
}
|
|
1080
|
+
if (options?.setup) {
|
|
1081
|
+
await options.setup(context.session.sessionManager);
|
|
1082
|
+
}
|
|
1083
|
+
resetSessionView();
|
|
1084
|
+
setInput("");
|
|
1085
|
+
setStatus("New session started.", "success");
|
|
1086
|
+
return { cancelled: false };
|
|
1087
|
+
},
|
|
1088
|
+
fork: async (entryId) => {
|
|
1089
|
+
const result = await context.session.fork(entryId);
|
|
1090
|
+
if (result.cancelled) {
|
|
1091
|
+
return { cancelled: true };
|
|
1092
|
+
}
|
|
1093
|
+
resetSessionView();
|
|
1094
|
+
setInput(result.selectedText);
|
|
1095
|
+
setStatus("Forked to new session.", "success");
|
|
1096
|
+
return { cancelled: false };
|
|
1097
|
+
},
|
|
1098
|
+
navigateTree: async (targetId, options) => {
|
|
1099
|
+
const result = await context.session.navigateTree(targetId, {
|
|
1100
|
+
summarize: options?.summarize,
|
|
1101
|
+
customInstructions: options?.customInstructions,
|
|
1102
|
+
replaceInstructions: options?.replaceInstructions,
|
|
1103
|
+
label: options?.label,
|
|
1104
|
+
});
|
|
1105
|
+
if (result.cancelled) {
|
|
1106
|
+
return { cancelled: true };
|
|
1107
|
+
}
|
|
1108
|
+
resetSessionView();
|
|
1109
|
+
if (result.editorText) {
|
|
1110
|
+
setInput(result.editorText);
|
|
1111
|
+
}
|
|
1112
|
+
setStatus("Navigated to selected point.", "success");
|
|
1113
|
+
return { cancelled: false };
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1116
|
+
shutdownHandler: handleExit,
|
|
1117
|
+
onError: (error) => {
|
|
1118
|
+
setError(`Extension error in ${error.extensionPath}: ${error.error}`);
|
|
1119
|
+
},
|
|
1120
|
+
onHookError: (error) => {
|
|
1121
|
+
setError(`Hook error in ${error.hookPath}: ${error.error}`);
|
|
1122
|
+
},
|
|
1123
|
+
});
|
|
1124
|
+
return () => {
|
|
1125
|
+
resetExtensionUi();
|
|
1126
|
+
};
|
|
1127
|
+
}, [context.session, footerDataProvider, keybindings]);
|
|
346
1128
|
useEffect(() => {
|
|
347
1129
|
if (startupRanRef.current)
|
|
348
1130
|
return;
|
|
@@ -370,35 +1152,48 @@ export function AppShell({ context, footerDataProvider, initialImages, initialMe
|
|
|
370
1152
|
}
|
|
371
1153
|
})();
|
|
372
1154
|
}, [context.session, initialImages, initialMessage, initialMessages, refresh]);
|
|
1155
|
+
const extensionUiActive = Boolean(extensionEditor) ||
|
|
1156
|
+
(extensionOverlayState.kind !== "none" &&
|
|
1157
|
+
!(extensionOverlayState.kind === "custom" && extensionOverlayState.hidden));
|
|
373
1158
|
useAppKeybindings({
|
|
374
1159
|
session: context.session,
|
|
375
|
-
overlayActive: state.overlay.kind !== "none",
|
|
1160
|
+
overlayActive: state.overlay.kind !== "none" || extensionUiActive,
|
|
376
1161
|
onExit: handleExit,
|
|
377
|
-
onRefresh: refresh,
|
|
378
1162
|
onOpenSettings: () => setOverlay({ kind: "settings" }),
|
|
379
1163
|
onOpenResume: () => {
|
|
380
|
-
|
|
381
|
-
setOverlay({ kind: "session", sessions: [], loading: true });
|
|
382
|
-
try {
|
|
383
|
-
const sessions = await listSessions(context.session);
|
|
384
|
-
setOverlay({ kind: "session", sessions, loading: false });
|
|
385
|
-
}
|
|
386
|
-
catch (error) {
|
|
387
|
-
setOverlay({
|
|
388
|
-
kind: "session",
|
|
389
|
-
sessions: [],
|
|
390
|
-
loading: false,
|
|
391
|
-
error: error instanceof Error ? error.message : String(error),
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
})();
|
|
1164
|
+
setOverlay({ kind: "session" });
|
|
395
1165
|
},
|
|
396
|
-
onOpenTree: () => setOverlay({ kind: "tree", items: flattenSessionTree(context.session) }),
|
|
397
1166
|
onStatus: setStatus,
|
|
398
1167
|
});
|
|
1168
|
+
useEffect(() => {
|
|
1169
|
+
if (state.overlay.kind !== "session") {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
let cancelled = false;
|
|
1173
|
+
void listCurrentSessions(context.session)
|
|
1174
|
+
.then((sessions) => {
|
|
1175
|
+
if (!cancelled) {
|
|
1176
|
+
setCurrentSessions(sessions);
|
|
1177
|
+
}
|
|
1178
|
+
})
|
|
1179
|
+
.catch((error) => {
|
|
1180
|
+
if (!cancelled) {
|
|
1181
|
+
setError(error instanceof Error ? error.message : String(error));
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
return () => {
|
|
1185
|
+
cancelled = true;
|
|
1186
|
+
};
|
|
1187
|
+
}, [context.session, state.overlay.kind]);
|
|
399
1188
|
const settingsItems = useMemo(() => buildSettingsItems(context.session, state, () => dispatch({ type: "footer-tick" }), setError), [context.session, state]);
|
|
400
1189
|
const maxItems = Math.max(10, terminalRows - (state.overlay.kind === "none" ? 14 : 24));
|
|
1190
|
+
const effectiveStatus = state.status ??
|
|
1191
|
+
(extensionWorkingMessage &&
|
|
1192
|
+
(snapshot.isStreaming || snapshot.isCompacting || snapshot.isBashRunning || snapshot.pendingToolCallCount > 0)
|
|
1193
|
+
? { kind: "busy", text: extensionWorkingMessage }
|
|
1194
|
+
: undefined);
|
|
401
1195
|
let overlay = null;
|
|
1196
|
+
let extensionOverlay = null;
|
|
402
1197
|
switch (state.overlay.kind) {
|
|
403
1198
|
case "settings":
|
|
404
1199
|
overlay = (_jsx(SettingsDialog, { items: settingsItems, onClose: closeOverlay }));
|
|
@@ -459,7 +1254,33 @@ export function AppShell({ context, footerDataProvider, initialImages, initialMe
|
|
|
459
1254
|
} }));
|
|
460
1255
|
break;
|
|
461
1256
|
case "session":
|
|
462
|
-
overlay = (_jsx(SessionDialog, {
|
|
1257
|
+
overlay = (_jsx(SessionDialog, { onClose: closeOverlay, currentSessionPath: context.session.sessionFile, onDeleteSession: async (item) => {
|
|
1258
|
+
try {
|
|
1259
|
+
if (item.path === context.session.sessionFile) {
|
|
1260
|
+
setStatus("Cannot delete the current active session from the picker.", "warning");
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
await deleteSessionAtPath(item.path);
|
|
1264
|
+
setCurrentSessions((sessions) => sessions.filter((session) => session.path !== item.path));
|
|
1265
|
+
setStatus("Deleted session.", "success");
|
|
1266
|
+
}
|
|
1267
|
+
catch (error) {
|
|
1268
|
+
setError(error instanceof Error ? error.message : String(error));
|
|
1269
|
+
}
|
|
1270
|
+
}, onLoadAllSessions: (onProgress) => listAllSessions(onProgress), onRenameSession: async (item, name) => {
|
|
1271
|
+
try {
|
|
1272
|
+
const resolvedName = await renameSessionAtPath(item.path, name);
|
|
1273
|
+
setCurrentSessions((sessions) => sessions.map((session) => session.path === item.path
|
|
1274
|
+
? { ...session, name: resolvedName, modified: new Date(), lastModified: Date.now() }
|
|
1275
|
+
: session));
|
|
1276
|
+
if (item.path === context.session.sessionFile)
|
|
1277
|
+
refresh();
|
|
1278
|
+
setStatus(`Renamed session to ${resolvedName}.`, "success");
|
|
1279
|
+
}
|
|
1280
|
+
catch (error) {
|
|
1281
|
+
setError(error instanceof Error ? error.message : String(error));
|
|
1282
|
+
}
|
|
1283
|
+
}, onSelect: async (item) => {
|
|
463
1284
|
try {
|
|
464
1285
|
await context.session.switchSession(item.path);
|
|
465
1286
|
closeOverlay();
|
|
@@ -469,7 +1290,7 @@ export function AppShell({ context, footerDataProvider, initialImages, initialMe
|
|
|
469
1290
|
catch (error) {
|
|
470
1291
|
setError(error instanceof Error ? error.message : String(error));
|
|
471
1292
|
}
|
|
472
|
-
},
|
|
1293
|
+
}, currentSessions: currentSessions }));
|
|
473
1294
|
break;
|
|
474
1295
|
case "tree":
|
|
475
1296
|
overlay = (_jsx(TreeDialog, { items: state.overlay.items, onClose: closeOverlay, onSelect: async (item) => {
|
|
@@ -568,6 +1389,32 @@ export function AppShell({ context, footerDataProvider, initialImages, initialMe
|
|
|
568
1389
|
case "none":
|
|
569
1390
|
break;
|
|
570
1391
|
}
|
|
1392
|
+
switch (extensionOverlayState.kind) {
|
|
1393
|
+
case "select":
|
|
1394
|
+
extensionOverlay = (_jsx(ExtensionSelectDialog, { onClose: () => extensionOverlayState.resolve(undefined), onSelect: (value) => extensionOverlayState.resolve(value), options: extensionOverlayState.options, timeoutAt: extensionOverlayState.timeoutAt, title: extensionOverlayState.title }));
|
|
1395
|
+
break;
|
|
1396
|
+
case "confirm":
|
|
1397
|
+
extensionOverlay = (_jsx(ExtensionSelectDialog, { onClose: () => extensionOverlayState.resolve(false), onSelect: (value) => extensionOverlayState.resolve(value === "Yes"), options: ["Yes", "No"], timeoutAt: extensionOverlayState.timeoutAt, title: `${extensionOverlayState.title}\n${extensionOverlayState.message}` }));
|
|
1398
|
+
break;
|
|
1399
|
+
case "input":
|
|
1400
|
+
extensionOverlay = (_jsx(ExtensionTextDialog, { onClose: () => extensionOverlayState.resolve(undefined), onSubmit: (value) => extensionOverlayState.resolve(value), placeholder: extensionOverlayState.placeholder, timeoutAt: extensionOverlayState.timeoutAt, title: extensionOverlayState.title }));
|
|
1401
|
+
break;
|
|
1402
|
+
case "editor":
|
|
1403
|
+
extensionOverlay = (_jsx(ExtensionTextDialog, { multiline: true, onClose: () => extensionOverlayState.resolve(undefined), onSubmit: (value) => extensionOverlayState.resolve(value), prefill: extensionOverlayState.prefill, timeoutAt: extensionOverlayState.timeoutAt, title: extensionOverlayState.title }));
|
|
1404
|
+
break;
|
|
1405
|
+
case "custom":
|
|
1406
|
+
if (extensionOverlayState.mode === "overlay" && !extensionOverlayState.hidden) {
|
|
1407
|
+
extensionOverlay = (_jsx(ExtensionComponentHost, { active: true, hosted: extensionOverlayState.hosted, width: terminalColumns }));
|
|
1408
|
+
}
|
|
1409
|
+
break;
|
|
1410
|
+
case "none":
|
|
1411
|
+
break;
|
|
1412
|
+
}
|
|
1413
|
+
useEffect(() => {
|
|
1414
|
+
if (!startupDismissed && snapshot.messages.length > 0) {
|
|
1415
|
+
setStartupDismissed(true);
|
|
1416
|
+
}
|
|
1417
|
+
}, [snapshot.messages.length, startupDismissed]);
|
|
571
1418
|
useEffect(() => {
|
|
572
1419
|
const nextShowThinking = !context.session.settingsManager.getHideThinkingBlock();
|
|
573
1420
|
const nextShowImages = context.session.settingsManager.getShowImages();
|
|
@@ -582,18 +1429,37 @@ export function AppShell({ context, footerDataProvider, initialImages, initialMe
|
|
|
582
1429
|
dispatch({ type: "set-theme-name", themeName: nextThemeName });
|
|
583
1430
|
}
|
|
584
1431
|
}, [context.session, state.footerTick, state.showImages, state.showThinking, state.themeName]);
|
|
585
|
-
|
|
1432
|
+
useEffect(() => {
|
|
1433
|
+
if (previousStartupDiagnosticsRef.current && !showStartupDiagnostics && process.stdout.isTTY) {
|
|
1434
|
+
process.stdout.write("\u001B[2J\u001B[3J\u001B[H");
|
|
1435
|
+
setLayoutEpoch((current) => current + 1);
|
|
1436
|
+
}
|
|
1437
|
+
previousStartupDiagnosticsRef.current = showStartupDiagnostics;
|
|
1438
|
+
}, [showStartupDiagnostics]);
|
|
1439
|
+
return (_jsxs(Box, { flexDirection: "column", children: [extensionHeader ? (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(ExtensionComponentHost, { hosted: extensionHeader, width: terminalColumns }) })) : (_jsx(Header, { collapseChangelog: context.session.settingsManager.getCollapseChangelog(), sessionName: snapshot.sessionName, showBranding: Boolean(verbose) || !context.session.settingsManager.getQuietStartup(), startupChangelog: startupChangelog, startupDiagnostics: showStartupDiagnostics ? startupDiagnostics : undefined, startupNotices: startupNotices, theme: theme })), _jsx(MessageList, { displayBlocks: displayBlocks, expandToolOutputs: toolOutputExpanded, maxItems: maxItems, messages: snapshot.messages, showImages: state.showImages, showThinking: state.showThinking, theme: theme }), _jsx(TaskPanel, { expandToolOutputs: toolOutputExpanded, pendingMessages: pendingMessages, snapshot: snapshot, theme: theme, toolExecutions: state.toolExecutions }), _jsx(StatusLine, { snapshot: snapshot, status: effectiveStatus, theme: theme }), Array.from(extensionWidgetsAbove.entries()).map(([key, widget]) => widget.kind === "lines" ? (_jsx(Box, { marginTop: 1, children: renderTextLines(widget.lines, `widget-above:${key}`) }, `widget-above:${key}`)) : (_jsx(Box, { marginTop: 1, children: _jsx(ExtensionComponentHost, { hosted: widget.hosted, maxLines: 10, width: terminalColumns }) }, `widget-above:${key}`))), extensionEditor ? (_jsx(Box, { marginTop: 1, children: _jsx(ExtensionComponentHost, { active: true, hosted: extensionEditor, onKeyData: handleExtensionShortcutKeyData, width: terminalColumns }) })) : extensionOverlayState.kind === "custom" &&
|
|
1440
|
+
extensionOverlayState.mode === "inline" &&
|
|
1441
|
+
!extensionOverlayState.hidden ? (_jsx(Box, { marginTop: 1, children: _jsx(ExtensionComponentHost, { active: true, hosted: extensionOverlayState.hosted, width: terminalColumns }) })) : (_jsx(PromptInput, { cursorOffset: state.cursorOffset, disabled: state.overlay.kind !== "none" || extensionUiActive, dispatch: dispatch, input: state.input, keybindings: keybindings, onCancelActiveWork: async (kind) => {
|
|
586
1442
|
if (kind === "bash") {
|
|
587
1443
|
context.session.abortBash();
|
|
588
1444
|
setStatus("Cancelled bash command.", "warning");
|
|
589
1445
|
return;
|
|
590
1446
|
}
|
|
591
|
-
|
|
1447
|
+
if (context.session.isCompacting) {
|
|
1448
|
+
context.session.abortCompaction();
|
|
1449
|
+
setStatus("Cancelling compaction...", "warning");
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
const restored = restoreQueuedMessagesToPrompt({ abort: true });
|
|
592
1453
|
refresh();
|
|
593
|
-
|
|
594
|
-
|
|
1454
|
+
if (restored > 0) {
|
|
1455
|
+
setStatus(`Cancelled active work and restored ${restored} queued message${restored === 1 ? "" : "s"}.`, "warning");
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
setStatus("Cancelled active work.", "warning");
|
|
1459
|
+
}
|
|
1460
|
+
}, onCycleModel: cycleModel, onDequeue: dequeueQueuedMessages, onExit: handleExit, onExternalEditor: openExternalEditor, onKeyData: handleExtensionShortcutKeyData, onOpenModelSelector: () => setOverlay({ kind: "model" }), onOpenFork: () => setOverlay({ kind: "userMessage", items: listUserMessages(context.session) }), onOpenTree: () => setOverlay({ kind: "tree", items: flattenSessionTree(context.session) }), onPasteClipboardImage: pasteClipboardImage, onStatus: setStatus, onSubmit: (value) => submitPromptWithStartupDismiss(value ?? state.input, "steer"), onSubmitFollowUp: (value) => submitPromptWithStartupDismiss(value ?? state.input, "followUp"), onSubmitSteer: (value) => submitPromptWithStartupDismiss(value ?? state.input, "steer"), onSuspend: suspendTerminal, onThinkingLevelChange: (level) => {
|
|
595
1461
|
refresh();
|
|
596
1462
|
setStatus(`Thinking level: ${level}`, "success");
|
|
597
|
-
}, session: context.session, theme: theme }), _jsx(Footer, { availableProviderCount: footerData.availableProviderCount, branch: footerData.branch, snapshot: snapshot, theme: theme }), overlay, state.overlay.kind === "none" ? null : _jsx(Text, { children: theme.muted("Dialogs pause typing in the main prompt until they close.") })] }));
|
|
1463
|
+
}, onToggleThinkingVisibility: toggleThinkingVisibility, onToggleToolExpansion: toggleToolOutputExpansion, session: context.session, theme: theme })), Array.from(extensionWidgetsBelow.entries()).map(([key, widget]) => widget.kind === "lines" ? (_jsx(Box, { marginTop: 1, children: renderTextLines(widget.lines, `widget-below:${key}`) }, `widget-below:${key}`)) : (_jsx(Box, { marginTop: 1, children: _jsx(ExtensionComponentHost, { hosted: widget.hosted, maxLines: 10, width: terminalColumns }) }, `widget-below:${key}`))), extensionFooter ? (_jsx(ExtensionComponentHost, { hosted: extensionFooter, width: terminalColumns })) : (_jsx(Footer, { availableProviderCount: footerData.availableProviderCount, branch: footerData.branch, extensionStatuses: footerData.extensionStatuses, snapshot: snapshot, theme: theme })), overlay, extensionOverlay, state.overlay.kind === "none" && extensionOverlayState.kind === "none" ? null : (_jsx(Text, { children: theme.muted("Dialogs pause typing in the main prompt until they close.") }))] }, layoutEpoch));
|
|
598
1464
|
}
|
|
599
1465
|
//# sourceMappingURL=AppShell.js.map
|