@fungi.computer/nacre 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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -2
  3. package/dist/app-directory.d.ts +600 -0
  4. package/dist/app-directory.js +323 -0
  5. package/dist/browser-actions.d.ts +7 -0
  6. package/dist/browser-actions.js +56 -0
  7. package/dist/command-host.d.ts +140 -0
  8. package/dist/command-host.js +532 -0
  9. package/dist/commands.d.ts +25 -0
  10. package/dist/commands.js +78 -0
  11. package/dist/index.d.ts +8 -0
  12. package/dist/index.js +9 -0
  13. package/dist/keys.d.ts +47 -0
  14. package/dist/keys.js +136 -0
  15. package/dist/shell-module.d.ts +98 -0
  16. package/dist/shell-module.js +90 -0
  17. package/dist/window-manager.d.ts +159 -0
  18. package/dist/window-manager.js +796 -0
  19. package/dist/workbench-contract.d.ts +70 -0
  20. package/dist/workbench-contract.js +1 -0
  21. package/dist/workbench-directory.d.ts +343 -0
  22. package/dist/workbench-directory.js +65 -0
  23. package/dist/workbench-host-types.d.ts +114 -0
  24. package/dist/workbench-host-types.js +1 -0
  25. package/dist/workbench-host.d.ts +34 -0
  26. package/dist/workbench-host.js +356 -0
  27. package/dist/workbench-model.d.ts +223 -0
  28. package/dist/workbench-model.js +93 -0
  29. package/dist/workbench.d.ts +4 -0
  30. package/dist/workbench.js +2 -0
  31. package/docs/api/README.md +21 -0
  32. package/docs/api/app-directory.md +587 -0
  33. package/docs/api/command-host.md +225 -0
  34. package/docs/api/commands.md +199 -0
  35. package/docs/api/index.md +1860 -0
  36. package/docs/api/keys.md +183 -0
  37. package/docs/api/shell-module.md +233 -0
  38. package/docs/api/window-manager.md +269 -0
  39. package/docs/api/workbench-directory.md +141 -0
  40. package/docs/api/workbench.md +201 -0
  41. package/package.json +98 -3
