@vornicx/origin 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 +21 -0
- package/README.md +238 -0
- package/dist/apollo/index.d.ts +87 -0
- package/dist/apollo/index.js +140 -0
- package/dist/apollo/index.js.map +1 -0
- package/dist/chunk-77G3SI57.js +1119 -0
- package/dist/chunk-77G3SI57.js.map +1 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +137 -0
- package/dist/index.js.map +1 -0
- package/dist/policy-KjPvxtus.d.ts +605 -0
- package/package.json +96 -0
|
@@ -0,0 +1,1119 @@
|
|
|
1
|
+
import { readFile, mkdir, appendFile, readdir, stat } from 'fs/promises';
|
|
2
|
+
import { join, relative, resolve, sep, isAbsolute, extname } from 'path';
|
|
3
|
+
import { randomUUID, createHash } from 'crypto';
|
|
4
|
+
import { stdout, stdin } from 'process';
|
|
5
|
+
import { createInterface } from 'readline/promises';
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
// src/context/ignore.ts
|
|
11
|
+
function parseIgnore(content) {
|
|
12
|
+
return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
13
|
+
}
|
|
14
|
+
var Ignore = class {
|
|
15
|
+
rules = [];
|
|
16
|
+
add(patterns) {
|
|
17
|
+
for (const pattern of patterns) {
|
|
18
|
+
const rule = compile(pattern);
|
|
19
|
+
if (rule) this.rules.push(rule);
|
|
20
|
+
}
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
/** Returns true if `path` (POSIX, relative to root) is ignored. Last match wins. */
|
|
24
|
+
ignores(path, isDir) {
|
|
25
|
+
let ignored = false;
|
|
26
|
+
for (const rule of this.rules) {
|
|
27
|
+
if (rule.dirOnly && !isDir) continue;
|
|
28
|
+
if (rule.regex.test(path)) ignored = !rule.negated;
|
|
29
|
+
}
|
|
30
|
+
return ignored;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
function compile(raw) {
|
|
34
|
+
let pattern = raw.trim();
|
|
35
|
+
if (!pattern || pattern.startsWith("#")) return null;
|
|
36
|
+
let negated = false;
|
|
37
|
+
if (pattern.startsWith("!")) {
|
|
38
|
+
negated = true;
|
|
39
|
+
pattern = pattern.slice(1);
|
|
40
|
+
}
|
|
41
|
+
let dirOnly = false;
|
|
42
|
+
if (pattern.endsWith("/")) {
|
|
43
|
+
dirOnly = true;
|
|
44
|
+
pattern = pattern.slice(0, -1);
|
|
45
|
+
}
|
|
46
|
+
let anchored = false;
|
|
47
|
+
if (pattern.startsWith("/")) {
|
|
48
|
+
anchored = true;
|
|
49
|
+
pattern = pattern.slice(1);
|
|
50
|
+
} else if (pattern.includes("/")) {
|
|
51
|
+
anchored = true;
|
|
52
|
+
}
|
|
53
|
+
if (!pattern) return null;
|
|
54
|
+
const prefix = anchored ? "^" : "(?:^|/)";
|
|
55
|
+
const regex = new RegExp(`${prefix}${globToRegex(pattern)}(?:/.*)?$`);
|
|
56
|
+
return { negated, dirOnly, regex };
|
|
57
|
+
}
|
|
58
|
+
function globToRegex(glob) {
|
|
59
|
+
let out = "";
|
|
60
|
+
for (let i = 0; i < glob.length; i++) {
|
|
61
|
+
const c = glob[i];
|
|
62
|
+
if (c === "*") {
|
|
63
|
+
if (glob[i + 1] === "*") {
|
|
64
|
+
if (glob[i + 2] === "/") {
|
|
65
|
+
out += "(?:.*/)?";
|
|
66
|
+
i += 2;
|
|
67
|
+
} else {
|
|
68
|
+
out += ".*";
|
|
69
|
+
i += 1;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
out += "[^/]*";
|
|
73
|
+
}
|
|
74
|
+
} else if (c === "?") {
|
|
75
|
+
out += "[^/]";
|
|
76
|
+
} else {
|
|
77
|
+
out += escapeRegexChar(c);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function escapeRegexChar(ch) {
|
|
83
|
+
return /[.+^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
84
|
+
}
|
|
85
|
+
var DEFAULT_IGNORE_DIRS = ["node_modules", ".git", ".origin", "dist", "coverage"];
|
|
86
|
+
var DEFAULT_IGNORE_FILES = [".gitignore", ".originignore"];
|
|
87
|
+
var DEFAULT_MAX_FILES = 2e3;
|
|
88
|
+
var LANGUAGE_BY_EXT = {
|
|
89
|
+
ts: "TypeScript",
|
|
90
|
+
tsx: "TypeScript",
|
|
91
|
+
js: "JavaScript",
|
|
92
|
+
jsx: "JavaScript",
|
|
93
|
+
mjs: "JavaScript",
|
|
94
|
+
cjs: "JavaScript",
|
|
95
|
+
py: "Python",
|
|
96
|
+
rs: "Rust",
|
|
97
|
+
go: "Go",
|
|
98
|
+
java: "Java",
|
|
99
|
+
rb: "Ruby",
|
|
100
|
+
php: "PHP",
|
|
101
|
+
c: "C",
|
|
102
|
+
h: "C",
|
|
103
|
+
cpp: "C++",
|
|
104
|
+
cs: "C#",
|
|
105
|
+
swift: "Swift",
|
|
106
|
+
kt: "Kotlin",
|
|
107
|
+
sql: "SQL",
|
|
108
|
+
sh: "Shell",
|
|
109
|
+
json: "JSON",
|
|
110
|
+
yaml: "YAML",
|
|
111
|
+
yml: "YAML",
|
|
112
|
+
toml: "TOML",
|
|
113
|
+
md: "Markdown",
|
|
114
|
+
html: "HTML",
|
|
115
|
+
css: "CSS"
|
|
116
|
+
};
|
|
117
|
+
var ContextEngine = class {
|
|
118
|
+
async scan(options = {}) {
|
|
119
|
+
const root = options.root ?? process.cwd();
|
|
120
|
+
const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
121
|
+
const ignoreFiles = options.ignoreFiles ?? DEFAULT_IGNORE_FILES;
|
|
122
|
+
const ignore = new Ignore().add(DEFAULT_IGNORE_DIRS.map((d) => `${d}/`));
|
|
123
|
+
for (const name of ignoreFiles) {
|
|
124
|
+
try {
|
|
125
|
+
ignore.add(parseIgnore(await readFile(join(root, name), "utf8")));
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err.code !== "ENOENT") throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const files = [];
|
|
131
|
+
let truncated = false;
|
|
132
|
+
const walk = async (dir) => {
|
|
133
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
if (truncated) return;
|
|
136
|
+
if (entry.isSymbolicLink()) continue;
|
|
137
|
+
const abs = join(dir, entry.name);
|
|
138
|
+
const rel = toPosix(relative(root, abs));
|
|
139
|
+
if (entry.isDirectory()) {
|
|
140
|
+
if (ignore.ignores(rel, true)) continue;
|
|
141
|
+
await walk(abs);
|
|
142
|
+
} else if (entry.isFile()) {
|
|
143
|
+
if (ignore.ignores(rel, false)) continue;
|
|
144
|
+
if (files.length >= maxFiles) {
|
|
145
|
+
truncated = true;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const info = await stat(abs);
|
|
149
|
+
const ext = extname(entry.name).slice(1).toLowerCase();
|
|
150
|
+
const language = LANGUAGE_BY_EXT[ext];
|
|
151
|
+
files.push(
|
|
152
|
+
language ? { path: rel, size: info.size, ext, language } : { path: rel, size: info.size, ext }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
await walk(root);
|
|
158
|
+
const languages = {};
|
|
159
|
+
for (const file of files) {
|
|
160
|
+
if (file.language) languages[file.language] = (languages[file.language] ?? 0) + 1;
|
|
161
|
+
}
|
|
162
|
+
const bundle = {
|
|
163
|
+
root,
|
|
164
|
+
files,
|
|
165
|
+
languages,
|
|
166
|
+
tools: options.tools ?? [],
|
|
167
|
+
truncated,
|
|
168
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
169
|
+
};
|
|
170
|
+
if (options.goal) bundle.intent = parseIntent(options.goal);
|
|
171
|
+
return bundle;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
var ACTION_VERBS = /* @__PURE__ */ new Set([
|
|
175
|
+
"add",
|
|
176
|
+
"analyze",
|
|
177
|
+
"build",
|
|
178
|
+
"configure",
|
|
179
|
+
"create",
|
|
180
|
+
"delete",
|
|
181
|
+
"deploy",
|
|
182
|
+
"document",
|
|
183
|
+
"fix",
|
|
184
|
+
"generate",
|
|
185
|
+
"implement",
|
|
186
|
+
"install",
|
|
187
|
+
"integrate",
|
|
188
|
+
"investigate",
|
|
189
|
+
"migrate",
|
|
190
|
+
"optimize",
|
|
191
|
+
"plan",
|
|
192
|
+
"prepare",
|
|
193
|
+
"publish",
|
|
194
|
+
"refactor",
|
|
195
|
+
"release",
|
|
196
|
+
"remove",
|
|
197
|
+
"review",
|
|
198
|
+
"summarize",
|
|
199
|
+
"test",
|
|
200
|
+
"update",
|
|
201
|
+
"write"
|
|
202
|
+
]);
|
|
203
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
204
|
+
"the",
|
|
205
|
+
"and",
|
|
206
|
+
"for",
|
|
207
|
+
"with",
|
|
208
|
+
"this",
|
|
209
|
+
"that",
|
|
210
|
+
"from",
|
|
211
|
+
"into",
|
|
212
|
+
"your",
|
|
213
|
+
"you",
|
|
214
|
+
"are",
|
|
215
|
+
"was",
|
|
216
|
+
"has",
|
|
217
|
+
"have",
|
|
218
|
+
"will",
|
|
219
|
+
"all",
|
|
220
|
+
"any",
|
|
221
|
+
"our",
|
|
222
|
+
"their",
|
|
223
|
+
"his",
|
|
224
|
+
"her",
|
|
225
|
+
"its"
|
|
226
|
+
]);
|
|
227
|
+
function parseIntent(goal) {
|
|
228
|
+
const words = goal.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter(Boolean);
|
|
229
|
+
const verbs = unique(words.filter((w) => ACTION_VERBS.has(w)));
|
|
230
|
+
const keywords = unique(
|
|
231
|
+
words.filter((w) => w.length > 3 && !STOPWORDS.has(w) && !ACTION_VERBS.has(w))
|
|
232
|
+
).slice(0, 12);
|
|
233
|
+
return { goal: goal.trim(), verbs, keywords };
|
|
234
|
+
}
|
|
235
|
+
function unique(items) {
|
|
236
|
+
return [...new Set(items)];
|
|
237
|
+
}
|
|
238
|
+
function toPosix(p) {
|
|
239
|
+
return p.split(sep).join("/");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/memory/store.ts
|
|
243
|
+
var InMemoryStore = class {
|
|
244
|
+
records = /* @__PURE__ */ new Map();
|
|
245
|
+
async all() {
|
|
246
|
+
return [...this.records.values()];
|
|
247
|
+
}
|
|
248
|
+
async get(id) {
|
|
249
|
+
return this.records.get(id) ?? null;
|
|
250
|
+
}
|
|
251
|
+
async put(record) {
|
|
252
|
+
this.records.set(record.id, record);
|
|
253
|
+
}
|
|
254
|
+
async delete(id) {
|
|
255
|
+
return this.records.delete(id);
|
|
256
|
+
}
|
|
257
|
+
async clear() {
|
|
258
|
+
this.records.clear();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
var DEFAULT_RECALL_LIMIT = 6;
|
|
262
|
+
var RECENCY_HALF_LIFE_DAYS = 30;
|
|
263
|
+
var Memory = class {
|
|
264
|
+
store;
|
|
265
|
+
now;
|
|
266
|
+
constructor(options = {}) {
|
|
267
|
+
this.store = options.store ?? new InMemoryStore();
|
|
268
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
269
|
+
}
|
|
270
|
+
async remember(input) {
|
|
271
|
+
const content = input.content?.trim();
|
|
272
|
+
if (!content) throw new Error("Memory.remember: `content` is required");
|
|
273
|
+
const ts = this.now().toISOString();
|
|
274
|
+
const record = {
|
|
275
|
+
id: randomUUID(),
|
|
276
|
+
content,
|
|
277
|
+
kind: input.kind ?? "note",
|
|
278
|
+
importance: clampImportance(input.importance ?? 1),
|
|
279
|
+
source: input.source,
|
|
280
|
+
metadata: input.metadata ?? {},
|
|
281
|
+
createdAt: ts,
|
|
282
|
+
updatedAt: ts
|
|
283
|
+
};
|
|
284
|
+
await this.store.put(record);
|
|
285
|
+
return record;
|
|
286
|
+
}
|
|
287
|
+
async recall(query, options = {}) {
|
|
288
|
+
const limit = options.limit ?? DEFAULT_RECALL_LIMIT;
|
|
289
|
+
if (this.store.recall) return this.store.recall(query, { ...options, limit });
|
|
290
|
+
const all = await this.store.all();
|
|
291
|
+
const nowMs = this.now().getTime();
|
|
292
|
+
const terms = tokenize(query);
|
|
293
|
+
return all.filter((r) => options.kind ? r.kind === options.kind : true).filter((r) => options.minImportance ? r.importance >= options.minImportance : true).map((r) => ({ ...r, score: scoreRecord(r, terms, nowMs) })).sort((a, b) => b.score - a.score || b.updatedAt.localeCompare(a.updatedAt)).slice(0, limit);
|
|
294
|
+
}
|
|
295
|
+
async list(options = {}) {
|
|
296
|
+
const all = await this.store.all();
|
|
297
|
+
const filtered = options.kind ? all.filter((r) => r.kind === options.kind) : all;
|
|
298
|
+
filtered.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
299
|
+
return options.limit ? filtered.slice(0, options.limit) : filtered;
|
|
300
|
+
}
|
|
301
|
+
async update(id, patch) {
|
|
302
|
+
const existing = await this.store.get(id);
|
|
303
|
+
if (!existing) return null;
|
|
304
|
+
const updated = {
|
|
305
|
+
...existing,
|
|
306
|
+
content: patch.content?.trim() || existing.content,
|
|
307
|
+
importance: patch.importance !== void 0 ? clampImportance(patch.importance) : existing.importance,
|
|
308
|
+
metadata: patch.metadata ? { ...existing.metadata, ...patch.metadata } : existing.metadata,
|
|
309
|
+
updatedAt: this.now().toISOString()
|
|
310
|
+
};
|
|
311
|
+
await this.store.put(updated);
|
|
312
|
+
return updated;
|
|
313
|
+
}
|
|
314
|
+
async forget(id) {
|
|
315
|
+
return this.store.delete(id);
|
|
316
|
+
}
|
|
317
|
+
async forgetAll() {
|
|
318
|
+
return this.store.clear();
|
|
319
|
+
}
|
|
320
|
+
/** All records, oldest first — for backup/inspection. */
|
|
321
|
+
async export() {
|
|
322
|
+
const all = await this.store.all();
|
|
323
|
+
return all.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
324
|
+
}
|
|
325
|
+
/** Recalled memories rendered as a system-prompt block (Apollo-compatible). */
|
|
326
|
+
async formatBlock(query, options = {}) {
|
|
327
|
+
return formatMemoriesBlock(await this.recall(query, options));
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
function clampImportance(n) {
|
|
331
|
+
if (!Number.isFinite(n)) return 1;
|
|
332
|
+
return Math.min(5, Math.max(1, Math.round(n)));
|
|
333
|
+
}
|
|
334
|
+
function tokenize(text) {
|
|
335
|
+
return text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter((w) => w.length > 2);
|
|
336
|
+
}
|
|
337
|
+
function scoreRecord(record, queryTerms, nowMs) {
|
|
338
|
+
const haystack = new Set(tokenize(record.content));
|
|
339
|
+
let overlap = 0;
|
|
340
|
+
for (const term of queryTerms) if (haystack.has(term)) overlap += 1;
|
|
341
|
+
const relevance = queryTerms.length > 0 ? overlap / queryTerms.length : 0;
|
|
342
|
+
const importance = (record.importance - 1) / 4;
|
|
343
|
+
const ageDays = Math.max(0, (nowMs - Date.parse(record.updatedAt)) / 864e5);
|
|
344
|
+
const recency = Math.exp(-ageDays / RECENCY_HALF_LIFE_DAYS);
|
|
345
|
+
return relevance * 1 + importance * 0.3 + recency * 0.2;
|
|
346
|
+
}
|
|
347
|
+
function formatMemoriesBlock(memories) {
|
|
348
|
+
if (memories.length === 0) return "";
|
|
349
|
+
const lines = memories.map((m) => {
|
|
350
|
+
const date = m.createdAt.slice(0, 10);
|
|
351
|
+
return `- [${m.kind} \xB7 ${date}] ${m.content.replace(/\s+/g, " ").trim().slice(0, 600)}`;
|
|
352
|
+
});
|
|
353
|
+
return [
|
|
354
|
+
"# Origin memory",
|
|
355
|
+
"Relevant things you remember about this user. Use them silently to stay consistent. Do not list them back unless asked.",
|
|
356
|
+
...lines
|
|
357
|
+
].join("\n");
|
|
358
|
+
}
|
|
359
|
+
var SECRET_KEY_RE = /(api[-_]?key|secret|token|password|passwd|authorization|auth|bearer|credential|private[-_]?key)/i;
|
|
360
|
+
var SECRET_VALUE_RES = [
|
|
361
|
+
/\bsk-[A-Za-z0-9]{16,}\b/g,
|
|
362
|
+
// OpenAI-style
|
|
363
|
+
/\bBearer\s+[A-Za-z0-9._-]{8,}\b/gi,
|
|
364
|
+
// Authorization headers
|
|
365
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}\b/g,
|
|
366
|
+
// GitHub tokens
|
|
367
|
+
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g
|
|
368
|
+
// Slack tokens
|
|
369
|
+
];
|
|
370
|
+
function redactValue(value, keyIsSecret) {
|
|
371
|
+
if (typeof value === "string") {
|
|
372
|
+
if (keyIsSecret) return "[redacted]";
|
|
373
|
+
let out = value;
|
|
374
|
+
for (const re of SECRET_VALUE_RES) out = out.replace(re, "[redacted]");
|
|
375
|
+
return out;
|
|
376
|
+
}
|
|
377
|
+
if (Array.isArray(value)) return value.map((v) => redactValue(v, false));
|
|
378
|
+
if (value && typeof value === "object") {
|
|
379
|
+
const out = {};
|
|
380
|
+
for (const [k, v] of Object.entries(value)) out[k] = redactValue(v, SECRET_KEY_RE.test(k));
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
return value;
|
|
384
|
+
}
|
|
385
|
+
var defaultRedactor = (data) => redactValue(data, false);
|
|
386
|
+
var MemorySink = class {
|
|
387
|
+
traces = /* @__PURE__ */ new Map();
|
|
388
|
+
async append(event) {
|
|
389
|
+
const events = this.traces.get(event.traceId) ?? [];
|
|
390
|
+
events.push(event);
|
|
391
|
+
this.traces.set(event.traceId, events);
|
|
392
|
+
}
|
|
393
|
+
async load(traceId) {
|
|
394
|
+
return [...this.traces.get(traceId) ?? []];
|
|
395
|
+
}
|
|
396
|
+
async list() {
|
|
397
|
+
return [...this.traces.keys()];
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var JsonlSink = class {
|
|
401
|
+
constructor(dir) {
|
|
402
|
+
this.dir = dir;
|
|
403
|
+
}
|
|
404
|
+
dir;
|
|
405
|
+
file(traceId) {
|
|
406
|
+
return join(this.dir, `${traceId}.jsonl`);
|
|
407
|
+
}
|
|
408
|
+
async append(event) {
|
|
409
|
+
await mkdir(this.dir, { recursive: true });
|
|
410
|
+
await appendFile(this.file(event.traceId), `${JSON.stringify(event)}
|
|
411
|
+
`, "utf8");
|
|
412
|
+
}
|
|
413
|
+
async load(traceId) {
|
|
414
|
+
try {
|
|
415
|
+
const raw = await readFile(this.file(traceId), "utf8");
|
|
416
|
+
return raw.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
417
|
+
} catch (err) {
|
|
418
|
+
if (err.code === "ENOENT") return [];
|
|
419
|
+
throw err;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
async list() {
|
|
423
|
+
try {
|
|
424
|
+
const entries = await readdir(this.dir);
|
|
425
|
+
return entries.filter((f) => f.endsWith(".jsonl")).map((f) => f.slice(0, -".jsonl".length));
|
|
426
|
+
} catch (err) {
|
|
427
|
+
if (err.code === "ENOENT") return [];
|
|
428
|
+
throw err;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
var Tracer = class {
|
|
433
|
+
traceId;
|
|
434
|
+
sink;
|
|
435
|
+
redactor;
|
|
436
|
+
now;
|
|
437
|
+
seq = 0;
|
|
438
|
+
events = [];
|
|
439
|
+
constructor(options) {
|
|
440
|
+
this.traceId = options.traceId;
|
|
441
|
+
this.sink = options.sink;
|
|
442
|
+
this.redactor = options.redactor;
|
|
443
|
+
this.now = options.now;
|
|
444
|
+
}
|
|
445
|
+
async event(kind, label, data, durationMs) {
|
|
446
|
+
const event = {
|
|
447
|
+
traceId: this.traceId,
|
|
448
|
+
seq: this.seq++,
|
|
449
|
+
at: this.now().toISOString(),
|
|
450
|
+
kind,
|
|
451
|
+
label,
|
|
452
|
+
...data ? { data: this.redactor(data) } : {},
|
|
453
|
+
...durationMs !== void 0 ? { durationMs } : {}
|
|
454
|
+
};
|
|
455
|
+
this.events.push(event);
|
|
456
|
+
await this.sink.append(event);
|
|
457
|
+
return event;
|
|
458
|
+
}
|
|
459
|
+
start(label = "mission", data) {
|
|
460
|
+
return this.event("mission_start", label, data);
|
|
461
|
+
}
|
|
462
|
+
end(label = "mission", data) {
|
|
463
|
+
return this.event("mission_end", label, data);
|
|
464
|
+
}
|
|
465
|
+
decision(label, data) {
|
|
466
|
+
return this.event("decision", label, data);
|
|
467
|
+
}
|
|
468
|
+
plan(label, data) {
|
|
469
|
+
return this.event("plan", label, data);
|
|
470
|
+
}
|
|
471
|
+
toolCall(label, data) {
|
|
472
|
+
return this.event("tool_call", label, data);
|
|
473
|
+
}
|
|
474
|
+
approval(label, data) {
|
|
475
|
+
return this.event("approval", label, data);
|
|
476
|
+
}
|
|
477
|
+
apply(label, data) {
|
|
478
|
+
return this.event("apply", label, data);
|
|
479
|
+
}
|
|
480
|
+
verify(label, data) {
|
|
481
|
+
return this.event("verify", label, data);
|
|
482
|
+
}
|
|
483
|
+
memory(label, data) {
|
|
484
|
+
return this.event("memory", label, data);
|
|
485
|
+
}
|
|
486
|
+
error(label, data) {
|
|
487
|
+
return this.event("error", label, data);
|
|
488
|
+
}
|
|
489
|
+
/** Start a timed span; call `.end()` to record it with a `durationMs`. */
|
|
490
|
+
span(kind, label, data) {
|
|
491
|
+
const startedAt = this.now().getTime();
|
|
492
|
+
return {
|
|
493
|
+
end: (extra) => {
|
|
494
|
+
const durationMs = this.now().getTime() - startedAt;
|
|
495
|
+
return this.event(kind, label, { ...data ?? {}, ...extra ?? {} }, durationMs);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/** In-memory view of this trace so far. */
|
|
500
|
+
snapshot() {
|
|
501
|
+
return buildTrace(this.traceId, this.events);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
var Observability = class {
|
|
505
|
+
sink;
|
|
506
|
+
redactor;
|
|
507
|
+
now;
|
|
508
|
+
constructor(options = {}) {
|
|
509
|
+
this.sink = options.sink ?? (options.dir ? new JsonlSink(options.dir) : new MemorySink());
|
|
510
|
+
this.redactor = options.redactor ?? defaultRedactor;
|
|
511
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
512
|
+
}
|
|
513
|
+
startTrace() {
|
|
514
|
+
return new Tracer({
|
|
515
|
+
traceId: randomUUID(),
|
|
516
|
+
sink: this.sink,
|
|
517
|
+
redactor: this.redactor,
|
|
518
|
+
now: this.now
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
async replay(traceId) {
|
|
522
|
+
return buildTrace(traceId, await this.sink.load(traceId));
|
|
523
|
+
}
|
|
524
|
+
async list() {
|
|
525
|
+
return this.sink.list();
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
function buildTrace(id, events) {
|
|
529
|
+
const sorted = [...events].sort((a, b) => a.seq - b.seq);
|
|
530
|
+
const startedAt = sorted[0]?.at;
|
|
531
|
+
const endEvent = [...sorted].reverse().find((e) => e.kind === "mission_end");
|
|
532
|
+
const endedAt = endEvent?.at ?? sorted[sorted.length - 1]?.at;
|
|
533
|
+
return {
|
|
534
|
+
id,
|
|
535
|
+
...startedAt ? { startedAt } : {},
|
|
536
|
+
...endedAt ? { endedAt } : {},
|
|
537
|
+
events: sorted
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
var autoApprove = () => ({ approved: true });
|
|
541
|
+
var denyAll = () => ({
|
|
542
|
+
approved: false,
|
|
543
|
+
note: "no approver configured"
|
|
544
|
+
});
|
|
545
|
+
function cliApprover() {
|
|
546
|
+
return async (req) => {
|
|
547
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
548
|
+
try {
|
|
549
|
+
const answer = await rl.question(
|
|
550
|
+
`
|
|
551
|
+
[origin] ${req.reason}
|
|
552
|
+
\u2192 ${req.action.summary}
|
|
553
|
+
Approve? [y/N] `
|
|
554
|
+
);
|
|
555
|
+
return { approved: /^y(es)?$/i.test(answer.trim()) };
|
|
556
|
+
} finally {
|
|
557
|
+
rl.close();
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
async function authorize(policy, action, approver, ctx) {
|
|
562
|
+
const gate = policy.gate(action, ctx);
|
|
563
|
+
if (gate === "allow") return { allowed: true, gate, via: "policy" };
|
|
564
|
+
if (gate === "deny") return { allowed: false, gate, via: "policy", note: "denied by policy" };
|
|
565
|
+
const decision = await approver({
|
|
566
|
+
action,
|
|
567
|
+
policy: policy.name,
|
|
568
|
+
reason: `${policy.name} requires approval for a ${action.kind} action`
|
|
569
|
+
});
|
|
570
|
+
return {
|
|
571
|
+
allowed: decision.approved,
|
|
572
|
+
gate,
|
|
573
|
+
via: "approver",
|
|
574
|
+
...decision.note ? { note: decision.note } : {}
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
var READ_ONLY = /* @__PURE__ */ new Set(["read", "plan", "memory"]);
|
|
578
|
+
var DEFAULT_BUDGET = { maxSteps: 25, maxToolCalls: 50 };
|
|
579
|
+
function resolveBudget(budget) {
|
|
580
|
+
return { ...DEFAULT_BUDGET, ...budget ?? {} };
|
|
581
|
+
}
|
|
582
|
+
function isReadOnly(action) {
|
|
583
|
+
return READ_ONLY.has(action.kind) && !action.mutating;
|
|
584
|
+
}
|
|
585
|
+
function outsideRoot(target, root) {
|
|
586
|
+
if (!target || !root) return false;
|
|
587
|
+
const rel = relative(resolve(root), resolve(root, target));
|
|
588
|
+
return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel);
|
|
589
|
+
}
|
|
590
|
+
function humanDirected(options = {}) {
|
|
591
|
+
return {
|
|
592
|
+
name: "humanDirected",
|
|
593
|
+
mode: "review",
|
|
594
|
+
budget: resolveBudget(options.budget),
|
|
595
|
+
gate(action) {
|
|
596
|
+
return isReadOnly(action) ? "allow" : "ask";
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function reviewOnly(options = {}) {
|
|
601
|
+
return {
|
|
602
|
+
name: "reviewOnly",
|
|
603
|
+
mode: "plan",
|
|
604
|
+
budget: resolveBudget(options.budget),
|
|
605
|
+
gate(action) {
|
|
606
|
+
return isReadOnly(action) ? "allow" : "deny";
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
function autonomous(options = {}) {
|
|
611
|
+
const fullAuto = options.fullAuto ?? false;
|
|
612
|
+
return {
|
|
613
|
+
name: fullAuto ? "autonomous(full-auto)" : "autonomous",
|
|
614
|
+
mode: fullAuto ? "full-auto" : "auto",
|
|
615
|
+
budget: resolveBudget(options.budget),
|
|
616
|
+
gate(action, ctx) {
|
|
617
|
+
if (fullAuto) return "allow";
|
|
618
|
+
return outsideRoot(action.target, ctx?.root) ? "ask" : "allow";
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
var pexec = promisify(exec);
|
|
623
|
+
var contractSchema = z.object({
|
|
624
|
+
commands: z.array(z.object({ cmd: z.string().min(1), expectExit: z.number().int().default(0) })).default([]),
|
|
625
|
+
http: z.array(z.object({ url: z.string().url(), expectStatus: z.number().int().default(200) })).default([]),
|
|
626
|
+
regex: z.array(z.object({ cmd: z.string().min(1), pattern: z.string().min(1) })).default([]),
|
|
627
|
+
criteria: z.array(z.string()).default([])
|
|
628
|
+
});
|
|
629
|
+
var defaultExec = async (cmd, opts) => {
|
|
630
|
+
try {
|
|
631
|
+
const { stdout: stdout2, stderr } = await pexec(cmd, {
|
|
632
|
+
cwd: opts.cwd,
|
|
633
|
+
timeout: opts.timeoutMs,
|
|
634
|
+
windowsHide: true
|
|
635
|
+
});
|
|
636
|
+
return { code: 0, stdout: String(stdout2), stderr: String(stderr) };
|
|
637
|
+
} catch (e) {
|
|
638
|
+
const err = e;
|
|
639
|
+
return {
|
|
640
|
+
code: typeof err.code === "number" ? err.code : 1,
|
|
641
|
+
stdout: err.stdout ?? "",
|
|
642
|
+
stderr: err.stderr ?? ""
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
async function evaluateContract(input, options = {}) {
|
|
647
|
+
const contract = contractSchema.parse(input);
|
|
648
|
+
const run = options.execImpl ?? defaultExec;
|
|
649
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
650
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
651
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
652
|
+
const checks = [];
|
|
653
|
+
for (const c of contract.commands) {
|
|
654
|
+
const res = await run(c.cmd, { cwd: options.cwd, timeoutMs });
|
|
655
|
+
checks.push({
|
|
656
|
+
type: "command",
|
|
657
|
+
target: c.cmd,
|
|
658
|
+
pass: res.code === c.expectExit,
|
|
659
|
+
detail: `exit ${res.code} (expected ${c.expectExit})`
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
for (const r of contract.regex) {
|
|
663
|
+
const res = await run(r.cmd, { cwd: options.cwd, timeoutMs });
|
|
664
|
+
let pass = false;
|
|
665
|
+
let detail;
|
|
666
|
+
try {
|
|
667
|
+
pass = new RegExp(r.pattern).test(`${res.stdout}
|
|
668
|
+
${res.stderr}`);
|
|
669
|
+
} catch {
|
|
670
|
+
detail = "invalid pattern";
|
|
671
|
+
}
|
|
672
|
+
checks.push({ type: "regex", target: `${r.cmd} =~ /${r.pattern}/`, pass, ...detail ? { detail } : {} });
|
|
673
|
+
}
|
|
674
|
+
for (const h of contract.http) {
|
|
675
|
+
try {
|
|
676
|
+
const res = await doFetch(h.url, { method: "GET", signal: AbortSignal.timeout(timeoutMs) });
|
|
677
|
+
checks.push({
|
|
678
|
+
type: "http",
|
|
679
|
+
target: h.url,
|
|
680
|
+
pass: res.status === h.expectStatus,
|
|
681
|
+
detail: `status ${res.status} (expected ${h.expectStatus})`
|
|
682
|
+
});
|
|
683
|
+
} catch (e) {
|
|
684
|
+
checks.push({ type: "http", target: h.url, pass: false, detail: String(e) });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return certify(checks, contract.criteria, now().toISOString());
|
|
688
|
+
}
|
|
689
|
+
function certify(checks, criteria, issuedAt) {
|
|
690
|
+
const ok = checks.every((c) => c.pass);
|
|
691
|
+
const canonical = JSON.stringify({
|
|
692
|
+
checks: [...checks].sort((a, b) => `${a.type}:${a.target}`.localeCompare(`${b.type}:${b.target}`)),
|
|
693
|
+
criteria,
|
|
694
|
+
ok
|
|
695
|
+
});
|
|
696
|
+
const hash = createHash("sha256").update(canonical).digest("hex");
|
|
697
|
+
return { ok, checks, criteria, issuedAt, hash };
|
|
698
|
+
}
|
|
699
|
+
function parseContractFromPlan(plan) {
|
|
700
|
+
const block = plan.match(/```contract\s*([\s\S]*?)```/i)?.[1] ?? plan.match(/##\s*contract\s*([\s\S]*?)(?:\n#{1,3}\s|\n\n\n|$)/i)?.[1];
|
|
701
|
+
if (!block) return null;
|
|
702
|
+
const trimmed = block.trim();
|
|
703
|
+
if (!trimmed.startsWith("{")) return null;
|
|
704
|
+
try {
|
|
705
|
+
return contractSchema.parse(JSON.parse(trimmed));
|
|
706
|
+
} catch {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/providers/providers.ts
|
|
712
|
+
var SUPPORTED_PROVIDERS = [
|
|
713
|
+
"anthropic",
|
|
714
|
+
"openai",
|
|
715
|
+
"google",
|
|
716
|
+
"groq",
|
|
717
|
+
"mistral",
|
|
718
|
+
"openrouter"
|
|
719
|
+
];
|
|
720
|
+
var PROVIDER_KEY_ENV = {
|
|
721
|
+
anthropic: ["ANTHROPIC_API_KEY"],
|
|
722
|
+
openai: ["OPENAI_API_KEY"],
|
|
723
|
+
google: ["GOOGLE_AI_API_KEY", "GEMINI_API_KEY"],
|
|
724
|
+
groq: ["GROQ_API_KEY"],
|
|
725
|
+
mistral: ["MISTRAL_API_KEY"],
|
|
726
|
+
openrouter: ["OPENROUTER_API_KEY"]
|
|
727
|
+
};
|
|
728
|
+
function isSupportedProvider(value) {
|
|
729
|
+
return SUPPORTED_PROVIDERS.includes(value);
|
|
730
|
+
}
|
|
731
|
+
function resolveProviderKey(provider, env = process.env) {
|
|
732
|
+
for (const name of PROVIDER_KEY_ENV[provider]) {
|
|
733
|
+
const value = env[name];
|
|
734
|
+
if (value) return value;
|
|
735
|
+
}
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
function availableProviders(env = process.env) {
|
|
739
|
+
return SUPPORTED_PROVIDERS.filter((p) => resolveProviderKey(p, env) !== null);
|
|
740
|
+
}
|
|
741
|
+
var ANTHROPIC_MAX_TOKENS = 4096;
|
|
742
|
+
async function callProviderText(call) {
|
|
743
|
+
const res = await dispatch(call, false);
|
|
744
|
+
if (!res.ok) {
|
|
745
|
+
const body = await res.text().catch(() => "");
|
|
746
|
+
throw new Error(`Provider error (${call.provider} ${res.status}): ${body.slice(0, 500)}`);
|
|
747
|
+
}
|
|
748
|
+
return parseProviderText(call.provider, await res.json());
|
|
749
|
+
}
|
|
750
|
+
async function callProviderStream(call) {
|
|
751
|
+
return dispatch(call, true);
|
|
752
|
+
}
|
|
753
|
+
function dispatch(call, stream) {
|
|
754
|
+
const doFetch = call.fetchImpl ?? fetch;
|
|
755
|
+
switch (call.provider) {
|
|
756
|
+
case "anthropic":
|
|
757
|
+
return callAnthropic(call, stream, doFetch);
|
|
758
|
+
case "google":
|
|
759
|
+
return callGoogle(call, stream, doFetch);
|
|
760
|
+
case "openai":
|
|
761
|
+
case "groq":
|
|
762
|
+
case "mistral":
|
|
763
|
+
case "openrouter":
|
|
764
|
+
return callOpenAICompatible(call, stream, doFetch);
|
|
765
|
+
default: {
|
|
766
|
+
const exhaustive = call.provider;
|
|
767
|
+
return Promise.resolve(new Response(`Unsupported provider: ${String(exhaustive)}`, { status: 400 }));
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function callAnthropic(call, stream, doFetch) {
|
|
772
|
+
const system = call.messages.find((m) => m.role === "system")?.content;
|
|
773
|
+
const messages = call.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role === "assistant" ? "assistant" : "user", content: m.content }));
|
|
774
|
+
return doFetch("https://api.anthropic.com/v1/messages", {
|
|
775
|
+
method: "POST",
|
|
776
|
+
headers: {
|
|
777
|
+
"x-api-key": call.apiKey,
|
|
778
|
+
"anthropic-version": "2023-06-01",
|
|
779
|
+
"content-type": "application/json"
|
|
780
|
+
},
|
|
781
|
+
body: JSON.stringify({
|
|
782
|
+
model: call.model,
|
|
783
|
+
max_tokens: ANTHROPIC_MAX_TOKENS,
|
|
784
|
+
stream,
|
|
785
|
+
...system ? { system } : {},
|
|
786
|
+
messages
|
|
787
|
+
}),
|
|
788
|
+
signal: call.signal
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
function callOpenAICompatible(call, stream, doFetch) {
|
|
792
|
+
const baseUrl = call.provider === "openai" ? "https://api.openai.com/v1" : call.provider === "groq" ? "https://api.groq.com/openai/v1" : call.provider === "openrouter" ? "https://openrouter.ai/api/v1" : "https://api.mistral.ai/v1";
|
|
793
|
+
return doFetch(`${baseUrl}/chat/completions`, {
|
|
794
|
+
method: "POST",
|
|
795
|
+
headers: {
|
|
796
|
+
Authorization: `Bearer ${call.apiKey}`,
|
|
797
|
+
"Content-Type": "application/json",
|
|
798
|
+
...call.provider === "openrouter" ? { "HTTP-Referer": "https://archic.es", "X-Title": "Origin" } : {}
|
|
799
|
+
},
|
|
800
|
+
body: JSON.stringify({ model: call.model, messages: call.messages, stream }),
|
|
801
|
+
signal: call.signal
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
function callGoogle(call, stream, doFetch) {
|
|
805
|
+
const system = call.messages.find((m) => m.role === "system")?.content;
|
|
806
|
+
const contents = call.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role === "assistant" ? "model" : "user", parts: [{ text: m.content }] }));
|
|
807
|
+
const method = stream ? "streamGenerateContent?alt=sse" : "generateContent";
|
|
808
|
+
const separator = stream ? "&" : "?";
|
|
809
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(call.model)}:${method}${separator}key=${encodeURIComponent(call.apiKey)}`;
|
|
810
|
+
return doFetch(url, {
|
|
811
|
+
method: "POST",
|
|
812
|
+
headers: { "Content-Type": "application/json" },
|
|
813
|
+
body: JSON.stringify({
|
|
814
|
+
contents,
|
|
815
|
+
...system ? { systemInstruction: { parts: [{ text: system }] } } : {}
|
|
816
|
+
}),
|
|
817
|
+
signal: call.signal
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
function makeSSEParser(provider) {
|
|
821
|
+
return (line) => {
|
|
822
|
+
if (!line || line.startsWith(":") || !line.startsWith("data:")) return "";
|
|
823
|
+
const payload = line.slice(5).trim();
|
|
824
|
+
if (!payload || payload === "[DONE]") return "";
|
|
825
|
+
try {
|
|
826
|
+
const obj = JSON.parse(payload);
|
|
827
|
+
if (provider === "anthropic") {
|
|
828
|
+
if (obj.type === "content_block_delta" && obj.delta?.type === "text_delta") {
|
|
829
|
+
return obj.delta.text ?? "";
|
|
830
|
+
}
|
|
831
|
+
return "";
|
|
832
|
+
}
|
|
833
|
+
if (provider === "google") {
|
|
834
|
+
const parts = obj.candidates?.[0]?.content?.parts;
|
|
835
|
+
if (Array.isArray(parts)) return parts.map((p) => p.text ?? "").join("");
|
|
836
|
+
return "";
|
|
837
|
+
}
|
|
838
|
+
return obj.choices?.[0]?.delta?.content ?? "";
|
|
839
|
+
} catch {
|
|
840
|
+
return "";
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function parseProviderText(provider, body) {
|
|
845
|
+
const obj = body;
|
|
846
|
+
if (provider === "anthropic") {
|
|
847
|
+
return obj.content?.map((p) => p.text ?? "").join("").trim() ?? "";
|
|
848
|
+
}
|
|
849
|
+
if (provider === "google") {
|
|
850
|
+
return obj.candidates?.[0]?.content?.parts?.map((p) => p.text ?? "").join("").trim() ?? "";
|
|
851
|
+
}
|
|
852
|
+
return obj.choices?.[0]?.message?.content?.trim() ?? "";
|
|
853
|
+
}
|
|
854
|
+
var planResponseSchema = z.object({
|
|
855
|
+
rationale: z.string().optional(),
|
|
856
|
+
steps: z.array(
|
|
857
|
+
z.object({
|
|
858
|
+
description: z.string(),
|
|
859
|
+
tool: z.string().optional(),
|
|
860
|
+
input: z.unknown().optional(),
|
|
861
|
+
mutating: z.boolean().optional()
|
|
862
|
+
})
|
|
863
|
+
).default([]),
|
|
864
|
+
contract: z.unknown().optional()
|
|
865
|
+
});
|
|
866
|
+
function extractJson(text) {
|
|
867
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1] ?? text;
|
|
868
|
+
const start = fenced.indexOf("{");
|
|
869
|
+
const end = fenced.lastIndexOf("}");
|
|
870
|
+
if (start === -1 || end === -1 || end < start) return {};
|
|
871
|
+
try {
|
|
872
|
+
return JSON.parse(fenced.slice(start, end + 1));
|
|
873
|
+
} catch {
|
|
874
|
+
return {};
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function parsePlan(text, tools) {
|
|
878
|
+
const parsed = planResponseSchema.safeParse(extractJson(text));
|
|
879
|
+
if (!parsed.success) return { steps: [], rationale: "planner returned no actionable steps" };
|
|
880
|
+
const toolByName = new Map(tools.map((t) => [t.name, t]));
|
|
881
|
+
const steps = parsed.data.steps.map((step, i) => {
|
|
882
|
+
const tool = step.tool ? toolByName.get(step.tool) : void 0;
|
|
883
|
+
return {
|
|
884
|
+
id: `s${i + 1}`,
|
|
885
|
+
description: step.description,
|
|
886
|
+
...tool ? { tool: tool.name } : {},
|
|
887
|
+
...step.input !== void 0 ? { input: step.input } : {},
|
|
888
|
+
kind: tool ? tool.kind ?? "tool" : "plan",
|
|
889
|
+
mutating: step.mutating ?? tool?.mutating ?? false
|
|
890
|
+
};
|
|
891
|
+
});
|
|
892
|
+
return {
|
|
893
|
+
steps,
|
|
894
|
+
...parsed.data.rationale ? { rationale: parsed.data.rationale } : {},
|
|
895
|
+
...parsed.data.contract !== void 0 ? { contract: parsed.data.contract } : {}
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/agent/reference-runtime.ts
|
|
900
|
+
var DEFAULT_MODELS = {
|
|
901
|
+
openrouter: "openai/gpt-4o-mini",
|
|
902
|
+
openai: "gpt-4o-mini",
|
|
903
|
+
anthropic: "claude-3-5-sonnet-latest",
|
|
904
|
+
google: "gemini-1.5-flash",
|
|
905
|
+
groq: "llama-3.1-8b-instant",
|
|
906
|
+
mistral: "mistral-small-latest"
|
|
907
|
+
};
|
|
908
|
+
var ReferenceRuntime = class {
|
|
909
|
+
name = "reference";
|
|
910
|
+
complete;
|
|
911
|
+
constructor(options = {}) {
|
|
912
|
+
if (options.complete) {
|
|
913
|
+
this.complete = options.complete;
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const env = options.env ?? process.env;
|
|
917
|
+
const provider = options.provider ?? availableProviders(env)[0];
|
|
918
|
+
if (!provider) return;
|
|
919
|
+
const apiKey = resolveProviderKey(provider, env);
|
|
920
|
+
if (!apiKey) return;
|
|
921
|
+
const model = options.model ?? DEFAULT_MODELS[provider];
|
|
922
|
+
const fetchImpl = options.fetchImpl;
|
|
923
|
+
this.complete = (messages) => callProviderText({ provider, model, apiKey, messages, ...fetchImpl ? { fetchImpl } : {} });
|
|
924
|
+
}
|
|
925
|
+
async plan(ctx) {
|
|
926
|
+
if (!this.complete) return { steps: [], rationale: "dry-run: no LLM provider configured" };
|
|
927
|
+
const text = await this.complete([
|
|
928
|
+
{ role: "system", content: buildPlannerSystem(ctx) },
|
|
929
|
+
{ role: "user", content: `Goal: ${ctx.goal}
|
|
930
|
+
|
|
931
|
+
Return the JSON plan now.` }
|
|
932
|
+
]);
|
|
933
|
+
return parsePlan(text, ctx.tools);
|
|
934
|
+
}
|
|
935
|
+
async synthesize(ctx, observations) {
|
|
936
|
+
if (!this.complete) {
|
|
937
|
+
return `Origin dry-run for "${ctx.goal}". No LLM provider configured; recorded ${observations.length} step(s). Set a provider key (e.g. OPENROUTER_API_KEY) to enable reasoning.`;
|
|
938
|
+
}
|
|
939
|
+
const obs = observations.map(
|
|
940
|
+
(o) => `- ${o.stepId}: ${o.ok ? "ok" : "failed"}` + (o.output !== void 0 ? ` \u2192 ${truncate(JSON.stringify(o.output), 300)}` : "") + (o.error ? ` (error: ${o.error})` : "")
|
|
941
|
+
).join("\n") || "(none)";
|
|
942
|
+
return this.complete([
|
|
943
|
+
{
|
|
944
|
+
role: "system",
|
|
945
|
+
content: "You are Origin. Using the goal, memory, and step results, write a concise, direct final answer for the user. No preamble."
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
role: "user",
|
|
949
|
+
content: `Goal: ${ctx.goal}
|
|
950
|
+
|
|
951
|
+
Memory:
|
|
952
|
+
${ctx.memoryBlock || "(none)"}
|
|
953
|
+
|
|
954
|
+
Step results:
|
|
955
|
+
${obs}
|
|
956
|
+
|
|
957
|
+
Final answer:`
|
|
958
|
+
}
|
|
959
|
+
]);
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
function buildPlannerSystem(ctx) {
|
|
963
|
+
const tools = ctx.tools.length ? ctx.tools.map((t) => `- ${t.name}: ${t.description ?? "(no description)"}${t.mutating ? " [mutating]" : ""}`).join("\n") : "(no tools available)";
|
|
964
|
+
return [
|
|
965
|
+
"You are Origin's planner. Decompose the user's goal into a short ordered list of steps (1-5).",
|
|
966
|
+
'Use a tool only when an action is required; otherwise omit "tool".',
|
|
967
|
+
"Available tools:",
|
|
968
|
+
tools,
|
|
969
|
+
'Respond ONLY with JSON: {"rationale": string, "steps": [{"description": string, "tool"?: string, "input"?: any, "mutating"?: boolean}], "contract"?: {"commands"?: [{"cmd": string}], "criteria"?: [string]}}.'
|
|
970
|
+
].join("\n");
|
|
971
|
+
}
|
|
972
|
+
function truncate(s, n) {
|
|
973
|
+
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/agent/agent.ts
|
|
977
|
+
var ApolloAgent = class {
|
|
978
|
+
memory;
|
|
979
|
+
observability;
|
|
980
|
+
tools;
|
|
981
|
+
policy;
|
|
982
|
+
contextEngine;
|
|
983
|
+
approver;
|
|
984
|
+
runtime;
|
|
985
|
+
root;
|
|
986
|
+
constructor(options = {}) {
|
|
987
|
+
this.memory = options.memory ?? new Memory();
|
|
988
|
+
this.tools = options.tools ?? [];
|
|
989
|
+
this.policy = options.policy ?? humanDirected();
|
|
990
|
+
this.contextEngine = options.context ?? new ContextEngine();
|
|
991
|
+
this.observability = options.observability ?? new Observability();
|
|
992
|
+
this.approver = options.approver ?? (this.policy.mode === "auto" || this.policy.mode === "full-auto" ? autoApprove : cliApprover());
|
|
993
|
+
this.runtime = options.runtime ?? new ReferenceRuntime({
|
|
994
|
+
...options.complete ? { complete: options.complete } : {},
|
|
995
|
+
...options.provider ? { provider: options.provider } : {},
|
|
996
|
+
...options.model ? { model: options.model } : {}
|
|
997
|
+
});
|
|
998
|
+
this.root = options.root ?? process.cwd();
|
|
999
|
+
}
|
|
1000
|
+
async run(goal) {
|
|
1001
|
+
const { runtime, memory, contextEngine, observability, policy, approver, tools, root } = this;
|
|
1002
|
+
const tracer = observability.startTrace();
|
|
1003
|
+
await tracer.start(goal, { policy: policy.name, mode: policy.mode });
|
|
1004
|
+
const context = await contextEngine.scan({
|
|
1005
|
+
root,
|
|
1006
|
+
goal,
|
|
1007
|
+
tools: tools.map((t) => ({ name: t.name, ...t.description ? { description: t.description } : {} }))
|
|
1008
|
+
});
|
|
1009
|
+
await tracer.decision("context scanned", {
|
|
1010
|
+
files: context.files.length,
|
|
1011
|
+
languages: context.languages
|
|
1012
|
+
});
|
|
1013
|
+
const memoryBlock = await memory.formatBlock(goal);
|
|
1014
|
+
if (memoryBlock) await tracer.memory("recalled memory");
|
|
1015
|
+
const rctx = { goal, context, memoryBlock, tools };
|
|
1016
|
+
const plan = await runtime.plan(rctx);
|
|
1017
|
+
await tracer.plan(`planned ${plan.steps.length} step(s)`, {
|
|
1018
|
+
...plan.rationale ? { rationale: plan.rationale } : {},
|
|
1019
|
+
steps: plan.steps.map((s) => s.description)
|
|
1020
|
+
});
|
|
1021
|
+
const observations = [];
|
|
1022
|
+
for (const step of plan.steps) {
|
|
1023
|
+
if (observations.length >= policy.budget.maxSteps) {
|
|
1024
|
+
await tracer.decision("step budget reached", { maxSteps: policy.budget.maxSteps });
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
const action = {
|
|
1028
|
+
kind: step.kind,
|
|
1029
|
+
summary: step.description,
|
|
1030
|
+
...step.tool ? { tool: step.tool } : {},
|
|
1031
|
+
mutating: step.mutating
|
|
1032
|
+
};
|
|
1033
|
+
const auth = await authorize(policy, action, approver, { root });
|
|
1034
|
+
await tracer.approval(`${auth.gate}: ${auth.allowed ? "allowed" : "blocked"}`, {
|
|
1035
|
+
step: step.description,
|
|
1036
|
+
via: auth.via,
|
|
1037
|
+
...auth.note ? { note: auth.note } : {}
|
|
1038
|
+
});
|
|
1039
|
+
if (!auth.allowed) {
|
|
1040
|
+
observations.push({
|
|
1041
|
+
stepId: step.id,
|
|
1042
|
+
ok: false,
|
|
1043
|
+
skipped: true,
|
|
1044
|
+
error: auth.note ?? "blocked by policy"
|
|
1045
|
+
});
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if (!step.tool) {
|
|
1049
|
+
observations.push({ stepId: step.id, ok: true, output: step.description });
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
const tool = tools.find((t) => t.name === step.tool);
|
|
1053
|
+
if (!tool) {
|
|
1054
|
+
await tracer.error("unknown tool", { tool: step.tool });
|
|
1055
|
+
observations.push({ stepId: step.id, ok: false, error: `unknown tool: ${step.tool}` });
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
const span = tracer.span("tool_call", tool.name, { input: step.input });
|
|
1059
|
+
try {
|
|
1060
|
+
const output = await tool.run(step.input, { goal });
|
|
1061
|
+
observations.push({ stepId: step.id, ok: true, output });
|
|
1062
|
+
await span.end({ ok: true });
|
|
1063
|
+
} catch (e) {
|
|
1064
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
1065
|
+
observations.push({ stepId: step.id, ok: false, error });
|
|
1066
|
+
await span.end({ ok: false, error });
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
const answer = await runtime.synthesize(rctx, observations);
|
|
1070
|
+
let certificate;
|
|
1071
|
+
if (plan.contract !== void 0 && policy.mode !== "plan") {
|
|
1072
|
+
const auth = await authorize(
|
|
1073
|
+
policy,
|
|
1074
|
+
{ kind: "exec", summary: "verify success contract", mutating: false },
|
|
1075
|
+
approver,
|
|
1076
|
+
{ root }
|
|
1077
|
+
);
|
|
1078
|
+
if (auth.allowed) {
|
|
1079
|
+
certificate = await evaluateContract(plan.contract, { cwd: root });
|
|
1080
|
+
await tracer.verify(certificate.ok ? "contract passed" : "contract failed", {
|
|
1081
|
+
ok: certificate.ok,
|
|
1082
|
+
hash: certificate.hash
|
|
1083
|
+
});
|
|
1084
|
+
} else {
|
|
1085
|
+
await tracer.decision("contract verification skipped", { reason: auth.note ?? "blocked" });
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
await memory.remember({
|
|
1089
|
+
content: `Goal: ${goal}
|
|
1090
|
+
Answer: ${truncate2(answer, 1500)}`,
|
|
1091
|
+
kind: "mission",
|
|
1092
|
+
importance: 2,
|
|
1093
|
+
source: "agent"
|
|
1094
|
+
});
|
|
1095
|
+
await tracer.memory("saved mission");
|
|
1096
|
+
await tracer.end("completed", certificate ? { contractOk: certificate.ok } : void 0);
|
|
1097
|
+
return {
|
|
1098
|
+
goal,
|
|
1099
|
+
answer,
|
|
1100
|
+
steps: plan.steps,
|
|
1101
|
+
observations,
|
|
1102
|
+
...certificate ? { certificate } : {},
|
|
1103
|
+
traceId: tracer.traceId,
|
|
1104
|
+
policy: policy.name
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
function truncate2(s, n) {
|
|
1109
|
+
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// src/agent/runtime.ts
|
|
1113
|
+
function defineTool(spec) {
|
|
1114
|
+
return spec;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
export { ApolloAgent, ContextEngine, Ignore, InMemoryStore, JsonlSink, Memory, MemorySink, Observability, PROVIDER_KEY_ENV, ReferenceRuntime, SUPPORTED_PROVIDERS, Tracer, authorize, autoApprove, autonomous, availableProviders, callProviderStream, callProviderText, cliApprover, contractSchema, defaultRedactor, defineTool, denyAll, evaluateContract, formatMemoriesBlock, humanDirected, isSupportedProvider, makeSSEParser, outsideRoot, parseContractFromPlan, parseIgnore, parseIntent, parsePlan, resolveProviderKey, reviewOnly, tokenize };
|
|
1118
|
+
//# sourceMappingURL=chunk-77G3SI57.js.map
|
|
1119
|
+
//# sourceMappingURL=chunk-77G3SI57.js.map
|