@agimon-ai/doompi-web-contracts 0.0.1-alpha.2 → 0.0.1-alpha.5
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/README.md +36 -17
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +1 -1
- package/dist/services/globalStore.cjs +2 -0
- package/dist/services/globalStore.cjs.map +1 -0
- package/dist/services/globalStore.d.cts +7 -0
- package/dist/services/globalStore.d.cts.map +1 -0
- package/dist/services/globalStore.d.mts +7 -0
- package/dist/services/globalStore.d.mts.map +1 -0
- package/dist/services/globalStore.mjs +2 -0
- package/dist/services/globalStore.mjs.map +1 -0
- package/dist/services/testing/channels.cjs +1 -1
- package/dist/services/testing/channels.cjs.map +1 -1
- package/dist/services/testing/channels.mjs +1 -1
- package/dist/services/testing/channels.mjs.map +1 -1
- package/dist/types/webHub.d.cts +22 -1
- package/dist/types/webHub.d.cts.map +1 -1
- package/dist/types/webHub.d.mts +22 -1
- package/dist/types/webHub.d.mts.map +1 -1
- package/dist/types/webPlugin.d.cts +164 -5
- package/dist/types/webPlugin.d.cts.map +1 -1
- package/dist/types/webPlugin.d.mts +164 -5
- package/dist/types/webPlugin.d.mts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,27 +1,46 @@
|
|
|
1
1
|
# @agimon-ai/doompi-web-contracts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Typed plugin, slot, session-channel, and hub-channel contracts for the DoomPi web cockpit.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This is a library, not a Pi extension. It starts no process and does not belong in `.doom/modes.yaml`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> **Alpha:** web plugin contracts may change between releases.
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
21
|
-
|
|
11
|
+
```bash
|
|
12
|
+
npm install @agimon-ai/doompi-web-contracts
|
|
13
|
+
```
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
|
|
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/
|
|
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,7 @@
|
|
|
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 { ActivityGroupContribution, GlobalStore, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, RepositorySettingsPanelContribution, RepositorySettingsPanelProps, RepositorySettingsRepository, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SettingsFieldContribution, SettingsFieldOption, SettingsSectionContribution, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ToolMessageRenderProps, ToolPromptContribution, ToolPromptDialog, ToolPromptRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps } from "./types/webPlugin.cjs";
|
|
2
2
|
import { defineSessionChannel, defineSlot, defineWebPlugin } from "./services/define.cjs";
|
|
3
|
+
import { defineGlobalStore } from "./services/globalStore.cjs";
|
|
3
4
|
import { defineSessionStore } from "./services/sessionStore.cjs";
|
|
4
5
|
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 };
|
|
6
|
+
import { ChannelFrame, HubChannelConnection, HubChannelHost, HubChannelSource, HubSessionApiRequest, HubSessionScope, WebHubChannel } from "./types/webHub.cjs";
|
|
7
|
+
export { type ActivityGroupContribution, type ChannelFrame, type GlobalStore, type HubChannelConnection, type HubChannelHost, type HubChannelSource, type HubSessionApiRequest, type HubSessionScope, type LeaderBindingContribution, type LeaderKeyContribution, type MinorModeContribution, 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 SettingsSectionContribution, type SlotDataFill, type SlotDeclaration, type SlotFillContribution, type SurfaceContribution, type TabContribution, type ToolMessageRenderProps, type ToolPromptContribution, type ToolPromptDialog, type ToolPromptRenderProps, type ToolRendererContribution, type ToolResultView, type TransientTab, type WebHubChannel, type WebPluginDefinition, type WebPluginRuntime, type WebPluginSlotProps, defineGlobalStore, defineSessionChannel, defineSessionStore, defineSlot, defineWebPlugin, toolResultText, toolResultTextLines };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 { ActivityGroupContribution, GlobalStore, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, RepositorySettingsPanelContribution, RepositorySettingsPanelProps, RepositorySettingsRepository, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SettingsFieldContribution, SettingsFieldOption, SettingsSectionContribution, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ToolMessageRenderProps, ToolPromptContribution, ToolPromptDialog, ToolPromptRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps } from "./types/webPlugin.mjs";
|
|
2
2
|
import { defineSessionChannel, defineSlot, defineWebPlugin } from "./services/define.mjs";
|
|
3
|
+
import { defineGlobalStore } from "./services/globalStore.mjs";
|
|
3
4
|
import { defineSessionStore } from "./services/sessionStore.mjs";
|
|
4
5
|
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 };
|
|
6
|
+
import { ChannelFrame, HubChannelConnection, HubChannelHost, HubChannelSource, HubSessionApiRequest, HubSessionScope, WebHubChannel } from "./types/webHub.mjs";
|
|
7
|
+
export { type ActivityGroupContribution, type ChannelFrame, type GlobalStore, type HubChannelConnection, type HubChannelHost, type HubChannelSource, type HubSessionApiRequest, type HubSessionScope, type LeaderBindingContribution, type LeaderKeyContribution, type MinorModeContribution, 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 SettingsSectionContribution, type SlotDataFill, type SlotDeclaration, type SlotFillContribution, type SurfaceContribution, type TabContribution, type ToolMessageRenderProps, type ToolPromptContribution, type ToolPromptDialog, type ToolPromptRenderProps, type ToolRendererContribution, type ToolResultView, type TransientTab, type WebHubChannel, 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{
|
|
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 @@
|
|
|
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 @@
|
|
|
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,
|
|
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,
|
|
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"}
|
package/dist/types/webHub.d.cts
CHANGED
|
@@ -20,12 +20,27 @@ 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
|
+
interface HubChannelConnection {
|
|
31
|
+
/** Opaque for one page socket lifetime. */
|
|
32
|
+
connectionId: string;
|
|
33
|
+
}
|
|
23
34
|
/** What the hub hands a channel when it starts. */
|
|
24
35
|
interface HubChannelHost {
|
|
25
36
|
/** Every session the hub currently manages. */
|
|
26
37
|
sessions(): readonly HubSessionScope[];
|
|
27
38
|
/** Live fan-out to the session's page subscribers. */
|
|
28
39
|
publish(sessionId: string, payload: unknown): void;
|
|
40
|
+
/** Delivers only to the authenticated page socket named by connectionId. */
|
|
41
|
+
publishToConnection?(connectionId: string, sessionId: string, payload: unknown): boolean;
|
|
42
|
+
/** Authenticated machine-local transport to a session package API. */
|
|
43
|
+
requestSessionApi(scope: HubSessionScope, request: HubSessionApiRequest): Promise<Response>;
|
|
29
44
|
onNotice(message: string): void;
|
|
30
45
|
}
|
|
31
46
|
/**
|
|
@@ -50,7 +65,13 @@ interface WebHubChannel {
|
|
|
50
65
|
/** Wire frame type; globally unique across every loaded plugin. */
|
|
51
66
|
frameType: string;
|
|
52
67
|
start(host: HubChannelHost): HubChannelSource;
|
|
68
|
+
/** Allows an authenticated connection to acknowledge targeted frames after its session subscription changes. */
|
|
69
|
+
receiveWithoutSubscription?: boolean;
|
|
70
|
+
/** Receives only frames matching frameType and a live session scope. */
|
|
71
|
+
receive?(scope: HubSessionScope, payload: unknown, connection: HubChannelConnection): void;
|
|
72
|
+
/** The authenticated page socket has closed. */
|
|
73
|
+
disconnected?(connection: HubChannelConnection): void;
|
|
53
74
|
}
|
|
54
75
|
//#endregion
|
|
55
|
-
export { ChannelFrame, HubChannelHost, HubChannelSource, HubSessionScope, WebHubChannel };
|
|
76
|
+
export { ChannelFrame, HubChannelConnection, HubChannelHost, HubChannelSource, HubSessionApiRequest, HubSessionScope, WebHubChannel };
|
|
56
77
|
//# 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;
|
|
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;;UAGM;;EAEf;;;UAIe;;EAEf,qBAAqB;;EAErB,QAAQ,mBAAmB;;EAE3B,qBAAqB,sBAAsB,mBAAmB;;EAE9D,kBAAkB,OAAO,iBAAiB,SAAS,uBAAuB,QAAQ;EAClF,SAAS;;;;;;;UAQM;;EAEf,WAAW,OAAO;EAClB,cAAc,OAAO;EACrB,gBAAgB;;;;;;EAMhB,eAAe,OAAO,iBAAiB;EACvC;;UAGe;;EAEf;EACA,MAAM,MAAM,iBAAiB;;EAE7B;;EAEA,SAAS,OAAO,iBAAiB,kBAAkB,YAAY;;EAE/D,cAAc,YAAY"}
|
package/dist/types/webHub.d.mts
CHANGED
|
@@ -20,12 +20,27 @@ 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
|
+
interface HubChannelConnection {
|
|
31
|
+
/** Opaque for one page socket lifetime. */
|
|
32
|
+
connectionId: string;
|
|
33
|
+
}
|
|
23
34
|
/** What the hub hands a channel when it starts. */
|
|
24
35
|
interface HubChannelHost {
|
|
25
36
|
/** Every session the hub currently manages. */
|
|
26
37
|
sessions(): readonly HubSessionScope[];
|
|
27
38
|
/** Live fan-out to the session's page subscribers. */
|
|
28
39
|
publish(sessionId: string, payload: unknown): void;
|
|
40
|
+
/** Delivers only to the authenticated page socket named by connectionId. */
|
|
41
|
+
publishToConnection?(connectionId: string, sessionId: string, payload: unknown): boolean;
|
|
42
|
+
/** Authenticated machine-local transport to a session package API. */
|
|
43
|
+
requestSessionApi(scope: HubSessionScope, request: HubSessionApiRequest): Promise<Response>;
|
|
29
44
|
onNotice(message: string): void;
|
|
30
45
|
}
|
|
31
46
|
/**
|
|
@@ -50,7 +65,13 @@ interface WebHubChannel {
|
|
|
50
65
|
/** Wire frame type; globally unique across every loaded plugin. */
|
|
51
66
|
frameType: string;
|
|
52
67
|
start(host: HubChannelHost): HubChannelSource;
|
|
68
|
+
/** Allows an authenticated connection to acknowledge targeted frames after its session subscription changes. */
|
|
69
|
+
receiveWithoutSubscription?: boolean;
|
|
70
|
+
/** Receives only frames matching frameType and a live session scope. */
|
|
71
|
+
receive?(scope: HubSessionScope, payload: unknown, connection: HubChannelConnection): void;
|
|
72
|
+
/** The authenticated page socket has closed. */
|
|
73
|
+
disconnected?(connection: HubChannelConnection): void;
|
|
53
74
|
}
|
|
54
75
|
//#endregion
|
|
55
|
-
export { ChannelFrame, HubChannelHost, HubChannelSource, HubSessionScope, WebHubChannel };
|
|
76
|
+
export { ChannelFrame, HubChannelConnection, HubChannelHost, HubChannelSource, HubSessionApiRequest, HubSessionScope, WebHubChannel };
|
|
56
77
|
//# sourceMappingURL=webHub.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webHub.d.mts","names":[],"sources":["../../src/types/webHub.ts"],"mappings":";;;;;;;;;;;;UAYiB;EACf;EACA;EACA;;UAGe;EACf;;EAEA;;;UAIe;;EAEf,qBAAqB;;EAErB,QAAQ,mBAAmB;
|
|
1
|
+
{"version":3,"file":"webHub.d.mts","names":[],"sources":["../../src/types/webHub.ts"],"mappings":";;;;;;;;;;;;UAYiB;EACf;EACA;EACA;;UAGe;EACf;;EAEA;;UAGe;EACf;EACA;EACA;EACA,gBAAgB,WAAW;EAC3B,SAAS;;UAGM;;EAEf;;;UAIe;;EAEf,qBAAqB;;EAErB,QAAQ,mBAAmB;;EAE3B,qBAAqB,sBAAsB,mBAAmB;;EAE9D,kBAAkB,OAAO,iBAAiB,SAAS,uBAAuB,QAAQ;EAClF,SAAS;;;;;;;UAQM;;EAEf,WAAW,OAAO;EAClB,cAAc,OAAO;EACrB,gBAAgB;;;;;;EAMhB,eAAe,OAAO,iBAAiB;EACvC;;UAGe;;EAEf;EACA,MAAM,MAAM,iBAAiB;;EAE7B;;EAEA,SAAS,OAAO,iBAAiB,kBAAkB,YAAY;;EAE/D,cAAc,YAAY"}
|
|
@@ -10,9 +10,11 @@ import { ComponentType, ReactNode } from "react";
|
|
|
10
10
|
* @tanstack/store, @tanstack/react-store, this contract,
|
|
11
11
|
* @agimon-ai/doompi-web-components, and the package's own web/ and src/types
|
|
12
12
|
* modules; never another plugin, a node builtin, or a server framework, which
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* modules; never another plugin, a node builtin, or a server framework, which
|
|
14
|
+
* the host bundle would swallow. Page-wide state takes the shape
|
|
15
|
+
* defineGlobalStore gives it; per-session state takes the shape defineSessionStore
|
|
16
|
+
* gives it. Tailwind utility classes must appear as complete literal strings so
|
|
17
|
+
* the host's class scanner can see them.
|
|
16
18
|
*
|
|
17
19
|
* Plugins are independent: none depends on another, any of them may be added
|
|
18
20
|
* or removed at the next sync, and every relation between two plugins (a fill
|
|
@@ -105,6 +107,81 @@ interface TabContribution {
|
|
|
105
107
|
/** A React hook, fully typed inside the plugin; 0 hides the badge. */
|
|
106
108
|
useBadge?: (sessionId: string | null) => number;
|
|
107
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* One field on a settings page, declared as data.
|
|
112
|
+
*
|
|
113
|
+
* Settings are data rather than a component because the host owns what a
|
|
114
|
+
* plugin cannot: which file an edit lands in. A page has one scope switch and
|
|
115
|
+
* one repository, and every field on it reports where its value came from and
|
|
116
|
+
* whether the selected scope may hold it. A plugin drawing its own form would
|
|
117
|
+
* have to reproduce all of that, and each would get it slightly different.
|
|
118
|
+
*/
|
|
119
|
+
interface SettingsFieldContribution {
|
|
120
|
+
/** Unique within the section; also the testid suffix. */
|
|
121
|
+
id: string;
|
|
122
|
+
label: string;
|
|
123
|
+
kind: 'text' | 'select' | 'toggle' | 'info';
|
|
124
|
+
/** The config key this reads and writes, e.g. ['modes','planning','main','model']. */
|
|
125
|
+
keyPath: readonly string[];
|
|
126
|
+
/** One line of help under the field. */
|
|
127
|
+
detail?: string;
|
|
128
|
+
/** Shown while the field is unset, in place of a value. */
|
|
129
|
+
placeholder?: string;
|
|
130
|
+
/** The closed set a 'select' offers, when the plugin knows it. */
|
|
131
|
+
options?: readonly SettingsFieldOption[];
|
|
132
|
+
/**
|
|
133
|
+
* An option set the host supplies instead. A model picker needs the machine's
|
|
134
|
+
* authenticated models, which no plugin can enumerate from the browser; a
|
|
135
|
+
* host that cannot answer leaves the field as free text.
|
|
136
|
+
*/
|
|
137
|
+
optionsFrom?: 'models';
|
|
138
|
+
}
|
|
139
|
+
interface SettingsFieldOption {
|
|
140
|
+
value: string;
|
|
141
|
+
label: string;
|
|
142
|
+
/** Groups entries under a heading, as providers group their models. */
|
|
143
|
+
group?: string;
|
|
144
|
+
}
|
|
145
|
+
/** One repository the host has admitted into repository-scoped settings. */
|
|
146
|
+
interface RepositorySettingsRepository {
|
|
147
|
+
/** Opaque hub-issued identity. Package APIs resolve this through DoomApiContext. */
|
|
148
|
+
id: string;
|
|
149
|
+
/** Last path segment, for compact pickers and headings. */
|
|
150
|
+
name: string;
|
|
151
|
+
/** Display-only absolute path. APIs must use id rather than trusting this value. */
|
|
152
|
+
path: string;
|
|
153
|
+
/** True while at least one session is open inside this repository. */
|
|
154
|
+
active: boolean;
|
|
155
|
+
}
|
|
156
|
+
/** Props for a package panel placed inside the host's repository control plane. */
|
|
157
|
+
interface RepositorySettingsPanelProps {
|
|
158
|
+
repository: RepositorySettingsRepository | null;
|
|
159
|
+
/** Same-origin request routed through the host's sealed transport when remote. */
|
|
160
|
+
request: (input: string, init?: RequestInit) => Promise<Response>;
|
|
161
|
+
/** The same transport, retrying once after the host's fresh passkey gesture. */
|
|
162
|
+
requestWithStepUp: (input: string, init?: RequestInit) => Promise<Response>;
|
|
163
|
+
}
|
|
164
|
+
/** One package-owned panel below the host's repository selection controls. */
|
|
165
|
+
interface RepositorySettingsPanelContribution {
|
|
166
|
+
label: string;
|
|
167
|
+
detail: string;
|
|
168
|
+
/** Sort position below the host controls; lower first, then plugin id. */
|
|
169
|
+
order?: number;
|
|
170
|
+
component: ComponentType<RepositorySettingsPanelProps>;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A page in cockpit settings, contributed by the package that owns the
|
|
174
|
+
* settings on it. The id is the URL segment (/settings/:id), so it is unique
|
|
175
|
+
* across plugins; a collision is an install diagnostic and the first wins.
|
|
176
|
+
*/
|
|
177
|
+
interface SettingsSectionContribution {
|
|
178
|
+
id: string;
|
|
179
|
+
label: string;
|
|
180
|
+
detail: string;
|
|
181
|
+
/** Sort position in the settings menu; lower first, id breaks ties. */
|
|
182
|
+
order?: number;
|
|
183
|
+
fields: readonly SettingsFieldContribution[];
|
|
184
|
+
}
|
|
108
185
|
interface SurfaceContribution {
|
|
109
186
|
id: string;
|
|
110
187
|
component: ComponentType<WebPluginSlotProps>;
|
|
@@ -214,9 +291,31 @@ interface ActivityGroupContribution {
|
|
|
214
291
|
widgetKeys?: string[];
|
|
215
292
|
/** The plugin tab the group's key chip opens; without one the chip is a plain label. */
|
|
216
293
|
tab?: string;
|
|
294
|
+
/**
|
|
295
|
+
* Drop the group entirely while its status key is present but empty, rather
|
|
296
|
+
* than showing a header over "idle".
|
|
297
|
+
*
|
|
298
|
+
* A session publishes a status key once and clears it by publishing nothing,
|
|
299
|
+
* which reaches the page as an empty string, so a group that has reported at
|
|
300
|
+
* all can never stop reporting. For a group whose whole content is the thing
|
|
301
|
+
* the session is doing (a goal, a recording, the files it changed), the
|
|
302
|
+
* header alone is a row that says nothing, and a dock of those buries the
|
|
303
|
+
* groups that do. A group that is also a way in, with a launcher or a tab,
|
|
304
|
+
* leaves this off: its frame is worth keeping when it is idle.
|
|
305
|
+
*/
|
|
306
|
+
hideWhenEmpty?: boolean;
|
|
307
|
+
/** Keeps the group visible below the dock's scrolling ordinary groups. */
|
|
308
|
+
placement?: 'bottom';
|
|
217
309
|
/** Sort position in the dock; lower first, name breaks ties. */
|
|
218
310
|
order?: number;
|
|
219
311
|
}
|
|
312
|
+
/** Page-wide plugin state shared by its runtime, channels, and every rendered contribution. */
|
|
313
|
+
interface GlobalStore<T> {
|
|
314
|
+
readonly store: Store<T>;
|
|
315
|
+
/** Replaces the value; an updater returning the current value publishes nothing. */
|
|
316
|
+
update(updater: (current: T) => T): void;
|
|
317
|
+
reset(): void;
|
|
318
|
+
}
|
|
220
319
|
/**
|
|
221
320
|
* One session-scoped data channel: the hub pushes ChannelFrame payloads whose
|
|
222
321
|
* frame type equals `channel`; parse is the validation gate at the boundary
|
|
@@ -289,6 +388,51 @@ interface ToolMessageRenderProps extends WebPluginSlotProps {
|
|
|
289
388
|
running: boolean;
|
|
290
389
|
isError: boolean;
|
|
291
390
|
}
|
|
391
|
+
/** The extension UI request a running tool is blocked on, as its prompt sees it. */
|
|
392
|
+
interface ToolPromptDialog {
|
|
393
|
+
id: string;
|
|
394
|
+
method: 'select' | 'confirm' | 'input' | 'editor';
|
|
395
|
+
title: string;
|
|
396
|
+
message: string;
|
|
397
|
+
options: readonly string[];
|
|
398
|
+
placeholder: string;
|
|
399
|
+
prefill: string;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Everything a tool's composer prompt receives: the timeline item's props,
|
|
403
|
+
* plus the request the tool is waiting on and the two ways to settle it.
|
|
404
|
+
*
|
|
405
|
+
* The dialog frame is deliberately thin, because Pi's own is: a select
|
|
406
|
+
* carries a title and a list of labels and nothing else. A prompt that needs
|
|
407
|
+
* more renders it from `args`, which is the whole call the tool was made
|
|
408
|
+
* with, and answers with whatever its session half agreed to read back.
|
|
409
|
+
*/
|
|
410
|
+
interface ToolPromptRenderProps extends ToolMessageRenderProps {
|
|
411
|
+
dialog: ToolPromptDialog;
|
|
412
|
+
/** Answers the request and unblocks the tool. Bound, so a prompt may destructure it. */
|
|
413
|
+
answer: (value: string) => void;
|
|
414
|
+
/** The reader declined; the tool sees a cancellation. */
|
|
415
|
+
cancel: () => void;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* A tool's stand-in for the composer input, shown while the tool is running
|
|
419
|
+
* and holding an extension UI request.
|
|
420
|
+
*
|
|
421
|
+
* A question the agent is blocked on belongs where the reader is already
|
|
422
|
+
* looking and typing, not behind a modal covering the conversation they need
|
|
423
|
+
* in order to answer. While a prompt is up the host's own dialog stands down,
|
|
424
|
+
* so exactly one surface owns the request.
|
|
425
|
+
*/
|
|
426
|
+
interface ToolPromptContribution {
|
|
427
|
+
/**
|
|
428
|
+
* True when this tool owns the open request. Without it the tool claims any
|
|
429
|
+
* request open while it runs, which is wrong the moment a second extension
|
|
430
|
+
* asks something during the same turn; a request it refuses falls back to
|
|
431
|
+
* the host's dialog.
|
|
432
|
+
*/
|
|
433
|
+
claims?(dialog: ToolPromptDialog, args: Record<string, unknown>): boolean;
|
|
434
|
+
component: ComponentType<ToolPromptRenderProps>;
|
|
435
|
+
}
|
|
292
436
|
/**
|
|
293
437
|
* The timeline item for the tools a package registers, the web half of the
|
|
294
438
|
* TUI's renderCall/renderResult with Pi's renderShell 'self': one `message`
|
|
@@ -306,11 +450,14 @@ interface ToolRendererContribution {
|
|
|
306
450
|
*/
|
|
307
451
|
matches?(toolName: string, statuses: Readonly<Record<string, string>>): boolean;
|
|
308
452
|
message: ComponentType<ToolMessageRenderProps>;
|
|
453
|
+
/** Stands in for the composer input while this tool runs and holds a request. */
|
|
454
|
+
prompt?: ToolPromptContribution;
|
|
309
455
|
}
|
|
310
|
-
/** What a plugin's optional runtime may do
|
|
456
|
+
/** What a plugin's optional runtime may do through the page's hub socket. */
|
|
311
457
|
interface WebPluginRuntime {
|
|
312
458
|
sendSessionFrame: SessionFrameSender;
|
|
313
459
|
sendHubFrame(frame: Record<string, unknown>): void;
|
|
460
|
+
onHubConnected(listener: () => void): () => void;
|
|
314
461
|
}
|
|
315
462
|
interface WebPluginDefinition {
|
|
316
463
|
id: string;
|
|
@@ -324,6 +471,8 @@ interface WebPluginDefinition {
|
|
|
324
471
|
leaderBindings?: LeaderBindingContribution[];
|
|
325
472
|
railSections?: SurfaceContribution[];
|
|
326
473
|
selectionBarItems?: SurfaceContribution[];
|
|
474
|
+
/** Compact controls placed in the mobile composer's action row, immediately before queue. */
|
|
475
|
+
composerActions?: SurfaceContribution[];
|
|
327
476
|
toolRenderers?: ToolRendererContribution[];
|
|
328
477
|
/**
|
|
329
478
|
* A section whose id names an activity group any plugin declares renders
|
|
@@ -331,6 +480,16 @@ interface WebPluginDefinition {
|
|
|
331
480
|
* one-line summary; any other section renders after the groups.
|
|
332
481
|
*/
|
|
333
482
|
activitySections?: SurfaceContribution[];
|
|
483
|
+
/**
|
|
484
|
+
* Pages this plugin adds to cockpit settings. The host renders the fields and
|
|
485
|
+
* owns the scope switch, the repository picker, and every write.
|
|
486
|
+
*/
|
|
487
|
+
settingsSections?: SettingsSectionContribution[];
|
|
488
|
+
/**
|
|
489
|
+
* A package-owned management panel inside the host's repository page. The
|
|
490
|
+
* host owns repository identity and passes only repositories it admitted.
|
|
491
|
+
*/
|
|
492
|
+
repositorySettingsPanel?: RepositorySettingsPanelContribution;
|
|
334
493
|
/** The slots this plugin opens for others, each named '<this plugin id>.<name>'. */
|
|
335
494
|
slots?: SlotDeclaration[];
|
|
336
495
|
/** This plugin's contributions into slots other plugins (or the host) declare. */
|
|
@@ -339,5 +498,5 @@ interface WebPluginDefinition {
|
|
|
339
498
|
start?(runtime: WebPluginRuntime): (() => void) | void;
|
|
340
499
|
}
|
|
341
500
|
//#endregion
|
|
342
|
-
export { ActivityGroupContribution, LeaderBindingBase, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ToolMessageRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps };
|
|
501
|
+
export { ActivityGroupContribution, GlobalStore, LeaderBindingBase, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, RepositorySettingsPanelContribution, RepositorySettingsPanelProps, RepositorySettingsRepository, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SettingsFieldContribution, SettingsFieldOption, SettingsSectionContribution, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ToolMessageRenderProps, ToolPromptContribution, ToolPromptDialog, ToolPromptRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps };
|
|
343
502
|
//# sourceMappingURL=webPlugin.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webPlugin.d.cts","names":[],"sources":["../../src/types/webPlugin.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"webPlugin.d.cts","names":[],"sources":["../../src/types/webPlugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;KA0BY,sBAAsB,mBAAmB,OAAO;;;;;;;;;;UAU3C,gBAAgB;EAC/B;;EAEA,OAAO,iBAAiB;;;UAGT,aAAa;EAC5B;EACA;EACA;EACA,MAAM;;;;;;;;UAQS;;EAEf;EACA;EACA,OAAO,cAAc;;;UAGN;EACf;;EAEA,UAAU;;EAEV,mBAAmB,KAAK;EACxB,oBAAoB;;;;;;EAMpB,eAAe,qBAAqB;;EAEpC,kBAAkB;;EAElB,aAAa,iBAAiB;;EAE9B,WAAW,MAAM,MAAM,gBAAgB,mBAAmB,aAAa;;;;;;;EAOvE,UAAU,SAAS;;;;;;;UAOJ;EACf;EACA;;EAEA;EACA;EACA,YAAY,cAAc;;UAEX;;EAEf;EACA;EACA,OAAO,cAAc;;EAErB,YAAY;;;;;;;;;;;UAWG;;EAEf;EACA;EACA;;EAEA;;EAEA;;EAEA;;EAEA,mBAAmB;;;;;;EAMnB;;UAEe;EACf;EACA;;EAEA;;;UAGe;;EAEf;;EAEA;;EAEA;;EAEA;;;UAIe;EACf,YAAY;;EAEZ,UAAU,eAAe,OAAO,gBAAgB,QAAQ;;EAExD,oBAAoB,eAAe,OAAO,gBAAgB,QAAQ;;;UAInD;EACf;EACA;;EAEA;EACA,WAAW,cAAc;;;;;;;UAQV;EACf;EACA;EACA;;EAEA;EACA,iBAAiB;;UAEF;EACf;EACA,WAAW,cAAc;;UAEV;EACf;;EAEA,QAAQ;EACR,kBAAkB;;UAEH;EACf;EACA;EACA;EACA,IAAI,SAAS;;;;;;;UAOE;EACf;EACA;EACA;;UAEe;EACf;;EAEA,MAAM;;;;;;;;;;;;;KAaI,6BACP;EACC;MAED;EACC,IAAI,SAAS;;;;;;;;UAQF;EACf;;;;;;;EAOA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;UAQe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;;;UAUe;EACf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;;;;;;EAaA;;EAEA;;EAEA;;;UAGe,YAAY;WAClB,OAAO,MAAM;;EAEtB,OAAO,UAAU,SAAS,MAAM;EAChC;;;;;;;UAQe,2BAA2B;EAC1C;EACA,MAAM,iBAAiB;EACvB,MAAM,mBAAmB,SAAS;EAClC,KAAK;;;KAGK,eAAe,KAAK,QAAQ,eAAe;;UAEtC,oBAAoB,GAAG;EACtC;EACA,MAAM,iBAAiB;;EAEvB,OAAO,SAAS,GAAG,SAAS,UAAU;;;;;;;UAOvB,aAAa;WACnB,OAAO,MAAM,eAAe;;;;;;EAMrC,OAAO,OAAO,eAAe,IAAI,2BAA2B;;EAE5D,OAAO,mBAAmB,UAAU,SAAS,MAAM;EACnD,KAAK;EACL;;EAEA,QAAQ,SAAS,SAAS,oBAAoB,GAAG,WAAW;;;;;;;UAO7C;EACf;EACA;;;;;;;;;;UAUe,+BAA+B;EAC9C;;EAEA;EACA,MAAM;;EAEN,UAAU,SAAS;;EAEnB,QAAQ;;EAER;;EAEA;EACA;;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;;UAWe,8BAA8B;EAC7C,QAAQ;;EAER,SAAS;;EAET;;;;;;;;;;;UAWe;;;;;;;EAOf,QAAQ,QAAQ,kBAAkB,MAAM;EACxC,WAAW,cAAc;;;;;;;;UAQV;;EAEf;;;;;;;EAOA,SAAS,kBAAkB,UAAU,SAAS;EAC9C,SAAS,cAAc;;EAEvB,SAAS;;;UAGM;EACf,kBAAkB;EAClB,aAAa,OAAO;EACpB,eAAe;;UAEA;EACf;EACA,OAAO;EACP,WAAW;EACX,gBAAgB;EAChB,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,kBAAkB;EAClB,iBAAiB;EACjB,eAAe;EACf,oBAAoB;;EAEpB,kBAAkB;EAClB,gBAAgB;;;;;;EAMhB,mBAAmB;;;;;EAKnB,mBAAmB;;;;;EAKnB,0BAA0B;;EAE1B,QAAQ;;EAER,QAAQ;;EAER,OAAO,SAAS"}
|
|
@@ -10,9 +10,11 @@ import { ComponentType, ReactNode } from "react";
|
|
|
10
10
|
* @tanstack/store, @tanstack/react-store, this contract,
|
|
11
11
|
* @agimon-ai/doompi-web-components, and the package's own web/ and src/types
|
|
12
12
|
* modules; never another plugin, a node builtin, or a server framework, which
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* modules; never another plugin, a node builtin, or a server framework, which
|
|
14
|
+
* the host bundle would swallow. Page-wide state takes the shape
|
|
15
|
+
* defineGlobalStore gives it; per-session state takes the shape defineSessionStore
|
|
16
|
+
* gives it. Tailwind utility classes must appear as complete literal strings so
|
|
17
|
+
* the host's class scanner can see them.
|
|
16
18
|
*
|
|
17
19
|
* Plugins are independent: none depends on another, any of them may be added
|
|
18
20
|
* or removed at the next sync, and every relation between two plugins (a fill
|
|
@@ -105,6 +107,81 @@ interface TabContribution {
|
|
|
105
107
|
/** A React hook, fully typed inside the plugin; 0 hides the badge. */
|
|
106
108
|
useBadge?: (sessionId: string | null) => number;
|
|
107
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* One field on a settings page, declared as data.
|
|
112
|
+
*
|
|
113
|
+
* Settings are data rather than a component because the host owns what a
|
|
114
|
+
* plugin cannot: which file an edit lands in. A page has one scope switch and
|
|
115
|
+
* one repository, and every field on it reports where its value came from and
|
|
116
|
+
* whether the selected scope may hold it. A plugin drawing its own form would
|
|
117
|
+
* have to reproduce all of that, and each would get it slightly different.
|
|
118
|
+
*/
|
|
119
|
+
interface SettingsFieldContribution {
|
|
120
|
+
/** Unique within the section; also the testid suffix. */
|
|
121
|
+
id: string;
|
|
122
|
+
label: string;
|
|
123
|
+
kind: 'text' | 'select' | 'toggle' | 'info';
|
|
124
|
+
/** The config key this reads and writes, e.g. ['modes','planning','main','model']. */
|
|
125
|
+
keyPath: readonly string[];
|
|
126
|
+
/** One line of help under the field. */
|
|
127
|
+
detail?: string;
|
|
128
|
+
/** Shown while the field is unset, in place of a value. */
|
|
129
|
+
placeholder?: string;
|
|
130
|
+
/** The closed set a 'select' offers, when the plugin knows it. */
|
|
131
|
+
options?: readonly SettingsFieldOption[];
|
|
132
|
+
/**
|
|
133
|
+
* An option set the host supplies instead. A model picker needs the machine's
|
|
134
|
+
* authenticated models, which no plugin can enumerate from the browser; a
|
|
135
|
+
* host that cannot answer leaves the field as free text.
|
|
136
|
+
*/
|
|
137
|
+
optionsFrom?: 'models';
|
|
138
|
+
}
|
|
139
|
+
interface SettingsFieldOption {
|
|
140
|
+
value: string;
|
|
141
|
+
label: string;
|
|
142
|
+
/** Groups entries under a heading, as providers group their models. */
|
|
143
|
+
group?: string;
|
|
144
|
+
}
|
|
145
|
+
/** One repository the host has admitted into repository-scoped settings. */
|
|
146
|
+
interface RepositorySettingsRepository {
|
|
147
|
+
/** Opaque hub-issued identity. Package APIs resolve this through DoomApiContext. */
|
|
148
|
+
id: string;
|
|
149
|
+
/** Last path segment, for compact pickers and headings. */
|
|
150
|
+
name: string;
|
|
151
|
+
/** Display-only absolute path. APIs must use id rather than trusting this value. */
|
|
152
|
+
path: string;
|
|
153
|
+
/** True while at least one session is open inside this repository. */
|
|
154
|
+
active: boolean;
|
|
155
|
+
}
|
|
156
|
+
/** Props for a package panel placed inside the host's repository control plane. */
|
|
157
|
+
interface RepositorySettingsPanelProps {
|
|
158
|
+
repository: RepositorySettingsRepository | null;
|
|
159
|
+
/** Same-origin request routed through the host's sealed transport when remote. */
|
|
160
|
+
request: (input: string, init?: RequestInit) => Promise<Response>;
|
|
161
|
+
/** The same transport, retrying once after the host's fresh passkey gesture. */
|
|
162
|
+
requestWithStepUp: (input: string, init?: RequestInit) => Promise<Response>;
|
|
163
|
+
}
|
|
164
|
+
/** One package-owned panel below the host's repository selection controls. */
|
|
165
|
+
interface RepositorySettingsPanelContribution {
|
|
166
|
+
label: string;
|
|
167
|
+
detail: string;
|
|
168
|
+
/** Sort position below the host controls; lower first, then plugin id. */
|
|
169
|
+
order?: number;
|
|
170
|
+
component: ComponentType<RepositorySettingsPanelProps>;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A page in cockpit settings, contributed by the package that owns the
|
|
174
|
+
* settings on it. The id is the URL segment (/settings/:id), so it is unique
|
|
175
|
+
* across plugins; a collision is an install diagnostic and the first wins.
|
|
176
|
+
*/
|
|
177
|
+
interface SettingsSectionContribution {
|
|
178
|
+
id: string;
|
|
179
|
+
label: string;
|
|
180
|
+
detail: string;
|
|
181
|
+
/** Sort position in the settings menu; lower first, id breaks ties. */
|
|
182
|
+
order?: number;
|
|
183
|
+
fields: readonly SettingsFieldContribution[];
|
|
184
|
+
}
|
|
108
185
|
interface SurfaceContribution {
|
|
109
186
|
id: string;
|
|
110
187
|
component: ComponentType<WebPluginSlotProps>;
|
|
@@ -214,9 +291,31 @@ interface ActivityGroupContribution {
|
|
|
214
291
|
widgetKeys?: string[];
|
|
215
292
|
/** The plugin tab the group's key chip opens; without one the chip is a plain label. */
|
|
216
293
|
tab?: string;
|
|
294
|
+
/**
|
|
295
|
+
* Drop the group entirely while its status key is present but empty, rather
|
|
296
|
+
* than showing a header over "idle".
|
|
297
|
+
*
|
|
298
|
+
* A session publishes a status key once and clears it by publishing nothing,
|
|
299
|
+
* which reaches the page as an empty string, so a group that has reported at
|
|
300
|
+
* all can never stop reporting. For a group whose whole content is the thing
|
|
301
|
+
* the session is doing (a goal, a recording, the files it changed), the
|
|
302
|
+
* header alone is a row that says nothing, and a dock of those buries the
|
|
303
|
+
* groups that do. A group that is also a way in, with a launcher or a tab,
|
|
304
|
+
* leaves this off: its frame is worth keeping when it is idle.
|
|
305
|
+
*/
|
|
306
|
+
hideWhenEmpty?: boolean;
|
|
307
|
+
/** Keeps the group visible below the dock's scrolling ordinary groups. */
|
|
308
|
+
placement?: 'bottom';
|
|
217
309
|
/** Sort position in the dock; lower first, name breaks ties. */
|
|
218
310
|
order?: number;
|
|
219
311
|
}
|
|
312
|
+
/** Page-wide plugin state shared by its runtime, channels, and every rendered contribution. */
|
|
313
|
+
interface GlobalStore<T> {
|
|
314
|
+
readonly store: Store<T>;
|
|
315
|
+
/** Replaces the value; an updater returning the current value publishes nothing. */
|
|
316
|
+
update(updater: (current: T) => T): void;
|
|
317
|
+
reset(): void;
|
|
318
|
+
}
|
|
220
319
|
/**
|
|
221
320
|
* One session-scoped data channel: the hub pushes ChannelFrame payloads whose
|
|
222
321
|
* frame type equals `channel`; parse is the validation gate at the boundary
|
|
@@ -289,6 +388,51 @@ interface ToolMessageRenderProps extends WebPluginSlotProps {
|
|
|
289
388
|
running: boolean;
|
|
290
389
|
isError: boolean;
|
|
291
390
|
}
|
|
391
|
+
/** The extension UI request a running tool is blocked on, as its prompt sees it. */
|
|
392
|
+
interface ToolPromptDialog {
|
|
393
|
+
id: string;
|
|
394
|
+
method: 'select' | 'confirm' | 'input' | 'editor';
|
|
395
|
+
title: string;
|
|
396
|
+
message: string;
|
|
397
|
+
options: readonly string[];
|
|
398
|
+
placeholder: string;
|
|
399
|
+
prefill: string;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Everything a tool's composer prompt receives: the timeline item's props,
|
|
403
|
+
* plus the request the tool is waiting on and the two ways to settle it.
|
|
404
|
+
*
|
|
405
|
+
* The dialog frame is deliberately thin, because Pi's own is: a select
|
|
406
|
+
* carries a title and a list of labels and nothing else. A prompt that needs
|
|
407
|
+
* more renders it from `args`, which is the whole call the tool was made
|
|
408
|
+
* with, and answers with whatever its session half agreed to read back.
|
|
409
|
+
*/
|
|
410
|
+
interface ToolPromptRenderProps extends ToolMessageRenderProps {
|
|
411
|
+
dialog: ToolPromptDialog;
|
|
412
|
+
/** Answers the request and unblocks the tool. Bound, so a prompt may destructure it. */
|
|
413
|
+
answer: (value: string) => void;
|
|
414
|
+
/** The reader declined; the tool sees a cancellation. */
|
|
415
|
+
cancel: () => void;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* A tool's stand-in for the composer input, shown while the tool is running
|
|
419
|
+
* and holding an extension UI request.
|
|
420
|
+
*
|
|
421
|
+
* A question the agent is blocked on belongs where the reader is already
|
|
422
|
+
* looking and typing, not behind a modal covering the conversation they need
|
|
423
|
+
* in order to answer. While a prompt is up the host's own dialog stands down,
|
|
424
|
+
* so exactly one surface owns the request.
|
|
425
|
+
*/
|
|
426
|
+
interface ToolPromptContribution {
|
|
427
|
+
/**
|
|
428
|
+
* True when this tool owns the open request. Without it the tool claims any
|
|
429
|
+
* request open while it runs, which is wrong the moment a second extension
|
|
430
|
+
* asks something during the same turn; a request it refuses falls back to
|
|
431
|
+
* the host's dialog.
|
|
432
|
+
*/
|
|
433
|
+
claims?(dialog: ToolPromptDialog, args: Record<string, unknown>): boolean;
|
|
434
|
+
component: ComponentType<ToolPromptRenderProps>;
|
|
435
|
+
}
|
|
292
436
|
/**
|
|
293
437
|
* The timeline item for the tools a package registers, the web half of the
|
|
294
438
|
* TUI's renderCall/renderResult with Pi's renderShell 'self': one `message`
|
|
@@ -306,11 +450,14 @@ interface ToolRendererContribution {
|
|
|
306
450
|
*/
|
|
307
451
|
matches?(toolName: string, statuses: Readonly<Record<string, string>>): boolean;
|
|
308
452
|
message: ComponentType<ToolMessageRenderProps>;
|
|
453
|
+
/** Stands in for the composer input while this tool runs and holds a request. */
|
|
454
|
+
prompt?: ToolPromptContribution;
|
|
309
455
|
}
|
|
310
|
-
/** What a plugin's optional runtime may do
|
|
456
|
+
/** What a plugin's optional runtime may do through the page's hub socket. */
|
|
311
457
|
interface WebPluginRuntime {
|
|
312
458
|
sendSessionFrame: SessionFrameSender;
|
|
313
459
|
sendHubFrame(frame: Record<string, unknown>): void;
|
|
460
|
+
onHubConnected(listener: () => void): () => void;
|
|
314
461
|
}
|
|
315
462
|
interface WebPluginDefinition {
|
|
316
463
|
id: string;
|
|
@@ -324,6 +471,8 @@ interface WebPluginDefinition {
|
|
|
324
471
|
leaderBindings?: LeaderBindingContribution[];
|
|
325
472
|
railSections?: SurfaceContribution[];
|
|
326
473
|
selectionBarItems?: SurfaceContribution[];
|
|
474
|
+
/** Compact controls placed in the mobile composer's action row, immediately before queue. */
|
|
475
|
+
composerActions?: SurfaceContribution[];
|
|
327
476
|
toolRenderers?: ToolRendererContribution[];
|
|
328
477
|
/**
|
|
329
478
|
* A section whose id names an activity group any plugin declares renders
|
|
@@ -331,6 +480,16 @@ interface WebPluginDefinition {
|
|
|
331
480
|
* one-line summary; any other section renders after the groups.
|
|
332
481
|
*/
|
|
333
482
|
activitySections?: SurfaceContribution[];
|
|
483
|
+
/**
|
|
484
|
+
* Pages this plugin adds to cockpit settings. The host renders the fields and
|
|
485
|
+
* owns the scope switch, the repository picker, and every write.
|
|
486
|
+
*/
|
|
487
|
+
settingsSections?: SettingsSectionContribution[];
|
|
488
|
+
/**
|
|
489
|
+
* A package-owned management panel inside the host's repository page. The
|
|
490
|
+
* host owns repository identity and passes only repositories it admitted.
|
|
491
|
+
*/
|
|
492
|
+
repositorySettingsPanel?: RepositorySettingsPanelContribution;
|
|
334
493
|
/** The slots this plugin opens for others, each named '<this plugin id>.<name>'. */
|
|
335
494
|
slots?: SlotDeclaration[];
|
|
336
495
|
/** This plugin's contributions into slots other plugins (or the host) declare. */
|
|
@@ -339,5 +498,5 @@ interface WebPluginDefinition {
|
|
|
339
498
|
start?(runtime: WebPluginRuntime): (() => void) | void;
|
|
340
499
|
}
|
|
341
500
|
//#endregion
|
|
342
|
-
export { ActivityGroupContribution, LeaderBindingBase, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ToolMessageRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps };
|
|
501
|
+
export { ActivityGroupContribution, GlobalStore, LeaderBindingBase, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, RepositorySettingsPanelContribution, RepositorySettingsPanelProps, RepositorySettingsRepository, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SettingsFieldContribution, SettingsFieldOption, SettingsSectionContribution, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ToolMessageRenderProps, ToolPromptContribution, ToolPromptDialog, ToolPromptRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps };
|
|
343
502
|
//# sourceMappingURL=webPlugin.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webPlugin.d.mts","names":[],"sources":["../../src/types/webPlugin.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"webPlugin.d.mts","names":[],"sources":["../../src/types/webPlugin.ts"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-web-contracts",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.5",
|
|
4
4
|
"description": "Web cockpit plugin contracts for DoomPi: plugin definitions, slot contributions, session data channels, and hub channel sources.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|