unbrowse 2.9.1 → 2.10.2

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.
@@ -0,0 +1,33 @@
1
+ import type { ExecutionTrace, OrchestrationTiming } from "./types/index.js";
2
+
3
+ export interface AnalyticsSessionPayload {
4
+ session_id: string;
5
+ started_at: string;
6
+ completed_at?: string;
7
+ trace_version?: string;
8
+ api_calls: number;
9
+ discovery_queries: number;
10
+ cached_skill_calls: number;
11
+ fresh_index_calls: number;
12
+ browser_mode: "default" | "replaced" | "manual" | "unknown";
13
+ }
14
+
15
+ export function buildAnalyticsSessionPayload(
16
+ sessionId: string,
17
+ startedAt: string,
18
+ source: OrchestrationTiming["source"],
19
+ trace: Pick<ExecutionTrace, "completed_at" | "network_events" | "trace_version">,
20
+ ): AnalyticsSessionPayload {
21
+ const cacheLike = source === "marketplace" || source === "route-cache" || source === "first-pass";
22
+ return {
23
+ session_id: sessionId,
24
+ started_at: startedAt,
25
+ completed_at: trace.completed_at,
26
+ trace_version: trace.trace_version,
27
+ api_calls: Math.max(1, trace.network_events?.length ?? 0),
28
+ discovery_queries: cacheLike ? 1 : 0,
29
+ cached_skill_calls: cacheLike ? 1 : 0,
30
+ fresh_index_calls: source === "live-capture" ? 1 : 0,
31
+ browser_mode: "unknown",
32
+ };
33
+ }
@@ -5,7 +5,7 @@ import { extractEndpoints, extractAuthHeaders } from "../reverse-engineer/index.
5
5
  import { INTERCEPTOR_SCRIPT, collectInterceptedRequests, injectInterceptor, type RawRequest } from "../capture/index.js";
6
6
  import { queueBackgroundIndex } from "../indexer/index.js";
7
7
  import { nanoid } from "nanoid";
8
- import type { SkillManifest } from "../types/index.js";
8
+ import type { ExecutionTrace, OrchestrationTiming, ProjectionOptions, SkillManifest } from "../types/index.js";
9
9
  import { extractBrowserCookies } from "../auth/browser-cookies.js";
10
10
  import { mergeEndpoints } from "../marketplace/index.js";
11
11
  import { buildSkillOperationGraph } from "../graph/index.js";
@@ -14,15 +14,13 @@ import { findExistingSkillForDomain, cachePublishedSkill } from "../client/index
14
14
  import { storeCredential } from "../vault/index.js";
15
15
  import { generateLocalDescription, writeSkillSnapshot, buildResolveCacheKey, getDomainReuseKey, domainSkillCache, persistDomainCache, scopedCacheKey, snapshotPathForCacheKey, invalidateRouteCacheForDomain, summarizeSchema, extractSampleValues } from "../orchestrator/index.js";
16
16
  import { TRACE_VERSION, CODE_HASH, GIT_SHA } from "../version.js";
17
- import { promoteExplicitExecution, resolveAndExecute } from "../orchestrator/index.js";
17
+ import { promoteExplicitExecution, resolveAndExecute, type OrchestratorResult } from "../orchestrator/index.js";
18
18
  import { getSkill } from "../marketplace/index.js";
19
19
  import { executeSkill, rankEndpoints } from "../execution/index.js";
20
- import { storeCredential } from "../vault/index.js";
21
20
  import { interactiveLogin, extractBrowserAuth } from "../auth/index.js";
22
21
  import { publishSkill } from "../marketplace/index.js";
23
- import { recordFeedback, recordDiagnostics, recordExecution, getApiKey, getRecentLocalSkill } from "../client/index.js";
22
+ import { recordFeedback, recordDiagnostics, recordExecution, getApiKey, getRecentLocalSkill, recordAnalyticsSession, type AnalyticsSessionPayload } from "../client/index.js";
24
23
  import { ROUTE_LIMITS } from "../ratelimit/index.js";
25
- import type { ProjectionOptions } from "../types/index.js";
26
24
  import { getSkillChunk, toAgentSkillChunkView } from "../graph/index.js";
