@fungi.computer/oyster 0.6.0 → 0.7.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 +132 -78
- package/dist/agent-archive-projection.d.ts +8 -0
- package/dist/agent-archive-projection.js +49 -0
- package/dist/agent-schedules.d.ts +50 -0
- package/dist/agent-schedules.js +17 -0
- package/dist/agents.d.ts +35 -14
- package/dist/agents.js +93 -144
- package/dist/app-builds.d.ts +7 -2
- package/dist/app-builds.js +19 -2
- package/dist/approval-capability.d.ts +1 -1
- package/dist/approval-capability.js +11 -16
- package/dist/approval-home.d.ts +2 -1
- package/dist/approval-home.js +5 -3
- package/dist/apps/catalog.js +1 -1
- package/dist/apps/gate.d.ts +4 -7
- package/dist/apps/gate.js +14 -29
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/installation.d.ts +4 -42
- package/dist/apps/installation.js +7 -75
- package/dist/apps/operations.js +0 -10
- package/dist/apps/permissions.d.ts +18 -8
- package/dist/apps/permissions.js +7 -1
- package/dist/auth.d.ts +1 -24
- package/dist/auth.js +9 -39
- package/dist/authorization.d.ts +307 -0
- package/dist/authorization.js +542 -0
- package/dist/boot-host-script.js +1 -1
- package/dist/build/build-jobs.d.ts +2 -2
- package/dist/build/candidate-builder.d.ts +2 -0
- package/dist/build/candidate-builder.js +5 -1
- package/dist/build/candidate-cli.js +3 -0
- package/dist/build/candidate-cli.mjs +59 -21
- package/dist/build/job-log.d.ts +23 -0
- package/dist/build/job-log.js +88 -0
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +11 -9
- package/dist/cloudflare.d.ts +4 -1
- package/dist/cloudflare.js +1 -1
- package/dist/files.js +9 -8
- package/dist/git-admission.d.ts +3 -3
- package/dist/git-admission.js +4 -6
- package/dist/git-door.d.ts +8 -7
- package/dist/git-door.js +18 -17
- package/dist/git.d.ts +1 -1
- package/dist/host.d.ts +33 -40
- package/dist/host.js +153 -272
- package/dist/installation-execution.js +10 -5
- package/dist/installation-operations.d.ts +4 -180
- package/dist/installation-operations.js +1 -5
- package/dist/installation-store-generation.d.ts +4 -1
- package/dist/installation-store-generation.js +59 -6
- package/dist/internal/delivery.js +0 -1
- package/dist/internal/host-gate.d.ts +3 -2
- package/dist/internal/host-gate.js +5 -18
- package/dist/internal/invocation-timing.d.ts +16 -0
- package/dist/internal/invocation-timing.js +23 -0
- package/dist/internal/registry-installations.js +1 -0
- package/dist/invocation.d.ts +15 -14
- package/dist/invocation.js +58 -26
- package/dist/keys.d.ts +2 -19
- package/dist/keys.js +1 -37
- package/dist/mcp.d.ts +15 -27
- package/dist/mcp.js +105 -155
- package/dist/page/agent-browser.d.ts +15 -0
- package/dist/page/agent-browser.js +162 -0
- package/dist/page/app-permissions-port.d.ts +3 -0
- package/dist/page/app-permissions-port.js +43 -0
- package/dist/page/app-permissions.d.ts +11 -0
- package/dist/page/app-permissions.js +296 -0
- package/dist/page/isolated-page-theme.d.ts +2 -0
- package/dist/page/isolated-page-theme.js +11 -0
- package/dist/page/mount.d.ts +2 -13
- package/dist/page/mount.js +7 -93
- package/dist/page-script.d.ts +0 -3
- package/dist/page-script.js +3 -4
- package/dist/standalone/agent-archive-owner.d.ts +42 -0
- package/dist/standalone/agent-archive-owner.js +116 -0
- package/dist/standalone/agent-archive-schedules.d.ts +6 -0
- package/dist/standalone/agent-archive-schedules.js +76 -0
- package/dist/standalone/agent-archive.d.ts +31 -0
- package/dist/standalone/agent-archive.js +67 -0
- package/dist/standalone/agent-computers.d.ts +7 -6
- package/dist/standalone/agent-computers.js +4 -7
- package/dist/standalone/agent-mail.d.ts +2 -2
- package/dist/standalone/agents.d.ts +157 -20
- package/dist/standalone/agents.js +234 -30
- package/dist/standalone/app-builds-binding.js +1 -1
- package/dist/standalone/app-set.js +2 -54
- package/dist/standalone/app-source.d.ts +3 -4
- package/dist/standalone/app-source.js +12 -9
- package/dist/standalone/builds.d.ts +2 -14
- package/dist/standalone/builds.js +15 -13
- package/dist/standalone/computer-fleet.d.ts +4 -4
- package/dist/standalone/computer-fleet.js +13 -42
- package/dist/standalone/computer.d.ts +4 -3
- package/dist/standalone/computer.js +52 -53
- package/dist/standalone/dashboard-approval.d.ts +10 -0
- package/dist/standalone/dashboard-approval.js +66 -0
- package/dist/standalone/machine-link.d.ts +4 -221
- package/dist/standalone/machine-link.js +21 -15
- package/dist/standalone/media.d.ts +1 -0
- package/dist/standalone/media.js +20 -14
- package/dist/standalone/owner-routes.d.ts +16 -0
- package/dist/standalone/owner-routes.js +21 -0
- package/dist/standalone/permission-resources.d.ts +5 -0
- package/dist/standalone/permission-resources.js +31 -0
- package/dist/standalone/registry.d.ts +7 -0
- package/dist/standalone/registry.js +3 -0
- package/dist/standalone/tenancy.d.ts +0 -3
- package/dist/standalone/tenancy.js +2 -5
- package/dist/standalone/worker-routes.d.ts +19 -0
- package/dist/standalone/worker-routes.js +9 -0
- package/dist/standalone/worker.d.ts +1 -1
- package/dist/standalone/worker.js +27 -35
- package/dist/standalone/workflow-binding.d.ts +8 -3
- package/dist/standalone/workflow-binding.js +14 -5
- package/dist/standalone/workflow-git-capture.js +2 -1
- package/dist/standalone/workflow-host.d.ts +8 -103
- package/dist/standalone/workflow-host.js +1 -1
- package/dist/standalone/workflow-node-capture.js +2 -2
- package/dist/standalone/workflow-root-authority.d.ts +1 -1
- package/dist/standalone/workflow-root-authority.js +1 -1
- package/dist/standalone/workflow-schedules.d.ts +14 -5
- package/dist/standalone/workflow-schedules.js +11 -5
- package/dist/standalone/workflow-webhooks.d.ts +4 -18
- package/dist/standalone/workflow-webhooks.js +5 -5
- package/dist/standalone/workflows.d.ts +36 -28
- package/dist/standalone/workflows.js +6 -3
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.d.ts +7 -6
- package/dist/testing/capabilities.js +3 -6
- package/dist/testing/installation.d.ts +0 -1
- package/dist/testing/installation.js +2 -5
- package/dist/workers-git.d.ts +12 -4
- package/dist/workers-git.js +38 -35
- package/dist/workers.d.ts +9 -1
- package/dist/workers.js +44 -8
- package/dist/workflows.d.ts +2 -0
- package/dist/workflows.js +1 -0
- package/docs/api/README.md +7 -0
- package/docs/api/agent-schedules.md +175 -0
- package/docs/api/agents.md +34 -13
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +323 -225
- package/docs/api/auth.md +0 -55
- package/docs/api/authorization.md +1300 -0
- package/docs/api/build.md +40 -37
- package/docs/api/cloudflare.md +3 -3
- package/docs/api/git.md +20 -11
- package/docs/api/host.md +64 -192
- package/docs/api/index.md +1 -1
- package/docs/api/invocation.md +14 -14
- package/docs/api/keys.md +1 -98
- package/docs/api/mcp.md +43 -40
- package/docs/api/modules.md +2 -0
- package/docs/api/page/mount.md +16 -19
- package/docs/api/workers-git.md +46 -12
- package/docs/api/workers.md +124 -235
- package/package.json +39 -20
- package/dist/page/computer-target.d.ts +0 -3
- package/dist/page/computer-target.js +0 -9
- package/dist/standalone/channel-session-door.d.ts +0 -4
- package/dist/standalone/channel-session-door.js +0 -72
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@ Oyster puts a desktop of Apps on your own Cloudflare account: a git platform,
|
|
|
4
4
|
agents, a terminal, an editor, workflows and image generation, each built from a
|
|
5
5
|
git commit on your own Computer.
|
|
6
6
|
|
|
7
|
+
The owner can read a build's status at `GET /api/builds/:jobId` and its combined
|
|
8
|
+
stdout/stderr at `GET /api/builds/:jobId?log`. The status diagnostic stays
|
|
9
|
+
within 200 characters and points to the log. The log returns base64url-encoded
|
|
10
|
+
raw bytes; if output exceeds 2 MiB it retains the head and tail and reports
|
|
11
|
+
`truncated.droppedBytes`. Both reads use the same owner authorization.
|
|
12
|
+
|
|
7
13
|
<picture>
|
|
8
14
|
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/fungi-computer/oyster/main/docs/media/desktop-light.png">
|
|
9
15
|
<img alt="The Tourtois desktop on Oyster with three Apps tiled: Trellis editing the Bonsai checks graph, Bonsai drawing a repository's network of agent forks, and Shed's terminal on the Computer" src="https://raw.githubusercontent.com/fungi-computer/oyster/main/docs/media/desktop-dark.png">
|
|
@@ -178,6 +184,42 @@ repositories and namespace. Use the configured resource names and confirm each
|
|
|
178
184
|
is gone through Cloudflare's dashboard or API. Capture teardown errors and the
|
|
179
185
|
Wrangler version rather than silently adding unrelated token permissions.
|
|
180
186
|
|
|
187
|
+
### Upgrading to owner-only Oyster
|
|
188
|
+
|
|
189
|
+
Upgrading to 0.7.0 resets the installed Apps when the registry contains the
|
|
190
|
+
pre-owner-only `visibility`, `approvedAnonymousMethods` or `anonymousApproval`
|
|
191
|
+
fields. This clears all installation rows and their installation command journal
|
|
192
|
+
once, renews the store incarnation, and permanently records the reset time and
|
|
193
|
+
row count. It does not uninstall Apps through their storage-purging delete path.
|
|
194
|
+
Agents, Sessions, files, keys, source and release data, tenancy, approval
|
|
195
|
+
records and Computer state are retained.
|
|
196
|
+
|
|
197
|
+
While no App is enabled, Oyster Home shows:
|
|
198
|
+
|
|
199
|
+
> This update reset your installed Apps. Reinstall them with npx oyster
|
|
200
|
+
> install-apps.
|
|
201
|
+
|
|
202
|
+
Run `npx oyster install-apps` to reinstall. The notice disappears when an App is
|
|
203
|
+
enabled; the audit fact remains. This is not a compatibility reader or a reset
|
|
204
|
+
on arbitrary corruption. The separate pre-0.3 refusal below is unchanged.
|
|
205
|
+
|
|
206
|
+
Same-ID App settings: kept and back after reinstall; backend SQLite: kept but
|
|
207
|
+
not reachable.
|
|
208
|
+
|
|
209
|
+
Each installed App's own backend database (for example saved Notes) stays in
|
|
210
|
+
storage but is no longer reachable; reinstalling creates a new App Instance that
|
|
211
|
+
starts empty. There is no recovery for App backend data in 0.7.0; reinstalling
|
|
212
|
+
with `npx oyster install-apps` restores the Apps and their same-ID App settings,
|
|
213
|
+
and Agents, Sessions, files and keys are retained.
|
|
214
|
+
|
|
215
|
+
These are different stores. Backend SQLite belongs to the Instance's `app`
|
|
216
|
+
facet. Host `APP_STORAGE` settings instead belong to the installation address
|
|
217
|
+
plus App ID, key, scope and member membership version; the starter installer
|
|
218
|
+
reuses the same installation IDs, so the same App reconnects to those settings.
|
|
219
|
+
There is no automatic backend-data reattachment or change to either keying
|
|
220
|
+
scheme. Files on Computers and Agent/Session data have their own retained
|
|
221
|
+
owners.
|
|
222
|
+
|
|
181
223
|
### Upgrading from Oyster 0.2 or earlier
|
|
182
224
|
|
|
183
225
|
Oyster 0.3 does not read data created by Oyster 0.1 or 0.2, and it does not
|
|
@@ -423,16 +465,16 @@ can host yourself.
|
|
|
423
465
|
|
|
424
466
|

