@fungi.computer/oyster 0.2.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 (287) hide show
  1. package/README.md +376 -72
  2. package/dist/agents.d.ts +3 -2
  3. package/dist/agents.js +4 -2
  4. package/dist/app-builds.d.ts +81 -8
  5. package/dist/app-builds.js +210 -60
  6. package/dist/app-storage.d.ts +3 -3
  7. package/dist/approval-capability.d.ts +5 -0
  8. package/dist/approval-capability.js +46 -0
  9. package/dist/approval-home.d.ts +51 -0
  10. package/dist/approval-home.js +226 -0
  11. package/dist/apps/catalog.d.ts +335 -0
  12. package/dist/apps/catalog.js +46 -0
  13. package/dist/apps/gate.d.ts +20 -6
  14. package/dist/apps/gate.js +42 -16
  15. package/dist/apps/index.d.ts +3 -1
  16. package/dist/apps/index.js +3 -1
  17. package/dist/apps/installation.d.ts +132 -51
  18. package/dist/apps/installation.js +176 -70
  19. package/dist/apps/permissions.d.ts +247 -0
  20. package/dist/apps/permissions.js +255 -0
  21. package/dist/boot-capability-route.d.ts +15 -0
  22. package/dist/boot-capability-route.js +196 -0
  23. package/dist/boot-capability.d.ts +103 -0
  24. package/dist/boot-capability.js +141 -0
  25. package/dist/boot-construction.d.ts +12 -0
  26. package/dist/boot-construction.js +175 -0
  27. package/dist/boot-gate.d.ts +267 -0
  28. package/dist/boot-gate.js +236 -0
  29. package/dist/boot-host-script.d.ts +2 -0
  30. package/dist/boot-host-script.js +1 -0
  31. package/dist/boot-mount.d.ts +21 -0
  32. package/dist/boot-mount.js +65 -0
  33. package/dist/browser/boot.d.ts +73 -0
  34. package/dist/browser/boot.js +502 -0
  35. package/dist/browser/browser.d.ts +104 -0
  36. package/dist/browser/browser.js +374 -0
  37. package/dist/browser/capability-source.d.ts +76 -0
  38. package/dist/browser/capability-source.js +338 -0
  39. package/dist/browser/cdp-client.d.ts +56 -0
  40. package/dist/browser/cdp-client.js +184 -0
  41. package/dist/browser/errors.d.ts +79 -0
  42. package/dist/browser/errors.js +32 -0
  43. package/dist/browser/index.d.ts +16 -0
  44. package/dist/browser/index.js +14 -0
  45. package/dist/browser/lease-registry.d.ts +61 -0
  46. package/dist/browser/lease-registry.js +101 -0
  47. package/dist/browser/operations.d.ts +225 -0
  48. package/dist/browser/operations.js +137 -0
  49. package/dist/browser/page.d.ts +25 -0
  50. package/dist/browser/page.js +172 -0
  51. package/dist/browser/quick-reads.d.ts +29 -0
  52. package/dist/browser/quick-reads.js +160 -0
  53. package/dist/browser/sessions.d.ts +52 -0
  54. package/dist/browser/sessions.js +118 -0
  55. package/dist/browser/url-policy.d.ts +18 -0
  56. package/dist/browser/url-policy.js +58 -0
  57. package/dist/build/app-imports.d.ts +12 -0
  58. package/dist/build/app-imports.js +105 -0
  59. package/dist/build/build-policy.d.ts +5 -3
  60. package/dist/build/build-policy.js +5 -3
  61. package/dist/build/candidate-builder.d.ts +1 -1
  62. package/dist/build/candidate-cli.mjs +8375 -334
  63. package/dist/build/local-build.d.ts +2 -0
  64. package/dist/build/local-build.js +28 -6
  65. package/dist/build/snapshot-package-store.d.ts +1 -1
  66. package/dist/build/snapshot-package-store.js +7 -2
  67. package/dist/build/workspace-build.d.ts +92 -5
  68. package/dist/build/workspace-build.js +93 -19
  69. package/dist/build.d.ts +1 -1
  70. package/dist/build.js +1 -1
  71. package/dist/catalog-frame.js +1 -1
  72. package/dist/cloudflare.d.ts +13 -3
  73. package/dist/cloudflare.js +102 -4
  74. package/dist/frame-binding-wire-script.js +1 -1
  75. package/dist/frame-binding-wire.js +4 -2
  76. package/dist/git-auth.d.ts +0 -2
  77. package/dist/git-auth.js +0 -2
  78. package/dist/git.d.ts +0 -1
  79. package/dist/git.js +0 -2
  80. package/dist/host.d.ts +39 -13
  81. package/dist/host.js +268 -142
  82. package/dist/index.d.ts +25 -15
  83. package/dist/index.js +39 -16
  84. package/dist/install-release-cli.d.ts +2 -0
  85. package/dist/install-release-cli.js +46 -0
  86. package/dist/installation-client.d.ts +43 -0
  87. package/dist/installation-client.js +178 -0
  88. package/dist/installation-execution.d.ts +11 -0
  89. package/dist/installation-execution.js +184 -0
  90. package/dist/installation-operations.d.ts +1437 -0
  91. package/dist/installation-operations.js +664 -0
  92. package/dist/installation-store-generation.d.ts +3 -0
  93. package/dist/installation-store-generation.js +16 -0
  94. package/dist/instance-id.d.ts +10 -0
  95. package/dist/instance-id.js +10 -0
  96. package/dist/instance-refusal.d.ts +11 -0
  97. package/dist/instance-refusal.js +21 -0
  98. package/dist/internal/delivery.js +4 -2
  99. package/dist/internal/host-gate.js +7 -1
  100. package/dist/internal/registry-installations.js +4 -4
  101. package/dist/keys.d.ts +5 -1
  102. package/dist/keys.js +10 -2
  103. package/dist/machine-link/authority.d.ts +89 -0
  104. package/dist/machine-link/authority.js +37 -0
  105. package/dist/machine-link/client.d.ts +66 -0
  106. package/dist/machine-link/client.js +94 -0
  107. package/dist/machine-link/delivery.d.ts +18 -0
  108. package/dist/machine-link/delivery.js +55 -0
  109. package/dist/machine-link/enrollment.d.ts +111 -0
  110. package/dist/machine-link/enrollment.js +286 -0
  111. package/dist/machine-link/fences.d.ts +37 -0
  112. package/dist/machine-link/fences.js +166 -0
  113. package/dist/machine-link/home.d.ts +20 -0
  114. package/dist/machine-link/home.js +24 -0
  115. package/dist/machine-link/host.d.ts +43 -0
  116. package/dist/machine-link/host.js +189 -0
  117. package/dist/machine-link/index.d.ts +15 -0
  118. package/dist/machine-link/index.js +15 -0
  119. package/dist/machine-link/link.d.ts +48 -0
  120. package/dist/machine-link/link.js +223 -0
  121. package/dist/machine-link/presence.d.ts +10 -0
  122. package/dist/machine-link/presence.js +25 -0
  123. package/dist/machine-link/workers.d.ts +6 -0
  124. package/dist/machine-link/workers.js +6 -0
  125. package/dist/mcp-owner.d.ts +2 -2
  126. package/dist/mcp-owner.js +41 -33
  127. package/dist/mcp.d.ts +15 -3
  128. package/dist/mcp.js +146 -25
  129. package/dist/pack.d.ts +14 -3
  130. package/dist/pack.js +44 -19
  131. package/dist/page/app-access.d.ts +7 -0
  132. package/dist/page/app-access.js +75 -0
  133. package/dist/page/boot-host.js +57 -0
  134. package/dist/page/capabilities.d.ts +3 -3
  135. package/dist/page/capabilities.js +16 -12
  136. package/dist/page/computer-handles.d.ts +3 -3
  137. package/dist/page/computer-target.d.ts +3 -0
  138. package/dist/page/computer-target.js +9 -0
  139. package/dist/page/mount.d.ts +23 -3
  140. package/dist/page/mount.js +152 -16
  141. package/dist/page-assets.d.ts +22 -0
  142. package/dist/page-assets.js +28 -0
  143. package/dist/page-script.d.ts +3 -0
  144. package/dist/page-script.js +5 -4
  145. package/dist/release.d.ts +13 -6
  146. package/dist/release.js +10 -7
  147. package/dist/root-data.d.ts +16 -6
  148. package/dist/root-data.js +2 -2
  149. package/dist/source/index.d.ts +1 -1
  150. package/dist/source/team-source.d.ts +20 -8
  151. package/dist/source/team-source.js +39 -10
  152. package/dist/source/template-seed.d.ts +2 -2
  153. package/dist/standalone/agent-mail.d.ts +24 -0
  154. package/dist/standalone/agent-mail.js +62 -0
  155. package/dist/standalone/agent-store.d.ts +9 -0
  156. package/dist/standalone/agent-store.js +30 -0
  157. package/dist/standalone/agents.d.ts +68 -9
  158. package/dist/standalone/agents.js +428 -56
  159. package/dist/standalone/app-builds-binding.d.ts +20 -2
  160. package/dist/standalone/app-builds-binding.js +147 -23
  161. package/dist/standalone/app-set.d.ts +5 -2
  162. package/dist/standalone/app-set.js +174 -143
  163. package/dist/standalone/browser.d.ts +11 -0
  164. package/dist/standalone/browser.js +72 -0
  165. package/dist/standalone/builds.d.ts +13 -4
  166. package/dist/standalone/builds.js +28 -44
  167. package/dist/standalone/computer-egress.d.ts +2 -0
  168. package/dist/standalone/computer-egress.js +13 -4
  169. package/dist/standalone/computer-fleet.d.ts +43 -0
  170. package/dist/standalone/computer-fleet.js +200 -0
  171. package/dist/standalone/computer.d.ts +44 -9
  172. package/dist/standalone/computer.js +129 -22
  173. package/dist/standalone/install-apps-cli.js +43 -6
  174. package/dist/standalone/instance.d.ts +2 -2
  175. package/dist/standalone/instance.js +8 -11
  176. package/dist/standalone/machine-link.d.ts +354 -0
  177. package/dist/standalone/machine-link.js +368 -0
  178. package/dist/standalone/media.d.ts +2 -2
  179. package/dist/standalone/media.js +19 -16
  180. package/dist/standalone/preview.d.ts +2 -2
  181. package/dist/standalone/preview.js +37 -10
  182. package/dist/standalone/registry.d.ts +149 -9
  183. package/dist/standalone/registry.js +299 -8
  184. package/dist/standalone/worker.d.ts +19 -5
  185. package/dist/standalone/worker.js +90 -54
  186. package/dist/standalone/workflow-binding.d.ts +18 -22
  187. package/dist/standalone/workflow-binding.js +18 -18
  188. package/dist/standalone/workflow-control.d.ts +4 -0
  189. package/dist/standalone/workflow-control.js +40 -0
  190. package/dist/standalone/workflow-events.d.ts +2 -2
  191. package/dist/standalone/workflow-events.js +9 -10
  192. package/dist/standalone/workflow-git-capture.js +4 -3
  193. package/dist/standalone/workflow-host.d.ts +162 -161
  194. package/dist/standalone/workflow-host.js +101 -21
  195. package/dist/standalone/workflow-node-capture.js +11 -4
  196. package/dist/standalone/workflow-schedules.d.ts +5 -2
  197. package/dist/standalone/workflow-schedules.js +9 -10
  198. package/dist/standalone/workflow-trigger-host.d.ts +65 -5
  199. package/dist/standalone/workflow-trigger-host.js +2 -2
  200. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  201. package/dist/standalone/workflow-webhooks.js +39 -0
  202. package/dist/standalone/workflows.d.ts +140 -43
  203. package/dist/standalone/workflows.js +198 -39
  204. package/dist/storage-frame.d.ts +6 -6
  205. package/dist/testing/capabilities.js +14 -3
  206. package/dist/testing/installation.js +26 -2
  207. package/dist/testing/worker.js +1 -1
  208. package/dist/testing/workers-runtime.d.ts +8 -2
  209. package/dist/testing/workers-runtime.js +20 -2
  210. package/dist/webhook-abuse.d.ts +25 -0
  211. package/dist/webhook-abuse.js +54 -0
  212. package/dist/webhook-custody.d.ts +144 -0
  213. package/dist/webhook-custody.js +222 -0
  214. package/dist/webhook-ingress.d.ts +36 -0
  215. package/dist/webhook-ingress.js +146 -0
  216. package/dist/webhooks.d.ts +111 -0
  217. package/dist/webhooks.js +189 -0
  218. package/dist/workers-git.d.ts +12 -17
  219. package/dist/workers-git.js +60 -52
  220. package/dist/workers.d.ts +224 -13
  221. package/dist/workers.js +351 -51
  222. package/dist/workflow-agent-authority.d.ts +8 -0
  223. package/dist/workflow-agent-authority.js +42 -0
  224. package/dist/workflows.d.ts +66 -0
  225. package/dist/workflows.js +97 -0
  226. package/docs/api/README.md +63 -37
  227. package/docs/api/agents.md +17 -0
  228. package/docs/api/app-storage.md +1 -1
  229. package/docs/api/apps.md +1394 -543
  230. package/docs/api/browser/boot.md +132 -0
  231. package/docs/api/browser.md +1440 -0
  232. package/docs/api/build-contract.md +38 -25
  233. package/docs/api/build.md +263 -84
  234. package/docs/api/cloudflare.md +93 -7
  235. package/docs/api/git.md +3 -13
  236. package/docs/api/host.md +944 -118
  237. package/docs/api/index.md +320 -18
  238. package/docs/api/keys.md +24 -1
  239. package/docs/api/mcp.md +59 -12
  240. package/docs/api/modules.md +33 -40
  241. package/docs/api/pack.md +57 -8
  242. package/docs/api/page/mount.md +92 -4
  243. package/docs/api/workers-git.md +103 -24
  244. package/docs/api/workers.md +1581 -244
  245. package/docs/site/agent-quickstart.mdx +28 -14
  246. package/examples/standalone/worker.ts +3 -1
  247. package/examples/standalone/wrangler.jsonc +2 -0
  248. package/package.json +73 -71
  249. package/dist/installation-consent.d.ts +0 -81
  250. package/dist/installation-consent.js +0 -84
  251. package/dist/shell/app-directory.d.ts +0 -542
  252. package/dist/shell/app-directory.js +0 -196
  253. package/dist/shell/browser-actions.d.ts +0 -7
  254. package/dist/shell/browser-actions.js +0 -56
  255. package/dist/shell/command-host.d.ts +0 -112
  256. package/dist/shell/command-host.js +0 -502
  257. package/dist/shell/commands.d.ts +0 -25
  258. package/dist/shell/commands.js +0 -78
  259. package/dist/shell/index.d.ts +0 -8
  260. package/dist/shell/index.js +0 -8
  261. package/dist/shell/keys.d.ts +0 -41
  262. package/dist/shell/keys.js +0 -134
  263. package/dist/shell/shell-module.d.ts +0 -55
  264. package/dist/shell/shell-module.js +0 -86
  265. package/dist/shell/window-manager.d.ts +0 -128
  266. package/dist/shell/window-manager.js +0 -730
  267. package/dist/shell/workbench-contract.d.ts +0 -68
  268. package/dist/shell/workbench-directory.d.ts +0 -332
  269. package/dist/shell/workbench-directory.js +0 -35
  270. package/dist/shell/workbench-host-types.d.ts +0 -96
  271. package/dist/shell/workbench-host-types.js +0 -1
  272. package/dist/shell/workbench-host.d.ts +0 -34
  273. package/dist/shell/workbench-host.js +0 -352
  274. package/dist/shell/workbench-model.d.ts +0 -194
  275. package/dist/shell/workbench-model.js +0 -53
  276. package/dist/shell/workbench.d.ts +0 -4
  277. package/dist/shell/workbench.js +0 -2
  278. package/docs/api/shell/app-directory.md +0 -599
  279. package/docs/api/shell/command-host.md +0 -224
  280. package/docs/api/shell/commands.md +0 -197
  281. package/docs/api/shell/keys.md +0 -183
  282. package/docs/api/shell/shell-module.md +0 -204
  283. package/docs/api/shell/window-manager.md +0 -267
  284. package/docs/api/shell/workbench-directory.md +0 -119
  285. package/docs/api/shell/workbench.md +0 -205
  286. package/docs/api/shell.md +0 -1915
  287. /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
