@llamaventures/cli 1.15.1 → 1.17.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/AGENT_BRIEFING.md +17 -7
- package/CHANGELOG.md +70 -1
- package/README.md +31 -6
- package/README.zh-CN.md +5 -4
- package/bin/llama-mcp.mjs +412 -25
- package/bin/llama.mjs +596 -38
- package/lib/client.mjs +353 -4
- package/package.json +3 -2
- package/scripts/verify-agent-routing.mjs +387 -18
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
import assert from "node:assert/strict";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
5
6
|
import { createServer } from "node:http";
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
7
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
8
9
|
import os from "node:os";
|
|
9
10
|
import path from "node:path";
|
|
10
11
|
import { fileURLToPath } from "node:url";
|
|
11
12
|
|
|
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 && npm pack --dry-run",
|
|
18
|
+
"CLI release gate must run agent routing tests and npm pack dry-run",
|
|
19
|
+
);
|
|
13
20
|
assert.equal(
|
|
14
21
|
existsSync(path.join(repoRoot, "docs/agent-skills.bundle.json")),
|
|
15
22
|
false,
|
|
@@ -20,8 +27,30 @@ assert.equal(
|
|
|
20
27
|
false,
|
|
21
28
|
"public llama-cli must not copy the Command-side skill mirror",
|
|
22
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
|
+
);
|
|
23
41
|
const calls = [];
|
|
24
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
|
+
}
|
|
25
54
|
|
|
26
55
|
async function readJson(req) {
|
|
27
56
|
let raw = "";
|
|
@@ -65,11 +94,48 @@ const server = createServer(async (req, res) => {
|
|
|
65
94
|
path: url.pathname,
|
|
66
95
|
query: Object.fromEntries(url.searchParams.entries()),
|
|
67
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
|
+
},
|
|
68
105
|
});
|
|
69
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
|
+
|
|
70
129
|
if (req.method === "GET" && url.pathname === "/api/agent/manifest") {
|
|
71
130
|
writeJson(res, {
|
|
72
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
|
+
},
|
|
73
139
|
briefing: "runtime briefing: use skills_search, skills_read, and object_inspect",
|
|
74
140
|
llama_os: {
|
|
75
141
|
visible_skill_count: 49,
|
|
@@ -85,6 +151,21 @@ const server = createServer(async (req, res) => {
|
|
|
85
151
|
return;
|
|
86
152
|
}
|
|
87
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
|
+
|
|
88
169
|
if (req.method === "GET" && url.pathname === "/api/agent/skills") {
|
|
89
170
|
writeJson(res, {
|
|
90
171
|
ok: true,
|
|
@@ -136,6 +217,114 @@ const server = createServer(async (req, res) => {
|
|
|
136
217
|
return;
|
|
137
218
|
}
|
|
138
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
|
+
|
|
139
328
|
if (req.method === "POST" && /^\/api\/deals\/[^/]+\/threads$/.test(url.pathname)) {
|
|
140
329
|
threadSeq += 1;
|
|
141
330
|
writeJson(res, { id: `thread-${threadSeq}` });
|
|
@@ -191,25 +380,34 @@ function resetCalls() {
|
|
|
191
380
|
threadSeq = 0;
|
|
192
381
|
}
|
|
193
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
|
+
|
|
194
391
|
function paths() {
|
|
195
|
-
return
|
|
392
|
+
return businessCalls().map((call) => `${call.method} ${call.path}`);
|
|
196
393
|
}
|
|
197
394
|
|
|
198
395
|
function assertNoEnrichCall() {
|
|
199
396
|
assert.equal(
|
|
200
|
-
|
|
397
|
+
businessCalls().some((call) => call.path.endsWith("/enrich")),
|
|
201
398
|
false,
|
|
202
399
|
`expected no /enrich call, got ${paths().join(", ")}`,
|
|
203
400
|
);
|
|
204
401
|
}
|
|
205
402
|
|
|
206
403
|
function assertThreadRun({ title, messageIncludes }) {
|
|
207
|
-
|
|
208
|
-
assert.
|
|
209
|
-
assert.
|
|
210
|
-
assert.
|
|
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$/);
|
|
211
409
|
for (const needle of messageIncludes) {
|
|
212
|
-
assert.match(
|
|
410
|
+
assert.match(relevant[1].body?.message ?? "", new RegExp(escapeRegExp(needle)));
|
|
213
411
|
}
|
|
214
412
|
}
|
|
215
413
|
|
|
@@ -313,18 +511,29 @@ const baseUrl = `http://${address.address}:${address.port}`;
|
|
|
313
511
|
const homeDir = await mkdtemp(path.join(os.tmpdir(), "llama-cli-routing-"));
|
|
314
512
|
|
|
315
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
|
+
|
|
316
524
|
resetCalls();
|
|
317
525
|
const bootstrapRun = await runCli(["agent", "bootstrap", "--limit", "3"], baseUrl, homeDir);
|
|
318
526
|
assert.match(bootstrapRun.stdout, /runtime briefing/);
|
|
319
527
|
assert.deepEqual(paths(), ["GET /api/agent/manifest"]);
|
|
320
|
-
assert.equal(
|
|
528
|
+
assert.equal(businessCalls()[0].query.limit, "3");
|
|
529
|
+
assert.ok(businessCalls()[0].query.clientVersion, "agent bootstrap passes clientVersion");
|
|
321
530
|
|
|
322
531
|
resetCalls();
|
|
323
532
|
const skillSearchRun = await runCli(["skills", "search", "pipeline", "--limit", "5"], baseUrl, homeDir);
|
|
324
533
|
assert.match(skillSearchRun.stdout, /llama-command/);
|
|
325
534
|
assert.deepEqual(paths(), ["GET /api/agent/skills"]);
|
|
326
|
-
assert.equal(
|
|
327
|
-
assert.equal(
|
|
535
|
+
assert.equal(businessCalls()[0].query.q, "pipeline");
|
|
536
|
+
assert.equal(businessCalls()[0].query.limit, "5");
|
|
328
537
|
|
|
329
538
|
resetCalls();
|
|
330
539
|
const skillShowRun = await runCli(["skills", "show", "llama-command"], baseUrl, homeDir);
|
|
@@ -336,7 +545,34 @@ try {
|
|
|
336
545
|
assert.match(explainRun.stdout, /Status: deleted/);
|
|
337
546
|
assert.match(explainRun.stdout, /Deleted by Kevin Yu/);
|
|
338
547
|
assert.deepEqual(paths(), ["GET /api/agent/explain"]);
|
|
339
|
-
assert.equal(
|
|
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");
|
|
340
576
|
|
|
341
577
|
resetCalls();
|
|
342
578
|
const enrichRun = await runCli(
|
|
@@ -369,9 +605,9 @@ try {
|
|
|
369
605
|
homeDir,
|
|
370
606
|
);
|
|
371
607
|
assert.deepEqual(paths(), ["POST /api/deals/deal-cli/enrich"]);
|
|
372
|
-
assert.equal(
|
|
373
|
-
assert.equal(
|
|
374
|
-
assert.equal(
|
|
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");
|
|
375
611
|
|
|
376
612
|
resetCalls();
|
|
377
613
|
const agentRun = await runCli(
|
|
@@ -386,6 +622,68 @@ try {
|
|
|
386
622
|
messageIncludes: ["custom server task"],
|
|
387
623
|
});
|
|
388
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
|
+
|
|
389
687
|
resetCalls();
|
|
390
688
|
const mcpResult = await callMcpTool(
|
|
391
689
|
"deal_enrich",
|
|
@@ -409,19 +707,90 @@ try {
|
|
|
409
707
|
assert.equal(payload.threadId, "thread-1");
|
|
410
708
|
assert.equal(payload.text, "agent done");
|
|
411
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
|
+
|
|
412
779
|
resetCalls();
|
|
413
780
|
const mcpBootstrap = await callMcpTool("agent_bootstrap", { limit: 2 }, baseUrl, homeDir);
|
|
414
781
|
const bootstrapPayload = JSON.parse(mcpBootstrap.content?.[0]?.text ?? "{}");
|
|
415
782
|
assert.equal(bootstrapPayload.ok, true);
|
|
416
783
|
assert.deepEqual(paths(), ["GET /api/agent/manifest"]);
|
|
417
|
-
assert.equal(
|
|
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");
|
|
418
787
|
|
|
419
788
|
resetCalls();
|
|
420
789
|
const mcpSkills = await callMcpTool("skills_search", { q: "command", limit: 4 }, baseUrl, homeDir);
|
|
421
790
|
const skillsPayload = JSON.parse(mcpSkills.content?.[0]?.text ?? "{}");
|
|
422
791
|
assert.equal(skillsPayload.skills?.[0]?.slug, "llama-command");
|
|
423
792
|
assert.deepEqual(paths(), ["GET /api/agent/skills"]);
|
|
424
|
-
assert.equal(
|
|
793
|
+
assert.equal(businessCalls()[0].query.q, "command");
|
|
425
794
|
|
|
426
795
|
resetCalls();
|
|
427
796
|
const mcpSkillRead = await callMcpTool("skills_read", { slug: "llama-command" }, baseUrl, homeDir);
|