unbrowse 3.1.0 → 3.2.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/dist/cli.js +455 -96
- package/dist/index.js +2 -6
- package/dist/mcp.js +695 -46
- package/dist/server.js +25811 -0
- package/package.json +1 -2
- package/vendor/kuri/darwin-arm64/kuri +0 -0
- package/vendor/kuri/darwin-x64/kuri +0 -0
- package/vendor/kuri/linux-arm64/kuri +0 -0
- package/vendor/kuri/linux-x64/kuri +0 -0
- package/vendor/kuri/manifest.json +7 -10
- package/runtime-src/agent-outcome.ts +0 -166
- package/runtime-src/analytics-session.ts +0 -55
- package/runtime-src/api/browse-index.ts +0 -317
- package/runtime-src/api/browse-session.ts +0 -572
- package/runtime-src/api/browse-submit-prereqs.ts +0 -48
- package/runtime-src/api/browse-submit.ts +0 -1184
- package/runtime-src/api/routes.ts +0 -1823
- package/runtime-src/auth/browser-cookies.ts +0 -423
- package/runtime-src/auth/index.ts +0 -535
- package/runtime-src/auth/runtime.ts +0 -116
- package/runtime-src/browser/index.ts +0 -659
- package/runtime-src/browser/types.ts +0 -41
- package/runtime-src/build-info.generated.ts +0 -6
- package/runtime-src/capture/index.ts +0 -1794
- package/runtime-src/capture/prefetch.ts +0 -95
- package/runtime-src/capture/rsc.ts +0 -45
- package/runtime-src/cli/shortcuts.ts +0 -273
- package/runtime-src/cli.ts +0 -1572
- package/runtime-src/client/graph-client.ts +0 -100
- package/runtime-src/client/index.ts +0 -1425
- package/runtime-src/debug-trace.ts +0 -18
- package/runtime-src/domain.ts +0 -38
- package/runtime-src/execution/index.ts +0 -3397
- package/runtime-src/execution/retry.ts +0 -46
- package/runtime-src/execution/robots.ts +0 -167
- package/runtime-src/execution/search-forms.ts +0 -188
- package/runtime-src/extraction/index.ts +0 -1507
- package/runtime-src/foundry/publish-bundle.ts +0 -392
- package/runtime-src/graph/agent-augment.ts +0 -315
- package/runtime-src/graph/index.ts +0 -1524
- package/runtime-src/graph/local-fixtures.ts +0 -393
- package/runtime-src/graph/local-harness.ts +0 -646
- package/runtime-src/graph/planner.ts +0 -411
- package/runtime-src/graph/session.ts +0 -294
- package/runtime-src/graph/trace-store.ts +0 -136
- package/runtime-src/index.ts +0 -24
- package/runtime-src/indexer/index.ts +0 -465
- package/runtime-src/intent-match.ts +0 -1515
- package/runtime-src/kuri/client.ts +0 -1839
- package/runtime-src/logger.ts +0 -30
- package/runtime-src/marketplace/index.ts +0 -103
- package/runtime-src/mcp.ts +0 -1747
- package/runtime-src/orchestrator/browser-agent.ts +0 -374
- package/runtime-src/orchestrator/dag-advisor.ts +0 -59
- package/runtime-src/orchestrator/dag-feedback.ts +0 -257
- package/runtime-src/orchestrator/first-pass-action.ts +0 -403
- package/runtime-src/orchestrator/index.ts +0 -4480
- package/runtime-src/orchestrator/passive-publish.ts +0 -187
- package/runtime-src/orchestrator/timing-economics.ts +0 -80
- package/runtime-src/payments/cascade.ts +0 -137
- package/runtime-src/payments/index.ts +0 -270
- package/runtime-src/payments/lobster-pay.ts +0 -182
- package/runtime-src/payments/wallet.ts +0 -98
- package/runtime-src/publish/review-context.ts +0 -93
- package/runtime-src/publish/sanitize.ts +0 -197
- package/runtime-src/publish/schema-review.ts +0 -192
- package/runtime-src/publish-admission.ts +0 -388
- package/runtime-src/ratelimit/index.ts +0 -23
- package/runtime-src/reverse-engineer/bundle-scanner.ts +0 -127
- package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
- package/runtime-src/reverse-engineer/index.ts +0 -1551
- package/runtime-src/router.ts +0 -17
- package/runtime-src/routing-telemetry.ts +0 -395
- package/runtime-src/runtime/browser-access.ts +0 -11
- package/runtime-src/runtime/browser-auth.ts +0 -12
- package/runtime-src/runtime/browser-host.ts +0 -48
- package/runtime-src/runtime/lifecycle.ts +0 -17
- package/runtime-src/runtime/local-server.ts +0 -311
- package/runtime-src/runtime/paths.ts +0 -99
- package/runtime-src/runtime/setup.ts +0 -251
- package/runtime-src/runtime/supervisor.ts +0 -69
- package/runtime-src/runtime/update-hints.ts +0 -351
- package/runtime-src/server.ts +0 -100
- package/runtime-src/session-logs.ts +0 -142
- package/runtime-src/settings.ts +0 -221
- package/runtime-src/single-binary.ts +0 -143
- package/runtime-src/site-policy.ts +0 -54
- package/runtime-src/stale-cleanup-runner.ts +0 -144
- package/runtime-src/stale-cleanup.ts +0 -133
- package/runtime-src/telemetry-attribution.ts +0 -120
- package/runtime-src/telemetry.ts +0 -253
- package/runtime-src/template-params.ts +0 -141
- package/runtime-src/transform/drift.ts +0 -60
- package/runtime-src/transform/index.ts +0 -277
- package/runtime-src/types/index.ts +0 -1
- package/runtime-src/types/skill.ts +0 -912
- package/runtime-src/vault/index.ts +0 -196
- package/runtime-src/verification/auth-gate.ts +0 -8
- package/runtime-src/verification/candidates.ts +0 -27
- package/runtime-src/verification/index.ts +0 -120
- package/runtime-src/verification/matrix.ts +0 -30
- package/runtime-src/version.ts +0 -148
- package/runtime-src/workflow/artifact.ts +0 -161
- package/runtime-src/workflow/compile.ts +0 -808
- package/runtime-src/workflow/publish.ts +0 -225
- package/runtime-src/workflow/runtime.ts +0 -213
- package/vendor/kuri/win-x64/kuri.exe +0 -0
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
export type FoundryHost = "codex" | "claude" | "openclaw";
|
|
5
|
-
|
|
6
|
-
export type FoundryPreset = {
|
|
7
|
-
bundle_id: string;
|
|
8
|
-
title: string;
|
|
9
|
-
fabric?: {
|
|
10
|
-
repo?: string;
|
|
11
|
-
skill?: string;
|
|
12
|
-
};
|
|
13
|
-
repo?: string;
|
|
14
|
-
bootstrap_skill?: string;
|
|
15
|
-
skills?: string[];
|
|
16
|
-
skill_sources?: Record<string, {
|
|
17
|
-
repo?: string;
|
|
18
|
-
install?: string;
|
|
19
|
-
source_path?: string;
|
|
20
|
-
}>;
|
|
21
|
-
dependency_graph?: {
|
|
22
|
-
nodes?: Array<Record<string, unknown>>;
|
|
23
|
-
};
|
|
24
|
-
routes?: Array<{
|
|
25
|
-
when: string;
|
|
26
|
-
call: string;
|
|
27
|
-
}>;
|
|
28
|
-
tool_routing?: Record<string, unknown>;
|
|
29
|
-
share?: {
|
|
30
|
-
transport?: string;
|
|
31
|
-
manifest_path?: string;
|
|
32
|
-
};
|
|
33
|
-
index?: {
|
|
34
|
-
slug?: string;
|
|
35
|
-
summary?: string;
|
|
36
|
-
tags?: string[];
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export type PublishFoundryBundleInput = {
|
|
41
|
-
preset?: FoundryPreset;
|
|
42
|
-
presetPath?: string;
|
|
43
|
-
repoRoot?: string;
|
|
44
|
-
siteUrl?: string;
|
|
45
|
-
hosts?: FoundryHost[];
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type PublishFoundryBundleResult = {
|
|
49
|
-
ok: true;
|
|
50
|
-
bundle_id: string;
|
|
51
|
-
preset_path?: string;
|
|
52
|
-
bundle_dir: string;
|
|
53
|
-
public_manifest_path: string;
|
|
54
|
-
public_url: string;
|
|
55
|
-
files: {
|
|
56
|
-
bundle: string;
|
|
57
|
-
registry_entry: string;
|
|
58
|
-
share: string;
|
|
59
|
-
candidate_skills: string;
|
|
60
|
-
action_dag: string;
|
|
61
|
-
next_action_dataset: string;
|
|
62
|
-
tool_routing_report: string;
|
|
63
|
-
host_snippets: Record<string, string>;
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const DEFAULT_FOUNDARY_INSTALL = "npx skills add https://github.com/unbrowse-ai/foundry --skill foundry --yes";
|
|
68
|
-
const DEFAULT_SITE_URL = "https://www.unbrowse.ai";
|
|
69
|
-
const DEFAULT_HOSTS: FoundryHost[] = ["codex", "claude", "openclaw"];
|
|
70
|
-
|
|
71
|
-
const HOST_TARGETS: Record<FoundryHost, { target_path: string; snippet_path: string; filename: string }> = {
|
|
72
|
-
codex: {
|
|
73
|
-
target_path: "./AGENTS.md",
|
|
74
|
-
snippet_path: "hosts/codex/AGENTS.md",
|
|
75
|
-
filename: "AGENTS.md",
|
|
76
|
-
},
|
|
77
|
-
claude: {
|
|
78
|
-
target_path: "./CLAUDE.md",
|
|
79
|
-
snippet_path: "hosts/claude/CLAUDE.md",
|
|
80
|
-
filename: "CLAUDE.md",
|
|
81
|
-
},
|
|
82
|
-
openclaw: {
|
|
83
|
-
target_path: "./MEMORY.md",
|
|
84
|
-
snippet_path: "hosts/openclaw/MEMORY.md",
|
|
85
|
-
filename: "MEMORY.md",
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
function ensureObject(value: unknown, message: string): Record<string, unknown> {
|
|
90
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
91
|
-
throw new Error(message);
|
|
92
|
-
}
|
|
93
|
-
return value as Record<string, unknown>;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function ensureBundleId(value: unknown): string {
|
|
97
|
-
const bundleId = typeof value === "string" ? value.trim() : "";
|
|
98
|
-
if (!bundleId) throw new Error("Foundry preset requires bundle_id");
|
|
99
|
-
return bundleId;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function ensureTitle(value: unknown, bundleId: string): string {
|
|
103
|
-
const title = typeof value === "string" ? value.trim() : "";
|
|
104
|
-
if (!title) throw new Error(`Foundry preset ${bundleId} requires title`);
|
|
105
|
-
return title;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function normalizeHosts(hosts?: FoundryHost[]): FoundryHost[] {
|
|
109
|
-
if (!hosts || hosts.length === 0) return [...DEFAULT_HOSTS];
|
|
110
|
-
return hosts.filter((host, index) => DEFAULT_HOSTS.includes(host) && hosts.indexOf(host) === index);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function normalizeManifestPath(bundleId: string, manifestPath?: string): string {
|
|
114
|
-
const normalized = (manifestPath?.trim() || `/.well-known/skill-bundles/${bundleId}/share.json`)
|
|
115
|
-
.replace(/\\/g, "/");
|
|
116
|
-
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function normalizeSiteUrl(siteUrl?: string): string {
|
|
120
|
-
return (siteUrl?.trim() || process.env.UNBROWSE_SITE_URL?.trim() || DEFAULT_SITE_URL).replace(/\/+$/, "");
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function resolveRepoPath(repoRoot: string, target: string): string {
|
|
124
|
-
return isAbsolute(target) ? target : resolve(repoRoot, target);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function readPresetFromPath(presetPath: string): FoundryPreset {
|
|
128
|
-
return JSON.parse(readFileSync(presetPath, "utf-8")) as FoundryPreset;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function loadPreset(input: PublishFoundryBundleInput): { preset: FoundryPreset; presetPath?: string } {
|
|
132
|
-
if (input.preset) return { preset: input.preset };
|
|
133
|
-
if (!input.presetPath) throw new Error("publish-bundle requires preset_path");
|
|
134
|
-
return {
|
|
135
|
-
preset: readPresetFromPath(input.presetPath),
|
|
136
|
-
presetPath: input.presetPath,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function installCommandForSkill(skill: string, source?: FoundryPreset["skill_sources"][string]): string | null {
|
|
141
|
-
if (source?.install?.trim()) return source.install.trim();
|
|
142
|
-
const repo = source?.repo?.trim();
|
|
143
|
-
if (!repo || repo === "local") return null;
|
|
144
|
-
return `npx skills add ${repo} --skill ${skill} --yes`;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function buildInstallCommands(preset: FoundryPreset): { foundry: string; bundle_skills: string[] } {
|
|
148
|
-
const foundry = preset.fabric?.repo?.trim()
|
|
149
|
-
? `npx skills add ${preset.fabric.repo.trim()} --skill ${preset.fabric?.skill?.trim() || "foundry"} --yes`
|
|
150
|
-
: DEFAULT_FOUNDARY_INSTALL;
|
|
151
|
-
|
|
152
|
-
const bundleSkills = (preset.skills ?? [])
|
|
153
|
-
.map((skill) => installCommandForSkill(skill, preset.skill_sources?.[skill]))
|
|
154
|
-
.filter((command): command is string => !!command);
|
|
155
|
-
|
|
156
|
-
return { foundry, bundle_skills: bundleSkills };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function buildHostTargets(hosts: FoundryHost[]): Array<{ host: FoundryHost; target_path: string; snippet_path: string }> {
|
|
160
|
-
return hosts.map((host) => ({
|
|
161
|
-
host,
|
|
162
|
-
target_path: HOST_TARGETS[host].target_path,
|
|
163
|
-
snippet_path: HOST_TARGETS[host].snippet_path,
|
|
164
|
-
}));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function buildBundleArtifact(
|
|
168
|
-
preset: FoundryPreset,
|
|
169
|
-
hosts: FoundryHost[],
|
|
170
|
-
installCommands: { foundry: string; bundle_skills: string[] },
|
|
171
|
-
manifestPath: string,
|
|
172
|
-
) {
|
|
173
|
-
return {
|
|
174
|
-
bundle_id: preset.bundle_id,
|
|
175
|
-
title: preset.title,
|
|
176
|
-
...(preset.fabric ? { fabric: preset.fabric } : {}),
|
|
177
|
-
...(preset.repo ? { repo: preset.repo } : {}),
|
|
178
|
-
...(preset.bootstrap_skill ? { bootstrap_skill: preset.bootstrap_skill } : {}),
|
|
179
|
-
skills: preset.skills ?? [],
|
|
180
|
-
...(preset.skill_sources ? { skill_sources: preset.skill_sources } : {}),
|
|
181
|
-
...(preset.dependency_graph ? { dependency_graph: preset.dependency_graph } : {}),
|
|
182
|
-
...(preset.routes ? { routes: preset.routes } : {}),
|
|
183
|
-
install_commands: installCommands,
|
|
184
|
-
host_targets: buildHostTargets(hosts),
|
|
185
|
-
share: {
|
|
186
|
-
transport: preset.share?.transport ?? "files",
|
|
187
|
-
manifest_path: manifestPath,
|
|
188
|
-
},
|
|
189
|
-
...(preset.index ? { index: preset.index } : {}),
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function buildRegistryEntry(preset: FoundryPreset) {
|
|
194
|
-
return {
|
|
195
|
-
slug: preset.index?.slug ?? preset.bundle_id,
|
|
196
|
-
title: preset.title,
|
|
197
|
-
summary: preset.index?.summary ?? "",
|
|
198
|
-
tags: preset.index?.tags ?? [],
|
|
199
|
-
...(preset.fabric ? { fabric: preset.fabric } : {}),
|
|
200
|
-
...(preset.repo ? { repo: preset.repo } : {}),
|
|
201
|
-
bundle_id: preset.bundle_id,
|
|
202
|
-
...(preset.bootstrap_skill ? { bootstrap_skill: preset.bootstrap_skill } : {}),
|
|
203
|
-
skills: preset.skills ?? [],
|
|
204
|
-
...(preset.skill_sources ? { skill_sources: preset.skill_sources } : {}),
|
|
205
|
-
...(preset.routes ? { routes: preset.routes } : {}),
|
|
206
|
-
...(preset.dependency_graph ? { dependency_graph: preset.dependency_graph } : {}),
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function buildShareArtifact(
|
|
211
|
-
preset: FoundryPreset,
|
|
212
|
-
hosts: FoundryHost[],
|
|
213
|
-
installCommands: { foundry: string; bundle_skills: string[] },
|
|
214
|
-
manifestPath: string,
|
|
215
|
-
) {
|
|
216
|
-
return {
|
|
217
|
-
bundle_id: preset.bundle_id,
|
|
218
|
-
...(preset.fabric ? { fabric: preset.fabric } : {}),
|
|
219
|
-
transport: preset.share?.transport ?? "files",
|
|
220
|
-
manifest_path: manifestPath,
|
|
221
|
-
...(preset.repo ? { repo: preset.repo } : {}),
|
|
222
|
-
skills: preset.skills ?? [],
|
|
223
|
-
...(preset.skill_sources ? { skill_sources: preset.skill_sources } : {}),
|
|
224
|
-
...(preset.dependency_graph ? { dependency_graph: preset.dependency_graph } : {}),
|
|
225
|
-
install_commands: installCommands,
|
|
226
|
-
host_targets: buildHostTargets(hosts),
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function buildCandidateSkillsArtifact(bundleId: string) {
|
|
231
|
-
return {
|
|
232
|
-
bundle_id: bundleId,
|
|
233
|
-
generated_at: new Date().toISOString(),
|
|
234
|
-
threshold: 3,
|
|
235
|
-
candidates: [],
|
|
236
|
-
install_result: null,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function buildActionDagArtifact() {
|
|
241
|
-
return {
|
|
242
|
-
nodes: [],
|
|
243
|
-
edges: [],
|
|
244
|
-
goal_starts: [],
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function buildNextActionDatasetArtifact() {
|
|
249
|
-
return {
|
|
250
|
-
examples: [],
|
|
251
|
-
count: 0,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function buildToolRoutingReportArtifact(bundleId: string) {
|
|
256
|
-
const actionDag = buildActionDagArtifact();
|
|
257
|
-
const nextActionDataset = buildNextActionDatasetArtifact();
|
|
258
|
-
return {
|
|
259
|
-
bundle_id: bundleId,
|
|
260
|
-
session_count: 0,
|
|
261
|
-
node_count: actionDag.nodes.length,
|
|
262
|
-
edge_count: actionDag.edges.length,
|
|
263
|
-
action_dag: actionDag,
|
|
264
|
-
next_action_dataset: nextActionDataset,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function renderHostSnippet(
|
|
269
|
-
preset: FoundryPreset,
|
|
270
|
-
host: FoundryHost,
|
|
271
|
-
installCommands: { foundry: string; bundle_skills: string[] },
|
|
272
|
-
): string {
|
|
273
|
-
const lines: string[] = [
|
|
274
|
-
"<!-- FOUNDRY_BUNDLE:BEGIN -->",
|
|
275
|
-
`## ${preset.title} (${host})`,
|
|
276
|
-
"",
|
|
277
|
-
"Install Foundry:",
|
|
278
|
-
`- \`${installCommands.foundry}\``,
|
|
279
|
-
"",
|
|
280
|
-
"Install bundled skills:",
|
|
281
|
-
];
|
|
282
|
-
|
|
283
|
-
if (installCommands.bundle_skills.length === 0) {
|
|
284
|
-
lines.push("- no installable remote skills declared in this preset");
|
|
285
|
-
} else {
|
|
286
|
-
for (const command of installCommands.bundle_skills) lines.push(`- \`${command}\``);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
lines.push(
|
|
290
|
-
"",
|
|
291
|
-
"Bundle entrypoint:",
|
|
292
|
-
"- If the request is about mining chat history into skills, discovering candidate skills, fabricating a portable bundle, sharing it, indexing it, or writing host routing memory, call `foundry`.",
|
|
293
|
-
"",
|
|
294
|
-
"Skill-call routing defaults:",
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
for (const route of preset.routes ?? []) {
|
|
298
|
-
lines.push(`- If ${route.when}, call \`${route.call}\`.`);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const bootstrapSkill = preset.bootstrap_skill?.trim();
|
|
302
|
-
if (bootstrapSkill) {
|
|
303
|
-
lines.push(
|
|
304
|
-
"",
|
|
305
|
-
`Use \`${bootstrapSkill}\` first when available to confirm/install the right skill, then call \`foundry\` or the routed skill.`,
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
lines.push("<!-- FOUNDRY_BUNDLE:END -->");
|
|
310
|
-
return lines.join("\n");
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function writeJson(target: string, data: unknown): string {
|
|
314
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
315
|
-
writeFileSync(target, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
316
|
-
return target;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function writeText(target: string, content: string): string {
|
|
320
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
321
|
-
writeFileSync(target, content + "\n", "utf-8");
|
|
322
|
-
return target;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function publishFoundryBundle(input: PublishFoundryBundleInput): PublishFoundryBundleResult {
|
|
326
|
-
const repoRoot = input.repoRoot ? resolve(input.repoRoot) : process.cwd();
|
|
327
|
-
const { preset: rawPreset, presetPath: rawPresetPath } = loadPreset(input);
|
|
328
|
-
const preset = ensureObject(rawPreset, "Foundry preset must be an object") as FoundryPreset;
|
|
329
|
-
preset.bundle_id = ensureBundleId(preset.bundle_id);
|
|
330
|
-
preset.title = ensureTitle(preset.title, preset.bundle_id);
|
|
331
|
-
|
|
332
|
-
const hosts = normalizeHosts(input.hosts);
|
|
333
|
-
if (hosts.length === 0) throw new Error("publish-bundle requires at least one valid host");
|
|
334
|
-
|
|
335
|
-
const manifestPath = normalizeManifestPath(preset.bundle_id, preset.share?.manifest_path);
|
|
336
|
-
const siteUrl = normalizeSiteUrl(input.siteUrl);
|
|
337
|
-
const installCommands = buildInstallCommands(preset);
|
|
338
|
-
const bundleDir = join(repoRoot, "skills", ".bundles", preset.bundle_id);
|
|
339
|
-
|
|
340
|
-
const bundleArtifact = buildBundleArtifact(preset, hosts, installCommands, manifestPath);
|
|
341
|
-
const registryEntryArtifact = buildRegistryEntry(preset);
|
|
342
|
-
const shareArtifact = buildShareArtifact(preset, hosts, installCommands, manifestPath);
|
|
343
|
-
const candidateSkillsArtifact = buildCandidateSkillsArtifact(preset.bundle_id);
|
|
344
|
-
const actionDagArtifact = buildActionDagArtifact();
|
|
345
|
-
const nextActionDatasetArtifact = buildNextActionDatasetArtifact();
|
|
346
|
-
const toolRoutingReportArtifact = buildToolRoutingReportArtifact(preset.bundle_id);
|
|
347
|
-
|
|
348
|
-
const presetPath = rawPresetPath ? resolveRepoPath(repoRoot, rawPresetPath) : undefined;
|
|
349
|
-
const publicManifestPath = join(repoRoot, "frontend", "public", manifestPath.replace(/^\/+/, ""));
|
|
350
|
-
|
|
351
|
-
const files = {
|
|
352
|
-
bundle: writeJson(join(bundleDir, "bundle.json"), bundleArtifact),
|
|
353
|
-
registry_entry: writeJson(join(bundleDir, "registry-entry.json"), registryEntryArtifact),
|
|
354
|
-
share: writeJson(join(bundleDir, "share.json"), shareArtifact),
|
|
355
|
-
candidate_skills: writeJson(join(bundleDir, "candidate-skills.json"), candidateSkillsArtifact),
|
|
356
|
-
action_dag: writeJson(join(bundleDir, "action-dag.json"), actionDagArtifact),
|
|
357
|
-
next_action_dataset: writeJson(join(bundleDir, "next-action-dataset.json"), nextActionDatasetArtifact),
|
|
358
|
-
tool_routing_report: writeJson(join(bundleDir, "tool-routing-report.json"), toolRoutingReportArtifact),
|
|
359
|
-
host_snippets: {} as Record<string, string>,
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
for (const host of hosts) {
|
|
363
|
-
const target = join(bundleDir, HOST_TARGETS[host].snippet_path);
|
|
364
|
-
files.host_snippets[host] = writeText(target, renderHostSnippet(preset, host, installCommands));
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
writeJson(publicManifestPath, shareArtifact);
|
|
368
|
-
|
|
369
|
-
return {
|
|
370
|
-
ok: true,
|
|
371
|
-
bundle_id: preset.bundle_id,
|
|
372
|
-
...(presetPath ? { preset_path: presetPath } : {}),
|
|
373
|
-
bundle_dir: bundleDir,
|
|
374
|
-
public_manifest_path: publicManifestPath,
|
|
375
|
-
public_url: `${siteUrl}${manifestPath}`,
|
|
376
|
-
files,
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export function publishFoundryBundleFromPresetPath(
|
|
381
|
-
presetPath: string,
|
|
382
|
-
options?: Omit<PublishFoundryBundleInput, "presetPath">,
|
|
383
|
-
): PublishFoundryBundleResult {
|
|
384
|
-
return publishFoundryBundle({
|
|
385
|
-
...options,
|
|
386
|
-
presetPath,
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
export function foundryBundleExists(repoRoot: string, bundleId: string): boolean {
|
|
391
|
-
return existsSync(join(repoRoot, "skills", ".bundles", bundleId, "bundle.json"));
|
|
392
|
-
}
|
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
import type { EndpointDescriptor, OperationBinding } from "../types/index.js";
|
|
2
|
-
import { resolveEndpointSemantic } from "./index.js";
|
|
3
|
-
|
|
4
|
-
const CHAT_URL = "https://api.tokenfactory.nebius.com/v1/chat/completions";
|
|
5
|
-
const OPENAI_CHAT_URL = "https://api.openai.com/v1/chat/completions";
|
|
6
|
-
const DEFAULT_MODEL = process.env.UNBROWSE_AGENT_SEMANTIC_MODEL ?? process.env.UNBROWSE_AGENT_JUDGE_MODEL ?? "gpt-4.1-mini";
|
|
7
|
-
const ENABLED = process.env.UNBROWSE_AGENT_SEMANTIC_AUGMENT !== "0";
|
|
8
|
-
const AUGMENT_TIMEOUT_MS = Number(process.env.UNBROWSE_AGENT_SEMANTIC_TIMEOUT_MS ?? 8000);
|
|
9
|
-
const MAX_AUGMENT_ENDPOINTS = Math.max(1, Number(process.env.UNBROWSE_AGENT_SEMANTIC_MAX_ENDPOINTS ?? 6));
|
|
10
|
-
const MAX_AUGMENT_PAYLOAD_CHARS = Math.max(4000, Number(process.env.UNBROWSE_AGENT_SEMANTIC_MAX_PAYLOAD_CHARS ?? 24000));
|
|
11
|
-
const NOISE_ENDPOINT_HINTS = /(analytics|telemetry|beacon|metrics|cookie[_-]?sync|lr[_-]?sync|setuid|consent|cms\b|ups\/|guce\.|doubleverify|pubmatic|optable|pixel|experiments?|config|settings|heartbeat|ping\b|track|sync\b|auth\b|login\b)/i;
|
|
12
|
-
const DATA_ENDPOINT_HINTS = /(\/api\/|graphql|\/ws\/|\/v\d+\/|\/quote\b|\/chart\b|\/search\b|\/feed\b|\/results\b|\/items?\b|\/products?\b|\/repos?\b|\/users?\b|\/channels?\b|\/guilds?\b|\/servers?\b)/i;
|
|
13
|
-
const GENERIC_SEMANTIC_TYPES = new Set(["identifier", "input", "resource", "entity", "item"]);
|
|
14
|
-
|
|
15
|
-
type Provider = {
|
|
16
|
-
url: string;
|
|
17
|
-
key: string;
|
|
18
|
-
model: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type LlmBinding = {
|
|
22
|
-
key?: string;
|
|
23
|
-
semantic_type?: string;
|
|
24
|
-
required?: boolean;
|
|
25
|
-
source?: string;
|
|
26
|
-
example_value?: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type LlmEndpointSemantic = {
|
|
30
|
-
endpoint_id?: string;
|
|
31
|
-
action_kind?: string;
|
|
32
|
-
resource_kind?: string;
|
|
33
|
-
description_out?: string;
|
|
34
|
-
requires?: LlmBinding[];
|
|
35
|
-
provides?: LlmBinding[];
|
|
36
|
-
negative_tags?: string[];
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
type LlmResponse = {
|
|
40
|
-
endpoints?: LlmEndpointSemantic[];
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export type AgentSemanticAugmentOptions = {
|
|
44
|
-
intent?: string;
|
|
45
|
-
domain?: string;
|
|
46
|
-
fetchImpl?: typeof fetch;
|
|
47
|
-
provider?: Provider | null;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
function compact(value: unknown, depth = 0): unknown {
|
|
51
|
-
if (depth > 2 || value == null) return value;
|
|
52
|
-
if (Array.isArray(value)) return value.slice(0, 3).map((item) => compact(item, depth + 1));
|
|
53
|
-
if (typeof value === "object") {
|
|
54
|
-
return Object.fromEntries(
|
|
55
|
-
Object.entries(value as Record<string, unknown>)
|
|
56
|
-
.slice(0, 10)
|
|
57
|
-
.map(([key, next]) => [key, compact(next, depth + 1)]),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
if (typeof value === "string" && value.length > 180) return `${value.slice(0, 177)}...`;
|
|
61
|
-
return value;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function tokenizeIntent(intent: string | undefined): string[] {
|
|
65
|
-
return (intent ?? "")
|
|
66
|
-
.toLowerCase()
|
|
67
|
-
.split(/[^a-z0-9]+/)
|
|
68
|
-
.filter((token) => token.length > 2 && !["get", "list", "find", "with", "from", "that", "this"].includes(token));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function describeEndpoint(endpoint: EndpointDescriptor): string {
|
|
72
|
-
const semantic = resolveEndpointSemantic(endpoint);
|
|
73
|
-
return [
|
|
74
|
-
endpoint.method,
|
|
75
|
-
endpoint.url_template,
|
|
76
|
-
endpoint.trigger_url,
|
|
77
|
-
endpoint.description,
|
|
78
|
-
semantic.action_kind,
|
|
79
|
-
semantic.resource_kind,
|
|
80
|
-
semantic.description_out,
|
|
81
|
-
...(semantic.example_fields ?? []),
|
|
82
|
-
]
|
|
83
|
-
.filter((part): part is string => typeof part === "string" && part.length > 0)
|
|
84
|
-
.join(" ")
|
|
85
|
-
.toLowerCase();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function endpointScore(endpoint: EndpointDescriptor, intent: string | undefined, domain: string | undefined): number {
|
|
89
|
-
const semantic = resolveEndpointSemantic(endpoint);
|
|
90
|
-
const haystack = describeEndpoint(endpoint);
|
|
91
|
-
const intentTokens = tokenizeIntent(intent);
|
|
92
|
-
let score = 0;
|
|
93
|
-
|
|
94
|
-
if (endpoint.response_schema) score += 30;
|
|
95
|
-
if (endpoint.dom_extraction) score += 25;
|
|
96
|
-
if (endpoint.trigger_url) score += 10;
|
|
97
|
-
if (DATA_ENDPOINT_HINTS.test(endpoint.url_template)) score += 18;
|
|
98
|
-
if (/GET|WS/.test(endpoint.method)) score += 6;
|
|
99
|
-
if (endpoint.reliability_score != null) score += endpoint.reliability_score * 10;
|
|
100
|
-
|
|
101
|
-
if (domain && haystack.includes(domain.toLowerCase())) score += 8;
|
|
102
|
-
for (const token of intentTokens) {
|
|
103
|
-
if (haystack.includes(token)) score += 6;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const semanticTypes = [
|
|
107
|
-
...(semantic.requires ?? []).map((binding) => binding.semantic_type),
|
|
108
|
-
...(semantic.provides ?? []).map((binding) => binding.semantic_type),
|
|
109
|
-
].filter((value): value is string => typeof value === "string" && value.length > 0);
|
|
110
|
-
if (semanticTypes.some((value) => !GENERIC_SEMANTIC_TYPES.has(value))) score += 8;
|
|
111
|
-
|
|
112
|
-
if (semantic.negative_tags?.length) score -= semantic.negative_tags.length * 6;
|
|
113
|
-
if (NOISE_ENDPOINT_HINTS.test(haystack)) score -= 40;
|
|
114
|
-
if (endpoint.method === "POST" && !endpoint.response_schema) score -= 12;
|
|
115
|
-
|
|
116
|
-
return score;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function buildEndpointPayload(endpoint: EndpointDescriptor) {
|
|
120
|
-
const semantic = resolveEndpointSemantic(endpoint);
|
|
121
|
-
return {
|
|
122
|
-
endpoint_id: endpoint.endpoint_id,
|
|
123
|
-
method: endpoint.method,
|
|
124
|
-
url_template: endpoint.url_template,
|
|
125
|
-
trigger_url: endpoint.trigger_url,
|
|
126
|
-
description: endpoint.description,
|
|
127
|
-
current_semantic: {
|
|
128
|
-
action_kind: semantic.action_kind,
|
|
129
|
-
resource_kind: semantic.resource_kind,
|
|
130
|
-
description_out: semantic.description_out,
|
|
131
|
-
requires: semantic.requires,
|
|
132
|
-
provides: semantic.provides,
|
|
133
|
-
negative_tags: semantic.negative_tags,
|
|
134
|
-
},
|
|
135
|
-
sample_request: compact(semantic.example_request),
|
|
136
|
-
sample_response: compact(semantic.example_response_compact),
|
|
137
|
-
example_fields: semantic.example_fields?.slice(0, 12) ?? [],
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function selectEndpointsForAugment(
|
|
142
|
-
endpoints: EndpointDescriptor[],
|
|
143
|
-
intent: string | undefined,
|
|
144
|
-
domain: string | undefined,
|
|
145
|
-
): EndpointDescriptor[] {
|
|
146
|
-
const ranked = [...endpoints]
|
|
147
|
-
.map((endpoint) => ({ endpoint, score: endpointScore(endpoint, intent, domain) }))
|
|
148
|
-
.sort((a, b) => b.score - a.score);
|
|
149
|
-
|
|
150
|
-
let selected = ranked
|
|
151
|
-
.filter((entry) => entry.score > -20)
|
|
152
|
-
.slice(0, MAX_AUGMENT_ENDPOINTS)
|
|
153
|
-
.map((entry) => entry.endpoint);
|
|
154
|
-
|
|
155
|
-
if (selected.length === 0) {
|
|
156
|
-
selected = ranked.slice(0, Math.min(MAX_AUGMENT_ENDPOINTS, ranked.length)).map((entry) => entry.endpoint);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
while (selected.length > 1) {
|
|
160
|
-
const payload = JSON.stringify({
|
|
161
|
-
intent,
|
|
162
|
-
domain,
|
|
163
|
-
endpoints: selected.map((endpoint) => buildEndpointPayload(endpoint)),
|
|
164
|
-
});
|
|
165
|
-
if (payload.length <= MAX_AUGMENT_PAYLOAD_CHARS) break;
|
|
166
|
-
selected = selected.slice(0, -1);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return selected;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function availableProviders(): Provider[] {
|
|
173
|
-
const providers = [
|
|
174
|
-
process.env.OPENAI_API_KEY ? { url: OPENAI_CHAT_URL, key: process.env.OPENAI_API_KEY, model: DEFAULT_MODEL } : null,
|
|
175
|
-
process.env.NEBIUS_API_KEY ? { url: CHAT_URL, key: process.env.NEBIUS_API_KEY, model: DEFAULT_MODEL } : null,
|
|
176
|
-
].filter((provider): provider is Provider => !!provider);
|
|
177
|
-
return providers;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function validBindingKeys(endpoint: EndpointDescriptor): Set<string> {
|
|
181
|
-
const semantic = resolveEndpointSemantic(endpoint);
|
|
182
|
-
return new Set([
|
|
183
|
-
...(semantic.requires ?? []).map((binding) => binding.key),
|
|
184
|
-
...(semantic.provides ?? []).map((binding) => binding.key),
|
|
185
|
-
]);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function mergeBindings(
|
|
189
|
-
existing: OperationBinding[] | undefined,
|
|
190
|
-
incoming: LlmBinding[] | undefined,
|
|
191
|
-
allowedKeys: Set<string>,
|
|
192
|
-
): OperationBinding[] {
|
|
193
|
-
if (!existing?.length) return existing ?? [];
|
|
194
|
-
if (!incoming?.length) return existing;
|
|
195
|
-
const byKey = new Map(existing.map((binding) => [binding.key, binding]));
|
|
196
|
-
for (const binding of incoming) {
|
|
197
|
-
const key = typeof binding.key === "string" ? binding.key : "";
|
|
198
|
-
if (!key || !allowedKeys.has(key)) continue;
|
|
199
|
-
const current = byKey.get(key);
|
|
200
|
-
if (!current) continue;
|
|
201
|
-
byKey.set(key, {
|
|
202
|
-
...current,
|
|
203
|
-
...(typeof binding.semantic_type === "string" && binding.semantic_type ? { semantic_type: binding.semantic_type } : {}),
|
|
204
|
-
...(typeof binding.required === "boolean" ? { required: binding.required } : {}),
|
|
205
|
-
...(typeof binding.source === "string" && binding.source ? { source: binding.source } : {}),
|
|
206
|
-
...(typeof binding.example_value === "string" && binding.example_value ? { example_value: binding.example_value } : {}),
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
return existing.map((binding) => byKey.get(binding.key) ?? binding);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function sanitizeSemanticUpdate(endpoint: EndpointDescriptor, update: LlmEndpointSemantic): EndpointDescriptor {
|
|
213
|
-
const semantic = resolveEndpointSemantic(endpoint);
|
|
214
|
-
const allowedKeys = validBindingKeys(endpoint);
|
|
215
|
-
const nextSemantic = {
|
|
216
|
-
...semantic,
|
|
217
|
-
...(typeof update.action_kind === "string" && update.action_kind ? { action_kind: update.action_kind } : {}),
|
|
218
|
-
...(typeof update.resource_kind === "string" && update.resource_kind ? { resource_kind: update.resource_kind } : {}),
|
|
219
|
-
...(typeof update.description_out === "string" && update.description_out ? { description_out: update.description_out } : {}),
|
|
220
|
-
...(Array.isArray(update.negative_tags) ? { negative_tags: update.negative_tags.filter((tag): tag is string => typeof tag === "string" && tag.length > 0).slice(0, 8) } : {}),
|
|
221
|
-
requires: mergeBindings(semantic.requires, update.requires, allowedKeys),
|
|
222
|
-
provides: mergeBindings(semantic.provides, update.provides, allowedKeys),
|
|
223
|
-
confidence: Math.max(semantic.confidence ?? 0, 0.9),
|
|
224
|
-
};
|
|
225
|
-
return {
|
|
226
|
-
...endpoint,
|
|
227
|
-
semantic: nextSemantic,
|
|
228
|
-
description: nextSemantic.description_out ?? endpoint.description,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function callAgent(
|
|
233
|
-
provider: Provider,
|
|
234
|
-
endpoints: EndpointDescriptor[],
|
|
235
|
-
intent: string | undefined,
|
|
236
|
-
domain: string | undefined,
|
|
237
|
-
fetchImpl: typeof fetch,
|
|
238
|
-
): Promise<LlmResponse | null> {
|
|
239
|
-
const prompt = [
|
|
240
|
-
"You refine learned API skill metadata for a web automation system.",
|
|
241
|
-
"Return JSON only.",
|
|
242
|
-
"Do not invent endpoints or binding keys.",
|
|
243
|
-
"Only reuse keys already present in each endpoint's current requires/provides.",
|
|
244
|
-
"Upgrade generic labels into better action/resource kinds and semantic binding types when grounded by the URL, trigger URL, sample request, sample response, and sibling endpoint context.",
|
|
245
|
-
"Prefer precise semantic types like repository_owner, repository_name, profile_identifier, query_text, product_identifier, recommendation_placement_id.",
|
|
246
|
-
"Reject generic output like identifier, input, resource unless no better grounded type exists.",
|
|
247
|
-
"For each endpoint, produce endpoint_id plus any improved action_kind, resource_kind, description_out, requires, provides, and negative_tags.",
|
|
248
|
-
].join("\n");
|
|
249
|
-
|
|
250
|
-
const endpointPayload = endpoints.map((endpoint) => buildEndpointPayload(endpoint));
|
|
251
|
-
const controller = new AbortController();
|
|
252
|
-
const timeout = setTimeout(() => controller.abort(), AUGMENT_TIMEOUT_MS);
|
|
253
|
-
let res: Response;
|
|
254
|
-
try {
|
|
255
|
-
res = await fetchImpl(provider.url, {
|
|
256
|
-
method: "POST",
|
|
257
|
-
headers: {
|
|
258
|
-
Authorization: `Bearer ${provider.key}`,
|
|
259
|
-
"Content-Type": "application/json",
|
|
260
|
-
},
|
|
261
|
-
body: JSON.stringify({
|
|
262
|
-
model: provider.model,
|
|
263
|
-
response_format: { type: "json_object" },
|
|
264
|
-
messages: [
|
|
265
|
-
{ role: "system", content: prompt },
|
|
266
|
-
{
|
|
267
|
-
role: "user",
|
|
268
|
-
content: JSON.stringify({
|
|
269
|
-
intent,
|
|
270
|
-
domain,
|
|
271
|
-
endpoints: endpointPayload,
|
|
272
|
-
}),
|
|
273
|
-
},
|
|
274
|
-
],
|
|
275
|
-
}),
|
|
276
|
-
signal: controller.signal,
|
|
277
|
-
});
|
|
278
|
-
} finally {
|
|
279
|
-
clearTimeout(timeout);
|
|
280
|
-
}
|
|
281
|
-
if (!res.ok) throw new Error(`semantic_augment_http_${res.status}`);
|
|
282
|
-
const body = await res.json() as {
|
|
283
|
-
choices?: Array<{ message?: { content?: string } }>;
|
|
284
|
-
};
|
|
285
|
-
const content = body.choices?.[0]?.message?.content;
|
|
286
|
-
if (!content) return null;
|
|
287
|
-
return JSON.parse(content) as LlmResponse;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export async function augmentEndpointsWithAgent(
|
|
291
|
-
endpoints: EndpointDescriptor[],
|
|
292
|
-
opts: AgentSemanticAugmentOptions = {},
|
|
293
|
-
): Promise<EndpointDescriptor[]> {
|
|
294
|
-
if (!ENABLED || endpoints.length === 0) return endpoints;
|
|
295
|
-
const provider = opts.provider === undefined ? availableProviders()[0] ?? null : opts.provider;
|
|
296
|
-
if (!provider) return endpoints;
|
|
297
|
-
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
298
|
-
const selected = selectEndpointsForAugment(endpoints, opts.intent, opts.domain);
|
|
299
|
-
if (selected.length === 0) return endpoints;
|
|
300
|
-
try {
|
|
301
|
-
const response = await callAgent(provider, selected, opts.intent, opts.domain, fetchImpl);
|
|
302
|
-
if (!response?.endpoints?.length) return endpoints;
|
|
303
|
-
const updates = new Map(
|
|
304
|
-
response.endpoints
|
|
305
|
-
.filter((item): item is LlmEndpointSemantic & { endpoint_id: string } => typeof item.endpoint_id === "string" && item.endpoint_id.length > 0)
|
|
306
|
-
.map((item) => [item.endpoint_id, item]),
|
|
307
|
-
);
|
|
308
|
-
return endpoints.map((endpoint) => {
|
|
309
|
-
const update = updates.get(endpoint.endpoint_id);
|
|
310
|
-
return update ? sanitizeSemanticUpdate(endpoint, update) : endpoint;
|
|
311
|
-
});
|
|
312
|
-
} catch {
|
|
313
|
-
return endpoints;
|
|
314
|
-
}
|
|
315
|
-
}
|