@travisennis/acai 0.0.5 → 0.0.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 (311) hide show
  1. package/README.md +4 -2
  2. package/dist/agent/index.d.ts +119 -0
  3. package/dist/agent/index.d.ts.map +1 -0
  4. package/dist/agent/index.js +406 -0
  5. package/dist/agent/manual-loop.d.ts +41 -0
  6. package/dist/agent/manual-loop.d.ts.map +1 -0
  7. package/dist/agent/manual-loop.js +278 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +27 -33
  11. package/dist/commands/add-directory-command.d.ts +3 -0
  12. package/dist/commands/add-directory-command.d.ts.map +1 -0
  13. package/dist/commands/add-directory-command.js +85 -0
  14. package/dist/commands/application-log-command.d.ts.map +1 -1
  15. package/dist/commands/application-log-command.js +34 -0
  16. package/dist/commands/clear-command.d.ts.map +1 -1
  17. package/dist/commands/clear-command.js +8 -0
  18. package/dist/commands/compact-command.d.ts.map +1 -1
  19. package/dist/commands/compact-command.js +15 -2
  20. package/dist/commands/context-command.d.ts +3 -0
  21. package/dist/commands/context-command.d.ts.map +1 -0
  22. package/dist/commands/context-command.js +183 -0
  23. package/dist/commands/copy-command.d.ts.map +1 -1
  24. package/dist/commands/copy-command.js +28 -0
  25. package/dist/commands/edit-command.d.ts.map +1 -1
  26. package/dist/commands/edit-command.js +33 -0
  27. package/dist/commands/edit-prompt-command.d.ts.map +1 -1
  28. package/dist/commands/edit-prompt-command.js +28 -0
  29. package/dist/commands/exit-command.d.ts.map +1 -1
  30. package/dist/commands/exit-command.js +20 -0
  31. package/dist/commands/files-command.d.ts.map +1 -1
  32. package/dist/commands/files-command.js +57 -0
  33. package/dist/commands/generate-rules-command.d.ts.map +1 -1
  34. package/dist/commands/generate-rules-command.js +311 -1
  35. package/dist/commands/handoff-command.d.ts +3 -0
  36. package/dist/commands/handoff-command.d.ts.map +1 -0
  37. package/dist/commands/handoff-command.js +202 -0
  38. package/dist/commands/health-command.d.ts.map +1 -1
  39. package/dist/commands/health-command.js +119 -2
  40. package/dist/commands/help-command.d.ts.map +1 -1
  41. package/dist/commands/help-command.js +28 -0
  42. package/dist/commands/history-command.d.ts +3 -0
  43. package/dist/commands/history-command.d.ts.map +1 -0
  44. package/dist/commands/history-command.js +534 -0
  45. package/dist/commands/init-command.d.ts +1 -1
  46. package/dist/commands/init-command.d.ts.map +1 -1
  47. package/dist/commands/init-command.js +55 -18
  48. package/dist/commands/last-log-command.d.ts.map +1 -1
  49. package/dist/commands/last-log-command.js +27 -0
  50. package/dist/commands/list-directories-command.d.ts +3 -0
  51. package/dist/commands/list-directories-command.d.ts.map +1 -0
  52. package/dist/commands/list-directories-command.js +48 -0
  53. package/dist/commands/list-tools-command.d.ts.map +1 -1
  54. package/dist/commands/list-tools-command.js +66 -3
  55. package/dist/commands/manager.d.ts +15 -3
  56. package/dist/commands/manager.d.ts.map +1 -1
  57. package/dist/commands/manager.js +86 -26
  58. package/dist/commands/model-command.d.ts +22 -0
  59. package/dist/commands/model-command.d.ts.map +1 -1
  60. package/dist/commands/model-command.js +256 -0
  61. package/dist/commands/paste-command.d.ts.map +1 -1
  62. package/dist/commands/paste-command.js +92 -0
  63. package/dist/commands/pickup-command.d.ts +3 -0
  64. package/dist/commands/pickup-command.d.ts.map +1 -0
  65. package/dist/commands/pickup-command.js +161 -0
  66. package/dist/commands/prompt-command.d.ts +1 -1
  67. package/dist/commands/prompt-command.d.ts.map +1 -1
  68. package/dist/commands/prompt-command.js +117 -2
  69. package/dist/commands/remove-directory-command.d.ts +3 -0
  70. package/dist/commands/remove-directory-command.d.ts.map +1 -0
  71. package/dist/commands/remove-directory-command.js +87 -0
  72. package/dist/commands/reset-command.d.ts +1 -1
  73. package/dist/commands/reset-command.d.ts.map +1 -1
  74. package/dist/commands/reset-command.js +13 -2
  75. package/dist/commands/rules-command.d.ts.map +1 -1
  76. package/dist/commands/rules-command.js +65 -0
  77. package/dist/commands/save-command.d.ts.map +1 -1
  78. package/dist/commands/save-command.js +12 -0
  79. package/dist/commands/shell-command.d.ts.map +1 -1
  80. package/dist/commands/shell-command.js +68 -0
  81. package/dist/commands/types.d.ts +9 -4
  82. package/dist/commands/types.d.ts.map +1 -1
  83. package/dist/commands/usage-command.d.ts.map +1 -1
  84. package/dist/commands/usage-command.js +22 -0
  85. package/dist/config.d.ts +6 -7
  86. package/dist/config.d.ts.map +1 -1
  87. package/dist/config.js +23 -29
  88. package/dist/formatting.d.ts +108 -0
  89. package/dist/formatting.d.ts.map +1 -1
  90. package/dist/formatting.js +147 -0
  91. package/dist/index.d.ts +7 -2
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +140 -38
  94. package/dist/logger.d.ts.map +1 -1
  95. package/dist/logger.js +47 -18
  96. package/dist/mentions.d.ts +2 -1
  97. package/dist/mentions.d.ts.map +1 -1
  98. package/dist/mentions.js +16 -1
  99. package/dist/messages.d.ts +8 -0
  100. package/dist/messages.d.ts.map +1 -1
  101. package/dist/messages.js +56 -19
  102. package/dist/middleware/cache.d.ts +3 -0
  103. package/dist/middleware/cache.d.ts.map +1 -0
  104. package/dist/middleware/cache.js +53 -0
  105. package/dist/middleware/index.d.ts +1 -0
  106. package/dist/middleware/index.d.ts.map +1 -1
  107. package/dist/middleware/index.js +1 -0
  108. package/dist/models/ai-config.d.ts +4 -2
  109. package/dist/models/ai-config.d.ts.map +1 -1
  110. package/dist/models/ai-config.js +12 -2
  111. package/dist/models/anthropic-provider.d.ts.map +1 -1
  112. package/dist/models/anthropic-provider.js +3 -60
  113. package/dist/models/manager.d.ts +2 -1
  114. package/dist/models/manager.d.ts.map +1 -1
  115. package/dist/models/manager.js +26 -2
  116. package/dist/models/openrouter-provider.d.ts +7 -14
  117. package/dist/models/openrouter-provider.d.ts.map +1 -1
  118. package/dist/models/openrouter-provider.js +114 -169
  119. package/dist/models/providers.d.ts +1 -1
  120. package/dist/models/providers.d.ts.map +1 -1
  121. package/dist/prompts.d.ts +1 -0
  122. package/dist/prompts.d.ts.map +1 -1
  123. package/dist/prompts.js +53 -4
  124. package/dist/repl/display-tool-messages.d.ts +1 -1
  125. package/dist/repl/display-tool-messages.d.ts.map +1 -1
  126. package/dist/repl/display-tool-messages.js +47 -44
  127. package/dist/repl/get-prompt-header.d.ts.map +1 -1
  128. package/dist/repl/get-prompt-header.js +1 -30
  129. package/dist/repl/project-status-line.d.ts +2 -0
  130. package/dist/repl/project-status-line.d.ts.map +1 -0
  131. package/dist/repl/project-status-line.js +31 -0
  132. package/dist/repl/prompt.d.ts +21 -0
  133. package/dist/repl/prompt.d.ts.map +1 -0
  134. package/dist/{repl-prompt.js → repl/prompt.js} +119 -22
  135. package/dist/repl/tool-call-repair.d.ts.map +1 -1
  136. package/dist/repl/tool-call-repair.js +8 -4
  137. package/dist/repl-new.d.ts +53 -0
  138. package/dist/repl-new.d.ts.map +1 -0
  139. package/dist/repl-new.js +374 -0
  140. package/dist/repl.d.ts +3 -5
  141. package/dist/repl.d.ts.map +1 -1
  142. package/dist/repl.js +74 -166
  143. package/dist/terminal/checkbox-prompt.d.ts.map +1 -1
  144. package/dist/terminal/checkbox-prompt.js +10 -4
  145. package/dist/terminal/index.d.ts +7 -0
  146. package/dist/terminal/index.d.ts.map +1 -1
  147. package/dist/terminal/index.js +94 -0
  148. package/dist/terminal/input-prompt.d.ts +2 -1
  149. package/dist/terminal/input-prompt.d.ts.map +1 -1
  150. package/dist/terminal/markdown.js +3 -0
  151. package/dist/terminal/search-prompt.d.ts.map +1 -1
  152. package/dist/terminal/search-prompt.js +11 -10
  153. package/dist/terminal/select-prompt.d.ts +2 -2
  154. package/dist/terminal/select-prompt.d.ts.map +1 -1
  155. package/dist/terminal/select-prompt.js +47 -39
  156. package/dist/tokens/threshold.d.ts +35 -0
  157. package/dist/tokens/threshold.d.ts.map +1 -0
  158. package/dist/tokens/threshold.js +85 -0
  159. package/dist/tools/advanced-edit-file.d.ts +69 -0
  160. package/dist/tools/advanced-edit-file.d.ts.map +1 -0
  161. package/dist/tools/advanced-edit-file.js +281 -0
  162. package/dist/tools/agent.d.ts +16 -5
  163. package/dist/tools/agent.d.ts.map +1 -1
  164. package/dist/tools/agent.js +71 -58
  165. package/dist/tools/bash-utils.d.ts +1 -1
  166. package/dist/tools/bash-utils.d.ts.map +1 -1
  167. package/dist/tools/bash-utils.js +14 -6
  168. package/dist/tools/bash.d.ts +21 -12
  169. package/dist/tools/bash.d.ts.map +1 -1
  170. package/dist/tools/bash.js +88 -135
  171. package/dist/tools/code-interpreter.d.ts +21 -9
  172. package/dist/tools/code-interpreter.d.ts.map +1 -1
  173. package/dist/tools/code-interpreter.js +138 -137
  174. package/dist/tools/delete-file.d.ts +17 -10
  175. package/dist/tools/delete-file.d.ts.map +1 -1
  176. package/dist/tools/delete-file.js +51 -95
  177. package/dist/tools/directory-tree.d.ts +17 -6
  178. package/dist/tools/directory-tree.d.ts.map +1 -1
  179. package/dist/tools/directory-tree.js +47 -49
  180. package/dist/tools/dynamic-tool-loader.d.ts +18 -8
  181. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  182. package/dist/tools/dynamic-tool-loader.js +121 -129
  183. package/dist/tools/dynamic-tool-parser.d.ts +1 -0
  184. package/dist/tools/dynamic-tool-parser.d.ts.map +1 -1
  185. package/dist/tools/dynamic-tool-parser.js +1 -0
  186. package/dist/tools/edit-file.d.ts +35 -15
  187. package/dist/tools/edit-file.d.ts.map +1 -1
  188. package/dist/tools/edit-file.js +112 -112
  189. package/dist/tools/filesystem-utils.d.ts +2 -1
  190. package/dist/tools/filesystem-utils.d.ts.map +1 -1
  191. package/dist/tools/filesystem-utils.js +31 -17
  192. package/dist/tools/glob.d.ts +36 -0
  193. package/dist/tools/glob.d.ts.map +1 -0
  194. package/dist/tools/glob.js +143 -0
  195. package/dist/tools/grep.d.ts +73 -12
  196. package/dist/tools/grep.d.ts.map +1 -1
  197. package/dist/tools/grep.js +413 -168
  198. package/dist/tools/index.d.ts +204 -124
  199. package/dist/tools/index.d.ts.map +1 -1
  200. package/dist/tools/index.js +242 -135
  201. package/dist/tools/llm-edit-fixer.d.ts +25 -0
  202. package/dist/tools/llm-edit-fixer.d.ts.map +1 -0
  203. package/dist/tools/llm-edit-fixer.js +150 -0
  204. package/dist/tools/move-file.d.ts +19 -7
  205. package/dist/tools/move-file.d.ts.map +1 -1
  206. package/dist/tools/move-file.js +40 -33
  207. package/dist/tools/read-file.d.ts +47 -9
  208. package/dist/tools/read-file.d.ts.map +1 -1
  209. package/dist/tools/read-file.js +74 -69
  210. package/dist/tools/read-multiple-files.d.ts +17 -6
  211. package/dist/tools/read-multiple-files.d.ts.map +1 -1
  212. package/dist/tools/read-multiple-files.js +76 -73
  213. package/dist/tools/save-file.d.ts +45 -12
  214. package/dist/tools/save-file.d.ts.map +1 -1
  215. package/dist/tools/save-file.js +58 -101
  216. package/dist/tools/think.d.ts +15 -7
  217. package/dist/tools/think.d.ts.map +1 -1
  218. package/dist/tools/think.js +30 -22
  219. package/dist/tools/types.d.ts +4 -10
  220. package/dist/tools/types.d.ts.map +1 -1
  221. package/dist/tools/types.js +9 -0
  222. package/dist/tools/utils.d.ts +14 -0
  223. package/dist/tools/utils.d.ts.map +1 -0
  224. package/dist/tools/utils.js +16 -0
  225. package/dist/tools/web-fetch.d.ts +11 -4
  226. package/dist/tools/web-fetch.d.ts.map +1 -1
  227. package/dist/tools/web-fetch.js +39 -38
  228. package/dist/tools/web-search.d.ts +15 -6
  229. package/dist/tools/web-search.d.ts.map +1 -1
  230. package/dist/tools/web-search.js +50 -32
  231. package/dist/tui/autocomplete.d.ts +44 -0
  232. package/dist/tui/autocomplete.d.ts.map +1 -0
  233. package/dist/tui/autocomplete.js +466 -0
  234. package/dist/tui/components/assistant-message.d.ts +18 -0
  235. package/dist/tui/components/assistant-message.d.ts.map +1 -0
  236. package/dist/tui/components/assistant-message.js +29 -0
  237. package/dist/tui/components/editor.d.ts +51 -0
  238. package/dist/tui/components/editor.d.ts.map +1 -0
  239. package/dist/tui/components/editor.js +758 -0
  240. package/dist/tui/components/footer.d.ts +24 -0
  241. package/dist/tui/components/footer.d.ts.map +1 -0
  242. package/dist/tui/components/footer.js +197 -0
  243. package/dist/tui/components/input.d.ts +14 -0
  244. package/dist/tui/components/input.d.ts.map +1 -0
  245. package/dist/tui/components/input.js +122 -0
  246. package/dist/tui/components/loader.d.ts +19 -0
  247. package/dist/tui/components/loader.d.ts.map +1 -0
  248. package/dist/tui/components/loader.js +45 -0
  249. package/dist/tui/components/markdown.d.ts +103 -0
  250. package/dist/tui/components/markdown.d.ts.map +1 -0
  251. package/dist/tui/components/markdown.js +533 -0
  252. package/dist/tui/components/modal.d.ts +40 -0
  253. package/dist/tui/components/modal.d.ts.map +1 -0
  254. package/dist/tui/components/modal.js +292 -0
  255. package/dist/tui/components/prompt-status.d.ts +16 -0
  256. package/dist/tui/components/prompt-status.d.ts.map +1 -0
  257. package/dist/tui/components/prompt-status.js +21 -0
  258. package/dist/tui/components/select-list.d.ts +22 -0
  259. package/dist/tui/components/select-list.d.ts.map +1 -0
  260. package/dist/tui/components/select-list.js +143 -0
  261. package/dist/tui/components/spacer.d.ts +16 -0
  262. package/dist/tui/components/spacer.d.ts.map +1 -0
  263. package/dist/tui/components/spacer.js +27 -0
  264. package/dist/tui/components/text.d.ts +26 -0
  265. package/dist/tui/components/text.d.ts.map +1 -0
  266. package/dist/tui/components/text.js +143 -0
  267. package/dist/tui/components/thinking-block.d.ts +14 -0
  268. package/dist/tui/components/thinking-block.d.ts.map +1 -0
  269. package/dist/tui/components/thinking-block.js +30 -0
  270. package/dist/tui/components/tool-execution.d.ts +17 -0
  271. package/dist/tui/components/tool-execution.d.ts.map +1 -0
  272. package/dist/tui/components/tool-execution.js +153 -0
  273. package/dist/tui/components/user-message.d.ts +9 -0
  274. package/dist/tui/components/user-message.d.ts.map +1 -0
  275. package/dist/tui/components/user-message.js +21 -0
  276. package/dist/tui/components/welcome.d.ts +6 -0
  277. package/dist/tui/components/welcome.d.ts.map +1 -0
  278. package/dist/tui/components/welcome.js +30 -0
  279. package/dist/tui/index.d.ts +14 -0
  280. package/dist/tui/index.d.ts.map +1 -0
  281. package/dist/tui/index.js +18 -0
  282. package/dist/tui/terminal.d.ts +37 -0
  283. package/dist/tui/terminal.d.ts.map +1 -0
  284. package/dist/tui/terminal.js +104 -0
  285. package/dist/tui/tui.d.ts +67 -0
  286. package/dist/tui/tui.d.ts.map +1 -0
  287. package/dist/tui/tui.js +184 -0
  288. package/dist/tui/utils.d.ts +19 -0
  289. package/dist/tui/utils.d.ts.map +1 -0
  290. package/dist/tui/utils.js +31 -0
  291. package/dist/utils/generators.d.ts +3 -0
  292. package/dist/utils/generators.d.ts.map +1 -0
  293. package/dist/utils/generators.js +25 -0
  294. package/dist/utils/iterables.d.ts +2 -0
  295. package/dist/utils/iterables.d.ts.map +1 -0
  296. package/dist/utils/iterables.js +6 -0
  297. package/package.json +16 -16
  298. package/dist/conversation-analyzer.d.ts +0 -11
  299. package/dist/conversation-analyzer.d.ts.map +0 -1
  300. package/dist/conversation-analyzer.js +0 -88
  301. package/dist/repl-prompt.d.ts +0 -15
  302. package/dist/repl-prompt.d.ts.map +0 -1
  303. package/dist/tokens/manage-output.d.ts +0 -34
  304. package/dist/tokens/manage-output.d.ts.map +0 -1
  305. package/dist/tokens/manage-output.js +0 -44
  306. package/dist/tool-executor.d.ts +0 -28
  307. package/dist/tool-executor.d.ts.map +0 -1
  308. package/dist/tool-executor.js +0 -74
  309. package/dist/tools/file-editing-utils.d.ts +0 -2
  310. package/dist/tools/file-editing-utils.d.ts.map +0 -1
  311. package/dist/tools/file-editing-utils.js +0 -135
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { text } from "node:stream/consumers";
3
3
  import { parseArgs } from "node:util";
