@llamaventures/cli 1.17.2 → 1.17.3
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/CHANGELOG.md +12 -0
- package/README.md +103 -375
- package/README.zh-CN.md +105 -339
- package/package.json +1 -5
- package/CONTRIBUTING.md +0 -100
- package/SECURITY.md +0 -62
- package/assets/llama-ventures-logo.svg +0 -25
- package/scripts/verify-agent-routing.mjs +0 -816
|
@@ -1,816 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import assert from "node:assert/strict";
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
6
|
-
import { createServer } from "node:http";
|
|
7
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
-
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
9
|
-
import os from "node:os";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
-
|
|
13
|
-
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
-
const packageJson = JSON.parse(readFileSync(path.join(repoRoot, "package.json"), "utf8"));
|
|
15
|
-
assert.equal(
|
|
16
|
-
packageJson.scripts?.["verify:release"],
|
|
17
|
-
"npm test && node scripts/verify-tarball-clean.mjs && npm pack --dry-run",
|
|
18
|
-
"CLI release gate must run agent routing tests, the publish-surface hygiene scan, and npm pack dry-run",
|
|
19
|
-
);
|
|
20
|
-
assert.equal(
|
|
21
|
-
existsSync(path.join(repoRoot, "docs/agent-skills.bundle.json")),
|
|
22
|
-
false,
|
|
23
|
-
"public llama-cli must not bundle private Llama OS skill content",
|
|
24
|
-
);
|
|
25
|
-
assert.equal(
|
|
26
|
-
existsSync(path.join(repoRoot, "src/data/llama-os-skills.bundle.json")),
|
|
27
|
-
false,
|
|
28
|
-
"public llama-cli must not copy the Command-side skill mirror",
|
|
29
|
-
);
|
|
30
|
-
const cliSource = readFileSync(path.join(repoRoot, "bin/llama.mjs"), "utf8");
|
|
31
|
-
assert.match(
|
|
32
|
-
cliSource,
|
|
33
|
-
/About ONE specific deal\? \.{8} llama html publish <deal-id-or-name> --file <path> --title "\.\.\."/,
|
|
34
|
-
"top-level help must route deal-specific HTML to the agent-safe publish path",
|
|
35
|
-
);
|
|
36
|
-
assert.doesNotMatch(
|
|
37
|
-
cliSource,
|
|
38
|
-
/For deal-specific HTML use "llama html upload <dealId>"/,
|
|
39
|
-
"wiki help must not route deal-specific HTML to the low-level upload path",
|
|
40
|
-
);
|
|
41
|
-
const calls = [];
|
|
42
|
-
let threadSeq = 0;
|
|
43
|
-
let eventSeq = 0;
|
|
44
|
-
const htmlDocs = new Map();
|
|
45
|
-
|
|
46
|
-
function sha256Hex(value) {
|
|
47
|
-
return createHash("sha256").update(value).digest("hex");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function docsForDeal(dealId) {
|
|
51
|
-
if (!htmlDocs.has(dealId)) htmlDocs.set(dealId, new Map());
|
|
52
|
-
return htmlDocs.get(dealId);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function readJson(req) {
|
|
56
|
-
let raw = "";
|
|
57
|
-
for await (const chunk of req) raw += chunk;
|
|
58
|
-
if (!raw) return null;
|
|
59
|
-
try {
|
|
60
|
-
return JSON.parse(raw);
|
|
61
|
-
} catch {
|
|
62
|
-
return raw;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function writeJson(res, data) {
|
|
67
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
68
|
-
res.end(JSON.stringify(data));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function writeSse(res) {
|
|
72
|
-
res.writeHead(200, {
|
|
73
|
-
"Content-Type": "text/event-stream",
|
|
74
|
-
"Cache-Control": "no-cache",
|
|
75
|
-
Connection: "keep-alive",
|
|
76
|
-
});
|
|
77
|
-
const events = [
|
|
78
|
-
{ tool_use: { name: "read_typed_factual_layer" } },
|
|
79
|
-
{ tool_result: { name: "read_typed_factual_layer", ok: true, summary: "ok" } },
|
|
80
|
-
{ text: "agent done" },
|
|
81
|
-
];
|
|
82
|
-
for (const event of events) {
|
|
83
|
-
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
84
|
-
}
|
|
85
|
-
res.end();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const server = createServer(async (req, res) => {
|
|
89
|
-
try {
|
|
90
|
-
const body = await readJson(req);
|
|
91
|
-
const url = new URL(req.url, "http://localhost");
|
|
92
|
-
calls.push({
|
|
93
|
-
method: req.method,
|
|
94
|
-
path: url.pathname,
|
|
95
|
-
query: Object.fromEntries(url.searchParams.entries()),
|
|
96
|
-
body,
|
|
97
|
-
headers: {
|
|
98
|
-
client: req.headers["x-llama-client"] ?? null,
|
|
99
|
-
clientVersion: req.headers["x-llama-client-version"] ?? null,
|
|
100
|
-
agentClient: req.headers["x-llama-agent-client"] ?? null,
|
|
101
|
-
session: req.headers["x-llama-agent-session"] ?? null,
|
|
102
|
-
command: req.headers["x-llama-command"] ?? null,
|
|
103
|
-
uploadId: req.headers["x-llama-upload-id"] ?? null,
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
if (req.method === "POST" && url.pathname === "/api/agent/client-events") {
|
|
108
|
-
eventSeq += 1;
|
|
109
|
-
writeJson(res, {
|
|
110
|
-
ok: true,
|
|
111
|
-
eventId: eventSeq,
|
|
112
|
-
candidateId: body?.command?.endsWith(".search") ? eventSeq + 1000 : null,
|
|
113
|
-
});
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (req.method === "POST" && url.pathname === "/api/agent/eval-feedback") {
|
|
118
|
-
writeJson(res, {
|
|
119
|
-
ok: true,
|
|
120
|
-
candidate: {
|
|
121
|
-
id: 42,
|
|
122
|
-
source_event_id: body?.eventId ?? null,
|
|
123
|
-
feedback: body?.action ?? null,
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (req.method === "GET" && url.pathname === "/api/agent/manifest") {
|
|
130
|
-
writeJson(res, {
|
|
131
|
-
ok: true,
|
|
132
|
-
contract: {
|
|
133
|
-
contract_version: "agent-contract.v1",
|
|
134
|
-
cli: {
|
|
135
|
-
client_version: url.searchParams.get("clientVersion"),
|
|
136
|
-
status: "ok",
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
briefing: "runtime briefing: use skills_search, skills_read, and object_inspect",
|
|
140
|
-
llama_os: {
|
|
141
|
-
visible_skill_count: 49,
|
|
142
|
-
included_skill_count: Number(url.searchParams.get("limit") || 25),
|
|
143
|
-
},
|
|
144
|
-
skills: [
|
|
145
|
-
{
|
|
146
|
-
slug: "llama-command",
|
|
147
|
-
description: "Llama Command runtime skill",
|
|
148
|
-
},
|
|
149
|
-
],
|
|
150
|
-
});
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (req.method === "GET" && url.pathname === "/api/agent/briefing") {
|
|
155
|
-
writeJson(res, {
|
|
156
|
-
ok: true,
|
|
157
|
-
contract: {
|
|
158
|
-
contract_version: "agent-contract.v1",
|
|
159
|
-
cli: {
|
|
160
|
-
client_version: url.searchParams.get("clientVersion"),
|
|
161
|
-
status: "ok",
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
briefing: "server-owned briefing: check CLI, use Pipeline First, prefer CLI/MCP",
|
|
165
|
-
});
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (req.method === "GET" && url.pathname === "/api/agent/skills") {
|
|
170
|
-
writeJson(res, {
|
|
171
|
-
ok: true,
|
|
172
|
-
q: url.searchParams.get("q"),
|
|
173
|
-
count: 1,
|
|
174
|
-
skills: [
|
|
175
|
-
{
|
|
176
|
-
slug: "llama-command",
|
|
177
|
-
description: "Llama Command runtime skill",
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
});
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (req.method === "GET" && url.pathname === "/api/agent/skills/llama-command") {
|
|
185
|
-
writeJson(res, {
|
|
186
|
-
ok: true,
|
|
187
|
-
skill: {
|
|
188
|
-
slug: "llama-command",
|
|
189
|
-
content: "---\nname: llama-command\n---\n# Llama Command runtime skill\n",
|
|
190
|
-
},
|
|
191
|
-
});
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (req.method === "GET" && url.pathname === "/api/agent/explain") {
|
|
196
|
-
writeJson(res, {
|
|
197
|
-
ok: true,
|
|
198
|
-
result: {
|
|
199
|
-
target: {
|
|
200
|
-
objectType: "wiki_article",
|
|
201
|
-
objectId: "missing-page",
|
|
202
|
-
status: "deleted",
|
|
203
|
-
title: "Missing Page",
|
|
204
|
-
detail: "Deleted by Alex Chen",
|
|
205
|
-
url: "https://command.llamaventures.vc/wiki/missing-page",
|
|
206
|
-
},
|
|
207
|
-
lifecycle: [
|
|
208
|
-
{
|
|
209
|
-
action: "deleted",
|
|
210
|
-
actor_label: "Alex Chen",
|
|
211
|
-
created_at: "2026-06-15T19:02:00Z",
|
|
212
|
-
reason: "user_deleted",
|
|
213
|
-
},
|
|
214
|
-
],
|
|
215
|
-
},
|
|
216
|
-
});
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (req.method === "GET" && url.pathname === "/api/wiki/search") {
|
|
221
|
-
writeJson(res, [
|
|
222
|
-
{
|
|
223
|
-
slug: "llama-weekly-2026-06-16",
|
|
224
|
-
title: "Llama Weekly 2026-06-16",
|
|
225
|
-
},
|
|
226
|
-
]);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (req.method === "GET" && url.pathname === "/api/deals") {
|
|
231
|
-
writeJson(res, {
|
|
232
|
-
deals: [
|
|
233
|
-
{
|
|
234
|
-
uuid: "deal-html",
|
|
235
|
-
companyName: "Acme AI",
|
|
236
|
-
founders: "Ada Founder",
|
|
237
|
-
description: "mock deal for HTML upload tests",
|
|
238
|
-
},
|
|
239
|
-
],
|
|
240
|
-
total: 1,
|
|
241
|
-
limit: Number(url.searchParams.get("limit") || 200),
|
|
242
|
-
offset: 0,
|
|
243
|
-
});
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const docsMatch = url.pathname.match(/^\/api\/deals\/([^/]+)\/documents$/);
|
|
248
|
-
if (docsMatch) {
|
|
249
|
-
const dealId = decodeURIComponent(docsMatch[1]);
|
|
250
|
-
if (dealId !== "deal-html") {
|
|
251
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
252
|
-
res.end(JSON.stringify({ error: "deal not found" }));
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
if (req.method === "GET") {
|
|
256
|
-
const docs = Array.from(docsForDeal(dealId).entries()).map(([slug, doc]) => ({
|
|
257
|
-
slug,
|
|
258
|
-
title: doc.title,
|
|
259
|
-
latest_version: doc.version ?? null,
|
|
260
|
-
latest_updated_at: doc.version ? "2026-06-23T04:00:00Z" : null,
|
|
261
|
-
}));
|
|
262
|
-
writeJson(res, { documents: docs });
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
if (req.method === "POST") {
|
|
266
|
-
const docs = docsForDeal(dealId);
|
|
267
|
-
const slug = body?.slug;
|
|
268
|
-
docs.set(slug, {
|
|
269
|
-
...(docs.get(slug) || {}),
|
|
270
|
-
title: body?.title || slug,
|
|
271
|
-
});
|
|
272
|
-
writeJson(res, { ok: true, slug, title: body?.title || slug });
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const htmlMatch = url.pathname.match(/^\/api\/deals\/([^/]+)\/documents\/([^/]+)\/html$/);
|
|
278
|
-
if (htmlMatch) {
|
|
279
|
-
const dealId = decodeURIComponent(htmlMatch[1]);
|
|
280
|
-
const slug = decodeURIComponent(htmlMatch[2]);
|
|
281
|
-
const docs = docsForDeal(dealId);
|
|
282
|
-
if (req.method === "PUT") {
|
|
283
|
-
const html = typeof body?.html === "string" ? body.html : "";
|
|
284
|
-
const previous = docs.get(slug) || { title: slug, version: 0 };
|
|
285
|
-
const version = Number(previous.version || 0) + 1;
|
|
286
|
-
const bytes = Buffer.byteLength(html, "utf8");
|
|
287
|
-
const sha256 = sha256Hex(html);
|
|
288
|
-
docs.set(slug, {
|
|
289
|
-
...previous,
|
|
290
|
-
html,
|
|
291
|
-
version,
|
|
292
|
-
bytes,
|
|
293
|
-
sha256,
|
|
294
|
-
source: body?.source || "cli",
|
|
295
|
-
client_upload_id: body?.client_upload_id || null,
|
|
296
|
-
});
|
|
297
|
-
writeJson(res, {
|
|
298
|
-
ok: true,
|
|
299
|
-
document_slug: slug,
|
|
300
|
-
version,
|
|
301
|
-
bytes,
|
|
302
|
-
sha256,
|
|
303
|
-
client_upload_id: body?.client_upload_id || null,
|
|
304
|
-
idempotent_replay: false,
|
|
305
|
-
});
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
if (req.method === "GET") {
|
|
309
|
-
const doc = docs.get(slug);
|
|
310
|
-
if (!doc?.html) {
|
|
311
|
-
writeJson(res, { empty: true });
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
writeJson(res, {
|
|
315
|
-
empty: false,
|
|
316
|
-
document_slug: slug,
|
|
317
|
-
version: doc.version,
|
|
318
|
-
bytes: doc.bytes,
|
|
319
|
-
sha256: doc.sha256,
|
|
320
|
-
source: doc.source,
|
|
321
|
-
created_at: "2026-06-23T04:00:00Z",
|
|
322
|
-
html: doc.html,
|
|
323
|
-
});
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (req.method === "POST" && /^\/api\/deals\/[^/]+\/threads$/.test(url.pathname)) {
|
|
329
|
-
threadSeq += 1;
|
|
330
|
-
writeJson(res, { id: `thread-${threadSeq}` });
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (req.method === "POST" && /^\/api\/deals\/[^/]+\/threads\/[^/]+$/.test(url.pathname)) {
|
|
335
|
-
writeSse(res);
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (req.method === "POST" && /^\/api\/deals\/[^/]+\/enrich$/.test(url.pathname)) {
|
|
340
|
-
writeJson(res, {
|
|
341
|
-
ok: true,
|
|
342
|
-
agentHarness: {
|
|
343
|
-
handoffPrompt: "mock handoff prompt",
|
|
344
|
-
systemInjection: "mock system injection",
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
351
|
-
res.end(JSON.stringify({ error: `Unexpected route ${req.method} ${url.pathname}` }));
|
|
352
|
-
} catch (err) {
|
|
353
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
354
|
-
res.end(JSON.stringify({ error: err?.message ?? String(err) }));
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
function listen(server) {
|
|
359
|
-
return new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function close(server) {
|
|
363
|
-
return new Promise((resolve, reject) => {
|
|
364
|
-
server.close((err) => (err ? reject(err) : resolve()));
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function childEnv(baseUrl, homeDir) {
|
|
369
|
-
return {
|
|
370
|
-
...process.env,
|
|
371
|
-
HOME: homeDir,
|
|
372
|
-
LLAMA_API_URL: baseUrl,
|
|
373
|
-
LLAMA_TOKEN: "llc_mock_agent_routing",
|
|
374
|
-
PATH: "/usr/bin:/bin",
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function resetCalls() {
|
|
379
|
-
calls.length = 0;
|
|
380
|
-
threadSeq = 0;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function businessCalls() {
|
|
384
|
-
return calls.filter((call) => call.path !== "/api/agent/client-events");
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function telemetryCalls() {
|
|
388
|
-
return calls.filter((call) => call.path === "/api/agent/client-events");
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function paths() {
|
|
392
|
-
return businessCalls().map((call) => `${call.method} ${call.path}`);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function assertNoEnrichCall() {
|
|
396
|
-
assert.equal(
|
|
397
|
-
businessCalls().some((call) => call.path.endsWith("/enrich")),
|
|
398
|
-
false,
|
|
399
|
-
`expected no /enrich call, got ${paths().join(", ")}`,
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function assertThreadRun({ title, messageIncludes }) {
|
|
404
|
-
const relevant = businessCalls();
|
|
405
|
-
assert.equal(relevant.length, 2, `expected thread create + SSE run, got ${paths().join(", ")}`);
|
|
406
|
-
assert.match(relevant[0].path, /^\/api\/deals\/[^/]+\/threads$/);
|
|
407
|
-
assert.equal(relevant[0].body?.title, title);
|
|
408
|
-
assert.match(relevant[1].path, /^\/api\/deals\/[^/]+\/threads\/thread-1$/);
|
|
409
|
-
for (const needle of messageIncludes) {
|
|
410
|
-
assert.match(relevant[1].body?.message ?? "", new RegExp(escapeRegExp(needle)));
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function escapeRegExp(value) {
|
|
415
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
async function runCli(args, baseUrl, homeDir) {
|
|
419
|
-
const child = spawn(process.execPath, ["bin/llama.mjs", ...args], {
|
|
420
|
-
cwd: repoRoot,
|
|
421
|
-
env: childEnv(baseUrl, homeDir),
|
|
422
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
423
|
-
});
|
|
424
|
-
let stdout = "";
|
|
425
|
-
let stderr = "";
|
|
426
|
-
child.stdout.on("data", (chunk) => {
|
|
427
|
-
stdout += chunk;
|
|
428
|
-
});
|
|
429
|
-
child.stderr.on("data", (chunk) => {
|
|
430
|
-
stderr += chunk;
|
|
431
|
-
});
|
|
432
|
-
const code = await new Promise((resolve) => child.on("close", resolve));
|
|
433
|
-
assert.equal(code, 0, `CLI failed (${code})\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`);
|
|
434
|
-
return { stdout, stderr };
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
async function callMcpTool(name, args, baseUrl, homeDir) {
|
|
438
|
-
const child = spawn(process.execPath, ["bin/llama-mcp.mjs"], {
|
|
439
|
-
cwd: repoRoot,
|
|
440
|
-
env: childEnv(baseUrl, homeDir),
|
|
441
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
442
|
-
});
|
|
443
|
-
let stderr = "";
|
|
444
|
-
let buffer = "";
|
|
445
|
-
child.stderr.on("data", (chunk) => {
|
|
446
|
-
stderr += chunk;
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
const result = await new Promise((resolve, reject) => {
|
|
450
|
-
const timeout = setTimeout(() => {
|
|
451
|
-
child.kill();
|
|
452
|
-
reject(new Error(`Timed out waiting for MCP response\nSTDERR:\n${stderr}`));
|
|
453
|
-
}, 8000);
|
|
454
|
-
|
|
455
|
-
child.stdout.on("data", (chunk) => {
|
|
456
|
-
buffer += chunk;
|
|
457
|
-
let idx;
|
|
458
|
-
while ((idx = buffer.indexOf("\n")) >= 0) {
|
|
459
|
-
const line = buffer.slice(0, idx).trim();
|
|
460
|
-
buffer = buffer.slice(idx + 1);
|
|
461
|
-
if (!line) continue;
|
|
462
|
-
let msg;
|
|
463
|
-
try {
|
|
464
|
-
msg = JSON.parse(line);
|
|
465
|
-
} catch {
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
if (msg.id === 2) {
|
|
469
|
-
clearTimeout(timeout);
|
|
470
|
-
child.kill();
|
|
471
|
-
resolve(msg);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
child.on("error", (err) => {
|
|
477
|
-
clearTimeout(timeout);
|
|
478
|
-
reject(err);
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
child.stdin.write(
|
|
482
|
-
[
|
|
483
|
-
JSON.stringify({
|
|
484
|
-
jsonrpc: "2.0",
|
|
485
|
-
id: 1,
|
|
486
|
-
method: "initialize",
|
|
487
|
-
params: {
|
|
488
|
-
protocolVersion: "2024-11-05",
|
|
489
|
-
capabilities: {},
|
|
490
|
-
clientInfo: { name: "routing-test", version: "1" },
|
|
491
|
-
},
|
|
492
|
-
}),
|
|
493
|
-
JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }),
|
|
494
|
-
JSON.stringify({
|
|
495
|
-
jsonrpc: "2.0",
|
|
496
|
-
id: 2,
|
|
497
|
-
method: "tools/call",
|
|
498
|
-
params: { name, arguments: args },
|
|
499
|
-
}),
|
|
500
|
-
].join("\n") + "\n",
|
|
501
|
-
);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
assert.ok(!result.error, `MCP returned error: ${JSON.stringify(result.error)}`);
|
|
505
|
-
return result.result;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
await listen(server);
|
|
509
|
-
const address = server.address();
|
|
510
|
-
const baseUrl = `http://${address.address}:${address.port}`;
|
|
511
|
-
const homeDir = await mkdtemp(path.join(os.tmpdir(), "llama-cli-routing-"));
|
|
512
|
-
|
|
513
|
-
try {
|
|
514
|
-
resetCalls();
|
|
515
|
-
const onboardRun = await runCli(["agent-onboard"], baseUrl, homeDir);
|
|
516
|
-
assert.match(onboardRun.stdout, /server-owned briefing/);
|
|
517
|
-
assert.deepEqual(paths(), ["GET /api/agent/briefing"]);
|
|
518
|
-
assert.ok(businessCalls()[0].query.clientVersion, "agent-onboard passes clientVersion");
|
|
519
|
-
assert.equal(telemetryCalls()[0].body?.command, "agent.briefing");
|
|
520
|
-
assert.equal(telemetryCalls()[0].body?.client, "cli");
|
|
521
|
-
assert.ok(telemetryCalls()[0].body?.sessionId, "telemetry includes an agent session id");
|
|
522
|
-
assert.equal(businessCalls()[0].headers.command, "agent.briefing");
|
|
523
|
-
|
|
524
|
-
resetCalls();
|
|
525
|
-
const bootstrapRun = await runCli(["agent", "bootstrap", "--limit", "3"], baseUrl, homeDir);
|
|
526
|
-
assert.match(bootstrapRun.stdout, /runtime briefing/);
|
|
527
|
-
assert.deepEqual(paths(), ["GET /api/agent/manifest"]);
|
|
528
|
-
assert.equal(businessCalls()[0].query.limit, "3");
|
|
529
|
-
assert.ok(businessCalls()[0].query.clientVersion, "agent bootstrap passes clientVersion");
|
|
530
|
-
|
|
531
|
-
resetCalls();
|
|
532
|
-
const skillSearchRun = await runCli(["skills", "search", "pipeline", "--limit", "5"], baseUrl, homeDir);
|
|
533
|
-
assert.match(skillSearchRun.stdout, /llama-command/);
|
|
534
|
-
assert.deepEqual(paths(), ["GET /api/agent/skills"]);
|
|
535
|
-
assert.equal(businessCalls()[0].query.q, "pipeline");
|
|
536
|
-
assert.equal(businessCalls()[0].query.limit, "5");
|
|
537
|
-
|
|
538
|
-
resetCalls();
|
|
539
|
-
const skillShowRun = await runCli(["skills", "show", "llama-command"], baseUrl, homeDir);
|
|
540
|
-
assert.match(skillShowRun.stdout, /# Llama Command runtime skill/);
|
|
541
|
-
assert.deepEqual(paths(), ["GET /api/agent/skills/llama-command"]);
|
|
542
|
-
|
|
543
|
-
resetCalls();
|
|
544
|
-
const explainRun = await runCli(["explain", "https://command.llamaventures.vc/wiki/missing-page"], baseUrl, homeDir);
|
|
545
|
-
assert.match(explainRun.stdout, /Status: deleted/);
|
|
546
|
-
assert.match(explainRun.stdout, /Deleted by Alex Chen/);
|
|
547
|
-
assert.deepEqual(paths(), ["GET /api/agent/explain"]);
|
|
548
|
-
assert.equal(businessCalls()[0].query.q, "https://command.llamaventures.vc/wiki/missing-page");
|
|
549
|
-
|
|
550
|
-
resetCalls();
|
|
551
|
-
const wikiRun = await runCli(["wiki", "search", "llama weekly"], baseUrl, homeDir);
|
|
552
|
-
assert.match(wikiRun.stdout, /llama-weekly-2026-06-16/);
|
|
553
|
-
assert.deepEqual(paths(), ["GET /api/wiki/search"]);
|
|
554
|
-
assert.equal(telemetryCalls()[0].body?.command, "wiki.search");
|
|
555
|
-
assert.equal(telemetryCalls()[0].body?.query, "llama weekly");
|
|
556
|
-
|
|
557
|
-
resetCalls();
|
|
558
|
-
const evalRun = await runCli(
|
|
559
|
-
[
|
|
560
|
-
"eval",
|
|
561
|
-
"bad",
|
|
562
|
-
"--last",
|
|
563
|
-
"--reason",
|
|
564
|
-
"missed dev weekly",
|
|
565
|
-
"--expect",
|
|
566
|
-
"wiki:llamaos-weekly-2026-06-17",
|
|
567
|
-
],
|
|
568
|
-
baseUrl,
|
|
569
|
-
homeDir,
|
|
570
|
-
);
|
|
571
|
-
assert.match(evalRun.stdout, /"feedback": "bad"/);
|
|
572
|
-
assert.deepEqual(paths(), ["POST /api/agent/eval-feedback"]);
|
|
573
|
-
assert.equal(businessCalls()[0].body?.action, "bad");
|
|
574
|
-
assert.equal(businessCalls()[0].body?.eventId, 6);
|
|
575
|
-
assert.equal(businessCalls()[0].body?.expected?.wikiSlugs?.[0], "llamaos-weekly-2026-06-17");
|
|
576
|
-
|
|
577
|
-
resetCalls();
|
|
578
|
-
const enrichRun = await runCli(
|
|
579
|
-
[
|
|
580
|
-
"deal",
|
|
581
|
-
"enrich",
|
|
582
|
-
"deal-cli",
|
|
583
|
-
"--apply",
|
|
584
|
-
"--executor",
|
|
585
|
-
"server_agent",
|
|
586
|
-
"--sources",
|
|
587
|
-
"website,monid",
|
|
588
|
-
"--budget-cents",
|
|
589
|
-
"12",
|
|
590
|
-
],
|
|
591
|
-
baseUrl,
|
|
592
|
-
homeDir,
|
|
593
|
-
);
|
|
594
|
-
assert.match(enrichRun.stdout, /agent done/);
|
|
595
|
-
assertNoEnrichCall();
|
|
596
|
-
assertThreadRun({
|
|
597
|
-
title: "CLI enrichment",
|
|
598
|
-
messageIncludes: ["website, monid", "12 cents", "upsert_typed_fact"],
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
resetCalls();
|
|
602
|
-
await runCli(
|
|
603
|
-
["deal", "enrich", "deal-cli", "--apply", "--executor", "server_agent", "--harness-only"],
|
|
604
|
-
baseUrl,
|
|
605
|
-
homeDir,
|
|
606
|
-
);
|
|
607
|
-
assert.deepEqual(paths(), ["POST /api/deals/deal-cli/enrich"]);
|
|
608
|
-
assert.equal(businessCalls()[0].body?.apply, true);
|
|
609
|
-
assert.equal(businessCalls()[0].body?.dryRun, false);
|
|
610
|
-
assert.equal(businessCalls()[0].body?.executor, "server_agent");
|
|
611
|
-
|
|
612
|
-
resetCalls();
|
|
613
|
-
const agentRun = await runCli(
|
|
614
|
-
["deal", "agent", "run", "deal-cli", "--message", "custom server task"],
|
|
615
|
-
baseUrl,
|
|
616
|
-
homeDir,
|
|
617
|
-
);
|
|
618
|
-
assert.match(agentRun.stdout, /agent done/);
|
|
619
|
-
assertNoEnrichCall();
|
|
620
|
-
assertThreadRun({
|
|
621
|
-
title: "CLI agent run",
|
|
622
|
-
messageIncludes: ["custom server task"],
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
const largeHtmlPath = path.join(homeDir, "full-memo.html");
|
|
626
|
-
const largeHtml =
|
|
627
|
-
"<!doctype html><html><head><title>Full Memo</title></head><body>" +
|
|
628
|
-
`<p>${"agent-safe upload ".repeat(18000)}</p>` +
|
|
629
|
-
"</body></html>";
|
|
630
|
-
assert.ok(
|
|
631
|
-
Buffer.byteLength(largeHtml, "utf8") > 252 * 1024,
|
|
632
|
-
"routing test HTML must exceed the incident-sized 252KB memo",
|
|
633
|
-
);
|
|
634
|
-
await writeFile(largeHtmlPath, largeHtml);
|
|
635
|
-
|
|
636
|
-
resetCalls();
|
|
637
|
-
const publishRun = await runCli(
|
|
638
|
-
[
|
|
639
|
-
"html",
|
|
640
|
-
"publish",
|
|
641
|
-
"Acme AI",
|
|
642
|
-
"--file",
|
|
643
|
-
largeHtmlPath,
|
|
644
|
-
"--title",
|
|
645
|
-
"Full Memo",
|
|
646
|
-
"--doc",
|
|
647
|
-
"full-memo",
|
|
648
|
-
],
|
|
649
|
-
baseUrl,
|
|
650
|
-
homeDir,
|
|
651
|
-
);
|
|
652
|
-
const publishPayload = JSON.parse(publishRun.stdout);
|
|
653
|
-
assert.equal(publishPayload.ok, true);
|
|
654
|
-
assert.equal(publishPayload.deal_uuid, "deal-html");
|
|
655
|
-
assert.equal(publishPayload.document_slug, "full-memo");
|
|
656
|
-
assert.equal(publishPayload.verified?.ok, true);
|
|
657
|
-
assert.match(publishPayload.client_upload_id, /^cli-[0-9a-f-]{36}$/);
|
|
658
|
-
assert.match(publishPayload.sha256, /^[a-f0-9]{64}$/);
|
|
659
|
-
assert.equal(publishPayload.verified?.sha256, publishPayload.sha256);
|
|
660
|
-
assert.deepEqual(paths(), [
|
|
661
|
-
"GET /api/deals/Acme%20AI/documents",
|
|
662
|
-
"GET /api/deals",
|
|
663
|
-
"GET /api/deals/deal-html/documents",
|
|
664
|
-
"POST /api/deals/deal-html/documents",
|
|
665
|
-
"PUT /api/deals/deal-html/documents/full-memo/html",
|
|
666
|
-
"GET /api/deals/deal-html/documents/full-memo/html",
|
|
667
|
-
]);
|
|
668
|
-
assert.equal(businessCalls()[4].body?.html, largeHtml);
|
|
669
|
-
assert.equal(businessCalls()[4].body?.source, "cli");
|
|
670
|
-
assert.equal(businessCalls()[4].body?.client_upload_id, publishPayload.client_upload_id);
|
|
671
|
-
assert.equal(businessCalls()[4].headers.uploadId, publishPayload.client_upload_id);
|
|
672
|
-
const publishTelemetry = telemetryCalls().find(
|
|
673
|
-
(call) =>
|
|
674
|
-
call.body?.method === "PUT" &&
|
|
675
|
-
call.body?.endpoint === "/api/deals/deal-html/documents/full-memo/html",
|
|
676
|
-
);
|
|
677
|
-
assert.ok(publishTelemetry, "publish upload request must record telemetry");
|
|
678
|
-
assert.equal(publishTelemetry.body?.args?.html?.redacted, true);
|
|
679
|
-
assert.equal(publishTelemetry.body?.args?.html?.bytes, Buffer.byteLength(largeHtml, "utf8"));
|
|
680
|
-
assert.equal(publishTelemetry.body?.args?.html?.sha256, sha256Hex(largeHtml));
|
|
681
|
-
assert.doesNotMatch(
|
|
682
|
-
JSON.stringify(telemetryCalls()),
|
|
683
|
-
/agent-safe upload agent-safe upload agent-safe upload/,
|
|
684
|
-
"telemetry must not contain raw memo HTML text",
|
|
685
|
-
);
|
|
686
|
-
|
|
687
|
-
resetCalls();
|
|
688
|
-
const mcpResult = await callMcpTool(
|
|
689
|
-
"deal_enrich",
|
|
690
|
-
{
|
|
691
|
-
dealId: "deal-mcp",
|
|
692
|
-
apply: true,
|
|
693
|
-
executor: "server_agent",
|
|
694
|
-
sources: ["web", "monid"],
|
|
695
|
-
budgetCents: 7,
|
|
696
|
-
},
|
|
697
|
-
baseUrl,
|
|
698
|
-
homeDir,
|
|
699
|
-
);
|
|
700
|
-
assertNoEnrichCall();
|
|
701
|
-
assertThreadRun({
|
|
702
|
-
title: "MCP enrichment",
|
|
703
|
-
messageIncludes: ["web, monid", "7 cents", "upsert_typed_fact"],
|
|
704
|
-
});
|
|
705
|
-
const payload = JSON.parse(mcpResult.content?.[0]?.text ?? "{}");
|
|
706
|
-
assert.equal(payload.ok, true);
|
|
707
|
-
assert.equal(payload.threadId, "thread-1");
|
|
708
|
-
assert.equal(payload.text, "agent done");
|
|
709
|
-
|
|
710
|
-
resetCalls();
|
|
711
|
-
const inlineGuard = await callMcpTool(
|
|
712
|
-
"html_upload",
|
|
713
|
-
{
|
|
714
|
-
dealId: "deal-html",
|
|
715
|
-
documentSlug: "inline-too-large",
|
|
716
|
-
html: largeHtml,
|
|
717
|
-
},
|
|
718
|
-
baseUrl,
|
|
719
|
-
homeDir,
|
|
720
|
-
);
|
|
721
|
-
assert.equal(inlineGuard.isError, true);
|
|
722
|
-
assert.match(inlineGuard.content?.[0]?.text ?? "", /Use html_upload_file/);
|
|
723
|
-
assert.deepEqual(paths(), []);
|
|
724
|
-
|
|
725
|
-
resetCalls();
|
|
726
|
-
const inlineBundleGuard = await callMcpTool(
|
|
727
|
-
"html_upload_bundle",
|
|
728
|
-
{
|
|
729
|
-
dealId: "deal-html",
|
|
730
|
-
documentSlug: "bundle-too-large",
|
|
731
|
-
html: largeHtml,
|
|
732
|
-
assets: [
|
|
733
|
-
{
|
|
734
|
-
path: "full-memo_files/cover.txt",
|
|
735
|
-
contentType: "text/plain",
|
|
736
|
-
base64: Buffer.from("asset").toString("base64"),
|
|
737
|
-
},
|
|
738
|
-
],
|
|
739
|
-
},
|
|
740
|
-
baseUrl,
|
|
741
|
-
homeDir,
|
|
742
|
-
);
|
|
743
|
-
assert.equal(inlineBundleGuard.isError, true);
|
|
744
|
-
assert.match(inlineBundleGuard.content?.[0]?.text ?? "", /Use html_upload_file/);
|
|
745
|
-
assert.deepEqual(paths(), []);
|
|
746
|
-
|
|
747
|
-
resetCalls();
|
|
748
|
-
const mcpFile = await callMcpTool(
|
|
749
|
-
"html_upload_file",
|
|
750
|
-
{
|
|
751
|
-
dealId: "deal-html",
|
|
752
|
-
documentSlug: "mcp-file",
|
|
753
|
-
filePath: largeHtmlPath,
|
|
754
|
-
},
|
|
755
|
-
baseUrl,
|
|
756
|
-
homeDir,
|
|
757
|
-
);
|
|
758
|
-
const mcpFilePayload = JSON.parse(mcpFile.content?.[0]?.text ?? "{}");
|
|
759
|
-
assert.equal(mcpFilePayload.ok, true);
|
|
760
|
-
assert.equal(mcpFilePayload.verified?.ok, true);
|
|
761
|
-
assert.match(mcpFilePayload.client_upload_id, /^mcp-[0-9a-f-]{36}$/);
|
|
762
|
-
assert.match(mcpFilePayload.sha256, /^[a-f0-9]{64}$/);
|
|
763
|
-
assert.equal(mcpFilePayload.verified?.sha256, mcpFilePayload.sha256);
|
|
764
|
-
assert.deepEqual(paths(), [
|
|
765
|
-
"PUT /api/deals/deal-html/documents/mcp-file/html",
|
|
766
|
-
"GET /api/deals/deal-html/documents/mcp-file/html",
|
|
767
|
-
]);
|
|
768
|
-
assert.equal(businessCalls()[0].body?.client_upload_id, mcpFilePayload.client_upload_id);
|
|
769
|
-
assert.equal(businessCalls()[0].headers.uploadId, mcpFilePayload.client_upload_id);
|
|
770
|
-
const mcpFileTelemetry = telemetryCalls().find(
|
|
771
|
-
(call) =>
|
|
772
|
-
call.body?.method === "PUT" &&
|
|
773
|
-
call.body?.endpoint === "/api/deals/deal-html/documents/mcp-file/html",
|
|
774
|
-
);
|
|
775
|
-
assert.ok(mcpFileTelemetry, "MCP file upload request must record telemetry");
|
|
776
|
-
assert.equal(mcpFileTelemetry.body?.args?.html?.redacted, true);
|
|
777
|
-
assert.equal(mcpFileTelemetry.body?.args?.html?.sha256, sha256Hex(largeHtml));
|
|
778
|
-
|
|
779
|
-
resetCalls();
|
|
780
|
-
const mcpBootstrap = await callMcpTool("agent_bootstrap", { limit: 2 }, baseUrl, homeDir);
|
|
781
|
-
const bootstrapPayload = JSON.parse(mcpBootstrap.content?.[0]?.text ?? "{}");
|
|
782
|
-
assert.equal(bootstrapPayload.ok, true);
|
|
783
|
-
assert.deepEqual(paths(), ["GET /api/agent/manifest"]);
|
|
784
|
-
assert.equal(businessCalls()[0].query.limit, "2");
|
|
785
|
-
assert.ok(businessCalls()[0].query.clientVersion, "mcp agent_bootstrap passes clientVersion");
|
|
786
|
-
assert.equal(telemetryCalls()[0].body?.client, "mcp");
|
|
787
|
-
|
|
788
|
-
resetCalls();
|
|
789
|
-
const mcpSkills = await callMcpTool("skills_search", { q: "command", limit: 4 }, baseUrl, homeDir);
|
|
790
|
-
const skillsPayload = JSON.parse(mcpSkills.content?.[0]?.text ?? "{}");
|
|
791
|
-
assert.equal(skillsPayload.skills?.[0]?.slug, "llama-command");
|
|
792
|
-
assert.deepEqual(paths(), ["GET /api/agent/skills"]);
|
|
793
|
-
assert.equal(businessCalls()[0].query.q, "command");
|
|
794
|
-
|
|
795
|
-
resetCalls();
|
|
796
|
-
const mcpSkillRead = await callMcpTool("skills_read", { slug: "llama-command" }, baseUrl, homeDir);
|
|
797
|
-
const skillPayload = JSON.parse(mcpSkillRead.content?.[0]?.text ?? "{}");
|
|
798
|
-
assert.match(skillPayload.skill?.content ?? "", /# Llama Command runtime skill/);
|
|
799
|
-
assert.deepEqual(paths(), ["GET /api/agent/skills/llama-command"]);
|
|
800
|
-
|
|
801
|
-
resetCalls();
|
|
802
|
-
const mcpInspect = await callMcpTool(
|
|
803
|
-
"object_inspect",
|
|
804
|
-
{ q: "https://command.llamaventures.vc/wiki/missing-page" },
|
|
805
|
-
baseUrl,
|
|
806
|
-
homeDir,
|
|
807
|
-
);
|
|
808
|
-
const inspectPayload = JSON.parse(mcpInspect.content?.[0]?.text ?? "{}");
|
|
809
|
-
assert.equal(inspectPayload.result?.target?.status, "deleted");
|
|
810
|
-
assert.deepEqual(paths(), ["GET /api/agent/explain"]);
|
|
811
|
-
|
|
812
|
-
console.log("agent routing verification passed");
|
|
813
|
-
} finally {
|
|
814
|
-
await close(server);
|
|
815
|
-
await rm(homeDir, { recursive: true, force: true });
|
|
816
|
-
}
|