@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,143 @@
1
+ /**
2
+ * Streaming request runner with an idle timeout and prefix-safe retry.
3
+ *
4
+ * `provider.stream()` only exposes the caller's AbortSignal, so a connection
5
+ * that stalls after the headers arrive can wait forever. This wrapper adds:
6
+ * • an idle timer reset on every chunk (default 60s),
7
+ * • retries routed through `retryStream`, which skips the already delivered
8
+ * prefix instead of duplicating tokens,
9
+ * • `incomplete` detection so the chat loop can tell "server closed early"
10
+ * from a normal finish.
11
+ */
12
+ import type { ChatChunk, ChatRequest, Provider } from "../types.js";
13
+ import { IncompleteStreamError, retryStream } from "../lib/net.js";
14
+
15
+ export interface RunStreamOptions {
16
+ signal?: AbortSignal;
17
+ idleTimeoutMs?: number;
18
+ maxRetries?: number;
19
+ onDelta?: (delta: string, text: string) => void;
20
+ onReset?: () => void;
21
+ clientRequestId?: string;
22
+ retryBackoffMs?: number[];
23
+ }
24
+
25
+ export interface RunStreamResult {
26
+ text: string;
27
+ usage?: ChatChunk["usage"];
28
+ toolCalls?: ChatChunk["toolCalls"];
29
+ incomplete: boolean;
30
+ retried: number;
31
+ }
32
+
33
+ export async function runStream(provider: Provider, req: ChatRequest, apiKey: string | undefined, options: RunStreamOptions = {}): Promise<RunStreamResult> {
34
+ const idleTimeoutMs = positive(options.idleTimeoutMs, 60_000);
35
+ const maxRetries = Math.max(0, Math.floor(options.maxRetries ?? 1));
36
+ let retried = 0;
37
+ let text = "";
38
+ let usage: ChatChunk["usage"];
39
+ let toolCalls: ChatChunk["toolCalls"];
40
+ let sawDone = false;
41
+ const send = async function* ({ clientRequestId }: { clientRequestId: string }) {
42
+ yield* attemptChunks(provider, { ...req, clientRequestId }, apiKey, {
43
+ signal: options.signal,
44
+ idleTimeoutMs,
45
+ });
46
+ };
47
+ try {
48
+ for await (const chunk of retryStream(send, {
49
+ clientRequestId: options.clientRequestId,
50
+ retries: maxRetries,
51
+ signal: options.signal,
52
+ backoffMs: options.retryBackoffMs,
53
+ onRetry: () => {
54
+ retried += 1;
55
+ },
56
+ onReset: options.onReset,
57
+ })) {
58
+ if (chunk.reset)
59
+ text = chunk.delta ?? "";
60
+ else if (chunk.delta)
61
+ text += chunk.delta;
62
+ if (chunk.delta)
63
+ options.onDelta?.(chunk.delta, text);
64
+ if (chunk.usage)
65
+ usage = chunk.usage;
66
+ if (chunk.toolCalls)
67
+ toolCalls = chunk.toolCalls;
68
+ if (chunk.done)
69
+ sawDone = true;
70
+ }
71
+ }
72
+ catch (err) {
73
+ if (err instanceof IncompleteStreamError) {
74
+ return { text: err.partialText || text, usage, toolCalls, incomplete: true, retried };
75
+ }
76
+ if (err && typeof err === "object" && !(err as { partialText?: string }).partialText && text)
77
+ (err as { partialText?: string }).partialText = text;
78
+ throw err;
79
+ }
80
+ return { text, usage, toolCalls, incomplete: !sawDone, retried };
81
+ }
82
+
83
+ interface AttemptOptions {
84
+ signal?: AbortSignal;
85
+ idleTimeoutMs: number;
86
+ }
87
+
88
+ async function* attemptChunks(provider: Provider, req: ChatRequest, apiKey: string | undefined, options: AttemptOptions): AsyncGenerator<ChatChunk> {
89
+ const parent = options.signal;
90
+ const child = new AbortController();
91
+ let timedOut = false;
92
+ const forwardAbort = () => child.abort();
93
+ if (parent) {
94
+ if (parent.aborted)
95
+ child.abort();
96
+ else
97
+ parent.addEventListener("abort", forwardAbort, { once: true });
98
+ }
99
+ let timer: ReturnType<typeof setTimeout> | undefined;
100
+ const resetIdle = () => {
101
+ if (timer)
102
+ clearTimeout(timer);
103
+ timer = setTimeout(() => {
104
+ timedOut = true;
105
+ child.abort();
106
+ }, options.idleTimeoutMs);
107
+ };
108
+ resetIdle();
109
+ try {
110
+ for await (const chunk of provider.stream({ ...req, signal: child.signal }, apiKey)) {
111
+ resetIdle();
112
+ yield chunk;
113
+ if (chunk.done)
114
+ return;
115
+ }
116
+ }
117
+ catch (err) {
118
+ if (parent?.aborted)
119
+ throw abortError();
120
+ if (timedOut)
121
+ throw new Error(`stream idle timeout after ${options.idleTimeoutMs}ms (no data received)`);
122
+ throw toError(err);
123
+ }
124
+ finally {
125
+ if (timer)
126
+ clearTimeout(timer);
127
+ parent?.removeEventListener("abort", forwardAbort);
128
+ }
129
+ }
130
+
131
+ function abortError(): Error {
132
+ const error = new Error("Aborted");
133
+ error.name = "AbortError";
134
+ return error;
135
+ }
136
+
137
+ function toError(value: unknown): Error {
138
+ return value instanceof Error ? value : new Error(String(value));
139
+ }
140
+
141
+ function positive(value: number | undefined, fallback: number): number {
142
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
143
+ }
@@ -0,0 +1,86 @@
1
+ import type { ToolSchema } from "../types.js";
2
+
3
+ const CWD_NOTE = "Paths are relative to the session working directory unless absolute.";
4
+
5
+ export function toolSchemas(): ToolSchema[] {
6
+ return [
7
+ schema("shell", "Run a shell command. Long commands return a session id for polling. Killing a session kills its process group; setsid descendants may escape.", {
8
+ command: { type: "string" },
9
+ timeoutMs: { type: "number" },
10
+ session_id: { type: "string", description: "Poll an existing background session." },
11
+ waitMs: { type: "number", description: "Milliseconds to wait for new session output." },
12
+ }),
13
+ schema("pwd", "Print the current working directory.", {}),
14
+ schema("read_file", "Read a file. Use offset/limit (1-based lines) for a targeted range in large files.", {
15
+ path: { type: "string" },
16
+ offset: { type: "number", minimum: 1, description: "First line to return (1-based)." },
17
+ limit: { type: "number", minimum: 1, maximum: 2000, description: "Maximum lines to return (default 2000)." },
18
+ }, ["path"]),
19
+ schema("read_many", "Read several files under a shared byte budget.", {
20
+ paths: { type: "array", items: { type: "string" } },
21
+ maxBytes: { type: "number", minimum: 1, description: "Total output budget across all files." },
22
+ }, ["paths"]),
23
+ schema("write_file", "Replace a file with full contents.", {
24
+ path: { type: "string" },
25
+ content: { type: "string" },
26
+ }, ["path", "content"]),
27
+ schema("append_file", "Append text to a file.", {
28
+ path: { type: "string" },
29
+ content: { type: "string" },
30
+ }, ["path", "content"]),
31
+ schema("apply_patch", "Apply a multi-file patch (*** Begin Patch ... *** End Patch).", {
32
+ patch: { type: "string" },
33
+ }, ["patch"]),
34
+ schema("copy_file", CWD_NOTE, {
35
+ from: { type: "string" },
36
+ to: { type: "string" },
37
+ overwrite: { type: "boolean", description: "Replace an existing target explicitly (default false)." },
38
+ }, ["from", "to"]),
39
+ schema("delete_file", "Delete a file, or a directory with recursive:true.", {
40
+ path: { type: "string" },
41
+ recursive: { type: "boolean", description: "Required for directories (default false)." },
42
+ }, ["path"]),
43
+ schema("list_files", "List files under a directory.", {
44
+ path: { type: "string" },
45
+ maxDepth: { type: "number" },
46
+ detail: { type: "boolean" },
47
+ }),
48
+ schema("make_dir", "Create a directory.", { path: { type: "string" } }, ["path"]),
49
+ schema("move_path", "Move/rename a path; refuses an existing target.", {
50
+ from: { type: "string" },
51
+ to: { type: "string" },
52
+ }, ["from", "to"]),
53
+ schema("grep", "Search file contents with a regular expression.", {
54
+ pattern: { type: "string" },
55
+ path: { type: "string" },
56
+ maxDepth: { type: "number", minimum: 1, maximum: 20 },
57
+ caseSensitive: { type: "boolean", description: "Match case exactly (default false)." },
58
+ glob: { type: "string", description: "Only search files matching this glob, e.g. *.ts." },
59
+ context: { type: "number", minimum: 0, maximum: 10, description: "Context lines before/after each match." },
60
+ maxResults: { type: "number", minimum: 1, maximum: 5000, description: "Stop after this many matching lines." },
61
+ }, ["pattern"]),
62
+ schema("replace_in_file", "Replace exact text occurrences; expectedMatches makes the count explicit.", {
63
+ path: { type: "string" },
64
+ find: { type: "string" },
65
+ replace: { type: "string" },
66
+ expectedMatches: { type: "number", minimum: 1, description: "Fail without writing unless this exact count is found." },
67
+ }, ["path", "find", "replace"]),
68
+ schema("stat_path", "Show file metadata.", { path: { type: "string" } }, ["path"]),
69
+ ];
70
+ }
71
+
72
+ function schema(name: string, description: string, properties: Record<string, any>, required?: string[]): ToolSchema {
73
+ return {
74
+ type: "function",
75
+ function: {
76
+ name,
77
+ description,
78
+ parameters: {
79
+ type: "object",
80
+ properties,
81
+ ...(required && required.length > 0 ? { required } : {}),
82
+ additionalProperties: false,
83
+ },
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,30 @@
1
+ import { existsSync } from "node:fs";
2
+ import { appendFile, mkdir } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { resolvePath } from "./paths.js";
5
+ import type { Tool } from "./types.js";
6
+ import { invalid, preparePath, requiredString } from "./validate.js";
7
+
8
+ export const appendFileTool: Tool = {
9
+ name: "append_file",
10
+ requiresTrust: true,
11
+ stateScope: "turn",
12
+ revalidate: ["trust", "sandboxRoots"],
13
+ prepare(call, ctx) {
14
+ try {
15
+ requiredString(call, "path");
16
+ requiredString(call, "content");
17
+ }
18
+ catch (err: any) {
19
+ return invalid(err.message);
20
+ }
21
+ return preparePath(ctx.cwd, requiredString(call, "path"));
22
+ },
23
+ async execute(call, ctx) {
24
+ const file = resolvePath(ctx.cwd, call);
25
+ const existed = existsSync(file);
26
+ await mkdir(dirname(file), { recursive: true });
27
+ await appendFile(file, requiredString(call, "content"), "utf8");
28
+ return { ok: true, output: `appended ${file}`, sideEffects: existed ? { touched: [file] } : { created: [file] } };
29
+ },
30
+ };
@@ -0,0 +1,95 @@
1
+ import { parsePatch } from "../../patch/parse.js";
2
+ import { planPatch, reviewPatchPlan } from "../../patch/plan.js";
3
+ import { applyPatchPlan } from "../../patch/apply.js";
4
+ import { DEFAULT_ALLOW_OPTIONS, approvalRequestId, type ApprovalOption, type ApprovalRequest } from "../approval.js";
5
+ import { approvalKey, isPreApproved, rememberApproval } from "../approval-store.js";
6
+ import type { Tool } from "./types.js";
7
+ import { invalid, requiredString } from "./validate.js";
8
+
9
+ /**
10
+ * Atomic multi-file patch.
11
+ *
12
+ * Applies Add / Delete / Update / Move only after every file plan succeeds.
13
+ * Symbolic links are refused, CRLF and mode bits are preserved, and per-file
14
+ * diffs are returned. xattr / ACL are NOT preserved and the inode changes
15
+ * (tmp+rename); restart file watchers if they miss the change.
16
+ */
17
+ export const applyPatchTool: Tool = {
18
+ name: "apply_patch",
19
+ requiresTrust: true,
20
+ stateScope: "turn",
21
+ revalidate: ["trust", "sandboxRoots"],
22
+ approvalHints: { mayRequireApproval: true },
23
+ prepare(call) {
24
+ let patch: string;
25
+ try {
26
+ patch = requiredString(call, "patch");
27
+ }
28
+ catch (err: any) {
29
+ return invalid(err.message);
30
+ }
31
+ const parsed = parsePatch(patch);
32
+ return parsed.ok === false ? invalid(parsed.output) : { ok: true };
33
+ },
34
+ async execute(call, ctx) {
35
+ const patch = requiredString(call, "patch");
36
+ const planned = planPatch(patch, ctx.cwd);
37
+ if (planned.ok === false)
38
+ return { ok: false, output: planned.output };
39
+ const review = reviewPatchPlan(planned.plan);
40
+ if (review.kind === "deny")
41
+ return { ok: false, output: `patch denied: ${review.reason}` };
42
+ if (review.kind === "pending") {
43
+ const key = approvalKey("apply_patch", call, ctx.cwd);
44
+ const catalogHash = ctx.catalogHash ?? "";
45
+ if (!isPreApproved(key, catalogHash)) {
46
+ if (!ctx.requestApproval) {
47
+ return {
48
+ ok: false,
49
+ output: `patch needs approval (${review.reason}), but no approval resolver is registered; nothing was written.`,
50
+ };
51
+ }
52
+ const request = patchApprovalRequest(call, review, planned.plan);
53
+ let decision;
54
+ try {
55
+ decision = await ctx.requestApproval(request);
56
+ }
57
+ catch (err: any) {
58
+ return { ok: false, output: `patch approval failed (fail closed): ${err?.message ?? String(err)}` };
59
+ }
60
+ if (!decision || decision.kind !== "allow") {
61
+ const reason = decision?.kind === "deny" ? decision.reason : decision?.kind === "feedback" ? decision.text : "no allow decision";
62
+ return { ok: false, output: `patch denied: ${reason ?? "no reason"}; nothing was written.` };
63
+ }
64
+ rememberApproval(key, catalogHash, decision.scope);
65
+ }
66
+ }
67
+ const applied = applyPatchPlan(planned.plan);
68
+ const warnings = applied.warnings.length > 0 ? "\n\nwarnings:\n" + applied.warnings.map((line) => "- " + line).join("\n") : "";
69
+ return {
70
+ ok: applied.ok,
71
+ output: applied.output + warnings,
72
+ sideEffects: { touched: applied.touched, removed: applied.removed },
73
+ };
74
+ },
75
+ };
76
+
77
+ function patchApprovalRequest(call: any, review: { reason: string; changedLines: number }, plan: any): ApprovalRequest {
78
+ const options = DEFAULT_ALLOW_OPTIONS.map((option) => ({ ...option })) as ApprovalOption[];
79
+ const denyIndex = options.findIndex((option) => option.kind === "deny");
80
+ const files = Array.isArray(plan?.files) ? plan.files : [];
81
+ const diff = files.map((file: any) => `${file.relPath ?? file.path}: ${file.changedLines ?? "?"} line(s)`).join("\n");
82
+ return {
83
+ id: approvalRequestId(),
84
+ tool: "apply_patch",
85
+ args: call,
86
+ reason: review.reason.includes("sensitive") ? "sensitive_path" : "large_diff",
87
+ options,
88
+ preview: diff ? { kind: "diff", text: diff } : undefined,
89
+ tier: 2,
90
+ prompt: `Apply patch? ${review.reason}`,
91
+ source: "system",
92
+ defaultIndex: denyIndex >= 0 ? denyIndex : 0,
93
+ timeoutMs: 120_000,
94
+ };
95
+ }
@@ -0,0 +1,56 @@
1
+ import { copyFile, mkdir, stat } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { sandboxPath } from "./paths.js";
4
+ import type { Tool } from "./types.js";
5
+ import { booleanOption, invalid, preparePath, requiredString } from "./validate.js";
6
+
7
+ async function exists(path: string): Promise<boolean> {
8
+ try {
9
+ await stat(path);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ export const copyFileTool: Tool = {
18
+ name: "copy_file",
19
+ requiresTrust: true,
20
+ stateScope: "turn",
21
+ revalidate: ["trust", "sandboxRoots"],
22
+ prepare(call, ctx) {
23
+ try {
24
+ const from = requiredString(call, "from");
25
+ const to = requiredString(call, "to");
26
+ const checkedFrom = preparePath(ctx.cwd, from);
27
+ if (!checkedFrom.ok)
28
+ return checkedFrom;
29
+ return preparePath(ctx.cwd, to);
30
+ }
31
+ catch (err: any) {
32
+ return invalid(err.message);
33
+ }
34
+ },
35
+ async execute(call, ctx) {
36
+ const from = sandboxPath(ctx.cwd, requiredString(call, "from"));
37
+ const to = sandboxPath(ctx.cwd, requiredString(call, "to"));
38
+ const overwrite = booleanOption(call.overwrite);
39
+ const existed = await exists(to);
40
+ if (existed && !overwrite) {
41
+ return {
42
+ ok: false,
43
+ output: `copy target exists: ${to}`,
44
+ code: "EEXIST",
45
+ hint: "Pass overwrite:true to replace the target explicitly.",
46
+ };
47
+ }
48
+ await mkdir(dirname(to), { recursive: true });
49
+ await copyFile(from, to);
50
+ return {
51
+ ok: true,
52
+ output: `copied ${from} to ${to}`,
53
+ sideEffects: existed ? { touched: [to] } : { created: [to] },
54
+ };
55
+ },
56
+ };
@@ -0,0 +1,45 @@
1
+ import { lstat, rm } from "node:fs/promises";
2
+ import { resolvePath } from "./paths.js";
3
+ import type { Tool } from "./types.js";
4
+ import { booleanOption, invalid, preparePath, requiredString } from "./validate.js";
5
+
6
+ export const deleteFileTool: Tool = {
7
+ name: "delete_file",
8
+ requiresTrust: true,
9
+ stateScope: "turn",
10
+ revalidate: ["trust", "sandboxRoots"],
11
+ approvalHints: { mayRequireApproval: true },
12
+ prepare(call, ctx) {
13
+ let path: string;
14
+ try {
15
+ path = requiredString(call, "path");
16
+ }
17
+ catch (err: any) {
18
+ return invalid(err.message);
19
+ }
20
+ return preparePath(ctx.cwd, path);
21
+ },
22
+ async execute(call, ctx) {
23
+ const file = resolvePath(ctx.cwd, call);
24
+ const recursive = booleanOption(call.recursive);
25
+ let info;
26
+ try {
27
+ info = await lstat(file);
28
+ }
29
+ catch (err: any) {
30
+ if (err?.code === "ENOENT")
31
+ return { ok: false, output: `path does not exist: ${file}`, code: "ENOENT", hint: "Check the path before deleting." };
32
+ throw err;
33
+ }
34
+ if (info.isDirectory() && !recursive) {
35
+ return {
36
+ ok: false,
37
+ output: `refusing to delete a directory without recursive:true: ${file}`,
38
+ code: "EISDIR",
39
+ hint: "Pass recursive:true to delete a directory tree.",
40
+ };
41
+ }
42
+ await rm(file, { recursive, force: false });
43
+ return { ok: true, output: `deleted ${file}`, sideEffects: { removed: [file] } };
44
+ },
45
+ };
@@ -0,0 +1,57 @@
1
+ import { grepFiles } from "./readers.js";
2
+ import { resolvePath } from "./paths.js";
3
+ import type { Tool } from "./types.js";
4
+ import { booleanOption, clampInt, invalid, optionalNumber, preparePath, requiredString } from "./validate.js";
5
+
6
+ const MAX_PATTERN_LENGTH = 1000;
7
+
8
+ export const grepTool: Tool = {
9
+ name: "grep",
10
+ stateScope: "call",
11
+ revalidate: ["sandboxRoots"],
12
+ prepare(call, ctx) {
13
+ let pattern: string;
14
+ try {
15
+ pattern = requiredString(call, "pattern");
16
+ if (pattern.length > MAX_PATTERN_LENGTH)
17
+ return invalid(`pattern is too long (max ${MAX_PATTERN_LENGTH} characters)`);
18
+ const flags = booleanOption(call.caseSensitive) ? "" : "i";
19
+ new RegExp(pattern, flags);
20
+ const context = optionalNumber(call, "context");
21
+ const maxResults = optionalNumber(call, "maxResults");
22
+ const maxDepth = optionalNumber(call, "maxDepth");
23
+ if (context !== undefined && (context < 0 || context > 10))
24
+ return invalid("context must be between 0 and 10");
25
+ if (maxResults !== undefined && (maxResults < 1 || maxResults > 5000))
26
+ return invalid("maxResults must be between 1 and 5000");
27
+ if (maxDepth !== undefined && (maxDepth < 1 || maxDepth > 20))
28
+ return invalid("maxDepth must be between 1 and 20");
29
+ if (call.glob !== undefined && typeof call.glob !== "string")
30
+ return invalid("glob must be a string");
31
+ }
32
+ catch (err: any) {
33
+ return invalid(`invalid regular expression: ${err?.message ?? String(err)}`, "INVALID_ARGS", "pattern must be a valid regular expression");
34
+ }
35
+ return preparePath(ctx.cwd, typeof call.path === "string" ? call.path : ".");
36
+ },
37
+ async execute(call, ctx) {
38
+ const pattern = requiredString(call, "pattern");
39
+ const result = await grepFiles(ctx.cwd, resolvePath(ctx.cwd, call, "."), {
40
+ pattern,
41
+ flags: booleanOption(call.caseSensitive) ? "" : "i",
42
+ glob: typeof call.glob === "string" ? call.glob : undefined,
43
+ context: clampInt(optionalNumber(call, "context"), 0, 0, 10),
44
+ maxResults: clampInt(optionalNumber(call, "maxResults"), 200, 1, 5000),
45
+ maxDepth: clampInt(optionalNumber(call, "maxDepth"), 4, 1, 20),
46
+ signal: ctx.signal,
47
+ });
48
+ return {
49
+ ok: true,
50
+ output: result.text,
51
+ truncated: result.truncated,
52
+ returnedLines: result.matchCount,
53
+ matches: result.matchCount,
54
+ hint: result.truncated ? "grep results were truncated by maxResults or the output cap." : undefined,
55
+ };
56
+ },
57
+ };
@@ -0,0 +1,34 @@
1
+ import type { Tool } from "./types.js";
2
+ import { shellTool } from "./shell.js";
3
+ import { pwdTool } from "./pwd.js";
4
+ import { readFileTool } from "./read_file.js";
5
+ import { readManyTool } from "./read_many.js";
6
+ import { writeFileTool } from "./write_file.js";
7
+ import { appendFileTool } from "./append_file.js";
8
+ import { applyPatchTool } from "./apply_patch.js";
9
+ import { copyFileTool } from "./copy_file.js";
10
+ import { deleteFileTool } from "./delete_file.js";
11
+ import { listFilesTool } from "./list_files.js";
12
+ import { makeDirTool } from "./make_dir.js";
13
+ import { movePathTool } from "./move_path.js";
14
+ import { grepTool } from "./grep.js";
15
+ import { replaceInFileTool } from "./replace_in_file.js";
16
+ import { statPathTool } from "./stat_path.js";
17
+
18
+ export const TOOLS: Tool[] = [
19
+ shellTool,
20
+ pwdTool,
21
+ readFileTool,
22
+ readManyTool,
23
+ writeFileTool,
24
+ appendFileTool,
25
+ applyPatchTool,
26
+ copyFileTool,
27
+ deleteFileTool,
28
+ listFilesTool,
29
+ makeDirTool,
30
+ movePathTool,
31
+ grepTool,
32
+ replaceInFileTool,
33
+ statPathTool,
34
+ ];
@@ -0,0 +1,19 @@
1
+ import { listFiles } from "./readers.js";
2
+ import { resolvePath } from "./paths.js";
3
+ import type { Tool } from "./types.js";
4
+ import { numberOption, preparePath } from "./validate.js";
5
+
6
+ export const listFilesTool: Tool = {
7
+ name: "list_files",
8
+ stateScope: "call",
9
+ revalidate: ["sandboxRoots"],
10
+ prepare(call, ctx) {
11
+ return preparePath(ctx.cwd, typeof call.path === "string" ? call.path : ".");
12
+ },
13
+ async execute(call, ctx) {
14
+ return {
15
+ ok: true,
16
+ output: await listFiles(ctx.cwd, resolvePath(ctx.cwd, call, "."), numberOption(call.maxDepth, 2), call.detail === true),
17
+ };
18
+ },
19
+ };
@@ -0,0 +1,28 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { resolvePath } from "./paths.js";
4
+ import type { Tool } from "./types.js";
5
+ import { invalid, preparePath, requiredString } from "./validate.js";
6
+
7
+ export const makeDirTool: Tool = {
8
+ name: "make_dir",
9
+ requiresTrust: true,
10
+ stateScope: "turn",
11
+ revalidate: ["trust", "sandboxRoots"],
12
+ prepare(call, ctx) {
13
+ let path: string;
14
+ try {
15
+ path = requiredString(call, "path");
16
+ }
17
+ catch (err: any) {
18
+ return invalid(err.message);
19
+ }
20
+ return preparePath(ctx.cwd, path);
21
+ },
22
+ async execute(call, ctx) {
23
+ const dir = resolvePath(ctx.cwd, call);
24
+ const existed = existsSync(dir);
25
+ await mkdir(dir, { recursive: true });
26
+ return { ok: true, output: `created ${dir}`, sideEffects: existed ? undefined : { created: [dir] } };
27
+ },
28
+ };