@fungi.computer/oyster 0.4.1 → 0.6.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.
Files changed (86) hide show
  1. package/README.md +46 -8
  2. package/dist/agents.d.ts +2 -1
  3. package/dist/agents.js +67 -8
  4. package/dist/apps/gate.d.ts +4 -0
  5. package/dist/apps/gate.js +3 -0
  6. package/dist/auth.d.ts +24 -1
  7. package/dist/auth.js +38 -0
  8. package/dist/boot-host-script.js +1 -1
  9. package/dist/build/candidate-cli.mjs +27 -5
  10. package/dist/channels/binding.d.ts +70 -0
  11. package/dist/channels/binding.js +97 -0
  12. package/dist/channels/conversations.d.ts +246 -0
  13. package/dist/channels/conversations.js +705 -0
  14. package/dist/channels/ids.d.ts +8 -0
  15. package/dist/channels/ids.js +15 -0
  16. package/dist/channels/visitor-channel.d.ts +53 -0
  17. package/dist/channels/visitor-channel.js +124 -0
  18. package/dist/channels/visitor-edge.d.ts +115 -0
  19. package/dist/channels/visitor-edge.js +582 -0
  20. package/dist/channels/visitor-request.d.ts +7 -0
  21. package/dist/channels/visitor-request.js +17 -0
  22. package/dist/channels/visitor-stream-slots.d.ts +2 -0
  23. package/dist/channels/visitor-stream-slots.js +33 -0
  24. package/dist/channels/visitors.d.ts +47 -0
  25. package/dist/channels/visitors.js +181 -0
  26. package/dist/cloudflare.d.ts +8 -2
  27. package/dist/cloudflare.js +27 -1
  28. package/dist/computer-target.d.ts +5 -0
  29. package/dist/computer-target.js +11 -0
  30. package/dist/host.d.ts +7 -4
  31. package/dist/host.js +96 -17
  32. package/dist/internal/host-gate.d.ts +6 -4
  33. package/dist/internal/host-gate.js +14 -7
  34. package/dist/invocation.d.ts +11 -4
  35. package/dist/invocation.js +134 -91
  36. package/dist/keys.d.ts +4 -1
  37. package/dist/keys.js +15 -4
  38. package/dist/machine-link/authority.d.ts +7 -0
  39. package/dist/machine-link/delivery.d.ts +12 -0
  40. package/dist/machine-link/delivery.js +22 -1
  41. package/dist/machine-link/disposition.d.ts +16 -0
  42. package/dist/machine-link/disposition.js +65 -0
  43. package/dist/machine-link/host.d.ts +11 -2
  44. package/dist/machine-link/host.js +125 -5
  45. package/dist/machine-link/link.d.ts +4 -0
  46. package/dist/machine-link/link.js +47 -1
  47. package/dist/page/capabilities.js +1 -1
  48. package/dist/page/child-selection.d.ts +27 -0
  49. package/dist/page/child-selection.js +38 -0
  50. package/dist/page/mount.js +1 -1
  51. package/dist/page-script.js +4 -4
  52. package/dist/release.d.ts +13 -0
  53. package/dist/release.js +10 -0
  54. package/dist/request-origin.d.ts +4 -0
  55. package/dist/request-origin.js +10 -0
  56. package/dist/standalone/agent-mail.js +4 -1
  57. package/dist/standalone/agents.d.ts +8 -0
  58. package/dist/standalone/agents.js +30 -11
  59. package/dist/standalone/channel-session-door.d.ts +4 -0
  60. package/dist/standalone/channel-session-door.js +72 -0
  61. package/dist/standalone/computer-fleet.js +7 -1
  62. package/dist/standalone/machine-link.d.ts +2 -0
  63. package/dist/standalone/machine-link.js +34 -1
  64. package/dist/standalone/tenancy.d.ts +3 -0
  65. package/dist/standalone/tenancy.js +7 -2
  66. package/dist/testing/capabilities.d.ts +2 -0
  67. package/dist/testing/capabilities.js +3 -1
  68. package/dist/turnstile.d.ts +27 -0
  69. package/dist/turnstile.js +51 -0
  70. package/dist/workers-git.js +21 -2
  71. package/dist/workers.d.ts +3 -0
  72. package/dist/workers.js +15 -12
  73. package/docs/api/agents.md +8 -4
  74. package/docs/api/apps.md +8 -4
  75. package/docs/api/auth.md +55 -0
  76. package/docs/api/browser.md +40 -16
  77. package/docs/api/build-contract.md +2 -2
  78. package/docs/api/cloudflare.md +26 -1
  79. package/docs/api/host.md +22 -13
  80. package/docs/api/invocation.md +7 -3
  81. package/docs/api/keys.md +25 -3
  82. package/docs/api/workers.md +38 -30
  83. package/docs/site/agent-quickstart.mdx +29 -2
  84. package/examples/standalone/wrangler.celld.jsonc +66 -0
  85. package/examples/standalone/wrangler.jsonc +3 -4
  86. package/package.json +53 -17
