@fungi.computer/agar 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +766 -2
- package/dist/agent-message-meta.d.ts +74 -0
- package/dist/agent-message-meta.js +63 -0
- package/dist/agents.d.ts +170 -0
- package/dist/agents.js +218 -0
- package/dist/app-build-contract.d.ts +43 -0
- package/dist/app-build-contract.js +24 -0
- package/dist/apps.d.ts +66 -0
- package/dist/apps.js +47 -0
- package/dist/backend-declaration.d.ts +6 -0
- package/dist/backend-declaration.js +65 -0
- package/dist/backend-definition.d.ts +88 -0
- package/dist/backend-definition.js +158 -0
- package/dist/backend-refusal.d.ts +10 -0
- package/dist/backend-refusal.js +10 -0
- package/dist/backend.d.ts +30 -0
- package/dist/backend.js +28 -0
- package/dist/capabilities.d.ts +30 -0
- package/dist/capabilities.js +50 -0
- package/dist/channels.d.ts +142 -0
- package/dist/channels.js +97 -0
- package/dist/commands.d.ts +33 -0
- package/dist/commands.js +46 -0
- package/dist/computer-contracts.d.ts +73 -0
- package/dist/computer-contracts.js +57 -0
- package/dist/computer.d.ts +294 -0
- package/dist/computer.js +319 -0
- package/dist/connect.d.ts +11 -0
- package/dist/connect.js +211 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.js +106 -0
- package/dist/failure.d.ts +2 -0
- package/dist/failure.js +9 -0
- package/dist/files.d.ts +25 -0
- package/dist/files.js +84 -0
- package/dist/game-servers.d.ts +530 -0
- package/dist/game-servers.js +244 -0
- package/dist/gills.d.ts +43 -0
- package/dist/gills.js +174 -0
- package/dist/git-commit.d.ts +143 -0
- package/dist/git-commit.js +251 -0
- package/dist/git-rpc.d.ts +310 -0
- package/dist/git-rpc.js +166 -0
- package/dist/git-steps.d.ts +579 -0
- package/dist/git-steps.js +804 -0
- package/dist/git-wire.d.ts +33 -0
- package/dist/git-wire.js +240 -0
- package/dist/git.d.ts +322 -0
- package/dist/git.js +98 -0
- package/dist/host.d.ts +97 -0
- package/dist/host.js +732 -0
- package/dist/inbox.d.ts +99 -0
- package/dist/inbox.js +184 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +15 -0
- package/dist/inference.d.ts +17 -0
- package/dist/inference.js +115 -0
- package/dist/internal/app-error.d.ts +8 -0
- package/dist/internal/app-error.js +36 -0
- package/dist/internal/app-session.d.ts +8 -0
- package/dist/internal/app-session.js +551 -0
- package/dist/internal/back-inputs.d.ts +22 -0
- package/dist/internal/back-inputs.js +93 -0
- package/dist/internal/browser-bridge.d.ts +30 -0
- package/dist/internal/browser-bridge.js +203 -0
- package/dist/internal/capabilities.d.ts +13 -0
- package/dist/internal/capabilities.js +138 -0
- package/dist/internal/commands.d.ts +9 -0
- package/dist/internal/commands.js +257 -0
- package/dist/internal/disconnect-host.d.ts +7 -0
- package/dist/internal/disconnect-host.js +19 -0
- package/dist/internal/git-edit-tree.d.ts +6 -0
- package/dist/internal/git-edit-tree.js +46 -0
- package/dist/internal/memory-fs.d.ts +39 -0
- package/dist/internal/memory-fs.js +154 -0
- package/dist/internal/protocol.d.ts +116 -0
- package/dist/internal/protocol.js +6 -0
- package/dist/internal/query-feed.d.ts +23 -0
- package/dist/internal/query-feed.js +111 -0
- package/dist/internal/result.d.ts +22 -0
- package/dist/internal/result.js +61 -0
- package/dist/internal/rpc-contract.d.ts +24 -0
- package/dist/internal/rpc-contract.js +78 -0
- package/dist/internal/schemas.d.ts +307 -0
- package/dist/internal/schemas.js +524 -0
- package/dist/internal/store.d.ts +8 -0
- package/dist/internal/store.js +30 -0
- package/dist/internal/terminal-gate.d.ts +17 -0
- package/dist/internal/terminal-gate.js +94 -0
- package/dist/lifecycle.d.ts +44 -0
- package/dist/lifecycle.js +1 -0
- package/dist/marketplace.d.ts +159 -0
- package/dist/marketplace.js +102 -0
- package/dist/media-json.d.ts +181 -0
- package/dist/media-json.js +204 -0
- package/dist/media-library.d.ts +11 -0
- package/dist/media-library.js +15 -0
- package/dist/media-request-id.d.ts +7 -0
- package/dist/media-request-id.js +16 -0
- package/dist/media.d.ts +354 -0
- package/dist/media.js +654 -0
- package/dist/method-refusal.d.ts +64 -0
- package/dist/method-refusal.js +48 -0
- package/dist/pending.d.ts +10 -0
- package/dist/pending.js +10 -0
- package/dist/query.d.ts +76 -0
- package/dist/query.js +195 -0
- package/dist/react-mount.d.ts +23 -0
- package/dist/react-mount.js +108 -0
- package/dist/react.d.ts +41 -0
- package/dist/react.js +111 -0
- package/dist/read-copy.d.ts +103 -0
- package/dist/read-copy.js +195 -0
- package/dist/roles/files.d.ts +295 -0
- package/dist/roles/files.js +184 -0
- package/dist/root.d.ts +21 -0
- package/dist/root.js +1 -0
- package/dist/rpc.d.ts +1 -0
- package/dist/rpc.js +1 -0
- package/dist/status.d.ts +17 -0
- package/dist/status.js +50 -0
- package/dist/storage-wire.d.ts +167 -0
- package/dist/storage-wire.js +115 -0
- package/dist/storage.d.ts +72 -0
- package/dist/storage.js +143 -0
- package/dist/store.d.ts +11 -0
- package/dist/store.js +1 -0
- package/dist/test-kit/git.d.ts +31 -0
- package/dist/test-kit/git.js +174 -0
- package/dist/ui.d.ts +169 -0
- package/dist/ui.js +10 -0
- package/dist/window.d.ts +41 -0
- package/dist/window.js +15 -0
- package/dist/world.d.ts +16 -0
- package/dist/world.js +17 -0
- package/docs/api/README.md +28 -0
- package/docs/api/app-build-contract.md +113 -0
- package/docs/api/backend-declaration.md +62 -0
- package/docs/api/backend-definition.md +215 -0
- package/docs/api/backend.md +269 -0
- package/docs/api/errors.md +169 -0
- package/docs/api/files.md +91 -0
- package/docs/api/gills.md +164 -0
- package/docs/api/git-commit.md +279 -0
- package/docs/api/git-rpc.md +243 -0
- package/docs/api/git-steps.md +1010 -0
- package/docs/api/git-wire.md +197 -0
- package/docs/api/git.md +579 -0
- package/docs/api/index.md +2880 -0
- package/docs/api/media-json.md +507 -0
- package/docs/api/media-request-id.md +32 -0
- package/docs/api/modules.md +32 -0
- package/docs/api/query.md +253 -0
- package/docs/api/react.md +216 -0
- package/docs/api/read-copy.md +204 -0
- package/docs/api/rpc.md +128 -0
- package/docs/api/storage.md +620 -0
- package/package.json +197 -3
package/README.md
CHANGED
|
@@ -1,3 +1,767 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Agar (`@fungi.computer/agar`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Agar defines typed App backends and host capabilities. Most people want
|
|
4
|
+
[Oyster](https://docs.fungi.computer/reference/packages/#oyster) to run Apps or
|
|
5
|
+
[Shiitake](https://docs.fungi.computer/reference/packages/#shiitake) to run
|
|
6
|
+
Agents.
|
|
7
|
+
|
|
8
|
+
Agar is the App contract for fungi.computer. It defines an App's typed backend
|
|
9
|
+
methods, connects its frontend to a host through typed capabilities, and gives
|
|
10
|
+
App builders selected context, host-rendered permission requests and explicit
|
|
11
|
+
cleanup. The host and resource owners authorize the work; the same App runs on
|
|
12
|
+
Fungi's hosted Teams or on any host that composes
|
|
13
|
+
[Oyster](https://oyster.shiit.app).
|
|
14
|
+
|
|
15
|
+
## Install or set up
|
|
16
|
+
|
|
17
|
+
Use Node 24 or newer.
|
|
18
|
+
|
|
19
|
+
Agar is a public package qualified with packed npm consumers. Qualification does
|
|
20
|
+
not publish an npm release; check the registry before choosing a released
|
|
21
|
+
version. To build the source workspace and check its tarball:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pnpm install --frozen-lockfile
|
|
25
|
+
pnpm turbo:run run build --filter=@fungi.computer/agar
|
|
26
|
+
pnpm --filter @fungi.computer/agar test
|
|
27
|
+
pnpm --filter @fungi.computer/agar run pack
|
|
28
|
+
pnpm --filter @fungi.computer/agar run stranger
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The React entrypoint takes `react`, `react-dom` and `@tanstack/react-query` as
|
|
32
|
+
optional peers; the semantic SDK and backend entrypoints need none of them.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
Define an App backend once. Its Zod schemas give the handler its TypeScript
|
|
37
|
+
contract and produce the JSON Schema API a host shows to Agents as tools. This
|
|
38
|
+
example runs in plain Node; no host is needed to define or call a method.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import * as z from "zod";
|
|
42
|
+
import {
|
|
43
|
+
defineAppBackend,
|
|
44
|
+
defineAppMethod,
|
|
45
|
+
} from "@fungi.computer/agar/backend-definition";
|
|
46
|
+
import { buildAppApi } from "@fungi.computer/agar/backend-declaration";
|
|
47
|
+
|
|
48
|
+
const notes = new Map<string, string>();
|
|
49
|
+
const appBackend = defineAppBackend({
|
|
50
|
+
methods: {
|
|
51
|
+
saveNote: defineAppMethod({
|
|
52
|
+
description: "Save a note under a title",
|
|
53
|
+
effect: "write",
|
|
54
|
+
requires: [],
|
|
55
|
+
input: z.object({ title: z.string().min(1), text: z.string() }),
|
|
56
|
+
output: z.object({ saved: z.boolean() }),
|
|
57
|
+
handler: ({ title, text }) => {
|
|
58
|
+
notes.set(title, text);
|
|
59
|
+
return { saved: true };
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const api = buildAppApi(appBackend);
|
|
66
|
+
console.log(Object.keys(api.methods)); // ["saveNote"]
|
|
67
|
+
|
|
68
|
+
const result = await appBackend.call(
|
|
69
|
+
"saveNote",
|
|
70
|
+
{ title: "hello", text: "from Agar" },
|
|
71
|
+
undefined,
|
|
72
|
+
{ computerId: null, actor: { kind: "member", memberId: "member_example" } },
|
|
73
|
+
);
|
|
74
|
+
console.log(result); // { saved: true }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
An App imports `connectApp` from `@fungi.computer/agar` and operation helpers
|
|
78
|
+
from the declared subpaths. A plain top-level page has no Fungi parent to
|
|
79
|
+
connect to. Use a host-mounted App or the existing host fixtures for
|
|
80
|
+
development.
|
|
81
|
+
|
|
82
|
+
## Git pushes
|
|
83
|
+
|
|
84
|
+
An App requests `git.repository` in `fungi.app.capabilities` and declares
|
|
85
|
+
`fungi.app.receives: ["git.push"]`. The request is not approval: the host checks
|
|
86
|
+
the installation's live grant and repository ownership before delivery.
|
|
87
|
+
Repositories are local to that installation; another installation's repository
|
|
88
|
+
and the Team-fork source repository are outside this scope.
|
|
89
|
+
|
|
90
|
+
`@fungi.computer/agar/git` owns `GitBinding`, `GitWriter`, `GitPushQuestion`,
|
|
91
|
+
`GitPushVerdict`, `AppGitPushFact` and `AppGitLifecycle`. An App can implement
|
|
92
|
+
`authorizeGitPush(question): Promise<GitPushVerdict>` and
|
|
93
|
+
`onGitPush(fact): Promise<void>` as lifecycle handlers. Neither is an ordinary
|
|
94
|
+
callable App method. The question covers every ref update in one push; any
|
|
95
|
+
refusal refuses the whole push. An absent authorization handler refuses external
|
|
96
|
+
pushes.
|
|
97
|
+
|
|
98
|
+
`GitWriter` uses Puffball's canonical `HostActor` and `WorkflowAttestation`.
|
|
99
|
+
Actor writers carry a member, Agent or Team-key identity, an optional
|
|
100
|
+
host-attested Team role and optional Workflow attribution with its exact branch
|
|
101
|
+
list. Installation writers carry `installationId` and
|
|
102
|
+
`onBehalfOf: HostActor | null`. These schemas decode data; the host still owns
|
|
103
|
+
identity, grants and admission.
|
|
104
|
+
|
|
105
|
+
`dispatchAppGitPush` decodes a fact before invoking its handler and preserves
|
|
106
|
+
rejection. Facts contain the local repository, canonical writer, accepted ref
|
|
107
|
+
updates and stable `eventId`; they contain no provider URL or credential.
|
|
108
|
+
Optional `unexplained` entries identify refs whose previous head differs from
|
|
109
|
+
the host journal. Delivery is at least once: the handler records `eventId`
|
|
110
|
+
atomically with its own effects. The parser does not deduplicate App effects.
|
|
111
|
+
|
|
112
|
+
`@fungi.computer/agar/git-rpc` owns the native Git argument tuples, result
|
|
113
|
+
codecs and frame method descriptors used by hosts. `nativeGitFrameMethods`
|
|
114
|
+
checks the approved operations, including both `exchange` and `write` for
|
|
115
|
+
receive-pack. The codecs preserve positional arguments and binary results; they
|
|
116
|
+
perform no I/O and grant no authority. `invokeNativeGit` dispatches those
|
|
117
|
+
decoded tuples to a host-admitted `GitBinding`, preserving native results.
|
|
118
|
+
|
|
119
|
+
`@fungi.computer/agar/git-wire` parses receive-pack commands and report-status,
|
|
120
|
+
and encodes whole-push refusals. It performs no I/O. Unsupported pre-flush
|
|
121
|
+
lines, including signed push certificates, refuse instead of disappearing from
|
|
122
|
+
the host's authorization question.
|
|
123
|
+
|
|
124
|
+
`@fungi.computer/agar/git-commit` writes through that binding without a
|
|
125
|
+
credential:
|
|
126
|
+
`commitFiles(git, { repository, branch, expectedHead, startFrom?, changes, message, author })`
|
|
127
|
+
commits file writes and deletions to a branch, and
|
|
128
|
+
`moveRef(git, { repository, ref, expected, commit, source })` points a branch or
|
|
129
|
+
tag at an existing commit.
|
|
130
|
+
`moveRefs(git, { repository, source, commit, updates: [{ ref, expected }, ...] })`
|
|
131
|
+
fetches the source once and pushes the ref moves in order from the same memory
|
|
132
|
+
repository. Each push checks its own expected ref head. It returns
|
|
133
|
+
`{ kind: "stale", ref, head }` at the first conflict; earlier accepted moves
|
|
134
|
+
remain accepted. The ref moves are separate pushes, with no rollback.
|
|
135
|
+
`mergeBranch(git, { target, source, branch, sourceBranch, expectedSource, message, author })`
|
|
136
|
+
merges one repository's branch (such as a fork's) into another's with a merge
|
|
137
|
+
commit, reporting `conflict` with paths. The required `expectedSource` is the
|
|
138
|
+
reviewed commit: a different fetched source head returns
|
|
139
|
+
`{ kind: "source-moved", head }` without writing the target. The merge commit's
|
|
140
|
+
second parent is that checked source snapshot. Optional `expectedTarget` and
|
|
141
|
+
`behind: 0` from the same copy let the helper fetch only the source when its
|
|
142
|
+
complete history proves the copied target is an ancestor. The target push still
|
|
143
|
+
uses compare-and-swap; drop both hints on a stale retry. Without both hints, the
|
|
144
|
+
two remotes fetch in separate bounded steps. An inaccurate hint falls back to
|
|
145
|
+
fetching the target. No-op merges read the current target before reporting
|
|
146
|
+
`up-to-date`. These helpers run isomorphic-git in memory over `exchange` and are
|
|
147
|
+
compare-and-swap. A single-ref write that is not at the expected head returns
|
|
148
|
+
`{ kind: "stale", head }` without changing that ref. An unchanged tree returns
|
|
149
|
+
`unchanged` without a commit. A push that loses a race after its advertisement
|
|
150
|
+
is also `stale`, with the fresh head. Commits edit trees directly, so every
|
|
151
|
+
entry a change does not name keeps its object and mode (executables, symlinks);
|
|
152
|
+
an edited file keeps its mode unless the change sets `mode`.
|
|
153
|
+
|
|
154
|
+
### Resumable writes
|
|
155
|
+
|
|
156
|
+
`@fungi.computer/agar/git-steps` exposes the same commit and merge engine to
|
|
157
|
+
background jobs. `await begin(operation, material)` fixes the timestamp and
|
|
158
|
+
stores file bytes before returning a strict JSON checkpoint. Each
|
|
159
|
+
`await runStep(checkpoint, { git, material }, signal)` performs at most one Git
|
|
160
|
+
exchange and returns `{ next }`, `{ done }`, or `{ yield: true }`. An aborted
|
|
161
|
+
signal yields before transport. A checkpoint contains Git data, never
|
|
162
|
+
credentials, roles or grants.
|
|
163
|
+
|
|
164
|
+
Use `sqliteGitStepMaterial(sql)` with the existing facet SQLite owner. Material
|
|
165
|
+
rows are at most 1 MiB; input and new push packs are bounded to 8 MiB, and merge
|
|
166
|
+
material to 16 MiB. The 8 MiB input bound includes JSON/base64 overhead (about 6
|
|
167
|
+
MiB of raw files); these bounds also apply to the foreground drivers, which
|
|
168
|
+
throw `RangeError` for `too_large`. Oversize work returns `too_large` before a
|
|
169
|
+
push. Exchange responses are buffered before checking this bound; it limits
|
|
170
|
+
retained material, not peak transport memory. Reopening SQLite between steps
|
|
171
|
+
restores the objects in a fresh memory repository. The foreground `commitFiles`
|
|
172
|
+
and `mergeBranch` drivers loop these same steps with volatile material.
|
|
173
|
+
|
|
174
|
+
The App must freshly admit the member and check its domain policy each frame,
|
|
175
|
+
then persist the next checkpoint and idempotent successor in its existing
|
|
176
|
+
Watchdog owner transaction. An intermediate checkpoint is not Synced. After
|
|
177
|
+
durably recording a terminal outcome, call `dropMaterial(checkpoint, material)`;
|
|
178
|
+
dropping it before settlement would break uncertain-push recovery. The owner
|
|
179
|
+
must durably retain the operation identity before `begin` and replay `begin`
|
|
180
|
+
with the same request key if it dies before checkpointing. Cleanup can remove
|
|
181
|
+
build/fetch orphans only when that owner reaches terminal settlement.
|
|
182
|
+
|
|
183
|
+
`GitStepMaterialError` identifies permanent missing, conflicting, or
|
|
184
|
+
digest-mismatched material. Catch it inside the background executor and settle
|
|
185
|
+
the existing failed job/domain projection; retrying it cannot restore the lost
|
|
186
|
+
material. Do not treat ordinary transport or database errors as this permanent
|
|
187
|
+
material classification.
|
|
188
|
+
|
|
189
|
+
A normal existing-branch commit uses a ref check, one fetch POST, one push POST
|
|
190
|
+
and final ref confirmation. A same-repository merge uses five steps; a
|
|
191
|
+
cross-repository merge can use seven. Explicit source and target checks preserve
|
|
192
|
+
stale-head and source-moved outcomes. Every push, including an accepted
|
|
193
|
+
report-status, gets a separate ref confirmation before terminal settlement. An
|
|
194
|
+
uncertain push retries the same built pack; merge retries recheck the reviewed
|
|
195
|
+
source. A bounded permanent push refusal throws instead of reporting an unmoved
|
|
196
|
+
head as stale. Optional commit `tags` add a receive-pack capability check before
|
|
197
|
+
moving main and tags in one atomic push (five steps for an existing branch). A
|
|
198
|
+
server without `atomic` is refused before the push. An unchanged tree still
|
|
199
|
+
publishes missing tags using the existing commit, without another commit. If all
|
|
200
|
+
requested tags already name that selected commit, final confirmation returns
|
|
201
|
+
`committed` idempotently even when a tag's expected value was null. This means
|
|
202
|
+
the desired refs are confirmed, not that a new commit was created; consumers use
|
|
203
|
+
the returned commit ID. A tag naming a different commit remains `stale` and is
|
|
204
|
+
not overwritten.
|
|
205
|
+
|
|
206
|
+
Foreground cross-repository fetches now run serially through the same bounded
|
|
207
|
+
step engine rather than overlapping. This preserves the data/parent/CAS laws but
|
|
208
|
+
adds source checks and final confirmation round trips. Superseded branch-only
|
|
209
|
+
writes need a full-history ancestry fetch, currently limited to 16 MiB; an
|
|
210
|
+
oversize confirmation throws rather than claiming success.
|
|
211
|
+
|
|
212
|
+
The optional third `commitFiles` argument, `beforeWrite: () => Promise<void>`,
|
|
213
|
+
runs a caller's validation alongside the fetch. Both settle before the helper
|
|
214
|
+
returns or writes; rejecting validation prevents every receive-pack request.
|
|
215
|
+
|
|
216
|
+
The GIT binding's approved operations bound every call. Receive-pack requires
|
|
217
|
+
both `exchange` and `write`. For an Agent caller, the host also checks the
|
|
218
|
+
Agent's live Team Git grant. Hosts keep all provider credentials; the binding
|
|
219
|
+
returns only credential-free repository and Git object data. Smart-HTTP exchange
|
|
220
|
+
uses RPC-safe bytes: a `Uint8Array | null` request body and a
|
|
221
|
+
`{ status, headers, body: Uint8Array }` response. Stream and `Response`
|
|
222
|
+
conversion belongs inside the host adapter. Oyster and Hub consume this one
|
|
223
|
+
contract.
|
|
224
|
+
|
|
225
|
+
Apps request `EVENTS` with the `emit` operation to emit their declared events.
|
|
226
|
+
It requires no additional host capability; declaration approval and the event
|
|
227
|
+
catalog bound emission. Event schema and routing are owned by the event host.
|
|
228
|
+
|
|
229
|
+
## Dynamic tool discovery
|
|
230
|
+
|
|
231
|
+
`defineAppBackend` accepts an optional
|
|
232
|
+
`toolFamily: { name: "flows", list: "listWorkflowTools", call: "runWorkflow" }`.
|
|
233
|
+
`buildAppApi` copies this descriptor into the signed API without executing the
|
|
234
|
+
listing or call handlers. The host reads inventory through the declared method
|
|
235
|
+
and keeps ordinary invocation admission; the descriptor grants no access.
|
|
236
|
+
|
|
237
|
+
## Backend method declarations
|
|
238
|
+
|
|
239
|
+
Declare `fungi.backendDefinition` in the App package manifest, pointing to a
|
|
240
|
+
runtime-neutral module such as `backend/methods.ts`. Export
|
|
241
|
+
`appBackend = defineAppBackend({ methods })` there. The platform derives the
|
|
242
|
+
App's API and declaration from that definition with
|
|
243
|
+
`buildAppDeclaration(appBackend, fungi.app)` from
|
|
244
|
+
`@fungi.computer/agar/backend-declaration`; the manifest's `fungi.app` supplies
|
|
245
|
+
capabilities, commands, storage and `backend.bindings`. Authors write no
|
|
246
|
+
declaration module. Workers import only the definition; JSON Schema projection
|
|
247
|
+
and declaration validation never run on the call path. Each entry uses
|
|
248
|
+
`defineAppMethod` from `@fungi.computer/agar/backend-definition`: Zod `input`
|
|
249
|
+
and `output` schemas, a description, `effect`, `requires`, and a typed
|
|
250
|
+
`handler`. The schemas produce both the handler's TypeScript contract and its
|
|
251
|
+
JSON Schema declaration. `effect` describes the work but does not promise an
|
|
252
|
+
approval queue; the Hub broker owns approval and consent.
|
|
253
|
+
|
|
254
|
+
The build emits the API from this definition; do not write a `fungi.app.api`
|
|
255
|
+
method table in the manifest. It rejects methods requiring capabilities absent
|
|
256
|
+
from the App declaration. The API and policy are covered by the immutable
|
|
257
|
+
Release root's digest; metadata alone grants no access.
|
|
258
|
+
`appBackend.call(name, input, context, caller)` admits only defined methods and
|
|
259
|
+
parses both JSON input and output. Schema generation never executes a handler.
|
|
260
|
+
The exercised build and refusal proof is backend-definition.test.ts.
|
|
261
|
+
|
|
262
|
+
Declare `hidden: true` for maintenance methods used by the App itself. The
|
|
263
|
+
signed API retains the method and its validators, while Workflows and Agent/MCP
|
|
264
|
+
discovery omit it. This marker changes discovery; existing call permissions
|
|
265
|
+
still apply to direct requests.
|
|
266
|
+
|
|
267
|
+
An App may also supply `events` to `defineAppBackend`. Each named entry has a
|
|
268
|
+
`description` and a Zod `payload` schema. `buildAppApi` projects those schemas
|
|
269
|
+
into the optional `api.events` catalog, with no handlers or call authority. The
|
|
270
|
+
payload schema describes the whole `EVENTS.emit` payload
|
|
271
|
+
(`{ type, timestamp, data }`), not only its `data` field. For example:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
const appBackend = defineAppBackend({
|
|
275
|
+
methods: {},
|
|
276
|
+
events: {
|
|
277
|
+
"git.push": {
|
|
278
|
+
description: "A repository accepted a push",
|
|
279
|
+
payload: z.strictObject({
|
|
280
|
+
type: z.literal("git.push"),
|
|
281
|
+
timestamp: z.string(),
|
|
282
|
+
data: z.strictObject({ branch: z.string() }),
|
|
283
|
+
}),
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Hosts validate emission against the selected Release's declaration. Defining an
|
|
290
|
+
event does not emit it or install a trigger. Apps that do not declare events
|
|
291
|
+
omit `api.events`; event metadata and payload changes are covered by the
|
|
292
|
+
immutable Release root, like method metadata.
|
|
293
|
+
|
|
294
|
+
A long-running method declares `pending: true` alongside its policy and returns
|
|
295
|
+
`pending(5000)` from `@fungi.computer/agar/backend-definition` while it is
|
|
296
|
+
working. This runtime-neutral entry also exports `pendingSchema` and the
|
|
297
|
+
`Pending` type. Its handler, runtime output parser and generated API accept
|
|
298
|
+
`Output | Pending`; methods without the opt-in keep their declared output.
|
|
299
|
+
`Pending` is `{ kind: "pending", retryAfterMs }`, where the delay is an integer
|
|
300
|
+
from 1000 to 60000 milliseconds. `pendingSchema` validates the same shape. The
|
|
301
|
+
opt-in lets a Workflow host poll this method with the same idempotency key.
|
|
302
|
+
|
|
303
|
+
Export the facet class through `appBackend.implement(YourFacetClass)`. This
|
|
304
|
+
generates declared methods through `appBackend.call` when the module loads.
|
|
305
|
+
Hand-written API methods, undeclared methods and accessors refuse registration,
|
|
306
|
+
including inherited ones. Host lifecycle methods (`fetch`, `alarm` and the
|
|
307
|
+
WebSocket handlers) remain outside the API. Handlers receive the calling
|
|
308
|
+
instance's context as their second argument; they do not keep Team state in the
|
|
309
|
+
exported definition. The Worker entry imports this same definition and supplies
|
|
310
|
+
its runtime class; keep `cloudflare:workers`, Node builtins and evaluation-time
|
|
311
|
+
network calls out of the definition module. Network work belongs inside method
|
|
312
|
+
handlers. The builder refuses offending imports with a message naming the import
|
|
313
|
+
and where to move it.
|
|
314
|
+
|
|
315
|
+
Customer builds run the emitter inside their Computer VM. Operator builds run
|
|
316
|
+
only reviewed repository source on the deploy host; the operator preparer
|
|
317
|
+
refuses foreign repositories. The backend bundle includes the exact compiled
|
|
318
|
+
definition that the emitter evaluates. Publisher reads the emitted file after
|
|
319
|
+
the build, strict-parses its bounded data and checks the source and backend
|
|
320
|
+
digests. For Team candidates this read happens in the Computer builder; the Hub
|
|
321
|
+
strictly decodes and verifies the submitted Release at its trust boundary. The
|
|
322
|
+
emitted declaration requests authority; it does not grant it. A load-time export
|
|
323
|
+
handshake is deferred; method calls still refuse undeclared or unimplemented
|
|
324
|
+
methods.
|
|
325
|
+
|
|
326
|
+
App Session sends are follow-ups: they cannot steer or interrupt a running turn.
|
|
327
|
+
The receiving Agent durably allows ten send attempts per rolling minute for each
|
|
328
|
+
installation and Session. A refused send returns
|
|
329
|
+
`{ error: "send_budget_exceeded", retryAfterMs }`; no message is dispatched.
|
|
330
|
+
Stored messages identify the calling App installation and whether it acts for an
|
|
331
|
+
Agent or Team member.
|
|
332
|
+
|
|
333
|
+
This authoring contract does not introduce Apps to Agents or change the current
|
|
334
|
+
browser `app.backend.request` transport. The invocation and UI migrations join
|
|
335
|
+
the declared methods separately.
|
|
336
|
+
|
|
337
|
+
## Connect and release
|
|
338
|
+
|
|
339
|
+
connectApp takes an exact `expectedHostOrigin` and optional abort signal. It
|
|
340
|
+
checks the parent window, origin, protocol and transferred port before returning
|
|
341
|
+
an AppRoot. It permits one live connection per frontend page. Do not use a
|
|
342
|
+
wildcard origin or a stranger-controlled query parameter as the expected host.
|
|
343
|
+
|
|
344
|
+
The root supplies:
|
|
345
|
+
|
|
346
|
+
| Surface | Purpose |
|
|
347
|
+
| ------------------------------- | ------------------------------------------------------- |
|
|
348
|
+
| `identity` | Host-issued App, Release and Surface IDs. |
|
|
349
|
+
| `context`, `theme`, `lifecycle` | Current snapshots and subscriptions to host facts. |
|
|
350
|
+
| `capabilities` | Handles to currently admitted operations. |
|
|
351
|
+
| `ui` | Host-rendered consent, navigation, billing and notices. |
|
|
352
|
+
| `commands`, `window` | Command registration, close consultation and Back. |
|
|
353
|
+
| `dispose()` | Release the mount connection and its handles. |
|
|
354
|
+
|
|
355
|
+
Abort a pending handshake when the caller unmounts. Dispose the root and every
|
|
356
|
+
subscription or command lease when that caller ends. A failed connection can be
|
|
357
|
+
retried after cleanup. Disposing a handle does not roll back work already
|
|
358
|
+
admitted by its resource owner.
|
|
359
|
+
|
|
360
|
+
## Request one operation
|
|
361
|
+
|
|
362
|
+
The `@fungi.computer/agar/channels` subpath supplies `channelsRead` and
|
|
363
|
+
`channelsPersonalManage`. Both are off until a member approves the declared
|
|
364
|
+
capability for that App installation. Read access lists websites and up to 100
|
|
365
|
+
recent conversation summaries without visitor credentials or site keys. Manage
|
|
366
|
+
access holds, releases and replies through the website takeover owner; it does
|
|
367
|
+
not expose direct Session prompting. Every call checks current App consent and
|
|
368
|
+
the member's Team permissions. These are frontend capabilities, not Agent
|
|
369
|
+
backend bindings.
|
|
370
|
+
|
|
371
|
+
Capability owners use `defineCapability(id, version, schema)` to decode their
|
|
372
|
+
live RPC interface and its results. `require` uses that owner schema after the
|
|
373
|
+
exact grant lookup. Schema functions stay local; only the descriptor's ID and
|
|
374
|
+
version travel with a grant. Data results are copied and their RPC envelopes
|
|
375
|
+
released; returned file, Fetch and terminal handles retain their own lifetime.
|
|
376
|
+
Local implementations register through the same typed descriptor.
|
|
377
|
+
|
|
378
|
+
Computer helpers expose bounded listing, file streams, terminal, process and
|
|
379
|
+
port operations. For example, `listComputerFiles` takes the root's capabilities
|
|
380
|
+
and `{ path, limit, offset }`, then decodes the listing. Its handles are bound
|
|
381
|
+
to the host-selected Computer. The frontend does not retarget one by submitting
|
|
382
|
+
another Computer ID. For an edited text file, `replaceComputerFileIfUnchanged`
|
|
383
|
+
accepts the opened `expectedContent` and new `content`. The Computer compares
|
|
384
|
+
and replaces them atomically; a changed file raises `AppError` with code
|
|
385
|
+
`CONFLICT` instead of acknowledging a stale save. `writeComputerFile` remains an
|
|
386
|
+
unconditional write for an explicit overwrite action.
|
|
387
|
+
|
|
388
|
+
The Computer instrument has separate `computer.instrument.read` and
|
|
389
|
+
`computer.instrument.control` handles. `readComputerInstrument` returns the
|
|
390
|
+
selected Computer's runtime, attached backends and resolved Internet state;
|
|
391
|
+
`startInstrumentComputer`, `setInstrumentInternetAccess` and
|
|
392
|
+
`detachInstrumentLocalBackend` require control. The host exposes those handles
|
|
393
|
+
only with the corresponding Computer grant and rechecks the member, App and
|
|
394
|
+
Computer owner on each operation.
|
|
395
|
+
|
|
396
|
+
First ask `root.ui.requestAppPermissions({ computers: ["computer.list"] })`.
|
|
397
|
+
Only a `granted` outcome warrants attempting the listing. `declined` and
|
|
398
|
+
`customize` are ordinary outcomes. The host bounds consent by the App's Release
|
|
399
|
+
declaration and the resource rechecks authority on each request. Connection,
|
|
400
|
+
declaration and grant are different facts.
|
|
401
|
+
|
|
402
|
+
The permission boundary proof crosses the actual host/client port with decoded
|
|
403
|
+
consent requests. The Computer tests and file tests cover data decoding, native
|
|
404
|
+
streams and disposal. These are local proofs, not customer authorization or a
|
|
405
|
+
hosted-provider qualification.
|
|
406
|
+
|
|
407
|
+
## Frontend and backend
|
|
408
|
+
|
|
409
|
+
The browser gets capability handles, not Team cookies, namespace bindings or
|
|
410
|
+
provider keys. File reads consume a native response and create the browser File
|
|
411
|
+
locally. They dispose returned RPC values and per-read handles. Read
|
|
412
|
+
cancellation and mount disposal release the host request.
|
|
413
|
+
|
|
414
|
+
appBackend invokes this App's admitted backend. Its JSON request/result grammar
|
|
415
|
+
belongs to the App. The host supplies authenticated actor identity. A backend is
|
|
416
|
+
separately declared in an immutable App artifact. Importing the frontend SDK
|
|
417
|
+
does not create one or grant arbitrary egress.
|
|
418
|
+
|
|
419
|
+
`apps.open` is a declared presentation handoff. Compose intent supplies a
|
|
420
|
+
bounded draft, not an instruction to send it. Each App keeps its own identity
|
|
421
|
+
and granted capabilities.
|
|
422
|
+
|
|
423
|
+
Agar owns each App role's wire format; Puffball's App vocabulary owns the role
|
|
424
|
+
names. `@fungi.computer/agar/roles/files` is the `files` role contract. A caller
|
|
425
|
+
opens `{ role: filesRole, intent: filesNavigateIntent(location) }`, where the
|
|
426
|
+
location is a repository file at a branch or a Computer file, and the Team's
|
|
427
|
+
files App decodes it with `readFilesLocation`. The same module owns the file
|
|
428
|
+
browsing, edit and snapshot payloads that Files Apps and their callers exchange.
|
|
429
|
+
|
|
430
|
+
## Back in the window chrome
|
|
431
|
+
|
|
432
|
+
An App keeps its own history. Route on memory history so the browser's history
|
|
433
|
+
never moves, because a host shows several App windows at once. The host shows
|
|
434
|
+
Back in the focused window's title bar while the App reports it can go back.
|
|
435
|
+
|
|
436
|
+
`root.window.navigation.publish({ canGoBack, title })` reports the latest state.
|
|
437
|
+
`title` is the current page, from 1 to 80 characters after trimming. An empty
|
|
438
|
+
title is dropped. `root.window.navigation.onBack(handler)` owns the host's Back
|
|
439
|
+
requests, one owner at a time. The handler answers `true` when it moved. The
|
|
440
|
+
report crosses as version 1 of the navigation contract, and a host rejects any
|
|
441
|
+
other version. Hosts without window chrome ignore reports.
|
|
442
|
+
|
|
443
|
+
`useHostNavigation` from `./react` does both for a React App and withdraws Back
|
|
444
|
+
on unmount. With TanStack Router:
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
const router = useRouter();
|
|
448
|
+
const canGoBack = useCanGoBack();
|
|
449
|
+
useHostNavigation(app?.window.navigation, {
|
|
450
|
+
canGoBack,
|
|
451
|
+
title: currentPageTitle,
|
|
452
|
+
onBack: () => {
|
|
453
|
+
if (!router.history.canGoBack()) return false;
|
|
454
|
+
router.history.back();
|
|
455
|
+
return true;
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
The Tourtois workbench's `Desktop / App Back` story runs this recipe in a
|
|
461
|
+
two-page App.
|
|
462
|
+
|
|
463
|
+
The connection also turns the browser's own back inputs inside the App into App
|
|
464
|
+
Back. These are `Alt+←`, `⌘` and `⌘←` on a Mac (outside text fields), and the
|
|
465
|
+
mouse back button. It cancels them, together with the matching forward inputs,
|
|
466
|
+
even while the App cannot go back. A stray press therefore never navigates the
|
|
467
|
+
workspace page away. Input the App already handled (`defaultPrevented`) is left
|
|
468
|
+
alone.
|
|
469
|
+
|
|
470
|
+
The browser toolbar's Back button, trackpad swipes and the history menu stay
|
|
471
|
+
outside an App's reach.
|
|
472
|
+
|
|
473
|
+
`./agents` also exports `FungiMessageMetaSchema`, `authorMeta` and
|
|
474
|
+
`messageAuthor` for the author metadata attached by an admitting host. Shiitake
|
|
475
|
+
stores that metadata opaquely. Apps display the authenticated author without
|
|
476
|
+
importing Hub policy; absent, foreign or malformed metadata names no author.
|
|
477
|
+
|
|
478
|
+
A delegated Agent's reply carries `meta.delegation.reply`
|
|
479
|
+
(`FungiDelegationReplySchema`): the replying `agentId` and `sessionId`, the
|
|
480
|
+
`commandId` that equals the sender's `agents.send` receipt, the exact `outcome`
|
|
481
|
+
(`completed`, `failed`, `interrupted`, `cancelled` or `outcome_unknown`) and,
|
|
482
|
+
for `failed`, `interrupted` and `outcome_unknown`, a bounded one-line
|
|
483
|
+
`diagnostic`. The admitting host attaches it only after authenticating the reply
|
|
484
|
+
against the mail it sent, and the line's `author` is then the replying Agent.
|
|
485
|
+
`messageDelegationReply(meta)` reads it; a line whose text merely looks like a
|
|
486
|
+
reply has none.
|
|
487
|
+
|
|
488
|
+
Agent creation through `./agents` accepts `computer: "new" | "none"` alongside
|
|
489
|
+
`name` and `requestId`. Omission keeps the Hub's new-Computer default. The host
|
|
490
|
+
still admits Team membership, App permission and funding; the Computer choice is
|
|
491
|
+
bound to the creation receipt and does not grant attachment authority.
|
|
492
|
+
|
|
493
|
+
## Errors and reference
|
|
494
|
+
|
|
495
|
+
[AppError carries a typed `code` and explicit `retryable` flag. Handle denial,
|
|
496
|
+
revocation, selection change and disconnect beside the affected operation.
|
|
497
|
+
Report unexpected failures with their cause. Do not convert a failed request to
|
|
498
|
+
an empty successful result or retry a mutation without reviewing its owner's
|
|
499
|
+
outcome.
|
|
500
|
+
|
|
501
|
+
Use `describeAppError` from `@fungi.computer/agar/errors` at the UI boundary:
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
import { describeAppError } from "@fungi.computer/agar/errors";
|
|
505
|
+
|
|
506
|
+
const description = describeAppError(cause);
|
|
507
|
+
// Render description.title and description.message beside the failed operation.
|
|
508
|
+
// Keep the original cause for diagnostics; it is never included in the copy.
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
The function returns `ErrorDescription`: `title`, `message`, the error owner's
|
|
512
|
+
`retryable` flag, and an optional `grant`, `retry`, `reconnect` or `billing`
|
|
513
|
+
action hint. Grant and reconnect describe prerequisites rather than retrying the
|
|
514
|
+
failed operation. The helper never performs an action or schedules a retry. The
|
|
515
|
+
caller still owns the operation and its recovery controls. `INSUFFICIENT_CREDIT`
|
|
516
|
+
offers `billing`; its caller opens the existing `app.ui.openTeamBilling()` host
|
|
517
|
+
action.
|
|
518
|
+
|
|
519
|
+
Media's `SAFETY_FILTER_REFUSED` outcome is non-retryable. Its typed copy
|
|
520
|
+
explains that the model's safety filter refused the prompt and asks you to
|
|
521
|
+
reword it; it offers no automatic retry action and never displays provider
|
|
522
|
+
diagnostics.
|
|
523
|
+
|
|
524
|
+
Only SDK-decoded `AppError` instances select typed copy. Other failures receive
|
|
525
|
+
a generic description with `retryable: false` and no action; exception messages,
|
|
526
|
+
nested causes and error-shaped objects are not interpreted as display text or
|
|
527
|
+
authority. The optional `Partial<Record<AppErrorCode, ErrorCopy>>` argument
|
|
528
|
+
replaces a code's title and message only. It cannot override retry truth or the
|
|
529
|
+
recovery hint.
|
|
530
|
+
|
|
531
|
+
The wording approved by Levi is collected in the `errorCopy` table in errors.ts.
|
|
532
|
+
Prints is the first consumer; other Apps remain Wave 2 work. Hub-specific error
|
|
533
|
+
descriptions and typed App backend RPC remain with their respective owners.
|
|
534
|
+
|
|
535
|
+
[package.json](package.json) owns the exports. The root supplies connection and
|
|
536
|
+
shared types. Operation subpaths include `./computer`, `./agents`,
|
|
537
|
+
`./inference`, `./media`, `./apps`, `./backend`, `./errors` and `./react`.
|
|
538
|
+
`./host` supports App-author iframe integration tests and standalone mounts.
|
|
539
|
+
Capability contract versions do not replace Installation authority versions.
|
|
540
|
+
|
|
541
|
+
Semantic tests and disposal laws are the nearest executable examples of
|
|
542
|
+
connection, live updates, commands and lifetime.
|
|
543
|
+
|
|
544
|
+
## React App mounting
|
|
545
|
+
|
|
546
|
+
`@fungi.computer/agar/react` owns the browser mount lifecycle. Its React
|
|
547
|
+
entrypoint uses React, React DOM and TanStack React Query peers; the semantic
|
|
548
|
+
SDK entrypoint has no React runtime dependency. Keep App CSS and root-element
|
|
549
|
+
selection in the App:
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
import { mountApp } from "@fungi.computer/agar/react";
|
|
553
|
+
|
|
554
|
+
const dispose = mountApp(root, { name: "Bazaar" }, ({ app }) => (
|
|
555
|
+
<MarketplaceApp app={app} />
|
|
556
|
+
));
|
|
557
|
+
import.meta.hot?.dispose(dispose);
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
`mountApp` authenticates the iframe host using the parsed referrer origin,
|
|
561
|
+
applies and subscribes to its theme, and restores the original theme on
|
|
562
|
+
disconnect or unmount. Its render callback receives `connecting`, `live`,
|
|
563
|
+
`standalone` (no host or usable referrer), or `ended` (failed/closed
|
|
564
|
+
connection). Only `live` carries an AppRoot. The returned disposer aborts a
|
|
565
|
+
pending handshake, releases subscriptions, disposes the connection and unmounts
|
|
566
|
+
React. A late connection is disposed too. Existing App-open React performance
|
|
567
|
+
marks are emitted at root render and commits.
|
|
568
|
+
|
|
569
|
+
Supply `queryClient` to wrap the App in `QueryClientProvider`. The caller owns
|
|
570
|
+
that client, its cache policy and grant invalidation; mounting does not clear
|
|
571
|
+
it.
|
|
572
|
+
|
|
573
|
+
- `useStore(store)` tracks a `ReadonlyStore`, including replacement/removal.
|
|
574
|
+
- `useAppContext(app)` returns the current host context or `null`.
|
|
575
|
+
- `useComputerId(app)` returns its selected Computer ID or `null`.
|
|
576
|
+
- `useAppCommandCatalog(app, { sourceId, namespace }, build)` owns local Whistle
|
|
577
|
+
contributions and host registration leases. It returns the local runtime for
|
|
578
|
+
slash projections. Memoize `build` with `useCallback`; changed dependencies
|
|
579
|
+
replace the catalog and its captured availability. Unmount releases both
|
|
580
|
+
sides, including host registrations acknowledged after unmount.
|
|
581
|
+
|
|
582
|
+
Bazaar is the first bootstrap adopter. Other Apps keep their existing
|
|
583
|
+
entrypoints until their adoption slices; query/feed polling belongs to the query
|
|
584
|
+
integration.
|
|
585
|
+
|
|
586
|
+
## Store an App's own data
|
|
587
|
+
|
|
588
|
+
Declare bounded `storage` keys in the App Release. The host then offers
|
|
589
|
+
`app.storage` v1 without a resource permission grant. It exposes only the
|
|
590
|
+
current installation's data: the calling member's private override, otherwise
|
|
591
|
+
the Team default, otherwise `source: "none"`. The App supplies its own code
|
|
592
|
+
constant for that last case. Overrides replace the whole JSON value, including
|
|
593
|
+
null; they are not merged.
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
import { appStorage } from "@fungi.computer/agar/storage";
|
|
597
|
+
|
|
598
|
+
const storage = root.capabilities.require(appStorage);
|
|
599
|
+
const desktop = await storage.watch("desktop");
|
|
600
|
+
const initial = desktop.get();
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
`watch` resolves after subscribing and reading a real snapshot. Its synchronous
|
|
604
|
+
`get` and `subscribe` follow later changes, including writes in other windows.
|
|
605
|
+
Close the watch with `desktop.close()` or `Symbol.dispose` when the consumer
|
|
606
|
+
ends, and release each subscription. Reconnection re-reads the current value;
|
|
607
|
+
this is a snapshot store, not a history of every mutation.
|
|
608
|
+
|
|
609
|
+
`read(key)` returns `{ value, source, team, member, canWriteTeam }`; each layer
|
|
610
|
+
is null or `{ value, revision }`. `write({ key, scope, value, basedOn })` and
|
|
611
|
+
`reset({ key, scope, basedOn })` compare that layer's revision, or null when it
|
|
612
|
+
is absent, and return `{ revision }`. Values must be JSON within the declared
|
|
613
|
+
byte bound and depth 32. The Hub also enforces App and Team quotas and a
|
|
614
|
+
mutation rate limit.
|
|
615
|
+
|
|
616
|
+
Only the current Team owner can write or reset the Team layer. No method takes a
|
|
617
|
+
member ID, and owners cannot read another member's override. A fork starts with
|
|
618
|
+
empty stored layers; uninstall and membership removal purge the affected values.
|
|
619
|
+
A member who rejoins starts with a fresh private scope.
|
|
620
|
+
|
|
621
|
+
`AppStorageError.code` is one of `invalid`, `undeclared_key`,
|
|
622
|
+
`scope_not_declared`, `owner_required`, `too_large`, `quota_exceeded`,
|
|
623
|
+
`conflict` or `unavailable`. A conflict requires a fresh read and a new user
|
|
624
|
+
decision; never overwrite the newer layer by retrying with an invented revision.
|
|
625
|
+
|
|
626
|
+
## Suggest desktop Gills
|
|
627
|
+
|
|
628
|
+
The admitted dashboard callback reads only a selected Gill’s saved sizes and
|
|
629
|
+
handles member-consented Add and Remove through its existing arranger. Ordinary
|
|
630
|
+
Apps receive no callback or arrange endpoint.
|
|
631
|
+
|
|
632
|
+
While a mounted App is focused and browser activation is active,
|
|
633
|
+
`await app.ui.openDesktopSettings()` asks the Hub to show a desktop Gill
|
|
634
|
+
suggestion. The member clicks **Review** in that host-rendered notice to open
|
|
635
|
+
**Settings → Apps → this App → Desktop**. It takes no arguments and returns no
|
|
636
|
+
desktop data. The Hub identifies the caller from its admitted surface; an App
|
|
637
|
+
cannot select another App or supply a placement. Browser activation does not
|
|
638
|
+
prove that the member intended to open Settings. An App can still take keyboard
|
|
639
|
+
focus; the Hub does not claim focus protection.
|
|
640
|
+
|
|
641
|
+
The member chooses one of that App’s declared desktop Gills, selects a size, and
|
|
642
|
+
clicks **Add to desktop**. Only that click calls the admitted dashboard’s member
|
|
643
|
+
arranger. Opening Settings never changes the layout or the Team default. Hosts
|
|
644
|
+
without this handoff reject it with `CAPABILITY_DENIED`.
|
|
645
|
+
|
|
646
|
+
## API reference
|
|
647
|
+
|
|
648
|
+
The generated [API reference](docs/api/README.md) is included in the tarball. It
|
|
649
|
+
covers the App author's surface: the root connection and shared types,
|
|
650
|
+
`./react`, `./query`, `./errors`, `./rpc`, `./backend`, `./backend-definition`,
|
|
651
|
+
`./backend-declaration`, `./files` and `./storage`.
|
|
652
|
+
|
|
653
|
+
Capability clients (`./computer`, `./agents`, `./apps`, `./channels`, `./inbox`,
|
|
654
|
+
`./gills` (host refusal decoding), `./media`, `./media-library`,
|
|
655
|
+
`./marketplace`, `./inference`, `./game-servers`, `./world`) only work against a
|
|
656
|
+
host that grants them, such as a Fungi Team. These capability clients do not yet
|
|
657
|
+
have generated reference pages; treat them as unstable.
|
|
658
|
+
|
|
659
|
+
## App-author iframe adapter
|
|
660
|
+
|
|
661
|
+
`@fungi.computer/agar/host` is the counterpart to `connectApp`. App authors use
|
|
662
|
+
`connectAppHost` to exercise their App in an integration test or mount it in a
|
|
663
|
+
standalone iframe page. Pass the exact child window and expected origin, a
|
|
664
|
+
launch identity and context, explicitly supplied capability handles, and
|
|
665
|
+
callbacks for the UI operations the page supports. The adapter binds the
|
|
666
|
+
MessagePort to that window and origin and owns its connection lifetime.
|
|
667
|
+
|
|
668
|
+
The returned `AppHost` updates context and theme, requests Back or Close, and
|
|
669
|
+
releases the connection with `dispose()`. The embedding page owns admission and
|
|
670
|
+
must supply only capabilities it can grant. Importing this adapter gives no
|
|
671
|
+
Fungi account, Hub keys, Team admission, desktop policy or window manager.
|
|
672
|
+
Optional UI callbacks route requests to the embedding page; they do not
|
|
673
|
+
implement billing or desktop settings. This is part of the App-author
|
|
674
|
+
integration contract. Oyster supplies the public shell helpers; Fungi's signed
|
|
675
|
+
host protocol remains private.
|
|
676
|
+
|
|
677
|
+
## Examples
|
|
678
|
+
|
|
679
|
+
The Quick start above defines a backend, builds its App API and calls a method
|
|
680
|
+
without a host. The Back example shows an App-owned navigation handler. The
|
|
681
|
+
maintained packed-consumer proof covers backend calls and imports the iframe
|
|
682
|
+
adapter from the installed tarball; connection and disposal suites additionally
|
|
683
|
+
exercise both sides of a real MessagePort in the repository.
|
|
684
|
+
|
|
685
|
+
## Limits and errors
|
|
686
|
+
|
|
687
|
+
A plain top-level page has no host to connect to; `connectApp` needs a
|
|
688
|
+
host-mounted iframe. Capability operations fail with typed `AppError` codes when
|
|
689
|
+
the host denies, revokes or lacks the capability. Method schemas validate input
|
|
690
|
+
and output on every call; a handler cannot return data its output schema
|
|
691
|
+
rejects.
|
|
692
|
+
|
|
693
|
+
## Contribute and license
|
|
694
|
+
|
|
695
|
+
Run `pnpm --filter @fungi.computer/agar test` for the SDK suites and
|
|
696
|
+
`pnpm --filter @fungi.computer/agar build` to compile the package. The package
|
|
697
|
+
uses the MIT license in [`LICENSE`](LICENSE).
|
|
698
|
+
|
|
699
|
+
Shared Gill declarations, row codecs and clients live in
|
|
700
|
+
`@fungi.computer/gills`. Define an App's compact views with `defineAppGill` from
|
|
701
|
+
`@fungi.computer/agar/gills` next to its `defineAppMethod` exports, then pass
|
|
702
|
+
them as `gills` to `defineAppBackend`. `buildAppDeclaration` generates their
|
|
703
|
+
Release metadata; do not repeat it in `package.json`.
|
|
704
|
+
|
|
705
|
+
Each Gill names a declared read method and a page `limit` from 1 to 200. Its
|
|
706
|
+
input mapping receives `{params, after, limit}` and must forward that limit to
|
|
707
|
+
the source method. Source methods must accept the limit and return one page with
|
|
708
|
+
their own valid continuation. The rows projection receives the method's parsed
|
|
709
|
+
JSON output and a second `{ params }` argument containing the placement values,
|
|
710
|
+
so navigation can retain the repository or other selected App context even when
|
|
711
|
+
the source output does not repeat it. It must not filter away source rows:
|
|
712
|
+
shorten text while keeping each row's identity and navigation parameters. The
|
|
713
|
+
helper bounds display text and the 240 KiB reply while preserving all rows and
|
|
714
|
+
the source's `next`; it refuses a page whose navigation alone exceeds the
|
|
715
|
+
budget.
|
|
716
|
+
|
|
717
|
+
A Gill's source runs through the method's existing input/handler/output parsing
|
|
718
|
+
and trusted caller context. A write source or an undeclared method is refused.
|
|
719
|
+
Placement may declare up to four Computer/repository/branch parameters, and a
|
|
720
|
+
storage marker or binding change feed. Repository or branch parameters may name
|
|
721
|
+
`options: "<own read method>"`. An options request calls that read method with
|
|
722
|
+
`{limit, params, after?}`; `params` contains the values chosen so far. Its
|
|
723
|
+
output is `{options: [{value, label, detail?}], next?}`, and it retains its own
|
|
724
|
+
capability requirements. Computer choices are host-owned; there is no picker
|
|
725
|
+
registry. `backend.gill` and the client return `GillReply`, a row/error response
|
|
726
|
+
or an options page. A row's `open.params` is an intent within that App, with no
|
|
727
|
+
URL or recipient App ID.
|
|
728
|
+
|
|
729
|
+
Agent extension backends compose `agentExtensionMethods` from
|
|
730
|
+
`@fungi.computer/shiitake/agar-extension` with `defineAppBackend`. The extension
|
|
731
|
+
adapter belongs to Shiitake; Agar does not depend on the Agent runtime.
|
|
732
|
+
|
|
733
|
+
## Media JSON transport
|
|
734
|
+
|
|
735
|
+
`@fungi.computer/agar/media-json` exports the page-side `MediaGeneration` and
|
|
736
|
+
`MediaGenerationAttempt` capability handles. Construct
|
|
737
|
+
`new MediaGeneration(call, computerId, mountSignal)` with the page's admitted
|
|
738
|
+
generic JSON method caller. Its signature is
|
|
739
|
+
`call(method, input?: Schema.Json, { signal }?) => Promise<Schema.Json | undefined>`;
|
|
740
|
+
signals are local fetch options and never enter JSON or RPC. Hosts own routing,
|
|
741
|
+
admission and HTTP translation. The App continues to use Agar's
|
|
742
|
+
`media.generation` v5 handle API.
|
|
743
|
+
|
|
744
|
+
The JSON methods are `describe()` (Computer identity, permission and models),
|
|
745
|
+
`generate({ requestId, destination: { path }, settings })` (a typed
|
|
746
|
+
`succeeded`/`failed` outcome or Agar `Pending`) and
|
|
747
|
+
`cancelAttempt({ requestId, computerId })` (a null or undefined acknowledgment).
|
|
748
|
+
Destination and reference inputs contain paths; hosts supply their authority.
|
|
749
|
+
The exported `mediaGenerationDescriptionSchema`,
|
|
750
|
+
`mediaGenerationJsonRequestSchema`, `mediaGenerationJsonOutcomeSchema`,
|
|
751
|
+
`mediaGenerationJsonReplySchema` and `mediaGenerationReferenceSchema` decode
|
|
752
|
+
these boundaries with the existing Agar Media grammar.
|
|
753
|
+
|
|
754
|
+
Attempts retain their canonical request ID when restored or when polling
|
|
755
|
+
Pending. The shared Agar Pending validator requires an integer `retryAfterMs`
|
|
756
|
+
from 1000 to 60000. Cancellation aborts the attempt's active local requests;
|
|
757
|
+
background generation retains its identity for later restoration. Malformed JSON
|
|
758
|
+
replies refuse with a typed `PROTOCOL` error. Browser TypeScript consumers use
|
|
759
|
+
the platform DOM types and `ESNext.Disposable`.
|
|
760
|
+
|
|
761
|
+
See the [generated API](docs/api/README.md) for signatures and host ports.
|
|
762
|
+
|
|
763
|
+
`@fungi.computer/agar/computer-contracts` is the runtime-neutral public home for
|
|
764
|
+
`workflowGitPassResultSchema` and `WorkflowGitPassResult`. App backend
|
|
765
|
+
definitions can validate host-issued Workflow pass metadata without importing
|
|
766
|
+
the Computer RPC client. The parser preserves the complete credential-free HTTPS
|
|
767
|
+
remote; admission, expiry and revocation remain host authority.
|