hoomanjs 1.31.0 → 1.32.0

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 (42) hide show
  1. package/README.md +21 -11
  2. package/dist/chat/app.d.ts +3 -1
  3. package/dist/chat/app.js +191 -87
  4. package/dist/chat/app.js.map +1 -1
  5. package/dist/chat/components/BottomChrome.d.ts +2 -1
  6. package/dist/chat/components/BottomChrome.js +2 -2
  7. package/dist/chat/components/BottomChrome.js.map +1 -1
  8. package/dist/chat/components/ChatMessage.js +1 -1
  9. package/dist/chat/components/ChatMessage.js.map +1 -1
  10. package/dist/chat/components/StatusBar.d.ts +2 -1
  11. package/dist/chat/components/StatusBar.js +2 -2
  12. package/dist/chat/components/StatusBar.js.map +1 -1
  13. package/dist/chat/components/ThoughtEvent.js +1 -1
  14. package/dist/chat/components/ThoughtEvent.js.map +1 -1
  15. package/dist/chat/components/ToolEvent.js +1 -1
  16. package/dist/chat/components/ToolEvent.js.map +1 -1
  17. package/dist/chat/components/ToolEventFileResult.js +1 -1
  18. package/dist/chat/components/ToolEventFileResult.js.map +1 -1
  19. package/dist/chat/components/Transcript.d.ts +13 -10
  20. package/dist/chat/components/Transcript.js +15 -76
  21. package/dist/chat/components/Transcript.js.map +1 -1
  22. package/dist/chat/components/markdown/BlockRenderer.d.ts +2 -2
  23. package/dist/chat/index.d.ts +5 -1
  24. package/dist/chat/index.js +21 -19
  25. package/dist/chat/index.js.map +1 -1
  26. package/dist/cli.js +58 -38
  27. package/dist/cli.js.map +1 -1
  28. package/dist/configure/app.js +4 -5
  29. package/dist/configure/app.js.map +1 -1
  30. package/dist/configure/index.js +9 -0
  31. package/dist/configure/index.js.map +1 -1
  32. package/dist/core/agent/index.d.ts +3 -1
  33. package/dist/core/agent/index.js +21 -4
  34. package/dist/core/agent/index.js.map +1 -1
  35. package/dist/core/prompts/system.d.ts +4 -3
  36. package/dist/core/prompts/system.js +10 -9
  37. package/dist/core/prompts/system.js.map +1 -1
  38. package/dist/core/skills/built-in/hooman-config/SKILL.md +1 -1
  39. package/package.json +1 -1
  40. package/dist/configure/components/HomeScreen.d.ts +0 -12
  41. package/dist/configure/components/HomeScreen.js +0 -7
  42. package/dist/configure/components/HomeScreen.js.map +0 -1
package/README.md CHANGED
@@ -21,7 +21,7 @@ It gives you a practical toolkit to build and run agent workflows:
21
21
  - a one-shot `exec` command for single prompts
22
22
  - a stateful `chat` interface for iterative sessions
23
23
  - a `daemon` command for channel-driven MCP automation
24
- - an Ink-powered `configure` workflow for app config, prompts, MCP servers, and installed skills
24
+ - an in-chat `/config` workflow (Ink-powered) for app config, prompts, MCP servers, and installed skills
25
25
  - an `acp` command for running Hooman as an Agent Client Protocol (ACP) agent over stdio
26
26
 
27
27
  ## Related
@@ -53,7 +53,6 @@ It gives you a practical toolkit to build and run agent workflows:
53
53
  Fastest way to get started without cloning the repo:
54
54
 
55
55
  ```bash
56
- npx hoomanjs configure
57
56
  npx hoomanjs
58
57
 
59
58
  # or install globally
@@ -63,14 +62,13 @@ npm i -g hoomanjs
63
62
  Or with Bun:
64
63
 
65
64
  ```bash
66
- bunx hoomanjs configure
67
65
  bunx hoomanjs
68
66
  ```
69
67
 
70
68
  Recommended first run:
71
69
 
72
- 1. Run `hooman configure` to choose your LLM provider and model.
73
- 2. Start chatting with `hooman` (same as `hooman chat`).
70
+ 1. Start chatting with `hooman` (same as `hooman chat`).
71
+ 2. Run `/config` in chat to choose your LLM provider and model, and to manage MCP servers and skills.
74
72
  3. Use `hooman exec "your prompt"` for one-off tasks.
75
73
 
76
74
  ## Must have
@@ -182,6 +180,18 @@ Start in ask mode:
182
180
  hooman chat --mode ask
183
181
  ```
