unbrowse 2.1.3 → 2.1.4

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/index.js CHANGED
@@ -6863,7 +6863,7 @@ function classifyRows(rows, intent) {
6863
6863
  return matching.length >= 1 ? { verdict: "pass", reason: "channel_rows" } : { verdict: "fail", reason: "wrong_entity_type" };
6864
6864
  }
6865
6865
  if (/\b(search|find|lookup)\b/.test(lower)) {
6866
- const matching = objects.filter((row) => hasAnyPath(row, ["name", "title", "songName", "resource_name"]) && hasAnyPath(row, ["id", "url", "link", "href", "resource_id"]) && hasAnyPath(row, ["description", "summary", "metadata", "stats", "author", "uploader", "createdAt", "updatedAt"]));
6866
+ const matching = objects.filter((row) => hasAnyPath(row, ["name", "title", "songName", "resource_name"]) && hasAnyPath(row, ["id", "url", "link", "href", "resource_id", "citation", "case_number"]) && hasAnyPath(row, ["description", "summary", "metadata", "stats", "author", "uploader", "createdAt", "updatedAt", "court", "decision_date", "coram", "catchword"]));
6867
6867
  return matching.length >= 1 ? { verdict: "pass", reason: "search_rows" } : { verdict: "fail", reason: "wrong_entity_type" };
6868
6868
  }
6869
6869
  return { verdict: "skip", reason: "unclassified_array" };
