@fungi.computer/oyster 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/README.md +376 -72
  2. package/dist/agent-avatars.d.ts +103 -0
  3. package/dist/agent-avatars.js +66 -0
  4. package/dist/agents.d.ts +15 -4
  5. package/dist/agents.js +43 -4
  6. package/dist/app-builds.d.ts +81 -8
  7. package/dist/app-builds.js +210 -60
  8. package/dist/app-storage.d.ts +3 -3
  9. package/dist/approval-capability.d.ts +5 -0
  10. package/dist/approval-capability.js +46 -0
  11. package/dist/approval-home.d.ts +51 -0
  12. package/dist/approval-home.js +226 -0
  13. package/dist/apps/catalog.d.ts +335 -0
  14. package/dist/apps/catalog.js +46 -0
  15. package/dist/apps/gate.d.ts +26 -12
  16. package/dist/apps/gate.js +48 -22
  17. package/dist/apps/index.d.ts +3 -1
  18. package/dist/apps/index.js +3 -1
  19. package/dist/apps/installation.d.ts +147 -66
  20. package/dist/apps/installation.js +193 -86
  21. package/dist/apps/permissions.d.ts +247 -0
  22. package/dist/apps/permissions.js +255 -0
  23. package/dist/boot-capability-route.d.ts +15 -0
  24. package/dist/boot-capability-route.js +196 -0
  25. package/dist/boot-capability.d.ts +103 -0
  26. package/dist/boot-capability.js +141 -0
  27. package/dist/boot-construction.d.ts +12 -0
  28. package/dist/boot-construction.js +175 -0
  29. package/dist/boot-gate.d.ts +267 -0
  30. package/dist/boot-gate.js +236 -0
  31. package/dist/boot-host-script.d.ts +2 -0
  32. package/dist/boot-host-script.js +1 -0
  33. package/dist/boot-mount.d.ts +21 -0
  34. package/dist/boot-mount.js +65 -0
  35. package/dist/browser/boot.d.ts +73 -0
  36. package/dist/browser/boot.js +502 -0
  37. package/dist/browser/browser.d.ts +104 -0
  38. package/dist/browser/browser.js +374 -0
  39. package/dist/browser/capability-source.d.ts +76 -0
  40. package/dist/browser/capability-source.js +338 -0
  41. package/dist/browser/cdp-client.d.ts +56 -0
  42. package/dist/browser/cdp-client.js +184 -0
  43. package/dist/browser/errors.d.ts +79 -0
  44. package/dist/browser/errors.js +32 -0
  45. package/dist/browser/index.d.ts +16 -0
  46. package/dist/browser/index.js +14 -0
  47. package/dist/browser/lease-registry.d.ts +61 -0
  48. package/dist/browser/lease-registry.js +101 -0
  49. package/dist/browser/operations.d.ts +225 -0
  50. package/dist/browser/operations.js +137 -0
  51. package/dist/browser/page.d.ts +25 -0
  52. package/dist/browser/page.js +172 -0
  53. package/dist/browser/quick-reads.d.ts +29 -0
  54. package/dist/browser/quick-reads.js +160 -0
  55. package/dist/browser/sessions.d.ts +52 -0
  56. package/dist/browser/sessions.js +118 -0
  57. package/dist/browser/url-policy.d.ts +18 -0
  58. package/dist/browser/url-policy.js +58 -0
  59. package/dist/build/app-imports.d.ts +12 -0
  60. package/dist/build/app-imports.js +105 -0
  61. package/dist/build/build-policy.d.ts +5 -3
  62. package/dist/build/build-policy.js +5 -3
  63. package/dist/build/candidate-builder.d.ts +1 -1
  64. package/dist/build/candidate-cli.mjs +8428 -337
  65. package/dist/build/local-build.d.ts +2 -0
  66. package/dist/build/local-build.js +28 -6
  67. package/dist/build/snapshot-package-store.d.ts +1 -1
  68. package/dist/build/snapshot-package-store.js +7 -2
  69. package/dist/build/workspace-build.d.ts +92 -5
  70. package/dist/build/workspace-build.js +93 -19
  71. package/dist/build-egress.js +4 -4
  72. package/dist/build.d.ts +1 -1
  73. package/dist/build.js +1 -1
  74. package/dist/catalog-frame.js +1 -1
  75. package/dist/cloudflare.d.ts +13 -3
  76. package/dist/cloudflare.js +106 -8
  77. package/dist/frame-binding-wire-script.js +1 -1
  78. package/dist/frame-binding-wire.d.ts +1 -1
  79. package/dist/frame-binding-wire.js +5 -3
  80. package/dist/git-auth.d.ts +0 -2
  81. package/dist/git-auth.js +0 -2
  82. package/dist/git-journal.js +4 -4
  83. package/dist/git.d.ts +0 -1
  84. package/dist/git.js +0 -2
  85. package/dist/host.d.ts +39 -13
  86. package/dist/host.js +268 -142
  87. package/dist/index.d.ts +26 -15
  88. package/dist/index.js +40 -16
  89. package/dist/install-release-cli.d.ts +2 -0
  90. package/dist/install-release-cli.js +46 -0
  91. package/dist/installation-client.d.ts +43 -0
  92. package/dist/installation-client.js +178 -0
  93. package/dist/installation-execution.d.ts +11 -0
  94. package/dist/installation-execution.js +184 -0
  95. package/dist/installation-operations.d.ts +1437 -0
  96. package/dist/installation-operations.js +664 -0
  97. package/dist/installation-store-generation.d.ts +3 -0
  98. package/dist/installation-store-generation.js +16 -0
  99. package/dist/instance-id.d.ts +10 -0
  100. package/dist/instance-id.js +10 -0
  101. package/dist/instance-refusal.d.ts +11 -0
  102. package/dist/instance-refusal.js +21 -0
  103. package/dist/internal/delivery.js +4 -2
  104. package/dist/internal/host-gate.js +7 -1
  105. package/dist/internal/registry-installations.js +4 -4
  106. package/dist/keys.d.ts +5 -1
  107. package/dist/keys.js +10 -2
  108. package/dist/machine-link/authority.d.ts +89 -0
  109. package/dist/machine-link/authority.js +37 -0
  110. package/dist/machine-link/client.d.ts +66 -0
  111. package/dist/machine-link/client.js +94 -0
  112. package/dist/machine-link/delivery.d.ts +18 -0
  113. package/dist/machine-link/delivery.js +55 -0
  114. package/dist/machine-link/enrollment.d.ts +111 -0
  115. package/dist/machine-link/enrollment.js +286 -0
  116. package/dist/machine-link/fences.d.ts +37 -0
  117. package/dist/machine-link/fences.js +166 -0
  118. package/dist/machine-link/home.d.ts +20 -0
  119. package/dist/machine-link/home.js +24 -0
  120. package/dist/machine-link/host.d.ts +43 -0
  121. package/dist/machine-link/host.js +189 -0
  122. package/dist/machine-link/index.d.ts +15 -0
  123. package/dist/machine-link/index.js +15 -0
  124. package/dist/machine-link/link.d.ts +48 -0
  125. package/dist/machine-link/link.js +223 -0
  126. package/dist/machine-link/presence.d.ts +10 -0
  127. package/dist/machine-link/presence.js +25 -0
  128. package/dist/machine-link/workers.d.ts +6 -0
  129. package/dist/machine-link/workers.js +6 -0
  130. package/dist/mcp-owner.d.ts +2 -2
  131. package/dist/mcp-owner.js +41 -33
  132. package/dist/mcp.d.ts +15 -3
  133. package/dist/mcp.js +146 -25
  134. package/dist/pack.d.ts +14 -3
  135. package/dist/pack.js +44 -19
  136. package/dist/page/app-access.d.ts +7 -0
  137. package/dist/page/app-access.js +75 -0
  138. package/dist/page/boot-host.js +57 -0
  139. package/dist/page/capabilities.d.ts +3 -3
  140. package/dist/page/capabilities.js +16 -12
  141. package/dist/page/computer-handles.d.ts +3 -3
  142. package/dist/page/computer-target.d.ts +3 -0
  143. package/dist/page/computer-target.js +9 -0
  144. package/dist/page/mount.d.ts +23 -7
  145. package/dist/page/mount.js +152 -16
  146. package/dist/page-assets.d.ts +22 -0
  147. package/dist/page-assets.js +28 -0
  148. package/dist/page-script.d.ts +3 -0
  149. package/dist/page-script.js +5 -4
  150. package/dist/release.d.ts +13 -6
  151. package/dist/release.js +10 -7
  152. package/dist/root-data.d.ts +16 -6
  153. package/dist/root-data.js +2 -2
  154. package/dist/source/index.d.ts +1 -1
  155. package/dist/source/team-source.d.ts +20 -8
  156. package/dist/source/team-source.js +39 -10
  157. package/dist/source/template-seed.d.ts +2 -2
  158. package/dist/standalone/agent-mail.d.ts +24 -0
  159. package/dist/standalone/agent-mail.js +62 -0
  160. package/dist/standalone/agent-store.d.ts +9 -0
  161. package/dist/standalone/agent-store.js +30 -0
  162. package/dist/standalone/agents.d.ts +78 -12
  163. package/dist/standalone/agents.js +470 -59
  164. package/dist/standalone/app-builds-binding.d.ts +20 -2
  165. package/dist/standalone/app-builds-binding.js +151 -27
  166. package/dist/standalone/app-set.d.ts +5 -2
  167. package/dist/standalone/app-set.js +174 -143
  168. package/dist/standalone/browser.d.ts +11 -0
  169. package/dist/standalone/browser.js +72 -0
  170. package/dist/standalone/builds.d.ts +13 -4
  171. package/dist/standalone/builds.js +28 -44
  172. package/dist/standalone/computer-egress.d.ts +2 -0
  173. package/dist/standalone/computer-egress.js +13 -4
  174. package/dist/standalone/computer-fleet.d.ts +43 -0
  175. package/dist/standalone/computer-fleet.js +200 -0
  176. package/dist/standalone/computer.d.ts +44 -9
  177. package/dist/standalone/computer.js +132 -25
  178. package/dist/standalone/install-apps-cli.js +43 -6
  179. package/dist/standalone/instance.d.ts +2 -2
  180. package/dist/standalone/instance.js +8 -11
  181. package/dist/standalone/machine-link.d.ts +354 -0
  182. package/dist/standalone/machine-link.js +368 -0
  183. package/dist/standalone/media.d.ts +2 -2
  184. package/dist/standalone/media.js +19 -16
  185. package/dist/standalone/preview.d.ts +2 -2
  186. package/dist/standalone/preview.js +39 -12
  187. package/dist/standalone/registry.d.ts +149 -9
  188. package/dist/standalone/registry.js +299 -8
  189. package/dist/standalone/worker.d.ts +19 -5
  190. package/dist/standalone/worker.js +90 -54
  191. package/dist/standalone/workflow-binding.d.ts +28 -32
  192. package/dist/standalone/workflow-binding.js +20 -20
  193. package/dist/standalone/workflow-control.d.ts +4 -0
  194. package/dist/standalone/workflow-control.js +40 -0
  195. package/dist/standalone/workflow-events.d.ts +2 -2
  196. package/dist/standalone/workflow-events.js +9 -10
  197. package/dist/standalone/workflow-git-capture.js +4 -3
  198. package/dist/standalone/workflow-host.d.ts +177 -152
  199. package/dist/standalone/workflow-host.js +137 -30
  200. package/dist/standalone/workflow-node-capture.js +11 -4
  201. package/dist/standalone/workflow-schedules.d.ts +5 -2
  202. package/dist/standalone/workflow-schedules.js +9 -10
  203. package/dist/standalone/workflow-trigger-host.d.ts +88 -6
  204. package/dist/standalone/workflow-trigger-host.js +2 -2
  205. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  206. package/dist/standalone/workflow-webhooks.js +39 -0
  207. package/dist/standalone/workflows.d.ts +203 -50
  208. package/dist/standalone/workflows.js +198 -39
  209. package/dist/storage-frame.d.ts +6 -6
  210. package/dist/testing/capabilities.js +14 -3
  211. package/dist/testing/installation.js +26 -2
  212. package/dist/testing/worker.js +1 -1
  213. package/dist/testing/workers-runtime.d.ts +8 -2
  214. package/dist/testing/workers-runtime.js +20 -2
  215. package/dist/webhook-abuse.d.ts +25 -0
  216. package/dist/webhook-abuse.js +54 -0
  217. package/dist/webhook-custody.d.ts +144 -0
  218. package/dist/webhook-custody.js +222 -0
  219. package/dist/webhook-ingress.d.ts +36 -0
  220. package/dist/webhook-ingress.js +146 -0
  221. package/dist/webhooks.d.ts +111 -0
  222. package/dist/webhooks.js +189 -0
  223. package/dist/workers-git.d.ts +12 -17
  224. package/dist/workers-git.js +60 -55
  225. package/dist/workers.d.ts +224 -13
  226. package/dist/workers.js +351 -51
  227. package/dist/workflow-agent-authority.d.ts +8 -0
  228. package/dist/workflow-agent-authority.js +42 -0
  229. package/dist/workflows.d.ts +66 -0
  230. package/dist/workflows.js +97 -0
  231. package/docs/api/README.md +63 -37
  232. package/docs/api/agents.md +17 -0
  233. package/docs/api/app-storage.md +1 -1
  234. package/docs/api/apps.md +1394 -543
  235. package/docs/api/browser/boot.md +132 -0
  236. package/docs/api/browser.md +1440 -0
  237. package/docs/api/build-contract.md +38 -25
  238. package/docs/api/build.md +263 -84
  239. package/docs/api/cloudflare.md +93 -7
  240. package/docs/api/git.md +3 -13
  241. package/docs/api/host.md +944 -118
  242. package/docs/api/index.md +320 -18
  243. package/docs/api/keys.md +24 -1
  244. package/docs/api/mcp.md +59 -12
  245. package/docs/api/modules.md +33 -40
  246. package/docs/api/pack.md +57 -8
  247. package/docs/api/page/mount.md +92 -4
  248. package/docs/api/workers-git.md +103 -24
  249. package/docs/api/workers.md +1581 -244
  250. package/docs/site/agent-quickstart.mdx +28 -14
  251. package/examples/standalone/worker.ts +3 -1
  252. package/examples/standalone/wrangler.jsonc +2 -0
  253. package/package.json +73 -69
  254. package/dist/installation-consent.d.ts +0 -79
  255. package/dist/installation-consent.js +0 -82
  256. package/dist/shell/app-directory.d.ts +0 -542
  257. package/dist/shell/app-directory.js +0 -199
  258. package/dist/shell/browser-actions.d.ts +0 -7
  259. package/dist/shell/browser-actions.js +0 -56
  260. package/dist/shell/command-host.d.ts +0 -112
  261. package/dist/shell/command-host.js +0 -502
  262. package/dist/shell/commands.d.ts +0 -25
  263. package/dist/shell/commands.js +0 -78
  264. package/dist/shell/index.d.ts +0 -8
  265. package/dist/shell/index.js +0 -8
  266. package/dist/shell/keys.d.ts +0 -41
  267. package/dist/shell/keys.js +0 -134
  268. package/dist/shell/shell-module.d.ts +0 -55
  269. package/dist/shell/shell-module.js +0 -86
  270. package/dist/shell/window-manager.d.ts +0 -124
  271. package/dist/shell/window-manager.js +0 -711
  272. package/dist/shell/workbench-contract.d.ts +0 -68
  273. package/dist/shell/workbench-directory.d.ts +0 -332
  274. package/dist/shell/workbench-directory.js +0 -35
  275. package/dist/shell/workbench-host-types.d.ts +0 -96
  276. package/dist/shell/workbench-host-types.js +0 -1
  277. package/dist/shell/workbench-host.d.ts +0 -34
  278. package/dist/shell/workbench-host.js +0 -347
  279. package/dist/shell/workbench-model.d.ts +0 -192
  280. package/dist/shell/workbench-model.js +0 -53
  281. package/dist/shell/workbench.d.ts +0 -4
  282. package/dist/shell/workbench.js +0 -2
  283. package/docs/api/shell/app-directory.md +0 -599
  284. package/docs/api/shell/command-host.md +0 -224
  285. package/docs/api/shell/commands.md +0 -197
  286. package/docs/api/shell/keys.md +0 -183
  287. package/docs/api/shell/shell-module.md +0 -204
  288. package/docs/api/shell/window-manager.md +0 -267
  289. package/docs/api/shell/workbench-directory.md +0 -119
  290. package/docs/api/shell/workbench.md +0 -205
  291. package/docs/api/shell.md +0 -1915
  292. /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
