@opencode/browse 0.0.6-dev.20 → 0.0.6-dev.21
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-anqhds1e.mjs → cli-qcdv8aqh.mjs} +85 -84
- package/dist/cli.mjs +14 -9
- package/dist/{helpers-n4k4pdag.mjs → helpers-d74qp3s1.mjs} +2 -1
- package/dist/{main-ma25hhqh.mjs → main-0m569gdn.mjs} +165 -127
- package/dist/{main-3rsfghef.mjs → main-33kf3cfr.mjs} +12 -4
- package/dist/main-3gthmkzw.mjs +112 -0
- package/dist/{main-hywb9qbv.mjs → main-6bjnpchg.mjs} +4 -4
- package/dist/{main-20ahaz2q.mjs → main-7qs0mwvc.mjs} +1 -1
- package/dist/main-ek76h9k1.mjs +4 -0
- package/dist/{main-0nta0a2z.mjs → main-qm2keaeb.mjs} +18 -12
- package/dist/{main-42qfr69q.mjs → main-ybvatjk0.mjs} +5 -5
- package/dist/{main-yd1d7qr1.mjs → main-yq58hnwq.mjs} +10 -10
- package/dist/{mcp-j9e2da4y.mjs → mcp-qmq1yerr.mjs} +38 -39
- package/dist/{relay-cvhz2f0a.mjs → relay-446cn6cs.mjs} +95 -69
- package/dist/{runtime-update-baranjc5.mjs → runtime-update-fj4s270v.mjs} +6 -5
- package/dist/{setup-7shrgdza.mjs → setup-atsf76r6.mjs} +3844 -3132
- package/dist/skill-text-v35020jn.mjs +1007 -0
- package/dist/supervisor-bvnx7xgg.mjs +154 -0
- package/dist/{update-vkspb2rz.mjs → update-38wp6pt4.mjs} +4 -2
- package/dist/{version-gfpqxbzm.mjs → version-x7djen82.mjs} +1 -1
- package/package.json +3 -2
- package/skills/browse/SKILL.md +29 -29
- package/dist/main-arx3pfyn.mjs +0 -4
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
opencodeBrowserVersion2,
|
|
3
|
+
opencodeBrowserBuildId2
|
|
4
|
+
} from "./main-ek76h9k1.mjs";
|
|
1
5
|
import {
|
|
2
6
|
dataRoot,
|
|
3
7
|
oldStoreMarker,
|
|
@@ -13,7 +17,9 @@ import {
|
|
|
13
17
|
storedMode,
|
|
14
18
|
setStoredMode,
|
|
15
19
|
autoUpdateStatus,
|
|
16
|
-
createAutoUpdater
|
|
20
|
+
createAutoUpdater
|
|
21
|
+
} from "./main-0m569gdn.mjs";
|
|
22
|
+
import {
|
|
17
23
|
extensionProtocolVersion,
|
|
18
24
|
minExtensionProtocolVersion,
|
|
19
25
|
checkoutReloadPrefix,
|
|
@@ -25,7 +31,7 @@ import {
|
|
|
25
31
|
isExtensionResponse,
|
|
26
32
|
isExtensionEvent,
|
|
27
33
|
isJsonObject
|
|
28
|
-
} from "./main-
|
|
34
|
+
} from "./main-3gthmkzw.mjs";
|
|
29
35
|
import {
|
|
30
36
|
isNodeError,
|
|
31
37
|
isUnsupportedDirectorySyncError,
|
|
@@ -36,7 +42,7 @@ import {
|
|
|
36
42
|
relayToken,
|
|
37
43
|
secretsEqual,
|
|
38
44
|
ensurePrivateDirectory
|
|
39
|
-
} from "./main-
|
|
45
|
+
} from "./main-7qs0mwvc.mjs";
|
|
40
46
|
import {
|
|
41
47
|
ExecuteRequest,
|
|
42
48
|
SessionAdoptRequest,
|
|
@@ -86,8 +92,10 @@ import {
|
|
|
86
92
|
isRestrictedTarget2,
|
|
87
93
|
isRestrictedUrl2,
|
|
88
94
|
dataUrlDocument2
|
|
89
|
-
} from "./main-
|
|
95
|
+
} from "./main-qm2keaeb.mjs";
|
|
90
96
|
import {
|
|
97
|
+
relayHostname,
|
|
98
|
+
isLocalPeer,
|
|
91
99
|
context,
|
|
92
100
|
pendingPairingCodeKeys,
|
|
93
101
|
consumePairingCode,
|
|
@@ -105,10 +113,8 @@ import {
|
|
|
105
113
|
sessionProof,
|
|
106
114
|
wrapToken,
|
|
107
115
|
installedDevExtension,
|
|
108
|
-
syncDevExtension
|
|
109
|
-
|
|
110
|
-
isLocalPeer
|
|
111
|
-
} from "./main-hywb9qbv.mjs";
|
|
116
|
+
syncDevExtension
|
|
117
|
+
} from "./main-6bjnpchg.mjs";
|
|
112
118
|
import {
|
|
113
119
|
SettingsInputError,
|
|
114
120
|
parseSettingChanges,
|
|
@@ -146,20 +152,16 @@ import {
|
|
|
146
152
|
checkNavigationUrl,
|
|
147
153
|
readSecret,
|
|
148
154
|
secretText
|
|
149
|
-
} from "./main-
|
|
150
|
-
import {
|
|
151
|
-
opencodeBrowserVersion2,
|
|
152
|
-
opencodeBrowserBuildId2
|
|
153
|
-
} from "./main-arx3pfyn.mjs";
|
|
155
|
+
} from "./main-yq58hnwq.mjs";
|
|
154
156
|
import {
|
|
155
157
|
defaultJournalBaseDir,
|
|
156
158
|
makeJournalEntry,
|
|
157
159
|
appendJournalEntry,
|
|
158
160
|
startJournalRetention
|
|
159
|
-
} from "./main-
|
|
161
|
+
} from "./main-ybvatjk0.mjs";
|
|
160
162
|
import {
|
|
161
163
|
createUpdateWatcher2
|
|
162
|
-
} from "./main-
|
|
164
|
+
} from "./main-33kf3cfr.mjs";
|
|
163
165
|
|
|
164
166
|
// src/relay.ts
|
|
165
167
|
import crypto10 from "node:crypto";
|
|
@@ -270,7 +272,7 @@ class ExtensionRpc {
|
|
|
270
272
|
return Effect.void;
|
|
271
273
|
}
|
|
272
274
|
if (!socket || socket.readyState !== WebSocket.OPEN || !this.acceptsEvents) {
|
|
273
|
-
resume(Effect.fail(new Error("
|
|
275
|
+
resume(Effect.fail(new Error("browse extension is not connected")));
|
|
274
276
|
return Effect.void;
|
|
275
277
|
}
|
|
276
278
|
const id = this.nextRequestId++;
|
|
@@ -1604,7 +1606,7 @@ import { chromium, errors } from "patchright-core";
|
|
|
1604
1606
|
|
|
1605
1607
|
// src/snapshot/aria-snapshot.ts
|
|
1606
1608
|
import { selectors } from "patchright-core";
|
|
1607
|
-
var redactionCleanupErrorMessage = "
|
|
1609
|
+
var redactionCleanupErrorMessage = "browse could not confirm ARIA snapshot value-redaction cleanup";
|
|
1608
1610
|
var redactionSelectorSource = `({
|
|
1609
1611
|
query(root, body) {
|
|
1610
1612
|
const separator = body.indexOf("_")
|
|
@@ -2307,7 +2309,7 @@ function assertExecuteFilePath(target, operation) {
|
|
|
2307
2309
|
const resolved = realTarget(target);
|
|
2308
2310
|
const data = realTarget(dataRoot());
|
|
2309
2311
|
if (isInside(resolved, data)) {
|
|
2310
|
-
throw new ExecuteFileAccessError(`${operation}: ${target} is inside
|
|
2312
|
+
throw new ExecuteFileAccessError(`${operation}: ${target} is inside browse's data directory, which execute code cannot access`);
|
|
2311
2313
|
}
|
|
2312
2314
|
if (!roots.some((root) => isInside(resolved, root))) {
|
|
2313
2315
|
throw new ExecuteFileAccessError(`${operation}: ${target} is outside the execute file roots ${describeRoots()}. Set ${allowNodeEnvironmentVariable}=1 for unrestricted file access.`);
|
|
@@ -2690,7 +2692,15 @@ var stopPromiseHook;
|
|
|
2690
2692
|
function trackPromisesOf(realm) {
|
|
2691
2693
|
const previous = activeRealm;
|
|
2692
2694
|
activeRealm = realm;
|
|
2693
|
-
if (
|
|
2695
|
+
if (process.versions.bun) {
|
|
2696
|
+
const entered = { realm, at: Date.now() };
|
|
2697
|
+
lastRealm = entered;
|
|
2698
|
+
return () => {
|
|
2699
|
+
activeRealm = previous;
|
|
2700
|
+
entered.at = Date.now();
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
if (!stopPromiseHook) {
|
|
2694
2704
|
stopPromiseHook = v8.promiseHooks.createHook({ init(promise) {
|
|
2695
2705
|
if (activeRealm)
|
|
2696
2706
|
promiseRealms.set(promise, activeRealm);
|
|
@@ -2704,8 +2714,23 @@ function trackPromisesOf(realm) {
|
|
|
2704
2714
|
}
|
|
2705
2715
|
};
|
|
2706
2716
|
}
|
|
2717
|
+
var realmsByPromisePrototype = new WeakMap;
|
|
2718
|
+
var lastRealm;
|
|
2719
|
+
var lastRealmWindowMs = 1000;
|
|
2720
|
+
function registerRealmPromisePrototype(prototype, realm) {
|
|
2721
|
+
if (process.versions.bun)
|
|
2722
|
+
realmsByPromisePrototype.set(prototype, realm);
|
|
2723
|
+
}
|
|
2724
|
+
function bunRejectionRealm(promise) {
|
|
2725
|
+
const byPrototype = realmsByPromisePrototype.get(Object.getPrototypeOf(promise));
|
|
2726
|
+
if (byPrototype)
|
|
2727
|
+
return byPrototype;
|
|
2728
|
+
if (!lastRealm || lastRealm.realm.isStopped)
|
|
2729
|
+
return;
|
|
2730
|
+
return activeRealm === lastRealm.realm || Date.now() - lastRealm.at <= lastRealmWindowMs ? lastRealm.realm : undefined;
|
|
2731
|
+
}
|
|
2707
2732
|
function handleExecuteRealmRejection(promise) {
|
|
2708
|
-
const realm = promiseRealms.get(promise);
|
|
2733
|
+
const realm = promiseRealms.get(promise) ?? (process.versions.bun ? bunRejectionRealm(promise) : undefined);
|
|
2709
2734
|
if (!realm)
|
|
2710
2735
|
return false;
|
|
2711
2736
|
realm.dispose(new Error("Unhandled execute rejection; JavaScript state will reset before the next execute"));
|
|
@@ -2864,6 +2889,7 @@ class ExecuteRealm {
|
|
|
2864
2889
|
this.sandboxFunctionPrototype = sandboxIntrinsics.Function.prototype;
|
|
2865
2890
|
this.sandboxArrayPrototype = sandboxIntrinsics.Array.prototype;
|
|
2866
2891
|
this.SandboxPromise = sandboxIntrinsics.Promise;
|
|
2892
|
+
registerRealmPromisePrototype(this.SandboxPromise.prototype, this);
|
|
2867
2893
|
this.SandboxError = sandboxIntrinsics.Error;
|
|
2868
2894
|
this.sandboxAwait = new vm3.Script("(async (value, resolve, reject) => { try { resolve(await value) } catch (error) { reject(error) } })").runInContext(this.context);
|
|
2869
2895
|
this.state = Object.create(this.sandboxObjectPrototype);
|
|
@@ -3752,7 +3778,7 @@ function selectTarget({
|
|
|
3752
3778
|
if (matches.length === 0) {
|
|
3753
3779
|
throw new TargetSelectionError({
|
|
3754
3780
|
reason: "not-found",
|
|
3755
|
-
message: `No existing attached page URL includes ${selection.urlIncludes}. Target selectors do not navigate or open pages: use page.goto() in the session page, or attach the intended user tab with the
|
|
3781
|
+
message: `No existing attached page URL includes ${selection.urlIncludes}. Target selectors do not navigate or open pages: use page.goto() in the session page, or attach the intended user tab with the browse toolbar first.`
|
|
3756
3782
|
});
|
|
3757
3783
|
}
|
|
3758
3784
|
if (matches.length > 1) {
|
|
@@ -3780,7 +3806,7 @@ function selectTarget({
|
|
|
3780
3806
|
if (targets.length === 0) {
|
|
3781
3807
|
throw new TargetSelectionError({
|
|
3782
3808
|
reason: "not-found",
|
|
3783
|
-
message: "No attached pages available. Attach the intended user tab with the
|
|
3809
|
+
message: "No attached pages available. Attach the intended user tab with the browse toolbar first."
|
|
3784
3810
|
});
|
|
3785
3811
|
}
|
|
3786
3812
|
if (targets.length > 1) {
|
|
@@ -3844,7 +3870,7 @@ var timedOut = Symbol("timed-out");
|
|
|
3844
3870
|
var defaultPageClosedWarning = "The session default page was closed; created a new page. References to the old page in state are stale.";
|
|
3845
3871
|
var defaultPageRecoveredWarning = "The session default page was unresponsive; created a new page. References to the old page in state are stale.";
|
|
3846
3872
|
var defaultPageCrashedWarning = "The session default page target crashed; checking it before the next execute.";
|
|
3847
|
-
var defaultPageRepairedWarning = "The session default page stopped answering automation;
|
|
3873
|
+
var defaultPageRepairedWarning = "The session default page stopped answering automation; browse reconnected and re-resolved the same tab. References to the old page in state are stale.";
|
|
3848
3874
|
var protectedExtensionUiWarning = "Chromium blocked protected extension UI, possibly a password manager. Ask the user to finish or dismiss it in the browser, then retry.";
|
|
3849
3875
|
var protectedExtensionUiDiagnostic = "target/cross-extension-page";
|
|
3850
3876
|
var syntheticInputWarning = "fillInput/fillInputs set the value with synthetic input events (isTrusted: false, no key presses), which pages and bot protection can tell apart from typing. Prefer locator.fill() or locator.pressSequentially().";
|
|
@@ -4000,7 +4026,7 @@ async function finishHandoff(options) {
|
|
|
4000
4026
|
const kind = runtimeFailureKind(error);
|
|
4001
4027
|
if (kind !== "context-destroyed" && kind !== "context-missing")
|
|
4002
4028
|
throw error;
|
|
4003
|
-
throw new Error(`Handoff resolved (${options.message}), but the page execution context did not become available within ${contextTimeoutMs}ms: ${error instanceof Error ? error.message : String(error)}. The tab was kept; run a short follow-up execute so
|
|
4029
|
+
throw new Error(`Handoff resolved (${options.message}), but the page execution context did not become available within ${contextTimeoutMs}ms: ${error instanceof Error ? error.message : String(error)}. The tab was kept; run a short follow-up execute so browse can re-check it.`, { cause: error });
|
|
4004
4030
|
}
|
|
4005
4031
|
}
|
|
4006
4032
|
function isSessionPageConnected(options) {
|
|
@@ -4353,7 +4379,7 @@ ${wrapCode(code)}
|
|
|
4353
4379
|
import { Effect as Effect6 } from "effect";
|
|
4354
4380
|
|
|
4355
4381
|
// src/execute/guards.ts
|
|
4356
|
-
var downloadCapabilityErrorMessage = "Downloads are unavailable in
|
|
4382
|
+
var downloadCapabilityErrorMessage = "Downloads are unavailable in browse extension-backed tabs: Chromium blocks Browser.setDownloadBehavior and Page.setDownloadBehavior through chrome.debugger, so Playwright cannot retain an artifact for download.saveAs(). Fetch the response in the page and save the returned bytes with files.write() when the site exposes them.";
|
|
4357
4383
|
var downloadGuardedPages = new WeakSet;
|
|
4358
4384
|
var downloadGuardedContexts = new WeakSet;
|
|
4359
4385
|
var viewportZoomGuardedPages = new WeakSet;
|
|
@@ -8565,7 +8591,7 @@ function buildSandboxGlobals(deps) {
|
|
|
8565
8591
|
const requestHandoff = deps.requestHandoff;
|
|
8566
8592
|
const handoff = async (message, options) => {
|
|
8567
8593
|
if (!requestHandoff) {
|
|
8568
|
-
throw new Error("handoff is not available in this sandbox; it requires a relay-backed
|
|
8594
|
+
throw new Error("handoff is not available in this sandbox; it requires a relay-backed browse session");
|
|
8569
8595
|
}
|
|
8570
8596
|
const handoffMessage = message?.trim() || defaultHandoffMessage;
|
|
8571
8597
|
const timeoutMs = options?.timeoutMs ?? defaultHandoffTimeoutMs;
|
|
@@ -9618,7 +9644,7 @@ function finishCapture(active, options, secrets, profileOptions) {
|
|
|
9618
9644
|
yield* writeArtifact(options.outputPath, {
|
|
9619
9645
|
log: {
|
|
9620
9646
|
version: "1.2",
|
|
9621
|
-
creator: { name: "
|
|
9647
|
+
creator: { name: "browse", version: "1" },
|
|
9622
9648
|
entries: protectedEntries.map(toHarEntry)
|
|
9623
9649
|
}
|
|
9624
9650
|
});
|
|
@@ -11386,7 +11412,7 @@ class FlightRecorderRelay {
|
|
|
11386
11412
|
}
|
|
11387
11413
|
async start(options) {
|
|
11388
11414
|
if (!this.options.isExtensionConnected())
|
|
11389
|
-
throw new Error("
|
|
11415
|
+
throw new Error("browse extension is not connected");
|
|
11390
11416
|
if (this.isActiveTab(options.tabId))
|
|
11391
11417
|
throw new Error("Flight recorder already active for this tab");
|
|
11392
11418
|
if (this.options.isTabRecording?.(options.tabId))
|
|
@@ -12134,7 +12160,7 @@ class SessionAdmission {
|
|
|
12134
12160
|
}
|
|
12135
12161
|
assertAdmission() {
|
|
12136
12162
|
if (this.admission !== "open") {
|
|
12137
|
-
throw sessionError("inactive", this.admission === "closed" ? "
|
|
12163
|
+
throw sessionError("inactive", this.admission === "closed" ? "browse sessions are closing" : "browse sessions are draining");
|
|
12138
12164
|
}
|
|
12139
12165
|
}
|
|
12140
12166
|
assertStableIdentity(id) {
|
|
@@ -12213,13 +12239,13 @@ class SessionAdmission {
|
|
|
12213
12239
|
}
|
|
12214
12240
|
});
|
|
12215
12241
|
this.persistenceTail.catch((error) => {
|
|
12216
|
-
console.error("Failed to persist
|
|
12242
|
+
console.error("Failed to persist browse sessions", error);
|
|
12217
12243
|
});
|
|
12218
12244
|
}
|
|
12219
12245
|
flushPersistence() {
|
|
12220
12246
|
return Effect16.tryPromise({
|
|
12221
12247
|
try: () => this.persistenceTail,
|
|
12222
|
-
catch: (cause) => cause instanceof Error ? cause : new Error("Persist
|
|
12248
|
+
catch: (cause) => cause instanceof Error ? cause : new Error("Persist browse sessions", { cause })
|
|
12223
12249
|
});
|
|
12224
12250
|
}
|
|
12225
12251
|
commitPersistence(rollback) {
|
|
@@ -13626,7 +13652,7 @@ class RecordingRelay {
|
|
|
13626
13652
|
return { success: false, error: "Recording frameRate must be an integer from 1 to 60" };
|
|
13627
13653
|
}
|
|
13628
13654
|
if (!this.options.isExtensionConnected()) {
|
|
13629
|
-
return { success: false, error: "
|
|
13655
|
+
return { success: false, error: "browse extension is not connected" };
|
|
13630
13656
|
}
|
|
13631
13657
|
if (this.activeRecordings.has(options.tabId) || this.startingRecordings.has(options.tabId)) {
|
|
13632
13658
|
return { success: false, error: "Recording already in progress for this tab" };
|
|
@@ -13659,7 +13685,7 @@ class RecordingRelay {
|
|
|
13659
13685
|
}
|
|
13660
13686
|
async stopRecording(options) {
|
|
13661
13687
|
if (!this.options.isExtensionConnected()) {
|
|
13662
|
-
return { success: false, error: "
|
|
13688
|
+
return { success: false, error: "browse extension is not connected" };
|
|
13663
13689
|
}
|
|
13664
13690
|
const recording = this.findRecording(options);
|
|
13665
13691
|
if (!recording) {
|
|
@@ -13777,7 +13803,7 @@ class RecordingRelay {
|
|
|
13777
13803
|
if (!this.options.isExtensionConnected()) {
|
|
13778
13804
|
recording.resolveStop?.({ success: false, error: "Recording was cancelled" });
|
|
13779
13805
|
await this.cleanupRecording(recording.tabId);
|
|
13780
|
-
return { success: false, error: "
|
|
13806
|
+
return { success: false, error: "browse extension is not connected" };
|
|
13781
13807
|
}
|
|
13782
13808
|
const result = this.options.sendToExtension({ method: "recording.cancel", params: { tabId: recording.tabId } }).then(parseCancelResult, (error) => ({ success: false, error: error instanceof Error ? error.message : String(error) })).then((result) => {
|
|
13783
13809
|
recording.resolveStop?.({ success: false, error: "Recording was cancelled" });
|
|
@@ -14362,7 +14388,7 @@ function handleCliRequest(options) {
|
|
|
14362
14388
|
const available = yield* options.sessions.targetsForAdoption(requestedSessionId, allTargets);
|
|
14363
14389
|
const byTab = request.tabId === undefined ? undefined : allTargets.find((target) => target.tabId === request.tabId);
|
|
14364
14390
|
if (request.tabId !== undefined && !byTab)
|
|
14365
|
-
throw new Error(`Tab ${request.tabId} is not available to agents. Ask the user to click the
|
|
14391
|
+
throw new Error(`Tab ${request.tabId} is not available to agents. Ask the user to click the browse toolbar button on that tab.`);
|
|
14366
14392
|
if (byTab && !available.includes(byTab))
|
|
14367
14393
|
return yield* new HttpRouteError2({ status: 400, code: "invalid-request", message: `Tab ${byTab.tabId} belongs to another browser. Start a new session for that browser.` });
|
|
14368
14394
|
const candidates = byTab ? [byTab] : request.targetSelection ? available : (() => {
|
|
@@ -14432,7 +14458,7 @@ function createHttpRequestHandler(options) {
|
|
|
14432
14458
|
const requestUrl = new URL(request.url ?? "/", `http://${formatHostForUrl2(options.host)}:${options.port}`);
|
|
14433
14459
|
const pathname = requestUrl.pathname.replace(/\/$/, "") || "/";
|
|
14434
14460
|
if (pathname !== "/" && pathname !== "/version" && !hasRelayToken(request, options.authToken)) {
|
|
14435
|
-
sendJson2(response, { error: "Missing or invalid
|
|
14461
|
+
sendJson2(response, { error: "Missing or invalid browse relay token", code: "unauthorized" }, 401);
|
|
14436
14462
|
return;
|
|
14437
14463
|
}
|
|
14438
14464
|
const observational = request.method === "GET" || pathname === "/network/status" || pathname === "/auth/status";
|
|
@@ -15216,7 +15242,7 @@ function guardStorageAccess(context) {
|
|
|
15216
15242
|
return null;
|
|
15217
15243
|
const everyOrigin = storageMethodsForAllOrigins.get(context.method);
|
|
15218
15244
|
if (everyOrigin) {
|
|
15219
|
-
return `
|
|
15245
|
+
return `browse blocked ${context.method}: ${everyOrigin}. Set ${allowAllStorageEnvironmentVariable}=1 on the relay to allow it.`;
|
|
15220
15246
|
}
|
|
15221
15247
|
if (storageMethodsWithoutOrigin.has(context.method))
|
|
15222
15248
|
return null;
|
|
@@ -15228,7 +15254,7 @@ function guardStorageAccess(context) {
|
|
|
15228
15254
|
});
|
|
15229
15255
|
if (outside === undefined)
|
|
15230
15256
|
return null;
|
|
15231
|
-
return `
|
|
15257
|
+
return `browse blocked ${context.method}: site storage can only be used for the hosts of this session's attached pages (${formatAllowedHosts(hosts)}), not ${outside}. Set ${allowAllStorageEnvironmentVariable}=1 on the relay to allow it.`;
|
|
15232
15258
|
}
|
|
15233
15259
|
var restrictedNavigationSchemes = new Set([
|
|
15234
15260
|
"file:",
|
|
@@ -15261,7 +15287,7 @@ function guardNavigation(context) {
|
|
|
15261
15287
|
if (scheme === "file:" && allowsFileUrls())
|
|
15262
15288
|
return null;
|
|
15263
15289
|
const hint = scheme === "file:" ? ` Set ${allowFileUrlsEnvironmentVariable}=1 on the relay to allow file: URLs.` : "";
|
|
15264
|
-
return `
|
|
15290
|
+
return `browse blocked ${context.method} to a ${scheme} URL: agents can't open browser-internal pages, other extensions, DevTools or local files.${hint}`;
|
|
15265
15291
|
}
|
|
15266
15292
|
function pageHosts(urls) {
|
|
15267
15293
|
const hosts = [];
|
|
@@ -15300,7 +15326,7 @@ function guardCookieWrite(context) {
|
|
|
15300
15326
|
for (const cookie of cookies) {
|
|
15301
15327
|
const domain = cookieDomainOf(cookie);
|
|
15302
15328
|
if (!domain || !hosts.some((host) => cookieAppliesToHost(domain, host))) {
|
|
15303
|
-
return `
|
|
15329
|
+
return `browse blocked ${context.method}: cookies can only be set for the hosts of this session's attached pages (${formatAllowedHosts(hosts)}), not ${domain ?? "a cookie without a domain or url"}. Set ${allowAllCookiesEnvironmentVariable}=1 on the relay to allow it.`;
|
|
15304
15330
|
}
|
|
15305
15331
|
}
|
|
15306
15332
|
return null;
|
|
@@ -15308,13 +15334,13 @@ function guardCookieWrite(context) {
|
|
|
15308
15334
|
function guardCdpMethod(context) {
|
|
15309
15335
|
const reason = alwaysBlocked.get(context.method);
|
|
15310
15336
|
if (reason) {
|
|
15311
|
-
return `
|
|
15337
|
+
return `browse blocked ${context.method}: ${reason}. This command is always blocked by the relay.`;
|
|
15312
15338
|
}
|
|
15313
15339
|
if (context.method.startsWith("WebAuthn.")) {
|
|
15314
|
-
return `
|
|
15340
|
+
return `browse blocked ${context.method}: agents can't drive WebAuthn or add virtual authenticators. Passkey sign-ins in agent tabs use the logins the user granted in Deadbolt. This command is always blocked by the relay.`;
|
|
15315
15341
|
}
|
|
15316
15342
|
if (context.method === "Page.bringToFront" && !allowsForeground()) {
|
|
15317
|
-
return "
|
|
15343
|
+
return "browse blocked Page.bringToFront: it raises the user's browser window over their work. Use handoff() when the user needs to act in the tab, or set OPENCODE_BROWSER_ALLOW_FOREGROUND=1 on the relay to allow it.";
|
|
15318
15344
|
}
|
|
15319
15345
|
if ((context.method === "Storage.setCookies" || context.method === "Network.setCookies" || context.method === "Network.setCookie") && !allowsAllCookies()) {
|
|
15320
15346
|
const rejection = guardCookieWrite(context);
|
|
@@ -15334,7 +15360,7 @@ function guardCdpMethod(context) {
|
|
|
15334
15360
|
const hosts = pageHosts(context.pageUrls?.() ?? []);
|
|
15335
15361
|
const host = typeof url === "string" ? pageHosts([url])[0] : undefined;
|
|
15336
15362
|
if (host === undefined || !hosts.includes(host)) {
|
|
15337
|
-
return `
|
|
15363
|
+
return `browse blocked Network.loadNetworkResource: it loads URLs with the user's credentials outside the page's same-origin policy, so only the hosts of this session's attached pages (${formatAllowedHosts(hosts)}) are allowed. Set ${allowAllStorageEnvironmentVariable}=1 on the relay to allow it.`;
|
|
15338
15364
|
}
|
|
15339
15365
|
}
|
|
15340
15366
|
if (context.readOnly && readOnlyBlockedPrefixes.some((prefix) => context.method.startsWith(prefix))) {
|
|
@@ -15923,11 +15949,11 @@ function createExtensionCommands(options) {
|
|
|
15923
15949
|
if (tabId !== undefined) {
|
|
15924
15950
|
const owner = browsers.browserTab(tabId);
|
|
15925
15951
|
if (!owner)
|
|
15926
|
-
return Effect26.fail(new Error("
|
|
15952
|
+
return Effect26.fail(new Error("browse extension is not connected"));
|
|
15927
15953
|
return sendToBrowser(owner.link, { ...command, params: { ...command.params, tabId: owner.tabId } });
|
|
15928
15954
|
}
|
|
15929
15955
|
const target = link ?? browsers.defaultLink();
|
|
15930
|
-
return target ? sendToBrowser(target, command) : Effect26.fail(new Error("
|
|
15956
|
+
return target ? sendToBrowser(target, command) : Effect26.fail(new Error("browse extension is not connected"));
|
|
15931
15957
|
};
|
|
15932
15958
|
const sendExtensionBestEffort = (command, link) => {
|
|
15933
15959
|
Effect26.runPromise(Effect26.ignore(sendToExtension(command, link))).catch(() => {});
|
|
@@ -15940,13 +15966,13 @@ function createExtensionCommands(options) {
|
|
|
15940
15966
|
const sendToEach = (command) => Effect26.gen(function* () {
|
|
15941
15967
|
const links = browsers.list().filter((link) => link.rpc.connected);
|
|
15942
15968
|
if (links.length === 0)
|
|
15943
|
-
return yield* Effect26.fail(new Error("
|
|
15969
|
+
return yield* Effect26.fail(new Error("browse extension is not connected"));
|
|
15944
15970
|
const results = yield* Effect26.forEach(links, (link) => Effect26.result(sendToBrowser(link, command)), { concurrency: "unbounded" });
|
|
15945
15971
|
const succeeded = results.find(Result2.isSuccess);
|
|
15946
15972
|
if (succeeded)
|
|
15947
15973
|
return succeeded.success;
|
|
15948
15974
|
const failed = results.find(Result2.isFailure);
|
|
15949
|
-
return yield* Effect26.fail(failed?.failure ?? new Error("
|
|
15975
|
+
return yield* Effect26.fail(failed?.failure ?? new Error("browse extension is not connected"));
|
|
15950
15976
|
});
|
|
15951
15977
|
return { sendToExtension, sendExtensionBestEffort, sendToEveryBrowser, sendToEach };
|
|
15952
15978
|
}
|
|
@@ -16551,7 +16577,7 @@ class ExtensionHandshake {
|
|
|
16551
16577
|
const rejected = link ? undefined : this.rejectedProtocol();
|
|
16552
16578
|
const recent = Date.now() - 65000;
|
|
16553
16579
|
const pairingState = browsers.connected ? "connected" : rejected ? "incompatible" : this.lastRefused > recent ? "refused" : this.lastUnpaired > recent ? "unpaired" : this.hasPairedBrowser ? "paired-offline" : "not-seen";
|
|
16554
|
-
const nextStep = pairingState === "connected" ? undefined : pairingState === "incompatible" ? this.mismatchAdvice(rejected?.version) : pairingState === "refused" || pairingState === "unpaired" ? `${pairingState === "refused" ? "
|
|
16580
|
+
const nextStep = pairingState === "connected" ? undefined : pairingState === "incompatible" ? this.mismatchAdvice(rejected?.version) : pairingState === "refused" || pairingState === "unpaired" ? `${pairingState === "refused" ? "browse refused the pairing attempt" : "browse is installed but not paired"}. Run \`${cliCommand("install")}\` again to pair it, or run \`${cliCommand("pair")}\` and enter the new code in browse's Options.` : pairingState === "paired-offline" ? "Open the browser profile already paired with browse and enable the extension; it reconnects automatically." : `No browser has contacted this relay yet. If browse is installed, open that browser and run \`${cliCommand("install")}\` to pair it. Otherwise ${storeId ? `add browse from ${storeUrl(storeId)}` : `follow the extension steps in \`${installCommand()}\``}.`;
|
|
16555
16581
|
return {
|
|
16556
16582
|
connected: browsers.connected,
|
|
16557
16583
|
pairingState,
|
|
@@ -16710,7 +16736,7 @@ class ExtensionHandshake {
|
|
|
16710
16736
|
this.legacyExtensionReloaded = true;
|
|
16711
16737
|
socket.send(JSON.stringify({ id: 0, method: "runtime.reload" }));
|
|
16712
16738
|
}
|
|
16713
|
-
socket.close(4005, "Pairing required: update
|
|
16739
|
+
socket.close(4005, "Pairing required: update browse");
|
|
16714
16740
|
return;
|
|
16715
16741
|
}
|
|
16716
16742
|
if (!/^[A-Za-z0-9_-]{22,128}$/.test(extensionNonce)) {
|
|
@@ -16743,7 +16769,7 @@ class ExtensionHandshake {
|
|
|
16743
16769
|
if (mode === "old") {
|
|
16744
16770
|
const code = legacyPairingCode();
|
|
16745
16771
|
if (code === undefined)
|
|
16746
|
-
return refuse("old extension, no shared code", "Pairing failed: update
|
|
16772
|
+
return refuse("old extension, no shared code", "Pairing failed: update browse, then pair it with the code `pair` prints");
|
|
16747
16773
|
socket.send(JSON.stringify({ method: "auth.challenge", params: { nonce: relayNonce, proof: legacyPairingProof(code, "relay", port, relayNonce, extensionNonce), protocol } }));
|
|
16748
16774
|
return { ...base, expected: legacyPairingProof(code, "extension", port, extensionNonce, relayNonce) };
|
|
16749
16775
|
}
|
|
@@ -17036,9 +17062,9 @@ function createRelaySettingsSync(options) {
|
|
|
17036
17062
|
const extensionSettings = (method, values) => Effect29.gen(function* () {
|
|
17037
17063
|
if (!browsers.connected) {
|
|
17038
17064
|
const rejected = handshake.rejectedProtocol();
|
|
17039
|
-
return yield* Effect29.fail(new Error(rejected ? handshake.mismatchAdvice(rejected.version) : "The
|
|
17065
|
+
return yield* Effect29.fail(new Error(rejected ? handshake.mismatchAdvice(rejected.version) : "The browse extension isn't connected, so its settings can't be read or changed. Ask the user to open the browser with the extension; its options page shows whether it's connected."));
|
|
17040
17066
|
}
|
|
17041
|
-
const result = yield* sendToExtension({ method, ...values ? { params: { values } } : {} }).pipe(Effect29.mapError((error) => /Invalid extension command/.test(error.message) ? new Error(`The connected
|
|
17067
|
+
const result = yield* sendToExtension({ method, ...values ? { params: { values } } : {} }).pipe(Effect29.mapError((error) => /Invalid extension command/.test(error.message) ? new Error(`The connected browse extension is too old to read or change settings from here. Update browse (\`${installCommand()}\`) and reload the extension, or change them on its options page.`) : error));
|
|
17042
17068
|
return settingsFromExtension(result);
|
|
17043
17069
|
});
|
|
17044
17070
|
const settingOverrides = () => {
|
|
@@ -17106,9 +17132,9 @@ function createRelaySettingsSync(options) {
|
|
|
17106
17132
|
|
|
17107
17133
|
// src/relay/tab-presentation.ts
|
|
17108
17134
|
import { Effect as Effect30 } from "effect";
|
|
17109
|
-
var attachedBadge = { text: "ON", color: "#7c3aed", title: "Detach from
|
|
17110
|
-
var executingBadge = { text: "RUN", color: "#f59e0b", title: "
|
|
17111
|
-
var waitingBadge = (message) => ({ text: "WAIT", color: "#2563eb", title: `
|
|
17135
|
+
var attachedBadge = { text: "ON", color: "#7c3aed", title: "Detach from browse" };
|
|
17136
|
+
var executingBadge = { text: "RUN", color: "#f59e0b", title: "browse is running a script. Click to stop it and take this tab back." };
|
|
17137
|
+
var waitingBadge = (message) => ({ text: "WAIT", color: "#2563eb", title: `browse is waiting for you: ${message} Click here when you're done.` });
|
|
17112
17138
|
function createTabPresentation(options) {
|
|
17113
17139
|
const {
|
|
17114
17140
|
registry,
|
|
@@ -17322,7 +17348,7 @@ function createTabPresentation(options) {
|
|
|
17322
17348
|
}
|
|
17323
17349
|
if (action === "stop-execution" && sessionId) {
|
|
17324
17350
|
relayLog("execute.stopped", { tabId, session: sessionId, from: "toolbar" });
|
|
17325
|
-
sessions.stopExecution(sessionId, `The user took tab ${tabId} back (
|
|
17351
|
+
sessions.stopExecution(sessionId, `The user took tab ${tabId} back (browse toolbar button) while this script ran, so it was stopped. Don't use that tab again unless the user lends it back; ask the user how to continue.`);
|
|
17326
17352
|
}
|
|
17327
17353
|
}
|
|
17328
17354
|
Effect30.runPromise(toggleTab(tabId)).catch((error) => {
|
|
@@ -17402,7 +17428,7 @@ function createRelayTransportServers(deps) {
|
|
|
17402
17428
|
}
|
|
17403
17429
|
if (!isReady()) {
|
|
17404
17430
|
response.writeHead(503, { "content-type": "application/json; charset=utf-8", "retry-after": "1" });
|
|
17405
|
-
response.end(JSON.stringify({ error: "
|
|
17431
|
+
response.end(JSON.stringify({ error: "browse relay is starting", code: "relay-starting" }));
|
|
17406
17432
|
return;
|
|
17407
17433
|
}
|
|
17408
17434
|
const started = performance.now();
|
|
@@ -17424,7 +17450,7 @@ function createRelayTransportServers(deps) {
|
|
|
17424
17450
|
const websocketServer = new WebSocketServer({ noServer: true, maxPayload: 256 * 1024 * 1024 });
|
|
17425
17451
|
httpServer.on("upgrade", (request, socket, head) => {
|
|
17426
17452
|
if (!isReady()) {
|
|
17427
|
-
sendUpgradeError({ socket, status: 404, message: "
|
|
17453
|
+
sendUpgradeError({ socket, status: 404, message: "browse relay is starting" });
|
|
17428
17454
|
return;
|
|
17429
17455
|
}
|
|
17430
17456
|
const requestUrl = new URL(request.url ?? "/", endpointUrl);
|
|
@@ -17455,7 +17481,7 @@ function createRelayTransportServers(deps) {
|
|
|
17455
17481
|
return;
|
|
17456
17482
|
}
|
|
17457
17483
|
if (!hasRelayToken(request, authToken) && !secretsEqual(authToken, requestUrl.searchParams.get(relayTokenQueryParam) ?? undefined)) {
|
|
17458
|
-
sendUpgradeError({ socket, status: 401, message: "Missing or invalid
|
|
17484
|
+
sendUpgradeError({ socket, status: 401, message: "Missing or invalid browse relay token" });
|
|
17459
17485
|
return;
|
|
17460
17486
|
}
|
|
17461
17487
|
if (requestUrl.pathname.startsWith("/devtools/browser/")) {
|
|
@@ -17847,7 +17873,7 @@ class SessionCatalog {
|
|
|
17847
17873
|
} catch (error) {
|
|
17848
17874
|
if (isNodeError(error) && error.code === "ENOENT")
|
|
17849
17875
|
return [];
|
|
17850
|
-
throw new Error(`Could not read
|
|
17876
|
+
throw new Error(`Could not read browse session catalog at ${this.filePath}`, { cause: error });
|
|
17851
17877
|
}
|
|
17852
17878
|
try {
|
|
17853
17879
|
const sessions = Schema13.decodeUnknownSync(Catalog)(JSON.parse(text)).sessions;
|
|
@@ -17857,7 +17883,7 @@ class SessionCatalog {
|
|
|
17857
17883
|
return sessions;
|
|
17858
17884
|
} catch (error) {
|
|
17859
17885
|
const detail = error instanceof Error && error.message ? `: ${error.message}` : "";
|
|
17860
|
-
throw new Error(`Could not decode
|
|
17886
|
+
throw new Error(`Could not decode browse session catalog at ${this.filePath}${detail}`, { cause: error });
|
|
17861
17887
|
}
|
|
17862
17888
|
}
|
|
17863
17889
|
async save(sessions) {
|
|
@@ -17893,7 +17919,7 @@ class SessionCatalog {
|
|
|
17893
17919
|
try {
|
|
17894
17920
|
await fs14.rm(temporaryPath, { force: true });
|
|
17895
17921
|
} catch {}
|
|
17896
|
-
throw new Error(`Could not write
|
|
17922
|
+
throw new Error(`Could not write browse session catalog at ${this.filePath}`, { cause: error });
|
|
17897
17923
|
}
|
|
17898
17924
|
}
|
|
17899
17925
|
}
|
|
@@ -17950,7 +17976,7 @@ class SessionNavigationGuard {
|
|
|
17950
17976
|
if (decision.allowed)
|
|
17951
17977
|
return;
|
|
17952
17978
|
this.dependencies.log("allowlist.refused", { session: options.sessionId, method: options.method, reason: decision.reason });
|
|
17953
|
-
return `
|
|
17979
|
+
return `browse blocked ${options.method}: session ${options.sessionId} has a domain allowlist and ${decision.reason}. Ask the user before widening it (session_allowlist / \`session allow\`).`;
|
|
17954
17980
|
}
|
|
17955
17981
|
popupAttached(target) {
|
|
17956
17982
|
if (!this.enforced(target))
|
|
@@ -17998,7 +18024,7 @@ class SessionNavigationGuard {
|
|
|
17998
18024
|
if (decision.allowed)
|
|
17999
18025
|
return;
|
|
18000
18026
|
this.dependencies.send(target.tabId, "Page.stopLoading", {}).catch(() => {});
|
|
18001
|
-
this.addWarning(sessionId, `Domain allowlist: a tab of session ${sessionId} started to navigate to ${shortUrl(url)}, but ${decision.reason};
|
|
18027
|
+
this.addWarning(sessionId, `Domain allowlist: a tab of session ${sessionId} started to navigate to ${shortUrl(url)}, but ${decision.reason}; browse stopped it. Ask the user before widening the allowlist.`);
|
|
18002
18028
|
this.dependencies.log("allowlist.stopped", { tabId: target.tabId, session: sessionId, method, reason: decision.reason });
|
|
18003
18029
|
}
|
|
18004
18030
|
enforced(target) {
|
|
@@ -18024,7 +18050,7 @@ class SessionNavigationGuard {
|
|
|
18024
18050
|
const shownUrl = shortUrl(url);
|
|
18025
18051
|
const popup = this.freshPopups.has(target.tabId) && target.owner === "relay";
|
|
18026
18052
|
const action = popup ? "closed the popup" : "sent the tab back to about:blank";
|
|
18027
|
-
this.addWarning(sessionId, `Domain allowlist: a tab of session ${sessionId} went to ${shownUrl}, but ${reason};
|
|
18053
|
+
this.addWarning(sessionId, `Domain allowlist: a tab of session ${sessionId} went to ${shownUrl}, but ${reason}; browse ${action}. Ask the user before widening the allowlist.`);
|
|
18028
18054
|
this.dependencies.log("allowlist.violation", { tabId: target.tabId, session: sessionId, url: shownUrl, popup });
|
|
18029
18055
|
if (popup) {
|
|
18030
18056
|
this.freshPopups.delete(target.tabId);
|
|
@@ -18441,7 +18467,7 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
18441
18467
|
}
|
|
18442
18468
|
const existing = sessionId ? registry.listRootTargets().find((target) => target.opencodeBrowserSessionId === sessionId && target.owner === "relay") : undefined;
|
|
18443
18469
|
const link = (existing && browsers.browserTab(existing.tabId)?.link) ?? browsers.defaultLink();
|
|
18444
|
-
return link ? Effect34.succeed(link) : Effect34.fail(new Error("
|
|
18470
|
+
return link ? Effect34.succeed(link) : Effect34.fail(new Error("browse extension is not connected"));
|
|
18445
18471
|
});
|
|
18446
18472
|
const createAndAttachTab = Effect34.fnUntraced(function* (createOptions) {
|
|
18447
18473
|
const link = yield* sessionBrowser(createOptions.opencodeBrowserSessionId);
|
|
@@ -18685,11 +18711,11 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
18685
18711
|
await sessionCatalog.save(restored);
|
|
18686
18712
|
return restored;
|
|
18687
18713
|
},
|
|
18688
|
-
catch: (cause) => cause instanceof Error ? cause : new Error("Load
|
|
18714
|
+
catch: (cause) => cause instanceof Error ? cause : new Error("Load browse session catalog", { cause })
|
|
18689
18715
|
});
|
|
18690
18716
|
yield* Effect34.try({
|
|
18691
18717
|
try: () => sessions.restore(restoredSessions),
|
|
18692
|
-
catch: (cause) => cause instanceof Error ? cause : new Error("Restore
|
|
18718
|
+
catch: (cause) => cause instanceof Error ? cause : new Error("Restore browse sessions", { cause })
|
|
18693
18719
|
});
|
|
18694
18720
|
catalogWritesEnabled = true;
|
|
18695
18721
|
if (managed) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import"./main-ek76h9k1.mjs";
|
|
1
2
|
import {
|
|
2
3
|
cliCommand,
|
|
3
4
|
installCommand,
|
|
@@ -16,15 +17,15 @@ import {
|
|
|
16
17
|
installVersion,
|
|
17
18
|
pruneVersions,
|
|
18
19
|
switchVersion
|
|
19
|
-
} from "./main-
|
|
20
|
+
} from "./main-0m569gdn.mjs";
|
|
21
|
+
import"./main-3gthmkzw.mjs";
|
|
20
22
|
import {
|
|
21
23
|
Service,
|
|
22
24
|
layerFetch,
|
|
23
25
|
restartRelay,
|
|
24
26
|
startManagedRelay
|
|
25
|
-
} from "./main-
|
|
26
|
-
import"./main-
|
|
27
|
-
import"./main-arx3pfyn.mjs";
|
|
27
|
+
} from "./main-qm2keaeb.mjs";
|
|
28
|
+
import"./main-7qs0mwvc.mjs";
|
|
28
29
|
|
|
29
30
|
// src/update/runtime-update.ts
|
|
30
31
|
import { Effect } from "effect";
|
|
@@ -68,7 +69,7 @@ function npmOnly(command) {
|
|
|
68
69
|
const runtime = npmRuntime();
|
|
69
70
|
if (runtime)
|
|
70
71
|
return runtime;
|
|
71
|
-
log(`\`${command}\` is for
|
|
72
|
+
log(`\`${command}\` is for installs that update themselves (\`${installCommand()}\`); this one isn't.`);
|
|
72
73
|
process.exitCode = 1;
|
|
73
74
|
return;
|
|
74
75
|
}
|