@@ -7282,6 +7282,14 @@ function normalizeStructureForIntent(structure, intent) {
7282
7282
  const objectRows = structure.data.filter((row) => !!row && typeof row === "object" && !Array.isArray(row));
7283
7283
  if (objectRows.length === 0)
7284
7284
  return structure;
7285
+ const normalizedLawNet = normalizeLawNetSearchRows(objectRows);
7286
+ if (normalizedLawNet.length >= 1) {
7287
+ return {
7288
+ ...structure,
7289
+ data: normalizedLawNet,
7290
+ element_count: normalizedLawNet.length
7291
+ };
7292
+ }
7285
7293
  const pruned = pruneRowsForIntent(objectRows, intent);
7286
7294
  if (pruned.length >= 1 && pruned.length < objectRows.length) {
7287
7295
  return {
@@ -7292,6 +7300,87 @@ function normalizeStructureForIntent(structure, intent) {
7292
7300
  }
7293
7301
  return structure;
7294
7302
  }
7303
+ function parseLawNetCitation(title) {
7304
+ const match = title.match(/^(.*?)\s*-\s*(\[[^\]]+\].+)$/);
7305
+ if (!match)
7306
+ return {};
7307
+ const case_name = cleanText(match[1] ?? "");
7308
+ const citation = cleanText(match[2] ?? "");
7309
+ return {
7310
+ ...case_name ? { case_name } : {},
7311
+ ...citation ? { citation } : {}
7312
+ };
7313
+ }
7314
+ function parseLawNetLabeledField(text, label) {
7315
+ const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7316
+ const match = text.match(new RegExp(`${escaped}\\s*:\\s*([\\s\\S]*?)(?=\\s+(?:Court|Corams?|Decision Date|Case Number|Catchword)\\s*:|$)`, "i"));
7317
+ const value = cleanText(match?.[1] ?? "");
7318
+ return value || undefined;
7319
+ }
7320
+ function isLikelyLawNetSearchShell(rows) {
7321
+ return rows.some((row) => {
7322
+ if (row.title === "Search Results")
7323
+ return true;
7324
+ const keys = Object.keys(row);
7325
+ if (!keys.some((key) => /^heading_\d+$/.test(key)))
7326
+ return false;
7327
+ return Object.values(row).some((value) => typeof value === "string" && (/Results returned:/i.test(value) || /\bCourt\s*:/.test(value) || /\[\d{4}\]/.test(value)));
7328
+ });
7329
+ }
7330
+ function parseLawNetCaseRow(text) {
7331
+ const cleaned = cleanText(text.replace(/\u00a0/g, " "));
7332
+ if (!cleaned)
7333
+ return null;
7334
+ if (cleaned === "Search Results" || /^Results returned:/i.test(cleaned) || /^(Catchword|Category|Courts|Coram|Jurisdiction|Years|Title \[A to Z\]|Title \[Z to A\]|Date \[latest first\])$/i.test(cleaned) || /^Please enter the no\. of words before and after\./i.test(cleaned)) {
7335
+ return null;
7336
+ }
7337
+ if (!/\[\d{4}\]/.test(cleaned))
7338
+ return null;
7339
+ const marker = cleaned.search(/\s+(?:Court|Corams?|Decision Date|Case Number|Catchword)\s*:/i);
7340
+ const title = cleanText(marker >= 0 ? cleaned.slice(0, marker) : cleaned);
7341
+ if (!title || !/\[\d{4}\]/.test(title) || title.length < 12)
7342
+ return null;
7343
+ const row = {
7344
+ title,
7345
+ ...parseLawNetCitation(title)
7346
+ };
7347
+ const court = parseLawNetLabeledField(cleaned, "Court");
7348
+ const coram = parseLawNetLabeledField(cleaned, "Corams") ?? parseLawNetLabeledField(cleaned, "Coram");
7349
+ const decision_date = parseLawNetLabeledField(cleaned, "Decision Date");
7350
+ const case_number = parseLawNetLabeledField(cleaned, "Case Number");
7351
+ const catchword = parseLawNetLabeledField(cleaned, "Catchword");
7352
+ if (court)
7353
+ row.court = court;
7354
+ if (coram)
7355
+ row.coram = coram;
7356
+ if (decision_date)
7357
+ row.decision_date = decision_date;
7358
+ if (case_number)
7359
+ row.case_number = case_number;
7360
+ if (catchword)
7361
+ row.catchword = catchword;
7362
+ row.raw_text = cleaned;
7363
+ return row;
7364
+ }
7365
+ function normalizeLawNetSearchRows(rows) {
7366
+ if (rows.length === 0 || !isLikelyLawNetSearchShell(rows))
7367
+ return [];
7368
+ const bestByTitle = new Map;
7369
+ for (const row of rows) {
7370
+ for (const value of Object.values(row)) {
7371
+ if (typeof value !== "string")
7372
+ continue;
7373
+ const parsed = parseLawNetCaseRow(value);
7374
+ if (!parsed)
7375
+ continue;
7376
+ const existing = bestByTitle.get(parsed.title);
7377
+ if (!existing || Object.keys(parsed).length > Object.keys(existing).length) {
7378
+ bestByTitle.set(parsed.title, parsed);
7379
+ }
7380
+ }
7381
+ }
7382
+ return [...bestByTitle.values()];
7383
+ }
7295
7384
  function normalizeGitHubPath(href) {
7296
7385
  if (!href)
7297
7386
  return null;
@@ -8090,6 +8179,31 @@ function scoreFieldRichness(structure) {
8090
8179
  return 8;
8091
8180
  return 0;
8092
8181
  }
8182
+ function scoreCaseRowElements(structure) {
8183
+ if (structure.type !== "repeated-elements" || !Array.isArray(structure.data))
8184
+ return 0;
8185
+ const items = structure.data;
8186
+ if (items.length < 2)
8187
+ return 0;
8188
+ const caseLike = items.filter((item) => typeof item.title === "string" && (typeof item.case_name === "string" || typeof item.citation === "string" || typeof item.case_number === "string" || typeof item.court === "string")).length;
8189
+ if (caseLike >= Math.min(3, items.length))
8190
+ return 220;
8191
+ if (caseLike >= 2)
8192
+ return 140;
8193
+ return 0;
8194
+ }
8195
+ function scoreSearchShellNoise(structure) {
8196
+ if (structure.type !== "key-value" || !structure.data || typeof structure.data !== "object")
8197
+ return 0;
8198
+ const record = structure.data;
8199
+ const title = typeof record.title === "string" ? record.title : "";
8200
+ const description = typeof record.description === "string" ? record.description : "";
8201
+ const headingCount = Object.keys(record).filter((key) => /^heading_\d+$/.test(key)).length;
8202
+ if (/^search results$/i.test(title) && /results returned:/i.test(description) && headingCount >= 6) {
8203
+ return -260;
8204
+ }
8205
+ return 0;
8206
+ }
8093
8207
  function buildReplaySelector($el) {
8094
8208
  const tag = $el.get(0)?.tagName;
8095
8209
  if (!tag)
@@ -8173,7 +8287,7 @@ function extractFromDOM(html, intent) {
8173
8287
  const intentWords = intent.toLowerCase().split(/\s+/).filter(Boolean);
8174
8288
  const scored = structures.map((s) => ({
8175
8289
  structure: s,
8176
- score: scoreRelevance(s, intentWords) + scoreSemanticFit(s, intent) + scoreSparseLinkList(s) + scoreFieldRichness(s)
8290
+ score: scoreRelevance(s, intentWords) + scoreSemanticFit(s, intent) + scoreSparseLinkList(s) + scoreFieldRichness(s) + scoreCaseRowElements(s) + scoreSearchShellNoise(s)
8177
8291
  }));
8178
8292
  scored.sort((a, b) => b.score - a.score);
8179
8293
  const passing = scored.filter((candidate) => assessIntentResult(candidate.structure.data, intent).verdict === "pass");
@@ -10645,6 +10759,12 @@ function stampTrace(trace) {
10645
10759
  trace.trace_version = TRACE_VERSION;
10646
10760
  return trace;
10647
10761
  }
10762
+ function canUseTriggerIntercept(endpoint) {
10763
+ return !!endpoint.trigger_url && (endpoint.method === "GET" || endpoint.idempotency === "safe");
10764
+ }
10765
+ function resolveTriggerInterceptTargetUrl(url, structuredReplayUrl, hasStructuredReplay) {
10766
+ return hasStructuredReplay ? structuredReplayUrl : url;
10767
+ }
10648
10768
  function mapHeaders(headers) {
10649
10769
  return Object.entries(headers ?? {}).map(([name, value]) => ({ name, value: String(value) }));
10650
10770
  }
@@ -12717,6 +12837,31 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
12717
12837
  }
12718
12838
  } catch {}
12719
12839
  }
12840
+ const reloadAuthMaterial = async () => {
12841
+ cookies.length = 0;
12842
+ for (const key of Object.keys(authHeaders))
12843
+ delete authHeaders[key];
12844
+ authSourceMeta = null;
12845
+ try {
12846
+ const epBundle = await getStoredAuthBundle(epDomain);
12847
+ if (epBundle) {
12848
+ cookies.push(...epBundle.cookies);
12849
+ Object.assign(authHeaders, epBundle.headers);
12850
+ authSourceMeta = epBundle.source_meta ?? authSourceMeta;
12851
+ }
12852
+ } catch {}
12853
+ if (registrableDomain && registrableDomain !== epDomain) {
12854
+ try {
12855
+ const regBundle = await getStoredAuthBundle(registrableDomain);
12856
+ if (regBundle) {
12857
+ cookies.push(...regBundle.cookies.filter((cookie) => !cookies.some((existing) => existing.name === cookie.name && existing.domain === cookie.domain)));
12858
+ if (Object.keys(authHeaders).length === 0)
12859
+ Object.assign(authHeaders, regBundle.headers);
12860
+ authSourceMeta = regBundle.source_meta ?? authSourceMeta;
12861
+ }
12862
+ } catch {}
12863
+ }
12864
+ };
12720
12865
  log("exec", `endpoint ${endpoint.endpoint_id}: cookies=${cookies.length} authHeaders=${Object.keys(authHeaders).length} hasAuth=${cookies.length > 0 || Object.keys(authHeaders).length > 0}`);
12721
12866
  let mergedParams = mergeContextTemplateParams(params, endpoint.url_template, options?.contextUrl);
12722
12867
  if (endpoint.path_params && typeof endpoint.path_params === "object") {
@@ -12821,6 +12966,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
12821
12966
  }
12822
12967
  const structuredReplayUrl = isSafe ? deriveStructuredDataReplayUrl(url) : url;
12823
12968
  const hasStructuredReplay = structuredReplayUrl !== url;
12969
+ const triggerInterceptTargetUrl = resolveTriggerInterceptTargetUrl(url, structuredReplayUrl, hasStructuredReplay);
12824
12970
  const serverFetch = async () => {
12825
12971
  const defaultAccept = !endpoint.dom_extraction && !endpoint.headers_template?.["accept"] ? { accept: "application/json" } : {};
12826
12972
  const headers = {
@@ -12906,6 +13052,17 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
12906
13052
  }
12907
13053
  return { data: last.data, status: last.status, trace_id: nanoid5(), network_events: networkEvents.length > 0 ? networkEvents : [last.event] };
12908
13054
  };
13055
+ const serverFetchWithAuthRefresh = async () => {
13056
+ let current = await serverFetch();
13057
+ if (current.status !== 401 && current.status !== 403)
13058
+ return current;
13059
+ const refreshedEndpoint = await refreshAuthFromBrowser(epDomain).catch(() => false);
13060
+ const refreshedRegistrable = !refreshedEndpoint && registrableDomain && registrableDomain !== epDomain ? await refreshAuthFromBrowser(registrableDomain).catch(() => false) : false;
13061
+ if (!refreshedEndpoint && !refreshedRegistrable)
13062
+ return current;
13063
+ await reloadAuthMaterial();
13064
+ return serverFetch();
13065
+ };
12909
13066
  const browserCall = () => executeInBrowser(url, endpoint.method, endpoint.headers_template ?? {}, body, authHeaders, cookies);
12910
13067
  let result;
12911
13068
  const hasAuth = cookies.length > 0 || Object.keys(authHeaders).length > 0;
@@ -12922,11 +13079,11 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
12922
13079
  let strategy;
12923
13080
  const endpointStrategy = endpoint.exec_strategy;
12924
13081
  if (hasStructuredReplay) {
12925
- result = await serverFetch();
13082
+ result = await serverFetchWithAuthRefresh();
12926
13083
  if (result.status >= 200 && result.status < 400 && !shouldFallbackToBrowserReplay(result.data, endpoint, options?.intent ?? skill.intent_signature, options?.contextUrl)) {
12927
13084
  strategy = "server";
12928
- } else if (endpoint.trigger_url && isSafe) {
12929
- result = await triggerAndIntercept(endpoint.trigger_url, endpoint.url_template, cookies, authHeaders, {
13085
+ } else if (canUseTriggerIntercept(endpoint)) {
13086
+ result = await triggerAndIntercept(endpoint.trigger_url, triggerInterceptTargetUrl, cookies, authHeaders, {
12930
13087
  authSource: authSourceMeta
12931
13088
  });
12932
13089
  strategy = "trigger-intercept";
@@ -12935,16 +13092,33 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
12935
13092
  strategy = "browser";
12936
13093
  }
12937
13094
  } else if (endpointStrategy === "server") {
12938
- result = await serverFetch();
12939
- if (shouldFallbackToBrowserReplay(result.data, endpoint, options?.intent ?? skill.intent_signature, options?.contextUrl)) {
13095
+ result = await serverFetchWithAuthRefresh();
13096
+ if (result.status >= 200 && result.status < 400) {
13097
+ if (shouldFallbackToBrowserReplay(result.data, endpoint, options?.intent ?? skill.intent_signature, options?.contextUrl)) {
13098
+ if (canUseTriggerIntercept(endpoint)) {
13099
+ result = await triggerAndIntercept(endpoint.trigger_url, triggerInterceptTargetUrl, cookies, authHeaders, {
13100
+ authSource: authSourceMeta
13101
+ });
13102
+ strategy = "trigger-intercept";
13103
+ } else {
13104
+ result = await withRetry(browserCall, (r) => isRetryableStatus(r.status));
13105
+ strategy = "browser";
13106
+ }
13107
+ } else {
13108
+ strategy = "server";
13109
+ }
13110
+ } else if (canUseTriggerIntercept(endpoint)) {
13111
+ result = await triggerAndIntercept(endpoint.trigger_url, triggerInterceptTargetUrl, cookies, authHeaders, {
13112
+ authSource: authSourceMeta
13113
+ });
13114
+ strategy = "trigger-intercept";
13115
+ } else {
12940
13116
  result = await withRetry(browserCall, (r) => isRetryableStatus(r.status));
12941
13117
  strategy = "browser";
12942
- } else {
12943
- strategy = "server";
12944
13118
  }
12945
- } else if (endpointStrategy === "trigger-intercept" && endpoint.trigger_url && isSafe) {
13119
+ } else if (endpointStrategy === "trigger-intercept" && canUseTriggerIntercept(endpoint)) {
12946
13120
  log("exec", `using learned strategy trigger-intercept via ${endpoint.trigger_url}`);
12947
- result = await triggerAndIntercept(endpoint.trigger_url, endpoint.url_template, cookies, authHeaders, {
13121
+ result = await triggerAndIntercept(endpoint.trigger_url, triggerInterceptTargetUrl, cookies, authHeaders, {
12948
13122
  authSource: authSourceMeta
12949
13123
  });
12950
13124
  strategy = "trigger-intercept";
@@ -12965,7 +13139,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
12965
13139
  }
12966
13140
  } else {
12967
13141
  try {
12968
- result = await serverFetch();
13142
+ result = await serverFetchWithAuthRefresh();
12969
13143
  if (result.status >= 200 && result.status < 400) {
12970
13144
  if (shouldFallbackToBrowserReplay(result.data, endpoint, options?.intent ?? skill.intent_signature, options?.contextUrl)) {
12971
13145
  result = await withRetry(browserCall, (r) => isRetryableStatus(r.status));
@@ -12975,8 +13149,8 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
12975
13149
  }
12976
13150
  } else {
12977
13151
  log("exec", `server fetch returned ${result.status}, falling back`);
12978
- if (endpoint.trigger_url && isSafe) {
12979
- result = await triggerAndIntercept(endpoint.trigger_url, endpoint.url_template, cookies, authHeaders, {
13152
+ if (canUseTriggerIntercept(endpoint)) {
13153
+ result = await triggerAndIntercept(endpoint.trigger_url, triggerInterceptTargetUrl, cookies, authHeaders, {
12980
13154
  authSource: authSourceMeta
12981
13155
  });
12982
13156
  strategy = "trigger-intercept";
@@ -13956,7 +14130,7 @@ function queuePassiveSkillPublish(skill, options = {}) {
13956
14130
 
13957
14131
  // ../../src/orchestrator/index.ts
13958
14132
  import { nanoid as nanoid6 } from "nanoid";
13959
- import { existsSync as existsSync8, writeFileSync as writeFileSync5, readFileSync as readFileSync5, mkdirSync as mkdirSync6 } from "node:fs";
14133
+ import { existsSync as existsSync8, writeFileSync as writeFileSync5, readFileSync as readFileSync5, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "node:fs";
13960
14134
  import { dirname as dirname2, join as join8 } from "node:path";
13961
14135
  import { createHash as createHash2 } from "node:crypto";
13962
14136
  var CONFIDENCE_THRESHOLD = 0.3;
@@ -14058,15 +14232,109 @@ function writeSkillSnapshot(cacheKey, skill) {
14058
14232
  return;
14059
14233
  }
14060
14234
  }
14235
+ function hasSearchBindings(endpoint) {
14236
+ const haystack = JSON.stringify({
14237
+ url: endpoint.url_template,
14238
+ query: endpoint.query ?? {},
14239
+ body_params: endpoint.body_params ?? {},
14240
+ body: endpoint.body ?? {},
14241
+ semantic: endpoint.semantic ?? {}
14242
+ }).toLowerCase();
14243
+ return /(basicsearchkey|query|keyword|search|lookup|find|term)/.test(haystack);
14244
+ }
14245
+ function scoreSkillSnapshot(skill) {
14246
+ let score = 0;
14247
+ for (const endpoint of skill.endpoints) {
14248
+ const active = endpoint.verification_status !== "disabled";
14249
+ if (active)
14250
+ score += 20;
14251
+ if (endpoint.dom_extraction || endpoint.response_schema)
14252
+ score += 10;
14253
+ if (hasSearchBindings(endpoint))
14254
+ score += 40;
14255
+ if (endpoint.method === "POST")
14256
+ score += 6;
14257
+ if (/\/result-page\b/i.test(endpoint.url_template))
14258
+ score += 12;
14259
+ if (/captured page artifact/i.test(endpoint.description ?? ""))
14260
+ score -= 18;
14261
+ }
14262
+ return score + skill.endpoints.length;
14263
+ }
14264
+ function pickPreferredSkillSnapshot(primary, candidates) {
14265
+ let best = primary;
14266
+ let bestScore = scoreSkillSnapshot(primary);
14267
+ for (const candidate of candidates) {
14268
+ if (candidate.skill_id !== primary.skill_id)
14269
+ continue;
14270
+ const candidateScore = scoreSkillSnapshot(candidate);
14271
+ if (candidateScore > bestScore) {
14272
+ best = candidate;
14273
+ bestScore = candidateScore;
14274
+ }
14275
+ }
14276
+ return best;
14277
+ }
14061
14278
  function readSkillSnapshot(path4) {
14062
14279
  if (!path4 || !existsSync8(path4))
14063
14280
  return;
14064
14281
  try {
14065
- return JSON.parse(readFileSync5(path4, "utf-8"));
14282
+ const primary = JSON.parse(readFileSync5(path4, "utf-8"));
14283
+ if (!existsSync8(SKILL_SNAPSHOT_DIR))
14284
+ return primary;
14285
+ const siblingSnapshots = [];
14286
+ for (const entry of readdirSync4(SKILL_SNAPSHOT_DIR)) {
14287
+ if (!entry.endsWith(".json"))
14288
+ continue;
14289
+ const candidatePath = join8(SKILL_SNAPSHOT_DIR, entry);
14290
+ if (candidatePath === path4)
14291
+ continue;
14292
+ try {
14293
+ const candidate = JSON.parse(readFileSync5(candidatePath, "utf-8"));
14294
+ if (candidate.skill_id === primary.skill_id)
14295
+ siblingSnapshots.push(candidate);
14296
+ } catch {}
14297
+ }
14298
+ return pickPreferredSkillSnapshot(primary, siblingSnapshots);
14066
14299
  } catch {
14067
14300
  return;
14068
14301
  }
14069
14302
  }
14303
+ function findBestLocalDomainSnapshot(requestedDomain, intent, contextUrl) {
14304
+ if (!existsSync8(SKILL_SNAPSHOT_DIR))
14305
+ return;
14306
+ const targetDomain = getRegistrableDomain(requestedDomain);
14307
+ const bestBySkill = new Map;
14308
+ for (const entry of readdirSync4(SKILL_SNAPSHOT_DIR)) {
14309
+ if (!entry.endsWith(".json"))
14310
+ continue;
14311
+ try {
14312
+ const candidate = JSON.parse(readFileSync5(join8(SKILL_SNAPSHOT_DIR, entry), "utf-8"));
14313
+ if (getRegistrableDomain(candidate.domain) !== targetDomain)
14314
+ continue;
14315
+ const existing = bestBySkill.get(candidate.skill_id);
14316
+ bestBySkill.set(candidate.skill_id, existing ? pickPreferredSkillSnapshot(existing, [candidate]) : candidate);
14317
+ } catch {}
14318
+ }
14319
+ let best;
14320
+ let bestScore = Number.NEGATIVE_INFINITY;
14321
+ for (const candidate of bestBySkill.values()) {
14322
+ if (!hasUsableEndpoints(candidate))
14323
+ continue;
14324
+ if (!isCachedSkillRelevantForIntent(candidate, intent, contextUrl))
14325
+ continue;
14326
+ if (!marketplaceSkillMatchesContext(candidate, intent, contextUrl))
14327
+ continue;
14328
+ const ranked = rankEndpoints(candidate.endpoints, intent, candidate.domain, contextUrl);
14329
+ const topScore = ranked[0]?.score ?? Number.NEGATIVE_INFINITY;
14330
+ const composite = topScore + scoreSkillSnapshot(candidate);
14331
+ if (composite > bestScore) {
14332
+ best = candidate;
14333
+ bestScore = composite;
14334
+ }
14335
+ }
14336
+ return best;
14337
+ }
14070
14338
  function isIpv4Hostname(hostname2) {
14071
14339
  return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(hostname2);
14072
14340
  }
@@ -14272,6 +14540,11 @@ function isCachedSkillRelevantForIntent(skill, intent, contextUrl) {
14272
14540
  if (top && isEducationCatalogIntent(intent) && isRootContextUrl(contextUrl) && /captured page artifact/i.test(top.endpoint.description ?? "") && top.endpoint.url_template === contextUrl) {
14273
14541
  return false;
14274
14542
  }
14543
+ if (isSearchIntent) {
14544
+ const hasStructuredSearchEndpoint = resolvedSkill.endpoints.some((endpoint) => endpointHasSearchBindings(endpoint) && (!!endpoint.dom_extraction || !!endpoint.response_schema) && endpointMatchesContextOrigin(endpoint, contextUrl));
14545
+ if (hasStructuredSearchEndpoint)
14546
+ return true;
14547
+ }
14275
14548
  return (top?.score ?? Number.NEGATIVE_INFINITY) >= 0;
14276
14549
  }
14277
14550
  function assessLocalExecutionResult(endpoint, result, intent, trace) {
@@ -14280,7 +14553,26 @@ function assessLocalExecutionResult(endpoint, result, intent, trace) {
14280
14553
  return semanticAssessment;
14281
14554
  if (endpoint.response_schema?.type !== "array")
14282
14555
  return semanticAssessment;
14283
- if (Array.isArray(result) || result == null || typeof result !== "object")
14556
+ if (Array.isArray(result)) {
14557
+ if (result.length === 0)
14558
+ return { verdict: "fail", reason: "search_empty_results" };
14559
+ const rows = result.filter((row) => !!row && typeof row === "object");
14560
+ if (rows.length === 0)
14561
+ return semanticAssessment;
14562
+ const authBounce = rows.some((row) => {
14563
+ const title2 = String(row.title ?? row.name ?? "").trim().toLowerCase();
14564
+ const description2 = String(row.description ?? row.summary ?? "").trim().toLowerCase();
14565
+ const link2 = String(row.link ?? row.url ?? "").trim().toLowerCase();
14566
+ return /^(about|home|welcome)\b/.test(title2) || /\b(login|log in|sign in|password)\b/.test(`${title2} ${description2}`) || /\/(?:about|home|login)\b/.test(link2);
14567
+ });
14568
+ if (authBounce)
14569
+ return { verdict: "fail", reason: "search_auth_or_homepage_bounce" };
14570
+ const hasStructuredRows = rows.some((row) => typeof row.title === "string" || typeof row.name === "string" || typeof row.case_name === "string" || typeof row.citation === "string");
14571
+ if (hasStructuredRows)
14572
+ return { verdict: "pass", reason: "search_result_rows" };
14573
+ return semanticAssessment;
14574
+ }
14575
+ if (result == null || typeof result !== "object")
14284
14576
  return semanticAssessment;
14285
14577
  const record = result;
14286
14578
  const title = String(record.title ?? "").trim().toLowerCase();
@@ -14516,9 +14808,216 @@ function inferDefaultParam(paramName, intent) {
14516
14808
  }
14517
14809
  return;
14518
14810
  }
14811
+ var SEARCH_INTENT_STOPWORDS = new Set([
14812
+ "a",
14813
+ "an",
14814
+ "and",
14815
+ "are",
14816
+ "at",
14817
+ "be",
14818
+ "boss",
14819
+ "but",
14820
+ "by",
14821
+ "do",
14822
+ "doing",
14823
+ "fact",
14824
+ "for",
14825
+ "from",
14826
+ "get",
14827
+ "going",
14828
+ "had",
14829
+ "has",
14830
+ "have",
14831
+ "i",
14832
+ "if",
14833
+ "im",
14834
+ "in",
14835
+ "into",
14836
+ "is",
14837
+ "it",
14838
+ "its",
14839
+ "just",
14840
+ "let",
14841
+ "like",
14842
+ "me",
14843
+ "my",
14844
+ "now",
14845
+ "of",
14846
+ "on",
14847
+ "or",
14848
+ "our",
14849
+ "s",
14850
+ "says",
14851
+ "search",
14852
+ "should",
14853
+ "show",
14854
+ "so",
14855
+ "take",
14856
+ "taking",
14857
+ "tell",
14858
+ "that",
14859
+ "the",
14860
+ "their",
14861
+ "them",
14862
+ "there",
14863
+ "these",
14864
+ "they",
14865
+ "this",
14866
+ "thoroughly",
14867
+ "to",
14868
+ "up",
14869
+ "us",
14870
+ "was",
14871
+ "we",
14872
+ "were",
14873
+ "what",
14874
+ "where",
14875
+ "which",
14876
+ "who",
14877
+ "why",
14878
+ "with",
14879
+ "would",
14880
+ "you",
14881
+ "your"
14882
+ ]);
14883
+ var SEARCH_DIRECTIVE_PREFIX = /^(search\s+for|search|find\s+me|find|look\s+for|looking\s+for|show\s+me|show|get\s+me|get|browse|discover|shop\s+for|buy)\s+/i;
14884
+ var SEARCH_TRAILING_SITE_HINT = /\s+(on|at|from|in|via)\s+\S+$/i;
14885
+ var SEARCH_INSTRUCTION_NOISE = /\b(do not|don't|dont|tell me|let me know|extremely thoroughly|thoroughly|random cases|for the sake of it|if there is no such|if none exists|if no such)\b/i;
14886
+ function isLikelySearchParam(urlTemplate, param) {
14887
+ const lowerParam = param.toLowerCase();
14888
+ if (/(^q$|^k$|basicsearchkey|basic_search_key|query|keyword|keywords|search|lookup|find|term|phrase|querystr|query_string)/.test(lowerParam)) {
14889
+ return true;
14890
+ }
14891
+ try {
14892
+ const parsed = new URL(urlTemplate.replace(/\{[^}]+\}/g, "x"));
14893
+ for (const [key, value] of parsed.searchParams.entries()) {
14894
+ if (key === param || value === "x") {
14895
+ if (/(^q$|^k$|query|keyword|keywords|search|lookup|find|term|phrase|querystr|query_string)/.test(key.toLowerCase())) {
14896
+ return true;
14897
+ }
14898
+ }
14899
+ }
14900
+ } catch {}
14901
+ return false;
14902
+ }
14903
+ function collectSearchBindingKeys(endpoint) {
14904
+ const keys = new Set;
14905
+ for (const key of Object.keys(endpoint.body_params ?? {})) {
14906
+ if (isLikelySearchParam(endpoint.url_template, key))
14907
+ keys.add(key);
14908
+ }
14909
+ for (const key of Object.keys(endpoint.query ?? {})) {
14910
+ if (isLikelySearchParam(endpoint.url_template, key))
14911
+ keys.add(key);
14912
+ }
14913
+ for (const [rawKey, bindingKey] of Object.entries(extractTemplateQueryBindings(endpoint.url_template))) {
14914
+ if (isLikelySearchParam(endpoint.url_template, rawKey) || isLikelySearchParam(endpoint.url_template, bindingKey)) {
14915
+ keys.add(bindingKey);
14916
+ }
14917
+ }
14918
+ for (const match of endpoint.url_template.matchAll(/\{([^}]+)\}/g)) {
14919
+ const key = match[1];
14920
+ if (isLikelySearchParam(endpoint.url_template, key))
14921
+ keys.add(key);
14922
+ }
14923
+ return [...keys];
14924
+ }
14925
+ function stripSearchIntentBoilerplate(intent) {
14926
+ return intent.trim().replace(SEARCH_DIRECTIVE_PREFIX, "").replace(SEARCH_TRAILING_SITE_HINT, "").replace(/\s+/g, " ").trim();
14927
+ }
14928
+ function extractLiteralSearchTermsFromIntent(intent) {
14929
+ const stripped = stripSearchIntentBoilerplate(intent);
14930
+ if (!stripped)
14931
+ return null;
14932
+ const clauses = stripped.split(/(?<=[.!?])\s+|\n+/).map((clause) => clause.trim()).filter(Boolean);
14933
+ if (clauses.length <= 1)
14934
+ return stripped;
14935
+ const scored = clauses.map((clause, index) => {
14936
+ const tokens = clause.toLowerCase().replace(/[^a-z0-9\-/]+/g, " ").split(/\s+/).filter((token) => token.length >= 3 && !SEARCH_INTENT_STOPWORDS.has(token));
14937
+ let score = Math.min(tokens.length, 12);
14938
+ if (/["“”']/.test(clause))
14939
+ score += 4;
14940
+ if (/[()]/.test(clause))
14941
+ score += 2;
14942
+ if (/\d/.test(clause))
14943
+ score += 2;
14944
+ if (SEARCH_INSTRUCTION_NOISE.test(clause))
14945
+ score -= 8;
14946
+ return { clause, index, score };
14947
+ });
14948
+ const selected = scored.filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.index - b.index).slice(0, 2).sort((a, b) => a.index - b.index).map((entry) => entry.clause.replace(/\s+/g, " ").trim());
14949
+ const joined = (selected.length > 0 ? selected.join(" ") : stripped).trim();
14950
+ return joined || null;
14951
+ }
14952
+ function inferSearchParamOverrides(endpoint, intent, explicitParams = {}) {
14953
+ if (!/\b(search|find|lookup|browse|discover)\b/i.test(intent))
14954
+ return {};
14955
+ const keys = collectSearchBindingKeys(endpoint);
14956
+ if (keys.length === 0)
14957
+ return {};
14958
+ const selectedTerms = selectSearchTermsForExecution(intent);
14959
+ if (!selectedTerms)
14960
+ return {};
14961
+ const overrides = {};
14962
+ for (const key of keys) {
14963
+ if (explicitParams[key] != null && explicitParams[key] !== "")
14964
+ continue;
14965
+ overrides[key] = selectedTerms;
14966
+ }
14967
+ return overrides;
14968
+ }
14969
+ function selectSearchTermsForExecution(intent) {
14970
+ const literal = extractLiteralSearchTermsFromIntent(intent);
14971
+ const condensed = extractSearchTermsFromIntent(intent);
14972
+ if (!literal)
14973
+ return condensed;
14974
+ if (!condensed || condensed === literal)
14975
+ return literal;
14976
+ const wordCount = literal.split(/\s+/).filter(Boolean).length;
14977
+ const hasQuotedPhrase = /["“”]/.test(literal);
14978
+ const hasSentencePunctuation = /[.!?]/.test(literal);
14979
+ const tooLongForSingleField = literal.length > 180 || wordCount > 24;
14980
+ if (hasQuotedPhrase && !tooLongForSingleField)
14981
+ return literal;
14982
+ if (!hasSentencePunctuation && !tooLongForSingleField)
14983
+ return literal;
14984
+ return condensed;
14985
+ }
14986
+ function condenseSearchIntent(intent) {
14987
+ const wantsSearchAction = /\b(search|find|lookup|look\s+for|browse|discover)\b/i.test(intent);
14988
+ const priorityPattern = /\b(high|court|appeal|leave|adduce|evidence|assessment|damages?|tranche|tranches|started|late|stage|hearing|trial|mediation|case|cases)\b/;
14989
+ const tokens = intent.toLowerCase().replace(/[^a-z0-9\][\-/]+/g, " ").split(/\s+/).map((token) => token.trim()).filter((token) => token.length >= 3 && !SEARCH_INTENT_STOPWORDS.has(token));
14990
+ const scored = new Map;
14991
+ tokens.forEach((token, index) => {
14992
+ let score = 0;
14993
+ if (priorityPattern.test(token))
14994
+ score += 10;
14995
+ if (token.length >= 8)
14996
+ score += 2;
14997
+ if (index < 12)
14998
+ score += 1;
14999
+ const existing = scored.get(token);
15000
+ if (!existing || score > existing.score) {
15001
+ scored.set(token, { token, index, score });
15002
+ }
15003
+ });
15004
+ const budget = wantsSearchAction ? 13 : 14;
15005
+ const selected = Array.from(scored.values()).sort((a, b) => b.score - a.score || a.index - b.index).slice(0, budget).sort((a, b) => a.index - b.index).map((entry) => entry.token);
15006
+ if (selected.length === 0)
15007
+ return null;
15008
+ if (wantsSearchAction && selected[0] !== "search") {
15009
+ selected.unshift("search");
15010
+ }
15011
+ return selected.join(" ");
15012
+ }
14519
15013
  function extractSearchTermsFromIntent(intent) {
14520
- let terms = intent.trim().toLowerCase();
14521
- terms = terms.replace(/^(search\s+for|find\s+me|find|look\s+for|looking\s+for|show\s+me|get\s+me|get|browse|shop\s+for|buy)\s+/i, "").replace(/\s+(on|at|from|in|via)\s+\S+$/i, "").trim();
15014
+ let terms = stripSearchIntentBoilerplate(intent).toLowerCase();
15015
+ if (!terms)
15016
+ return null;
15017
+ const words = terms.split(/\s+/).filter(Boolean);
15018
+ if (terms.length > 160 || words.length > 20 || /[.!?]/.test(terms)) {
15019
+ return condenseSearchIntent(terms);
15020
+ }
14522
15021
  if (/\b(from|to|between|before|after|near|in\s+\w+,?\s+\w+|under\s+\$|over\s+\$|cheaper\s+than|more\s+than)\b/i.test(terms)) {
14523
15022
  return null;
14524
15023
  }
@@ -14529,9 +15028,11 @@ async function inferParamsFromIntent(urlTemplate, intent, unboundParams, endpoin
14529
15028
  return {};
14530
15029
  if (unboundParams.length === 1) {
14531
15030
  const param = unboundParams[0];
14532
- const searchTerms = extractSearchTermsFromIntent(intent);
14533
- if (searchTerms) {
14534
- return { [param]: searchTerms };
15031
+ if (isLikelySearchParam(urlTemplate, param, endpointDescription)) {
15032
+ const searchTerms = selectSearchTermsForExecution(intent);
15033
+ if (searchTerms) {
15034
+ return { [param]: searchTerms };
15035
+ }
14535
15036
  }
14536
15037
  }
14537
15038
  const system = `You extract URL query/path parameter values from a user's natural-language intent.
@@ -14954,6 +15455,9 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14954
15455
  search_candidates: [],
14955
15456
  autoexec_attempts: []
14956
15457
  };
15458
+ const queryIntent = extractSearchTermsFromIntent(intent) ?? intent;
15459
+ if (queryIntent !== intent)
15460
+ decisionTrace.query_intent = queryIntent;
14957
15461
  const DEFAULT_CAPTURE_MS = 22000;
14958
15462
  const DEFAULT_CAPTURE_TOKENS = 30000;
14959
15463
  const CHARS_PER_TOKEN = 4;
@@ -15000,7 +15504,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15000
15504
  return timing;
15001
15505
  }
15002
15506
  async function buildDeferralWithAutoExec(skill, source, extraFields) {
15003
- if (intent && intent.trim().length > 0) {
15507
+ if (queryIntent && queryIntent.trim().length > 0) {
15004
15508
  try {
15005
15509
  const autoResult = await tryAutoExecute(skill, source);
15006
15510
  if (autoResult) {
@@ -15021,13 +15525,13 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15021
15525
  };
15022
15526
  }
15023
15527
  function buildDeferral(skill, source, extraFields) {
15024
- const resolvedSkill = withContextReplayEndpoint(skill, intent, context?.url);
15528
+ const resolvedSkill = withContextReplayEndpoint(skill, queryIntent, context?.url);
15025
15529
  const chunk = getSkillChunk(resolvedSkill, {
15026
- intent,
15530
+ intent: queryIntent,
15027
15531
  known_bindings: knownBindingsFromInputs(params, context?.url),
15028
15532
  max_operations: 8
15029
15533
  });
15030
- const epRanked = rankEndpoints(resolvedSkill.endpoints, intent, resolvedSkill.domain, context?.url);
15534
+ const epRanked = rankEndpoints(resolvedSkill.endpoints, queryIntent, resolvedSkill.domain, context?.url);
15031
15535
  const deferTrace = {
15032
15536
  trace_id: nanoid6(),
15033
15537
  skill_id: resolvedSkill.skill_id,
@@ -15093,9 +15597,9 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15093
15597
  function canAutoExecuteEndpoint(endpoint) {
15094
15598
  const endpointParams = resolveEndpointTemplateBindings(endpoint, resolvedParams, context?.url);
15095
15599
  const missing = missingTemplateParams(endpoint, endpointParams);
15096
- const unresolvedBySync = missing.filter((name) => inferDefaultParam(name, intent) === undefined);
15600
+ const unresolvedBySync = missing.filter((name) => inferDefaultParam(name, queryIntent) === undefined);
15097
15601
  if (unresolvedBySync.length > 0) {
15098
- if (!intent || intent.trim().length === 0)
15602
+ if (!queryIntent || queryIntent.trim().length === 0)
15099
15603
  return false;
15100
15604
  if (unresolvedBySync.length > 4)
15101
15605
  return false;
@@ -15118,10 +15622,10 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15118
15622
  return merged;
15119
15623
  })();
15120
15624
  async function tryAutoExecute(skill, source) {
15121
- let epRanked = rankEndpoints(skill.endpoints, intent, skill.domain, context?.url);
15625
+ let epRanked = rankEndpoints(skill.endpoints, queryIntent, skill.domain, context?.url);
15122
15626
  const originalRanked = epRanked;
15123
15627
  const chunk = getSkillChunk(skill, {
15124
- intent,
15628
+ intent: queryIntent,
15125
15629
  known_bindings: knownBindingsFromInputs(resolvedParams, context?.url),
15126
15630
  max_operations: 8
15127
15631
  });
@@ -15144,8 +15648,8 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15144
15648
  url: ranked.endpoint.url_template,
15145
15649
  dom_extraction: !!ranked.endpoint.dom_extraction
15146
15650
  }));
15147
- if (epRanked.length >= 2 && intent) {
15148
- const intentTokens = new Set(intent.toLowerCase().replace(/[^a-z0-9]+/g, " ").split(/\s+/).filter((w) => w.length > 2));
15651
+ if (epRanked.length >= 2 && queryIntent) {
15652
+ const intentTokens = new Set(queryIntent.toLowerCase().replace(/[^a-z0-9]+/g, " ").split(/\s+/).filter((w) => w.length > 2));
15149
15653
  epRanked = epRanked.map((r) => {
15150
15654
  let schemaBonus = 0;
15151
15655
  const schema = r.endpoint.response_schema;
@@ -15186,11 +15690,11 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15186
15690
  if (missing.length === 0)
15187
15691
  readinessBonus += 40;
15188
15692
  else {
15189
- const syncSatisfiable = missing.filter((name) => inferDefaultParam(name, intent) !== undefined);
15693
+ const syncSatisfiable = missing.filter((name) => inferDefaultParam(name, queryIntent) !== undefined);
15190
15694
  const remaining = missing.length - syncSatisfiable.length;
15191
15695
  if (remaining === 0) {
15192
15696
  readinessBonus += 8;
15193
- } else if (intent && remaining <= 4) {
15697
+ } else if (queryIntent && remaining <= 4) {
15194
15698
  readinessBonus += 4 - remaining * 5;
15195
15699
  } else {
15196
15700
  readinessBonus -= missing.length * 25;
@@ -15219,12 +15723,12 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15219
15723
  return { ...r, score: r.score + readinessBonus };
15220
15724
  });
15221
15725
  epRanked.sort((a, b) => b.score - a.score);
15222
- epRanked = prioritizeIntentMatchedApis(epRanked, intent, context?.url);
15726
+ epRanked = prioritizeIntentMatchedApis(epRanked, queryIntent, context?.url);
15223
15727
  const ready = epRanked.filter((r) => canAutoExecuteEndpoint(r.endpoint));
15224
15728
  const tryList = ready.length > 0 ? [...ready, ...epRanked.filter((r) => !canAutoExecuteEndpoint(r.endpoint))] : epRanked;
15225
15729
  const MAX_TRIES = Math.min(tryList.length, 5);
15226
- const deterministicStructuredSearchLeader = /\b(search|find|lookup|browse|discover)\b/i.test(intent) && !!epRanked[0] && endpointHasSearchBindings(epRanked[0].endpoint) && (!!epRanked[0].endpoint.dom_extraction || !!epRanked[0].endpoint.response_schema);
15227
- const agentOrder = !agentChoseEndpoint && tryList.length > 1 && !deterministicStructuredSearchLeader ? await agentSelectEndpoint(intent, skill, tryList.slice(0, MAX_TRIES), context?.url) : null;
15730
+ const deterministicStructuredSearchLeader = /\b(search|find|lookup|browse|discover)\b/i.test(queryIntent) && !!epRanked[0] && endpointHasSearchBindings(epRanked[0].endpoint) && (!!epRanked[0].endpoint.dom_extraction || !!epRanked[0].endpoint.response_schema);
15731
+ const agentOrder = !agentChoseEndpoint && tryList.length > 1 && !deterministicStructuredSearchLeader ? await agentSelectEndpoint(queryIntent, skill, tryList.slice(0, MAX_TRIES), context?.url) : null;
15228
15732
  const orderedTryList = agentOrder ? [
15229
15733
  ...agentOrder.map((endpointId) => tryList.find((r) => r.endpoint.endpoint_id === endpointId)).filter((r) => !!r),
15230
15734
  ...tryList.filter((r) => !agentOrder.includes(r.endpoint.endpoint_id))
@@ -15236,28 +15740,42 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15236
15740
  console.log(`[auto-exec] trying #${i + 1}: ${candidate.endpoint.endpoint_id} score=${candidate.score.toFixed(1)}`);
15237
15741
  try {
15238
15742
  const endpointParams = mergeContextTemplateParams(resolvedParams, candidate.endpoint.url_template, context?.url);
15743
+ const templateDefaults = {
15744
+ ...candidate.endpoint.path_params ?? {},
15745
+ ...candidate.endpoint.body_params ?? {}
15746
+ };
15747
+ const searchOverrides = inferSearchParamOverrides(candidate.endpoint, intent, params);
15239
15748
  const inferredOptionalParams = {};
15240
- const inferredType = inferDefaultParam("type", intent);
15749
+ const inferredType = inferDefaultParam("type", queryIntent);
15241
15750
  if (inferredType !== undefined && endpointParams.type == null && /\/(search|lookup|find)\b/i.test(candidate.endpoint.url_template)) {
15242
15751
  inferredOptionalParams.type = inferredType;
15243
15752
  }
15244
- const syncInferred = Object.fromEntries([...candidate.endpoint.url_template.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]).filter((name) => endpointParams[name] == null || endpointParams[name] === "").map((name) => [name, inferDefaultParam(name, intent)]).filter((entry) => entry[1] !== undefined));
15245
- const allBound = { ...endpointParams, ...syncInferred, ...inferredOptionalParams };
15753
+ const syncInferred = Object.fromEntries([...candidate.endpoint.url_template.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]).filter((name) => endpointParams[name] == null || endpointParams[name] === "").map((name) => [name, inferDefaultParam(name, queryIntent)]).filter((entry) => entry[1] !== undefined));
15754
+ const allBound = {
15755
+ ...templateDefaults,
15756
+ ...endpointParams,
15757
+ ...syncInferred,
15758
+ ...searchOverrides,
15759
+ ...inferredOptionalParams
15760
+ };
15246
15761
  const stillUnbound = [...candidate.endpoint.url_template.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]).filter((name) => allBound[name] == null || allBound[name] === "");
15247
15762
  let llmInferred = {};
15248
- if (stillUnbound.length > 0 && intent) {
15249
- llmInferred = await inferParamsFromIntent(candidate.endpoint.url_template, intent, stillUnbound, candidate.endpoint.description);
15763
+ if (stillUnbound.length > 0 && queryIntent) {
15764
+ llmInferred = await inferParamsFromIntent(candidate.endpoint.url_template, queryIntent, stillUnbound, candidate.endpoint.description);
15250
15765
  }
15251
15766
  const execOut = await executeSkill(skill, {
15767
+ ...templateDefaults,
15252
15768
  ...endpointParams,
15253
15769
  ...syncInferred,
15770
+ ...searchOverrides,
15254
15771
  ...llmInferred,
15255
15772
  ...inferredOptionalParams,
15256
- endpoint_id: candidate.endpoint.endpoint_id
15257
- }, projection, { ...options, intent, contextUrl: context?.url });
15773
+ endpoint_id: candidate.endpoint.endpoint_id,
15774
+ ...queryIntent !== intent ? { intent: queryIntent } : {}
15775
+ }, projection, { ...options, intent: queryIntent, contextUrl: context?.url });
15258
15776
  timing.execute_ms = Date.now() - te02;
15259
15777
  if (execOut.trace.success) {
15260
- const localAssessment = assessLocalExecutionResult(candidate.endpoint, execOut.result, intent, execOut.trace);
15778
+ const localAssessment = assessLocalExecutionResult(candidate.endpoint, execOut.result, queryIntent, execOut.trace);
15261
15779
  if (localAssessment.verdict === "fail") {
15262
15780
  decisionTrace.autoexec_attempts.push({
15263
15781
  endpoint_id: candidate.endpoint.endpoint_id,
@@ -15352,7 +15870,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15352
15870
  if (!forceCapture && !agentChoseEndpoint) {
15353
15871
  const cachedResult = routeResultCache.get(cacheKey);
15354
15872
  if (cachedResult) {
15355
- if (!shouldReuseRouteResultSnapshot(cachedResult, intent, context?.url)) {
15873
+ if (!shouldReuseRouteResultSnapshot(cachedResult, queryIntent, context?.url)) {
15356
15874
  routeResultCache.delete(cacheKey);
15357
15875
  } else {
15358
15876
  timing.cache_hit = true;
@@ -15379,7 +15897,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15379
15897
  continue;
15380
15898
  }
15381
15899
  const skill = readSkillSnapshot(cached.localSkillPath) ?? await getSkillWithTimeout(cached.skillId, clientScope);
15382
- if (!skill || !isCachedSkillRelevantForIntent(skill, intent, context?.url)) {
15900
+ if (!skill || !isCachedSkillRelevantForIntent(skill, queryIntent, context?.url)) {
15383
15901
  skillRouteCache.delete(scopedKey);
15384
15902
  persistRouteCache();
15385
15903
  continue;
@@ -15391,7 +15909,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15391
15909
  skill
15392
15910
  });
15393
15911
  }
15394
- const bestCached = chooseBestRouteCacheCandidate(routeCacheCandidates, intent, context?.url);
15912
+ const bestCached = chooseBestRouteCacheCandidate(routeCacheCandidates, queryIntent, context?.url);
15395
15913
  if (bestCached) {
15396
15914
  if (bestCached.scopedKey !== cacheKey) {
15397
15915
  promoteLearnedSkill(clientScope, resolveCacheKey, bestCached.skill, bestCached.entry.endpointId, context?.url);
@@ -15412,7 +15930,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15412
15930
  const domainCached = domainKey ? domainSkillCache.get(domainKey) : null;
15413
15931
  if (domainCached && Date.now() - domainCached.ts < 7 * 24 * 60 * 60000) {
15414
15932
  const skill = readSkillSnapshot(domainCached.localSkillPath) ?? await getSkill2(domainCached.skillId, clientScope);
15415
- if (skill && isCachedSkillRelevantForIntent(skill, intent, context?.url)) {
15933
+ if (skill && isCachedSkillRelevantForIntent(skill, queryIntent, context?.url)) {
15416
15934
  console.log(`[domain-cache] hit for ${domainKey} → skill ${skill.skill_id.slice(0, 15)}`);
15417
15935
  const result2 = await buildDeferralWithAutoExec(skill, "marketplace");
15418
15936
  if (shouldFallbackToLiveCaptureAfterAutoexecFailure(result2.autoexecFailedAll, context?.url)) {
@@ -15424,9 +15942,22 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15424
15942
  return result2.orchestratorResult;
15425
15943
  }
15426
15944
  } else if (skill) {
15427
- const ranked = rankEndpoints(skill.endpoints, intent, skill.domain, context?.url);
15945
+ const ranked = rankEndpoints(skill.endpoints, queryIntent, skill.domain, context?.url);
15428
15946
  const top = ranked[0];
15429
- console.log(`[domain-cache] skip ${domainKey}: no relevant endpoint for "${intent}"` + (top ? ` (${top.endpoint.endpoint_id} score=${top.score.toFixed(1)})` : ""));
15947
+ console.log(`[domain-cache] skip ${domainKey}: no relevant endpoint for "${queryIntent}"` + (top ? ` (${top.endpoint.endpoint_id} score=${top.score.toFixed(1)})` : ""));
15948
+ }
15949
+ }
15950
+ const localDomainSkill = findBestLocalDomainSnapshot(requestedDomain, queryIntent, context?.url);
15951
+ if (localDomainSkill) {
15952
+ console.log(`[local-snapshot] hit for ${requestedDomain} → skill ${localDomainSkill.skill_id.slice(0, 15)}`);
15953
+ const result2 = await buildDeferralWithAutoExec(localDomainSkill, "marketplace");
15954
+ if (shouldFallbackToLiveCaptureAfterAutoexecFailure(result2.autoexecFailedAll, context?.url)) {
15955
+ console.log(`[local-snapshot] stale skill for ${requestedDomain}; retrying via live capture`);
15956
+ } else {
15957
+ timing.cache_hit = true;
15958
+ result2.orchestratorResult.timing.cache_hit = true;
15959
+ promoteLearnedSkill(clientScope, cacheKey, localDomainSkill, result2.orchestratorResult.trace.endpoint_id, context?.url);
15960
+ return result2.orchestratorResult;
15430
15961
  }
15431
15962
  }
15432
15963
  }
@@ -15451,7 +15982,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15451
15982
  skill
15452
15983
  });
15453
15984
  }
15454
- const cached = chooseBestRouteCacheCandidate(routeCacheCandidates, intent, context?.url);
15985
+ const cached = chooseBestRouteCacheCandidate(routeCacheCandidates, queryIntent, context?.url);
15455
15986
  if (cached) {
15456
15987
  if (cached.scopedKey !== cacheKey) {
15457
15988
  promoteLearnedSkill(clientScope, resolveCacheKey, cached.skill, cached.entry.endpointId, context?.url);
@@ -15460,9 +15991,9 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15460
15991
  if (skill) {
15461
15992
  const te02 = Date.now();
15462
15993
  try {
15463
- const execOut = await executeSkill(skill, { ...params, endpoint_id: params.endpoint_id ?? cached.entry.endpointId }, projection, { ...options, intent, contextUrl: context?.url });
15994
+ const execOut = await executeSkill(skill, { ...params, endpoint_id: params.endpoint_id ?? cached.entry.endpointId, ...queryIntent !== intent ? { intent: queryIntent } : {} }, projection, { ...options, intent: queryIntent, contextUrl: context?.url });
15464
15995
  timing.execute_ms = Date.now() - te02;
15465
- if (execOut.trace.success && isAcceptableIntentResult(execOut.result, intent)) {
15996
+ if (execOut.trace.success && isAcceptableIntentResult(execOut.result, queryIntent)) {
15466
15997
  timing.cache_hit = true;
15467
15998
  promoteResultSnapshot(cacheKey, skill, params.endpoint_id ?? cached.entry.endpointId, execOut.result, execOut.trace, execOut.response_schema, execOut.extraction_hints);
15468
15999
  return {
@@ -15484,7 +16015,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15484
16015
  }
15485
16016
  if (!forceCapture) {
15486
16017
  const ts0 = Date.now();
15487
- const { domain_results: domainResults, global_results: globalResults } = await searchIntentResolve(intent, requestedDomain ?? undefined, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K).catch(() => ({
16018
+ const { domain_results: domainResults, global_results: globalResults } = await searchIntentResolve(queryIntent, requestedDomain ?? undefined, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K).catch(() => ({
15488
16019
  domain_results: [],
15489
16020
  global_results: [],
15490
16021
  skipped_global: false
@@ -15523,9 +16054,9 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15523
16054
  continue;
15524
16055
  if (!hasUsableEndpoints(skill))
15525
16056
  continue;
15526
- if (!isCachedSkillRelevantForIntent(skill, intent, context?.url))
16057
+ if (!isCachedSkillRelevantForIntent(skill, queryIntent, context?.url))
15527
16058
  continue;
15528
- if (!marketplaceSkillMatchesContext(skill, intent, context?.url))
16059
+ if (!marketplaceSkillMatchesContext(skill, queryIntent, context?.url))
15529
16060
  continue;
15530
16061
  if (targetRegDomain && getRegistrableDomain(skill.domain) !== targetRegDomain)
15531
16062
  continue;
@@ -15548,7 +16079,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15548
16079
  const winner = await Promise.any(viable.map((candidate, i) => Promise.race([
15549
16080
  executeSkill(candidate.skill, params, projection, {
15550
16081
  ...options,
15551
- intent,
16082
+ intent: queryIntent,
15552
16083
  contextUrl: context?.url
15553
16084
  }).then((execOut) => {
15554
16085
  if (!execOut.trace.success) {
@@ -15597,12 +16128,12 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15597
16128
  const captureDomain = new URL(context.url).hostname;
15598
16129
  const domainHit = !forceCapture ? capturedDomainCache.get(cacheKey) : undefined;
15599
16130
  if (domainHit && Date.now() < domainHit.expires) {
15600
- if (!isCachedSkillRelevantForIntent(domainHit.skill, intent, context?.url)) {
16131
+ if (!isCachedSkillRelevantForIntent(domainHit.skill, queryIntent, context?.url)) {
15601
16132
  capturedDomainCache.delete(cacheKey);
15602
16133
  } else {
15603
16134
  if (agentChoseEndpoint) {
15604
- const execOut = await executeSkill(domainHit.skill, { ...params, endpoint_id: params.endpoint_id ?? domainHit.endpointId }, projection, { ...options, intent, contextUrl: context?.url });
15605
- if (execOut.trace.success && isAcceptableIntentResult(execOut.result, intent)) {
16135
+ const execOut = await executeSkill(domainHit.skill, { ...params, endpoint_id: params.endpoint_id ?? domainHit.endpointId, ...queryIntent !== intent ? { intent: queryIntent } : {} }, projection, { ...options, intent: queryIntent, contextUrl: context?.url });
16136
+ if (execOut.trace.success && isAcceptableIntentResult(execOut.result, queryIntent)) {
15606
16137
  promoteResultSnapshot(cacheKey, domainHit.skill, params.endpoint_id ?? domainHit.endpointId, execOut.result, execOut.trace, execOut.response_schema, execOut.extraction_hints);
15607
16138
  return {
15608
16139
  result: execOut.result,
@@ -15723,18 +16254,18 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15723
16254
  if (!directDomCaptureResult)
15724
16255
  learned_skill = undefined;
15725
16256
  }
15726
- if (learned_skill && learnedSkillUsable && !isCachedSkillRelevantForIntent(learned_skill, intent, context?.url)) {
15727
- const resolvedSkill = withContextReplayEndpoint(learned_skill, intent, context?.url);
15728
- const ranked = rankEndpoints(resolvedSkill.endpoints, intent, resolvedSkill.domain, context?.url);
16257
+ if (learned_skill && learnedSkillUsable && !isCachedSkillRelevantForIntent(learned_skill, queryIntent, context?.url)) {
16258
+ const resolvedSkill = withContextReplayEndpoint(learned_skill, queryIntent, context?.url);
16259
+ const ranked = rankEndpoints(resolvedSkill.endpoints, queryIntent, resolvedSkill.domain, context?.url);
15729
16260
  const rejectedTrace = {
15730
16261
  ...trace,
15731
16262
  success: false,
15732
- error: `No relevant endpoint discovered for "${intent}"`
16263
+ error: `No relevant endpoint discovered for "${queryIntent}"`
15733
16264
  };
15734
- console.warn(`[capture] dropping learned skill with no relevant endpoints for "${intent}"`);
16265
+ console.warn(`[capture] dropping learned skill with no relevant endpoints for "${queryIntent}"`);
15735
16266
  return {
15736
16267
  result: {
15737
- error: `No relevant endpoint discovered for "${intent}"`,
16268
+ error: `No relevant endpoint discovered for "${queryIntent}"`,
15738
16269
  discovered_endpoints: ranked.slice(0, 3).map((candidate) => ({
15739
16270
  endpoint_id: candidate.endpoint.endpoint_id,
15740
16271
  score: Math.round(candidate.score * 10) / 10,
@@ -15787,7 +16318,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15787
16318
  };
15788
16319
  }
15789
16320
  const hasNonDomApiEndpoints = !!learned_skill?.endpoints?.some((ep) => !ep.dom_extraction && ep.method !== "WS");
15790
- const hasBetterStructuredSearchEndpoint = learned_skill ? skillHasBetterStructuredSearchEndpoint(learned_skill, trace.endpoint_id, intent, context?.url) : false;
16321
+ const hasBetterStructuredSearchEndpoint = learned_skill ? skillHasBetterStructuredSearchEndpoint(learned_skill, trace.endpoint_id, queryIntent, context?.url) : false;
15791
16322
  const isDirectDomResult = directDomCaptureResult;
15792
16323
  const directExtractionSource = isDirectDomResult && result && typeof result === "object" ? result._extraction?.source : undefined;
15793
16324
  if (isDirectDomResult && (directExtractionSource === "html-embedded" && !hasBetterStructuredSearchEndpoint || !hasNonDomApiEndpoints)) {
@@ -15823,13 +16354,13 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15823
16354
  const te1 = Date.now();
15824
16355
  const execOut = await executeSkill(learned_skill, params, projection, {
15825
16356
  ...options,
15826
- intent,
16357
+ intent: queryIntent,
15827
16358
  contextUrl: context?.url
15828
16359
  });
15829
16360
  timing.execute_ms += Date.now() - te1;
15830
16361
  if (execOut.trace.success)
15831
16362
  promoteLearnedSkill(clientScope, cacheKey, learned_skill, execOut.trace.endpoint_id, context?.url);
15832
- if (execOut.trace.success && isAcceptableIntentResult(execOut.result, intent)) {
16363
+ if (execOut.trace.success && isAcceptableIntentResult(execOut.result, queryIntent)) {
15833
16364
  queuePassivePublishIfExecuted(learned_skill, {
15834
16365
  result: execOut.result,
15835
16366
  trace: execOut.trace,
@@ -15840,7 +16371,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15840
16371
  extraction_hints: execOut.extraction_hints
15841
16372
  }, parityBaseline);
15842
16373
  }
15843
- if (execOut.trace.success && isAcceptableIntentResult(execOut.result, intent)) {
16374
+ if (execOut.trace.success && isAcceptableIntentResult(execOut.result, queryIntent)) {
15844
16375
  promoteResultSnapshot(cacheKey, learned_skill, execOut.trace.endpoint_id, execOut.result, execOut.trace, execOut.response_schema, execOut.extraction_hints);
15845
16376
  }
15846
16377
  return {
@@ -16007,7 +16538,7 @@ var ROUTE_LIMITS = {
16007
16538
 
16008
16539
  // ../../src/session-logs.ts
16009
16540
  init_domain();
16010
- import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync } from "node:fs";
16541
+ import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync } from "node:fs";
16011
16542
  import { join as join9 } from "node:path";
16012
16543
  function normalizeDomainVariants(domain) {
16013
16544
  const trimmed = domain.trim().toLowerCase();
@@ -16086,7 +16617,7 @@ function listRecentSessionsForDomain(tracesDir, domain, limit = 10) {
16086
16617
  if (variants.length === 0)
16087
16618
  return [];
16088
16619
  const entries = [];
16089
- for (const name of readdirSync4(tracesDir)) {
16620
+ for (const name of readdirSync5(tracesDir)) {
16090
16621
  if (!name.endsWith(".json"))
16091
16622
  continue;
16092
16623
  const filePath = join9(tracesDir, name);