@fungi.computer/oyster 0.2.0 → 0.3.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/README.md +376 -72
- package/dist/agents.d.ts +3 -2
- package/dist/agents.js +4 -2
- package/dist/app-builds.d.ts +81 -8
- package/dist/app-builds.js +210 -60
- package/dist/app-storage.d.ts +3 -3
- package/dist/approval-capability.d.ts +5 -0
- package/dist/approval-capability.js +46 -0
- package/dist/approval-home.d.ts +51 -0
- package/dist/approval-home.js +226 -0
- package/dist/apps/catalog.d.ts +335 -0
- package/dist/apps/catalog.js +46 -0
- package/dist/apps/gate.d.ts +20 -6
- package/dist/apps/gate.js +42 -16
- package/dist/apps/index.d.ts +3 -1
- package/dist/apps/index.js +3 -1
- package/dist/apps/installation.d.ts +132 -51
- package/dist/apps/installation.js +176 -70
- package/dist/apps/permissions.d.ts +247 -0
- package/dist/apps/permissions.js +255 -0
- package/dist/boot-capability-route.d.ts +15 -0
- package/dist/boot-capability-route.js +196 -0
- package/dist/boot-capability.d.ts +103 -0
- package/dist/boot-capability.js +141 -0
- package/dist/boot-construction.d.ts +12 -0
- package/dist/boot-construction.js +175 -0
- package/dist/boot-gate.d.ts +267 -0
- package/dist/boot-gate.js +236 -0
- package/dist/boot-host-script.d.ts +2 -0
- package/dist/boot-host-script.js +1 -0
- package/dist/boot-mount.d.ts +21 -0
- package/dist/boot-mount.js +65 -0
- package/dist/browser/boot.d.ts +73 -0
- package/dist/browser/boot.js +502 -0
- package/dist/browser/browser.d.ts +104 -0
- package/dist/browser/browser.js +374 -0
- package/dist/browser/capability-source.d.ts +76 -0
- package/dist/browser/capability-source.js +338 -0
- package/dist/browser/cdp-client.d.ts +56 -0
- package/dist/browser/cdp-client.js +184 -0
- package/dist/browser/errors.d.ts +79 -0
- package/dist/browser/errors.js +32 -0
- package/dist/browser/index.d.ts +16 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/lease-registry.d.ts +61 -0
- package/dist/browser/lease-registry.js +101 -0
- package/dist/browser/operations.d.ts +225 -0
- package/dist/browser/operations.js +137 -0
- package/dist/browser/page.d.ts +25 -0
- package/dist/browser/page.js +172 -0
- package/dist/browser/quick-reads.d.ts +29 -0
- package/dist/browser/quick-reads.js +160 -0
- package/dist/browser/sessions.d.ts +52 -0
- package/dist/browser/sessions.js +118 -0
- package/dist/browser/url-policy.d.ts +18 -0
- package/dist/browser/url-policy.js +58 -0
- package/dist/build/app-imports.d.ts +12 -0
- package/dist/build/app-imports.js +105 -0
- package/dist/build/build-policy.d.ts +5 -3
- package/dist/build/build-policy.js +5 -3
- package/dist/build/candidate-builder.d.ts +1 -1
- package/dist/build/candidate-cli.mjs +8375 -334
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +28 -6
- package/dist/build/snapshot-package-store.d.ts +1 -1
- package/dist/build/snapshot-package-store.js +7 -2
- package/dist/build/workspace-build.d.ts +92 -5
- package/dist/build/workspace-build.js +93 -19
- package/dist/build.d.ts +1 -1
- package/dist/build.js +1 -1
- package/dist/catalog-frame.js +1 -1
- package/dist/cloudflare.d.ts +13 -3
- package/dist/cloudflare.js +102 -4
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.js +4 -2
- package/dist/git-auth.d.ts +0 -2
- package/dist/git-auth.js +0 -2
- package/dist/git.d.ts +0 -1
- package/dist/git.js +0 -2
- package/dist/host.d.ts +39 -13
- package/dist/host.js +268 -142
- package/dist/index.d.ts +25 -15
- package/dist/index.js +39 -16
- package/dist/install-release-cli.d.ts +2 -0
- package/dist/install-release-cli.js +46 -0
- package/dist/installation-client.d.ts +43 -0
- package/dist/installation-client.js +178 -0
- package/dist/installation-execution.d.ts +11 -0
- package/dist/installation-execution.js +184 -0
- package/dist/installation-operations.d.ts +1437 -0
- package/dist/installation-operations.js +664 -0
- package/dist/installation-store-generation.d.ts +3 -0
- package/dist/installation-store-generation.js +16 -0
- package/dist/instance-id.d.ts +10 -0
- package/dist/instance-id.js +10 -0
- package/dist/instance-refusal.d.ts +11 -0
- package/dist/instance-refusal.js +21 -0
- package/dist/internal/delivery.js +4 -2
- package/dist/internal/host-gate.js +7 -1
- package/dist/internal/registry-installations.js +4 -4
- package/dist/keys.d.ts +5 -1
- package/dist/keys.js +10 -2
- package/dist/machine-link/authority.d.ts +89 -0
- package/dist/machine-link/authority.js +37 -0
- package/dist/machine-link/client.d.ts +66 -0
- package/dist/machine-link/client.js +94 -0
- package/dist/machine-link/delivery.d.ts +18 -0
- package/dist/machine-link/delivery.js +55 -0
- package/dist/machine-link/enrollment.d.ts +111 -0
- package/dist/machine-link/enrollment.js +286 -0
- package/dist/machine-link/fences.d.ts +37 -0
- package/dist/machine-link/fences.js +166 -0
- package/dist/machine-link/home.d.ts +20 -0
- package/dist/machine-link/home.js +24 -0
- package/dist/machine-link/host.d.ts +43 -0
- package/dist/machine-link/host.js +189 -0
- package/dist/machine-link/index.d.ts +15 -0
- package/dist/machine-link/index.js +15 -0
- package/dist/machine-link/link.d.ts +48 -0
- package/dist/machine-link/link.js +223 -0
- package/dist/machine-link/presence.d.ts +10 -0
- package/dist/machine-link/presence.js +25 -0
- package/dist/machine-link/workers.d.ts +6 -0
- package/dist/machine-link/workers.js +6 -0
- package/dist/mcp-owner.d.ts +2 -2
- package/dist/mcp-owner.js +41 -33
- package/dist/mcp.d.ts +15 -3
- package/dist/mcp.js +146 -25
- package/dist/pack.d.ts +14 -3
- package/dist/pack.js +44 -19
- package/dist/page/app-access.d.ts +7 -0
- package/dist/page/app-access.js +75 -0
- package/dist/page/boot-host.js +57 -0
- package/dist/page/capabilities.d.ts +3 -3
- package/dist/page/capabilities.js +16 -12
- package/dist/page/computer-handles.d.ts +3 -3
- package/dist/page/computer-target.d.ts +3 -0
- package/dist/page/computer-target.js +9 -0
- package/dist/page/mount.d.ts +23 -3
- package/dist/page/mount.js +152 -16
- package/dist/page-assets.d.ts +22 -0
- package/dist/page-assets.js +28 -0
- package/dist/page-script.d.ts +3 -0
- package/dist/page-script.js +5 -4
- package/dist/release.d.ts +13 -6
- package/dist/release.js +10 -7
- package/dist/root-data.d.ts +16 -6
- package/dist/root-data.js +2 -2
- package/dist/source/index.d.ts +1 -1
- package/dist/source/team-source.d.ts +20 -8
- package/dist/source/team-source.js +39 -10
- package/dist/source/template-seed.d.ts +2 -2
- package/dist/standalone/agent-mail.d.ts +24 -0
- package/dist/standalone/agent-mail.js +62 -0
- package/dist/standalone/agent-store.d.ts +9 -0
- package/dist/standalone/agent-store.js +30 -0
- package/dist/standalone/agents.d.ts +68 -9
- package/dist/standalone/agents.js +428 -56
- package/dist/standalone/app-builds-binding.d.ts +20 -2
- package/dist/standalone/app-builds-binding.js +147 -23
- package/dist/standalone/app-set.d.ts +5 -2
- package/dist/standalone/app-set.js +174 -143
- package/dist/standalone/browser.d.ts +11 -0
- package/dist/standalone/browser.js +72 -0
- package/dist/standalone/builds.d.ts +13 -4
- package/dist/standalone/builds.js +28 -44
- package/dist/standalone/computer-egress.d.ts +2 -0
- package/dist/standalone/computer-egress.js +13 -4
- package/dist/standalone/computer-fleet.d.ts +43 -0
- package/dist/standalone/computer-fleet.js +200 -0
- package/dist/standalone/computer.d.ts +44 -9
- package/dist/standalone/computer.js +129 -22
- package/dist/standalone/install-apps-cli.js +43 -6
- package/dist/standalone/instance.d.ts +2 -2
- package/dist/standalone/instance.js +8 -11
- package/dist/standalone/machine-link.d.ts +354 -0
- package/dist/standalone/machine-link.js +368 -0
- package/dist/standalone/media.d.ts +2 -2
- package/dist/standalone/media.js +19 -16
- package/dist/standalone/preview.d.ts +2 -2
- package/dist/standalone/preview.js +37 -10
- package/dist/standalone/registry.d.ts +149 -9
- package/dist/standalone/registry.js +299 -8
- package/dist/standalone/worker.d.ts +19 -5
- package/dist/standalone/worker.js +90 -54
- package/dist/standalone/workflow-binding.d.ts +18 -22
- package/dist/standalone/workflow-binding.js +18 -18
- package/dist/standalone/workflow-control.d.ts +4 -0
- package/dist/standalone/workflow-control.js +40 -0
- package/dist/standalone/workflow-events.d.ts +2 -2
- package/dist/standalone/workflow-events.js +9 -10
- package/dist/standalone/workflow-git-capture.js +4 -3
- package/dist/standalone/workflow-host.d.ts +162 -161
- package/dist/standalone/workflow-host.js +101 -21
- package/dist/standalone/workflow-node-capture.js +11 -4
- package/dist/standalone/workflow-schedules.d.ts +5 -2
- package/dist/standalone/workflow-schedules.js +9 -10
- package/dist/standalone/workflow-trigger-host.d.ts +65 -5
- package/dist/standalone/workflow-trigger-host.js +2 -2
- package/dist/standalone/workflow-webhooks.d.ts +22 -0
- package/dist/standalone/workflow-webhooks.js +39 -0
- package/dist/standalone/workflows.d.ts +140 -43
- package/dist/standalone/workflows.js +198 -39
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.js +14 -3
- package/dist/testing/installation.js +26 -2
- package/dist/testing/worker.js +1 -1
- package/dist/testing/workers-runtime.d.ts +8 -2
- package/dist/testing/workers-runtime.js +20 -2
- package/dist/webhook-abuse.d.ts +25 -0
- package/dist/webhook-abuse.js +54 -0
- package/dist/webhook-custody.d.ts +144 -0
- package/dist/webhook-custody.js +222 -0
- package/dist/webhook-ingress.d.ts +36 -0
- package/dist/webhook-ingress.js +146 -0
- package/dist/webhooks.d.ts +111 -0
- package/dist/webhooks.js +189 -0
- package/dist/workers-git.d.ts +12 -17
- package/dist/workers-git.js +60 -52
- package/dist/workers.d.ts +224 -13
- package/dist/workers.js +351 -51
- package/dist/workflow-agent-authority.d.ts +8 -0
- package/dist/workflow-agent-authority.js +42 -0
- package/dist/workflows.d.ts +66 -0
- package/dist/workflows.js +97 -0
- package/docs/api/README.md +63 -37
- package/docs/api/agents.md +17 -0
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +1394 -543
- package/docs/api/browser/boot.md +132 -0
- package/docs/api/browser.md +1440 -0
- package/docs/api/build-contract.md +38 -25
- package/docs/api/build.md +263 -84
- package/docs/api/cloudflare.md +93 -7
- package/docs/api/git.md +3 -13
- package/docs/api/host.md +944 -118
- package/docs/api/index.md +320 -18
- package/docs/api/keys.md +24 -1
- package/docs/api/mcp.md +59 -12
- package/docs/api/modules.md +33 -40
- package/docs/api/pack.md +57 -8
- package/docs/api/page/mount.md +92 -4
- package/docs/api/workers-git.md +103 -24
- package/docs/api/workers.md +1581 -244
- package/docs/site/agent-quickstart.mdx +28 -14
- package/examples/standalone/worker.ts +3 -1
- package/examples/standalone/wrangler.jsonc +2 -0
- package/package.json +73 -71
- package/dist/installation-consent.d.ts +0 -81
- package/dist/installation-consent.js +0 -84
- package/dist/shell/app-directory.d.ts +0 -542
- package/dist/shell/app-directory.js +0 -196
- package/dist/shell/browser-actions.d.ts +0 -7
- package/dist/shell/browser-actions.js +0 -56
- package/dist/shell/command-host.d.ts +0 -112
- package/dist/shell/command-host.js +0 -502
- package/dist/shell/commands.d.ts +0 -25
- package/dist/shell/commands.js +0 -78
- package/dist/shell/index.d.ts +0 -8
- package/dist/shell/index.js +0 -8
- package/dist/shell/keys.d.ts +0 -41
- package/dist/shell/keys.js +0 -134
- package/dist/shell/shell-module.d.ts +0 -55
- package/dist/shell/shell-module.js +0 -86
- package/dist/shell/window-manager.d.ts +0 -128
- package/dist/shell/window-manager.js +0 -730
- package/dist/shell/workbench-contract.d.ts +0 -68
- package/dist/shell/workbench-directory.d.ts +0 -332
- package/dist/shell/workbench-directory.js +0 -35
- package/dist/shell/workbench-host-types.d.ts +0 -96
- package/dist/shell/workbench-host-types.js +0 -1
- package/dist/shell/workbench-host.d.ts +0 -34
- package/dist/shell/workbench-host.js +0 -352
- package/dist/shell/workbench-model.d.ts +0 -194
- package/dist/shell/workbench-model.js +0 -53
- package/dist/shell/workbench.d.ts +0 -4
- package/dist/shell/workbench.js +0 -2
- package/docs/api/shell/app-directory.md +0 -599
- package/docs/api/shell/command-host.md +0 -224
- package/docs/api/shell/commands.md +0 -197
- package/docs/api/shell/keys.md +0 -183
- package/docs/api/shell/shell-module.md +0 -204
- package/docs/api/shell/window-manager.md +0 -267
- package/docs/api/shell/workbench-directory.md +0 -119
- package/docs/api/shell/workbench.md +0 -205
- package/docs/api/shell.md +0 -1915
- /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { CapabilityFailed, Mycelium, } from "@fungi.computer/mycelium";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { clickInputSchema, closeInputSchema, extractInputSchema, leaseAckSchema, markdownInputSchema, navigateInputSchema, openInputSchema, pressInputSchema, scrapeInputSchema, scrollInputSchema, snapshotInputSchema, typeInputSchema, urlInputSchema, markdownResultSchema, linksResultSchema, scrapeResultSchema, extractResultSchema, openResultSchema, navigateResultSchema, snapshotResultSchema, closeResultSchema, } from "./operations.js";
|
|
4
|
+
import { CloudBrowserCapacityExceeded, CloudBrowserCreditDenied, CloudBrowserLeaseNotFound, CloudBrowserLeasePaused, CloudBrowserTimeout, CloudBrowserUnavailable, CloudBrowserUpstreamError, CloudBrowserUrlDenied, } from "./errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* The `cloudBrowser` Mycelium module: a work browser for reading and driving
|
|
7
|
+
* public pages. To show the member something, use the Session browser
|
|
8
|
+
* tools. Quick Reads (`markdown`/`links`/`scrape`/`extract`) need no open
|
|
9
|
+
* session; `open`..`close` share one driven session by `leaseId`.
|
|
10
|
+
* `screenshot`/`pdf` save to a named Computer path instead of returning
|
|
11
|
+
* bytes, and `handoff` asks a Team member to drive the page for a login or
|
|
12
|
+
* captcha, through the existing `handoff` card, never a CDP URL.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The one source of truth for every `cloudBrowser` description: the module
|
|
16
|
+
* itself and each operation. `cloud-browser-descriptions.test.ts` imports
|
|
17
|
+
* this and asserts every entry stays under 1000 characters (the acceptance
|
|
18
|
+
* criterion in `issues/retained-features-v1/02-*.md`); the guard right below
|
|
19
|
+
* repeats that check at import time so a regression fails fast either way.
|
|
20
|
+
*/
|
|
21
|
+
export const CLOUD_BROWSER_DESCRIPTIONS = Object.freeze({
|
|
22
|
+
module: "A work browser for reading and driving public pages. markdown/links/scrape/extract " +
|
|
23
|
+
"need no session. open/navigate/snapshot/click/type/press/scroll drive one session by " +
|
|
24
|
+
"leaseId; screenshot/pdf save to a Computer path (bytes never enter this transcript). " +
|
|
25
|
+
"handoff asks a Team member to take over for a login or captcha; close ends the lease. " +
|
|
26
|
+
"To show the member something yourself, use the Session browser tools, not this.",
|
|
27
|
+
markdown: "Render a JavaScript page and return it as markdown, truncated to maxChars (default 20000).",
|
|
28
|
+
links: "Render a page and return up to 500 links found on it.",
|
|
29
|
+
scrape: "Render a page and return matched elements for up to 10 CSS selectors, bounded to 64 KiB.",
|
|
30
|
+
extract: "Render a page and extract structured data matching a prompt.",
|
|
31
|
+
open: "Open a driven browser session, optionally navigating to a URL, and return its leaseId.",
|
|
32
|
+
navigate: "Navigate an open session's page to a URL.",
|
|
33
|
+
snapshot: "Read an open session's page as a bounded accessibility-tree text summary.",
|
|
34
|
+
click: "Click at a viewport pixel coordinate in an open session.",
|
|
35
|
+
type: "Insert text at the current focus in an open session.",
|
|
36
|
+
press: 'Press one key (e.g. "Enter", "Tab") in an open session.',
|
|
37
|
+
scroll: "Scroll an open session's page by a pixel delta.",
|
|
38
|
+
screenshot: "Capture a PNG screenshot of an open session's page and save it to a Computer path; returns the saved byte count, never the bytes.",
|
|
39
|
+
pdf: "Render an open session's page to PDF and save it to a Computer path; returns the saved byte count, never the bytes.",
|
|
40
|
+
handoff: "Ask a Team member to take over an open session for a login or captcha; a card appears in the conversation and the run continues once they finish.",
|
|
41
|
+
close: "End a driven session and release its lease.",
|
|
42
|
+
});
|
|
43
|
+
for (const [name, text] of Object.entries(CLOUD_BROWSER_DESCRIPTIONS))
|
|
44
|
+
if (text.length >= 1000)
|
|
45
|
+
throw new Error(`cloudBrowser.${name} description must stay under 1000 characters`);
|
|
46
|
+
function isCloudBrowserFailure(cause) {
|
|
47
|
+
return (cause instanceof CloudBrowserUrlDenied ||
|
|
48
|
+
cause instanceof CloudBrowserUpstreamError ||
|
|
49
|
+
cause instanceof CloudBrowserTimeout ||
|
|
50
|
+
cause instanceof CloudBrowserLeaseNotFound ||
|
|
51
|
+
cause instanceof CloudBrowserLeasePaused ||
|
|
52
|
+
cause instanceof CloudBrowserCapacityExceeded ||
|
|
53
|
+
cause instanceof CloudBrowserCreditDenied ||
|
|
54
|
+
cause instanceof CloudBrowserUnavailable);
|
|
55
|
+
}
|
|
56
|
+
/** Map the closed cloud-browser failure union onto Mycelium's admitted diagnostic. */
|
|
57
|
+
function cloudBrowserFailureMessage(error) {
|
|
58
|
+
switch (error._tag) {
|
|
59
|
+
case "CloudBrowserUrlDenied":
|
|
60
|
+
return error.message;
|
|
61
|
+
case "CloudBrowserUpstreamError":
|
|
62
|
+
return `the cloud browser could not complete ${error.op}`;
|
|
63
|
+
case "CloudBrowserTimeout":
|
|
64
|
+
return `${error.op} did not answer in time`;
|
|
65
|
+
case "CloudBrowserLeaseNotFound":
|
|
66
|
+
return "that leaseId is not open for this session; call open first";
|
|
67
|
+
case "CloudBrowserLeasePaused":
|
|
68
|
+
return "a Team member is driving this lease; wait for them to finish";
|
|
69
|
+
case "CloudBrowserCapacityExceeded":
|
|
70
|
+
return "this Team has too many cloud browsers open at once; close one first";
|
|
71
|
+
case "CloudBrowserCreditDenied":
|
|
72
|
+
return "this Team has no credit for cloud browser time";
|
|
73
|
+
case "CloudBrowserUnavailable":
|
|
74
|
+
return error.reason === "paused"
|
|
75
|
+
? "the cloud browser is turned off for this deployment"
|
|
76
|
+
: "the cloud browser is not configured";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function toCapabilityFailed(operation, cause) {
|
|
80
|
+
if (cause instanceof CapabilityFailed)
|
|
81
|
+
return cause;
|
|
82
|
+
if (isCloudBrowserFailure(cause))
|
|
83
|
+
return new CapabilityFailed({
|
|
84
|
+
namespace: "cloudBrowser",
|
|
85
|
+
operation,
|
|
86
|
+
cause: {
|
|
87
|
+
kind: cause._tag === "CloudBrowserUrlDenied"
|
|
88
|
+
? "InvalidInput"
|
|
89
|
+
: cause._tag === "CloudBrowserLeaseNotFound"
|
|
90
|
+
? "InvalidInput"
|
|
91
|
+
: "Unavailable",
|
|
92
|
+
message: cloudBrowserFailureMessage(cause),
|
|
93
|
+
},
|
|
94
|
+
rawCause: cause,
|
|
95
|
+
});
|
|
96
|
+
return new CapabilityFailed({
|
|
97
|
+
namespace: "cloudBrowser",
|
|
98
|
+
operation,
|
|
99
|
+
cause: { kind: "Unavailable", message: "the cloud browser failed" },
|
|
100
|
+
rawCause: cause,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const destinationSchema = z.strictObject({
|
|
104
|
+
leaseId: leaseAckSchema.shape.leaseId,
|
|
105
|
+
computerId: z.string().min(1).max(256),
|
|
106
|
+
path: z.string().min(1).max(1024),
|
|
107
|
+
});
|
|
108
|
+
const destinationResultSchema = z.strictObject({
|
|
109
|
+
leaseId: z.string(),
|
|
110
|
+
computerId: z.string(),
|
|
111
|
+
path: z.string(),
|
|
112
|
+
bytes: z.number().int().nonnegative(),
|
|
113
|
+
});
|
|
114
|
+
const handoffResultSchema = z.strictObject({
|
|
115
|
+
status: z.literal("handed_off"),
|
|
116
|
+
});
|
|
117
|
+
/** Build the `cloudBrowser` module once a Session identity is known. */
|
|
118
|
+
function buildCloudBrowserModule(options) {
|
|
119
|
+
/** Run one call, mapping any thrown failure and parsing the result through `schema`. */
|
|
120
|
+
async function run(operation, schema, action) {
|
|
121
|
+
try {
|
|
122
|
+
return schema.parse(await action());
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
throw toCapabilityFailed(operation, error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const binaryResultSchema = z.strictObject({
|
|
129
|
+
leaseId: z.string(),
|
|
130
|
+
bytes: z.instanceof(Uint8Array),
|
|
131
|
+
contentType: z.string(),
|
|
132
|
+
});
|
|
133
|
+
function saveBinary(saveFile) {
|
|
134
|
+
return async (operation, leaseId, computerId, path, defaultContentType, signal) => {
|
|
135
|
+
const raw = await run(operation, binaryResultSchema, () => options.call({ kind: operation, leaseId }, signal));
|
|
136
|
+
try {
|
|
137
|
+
const saved = await saveFile({
|
|
138
|
+
computerId,
|
|
139
|
+
path,
|
|
140
|
+
bytes: raw.bytes,
|
|
141
|
+
contentType: raw.contentType || defaultContentType,
|
|
142
|
+
});
|
|
143
|
+
return destinationResultSchema.parse({ leaseId, ...saved });
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
throw toCapabilityFailed(operation, error);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function saveOperations(saveFile) {
|
|
151
|
+
const save = saveBinary(saveFile);
|
|
152
|
+
return {
|
|
153
|
+
screenshot: Mycelium.operation({
|
|
154
|
+
replay: "unsafe",
|
|
155
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.screenshot,
|
|
156
|
+
input: destinationSchema,
|
|
157
|
+
output: destinationResultSchema,
|
|
158
|
+
execute: (input, context) => save("screenshot", input.leaseId, input.computerId, input.path, "image/png", context.signal),
|
|
159
|
+
}),
|
|
160
|
+
pdf: Mycelium.operation({
|
|
161
|
+
replay: "unsafe",
|
|
162
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.pdf,
|
|
163
|
+
input: destinationSchema,
|
|
164
|
+
output: destinationResultSchema,
|
|
165
|
+
execute: (input, context) => save("pdf", input.leaseId, input.computerId, input.path, "application/pdf", context.signal),
|
|
166
|
+
}),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function handoffOperation(handoff) {
|
|
170
|
+
return {
|
|
171
|
+
handoff: Mycelium.operation({
|
|
172
|
+
replay: "unsafe",
|
|
173
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.handoff,
|
|
174
|
+
input: z.strictObject({
|
|
175
|
+
leaseId: leaseAckSchema.shape.leaseId,
|
|
176
|
+
reason: z.string().min(1).max(500),
|
|
177
|
+
}),
|
|
178
|
+
output: handoffResultSchema,
|
|
179
|
+
execute: (input) => run("handoff", handoffResultSchema, async () => {
|
|
180
|
+
await handoff(input.leaseId, input.reason);
|
|
181
|
+
return { status: "handed_off" };
|
|
182
|
+
}),
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const quickReads = {
|
|
187
|
+
markdown: Mycelium.operation({
|
|
188
|
+
replay: "unsafe",
|
|
189
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.markdown,
|
|
190
|
+
input: markdownInputSchema,
|
|
191
|
+
output: markdownResultSchema,
|
|
192
|
+
execute: (input, context) => run("markdown", markdownResultSchema, () => options.call({ kind: "markdown", input }, context.signal)),
|
|
193
|
+
}),
|
|
194
|
+
links: Mycelium.operation({
|
|
195
|
+
replay: "unsafe",
|
|
196
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.links,
|
|
197
|
+
input: urlInputSchema,
|
|
198
|
+
output: linksResultSchema,
|
|
199
|
+
execute: (input, context) => run("links", linksResultSchema, () => options.call({ kind: "links", url: input.url }, context.signal)),
|
|
200
|
+
}),
|
|
201
|
+
scrape: Mycelium.operation({
|
|
202
|
+
replay: "unsafe",
|
|
203
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.scrape,
|
|
204
|
+
input: scrapeInputSchema,
|
|
205
|
+
output: scrapeResultSchema,
|
|
206
|
+
execute: (input, context) => run("scrape", scrapeResultSchema, () => options.call({ kind: "scrape", input }, context.signal)),
|
|
207
|
+
}),
|
|
208
|
+
extract: Mycelium.operation({
|
|
209
|
+
replay: "unsafe",
|
|
210
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.extract,
|
|
211
|
+
input: extractInputSchema,
|
|
212
|
+
output: extractResultSchema,
|
|
213
|
+
execute: (input, context) => run("extract", extractResultSchema, () => options.call({ kind: "extract", input }, context.signal)),
|
|
214
|
+
}),
|
|
215
|
+
};
|
|
216
|
+
const driven = {
|
|
217
|
+
open: Mycelium.operation({
|
|
218
|
+
replay: "unsafe",
|
|
219
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.open,
|
|
220
|
+
input: openInputSchema,
|
|
221
|
+
output: openResultSchema,
|
|
222
|
+
execute: (input, context) => run("open", openResultSchema, () => options.call({ kind: "open", url: input.url }, context.signal)),
|
|
223
|
+
}),
|
|
224
|
+
navigate: Mycelium.operation({
|
|
225
|
+
replay: "unsafe",
|
|
226
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.navigate,
|
|
227
|
+
input: navigateInputSchema,
|
|
228
|
+
output: navigateResultSchema,
|
|
229
|
+
execute: (input, context) => run("navigate", navigateResultSchema, () => options.call({ kind: "navigate", leaseId: input.leaseId, url: input.url }, context.signal)),
|
|
230
|
+
}),
|
|
231
|
+
snapshot: Mycelium.operation({
|
|
232
|
+
replay: "unsafe",
|
|
233
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.snapshot,
|
|
234
|
+
input: snapshotInputSchema,
|
|
235
|
+
output: snapshotResultSchema,
|
|
236
|
+
execute: (input, context) => run("snapshot", snapshotResultSchema, () => options.call({ kind: "snapshot", leaseId: input.leaseId }, context.signal)),
|
|
237
|
+
}),
|
|
238
|
+
click: Mycelium.operation({
|
|
239
|
+
replay: "unsafe",
|
|
240
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.click,
|
|
241
|
+
input: clickInputSchema,
|
|
242
|
+
output: leaseAckSchema,
|
|
243
|
+
execute: (input, context) => run("click", leaseAckSchema, () => options.call({ kind: "click", leaseId: input.leaseId, x: input.x, y: input.y }, context.signal)),
|
|
244
|
+
}),
|
|
245
|
+
type: Mycelium.operation({
|
|
246
|
+
replay: "unsafe",
|
|
247
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.type,
|
|
248
|
+
input: typeInputSchema,
|
|
249
|
+
output: leaseAckSchema,
|
|
250
|
+
execute: (input, context) => run("type", leaseAckSchema, () => options.call({ kind: "type", leaseId: input.leaseId, text: input.text }, context.signal)),
|
|
251
|
+
}),
|
|
252
|
+
press: Mycelium.operation({
|
|
253
|
+
replay: "unsafe",
|
|
254
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.press,
|
|
255
|
+
input: pressInputSchema,
|
|
256
|
+
output: leaseAckSchema,
|
|
257
|
+
execute: (input, context) => run("press", leaseAckSchema, () => options.call({ kind: "press", leaseId: input.leaseId, key: input.key }, context.signal)),
|
|
258
|
+
}),
|
|
259
|
+
scroll: Mycelium.operation({
|
|
260
|
+
replay: "unsafe",
|
|
261
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.scroll,
|
|
262
|
+
input: scrollInputSchema,
|
|
263
|
+
output: leaseAckSchema,
|
|
264
|
+
execute: (input, context) => run("scroll", leaseAckSchema, () => options.call({
|
|
265
|
+
kind: "scroll",
|
|
266
|
+
leaseId: input.leaseId,
|
|
267
|
+
deltaX: input.deltaX,
|
|
268
|
+
deltaY: input.deltaY,
|
|
269
|
+
}, context.signal)),
|
|
270
|
+
}),
|
|
271
|
+
close: Mycelium.operation({
|
|
272
|
+
replay: "unsafe",
|
|
273
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.close,
|
|
274
|
+
input: closeInputSchema,
|
|
275
|
+
output: closeResultSchema,
|
|
276
|
+
execute: (input, context) => run("close", closeResultSchema, () => options.call({ kind: "close", leaseId: input.leaseId }, context.signal)),
|
|
277
|
+
}),
|
|
278
|
+
};
|
|
279
|
+
// The host offers only what it has: Quick Reads, a Computer to save to and
|
|
280
|
+
// a member to hand off to are each optional; driven sessions never are.
|
|
281
|
+
const operations = { ...driven };
|
|
282
|
+
if (options.quickReads)
|
|
283
|
+
Object.assign(operations, quickReads);
|
|
284
|
+
if (options.saveFile !== null)
|
|
285
|
+
Object.assign(operations, saveOperations(options.saveFile));
|
|
286
|
+
if (options.handoff !== null)
|
|
287
|
+
Object.assign(operations, handoffOperation(options.handoff));
|
|
288
|
+
return Mycelium.module({
|
|
289
|
+
id: "cloud-browser",
|
|
290
|
+
name: "cloudBrowser",
|
|
291
|
+
description: CLOUD_BROWSER_DESCRIPTIONS.module,
|
|
292
|
+
operations,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Compose the `cloudBrowser` source for one Session, on either host. `browser`
|
|
297
|
+
* is the module's lease door (in-process, or the Hub's egress RPC); `saveFile`
|
|
298
|
+
* is the host's Computer bytes writer and `requestHandoff` its "needs you"
|
|
299
|
+
* card. A host without one passes `null` and the matching operations are not
|
|
300
|
+
* offered. A lease needs a Session identity, so a session-free host (e.g. the
|
|
301
|
+
* MCP execute path) gets no module at all.
|
|
302
|
+
*/
|
|
303
|
+
export function browserCapabilitySource(deps) {
|
|
304
|
+
return {
|
|
305
|
+
id: "cloud-browser",
|
|
306
|
+
acquire: ({ signal, session }) => {
|
|
307
|
+
signal.throwIfAborted();
|
|
308
|
+
const sessionId = session?.sessionId;
|
|
309
|
+
const identity = deps.identity();
|
|
310
|
+
if (sessionId === undefined || identity === null)
|
|
311
|
+
return Promise.resolve({
|
|
312
|
+
snapshot: { version: "0", modules: [] },
|
|
313
|
+
release: () => Promise.resolve(),
|
|
314
|
+
});
|
|
315
|
+
const owner = {
|
|
316
|
+
teamId: identity.teamId,
|
|
317
|
+
agentId: identity.agentId,
|
|
318
|
+
sessionId,
|
|
319
|
+
};
|
|
320
|
+
const requestHandoff = deps.requestHandoff;
|
|
321
|
+
const registration = buildCloudBrowserModule({
|
|
322
|
+
call: (op, opSignal) => deps.browser.call(owner, op, opSignal),
|
|
323
|
+
quickReads: deps.quickReads,
|
|
324
|
+
saveFile: deps.saveFile,
|
|
325
|
+
handoff: requestHandoff === null
|
|
326
|
+
? null
|
|
327
|
+
: async (leaseId, reason) => {
|
|
328
|
+
await deps.browser.handoff(owner, leaseId);
|
|
329
|
+
requestHandoff(sessionId, `cloud browser: ${reason}`);
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
return Promise.resolve({
|
|
333
|
+
snapshot: { version: "cloud-browser-v1", modules: [registration] },
|
|
334
|
+
release: () => Promise.resolve(),
|
|
335
|
+
});
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* A minimal Chrome DevTools Protocol client over one WebSocket to a browser
|
|
4
|
+
* session (Browser Run REST or a Workers BROWSER binding; see `sessions.ts`).
|
|
5
|
+
* This is the ONLY place in the browser module that speaks the CDP wire; it
|
|
6
|
+
* exposes `call` (one request/response), `waitForEvent` (one named event)
|
|
7
|
+
* and `on` (a bounded subscription for the host boot observer). It is
|
|
8
|
+
* internal: no export path or agent tool exposes it, so the Agent never sees
|
|
9
|
+
* a `send`/raw-method tool.
|
|
10
|
+
*/
|
|
11
|
+
/** CDP's own wire values are arbitrary JSON; every caller parses its own
|
|
12
|
+
* expected shape with a schema (e.g. `getTargetsResultSchema.parse(...)`). */
|
|
13
|
+
export type Json = z.core.util.JSONType;
|
|
14
|
+
/**
|
|
15
|
+
* The minimal WebSocket surface this client drives (a fake in tests). A real
|
|
16
|
+
* `WebSocket`'s `addEventListener` structurally satisfies this: the listener
|
|
17
|
+
* accepts any platform `Event` and `readCdpMessage` parses a message's `data`.
|
|
18
|
+
*/
|
|
19
|
+
export type CdpSocket = Readonly<{
|
|
20
|
+
/** Send one JSON text frame. */
|
|
21
|
+
send: (data: string) => void;
|
|
22
|
+
/** Close the transport. */
|
|
23
|
+
close: (code?: number, reason?: string) => void;
|
|
24
|
+
/** Observe frames, closure and transport errors. */
|
|
25
|
+
addEventListener: (type: "message" | "close" | "error", listener: (event: Event) => void) => void;
|
|
26
|
+
}>;
|
|
27
|
+
/** The one fetch shape the module calls: global `fetch` or a binding's. */
|
|
28
|
+
export type HttpFetch = (url: string, init?: RequestInit) => Promise<Response>;
|
|
29
|
+
/**
|
|
30
|
+
* Workers/workerd outbound WebSocket: a `fetch` with an `upgrade` header
|
|
31
|
+
* returns a `Response.webSocket` to `.accept()`. Browser Run's REST CDP
|
|
32
|
+
* endpoint and a BROWSER binding's `/v1/devtools/browser/{id}` answer the
|
|
33
|
+
* same upgrade (https://developers.cloudflare.com/browser-run/cdp/).
|
|
34
|
+
*/
|
|
35
|
+
export declare function openCdpSocket(fetcher: HttpFetch, url: string, headers: Readonly<Record<string, string>>, signal: AbortSignal): Promise<CdpSocket>;
|
|
36
|
+
export type CdpClient = Readonly<{
|
|
37
|
+
/** One command/response round trip, optionally flattened into `sessionId`. */
|
|
38
|
+
call: (method: string, params: Readonly<Record<string, Json>>, options?: Readonly<{
|
|
39
|
+
sessionId?: string;
|
|
40
|
+
timeoutMs?: number;
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
}>) => Promise<Json>;
|
|
43
|
+
/** Resolves on the next matching event, or times out. */
|
|
44
|
+
waitForEvent: (method: string, options: Readonly<{
|
|
45
|
+
sessionId?: string;
|
|
46
|
+
timeoutMs: number;
|
|
47
|
+
}>) => Promise<Json>;
|
|
48
|
+
/**
|
|
49
|
+
* Every matching event, with the flattened CDP session it came from,
|
|
50
|
+
* until the returned function unsubscribes.
|
|
51
|
+
*/
|
|
52
|
+
on: (method: string, listener: (params: Json, sessionId: string | undefined) => void) => () => void;
|
|
53
|
+
close: () => void;
|
|
54
|
+
}>;
|
|
55
|
+
/** Wrap one connected CDP socket with id-correlated calls and event waits. */
|
|
56
|
+
export declare function createCdpClient(socket: CdpSocket): CdpClient;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CloudBrowserTimeout, CloudBrowserUpstreamError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Workers/workerd outbound WebSocket: a `fetch` with an `upgrade` header
|
|
5
|
+
* returns a `Response.webSocket` to `.accept()`. Browser Run's REST CDP
|
|
6
|
+
* endpoint and a BROWSER binding's `/v1/devtools/browser/{id}` answer the
|
|
7
|
+
* same upgrade (https://developers.cloudflare.com/browser-run/cdp/).
|
|
8
|
+
*/
|
|
9
|
+
export async function openCdpSocket(fetcher, url, headers, signal) {
|
|
10
|
+
const response = await fetcher(url, {
|
|
11
|
+
headers: { ...headers, upgrade: "websocket" },
|
|
12
|
+
signal,
|
|
13
|
+
});
|
|
14
|
+
const socket = response.webSocket;
|
|
15
|
+
if (socket === null)
|
|
16
|
+
throw new CloudBrowserUpstreamError({
|
|
17
|
+
op: "cdp.connect",
|
|
18
|
+
status: response.status,
|
|
19
|
+
message: "The browser did not upgrade the CDP connection",
|
|
20
|
+
});
|
|
21
|
+
socket.accept();
|
|
22
|
+
return socket;
|
|
23
|
+
}
|
|
24
|
+
const cdpMessageSchema = z.object({
|
|
25
|
+
id: z.number().optional(),
|
|
26
|
+
method: z.string().optional(),
|
|
27
|
+
params: z.json().optional(),
|
|
28
|
+
result: z.json().optional(),
|
|
29
|
+
error: z.object({ code: z.number(), message: z.string() }).optional(),
|
|
30
|
+
sessionId: z.string().optional(),
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* CDP frames are always a JSON text message. `cdpMessageSchema` is the one
|
|
34
|
+
* place that decides what counts as a real CDP message; anything else
|
|
35
|
+
* (binary, malformed JSON, the wrong shape) is silently dropped, same as a
|
|
36
|
+
* CDP client ignoring a frame it does not understand.
|
|
37
|
+
*/
|
|
38
|
+
const textFrameSchema = z.object({ data: z.string() });
|
|
39
|
+
const unreadable = { kind: "unreadable" };
|
|
40
|
+
function readCdpMessage(event) {
|
|
41
|
+
const text = textFrameSchema.safeParse(event);
|
|
42
|
+
if (!text.success)
|
|
43
|
+
return unreadable;
|
|
44
|
+
let candidate;
|
|
45
|
+
try {
|
|
46
|
+
candidate = JSON.parse(text.data.data);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return unreadable;
|
|
50
|
+
}
|
|
51
|
+
const parsed = cdpMessageSchema.safeParse(candidate);
|
|
52
|
+
return parsed.success
|
|
53
|
+
? { kind: "message", message: parsed.data }
|
|
54
|
+
: unreadable;
|
|
55
|
+
}
|
|
56
|
+
/** Wrap one connected CDP socket with id-correlated calls and event waits. */
|
|
57
|
+
export function createCdpClient(socket) {
|
|
58
|
+
const pending = new Map();
|
|
59
|
+
const waiters = [];
|
|
60
|
+
const subscribers = new Set();
|
|
61
|
+
let nextRequestNumber = 1;
|
|
62
|
+
let closed = false;
|
|
63
|
+
socket.addEventListener("message", (event) => {
|
|
64
|
+
const frame = readCdpMessage(event);
|
|
65
|
+
if (frame.kind === "unreadable")
|
|
66
|
+
return;
|
|
67
|
+
const { message } = frame;
|
|
68
|
+
if (message.id !== undefined) {
|
|
69
|
+
const waiting = pending.get(message.id);
|
|
70
|
+
if (waiting === undefined)
|
|
71
|
+
return;
|
|
72
|
+
pending.delete(message.id);
|
|
73
|
+
if (message.error !== undefined)
|
|
74
|
+
waiting.reject(new CloudBrowserUpstreamError({
|
|
75
|
+
op: "cdp.call",
|
|
76
|
+
status: null,
|
|
77
|
+
message: message.error.message,
|
|
78
|
+
}));
|
|
79
|
+
else
|
|
80
|
+
waiting.resolve(message.result ?? null);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (message.method === undefined)
|
|
84
|
+
return;
|
|
85
|
+
for (const subscriber of subscribers)
|
|
86
|
+
if (subscriber.method === message.method)
|
|
87
|
+
subscriber.listener(message.params ?? null, message.sessionId);
|
|
88
|
+
for (let index = waiters.length - 1; index >= 0; index -= 1) {
|
|
89
|
+
const waiter = waiters[index];
|
|
90
|
+
if (waiter?.method === message.method &&
|
|
91
|
+
(waiter.sessionId === undefined ||
|
|
92
|
+
waiter.sessionId === message.sessionId)) {
|
|
93
|
+
waiters.splice(index, 1);
|
|
94
|
+
waiter.resolve(message.params ?? null);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
socket.addEventListener("close", () => {
|
|
99
|
+
closed = true;
|
|
100
|
+
for (const { reject } of pending.values())
|
|
101
|
+
reject(new CloudBrowserUpstreamError({
|
|
102
|
+
op: "cdp.socket",
|
|
103
|
+
status: null,
|
|
104
|
+
message: "browser CDP socket closed",
|
|
105
|
+
}));
|
|
106
|
+
pending.clear();
|
|
107
|
+
});
|
|
108
|
+
socket.addEventListener("error", () => {
|
|
109
|
+
closed = true;
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
call: (method, params, options) => new Promise((resolve, reject) => {
|
|
113
|
+
if (closed) {
|
|
114
|
+
reject(new CloudBrowserUpstreamError({
|
|
115
|
+
op: method,
|
|
116
|
+
status: null,
|
|
117
|
+
message: "browser CDP socket is closed",
|
|
118
|
+
}));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const requestNumber = nextRequestNumber;
|
|
122
|
+
nextRequestNumber += 1;
|
|
123
|
+
const timeoutMs = options?.timeoutMs ?? 15_000;
|
|
124
|
+
const timer = setTimeout(() => {
|
|
125
|
+
pending.delete(requestNumber);
|
|
126
|
+
reject(new CloudBrowserTimeout({ op: method, timeoutMs }));
|
|
127
|
+
}, timeoutMs);
|
|
128
|
+
const onAbort = () => {
|
|
129
|
+
pending.delete(requestNumber);
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
reject(new CloudBrowserTimeout({ op: method, timeoutMs: 0 }));
|
|
132
|
+
};
|
|
133
|
+
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
134
|
+
pending.set(requestNumber, {
|
|
135
|
+
resolve: (value) => {
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
138
|
+
resolve(value);
|
|
139
|
+
},
|
|
140
|
+
reject: (error) => {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
143
|
+
reject(error);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const frame = { id: requestNumber, method, params };
|
|
147
|
+
socket.send(JSON.stringify(options?.sessionId === undefined
|
|
148
|
+
? frame
|
|
149
|
+
: { ...frame, sessionId: options.sessionId }));
|
|
150
|
+
}),
|
|
151
|
+
waitForEvent: (method, options) => new Promise((resolve, reject) => {
|
|
152
|
+
const waiter = {
|
|
153
|
+
method,
|
|
154
|
+
sessionId: options.sessionId,
|
|
155
|
+
resolve: (value) => {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
resolve(value);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
const timer = setTimeout(() => {
|
|
161
|
+
const index = waiters.indexOf(waiter);
|
|
162
|
+
if (index >= 0)
|
|
163
|
+
waiters.splice(index, 1);
|
|
164
|
+
reject(new CloudBrowserTimeout({
|
|
165
|
+
op: method,
|
|
166
|
+
timeoutMs: options.timeoutMs,
|
|
167
|
+
}));
|
|
168
|
+
}, options.timeoutMs);
|
|
169
|
+
waiters.push(waiter);
|
|
170
|
+
}),
|
|
171
|
+
on: (method, listener) => {
|
|
172
|
+
const subscriber = { method, listener };
|
|
173
|
+
subscribers.add(subscriber);
|
|
174
|
+
return () => {
|
|
175
|
+
subscribers.delete(subscriber);
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
close: () => {
|
|
179
|
+
closed = true;
|
|
180
|
+
subscribers.clear();
|
|
181
|
+
socket.close();
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
declare const CloudBrowserUrlDenied_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
2
|
+
readonly _tag: "CloudBrowserUrlDenied";
|
|
3
|
+
} & Readonly<A>;
|
|
4
|
+
/**
|
|
5
|
+
* The cloud-browser backend's closed, typed failure channel. Every backend
|
|
6
|
+
* (today only Cloudflare Browser Run) throws exclusively from this union;
|
|
7
|
+
* `agent-cloud-browser-source.ts` is the one place that maps it onto
|
|
8
|
+
* Mycelium's `CapabilityFailed`, so the model only ever sees a bounded,
|
|
9
|
+
* owner-admitted message (never a raw provider error body or stack).
|
|
10
|
+
*/
|
|
11
|
+
/** The target URL failed policy before any provider call was made. */
|
|
12
|
+
export declare class CloudBrowserUrlDenied extends CloudBrowserUrlDenied_base<{
|
|
13
|
+
readonly reason: "invalid_url" | "unsupported_scheme" | "credentials_in_url" | "not_public" | "dns_unavailable";
|
|
14
|
+
readonly message: string;
|
|
15
|
+
}> {
|
|
16
|
+
}
|
|
17
|
+
declare const CloudBrowserUpstreamError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
18
|
+
readonly _tag: "CloudBrowserUpstreamError";
|
|
19
|
+
} & Readonly<A>;
|
|
20
|
+
/** The provider (Cloudflare Browser Run) answered with a failure. */
|
|
21
|
+
export declare class CloudBrowserUpstreamError extends CloudBrowserUpstreamError_base<{
|
|
22
|
+
readonly op: string;
|
|
23
|
+
readonly status: number | null;
|
|
24
|
+
readonly message: string;
|
|
25
|
+
}> {
|
|
26
|
+
}
|
|
27
|
+
declare const CloudBrowserTimeout_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
28
|
+
readonly _tag: "CloudBrowserTimeout";
|
|
29
|
+
} & Readonly<A>;
|
|
30
|
+
/** A call did not complete within its deadline. */
|
|
31
|
+
export declare class CloudBrowserTimeout extends CloudBrowserTimeout_base<{
|
|
32
|
+
readonly op: string;
|
|
33
|
+
readonly timeoutMs: number;
|
|
34
|
+
}> {
|
|
35
|
+
}
|
|
36
|
+
declare const CloudBrowserLeaseNotFound_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
37
|
+
readonly _tag: "CloudBrowserLeaseNotFound";
|
|
38
|
+
} & Readonly<A>;
|
|
39
|
+
/** The named lease is not held by this (teamId, agentId, sessionId). */
|
|
40
|
+
export declare class CloudBrowserLeaseNotFound extends CloudBrowserLeaseNotFound_base<{
|
|
41
|
+
readonly leaseId: string;
|
|
42
|
+
}> {
|
|
43
|
+
}
|
|
44
|
+
declare const CloudBrowserLeasePaused_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
45
|
+
readonly _tag: "CloudBrowserLeasePaused";
|
|
46
|
+
} & Readonly<A>;
|
|
47
|
+
/** A member was asked to take over; the Agent cannot drive it until "Done". */
|
|
48
|
+
export declare class CloudBrowserLeasePaused extends CloudBrowserLeasePaused_base<{
|
|
49
|
+
readonly leaseId: string;
|
|
50
|
+
}> {
|
|
51
|
+
}
|
|
52
|
+
declare const CloudBrowserCapacityExceeded_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
53
|
+
readonly _tag: "CloudBrowserCapacityExceeded";
|
|
54
|
+
} & Readonly<A>;
|
|
55
|
+
/** The Team has no more concurrent leases available right now. */
|
|
56
|
+
export declare class CloudBrowserCapacityExceeded extends CloudBrowserCapacityExceeded_base<{
|
|
57
|
+
readonly teamId: string;
|
|
58
|
+
readonly limit: number;
|
|
59
|
+
}> {
|
|
60
|
+
}
|
|
61
|
+
declare const CloudBrowserCreditDenied_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
62
|
+
readonly _tag: "CloudBrowserCreditDenied";
|
|
63
|
+
} & Readonly<A>;
|
|
64
|
+
/** The Team has no credit to start or continue a lease. */
|
|
65
|
+
export declare class CloudBrowserCreditDenied extends CloudBrowserCreditDenied_base<{
|
|
66
|
+
readonly teamId: string;
|
|
67
|
+
}> {
|
|
68
|
+
}
|
|
69
|
+
declare const CloudBrowserUnavailable_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
70
|
+
readonly _tag: "CloudBrowserUnavailable";
|
|
71
|
+
} & Readonly<A>;
|
|
72
|
+
/** No backend is configured (credentials absent, or the feature is paused). */
|
|
73
|
+
export declare class CloudBrowserUnavailable extends CloudBrowserUnavailable_base<{
|
|
74
|
+
readonly reason: "unconfigured" | "paused";
|
|
75
|
+
}> {
|
|
76
|
+
}
|
|
77
|
+
/** Every typed failure the browser module throws; nothing else escapes it. */
|
|
78
|
+
export type CloudBrowserFailure = CloudBrowserUrlDenied | CloudBrowserUpstreamError | CloudBrowserTimeout | CloudBrowserLeaseNotFound | CloudBrowserLeasePaused | CloudBrowserCapacityExceeded | CloudBrowserCreditDenied | CloudBrowserUnavailable;
|
|
79
|
+
export {};
|