@agimon-ai/doompi-web-contracts 0.0.1-alpha.2 → 0.0.1-alpha.20

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 (38) hide show
  1. package/README.md +36 -17
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +5 -3
  4. package/dist/index.d.mts +5 -3
  5. package/dist/index.mjs +1 -1
  6. package/dist/services/globalStore.cjs +2 -0
  7. package/dist/services/globalStore.cjs.map +1 -0
  8. package/dist/services/globalStore.d.cts +7 -0
  9. package/dist/services/globalStore.d.cts.map +1 -0
  10. package/dist/services/globalStore.d.mts +7 -0
  11. package/dist/services/globalStore.d.mts.map +1 -0
  12. package/dist/services/globalStore.mjs +2 -0
  13. package/dist/services/globalStore.mjs.map +1 -0
  14. package/dist/services/testing/channels.cjs +1 -1
  15. package/dist/services/testing/channels.cjs.map +1 -1
  16. package/dist/services/testing/channels.mjs +1 -1
  17. package/dist/services/testing/channels.mjs.map +1 -1
  18. package/dist/services/testing/slotProps.cjs +1 -1
  19. package/dist/services/testing/slotProps.cjs.map +1 -1
  20. package/dist/services/testing/slotProps.d.cts +15 -4
  21. package/dist/services/testing/slotProps.d.cts.map +1 -1
  22. package/dist/services/testing/slotProps.d.mts +15 -4
  23. package/dist/services/testing/slotProps.d.mts.map +1 -1
  24. package/dist/services/testing/slotProps.mjs +1 -1
  25. package/dist/services/testing/slotProps.mjs.map +1 -1
  26. package/dist/types/modelContext.d.cts +44 -0
  27. package/dist/types/modelContext.d.cts.map +1 -0
  28. package/dist/types/modelContext.d.mts +44 -0
  29. package/dist/types/modelContext.d.mts.map +1 -0
  30. package/dist/types/webHub.d.cts +42 -1
  31. package/dist/types/webHub.d.cts.map +1 -1
  32. package/dist/types/webHub.d.mts +42 -1
  33. package/dist/types/webHub.d.mts.map +1 -1
  34. package/dist/types/webPlugin.d.cts +395 -16
  35. package/dist/types/webPlugin.d.cts.map +1 -1
  36. package/dist/types/webPlugin.d.mts +395 -16
  37. package/dist/types/webPlugin.d.mts.map +1 -1
  38. package/package.json +4 -4
package/README.md CHANGED
@@ -1,27 +1,46 @@
1
1
  # @agimon-ai/doompi-web-contracts
2
2
 
3
- Web cockpit plugin contracts for DoomPi.
3
+ Typed plugin, slot, session-channel, and hub-channel contracts for the DoomPi web cockpit.
4
4
 
5
- Part of the [DoomPi distribution](https://www.npmjs.com/package/@agimon-ai/doompi).
5
+ This is a library, not a Pi extension. It starts no process and does not belong in `.doom/modes.yaml`.
6
6
 
7
- A DoomPi web plugin has two halves that share these types:
7
+ > **Alpha:** web plugin contracts may change between releases.
8
8
 
9
- - A client entry exporting `webPlugin: WebPluginDefinition`, compiled into the cockpit bundle by
10
- the host package's build. It can contribute tabs with panels and badges, session data channels,
11
- overlays, Leader Space key bindings, command palette commands, rail, selection bar, and
12
- activity dock sections, and tool renderers: one `message` component per claimed tool (by name,
13
- or by a `matches` predicate for tools named at runtime) that owns the tool call's whole timeline
14
- item, composed from the shared components package's `MessageItem` so it looks like every other
15
- item; `toolResultText` and `toolResultTextLines` read a result's text blocks the same way.
16
- - An optional hub entry exporting `webHubChannels: readonly WebHubChannel[]`, loaded by the cockpit
17
- server at startup. A channel watches some data source and answers per-session payloads that reach
18
- the page as `ChannelFrame` messages whose frame type is the channel name.
9
+ ## Install
19
10
 
20
- Plugin packages declare both entries in a `doompiWeb` block in their package.json; the cockpit's
21
- build scans the workspace and generates its registration modules from those blocks.
11
+ ```bash
12
+ npm install @agimon-ai/doompi-web-contracts
13
+ ```
22
14
 
23
- Use `defineWebPlugin` and `defineSessionChannel` for checked literals. Server code should import
24
- these contracts type-only.
15
+ Web plugin clients also require the package's React, React DOM, and TanStack Store peers when they use
16
+ those surfaces.
17
+
18
+ ## Plugin shape
19
+
20
+ A web plugin can have two entries:
21
+
22
+ - A client entry exports `webPlugin: WebPluginDefinition`. It can contribute tabs, slots, Leader
23
+ bindings, commands, session channels, activity surfaces, and tool renderers.
24
+ - An optional hub entry exports `webHubChannels: readonly WebHubChannel[]`. Each channel watches a
25
+ data source and emits per-session `ChannelFrame` payloads to the page.
26
+
27
+ Declare both entries in the plugin package's `doompiWeb` manifest block. The DoomPi cockpit bundler
28
+ reads those declarations and generates the registration modules.
29
+
30
+ Use the definition helpers for checked literals:
31
+
32
+ ```ts
33
+ import {
34
+ defineSessionChannel,
35
+ defineSessionStore,
36
+ defineSlot,
37
+ defineWebPlugin,
38
+ toolResultText,
39
+ } from '@agimon-ai/doompi-web-contracts';
40
+ ```
41
+
42
+ Import contracts type-only from server code. The `/testing` subpath provides channel, render, slot,
43
+ and tool-message fixtures for plugin tests.
25
44
 
26
45
  ## License
27
46
 
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/define.cjs"),t=require("./services/sessionStore.cjs"),n=require("./services/toolResult.cjs");exports.defineSessionChannel=e.defineSessionChannel,exports.defineSessionStore=t.defineSessionStore,exports.defineSlot=e.defineSlot,exports.defineWebPlugin=e.defineWebPlugin,exports.toolResultText=n.toolResultText,exports.toolResultTextLines=n.toolResultTextLines;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/define.cjs"),t=require("./services/globalStore.cjs"),n=require("./services/sessionStore.cjs"),r=require("./services/toolResult.cjs");exports.defineGlobalStore=t.defineGlobalStore,exports.defineSessionChannel=e.defineSessionChannel,exports.defineSessionStore=n.defineSessionStore,exports.defineSlot=e.defineSlot,exports.defineWebPlugin=e.defineWebPlugin,exports.toolResultText=r.toolResultText,exports.toolResultTextLines=r.toolResultTextLines;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,8 @@
1
- import { ActivityGroupContribution, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ToolMessageRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps } from "./types/webPlugin.cjs";
1
+ import { ModelContext, ModelContextAbortSignal, ModelContextBinding, ModelContextExecutionOptions, ModelContextRegistrationOptions, ModelContextTool, ModelContextToolChangeEvent, ModelContextToolChangeListener, ModelContextToolDescriptor } from "./types/modelContext.cjs";
2
+ import { ActivityGroupContribution, ComposerCapture, ComposerSubmission, ContextAction, ContextActionContribution, ContextActionRunContext, DockFaceContribution, FileLinkSource, GlobalStore, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, RepositorySettingsPanelContribution, RepositorySettingsPanelProps, RepositorySettingsRepository, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SettingsFieldContribution, SettingsFieldOption, SettingsPanelContribution, SettingsPanelProps, SettingsSectionContribution, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ThreadViewOptions, ToolMessageRenderProps, ToolPromptContribution, ToolPromptDialog, ToolPromptRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginContextInventoryItem, WebPluginContextItem, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps } from "./types/webPlugin.cjs";
2
3
  import { defineSessionChannel, defineSlot, defineWebPlugin } from "./services/define.cjs";
4
+ import { defineGlobalStore } from "./services/globalStore.cjs";
3
5
  import { defineSessionStore } from "./services/sessionStore.cjs";
4
6
  import { toolResultText, toolResultTextLines } from "./services/toolResult.cjs";
5
- import { ChannelFrame, HubChannelHost, HubChannelSource, HubSessionScope, WebHubChannel } from "./types/webHub.cjs";
6
- export { type ActivityGroupContribution, type ChannelFrame, type HubChannelHost, type HubChannelSource, type HubSessionScope, type LeaderBindingContribution, type LeaderKeyContribution, type MinorModeContribution, type PaletteCommandContext, type PaletteCommandContribution, type SelectionAxisContribution, type SessionChannelContribution, type SessionFrameSender, type SessionRecords, type SessionStore, type SessionStoreChannel, type SlotDataFill, type SlotDeclaration, type SlotFillContribution, type SurfaceContribution, type TabContribution, type ToolMessageRenderProps, type ToolRendererContribution, type ToolResultView, type TransientTab, type WebHubChannel, type WebPluginDefinition, type WebPluginRuntime, type WebPluginSlotProps, defineSessionChannel, defineSessionStore, defineSlot, defineWebPlugin, toolResultText, toolResultTextLines };
7
+ import { ChannelFrame, ComputerUseHostBinding, ComputerUseHostOperation, ComputerUseHostRequest, HubChannelConnection, HubChannelHost, HubChannelLifecycle, HubChannelSource, HubSessionApiRequest, HubSessionScope, WebHubChannel } from "./types/webHub.cjs";
8
+ export { type ActivityGroupContribution, type ChannelFrame, type ComposerCapture, type ComposerSubmission, type ComputerUseHostBinding, type ComputerUseHostOperation, type ComputerUseHostRequest, type ContextAction, type ContextActionContribution, type ContextActionRunContext, type DockFaceContribution, type FileLinkSource, type GlobalStore, type HubChannelConnection, type HubChannelHost, type HubChannelLifecycle, type HubChannelSource, type HubSessionApiRequest, type HubSessionScope, type LeaderBindingContribution, type LeaderKeyContribution, type MinorModeContribution, type ModelContext, type ModelContextAbortSignal, type ModelContextBinding, type ModelContextExecutionOptions, type ModelContextRegistrationOptions, type ModelContextTool, type ModelContextToolChangeEvent, type ModelContextToolChangeListener, type ModelContextToolDescriptor, type PaletteCommandContext, type PaletteCommandContribution, type RepositorySettingsPanelContribution, type RepositorySettingsPanelProps, type RepositorySettingsRepository, type SelectionAxisContribution, type SessionChannelContribution, type SessionFrameSender, type SessionRecords, type SessionStore, type SessionStoreChannel, type SettingsFieldContribution, type SettingsFieldOption, type SettingsPanelContribution, type SettingsPanelProps, type SettingsSectionContribution, type SlotDataFill, type SlotDeclaration, type SlotFillContribution, type SurfaceContribution, type TabContribution, type ThreadViewOptions, type ToolMessageRenderProps, type ToolPromptContribution, type ToolPromptDialog, type ToolPromptRenderProps, type ToolRendererContribution, type ToolResultView, type TransientTab, type WebHubChannel, type WebPluginContextInventoryItem, type WebPluginContextItem, type WebPluginDefinition, type WebPluginRuntime, type WebPluginSlotProps, defineGlobalStore, defineSessionChannel, defineSessionStore, defineSlot, defineWebPlugin, toolResultText, toolResultTextLines };
package/dist/index.d.mts CHANGED
@@ -1,6 +1,8 @@
1
- import { ActivityGroupContribution, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ToolMessageRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps } from "./types/webPlugin.mjs";
1
+ import { ModelContext, ModelContextAbortSignal, ModelContextBinding, ModelContextExecutionOptions, ModelContextRegistrationOptions, ModelContextTool, ModelContextToolChangeEvent, ModelContextToolChangeListener, ModelContextToolDescriptor } from "./types/modelContext.mjs";
2
+ import { ActivityGroupContribution, ComposerCapture, ComposerSubmission, ContextAction, ContextActionContribution, ContextActionRunContext, DockFaceContribution, FileLinkSource, GlobalStore, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, RepositorySettingsPanelContribution, RepositorySettingsPanelProps, RepositorySettingsRepository, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SettingsFieldContribution, SettingsFieldOption, SettingsPanelContribution, SettingsPanelProps, SettingsSectionContribution, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ThreadViewOptions, ToolMessageRenderProps, ToolPromptContribution, ToolPromptDialog, ToolPromptRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginContextInventoryItem, WebPluginContextItem, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps } from "./types/webPlugin.mjs";
2
3
  import { defineSessionChannel, defineSlot, defineWebPlugin } from "./services/define.mjs";
4
+ import { defineGlobalStore } from "./services/globalStore.mjs";
3
5
  import { defineSessionStore } from "./services/sessionStore.mjs";
4
6
  import { toolResultText, toolResultTextLines } from "./services/toolResult.mjs";
