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.
- package/CHANGELOG.md +98 -0
- package/dist/{FunctionComponent-CHtojpp7.d.ts → FunctionComponent-BDVrANP0.d.ts} +371 -115
- package/dist/{serveChannels-BlEag7WU.js → WorkerPorts-m_hLRoPj.js} +8 -240
- package/dist/WorkerPorts-m_hLRoPj.js.map +1 -0
- package/dist/agent/index.d.ts +55 -2
- package/dist/agent/index.js +8 -4
- package/dist/agent/index.js.map +1 -1
- package/dist/app-De3z58x7.js +638 -0
- package/dist/app-De3z58x7.js.map +1 -0
- package/dist/computed-DyW9SPpq.js +587 -0
- package/dist/computed-DyW9SPpq.js.map +1 -0
- package/dist/createComponent-DJads88q.js +47 -0
- package/dist/createComponent-DJads88q.js.map +1 -0
- package/dist/{index-DZbSb8IN.d.ts → index-DocVauCS.d.ts} +593 -119
- package/dist/index.d.ts +4 -5
- package/dist/index.js +609 -6641
- package/dist/index.js.map +1 -1
- package/dist/jsx/jsx-runtime.d.ts +8 -5
- package/dist/jsx/jsx-runtime.js +15 -6
- package/dist/jsx/jsx-runtime.js.map +1 -1
- package/dist/{persisted-fUzIU8FZ.js → persisted-qEBOHDnj.js} +4 -576
- package/dist/persisted-qEBOHDnj.js.map +1 -0
- package/dist/remote/index.d.ts +2 -2
- package/dist/remote/index.js +2 -1
- package/dist/remote/index.js.map +1 -1
- package/dist/{remote-BoB8zh7z.js → remote-B4bjdTzg.js} +19 -4
- package/dist/remote-B4bjdTzg.js.map +1 -0
- package/dist/render-W2XO444K.js +7310 -0
- package/dist/render-W2XO444K.js.map +1 -0
- package/dist/serveChannels-DReCmRxO.js +241 -0
- package/dist/serveChannels-DReCmRxO.js.map +1 -0
- package/dist/{createComponent-3n1ZSql8.js → show-D0MevHaV.js} +2 -46
- package/dist/show-D0MevHaV.js.map +1 -0
- package/dist/{ui-CHtNccR8.d.ts → tools-Bl8LZ7LR.d.ts} +351 -8
- package/dist/{webmcp-CdDq2nzS.js → webmcp-D7IlA0fu.js} +3 -3
- package/dist/webmcp-D7IlA0fu.js.map +1 -0
- package/dist/worker/index.d.ts +3 -4
- package/dist/worker/index.js +5 -3
- package/dist/worker/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/ChannelProtocol-ByNoHujM.d.ts +0 -247
- package/dist/app-Yig59Ygf.js +0 -326
- package/dist/app-Yig59Ygf.js.map +0 -1
- package/dist/createComponent-3n1ZSql8.js.map +0 -1
- package/dist/persisted-fUzIU8FZ.js.map +0 -1
- package/dist/remote-BoB8zh7z.js.map +0 -1
- package/dist/serveChannels-BlEag7WU.js.map +0 -1
- package/dist/webmcp-CdDq2nzS.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
556
|
-
|
|
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-
|
|
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
|
|
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-
|
|
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"}
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
/**
|
package/dist/worker/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as
|
|
3
|
-
import { c as
|
|
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. */
|
package/dist/worker/index.js.map
CHANGED
|
@@ -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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gesso-framework",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
72
|
+
"gesso-core": "^0.8.0"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"rxjs": "^7.8.2"
|