@datename/fastcode-termux 0.0.0-stage → 0.1.6-beta

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 (347) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +418 -2
  3. package/bin/fastcode.mjs +59 -0
  4. package/dist/agent/approval.d.ts +50 -0
  5. package/dist/agent/approval.js +7 -0
  6. package/dist/agent/compaction.d.ts +39 -0
  7. package/dist/agent/compaction.js +227 -0
  8. package/dist/agent/context.d.ts +29 -0
  9. package/dist/agent/context.js +62 -0
  10. package/dist/agent/guards.d.ts +43 -0
  11. package/dist/agent/guards.js +269 -0
  12. package/dist/agent/ignore.d.ts +11 -0
  13. package/dist/agent/ignore.js +129 -0
  14. package/dist/agent/limits.d.ts +24 -0
  15. package/dist/agent/limits.js +71 -0
  16. package/dist/agent/process-tree.d.ts +2 -0
  17. package/dist/agent/process-tree.js +31 -0
  18. package/dist/agent/protocol-mode.d.ts +6 -0
  19. package/dist/agent/protocol-mode.js +51 -0
  20. package/dist/agent/protocol.d.ts +4 -0
  21. package/dist/agent/protocol.js +129 -0
  22. package/dist/agent/registry.d.ts +17 -0
  23. package/dist/agent/registry.js +90 -0
  24. package/dist/agent/runner.d.ts +106 -0
  25. package/dist/agent/runner.js +352 -0
  26. package/dist/agent/shell-session.d.ts +37 -0
  27. package/dist/agent/shell-session.js +400 -0
  28. package/dist/agent/stream.d.ts +29 -0
  29. package/dist/agent/stream.js +103 -0
  30. package/dist/agent/tool-schemas.d.ts +2 -0
  31. package/dist/agent/tool-schemas.js +63 -0
  32. package/dist/agent/tools/append_file.d.ts +2 -0
  33. package/dist/agent/tools/append_file.js +26 -0
  34. package/dist/agent/tools/apply_patch.d.ts +10 -0
  35. package/dist/agent/tools/apply_patch.js +52 -0
  36. package/dist/agent/tools/copy_file.d.ts +2 -0
  37. package/dist/agent/tools/copy_file.js +30 -0
  38. package/dist/agent/tools/delete_file.d.ts +2 -0
  39. package/dist/agent/tools/delete_file.js +24 -0
  40. package/dist/agent/tools/grep.d.ts +2 -0
  41. package/dist/agent/tools/grep.js +23 -0
  42. package/dist/agent/tools/index.d.ts +2 -0
  43. package/dist/agent/tools/index.js +32 -0
  44. package/dist/agent/tools/list_files.d.ts +2 -0
  45. package/dist/agent/tools/list_files.js +17 -0
  46. package/dist/agent/tools/make_dir.d.ts +2 -0
  47. package/dist/agent/tools/make_dir.js +24 -0
  48. package/dist/agent/tools/move_path.d.ts +2 -0
  49. package/dist/agent/tools/move_path.js +47 -0
  50. package/dist/agent/tools/paths.d.ts +7 -0
  51. package/dist/agent/tools/paths.js +84 -0
  52. package/dist/agent/tools/pwd.d.ts +2 -0
  53. package/dist/agent/tools/pwd.js +10 -0
  54. package/dist/agent/tools/read_file.d.ts +2 -0
  55. package/dist/agent/tools/read_file.js +21 -0
  56. package/dist/agent/tools/read_many.d.ts +2 -0
  57. package/dist/agent/tools/read_many.js +25 -0
  58. package/dist/agent/tools/readers.d.ts +6 -0
  59. package/dist/agent/tools/readers.js +216 -0
  60. package/dist/agent/tools/replace_in_file.d.ts +2 -0
  61. package/dist/agent/tools/replace_in_file.js +30 -0
  62. package/dist/agent/tools/shell.d.ts +2 -0
  63. package/dist/agent/tools/shell.js +48 -0
  64. package/dist/agent/tools/stat_path.d.ts +2 -0
  65. package/dist/agent/tools/stat_path.js +31 -0
  66. package/dist/agent/tools/types.d.ts +39 -0
  67. package/dist/agent/tools/types.js +1 -0
  68. package/dist/agent/tools/validate.d.ts +6 -0
  69. package/dist/agent/tools/validate.js +29 -0
  70. package/dist/agent/tools/write_file.d.ts +2 -0
  71. package/dist/agent/tools/write_file.js +26 -0
  72. package/dist/agent/tools.d.ts +3 -0
  73. package/dist/agent/tools.js +2 -0
  74. package/dist/app.d.ts +4 -0
  75. package/dist/app.js +708 -0
  76. package/dist/cli.d.ts +1 -0
  77. package/dist/cli.js +569 -0
  78. package/dist/commands/about.d.ts +8 -0
  79. package/dist/commands/about.js +16 -0
  80. package/dist/commands/agent.d.ts +11 -0
  81. package/dist/commands/agent.js +18 -0
  82. package/dist/commands/attach.d.ts +9 -0
  83. package/dist/commands/attach.js +15 -0
  84. package/dist/commands/bg.d.ts +7 -0
  85. package/dist/commands/bg.js +9 -0
  86. package/dist/commands/cancel.d.ts +10 -0
  87. package/dist/commands/cancel.js +10 -0
  88. package/dist/commands/clear.d.ts +11 -0
  89. package/dist/commands/clear.js +9 -0
  90. package/dist/commands/compact.d.ts +10 -0
  91. package/dist/commands/compact.js +22 -0
  92. package/dist/commands/config.d.ts +7 -0
  93. package/dist/commands/config.js +24 -0
  94. package/dist/commands/context.d.ts +7 -0
  95. package/dist/commands/context.js +24 -0
  96. package/dist/commands/cost.d.ts +9 -0
  97. package/dist/commands/cost.js +13 -0
  98. package/dist/commands/cwd.d.ts +7 -0
  99. package/dist/commands/cwd.js +7 -0
  100. package/dist/commands/edit.d.ts +10 -0
  101. package/dist/commands/edit.js +36 -0
  102. package/dist/commands/effort.d.ts +25 -0
  103. package/dist/commands/effort.js +48 -0
  104. package/dist/commands/exit.d.ts +9 -0
  105. package/dist/commands/exit.js +8 -0
  106. package/dist/commands/expand.d.ts +10 -0
  107. package/dist/commands/expand.js +8 -0
  108. package/dist/commands/help.d.ts +6 -0
  109. package/dist/commands/help.js +18 -0
  110. package/dist/commands/index.d.ts +9 -0
  111. package/dist/commands/index.js +80 -0
  112. package/dist/commands/jobs.d.ts +7 -0
  113. package/dist/commands/jobs.js +16 -0
  114. package/dist/commands/kill.d.ts +7 -0
  115. package/dist/commands/kill.js +11 -0
  116. package/dist/commands/login.d.ts +18 -0
  117. package/dist/commands/login.js +63 -0
  118. package/dist/commands/logout.d.ts +9 -0
  119. package/dist/commands/logout.js +45 -0
  120. package/dist/commands/model.d.ts +24 -0
  121. package/dist/commands/model.js +50 -0
  122. package/dist/commands/plugin.d.ts +11 -0
  123. package/dist/commands/plugin.js +74 -0
  124. package/dist/commands/provider.d.ts +24 -0
  125. package/dist/commands/provider.js +39 -0
  126. package/dist/commands/reset.d.ts +9 -0
  127. package/dist/commands/reset.js +13 -0
  128. package/dist/commands/skills.d.ts +11 -0
  129. package/dist/commands/skills.js +106 -0
  130. package/dist/commands/steer.d.ts +11 -0
  131. package/dist/commands/steer.js +13 -0
  132. package/dist/commands/system.d.ts +11 -0
  133. package/dist/commands/system.js +20 -0
  134. package/dist/commands/theme.d.ts +11 -0
  135. package/dist/commands/theme.js +22 -0
  136. package/dist/commands/tools.d.ts +7 -0
  137. package/dist/commands/tools.js +29 -0
  138. package/dist/commands/whoami.d.ts +9 -0
  139. package/dist/commands/whoami.js +52 -0
  140. package/dist/config/paths.d.ts +4 -0
  141. package/dist/config/paths.js +59 -0
  142. package/dist/config/store.d.ts +89 -0
  143. package/dist/config/store.js +260 -0
  144. package/dist/config/themes.d.ts +73 -0
  145. package/dist/config/themes.js +77 -0
  146. package/dist/config/trust.d.ts +22 -0
  147. package/dist/config/trust.js +153 -0
  148. package/dist/index.d.ts +1 -0
  149. package/dist/index.js +31 -0
  150. package/dist/lib/diagnostics.d.ts +66 -0
  151. package/dist/lib/diagnostics.js +195 -0
  152. package/dist/lib/http.d.ts +23 -0
  153. package/dist/lib/http.js +128 -0
  154. package/dist/lib/markdown.d.ts +1 -0
  155. package/dist/lib/markdown.js +32 -0
  156. package/dist/lib/model-meta.d.ts +19 -0
  157. package/dist/lib/model-meta.js +113 -0
  158. package/dist/lib/native-tools.d.ts +12 -0
  159. package/dist/lib/native-tools.js +21 -0
  160. package/dist/lib/net.d.ts +33 -0
  161. package/dist/lib/net.js +168 -0
  162. package/dist/lib/pkg.d.ts +5 -0
  163. package/dist/lib/pkg.js +27 -0
  164. package/dist/lib/platform.d.ts +7 -0
  165. package/dist/lib/platform.js +57 -0
  166. package/dist/lib/startup.d.ts +6 -0
  167. package/dist/lib/startup.js +25 -0
  168. package/dist/lib/uiMode.d.ts +2 -0
  169. package/dist/lib/uiMode.js +7 -0
  170. package/dist/patch/apply.d.ts +15 -0
  171. package/dist/patch/apply.js +172 -0
  172. package/dist/patch/parse.d.ts +29 -0
  173. package/dist/patch/parse.js +112 -0
  174. package/dist/patch/plan.d.ts +49 -0
  175. package/dist/patch/plan.js +273 -0
  176. package/dist/plugins/manager.d.ts +23 -0
  177. package/dist/plugins/manager.js +101 -0
  178. package/dist/providers/anthropic.d.ts +22 -0
  179. package/dist/providers/anthropic.js +119 -0
  180. package/dist/providers/custom.d.ts +33 -0
  181. package/dist/providers/custom.js +25 -0
  182. package/dist/providers/deepseek.d.ts +34 -0
  183. package/dist/providers/deepseek.js +16 -0
  184. package/dist/providers/google.d.ts +33 -0
  185. package/dist/providers/google.js +19 -0
  186. package/dist/providers/groq.d.ts +34 -0
  187. package/dist/providers/groq.js +21 -0
  188. package/dist/providers/index.d.ts +10 -0
  189. package/dist/providers/index.js +67 -0
  190. package/dist/providers/mistral.d.ts +34 -0
  191. package/dist/providers/mistral.js +20 -0
  192. package/dist/providers/moonshot.d.ts +34 -0
  193. package/dist/providers/moonshot.js +23 -0
  194. package/dist/providers/ollama.d.ts +33 -0
  195. package/dist/providers/ollama.js +22 -0
  196. package/dist/providers/openai.d.ts +34 -0
  197. package/dist/providers/openai.js +26 -0
  198. package/dist/providers/openaiCompatible.d.ts +26 -0
  199. package/dist/providers/openaiCompatible.js +158 -0
  200. package/dist/providers/openrouter.d.ts +34 -0
  201. package/dist/providers/openrouter.js +27 -0
  202. package/dist/providers/qwen.d.ts +34 -0
  203. package/dist/providers/qwen.js +24 -0
  204. package/dist/providers/xai.d.ts +34 -0
  205. package/dist/providers/xai.js +19 -0
  206. package/dist/providers/zhipu.d.ts +34 -0
  207. package/dist/providers/zhipu.js +24 -0
  208. package/dist/session/log.d.ts +52 -0
  209. package/dist/session/log.js +331 -0
  210. package/dist/session/projection.d.ts +43 -0
  211. package/dist/session/projection.js +257 -0
  212. package/dist/skills/manager.d.ts +18 -0
  213. package/dist/skills/manager.js +201 -0
  214. package/dist/skills/parser.d.ts +18 -0
  215. package/dist/skills/parser.js +65 -0
  216. package/dist/types.d.ts +205 -0
  217. package/dist/types.js +1 -0
  218. package/dist/ui/Banner.d.ts +5 -0
  219. package/dist/ui/Banner.js +65 -0
  220. package/dist/ui/Message.d.ts +1 -0
  221. package/dist/ui/Message.js +24 -0
  222. package/dist/ui/Picker.d.ts +8 -0
  223. package/dist/ui/Picker.js +24 -0
  224. package/dist/ui/Prompt.d.ts +9 -0
  225. package/dist/ui/Prompt.js +38 -0
  226. package/dist/ui/Spinner.d.ts +5 -0
  227. package/dist/ui/Spinner.js +39 -0
  228. package/dist/ui/StatusBar.d.ts +11 -0
  229. package/dist/ui/StatusBar.js +20 -0
  230. package/dist/ui/TrustGate.d.ts +5 -0
  231. package/dist/ui/TrustGate.js +26 -0
  232. package/package.json +96 -4
  233. package/src/agent/approval.ts +47 -0
  234. package/src/agent/compaction.ts +276 -0
  235. package/src/agent/context.ts +97 -0
  236. package/src/agent/guards.ts +270 -0
  237. package/src/agent/ignore.ts +147 -0
  238. package/src/agent/limits.ts +71 -0
  239. package/src/agent/process-tree.ts +32 -0
  240. package/src/agent/protocol-mode.ts +59 -0
  241. package/src/agent/protocol.ts +129 -0
  242. package/src/agent/registry.ts +98 -0
  243. package/src/agent/runner.ts +443 -0
  244. package/src/agent/shell-session.ts +449 -0
  245. package/src/agent/stream.ts +143 -0
  246. package/src/agent/tool-schemas.ts +67 -0
  247. package/src/agent/tools/append_file.ts +28 -0
  248. package/src/agent/tools/apply_patch.ts +54 -0
  249. package/src/agent/tools/copy_file.ts +32 -0
  250. package/src/agent/tools/delete_file.ts +26 -0
  251. package/src/agent/tools/grep.ts +25 -0
  252. package/src/agent/tools/index.ts +34 -0
  253. package/src/agent/tools/list_files.ts +19 -0
  254. package/src/agent/tools/make_dir.ts +26 -0
  255. package/src/agent/tools/move_path.ts +50 -0
  256. package/src/agent/tools/paths.ts +92 -0
  257. package/src/agent/tools/pwd.ts +12 -0
  258. package/src/agent/tools/read_file.ts +23 -0
  259. package/src/agent/tools/read_many.ts +27 -0
  260. package/src/agent/tools/readers.ts +228 -0
  261. package/src/agent/tools/replace_in_file.ts +32 -0
  262. package/src/agent/tools/shell.ts +53 -0
  263. package/src/agent/tools/stat_path.ts +33 -0
  264. package/src/agent/tools/types.ts +42 -0
  265. package/src/agent/tools/validate.ts +35 -0
  266. package/src/agent/tools/write_file.ts +28 -0
  267. package/src/agent/tools.ts +3 -0
  268. package/src/app.ts +708 -0
  269. package/src/cli.ts +335 -0
  270. package/src/commands/about.ts +16 -0
  271. package/src/commands/agent.ts +18 -0
  272. package/src/commands/attach.ts +16 -0
  273. package/src/commands/bg.ts +9 -0
  274. package/src/commands/cancel.ts +10 -0
  275. package/src/commands/clear.ts +9 -0
  276. package/src/commands/compact.ts +23 -0
  277. package/src/commands/config.ts +24 -0
  278. package/src/commands/context.ts +25 -0
  279. package/src/commands/cost.ts +13 -0
  280. package/src/commands/cwd.ts +7 -0
  281. package/src/commands/edit.ts +37 -0
  282. package/src/commands/effort.ts +49 -0
  283. package/src/commands/exit.ts +8 -0
  284. package/src/commands/expand.ts +8 -0
  285. package/src/commands/help.ts +18 -0
  286. package/src/commands/index.ts +80 -0
  287. package/src/commands/jobs.ts +17 -0
  288. package/src/commands/kill.ts +12 -0
  289. package/src/commands/login.ts +63 -0
  290. package/src/commands/logout.ts +45 -0
  291. package/src/commands/model.ts +50 -0
  292. package/src/commands/plugin.ts +74 -0
  293. package/src/commands/provider.ts +39 -0
  294. package/src/commands/reset.ts +13 -0
  295. package/src/commands/skills.ts +106 -0
  296. package/src/commands/steer.ts +13 -0
  297. package/src/commands/system.ts +20 -0
  298. package/src/commands/theme.ts +22 -0
  299. package/src/commands/tools.ts +29 -0
  300. package/src/commands/whoami.ts +52 -0
  301. package/src/config/paths.ts +59 -0
  302. package/src/config/store.ts +293 -0
  303. package/src/config/themes.ts +77 -0
  304. package/src/config/trust.ts +183 -0
  305. package/src/index.ts +31 -0
  306. package/src/lib/diagnostics.ts +195 -0
  307. package/src/lib/http.ts +132 -0
  308. package/src/lib/markdown.ts +32 -0
  309. package/src/lib/model-meta.ts +138 -0
  310. package/src/lib/native-tools.ts +32 -0
  311. package/src/lib/net.ts +204 -0
  312. package/src/lib/pkg.ts +35 -0
  313. package/src/lib/platform.ts +57 -0
  314. package/src/lib/startup.ts +25 -0
  315. package/src/lib/uiMode.ts +7 -0
  316. package/src/patch/apply.ts +194 -0
  317. package/src/patch/parse.ts +131 -0
  318. package/src/patch/plan.ts +317 -0
  319. package/src/plugins/manager.ts +101 -0
  320. package/src/providers/anthropic.ts +119 -0
  321. package/src/providers/custom.ts +25 -0
  322. package/src/providers/deepseek.ts +16 -0
  323. package/src/providers/google.ts +19 -0
  324. package/src/providers/groq.ts +21 -0
  325. package/src/providers/index.ts +68 -0
  326. package/src/providers/mistral.ts +20 -0
  327. package/src/providers/moonshot.ts +23 -0
  328. package/src/providers/ollama.ts +22 -0
  329. package/src/providers/openai.ts +26 -0
  330. package/src/providers/openaiCompatible.ts +158 -0
  331. package/src/providers/openrouter.ts +27 -0
  332. package/src/providers/qwen.ts +24 -0
  333. package/src/providers/xai.ts +19 -0
  334. package/src/providers/zhipu.ts +24 -0
  335. package/src/session/log.ts +374 -0
  336. package/src/session/projection.ts +308 -0
  337. package/src/skills/manager.ts +201 -0
  338. package/src/skills/parser.ts +65 -0
  339. package/src/types.ts +205 -0
  340. package/src/ui/Banner.ts +68 -0
  341. package/src/ui/Message.ts +24 -0
  342. package/src/ui/Picker.ts +24 -0
  343. package/src/ui/Prompt.ts +38 -0
  344. package/src/ui/Spinner.ts +39 -0
  345. package/src/ui/StatusBar.ts +20 -0
  346. package/src/ui/TrustGate.ts +26 -0
  347. package/tsconfig.json +22 -0
