unbrowse 2.1.2 → 2.1.3

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 CHANGED
@@ -1302,7 +1302,7 @@ async function handleMessage(msg, unbrowseBin, timeoutMs) {
1302
1302
  loadEnv({ quiet: true });
1303
1303
  loadEnv({ path: ".env.runtime", quiet: true });
1304
1304
  var BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
1305
- var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
1305
+ var CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || "cli-local";
1306
1306
  function parseArgs(argv) {
1307
1307
  const raw = argv.slice(2);
1308
1308
  const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : "help";
package/dist/index.js CHANGED
@@ -14177,6 +14177,25 @@ function promoteResultSnapshot(cacheKey, skill, endpointId, result, trace, respo
14177
14177
  expires: Date.now() + ROUTE_CACHE_TTL
14178
14178
  });
14179
14179
  }
14180
+ function buildCachedResultResponse(cached, source, timing) {
14181
+ const now = new Date().toISOString();
14182
+ return {
14183
+ result: cached.result,
14184
+ trace: {
14185
+ ...cached.trace,
14186
+ trace_id: nanoid6(),
14187
+ started_at: now,
14188
+ completed_at: now,
14189
+ endpoint_id: cached.endpointId ?? cached.trace.endpoint_id,
14190
+ skill_id: cached.skill.skill_id
14191
+ },
14192
+ source,
14193
+ skill: cached.skill,
14194
+ timing,
14195
+ response_schema: cached.response_schema,
14196
+ extraction_hints: cached.extraction_hints
14197
+ };
14198
+ }
14180
14199
  function invalidateResolveCacheEntries(cacheKeys, domainKeys = []) {
14181
14200
  let routeCacheDirty = false;
14182
14201
  let domainCacheDirty = false;
@@ -14401,6 +14420,11 @@ async function withDomainCaptureLock(domain, fn) {
14401
14420
  function shouldFallbackToLiveCaptureAfterAutoexecFailure(autoexecFailedAll, contextUrl) {
14402
14421
  return autoexecFailedAll && !!contextUrl;
14403
14422
  }
14423
+ function shouldReuseRouteResultSnapshot(cached, intent, contextUrl, now = Date.now()) {
14424
+ if (cached.expires <= now)
14425
+ return false;
14426
+ return isCachedSkillRelevantForIntent(cached.skill, intent, contextUrl);
14427
+ }
14404
14428
  function computeCompositeScore(embeddingScore, skill) {
14405
14429
  const reliabilities = skill.endpoints.map((e) => e.reliability_score);
14406
14430
  const avgReliability = reliabilities.length > 0 ? reliabilities.reduce((a, b) => a + b, 0) / reliabilities.length : 0.5;
@@ -15328,18 +15352,18 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15328
15352
  if (!forceCapture && !agentChoseEndpoint) {
15329
15353
  const cachedResult = routeResultCache.get(cacheKey);
15330
15354
  if (cachedResult) {
15331
- if (cachedResult.expires <= Date.now() || !isAcceptableIntentResult(cachedResult.result, intent) || !isCachedSkillRelevantForIntent(cachedResult.skill, intent, context?.url)) {
15355
+ if (!shouldReuseRouteResultSnapshot(cachedResult, intent, context?.url)) {
15332
15356
  routeResultCache.delete(cacheKey);
15333
15357
  } else {
15334
- const deferred2 = await buildDeferralWithAutoExec(cachedResult.skill, "marketplace");
15335
- if (shouldFallbackToLiveCaptureAfterAutoexecFailure(deferred2.autoexecFailedAll, context?.url)) {
15336
- console.log("[route-result-cache] stale cached skill; retrying via live capture");
15337
- invalidateResolveCacheEntries([cacheKey], requestedDomainCacheKey ? [requestedDomainCacheKey] : []);
15338
- } else {
15339
- timing.cache_hit = true;
15340
- deferred2.orchestratorResult.timing.cache_hit = true;
15341
- return deferred2.orchestratorResult;
15342
- }
15358
+ timing.cache_hit = true;
15359
+ writeDebugTrace("resolve", {
15360
+ ...decisionTrace,
15361
+ outcome: "route_result_cache_hit",
15362
+ source: "route-cache",
15363
+ skill_id: cachedResult.skill.skill_id,
15364
+ selected_endpoint_id: cachedResult.endpointId ?? cachedResult.trace.endpoint_id
15365
+ });
15366
+ return buildCachedResultResponse(cachedResult, "marketplace", finalize2("route-cache", cachedResult.result, cachedResult.skill.skill_id, cachedResult.skill, cachedResult.trace));
15343
15367
  }
15344
15368
  }
15345
15369
  }
@@ -16202,7 +16226,7 @@ async function fetchStats() {
16202
16226
  return data;
16203
16227
  }
16204
16228
  async function registerRoutes(app) {
16205
- const clientScopeFor = (req) => typeof req.headers["x-unbrowse-client-id"] === "string" && req.headers["x-unbrowse-client-id"].trim() ? req.headers["x-unbrowse-client-id"].trim() : req.id;
16229
+ const clientScopeFor = (req) => typeof req.headers["x-unbrowse-client-id"] === "string" && req.headers["x-unbrowse-client-id"].trim() ? req.headers["x-unbrowse-client-id"].trim() : "global";
16206
16230
  app.addHook("onRequest", async (req, reply) => {
16207
16231
  if (req.url === "/health" || req.url === "/v1/stats")
16208
16232
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unbrowse",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "Reverse-engineer any website into reusable API skills. npm CLI + local engine.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -141,7 +141,7 @@ export async function registerRoutes(app: FastifyInstance) {
141
141
  const clientScopeFor = (req: { headers: Record<string, unknown>; id: string }) =>
142
142
  (typeof req.headers["x-unbrowse-client-id"] === "string" && req.headers["x-unbrowse-client-id"].trim())
143
143
  ? req.headers["x-unbrowse-client-id"].trim()
144
- : req.id;
144
+ : "global";
145
145
 
146
146
  // Auth gate: block all routes except /health when no API key is configured
147
147
  app.addHook("onRequest", async (req, reply) => {
@@ -18,7 +18,7 @@ loadEnv({ quiet: true });
18
18
  loadEnv({ path: ".env.runtime", quiet: true });
19
19
 
20
20
  const BASE_URL = process.env.UNBROWSE_URL || "http://localhost:6969";
21
- const CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || `cli-${process.ppid || process.pid}`;
21
+ const CLI_CLIENT_ID = process.env.UNBROWSE_CLIENT_ID || "cli-local";
22
22
 
23
23
  // ---------------------------------------------------------------------------
24
24
  // Arg parser
@@ -329,6 +329,37 @@ function promoteResultSnapshot(
329
329
  });
330
330
  }
331
331
 
332
+ function buildCachedResultResponse(
333
+ cached: {
334
+ skill: SkillManifest;
335
+ endpointId?: string;
336
+ result: unknown;
337
+ trace: ExecutionTrace;
338
+ response_schema?: ResponseSchema;
339
+ extraction_hints?: OrchestratorResult["extraction_hints"];
340
+ },
341
+ source: "marketplace" | "live-capture",
342
+ timing: OrchestrationTiming,
343
+ ): OrchestratorResult {
344
+ const now = new Date().toISOString();
345
+ return {
346
+ result: cached.result,
347
+ trace: {
348
+ ...cached.trace,
349
+ trace_id: nanoid(),
350
+ started_at: now,
351
+ completed_at: now,
352
+ endpoint_id: cached.endpointId ?? cached.trace.endpoint_id,
353
+ skill_id: cached.skill.skill_id,
354
+ },
355
+ source,
356
+ skill: cached.skill,
357
+ timing,
358
+ response_schema: cached.response_schema,
359
+ extraction_hints: cached.extraction_hints,
360
+ };
361
+ }
362
+
332
363
  function invalidateResolveCacheEntries(cacheKeys: string[], domainKeys: string[] = []): void {
333
364
  let routeCacheDirty = false;
334
365
  let domainCacheDirty = false;
@@ -670,6 +701,19 @@ export function shouldFallbackToLiveCaptureAfterAutoexecFailure(
670
701
  return autoexecFailedAll && !!contextUrl;
671
702
  }
672
703
 
704
+ export function shouldReuseRouteResultSnapshot(
705
+ cached: {
706
+ expires: number;
707
+ skill: SkillManifest;
708
+ },
709
+ intent: string,
710
+ contextUrl?: string,
711
+ now = Date.now(),
712
+ ): boolean {
713
+ if (cached.expires <= now) return false;
714
+ return isCachedSkillRelevantForIntent(cached.skill, intent, contextUrl);
715
+ }
716
+
673
717
  function computeCompositeScore(embeddingScore: number, skill: SkillManifest): number {
674
718
  // Average reliability across endpoints
675
719
  const reliabilities = skill.endpoints.map((e) => e.reliability_score);
@@ -1940,22 +1984,28 @@ export async function resolveAndExecute(
1940
1984
  if (!forceCapture && !agentChoseEndpoint) {
1941
1985
  const cachedResult = routeResultCache.get(cacheKey);
1942
1986
  if (cachedResult) {
1943
- if (
1944
- cachedResult.expires <= Date.now() ||
1945
- !isAcceptableIntentResult(cachedResult.result, intent) ||
1946
- !isCachedSkillRelevantForIntent(cachedResult.skill, intent, context?.url)
1947
- ) {
1987
+ if (!shouldReuseRouteResultSnapshot(cachedResult, intent, context?.url)) {
1948
1988
  routeResultCache.delete(cacheKey);
1949
1989
  } else {
1950
- const deferred = await buildDeferralWithAutoExec(cachedResult.skill, "marketplace");
1951
- if (shouldFallbackToLiveCaptureAfterAutoexecFailure(deferred.autoexecFailedAll, context?.url)) {
1952
- console.log("[route-result-cache] stale cached skill; retrying via live capture");
1953
- invalidateResolveCacheEntries([cacheKey], requestedDomainCacheKey ? [requestedDomainCacheKey] : []);
1954
- } else {
1955
- timing.cache_hit = true;
1956
- deferred.orchestratorResult.timing.cache_hit = true;
1957
- return deferred.orchestratorResult;
1958
- }
1990
+ timing.cache_hit = true;
1991
+ writeDebugTrace("resolve", {
1992
+ ...decisionTrace,
1993
+ outcome: "route_result_cache_hit",
1994
+ source: "route-cache",
1995
+ skill_id: cachedResult.skill.skill_id,
1996
+ selected_endpoint_id: cachedResult.endpointId ?? cachedResult.trace.endpoint_id,
1997
+ });
1998
+ return buildCachedResultResponse(
1999
+ cachedResult,
2000
+ "marketplace",
2001
+ finalize(
2002
+ "route-cache",
2003
+ cachedResult.result,
2004
+ cachedResult.skill.skill_id,
2005
+ cachedResult.skill,
2006
+ cachedResult.trace,
2007
+ ),
2008
+ );
1959
2009
  }
1960
2010
  }
1961
2011
  }