@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
package/dist/mcp-owner.js CHANGED
@@ -2,7 +2,6 @@ import { DurableObject } from "cloudflare:workers";
2
2
  import { nanoid } from "@fungi.computer/cairn";
3
3
  import { createCodemodeSandbox } from "@fungi.computer/mycelium/workers";
4
4
  import * as Schema from "effect/Schema";
5
- import { oysterMcpCatalogLoad } from "./mcp.js";
6
5
  import { tenantIdSchema, tenantScope } from "./tenant.js";
7
6
  /**
8
7
  * The MCP protocol SDK builds hundreds of schemas when it loads. Only the first
@@ -10,11 +9,12 @@ import { tenantIdSchema, tenantScope } from "./tenant.js";
10
9
  * (which every request and every deploy pays) never does.
11
10
  */
12
11
  const loadMcpProtocol = async () => {
13
- const [mycelium, sdk] = await Promise.all([
12
+ const [mycelium, sdk, catalog] = await Promise.all([
14
13
  import("@fungi.computer/mycelium/mcp"),
15
14
  import("@modelcontextprotocol/server"),
15
+ import("./mcp.js"),
16
16
  ]);
17
- return { mycelium, sdk };
17
+ return { mycelium, sdk, catalog };
18
18
  };
19
19
  const pinSchema = Schema.Struct({
20
20
  tenant_id: tenantIdSchema,
@@ -97,39 +97,47 @@ export class OysterMcpOwner extends DurableObject {
97
97
  else {
98
98
  sql.exec("INSERT INTO oyster_mcp_tenant (id, tenant_id, origin) VALUES (1,?,?)", scope.tenantId, scope.origin);
99
99
  }
100
- // Loaded before the check so the session map is still created exactly once.
101
- const protocol = await loadMcpProtocol();
102
100
  if (this.#mcp === undefined) {
103
- const host = this.#createHost(this.env, scope);
104
- const persistence = {
105
- eventStore: (id) => this.#eventStore(id, protocol),
106
- initialized: (id, session) => {
107
- sql.exec("INSERT INTO oyster_mcp_sessions (protocol_id, catalog_id, principal) VALUES (?,?,?)", id, session.id, JSON.stringify(session.principal));
108
- return Promise.resolve();
109
- },
110
- closed: (id, session) => {
111
- this.ctx.storage.transactionSync(() => {
112
- sql.exec("DELETE FROM oyster_mcp_events WHERE catalog_id=?", session.id);
113
- sql.exec("DELETE FROM oyster_mcp_sessions WHERE protocol_id=?", id);
114
- });
115
- return Promise.resolve();
116
- },
117
- };
118
- this.#mcp = protocol.mycelium.createMcp({
119
- persistence,
120
- catalog: protocol.mycelium.createMcpCatalog({
121
- load: oysterMcpCatalogLoad(host),
122
- sandbox: createCodemodeSandbox(this.env.LOADER),
123
- }),
124
- authenticate: async (incoming) => {
125
- const admission = await host.auth.authenticate(incoming);
126
- return admission.kind === "refused"
127
- ? admission
128
- : { kind: "admitted", principal: admission.principal };
129
- },
101
+ // Publish the flight before any await: live SDK sessions belong to one owner.
102
+ this.#mcp = loadMcpProtocol()
103
+ .then(async (protocol) => {
104
+ const host = await this.#createHost(this.env, scope);
105
+ const persistence = {
106
+ eventStore: (id) => this.#eventStore(id, protocol),
107
+ initialized: (id, session) => {
108
+ sql.exec("INSERT INTO oyster_mcp_sessions (protocol_id, catalog_id, principal) VALUES (?,?,?)", id, session.id, JSON.stringify(session.principal));
109
+ return Promise.resolve();
110
+ },
111
+ closed: (id, session) => {
112
+ this.ctx.storage.transactionSync(() => {
113
+ sql.exec("DELETE FROM oyster_mcp_events WHERE catalog_id=?", session.id);
114
+ sql.exec("DELETE FROM oyster_mcp_sessions WHERE protocol_id=?", id);
115
+ });
116
+ return Promise.resolve();
117
+ },
118
+ };
119
+ return protocol.mycelium.createMcp({
120
+ persistence,
121
+ catalog: protocol.mycelium.createMcpCatalog({
122
+ // Each acquisition captures the current durable selection. Existing
123
+ // operation handles retain the Computer captured by their own catalog.
124
+ load: async (principal) => protocol.catalog.oysterMcpCatalogLoad(await this.#createHost(this.env, scope))(principal),
125
+ sandbox: createCodemodeSandbox(this.env.LOADER),
126
+ }),
127
+ authenticate: async (incoming) => {
128
+ const admission = await host.auth.authenticate(incoming);
129
+ return admission.kind === "refused"
130
+ ? admission
131
+ : { kind: "admitted", principal: admission.principal };
132
+ },
133
+ });
134
+ })
135
+ .catch((error) => {
136
+ this.#mcp = undefined;
137
+ throw error;
130
138
  });
131
139
  }
132
- return this.#mcp.handleRequest(request);
140
+ return (await this.#mcp).handleRequest(request);
133
141
  }
134
142
  }
135
143
  /** Bind the owner to the deployment's existing host factory and options. */
package/dist/mcp.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type AppToolPolicy } from "./apps/tools.js";
2
2
  import type { DeclaredOperations, Installation } from "./apps/installation.js";
3
- import { type CapabilitySource, type Grants } from "@fungi.computer/mycelium";
3
+ import { type CapabilitySource, type Grants, type ModuleRegistration } from "@fungi.computer/mycelium";
4
4
  import { appToolSchemas } from "@fungi.computer/forage";
5
5
  import type { OysterHost, OysterPrincipal } from "./host.js";
6
6
  import type { McpPrincipal, McpCatalogSnapshot } from "@fungi.computer/mycelium/mcp";
@@ -52,6 +52,18 @@ export type ReleaseSurface = Readonly<{
52
52
  export type AppSurfaceCache = (release: Installation["release"]) => Promise<ReleaseSurface | null>;
53
53
  /** Make one {@link AppSurfaceCache} for a host whose artifact store and capabilities stay fixed. */
54
54
  export declare function appSurfaceCache(host: Pick<OysterHost, "artifacts" | "capabilities">): AppSurfaceCache;
55
+ /** Derive the current principal's admitted App modules and grants. */
56
+ export declare function oysterAppSnapshot(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "capabilities" | "appToolPolicy" | "keys">, surfaces: AppSurfaceCache, principal: OysterPrincipal, installations: readonly Installation[], access: OysterCallerAccess, sessionId?: string,
57
+ /** Shares one tool-family listing per Instance across the two
58
+ * `appModules` passes one `oysterAppMethods` call can make (the eager
59
+ * grants/version pass, and a session-scoped re-derivation); never shared
60
+ * across separate calls, so it never serves another request's listing. */
61
+ familyListingCache?: Map<string, unknown>): Promise<Readonly<{
62
+ modules: Readonly<Record<string, ModuleRegistration>>;
63
+ levels: Readonly<Record<string, AppToolPolicy["level"]>>;
64
+ fences: Readonly<Record<string, string>>;
65
+ effects: Readonly<Record<string, Readonly<Record<string, "read" | "write" | "external">>>>;
66
+ }>>;
55
67
  /**
56
68
  * Capture installed App methods for MCP or agents as one principal-scoped source.
57
69
  * Enabled installations become `env.<APP>` modules using the Instance call path; a dotted method such as `repos.list` is `env.<APP>.repos.list()`.
@@ -62,7 +74,7 @@ export declare function appSurfaceCache(host: Pick<OysterHost, "artifacts" | "ca
62
74
  * (`mycelium/src/access.ts`). Call again to capture changes to Releases,
63
75
  * installation authority or the caller's own grants.
64
76
  */
65
- export declare function oysterAppMethods(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "capabilities" | "appToolPolicy" | "keys">, principal: OysterPrincipal, access: OysterCallerAccess, surfaces: AppSurfaceCache, preloadedInstallations?: readonly Installation[]): Promise<CapabilitySource & {
77
+ export declare function oysterAppMethods(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "capabilities" | "appToolPolicy" | "keys" | "approvals">, principal: OysterPrincipal, access: OysterCallerAccess, surfaces: AppSurfaceCache, preloadedInstallations?: readonly Installation[]): Promise<CapabilitySource & {
66
78
  readonly grants: Grants;
67
79
  }>;
68
80
  /**
@@ -86,4 +98,4 @@ export declare function oysterComputerTools(host: Pick<OysterHost, "computerId"
86
98
  * still re-admits the key, rechecks that same stored approval, and passes
87
99
  * the App's gate.
88
100
  */
89
- export declare function oysterMcpCatalogLoad(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "computer" | "capabilities" | "appToolPolicy" | "keys">): (principal: McpPrincipal) => Promise<McpCatalogSnapshot>;
101
+ export declare function oysterMcpCatalogLoad(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "computer" | "capabilities" | "appToolPolicy" | "keys" | "workflows">): (principal: McpPrincipal) => Promise<McpCatalogSnapshot>;
package/dist/mcp.js CHANGED
@@ -1,4 +1,9 @@
1
+ import { z } from "zod";
2
+ import { describeModule, invokeModuleOperation, } from "@fungi.computer/mycelium/remote";
3
+ import { PendingApprovalSchema, ApprovalRefusalSchema, } from "@fungi.computer/approvals";
4
+ import { jsonSchemaToZod } from "@fungi.computer/forage";
1
5
  import { admits } from "./apps/gate.js";
6
+ import { createMcpWorkflowControl, WORKFLOW_CONTROL_MODULE_ID, } from "./workflows.js";
2
7
  import { causeClass } from "./cause-class.js";
3
8
  import { deriveDeclaredOperations } from "./apps/operations.js";
4
9
  import { appToolPolicySchema } from "./apps/tools.js";
@@ -77,18 +82,22 @@ export function appSurfaceCache(host) {
77
82
  return entry;
78
83
  };
79
84
  }
80
- async function appModules(host, surfaces, principal, installations, access, sessionId,
85
+ /** Derive the current principal's admitted App modules and grants. */
86
+ export async function oysterAppSnapshot(host, surfaces, principal, installations, access, sessionId,
81
87
  /** Shares one tool-family listing per Instance across the two
82
88
  * `appModules` passes one `oysterAppMethods` call can make (the eager
83
89
  * grants/version pass, and a session-scoped re-derivation); never shared
84
90
  * across separate calls, so it never serves another request's listing. */
85
91
  familyListingCache = new Map()) {
86
92
  const limits = guestLimits(host.guestLimits);
93
+ const generation = await host.installations.generation();
87
94
  const caller = invocationCallerContext(principal, host.computerId ?? null);
88
95
  const { actor } = principal;
89
96
  const visitor = actor.kind === "visitor";
90
97
  const modules = {};
91
98
  const levels = {};
99
+ const fences = {};
100
+ const effects = {};
92
101
  for (const installation of installations) {
93
102
  if (installation.status !== "enabled" || installation.instanceId === null)
94
103
  continue;
@@ -125,8 +134,8 @@ familyListingCache = new Map()) {
125
134
  signal?.throwIfAborted();
126
135
  if (currentInstallation?.status !== "enabled" ||
127
136
  currentInstallation.instanceId !== instanceId ||
128
- currentInstallation.authorityVersion !==
129
- installation.authorityVersion ||
137
+ currentInstallation.authority.authorityVersion !==
138
+ installation.authority.authorityVersion ||
130
139
  currentInstallation.release.releaseId !==
131
140
  installation.release.releaseId ||
132
141
  currentInstallation.release.artifact.artifactDigest !==
@@ -156,7 +165,13 @@ familyListingCache = new Map()) {
156
165
  signal?.throwIfAborted();
157
166
  const json = Schema.decodeUnknownSync(Schema.Json)(input);
158
167
  const decision = admits({
159
- installation,
168
+ installation: {
169
+ status: installation.status,
170
+ visibility: installation.visibility,
171
+ instanceId: installation.instanceId,
172
+ authority: installation.authority,
173
+ artifactDigest: installation.release.artifact.artifactDigest,
174
+ },
160
175
  declaration,
161
176
  declaredOperations,
162
177
  api,
@@ -187,9 +202,13 @@ familyListingCache = new Map()) {
187
202
  throw new AppMethodRefused(result.value.code, result.value.message);
188
203
  return result.value.value;
189
204
  };
205
+ const operationEffects = {};
206
+ effects[id] = operationEffects;
207
+ let familyFacts = null;
190
208
  const toolOperations = Object.entries(toolSchemas)
191
209
  .filter(([, method]) => !visitor || method.effect === "read")
192
210
  .map(([name, method]) => {
211
+ operationEffects[name] = method.effect;
193
212
  const appMethod = Mycelium.operation({
194
213
  replay: "unsafe",
195
214
  access: method.effect === "read" ? "read" : "write",
@@ -225,7 +244,10 @@ familyListingCache = new Map()) {
225
244
  const compiled = appToolFamilySchemas(api, listing);
226
245
  if (compiled === null)
227
246
  return [];
247
+ familyFacts = Schema.decodeUnknownSync(Schema.Json)(listing);
228
248
  return compiled.tools.map((tool) => {
249
+ operationEffects[compiled.name + "." + tool.row.tool] =
250
+ compiled.effect;
229
251
  const operation = Mycelium.operation({
230
252
  replay: "unsafe",
231
253
  access: compiled.effect === "read" ? "read" : "write",
@@ -274,6 +296,15 @@ familyListingCache = new Map()) {
274
296
  return [];
275
297
  }
276
298
  })();
299
+ fences[id] = await sha256(new TextEncoder().encode(JSON.stringify({
300
+ installation,
301
+ generation,
302
+ principal,
303
+ access: captured,
304
+ api,
305
+ family: familyFacts,
306
+ computerId: host.computerId ?? null,
307
+ })));
277
308
  modules[id] = Mycelium.module({
278
309
  id,
279
310
  name: appBindingName(installation.installationId),
@@ -281,7 +312,7 @@ familyListingCache = new Map()) {
281
312
  operations: Object.fromEntries([...toolOperations, ...familyOperations]),
282
313
  });
283
314
  }
284
- return { modules, levels };
315
+ return { modules, levels, fences, effects };
285
316
  }
286
317
  function serializableAccess(access) {
287
318
  return access === null || access.kind === "agent"
@@ -307,8 +338,8 @@ export async function oysterAppMethods(host, principal, access, surfaces, preloa
307
338
  // Shared so a session-scoped re-derivation below reuses this call's tool-
308
339
  // family listings instead of asking each Instance for them twice.
309
340
  const familyListingCache = new Map();
310
- const { modules: entries, levels } = await appModules(host, surfaces, principal, installations, access, undefined, familyListingCache);
311
- const modules = Object.values(entries);
341
+ const initial = await oysterAppSnapshot(host, surfaces, principal, installations, access, undefined, familyListingCache);
342
+ const modules = admittedAppModules(host, principal, initial, null);
312
343
  const version = await sha256(new TextEncoder().encode(JSON.stringify({
313
344
  access: serializableAccess(access),
314
345
  installations: [...installations]
@@ -318,19 +349,19 @@ export async function oysterAppMethods(host, principal, access, surfaces, preloa
318
349
  installation.instanceId,
319
350
  installation.release.releaseId,
320
351
  installation.release.artifact.artifactDigest,
321
- installation.authorityVersion,
352
+ installation.authority.authorityVersion,
322
353
  installation.status,
323
354
  installation.visibility,
324
355
  ]),
325
356
  })));
326
357
  return {
327
358
  id: "oyster-apps",
328
- grants: levels,
359
+ grants: initial.levels,
329
360
  acquire: async ({ signal, session }) => {
330
361
  signal.throwIfAborted();
331
362
  const acquiredModules = session === undefined
332
363
  ? modules
333
- : Object.values((await appModules(host, surfaces, principal, installations, access, session.sessionId, familyListingCache)).modules);
364
+ : admittedAppModules(host, principal, await oysterAppSnapshot(host, surfaces, principal, installations, access, session.sessionId, familyListingCache), session.sessionId);
334
365
  signal.throwIfAborted();
335
366
  return {
336
367
  snapshot: { version, modules: acquiredModules },
@@ -402,28 +433,118 @@ export function oysterMcpCatalogLoad(host) {
402
433
  access = { kind: "key", keyId: actor.keyId, byInstallation };
403
434
  }
404
435
  const apps = await oysterAppMethods(host, principal, access, surfaces, installations);
405
- // Pre-approval ("never asked") is what a captured Agent/key policy
406
- // itself grants; an owner's own calls, or an Agent/key with none, are
407
- // never pre-approved even though their modules still default to the
408
- // coarse "write" ceiling above.
409
- const approvedWrites = access === null
410
- ? []
411
- : Object.entries(apps.grants)
412
- .filter(([, level]) => level === "write")
413
- .map(([id]) => id);
414
436
  const computer = await oysterComputerTools(host, principal);
437
+ const control = await host.workflows?.(principal);
438
+ const workflowSources = control == null
439
+ ? []
440
+ : [
441
+ {
442
+ id: "oyster-workflows",
443
+ acquire: () => Promise.resolve({
444
+ snapshot: {
445
+ version: WORKFLOW_CONTROL_MODULE_ID,
446
+ modules: [createMcpWorkflowControl(control).module],
447
+ },
448
+ release: () => Promise.resolve(),
449
+ }),
450
+ },
451
+ ];
452
+ const workflowGrants = control == null ? {} : { [WORKFLOW_CONTROL_MODULE_ID]: "write" };
415
453
  if (computer === null)
416
454
  return {
417
455
  instructions: "Installed Apps on this Oyster. Each App is env.<APP>; dotted method names are nested properties, e.g. env.BONSAI.repos.list({}). Use forage.search / forage.describe to find methods.",
418
- grants: apps.grants,
419
- approvedWrites,
420
- sources: [apps],
456
+ grants: { ...apps.grants, ...workflowGrants },
457
+ sources: [apps, ...workflowSources],
421
458
  };
422
459
  return {
423
460
  instructions: 'Installed Apps and this Oyster\'s Computer. Each App is env.<APP>; dotted method names are nested properties, e.g. env.BONSAI.repos.list({}). The Computer is env.computers.workspace, e.g. env.computers.workspace.exec({command: "ls"}). Use forage.search / forage.describe to find methods.',
424
- grants: { ...apps.grants, ...computer.grants },
425
- approvedWrites,
426
- sources: [apps, computer],
461
+ grants: { ...apps.grants, ...computer.grants, ...workflowGrants },
462
+ sources: [apps, computer, ...workflowSources],
427
463
  };
428
464
  };
429
465
  }
466
+ function admittedAppModules(host, principal, snapshot, sessionId) {
467
+ return Object.entries(snapshot.modules).map(([id, raw]) => {
468
+ const { description: moduleDescription, ...descriptor } = describeModule(raw);
469
+ const installationId = id.slice("oyster-app:".length);
470
+ const moduleMetadata = {};
471
+ if (moduleDescription !== undefined)
472
+ moduleMetadata.description = moduleDescription;
473
+ return Mycelium.module({
474
+ ...descriptor,
475
+ ...moduleMetadata,
476
+ id,
477
+ name: appBindingName(installationId),
478
+ operations: Object.fromEntries(Object.entries(descriptor.operations).map(([path, rawOperation]) => {
479
+ const { description: operationDescription, ...operation } = rawOperation;
480
+ const effect = snapshot.effects[id]?.[path];
481
+ const fence = snapshot.fences[id];
482
+ if (effect === undefined || fence === undefined)
483
+ throw new Error("App snapshot has no operation authority facts");
484
+ const declaredOutput = operation.output ?? true;
485
+ // Decode each document at its own root before composing: a nested
486
+ // JSON Schema document's root-relative $refs would otherwise escape.
487
+ const declared = jsonSchemaToZod(declaredOutput);
488
+ const output = effect === "read"
489
+ ? declared
490
+ : z.union([
491
+ declared,
492
+ jsonSchemaToZod(Schema.toStandardJSONSchemaV1(PendingApprovalSchema)["~standard"].jsonSchema.output({ target: "draft-2020-12" })),
493
+ jsonSchemaToZod(Schema.toStandardJSONSchemaV1(ApprovalRefusalSchema)["~standard"].jsonSchema.output({ target: "draft-2020-12" })),
494
+ ]);
495
+ const outputValidator = z.intersection(output, z.json());
496
+ const operationMetadata = {};
497
+ if (operationDescription !== undefined)
498
+ operationMetadata.description = operationDescription;
499
+ // App methods are never replayed by the host: a write goes through
500
+ // its approval record, and even a read may have App-side effects.
501
+ const appOperation = Mycelium.operation({
502
+ ...operation,
503
+ ...operationMetadata,
504
+ replay: "unsafe",
505
+ access: effect === "read" ? "read" : "write",
506
+ input: z.intersection(jsonSchemaToZod(operation.input), z.json()),
507
+ output: {
508
+ "~standard": {
509
+ ...outputValidator["~standard"],
510
+ // As appMethodSchemas does, describe the JSON contract
511
+ // without nesting another redundant recursive JSON guard.
512
+ jsonSchema: output["~standard"].jsonSchema,
513
+ },
514
+ },
515
+ execute: async (input, context) => {
516
+ if (effect === "read")
517
+ return z
518
+ .json()
519
+ .parse(await invokeModuleOperation(raw, path, input, context));
520
+ if (principal.actor.kind === "visitor")
521
+ return { kind: "refused", reason: "not-granted" };
522
+ if (host.approvals === undefined)
523
+ return { kind: "refused", reason: "not-granted" };
524
+ // Mycelium numbers each operation call within one execute and
525
+ // replays the same sequence; a direct call is its own execution.
526
+ const ordinal = context.operationOrdinal ?? 0;
527
+ const result = await host.approvals.submit({
528
+ actor: principal.actor,
529
+ installationId,
530
+ sessionId,
531
+ operation: path,
532
+ access: effect,
533
+ authorityFence: fence,
534
+ idempotencyKey: await sha256(new TextEncoder().encode(JSON.stringify([
535
+ context.executionId,
536
+ context.toolCallId,
537
+ ordinal,
538
+ ]))),
539
+ input,
540
+ });
541
+ return z
542
+ .json()
543
+ .parse(result.kind === "applied" ? result.output : result);
544
+ },
545
+ });
546
+ return [path, appOperation];
547
+ })),
548
+ });
549
+ });
550
+ }
package/dist/pack.d.ts CHANGED
@@ -1,5 +1,8 @@
1
+ import { type InstallationObservationBudget } from "./installation-client.js";
1
2
  import { type ArtifactRef, type Release } from "@fungi.computer/puffball";
2
3
  import { type AppPublisherCoreConfigInput } from "./build.js";
4
+ import type { InstallationOperation } from "./installation-operations.js";
5
+ export { InstallationObservationError } from "./installation-client.js";
3
6
  /** One committed repository and its canonical App build recipe. */
4
7
  export type PackOptions = Readonly<{
5
8
  /** The App's ID, `app_<name>`. */
@@ -32,6 +35,8 @@ export declare function packRelease(options: PackOptions): Promise<PackedRelease
32
35
  * Capture an Artifacts Git commit, build its Release, upload it to an Oyster,
33
36
  * and install it, or switch an existing installation to it
34
37
  * ({@link installRelease}).
38
+ * `waitMs` starts with the upload/observation phase after packing; a supplied
39
+ * caller signal retains its original lifetime through that phase.
35
40
  */
36
41
  export declare function installFromSource(input: Readonly<{
37
42
  appId: string;
@@ -49,11 +54,15 @@ export declare function installFromSource(input: Readonly<{
49
54
  /** Destination Oyster owner key; distinct from the source Git credential. */
50
55
  key: string;
51
56
  installationId: string;
52
- }>): Promise<Response>;
57
+ }> & InstallationObservationBudget): Promise<Response>;
53
58
  /**
54
59
  * Upload a packed Release to an Oyster and install it. The host installs a
55
60
  * new installation (201) or switches an existing one to this Release (200),
56
- * keeping its state; both go through its one install-or-select owner.
61
+ * keeping its state. Uploads precede admission; this helper then waits for the
62
+ * correlated terminal owner receipt, not merely the HTTP acknowledgement.
63
+ * Uploads and observation share the caller signal or explicit `waitMs`; absent
64
+ * either, the installer owns a twenty-minute default. Expiry does not cancel
65
+ * admitted work and is reported as an unconfirmed observation, not refusal.
57
66
  */
58
67
  export declare function installRelease(input: Readonly<{
59
68
  directory: string;
@@ -61,7 +70,9 @@ export declare function installRelease(input: Readonly<{
61
70
  /** The Oyster owner key. */
62
71
  key: string;
63
72
  installationId?: string;
64
- }>): Promise<Response>;
73
+ /** Report owner-correlated admission without shortening the completion wait. */
74
+ onAdmitted?: (operation: InstallationOperation) => void;
75
+ }> & InstallationObservationBudget): Promise<Response>;
65
76
  /** Project one exact first-party commit's editable source, `git push` it to
66
77
  * an Oyster's own template for that App, and claim the pushed head, so
67
78
  * `APPS.fork` and Computer builds can later read it. The operator owner key
package/dist/pack.js CHANGED
@@ -4,11 +4,13 @@ import { join, relative, resolve } from "node:path";
4
4
  import * as Effect from "effect/Effect";
5
5
  import * as Schema from "effect/Schema";
6
6
  import { releaseSchema } from "@fungi.computer/puffball/effect";
7
+ import { installOnOyster, installationObservationSignal, completedInstallationResponse, } from "./installation-client.js";
7
8
  import { parseArtifactRef, parseReleaseId, parseSha256Digest, parseSourceRevision, } from "@fungi.computer/puffball";
8
9
  import { commitPreparedCandidate, createAppCandidatePreparer, createArtifactsSourceCapture, createFirstPartyAppSourceDistribution, createLocalArtifactWriter, createLocalBuildExecutor, createLocalCommitSourceCapture, normalizeAppPublisherRecipe, } from "./build.js";
9
10
  import { templateSeedClaimSchema, } from "./source/template-seed.js";
10
11
  import { pushTemplateSource } from "./source/template-push.js";
11
12
  import { sha256 } from "./release.js";
13
+ export { InstallationObservationError } from "./installation-client.js";
12
14
  async function filesUnder(directory) {
13
15
  const entries = await readdir(directory, {
14
16
  recursive: true,
@@ -26,9 +28,9 @@ async function packCapturedSource(options, sourceCapture) {
26
28
  const preparer = createAppCandidatePreparer({
27
29
  config: { appId: options.appId, recipe: options.recipe },
28
30
  sourceCapture,
29
- buildExecutor: createLocalBuildExecutor({
30
- packageStoreDirectory: options.packageStoreDirectory,
31
- }),
31
+ buildExecutor: createLocalBuildExecutor(options.packageStoreDirectory === undefined
32
+ ? {}
33
+ : { packageStoreDirectory: options.packageStoreDirectory }),
32
34
  });
33
35
  const prepared = await Effect.runPromise(Effect.scoped(preparer.prepareCandidate(parseReleaseId(options.releaseId ??
34
36
  `rel_${revision.slice("git:".length, "git:".length + 12)}`))));
@@ -52,27 +54,40 @@ export async function packRelease(options) {
52
54
  * Capture an Artifacts Git commit, build its Release, upload it to an Oyster,
53
55
  * and install it, or switch an existing installation to it
54
56
  * ({@link installRelease}).
57
+ * `waitMs` starts with the upload/observation phase after packing; a supplied
58
+ * caller signal retains its original lifetime through that phase.
55
59
  */
56
60
  export async function installFromSource(input) {
57
61
  const outDirectory = await mkdtemp(join(tmpdir(), "oyster-source-release-"));
58
62
  try {
59
- await packCapturedSource({
63
+ let options = {
60
64
  appId: input.appId,
61
- releaseId: input.releaseId,
62
65
  recipe: input.recipe,
63
66
  sourceRevision: input.source.sourceRevision,
64
- packageStoreDirectory: input.packageStoreDirectory,
65
67
  outDirectory,
66
- }, createArtifactsSourceCapture({
68
+ };
69
+ if (input.releaseId !== undefined)
70
+ options = { ...options, releaseId: input.releaseId };
71
+ if (input.packageStoreDirectory !== undefined)
72
+ options = {
73
+ ...options,
74
+ packageStoreDirectory: input.packageStoreDirectory,
75
+ };
76
+ await packCapturedSource(options, createArtifactsSourceCapture({
67
77
  ...input.source,
68
78
  sourceRevision: parseSourceRevision(input.source.sourceRevision),
69
79
  }));
70
- return await installRelease({
80
+ let installation = {
71
81
  directory: outDirectory,
72
82
  url: input.url,
73
83
  key: input.key,
74
84
  installationId: input.installationId,
75
- });
85
+ };
86
+ if (input.waitMs !== undefined)
87
+ installation = { ...installation, waitMs: input.waitMs };
88
+ if (input.signal !== undefined)
89
+ installation = { ...installation, signal: input.signal };
90
+ return await installRelease(installation);
76
91
  }
77
92
  finally {
78
93
  await rm(outDirectory, { recursive: true, force: true });
@@ -81,9 +96,16 @@ export async function installFromSource(input) {
81
96
  /**
82
97
  * Upload a packed Release to an Oyster and install it. The host installs a
83
98
  * new installation (201) or switches an existing one to this Release (200),
84
- * keeping its state; both go through its one install-or-select owner.
99
+ * keeping its state. Uploads precede admission; this helper then waits for the
100
+ * correlated terminal owner receipt, not merely the HTTP acknowledgement.
101
+ * Uploads and observation share the caller signal or explicit `waitMs`; absent
102
+ * either, the installer owns a twenty-minute default. Expiry does not cancel
103
+ * admitted work and is reported as an unconfirmed observation, not refusal.
85
104
  */
86
105
  export async function installRelease(input) {
106
+ // One installer budget for uploads + observation, not an App authority grant.
107
+ const signal = installationObservationSignal(input);
108
+ signal.throwIfAborted();
87
109
  const directory = resolve(input.directory);
88
110
  const release = Schema.decodeUnknownSync(Schema.fromJsonString(releaseSchema))(await readFile(join(directory, "release.json"), "utf8"));
89
111
  const authorization = { Authorization: `Bearer ${input.key}` };
@@ -91,18 +113,21 @@ export async function installRelease(input) {
91
113
  const bytes = new Uint8Array(await readFile(file));
92
114
  const artifactRef = `artifact:${relative(join(directory, "artifacts"), file)}`;
93
115
  const digest = parseSha256Digest(await sha256(bytes));
94
- const response = await fetch(new URL(`/api/artifacts/${encodeURIComponent(artifactRef)}?digest=${digest}`, input.url), { method: "PUT", headers: authorization, body: bytes });
116
+ const response = await fetch(new URL(`/api/artifacts/${encodeURIComponent(artifactRef)}?digest=${digest}`, input.url), { method: "PUT", headers: authorization, body: bytes, signal });
95
117
  if (!response.ok)
96
118
  throw new Error(`Artifact upload failed (${String(response.status)}): ${artifactRef}`);
97
119
  }
98
- return fetch(new URL("/api/installations", input.url), {
99
- method: "POST",
100
- headers: { ...authorization, "Content-Type": "application/json" },
101
- body: JSON.stringify({
102
- release: Schema.encodeUnknownSync(releaseSchema)(release),
103
- installationId: input.installationId,
104
- }),
105
- });
120
+ let installation = {
121
+ url: input.url,
122
+ key: input.key,
123
+ release,
124
+ installationId: input.installationId ?? release.appId.replaceAll("_", "-"),
125
+ signal,
126
+ };
127
+ if (input.onAdmitted !== undefined)
128
+ installation = { ...installation, onAdmitted: input.onAdmitted };
129
+ const operation = await installOnOyster(installation);
130
+ return completedInstallationResponse(operation);
106
131
  }
107
132
  /** Project one exact first-party commit's editable source, `git push` it to
108
133
  * an Oyster's own template for that App, and claim the pushed head, so
@@ -0,0 +1,7 @@
1
+ import type { RequestShellAppAccess } from "@fungi.computer/nacre/shell-module";
2
+ import type { WorkbenchDirectory } from "@fungi.computer/nacre/workbench-directory";
3
+ /**
4
+ * Keep standalone consent in the Oyster host: the picked shell draws the
5
+ * prompt, while Oyster bounds the request and commits through its owner CAS.
6
+ */
7
+ export declare function createShellAppAccess(directory: () => Promise<WorkbenchDirectory>): RequestShellAppAccess;