@fungi.computer/tourtois 0.1.0 → 0.2.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 (52) hide show
  1. package/README.md +19 -0
  2. package/dist/AppAccessConsent.d.ts +6 -1
  3. package/dist/AppAccessConsent.js +4 -5
  4. package/dist/AppDesktopSettings.d.ts +2 -2
  5. package/dist/AppPane.d.ts +11 -4
  6. package/dist/AppPane.js +50 -10
  7. package/dist/AppWorkbench.d.ts +1 -1
  8. package/dist/AppWorkbench.js +10 -5
  9. package/dist/AvatarPreview.d.ts +8 -1
  10. package/dist/AvatarPreview.js +9 -1
  11. package/dist/Keys.d.ts +42 -121
  12. package/dist/Keys.js +200 -157
  13. package/dist/OysterComputers.d.ts +22 -0
  14. package/dist/OysterComputers.js +177 -0
  15. package/dist/SettingsNavigation.d.ts +1 -1
  16. package/dist/SettingsNavigation.js +1 -1
  17. package/dist/SystemSettings.d.ts +2 -2
  18. package/dist/SystemSettings.js +1 -1
  19. package/dist/TeamDesktop.d.ts +1 -1
  20. package/dist/app-authority.d.ts +37 -37
  21. package/dist/app-authority.js +1 -1
  22. package/dist/app-directory-reconciliation.js +7 -2
  23. package/dist/app-directory.d.ts +6 -5
  24. package/dist/app-directory.js +4 -1
  25. package/dist/app-launch.d.ts +1 -1
  26. package/dist/app-launch.js +6 -3
  27. package/dist/app-menus.d.ts +1 -1
  28. package/dist/app-surface.d.ts +2 -1
  29. package/dist/app-windows.d.ts +5 -2
  30. package/dist/app-windows.js +1 -1
  31. package/dist/command-dialogs.d.ts +1 -1
  32. package/dist/command-dialogs.js +4 -4
  33. package/dist/desktop/Desktop.d.ts +1 -1
  34. package/dist/desktop/consent.d.ts +1 -1
  35. package/dist/desktop/consent.js +1 -1
  36. package/dist/desktop/row-gill.d.ts +1 -1
  37. package/dist/oyster-platform-controls.d.ts +1 -1
  38. package/dist/oyster-platform-controls.js +64 -23
  39. package/dist/platform-controls.d.ts +6 -5
  40. package/dist/platform-controls.js +3 -3
  41. package/dist/queries.d.ts +4 -0
  42. package/dist/queries.js +4 -0
  43. package/dist/shell-commands.d.ts +1 -1
  44. package/dist/shell-commands.js +4 -4
  45. package/dist/shell-host.d.ts +7 -9
  46. package/dist/shell-host.js +36 -10
  47. package/dist/shell-module.d.ts +1 -1
  48. package/dist/shell.d.ts +1 -1
  49. package/dist/shell.js +5 -9
  50. package/dist/useAppWindowManager.d.ts +4 -1
  51. package/dist/useAppWindowManager.js +3 -0
  52. package/package.json +39 -18
@@ -1,6 +1,6 @@
1
1
  import { type AppDesktopSettingsApp } from "./AppDesktopSettings.js";
2
- import { type AppDirectoryEntry } from "@fungi.computer/oyster/shell/app-directory";
3
- import type { WorkbenchDesktopSettings } from "@fungi.computer/oyster/shell/workbench";
2
+ import { type AppDirectoryEntry } from "@fungi.computer/nacre/app-directory";
3
+ import type { WorkbenchDesktopSettings } from "@fungi.computer/nacre/workbench";
4
4
  import { type SettingsSectionPorts } from "./settings-sections.js";
5
5
  import type { SettingsLocation } from "./settings-routes.js";
6
6
  /** One system-settings surface. The menu only requests opening it. */
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { AppCallPermissions } from "./AppCallPermissions.js";
3
3
  import { SectionHeading } from "@fungi.computer/caps/blocks/section-heading";
4
4
  import { AppDesktopSettings, } from "./AppDesktopSettings.js";
5
- import { addressKey, } from "@fungi.computer/oyster/shell/app-directory";
5
+ import { addressKey, } from "@fungi.computer/nacre/app-directory";
6
6
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
7
7
  import { AgentMcpServers } from "./AgentMcpServers.js";
8
8
  import { Keys } from "./Keys.js";
@@ -3,7 +3,7 @@ import type { ComputerOption } from "./app-launch.js";
3
3
  import type { SettingsLocation } from "./settings-routes.js";
4
4
  import type { ReactNode } from "react";
5
5
  import type { AppBarItem } from "@fungi.computer/caps/blocks/app-bar";
6
- import type { WorkbenchDirectory } from "@fungi.computer/oyster/shell/workbench-directory";
6
+ import type { WorkbenchDirectory } from "@fungi.computer/nacre/workbench-directory";
7
7
  import type { PlatformControls } from "./platform-controls.js";
8
8
  import type { AppStorage } from "@fungi.computer/agar/storage";
9
9
  import type { DesktopGillTransport } from "./desktop/row-gill.js";
@@ -1,47 +1,47 @@
1
1
  import * as z from "zod";