5
- import { ChannelFrame, HubChannelHost, HubChannelSource, HubSessionScope, WebHubChannel } from "./types/webHub.mjs";
6
- export { type ActivityGroupContribution, type ChannelFrame, type HubChannelHost, type HubChannelSource, type HubSessionScope, type LeaderBindingContribution, type LeaderKeyContribution, type MinorModeContribution, type PaletteCommandContext, type PaletteCommandContribution, type SelectionAxisContribution, type SessionChannelContribution, type SessionFrameSender, type SessionRecords, type SessionStore, type SessionStoreChannel, type SlotDataFill, type SlotDeclaration, type SlotFillContribution, type SurfaceContribution, type TabContribution, type ToolMessageRenderProps, type ToolRendererContribution, type ToolResultView, type TransientTab, type WebHubChannel, type WebPluginDefinition, type WebPluginRuntime, type WebPluginSlotProps, defineSessionChannel, defineSessionStore, defineSlot, defineWebPlugin, toolResultText, toolResultTextLines };
7
+ import { ChannelFrame, ComputerUseHostBinding, ComputerUseHostOperation, ComputerUseHostRequest, HubChannelConnection, HubChannelHost, HubChannelLifecycle, HubChannelSource, HubSessionApiRequest, HubSessionScope, WebHubChannel } from "./types/webHub.mjs";
8
+ export { type ActivityGroupContribution, type ChannelFrame, type ComposerCapture, type ComposerSubmission, type ComputerUseHostBinding, type ComputerUseHostOperation, type ComputerUseHostRequest, type ContextAction, type ContextActionContribution, type ContextActionRunContext, type DockFaceContribution, type FileLinkSource, type GlobalStore, type HubChannelConnection, type HubChannelHost, type HubChannelLifecycle, type HubChannelSource, type HubSessionApiRequest, type HubSessionScope, type LeaderBindingContribution, type LeaderKeyContribution, type MinorModeContribution, type ModelContext, type ModelContextAbortSignal, type ModelContextBinding, type ModelContextExecutionOptions, type ModelContextRegistrationOptions, type ModelContextTool, type ModelContextToolChangeEvent, type ModelContextToolChangeListener, type ModelContextToolDescriptor, type PaletteCommandContext, type PaletteCommandContribution, type RepositorySettingsPanelContribution, type RepositorySettingsPanelProps, type RepositorySettingsRepository, type SelectionAxisContribution, type SessionChannelContribution, type SessionFrameSender, type SessionRecords, type SessionStore, type SessionStoreChannel, type SettingsFieldContribution, type SettingsFieldOption, type SettingsPanelContribution, type SettingsPanelProps, type SettingsSectionContribution, type SlotDataFill, type SlotDeclaration, type SlotFillContribution, type SurfaceContribution, type TabContribution, type ThreadViewOptions, type ToolMessageRenderProps, type ToolPromptContribution, type ToolPromptDialog, type ToolPromptRenderProps, type ToolRendererContribution, type ToolResultView, type TransientTab, type WebHubChannel, type WebPluginContextInventoryItem, type WebPluginContextItem, type WebPluginDefinition, type WebPluginRuntime, type WebPluginSlotProps, defineGlobalStore, defineSessionChannel, defineSessionStore, defineSlot, defineWebPlugin, toolResultText, toolResultTextLines };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{defineSessionChannel as e,defineSlot as t,defineWebPlugin as n}from"./services/define.mjs";import{defineSessionStore as r}from"./services/sessionStore.mjs";import{toolResultText as i,toolResultTextLines as a}from"./services/toolResult.mjs";export{e as defineSessionChannel,r as defineSessionStore,t as defineSlot,n as defineWebPlugin,i as toolResultText,a as toolResultTextLines};
1
+ import{defineSessionChannel as e,defineSlot as t,defineWebPlugin as n}from"./services/define.mjs";import{defineGlobalStore as r}from"./services/globalStore.mjs";import{defineSessionStore as i}from"./services/sessionStore.mjs";import{toolResultText as a,toolResultTextLines as o}from"./services/toolResult.mjs";export{r as defineGlobalStore,e as defineSessionChannel,i as defineSessionStore,t as defineSlot,n as defineWebPlugin,a as toolResultText,o as toolResultTextLines};
@@ -0,0 +1,2 @@
1
+ let e=require("@tanstack/store");function t(t){let n=new e.Store(t);return{store:n,update:e=>{let t=n.state,r=e(t);r!==t&&n.setState(()=>r)},reset:()=>{n.setState(e=>e===t?e:t)}}}exports.defineGlobalStore=t;
2
+ //# sourceMappingURL=globalStore.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalStore.cjs","names":["Store"],"sources":["../../src/services/globalStore.ts"],"sourcesContent":["import { Store } from '@tanstack/store';\nimport type { GlobalStore } from '../types/webPlugin.ts';\n\n/** Page-wide plugin state shared by every contribution from the plugin. */\nexport function defineGlobalStore<T>(initial: T): GlobalStore<T> {\n const store = new Store<T>(initial);\n\n const update = (updater: (current: T) => T): void => {\n const current = store.state;\n const next = updater(current);\n if (next !== current) store.setState(() => next);\n };\n\n const reset = (): void => {\n store.setState((current) => (current === initial ? current : initial));\n };\n\n return { store, update, reset };\n}\n"],"mappings":"iCAIA,SAAgB,EAAqB,EAA4B,CAC/D,IAAM,EAAQ,IAAIA,EAAAA,MAAS,CAAO,EAYlC,MAAO,CAAE,QAAO,OAVA,GAAqC,CACnD,IAAM,EAAU,EAAM,MAChB,EAAO,EAAQ,CAAO,EACxB,IAAS,GAAS,EAAM,aAAe,CAAI,CACjD,EAMwB,UAJE,CACxB,EAAM,SAAU,GAAa,IAAY,EAAU,EAAU,CAAQ,CACvE,CAE8B,CAChC"}
@@ -0,0 +1,7 @@
1
+ import { GlobalStore } from "../types/webPlugin.cjs";
2
+ //#region src/services/globalStore.d.ts
3
+ /** Page-wide plugin state shared by every contribution from the plugin. */
4
+ declare function defineGlobalStore<T>(initial: T): GlobalStore<T>;
5
+ //#endregion
6
+ export { defineGlobalStore };
7
+ //# sourceMappingURL=globalStore.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalStore.d.cts","names":[],"sources":["../../src/services/globalStore.ts"],"mappings":";;;iBAIgB,kBAAkB,GAAG,SAAS,IAAI,YAAY"}
@@ -0,0 +1,7 @@
1
+ import { GlobalStore } from "../types/webPlugin.mjs";
2
+ //#region src/services/globalStore.d.ts
3
+ /** Page-wide plugin state shared by every contribution from the plugin. */
4
+ declare function defineGlobalStore<T>(initial: T): GlobalStore<T>;
5
+ //#endregion
6
+ export { defineGlobalStore };
7
+ //# sourceMappingURL=globalStore.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalStore.d.mts","names":[],"sources":["../../src/services/globalStore.ts"],"mappings":";;;iBAIgB,kBAAkB,GAAG,SAAS,IAAI,YAAY"}
@@ -0,0 +1,2 @@
1
+ import{Store as e}from"@tanstack/store";function t(t){let n=new e(t);return{store:n,update:e=>{let t=n.state,r=e(t);r!==t&&n.setState(()=>r)},reset:()=>{n.setState(e=>e===t?e:t)}}}export{t as defineGlobalStore};
2
+ //# sourceMappingURL=globalStore.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalStore.mjs","names":[],"sources":["../../src/services/globalStore.ts"],"sourcesContent":["import { Store } from '@tanstack/store';\nimport type { GlobalStore } from '../types/webPlugin.ts';\n\n/** Page-wide plugin state shared by every contribution from the plugin. */\nexport function defineGlobalStore<T>(initial: T): GlobalStore<T> {\n const store = new Store<T>(initial);\n\n const update = (updater: (current: T) => T): void => {\n const current = store.state;\n const next = updater(current);\n if (next !== current) store.setState(() => next);\n };\n\n const reset = (): void => {\n store.setState((current) => (current === initial ? current : initial));\n };\n\n return { store, update, reset };\n}\n"],"mappings":"wCAIA,SAAgB,EAAqB,EAA4B,CAC/D,IAAM,EAAQ,IAAI,EAAS,CAAO,EAYlC,MAAO,CAAE,QAAO,OAVA,GAAqC,CACnD,IAAM,EAAU,EAAM,MAChB,EAAO,EAAQ,CAAO,EACxB,IAAS,GAAS,EAAM,aAAe,CAAI,CACjD,EAMwB,UAJE,CACxB,EAAM,SAAU,GAAa,IAAY,EAAU,EAAU,CAAQ,CACvE,CAE8B,CAChC"}
@@ -1,2 +1,2 @@
1
- function e(e,t,n){let r=e.parse(n);return r===null?{accepted:!1}:(e.apply(t,r),{accepted:!0})}const t={sessionId:`s1`,cwd:`/repo`};function n(e,n={}){let r=[...n.sessions??[t]],i=[],a=[],o=e.start({sessions:()=>r,publish:(t,n)=>{i.push({type:e.frameType,sessionId:t,payload:n})},onNotice:e=>a.push(e)});return{source:o,published:i,notices:a,snapshot(e){let n=r.find(t=>t.sessionId===e)??r[0]??t;return o.payloadFor(n)},addSession(e){r.push(e),o.sessionAdded?.(e)},removeSession(e){let t=r.findIndex(t=>t.sessionId===e);t>=0&&r.splice(t,1),o.sessionRemoved?.(e)},close:()=>o.close()}}exports.driveChannel=e,exports.hubChannelHarness=n;
1
+ function e(e,t,n){let r=e.parse(n);return r===null?{accepted:!1}:(e.apply(t,r),{accepted:!0})}const t={sessionId:`s1`,cwd:`/repo`};function n(e,n={}){let r=[...n.sessions??[t]],i=[],a=[],o=e.start({sessions:()=>r,publish:(t,n)=>{i.push({type:e.frameType,sessionId:t,payload:n})},requestSessionApi:()=>Promise.resolve(Response.json({error:`No session API in the channel harness.`},{status:501})),onNotice:e=>a.push(e)});return{source:o,published:i,notices:a,snapshot(e){let n=r.find(t=>t.sessionId===e)??r[0]??t;return o.payloadFor(n)},addSession(e){r.push(e),o.sessionAdded?.(e)},removeSession(e){let t=r.findIndex(t=>t.sessionId===e);t>=0&&r.splice(t,1),o.sessionRemoved?.(e)},close:()=>o.close()}}exports.driveChannel=e,exports.hubChannelHarness=n;
2
2
  //# sourceMappingURL=channels.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"channels.cjs","names":[],"sources":["../../../src/services/testing/channels.ts"],"sourcesContent":["import type {\n ChannelFrame,\n HubChannelHost,\n HubChannelSource,\n HubSessionScope,\n WebHubChannel,\n} from '../../types/webHub.ts';\nimport type { SessionChannelContribution } from '../../types/webPlugin.ts';\n\n/**\n * The two halves of a plugin's data channel, driven the way the hub drives them.\n *\n * A channel is the one place a plugin parses input it did not produce, so its\n * parse gate is the boundary that matters, and a test that calls `apply`\n * directly steps straight over it. `driveChannel` sends a wire payload the way\n * the page does: through `parse` first, and nothing applied when it is\n * rejected.\n */\n\nexport interface ChannelDelivery {\n /** Whether the parse gate accepted the payload. */\n accepted: boolean;\n}\n\n/**\n * Sends one wire payload to a session channel.\n *\n * Returns whether the gate accepted it, so a test can assert a rejection\n * without knowing what the plugin's parsed shape looks like.\n */\nexport function driveChannel(\n channel: SessionChannelContribution,\n sessionId: string,\n payload: unknown,\n): ChannelDelivery {\n const parsed: unknown = channel.parse(payload);\n if (parsed === null) return { accepted: false };\n channel.apply(sessionId, parsed);\n return { accepted: true };\n}\n\nexport interface HubChannelHarness {\n /** The running source, as the hub holds it. */\n source: HubChannelSource;\n /** Everything the source pushed, in order. */\n readonly published: readonly ChannelFrame[];\n /** What the source told the hub. */\n readonly notices: readonly string[];\n /** The subscribe-time snapshot one session would receive; undefined means no frame. */\n snapshot(sessionId?: string): unknown;\n /** Registers a session with the source, as the hub does when one appears. */\n addSession(scope: HubSessionScope): void;\n removeSession(sessionId: string): void;\n close(): void;\n}\n\nexport interface HubChannelHarnessOptions {\n /** Sessions the hub already manages when the channel starts. */\n sessions?: readonly HubSessionScope[];\n}\n\nconst DEFAULT_SESSION: HubSessionScope = { sessionId: 's1', cwd: '/repo' };\n\n/**\n * A plugin's hub channel, started against a real host.\n *\n * The hub calls `payloadFor` once per subscriber and pushes through `publish`\n * afterwards, and a channel that only answers one of those looks correct in a\n * test that exercises the other.\n */\nexport function hubChannelHarness(channel: WebHubChannel, options: HubChannelHarnessOptions = {}): HubChannelHarness {\n const sessions = [...(options.sessions ?? [DEFAULT_SESSION])];\n const published: ChannelFrame[] = [];\n const notices: string[] = [];\n const host: HubChannelHost = {\n sessions: () => sessions,\n publish: (sessionId, payload) => {\n published.push({ type: channel.frameType, sessionId, payload });\n },\n onNotice: (message) => notices.push(message),\n };\n const source = channel.start(host);\n\n return {\n source,\n published,\n notices,\n snapshot(sessionId) {\n const scope = sessions.find((candidate) => candidate.sessionId === sessionId) ?? sessions[0] ?? DEFAULT_SESSION;\n return source.payloadFor(scope);\n },\n addSession(scope) {\n sessions.push(scope);\n source.sessionAdded?.(scope);\n },\n removeSession(sessionId) {\n const index = sessions.findIndex((candidate) => candidate.sessionId === sessionId);\n if (index >= 0) sessions.splice(index, 1);\n source.sessionRemoved?.(sessionId);\n },\n close: () => source.close(),\n };\n}\n"],"mappings":"AA8BA,SAAgB,EACd,EACA,EACA,EACiB,CACjB,IAAM,EAAkB,EAAQ,MAAM,CAAO,EAG7C,OAFI,IAAW,KAAa,CAAE,SAAU,EAAM,GAC9C,EAAQ,MAAM,EAAW,CAAM,EACxB,CAAE,SAAU,EAAK,EAC1B,CAsBA,MAAM,EAAmC,CAAE,UAAW,KAAM,IAAK,OAAQ,EASzE,SAAgB,EAAkB,EAAwB,EAAoC,CAAC,EAAsB,CACnH,IAAM,EAAW,CAAC,GAAI,EAAQ,UAAY,CAAC,CAAe,CAAE,EACtD,EAA4B,CAAC,EAC7B,EAAoB,CAAC,EAQrB,EAAS,EAAQ,MAAM,CAN3B,aAAgB,EAChB,SAAU,EAAW,IAAY,CAC/B,EAAU,KAAK,CAAE,KAAM,EAAQ,UAAW,YAAW,SAAQ,CAAC,CAChE,EACA,SAAW,GAAY,EAAQ,KAAK,CAAO,CAEb,CAAC,EAEjC,MAAO,CACL,SACA,YACA,UACA,SAAS,EAAW,CAClB,IAAM,EAAQ,EAAS,KAAM,GAAc,EAAU,YAAc,CAAS,GAAK,EAAS,IAAM,EAChG,OAAO,EAAO,WAAW,CAAK,CAChC,EACA,WAAW,EAAO,CAChB,EAAS,KAAK,CAAK,EACnB,EAAO,eAAe,CAAK,CAC7B,EACA,cAAc,EAAW,CACvB,IAAM,EAAQ,EAAS,UAAW,GAAc,EAAU,YAAc,CAAS,EAC7E,GAAS,GAAG,EAAS,OAAO,EAAO,CAAC,EACxC,EAAO,iBAAiB,CAAS,CACnC,EACA,UAAa,EAAO,MAAM,CAC5B,CACF"}
