unbrowse 3.1.0 → 3.2.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 +455 -96
- package/dist/index.js +2 -6
- package/dist/mcp.js +695 -46
- package/dist/server.js +25811 -0
- package/package.json +1 -2
- package/vendor/kuri/darwin-arm64/kuri +0 -0
- package/vendor/kuri/darwin-x64/kuri +0 -0
- package/vendor/kuri/linux-arm64/kuri +0 -0
- package/vendor/kuri/linux-x64/kuri +0 -0
- package/vendor/kuri/manifest.json +7 -10
- package/runtime-src/agent-outcome.ts +0 -166
- package/runtime-src/analytics-session.ts +0 -55
- package/runtime-src/api/browse-index.ts +0 -317
- package/runtime-src/api/browse-session.ts +0 -572
- package/runtime-src/api/browse-submit-prereqs.ts +0 -48
- package/runtime-src/api/browse-submit.ts +0 -1184
- package/runtime-src/api/routes.ts +0 -1823
- package/runtime-src/auth/browser-cookies.ts +0 -423
- package/runtime-src/auth/index.ts +0 -535
- package/runtime-src/auth/runtime.ts +0 -116
- package/runtime-src/browser/index.ts +0 -659
- package/runtime-src/browser/types.ts +0 -41
- package/runtime-src/build-info.generated.ts +0 -6
- package/runtime-src/capture/index.ts +0 -1794
- package/runtime-src/capture/prefetch.ts +0 -95
- package/runtime-src/capture/rsc.ts +0 -45
- package/runtime-src/cli/shortcuts.ts +0 -273
- package/runtime-src/cli.ts +0 -1572
- package/runtime-src/client/graph-client.ts +0 -100
- package/runtime-src/client/index.ts +0 -1425
- package/runtime-src/debug-trace.ts +0 -18
- package/runtime-src/domain.ts +0 -38
- package/runtime-src/execution/index.ts +0 -3397
- package/runtime-src/execution/retry.ts +0 -46
- package/runtime-src/execution/robots.ts +0 -167
- package/runtime-src/execution/search-forms.ts +0 -188
- package/runtime-src/extraction/index.ts +0 -1507
- package/runtime-src/foundry/publish-bundle.ts +0 -392
- package/runtime-src/graph/agent-augment.ts +0 -315
- package/runtime-src/graph/index.ts +0 -1524
- package/runtime-src/graph/local-fixtures.ts +0 -393
- package/runtime-src/graph/local-harness.ts +0 -646
- package/runtime-src/graph/planner.ts +0 -411
- package/runtime-src/graph/session.ts +0 -294
- package/runtime-src/graph/trace-store.ts +0 -136
- package/runtime-src/index.ts +0 -24
- package/runtime-src/indexer/index.ts +0 -465
- package/runtime-src/intent-match.ts +0 -1515
- package/runtime-src/kuri/client.ts +0 -1839
- package/runtime-src/logger.ts +0 -30
- package/runtime-src/marketplace/index.ts +0 -103
- package/runtime-src/mcp.ts +0 -1747
- package/runtime-src/orchestrator/browser-agent.ts +0 -374
- package/runtime-src/orchestrator/dag-advisor.ts +0 -59
- package/runtime-src/orchestrator/dag-feedback.ts +0 -257
- package/runtime-src/orchestrator/first-pass-action.ts +0 -403
- package/runtime-src/orchestrator/index.ts +0 -4480
- package/runtime-src/orchestrator/passive-publish.ts +0 -187
- package/runtime-src/orchestrator/timing-economics.ts +0 -80
- package/runtime-src/payments/cascade.ts +0 -137
- package/runtime-src/payments/index.ts +0 -270
- package/runtime-src/payments/lobster-pay.ts +0 -182
- package/runtime-src/payments/wallet.ts +0 -98
- package/runtime-src/publish/review-context.ts +0 -93
- package/runtime-src/publish/sanitize.ts +0 -197
- package/runtime-src/publish/schema-review.ts +0 -192
- package/runtime-src/publish-admission.ts +0 -388
- package/runtime-src/ratelimit/index.ts +0 -23
- package/runtime-src/reverse-engineer/bundle-scanner.ts +0 -127
- package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
- package/runtime-src/reverse-engineer/index.ts +0 -1551
- package/runtime-src/router.ts +0 -17
- package/runtime-src/routing-telemetry.ts +0 -395
- package/runtime-src/runtime/browser-access.ts +0 -11
- package/runtime-src/runtime/browser-auth.ts +0 -12
- package/runtime-src/runtime/browser-host.ts +0 -48
- package/runtime-src/runtime/lifecycle.ts +0 -17
- package/runtime-src/runtime/local-server.ts +0 -311
- package/runtime-src/runtime/paths.ts +0 -99
- package/runtime-src/runtime/setup.ts +0 -251
- package/runtime-src/runtime/supervisor.ts +0 -69
- package/runtime-src/runtime/update-hints.ts +0 -351
- package/runtime-src/server.ts +0 -100
- package/runtime-src/session-logs.ts +0 -142
- package/runtime-src/settings.ts +0 -221
- package/runtime-src/single-binary.ts +0 -143
- package/runtime-src/site-policy.ts +0 -54
- package/runtime-src/stale-cleanup-runner.ts +0 -144
- package/runtime-src/stale-cleanup.ts +0 -133
- package/runtime-src/telemetry-attribution.ts +0 -120
- package/runtime-src/telemetry.ts +0 -253
- package/runtime-src/template-params.ts +0 -141
- package/runtime-src/transform/drift.ts +0 -60
- package/runtime-src/transform/index.ts +0 -277
- package/runtime-src/types/index.ts +0 -1
- package/runtime-src/types/skill.ts +0 -912
- package/runtime-src/vault/index.ts +0 -196
- package/runtime-src/verification/auth-gate.ts +0 -8
- package/runtime-src/verification/candidates.ts +0 -27
- package/runtime-src/verification/index.ts +0 -120
- package/runtime-src/verification/matrix.ts +0 -30
- package/runtime-src/version.ts +0 -148
- package/runtime-src/workflow/artifact.ts +0 -161
- package/runtime-src/workflow/compile.ts +0 -808
- package/runtime-src/workflow/publish.ts +0 -225
- package/runtime-src/workflow/runtime.ts +0 -213
- package/vendor/kuri/win-x64/kuri.exe +0 -0
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
import * as kuri from "../kuri/client.js";
|
|
2
|
-
import type { KuriHarEntry } from "../kuri/client.js";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
import type { EndpointDescriptor, SkillManifest } from "../types/skill.js";
|
|
5
|
-
|
|
6
|
-
export type IntentClass = "search" | "navigate" | "click" | "submit" | "read" | "unknown";
|
|
7
|
-
|
|
8
|
-
export interface FirstPassResult {
|
|
9
|
-
intentClass: IntentClass;
|
|
10
|
-
actionTaken: string;
|
|
11
|
-
hit: boolean;
|
|
12
|
-
interceptedEntries: KuriHarEntry[];
|
|
13
|
-
miniSkill?: SkillManifest;
|
|
14
|
-
result?: unknown;
|
|
15
|
-
timeMs: number;
|
|
16
|
-
/** Tab ID kept alive on miss for Phase 4 agentic loop to reuse */
|
|
17
|
-
tabId?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface FirstPassOptions {
|
|
21
|
-
signal?: AbortSignal;
|
|
22
|
-
clientScope?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Classify an intent string into an action category. */
|
|
26
|
-
export function classifyIntent(intent: string): IntentClass {
|
|
27
|
-
if (/\b(search|find|discover|look\s+for|browse)\b/i.test(intent)) return "search";
|
|
28
|
-
if (/\b(go\s+to|open|visit|navigate)\b/i.test(intent)) return "navigate";
|
|
29
|
-
if (/\b(click|tap|press|select)\b/i.test(intent)) return "click";
|
|
30
|
-
if (/\b(submit|register|sign\s+up|rsvp|book|apply)\b/i.test(intent)) return "submit";
|
|
31
|
-
if (/\b(read|get|fetch|show|list|view)\b/i.test(intent)) return "read";
|
|
32
|
-
return "unknown";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Map an intent class to a browser action descriptor. */
|
|
36
|
-
export function classifyAction(
|
|
37
|
-
intentClass: IntentClass,
|
|
38
|
-
_url: string,
|
|
39
|
-
): { type: "search" | "click" | "navigate" | "wait"; selector?: string } {
|
|
40
|
-
switch (intentClass) {
|
|
41
|
-
case "search":
|
|
42
|
-
return {
|
|
43
|
-
type: "search",
|
|
44
|
-
selector: "input[type=search],input[name=q],input[name=query],input[name=search]",
|
|
45
|
-
};
|
|
46
|
-
case "submit":
|
|
47
|
-
return {
|
|
48
|
-
type: "click",
|
|
49
|
-
selector: "button[type=submit],input[type=submit],[role=button]",
|
|
50
|
-
};
|
|
51
|
-
case "click":
|
|
52
|
-
return { type: "click", selector: "[role=button],button,a" };
|
|
53
|
-
case "navigate":
|
|
54
|
-
case "read":
|
|
55
|
-
case "unknown":
|
|
56
|
-
default:
|
|
57
|
-
return { type: "navigate" };
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Filter HAR entries to only JSON API responses with 2xx status. */
|
|
62
|
-
function filterJsonApiEntries(entries: KuriHarEntry[]): KuriHarEntry[] {
|
|
63
|
-
return entries.filter((entry) => {
|
|
64
|
-
const status = entry.response?.status;
|
|
65
|
-
if (!status || status < 200 || status > 299) return false;
|
|
66
|
-
const ct =
|
|
67
|
-
(entry.response.headers ?? []).find(
|
|
68
|
-
(h) => h.name.toLowerCase() === "content-type",
|
|
69
|
-
)?.value ?? "";
|
|
70
|
-
return ct.includes("application/json") || ct.includes("+json");
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Build a lightweight skill manifest from intercepted HAR entries. */
|
|
75
|
-
export function synthesizeSkillFromIntercepted(
|
|
76
|
-
interceptedEntries: KuriHarEntry[],
|
|
77
|
-
domain: string,
|
|
78
|
-
intent: string,
|
|
79
|
-
): SkillManifest | undefined {
|
|
80
|
-
if (interceptedEntries.length === 0) return undefined;
|
|
81
|
-
|
|
82
|
-
const hash = createHash("sha1")
|
|
83
|
-
.update(interceptedEntries.map((e) => e.request.url).join("|"))
|
|
84
|
-
.digest("hex")
|
|
85
|
-
.slice(0, 8);
|
|
86
|
-
|
|
87
|
-
const endpoints: EndpointDescriptor[] = interceptedEntries.map((entry, i) => {
|
|
88
|
-
const parsed = new URL(entry.request.url);
|
|
89
|
-
return {
|
|
90
|
-
endpoint_id: `fp-ep-${i}`,
|
|
91
|
-
method: entry.request.method.toUpperCase() as EndpointDescriptor["method"],
|
|
92
|
-
url_template: parsed.origin + parsed.pathname,
|
|
93
|
-
idempotency: "safe" as const,
|
|
94
|
-
verification_status: "unverified" as const,
|
|
95
|
-
reliability_score: 0.5,
|
|
96
|
-
description: `First-pass intercepted endpoint for intent: ${intent}`,
|
|
97
|
-
};
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
skill_id: `first-pass-${domain}-${hash}`,
|
|
102
|
-
version: "0.0.1",
|
|
103
|
-
schema_version: "2",
|
|
104
|
-
name: `First-pass: ${domain}`,
|
|
105
|
-
intent_signature: intent,
|
|
106
|
-
domain,
|
|
107
|
-
description: "Lightweight first-pass skill synthesized from intercepted network requests",
|
|
108
|
-
owner_type: "agent",
|
|
109
|
-
execution_type: "http",
|
|
110
|
-
lifecycle: "active",
|
|
111
|
-
endpoints,
|
|
112
|
-
created_at: new Date().toISOString(),
|
|
113
|
-
updated_at: new Date().toISOString(),
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Lightweight first-pass browser action: navigate to contextUrl, optionally
|
|
119
|
-
* perform an intent-driven action (search / click), collect intercepted
|
|
120
|
-
* JSON API requests via HAR recording, and return them for passive indexing.
|
|
121
|
-
*
|
|
122
|
-
* Designed to complete within ~5-8s. On any error, returns hit:false
|
|
123
|
-
* and never throws.
|
|
124
|
-
*/
|
|
125
|
-
export async function tryFirstPassBrowserAction(
|
|
126
|
-
intent: string,
|
|
127
|
-
params: Record<string, unknown>,
|
|
128
|
-
contextUrl: string,
|
|
129
|
-
options?: FirstPassOptions,
|
|
130
|
-
): Promise<FirstPassResult> {
|
|
131
|
-
const t0 = Date.now();
|
|
132
|
-
const intentClass = classifyIntent(intent);
|
|
133
|
-
const action = classifyAction(intentClass, contextUrl);
|
|
134
|
-
|
|
135
|
-
let domain = "";
|
|
136
|
-
try {
|
|
137
|
-
domain = new URL(contextUrl).hostname;
|
|
138
|
-
} catch {
|
|
139
|
-
domain = "unknown";
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Keep resolve misses fast: this is a short probe, not a full capture.
|
|
143
|
-
const timeoutMs = Number(process.env.UNBROWSE_FIRST_PASS_TIMEOUT_MS ?? "5000");
|
|
144
|
-
const deadline = t0 + timeoutMs;
|
|
145
|
-
const externalSignal = options?.signal;
|
|
146
|
-
|
|
147
|
-
function createAbortError(): Error {
|
|
148
|
-
const error = new Error("aborted");
|
|
149
|
-
error.name = "AbortError";
|
|
150
|
-
return error;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function isAborted(): boolean {
|
|
154
|
-
if (externalSignal?.aborted) return true;
|
|
155
|
-
return Date.now() >= deadline;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function abortRace<T>(): Promise<T> {
|
|
159
|
-
if (isAborted()) return Promise.reject(createAbortError());
|
|
160
|
-
return new Promise<T>((_, reject) => {
|
|
161
|
-
const remaining = Math.max(0, deadline - Date.now());
|
|
162
|
-
const timer = setTimeout(() => {
|
|
163
|
-
cleanup();
|
|
164
|
-
reject(createAbortError());
|
|
165
|
-
}, remaining);
|
|
166
|
-
const onAbort = () => {
|
|
167
|
-
cleanup();
|
|
168
|
-
reject(createAbortError());
|
|
169
|
-
};
|
|
170
|
-
const cleanup = () => {
|
|
171
|
-
clearTimeout(timer);
|
|
172
|
-
externalSignal?.removeEventListener("abort", onAbort);
|
|
173
|
-
};
|
|
174
|
-
externalSignal?.addEventListener("abort", onAbort, { once: true });
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async function runBounded<T>(fn: () => Promise<T>): Promise<T> {
|
|
179
|
-
return await Promise.race([fn(), abortRace<T>()]);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async function sleepMs(ms: number): Promise<void> {
|
|
183
|
-
const remaining = deadline - Date.now();
|
|
184
|
-
const actual = Math.min(ms, Math.max(0, remaining));
|
|
185
|
-
if (actual <= 0) return;
|
|
186
|
-
return new Promise<void>((resolve) => {
|
|
187
|
-
let settled = false;
|
|
188
|
-
const cleanup = () => {
|
|
189
|
-
if (settled) return;
|
|
190
|
-
settled = true;
|
|
191
|
-
clearTimeout(tid);
|
|
192
|
-
externalSignal?.removeEventListener("abort", cleanup);
|
|
193
|
-
resolve();
|
|
194
|
-
};
|
|
195
|
-
const tid = setTimeout(cleanup, actual);
|
|
196
|
-
externalSignal?.addEventListener("abort", cleanup, { once: true });
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
let tabId: string | undefined;
|
|
201
|
-
let createdFreshTab = false;
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
if (isAborted()) {
|
|
205
|
-
return miss("aborted-before-start", intentClass, t0);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Ensure kuri is running
|
|
209
|
-
await runBounded(() => kuri.start());
|
|
210
|
-
await runBounded(() => kuri.discoverTabs());
|
|
211
|
-
|
|
212
|
-
if (isAborted()) {
|
|
213
|
-
return miss("aborted-after-start", intentClass, t0);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Open a fresh tab; never reuse Kuri's implicit default tab.
|
|
217
|
-
try {
|
|
218
|
-
tabId = await runBounded(() => kuri.newTab("about:blank"));
|
|
219
|
-
createdFreshTab = !!tabId;
|
|
220
|
-
} catch {
|
|
221
|
-
tabId = undefined;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (!tabId) {
|
|
225
|
-
return miss("no-tab", intentClass, t0);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (isAborted()) {
|
|
229
|
-
await cleanupTab(tabId, createdFreshTab);
|
|
230
|
-
return miss("aborted-before-navigate", intentClass, t0);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Start HAR recording before navigation to capture all requests
|
|
234
|
-
await runBounded(() => kuri.harStart(tabId));
|
|
235
|
-
|
|
236
|
-
// Navigate to the target URL
|
|
237
|
-
await runBounded(() => kuri.navigate(tabId, contextUrl));
|
|
238
|
-
await sleepMs(1_500);
|
|
239
|
-
|
|
240
|
-
if (isAborted()) {
|
|
241
|
-
await safeHarStop(tabId);
|
|
242
|
-
await cleanupTab(tabId, createdFreshTab);
|
|
243
|
-
return miss("aborted-after-navigate", intentClass, t0);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Perform intent-driven action
|
|
247
|
-
let actionTaken = "navigate";
|
|
248
|
-
if (action.type === "search" && action.selector) {
|
|
249
|
-
const searchTerm =
|
|
250
|
-
typeof params.query === "string"
|
|
251
|
-
? params.query
|
|
252
|
-
: typeof params.q === "string"
|
|
253
|
-
? params.q
|
|
254
|
-
: intent;
|
|
255
|
-
try {
|
|
256
|
-
const searchResult = await runBounded(() => kuri.evaluate(
|
|
257
|
-
tabId,
|
|
258
|
-
`(function(){
|
|
259
|
-
var sel = ${JSON.stringify(action.selector)};
|
|
260
|
-
var el = document.querySelector(sel);
|
|
261
|
-
if (!el) return 'not-found';
|
|
262
|
-
el.focus();
|
|
263
|
-
el.value = ${JSON.stringify(searchTerm)};
|
|
264
|
-
el.dispatchEvent(new Event('input', {bubbles:true}));
|
|
265
|
-
el.dispatchEvent(new Event('change', {bubbles:true}));
|
|
266
|
-
var form = el.closest('form');
|
|
267
|
-
if (form) { form.dispatchEvent(new Event('submit', {bubbles:true, cancelable:true})); return 'form-submit'; }
|
|
268
|
-
el.dispatchEvent(new KeyboardEvent('keydown', {key:'Enter',keyCode:13,bubbles:true}));
|
|
269
|
-
el.dispatchEvent(new KeyboardEvent('keypress', {key:'Enter',keyCode:13,bubbles:true}));
|
|
270
|
-
el.dispatchEvent(new KeyboardEvent('keyup', {key:'Enter',keyCode:13,bubbles:true}));
|
|
271
|
-
return 'search-enter';
|
|
272
|
-
})()`,
|
|
273
|
-
));
|
|
274
|
-
actionTaken =
|
|
275
|
-
typeof searchResult === "string" ? searchResult : "search-attempted";
|
|
276
|
-
} catch {
|
|
277
|
-
actionTaken = "search-failed";
|
|
278
|
-
}
|
|
279
|
-
} else if (action.type === "click" && action.selector) {
|
|
280
|
-
try {
|
|
281
|
-
const clickResult = await runBounded(() => kuri.evaluate(
|
|
282
|
-
tabId,
|
|
283
|
-
`(function(){
|
|
284
|
-
var el = document.querySelector(${JSON.stringify(action.selector)});
|
|
285
|
-
if (!el) return 'not-found';
|
|
286
|
-
el.click();
|
|
287
|
-
return 'clicked';
|
|
288
|
-
})()`,
|
|
289
|
-
));
|
|
290
|
-
actionTaken =
|
|
291
|
-
typeof clickResult === "string" ? clickResult : "click-attempted";
|
|
292
|
-
} catch {
|
|
293
|
-
actionTaken = "click-failed";
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Wait for network activity to settle after the action
|
|
298
|
-
await sleepMs(2_000);
|
|
299
|
-
|
|
300
|
-
if (isAborted()) {
|
|
301
|
-
await safeHarStop(tabId);
|
|
302
|
-
await cleanupTab(tabId, createdFreshTab);
|
|
303
|
-
return miss(actionTaken, intentClass, t0);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Stop HAR recording and collect entries
|
|
307
|
-
let harEntries: KuriHarEntry[] = [];
|
|
308
|
-
try {
|
|
309
|
-
const harResult = await runBounded(() => kuri.harStop(tabId));
|
|
310
|
-
harEntries = harResult.entries ?? [];
|
|
311
|
-
} catch {
|
|
312
|
-
// non-fatal — HAR collection failed
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// On hit: cleanup tab. On miss: keep tab alive for Phase 4 agentic loop.
|
|
316
|
-
if (filterJsonApiEntries(harEntries).length > 0) {
|
|
317
|
-
await cleanupTab(tabId, createdFreshTab);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Filter to JSON API responses only
|
|
321
|
-
const jsonEntries = filterJsonApiEntries(harEntries);
|
|
322
|
-
const hit = jsonEntries.length > 0;
|
|
323
|
-
|
|
324
|
-
let result: unknown;
|
|
325
|
-
let miniSkill: SkillManifest | undefined;
|
|
326
|
-
|
|
327
|
-
if (hit) {
|
|
328
|
-
try {
|
|
329
|
-
const firstEntry = jsonEntries[0];
|
|
330
|
-
const text = firstEntry?.response.content?.text;
|
|
331
|
-
if (text) result = JSON.parse(text);
|
|
332
|
-
} catch {
|
|
333
|
-
result = undefined;
|
|
334
|
-
}
|
|
335
|
-
miniSkill = synthesizeSkillFromIntercepted(jsonEntries, domain, intent);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
let reusableTabId = hit ? undefined : tabId;
|
|
339
|
-
if (!hit && reusableTabId) {
|
|
340
|
-
const kuriHealth = await kuri.health().catch(() => ({ ok: false }));
|
|
341
|
-
if (!kuriHealth.ok) {
|
|
342
|
-
reusableTabId = undefined;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
hit,
|
|
348
|
-
result,
|
|
349
|
-
interceptedEntries: jsonEntries,
|
|
350
|
-
miniSkill,
|
|
351
|
-
actionTaken,
|
|
352
|
-
intentClass,
|
|
353
|
-
timeMs: Date.now() - t0,
|
|
354
|
-
tabId: reusableTabId, // keep tab alive on miss only while Kuri still owns it
|
|
355
|
-
};
|
|
356
|
-
} catch (err) {
|
|
357
|
-
// On any error, cleanup and return miss — never throw
|
|
358
|
-
if (tabId) {
|
|
359
|
-
try {
|
|
360
|
-
await safeHarStop(tabId);
|
|
361
|
-
} catch {
|
|
362
|
-
/* ignore */
|
|
363
|
-
}
|
|
364
|
-
try {
|
|
365
|
-
await cleanupTab(tabId, createdFreshTab);
|
|
366
|
-
} catch {
|
|
367
|
-
/* ignore */
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
const isAbortError =
|
|
371
|
-
err instanceof Error &&
|
|
372
|
-
(err.name === "AbortError" || err.name === "TimeoutError");
|
|
373
|
-
return {
|
|
374
|
-
hit: false,
|
|
375
|
-
interceptedEntries: [],
|
|
376
|
-
actionTaken: isAbortError ? "aborted" : "error",
|
|
377
|
-
intentClass,
|
|
378
|
-
timeMs: Date.now() - t0,
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
async function safeHarStop(tabId: string): Promise<void> {
|
|
384
|
-
try {
|
|
385
|
-
await Promise.race([
|
|
386
|
-
kuri.harStop(tabId),
|
|
387
|
-
new Promise<void>((resolve) => setTimeout(resolve, 250)),
|
|
388
|
-
]);
|
|
389
|
-
} catch {
|
|
390
|
-
/* best-effort */
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
async function cleanupTab(tabId: string, close: boolean): Promise<void> {
|
|
395
|
-
try {
|
|
396
|
-
await Promise.race([
|
|
397
|
-
close ? kuri.closeTab(tabId) : kuri.navigate(tabId, "about:blank"),
|
|
398
|
-
new Promise<void>((resolve) => setTimeout(resolve, 250)),
|
|
399
|
-
]);
|
|
400
|
-
} catch {
|
|
401
|
-
/* best-effort */
|
|
402
|
-
}
|
|
403
|
-
}
|