@@ -0,0 +1,75 @@
1
+ import { grantRequestFor, appPermissionPageSchema, } from "../apps/permissions.js";
2
+ /**
3
+ * Keep standalone consent in the Oyster host: the picked shell draws the
4
+ * prompt, while Oyster bounds the request and commits through its owner CAS.
5
+ */
6
+ export function createShellAppAccess(directory) {
7
+ return async (target, input, dialog) => {
8
+ const { surfaceId, computerId } = target;
9
+ const current = await directory();
10
+ const app = current.apps.find((row) => row.surfaceId === surfaceId);
11
+ const installationId = app?.installationId ?? null;
12
+ const customize = () => dialog.customize(installationId);
13
+ if (installationId === null)
14
+ return customize();
15
+ const path = `/api/installations/${encodeURIComponent(installationId)}/grants`;
16
+ const response = await fetch(path, {
17
+ headers: { Accept: "application/json" },
18
+ credentials: "same-origin",
19
+ cache: "no-store",
20
+ redirect: "error",
21
+ });
22
+ if (!response.ok)
23
+ return "declined";
24
+ const view = appPermissionPageSchema.parse(await response.json());
25
+ const { permissions } = view;
26
+ const decision = grantRequestFor(permissions.authority, {
27
+ declaration: {
28
+ capabilities: permissions.declaredOperations.capabilities.map((row) => row.capability),
29
+ },
30
+ declaredOperations: permissions.declaredOperations,
31
+ }, input, { computerId });
32
+ if (decision.kind === "covered")
33
+ return "granted";
34
+ if (decision.kind === "refused") {
35
+ if (decision.code !== "window_scope_unavailable")
36
+ return "declined";
37
+ const outcome = await dialog.ask({
38
+ appName: view.appName,
39
+ agents: input.agents ?? [],
40
+ computers: input.computers ?? [],
41
+ refusal: {
42
+ code: decision.code,
43
+ message: "Allow for this window is unavailable on this Oyster. No access was changed. Use Customize to review Computer permissions explicitly.",
44
+ },
45
+ allow: () => Promise.resolve(false),
46
+ });
47
+ return outcome === "customize" ? customize() : outcome;
48
+ }
49
+ if (decision.kind === "customize")
50
+ return customize();
51
+ const outcome = await dialog.ask({
52
+ appName: view.appName,
53
+ agents: input.agents ?? [],
54
+ computers: input.computers ?? [],
55
+ allow: async () => {
56
+ const saved = await fetch(path, {
57
+ method: "PUT",
58
+ headers: { "Content-Type": "application/json" },
59
+ credentials: "same-origin",
60
+ cache: "no-store",
61
+ redirect: "error",
62
+ body: JSON.stringify({
63
+ storeGeneration: view.storeGeneration,
64
+ authorityVersion: permissions.authority.authorityVersion,
65
+ artifactDigest: view.artifactDigest,
66
+ command: decision.command,
67
+ }),
68
+ });
69
+ return saved.ok;
70
+ },
71
+ });
72
+ // False saves settle declined; rejected saves leave the shell open for retry.
73
+ return outcome === "customize" ? customize() : outcome;
74
+ };
75
+ }
@@ -0,0 +1,57 @@
1
+ import { connectAppHost } from "@fungi.computer/agar/host";
2
+ import { z } from "zod";
3
+ import { bootUiSchema } from "../boot-gate.js";
4
+ /**
5
+ * Trusted, zero-grant parent for a candidate entry. App readiness messages
6
+ * establish the ordinary Agar connection, never the boot-gate verdict.
7
+ */
8
+ async function bootHost() {
9
+ const element = document.getElementById("boot-config");
10
+ if (element === null)
11
+ throw new Error("Host boot configuration unavailable");
12
+ const config = bootUiSchema
13
+ .pick({ identity: true })
14
+ .extend({ entry: z.url() })
15
+ .parse(JSON.parse(element.textContent));
16
+ const frame = document.getElementById("candidate");
17
+ if (!(frame instanceof HTMLIFrameElement) || frame.contentWindow === null)
18
+ throw new Error("Host candidate frame unavailable");
19
+ const denied = () => Promise.reject(new Error("No grants during host boot"));
20
+ const connection = connectAppHost({
21
+ appWindow: frame.contentWindow,
22
+ expectedAppOrigin: "null",
23
+ hostWindow: window,
24
+ launch: {
25
+ identity: config.identity,
26
+ capabilities: [],
27
+ context: {
28
+ version: 1,
29
+ value: {
30
+ computerId: null,
31
+ focus: { editing: false, surface: null },
32
+ openRequest: null,
33
+ visibility: "visible",
34
+ },
35
+ },
36
+ lifecycle: { version: 1, value: "connected" },
37
+ reservedChords: { version: 1, value: [] },
38
+ theme: { version: 1, value: "light" },
39
+ },
40
+ handlers: {
41
+ invokeReservedChord: denied,
42
+ onDisconnect: () => Promise.resolve(),
43
+ openNavigation: denied,
44
+ registerCommands: () => Promise.resolve({
45
+ update: () => undefined,
46
+ dispose: () => undefined,
47
+ }),
48
+ reportFocus: () => Promise.resolve(),
49
+ reportAgentSessionSelection: () => Promise.resolve(),
50
+ showToast: () => Promise.resolve(),
51
+ },
52
+ });
53
+ // Install the host connection listener before the candidate can announce ready.
54
+ frame.src = config.entry;
55
+ await connection;
56
+ }
57
+ await bootHost();
@@ -5,7 +5,7 @@ import type { CapabilityDescriptor } from "../capability-descriptor.js";
5
5
  /** Agar's opaque host handle, shared by the page's exported factory contracts. */