1
+ {"version":3,"file":"channels.cjs","names":[],"sources":["../../../src/services/testing/channels.ts"],"sourcesContent":["import type {\n ChannelFrame,\n HubChannelHost,\n HubChannelSource,\n HubSessionScope,\n WebHubChannel,\n} from '../../types/webHub.ts';\nimport type { SessionChannelContribution } from '../../types/webPlugin.ts';\n\n/**\n * The two halves of a plugin's data channel, driven the way the hub drives them.\n *\n * A channel is the one place a plugin parses input it did not produce, so its\n * parse gate is the boundary that matters, and a test that calls `apply`\n * directly steps straight over it. `driveChannel` sends a wire payload the way\n * the page does: through `parse` first, and nothing applied when it is\n * rejected.\n */\n\nexport interface ChannelDelivery {\n /** Whether the parse gate accepted the payload. */\n accepted: boolean;\n}\n\n/**\n * Sends one wire payload to a session channel.\n *\n * Returns whether the gate accepted it, so a test can assert a rejection\n * without knowing what the plugin's parsed shape looks like.\n */\nexport function driveChannel(\n channel: SessionChannelContribution,\n sessionId: string,\n payload: unknown,\n): ChannelDelivery {\n const parsed: unknown = channel.parse(payload);\n if (parsed === null) return { accepted: false };\n channel.apply(sessionId, parsed);\n return { accepted: true };\n}\n\nexport interface HubChannelHarness {\n /** The running source, as the hub holds it. */\n source: HubChannelSource;\n /** Everything the source pushed, in order. */\n readonly published: readonly ChannelFrame[];\n /** What the source told the hub. */\n readonly notices: readonly string[];\n /** The subscribe-time snapshot one session would receive; undefined means no frame. */\n snapshot(sessionId?: string): unknown;\n /** Registers a session with the source, as the hub does when one appears. */\n addSession(scope: HubSessionScope): void;\n removeSession(sessionId: string): void;\n close(): void;\n}\n\nexport interface HubChannelHarnessOptions {\n /** Sessions the hub already manages when the channel starts. */\n sessions?: readonly HubSessionScope[];\n}\n\nconst DEFAULT_SESSION: HubSessionScope = { sessionId: 's1', cwd: '/repo' };\n\n/**\n * A plugin's hub channel, started against a real host.\n *\n * The hub calls `payloadFor` once per subscriber and pushes through `publish`\n * afterwards, and a channel that only answers one of those looks correct in a\n * test that exercises the other.\n */\nexport function hubChannelHarness(channel: WebHubChannel, options: HubChannelHarnessOptions = {}): HubChannelHarness {\n const sessions = [...(options.sessions ?? [DEFAULT_SESSION])];\n const published: ChannelFrame[] = [];\n const notices: string[] = [];\n const host: HubChannelHost = {\n sessions: () => sessions,\n publish: (sessionId, payload) => {\n published.push({ type: channel.frameType, sessionId, payload });\n },\n requestSessionApi: () =>\n Promise.resolve(Response.json({ error: 'No session API in the channel harness.' }, { status: 501 })),\n onNotice: (message) => notices.push(message),\n };\n const source = channel.start(host);\n\n return {\n source,\n published,\n notices,\n snapshot(sessionId) {\n const scope = sessions.find((candidate) => candidate.sessionId === sessionId) ?? sessions[0] ?? DEFAULT_SESSION;\n return source.payloadFor(scope);\n },\n addSession(scope) {\n sessions.push(scope);\n source.sessionAdded?.(scope);\n },\n removeSession(sessionId) {\n const index = sessions.findIndex((candidate) => candidate.sessionId === sessionId);\n if (index >= 0) sessions.splice(index, 1);\n source.sessionRemoved?.(sessionId);\n },\n close: () => source.close(),\n };\n}\n"],"mappings":"AA8BA,SAAgB,EACd,EACA,EACA,EACiB,CACjB,IAAM,EAAkB,EAAQ,MAAM,CAAO,EAG7C,OAFI,IAAW,KAAa,CAAE,SAAU,EAAM,GAC9C,EAAQ,MAAM,EAAW,CAAM,EACxB,CAAE,SAAU,EAAK,EAC1B,CAsBA,MAAM,EAAmC,CAAE,UAAW,KAAM,IAAK,OAAQ,EASzE,SAAgB,EAAkB,EAAwB,EAAoC,CAAC,EAAsB,CACnH,IAAM,EAAW,CAAC,GAAI,EAAQ,UAAY,CAAC,CAAe,CAAE,EACtD,EAA4B,CAAC,EAC7B,EAAoB,CAAC,EAUrB,EAAS,EAAQ,MAAM,CAR3B,aAAgB,EAChB,SAAU,EAAW,IAAY,CAC/B,EAAU,KAAK,CAAE,KAAM,EAAQ,UAAW,YAAW,SAAQ,CAAC,CAChE,EACA,sBACE,QAAQ,QAAQ,SAAS,KAAK,CAAE,MAAO,wCAAyC,EAAG,CAAE,OAAQ,GAAI,CAAC,CAAC,EACrG,SAAW,GAAY,EAAQ,KAAK,CAAO,CAEb,CAAC,EAEjC,MAAO,CACL,SACA,YACA,UACA,SAAS,EAAW,CAClB,IAAM,EAAQ,EAAS,KAAM,GAAc,EAAU,YAAc,CAAS,GAAK,EAAS,IAAM,EAChG,OAAO,EAAO,WAAW,CAAK,CAChC,EACA,WAAW,EAAO,CAChB,EAAS,KAAK,CAAK,EACnB,EAAO,eAAe,CAAK,CAC7B,EACA,cAAc,EAAW,CACvB,IAAM,EAAQ,EAAS,UAAW,GAAc,EAAU,YAAc,CAAS,EAC7E,GAAS,GAAG,EAAS,OAAO,EAAO,CAAC,EACxC,EAAO,iBAAiB,CAAS,CACnC,EACA,UAAa,EAAO,MAAM,CAC5B,CACF"}
@@ -1,2 +1,2 @@
1
- function e(e,t,n){let r=e.parse(n);return r===null?{accepted:!1}:(e.apply(t,r),{accepted:!0})}const t={sessionId:`s1`,cwd:`/repo`};function n(e,n={}){let r=[...n.sessions??[t]],i=[],a=[],o=e.start({sessions:()=>r,publish:(t,n)=>{i.push({type:e.frameType,sessionId:t,payload:n})},onNotice:e=>a.push(e)});return{source:o,published:i,notices:a,snapshot(e){let n=r.find(t=>t.sessionId===e)??r[0]??t;return o.payloadFor(n)},addSession(e){r.push(e),o.sessionAdded?.(e)},removeSession(e){let t=r.findIndex(t=>t.sessionId===e);t>=0&&r.splice(t,1),o.sessionRemoved?.(e)},close:()=>o.close()}}export{e as driveChannel,n as hubChannelHarness};
1
+ function e(e,t,n){let r=e.parse(n);return r===null?{accepted:!1}:(e.apply(t,r),{accepted:!0})}const t={sessionId:`s1`,cwd:`/repo`};function n(e,n={}){let r=[...n.sessions??[t]],i=[],a=[],o=e.start({sessions:()=>r,publish:(t,n)=>{i.push({type:e.frameType,sessionId:t,payload:n})},requestSessionApi:()=>Promise.resolve(Response.json({error:`No session API in the channel harness.`},{status:501})),onNotice:e=>a.push(e)});return{source:o,published:i,notices:a,snapshot(e){let n=r.find(t=>t.sessionId===e)??r[0]??t;return o.payloadFor(n)},addSession(e){r.push(e),o.sessionAdded?.(e)},removeSession(e){let t=r.findIndex(t=>t.sessionId===e);t>=0&&r.splice(t,1),o.sessionRemoved?.(e)},close:()=>o.close()}}export{e as driveChannel,n as hubChannelHarness};
2
2
  //# sourceMappingURL=channels.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"channels.mjs","names":[],"sources":["../../../src/services/testing/channels.ts"],"sourcesContent":["import type {\n ChannelFrame,\n HubChannelHost,\n HubChannelSource,\n HubSessionScope,\n WebHubChannel,\n} from '../../types/webHub.ts';\nimport type { SessionChannelContribution } from '../../types/webPlugin.ts';\n\n/**\n * The two halves of a plugin's data channel, driven the way the hub drives them.\n *\n * A channel is the one place a plugin parses input it did not produce, so its\n * parse gate is the boundary that matters, and a test that calls `apply`\n * directly steps straight over it. `driveChannel` sends a wire payload the way\n * the page does: through `parse` first, and nothing applied when it is\n * rejected.\n */\n\nexport interface ChannelDelivery {\n /** Whether the parse gate accepted the payload. */\n accepted: boolean;\n}\n\n/**\n * Sends one wire payload to a session channel.\n *\n * Returns whether the gate accepted it, so a test can assert a rejection\n * without knowing what the plugin's parsed shape looks like.\n */\nexport function driveChannel(\n channel: SessionChannelContribution,\n sessionId: string,\n payload: unknown,\n): ChannelDelivery {\n const parsed: unknown = channel.parse(payload);\n if (parsed === null) return { accepted: false };\n channel.apply(sessionId, parsed);\n return { accepted: true };\n}\n\nexport interface HubChannelHarness {\n /** The running source, as the hub holds it. */\n source: HubChannelSource;\n /** Everything the source pushed, in order. */\n readonly published: readonly ChannelFrame[];\n /** What the source told the hub. */\n readonly notices: readonly string[];\n /** The subscribe-time snapshot one session would receive; undefined means no frame. */\n snapshot(sessionId?: string): unknown;\n /** Registers a session with the source, as the hub does when one appears. */\n addSession(scope: HubSessionScope): void;\n removeSession(sessionId: string): void;\n close(): void;\n}\n\nexport interface HubChannelHarnessOptions {\n /** Sessions the hub already manages when the channel starts. */\n sessions?: readonly HubSessionScope[];\n}\n\nconst DEFAULT_SESSION: HubSessionScope = { sessionId: 's1', cwd: '/repo' };\n\n/**\n * A plugin's hub channel, started against a real host.\n *\n * The hub calls `payloadFor` once per subscriber and pushes through `publish`\n * afterwards, and a channel that only answers one of those looks correct in a\n * test that exercises the other.\n */\nexport function hubChannelHarness(channel: WebHubChannel, options: HubChannelHarnessOptions = {}): HubChannelHarness {\n const sessions = [...(options.sessions ?? [DEFAULT_SESSION])];\n const published: ChannelFrame[] = [];\n const notices: string[] = [];\n const host: HubChannelHost = {\n sessions: () => sessions,\n publish: (sessionId, payload) => {\n published.push({ type: channel.frameType, sessionId, payload });\n },\n onNotice: (message) => notices.push(message),\n };\n const source = channel.start(host);\n\n return {\n source,\n published,\n notices,\n snapshot(sessionId) {\n const scope = sessions.find((candidate) => candidate.sessionId === sessionId) ?? sessions[0] ?? DEFAULT_SESSION;\n return source.payloadFor(scope);\n },\n addSession(scope) {\n sessions.push(scope);\n source.sessionAdded?.(scope);\n },\n removeSession(sessionId) {\n const index = sessions.findIndex((candidate) => candidate.sessionId === sessionId);\n if (index >= 0) sessions.splice(index, 1);\n source.sessionRemoved?.(sessionId);\n },\n close: () => source.close(),\n };\n}\n"],"mappings":"AA8BA,SAAgB,EACd,EACA,EACA,EACiB,CACjB,IAAM,EAAkB,EAAQ,MAAM,CAAO,EAG7C,OAFI,IAAW,KAAa,CAAE,SAAU,EAAM,GAC9C,EAAQ,MAAM,EAAW,CAAM,EACxB,CAAE,SAAU,EAAK,EAC1B,CAsBA,MAAM,EAAmC,CAAE,UAAW,KAAM,IAAK,OAAQ,EASzE,SAAgB,EAAkB,EAAwB,EAAoC,CAAC,EAAsB,CACnH,IAAM,EAAW,CAAC,GAAI,EAAQ,UAAY,CAAC,CAAe,CAAE,EACtD,EAA4B,CAAC,EAC7B,EAAoB,CAAC,EAQrB,EAAS,EAAQ,MAAM,CAN3B,aAAgB,EAChB,SAAU,EAAW,IAAY,CAC/B,EAAU,KAAK,CAAE,KAAM,EAAQ,UAAW,YAAW,SAAQ,CAAC,CAChE,EACA,SAAW,GAAY,EAAQ,KAAK,CAAO,CAEb,CAAC,EAEjC,MAAO,CACL,SACA,YACA,UACA,SAAS,EAAW,CAClB,IAAM,EAAQ,EAAS,KAAM,GAAc,EAAU,YAAc,CAAS,GAAK,EAAS,IAAM,EAChG,OAAO,EAAO,WAAW,CAAK,CAChC,EACA,WAAW,EAAO,CAChB,EAAS,KAAK,CAAK,EACnB,EAAO,eAAe,CAAK,CAC7B,EACA,cAAc,EAAW,CACvB,IAAM,EAAQ,EAAS,UAAW,GAAc,EAAU,YAAc,CAAS,EAC7E,GAAS,GAAG,EAAS,OAAO,EAAO,CAAC,EACxC,EAAO,iBAAiB,CAAS,CACnC,EACA,UAAa,EAAO,MAAM,CAC5B,CACF"}
