hoomanjs 1.29.2 → 1.31.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 (292) hide show
  1. package/README.md +216 -69
  2. package/dist/acp/acp-agent.d.ts +5 -5
  3. package/dist/acp/acp-agent.js +55 -54
  4. package/dist/acp/acp-agent.js.map +1 -1
  5. package/dist/acp/approvals.d.ts +3 -3
  6. package/dist/acp/approvals.js +73 -115
  7. package/dist/acp/approvals.js.map +1 -1
  8. package/dist/acp/sessions/options.js +11 -25
  9. package/dist/acp/sessions/options.js.map +1 -1
  10. package/dist/acp/utils/tool-kind.js +1 -5
  11. package/dist/acp/utils/tool-kind.js.map +1 -1
  12. package/dist/chat/app.d.ts +5 -1
  13. package/dist/chat/app.js +469 -234
  14. package/dist/chat/app.js.map +1 -1
  15. package/dist/chat/approvals.d.ts +4 -3
  16. package/dist/chat/approvals.js +11 -45
  17. package/dist/chat/approvals.js.map +1 -1
  18. package/dist/chat/components/BottomChrome.d.ts +52 -0
  19. package/dist/chat/components/BottomChrome.js +12 -0
  20. package/dist/chat/components/BottomChrome.js.map +1 -0
  21. package/dist/chat/components/ChatMessage.d.ts +2 -2
  22. package/dist/chat/components/ChatMessage.js +5 -6
  23. package/dist/chat/components/ChatMessage.js.map +1 -1
  24. package/dist/chat/components/ChromePicker.d.ts +17 -0
  25. package/dist/chat/components/ChromePicker.js +35 -0
  26. package/dist/chat/components/ChromePicker.js.map +1 -0
  27. package/dist/chat/components/EmptyChatBanner.js +2 -3
  28. package/dist/chat/components/EmptyChatBanner.js.map +1 -1
  29. package/dist/chat/components/StatusBar.d.ts +1 -2
  30. package/dist/chat/components/StatusBar.js +8 -5
  31. package/dist/chat/components/StatusBar.js.map +1 -1
  32. package/dist/chat/components/ThoughtEvent.d.ts +7 -0
  33. package/dist/chat/components/ThoughtEvent.js +30 -0
  34. package/dist/chat/components/ThoughtEvent.js.map +1 -0
  35. package/dist/chat/components/TodoPanel.js +52 -2
  36. package/dist/chat/components/TodoPanel.js.map +1 -1
  37. package/dist/chat/components/ToolEvent.js +1 -1
  38. package/dist/chat/components/ToolEvent.js.map +1 -1
  39. package/dist/chat/components/Transcript.d.ts +11 -2
  40. package/dist/chat/components/Transcript.js +78 -4
  41. package/dist/chat/components/Transcript.js.map +1 -1
  42. package/dist/chat/components/markdown/BlockRenderer.js +7 -7
  43. package/dist/chat/components/markdown/BlockRenderer.js.map +1 -1
  44. package/dist/chat/components/markdown/MarkdownMessage.js +1 -1
  45. package/dist/chat/components/markdown/MarkdownMessage.js.map +1 -1
  46. package/dist/chat/components/shared.js +2 -0
  47. package/dist/chat/components/shared.js.map +1 -1
  48. package/dist/chat/index.d.ts +4 -0
  49. package/dist/chat/index.js +21 -4
  50. package/dist/chat/index.js.map +1 -1
  51. package/dist/chat/steering.d.ts +20 -0
  52. package/dist/chat/steering.js +67 -0
  53. package/dist/chat/steering.js.map +1 -0
  54. package/dist/chat/types.d.ts +5 -1
  55. package/dist/cli.js +91 -13
  56. package/dist/cli.js.map +1 -1
  57. package/dist/configure/app.d.ts +1 -1
  58. package/dist/configure/app.js +581 -303
  59. package/dist/configure/app.js.map +1 -1
  60. package/dist/configure/components/MenuScreen.d.ts +4 -2
  61. package/dist/configure/components/MenuScreen.js +33 -6
  62. package/dist/configure/components/MenuScreen.js.map +1 -1
  63. package/dist/configure/components/SelectMenuItem.d.ts +1 -0
  64. package/dist/configure/components/SelectMenuItem.js +19 -10
  65. package/dist/configure/components/SelectMenuItem.js.map +1 -1
  66. package/dist/configure/index.js +4 -2
  67. package/dist/configure/index.js.map +1 -1
  68. package/dist/configure/types.d.ts +14 -12
  69. package/dist/configure/utils.d.ts +2 -0
  70. package/dist/configure/utils.js +21 -2
  71. package/dist/configure/utils.js.map +1 -1
  72. package/dist/core/agent/index.d.ts +3 -3
  73. package/dist/core/agent/index.js +26 -29
  74. package/dist/core/agent/index.js.map +1 -1
  75. package/dist/core/agent/mode-aware-tool-registry.js +2 -1
  76. package/dist/core/agent/mode-aware-tool-registry.js.map +1 -1
  77. package/dist/core/approvals/intervention.d.ts +34 -0
  78. package/dist/core/approvals/intervention.js +94 -0
  79. package/dist/core/approvals/intervention.js.map +1 -0
  80. package/dist/core/config.d.ts +49 -28
  81. package/dist/core/config.js +85 -26
  82. package/dist/core/config.js.map +1 -1
  83. package/dist/core/context/index.d.ts +11 -2
  84. package/dist/core/context/index.js +54 -4
  85. package/dist/core/context/index.js.map +1 -1
  86. package/dist/core/context/model-extractor.d.ts +13 -0
  87. package/dist/core/context/model-extractor.js +98 -0
  88. package/dist/core/context/model-extractor.js.map +1 -0
  89. package/dist/core/index.d.ts +6 -2
  90. package/dist/core/index.js +6 -1
  91. package/dist/core/index.js.map +1 -1
  92. package/dist/core/mcp/config.d.ts +28 -0
  93. package/dist/core/mcp/index.d.ts +5 -3
  94. package/dist/core/mcp/index.js +4 -2
  95. package/dist/core/mcp/index.js.map +1 -1
  96. package/dist/core/mcp/manager.d.ts +14 -1
  97. package/dist/core/mcp/manager.js +76 -4
  98. package/dist/core/mcp/manager.js.map +1 -1
  99. package/dist/core/mcp/oauth/callback-server.d.ts +16 -0
  100. package/dist/core/mcp/oauth/callback-server.js +134 -0
  101. package/dist/core/mcp/oauth/callback-server.js.map +1 -0
  102. package/dist/core/mcp/oauth/identity.d.ts +9 -0
  103. package/dist/core/mcp/oauth/identity.js +31 -0
  104. package/dist/core/mcp/oauth/identity.js.map +1 -0
  105. package/dist/core/mcp/oauth/index.d.ts +11 -0
  106. package/dist/core/mcp/oauth/index.js +15 -0
  107. package/dist/core/mcp/oauth/index.js.map +1 -0
  108. package/dist/core/mcp/oauth/provider.d.ts +43 -0
  109. package/dist/core/mcp/oauth/provider.js +203 -0
  110. package/dist/core/mcp/oauth/provider.js.map +1 -0
  111. package/dist/core/mcp/oauth/service.d.ts +29 -0
  112. package/dist/core/mcp/oauth/service.js +139 -0
  113. package/dist/core/mcp/oauth/service.js.map +1 -0
  114. package/dist/core/mcp/oauth/store.d.ts +14 -0
  115. package/dist/core/mcp/oauth/store.js +86 -0
  116. package/dist/core/mcp/oauth/store.js.map +1 -0
  117. package/dist/core/mcp/oauth/types.d.ts +87 -0
  118. package/dist/core/mcp/oauth/types.js +45 -0
  119. package/dist/core/mcp/oauth/types.js.map +1 -0
  120. package/dist/core/mcp/types.d.ts +56 -0
  121. package/dist/core/mcp/types.js +3 -0
  122. package/dist/core/mcp/types.js.map +1 -1
  123. package/dist/core/memory/file-store.d.ts +24 -0
  124. package/dist/core/memory/file-store.js +151 -0
  125. package/dist/core/memory/file-store.js.map +1 -0
  126. package/dist/core/memory/index.d.ts +2 -5
  127. package/dist/core/memory/index.js +2 -3
  128. package/dist/core/memory/index.js.map +1 -1
  129. package/dist/core/memory/runtime.d.ts +4 -0
  130. package/dist/core/memory/runtime.js +37 -0
  131. package/dist/core/memory/runtime.js.map +1 -0
  132. package/dist/core/models/anthropic.js +24 -1
  133. package/dist/core/models/anthropic.js.map +1 -1
  134. package/dist/core/models/moonshot.js +33 -1
  135. package/dist/core/models/moonshot.js.map +1 -1
  136. package/dist/core/modes/definitions.d.ts +19 -0
  137. package/dist/core/modes/definitions.js +99 -0
  138. package/dist/core/modes/definitions.js.map +1 -0
  139. package/dist/core/modes/index.d.ts +3 -0
  140. package/dist/core/modes/index.js +4 -0
  141. package/dist/core/modes/index.js.map +1 -0
  142. package/dist/core/modes/registry.d.ts +9 -0
  143. package/dist/core/modes/registry.js +57 -0
  144. package/dist/core/modes/registry.js.map +1 -0
  145. package/dist/core/modes/schema.d.ts +5 -0
  146. package/dist/core/modes/schema.js +6 -0
  147. package/dist/core/modes/schema.js.map +1 -0
  148. package/dist/core/prompts/agents/research.md +8 -8
  149. package/dist/core/prompts/bundled.d.ts +3 -0
  150. package/dist/core/prompts/bundled.js +14 -0
  151. package/dist/core/prompts/bundled.js.map +1 -0
  152. package/dist/core/prompts/environment.d.ts +0 -1
  153. package/dist/core/prompts/environment.js +0 -2
  154. package/dist/core/prompts/environment.js.map +1 -1
  155. package/dist/core/prompts/index.d.ts +1 -4
  156. package/dist/core/prompts/index.js +1 -7
  157. package/dist/core/prompts/index.js.map +1 -1
  158. package/dist/core/prompts/modes/agent.md +3 -0
  159. package/dist/core/prompts/modes/ask.md +5 -3
  160. package/dist/core/prompts/modes/plan.md +2 -2
  161. package/dist/core/prompts/runtime.d.ts +10 -0
  162. package/dist/core/prompts/runtime.js +136 -0
  163. package/dist/core/prompts/runtime.js.map +1 -0
  164. package/dist/core/prompts/session-mode-appendix.d.ts +3 -9
  165. package/dist/core/prompts/session-mode-appendix.js +32 -69
  166. package/dist/core/prompts/session-mode-appendix.js.map +1 -1
  167. package/dist/core/prompts/static/environment.md +2 -2
  168. package/dist/core/prompts/static/filesystem.md +2 -0
  169. package/dist/core/prompts/static/init.md +25 -0
  170. package/dist/core/prompts/static/memory.md +9 -123
  171. package/dist/core/prompts/static/planning.md +1 -1
  172. package/dist/core/prompts/static/skills.md +6 -5
  173. package/dist/core/prompts/static/subagents.md +2 -2
  174. package/dist/core/prompts/static/web-search.md +2 -1
  175. package/dist/core/prompts/system.js +12 -29
  176. package/dist/core/prompts/system.js.map +1 -1
  177. package/dist/core/skills/built-in/hooman-config/SKILL.md +105 -32
  178. package/dist/core/skills/built-in/hooman-skills/SKILL.md +1 -1
  179. package/dist/core/skills/index.d.ts +1 -0
  180. package/dist/core/skills/index.js +1 -0
  181. package/dist/core/skills/index.js.map +1 -1
  182. package/dist/core/skills/plugin.d.ts +7 -0
  183. package/dist/core/skills/plugin.js +40 -0
  184. package/dist/core/skills/plugin.js.map +1 -0
  185. package/dist/core/state/agent-app-state.d.ts +1 -1
  186. package/dist/core/state/agent-app-state.js +1 -1
  187. package/dist/core/state/session-mode.d.ts +2 -9
  188. package/dist/core/state/session-mode.js +4 -12
  189. package/dist/core/state/session-mode.js.map +1 -1
  190. package/dist/core/state/tool-approvals.d.ts +4 -8
  191. package/dist/core/state/tool-approvals.js +14 -76
  192. package/dist/core/state/tool-approvals.js.map +1 -1
  193. package/dist/core/subagents/index.d.ts +3 -0
  194. package/dist/core/subagents/index.js +4 -0
  195. package/dist/core/subagents/index.js.map +1 -0
  196. package/dist/core/subagents/research.d.ts +16 -0
  197. package/dist/core/subagents/research.js +58 -0
  198. package/dist/core/subagents/research.js.map +1 -0
  199. package/dist/core/{agents → subagents}/runner.d.ts +6 -5
  200. package/dist/core/{agents → subagents}/runner.js +8 -10
  201. package/dist/core/subagents/runner.js.map +1 -0
  202. package/dist/core/{agents/tools.d.ts → subagents/tool.d.ts} +6 -6
  203. package/dist/core/{agents/tools.js → subagents/tool.js} +14 -16
  204. package/dist/core/subagents/tool.js.map +1 -0
  205. package/dist/core/tools/filesystem.d.ts +1 -0
  206. package/dist/core/tools/filesystem.js +38 -3
  207. package/dist/core/tools/filesystem.js.map +1 -1
  208. package/dist/core/tools/index.d.ts +1 -1
  209. package/dist/core/tools/index.js +1 -1
  210. package/dist/core/tools/index.js.map +1 -1
  211. package/dist/core/tools/plan.js +2 -6
  212. package/dist/core/tools/plan.js.map +1 -1
  213. package/dist/core/tools/time.js +1 -1
  214. package/dist/core/tools/time.js.map +1 -1
  215. package/dist/core/utils/browser.d.ts +1 -0
  216. package/dist/core/utils/browser.js +25 -0
  217. package/dist/core/utils/browser.js.map +1 -0
  218. package/dist/core/utils/paths.d.ts +2 -4
  219. package/dist/core/utils/paths.js +2 -4
  220. package/dist/core/utils/paths.js.map +1 -1
  221. package/dist/daemon/approvals.d.ts +2 -2
  222. package/dist/daemon/approvals.js +51 -56
  223. package/dist/daemon/approvals.js.map +1 -1
  224. package/dist/daemon/index.js +9 -6
  225. package/dist/daemon/index.js.map +1 -1
  226. package/dist/exec/approvals.d.ts +2 -4
  227. package/dist/exec/approvals.js +16 -48
  228. package/dist/exec/approvals.js.map +1 -1
  229. package/dist/index.d.ts +13 -18
  230. package/dist/index.js +9 -11
  231. package/dist/index.js.map +1 -1
  232. package/package.json +5 -18
  233. package/dist/chat/components/ScrollView.d.ts +0 -106
  234. package/dist/chat/components/ScrollView.js +0 -80
  235. package/dist/chat/components/ScrollView.js.map +0 -1
  236. package/dist/chat/components/TranscriptViewport.d.ts +0 -9
  237. package/dist/chat/components/TranscriptViewport.js +0 -124
  238. package/dist/chat/components/TranscriptViewport.js.map +0 -1
  239. package/dist/core/agents/definitions.d.ts +0 -12
  240. package/dist/core/agents/definitions.js +0 -20
  241. package/dist/core/agents/definitions.js.map +0 -1
  242. package/dist/core/agents/index.d.ts +0 -4
  243. package/dist/core/agents/index.js +0 -5
  244. package/dist/core/agents/index.js.map +0 -1
  245. package/dist/core/agents/registry.d.ts +0 -5
  246. package/dist/core/agents/registry.js +0 -84
  247. package/dist/core/agents/registry.js.map +0 -1
  248. package/dist/core/agents/runner.js.map +0 -1
  249. package/dist/core/agents/tools.js.map +0 -1
  250. package/dist/core/inference/embedder.d.ts +0 -26
  251. package/dist/core/inference/embedder.js +0 -85
  252. package/dist/core/inference/embedder.js.map +0 -1
  253. package/dist/core/inference/index.d.ts +0 -9
  254. package/dist/core/inference/index.js +0 -13
  255. package/dist/core/inference/index.js.map +0 -1
  256. package/dist/core/inference/loader.d.ts +0 -16
  257. package/dist/core/inference/loader.js +0 -129
  258. package/dist/core/inference/loader.js.map +0 -1
  259. package/dist/core/inference/reranker.d.ts +0 -28
  260. package/dist/core/inference/reranker.js +0 -66
  261. package/dist/core/inference/reranker.js.map +0 -1
  262. package/dist/core/memory/brain.d.ts +0 -25
  263. package/dist/core/memory/brain.js +0 -137
  264. package/dist/core/memory/brain.js.map +0 -1
  265. package/dist/core/memory/database.d.ts +0 -4
  266. package/dist/core/memory/database.js +0 -87
  267. package/dist/core/memory/database.js.map +0 -1
  268. package/dist/core/memory/tools.d.ts +0 -16
  269. package/dist/core/memory/tools.js +0 -110
  270. package/dist/core/memory/tools.js.map +0 -1
  271. package/dist/core/memory/types.d.ts +0 -11
  272. package/dist/core/memory/types.js +0 -2
  273. package/dist/core/memory/types.js.map +0 -1
  274. package/dist/core/prompts/skills.d.ts +0 -15
  275. package/dist/core/prompts/skills.js +0 -92
  276. package/dist/core/prompts/skills.js.map +0 -1
  277. package/dist/core/prompts/static/wiki.md +0 -25
  278. package/dist/core/wiki/converters.d.ts +0 -12
  279. package/dist/core/wiki/converters.js +0 -73
  280. package/dist/core/wiki/converters.js.map +0 -1
  281. package/dist/core/wiki/database.d.ts +0 -39
  282. package/dist/core/wiki/database.js +0 -177
  283. package/dist/core/wiki/database.js.map +0 -1
  284. package/dist/core/wiki/index.d.ts +0 -7
  285. package/dist/core/wiki/index.js +0 -5
  286. package/dist/core/wiki/index.js.map +0 -1
  287. package/dist/core/wiki/storage.d.ts +0 -38
  288. package/dist/core/wiki/storage.js +0 -206
  289. package/dist/core/wiki/storage.js.map +0 -1
  290. package/dist/core/wiki/tools.d.ts +0 -5
  291. package/dist/core/wiki/tools.js +0 -32
  292. package/dist/core/wiki/tools.js.map +0 -1
