gesso-electrobun 0.6.7 → 0.6.8

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # gesso-electrobun
2
2
 
3
+ ## 0.6.8
4
+
5
+ ### Patch Changes
6
+
7
+ - dc47f50: `messageBoxConfirm` now shows a person everything they are being asked to approve, and declines on Enter on macOS. The command's description and the arguments the agent sent were in the dialog's `detail`, which macOS does not show, so a Mac asked only "An AI agent wants to createBranch in workspace" with nothing to say which branch; they are now part of the message, which every platform shows. And the buttons were Allow then Decline: macOS makes the first button the default whatever `defaultId` says, so Enter allowed the command. Decline is now first, and the default on every platform.
8
+ - Updated dependencies [a7bb34e]
9
+ - gesso-framework@0.6.8
10
+
3
11
  ## 0.6.7
4
12
 
5
13
  ### Patch Changes
package/dist/desktop.d.ts CHANGED
@@ -81,8 +81,17 @@ type ShowMessageBox = (options: {
81
81
  *
82
82
  * Takes Electrobun's `Utils.showMessageBox` rather than importing it,
83
83
  * because Electrobun is a toolchain a project is projected into, not a
84
- * package this one can depend on. The safe answer is the default one:
85
- * Escape, closing the dialog, and Enter all decline.
84
+ * package this one can depend on.
85
+ *
86
+ * Everything the person needs to decide is in `message`, the command's
87
+ * description and its arguments included, because macOS shows no
88
+ * `detail`: a dialog that asked there showed only "wants to
89
+ * createBranch in workspace", with nothing to say which branch.
90
+ *
91
+ * Decline is the first button. macOS makes the first button the
92
+ * default, whatever `defaultId` says, so with Allow first, Enter
93
+ * approved; first is also what `defaultId` and `cancelId` name, so
94
+ * Enter, Escape and closing the dialog decline on every platform.
86
95
  */
87
96
  declare function messageBoxConfirm(showMessageBox: ShowMessageBox): (request: AgentConfirmation) => Promise<boolean>;
88
97
  //#endregion
package/dist/desktop.js CHANGED
@@ -45,22 +45,35 @@ function serveDesktopAgent(channels, options = {}) {
45
45
  *
46
46
  * Takes Electrobun's `Utils.showMessageBox` rather than importing it,
47
47
  * because Electrobun is a toolchain a project is projected into, not a
48
- * package this one can depend on. The safe answer is the default one:
49
- * Escape, closing the dialog, and Enter all decline.
48
+ * package this one can depend on.
49
+ *
50
+ * Everything the person needs to decide is in `message`, the command's
51
+ * description and its arguments included, because macOS shows no
52
+ * `detail`: a dialog that asked there showed only "wants to
53
+ * createBranch in workspace", with nothing to say which branch.
54
+ *
55
+ * Decline is the first button. macOS makes the first button the
56
+ * default, whatever `defaultId` says, so with Allow first, Enter
57
+ * approved; first is also what `defaultId` and `cancelId` name, so
58
+ * Enter, Escape and closing the dialog decline on every platform.
50
59
  */
51
60
  function messageBoxConfirm(showMessageBox) {
52
61
  return async (request) => {
53
- const details = [request.description, Object.keys(request.arguments).length > 0 ? JSON.stringify(request.arguments, null, 2) : void 0].filter((part) => part !== void 0 && part !== "").join("\n\n");
62
+ const argumentLines = Object.entries(request.arguments).map(([name, value]) => `${name}: ${JSON.stringify(value)}`);
63
+ const message = [
64
+ `An AI agent wants to ${request.command} in ${request.channel}.${request.destructive ? " This cannot be undone." : ""}`,
65
+ request.description,
66
+ argumentLines.join("\n")
67
+ ].filter((part) => part !== void 0 && part !== "").join("\n\n");
54
68
  const { response } = await showMessageBox({
55
69
  type: request.destructive ? "warning" : "question",
56
70
  title: "An AI agent is asking",
57
- message: `An AI agent wants to ${request.command} in ${request.channel}.${request.destructive ? " This cannot be undone." : ""}`,
58
- detail: details,
59
- buttons: ["Allow", "Decline"],
60
- defaultId: 1,
61
- cancelId: 1
71
+ message,
72
+ buttons: ["Decline", "Allow"],
73
+ defaultId: 0,
74
+ cancelId: 0
62
75
  });
63
- return response === 0;
76
+ return response === 1;
64
77
  };
65
78
  }
66
79
  function bunServe() {
@@ -1 +1 @@
1
- {"version":3,"file":"desktop.js","names":[],"sources":["../src/agent.ts","../src/desktop.ts"],"sourcesContent":["import type { ServedChannel } from 'gesso-framework';\nimport {\n agentSurface,\n mcpHandler,\n type AgentConfirmation,\n type AgentSurface,\n type McpHandlerOptions\n} from 'gesso-framework/agent';\n\n/**\n * A desktop application's channels, served to AI agents over MCP.\n *\n * The main process already holds every channel's source: it is what\n * `createDesktopApp` serves to each window. So it is also where an\n * agent connects. This serves those same sources with MCP's HTTP\n * transport on the person's own machine, through `Bun.serve`, which\n * Cottontail provides as Bun does, and an agent such as Claude Code\n * connects by URL:\n *\n * const agent = serveDesktopAgent([counter], { name: 'my-app', confirm: messageBoxConfirm(Utils.showMessageBox) });\n * // claude mcp add --transport http my-app http://127.0.0.1:7310/mcp\n *\n * The channels are passed rather than read from the app, because\n * `createDesktopApp` takes them per window, and the per-window ones\n * (`windowsChannel`) are about a window an agent does not have.\n *\n * What it offers is the channel tools only. The screen is in each\n * window's render worker, out of the main process's reach, so the\n * screen tools a web app offers are not here.\n *\n * The descriptions come from `channelSchema`. A main process is bundled\n * by Electrobun's own build, which `gesso-vite-plugin` never sees, so\n * the template runs `gesso-channels` to write a module that describes\n * the contracts, and imports it.\n */\n\n/** A `Bun.serve`, as far as this uses one. */\nexport type DesktopServe = (options: {\n hostname: string;\n port: number;\n fetch: (request: Request) => Promise<Response>;\n}) => { stop(closeActiveConnections?: boolean): void };\n\nexport interface DesktopAgentOptions extends Omit<McpHandlerOptions, 'allowedOrigins'> {\n /**\n * The port to listen on (default 7310). When it is taken, the next\n * nine are tried in turn, so a second copy of the app still serves;\n * `url` says which one it got.\n */\n port?: number;\n /** Default `127.0.0.1`: reachable from this machine only. */\n hostname?: string;\n /**\n * Asks the person whether a `@confirm` command may be sent. Without\n * it such a command is refused. `messageBoxConfirm` makes one from\n * Electrobun's native dialog.\n */\n confirm?: (request: AgentConfirmation) => boolean | Promise<boolean>;\n /** The server to start. Defaults to the runtime's `Bun.serve`. */\n serve?: DesktopServe;\n}\n\nexport interface DesktopAgent {\n /** Where an agent connects: `http://127.0.0.1:<port>/mcp`. */\n readonly url: string;\n readonly surface: AgentSurface;\n /** Stops serving and stops following the channels. */\n stop(): void;\n}\n\nconst DEFAULT_PORT = 7310;\nconst PORTS_TRIED = 10;\n\nexport function serveDesktopAgent(channels: readonly ServedChannel[], options: DesktopAgentOptions = {}): DesktopAgent {\n const serve = options.serve ?? bunServe();\n const hostname = options.hostname ?? '127.0.0.1';\n const first = options.port ?? DEFAULT_PORT;\n const surface = agentSurface(channels, options.confirm === undefined ? {} : { confirm: options.confirm });\n // A desktop app has no browser pages of its own to let in, so every\n // request carrying an Origin is refused: that is a web page the person\n // has open, reaching for their machine.\n const fetch = mcpHandler(surface, { ...options, allowedOrigins: [] });\n\n let lastError: unknown;\n for (let port = first; port < first + PORTS_TRIED; port++) {\n try {\n const server = serve({ hostname, port, fetch });\n return {\n url: `http://${hostname}:${port}/mcp`,\n surface,\n stop: () => {\n server.stop(true);\n surface.dispose();\n }\n };\n } catch (error) {\n if (!isAddressInUse(error)) {\n surface.dispose();\n throw error;\n }\n lastError = error;\n }\n }\n surface.dispose();\n throw new Error(\n `Ports ${first} to ${first + PORTS_TRIED - 1} are all in use, so agents cannot be served. ` +\n `Pass another port to serveDesktopAgent. (${String(lastError)})`\n );\n}\n\n/** The options Electrobun's `Utils.showMessageBox` takes, as far as this uses them. */\nexport type ShowMessageBox = (options: {\n type?: 'info' | 'warning' | 'error' | 'question';\n title?: string;\n message?: string;\n detail?: string;\n buttons?: string[];\n defaultId?: number;\n cancelId?: number;\n}) => Promise<{ response: number }>;\n\n/**\n * A `confirm` that asks with the operating system's own dialog.\n *\n * Takes Electrobun's `Utils.showMessageBox` rather than importing it,\n * because Electrobun is a toolchain a project is projected into, not a\n * package this one can depend on. The safe answer is the default one:\n * Escape, closing the dialog, and Enter all decline.\n */\nexport function messageBoxConfirm(showMessageBox: ShowMessageBox): (request: AgentConfirmation) => Promise<boolean> {\n return async request => {\n const details = [\n request.description,\n Object.keys(request.arguments).length > 0 ? JSON.stringify(request.arguments, null, 2) : undefined\n ]\n .filter((part): part is string => part !== undefined && part !== '')\n .join('\\n\\n');\n const { response } = await showMessageBox({\n type: request.destructive ? 'warning' : 'question',\n title: 'An AI agent is asking',\n message: `An AI agent wants to ${request.command} in ${request.channel}.${request.destructive ? ' This cannot be undone.' : ''}`,\n detail: details,\n buttons: ['Allow', 'Decline'],\n defaultId: 1,\n cancelId: 1\n });\n return response === 0;\n };\n}\n\nfunction bunServe(): DesktopServe {\n const bun = (globalThis as { Bun?: { serve?: DesktopServe } }).Bun;\n if (typeof bun?.serve !== 'function') {\n throw new Error(\n 'serveDesktopAgent needs Bun.serve, which Cottontail and Bun provide. Pass serve to use another server.'\n );\n }\n return options => bun.serve!(options);\n}\n\nfunction isAddressInUse(error: unknown): boolean {\n const code = (error as { code?: unknown } | null)?.code;\n return code === 'EADDRINUSE' || /in use|EADDRINUSE/i.test(String((error as Error | null)?.message ?? error));\n}\n","/**\n * An application in the main process: windows, and the channels each\n * one is served.\n *\n * Nothing here imports Electrobun, and that is not fastidiousness. The\n * SDK is projected into a project by Hutch rather than installed from\n * a registry, so a package in\n * this workspace could not import it even if it wanted to. What the\n * application supplies instead is one function that opens a window,\n * which is the only Electrobun-shaped thing this needs, and which is\n * five lines at the call site:\n *\n * const app = createDesktopApp({\n * channels: window => [\n * { token: Catalogue, source: catalogue },\n * windowsChannel(app, window)\n * ],\n * open: (receive, handle) => {\n * const rpc = BrowserView.defineRPC<GessoWindowRPC>({\n * handlers: { requests: {}, messages: { gessoFrame: receive } }\n * });\n * const window = new BrowserWindow({\n * title: 'Notes',\n * url: withWindowRoute('views://mainview/index.html', handle.route),\n * rpc\n * });\n * return {\n * send: frame => window.webview.rpc.send.gessoFrame(frame),\n * close: () => window.close()\n * };\n * }\n * });\n * app.openWindow();\n *\n * The arrangement it buys: every\n * window is a replica of the same channels, so two windows agree by\n * construction rather than by synchronisation.\n */\nimport { channel, type ChannelToken, type ServedChannel } from 'gesso-framework';\nimport { BehaviorSubject, type Observable, type Subscription } from 'rxjs';\n\nimport type { GessoFrame } from './frames';\nimport { serveChannelsToWindow, type ChannelHost } from './main';\n\nexport { withWindowRoute } from './route';\n\n/** What the application does with the window it opened. */\nexport interface DesktopWindowTransport {\n /** Sends one frame to this window, over its own RPC. */\n send: (frame: GessoFrame) => void;\n /** Closes the native window. The adapter calls this; the platform may also close it on its own. */\n close: () => void;\n}\n\n/** A window this application opened. */\nexport interface DesktopWindowHandle {\n /** Stable for the life of the window, and never reused. */\n readonly id: number;\n /**\n * The application url the window was asked to open at, or null for\n * wherever the application starts.\n *\n * Set by `openWindow({ route })`, which is what a Cmd-click on an\n * in-app link comes to by default. The window cannot be told it\n * through a frame in time to start there, so `open` puts it on the\n * view's url, with `withWindowRoute`, and the window reads it back\n * with `windowRoute` before its shell starts.\n */\n readonly route: string | null;\n /** Closes the window and disposes the channels it was served. */\n close(): void;\n}\n\n/** What `openWindow` may be told about the window it opens. */\nexport interface DesktopWindowOptions {\n /**\n * The application url to open at, `/epic/BUD-12?story=BUD-13`. Carried\n * to `open` as `window.route`; see `DesktopWindowHandle.route`.\n */\n readonly route?: string;\n}\n\nexport interface DesktopAppOptions {\n /**\n * The channels each window is served.\n *\n * A function when a window needs a channel of its own, which is what\n * `windowsChannel` uses to give a window a way to close itself. It\n * is called once per window, and the observables it returns are\n * ordinarily the same ones every time: `provide` keeps a separate\n * record of what each client has seen, so sharing a source between\n * windows is what makes them agree.\n */\n channels: readonly ServedChannel[] | ((window: DesktopWindowHandle) => readonly ServedChannel[]);\n /**\n * Opens a native window.\n *\n * `receive` is what the window's frames must be fed into: wire it to\n * the RPC message the window sends frames on, before the window\n * opens, or the first handshake is lost.\n *\n * `window.route` is the application url the window should start at,\n * or null. Put it on the view's url with `withWindowRoute`, and read\n * it in the window with `windowRoute`; an `open` that ignores it\n * opens every window at the application's start, as before.\n */\n open: (receive: (frame: GessoFrame) => void, window: DesktopWindowHandle) => DesktopWindowTransport;\n /**\n * A url a window asked to have opened outside itself, with the\n * window that asked. `Utils.openExternal(url)` is what an Electrobun\n * application passes here.\n */\n onOpenUrl?: (url: string, window: DesktopWindowHandle) => void;\n /**\n * One of the application's own urls a window asked to have opened\n * somewhere new (a Cmd-click or a Ctrl-click on an in-app link), with\n * the window that asked.\n *\n * Defaults to `app.openWindow({ route: url })`: a new window of this\n * application, at that route, which is what a new tab is in an\n * application whose windows have none. An outbound url never comes\n * here; it is `onOpenUrl`'s, and goes to the person's browser.\n */\n onOpenRoute?: (url: string, window: DesktopWindowHandle) => void;\n /**\n * The appearance the platform is in, pushed to every window as it\n * changes and to a new window as it opens.\n *\n * The application supplies it because Electrobun does not: its SDK\n * has no appearance API at all, and the webview's own\n * `prefers-color-scheme` is wrong on WebKitGTK\n *. On a platform that has a\n * signal, this is where it goes; on one that does not, an\n * application setting is a perfectly good source.\n */\n colorScheme?: Observable<'light' | 'dark'>;\n /**\n * Called when the last window closes. A desktop application usually\n * stops here; one with a tray or a menu bar does not, which is why\n * this is a callback rather than an exit.\n */\n onLastWindowClosed?: () => void;\n /** Overrides the frame size messages are split at. Only a test should need to. */\n chunkBytes?: number;\n}\n\nexport interface DesktopApp {\n /**\n * Opens a window, serves it every channel, and returns its handle;\n * at `options.route` when one is given.\n */\n openWindow(options?: DesktopWindowOptions): DesktopWindowHandle;\n /** The windows open now, in the order they were opened. */\n readonly windows: readonly DesktopWindowHandle[];\n /** How many windows are open, as something a channel can publish. */\n readonly windowCount: Observable<number>;\n /** Closes every window and stops serving. */\n dispose(): void;\n}\n\nexport function createDesktopApp(options: DesktopAppOptions): DesktopApp {\n interface Entry {\n handle: DesktopWindowHandle;\n transport: DesktopWindowTransport;\n host: ChannelHost;\n }\n const entries = new Map<number, Entry>();\n /** One appearance subscription per window, ended when it closes. */\n const subscriptions = new Map<number, Subscription>();\n const order: DesktopWindowHandle[] = [];\n const count = new BehaviorSubject(0);\n let nextId = 1;\n let disposed = false;\n\n const forget = (id: number, closeNative: boolean): void => {\n const entry = entries.get(id);\n if (entry === undefined) {\n return;\n }\n entries.delete(id);\n subscriptions.get(id)?.unsubscribe();\n subscriptions.delete(id);\n const at = order.indexOf(entry.handle);\n if (at >= 0) {\n order.splice(at, 1);\n }\n entry.host.dispose();\n if (closeNative) {\n entry.transport.close();\n }\n count.next(order.length);\n if (order.length === 0 && !disposed) {\n options.onLastWindowClosed?.();\n }\n };\n\n const app: DesktopApp = {\n openWindow(windowOptions: DesktopWindowOptions = {}): DesktopWindowHandle {\n if (disposed) {\n throw new Error('This desktop application has been disposed; it cannot open a window.');\n }\n const id = nextId++;\n const handle: DesktopWindowHandle = {\n id,\n route: windowOptions.route ?? null,\n close: () => forget(id, true)\n };\n\n // The host exists before the window does, because `open` may\n // deliver a frame synchronously and a window whose first\n // handshake was dropped never replicates anything.\n let transport: DesktopWindowTransport | undefined;\n const host = serveChannelsToWindow(\n typeof options.channels === 'function' ? options.channels(handle) : options.channels,\n {\n send: frame => transport?.send(frame),\n chunkBytes: options.chunkBytes,\n onOpenUrl: url => options.onOpenUrl?.(url, handle),\n onOpenRoute: url => {\n if (options.onOpenRoute !== undefined) {\n options.onOpenRoute(url, handle);\n } else if (!disposed) {\n app.openWindow({ route: url });\n }\n }\n }\n );\n entries.set(id, { handle, transport: { send: () => {}, close: () => {} }, host });\n order.push(handle);\n\n // Anything pushed before the window has spoken is lost: a\n // webview's RPC is not listening until its page has loaded, and\n // the window opens well before that. Channels do not notice,\n // because a channel starts with the window asking. The\n // appearance is the one thing this side sends first, so it waits\n // for the window's first frame, whatever that frame is. A native\n // window found this; a spec with a transport that was live\n // immediately could not.\n let greeted = false;\n let latest: 'light' | 'dark' | undefined;\n transport = options.open(frame => {\n if (!greeted) {\n greeted = true;\n if (latest !== undefined) {\n host.setColorScheme(latest);\n }\n }\n host.receive(frame);\n }, handle);\n entries.set(id, { handle, transport, host });\n const appearance = options.colorScheme?.subscribe(scheme => {\n latest = scheme;\n if (greeted) {\n host.setColorScheme(scheme);\n }\n });\n if (appearance !== undefined) {\n subscriptions.set(id, appearance);\n }\n count.next(order.length);\n return handle;\n },\n get windows(): readonly DesktopWindowHandle[] {\n return [...order];\n },\n windowCount: count.asObservable(),\n dispose(): void {\n disposed = true;\n for (const id of new Set(entries.keys())) {\n forget(id, true);\n }\n count.complete();\n }\n };\n\n return app;\n}\n\n/** What a window can see and do about the windows of its application. */\nexport interface DesktopWindowsView {\n /** How many windows this application has open. */\n count: number;\n /** Which one this is, so a screen can say \"window 2 of 3\" without asking. */\n id: number;\n}\n\nexport interface DesktopWindowsCommands {\n open: () => void;\n closeThis: () => void;\n}\n\n/**\n * The channel a window opens another window through.\n *\n * It exists because opening a window is the one native act a screen\n * genuinely needs, and going through a channel keeps the view layer\n * from importing an adapter. the \"native menus bound to\n * store actions\" is the same idea from the other end, and on Linux it\n * is the only end: the runtime has no application menus there, so a\n * menu is a component and this is what it calls.\n */\nexport const DesktopWindows: ChannelToken<DesktopWindowsView, DesktopWindowsCommands> = channel<\n DesktopWindowsView,\n DesktopWindowsCommands\n>('gesso:windows', { count: 0, id: 0 });\n\n/** Serves `DesktopWindows` to one window. Put it in `channels`. */\nexport function windowsChannel(app: DesktopApp, window: DesktopWindowHandle): ServedChannel {\n return {\n token: DesktopWindows,\n source: {\n view: {\n count: app.windowCount,\n id: new BehaviorSubject(window.id)\n },\n commands: {\n open: () => {\n app.openWindow();\n },\n closeThis: () => {\n window.close();\n }\n }\n }\n };\n}\n\nexport {\n messageBoxConfirm,\n serveDesktopAgent,\n type DesktopAgent,\n type DesktopAgentOptions,\n type DesktopServe,\n type ShowMessageBox\n} from './agent';\n"],"mappings":"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"}
1
+ {"version":3,"file":"desktop.js","names":[],"sources":["../src/agent.ts","../src/desktop.ts"],"sourcesContent":["import type { ServedChannel } from 'gesso-framework';\nimport {\n agentSurface,\n mcpHandler,\n type AgentConfirmation,\n type AgentSurface,\n type McpHandlerOptions\n} from 'gesso-framework/agent';\n\n/**\n * A desktop application's channels, served to AI agents over MCP.\n *\n * The main process already holds every channel's source: it is what\n * `createDesktopApp` serves to each window. So it is also where an\n * agent connects. This serves those same sources with MCP's HTTP\n * transport on the person's own machine, through `Bun.serve`, which\n * Cottontail provides as Bun does, and an agent such as Claude Code\n * connects by URL:\n *\n * const agent = serveDesktopAgent([counter], { name: 'my-app', confirm: messageBoxConfirm(Utils.showMessageBox) });\n * // claude mcp add --transport http my-app http://127.0.0.1:7310/mcp\n *\n * The channels are passed rather than read from the app, because\n * `createDesktopApp` takes them per window, and the per-window ones\n * (`windowsChannel`) are about a window an agent does not have.\n *\n * What it offers is the channel tools only. The screen is in each\n * window's render worker, out of the main process's reach, so the\n * screen tools a web app offers are not here.\n *\n * The descriptions come from `channelSchema`. A main process is bundled\n * by Electrobun's own build, which `gesso-vite-plugin` never sees, so\n * the template runs `gesso-channels` to write a module that describes\n * the contracts, and imports it.\n */\n\n/** A `Bun.serve`, as far as this uses one. */\nexport type DesktopServe = (options: {\n hostname: string;\n port: number;\n fetch: (request: Request) => Promise<Response>;\n}) => { stop(closeActiveConnections?: boolean): void };\n\nexport interface DesktopAgentOptions extends Omit<McpHandlerOptions, 'allowedOrigins'> {\n /**\n * The port to listen on (default 7310). When it is taken, the next\n * nine are tried in turn, so a second copy of the app still serves;\n * `url` says which one it got.\n */\n port?: number;\n /** Default `127.0.0.1`: reachable from this machine only. */\n hostname?: string;\n /**\n * Asks the person whether a `@confirm` command may be sent. Without\n * it such a command is refused. `messageBoxConfirm` makes one from\n * Electrobun's native dialog.\n */\n confirm?: (request: AgentConfirmation) => boolean | Promise<boolean>;\n /** The server to start. Defaults to the runtime's `Bun.serve`. */\n serve?: DesktopServe;\n}\n\nexport interface DesktopAgent {\n /** Where an agent connects: `http://127.0.0.1:<port>/mcp`. */\n readonly url: string;\n readonly surface: AgentSurface;\n /** Stops serving and stops following the channels. */\n stop(): void;\n}\n\nconst DEFAULT_PORT = 7310;\nconst PORTS_TRIED = 10;\n\nexport function serveDesktopAgent(channels: readonly ServedChannel[], options: DesktopAgentOptions = {}): DesktopAgent {\n const serve = options.serve ?? bunServe();\n const hostname = options.hostname ?? '127.0.0.1';\n const first = options.port ?? DEFAULT_PORT;\n const surface = agentSurface(channels, options.confirm === undefined ? {} : { confirm: options.confirm });\n // A desktop app has no browser pages of its own to let in, so every\n // request carrying an Origin is refused: that is a web page the person\n // has open, reaching for their machine.\n const fetch = mcpHandler(surface, { ...options, allowedOrigins: [] });\n\n let lastError: unknown;\n for (let port = first; port < first + PORTS_TRIED; port++) {\n try {\n const server = serve({ hostname, port, fetch });\n return {\n url: `http://${hostname}:${port}/mcp`,\n surface,\n stop: () => {\n server.stop(true);\n surface.dispose();\n }\n };\n } catch (error) {\n if (!isAddressInUse(error)) {\n surface.dispose();\n throw error;\n }\n lastError = error;\n }\n }\n surface.dispose();\n throw new Error(\n `Ports ${first} to ${first + PORTS_TRIED - 1} are all in use, so agents cannot be served. ` +\n `Pass another port to serveDesktopAgent. (${String(lastError)})`\n );\n}\n\n/** The options Electrobun's `Utils.showMessageBox` takes, as far as this uses them. */\nexport type ShowMessageBox = (options: {\n type?: 'info' | 'warning' | 'error' | 'question';\n title?: string;\n message?: string;\n detail?: string;\n buttons?: string[];\n defaultId?: number;\n cancelId?: number;\n}) => Promise<{ response: number }>;\n\n/**\n * A `confirm` that asks with the operating system's own dialog.\n *\n * Takes Electrobun's `Utils.showMessageBox` rather than importing it,\n * because Electrobun is a toolchain a project is projected into, not a\n * package this one can depend on.\n *\n * Everything the person needs to decide is in `message`, the command's\n * description and its arguments included, because macOS shows no\n * `detail`: a dialog that asked there showed only \"wants to\n * createBranch in workspace\", with nothing to say which branch.\n *\n * Decline is the first button. macOS makes the first button the\n * default, whatever `defaultId` says, so with Allow first, Enter\n * approved; first is also what `defaultId` and `cancelId` name, so\n * Enter, Escape and closing the dialog decline on every platform.\n */\nexport function messageBoxConfirm(showMessageBox: ShowMessageBox): (request: AgentConfirmation) => Promise<boolean> {\n return async request => {\n const argumentLines = Object.entries(request.arguments).map(([name, value]) => `${name}: ${JSON.stringify(value)}`);\n const message = [\n `An AI agent wants to ${request.command} in ${request.channel}.${request.destructive ? ' This cannot be undone.' : ''}`,\n request.description,\n argumentLines.join('\\n')\n ]\n .filter((part): part is string => part !== undefined && part !== '')\n .join('\\n\\n');\n const { response } = await showMessageBox({\n type: request.destructive ? 'warning' : 'question',\n title: 'An AI agent is asking',\n message,\n buttons: ['Decline', 'Allow'],\n defaultId: 0,\n cancelId: 0\n });\n return response === 1;\n };\n}\n\nfunction bunServe(): DesktopServe {\n const bun = (globalThis as { Bun?: { serve?: DesktopServe } }).Bun;\n if (typeof bun?.serve !== 'function') {\n throw new Error(\n 'serveDesktopAgent needs Bun.serve, which Cottontail and Bun provide. Pass serve to use another server.'\n );\n }\n return options => bun.serve!(options);\n}\n\nfunction isAddressInUse(error: unknown): boolean {\n const code = (error as { code?: unknown } | null)?.code;\n return code === 'EADDRINUSE' || /in use|EADDRINUSE/i.test(String((error as Error | null)?.message ?? error));\n}\n","/**\n * An application in the main process: windows, and the channels each\n * one is served.\n *\n * Nothing here imports Electrobun, and that is not fastidiousness. The\n * SDK is projected into a project by Hutch rather than installed from\n * a registry, so a package in\n * this workspace could not import it even if it wanted to. What the\n * application supplies instead is one function that opens a window,\n * which is the only Electrobun-shaped thing this needs, and which is\n * five lines at the call site:\n *\n * const app = createDesktopApp({\n * channels: window => [\n * { token: Catalogue, source: catalogue },\n * windowsChannel(app, window)\n * ],\n * open: (receive, handle) => {\n * const rpc = BrowserView.defineRPC<GessoWindowRPC>({\n * handlers: { requests: {}, messages: { gessoFrame: receive } }\n * });\n * const window = new BrowserWindow({\n * title: 'Notes',\n * url: withWindowRoute('views://mainview/index.html', handle.route),\n * rpc\n * });\n * return {\n * send: frame => window.webview.rpc.send.gessoFrame(frame),\n * close: () => window.close()\n * };\n * }\n * });\n * app.openWindow();\n *\n * The arrangement it buys: every\n * window is a replica of the same channels, so two windows agree by\n * construction rather than by synchronisation.\n */\nimport { channel, type ChannelToken, type ServedChannel } from 'gesso-framework';\nimport { BehaviorSubject, type Observable, type Subscription } from 'rxjs';\n\nimport type { GessoFrame } from './frames';\nimport { serveChannelsToWindow, type ChannelHost } from './main';\n\nexport { withWindowRoute } from './route';\n\n/** What the application does with the window it opened. */\nexport interface DesktopWindowTransport {\n /** Sends one frame to this window, over its own RPC. */\n send: (frame: GessoFrame) => void;\n /** Closes the native window. The adapter calls this; the platform may also close it on its own. */\n close: () => void;\n}\n\n/** A window this application opened. */\nexport interface DesktopWindowHandle {\n /** Stable for the life of the window, and never reused. */\n readonly id: number;\n /**\n * The application url the window was asked to open at, or null for\n * wherever the application starts.\n *\n * Set by `openWindow({ route })`, which is what a Cmd-click on an\n * in-app link comes to by default. The window cannot be told it\n * through a frame in time to start there, so `open` puts it on the\n * view's url, with `withWindowRoute`, and the window reads it back\n * with `windowRoute` before its shell starts.\n */\n readonly route: string | null;\n /** Closes the window and disposes the channels it was served. */\n close(): void;\n}\n\n/** What `openWindow` may be told about the window it opens. */\nexport interface DesktopWindowOptions {\n /**\n * The application url to open at, `/epic/BUD-12?story=BUD-13`. Carried\n * to `open` as `window.route`; see `DesktopWindowHandle.route`.\n */\n readonly route?: string;\n}\n\nexport interface DesktopAppOptions {\n /**\n * The channels each window is served.\n *\n * A function when a window needs a channel of its own, which is what\n * `windowsChannel` uses to give a window a way to close itself. It\n * is called once per window, and the observables it returns are\n * ordinarily the same ones every time: `provide` keeps a separate\n * record of what each client has seen, so sharing a source between\n * windows is what makes them agree.\n */\n channels: readonly ServedChannel[] | ((window: DesktopWindowHandle) => readonly ServedChannel[]);\n /**\n * Opens a native window.\n *\n * `receive` is what the window's frames must be fed into: wire it to\n * the RPC message the window sends frames on, before the window\n * opens, or the first handshake is lost.\n *\n * `window.route` is the application url the window should start at,\n * or null. Put it on the view's url with `withWindowRoute`, and read\n * it in the window with `windowRoute`; an `open` that ignores it\n * opens every window at the application's start, as before.\n */\n open: (receive: (frame: GessoFrame) => void, window: DesktopWindowHandle) => DesktopWindowTransport;\n /**\n * A url a window asked to have opened outside itself, with the\n * window that asked. `Utils.openExternal(url)` is what an Electrobun\n * application passes here.\n */\n onOpenUrl?: (url: string, window: DesktopWindowHandle) => void;\n /**\n * One of the application's own urls a window asked to have opened\n * somewhere new (a Cmd-click or a Ctrl-click on an in-app link), with\n * the window that asked.\n *\n * Defaults to `app.openWindow({ route: url })`: a new window of this\n * application, at that route, which is what a new tab is in an\n * application whose windows have none. An outbound url never comes\n * here; it is `onOpenUrl`'s, and goes to the person's browser.\n */\n onOpenRoute?: (url: string, window: DesktopWindowHandle) => void;\n /**\n * The appearance the platform is in, pushed to every window as it\n * changes and to a new window as it opens.\n *\n * The application supplies it because Electrobun does not: its SDK\n * has no appearance API at all, and the webview's own\n * `prefers-color-scheme` is wrong on WebKitGTK\n *. On a platform that has a\n * signal, this is where it goes; on one that does not, an\n * application setting is a perfectly good source.\n */\n colorScheme?: Observable<'light' | 'dark'>;\n /**\n * Called when the last window closes. A desktop application usually\n * stops here; one with a tray or a menu bar does not, which is why\n * this is a callback rather than an exit.\n */\n onLastWindowClosed?: () => void;\n /** Overrides the frame size messages are split at. Only a test should need to. */\n chunkBytes?: number;\n}\n\nexport interface DesktopApp {\n /**\n * Opens a window, serves it every channel, and returns its handle;\n * at `options.route` when one is given.\n */\n openWindow(options?: DesktopWindowOptions): DesktopWindowHandle;\n /** The windows open now, in the order they were opened. */\n readonly windows: readonly DesktopWindowHandle[];\n /** How many windows are open, as something a channel can publish. */\n readonly windowCount: Observable<number>;\n /** Closes every window and stops serving. */\n dispose(): void;\n}\n\nexport function createDesktopApp(options: DesktopAppOptions): DesktopApp {\n interface Entry {\n handle: DesktopWindowHandle;\n transport: DesktopWindowTransport;\n host: ChannelHost;\n }\n const entries = new Map<number, Entry>();\n /** One appearance subscription per window, ended when it closes. */\n const subscriptions = new Map<number, Subscription>();\n const order: DesktopWindowHandle[] = [];\n const count = new BehaviorSubject(0);\n let nextId = 1;\n let disposed = false;\n\n const forget = (id: number, closeNative: boolean): void => {\n const entry = entries.get(id);\n if (entry === undefined) {\n return;\n }\n entries.delete(id);\n subscriptions.get(id)?.unsubscribe();\n subscriptions.delete(id);\n const at = order.indexOf(entry.handle);\n if (at >= 0) {\n order.splice(at, 1);\n }\n entry.host.dispose();\n if (closeNative) {\n entry.transport.close();\n }\n count.next(order.length);\n if (order.length === 0 && !disposed) {\n options.onLastWindowClosed?.();\n }\n };\n\n const app: DesktopApp = {\n openWindow(windowOptions: DesktopWindowOptions = {}): DesktopWindowHandle {\n if (disposed) {\n throw new Error('This desktop application has been disposed; it cannot open a window.');\n }\n const id = nextId++;\n const handle: DesktopWindowHandle = {\n id,\n route: windowOptions.route ?? null,\n close: () => forget(id, true)\n };\n\n // The host exists before the window does, because `open` may\n // deliver a frame synchronously and a window whose first\n // handshake was dropped never replicates anything.\n let transport: DesktopWindowTransport | undefined;\n const host = serveChannelsToWindow(\n typeof options.channels === 'function' ? options.channels(handle) : options.channels,\n {\n send: frame => transport?.send(frame),\n chunkBytes: options.chunkBytes,\n onOpenUrl: url => options.onOpenUrl?.(url, handle),\n onOpenRoute: url => {\n if (options.onOpenRoute !== undefined) {\n options.onOpenRoute(url, handle);\n } else if (!disposed) {\n app.openWindow({ route: url });\n }\n }\n }\n );\n entries.set(id, { handle, transport: { send: () => {}, close: () => {} }, host });\n order.push(handle);\n\n // Anything pushed before the window has spoken is lost: a\n // webview's RPC is not listening until its page has loaded, and\n // the window opens well before that. Channels do not notice,\n // because a channel starts with the window asking. The\n // appearance is the one thing this side sends first, so it waits\n // for the window's first frame, whatever that frame is. A native\n // window found this; a spec with a transport that was live\n // immediately could not.\n let greeted = false;\n let latest: 'light' | 'dark' | undefined;\n transport = options.open(frame => {\n if (!greeted) {\n greeted = true;\n if (latest !== undefined) {\n host.setColorScheme(latest);\n }\n }\n host.receive(frame);\n }, handle);\n entries.set(id, { handle, transport, host });\n const appearance = options.colorScheme?.subscribe(scheme => {\n latest = scheme;\n if (greeted) {\n host.setColorScheme(scheme);\n }\n });\n if (appearance !== undefined) {\n subscriptions.set(id, appearance);\n }\n count.next(order.length);\n return handle;\n },\n get windows(): readonly DesktopWindowHandle[] {\n return [...order];\n },\n windowCount: count.asObservable(),\n dispose(): void {\n disposed = true;\n for (const id of new Set(entries.keys())) {\n forget(id, true);\n }\n count.complete();\n }\n };\n\n return app;\n}\n\n/** What a window can see and do about the windows of its application. */\nexport interface DesktopWindowsView {\n /** How many windows this application has open. */\n count: number;\n /** Which one this is, so a screen can say \"window 2 of 3\" without asking. */\n id: number;\n}\n\nexport interface DesktopWindowsCommands {\n open: () => void;\n closeThis: () => void;\n}\n\n/**\n * The channel a window opens another window through.\n *\n * It exists because opening a window is the one native act a screen\n * genuinely needs, and going through a channel keeps the view layer\n * from importing an adapter. the \"native menus bound to\n * store actions\" is the same idea from the other end, and on Linux it\n * is the only end: the runtime has no application menus there, so a\n * menu is a component and this is what it calls.\n */\nexport const DesktopWindows: ChannelToken<DesktopWindowsView, DesktopWindowsCommands> = channel<\n DesktopWindowsView,\n DesktopWindowsCommands\n>('gesso:windows', { count: 0, id: 0 });\n\n/** Serves `DesktopWindows` to one window. Put it in `channels`. */\nexport function windowsChannel(app: DesktopApp, window: DesktopWindowHandle): ServedChannel {\n return {\n token: DesktopWindows,\n source: {\n view: {\n count: app.windowCount,\n id: new BehaviorSubject(window.id)\n },\n commands: {\n open: () => {\n app.openWindow();\n },\n closeThis: () => {\n window.close();\n }\n }\n }\n };\n}\n\nexport {\n messageBoxConfirm,\n serveDesktopAgent,\n type DesktopAgent,\n type DesktopAgentOptions,\n type DesktopServe,\n type ShowMessageBox\n} from './agent';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gesso-electrobun",
3
- "version": "0.6.7",
3
+ "version": "0.6.8",
4
4
  "description": "Run a Gesso application in an Electrobun window, with its stores in the main process.",
5
5
  "license": "MIT",
6
6
  "author": "Kevin Baker",
@@ -54,7 +54,7 @@
54
54
  "LICENSE"
55
55
  ],
56
56
  "dependencies": {
57
- "gesso-framework": "^0.6.7"
57
+ "gesso-framework": "^0.6.8"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "rxjs": "^7.8.2"