1
+ {"version":3,"file":"channels.mjs","names":[],"sources":["../../../src/services/testing/channels.ts"],"sourcesContent":["import type {\n ChannelFrame,\n HubChannelHost,\n HubChannelSource,\n HubSessionScope,\n WebHubChannel,\n} from '../../types/webHub.ts';\nimport type { SessionChannelContribution } from '../../types/webPlugin.ts';\n\n/**\n * The two halves of a plugin's data channel, driven the way the hub drives them.\n *\n * A channel is the one place a plugin parses input it did not produce, so its\n * parse gate is the boundary that matters, and a test that calls `apply`\n * directly steps straight over it. `driveChannel` sends a wire payload the way\n * the page does: through `parse` first, and nothing applied when it is\n * rejected.\n */\n\nexport interface ChannelDelivery {\n /** Whether the parse gate accepted the payload. */\n accepted: boolean;\n}\n\n/**\n * Sends one wire payload to a session channel.\n *\n * Returns whether the gate accepted it, so a test can assert a rejection\n * without knowing what the plugin's parsed shape looks like.\n */\nexport function driveChannel(\n channel: SessionChannelContribution,\n sessionId: string,\n payload: unknown,\n): ChannelDelivery {\n const parsed: unknown = channel.parse(payload);\n if (parsed === null) return { accepted: false };\n channel.apply(sessionId, parsed);\n return { accepted: true };\n}\n\nexport interface HubChannelHarness {\n /** The running source, as the hub holds it. */\n source: HubChannelSource;\n /** Everything the source pushed, in order. */\n readonly published: readonly ChannelFrame[];\n /** What the source told the hub. */\n readonly notices: readonly string[];\n /** The subscribe-time snapshot one session would receive; undefined means no frame. */\n snapshot(sessionId?: string): unknown;\n /** Registers a session with the source, as the hub does when one appears. */\n addSession(scope: HubSessionScope): void;\n removeSession(sessionId: string): void;\n close(): void;\n}\n\nexport interface HubChannelHarnessOptions {\n /** Sessions the hub already manages when the channel starts. */\n sessions?: readonly HubSessionScope[];\n}\n\nconst DEFAULT_SESSION: HubSessionScope = { sessionId: 's1', cwd: '/repo' };\n\n/**\n * A plugin's hub channel, started against a real host.\n *\n * The hub calls `payloadFor` once per subscriber and pushes through `publish`\n * afterwards, and a channel that only answers one of those looks correct in a\n * test that exercises the other.\n */\nexport function hubChannelHarness(channel: WebHubChannel, options: HubChannelHarnessOptions = {}): HubChannelHarness {\n const sessions = [...(options.sessions ?? [DEFAULT_SESSION])];\n const published: ChannelFrame[] = [];\n const notices: string[] = [];\n const host: HubChannelHost = {\n sessions: () => sessions,\n publish: (sessionId, payload) => {\n published.push({ type: channel.frameType, sessionId, payload });\n },\n requestSessionApi: () =>\n Promise.resolve(Response.json({ error: 'No session API in the channel harness.' }, { status: 501 })),\n onNotice: (message) => notices.push(message),\n };\n const source = channel.start(host);\n\n return {\n source,\n published,\n notices,\n snapshot(sessionId) {\n const scope = sessions.find((candidate) => candidate.sessionId === sessionId) ?? sessions[0] ?? DEFAULT_SESSION;\n return source.payloadFor(scope);\n },\n addSession(scope) {\n sessions.push(scope);\n source.sessionAdded?.(scope);\n },\n removeSession(sessionId) {\n const index = sessions.findIndex((candidate) => candidate.sessionId === sessionId);\n if (index >= 0) sessions.splice(index, 1);\n source.sessionRemoved?.(sessionId);\n },\n close: () => source.close(),\n };\n}\n"],"mappings":"AA8BA,SAAgB,EACd,EACA,EACA,EACiB,CACjB,IAAM,EAAkB,EAAQ,MAAM,CAAO,EAG7C,OAFI,IAAW,KAAa,CAAE,SAAU,EAAM,GAC9C,EAAQ,MAAM,EAAW,CAAM,EACxB,CAAE,SAAU,EAAK,EAC1B,CAsBA,MAAM,EAAmC,CAAE,UAAW,KAAM,IAAK,OAAQ,EASzE,SAAgB,EAAkB,EAAwB,EAAoC,CAAC,EAAsB,CACnH,IAAM,EAAW,CAAC,GAAI,EAAQ,UAAY,CAAC,CAAe,CAAE,EACtD,EAA4B,CAAC,EAC7B,EAAoB,CAAC,EAUrB,EAAS,EAAQ,MAAM,CAR3B,aAAgB,EAChB,SAAU,EAAW,IAAY,CAC/B,EAAU,KAAK,CAAE,KAAM,EAAQ,UAAW,YAAW,SAAQ,CAAC,CAChE,EACA,sBACE,QAAQ,QAAQ,SAAS,KAAK,CAAE,MAAO,wCAAyC,EAAG,CAAE,OAAQ,GAAI,CAAC,CAAC,EACrG,SAAW,GAAY,EAAQ,KAAK,CAAO,CAEb,CAAC,EAEjC,MAAO,CACL,SACA,YACA,UACA,SAAS,EAAW,CAClB,IAAM,EAAQ,EAAS,KAAM,GAAc,EAAU,YAAc,CAAS,GAAK,EAAS,IAAM,EAChG,OAAO,EAAO,WAAW,CAAK,CAChC,EACA,WAAW,EAAO,CAChB,EAAS,KAAK,CAAK,EACnB,EAAO,eAAe,CAAK,CAC7B,EACA,cAAc,EAAW,CACvB,IAAM,EAAQ,EAAS,UAAW,GAAc,EAAU,YAAc,CAAS,EAC7E,GAAS,GAAG,EAAS,OAAO,EAAO,CAAC,EACxC,EAAO,iBAAiB,CAAS,CACnC,EACA,UAAa,EAAO,MAAM,CAC5B,CACF"}
@@ -1,2 +1,2 @@
1
- function e(e={}){let t=[];return{props:{sessionId:e.sessionId===void 0?`s1`:e.sessionId,statuses:e.statuses??{},openTab:e=>{t.push({action:`openTab`,target:e})},openTransientTab:e=>{t.push({action:`openTransientTab`,target:e.id})},closeTransientTab:e=>{t.push({action:`closeTransientTab`,target:e})},sendSessionFrame:(e,n)=>{t.push({action:`sendSessionFrame`,target:e,frame:n})},renderThread:t=>e.thread?.(t)??null,renderSlot:t=>e.slotContent?.[t]??null,slotData:t=>e.slotData?.[t.slot]??[]},actions:t,frames:()=>t.flatMap(e=>e.frame?[e.frame]:[])}}function t(t){let n=e(t),r=t.result===void 0?null:t.result;return{...n,props:{...n.props,toolCallId:t.toolCallId??`call-1`,toolName:t.toolName,args:t.args??{},result:r,output:t.output??``,running:t.running??!1,isError:t.isError??!1}}}exports.slotPropsFixture=e,exports.toolMessagePropsFixture=t;
1
+ function e(e={}){let t=[],n={sessionId:e.sessionId===void 0?`s1`:e.sessionId,statuses:e.statuses??{},contextInventory:e.contextInventory??[],openTab:e=>{t.push({action:`openTab`,target:e})},openTransientTab:e=>{t.push({action:`openTransientTab`,target:e.id})},closeTransientTab:e=>{t.push({action:`closeTransientTab`,target:e})},fileTabFor:t=>e.fileTab?.(t),appendComposerDraft:e=>{t.push({action:`appendComposerDraft`,target:n.sessionId,text:e})},attachComposerContext:e=>{t.push({action:`attachComposerContext`,target:n.sessionId,context:e})},attachComposerCapture:e=>{t.push({action:`attachComposerCapture`,target:n.sessionId,capture:e})},contextActionsFor:t=>e.contextActions?.(t)??[],sendSessionFrame:(e,n)=>{t.push({action:`sendSessionFrame`,target:e,frame:n})},renderThread:(t,n)=>e.thread?.(t,n)??null,renderSlot:t=>e.slotContent?.[t]??null,slotData:t=>e.slotData?.[t.slot]??[]};return{props:n,actions:t,frames:()=>t.flatMap(e=>e.frame?[e.frame]:[])}}function t(t){let n=e(t),r=t.result===void 0?null:t.result;return{...n,props:{...n.props,toolCallId:t.toolCallId??`call-1`,toolName:t.toolName,args:t.args??{},result:r,output:t.output??``,running:t.running??!1,isError:t.isError??!1}}}exports.slotPropsFixture=e,exports.toolMessagePropsFixture=t;
2
2
  //# sourceMappingURL=slotProps.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"slotProps.cjs","names":[],"sources":["../../../src/services/testing/slotProps.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n SlotDataFill,\n SlotDeclaration,\n ToolMessageRenderProps,\n ToolResultView,\n TransientTab,\n WebPluginSlotProps,\n} from '../../types/webPlugin.ts';\n\n/**\n * The props the cockpit hands a plugin's components, built for a test.\n *\n * A plugin component takes fifteen props and reads three of them. Written out\n * by hand in each test, the other twelve are noise that goes stale the moment\n * the contract grows a member; written as a cast, the component compiles\n * against props the host never sends. This builds all of them, records what the\n * component did with the actions, and lets a test override the two or three it\n * cares about.\n */\n\nexport interface RecordedSlotAction {\n action: 'openTab' | 'openTransientTab' | 'closeTransientTab' | 'sendSessionFrame';\n /** The tab id, the transient tab's id, or the target session of a frame. */\n target: string | null;\n /** The frame a component sent, for `sendSessionFrame`. */\n frame?: Record<string, unknown>;\n}\n\nexport interface SlotPropsFixture {\n props: WebPluginSlotProps;\n /** Everything the component did through its props, in order. */\n readonly actions: readonly RecordedSlotAction[];\n /** Frames the component sent, which is what most assertions want. */\n frames(): readonly Record<string, unknown>[];\n}\n\nexport interface SlotPropsOptions {\n sessionId?: string | null;\n statuses?: Readonly<Record<string, string>>;\n /** Component fills this slot owner should see; keyed by slot name. */\n slotContent?: Readonly<Record<string, ReactNode>>;\n /** Data fills this slot owner should read back, keyed by slot name. */\n slotData?: Readonly<Record<string, readonly SlotDataFill[]>>;\n /** What `renderThread` returns for a plugin that renders one. */\n thread?: (threadId: string) => ReactNode;\n}\n\nconst DEFAULT_SESSION_ID = 's1';\n\nexport function slotPropsFixture(options: SlotPropsOptions = {}): SlotPropsFixture {\n const actions: RecordedSlotAction[] = [];\n const props: WebPluginSlotProps = {\n sessionId: options.sessionId === undefined ? DEFAULT_SESSION_ID : options.sessionId,\n statuses: options.statuses ?? {},\n openTab: (tabId) => {\n actions.push({ action: 'openTab', target: tabId });\n },\n openTransientTab: (tab: TransientTab) => {\n actions.push({ action: 'openTransientTab', target: tab.id });\n },\n closeTransientTab: (tabId: string) => {\n actions.push({ action: 'closeTransientTab', target: tabId });\n },\n sendSessionFrame: (sessionId, frame) => {\n actions.push({ action: 'sendSessionFrame', target: sessionId, frame });\n },\n renderThread: (threadId) => options.thread?.(threadId) ?? null,\n renderSlot: (slot) => options.slotContent?.[slot] ?? null,\n // The host resolves fills by slot name and hands the owner its own typed\n // view, so a test declares them by name too.\n slotData: <Data>(slot: SlotDeclaration<Data>) =>\n (options.slotData?.[slot.slot] ?? []) as readonly SlotDataFill<Data>[],\n };\n\n return {\n props,\n actions,\n frames: () => actions.flatMap((entry) => (entry.frame ? [entry.frame] : [])),\n };\n}\n\nexport interface ToolMessagePropsOptions extends SlotPropsOptions {\n toolCallId?: string;\n toolName: string;\n args?: Record<string, unknown>;\n /** The newest result; null is what the host sends before any output. */\n result?: ToolResultView | null;\n /** The result's text blocks joined, as the host's own fallback item shows them. */\n output?: string;\n running?: boolean;\n isError?: boolean;\n}\n\nexport interface ToolMessagePropsFixture extends Omit<SlotPropsFixture, 'props'> {\n props: ToolMessageRenderProps;\n}\n\nconst DEFAULT_TOOL_CALL_ID = 'call-1';\n\n/** The props a tool's timeline item receives, in any of its four states. */\nexport function toolMessagePropsFixture(options: ToolMessagePropsOptions): ToolMessagePropsFixture {\n const base = slotPropsFixture(options);\n const result = options.result === undefined ? null : options.result;\n return {\n ...base,\n props: {\n ...base.props,\n toolCallId: options.toolCallId ?? DEFAULT_TOOL_CALL_ID,\n toolName: options.toolName,\n args: options.args ?? {},\n result,\n output: options.output ?? '',\n running: options.running ?? false,\n isError: options.isError ?? false,\n },\n };\n}\n"],"mappings":"AAkDA,SAAgB,EAAiB,EAA4B,CAAC,EAAqB,CACjF,IAAM,EAAgC,CAAC,EAwBvC,MAAO,CACL,MAAA,CAvBA,UAAW,EAAQ,YAAc,IAAA,GAAY,KAAqB,EAAQ,UAC1E,SAAU,EAAQ,UAAY,CAAC,EAC/B,QAAU,GAAU,CAClB,EAAQ,KAAK,CAAE,OAAQ,UAAW,OAAQ,CAAM,CAAC,CACnD,EACA,iBAAmB,GAAsB,CACvC,EAAQ,KAAK,CAAE,OAAQ,mBAAoB,OAAQ,EAAI,EAAG,CAAC,CAC7D,EACA,kBAAoB,GAAkB,CACpC,EAAQ,KAAK,CAAE,OAAQ,oBAAqB,OAAQ,CAAM,CAAC,CAC7D,EACA,kBAAmB,EAAW,IAAU,CACtC,EAAQ,KAAK,CAAE,OAAQ,mBAAoB,OAAQ,EAAW,OAAM,CAAC,CACvE,EACA,aAAe,GAAa,EAAQ,SAAS,CAAQ,GAAK,KAC1D,WAAa,GAAS,EAAQ,cAAc,IAAS,KAGrD,SAAiB,GACd,EAAQ,WAAW,EAAK,OAAS,CAAC,CAIjC,EACJ,UACA,WAAc,EAAQ,QAAS,GAAW,EAAM,MAAQ,CAAC,EAAM,KAAK,EAAI,CAAC,CAAE,CAC7E,CACF,CAqBA,SAAgB,EAAwB,EAA2D,CACjG,IAAM,EAAO,EAAiB,CAAO,EAC/B,EAAS,EAAQ,SAAW,IAAA,GAAY,KAAO,EAAQ,OAC7D,MAAO,CACL,GAAG,EACH,MAAO,CACL,GAAG,EAAK,MACR,WAAY,EAAQ,YAAc,SAClC,SAAU,EAAQ,SAClB,KAAM,EAAQ,MAAQ,CAAC,EACvB,SACA,OAAQ,EAAQ,QAAU,GAC1B,QAAS,EAAQ,SAAW,GAC5B,QAAS,EAAQ,SAAW,EAC9B,CACF,CACF"}
