@datename/fastcode-termux 0.1.6-beta → 0.1.6

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 (246) hide show
  1. package/README.md +6 -24
  2. package/dist/agent/guards.d.ts +0 -8
  3. package/dist/agent/guards.js +0 -195
  4. package/dist/agent/protocol.js +1 -4
  5. package/dist/agent/tools.d.ts +1 -3
  6. package/dist/agent/tools.js +377 -2
  7. package/dist/app.js +103 -335
  8. package/dist/cli.js +311 -362
  9. package/dist/commands/index.js +0 -22
  10. package/dist/commands/login.d.ts +2 -4
  11. package/dist/commands/login.js +10 -12
  12. package/dist/commands/logout.js +16 -8
  13. package/dist/commands/model.d.ts +0 -13
  14. package/dist/commands/model.js +22 -42
  15. package/dist/commands/provider.d.ts +0 -13
  16. package/dist/commands/provider.js +9 -20
  17. package/dist/commands/whoami.js +5 -6
  18. package/dist/config/store.d.ts +12 -32
  19. package/dist/config/store.js +65 -117
  20. package/dist/config/trust.d.ts +8 -21
  21. package/dist/config/trust.js +13 -99
  22. package/dist/lib/diagnostics.d.ts +4 -29
  23. package/dist/lib/diagnostics.js +4 -60
  24. package/dist/lib/http.d.ts +1 -11
  25. package/dist/lib/http.js +4 -62
  26. package/dist/providers/anthropic.d.ts +0 -1
  27. package/dist/providers/anthropic.js +0 -12
  28. package/dist/providers/custom.d.ts +0 -9
  29. package/dist/providers/deepseek.d.ts +0 -10
  30. package/dist/providers/deepseek.js +1 -2
  31. package/dist/providers/google.d.ts +0 -9
  32. package/dist/providers/groq.d.ts +0 -10
  33. package/dist/providers/groq.js +1 -2
  34. package/dist/providers/index.d.ts +201 -10
  35. package/dist/providers/index.js +0 -30
  36. package/dist/providers/mistral.d.ts +0 -10
  37. package/dist/providers/mistral.js +1 -2
  38. package/dist/providers/moonshot.d.ts +0 -10
  39. package/dist/providers/moonshot.js +1 -2
  40. package/dist/providers/ollama.d.ts +0 -9
  41. package/dist/providers/openai.d.ts +0 -10
  42. package/dist/providers/openai.js +1 -2
  43. package/dist/providers/openaiCompatible.d.ts +0 -9
  44. package/dist/providers/openaiCompatible.js +10 -58
  45. package/dist/providers/openrouter.d.ts +0 -10
  46. package/dist/providers/openrouter.js +1 -2
  47. package/dist/providers/qwen.d.ts +0 -10
  48. package/dist/providers/qwen.js +1 -2
  49. package/dist/providers/xai.d.ts +0 -10
  50. package/dist/providers/xai.js +1 -2
  51. package/dist/providers/zhipu.d.ts +0 -10
  52. package/dist/providers/zhipu.js +1 -2
  53. package/dist/session/log.d.ts +12 -35
  54. package/dist/session/log.js +10 -206
  55. package/dist/types.d.ts +1 -60
  56. package/dist/ui/Message.d.ts +7 -1
  57. package/dist/ui/Message.js +2 -13
  58. package/dist/ui/Prompt.d.ts +2 -1
  59. package/dist/ui/Prompt.js +2 -2
  60. package/dist/ui/Spinner.js +5 -11
  61. package/dist/ui/StatusBar.d.ts +1 -2
  62. package/dist/ui/StatusBar.js +5 -6
  63. package/package.json +1 -1
  64. package/src/agent/guards.ts +0 -196
  65. package/src/agent/protocol.ts +1 -4
  66. package/src/agent/tools.ts +377 -3
  67. package/src/app.ts +103 -335
  68. package/src/cli.ts +3 -30
  69. package/src/commands/index.ts +0 -22
  70. package/src/commands/login.ts +10 -12
  71. package/src/commands/logout.ts +16 -8
  72. package/src/commands/model.ts +22 -42
  73. package/src/commands/provider.ts +9 -20
  74. package/src/commands/whoami.ts +5 -6
  75. package/src/config/store.ts +69 -122
  76. package/src/config/trust.ts +21 -137
  77. package/src/lib/diagnostics.ts +4 -60
  78. package/src/lib/http.ts +4 -66
  79. package/src/providers/anthropic.ts +1 -13
  80. package/src/providers/deepseek.ts +1 -2
  81. package/src/providers/groq.ts +1 -2
  82. package/src/providers/index.ts +1 -32
  83. package/src/providers/mistral.ts +1 -2
  84. package/src/providers/moonshot.ts +1 -2
  85. package/src/providers/openai.ts +1 -2
  86. package/src/providers/openaiCompatible.ts +10 -58
  87. package/src/providers/openrouter.ts +1 -2
  88. package/src/providers/qwen.ts +1 -2
  89. package/src/providers/xai.ts +1 -2
  90. package/src/providers/zhipu.ts +1 -2
  91. package/src/session/log.ts +28 -267
  92. package/src/types.ts +1 -63
  93. package/src/ui/Message.ts +2 -13
  94. package/src/ui/Prompt.ts +2 -2
  95. package/src/ui/Spinner.ts +5 -11
  96. package/src/ui/StatusBar.ts +5 -6
  97. package/dist/agent/approval.d.ts +0 -50
  98. package/dist/agent/approval.js +0 -7
  99. package/dist/agent/compaction.d.ts +0 -39
  100. package/dist/agent/compaction.js +0 -227
  101. package/dist/agent/context.d.ts +0 -29
  102. package/dist/agent/context.js +0 -62
  103. package/dist/agent/ignore.d.ts +0 -11
  104. package/dist/agent/ignore.js +0 -129
  105. package/dist/agent/process-tree.d.ts +0 -2
  106. package/dist/agent/process-tree.js +0 -31
  107. package/dist/agent/protocol-mode.d.ts +0 -6
  108. package/dist/agent/protocol-mode.js +0 -51
  109. package/dist/agent/registry.d.ts +0 -17
  110. package/dist/agent/registry.js +0 -90
  111. package/dist/agent/runner.d.ts +0 -106
  112. package/dist/agent/runner.js +0 -352
  113. package/dist/agent/shell-session.d.ts +0 -37
  114. package/dist/agent/shell-session.js +0 -400
  115. package/dist/agent/stream.d.ts +0 -29
  116. package/dist/agent/stream.js +0 -103
  117. package/dist/agent/tool-schemas.d.ts +0 -2
  118. package/dist/agent/tool-schemas.js +0 -63
  119. package/dist/agent/tools/append_file.d.ts +0 -2
  120. package/dist/agent/tools/append_file.js +0 -26
  121. package/dist/agent/tools/apply_patch.d.ts +0 -10
  122. package/dist/agent/tools/apply_patch.js +0 -52
  123. package/dist/agent/tools/copy_file.d.ts +0 -2
  124. package/dist/agent/tools/copy_file.js +0 -30
  125. package/dist/agent/tools/delete_file.d.ts +0 -2
  126. package/dist/agent/tools/delete_file.js +0 -24
  127. package/dist/agent/tools/grep.d.ts +0 -2
  128. package/dist/agent/tools/grep.js +0 -23
  129. package/dist/agent/tools/index.d.ts +0 -2
  130. package/dist/agent/tools/index.js +0 -32
  131. package/dist/agent/tools/list_files.d.ts +0 -2
  132. package/dist/agent/tools/list_files.js +0 -17
  133. package/dist/agent/tools/make_dir.d.ts +0 -2
  134. package/dist/agent/tools/make_dir.js +0 -24
  135. package/dist/agent/tools/move_path.d.ts +0 -2
  136. package/dist/agent/tools/move_path.js +0 -47
  137. package/dist/agent/tools/paths.d.ts +0 -7
  138. package/dist/agent/tools/paths.js +0 -84
  139. package/dist/agent/tools/pwd.d.ts +0 -2
  140. package/dist/agent/tools/pwd.js +0 -10
  141. package/dist/agent/tools/read_file.d.ts +0 -2
  142. package/dist/agent/tools/read_file.js +0 -21
  143. package/dist/agent/tools/read_many.d.ts +0 -2
  144. package/dist/agent/tools/read_many.js +0 -25
  145. package/dist/agent/tools/readers.d.ts +0 -6
  146. package/dist/agent/tools/readers.js +0 -216
  147. package/dist/agent/tools/replace_in_file.d.ts +0 -2
  148. package/dist/agent/tools/replace_in_file.js +0 -30
  149. package/dist/agent/tools/shell.d.ts +0 -2
  150. package/dist/agent/tools/shell.js +0 -48
  151. package/dist/agent/tools/stat_path.d.ts +0 -2
  152. package/dist/agent/tools/stat_path.js +0 -31
  153. package/dist/agent/tools/types.d.ts +0 -39
  154. package/dist/agent/tools/types.js +0 -1
  155. package/dist/agent/tools/validate.d.ts +0 -6
  156. package/dist/agent/tools/validate.js +0 -29
  157. package/dist/agent/tools/write_file.d.ts +0 -2
  158. package/dist/agent/tools/write_file.js +0 -26
  159. package/dist/commands/attach.d.ts +0 -9
  160. package/dist/commands/attach.js +0 -15
  161. package/dist/commands/bg.d.ts +0 -7
  162. package/dist/commands/bg.js +0 -9
  163. package/dist/commands/cancel.d.ts +0 -10
  164. package/dist/commands/cancel.js +0 -10
  165. package/dist/commands/compact.d.ts +0 -10
  166. package/dist/commands/compact.js +0 -22
  167. package/dist/commands/context.d.ts +0 -7
  168. package/dist/commands/context.js +0 -24
  169. package/dist/commands/edit.d.ts +0 -10
  170. package/dist/commands/edit.js +0 -36
  171. package/dist/commands/effort.d.ts +0 -25
  172. package/dist/commands/effort.js +0 -48
  173. package/dist/commands/expand.d.ts +0 -10
  174. package/dist/commands/expand.js +0 -8
  175. package/dist/commands/jobs.d.ts +0 -7
  176. package/dist/commands/jobs.js +0 -16
  177. package/dist/commands/kill.d.ts +0 -7
  178. package/dist/commands/kill.js +0 -11
  179. package/dist/commands/steer.d.ts +0 -11
  180. package/dist/commands/steer.js +0 -13
  181. package/dist/lib/model-meta.d.ts +0 -19
  182. package/dist/lib/model-meta.js +0 -113
  183. package/dist/lib/native-tools.d.ts +0 -12
  184. package/dist/lib/native-tools.js +0 -21
  185. package/dist/lib/net.d.ts +0 -33
  186. package/dist/lib/net.js +0 -168
  187. package/dist/patch/apply.d.ts +0 -15
  188. package/dist/patch/apply.js +0 -172
  189. package/dist/patch/parse.d.ts +0 -29
  190. package/dist/patch/parse.js +0 -112
  191. package/dist/patch/plan.d.ts +0 -49
  192. package/dist/patch/plan.js +0 -273
  193. package/dist/session/projection.d.ts +0 -43
  194. package/dist/session/projection.js +0 -257
  195. package/dist/ui/Picker.d.ts +0 -8
  196. package/dist/ui/Picker.js +0 -24
  197. package/src/agent/approval.ts +0 -47
  198. package/src/agent/compaction.ts +0 -276
  199. package/src/agent/context.ts +0 -97
  200. package/src/agent/ignore.ts +0 -147
  201. package/src/agent/process-tree.ts +0 -32
  202. package/src/agent/protocol-mode.ts +0 -59
  203. package/src/agent/registry.ts +0 -98
  204. package/src/agent/runner.ts +0 -443
  205. package/src/agent/shell-session.ts +0 -449
  206. package/src/agent/stream.ts +0 -143
  207. package/src/agent/tool-schemas.ts +0 -67
  208. package/src/agent/tools/append_file.ts +0 -28
  209. package/src/agent/tools/apply_patch.ts +0 -54
  210. package/src/agent/tools/copy_file.ts +0 -32
  211. package/src/agent/tools/delete_file.ts +0 -26
  212. package/src/agent/tools/grep.ts +0 -25
  213. package/src/agent/tools/index.ts +0 -34
  214. package/src/agent/tools/list_files.ts +0 -19
  215. package/src/agent/tools/make_dir.ts +0 -26
  216. package/src/agent/tools/move_path.ts +0 -50
  217. package/src/agent/tools/paths.ts +0 -92
  218. package/src/agent/tools/pwd.ts +0 -12
  219. package/src/agent/tools/read_file.ts +0 -23
  220. package/src/agent/tools/read_many.ts +0 -27
  221. package/src/agent/tools/readers.ts +0 -228
  222. package/src/agent/tools/replace_in_file.ts +0 -32
  223. package/src/agent/tools/shell.ts +0 -53
  224. package/src/agent/tools/stat_path.ts +0 -33
  225. package/src/agent/tools/types.ts +0 -42
  226. package/src/agent/tools/validate.ts +0 -35
  227. package/src/agent/tools/write_file.ts +0 -28
  228. package/src/commands/attach.ts +0 -16
  229. package/src/commands/bg.ts +0 -9
  230. package/src/commands/cancel.ts +0 -10
  231. package/src/commands/compact.ts +0 -23
  232. package/src/commands/context.ts +0 -25
  233. package/src/commands/edit.ts +0 -37
  234. package/src/commands/effort.ts +0 -49
  235. package/src/commands/expand.ts +0 -8
  236. package/src/commands/jobs.ts +0 -17
  237. package/src/commands/kill.ts +0 -12
  238. package/src/commands/steer.ts +0 -13
  239. package/src/lib/model-meta.ts +0 -138
  240. package/src/lib/native-tools.ts +0 -32
  241. package/src/lib/net.ts +0 -204
  242. package/src/patch/apply.ts +0 -194
  243. package/src/patch/parse.ts +0 -131
  244. package/src/patch/plan.ts +0 -317
  245. package/src/session/projection.ts +0 -308
  246. package/src/ui/Picker.ts +0 -24