2
- import { type AppAddress } from "@fungi.computer/oyster/shell/app-directory";
2
+ import { type AppAddress } from "@fungi.computer/nacre/app-directory";
3
3
  export declare const appPermissionChangesSchema: z.ZodObject<{
4
4
  computer: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodLazy<z.ZodObject<{
5
5
  kind: z.ZodLiteral<"selected-computers">;
6
6
  computers: z.ZodArray<z.ZodObject<{
7
7
  computerId: z.ZodString;
8
8
  operations: z.ZodArray<z.ZodEnum<{
9
- "computer.list": "computer.list";
10
- "computer.readFile": "computer.readFile";
11
- "computer.writeFile": "computer.writeFile";
12
- "computer.execute": "computer.execute";
9
+ readonly "computer.list": "computer.list";
10
+ readonly "computer.readFile": "computer.readFile";
11
+ readonly "computer.writeFile": "computer.writeFile";
12
+ readonly "computer.execute": "computer.execute";
13
13
  }>>;
14
14
  }, z.core.$strict>>;
15
15
  }, z.core.$strict>>, z.ZodLazy<z.ZodObject<{
16
16
  kind: z.ZodLiteral<"all-team-computers">;
17
17
  operations: z.ZodArray<z.ZodEnum<{
18
- "computer.list": "computer.list";
19
- "computer.readFile": "computer.readFile";
20
- "computer.writeFile": "computer.writeFile";
21
- "computer.execute": "computer.execute";
18
+ readonly "computer.list": "computer.list";
19
+ readonly "computer.readFile": "computer.readFile";
20
+ readonly "computer.writeFile": "computer.writeFile";
21
+ readonly "computer.execute": "computer.execute";
22
22
  }>>;
23
23
  }, z.core.$strict>>], "kind">>;
24
24
  agents: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
25
25
  kind: z.ZodLiteral<"all-team-agents">;
26
26
  directoryOperations: z.ZodArray<z.ZodEnum<{
27
- "agent.list": "agent.list";
27
+ readonly "agent.list": "agent.list";
28
28
  }>>;
29
29
  sessionOperations: z.ZodArray<z.ZodEnum<{
30
- "agent.read": "agent.read";
31
- "agent.send": "agent.send";
32
- "agent.manage": "agent.manage";
30
+ readonly "agent.read": "agent.read";
31
+ readonly "agent.send": "agent.send";
32
+ readonly "agent.manage": "agent.manage";
33
33
  }>>;
34
34
  }, z.core.$strict>, z.ZodObject<{
35
35
  kind: z.ZodLiteral<"selected-agents">;
36
36
  directoryOperations: z.ZodArray<z.ZodEnum<{
37
- "agent.list": "agent.list";
37
+ readonly "agent.list": "agent.list";
38
38
  }>>;
39
39
  agents: z.ZodArray<z.ZodObject<{
40
40
  agentId: z.ZodString;
41
41
  sessionOperations: z.ZodArray<z.ZodEnum<{
42
- "agent.read": "agent.read";
43
- "agent.send": "agent.send";
44
- "agent.manage": "agent.manage";
42
+ readonly "agent.read": "agent.read";
43
+ readonly "agent.send": "agent.send";
44
+ readonly "agent.manage": "agent.manage";
45
45
  }>>;
46
46
  }, z.core.$strict>>;
47
47
  }, z.core.$strict>], "kind">>;
@@ -63,42 +63,42 @@ export declare const appPermissionReceiptSchema: z.ZodObject<{
63
63
  computers: z.ZodArray<z.ZodObject<{
64
64
  computerId: z.ZodString;
65
65
  operations: z.ZodArray<z.ZodEnum<{
66
- "computer.list": "computer.list";
67
- "computer.readFile": "computer.readFile";
68
- "computer.writeFile": "computer.writeFile";
69
- "computer.execute": "computer.execute";
66
+ readonly "computer.list": "computer.list";
67
+ readonly "computer.readFile": "computer.readFile";
68
+ readonly "computer.writeFile": "computer.writeFile";
69
+ readonly "computer.execute": "computer.execute";
70
70
  }>>;
71
71
  }, z.core.$strict>>;
72
72
  }, z.core.$strict>>, z.ZodLazy<z.ZodObject<{
73
73
  kind: z.ZodLiteral<"all-team-computers">;
74
74
  operations: z.ZodArray<z.ZodEnum<{
75
- "computer.list": "computer.list";
76
- "computer.readFile": "computer.readFile";
77
- "computer.writeFile": "computer.writeFile";
78
- "computer.execute": "computer.execute";
75
+ readonly "computer.list": "computer.list";
76
+ readonly "computer.readFile": "computer.readFile";
77
+ readonly "computer.writeFile": "computer.writeFile";
78
+ readonly "computer.execute": "computer.execute";
79
79
  }>>;
80
80
  }, z.core.$strict>>], "kind">>;