6
6
  export type HostCapabilityHandle = AppHostLaunch["capabilities"][number]["handle"];
7
7
  /** Bind a page-local JSON caller; cancellation never enters serialized input. */
8
- export declare function capabilityJsonCaller(descriptor: CapabilityDescriptor, installationId: string): (method: string, raw?: unknown, options?: {
8
+ export declare function capabilityJsonCaller(descriptor: CapabilityDescriptor, installationId: string, computerTarget?: string): (method: string, raw?: unknown, options?: {
9
9
  readonly signal?: AbortSignal;
10
10
  }) => Promise<import("zod").JSONType | undefined>;
11
11
  /** Local JSON calls carry cancellation separately from their serialized input. */
@@ -13,11 +13,11 @@ export type CapabilityJsonCaller = ReturnType<typeof capabilityJsonCaller>;
13
13
  /** Local factories may open only transports present in their descriptor. */
14
14
  export type CapabilityTransportOpener = (method: string, input?: Schema.Json) => WebSocket;
15
15
  /** Build the App's RPC handle from its admitted descriptor and installation. */
16
- export declare function capabilityHandle(descriptor: CapabilityDescriptor, installationId: string): HostCapabilityHandle;
16
+ export declare function capabilityHandle(descriptor: CapabilityDescriptor, installationId: string, computerTarget?: string): HostCapabilityHandle;
17
17
  /** A supported contract may adapt local JSON calls into its App-facing handle. */
18
18
  export type CapabilityRegistration = Readonly<{
19
19
  contract: CapabilityContract<unknown>;
20
20
  createHandle?: (call: CapabilityJsonCaller, transport: CapabilityTransportOpener) => HostCapabilityHandle | Promise<HostCapabilityHandle>;
21
21
  }>;
22
22
  /** Create a registered handle, using the descriptor's ordinary RPC handle by default. */
23
- export declare function registeredCapabilityHandle(registration: CapabilityRegistration, descriptor: CapabilityDescriptor, installationId: string): Promise<HostCapabilityHandle>;
23
+ export declare function registeredCapabilityHandle(registration: CapabilityRegistration, descriptor: CapabilityDescriptor, installationId: string, computerTarget?: string): Promise<HostCapabilityHandle>;
@@ -3,6 +3,7 @@ import { compile, parse, pathToRegexp } from "path-to-regexp";
3
3
  import * as Schema from "effect/Schema";
4
4
  import { AppError } from "@fungi.computer/agar";
5
5
  import { parseJsonValue } from "@fungi.computer/whistle/wire";
6
+ import { computerTargetUrl } from "./computer-target.js";
6
7
  const queryInput = Schema.Record(Schema.String, Schema.Union([Schema.String, Schema.Number, Schema.Boolean]));
7
8
  const capabilityInputSchema = Schema.Union([Schema.Json, Schema.Undefined]);
8
9
  function resolveRoute(path, input) {
@@ -163,7 +164,7 @@ class TransportHandle extends RpcTarget {
163
164
  return Promise.resolve();
164
165
  }
165
166
  }
166
- function capabilityRequest(descriptor, installationId, route, raw) {
167
+ function capabilityRequest(descriptor, installationId, route, raw, computerTarget) {
167
168
  const resolved = resolveRoute(route.path, raw);
168
169
  const input = resolved.input;
169
170
  const headers = new Headers({ "X-Oyster-Installation": installationId });
@@ -184,9 +185,12 @@ function capabilityRequest(descriptor, installationId, route, raw) {
184
185
  if (route.kind === "json" && route.method !== "GET") {
185
186
  headers.set("Content-Type", "application/json");
186
187
  init.body = JSON.stringify(input);
187
- return { url, init };
188
+ return { url: computerTargetUrl(url, computerTarget), init };
188
189
  }
189
- return { url: `${url}${query(input)}`, init };
190
+ return {
191
+ url: computerTargetUrl(`${url}${query(input)}`, computerTarget),
192
+ init,
193
+ };
190
194
  }
191
195
  async function jsonReply(url, init) {
192
196
  const response = await fetch(url, init);
@@ -196,7 +200,7 @@ async function jsonReply(url, init) {
196
200
  return parseJsonValue(await response.json());
197
201
  }
198
202
  /** Bind a page-local JSON caller; cancellation never enters serialized input. */
199
- export function capabilityJsonCaller(descriptor, installationId) {
203
+ export function capabilityJsonCaller(descriptor, installationId, computerTarget) {
200
204
  return async (method, raw, options) => {
201
205
  const input = Schema.decodeUnknownSync(capabilityInputSchema)(raw);
202
206
  const route = descriptor.methods[method];
@@ -205,12 +209,12 @@ export function capabilityJsonCaller(descriptor, installationId) {
205
209
  code: "INVALID_ARGUMENT",
206
210
  retryable: false,
207
211
  });
208
- const { url, init } = capabilityRequest(descriptor, installationId, route, input);
212
+ const { url, init } = capabilityRequest(descriptor, installationId, route, input, computerTarget);
209
213
  return jsonReply(url, { ...init, signal: options?.signal });
210
214
  };
211
215
  }
212
216
  /** Open a browser transport using the page's admitted installation. */
213
- function capabilityTransportOpener(descriptor, installationId) {
217
+ function capabilityTransportOpener(descriptor, installationId, computerTarget) {
214
218
  return (method, raw) => {
215
219
  const route = descriptor.methods[method];
216
220
  if (route?.kind !== "transport" ||
@@ -219,7 +223,7 @@ function capabilityTransportOpener(descriptor, installationId) {
219
223
  code: "INVALID_ARGUMENT",
220
224
  retryable: false,
221
225
  });
222
- const { url } = capabilityRequest(descriptor, installationId, route, Schema.decodeUnknownSync(capabilityInputSchema)(raw));
226
+ const { url } = capabilityRequest(descriptor, installationId, route, Schema.decodeUnknownSync(capabilityInputSchema)(raw), computerTarget);
223
227
  const target = new URL(url, "https://oyster.invalid");
224
228
  // Browser WebSockets cannot send the installation header used by Fetch.
225
229
  target.searchParams.set("oysterInstallation", installationId);
@@ -227,7 +231,7 @@ function capabilityTransportOpener(descriptor, installationId) {
227
231
  };
228
232
  }
229
233
  /** Build the App's RPC handle from its admitted descriptor and installation. */
230
- export function capabilityHandle(descriptor, installationId) {
234
+ export function capabilityHandle(descriptor, installationId, computerTarget) {
231
235
  // Cap'n Web exposes prototype methods, never an instance's own properties.
232
236
  // Only input crosses RPC; AbortSignal options belong to the local caller.
233
237
  class CapabilityHandle extends RpcTarget {
@@ -236,7 +240,7 @@ export function capabilityHandle(descriptor, installationId) {
236
240
  name,
237
241
  async (raw) => {
238
242
  const input = Schema.decodeUnknownSync(capabilityInputSchema)(raw);
239
- const { url, init } = capabilityRequest(descriptor, installationId, route, input);
243
+ const { url, init } = capabilityRequest(descriptor, installationId, route, input, computerTarget);
240
244
  if (route.kind === "read")
241
245
  return new ReadHandle(url, init);
242
246
  if (route.kind === "write")
@@ -249,8 +253,8 @@ export function capabilityHandle(descriptor, installationId) {
249
253
  return new CapabilityHandle();
250
254
  }
251
255
  /** Create a registered handle, using the descriptor's ordinary RPC handle by default. */
252
- export async function registeredCapabilityHandle(registration, descriptor, installationId) {
256
+ export async function registeredCapabilityHandle(registration, descriptor, installationId, computerTarget) {
253
257
  if (registration.createHandle === undefined)
254
- return capabilityHandle(descriptor, installationId);
255
- return await registration.createHandle(capabilityJsonCaller(descriptor, installationId), capabilityTransportOpener(descriptor, installationId));
258
+ return capabilityHandle(descriptor, installationId, computerTarget);
259
+ return await registration.createHandle(capabilityJsonCaller(descriptor, installationId, computerTarget), capabilityTransportOpener(descriptor, installationId, computerTarget));
256
260
  }
@@ -46,7 +46,7 @@ export declare class ComputerProcessesHandle extends RpcTarget implements Handle
46
46
  readonly startedAt: number;
47
47
  readonly state: "running" | "exited" | "uncertain";
48
48
  readonly exitCode: number | null;
49
- readonly sync: "synced" | "pending" | "unknown";
49
+ readonly sync: "unknown" | "pending" | "synced";
50
50
  readonly endedAt: number | null;
51
51
  }>;
52
52
  list(): Promise<{
@@ -58,7 +58,7 @@ export declare class ComputerProcessesHandle extends RpcTarget implements Handle
58
58
  readonly startedAt: number;
59
59
  readonly state: "running" | "exited" | "uncertain";
60
60
  readonly exitCode: number | null;
61
- readonly sync: "synced" | "pending" | "unknown";
61
+ readonly sync: "unknown" | "pending" | "synced";
62
62
  readonly endedAt: number | null;
63
63
  }[];
64
64
  }>;
@@ -71,7 +71,7 @@ export declare class ComputerProcessesHandle extends RpcTarget implements Handle
71
71
  readonly startedAt: number;
72
72
  readonly state: "running" | "exited" | "uncertain";
73
73
  readonly exitCode: number | null;
74
- readonly sync: "synced" | "pending" | "unknown";
74
+ readonly sync: "unknown" | "pending" | "synced";
75
75
  readonly endedAt: number | null;
76
76
  };
77
77
  readonly signalled: boolean;
@@ -0,0 +1,3 @@
1
+ /** Serialize a window's routing hint. The server must admit it through the live
2
+ * owner and fleet before constructing that request's workspace. */
3
+ export declare function computerTargetUrl(path: string, computerId?: string): string;
@@ -0,0 +1,9 @@
1
+ /** Serialize a window's routing hint. The server must admit it through the live
2
+ * owner and fleet before constructing that request's workspace. */
3
+ export function computerTargetUrl(path, computerId) {
4
+ if (computerId === undefined)
5
+ return path;
6
+ const url = new URL(path, "https://oyster.invalid");
7
+ url.searchParams.set("computerId", computerId);
8
+ return url.pathname + url.search;
9
+ }
@@ -1,7 +1,21 @@
1
1
  import { type AppOpenInput, type AppRoleChoice } from "@fungi.computer/agar/apps";
2
+ import type { WhistleRuntime } from "@fungi.computer/whistle";
2
3
  import * as Schema from "effect/Schema";
4
+ import { type CapabilityDirectory, type AgentSessionSelection } from "@fungi.computer/agar";
3
5
  import { type AppHost, type AppHostLaunch, type AppHostHandlers } from "@fungi.computer/agar/host";
4
6
  import type { WhistleJsonValue } from "@fungi.computer/whistle";
7
+ /** One page-wide selected Session, using only its reporting App's admitted view. */
8
+ export declare class OysterAgentBrowserHost {
9
+ #private;
10
+ /** End the selected connection with the page's own lifetime. */
11
+ constructor(signal: AbortSignal);
12
+ /** A departing or deselecting mount cannot close a different active mount. */
13
+ release(frame: HTMLIFrameElement): void;
14
+ /** Release the page's current selection; later reports cannot reconnect it. */
15
+ dispose(): void;
16
+ /** Bind the reporting mount's current command view to its explicitly selected Session. */
17
+ report(frame: HTMLIFrameElement, selection: AgentSessionSelection | null, capabilities: CapabilityDirectory, whistle: Pick<WhistleRuntime, "snapshot" | "execute">): Promise<void>;
18
+ }
5
19
  /** Current server-admitted configuration for one installed App page. */
6
20
  export declare const oysterAppConfigSchema: Schema.Struct<{
7
21
  readonly workbench: Schema.Boolean;
@@ -32,7 +46,7 @@ export declare const oysterAppConfigSchema: Schema.Struct<{
32
46
  help?: Readonly<{
33
47
  order?: number | undefined;
34
48
  }> | undefined;
35
- menu? /** Which App that role's open would reach, or null when none fills it. */: Readonly<{
49
+ menu?: Readonly<{
36
50
  order?: number | undefined;
37
51
  }> | undefined;
38
52
  palette?: Readonly<{
@@ -64,7 +78,7 @@ export declare const oysterAppConfigSchema: Schema.Struct<{
64
78
  help?: Readonly<{
65
79
  order?: number | undefined;
66
80
  }> | undefined;
67
- menu? /** Which App that role's open would reach, or null when none fills it. */: Readonly<{
81
+ menu?: Readonly<{
68
82
  order?: number | undefined;
69
83
  }> | undefined;
70
84
  palette?: Readonly<{
@@ -141,6 +155,12 @@ export type OysterAppConfig = typeof oysterAppConfigSchema.Type;
141
155
  /** One isolated App connection, with the slots this host admitted for it. */
142
156
  export type OysterAppConnectionOptions = Readonly<{
143
157
  config: OysterAppConfig;
158
+ /** The page-wide selected Session owner shared by all child mounts. */
159
+ agentBrowser: OysterAgentBrowserHost;
160
+ /** The existing command owner's live, mount-scoped Agent view. */
161
+ agentCommands: Pick<WhistleRuntime, "snapshot" | "execute">;
162
+ /** An independently admitted per-window target; absent on fixed/default hosts. */
163
+ computerTarget?: string | undefined;
144
164
  frame: HTMLIFrameElement;
145
165
  signal: AbortSignal;
146
166
  handlers: AppHostHandlers;
@@ -156,4 +176,4 @@ export declare function connectOysterApp(options: OysterAppConnectionOptions): P
156
176
  export { oysterStorageHandle } from "./storage.js";
157
177
  export { appSandbox } from "../app-sandbox.js";
158
178
  /** Resolve only the slots admitted for this installation by its current host. */
159
- export declare function oysterAppCapabilities(options: Pick<OysterAppConnectionOptions, "config" | "signal" | "appOpen">): Promise<AppHostLaunch["capabilities"]>;
179
+ export declare function oysterAppCapabilities(options: Pick<OysterAppConnectionOptions, "config" | "signal" | "appOpen" | "computerTarget">): Promise<AppHostLaunch["capabilities"]>;
@@ -1,19 +1,23 @@
1
1
  import { appOpenInputSchema, appsCall, appsOpen, } from "@fungi.computer/agar/apps";
2
2
  import { appApiMethodNameSchema } from "@fungi.computer/puffball/app";
3
3
  import { appIdSchema } from "@fungi.computer/puffball/effect";
4
- import { createBrowserActionHandler } from "../shell/browser-actions.js";
5
- import { createAppSurfaceCommandHost } from "../shell/command-host.js";
4
+ import { createBrowserActionHandler } from "@fungi.computer/nacre";
5
+ import { createAppSurfaceCommandHost } from "@fungi.computer/nacre/command-host";
6
6
  // App page: mounts one installed App's sandboxed frame and gives it the
7
7
  // capabilities this Oyster host provides. Bundled into `shellScript`.
8
- import { RpcTarget } from "@fungi.computer/capnweb";
8
+ import { RpcTarget, RpcStub } from "@fungi.computer/capnweb";
9
+ import { EventSource } from "eventsource";
10
+ import { connectBrowser, UiBrowserError, } from "@fungi.computer/knapsack/ui/browser";
9
11
  import { createPageLifetime } from "./lifetime.js";
12
+ import { computerTargetUrl } from "./computer-target.js";
10
13
  import * as Schema from "effect/Schema";
11
- import { AppError } from "@fungi.computer/agar";
14
+ import { AppError, createCapabilityRegistry, } from "@fungi.computer/agar";
12
15
  import { appStorage } from "@fungi.computer/agar/storage";
16
+ import { approvals } from "@fungi.computer/agar/approvals";
13
17
  import { oysterStorageHandle } from "./storage.js";
14
18
  import { appBackend, appMethods } from "@fungi.computer/agar/backend";
15
19
  import { computerList, computerPorts, computerProcesses, computerReadFile, computerTerminal, computerWriteFile, } from "@fungi.computer/agar/computer";
16
- import { agentList, agentManage, agentSessionRead, agentSessionWrite, } from "@fungi.computer/agar/agents";
20
+ import { agentList, agentManage, agentSessionRead, agentSessionWrite, openAppAgent, } from "@fungi.computer/agar/agents";
17
21
  import { mediaGeneration } from "@fungi.computer/agar/media";
18
22
  import { MediaGeneration, mediaGenerationDescriptionSchema, } from "@fungi.computer/agar/media-json";
19
23
  import { registeredCapabilityHandle, } from "./capabilities.js";
@@ -23,6 +27,105 @@ import { parseJsonValue } from "@fungi.computer/whistle/wire";
23
27
  import { appDeclaredCommandSchema } from "@fungi.computer/puffball/app";
24
28
  import { TerminalCapability } from "./terminal.js";
25
29
  import { ComputerPortsHandle, ComputerProcessesHandle, } from "./computer-handles.js";
30
+ /** One page-wide selected Session, using only its reporting App's admitted view. */
31
+ export class OysterAgentBrowserHost {
32
+ #active;
33
+ #disposed = false;
34
+ /** End the selected connection with the page's own lifetime. */
35
+ constructor(signal) {
36
+ if (signal.aborted)
37
+ this.dispose();
38
+ else
39
+ signal.addEventListener("abort", () => {
40
+ this.dispose();
41
+ }, { once: true });
42
+ }
43
+ /** A departing or deselecting mount cannot close a different active mount. */
44
+ release(frame) {
45
+ if (this.#active?.frame !== frame)
46
+ return;
47
+ const previous = this.#active;
48
+ this.#active = undefined;
49
+ previous.close();
50
+ }
51
+ /** Release the page's current selection; later reports cannot reconnect it. */
52
+ dispose() {
53
+ this.#disposed = true;
54
+ if (this.#active !== undefined)
55
+ this.release(this.#active.frame);
56
+ }
57
+ /** Bind the reporting mount's current command view to its explicitly selected Session. */
58
+ async report(frame, selection, capabilities, whistle) {
59
+ if (this.#disposed)
60
+ return;
61
+ if (selection === null) {
62
+ this.release(frame);
63
+ return;
64
+ }
65
+ if (this.#active !== undefined)
66
+ this.release(this.#active.frame);
67
+ // Read-only Apps may observe Sessions, but cannot claim or settle UI work.
68
+ if (!capabilities.has(agentSessionWrite) && !capabilities.has(agentManage))
69
+ return;
70
+ let transport;
71
+ let connection;
72
+ const failure = () => {
73
+ console.warn("Oyster Agent browser connection failed");
74
+ };
75
+ const active = {
76
+ frame,
77
+ close: () => {
78
+ connection?.close();
79
+ if (transport !== undefined)
80
+ void transport.close().catch(failure);
81
+ },
82
+ };
83
+ this.#active = active;
84
+ try {
85
+ transport = await openAppAgent(capabilities, {
86
+ agentId: selection.agentId,
87
+ });
88
+ if (this.#active !== active) {
89
+ await transport.close();
90
+ return;
91
+ }
92
+ const admitted = transport;
93
+ connection = connectBrowser({
94
+ baseUrl: `${admitted.baseUrl}/browser/sessions/${encodeURIComponent(selection.sessionId)}`,
95
+ whistle,
96
+ transport: {
97
+ fetch: admitted.fetch,
98
+ openEvents: (url) => new EventSource(url, { fetch: admitted.fetch }),
99
+ },
100
+ });
101
+ void connection.closed.catch((cause) => {
102
+ if (this.#active === active)
103
+ this.release(frame);
104
+ console.warn("Oyster Agent browser connection failed", cause instanceof UiBrowserError
105
+ ? cause.message
106
+ : "Transport unavailable");
107
+ });
108
+ }
109
+ catch (cause) {
110
+ if (this.#active === active)
111
+ this.release(frame);
112
+ throw cause;
113
+ }
114
+ }
115
+ }
116
+ function agentDirectory(capabilities) {
117
+ const directory = createCapabilityRegistry();
118
+ const read = capabilities.find(({ id, version }) => id === agentSessionRead.id && version === agentSessionRead.version);
119
+ const write = capabilities.find(({ id, version }) => id === agentSessionWrite.id && version === agentSessionWrite.version);
120
+ const manage = capabilities.find(({ id, version }) => id === agentManage.id && version === agentManage.version);
121
+ if (read !== undefined)
122
+ directory.provide(agentSessionRead, agentSessionRead.schema.parse(new RpcStub(read.handle)));
123
+ if (write !== undefined)
124
+ directory.provide(agentSessionWrite, agentSessionWrite.schema.parse(new RpcStub(write.handle)));
125
+ if (manage !== undefined)
126
+ directory.provide(agentManage, agentManage.schema.parse(new RpcStub(manage.handle)));
127
+ return directory;
128
+ }
26
129
  /** Current server-admitted configuration for one installed App page. */
27
130
  export const oysterAppConfigSchema = Schema.Struct({
28
131
  workbench: Schema.Boolean,
@@ -59,15 +162,17 @@ async function answer(response) {
59
162
  }
60
163
  /** The App's typed methods, reached through this host's method route. */
61
164
  class Methods extends RpcTarget {
165
+ computerTarget;
62
166
  #url;
63
- constructor(url) {
167
+ constructor(url, computerTarget) {
64
168
  super();
169
+ this.computerTarget = computerTarget;
65
170
  this.#url = url;
66
171
  }
67
172
  async call(method, input) {
68
173
  const name = Schema.decodeUnknownSync(Schema.String.check(Schema.isPattern(/^[A-Za-z][A-Za-z0-9_.]{0,79}$/u)))(method);
69
174
  const body = Schema.decodeUnknownSync(Schema.String)(input);
70
- return answer(await fetch(`${this.#url}${name}`, {
175
+ return answer(await fetch(computerTargetUrl(`${this.#url}${name}`, this.computerTarget), {
71
176
  method: "POST",
72
177
  headers: { "Content-Type": "application/json" },
73
178
  body,
@@ -81,10 +186,12 @@ class Methods extends RpcTarget {
81
186
  class Calls extends RpcTarget {
82
187
  base;
83
188
  signal;
84
- constructor(base, signal) {
189
+ computerTarget;
190
+ constructor(base, signal, computerTarget) {
85
191
  super();
86
192
  this.base = base;
87
193
  this.signal = signal;
194
+ this.computerTarget = computerTarget;
88
195
  }
89
196
  async call(appId, method, input) {
90
197
  const target = Schema.decodeUnknownSync(appIdSchema)(appId);
@@ -96,7 +203,7 @@ class Calls extends RpcTarget {
96
203
  retryable: false,
97
204
  });
98
205
  Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Json))(body);
99
- return answer(await fetch(`${this.base}${encodeURIComponent(target)}/${encodeURIComponent(name)}`, {
206
+ return answer(await fetch(computerTargetUrl(`${this.base}${encodeURIComponent(target)}/${encodeURIComponent(name)}`, this.computerTarget), {
100
207
  method: "POST",
101
208
  headers: { "Content-Type": "application/json" },
102
209
  body,
@@ -205,11 +312,14 @@ export async function mountOysterPage() {
205
312
  // The App's commands are still admitted against its Release declaration.
206
313
  const commands = createAppSurfaceCommandHost();
207
314
  const mount = commands.mount(config.installationId, config.commands);
315
+ const agentBrowser = new OysterAgentBrowserHost(lifetime.signal);
208
316
  lifetime.own(() => {
209
317
  commands.dispose();
210
318
  });
211
319
  return connectOysterApp({
212
320
  config,
321
+ agentBrowser,
322
+ agentCommands: mount.agentCommands,
213
323
  frame,
214
324
  signal: lifetime.signal,
215
325
  reservedChords: [],
@@ -234,6 +344,11 @@ export async function connectOysterApp(options) {
234
344
  if (frame.contentWindow === null)
235
345
  throw new Error("App frame is unavailable");
236
346
  const capabilities = await oysterAppCapabilities(options);
347
+ const agents = agentDirectory(capabilities);
348
+ const releaseBrowser = () => {
349
+ options.agentBrowser.release(frame);
350
+ };
351
+ options.signal.addEventListener("abort", releaseBrowser, { once: true });
237
352
  const host = await connectAppHost({
238
353
  appWindow: frame.contentWindow,
239
354
  expectedAppOrigin: "null",
@@ -261,6 +376,14 @@ export async function connectOysterApp(options) {
261
376
  },
262
377
  handlers: {
263
378
  ...options.handlers,
379
+ reportAgentSessionSelection: async (selection) => {
380
+ await options.agentBrowser.report(frame, selection, agents, options.agentCommands);
381
+ await options.handlers.reportAgentSessionSelection(selection);
382
+ },
383
+ onDisconnect: async () => {
384
+ releaseBrowser();
385
+ await options.handlers.onDisconnect();
386
+ },
264
387
  requestBrowserAction: createBrowserActionHandler({
265
388
  frame,
266
389
  hostWindow: window,
@@ -305,11 +428,11 @@ export async function oysterAppCapabilities(options) {
305
428
  let leaseExpiresAt = 0;
306
429
  return new TerminalCapability((input) => transport("open", input), {
307
430
  computerId: config.computerId,
308
- terminalUrl: selected.terminalUrl,
431
+ terminalUrl: computerTargetUrl(selected.terminalUrl, options.computerTarget),
309
432
  hostWindow: window,
310
433
  mountSignal: options.signal,
311
434
  ensureAccess: Object.assign(async (signal) => {
312
- const response = await fetch(`/apps/${encodeURIComponent(config.installationId)}/`, {
435
+ const response = await fetch(computerTargetUrl(`/apps/${encodeURIComponent(config.installationId)}/`, options.computerTarget), {
313
436
  headers: { Accept: "application/json" },
314
437
  credentials: "same-origin",
315
438
  cache: "no-store",
@@ -325,11 +448,18 @@ export async function oysterAppCapabilities(options) {
325
448
  retryable: false,
326
449
  });
327
450
  leaseExpiresAt = Date.now() + 60_000;
328
- return terminalSelection(current);
451
+ const selected = terminalSelection(current);
452
+ return {
453
+ ...selected,
454
+ terminalUrl: selected.terminalUrl === null
455
+ ? null
456
+ : computerTargetUrl(selected.terminalUrl, options.computerTarget),
457
+ };
329
458
  }, { leaseExpiresAt: () => leaseExpiresAt }),
330
459
  });
331
460
  },
332
461
  },
462
+ { contract: approvals },
333
463
  { contract: agentList },
334
464
  { contract: agentManage },
335
465
  { contract: agentSessionRead },
@@ -357,7 +487,7 @@ export async function oysterAppCapabilities(options) {
357
487
  return [
358
488
  Promise.resolve({
359
489
  ...appsCall,
360
- handle: new Calls(base, options.signal),
490
+ handle: new Calls(base, options.signal, options.computerTarget),
361
491
  }),
362
492
  ];
363
493
  }
@@ -368,14 +498,20 @@ export async function oysterAppCapabilities(options) {
368
498
  return [];
369
499
  }
370
500
  return [
371
- registeredCapabilityHandle(registration, descriptor, config.installationId).then((handle) => ({ ...registration.contract, handle })),
501
+ registeredCapabilityHandle(registration, descriptor, config.installationId, options.computerTarget).then((handle) => ({ ...registration.contract, handle })),
372
502
  ];
373
503
  }));
374
504
  if (unknownCapabilities.length > 0)
375
505
  document.body.dataset.oysterCapability = unknownCapabilities.join(", ");
376
506
  const resolved = [
377
- { ...appBackend, handle: new Backend(config.backendUrl) },
378
- { ...appMethods, handle: new Methods(config.methodsUrl) },
507
+ {
508
+ ...appBackend,
509
+ handle: new Backend(computerTargetUrl(config.backendUrl, options.computerTarget)),
510
+ },
511
+ {
512
+ ...appMethods,
513
+ handle: new Methods(config.methodsUrl, options.computerTarget),
514
+ },
379
515
  ...capabilities,
380
516
  ...(config.appsOpen && options.appOpen
381
517
  ? [{ ...appsOpen, handle: new AppOpen(options.appOpen) }]
@@ -0,0 +1,22 @@
1
+ import { HTTPException } from "hono/http-exception";
2
+ /**
3
+ * Project the generated module's fields without exposing its private module name.
4
+ * The generated module remains the sole page-source contract.
5
+ * @inline
6
+ */
7
+ export type PageAssetSources = {
8
+ [Key in keyof typeof import("#page-script")]: (typeof import("#page-script"))[Key];
9
+ };
10
+ /**
11
+ * Acquire page sources only when a host page or asset request needs them.
12
+ */
13
+ export type PageAssetLoader = () => Promise<PageAssetSources>;
14
+ /** A failed acquisition refuses this request without exposing loader details. */
15
+ export declare class PageAssetsLoadError extends HTTPException {
16
+ constructor(cause: unknown);
17
+ getResponse(): Response;
18
+ }
19
+ /**
20
+ * Missing page sources must fail the request, never produce an empty script.
21
+ */
22
+ export declare const loadPageAssets: PageAssetLoader;