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