@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,11 @@
1
+ export interface IgnoreRule {
2
+ regex: RegExp;
3
+ negate: boolean;
4
+ dirOnly: boolean;
5
+ }
6
+ export interface IgnoreMatcher {
7
+ ignored(name: string, fullPath: string, isDir: boolean): boolean;
8
+ isBinary(name: string): boolean;
9
+ }
10
+ export declare function isBinaryName(name: string): boolean;
11
+ export declare function createIgnore(root: string): IgnoreMatcher;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Ignore rules for directory walks and reads.
3
+ *
4
+ * Defaults cover the usual dependency/build folders. Users can add more with
5
+ * `.fastcodeignore` in the project root, using a gitignore-like syntax:
6
+ *
7
+ * node_modules/ # trailing slash = directories only
8
+ * *.log # basename at any depth
9
+ * /secrets.txt # anchored to the project root
10
+ * !important.log # re-include (last matching rule wins)
11
+ */
12
+ import { readFileSync, statSync } from "node:fs";
13
+ import { join, relative, sep } from "node:path";
14
+ const DEFAULT_NAMES = new Set([
15
+ "node_modules",
16
+ ".git",
17
+ ".hg",
18
+ ".svn",
19
+ "dist",
20
+ "build",
21
+ "out",
22
+ ".next",
23
+ ".nuxt",
24
+ ".output",
25
+ ".turbo",
26
+ ".parcel-cache",
27
+ ".cache",
28
+ "coverage",
29
+ ".nyc_output",
30
+ "__pycache__",
31
+ ".venv",
32
+ "venv",
33
+ "target",
34
+ ".gradle",
35
+ ".idea",
36
+ ]);
37
+ const BINARY_EXT = /\.(png|jpe?g|gif|webp|ico|bmp|tiff?|pdf|zip|gz|tgz|bz2|xz|7z|rar|exe|dll|so|dylib|bin|wasm|mp3|mp4|mov|avi|mkv|wav|flac|ttf|otf|woff2?|eot|class|jar|apk|ipa|sqlite|db|lock)$/i;
38
+ export function isBinaryName(name) {
39
+ return BINARY_EXT.test(name);
40
+ }
41
+ export function createIgnore(root) {
42
+ const rules = loadRules(root);
43
+ return {
44
+ ignored(name, fullPath, isDir) {
45
+ if (DEFAULT_NAMES.has(name))
46
+ return true;
47
+ if (rules.length === 0)
48
+ return false;
49
+ const rel = relative(root, fullPath).split(sep).join("/");
50
+ let ignored = false;
51
+ for (const rule of rules) {
52
+ if (rule.dirOnly && !isDir)
53
+ continue;
54
+ if (rule.regex.test(rel) || rule.regex.test(name))
55
+ ignored = !rule.negate;
56
+ }
57
+ return ignored;
58
+ },
59
+ isBinary(name) {
60
+ return BINARY_EXT.test(name);
61
+ },
62
+ };
63
+ }
64
+ function loadRules(root) {
65
+ const file = join(root, ".fastcodeignore");
66
+ let text = "";
67
+ try {
68
+ const info = statSync(file);
69
+ if (!info.isFile())
70
+ return [];
71
+ text = readFileSync(file, "utf8");
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ const rules = [];
77
+ for (const raw of text.split(/\r?\n/)) {
78
+ const line = raw.trim();
79
+ if (!line || line.startsWith("#"))
80
+ continue;
81
+ const negate = line.startsWith("!");
82
+ const body = negate ? line.slice(1).trim() : line;
83
+ if (!body)
84
+ continue;
85
+ const dirOnly = body.endsWith("/");
86
+ const pattern = dirOnly ? body.slice(0, -1) : body;
87
+ const anchored = pattern.startsWith("/");
88
+ const glob = anchored ? pattern.slice(1) : pattern;
89
+ try {
90
+ rules.push({
91
+ regex: globToRegExp(glob, anchored),
92
+ negate,
93
+ dirOnly,
94
+ });
95
+ }
96
+ catch {
97
+ // A bad pattern must not disable the whole ignore file.
98
+ }
99
+ }
100
+ return rules;
101
+ }
102
+ function globToRegExp(glob, anchored) {
103
+ let source = "";
104
+ for (let i = 0; i < glob.length; i += 1) {
105
+ const char = glob[i];
106
+ if (char === "*") {
107
+ if (glob[i + 1] === "*") {
108
+ source += ".*";
109
+ i += 1;
110
+ if (glob[i + 1] === "/")
111
+ i += 1;
112
+ }
113
+ else {
114
+ source += "[^/]*";
115
+ }
116
+ }
117
+ else if (char === "?") {
118
+ source += "[^/]";
119
+ }
120
+ else {
121
+ source += escapeRegExp(char);
122
+ }
123
+ }
124
+ // Unanchored patterns match a basename at any depth.
125
+ return new RegExp("^" + (anchored ? "" : "(?:.*/)?") + source + "$");
126
+ }
127
+ function escapeRegExp(value) {
128
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
129
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Lightweight egress defaults. Kept separate from the policy engine so
3
+ * config/store can include the default object without loading guards.
4
+ */
5
+ export type EgressMode = "off" | "audit" | "block";
6
+ export interface EgressConfig {
7
+ enabled: boolean;
8
+ mode: EgressMode;
9
+ denyPaths: string[];
10
+ denyGlobs: string[];
11
+ denyContentMarkers: string[];
12
+ maxScanBytes: number;
13
+ scanToolOutput: boolean;
14
+ }
15
+ export declare const DEFAULT_CONTENT_MARKERS: string[];
16
+ export declare const DEFAULT_EGRESS_CONFIG: EgressConfig;
@@ -0,0 +1,17 @@
1
+ export const DEFAULT_CONTENT_MARKERS = [
2
+ "-----BEGIN [A-Z ]*PRIVATE KEY-----",
3
+ "AKIA[0-9A-Z]{16}",
4
+ "gh[pousr]_[A-Za-z0-9]{36,}",
5
+ "xox[baprs]-[A-Za-z0-9-]{10,}",
6
+ "AIza[0-9A-Za-z_-]{35}",
7
+ "sk-[A-Za-z0-9]{32,}",
8
+ ];
9
+ export const DEFAULT_EGRESS_CONFIG = {
10
+ enabled: false,
11
+ mode: "audit",
12
+ denyPaths: [],
13
+ denyGlobs: [],
14
+ denyContentMarkers: [...DEFAULT_CONTENT_MARKERS],
15
+ maxScanBytes: 1_000_000,
16
+ scanToolOutput: true,
17
+ };
@@ -0,0 +1,32 @@
1
+ import type { EgressAudit, ToolCall } from "../tools/types.js";
2
+ import { type EgressConfig, type EgressMode } from "./defaults.js";
3
+ export type { EgressConfig, EgressMode } from "./defaults.js";
4
+ export { DEFAULT_CONTENT_MARKERS, DEFAULT_EGRESS_CONFIG } from "./defaults.js";
5
+ export type EgressAction = "read" | "write" | "delete" | "egress" | "payload";
6
+ export interface EgressSubject {
7
+ outlet: string;
8
+ action: EgressAction;
9
+ target?: string;
10
+ payload?: string;
11
+ outbound?: boolean;
12
+ }
13
+ export interface EgressDecision {
14
+ outlet: string;
15
+ target?: string;
16
+ rule: string;
17
+ mode: EgressMode;
18
+ wouldBlock: boolean;
19
+ code: "EGRESS_DENY";
20
+ reason: string;
21
+ }
22
+ export interface EgressEvaluation {
23
+ action: "allow" | "audit" | "deny";
24
+ decision?: EgressDecision;
25
+ }
26
+ export declare function normalizeEgressConfig(raw: any): EgressConfig;
27
+ export declare function egressSubjects(call: ToolCall): EgressSubject[];
28
+ export declare function evaluateEgressCall(call: ToolCall, cwd: string, config: EgressConfig): EgressEvaluation;
29
+ export declare function evaluateEgressOutput(tool: string, output: string, config: EgressConfig): EgressEvaluation;
30
+ export declare function egressAudit(decision: EgressDecision): EgressAudit;
31
+ export declare function formatEgressDenial(decision: EgressDecision): string;
32
+ export declare function formatEgressAudit(audit: EgressAudit): string;
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Application-layer egress policy.
3
+ *
4
+ * This is intentionally a pure policy module plus a thin registry hook. It is
5
+ * not a kernel and cannot stop code that bypasses FastCode's tool registry.
6
+ *
7
+ * Default posture: audit only (`enabled:false`, `mode:"audit"`). Set
8
+ * `egress.enabled=true` and `egress.mode="block"` to enforce.
9
+ */
10
+ import { basename, relative, resolve } from "node:path";
11
+ import { isCriticalReadTarget, isSensitiveTarget, isSharedStoragePath } from "../guards.js";
12
+ import { DEFAULT_CONTENT_MARKERS, DEFAULT_EGRESS_CONFIG } from "./defaults.js";
13
+ export { DEFAULT_CONTENT_MARKERS, DEFAULT_EGRESS_CONFIG } from "./defaults.js";
14
+ function strings(value) {
15
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim()).map((entry) => entry.trim()) : [];
16
+ }
17
+ function positiveInt(value, fallback) {
18
+ const number = Number(value);
19
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
20
+ }
21
+ export function normalizeEgressConfig(raw) {
22
+ const source = raw && typeof raw === "object" ? raw : {};
23
+ const mode = source.mode === "block" ? "block" : source.mode === "off" ? "off" : "audit";
24
+ return {
25
+ enabled: source.enabled === true,
26
+ mode,
27
+ denyPaths: strings(source.denyPaths),
28
+ denyGlobs: strings(source.denyGlobs),
29
+ // Built-in markers cannot be removed by configuration.
30
+ denyContentMarkers: Array.from(new Set([...DEFAULT_CONTENT_MARKERS, ...strings(source.denyContentMarkers)])),
31
+ maxScanBytes: positiveInt(source.maxScanBytes, DEFAULT_EGRESS_CONFIG.maxScanBytes),
32
+ scanToolOutput: source.scanToolOutput !== false,
33
+ };
34
+ }
35
+ const compileCache = new Map();
36
+ function safeRegExp(pattern) {
37
+ try {
38
+ return new RegExp(pattern, "i");
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
44
+ function globToRegExp(glob) {
45
+ let source = "^";
46
+ for (let index = 0; index < glob.length; index += 1) {
47
+ const char = glob[index];
48
+ if (char === "*") {
49
+ if (glob[index + 1] === "*") {
50
+ source += ".*";
51
+ index += 1;
52
+ if (glob[index + 1] === "/")
53
+ index += 1;
54
+ }
55
+ else {
56
+ source += "[^/]*";
57
+ }
58
+ }
59
+ else if (char === "?") {
60
+ source += "[^/]";
61
+ }
62
+ else if (/[.+^${}()|[\]\\]/.test(char)) {
63
+ source += "\\" + char;
64
+ }
65
+ else {
66
+ source += char;
67
+ }
68
+ }
69
+ return new RegExp(source + "$", "i");
70
+ }
71
+ function compile(config) {
72
+ const key = JSON.stringify(config);
73
+ const cached = compileCache.get(key);
74
+ if (cached)
75
+ return cached;
76
+ const compiled = {
77
+ markers: config.denyContentMarkers.map(safeRegExp).filter(Boolean),
78
+ globs: config.denyGlobs.map(globToRegExp),
79
+ denyPaths: config.denyPaths.map((entry) => resolve(entry).replace(/\\/g, "/")),
80
+ };
81
+ compileCache.set(key, compiled);
82
+ if (compileCache.size > 8)
83
+ compileCache.delete(compileCache.keys().next().value);
84
+ return compiled;
85
+ }
86
+ function expandHome(target) {
87
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "";
88
+ if (!home)
89
+ return target;
90
+ if (target === "~")
91
+ return home;
92
+ if (target.startsWith("~/") || target.startsWith("~\\"))
93
+ return home + target.slice(1);
94
+ return target.replace(/^\$(?:HOME|\{HOME\})/i, home);
95
+ }
96
+ function normalizeTarget(target, cwd) {
97
+ return resolve(cwd, expandHome(target)).replace(/\\/g, "/");
98
+ }
99
+ function isInside(child, parent) {
100
+ const rel = relative(parent, child).replace(/\\/g, "/");
101
+ return rel === "" || (!rel.startsWith("..") && !rel.startsWith("/"));
102
+ }
103
+ function markerHit(payload, compiled) {
104
+ for (const marker of compiled.markers) {
105
+ if (marker.test(payload))
106
+ return marker.source;
107
+ }
108
+ return undefined;
109
+ }
110
+ function scanWindow(payload, maxBytes) {
111
+ if (Buffer.byteLength(payload, "utf8") <= maxBytes)
112
+ return payload;
113
+ const buffer = Buffer.from(payload, "utf8");
114
+ const half = Math.max(1, Math.floor(maxBytes / 2));
115
+ return `${buffer.subarray(0, half).toString("utf8")}\n${buffer.subarray(buffer.length - half).toString("utf8")}`;
116
+ }
117
+ function evaluateSubject(subject, cwd, config, compiled) {
118
+ const decide = (rule, reason) => ({
119
+ outlet: subject.outlet,
120
+ target: subject.target,
121
+ rule,
122
+ mode: config.mode,
123
+ wouldBlock: true,
124
+ code: "EGRESS_DENY",
125
+ reason,
126
+ });
127
+ if (subject.action === "payload") {
128
+ const payload = subject.payload ?? "";
129
+ if (!payload)
130
+ return undefined;
131
+ const overBudget = Buffer.byteLength(payload, "utf8") > config.maxScanBytes;
132
+ if (overBudget && subject.outbound)
133
+ return decide("scan_budget_exceeded", `payload exceeds the ${config.maxScanBytes}-byte egress scan budget`);
134
+ const marker = markerHit(scanWindow(payload, config.maxScanBytes), compiled);
135
+ if (marker)
136
+ return decide("content_marker", `payload matched denyContentMarkers (${marker})`);
137
+ return undefined;
138
+ }
139
+ const target = subject.target;
140
+ if (!target)
141
+ return undefined;
142
+ if (subject.action === "read" && isCriticalReadTarget(target))
143
+ return decide("critical_credential_read", "reading a critical private key or system credential file");
144
+ if (subject.action !== "read" && isSensitiveTarget(target))
145
+ return decide("sensitive_path", "writing or deleting a protected credential/session file");
146
+ if (subject.action !== "read" && isSharedStoragePath(target))
147
+ return decide("shared_storage", "writing to shared storage is treated as an egress surface");
148
+ const normalized = normalizeTarget(target, cwd);
149
+ for (const denyPath of compiled.denyPaths) {
150
+ if (isInside(normalized, denyPath))
151
+ return decide("config:deny_path", `path matches denyPaths entry ${denyPath}`);
152
+ }
153
+ for (const glob of compiled.globs) {
154
+ if (glob.test(normalized) || glob.test(basename(normalized)))
155
+ return decide("config:deny_glob", "path matches a denyGlobs entry");
156
+ }
157
+ return undefined;
158
+ }
159
+ function pathSubject(outlet, action, target) {
160
+ return typeof target === "string" && target ? { outlet, action, target } : undefined;
161
+ }
162
+ function patchTargets(patch) {
163
+ const subjects = [];
164
+ const filePattern = /^\*\*\* (Add|Update|Delete) File: (.+)$/gm;
165
+ for (const match of patch.matchAll(filePattern)) {
166
+ const action = match[1] === "Delete" ? "delete" : "write";
167
+ subjects.push({ outlet: "apply_patch.path", action, target: match[2].trim() });
168
+ }
169
+ const movePattern = /^\*\*\* Move to: (.+)$/gm;
170
+ for (const match of patch.matchAll(movePattern))
171
+ subjects.push({ outlet: "apply_patch.move", action: "write", target: match[1].trim() });
172
+ return subjects;
173
+ }
174
+ export function egressSubjects(call) {
175
+ const subjects = [];
176
+ const add = (subject) => {
177
+ if (subject)
178
+ subjects.push(subject);
179
+ };
180
+ switch (call.tool) {
181
+ case "read_file":
182
+ case "list_files":
183
+ case "stat_path":
184
+ case "grep":
185
+ add(pathSubject(`${call.tool}.path`, "read", typeof call.path === "string" ? call.path : "."));
186
+ break;
187
+ case "read_many":
188
+ if (Array.isArray(call.paths)) {
189
+ for (const path of call.paths)
190
+ add(pathSubject("read_many.paths", "read", path));
191
+ }
192
+ break;
193
+ case "write_file":
194
+ case "append_file":
195
+ add(pathSubject(`${call.tool}.path`, "write", call.path));
196
+ if (typeof call.content === "string")
197
+ subjects.push({ outlet: `${call.tool}.content`, action: "payload", payload: call.content, outbound: false });
198
+ break;
199
+ case "replace_in_file":
200
+ add(pathSubject("replace_in_file.path", "write", call.path));
201
+ if (typeof call.replace === "string")
202
+ subjects.push({ outlet: "replace_in_file.replace", action: "payload", payload: call.replace, outbound: false });
203
+ break;
204
+ case "delete_file":
205
+ add(pathSubject("delete_file.path", "delete", call.path));
206
+ break;
207
+ case "make_dir":
208
+ add(pathSubject("make_dir.path", "write", call.path));
209
+ break;
210
+ case "copy_file":
211
+ add(pathSubject("copy_file.from", "egress", call.from));
212
+ add(pathSubject("copy_file.to", "write", call.to));
213
+ break;
214
+ case "move_path":
215
+ add(pathSubject("move_path.from", "egress", call.from));
216
+ add(pathSubject("move_path.to", "write", call.to));
217
+ break;
218
+ case "apply_patch":
219
+ if (typeof call.patch === "string") {
220
+ subjects.push({ outlet: "apply_patch.patch", action: "payload", payload: call.patch, outbound: false });
221
+ subjects.push(...patchTargets(call.patch));
222
+ }
223
+ break;
224
+ case "shell":
225
+ if (typeof call.command === "string")
226
+ subjects.push({ outlet: "shell.command", action: "payload", payload: call.command, outbound: true });
227
+ break;
228
+ default:
229
+ break;
230
+ }
231
+ return subjects;
232
+ }
233
+ export function evaluateEgressCall(call, cwd, config) {
234
+ if (config.mode === "off")
235
+ return { action: "allow" };
236
+ const compiled = compile(config);
237
+ for (const subject of egressSubjects(call)) {
238
+ const decision = evaluateSubject(subject, cwd, config, compiled);
239
+ if (!decision)
240
+ continue;
241
+ const enforce = config.enabled && config.mode === "block";
242
+ return { action: enforce ? "deny" : "audit", decision };
243
+ }
244
+ return { action: "allow" };
245
+ }
246
+ export function evaluateEgressOutput(tool, output, config) {
247
+ if (!config.scanToolOutput || config.mode === "off" || !output)
248
+ return { action: "allow" };
249
+ const compiled = compile(config);
250
+ const decision = evaluateSubject({
251
+ outlet: `${tool}.output`,
252
+ action: "payload",
253
+ payload: output,
254
+ outbound: true,
255
+ }, process.cwd(), config, compiled);
256
+ if (!decision)
257
+ return { action: "allow" };
258
+ const enforce = config.enabled && config.mode === "block";
259
+ return { action: enforce ? "deny" : "audit", decision };
260
+ }
261
+ export function egressAudit(decision) {
262
+ return {
263
+ outlet: decision.outlet,
264
+ target: decision.target,
265
+ rule: decision.rule,
266
+ mode: decision.mode === "block" ? "block" : "audit",
267
+ wouldBlock: decision.wouldBlock,
268
+ code: decision.code,
269
+ reason: decision.reason,
270
+ };
271
+ }
272
+ export function formatEgressDenial(decision) {
273
+ const target = decision.target ? ` target=${JSON.stringify(decision.target)}` : "";
274
+ return `[egress] DENY rule=${decision.rule}${target} reason=${decision.reason}\n` +
275
+ "This decision cannot be overridden by FASTCODE_ALLOW_DANGEROUS, trust, sharedDirs, or session/always approval.";
276
+ }
277
+ export function formatEgressAudit(audit) {
278
+ const target = audit.target ? ` target=${JSON.stringify(audit.target)}` : "";
279
+ return `[egress] AUDIT rule=${audit.rule}${target} would_block=${audit.wouldBlock} reason=${audit.reason}`;
280
+ }
@@ -0,0 +1,22 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ export declare const KILL_GRACE_MS = 1000;
3
+ export interface TerminateOptions {
4
+ /** Milliseconds to wait after SIGTERM before escalating to SIGKILL. */
5
+ graceMs?: number;
6
+ }
7
+ /**
8
+ * Send a single signal to the process group (falling back to the pid).
9
+ * Kept for callers that need one-shot signalling; shell sessions should use
10
+ * terminateProcessTree so SIGKILL escalation is always available.
11
+ */
12
+ export declare function killProcessTree(child: ChildProcess | undefined, signal?: NodeJS.Signals): void;
13
+ /**
14
+ * Terminate a spawned process group with a SIGTERM -> grace -> SIGKILL flow.
15
+ *
16
+ * Boundary: this only guarantees the spawned process group is killed. A
17
+ * descendant that calls setsid() leaves the group and may escape; we do not
18
+ * scan for ppid=1 processes because that risks killing unrelated background
19
+ * jobs on Termux. Callers should report
20
+ * "process group killed; setsid descendants may escape".
21
+ */
22
+ export declare function terminateProcessTree(child: ChildProcess | undefined, options?: TerminateOptions): void;
@@ -0,0 +1,102 @@
1
+ import { spawn } from "node:child_process";
2
+ export const KILL_GRACE_MS = 1000;
3
+ const pendingKills = new WeakMap();
4
+ function sendSignal(pid, signal) {
5
+ try {
6
+ process.kill(-pid, signal);
7
+ return "sent";
8
+ }
9
+ catch (err) {
10
+ if (err?.code !== "ESRCH")
11
+ return "sent";
12
+ try {
13
+ process.kill(pid, signal);
14
+ return "sent";
15
+ }
16
+ catch (err2) {
17
+ return err2?.code === "ESRCH" ? "gone" : "sent";
18
+ }
19
+ }
20
+ }
21
+ function isAlive(pid) {
22
+ try {
23
+ process.kill(-pid, 0);
24
+ return true;
25
+ }
26
+ catch (err) {
27
+ if (err?.code !== "ESRCH")
28
+ return true;
29
+ try {
30
+ process.kill(pid, 0);
31
+ return true;
32
+ }
33
+ catch (err2) {
34
+ return err2?.code !== "ESRCH";
35
+ }
36
+ }
37
+ }
38
+ function clearPending(child) {
39
+ const timer = pendingKills.get(child);
40
+ if (timer) {
41
+ clearTimeout(timer);
42
+ pendingKills.delete(child);
43
+ }
44
+ }
45
+ /**
46
+ * Send a single signal to the process group (falling back to the pid).
47
+ * Kept for callers that need one-shot signalling; shell sessions should use
48
+ * terminateProcessTree so SIGKILL escalation is always available.
49
+ */
50
+ export function killProcessTree(child, signal = "SIGTERM") {
51
+ if (!child || typeof child.pid !== "number")
52
+ return;
53
+ if (process.platform === "win32") {
54
+ try {
55
+ const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
56
+ killer.unref();
57
+ }
58
+ catch {
59
+ try {
60
+ child.kill();
61
+ }
62
+ catch {
63
+ // The process may already be gone.
64
+ }
65
+ }
66
+ return;
67
+ }
68
+ sendSignal(child.pid, signal);
69
+ }
70
+ /**
71
+ * Terminate a spawned process group with a SIGTERM -> grace -> SIGKILL flow.
72
+ *
73
+ * Boundary: this only guarantees the spawned process group is killed. A
74
+ * descendant that calls setsid() leaves the group and may escape; we do not
75
+ * scan for ppid=1 processes because that risks killing unrelated background
76
+ * jobs on Termux. Callers should report
77
+ * "process group killed; setsid descendants may escape".
78
+ */
79
+ export function terminateProcessTree(child, options = {}) {
80
+ if (!child || typeof child.pid !== "number")
81
+ return;
82
+ if (process.platform === "win32") {
83
+ killProcessTree(child, "SIGTERM");
84
+ return;
85
+ }
86
+ const pid = child.pid;
87
+ clearPending(child);
88
+ if (sendSignal(pid, "SIGTERM") === "gone")
89
+ return;
90
+ const graceMs = Math.max(0, Math.floor(options.graceMs ?? KILL_GRACE_MS));
91
+ const timer = setTimeout(() => {
92
+ pendingKills.delete(child);
93
+ if (child.exitCode !== null || child.signalCode !== null)
94
+ return;
95
+ if (!isAlive(pid))
96
+ return;
97
+ sendSignal(pid, "SIGKILL");
98
+ }, graceMs);
99
+ timer.unref?.();
100
+ pendingKills.set(child, timer);
101
+ child.once("exit", () => clearPending(child));
102
+ }
@@ -0,0 +1,6 @@
1
+ export type ToolProtocol = "legacy" | "native";
2
+ export type ToolCapability = "native" | "pseudo";
3
+ export declare function toolCapabilityKey(providerId: string, model: string): string;
4
+ export declare function resolveToolProtocol(config: any, providerId: string, model: string, env?: NodeJS.ProcessEnv): ToolProtocol;
5
+ export declare function withToolCapability(config: any, providerId: string, model: string, capability: ToolCapability): any;
6
+ export declare function isNativeUnsupportedError(message: string): boolean;
@@ -0,0 +1,51 @@
1
+ import { getModelMeta } from "../lib/model-meta.js";
2
+ const NATIVE_PROVIDERS = new Set([
3
+ "openai",
4
+ "openrouter",
5
+ "deepseek",
6
+ "groq",
7
+ "mistral",
8
+ "xai",
9
+ "moonshot",
10
+ "zhipu",
11
+ "qwen",
12
+ "ollama",
13
+ ]);
14
+ export function toolCapabilityKey(providerId, model) {
15
+ return `${providerId}::${model}`;
16
+ }
17
+ export function resolveToolProtocol(config, providerId, model, env = process.env) {
18
+ const mode = String(env["FASTCODE_TOOL_PROTOCOL"] ?? "legacy").trim().toLowerCase();
19
+ if (mode === "legacy")
20
+ return "legacy";
21
+ if (mode === "native")
22
+ return "native";
23
+ if (mode !== "auto")
24
+ return "legacy";
25
+ const cached = config?.capabilities?.toolCalling?.[toolCapabilityKey(providerId, model)];
26
+ if (cached === "pseudo")
27
+ return "legacy";
28
+ if (cached === "native")
29
+ return "native";
30
+ if (!NATIVE_PROVIDERS.has(providerId))
31
+ return "legacy";
32
+ return getModelMeta(providerId, model).supportsTools ? "native" : "legacy";
33
+ }
34
+ export function withToolCapability(config, providerId, model, capability) {
35
+ const key = toolCapabilityKey(providerId, model);
36
+ return {
37
+ ...config,
38
+ capabilities: {
39
+ ...(config?.capabilities ?? {}),
40
+ toolCalling: {
41
+ ...(config?.capabilities?.toolCalling ?? {}),
42
+ [key]: capability,
43
+ },
44
+ },
45
+ };
46
+ }
47
+ export function isNativeUnsupportedError(message) {
48
+ return /\b400\b/.test(message)
49
+ && /tool|function/i.test(message)
50
+ && /not supported|unsupported|unrecognized|unknown|invalid|does not support/i.test(message);
51
+ }