81
81
  agentPermission: z.ZodDiscriminatedUnion<[z.ZodObject<{
82
82
  kind: z.ZodLiteral<"all-team-agents">;
83
83
  directoryOperations: z.ZodArray<z.ZodEnum<{
84
- "agent.list": "agent.list";
84
+ readonly "agent.list": "agent.list";
85
85
  }>>;
86
86
  sessionOperations: z.ZodArray<z.ZodEnum<{
87
- "agent.read": "agent.read";
88
- "agent.send": "agent.send";
89
- "agent.manage": "agent.manage";
87
+ readonly "agent.read": "agent.read";
88
+ readonly "agent.send": "agent.send";
89
+ readonly "agent.manage": "agent.manage";
90
90
  }>>;
91
91
  }, z.core.$strict>, z.ZodObject<{
92
92
  kind: z.ZodLiteral<"selected-agents">;
93
93
  directoryOperations: z.ZodArray<z.ZodEnum<{
94
- "agent.list": "agent.list";
94
+ readonly "agent.list": "agent.list";
95
95
  }>>;
96
96
  agents: z.ZodArray<z.ZodObject<{
97
97
  agentId: z.ZodString;
98
98
  sessionOperations: z.ZodArray<z.ZodEnum<{
99
- "agent.read": "agent.read";
100
- "agent.send": "agent.send";
101
- "agent.manage": "agent.manage";
99
+ readonly "agent.read": "agent.read";
100
+ readonly "agent.send": "agent.send";
101
+ readonly "agent.manage": "agent.manage";
102
102
  }>>;
103
103
  }, z.core.$strict>>;
104
104
  }, z.core.$strict>], "kind">;
@@ -140,10 +140,10 @@ export declare const appMutationResponseSchema: z.ZodObject<{
140
140
  }, z.core.$strict>]>>;
141
141
  authorityVersion: z.ZodNumber;
142
142
  state: z.ZodEnum<{
143
- unaffected: "unaffected";
144
- pending: "pending";
145
- applied: "applied";
146
- expired: "expired";
143
+ readonly unaffected: "unaffected";
144
+ readonly pending: "pending";
145
+ readonly applied: "applied";
146
+ readonly expired: "expired";
147
147
  }>;
148
148
  outstanding: z.ZodNumber;
149
149
  }, z.core.$strict>>>;
@@ -1,5 +1,5 @@
1
1
  import * as z from "zod";
2
- import { appAuthorityEnforcementSchema, appComputerPermissionSchema, appAgentPermissionSchema, appGenerationPermissionsSchema, appMarketplacePermissionsSchema, } from "@fungi.computer/oyster/shell/app-directory";
2
+ import { appAuthorityEnforcementSchema, appComputerPermissionSchema, appAgentPermissionSchema, appGenerationPermissionsSchema, appMarketplacePermissionsSchema, } from "@fungi.computer/nacre/app-directory";
3
3
  export const appPermissionChangesSchema = z
