@datename/fastcode-termux 0.1.6 → 0.1.8-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +67 -16
  3. package/dist/agent/approval-json.d.ts +43 -0
  4. package/dist/agent/approval-json.js +69 -0
  5. package/dist/agent/approval-native.d.ts +3 -0
  6. package/dist/agent/approval-native.js +165 -0
  7. package/dist/agent/approval-store.d.ts +16 -0
  8. package/dist/agent/approval-store.js +21 -0
  9. package/dist/agent/approval.d.ts +83 -0
  10. package/dist/agent/approval.js +42 -0
  11. package/dist/agent/compaction.d.ts +41 -0
  12. package/dist/agent/compaction.js +270 -0
  13. package/dist/agent/context.d.ts +29 -0
  14. package/dist/agent/context.js +62 -0
  15. package/dist/agent/guards.d.ts +24 -16
  16. package/dist/agent/guards.js +1167 -18
  17. package/dist/agent/ignore.d.ts +11 -0
  18. package/dist/agent/ignore.js +129 -0
  19. package/dist/agent/policy/defaults.d.ts +16 -0
  20. package/dist/agent/policy/defaults.js +17 -0
  21. package/dist/agent/policy/egress.d.ts +32 -0
  22. package/dist/agent/policy/egress.js +280 -0
  23. package/dist/agent/process-tree.d.ts +22 -0
  24. package/dist/agent/process-tree.js +102 -0
  25. package/dist/agent/protocol-mode.d.ts +6 -0
  26. package/dist/agent/protocol-mode.js +51 -0
  27. package/dist/agent/protocol.d.ts +20 -1
  28. package/dist/agent/protocol.js +169 -39
  29. package/dist/agent/registry.d.ts +19 -0
  30. package/dist/agent/registry.js +247 -0
  31. package/dist/agent/runner.d.ts +127 -0
  32. package/dist/agent/runner.js +391 -0
  33. package/dist/agent/shell-ast.d.ts +11 -0
  34. package/dist/agent/shell-ast.js +204 -0
  35. package/dist/agent/shell-session.d.ts +51 -0
  36. package/dist/agent/shell-session.js +611 -0
  37. package/dist/agent/stream.d.ts +29 -0
  38. package/dist/agent/stream.js +103 -0
  39. package/dist/agent/tool-schemas.d.ts +2 -0
  40. package/dist/agent/tool-schemas.js +82 -0
  41. package/dist/agent/tools/append_file.d.ts +2 -0
  42. package/dist/agent/tools/append_file.js +28 -0
  43. package/dist/agent/tools/apply_patch.d.ts +10 -0
  44. package/dist/agent/tools/apply_patch.js +92 -0
  45. package/dist/agent/tools/copy_file.d.ts +2 -0
  46. package/dist/agent/tools/copy_file.js +53 -0
  47. package/dist/agent/tools/delete_file.d.ts +2 -0
  48. package/dist/agent/tools/delete_file.js +43 -0
  49. package/dist/agent/tools/grep.d.ts +2 -0
  50. package/dist/agent/tools/grep.js +54 -0
  51. package/dist/agent/tools/index.d.ts +2 -0
  52. package/dist/agent/tools/index.js +32 -0
  53. package/dist/agent/tools/list_files.d.ts +2 -0
  54. package/dist/agent/tools/list_files.js +17 -0
  55. package/dist/agent/tools/make_dir.d.ts +2 -0
  56. package/dist/agent/tools/make_dir.js +26 -0
  57. package/dist/agent/tools/move_path.d.ts +9 -0
  58. package/dist/agent/tools/move_path.js +73 -0
  59. package/dist/agent/tools/paths.d.ts +7 -0
  60. package/dist/agent/tools/paths.js +84 -0
  61. package/dist/agent/tools/pwd.d.ts +2 -0
  62. package/dist/agent/tools/pwd.js +10 -0
  63. package/dist/agent/tools/read_file.d.ts +2 -0
  64. package/dist/agent/tools/read_file.js +81 -0
  65. package/dist/agent/tools/read_many.d.ts +2 -0
  66. package/dist/agent/tools/read_many.js +43 -0
  67. package/dist/agent/tools/readers.d.ts +58 -0
  68. package/dist/agent/tools/readers.js +426 -0
  69. package/dist/agent/tools/replace_in_file.d.ts +2 -0
  70. package/dist/agent/tools/replace_in_file.js +63 -0
  71. package/dist/agent/tools/shell.d.ts +2 -0
  72. package/dist/agent/tools/shell.js +48 -0
  73. package/dist/agent/tools/stat_path.d.ts +2 -0
  74. package/dist/agent/tools/stat_path.js +31 -0
  75. package/dist/agent/tools/types.d.ts +65 -0
  76. package/dist/agent/tools/types.js +1 -0
  77. package/dist/agent/tools/validate.d.ts +19 -0
  78. package/dist/agent/tools/validate.js +92 -0
  79. package/dist/agent/tools/write_file.d.ts +2 -0
  80. package/dist/agent/tools/write_file.js +28 -0
  81. package/dist/agent/tools.d.ts +3 -1
  82. package/dist/agent/tools.js +2 -377
  83. package/dist/app.d.ts +2 -1
  84. package/dist/app.js +846 -173
  85. package/dist/cli-main.js +363 -0
  86. package/dist/cli.js +444 -333
  87. package/dist/commands/about.js +1 -1
  88. package/dist/commands/attach.d.ts +9 -0
  89. package/dist/commands/attach.js +15 -0
  90. package/dist/commands/bg.d.ts +7 -0
  91. package/dist/commands/bg.js +9 -0
  92. package/dist/commands/cancel.d.ts +10 -0
  93. package/dist/commands/cancel.js +10 -0
  94. package/dist/commands/clear.d.ts +1 -0
  95. package/dist/commands/clear.js +1 -1
  96. package/dist/commands/compact.d.ts +22 -0
  97. package/dist/commands/compact.js +34 -0
  98. package/dist/commands/context.d.ts +7 -0
  99. package/dist/commands/context.js +25 -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/expand.d.ts +10 -0
  105. package/dist/commands/expand.js +8 -0
  106. package/dist/commands/index.js +24 -0
  107. package/dist/commands/jobs.d.ts +7 -0
  108. package/dist/commands/jobs.js +17 -0
  109. package/dist/commands/kill.d.ts +7 -0
  110. package/dist/commands/kill.js +13 -0
  111. package/dist/commands/login.d.ts +6 -4
  112. package/dist/commands/login.js +24 -22
  113. package/dist/commands/logout.js +8 -16
  114. package/dist/commands/model.d.ts +1 -0
  115. package/dist/commands/model.js +10 -22
  116. package/dist/commands/provider.d.ts +1 -2
  117. package/dist/commands/provider.js +4 -11
  118. package/dist/commands/skills.js +34 -4
  119. package/dist/commands/steer.d.ts +11 -0
  120. package/dist/commands/steer.js +13 -0
  121. package/dist/commands/threads.d.ts +9 -0
  122. package/dist/commands/threads.js +26 -0
  123. package/dist/commands/whoami.js +6 -5
  124. package/dist/config/store.d.ts +53 -12
  125. package/dist/config/store.js +163 -73
  126. package/dist/config/trust.d.ts +30 -8
  127. package/dist/config/trust.js +139 -14
  128. package/dist/headless.d.ts +7 -0
  129. package/dist/headless.js +213 -0
  130. package/dist/index.js +19 -3
  131. package/dist/lib/debug.d.ts +2 -0
  132. package/dist/lib/debug.js +22 -0
  133. package/dist/lib/diagnostics.d.ts +48 -4
  134. package/dist/lib/diagnostics.js +90 -8
  135. package/dist/lib/highlight-subset.d.ts +21 -0
  136. package/dist/lib/highlight-subset.js +88 -0
  137. package/dist/lib/http.d.ts +11 -1
  138. package/dist/lib/http.js +62 -4
  139. package/dist/lib/lifecycle.d.ts +5 -0
  140. package/dist/lib/lifecycle.js +62 -0
  141. package/dist/lib/markdown.js +22 -4
  142. package/dist/lib/model-meta.d.ts +19 -0
  143. package/dist/lib/model-meta.js +113 -0
  144. package/dist/lib/native-tools.d.ts +12 -0
  145. package/dist/lib/native-tools.js +21 -0
  146. package/dist/lib/net.d.ts +33 -0
  147. package/dist/lib/net.js +168 -0
  148. package/dist/patch/apply.d.ts +15 -0
  149. package/dist/patch/apply.js +172 -0
  150. package/dist/patch/parse.d.ts +29 -0
  151. package/dist/patch/parse.js +112 -0
  152. package/dist/patch/plan.d.ts +49 -0
  153. package/dist/patch/plan.js +273 -0
  154. package/dist/providers/anthropic.d.ts +1 -0
  155. package/dist/providers/anthropic.js +12 -0
  156. package/dist/providers/custom.d.ts +9 -0
  157. package/dist/providers/deepseek.d.ts +10 -0
  158. package/dist/providers/deepseek.js +3 -2
  159. package/dist/providers/google.d.ts +9 -0
  160. package/dist/providers/groq.d.ts +10 -0
  161. package/dist/providers/groq.js +2 -1
  162. package/dist/providers/index.d.ts +10 -201
  163. package/dist/providers/index.js +30 -0
  164. package/dist/providers/mistral.d.ts +10 -0
  165. package/dist/providers/mistral.js +2 -1
  166. package/dist/providers/moonshot.d.ts +10 -0
  167. package/dist/providers/moonshot.js +2 -1
  168. package/dist/providers/ollama.d.ts +9 -0
  169. package/dist/providers/openai.d.ts +10 -0
  170. package/dist/providers/openai.js +2 -1
  171. package/dist/providers/openaiCompatible.d.ts +33 -2
  172. package/dist/providers/openaiCompatible.js +78 -16
  173. package/dist/providers/openrouter.d.ts +10 -0
  174. package/dist/providers/openrouter.js +2 -1
  175. package/dist/providers/qwen.d.ts +10 -0
  176. package/dist/providers/qwen.js +2 -1
  177. package/dist/providers/spec.d.ts +12 -0
  178. package/dist/providers/spec.js +29 -0
  179. package/dist/providers/xai.d.ts +10 -0
  180. package/dist/providers/xai.js +2 -1
  181. package/dist/providers/zhipu.d.ts +10 -0
  182. package/dist/providers/zhipu.js +2 -1
  183. package/dist/session/log.d.ts +49 -14
  184. package/dist/session/log.js +236 -11
  185. package/dist/session/projection.d.ts +76 -0
  186. package/dist/session/projection.js +526 -0
  187. package/dist/skills/manager.d.ts +15 -0
  188. package/dist/skills/manager.js +135 -18
  189. package/dist/types.d.ts +75 -1
  190. package/dist/ui/Message.d.ts +1 -7
  191. package/dist/ui/Message.js +13 -2
  192. package/dist/ui/Picker.d.ts +8 -0
  193. package/dist/ui/Picker.js +24 -0
  194. package/dist/ui/Prompt.d.ts +1 -2
  195. package/dist/ui/Prompt.js +2 -2
  196. package/dist/ui/Spinner.js +11 -5
  197. package/dist/ui/StatusBar.d.ts +2 -1
  198. package/dist/ui/StatusBar.js +6 -5
  199. package/package.json +3 -2
  200. package/src/agent/approval-json.ts +103 -0
  201. package/src/agent/approval-native.ts +168 -0
  202. package/src/agent/approval-store.ts +38 -0
  203. package/src/agent/approval.ts +94 -0
  204. package/src/agent/compaction.ts +321 -0
  205. package/src/agent/context.ts +97 -0
  206. package/src/agent/guards.ts +1239 -19
  207. package/src/agent/ignore.ts +147 -0
  208. package/src/agent/policy/defaults.ts +34 -0
  209. package/src/agent/policy/egress.ts +335 -0
  210. package/src/agent/process-tree.ts +114 -0
  211. package/src/agent/protocol-mode.ts +59 -0
  212. package/src/agent/protocol.ts +191 -40
  213. package/src/agent/registry.ts +270 -0
  214. package/src/agent/runner.ts +486 -0
  215. package/src/agent/shell-ast.ts +227 -0
  216. package/src/agent/shell-session.ts +673 -0
  217. package/src/agent/stream.ts +143 -0
  218. package/src/agent/tool-schemas.ts +86 -0
  219. package/src/agent/tools/append_file.ts +30 -0
  220. package/src/agent/tools/apply_patch.ts +95 -0
  221. package/src/agent/tools/copy_file.ts +56 -0
  222. package/src/agent/tools/delete_file.ts +45 -0
  223. package/src/agent/tools/grep.ts +57 -0
  224. package/src/agent/tools/index.ts +34 -0
  225. package/src/agent/tools/list_files.ts +19 -0
  226. package/src/agent/tools/make_dir.ts +28 -0
  227. package/src/agent/tools/move_path.ts +83 -0
  228. package/src/agent/tools/paths.ts +92 -0
  229. package/src/agent/tools/pwd.ts +12 -0
  230. package/src/agent/tools/read_file.ts +84 -0
  231. package/src/agent/tools/read_many.ts +45 -0
  232. package/src/agent/tools/readers.ts +491 -0
  233. package/src/agent/tools/replace_in_file.ts +66 -0
  234. package/src/agent/tools/shell.ts +53 -0
  235. package/src/agent/tools/stat_path.ts +33 -0
  236. package/src/agent/tools/types.ts +84 -0
  237. package/src/agent/tools/validate.ts +104 -0
  238. package/src/agent/tools/write_file.ts +30 -0
  239. package/src/agent/tools.ts +3 -377
  240. package/src/app.ts +853 -170
  241. package/src/{cli.ts → cli-main.ts} +64 -9
  242. package/src/commands/about.ts +1 -1
  243. package/src/commands/attach.ts +16 -0
  244. package/src/commands/bg.ts +9 -0
  245. package/src/commands/cancel.ts +10 -0
  246. package/src/commands/clear.ts +1 -1
  247. package/src/commands/compact.ts +35 -0
  248. package/src/commands/context.ts +26 -0
  249. package/src/commands/edit.ts +37 -0
  250. package/src/commands/effort.ts +49 -0
  251. package/src/commands/expand.ts +8 -0
  252. package/src/commands/index.ts +24 -0
  253. package/src/commands/jobs.ts +18 -0
  254. package/src/commands/kill.ts +14 -0
  255. package/src/commands/login.ts +24 -22
  256. package/src/commands/logout.ts +8 -16
  257. package/src/commands/model.ts +10 -22
  258. package/src/commands/provider.ts +4 -11
  259. package/src/commands/skills.ts +34 -4
  260. package/src/commands/steer.ts +13 -0
  261. package/src/commands/threads.ts +28 -0
  262. package/src/commands/whoami.ts +6 -5
  263. package/src/config/store.ts +168 -77
  264. package/src/config/trust.ts +181 -22
  265. package/src/headless.ts +225 -0
  266. package/src/index.ts +19 -3
  267. package/src/lib/debug.ts +24 -0
  268. package/src/lib/diagnostics.ts +90 -8
  269. package/src/lib/highlight-subset.ts +93 -0
  270. package/src/lib/http.ts +66 -4
  271. package/src/lib/lifecycle.ts +69 -0
  272. package/src/lib/markdown.ts +22 -4
  273. package/src/lib/model-meta.ts +138 -0
  274. package/src/lib/native-tools.ts +32 -0
  275. package/src/lib/net.ts +204 -0
  276. package/src/patch/apply.ts +194 -0
  277. package/src/patch/parse.ts +131 -0
  278. package/src/patch/plan.ts +317 -0
  279. package/src/providers/anthropic.ts +13 -1
  280. package/src/providers/deepseek.ts +3 -2
  281. package/src/providers/groq.ts +2 -1
  282. package/src/providers/index.ts +32 -1
  283. package/src/providers/mistral.ts +2 -1
  284. package/src/providers/moonshot.ts +2 -1
  285. package/src/providers/openai.ts +2 -1
  286. package/src/providers/openaiCompatible.ts +104 -19
  287. package/src/providers/openrouter.ts +2 -1
  288. package/src/providers/qwen.ts +2 -1
  289. package/src/providers/spec.ts +42 -0
  290. package/src/providers/xai.ts +2 -1
  291. package/src/providers/zhipu.ts +2 -1
  292. package/src/session/log.ts +311 -29
  293. package/src/session/projection.ts +621 -0
  294. package/src/skills/manager.ts +140 -18
  295. package/src/types/highlight-shims.d.ts +14 -0
  296. package/src/types.ts +79 -2
  297. package/src/ui/Message.ts +13 -2
  298. package/src/ui/Picker.ts +24 -0
  299. package/src/ui/Prompt.ts +2 -2
  300. package/src/ui/Spinner.ts +11 -5
  301. package/src/ui/StatusBar.ts +6 -5
  302. package/tsconfig.json +1 -0
  303. /package/dist/{cli.d.ts → cli-main.d.ts} +0 -0