package/dist/chat/app.js CHANGED
@@ -2,27 +2,22 @@ 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
4
  import { Box, useApp, useInput, useWindowSize } from "ink";
5
- import { BeforeToolCallEvent, Message, TextBlock, } from "@strands-agents/sdk";
5
+ import { Message, TextBlock, ToolResultBlock, ToolUseBlock, } from "@strands-agents/sdk";
6
6
  import { accumulateUsage, createEmptyUsage, } from "../core/utils/strands-usage-accumulate.js";
7
- import { bootstrap } from "../core/index.js";
7
+ import { modelProviders } from "../core/models/index.js";
8
+ import { formatModeNames, isKnownSessionMode } from "../core/modes/index.js";
8
9
  import { takeFileToolDisplay } from "../core/state/file-tool-display.js";
9
- import { ChatApprovalController, createChatApprovalHandler, } from "./approvals.js";
10
- import { ApprovalPrompt } from "./components/ApprovalPrompt.js";
11
- import { Composer } from "./components/Composer.js";
12
- import { SelectPicker } from "./components/SelectPicker.js";
13
- import { QueuedPrompts } from "./components/QueuedPrompts.js";
14
- import { SlashCommands } from "./components/SlashCommands.js";
15
- import { StatusBar } from "./components/StatusBar.js";
16
- import { TodoPanel } from "./components/TodoPanel.js";
17
- import { TranscriptViewport } from "./components/TranscriptViewport.js";
10
+ import { BottomChrome } from "./components/BottomChrome.js";
11
+ import { Transcript } from "./components/Transcript.js";
18
12
  import { getTodoViewState } from "../core/state/todos.js";