@@ -0,0 +1,66 @@
1
+ import { seededAvatar } from "@fungi.computer/sprite/avatar";
2
+ import { avatarSchema } from "@fungi.computer/sprite-zdog/avatar";
3
+ import { avatarCatalog } from "@fungi.computer/sprite-zdog/catalog";
4
+ import { renderStyleSchema } from "@fungi.computer/sprite";
5
+ import { z } from "zod";
6
+ const storedAvatarRowSchema = z.strictObject({ avatar: z.string() });
7
+ /** The catalog-valid avatar an owner may choose for an Agent. */
8
+ export const agentAvatarSchema = avatarSchema;
9
+ const appearanceValue = z.union([z.string(), z.number(), z.boolean()]);
10
+ /** One character's avatar with its appearance closed to that character's controls. */
11
+ function closedCharacterAvatar(character) {
12
+ return z.strictObject({
13
+ v: z.literal(1),
14
+ character: z.literal(character),
15
+ appearance: z.strictObject(Object.fromEntries(Object.keys(avatarCatalog[character].controls).map((control) => [
16
+ control,
17
+ appearanceValue.optional(),
18
+ ]))),
19
+ expression: z.string().min(1),
20
+ renderStyle: renderStyleSchema,
21
+ });
22
+ }
23
+ /**
24
+ * The avatar as a closed record, for contracts that refuse open maps (Hub
25
+ * projections): each character's appearance names only its own controls,
26
+ * then the catalog refines it exactly as `agentAvatarSchema` does.
27
+ */
28
+ export const agentAvatarProjectionSchema = z
29
+ .discriminatedUnion("character", [
30
+ closedCharacterAvatar("mushroom"),
31
+ closedCharacterAvatar("cat"),
32
+ closedCharacterAvatar("burger"),
33
+ ])
34
+ .superRefine((avatar, context) => {
35
+ for (const issue of agentAvatarSchema.safeParse(avatar).error?.issues ?? [])
36
+ context.addIssue({
37
+ code: "custom",
38
+ path: issue.path,
39
+ message: issue.message,
40
+ });
41
+ });
42
+ /** A projected avatar as the Avatar descriptor Sprite draws. */
43
+ export function avatarFromProjection(projected) {
44
+ return agentAvatarSchema.parse(projected);
45
+ }
46
+ /** Open the chosen-avatar table in the Agent directory's own SQLite. */
47
+ export function createAgentAvatarStore(sql) {
48
+ sql.exec("CREATE TABLE IF NOT EXISTS agent_avatars (agent_id TEXT PRIMARY KEY, avatar TEXT NOT NULL) STRICT");
49
+ return {
50
+ read(agentId) {
51
+ const row = sql
52
+ .exec("SELECT avatar FROM agent_avatars WHERE agent_id = ?", agentId)
53
+ .toArray()[0];
54
+ if (row === undefined)
55
+ return seededAvatar(agentId);
56
+ const stored = storedAvatarRowSchema.parse(row);
57
+ return agentAvatarSchema.parse(JSON.parse(stored.avatar));
58
+ },
59
+ write(agentId, avatar) {
60
+ sql.exec("INSERT INTO agent_avatars(agent_id, avatar) VALUES(?, ?) ON CONFLICT(agent_id) DO UPDATE SET avatar = excluded.avatar", agentId, JSON.stringify(agentAvatarSchema.parse(avatar)));
61
+ },
62
+ remove(agentId) {
63
+ sql.exec("DELETE FROM agent_avatars WHERE agent_id = ?", agentId);
64
+ },
65
+ };
66
+ }
package/dist/agents.d.ts CHANGED
@@ -3,18 +3,22 @@
3
3
  * the App page calls. Apps reach agents through Agar's `agent.list`,