package/dist/workers.js CHANGED
@@ -35,10 +35,11 @@ import * as Schema from "effect/Schema";
35
35
  import * as Stream from "effect/Stream";
36
36
  import { releaseSchema } from "@fungi.computer/puffball/effect";
37
37
  import { AppInstanceError, } from "./index.js";
38
- import { createCloudflareAppInstance, r2ArtifactStore, createCloudflareInvocationGate, decodeFrameBindingProps, captureFrameBindingReply, frameBindingEntrypoint, backgroundFrameMethods, } from "./cloudflare.js";
38
+ import { createCloudflareAppInstance, r2ArtifactStore, createCloudflareInvocationGate, edgeVerifiedObjectCache, decodeFrameBindingProps, captureFrameBindingReply, frameBindingEntrypoint, backgroundFrameMethods, } from "./cloudflare.js";
39
39
  import { invocationFactsSchema, assertBackgroundBinding, admitBackgroundFrame, } from "./invocation.js";
40
40
  import { deliverBackground, deliverGitPush } from "./internal/delivery.js";
41
- import { createOyster, verifyRelease, defaultAssetPolicy, } from "./host.js";
41
+ import { releaseCache } from "./internal/host-gate.js";
42
+ import { createOyster, } from "./host.js";
42
43
  import { createGitPushJournal } from "./git-journal.js";
43
44
  import { AppStorageError, } from "@fungi.computer/agar/storage";
44
45
  import { storageFrameMethods } from "./storage-frame.js";
@@ -148,6 +149,7 @@ export class OysterRegistry extends DurableObject {
148
149
  return this.approvalHome(pin).capability(actor).setAutoApprove(input);
149
150
  }
150
151
  #rootData;
152
+ #releases = releaseCache(r2ArtifactStore(this.env.RELEASES));
151
153
  /** Append-only App source seed receipts; a Git-capable subclass forks over these. */
152
154
  appSourceReceipts;
153
155
  /** Team-owned fork registrations, one App publisher record per App. */
@@ -196,7 +198,7 @@ export class OysterRegistry extends DurableObject {
196
198
  installations: {
197
199
  list: () => Promise.resolve(this.list()),
198
200
  },
199
- resolveRelease: (release) => verifyRelease(r2ArtifactStore(env.RELEASES), release, defaultAssetPolicy, false),
201
+ resolveRelease: this.#releases,
200
202
  });
201
203
  }
