unbrowse 6.5.0-preview.9 → 6.5.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 CHANGED
@@ -31,7 +31,7 @@ var __promiseAll = (args) => Promise.all(args);
31
31
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
32
32
 
33
33
  // ../../src/build-info.generated.ts
34
- var BUILD_RELEASE_VERSION = "6.5.0-preview.9", BUILD_GIT_SHA = "8ee8dcabdd1a", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAtcHJldmlldy45IiwiZ2l0X3NoYSI6IjhlZThkY2FiZGQxYSIsImNvZGVfaGFzaCI6IjVkOWViZjYxOWM2MSIsInRyYWNlX3ZlcnNpb24iOiI1ZDllYmY2MTljNjFAOGVlOGRjYWJkZDFhIiwiaXNzdWVkX2F0IjoiMjAyNi0wNS0wM1QxMjoyNTo1Mi45MDJaIn0", BUILD_RELEASE_MANIFEST_SIGNATURE = "lq476Pn3w-FPdKDYAr8Z8fONcXUmnNIGPR6Zgxd2Njg", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
34
+ var BUILD_RELEASE_VERSION = "6.5.0", BUILD_GIT_SHA = "a0a05eb03b07", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAiLCJnaXRfc2hhIjoiYTBhMDVlYjAzYjA3IiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUBhMGEwNWViMDNiMDciLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTAzVDE3OjA4OjI2Ljg3OVoifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "QG8diudmY_8OEQowkwHoR3Wac09OBJjDifA_xd9kmr0", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
35
35
 
36
36
  // ../../src/version.ts
37
37
  import { createHash } from "crypto";
@@ -2949,6 +2949,17 @@ function loadConfig() {
2949
2949
  } catch {}
2950
2950
  return null;
2951
2951
  }
2952
+ function resetLocalRegistration() {
2953
+ const configPath = getConfigPath();
2954
+ try {
2955
+ if (!existsSync4(configPath))
2956
+ return { removed: false, config_path: configPath };
2957
+ unlinkSync(configPath);
2958
+ return { removed: true, config_path: configPath };
2959
+ } catch {
2960
+ return { removed: false, config_path: configPath };
2961
+ }
2962
+ }
2952
2963
  function saveConfig(config) {
2953
2964
  const configDir = getConfigDir();
2954
2965
  const configPath = getConfigPath();
@@ -3144,9 +3155,13 @@ function getLocalWalletContext() {
3144
3155
  function getApiKey() {
3145
3156
  if (LOCAL_ONLY)
3146
3157
  return "local-only";
3158
+ const config = loadConfig();
3159
+ if (config?.ignore_env_api_key && config.api_key) {
3160
+ process.env.UNBROWSE_API_KEY = config.api_key;
3161
+ return config.api_key;
3162
+ }
3147
3163
  if (process.env.UNBROWSE_API_KEY)
3148
3164
  return process.env.UNBROWSE_API_KEY;
3149
- const config = loadConfig();
3150
3165
  if (config?.api_key) {
3151
3166
  process.env.UNBROWSE_API_KEY = config.api_key;
3152
3167
  return config.api_key;
@@ -3459,7 +3474,19 @@ async function ensureRegistered(options) {
3459
3474
  try {
3460
3475
  const wallet = getLocalWalletContext();
3461
3476
  const attribution = parseInstallAttribution();
3462
- const { agent_id, api_key } = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...wallet, ...attribution });
3477
+ let registeredWallet = wallet;
3478
+ let registration;
3479
+ try {
3480
+ registration = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...wallet, ...attribution });
3481
+ } catch (err) {
3482
+ const msg = err.message ?? "";
3483
+ if (!wallet.wallet_address || !msg.includes("wallet_already_claimed"))
3484
+ throw err;
3485
+ console.warn("[unbrowse] Wallet is already claimed by another agent. Registering this CLI without a payout wallet; sign in by email or run `unbrowse register --email ... --reset` to recover that account.");
3486
+ registeredWallet = {};
3487
+ registration = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...attribution });
3488
+ }
3489
+ const { agent_id, api_key } = registration;
3463
3490
  process.env.UNBROWSE_API_KEY = api_key;