package/dist/app.js CHANGED
@@ -11,18 +11,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  */
12
12
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
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";
14
+ import { AGENT_SYSTEM_PROMPT, formatToolResults, parseToolCalls, stripToolCalls, } from "./agent/protocol.js";
15
+ import { runAgentTool } from "./agent/tools.js";
20
16
  import { Banner } from "./ui/Banner.js";
21
17
  import { Spinner } from "./ui/Spinner.js";
22
18
  import { StatusBar } from "./ui/StatusBar.js";
23
19
  import { MessageBubble } from "./ui/Message.js";
24
20
  import { Prompt } from "./ui/Prompt.js";
25
- import { Picker } from "./ui/Picker.js";
26
21
  import { COMMANDS, findCommand, parseSlash } from "./commands/index.js";
27
22
  import { saveScopedKey } from "./commands/login.js";
28
23
  import { getProvider, getProviderOrThrow, PROVIDERS } from "./providers/index.js";
@@ -32,8 +27,7 @@ import { createStartupTranscript } from "./lib/startup.js";
32
27
  import { shouldUseLiveStreamingUi, shouldUseStableTerminalUi } from "./lib/uiMode.js";
33
28
  import { buildPluginsPrompt } from "./plugins/manager.js";
34
29
  import { buildSkillsPrompt } from "./skills/manager.js";