4
4
  * `agent.manage`, `agent.read` and `agent.send` contracts, so an App written
5
5
  * for the Hub (Grove) runs unchanged. Each agent's own transport is
6
- * Shiitake's HTTP routes (`/sessions…`, `/acp`), served by the adapter.
6
+ * Shiitake's HTTP routes (`/sessions…`, `/acp`) and Knapsack's Session browser
7
+ * transport (`/browser/…`), served by the adapter behind the same App grants.
7
8
  */
8
9
  import { Hono } from "hono";
9
10
  import type { AppToolPolicy } from "./apps/tools.js";
10
11
  import * as Schema from "effect/Schema";
11
12
  import { type OysterCapability, type OysterEnv, type OysterPrincipal } from "./host.js";
12
13
  import type { HostActor } from "@fungi.computer/puffball/app";
14
+ import type { Avatar } from "@fungi.computer/sprite/avatar";
13
15
  import type { MemberInboxItem, MemberInboxSession } from "@fungi.computer/puffball/inbox";
14
16
  /** One agent as Apps see it, in Agar's `appAgentSchema` shape. */
15
17
  export type OysterAgent = Readonly<{
16
18
  agentId: string;
17
19
  name: string;
20
+ /** The Agent's own avatar: the owner's choice, else its seeded default. */
21
+ avatar: Avatar;
18
22
  sessionOperations: readonly ("agent.read" | "agent.send" | "agent.manage")[];
19
23
  }>;
