@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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +766 -2
  3. package/dist/agent-message-meta.d.ts +74 -0
  4. package/dist/agent-message-meta.js +63 -0
  5. package/dist/agents.d.ts +170 -0
  6. package/dist/agents.js +218 -0
  7. package/dist/app-build-contract.d.ts +43 -0
  8. package/dist/app-build-contract.js +24 -0
  9. package/dist/apps.d.ts +66 -0
  10. package/dist/apps.js +47 -0
  11. package/dist/backend-declaration.d.ts +6 -0
  12. package/dist/backend-declaration.js +65 -0
  13. package/dist/backend-definition.d.ts +88 -0
  14. package/dist/backend-definition.js +158 -0
  15. package/dist/backend-refusal.d.ts +10 -0
  16. package/dist/backend-refusal.js +10 -0
  17. package/dist/backend.d.ts +30 -0
  18. package/dist/backend.js +28 -0
  19. package/dist/capabilities.d.ts +30 -0
  20. package/dist/capabilities.js +50 -0
  21. package/dist/channels.d.ts +142 -0
  22. package/dist/channels.js +97 -0
  23. package/dist/commands.d.ts +33 -0
  24. package/dist/commands.js +46 -0
  25. package/dist/computer-contracts.d.ts +73 -0
  26. package/dist/computer-contracts.js +57 -0
  27. package/dist/computer.d.ts +294 -0
  28. package/dist/computer.js +319 -0
  29. package/dist/connect.d.ts +11 -0
  30. package/dist/connect.js +211 -0
  31. package/dist/errors.d.ts +34 -0
  32. package/dist/errors.js +106 -0
  33. package/dist/failure.d.ts +2 -0
  34. package/dist/failure.js +9 -0
  35. package/dist/files.d.ts +25 -0
  36. package/dist/files.js +84 -0
  37. package/dist/game-servers.d.ts +530 -0
  38. package/dist/game-servers.js +244 -0
  39. package/dist/gills.d.ts +43 -0
  40. package/dist/gills.js +174 -0
  41. package/dist/git-commit.d.ts +143 -0
  42. package/dist/git-commit.js +251 -0
  43. package/dist/git-rpc.d.ts +310 -0
  44. package/dist/git-rpc.js +166 -0
  45. package/dist/git-steps.d.ts +579 -0
  46. package/dist/git-steps.js +804 -0
  47. package/dist/git-wire.d.ts +33 -0
  48. package/dist/git-wire.js +240 -0
  49. package/dist/git.d.ts +322 -0
  50. package/dist/git.js +98 -0
  51. package/dist/host.d.ts +97 -0
  52. package/dist/host.js +732 -0
  53. package/dist/inbox.d.ts +99 -0
  54. package/dist/inbox.js +184 -0
  55. package/dist/index.d.ts +31 -0
  56. package/dist/index.js +15 -0
  57. package/dist/inference.d.ts +17 -0
  58. package/dist/inference.js +115 -0
  59. package/dist/internal/app-error.d.ts +8 -0
  60. package/dist/internal/app-error.js +36 -0
  61. package/dist/internal/app-session.d.ts +8 -0
  62. package/dist/internal/app-session.js +551 -0
  63. package/dist/internal/back-inputs.d.ts +22 -0
  64. package/dist/internal/back-inputs.js +93 -0
  65. package/dist/internal/browser-bridge.d.ts +30 -0
  66. package/dist/internal/browser-bridge.js +203 -0
  67. package/dist/internal/capabilities.d.ts +13 -0
  68. package/dist/internal/capabilities.js +138 -0
  69. package/dist/internal/commands.d.ts +9 -0
  70. package/dist/internal/commands.js +257 -0
  71. package/dist/internal/disconnect-host.d.ts +7 -0
  72. package/dist/internal/disconnect-host.js +19 -0
  73. package/dist/internal/git-edit-tree.d.ts +6 -0
  74. package/dist/internal/git-edit-tree.js +46 -0
  75. package/dist/internal/memory-fs.d.ts +39 -0
  76. package/dist/internal/memory-fs.js +154 -0
  77. package/dist/internal/protocol.d.ts +116 -0
  78. package/dist/internal/protocol.js +6 -0
  79. package/dist/internal/query-feed.d.ts +23 -0
  80. package/dist/internal/query-feed.js +111 -0
  81. package/dist/internal/result.d.ts +22 -0
  82. package/dist/internal/result.js +61 -0
  83. package/dist/internal/rpc-contract.d.ts +24 -0
  84. package/dist/internal/rpc-contract.js +78 -0
  85. package/dist/internal/schemas.d.ts +307 -0
  86. package/dist/internal/schemas.js +524 -0
  87. package/dist/internal/store.d.ts +8 -0
  88. package/dist/internal/store.js +30 -0
  89. package/dist/internal/terminal-gate.d.ts +17 -0
  90. package/dist/internal/terminal-gate.js +94 -0
  91. package/dist/lifecycle.d.ts +44 -0
  92. package/dist/lifecycle.js +1 -0
  93. package/dist/marketplace.d.ts +159 -0
  94. package/dist/marketplace.js +102 -0
  95. package/dist/media-json.d.ts +181 -0
  96. package/dist/media-json.js +204 -0
  97. package/dist/media-library.d.ts +11 -0
  98. package/dist/media-library.js +15 -0
  99. package/dist/media-request-id.d.ts +7 -0
  100. package/dist/media-request-id.js +16 -0
  101. package/dist/media.d.ts +354 -0
  102. package/dist/media.js +654 -0
  103. package/dist/method-refusal.d.ts +64 -0
  104. package/dist/method-refusal.js +48 -0
  105. package/dist/pending.d.ts +10 -0
  106. package/dist/pending.js +10 -0
  107. package/dist/query.d.ts +76 -0
  108. package/dist/query.js +195 -0
  109. package/dist/react-mount.d.ts +23 -0
  110. package/dist/react-mount.js +108 -0
  111. package/dist/react.d.ts +41 -0
  112. package/dist/react.js +111 -0
  113. package/dist/read-copy.d.ts +103 -0
  114. package/dist/read-copy.js +195 -0
  115. package/dist/roles/files.d.ts +295 -0
  116. package/dist/roles/files.js +184 -0
  117. package/dist/root.d.ts +21 -0
  118. package/dist/root.js +1 -0
  119. package/dist/rpc.d.ts +1 -0
  120. package/dist/rpc.js +1 -0
  121. package/dist/status.d.ts +17 -0
  122. package/dist/status.js +50 -0
  123. package/dist/storage-wire.d.ts +167 -0
  124. package/dist/storage-wire.js +115 -0
  125. package/dist/storage.d.ts +72 -0
  126. package/dist/storage.js +143 -0
  127. package/dist/store.d.ts +11 -0
  128. package/dist/store.js +1 -0
  129. package/dist/test-kit/git.d.ts +31 -0
  130. package/dist/test-kit/git.js +174 -0
  131. package/dist/ui.d.ts +169 -0
  132. package/dist/ui.js +10 -0
  133. package/dist/window.d.ts +41 -0
  134. package/dist/window.js +15 -0
  135. package/dist/world.d.ts +16 -0
  136. package/dist/world.js +17 -0
  137. package/docs/api/README.md +28 -0
  138. package/docs/api/app-build-contract.md +113 -0
  139. package/docs/api/backend-declaration.md +62 -0
  140. package/docs/api/backend-definition.md +215 -0
  141. package/docs/api/backend.md +269 -0
  142. package/docs/api/errors.md +169 -0
  143. package/docs/api/files.md +91 -0
  144. package/docs/api/gills.md +164 -0
  145. package/docs/api/git-commit.md +279 -0
  146. package/docs/api/git-rpc.md +243 -0
  147. package/docs/api/git-steps.md +1010 -0
  148. package/docs/api/git-wire.md +197 -0
  149. package/docs/api/git.md +579 -0
  150. package/docs/api/index.md +2880 -0
  151. package/docs/api/media-json.md +507 -0
  152. package/docs/api/media-request-id.md +32 -0
  153. package/docs/api/modules.md +32 -0
  154. package/docs/api/query.md +253 -0
  155. package/docs/api/react.md +216 -0
  156. package/docs/api/read-copy.md +204 -0
  157. package/docs/api/rpc.md +128 -0
  158. package/docs/api/storage.md +620 -0
  159. package/package.json +197 -3
package/README.md CHANGED
@@ -1,3 +1,767 @@
1
- # Temporary Holding Version
1
+ # Agar (`@fungi.computer/agar`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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.