gesso-framework 0.6.30 → 0.8.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 (48) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/dist/{FunctionComponent-CHtojpp7.d.ts → FunctionComponent-BDVrANP0.d.ts} +371 -115
  3. package/dist/{serveChannels-BlEag7WU.js → WorkerPorts-m_hLRoPj.js} +8 -240
  4. package/dist/WorkerPorts-m_hLRoPj.js.map +1 -0
  5. package/dist/agent/index.d.ts +55 -2
  6. package/dist/agent/index.js +8 -4
  7. package/dist/agent/index.js.map +1 -1
  8. package/dist/app-De3z58x7.js +638 -0
  9. package/dist/app-De3z58x7.js.map +1 -0
  10. package/dist/computed-DyW9SPpq.js +587 -0
  11. package/dist/computed-DyW9SPpq.js.map +1 -0
  12. package/dist/createComponent-DJads88q.js +47 -0
  13. package/dist/createComponent-DJads88q.js.map +1 -0
  14. package/dist/{index-DZbSb8IN.d.ts → index-DocVauCS.d.ts} +593 -119
  15. package/dist/index.d.ts +4 -5
  16. package/dist/index.js +609 -6641
  17. package/dist/index.js.map +1 -1
  18. package/dist/jsx/jsx-runtime.d.ts +8 -5
  19. package/dist/jsx/jsx-runtime.js +15 -6
  20. package/dist/jsx/jsx-runtime.js.map +1 -1
  21. package/dist/{persisted-fUzIU8FZ.js → persisted-qEBOHDnj.js} +4 -576
  22. package/dist/persisted-qEBOHDnj.js.map +1 -0
  23. package/dist/remote/index.d.ts +2 -2
  24. package/dist/remote/index.js +2 -1
  25. package/dist/remote/index.js.map +1 -1
  26. package/dist/{remote-BoB8zh7z.js → remote-B4bjdTzg.js} +19 -4
  27. package/dist/remote-B4bjdTzg.js.map +1 -0
  28. package/dist/render-W2XO444K.js +7310 -0
  29. package/dist/render-W2XO444K.js.map +1 -0
  30. package/dist/serveChannels-DReCmRxO.js +241 -0
  31. package/dist/serveChannels-DReCmRxO.js.map +1 -0
  32. package/dist/{createComponent-3n1ZSql8.js → show-D0MevHaV.js} +2 -46
  33. package/dist/show-D0MevHaV.js.map +1 -0
  34. package/dist/{ui-CHtNccR8.d.ts → tools-Bl8LZ7LR.d.ts} +351 -8
  35. package/dist/{webmcp-CdDq2nzS.js → webmcp-D7IlA0fu.js} +3 -3
  36. package/dist/webmcp-D7IlA0fu.js.map +1 -0
  37. package/dist/worker/index.d.ts +3 -4
  38. package/dist/worker/index.js +5 -3
  39. package/dist/worker/index.js.map +1 -1
  40. package/package.json +2 -2
  41. package/dist/ChannelProtocol-ByNoHujM.d.ts +0 -247
  42. package/dist/app-Yig59Ygf.js +0 -326
  43. package/dist/app-Yig59Ygf.js.map +0 -1
  44. package/dist/createComponent-3n1ZSql8.js.map +0 -1
  45. package/dist/persisted-fUzIU8FZ.js.map +0 -1
  46. package/dist/remote-BoB8zh7z.js.map +0 -1
  47. package/dist/serveChannels-BlEag7WU.js.map +0 -1
  48. package/dist/webmcp-CdDq2nzS.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { f as ChannelToken, m as CommandMap, p as Command, r as ChannelPort } from "./ChannelProtocol-ByNoHujM.js";
1
+ import { L as ChannelToken, O as ChannelPort, R as Command, a as ComponentType, h as ChannelReplica, z as CommandMap } from "./FunctionComponent-BDVrANP0.js";
2
2
  import { Observable } from "rxjs";
3
- import { UiKeyModifiers, UiSemanticsAction, UiSemanticsMap, UiSemanticsRecord } from "gesso-core";
3
+ import { CanvasHost, UiChild, UiKeyModifiers, UiSemanticsAction, UiSemanticsMap, UiSemanticsRecord, UiSourceLocation, UiTheme } from "gesso-core";
4
4
  //#region src/channel/ChannelSchema.d.ts
5
5
  /**
6
6
  * A JSON Schema (draft 2020-12) object.
@@ -396,7 +396,16 @@ interface AgentTool {
396
396
  readonly idempotentHint?: boolean;
397
397
  readonly openWorldHint: false;
398
398
  };
399
+ /**
400
+ * MCP's own place for what is not in its schema. Gesso puts
401
+ * `gesso/waitsUpToMs` here for a tool that waits on purpose (see
402
+ * `AgentToolDefinition.waitsUpToMs`), so the bridges between the agent
403
+ * and the thread give its calls that long.
404
+ */
405
+ readonly _meta?: Readonly<Record<string, unknown>>;
399
406
  }
407
+ /** The `_meta` key a tool that waits on purpose says how long under, in milliseconds. */
408
+ declare const WAITS_UP_TO = "gesso/waitsUpToMs";
400
409
  /** A resource, in the shape MCP's `resources/list` returns. */
