vaan-ai 0.1.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 +216 -0
- package/README.md +390 -0
- package/dist/src/agent.js +151 -0
- package/dist/src/agent.js.map +1 -0
- package/dist/src/cli/args.js +38 -0
- package/dist/src/cli/args.js.map +1 -0
- package/dist/src/cli/doctor.js +98 -0
- package/dist/src/cli/doctor.js.map +1 -0
- package/dist/src/cli/dotenv.js +58 -0
- package/dist/src/cli/dotenv.js.map +1 -0
- package/dist/src/cli/main.js +138 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/cli/memory-cmd.js +31 -0
- package/dist/src/cli/memory-cmd.js.map +1 -0
- package/dist/src/cli/repl.js +275 -0
- package/dist/src/cli/repl.js.map +1 -0
- package/dist/src/cli/schedule.js +184 -0
- package/dist/src/cli/schedule.js.map +1 -0
- package/dist/src/cli/suggested.js +26 -0
- package/dist/src/cli/suggested.js.map +1 -0
- package/dist/src/cli/trace-cmd.js +69 -0
- package/dist/src/cli/trace-cmd.js.map +1 -0
- package/dist/src/cli/wizard.js +346 -0
- package/dist/src/cli/wizard.js.map +1 -0
- package/dist/src/config.js +130 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/eval/graders.js +145 -0
- package/dist/src/eval/graders.js.map +1 -0
- package/dist/src/eval/report.js +20 -0
- package/dist/src/eval/report.js.map +1 -0
- package/dist/src/eval/runner.js +67 -0
- package/dist/src/eval/runner.js.map +1 -0
- package/dist/src/eval/suites.js +164 -0
- package/dist/src/eval/suites.js.map +1 -0
- package/dist/src/http.js +134 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/inbox.js +51 -0
- package/dist/src/inbox.js.map +1 -0
- package/dist/src/index.js +180 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/memory/context.js +63 -0
- package/dist/src/memory/context.js.map +1 -0
- package/dist/src/memory/index.js +28 -0
- package/dist/src/memory/index.js.map +1 -0
- package/dist/src/memory/search.js +32 -0
- package/dist/src/memory/search.js.map +1 -0
- package/dist/src/memory/store.js +171 -0
- package/dist/src/memory/store.js.map +1 -0
- package/dist/src/observability/events.js +109 -0
- package/dist/src/observability/events.js.map +1 -0
- package/dist/src/observability/request.js +20 -0
- package/dist/src/observability/request.js.map +1 -0
- package/dist/src/observability/trace.js +149 -0
- package/dist/src/observability/trace.js.map +1 -0
- package/dist/src/paths.js +24 -0
- package/dist/src/paths.js.map +1 -0
- package/dist/src/permissions/approval.js +80 -0
- package/dist/src/permissions/approval.js.map +1 -0
- package/dist/src/permissions/policy.js +75 -0
- package/dist/src/permissions/policy.js.map +1 -0
- package/dist/src/permissions/rules.js +200 -0
- package/dist/src/permissions/rules.js.map +1 -0
- package/dist/src/prompt.js +114 -0
- package/dist/src/prompt.js.map +1 -0
- package/dist/src/providers/anthropic.js +233 -0
- package/dist/src/providers/anthropic.js.map +1 -0
- package/dist/src/providers/custom.js +46 -0
- package/dist/src/providers/custom.js.map +1 -0
- package/dist/src/providers/google.js +197 -0
- package/dist/src/providers/google.js.map +1 -0
- package/dist/src/providers/index.js +122 -0
- package/dist/src/providers/index.js.map +1 -0
- package/dist/src/providers/ollama.js +44 -0
- package/dist/src/providers/ollama.js.map +1 -0
- package/dist/src/providers/openai.js +193 -0
- package/dist/src/providers/openai.js.map +1 -0
- package/dist/src/providers/shared.js +32 -0
- package/dist/src/providers/shared.js.map +1 -0
- package/dist/src/sandbox/filesystem.js +193 -0
- package/dist/src/sandbox/filesystem.js.map +1 -0
- package/dist/src/sandbox/network.js +119 -0
- package/dist/src/sandbox/network.js.map +1 -0
- package/dist/src/sandbox/process.js +198 -0
- package/dist/src/sandbox/process.js.map +1 -0
- package/dist/src/skills.js +80 -0
- package/dist/src/skills.js.map +1 -0
- package/dist/src/tools/basics.js +41 -0
- package/dist/src/tools/basics.js.map +1 -0
- package/dist/src/tools/code.js +114 -0
- package/dist/src/tools/code.js.map +1 -0
- package/dist/src/tools/files.js +162 -0
- package/dist/src/tools/files.js.map +1 -0
- package/dist/src/tools/git.js +91 -0
- package/dist/src/tools/git.js.map +1 -0
- package/dist/src/tools/grep.js +92 -0
- package/dist/src/tools/grep.js.map +1 -0
- package/dist/src/tools/index.js +66 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/shared.js +11 -0
- package/dist/src/tools/shared.js.map +1 -0
- package/dist/src/tools/web.js +217 -0
- package/dist/src/tools/web.js.map +1 -0
- package/dist/src/types.js +14 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +59 -0
- package/scripts/postinstall.js +47 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// The network boundary for tools.
|
|
2
|
+
//
|
|
3
|
+
// The provider adapters don't come through here — talking to the model is the
|
|
4
|
+
// one network call the user configured on purpose. Everything the *model* asks
|
|
5
|
+
// for does, and the interesting case isn't the public internet. It's the local
|
|
6
|
+
// one: a page that says "fetch http://169.254.169.254/latest/meta-data/" is
|
|
7
|
+
// asking the agent to read cloud credentials from inside the trust boundary,
|
|
8
|
+
// and a page that says "fetch http://localhost:8080/admin" is asking it to
|
|
9
|
+
// reach something the firewall thinks is safe because it's local.
|
|
10
|
+
//
|
|
11
|
+
// So loopback and private ranges are refused by default, and text from a page
|
|
12
|
+
// is information rather than instruction wherever it ends up.
|
|
13
|
+
export const DEFAULT_NETWORK_POLICY = { allow: [], deny: [], allowLocal: false };
|
|
14
|
+
/** Link-local, loopback, and the ranges that are only reachable from inside. */
|
|
15
|
+
const PRIVATE_HOSTS = [
|
|
16
|
+
/^localhost$/i,
|
|
17
|
+
/^127\./,
|
|
18
|
+
/^0\.0\.0\.0$/,
|
|
19
|
+
/^10\./,
|
|
20
|
+
/^192\.168\./,
|
|
21
|
+
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
22
|
+
/^169\.254\./, // includes the cloud metadata address
|
|
23
|
+
/^::1$/,
|
|
24
|
+
/^\[::1\]$/,
|
|
25
|
+
/^f[cd][0-9a-f]{2}:/i,
|
|
26
|
+
/\.local$/i,
|
|
27
|
+
/\.internal$/i,
|
|
28
|
+
];
|
|
29
|
+
/** Decide whether a tool may fetch this URL, before any permission prompt. */
|
|
30
|
+
export function checkUrl(raw, policy = DEFAULT_NETWORK_POLICY) {
|
|
31
|
+
let url;
|
|
32
|
+
try {
|
|
33
|
+
url = new URL(raw);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { ok: false, reason: "not a valid URL", host: "" };
|
|
37
|
+
}
|
|
38
|
+
const host = url.hostname;
|
|
39
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
40
|
+
return { ok: false, reason: `${url.protocol} is not a scheme Vaan fetches`, host };
|
|
41
|
+
}
|
|
42
|
+
if (policy.deny.some((entry) => hostMatches(entry, host))) {
|
|
43
|
+
return { ok: false, reason: "host is on the deny list", host };
|
|
44
|
+
}
|
|
45
|
+
if (policy.allow.some((entry) => hostMatches(entry, host))) {
|
|
46
|
+
return { ok: true, reason: "host is on the allow list", host };
|
|
47
|
+
}
|
|
48
|
+
if (!policy.allowLocal && PRIVATE_HOSTS.some((pattern) => pattern.test(host))) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
reason: "that address is on this machine or its private network, which the agent doesn't reach",
|
|
52
|
+
host,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { ok: true, reason: "public host", host };
|
|
56
|
+
}
|
|
57
|
+
const hostMatches = (entry, host) => entry.toLowerCase() === host.toLowerCase() || host.toLowerCase().endsWith(`.${entry.toLowerCase()}`);
|
|
58
|
+
export const MAX_FETCH_BYTES = 200_000;
|
|
59
|
+
const FETCH_TIMEOUT_MS = 20_000;
|
|
60
|
+
/**
|
|
61
|
+
* Fetch a page as text, with a size cap and a timeout.
|
|
62
|
+
*
|
|
63
|
+
* Redirects are followed by `fetch`, so the final URL is re-checked: a public
|
|
64
|
+
* URL that 302s to 169.254.169.254 would otherwise walk straight past the
|
|
65
|
+
* check above.
|
|
66
|
+
*/
|
|
67
|
+
export async function fetchText(raw, policy = DEFAULT_NETWORK_POLICY, signal) {
|
|
68
|
+
const verdict = checkUrl(raw, policy);
|
|
69
|
+
if (!verdict.ok)
|
|
70
|
+
throw new Error(`${raw}: ${verdict.reason}`);
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
73
|
+
signal?.addEventListener("abort", () => controller.abort(), { once: true });
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(raw, {
|
|
76
|
+
redirect: "follow",
|
|
77
|
+
signal: controller.signal,
|
|
78
|
+
headers: { accept: "text/html,text/plain,application/json;q=0.9,*/*;q=0.5" },
|
|
79
|
+
});
|
|
80
|
+
const landed = checkUrl(response.url || raw, policy);
|
|
81
|
+
if (!landed.ok)
|
|
82
|
+
throw new Error(`${raw} redirected to ${landed.host}: ${landed.reason}`);
|
|
83
|
+
const body = await response.text();
|
|
84
|
+
return {
|
|
85
|
+
url: response.url || raw,
|
|
86
|
+
status: response.status,
|
|
87
|
+
contentType: response.headers.get("content-type") ?? "",
|
|
88
|
+
text: body.slice(0, MAX_FETCH_BYTES),
|
|
89
|
+
truncated: body.length > MAX_FETCH_BYTES,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const BLOCK_TAGS = /<(script|style|noscript|template|svg)[^>]*>[\s\S]*?<\/\1>/gi;
|
|
97
|
+
const ENTITIES = {
|
|
98
|
+
"&": "&",
|
|
99
|
+
"<": "<",
|
|
100
|
+
">": ">",
|
|
101
|
+
""": '"',
|
|
102
|
+
"'": "'",
|
|
103
|
+
"'": "'",
|
|
104
|
+
" ": " ",
|
|
105
|
+
};
|
|
106
|
+
/** HTML to something a model can read. Not a browser, and doesn't pretend to be. */
|
|
107
|
+
export function toReadableText(html) {
|
|
108
|
+
return html
|
|
109
|
+
.replace(BLOCK_TAGS, " ")
|
|
110
|
+
.replace(/<!--[\s\S]*?-->/g, " ")
|
|
111
|
+
.replace(/<\/(p|div|section|article|li|tr|h[1-6])>/gi, "\n")
|
|
112
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
113
|
+
.replace(/<[^>]*>/g, " ")
|
|
114
|
+
.replace(/&(?:amp|lt|gt|quot|#x27|#39|nbsp);/g, (entity) => ENTITIES[entity] ?? entity)
|
|
115
|
+
.replace(/[ \t]+/g, " ")
|
|
116
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
117
|
+
.trim();
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../../src/sandbox/network.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Running a command, without handing the model a shell.
|
|
2
|
+
//
|
|
3
|
+
// The old objection to an exec tool was sound: a shell resolves its own paths,
|
|
4
|
+
// so it can't be jailed, and approving `bash -c "…"` is approving an opaque
|
|
5
|
+
// string, so it can't be meaningfully confirmed either. Both problems come from
|
|
6
|
+
// the shell, not from execution.
|
|
7
|
+
//
|
|
8
|
+
// So there is no shell. The command is tokenised here, in-process, and spawned
|
|
9
|
+
// with `shell: false`. Metacharacters outside quotes are rejected rather than
|
|
10
|
+
// interpreted, which means the argv a human approves is exactly the argv that
|
|
11
|
+
// runs: no pipes, no redirection, no command substitution, no `&&` tail that
|
|
12
|
+
// nobody read. What's left is `npm test`, `git status`, `pytest -k auth` — the
|
|
13
|
+
// things a coding agent actually needs.
|
|
14
|
+
//
|
|
15
|
+
// On top of that: cwd is pinned to the workspace, credentials are stripped from
|
|
16
|
+
// the environment, output is capped, and the whole thing is killed on a timeout.
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
18
|
+
import { realpathSync } from "node:fs";
|
|
19
|
+
import { resolve, sep } from "node:path";
|
|
20
|
+
export class CommandError extends Error {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "CommandError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Rejected outside quotes. Each one is a way to run something else entirely. */
|
|
27
|
+
const METACHARACTERS = new Set([";", "&", "|", "<", ">", "`", "$", "(", ")", "{", "}", "\n", "\r"]);
|
|
28
|
+
/**
|
|
29
|
+
* Split a command line into argv, or throw.
|
|
30
|
+
*
|
|
31
|
+
* Quoted segments may contain anything — `--grep "a|b"` is a legitimate
|
|
32
|
+
* argument, not a pipe. Unquoted metacharacters are refused with a message the
|
|
33
|
+
* model can act on, because "run these as two commands" is a fix it can make.
|
|
34
|
+
*/
|
|
35
|
+
export function tokenize(command) {
|
|
36
|
+
const argv = [];
|
|
37
|
+
let current = "";
|
|
38
|
+
let started = false;
|
|
39
|
+
let quote;
|
|
40
|
+
for (let index = 0; index < command.length; index++) {
|
|
41
|
+
const char = command[index];
|
|
42
|
+
if (quote) {
|
|
43
|
+
if (char === quote)
|
|
44
|
+
quote = undefined;
|
|
45
|
+
else if (char === "\\" && quote === '"' && index + 1 < command.length) {
|
|
46
|
+
current += command[++index];
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
current += char;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (char === '"' || char === "'") {
|
|
53
|
+
quote = char;
|
|
54
|
+
started = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (char === " " || char === "\t") {
|
|
58
|
+
if (started)
|
|
59
|
+
argv.push(current);
|
|
60
|
+
current = "";
|
|
61
|
+
started = false;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (METACHARACTERS.has(char)) {
|
|
65
|
+
throw new CommandError(`"${char}" is a shell metacharacter and Vaan does not run commands through a shell. ` +
|
|
66
|
+
`Run one command at a time, and quote arguments that need it.`);
|
|
67
|
+
}
|
|
68
|
+
current += char;
|
|
69
|
+
started = true;
|
|
70
|
+
}
|
|
71
|
+
if (quote)
|
|
72
|
+
throw new CommandError("Unbalanced quote in the command.");
|
|
73
|
+
if (started)
|
|
74
|
+
argv.push(current);
|
|
75
|
+
if (argv.length === 0)
|
|
76
|
+
throw new CommandError("No command given.");
|
|
77
|
+
return argv;
|
|
78
|
+
}
|
|
79
|
+
/** Environment names whose value never enters the sandbox. */
|
|
80
|
+
const SECRET_ENV = /(?:^|_)(?:API[-_]?KEY|APIKEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIALS?|PRIVATE[-_]?KEY|SESSION|COOKIE|AUTH)(?:$|_)/i;
|
|
81
|
+
/**
|
|
82
|
+
* The parent environment minus anything that looks like a credential.
|
|
83
|
+
*
|
|
84
|
+
* A test run genuinely needs PATH, HOME and the rest of the ambient
|
|
85
|
+
* environment, so this is a denylist rather than an allowlist — but the model's
|
|
86
|
+
* own provider keys are exactly the shape this matches, so they never reach a
|
|
87
|
+
* process the model asked for.
|
|
88
|
+
*/
|
|
89
|
+
export function scrubEnv(env) {
|
|
90
|
+
const out = {};
|
|
91
|
+
for (const [key, value] of Object.entries(env)) {
|
|
92
|
+
if (value === undefined)
|
|
93
|
+
continue;
|
|
94
|
+
if (SECRET_ENV.test(key))
|
|
95
|
+
continue;
|
|
96
|
+
out[key] = value;
|
|
97
|
+
}
|
|
98
|
+
out.VAAN_SANDBOX = "1";
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
export const DEFAULT_TIMEOUT_MS = 120_000;
|
|
102
|
+
export const MAX_OUTPUT_BYTES = 60_000;
|
|
103
|
+
/**
|
|
104
|
+
* Run `command` inside the workspace and come back with what it printed.
|
|
105
|
+
*
|
|
106
|
+
* `async` rather than a plain Promise-returning function so that a refused
|
|
107
|
+
* command — a metacharacter, a cwd outside the workspace — arrives as a
|
|
108
|
+
* rejection like every other failure, instead of throwing synchronously past a
|
|
109
|
+
* caller that was only expecting `.catch()`.
|
|
110
|
+
*/
|
|
111
|
+
export async function runCommand(opts) {
|
|
112
|
+
const argv = tokenize(opts.command);
|
|
113
|
+
const [program, ...args] = argv;
|
|
114
|
+
const cwd = jailCwd(opts.workspace, opts.cwd);
|
|
115
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
116
|
+
const startedAt = Date.now();
|
|
117
|
+
return new Promise((settle) => {
|
|
118
|
+
const child = spawn(program, args, {
|
|
119
|
+
cwd,
|
|
120
|
+
env: scrubEnv(opts.env ?? process.env),
|
|
121
|
+
shell: false,
|
|
122
|
+
// Nothing to type at: a command that waits for input would hang forever,
|
|
123
|
+
// so it gets EOF immediately instead.
|
|
124
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
125
|
+
});
|
|
126
|
+
let stdout = "";
|
|
127
|
+
let stderr = "";
|
|
128
|
+
let truncated = false;
|
|
129
|
+
let timedOut = false;
|
|
130
|
+
const capture = (chunk, into) => {
|
|
131
|
+
const text = chunk.toString("utf8");
|
|
132
|
+
if (into === "out") {
|
|
133
|
+
if (stdout.length + text.length > MAX_OUTPUT_BYTES)
|
|
134
|
+
truncated = true;
|
|
135
|
+
stdout = (stdout + text).slice(0, MAX_OUTPUT_BYTES);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
if (stderr.length + text.length > MAX_OUTPUT_BYTES)
|
|
139
|
+
truncated = true;
|
|
140
|
+
stderr = (stderr + text).slice(0, MAX_OUTPUT_BYTES);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
child.stdout?.on("data", (chunk) => capture(chunk, "out"));
|
|
144
|
+
child.stderr?.on("data", (chunk) => capture(chunk, "err"));
|
|
145
|
+
const stop = () => {
|
|
146
|
+
child.kill("SIGKILL");
|
|
147
|
+
};
|
|
148
|
+
const timer = setTimeout(() => {
|
|
149
|
+
timedOut = true;
|
|
150
|
+
stop();
|
|
151
|
+
}, timeoutMs);
|
|
152
|
+
opts.signal?.addEventListener("abort", stop, { once: true });
|
|
153
|
+
const done = (code) => {
|
|
154
|
+
clearTimeout(timer);
|
|
155
|
+
opts.signal?.removeEventListener("abort", stop);
|
|
156
|
+
settle({
|
|
157
|
+
argv,
|
|
158
|
+
code,
|
|
159
|
+
stdout,
|
|
160
|
+
stderr,
|
|
161
|
+
timedOut,
|
|
162
|
+
truncated,
|
|
163
|
+
ms: Date.now() - startedAt,
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
child.on("error", (err) => {
|
|
167
|
+
stderr = `${stderr}${err.message}`;
|
|
168
|
+
done(null);
|
|
169
|
+
});
|
|
170
|
+
child.on("close", done);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** The working directory must be inside the workspace, like every other path. */
|
|
174
|
+
function jailCwd(workspace, candidate) {
|
|
175
|
+
const root = realpathSync(resolve(workspace));
|
|
176
|
+
if (!candidate)
|
|
177
|
+
return root;
|
|
178
|
+
const target = resolve(root, candidate);
|
|
179
|
+
const real = (() => {
|
|
180
|
+
try {
|
|
181
|
+
return realpathSync(target);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return target;
|
|
185
|
+
}
|
|
186
|
+
})();
|
|
187
|
+
if (real !== root && !real.startsWith(root + sep)) {
|
|
188
|
+
throw new CommandError(`"${candidate}" is outside the workspace.`);
|
|
189
|
+
}
|
|
190
|
+
return real;
|
|
191
|
+
}
|
|
192
|
+
/** A one-line summary of a run, for the trace and for the tool result. */
|
|
193
|
+
export function summarise(result) {
|
|
194
|
+
if (result.timedOut)
|
|
195
|
+
return `timed out after ${result.ms}ms`;
|
|
196
|
+
return `exit ${result.code ?? "signal"} in ${result.ms}ms`;
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../../src/sandbox/process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Skills are markdown in .vaan/skills/, not code. There is no plugin API to
|
|
2
|
+
// learn and nothing to keep in sync with a Vaan release.
|
|
3
|
+
//
|
|
4
|
+
// .vaan/skills/
|
|
5
|
+
// code-review/SKILL.md
|
|
6
|
+
// deployment/SKILL.md
|
|
7
|
+
//
|
|
8
|
+
// Only each skill's first line goes in the system prompt. When one applies, the
|
|
9
|
+
// model reads the whole file with read_file — the skills folder is inside the
|
|
10
|
+
// workspace, so the tool it already has is the tool it uses. Twenty skills
|
|
11
|
+
// therefore cost twenty lines per turn, not twenty skills' worth.
|
|
12
|
+
//
|
|
13
|
+
// A skill is instruction, not permission. Nothing a SKILL.md says can widen
|
|
14
|
+
// what the agent may do: the gate doesn't read them and wouldn't care.
|
|
15
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { skillsDir, skillsRelative } from "./paths.js";
|
|
18
|
+
/** Past this many, the descriptions themselves start costing real tokens. */
|
|
19
|
+
const WARN_ABOVE = 20;
|
|
20
|
+
const MAX_DESCRIPTION = 200;
|
|
21
|
+
export function loadSkills(workspace) {
|
|
22
|
+
const root = skillsDir(workspace);
|
|
23
|
+
let entries;
|
|
24
|
+
try {
|
|
25
|
+
entries = readdirSync(root).sort();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { skills: [], warnings: [] };
|
|
29
|
+
}
|
|
30
|
+
const skills = [];
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const found = locate(root, entry);
|
|
33
|
+
if (!found)
|
|
34
|
+
continue;
|
|
35
|
+
const description = firstLine(join(workspace, found.path));
|
|
36
|
+
if (!description)
|
|
37
|
+
continue;
|
|
38
|
+
skills.push({ name: found.name, path: found.path, description });
|
|
39
|
+
}
|
|
40
|
+
const warnings = skills.length > WARN_ABOVE
|
|
41
|
+
? [
|
|
42
|
+
`${skills.length} skills in ${skillsRelative}. Their descriptions are sent on every ` +
|
|
43
|
+
`turn — consider merging or removing some.`,
|
|
44
|
+
]
|
|
45
|
+
: [];
|
|
46
|
+
return { skills, warnings };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A skill is `<name>/SKILL.md`. A bare `<name>.md` is accepted too, because
|
|
50
|
+
* that is what people write first and refusing it teaches nothing.
|
|
51
|
+
*/
|
|
52
|
+
function locate(root, entry) {
|
|
53
|
+
const full = join(root, entry);
|
|
54
|
+
try {
|
|
55
|
+
if (statSync(full).isDirectory()) {
|
|
56
|
+
const inner = join(full, "SKILL.md");
|
|
57
|
+
statSync(inner);
|
|
58
|
+
return { name: entry, path: join(skillsRelative, entry, "SKILL.md") };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined; // A directory without a SKILL.md isn't a skill.
|
|
63
|
+
}
|
|
64
|
+
if (!entry.endsWith(".md"))
|
|
65
|
+
return undefined;
|
|
66
|
+
return { name: entry.replace(/\.md$/, ""), path: join(skillsRelative, entry) };
|
|
67
|
+
}
|
|
68
|
+
function firstLine(path) {
|
|
69
|
+
try {
|
|
70
|
+
const line = readFileSync(path, "utf8")
|
|
71
|
+
.split("\n")
|
|
72
|
+
.map((candidate) => candidate.replace(/^#+\s*/, "").trim())
|
|
73
|
+
.find((candidate) => candidate.length > 0);
|
|
74
|
+
return line ? line.slice(0, MAX_DESCRIPTION) : "";
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const now = {
|
|
2
|
+
name: "now",
|
|
3
|
+
description: "The current date and time. Call this before answering anything that depends on today's " +
|
|
4
|
+
"date — you have no other way to know it.",
|
|
5
|
+
group: "files",
|
|
6
|
+
parameters: { type: "object", properties: {} },
|
|
7
|
+
run() {
|
|
8
|
+
const at = new Date();
|
|
9
|
+
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
10
|
+
const local = at.toLocaleString(undefined, { dateStyle: "full", timeStyle: "long" });
|
|
11
|
+
return `${at.toISOString()} (UTC)\n${local} (${zone})`;
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export const remember = {
|
|
15
|
+
name: "remember",
|
|
16
|
+
description: "Store one short, durable fact about the user, so it is available in every future " +
|
|
17
|
+
"conversation. Good: a dietary restriction, where they live, what they're building, how " +
|
|
18
|
+
"they like answers written. Not for anything that stops being true next week, and not for " +
|
|
19
|
+
"restating what was just said. Every stored fact costs tokens on every turn, so keep each " +
|
|
20
|
+
"one to a sentence and only store what's worth carrying.",
|
|
21
|
+
group: "memory",
|
|
22
|
+
parameters: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
fact: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: 'The fact, written as a standalone sentence: "Is allergic to prawns."',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ["fact"],
|
|
31
|
+
},
|
|
32
|
+
async run(input, ctx) {
|
|
33
|
+
const fact = String(input.fact ?? "").trim();
|
|
34
|
+
if (!fact)
|
|
35
|
+
return "Nothing to remember — the fact was empty.";
|
|
36
|
+
await ctx.remember(fact);
|
|
37
|
+
ctx.trace.record({ kind: "memory", op: "remember", detail: fact });
|
|
38
|
+
return `Remembered: ${fact}`;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=basics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basics.js","sourceRoot":"","sources":["../../../src/tools/basics.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,GAAG,GAAS;IACvB,IAAI,EAAE,KAAK;IACX,WAAW,EACT,yFAAyF;QACzF,0CAA0C;IAC5C,KAAK,EAAE,OAAO;IACd,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;IAC9C,GAAG;QACD,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC,QAAQ,CAAC;QAC9D,MAAM,KAAK,GAAG,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QACrF,OAAO,GAAG,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,KAAK,IAAI,GAAG,CAAC;IACzD,CAAC;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAS;IAC5B,IAAI,EAAE,UAAU;IAChB,WAAW,EACT,mFAAmF;QACnF,yFAAyF;QACzF,2FAA2F;QAC3F,2FAA2F;QAC3F,yDAAyD;IAC3D,KAAK,EAAE,QAAQ;IACf,UAAU,EAAE;QACV,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,sEAAsE;aACpF;SACF;QACD,QAAQ,EAAE,CAAC,MAAM,CAAC;KACnB;IACD,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG;QAClB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI;YAAE,OAAO,2CAA2C,CAAC;QAC9D,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACzB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,OAAO,eAAe,IAAI,EAAE,CAAC;IAC/B,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// run_command — the coding sandbox's one entry point.
|
|
2
|
+
//
|
|
3
|
+
// This is the tool the old design refused to ship, and the objection was right
|
|
4
|
+
// as far as it went: a shell resolves its own paths so it can't be jailed, and
|
|
5
|
+
// approving `bash -c "…"` is approving an opaque string so it can't be
|
|
6
|
+
// meaningfully confirmed. Both of those are properties of the shell.
|
|
7
|
+
//
|
|
8
|
+
// So there is no shell. src/sandbox/process.ts tokenises the command in-process
|
|
9
|
+
// and spawns it with `shell: false`; unquoted metacharacters are rejected rather
|
|
10
|
+
// than interpreted. What the user approves is the argv that runs — no pipe, no
|
|
11
|
+
// `&&` tail, no command substitution, nothing that resolves to something else
|
|
12
|
+
// after they said yes. On top of that, cwd is pinned inside the workspace,
|
|
13
|
+
// credentials are stripped from the environment, output is capped and the
|
|
14
|
+
// process is killed on a timeout.
|
|
15
|
+
//
|
|
16
|
+
// Classification decides which permission is asked for: `npm test` is
|
|
17
|
+
// `execute`, `rm -rf build` is `destructive`, `npm install` is `network`, and
|
|
18
|
+
// `mkfs` is refused without a prompt because there is no good answer to it.
|
|
19
|
+
import { classifyCommand } from "../permissions/rules.js";
|
|
20
|
+
import { CommandError, DEFAULT_TIMEOUT_MS, runCommand, summarise } from "../sandbox/process.js";
|
|
21
|
+
const MAX_TIMEOUT_MS = 600_000;
|
|
22
|
+
export const runCommandTool = {
|
|
23
|
+
name: "run_command",
|
|
24
|
+
description: "Run one command inside the workspace and get back its output and exit code. Use it to " +
|
|
25
|
+
"run tests, build, or inspect the project with its own tooling. There is no shell: " +
|
|
26
|
+
"pipes, redirection, `&&` and command substitution are rejected, so run one command at " +
|
|
27
|
+
"a time. The user approves the exact command before it runs.",
|
|
28
|
+
group: "code",
|
|
29
|
+
parameters: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
command: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: 'One command with its arguments, e.g. "npm test" or "pytest -k auth".',
|
|
35
|
+
},
|
|
36
|
+
cwd: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Directory to run in, relative to the workspace. Defaults to its root.",
|
|
39
|
+
},
|
|
40
|
+
timeout_seconds: {
|
|
41
|
+
type: "number",
|
|
42
|
+
description: "How long to wait before killing it. Default 120, maximum 600.",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
required: ["command"],
|
|
46
|
+
},
|
|
47
|
+
async run(input, ctx) {
|
|
48
|
+
const command = String(input.command ?? "").trim();
|
|
49
|
+
if (!command)
|
|
50
|
+
return "No command given.";
|
|
51
|
+
const shape = classifyCommand(command);
|
|
52
|
+
if (shape.forbidden) {
|
|
53
|
+
ctx.trace.record({
|
|
54
|
+
kind: "sandbox",
|
|
55
|
+
op: "refused",
|
|
56
|
+
target: command,
|
|
57
|
+
ok: false,
|
|
58
|
+
detail: shape.reasons.join("; "),
|
|
59
|
+
});
|
|
60
|
+
return (`Refused: that command ${shape.reasons.join(", ")}. This one isn't approvable — ` +
|
|
61
|
+
`if it's genuinely what you want, the user should run it themselves.`);
|
|
62
|
+
}
|
|
63
|
+
await ctx.gate.require({
|
|
64
|
+
tool: "run_command",
|
|
65
|
+
capability: shape.capability,
|
|
66
|
+
target: command,
|
|
67
|
+
...(shape.reasons.length ? { detail: shape.reasons.join(", ") } : {}),
|
|
68
|
+
});
|
|
69
|
+
const seconds = Number(input.timeout_seconds);
|
|
70
|
+
const timeoutMs = Number.isFinite(seconds)
|
|
71
|
+
? Math.min(Math.max(seconds, 1) * 1000, MAX_TIMEOUT_MS)
|
|
72
|
+
: DEFAULT_TIMEOUT_MS;
|
|
73
|
+
try {
|
|
74
|
+
const result = await runCommand({
|
|
75
|
+
command,
|
|
76
|
+
workspace: ctx.workspace,
|
|
77
|
+
...(input.cwd === undefined ? {} : { cwd: String(input.cwd) }),
|
|
78
|
+
timeoutMs,
|
|
79
|
+
env: ctx.env,
|
|
80
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
81
|
+
});
|
|
82
|
+
ctx.trace.record({
|
|
83
|
+
kind: "sandbox",
|
|
84
|
+
op: "exec",
|
|
85
|
+
target: result.argv.join(" "),
|
|
86
|
+
ok: result.code === 0,
|
|
87
|
+
detail: summarise(result),
|
|
88
|
+
});
|
|
89
|
+
return format(result.argv.join(" "), result);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof CommandError)
|
|
93
|
+
return err.message;
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
function format(command, result) {
|
|
99
|
+
const lines = [`$ ${command}`];
|
|
100
|
+
if (result.timedOut)
|
|
101
|
+
lines.push(`[killed after ${result.ms}ms]`);
|
|
102
|
+
else
|
|
103
|
+
lines.push(`[exit ${result.code ?? "signal"} in ${result.ms}ms]`);
|
|
104
|
+
if (result.stdout.trim())
|
|
105
|
+
lines.push("", result.stdout.trimEnd());
|
|
106
|
+
if (result.stderr.trim())
|
|
107
|
+
lines.push("", "stderr:", result.stderr.trimEnd());
|
|
108
|
+
if (!result.stdout.trim() && !result.stderr.trim())
|
|
109
|
+
lines.push("", "(no output)");
|
|
110
|
+
if (result.truncated)
|
|
111
|
+
lines.push("", "[output truncated]");
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code.js","sourceRoot":"","sources":["../../../src/tools/code.ts"],"names":[],"mappings":"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"}
|