20
24
  /** An Agent owner's Session facts before the member identity and visit are added. */
@@ -70,12 +74,19 @@ export type AgentToolPolicyStatus = Readonly<{
70
74
  export type OysterAgents = Readonly<{
71
75
  /** Every agent, oldest first. */
72
76
  list(): Promise<readonly OysterAgent[]>;
73
- /** Create an agent; retrying with the same `requestId` returns the same one. */
74
- create(name: string, requestId: string, principal: OysterPrincipal, actor: AgentPolicyActor): Promise<OysterAgent>;
77
+ /**
78
+ * Create an agent; retrying with the same `requestId` returns the same one.
79
+ * With no `avatar` it wears its seeded default until the owner picks one.
80
+ */
81
+ create(name: string, requestId: string, principal: OysterPrincipal, actor: AgentPolicyActor, avatar?: Avatar): Promise<OysterAgent>;
75
82
  /** Owner-only rename; refuses a name already held by another Agent. */
76
83
  rename(agentId: string, name: string, principal: OysterPrincipal): Promise<OysterAgent | Readonly<{
77
84
  error: "not_found" | "name_taken" | "not_granted";
78
85
  }>>;
86
+ /** Owner-only avatar change for an existing Agent. */
87
+ setAvatar(agentId: string, avatar: Avatar, principal: OysterPrincipal): Promise<OysterAgent | Readonly<{
88
+ error: "not_found" | "not_granted";
89
+ }>>;
79
90
  /** This Agent's App-tool policy status, or `null` for no such Agent. */
80
91
  policyStatus(agentId: string): Promise<AgentToolPolicyStatus | null>;
81
92
  /** Read this Agent's stored App-tool approval; absence requires re-approval. A key's App access is its own grants (Ear Tag), not this policy. */
@@ -94,7 +105,7 @@ export type OysterAgents = Readonly<{
94
105
  addMcpServer(agentId: string, input: AgentMcpServerInput): Promise<AgentMcpServer | AgentMcpServerRefusal>;
95
106
  /** Disconnect one MCP server; `false` when the Agent or server is unknown. */
96
107
  removeMcpServer(agentId: string, serverId: string): Promise<boolean>;
97
- /** Serve one Shiitake transport request (`/sessions…`, `/acp`) for an agent. */
108
+ /** Serve this Agent's Shiitake and Knapsack Session browser transports. */
98
109
  fetch(agentId: string, request: Request): Promise<Response | null>;
99
110
  /** This member's Session and Workflow review rows, admitted by the host. */
100
111
  inbox(): Promise<OysterInbox>;
package/dist/agents.js CHANGED
@@ -3,12 +3,15 @@
3
3
  * the App page calls. Apps reach agents through Agar's `agent.list`,
4
4
  * `agent.manage`, `agent.read` and `agent.send` contracts, so an App written
5
5
  * for the Hub (Grove) runs unchanged. Each agent's own transport is
6
- * Shiitake's HTTP routes (`/sessions…`, `/acp`), served by the adapter.
6
+ * Shiitake's HTTP routes (`/sessions…`, `/acp`) and Knapsack's Session browser
7
+ * transport (`/browser/…`), served by the adapter behind the same App grants.
7
8
  */
8
9
  import { Hono } from "hono";
9
10
  import * as Schema from "effect/Schema";
11
+ import { z } from "zod";
10
12
  import { oysterPrincipalIsOwner, } from "./host.js";
11
- import { agentList, agentManage, agentRename, agentSessionRead, agentSessionWrite, agentToolPolicy, } from "@fungi.computer/agar/agents";
13
+ import { agentAvatarSchema } from "./agent-avatars.js";
14
+ import { agentAvatar, agentList, agentManage, agentRename, agentSessionRead, agentSessionWrite, agentToolPolicy, } from "@fungi.computer/agar/agents";
12
15
  /** At most this many MCP servers per Agent. */
13
16
  export const maxAgentMcpServers = 16;
14
17
  /** Owner input for one MCP server: a guest identifier, an `https:` endpoint and an optional bearer. */
@@ -30,10 +33,12 @@ export const agentMcpServerInputSchema = Schema.Struct({
30
33
  const createSchema = Schema.Struct({
31
34
  name: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)),
32
35
  requestId: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_.:-]{1,256}$/u)),
36
+ avatar: Schema.optionalKey(Schema.Unknown),
33
37
  });