1
+ {"version":3,"file":"slotProps.cjs","names":[],"sources":["../../../src/services/testing/slotProps.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n ComposerCapture,\n ContextAction,\n SlotDataFill,\n SlotDeclaration,\n ThreadViewOptions,\n ToolMessageRenderProps,\n ToolResultView,\n TransientTab,\n WebPluginContextItem,\n WebPluginSlotProps,\n} from '../../types/webPlugin.ts';\n\n/**\n * The props the cockpit hands a plugin's components, built for a test.\n *\n * A plugin component takes fifteen props and reads three of them. Written out\n * by hand in each test, the other twelve are noise that goes stale the moment\n * the contract grows a member; written as a cast, the component compiles\n * against props the host never sends. This builds all of them, records what the\n * component did with the actions, and lets a test override the two or three it\n * cares about.\n */\n\nexport interface RecordedSlotAction {\n action:\n | 'appendComposerDraft'\n | 'attachComposerCapture'\n | 'attachComposerContext'\n | 'openTab'\n | 'openTransientTab'\n | 'closeTransientTab'\n | 'sendSessionFrame';\n /** The tab id, the transient tab's id, or the target session of a frame or composer action. */\n target: string | null;\n /** The text passed to `appendComposerDraft`. */\n text?: string;\n /** The structured item passed to `attachComposerContext`. */\n context?: WebPluginContextItem;\n /** The atomic screenshot and context passed to `attachComposerCapture`. */\n capture?: ComposerCapture;\n /** The frame a component sent, for `sendSessionFrame`. */\n frame?: Record<string, unknown>;\n}\n\nexport interface SlotPropsFixture {\n props: WebPluginSlotProps;\n /** Everything the component did through its props, in order. */\n readonly actions: readonly RecordedSlotAction[];\n /** Frames the component sent, which is what most assertions want. */\n frames(): readonly Record<string, unknown>[];\n}\n\nexport interface SlotPropsOptions {\n sessionId?: string | null;\n statuses?: Readonly<Record<string, string>>;\n contextInventory?: WebPluginSlotProps['contextInventory'];\n /** Component fills this slot owner should see; keyed by slot name. */\n slotContent?: Readonly<Record<string, ReactNode>>;\n /** Data fills this slot owner should read back, keyed by slot name. */\n slotData?: Readonly<Record<string, readonly SlotDataFill[]>>;\n /** What `renderThread` returns for a plugin that renders one. */\n thread?: (threadId: string, options?: ThreadViewOptions) => ReactNode;\n /** The tab a path opens; by default no path opens one, as with no file plugin installed. */\n fileTab?: (path: string) => TransientTab | undefined;\n /** Context actions installed plugins should offer for the item under test. */\n contextActions?: (item: WebPluginContextItem) => readonly ContextAction[];\n}\n\nconst DEFAULT_SESSION_ID = 's1';\n\nexport function slotPropsFixture(options: SlotPropsOptions = {}): SlotPropsFixture {\n const actions: RecordedSlotAction[] = [];\n const props: WebPluginSlotProps = {\n sessionId: options.sessionId === undefined ? DEFAULT_SESSION_ID : options.sessionId,\n statuses: options.statuses ?? {},\n contextInventory: options.contextInventory ?? [],\n openTab: (tabId) => {\n actions.push({ action: 'openTab', target: tabId });\n },\n openTransientTab: (tab: TransientTab) => {\n actions.push({ action: 'openTransientTab', target: tab.id });\n },\n closeTransientTab: (tabId: string) => {\n actions.push({ action: 'closeTransientTab', target: tabId });\n },\n fileTabFor: (path: string) => options.fileTab?.(path),\n appendComposerDraft: (text) => {\n actions.push({ action: 'appendComposerDraft', target: props.sessionId, text });\n },\n attachComposerContext: (context) => {\n actions.push({ action: 'attachComposerContext', target: props.sessionId, context });\n },\n attachComposerCapture: (capture) => {\n actions.push({ action: 'attachComposerCapture', target: props.sessionId, capture });\n },\n contextActionsFor: (item) => options.contextActions?.(item) ?? [],\n sendSessionFrame: (sessionId, frame) => {\n actions.push({ action: 'sendSessionFrame', target: sessionId, frame });\n },\n renderThread: (threadId, threadOptions) => options.thread?.(threadId, threadOptions) ?? null,\n renderSlot: (slot) => options.slotContent?.[slot] ?? null,\n // The host resolves fills by slot name and hands the owner its own typed\n // view, so a test declares them by name too.\n slotData: <Data>(slot: SlotDeclaration<Data>) =>\n (options.slotData?.[slot.slot] ?? []) as readonly SlotDataFill<Data>[],\n };\n\n return {\n props,\n actions,\n frames: () => actions.flatMap((entry) => (entry.frame ? [entry.frame] : [])),\n };\n}\n\nexport interface ToolMessagePropsOptions extends SlotPropsOptions {\n toolCallId?: string;\n toolName: string;\n args?: Record<string, unknown>;\n /** The newest result; null is what the host sends before any output. */\n result?: ToolResultView | null;\n /** The result's text blocks joined, as the host's own fallback item shows them. */\n output?: string;\n running?: boolean;\n isError?: boolean;\n}\n\nexport interface ToolMessagePropsFixture extends Omit<SlotPropsFixture, 'props'> {\n props: ToolMessageRenderProps;\n}\n\nconst DEFAULT_TOOL_CALL_ID = 'call-1';\n\n/** The props a tool's timeline item receives, in any of its four states. */\nexport function toolMessagePropsFixture(options: ToolMessagePropsOptions): ToolMessagePropsFixture {\n const base = slotPropsFixture(options);\n const result = options.result === undefined ? null : options.result;\n return {\n ...base,\n props: {\n ...base.props,\n toolCallId: options.toolCallId ?? DEFAULT_TOOL_CALL_ID,\n toolName: options.toolName,\n args: options.args ?? {},\n result,\n output: options.output ?? '',\n running: options.running ?? false,\n isError: options.isError ?? false,\n },\n };\n}\n"],"mappings":"AAwEA,SAAgB,EAAiB,EAA4B,CAAC,EAAqB,CACjF,IAAM,EAAgC,CAAC,EACjC,EAA4B,CAChC,UAAW,EAAQ,YAAc,IAAA,GAAY,KAAqB,EAAQ,UAC1E,SAAU,EAAQ,UAAY,CAAC,EAC/B,iBAAkB,EAAQ,kBAAoB,CAAC,EAC/C,QAAU,GAAU,CAClB,EAAQ,KAAK,CAAE,OAAQ,UAAW,OAAQ,CAAM,CAAC,CACnD,EACA,iBAAmB,GAAsB,CACvC,EAAQ,KAAK,CAAE,OAAQ,mBAAoB,OAAQ,EAAI,EAAG,CAAC,CAC7D,EACA,kBAAoB,GAAkB,CACpC,EAAQ,KAAK,CAAE,OAAQ,oBAAqB,OAAQ,CAAM,CAAC,CAC7D,EACA,WAAa,GAAiB,EAAQ,UAAU,CAAI,EACpD,oBAAsB,GAAS,CAC7B,EAAQ,KAAK,CAAE,OAAQ,sBAAuB,OAAQ,EAAM,UAAW,MAAK,CAAC,CAC/E,EACA,sBAAwB,GAAY,CAClC,EAAQ,KAAK,CAAE,OAAQ,wBAAyB,OAAQ,EAAM,UAAW,SAAQ,CAAC,CACpF,EACA,sBAAwB,GAAY,CAClC,EAAQ,KAAK,CAAE,OAAQ,wBAAyB,OAAQ,EAAM,UAAW,SAAQ,CAAC,CACpF,EACA,kBAAoB,GAAS,EAAQ,iBAAiB,CAAI,GAAK,CAAC,EAChE,kBAAmB,EAAW,IAAU,CACtC,EAAQ,KAAK,CAAE,OAAQ,mBAAoB,OAAQ,EAAW,OAAM,CAAC,CACvE,EACA,cAAe,EAAU,IAAkB,EAAQ,SAAS,EAAU,CAAa,GAAK,KACxF,WAAa,GAAS,EAAQ,cAAc,IAAS,KAGrD,SAAiB,GACd,EAAQ,WAAW,EAAK,OAAS,CAAC,CACvC,EAEA,MAAO,CACL,QACA,UACA,WAAc,EAAQ,QAAS,GAAW,EAAM,MAAQ,CAAC,EAAM,KAAK,EAAI,CAAC,CAAE,CAC7E,CACF,CAqBA,SAAgB,EAAwB,EAA2D,CACjG,IAAM,EAAO,EAAiB,CAAO,EAC/B,EAAS,EAAQ,SAAW,IAAA,GAAY,KAAO,EAAQ,OAC7D,MAAO,CACL,GAAG,EACH,MAAO,CACL,GAAG,EAAK,MACR,WAAY,EAAQ,YAAc,SAClC,SAAU,EAAQ,SAClB,KAAM,EAAQ,MAAQ,CAAC,EACvB,SACA,OAAQ,EAAQ,QAAU,GAC1B,QAAS,EAAQ,SAAW,GAC5B,QAAS,EAAQ,SAAW,EAC9B,CACF,CACF"}
@@ -1,4 +1,4 @@
1
- import { SlotDataFill, ToolMessageRenderProps, ToolResultView, WebPluginSlotProps } from "../../types/webPlugin.cjs";
1
+ import { ComposerCapture, ContextAction, SlotDataFill, ThreadViewOptions, ToolMessageRenderProps, ToolResultView, TransientTab, WebPluginContextItem, WebPluginSlotProps } from "../../types/webPlugin.cjs";
2
2
  import { ReactNode } from "react";
3
3
  //#region src/services/testing/slotProps.d.ts
4
4
  /**
@@ -12,9 +12,15 @@ import { ReactNode } from "react";
12
12
  * cares about.
13
13
  */
