@fungi.computer/oyster 0.3.1 → 0.4.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 +2 -1
- package/dist/agent-computer-source.d.ts +42 -0
- package/dist/agent-computer-source.js +88 -0
- package/dist/agent-computers.d.ts +104 -0
- package/dist/agent-computers.js +227 -0
- package/dist/agents.d.ts +10 -0
- package/dist/agents.js +33 -0
- package/dist/boot-capability.d.ts +1 -1
- package/dist/boot-capability.js +1 -1
- package/dist/browser/boot.d.ts +2 -2
- package/dist/browser/boot.js +10 -3
- package/dist/browser/browser.d.ts +1 -1
- package/dist/browser/browser.js +2 -1
- package/dist/browser/cdp-client.d.ts +2 -0
- package/dist/browser/errors.d.ts +2 -2
- package/dist/browser/errors.js +2 -2
- package/dist/browser/index.d.ts +3 -2
- package/dist/browser/index.js +3 -2
- package/dist/browser/page.js +11 -4
- package/dist/browser/sessions.d.ts +7 -4
- package/dist/browser/sessions.js +159 -0
- package/dist/browser/url-policy.d.ts +4 -2
- package/dist/browser/url-policy.js +4 -2
- package/dist/computer-control-port.d.ts +26 -0
- package/dist/computer-control-port.js +1 -0
- package/dist/host.d.ts +3 -0
- package/dist/machine-link/delivery.d.ts +1 -1
- package/dist/machine-link/delivery.js +2 -3
- package/dist/machine-link/host.d.ts +37 -6
- package/dist/machine-link/host.js +209 -14
- package/dist/machine-link/link.d.ts +13 -1
- package/dist/machine-link/link.js +20 -7
- package/dist/mcp.d.ts +152 -11
- package/dist/mcp.js +131 -13
- package/dist/page/computer-handles.d.ts +3 -3
- package/dist/page-script.js +1 -1
- package/dist/standalone/agent-computers.d.ts +29 -0
- package/dist/standalone/agent-computers.js +101 -0
- package/dist/standalone/agent-mail.d.ts +41 -18
- package/dist/standalone/agent-model-choice.d.ts +26 -0
- package/dist/standalone/agent-model-choice.js +72 -0
- package/dist/standalone/agents.d.ts +50 -5
- package/dist/standalone/agents.js +505 -53
- package/dist/standalone/app-builds-binding.js +1 -1
- package/dist/standalone/computer-control.d.ts +4 -0
- package/dist/standalone/computer-control.js +62 -0
- package/dist/standalone/computer-fleet.d.ts +1 -0
- package/dist/standalone/computer-fleet.js +1 -1
- package/dist/standalone/computer.d.ts +21 -2
- package/dist/standalone/computer.js +12 -0
- package/dist/standalone/machine-link.d.ts +10 -0
- package/dist/standalone/machine-link.js +6 -0
- package/dist/standalone/registry.d.ts +12 -0
- package/dist/standalone/registry.js +18 -0
- package/dist/standalone/tenancy.d.ts +14 -1
- package/dist/standalone/tenancy.js +37 -3
- package/dist/standalone/worker.js +36 -9
- package/dist/standalone/workflow-binding.d.ts +2 -2
- package/dist/standalone/workflow-host.d.ts +11 -11
- package/dist/standalone/workflows.d.ts +22 -22
- package/dist/storage-frame.d.ts +3 -3
- package/dist/workers-git.d.ts +2 -0
- package/dist/workers-git.js +11 -1
- package/dist/workers.d.ts +8 -8
- package/docs/api/agents.md +66 -0
- package/docs/api/apps.md +104 -104
- package/docs/api/build-contract.md +35 -35
- package/docs/api/build.md +12 -12
- package/docs/api/host.md +61 -61
- package/docs/api/mcp.md +141 -13
- package/docs/api/root-data.md +1 -1
- package/docs/api/tenancy.md +1 -1
- package/docs/api/workers-git.md +3 -2
- package/docs/api/workers.md +81 -83
- package/package.json +25 -17
package/dist/browser/errors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Data from "effect/Data";
|
|
2
2
|
/**
|
|
3
3
|
* The cloud-browser backend's closed, typed failure channel. Every backend
|
|
4
|
-
* (
|
|
4
|
+
* (Cloudflare Browser Run or on-box Chromium) throws from this union;
|
|
5
5
|
* `agent-cloud-browser-source.ts` is the one place that maps it onto
|
|
6
6
|
* Mycelium's `CapabilityFailed`, so the model only ever sees a bounded,
|
|
7
7
|
* owner-admitted message (never a raw provider error body or stack).
|
|
@@ -9,7 +9,7 @@ import * as Data from "effect/Data";
|
|
|
9
9
|
/** The target URL failed policy before any provider call was made. */
|
|
10
10
|
export class CloudBrowserUrlDenied extends Data.TaggedError("CloudBrowserUrlDenied") {
|
|
11
11
|
}
|
|
12
|
-
/** The provider
|
|
12
|
+
/** The configured provider answered with a failure or is unreachable. */
|
|
13
13
|
export class CloudBrowserUpstreamError extends Data.TaggedError("CloudBrowserUpstreamError") {
|
|
14
14
|
}
|
|
15
15
|
/** A call did not complete within its deadline. */
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@fungi.computer/oyster/browser`: the one agent browser for the Hub and a
|
|
3
3
|
* standalone Oyster. A host builds the port from a provider (`browserRunSessions`
|
|
4
|
-
* or `bindingSessions
|
|
4
|
+
* or `bindingSessions` or `onboxBrowserSessions` on staging, plus Browser Run
|
|
5
|
+
* Quick Reads when it has them), its
|
|
5
6
|
* required metering owner and an event sink; `createBrowser` owns lease
|
|
6
7
|
* ownership, egress policy and billing order; `browserCapabilitySource` is
|
|
7
8
|
* the agent's `cloudBrowser` tool. Raw CDP stays internal to this directory.
|
|
@@ -13,4 +14,4 @@ export type { LeaseOwner } from "./lease-registry.js";
|
|
|
13
14
|
export { cloudBrowserBounds, extractInputSchema, extractResultSchema, linksResultSchema, markdownInputSchema, markdownResultSchema, navigateResultSchema, openResultSchema, scrapeInputSchema, scrapeResultSchema, snapshotResultSchema, type BinaryResult, type CloudBrowserAck, type CloudBrowserClosed, type CloudBrowserOperation, type CloudBrowserOperationResult, type ExtractInput, type ExtractResult, type LinksResult, type MarkdownInput, type MarkdownResult, type NavigateResult, type OpenResult, type ScrapeInput, type ScrapeResult, type SnapshotResult, } from "./operations.js";
|
|
14
15
|
export type { CdpSocket, HttpFetch } from "./cdp-client.js";
|
|
15
16
|
export { browserRunQuickReads, type BrowserRunQuickReadsConfig, type QuickRead, type QuickReads, } from "./quick-reads.js";
|
|
16
|
-
export { bindingSessions, browserRunSessions, type BrowserBinding, type BrowserSessions, type CdpConnect, } from "./sessions.js";
|
|
17
|
+
export { bindingSessions, browserRunSessions, onboxBrowserSessions, type BrowserBinding, type BrowserSessions, type CdpConnect, } from "./sessions.js";
|
package/dist/browser/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@fungi.computer/oyster/browser`: the one agent browser for the Hub and a
|
|
3
3
|
* standalone Oyster. A host builds the port from a provider (`browserRunSessions`
|
|
4
|
-
* or `bindingSessions
|
|
4
|
+
* or `bindingSessions` or `onboxBrowserSessions` on staging, plus Browser Run
|
|
5
|
+
* Quick Reads when it has them), its
|
|
5
6
|
* required metering owner and an event sink; `createBrowser` owns lease
|
|
6
7
|
* ownership, egress policy and billing order; `browserCapabilitySource` is
|
|
7
8
|
* the agent's `cloudBrowser` tool. Raw CDP stays internal to this directory.
|
|
@@ -11,4 +12,4 @@ export { browserCapabilitySource, CLOUD_BROWSER_DESCRIPTIONS, } from "./capabili
|
|
|
11
12
|
export { CloudBrowserCapacityExceeded, CloudBrowserCreditDenied, CloudBrowserLeaseNotFound, CloudBrowserLeasePaused, CloudBrowserTimeout, CloudBrowserUnavailable, CloudBrowserUpstreamError, CloudBrowserUrlDenied, } from "./errors.js";
|
|
12
13
|
export { cloudBrowserBounds, extractInputSchema, extractResultSchema, linksResultSchema, markdownInputSchema, markdownResultSchema, navigateResultSchema, openResultSchema, scrapeInputSchema, scrapeResultSchema, snapshotResultSchema, } from "./operations.js";
|
|
13
14
|
export { browserRunQuickReads, } from "./quick-reads.js";
|
|
14
|
-
export { bindingSessions, browserRunSessions, } from "./sessions.js";
|
|
15
|
+
export { bindingSessions, browserRunSessions, onboxBrowserSessions, } from "./sessions.js";
|
package/dist/browser/page.js
CHANGED
|
@@ -8,7 +8,11 @@ import { cloudBrowserBounds, } from "./operations.js";
|
|
|
8
8
|
* The session-create response shape was confirmed by the live workerd smoke
|
|
9
9
|
* on 2026-10-03; see `test/browser/cdp-client.test.ts`.
|
|
10
10
|
*/
|
|
11
|
-
const targetSchema = z.object({
|
|
11
|
+
const targetSchema = z.object({
|
|
12
|
+
targetId: z.string(),
|
|
13
|
+
type: z.string(),
|
|
14
|
+
browserContextId: z.string().optional(),
|
|
15
|
+
});
|
|
12
16
|
const getTargetsResultSchema = z.object({ targetInfos: z.array(targetSchema) });
|
|
13
17
|
const attachResultSchema = z.object({ sessionId: z.string() });
|
|
14
18
|
const navigateResultSchema = z.object({
|
|
@@ -31,11 +35,14 @@ const capturedDataSchema = z.object({ data: z.string() });
|
|
|
31
35
|
/** Attach to the session's first page target (or a new blank one). */
|
|
32
36
|
export async function attachPage(client) {
|
|
33
37
|
const targets = getTargetsResultSchema.parse(await client.call("Target.getTargets", {}));
|
|
34
|
-
const targetId = targets.targetInfos.find((target) => target.type === "page"
|
|
38
|
+
const targetId = targets.targetInfos.find((target) => target.type === "page" &&
|
|
39
|
+
(client.browserContextId === undefined ||
|
|
40
|
+
target.browserContextId === client.browserContextId))?.targetId ??
|
|
35
41
|
z
|
|
36
42
|
.object({ targetId: z.string() })
|
|
37
|
-
.parse(await client.call("Target.createTarget",
|
|
38
|
-
|
|
43
|
+
.parse(await client.call("Target.createTarget", client.browserContextId === undefined
|
|
44
|
+
? { url: "about:blank" }
|
|
45
|
+
: { url: "about:blank", browserContextId: client.browserContextId })).targetId;
|
|
39
46
|
const { sessionId: cdpSessionId } = attachResultSchema.parse(await client.call("Target.attachToTarget", { targetId, flatten: true }));
|
|
40
47
|
await client.call("Page.enable", {}, { sessionId: cdpSessionId });
|
|
41
48
|
await client.call("Accessibility.enable", {}, { sessionId: cdpSessionId });
|
|
@@ -11,16 +11,17 @@ import { type CdpClient, type CdpSocket, type HttpFetch } from "./cdp-client.js"
|
|
|
11
11
|
* - Binding: `binding.fetch("http://fake.host/v1/devtools/…")`, the endpoints
|
|
12
12
|
* a Workers Browser Run binding answers.
|
|
13
13
|
*
|
|
14
|
+
* Chromium on staging uses browser contexts rather than Browser Run endpoints.
|
|
14
15
|
* Provider session ids stay inside the module; agent leases use their own ids.
|
|
15
16
|
*/
|
|
16
17
|
export type AcquireOptions = Readonly<{
|
|
17
|
-
/**
|
|
18
|
+
/** Browser Run idle keep-alive (10s–20min); Chromium has no equivalent. */
|
|
18
19
|
keepAliveMs: number;
|
|
19
|
-
/**
|
|
20
|
+
/** Browser Run provider guardrail; Chromium relies on its caller's URL gate. */
|
|
20
21
|
allowedDomains?: readonly string[];
|
|
21
22
|
}>;
|
|
22
23
|
/**
|
|
23
|
-
* The provider half of the host port.
|
|
24
|
+
* The provider half of the host port.
|
|
24
25
|
* Each step is separate so `createBrowser` owns a provider session id from the
|
|
25
26
|
* moment it exists, including when its CDP connect then fails.
|
|
26
27
|
*/
|
|
@@ -31,7 +32,7 @@ export type BrowserSessions = Readonly<{
|
|
|
31
32
|
connect: (sessionId: string, signal: AbortSignal) => Promise<CdpClient>;
|
|
32
33
|
/** Ask the provider to delete a session; 404 is already gone. @internal */
|
|
33
34
|
release: (sessionId: string, signal: AbortSignal) => Promise<"closed" | "closing">;
|
|
34
|
-
/**
|
|
35
|
+
/** Confirm provider closure (Chromium checks context absence). @internal */
|
|
35
36
|
confirmClosed: (sessionId: string, deadlineAt: number) => Promise<void>;
|
|
36
37
|
}>;
|
|
37
38
|
/** A Workers BROWSER binding (`Fetcher`), reduced to the call this module makes. */
|
|
@@ -50,3 +51,5 @@ export declare function browserRunSessions(config: Readonly<{
|
|
|
50
51
|
}>): BrowserSessions;
|
|
51
52
|
/** A standalone Oyster's own Workers BROWSER binding. */
|
|
52
53
|
export declare function bindingSessions(binding: BrowserBinding): BrowserSessions;
|
|
54
|
+
/** Staging uses Chromium's native CDP browser contexts; no session store. */
|
|
55
|
+
export declare function onboxBrowserSessions(origin: string, fetcher?: HttpFetch): BrowserSessions;
|
package/dist/browser/sessions.js
CHANGED
|
@@ -116,3 +116,162 @@ export function bindingSessions(binding) {
|
|
|
116
116
|
connect: (url, headers, signal) => openCdpSocket(fetcher, url, headers, signal),
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
+
/** Staging uses Chromium's native CDP browser contexts; no session store. */
|
|
120
|
+
export function onboxBrowserSessions(origin, fetcher = (url, init) => fetch(url, init)) {
|
|
121
|
+
const base = new URL(origin);
|
|
122
|
+
if (base.protocol !== "http:" ||
|
|
123
|
+
base.hostname !== "127.0.0.1" ||
|
|
124
|
+
base.username ||
|
|
125
|
+
base.password ||
|
|
126
|
+
base.pathname !== "/" ||
|
|
127
|
+
base.search ||
|
|
128
|
+
base.hash)
|
|
129
|
+
throw new TypeError("On-box browser origin must be bare IPv4 loopback");
|
|
130
|
+
const browserEndpointSchema = z.object({ webSocketDebuggerUrl: z.url() });
|
|
131
|
+
const contextsSchema = z.object({ browserContextIds: z.array(z.string()) });
|
|
132
|
+
const createdContextSchema = z.object({
|
|
133
|
+
browserContextId: z.string().min(1),
|
|
134
|
+
});
|
|
135
|
+
async function connectBrowser(signal) {
|
|
136
|
+
// Chromium's native discovery endpoint is not a Botanical session server.
|
|
137
|
+
let discovery;
|
|
138
|
+
try {
|
|
139
|
+
discovery = await fetcher(new URL("/json/version", base).href, {
|
|
140
|
+
signal,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (cause) {
|
|
144
|
+
if (signal.aborted)
|
|
145
|
+
throw cause;
|
|
146
|
+
throw new CloudBrowserUpstreamError({
|
|
147
|
+
op: "onbox.connect",
|
|
148
|
+
status: null,
|
|
149
|
+
message: "On-box Chromium is unavailable",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (!discovery.ok)
|
|
153
|
+
throw new CloudBrowserUpstreamError({
|
|
154
|
+
op: "onbox.connect",
|
|
155
|
+
status: discovery.status,
|
|
156
|
+
message: "On-box Chromium is unavailable",
|
|
157
|
+
});
|
|
158
|
+
let body;
|
|
159
|
+
try {
|
|
160
|
+
body = await discovery.json();
|
|
161
|
+
}
|
|
162
|
+
catch (cause) {
|
|
163
|
+
if (signal.aborted)
|
|
164
|
+
throw cause;
|
|
165
|
+
throw new CloudBrowserUpstreamError({
|
|
166
|
+
op: "onbox.connect",
|
|
167
|
+
status: null,
|
|
168
|
+
message: "On-box browser returned an unreadable CDP endpoint",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const discovered = browserEndpointSchema.safeParse(body);
|
|
172
|
+
if (!discovered.success)
|
|
173
|
+
throw new CloudBrowserUpstreamError({
|
|
174
|
+
op: "onbox.connect",
|
|
175
|
+
status: null,
|
|
176
|
+
message: "On-box browser returned an unreadable CDP endpoint",
|
|
177
|
+
});
|
|
178
|
+
const { webSocketDebuggerUrl } = discovered.data;
|
|
179
|
+
const endpoint = new URL(webSocketDebuggerUrl);
|
|
180
|
+
if (endpoint.protocol !== "ws:" ||
|
|
181
|
+
endpoint.hostname !== "127.0.0.1" ||
|
|
182
|
+
endpoint.port !== base.port ||
|
|
183
|
+
!/^\/devtools\/browser\/[^/]+$/u.test(endpoint.pathname) ||
|
|
184
|
+
endpoint.username ||
|
|
185
|
+
endpoint.password ||
|
|
186
|
+
endpoint.search ||
|
|
187
|
+
endpoint.hash)
|
|
188
|
+
throw new CloudBrowserUpstreamError({
|
|
189
|
+
op: "onbox.connect",
|
|
190
|
+
status: null,
|
|
191
|
+
message: "On-box browser returned an invalid CDP endpoint",
|
|
192
|
+
});
|
|
193
|
+
return createCdpClient(await openCdpSocket(fetcher, endpoint.href.replace(/^ws:/u, "http:"), {}, signal));
|
|
194
|
+
}
|
|
195
|
+
async function contexts(client, signal) {
|
|
196
|
+
const result = contextsSchema.safeParse(await client.call("Target.getBrowserContexts", {}, { signal }));
|
|
197
|
+
if (!result.success)
|
|
198
|
+
throw new CloudBrowserUpstreamError({
|
|
199
|
+
op: "onbox.contexts",
|
|
200
|
+
status: null,
|
|
201
|
+
message: "On-box browser returned unreadable contexts",
|
|
202
|
+
});
|
|
203
|
+
return result.data.browserContextIds;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
create: async (_options, signal) => {
|
|
207
|
+
// Chromium has no idle keep-alive or allowedDomains create guardrail:
|
|
208
|
+
// Oyster's Agent URL policy and the boot page request gate remain active.
|
|
209
|
+
// A Chromium restart ends every context; no restart recovery is claimed.
|
|
210
|
+
const client = await connectBrowser(signal);
|
|
211
|
+
try {
|
|
212
|
+
const created = createdContextSchema.safeParse(await client.call("Target.createBrowserContext", {}, { signal }));
|
|
213
|
+
if (!created.success)
|
|
214
|
+
throw new CloudBrowserUpstreamError({
|
|
215
|
+
op: "onbox.create",
|
|
216
|
+
status: null,
|
|
217
|
+
message: "On-box browser returned an unreadable context",
|
|
218
|
+
});
|
|
219
|
+
const { browserContextId } = created.data;
|
|
220
|
+
try {
|
|
221
|
+
await client.call("Target.createTarget", { url: "about:blank", browserContextId }, { signal });
|
|
222
|
+
return browserContextId;
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
await client.call("Target.disposeBrowserContext", { browserContextId }, { signal });
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
client.close();
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
connect: async (id, signal) => {
|
|
234
|
+
const client = await connectBrowser(signal);
|
|
235
|
+
try {
|
|
236
|
+
if (!(await contexts(client, signal)).includes(id))
|
|
237
|
+
throw new CloudBrowserUpstreamError({
|
|
238
|
+
op: "onbox.connect",
|
|
239
|
+
status: null,
|
|
240
|
+
message: "On-box browser context is gone",
|
|
241
|
+
});
|
|
242
|
+
return { ...client, browserContextId: id };
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
client.close();
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
release: async (id, signal) => {
|
|
250
|
+
const client = await connectBrowser(signal);
|
|
251
|
+
try {
|
|
252
|
+
if ((await contexts(client, signal)).includes(id))
|
|
253
|
+
await client.call("Target.disposeBrowserContext", { browserContextId: id }, { signal });
|
|
254
|
+
return "closed";
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
client.close();
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
confirmClosed: async (id, deadlineAt) => {
|
|
261
|
+
const signal = AbortSignal.timeout(Math.max(1, deadlineAt - Date.now()));
|
|
262
|
+
const client = await connectBrowser(signal);
|
|
263
|
+
try {
|
|
264
|
+
if (!(await contexts(client, signal)).includes(id))
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
client.close();
|
|
269
|
+
}
|
|
270
|
+
throw new CloudBrowserUpstreamError({
|
|
271
|
+
op: "onbox.close.confirm",
|
|
272
|
+
status: null,
|
|
273
|
+
message: "On-box browser did not confirm closure",
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
@@ -12,7 +12,9 @@ export type UrlCheckOptions = Readonly<{
|
|
|
12
12
|
* `169.254.169.254`, a private range like `10/8`, the box's own address, and
|
|
13
13
|
* a rebinding hostname (one whose DNS answer is private) are all refused
|
|
14
14
|
* here, before Cloudflare ever sees the URL. The name is resolved once; the
|
|
15
|
-
* caller
|
|
16
|
-
*
|
|
15
|
+
* caller does not connect directly: Browser Run does in production, Chromium
|
|
16
|
+
* does on staging. This check is not network confinement: a page can redirect,
|
|
17
|
+
* load subresources or resolve the host differently after this check. Staging
|
|
18
|
+
* must isolate Chromium's network egress before enabling Agent leases.
|
|
17
19
|
*/
|
|
18
20
|
export declare function assertCloudBrowserUrlAllowed(raw: string, options: UrlCheckOptions): Promise<URL>;
|
|
@@ -10,8 +10,10 @@ import { CloudBrowserUrlDenied } from "./errors.js";
|
|
|
10
10
|
* `169.254.169.254`, a private range like `10/8`, the box's own address, and
|
|
11
11
|
* a rebinding hostname (one whose DNS answer is private) are all refused
|
|
12
12
|
* here, before Cloudflare ever sees the URL. The name is resolved once; the
|
|
13
|
-
* caller
|
|
14
|
-
*
|
|
13
|
+
* caller does not connect directly: Browser Run does in production, Chromium
|
|
14
|
+
* does on staging. This check is not network confinement: a page can redirect,
|
|
15
|
+
* load subresources or resolve the host differently after this check. Staging
|
|
16
|
+
* must isolate Chromium's network egress before enabling Agent leases.
|
|
15
17
|
*/
|
|
16
18
|
export async function assertCloudBrowserUrlAllowed(raw, options) {
|
|
17
19
|
let url;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { computerCreateRequestSchema, computerPageSchema } from "@fungi.computer/computer/fleet/inventory-wire";
|
|
3
|
+
import type { computerStateSchema } from "@fungi.computer/computer/fleet/wire";
|
|
4
|
+
import type { teamComputerDirectorySchema } from "@fungi.computer/nacre/workbench";
|
|
5
|
+
import type { InvocationContext } from "@fungi.computer/mycelium";
|
|
6
|
+
/** Host policy stays outside this family: Hub admits funded slots, standalone
|
|
7
|
+
* admits its current owner. Both call their durable fleet through this port.
|
|
8
|
+
* Keep the host contract independent of the Worker-only fleet lifecycle. */
|
|
9
|
+
export type ComputerControlPort = Readonly<{
|
|
10
|
+
/** List a bounded page from the host-admitted inventory. */
|
|
11
|
+
list(page: z.output<typeof computerPageSchema>, context: InvocationContext): Promise<z.output<typeof teamComputerDirectorySchema>>;
|
|
12
|
+
/** Read liveness without waking the Computer. */
|
|
13
|
+
state(computerId: string, context: InvocationContext): Promise<z.output<typeof computerStateSchema>>;
|
|
14
|
+
/** Start one inventoried hosted Computer through the host's admission. */
|
|
15
|
+
start(computerId: string, context: InvocationContext): Promise<z.output<typeof computerStateSchema>>;
|
|
16
|
+
/** Create or replay a durable receipt tied to this request identity. */
|
|
17
|
+
create(input: z.output<typeof computerCreateRequestSchema>, context: InvocationContext): Promise<{
|
|
18
|
+
kind: "created";
|
|
19
|
+
computer: {
|
|
20
|
+
computerId: string;
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
} | {
|
|
24
|
+
kind: "uncertain" | "denied" | "conflict" | "invalid";
|
|
25
|
+
}>;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/host.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OysterApprovalCall } from "./approval-home.js";
|
|
2
2
|
import type { PendingApproval, ApprovalApplyResult } from "@fungi.computer/approvals";
|
|
3
3
|
import type { WorkflowControl } from "./workflows.js";
|
|
4
|
+
import type { ComputerControlPort } from "./computer-control-port.js";
|
|
4
5
|
import { type InstallationOperations } from "./installation-operations.js";
|
|
5
6
|
export type { InstallationOperations };
|
|
6
7
|
import type { KnapsackComputer } from "@fungi.computer/knapsack";
|
|
@@ -192,6 +193,8 @@ export type OysterHost = Readonly<{
|
|
|
192
193
|
* @inlineType WorkflowControl
|
|
193
194
|
*/
|
|
194
195
|
workflows?: (principal: OysterPrincipal) => Promise<WorkflowControl | null>;
|
|
196
|
+
/** Owner-admitted durable Computer inventory, absent on hosts without a fleet. */
|
|
197
|
+
computerControl?: (principal: OysterPrincipal) => ComputerControlPort | null;
|
|
195
198
|
auth: OysterAuth;
|
|
196
199
|
/** Host-held sealing key for member writes; never exposed to the guest.
|
|
197
200
|
* Absence refuses eligible named member writes, even when the App has no sync continuation.
|
|
@@ -13,6 +13,6 @@ export declare function createHomeAwareAgentMailNamespace(options: {
|
|
|
13
13
|
home: (agentId: string) => Promise<MachineAgentHome | null>;
|
|
14
14
|
hosted: PostRoute<never, never>["namespace"];
|
|
15
15
|
machine: (deviceId: string, envelope: PostEnvelope) => Promise<PostDeliveryResult>;
|
|
16
|
-
/** Host's Agent grammar:
|
|
16
|
+
/** Host's Agent grammar: validates Accepted or Held without conflating them. */
|
|
17
17
|
accepted: (receipt: Schema.Json) => void;
|
|
18
18
|
}): PostRoute<never, never>["namespace"];
|
|
@@ -28,8 +28,7 @@ export function createHomeAwareAgentMailNamespace(options) {
|
|
|
28
28
|
address.scopeId !== options.sender.teamId ||
|
|
29
29
|
address.agentId !== agentId ||
|
|
30
30
|
mail.to.agentId !== agentId ||
|
|
31
|
-
mail.from.agentId !== options.sender.agentId
|
|
32
|
-
new TextEncoder().encode(JSON.stringify(mail)).byteLength > 256 * 1024)
|
|
31
|
+
mail.from.agentId !== options.sender.agentId)
|
|
33
32
|
return "refused";
|
|
34
33
|
const projected = await options.home(agentId);
|
|
35
34
|
if (projected === null)
|
|
@@ -45,7 +44,7 @@ export function createHomeAwareAgentMailNamespace(options) {
|
|
|
45
44
|
if (result === "accepted" ||
|
|
46
45
|
result === "duplicate" ||
|
|
47
46
|
!("receipt" in result))
|
|
48
|
-
throw new Error("Agent mail acknowledgment lacks
|
|
47
|
+
throw new Error("Agent mail acknowledgment lacks a valid acceptance or Held receipt");
|
|
49
48
|
if (result.status !== "refused")
|
|
50
49
|
options.accepted(result.receipt);
|
|
51
50
|
return result;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type
|
|
1
|
+
import { type SeamReplayStore } from "@fungi.computer/seam";
|
|
2
|
+
import { type PostEnvelope, type PostDeliveryResult } from "@fungi.computer/carrier/post";
|
|
3
|
+
import { type WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
3
4
|
export type MachineLinkDevice = Readonly<{
|
|
4
5
|
deviceId: string;
|
|
5
6
|
memberId: string;
|
|
@@ -8,16 +9,21 @@ export type MachineLinkDevice = Readonly<{
|
|
|
8
9
|
}>;
|
|
9
10
|
/**
|
|
10
11
|
* Native device socket I/O. Pins/member authority and mail admission are supplied
|
|
11
|
-
* by their existing owners. Native attachments survive hibernation;
|
|
12
|
-
*
|
|
12
|
+
* by their existing owners. Native attachments survive hibernation; Carrier
|
|
13
|
+
* owns durable custody and per-Agent ordering across socket lifetimes.
|
|
13
14
|
*/
|
|
14
15
|
export declare class MachineLinkHost {
|
|
15
16
|
#private;
|
|
16
17
|
private readonly context;
|
|
18
|
+
private readonly owner;
|
|
17
19
|
private readonly options;
|
|
18
20
|
private readonly channels;
|
|
19
|
-
private
|
|
20
|
-
private readonly
|
|
21
|
+
private replayRuntime;
|
|
22
|
+
private readonly router;
|
|
23
|
+
private readonly custodyRoutes;
|
|
24
|
+
private deviceId;
|
|
25
|
+
private custodyRuntime;
|
|
26
|
+
private readonly receipts;
|
|
21
27
|
constructor(context: Pick<DurableObjectState, "getWebSockets" | "acceptWebSocket" | "setWebSocketAutoResponse" | "waitUntil">, owner: WatchdogSqliteOwner, options: Readonly<{
|
|
22
28
|
teamId: string;
|
|
23
29
|
directory: () => Promise<readonly {
|
|
@@ -25,14 +31,39 @@ export declare class MachineLinkHost {
|
|
|
25
31
|
name: string;
|
|
26
32
|
}[]>;
|
|
27
33
|
authorize: (deviceId: string) => Promise<MachineLinkDevice>;
|
|
34
|
+
replay: () => Promise<SeamReplayStore>;
|
|
28
35
|
receive: (device: MachineLinkDevice, envelope: PostEnvelope) => Promise<PostDeliveryResult>;
|
|
36
|
+
/** Arm the Durable Object alarm for Carrier/Watchdog progress. */
|
|
37
|
+
scheduleWake: (at: number) => Promise<void>;
|
|
29
38
|
/** The host's failure log for refused dials and closed links. */
|
|
30
39
|
report: (operation: string, cause: unknown) => void;
|
|
31
40
|
}>);
|
|
32
41
|
fetch(request: Request): Promise<Response>;
|
|
42
|
+
private replayStore;
|
|
43
|
+
/** Start the fresh socket and then hand durable rows to its live channel. */
|
|
44
|
+
connected(socket: WebSocket, deviceId: string): Promise<void>;
|
|
33
45
|
private publishDirectory;
|
|
34
46
|
private channel;
|
|
35
47
|
deliver(deviceId: string, envelope: PostEnvelope): Promise<PostDeliveryResult>;
|
|
48
|
+
private deliverLiveOperation;
|
|
49
|
+
private deliverCustodyMail;
|
|
50
|
+
/** Run due Carrier work; expired rows remain visible as Carrier dead letters. */
|
|
51
|
+
alarm(): Promise<void>;
|
|
52
|
+
/** Expose terminal custody rows so S9 can stage its own disposition reply. */
|
|
53
|
+
deadLetters(afterKey?: string): Promise<Readonly<{
|
|
54
|
+
letters: readonly Readonly<{
|
|
55
|
+
key: string;
|
|
56
|
+
reason: "refused" | "exhausted" | "expired";
|
|
57
|
+
envelope: PostEnvelope;
|
|
58
|
+
attempts: number;
|
|
59
|
+
}>[];
|
|
60
|
+
nextCursor: string | undefined;
|
|
61
|
+
}>>;
|
|
62
|
+
private deliverToSocket;
|
|
63
|
+
private custody;
|
|
64
|
+
private createCustody;
|
|
65
|
+
private releaseCustody;
|
|
66
|
+
private drainCustody;
|
|
36
67
|
message(socket: WebSocket, message: string | ArrayBuffer): Promise<void>;
|
|
37
68
|
close(socket: WebSocket): void;
|
|
38
69
|
private authorizeSocket;
|