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.
Files changed (106) hide show
  1. package/LICENSE +216 -0
  2. package/README.md +390 -0
  3. package/dist/src/agent.js +151 -0
  4. package/dist/src/agent.js.map +1 -0
  5. package/dist/src/cli/args.js +38 -0
  6. package/dist/src/cli/args.js.map +1 -0
  7. package/dist/src/cli/doctor.js +98 -0
  8. package/dist/src/cli/doctor.js.map +1 -0
  9. package/dist/src/cli/dotenv.js +58 -0
  10. package/dist/src/cli/dotenv.js.map +1 -0
  11. package/dist/src/cli/main.js +138 -0
  12. package/dist/src/cli/main.js.map +1 -0
  13. package/dist/src/cli/memory-cmd.js +31 -0
  14. package/dist/src/cli/memory-cmd.js.map +1 -0
  15. package/dist/src/cli/repl.js +275 -0
  16. package/dist/src/cli/repl.js.map +1 -0
  17. package/dist/src/cli/schedule.js +184 -0
  18. package/dist/src/cli/schedule.js.map +1 -0
  19. package/dist/src/cli/suggested.js +26 -0
  20. package/dist/src/cli/suggested.js.map +1 -0
  21. package/dist/src/cli/trace-cmd.js +69 -0
  22. package/dist/src/cli/trace-cmd.js.map +1 -0
  23. package/dist/src/cli/wizard.js +346 -0
  24. package/dist/src/cli/wizard.js.map +1 -0
  25. package/dist/src/config.js +130 -0
  26. package/dist/src/config.js.map +1 -0
  27. package/dist/src/eval/graders.js +145 -0
  28. package/dist/src/eval/graders.js.map +1 -0
  29. package/dist/src/eval/report.js +20 -0
  30. package/dist/src/eval/report.js.map +1 -0
  31. package/dist/src/eval/runner.js +67 -0
  32. package/dist/src/eval/runner.js.map +1 -0
  33. package/dist/src/eval/suites.js +164 -0
  34. package/dist/src/eval/suites.js.map +1 -0
  35. package/dist/src/http.js +134 -0
  36. package/dist/src/http.js.map +1 -0
  37. package/dist/src/inbox.js +51 -0
  38. package/dist/src/inbox.js.map +1 -0
  39. package/dist/src/index.js +180 -0
  40. package/dist/src/index.js.map +1 -0
  41. package/dist/src/memory/context.js +63 -0
  42. package/dist/src/memory/context.js.map +1 -0
  43. package/dist/src/memory/index.js +28 -0
  44. package/dist/src/memory/index.js.map +1 -0
  45. package/dist/src/memory/search.js +32 -0
  46. package/dist/src/memory/search.js.map +1 -0
  47. package/dist/src/memory/store.js +171 -0
  48. package/dist/src/memory/store.js.map +1 -0
  49. package/dist/src/observability/events.js +109 -0
  50. package/dist/src/observability/events.js.map +1 -0
  51. package/dist/src/observability/request.js +20 -0
  52. package/dist/src/observability/request.js.map +1 -0
  53. package/dist/src/observability/trace.js +149 -0
  54. package/dist/src/observability/trace.js.map +1 -0
  55. package/dist/src/paths.js +24 -0
  56. package/dist/src/paths.js.map +1 -0
  57. package/dist/src/permissions/approval.js +80 -0
  58. package/dist/src/permissions/approval.js.map +1 -0
  59. package/dist/src/permissions/policy.js +75 -0
  60. package/dist/src/permissions/policy.js.map +1 -0
  61. package/dist/src/permissions/rules.js +200 -0
  62. package/dist/src/permissions/rules.js.map +1 -0
  63. package/dist/src/prompt.js +114 -0
  64. package/dist/src/prompt.js.map +1 -0
  65. package/dist/src/providers/anthropic.js +233 -0
  66. package/dist/src/providers/anthropic.js.map +1 -0
  67. package/dist/src/providers/custom.js +46 -0
  68. package/dist/src/providers/custom.js.map +1 -0
  69. package/dist/src/providers/google.js +197 -0
  70. package/dist/src/providers/google.js.map +1 -0
  71. package/dist/src/providers/index.js +122 -0
  72. package/dist/src/providers/index.js.map +1 -0
  73. package/dist/src/providers/ollama.js +44 -0
  74. package/dist/src/providers/ollama.js.map +1 -0
  75. package/dist/src/providers/openai.js +193 -0
  76. package/dist/src/providers/openai.js.map +1 -0
  77. package/dist/src/providers/shared.js +32 -0
  78. package/dist/src/providers/shared.js.map +1 -0
  79. package/dist/src/sandbox/filesystem.js +193 -0
  80. package/dist/src/sandbox/filesystem.js.map +1 -0
  81. package/dist/src/sandbox/network.js +119 -0
  82. package/dist/src/sandbox/network.js.map +1 -0
  83. package/dist/src/sandbox/process.js +198 -0
  84. package/dist/src/sandbox/process.js.map +1 -0
  85. package/dist/src/skills.js +80 -0
  86. package/dist/src/skills.js.map +1 -0
  87. package/dist/src/tools/basics.js +41 -0
  88. package/dist/src/tools/basics.js.map +1 -0
  89. package/dist/src/tools/code.js +114 -0
  90. package/dist/src/tools/code.js.map +1 -0
  91. package/dist/src/tools/files.js +162 -0
  92. package/dist/src/tools/files.js.map +1 -0
  93. package/dist/src/tools/git.js +91 -0
  94. package/dist/src/tools/git.js.map +1 -0
  95. package/dist/src/tools/grep.js +92 -0
  96. package/dist/src/tools/grep.js.map +1 -0
  97. package/dist/src/tools/index.js +66 -0
  98. package/dist/src/tools/index.js.map +1 -0
  99. package/dist/src/tools/shared.js +11 -0
  100. package/dist/src/tools/shared.js.map +1 -0
  101. package/dist/src/tools/web.js +217 -0
  102. package/dist/src/tools/web.js.map +1 -0
  103. package/dist/src/types.js +14 -0
  104. package/dist/src/types.js.map +1 -0
  105. package/package.json +59 -0
  106. package/scripts/postinstall.js +47 -0