401
410
  interface AgentResource {
402
411
  readonly uri: string;
@@ -404,12 +413,20 @@ interface AgentResource {
404
413
  readonly description?: string;
405
414
  readonly mimeType: 'application/json';
406
415
  }
416
+ /** One part of a tool's answer: text, or a picture, as `render_frame` returns. */
417
+ type AgentContent = {
418
+ readonly type: 'text';
419
+ readonly text: string;
420
+ } | {
421
+ readonly type: 'image';
422
+ readonly data: string;
423
+ readonly mimeType: string;
424
+ };
425
+ /** A result's text parts, joined: what a reader that shows no pictures shows. */
426
+ declare function resultText(result: AgentToolResult): string;
407
427
  /** What a tool call returned, in the shape MCP's `tools/call` returns. */
408
428
  interface AgentToolResult {
409
- readonly content: readonly {
410
- readonly type: 'text';
411
- readonly text: string;
412
- }[];
429
+ readonly content: readonly AgentContent[];
413
430
  readonly structuredContent?: Record<string, unknown>;
414
431
  readonly isError: boolean;
415
432
  }
@@ -465,6 +482,258 @@ declare function agentSurface(channels: readonly ServedChannel[], options?: Agen
465
482
  /** The URI of a channel's view. */
466
483
  declare function resourceUri(channel: string): string;
467
484
  //#endregion
485
+ //#region src/agent/designedTheme.d.ts
486
+ /** A theme an agent asks for: the application's own, or the light or dark one it designs in. */
487
+ type AgentThemeName = 'app' | 'light' | 'dark';
488
+ /**
489
+ * The themes an application designs in, when they are not the ones it
490
+ * is drawn in: a design editor's are the project's, and its own chrome
491
+ * is no business of an agent designing that project.
492
+ */
493
+ interface DesignThemes {
494
+ readonly light?: UiTheme;
495
+ readonly dark?: UiTheme;
496
+ }
497
+ /**
498
+ * The theme an agent's `theme` names, and what to call it back.
499
+ *
500
+ * `light` and `dark` are the application's design themes, or Gesso's
501
+ * where it has none. `app` is the theme the running application is
502
+ * drawn in. Asked for nothing, an application that has design themes
503
+ * means them, its light one before its dark, since what it is drawn in
504
+ * is not what is being designed; one without them means itself.
505
+ */
506
+ declare function designedTheme(asked: AgentThemeName | undefined, themes: DesignThemes, appTheme: () => UiTheme | undefined): {
507
+ readonly theme: UiTheme;
508
+ readonly name: AgentThemeName;
509
+ };
510
+ //#endregion
511
+ //#region src/channel/ChannelRegistry.d.ts
512
+ /**
513
+ * The channels a runtime can hand to its components, by name.
514
+ *
515
+ * A replica is the only thing kept: whether the data lives in another
516
+ * worker or on this very thread is settled by the port it was attached
517
+ * with, and nothing above here can tell.
518
+ */
519
+ declare class ChannelRegistry {
520
+ private readonly replicas;
521
+ /** Attaches a channel over `port`. */
522
+ attach<View extends object, Commands extends object>(token: ChannelToken<View, Commands>, port: ChannelPort): ChannelReplica<View, Commands>;
523
+ get<View extends object, Commands extends object>(token: ChannelToken<View, Commands>): ChannelReplica<View, Commands>;
524
+ has(token: ChannelToken<object, CommandMap>): boolean;
525
+ /** Every attached replica, for frame-aligned patch flushing. */
526
+ all(): ChannelReplica<object, CommandMap>[];
527
+ dispose(): void;
528
+ }
529
+ //#endregion
530
+ //#region src/service/ServiceRegistry.d.ts
531
+ /**
532
+ * The runtime services a component may inject.
533
+ *
534
+ * A service is a plain class the runtime constructs once and hands to
535
+ * whoever asks: the clipboard and window (`ShellService`), overlays,
536
+ * focus, find, media, animation. They are not application state and
537
+ * they never cross a thread — they hold a `UiNode`, an `ImageResolver`,
538
+ * decoded bitmaps, a focus manager — so there is no replica, no patch
539
+ * and no wire format anywhere near them.
540
+ *
541
+ * That is the whole difference from what used to be here. Application
542
+ * state crosses the barrier and is declared as a channel; a service
543
+ * stays on the render thread and is simply called. Both used to be
544
+ * `Store`, and the single name hid which of the two rules applied.
545
+ */
546
+ declare class ServiceRegistry {
547
+ private readonly services;
548
+ private readonly parent;
549
+ /**
550
+ * @param parent Where `get` looks for a service this registry does not
551
+ * hold. A runtime drawing a frame off screen (`renderFrame`) gets one
552
+ * of these over the application's registry: the services the
553
+ * application registered are found there, and the runtime's own,
554
+ * which it registers wherever `has` says none is, are its own.
555
+ */
556
+ constructor(parent?: ServiceRegistry);
557
+ /** Constructs and registers a service, once. */
558
+ register<T extends object>(ServiceClass: new () => T): T;
559
+ /**
560
+ * Points an existing service at a replacement class of the same
561
+ * name, keeping the instance (the HMR).
562
+ *
563
+ * A registry is keyed by the class object, which is the right key
564
+ * for every purpose but one: replacing a module produces a new class
565
+ * object, so a component from the replaced module injects a class
566
+ * the registry has never seen even though a service of that name is
567
+ * sitting in it. Re-keying is what lets the tree be rebuilt from new
568
+ * code while the service it depends on carries on holding what it
569
+ * was holding.
570
+ *
571
+ * **The instance keeps the behaviour it was constructed with.** It
572
+ * was built from the old class and its methods are the old code, so
573
+ * a change to a service's own body needs a full reload to take
574
+ * effect. State survives; behaviour does not. That is the trade
575
+ * every hot-replacement system makes, and pretending otherwise would
576
+ * be worse than saying it.
577
+ *
578
+ * Returns false when nothing of that name was registered, which
579
+ * means the caller is looking at a genuinely new service rather than
580
+ * a replacement.
581
+ */
582
+ adopt(ServiceClass: new () => object): boolean;
583
+ get<T extends object>(ServiceClass: new () => T): T;
584
+ /** Whether this registry holds the service: not its parent, which `get` also asks. */
585
+ has(ServiceClass: Function): boolean;
586
+ }
587
+ //#endregion
588
+ //#region src/ComponentElement.d.ts
589
+ /**
590
+ * Declarative representation of a component instance in the tree.
591
+ *
592
+ * ComponentElements are resolved by ComponentRenderer into plain
593
+ * UiElement trees before being passed to UiGraphBuilder.
594
+ */
595
+ interface ComponentElement<P = Record<string, unknown>> {
596
+ readonly kind: 'component';
597
+ readonly tag: string;
598
+ /** The class or function to mount. */
599
+ readonly component: ComponentType;
600
+ readonly props: P;
601
+ readonly key?: string | number;
602
+ /** Where the component was used, in development; see `UiSourceLocation`. */
603
+ readonly source?: UiSourceLocation;
604
+ }
605
+ declare function isComponentElement(value: unknown): value is ComponentElement;
606
+ /**
607
+ * Anything the framework accepts as a child: a runtime element, an
608
+ * observable stream of elements, or a component definition.
609
+ */
610
+ type FrameworkChild = UiChild | ComponentElement;
611
+ //#endregion
612
+ //#region src/app/renderFrame.d.ts
613
+ /**
614
+ * A tree drawn once, off screen, to a PNG.
615
+ *
616
+ * What an agent designing a screen needs most is to see it. The screen
617
+ * in front of the person is one size and one state; a mock is wanted at
618
+ * a phone's width and a desktop's, and before anything has mounted it.
619
+ * So the tree gets a runtime of its own, on a canvas nobody sees, with
620
+ * a clock this drives by hand: it runs frames until the tree stops
621
+ * asking for them and no picture is still on its way (a picture being
622
+ * fetched asks for no frames until it arrives), sizes the canvas to the
623
+ * content, draws, and is thrown away.
624
+ *
625
+ * The runtime is the same one the application uses, with Canvas2D, so
626
+ * the picture is the application's pixels and not an approximation of
627
+ * them: the same layout engine, text shaping and paint.
628
+ */
629
+ /** A canvas this can read a PNG back from: an `OffscreenCanvas`, in a worker or a page. */
630
+ interface PictureCanvas extends CanvasHost {
631
+ convertToBlob(options?: {
632
+ type?: string;
633
+ }): Promise<Blob>;
634
+ }
635
+ interface RenderFrameOptions {
636
+ /** Its width, in logical pixels. */
637
+ readonly width: number;
638
+ /**
639
+ * Its height, which content that fills its parent fills: give one
640
+ * for a whole screen. Left out, the frame is as tall as its content,
641
+ * up to `maxHeight`, which is right for a component or a section; a
642
+ * screen that fills the window would collapse to what is in it.
643
+ */
644
+ readonly height?: number;
645
+ /** Device pixels per logical pixel (default 1). */
646
+ readonly scale?: number;
647
+ /** The theme it is drawn in (default the light theme). */
648
+ readonly theme?: UiTheme;
649
+ /** The tallest a frame grows to fit its content (default 4000). */
650
+ readonly maxHeight?: number;
651
+ /** Channels for the components in it to replicate: the running application's, to show its data. */
652
+ readonly channels?: ChannelRegistry;
653
+ /**
654
+ * Services for the components in it to inject: the running
655
+ * application's. The frame's runtime registers its own over them
656
+ * rather than borrowing them, since those belong to the window.
657
+ */
658
+ readonly services?: ServiceRegistry;
659
+ /** The longest to wait for the tree to stop asking for frames, in milliseconds (default 2000). */
660
+ readonly settleMs?: number;
661
+ /**
662
+ * Whether an error thrown while the frame is drawn is the frame's own,
663
+ * to keep it from the application's error handling: a draft an agent
664
+ * got wrong should not put an error over the person's screen (see
665
+ * `claimedErrors.ts`). One it does not claim is still listed in
666
+ * `errors`, and still reported.
667
+ */
668
+ readonly claims?: (error: unknown) => boolean;
669
+ /** Makes the canvas (default `new OffscreenCanvas`). */
670
+ readonly createCanvas?: (width: number, height: number) => PictureCanvas;
671
+ }
672
+ interface RenderedFrame {
673
+ readonly png: Uint8Array;
674
+ /** Logical pixels; the PNG is these times `scale`. */
675
+ readonly width: number;
676
+ readonly height: number;
677
+ /** Whether the content was taller than `maxHeight`, and is cut off there. */
678
+ readonly clipped: boolean;
679
+ /** Whether the content had no height of its own: a closed popup, say, or one waiting on inputs. */
680
+ readonly empty: boolean;
681
+ /**
682
+ * What was thrown and not caught in this thread while the frame was
683
+ * drawn. A component reading an input it was not given throws from a
684
+ * subscription, which no frame catches, and the picture alone would
685
+ * only show that nothing was drawn. The thread is shared, so an error
686
+ * here is likely the frame's and not certainly.
687
+ */
688
+ readonly errors: readonly string[];
689
+ }
690
+ declare function renderFrame(root: FrameworkChild, options: RenderFrameOptions): Promise<RenderedFrame>;
691
+ //#endregion
692
+ //#region src/agent/render.d.ts
693
+ /**
694
+ * `render_frame`: a module of the project, drawn to a PNG an agent can
695
+ * look at.
696
+ *
697
+ * An agent that writes a screen and cannot see it is designing blind;
698
+ * the outline `ui_snapshot` gives says what is there, not how it looks.
699
+ * So this draws one: a mock, or a component with inputs, at the width
700
+ * asked for, in the theme the agent is designing in by default (see
701
+ * `designedTheme`), through the same runtime the application draws with
702
+ * (see `renderFrame`).
703
+ *
704
+ * The module is loaded by path from where the application is served,
705
+ * which in development is the dev server: the same file the agent just
706
+ * wrote, as Vite serves it.
707
+ */
708
+ interface RenderRequest {
709
+ /** The module, from the project root, with or without its extension: `src/mocks/Settings`. */
710
+ readonly module: string;
711
+ /** The export to draw. By default the module's default export, else its only component. */
712
+ readonly export?: string;
713
+ /** Inputs for a component, by name. */
714
+ readonly inputs?: Readonly<Record<string, unknown>>;
715
+ readonly width: number;
716
+ readonly height?: number;
717
+ readonly scale?: number;
718
+ /** The theme to draw in; see `designedTheme` for what each means and which is the default. */
719
+ readonly theme?: AgentThemeName;
720
+ }
721
+ interface RenderDependencies {
722
+ /** Loads a module by its URL path, `/src/mocks/Settings.tsx`: `import()` where it is served. */
723
+ load(path: string): Promise<Record<string, unknown>>;
724
+ /** The theme the running application is drawn in. */
725
+ appTheme(): UiTheme | undefined;
726
+ /** The running application's channels, so a component that replicates one shows its data. */
727
+ readonly channels?: ChannelRegistry;
728
+ /** The running application's services, so a component that injects one finds it. */
729
+ readonly services?: ServiceRegistry;
730
+ /** The themes the application designs in, if it has any; see `designedTheme`. */
731
+ readonly themes?: () => DesignThemes;
732
+ /** Passed to `renderFrame`, for a test that has no `OffscreenCanvas`. */
733
+ readonly frame?: Pick<RenderFrameOptions, 'createCanvas' | 'settleMs'>;
734
+ }
735
+ declare function renderModule(request: RenderRequest, deps: RenderDependencies): Promise<AgentToolResult>;
736
+ //#endregion
468
737
  //#region src/agent/ui.d.ts
469
738
  /**
470
739
  * The screen, as tools an agent can use.
@@ -489,6 +758,8 @@ declare function resourceUri(channel: string): string;
489
758
  * ui_type replace a field's text
490
759
  * ui_focus move focus to a control
491
760
  * ui_key press a key where focus is: Enter, Escape, Tab, ArrowDown
761
+ * tokens_view the theme's colours, type, radii and spacing, by name
762
+ * render_frame a module of the project drawn to a PNG, at a width
492
763
  *
493
764
  * Each action answers with the outline as it is afterwards.
494
765
  */
@@ -504,6 +775,17 @@ interface UiHost {
504
775
  * this reads back.
505
776
  */
506
777
  flush(): void;
778
+ /**
779
+ * The theme in force at a node, by semantics record id, or at the
780
+ * application's outermost content when none is named. Optional, so a
781
+ * host written before it still serves the other tools; without it,
782
+ * `tokens_view` says it cannot answer.
783
+ */
784
+ theme?(at?: string): UiTheme | undefined;
785
+ /** The themes the application designs in, if they are not its own; see `designedTheme`. */
786
+ designThemes?(): DesignThemes;
787
+ /** Draws a module of the project to a PNG; see `renderModule`. Optional, as `theme` is. */
788
+ render?(request: RenderRequest): Promise<AgentToolResult>;
507
789
  }
508
790
  interface UiSurfaceOptions {
509
791
  /** How long the screen must hold still after an action before it is read back (default 50 ms). */
@@ -511,6 +793,13 @@ interface UiSurfaceOptions {
511
793
  /** The longest an action waits for that (default 1000 ms). */
512
794
  settleMs?: number;
513
795
  }
796
+ /**
797
+ * The screen's tools, by name: what a relay that carries only the screen
798
+ * (`gesso-electrobun`'s, to a desktop app's main process) lets through.
799
+ * Listed from the tools themselves, so a tool added here is relayed
800
+ * without the relay being told.
801
+ */
802
+ declare const SCREEN_TOOL_NAMES: readonly string[];
514
803
  /**
515
804
  * Short names for record ids.
516
805
  *
@@ -552,5 +841,59 @@ declare function outline(tree: UiSemanticsMap, focused: string | null, refs?: Ui
552
841
  */
553
842
  declare function resolveTarget(tree: UiSemanticsMap, args: Readonly<Record<string, unknown>>, refs?: UiRefs): UiSemanticsRecord | string;
554
843
  //#endregion
555
- export { portHandle as A, MessageEndpoint as C, isHubMessage as D, WorkerHandle as E, JsonSchema as F, channelSchema as I, describeChannel as L, workerHandle as M, ChannelSchema as N, isPortErrorMessage as O, CommandSchema as P, APPLICATION_WORKER as S, PortHost as T, serve as _, resolveTarget as a, ProvidedChannel as b, AgentResource as c, AgentSurfaceOptions as d, AgentTool as f, ServedChannel as g, resourceUri as h, outline as i, servePorts as j, isPortHandshake as k, AgentSurface as l, agentSurface as m, UiRefs as n, uiSurface as o, AgentToolResult as p, UiSurfaceOptions as r, AgentConfirmation as s, UiHost as t, AgentSurfaceLike as u, serveChannels as v, PortHandshake as w, provide as x, ChannelSource as y };
556
- //# sourceMappingURL=ui-CHtNccR8.d.ts.map
844
+ //#region src/agent/tools.d.ts
845
+ /**
846
+ * Tools an application offers an agent of its own accord, beside the
847
+ * ones its channels and its screen give it.
848
+ *
849
+ * A channel command answers with the view, which is right for "do this
850
+ * and show me the state": the agent sees what its call did. Some tools
851
+ * answer with something else: a measurement, a list of problems, a
852
+ * picture composed for the occasion. Those an application writes here,
853
+ * and the render worker serves them with the rest:
854
+ *
855
+ * renderRoot(App).useAgentTools([{
856
+ * name: 'board_check',
857
+ * description: 'What is wrong with a screen, by element.',
858
+ * inputSchema: { type: 'object', properties: { screen: { type: 'string' } }, required: ['screen'] },
859
+ * readOnly: true,
860
+ * run: async ({ screen }) => agentText(await check(screen as string))
861
+ * }]);
862
+ *
863
+ * Arguments are checked against `inputSchema` before `run` is called,
864
+ * and a thrown error becomes a result the agent reads, as every Gesso
865
+ * tool's does: an agent corrects a call it can read the failure of.
866
+ */
867
+ interface AgentToolDefinition {
868
+ readonly name: string;
869
+ readonly title?: string;
870
+ readonly description: string;
871
+ /** An object schema: the tool's arguments, by name. */
872
+ readonly inputSchema: JsonSchema;
873
+ /** It changes nothing. */
874
+ readonly readOnly?: boolean;
875
+ /** It may undo or overwrite something a person made. */
876
+ readonly destructive?: boolean;
877
+ /** Calling it twice with the same arguments does what calling it once did. */
878
+ readonly idempotent?: boolean;
879
+ /**
880
+ * How long a call may take, in milliseconds, for a tool that waits on
881
+ * purpose: for a person to answer, or for something to happen (a tool
882
+ * an agent calls to wait for its next task). The bridges between the
883
+ * agent and this thread give it that long, where they would give up on
884
+ * any other call after two minutes.
885
+ */
886
+ readonly waitsUpToMs?: number;
887
+ run(args: Readonly<Record<string, unknown>>): AgentToolResult | Promise<AgentToolResult>;
888
+ }
889
+ /** A tool's answer as text, and, given, the same as data. */
890
+ declare function agentText(text: string, structured?: Record<string, unknown>): AgentToolResult;
891
+ /** A tool's failure, written for the agent to correct its call by. */
892
+ declare function agentError(text: string): AgentToolResult;
893
+ /** A tool's answer as text and pictures, each a PNG's bytes. */
894
+ declare function agentImages(text: string, pictures: readonly Uint8Array[], structured?: Record<string, unknown>): AgentToolResult;
895
+ /** Serves a list of tool definitions as a surface, read afresh each time so a list may change. */
896
+ declare function toolSurface(definitions: () => readonly AgentToolDefinition[]): AgentSurfaceLike;
897
+ //#endregion
898
+ export { isPortHandshake as $, AgentResource as A, ServedChannel as B, ServiceRegistry as C, designedTheme as D, DesignThemes as E, AgentToolResult as F, provide as G, serveChannels as H, WAITS_UP_TO as I, PortHandshake as J, APPLICATION_WORKER as K, agentSurface as L, AgentSurfaceLike as M, AgentSurfaceOptions as N, AgentConfirmation as O, AgentTool as P, isPortErrorMessage as Q, resourceUri as R, isComponentElement as S, AgentThemeName as T, ChannelSource as U, serve as V, ProvidedChannel as W, WorkerHandle as X, PortHost as Y, isHubMessage as Z, RenderFrameOptions as _, toolSurface as a, JsonSchema as at, ComponentElement as b, UiRefs as c, resolveTarget as d, portHandle as et, uiSurface as f, PictureCanvas as g, renderModule as h, agentText as i, CommandSchema as it, AgentSurface as j, AgentContent as k, UiSurfaceOptions as l, RenderRequest as m, agentError as n, workerHandle as nt, SCREEN_TOOL_NAMES as o, channelSchema as ot, RenderDependencies as p, MessageEndpoint as q, agentImages as r, ChannelSchema as rt, UiHost as s, describeChannel as st, AgentToolDefinition as t, servePorts as tt, outline as u, RenderedFrame as v, ChannelRegistry as w, FrameworkChild as x, renderFrame as y, resultText as z };
899
+ //# sourceMappingURL=tools-Bl8LZ7LR.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { r as remoteSurface, t as AGENT_PORT } from "./remote-BoB8zh7z.js";
2
+ import { c as resultText, r as remoteSurface, t as AGENT_PORT } from "./remote-B4bjdTzg.js";
3
3
  //#region src/agent/webmcp.ts
4
4
  var webmcp_exports = /* @__PURE__ */ __exportAll({
5
5
  confirmInWindow: () => confirmInWindow,
@@ -67,11 +67,11 @@ function confirmInWindow(request) {
67
67
  }
68
68
  /** What a call hands back to WebMCP: the view it left, or a rejection that says why. */
69
69
  function answerOf(result) {
70
- const text = result.content.map((part) => part.text).join("\n");
70
+ const text = resultText(result);
71
71
  if (result.isError) throw new Error(text);
72
72
  return result.structuredContent ?? text;
73
73
  }
74
74
  //#endregion
75
75
  export { webmcp_exports as a, registerWebMcpTools as i, connectWebMcp as n, pageModelContext as r, confirmInWindow as t };
76
76
 
77
- //# sourceMappingURL=webmcp-CdDq2nzS.js.map
77
+ //# sourceMappingURL=webmcp-D7IlA0fu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp-D7IlA0fu.js","names":[],"sources":["../src/agent/webmcp.ts"],"sourcesContent":["import { resultText, type AgentConfirmation, type AgentSurfaceLike, type AgentToolResult } from './AgentSurface';\nimport { AGENT_PORT, remoteSurface, type AgentConfirm } from './remote';\n\n/**\n * An application's channels, offered to an agent in the browser.\n *\n * WebMCP (https://webmachinelearning.github.io/webmcp/) is a page\n * registering tools with the browser, `document.modelContext.registerTool`,\n * for an agent the browser runs or hosts to call. It is MCP without\n * the server: no port, no process, and the tools live exactly as long\n * as the page does. The channel surface is already a list of tools\n * with JSON Schema inputs and an answer per call, so this registers\n * each one and routes its calls back through the render worker.\n *\n * What the spec gives a tool that a channel does not, and the reverse:\n *\n * - `readOnlyHint` is a channel's view tool; `consequentialHint`, the\n * spec's \"significant, real-world, or non-reversible\", is a command\n * marked `@destructive`. `untrustedContentHint` is never set: a\n * channel's view is the application's own data.\n * - There is no output schema. The result of a call is serialized to\n * JSON for the agent, so a call answers with the view it left.\n * - There is no way to ask the person. A command marked `@confirm`\n * asks through `confirm`, `window.confirm` unless told otherwise.\n * - A failed call rejects, which the spec turns into a failed\n * `executeTool` the agent reads, with the sentence that says why.\n *\n * The API is behind an origin trial in Chrome 149 to 156, and was\n * `navigator.modelContext` until Chrome 150; the old name is read when\n * the new one is missing. Where neither exists, nothing is registered\n * and nothing fails: a page offering tools to a browser that takes\n * none is an ordinary page.\n */\n\n/** The part of `ModelContext` this uses. */\nexport interface ModelContextLike {\n registerTool(\n tool: {\n name: string;\n title?: string;\n description: string;\n inputSchema?: object;\n annotations?: { readOnlyHint?: boolean; consequentialHint?: boolean };\n execute: (input: Record<string, unknown>, options?: { signal?: AbortSignal }) => Promise<unknown>;\n },\n options?: { signal?: AbortSignal }\n ): Promise<void>;\n}\n\nexport interface WebMcpOptions {\n /**\n * Asks the person whether a `@confirm` command may be sent. Defaults to\n * the browser's own `window.confirm`, which an application with its\n * own dialogs will want to replace.\n */\n confirm?: AgentConfirm;\n /** Where to register. Defaults to the page's own `modelContext`. */\n modelContext?: ModelContextLike;\n}\n\n/** The page's model context, if the browser has one. */\nexport function pageModelContext(): ModelContextLike | undefined {\n const fromDocument = (globalThis.document as { modelContext?: ModelContextLike } | undefined)?.modelContext;\n return fromDocument ?? (globalThis.navigator as { modelContext?: ModelContextLike } | undefined)?.modelContext;\n}\n\n/**\n * Registers every tool a surface offers, and returns what removes them.\n *\n * A tool the browser refuses, a name already taken by something else on\n * the page, is reported on the console and skipped rather than taking\n * the rest down with it. Resolves to a no-op when there is no model\n * context to register with.\n */\nexport async function registerWebMcpTools(\n surface: AgentSurfaceLike,\n modelContext: ModelContextLike | undefined = pageModelContext()\n): Promise<() => void> {\n if (modelContext === undefined) {\n return () => {};\n }\n const registration = new AbortController();\n for (const tool of await surface.tools()) {\n try {\n await modelContext.registerTool(\n {\n name: tool.name,\n ...(tool.title === undefined ? {} : { title: tool.title }),\n description: tool.description,\n inputSchema: tool.inputSchema,\n annotations: {\n readOnlyHint: tool.annotations.readOnlyHint,\n consequentialHint: tool.annotations.destructiveHint === true\n },\n execute: async input => answerOf(await surface.call(tool.name, input ?? {}))\n },\n { signal: registration.signal }\n );\n } catch (error) {\n console.warn(`[gesso] WebMCP refused the tool ${tool.name}:`, error);\n }\n }\n return () => registration.abort();\n}\n\n/**\n * Registers an application's channels once it is mounted.\n *\n * Reaches them the way the development bridge does, through an agent\n * port to the render worker, so every channel the page can reach is\n * offered, wherever it is served.\n */\nexport async function connectWebMcp(\n app: { openRenderPort(key: string): MessagePort | undefined },\n options: WebMcpOptions = {}\n): Promise<() => void> {\n const modelContext = options.modelContext ?? pageModelContext();\n if (modelContext === undefined) {\n return () => {};\n }\n const port = app.openRenderPort(AGENT_PORT);\n if (port === undefined) {\n throw new Error('connectWebMcp needs a mounted app: call it after app.mount().');\n }\n const surface = remoteSurface(port, { confirm: options.confirm ?? confirmInWindow });\n const unregister = await registerWebMcpTools(surface, modelContext);\n return () => {\n unregister();\n port.close();\n };\n}\n\n/** The browser's own dialog, worded for a person who did not ask for anything. */\nexport function confirmInWindow(request: AgentConfirmation): boolean {\n const lines = [`An AI agent wants to ${request.command} in ${request.channel}.`];\n if (request.description !== undefined) {\n lines.push(request.description);\n }\n if (Object.keys(request.arguments).length > 0) {\n lines.push(JSON.stringify(request.arguments, null, 2));\n }\n if (request.destructive) {\n lines.push('This cannot be undone.');\n }\n return window.confirm(lines.join('\\n\\n'));\n}\n\n/** What a call hands back to WebMCP: the view it left, or a rejection that says why. */\nfunction answerOf(result: AgentToolResult): unknown {\n // A picture goes as structured content; see `render_frame`.\n const text = resultText(result);\n if (result.isError) {\n throw new Error(text);\n }\n return result.structuredContent ?? text;\n}\n"],"mappings":";;;;;;;;;;AA6DA,SAAgB,mBAAiD;CAE/D,OADsB,WAAW,UAA8D,gBACvE,WAAW,WAA+D;AACpG;;;;;;;;;AAUA,eAAsB,oBACpB,SACA,eAA6C,iBAAiB,GACzC;CACrB,IAAI,iBAAiB,KAAA,GACnB,aAAa,CAAC;CAEhB,MAAM,eAAe,IAAI,gBAAgB;CACzC,KAAK,MAAM,QAAQ,MAAM,QAAQ,MAAM,GACrC,IAAI;EACF,MAAM,aAAa,aACjB;GACE,MAAM,KAAK;GACX,GAAI,KAAK,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM;GACxD,aAAa,KAAK;GAClB,aAAa,KAAK;GAClB,aAAa;IACX,cAAc,KAAK,YAAY;IAC/B,mBAAmB,KAAK,YAAY,oBAAoB;GAC1D;GACA,SAAS,OAAM,UAAS,SAAS,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,CAAC,CAAC,CAAC;EAC7E,GACA,EAAE,QAAQ,aAAa,OAAO,CAChC;CACF,SAAS,OAAO;EACd,QAAQ,KAAK,mCAAmC,KAAK,KAAK,IAAI,KAAK;CACrE;CAEF,aAAa,aAAa,MAAM;AAClC;;;;;;;;AASA,eAAsB,cACpB,KACA,UAAyB,CAAC,GACL;CACrB,MAAM,eAAe,QAAQ,gBAAgB,iBAAiB;CAC9D,IAAI,iBAAiB,KAAA,GACnB,aAAa,CAAC;CAEhB,MAAM,OAAO,IAAI,eAAe,UAAU;CAC1C,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,+DAA+D;CAGjF,MAAM,aAAa,MAAM,oBADT,cAAc,MAAM,EAAE,SAAS,QAAQ,WAAW,gBAAgB,CAC/B,GAAG,YAAY;CAClE,aAAa;EACX,WAAW;EACX,KAAK,MAAM;CACb;AACF;;AAGA,SAAgB,gBAAgB,SAAqC;CACnE,MAAM,QAAQ,CAAC,wBAAwB,QAAQ,QAAQ,MAAM,QAAQ,QAAQ,EAAE;CAC/E,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,MAAM,KAAK,QAAQ,WAAW;CAEhC,IAAI,OAAO,KAAK,QAAQ,SAAS,CAAC,CAAC,SAAS,GAC1C,MAAM,KAAK,KAAK,UAAU,QAAQ,WAAW,MAAM,CAAC,CAAC;CAEvD,IAAI,QAAQ,aACV,MAAM,KAAK,wBAAwB;CAErC,OAAO,OAAO,QAAQ,MAAM,KAAK,MAAM,CAAC;AAC1C;;AAGA,SAAS,SAAS,QAAkC;CAElD,MAAM,OAAO,WAAW,MAAM;CAC9B,IAAI,OAAO,SACT,MAAM,IAAI,MAAM,IAAI;CAEtB,OAAO,OAAO,qBAAqB;AACrC"}
@@ -1,7 +1,6 @@
1
- import { _ as channel, a as isChannelHostMessage, d as ChannelSpec, f as ChannelToken, g as ViewOf, h as CommandsOf, i as isChannelClientMessage, m as CommandMap, n as ChannelHostMessage, p as Command, r as ChannelPort, t as ChannelClientMessage, v as defineChannel, y as viewKeys } from "../ChannelProtocol-ByNoHujM.js";
2
- import { A as portHandle, C as MessageEndpoint, O as isPortErrorMessage, S as APPLICATION_WORKER, T as PortHost, _ as serve, b as ProvidedChannel, g as ServedChannel, j as servePorts, k as isPortHandshake, v as serveChannels, x as provide, y as ChannelSource } from "../ui-CHtNccR8.js";
3
- import { C as InternalState, v as ReadableCell, w as internalState } from "../FunctionComponent-CHtojpp7.js";
4
- import { $ as StorageAdapter, $r as mutate, B as PersistedOptions, Cr as pick, Ft as ConsoleEntry, G as IndexedDbStorage, H as persisted, J as OpfsFileHandle, K as IndexedDbStorageOptions, Or as findUnplainPath, Q as MemoryStorage, Qr as Mutation, Sr as structurallyEqual, V as PersistedState, X as OpfsStorageOptions, Xr as throttled, Y as OpfsStorage, Yr as debounced, Z as OpfsWritable, Zr as MutateOptions, _i as Equality, ai as SelectOptions, at as storageReadValue, ct as UndoableOptions, dt as UndoStackOptions, ei as Resource, et as StorageOutcome, fi as ComputedCell, ft as UndoTransaction, gi as DeriveOptions, hi as computed, ii as resource, it as storageReadFailure, kr as requirePlainData, lt as undoable, mi as ReadSource, ni as ResourceState, nt as classifyStorageError, oi as select, pi as ComputedOptions, q as OpfsDirectory, ri as ResourceStatus, rt as storageErrorMessage, ti as ResourceOptions, tt as StorageRead, ut as UndoStack, vi as derive, wr as pickKeys } from "../index-DZbSb8IN.js";
1
+ import { A as isChannelHostMessage, B as CommandsOf, D as ChannelHostMessage, E as ChannelClientMessage, H as channel, I as ChannelSpec, L as ChannelToken, O as ChannelPort, R as Command, U as defineChannel, V as ViewOf, W as viewKeys, k as isChannelClientMessage, m as internalState, p as InternalState, x as ReadableCell, z as CommandMap } from "../FunctionComponent-BDVrANP0.js";
2
+ import { $ as isPortHandshake, B as ServedChannel, G as provide, H as serveChannels, K as APPLICATION_WORKER, Q as isPortErrorMessage, U as ChannelSource, V as serve, W as ProvidedChannel, Y as PortHost, et as portHandle, q as MessageEndpoint, tt as servePorts } from "../tools-Bl8LZ7LR.js";
3
+ import { $ as OpfsStorageOptions, Ai as DeriveOptions, Br as findUnplainPath, Di as ComputedOptions, Ei as ComputedCell, Fr as pick, G as persisted, Ir as pickKeys, J as IndexedDbStorage, Mi as derive, Oi as ReadSource, Pr as structurallyEqual, Q as OpfsStorage, Rt as ConsoleEntry, U as PersistedOptions, Vr as requirePlainData, W as PersistedState, X as OpfsDirectory, Y as IndexedDbStorageOptions, Z as OpfsFileHandle, _i as ResourceStatus, at as classifyStorageError, bi as select, ct as storageReadValue, di as MutateOptions, dt as UndoableOptions, et as OpfsWritable, fi as Mutation, ft as undoable, gi as ResourceState, hi as ResourceOptions, ht as UndoTransaction, it as StorageRead, ji as Equality, ki as computed, li as debounced, mi as Resource, mt as UndoStackOptions, nt as StorageAdapter, ot as storageErrorMessage, pi as mutate, pt as UndoStack, rt as StorageOutcome, st as storageReadFailure, tt as MemoryStorage, ui as throttled, vi as resource, yi as SelectOptions } from "../index-DocVauCS.js";
5
4
  import { BehaviorSubject } from "rxjs";
6
5
  //#region src/worker/captureConsole.d.ts
7
6
  /**
@@ -1,6 +1,8 @@
1
- import { S as derive, T as internalState, _ as Resource, a as undoable, b as ComputedCell, c as classifyStorageError, d as storageReadValue, f as pick, g as mutate, h as throttled, i as OpfsStorage, l as storageErrorMessage, m as debounced, n as persisted, o as UndoStack, p as pickKeys, r as IndexedDbStorage, s as MemoryStorage, t as PersistedState, u as storageReadFailure, v as resource, w as InternalState, x as computed, y as select } from "../persisted-fUzIU8FZ.js";
2
- import { C as isChannelClientMessage, D as viewKeys, E as defineChannel, O as structurallyEqual, T as channel, a as isPortErrorMessage, c as servePorts, n as serveChannels, o as isPortHandshake, r as APPLICATION_WORKER, s as portHandle, t as serve, u as captureConsole, v as ProvidedChannel, w as isChannelHostMessage, y as provide } from "../serveChannels-BlEag7WU.js";
3
- import { c as findUnplainPath, l as requirePlainData } from "../remote-BoB8zh7z.js";
1
+ import { a as InternalState, n as computed, o as internalState, r as derive, t as ComputedCell } from "../computed-DyW9SPpq.js";
2
+ import { C as defineChannel, S as channel, T as structurallyEqual, a as portHandle, b as isChannelClientMessage, c as captureConsole, i as isPortHandshake, o as servePorts, r as isPortErrorMessage, t as APPLICATION_WORKER, w as viewKeys, x as isChannelHostMessage } from "../WorkerPorts-m_hLRoPj.js";
3
+ import { _ as Resource, a as undoable, c as classifyStorageError, d as storageReadValue, f as pick, g as mutate, h as throttled, i as OpfsStorage, l as storageErrorMessage, m as debounced, n as persisted, o as UndoStack, p as pickKeys, r as IndexedDbStorage, s as MemoryStorage, t as PersistedState, u as storageReadFailure, v as resource, y as select } from "../persisted-qEBOHDnj.js";
4
+ import { d as requirePlainData, u as findUnplainPath } from "../remote-B4bjdTzg.js";
5
+ import { i as provide, n as serveChannels, r as ProvidedChannel, t as serve } from "../serveChannels-DReCmRxO.js";
4
6
  import { BehaviorSubject } from "rxjs";
5
7
  //#region src/worker/supervise.ts
6
8
  /** The field the two halves' own messages carry, so an application's never collide with them. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/worker/supervise.ts"],"sourcesContent":["import { BehaviorSubject } from 'rxjs';\n\n/**\n * A worker that runs code this application does not trust to behave: a\n * plugin, a user's script, a parser fed whatever a file holds. It is\n * started, asked every second whether it is there, and stopped if it has\n * not answered for too long, or has not said it is ready in time, so a\n * loop in it costs a core and nothing else: no thread waits on it, and\n * the person is told.\n *\n * Two halves. `SupervisedWorker` is the side that starts the worker, in\n * an application worker or the page; `superviseSelf` is the worker's,\n * which answers the questions, says when it is ready, and reports what\n * nothing in it caught. Their own messages are told apart from the\n * application's by a field of their own, so either side's protocol is\n * its business: `onMessage` and `post` carry everything else.\n *\n * Found building Gesso Code's plugins, whose host did all of this by\n * hand.\n */\n\n/** Where a supervised worker is in its life. */\nexport type SupervisedState = 'starting' | 'running' | 'failed' | 'stopped';\n\nexport interface SupervisedStatus {\n readonly state: SupervisedState;\n /** Why it failed or was stopped; `''` while it starts or runs. */\n readonly message: string;\n /** The stack of what it threw, when it failed by throwing; `''` otherwise. */\n readonly stack: string;\n}\n\n/** What the worker said that went wrong: `failed` before it was ready, `error` after, which it lives through. */\nexport interface SupervisedError {\n readonly message: string;\n readonly stack: string;\n}\n\n/** The field the two halves' own messages carry, so an application's never collide with them. */\nconst KEY = '__gessoSupervised';\n\ntype Supervision =\n | { readonly [KEY]: 'ping'; readonly n: number }\n | { readonly [KEY]: 'pong'; readonly n: number }\n | { readonly [KEY]: 'ready' }\n | { readonly [KEY]: 'failed'; readonly message: string; readonly stack: string }\n | { readonly [KEY]: 'error'; readonly message: string; readonly stack: string };\n\nfunction isSupervision(data: unknown): data is Supervision {\n return typeof data === 'object' && data !== null && KEY in data;\n}\n\n/** What `SupervisedWorker` needs of a worker: a `Worker`, or anything shaped like one, as a spec's fake. */\nexport interface SupervisableWorker {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n terminate(): void;\n onmessage: ((event: MessageEvent) => void) | null;\n onerror: ((event: ErrorEvent) => void) | null;\n}\n\nexport interface SupervisedWorkerOptions {\n /** How long it may go without answering, in ms: 5 s by default. */\n readonly unansweredMs?: number;\n /** How long it has to say it is ready, in ms: 10 s by default. */\n readonly startMs?: number;\n /** How often it is asked, in ms: every second by default. */\n readonly pingMs?: number;\n /** Every message that is not the supervision's own. */\n readonly onMessage?: (data: unknown) => void;\n /** What it reported that nothing in it caught, while it ran on. */\n readonly onError?: (error: SupervisedError) => void;\n /**\n * The clock and the timer: a spec passes its own, and calls `check`.\n * `schedule` returns what cancels it.\n */\n readonly now?: () => number;\n readonly schedule?: (check: () => void, everyMs: number) => () => void;\n}\n\nexport class SupervisedWorker {\n /** Where it is, and why it stopped. */\n readonly status$: BehaviorSubject<SupervisedStatus>;\n private readonly started: number;\n private lastAnswer: number;\n private pinged = 0;\n private readonly cancel: () => void;\n private readonly now: () => number;\n private readonly unansweredMs: number;\n private readonly startMs: number;\n\n constructor(\n private readonly worker: SupervisableWorker,\n private readonly options: SupervisedWorkerOptions = {}\n ) {\n this.now = options.now ?? (() => performance.now());\n this.unansweredMs = options.unansweredMs ?? 5000;\n this.startMs = options.startMs ?? 10_000;\n this.started = this.now();\n this.lastAnswer = this.started;\n this.status$ = new BehaviorSubject<SupervisedStatus>({ state: 'starting', message: '', stack: '' });\n worker.onmessage = event => this.receive(event.data);\n worker.onerror = event => {\n // The worker's script itself would not load or run: nothing more will come from it.\n event.preventDefault();\n this.end('failed', `Its worker failed: ${event.message}`);\n };\n const schedule =\n options.schedule ??\n ((check, everyMs) => {\n const timer = setInterval(check, everyMs);\n return () => clearInterval(timer);\n });\n this.cancel = schedule(() => this.check(), options.pingMs ?? 1000);\n }\n\n get state(): SupervisedState {\n return this.status$.value.state;\n }\n\n /** Whether it can still be sent anything: it has not failed or been stopped. */\n get alive(): boolean {\n return this.state === 'starting' || this.state === 'running';\n }\n\n /** Sends it a message of the application's; nothing, once it is not alive. */\n post(message: unknown, transfer?: Transferable[]): void {\n if (this.alive) this.worker.postMessage(message, transfer);\n }\n\n /** Ends it, as the application decided: by the person, or for something it sent. */\n stop(message = 'Stopped.'): void {\n this.end('stopped', message);\n }\n\n /** Ends it as failed: what it did, or did not do, means it cannot go on. */\n fail(message: string): void {\n this.end('failed', message);\n }\n\n /** Asks it whether it is there, and ends it if it has been too long. The timer calls it; a spec may. */\n check(): void {\n if (!this.alive) return;\n const now = this.now();\n if (this.state === 'starting' && now - this.started > this.startMs) {\n this.end('failed', `It did not say it was ready within ${seconds(this.startMs)}.`);\n return;\n }\n if (now - this.lastAnswer > this.unansweredMs) {\n this.end('stopped', `It was busy for ${seconds(this.unansweredMs)} without answering.`);\n return;\n }\n this.worker.postMessage({ [KEY]: 'ping', n: ++this.pinged } satisfies Supervision);\n }\n\n private receive(data: unknown): void {\n if (!this.alive) return;\n // Anything it says is an answer: a busy worker that still sends is not stuck.\n this.lastAnswer = this.now();\n if (!isSupervision(data)) {\n this.options.onMessage?.(data);\n return;\n }\n switch (data[KEY]) {\n case 'pong':\n case 'ping':\n return;\n case 'ready':\n if (this.state === 'starting') this.status$.next({ state: 'running', message: '', stack: '' });\n return;\n case 'failed':\n this.end('failed', data.message, data.stack);\n return;\n case 'error':\n this.options.onError?.({ message: data.message, stack: data.stack });\n return;\n }\n }\n\n private end(state: 'failed' | 'stopped', message: string, stack = ''): void {\n if (!this.alive) return;\n this.worker.terminate();\n this.cancel();\n this.status$.next({ state, message, stack });\n }\n}\n\nfunction seconds(ms: number): string {\n const s = ms / 1000;\n return `${s} second${s === 1 ? '' : 's'}`;\n}\n\n/** What a worker's global is, as far as `superviseSelf` uses it. */\nexport interface SupervisedScope {\n postMessage(message: unknown): void;\n addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;\n addEventListener(type: 'error', listener: (event: ErrorEvent) => void): void;\n addEventListener(type: 'unhandledrejection', listener: (event: PromiseRejectionEvent) => void): void;\n}\n\nexport interface SupervisedSelf {\n /** Says it is ready: `SupervisedWorker` stops counting toward `startMs`. */\n ready(): void;\n /** Says it could not start (its setup threw): it is ended, as failed, with this. */\n failed(error: unknown): void;\n /** Says something went wrong that it lives through: the other side's `onError` hears it. */\n report(error: unknown): void;\n}\n\n/**\n * The worker's half: answers `SupervisedWorker`'s questions as they\n * come, and reports what nothing caught. Call it first, before anything\n * that can loop: an answer is only late while the worker is busy, which\n * is the point.\n *\n * `onMessage` is given every message that is not the supervision's own;\n * a worker can as well add its own listener and ignore those\n * (`isSupervisionMessage`).\n */\nexport function superviseSelf(\n onMessage?: (data: unknown) => void,\n scope: SupervisedScope = self as unknown as SupervisedScope\n): SupervisedSelf {\n const describe = (error: unknown): SupervisedError =>\n error instanceof Error\n ? { message: error.message, stack: error.stack ?? '' }\n : { message: String(error), stack: '' };\n scope.addEventListener('message', event => {\n const data: unknown = event.data;\n if (!isSupervision(data)) {\n onMessage?.(data);\n return;\n }\n if (data[KEY] === 'ping') scope.postMessage({ [KEY]: 'pong', n: data.n } satisfies Supervision);\n });\n scope.addEventListener('error', event =>\n scope.postMessage({ [KEY]: 'error', ...describe(event.error ?? event.message) } satisfies Supervision)\n );\n scope.addEventListener('unhandledrejection', event =>\n scope.postMessage({ [KEY]: 'error', ...describe(event.reason) } satisfies Supervision)\n );\n return {\n ready: () => scope.postMessage({ [KEY]: 'ready' } satisfies Supervision),\n failed: error => scope.postMessage({ [KEY]: 'failed', ...describe(error) } satisfies Supervision),\n report: error => scope.postMessage({ [KEY]: 'error', ...describe(error) } satisfies Supervision)\n };\n}\n\n/** Whether a message is one of the supervision's own, which an application's listener can pass over. */\nexport function isSupervisionMessage(data: unknown): boolean {\n return isSupervision(data);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/worker/supervise.ts"],"sourcesContent":["import { BehaviorSubject } from 'rxjs';\n\n/**\n * A worker that runs code this application does not trust to behave: a\n * plugin, a user's script, a parser fed whatever a file holds. It is\n * started, asked every second whether it is there, and stopped if it has\n * not answered for too long, or has not said it is ready in time, so a\n * loop in it costs a core and nothing else: no thread waits on it, and\n * the person is told.\n *\n * Two halves. `SupervisedWorker` is the side that starts the worker, in\n * an application worker or the page; `superviseSelf` is the worker's,\n * which answers the questions, says when it is ready, and reports what\n * nothing in it caught. Their own messages are told apart from the\n * application's by a field of their own, so either side's protocol is\n * its business: `onMessage` and `post` carry everything else.\n *\n * Found building Gesso Code's plugins, whose host did all of this by\n * hand.\n */\n\n/** Where a supervised worker is in its life. */\nexport type SupervisedState = 'starting' | 'running' | 'failed' | 'stopped';\n\nexport interface SupervisedStatus {\n readonly state: SupervisedState;\n /** Why it failed or was stopped; `''` while it starts or runs. */\n readonly message: string;\n /** The stack of what it threw, when it failed by throwing; `''` otherwise. */\n readonly stack: string;\n}\n\n/** What the worker said that went wrong: `failed` before it was ready, `error` after, which it lives through. */\nexport interface SupervisedError {\n readonly message: string;\n readonly stack: string;\n}\n\n/** The field the two halves' own messages carry, so an application's never collide with them. */\nconst KEY = '__gessoSupervised';\n\ntype Supervision =\n | { readonly [KEY]: 'ping'; readonly n: number }\n | { readonly [KEY]: 'pong'; readonly n: number }\n | { readonly [KEY]: 'ready' }\n | { readonly [KEY]: 'failed'; readonly message: string; readonly stack: string }\n | { readonly [KEY]: 'error'; readonly message: string; readonly stack: string };\n\nfunction isSupervision(data: unknown): data is Supervision {\n return typeof data === 'object' && data !== null && KEY in data;\n}\n\n/** What `SupervisedWorker` needs of a worker: a `Worker`, or anything shaped like one, as a spec's fake. */\nexport interface SupervisableWorker {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n terminate(): void;\n onmessage: ((event: MessageEvent) => void) | null;\n onerror: ((event: ErrorEvent) => void) | null;\n}\n\nexport interface SupervisedWorkerOptions {\n /** How long it may go without answering, in ms: 5 s by default. */\n readonly unansweredMs?: number;\n /** How long it has to say it is ready, in ms: 10 s by default. */\n readonly startMs?: number;\n /** How often it is asked, in ms: every second by default. */\n readonly pingMs?: number;\n /** Every message that is not the supervision's own. */\n readonly onMessage?: (data: unknown) => void;\n /** What it reported that nothing in it caught, while it ran on. */\n readonly onError?: (error: SupervisedError) => void;\n /**\n * The clock and the timer: a spec passes its own, and calls `check`.\n * `schedule` returns what cancels it.\n */\n readonly now?: () => number;\n readonly schedule?: (check: () => void, everyMs: number) => () => void;\n}\n\nexport class SupervisedWorker {\n /** Where it is, and why it stopped. */\n readonly status$: BehaviorSubject<SupervisedStatus>;\n private readonly started: number;\n private lastAnswer: number;\n private pinged = 0;\n private readonly cancel: () => void;\n private readonly now: () => number;\n private readonly unansweredMs: number;\n private readonly startMs: number;\n\n constructor(\n private readonly worker: SupervisableWorker,\n private readonly options: SupervisedWorkerOptions = {}\n ) {\n this.now = options.now ?? (() => performance.now());\n this.unansweredMs = options.unansweredMs ?? 5000;\n this.startMs = options.startMs ?? 10_000;\n this.started = this.now();\n this.lastAnswer = this.started;\n this.status$ = new BehaviorSubject<SupervisedStatus>({ state: 'starting', message: '', stack: '' });\n worker.onmessage = event => this.receive(event.data);\n worker.onerror = event => {\n // The worker's script itself would not load or run: nothing more will come from it.\n event.preventDefault();\n this.end('failed', `Its worker failed: ${event.message}`);\n };\n const schedule =\n options.schedule ??\n ((check, everyMs) => {\n const timer = setInterval(check, everyMs);\n return () => clearInterval(timer);\n });\n this.cancel = schedule(() => this.check(), options.pingMs ?? 1000);\n }\n\n get state(): SupervisedState {\n return this.status$.value.state;\n }\n\n /** Whether it can still be sent anything: it has not failed or been stopped. */\n get alive(): boolean {\n return this.state === 'starting' || this.state === 'running';\n }\n\n /** Sends it a message of the application's; nothing, once it is not alive. */\n post(message: unknown, transfer?: Transferable[]): void {\n if (this.alive) this.worker.postMessage(message, transfer);\n }\n\n /** Ends it, as the application decided: by the person, or for something it sent. */\n stop(message = 'Stopped.'): void {\n this.end('stopped', message);\n }\n\n /** Ends it as failed: what it did, or did not do, means it cannot go on. */\n fail(message: string): void {\n this.end('failed', message);\n }\n\n /** Asks it whether it is there, and ends it if it has been too long. The timer calls it; a spec may. */\n check(): void {\n if (!this.alive) return;\n const now = this.now();\n if (this.state === 'starting' && now - this.started > this.startMs) {\n this.end('failed', `It did not say it was ready within ${seconds(this.startMs)}.`);\n return;\n }\n if (now - this.lastAnswer > this.unansweredMs) {\n this.end('stopped', `It was busy for ${seconds(this.unansweredMs)} without answering.`);\n return;\n }\n this.worker.postMessage({ [KEY]: 'ping', n: ++this.pinged } satisfies Supervision);\n }\n\n private receive(data: unknown): void {\n if (!this.alive) return;\n // Anything it says is an answer: a busy worker that still sends is not stuck.\n this.lastAnswer = this.now();\n if (!isSupervision(data)) {\n this.options.onMessage?.(data);\n return;\n }\n switch (data[KEY]) {\n case 'pong':\n case 'ping':\n return;\n case 'ready':\n if (this.state === 'starting') this.status$.next({ state: 'running', message: '', stack: '' });\n return;\n case 'failed':\n this.end('failed', data.message, data.stack);\n return;\n case 'error':\n this.options.onError?.({ message: data.message, stack: data.stack });\n return;\n }\n }\n\n private end(state: 'failed' | 'stopped', message: string, stack = ''): void {\n if (!this.alive) return;\n this.worker.terminate();\n this.cancel();\n this.status$.next({ state, message, stack });\n }\n}\n\nfunction seconds(ms: number): string {\n const s = ms / 1000;\n return `${s} second${s === 1 ? '' : 's'}`;\n}\n\n/** What a worker's global is, as far as `superviseSelf` uses it. */\nexport interface SupervisedScope {\n postMessage(message: unknown): void;\n addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;\n addEventListener(type: 'error', listener: (event: ErrorEvent) => void): void;\n addEventListener(type: 'unhandledrejection', listener: (event: PromiseRejectionEvent) => void): void;\n}\n\nexport interface SupervisedSelf {\n /** Says it is ready: `SupervisedWorker` stops counting toward `startMs`. */\n ready(): void;\n /** Says it could not start (its setup threw): it is ended, as failed, with this. */\n failed(error: unknown): void;\n /** Says something went wrong that it lives through: the other side's `onError` hears it. */\n report(error: unknown): void;\n}\n\n/**\n * The worker's half: answers `SupervisedWorker`'s questions as they\n * come, and reports what nothing caught. Call it first, before anything\n * that can loop: an answer is only late while the worker is busy, which\n * is the point.\n *\n * `onMessage` is given every message that is not the supervision's own;\n * a worker can as well add its own listener and ignore those\n * (`isSupervisionMessage`).\n */\nexport function superviseSelf(\n onMessage?: (data: unknown) => void,\n scope: SupervisedScope = self as unknown as SupervisedScope\n): SupervisedSelf {\n const describe = (error: unknown): SupervisedError =>\n error instanceof Error\n ? { message: error.message, stack: error.stack ?? '' }\n : { message: String(error), stack: '' };\n scope.addEventListener('message', event => {\n const data: unknown = event.data;\n if (!isSupervision(data)) {\n onMessage?.(data);\n return;\n }\n if (data[KEY] === 'ping') scope.postMessage({ [KEY]: 'pong', n: data.n } satisfies Supervision);\n });\n scope.addEventListener('error', event =>\n scope.postMessage({ [KEY]: 'error', ...describe(event.error ?? event.message) } satisfies Supervision)\n );\n scope.addEventListener('unhandledrejection', event =>\n scope.postMessage({ [KEY]: 'error', ...describe(event.reason) } satisfies Supervision)\n );\n return {\n ready: () => scope.postMessage({ [KEY]: 'ready' } satisfies Supervision),\n failed: error => scope.postMessage({ [KEY]: 'failed', ...describe(error) } satisfies Supervision),\n report: error => scope.postMessage({ [KEY]: 'error', ...describe(error) } satisfies Supervision)\n };\n}\n\n/** Whether a message is one of the supervision's own, which an application's listener can pass over. */\nexport function isSupervisionMessage(data: unknown): boolean {\n return isSupervision(data);\n}\n"],"mappings":";;;;;;;;AAuCA,MAAM,MAAM;AASZ,SAAS,cAAc,MAAoC;CACzD,OAAO,OAAO,SAAS,YAAY,SAAS,QAAQ,OAAO;AAC7D;AA6BA,IAAa,mBAAb,MAA8B;CAYT;CACA;;CAXnB;CACA;CACA;CACA,SAAiB;CACjB;CACA;CACA;CACA;CAEA,YACE,QACA,UAAoD,CAAC,GACrD;EAFiB,KAAA,SAAA;EACA,KAAA,UAAA;EAEjB,KAAK,MAAM,QAAQ,cAAc,YAAY,IAAI;EACjD,KAAK,eAAe,QAAQ,gBAAgB;EAC5C,KAAK,UAAU,QAAQ,WAAW;EAClC,KAAK,UAAU,KAAK,IAAI;EACxB,KAAK,aAAa,KAAK;EACvB,KAAK,UAAU,IAAI,gBAAkC;GAAE,OAAO;GAAY,SAAS;GAAI,OAAO;EAAG,CAAC;EAClG,OAAO,aAAY,UAAS,KAAK,QAAQ,MAAM,IAAI;EACnD,OAAO,WAAU,UAAS;GAExB,MAAM,eAAe;GACrB,KAAK,IAAI,UAAU,sBAAsB,MAAM,SAAS;EAC1D;EACA,MAAM,WACJ,QAAQ,cACN,OAAO,YAAY;GACnB,MAAM,QAAQ,YAAY,OAAO,OAAO;GACxC,aAAa,cAAc,KAAK;EAClC;EACF,KAAK,SAAS,eAAe,KAAK,MAAM,GAAG,QAAQ,UAAU,GAAI;CACnE;CAEA,IAAI,QAAyB;EAC3B,OAAO,KAAK,QAAQ,MAAM;CAC5B;;CAGA,IAAI,QAAiB;EACnB,OAAO,KAAK,UAAU,cAAc,KAAK,UAAU;CACrD;;CAGA,KAAK,SAAkB,UAAiC;EACtD,IAAI,KAAK,OAAO,KAAK,OAAO,YAAY,SAAS,QAAQ;CAC3D;;CAGA,KAAK,UAAU,YAAkB;EAC/B,KAAK,IAAI,WAAW,OAAO;CAC7B;;CAGA,KAAK,SAAuB;EAC1B,KAAK,IAAI,UAAU,OAAO;CAC5B;;CAGA,QAAc;EACZ,IAAI,CAAC,KAAK,OAAO;EACjB,MAAM,MAAM,KAAK,IAAI;EACrB,IAAI,KAAK,UAAU,cAAc,MAAM,KAAK,UAAU,KAAK,SAAS;GAClE,KAAK,IAAI,UAAU,sCAAsC,QAAQ,KAAK,OAAO,EAAE,EAAE;GACjF;EACF;EACA,IAAI,MAAM,KAAK,aAAa,KAAK,cAAc;GAC7C,KAAK,IAAI,WAAW,mBAAmB,QAAQ,KAAK,YAAY,EAAE,oBAAoB;GACtF;EACF;EACA,KAAK,OAAO,YAAY;IAAG,MAAM;GAAQ,GAAG,EAAE,KAAK;EAAO,CAAuB;CACnF;CAEA,QAAgB,MAAqB;EACnC,IAAI,CAAC,KAAK,OAAO;EAEjB,KAAK,aAAa,KAAK,IAAI;EAC3B,IAAI,CAAC,cAAc,IAAI,GAAG;GACxB,KAAK,QAAQ,YAAY,IAAI;GAC7B;EACF;EACA,QAAQ,KAAK,MAAb;GACE,KAAK;GACL,KAAK,QACH;GACF,KAAK;IACH,IAAI,KAAK,UAAU,YAAY,KAAK,QAAQ,KAAK;KAAE,OAAO;KAAW,SAAS;KAAI,OAAO;IAAG,CAAC;IAC7F;GACF,KAAK;IACH,KAAK,IAAI,UAAU,KAAK,SAAS,KAAK,KAAK;IAC3C;GACF,KAAK;IACH,KAAK,QAAQ,UAAU;KAAE,SAAS,KAAK;KAAS,OAAO,KAAK;IAAM,CAAC;IACnE;EACJ;CACF;CAEA,IAAY,OAA6B,SAAiB,QAAQ,IAAU;EAC1E,IAAI,CAAC,KAAK,OAAO;EACjB,KAAK,OAAO,UAAU;EACtB,KAAK,OAAO;EACZ,KAAK,QAAQ,KAAK;GAAE;GAAO;GAAS;EAAM,CAAC;CAC7C;AACF;AAEA,SAAS,QAAQ,IAAoB;CACnC,MAAM,IAAI,KAAK;CACf,OAAO,GAAG,EAAE,SAAS,MAAM,IAAI,KAAK;AACtC;;;;;;;;;;;AA6BA,SAAgB,cACd,WACA,QAAyB,MACT;CAChB,MAAM,YAAY,UAChB,iBAAiB,QACb;EAAE,SAAS,MAAM;EAAS,OAAO,MAAM,SAAS;CAAG,IACnD;EAAE,SAAS,OAAO,KAAK;EAAG,OAAO;CAAG;CAC1C,MAAM,iBAAiB,YAAW,UAAS;EACzC,MAAM,OAAgB,MAAM;EAC5B,IAAI,CAAC,cAAc,IAAI,GAAG;GACxB,YAAY,IAAI;GAChB;EACF;EACA,IAAI,KAAK,SAAS,QAAQ,MAAM,YAAY;IAAG,MAAM;GAAQ,GAAG,KAAK;EAAE,CAAuB;CAChG,CAAC;CACD,MAAM,iBAAiB,UAAS,UAC9B,MAAM,YAAY;GAAG,MAAM;EAAS,GAAG,SAAS,MAAM,SAAS,MAAM,OAAO;CAAE,CAAuB,CACvG;CACA,MAAM,iBAAiB,uBAAsB,UAC3C,MAAM,YAAY;GAAG,MAAM;EAAS,GAAG,SAAS,MAAM,MAAM;CAAE,CAAuB,CACvF;CACA,OAAO;EACL,aAAa,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAuB;EACvE,SAAQ,UAAS,MAAM,YAAY;IAAG,MAAM;GAAU,GAAG,SAAS,KAAK;EAAE,CAAuB;EAChG,SAAQ,UAAS,MAAM,YAAY;IAAG,MAAM;GAAS,GAAG,SAAS,KAAK;EAAE,CAAuB;CACjG;AACF;;AAGA,SAAgB,qBAAqB,MAAwB;CAC3D,OAAO,cAAc,IAAI;AAC3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gesso-framework",
3
- "version": "0.6.30",
3
+ "version": "0.8.0",
4
4
  "description": "SwiftUI and Compose, for the web: declarative components, stores and a frame runtime that keeps the whole interface off the main thread.",
5
5
  "license": "MIT",
6
6
  "author": "Kevin Baker",
@@ -69,7 +69,7 @@
69
69
  "LICENSE"
70
70
  ],
71
71
  "dependencies": {
72
- "gesso-core": "^0.6.30"
72
+ "gesso-core": "^0.8.0"
73
73
  },
74
74
  "peerDependencies": {
75
75
  "rxjs": "^7.8.2"