@@ -0,0 +1,323 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { appDeclaredCommandSchema, appCallsSchema, } from "@fungi.computer/puffball/app";
3
+ import { appDeclaredGillSchema, appGillParamsSchema, } from "@fungi.computer/gills";
4
+ import * as z from "zod";
5
+ /** An App address selects a host default or a specific installation. */
6
+ export const appAddressSchema = z.lazy(() => z.union([
7
+ z.strictObject({
8
+ /** Marks an address that selects a host default App. */
9
+ kind: z.literal("default"),
10
+ /** The App whose host default is selected. */
11
+ appId: z.string().min(1),
12
+ }),
13
+ z.strictObject({
14
+ /** Marks an address that selects one installation. */
15
+ kind: z.literal("installation"),
16
+ /** The installation that is selected. */
17
+ installationId: z.string().min(1),
18
+ }),
19
+ ]));
20
+ /** Host-reported progress of applying a revised App authority version. */
21
+ export const appAuthorityEnforcementSchema = z.lazy(() => z.strictObject({
22
+ /** The host's id for the authority change being applied. */
23
+ requestId: z.string().min(1),
24
+ /** The App address whose authority changed. */
25
+ address: appAddressSchema,
26
+ /** The authority version being applied. */
27
+ authorityVersion: z.number().int().positive(),
28
+ /** How far the change has progressed. */
29
+ state: z.enum({
30
+ /** The change needs no delivery. */
31
+ unaffected: "unaffected",
32
+ /** Some deliveries are still unconfirmed. */
33
+ pending: "pending",
34
+ /** Every delivery was confirmed. */
35
+ applied: "applied",
36
+ /** Nothing is outstanding, but not every delivery was confirmed. */
37
+ expired: "expired",
38
+ }),
39
+ /** How many deliveries of the change are still unconfirmed. */
40
+ outstanding: z.number().int().nonnegative(),
41
+ }));
42
+ /** Stable identity of one App address across directory and settings views. */
43
+ export const addressKey = (address) => address.kind === "default"
44
+ ? `default:${address.appId}`
45
+ : `installation:${address.installationId}`;
46
+ /** Reusable App metadata supplied by an embedding host; it conveys no permissions. */
47
+ export const appDirectoryEntrySchema = z.strictObject({
48
+ /** The address that selects this App. */
49
+ address: appAddressSchema,
50
+ /** The installation this row describes, or null when it describes none. */
51
+ installationId: z.string().min(1).nullable(),
52
+ /** The id of the App surface this row opens. */
53
+ surfaceId: z.string().min(1),
54
+ /** The App's id. */
55
+ appId: z.string().min(1),
56
+ /** The name shown for the App. */
57
+ displayName: z.string().trim().min(1).optional(),
58
+ /** The App's icon, or null when it has none. */
59
+ icon: z.string().min(1).nullable().optional(),
60
+ /** The Release the row runs, or null when none is recorded. */
61
+ releaseId: z.string().min(1).nullable(),
62
+ /** The version of the App's authority. */
63
+ authorityVersion: z.number().int().nonnegative(),
64
+ /**
65
+ * Progress of applying a revised authority version, or null when none is in
66
+ * progress.
67
+ */
68
+ enforcement: appAuthorityEnforcementSchema.nullable(),
69
+ /** The kinds of handler the App offers, such as file.open. */
70
+ handlers: z.array(z.literal("file.open")),
71
+ /** The roles the App can fill. */
72
+ roles: z.array(z.string().min(1)),
73
+ /** Capabilities the Release declares: the ceiling for host consent, never a grant. */
74
+ declaredCapabilities: z.array(z.string().min(1)),
75
+ /** The calls the App's Release declares. */
76
+ calls: z
77
+ .unknown()
78
+ .transform((value) => Schema.decodeUnknownSync(appCallsSchema)(value))
79
+ .optional(),
80
+ /** The commands the App's Release declares. */
81
+ commands: z
82
+ .array(z.unknown())
83
+ .transform((values) => values.map((value) => Schema.decodeUnknownSync(appDeclaredCommandSchema)(value))),
84
+ /** The Gills the App's Release declares. */
85
+ gills: z.array(z.unknown()).transform((values) => values.map((value) => Schema.decodeUnknownSync(appDeclaredGillSchema, {
86
+ onExcessProperty: "error",
87
+ })(value))),
88
+ /** Whether the App can be opened now. */
89
+ availability: z.enum({
90
+ /** The App can be opened. */
91
+ available: "available",
92
+ /** The App cannot be opened now. */
93
+ unavailable: "unavailable",
94
+ }),
95
+ /** The App's surface, or null when it has none. */
96
+ surface: z
97
+ .strictObject({
98
+ /** The route that identifies the surface across Releases. */
99
+ stableRoute: z.string().min(1),
100
+ })
101
+ .nullable(),
102
+ /** The path, starting with a slash, where the surface is served. */
103
+ surfacePath: z.string().startsWith("/"),
104
+ });
105
+ /** A host-resolved role selection; a surface address is not launch authority. */
106
+ export const appRoleSelectionSchema = z.strictObject({
107
+ /** The surface selected for the role, or null when none is. */
108
+ surfaceId: z.string().min(1).nullable(),
109
+ /** Whether an App is selected for the role. */
110
+ chosen: z.boolean(),
111
+ });
112
+ const appComputerOperationSchema = z.enum({
113
+ /** List the Team's Computers. */
114
+ "computer.list": "computer.list",
115
+ /** Read a file on a Computer. */
116
+ "computer.readFile": "computer.readFile",
117
+ /** Write a file on a Computer. */
118
+ "computer.writeFile": "computer.writeFile",
119
+ /** Run a command on a Computer. */
120
+ "computer.execute": "computer.execute",
121
+ });
122
+ const selectedComputerPermissionSchema = z.lazy(() => z.strictObject({
123
+ /** Marks a permission limited to selected Computers. */
124
+ kind: z.literal("selected-computers"),
125
+ /** The selected Computers and what the App may do on each. */
126
+ computers: z.array(z.strictObject({
127
+ /** The Computer the operations apply to. */
128
+ computerId: z.string().min(1),
129
+ /** The operations the App may request on that Computer. */
130
+ operations: z.array(appComputerOperationSchema),
131
+ })),
132
+ }));
133
+ const allTeamComputersPermissionSchema = z.lazy(() => z.strictObject({
134
+ /** Marks a permission covering all of the Team's Computers. */
135
+ kind: z.literal("all-team-computers"),
136
+ /** The operations the App may request on any Team Computer. */
137
+ operations: z.array(appComputerOperationSchema),
138
+ }));
139
+ /** App Computer permissions limited to selected Computers or the Team's Computers. */
140
+ export const appComputerPermissionSchema = z.discriminatedUnion("kind", [
141
+ selectedComputerPermissionSchema,
142
+ allTeamComputersPermissionSchema,
143
+ ]);
144
+ const agentDirectoryOperationSchema = z.enum({
145
+ /** List the Team's agents. */
146
+ "agent.list": "agent.list",
147
+ });
148
+ const agentSessionOperationSchema = z.enum({
149
+ /** Read an agent's session. */
150
+ "agent.read": "agent.read",
151
+ /** Send a message in an agent's session. */
152
+ "agent.send": "agent.send",
153
+ /** Manage an agent's session. */
154
+ "agent.manage": "agent.manage",
155
+ });
156
+ /** Agent directory and session operations an App may request from its host. */
157
+ export const appAgentPermissionSchema = z.discriminatedUnion("kind", [
158
+ z.strictObject({
159
+ /** Marks a permission covering all of the Team's agents. */
160
+ kind: z.literal("all-team-agents"),
161
+ /** What the App may do with the agent directory. */
162
+ directoryOperations: z.array(agentDirectoryOperationSchema),
163
+ /** What the App may do in any agent's session. */
164
+ sessionOperations: z.array(agentSessionOperationSchema),
165
+ }),
166
+ z.strictObject({
167
+ /** Marks a permission limited to selected agents. */
168
+ kind: z.literal("selected-agents"),
169
+ /** What the App may do with the agent directory. */
170
+ directoryOperations: z.array(agentDirectoryOperationSchema),
171
+ /** The selected agents and what the App may do in each one's session. */
172
+ agents: z.array(z.strictObject({
173
+ /** The agent the operations apply to. */
174
+ agentId: z.string().min(1).max(256),
175
+ /** What the App may do in that agent's session. */
176
+ sessionOperations: z.array(agentSessionOperationSchema),
177
+ })),
178
+ }),
179
+ ]);
180
+ /** Whether an App may use language-model and media generation services. */
181
+ export const appGenerationPermissionsSchema = z.strictObject({
182
+ /** Whether the App may use language-model generation. */
183
+ llm: z.boolean(),
184
+ /** Whether the App may use media generation. */
185
+ media: z.boolean(),
186
+ });
187
+ /** Whether an App may browse, buy, or install free marketplace entries. */
188
+ export const appMarketplacePermissionsSchema = z.strictObject({
189
+ /** Whether the App may browse marketplace entries. */
190
+ browse: z.boolean(),
191
+ /** Whether the App may buy marketplace entries. */
192
+ purchase: z.boolean(),
193
+ /** Whether the App may install free marketplace entries. */
194
+ installFree: z.boolean(),
195
+ });
196
+ /**
197
+ * A host's Team App directory with its permission projection, parsed strictly
198
+ * by every consumer: an unknown field is a contract change both sides make.
199
+ */
200
+ export const appDirectoryResponseSchema = z.lazy(() => z.strictObject({
201
+ /** Whether the person asking owns the Team. */
202
+ actorIsTeamOwner: z.boolean(),
203
+ /** Directory records the host rejected. Each carries identity only, never launch authority. */
204
+ needsUpdate: z
205
+ .array(z.strictObject({
206
+ /** The rejected record's installation, or null when it has none. */
207
+ installationId: z.string().min(1).nullable(),
208
+ /** The rejected record's App, or null when unknown. */
209
+ appId: z.string().min(1).nullable(),
210
+ /** The name shown for the rejected record. */
211
+ displayName: z.string().min(1),
212
+ }))
213
+ .optional(),
214
+ /** The Team's App rows with their permissions. */
215
+ apps: z.array(appDirectoryEntrySchema.extend({
216
+ /**
217
+ * The Release and surface route the Team has set for this App, or null when none.
218
+ */
219
+ override: z
220
+ .strictObject({
221
+ /** The App the override names. */
222
+ appId: z.string().min(1).optional(),
223
+ /** The Release the override pins. */
224
+ releaseId: z.string().min(1),
225
+ /**
226
+ * The surface route the override pins, or null when it pins none.
227
+ */
228
+ surfaceRoute: z.string().min(1).nullable(),
229
+ })
230
+ .nullable(),
231
+ /** The operation grants held for this App. */
232
+ grants: z.array(z.strictObject({
233
+ /** The operation that is granted. */
234
+ operation: z.string().min(1),
235
+ /** The resource the grant covers. */
236
+ resourceId: z.string().min(1),
237
+ })),
238
+ /**
239
+ * The App's Computer permission, or null or absent when it has none.
240
+ */
241
+ computerPermission: appComputerPermissionSchema.nullable().optional(),
242
+ /** The App's generation permissions. */
243
+ generationPermissions: appGenerationPermissionsSchema,
244
+ /** The App's marketplace permissions. */
245
+ marketplacePermissions: appMarketplacePermissionsSchema,
246
+ /** The App's Agent permission. */
247
+ agentPermission: appAgentPermissionSchema,
248
+ })),
249
+ /** The Team's effective App per role, owned and resolved by the host. */
250
+ roles: z.record(z.string().min(1), appRoleSelectionSchema),
251
+ }));
252
+ /** Desktop metadata is a method result, distinct from the full Release Gill declaration. */
253
+ const catalogGillParamsSchema = z.lazy(() => z
254
+ .array(z.strictObject({
255
+ /** The parameter's key. */
256
+ key: z.string().max(64),
257
+ /** The label shown for the parameter. */
258
+ label: z.string().max(256),
259
+ /** What the parameter picks: a Computer, a repository or a branch. */
260
+ kind: z.enum(["computer", "repository", "branch"]),
261
+ /** Whether the Gill cannot be placed without it. */
262
+ required: z.boolean(),
263
+ /** Extra options for the parameter. */
264
+ options: z.string().max(256).optional(),
265
+ }))
266
+ .max(4)
267
+ // The wire shape is representable; Gill owns its domain refinements.
268
+ .refine(Schema.is(appGillParamsSchema), "Invalid Gill placement parameters"));
269
+ const catalogGillSchema = z.strictObject({
270
+ /** The Gill's id. */
271
+ id: z.string().trim().min(1).max(256),
272
+ /** The name shown for the Gill. */
273
+ title: z.string().trim().min(1).max(256),
274
+ /** Placement parameters the Gill asks for. */
275
+ params: catalogGillParamsSchema.optional(),
276
+ /** How the Gill may appear on the dashboard. */
277
+ desktop: z
278
+ .strictObject({
279
+ /** Layout sizes the Gill supports. */
280
+ sizes: z
281
+ .array(z.enum({
282
+ /** Small: one dashboard cell. */
283
+ S: "S",
284
+ /** Medium: two cells wide, one tall. */
285
+ M: "M",
286
+ /** Large: two cells wide, two tall. */
287
+ L: "L",
288
+ }))
289
+ .min(1)
290
+ .max(3)
291
+ .refine((sizes) => new Set(sizes).size === sizes.length, "Repeated Gill size"),
292
+ })
293
+ .optional(),
294
+ });
295
+ /** Read-only catalog metadata for one App, without host-specific role policy. */
296
+ export const appCatalogEntrySchema = appDirectoryEntrySchema
297
+ .pick({
298
+ surfaceId: true,
299
+ appId: true,
300
+ displayName: true,
301
+ })
302
+ .extend({
303
+ /** The Gills the App declares for the desktop. */
304
+ gills: z.array(catalogGillSchema).max(16),
305
+ });
306
+ /** Read-only desktop metadata; identities are surface addresses, never launch authority. */
307
+ export const appCatalogResponseSchema = z.lazy(() => z.strictObject({
308
+ /** The Apps in the catalog. */
309
+ apps: z.array(appCatalogEntrySchema).max(50),
310
+ /** The role selections the catalog reports. */
311
+ roles: z.strictObject({
312
+ /** The selection for the inbox role, when there is one. */
313
+ inbox: appRoleSelectionSchema.optional(),
314
+ }),
315
+ }));
316
+ /** Complete APPS.catalog reply, including broker admission failures. */
317
+ export const appCatalogBrokerResultSchema = z.lazy(() => z.union([
318
+ appCatalogResponseSchema,
319
+ z.strictObject({
320
+ /** Why the broker refused the catalog request. */
321
+ error: z.string(),
322
+ }),
323
+ ]));
@@ -0,0 +1,7 @@
1
+ import type { AppBrowserAction, AppBrowserActionOutcome } from "@fungi.computer/agar";
2
+ /** Browser effects stay in the trusted host, bound to one mounted App frame. */
3
+ export declare function createBrowserActionHandler(options: {
4
+ frame: HTMLIFrameElement;
5
+ hostWindow: Window;
6
+ signal: AbortSignal;
7
+ }): (request: AppBrowserAction) => Promise<AppBrowserActionOutcome>;
@@ -0,0 +1,56 @@
1
+ import { APP_BROWSER_SAVE_MAX_BYTES, APP_BROWSER_COPY_MAX_BYTES, } from "@fungi.computer/agar";
2
+ /** Browser effects stay in the trusted host, bound to one mounted App frame. */
3
+ export function createBrowserActionHandler(options) {
4
+ let pending = false;
5
+ return async (request) => {
6
+ const { frame, hostWindow, signal } = options;
7
+ if (signal.aborted || !frame.isConnected)
8
+ return { kind: "refused", reason: "unavailable" };
9
+ if (!hostWindow.navigator.userActivation.isActive ||
10
+ hostWindow.document.activeElement !== frame)
11
+ return { kind: "refused", reason: "no-user-gesture" };
12
+ if (request.kind === "saveFile"
13
+ ? request.bytes.byteLength > APP_BROWSER_SAVE_MAX_BYTES
14
+ : new TextEncoder().encode(request.text).byteLength >
15
+ APP_BROWSER_COPY_MAX_BYTES)
16
+ return { kind: "refused", reason: "too-large" };
17
+ if (pending)
18
+ return { kind: "refused", reason: "busy" };
19
+ pending = true;
20
+ try {
21
+ if (request.kind === "copyText") {
22
+ await hostWindow.navigator.clipboard.writeText(request.text);
23
+ return { kind: "copied" };
24
+ }
25
+ const blob = new Blob([new Uint8Array(request.bytes)], {
26
+ type: request.mime,
27
+ });
28
+ const url = URL.createObjectURL(blob);
29
+ try {
30
+ const link = hostWindow.document.createElement("a");
31
+ link.href = url;
32
+ link.download = request.name;
33
+ link.click();
34
+ }
35
+ finally {
36
+ // Keep the bytes alive until the browser handles the navigation task.
37
+ hostWindow.setTimeout(() => {
38
+ URL.revokeObjectURL(url);
39
+ }, 0);
40
+ }
41
+ // The browser accepted a download; this does not attest a disk write.
42
+ return { kind: "saved" };
43
+ }
44
+ catch (cause) {
45
+ return {
46
+ kind: "refused",
47
+ reason: cause instanceof DOMException && cause.name === "NotAllowedError"
48
+ ? "denied"
49
+ : "unavailable",
50
+ };
51
+ }
52
+ finally {
53
+ pending = false;
54
+ }
55
+ };
56
+ }
@@ -0,0 +1,140 @@
1
+ /** An optional field a declaration may carry as explicitly undefined. */
2
+ export type AppCommandOptional<T> = T | undefined;
3
+ /**
4
+ * The digest-covered command rows a mounted App's Release declares, as the
5
+ * host receives them. Oyster compares and projects them; the Release
6
+ * declaration grammar stays with its owner, which hands rows of this shape.
7
+ */
8
+ export type AppCommandDeclaration = Readonly<{
9
+ /** The command's id inside the App's Release declaration. */
10
+ id: string;
11
+ /** The name people see for the command. */
12
+ title: string;
13
+ /** The group the command is listed under. */
14
+ category: string;
15
+ /** What the command does, in a sentence. */
16
+ description?: AppCommandOptional<string>;
17
+ /** Where the command is offered, with a sort position for each place. */
18
+ projections?: AppCommandOptional<Readonly<{
19
+ /** Key bindings that run the command. */
20
+ bindings?: AppCommandOptional<Readonly<{
21
+ /** Sort position among the binding rows. */
22
+ order?: AppCommandOptional<number>;
23
+ /**
24
+ * The binding strings, such as alt+left or a sequence after the
25
+ * leader key.
26
+ */
27
+ values: readonly string[];
28
+ }>>;
29
+ /** The command as offered to an agent. */
30
+ agent?: AppCommandOptional<Readonly<{
31
+ /** Sort position among the agent rows. */
32
+ order?: AppCommandOptional<number>;
33
+ /** The key binding an agent uses to run the command. */
34
+ keybind?: AppCommandOptional<string>;
35
+ }>>;
36
+ /** The command as listed in help. */
37
+ help?: AppCommandOptional<Readonly<{
38
+ /** Sort position among the help rows. */
39
+ order?: AppCommandOptional<number>;
40
+ }>>;
41
+ /** The command as listed in a menu. */
42
+ menu?: AppCommandOptional<Readonly<{
43
+ /** Sort position among the menu rows. */
44
+ order?: AppCommandOptional<number>;
45
+ }>>;
46
+ /** The command as listed in the command palette. */
47
+ palette?: AppCommandOptional<Readonly<{
48
+ /** Sort position among the palette rows. */
49
+ order?: AppCommandOptional<number>;
50
+ }>>;
51
+ /** The command as a slash command. */
52
+ slash?: AppCommandOptional<Readonly<{
53
+ /** Sort position among the slash rows. */
54
+ order?: AppCommandOptional<number>;
55
+ /** Other slash names that run the command. */
56
+ aliases: readonly string[];
57
+ }>>;
58
+ }>>;
59
+ }>;
60
+ import type { AppHostHandlers, AppHostReservedChord } from "@fungi.computer/agar/host";
61
+ import { type WhistleExecutionInput, type WhistleExecutionOutcome, type WhistleContributionLease, type WhistleCommand, type WhistleMenuProjection, type WhistleRuntime, type WhistleSnapshot } from "@fungi.computer/whistle";
62
+ /** Select the shell command-palette chord for a Mac or non-Mac keyboard. */
63
+ export declare function commandPaletteReservedChordForPlatform(mac: boolean): AppHostReservedChord;
64
+ /** Select the shell command-palette chord from the embedding browser user agent. */
65
+ export declare function commandPaletteReservedChordForUserAgent(userAgent: string): AppHostReservedChord;
66
+ /** Whistle source of Release-advertised rows for Apps not yet mounted. */
67
+ export declare const appDirectorySourceId = "app-directory";
68
+ /** The command side of one mounted App's host handlers. */
69
+ export type AppSurfaceCommandMount = Readonly<{
70
+ /** Borrow this mount's live Agent projection and gated execution, not the shell graph. */
71
+ readonly agentCommands: Pick<WhistleRuntime, "snapshot" | "execute">;
72
+ /** The narrow handler shape consumed by Agar's App host connection. */
73
+ readonly handlers: Pick<AppHostHandlers, "registerCommands" | "invokeReservedChord">;
74
+ /** Release this App's contribution and its mount identity. */
75
+ readonly dispose: () => void;
76
+ }>;
77
+ /** Commands owned by one pane, projected to globals plus its focus scope. */
78
+ export type PaneCommands = Readonly<{
79
+ /** Qualify a locally contributed id in this pane's own namespace. */
80
+ commandId: (id: string) => string;
81
+ /** Contribute or replace commands owned by this pane through the returned lease. */
82
+ contribute: (commands: readonly WhistleCommand<never>[]) => WhistleContributionLease;
83
+ /** Run a command by id. */
84
+ execute: WhistleRuntime["execute"];
85
+ /** Read the commands and bindings currently available. */
86
+ snapshot: WhistleRuntime["snapshot"];
87
+ /** Be told when the available commands change. */
88
+ subscribe: WhistleRuntime["subscribe"];
89
+ /** Registered locally; only the focused pane presents human executions. */
90
+ present: WhistleRuntime["present"];
91
+ }>;
92
+ /** Closing a pane withdraws all its leases, presenters and subscriptions. */
93
+ export type PaneCommandMount = PaneCommands & Readonly<{
94
+ /** Withdraw this pane's contributions, presenters and subscriptions. */
95
+ dispose: () => void;
96
+ }>;
97
+ /** The shell-owned Whistle graph and its focused menu projection. */
98
+ export type AppSurfaceCommandHost = Readonly<{
99
+ /** Create the command scope for one locally rendered pane. */
100
+ readonly mountPane: (paneId: string) => PaneCommandMount;
101
+ /** Create one command admission scope for a mounted App. */
102
+ readonly mount: (mountId: string, declarations: readonly AppCommandDeclaration[]) => AppSurfaceCommandMount;
103
+ /** Select the App or pane whose commands are the active human-facing ones. */
104
+ readonly focus: (mountId: string | null) => void;
105
+ /** Register the shell's own global commands in the shared Whistle graph. */
106
+ readonly registerShellCommands: (commands: readonly WhistleCommand<never>[]) => () => void;
107
+ /** Publish verified Release command rows before their App is mounted. */
108
+ readonly registerDirectoryCommands: (commands: readonly Readonly<{
109
+ surfaceId: string;
110
+ appTitle: string;
111
+ command: AppCommandDeclaration;
112
+ }>[], run: (surfaceId: string, commandId: string) => Promise<void>) => () => void;
113
+ /** Read the menu rows belonging to the focused App. */
114
+ readonly menu: () => readonly WhistleMenuProjection[];
115
+ /**
116
+ * The human-facing commands for the current focus: shell rows plus the
117
+ * focused mount's rows, with its aliases and keys shadowing the shell's.
118
+ */
119
+ readonly active: () => WhistleSnapshot;
120
+ /** Execute an active row, an agent row, or an exact programmatic action. */
121
+ readonly execute: (commandId: string, input: WhistleExecutionInput) => Promise<WhistleExecutionOutcome>;
122
+ /** Read the complete renderer-neutral graph for host consumers. */
123
+ readonly snapshot: () => WhistleSnapshot;
124
+ /** Observe graph changes, including contribution replacement and disposal. */
125
+ readonly subscribe: (listener: () => void) => () => void;
126
+ /** Dispose all App contributions and close the shell runtime. */
127
+ readonly dispose: () => void;
128
+ }>;
129
+ /**
130
+ * Own one shell's Whistle runtime for mounted Apps and local panes. The
131
+ * embedding host supplies verified Release declarations and decides which
132
+ * App or pane has focus.
133
+ *
134
+ * Agar invokes `registerCommands` once for the aggregate command set of a
135
+ * mount. The adapter keeps that aggregate as one Whistle generation, so an
136
+ * Agar update replaces the generation atomically and disposal releases only
137
+ * that App. Command handlers remain the App's callback; the shell only adds
138
+ * the Whistle identity and routes focused menu execution to it.
139
+ */
140
+ export declare function createAppSurfaceCommandHost(whistle?: WhistleRuntime): AppSurfaceCommandHost;