27
25
  import { listRecentSessionsForDomain } from "../session-logs.js";
28
26
  import { mergeAgentReview } from "../indexer/index.js";
@@ -33,6 +31,43 @@ const BETA_API_URL = process.env.UNBROWSE_BACKEND_URL || "https://beta-api.unbro
33
31
 
34
32
  const TRACES_DIR = process.env.TRACES_DIR ?? join(process.cwd(), "traces");
35
33
 
34
+ type AnalyticsSessionResult = {
35
+ trace: Pick<ExecutionTrace, "trace_id" | "started_at" | "completed_at" | "endpoint_id" | "trace_version">;
36
+ timing?: Pick<OrchestrationTiming, "source">;
37
+ source?: OrchestratorResult["source"];
38
+ };
39
+
40
+ export function buildAnalyticsSessionPayload(
41
+ result: AnalyticsSessionResult,
42
+ opts: {
43
+ browser_mode: AnalyticsSessionPayload["browser_mode"];
44
+ discovery_queries: number;
45
+ cached_skill_calls?: number;
46
+ fresh_index_calls?: number;
47
+ },
48
+ ): AnalyticsSessionPayload {
49
+ const source = result.timing?.source ?? result.source;
50
+ const apiCalls = result.trace.endpoint_id ? 1 : 0;
51
+ const cachedSkillCalls = opts.cached_skill_calls ?? (
52
+ apiCalls > 0 && source !== "live-capture" && source !== "first-pass" ? 1 : 0
53
+ );
54
+ const freshIndexCalls = opts.fresh_index_calls ?? (
55
+ apiCalls > 0 && (source === "live-capture" || source === "first-pass") ? 1 : 0
56
+ );
57
+
58
+ return {
59
+ session_id: result.trace.trace_id,
60
+ started_at: result.trace.started_at,
61
+ completed_at: result.trace.completed_at,
62
+ trace_version: result.trace.trace_version ?? TRACE_VERSION,
63
+ api_calls: apiCalls,
64
+ discovery_queries: opts.discovery_queries,
65
+ cached_skill_calls: cachedSkillCalls,
66
+ fresh_index_calls: freshIndexCalls,
67
+ browser_mode: opts.browser_mode ?? "unknown",
68
+ };
69
+ }
70
+
36
71
 
37
72
  /** Convert Kuri HAR entries to RawRequest format */
