@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.
- package/LICENSE +4 -0
- package/README.md +67 -16
- package/dist/agent/approval-json.d.ts +43 -0
- package/dist/agent/approval-json.js +69 -0
- package/dist/agent/approval-native.d.ts +3 -0
- package/dist/agent/approval-native.js +165 -0
- package/dist/agent/approval-store.d.ts +16 -0
- package/dist/agent/approval-store.js +21 -0
- package/dist/agent/approval.d.ts +83 -0
- package/dist/agent/approval.js +42 -0
- package/dist/agent/compaction.d.ts +41 -0
- package/dist/agent/compaction.js +270 -0
- package/dist/agent/context.d.ts +29 -0
- package/dist/agent/context.js +62 -0
- package/dist/agent/guards.d.ts +24 -16
- package/dist/agent/guards.js +1167 -18
- package/dist/agent/ignore.d.ts +11 -0
- package/dist/agent/ignore.js +129 -0
- package/dist/agent/policy/defaults.d.ts +16 -0
- package/dist/agent/policy/defaults.js +17 -0
- package/dist/agent/policy/egress.d.ts +32 -0
- package/dist/agent/policy/egress.js +280 -0
- package/dist/agent/process-tree.d.ts +22 -0
- package/dist/agent/process-tree.js +102 -0
- package/dist/agent/protocol-mode.d.ts +6 -0
- package/dist/agent/protocol-mode.js +51 -0
- package/dist/agent/protocol.d.ts +20 -1
- package/dist/agent/protocol.js +169 -39
- package/dist/agent/registry.d.ts +19 -0
- package/dist/agent/registry.js +247 -0
- package/dist/agent/runner.d.ts +127 -0
- package/dist/agent/runner.js +391 -0
- package/dist/agent/shell-ast.d.ts +11 -0
- package/dist/agent/shell-ast.js +204 -0
- package/dist/agent/shell-session.d.ts +51 -0
- package/dist/agent/shell-session.js +611 -0
- package/dist/agent/stream.d.ts +29 -0
- package/dist/agent/stream.js +103 -0
- package/dist/agent/tool-schemas.d.ts +2 -0
- package/dist/agent/tool-schemas.js +82 -0
- package/dist/agent/tools/append_file.d.ts +2 -0
- package/dist/agent/tools/append_file.js +28 -0
- package/dist/agent/tools/apply_patch.d.ts +10 -0
- package/dist/agent/tools/apply_patch.js +92 -0
- package/dist/agent/tools/copy_file.d.ts +2 -0
- package/dist/agent/tools/copy_file.js +53 -0
- package/dist/agent/tools/delete_file.d.ts +2 -0
- package/dist/agent/tools/delete_file.js +43 -0
- package/dist/agent/tools/grep.d.ts +2 -0
- package/dist/agent/tools/grep.js +54 -0
- package/dist/agent/tools/index.d.ts +2 -0
- package/dist/agent/tools/index.js +32 -0
- package/dist/agent/tools/list_files.d.ts +2 -0
- package/dist/agent/tools/list_files.js +17 -0
- package/dist/agent/tools/make_dir.d.ts +2 -0
- package/dist/agent/tools/make_dir.js +26 -0
- package/dist/agent/tools/move_path.d.ts +9 -0
- package/dist/agent/tools/move_path.js +73 -0
- package/dist/agent/tools/paths.d.ts +7 -0
- package/dist/agent/tools/paths.js +84 -0
- package/dist/agent/tools/pwd.d.ts +2 -0
- package/dist/agent/tools/pwd.js +10 -0
- package/dist/agent/tools/read_file.d.ts +2 -0
- package/dist/agent/tools/read_file.js +81 -0
- package/dist/agent/tools/read_many.d.ts +2 -0
- package/dist/agent/tools/read_many.js +43 -0
- package/dist/agent/tools/readers.d.ts +58 -0
- package/dist/agent/tools/readers.js +426 -0
- package/dist/agent/tools/replace_in_file.d.ts +2 -0
- package/dist/agent/tools/replace_in_file.js +63 -0
- package/dist/agent/tools/shell.d.ts +2 -0
- package/dist/agent/tools/shell.js +48 -0
- package/dist/agent/tools/stat_path.d.ts +2 -0
- package/dist/agent/tools/stat_path.js +31 -0
- package/dist/agent/tools/types.d.ts +65 -0
- package/dist/agent/tools/types.js +1 -0
- package/dist/agent/tools/validate.d.ts +19 -0
- package/dist/agent/tools/validate.js +92 -0
- package/dist/agent/tools/write_file.d.ts +2 -0
- package/dist/agent/tools/write_file.js +28 -0
- package/dist/agent/tools.d.ts +3 -1
- package/dist/agent/tools.js +2 -377
- package/dist/app.d.ts +2 -1
- package/dist/app.js +846 -173
- package/dist/cli-main.js +363 -0
- package/dist/cli.js +444 -333
- package/dist/commands/about.js +1 -1
- package/dist/commands/attach.d.ts +9 -0
- package/dist/commands/attach.js +15 -0
- package/dist/commands/bg.d.ts +7 -0
- package/dist/commands/bg.js +9 -0
- package/dist/commands/cancel.d.ts +10 -0
- package/dist/commands/cancel.js +10 -0
- package/dist/commands/clear.d.ts +1 -0
- package/dist/commands/clear.js +1 -1
- package/dist/commands/compact.d.ts +22 -0
- package/dist/commands/compact.js +34 -0
- package/dist/commands/context.d.ts +7 -0
- package/dist/commands/context.js +25 -0
- package/dist/commands/edit.d.ts +10 -0
- package/dist/commands/edit.js +36 -0
- package/dist/commands/effort.d.ts +25 -0
- package/dist/commands/effort.js +48 -0
- package/dist/commands/expand.d.ts +10 -0
- package/dist/commands/expand.js +8 -0
- package/dist/commands/index.js +24 -0
- package/dist/commands/jobs.d.ts +7 -0
- package/dist/commands/jobs.js +17 -0
- package/dist/commands/kill.d.ts +7 -0
- package/dist/commands/kill.js +13 -0
- package/dist/commands/login.d.ts +6 -4
- package/dist/commands/login.js +24 -22
- package/dist/commands/logout.js +8 -16
- package/dist/commands/model.d.ts +1 -0
- package/dist/commands/model.js +10 -22
- package/dist/commands/provider.d.ts +1 -2
- package/dist/commands/provider.js +4 -11
- package/dist/commands/skills.js +34 -4
- package/dist/commands/steer.d.ts +11 -0
- package/dist/commands/steer.js +13 -0
- package/dist/commands/threads.d.ts +9 -0
- package/dist/commands/threads.js +26 -0
- package/dist/commands/whoami.js +6 -5
- package/dist/config/store.d.ts +53 -12
- package/dist/config/store.js +163 -73
- package/dist/config/trust.d.ts +30 -8
- package/dist/config/trust.js +139 -14
- package/dist/headless.d.ts +7 -0
- package/dist/headless.js +213 -0
- package/dist/index.js +19 -3
- package/dist/lib/debug.d.ts +2 -0
- package/dist/lib/debug.js +22 -0
- package/dist/lib/diagnostics.d.ts +48 -4
- package/dist/lib/diagnostics.js +90 -8
- package/dist/lib/highlight-subset.d.ts +21 -0
- package/dist/lib/highlight-subset.js +88 -0
- package/dist/lib/http.d.ts +11 -1
- package/dist/lib/http.js +62 -4
- package/dist/lib/lifecycle.d.ts +5 -0
- package/dist/lib/lifecycle.js +62 -0
- package/dist/lib/markdown.js +22 -4
- package/dist/lib/model-meta.d.ts +19 -0
- package/dist/lib/model-meta.js +113 -0
- package/dist/lib/native-tools.d.ts +12 -0
- package/dist/lib/native-tools.js +21 -0
- package/dist/lib/net.d.ts +33 -0
- package/dist/lib/net.js +168 -0
- package/dist/patch/apply.d.ts +15 -0
- package/dist/patch/apply.js +172 -0
- package/dist/patch/parse.d.ts +29 -0
- package/dist/patch/parse.js +112 -0
- package/dist/patch/plan.d.ts +49 -0
- package/dist/patch/plan.js +273 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +12 -0
- package/dist/providers/custom.d.ts +9 -0
- package/dist/providers/deepseek.d.ts +10 -0
- package/dist/providers/deepseek.js +3 -2
- package/dist/providers/google.d.ts +9 -0
- package/dist/providers/groq.d.ts +10 -0
- package/dist/providers/groq.js +2 -1
- package/dist/providers/index.d.ts +10 -201
- package/dist/providers/index.js +30 -0
- package/dist/providers/mistral.d.ts +10 -0
- package/dist/providers/mistral.js +2 -1
- package/dist/providers/moonshot.d.ts +10 -0
- package/dist/providers/moonshot.js +2 -1
- package/dist/providers/ollama.d.ts +9 -0
- package/dist/providers/openai.d.ts +10 -0
- package/dist/providers/openai.js +2 -1
- package/dist/providers/openaiCompatible.d.ts +33 -2
- package/dist/providers/openaiCompatible.js +78 -16
- package/dist/providers/openrouter.d.ts +10 -0
- package/dist/providers/openrouter.js +2 -1
- package/dist/providers/qwen.d.ts +10 -0
- package/dist/providers/qwen.js +2 -1
- package/dist/providers/spec.d.ts +12 -0
- package/dist/providers/spec.js +29 -0
- package/dist/providers/xai.d.ts +10 -0
- package/dist/providers/xai.js +2 -1
- package/dist/providers/zhipu.d.ts +10 -0
- package/dist/providers/zhipu.js +2 -1
- package/dist/session/log.d.ts +49 -14
- package/dist/session/log.js +236 -11
- package/dist/session/projection.d.ts +76 -0
- package/dist/session/projection.js +526 -0
- package/dist/skills/manager.d.ts +15 -0
- package/dist/skills/manager.js +135 -18
- package/dist/types.d.ts +75 -1
- package/dist/ui/Message.d.ts +1 -7
- package/dist/ui/Message.js +13 -2
- package/dist/ui/Picker.d.ts +8 -0
- package/dist/ui/Picker.js +24 -0
- package/dist/ui/Prompt.d.ts +1 -2
- package/dist/ui/Prompt.js +2 -2
- package/dist/ui/Spinner.js +11 -5
- package/dist/ui/StatusBar.d.ts +2 -1
- package/dist/ui/StatusBar.js +6 -5
- package/package.json +3 -2
- package/src/agent/approval-json.ts +103 -0
- package/src/agent/approval-native.ts +168 -0
- package/src/agent/approval-store.ts +38 -0
- package/src/agent/approval.ts +94 -0
- package/src/agent/compaction.ts +321 -0
- package/src/agent/context.ts +97 -0
- package/src/agent/guards.ts +1239 -19
- package/src/agent/ignore.ts +147 -0
- package/src/agent/policy/defaults.ts +34 -0
- package/src/agent/policy/egress.ts +335 -0
- package/src/agent/process-tree.ts +114 -0
- package/src/agent/protocol-mode.ts +59 -0
- package/src/agent/protocol.ts +191 -40
- package/src/agent/registry.ts +270 -0
- package/src/agent/runner.ts +486 -0
- package/src/agent/shell-ast.ts +227 -0
- package/src/agent/shell-session.ts +673 -0
- package/src/agent/stream.ts +143 -0
- package/src/agent/tool-schemas.ts +86 -0
- package/src/agent/tools/append_file.ts +30 -0
- package/src/agent/tools/apply_patch.ts +95 -0
- package/src/agent/tools/copy_file.ts +56 -0
- package/src/agent/tools/delete_file.ts +45 -0
- package/src/agent/tools/grep.ts +57 -0
- package/src/agent/tools/index.ts +34 -0
- package/src/agent/tools/list_files.ts +19 -0
- package/src/agent/tools/make_dir.ts +28 -0
- package/src/agent/tools/move_path.ts +83 -0
- package/src/agent/tools/paths.ts +92 -0
- package/src/agent/tools/pwd.ts +12 -0
- package/src/agent/tools/read_file.ts +84 -0
- package/src/agent/tools/read_many.ts +45 -0
- package/src/agent/tools/readers.ts +491 -0
- package/src/agent/tools/replace_in_file.ts +66 -0
- package/src/agent/tools/shell.ts +53 -0
- package/src/agent/tools/stat_path.ts +33 -0
- package/src/agent/tools/types.ts +84 -0
- package/src/agent/tools/validate.ts +104 -0
- package/src/agent/tools/write_file.ts +30 -0
- package/src/agent/tools.ts +3 -377
- package/src/app.ts +853 -170
- package/src/{cli.ts → cli-main.ts} +64 -9
- package/src/commands/about.ts +1 -1
- package/src/commands/attach.ts +16 -0
- package/src/commands/bg.ts +9 -0
- package/src/commands/cancel.ts +10 -0
- package/src/commands/clear.ts +1 -1
- package/src/commands/compact.ts +35 -0
- package/src/commands/context.ts +26 -0
- package/src/commands/edit.ts +37 -0
- package/src/commands/effort.ts +49 -0
- package/src/commands/expand.ts +8 -0
- package/src/commands/index.ts +24 -0
- package/src/commands/jobs.ts +18 -0
- package/src/commands/kill.ts +14 -0
- package/src/commands/login.ts +24 -22
- package/src/commands/logout.ts +8 -16
- package/src/commands/model.ts +10 -22
- package/src/commands/provider.ts +4 -11
- package/src/commands/skills.ts +34 -4
- package/src/commands/steer.ts +13 -0
- package/src/commands/threads.ts +28 -0
- package/src/commands/whoami.ts +6 -5
- package/src/config/store.ts +168 -77
- package/src/config/trust.ts +181 -22
- package/src/headless.ts +225 -0
- package/src/index.ts +19 -3
- package/src/lib/debug.ts +24 -0
- package/src/lib/diagnostics.ts +90 -8
- package/src/lib/highlight-subset.ts +93 -0
- package/src/lib/http.ts +66 -4
- package/src/lib/lifecycle.ts +69 -0
- package/src/lib/markdown.ts +22 -4
- package/src/lib/model-meta.ts +138 -0
- package/src/lib/native-tools.ts +32 -0
- package/src/lib/net.ts +204 -0
- package/src/patch/apply.ts +194 -0
- package/src/patch/parse.ts +131 -0
- package/src/patch/plan.ts +317 -0
- package/src/providers/anthropic.ts +13 -1
- package/src/providers/deepseek.ts +3 -2
- package/src/providers/groq.ts +2 -1
- package/src/providers/index.ts +32 -1
- package/src/providers/mistral.ts +2 -1
- package/src/providers/moonshot.ts +2 -1
- package/src/providers/openai.ts +2 -1
- package/src/providers/openaiCompatible.ts +104 -19
- package/src/providers/openrouter.ts +2 -1
- package/src/providers/qwen.ts +2 -1
- package/src/providers/spec.ts +42 -0
- package/src/providers/xai.ts +2 -1
- package/src/providers/zhipu.ts +2 -1
- package/src/session/log.ts +311 -29
- package/src/session/projection.ts +621 -0
- package/src/skills/manager.ts +140 -18
- package/src/types/highlight-shims.d.ts +14 -0
- package/src/types.ts +79 -2
- package/src/ui/Message.ts +13 -2
- package/src/ui/Picker.ts +24 -0
- package/src/ui/Prompt.ts +2 -2
- package/src/ui/Spinner.ts +11 -5
- package/src/ui/StatusBar.ts +6 -5
- package/tsconfig.json +1 -0
- /package/dist/{cli.d.ts → cli-main.d.ts} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { debugLog } from "../../lib/debug.js";
|
|
2
|
+
import { sandboxPath } from "./paths.js";
|
|
3
|
+
export function invalid(output, code = "INVALID_ARGS", hint) {
|
|
4
|
+
return { ok: false, output, code, ...(hint ? { hint } : {}) };
|
|
5
|
+
}
|
|
6
|
+
export function requiredString(call, key) {
|
|
7
|
+
const value = call[key];
|
|
8
|
+
if (typeof value !== "string")
|
|
9
|
+
throw new Error(`${key} must be a string`);
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
export function stringArray(call, key) {
|
|
13
|
+
const value = call[key];
|
|
14
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
15
|
+
throw new Error(`${key} must be an array of strings`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
export function numberOption(value, fallback) {
|
|
20
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
21
|
+
}
|
|
22
|
+
export function optionalNumber(call, key) {
|
|
23
|
+
const value = call[key];
|
|
24
|
+
if (value === undefined || value === null)
|
|
25
|
+
return undefined;
|
|
26
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
27
|
+
throw new Error(`${key} must be a finite number`);
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
export function clampInt(value, fallback, min, max) {
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
return fallback;
|
|
33
|
+
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
34
|
+
}
|
|
35
|
+
export function booleanOption(value, fallback = false) {
|
|
36
|
+
return typeof value === "boolean" ? value : fallback;
|
|
37
|
+
}
|
|
38
|
+
export const MAX_TIMEOUT_MS = 2147483647;
|
|
39
|
+
/**
|
|
40
|
+
* Normalize a tool `timeoutMs` value.
|
|
41
|
+
*
|
|
42
|
+
* Only `null`/`undefined` mean "not specified". Any other non-number, NaN,
|
|
43
|
+
* Infinity, or negative value is a caller error and must be surfaced instead
|
|
44
|
+
* of silently falling back. Large finite values are clamped to the
|
|
45
|
+
* `setTimeout` ceiling (2^31-1) rather than overflowing to 1ms.
|
|
46
|
+
*/
|
|
47
|
+
export function normalizeTimeoutMs(value, fallback = 120000) {
|
|
48
|
+
if (value === undefined || value === null)
|
|
49
|
+
return fallback;
|
|
50
|
+
if (typeof value !== "number") {
|
|
51
|
+
throw new TypeError(`timeoutMs must be a finite number; received ${describeValue(value)}`);
|
|
52
|
+
}
|
|
53
|
+
if (!Number.isFinite(value)) {
|
|
54
|
+
throw new TypeError(`timeoutMs must be a finite number; received ${describeValue(value)}`);
|
|
55
|
+
}
|
|
56
|
+
if (value < 0) {
|
|
57
|
+
throw new TypeError(`timeoutMs must not be negative; received ${describeValue(value)}`);
|
|
58
|
+
}
|
|
59
|
+
const floored = Math.floor(value);
|
|
60
|
+
const clamped = Math.min(floored, MAX_TIMEOUT_MS);
|
|
61
|
+
if (clamped !== floored)
|
|
62
|
+
debugLog("timeout", `timeoutMs ${value} clamped to ${clamped}`);
|
|
63
|
+
return clamped;
|
|
64
|
+
}
|
|
65
|
+
function describeValue(value) {
|
|
66
|
+
if (typeof value === "string")
|
|
67
|
+
return `string ${JSON.stringify(value)}`;
|
|
68
|
+
if (typeof value === "number")
|
|
69
|
+
return String(value);
|
|
70
|
+
if (typeof value === "bigint")
|
|
71
|
+
return `bigint ${value}n`;
|
|
72
|
+
if (typeof value === "object" && value !== null) {
|
|
73
|
+
try {
|
|
74
|
+
return `object ${JSON.stringify(value)}`;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return "object";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return String(value);
|
|
81
|
+
}
|
|
82
|
+
export function preparePath(cwd, raw) {
|
|
83
|
+
try {
|
|
84
|
+
sandboxPath(cwd, raw);
|
|
85
|
+
return { ok: true };
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const message = err?.message ?? String(err);
|
|
89
|
+
const code = /outside sandbox/i.test(message) ? "SANDBOX_DENY" : "INVALID_ARGS";
|
|
90
|
+
return invalid(message, code, /outside sandbox/i.test(message) ? "Use a path inside the project or an allowed root." : undefined);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { resolvePath } from "./paths.js";
|
|
5
|
+
import { invalid, preparePath, requiredString } from "./validate.js";
|
|
6
|
+
export const writeFileTool = {
|
|
7
|
+
name: "write_file",
|
|
8
|
+
requiresTrust: true,
|
|
9
|
+
stateScope: "turn",
|
|
10
|
+
revalidate: ["trust", "sandboxRoots"],
|
|
11
|
+
prepare(call, ctx) {
|
|
12
|
+
try {
|
|
13
|
+
requiredString(call, "path");
|
|
14
|
+
requiredString(call, "content");
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
return invalid(err.message);
|
|
18
|
+
}
|
|
19
|
+
return preparePath(ctx.cwd, requiredString(call, "path"));
|
|
20
|
+
},
|
|
21
|
+
async execute(call, ctx) {
|
|
22
|
+
const file = resolvePath(ctx.cwd, call);
|
|
23
|
+
const existed = existsSync(file);
|
|
24
|
+
await mkdir(dirname(file), { recursive: true });
|
|
25
|
+
await writeFile(file, requiredString(call, "content"), "utf8");
|
|
26
|
+
return { ok: true, output: `wrote ${file}`, sideEffects: existed ? { touched: [file] } : { created: [file] } };
|
|
27
|
+
},
|
|
28
|
+
};
|
package/dist/agent/tools.d.ts
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { assertToolContract, registry, runAgentTool, ToolRegistry } from "./registry.js";
|
|
2
|
+
export { TOOLS } from "./tools/index.js";
|
|
3
|
+
export type { RevalidateKey, SideEffects, StateScope, Tool, ToolCall, ToolCtx, ToolResult } from "./tools/types.js";
|
package/dist/agent/tools.js
CHANGED
|
@@ -1,377 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { existsSync, realpathSync } from "node:fs";
|
|
4
|
-
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
|
-
import { detectPlatform } from "../lib/platform.js";
|
|
6
|
-
import { evaluateShellCommand } from "./guards.js";
|
|
7
|
-
import { createOutputBuffer } from "./limits.js";
|
|
8
|
-
export async function runAgentTool(call, cwd = process.cwd()) {
|
|
9
|
-
const sandbox = resolve(cwd);
|
|
10
|
-
try {
|
|
11
|
-
switch (call.tool) {
|
|
12
|
-
case "shell":
|
|
13
|
-
return await runShell(requiredString(call, "command"), sandbox, numberOption(call.timeoutMs, 120000));
|
|
14
|
-
case "pwd":
|
|
15
|
-
return { ok: true, output: sandbox };
|
|
16
|
-
case "read_file":
|
|
17
|
-
return { ok: true, output: await readFile(resolvePath(sandbox, call), "utf8") };
|
|
18
|
-
case "read_many":
|
|
19
|
-
return { ok: true, output: await readMany(sandbox, stringArray(call, "paths")) };
|
|
20
|
-
case "write_file": {
|
|
21
|
-
const file = resolvePath(sandbox, call);
|
|
22
|
-
await mkdir(dirname(file), { recursive: true });
|
|
23
|
-
await writeFile(file, requiredString(call, "content"), "utf8");
|
|
24
|
-
return { ok: true, output: `wrote ${file}` };
|
|
25
|
-
}
|
|
26
|
-
case "append_file": {
|
|
27
|
-
const file = resolvePath(sandbox, call);
|
|
28
|
-
await mkdir(dirname(file), { recursive: true });
|
|
29
|
-
await appendFile(file, requiredString(call, "content"), "utf8");
|
|
30
|
-
return { ok: true, output: `appended ${file}` };
|
|
31
|
-
}
|
|
32
|
-
case "apply_patch":
|
|
33
|
-
return await applyPatch(sandbox, requiredString(call, "patch"));
|
|
34
|
-
case "copy_file": {
|
|
35
|
-
const from = sandboxPath(sandbox, requiredString(call, "from"));
|
|
36
|
-
const to = sandboxPath(sandbox, requiredString(call, "to"));
|
|
37
|
-
await mkdir(dirname(to), { recursive: true });
|
|
38
|
-
await copyFile(from, to);
|
|
39
|
-
return { ok: true, output: `copied ${from} to ${to}` };
|
|
40
|
-
}
|
|
41
|
-
case "delete_file": {
|
|
42
|
-
const file = resolvePath(sandbox, call);
|
|
43
|
-
await rm(file, { recursive: true, force: true });
|
|
44
|
-
return { ok: true, output: `deleted ${file}` };
|
|
45
|
-
}
|
|
46
|
-
case "list_files":
|
|
47
|
-
return { ok: true, output: await listFiles(sandbox, resolvePath(sandbox, call, "."), numberOption(call.maxDepth, 2)) };
|
|
48
|
-
case "make_dir": {
|
|
49
|
-
const dir = resolvePath(sandbox, call);
|
|
50
|
-
await mkdir(dir, { recursive: true });
|
|
51
|
-
return { ok: true, output: `created ${dir}` };
|
|
52
|
-
}
|
|
53
|
-
case "move_path": {
|
|
54
|
-
const from = sandboxPath(sandbox, requiredString(call, "from"));
|
|
55
|
-
const to = sandboxPath(sandbox, requiredString(call, "to"));
|
|
56
|
-
await mkdir(dirname(to), { recursive: true });
|
|
57
|
-
await rename(from, to);
|
|
58
|
-
return { ok: true, output: `moved ${from} to ${to}` };
|
|
59
|
-
}
|
|
60
|
-
case "grep":
|
|
61
|
-
return {
|
|
62
|
-
ok: true,
|
|
63
|
-
output: await grepFiles(sandbox, resolvePath(sandbox, call, "."), requiredString(call, "pattern"), numberOption(call.maxDepth, 4)),
|
|
64
|
-
};
|
|
65
|
-
case "replace_in_file": {
|
|
66
|
-
const file = resolvePath(sandbox, call);
|
|
67
|
-
const find = requiredString(call, "find");
|
|
68
|
-
const replacement = requiredString(call, "replace");
|
|
69
|
-
const source = await readFile(file, "utf8");
|
|
70
|
-
if (!source.includes(find))
|
|
71
|
-
return { ok: false, output: `text not found in ${file}` };
|
|
72
|
-
await writeFile(file, source.split(find).join(replacement), "utf8");
|
|
73
|
-
return { ok: true, output: `replaced text in ${file}` };
|
|
74
|
-
}
|
|
75
|
-
case "stat_path": {
|
|
76
|
-
const file = resolvePath(sandbox, call);
|
|
77
|
-
const info = await stat(file);
|
|
78
|
-
return {
|
|
79
|
-
ok: true,
|
|
80
|
-
output: JSON.stringify({
|
|
81
|
-
path: file,
|
|
82
|
-
type: info.isDirectory() ? "dir" : info.isFile() ? "file" : "other",
|
|
83
|
-
size: info.size,
|
|
84
|
-
modified: info.mtime.toISOString(),
|
|
85
|
-
}, null, 2),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
default:
|
|
89
|
-
return { ok: false, output: `unknown tool: ${String(call.tool)}` };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
return { ok: false, output: err.message };
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async function runShell(command, cwd, timeoutMs) {
|
|
97
|
-
const verdict = evaluateShellCommand(command);
|
|
98
|
-
if (!verdict.allow)
|
|
99
|
-
return { ok: false, output: verdict.output };
|
|
100
|
-
return await new Promise((resolvePromise) => {
|
|
101
|
-
const child = spawn(command, {
|
|
102
|
-
cwd,
|
|
103
|
-
shell: true,
|
|
104
|
-
env: { ...process.env, FASTCODE_SANDBOX: "1", FASTCODE_SANDBOX_ROOT: cwd },
|
|
105
|
-
windowsHide: true,
|
|
106
|
-
});
|
|
107
|
-
const timeout = setTimeout(() => {
|
|
108
|
-
child.kill("SIGTERM");
|
|
109
|
-
resolvePromise({ ok: false, output: `command timed out after ${timeoutMs}ms\n${output.trimEnd()}` });
|
|
110
|
-
}, timeoutMs);
|
|
111
|
-
const output = createOutputBuffer(undefined, "tail");
|
|
112
|
-
const collect = (chunk) => {
|
|
113
|
-
output.push(chunk.toString("utf8"));
|
|
114
|
-
};
|
|
115
|
-
child.stdout.on("data", collect);
|
|
116
|
-
child.stderr.on("data", collect);
|
|
117
|
-
child.on("error", (err) => {
|
|
118
|
-
clearTimeout(timeout);
|
|
119
|
-
resolvePromise({ ok: false, output: err.message });
|
|
120
|
-
});
|
|
121
|
-
child.on("close", (code) => {
|
|
122
|
-
clearTimeout(timeout);
|
|
123
|
-
resolvePromise({
|
|
124
|
-
ok: code === 0,
|
|
125
|
-
output: output.trimEnd(),
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
async function readMany(cwd, paths) {
|
|
131
|
-
const blocks = [];
|
|
132
|
-
for (const path of paths) {
|
|
133
|
-
const file = sandboxPath(cwd, path);
|
|
134
|
-
blocks.push(`### ${file}\n${await readFile(file, "utf8")}`);
|
|
135
|
-
}
|
|
136
|
-
return blocks.join("\n\n");
|
|
137
|
-
}
|
|
138
|
-
async function listFiles(sandbox, root, maxDepth) {
|
|
139
|
-
const out = createOutputBuffer();
|
|
140
|
-
await walkWithin(sandbox, root, maxDepth, async (file, info) => {
|
|
141
|
-
out.line((info.isDirectory() ? "dir " : "file") + " " + file);
|
|
142
|
-
return !out.full;
|
|
143
|
-
});
|
|
144
|
-
return out.value();
|
|
145
|
-
}
|
|
146
|
-
async function grepFiles(sandbox, root, pattern, maxDepth) {
|
|
147
|
-
const re = new RegExp(pattern, "i");
|
|
148
|
-
const out = createOutputBuffer();
|
|
149
|
-
await walkWithin(sandbox, root, maxDepth, async (file, info) => {
|
|
150
|
-
if (!info.isFile())
|
|
151
|
-
return;
|
|
152
|
-
try {
|
|
153
|
-
const text = await readFile(file, "utf8");
|
|
154
|
-
const lines = text.split(/\r?\n/);
|
|
155
|
-
lines.forEach((line, index) => {
|
|
156
|
-
if (re.test(line))
|
|
157
|
-
out.line(file + ":" + (index + 1) + ": " + line);
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
// Binary or unreadable files are skipped.
|
|
162
|
-
}
|
|
163
|
-
return !out.full;
|
|
164
|
-
});
|
|
165
|
-
return out.value();
|
|
166
|
-
}
|
|
167
|
-
/** Start a walk bounded to the sandbox plus every allowed extra root. */
|
|
168
|
-
async function walkWithin(sandbox, root, maxDepth, visit) {
|
|
169
|
-
const roots = allowedRoots(sandbox).map((entry) => realpathOf(entry)).filter(Boolean);
|
|
170
|
-
await walk(root, maxDepth, visit, 0, { roots, visited: 0, maxVisited: 5000 }, []);
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Tolerant recursive walk.
|
|
174
|
-
*
|
|
175
|
-
* stat() follows symlinks, so a dangling link (common on Termux: after
|
|
176
|
-
* termux-setup-storage the ~/storage/* links break once the phone-side folder
|
|
177
|
-
* is deleted) throws ENOENT. One bad entry must never abort the whole walk, so
|
|
178
|
-
* every stat/readdir/visit is guarded individually.
|
|
179
|
-
*
|
|
180
|
-
* Valid symlinks are followed. Recursion stops only where the real target is
|
|
181
|
-
* outside every allowed root: the sandbox, FASTCODE_ALLOW_PATHS, and on Termux
|
|
182
|
-
* ~/storage/shared (allowed by default). A per-branch chain of real paths breaks
|
|
183
|
-
* symlink cycles (loop -> ancestor) without hiding legitimate sibling symlinks.
|
|
184
|
-
*/
|
|
185
|
-
async function walk(node, maxDepth, visit, depth = 0, state, chain = []) {
|
|
186
|
-
if (state.visited >= state.maxVisited)
|
|
187
|
-
return;
|
|
188
|
-
let info;
|
|
189
|
-
try {
|
|
190
|
-
info = await stat(node);
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
return; // dangling symlink or unreadable entry: skip it, keep going
|
|
194
|
-
}
|
|
195
|
-
let real;
|
|
196
|
-
try {
|
|
197
|
-
real = await realpath(node);
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
real = node;
|
|
201
|
-
}
|
|
202
|
-
if (chain.includes(real))
|
|
203
|
-
return;
|
|
204
|
-
state.visited += 1;
|
|
205
|
-
const insideSandbox = !state.roots || state.roots.length === 0 || isInsideAny(real, state.roots);
|
|
206
|
-
let keepGoing = true;
|
|
207
|
-
try {
|
|
208
|
-
keepGoing = (await visit(node, info)) !== false;
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
// A visitor failure must not abort the surrounding walk.
|
|
212
|
-
}
|
|
213
|
-
if (!keepGoing)
|
|
214
|
-
return false;
|
|
215
|
-
if (!info.isDirectory() || depth >= maxDepth || !insideSandbox)
|
|
216
|
-
return;
|
|
217
|
-
let entries;
|
|
218
|
-
try {
|
|
219
|
-
entries = await readdir(node, { withFileTypes: true });
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
const nextChain = chain.concat(real);
|
|
225
|
-
for (const entry of entries) {
|
|
226
|
-
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist")
|
|
227
|
-
continue;
|
|
228
|
-
if (state.visited >= state.maxVisited)
|
|
229
|
-
return;
|
|
230
|
-
try {
|
|
231
|
-
const keepWalking = await walk(join(node, entry.name), maxDepth, visit, depth + 1, state, nextChain);
|
|
232
|
-
if (keepWalking === false)
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
// Keep walking siblings.
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Roots the agent may touch: the sandbox (cwd) plus configured extras.
|
|
242
|
-
*
|
|
243
|
-
* Termux users keep projects and downloads in shared storage, so on Termux
|
|
244
|
-
* ~/storage/shared (-> /storage/emulated/0) is allowed by default.
|
|
245
|
-
*
|
|
246
|
-
* FASTCODE_ALLOW_PATHS=/sdcard/projects:/sdcard/Download extra roots
|
|
247
|
-
* FASTCODE_ALLOW_SHARED_STORAGE=0 disable the default
|
|
248
|
-
*/
|
|
249
|
-
function allowedRoots(cwd) {
|
|
250
|
-
const roots = [resolve(cwd)];
|
|
251
|
-
const configured = (process.env["FASTCODE_ALLOW_PATHS"] ?? "")
|
|
252
|
-
.split(delimiter)
|
|
253
|
-
.map((entry) => entry.trim())
|
|
254
|
-
.filter(Boolean);
|
|
255
|
-
for (const entry of configured)
|
|
256
|
-
roots.push(resolve(entry));
|
|
257
|
-
if (detectPlatform() === "termux" && !isDisabled(process.env["FASTCODE_ALLOW_SHARED_STORAGE"])) {
|
|
258
|
-
const home = process.env["HOME"];
|
|
259
|
-
if (home) {
|
|
260
|
-
const shared = join(home, "storage", "shared");
|
|
261
|
-
if (existsSync(shared))
|
|
262
|
-
roots.push(shared);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return roots;
|
|
266
|
-
}
|
|
267
|
-
function isDisabled(value) {
|
|
268
|
-
if (value === undefined)
|
|
269
|
-
return false;
|
|
270
|
-
return ["0", "false", "no", "off"].includes(value.trim().toLowerCase());
|
|
271
|
-
}
|
|
272
|
-
function isInsideAny(child, roots) {
|
|
273
|
-
return roots.some((root) => {
|
|
274
|
-
const rel = relative(root, child);
|
|
275
|
-
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
function resolvePath(cwd, call, fallback = "") {
|
|
279
|
-
const raw = typeof call.path === "string" ? call.path : fallback;
|
|
280
|
-
return sandboxPath(cwd, raw);
|
|
281
|
-
}
|
|
282
|
-
function sandboxPath(cwd, raw) {
|
|
283
|
-
const target = resolve(cwd, raw);
|
|
284
|
-
const roots = allowedRoots(cwd);
|
|
285
|
-
// Real-path containment is authoritative: it is the only check that sees
|
|
286
|
-
// through symlinks, so a link planted inside the sandbox cannot escape.
|
|
287
|
-
const realRoots = roots.map((root) => realpathOf(root)).filter(Boolean);
|
|
288
|
-
const realTarget = realpathOf(target);
|
|
289
|
-
if (realTarget) {
|
|
290
|
-
if (isInsideAny(realTarget, realRoots))
|
|
291
|
-
return target;
|
|
292
|
-
throw new Error("path outside sandbox: " + raw);
|
|
293
|
-
}
|
|
294
|
-
// realpathOf() returns undefined only when nothing on the chain exists yet,
|
|
295
|
-
// so a lexical check against the configured roots is safe here.
|
|
296
|
-
if (isInsideAny(target, roots))
|
|
297
|
-
return target;
|
|
298
|
-
throw new Error("path outside sandbox: " + raw);
|
|
299
|
-
}
|
|
300
|
-
/** Resolve a path that may not exist yet by realpath-ing its deepest existing ancestor. */
|
|
301
|
-
function realpathOf(input) {
|
|
302
|
-
let current = resolve(input);
|
|
303
|
-
const missing = [];
|
|
304
|
-
for (;;) {
|
|
305
|
-
if (existsSync(current)) {
|
|
306
|
-
try {
|
|
307
|
-
return join(realpathSync(current), ...missing.slice().reverse());
|
|
308
|
-
}
|
|
309
|
-
catch {
|
|
310
|
-
return undefined;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
const parent = dirname(current);
|
|
314
|
-
if (parent === current)
|
|
315
|
-
return undefined;
|
|
316
|
-
missing.push(basename(current));
|
|
317
|
-
current = parent;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
function requiredString(call, key) {
|
|
321
|
-
const value = call[key];
|
|
322
|
-
if (typeof value !== "string")
|
|
323
|
-
throw new Error(`${key} must be a string`);
|
|
324
|
-
return value;
|
|
325
|
-
}
|
|
326
|
-
function stringArray(call, key) {
|
|
327
|
-
const value = call[key];
|
|
328
|
-
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
329
|
-
throw new Error(`${key} must be an array of strings`);
|
|
330
|
-
}
|
|
331
|
-
return value;
|
|
332
|
-
}
|
|
333
|
-
function numberOption(value, fallback) {
|
|
334
|
-
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
335
|
-
}
|
|
336
|
-
async function applyPatch(cwd, patch) {
|
|
337
|
-
const lines = patch.split(/\r?\n/);
|
|
338
|
-
if (lines[0] !== "*** Begin Patch")
|
|
339
|
-
return { ok: false, output: "patch must start with *** Begin Patch" };
|
|
340
|
-
let i = 1;
|
|
341
|
-
let changed = 0;
|
|
342
|
-
while (i < lines.length) {
|
|
343
|
-
const line = lines[i];
|
|
344
|
-
if (line === "*** End Patch" || line === "")
|
|
345
|
-
break;
|
|
346
|
-
if (!line?.startsWith("*** Update File: ")) {
|
|
347
|
-
return { ok: false, output: `unsupported patch directive: ${line ?? ""}` };
|
|
348
|
-
}
|
|
349
|
-
const relative = line.slice("*** Update File: ".length).trim();
|
|
350
|
-
const file = resolve(cwd, relative);
|
|
351
|
-
let source = await readFile(file, "utf8");
|
|
352
|
-
i += 1;
|
|
353
|
-
while (i < lines.length && !lines[i]?.startsWith("*** ")) {
|
|
354
|
-
if (lines[i] === "@@")
|
|
355
|
-
i += 1;
|
|
356
|
-
const remove = [];
|
|
357
|
-
const add = [];
|
|
358
|
-
while (i < lines.length && lines[i] !== "@@" && !lines[i]?.startsWith("*** ")) {
|
|
359
|
-
const hunkLine = lines[i] ?? "";
|
|
360
|
-
if (hunkLine.startsWith("-"))
|
|
361
|
-
remove.push(hunkLine.slice(1));
|
|
362
|
-
if (hunkLine.startsWith("+"))
|
|
363
|
-
add.push(hunkLine.slice(1));
|
|
364
|
-
i += 1;
|
|
365
|
-
}
|
|
366
|
-
const needle = `${remove.join("\n")}${remove.length > 0 ? "\n" : ""}`;
|
|
367
|
-
const replacement = `${add.join("\n")}${add.length > 0 ? "\n" : ""}`;
|
|
368
|
-
if (needle && !source.includes(needle)) {
|
|
369
|
-
return { ok: false, output: `patch context not found in ${file}: ${JSON.stringify(needle)}` };
|
|
370
|
-
}
|
|
371
|
-
source = needle ? source.replace(needle, replacement) : source + replacement;
|
|
372
|
-
changed += 1;
|
|
373
|
-
}
|
|
374
|
-
await writeFile(file, source, "utf8");
|
|
375
|
-
}
|
|
376
|
-
return { ok: true, output: `applied ${changed} patch hunk(s)` };
|
|
377
|
-
}
|
|
1
|
+
export { assertToolContract, registry, runAgentTool, ToolRegistry } from "./registry.js";
|
|
2
|
+
export { TOOLS } from "./tools/index.js";
|
package/dist/app.d.ts
CHANGED