14
14
  interface RecordedSlotAction {
15
- action: 'openTab' | 'openTransientTab' | 'closeTransientTab' | 'sendSessionFrame';
16
- /** The tab id, the transient tab's id, or the target session of a frame. */
15
+ action: 'appendComposerDraft' | 'attachComposerCapture' | 'attachComposerContext' | 'openTab' | 'openTransientTab' | 'closeTransientTab' | 'sendSessionFrame';
16
+ /** The tab id, the transient tab's id, or the target session of a frame or composer action. */
17
17
  target: string | null;
18
+ /** The text passed to `appendComposerDraft`. */
19
+ text?: string;
20
+ /** The structured item passed to `attachComposerContext`. */
21
+ context?: WebPluginContextItem;
22
+ /** The atomic screenshot and context passed to `attachComposerCapture`. */
23
+ capture?: ComposerCapture;
18
24
  /** The frame a component sent, for `sendSessionFrame`. */
19
25
  frame?: Record<string, unknown>;
20
26
  }
@@ -28,12 +34,17 @@ interface SlotPropsFixture {
28
34
  interface SlotPropsOptions {
29
35
  sessionId?: string | null;
30
36
  statuses?: Readonly<Record<string, string>>;
37
+ contextInventory?: WebPluginSlotProps['contextInventory'];
31
38
  /** Component fills this slot owner should see; keyed by slot name. */
32
39
  slotContent?: Readonly<Record<string, ReactNode>>;
33
40
  /** Data fills this slot owner should read back, keyed by slot name. */
34
41
  slotData?: Readonly<Record<string, readonly SlotDataFill[]>>;
35
42
  /** What `renderThread` returns for a plugin that renders one. */
36
- thread?: (threadId: string) => ReactNode;
43
+ thread?: (threadId: string, options?: ThreadViewOptions) => ReactNode;
44
+ /** The tab a path opens; by default no path opens one, as with no file plugin installed. */
45
+ fileTab?: (path: string) => TransientTab | undefined;
46
+ /** Context actions installed plugins should offer for the item under test. */
47
+ contextActions?: (item: WebPluginContextItem) => readonly ContextAction[];
37
48
  }
38
49
  declare function slotPropsFixture(options?: SlotPropsOptions): SlotPropsFixture;
39
50
  interface ToolMessagePropsOptions extends SlotPropsOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"slotProps.d.cts","names":[],"sources":["../../../src/services/testing/slotProps.ts"],"mappings":";;;;;;;;;;;;;UAqBiB;EACf;;EAEA;;EAEA,QAAQ;;UAGO;EACf,OAAO;;WAEE,kBAAkB;;EAE3B,mBAAmB;;UAGJ;EACf;EACA,WAAW,SAAS;;EAEpB,cAAc,SAAS,eAAe;;EAEtC,WAAW,SAAS,wBAAwB;;EAE5C,UAAU,qBAAqB;;iBAKjB,iBAAiB,UAAS,mBAAwB;UAgCjD,gCAAgC;EAC/C;EACA;EACA,OAAO;;EAEP,SAAS;;EAET;EACA;EACA;;UAGe,gCAAgC,KAAK;EACpD,OAAO;;;iBAMO,wBAAwB,SAAS,0BAA0B"}
1
+ {"version":3,"file":"slotProps.d.cts","names":[],"sources":["../../../src/services/testing/slotProps.ts"],"mappings":";;;;;;;;;;;;;UAyBiB;EACf;;EASA;;EAEA;;EAEA,UAAU;;EAEV,UAAU;;EAEV,QAAQ;;UAGO;EACf,OAAO;;WAEE,kBAAkB;;EAE3B,mBAAmB;;UAGJ;EACf;EACA,WAAW,SAAS;EACpB,mBAAmB;;EAEnB,cAAc,SAAS,eAAe;;EAEtC,WAAW,SAAS,wBAAwB;;EAE5C,UAAU,kBAAkB,UAAU,sBAAsB;;EAE5D,WAAW,iBAAiB;;EAE5B,kBAAkB,MAAM,kCAAkC;;iBAK5C,iBAAiB,UAAS,mBAAwB;UA4CjD,gCAAgC;EAC/C;EACA;EACA,OAAO;;EAEP,SAAS;;EAET;EACA;EACA;;UAGe,gCAAgC,KAAK;EACpD,OAAO;;;iBAMO,wBAAwB,SAAS,0BAA0B"}
@@ -1,4 +1,4 @@
1
- import { SlotDataFill, ToolMessageRenderProps, ToolResultView, WebPluginSlotProps } from "../../types/webPlugin.mjs";
1
+ import { ComposerCapture, ContextAction, SlotDataFill, ThreadViewOptions, ToolMessageRenderProps, ToolResultView, TransientTab, WebPluginContextItem, WebPluginSlotProps } from "../../types/webPlugin.mjs";
2
2
  import { ReactNode } from "react";
3
3
  //#region src/services/testing/slotProps.d.ts
4
4
  /**
@@ -12,9 +12,15 @@ import { ReactNode } from "react";
12
12
  * cares about.
13
13
  */
14
14
  interface RecordedSlotAction {
15
- action: 'openTab' | 'openTransientTab' | 'closeTransientTab' | 'sendSessionFrame';
16
- /** The tab id, the transient tab's id, or the target session of a frame. */
15
+ action: 'appendComposerDraft' | 'attachComposerCapture' | 'attachComposerContext' | 'openTab' | 'openTransientTab' | 'closeTransientTab' | 'sendSessionFrame';
16
+ /** The tab id, the transient tab's id, or the target session of a frame or composer action. */
17
17
  target: string | null;
18
+ /** The text passed to `appendComposerDraft`. */
19
+ text?: string;
20
+ /** The structured item passed to `attachComposerContext`. */
21
+ context?: WebPluginContextItem;
22
+ /** The atomic screenshot and context passed to `attachComposerCapture`. */
23
+ capture?: ComposerCapture;
18
24
  /** The frame a component sent, for `sendSessionFrame`. */
19
25
  frame?: Record<string, unknown>;
20
26
  }
@@ -28,12 +34,17 @@ interface SlotPropsFixture {
28
34
  interface SlotPropsOptions {
29
35
  sessionId?: string | null;
30
36
  statuses?: Readonly<Record<string, string>>;
37
+ contextInventory?: WebPluginSlotProps['contextInventory'];
31
38
  /** Component fills this slot owner should see; keyed by slot name. */
32
39
  slotContent?: Readonly<Record<string, ReactNode>>;
33
40
  /** Data fills this slot owner should read back, keyed by slot name. */
34
41
  slotData?: Readonly<Record<string, readonly SlotDataFill[]>>;
35
42
  /** What `renderThread` returns for a plugin that renders one. */
36
- thread?: (threadId: string) => ReactNode;
43
+ thread?: (threadId: string, options?: ThreadViewOptions) => ReactNode;
44
+ /** The tab a path opens; by default no path opens one, as with no file plugin installed. */
45
+ fileTab?: (path: string) => TransientTab | undefined;
46
+ /** Context actions installed plugins should offer for the item under test. */
47
+ contextActions?: (item: WebPluginContextItem) => readonly ContextAction[];
37
48
  }
38
49
  declare function slotPropsFixture(options?: SlotPropsOptions): SlotPropsFixture;
39
50
  interface ToolMessagePropsOptions extends SlotPropsOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"slotProps.d.mts","names":[],"sources":["../../../src/services/testing/slotProps.ts"],"mappings":";;;;;;;;;;;;;UAqBiB;EACf;;EAEA;;EAEA,QAAQ;;UAGO;EACf,OAAO;;WAEE,kBAAkB;;EAE3B,mBAAmB;;UAGJ;EACf;EACA,WAAW,SAAS;;EAEpB,cAAc,SAAS,eAAe;;EAEtC,WAAW,SAAS,wBAAwB;;EAE5C,UAAU,qBAAqB;;iBAKjB,iBAAiB,UAAS,mBAAwB;UAgCjD,gCAAgC;EAC/C;EACA;EACA,OAAO;;EAEP,SAAS;;EAET;EACA;EACA;;UAGe,gCAAgC,KAAK;EACpD,OAAO;;;iBAMO,wBAAwB,SAAS,0BAA0B"}
1
+ {"version":3,"file":"slotProps.d.mts","names":[],"sources":["../../../src/services/testing/slotProps.ts"],"mappings":";;;;;;;;;;;;;UAyBiB;EACf;;EASA;;EAEA;;EAEA,UAAU;;EAEV,UAAU;;EAEV,QAAQ;;UAGO;EACf,OAAO;;WAEE,kBAAkB;;EAE3B,mBAAmB;;UAGJ;EACf;EACA,WAAW,SAAS;EACpB,mBAAmB;;EAEnB,cAAc,SAAS,eAAe;;EAEtC,WAAW,SAAS,wBAAwB;;EAE5C,UAAU,kBAAkB,UAAU,sBAAsB;;EAE5D,WAAW,iBAAiB;;EAE5B,kBAAkB,MAAM,kCAAkC;;iBAK5C,iBAAiB,UAAS,mBAAwB;UA4CjD,gCAAgC;EAC/C;EACA;EACA,OAAO;;EAEP,SAAS;;EAET;EACA;EACA;;UAGe,gCAAgC,KAAK;EACpD,OAAO;;;iBAMO,wBAAwB,SAAS,0BAA0B"}
@@ -1,2 +1,2 @@
1
- function e(e={}){let t=[];return{props:{sessionId:e.sessionId===void 0?`s1`:e.sessionId,statuses:e.statuses??{},openTab:e=>{t.push({action:`openTab`,target:e})},openTransientTab:e=>{t.push({action:`openTransientTab`,target:e.id})},closeTransientTab:e=>{t.push({action:`closeTransientTab`,target:e})},sendSessionFrame:(e,n)=>{t.push({action:`sendSessionFrame`,target:e,frame:n})},renderThread:t=>e.thread?.(t)??null,renderSlot:t=>e.slotContent?.[t]??null,slotData:t=>e.slotData?.[t.slot]??[]},actions:t,frames:()=>t.flatMap(e=>e.frame?[e.frame]:[])}}function t(t){let n=e(t),r=t.result===void 0?null:t.result;return{...n,props:{...n.props,toolCallId:t.toolCallId??`call-1`,toolName:t.toolName,args:t.args??{},result:r,output:t.output??``,running:t.running??!1,isError:t.isError??!1}}}export{e as slotPropsFixture,t as toolMessagePropsFixture};
1
+ function e(e={}){let t=[],n={sessionId:e.sessionId===void 0?`s1`:e.sessionId,statuses:e.statuses??{},contextInventory:e.contextInventory??[],openTab:e=>{t.push({action:`openTab`,target:e})},openTransientTab:e=>{t.push({action:`openTransientTab`,target:e.id})},closeTransientTab:e=>{t.push({action:`closeTransientTab`,target:e})},fileTabFor:t=>e.fileTab?.(t),appendComposerDraft:e=>{t.push({action:`appendComposerDraft`,target:n.sessionId,text:e})},attachComposerContext:e=>{t.push({action:`attachComposerContext`,target:n.sessionId,context:e})},attachComposerCapture:e=>{t.push({action:`attachComposerCapture`,target:n.sessionId,capture:e})},contextActionsFor:t=>e.contextActions?.(t)??[],sendSessionFrame:(e,n)=>{t.push({action:`sendSessionFrame`,target:e,frame:n})},renderThread:(t,n)=>e.thread?.(t,n)??null,renderSlot:t=>e.slotContent?.[t]??null,slotData:t=>e.slotData?.[t.slot]??[]};return{props:n,actions:t,frames:()=>t.flatMap(e=>e.frame?[e.frame]:[])}}function t(t){let n=e(t),r=t.result===void 0?null:t.result;return{...n,props:{...n.props,toolCallId:t.toolCallId??`call-1`,toolName:t.toolName,args:t.args??{},result:r,output:t.output??``,running:t.running??!1,isError:t.isError??!1}}}export{e as slotPropsFixture,t as toolMessagePropsFixture};
2
2
  //# sourceMappingURL=slotProps.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"slotProps.mjs","names":[],"sources":["../../../src/services/testing/slotProps.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n SlotDataFill,\n SlotDeclaration,\n ToolMessageRenderProps,\n ToolResultView,\n TransientTab,\n WebPluginSlotProps,\n} from '../../types/webPlugin.ts';\n\n/**\n * The props the cockpit hands a plugin's components, built for a test.\n *\n * A plugin component takes fifteen props and reads three of them. Written out\n * by hand in each test, the other twelve are noise that goes stale the moment\n * the contract grows a member; written as a cast, the component compiles\n * against props the host never sends. This builds all of them, records what the\n * component did with the actions, and lets a test override the two or three it\n * cares about.\n */\n\nexport interface RecordedSlotAction {\n action: 'openTab' | 'openTransientTab' | 'closeTransientTab' | 'sendSessionFrame';\n /** The tab id, the transient tab's id, or the target session of a frame. */\n target: string | null;\n /** The frame a component sent, for `sendSessionFrame`. */\n frame?: Record<string, unknown>;\n}\n\nexport interface SlotPropsFixture {\n props: WebPluginSlotProps;\n /** Everything the component did through its props, in order. */\n readonly actions: readonly RecordedSlotAction[];\n /** Frames the component sent, which is what most assertions want. */\n frames(): readonly Record<string, unknown>[];\n}\n\nexport interface SlotPropsOptions {\n sessionId?: string | null;\n statuses?: Readonly<Record<string, string>>;\n /** Component fills this slot owner should see; keyed by slot name. */\n slotContent?: Readonly<Record<string, ReactNode>>;\n /** Data fills this slot owner should read back, keyed by slot name. */\n slotData?: Readonly<Record<string, readonly SlotDataFill[]>>;\n /** What `renderThread` returns for a plugin that renders one. */\n thread?: (threadId: string) => ReactNode;\n}\n\nconst DEFAULT_SESSION_ID = 's1';\n\nexport function slotPropsFixture(options: SlotPropsOptions = {}): SlotPropsFixture {\n const actions: RecordedSlotAction[] = [];\n const props: WebPluginSlotProps = {\n sessionId: options.sessionId === undefined ? DEFAULT_SESSION_ID : options.sessionId,\n statuses: options.statuses ?? {},\n openTab: (tabId) => {\n actions.push({ action: 'openTab', target: tabId });\n },\n openTransientTab: (tab: TransientTab) => {\n actions.push({ action: 'openTransientTab', target: tab.id });\n },\n closeTransientTab: (tabId: string) => {\n actions.push({ action: 'closeTransientTab', target: tabId });\n },\n sendSessionFrame: (sessionId, frame) => {\n actions.push({ action: 'sendSessionFrame', target: sessionId, frame });\n },\n renderThread: (threadId) => options.thread?.(threadId) ?? null,\n renderSlot: (slot) => options.slotContent?.[slot] ?? null,\n // The host resolves fills by slot name and hands the owner its own typed\n // view, so a test declares them by name too.\n slotData: <Data>(slot: SlotDeclaration<Data>) =>\n (options.slotData?.[slot.slot] ?? []) as readonly SlotDataFill<Data>[],\n };\n\n return {\n props,\n actions,\n frames: () => actions.flatMap((entry) => (entry.frame ? [entry.frame] : [])),\n };\n}\n\nexport interface ToolMessagePropsOptions extends SlotPropsOptions {\n toolCallId?: string;\n toolName: string;\n args?: Record<string, unknown>;\n /** The newest result; null is what the host sends before any output. */\n result?: ToolResultView | null;\n /** The result's text blocks joined, as the host's own fallback item shows them. */\n output?: string;\n running?: boolean;\n isError?: boolean;\n}\n\nexport interface ToolMessagePropsFixture extends Omit<SlotPropsFixture, 'props'> {\n props: ToolMessageRenderProps;\n}\n\nconst DEFAULT_TOOL_CALL_ID = 'call-1';\n\n/** The props a tool's timeline item receives, in any of its four states. */\nexport function toolMessagePropsFixture(options: ToolMessagePropsOptions): ToolMessagePropsFixture {\n const base = slotPropsFixture(options);\n const result = options.result === undefined ? null : options.result;\n return {\n ...base,\n props: {\n ...base.props,\n toolCallId: options.toolCallId ?? DEFAULT_TOOL_CALL_ID,\n toolName: options.toolName,\n args: options.args ?? {},\n result,\n output: options.output ?? '',\n running: options.running ?? false,\n isError: options.isError ?? false,\n },\n };\n}\n"],"mappings":"AAkDA,SAAgB,EAAiB,EAA4B,CAAC,EAAqB,CACjF,IAAM,EAAgC,CAAC,EAwBvC,MAAO,CACL,MAAA,CAvBA,UAAW,EAAQ,YAAc,IAAA,GAAY,KAAqB,EAAQ,UAC1E,SAAU,EAAQ,UAAY,CAAC,EAC/B,QAAU,GAAU,CAClB,EAAQ,KAAK,CAAE,OAAQ,UAAW,OAAQ,CAAM,CAAC,CACnD,EACA,iBAAmB,GAAsB,CACvC,EAAQ,KAAK,CAAE,OAAQ,mBAAoB,OAAQ,EAAI,EAAG,CAAC,CAC7D,EACA,kBAAoB,GAAkB,CACpC,EAAQ,KAAK,CAAE,OAAQ,oBAAqB,OAAQ,CAAM,CAAC,CAC7D,EACA,kBAAmB,EAAW,IAAU,CACtC,EAAQ,KAAK,CAAE,OAAQ,mBAAoB,OAAQ,EAAW,OAAM,CAAC,CACvE,EACA,aAAe,GAAa,EAAQ,SAAS,CAAQ,GAAK,KAC1D,WAAa,GAAS,EAAQ,cAAc,IAAS,KAGrD,SAAiB,GACd,EAAQ,WAAW,EAAK,OAAS,CAAC,CAIjC,EACJ,UACA,WAAc,EAAQ,QAAS,GAAW,EAAM,MAAQ,CAAC,EAAM,KAAK,EAAI,CAAC,CAAE,CAC7E,CACF,CAqBA,SAAgB,EAAwB,EAA2D,CACjG,IAAM,EAAO,EAAiB,CAAO,EAC/B,EAAS,EAAQ,SAAW,IAAA,GAAY,KAAO,EAAQ,OAC7D,MAAO,CACL,GAAG,EACH,MAAO,CACL,GAAG,EAAK,MACR,WAAY,EAAQ,YAAc,SAClC,SAAU,EAAQ,SAClB,KAAM,EAAQ,MAAQ,CAAC,EACvB,SACA,OAAQ,EAAQ,QAAU,GAC1B,QAAS,EAAQ,SAAW,GAC5B,QAAS,EAAQ,SAAW,EAC9B,CACF,CACF"}