34
38
  const renameSchema = Schema.Struct({
35
39
  name: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)),
36
40
  });
41
+ const avatarInputSchema = z.object({ avatar: agentAvatarSchema });
37
42
  const visitSchema = Schema.Struct({
38
43
  agentId: Schema.String,
39
44
  sessionId: Schema.String,
@@ -76,6 +81,19 @@ export function agentCapabilities(agents) {
76
81
  rename: { kind: "json", method: "PUT", path: "agent-names/:agentId" },
77
82
  },
78
83
  },
84
+ {
85
+ ...shared,
86
+ id: "agent.avatar",
87
+ permission: "agent.avatar",
88
+ version: agentAvatar.version,
89
+ methods: {
90
+ setAvatar: {
91
+ kind: "json",
92
+ method: "PUT",
93
+ path: "agent-avatars/:agentId",
94
+ },
95
+ },
96
+ },
79
97
  {
80
98
  ...shared,
81
99
  id: "agent.toolPolicy",
@@ -126,9 +144,14 @@ function createAgentRoutes(agents) {
126
144
  const body = Schema.decodeUnknownOption(createSchema)(await context.req.json());
127
145
  if (body._tag === "None")
128
146
  return refusal(400, "Name the agent");
147
+ const avatar = body.value.avatar === undefined
148
+ ? undefined
149
+ : agentAvatarSchema.safeParse(body.value.avatar);
150
+ if (avatar?.success === false)
151
+ return refusal(400, "Choose a valid avatar");
129
152
  // The owner's own authenticated request creates this Agent; its App tools
130
153
  // are approved the same way direct owner approval is, with no second path.
131
- return Response.json(await agents.create(body.value.name, body.value.requestId, principal, "owner-direct"));
154
+ return Response.json(await agents.create(body.value.name, body.value.requestId, principal, "owner-direct", avatar?.data));
132
155
  });
133
156
  routes.put("/agent-names/:agentId", async (context) => {
134
157
  const { principal } = context.env;
@@ -149,6 +172,21 @@ function createAgentRoutes(agents) {
149
172
  : 403, result.error);
150
173
  return Response.json(result);
151
174
  });
175
+ routes.put("/agent-avatars/:agentId", async (context) => {
176
+ const { principal } = context.env;
177
+ if (!oysterPrincipalIsOwner(principal))
178
+ return refusal(403, "Only the owner changes avatars");
179
+ const agentId = context.req.param("agentId");
180
+ if (!agentIdPattern.test(agentId))
181
+ return refusal(404, "No such agent");
182
+ const body = avatarInputSchema.safeParse(await context.req.json());
183
+ if (!body.success)
184
+ return refusal(400, "Choose a valid avatar");
185
+ const result = await agents.setAvatar(agentId, body.data.avatar, principal);
186
+ if ("error" in result)
187
+ return refusal(result.error === "not_found" ? 404 : 403, result.error);
188
+ return Response.json(result);
189
+ });
152
190
  routes.get("/agent-policies/:agentId", async (context) => {
153
191
  const agentId = context.req.param("agentId");
154
192
  if (!agentIdPattern.test(agentId))
@@ -168,7 +206,8 @@ function createAgentRoutes(agents) {
168
206
  const path = url.pathname.slice(`/agents/${agentId}`.length);
169
207
  if (!(path === "/acp" ||
170
208
  path === "/sessions" ||
171
- path.startsWith("/sessions/")))
209
+ path.startsWith("/sessions/") ||
210
+ path.startsWith("/browser/")))
172
211
  return refusal(404, "Not found");
173
212
  const length = Number(incoming.headers.get("content-length") ?? "0");
174
213
  if (length > maxAgentRequestBytes)
@@ -19,6 +19,7 @@
19
19
  import * as Schema from "effect/Schema";
20
20
  import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
21
21
  import { type Release } from "@fungi.computer/puffball";
22
+ import { type AppPublisherCoreConfigInput } from "./build/effect.js";
22
23
  import { type TeamAppCandidateManifest } from "./build/submission-contract.js";
23
24
  import { type ArtifactHeads, type ArtifactStore } from "./release.js";
24
25
  import type { PublisherTemplateSeedReceipts } from "./source/seed-receipt.js";
@@ -77,6 +78,19 @@ export declare const oysterBuildSourceSchema: Schema.Struct<{
77
78
  readonly buildRecipeDigest: Schema.refine<import("@fungi.computer/puffball").Sha256Digest, Schema.String>;
78
79
  }>;
79
80
  export type OysterBuildSource = Schema.Schema.Type<typeof oysterBuildSourceSchema>;
81
+ /** Installation is a build outcome, never a pending consent/approval record. */
82
+ export declare const oysterBuildInstallationSchema: Schema.Union<readonly [Schema.Struct<{
83
+ readonly kind: Schema.Literal<"pending">;
84
+ readonly installationId: Schema.String;
85
+ }>, Schema.Struct<{
86
+ readonly kind: Schema.Literals<readonly ["installed", "needs_consent"]>;
87
+ readonly installationId: Schema.String;
88
+ }>, Schema.Struct<{
89
+ readonly kind: Schema.Literal<"failed">;
90
+ readonly installationId: Schema.String;
91
+ readonly detail: Schema.String;
92
+ }>]>;
93
+ export type OysterBuildInstallation = Schema.Schema.Type<typeof oysterBuildInstallationSchema>;
80
94
  /** What an owner sees of one build. */
81
95
  export type OysterBuildView = Readonly<{
82
96
  jobId: string;
@@ -86,8 +100,42 @@ export type OysterBuildView = Readonly<{
86
100
  status: "running" | "succeeded" | "failed" | "revoked";
87
101
  detail: string | null;
88
102
  release: Release | null;
103
+ installation: OysterBuildInstallation | null;
104
+ }>;
105
+ /** Known preflight refusals, before a job or Computer run exists. */
106
+ export declare const oysterBuildStartReasonSchema: Schema.Literals<readonly ["invalid_revision", "no_source", "not_template_seed", "no_backend"]>;
107
+ export type OysterBuildStartReason = Schema.Schema.Type<typeof oysterBuildStartReasonSchema>;
108
+ export declare const oysterBuildStartMessages: {
109
+ invalid_revision: string;
110
+ no_source: string;
111
+ no_backend: string;
112
+ not_template_seed: string;
113
+ };
114
+ declare const OysterBuildStartError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
115
+ readonly _tag: "OysterBuildStartError";
116
+ } & Readonly<A>;
117
+ /** Local typed failure. The registry turns it into data before a DO RPC. */
118
+ export declare class OysterBuildStartError extends OysterBuildStartError_base<{
119
+ readonly reason: OysterBuildStartReason;
120
+ readonly message: string;
121
+ }> {
122
+ constructor(reason: OysterBuildStartReason);
123
+ }
124
+ export type OysterBuildStartResult = Readonly<{
125
+ kind: "started";
126
+ build: OysterBuildView;
127
+ }> | Readonly<{
128
+ kind: "refused";
129
+ reason: OysterBuildStartReason;
130
+ }>;
131
+ export type OysterBuildBackend = Readonly<{
132
+ kind: "hosted";
133
+ }> | Readonly<{
134
+ kind: "no_backend";
89
135
  }>;
90
136
  export type OysterBuildComputer = Readonly<{
137
+ /** Read the configured hosted route without starting a machine. Local machines never build Apps. */
138
+ buildBackend(): Promise<OysterBuildBackend>;
91
139
  /** Run the fixed build command; the Computer re-admits the job first.
92
140
  * A missing `output` means no terminal result was established: the
93
141
  * command may still be running, so it is never read as an exit. */
@@ -118,17 +166,28 @@ export type OysterAppBuildOptions = Readonly<{
118
166
  templates: Pick<PublisherTemplateSeedReceipts, "read">;
119
167
  now?: () => number;
120
168
  }>;
121
- /**
122
- * The SQLite tables one Oyster build owner keeps. Creation and any carrying
123
- * forward of retained facts run as one transaction of the owner's storage, so
124
- * an interrupted conversion leaves the facts as they were for a retry.
125
- */
169
+ /** Create the current build tables and index atomically; never convert old stores. */
126
170
  export declare function migrateOysterAppBuilds(owner: WatchdogSqliteOwner): void;
127
171
  /** The build owner over one tenant's SQLite, Computer and artifact store. */
128
172
  export declare function createOysterAppBuilds(options: OysterAppBuildOptions): Readonly<{
129
173
  /** Record (or replace) an App's build source; the caller is the owner. */
130
- registerSource(source: OysterBuildSource): OysterBuildSource;
174
+ registerSource: (source: OysterBuildSource) => OysterBuildSource;
175
+ /** Resolve repository identity and replace its current recipe as one transition.
176
+ * A fork's existing identity wins over the copied manifest's App id. */
177
+ registerRepositorySource(input: {
178
+ appId: string;
179
+ location: Extract<OysterBuildLocation, {
180
+ kind: "installation";
181
+ }>;
182
+ recipe: AppPublisherCoreConfigInput["recipe"];
183
+ }): {
184
+ kind: "registered";
185
+ source: OysterBuildSource;
186
+ } | {
187
+ kind: "conflict";
188
+ };
131
189
  readSource: (appId: string) => OysterBuildSource | null;
190
+ readRepositorySource: (installationId: string, repository: string) => OysterBuildSource | null;
132
191
  /**
133
192
  * Mint one build job for an App's registered source at one exact commit.
134
193
  * A template source builds only a seed revision recorded for its pinned
@@ -136,15 +195,28 @@ export declare function createOysterAppBuilds(options: OysterAppBuildOptions): R
136
195
  * Answers the job and its one run, which the caller keeps alive (the
137
196
  * upload grant exists only inside that run's fixed command).
138
197
  */
139
- start(input: {
198
+ start: (input: {
140
199
  appId: string;
141
200
  sourceRevision: string;
142
201
  requestedBy: string;
143
- }): Promise<Readonly<{
202
+ /** The Workflow's installation target. Absence leaves manual builds manual. */
203
+ installAt?: string;
204
+ /** One host-owned Workflow node reuses its job, including after restart. */
205
+ requestId?: string;
206
+ }) => Promise<Readonly<{
144
207
  build: OysterBuildView;
145
208
  run: () => Promise<OysterBuildView>;
146
209
  }>>;
147
210
  read(jobId: string): OysterBuildView | null;
211
+ /** Discover the current source's latest job from the caller's Git owner,
212
+ * never a job id remembered in a browser or another installation's repo. */
213
+ latestRepositoryBuild(installationId: string, repository: string): OysterBuildView | null;
214
+ /** One bounded host installation at a time. Only a verified successful
215
+ * build may reach the installer; the same existing row owns its outcome.
216
+ * A Workflow retry may resume a pending decision, never a terminal one. */
217
+ install(jobId: string, decide: (release: Release, installationId: string) => Promise<Exclude<OysterBuildInstallation, {
218
+ kind: "pending";
219
+ }>>): Promise<boolean>;
148
220
  /** A built Release by id, for an owner to install or select. */
149
221
  release(releaseId: string): Release | null;
150
222
  /** May this Computer execute this job now? Re-checked by the Computer. */
@@ -208,3 +280,4 @@ export declare function createOysterAppBuilds(options: OysterAppBuildOptions): R
208
280
  submitCandidate(jobId: string, manifest: TeamAppCandidateManifest, grant: string, credentialId: string): Promise<Response>;
209
281
  }>;
210
282
  export type OysterAppBuilds = ReturnType<typeof createOysterAppBuilds>;
283
+ export {};