@llamaventures/cli 1.16.0 → 1.17.1
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 -4
- package/CHANGELOG.md +58 -1
- package/README.md +3 -0
- package/README.zh-CN.md +3 -0
- package/bin/llama-mcp.mjs +335 -17
- package/bin/llama.mjs +470 -9
- package/lib/client.mjs +26 -9
- package/package.json +3 -2
- package/scripts/verify-agent-routing.mjs +260 -2
package/lib/client.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import path from "path";
|
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
14
|
import { execFile as _execFile } from "child_process";
|
|
15
15
|
import { promisify } from "util";
|
|
16
|
-
import { randomUUID } from "crypto";
|
|
16
|
+
import { createHash, randomUUID } from "crypto";
|
|
17
17
|
|
|
18
18
|
const execFile = promisify(_execFile);
|
|
19
19
|
|
|
@@ -217,23 +217,39 @@ function agentClientHeaders(command) {
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
const SECRET_KEY_RE = /(token|secret|password|authorization|cookie|api[_-]?key|keychain|jwt)/i;
|
|
220
|
+
const CONTENT_PAYLOAD_KEY_RE = /(^|_)(html|body|content|markdown|message|text)$/i;
|
|
220
221
|
|
|
221
222
|
function truncateText(text, max = 2000) {
|
|
222
223
|
return text.length > max ? `${text.slice(0, max)}...[truncated]` : text;
|
|
223
224
|
}
|
|
224
225
|
|
|
225
|
-
function
|
|
226
|
+
function summarizePayloadText(value) {
|
|
227
|
+
const text = String(value ?? "");
|
|
228
|
+
return {
|
|
229
|
+
redacted: true,
|
|
230
|
+
type: "text_payload",
|
|
231
|
+
chars: text.length,
|
|
232
|
+
bytes: Buffer.byteLength(text, "utf8"),
|
|
233
|
+
sha256: createHash("sha256").update(text).digest("hex"),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function sanitizeTelemetryValue(value, depth = 0, keyHint = "") {
|
|
226
238
|
if (depth > 4) return "[max-depth]";
|
|
227
239
|
if (value === null || value === undefined) return value;
|
|
228
|
-
if (typeof value === "string")
|
|
240
|
+
if (typeof value === "string") {
|
|
241
|
+
return CONTENT_PAYLOAD_KEY_RE.test(keyHint)
|
|
242
|
+
? summarizePayloadText(value)
|
|
243
|
+
: truncateText(value);
|
|
244
|
+
}
|
|
229
245
|
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
230
246
|
if (Array.isArray(value)) {
|
|
231
|
-
return value.slice(0, 20).map((item) => sanitizeTelemetryValue(item, depth + 1));
|
|
247
|
+
return value.slice(0, 20).map((item) => sanitizeTelemetryValue(item, depth + 1, keyHint));
|
|
232
248
|
}
|
|
233
249
|
if (typeof value === "object") {
|
|
234
250
|
const out = {};
|
|
235
251
|
for (const [key, val] of Object.entries(value).slice(0, 40)) {
|
|
236
|
-
out[key] = SECRET_KEY_RE.test(key) ? "[redacted]" : sanitizeTelemetryValue(val, depth + 1);
|
|
252
|
+
out[key] = SECRET_KEY_RE.test(key) ? "[redacted]" : sanitizeTelemetryValue(val, depth + 1, key);
|
|
237
253
|
}
|
|
238
254
|
return out;
|
|
239
255
|
}
|
|
@@ -494,15 +510,15 @@ function unauthorizedError() {
|
|
|
494
510
|
);
|
|
495
511
|
}
|
|
496
512
|
|
|
497
|
-
export async function request(method, endpoint, body) {
|
|
498
|
-
return requestWithRetry(method, endpoint, body, /* allowRetry */ true);
|
|
513
|
+
export async function request(method, endpoint, body, opts = {}) {
|
|
514
|
+
return requestWithRetry(method, endpoint, body, opts, /* allowRetry */ true);
|
|
499
515
|
}
|
|
500
516
|
|
|
501
517
|
export async function requestSse(method, endpoint, body, opts = {}) {
|
|
502
518
|
return requestSseWithRetry(method, endpoint, body, opts, /* allowRetry */ true);
|
|
503
519
|
}
|
|
504
520
|
|
|
505
|
-
async function requestWithRetry(method, endpoint, body, allowRetry) {
|
|
521
|
+
async function requestWithRetry(method, endpoint, body, opts, allowRetry) {
|
|
506
522
|
const authHeaders = await getAuthHeaders();
|
|
507
523
|
if (Object.keys(authHeaders).length === 0) throw noAuthError();
|
|
508
524
|
const command = inferCommand(method, endpoint);
|
|
@@ -513,6 +529,7 @@ async function requestWithRetry(method, endpoint, body, allowRetry) {
|
|
|
513
529
|
"Content-Type": "application/json",
|
|
514
530
|
...agentClientHeaders(command),
|
|
515
531
|
...authHeaders,
|
|
532
|
+
...(opts.headers || {}),
|
|
516
533
|
},
|
|
517
534
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
518
535
|
});
|
|
@@ -532,7 +549,7 @@ async function requestWithRetry(method, endpoint, body, allowRetry) {
|
|
|
532
549
|
refreshed = null;
|
|
533
550
|
}
|
|
534
551
|
if (refreshed) {
|
|
535
|
-
return requestWithRetry(method, endpoint, body, /* allowRetry */ false);
|
|
552
|
+
return requestWithRetry(method, endpoint, body, opts, /* allowRetry */ false);
|
|
536
553
|
}
|
|
537
554
|
throw unauthorizedError();
|
|
538
555
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llamaventures/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
4
4
|
"description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "npm run test:agent-routing",
|
|
8
|
-
"test:agent-routing": "node scripts/verify-agent-routing.mjs"
|
|
8
|
+
"test:agent-routing": "node scripts/verify-agent-routing.mjs",
|
|
9
|
+
"verify:release": "npm test && npm pack --dry-run"
|
|
9
10
|
},
|
|
10
11
|
"bin": {
|
|
11
12
|
"llama": "bin/llama.mjs",
|
|
@@ -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,9 +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;
|
|
25
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
|
+
}
|
|
26
54
|
|
|
27
55
|
async function readJson(req) {
|
|
28
56
|
let raw = "";
|
|
@@ -72,6 +100,7 @@ const server = createServer(async (req, res) => {
|
|
|
72
100
|
agentClient: req.headers["x-llama-agent-client"] ?? null,
|
|
73
101
|
session: req.headers["x-llama-agent-session"] ?? null,
|
|
74
102
|
command: req.headers["x-llama-command"] ?? null,
|
|
103
|
+
uploadId: req.headers["x-llama-upload-id"] ?? null,
|
|
75
104
|
},
|
|
76
105
|
});
|
|
77
106
|
|
|
@@ -198,6 +227,104 @@ const server = createServer(async (req, res) => {
|
|
|
198
227
|
return;
|
|
199
228
|
}
|
|
200
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
|
+
|
|
201
328
|
if (req.method === "POST" && /^\/api\/deals\/[^/]+\/threads$/.test(url.pathname)) {
|
|
202
329
|
threadSeq += 1;
|
|
203
330
|
writeJson(res, { id: `thread-${threadSeq}` });
|
|
@@ -495,6 +622,68 @@ try {
|
|
|
495
622
|
messageIncludes: ["custom server task"],
|
|
496
623
|
});
|
|
497
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
|
+
|
|
498
687
|
resetCalls();
|
|
499
688
|
const mcpResult = await callMcpTool(
|
|
500
689
|
"deal_enrich",
|
|
@@ -518,6 +707,75 @@ try {
|
|
|
518
707
|
assert.equal(payload.threadId, "thread-1");
|
|
519
708
|
assert.equal(payload.text, "agent done");
|
|
520
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
|
+
|
|
521
779
|
resetCalls();
|
|
522
780
|
const mcpBootstrap = await callMcpTool("agent_bootstrap", { limit: 2 }, baseUrl, homeDir);
|
|
523
781
|
const bootstrapPayload = JSON.parse(mcpBootstrap.content?.[0]?.text ?? "{}");
|