1
+ {"version":3,"file":"slotProps.mjs","names":[],"sources":["../../../src/services/testing/slotProps.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n ComposerCapture,\n ContextAction,\n SlotDataFill,\n SlotDeclaration,\n ThreadViewOptions,\n ToolMessageRenderProps,\n ToolResultView,\n TransientTab,\n WebPluginContextItem,\n WebPluginSlotProps,\n} from '../../types/webPlugin.ts';\n\n/**\n * The props the cockpit hands a plugin's components, built for a test.\n *\n * A plugin component takes fifteen props and reads three of them. Written out\n * by hand in each test, the other twelve are noise that goes stale the moment\n * the contract grows a member; written as a cast, the component compiles\n * against props the host never sends. This builds all of them, records what the\n * component did with the actions, and lets a test override the two or three it\n * cares about.\n */\n\nexport interface RecordedSlotAction {\n action:\n | 'appendComposerDraft'\n | 'attachComposerCapture'\n | 'attachComposerContext'\n | 'openTab'\n | 'openTransientTab'\n | 'closeTransientTab'\n | 'sendSessionFrame';\n /** The tab id, the transient tab's id, or the target session of a frame or composer action. */\n target: string | null;\n /** The text passed to `appendComposerDraft`. */\n text?: string;\n /** The structured item passed to `attachComposerContext`. */\n context?: WebPluginContextItem;\n /** The atomic screenshot and context passed to `attachComposerCapture`. */\n capture?: ComposerCapture;\n /** The frame a component sent, for `sendSessionFrame`. */\n frame?: Record<string, unknown>;\n}\n\nexport interface SlotPropsFixture {\n props: WebPluginSlotProps;\n /** Everything the component did through its props, in order. */\n readonly actions: readonly RecordedSlotAction[];\n /** Frames the component sent, which is what most assertions want. */\n frames(): readonly Record<string, unknown>[];\n}\n\nexport interface SlotPropsOptions {\n sessionId?: string | null;\n statuses?: Readonly<Record<string, string>>;\n contextInventory?: WebPluginSlotProps['contextInventory'];\n /** Component fills this slot owner should see; keyed by slot name. */\n slotContent?: Readonly<Record<string, ReactNode>>;\n /** Data fills this slot owner should read back, keyed by slot name. */\n slotData?: Readonly<Record<string, readonly SlotDataFill[]>>;\n /** What `renderThread` returns for a plugin that renders one. */\n thread?: (threadId: string, options?: ThreadViewOptions) => ReactNode;\n /** The tab a path opens; by default no path opens one, as with no file plugin installed. */\n fileTab?: (path: string) => TransientTab | undefined;\n /** Context actions installed plugins should offer for the item under test. */\n contextActions?: (item: WebPluginContextItem) => readonly ContextAction[];\n}\n\nconst DEFAULT_SESSION_ID = 's1';\n\nexport function slotPropsFixture(options: SlotPropsOptions = {}): SlotPropsFixture {\n const actions: RecordedSlotAction[] = [];\n const props: WebPluginSlotProps = {\n sessionId: options.sessionId === undefined ? DEFAULT_SESSION_ID : options.sessionId,\n statuses: options.statuses ?? {},\n contextInventory: options.contextInventory ?? [],\n openTab: (tabId) => {\n actions.push({ action: 'openTab', target: tabId });\n },\n openTransientTab: (tab: TransientTab) => {\n actions.push({ action: 'openTransientTab', target: tab.id });\n },\n closeTransientTab: (tabId: string) => {\n actions.push({ action: 'closeTransientTab', target: tabId });\n },\n fileTabFor: (path: string) => options.fileTab?.(path),\n appendComposerDraft: (text) => {\n actions.push({ action: 'appendComposerDraft', target: props.sessionId, text });\n },\n attachComposerContext: (context) => {\n actions.push({ action: 'attachComposerContext', target: props.sessionId, context });\n },\n attachComposerCapture: (capture) => {\n actions.push({ action: 'attachComposerCapture', target: props.sessionId, capture });\n },\n contextActionsFor: (item) => options.contextActions?.(item) ?? [],\n sendSessionFrame: (sessionId, frame) => {\n actions.push({ action: 'sendSessionFrame', target: sessionId, frame });\n },\n renderThread: (threadId, threadOptions) => options.thread?.(threadId, threadOptions) ?? null,\n renderSlot: (slot) => options.slotContent?.[slot] ?? null,\n // The host resolves fills by slot name and hands the owner its own typed\n // view, so a test declares them by name too.\n slotData: <Data>(slot: SlotDeclaration<Data>) =>\n (options.slotData?.[slot.slot] ?? []) as readonly SlotDataFill<Data>[],\n };\n\n return {\n props,\n actions,\n frames: () => actions.flatMap((entry) => (entry.frame ? [entry.frame] : [])),\n };\n}\n\nexport interface ToolMessagePropsOptions extends SlotPropsOptions {\n toolCallId?: string;\n toolName: string;\n args?: Record<string, unknown>;\n /** The newest result; null is what the host sends before any output. */\n result?: ToolResultView | null;\n /** The result's text blocks joined, as the host's own fallback item shows them. */\n output?: string;\n running?: boolean;\n isError?: boolean;\n}\n\nexport interface ToolMessagePropsFixture extends Omit<SlotPropsFixture, 'props'> {\n props: ToolMessageRenderProps;\n}\n\nconst DEFAULT_TOOL_CALL_ID = 'call-1';\n\n/** The props a tool's timeline item receives, in any of its four states. */\nexport function toolMessagePropsFixture(options: ToolMessagePropsOptions): ToolMessagePropsFixture {\n const base = slotPropsFixture(options);\n const result = options.result === undefined ? null : options.result;\n return {\n ...base,\n props: {\n ...base.props,\n toolCallId: options.toolCallId ?? DEFAULT_TOOL_CALL_ID,\n toolName: options.toolName,\n args: options.args ?? {},\n result,\n output: options.output ?? '',\n running: options.running ?? false,\n isError: options.isError ?? false,\n },\n };\n}\n"],"mappings":"AAwEA,SAAgB,EAAiB,EAA4B,CAAC,EAAqB,CACjF,IAAM,EAAgC,CAAC,EACjC,EAA4B,CAChC,UAAW,EAAQ,YAAc,IAAA,GAAY,KAAqB,EAAQ,UAC1E,SAAU,EAAQ,UAAY,CAAC,EAC/B,iBAAkB,EAAQ,kBAAoB,CAAC,EAC/C,QAAU,GAAU,CAClB,EAAQ,KAAK,CAAE,OAAQ,UAAW,OAAQ,CAAM,CAAC,CACnD,EACA,iBAAmB,GAAsB,CACvC,EAAQ,KAAK,CAAE,OAAQ,mBAAoB,OAAQ,EAAI,EAAG,CAAC,CAC7D,EACA,kBAAoB,GAAkB,CACpC,EAAQ,KAAK,CAAE,OAAQ,oBAAqB,OAAQ,CAAM,CAAC,CAC7D,EACA,WAAa,GAAiB,EAAQ,UAAU,CAAI,EACpD,oBAAsB,GAAS,CAC7B,EAAQ,KAAK,CAAE,OAAQ,sBAAuB,OAAQ,EAAM,UAAW,MAAK,CAAC,CAC/E,EACA,sBAAwB,GAAY,CAClC,EAAQ,KAAK,CAAE,OAAQ,wBAAyB,OAAQ,EAAM,UAAW,SAAQ,CAAC,CACpF,EACA,sBAAwB,GAAY,CAClC,EAAQ,KAAK,CAAE,OAAQ,wBAAyB,OAAQ,EAAM,UAAW,SAAQ,CAAC,CACpF,EACA,kBAAoB,GAAS,EAAQ,iBAAiB,CAAI,GAAK,CAAC,EAChE,kBAAmB,EAAW,IAAU,CACtC,EAAQ,KAAK,CAAE,OAAQ,mBAAoB,OAAQ,EAAW,OAAM,CAAC,CACvE,EACA,cAAe,EAAU,IAAkB,EAAQ,SAAS,EAAU,CAAa,GAAK,KACxF,WAAa,GAAS,EAAQ,cAAc,IAAS,KAGrD,SAAiB,GACd,EAAQ,WAAW,EAAK,OAAS,CAAC,CACvC,EAEA,MAAO,CACL,QACA,UACA,WAAc,EAAQ,QAAS,GAAW,EAAM,MAAQ,CAAC,EAAM,KAAK,EAAI,CAAC,CAAE,CAC7E,CACF,CAqBA,SAAgB,EAAwB,EAA2D,CACjG,IAAM,EAAO,EAAiB,CAAO,EAC/B,EAAS,EAAQ,SAAW,IAAA,GAAY,KAAO,EAAQ,OAC7D,MAAO,CACL,GAAG,EACH,MAAO,CACL,GAAG,EAAK,MACR,WAAY,EAAQ,YAAc,SAClC,SAAU,EAAQ,SAClB,KAAM,EAAQ,MAAQ,CAAC,EACvB,SACA,OAAQ,EAAQ,QAAU,GAC1B,QAAS,EAAQ,SAAW,GAC5B,QAAS,EAAQ,SAAW,EAC9B,CACF,CACF"}
@@ -0,0 +1,44 @@
1
+ //#region src/types/modelContext.d.ts
2
+ /** The model-visible part of a WebMCP tool registration. */
3
+ interface ModelContextToolDescriptor {
4
+ readonly name: string;
5
+ readonly description: string;
6
+ readonly inputSchema: Readonly<Record<string, unknown>>;
7
+ }
8
+ interface ModelContextAbortSignal {
9
+ readonly aborted: boolean;
10
+ readonly reason?: unknown;
11
+ addEventListener(type: 'abort', listener: () => void, options?: {
12
+ readonly once?: boolean;
13
+ }): void;
14
+ removeEventListener(type: 'abort', listener: () => void): void;
15
+ }
16
+ interface ModelContextExecutionOptions {
17
+ readonly signal: ModelContextAbortSignal;
18
+ }
19
+ interface ModelContextRegistrationOptions {
20
+ readonly signal: ModelContextAbortSignal;
21
+ }
22
+ /** A descriptor plus the page callback that implements it. */
23
+ interface ModelContextTool extends ModelContextToolDescriptor {
24
+ execute(input: string, options: ModelContextExecutionOptions): Promise<unknown>;
25
+ }
26
+ interface ModelContextToolChangeEvent {
27
+ readonly type: 'toolchange';
28
+ }
29
+ type ModelContextToolChangeListener = (event: ModelContextToolChangeEvent) => void;
30
+ /** The common surface supplied by either the browser or DoomPi's simulator. */
31
+ interface ModelContext {
32
+ registerTool(tool: ModelContextTool, options: ModelContextRegistrationOptions): void | Promise<void>;
33
+ getTools(): Promise<readonly ModelContextToolDescriptor[]>;
34
+ executeTool(name: string, input: string, options: ModelContextExecutionOptions): Promise<unknown>;
35
+ addEventListener(type: 'toolchange', listener: ModelContextToolChangeListener): void;
36
+ removeEventListener(type: 'toolchange', listener: ModelContextToolChangeListener): void;
37
+ }
38
+ interface ModelContextBinding {
39
+ readonly kind: 'native' | 'simulator';
40
+ readonly modelContext: ModelContext;
41
+ }
42
+ //#endregion
43
+ export { ModelContext, ModelContextAbortSignal, ModelContextBinding, ModelContextExecutionOptions, ModelContextRegistrationOptions, ModelContextTool, ModelContextToolChangeEvent, ModelContextToolChangeListener, ModelContextToolDescriptor };
44
+ //# sourceMappingURL=modelContext.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelContext.d.cts","names":[],"sources":["../../src/types/modelContext.ts"],"mappings":";;UACiB;WACN;WACA;WACA,aAAa,SAAS;;UAGhB;WACN;WACA;EACT,iBAAiB,eAAe,sBAAsB;aAAqB;;EAC3E,oBAAoB,eAAe;;UAGpB;WACN,QAAQ;;UAGF;WACN,QAAQ;;;UAIF,yBAAyB;EACxC,QAAQ,eAAe,SAAS,+BAA+B;;UAGhD;WACN;;KAGC,kCAAkC,OAAO;;UAGpC;EACf,aAAa,MAAM,kBAAkB,SAAS,yCAAyC;EACvF,YAAY,iBAAiB;EAC7B,YAAY,cAAc,eAAe,SAAS,+BAA+B;EACjF,iBAAiB,oBAAoB,UAAU;EAC/C,oBAAoB,oBAAoB,UAAU;;UAGnC;WACN;WACA,cAAc"}
@@ -0,0 +1,44 @@
1
+ //#region src/types/modelContext.d.ts
2
+ /** The model-visible part of a WebMCP tool registration. */
3
+ interface ModelContextToolDescriptor {
4
+ readonly name: string;
5
+ readonly description: string;
6
+ readonly inputSchema: Readonly<Record<string, unknown>>;
7
+ }
8
+ interface ModelContextAbortSignal {
9
+ readonly aborted: boolean;
10
+ readonly reason?: unknown;
11
+ addEventListener(type: 'abort', listener: () => void, options?: {
12
+ readonly once?: boolean;
13
+ }): void;
14
+ removeEventListener(type: 'abort', listener: () => void): void;
15
+ }
16
+ interface ModelContextExecutionOptions {
17
+ readonly signal: ModelContextAbortSignal;
18
+ }
19
+ interface ModelContextRegistrationOptions {
20
+ readonly signal: ModelContextAbortSignal;
21
+ }
22
+ /** A descriptor plus the page callback that implements it. */
23
+ interface ModelContextTool extends ModelContextToolDescriptor {
24
+ execute(input: string, options: ModelContextExecutionOptions): Promise<unknown>;
25
+ }
26
+ interface ModelContextToolChangeEvent {
27
+ readonly type: 'toolchange';
28
+ }
29
+ type ModelContextToolChangeListener = (event: ModelContextToolChangeEvent) => void;
30
+ /** The common surface supplied by either the browser or DoomPi's simulator. */
31
+ interface ModelContext {
32
+ registerTool(tool: ModelContextTool, options: ModelContextRegistrationOptions): void | Promise<void>;
33
+ getTools(): Promise<readonly ModelContextToolDescriptor[]>;
34
+ executeTool(name: string, input: string, options: ModelContextExecutionOptions): Promise<unknown>;
35
+ addEventListener(type: 'toolchange', listener: ModelContextToolChangeListener): void;
36
+ removeEventListener(type: 'toolchange', listener: ModelContextToolChangeListener): void;
37
+ }
38
+ interface ModelContextBinding {
39
+ readonly kind: 'native' | 'simulator';
40
+ readonly modelContext: ModelContext;
41
+ }
42
+ //#endregion
43
+ export { ModelContext, ModelContextAbortSignal, ModelContextBinding, ModelContextExecutionOptions, ModelContextRegistrationOptions, ModelContextTool, ModelContextToolChangeEvent, ModelContextToolChangeListener, ModelContextToolDescriptor };
44
+ //# sourceMappingURL=modelContext.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelContext.d.mts","names":[],"sources":["../../src/types/modelContext.ts"],"mappings":";;UACiB;WACN;WACA;WACA,aAAa,SAAS;;UAGhB;WACN;WACA;EACT,iBAAiB,eAAe,sBAAsB;aAAqB;;EAC3E,oBAAoB,eAAe;;UAGpB;WACN,QAAQ;;UAGF;WACN,QAAQ;;;UAIF,yBAAyB;EACxC,QAAQ,eAAe,SAAS,+BAA+B;;UAGhD;WACN;;KAGC,kCAAkC,OAAO;;UAGpC;EACf,aAAa,MAAM,kBAAkB,SAAS,yCAAyC;EACvF,YAAY,iBAAiB;EAC7B,YAAY,cAAc,eAAe,SAAS,+BAA+B;EACjF,iBAAiB,oBAAoB,UAAU;EAC/C,oBAAoB,oBAAoB,UAAU;;UAGnC;WACN;WACA,cAAc"}
@@ -20,12 +20,41 @@ interface HubSessionScope {
20
20
  /** The session's working directory, for repo-scoped data sources. */
21
21
  cwd: string;
22
22
  }