19
13
  import { isExitRequested } from "../core/state/exit-request.js";
20
- import { copyAgentAppState } from "../core/state/agent-app-state.js";
21
14
  import { getModeState, setSessionMode, } from "../core/state/session-mode.js";
22
15
  import { isYoloEnabled, setYoloEnabled } from "../core/state/yolo.js";
23
16
  import { applySessionMode } from "../core/agent/sync-tool-registry-mode.js";
24
17
  import { attachmentPathsToPromptBlocks } from "../core/utils/attachments.js";
25
18
  import { isMouseInput } from "./mouse.js";
19
+ import { readBundledPrompt } from "../core/prompts/bundled.js";
20
+ import { runWithAgentMemoryScope } from "../core/memory/index.js";
26
21
  function emptyTurnUsage() {
27
22
  return { ...createEmptyUsage(), latencyMs: 0 };
28
23
  }
@@ -37,7 +32,9 @@ function normalizePromptSubmission(value) {
37
32
  ],
38
33
  };
39
34
  }
40
- const INPUT_HINT = "shift/meta+enter or \\+enter: newline | esc/ctrl+c: cancel or exit";
35
+ const INPUT_HINT = "pgup/pgdn: scroll | shift/meta+enter or \\+enter: newline | esc/ctrl+c: cancel or exit";
36
+ const INPUT_HINT_WITH_STEERING = "enter on empty input: steer active turn with queued prompts | pgup/pgdn: scroll | shift/meta+enter or \\+enter: newline | esc/ctrl+c: cancel or exit";
37
+ const INIT_AGENTS_PROMPT = readBundledPrompt("static", "init.md");
41
38
  function nowId() {
42
39
  return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
43
40
  }
