@fungi.computer/oyster 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1082 -2
  3. package/dist/agents.d.ts +114 -0
  4. package/dist/agents.js +322 -0
  5. package/dist/app-builds.d.ts +151 -0
  6. package/dist/app-builds.js +355 -0
  7. package/dist/app-sandbox.d.ts +2 -0
  8. package/dist/app-sandbox.js +2 -0
  9. package/dist/app-sources.d.ts +46 -0
  10. package/dist/app-sources.js +335 -0
  11. package/dist/app-storage.d.ts +141 -0
  12. package/dist/app-storage.js +281 -0
  13. package/dist/apps/gate.d.ts +105 -0
  14. package/dist/apps/gate.js +273 -0
  15. package/dist/apps/index.d.ts +5 -0
  16. package/dist/apps/index.js +5 -0
  17. package/dist/apps/installation.d.ts +248 -0
  18. package/dist/apps/installation.js +307 -0
  19. package/dist/apps/operations.d.ts +8 -0
  20. package/dist/apps/operations.js +98 -0
  21. package/dist/apps/tools.d.ts +33 -0
  22. package/dist/apps/tools.js +17 -0
  23. package/dist/auth.d.ts +11 -0
  24. package/dist/auth.js +93 -0
  25. package/dist/backend-request.d.ts +3 -0
  26. package/dist/backend-request.js +53 -0
  27. package/dist/build/build-jobs.d.ts +128 -0
  28. package/dist/build/build-jobs.js +197 -0
  29. package/dist/build/build-step.d.ts +26 -0
  30. package/dist/build/build-step.js +20 -0
  31. package/dist/build/bundled-html.d.ts +7 -0
  32. package/dist/build/bundled-html.js +64 -0
  33. package/dist/build/definition-emission.d.ts +34 -0
  34. package/dist/build/definition-emission.js +201 -0
  35. package/dist/build/effect.d.ts +231 -0
  36. package/dist/build/effect.js +396 -0
  37. package/dist/build/git-environment.d.ts +6 -0
  38. package/dist/build/git-environment.js +48 -0
  39. package/dist/build/html-recipe.d.ts +20 -0
  40. package/dist/build/html-recipe.js +51 -0
  41. package/dist/build/local-build.d.ts +57 -0
  42. package/dist/build/local-build.js +1078 -0
  43. package/dist/build/r2-artifact-writer.d.ts +31 -0
  44. package/dist/build/r2-artifact-writer.js +179 -0
  45. package/dist/build/snapshot-package-store.d.ts +2 -0
  46. package/dist/build/snapshot-package-store.js +21 -0
  47. package/dist/build/snapshot-program-scripts.d.ts +6 -0
  48. package/dist/build/snapshot-program-scripts.js +2 -0
  49. package/dist/build/submission-contract.d.ts +384 -0
  50. package/dist/build/submission-contract.js +70 -0
  51. package/dist/build/workspace-build.d.ts +50 -0
  52. package/dist/build/workspace-build.js +156 -0
  53. package/dist/build-contract.d.ts +4 -0
  54. package/dist/build-contract.js +4 -0
  55. package/dist/build-jobs.d.ts +2 -0
  56. package/dist/build-jobs.js +2 -0
  57. package/dist/build.d.ts +11 -0
  58. package/dist/build.js +13 -0
  59. package/dist/capability-descriptor.d.ts +46 -0
  60. package/dist/capability-descriptor.js +51 -0
  61. package/dist/catalog-frame.d.ts +39 -0
  62. package/dist/catalog-frame.js +72 -0
  63. package/dist/cause-class.d.ts +19 -0
  64. package/dist/cause-class.js +54 -0
  65. package/dist/cloudflare.d.ts +123 -0
  66. package/dist/cloudflare.js +391 -0
  67. package/dist/computer-terminal.d.ts +38 -0
  68. package/dist/computer-terminal.js +410 -0
  69. package/dist/computer.d.ts +60 -0
  70. package/dist/computer.js +33 -0
  71. package/dist/event-recipients.d.ts +57 -0
  72. package/dist/event-recipients.js +144 -0
  73. package/dist/facet-invocation.d.ts +4 -0
  74. package/dist/facet-invocation.js +45 -0
  75. package/dist/files.d.ts +42 -0
  76. package/dist/files.js +123 -0
  77. package/dist/frame-binding-wire-script.d.ts +2 -0
  78. package/dist/frame-binding-wire-script.js +1 -0
  79. package/dist/frame-binding-wire.d.ts +28 -0
  80. package/dist/frame-binding-wire.js +54 -0
  81. package/dist/git-admission.d.ts +52 -0
  82. package/dist/git-admission.js +188 -0
  83. package/dist/git-auth.d.ts +11 -0
  84. package/dist/git-auth.js +27 -0
  85. package/dist/git-door.d.ts +35 -0
  86. package/dist/git-door.js +108 -0
  87. package/dist/git-journal.d.ts +31 -0
  88. package/dist/git-journal.js +188 -0
  89. package/dist/git.d.ts +31 -0
  90. package/dist/git.js +64 -0
  91. package/dist/guest-policy.d.ts +9 -0
  92. package/dist/guest-policy.js +13 -0
  93. package/dist/host.d.ts +244 -0
  94. package/dist/host.js +1043 -0
  95. package/dist/index.d.ts +166 -0
  96. package/dist/index.js +320 -0
  97. package/dist/installation-consent.d.ts +79 -0
  98. package/dist/installation-consent.js +82 -0
  99. package/dist/internal/background-origins.d.ts +3 -0
  100. package/dist/internal/background-origins.js +8 -0
  101. package/dist/internal/delivery.d.ts +12 -0
  102. package/dist/internal/delivery.js +167 -0
  103. package/dist/internal/host-gate.d.ts +14 -0
  104. package/dist/internal/host-gate.js +62 -0
  105. package/dist/internal/registry-installations.d.ts +6 -0
  106. package/dist/internal/registry-installations.js +12 -0
  107. package/dist/invocation.d.ts +251 -0
  108. package/dist/invocation.js +377 -0
  109. package/dist/keys.d.ts +48 -0
  110. package/dist/keys.js +52 -0
  111. package/dist/mcp-owner.d.ts +27 -0
  112. package/dist/mcp-owner.js +143 -0
  113. package/dist/mcp.d.ts +89 -0
  114. package/dist/mcp.js +429 -0
  115. package/dist/media.d.ts +23 -0
  116. package/dist/media.js +12 -0
  117. package/dist/pack.d.ts +76 -0
  118. package/dist/pack.js +134 -0
  119. package/dist/page/capabilities.d.ts +23 -0
  120. package/dist/page/capabilities.js +256 -0
  121. package/dist/page/computer-handles.d.ts +79 -0
  122. package/dist/page/computer-handles.js +44 -0
  123. package/dist/page/lifetime.d.ts +6 -0
  124. package/dist/page/lifetime.js +19 -0
  125. package/dist/page/mount.d.ts +163 -0
  126. package/dist/page/mount.js +390 -0
  127. package/dist/page/storage.d.ts +3 -0
  128. package/dist/page/storage.js +82 -0
  129. package/dist/page/terminal.d.ts +5 -0
  130. package/dist/page/terminal.js +7 -0
  131. package/dist/page-script.d.ts +14 -0
  132. package/dist/page-script.js +5 -0
  133. package/dist/r2-files.d.ts +3 -0
  134. package/dist/r2-files.js +79 -0
  135. package/dist/release.d.ts +72 -0
  136. package/dist/release.js +164 -0
  137. package/dist/root-data.d.ts +86 -0
  138. package/dist/root-data.js +69 -0
  139. package/dist/shell/app-directory.d.ts +537 -0
  140. package/dist/shell/app-directory.js +195 -0
  141. package/dist/shell/browser-actions.d.ts +7 -0
  142. package/dist/shell/browser-actions.js +56 -0
  143. package/dist/shell/command-host.d.ts +112 -0
  144. package/dist/shell/command-host.js +502 -0
  145. package/dist/shell/commands.d.ts +25 -0
  146. package/dist/shell/commands.js +78 -0
  147. package/dist/shell/index.d.ts +8 -0
  148. package/dist/shell/index.js +8 -0
  149. package/dist/shell/keys.d.ts +41 -0
  150. package/dist/shell/keys.js +134 -0
  151. package/dist/shell/shell-module.d.ts +31 -0
  152. package/dist/shell/shell-module.js +37 -0
  153. package/dist/shell/window-manager.d.ts +124 -0
  154. package/dist/shell/window-manager.js +711 -0
  155. package/dist/shell/workbench-contract.d.ts +68 -0
  156. package/dist/shell/workbench-contract.js +1 -0
  157. package/dist/shell/workbench-host-types.d.ts +96 -0
  158. package/dist/shell/workbench-host-types.js +1 -0
  159. package/dist/shell/workbench-host.d.ts +34 -0
  160. package/dist/shell/workbench-host.js +347 -0
  161. package/dist/shell/workbench-model.d.ts +192 -0
  162. package/dist/shell/workbench-model.js +53 -0
  163. package/dist/shell/workbench.d.ts +4 -0
  164. package/dist/shell/workbench.js +2 -0
  165. package/dist/source/fork.d.ts +27 -0
  166. package/dist/source/fork.js +46 -0
  167. package/dist/source/index.d.ts +11 -0
  168. package/dist/source/index.js +11 -0
  169. package/dist/source/seed-receipt.d.ts +53 -0
  170. package/dist/source/seed-receipt.js +18 -0
  171. package/dist/source/team-source.d.ts +32 -0
  172. package/dist/source/team-source.js +42 -0
  173. package/dist/source/template-repository.d.ts +2 -0
  174. package/dist/source/template-repository.js +8 -0
  175. package/dist/source/template-write.d.ts +69 -0
  176. package/dist/source/template-write.js +344 -0
  177. package/dist/storage-frame.d.ts +64 -0
  178. package/dist/storage-frame.js +28 -0
  179. package/dist/tenant.d.ts +81 -0
  180. package/dist/tenant.js +71 -0
  181. package/dist/testing/capabilities.d.ts +29 -0
  182. package/dist/testing/capabilities.js +130 -0
  183. package/dist/testing/catalog.d.ts +3 -0
  184. package/dist/testing/catalog.js +53 -0
  185. package/dist/testing/index.d.ts +10 -0
  186. package/dist/testing/index.js +10 -0
  187. package/dist/testing/installation.d.ts +11 -0
  188. package/dist/testing/installation.js +25 -0
  189. package/dist/testing/native-worker.d.ts +21 -0
  190. package/dist/testing/native-worker.js +106 -0
  191. package/dist/testing/worker.d.ts +18 -0
  192. package/dist/testing/worker.js +67 -0
  193. package/dist/testing/workers-runtime.d.ts +24 -0
  194. package/dist/testing/workers-runtime.js +33 -0
  195. package/dist/webhook-delivery.d.ts +14 -0
  196. package/dist/webhook-delivery.js +17 -0
  197. package/dist/workers-git.d.ts +285 -0
  198. package/dist/workers-git.js +1174 -0
  199. package/dist/workers.d.ts +352 -0
  200. package/dist/workers.js +866 -0
  201. package/docs/api/README.md +82 -0
  202. package/docs/api/agents.md +332 -0
  203. package/docs/api/app-storage.md +209 -0
  204. package/docs/api/apps.md +1375 -0
  205. package/docs/api/auth.md +63 -0
  206. package/docs/api/build-contract.md +507 -0
  207. package/docs/api/build.md +1476 -0
  208. package/docs/api/cloudflare.md +421 -0
  209. package/docs/api/computer.md +203 -0
  210. package/docs/api/files.md +117 -0
  211. package/docs/api/git.md +470 -0
  212. package/docs/api/host.md +1328 -0
  213. package/docs/api/index.md +430 -0
  214. package/docs/api/invocation.md +738 -0
  215. package/docs/api/keys.md +175 -0
  216. package/docs/api/mcp.md +301 -0
  217. package/docs/api/media.md +40 -0
  218. package/docs/api/modules.md +42 -0
  219. package/docs/api/pack.md +138 -0
  220. package/docs/api/page/mount.md +298 -0
  221. package/docs/api/root-data.md +111 -0
  222. package/docs/api/shell/app-directory.md +332 -0
  223. package/docs/api/shell/command-host.md +224 -0
  224. package/docs/api/shell/commands.md +197 -0
  225. package/docs/api/shell/keys.md +183 -0
  226. package/docs/api/shell/shell-module.md +143 -0
  227. package/docs/api/shell/window-manager.md +267 -0
  228. package/docs/api/shell/workbench.md +186 -0
  229. package/docs/api/shell.md +1788 -0
  230. package/docs/api/tenant.md +194 -0
  231. package/docs/api/workers-git.md +1527 -0
  232. package/docs/api/workers.md +1678 -0
  233. package/docs/media/architecture.mmd +32 -0
  234. package/docs/media/architecture.png +0 -0
  235. package/docs/media/bonsai-network.png +0 -0
  236. package/docs/media/bonsai-repository.png +0 -0
  237. package/docs/site/agent-quickstart.mdx +349 -0
  238. package/examples/git/worker.ts +39 -0
  239. package/examples/git/wrangler.jsonc +32 -0
  240. package/examples/standalone/worker.ts +14 -0
  241. package/examples/standalone/wrangler.jsonc +21 -0
  242. package/package.json +249 -3