184
182
 
183
+ ### Chat commands
184
+
185
+ Inside an interactive `chat` session, type `/` to discover slash commands:
186
+
187
+ - `/model` - pick or set the chat model for this session.
188
+ - `/mode` - switch the session mode (`agent`, `ask`, `plan`); see [Session mode](#session-mode).
189
+ - `/yolo` - toggle auto-approve of tool calls (`on` / `off`).
190
+ - `/init` - generate or refresh `AGENTS.md` for the current project.
191
+ - `/compact` - compact the conversation history now and persist the result.
192
+ - `/new` - start a fresh chat session.
193
+ - `/config` - launch the configuration workflow (see below).
194
+
185
195
  ### Session mode
186
196
 
187
197
  `exec`, `chat`, and `daemon` accept **`-m` / `--mode`** with:
@@ -243,15 +253,15 @@ Runtime tool and prompt switches are controlled from `config.json`:
243
253
  - `tools.agents.enabled` (enables built-in `run_subagents` tool)
244
254
  - `tools.agents.concurrency` (defaults to `3` when omitted on load; a freshly generated default `config.json` uses `2`)
245
255
 
246
- ### `hooman configure`
256
+ ### `/config`
247
257
 
248
- Open the Ink configuration workflow.
258
+ The configuration workflow is launched from inside a `chat` session with the `/config` slash command (there is no separate top-level `configure` command). It takes over the terminal on the alternate screen buffer while open, and restores the chat session on exit. Any config changes are picked up when the session re-bootstraps.
249
259
 
250
- ```bash
251
- hooman configure
260
+ ```text
261
+ /config
252
262
  ```
253
263
 
254
- The configure UI currently lets you:
264
+ The configuration UI currently lets you:
255
265
 
256
266
  - edit app configuration values
257
267
  - choose search provider and set its API key
@@ -751,7 +761,7 @@ The local skills folder is treated as a parent directory of skill subdirectories
751
761
 
752
762
  When a session starts, the plugin injects available skill metadata into the system prompt and exposes the `skills` tool so the model can activate a skill and load its full instructions on demand.
753
763
 
754
- The configure workflow can:
764
+ The `/config` workflow can:
755
765
 
756
766
  - search the public skills catalog
757
767
  - install a skill from a source string, repo, URL, or local path
@@ -15,6 +15,8 @@ type ChatAppProps = {
15
15
  steering: ChatTurnSteeringController;
16
16
  prompt?: string;
17
17
  onExit: () => void;
18
+ onNewSession: () => void;
19
+ onConfigure: () => void;
18
20
  };
19
- export declare function ChatApp({ agent, config, sessionId, manager, registry, approvals, steering, prompt, onExit, }: ChatAppProps): React.JSX.Element;
21
+ export declare function ChatApp({ agent, config, sessionId, manager, registry, approvals, steering, prompt, onExit, onNewSession, onConfigure, }: ChatAppProps): React.JSX.Element;
20
22
  export {};
package/dist/chat/app.js CHANGED
@@ -1,19 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
3
3
  import fastq from "fastq";
4
- import { Box, useApp, useInput, useWindowSize } from "ink";
4
+ import { Box, Static, useApp, useInput, useWindowSize } from "ink";
5
5
  import { Message, TextBlock, ToolResultBlock, ToolUseBlock, } from "@strands-agents/sdk";
6
6
  import { accumulateUsage, createEmptyUsage, } from "../core/utils/strands-usage-accumulate.js";
7
7
  import { modelProviders } from "../core/models/index.js";
8
8
  import { formatModeNames, isKnownSessionMode } from "../core/modes/index.js";
9
9
  import { takeFileToolDisplay } from "../core/state/file-tool-display.js";
10
10
  import { BottomChrome } from "./components/BottomChrome.js";
11
- import { Transcript } from "./components/Transcript.js";
11
+ import { LiveTranscript, TranscriptLine } from "./components/Transcript.js";
12
+ import { EmptyChatBanner } from "./components/EmptyChatBanner.js";
12
13
  import { getTodoViewState } from "../core/state/todos.js";
13
14
  import { isExitRequested } from "../core/state/exit-request.js";
14
15
  import { getModeState, setSessionMode, } from "../core/state/session-mode.js";
15
16
  import { isYoloEnabled, setYoloEnabled } from "../core/state/yolo.js";
16
17
  import { applySessionMode } from "../core/agent/sync-tool-registry-mode.js";
18
+ import { getAgentConversationManager, getAgentSessionManager, } from "../core/agent/index.js";
17
19
  import { attachmentPathsToPromptBlocks } from "../core/utils/attachments.js";
18
20
  import { isMouseInput } from "./mouse.js";
19
21
  import { readBundledPrompt } from "../core/prompts/bundled.js";
@@ -237,6 +239,14 @@ function listModelsText(config) {
237
239
  ].join("\n");
238
240
  }
239
241
  const SLASH_COMMANDS = [
242
+ {
243
+ name: "compact",
244
+ description: "Compact conversation history now.",
245
+ },
246
+ {
247
+ name: "config",
248
+ description: "Launch the configuration flow.",
249
+ },
240
250
  {
241
251
  name: "init",
242
252
  description: "Generate or refresh AGENTS.md for this project.",
@@ -249,6 +259,10 @@ const SLASH_COMMANDS = [
249
259
  name: "model",
250
260
  description: "Pick or set the chat model.",
251
261
  },
262
+ {
263
+ name: "new",
264
+ description: "Start a new chat session.",
265
+ },
252
266
  {
253
267
  name: "yolo",
254
268
  description: "Auto-approve tools (on|off).",
@@ -268,36 +282,7 @@ function matchingSlashCommands(input) {
268
282
  }
269
283
  return SLASH_COMMANDS.filter((item) => item.name.startsWith(query));
270
284
  }
271
- function estimateChromeRows(params) {
272
- let rows = 8;
273
- if (params.running && params.todoCount > 0) {
274
- rows += 2 + params.todoCount;
275
- }
276
- if (params.queueCount > 0) {
277
- rows += 2 + params.queueCount;
278
- }
279
- if (params.pendingApproval) {
280
- rows += 4;
281
- return rows;
282
- }
283
- if (params.picker === "model") {
284
- rows += 2 + 4;
285
- return rows;
286
- }
287
- if (params.picker === "yolo") {
288
- rows += 2 + 2;
289
- return rows;
290
- }
291
- if (params.picker === "mode") {
292
- rows += 2 + 3;
293
- return rows;
294
- }
295
- if (params.slashCommandCount > 0) {
296
- rows += params.slashCommandCount + 1;
297
- }
298
- return rows;
299
- }
300
- export function ChatApp({ agent, config, sessionId, manager, registry, approvals, steering, prompt, onExit, }) {
285
+ export function ChatApp({ agent, config, sessionId, manager, registry, approvals, steering, prompt, onExit, onNewSession, onConfigure, }) {
301
286
  const { exit } = useApp();
302
287
  const windowSize = useWindowSize();
303
288
  const [input, setInput] = useState("");
@@ -318,7 +303,7 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
318
303
  }, []);
319
304
  const [slashHighlightIndex, setSlashHighlightIndex] = useState(0);
320
305
  const [queuedPrompts, setQueuedPrompts] = useState([]);
321
- const [transcriptScrollOffset, setTranscriptScrollOffset] = useState(0);
306
+ const [mcpNeedsAttention, setMcpNeedsAttention] = useState(false);
322
307
  const [todoState, setTodoState] = useState(() => getTodoViewState(agent));
323
308
  const mountedRef = useRef(true);
324
309
  const runningRef = useRef(false);
@@ -354,6 +339,27 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
354
339
  cancelled = true;
355
340
  };
356
341
  }, [registry]);
342
+ // Surface MCP OAuth state in the status bar. Re-checked whenever a turn ends,
343
+ // since a tool call may have triggered (or completed) an auth flow mid-session.
344
+ useEffect(() => {
345
+ let cancelled = false;
346
+ void manager
347
+ .listAuthStatuses()
348
+ .then((rows) => {
349
+ if (cancelled) {
350
+ return;
351
+ }
352
+ setMcpNeedsAttention(rows.some((row) => row.status === "unauthenticated" || row.status === "expired"));
353
+ })
354
+ .catch(() => {
355
+ if (!cancelled) {
356
+ setMcpNeedsAttention(false);
357
+ }
358
+ });
359
+ return () => {
360
+ cancelled = true;
361
+ };
362
+ }, [manager, running]);
357
363
  useEffect(() => {
358
364
  if (!running || turnStartedAt === null) {
359
365
  setTurnElapsedMs(0);
@@ -427,6 +433,9 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
427
433
  const updateLine = useCallback((id, patch) => {
428
434
  setLines((prev) => prev.map((line) => (line.id === id ? { ...line, ...patch } : line)));
429
435
  }, []);
436
+ const removeLine = useCallback((id) => {
437
+ setLines((prev) => prev.filter((line) => line.id !== id));
438
+ }, []);
430
439
  const moveLineToEnd = useCallback((id) => {
431
440
  setLines((prev) => {
432
441
  const index = prev.findIndex((line) => line.id === id);
@@ -463,11 +472,17 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
463
472
  if (!id) {
464
473
  return;
465
474
  }
466
- updateLine(id, { done: true });
475
+ const text = `${assistantCommittedTextRef.current}${streamedAssistantBlockRef.current ?? ""}`;
476
+ if (text.trim().length === 0) {
477
+ removeLine(id);
478
+ }
479
+ else {
480
+ updateLine(id, { done: true });
481
+ }
467
482
  assistantLineIdRef.current = null;
468
483
  assistantCommittedTextRef.current = "";
469
484
  streamedAssistantBlockRef.current = null;
470
- }, [updateLine]);
485
+ }, [removeLine, updateLine]);
471
486
  const ensureAssistantLine = useCallback(() => {
472
487
  const existing = assistantLineIdRef.current;
473
488
  if (existing) {
@@ -652,6 +667,95 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
652
667
  applySessionMode(agent);
653
668
  bumpSessionChrome();
654
669
  }, [agent, appendLine, bumpSessionChrome]);
670
+ const handleCompactCommand = useCallback(async () => {
671
+ if (runningRef.current) {
672
+ appendLine({
673
+ id: nowId(),
674
+ role: "system",
675
+ title: "compact",
676
+ content: "Wait for the active turn to finish before compacting history.",
677
+ done: true,
678
+ });
679
+ return;
680
+ }
681
+ const conversationManager = getAgentConversationManager(agent);
682
+ if (!conversationManager) {
683
+ appendLine({
684
+ id: nowId(),
685
+ role: "system",
686
+ title: "compact",
687
+ content: "This session does not have a conversation manager to compact.",
688
+ done: true,
689
+ });
690
+ return;
691
+ }
692
+ const beforeCount = agent.messages.length;
693
+ try {
694
+ const reduced = await conversationManager.reduce({
695
+ agent,
696
+ model: agent.model,
697
+ });
698
+ const afterCount = agent.messages.length;
699
+ if (!reduced) {
700
+ appendLine({
701
+ id: nowId(),
702
+ role: "system",
703
+ title: "compact",
704
+ content: "Conversation history is already too short to compact.",
705
+ done: true,
706
+ });
707
+ return;
708
+ }
709
+ await getAgentSessionManager(agent)?.saveSnapshot({
710
+ target: agent,
711
+ isLatest: true,
712
+ });
713
+ appendLine({
714
+ id: nowId(),
715
+ role: "system",
716
+ title: "compact",
717
+ content: `Compacted conversation history for future turns (${beforeCount} messages -> ${afterCount}).`,
718
+ done: true,
719
+ });
720
+ }
721
+ catch (error) {
722
+ appendLine({
723
+ id: nowId(),
724
+ role: "system",
725
+ title: "compact",
726
+ content: error instanceof Error ? error.message : String(error),
727
+ done: true,
728
+ });
729
+ }
730
+ }, [agent, appendLine]);
731
+ const handleNewCommand = useCallback(() => {
732
+ if (runningRef.current) {
733
+ appendLine({
734
+ id: nowId(),
735
+ role: "system",
736
+ title: "new",
737
+ content: "Wait for the active turn to finish before starting a new session.",
738
+ done: true,
739
+ });
740
+ return;
741
+ }
742
+ onNewSession();
743
+ exit();
744
+ }, [appendLine, exit, onNewSession]);
745
+ const handleConfigCommand = useCallback(() => {
746
+ if (runningRef.current) {
747
+ appendLine({
748
+ id: nowId(),
749
+ role: "system",
750
+ title: "config",
751
+ content: "Wait for the active turn to finish before launching configuration.",
752
+ done: true,
753
+ });
754
+ return;
755
+ }
756
+ onConfigure();
757
+ exit();
758
+ }, [appendLine, exit, onConfigure]);
655
759
  const runTurn = useCallback(async (prompt) => {
656
760
  const trimmed = prompt.text.trim();
657
761
  if (!trimmed && prompt.attachments.length === 0) {
@@ -895,7 +999,6 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
895
999
  id: nowId(),
896
1000
  prompt: { ...normalized, text: trimmed },
897
1001
  };
898
- setTranscriptScrollOffset(0);
899
1002
  setQueuedPrompts((prev) => [...prev, item]);
900
1003
  void queueRef.current?.push(item).catch((error) => {
901
1004
  if (!mountedRef.current) {
@@ -968,6 +1071,21 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
968
1071
  setInput("");
969
1072
  return;
970
1073
  }
1074
+ if (command.name === "compact") {
1075
+ void handleCompactCommand();
1076
+ setInput("");
1077
+ return;
1078
+ }
1079
+ if (command.name === "config") {
1080
+ handleConfigCommand();
1081
+ setInput("");
1082
+ return;
1083
+ }
1084
+ if (command.name === "new") {
1085
+ handleNewCommand();
1086
+ setInput("");
1087
+ return;
1088
+ }
971
1089
  }
972
1090
  if (pushPrompt(value)) {
973
1091
  setInput("");
@@ -975,7 +1093,10 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
975
1093
  }, [
976
1094
  appendLine,
977
1095
  handleModelCommand,
1096
+ handleCompactCommand,
1097
+ handleConfigCommand,
978
1098
  handleModeCommand,
1099
+ handleNewCommand,
979
1100
  handleYoloCommand,
980
1101
  pendingApproval,
981
1102
  pushPrompt,
@@ -985,24 +1106,9 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
985
1106
  if (isMouseInput(inputKey)) {
986
1107
  return;
987
1108
  }
988
- if (!key.ctrl && !key.meta && !key.super) {
989
- if (key.pageUp) {
990
- setTranscriptScrollOffset((offset) => Math.min(Math.max(0, lines.length - 1), offset + transcriptScrollStep));
991
- return;
992
- }
993
- if (key.pageDown) {
994
- setTranscriptScrollOffset((offset) => Math.max(0, offset - transcriptScrollStep));
995
- return;
996
- }
997
- if (key.home) {
998
- setTranscriptScrollOffset(Math.max(0, lines.length - 1));
999
- return;
1000
- }
1001
- if (key.end) {
1002
- setTranscriptScrollOffset(0);
1003
- return;
1004
- }
1005
- }
1109
+ // Scrolling is handled natively by the terminal: finished transcript
1110
+ // lines are flushed to real scrollback via <Static>, so the user scrolls
1111
+ // with their mouse/trackpad/terminal like any other command output.
1006
1112
  if (key.ctrl && inputKey.toLowerCase() === "c") {
1007
1113
  if (runningRef.current) {
1008
1114
  agent.cancel();
@@ -1041,38 +1147,36 @@ export function ChatApp({ agent, config, sessionId, manager, registry, approvals
1041
1147
  return `${String(min).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
1042
1148
  }, [turnElapsedMs]);
