unbrowse 2.9.0 → 2.9.1
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 +130 -16
- package/package.json +1 -1
- package/runtime-src/api/routes.ts +3 -1
- package/runtime-src/client/index.ts +14 -3
- package/runtime-src/kuri/client.ts +68 -2
- package/runtime-src/orchestrator/index.ts +57 -19
package/dist/cli.js
CHANGED
|
@@ -501,11 +501,75 @@ async function getCookies(tabId) {
|
|
|
501
501
|
const raw = await kuriGet("/cookies", { tab_id: tabId });
|
|
502
502
|
return raw?.result?.cookies ?? [];
|
|
503
503
|
}
|
|
504
|
+
async function setCookieViaCDP(wsUrl, cookie) {
|
|
505
|
+
return new Promise((resolve) => {
|
|
506
|
+
const timer = setTimeout(() => {
|
|
507
|
+
resolve(false);
|
|
508
|
+
}, 3000);
|
|
509
|
+
try {
|
|
510
|
+
const ws = new (__require("ws"))(wsUrl);
|
|
511
|
+
ws.on("open", () => {
|
|
512
|
+
ws.send(JSON.stringify({
|
|
513
|
+
id: 1,
|
|
514
|
+
method: "Network.setCookie",
|
|
515
|
+
params: {
|
|
516
|
+
...cookie,
|
|
517
|
+
url: `https://${cookie.domain.replace(/^\./, "")}/`
|
|
518
|
+
}
|
|
519
|
+
}));
|
|
520
|
+
});
|
|
521
|
+
ws.on("message", (data) => {
|
|
522
|
+
clearTimeout(timer);
|
|
523
|
+
try {
|
|
524
|
+
const msg = JSON.parse(data.toString());
|
|
525
|
+
if (msg.id === 1) {
|
|
526
|
+
ws.close();
|
|
527
|
+
resolve(msg.result?.success ?? false);
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
ws.close();
|
|
531
|
+
resolve(false);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
ws.on("error", () => {
|
|
535
|
+
clearTimeout(timer);
|
|
536
|
+
resolve(false);
|
|
537
|
+
});
|
|
538
|
+
} catch {
|
|
539
|
+
clearTimeout(timer);
|
|
540
|
+
resolve(false);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
504
544
|
async function setCookie(tabId, cookie) {
|
|
545
|
+
const value = cookie.value.replace(/^"|"$/g, "");
|
|
546
|
+
if (cookie.secure || cookie.httpOnly) {
|
|
547
|
+
try {
|
|
548
|
+
const res = await fetch("http://127.0.0.1:9222/json", { signal: AbortSignal.timeout(1000) }).catch(() => null);
|
|
549
|
+
if (res?.ok) {
|
|
550
|
+
const pages = await res.json();
|
|
551
|
+
const page = pages.find((p) => p.id === tabId);
|
|
552
|
+
if (page?.webSocketDebuggerUrl) {
|
|
553
|
+
const success = await setCookieViaCDP(page.webSocketDebuggerUrl, {
|
|
554
|
+
name: cookie.name,
|
|
555
|
+
value,
|
|
556
|
+
domain: cookie.domain,
|
|
557
|
+
path: cookie.path || "/",
|
|
558
|
+
secure: cookie.secure ?? false,
|
|
559
|
+
httpOnly: cookie.httpOnly ?? false,
|
|
560
|
+
sameSite: cookie.sameSite || "Lax",
|
|
561
|
+
...cookie.expires && cookie.expires > 0 ? { expires: cookie.expires } : {}
|
|
562
|
+
});
|
|
563
|
+
if (success)
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
} catch {}
|
|
568
|
+
}
|
|
505
569
|
await kuriGet("/cookies", {
|
|
506
570
|
tab_id: tabId,
|
|
507
571
|
name: cookie.name,
|
|
508
|
-
value
|
|
572
|
+
value,
|
|
509
573
|
domain: cookie.domain,
|
|
510
574
|
...cookie.path ? { path: cookie.path } : {}
|
|
511
575
|
});
|
|
@@ -4596,6 +4660,7 @@ __export(exports_client2, {
|
|
|
4596
4660
|
publishGraphEdges: () => publishGraphEdges,
|
|
4597
4661
|
normalizeAgentEmail: () => normalizeAgentEmail2,
|
|
4598
4662
|
listSkills: () => listSkills,
|
|
4663
|
+
isX402Error: () => isX402Error,
|
|
4599
4664
|
isValidAgentEmail: () => isValidAgentEmail2,
|
|
4600
4665
|
isLocalOnlyMode: () => isLocalOnlyMode,
|
|
4601
4666
|
hashApiKey: () => hashApiKey,
|
|
@@ -4638,6 +4703,9 @@ function decodeBase64Json2(value) {
|
|
|
4638
4703
|
return;
|
|
4639
4704
|
}
|
|
4640
4705
|
}
|
|
4706
|
+
function isX402Error(err) {
|
|
4707
|
+
return !!err && typeof err === "object" && err.x402 === true;
|
|
4708
|
+
}
|
|
4641
4709
|
function scopedSkillKey(skillId, scopeId) {
|
|
4642
4710
|
return scopeId ? `${scopeId}:${skillId}` : skillId;
|
|
4643
4711
|
}
|
|
@@ -5142,10 +5210,20 @@ async function searchIntentResolve(intent, domain, domainK = 5, globalK = 10) {
|
|
|
5142
5210
|
domain_k: domainK,
|
|
5143
5211
|
global_k: globalK
|
|
5144
5212
|
});
|
|
5145
|
-
} catch {
|
|
5213
|
+
} catch (err) {
|
|
5214
|
+
if (isX402Error(err))
|
|
5215
|
+
throw err;
|
|
5146
5216
|
const [domain_results, global_results] = await Promise.all([
|
|
5147
|
-
domain ? searchIntentInDomain(intent, domain, domainK).catch(() =>
|
|
5148
|
-
|
|
5217
|
+
domain ? searchIntentInDomain(intent, domain, domainK).catch((fallbackErr) => {
|
|
5218
|
+
if (isX402Error(fallbackErr))
|
|
5219
|
+
throw fallbackErr;
|
|
5220
|
+
return [];
|
|
5221
|
+
}) : Promise.resolve([]),
|
|
5222
|
+
searchIntent(intent, globalK).catch((fallbackErr) => {
|
|
5223
|
+
if (isX402Error(fallbackErr))
|
|
5224
|
+
throw fallbackErr;
|
|
5225
|
+
return [];
|
|
5226
|
+
})
|
|
5149
5227
|
]);
|
|
5150
5228
|
return { domain_results, global_results, skipped_global: false };
|
|
5151
5229
|
}
|
|
@@ -15053,17 +15131,51 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
|
|
|
15053
15131
|
const MARKETPLACE_TIMEOUT_MS = context?.url ? 5000 : 30000;
|
|
15054
15132
|
if (!forceCapture) {
|
|
15055
15133
|
const ts0 = Date.now();
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
resolve
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15134
|
+
let searchResponse;
|
|
15135
|
+
try {
|
|
15136
|
+
searchResponse = await Promise.race([
|
|
15137
|
+
searchIntentResolve(queryIntent, requestedDomain ?? undefined, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K),
|
|
15138
|
+
new Promise((resolve) => setTimeout(() => {
|
|
15139
|
+
console.log(`[marketplace] timeout after ${MARKETPLACE_TIMEOUT_MS}ms — falling through to browser`);
|
|
15140
|
+
resolve({ domain_results: [], global_results: [], skipped_global: true });
|
|
15141
|
+
}, MARKETPLACE_TIMEOUT_MS))
|
|
15142
|
+
]);
|
|
15143
|
+
} catch (err) {
|
|
15144
|
+
if (isX402Error(err)) {
|
|
15145
|
+
const trace2 = {
|
|
15146
|
+
trace_id: nanoid7(),
|
|
15147
|
+
skill_id: "marketplace-search",
|
|
15148
|
+
endpoint_id: "search",
|
|
15149
|
+
started_at: new Date().toISOString(),
|
|
15150
|
+
completed_at: new Date().toISOString(),
|
|
15151
|
+
success: false,
|
|
15152
|
+
status_code: 402,
|
|
15153
|
+
error: "payment_required"
|
|
15154
|
+
};
|
|
15155
|
+
return {
|
|
15156
|
+
result: {
|
|
15157
|
+
error: "payment_required",
|
|
15158
|
+
payment_status: "payment_required",
|
|
15159
|
+
wallet_provider: "lobster.cash",
|
|
15160
|
+
message: "Marketplace search requires payment before shared graph results are returned.",
|
|
15161
|
+
next_step: "Pay the Tier 3 search fee, or re-run with force capture for free local discovery.",
|
|
15162
|
+
indexing_fallback_available: true,
|
|
15163
|
+
tier: "tier3",
|
|
15164
|
+
terms: err.terms
|
|
15165
|
+
},
|
|
15166
|
+
trace: trace2,
|
|
15167
|
+
source: "marketplace",
|
|
15168
|
+
skill: undefined,
|
|
15169
|
+
timing: finalize("marketplace", null, undefined, undefined, trace2)
|
|
15170
|
+
};
|
|
15171
|
+
}
|
|
15172
|
+
searchResponse = {
|
|
15173
|
+
domain_results: [],
|
|
15174
|
+
global_results: [],
|
|
15175
|
+
skipped_global: false
|
|
15176
|
+
};
|
|
15177
|
+
}
|
|
15178
|
+
const { domain_results: domainResults, global_results: globalResults } = searchResponse;
|
|
15067
15179
|
timing.search_ms = Date.now() - ts0;
|
|
15068
15180
|
console.log(`[marketplace] search: ${domainResults.length} domain + ${globalResults.length} global results (${timing.search_ms}ms)`);
|
|
15069
15181
|
const seen = new Set;
|
|
@@ -16995,6 +17107,7 @@ async function registerRoutes(app) {
|
|
|
16995
17107
|
if (session.domain && session.domain !== newDomain) {
|
|
16996
17108
|
await authProfileSave(session.tabId, session.domain).catch(() => {});
|
|
16997
17109
|
}
|
|
17110
|
+
let cookiesInjected = 0;
|
|
16998
17111
|
if (newDomain && newDomain !== session.domain) {
|
|
16999
17112
|
await authProfileLoad(session.tabId, newDomain).catch(() => {});
|
|
17000
17113
|
try {
|
|
@@ -17003,6 +17116,7 @@ async function registerRoutes(app) {
|
|
|
17003
17116
|
for (const c of browserCookies) {
|
|
17004
17117
|
await setCookie(session.tabId, c).catch(() => {});
|
|
17005
17118
|
}
|
|
17119
|
+
cookiesInjected = browserCookies.length;
|
|
17006
17120
|
}
|
|
17007
17121
|
} catch {}
|
|
17008
17122
|
}
|
|
@@ -17015,7 +17129,7 @@ async function registerRoutes(app) {
|
|
|
17015
17129
|
session.url = typeof finalUrl === "string" && finalUrl.startsWith("http") ? finalUrl : url;
|
|
17016
17130
|
session.domain = profileName(session.url);
|
|
17017
17131
|
await injectInterceptor(session.tabId);
|
|
17018
|
-
return reply.send({ ok: true, url: session.url, tab_id: session.tabId, auth_profile: session.domain });
|
|
17132
|
+
return reply.send({ ok: true, url: session.url, tab_id: session.tabId, auth_profile: session.domain, ...cookiesInjected > 0 ? { cookies_injected: cookiesInjected } : {} });
|
|
17019
17133
|
});
|
|
17020
17134
|
app.post("/v1/browse/snap", async (req, reply) => {
|
|
17021
17135
|
const { filter } = req.body ?? {};
|
package/package.json
CHANGED
|
@@ -845,6 +845,7 @@ export async function registerRoutes(app: FastifyInstance) {
|
|
|
845
845
|
}
|
|
846
846
|
|
|
847
847
|
// Inject cookies: try Kuri auth profile first, fall back to Chrome SQLite extraction
|
|
848
|
+
let cookiesInjected = 0;
|
|
848
849
|
if (newDomain && newDomain !== session.domain) {
|
|
849
850
|
await kuri.authProfileLoad(session.tabId, newDomain).catch(() => {});
|
|
850
851
|
|
|
@@ -855,6 +856,7 @@ export async function registerRoutes(app: FastifyInstance) {
|
|
|
855
856
|
for (const c of browserCookies) {
|
|
856
857
|
await kuri.setCookie(session.tabId, c).catch(() => {});
|
|
857
858
|
}
|
|
859
|
+
cookiesInjected = browserCookies.length;
|
|
858
860
|
}
|
|
859
861
|
} catch { /* non-fatal */ }
|
|
860
862
|
}
|
|
@@ -872,7 +874,7 @@ export async function registerRoutes(app: FastifyInstance) {
|
|
|
872
874
|
// Re-inject interceptor via evaluate for current page context
|
|
873
875
|
await injectInterceptor(session.tabId); // chunked injection for current page
|
|
874
876
|
|
|
875
|
-
return reply.send({ ok: true, url: session.url, tab_id: session.tabId, auth_profile: session.domain });
|
|
877
|
+
return reply.send({ ok: true, url: session.url, tab_id: session.tabId, auth_profile: session.domain, ...(cookiesInjected > 0 ? { cookies_injected: cookiesInjected } : {}) });
|
|
876
878
|
});
|
|
877
879
|
|
|
878
880
|
// POST /v1/browse/snap — a11y snapshot
|
|
@@ -28,6 +28,10 @@ function decodeBase64Json(value: string): unknown {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export function isX402Error(err: unknown): err is Error & { x402: true; terms?: unknown; status?: number } {
|
|
32
|
+
return !!err && typeof err === "object" && (err as { x402?: unknown }).x402 === true;
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
function scopedSkillKey(skillId: string, scopeId?: string): string {
|
|
32
36
|
return scopeId ? `${scopeId}:${skillId}` : skillId;
|
|
33
37
|
}
|
|
@@ -678,12 +682,19 @@ export async function searchIntentResolve(
|
|
|
678
682
|
domain_k: domainK,
|
|
679
683
|
global_k: globalK,
|
|
680
684
|
});
|
|
681
|
-
} catch {
|
|
685
|
+
} catch (err) {
|
|
686
|
+
if (isX402Error(err)) throw err;
|
|
682
687
|
const [domain_results, global_results] = await Promise.all([
|
|
683
688
|
domain
|
|
684
|
-
? searchIntentInDomain(intent, domain, domainK).catch(() =>
|
|
689
|
+
? searchIntentInDomain(intent, domain, domainK).catch((fallbackErr) => {
|
|
690
|
+
if (isX402Error(fallbackErr)) throw fallbackErr;
|
|
691
|
+
return [] as Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
|
|
692
|
+
})
|
|
685
693
|
: Promise.resolve([] as Array<{ id: number; score: number; metadata: Record<string, unknown> }>),
|
|
686
|
-
searchIntent(intent, globalK).catch(() =>
|
|
694
|
+
searchIntent(intent, globalK).catch((fallbackErr) => {
|
|
695
|
+
if (isX402Error(fallbackErr)) throw fallbackErr;
|
|
696
|
+
return [] as Array<{ id: number; score: number; metadata: Record<string, unknown> }>;
|
|
697
|
+
}),
|
|
687
698
|
]);
|
|
688
699
|
return { domain_results, global_results, skipped_global: false };
|
|
689
700
|
}
|
|
@@ -526,12 +526,78 @@ export async function getCookies(tabId: string): Promise<KuriCookie[]> {
|
|
|
526
526
|
return raw?.result?.cookies ?? [];
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
-
/** Set a
|
|
529
|
+
/** Set a cookie via raw CDP WebSocket — supports all cookie attributes (secure, httpOnly, sameSite, expires). */
|
|
530
|
+
async function setCookieViaCDP(wsUrl: string, cookie: {
|
|
531
|
+
name: string; value: string; domain: string; path: string;
|
|
532
|
+
secure: boolean; httpOnly: boolean; sameSite: string; expires?: number;
|
|
533
|
+
}): Promise<boolean> {
|
|
534
|
+
return new Promise((resolve) => {
|
|
535
|
+
const timer = setTimeout(() => { resolve(false); }, 3000);
|
|
536
|
+
try {
|
|
537
|
+
const ws = new (require("ws") as typeof import("ws"))(wsUrl);
|
|
538
|
+
ws.on("open", () => {
|
|
539
|
+
ws.send(JSON.stringify({
|
|
540
|
+
id: 1,
|
|
541
|
+
method: "Network.setCookie",
|
|
542
|
+
params: {
|
|
543
|
+
...cookie,
|
|
544
|
+
url: `https://${cookie.domain.replace(/^\./, "")}/`,
|
|
545
|
+
},
|
|
546
|
+
}));
|
|
547
|
+
});
|
|
548
|
+
ws.on("message", (data: Buffer) => {
|
|
549
|
+
clearTimeout(timer);
|
|
550
|
+
try {
|
|
551
|
+
const msg = JSON.parse(data.toString());
|
|
552
|
+
if (msg.id === 1) {
|
|
553
|
+
ws.close();
|
|
554
|
+
resolve(msg.result?.success ?? false);
|
|
555
|
+
}
|
|
556
|
+
} catch { ws.close(); resolve(false); }
|
|
557
|
+
});
|
|
558
|
+
ws.on("error", () => { clearTimeout(timer); resolve(false); });
|
|
559
|
+
} catch { clearTimeout(timer); resolve(false); }
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** Set a single cookie via raw CDP (Chrome debug port) for full attribute support.
|
|
564
|
+
* Falls back to Kuri's /cookies endpoint if CDP is unavailable. */
|
|
565
|
+
/** Set a single cookie via raw CDP (Chrome debug port) for full attribute support.
|
|
566
|
+
* Falls back to Kuri's /cookies endpoint if CDP is unavailable. */
|
|
530
567
|
export async function setCookie(tabId: string, cookie: KuriCookie): Promise<void> {
|
|
568
|
+
// Strip wrapping quotes from cookie values (Chrome stores some values like JSESSIONID with literal quotes)
|
|
569
|
+
const value = cookie.value.replace(/^"|"$/g, "");
|
|
570
|
+
|
|
571
|
+
// Try raw CDP first — Kuri's /cookies endpoint doesn't pass secure/httpOnly/sameSite/expires
|
|
572
|
+
// which causes auth failures on sites like LinkedIn that require secure cookies.
|
|
573
|
+
if (cookie.secure || cookie.httpOnly) {
|
|
574
|
+
try {
|
|
575
|
+
const res = await fetch("http://127.0.0.1:9222/json", { signal: AbortSignal.timeout(1000) }).catch(() => null);
|
|
576
|
+
if (res?.ok) {
|
|
577
|
+
const pages = await res.json() as Array<{ id: string; webSocketDebuggerUrl?: string }>;
|
|
578
|
+
const page = pages.find(p => p.id === tabId);
|
|
579
|
+
if (page?.webSocketDebuggerUrl) {
|
|
580
|
+
const success = await setCookieViaCDP(page.webSocketDebuggerUrl, {
|
|
581
|
+
name: cookie.name,
|
|
582
|
+
value,
|
|
583
|
+
domain: cookie.domain,
|
|
584
|
+
path: cookie.path || "/",
|
|
585
|
+
secure: cookie.secure ?? false,
|
|
586
|
+
httpOnly: cookie.httpOnly ?? false,
|
|
587
|
+
sameSite: cookie.sameSite || "Lax",
|
|
588
|
+
...(cookie.expires && cookie.expires > 0 ? { expires: cookie.expires } : {}),
|
|
589
|
+
});
|
|
590
|
+
if (success) return;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
} catch { /* CDP unavailable, fall through to Kuri */ }
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Fallback: Kuri's /cookies endpoint (no secure/httpOnly support)
|
|
531
597
|
await kuriGet("/cookies", {
|
|
532
598
|
tab_id: tabId,
|
|
533
599
|
name: cookie.name,
|
|
534
|
-
value
|
|
600
|
+
value,
|
|
535
601
|
domain: cookie.domain,
|
|
536
602
|
...(cookie.path ? { path: cookie.path } : {}),
|
|
537
603
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { searchIntentResolve, recordOrchestrationPerf } from "../client/index.js";
|
|
1
|
+
import { isX402Error, searchIntentResolve, recordOrchestrationPerf } from "../client/index.js";
|
|
2
2
|
import * as kuri from "../kuri/client.js";
|
|
3
3
|
import { emitRouteTrace, recordFailure } from "../telemetry.js";
|
|
4
4
|
import { publishSkill, getSkill } from "../marketplace/index.js";
|
|
@@ -2931,24 +2931,62 @@ export async function resolveAndExecute(
|
|
|
2931
2931
|
// 1. Search marketplace — single remote call, capped by timeout when URL available
|
|
2932
2932
|
const ts0 = Date.now();
|
|
2933
2933
|
type SearchResult = { id: number; score: number; metadata: Record<string, unknown> };
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2934
|
+
let searchResponse: {
|
|
2935
|
+
domain_results: SearchResult[];
|
|
2936
|
+
global_results: SearchResult[];
|
|
2937
|
+
skipped_global: boolean;
|
|
2938
|
+
};
|
|
2939
|
+
try {
|
|
2940
|
+
searchResponse = await Promise.race([
|
|
2941
|
+
searchIntentResolve(
|
|
2942
|
+
queryIntent,
|
|
2943
|
+
requestedDomain ?? undefined,
|
|
2944
|
+
MARKETPLACE_DOMAIN_SEARCH_K,
|
|
2945
|
+
MARKETPLACE_GLOBAL_SEARCH_K,
|
|
2946
|
+
),
|
|
2947
|
+
new Promise<{ domain_results: SearchResult[]; global_results: SearchResult[]; skipped_global: boolean }>((resolve) =>
|
|
2948
|
+
setTimeout(() => {
|
|
2949
|
+
console.log(`[marketplace] timeout after ${MARKETPLACE_TIMEOUT_MS}ms — falling through to browser`);
|
|
2950
|
+
resolve({ domain_results: [], global_results: [], skipped_global: true });
|
|
2951
|
+
}, MARKETPLACE_TIMEOUT_MS),
|
|
2952
|
+
),
|
|
2953
|
+
]);
|
|
2954
|
+
} catch (err) {
|
|
2955
|
+
if (isX402Error(err)) {
|
|
2956
|
+
const trace: ExecutionTrace = {
|
|
2957
|
+
trace_id: nanoid(),
|
|
2958
|
+
skill_id: "marketplace-search",
|
|
2959
|
+
endpoint_id: "search",
|
|
2960
|
+
started_at: new Date().toISOString(),
|
|
2961
|
+
completed_at: new Date().toISOString(),
|
|
2962
|
+
success: false,
|
|
2963
|
+
status_code: 402,
|
|
2964
|
+
error: "payment_required",
|
|
2965
|
+
};
|
|
2966
|
+
return {
|
|
2967
|
+
result: {
|
|
2968
|
+
error: "payment_required",
|
|
2969
|
+
payment_status: "payment_required",
|
|
2970
|
+
wallet_provider: "lobster.cash",
|
|
2971
|
+
message: "Marketplace search requires payment before shared graph results are returned.",
|
|
2972
|
+
next_step: "Pay the Tier 3 search fee, or re-run with force capture for free local discovery.",
|
|
2973
|
+
indexing_fallback_available: true,
|
|
2974
|
+
tier: "tier3",
|
|
2975
|
+
terms: err.terms,
|
|
2976
|
+
},
|
|
2977
|
+
trace,
|
|
2978
|
+
source: "marketplace",
|
|
2979
|
+
skill: undefined as any,
|
|
2980
|
+
timing: finalize("marketplace", null, undefined, undefined, trace),
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
searchResponse = {
|
|
2984
|
+
domain_results: [] as SearchResult[],
|
|
2985
|
+
global_results: [] as SearchResult[],
|
|
2986
|
+
skipped_global: false,
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
const { domain_results: domainResults, global_results: globalResults } = searchResponse;
|
|
2952
2990
|
timing.search_ms = Date.now() - ts0;
|
|
2953
2991
|
console.log(`[marketplace] search: ${domainResults.length} domain + ${globalResults.length} global results (${timing.search_ms}ms)`);
|
|
2954
2992
|
|