4
4
  import { asyncTry } from "@travisennis/stdlib/try";
5
5
  import { isDefined } from "@travisennis/stdlib/typeguards";
6
+ import { Agent } from "./agent/index.js";
6
7
  import { Cli } from "./cli.js";
7
8
  import { CommandManager } from "./commands/manager.js";
8
9
  import { config } from "./config.js";
@@ -11,40 +12,53 @@ import { MessageHistory } from "./messages.js";
11
12
  import { ModelManager } from "./models/manager.js";
12
13
  import { isSupportedModel } from "./models/providers.js";
13
14
  import { PromptManager } from "./prompts/manager.js";
15
+ import { systemPrompt } from "./prompts.js";
14
16
  import { Repl } from "./repl.js";
17
+ import { NewRepl } from "./repl-new.js";
15
18
  import { initTerminal } from "./terminal/index.js";
16
19
  import { select } from "./terminal/select-prompt.js";
17
20
  import { TokenCounter } from "./tokens/counter.js";
18
21
  import { TokenTracker } from "./tokens/tracker.js";
19
- import { ToolExecutor } from "./tool-executor.js";
22
+ import { initAgents, initTools } from "./tools/index.js";
20
23
  import { getPackageVersion } from "./version.js";
24
+ // Create workspace context from CLI arguments
25
+ export function createWorkspaceContext(addDirArgs = []) {
26
+ const primaryDir = process.cwd();
27
+ const allowedDirs = [primaryDir, ...addDirArgs];
28
+ // Remove duplicates while preserving order
29
+ const uniqueDirs = allowedDirs.filter((dir, index, array) => array.indexOf(dir) === index);
30
+ return {
31
+ primaryDir,
32
+ allowedDirs: uniqueDirs,
33
+ };
34
+ }
21
35
  const helpText = `
22
36
  Usage
23
37
  $ acai <input>
24
38
 
25
39
  Options
26
40
  --model, -m Sets the model to use
27
- --prompt, -p Sets the prompt
28
- --oneshot, -o Run once and exit
41
+ --prompt, -p Sets the prompt (runs in CLI mode)
29
42
  --continue Load the most recent conversation
30
43
  --resume Select a recent conversation to resume
31
- --autoAcceptAll Accept all commands and edits without prompting
44
+ --add-dir Add additional working directory (can be used multiple times)
45
+ --new-repl
32
46
  --help, -h Show help
33
47
  --version, -v Show version
34
48
 
35
49
  Examples
36
50
  $ acai --model anthopric:sonnet
37
51
  $ acai -p "initial prompt"
38
- $ acai -p "one-shot prompt" -o
52
+ $ acai --add-dir /path/to/project1 --add-dir /path/to/project2
39
53
  `;