@@ -0,0 +1,68 @@
1
+ import type { AgentSessionSelection } from "@fungi.computer/agar";
2
+ import type { GillReply } from "@fungi.computer/gills";
3
+ import type { AppOpenInput } from "@fungi.computer/agar/apps";
4
+ import type { WhistleJsonValue } from "@fungi.computer/whistle";
5
+ import type { WorkbenchChildEvents, WorkbenchChildHandle, WorkbenchChildLaunch, WorkbenchGillRequest, WorkbenchGillWatch, WorkbenchPlatformDestination, WorkbenchDesktopSettings, WorkbenchDesktopRegistration } from "./workbench-model.js";
6
+ /** Team dashboards additionally relay the selected Agent Session to the Hub. */
7
+ export type TeamWorkbenchChildEvents = WorkbenchChildEvents & Readonly<{
8
+ /** Relay a child's explicit Agent Session selection to its host. */
9
+ onAgentSessionSelection: (selection: AgentSessionSelection | null) => Promise<void>;
10
+ }>;
11
+ /**
12
+ * Team/Computer/App-directory reads and child-App hosting for the trusted
13
+ * shell. The host composes the shell into its own top-level document, so the
14
+ * shell runs with the host's escalated permissions; each child App still runs
15
+ * in its own sandboxed frame with its own separately resolved approvals.
16
+ */
17
+ export type TeamWorkbenchHostHandle = {
18
+ /** The authenticated Team hosting this dashboard; the caller parses it. */
19
+ team(): Promise<WhistleJsonValue>;
20
+ /** A page of the Team's Computer inventory; the caller parses it. */
21
+ computers(input: Readonly<{
22
+ limit: number;
23
+ offset: number;
24
+ }>): Promise<WhistleJsonValue>;
25
+ /** The Team's effective App directory and role choices; the caller parses it. */
26
+ directory(): Promise<WhistleJsonValue>;
27
+ /** Invoke a declared Gill of an admitted App in this Team. No caller-provided resource data. */
28
+ requestGill(input: WorkbenchGillRequest): Promise<GillReply>;
29
+ /** Observe resource changes; only the host owns the authenticated stream. Close while covered. */
30
+ watchGillChanges(input: WorkbenchGillRequest, onChanged: () => Promise<void>): Promise<WorkbenchGillWatch>;
31
+ /**
32
+ * Resolve, authorize and mount one child App inside `container`, the
33
+ * shell's own slot in that App's window. The host never moves the child:
34
+ * the shell keeps `container` attached and moves its window instead.
35
+ */
36
+ mount(container: HTMLElement, input: WorkbenchChildLaunch, events: TeamWorkbenchChildEvents): Promise<WorkbenchChildHandle>;
37
+ /** Bind one mounted child's explicit Agent Session selection to the host. */
38
+ reportAgentSessionSelection(windowId: string, selection: AgentSessionSelection | null): Promise<void>;
39
+ /** The platform destinations the host currently offers. */
40
+ platformDestinations(): Promise<readonly WorkbenchPlatformDestination[]>;
41
+ /** Offered Settings categories, or null for unrestricted host controls. */
42
+ settingsSections(): Promise<readonly string[] | null>;
43
+ /** Ask the host to navigate to one offered platform destination. */
44
+ openPlatformDestination(id: string): Promise<void>;
45
+ /**
46
+ * Take, once, the App-open handoff the visitor's Hub address carried (for
47
+ * example `?open=inbox&intent=compose`). The host consumes it from its own
48
+ * address; the dashboard opens it like any other `apps.open` request.
49
+ */
50
+ takeAppOpenRequest(): Promise<AppOpenInput | null>;
51
+ /**
52
+ * Open the host's own Team settings at a dashboard-defined location (the
53
+ * host parses it). Administration there runs with the signed-in person's
54
+ * own authority in host-rendered UI, never through the dashboard.
55
+ * Resolves when Settings close, so the dashboard restores its own focus.
56
+ */
57
+ openSettings(location: WhistleJsonValue): Promise<void>;
58
+ /** Bind the admitted dashboard's arranger to the host's member-consent UI.
59
+ * Only the host invokes it; ordinary Apps can only open their own Settings. */
60
+ registerDesktopSettings(handler: WorkbenchDesktopSettings): Promise<WorkbenchDesktopRegistration>;
61
+ /**
62
+ * Hear when host-rendered settings or App access consent changed the
63
+ * Team's Apps or the person's settings, so the dashboard refreshes.
64
+ */
65
+ watchSettingsChanges(onChanged: () => Promise<void>): Promise<void>;
66
+ /** The signed-in person's own avatar preference; the caller parses it. */
67
+ memberAvatar(): Promise<WhistleJsonValue>;
68
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,96 @@
1
+ import type { TeamWorkbenchChildEvents, TeamWorkbenchHostHandle } from "./workbench-contract.js";
2
+ import type { AgentSessionSelection, AppPermissionOutcome, AppPermissionRequest } from "@fungi.computer/agar";
3
+ import type { WhistleJsonValue } from "@fungi.computer/whistle";
4
+ import type { AppCommandDeclaration, AppSurfaceCommandMount } from "./command-host.js";
5
+ import type { WorkbenchChildHandle, WorkbenchChildLaunch, WorkbenchDesktopSettings, WorkbenchPlatformDestination } from "./workbench-model.js";
6
+ /** A launch checked against the embedding host's own installation authority. */
7
+ export type WorkbenchResolvedChild = Readonly<{
8
+ launch: Omit<WorkbenchChildLaunch, "title">;
9
+ title?: string;
10
+ }>;
11
+ /** The host's real mounted App; selection and lease replacement stay with it. */
12
+ export type WorkbenchChildSurface = Pick<WorkbenchChildHandle, "setVisibility" | "requestClose" | "requestBack" | "activateStatus"> & {
13
+ /** Release the mounted child or host-owned observations. */
14
+ dispose(): void;
15
+ /** Replace the host-admitted selection while retaining the child document. */
16
+ reconfigure(launch: WorkbenchResolvedChild["launch"]): Promise<void>;
17
+ };
18
+ /** Inputs shared child-window mechanics supply to the host's admitted mount. */
19
+ export type WorkbenchChildMountOptions = Readonly<{
20
+ container: HTMLElement;
21
+ hostWindow: Window;
22
+ title?: string;
23
+ events: TeamWorkbenchChildEvents;
24
+ commands: AppSurfaceCommandMount;
25
+ /** Suggest the child's admitted surface in host-rendered Settings. */
26
+ onDesktopSettings: (surfaceId: string) => Promise<void>;
27
+ /** Ask the host to obtain the person's approval for the child. */
28
+ onPermissionRequest: (input: AppPermissionRequest) => Promise<AppPermissionOutcome>;
29
+ }>;
30
+ /** Host-owned admission and lifetime of one prepared child. */
31
+ export type WorkbenchPreparedChild = Readonly<{
32
+ commands: readonly AppCommandDeclaration[];
33
+ /** Connect the prepared App using the shared window callbacks and commands. */
34
+ mount(options: WorkbenchChildMountOptions): Promise<WorkbenchChildSurface>;
35
+ /** Release preparation and lease resources, including a failed mount. */
36
+ dispose(): Promise<void>;
37
+ }>;
38
+ /** Host services retain their own Team, account, Gill and consent authority. */
39
+ export type WorkbenchHostServices = Pick<TeamWorkbenchHostHandle, "team" | "computers" | "directory" | "requestGill" | "watchGillChanges" | "memberAvatar"> & Readonly<{
40
+ /** Offer host-owned desktop Settings for this currently admitted surface. */
41
+ suggestDesktopSettings(surfaceId: string): Promise<void>;
42
+ /** Release the mounted child or host-owned observations. */
43
+ dispose(): void;
44
+ }>;
45
+ /** Controls offered by the real embedding host, never grants lent by the App. */
46
+ export type WorkbenchHostControls = Readonly<{
47
+ /** Implemented Settings categories; omission means the retained unrestricted controls. */
48
+ sections?: readonly string[] | undefined;
49
+ /** Validate a location against the Settings this host actually offers. */
50
+ parseSettings(location: WhistleJsonValue): WhistleJsonValue;
51
+ /** Open validated host-rendered Settings without racing dashboard focus. */
52
+ openSettings(location: WhistleJsonValue, options: Readonly<{
53
+ restoreFocus: boolean;
54
+ }>): void;
55
+ /** Notify when the host dialog has closed; return its unsubscribe action. */
56
+ subscribeSettingsClosed(listener: () => void): () => void;
57
+ /** Observe host Settings changes until the returned unsubscribe is called. */
58
+ subscribeChanges(listener: () => void): () => void;
59
+ /** Attach the dashboard arranger; return the host registration cleanup. */
60
+ attachDesktopSettings(handler: WorkbenchDesktopSettings): () => void;
61
+ /** Ask the person for the child's own access, never through the dashboard. */
62
+ requestAppAccess(target: Readonly<{
63
+ surfaceId: string;
64
+ title: string;
65
+ }>, input: AppPermissionRequest): Promise<AppPermissionOutcome>;
66
+ }>;
67
+ /** Retained host warning identifiers; adapters own their diagnostic sink. */
68
+ export type WorkbenchWarningEvent = "team_workbench_commands_failed" | "team_workbench_settings_notification_failed" | "team_workbench_focus_notification_failed";
69
+ /** Fields of a retained child/settings notification failure. */
70
+ export type WorkbenchWarningFields = Readonly<{
71
+ cause: unknown;
72
+ }> | Readonly<{
73
+ stage: "update" | "dispose";
74
+ cause: unknown;
75
+ }>;
76
+ /** One workbench's child mechanics over host-owned admission and services. */
77
+ export type WorkbenchHostOptions = Readonly<{
78
+ hostWindow: Window;
79
+ /** Check identity and launch address against this host's installation scope. */
80
+ resolveLaunch(input: WorkbenchChildLaunch): WorkbenchResolvedChild;
81
+ /** Prepare the child using its own current grants and lease owner. */
82
+ prepareChild(launch: WorkbenchResolvedChild["launch"]): Promise<WorkbenchPreparedChild>;
83
+ services: WorkbenchHostServices;
84
+ /** Report a mounted child's explicit selection to the embedding host. */
85
+ reportAgentSessionSelection(windowId: string, selection: AgentSessionSelection | null): void;
86
+ platform: Readonly<{
87
+ controls: WorkbenchHostControls;
88
+ /** List only destinations the host currently implements. */
89
+ destinations(): Promise<readonly WorkbenchPlatformDestination[]>;
90
+ /** Follow a destination that the shared host verified was offered. */
91
+ open(id: string): void;
92
+ takeAppOpenRequest: TeamWorkbenchHostHandle["takeAppOpenRequest"];
93
+ }>;
94
+ /** Surface retained callback failures through the host's diagnostic sink. */
95
+ log(event: WorkbenchWarningEvent, fields: WorkbenchWarningFields): void;
96
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import type { TeamWorkbenchHostHandle, TeamWorkbenchChildEvents } from "./workbench-contract.js";
2
+ import type { AgentSessionSelection } from "@fungi.computer/agar";
3
+ import type { AppOpenInput } from "@fungi.computer/agar/apps";
4
+ import type { WhistleJsonValue } from "@fungi.computer/whistle";
5
+ import { type WorkbenchDesktopRegistration, type WorkbenchChildHandle, type WorkbenchChildLaunch, type WorkbenchPlatformDestination } from "./workbench-model.js";
6
+ import type { WorkbenchHostOptions } from "./workbench-host-types.js";
7
+ /**
8
+ * Child-window, command and callback lifetime for the trusted shell the host
9
+ * composes into its own document. Each child mounts inside the window slot
10
+ * the shell hands it, so the browser moves, clips and stacks it with that
11
+ * window; the host never positions a child itself.
12
+ */
13
+ export declare class WorkbenchHost implements TeamWorkbenchHostHandle {
14
+ #private;
15
+ constructor(options: WorkbenchHostOptions);
16
+ team(): ReturnType<TeamWorkbenchHostHandle["team"]>;
17
+ computers(input: Parameters<TeamWorkbenchHostHandle["computers"]>[0]): ReturnType<TeamWorkbenchHostHandle["computers"]>;
18
+ directory(): ReturnType<TeamWorkbenchHostHandle["directory"]>;
19
+ requestGill(input: Parameters<TeamWorkbenchHostHandle["requestGill"]>[0]): ReturnType<TeamWorkbenchHostHandle["requestGill"]>;
20
+ watchGillChanges(...input: Parameters<TeamWorkbenchHostHandle["watchGillChanges"]>): ReturnType<TeamWorkbenchHostHandle["watchGillChanges"]>;
21
+ platformDestinations(): Promise<readonly WorkbenchPlatformDestination[]>;
22
+ openPlatformDestination(id: string): Promise<void>;
23
+ /** Describe the actual controls without offering unsupported Settings. */
24
+ settingsSections(): Promise<readonly string[] | null>;
25
+ takeAppOpenRequest(): Promise<AppOpenInput | null>;
26
+ registerDesktopSettings(handler: unknown): Promise<WorkbenchDesktopRegistration>;
27
+ openSettings(location: WhistleJsonValue): Promise<void>;
28
+ memberAvatar(): ReturnType<TeamWorkbenchHostHandle["memberAvatar"]>;
29
+ watchSettingsChanges(onChanged: () => Promise<void>): Promise<void>;
30
+ /** The shell unmounted: stop notifying its callbacks. */
31
+ [Symbol.dispose](): void;
32
+ mount(container: HTMLElement, input: WorkbenchChildLaunch, events: TeamWorkbenchChildEvents): Promise<WorkbenchChildHandle>;
33
+ reportAgentSessionSelection(windowId: string, selection: AgentSessionSelection | null): Promise<void>;
34
+ }
@@ -0,0 +1,347 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { AppError, parseAppStatusAction } from "@fungi.computer/agar";
3
+ import { parseJsonValue } from "@fungi.computer/whistle/wire";
4
+ import { desktopSettingsHandlerSchema, } from "./workbench-model.js";
5
+ import { createAppSurfaceCommandHost, } from "./command-host.js";
6
+ /** A dashboard may keep this many child Apps mounted at once. */
7
+ const maxChildren = 32;
8
+ const nonBlank = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(2_048)));
9
+ const visibilitySchema = Schema.Literals(["hidden", "visible"]);
10
+ // A dashboard presents a child's commands to people; it never acts as the
11
+ // child's Agent.
12
+ const commandInvocationSchema = Schema.Struct({
13
+ commandId: nonBlank,
14
+ origin: Schema.Literals([
15
+ "keyboard",
16
+ "palette",
17
+ "menu",
18
+ "browser",
19
+ "programmatic",
20
+ "slash",
21
+ "terminal",
22
+ ]),
23
+ });
24
+ /**
25
+ * Forward a child's Release-validated commands and reserved chords to the
26
+ * dashboard that presents them. Validation stays here, against the child's
27
+ * own Release declaration, so the dashboard can never widen them.
28
+ */
29
+ function forwardCommands(log, validated, events, current) {
30
+ const present = (commands) => events.onCommands?.(commands) ?? Promise.resolve();
31
+ const handlers = {
32
+ registerCommands: async (input) => {
33
+ const contribution = await validated.handlers.registerCommands(input);
34
+ current.invoke = input.invoke;
35
+ await present(input.commands);
36
+ return {
37
+ update: (commands) => {
38
+ contribution.update(commands);
39
+ void present(commands).catch((cause) => {
40
+ log("team_workbench_commands_failed", {
41
+ stage: "update",
42
+ cause,
43
+ });
44
+ });
45
+ },
46
+ dispose: () => {
47
+ contribution.dispose();
48
+ if (current.invoke === input.invoke)
49
+ current.invoke = undefined;
50
+ void present([]).catch((cause) => {
51
+ log("team_workbench_commands_failed", {
52
+ stage: "dispose",
53
+ cause,
54
+ });
55
+ });
56
+ },
57
+ };
58
+ },
59
+ invokeReservedChord: async (input) => {
60
+ if (events.onReservedChord === undefined)
61
+ throw new Error("App reserved chord is unavailable");
62
+ await events.onReservedChord(input);
63
+ },
64
+ };
65
+ return Object.freeze({
66
+ handlers: Object.freeze(handlers),
67
+ dispose: validated.dispose,
68
+ });
69
+ }
70
+ /**
71
+ * One mounted child App, proxying to its real host-owned mount. The shell
72
+ * holds this bounded handle; the host's lease machinery stays behind it.
73
+ */
74
+ class MountedChildHandle {
75
+ surface;
76
+ commands;
77
+ reconfigureCurrent;
78
+ release;
79
+ #disposed = false;
80
+ constructor(surface, commands, reconfigureCurrent, release) {
81
+ this.surface = surface;
82
+ this.commands = commands;
83
+ this.reconfigureCurrent = reconfigureCurrent;
84
+ this.release = release;
85
+ }
86
+ async setVisibility(visibility) {
87
+ if (this.#disposed)
88
+ return;
89
+ await this.surface.setVisibility(Schema.decodeUnknownSync(visibilitySchema)(visibility));
90
+ }
91
+ async reconfigure(launch) {
92
+ if (this.#disposed)
93
+ return;
94
+ await this.reconfigureCurrent(launch);
95
+ }
96
+ async requestClose() {
97
+ if (this.#disposed)
98
+ return true;
99
+ return this.surface.requestClose();
100
+ }
101
+ async requestBack() {
102
+ if (this.#disposed)
103
+ return false;
104
+ return this.surface.requestBack();
105
+ }
106
+ async activateStatus(action) {
107
+ if (this.#disposed)
108
+ return false;
109
+ // The shell relays the person's click; parse it at the host's edge.
110
+ return this.surface.activateStatus(parseAppStatusAction(action));
111
+ }
112
+ async invokeCommand(commandId, input) {
113
+ const invoke = this.commands.invoke;
114
+ if (this.#disposed || invoke === undefined)
115
+ throw new AppError("That App command is unavailable", {
116
+ code: "CAPABILITY_DENIED",
117
+ retryable: false,
118
+ });
119
+ const parsed = Schema.decodeUnknownSync(commandInvocationSchema)({
120
+ commandId,
121
+ origin: input.origin,
122
+ });
123
+ return invoke(parsed.commandId, input.arguments === undefined
124
+ ? { origin: parsed.origin }
125
+ : { arguments: parseJsonValue(input.arguments), origin: parsed.origin });
126
+ }
127
+ async dispose() {
128
+ if (this.#disposed)
129
+ return;
130
+ this.#disposed = true;
131
+ await this.release();
132
+ }
133
+ [Symbol.dispose]() {
134
+ void this.dispose();
135
+ }
136
+ }
137
+ class DesktopSettingsRegistration {
138
+ detach;
139
+ constructor(detach) {
140
+ this.detach = detach;
141
+ }
142
+ close() {
143
+ this.detach();
144
+ return Promise.resolve();
145
+ }
146
+ [Symbol.dispose]() {
147
+ this.detach();
148
+ }
149
+ }
150
+ /**
151
+ * Child-window, command and callback lifetime for the trusted shell the host
152
+ * composes into its own document. Each child mounts inside the window slot
153
+ * the shell hands it, so the browser moves, clips and stacks it with that
154
+ * window; the host never positions a child itself.
155
+ */
156
+ export class WorkbenchHost {
157
+ #hostWindow;
158
+ #options;
159
+ #commands = createAppSurfaceCommandHost();
160
+ #children = new Set();
161
+ #releases = [];
162
+ #desktopSettingsClose;
163
+ #settingsClose;
164
+ #finishSettings;
165
+ constructor(options) {
166
+ this.#hostWindow = options.hostWindow;
167
+ this.#options = options;
168
+ }
169
+ team() {
170
+ return this.#options.services.team();
171
+ }
172
+ computers(input) {
173
+ return this.#options.services.computers(input);
174
+ }
175
+ directory() {
176
+ return this.#options.services.directory();
177
+ }
178
+ requestGill(input) {
179
+ return this.#options.services.requestGill(input);
180
+ }
181
+ watchGillChanges(...input) {
182
+ return this.#options.services.watchGillChanges(...input);
183
+ }
184
+ async platformDestinations() {
185
+ return (await this.#options.platform.destinations()).map(({ id, label }) => ({ id, label }));
186
+ }
187
+ async openPlatformDestination(id) {
188
+ const parsed = Schema.decodeUnknownSync(nonBlank)(id);
189
+ const offered = await this.#options.platform.destinations();
190
+ if (!offered.some((destination) => destination.id === parsed))
191
+ throw new AppError("That destination is unavailable", {
192
+ code: "INVALID_ARGUMENT",
193
+ retryable: false,
194
+ });
195
+ this.#options.platform.open(parsed);
196
+ }
197
+ /** Describe the actual controls without offering unsupported Settings. */
198
+ settingsSections() {
199
+ const controls = this.#options.platform.controls;
200
+ return Promise.resolve(controls.sections?.slice() ?? null);
201
+ }
202
+ takeAppOpenRequest() {
203
+ return Promise.resolve(this.#options.platform.takeAppOpenRequest());
204
+ }
205
+ registerDesktopSettings(handler) {
206
+ return Promise.resolve().then(() => {
207
+ let parsed;
208
+ try {
209
+ parsed = desktopSettingsHandlerSchema.parse(handler);
210
+ }
211
+ catch (cause) {
212
+ throw new AppError("Desktop settings registration is invalid", {
213
+ code: "INVALID_ARGUMENT",
214
+ retryable: false,
215
+ cause,
216
+ });
217
+ }
218
+ this.#desktopSettingsClose?.();
219
+ const detach = this.#options.platform.controls.attachDesktopSettings(parsed);
220
+ let closed = false;
221
+ const close = () => {
222
+ if (closed)
223
+ return;
224
+ closed = true;
225
+ detach();
226
+ if (this.#desktopSettingsClose === close)
227
+ this.#desktopSettingsClose = undefined;
228
+ };
229
+ this.#desktopSettingsClose = close;
230
+ return new DesktopSettingsRegistration(close);
231
+ });
232
+ }
233
+ async openSettings(location) {
234
+ const parsed = this.#options.platform.controls.parseSettings(location);
235
+ const controls = this.#options.platform.controls;
236
+ if (this.#settingsClose !== undefined) {
237
+ const closed = this.#settingsClose;
238
+ controls.openSettings(parsed, { restoreFocus: false });
239
+ return closed;
240
+ }
241
+ const closed = new Promise((resolve) => {
242
+ const unsubscribe = controls.subscribeSettingsClosed(() => this.#finishSettings?.());
243
+ this.#finishSettings = () => {
244
+ unsubscribe();
245
+ this.#settingsClose = undefined;
246
+ this.#finishSettings = undefined;
247
+ resolve();
248
+ };
249
+ });
250
+ this.#settingsClose = closed;
251
+ // The shell restores its own invoker once Settings close; returning
252
+ // focus here would race that restoration.
253
+ controls.openSettings(parsed, { restoreFocus: false });
254
+ return closed;
255
+ }
256
+ memberAvatar() {
257
+ return this.#options.services.memberAvatar();
258
+ }
259
+ watchSettingsChanges(onChanged) {
260
+ const unsubscribe = this.#options.platform.controls.subscribeChanges(() => {
261
+ void onChanged().catch((cause) => {
262
+ this.#options.log("team_workbench_settings_notification_failed", {
263
+ cause,
264
+ });
265
+ });
266
+ });
267
+ this.#releases.push(unsubscribe);
268
+ return Promise.resolve();
269
+ }
270
+ /** The shell unmounted: stop notifying its callbacks. */
271
+ [Symbol.dispose]() {
272
+ this.#finishSettings?.();
273
+ this.#desktopSettingsClose?.();
274
+ this.#options.services.dispose();
275
+ for (const release of this.#releases.splice(0))
276
+ release();
277
+ }
278
+ async mount(container, input, events) {
279
+ const request = this.#options.resolveLaunch(input);
280
+ const mountId = request.launch.windowId;
281
+ if (this.#children.has(mountId))
282
+ throw new AppError("That App window is already open", {
283
+ code: "INVALID_ARGUMENT",
284
+ retryable: false,
285
+ });
286
+ if (this.#children.size >= maxChildren)
287
+ throw new AppError("Too many Apps are open", {
288
+ code: "INVALID_ARGUMENT",
289
+ retryable: false,
290
+ });
291
+ this.#children.add(mountId);
292
+ let prepared;
293
+ let commandMount;
294
+ const childCommands = { invoke: undefined };
295
+ const release = async () => {
296
+ try {
297
+ commandMount?.dispose();
298
+ if (prepared)
299
+ await prepared.dispose();
300
+ }
301
+ finally {
302
+ this.#children.delete(mountId);
303
+ }
304
+ };
305
+ try {
306
+ prepared = await this.#options.prepareChild(request.launch);
307
+ commandMount = forwardCommands(this.#options.log, this.#commands.mount(mountId, prepared.commands), events, childCommands);
308
+ const surface = await prepared.mount({
309
+ container,
310
+ hostWindow: this.#hostWindow,
311
+ ...(request.title === undefined ? undefined : { title: request.title }),
312
+ commands: commandMount,
313
+ events,
314
+ onDesktopSettings: (surfaceId) => this.#options.services.suggestDesktopSettings(surfaceId),
315
+ onPermissionRequest: (permission) => this.#options.platform.controls.requestAppAccess({
316
+ surfaceId: request.launch.surfaceId,
317
+ title: request.title ?? request.launch.appId,
318
+ }, permission),
319
+ });
320
+ const reconfigureCurrent = async (next) => {
321
+ const nextRequest = this.#options.resolveLaunch(next);
322
+ if (nextRequest.launch.appId !== request.launch.appId ||
323
+ nextRequest.launch.releaseId !== request.launch.releaseId ||
324
+ nextRequest.launch.surfaceId !== request.launch.surfaceId)
325
+ throw new Error("App identity cannot change during reconfiguration");
326
+ await surface.reconfigure(nextRequest.launch);
327
+ };
328
+ return new MountedChildHandle(surface, childCommands, reconfigureCurrent, async () => {
329
+ try {
330
+ surface.dispose();
331
+ }
332
+ finally {
333
+ await release();
334
+ }
335
+ });
336
+ }
337
+ catch (error) {
338
+ await release();
339
+ throw error;
340
+ }
341
+ }
342
+ reportAgentSessionSelection(windowId, selection) {
343
+ const parsed = Schema.decodeUnknownSync(nonBlank)(windowId);
344
+ this.#options.reportAgentSessionSelection(parsed, selection);
345
+ return Promise.resolve();
346
+ }
347
+ }