|
|
425
467
|
|
|
426
|
-
| Piece | Owns
|
|
427
|
-
| ----------------- |
|
|
428
|
-
| Router |
|
|
429
|
-
| `OysterRegistry` | The installation list: which Release each installation runs
|
|
430
|
-
| R2 | Release artifacts, stored by digest.
|
|
431
|
-
| App Instance | One Durable Object per installation: lifecycle, owner state and the backend facet.
|
|
432
|
-
| Backend facet | The App's own code and SQLite database, loaded through the Worker Loader.
|
|
433
|
-
| git door | Smart-HTTP `clone`, `fetch` and `push`, admitted per key and forwarded to Artifacts.
|
|
434
|
-
| Tourtois | The trusted shell: launcher, windows, menus and settings, rendered as the top-level page.
|
|
435
|
-
| Platform bindings | Workflows, Computers, Agents and Workers AI, offered only to Apps that declare them.
|
|
468
|
+
| Piece | Owns |
|
|
469
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------- |
|
|
470
|
+
| Router | One declared route table (`authorization.ts`): sign-in, App pages and methods, owner routes, `/mcp`. |
|
|
471
|
+
| `OysterRegistry` | The installation list: which Release each installation runs. |
|
|
472
|
+
| R2 | Release artifacts, stored by digest. |
|
|
473
|
+
| App Instance | One Durable Object per installation: lifecycle, owner state and the backend facet. |
|
|
474
|
+
| Backend facet | The App's own code and SQLite database, loaded through the Worker Loader. |
|
|
475
|
+
| git door | Smart-HTTP `clone`, `fetch` and `push`, admitted per key and forwarded to Artifacts. |
|
|
476
|
+
| Tourtois | The trusted shell: launcher, windows, menus and settings, rendered as the top-level page. |
|
|
477
|
+
| Platform bindings | Workflows, Computers, Agents and Workers AI, offered only to Apps that declare them. |
|
|
436
478
|
|
|
437
479
|
The diagram source is
|
|
438
480
|
[`docs/media/architecture.mmd`](https://github.com/fungi-computer/oyster/blob/main/docs/media/architecture.mmd).
|
|
@@ -467,31 +509,17 @@ the Computer. `examples/standalone/wrangler.jsonc` declares every binding it
|
|
|
467
509
|
uses. Set the owner key as a secret (`OYSTER_OWNER_KEY`, 24+ characters) and
|
|
468
510
|
deploy with `wrangler deploy`. Sign in at `/login`.
|
|
469
511
|
|
|
470
|
-
|
|
471
|
-
`
|
|
472
|
-
`
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
`HttpOnly`, `Secure` and `SameSite=Lax` (or stricter) as defense in depth.
|
|
476
|
-
Oyster independently checks request provenance. Cookie-only GET/HEAD navigation
|
|
477
|
-
is allowed. Other methods require `Origin` to be present and equal the request
|
|
478
|
-
URL's origin, and refuse `Sec-Fetch-Site: cross-site`. Missing `Origin` is
|
|
479
|
-
refused, using the same strict predicate as the channel's POST routes. Oyster
|
|
480
|
-
stores no browser sessions. `keySessionAuth` wraps ordinary `ownerOrKeyAuth` at
|
|
481
|
-
the standalone tenancy composition root. Each cookie admission reopens the live
|
|
482
|
-
key and checks its canonical owner; every authenticated door still uses that
|
|
483
|
-
key's current grants. App pages refuse keys without App access, even when the
|
|
484
|
-
App is public to visitors. Explicit header authentication is unchanged, and a
|
|
485
|
-
failed Authorization header cannot fall back to a named-key cookie. The owner
|
|
486
|
-
cookie and owner-only management API are unchanged; without the binding
|
|
487
|
-
named-key cookies admit nobody.
|
|
512
|
+
Oyster is owner-only. A browser signs in only with the owner key, through
|
|
513
|
+
`/login` and its `SameSite=Strict` session cookie. Named keys sign in only by
|
|
514
|
+
header (`Authorization`), for MCP, Git and the API; no cookie admits a named
|
|
515
|
+
key. Anonymous requests reach only `/login`, `robots.txt` and digest-pinned
|
|
516
|
+
shell and App UI bytes.
|
|
488
517
|
|
|
489
518
|
The celld example is a limited trial, not the full Cloudflare setup above. It
|
|
490
|
-
requires access to Fungi's private celld fork
|
|
491
|
-
|
|
492
|
-
Cloudflare-only config.
|
|
493
|
-
|
|
494
|
-
package including it is released.
|
|
519
|
+
requires access to Fungi's private celld fork. Use
|
|
520
|
+
`examples/standalone/wrangler.celld.jsonc` beside `worker.ts`, not the
|
|
521
|
+
Cloudflare-only config. Oyster `0.6.0` includes both files under
|
|
522
|
+
`examples/standalone` in the installed package.
|
|
495
523
|
|
|
496
524
|
The layers it is built from stay exported for hosts that compose their own:
|
|
497
525
|
`@fungi.computer/oyster/turnstile` owns Cloudflare challenge configuration and
|
|
@@ -597,7 +625,7 @@ flowchart LR
|
|
|
597
625
|
a named key may also be the Basic password from a git credential helper. A
|
|
598
626
|
Workflow Git pass (a `wgp.` Bearer header, never in a URL) goes only to
|
|
599
627
|
`host.git.workflowPass`; without that port it admits nothing. Anonymous
|
|
600
|
-
requests
|
|
628
|
+
requests never clone or push.
|
|
601
629
|
- **Git admission** reopens the live installation before every provider call: it
|
|
602
630
|
must be enabled and its Release must declare `git.repository`. The tenant
|
|
603
631
|
scope prefixes every repository name (`<installation>--<repo>` for the default
|
|
@@ -659,7 +687,9 @@ and the exact installation, repository and read/write scope it names. The last
|
|
|
659
687
|
returns an `AdmittedGit`; the door forwards reads to `.read` and pushes to
|
|
660
688
|
`.push`. Provider credentials stay inside the storage adapter. `createOyster`
|
|
661
689
|
builds these ports from `host.auth`, `host.installations` and `host.git`, so
|
|
662
|
-
most hosts set `host.git` and never call `createGitDoor` directly.
|
|
690
|
+
most hosts set `host.git` and never call `createGitDoor` directly. There a named
|
|
691
|
+
key signs Git in only for installations its own grants reach: a clone needs App
|
|
692
|
+
read and a push needs App use; otherwise the door refuses it.
|
|
663
693
|
|
|
664
694
|
A host whose Workflow run owner issues Workflow Git credentials supplies
|
|
665
695
|
`workflowGitPass` in `oysterGitRoot` or `oysterWorkerGit` options. The run owner
|
|
@@ -675,11 +705,13 @@ branches are refused before the App's push policy runs.
|
|
|
675
705
|
### Provide App capabilities
|
|
676
706
|
|
|
677
707
|
Each `OysterCapability` supplies its Agar `id` and `version`, a mount `base`,
|
|
678
|
-
relative
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
708
|
+
relative declared `routes` (a `routes()` table from
|
|
709
|
+
`@fungi.computer/oyster/authorization`, where every route names its requirement:
|
|
710
|
+
`owner`, `agent`, `computer`, `member`...), and `methods`. Each method
|
|
711
|
+
descriptor has `kind`, `method` (HTTP method), and `path` (relative route).
|
|
712
|
+
`CapabilityMethodKind` is `json | transport | read | write`: JSON calls return
|
|
713
|
+
decoded data, transports return Fetch handles, and reads/writes return Agar's
|
|
714
|
+
stream handles.
|
|
683
715
|
|
|
684
716
|
Method paths may contain named parameters, such as `:agentId/sessions`. Oyster
|
|
685
717
|
uses `path-to-regexp` to compile input fields into those parameters; remaining
|
|
@@ -697,10 +729,9 @@ from `@fungi.computer/oyster/files` supplies the three file contracts over the
|
|
|
697
729
|
existing `/files/` GET, PUT and POST routes.
|
|
698
730
|
|
|
699
731
|
The page offers a capability only when the installed Release declares its ID and
|
|
700
|
-
`admits` allows the principal.
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
file mount.
|
|
732
|
+
`admits` allows the principal. Every route request also rechecks that
|
|
733
|
+
declaration and admission using the `X-Oyster-Installation` header set by the
|
|
734
|
+
page; a read grant does not authorize the write route sharing its file mount.
|
|
704
735
|
|
|
705
736
|
Consent approves capabilities, like OAuth scopes. An App upgrade may add a
|
|
706
737
|
method that uses an already-approved capability without asking again. A new
|
|
@@ -715,10 +746,9 @@ your approval.**
|
|
|
715
746
|
The `apps.call` permission also shows the declared target Apps and methods. Its
|
|
716
747
|
route approvals retain those exact pairs: adding or renaming a target method
|
|
717
748
|
needs owner approval. On Oyster these page calls run as the signed-in member
|
|
718
|
-
through the target App's ordinary method gate; Agents
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
capability.
|
|
749
|
+
through the target App's ordinary method gate; Agents and keys cannot use this
|
|
750
|
+
path. The target receives its own grants, never the caller App's grants. The Hub
|
|
751
|
+
shows the same consent list but does not offer this page capability.
|
|
722
752
|
|
|
723
753
|
### Connect an agent over MCP
|
|
724
754
|
|
|
@@ -743,8 +773,8 @@ operations are the ones every Computer advertises (`read`, `list`, `write`,
|
|
|
743
773
|
The host supplies `computer(principal)` on its workspace: it returns the
|
|
744
774
|
Computer's calls for a caller with Computer access and `null` otherwise. The
|
|
745
775
|
standalone host gives an owner, key or member the same Computer grant its
|
|
746
|
-
`/computer` routes use;
|
|
747
|
-
|
|
776
|
+
`/computer` routes use; Agents get none. The Computer accepts every call against
|
|
777
|
+
that grant. `oysterComputerTools(host, principal)` from
|
|
748
778
|
`@fungi.computer/oyster/mcp` returns this Knapsack source and its `grants`.
|
|
749
779
|
|
|
750
780
|
Workers route every MCP request to the tenant's `MCP` Durable Object. Export
|
|
@@ -769,15 +799,15 @@ module's own grant is `read`. For an Agent, `access` is
|
|
|
769
799
|
`{level, revision, grantedBy}` approval; missing it grants no tool access. For a
|
|
770
800
|
named key, `access` is `{kind: "key", keyId, byInstallation}`, one
|
|
771
801
|
`{level, revision, grantedBy}` per installation the key's own Ear Tag grants
|
|
772
|
-
admit (`
|
|
773
|
-
`byInstallation` and never listed at all, not merely
|
|
774
|
-
set once, at `POST /api/keys`, and never edited: to
|
|
775
|
-
revokes the key (`POST /api/keys/revoke`) and issues a new
|
|
776
|
-
does not approve an App operation. The gate also intersects
|
|
777
|
-
explicit approvals with its verified Release; `external`
|
|
778
|
-
with `approval_required`. MCP uses this same projection.
|
|
779
|
-
policy, installation authority, a key's own grants or a
|
|
780
|
-
source represents one immutable catalog snapshot.
|
|
802
|
+
admit (`appToolAccess` in `authorization.ts`); an installation the key has no
|
|
803
|
+
grant for is absent from `byInstallation` and never listed at all, not merely
|
|
804
|
+
refused. A key's grants are set once, at `POST /api/keys`, and never edited: to
|
|
805
|
+
change them the owner revokes the key (`POST /api/keys/revoke`) and issues a new
|
|
806
|
+
one. A module grant does not approve an App operation. The gate also intersects
|
|
807
|
+
the installation's explicit approvals with its verified Release; `external`
|
|
808
|
+
methods are refused with `approval_required`. MCP uses this same projection.
|
|
809
|
+
Capture again when tool policy, installation authority, a key's own grants or a
|
|
810
|
+
Release changes; the source represents one immutable catalog snapshot.
|
|
781
811
|
|
|
782
812
|
### Approve an Agent's App tools
|
|
783
813
|
|
|
@@ -980,6 +1010,20 @@ that read when host settings change. Account writes remain Hub-owned.
|
|
|
980
1010
|
|
|
981
1011
|
## Core concepts
|
|
982
1012
|
|
|
1013
|
+
### Authorization
|
|
1014
|
+
|
|
1015
|
+
`@fungi.computer/oyster/authorization` is Oyster's one authorization owner.
|
|
1016
|
+
Every route is registered through `routes()` with a typed requirement
|
|
1017
|
+
(`publicRoute`, `door`, `owner`, `member`, `app`, `apps`, `agent`, `agents`,
|
|
1018
|
+
`computer`), and a table's `serve` is the only router the host builds: an
|
|
1019
|
+
undeclared route is not served, and a handler receives only what its requirement
|
|
1020
|
+
admitted. The owner reaches everything. A named key reaches exactly its Ear Tag
|
|
1021
|
+
grants: `apps.read`/`apps.use` per installation (including that App's Git
|
|
1022
|
+
repositories), `agents.use` per Agent, and `computer.use` for the Computer, its
|
|
1023
|
+
workspace files and media. An Agent reaches its approved App tool policy;
|
|
1024
|
+
nothing anonymous reaches an App. `owner("direct")` refuses App-mediated
|
|
1025
|
+
requests, so consent, keys and secrets never ride an App.
|
|
1026
|
+
|
|
983
1027
|
### Owner state and selection
|
|
984
1028
|
|
|
985
1029
|
The host calls `apply` with the complete owner-issued state, then `fetch` or
|
|
@@ -1098,6 +1142,17 @@ Guest code receives only the `bindings()` its host builds, no ambient outbound
|
|
|
1098
1142
|
access, and requests carry only `Content-Type` and the host-admitted actor,
|
|
1099
1143
|
Computer and resource headers.
|
|
1100
1144
|
|
|
1145
|
+
One Instance shares its guest between up to eight verified foreground reads.
|
|
1146
|
+
Writes, raw backend requests, Gills, replay and supervisor-exclusive work run
|
|
1147
|
+
alone; a waiting exclusive caller holds back later reads. Every completed frame
|
|
1148
|
+
closes its own authority, and packets naming that finished frame refuse. Guest
|
|
1149
|
+
work runs only while at least one frame on that Instance remains admitted: the
|
|
1150
|
+
last frame stops the guest, then closes its authority, then releases admission.
|
|
1151
|
+
Arrivals wait while that stop is in flight. Once the opening frame's deadline
|
|
1152
|
+
passes, new reads wait for the cohort to drain rather than prolonging the guest.
|
|
1153
|
+
Revocation, expiry and a verified write preempting replay still stop it at once.
|
|
1154
|
+
Stopping the guest does not erase the facet's durable database.
|
|
1155
|
+
|
|
1101
1156
|
## API reference
|
|
1102
1157
|
|
|
1103
1158
|
The core is `@fungi.computer/oyster`; the Durable Object adapter is
|
|
@@ -1317,15 +1372,14 @@ returns typed no_backend before creating a new job or starting a machine.
|
|
|
1317
1372
|
Only an admitted Workflow-node APPS call can install its matching successful
|
|
1318
1373
|
build's recorded Release at the recorded target. Ordinary App-origin
|
|
1319
1374
|
installation remains refused. Existing grants may be reused, but new permissions
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
Workflow-created installation
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
configuration or separate consent ledger.
|
|
1375
|
+
return needs_consent without changing the running Release. Registering a
|
|
1376
|
+
repository as an App (**Make this an App**) is the owner's consent for its merge
|
|
1377
|
+
Workflow to create that App's installation when none exists yet. A
|
|
1378
|
+
Workflow-created installation has no grants. Later Workflows reuse exactly that
|
|
1379
|
+
approval and can never widen it; every grant still needs the owner's explicit
|
|
1380
|
+
consent. The build row records the outcome; status reads are read-only and no
|
|
1381
|
+
native alarm resumes installation. Workflow execution owns retries. This
|
|
1382
|
+
template has no review step, review configuration or separate consent ledger.
|
|
1329
1383
|
|
|
1330
1384
|
**Breaking installation contract:** Bonsai apps.install and APPS.select require
|
|
1331
1385
|
the successful build's jobId alongside installationId and release. The host
|
|
@@ -1437,8 +1491,9 @@ tenant from the request origin before sign-in and returns that tenant's
|
|
|
1437
1491
|
or `null` to refuse an unknown origin. Oyster uses the scope for registry,
|
|
1438
1492
|
workspace, keys, webhooks and repository names, and pins each Instance to its
|
|
1439
1493
|
selected tenant. Canonical member roles remain distinct from owner authority.
|
|
1440
|
-
|
|
1441
|
-
|
|
1494
|
+
Anonymous requests reach only sign-in, `robots.txt` and the digest-pinned shell
|
|
1495
|
+
and App UI bytes; unknown origins and policy refusals fail closed.
|
|
1496
|
+
`ownerOrKeyTenancy` is the single-tenant case.
|
|
1442
1497
|
|
|
1443
1498
|
## Machine link protocol
|
|
1444
1499
|
|
|
@@ -1480,14 +1535,13 @@ The page sends an owner-only `PUT` to the same path, carrying the captured
|
|
|
1480
1535
|
`storeGeneration`, `authorityVersion`, Release `artifactDigest`, and a bounded
|
|
1481
1536
|
`{kind:"change-grants",changes:[{capability,grant}]}` command. Each grant
|
|
1482
1537
|
contains `operations` and `resources`; `null` revokes that capability. Unnamed
|
|
1483
|
-
approvals retain their existing operation and resource ceilings. Agents, keys
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
pins in the existing commit transaction.
|
|
1538
|
+
approvals retain their existing operation and resource ceilings. Agents, keys
|
|
1539
|
+
and App-origin requests cannot approve. A replaced store, stale Release/version
|
|
1540
|
+
or concurrent approval returns `409`; the page refreshes the facts and rebases
|
|
1541
|
+
touched edits for review. The required `InstallationStore` exposes its durable
|
|
1542
|
+
`generation()` and `compareAndPut(record, expectedVersion, expectedGeneration)`.
|
|
1543
|
+
The latter returns a serializable commit/refusal result across Durable Object
|
|
1544
|
+
RPC and checks both pins in the existing commit transaction.
|
|
1491
1545
|
|
|
1492
1546
|
## Breaking App send contract
|
|
1493
1547
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
2
|
+
/** Revisioned, bounded-stale directory view; never the Agent's flag authority. */
|
|
3
|
+
export declare function agentArchiveProjection(sql: WatchdogSqliteOwner["sql"]): {
|
|
4
|
+
/** Only an advanced fact changes the view; replay can still be acked. */
|
|
5
|
+
record(agentId: string, archived: boolean, revision: number): "advanced" | "replayed" | "stale";
|
|
6
|
+
archived(agentId: string): boolean;
|
|
7
|
+
archivedIds(): ReadonlySet<string>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const archivedRow = z.strictObject({
|
|
3
|
+
archived: z.union([z.literal(0), z.literal(1)]),
|
|
4
|
+
revision: z.number().int().nonnegative(),
|
|
5
|
+
});
|
|
6
|
+
const agentRow = z.strictObject({ agent_id: z.string() });
|
|
7
|
+
const changesRow = z.strictObject({ count: z.number() });
|
|
8
|
+
/** Revisioned, bounded-stale directory view; never the Agent's flag authority. */
|
|
9
|
+
export function agentArchiveProjection(sql) {
|
|
10
|
+
sql.exec(`CREATE TABLE IF NOT EXISTS oyster_archive_projection (
|
|
11
|
+
agent_id TEXT PRIMARY KEY,
|
|
12
|
+
archived INTEGER NOT NULL CHECK(archived IN (0, 1)),
|
|
13
|
+
revision INTEGER NOT NULL
|
|
14
|
+
) STRICT`);
|
|
15
|
+
return {
|
|
16
|
+
/** Only an advanced fact changes the view; replay can still be acked. */
|
|
17
|
+
record(agentId, archived, revision) {
|
|
18
|
+
sql.exec(`INSERT INTO oyster_archive_projection(agent_id,archived,revision)
|
|
19
|
+
VALUES(?,?,?)
|
|
20
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
21
|
+
archived=excluded.archived, revision=excluded.revision
|
|
22
|
+
WHERE oyster_archive_projection.revision < excluded.revision`, agentId, archived ? 1 : 0, revision);
|
|
23
|
+
if (changesRow.parse(sql.exec("SELECT changes() AS count").toArray()[0])
|
|
24
|
+
.count > 0)
|
|
25
|
+
return "advanced";
|
|
26
|
+
const row = sql
|
|
27
|
+
.exec("SELECT archived, revision FROM oyster_archive_projection WHERE agent_id=?", agentId)
|
|
28
|
+
.toArray()[0];
|
|
29
|
+
const current = row === undefined ? null : archivedRow.parse(row);
|
|
30
|
+
return current !== null &&
|
|
31
|
+
current.revision === revision &&
|
|
32
|
+
current.archived === (archived ? 1 : 0)
|
|
33
|
+
? "replayed"
|
|
34
|
+
: "stale";
|
|
35
|
+
},
|
|
36
|
+
archived(agentId) {
|
|
37
|
+
const row = sql
|
|
38
|
+
.exec("SELECT archived, revision FROM oyster_archive_projection WHERE agent_id=?", agentId)
|
|
39
|
+
.toArray()[0];
|
|
40
|
+
return row === undefined ? false : archivedRow.parse(row).archived === 1;
|
|
41
|
+
},
|
|
42
|
+
archivedIds() {
|
|
43
|
+
return new Set(sql
|
|
44
|
+
.exec("SELECT agent_id FROM oyster_archive_projection WHERE archived=1")
|
|
45
|
+
.toArray()
|
|
46
|
+
.map((row) => agentRow.parse(row).agent_id));
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export { agentSchedulePageSchema, agentScheduleControlReceiptSchema, } from "@fungi.computer/shiitake-extensions/schedules";
|
|
3
|
+
export type { AgentSchedulePage, AgentScheduleTarget, AgentScheduleControlReceipt, } from "@fungi.computer/shiitake-extensions/schedules";
|
|
4
|
+
/** The App selects one Agent; its host must admit that target before opening it. */
|
|
5
|
+
export declare const appAgentScheduleListInputSchema: z.ZodObject<{
|
|
6
|
+
after: z.ZodOptional<z.ZodObject<{
|
|
7
|
+
agentId: z.ZodString;
|
|
8
|
+
kind: z.ZodUnion<readonly [z.ZodLiteral<"almanac">, z.ZodLiteral<"loop">]>;
|
|
9
|
+
after: z.ZodOptional<z.ZodString>;
|
|
10
|
+
}, z.core.$strict>>;
|
|
11
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
agentId: z.ZodString;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
/** A single bounded native read, not an all-Agent inventory grant. */
|
|
15
|
+
export type AppAgentScheduleListInput = z.input<typeof appAgentScheduleListInputSchema>;
|
|
16
|
+
/** Both hosts decode the ordinary AGENTS packet with the native owner's grammar. */
|
|
17
|
+
export declare const agentScheduleBindingArgs: {
|
|
18
|
+
/** One bounded content-bearing read, admitted independently of manage. */
|
|
19
|
+
schedules: z.ZodTuple<[z.ZodObject<{
|
|
20
|
+
after: z.ZodOptional<z.ZodObject<{
|
|
21
|
+
agentId: z.ZodString;
|
|
22
|
+
kind: z.ZodUnion<readonly [z.ZodLiteral<"almanac">, z.ZodLiteral<"loop">]>;
|
|
23
|
+
after: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, z.core.$strict>>;
|
|
25
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
agentId: z.ZodString;
|
|
27
|
+
}, z.core.$strict>], null>;
|
|
28
|
+
/** Pause the exact listed schedule, retaining future resume and accepted work. */
|
|
29
|
+
pauseSchedule: z.ZodTuple<[z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
30
|
+
kind: z.ZodLiteral<"almanac">;
|
|
31
|
+
agentId: z.ZodString;
|
|
32
|
+
scheduleId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
33
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
34
|
+
kind: z.ZodLiteral<"loop">;
|
|
35
|
+
agentId: z.ZodString;
|
|
36
|
+
sessionId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
37
|
+
loopId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
38
|
+
}, z.core.$strict>], "kind">], null>;
|
|
39
|
+
/** Cancel future occurrences of the exact identity without erasing history. */
|
|
40
|
+
deleteSchedule: z.ZodTuple<[z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
41
|
+
kind: z.ZodLiteral<"almanac">;
|
|
42
|
+
agentId: z.ZodString;
|
|
43
|
+
scheduleId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
44
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
45
|
+
kind: z.ZodLiteral<"loop">;
|
|
46
|
+
agentId: z.ZodString;
|
|
47
|
+
sessionId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
48
|
+
loopId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
49
|
+
}, z.core.$strict>], "kind">], null>;
|
|
50
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { agentScheduleListInputSchema, agentScheduleTargetSchema, } from "@fungi.computer/shiitake-extensions/schedules";
|
|
3
|
+
export { agentSchedulePageSchema, agentScheduleControlReceiptSchema, } from "@fungi.computer/shiitake-extensions/schedules";
|
|
4
|
+
/** The App selects one Agent; its host must admit that target before opening it. */
|
|
5
|
+
export const appAgentScheduleListInputSchema = agentScheduleListInputSchema.extend({
|
|
6
|
+
/** The native directory identity whose owner the host must admit. */
|
|
7
|
+
agentId: z.string().min(1),
|
|
8
|
+
});
|
|
9
|
+
/** Both hosts decode the ordinary AGENTS packet with the native owner's grammar. */
|
|
10
|
+
export const agentScheduleBindingArgs = {
|
|
11
|
+
/** One bounded content-bearing read, admitted independently of manage. */
|
|
12
|
+
schedules: z.tuple([appAgentScheduleListInputSchema]),
|
|
13
|
+
/** Pause the exact listed schedule, retaining future resume and accepted work. */
|
|
14
|
+
pauseSchedule: z.tuple([agentScheduleTargetSchema]),
|
|
15
|
+
/** Cancel future occurrences of the exact identity without erasing history. */
|
|
16
|
+
deleteSchedule: z.tuple([agentScheduleTargetSchema]),
|
|
17
|
+
};
|
package/dist/agents.d.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* Shiitake's HTTP routes (`/sessions…`, `/acp`) and Knapsack's Session browser
|
|
7
7
|
* transport (`/browser/…`), served by the adapter behind the same App grants.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import { type
|
|
9
|
+
import type { TenantPrincipal } from "./tenant.js";
|
|
10
|
+
import { type Routes } from "./authorization.js";
|
|
11
11
|
import type { AppToolPolicy } from "./apps/tools.js";
|
|
12
12
|
import * as Schema from "effect/Schema";
|
|
13
|
-
import { type OysterCapability, type OysterEnv
|
|
13
|
+
import { type OysterCapability, type OysterEnv } from "./host.js";
|
|
14
14
|
import type { HostActor } from "@fungi.computer/puffball/app";
|
|
15
15
|
import type { Avatar } from "@fungi.computer/sprite/avatar";
|
|
16
16
|
import type { AgentModelChoice, AgentModelRefusal } from "./standalone/agent-model-choice.js";
|
|
@@ -73,21 +73,42 @@ export type AgentToolPolicyStatus = Readonly<{
|
|
|
73
73
|
status: "pending" | "approved";
|
|
74
74
|
revision: string;
|
|
75
75
|
}>;
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Where an Oyster's agents live and run. Archive is non-destructive: `list()`
|
|
78
|
+
* hides archived agents, and `list({ archived: true })` shows only archives.
|
|
79
|
+
* Archived agents keep existing reads, replays and settlements, but refuse new
|
|
80
|
+
* Sessions, prompts and steers with `contract/archived`. Archive refuses
|
|
81
|
+
* non-idle Session work or unsettled delegations; it pauses Almanac and Loop
|
|
82
|
+
* schedules and restores only archive-paused schedules on unarchive.
|
|
83
|
+
*/
|
|
77
84
|
export type OysterAgents = Readonly<{
|
|
78
|
-
/**
|
|
79
|
-
list(
|
|
85
|
+
/** Active agents by default; archived agents only by explicit selection. */
|
|
86
|
+
list(options?: Readonly<{
|
|
87
|
+
archived?: boolean;
|
|
88
|
+
}>): Promise<readonly OysterAgent[]>;
|
|
89
|
+
/** Owner-only HTTP transition; creator Agents use their own control tool. */
|
|
90
|
+
setArchived(agentId: string, archived: boolean, principal: TenantPrincipal): Promise<Readonly<{
|
|
91
|
+
archived: boolean;
|
|
92
|
+
revision: number;
|
|
93
|
+
}> | Readonly<{
|
|
94
|
+
error: "not_found" | "not_granted" | "self" | "not_creator" | "running" | "pending" | "unreadable" | "archived";
|
|
95
|
+
/** Safe refusal text; busy work names its command. */
|
|
96
|
+
message?: string;
|
|
97
|
+
/** A repair failure can follow an already-committed owner flag. */
|
|
98
|
+
archived?: boolean;
|
|
99
|
+
revision?: number;
|
|
100
|
+
}>>;
|
|
80
101
|
/**
|
|
81
102
|
* Create an agent; retrying with the same `requestId` returns the same one.
|
|
82
103
|
* With no `avatar` it wears its seeded default until the owner picks one.
|
|
83
104
|
*/
|
|
84
|
-
create(name: string, requestId: string, principal:
|
|
105
|
+
create(name: string, requestId: string, principal: TenantPrincipal, actor: AgentPolicyActor, avatar?: Avatar): Promise<OysterAgent>;
|
|
85
106
|
/** Owner-only rename; refuses a name already held by another Agent. */
|
|
86
|
-
rename(agentId: string, name: string, principal:
|
|
107
|
+
rename(agentId: string, name: string, principal: TenantPrincipal): Promise<OysterAgent | Readonly<{
|
|
87
108
|
error: "not_found" | "name_taken" | "not_granted";
|
|
88
109
|
}>>;
|
|
89
110
|
/** Owner-only avatar change for an existing Agent. */
|
|
90
|
-
setAvatar(agentId: string, avatar: Avatar, principal:
|
|
111
|
+
setAvatar(agentId: string, avatar: Avatar, principal: TenantPrincipal): Promise<OysterAgent | Readonly<{
|
|
91
112
|
error: "not_found" | "not_granted";
|
|
92
113
|
}>>;
|
|
93
114
|
/** Owner-only, secret-free Agent default; an unknown Agent cannot be configured. */
|
|
@@ -95,7 +116,7 @@ export type OysterAgents = Readonly<{
|
|
|
95
116
|
/** Parse the owner's JSON through the deployable Agent's model preference schema. */
|
|
96
117
|
decodeModelChoice(json: string): AgentModelChoice | null;
|
|
97
118
|
/** Persist a supported model and source reference for this Agent, or return an explicit refusal. */
|
|
98
|
-
setModelChoice(agentId: string, choice: AgentModelChoice, principal:
|
|
119
|
+
setModelChoice(agentId: string, choice: AgentModelChoice, principal: TenantPrincipal): Promise<AgentModelChoice | Readonly<{
|
|
99
120
|
error: "not_found" | "not_granted" | AgentModelRefusal;
|
|
100
121
|
}>>;
|
|
101
122
|
/** This Agent's App-tool policy status, or `null` for no such Agent. */
|
|
@@ -107,7 +128,7 @@ export type OysterAgents = Readonly<{
|
|
|
107
128
|
/** Read the current revision for direct owner approval. */
|
|
108
129
|
policyRevision(agentId: string): Promise<string | null>;
|
|
109
130
|
/** Replace an Agent's tool approval under its authenticated attaching human. */
|
|
110
|
-
approveAppToolPolicy(agentId: string, principal:
|
|
131
|
+
approveAppToolPolicy(agentId: string, principal: TenantPrincipal, level: AppToolPolicy["level"], expectedRevision: string | null, actor: AgentPolicyActor): Promise<AppToolPolicy | PendingAgentPolicy | Readonly<{
|
|
111
132
|
error: "not_found" | "revision_conflict" | "not_granted";
|
|
112
133
|
}>>;
|
|
113
134
|
/** An Agent's connected MCP servers, or `null` when there is no such Agent. */
|
|
@@ -126,11 +147,11 @@ export type OysterAgents = Readonly<{
|
|
|
126
147
|
visit(agentId: string, sessionId: string): Promise<boolean>;
|
|
127
148
|
}>;
|
|
128
149
|
/** Agent adapters admitted against each App's declared capabilities. */
|
|
129
|
-
export declare function agentCapabilities(agents: OysterAgents
|
|
150
|
+
export declare function agentCapabilities(agents: OysterAgents): readonly OysterCapability[];
|
|
130
151
|
/** Host-owned direct approval under the admitted owner; never an App capability. */
|
|
131
|
-
export declare function agentApprovalRoutes(agents: OysterAgents):
|
|
152
|
+
export declare function agentApprovalRoutes(agents: OysterAgents): Routes<OysterEnv>;
|
|
132
153
|
/**
|
|
133
154
|
* Host-owned Agent MCP server custody for the owner directly; never an App
|
|
134
155
|
* capability. Bearer tokens go in and never come back out.
|
|
135
156
|
*/
|
|
136
|
-
export declare function agentMcpServerRoutes(agents: OysterAgents):
|
|
157
|
+
export declare function agentMcpServerRoutes(agents: OysterAgents): Routes<OysterEnv>;
|