40
54
  const parsed = parseArgs({
41
55
  options: {
42
56
  model: { type: "string", short: "m" },
43
57
  prompt: { type: "string", short: "p" },
44
- oneshot: { type: "boolean", short: "o", default: false },
45
58
  continue: { type: "boolean", default: false },
46
59
  resume: { type: "boolean", default: false },
47
- autoAcceptAll: { type: "boolean", default: false },
60
+ "add-dir": { type: "string", multiple: true },
61
+ "new-repl": { type: "boolean" },
48
62
  help: { type: "boolean", short: "h" },
49
63
  version: { type: "boolean", short: "v" },
50
64
  },
@@ -52,6 +66,8 @@ const parsed = parseArgs({
52
66
  });
53
67
  const flags = parsed.values;
54
68
  const input = parsed.positionals;
69
+ // Create workspace context from CLI arguments
70
+ const workspace = createWorkspaceContext(flags["add-dir"]);
55
71
  /**
56
72
  * Global error handler function.
57
73
  * @param {Error} error - The error to be handled.
@@ -78,10 +94,6 @@ async function main() {
78
94
  process.exit(1);
79
95
  }
80
96
  const hasContinueOrResume = flags.continue === true || flags.resume === true;
81
- if (hasContinueOrResume && flags.oneshot === true) {
82
- console.error("Cannot use --continue or --resume with --oneshot.");
83
- process.exit(1);
84
- }
85
97
  // --- Determine Initial Prompt (potential conflict) ---
86
98
  const positionalPrompt = input.at(0);
87
99
  let stdInPrompt;
@@ -105,10 +117,10 @@ async function main() {
105
117
  process.exit(1);
106
118
  }
107
119
  const terminal = initTerminal();
108
- terminal.setTitle(`acai: ${process.cwd()}`);
120
+ terminal.setTitle(`acai: ${workspace.primaryDir}`);
109
121
  const chosenModel = isSupportedModel(flags.model)
110
122
  ? flags.model
111
- : "openrouter:glm-4.5";
123
+ : "openrouter:glm-4.6";
112
124
  const modelManager = new ModelManager({
113
125
  stateDir: await appDir.ensurePath("audit"),
114
126
  });
@@ -119,7 +131,9 @@ async function main() {
119
131
  modelManager.setModel("tool-repair", "openai:gpt-4.1");
120
132
  modelManager.setModel("conversation-analyzer", "openrouter:gemini-flash25");
121
133
  modelManager.setModel("init-project", chosenModel);
122
- modelManager.setModel("task-agent", "openrouter:gemini-flash25");
134
+ modelManager.setModel("task-agent", "openrouter:gpt-5-mini");
135
+ modelManager.setModel("handoff-agent", chosenModel);
136
+ modelManager.setModel("edit-fix", "openrouter:gemini-flash25");
123
137
  const tokenTracker = new TokenTracker();
124
138
  const tokenCounter = new TokenCounter();
125
139
  const messageHistory = new MessageHistory({
@@ -144,31 +158,44 @@ async function main() {
144
158
  else if (flags.resume === true) {
145
159
  const histories = await MessageHistory.load(messageHistoryDir, 10);
146
160
  if (histories.length > 0) {
147
- const choice = await select({
148
- message: "Select a conversation to resume:",
149
- choices: histories.map((h, index) => ({
150
- name: `${index + 1}: ${h.title} (${h.updatedAt.toLocaleString()})`,
151
- value: index,
152
- description: `${h.messages.length} messages`,
153
- })),
154
- });
155
- const selectedHistory = histories.at(choice);
156
- if (selectedHistory) {
157
- messageHistory.restore(selectedHistory);
158
- logger.info(`Resuming conversation: ${selectedHistory.title}`);
159
- // Set terminal title after restoring
160
- terminal.setTitle(selectedHistory.title || `acai: ${process.cwd()}`);
161
+ try {
162
+ const choice = await select({
163
+ message: "Select a conversation to resume:",
164
+ choices: histories.map((h, index) => ({
165
+ name: `${index + 1}: ${h.title} (${h.updatedAt.toLocaleString()})`,
166
+ value: index,
167
+ description: `${h.messages.length} messages`,
168
+ })),
169
+ });
170
+ const selectedHistory = histories.at(choice);
171
+ if (selectedHistory) {
172
+ messageHistory.restore(selectedHistory);
173
+ logger.info(`Resuming conversation: ${selectedHistory.title}`);
174
+ // Set terminal title after restoring
175
+ terminal.setTitle(selectedHistory.title || `acai: ${process.cwd()}`);
176
+ }
177
+ else {
178
+ // This case should theoretically not happen if choice is valid
179
+ logger.error("Selected history index out of bounds.");
180
+ }
161
181
  }
162
- else {
163
- // This case should theoretically not happen if choice is valid
164
- logger.error("Selected history index out of bounds.");
182
+ catch (error) {
183
+ // Handle Ctrl-C cancellation
184
+ if (error instanceof Error &&
185
+ "isCanceled" in error &&
186
+ error.isCanceled === true) {
187
+ logger.info("Resume selection cancelled.");
188
+ }
189
+ else {
190
+ // Re-throw other errors
191
+ throw error;
192
+ }
165
193
  }
166
194
  }
167
195
  else {
168
196
  logger.info("No previous conversations found to resume.");
169
197
  }
170
198
  }
171
- const toolExecutor = new ToolExecutor(flags.autoAcceptAll === true);
172
199
  // --- Setup Prompt Manager (only if not continuing/resuming) ---
173
200
  const promptManager = new PromptManager(tokenCounter);
174
201
  if (!hasContinueOrResume && isDefined(initialPromptInput)) {
@@ -177,7 +204,6 @@ async function main() {
177
204
  if (stdInPrompt) {
178
205
  promptManager.addContext(stdInPrompt);
179
206
  }
180
- const toolEvents = new Map();
181
207
  const promptHistory = [];
182
208
  const commands = new CommandManager({
183
209
  promptManager,
@@ -187,12 +213,11 @@ async function main() {
187
213
  tokenTracker,
188
214
  config,
189
215
  tokenCounter,
190
- toolExecutor,
191
- toolEvents,
192
216
  promptHistory,
217
+ workspace,
193
218
  });
194
219
  await commands.initializeCommmands();
195
- if (flags.oneshot === true) {
220
+ if (isDefined(initialPromptInput)) {
196
221
  const cliProcess = new Cli({
197
222
  promptManager,
198
223
  config: appConfig,
@@ -200,9 +225,87 @@ async function main() {
200
225
  modelManager,
201
226
  tokenTracker,
202
227
  tokenCounter,
228
+ workspace,
203
229
  });
204
230
  return (await asyncTry(cliProcess.run())).recover(handleError);
205
231
  }
232
+ const agent = new Agent({
233
+ messageHistory,
234
+ modelManager,
235
+ tokenTracker,
236
+ });
237
+ if (flags["new-repl"]) {
238
+ const repl = new NewRepl({
239
+ agent,
240
+ promptManager,
241
+ terminal,
242
+ config: appConfig,
243
+ messageHistory,
244
+ modelManager,
245
+ tokenTracker,
246
+ commands,
247
+ tokenCounter,
248
+ promptHistory,
249
+ workspace,
250
+ });
251
+ // Initialize TUI
252
+ await repl.init();
253
+ messageHistory.on("clear-history", () => {
254
+ logger.info("Resetting agent state.");
255
+ agent.resetState();
256
+ repl.rerender();
257
+ });
258
+ // Set interrupt callback
259
+ repl.setInterruptCallback(() => {
260
+ agent.abort();
261
+ });
262
+ // Render any existing messages (from --continue mode)
263
+ // repl.renderInitialMessages(agent.state);
264
+ // Initialize tools once outside the loop - all models support tool calling
265
+ const coreTools = await initTools({
266
+ tokenCounter,
267
+ workspace,
268
+ modelManager,
269
+ tokenTracker,
270
+ });
271
+ const agentTools = await initAgents({
272
+ terminal,
273
+ modelManager,
274
+ tokenTracker,
275
+ tokenCounter,
276
+ workspace,
277
+ });
278
+ const completeToolDefs = {
279
+ ...coreTools.toolDefs,
280
+ ...agentTools.toolDefs,
281
+ };
282
+ const tools = {
283
+ toolDefs: completeToolDefs,
284
+ executors: new Map([...coreTools.executors, ...agentTools.executors]),
285
+ };
286
+ // Interactive loop
287
+ while (true) {
288
+ const userInput = await repl.getUserInput();
289
+ // Process the message - agent.prompt will add user message and trigger state updates
290
+ try {
291
+ const results = agent.run({
292
+ systemPrompt: await systemPrompt(),
293
+ input: userInput,
294
+ toolDefs: tools.toolDefs,
295
+ executors: tools.executors,
296
+ abortSignal: agent.abortSignal,
297
+ });
298
+ for await (const result of results) {
299
+ repl.handle(result, agent.state);
300
+ }
301
+ messageHistory.save();
302
+ }
303
+ catch (_error) {
304
+ // Display error in the TUI by adding an error message to the chat
305
+ // repl.showError((error as Error).message || "Unknown error occurred");
306
+ }
307
+ }
308
+ }
206
309
  const repl = new Repl({
207
310
  promptManager,
208
311
  terminal,
@@ -212,9 +315,8 @@ async function main() {
212
315
  tokenTracker,
213
316
  commands,
214
317
  tokenCounter,
215
- toolEvents,
216
- toolExecutor,
217
318
  promptHistory,
319
+ workspace,
218
320
  showLastMessage: hasContinueOrResume
219
321
  ? !!(messageHistory.get() && messageHistory.get().length > 0)
220
322
  : false,
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../source/logger.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AAgBxB,eAAO,MAAM,MAAM,6BAWlB,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../source/logger.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AAwDxB,eAAO,MAAM,MAAM,6BAIjB,CAAC"}
package/dist/logger.js CHANGED
@@ -1,24 +1,53 @@
1
1
  import { join } from "node:path";
2
2
  import pino from "pino";
3
3
  import { config } from "./config.js";
4
- const transport = pino.transport({
5
- target: "pino-roll",
6
- options: {
7
- file: join(config.app.ensurePathSync("logs"), "acai.log"),
8
- size: "10m",
9
- symlink: true,
10
- limit: {
11
- count: 3,
4
+ // Create a lazy logger factory that only initializes when first used
5
+ let loggerInstance = null;
6
+ function createLogger() {
7
+ // Check if we're running in code interpreter context
8
+ // Try multiple detection methods:
9
+ // 1. Environment variable (primary method)
10
+ // 2. Check if we're running with Node.js permissions (fallback)
11
+ const isCodeInterpreter = process.env["ACAI_CODE_INTERPRETER"] === "true" ||
12
+ (typeof process.permission !== "undefined" &&
13
+ process.permission.has !== undefined);
14
+ if (isCodeInterpreter) {
15
+ // In code interpreter context, use a no-op logger to avoid noise in script output
16
+ return pino({
17
+ level: "silent", // Completely disable logging
18
+ enabled: false, // Disable the logger entirely
19
+ });
20
+ }
21
+ // Normal file-based logging
22
+ return pino({
23
+ level: process.env["LOG_LEVEL"] ?? "debug",
24
+ formatters: {
25
+ level: (label) => {
26
+ return { level: label.toUpperCase() };
27
+ },
12
28
  },
13
- mkdir: true,
14
- },
15
- });
16
- export const logger = pino({
17
- level: process.env["LOG_LEVEL"] ?? "debug",
18
- formatters: {
19
- level: (label) => {
20
- return { level: label.toUpperCase() };
29
+ timestamp: pino.stdTimeFunctions.isoTime,
30
+ }, pino.transport({
31
+ target: "pino-roll",
32
+ options: {
33
+ file: join(config.app.ensurePathSync("logs"), "acai.log"),
34
+ size: "10m",
35
+ symlink: true,
36
+ limit: {
37
+ count: 3,
38
+ },
39
+ mkdir: true,
21
40
  },
41
+ }));
42
+ }
43
+ function getLogger() {
44
+ if (!loggerInstance) {
45
+ loggerInstance = createLogger();
46
+ }
47
+ return loggerInstance;
48
+ }
49
+ export const logger = new Proxy({}, {
50
+ get(_target, prop) {
51
+ return getLogger()[prop];
22
52
  },
23
- timestamp: pino.stdTimeFunctions.isoTime,
24
- }, transport);
53
+ });
@@ -3,9 +3,10 @@ import type { ContextItem } from "./prompts/manager.ts";
3
3
  export declare class PromptError extends Error {
4
4
  constructor(message: string, cause?: Error);
5
5
  }
6
- export declare function processPrompt(message: string, { baseDir, model }: {
6
+ export declare function processPrompt(message: string, { baseDir, model, pasteStore, }: {
7
7
  baseDir: string;
8
8
  model: ModelMetadata;
9
+ pasteStore?: Map<number, string>;
9
10
  }): Promise<{
10
11
  message: string;
11
12
  context: ContextItem[];
@@ -1 +1 @@
1
- {"version":3,"file":"mentions.d.ts","sourceRoot":"","sources":["../source/mentions.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAsBxD,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;CAI3C;AA+JD,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,GAC5D,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC,CA0FtD"}
1
+ {"version":3,"file":"mentions.d.ts","sourceRoot":"","sources":["../source/mentions.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAsBxD,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;CAI3C;AA+JD,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,EACE,OAAO,EACP,KAAK,EACL,UAAU,GACX,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,GACA,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC,CA8GtD"}
package/dist/mentions.js CHANGED
@@ -137,7 +137,7 @@ async function processUrlCommand(context) {
137
137
  };
138
138
  }
139
139
  }
140
- export async function processPrompt(message, { baseDir, model }) {
140
+ export async function processPrompt(message, { baseDir, model, pasteStore, }) {
141
141
  const fileRegex = /@([^\s@]+(?:\.[\w\d]+))/g;
142
142
  const urlRegex = /@(https?:\/\/[^\s]+)/g;
143
143
  const shellRegex = /!`([^`]+)`/g;
@@ -171,6 +171,21 @@ export async function processPrompt(message, { baseDir, model }) {
171
171
  }
172
172
  }
173
173
  let processedMessage = message;
174
+ // Process paste placeholders
175
+ if (pasteStore && pasteStore.size > 0) {
176
+ const pasteRegex = /\[Paste #(\d+), (\d+) characters\]/g;
177
+ let match = pasteRegex.exec(processedMessage);
178
+ while (match !== null) {
179
+ const pasteId = Number.parseInt(match[1], 10);
180
+ const pasteContent = pasteStore.get(pasteId);
181
+ if (pasteContent) {
182
+ processedMessage = processedMessage.replace(match[0], pasteContent);
183
+ // Reset regex lastIndex since we modified the string
184
+ pasteRegex.lastIndex = 0;
185
+ }
186
+ match = pasteRegex.exec(processedMessage);
187
+ }
188
+ }
174
189
  // Process shell commands
175
190
  for (const match of shellMatches) {
176
191
  const command = match[1];
@@ -11,6 +11,9 @@ It can be either an assistant message or a tool message.
11
11
  */
12
12
  type ResponseMessage = AssistantModelMessage | ToolModelMessage;
13
13
  type SavedMessageHistory = {
14
+ project: string;
15
+ sessionId: string;
16
+ modelId: string;
14
17
  title: string;
15
18
  createdAt: Date;
16
19
  updatedAt: Date;
@@ -22,6 +25,8 @@ interface MessageHistoryEvents {
22
25
  }
23
26
  export declare class MessageHistory extends EventEmitter<MessageHistoryEvents> {
24
27
  private history;
28
+ private sessionId;
29
+ private modelId;
25
30
  private title;
26
31
  private createdAt;
27
32
  private updatedAt;
@@ -33,6 +38,7 @@ export declare class MessageHistory extends EventEmitter<MessageHistoryEvents> {
33
38
  modelManager: ModelManager;
34
39
  tokenTracker: TokenTracker;
35
40
  });
41
+ create(modelId: string): void;
36
42
  private validMessage;
37
43
  get(): ModelMessage[];
38
44
  clear(): void;
@@ -40,11 +46,13 @@ export declare class MessageHistory extends EventEmitter<MessageHistoryEvents> {
40
46
  appendUserMessage(msg: UserModelMessage): void;
41
47
  appendAssistantMessage(msg: string): void;
42
48
  appendAssistantMessage(msg: AssistantModelMessage): void;
49
+ appendToolMessages(toolResultMessages: ToolModelMessage[]): void;
43
50
  appendResponseMessages(responseMessages: ResponseMessage[]): void;
44
51
  isEmpty(): boolean;
45
52
  save(): Promise<void>;
46
53
  private generateTitle;
47
54
  getFirstUserMessage(): UserModelMessage | undefined;
55
+ getLastUserMessage(): UserModelMessage | undefined;
48
56
  /**
49
57
  * Extracts the last message from the conversation history for display purposes.
50
58
  * Prioritizes assistant messages, falls back to user messages if no assistant messages exist.
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../source/messages.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAC;AAIvC,OAAO,EACL,KAAK,qBAAqB,EAE1B,KAAK,SAAS,EAAE,kBAAkB;AAClC,KAAK,YAAY,EAEjB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,IAAI,CAAC;AACZ,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,SAAS,CAAC;AAExD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,sBAAsB,EAAE,EACtC,MAAM,CAAC,EAAE,MAAM,GACd,gBAAgB,CAuBlB;AAcD;;;GAGG;AACH,KAAK,eAAe,GAAG,qBAAqB,GAAG,gBAAgB,CAAC;AAEhE,KAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B,CAAC;AAUF,UAAU,oBAAoB;IAC5B,cAAc,EAAE,CAAC,MAAM,CAAC,CAAC;IACzB,eAAe,EAAE,EAAE,CAAC;CACrB;AAED,qBAAa,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IACpE,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAe;gBAEvB,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,GACb,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,YAAY,CAAC;QAC3B,YAAY,EAAE,YAAY,CAAC;KAC5B;IAWD,OAAO,CAAC,YAAY;IAoBpB,GAAG;IAIH,KAAK;IAKL,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IACpC,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI;IAmB9C,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IACzC,sBAAsB,CAAC,GAAG,EAAE,qBAAqB,GAAG,IAAI;IAOxD,sBAAsB,CAAC,gBAAgB,EAAE,eAAe,EAAE;IAO1D,OAAO;IAID,IAAI;YAgBI,aAAa;IA4B3B,mBAAmB,IAAI,gBAAgB,GAAG,SAAS;IAOnD;;;OAGG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;WA2ClB,IAAI,CACf,QAAQ,EAAE,MAAM,EAChB,KAAK,SAAK,GACT,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAqDjC,OAAO,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI;CAajD"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../source/messages.ts"],"names":[],"mappings":"AACA,OAAO,YAAY,MAAM,aAAa,CAAC;AAIvC,OAAO,EACL,KAAK,qBAAqB,EAE1B,KAAK,SAAS,EAAE,kBAAkB;AAClC,KAAK,YAAY,EAEjB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,IAAI,CAAC;AACZ,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,SAAS,CAAC;AAExD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,sBAAsB,EAAE,EACtC,MAAM,CAAC,EAAE,MAAM,GACd,gBAAgB,CAuBlB;AAcD;;;GAGG;AACH,KAAK,eAAe,GAAG,qBAAqB,GAAG,gBAAgB,CAAC;AAEhE,KAAK,mBAAmB,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B,CAAC;AAUF,UAAU,oBAAoB;IAC5B,cAAc,EAAE,CAAC,MAAM,CAAC,CAAC;IACzB,eAAe,EAAE,EAAE,CAAC;CACrB;AAED,qBAAa,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IACpE,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAe;gBAEvB,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,GACb,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,YAAY,CAAC;QAC3B,YAAY,EAAE,YAAY,CAAC;KAC5B;IAaD,MAAM,CAAC,OAAO,EAAE,MAAM;IAStB,OAAO,CAAC,YAAY;IAoBpB,GAAG;IAIH,KAAK;IAKL,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IACpC,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI;IAmB9C,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IACzC,sBAAsB,CAAC,GAAG,EAAE,qBAAqB,GAAG,IAAI;IAOxD,kBAAkB,CAAC,kBAAkB,EAAE,gBAAgB,EAAE;IAKzD,sBAAsB,CAAC,gBAAgB,EAAE,eAAe,EAAE;IAO1D,OAAO;IAID,IAAI;YAoBI,aAAa;IA6B3B,mBAAmB,IAAI,gBAAgB,GAAG,SAAS;IAOnD,kBAAkB,IAAI,gBAAgB,GAAG,SAAS;IAOlD;;;OAGG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;WA2ClB,IAAI,CACf,QAAQ,EAAE,MAAM,EAChB,KAAK,SAAK,GACT,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA6EjC,OAAO,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI;CAajD"}
package/dist/messages.js CHANGED
@@ -1,6 +1,7 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import EventEmitter from "node:events";
2
- import { readdir, readFile, writeFile } from "node:fs/promises";
3
- import { join } from "node:path";
3
+ import { readdir, readFile, stat, writeFile } from "node:fs/promises";
4
+ import { basename, join } from "node:path";
4
5
  import { isString } from "@travisennis/stdlib/typeguards";
5
6
  import { generateText, } from "ai";
6
7
  export function createUserMessage(contentItems, prompt) {
@@ -38,6 +39,8 @@ function createAssistantMessage(content) {
38
39
  }
39
40
  export class MessageHistory extends EventEmitter {
40
41
  history;
42
+ sessionId;
43
+ modelId;
41
44
  title;
42
45
  createdAt;
43
46
  updatedAt;
@@ -47,6 +50,8 @@ export class MessageHistory extends EventEmitter {
47
50
  constructor({ stateDir, modelManager, tokenTracker, }) {
48
51
  super();
49
52
  this.history = [];
53
+ this.sessionId = randomUUID();
54
+ this.modelId = "";
50
55
  this.title = "";
51
56
  this.createdAt = new Date();
52
57
  this.updatedAt = new Date();
@@ -54,6 +59,14 @@ export class MessageHistory extends EventEmitter {
54
59
  this.modelManager = modelManager;
55
60
  this.tokenTracker = tokenTracker;
56
61
  }
62
+ create(modelId) {
63
+ this.clear();
64
+ this.modelId = modelId;
65
+ this.sessionId = randomUUID();
66
+ this.title = "";
67
+ this.createdAt = new Date();
68
+ this.updatedAt = new Date();
69
+ }
57
70
  validMessage(msg) {
58
71
  // Filter out messages with empty content arrays
59
72
  if (Array.isArray(msg.content) && msg.content.length === 0) {
@@ -96,6 +109,10 @@ export class MessageHistory extends EventEmitter {
96
109
  const msgObj = isString(msg) ? createAssistantMessage(msg) : msg;
97
110
  this.history.push(msgObj);
98
111
  }
112
+ appendToolMessages(toolResultMessages) {
113
+ this.updatedAt = new Date();
114
+ this.history.push(...toolResultMessages);
115
+ }
99
116
  appendResponseMessages(responseMessages) {
100
117
  this.updatedAt = new Date();
101
118
  // Filter out messages with empty content arrays
@@ -107,10 +124,13 @@ export class MessageHistory extends EventEmitter {
107
124
  }
108
125
  async save() {
109
126
  const msgHistoryDir = this.stateDir;
110
- const timestamp = new Date().toISOString().replace(/:/g, "-");
111
- const fileName = `message-history-${timestamp}.json`;
127
+ const fileName = `message-history-${this.sessionId}.json`;
112
128
  const filePath = join(msgHistoryDir, fileName);
129
+ const project = basename(process.cwd());
113
130
  const output = {
131
+ project,
132
+ sessionId: this.sessionId,
133
+ modelId: this.modelId,
114
134
  title: this.title,
115
135
  createdAt: this.createdAt,
116
136
  updatedAt: this.updatedAt,
@@ -129,6 +149,7 @@ export class MessageHistory extends EventEmitter {
129
149
  const { text, usage } = await generateText({
130
150
  model: this.modelManager.getModel(app),
131
151
  system: systemPrompt,
152
+ maxOutputTokens: 100,
132
153
  prompt: `Request:\n${message}\nTitle:`,
133
154
  });
134
155
  this.tokenTracker.trackUsage(app, usage);
@@ -145,6 +166,10 @@ export class MessageHistory extends EventEmitter {
145
166
  const firstUser = this.get().find((msg) => msg.role === "user");
146
167
  return firstUser;
147
168
  }
169
+ getLastUserMessage() {
170
+ const userMsg = this.history.findLast((value) => value.role === "user");
171
+ return userMsg;
172
+ }
148
173
  /**
149
174
  * Extracts the last message from the conversation history for display purposes.
150
175
  * Prioritizes assistant messages, falls back to user messages if no assistant messages exist.
@@ -180,16 +205,31 @@ export class MessageHistory extends EventEmitter {
180
205
  static async load(stateDir, count = 10) {
181
206
  try {
182
207
  const files = await readdir(stateDir);
183
- const messageHistoryFiles = files
184
- .filter((file) => file.startsWith("message-history-") && file.endsWith(".json"))
185
- .sort((a, b) => {
186
- // Extract timestamps and compare in reverse order (newest first)
187
- const timeA = a.replace("message-history-", "").replace(".json", "");
188
- const timeB = b.replace("message-history-", "").replace(".json", "");
189
- return timeB.localeCompare(timeA); // Newest first
190
- })
191
- .slice(0, count); // Use the count parameter here
192
- const fileReadPromises = messageHistoryFiles.map(async (fileName) => {
208
+ const messageHistoryFiles = files.filter((file) => file.startsWith("message-history-") && file.endsWith(".json"));
209
+ // Get file stats and sort by modification time (newest first)
210
+ const fileStatsPromises = messageHistoryFiles.map(async (fileName) => {
211
+ const filePath = join(stateDir, fileName);
212
+ try {
213
+ const fileStat = await stat(filePath);
214
+ return {
215
+ fileName,
216
+ filePath,
217
+ modifiedTime: fileStat.mtime,
218
+ };
219
+ }
220
+ catch (error) {
221
+ console.error(`Error getting stats for file ${filePath}:`, error);
222
+ return null;
223
+ }
224
+ });
225
+ const fileStats = await Promise.all(fileStatsPromises);
226
+ // Filter out null results and sort by modification time (newest first)
227
+ const sortedFiles = fileStats
228
+ .filter((stat) => stat !== null)
229
+ .sort((a, b) => b.modifiedTime.getTime() - a.modifiedTime.getTime())
230
+ .slice(0, count)
231
+ .map((stat) => stat.fileName);
232
+ const fileReadPromises = sortedFiles.map(async (fileName) => {
193
233
  const filePath = join(stateDir, fileName);
194
234
  try {
195
235
  const content = await readFile(filePath, "utf-8");
@@ -209,11 +249,8 @@ export class MessageHistory extends EventEmitter {
209
249
  return null; // Return null for failed reads/parses
210
250
  });
211
251
  const results = await Promise.all(fileReadPromises);
212
- // Filter out null results (failed reads/parses)
213
- //sort results by result.updatedAt which is a Date
214
- return results
215
- .filter((result) => result !== null)
216
- .sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
252
+ // Filter out null results and return them (already sorted by file modification time)
253
+ return results.filter((result) => result !== null);
217
254
  }
218
255
  catch (error) {
219
256
  // Handle cases where the directory might not exist or other readdir errors
@@ -0,0 +1,3 @@
1
+ import type { LanguageModelV2Middleware } from "@ai-sdk/provider";
2
+ export declare const cacheMiddleware: LanguageModelV2Middleware;
3
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../source/middleware/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,kBAAkB,CAAC;AAsB1B,eAAO,MAAM,eAAe,EAAE,yBAyC7B,CAAC"}