package/dist/app.js ADDED
@@ -0,0 +1,708 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * FastCode App component — the heart of the TUI.
4
+ *
5
+ * Responsibilities:
6
+ * • Render banner, transcript, prompt, and status bar.
7
+ * • Drive the chat loop: send user messages, stream provider responses,
8
+ * update token/cost state, and persist config across runs.
9
+ * • Dispatch slash commands.
10
+ * • Cancel streaming on Esc and quit cleanly on /exit.
11
+ */
12
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
+ import { Box, Static, Text, useApp, useInput } from "ink";
14
+ import { AGENT_SYSTEM_PROMPT, stripToolCalls, } from "./agent/protocol.js";
15
+ import { compactionConfigFromEnv } from "./agent/compaction.js";
16
+ import { resolveToolProtocol, withToolCapability } from "./agent/protocol-mode.js";
17
+ import { runAgent } from "./agent/runner.js";
18
+ import { toolSchemas } from "./agent/tool-schemas.js";
19
+ import { shutdownShells } from "./agent/shell-session.js";
20
+ import { Banner } from "./ui/Banner.js";
21
+ import { Spinner } from "./ui/Spinner.js";
22
+ import { StatusBar } from "./ui/StatusBar.js";
23
+ import { MessageBubble } from "./ui/Message.js";
24
+ import { Prompt } from "./ui/Prompt.js";
25
+ import { Picker } from "./ui/Picker.js";
26
+ import { COMMANDS, findCommand, parseSlash } from "./commands/index.js";
27
+ import { saveScopedKey } from "./commands/login.js";
28
+ import { getProvider, getProviderOrThrow, PROVIDERS } from "./providers/index.js";
29
+ import { loadConfig, saveConfig, resolveApiKey, getLegacy, } from "./config/store.js";
30
+ import { getTheme } from "./config/themes.js";
31
+ import { createStartupTranscript } from "./lib/startup.js";
32
+ import { shouldUseLiveStreamingUi, shouldUseStableTerminalUi } from "./lib/uiMode.js";
33
+ import { buildPluginsPrompt } from "./plugins/manager.js";
34
+ import { buildSkillsPrompt } from "./skills/manager.js";
35
+ import { createSessionLog, latestUnclosedSession, loggingEnabled, openSessionLog } from "./session/log.js";
36
+ import { resumeSession } from "./session/projection.js";
37
+ import { isTrusted, trustProject } from "./config/trust.js";
38
+ import { TrustGate } from "./ui/TrustGate.js";
39
+ /**
40
+ * Mask the secret part of a `/login` invocation in the transcript:
41
+ * `/login prov` → unchanged, `/login prov <key>` → key masked,
42
+ * `/login prov model <key>` → provider/model shown, key masked.
43
+ */
44
+ function maskLoginEcho(args) {
45
+ const parts = ["/login", args[0] ?? ""];
46
+ if (args.length === 2) {
47
+ parts.push("********");
48
+ }
49
+ else if (args.length >= 3) {
50
+ parts.push(args[1]);
51
+ for (let i = 2; i < args.length; i += 1)
52
+ parts.push("********");
53
+ }
54
+ return parts.filter((p) => p !== "").join(" ");
55
+ }
56
+ function positiveInt(value, fallback) {
57
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
58
+ }
59
+ function streamingTail(text, maxLines = 6) {
60
+ const lines = String(text ?? "").split(/\r?\n/);
61
+ if (lines.length <= maxLines)
62
+ return String(text ?? "");
63
+ return "…\n" + lines.slice(-maxLines).join("\n");
64
+ }
65
+ function collapseToolOutput(output) {
66
+ const text = String(output ?? "");
67
+ const lines = text.split(/\r?\n/);
68
+ const first = (lines.find((line) => line.trim()) ?? "").trim();
69
+ const clipped = first.length > 160 ? first.slice(0, 157) + "..." : first;
70
+ const rest = Math.max(0, lines.length - 1);
71
+ return `${clipped}${clipped ? "\n" : ""}[${rest} more lines, ${text.length} chars]`;
72
+ }
73
+ function optionalBudget(name) {
74
+ const value = Number(process.env[name]);
75
+ return Number.isFinite(value) && value > 0 ? value : undefined;
76
+ }
77
+ function unclosedNotice(session) {
78
+ const where = session.cwd || "(unknown directory)";
79
+ const files = session.activeFiles?.length ?? 0;
80
+ return `Last session did not close normally (${where}, ~${session.turns} turns, ${files} file${files === 1 ? "" : "s"} changed).\n` +
81
+ `Resume it with \`fastcode resume ${session.id}\`, or list sessions with \`fastcode sessions\`.`;
82
+ }
83
+ function streamErrorMessage(message, provider, model) {
84
+ if (/\b(401|403)\b/.test(message) || /invalid api key|unauthorized|authentication/i.test(message)) {
85
+ return `Authentication failed for ${provider.label} · ${model}. The key may be wrong or not allowed for this model.\n` +
86
+ `Check with \`/whoami\`, or re-save: \`/login ${provider.id} ${model} <key>\`.`;
87
+ }
88
+ if (/\b(402|429)\b/.test(message) || /quota|insufficient|rate limit|billing|balance/i.test(message)) {
89
+ return `Request blocked: ${provider.label} reports no remaining quota / rate limit for ${model}.\n` +
90
+ `Top up the account or switch model/provider. (${message.slice(0, 200)})`;
91
+ }
92
+ return `Error: ${message}`;
93
+ }
94
+ export const App = ({ initialPrompt, resume }) => {
95
+ const { exit } = useApp();
96
+ const startupRef = useRef(null);
97
+ if (!startupRef.current) {
98
+ const config = loadConfig();
99
+ const resumed = resume ? resumeSession(resume) : undefined;
100
+ if (resumed) {
101
+ const provider = resumed.meta.providerId ? getProvider(resumed.meta.providerId) : undefined;
102
+ if (provider) {
103
+ config.providerId = provider.id;
104
+ if (resumed.meta.model)
105
+ config.model = resumed.meta.model;
106
+ }
107
+ }
108
+ const restored = resumed?.messages ?? [];
109
+ const transcript = restored.length > 0
110
+ ? restored.map((message, index) => ({
111
+ id: index + 1,
112
+ role: message.role,
113
+ content: message.role === "assistant" ? stripToolCalls(message.content) : message.content,
114
+ markdown: message.role !== "user",
115
+ }))
116
+ : createStartupTranscript(config);
117
+ if (resumed) {
118
+ const push = (text) => transcript.push({ id: transcript.length + 1, role: "system", content: text, markdown: true });
119
+ if (resumed.meta.providerId && !getProvider(resumed.meta.providerId))
120
+ push(`The session used provider ${resumed.meta.providerId}, which is not available in this build.`);
121
+ push(`Resumed session: ${config.providerId}::${config.model}${resumed.rebuilt ? " (projection rebuilt)" : ""}.`);
122
+ for (const warning of resumed.warnings)
123
+ push(warning);
124
+ }
125
+ else if (!resume && loggingEnabled()) {
126
+ const unclosed = latestUnclosedSession();
127
+ if (unclosed) {
128
+ transcript.push({
129
+ id: transcript.length + 1,
130
+ role: "system",
131
+ content: unclosedNotice(unclosed),
132
+ markdown: true,
133
+ });
134
+ }
135
+ }
136
+ startupRef.current = {
137
+ config,
138
+ transcript,
139
+ history: restored.map((message) => ({ role: message.role, content: message.content })),
140
+ };
141
+ }
142
+ const startup = startupRef.current;
143
+ const sessionRef = useRef(null);
144
+ if (!sessionRef.current) {
145
+ sessionRef.current = resume
146
+ ? openSessionLog(resume, {
147
+ providerId: startup.config.providerId,
148
+ model: startup.config.model,
149
+ cwd: process.cwd(),
150
+ })
151
+ : createSessionLog({
152
+ providerId: startup.config.providerId,
153
+ model: startup.config.model,
154
+ cwd: process.cwd(),
155
+ });
156
+ }
157
+ const [, setTrustEpoch] = useState(0);
158
+ const trusted = isTrusted(process.cwd());
159
+ const [config, setConfig] = useState(startup.config);
160
+ const [history, setHistory] = useState(startup.history ?? []);
161
+ const [transcript, setTranscript] = useState(startup.transcript);
162
+ const [streamingId, setStreamingId] = useState(null);
163
+ const [streamingText, setStreamingText] = useState("");
164
+ const [streamPhase, setStreamPhase] = useState("thinking");
165
+ const [input, setInput] = useState("");
166
+ const [pendingLogin, setPendingLogin] = useState(null);
167
+ const [picker, setPicker] = useState(null);
168
+ const [pickerIndex, setPickerIndex] = useState(0);
169
+ const [busy, setBusy] = useState(false);
170
+ const [expanded, setExpanded] = useState(false);
171
+ const steerQueueRef = useRef([]);
172
+ const [totalIn, setTotalIn] = useState(0);
173
+ const [totalOut, setTotalOut] = useState(0);
174
+ const [totalCost, setTotalCost] = useState(0);
175
+ const [agentActivity, setAgentActivity] = useState(null);
176
+ const idRef = useRef(startup.transcript.length);
177
+ const abortRef = useRef(null);
178
+ const sawDeltaRef = useRef(false);
179
+ const lastStreamFlushRef = useRef(0);
180
+ const ctrlCRef = useRef(0);
181
+ const initialPromptSubmittedRef = useRef(false);
182
+ const theme = getTheme(config.theme);
183
+ const stableUi = useMemo(() => shouldUseStableTerminalUi(), []);
184
+ const liveStreamingUi = useMemo(() => shouldUseLiveStreamingUi(), []);
185
+ // The banner picks a font that fits the terminal width (see ui/Banner.ts)
186
+ // and renders nothing when even the smallest font is too wide.
187
+ const showBanner = transcript.length <= startup.transcript.length && !busy;
188
+ const promptPlaceholder = pendingLogin
189
+ ? pendingLogin.step === "provider"
190
+ ? "Provider id (e.g. deepseek) · Esc to cancel"
191
+ : pendingLogin.step === "model"
192
+ ? "Model id (e.g. deepseek-chat) · Esc to cancel"
193
+ : pendingLogin.provider && getLegacy(pendingLogin.provider)
194
+ ? "Paste API key (masked), or type 'keep' · Esc to cancel"
195
+ : "Paste API key (masked) · Esc to cancel"
196
+ : undefined;
197
+ const promptMask = pendingLogin?.step === "key" ? "*" : undefined;
198
+ // Persist config whenever it changes.
199
+ useEffect(() => {
200
+ saveConfig(config);
201
+ }, [config]);
202
+ const chooseItem = useCallback((request, index) => {
203
+ const item = request?.items?.[index];
204
+ setPicker(null);
205
+ if (item)
206
+ request.onSelect(item.value, item);
207
+ }, []);
208
+ // Esc cancels streaming. Double Ctrl+C quits.
209
+ useInput((inputChar, key) => {
210
+ if (picker) {
211
+ if (key.escape) {
212
+ picker.onCancel?.();
213
+ setPicker(null);
214
+ return;
215
+ }
216
+ if (key.upArrow) {
217
+ setPickerIndex((i) => Math.max(0, i - 1));
218
+ return;
219
+ }
220
+ if (key.downArrow) {
221
+ setPickerIndex((i) => Math.min(picker.items.length - 1, i + 1));
222
+ return;
223
+ }
224
+ if (key.return) {
225
+ chooseItem(picker, pickerIndex);
226
+ return;
227
+ }
228
+ if (/^[1-9]$/.test(inputChar)) {
229
+ chooseItem(picker, Number(inputChar) - 1);
230
+ return;
231
+ }
232
+ return;
233
+ }
234
+ if (key.escape) {
235
+ if (pendingLogin) {
236
+ setPendingLogin(null);
237
+ setInput("");
238
+ pushSystem("API key entry cancelled.");
239
+ return;
240
+ }
241
+ if (abortRef.current) {
242
+ abortRef.current.abort();
243
+ abortRef.current = null;
244
+ }
245
+ return;
246
+ }
247
+ if (key.ctrl && inputChar === "c") {
248
+ ctrlCRef.current += 1;
249
+ if (ctrlCRef.current >= 2) {
250
+ exit();
251
+ }
252
+ else {
253
+ pushSystem("Press Ctrl+C again to quit.");
254
+ setTimeout(() => {
255
+ ctrlCRef.current = 0;
256
+ }, 1500);
257
+ }
258
+ }
259
+ });
260
+ const pushSystem = useCallback((text) => {
261
+ idRef.current += 1;
262
+ sessionRef.current.append({ type: "notice", content: text });
263
+ setTranscript((prev) => [
264
+ ...prev,
265
+ { id: idRef.current, role: "system", content: text, markdown: true },
266
+ ]);
267
+ }, []);
268
+ const pushUser = useCallback((text, recordFact = true) => {
269
+ idRef.current += 1;
270
+ sessionRef.current.append(recordFact
271
+ ? { type: "user", content: text }
272
+ : { type: "notice", content: text });
273
+ setTranscript((prev) => [
274
+ ...prev,
275
+ { id: idRef.current, role: "user", content: text, markdown: false },
276
+ ]);
277
+ }, []);
278
+ const actions = useMemo(() => ({
279
+ setProvider: (id) => setConfig((c) => ({ ...c, providerId: id })),
280
+ setModel: (id) => setConfig((c) => ({ ...c, model: id })),
281
+ setReasoningEffort: (effort) => setConfig((c) => ({ ...c, reasoningEffort: effort })),
282
+ setTheme: (id) => setConfig((c) => ({ ...c, theme: id })),
283
+ setAgentMode: (enabled) => setConfig((c) => ({ ...c, agentMode: enabled })),
284
+ setSystemPrompt: (p) => setConfig((c) => ({ ...c, systemPrompt: p })),
285
+ setSkillsRegistry: (url) => setConfig((c) => ({ ...c, skillsRegistry: url })),
286
+ enableSkill: (name) => setConfig((c) => ({
287
+ ...c,
288
+ enabledSkills: Array.from(new Set([...(c.enabledSkills ?? []), name])),
289
+ })),
290
+ disableSkill: (name) => setConfig((c) => ({
291
+ ...c,
292
+ enabledSkills: (c.enabledSkills ?? []).filter((n) => n !== name),
293
+ })),
294
+ enablePlugin: (name) => setConfig((c) => ({
295
+ ...c,
296
+ enabledPlugins: Array.from(new Set([...(c.enabledPlugins ?? []), name])),
297
+ })),
298
+ disablePlugin: (name) => setConfig((c) => ({
299
+ ...c,
300
+ enabledPlugins: (c.enabledPlugins ?? []).filter((n) => n !== name),
301
+ })),
302
+ clearHistory: () => {
303
+ setHistory([]);
304
+ setTranscript([]);
305
+ },
306
+ replaceHistory: (messages) => setHistory(messages),
307
+ cancelRun: () => {
308
+ if (abortRef.current) {
309
+ abortRef.current.abort();
310
+ abortRef.current = null;
311
+ }
312
+ },
313
+ steer: (text) => {
314
+ steerQueueRef.current.push(text);
315
+ },
316
+ setExpanded: (value) => setExpanded(value),
317
+ setInput: (value) => setInput(value),
318
+ pushSystemMessage: pushSystem,
319
+ startLogin: ({ provider, model }) => {
320
+ setPendingLogin({
321
+ step: provider ? (model ? "key" : "model") : "provider",
322
+ provider,
323
+ model,
324
+ });
325
+ },
326
+ }), [pushSystem]);
327
+ const runSlash = useCallback(async (raw) => {
328
+ const parsed = parseSlash(raw);
329
+ if (!parsed)
330
+ return false;
331
+ const cmd = findCommand(parsed.name);
332
+ if (!cmd) {
333
+ pushSystem(`Unknown command "/${parsed.name}". Try /help.`);
334
+ return true;
335
+ }
336
+ if (busy && !["cancel", "steer", "jobs", "kill", "attach", "expand", "context"].includes(cmd.name)) {
337
+ pushSystem(`/${cmd.name} is unavailable while a run is active. Try /cancel or /steer.`);
338
+ return true;
339
+ }
340
+ // Echo the slash invocation for transcript clarity, but never echo a
341
+ // secret: the login key is masked, while provider/model stay readable.
342
+ const echo = parsed.name === "login" ? maskLoginEcho(parsed.args) : raw;
343
+ pushUser(echo, false);
344
+ try {
345
+ const res = await cmd.run({
346
+ args: parsed.args,
347
+ raw: parsed.raw,
348
+ state: {
349
+ providerId: config.providerId,
350
+ model: config.model,
351
+ theme: config.theme,
352
+ agentMode: config.agentMode,
353
+ history,
354
+ systemPrompt: config.systemPrompt,
355
+ totalCostUsd: totalCost,
356
+ totalInputTokens: totalIn,
357
+ totalOutputTokens: totalOut,
358
+ enabledSkills: config.enabledSkills,
359
+ enabledPlugins: config.enabledPlugins,
360
+ skillsRegistry: config.skillsRegistry,
361
+ reasoningEffort: config.reasoningEffort,
362
+ expanded,
363
+ busy,
364
+ draft: input,
365
+ },
366
+ actions,
367
+ });
368
+ if (res?.message)
369
+ pushSystem(res.message);
370
+ if (res?.picker) {
371
+ const index = Math.max(0, res.picker.items.findIndex((item) => item.value === res.picker.current));
372
+ setPickerIndex(index);
373
+ setPicker(res.picker);
374
+ setInput("");
375
+ }
376
+ if (res?.quit)
377
+ exit();
378
+ }
379
+ catch (err) {
380
+ pushSystem(`Command failed: ${err.message}`);
381
+ }
382
+ return true;
383
+ }, [
384
+ actions,
385
+ config.agentMode,
386
+ config.enabledPlugins,
387
+ config.enabledSkills,
388
+ config.model,
389
+ config.providerId,
390
+ config.skillsRegistry,
391
+ config.systemPrompt,
392
+ config.theme,
393
+ busy,
394
+ expanded,
395
+ exit,
396
+ history,
397
+ pushSystem,
398
+ pushUser,
399
+ totalCost,
400
+ totalIn,
401
+ totalOut,
402
+ ]);
403
+ const handleSubmit = useCallback(async (raw) => {
404
+ const text = raw.trim();
405
+ if (!text)
406
+ return;
407
+ if (busy) {
408
+ if (text.startsWith("/")) {
409
+ await runSlash(text);
410
+ return;
411
+ }
412
+ pushSystem("A run is active. Use /cancel to stop, /steer <text> to guide it, or wait.");
413
+ return;
414
+ }
415
+ setInput("");
416
+ if (text.startsWith("/")) {
417
+ await runSlash(text);
418
+ return;
419
+ }
420
+ pushUser(text);
421
+ const provider = getProviderOrThrow(config.providerId);
422
+ let apiKey;
423
+ if (provider.authEnv.length > 0) {
424
+ const lookup = resolveApiKey(provider.id, config.model, provider.authEnv);
425
+ if (lookup.status === "unbound") {
426
+ pushSystem(`Cannot send: your stored ${provider.label} key isn't bound to a model, so it won't be used.\n` +
427
+ `Re-bind it with \`/login ${provider.id} ${config.model} <key>\` (or \`/logout ${provider.id}\` to discard it).`);
428
+ return;
429
+ }
430
+ if (lookup.status === "missing") {
431
+ pushSystem(`Cannot send: no API key for ${provider.label}.\n` +
432
+ `Run \`/login ${provider.id} ${config.model} <key>\` (one key covers every ${provider.id} model), or set ${provider.authEnv[0]}.`);
433
+ return;
434
+ }
435
+ apiKey = lookup.key;
436
+ }
437
+ idRef.current += 1;
438
+ const bubbleId = idRef.current;
439
+ setStreamingId(bubbleId);
440
+ setStreamingText("");
441
+ setStreamPhase("thinking");
442
+ steerQueueRef.current = [];
443
+ lastStreamFlushRef.current = 0;
444
+ const ctrl = new AbortController();
445
+ abortRef.current = ctrl;
446
+ setBusy(true);
447
+ let receivedDone = false;
448
+ try {
449
+ const maxTurns = config.agentMode ? positiveInt(config.maxAgentTurns, 24) : 1;
450
+ const skillsBlock = buildSkillsPrompt(config.enabledSkills);
451
+ const pluginsBlock = buildPluginsPrompt(config.enabledPlugins);
452
+ const agentBlock = config.agentMode ? AGENT_SYSTEM_PROMPT : "";
453
+ const fullSystem = [config.systemPrompt, skillsBlock, pluginsBlock, agentBlock]
454
+ .filter((part) => part.trim())
455
+ .join("\n\n");
456
+ let compactionNotified = false;
457
+ let lastRawFact = "";
458
+ sawDeltaRef.current = false;
459
+ const protocol = config.agentMode ? resolveToolProtocol(config, config.providerId, config.model) : "legacy";
460
+ for await (const ev of runAgent({
461
+ provider,
462
+ apiKey,
463
+ model: config.model,
464
+ messages: history,
465
+ prompt: text,
466
+ system: fullSystem,
467
+ maxTurns,
468
+ allowTools: config.agentMode,
469
+ tokenBudget: optionalBudget("FASTCODE_TOKEN_BUDGET"),
470
+ costBudgetUsd: optionalBudget("FASTCODE_COST_BUDGET_USD"),
471
+ signal: ctrl.signal,
472
+ cwd: process.cwd(),
473
+ idleTimeoutMs: config.requestIdleTimeoutMs,
474
+ compaction: compactionConfigFromEnv(),
475
+ reasoningEffort: config.reasoningEffort,
476
+ protocol,
477
+ tools: protocol === "native" ? toolSchemas() : undefined,
478
+ onProtocolFallback: (message) => {
479
+ setConfig((current) => withToolCapability(current, config.providerId, config.model, "pseudo"));
480
+ pushSystem(message);
481
+ },
482
+ onNativeOk: () => {
483
+ if (protocol === "native")
484
+ setConfig((current) => withToolCapability(current, config.providerId, config.model, "native"));
485
+ },
486
+ takeSteering: () => {
487
+ const queued = steerQueueRef.current;
488
+ steerQueueRef.current = [];
489
+ return queued;
490
+ },
491
+ onDelta: (delta, full) => {
492
+ if (!sawDeltaRef.current) {
493
+ sawDeltaRef.current = true;
494
+ setStreamPhase("streaming");
495
+ }
496
+ if (!liveStreamingUi)
497
+ return;
498
+ const now = Date.now();
499
+ if (now - lastStreamFlushRef.current < 60)
500
+ return;
501
+ lastStreamFlushRef.current = now;
502
+ setStreamingText(stripToolCalls(full));
503
+ },
504
+ })) {
505
+ switch (ev.type) {
506
+ case "compaction":
507
+ sessionRef.current.append({
508
+ type: "compacted",
509
+ summaryRef: ev.summaryFile,
510
+ source: ev.source,
511
+ beforeTokens: ev.beforeTokens,
512
+ afterTokens: ev.afterTokens,
513
+ droppedMessages: ev.droppedMessages,
514
+ keepMessages: ev.keptMessages,
515
+ });
516
+ if (!compactionNotified) {
517
+ compactionNotified = true;
518
+ pushSystem(`[context compacted] ${ev.beforeTokens} → ${ev.afterTokens} tokens (${ev.reason}); recent turns kept.` +
519
+ (ev.summaryFile ? `\nSummary: ${ev.summaryFile}` : ""));
520
+ }
521
+ break;
522
+ case "assistant_raw":
523
+ lastRawFact = ev.text;
524
+ sessionRef.current.append({ type: "assistant_raw", content: ev.text, toolCalls: ev.toolCalls });
525
+ break;
526
+ case "assistant_text":
527
+ pushSystem(ev.text);
528
+ break;
529
+ case "tool_call":
530
+ sessionRef.current.append({ type: "tool_call", id: ev.id, tool: ev.call.tool, args: ev.call, native: Boolean(ev.native) });
531
+ setStreamPhase("tool");
532
+ setAgentActivity(`Coding: ${ev.call.tool}`);
533
+ pushSystem(`Running tool: \`${ev.call.tool}\`\n\n\`\`\`json\n${JSON.stringify(ev.call, null, 2)}\n\`\`\``);
534
+ break;
535
+ case "tool_result": {
536
+ sessionRef.current.append({
537
+ type: "tool_result",
538
+ id: ev.id,
539
+ tool: ev.call.tool,
540
+ ok: ev.ok,
541
+ output: ev.output,
542
+ sideEffects: ev.sideEffects,
543
+ });
544
+ const collapsed = stableUi && !expanded;
545
+ const body = collapsed ? collapseToolOutput(ev.output) : ev.output;
546
+ const hint = collapsed ? "\n\n_(output collapsed; /expand to show full output)_" : "";
547
+ pushSystem(`Tool ${ev.ok ? "completed" : "failed"}: \`${ev.call.tool}\`\n\n\`\`\`\n${body}\n\`\`\`${hint}`);
548
+ break;
549
+ }
550
+ case "usage":
551
+ if (ev.usage.inputTokens)
552
+ setTotalIn((n) => n + (ev.usage.inputTokens ?? 0));
553
+ if (ev.usage.outputTokens)
554
+ setTotalOut((n) => n + (ev.usage.outputTokens ?? 0));
555
+ if (ev.usage.costUsd)
556
+ setTotalCost((n) => n + (ev.usage.costUsd ?? 0));
557
+ break;
558
+ case "loop_detected":
559
+ pushSystem(`Loop detected: \`${ev.fingerprint.split(":")[0]}\` repeated ${ev.occurrences} times. Asking the model to change approach.`);
560
+ break;
561
+ case "notice":
562
+ pushSystem(ev.text);
563
+ break;
564
+ case "checkpoint":
565
+ sessionRef.current.append({
566
+ type: "checkpoint",
567
+ turn: ev.turn,
568
+ cwd: ev.cwd,
569
+ providerId: ev.providerId,
570
+ model: ev.model,
571
+ activeFiles: ev.activeFiles,
572
+ lastFactSeq: sessionRef.current.count(),
573
+ });
574
+ break;
575
+ case "error":
576
+ if (ev.kind === "aborted")
577
+ pushSystem("Cancelled.");
578
+ else if (ev.kind === "stream_error")
579
+ pushSystem(streamErrorMessage(ev.message, provider, config.model));
580
+ else
581
+ pushSystem(ev.message);
582
+ break;
583
+ case "done":
584
+ receivedDone = true;
585
+ setHistory(ev.history);
586
+ if (ev.finalText) {
587
+ idRef.current += 1;
588
+ const finalId = idRef.current;
589
+ if (ev.finalText !== lastRawFact)
590
+ sessionRef.current.append({ type: "assistant_raw", content: ev.finalText });
591
+ setTranscript((prev) => [
592
+ ...prev,
593
+ { id: finalId, role: "assistant", content: ev.finalText, markdown: true },
594
+ ]);
595
+ }
596
+ break;
597
+ }
598
+ }
599
+ }
600
+ catch (err) {
601
+ if (!receivedDone)
602
+ setHistory([...history, { role: "user", content: text }]);
603
+ pushSystem(`Error: ${String(err?.message ?? err)}`);
604
+ }
605
+ finally {
606
+ setStreamingId(null);
607
+ setStreamingText("");
608
+ setAgentActivity(null);
609
+ setStreamPhase("thinking");
610
+ setBusy(false);
611
+ abortRef.current = null;
612
+ }
613
+ }, [
614
+ busy,
615
+ config.agentMode,
616
+ config.enabledSkills,
617
+ config.enabledPlugins,
618
+ config.model,
619
+ config.providerId,
620
+ config.systemPrompt,
621
+ history,
622
+ pushSystem,
623
+ pushUser,
624
+ runSlash,
625
+ ]);
626
+ // Guided `/login`: provider → model → key. Text prompts only; the key step
627
+ // is masked. `keep` re-binds an existing unbound (legacy) key to a model.
628
+ const handleWizardSubmit = useCallback(async (raw) => {
629
+ const value = raw.trim();
630
+ setInput("");
631
+ if (!value || !pendingLogin)
632
+ return;
633
+ if (pendingLogin.step === "provider") {
634
+ const provider = getProvider(value);
635
+ if (!provider) {
636
+ pushSystem(`Unknown provider "${value}". Available: ${PROVIDERS.map((p) => p.id).join(", ")}`);
637
+ return;
638
+ }
639
+ if (provider.authEnv.length === 0) {
640
+ pushSystem(`${provider.label} doesn't require an API key. Nothing to save.`);
641
+ setPendingLogin(null);
642
+ return;
643
+ }
644
+ setPendingLogin({ step: "model", provider: provider.id });
645
+ const models = await provider.listModels(undefined).catch(() => []);
646
+ const names = models.slice(0, 8).map((m) => m.id).join(", ");
647
+ pushSystem(`Provider: ${provider.label} (${provider.id}).\n` +
648
+ `Type the model id to bind this key to — e.g. \`${provider.defaultModel}\`.` +
649
+ (names ? `\n known models: ${names}` : ""));
650
+ return;
651
+ }
652
+ if (pendingLogin.step === "model") {
653
+ const provider = getProvider(pendingLogin.provider);
654
+ if (!provider) {
655
+ setPendingLogin(null);
656
+ return;
657
+ }
658
+ setPendingLogin({ step: "key", provider: provider.id, model: value });
659
+ pushSystem(`Saved from model: ${provider.id} · ${value}.\n` +
660
+ `This key will be reused by every ${provider.label} model.\n` +
661
+ `Paste the API key (input is masked).` +
662
+ (getLegacy(provider.id) ? `\nOr type \`keep\` to re-bind the stored ${provider.label} key.` : ""));
663
+ return;
664
+ }
665
+ const provider = getProvider(pendingLogin.provider);
666
+ if (!provider) {
667
+ setPendingLogin(null);
668
+ return;
669
+ }
670
+ let key = value;
671
+ if (value.toLowerCase() === "keep") {
672
+ const legacy = getLegacy(provider.id);
673
+ if (!legacy) {
674
+ pushSystem(`No stored ${provider.label} key to reuse — paste the key instead.`);
675
+ return;
676
+ }
677
+ key = legacy;
678
+ }
679
+ pushSystem(saveScopedKey(provider.id, provider.label, pendingLogin.model, key));
680
+ setPendingLogin(null);
681
+ }, [pendingLogin, pushSystem]);
682
+ useEffect(() => {
683
+ if (!initialPrompt || initialPromptSubmittedRef.current)
684
+ return;
685
+ initialPromptSubmittedRef.current = true;
686
+ const t = setTimeout(() => void handleSubmit(initialPrompt), 50);
687
+ return () => clearTimeout(t);
688
+ }, [handleSubmit, initialPrompt]);
689
+ useEffect(() => () => {
690
+ shutdownShells();
691
+ if (sessionRef.current)
692
+ sessionRef.current.close();
693
+ }, []);
694
+ if (config.agentMode && !trusted) {
695
+ const decide = (allow) => {
696
+ if (allow) {
697
+ trustProject(process.cwd());
698
+ }
699
+ else {
700
+ setConfig((c) => ({ ...c, agentMode: false }));
701
+ pushSystem("Trust declined: agent tools are off for this session (chat only).");
702
+ }
703
+ setTrustEpoch((n) => n + 1);
704
+ };
705
+ return _jsx(TrustGate, { theme: theme, cwd: process.cwd(), onDecision: decide });
706
+ }
707
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [showBanner ? _jsx(Banner, { theme: theme, stable: stableUi }) : null, _jsx(Static, { items: transcript, children: (entry) => (_jsx(Box, { marginTop: 1, children: _jsx(MessageBubble, { theme: theme, role: entry.role, content: entry.content, markdown: entry.markdown }) }, entry.id)) }), streamingId !== null && liveStreamingUi ? (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(MessageBubble, { theme: theme, role: "assistant", content: streamingTail(streamingText, 6), markdown: false, streaming: true }) })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [busy ? (_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { theme: theme, label: agentActivity ?? (streamPhase === "streaming" ? "Streaming" : streamPhase === "tool" ? "Waiting for tool" : "Thinking"), stable: stableUi }) })) : null, picker ? _jsx(Picker, { theme: theme, title: picker.title, items: picker.items, index: pickerIndex, current: picker.current }) : _jsx(Prompt, { theme: theme, value: input, onChange: setInput, onSubmit: pendingLogin ? handleWizardSubmit : handleSubmit, disabled: false, placeholder: promptPlaceholder, mask: promptMask }), _jsx(StatusBar, { theme: theme, providerId: config.providerId, model: config.model, inputTokens: totalIn, outputTokens: totalOut, costUsd: totalCost, enabledSkillCount: config.enabledSkills.length + config.enabledPlugins.length, reasoningEffort: config.reasoningEffort }), _jsxs(Text, { color: theme.muted, children: [COMMANDS.length, " commands \u00B7 ", config.enabledSkills.length, " skill(s) active"] })] })] }));
708
+ };