@@ -0,0 +1,426 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { open, readdir, stat } from "node:fs/promises";
3
+ import { basename, extname, join, relative } from "node:path";
4
+ import readline from "node:readline";
5
+ import { createIgnore, isBinaryName } from "../ignore.js";
6
+ import { createOutputBuffer } from "../limits.js";
7
+ import { allowedRoots, isInsideAny, realpathOf, sandboxPath } from "./paths.js";
8
+ export async function readManyResult(cwd, paths, maxBytes = readManyMaxBytes()) {
9
+ const blocks = [];
10
+ let used = 0;
11
+ let totalBytes = 0;
12
+ let filesRead = 0;
13
+ let truncated = false;
14
+ for (const path of paths) {
15
+ if (used >= maxBytes) {
16
+ truncated = true;
17
+ break;
18
+ }
19
+ const file = sandboxPath(cwd, path);
20
+ const result = await readTextFileResult(file, maxBytes - used);
21
+ totalBytes += result.totalBytes;
22
+ const block = `### ${file}\n${result.text}`;
23
+ const blockBytes = Buffer.byteLength(block, "utf8");
24
+ if (used + blockBytes > maxBytes) {
25
+ const allowed = Math.max(0, maxBytes - used);
26
+ const clipped = Buffer.from(block, "utf8").subarray(0, allowed).toString("utf8");
27
+ blocks.push(`${clipped}\n[... read_many budget exhausted at ${maxBytes} bytes ...]`);
28
+ used = maxBytes;
29
+ filesRead += 1;
30
+ truncated = true;
31
+ break;
32
+ }
33
+ blocks.push(block);
34
+ used += blockBytes;
35
+ filesRead += 1;
36
+ if (result.truncated)
37
+ truncated = true;
38
+ }
39
+ if (filesRead < paths.length)
40
+ truncated = true;
41
+ return {
42
+ text: blocks.join("\n\n"),
43
+ truncated,
44
+ totalBytes,
45
+ returnedBytes: used,
46
+ filesRead,
47
+ filesSkipped: Math.max(0, paths.length - filesRead),
48
+ };
49
+ }
50
+ export async function readMany(cwd, paths) {
51
+ return (await readManyResult(cwd, paths)).text;
52
+ }
53
+ const CODE_EXT = new Set([
54
+ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java",
55
+ ".rb", ".php", ".cs", ".c", ".h", ".cpp", ".hpp", ".kt", ".swift", ".scala",
56
+ ".sh", ".lua", ".ex", ".exs", ".dart", ".vue", ".svelte",
57
+ ]);
58
+ export async function listFiles(sandbox, root, maxDepth, detail) {
59
+ const out = createOutputBuffer();
60
+ const ignore = createIgnore(sandbox);
61
+ if (detail) {
62
+ await walkWithin(sandbox, root, maxDepth, async (file, info) => {
63
+ out.line((info.isDirectory() ? "dir " : "file") + " " + file);
64
+ return !out.full;
65
+ });
66
+ return out.value();
67
+ }
68
+ async function visitDir(dir, depth, label) {
69
+ let entries;
70
+ try {
71
+ entries = await readdir(dir, { withFileTypes: true });
72
+ }
73
+ catch {
74
+ out.line(`${indent(depth)}${label}/ (unreadable)`);
75
+ return;
76
+ }
77
+ entries = entries.filter((entry) => !ignore.ignored(entry.name, join(dir, entry.name), entry.isDirectory()));
78
+ entries.sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name));
79
+ const dirs = entries.filter((entry) => entry.isDirectory());
80
+ const files = entries.filter((entry) => entry.isFile());
81
+ out.line(`${indent(depth)}${label}/ (${files.length} file${files.length === 1 ? "" : "s"}, ${dirs.length} dir${dirs.length === 1 ? "" : "s"})`);
82
+ for (const entry of files) {
83
+ const full = join(dir, entry.name);
84
+ const info = await stat(full).catch(() => undefined);
85
+ if (!info)
86
+ continue;
87
+ let line = `${indent(depth + 1)}${entry.name} (${formatSize(info.size)})`;
88
+ const symbols = await fileSymbols(full, entry.name, info.size);
89
+ if (symbols)
90
+ line += ` 鈥?${symbols}`;
91
+ out.line(line);
92
+ if (out.full)
93
+ return;
94
+ }
95
+ if (depth >= maxDepth)
96
+ return;
97
+ for (const entry of dirs) {
98
+ await visitDir(join(dir, entry.name), depth + 1, entry.name);
99
+ if (out.full)
100
+ return;
101
+ }
102
+ }
103
+ await visitDir(root, 0, basename(root) || ".");
104
+ return out.value();
105
+ }
106
+ async function fileSymbols(file, name, size) {
107
+ if (size > 256 * 1024 || !CODE_EXT.has(extname(name).toLowerCase()))
108
+ return "";
109
+ try {
110
+ const text = await readTextFile(file, 64 * 1024);
111
+ const names = new Set();
112
+ const patterns = [
113
+ /(?:^|\n)\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|enum|struct|trait|impl|def)\s+([A-Za-z_$][\w$]*)/g,
114
+ /(?:^|\n)\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?(?:\(|function)/g,
115
+ ];
116
+ for (const pattern of patterns) {
117
+ for (const match of text.matchAll(pattern)) {
118
+ names.add(match[1]);
119
+ if (names.size >= 8)
120
+ break;
121
+ }
122
+ }
123
+ return Array.from(names).slice(0, 8).join(", ");
124
+ }
125
+ catch {
126
+ return "";
127
+ }
128
+ }
129
+ function indent(depth) {
130
+ return " ".repeat(depth);
131
+ }
132
+ export function formatSize(bytes) {
133
+ if (bytes < 1024)
134
+ return `${bytes} B`;
135
+ if (bytes < 1024 * 1024)
136
+ return `${(bytes / 1024).toFixed(1)} KB`;
137
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
138
+ }
139
+ export async function grepFiles(sandbox, root, options) {
140
+ const re = new RegExp(options.pattern, options.flags ?? "i");
141
+ const maxResults = Math.max(1, options.maxResults ?? 200);
142
+ const context = Math.max(0, options.context ?? 0);
143
+ const maxBytes = grepMaxBytes();
144
+ const out = createOutputBuffer();
145
+ const globRe = options.glob ? globToRegExp(options.glob) : undefined;
146
+ let matchCount = 0;
147
+ let filesScanned = 0;
148
+ let truncated = false;
149
+ await walkWithin(sandbox, root, options.maxDepth ?? 4, async (file, info) => {
150
+ if (options.signal?.aborted)
151
+ throw Object.assign(new Error("grep aborted"), { code: "ABORTED" });
152
+ if (!info.isFile())
153
+ return;
154
+ if (isBinaryName(basename(file)))
155
+ return;
156
+ if (globRe) {
157
+ const rel = relative(root, file).replace(/\\/g, "/");
158
+ const target = options.glob && options.glob.includes("/") ? rel : basename(file);
159
+ if (!globRe.test(target))
160
+ return;
161
+ }
162
+ if (info.size > maxBytes) {
163
+ out.line(`${file}: skipped (${formatSize(info.size)} exceeds ${formatSize(maxBytes)})`);
164
+ return !out.full;
165
+ }
166
+ filesScanned += 1;
167
+ try {
168
+ const result = await readTextFileResult(file, maxBytes);
169
+ if (result.binary)
170
+ return;
171
+ const lines = result.text.split(/\r?\n/);
172
+ const printed = new Set();
173
+ for (let index = 0; index < lines.length; index += 1) {
174
+ if (!re.test(lines[index]))
175
+ continue;
176
+ matchCount += 1;
177
+ const start = Math.max(0, index - context);
178
+ const end = Math.min(lines.length - 1, index + context);
179
+ for (let cursor = start; cursor <= end; cursor += 1) {
180
+ if (printed.has(cursor) && cursor !== index)
181
+ continue;
182
+ printed.add(cursor);
183
+ const isMatch = cursor === index;
184
+ out.line(`${file}:${cursor + 1}${isMatch ? ": " : "- "}${lines[cursor]}`);
185
+ if (out.full) {
186
+ truncated = true;
187
+ return false;
188
+ }
189
+ }
190
+ if (matchCount >= maxResults) {
191
+ truncated = true;
192
+ return false;
193
+ }
194
+ }
195
+ }
196
+ catch (error) {
197
+ if (error?.code === "ABORTED")
198
+ throw error;
199
+ // Binary or unreadable files are skipped.
200
+ }
201
+ return !out.full;
202
+ });
203
+ if (out.full)
204
+ truncated = true;
205
+ return { text: out.value(), truncated, matchCount, filesScanned };
206
+ }
207
+ function globToRegExp(glob) {
208
+ let source = "^";
209
+ for (let i = 0; i < glob.length; i += 1) {
210
+ const char = glob[i];
211
+ if (char === "*") {
212
+ if (glob[i + 1] === "*") {
213
+ source += ".*";
214
+ i += 1;
215
+ if (glob[i + 1] === "/")
216
+ i += 1;
217
+ }
218
+ else {
219
+ source += "[^/]*";
220
+ }
221
+ }
222
+ else if (char === "?") {
223
+ source += "[^/]";
224
+ }
225
+ else if (/[.+^${}()|[\]\\]/.test(char)) {
226
+ source += "\\" + char;
227
+ }
228
+ else {
229
+ source += char;
230
+ }
231
+ }
232
+ return new RegExp(source + "$", "i");
233
+ }
234
+ export async function readTextFileResult(file, limit = readMaxBytes()) {
235
+ const info = await stat(file);
236
+ if (!info.isFile())
237
+ throw Object.assign(new Error(`not a file: ${file}`), { code: "EISDIR" });
238
+ const length = Math.min(info.size, limit);
239
+ const handle = await open(file, "r");
240
+ try {
241
+ const buffer = Buffer.alloc(length);
242
+ const { bytesRead } = await handle.read(buffer, 0, length, 0);
243
+ const slice = buffer.subarray(0, bytesRead);
244
+ if (slice.subarray(0, Math.min(8192, slice.length)).includes(0)) {
245
+ return {
246
+ text: `[binary file omitted: ${file} (${info.size} bytes)]`,
247
+ truncated: false,
248
+ totalLines: undefined,
249
+ returnedLines: 0,
250
+ totalBytes: info.size,
251
+ returnedBytes: 0,
252
+ binary: true,
253
+ };
254
+ }
255
+ const text = slice.toString("utf8");
256
+ const truncated = info.size > length;
257
+ const returnedLines = text ? text.split(/\r?\n/).length : 0;
258
+ return {
259
+ text,
260
+ truncated,
261
+ totalLines: truncated ? undefined : returnedLines,
262
+ returnedLines,
263
+ totalBytes: info.size,
264
+ returnedBytes: Buffer.byteLength(text, "utf8"),
265
+ binary: false,
266
+ };
267
+ }
268
+ finally {
269
+ await handle.close();
270
+ }
271
+ }
272
+ export async function readFileRange(file, offset, limit, options = {}) {
273
+ const info = await stat(file);
274
+ if (!info.isFile())
275
+ throw Object.assign(new Error(`not a file: ${file}`), { code: "EISDIR" });
276
+ const probe = await readTextFileResult(file, 8192);
277
+ if (probe.binary) {
278
+ return {
279
+ text: probe.text,
280
+ truncated: false,
281
+ totalLines: 0,
282
+ returnedLines: 0,
283
+ totalBytes: info.size,
284
+ returnedBytes: 0,
285
+ scannedAll: true,
286
+ binary: true,
287
+ };
288
+ }
289
+ const start = Math.max(1, Math.floor(offset));
290
+ const count = Math.max(1, Math.floor(limit));
291
+ const maxBytes = options.maxBytes ?? readMaxBytes();
292
+ const scanMaxBytes = options.scanMaxBytes ?? readScanMaxBytes();
293
+ const collected = [];
294
+ let lineNo = 0;
295
+ let returnedBytes = 0;
296
+ let totalBytes = 0;
297
+ let truncated = false;
298
+ let scannedAll = true;
299
+ let sawRange = false;
300
+ const stream = createReadStream(file, { encoding: "utf8" });
301
+ const lines = readline.createInterface({ input: stream, crlfDelay: Infinity });
302
+ try {
303
+ for await (const line of lines) {
304
+ if (options.signal?.aborted)
305
+ throw Object.assign(new Error("read aborted"), { code: "ABORTED" });
306
+ lineNo += 1;
307
+ totalBytes += Buffer.byteLength(line, "utf8") + 1;
308
+ if (lineNo >= start && collected.length < count) {
309
+ sawRange = true;
310
+ collected.push(line);
311
+ returnedBytes += Buffer.byteLength(line, "utf8") + 1;
312
+ if (returnedBytes > maxBytes) {
313
+ truncated = true;
314
+ scannedAll = false;
315
+ break;
316
+ }
317
+ }
318
+ else if (lineNo > start + count - 1) {
319
+ truncated = true;
320
+ }
321
+ if (totalBytes > scanMaxBytes) {
322
+ truncated = true;
323
+ scannedAll = false;
324
+ break;
325
+ }
326
+ }
327
+ }
328
+ finally {
329
+ lines.close();
330
+ stream.destroy();
331
+ }
332
+ return {
333
+ text: collected.join("\n"),
334
+ truncated,
335
+ totalLines: lineNo,
336
+ returnedLines: sawRange ? collected.length : 0,
337
+ totalBytes: info.size,
338
+ returnedBytes,
339
+ scannedAll,
340
+ binary: false,
341
+ };
342
+ }
343
+ export async function readTextFile(file, limit = readMaxBytes()) {
344
+ const result = await readTextFileResult(file, limit);
345
+ return result.truncated
346
+ ? `${result.text}\n[... truncated at ${result.returnedBytes} of ${result.totalBytes} bytes; use read_file offset/limit, grep, or a narrower file ...]`
347
+ : result.text;
348
+ }
349
+ export function readMaxBytes() {
350
+ const raw = Number(process.env["FASTCODE_READ_MAX_BYTES"]);
351
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 200_000;
352
+ }
353
+ export function grepMaxBytes() {
354
+ const raw = Number(process.env["FASTCODE_GREP_MAX_BYTES"]);
355
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 1_000_000;
356
+ }
357
+ export function readScanMaxBytes() {
358
+ const raw = Number(process.env["FASTCODE_READ_SCAN_MAX_BYTES"]);
359
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 8_000_000;
360
+ }
361
+ export function readManyMaxBytes() {
362
+ const raw = Number(process.env["FASTCODE_READ_MANY_MAX_BYTES"]);
363
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 400_000;
364
+ }
365
+ async function walkWithin(sandbox, root, maxDepth, visit) {
366
+ const roots = allowedRoots(sandbox).map((entry) => realpathOf(entry)).filter(Boolean);
367
+ const ignore = createIgnore(sandbox);
368
+ await walk(root, maxDepth, visit, 0, { roots, visited: 0, maxVisited: 5000, ignore }, []);
369
+ }
370
+ async function walk(node, maxDepth, visit, depth = 0, state, chain = []) {
371
+ if (state.visited >= state.maxVisited)
372
+ return;
373
+ let info;
374
+ try {
375
+ info = await stat(node);
376
+ }
377
+ catch {
378
+ return;
379
+ }
380
+ let real;
381
+ try {
382
+ real = await realpathOf(node) ?? node;
383
+ }
384
+ catch {
385
+ real = node;
386
+ }
387
+ if (chain.includes(real))
388
+ return;
389
+ state.visited += 1;
390
+ const insideSandbox = !state.roots || state.roots.length === 0 || isInsideAny(real, state.roots);
391
+ if (!insideSandbox)
392
+ return;
393
+ let keepGoing = true;
394
+ try {
395
+ keepGoing = await visit(node, info);
396
+ }
397
+ catch {
398
+ // A visitor failure must not abort the surrounding walk.
399
+ }
400
+ if (keepGoing === false)
401
+ return false;
402
+ if (!info.isDirectory() || depth >= maxDepth)
403
+ return;
404
+ let entries;
405
+ try {
406
+ entries = await readdir(node, { withFileTypes: true });
407
+ }
408
+ catch {
409
+ return;
410
+ }
411
+ const nextChain = chain.concat(real);
412
+ for (const entry of entries) {
413
+ if (state.ignore?.ignored(entry.name, join(node, entry.name), entry.isDirectory()))
414
+ continue;
415
+ if (state.visited >= state.maxVisited)
416
+ return;
417
+ try {
418
+ const keepWalking = await walk(join(node, entry.name), maxDepth, visit, depth + 1, state, nextChain);
419
+ if (keepWalking === false)
420
+ return false;
421
+ }
422
+ catch {
423
+ // Keep walking siblings.
424
+ }
425
+ }
426
+ }
@@ -0,0 +1,2 @@
1
+ import type { Tool } from "./types.js";
2
+ export declare const replaceInFileTool: Tool;
@@ -0,0 +1,63 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { resolvePath } from "./paths.js";
3
+ import { invalid, optionalNumber, preparePath, requiredString } from "./validate.js";
4
+ function countOccurrences(source, find) {
5
+ if (!find)
6
+ return 0;
7
+ return source.split(find).length - 1;
8
+ }
9
+ export const replaceInFileTool = {
10
+ name: "replace_in_file",
11
+ requiresTrust: true,
12
+ stateScope: "turn",
13
+ revalidate: ["trust", "sandboxRoots"],
14
+ prepare(call, ctx) {
15
+ try {
16
+ requiredString(call, "path");
17
+ const find = requiredString(call, "find");
18
+ requiredString(call, "replace");
19
+ if (!find)
20
+ return invalid("find must not be empty");
21
+ const expected = optionalNumber(call, "expectedMatches");
22
+ if (expected !== undefined && (expected < 1 || !Number.isInteger(expected)))
23
+ return invalid("expectedMatches must be a positive integer");
24
+ }
25
+ catch (err) {
26
+ return invalid(err.message);
27
+ }
28
+ return preparePath(ctx.cwd, requiredString(call, "path"));
29
+ },
30
+ async execute(call, ctx) {
31
+ const file = resolvePath(ctx.cwd, call);
32
+ const find = requiredString(call, "find");
33
+ const replacement = requiredString(call, "replace");
34
+ const expected = optionalNumber(call, "expectedMatches");
35
+ const source = await readFile(file, "utf8");
36
+ const count = countOccurrences(source, find);
37
+ if (count === 0) {
38
+ return {
39
+ ok: false,
40
+ output: `text not found in ${file}`,
41
+ code: "NOT_FOUND",
42
+ hint: "Read the file first and pass the exact current text.",
43
+ matches: 0,
44
+ };
45
+ }
46
+ if (expected !== undefined && count !== expected) {
47
+ return {
48
+ ok: false,
49
+ output: `expected ${expected} match(es) but found ${count} in ${file}; file unchanged`,
50
+ code: "INVALID_ARGS",
51
+ hint: "Update find or expectedMatches so the replacement is explicit.",
52
+ matches: count,
53
+ };
54
+ }
55
+ await writeFile(file, source.split(find).join(replacement), "utf8");
56
+ return {
57
+ ok: true,
58
+ output: `replaced ${count} occurrence(s) in ${file}`,
59
+ matches: count,
60
+ sideEffects: { touched: [file] },
61
+ };
62
+ },
63
+ };
@@ -0,0 +1,2 @@
1
+ import type { Tool } from "./types.js";
2
+ export declare const shellTool: Tool;
@@ -0,0 +1,48 @@
1
+ import { executeShellCommand, pollShellSession } from "../shell-session.js";
2
+ import { invalid, normalizeTimeoutMs, numberOption } from "./validate.js";
3
+ export const shellTool = {
4
+ name: "shell",
5
+ requiresTrust: true,
6
+ stateScope: "turn",
7
+ revalidate: ["trust", "sandboxRoots"],
8
+ approvalHints: { mayRequireApproval: true },
9
+ prepare(call) {
10
+ if (typeof call.session_id === "string" && call.session_id.trim())
11
+ return { ok: true };
12
+ if (typeof call.command !== "string" || call.command.trim() === "")
13
+ return invalid("command must be a non-empty string");
14
+ return { ok: true };
15
+ },
16
+ async execute(call, ctx) {
17
+ if (typeof call.session_id === "string" && call.session_id.trim()) {
18
+ const polled = await pollShellSession(call.session_id, numberOption(call.waitMs, 0));
19
+ if (!polled)
20
+ return { ok: false, output: `unknown shell session: ${call.session_id}` };
21
+ return formatPoll(call.session_id, polled);
22
+ }
23
+ const result = await executeShellCommand(call.command, ctx.cwd, {
24
+ signal: ctx.signal,
25
+ turn: ctx.turn,
26
+ timeoutMs: normalizeTimeoutMs(call.timeoutMs),
27
+ });
28
+ return formatExec(result);
29
+ },
30
+ };
31
+ function formatExec(result) {
32
+ if (result.status !== "running")
33
+ return { ok: result.ok, output: result.output || `[command ${result.status}]` };
34
+ const header = `[session ${result.sessionId}] running`;
35
+ const body = result.output ? "\n" + result.output : "";
36
+ return {
37
+ ok: true,
38
+ output: `${header}${body}\n\nPoll with: {"tool":"shell","session_id":"${result.sessionId}"}`,
39
+ };
40
+ }
41
+ function formatPoll(id, result) {
42
+ const status = result.status === "running"
43
+ ? `running (next poll ~${Math.round((result.nextPollMs ?? 1000) / 1000)}s)`
44
+ : `${result.status}${result.exitCode !== undefined && result.exitCode !== null ? ` (exit ${result.exitCode})` : ""}`;
45
+ const header = `[session ${id}] ${status}`;
46
+ const body = result.output ? "\n" + result.output : result.status === "running" ? "\n(no new output)" : "";
47
+ return { ok: result.ok, output: header + body };
48
+ }
@@ -0,0 +1,2 @@
1
+ import type { Tool } from "./types.js";
2
+ export declare const statPathTool: Tool;
@@ -0,0 +1,31 @@
1
+ import { stat } from "node:fs/promises";
2
+ import { resolvePath } from "./paths.js";
3
+ import { invalid, preparePath, requiredString } from "./validate.js";
4
+ export const statPathTool = {
5
+ name: "stat_path",
6
+ stateScope: "call",
7
+ revalidate: ["sandboxRoots"],
8
+ prepare(call, ctx) {
9
+ let path;
10
+ try {
11
+ path = requiredString(call, "path");
12
+ }
13
+ catch (err) {
14
+ return invalid(err.message);
15
+ }
16
+ return preparePath(ctx.cwd, path);
17
+ },
18
+ async execute(call, ctx) {
19
+ const file = resolvePath(ctx.cwd, call);
20
+ const info = await stat(file);
21
+ return {
22
+ ok: true,
23
+ output: JSON.stringify({
24
+ path: file,
25
+ type: info.isDirectory() ? "dir" : info.isFile() ? "file" : "other",
26
+ size: info.size,
27
+ modified: info.mtime.toISOString(),
28
+ }, null, 2),
29
+ };
30
+ },
31
+ };
@@ -0,0 +1,65 @@
1
+ export type StateScope = "call" | "turn" | "session";
2
+ export type RevalidateKey = "trust" | "sandboxRoots" | "schema" | "modelCaps";
3
+ export type ToolErrorCode = "ENOENT" | "EACCES" | "EEXIST" | "ENOTDIR" | "EISDIR" | "EXDEV" | "INVALID_ARGS" | "SANDBOX_DENY" | "APPROVAL_DENY" | "EGRESS_DENY" | "NOT_FOUND" | "ABORTED" | "UNKNOWN";
4
+ export interface SideEffects {
5
+ touched?: string[];
6
+ created?: string[];
7
+ removed?: string[];
8
+ }
9
+ export interface EgressAudit {
10
+ outlet: string;
11
+ target?: string;
12
+ rule: string;
13
+ mode: "audit" | "block";
14
+ wouldBlock: boolean;
15
+ code: "EGRESS_DENY";
16
+ reason: string;
17
+ }
18
+ import type { ApprovalResolver } from "../approval.js";
19
+ export interface ToolCall {
20
+ tool: string;
21
+ [key: string]: any;
22
+ }
23
+ export interface ToolResult {
24
+ ok: boolean;
25
+ output: string;
26
+ code?: ToolErrorCode;
27
+ hint?: string;
28
+ truncated?: boolean;
29
+ totalLines?: number;
30
+ returnedLines?: number;
31
+ totalBytes?: number;
32
+ returnedBytes?: number;
33
+ filesRead?: number;
34
+ filesSkipped?: number;
35
+ matches?: number;
36
+ egress?: EgressAudit;
37
+ sideEffects?: SideEffects;
38
+ }
39
+ export interface ToolCtx {
40
+ cwd: string;
41
+ signal?: AbortSignal;
42
+ turn?: number;
43
+ requestApproval?: ApprovalResolver;
44
+ catalogHash?: string;
45
+ }
46
+ export type Prepared = {
47
+ ok: true;
48
+ } | {
49
+ ok: false;
50
+ output: string;
51
+ code?: ToolErrorCode;
52
+ hint?: string;
53
+ };
54
+ export interface ApprovalHints {
55
+ mayRequireApproval: boolean;
56
+ }
57
+ export interface Tool {
58
+ name: string;
59
+ requiresTrust?: boolean;
60
+ stateScope: StateScope;
61
+ revalidate?: RevalidateKey[];
62
+ approvalHints?: ApprovalHints;
63
+ prepare(call: ToolCall, ctx: ToolCtx): Prepared;
64
+ execute(call: ToolCall, ctx: ToolCtx): Promise<ToolResult>;
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { Prepared, ToolCall, ToolErrorCode } from "./types.js";
2
+ export declare function invalid(output: string, code?: ToolErrorCode, hint?: string): Prepared;
3
+ export declare function requiredString(call: ToolCall, key: string): string;
4
+ export declare function stringArray(call: ToolCall, key: string): string[];
5
+ export declare function numberOption(value: any, fallback: number): number;
6
+ export declare function optionalNumber(call: ToolCall, key: string): number | undefined;
7
+ export declare function clampInt(value: number | undefined, fallback: number, min: number, max: number): number;
8
+ export declare function booleanOption(value: any, fallback?: boolean): boolean;
9
+ export declare const MAX_TIMEOUT_MS = 2147483647;
10
+ /**
11
+ * Normalize a tool `timeoutMs` value.
12
+ *
13
+ * Only `null`/`undefined` mean "not specified". Any other non-number, NaN,
14
+ * Infinity, or negative value is a caller error and must be surfaced instead
15
+ * of silently falling back. Large finite values are clamped to the
16
+ * `setTimeout` ceiling (2^31-1) rather than overflowing to 1ms.
17
+ */
18
+ export declare function normalizeTimeoutMs(value: unknown, fallback?: number): number;
19
+ export declare function preparePath(cwd: string, raw: string): Prepared;