38
73
  function harEntriesToRawRequests(entries: KuriHarEntry[]): RawRequest[] {
@@ -183,11 +218,11 @@ async function fetchStats() {
183
218
 
184
219
  const externalCalls: Promise<unknown>[] = [
185
220
  npmPoint("unbrowse", "last-month"),
186
- npmPoint("@getfoundry/unbrowse-openclaw", "last-month"),
221
+ npmPoint("unbrowse-openclaw", "last-month"),
187
222
  npmPoint("unbrowse", "1970-01-01:2099-12-31"),
188
- npmPoint("@getfoundry/unbrowse-openclaw", "1970-01-01:2099-12-31"),
223
+ npmPoint("unbrowse-openclaw", "1970-01-01:2099-12-31"),
189
224
  npmRange("unbrowse"),
190
- npmRange("@getfoundry/unbrowse-openclaw"),
225
+ npmRange("unbrowse-openclaw"),
191
226
  fetch("https://api.github.com/repos/anthropic-ai/unbrowse", {
192
227
  headers: { "User-Agent": "unbrowse-stats" },
193
228
  }).then(r => r.json() as Promise<Record<string, unknown>>),
@@ -332,6 +367,11 @@ export async function registerRoutes(app: FastifyInstance) {
332
367
  res.available_endpoints = innerResult.available_endpoints;
333
368
  }
334
369
 
370
+ await recordAnalyticsSession(buildAnalyticsSessionPayload(result, {
371
+ browser_mode: "replaced",
372
+ discovery_queries: 1,
373
+ })).catch(() => {});
374
+
335
375
  return reply.send(result);
336
376
  } catch (err) {
337
377
  return reply.code(500).send({ error: (err as Error).message });
@@ -602,6 +642,10 @@ export async function registerRoutes(app: FastifyInstance) {
602
642
  if (freshResult.trace?.skill_id && freshResult.trace?.endpoint_id) {
603
643
  recordExecution(freshResult.trace.skill_id, freshResult.trace.endpoint_id, freshResult.trace, skill).catch(() => {});
604
644
  }
645
+ await recordAnalyticsSession(buildAnalyticsSessionPayload(freshResult, {
646
+ browser_mode: "manual",
647
+ discovery_queries: 1,
648
+ })).catch(() => {});
605
649
  return reply.send({
606
650
  ...freshResult,
607
651
  _recovery: {
@@ -615,6 +659,13 @@ export async function registerRoutes(app: FastifyInstance) {
615
659
  }
616
660
  }
617
661
 
662
+ await recordAnalyticsSession(buildAnalyticsSessionPayload(execResult, {
663
+ browser_mode: "manual",
664
+ discovery_queries: 0,
665
+ cached_skill_calls: execResult.trace.endpoint_id ? 1 : 0,
666
+ fresh_index_calls: 0,
667
+ })).catch(() => {});
668
+
618
669
  return reply.send(execResult);
619
670
  } catch (err) {
620
671
  return reply.code(500).send({ error: (err as Error).message });
@@ -27,6 +27,29 @@ export interface LoginResult {
27
27
  error?: string;
28
28
  }
29
29
 
30
+ export interface BrowserAuthSourceMeta {
31
+ family?: string;
32
+ userDataDir?: string;
33
+ cookieDbPath?: string;
34
+ }
35
+
36
+ export interface StoredAuthBundle {
37
+ cookies: AuthCookie[];
38
+ headers: Record<string, string>;
39
+ source_keys: string[];
40
+ source_meta?: BrowserAuthSourceMeta | null;
41
+ }
42
+
43
+ export function storedAuthNeedsBrowserRefresh(bundle: StoredAuthBundle | null | undefined): boolean {
44
+ if (!bundle) return true;
45
+ if (bundle.cookies.length === 0 && Object.keys(bundle.headers).length === 0) return true;
46
+ const sourceMeta = bundle.source_meta;
47
+ if (!sourceMeta) return true;
48
+ if (sourceMeta.family === "chromium" && !sourceMeta.userDataDir && !sourceMeta.cookieDbPath) {
49
+ return true;
50
+ }
51
+ return false;
52
+ }
30
53
  /**
31
54
  * Open a visible browser for the user to complete login.
32
55
  * Uses Kuri to manage the browser tab, polls for login completion via cookies.
@@ -225,6 +248,17 @@ function filterExpired(cookies: AuthCookie[]): AuthCookie[] {
225
248
  export async function getStoredAuth(
226
249
  domain: string
227
250
  ): Promise<AuthCookie[] | null> {
251
+ const bundle = await getStoredAuthBundle(domain);
252
+ return bundle?.cookies?.length ? bundle.cookies : null;
253
+ }
254
+
255
+ /**
256
+ * Retrieve the stored auth bundle for a domain from the vault.
257
+ * Preserves headers/source metadata while filtering expired cookies.
258
+ */
259
+ export async function getStoredAuthBundle(
260
+ domain: string
261
+ ): Promise<StoredAuthBundle | null> {
228
262
  const regDomain = getRegistrableDomain(domain);
229
263
  const keysToTry = [`auth:${regDomain}`];
230
264
  if (domain !== regDomain) keysToTry.push(`auth:${domain}`);
@@ -233,12 +267,10 @@ export async function getStoredAuth(
233
267
  const stored = await getCredential(key);
234
268
  if (!stored) continue;
235
269
  try {
236
- const parsed = JSON.parse(stored) as { cookies?: AuthCookie[] };
237
- const cookies = parsed.cookies;
238
- if (!cookies || cookies.length === 0) continue;
239
-
270
+ const parsed = JSON.parse(stored) as Partial<StoredAuthBundle> & { cookies?: AuthCookie[] };
271
+ const cookies = parsed.cookies ?? [];
240
272
  const valid = filterExpired(cookies);
241
- if (valid.length === 0) {
273
+ if (cookies.length > 0 && valid.length === 0 && Object.keys(parsed.headers ?? {}).length === 0) {
242
274
  log("auth", `all ${cookies.length} cookies for ${domain} (key: ${key}) are expired — deleting`);
243
275
  await deleteCredential(key);
244
276
  continue;
@@ -246,11 +278,17 @@ export async function getStoredAuth(
246
278
  if (valid.length < cookies.length) {
247
279
  log("auth", `filtered ${cookies.length - valid.length} expired cookies for ${domain}`);
248
280
  }
249
- return valid;
281
+ return {
282
+ cookies: valid,
283
+ headers: parsed.headers ?? {},
284
+ source_keys: parsed.source_keys ?? [],
285
+ source_meta: parsed.source_meta ?? null,
286
+ };
250
287
  } catch {
251
288
  continue;
252
289
  }
253
290
  }
291
+
254
292
  return null;
255
293
  }
256
294
 
@@ -4,6 +4,7 @@ import { homedir, hostname } from "os";
4
4
  import { randomBytes, createHash } from "crypto";
5
5
  import { createInterface } from "readline";
6
6
  import type { AgentSkillChunkView, EndpointStats, ExecutionTrace, OrchestrationTiming, SkillManifest, ValidationResult } from "../types/index.js";
7
+ import { ensureCascadeSplitForSkill } from "../payments/cascade.js";
7
8
  import { attributeLifecycle } from "../runtime/lifecycle.js";
8
9
  import type { LifecycleEvent } from "../runtime/lifecycle.js";
9
10
 
@@ -70,6 +71,8 @@ interface UnbrowseConfig {
70
71
  registered_at: string;
71
72
  tos_accepted_version: string | null;
72
73
  tos_accepted_at: string | null;
74
+ wallet_address?: string;
75
+ wallet_provider?: string;
73
76
  }
74
77
 
75
78
  type ApiKeySource = "env" | "config";
@@ -116,6 +119,23 @@ export function resolveAgentName(preferredEmail: string | undefined, fallbackNam
116
119
  return isValidAgentEmail(normalized) ? normalized : fallbackName;
117
120
  }
118
121
 
122
+ function getLocalWalletContext(): { wallet_address?: string; wallet_provider?: string } {
123
+ const lobsterWallet = process.env.LOBSTER_WALLET_ADDRESS?.trim();
124
+ if (lobsterWallet) {
125
+ return { wallet_address: lobsterWallet, wallet_provider: "lobster.cash" };
126
+ }
127
+
128
+ const genericWallet = process.env.AGENT_WALLET_ADDRESS?.trim();
129
+ if (genericWallet) {
130
+ return {
131
+ wallet_address: genericWallet,
132
+ wallet_provider: process.env.AGENT_WALLET_PROVIDER?.trim() || undefined,
133
+ };
134
+ }
135
+
136
+ return {};
137
+ }
138
+
119
139
  export function getApiKey(): string {
120
140
  if (LOCAL_ONLY) return "local-only";
121
141
  // Env var takes priority, then cached config
@@ -211,7 +231,12 @@ async function findUsableApiKey(): Promise<{ key: string; source: ApiKeySource }
211
231
  return null;
212
232
  }
213
233
 
214
- async function api<T = unknown>(method: string, path: string, body?: unknown, opts?: { noAuth?: boolean; timeoutMs?: number }): Promise<T> {
234
+ async function apiRequest<T = unknown>(
235
+ method: string,
236
+ path: string,
237
+ body?: unknown,
238
+ opts?: { noAuth?: boolean; timeoutMs?: number },
239
+ ): Promise<{ data: T; headers: Headers }> {
215
240
  const key = opts?.noAuth ? "" : getApiKey();
216
241
  const controller = new AbortController();
217
242
  const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? API_TIMEOUT_MS);
@@ -268,6 +293,11 @@ async function api<T = unknown>(method: string, path: string, body?: unknown, op
268
293
  const msg = errData.details?.length ? `${errData.error}: ${errData.details.join("; ")}` : errData.error ?? `API HTTP ${res.status}`;
269
294
  throw new Error(msg);
270
295
  }
296
+ return { data: data as T, headers: res.headers };
297
+ }
298
+
299
+ async function api<T = unknown>(method: string, path: string, body?: unknown, opts?: { noAuth?: boolean; timeoutMs?: number }): Promise<T> {
300
+ const { data } = await apiRequest<T>(method, path, body, opts);
271
301
  return data;
272
302
  }
273
303
 
@@ -382,6 +412,13 @@ export async function ensureRegistered(options?: { promptForEmail?: boolean }):
382
412
  console.log("[unbrowse] Restored saved registration.");
383
413
  }
384
414
  await checkTosStatus();
415
+ try {
416
+ const profile = await getMyProfile();
417
+ const wallet = getLocalWalletContext();
418
+ if (wallet.wallet_address && profile.wallet_address !== wallet.wallet_address) {
419
+ await syncAgentWallet(wallet);
420
+ }
421
+ } catch { /* non-fatal */ }
385
422
  return;
386
423
  }
387
424
 
@@ -408,8 +445,9 @@ export async function ensureRegistered(options?: { promptForEmail?: boolean }):
408
445
  console.log(`Registering as "${name}"...`);
409
446
 
410
447
  try {
448
+ const wallet = getLocalWalletContext();
411
449
  const { agent_id, api_key } = await api<{ agent_id: string; api_key: string }>(
412
- "POST", "/v1/agents/register", { name, tos_version: tosInfo.version }
450
+ "POST", "/v1/agents/register", { name, tos_version: tosInfo.version, ...wallet }
413
451
  );
414
452
 
415
453
  process.env.UNBROWSE_API_KEY = api_key;
@@ -420,6 +458,7 @@ export async function ensureRegistered(options?: { promptForEmail?: boolean }):
420
458
  registered_at: new Date().toISOString(),
421
459
  tos_accepted_version: tosInfo.version,
422
460
  tos_accepted_at: new Date().toISOString(),
461
+ ...wallet,
423
462
  });
424
463
 
425
464
  console.log(`Registered as ${name}. API key saved to ~/.unbrowse/config.json`);
@@ -596,7 +635,29 @@ export async function publishSkill(
596
635
  } as SkillManifest & { warnings: string[] };
597
636
  }
598
637
  if (LOCAL_ONLY) throw new Error("local-only mode");
599
- return api("POST", "/v1/skills", draft, { timeoutMs: PUBLISH_TIMEOUT_MS });
638
+ const wallet = getLocalWalletContext();
639
+ const published = await api<SkillManifest & { warnings: string[] }>("POST", "/v1/skills", {
640
+ ...draft,
641
+ ...(wallet.wallet_address ? wallet : {}),
642
+ }, { timeoutMs: PUBLISH_TIMEOUT_MS });
643
+
644
+ const cascade = await ensureCascadeSplitForSkill(published).catch((err) => ({
645
+ warning: `cascade_split_failed:${(err as Error).message}`,
646
+ }));
647
+ const warnings = [...(published.warnings ?? [])];
648
+ if (cascade.warning) warnings.push(cascade.warning);
649
+
650
+ if (cascade.split_config && cascade.split_config !== published.split_config) {
651
+ const updated = await api<SkillManifest>("PATCH", `/v1/skills/${published.skill_id}`, {
652
+ split_config: cascade.split_config,
653
+ });
654
+ return {
655
+ ...updated,
656
+ warnings,
657
+ };
658
+ }
659
+
660
+ return { ...published, warnings };
600
661
  }
601
662
 
602
663
  export async function deprecateSkill(skillId: string): Promise<void> {
@@ -669,10 +730,11 @@ export async function searchIntentResolve(
669
730
  domain_results: Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
670
731
  global_results: Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
671
732
  skipped_global: boolean;
733
+ actual_cost_uc?: number;
672
734
  }> {
673
735
  if (LOCAL_ONLY) return { domain_results: [], global_results: [], skipped_global: false };
674
736
  try {
675
- return await api<{
737
+ const { data, headers } = await apiRequest<{
676
738
  domain_results: Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
677
739
  global_results: Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
678
740
  skipped_global: boolean;
@@ -682,6 +744,11 @@ export async function searchIntentResolve(
682
744
  domain_k: domainK,
683
745
  global_k: globalK,
684
746
  });
747
+ const actualCostHeader = headers.get("X-Unbrowse-Cost-Uc");
748
+ const actualCostUc = actualCostHeader && /^\d+$/.test(actualCostHeader)
749
+ ? Number(actualCostHeader)
750
+ : undefined;
751
+ return actualCostUc != null ? { ...data, actual_cost_uc: actualCostUc } : data;
685
752
  } catch (err) {
686
753
  if (isX402Error(err)) throw err;
687
754
  const [domain_results, global_results] = await Promise.all([
@@ -710,6 +777,18 @@ export interface ExecutionPayload {
710
777
  indexer_id?: string;
711
778
  }
712
779
 
780
+ export interface AnalyticsSessionPayload {
781
+ session_id: string;
782
+ started_at: string;
783
+ completed_at?: string;
784
+ trace_version?: string;
785
+ api_calls: number;
786
+ discovery_queries?: number;
787
+ cached_skill_calls?: number;
788
+ fresh_index_calls?: number;
789
+ browser_mode?: "default" | "replaced" | "manual" | "unknown";
790
+ }
791
+
713
792
  /**
714
793
  * Build the POST body for /v1/stats/execution.
715
794
  * Pure function — no I/O, fully testable.
@@ -747,11 +826,16 @@ export async function recordExecution(
747
826
  await api("POST", "/v1/stats/execution", payload);
748
827
  }
749
828
 
829
+ export async function recordAnalyticsSession(payload: AnalyticsSessionPayload): Promise<void> {
830
+ if (LOCAL_ONLY) return;
831
+ await api("POST", "/v1/analytics/sessions", payload);
832
+ }
833
+
750
834
  /** Record a payment transaction for a paid skill execution. Fire-and-forget. */
751
835
  export async function recordTransaction(params: {
752
836
  transaction_id: string;
753
837
  consumer_id: string;
754
- creator_id: string;
838
+ creator_id?: string;
755
839
  skill_id: string;
756
840
  endpoint_id?: string;
757
841
  price_usd: number;
@@ -812,6 +896,23 @@ export async function recordOrchestrationPerf(timing: OrchestrationTiming): Prom
812
896
  await api("POST", "/v1/stats/perf", { ...timing, phase_totals_ms: phaseTotals });
813
897
  }
814
898
 
899
+ // --- Analytics Sessions ---
900
+
901
+ export async function recordAnalyticsSession(session: {
902
+ session_id: string;
903
+ started_at: string;
904
+ completed_at?: string;
905
+ trace_version?: string;
906
+ api_calls: number;
907
+ discovery_queries?: number;
908
+ cached_skill_calls?: number;
909
+ fresh_index_calls?: number;
910
+ browser_mode?: "default" | "replaced" | "manual" | "unknown";
911
+ }): Promise<void> {
912
+ if (LOCAL_ONLY) return;
913
+ await api("POST", "/v1/analytics/sessions", session);
914
+ }
915
+
815
916
  // --- Validation ---
816
917
 
817
918
  export async function validateManifest(manifest: unknown): Promise<ValidationResult> {
@@ -917,11 +1018,23 @@ export async function verifyMarketplaceDiscovery(
917
1018
 
918
1019
  // --- Agent Registration ---
919
1020
 
920
- export async function registerAgent(name: string): Promise<{ agent_id: string; api_key: string }> {
921
- return api<{ agent_id: string; api_key: string }>("POST", "/v1/agents/register", { name });
1021
+ export async function registerAgent(
1022
+ name: string,
1023
+ wallet: { wallet_address?: string; wallet_provider?: string } = getLocalWalletContext(),
1024
+ ): Promise<{ agent_id: string; api_key: string }> {
1025
+ return api<{ agent_id: string; api_key: string }>("POST", "/v1/agents/register", { name, ...wallet });
922
1026
  }
923
1027
 
924
- export async function getAgent(agentId: string): Promise<{ agent_id: string; name: string; created_at: string; skills_discovered: string[]; total_executions: number; total_feedback_given: number } | null> {
1028
+ export async function getAgent(agentId: string): Promise<{
1029
+ agent_id: string;
1030
+ name: string;
1031
+ created_at: string;
1032
+ wallet_address?: string | null;
1033
+ wallet_provider?: string | null;
1034
+ skills_discovered: string[];
1035
+ total_executions: number;
1036
+ total_feedback_given: number;
1037
+ } | null> {
925
1038
  try {
926
1039
  return await api("GET", `/v1/agents/${agentId}`);
927
1040
  } catch {
@@ -929,10 +1042,27 @@ export async function getAgent(agentId: string): Promise<{ agent_id: string; nam
929
1042
  }
930
1043
  }
931
1044
 
932
- export async function getMyProfile(): Promise<{ agent_id: string; name: string; created_at: string; skills_discovered: string[]; total_executions: number; total_feedback_given: number }> {
1045
+ export async function getMyProfile(): Promise<{
1046
+ agent_id: string;
1047
+ name: string;
1048
+ created_at: string;
1049
+ wallet_address?: string | null;
1050
+ wallet_provider?: string | null;
1051
+ skills_discovered: string[];
1052
+ total_executions: number;
1053
+ total_feedback_given: number;
1054
+ }> {
933
1055
  return api("GET", "/v1/agents/me", undefined);
934
1056
  }
935
1057
 
1058
+ export async function syncAgentWallet(wallet = getLocalWalletContext()): Promise<void> {
1059
+ if (!wallet.wallet_address) return;
1060
+ await api("POST", "/v1/agents/wallet", wallet);
1061
+ const config = loadConfig();
1062
+ if (!config) return;
1063
+ saveConfig({ ...config, ...wallet });
1064
+ }
1065
+
936
1066
 
937
1067
  // --- Transaction Visibility ---
938
1068
 
@@ -989,3 +1119,7 @@ export async function getCreatorEarnings(agentId: string): Promise<{
989
1119
  export async function setSkillPrice(skillId: string, priceUsd: number): Promise<unknown> {
990
1120
  return api("PATCH", `/v1/skills/${skillId}`, { base_price_usd: priceUsd });
991
1121
  }
1122
+
1123
+ export async function setSkillSplitConfig(skillId: string, splitConfig: string | null): Promise<unknown> {
1124
+ return api("PATCH", `/v1/skills/${skillId}`, { split_config: splitConfig });
1125
+ }
@@ -6,6 +6,7 @@ import { publishSkill, mergeEndpoints } from "../marketplace/index.js";
6
6
  import { updateEndpointScore } from "../marketplace/index.js";
7
7
  import { getCredential, storeCredential, deleteCredential } from "../vault/index.js";
8
8
  import { getStoredAuth, getAuthCookies, refreshAuthFromBrowser } from "../auth/index.js";
9
+ import { resolvePreExecutionAuth } from "../auth/dependency-runtime.js";
9
10
  import { authRuntime } from "../auth/runtime.js";
10
11
  import { applyProjection, inferSchema } from "../transform/index.js";
11
12
  import { detectSchemaDrift } from "../transform/drift.js";
@@ -2284,7 +2285,7 @@ export async function executeEndpoint(
2284
2285
  recordExecution(skill.skill_id, endpoint.endpoint_id, trace, skill).catch(() => {});
2285
2286
 
2286
2287
  // Record transaction if this was a paid execution (fire-and-forget)
2287
- if (trace.success && skill.indexer_id && skill.base_price_usd && skill.base_price_usd > 0) {
2288
+ if (trace.success && options?.payment_verified === true && skill.base_price_usd && skill.base_price_usd > 0) {
2288
2289
  const consumerConfig = (() => {
2289
2290
  try { return JSON.parse(require("fs").readFileSync(require("os").homedir() + "/.unbrowse/config.json", "utf-8")); }
2290
2291
  catch { return {}; }
@@ -1737,6 +1737,7 @@ export async function resolveAndExecute(
1737
1737
  response_bytes: 0,
1738
1738
  time_saved_pct: 0,
1739
1739
  tokens_saved_pct: 0,
1740
+ actual_total_ms: 0,
1740
1741
  trace_version: TRACE_VERSION,
1741
1742
  };
1742
1743
  const decisionTrace: Record<string, unknown> = {
@@ -1781,6 +1782,7 @@ export async function resolveAndExecute(
1781
1782
  trace?: ExecutionTrace,
1782
1783
  ): OrchestrationTiming {
1783
1784
  timing.total_ms = Date.now() - t0;
1785
+ timing.actual_total_ms = timing.total_ms;
1784
1786
  timing.source = source;
1785
1787
  timing.skill_id = skillId;
1786
1788
 
@@ -1793,6 +1795,10 @@ export async function resolveAndExecute(
1793
1795
  const cost = skill?.discovery_cost;
1794
1796
  const baselineTokens = cost?.capture_tokens ?? DEFAULT_CAPTURE_TOKENS;
1795
1797
  const baselineMs = cost?.capture_ms ?? DEFAULT_CAPTURE_MS;
1798
+ const paidSearchUc = timing.paid_search_uc ?? 0;
1799
+ const paidExecutionUc = timing.paid_execution_uc ?? 0;
1800
+ const totalActualCostUc = paidSearchUc + paidExecutionUc;
1801
+ if (totalActualCostUc > 0) timing.actual_cost_uc = totalActualCostUc;
1796
1802
 
1797
1803
  // Token savings: marketplace/cache returns structured data, skipping full-page browsing
1798
1804
  if (source === "marketplace" || source === "route-cache" || source === "first-pass") {
@@ -1804,6 +1810,10 @@ export async function resolveAndExecute(
1804
1810
  ? Math.round((Math.max(0, baselineMs - timing.total_ms) / baselineMs) * 100)
1805
1811
  : 0;
1806
1812
  }
1813
+ if (cost?.capture_ms != null) {
1814
+ timing.baseline_total_ms = cost.capture_ms;
1815
+ timing.time_saved_ms = Math.max(0, cost.capture_ms - timing.total_ms);
1816
+ }
1807
1817
 
1808
1818
  // Stamp trace with token metrics so they persist in trace files
1809
1819
  if (trace) {
@@ -2935,6 +2945,7 @@ export async function resolveAndExecute(
2935
2945
  domain_results: SearchResult[];
2936
2946
  global_results: SearchResult[];
2937
2947
  skipped_global: boolean;
2948
+ actual_cost_uc?: number;
2938
2949
  };
2939
2950
  try {
2940
2951
  searchResponse = await Promise.race([
@@ -2944,7 +2955,7 @@ export async function resolveAndExecute(
2944
2955
  MARKETPLACE_DOMAIN_SEARCH_K,
2945
2956
  MARKETPLACE_GLOBAL_SEARCH_K,
2946
2957
  ),
2947
- new Promise<{ domain_results: SearchResult[]; global_results: SearchResult[]; skipped_global: boolean }>((resolve) =>
2958
+ new Promise<{ domain_results: SearchResult[]; global_results: SearchResult[]; skipped_global: boolean; actual_cost_uc?: number }>((resolve) =>
2948
2959
  setTimeout(() => {
2949
2960
  console.log(`[marketplace] timeout after ${MARKETPLACE_TIMEOUT_MS}ms — falling through to browser`);
2950
2961
  resolve({ domain_results: [], global_results: [], skipped_global: true });
@@ -2986,6 +2997,9 @@ export async function resolveAndExecute(
2986
2997
  skipped_global: false,
2987
2998
  };
2988
2999
  }
3000
+ if (typeof searchResponse.actual_cost_uc === "number" && searchResponse.actual_cost_uc > 0) {
3001
+ timing.paid_search_uc = searchResponse.actual_cost_uc;
3002
+ }
2989
3003
  const { domain_results: domainResults, global_results: globalResults } = searchResponse;
2990
3004
  timing.search_ms = Date.now() - ts0;
2991
3005
  console.log(`[marketplace] search: ${domainResults.length} domain + ${globalResults.length} global results (${timing.search_ms}ms)`);