4
4
  .strictObject({
5
5
  computer: appComputerPermissionSchema.optional(),
@@ -62,11 +62,16 @@ function appLaunchRequiresReplacement(current, next) {
62
62
  function appLaunchRequiresDirectoryUpdate(current, next) {
63
63
  return (next.appId !== current.appId ||
64
64
  next.surfaceId !== current.surfaceId ||
65
- stableLaunchHref(next.href, true) !== stableLaunchHref(current.href, true));
65
+ stableLaunchHref(next.href, true, next.releaseId !== current.releaseId) !==
66
+ stableLaunchHref(current.href, true, next.releaseId !== current.releaseId));
66
67
  }
67
- function stableLaunchHref(href, ignoreReleaseId = false) {
68
+ function stableLaunchHref(href, ignoreReleaseId = false, ignoreAuthorityVersion = false) {
68
69
  const url = new URL(href, "https://tourtois.invalid");
69
70
  if (ignoreReleaseId)
70
71
  url.searchParams.delete("releaseId");
72
+ // A promotion changes both selection facts. Leave the admitted mount alone
73
+ // until Reload; other authority/permission changes still reconcile normally.
74
+ if (ignoreAuthorityVersion)
75
+ url.searchParams.delete("authorityVersion");
71
76
  return url.pathname + url.search;
72
77
  }
@@ -1,4 +1,4 @@
1
- import { type WorkbenchDirectory } from "@fungi.computer/oyster/shell/workbench-directory";
1
+ import { type WorkbenchDirectory } from "@fungi.computer/nacre/workbench-directory";
2
2
  /** Shell composition keeps an unread directory distinct from an empty one. */
3
3
  export declare function useAppDirectory(teamId: string, directoryReader: () => Promise<WorkbenchDirectory>): {
4
4
  query: import("@tanstack/react-query").UseQueryResult<{
@@ -64,7 +64,7 @@ export declare function useAppDirectory(teamId: string, directoryReader: () => P
64
64
  }>) | undefined;
65
65
  }> | undefined;
66
66
  }>[];
67
- gills: import("@fungi.computer/oyster/shell/app-directory").AppDirectoryGill[];
67
+ gills: import("@fungi.computer/nacre/app-directory").AppDirectoryGill[];
68
68
  availability: "available" | "unavailable";
69
69
  surface: {
70
70
  stableRoute: string;
@@ -76,7 +76,7 @@ export declare function useAppDirectory(teamId: string, directoryReader: () => P
76
76
  readonly appId: string;
77
77
  readonly methods: readonly string[];
78
78
  }[] | undefined;
79
- computer?: {
79
+ computer?: "owner-fleet" | {
80
80
  computerId: string;
81
81
  name: string;
82
82
  } | undefined;
@@ -99,6 +99,7 @@ export declare function useAppDirectory(teamId: string, directoryReader: () => P
99
99
  computerId: string;
100
100
  name: string;
101
101
  } | undefined;
102
+ computerContext?: "owner-fleet" | undefined;
102
103
  needsUpdate?: {
103
104
  installationId: string | null;
104
105
  appId: string | null;
@@ -167,7 +168,7 @@ export declare function useAppDirectory(teamId: string, directoryReader: () => P
167
168
  }>) | undefined;
168
169
  }> | undefined;
169
170
  }>[];
170
- gills: import("@fungi.computer/oyster/shell/app-directory").AppDirectoryGill[];
171
+ gills: import("@fungi.computer/nacre/app-directory").AppDirectoryGill[];
171
172
  availability: "available" | "unavailable";
172
173
  surface: {
173
174
  stableRoute: string;
@@ -179,7 +180,7 @@ export declare function useAppDirectory(teamId: string, directoryReader: () => P
179
180
  readonly appId: string;
180
181
  readonly methods: readonly string[];
181
182
  }[] | undefined;
182
- computer?: {
183
+ computer?: "owner-fleet" | {
183
184
  computerId: string;
184
185
  name: string;
185
186
  } | undefined;
@@ -1,6 +1,6 @@
1
1
  import { tourtoisQueryKeys } from "./queries.js";
2
2
  import { useQuery } from "@tanstack/react-query";
3
- import { workbenchDirectorySchema, } from "@fungi.computer/oyster/shell/workbench-directory";
3
+ import { workbenchDirectorySchema, } from "@fungi.computer/nacre/workbench-directory";
4
4
  const noRoles = {};
5
5
  /** Shell composition keeps an unread directory distinct from an empty one. */
6
6
  export function useAppDirectory(teamId, directoryReader) {
@@ -11,6 +11,9 @@ export function useAppDirectory(teamId, directoryReader) {
11
11
  // The host's selection read can finish before Tourtois mounts. Keep the
12
12
  // result fresh for that handoff; explicit mutations still invalidate it.
13
13
  staleTime: 5_000,
14
+ // Quiet open Apps need release availability too, not just after a request
15
+ // is refused. React Query pauses this polling while the page is hidden.
16
+ refetchInterval: 15_000,
14
17
  });
15
18
  return {
16
19
  query,
@@ -2,7 +2,7 @@ import type { AppOpenInput } from "@fungi.computer/agar/apps";
2
2
  import type { WhistleSnapshot } from "@fungi.computer/whistle";
3
3
  import { type TourtoisAppSurfaceHost } from "./app-surface.js";
4
4
  import { createAppWindowManager, type AppLaunchDescriptor, type AppWindowManager, type AppWindowProjection } from "./app-windows.js";
5
- import type { WorkbenchDirectory } from "@fungi.computer/oyster/shell/workbench-directory";
5
+ import type { WorkbenchDirectory } from "@fungi.computer/nacre/workbench-directory";
6
6
  export type AppDirectoryApp = WorkbenchDirectory["apps"][number];
7
7
  export type AppDirectoryRoles = WorkbenchDirectory["roles"];
8
8
  /** The Team's effective App for a role when it can be launched now. */
@@ -114,10 +114,13 @@ export function sameOfferedComputers(left, right) {
114
114
  }));
115
115
  }
116
116
  function computersForApp(app, computers) {
117
- // A standalone host supplies its fixed context, not fabricated Team grants.
117
+ // Standalone context is fixed or owner-fleet, never fabricated Team grants.
118
118
  // Only an offered inventory entry can become a window's Computer choice.
119
- if (app.computer !== undefined)
120
- return computers.filter((computer) => computer.computerId === app.computer?.computerId);
119
+ const context = app.computer;
120
+ if (context === "owner-fleet")
121
+ return computers;
122
+ if (context !== undefined)
123
+ return computers.filter((computer) => computer.computerId === context.computerId);
121
124
  const permission = app.computerPermission;
122
125
  if (!permission)
123
126
  return [];
@@ -1,4 +1,4 @@
1
- import type { AppSurfaceCommandHost } from "@fungi.computer/oyster/shell/command-host";
1
+ import type { AppSurfaceCommandHost } from "@fungi.computer/nacre/command-host";
2
2
  /** Render the focused App's Whistle commands in the desktop's system bar. */
3
3
  export declare function AppMenus({ host, container: popover, }: Readonly<{
4
4
  host: Pick<AppSurfaceCommandHost, "subscribe" | "menu" | "execute"> | null;
@@ -4,8 +4,9 @@ import type { AgentSessionSelection, AppNavigationState, AppStatusItem, FileNavi
4
4
  import type { AppOpenInput, AppRoleChoice } from "@fungi.computer/agar/apps";
5
5
  import type { AppDeclaredCommand } from "@fungi.computer/puffball/app";
6
6
  import type { AppWindowMount } from "./app-windows.js";
7
+ import type { WindowEvents } from "@fungi.computer/nacre/window-manager";
7
8
  /** What one mounted App may raise toward the shell. */
8
- export type TourtoisAppMountEvents = Readonly<{
9
+ export type TourtoisAppMountEvents = Pick<WindowEvents, "onDeliveryPending"> & Readonly<{
9
10
  onComputerChoice?: () => Promise<void>;
10
11
  onAgentSessionSelection: (selection: AgentSessionSelection | null) => Promise<void>;
11
12
  /** A presentation-neutral file handoff to another App. */
@@ -1,5 +1,5 @@
1
1
  import type { AppOpenRequest } from "@fungi.computer/agar";
2
- import { type WindowHost, type WindowEvents, type WindowProjection, type WindowMount, type WindowManager, type WindowManagerOptions } from "@fungi.computer/oyster/shell/window-manager";
2
+ import { type WindowHost, type WindowEvents, type WindowProjection, type WindowMount, type WindowManager, type WindowManagerOptions } from "@fungi.computer/nacre/window-manager";
3
3
  export type AppLaunchDescriptor = Readonly<{
4
4
  windowId: string;
5
5
  /** Stable presentation identity across default inheritance/customization. */
@@ -92,7 +92,7 @@ export declare const hasVisibleAppWindows: (projection: Readonly<{
92
92
  }>[];
93
93
  }>;
94
94
  container: HTMLElement;
95
- status: import("@fungi.computer/oyster/shell/window-manager").WindowStatus;
95
+ status: import("@fungi.computer/nacre/window-manager").WindowStatus;
96
96
  generation: number;
97
97
  error?: unknown;
98
98
  cleanupError?: unknown;
@@ -110,6 +110,7 @@ export declare const hasVisibleAppWindows: (projection: Readonly<{
110
110
  primary?: readonly Readonly<{
111
111
  id: string;
112
112
  label: string;
113
+ icon?: "play";
113
114
  variant?: "primary" | "outline" | "ghost";
114
115
  tone?: "destructive";
115
116
  disabled?: boolean;
@@ -117,6 +118,7 @@ export declare const hasVisibleAppWindows: (projection: Readonly<{
117
118
  menu?: readonly Readonly<{
118
119
  id: string;
119
120
  label: string;
121
+ icon?: "play";
120
122
  variant?: "primary" | "outline" | "ghost";
121
123
  tone?: "destructive";
122
124
  disabled?: boolean;
@@ -124,6 +126,7 @@ export declare const hasVisibleAppWindows: (projection: Readonly<{
124
126
  status?: readonly Readonly<{
125
127
  id: string;
126
128
  label: string;
129
+ presentation?: "bead";
127
130
  tone?: "neutral" | "info" | "warning" | "success" | "error";
128
131
  }>[];
129
132
  }> | null;
@@ -1,4 +1,4 @@
1
- import { createWindowManager, hasVisibleWindows, } from "@fungi.computer/oyster/shell/window-manager";
1
+ import { createWindowManager, hasVisibleWindows, } from "@fungi.computer/nacre/window-manager";
2
2
  export const hasVisibleAppWindows = (hasVisibleWindows);
3
3
  /** DOM attachment and App identity belong to the browser host. */
4
4
  export function createAppWindowManager(options) {
@@ -1,7 +1,7 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { WhistleSnapshot } from "@fungi.computer/whistle";
3
3
  import type { AppDeclaredCommand } from "@fungi.computer/puffball/app";
4
- import { type AppSurfaceCommandHost } from "@fungi.computer/oyster/shell/command-host";
4
+ import { type AppSurfaceCommandHost } from "@fungi.computer/nacre/command-host";
5
5
  /** Current command view and the host-owned dialog visibility. */
6
6
  export type ShellCommandState = Readonly<{
7
7
  active: WhistleSnapshot;
@@ -3,8 +3,8 @@ import { useCallback, useLayoutEffect, useMemo } from "react";
3
3
  import { CommandMenu } from "@fungi.computer/caps/blocks/command-menu";
4
4
  import { Combobox } from "@fungi.computer/caps/components/combobox";
5
5
  import { Dialog, DialogContent, DialogDescription, DialogTitle, } from "@fungi.computer/caps/components/dialog";
6
- import { appDirectorySourceId, } from "@fungi.computer/oyster/shell/command-host";
7
- import { leaderKeyMatches, leaderKeys, parseChord, } from "@fungi.computer/oyster/shell/keys";
6
+ import { appDirectorySourceId, } from "@fungi.computer/nacre/command-host";
7
+ import { leaderKeyMatches, leaderKeys, parseChord, } from "@fungi.computer/nacre/keys";
8
8
  function isMac() {
9
9
  return /Mac|iPhone|iPad/u.test(navigator.userAgent);
10
10
  }
@@ -37,9 +37,9 @@ function commandKeycaps(commandId, active, mac, declared) {
37
37
  if (leader !== undefined)
38
38
  return [mac ? "⌘ K" : "Ctrl K", "then", chordKey(leader.key)];
39
39
  const binding = active.bindings.find((row) => row.commandId === commandId);
40
- const declaredBinding = declared?.projections?.bindings?.values.find((value) => value.startsWith("<leader> "));
40
+ const declaredBinding = declared?.projections?.bindings?.values.find((value) => value.startsWith("<leader>"));
41
41
  if (declaredBinding !== undefined)
42
- return [mac ? "⌘ K" : "Ctrl K", "then", chordKey(declaredBinding.slice(9))];
42
+ return [mac ? "⌘ K" : "Ctrl K", "then", chordKey(declaredBinding.slice(8))];
43
43
  if (binding === undefined)
44
44
  return [];
45
45
  const chord = parseChord(binding.binding, mac);
@@ -2,7 +2,7 @@ import type { AppOpenInput } from "@fungi.computer/agar/apps";
2
2
  import type { DesktopMethods } from "../desktop-api.js";
3
3
  import type { AppStorage } from "@fungi.computer/agar/storage";
4
4
  import type { AppBarItem } from "@fungi.computer/caps/blocks/app-bar";
5
- import type { WorkbenchDirectory } from "@fungi.computer/oyster/shell/workbench-directory";
5
+ import type { WorkbenchDirectory } from "@fungi.computer/nacre/workbench-directory";
6
6
  import { type DesktopLayoutClient } from "../desktop-client.js";
7
7
  import { type DesktopGillTransport } from "./row-gill.js";
8
8
  export type DesktopProps = {
@@ -1,4 +1,4 @@
1
- import { type WorkbenchDesktopSettings } from "@fungi.computer/oyster/shell/workbench";
1
+ import { type WorkbenchDesktopSettings } from "@fungi.computer/nacre/workbench";
2
2
  import type { DesktopLayoutClient } from "../desktop-client.js";
3
3
  /** Only the host's member-consent UI invokes this admitted dashboard callback. */
4
4
  export declare function desktopSettingsHandler(current: () => DesktopLayoutClient | null): WorkbenchDesktopSettings;
@@ -1,5 +1,5 @@
1
1
  import { AppError } from "@fungi.computer/agar";
2
- import { desktopGillAdditionSchema, desktopGillSelectionSchema, } from "@fungi.computer/oyster/shell/workbench";
2
+ import { desktopGillAdditionSchema, desktopGillSelectionSchema, } from "@fungi.computer/nacre/workbench";
3
3
  /** Only the host's member-consent UI invokes this admitted dashboard callback. */
4
4
  export function desktopSettingsHandler(current) {
5
5
  const owner = () => {
@@ -1,7 +1,7 @@
1
1
  import type { AppBarItem } from "@fungi.computer/caps/blocks/app-bar";
2
2
  import { type ReactNode } from "react";
3
3
  import { type GillResponse } from "@fungi.computer/gills";
4
- import type { TeamWorkbenchHostHandle } from "@fungi.computer/oyster/shell/workbench";
4
+ import type { TeamWorkbenchHostHandle } from "@fungi.computer/nacre/workbench";
5
5
  import type { AppDeclaredGill, AppGillDesktopSize } from "@fungi.computer/gills";
6
6
  export type DesktopGillTransport = Pick<TeamWorkbenchHostHandle, "requestGill" | "watchGillChanges">;
7
7
  /** The host admits reads; opening a row navigates inside its App. */
@@ -1,4 +1,4 @@
1
1
  import "@fungi.computer/parakeet/styles.css";
2
2
  import "./shell.css";
3
- import type { MountPlatformControls } from "@fungi.computer/oyster/shell/shell-module";
3
+ import type { MountPlatformControls } from "@fungi.computer/nacre/shell-module";
4
4
  export declare const mountPlatformControls: MountPlatformControls;
@@ -7,12 +7,14 @@ import "@fungi.computer/parakeet/styles.css";
7
7
  import "./shell.css";
8
8
  import { AppError } from "@fungi.computer/agar";
9
9
  import { createQueryClient } from "@fungi.computer/agar/query";
10
- import { workbenchDirectorySchema } from "@fungi.computer/oyster/shell/workbench-directory";
10
+ import { workbenchDirectorySchema } from "@fungi.computer/nacre/workbench-directory";
11
11
  import { QueryClientProvider } from "@tanstack/react-query";
12
12
  import * as S from "effect/Schema";
13
+ import { z } from "zod";
13
14
  import { createRoot } from "react-dom/client";
14
- import { agentMcpServersViewSchema, createPlatformControls, keyIssueResultSchema, keyRecordSchema, keysViewSchema, PlatformControlsView, } from "./platform-controls.js";
15
+ import { agentMcpServersViewSchema, createPlatformControls, PlatformControlsView, } from "./platform-controls.js";
15
16
  import { settingsLocationSchema } from "./settings-routes.js";
17
+ import { OysterComputers, createOysterComputersPort, } from "./OysterComputers.js";
16
18
  async function json(path) {
17
19
  const response = await fetch(path, {
18
20
  credentials: "same-origin",
@@ -61,33 +63,72 @@ const agentMcpServers = {
61
63
  },
62
64
  };
63
65
  /** Owner-direct Keys custody; a plaintext secret comes back only once, on issue. */
64
- const keys = {
65
- list: async () => keysViewSchema.parse(await json("/api/keys")),
66
- issue: async (draft) => {
67
- const response = await ownerRequest("/api/keys", {
68
- method: "POST",
69
- headers: { "Content-Type": "application/json" },
70
- body: JSON.stringify(draft),
71
- }, "The key request was refused");
72
- return keyIssueResultSchema.parse(await response.json());
73
- },
74
- revoke: async (keyId, requestId) => {
75
- const response = await ownerRequest("/api/keys/revoke", {
76
- method: "POST",
77
- headers: { "Content-Type": "application/json" },
78
- body: JSON.stringify({ keyId, requestId }),
79
- }, "The key request was refused");
80
- return keyRecordSchema.parse(await response.json());
81
- },
82
- };
66
+ function createOysterKeysPort(oysterKeysWire) {
67
+ const oysterKeysView = z.strictObject({
68
+ keys: z.array(oysterKeysWire.summary),
69
+ });
70
+ const oysterKeyIssue = z.strictObject({
71
+ plaintext: z.string().min(1),
72
+ key: oysterKeysWire.summary,
73
+ });
74
+ const oysterKeyMutation = z.strictObject({ key: oysterKeysWire.summary });
75
+ return {
76
+ scope: "oyster",
77
+ expiry: "optional",
78
+ options: (apps) => {
79
+ const targets = apps.map((app) => ({
80
+ id: app.installationId,
81
+ name: app.displayName,
82
+ }));
83
+ return Promise.resolve([
84
+ { capability: "apps.read", label: "Read Apps", targets },
85
+ {
86
+ capability: "apps.use",
87
+ label: "Use Apps and run Workflows",
88
+ targets,
89
+ },
90
+ { capability: "computer.use", label: "Use the Computer" },
91
+ ]);
92
+ },
93
+ list: async () => oysterKeysView.parse(await json("/api/keys")),
94
+ issue: async (draft) => {
95
+ const response = await ownerRequest("/api/keys", {
96
+ method: "POST",
97
+ headers: { "Content-Type": "application/json" },
98
+ body: JSON.stringify(oysterKeysWire.issue.parse(draft)),
99
+ }, "The key request was refused");
100
+ return oysterKeyIssue.parse(await response.json());
101
+ },
102
+ revoke: async (keyId, requestId) => {
103
+ const response = await ownerRequest("/api/keys/revoke", {
104
+ method: "POST",
105
+ headers: { "Content-Type": "application/json" },
106
+ body: JSON.stringify(oysterKeysWire.revoke.parse({ keyId, requestId })),
107
+ }, "The key request was refused");
108
+ const result = oysterKeyMutation.parse(await response.json());
109
+ return { key: result.key, notice: "Key revoked.", retry: false };
110
+ },
111
+ };
112
+ }
83
113
  const directory = async () => workbenchDirectorySchema.parse(await json("/api/workbench/directory"));
84
114
  export const mountPlatformControls = (root, input) => {
115
+ const keys = createOysterKeysPort(input.keysWire);
116
+ const requestAppAccess = (target, request, dialog) => input.requestAppAccess(target, request, {
117
+ ask: dialog.ask,
118
+ customize: (installationId) => dialog.customize({
119
+ category: "apps",
120
+ appKey: installationId === null ? null : `installation:${installationId}`,
121
+ section: "desktop",
122
+ }),
123
+ });
85
124
  const controls = createPlatformControls({
86
- sections: ["home", "apps", "connectors", "keys"],
125
+ sections: ["home", "apps", "connectors", "computers", "keys"],
87
126
  });
127
+ const computersPort = createOysterComputersPort(new URL(window.location.origin));
128
+ const computers = () => (_jsx(OysterComputers, { teamId: input.teamId, port: computersPort, onChanged: controls.changed }));
88
129
  const client = createQueryClient();
89
130
  const settings = createRoot(root);
90
- settings.render(_jsx(QueryClientProvider, { client: client, children: _jsx(PlatformControlsView, { teamId: input.teamId, controls: controls, data: { directory, agentMcpServers, keys } }) }));
131
+ settings.render(_jsx(QueryClientProvider, { client: client, children: _jsx(PlatformControlsView, { teamId: input.teamId, controls: controls, data: { directory, agentMcpServers, computers, keys, requestAppAccess } }) }));
91
132
  return Promise.resolve({
92
133
  controls: {
93
134
  ...controls,
@@ -1,5 +1,6 @@
1
- import type { WorkbenchDirectory } from "@fungi.computer/oyster/shell/workbench-directory";
2
- import { type WorkbenchDesktopSettings, type WorkbenchDesktopRegistration } from "@fungi.computer/oyster/shell/workbench";
1
+ import type { WorkbenchHostControls } from "@fungi.computer/nacre/workbench";
2
+ import type { WorkbenchDirectory } from "@fungi.computer/nacre/workbench-directory";
3
+ import { type WorkbenchDesktopSettings, type WorkbenchDesktopRegistration } from "@fungi.computer/nacre/workbench";
3
4
  import type { AppPermissionOutcome, AppPermissionRequest } from "@fungi.computer/agar";
4
5
  import { type AppAccessRequest } from "./AppAccessConsent.js";
5
6
  import { createSettingsRouter, type SettingsLocation } from "./settings-routes.js";
@@ -7,7 +8,7 @@ export { parseSettingsLocation } from "./settings-routes.js";
7
8
  import type { SettingsSectionPorts } from "./settings-sections.js";
8
9
  export type { SettingsSectionPorts, TeamAdministrationPort, } from "./settings-sections.js";
9
10
  export { agentMcpServersViewSchema, type AgentMcpServerDraft, type AgentMcpServersPort, type AgentMcpServersView, } from "./AgentMcpServers.js";
10
- export { keyGrantSchema, keyGrantTargetSchema, keyIssueResultSchema, keyRecordSchema, keysViewSchema, type KeyAppAccessLevel, type KeyAppOption, type KeyGrant, type KeyGrantTarget, type KeyIssueDraft, type KeyIssueResult, type KeyRecord, type KeysPort, type KeysView, } from "./Keys.js";
11
+ export { type KeyAppOption, type KeyGrantOption, type KeyGrantPreviewProps, type KeyTargetOption, type KeyChange, type KeyIssueDraft, type KeyIssueResult, type KeysPort, type KeysView, } from "./Keys.js";
11
12
  /**
12
13
  * Team administration a dashboard may ask for but never performs itself:
13
14
  * System Settings (App permissions, forks, default Apps, Team Git, sprite)
@@ -36,7 +37,7 @@ export type PlatformControlsHost = PlatformControls & Readonly<{
36
37
  desktopConsentReadyAt: () => number;
37
38
  /** A child App can suggest Settings; only the member's Review opens them. */
38
39
  suggestDesktopSettings: (target: AppAccessTarget) => void;
39
- requestAppAccess: (target: AppAccessTarget, input: AppPermissionRequest) => Promise<AppPermissionOutcome>;
40
+ requestAppAccess: WorkbenchHostControls["requestAppAccess"];
40
41
  /** View wiring: current location, navigation and the consent handler. */
41
42
  /** Settings the embedding host backs with real data and actions. */
42
43
  sections?: readonly SettingsLocation["category"][] | undefined;
@@ -74,7 +75,7 @@ export type PlatformControlsData = SettingsSectionPorts & Readonly<{
74
75
  * capabilities; the owner's click in this trusted shell is the only grant
75
76
  * a host without a separate per-App ceiling needs.
76
77
  */
77
- requestAppAccess?(target: AppAccessTarget, input: AppPermissionRequest, dialog: AppAccessDialog): Promise<AppPermissionOutcome>;
78
+ requestAppAccess?(target: Parameters<WorkbenchHostControls["requestAppAccess"]>[0], input: AppPermissionRequest, dialog: AppAccessDialog): Promise<AppPermissionOutcome>;
78
79
  }>;
79
80
  /** Tourtois owns the same Settings renderer on every embedding host. */
80
81
  export declare function PlatformControlsView({ teamId, controls, data, }: {
@@ -2,8 +2,8 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { AppError } from "@fungi.computer/agar";
3
3
  import { Button } from "@fungi.computer/caps/components/button";
4
4
  import { toast } from "@fungi.computer/caps/components/toaster";
5
- import { desktopGillAdditionSchema, desktopGillSelectionSchema, } from "@fungi.computer/oyster/shell/workbench";
6
- import { addressKey } from "@fungi.computer/oyster/shell/app-directory";
5
+ import { desktopGillAdditionSchema, desktopGillSelectionSchema, } from "@fungi.computer/nacre/workbench";
6
+ import { addressKey } from "@fungi.computer/nacre/app-directory";
7
7
  import { tourtoisQueryKeys } from "./queries.js";
8
8
  import { useQuery } from "@tanstack/react-query";
9
9
  import { lazy, Suspense, useEffect, useState, useSyncExternalStore, } from "react";
@@ -13,7 +13,7 @@ import { createSettingsRouter, navigateSettings, settingsLocationSchema, } from
13
13
  export { parseSettingsLocation } from "./settings-routes.js";
14
14
  import { MemberAvatarSource } from "./member-avatar.js";
15
15
  export { agentMcpServersViewSchema, } from "./AgentMcpServers.js";
16
- export { keyGrantSchema, keyGrantTargetSchema, keyIssueResultSchema, keyRecordSchema, keysViewSchema, } from "./Keys.js";
16
+ export {} from "./Keys.js";
17
17
  /** One host's platform controls; render them with `PlatformControlsView`. */
18
18
  export function createPlatformControls(options) {
19
19
  const sections = options?.sections.slice();
package/dist/queries.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  /** Keep host and isolated-workbench reads in distinct cache entries. */
2
2
  export declare const tourtoisQueryKeys: {
3
+ oysterComputers: (teamId: string) => readonly ["tourtois", "oyster-computers", string];
4
+ oysterComputerPage: (teamId: string, offset: number) => readonly ["tourtois", "oyster-computers", string, number];
5
+ oysterSelectedComputer: (teamId: string) => readonly ["tourtois", "oyster-selected-computer", string];
6
+ oysterInternetAccess: (teamId: string, computerId: string) => readonly ["tourtois", "oyster-internet-access", string, string];
3
7
  hostSettings: (workspaceId: string) => readonly ["tourtois", "host-settings", string];
4
8
  workbench: () => readonly ["tourtois", "workbench"];
5
9
  workbenchComputers: (teamId: string) => readonly ["tourtois", "workbench-computers", string];