threadnote 1.5.0 → 1.6.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/config/seed-manifest.example.yaml +9 -0
- package/dist/mcp_server.cjs +180 -8
- package/dist/threadnote.cjs +774 -151
- package/package.json +1 -1
|
@@ -13,3 +13,12 @@ projects:
|
|
|
13
13
|
- .claude/commands/*.md
|
|
14
14
|
- .claude/skills/**/SKILL.md
|
|
15
15
|
- docs/**/*.md
|
|
16
|
+
|
|
17
|
+
# Optional: worksets group related projects into one recall working set.
|
|
18
|
+
# `threadnote recall --workset <name>` (or a query mentioning the name) expands
|
|
19
|
+
# recall across every member's durable + seeded scope. Unknown member names are
|
|
20
|
+
# ignored. Inspect with `threadnote workset list` / `threadnote workset show`.
|
|
21
|
+
# worksets:
|
|
22
|
+
# - name: example-stack
|
|
23
|
+
# description: service plus its client
|
|
24
|
+
# projects: [example-service, example-client]
|
package/dist/mcp_server.cjs
CHANGED
|
@@ -35520,7 +35520,7 @@ function redactText(content) {
|
|
|
35520
35520
|
).replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/sk-[A-Za-z0-9_-]{16,}/g, "sk-[REDACTED]").replace(/gh[pousr]_[A-Za-z0-9_]{16,}/g, "gh_[REDACTED]");
|
|
35521
35521
|
}
|
|
35522
35522
|
function escapeRegExp(value) {
|
|
35523
|
-
return value.replace(/[
|
|
35523
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35524
35524
|
}
|
|
35525
35525
|
async function requiredOpenVikingCli() {
|
|
35526
35526
|
const command2 = await findOpenVikingCli();
|
|
@@ -36368,6 +36368,35 @@ async function inferProjectFromQuery(manifestPath, query) {
|
|
|
36368
36368
|
return void 0;
|
|
36369
36369
|
}
|
|
36370
36370
|
}
|
|
36371
|
+
function resolveWorksetProjects(manifest, workset) {
|
|
36372
|
+
const byName = new Map(manifest.projects.map((project) => [project.name.toLowerCase(), project]));
|
|
36373
|
+
const projects = workset.projects.map((name) => byName.get(name.toLowerCase())).filter((project) => project !== void 0);
|
|
36374
|
+
return { name: workset.name, projects };
|
|
36375
|
+
}
|
|
36376
|
+
async function requireWorkset(manifestPath, worksetName) {
|
|
36377
|
+
const manifest = await readSeedManifest(manifestPath);
|
|
36378
|
+
const workset = manifest.worksets?.find((entry) => entry.name.toLowerCase() === worksetName.toLowerCase());
|
|
36379
|
+
if (!workset) {
|
|
36380
|
+
throw new Error(`No workset named "${worksetName}" in ${manifestPath}.`);
|
|
36381
|
+
}
|
|
36382
|
+
return resolveWorksetProjects(manifest, workset);
|
|
36383
|
+
}
|
|
36384
|
+
async function inferWorksetFromQuery(manifestPath, query) {
|
|
36385
|
+
try {
|
|
36386
|
+
const manifest = await readSeedManifest(manifestPath);
|
|
36387
|
+
if (!manifest.worksets || manifest.worksets.length === 0) {
|
|
36388
|
+
return void 0;
|
|
36389
|
+
}
|
|
36390
|
+
const workset = manifest.worksets.find((entry) => containsNameToken(query, entry.name));
|
|
36391
|
+
return workset ? resolveWorksetProjects(manifest, workset) : void 0;
|
|
36392
|
+
} catch {
|
|
36393
|
+
return void 0;
|
|
36394
|
+
}
|
|
36395
|
+
}
|
|
36396
|
+
function containsNameToken(query, name) {
|
|
36397
|
+
const escaped = escapeRegExp(name.toLowerCase());
|
|
36398
|
+
return new RegExp(`(^|[^a-z0-9])${escaped}($|[^a-z0-9])`).test(query.toLowerCase());
|
|
36399
|
+
}
|
|
36371
36400
|
async function readSeedManifest(path) {
|
|
36372
36401
|
const raw = await (0, import_promises2.readFile)(path, "utf8");
|
|
36373
36402
|
const loaded = index_vite_proxy_tmp_default.load(raw);
|
|
@@ -36399,7 +36428,33 @@ async function readSeedManifest(path) {
|
|
|
36399
36428
|
uri: readString(loaded.future_monorepo, "uri")
|
|
36400
36429
|
};
|
|
36401
36430
|
}
|
|
36402
|
-
|
|
36431
|
+
let worksets;
|
|
36432
|
+
if (loaded.worksets !== void 0) {
|
|
36433
|
+
if (!Array.isArray(loaded.worksets)) {
|
|
36434
|
+
throw new Error(`Manifest worksets must be an array: ${path}`);
|
|
36435
|
+
}
|
|
36436
|
+
worksets = loaded.worksets.map((worksetValue) => {
|
|
36437
|
+
if (!isJsonObject(worksetValue)) {
|
|
36438
|
+
throw new Error(`Manifest workset must be an object: ${path}`);
|
|
36439
|
+
}
|
|
36440
|
+
return {
|
|
36441
|
+
description: readOptionalString(worksetValue, "description"),
|
|
36442
|
+
name: readString(worksetValue, "name"),
|
|
36443
|
+
projects: readStringArray(worksetValue, "projects")
|
|
36444
|
+
};
|
|
36445
|
+
});
|
|
36446
|
+
}
|
|
36447
|
+
return { futureMonorepo, projects, version: version2, worksets };
|
|
36448
|
+
}
|
|
36449
|
+
function readOptionalString(object3, key) {
|
|
36450
|
+
const value = object3[key];
|
|
36451
|
+
if (value === void 0) {
|
|
36452
|
+
return void 0;
|
|
36453
|
+
}
|
|
36454
|
+
if (typeof value !== "string") {
|
|
36455
|
+
throw new Error(`Expected string for ${key}`);
|
|
36456
|
+
}
|
|
36457
|
+
return value;
|
|
36403
36458
|
}
|
|
36404
36459
|
function readString(object3, key) {
|
|
36405
36460
|
const value = object3[key];
|
|
@@ -38588,7 +38643,7 @@ function stripPersonalProvenance(content) {
|
|
|
38588
38643
|
}
|
|
38589
38644
|
const cleaned = [];
|
|
38590
38645
|
for (let index = 0; index < headerEnd; index += 1) {
|
|
38591
|
-
if (/^(?:supersedes|archived_from):\s/.test(lines[index])) {
|
|
38646
|
+
if (/^(?:supersedes|archived_from|references):\s/.test(lines[index])) {
|
|
38592
38647
|
continue;
|
|
38593
38648
|
}
|
|
38594
38649
|
cleaned.push(lines[index]);
|
|
@@ -39002,6 +39057,7 @@ function parseMemoryDocument(uri, content) {
|
|
|
39002
39057
|
archivedFrom: headerValue(header, "archived_from"),
|
|
39003
39058
|
kind,
|
|
39004
39059
|
project: normalizeOptionalMetadata(headerValue(header, "project") ?? headerValue(header, "repo")),
|
|
39060
|
+
references: headerValues(header, "references"),
|
|
39005
39061
|
sourceAgentClient: headerValue(header, "source_agent_client") ?? "unknown",
|
|
39006
39062
|
status,
|
|
39007
39063
|
supersedes: headerValue(header, "supersedes"),
|
|
@@ -39203,6 +39259,24 @@ function recallHygieneNudges(text, options) {
|
|
|
39203
39259
|
}
|
|
39204
39260
|
return [...new Set(nudges)];
|
|
39205
39261
|
}
|
|
39262
|
+
function referencedUrisFromRecords(records, recallOutput) {
|
|
39263
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39264
|
+
const result = [];
|
|
39265
|
+
for (const record2 of records) {
|
|
39266
|
+
for (const uri of record2.metadata.references ?? []) {
|
|
39267
|
+
if (seen.has(uri) || recallOutput.includes(uri)) {
|
|
39268
|
+
continue;
|
|
39269
|
+
}
|
|
39270
|
+
seen.add(uri);
|
|
39271
|
+
result.push(uri);
|
|
39272
|
+
}
|
|
39273
|
+
}
|
|
39274
|
+
return result;
|
|
39275
|
+
}
|
|
39276
|
+
function referencedContextExcerpt(body, maxLines) {
|
|
39277
|
+
const lines = body.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(0, maxLines);
|
|
39278
|
+
return lines.map((line) => ` ${line}`).join("\n");
|
|
39279
|
+
}
|
|
39206
39280
|
function activePersonalMemoryUrisFromText(text, user) {
|
|
39207
39281
|
const userSegment = uriSegment(user);
|
|
39208
39282
|
const matches = text.matchAll(/viking:\/\/[^\s)]+/g);
|
|
@@ -39343,7 +39417,8 @@ function formatMemoryDocument(title, metadata, body) {
|
|
|
39343
39417
|
`source_agent_client: ${metadata.sourceAgentClient}`,
|
|
39344
39418
|
`timestamp: ${metadata.timestamp}`,
|
|
39345
39419
|
metadata.supersedes ? `supersedes: ${metadata.supersedes}` : void 0,
|
|
39346
|
-
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0
|
|
39420
|
+
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0,
|
|
39421
|
+
...(metadata.references ?? []).map((uri) => `references: ${uri}`)
|
|
39347
39422
|
].filter((line) => line !== void 0);
|
|
39348
39423
|
return [...header, "", body.trim()].join("\n");
|
|
39349
39424
|
}
|
|
@@ -39351,6 +39426,11 @@ function headerValue(header, key) {
|
|
|
39351
39426
|
const prefix = `${key}:`;
|
|
39352
39427
|
return header.split("\n").find((line) => line.startsWith(prefix))?.slice(prefix.length).trim();
|
|
39353
39428
|
}
|
|
39429
|
+
function headerValues(header, key) {
|
|
39430
|
+
const prefix = `${key}:`;
|
|
39431
|
+
const values = header.split("\n").filter((line) => line.startsWith(prefix)).map((line) => line.slice(prefix.length).trim()).filter((value) => value.length > 0);
|
|
39432
|
+
return values.length > 0 ? values : void 0;
|
|
39433
|
+
}
|
|
39354
39434
|
function parseOptionalMemoryKind(value) {
|
|
39355
39435
|
if (!value) {
|
|
39356
39436
|
return void 0;
|
|
@@ -40107,10 +40187,13 @@ function registerSearchTool(server, config2, name, description) {
|
|
|
40107
40187
|
includeArchived: external_exports.boolean().optional().describe("Include archived memories in recall results"),
|
|
40108
40188
|
threshold: external_exports.number().min(0).max(1).optional().describe(
|
|
40109
40189
|
"Minimum relevance score 0-1 (default 0.5); lower it (toward 0) to broaden when a recall comes back empty"
|
|
40190
|
+
),
|
|
40191
|
+
workset: external_exports.string().optional().describe(
|
|
40192
|
+
"Optional named workset (a set of related repos from the seed manifest) to recall across as one working set"
|
|
40110
40193
|
)
|
|
40111
40194
|
}
|
|
40112
40195
|
},
|
|
40113
|
-
async ({ callerCwd, includeArchived, nodeLimit, query, threshold, uri }) => {
|
|
40196
|
+
async ({ callerCwd, includeArchived, nodeLimit, query, threshold, uri, workset }) => {
|
|
40114
40197
|
const checkedQuery = requiredText(query, name, "query", { query: "unity-ui-ccc latest handoff" });
|
|
40115
40198
|
if (!checkedQuery.ok) {
|
|
40116
40199
|
return checkedQuery.error;
|
|
@@ -40126,7 +40209,8 @@ function registerSearchTool(server, config2, name, description) {
|
|
|
40126
40209
|
pinnedUri: checkedUri.value,
|
|
40127
40210
|
nodeLimit,
|
|
40128
40211
|
includeArchived: includeArchived === true,
|
|
40129
|
-
threshold: threshold === void 0 ? void 0 : String(threshold)
|
|
40212
|
+
threshold: threshold === void 0 ? void 0 : String(threshold),
|
|
40213
|
+
workset: workset?.trim() || void 0
|
|
40130
40214
|
})
|
|
40131
40215
|
);
|
|
40132
40216
|
}
|
|
@@ -40161,6 +40245,7 @@ async function runRecallTool(config2, params) {
|
|
|
40161
40245
|
const project = params.pinnedUri ? void 0 : await inferProjectFromQuery(config2.manifestPath, projectQuery);
|
|
40162
40246
|
const limitArgs = params.nodeLimit ? ["--node-limit", String(params.nodeLimit)] : [];
|
|
40163
40247
|
const threshold = params.threshold ?? RECALL_SCORE_THRESHOLD;
|
|
40248
|
+
const explicitWorkset = params.workset ? await requireWorkset(config2.manifestPath, params.workset) : void 0;
|
|
40164
40249
|
const pinnedArgs = params.pinnedUri ? ["--uri", params.pinnedUri] : [];
|
|
40165
40250
|
const base = await recallSearchHits(
|
|
40166
40251
|
config2,
|
|
@@ -40180,6 +40265,21 @@ async function runRecallTool(config2, params) {
|
|
|
40180
40265
|
passes.push(seeded.hits);
|
|
40181
40266
|
}
|
|
40182
40267
|
const sections = [];
|
|
40268
|
+
const workset = params.pinnedUri ? void 0 : explicitWorkset ? explicitWorkset : await inferWorksetFromQuery(config2.manifestPath, projectQuery);
|
|
40269
|
+
if (workset && workset.projects.length > 0) {
|
|
40270
|
+
sections.push(`Workset scope: ${workset.name} (${workset.projects.map((member) => member.name).join(", ")})`);
|
|
40271
|
+
const alreadyScoped = new Set([params.pinnedUri, seededUri].filter((uri) => uri !== void 0));
|
|
40272
|
+
const worksetScopes = worksetScopeUris(config2, workset).filter((uri) => !alreadyScoped.has(uri)).slice(0, MAX_WORKSET_PASSES);
|
|
40273
|
+
for (const scope of worksetScopes) {
|
|
40274
|
+
const worksetPass = await recallSearchHits(
|
|
40275
|
+
config2,
|
|
40276
|
+
["search", query, "--uri", scope, ...limitArgs],
|
|
40277
|
+
threshold,
|
|
40278
|
+
params.includeArchived
|
|
40279
|
+
);
|
|
40280
|
+
passes.push(worksetPass.hits);
|
|
40281
|
+
}
|
|
40282
|
+
}
|
|
40183
40283
|
const exactMatches = await collectExactMemoryMatches(config2, query, params.includeArchived, project);
|
|
40184
40284
|
const { semanticSection, exactTail } = buildRecallSections(passes, exactMatches, params.nodeLimit ?? 12);
|
|
40185
40285
|
if (semanticSection) {
|
|
@@ -40193,6 +40293,10 @@ async function runRecallTool(config2, params) {
|
|
|
40193
40293
|
if (exactTail) {
|
|
40194
40294
|
sections.push(exactTail);
|
|
40195
40295
|
}
|
|
40296
|
+
const referencedContext = await referencedContextSection(config2, sections.join("\n\n"));
|
|
40297
|
+
if (referencedContext) {
|
|
40298
|
+
sections.push(referencedContext);
|
|
40299
|
+
}
|
|
40196
40300
|
const hygieneHints = await recallHygieneHintsSection(config2, sections.join("\n\n"));
|
|
40197
40301
|
if (hygieneHints) {
|
|
40198
40302
|
sections.push(hygieneHints);
|
|
@@ -40238,6 +40342,36 @@ async function recallHygieneHintsSection(config2, recallText) {
|
|
|
40238
40342
|
const nudges = recallHygieneNudges(recallText, { records, user: config2.user });
|
|
40239
40343
|
return nudges.length > 0 ? ["Memory hygiene hints:", ...nudges.map((nudge) => `- ${nudge}`)].join("\n") : void 0;
|
|
40240
40344
|
}
|
|
40345
|
+
var MAX_REFERENCED_CONTEXT = 5;
|
|
40346
|
+
var REFERENCED_EXCERPT_LINES = 12;
|
|
40347
|
+
async function referencedContextSection(config2, recallText) {
|
|
40348
|
+
const surfacedUris = activePersonalMemoryUrisFromText(recallText, config2.user);
|
|
40349
|
+
if (surfacedUris.length === 0) {
|
|
40350
|
+
return void 0;
|
|
40351
|
+
}
|
|
40352
|
+
const surfaced = await readMemoryRecordsByUri(config2, surfacedUris);
|
|
40353
|
+
const referenced = referencedUrisFromRecords(surfaced, recallText);
|
|
40354
|
+
if (referenced.length === 0) {
|
|
40355
|
+
return void 0;
|
|
40356
|
+
}
|
|
40357
|
+
const capped = referenced.slice(0, MAX_REFERENCED_CONTEXT);
|
|
40358
|
+
const records = await readMemoryRecordsByUri(config2, capped);
|
|
40359
|
+
const byUri = new Map(records.map((record2) => [record2.uri, record2]));
|
|
40360
|
+
const lines = ["Referenced read-only context (one-way pointers from surfaced memories):"];
|
|
40361
|
+
for (const uri of capped) {
|
|
40362
|
+
const record2 = byUri.get(uri);
|
|
40363
|
+
if (record2) {
|
|
40364
|
+
lines.push(`- ${uri}`, referencedContextExcerpt(record2.body, REFERENCED_EXCERPT_LINES));
|
|
40365
|
+
} else {
|
|
40366
|
+
lines.push(`- ${uri} [reference unavailable locally]`);
|
|
40367
|
+
}
|
|
40368
|
+
}
|
|
40369
|
+
if (referenced.length > capped.length) {
|
|
40370
|
+
const omitted = referenced.length - capped.length;
|
|
40371
|
+
lines.push(`- \u2026 ${omitted} more referenced ${omitted === 1 ? "memory" : "memories"} omitted`);
|
|
40372
|
+
}
|
|
40373
|
+
return lines.join("\n");
|
|
40374
|
+
}
|
|
40241
40375
|
async function collectExactMemoryMatches(config2, query, includeArchived, project) {
|
|
40242
40376
|
const terms = exactRecallTerms(query);
|
|
40243
40377
|
if (terms.length === 0) {
|
|
@@ -40330,6 +40464,9 @@ function registerStoreTool(server, config2, name, description) {
|
|
|
40330
40464
|
inputSchema: {
|
|
40331
40465
|
kind: external_exports.enum(["durable", "handoff", "incident", "preference", "smoke"]).optional().describe("Memory lifecycle kind; durable facts and handoffs are most common"),
|
|
40332
40466
|
project: external_exports.string().optional().describe("Project/repo namespace, for example threadnote or mobile-native"),
|
|
40467
|
+
references: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).optional().describe(
|
|
40468
|
+
"Optional viking:// URI(s) to record as one-way, read-only prior context for this memory. Recall surfaces a short excerpt of each. Stripped from shared copies on publish."
|
|
40469
|
+
),
|
|
40333
40470
|
replaceUri: external_exports.string().optional().describe(
|
|
40334
40471
|
"Optional viking:// memory URI to replace. Shared URIs are updated in place and pushed; personal URIs are forgotten after the replacement is safely stored."
|
|
40335
40472
|
),
|
|
@@ -40339,7 +40476,7 @@ function registerStoreTool(server, config2, name, description) {
|
|
|
40339
40476
|
topic: external_exports.string().optional().describe("Stable topic; active project/topic memories update one file")
|
|
40340
40477
|
}
|
|
40341
40478
|
},
|
|
40342
|
-
async ({ kind, project, replaceUri, sourceAgentClient, status, text, topic }) => {
|
|
40479
|
+
async ({ kind, project, references, replaceUri, sourceAgentClient, status, text, topic }) => {
|
|
40343
40480
|
const checkedText = requiredText(text, name, "text", { text: "Durable engineering note..." });
|
|
40344
40481
|
if (!checkedText.ok) {
|
|
40345
40482
|
return checkedText.error;
|
|
@@ -40348,9 +40485,14 @@ function registerStoreTool(server, config2, name, description) {
|
|
|
40348
40485
|
if (!checkedReplaceUri.ok) {
|
|
40349
40486
|
return checkedReplaceUri.error;
|
|
40350
40487
|
}
|
|
40488
|
+
const checkedReferences = optionalVikingUriList(references, name);
|
|
40489
|
+
if (!checkedReferences.ok) {
|
|
40490
|
+
return checkedReferences.error;
|
|
40491
|
+
}
|
|
40351
40492
|
const metadata = {
|
|
40352
40493
|
kind: kind ?? "durable",
|
|
40353
40494
|
project: normalizeOptionalMetadata2(project),
|
|
40495
|
+
references: checkedReferences.value,
|
|
40354
40496
|
sourceAgentClient: sourceAgentClient ?? "mcp",
|
|
40355
40497
|
status: status ?? "active",
|
|
40356
40498
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -40801,6 +40943,18 @@ function exactMemoryScopes(config2, includeArchived, query, project) {
|
|
|
40801
40943
|
userBase: `viking://user/${uriSegment2(config2.user)}/memories`
|
|
40802
40944
|
});
|
|
40803
40945
|
}
|
|
40946
|
+
var MAX_WORKSET_PASSES = 12;
|
|
40947
|
+
function worksetScopeUris(config2, workset) {
|
|
40948
|
+
const scopes = [];
|
|
40949
|
+
for (const member of workset.projects) {
|
|
40950
|
+
scopes.push(`viking://user/${uriSegment2(config2.user)}/memories/durable/projects/${uriSegment2(member.name)}`);
|
|
40951
|
+
const seeded = trimTrailingSlash(member.uri);
|
|
40952
|
+
if (seeded.startsWith("viking://")) {
|
|
40953
|
+
scopes.push(seeded);
|
|
40954
|
+
}
|
|
40955
|
+
}
|
|
40956
|
+
return [...new Set(scopes)];
|
|
40957
|
+
}
|
|
40804
40958
|
function formatMemoryDocument2(title, metadata, body) {
|
|
40805
40959
|
const header = [
|
|
40806
40960
|
title,
|
|
@@ -40811,7 +40965,8 @@ function formatMemoryDocument2(title, metadata, body) {
|
|
|
40811
40965
|
`source_agent_client: ${metadata.sourceAgentClient}`,
|
|
40812
40966
|
`timestamp: ${metadata.timestamp}`,
|
|
40813
40967
|
metadata.supersedes ? `supersedes: ${metadata.supersedes}` : void 0,
|
|
40814
|
-
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0
|
|
40968
|
+
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0,
|
|
40969
|
+
...(metadata.references ?? []).map((uri) => `references: ${uri}`)
|
|
40815
40970
|
].filter((line) => line !== void 0);
|
|
40816
40971
|
return [...header, "", body.trim()].join("\n");
|
|
40817
40972
|
}
|
|
@@ -40867,6 +41022,23 @@ function optionalVikingUri(value, toolName) {
|
|
|
40867
41022
|
ok: false
|
|
40868
41023
|
};
|
|
40869
41024
|
}
|
|
41025
|
+
function optionalVikingUriList(value, toolName) {
|
|
41026
|
+
const rawValues = Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
41027
|
+
const uris = rawValues.map((uri) => uri.trim()).filter(Boolean);
|
|
41028
|
+
if (uris.length === 0) {
|
|
41029
|
+
return { ok: true, value: void 0 };
|
|
41030
|
+
}
|
|
41031
|
+
const invalid = uris.find((uri) => !uri.startsWith("viking://"));
|
|
41032
|
+
if (invalid) {
|
|
41033
|
+
return {
|
|
41034
|
+
error: argumentError(
|
|
41035
|
+
`Threadnote MCP tool "${toolName}" needs viking:// URI values for "references". Received: ${invalid}`
|
|
41036
|
+
),
|
|
41037
|
+
ok: false
|
|
41038
|
+
};
|
|
41039
|
+
}
|
|
41040
|
+
return { ok: true, value: [...new Set(uris)] };
|
|
41041
|
+
}
|
|
40870
41042
|
function requiredVikingUriList(value, toolName, exampleUri) {
|
|
40871
41043
|
const rawValues = Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
40872
41044
|
const uris = rawValues.map((uri) => uri.trim()).filter(Boolean);
|