35
- import { createSessionLog, latestUnclosedSession, loggingEnabled, openSessionLog } from "./session/log.js";
36
- import { resumeSession } from "./session/projection.js";
30
+ import { createSessionLog, loadSessionMessages, openSessionLog } from "./session/log.js";
37
31
  import { isTrusted, trustProject } from "./config/trust.js";
38
32
  import { TrustGate } from "./ui/TrustGate.js";
39
33
  /**
@@ -53,89 +47,22 @@ function maskLoginEcho(args) {
53
47
  }
54
48
  return parts.filter((p) => p !== "").join(" ");
55
49
  }
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
50
  export const App = ({ initialPrompt, resume }) => {
95
51
  const { exit } = useApp();
96
52
  const startupRef = useRef(null);
97
53
  if (!startupRef.current) {
98
54
  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
- }
55
+ const restored = resume ? loadSessionMessages(resume) : [];
136
56
  startupRef.current = {
137
57
  config,
138
- transcript,
58
+ transcript: restored.length > 0
59
+ ? restored.map((message, index) => ({
60
+ id: index + 1,
61
+ role: message.role,
62
+ content: message.content,
63
+ markdown: message.role !== "user",
64
+ }))
65
+ : createStartupTranscript(config),
139
66
  history: restored.map((message) => ({ role: message.role, content: message.content })),
140
67
  };
141
68
  }
@@ -143,40 +70,28 @@ export const App = ({ initialPrompt, resume }) => {
143
70
  const sessionRef = useRef(null);
144
71
  if (!sessionRef.current) {
145
72
  sessionRef.current = resume
146
- ? openSessionLog(resume, {
147
- providerId: startup.config.providerId,
148
- model: startup.config.model,
149
- cwd: process.cwd(),
150
- })
73
+ ? openSessionLog(resume)
151
74
  : createSessionLog({
152
75
  providerId: startup.config.providerId,
153
76
  model: startup.config.model,
154
77
  cwd: process.cwd(),
155
78
  });
156
79
  }
157
- const [, setTrustEpoch] = useState(0);
158
- const trusted = isTrusted(process.cwd());
80
+ const [trusted, setTrusted] = useState(() => isTrusted(process.cwd()));
159
81
  const [config, setConfig] = useState(startup.config);
160
82
  const [history, setHistory] = useState(startup.history ?? []);
161
83
  const [transcript, setTranscript] = useState(startup.transcript);
162
84
  const [streamingId, setStreamingId] = useState(null);
163
85
  const [streamingText, setStreamingText] = useState("");
164
- const [streamPhase, setStreamPhase] = useState("thinking");
165
86
  const [input, setInput] = useState("");
166
87
  const [pendingLogin, setPendingLogin] = useState(null);
167
- const [picker, setPicker] = useState(null);
168
- const [pickerIndex, setPickerIndex] = useState(0);
169
88
  const [busy, setBusy] = useState(false);
170
- const [expanded, setExpanded] = useState(false);
171
- const steerQueueRef = useRef([]);
172
89
  const [totalIn, setTotalIn] = useState(0);
173
90
  const [totalOut, setTotalOut] = useState(0);
174
91
  const [totalCost, setTotalCost] = useState(0);
175
92
  const [agentActivity, setAgentActivity] = useState(null);
176
93
  const idRef = useRef(startup.transcript.length);
177
94
  const abortRef = useRef(null);
178
- const sawDeltaRef = useRef(false);
179
- const lastStreamFlushRef = useRef(0);
180
95
  const ctrlCRef = useRef(0);
181
96
  const initialPromptSubmittedRef = useRef(false);
182
97
  const theme = getTheme(config.theme);
@@ -199,38 +114,8 @@ export const App = ({ initialPrompt, resume }) => {
199
114
  useEffect(() => {
200
115
  saveConfig(config);
201
116
  }, [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
117
  // Esc cancels streaming. Double Ctrl+C quits.
209
118
  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
119
  if (key.escape) {
235
120
  if (pendingLogin) {
236
121
  setPendingLogin(null);
@@ -259,17 +144,15 @@ export const App = ({ initialPrompt, resume }) => {
259
144
  });
260
145
  const pushSystem = useCallback((text) => {
261
146
  idRef.current += 1;
262
- sessionRef.current.append({ type: "notice", content: text });
147
+ sessionRef.current.append({ type: "message", role: "system", content: text });
263
148
  setTranscript((prev) => [
264
149
  ...prev,
265
150
  { id: idRef.current, role: "system", content: text, markdown: true },
266
151
  ]);
267
152
  }, []);
268
- const pushUser = useCallback((text, recordFact = true) => {
153
+ const pushUser = useCallback((text) => {
269
154
  idRef.current += 1;
270
- sessionRef.current.append(recordFact
271
- ? { type: "user", content: text }
272
- : { type: "notice", content: text });
155
+ sessionRef.current.append({ type: "message", role: "user", content: text });
273
156
  setTranscript((prev) => [
274
157
  ...prev,
275
158
  { id: idRef.current, role: "user", content: text, markdown: false },
@@ -278,7 +161,6 @@ export const App = ({ initialPrompt, resume }) => {
278
161
  const actions = useMemo(() => ({
279
162
  setProvider: (id) => setConfig((c) => ({ ...c, providerId: id })),
280
163
  setModel: (id) => setConfig((c) => ({ ...c, model: id })),
281
- setReasoningEffort: (effort) => setConfig((c) => ({ ...c, reasoningEffort: effort })),
282
164
  setTheme: (id) => setConfig((c) => ({ ...c, theme: id })),
283
165
  setAgentMode: (enabled) => setConfig((c) => ({ ...c, agentMode: enabled })),
284
166
  setSystemPrompt: (p) => setConfig((c) => ({ ...c, systemPrompt: p })),
@@ -303,18 +185,6 @@ export const App = ({ initialPrompt, resume }) => {
303
185
  setHistory([]);
304
186
  setTranscript([]);
305
187
  },
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
188
  pushSystemMessage: pushSystem,
319
189
  startLogin: ({ provider, model }) => {
320
190
  setPendingLogin({
@@ -333,14 +203,10 @@ export const App = ({ initialPrompt, resume }) => {
333
203
  pushSystem(`Unknown command "/${parsed.name}". Try /help.`);
334
204
  return true;
335
205
  }
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
206
  // Echo the slash invocation for transcript clarity, but never echo a
341
207
  // secret: the login key is masked, while provider/model stay readable.
342
208
  const echo = parsed.name === "login" ? maskLoginEcho(parsed.args) : raw;
343
- pushUser(echo, false);
209
+ pushUser(echo);
344
210
  try {
345
211
  const res = await cmd.run({
346
212
  args: parsed.args,
@@ -358,21 +224,11 @@ export const App = ({ initialPrompt, resume }) => {
358
224
  enabledSkills: config.enabledSkills,
359
225
  enabledPlugins: config.enabledPlugins,
360
226
  skillsRegistry: config.skillsRegistry,
361
- reasoningEffort: config.reasoningEffort,
362
- expanded,
363
- busy,
364
- draft: input,
365
227
  },
366
228
  actions,
367
229
  });
368
230
  if (res?.message)
369
231
  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
232
  if (res?.quit)
377
233
  exit();
378
234
  }
@@ -390,8 +246,6 @@ export const App = ({ initialPrompt, resume }) => {
390
246
  config.skillsRegistry,
391
247
  config.systemPrompt,
392
248
  config.theme,
393
- busy,
394
- expanded,
395
249
  exit,
396
250
  history,
397
251
  pushSystem,
@@ -402,16 +256,8 @@ export const App = ({ initialPrompt, resume }) => {
402
256
  ]);
403
257
  const handleSubmit = useCallback(async (raw) => {
404
258
  const text = raw.trim();
405
- if (!text)
259
+ if (!text || busy)
406
260
  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
261
  setInput("");
416
262
  if (text.startsWith("/")) {
417
263
  await runSlash(text);
@@ -428,185 +274,110 @@ export const App = ({ initialPrompt, resume }) => {
428
274
  return;
429
275
  }
430
276
  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]}.`);
277
+ pushSystem(`Cannot send: no API key for ${provider.label} · ${config.model}.\n` +
278
+ `Run \`/login ${provider.id} ${config.model} <key>\` for this model, or set ${provider.authEnv[0]} (provider-wide).`);
433
279
  return;
434
280
  }
435
281
  apiKey = lookup.key;
436
282
  }
283
+ const nextHistory = [...history, { role: "user", content: text }];
284
+ setHistory(nextHistory);
437
285
  idRef.current += 1;
438
286
  const bubbleId = idRef.current;
439
287
  setStreamingId(bubbleId);
440
288
  setStreamingText("");
441
- setStreamPhase("thinking");
442
- steerQueueRef.current = [];
443
- lastStreamFlushRef.current = 0;
444
289
  const ctrl = new AbortController();
445
290
  abortRef.current = ctrl;
446
291
  setBusy(true);
447
- let receivedDone = false;
448
292
  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");
293
+ let workingHistory = nextHistory;
294
+ let finalText = "";
295
+ for (let turn = 0; turn < (config.agentMode ? 8 : 1); turn += 1) {
296
+ const skillsBlock = buildSkillsPrompt(config.enabledSkills);
297
+ const pluginsBlock = buildPluginsPrompt(config.enabledPlugins);
298
+ const agentBlock = config.agentMode ? AGENT_SYSTEM_PROMPT : "";
299
+ const fullSystem = [config.systemPrompt, skillsBlock, pluginsBlock, agentBlock]
300
+ .filter((part) => part.trim())
301
+ .join("\n\n");
302
+ let acc = "";
303
+ for await (const chunk of provider.stream({
304
+ model: config.model,
305
+ messages: workingHistory,
306
+ system: fullSystem,
307
+ signal: ctrl.signal,
308
+ }, apiKey)) {
309
+ if (chunk.delta) {
310
+ acc += chunk.delta;
311
+ if (liveStreamingUi)
312
+ setStreamingText(stripToolCalls(acc));
495
313
  }
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;
314
+ if (chunk.done) {
315
+ if (chunk.usage?.inputTokens)
316
+ setTotalIn((n) => n + (chunk.usage.inputTokens ?? 0));
317
+ if (chunk.usage?.outputTokens)
318
+ setTotalOut((n) => n + (chunk.usage.outputTokens ?? 0));
319
+ if (chunk.usage?.costUsd)
320
+ setTotalCost((n) => n + (chunk.usage.costUsd ?? 0));
549
321
  }
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
322
  }
323
+ const visibleText = stripToolCalls(acc);
324
+ const calls = config.agentMode ? parseToolCalls(acc) : [];
325
+ if (calls.length === 0) {
326
+ finalText = visibleText || acc;
327
+ break;
328
+ }
329
+ if (visibleText)
330
+ pushSystem(visibleText);
331
+ const results = [];
332
+ for (const call of calls) {
333
+ setAgentActivity(`Coding: ${call.tool}`);
334
+ pushSystem(`Running tool: \`${call.tool}\`\n\n\`\`\`json\n${JSON.stringify(call, null, 2)}\n\`\`\``);
335
+ const result = await runAgentTool(call, process.cwd());
336
+ results.push({ call, ...result });
337
+ pushSystem(`Tool ${result.ok ? "completed" : "failed"}: \`${call.tool}\`\n\n\`\`\`\n${result.output}\n\`\`\``);
338
+ }
339
+ const toolMessage = formatToolResults(results);
340
+ workingHistory = [
341
+ ...workingHistory,
342
+ { role: "assistant", content: acc },
343
+ { role: "user", content: toolMessage },
344
+ ];
345
+ setHistory(workingHistory);
346
+ setStreamingText("");
347
+ setAgentActivity(null);
348
+ }
349
+ if (!finalText) {
350
+ finalText = "Stopped after running the maximum number of agent tool turns.";
598
351
  }
352
+ setHistory([...workingHistory, { role: "assistant", content: finalText }]);
353
+ idRef.current = Math.max(idRef.current, bubbleId);
354
+ sessionRef.current.append({ type: "message", role: "assistant", content: finalText });
355
+ setTranscript((prev) => [
356
+ ...prev,
357
+ { id: bubbleId, role: "assistant", content: finalText, markdown: true },
358
+ ]);
599
359
  }
600
360
  catch (err) {
601
- if (!receivedDone)
602
- setHistory([...history, { role: "user", content: text }]);
603
- pushSystem(`Error: ${String(err?.message ?? err)}`);
361
+ const message = String(err?.message ?? err);
362
+ if (err?.name === "AbortError") {
363
+ pushSystem("Cancelled.");
364
+ }
365
+ else if (/\b(401|403)\b/.test(message) || /invalid api key|unauthorized|authentication/i.test(message)) {
366
+ pushSystem(`Authentication failed for ${provider.label} · ${config.model}. The key may be wrong or not allowed for this model.\n` +
367
+ `Check with \`/whoami\`, or re-save: \`/login ${provider.id} ${config.model} <key>\`.`);
368
+ }
369
+ else if (/\b(402|429)\b/.test(message) || /quota|insufficient|rate limit|billing|balance/i.test(message)) {
370
+ pushSystem(`Request blocked: ${provider.label} reports no remaining quota / rate limit for ${config.model}.\n` +
371
+ `Top up the account or switch model/provider. (${message.slice(0, 200)})`);
372
+ }
373
+ else {
374
+ pushSystem(`Error: ${message}`);
375
+ }
604
376
  }
605
377
  finally {
606
378
  setStreamingId(null);
607
379
  setStreamingText("");
608
380
  setAgentActivity(null);
609
- setStreamPhase("thinking");
610
381
  setBusy(false);
611
382
  abortRef.current = null;
612
383
  }
@@ -656,9 +427,7 @@ export const App = ({ initialPrompt, resume }) => {
656
427
  return;
657
428
  }
658
429
  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).` +
430
+ pushSystem(`Scope: ${provider.id} · ${value}.\nPaste the API key for this model (input is masked).` +
662
431
  (getLegacy(provider.id) ? `\nOr type \`keep\` to re-bind the stored ${provider.label} key.` : ""));
663
432
  return;
664
433
  }
@@ -687,7 +456,6 @@ export const App = ({ initialPrompt, resume }) => {
687
456
  return () => clearTimeout(t);
688
457
  }, [handleSubmit, initialPrompt]);
689
458
  useEffect(() => () => {
690
- shutdownShells();
691
459
  if (sessionRef.current)
692
460
  sessionRef.current.close();
693
461
  }, []);
@@ -700,9 +468,9 @@ export const App = ({ initialPrompt, resume }) => {
700
468
  setConfig((c) => ({ ...c, agentMode: false }));
701
469
  pushSystem("Trust declined: agent tools are off for this session (chat only).");
702
470
  }
703
- setTrustEpoch((n) => n + 1);
471
+ setTrusted(true);
704
472
  };
705
473
  return _jsx(TrustGate, { theme: theme, cwd: process.cwd(), onDecision: decide });
706
474
  }
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"] })] })] }));
475
+ 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: streamingText, markdown: true, streaming: true }) })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [busy ? (_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { theme: theme, label: agentActivity ?? (streamingId !== null ? "Streaming" : "Thinking"), stable: stableUi && !agentActivity }) })) : null, _jsx(Prompt, { theme: theme, value: input, onChange: setInput, onSubmit: pendingLogin ? handleWizardSubmit : handleSubmit, disabled: busy, stable: stableUi, 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 }), _jsxs(Text, { color: theme.muted, children: [COMMANDS.length, " commands \u00B7 ", config.enabledSkills.length, " skill(s) active"] })] })] }));
708
476
  };