@@ -49,6 +46,17 @@ function stringifyUnknown(value) {
49
46
  return String(value);
50
47
  }
51
48
  }
49
+ function blockToFallbackText(block) {
50
+ if (block.type === "textBlock") {
51
+ return block.text;
52
+ }
53
+ try {
54
+ return JSON.stringify(block.toJSON?.() ?? block, null, 2);
55
+ }
56
+ catch {
57
+ return String(block);
58
+ }
59
+ }
52
60
  function toToolResultText(result) {
53
61
  const data = result;
54
62
  const pieces = (data.content ?? []).map((item) => {
@@ -76,6 +84,83 @@ function getToolUseId(value) {
76
84
  }
77
85
  return null;
78
86
  }
87
+ function estimateReasoningTokens(text) {
88
+ const trimmed = text.trim();
89
+ if (!trimmed) {
90
+ return 0;
91
+ }
92
+ return Math.max(1, Math.round(trimmed.length / 4));
93
+ }
94
+ function buildInitialTranscript(messages) {
95
+ const lines = [];
96
+ const toolResults = new Map();
97
+ for (const message of messages) {
98
+ for (const block of message.content) {
99
+ if (block instanceof ToolResultBlock) {
100
+ toolResults.set(block.toolUseId, block);
101
+ }
102
+ }
103
+ }
104
+ for (const message of messages) {
105
+ if (message.role === "user") {
106
+ const parts = message.content
107
+ .filter((block) => !(block instanceof ToolResultBlock))
108
+ .map((block) => blockToFallbackText(block)?.trimEnd() ?? "")
109
+ .filter(Boolean);
110
+ if (parts.length > 0) {
111
+ lines.push({
112
+ id: nowId(),
113
+ role: "user",
114
+ content: parts.join("\n\n"),
115
+ done: true,
116
+ });
117
+ }
118
+ continue;
119
+ }
120
+ if (message.role !== "assistant") {
121
+ continue;
122
+ }
123
+ for (const block of message.content) {
124
+ if (block instanceof ToolUseBlock) {
125
+ const result = toolResults.get(block.toolUseId);
126
+ lines.push({
127
+ id: nowId(),
128
+ role: "tool",
129
+ title: "tool",
130
+ toolName: block.name,
131
+ content: stringifyUnknown(block.input ?? {}),
132
+ resultContent: result ? toToolResultText(result) : undefined,
133
+ phase: result ? "done" : "running",
134
+ done: Boolean(result),
135
+ });
136
+ continue;
137
+ }
138
+ if (block.type === "reasoningBlock") {
139
+ const text = blockToFallbackText(block)?.trim();
140
+ if (text) {
141
+ lines.push({
142
+ id: nowId(),
143
+ role: "thought",
144
+ content: text,
145
+ done: true,
146
+ estimatedTokens: estimateReasoningTokens(text),
147
+ });
148
+ }
149
+ continue;
150
+ }
151
+ const text = blockToFallbackText(block)?.trimEnd();
152
+ if (text) {
153
+ lines.push({
154
+ id: nowId(),
155
+ role: "assistant",
156
+ content: text,
157
+ done: true,
158
+ });
159
+ }
160
+ }
161
+ }
162
+ return lines;
163
+ }
79
164
  function currentModelName(config) {
80
165
  return (config.llms.find((m) => m.default)?.name ??
81
166
  config.llms[0]?.name ??
@@ -129,7 +214,7 @@ function parseYoloToggleArg(raw) {
129
214
  }
130
215
  function parseSessionModeArg(raw) {
131
216
  const t = raw.trim().toLowerCase();
132
- if (t === "default" || t === "plan" || t === "ask") {
217
+ if (isKnownSessionMode(t)) {
133
218
  return t;
134
219
  }
135
220
  return undefined;
@@ -138,7 +223,11 @@ function listModelsText(config) {
138
223
  const current = currentModelName(config);
139
224
  const options = config.llms.map((entry) => {
140
225
  const marker = entry.name === current ? "*" : "-";
141
- return `${marker} ${entry.name} (${entry.options.provider}/${entry.options.model})`;
226
+ const resolved = config.resolveLlm(entry.name);
227
+ if (!resolved) {
228
+ return `${marker} ${entry.name} (${entry.options.provider}/${entry.options.model})`;
229
+ }
230
+ return `${marker} ${entry.name} (${entry.options.provider} -> ${resolved.options.provider}/${resolved.options.model})`;
142
231
  });
143
232
  return [
144
233
  `Current model: ${current}`,
@@ -148,9 +237,13 @@ function listModelsText(config) {
148
237
  ].join("\n");
149
238
  }
150
239
  const SLASH_COMMANDS = [
240
+ {
241
+ name: "init",
242
+ description: "Generate or refresh AGENTS.md for this project.",
243
+ },
151
244
  {
152
245
  name: "mode",
153
- description: "Session mode: default, plan, or ask.",
246
+ description: `Session mode: ${formatModeNames()}.`,
154
247
  },
155
248
  {
156
249
  name: "model",
@@ -175,15 +268,42 @@ function matchingSlashCommands(input) {
175
268
  }
176
269
  return SLASH_COMMANDS.filter((item) => item.name.startsWith(query));
177
270
  }
178
- export function ChatApp({ agent, config, sessionId, manager, registry, prompt, onExit, }) {
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, }) {
179
301
  const { exit } = useApp();
180
- const { rows } = useWindowSize();
181
- const [currentAgent, setCurrentAgent] = useState(agent);
182
- const [currentManager, setCurrentManager] = useState(manager);
302
+ const windowSize = useWindowSize();
183
303
  const [input, setInput] = useState("");
184
304
  const [running, setRunning] = useState(false);
185
305
  const [status, setStatus] = useState("ready");
186
- const [lines, setLines] = useState([]);
306
+ const [lines, setLines] = useState(() => buildInitialTranscript(agent.messages));
187
307
  const [turnCount, setTurnCount] = useState(0);
188
308
  const [skillsFound, setSkillsFound] = useState(0);
189
309
  const [turnStartedAt, setTurnStartedAt] = useState(null);
@@ -198,16 +318,24 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
198
318
  }, []);
199
319
  const [slashHighlightIndex, setSlashHighlightIndex] = useState(0);
200
320
  const [queuedPrompts, setQueuedPrompts] = useState([]);
201
- const [liveReasoning, setLiveReasoning] = useState("");
202
- const [followRequest, setFollowRequest] = useState(0);
321
+ const [transcriptScrollOffset, setTranscriptScrollOffset] = useState(0);
203
322
  const [todoState, setTodoState] = useState(() => getTodoViewState(agent));
204
- const controllerRef = useRef(new ChatApprovalController());
205
323
  const mountedRef = useRef(true);
206
324
  const runningRef = useRef(false);
207
325
  const assistantLineIdRef = useRef(null);
326
+ const assistantCommittedTextRef = useRef("");
327
+ const streamedAssistantBlockRef = useRef(null);
328
+ const thoughtLineIdRef = useRef(null);
329
+ const thoughtTextRef = useRef("");
330
+ const thoughtStartedAtRef = useRef(null);
208
331
  const toolLineIdsRef = useRef(new Map());
209
332
  const pendingToolLineIdsRef = useRef([]);
210
333
  const initialRanRef = useRef(false);
334
+ const queuedPromptsRef = useRef([]);
335
+ const skippedQueueIdsRef = useRef(new Set());
336
+ useEffect(() => {
337
+ queuedPromptsRef.current = queuedPrompts;
338
+ }, [queuedPrompts]);
211
339
  useEffect(() => {
212
340
  let cancelled = false;
213
341
  void registry
@@ -240,23 +368,20 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
240
368
  };
241
369
  }, [running, turnStartedAt]);
242
370
  useEffect(() => {
243
- const controller = controllerRef.current;
244
- const cleanupListener = controller.subscribe(() => {
245
- setPendingApproval(controller.pending);
371
+ const cleanupListener = approvals.subscribe(() => {
372
+ setPendingApproval(approvals.pending);
246
373
  });
247
- const cleanupHook = currentAgent.addHook(BeforeToolCallEvent, createChatApprovalHandler(controller));
248
374
  return () => {
249
375
  cleanupListener();
250
- cleanupHook();
251
376
  };
252
- }, [currentAgent]);
377
+ }, [approvals]);
253
378
  useEffect(() => {
254
379
  let active = true;
255
380
  const refresh = () => {
256
381
  if (!active) {
257
382
  return;
258
383
  }
259
- setTodoState(getTodoViewState(currentAgent));
384
+ setTodoState(getTodoViewState(agent));
260
385
  };
261
386
  refresh();
262
387
  const timer = setInterval(refresh, running ? 200 : 800);
@@ -264,8 +389,8 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
264
389
  active = false;
265
390
  clearInterval(timer);
266
391
  };
267
- }, [currentAgent, running]);
268
- const totalTools = currentAgent.tools?.length ?? 0;
392
+ }, [agent, running]);
393
+ const totalTools = agent.tools?.length ?? 0;
269
394
  const slashCommands = useMemo(() => matchingSlashCommands(input), [input]);
270
395
  useEffect(() => {
271
396
  setSlashHighlightIndex((i) => {
@@ -317,26 +442,91 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
317
442
  return next;
318
443
  });
319
444
  }, []);
320
- const appendAssistantText = useCallback((text) => {
445
+ const replaceAssistantText = useCallback((text) => {
321
446
  const id = assistantLineIdRef.current;
322
447
  if (!id) {
323
448
  return;
324
449
  }
325
- setLines((prev) => prev.map((line) => line.id === id ? { ...line, content: `${line.content}${text}` } : line));
450
+ setLines((prev) => prev.map((line) => (line.id === id ? { ...line, content: text } : line)));
326
451
  }, []);
327
- const rebuildAgent = useCallback(async () => {
328
- const messageSnapshot = currentAgent.messages.map((message) => message.toJSON());
329
- const { agent: nextAgent, mcp: { manager: nextManager }, } = await bootstrap("default", { sessionId, yolo: isYoloEnabled(currentAgent) }, false, config);
330
- nextAgent.messages.length = 0;
331
- for (const message of messageSnapshot) {
332
- nextAgent.messages.push(Message.fromJSON(message));
333
- }
334
- copyAgentAppState(currentAgent, nextAgent);
335
- applySessionMode(nextAgent);
336
- setCurrentAgent(nextAgent);
337
- setCurrentManager(nextManager);
338
- await currentManager.disconnect();
339
- }, [config, currentAgent, currentManager, sessionId]);
452
+ const appendAssistantText = useCallback((text) => {
453
+ streamedAssistantBlockRef.current = `${streamedAssistantBlockRef.current ?? ""}${text}`;
454
+ replaceAssistantText(`${assistantCommittedTextRef.current}${streamedAssistantBlockRef.current}`);
455
+ }, [replaceAssistantText]);
456
+ const commitAssistantBlock = useCallback((text) => {
457
+ assistantCommittedTextRef.current = `${assistantCommittedTextRef.current}${text}`;
458
+ streamedAssistantBlockRef.current = null;
459
+ replaceAssistantText(assistantCommittedTextRef.current);
460
+ }, [replaceAssistantText]);
461
+ const finalizeAssistantLine = useCallback(() => {
462
+ const id = assistantLineIdRef.current;
463
+ if (!id) {
464
+ return;
465
+ }
466
+ updateLine(id, { done: true });
467
+ assistantLineIdRef.current = null;
468
+ assistantCommittedTextRef.current = "";
469
+ streamedAssistantBlockRef.current = null;
470
+ }, [updateLine]);
471
+ const ensureAssistantLine = useCallback(() => {
472
+ const existing = assistantLineIdRef.current;
473
+ if (existing) {
474
+ return existing;
475
+ }
476
+ const id = nowId();
477
+ assistantLineIdRef.current = id;
478
+ assistantCommittedTextRef.current = "";
479
+ streamedAssistantBlockRef.current = null;
480
+ appendLine({
481
+ id,
482
+ role: "assistant",
483
+ content: "",
484
+ done: false,
485
+ });
486
+ return id;
487
+ }, [appendLine]);
488
+ const finalizeThoughtLine = useCallback(() => {
489
+ const id = thoughtLineIdRef.current;
490
+ if (!id) {
491
+ return;
492
+ }
493
+ const finishedAt = Date.now();
494
+ updateLine(id, {
495
+ done: true,
496
+ finishedAt,
497
+ estimatedTokens: estimateReasoningTokens(thoughtTextRef.current),
498
+ });
499
+ thoughtLineIdRef.current = null;
500
+ thoughtTextRef.current = "";
501
+ thoughtStartedAtRef.current = null;
502
+ }, [updateLine]);
503
+ const ensureThoughtLine = useCallback(() => {
504
+ const existing = thoughtLineIdRef.current;
505
+ if (existing) {
506
+ return existing;
507
+ }
508
+ finalizeAssistantLine();
509
+ const startedAt = Date.now();
510
+ const id = nowId();
511
+ thoughtLineIdRef.current = id;
512
+ thoughtTextRef.current = "";
513
+ thoughtStartedAtRef.current = startedAt;
514
+ appendLine({
515
+ id,
516
+ role: "thought",
517
+ content: "",
518
+ done: false,
519
+ startedAt,
520
+ });
521
+ return id;
522
+ }, [appendLine, finalizeAssistantLine]);
523
+ const appendThoughtText = useCallback((text) => {
524
+ const id = ensureThoughtLine();
525
+ thoughtTextRef.current = `${thoughtTextRef.current}${text}`;
526
+ setLines((prev) => prev.map((line) => line.id === id
527
+ ? { ...line, content: `${line.content}${text}` }
528
+ : line));
529
+ }, [ensureThoughtLine]);
340
530
  const handleModelCommand = useCallback(async (args) => {
341
531
  if (runningRef.current) {
342
532
  appendLine({
@@ -374,7 +564,8 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
374
564
  })),
375
565
  });
376
566
  try {
377
- await rebuildAgent();
567
+ const provider = await modelProviders[config.llm.provider]();
568
+ agent.model = provider.create(config.llm.model, config.llm.params);
378
569
  }
379
570
  catch (error) {
380
571
  config.update({
@@ -391,7 +582,7 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
391
582
  done: true,
392
583
  });
393
584
  }
394
- }, [appendLine, config, rebuildAgent]);
585
+ }, [agent, appendLine, config]);
395
586
  const handleYoloCommand = useCallback(async (args) => {
396
587
  if (runningRef.current) {
397
588
  appendLine({
@@ -419,13 +610,13 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
419
610
  });
420
611
  return;
421
612
  }
422
- const prev = isYoloEnabled(currentAgent);
613
+ const prev = isYoloEnabled(agent);
423
614
  if (prev === enabled) {
424
615
  return;
425
616
  }
426
- setYoloEnabled(currentAgent, enabled);
617
+ setYoloEnabled(agent, enabled);
427
618
  bumpSessionChrome();
428
- }, [appendLine, bumpSessionChrome, currentAgent]);
619
+ }, [agent, appendLine, bumpSessionChrome]);
429
620
  const handleModeCommand = useCallback(async (args) => {
430
621
  if (runningRef.current) {
431
622
  appendLine({
@@ -448,19 +639,19 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
448
639
  id: nowId(),
449
640
  role: "system",
450
641
  title: "mode",
451
- content: `Unknown mode "${trimmed}". Use default, plan, or ask, or open the picker with /mode.`,
642
+ content: `Unknown mode "${trimmed}". Use agent, ask, or plan, or open the picker with /mode.`,
452
643
  done: true,
453
644
  });
454
645
  return;
455
646
  }
456
- const prev = getModeState(currentAgent).mode;
647
+ const prev = getModeState(agent).mode;
457
648
  if (prev === mode) {
458
649
  return;
459
650
  }
460
- setSessionMode(currentAgent, mode);
461
- applySessionMode(currentAgent);
651
+ setSessionMode(agent, mode);
652
+ applySessionMode(agent);
462
653
  bumpSessionChrome();
463
- }, [appendLine, bumpSessionChrome, currentAgent]);
654
+ }, [agent, appendLine, bumpSessionChrome]);
464
655
  const runTurn = useCallback(async (prompt) => {
465
656
  const trimmed = prompt.text.trim();
466
657
  if (!trimmed && prompt.attachments.length === 0) {
@@ -471,7 +662,6 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
471
662
  setRunning(true);
472
663
  setStatus("thinking");
473
664
  setTurnStartedAt(Date.now());
474
- setLiveReasoning("");
475
665
  setTurnCount((value) => value + 1);
476
666
  appendLine({
477
667
  id: nowId(),
@@ -483,14 +673,6 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
483
673
  : trimmed,
484
674
  done: true,
485
675
  });
486
- const assistantId = nowId();
487
- assistantLineIdRef.current = assistantId;
488
- appendLine({
489
- id: assistantId,
490
- role: "assistant",
491
- content: "",
492
- done: false,
493
- });
494
676
  try {
495
677
  const streamInput = attachmentBlocks.length > 0
496
678
  ? [
@@ -503,136 +685,142 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
503
685
  }),
504
686
  ]
505
687
  : trimmed;
506
- for await (const event of currentAgent.stream(streamInput)) {
507
- const e = event;
508
- switch (e.type) {
509
- case "contentBlockEvent": {
510
- const block = e.contentBlock;
511
- if (block.type === "textBlock") {
512
- appendAssistantText(block.text ?? "");
513
- }
514
- else if (block.type === "toolUseBlock") {
515
- const toolId = nowId();
516
- const toolUseId = getToolUseId(block);
517
- if (toolUseId) {
518
- toolLineIdsRef.current.set(toolUseId, toolId);
688
+ await runWithAgentMemoryScope(agent, async () => {
689
+ for await (const event of agent.stream(streamInput)) {
690
+ const e = event;
691
+ switch (e.type) {
692
+ case "contentBlockEvent": {
693
+ const block = e.contentBlock;
694
+ if (block.type === "textBlock") {
695
+ finalizeThoughtLine();
696
+ ensureAssistantLine();
697
+ commitAssistantBlock(block.text ?? "");
519
698
  }
520
- else {
521
- pendingToolLineIdsRef.current.push(toolId);
522
- }
523
- appendLine({
524
- id: toolId,
525
- role: "tool",
526
- title: "tool",
527
- toolName: block.name ?? "unknown",
528
- content: stringifyUnknown(block.input ?? {}),
529
- phase: "running",
530
- done: false,
531
- });
532
- if (assistantLineIdRef.current) {
533
- moveLineToEnd(assistantLineIdRef.current);
699
+ else if (block.type === "toolUseBlock") {
700
+ finalizeThoughtLine();
701
+ finalizeAssistantLine();
702
+ const toolId = nowId();
703
+ const toolUseId = getToolUseId(block);
704
+ if (toolUseId) {
705
+ toolLineIdsRef.current.set(toolUseId, toolId);
706
+ }
707
+ else {
708
+ pendingToolLineIdsRef.current.push(toolId);
709
+ }
710
+ appendLine({
711
+ id: toolId,
712
+ role: "tool",
713
+ title: "tool",
714
+ toolName: block.name ?? "unknown",
715
+ content: stringifyUnknown(block.input ?? {}),
716
+ phase: "running",
717
+ done: false,
718
+ });
534
719
  }
720
+ break;
535
721
  }
536
- break;
537
- }
538
- case "toolResultEvent": {
539
- const resultContent = toToolResultText(e.result);
540
- const toolUseId = getToolUseId(e.result);
541
- const fileToolDisplay = takeFileToolDisplay(currentAgent.appState, toolUseId);
542
- let toolLineId = toolUseId
543
- ? toolLineIdsRef.current.get(toolUseId)
544
- : undefined;
545
- if (toolLineId && toolUseId) {
546
- toolLineIdsRef.current.delete(toolUseId);
547
- }
548
- toolLineId ??= pendingToolLineIdsRef.current.shift();
549
- if (!toolLineId) {
550
- const firstTrackedTool = toolLineIdsRef.current
551
- .entries()
552
- .next();
553
- if (!firstTrackedTool.done) {
554
- const [trackedToolUseId, trackedToolLineId] = firstTrackedTool.value;
555
- toolLineIdsRef.current.delete(trackedToolUseId);
556
- toolLineId = trackedToolLineId;
722
+ case "toolResultEvent": {
723
+ const resultContent = toToolResultText(e.result);
724
+ const toolUseId = getToolUseId(e.result);
725
+ const fileToolDisplay = takeFileToolDisplay(agent.appState, toolUseId);
726
+ let toolLineId = toolUseId
727
+ ? toolLineIdsRef.current.get(toolUseId)
728
+ : undefined;
729
+ if (toolLineId && toolUseId) {
730
+ toolLineIdsRef.current.delete(toolUseId);
557
731
  }
558
- }
559
- if (toolLineId) {
560
- updateLine(toolLineId, {
561
- phase: "done",
562
- done: true,
563
- resultContent,
564
- fileToolDisplay,
565
- });
566
- }
567
- else {
568
- appendLine({
569
- id: nowId(),
570
- role: "tool",
571
- title: "tool",
572
- toolName: "unknown",
573
- content: "",
574
- resultContent,
575
- fileToolDisplay,
576
- phase: "done",
577
- done: true,
578
- });
579
- }
580
- break;
581
- }
582
- case "toolStreamUpdateEvent":
583
- setStatus("running tool");
584
- break;
585
- case "modelStreamUpdateEvent": {
586
- const modelEvent = e.event;
587
- if (modelEvent?.type === "modelContentBlockDeltaEvent") {
588
- const delta = modelEvent.delta;
589
- if (delta?.type === "reasoningContentDelta" && delta.text) {
590
- setStatus("thinking");
591
- setLiveReasoning((prev) => `${prev}${delta.text}`);
732
+ toolLineId ??= pendingToolLineIdsRef.current.shift();
733
+ if (!toolLineId) {
734
+ const firstTrackedTool = toolLineIdsRef.current
735
+ .entries()
736
+ .next();
737
+ if (!firstTrackedTool.done) {
738
+ const [trackedToolUseId, trackedToolLineId] = firstTrackedTool.value;
739
+ toolLineIdsRef.current.delete(trackedToolUseId);
740
+ toolLineId = trackedToolLineId;
741
+ }
742
+ }
743
+ if (toolLineId) {
744
+ updateLine(toolLineId, {
745
+ phase: "done",
746
+ done: true,
747
+ resultContent,
748
+ fileToolDisplay,
749
+ });
592
750
  }
593
- else if (delta?.type === "textDelta") {
594
- setStatus("streaming");
751
+ else {
752
+ appendLine({
753
+ id: nowId(),
754
+ role: "tool",
755
+ title: "tool",
756
+ toolName: "unknown",
757
+ content: "",
758
+ resultContent,
759
+ fileToolDisplay,
760
+ phase: "done",
761
+ done: true,
762
+ });
595
763
  }
764
+ break;
596
765
  }
597
- if (modelEvent?.type === "modelMetadataEvent") {
598
- const u = (modelEvent.usage ?? {});
599
- const delta = {
600
- inputTokens: u.inputTokens ?? 0,
601
- outputTokens: u.outputTokens ?? 0,
602
- totalTokens: u.totalTokens ?? 0,
603
- ...(u.cacheReadInputTokens !== undefined && {
604
- cacheReadInputTokens: u.cacheReadInputTokens,
605
- }),
606
- ...(u.cacheWriteInputTokens !== undefined && {
607
- cacheWriteInputTokens: u.cacheWriteInputTokens,
608
- }),
609
- };
610
- const metricsData = (modelEvent.metrics ?? {});
611
- const lat = metricsData.latencyMs ?? 0;
612
- // Sum every `modelMetadataEvent` for this chat session (Strands meter semantics). Never reset on
613
- // new prompts so the footer stays monotonic; note input totals sum per-request prompt sizes.
614
- setUsage((prev) => {
615
- const tokens = {
616
- inputTokens: prev.inputTokens,
617
- outputTokens: prev.outputTokens,
618
- totalTokens: prev.totalTokens,
619
- ...(prev.cacheReadInputTokens !== undefined && {
620
- cacheReadInputTokens: prev.cacheReadInputTokens,
766
+ case "toolStreamUpdateEvent":
767
+ setStatus("running tool");
768
+ break;
769
+ case "modelStreamUpdateEvent": {
770
+ const modelEvent = e.event;
771
+ if (modelEvent?.type === "modelContentBlockDeltaEvent") {
772
+ const delta = modelEvent.delta;
773
+ if (delta?.type === "reasoningContentDelta" && delta.text) {
774
+ setStatus("thinking");
775
+ appendThoughtText(delta.text);
776
+ }
777
+ else if (delta?.type === "textDelta" && delta.text) {
778
+ finalizeThoughtLine();
779
+ setStatus("streaming");
780
+ ensureAssistantLine();
781
+ appendAssistantText(delta.text);
782
+ }
783
+ }
784
+ if (modelEvent?.type === "modelMetadataEvent") {
785
+ const u = (modelEvent.usage ?? {});
786
+ const delta = {
787
+ inputTokens: u.inputTokens ?? 0,
788
+ outputTokens: u.outputTokens ?? 0,
789
+ totalTokens: u.totalTokens ?? 0,
790
+ ...(u.cacheReadInputTokens !== undefined && {
791
+ cacheReadInputTokens: u.cacheReadInputTokens,
621
792
  }),
622
- ...(prev.cacheWriteInputTokens !== undefined && {
623
- cacheWriteInputTokens: prev.cacheWriteInputTokens,
793
+ ...(u.cacheWriteInputTokens !== undefined && {
794
+ cacheWriteInputTokens: u.cacheWriteInputTokens,
624
795
  }),
625
796
  };
626
- accumulateUsage(tokens, delta);
627
- return { ...tokens, latencyMs: prev.latencyMs + lat };
628
- });
797
+ const metricsData = (modelEvent.metrics ?? {});
798
+ const lat = metricsData.latencyMs ?? 0;
799
+ // Sum every `modelMetadataEvent` for this chat session (Strands meter semantics). Never reset on
800
+ // new prompts so the footer stays monotonic; note input totals sum per-request prompt sizes.
801
+ setUsage((prev) => {
802
+ const tokens = {
803
+ inputTokens: prev.inputTokens,
804
+ outputTokens: prev.outputTokens,
805
+ totalTokens: prev.totalTokens,
806
+ ...(prev.cacheReadInputTokens !== undefined && {
807
+ cacheReadInputTokens: prev.cacheReadInputTokens,
808
+ }),
809
+ ...(prev.cacheWriteInputTokens !== undefined && {
810
+ cacheWriteInputTokens: prev.cacheWriteInputTokens,
811
+ }),
812
+ };
813
+ accumulateUsage(tokens, delta);
814
+ return { ...tokens, latencyMs: prev.latencyMs + lat };
815
+ });
816
+ }
817
+ break;
629
818
  }
630
- break;
819
+ default:
820
+ break;
631
821
  }
632
- default:
633
- break;
634
822
  }
635
- }
823
+ });
636
824
  }
637
825
  catch (error) {
638
826
  appendLine({
@@ -644,8 +832,8 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
644
832
  });
645
833
  }
646
834
  finally {
647
- updateLine(assistantId, { done: true });
648
- assistantLineIdRef.current = null;
835
+ finalizeThoughtLine();
836
+ finalizeAssistantLine();
649
837
  for (const toolLineId of toolLineIdsRef.current.values()) {
650
838
  updateLine(toolLineId, { phase: "done", done: true });
651
839
  }
@@ -657,19 +845,21 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
657
845
  runningRef.current = false;
658
846
  setRunning(false);
659
847
  setTurnStartedAt(null);
660
- setLiveReasoning("");
661
848
  setStatus("ready");
662
- if (isExitRequested(currentAgent)) {
849
+ if (isExitRequested(agent)) {
663
850
  onExit();
664
851
  exit();
665
852
  }
666
853
  }
667
854
  }, [
668
855
  appendAssistantText,
856
+ appendThoughtText,
669
857
  appendLine,
670
- currentAgent,
858
+ agent,
671
859
  exit,
672
- moveLineToEnd,
860
+ ensureAssistantLine,
861
+ finalizeAssistantLine,
862
+ finalizeThoughtLine,
673
863
  onExit,
674
864
  updateLine,
675
865
  ]);
@@ -680,6 +870,9 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
680
870
  const queueRef = useRef(null);
681
871
  if (!queueRef.current) {
682
872
  queueRef.current = fastq.promise(async (item) => {
873
+ if (skippedQueueIdsRef.current.delete(item.id)) {
874
+ return;
875
+ }
683
876
  if (mountedRef.current) {
684
877
  setQueuedPrompts((prev) => prev.filter((entry) => entry.id !== item.id));
685
878
  }
@@ -702,6 +895,7 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
702
895
  id: nowId(),
703
896
  prompt: { ...normalized, text: trimmed },
704
897
  };
898
+ setTranscriptScrollOffset(0);
705
899
  setQueuedPrompts((prev) => [...prev, item]);
706
900
  void queueRef.current?.push(item).catch((error) => {
707
901
  if (!mountedRef.current) {
@@ -728,8 +922,37 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
728
922
  if (pendingApproval) {
729
923
  return;
730
924
  }
925
+ const trimmed = value.text.trim();
926
+ if (runningRef.current &&
927
+ !trimmed &&
928
+ value.attachments.length === 0 &&
929
+ queuedPromptsRef.current.length > 0) {
930
+ const queued = queuedPromptsRef.current;
931
+ queuedPromptsRef.current = [];
932
+ for (const item of queued) {
933
+ skippedQueueIdsRef.current.add(item.id);
934
+ }
935
+ setQueuedPrompts([]);
936
+ if (steering.queue(queued.map((item) => item.prompt))) {
937
+ appendLine({
938
+ id: nowId(),
939
+ role: "system",
940
+ title: "steering",
941
+ content: `Steered the active turn with ${queued.length} queued prompt${queued.length === 1 ? "" : "s"}.`,
942
+ done: true,
943
+ });
944
+ }
945
+ setInput("");
946
+ return;
947
+ }
731
948
  const command = parseChatCommand(value.text);
732
949
  if (command && value.attachments.length === 0) {
950
+ if (command.name === "init") {
951
+ if (pushPrompt(INIT_AGENTS_PROMPT)) {
952
+ setInput("");
953
+ }
954
+ return;
955
+ }
733
956
  if (command.name === "model") {
734
957
  void handleModelCommand(command.args);
735
958
  setInput("");
@@ -747,23 +970,42 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
747
970
  }
748
971
  }
749
972
  if (pushPrompt(value)) {
750
- setFollowRequest((value) => value + 1);
751
973
  setInput("");
752
974
  }
753
975
  }, [
976
+ appendLine,
754
977
  handleModelCommand,
755
978
  handleModeCommand,
756
979
  handleYoloCommand,
757
980
  pendingApproval,
758
981
  pushPrompt,
982
+ steering,
759
983
  ]);
760
984
  useInput((inputKey, key) => {
761
985
  if (isMouseInput(inputKey)) {
762
986
  return;
763
987
  }
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
+ }
764
1006
  if (key.ctrl && inputKey.toLowerCase() === "c") {
765
1007
  if (runningRef.current) {
766
- currentAgent.cancel();
1008
+ agent.cancel();
767
1009
  setStatus("cancel requested");
768
1010
  return;
769
1011
  }
@@ -781,7 +1023,7 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
781
1023
  return;
782
1024
  }
783
1025
  if (runningRef.current) {
784
- currentAgent.cancel();
1026
+ agent.cancel();
785
1027
  setStatus("cancel requested");
786
1028
  return;
787
1029
  }
@@ -798,46 +1040,39 @@ export function ChatApp({ agent, config, sessionId, manager, registry, prompt, o
798
1040
  const sec = seconds % 60;
799
1041
  return `${String(min).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
800
1042
  }, [turnElapsedMs]);
801
- const activeTodo = useMemo(() => todoState.todos.find((todo) => todo.status === "in_progress"), [todoState.todos]);
802
- const statusLabel = running && activeTodo
803
- ? activeTodo.activeForm.trim() || activeTodo.content
804
- : status;
805
- return (_jsxs(Box, { flexDirection: "column", width: "100%", height: rows, paddingX: 1, children: [_jsx(TranscriptViewport, { lines: lines, liveReasoning: liveReasoning, followRequest: followRequest }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [running && todoState.visible && todoState.todos.length > 0 ? (_jsx(TodoPanel, { todos: todoState.todos })) : null, _jsx(QueuedPrompts, { prompts: queuedPrompts }), pendingApproval ? (_jsx(ApprovalPrompt, { onDecision: (decision) => controllerRef.current.decide(decision) })) : null, !pendingApproval && picker === "model" ? (_jsx(SelectPicker, { title: "Choose model", items: config.llms.map((entry) => ({
806
- label: `${entry.name} • ${entry.options.provider}/${entry.options.model}${entry.default ? " • current" : ""}`,
807
- value: entry.name,
808
- })), onSelect: (name) => {
809
- setPicker(null);
810
- void handleModelCommand(name);
811
- } })) : null, !pendingApproval && picker === "yolo" ? (_jsx(SelectPicker, { title: "Auto-approve tools (yolo)", items: [
812
- {
813
- label: `Off • confirm each tool${!isYoloEnabled(currentAgent) ? " • current" : ""}`,
814
- value: "off",
815
- },
816
- {
817
- label: `On • run tools without prompts${isYoloEnabled(currentAgent) ? " • current" : ""}`,
818
- value: "on",
819
- },
820
- ], onSelect: (v) => {
821
- setPicker(null);
822
- void handleYoloCommand(v);
823
- } })) : null, !pendingApproval && picker === "mode" ? (_jsx(SelectPicker, { title: "Session mode", items: [
824
- {
825
- label: `Default full tool surface${getModeState(currentAgent).mode === "default"
826
- ? " • current"
827
- : ""}`,
828
- value: "default",
829
- },
830
- {
831
- label: `Plan • read only tools + plan file${getModeState(currentAgent).mode === "plan" ? " • current" : ""}`,
832
- value: "plan",
833
- },
834
- {
835
- label: `Ask • read only tools, no plan workflow${getModeState(currentAgent).mode === "ask" ? " • current" : ""}`,
836
- value: "ask",
837
- },
838
- ], onSelect: (v) => {
839
- setPicker(null);
840
- void handleModeCommand(v);
841
- } })) : null, !pendingApproval && !picker ? (_jsx(SlashCommands, { items: slashCommands, highlightIndex: slashHighlightIndex })) : null, !pendingApproval && !picker ? (_jsx(Composer, { input: input, running: running, disabled: Boolean(pendingApproval), hint: INPUT_HINT, onChange: setInput, onSubmit: onSubmit, slashMenu: slashMenu })) : null, _jsx(StatusBar, { running: running, status: status, statusLabel: statusLabel, sessionId: sessionId, currentModel: currentModelLabel(config), yoloOn: isYoloEnabled(currentAgent), sessionMode: getModeState(currentAgent).mode, elapsedLabel: elapsedLabel, turnCount: turnCount, totalTools: totalTools, skillsFound: skillsFound, manager: currentManager, usage: usage })] })] }));
1043
+ 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 })] }));
842
1077
  }
843
1078
  //# sourceMappingURL=app.js.map