1043
1149
  const inputHint = running && queuedPrompts.length > 0 ? INPUT_HINT_WITH_STEERING : INPUT_HINT;
1044
- const transcriptRows = useMemo(() => Math.max(6, windowSize.rows -
1045
- estimateChromeRows({
1046
- running,
1047
- todoCount: todoState.visible ? todoState.todos.length : 0,
1048
- queueCount: queuedPrompts.length,
1049
- pendingApproval: Boolean(pendingApproval),
1050
- picker,
1051
- slashCommandCount: !pendingApproval && !picker ? slashCommands.length : 0,
1052
- }) -
1053
- 2), [
1054
- pendingApproval,
1055
- picker,
1056
- queuedPrompts.length,
1057
- running,
1058
- slashCommands.length,
1059
- todoState.todos.length,
1060
- todoState.visible,
1061
- windowSize.rows,
1062
- ]);
1063
- const transcriptScrollStep = useMemo(() => Math.max(1, Math.floor(transcriptRows / 3)), [transcriptRows]);
1064
- useEffect(() => {
1065
- setTranscriptScrollOffset((offset) => Math.min(offset, lines.length - 1));
1066
- }, [lines.length]);
1067
- return (_jsxs(Box, { flexDirection: "column", width: "100%", paddingX: 1, height: Math.max(1, windowSize.rows - 1), children: [_jsx(Transcript, { lines: lines, assistantName: config.name, showEmptyBanner: lines.length === 0, marginTop: 1, maxRows: transcriptRows, scrollOffset: transcriptScrollOffset }), _jsx(BottomChrome, { config: config, running: running, status: status, sessionId: sessionId, currentModel: currentModelLabel(config), yoloOn: isYoloEnabled(agent), sessionMode: getModeState(agent).mode, elapsedLabel: elapsedLabel, turnCount: turnCount, totalTools: totalTools, skillsFound: skillsFound, manager: manager, usage: usage, todoState: todoState, queuedPrompts: queuedPrompts, pendingApproval: Boolean(pendingApproval), picker: picker, slashCommands: slashCommands, slashHighlightIndex: slashHighlightIndex, input: input, inputHint: inputHint, slashMenu: slashMenu, onApprovalDecision: (decision) => approvals.decide(decision), onModelSelect: (name) => {
1068
- setPicker(null);
1069
- void handleModelCommand(name);
1070
- }, onYoloSelect: (value) => {
1071
- setPicker(null);
1072
- void handleYoloCommand(value);
1073
- }, onModeSelect: (value) => {
1074
- setPicker(null);
1075
- void handleModeCommand(value);
1076
- }, onInputChange: setInput, onSubmit: onSubmit })] }));
1150
+ // Split the transcript at the first not-yet-finalized entry. Everything before
1151
+ // it is final and append-only, so it can be flushed to the terminal scrollback
1152
+ // via <Static> (printed once, never re-rendered). Everything from there on is
1153
+ // the live tail that Ink keeps re-rendering: streaming text, running tools,
1154
+ // active reasoning. Using the done-prefix (rather than per-line `done`)
1155
+ // guarantees Static only ever grows and stays in chronological order even when
1156
+ // tools finish out of order.
1157
+ const committedCount = useMemo(() => {
1158
+ let end = 0;
1159
+ while (end < lines.length && lines[end]?.done) {
1160
+ end += 1;
1161
+ }
1162
+ return end;
1163
+ }, [lines]);
1164
+ const committedLines = useMemo(() => lines.slice(0, committedCount), [lines, committedCount]);
1165
+ const liveLines = useMemo(() => lines.slice(committedCount), [lines, committedCount]);
1166
+ // Before the first prompt there is nothing in scrollback yet, so grow the live
1167
+ // region to the full viewport and vertically center the banner (with the
1168
+ // composer pinned to the bottom). Once any line exists the region collapses to
1169
+ // its natural height and finished lines flow into the terminal scrollback.
1170
+ const isEmpty = lines.length === 0;
1171
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsx(Static, { items: committedLines, children: (line) => (_jsx(Box, { paddingX: 1, children: _jsx(TranscriptLine, { line: line, assistantName: config.name }) }, line.id)) }), _jsxs(Box, { flexDirection: "column", width: "100%", paddingX: 1, ...(isEmpty ? { height: Math.max(1, windowSize.rows - 1) } : {}), children: [isEmpty ? (_jsx(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "center", children: _jsx(EmptyChatBanner, {}) })) : null, _jsx(LiveTranscript, { lines: liveLines, assistantName: config.name }), _jsx(BottomChrome, { config: config, running: running, status: status, sessionId: sessionId, currentModel: currentModelLabel(config), yoloOn: isYoloEnabled(agent), sessionMode: getModeState(agent).mode, elapsedLabel: elapsedLabel, turnCount: turnCount, totalTools: totalTools, skillsFound: skillsFound, manager: manager, mcpNeedsAttention: mcpNeedsAttention, usage: usage, todoState: todoState, queuedPrompts: queuedPrompts, pendingApproval: Boolean(pendingApproval), picker: picker, slashCommands: slashCommands, slashHighlightIndex: slashHighlightIndex, input: input, inputHint: inputHint, slashMenu: slashMenu, onApprovalDecision: (decision) => approvals.decide(decision), onModelSelect: (name) => {
1172
+ setPicker(null);
1173
+ void handleModelCommand(name);
1174
+ }, onYoloSelect: (value) => {
1175
+ setPicker(null);
1176
+ void handleYoloCommand(value);
1177
+ }, onModeSelect: (value) => {
1178
+ setPicker(null);
1179
+ void handleModeCommand(value);
1180
+ }, onInputChange: setInput, onSubmit: onSubmit })] })] }));
1077
1181
  }
1078
1182
  //# sourceMappingURL=app.js.map