202
204
  /** The installation owner's RPC: admit, read, load, plan or commit one
@@ -319,7 +321,7 @@ export class OysterRegistry extends DurableObject {
319
321
  expected.selection.releaseId ||
320
322
  installation.release.appId !== expected.selection.appId)))
321
323
  throw new AppStorageError("unavailable");
322
- const resolved = await verifyRelease(r2ArtifactStore(this.env.RELEASES), installation.release, defaultAssetPolicy, false);
324
+ const resolved = await this.#releases(installation.release);
323
325
  if (expected !== undefined &&
324
326
  resolved.backend?.digest !== expected.artifactDigest)
325
327
  throw new AppStorageError("unavailable");
@@ -756,9 +758,9 @@ export class OysterInstance extends DurableObject {
756
758
  }
757
759
  async executeBindingCall(raw, method, input) {
758
760
  const props = decodeFrameBindingProps(raw);
759
- const frame = this.frames.active();
760
- if (props.instanceId !== frame.facts.instanceId ||
761
- props.frameId !== frame.frameId)
761
+ // The packet's own frame: a closed or foreign frame id refuses.
762
+ const frame = this.frames.active(props.frameId);
763
+ if (props.instanceId !== frame.facts.instanceId)
762
764
  throw new AppInstanceError("revoked");
763
765
  assertBackgroundBinding(frame, props.binding);
764
766
  // A guest packet is its own entry: exactly one registry read re-admits it.
@@ -787,7 +789,7 @@ export class OysterInstance extends DurableObject {
787
789
  .every((operation) => granted.includes(operation)))
788
790
  throw new AppInstanceError("binding_not_granted");
789
791
  const result = await row.dispatch(this.#bindingContext, frame, props, method, args);
790
- if (this.frames.active() !== frame)
792
+ if (this.frames.active(props.frameId) !== frame)
791
793
  throw new AppInstanceError("revoked");
792
794
  assertBackgroundBinding(frame, props.binding);
793
795
  return descriptor.decodeResult(result);
@@ -1102,10 +1104,7 @@ export function oysterWorkerHost(env, auth, options = {}) {
1102
1104
  },
1103
1105
  auth,
1104
1106
  workbench: {
1105
- directory: async () => {
1106
- await installations.list();
1107
- return registry().directory();
1108
- },
1107
+ directory: () => registry().directory(),
1109
1108
  storage: (installationId, ownerId) => ({
1110
1109
  read: (key) => registry().storageRead(installationId, ownerId, key),
1111
1110
  write: (input) => registry().storageWrite(installationId, ownerId, input),
@@ -1115,6 +1114,10 @@ export function oysterWorkerHost(env, auth, options = {}) {
1115
1114
  },
1116
1115
  guestLimits: guestLimits(options.guestLimits),
1117
1116
  artifacts,
1117
+ // Workers and workerd keep a zone cache; a runtime without one reads R2.
1118
+ ...("caches" in globalThis && {
1119
+ uiCache: edgeVerifiedObjectCache(caches.default, scope.origin),
1120
+ }),
1118
1121
  bootUi: oysterBootUi(env, scope),
1119
1122
  // File-only hosts may keep workspace files beside their Release artifacts.
1120
1123
  capabilities: [
@@ -358,16 +358,20 @@ optional bearer.
358
358
  ### agentCapabilities()
359
359
 
360
360
  ```ts
361
- function agentCapabilities(agents): readonly Readonly<object & object>[];
361
+ function agentCapabilities(
362
+ agents,
363
+ liveKey,
364
+ ): readonly Readonly<object & object>[];
362
365
  ```
363
366
 
364
367
  Agent adapters admitted against each App's declared capabilities.
365
368
 
366
369
  #### Parameters
367
370
 
368
- | Parameter | Type |
369
- | --------- | ------------------------------- |
370
- | `agents` | [`OysterAgents`](#oysteragents) |
371
+ | Parameter | Type |
372
+ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
373
+ | `agents` | [`OysterAgents`](#oysteragents) |
374
+ | `liveKey` | (`keyId`) => `Promise`\< \| `Readonly`\<\{ `keyId`: `string`; `owner`: `string`; `grantVersion`: `number`; `grants`: readonly `Readonly`\<\{ `capability`: [`OysterKeyCapability`](keys.md#oysterkeycapability); `targets`: `GrantTargets`; \}\>[]; \}\> \| `null`\> |
371
375
 
372
376
  #### Returns
373
377
 
package/docs/api/apps.md CHANGED
@@ -852,6 +852,10 @@ const gateRequestSchema: ZodLazy<ZodDiscriminatedUnion<[ZodObject<{
852
852
  | "channels.read"
853
853
  | "channels.personal.manage", unknown>>;
854
854
  operation: ZodString;
855
+ effect: ZodEnum<{
856
+ read: "read";
857
+ write: "write";
858
+ }>;
855
859
  }, $strict>], "kind">>;
856
860
  ```
857
861
 
@@ -1676,10 +1680,10 @@ stay outside this pure gate.
1676
1680
 
1677
1681
  #### Parameters
1678
1682
 
1679
- | Parameter | Type |
1680
- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1681
- | `facts` | [`GateFacts`](#gatefacts) |
1682
- | `rawRequest` | \| \{ `kind`: `"method"`; `name`: `string`; `input`: `unknown`; `via`: `"ui"` \| `"agent"` \| `"mcp"` \| `"workflow"`; `resource?`: `Readonly`\<\{ `kind`: `string`; `id`: `string`; \}\>; \} \| \{ `kind`: `"capability"`; `capability`: \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"approvals"` \| `"agent.list"` \| `"agent.manage"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"agent.read"` \| `"agent.send"` \| `"llm.generation"` \| `"media.generation"` \| `"games:servers"` \| `"git.repository"` \| `"apps.open"` \| `"apps.call"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`; `operation?`: `string`; `resource`: \| `"any"` \| `Readonly`\<\{ `kind`: `string`; `id`: `string`; \}\>; \} \| \{ `kind`: `"capability-route"`; `capability`: \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"approvals"` \| `"agent.list"` \| `"agent.manage"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"agent.read"` \| `"agent.send"` \| `"llm.generation"` \| `"media.generation"` \| `"games:servers"` \| `"git.repository"` \| `"apps.open"` \| `"apps.call"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`; `operation`: `string`; \} \| \{ `kind`: `"binding"`; `binding`: \| `"BACKGROUND"` \| `"GIT"` \| `"EVENTS"` \| `"APPS"` \| `"WORKFLOWS"` \| `"APP_STORAGE"` \| `"MEDIA"` \| `"COMPUTER"` \| `"INBOX"` \| `"AGENTS"`; `operation`: `string`; \} \| \{ `kind`: `"list-tools"`; \} \| \{ `kind`: `"backend"`; \} |
1683
+ | Parameter | Type |
1684
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1685
+ | `facts` | [`GateFacts`](#gatefacts) |
1686
+ | `rawRequest` | \| \{ `kind`: `"method"`; `name`: `string`; `input`: `unknown`; `via`: `"ui"` \| `"agent"` \| `"mcp"` \| `"workflow"`; `resource?`: `Readonly`\<\{ `kind`: `string`; `id`: `string`; \}\>; \} \| \{ `kind`: `"capability"`; `capability`: \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"approvals"` \| `"agent.list"` \| `"agent.manage"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"agent.read"` \| `"agent.send"` \| `"llm.generation"` \| `"media.generation"` \| `"games:servers"` \| `"git.repository"` \| `"apps.open"` \| `"apps.call"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`; `operation?`: `string`; `resource`: \| `"any"` \| `Readonly`\<\{ `kind`: `string`; `id`: `string`; \}\>; \} \| \{ `kind`: `"capability-route"`; `capability`: \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"approvals"` \| `"agent.list"` \| `"agent.manage"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"agent.read"` \| `"agent.send"` \| `"llm.generation"` \| `"media.generation"` \| `"games:servers"` \| `"git.repository"` \| `"apps.open"` \| `"apps.call"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`; `operation`: `string`; `effect`: `"read"` \| `"write"`; \} \| \{ `kind`: `"binding"`; `binding`: \| `"BACKGROUND"` \| `"GIT"` \| `"EVENTS"` \| `"APPS"` \| `"WORKFLOWS"` \| `"APP_STORAGE"` \| `"MEDIA"` \| `"COMPUTER"` \| `"INBOX"` \| `"AGENTS"`; `operation`: `string`; \} \| \{ `kind`: `"list-tools"`; \} \| \{ `kind`: `"backend"`; \} |
1683
1687
 
1684
1688
  #### Returns
1685
1689
 
package/docs/api/auth.md CHANGED
@@ -8,6 +8,26 @@
8
8
 
9
9
  # auth
10
10
 
11
+ ## Type Aliases
12
+
13
+ ### OysterKeySessionResolver
14
+
15
+ ```ts
16
+ type OysterKeySessionResolver = Readonly<{
17
+ resolve: (value) => Promise<Readonly<{
18
+ keyId: string;
19
+ }> | null>;
20
+ }>;
21
+ ```
22
+
23
+ The host owns session minting, hashing/storage and expiry. A session resolves
24
+ only to a key id, never to owner or grant authority. Hosts should use Path=/,
25
+ HttpOnly, Secure and SameSite=Lax (or stricter) as defense in depth; Oyster
26
+ independently checks request provenance. Cookie-only admission allows GET/HEAD
27
+ navigation. Every other method requires Origin to be present and equal the
28
+ request URL's origin, and Sec-Fetch-Site not to be cross-site. Missing Origin is
29
+ refused.
30
+
11
31
  ## Variables
12
32
 
13
33
  ### ownerKeyCookieName
@@ -18,6 +38,16 @@ const ownerKeyCookieName: "oyster_session" = "oyster_session";
18
38
 
19
39
  Cookie emitted by ownerKeyAuth, for composing credential-specific adapters.
20
40
 
41
+ ---
42
+
43
+ ### keySessionCookieName
44
+
45
+ ```ts
46
+ const keySessionCookieName: "oyster_key_session" = "oyster_key_session";
47
+ ```
48
+
49
+ Host-minted opaque sessions for named keys, separate from the owner cookie.
50
+
21
51
  ## Functions
22
52
 
23
53
  ### ownerKeyAuth()
@@ -61,3 +91,28 @@ The owner key, or a named key admitted as its canonical Team-key actor.
61
91
  #### Returns
62
92
 
63
93
  [`OysterAuth`](host.md#oysterauth)
94
+
95
+ ---
96
+
97
+ ### keySessionAuth()
98
+
99
+ ```ts
100
+ function keySessionAuth(auth, ownerId, keys, sessions): OysterAuth;
101
+ ```
102
+
103
+ Add host-owned browser sessions to ownerOrKeyAuth. Explicit credentials and
104
+ non-401 refusals keep their ordinary result; ambient cookies cannot undo them.
105
+ Every cookie admission rechecks the key's liveness and canonical owner.
106
+
107
+ #### Parameters
108
+
109
+ | Parameter | Type |
110
+ | ---------- | --------------------------------------------------------------------- |
111
+ | `auth` | [`OysterAuth`](host.md#oysterauth) |
112
+ | `ownerId` | `string` |
113
+ | `keys` | () => `NonNullable`\<[`OysterHost`](host.md#oysterhost)\[`"keys"`\]\> |
114
+ | `sessions` | [`OysterKeySessionResolver`](#oysterkeysessionresolver) |
115
+
116
+ #### Returns
117
+
118
+ [`OysterAuth`](host.md#oysterauth)
@@ -91,7 +91,7 @@ Data.TaggedError("CloudBrowserUrlDenied").message;
91
91
 
92
92
  ### CloudBrowserUpstreamError
93
93
 
94
- The provider (Cloudflare Browser Run) answered with a failure.
94
+ The configured provider answered with a failure or is unreachable.
95
95
 
96
96
  #### Extends
97
97
 
@@ -593,10 +593,11 @@ type BrowserLimits = Readonly<{
593
593
 
594
594
  `@fungi.computer/oyster/browser`: the one agent browser for the Hub and a
595
595
  standalone Oyster. A host builds the port from a provider (`browserRunSessions`
596
- or `bindingSessions`, plus Browser Run Quick Reads when it has them), its
597
- required metering owner and an event sink; `createBrowser` owns lease ownership,
598
- egress policy and billing order; `browserCapabilitySource` is the agent's
599
- `cloudBrowser` tool. Raw CDP stays internal to this directory.
596
+ or `bindingSessions` or `onboxBrowserSessions` on staging, plus Browser Run
597
+ Quick Reads when it has them), its required metering owner and an event sink;
598
+ `createBrowser` owns lease ownership, egress policy and billing order;
599
+ `browserCapabilitySource` is the agent's `cloudBrowser` tool. Raw CDP stays
600
+ internal to this directory.
600
601
 
601
602
  ---
602
603
 
@@ -643,10 +644,11 @@ type Browser = Readonly<{
643
644
 
644
645
  `@fungi.computer/oyster/browser`: the one agent browser for the Hub and a
645
646
  standalone Oyster. A host builds the port from a provider (`browserRunSessions`
646
- or `bindingSessions`, plus Browser Run Quick Reads when it has them), its
647
- required metering owner and an event sink; `createBrowser` owns lease ownership,
648
- egress policy and billing order; `browserCapabilitySource` is the agent's
649
- `cloudBrowser` tool. Raw CDP stays internal to this directory.
647
+ or `bindingSessions` or `onboxBrowserSessions` on staging, plus Browser Run
648
+ Quick Reads when it has them), its required metering owner and an event sink;
649
+ `createBrowser` owns lease ownership, egress policy and billing order;
650
+ `browserCapabilitySource` is the agent's `cloudBrowser` tool. Raw CDP stays
651
+ internal to this directory.
650
652
 
651
653
  ---
652
654
 
@@ -1058,9 +1060,9 @@ caller (`browser.ts`) owns the deadline through `signal`.
1058
1060
  type BrowserSessions = Readonly<{}>;
1059
1061
  ```
1060
1062
 
1061
- The provider half of the host port. Built only by the two functions below. Each
1062
- step is separate so `createBrowser` owns a provider session id from the moment
1063
- it exists, including when its CDP connect then fails.
1063
+ The provider half of the host port. Each step is separate so `createBrowser`
1064
+ owns a provider session id from the moment it exists, including when its CDP
1065
+ connect then fails.
1064
1066
 
1065
1067
  ---
1066
1068
 
@@ -1338,10 +1340,11 @@ function createBrowser(host, limits): Browser;
1338
1340
 
1339
1341
  `@fungi.computer/oyster/browser`: the one agent browser for the Hub and a
1340
1342
  standalone Oyster. A host builds the port from a provider (`browserRunSessions`
1341
- or `bindingSessions`, plus Browser Run Quick Reads when it has them), its
1342
- required metering owner and an event sink; `createBrowser` owns lease ownership,
1343
- egress policy and billing order; `browserCapabilitySource` is the agent's
1344
- `cloudBrowser` tool. Raw CDP stays internal to this directory.
1343
+ or `bindingSessions` or `onboxBrowserSessions` on staging, plus Browser Run
1344
+ Quick Reads when it has them), its required metering owner and an event sink;
1345
+ `createBrowser` owns lease ownership, egress policy and billing order;
1346
+ `browserCapabilitySource` is the agent's `cloudBrowser` tool. Raw CDP stays
1347
+ internal to this directory.
1345
1348
 
1346
1349
  #### Parameters
1347
1350
 
@@ -1438,3 +1441,24 @@ A standalone Oyster's own Workers BROWSER binding.
1438
1441
  #### Returns
1439
1442
 
1440
1443
  [`BrowserSessions`](#browsersessions)
1444
+
1445
+ ---
1446
+
1447
+ ### onboxBrowserSessions()
1448
+
1449
+ ```ts
1450
+ function onboxBrowserSessions(origin, fetcher?): BrowserSessions;
1451
+ ```
1452
+
1453
+ Staging uses Chromium's native CDP browser contexts; no session store.
1454
+
1455
+ #### Parameters
1456
+
1457
+ | Parameter | Type |
1458
+ | --------- | ------------------------- |
1459
+ | `origin` | `string` |
1460
+ | `fetcher` | [`HttpFetch`](#httpfetch) |
1461
+
1462
+ #### Returns
1463
+
1464
+ [`BrowserSessions`](#browsersessions)
@@ -966,7 +966,7 @@ function putContentObject(
966
966
  object,
967
967
  body,
968
968
  mediaType,
969
- ): Promise<"refused" | "stored">;
969
+ ): Promise<"stored" | "refused">;
970
970
  ```
971
971
 
972
972
  Stream one object into the store under its digest. The store refuses a body
@@ -987,7 +987,7 @@ the body is discarded, never stored.
987
987
 
988
988
  #### Returns
989
989
 
990
- `Promise`\<`"refused"` \| `"stored"`\>
990
+ `Promise`\<`"stored"` \| `"refused"`\>
991
991
 
992
992
  ---
993
993
 
@@ -305,6 +305,30 @@ frame or guestEnv factory is accepted.
305
305
 
306
306
  ---
307
307
 
308
+ ### edgeVerifiedObjectCache()
309
+
310
+ ```ts
311
+ function edgeVerifiedObjectCache(cache, origin): VerifiedObjectCache;
312
+ ```
313
+
314
+ The zone's edge cache as a [VerifiedObjectCache](host.md#verifiedobjectcache):
315
+ each verified object under a digest-named key on `origin` that no route serves.
316
+ A failed write is logged and answers `unstored`; the next reader verifies from
317
+ the store again.
318
+
319
+ #### Parameters
320
+
321
+ | Parameter | Type |
322
+ | --------- | --------------------------------------- |
323
+ | `cache` | `Pick`\<`Cache`, `"match"` \| `"put"`\> |
324
+ | `origin` | `string` |
325
+
326
+ #### Returns
327
+
328
+ [`VerifiedObjectCache`](host.md#verifiedobjectcache)
329
+
330
+ ---
331
+
308
332
  ### r2ArtifactStore()
309
333
 
310
334
  ```ts
@@ -435,7 +459,8 @@ function createCloudflareInvocationGate<Host, FacetClass>(
435
459
  ): InvocationGate<Host>;
436
460
  ```
437
461
 
438
- End the native App facet before shared frame admission releases its permit.
462
+ End the native App facet when live authority closes early, before the gate
463
+ admits again.
439
464
 
440
465
  #### Type Parameters
441
466
 
package/docs/api/host.md CHANGED
@@ -375,24 +375,17 @@ A capability adapter whose routes require live installation admission.
375
375
  ### OysterVariables
376
376
 
377
377
  ```ts
378
- type OysterVariables = object;
378
+ type OysterVariables = TimingVariables & object;
379
379
  ```
380
380
 
381
381
  What the host stamps once per request; inner routes read it with `c.get`.
382
382
 
383
- #### Properties
384
-
385
- ##### principal
386
-
387
- ```ts
388
- principal: OysterPrincipal;
389
- ```
390
-
391
- ##### installation?
383
+ #### Type Declaration
392
384
 
393
- ```ts
394
- optional installation?: Installation;
395
- ```
385
+ | Name | Type |
386
+ | --------------- | -------------------------------------- |
387
+ | `principal` | [`OysterPrincipal`](#oysterprincipal) |
388
+ | `installation?` | [`Installation`](apps.md#installation) |
396
389
 
397
390
  ---
398
391
 
@@ -473,6 +466,7 @@ type OysterHost = Readonly<{
473
466
  auth: OysterAuth;
474
467
  backgroundOriginKey?: () => Promise<CryptoKey>;
475
468
  artifacts: ArtifactStore;
469
+ uiCache?: VerifiedObjectCache;
476
470
  installations: InstallationStore;
477
471
  instances: InstanceDirectory;
478
472
  bootUi: (release) => Promise<UiBootCheckOutcome>;
@@ -1309,6 +1303,21 @@ type VerifiedRelease = Readonly<{
1309
1303
 
1310
1304
  A Release whose root, UI and backend artifacts were read and verified.
1311
1305
 
1306
+ ---
1307
+
1308
+ ### VerifiedObjectCache
1309
+
1310
+ ```ts
1311
+ type VerifiedObjectCache = Readonly<{
1312
+ match: (digest) => Promise<ReadableStream<Uint8Array> | null>;
1313
+ put: (digest, bytes) => Promise<"stored" | "unstored">;
1314
+ }>;
1315
+ ```
1316
+
1317
+ Copies of verified artifacts near the reader, keyed by content digest. An entry
1318
+ holds only bytes that already matched its digest, so a hit is served without
1319
+ another store read or hash.
1320
+
1312
1321
  ## Variables
1313
1322
 
1314
1323
  ### capabilityMethodKindSchema
@@ -122,7 +122,7 @@ The host's native guest-abort and authority-close hooks.
122
122
  ```ts
123
123
  type InvocationGate<Host> = Readonly<{
124
124
  run: <A>(facts, host, work) => Promise<A>;
125
- active: () => InvocationFrame<Host>;
125
+ active: (frameId) => InvocationFrame<Host>;
126
126
  revoke: (reason?, target?) => Promise<void>;
127
127
  exclusive: <A>(work) => Promise<A>;
128
128
  }>;
@@ -412,8 +412,12 @@ only its decision.
412
412
  function createInvocationGate<Host>(ports): InvocationGate<Host>;
413
413
  ```
414
414
 
415
- Serialize authority, preempt replay only for verified writes, and close each
416
- guest before admission.
415
+ Admit frames against one guest. Verified foreground reads share it, up to eight
416
+ at once; writes, raw backend requests, Gills and replay run alone. Every guest
417
+ binding packet names its frame, so a finished frame's leftover timers cannot
418
+ borrow a later frame's authority and a normal finish keeps the guest warm.
419
+ Closing authority early (revocation, expiry, a write preempting replay) aborts
420
+ the guest and so every frame sharing it; replay only yields to a verified write.
417
421
 
418
422
  #### Type Parameters
419
423
 
package/docs/api/keys.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  ```ts
16
16
  type OysterKeyCapability =
17
- "apps.read" | "apps.use" | "computer.use" | "machine.enroll";
17
+ "apps.read" | "apps.use" | "agents.use" | "computer.use" | "machine.enroll";
18
18
  ```
19
19
 
20
20
  Every capability an Oyster key may carry.
@@ -88,8 +88,9 @@ const oysterKeyVocabulary: GrantVocabulary<OysterKeyCapability>;
88
88
  `apps.use` also reaches `apps.read` (one level, per `defineGrants`'s own
89
89
  `implies`): a key that may change an App's data may also read it. `computer.use`
90
90
  has no narrower capability under it; the Computer is on or off, host-wide.
91
- `machine.enroll` admits enrollment only; it implies no App or Computer access.
92
- The enrollment owner, not this vocabulary, owns one-use consumption.
91
+ `agents.use` selects Agent ids independently of App grants. `machine.enroll`
92
+ admits enrollment only; it implies no App or Computer access. The enrollment
93
+ owner, not this vocabulary, owns one-use consumption.
93
94
 
94
95
  ---
95
96
 
@@ -159,6 +160,27 @@ name for MCP) to reach the full `AppToolAccess`.
159
160
 
160
161
  ---
161
162
 
163
+ ### keyAgentAccess()
164
+
165
+ ```ts
166
+ function keyAgentAccess(key, agentId): boolean;
167
+ ```
168
+
169
+ Agent access is scoped to the granted Agent ids, never inferred from App grants.
170
+
171
+ #### Parameters
172
+
173
+ | Parameter | Type |
174
+ | --------- | --------------------------------- |
175
+ | `key` | [`LiveOysterKey`](#liveoysterkey) |
176
+ | `agentId` | `string` |
177
+
178
+ #### Returns
179
+
180
+ `boolean`
181
+
182
+ ---
183
+
162
184
  ### keyAllowsComputerUse()
163
185
 
164
186
  ```ts