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,180 @@
|
|
|
1
|
+
// The agent runtime. Everything that knows about everything else lives here,
|
|
2
|
+
// so the pieces underneath don't have to know about each other.
|
|
3
|
+
//
|
|
4
|
+
// The shape worth noticing is which way the arrows point. The runtime builds
|
|
5
|
+
// the policy, the gate and the trace, and hands tools a context that can *ask*
|
|
6
|
+
// for permission. It never hands them the thing that answers. The approver is a
|
|
7
|
+
// function the CLI closed over a terminal, and it stays in this file's closure
|
|
8
|
+
// — that is the mechanical reason the model cannot approve its own actions,
|
|
9
|
+
// rather than an instruction somewhere asking it not to.
|
|
10
|
+
//
|
|
11
|
+
// This is internal. Vaan ships as an agent, not a library — package.json
|
|
12
|
+
// exports nothing but itself, so importing "vaan" fails cleanly rather than
|
|
13
|
+
// half-working against interfaces that are still moving.
|
|
14
|
+
import { continueAgent } from "./agent.js";
|
|
15
|
+
import { defaultConfig } from "./config.js";
|
|
16
|
+
import { noMemory } from "./memory/index.js";
|
|
17
|
+
import { needsMemory } from "./memory/context.js";
|
|
18
|
+
import { openMemory } from "./memory/store.js";
|
|
19
|
+
import { newRequest } from "./observability/request.js";
|
|
20
|
+
import { openTrace } from "./observability/trace.js";
|
|
21
|
+
import { approveAll, createGate, denyAll } from "./permissions/approval.js";
|
|
22
|
+
import { createPolicy } from "./permissions/policy.js";
|
|
23
|
+
import { home } from "./paths.js";
|
|
24
|
+
import { buildSystemPrompt, loadPersona } from "./prompt.js";
|
|
25
|
+
import { resolve } from "./providers/index.js";
|
|
26
|
+
import { loadSkills } from "./skills.js";
|
|
27
|
+
import { builtinTools } from "./tools/index.js";
|
|
28
|
+
import { scrubEnv } from "./sandbox/process.js";
|
|
29
|
+
/** Rough cap on in-process history, trimmed whole rounds at a time. */
|
|
30
|
+
const MAX_HISTORY = 40;
|
|
31
|
+
export function createSession(opts) {
|
|
32
|
+
const env = opts.env ?? process.env;
|
|
33
|
+
const workspace = opts.workspace;
|
|
34
|
+
const config = opts.config ?? defaultConfig(workspace, opts.model);
|
|
35
|
+
const { provider, model } = resolve(opts.model, env);
|
|
36
|
+
const memoryOn = opts.memory ?? config.memory;
|
|
37
|
+
const memory = memoryOn ? openMemory({ dir: workspace }) : noMemory;
|
|
38
|
+
const persona = loadPersona(workspace);
|
|
39
|
+
const { skills, warnings: skillWarnings } = loadSkills(workspace);
|
|
40
|
+
const policy = createPolicy({ workspace, approvals: config.approvals });
|
|
41
|
+
// The gate lives for the session so that "yes, run npm test" holds for the
|
|
42
|
+
// session. The trace it writes to changes every request, hence the accessor.
|
|
43
|
+
let current;
|
|
44
|
+
const gate = createGate({
|
|
45
|
+
policy,
|
|
46
|
+
approve: opts.yes ? approveAll : (opts.approve ?? denyAll),
|
|
47
|
+
trace: () => current,
|
|
48
|
+
memoise: !config.sandbox.confirmEveryCommand,
|
|
49
|
+
});
|
|
50
|
+
const tools = builtinTools({
|
|
51
|
+
groups: config.tools,
|
|
52
|
+
...(opts.search ? { search: opts.search } : {}),
|
|
53
|
+
});
|
|
54
|
+
// Credentials are stripped once, here, rather than at each spawn: the child
|
|
55
|
+
// environment is a property of the session, not of the command.
|
|
56
|
+
const childEnv = scrubEnv(env);
|
|
57
|
+
// The retrieval gate runs on every turn, so it's worth pointing at something
|
|
58
|
+
// cheap. Unrelated to the permission gate, which shares nothing but the word.
|
|
59
|
+
const recallModel = env.VAAN_GATE_MODEL ? resolve(env.VAAN_GATE_MODEL, env) : { provider, model };
|
|
60
|
+
let history = [];
|
|
61
|
+
const usage = { input: 0, output: 0 };
|
|
62
|
+
return {
|
|
63
|
+
model: opts.model,
|
|
64
|
+
provider,
|
|
65
|
+
memory,
|
|
66
|
+
tools,
|
|
67
|
+
config,
|
|
68
|
+
policy,
|
|
69
|
+
usage,
|
|
70
|
+
warnings: [...persona.warnings, ...skillWarnings],
|
|
71
|
+
async ask(input, handlers = {}) {
|
|
72
|
+
const request = newRequest(input, opts.model, workspace);
|
|
73
|
+
handlers.onRequest?.(request);
|
|
74
|
+
// Events are always collected — the eval graders read them — but only
|
|
75
|
+
// written to disk when tracing is on.
|
|
76
|
+
const trace = openTrace({
|
|
77
|
+
home: home(workspace),
|
|
78
|
+
request,
|
|
79
|
+
persist: opts.trace !== false,
|
|
80
|
+
});
|
|
81
|
+
current = trace;
|
|
82
|
+
const ctx = {
|
|
83
|
+
workspace,
|
|
84
|
+
gate,
|
|
85
|
+
policy,
|
|
86
|
+
trace,
|
|
87
|
+
network: config.network,
|
|
88
|
+
confirm: opts.yes ? async () => true : (opts.confirm ?? (async () => false)),
|
|
89
|
+
remember: async (fact) => memory.remember(workspace, { kind: "fact", text: fact, source: "tool" }),
|
|
90
|
+
env: childEnv,
|
|
91
|
+
...(handlers.signal ? { signal: handlers.signal } : {}),
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
const wanted = await needsMemory(input, {
|
|
95
|
+
provider: recallModel.provider,
|
|
96
|
+
model: recallModel.model,
|
|
97
|
+
...(handlers.signal ? { signal: handlers.signal } : {}),
|
|
98
|
+
});
|
|
99
|
+
handlers.onRecall?.(wanted);
|
|
100
|
+
trace.record({
|
|
101
|
+
kind: "memory",
|
|
102
|
+
op: wanted ? "recall" : "skipped",
|
|
103
|
+
detail: wanted ? input : "the question stands on its own",
|
|
104
|
+
});
|
|
105
|
+
// Facts come back either way — they're injected, not searched, and the
|
|
106
|
+
// gate only decides whether to spend a query on past turns.
|
|
107
|
+
const recalled = await memory.recall(workspace, wanted ? input : "");
|
|
108
|
+
const system = buildSystemPrompt({
|
|
109
|
+
workspace,
|
|
110
|
+
agentName: config.agentName,
|
|
111
|
+
persona,
|
|
112
|
+
facts: recalled.facts,
|
|
113
|
+
turns: wanted ? recalled.turns : [],
|
|
114
|
+
skills,
|
|
115
|
+
toolNames: tools.map((tool) => tool.name),
|
|
116
|
+
});
|
|
117
|
+
history = trim([
|
|
118
|
+
...history,
|
|
119
|
+
{ role: "user", parts: [{ type: "text", text: input }] },
|
|
120
|
+
]);
|
|
121
|
+
const result = await continueAgent(history, {
|
|
122
|
+
provider,
|
|
123
|
+
model,
|
|
124
|
+
system,
|
|
125
|
+
tools,
|
|
126
|
+
ctx,
|
|
127
|
+
trace,
|
|
128
|
+
...(opts.stream ? { stream: true } : {}),
|
|
129
|
+
...(handlers.onEvent ? { onEvent: handlers.onEvent } : {}),
|
|
130
|
+
...(handlers.signal ? { signal: handlers.signal } : {}),
|
|
131
|
+
});
|
|
132
|
+
history = result.messages;
|
|
133
|
+
if (result.usage) {
|
|
134
|
+
usage.input += result.usage.input;
|
|
135
|
+
usage.output += result.usage.output;
|
|
136
|
+
}
|
|
137
|
+
await memory.remember(workspace, {
|
|
138
|
+
kind: "turn",
|
|
139
|
+
userText: input,
|
|
140
|
+
replyText: result.text,
|
|
141
|
+
tools: result.usedTools,
|
|
142
|
+
});
|
|
143
|
+
const stored = trace.finish(true, result.text);
|
|
144
|
+
return { ...result, requestId: request.id, trace: stored };
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
148
|
+
trace.record({ kind: "error", where: "runtime", message: detail });
|
|
149
|
+
trace.finish(false, detail);
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
current = undefined;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
reset() {
|
|
157
|
+
history = [];
|
|
158
|
+
},
|
|
159
|
+
close() {
|
|
160
|
+
memory.close?.();
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Keep the transcript from growing without limit, dropping whole rounds from
|
|
166
|
+
* the front. Trimming at an arbitrary index would orphan a tool_result from its
|
|
167
|
+
* tool_call, which providers reject — so cuts only happen at a plain user turn.
|
|
168
|
+
*/
|
|
169
|
+
function trim(messages) {
|
|
170
|
+
if (messages.length <= MAX_HISTORY)
|
|
171
|
+
return messages;
|
|
172
|
+
for (let index = messages.length - MAX_HISTORY; index < messages.length; index++) {
|
|
173
|
+
const message = messages[index];
|
|
174
|
+
if (message?.role === "user" && message.parts.every((part) => part.type === "text")) {
|
|
175
|
+
return messages.slice(index);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return messages;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// What memory actually contributes to a turn: the gate that decides whether to
|
|
2
|
+
// look, and the shape recalled memory takes once it's in the prompt.
|
|
3
|
+
//
|
|
4
|
+
// The gate is the most visible thing Vaan does. Before touching memory, one
|
|
5
|
+
// cheap call answers one question: does answering this need anything we've been
|
|
6
|
+
// told? "What's 2+2" doesn't. "When am I seeing Alex" does. Skipping the search
|
|
7
|
+
// on the first kind keeps the prompt small and the turn fast.
|
|
8
|
+
//
|
|
9
|
+
// It fails open. If the gate errors, times out, or answers something we can't
|
|
10
|
+
// read, we retrieve — a slightly larger prompt is a much smaller problem than an
|
|
11
|
+
// agent that forgot you.
|
|
12
|
+
const GATE_SYSTEM = `You decide whether answering a message needs the user's stored memory.
|
|
13
|
+
|
|
14
|
+
Reply with exactly one word: YES or NO.
|
|
15
|
+
|
|
16
|
+
YES when the message touches the user themselves — their history, preferences,
|
|
17
|
+
plans, people, projects, or anything they would have had to tell you.
|
|
18
|
+
NO when the message stands on its own: arithmetic, general knowledge, a
|
|
19
|
+
definition, a translation, or writing something from scratch.`;
|
|
20
|
+
export async function needsMemory(message, opts) {
|
|
21
|
+
try {
|
|
22
|
+
const reply = await opts.provider.generate({
|
|
23
|
+
model: opts.model,
|
|
24
|
+
system: GATE_SYSTEM,
|
|
25
|
+
messages: [{ role: "user", parts: [{ type: "text", text: message }] }],
|
|
26
|
+
tools: [],
|
|
27
|
+
maxTokens: 8,
|
|
28
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
29
|
+
});
|
|
30
|
+
const answer = reply.message.parts
|
|
31
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
32
|
+
.join("")
|
|
33
|
+
.trim()
|
|
34
|
+
.toUpperCase();
|
|
35
|
+
return !answer.startsWith("NO");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Facts are injected, not searched, so they carry their own framing. */
|
|
42
|
+
export function factsSection(facts) {
|
|
43
|
+
if (facts.length === 0)
|
|
44
|
+
return undefined;
|
|
45
|
+
return (`# What you know about this user\n\n` +
|
|
46
|
+
`Things they've told you in past sessions. Use them without being asked, and without\n` +
|
|
47
|
+
`announcing that you remembered.\n\n` +
|
|
48
|
+
facts.map((fact) => `- ${fact.text}`).join("\n"));
|
|
49
|
+
}
|
|
50
|
+
/** Recalled turns are retrieved, so they say so — retrieval can and does miss. */
|
|
51
|
+
export function turnsSection(turns) {
|
|
52
|
+
if (turns.length === 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
return (`# Relevant past conversation\n\n` +
|
|
55
|
+
`Retrieved because it looked related to what was just asked. It may not be.\n\n` +
|
|
56
|
+
turns.map(formatTurn).join("\n\n"));
|
|
57
|
+
}
|
|
58
|
+
const TURN_CLIP = 500;
|
|
59
|
+
const formatTurn = (turn) => `[${turn.ts.slice(0, 10)}]\n` +
|
|
60
|
+
`user: ${clip(turn.userText, TURN_CLIP)}\n` +
|
|
61
|
+
`you: ${clip(turn.replyText, TURN_CLIP)}`;
|
|
62
|
+
const clip = (text, max) => text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
63
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/memory/context.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,qEAAqE;AACrE,EAAE;AACF,4EAA4E;AAC5E,gFAAgF;AAChF,gFAAgF;AAChF,8DAA8D;AAC9D,EAAE;AACF,8EAA8E;AAC9E,iFAAiF;AACjF,yBAAyB;AAKzB,MAAM,WAAW,GAAG;;;;;;;8DAO0C,CAAC;AAQ/D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAe,EAAE,IAAiB;IAClE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACzC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,WAAW;YACnB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;YACtE,KAAK,EAAE,EAAE;YACT,SAAS,EAAE,CAAC;YACZ,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChD,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK;aAC/B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;aACtD,IAAI,CAAC,EAAE,CAAC;aACR,IAAI,EAAE;aACN,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,OAAO,CACL,qCAAqC;QACrC,uFAAuF;QACvF,qCAAqC;QACrC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACjD,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,OAAO,CACL,kCAAkC;QAClC,gFAAgF;QAChF,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CACnC,CAAC;AACJ,CAAC;AAED,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,MAAM,UAAU,GAAG,CAAC,IAAU,EAAU,EAAE,CACxC,IAAI,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK;IAC7B,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI;IAC3C,QAAQ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;AAE5C,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,GAAW,EAAU,EAAE,CACjD,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Two tiers, and the difference is the whole design.
|
|
2
|
+
//
|
|
3
|
+
// Facts are short statements about the user. They go into the system prompt on
|
|
4
|
+
// every turn, so they work even when the question doesn't mention them.
|
|
5
|
+
// "Allergic to prawns" has to surface when you ask what to order, not only
|
|
6
|
+
// when you say the word prawn.
|
|
7
|
+
//
|
|
8
|
+
// Turns are past conversations, searched full-text and ranked against whatever
|
|
9
|
+
// you just asked. Only the relevant ones come back.
|
|
10
|
+
//
|
|
11
|
+
// Why both: retrieval can miss, injection can't. Injection is paid on every
|
|
12
|
+
// turn, so the facts tier stays small on purpose.
|
|
13
|
+
export const EMPTY_RECALL = { facts: [], turns: [] };
|
|
14
|
+
/**
|
|
15
|
+
* A memory that remembers nothing, for `--no-memory` and for tests. Keeping the
|
|
16
|
+
* null case behind the same interface means nothing downstream needs to ask
|
|
17
|
+
* whether memory is on.
|
|
18
|
+
*/
|
|
19
|
+
export const noMemory = {
|
|
20
|
+
async recall() {
|
|
21
|
+
return EMPTY_RECALL;
|
|
22
|
+
},
|
|
23
|
+
async remember() { },
|
|
24
|
+
async forget() {
|
|
25
|
+
return 0;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/memory/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,EAAE;AACF,+EAA+E;AAC/E,wEAAwE;AACxE,2EAA2E;AAC3E,+BAA+B;AAC/B,EAAE;AACF,+EAA+E;AAC/E,oDAAoD;AACpD,EAAE;AACF,4EAA4E;AAC5E,kDAAkD;AAwClD,MAAM,CAAC,MAAM,YAAY,GAAW,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAE7D;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAW;IAC9B,KAAK,CAAC,MAAM;QACV,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,KAAK,CAAC,QAAQ,KAAI,CAAC;IACnB,KAAK,CAAC,MAAM;QACV,OAAO,CAAC,CAAC;IACX,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Turning a sentence into something FTS5 will accept.
|
|
2
|
+
//
|
|
3
|
+
// This is separated from the store because it is the part with no I/O in it and
|
|
4
|
+
// the part most likely to be wrong: user text goes straight into query syntax,
|
|
5
|
+
// where a stray quote or the bare word "AND" is a syntax error rather than a
|
|
6
|
+
// search term.
|
|
7
|
+
/** Words too common to be worth a BM25 term. Short list on purpose. */
|
|
8
|
+
export const STOPWORDS = new Set(("the a an and or but if then than that this these those is are was were be been being do does did " +
|
|
9
|
+
"for to of in on at by with from about into over after before you your my me i it its as what when " +
|
|
10
|
+
"where who how why can could should would will just have has had not no yes please").split(" "));
|
|
11
|
+
/** Terms past this add noise rather than signal, and cost query time. */
|
|
12
|
+
const MAX_TERMS = 12;
|
|
13
|
+
const MIN_TERM_LENGTH = 3;
|
|
14
|
+
/**
|
|
15
|
+
* An FTS5 MATCH expression, or undefined when there's nothing worth searching.
|
|
16
|
+
*
|
|
17
|
+
* Reducing to alphanumeric words and quoting each one sidesteps the whole
|
|
18
|
+
* grammar: no operator can survive the reduction, so no input can be a syntax
|
|
19
|
+
* error, and `OR` between terms lets BM25 rank rather than filter.
|
|
20
|
+
*/
|
|
21
|
+
export function toMatchQuery(text) {
|
|
22
|
+
const useful = terms(text);
|
|
23
|
+
return useful.length > 0 ? useful.map((word) => `"${word}"`).join(" OR ") : undefined;
|
|
24
|
+
}
|
|
25
|
+
/** The words a query actually searches on. Exposed so tests can assert on them. */
|
|
26
|
+
export function terms(text) {
|
|
27
|
+
const words = text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
28
|
+
return words
|
|
29
|
+
.filter((word) => word.length >= MIN_TERM_LENGTH && !STOPWORDS.has(word))
|
|
30
|
+
.slice(0, MAX_TERMS);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/memory/search.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,6EAA6E;AAC7E,eAAe;AAEf,uEAAuE;AACvE,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,GAAG,CAC9B,CAAC,mGAAmG;IAClG,oGAAoG;IACpG,mFAAmF,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAClG,CAAC;AAEF,yEAAyE;AACzE,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxF,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,KAAK,CAAC,IAAY;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;IAChE,OAAO,KAAK;SACT,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,eAAe,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;SACxE,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// SQLite with FTS5, at .vaan/memory/state.db.
|
|
2
|
+
//
|
|
3
|
+
// Why a native dependency rather than a flat file: ranked retrieval needs real
|
|
4
|
+
// BM25 scoring, and Node's built-in node:sqlite ships compiled *without* FTS5
|
|
5
|
+
// (nodejs/node#56951), so `CREATE VIRTUAL TABLE … USING fts5` throws there.
|
|
6
|
+
// better-sqlite3 bundles its own SQLite with FTS5 enabled. The alternative was
|
|
7
|
+
// hand-rolling TF-IDF over JSON files, which we'd then own and get subtly wrong.
|
|
8
|
+
//
|
|
9
|
+
// Nothing in here is allowed to throw. A corrupt database, a locked file, a
|
|
10
|
+
// disk that filled up — none of that should stop the agent answering. Every
|
|
11
|
+
// public method falls back to "no memory" and warns once.
|
|
12
|
+
import Database from "better-sqlite3";
|
|
13
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { databaseFile, memoryDir, memoryMirror } from "../paths.js";
|
|
15
|
+
import { EMPTY_RECALL, } from "./index.js";
|
|
16
|
+
import { toMatchQuery } from "./search.js";
|
|
17
|
+
const SCHEMA = `
|
|
18
|
+
CREATE TABLE IF NOT EXISTS turns (
|
|
19
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
20
|
+
scope TEXT NOT NULL,
|
|
21
|
+
ts TEXT NOT NULL,
|
|
22
|
+
user_text TEXT NOT NULL,
|
|
23
|
+
reply_text TEXT NOT NULL,
|
|
24
|
+
tools_json TEXT NOT NULL
|
|
25
|
+
);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS turns_by_scope ON turns(scope, id DESC);
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS facts (
|
|
29
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
30
|
+
scope TEXT NOT NULL,
|
|
31
|
+
text TEXT NOT NULL,
|
|
32
|
+
created_at TEXT NOT NULL,
|
|
33
|
+
source TEXT NOT NULL,
|
|
34
|
+
UNIQUE(scope, text)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS turns_fts USING fts5(user_text, reply_text);
|
|
38
|
+
`;
|
|
39
|
+
/**
|
|
40
|
+
* Confirm this build of SQLite actually has FTS5. Returns the failure message,
|
|
41
|
+
* or undefined when all is well. The postinstall script runs the same check;
|
|
42
|
+
* the onboarding runs it again because a working install can still be moved to
|
|
43
|
+
* a machine whose native module doesn't load.
|
|
44
|
+
*/
|
|
45
|
+
export function probeFts5() {
|
|
46
|
+
try {
|
|
47
|
+
const db = new Database(":memory:");
|
|
48
|
+
db.exec("CREATE VIRTUAL TABLE probe USING fts5(x)");
|
|
49
|
+
db.close();
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
return err instanceof Error ? err.message : String(err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function openMemory(opts) {
|
|
57
|
+
const turnLimit = opts.turnLimit ?? 5;
|
|
58
|
+
const factLimit = opts.factLimit ?? 64;
|
|
59
|
+
mkdirSync(memoryDir(opts.dir), { recursive: true });
|
|
60
|
+
const db = new Database(databaseFile(opts.dir));
|
|
61
|
+
db.pragma("journal_mode = WAL");
|
|
62
|
+
db.exec(SCHEMA);
|
|
63
|
+
const statements = {
|
|
64
|
+
facts: db.prepare("SELECT id, text, created_at, source FROM facts WHERE scope = ? ORDER BY id LIMIT ?"),
|
|
65
|
+
insertFact: db.prepare("INSERT OR IGNORE INTO facts (scope, text, created_at, source) VALUES (?, ?, ?, ?)"),
|
|
66
|
+
deleteFact: db.prepare("DELETE FROM facts WHERE scope = ? AND id = ?"),
|
|
67
|
+
insertTurn: db.prepare("INSERT INTO turns (scope, ts, user_text, reply_text, tools_json) VALUES (?, ?, ?, ?, ?)"),
|
|
68
|
+
indexTurn: db.prepare("INSERT INTO turns_fts (rowid, user_text, reply_text) VALUES (?, ?, ?)"),
|
|
69
|
+
recent: db.prepare("SELECT id, ts, user_text, reply_text, tools_json FROM turns WHERE scope = ? ORDER BY id DESC LIMIT ?"),
|
|
70
|
+
search: db.prepare(`SELECT t.id, t.ts, t.user_text, t.reply_text, t.tools_json
|
|
71
|
+
FROM turns_fts f JOIN turns t ON t.id = f.rowid
|
|
72
|
+
WHERE turns_fts MATCH ? AND t.scope = ?
|
|
73
|
+
ORDER BY bm25(turns_fts) LIMIT ?`),
|
|
74
|
+
};
|
|
75
|
+
const readFacts = (scope) => statements.facts.all(scope, factLimit).map(toFact);
|
|
76
|
+
function mirror(scope) {
|
|
77
|
+
// The database is authoritative. This file is for humans, and is
|
|
78
|
+
// regenerated from the facts table every time they change.
|
|
79
|
+
const facts = readFacts(scope);
|
|
80
|
+
const lines = [
|
|
81
|
+
"# MEMORY.md",
|
|
82
|
+
"",
|
|
83
|
+
"<!-- Generated by Vaan from .vaan/memory/state.db. Edits here are overwritten.",
|
|
84
|
+
" To remove a fact, use /forget in the REPL. -->",
|
|
85
|
+
"",
|
|
86
|
+
...(facts.length === 0
|
|
87
|
+
? ["_Nothing remembered yet._"]
|
|
88
|
+
: facts.map((fact) => `- ${fact.text} \`#${fact.id}\``)),
|
|
89
|
+
"",
|
|
90
|
+
];
|
|
91
|
+
writeFileSync(memoryMirror(opts.dir), lines.join("\n"), "utf8");
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
async recall(scope, query) {
|
|
95
|
+
return guard(EMPTY_RECALL, () => {
|
|
96
|
+
const facts = readFacts(scope);
|
|
97
|
+
const match = toMatchQuery(query);
|
|
98
|
+
const rows = match
|
|
99
|
+
? statements.search.all(match, scope, turnLimit)
|
|
100
|
+
: statements.recent.all(scope, turnLimit);
|
|
101
|
+
return { facts, turns: rows.map(toTurn) };
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
async remember(scope, item) {
|
|
105
|
+
return guard(undefined, () => {
|
|
106
|
+
if (item.kind === "fact") {
|
|
107
|
+
const text = item.text.trim();
|
|
108
|
+
if (!text)
|
|
109
|
+
return;
|
|
110
|
+
statements.insertFact.run(scope, text, new Date().toISOString(), item.source ?? "tool");
|
|
111
|
+
mirror(scope);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const result = statements.insertTurn.run(scope, new Date().toISOString(), item.userText, item.replyText, JSON.stringify(item.tools));
|
|
115
|
+
statements.indexTurn.run(result.lastInsertRowid, item.userText, item.replyText);
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
async forget(scope, factId) {
|
|
119
|
+
return guard(0, () => {
|
|
120
|
+
const removed = statements.deleteFact.run(scope, factId).changes;
|
|
121
|
+
if (removed > 0)
|
|
122
|
+
mirror(scope);
|
|
123
|
+
return removed;
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
close() {
|
|
127
|
+
guard(undefined, () => db.close());
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const toFact = (row) => ({
|
|
132
|
+
id: row.id,
|
|
133
|
+
text: row.text,
|
|
134
|
+
createdAt: row.created_at,
|
|
135
|
+
source: row.source,
|
|
136
|
+
});
|
|
137
|
+
const toTurn = (row) => ({
|
|
138
|
+
id: row.id,
|
|
139
|
+
ts: row.ts,
|
|
140
|
+
userText: row.user_text,
|
|
141
|
+
replyText: row.reply_text,
|
|
142
|
+
tools: parseTools(row.tools_json),
|
|
143
|
+
});
|
|
144
|
+
function parseTools(json) {
|
|
145
|
+
try {
|
|
146
|
+
const parsed = JSON.parse(json);
|
|
147
|
+
return Array.isArray(parsed)
|
|
148
|
+
? parsed.filter((item) => typeof item === "string")
|
|
149
|
+
: [];
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
let warned = false;
|
|
156
|
+
/** Run `fn`, and on any failure warn once and hand back `fallback`. */
|
|
157
|
+
function guard(fallback, fn) {
|
|
158
|
+
try {
|
|
159
|
+
return fn();
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
if (!warned) {
|
|
163
|
+
warned = true;
|
|
164
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
165
|
+
process.stderr.write(`vaan: memory is unavailable (${detail}). Continuing without it.\n`);
|
|
166
|
+
}
|
|
167
|
+
return fallback;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export { toMatchQuery };
|
|
171
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/memory/store.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,4EAA4E;AAC5E,+EAA+E;AAC/E,iFAAiF;AACjF,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,0DAA0D;AAE1D,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EACL,YAAY,GAMb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBd,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC;QACpC,EAAE,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;QACpD,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AASD,MAAM,UAAU,UAAU,CAAC,IAAmB;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;IAEvC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEhB,MAAM,UAAU,GAAG;QACjB,KAAK,EAAE,EAAE,CAAC,OAAO,CACf,oFAAoF,CACrF;QACD,UAAU,EAAE,EAAE,CAAC,OAAO,CACpB,mFAAmF,CACpF;QACD,UAAU,EAAE,EAAE,CAAC,OAAO,CAAC,8CAA8C,CAAC;QACtE,UAAU,EAAE,EAAE,CAAC,OAAO,CACpB,yFAAyF,CAC1F;QACD,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,uEAAuE,CAAC;QAC9F,MAAM,EAAE,EAAE,CAAC,OAAO,CAChB,sGAAsG,CACvG;QACD,MAAM,EAAE,EAAE,CAAC,OAAO,CAChB;;;yCAGmC,CACpC;KACF,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,KAAa,EAAU,EAAE,CACzC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAEpE,SAAS,MAAM,CAAC,KAAa;QAC3B,iEAAiE;QACjE,2DAA2D;QAC3D,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG;YACZ,aAAa;YACb,EAAE;YACF,gFAAgF;YAChF,qDAAqD;YACrD,EAAE;YACF,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;gBACpB,CAAC,CAAC,CAAC,2BAA2B,CAAC;gBAC/B,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,QAAQ,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3D,EAAE;SACH,CAAC;QACF,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IAClE,CAAC;IAED,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK;YACvB,OAAO,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE;gBAC9B,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;gBAC/B,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;gBAClC,MAAM,IAAI,GAAG,KAAK;oBAChB,CAAC,CAAE,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAe;oBAC/D,CAAC,CAAE,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAe,CAAC;gBAC3D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5C,CAAC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI;YACxB,OAAO,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE;gBAC3B,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;oBAC9B,IAAI,CAAC,IAAI;wBAAE,OAAO;oBAClB,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;oBACxF,MAAM,CAAC,KAAK,CAAC,CAAC;oBACd,OAAO;gBACT,CAAC;gBACD,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,CACtC,KAAK,EACL,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EACxB,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,SAAS,EACd,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAC3B,CAAC;gBACF,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAClF,CAAC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM;YACxB,OAAO,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE;gBACnB,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC;gBACjE,IAAI,OAAO,GAAG,CAAC;oBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC/B,OAAO,OAAO,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC;QAED,KAAK;YACH,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC;AAiBD,MAAM,MAAM,GAAG,CAAC,GAAY,EAAQ,EAAE,CAAC,CAAC;IACtC,EAAE,EAAE,GAAG,CAAC,EAAE;IACV,IAAI,EAAE,GAAG,CAAC,IAAI;IACd,SAAS,EAAE,GAAG,CAAC,UAAU;IACzB,MAAM,EAAE,GAAG,CAAC,MAAM;CACnB,CAAC,CAAC;AAEH,MAAM,MAAM,GAAG,CAAC,GAAY,EAAQ,EAAE,CAAC,CAAC;IACtC,EAAE,EAAE,GAAG,CAAC,EAAE;IACV,EAAE,EAAE,GAAG,CAAC,EAAE;IACV,QAAQ,EAAE,GAAG,CAAC,SAAS;IACvB,SAAS,EAAE,GAAG,CAAC,UAAU;IACzB,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC;CAClC,CAAC,CAAC;AAEH,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAC1B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC;YACnE,CAAC,CAAC,EAAE,CAAC;IACT,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,IAAI,MAAM,GAAG,KAAK,CAAC;AAEnB,uEAAuE;AACvE,SAAS,KAAK,CAAI,QAAW,EAAE,EAAW;IACxC,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,IAAI,CAAC;YACd,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,MAAM,6BAA6B,CAAC,CAAC;QAC5F,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// The event taxonomy, and the redactor.
|
|
2
|
+
//
|
|
3
|
+
// This module is a leaf on purpose: permissions, the sandbox, the providers and
|
|
4
|
+
// the loop all emit into it, so it must not import any of them. Events are
|
|
5
|
+
// plain data with string discriminants for exactly that reason.
|
|
6
|
+
//
|
|
7
|
+
// Nothing here is allowed to record a credential. `redact` runs over every
|
|
8
|
+
// payload on the way in, not on the way out, so a secret is never written to
|
|
9
|
+
// disk in the first place — a trace file that has to be sanitised later is a
|
|
10
|
+
// trace file that already leaked.
|
|
11
|
+
/** Field names whose value is a secret whatever it looks like. */
|
|
12
|
+
const SECRET_KEYS = /^(?:.*_)?(?:api[-_]?key|apikey|authorization|auth|token|access[-_]?token|refresh[-_]?token|secret|password|passwd|credential|credentials|cookie|session|private[-_]?key)$/i;
|
|
13
|
+
/** Shapes that are a secret whatever they're called. */
|
|
14
|
+
const SECRET_VALUES = [
|
|
15
|
+
/\bsk-[A-Za-z0-9_-]{16,}/g, // OpenAI, Anthropic
|
|
16
|
+
/\bsk-ant-[A-Za-z0-9_-]{16,}/g,
|
|
17
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}/g, // GitHub
|
|
18
|
+
/\bAKIA[0-9A-Z]{16}\b/g, // AWS access key id
|
|
19
|
+
/\bAIza[0-9A-Za-z_-]{30,}\b/g, // Google
|
|
20
|
+
/\bxox[abprs]-[A-Za-z0-9-]{10,}/g, // Slack
|
|
21
|
+
/\bBearer\s+[A-Za-z0-9._~+/-]{20,}=*/gi,
|
|
22
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, // JWT
|
|
23
|
+
];
|
|
24
|
+
export const REDACTED = "[redacted]";
|
|
25
|
+
/** Strip anything that looks like a credential out of a string. */
|
|
26
|
+
export function redactText(text) {
|
|
27
|
+
let out = text;
|
|
28
|
+
for (const pattern of SECRET_VALUES)
|
|
29
|
+
out = out.replace(pattern, REDACTED);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Deep-copy `value` with every secret removed: keys whose *name* marks them
|
|
34
|
+
* secret lose their value entirely, and every remaining string is scanned for
|
|
35
|
+
* secret-shaped substrings. Cycles are cut rather than followed.
|
|
36
|
+
*/
|
|
37
|
+
export function redact(value, seen = new WeakSet()) {
|
|
38
|
+
if (typeof value === "string")
|
|
39
|
+
return redactText(value);
|
|
40
|
+
if (value === null || typeof value !== "object")
|
|
41
|
+
return value;
|
|
42
|
+
if (seen.has(value))
|
|
43
|
+
return "[circular]";
|
|
44
|
+
seen.add(value);
|
|
45
|
+
if (Array.isArray(value))
|
|
46
|
+
return value.map((item) => redact(item, seen));
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [key, item] of Object.entries(value)) {
|
|
49
|
+
out[key] = SECRET_KEYS.test(key) ? REDACTED : redact(item, seen);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/** Redact an event's free-form payload. Called once, as the event is recorded. */
|
|
54
|
+
export function redactEvent(event) {
|
|
55
|
+
switch (event.kind) {
|
|
56
|
+
case "tool_call":
|
|
57
|
+
return { ...event, input: redact(event.input) };
|
|
58
|
+
case "request":
|
|
59
|
+
return { ...event, input: redactText(event.input) };
|
|
60
|
+
case "tool_result":
|
|
61
|
+
return { ...event, summary: redactText(event.summary) };
|
|
62
|
+
case "result":
|
|
63
|
+
return { ...event, summary: redactText(event.summary) };
|
|
64
|
+
case "error":
|
|
65
|
+
return { ...event, message: redactText(event.message) };
|
|
66
|
+
case "permission":
|
|
67
|
+
return { ...event, target: redactText(event.target), reason: redactText(event.reason) };
|
|
68
|
+
case "sandbox":
|
|
69
|
+
return {
|
|
70
|
+
...event,
|
|
71
|
+
target: redactText(event.target),
|
|
72
|
+
...(event.detail === undefined ? {} : { detail: redactText(event.detail) }),
|
|
73
|
+
};
|
|
74
|
+
case "memory":
|
|
75
|
+
return { ...event, detail: redactText(event.detail) };
|
|
76
|
+
default:
|
|
77
|
+
return event;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** One line per event, for `vaan trace`. */
|
|
81
|
+
export function describeEvent(event) {
|
|
82
|
+
switch (event.kind) {
|
|
83
|
+
case "request":
|
|
84
|
+
return `request ${clip(event.input, 60)}`;
|
|
85
|
+
case "model_call": {
|
|
86
|
+
const tokens = event.tokensIn === undefined ? "" : ` ${event.tokensIn} in / ${event.tokensOut ?? 0} out`;
|
|
87
|
+
return `model ${event.provider}/${event.model}${tokens}`;
|
|
88
|
+
}
|
|
89
|
+
case "tool_call":
|
|
90
|
+
return `tool ${event.tool}`;
|
|
91
|
+
case "tool_result":
|
|
92
|
+
return `${event.ok ? "result " : "FAILED "} ${event.tool} — ${clip(event.summary, 60)}`;
|
|
93
|
+
case "permission":
|
|
94
|
+
return `permit ${event.capability} ${clip(event.target, 40)} — ${event.decision.toUpperCase()}`;
|
|
95
|
+
case "sandbox":
|
|
96
|
+
return `sandbox ${event.op} ${clip(event.target, 50)}${event.ok ? "" : " — failed"}`;
|
|
97
|
+
case "memory":
|
|
98
|
+
return `memory ${event.op} ${clip(event.detail, 50)}`;
|
|
99
|
+
case "error":
|
|
100
|
+
return `error ${event.where}: ${clip(event.message, 60)}`;
|
|
101
|
+
case "result":
|
|
102
|
+
return `${event.ok ? "done" : "FAIL"} ${clip(event.summary, 60)}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const clip = (text, max) => {
|
|
106
|
+
const line = text.replace(/\s+/g, " ").trim();
|
|
107
|
+
return line.length <= max ? line : `${line.slice(0, max)}…`;
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/observability/events.ts"],"names":[],"mappings":"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"}
|