@@ -0,0 +1,20 @@
1
+ // A request is one complete agent flow: what the user asked, through to the
2
+ // answer. Everything the runtime does carries this id, which is the only thing
3
+ // that makes a trace reconstructable after the fact.
4
+ import { randomUUID } from "node:crypto";
5
+ export function newRequest(input, model, workspace) {
6
+ return {
7
+ id: randomUUID(),
8
+ input,
9
+ model,
10
+ workspace,
11
+ startedAt: Date.now(),
12
+ at: new Date().toISOString(),
13
+ };
14
+ }
15
+ /** Enough of an id to say out loud, and to disambiguate in `vaan trace`. */
16
+ export const shortId = (id) => id.slice(0, 8);
17
+ export const elapsedMs = (ctx) => Date.now() - ctx.startedAt;
18
+ /** Accepts a full uuid or any unambiguous prefix, which is what people type. */
19
+ export const idMatches = (id, query) => id === query || id.startsWith(query.toLowerCase());
20
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../../src/observability/request.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,+EAA+E;AAC/E,qDAAqD;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAazC,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,KAAa,EAAE,SAAiB;IACxE,OAAO;QACL,EAAE,EAAE,UAAU,EAAE;QAChB,KAAK;QACL,KAAK;QACL,SAAS;QACT,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KAC7B,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,GAAmB,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC;AAErF,gFAAgF;AAChF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,EAAU,EAAE,KAAa,EAAW,EAAE,CAC9D,EAAE,KAAK,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC"}
@@ -0,0 +1,149 @@
1
+ // The recorder, and the reader behind `vaan trace`.
2
+ //
3
+ // One JSON file per request in .vaan/traces. A file rather than a shared log
4
+ // because requests can overlap — a scheduled run while you're typing — and
5
+ // interleaved appends from two writers is how a log file becomes unparseable.
6
+ //
7
+ // Nothing here throws. A trace that can't be written is a missing trace, not a
8
+ // failed request; observability that can take the agent down with it is worse
9
+ // than no observability.
10
+ import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { describeEvent, redactEvent } from "./events.js";
13
+ import { elapsedMs, idMatches } from "./request.js";
14
+ /** Traces past this many are pruned oldest-first, so .vaan never grows forever. */
15
+ const KEEP = 200;
16
+ export function openTrace(opts) {
17
+ const collected = [];
18
+ const { request } = opts;
19
+ let closed = false;
20
+ const record = (event) => {
21
+ collected.push({
22
+ ...redactEvent(event),
23
+ at: new Date().toISOString(),
24
+ ms: elapsedMs(request),
25
+ });
26
+ };
27
+ record({
28
+ kind: "request",
29
+ input: request.input,
30
+ model: request.model,
31
+ workspace: request.workspace,
32
+ });
33
+ return {
34
+ id: request.id,
35
+ request,
36
+ record,
37
+ events: () => [...collected],
38
+ finish(ok, summary) {
39
+ if (!closed) {
40
+ closed = true;
41
+ record({ kind: "result", ok, summary });
42
+ }
43
+ const stored = {
44
+ id: request.id,
45
+ at: request.at,
46
+ input: collected[0]?.kind === "request" ? collected[0].input : request.input,
47
+ model: request.model,
48
+ workspace: request.workspace,
49
+ durationMs: elapsedMs(request),
50
+ ok,
51
+ summary,
52
+ events: collected,
53
+ };
54
+ if (opts.persist !== false)
55
+ write(opts.home, stored);
56
+ return stored;
57
+ },
58
+ };
59
+ }
60
+ /** A trace that records nothing, for tests and for `--no-trace`. */
61
+ export const noTrace = (request) => ({
62
+ id: request.id,
63
+ request,
64
+ record() { },
65
+ events: () => [],
66
+ finish: (ok, summary) => ({
67
+ id: request.id,
68
+ at: request.at,
69
+ input: request.input,
70
+ model: request.model,
71
+ workspace: request.workspace,
72
+ durationMs: elapsedMs(request),
73
+ ok,
74
+ summary,
75
+ events: [],
76
+ }),
77
+ });
78
+ const dirOf = (home) => join(home, "traces");
79
+ function write(home, trace) {
80
+ try {
81
+ const dir = dirOf(home);
82
+ mkdirSync(dir, { recursive: true });
83
+ // Timestamp first so a lexical sort is a chronological sort, which is what
84
+ // both the pruner and the listing rely on.
85
+ const stamp = trace.at.replace(/[:.]/g, "-");
86
+ writeFileSync(join(dir, `${stamp}__${trace.id}.json`), JSON.stringify(trace, null, 2), "utf8");
87
+ prune(dir);
88
+ }
89
+ catch {
90
+ // A trace is a record of work, not the work.
91
+ }
92
+ }
93
+ function prune(dir) {
94
+ try {
95
+ const files = readdirSync(dir)
96
+ .filter((name) => name.endsWith(".json"))
97
+ .sort();
98
+ for (const name of files.slice(0, Math.max(0, files.length - KEEP))) {
99
+ rmSync(join(dir, name), { force: true });
100
+ }
101
+ }
102
+ catch {
103
+ // Nothing to prune, or nothing we may prune.
104
+ }
105
+ }
106
+ /** Newest first. Reads only what `vaan trace` prints, not every event. */
107
+ export function listTraces(home, limit = 20) {
108
+ try {
109
+ return readdirSync(dirOf(home))
110
+ .filter((name) => name.endsWith(".json"))
111
+ .sort()
112
+ .reverse()
113
+ .slice(0, limit)
114
+ .map((name) => parse(join(dirOf(home), name)))
115
+ .filter((trace) => trace !== undefined);
116
+ }
117
+ catch {
118
+ return [];
119
+ }
120
+ }
121
+ /** Look one up by full id or by any prefix, which is what people type. */
122
+ export function readTrace(home, query) {
123
+ try {
124
+ const match = readdirSync(dirOf(home))
125
+ .filter((name) => name.endsWith(".json"))
126
+ .sort()
127
+ .reverse()
128
+ .find((name) => idMatches(name.split("__")[1]?.replace(/\.json$/, "") ?? "", query));
129
+ return match ? parse(join(dirOf(home), match)) : undefined;
130
+ }
131
+ catch {
132
+ return undefined;
133
+ }
134
+ }
135
+ function parse(path) {
136
+ try {
137
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
138
+ return isTrace(parsed) ? parsed : undefined;
139
+ }
140
+ catch {
141
+ return undefined;
142
+ }
143
+ }
144
+ const isTrace = (value) => typeof value === "object" &&
145
+ value !== null &&
146
+ typeof value.id === "string" &&
147
+ Array.isArray(value.events);
148
+ export { describeEvent };
149
+ //# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","sourceRoot":"","sources":["../../../src/observability/trace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ // Every path Vaan writes to, in one place.
2
+ //
3
+ // `.vaan/` sits at the root of the workspace rather than in the home directory,
4
+ // because the interesting state is per-project: this project's memory, this
5
+ // project's traces, this project's skills. Nothing here is shared between
6
+ // workspaces, which is also why nothing here needs a lock.
7
+ import { join } from "node:path";
8
+ export const HOME_DIR = ".vaan";
9
+ /** `<workspace>/.vaan` — the root of everything below. */
10
+ export const home = (workspace) => join(workspace, HOME_DIR);
11
+ export const configDir = (workspace) => join(home(workspace), "config");
12
+ export const memoryDir = (workspace) => join(home(workspace), "memory");
13
+ export const tracesDir = (workspace) => join(home(workspace), "traces");
14
+ export const stateDir = (workspace) => join(home(workspace), "state");
15
+ export const skillsDir = (workspace) => join(home(workspace), "skills");
16
+ export const configFile = (workspace) => join(configDir(workspace), "config.json");
17
+ export const databaseFile = (workspace) => join(memoryDir(workspace), "state.db");
18
+ export const memoryMirror = (workspace) => join(memoryDir(workspace), "MEMORY.md");
19
+ export const inboxFile = (workspace) => join(stateDir(workspace), "inbox.json");
20
+ /** Relative to the workspace, which is what `read_file` wants. */
21
+ export const skillsRelative = join(HOME_DIR, "skills");
22
+ export const SOUL_FILE = "SOUL.md";
23
+ export const GUARDRAILS_FILE = "GUARDRAILS.md";
24
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/paths.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,EAAE;AACF,gFAAgF;AAChF,4EAA4E;AAC5E,0EAA0E;AAC1E,2DAA2D;AAE3D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC;AAEhC,0DAA0D;AAC1D,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAE7E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;AACtF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;AAExF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,CAAC;AACnG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,CAAC;AAClG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,CAAC;AACnG,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC,CAAC;AAEhG,kEAAkE;AAClE,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAEvD,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC;AACnC,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,CAAC"}
@@ -0,0 +1,80 @@
1
+ // The gate. Every tool that touches anything comes through here first.
2
+ //
3
+ // The one invariant worth stating plainly: the model cannot approve its own
4
+ // actions. The approver is a function the CLI supplies when the session is
5
+ // built, closed over a readline handle attached to the real terminal. It is
6
+ // never passed to a tool, never named in a prompt, and never reachable from
7
+ // anything the model emits. A tool can ask the gate; only a human can answer.
8
+ export class PermissionDenied extends Error {
9
+ request;
10
+ reason;
11
+ constructor(request, reason) {
12
+ super(`Not permitted: ${request.capability} on ${request.target} — ${reason}`);
13
+ this.request = request;
14
+ this.reason = reason;
15
+ this.name = "PermissionDenied";
16
+ }
17
+ }
18
+ /** `--yes`, and the eval runner. Never wired to anything the model controls. */
19
+ export const approveAll = async () => true;
20
+ /** Non-interactive sessions: nobody is there to ask, so the answer is no. */
21
+ export const denyAll = async () => false;
22
+ /** Capabilities whose target fully describes the action, so a yes can be reused. */
23
+ const MEMOABLE = new Set(["execute", "destructive", "network"]);
24
+ export function createGate(opts) {
25
+ // Approving `npm test` once and then being asked again for the identical
26
+ // command is the fastest way to teach someone to hold down `y`. Commands and
27
+ // hosts are memoised by exact target. File writes deliberately are not: the
28
+ // path is the same on every write, and the content is what changed.
29
+ const granted = new Set();
30
+ const memoise = opts.memoise !== false;
31
+ async function decide(request) {
32
+ const verdict = opts.policy.check(request);
33
+ const key = `${request.capability} ${request.target}`;
34
+ let allowed;
35
+ let how;
36
+ if (verdict.decision === "deny") {
37
+ allowed = false;
38
+ how = verdict.fixed ? "denied by fixed policy" : "denied by policy";
39
+ }
40
+ else if (verdict.decision === "allow") {
41
+ allowed = true;
42
+ how = "allowed by policy";
43
+ }
44
+ else if (memoise && MEMOABLE.has(request.capability) && granted.has(key)) {
45
+ allowed = true;
46
+ how = "approved earlier this session";
47
+ }
48
+ else {
49
+ allowed = await opts.approve(request, verdict.reason);
50
+ how = allowed ? "approved by user" : "declined by user";
51
+ if (allowed && memoise && MEMOABLE.has(request.capability))
52
+ granted.add(key);
53
+ }
54
+ opts.trace?.()?.record({
55
+ kind: "permission",
56
+ tool: request.tool,
57
+ capability: request.capability,
58
+ target: request.target,
59
+ decision: allowed ? "allow" : "deny",
60
+ reason: `${verdict.reason} — ${how}`,
61
+ });
62
+ return allowed;
63
+ }
64
+ return {
65
+ allowed: decide,
66
+ async require(request) {
67
+ if (!(await decide(request))) {
68
+ throw new PermissionDenied(request, opts.policy.check(request).reason);
69
+ }
70
+ },
71
+ };
72
+ }
73
+ /** A gate that permits everything, for unit tests of things that aren't the gate. */
74
+ export const openGate = {
75
+ async require() { },
76
+ async allowed() {
77
+ return true;
78
+ },
79
+ };
80
+ //# sourceMappingURL=approval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval.js","sourceRoot":"","sources":["../../../src/permissions/approval.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,8EAA8E;AAM9E,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAE9B;IACA;IAFX,YACW,OAA0B,EAC1B,MAAc;QAEvB,KAAK,CAAC,kBAAkB,OAAO,CAAC,UAAU,OAAO,OAAO,CAAC,MAAM,MAAM,MAAM,EAAE,CAAC,CAAC;QAHtE,YAAO,GAAP,OAAO,CAAmB;QAC1B,WAAM,GAAN,MAAM,CAAQ;QAGvB,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAKD,gFAAgF;AAChF,MAAM,CAAC,MAAM,UAAU,GAAa,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC;AAErD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,OAAO,GAAa,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC;AAsBnD,oFAAoF;AACpF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;AAEhE,MAAM,UAAU,UAAU,CAAC,IAAiB;IAC1C,yEAAyE;IACzE,6EAA6E;IAC7E,4EAA4E;IAC5E,oEAAoE;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC;IAEvC,KAAK,UAAU,MAAM,CAAC,OAA0B;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QAEtD,IAAI,OAAgB,CAAC;QACrB,IAAI,GAAW,CAAC;QAChB,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAChC,OAAO,GAAG,KAAK,CAAC;YAChB,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,kBAAkB,CAAC;QACtE,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACxC,OAAO,GAAG,IAAI,CAAC;YACf,GAAG,GAAG,mBAAmB,CAAC;QAC5B,CAAC;aAAM,IAAI,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3E,OAAO,GAAG,IAAI,CAAC;YACf,GAAG,GAAG,+BAA+B,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YACtD,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,kBAAkB,CAAC;YACxD,IAAI,OAAO,IAAI,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/E,CAAC;QAED,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,MAAM,CAAC;YACrB,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;YACpC,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,MAAM,GAAG,EAAE;SACrC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO;QACL,OAAO,EAAE,MAAM;QACf,KAAK,CAAC,OAAO,CAAC,OAAO;YACnB,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;gBAC7B,MAAM,IAAI,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,MAAM,QAAQ,GAAS;IAC5B,KAAK,CAAC,OAAO,KAAI,CAAC;IAClB,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAC"}
@@ -0,0 +1,75 @@
1
+ // The policy: the user's rules, plus the mapping from a concrete action to the
2
+ // capability it needs.
3
+ //
4
+ // Classification is the part that matters. A rule that says "ask before writing
5
+ // outside the workspace" is worth nothing if a path that walks out through a
6
+ // symlink is classified as an ordinary workspace write, so paths are resolved
7
+ // to real paths before anything else looks at them.
8
+ import { realpathSync } from "node:fs";
9
+ import { dirname, isAbsolute, resolve, sep } from "node:path";
10
+ import { ALL_APPROVALS, defaultRules, evaluate, isSensitiveFile, } from "./rules.js";
11
+ export function createPolicy(opts) {
12
+ const workspace = realpathish(opts.workspace);
13
+ const rules = opts.rules ?? defaultRules(opts.approvals ?? ALL_APPROVALS);
14
+ return {
15
+ workspace,
16
+ rules,
17
+ check: (request) => evaluate(rules, request),
18
+ forPath(tool, path, intent) {
19
+ const target = isAbsolute(path) ? path : resolve(workspace, path);
20
+ const real = realpathOfNearest(target);
21
+ const capability = isSensitiveFile(real)
22
+ ? "credentials"
23
+ : within(workspace, real)
24
+ ? intent
25
+ : "outside";
26
+ return { tool, capability, target: real };
27
+ },
28
+ forUrl(tool, url) {
29
+ return { tool, capability: "network", target: url };
30
+ },
31
+ };
32
+ }
33
+ const within = (root, path) => path === root || path.startsWith(root + sep);
34
+ function realpathish(path) {
35
+ try {
36
+ return realpathSync(resolve(path));
37
+ }
38
+ catch {
39
+ return resolve(path);
40
+ }
41
+ }
42
+ /**
43
+ * The real path of `target`, or of the deepest ancestor that exists with the
44
+ * missing tail re-attached.
45
+ *
46
+ * A path component that doesn't exist yet can't be a symlink, so the deepest
47
+ * real ancestor decides where the finished path will land. Without this, "write
48
+ * a new file" would classify by its own spelling rather than by where its
49
+ * parent directory actually points.
50
+ */
51
+ export function realpathOfNearest(target) {
52
+ const full = resolve(target);
53
+ let current = full;
54
+ for (;;) {
55
+ try {
56
+ const real = realpathSync(current);
57
+ return current === full ? real : real + full.slice(current.length);
58
+ }
59
+ catch {
60
+ const parent = dirname(current);
61
+ if (parent === current)
62
+ return full;
63
+ current = parent;
64
+ }
65
+ }
66
+ }
67
+ /** What `vaan doctor` and `/status` print. */
68
+ export function describeRules(rules) {
69
+ return rules.map((rule) => {
70
+ const scope = rule.match ? ` ${rule.match}` : "";
71
+ return `${rule.capability}${scope}`.padEnd(24) + rule.decision;
72
+ });
73
+ }
74
+ export { defaultRules };
75
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/permissions/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,200 @@
1
+ // What an action *is*, and what the rules say about it.
2
+ //
3
+ // Two layers, and the difference is the whole point. `FLOOR` is not
4
+ // configurable: no config file, no onboarding checkbox and no instruction in
5
+ // SOUL.md can turn it off. Everything above it is the user's policy, which the
6
+ // onboarding writes and the user can edit.
7
+ //
8
+ // GUARDRAILS.md asks the model to behave. This decides whether it may.
9
+ /**
10
+ * Never allowed, whatever the config says.
11
+ *
12
+ * These are the cases where a mistake isn't recoverable by saying no next time:
13
+ * key material that has already been read is already leaked, and a filesystem
14
+ * that has already been formatted is already gone.
15
+ */
16
+ export const FLOOR = [
17
+ {
18
+ capability: "credentials",
19
+ decision: "deny",
20
+ reason: "credential material is never read by the agent",
21
+ },
22
+ { capability: "system", decision: "deny", match: "**", reason: "system changes are out of reach" },
23
+ { capability: "outside", decision: "deny", match: "/etc/**", reason: "system configuration" },
24
+ { capability: "outside", decision: "deny", match: "/dev/**", reason: "device files" },
25
+ { capability: "outside", decision: "deny", match: "/proc/**", reason: "kernel interface" },
26
+ ];
27
+ /** Files that are never read or written, wherever they sit. */
28
+ export const SENSITIVE_FILES = [
29
+ /(^|[\\/])\.env(\.|$)/i,
30
+ /(^|[\\/])\.git[\\/]config$/i,
31
+ /(^|[\\/])\.npmrc$/i,
32
+ /(^|[\\/])\.netrc$/i,
33
+ /(^|[\\/])\.pypirc$/i,
34
+ /(^|[\\/])id_(rsa|dsa|ecdsa|ed25519)$/i,
35
+ /(^|[\\/])\.ssh[\\/]/i,
36
+ /(^|[\\/])\.aws[\\/]/i,
37
+ /(^|[\\/])\.gnupg[\\/]/i,
38
+ /(^|[\\/])\.kube[\\/]config$/i,
39
+ /(^|[\\/])credentials(\.json)?$/i,
40
+ /(^|[\\/])service-account.*\.json$/i,
41
+ ];
42
+ export const isSensitiveFile = (path) => SENSITIVE_FILES.some((pattern) => pattern.test(path));
43
+ export const ALL_APPROVALS = {
44
+ destructive: true,
45
+ network: true,
46
+ credentials: true,
47
+ outside: true,
48
+ system: true,
49
+ };
50
+ /**
51
+ * The starting policy. Reading inside the workspace is free; changing anything
52
+ * is not. A flag the user cleared downgrades `ask` to `allow` — that is what
53
+ * the checkbox means — but it can never reach past `FLOOR`.
54
+ */
55
+ export function defaultRules(flags = ALL_APPROVALS) {
56
+ const gated = (on) => (on ? "ask" : "allow");
57
+ return [
58
+ { capability: "read", decision: "allow", reason: "reading inside the workspace" },
59
+ { capability: "write", decision: "ask", reason: "changing a file in the workspace" },
60
+ { capability: "execute", decision: "ask", reason: "running a command in the sandbox" },
61
+ { capability: "destructive", decision: gated(flags.destructive), reason: "destructive command" },
62
+ { capability: "network", decision: gated(flags.network), reason: "external network action" },
63
+ {
64
+ capability: "credentials",
65
+ decision: flags.credentials ? "deny" : "ask",
66
+ reason: "credential access",
67
+ },
68
+ {
69
+ capability: "outside",
70
+ decision: flags.outside ? "ask" : "allow",
71
+ reason: "path outside the workspace",
72
+ },
73
+ { capability: "system", decision: flags.system ? "deny" : "ask", reason: "system change" },
74
+ ];
75
+ }
76
+ /**
77
+ * Glob matching, `*` within a segment and `**` across them. Small on purpose:
78
+ * a security decision should not depend on a pattern language nobody can hold
79
+ * in their head.
80
+ */
81
+ export function matchesGlob(pattern, target) {
82
+ const normalised = target.replace(/\\/g, "/");
83
+ const source = pattern.replace(/\\/g, "/");
84
+ let regex = "";
85
+ // Scanned rather than built from chained replaces. The chained version got
86
+ // `**` wrong in a way nothing would have noticed: it compiled to a pattern
87
+ // that matched nothing, which silently disabled the FLOOR rule denying system
88
+ // changes. A security decision shouldn't rest on replace ordering.
89
+ for (let index = 0; index < source.length; index++) {
90
+ const char = source[index];
91
+ if (char === "*") {
92
+ if (source[index + 1] === "*") {
93
+ // `**/` crosses zero or more directories; a bare `**` matches the rest
94
+ // of the path, separators included.
95
+ if (source[index + 2] === "/") {
96
+ regex += "(?:.*/)?";
97
+ index += 2;
98
+ }
99
+ else {
100
+ regex += ".*";
101
+ index += 1;
102
+ }
103
+ }
104
+ else {
105
+ regex += "[^/]*";
106
+ }
107
+ continue;
108
+ }
109
+ if (char === "?") {
110
+ regex += "[^/]";
111
+ continue;
112
+ }
113
+ regex += char.replace(/[.+^${}()|[\]\\]/g, "\\$&");
114
+ }
115
+ return new RegExp(`^${regex}$`).test(normalised);
116
+ }
117
+ /** First matching rule wins, floor first. An unmatched capability is denied. */
118
+ export function evaluate(rules, request) {
119
+ for (const rule of FLOOR) {
120
+ if (applies(rule, request)) {
121
+ return { decision: rule.decision, reason: rule.reason ?? "not permitted", fixed: true };
122
+ }
123
+ }
124
+ for (const rule of rules) {
125
+ if (applies(rule, request)) {
126
+ return { decision: rule.decision, reason: rule.reason ?? rule.capability, fixed: false };
127
+ }
128
+ }
129
+ return { decision: "deny", reason: `no rule covers ${request.capability}`, fixed: false };
130
+ }
131
+ const applies = (rule, request) => rule.capability === request.capability &&
132
+ (rule.match === undefined || matchesGlob(rule.match, request.target));
133
+ /** Refused outright: no approval prompt, because there is no good answer to it. */
134
+ const FORBIDDEN = [
135
+ { pattern: /\bmkfs(\.\w+)?\b/, why: "formats a filesystem" },
136
+ { pattern: /\bdd\b[^\n]*\bof=\s*\/dev\//, why: "writes directly to a device" },
137
+ { pattern: /\b(shutdown|reboot|halt|poweroff)\b/, why: "changes machine state" },
138
+ { pattern: /\brm\s+(-[a-zA-Z]*\s+)*\/(\s|$)/, why: "deletes the filesystem root" },
139
+ { pattern: /\b(chmod|chown)\s+-R\b[^\n]*\s\/(\s|$)/, why: "rewrites the filesystem root" },
140
+ { pattern: /\b(curl|wget)\b[^\n]*\|\s*(ba|z|k)?sh\b/, why: "pipes a download into a shell" },
141
+ ];
142
+ const DESTRUCTIVE = [
143
+ { pattern: /\brm\s+-[a-zA-Z]*[rf]/, why: "recursive or forced delete" },
144
+ { pattern: /\bgit\s+push\b[^\n]*--force/, why: "force push" },
145
+ { pattern: /\bgit\s+(reset\s+--hard|clean\s+-[a-zA-Z]*[fd])/, why: "discards working-tree changes" },
146
+ { pattern: /\bgit\s+branch\s+-D\b/, why: "deletes a branch" },
147
+ { pattern: /\bnpm\s+(publish|unpublish)\b/, why: "publishes a package" },
148
+ { pattern: /\b(drop|truncate)\s+(table|database)\b/i, why: "drops data" },
149
+ { pattern: /\bdocker\s+(rm|rmi|system\s+prune)\b/, why: "removes containers or images" },
150
+ { pattern: /\bkill(all)?\s+-9\b/, why: "force-kills processes" },
151
+ ];
152
+ const NETWORKED = [
153
+ { pattern: /\b(curl|wget|nc|ssh|scp|rsync|ftp)\b/, why: "reaches the network" },
154
+ { pattern: /\b(npm|pnpm|yarn|bun)\s+(i|install|add|ci|update)\b/, why: "downloads packages" },
155
+ { pattern: /\b(pip3?|uv|poetry)\s+(install|add|sync)\b/, why: "downloads packages" },
156
+ {
157
+ pattern: /\b(go\s+get|cargo\s+(add|install)|gem\s+install|apt-get|apk\s+add|brew\s+install)\b/,
158
+ why: "downloads packages",
159
+ },
160
+ { pattern: /\bgit\s+(push|pull|fetch|clone)\b/, why: "talks to a remote" },
161
+ ];
162
+ const PRIVILEGED = [
163
+ { pattern: /\b(sudo|doas|su)\b/, why: "asks for elevated privileges" },
164
+ { pattern: /\b(systemctl|launchctl|service)\b/, why: "changes system services" },
165
+ { pattern: /\b(useradd|userdel|visudo)\b/, why: "changes system users" },
166
+ ];
167
+ const CREDENTIAL_READS = [
168
+ { pattern: /\b(printenv|env)\b(?!\s*\w+=)/, why: "dumps the environment" },
169
+ { pattern: /\bcat\b[^\n]*\.(env|npmrc|netrc|pypirc)\b/, why: "reads a credential file" },
170
+ { pattern: /\b(security|keychain|gpg|pass)\s/, why: "reads a credential store" },
171
+ { pattern: /\baws\s+configure\b/, why: "reads cloud credentials" },
172
+ ];
173
+ /**
174
+ * Read a command line and say what it is. Deliberately over-broad: a false
175
+ * "this looks destructive" costs one keystroke, a false "this looks harmless"
176
+ * costs a working tree.
177
+ */
178
+ export function classifyCommand(command) {
179
+ const hits = (patterns) => patterns.filter((entry) => entry.pattern.test(command)).map((entry) => entry.why);
180
+ const forbidden = hits(FORBIDDEN);
181
+ if (forbidden.length > 0) {
182
+ return { capability: "destructive", reasons: forbidden, forbidden: true };
183
+ }
184
+ const credentials = hits(CREDENTIAL_READS);
185
+ if (credentials.length > 0) {
186
+ return { capability: "credentials", reasons: credentials, forbidden: false };
187
+ }
188
+ const privileged = hits(PRIVILEGED);
189
+ if (privileged.length > 0)
190
+ return { capability: "system", reasons: privileged, forbidden: false };
191
+ const destructive = hits(DESTRUCTIVE);
192
+ if (destructive.length > 0) {
193
+ return { capability: "destructive", reasons: destructive, forbidden: false };
194
+ }
195
+ const networked = hits(NETWORKED);
196
+ if (networked.length > 0)
197
+ return { capability: "network", reasons: networked, forbidden: false };
198
+ return { capability: "execute", reasons: [], forbidden: false };
199
+ }
200
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/permissions/rules.ts"],"names":[],"mappings":"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"}