23
+ interface HubSessionApiRequest {
24
+ basePath: string;
25
+ path: string;
26
+ method: string;
27
+ body?: string | Uint8Array<ArrayBuffer> | null;
28
+ signal?: AbortSignal;
29
+ }
30
+ type ComputerUseHostOperation = 'status' | 'targets' | 'activate' | 'observe' | 'act' | 'stop';
31
+ interface ComputerUseHostRequest {
32
+ readonly operation: ComputerUseHostOperation;
33
+ readonly payload?: unknown;
34
+ readonly signal?: AbortSignal;
35
+ }
36
+ /** Narrow optional binding supplied only by a Desktop-owned cockpit hub. */
37
+ interface ComputerUseHostBinding {
38
+ readonly available: boolean;
39
+ request(scope: HubSessionScope, request: ComputerUseHostRequest): Promise<unknown>;
40
+ close?(): void;
41
+ }
42
+ interface HubChannelConnection {
43
+ /** Opaque for one page socket lifetime. */
44
+ connectionId: string;
45
+ }
23
46
  /** What the hub hands a channel when it starts. */
24
47
  interface HubChannelHost {
25
48
  /** Every session the hub currently manages. */
26
49
  sessions(): readonly HubSessionScope[];
27
50
  /** Live fan-out to the session's page subscribers. */
28
51
  publish(sessionId: string, payload: unknown): void;
52
+ /** Delivers only to the authenticated page socket named by connectionId. */
53
+ publishToConnection?(connectionId: string, sessionId: string, payload: unknown): boolean;
54
+ /** Authenticated machine-local transport to a session package API. */
55
+ requestSessionApi(scope: HubSessionScope, request: HubSessionApiRequest): Promise<Response>;
56
+ /** Absent unless this exact hub is the child owned by DoomPi Desktop. */
57
+ computerUse?: ComputerUseHostBinding;
29
58
  onNotice(message: string): void;
30
59
  }
31
60
  /**
@@ -46,11 +75,23 @@ interface HubChannelSource {
46
75
  threadJournal?(scope: HubSessionScope, threadId: string): string | undefined;
47
76
  close(): void;
48
77
  }
78
+ type HubChannelLifecycle = 'session' | 'hub';
49
79
  interface WebHubChannel {
50
80
  /** Wire frame type; globally unique across every loaded plugin. */
51
81
  frameType: string;
82
+ /**
83
+ * Whether this channel source belongs to one session or the whole hub.
84
+ * Omitted channels retain the session-local lifecycle for compatibility.
85
+ */
86
+ lifecycle?: HubChannelLifecycle;
52
87
  start(host: HubChannelHost): HubChannelSource;
88
+ /** Allows an authenticated connection to acknowledge targeted frames after its session subscription changes. */
89
+ receiveWithoutSubscription?: boolean;
90
+ /** Receives only frames matching frameType and a live session scope. */
91
+ receive?(scope: HubSessionScope, payload: unknown, connection: HubChannelConnection): void;
92
+ /** The authenticated page socket has closed. */
93
+ disconnected?(connection: HubChannelConnection): void;
53
94
  }
54
95
  //#endregion
55
- export { ChannelFrame, HubChannelHost, HubChannelSource, HubSessionScope, WebHubChannel };
96
+ export { ChannelFrame, ComputerUseHostBinding, ComputerUseHostOperation, ComputerUseHostRequest, HubChannelConnection, HubChannelHost, HubChannelLifecycle, HubChannelSource, HubSessionApiRequest, HubSessionScope, WebHubChannel };
56
97
  //# sourceMappingURL=webHub.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webHub.d.cts","names":[],"sources":["../../src/types/webHub.ts"],"mappings":";;;;;;;;;;;;UAYiB;EACf;EACA;EACA;;UAGe;EACf;;EAEA;;;UAIe;;EAEf,qBAAqB;;EAErB,QAAQ,mBAAmB;EAC3B,SAAS;;;;;;;UAQM;;EAEf,WAAW,OAAO;EAClB,cAAc,OAAO;EACrB,gBAAgB;;;;;;EAMhB,eAAe,OAAO,iBAAiB;EACvC;;UAGe;;EAEf;EACA,MAAM,MAAM,iBAAiB"}
1
+ {"version":3,"file":"webHub.d.cts","names":[],"sources":["../../src/types/webHub.ts"],"mappings":";;;;;;;;;;;;UAYiB;EACf;EACA;EACA;;UAGe;EACf;;EAEA;;UAGe;EACf;EACA;EACA;EACA,gBAAgB,WAAW;EAC3B,SAAS;;KAGC;UAEK;WACN,WAAW;WACX;WACA,SAAS;;;UAIH;WACN;EACT,QAAQ,OAAO,iBAAiB,SAAS,yBAAyB;EAClE;;UAGe;;EAEf;;;UAIe;;EAEf,qBAAqB;;EAErB,QAAQ,mBAAmB;;EAE3B,qBAAqB,sBAAsB,mBAAmB;;EAE9D,kBAAkB,OAAO,iBAAiB,SAAS,uBAAuB,QAAQ;;EAElF,cAAc;EACd,SAAS;;;;;;;UAQM;;EAEf,WAAW,OAAO;EAClB,cAAc,OAAO;EACrB,gBAAgB;;;;;;EAMhB,eAAe,OAAO,iBAAiB;EACvC;;KAGU;UAEK;;EAEf;;;;;EAKA,YAAY;EACZ,MAAM,MAAM,iBAAiB;;EAE7B;;EAEA,SAAS,OAAO,iBAAiB,kBAAkB,YAAY;;EAE/D,cAAc,YAAY"}