3464
3491
  saveConfig({
3465
3492
  api_key,
@@ -3468,7 +3495,8 @@ async function ensureRegistered(options) {
3468
3495
  registered_at: new Date().toISOString(),
3469
3496
  tos_accepted_version: tosInfo.version,
3470
3497
  tos_accepted_at: new Date().toISOString(),
3471
- ...wallet
3498
+ ...process.env.UNBROWSE_IGNORE_ENV_API_KEY === "1" ? { ignore_env_api_key: true } : {},
3499
+ ...registeredWallet
3472
3500
  });
3473
3501
  await recordFunnelTelemetryEvent("registration_succeeded", {
3474
3502
  source: "cli",
@@ -5641,6 +5669,7 @@ async function cmdExecute(flags) {
5641
5669
  const limitFlag = flags.limit ? Number(flags.limit) : undefined;
5642
5670
  const schemaFlag = !!flags.schema;
5643
5671
  const rawFlag = !!flags.raw;
5672
+ const resultError = resolveResultError(result);
5644
5673
  if (schemaFlag && !rawFlag) {
5645
5674
  const data = result.result;
5646
5675
  output({
@@ -5651,7 +5680,7 @@ async function cmdExecute(flags) {
5651
5680
  }, !!flags.pretty);
5652
5681
  return;
5653
5682
  }
5654
- if (!rawFlag && (pathFlag || extractFlag || limitFlag)) {
5683
+ if (!rawFlag && !resultError && (pathFlag || extractFlag || limitFlag)) {
5655
5684
  const data = pathFlag ? drillPath(result.result, pathFlag) : result.result;
5656
5685
  const items = Array.isArray(data) ? data : data != null ? [data] : [];
5657
5686
  const extracted = extractFlag ? applyExtract(items, extractFlag) : items;
@@ -6115,6 +6144,14 @@ async function refreshContributionPreferenceFromServer(verbose = false) {
6115
6144
  }
6116
6145
  }
6117
6146
  async function cmdAccount(flags) {
6147
+ if (flags["reset-key"]) {
6148
+ await cmdRegister({
6149
+ reset: true,
6150
+ email: typeof flags.email === "string" ? flags.email : undefined,
6151
+ "no-prompt": flags["no-prompt"]
6152
+ });
6153
+ return;
6154
+ }
6118
6155
  await refreshContributionPreferenceFromServer(false);
6119
6156
  const cfg = loadConfig();
6120
6157
  const contribution = getContributionConfig2();
@@ -6290,8 +6327,8 @@ var CLI_REFERENCE = {
6290
6327
  { name: "earnings", usage: "[--json]", desc: "Show your credit balance, earnings from indexing, and spending" },
6291
6328
  { name: "corpus-test", usage: "--url <url> [--id <id>] [--retries N]", desc: "Capture a single URL with retry logic; keeps best result across N attempts" },
6292
6329
  { name: "corpus-run", usage: "--corpus <file> --out <file> [--retries N]", desc: "Run corpus-test over all cases in a corpus JSON file and write a comparable snapshot" },
6293
- { name: "register", usage: "[--email lewis@example.com] [--no-prompt]", desc: "Register an API key. With --email, sends a magic link via Resend; otherwise creates an anonymous key." },
6294
- { name: "account", usage: "[--json] [--pretty]", desc: "Show local account, dashboard link, wallet, and contribution mode" },
6330
+ { name: "register", usage: "[--email lewis@example.com] [--reset] [--no-prompt]", desc: "Register an API key. With --reset, discard the local cached key first; with --email, mint an account-bound key." },
6331
+ { name: "account", usage: "[--json] [--pretty] [--reset-key] [--email lewis@example.com]", desc: "Show local account, dashboard link, wallet, and contribution mode; --reset-key forces local key reset." },
6295
6332
  { name: "dashboard", usage: "[--no-open] [--pretty]", desc: "Open the website dashboard and pair it to this CLI install through localhost" },
6296
6333
  { name: "mode", usage: "", desc: "Re-prompt for contribution mode (private / share / share + earn)" },
6297
6334
  { name: "capture", usage: "--url <url> --intent <intent>", desc: "Live-browser capture for a single URL \u2014 discovers + indexes API endpoints. Marketplace publish gated by `unbrowse mode`." },
@@ -6890,9 +6927,35 @@ async function cmdClose(flags) {
6890
6927
  output(await api2("POST", "/v1/browse/close", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
6891
6928
  }
6892
6929
  async function cmdRegister(flags) {
6930
+ const reset = flags.reset === true || flags.force === true || flags["reset-key"] === true;
6931
+ const previousConfig = reset ? loadConfig() : null;
6932
+ let ignoredEnvApiKey = false;
6933
+ const stopServerAfterReset = () => {
6934
+ if (!reset)
6935
+ return;
6936
+ if (stopServer(BASE_URL)) {
6937
+ info("Stopped local server so the next command starts with the fresh key.");
6938
+ }
6939
+ };
6940
+ if (reset) {
6941
+ const envKey = process.env.UNBROWSE_API_KEY?.trim();
6942
+ const result = resetLocalRegistration();
6943
+ delete process.env.UNBROWSE_API_KEY;
6944
+ if (envKey) {
6945
+ ignoredEnvApiKey = true;
6946
+ process.env.UNBROWSE_IGNORE_ENV_API_KEY = "1";
6947
+ }
6948
+ info(`${result.removed ? "Removed" : "No"} local API key cache at ${result.config_path}.`);
6949
+ if (envKey) {
6950
+ info("Ignoring UNBROWSE_API_KEY for this reset run. Future Unbrowse commands will prefer the fresh saved key; still remove or update that env var in your shell.");
6951
+ }
6952
+ if (typeof flags.email !== "string" && previousConfig?.email) {
6953
+ flags.email = previousConfig.email;
6954
+ }
6955
+ }
6893
6956
  if (typeof flags.email === "string" && flags.email.length > 0) {
6894
6957
  const email = flags.email;
6895
- if (getApiKey()) {
6958
+ if (!reset && getApiKey()) {
6896
6959
  info("Already registered. Re-running with --email will mint a new key and overwrite ~/.unbrowse/config.json.");
6897
6960
  }
6898
6961
  info(`Sending magic link to ${email}\u2026`);
@@ -6914,7 +6977,8 @@ async function cmdRegister(flags) {
6914
6977
  tos_accepted_version: null,
6915
6978
  tos_accepted_at: null,
6916
6979
  email: result.email,
6917
- user_id: result.user_id
6980
+ user_id: result.user_id,
6981
+ ...ignoredEnvApiKey ? { ignore_env_api_key: true } : {}
6918
6982
  });
6919
6983
  process.env.UNBROWSE_API_KEY = result.api_key;
6920
6984
  info(`Signed in as ${result.email}. API key saved to ~/.unbrowse/config.json.`);
@@ -6929,15 +6993,17 @@ async function cmdRegister(flags) {
6929
6993
  info(`Auto-publish to marketplace: ${serverPrefs.share_pointers ? "ON" : "off"} (synced from your account).`);
6930
6994
  }
6931
6995
  } catch {}
6996
+ stopServerAfterReset();
6932
6997
  return;
6933
6998
  }
6934
- if (getApiKey()) {
6999
+ if (!reset && getApiKey()) {
6935
7000
  info("Already registered. API key loaded from env or ~/.unbrowse/config.json");
6936
7001
  return;
6937
7002
  }
6938
7003
  await ensureRegistered({ promptForEmail: !flags["no-prompt"], exitOnFailure: false });
6939
7004
  if (getApiKey()) {
6940
7005
  info("Registration complete. You can now publish skills and check earnings.");
7006
+ stopServerAfterReset();
6941
7007
  } else {
6942
7008
  info("Registration skipped or failed. Unbrowse still works locally \u2014 publish/earnings are disabled.");
6943
7009
  }
package/dist/mcp.js CHANGED
@@ -226,11 +226,11 @@ import { dirname, join, parse } from "path";
226
226
  import { fileURLToPath as fileURLToPath2 } from "url";
227
227
 
228
228
  // ../../src/build-info.generated.ts
229
- var BUILD_RELEASE_VERSION = "6.5.0-preview.9";
230
- var BUILD_GIT_SHA = "8ee8dcabdd1a";
229
+ var BUILD_RELEASE_VERSION = "6.5.0";
230
+ var BUILD_GIT_SHA = "a0a05eb03b07";
231
231
  var BUILD_CODE_HASH = "5d9ebf619c61";
232
- var BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAtcHJldmlldy45IiwiZ2l0X3NoYSI6IjhlZThkY2FiZGQxYSIsImNvZGVfaGFzaCI6IjVkOWViZjYxOWM2MSIsInRyYWNlX3ZlcnNpb24iOiI1ZDllYmY2MTljNjFAOGVlOGRjYWJkZDFhIiwiaXNzdWVkX2F0IjoiMjAyNi0wNS0wM1QxMjoyNTo1Mi45MDJaIn0";
233
- var BUILD_RELEASE_MANIFEST_SIGNATURE = "lq476Pn3w-FPdKDYAr8Z8fONcXUmnNIGPR6Zgxd2Njg";
232
+ var BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAiLCJnaXRfc2hhIjoiYTBhMDVlYjAzYjA3IiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUBhMGEwNWViMDNiMDciLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTAzVDE3OjA4OjI2Ljg3OVoifQ";
233
+ var BUILD_RELEASE_MANIFEST_SIGNATURE = "QG8diudmY_8OEQowkwHoR3Wac09OBJjDifA_xd9kmr0";
234
234
  var BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
235
235
  var BUILD_DEFAULT_PROFILE = "";
236
236
 
@@ -809,9 +809,13 @@ function loadConfig() {
809
809
  function getApiKey() {
810
810
  if (LOCAL_ONLY)
811
811
  return "local-only";
812
+ const config = loadConfig();
813
+ if (config?.ignore_env_api_key && config.api_key) {
814
+ process.env.UNBROWSE_API_KEY = config.api_key;
815
+ return config.api_key;
816
+ }
812
817
  if (process.env.UNBROWSE_API_KEY)
813
818
  return process.env.UNBROWSE_API_KEY;
814
- const config = loadConfig();
815
819
  if (config?.api_key) {
816
820
  process.env.UNBROWSE_API_KEY = config.api_key;
817
821
  return config.api_key;
package/dist/server.js CHANGED
@@ -7344,7 +7344,7 @@ var init_capture = __esm(async () => {
7344
7344
  });
7345
7345
 
7346
7346
  // ../../src/build-info.generated.ts
7347
- var BUILD_RELEASE_VERSION = "6.5.0-preview.9", BUILD_GIT_SHA = "8ee8dcabdd1a", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAtcHJldmlldy45IiwiZ2l0X3NoYSI6IjhlZThkY2FiZGQxYSIsImNvZGVfaGFzaCI6IjVkOWViZjYxOWM2MSIsInRyYWNlX3ZlcnNpb24iOiI1ZDllYmY2MTljNjFAOGVlOGRjYWJkZDFhIiwiaXNzdWVkX2F0IjoiMjAyNi0wNS0wM1QxMjoyNTo1Mi45MDJaIn0", BUILD_RELEASE_MANIFEST_SIGNATURE = "lq476Pn3w-FPdKDYAr8Z8fONcXUmnNIGPR6Zgxd2Njg", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
7347
+ var BUILD_RELEASE_VERSION = "6.5.0", BUILD_GIT_SHA = "a0a05eb03b07", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi41LjAiLCJnaXRfc2hhIjoiYTBhMDVlYjAzYjA3IiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUBhMGEwNWViMDNiMDciLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTAzVDE3OjA4OjI2Ljg3OVoifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "QG8diudmY_8OEQowkwHoR3Wac09OBJjDifA_xd9kmr0", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
7348
7348
 
7349
7349
  // ../../src/version.ts
7350
7350
  import { createHash as createHash2 } from "crypto";
@@ -7815,6 +7815,7 @@ __export(exports_client2, {
7815
7815
  searchIntent: () => searchIntent,
7816
7816
  saveConfig: () => saveConfig,
7817
7817
  resolveAgentName: () => resolveAgentName,
7818
+ resetLocalRegistration: () => resetLocalRegistration,
7818
7819
  registerAgent: () => registerAgent,
7819
7820
  recordTransaction: () => recordTransaction,
7820
7821
  recordRoutingTelemetry: () => recordRoutingTelemetry,
@@ -7956,6 +7957,17 @@ function loadConfig() {
7956
7957
  } catch {}
7957
7958
  return null;
7958
7959
  }
7960
+ function resetLocalRegistration() {
7961
+ const configPath = getConfigPath();
7962
+ try {
7963
+ if (!existsSync7(configPath))
7964
+ return { removed: false, config_path: configPath };
7965
+ unlinkSync(configPath);
7966
+ return { removed: true, config_path: configPath };
7967
+ } catch {
7968
+ return { removed: false, config_path: configPath };
7969
+ }
7970
+ }
7959
7971
  function saveConfig(config) {
7960
7972
  const configDir = getConfigDir();
7961
7973
  const configPath = getConfigPath();
@@ -8178,9 +8190,13 @@ function getLocalWalletContext() {
8178
8190
  function getApiKey() {
8179
8191
  if (LOCAL_ONLY)
8180
8192
  return "local-only";
8193
+ const config = loadConfig();
8194
+ if (config?.ignore_env_api_key && config.api_key) {
8195
+ process.env.UNBROWSE_API_KEY = config.api_key;
8196
+ return config.api_key;
8197
+ }
8181
8198
  if (process.env.UNBROWSE_API_KEY)
8182
8199
  return process.env.UNBROWSE_API_KEY;
8183
- const config = loadConfig();
8184
8200
  if (config?.api_key) {
8185
8201
  process.env.UNBROWSE_API_KEY = config.api_key;
8186
8202
  return config.api_key;
@@ -8496,7 +8512,19 @@ async function ensureRegistered(options) {
8496
8512
  try {
8497
8513
  const wallet = getLocalWalletContext();
8498
8514
  const attribution = parseInstallAttribution();
8499
- const { agent_id, api_key } = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...wallet, ...attribution });
8515
+ let registeredWallet = wallet;
8516
+ let registration;
8517
+ try {
8518
+ registration = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...wallet, ...attribution });
8519
+ } catch (err) {
8520
+ const msg = err.message ?? "";
8521
+ if (!wallet.wallet_address || !msg.includes("wallet_already_claimed"))
8522
+ throw err;
8523
+ console.warn("[unbrowse] Wallet is already claimed by another agent. Registering this CLI without a payout wallet; sign in by email or run `unbrowse register --email ... --reset` to recover that account.");
8524
+ registeredWallet = {};
8525
+ registration = await api("POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...attribution });
8526
+ }
8527
+ const { agent_id, api_key } = registration;
8500
8528
  process.env.UNBROWSE_API_KEY = api_key;
8501
8529
  saveConfig({
8502
8530
  api_key,
@@ -8505,7 +8533,8 @@ async function ensureRegistered(options) {
8505
8533
  registered_at: new Date().toISOString(),
8506
8534
  tos_accepted_version: tosInfo.version,
8507
8535
  tos_accepted_at: new Date().toISOString(),
8508
- ...wallet
8536
+ ...process.env.UNBROWSE_IGNORE_ENV_API_KEY === "1" ? { ignore_env_api_key: true } : {},
8537
+ ...registeredWallet
8509
8538
  });
8510
8539
  await recordFunnelTelemetryEvent("registration_succeeded", {
8511
8540
  source: "cli",
@@ -16086,6 +16115,27 @@ function stampTrace(trace) {
16086
16115
  trace.trace_version = TRACE_VERSION;
16087
16116
  return trace;
16088
16117
  }
16118
+ function buildBrowserFallbackCommands(contextUrl, skill) {
16119
+ const target = contextUrl || `https://${skill.domain}`;
16120
+ return [
16121
+ `unbrowse go "${target}"`,
16122
+ "unbrowse snap --filter interactive",
16123
+ "unbrowse text",
16124
+ "unbrowse close"
16125
+ ];
16126
+ }
16127
+ function staleEndpointResult(status, skill, endpoint, contextUrl, message) {
16128
+ const target = contextUrl || endpoint.trigger_url || `https://${skill.domain}`;
16129
+ return {
16130
+ error: "stale_endpoint",
16131
+ status_code: status,
16132
+ skill_id: skill.skill_id,
16133
+ endpoint_id: endpoint.endpoint_id,
16134
+ message: message ?? `Endpoint ${endpoint.endpoint_id} returned HTTP ${status} after replay recovery. Treat this marketplace route as stale and use browser capture for this task.`,
16135
+ next_step: `Use browser fallback: unbrowse go "${target}", inspect with snap/text, then close to checkpoint and publish a fresh route.`,
16136
+ commands: buildBrowserFallbackCommands(target, skill)
16137
+ };
16138
+ }
16089
16139
  function serializeReplayBody(body, headers) {
16090
16140
  if (body == null)
16091
16141
  return;
@@ -17896,9 +17946,9 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
17896
17946
  }
17897
17947
  }
17898
17948
  }
17899
- const { status, trace_id } = result;
17949
+ let { status, trace_id } = result;
17900
17950
  let data = result.data;
17901
- const trace = stampTrace({
17951
+ let trace = stampTrace({
17902
17952
  trace_id,
17903
17953
  skill_id: skill.skill_id,
17904
17954
  endpoint_id: endpoint.endpoint_id,
@@ -17926,7 +17976,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
17926
17976
  try {
17927
17977
  const sessionRefreshed = await authRuntime.refreshSession(epDomain);
17928
17978
  if (sessionRefreshed) {
17929
- log("auth", `session refreshed via authRuntime for ${epDomain} — retry should succeed`);
17979
+ log("auth", `session refreshed via authRuntime for ${epDomain} — retrying replay once`);
17930
17980
  authRecovered = true;
17931
17981
  }
17932
17982
  if (!authRecovered) {
@@ -17944,20 +17994,51 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
17944
17994
  }
17945
17995
  }
17946
17996
  if (authRecovered) {
17947
- trace.error = `${trace.error} (credentials refreshed retry should succeed)`;
17997
+ await reloadExecutionAuthState(skill, epDomain, authHeaders, cookies);
17998
+ const retry = await serverFetch(workflowBindings?.extraHeaders, workflowBindings?.bodyOverride);
17999
+ decisionTrace.push({ step: "auth_recovery_retry", status: retry.status });
18000
+ result = retry;
18001
+ status = retry.status;
18002
+ trace_id = retry.trace_id;
18003
+ data = retry.data;
18004
+ trace = stampTrace({
18005
+ trace_id,
18006
+ skill_id: skill.skill_id,
18007
+ endpoint_id: endpoint.endpoint_id,
18008
+ started_at: startedAt,
18009
+ completed_at: new Date().toISOString(),
18010
+ success: status >= 200 && status < 300,
18011
+ status_code: status
18012
+ });
18013
+ trace.decision_trace = decisionTrace;
18014
+ if (trace.success) {
18015
+ trace.result = data;
18016
+ } else {
18017
+ trace.error = `HTTP ${status}`;
18018
+ data = staleEndpointResult(status, skill, endpoint, options?.contextUrl, `Credentials were refreshed, but endpoint ${endpoint.endpoint_id} still returned HTTP ${status}. Treat this marketplace route as stale and use browser capture for this task.`);
18019
+ trace.result = data;
18020
+ }
17948
18021
  } else {
17949
18022
  if (skill.auth_profile_ref) {
17950
18023
  await deleteCredential(skill.auth_profile_ref);
17951
18024
  }
17952
18025
  trace.error = `${trace.error} (stale credentials — re-authenticate via /v1/auth/login)`;
18026
+ data = staleEndpointResult(status, skill, endpoint, options?.contextUrl, `Endpoint ${endpoint.endpoint_id} returned HTTP ${status}, and credential recovery did not produce a usable session.`);
18027
+ trace.result = data;
17953
18028
  }
17954
18029
  } catch {
17955
18030
  if (skill.auth_profile_ref) {
17956
18031
  await deleteCredential(skill.auth_profile_ref);
17957
18032
  }
17958
18033
  trace.error = `${trace.error} (stale credential deleted)`;
18034
+ data = staleEndpointResult(status, skill, endpoint, options?.contextUrl, `Endpoint ${endpoint.endpoint_id} returned HTTP ${status}; credential recovery failed and stale credentials were cleared.`);
18035
+ trace.result = data;
17959
18036
  }
17960
18037
  }
18038
+ if (!trace.success && (status === 404 || status === 429 || status >= 500)) {
18039
+ data = staleEndpointResult(status, skill, endpoint, options?.contextUrl);
18040
+ trace.result = data;
18041
+ }
17961
18042
  if (trace.success && endpoint.response_schema && data != null) {
17962
18043
  const drift = detectSchemaDrift(endpoint.response_schema, data);
17963
18044
  if (drift.drifted) {
@@ -25545,14 +25626,29 @@ async function createBrowseSession(sessions, client, injectInterceptor2, session
25545
25626
  });
25546
25627
  }
25547
25628
  async function isBrowseSessionLive(session, client) {
25548
- if (!session.tabId)
25629
+ const dbg = (reason, extra = {}) => {
25630
+ if (!process.env.UNBROWSE_BROWSE_LIVENESS_DEBUG)
25631
+ return;
25632
+ console.warn("[browse-liveness] not live", JSON.stringify({
25633
+ session_id: session.sessionId,
25634
+ tab_id: session.tabId,
25635
+ url: session.url,
25636
+ reason,
25637
+ ...extra
25638
+ }));
25639
+ };
25640
+ if (!session.tabId) {
25641
+ dbg("no_tab_id");
25549
25642
  return false;
25643
+ }
25550
25644
  const sessionClient = resolveSessionClient(session, client);
25551
25645
  try {
25552
25646
  await sessionClient.start();
25553
25647
  } catch (error) {
25554
- if (isRecoverableBrowseFailure(error))
25648
+ if (isRecoverableBrowseFailure(error)) {
25649
+ dbg("session_client_start_recoverable", { error: String(error?.message ?? error) });
25555
25650
  return false;
25651
+ }
25556
25652
  throw error;
25557
25653
  }
25558
25654
  for (let attempt = 0;attempt < LIVE_CHECK_RETRIES; attempt += 1) {
@@ -25564,6 +25660,11 @@ async function isBrowseSessionLive(session, client) {
25564
25660
  await sleep(LIVE_CHECK_RETRY_DELAY_MS);
25565
25661
  continue;
25566
25662
  }
25663
+ dbg("tab_not_in_discover", {
25664
+ attempt,
25665
+ discovered_tab_ids: tabs.map((t) => t.id),
25666
+ discovered_tab_count: tabs.length
25667
+ });
25567
25668
  return false;
25568
25669
  }
25569
25670
  session.brokerPort = sessionClient.getPort?.() ?? session.brokerPort;
@@ -25589,8 +25690,10 @@ async function isBrowseSessionLive(session, client) {
25589
25690
  return true;
25590
25691
  }
25591
25692
  } catch (error) {
25592
- if (!isRecoverableBrowseFailure(error))
25693
+ if (!isRecoverableBrowseFailure(error)) {
25694
+ dbg("get_current_url_unrecoverable", { error: String(error?.message ?? error) });
25593
25695
  return false;
25696
+ }
25594
25697
  }
25595
25698
  if (hasMeaningfulBrowseUrl(session.url) || hasMeaningfulBrowseUrl(exactTab.url)) {
25596
25699
  if (hasMeaningfulBrowseUrl(exactTab.url)) {
@@ -25600,12 +25703,15 @@ async function isBrowseSessionLive(session, client) {
25600
25703
  return true;
25601
25704
  }
25602
25705
  } catch (error) {
25603
- if (!isRecoverableBrowseFailure(error))
25706
+ if (!isRecoverableBrowseFailure(error)) {
25707
+ dbg("loop_unrecoverable", { error: String(error?.message ?? error) });
25604
25708
  return false;
25709
+ }
25605
25710
  }
25606
25711
  if (attempt < LIVE_CHECK_RETRIES - 1)
25607
25712
  await sleep(LIVE_CHECK_RETRY_DELAY_MS);
25608
25713
  }
25714
+ dbg("retries_exhausted_url_check_failed");
25609
25715
  return false;
25610
25716
  }
25611
25717
  async function listLiveBrowseSessions(sessions, client) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unbrowse",
3
- "version": "6.5.0-preview.9",
3
+ "version": "6.5.0",
4
4
  "description": "Reverse-engineer any website into reusable API skills. Zero-dep single binary with embedded browser engine.",
5
5
  "type": "module",
6
6
  "bin": {