@agimon-ai/doompi-web-contracts 0.0.1-alpha.11

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 (75) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +47 -0
  3. package/dist/index.cjs +1 -0
  4. package/dist/index.d.cts +7 -0
  5. package/dist/index.d.mts +7 -0
  6. package/dist/index.mjs +1 -0
  7. package/dist/services/define.cjs +2 -0
  8. package/dist/services/define.cjs.map +1 -0
  9. package/dist/services/define.d.cts +20 -0
  10. package/dist/services/define.d.cts.map +1 -0
  11. package/dist/services/define.d.mts +20 -0
  12. package/dist/services/define.d.mts.map +1 -0
  13. package/dist/services/define.mjs +2 -0
  14. package/dist/services/define.mjs.map +1 -0
  15. package/dist/services/globalStore.cjs +2 -0
  16. package/dist/services/globalStore.cjs.map +1 -0
  17. package/dist/services/globalStore.d.cts +7 -0
  18. package/dist/services/globalStore.d.cts.map +1 -0
  19. package/dist/services/globalStore.d.mts +7 -0
  20. package/dist/services/globalStore.d.mts.map +1 -0
  21. package/dist/services/globalStore.mjs +2 -0
  22. package/dist/services/globalStore.mjs.map +1 -0
  23. package/dist/services/sessionStore.cjs +2 -0
  24. package/dist/services/sessionStore.cjs.map +1 -0
  25. package/dist/services/sessionStore.d.cts +15 -0
  26. package/dist/services/sessionStore.d.cts.map +1 -0
  27. package/dist/services/sessionStore.d.mts +15 -0
  28. package/dist/services/sessionStore.d.mts.map +1 -0
  29. package/dist/services/sessionStore.mjs +2 -0
  30. package/dist/services/sessionStore.mjs.map +1 -0
  31. package/dist/services/testing/channels.cjs +2 -0
  32. package/dist/services/testing/channels.cjs.map +1 -0
  33. package/dist/services/testing/channels.d.cts +52 -0
  34. package/dist/services/testing/channels.d.cts.map +1 -0
  35. package/dist/services/testing/channels.d.mts +52 -0
  36. package/dist/services/testing/channels.d.mts.map +1 -0
  37. package/dist/services/testing/channels.mjs +2 -0
  38. package/dist/services/testing/channels.mjs.map +1 -0
  39. package/dist/services/testing/render.cjs +2 -0
  40. package/dist/services/testing/render.cjs.map +1 -0
  41. package/dist/services/testing/render.d.cts +32 -0
  42. package/dist/services/testing/render.d.cts.map +1 -0
  43. package/dist/services/testing/render.d.mts +32 -0
  44. package/dist/services/testing/render.d.mts.map +1 -0
  45. package/dist/services/testing/render.mjs +2 -0
  46. package/dist/services/testing/render.mjs.map +1 -0
  47. package/dist/services/testing/slotProps.cjs +2 -0
  48. package/dist/services/testing/slotProps.cjs.map +1 -0
  49. package/dist/services/testing/slotProps.d.cts +59 -0
  50. package/dist/services/testing/slotProps.d.cts.map +1 -0
  51. package/dist/services/testing/slotProps.d.mts +59 -0
  52. package/dist/services/testing/slotProps.d.mts.map +1 -0
  53. package/dist/services/testing/slotProps.mjs +2 -0
  54. package/dist/services/testing/slotProps.mjs.map +1 -0
  55. package/dist/services/toolResult.cjs +7 -0
  56. package/dist/services/toolResult.cjs.map +1 -0
  57. package/dist/services/toolResult.d.cts +15 -0
  58. package/dist/services/toolResult.d.cts.map +1 -0
  59. package/dist/services/toolResult.d.mts +15 -0
  60. package/dist/services/toolResult.d.mts.map +1 -0
  61. package/dist/services/toolResult.mjs +7 -0
  62. package/dist/services/toolResult.mjs.map +1 -0
  63. package/dist/testing.cjs +1 -0
  64. package/dist/testing.d.cts +4 -0
  65. package/dist/testing.d.mts +4 -0
  66. package/dist/testing.mjs +1 -0
  67. package/dist/types/webHub.d.cts +77 -0
  68. package/dist/types/webHub.d.cts.map +1 -0
  69. package/dist/types/webHub.d.mts +77 -0
  70. package/dist/types/webHub.d.mts.map +1 -0
  71. package/dist/types/webPlugin.d.cts +552 -0
  72. package/dist/types/webPlugin.d.cts.map +1 -0
  73. package/dist/types/webPlugin.d.mts +552 -0
  74. package/dist/types/webPlugin.d.mts.map +1 -0
  75. package/package.json +85 -0
@@ -0,0 +1,552 @@
1
+ import { Store } from "@tanstack/store";
2
+ import { ComponentType, ReactNode } from "react";
3
+ //#region src/types/webPlugin.d.ts
4
+ /**
5
+ * The client half of the DoomPi web plugin contract.
6
+ *
7
+ * A plugin package exports one `webPlugin: WebPluginDefinition` from its
8
+ * declared client entry. The cockpit's bundler compiles that entry into the
9
+ * host bundle, so a plugin's client code may import only react,
10
+ * @tanstack/store, @tanstack/react-store, this contract,
11
+ * @agimon-ai/doompi-web-components, and the package's own web/ and src/types
12
+ * modules; never another plugin, a node builtin, or a server framework, which
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.
18
+ *
19
+ * Plugins are independent: none depends on another, any of them may be added
20
+ * or removed at the next sync, and every relation between two plugins (a fill
21
+ * into a slot, a section inside an activity group) resolves by name once every
22
+ * plugin is installed. The manifest's registrationOrder is only a tiebreak,
23
+ * and a collision between two plugins is an install diagnostic, never a
24
+ * failure.
25
+ */
26
+ /** Sends one command frame to a session's agent on the page's hub socket. */
27
+ type SessionFrameSender = (sessionId: string, frame: Record<string, unknown>) => void;
28
+ /**
29
+ * A slot a plugin opens inside its own UI for independent plugins to fill.
30
+ * The name is namespaced by its owner, '<pluginId>.<name>', so no two plugins
31
+ * open the same slot. A slot with a parse gate takes data fills, declared as
32
+ * data the owner renders; one without takes component fills the owner places
33
+ * with renderSlot. Either side may be absent: the owner renders with zero
34
+ * fills, and a fill into a slot no installed plugin declares is an install
35
+ * diagnostic, never a failure.
36
+ */
37
+ interface SlotDeclaration<Data = unknown> {
38
+ slot: string;
39
+ /** The gate for data fills, run once at install; null rejects the fill with a diagnostic. */
40
+ parse?(input: unknown): Data | null;
41
+ }
42
+ /** A data fill as its owner reads it back, already through the parse gate. */
43
+ interface SlotDataFill<Data = unknown> {
44
+ pluginId: string;
45
+ id: string;
46
+ order: number;
47
+ data: Data;
48
+ }
49
+ /**
50
+ * A tab a plugin opens at runtime for one session, beside the declared ones:
51
+ * the reader closes it, and it goes with the session or the page. The host
52
+ * keeps the panel it was opened with, so opening the same id again only
53
+ * focuses the tab.
54
+ */
55
+ interface TransientTab {
56
+ /** Unique across plugins and URL-safe: '<pluginId>-<name>-<key>'. */
57
+ id: string;
58
+ label: string;
59
+ panel: ComponentType<WebPluginSlotProps>;
60
+ }
61
+ /**
62
+ * Which paths a message names can be opened, and what tab each one opens.
63
+ *
64
+ * The host renders the conversation but knows nothing about files: the package
65
+ * that receives the session's file reports holds the set, and it owns the
66
+ * panel a file opens in. So the host asks this source about each path-shaped
67
+ * token it finds, and links only the ones the source claims. A token it does
68
+ * not recognise stays plain text, which is what keeps a class name or a glob
69
+ * from becoming a dead link.
70
+ */
71
+ interface FileLinkSource {
72
+ /** Notifies while a message is on screen and the linkable set changes. */
73
+ subscribe(listener: () => void): () => void;
74
+ /** Changes only when the session's linkable set does; the host re-reads on a change. */
75
+ fingerprint(sessionId: string | null): string;
76
+ /** The tab this path opens, or undefined when the source does not recognise it. */
77
+ resolve(sessionId: string | null, path: string): TransientTab | undefined;
78
+ }
79
+ /**
80
+ * How a thread is drawn where it is not the whole surface: a card body wants
81
+ * the last few entries at card scale, not a full transcript with its own
82
+ * paging. Omitting both draws the thread exactly as the conversation does.
83
+ */
84
+ interface ThreadViewOptions {
85
+ /** Draw only the newest entries, at most this many. */
86
+ limit?: number;
87
+ /** Card scale: tighter spacing, no history paging, no jump control. */
88
+ compact?: boolean;
89
+ }
90
+ /** Every slot component receives the focused session; null while nothing is focused. */
91
+ interface WebPluginSlotProps {
92
+ sessionId: string | null;
93
+ /** Host navigation for the focused session; null returns to the conversation tab. */
94
+ openTab: (tabId: string | null) => void;
95
+ /** Opens the tab for the focused session, or focuses it when one with the same id is already open. */
96
+ openTransientTab: (tab: TransientTab) => void;
97
+ closeTransientTab: (tabId: string) => void;
98
+ /**
99
+ * The host's live conversation view of one thread of the focused session,
100
+ * rendered like the session's own timeline and subscribed while mounted. A
101
+ * plugin's hub source names the thread's journal (HubChannelSource.threadJournal).
102
+ */
103
+ renderThread: (threadId: string, options?: ThreadViewOptions) => ReactNode;
104
+ /** Appends text to the focused session's current composer draft. Bound, so a component may destructure it. */
105
+ appendComposerDraft: (text: string) => void;
106
+ /** The same sender palette commands and `start` receive; components act through it. */
107
+ sendSessionFrame: SessionFrameSender;
108
+ /** The component fills of one slot, in slot order; the host resolves them, so this contract holds no state. */
109
+ renderSlot: (slot: string) => ReactNode;
110
+ /** The data fills of one slot, typed by the declaration handle only its owner holds. */
111
+ slotData: <Data>(slot: SlotDeclaration<Data>) => readonly SlotDataFill<Data>[];
112
+ /**
113
+ * The footer statuses the focused session has published, raw, keyed as the
114
+ * publishing extension named them. A package reads its own key: the status
115
+ * line is the only thing some modes report, and a plugin that renders its
116
+ * own surface needs the same facts the host folds into the selection bar.
117
+ */
118
+ statuses: Readonly<Record<string, string>>;
119
+ }
120
+ /**
121
+ * One contribution into a slot, keyed by (pluginId, id): independent plugins
122
+ * never collide on an id. A component fill renders where the owner places the
123
+ * slot; a data fill is what the owner's parse gate reads.
124
+ */
125
+ interface SlotFillContribution {
126
+ slot: string;
127
+ id: string;
128
+ /** Sort position within the slot; lower first, then pluginId, then id. */
129
+ order?: number;
130
+ data?: unknown;
131
+ component?: ComponentType<WebPluginSlotProps>;
132
+ }
133
+ interface TabContribution {
134
+ /** URL segment (/session/:id/:tabId) and testid suffix (tab-<id>, tab-<id>-count). */
135
+ id: string;
136
+ label: string;
137
+ panel: ComponentType<WebPluginSlotProps>;
138
+ /** A React hook, fully typed inside the plugin; 0 hides the badge. */
139
+ useBadge?: (sessionId: string | null) => number;
140
+ }
141
+ /**
142
+ * One field on a settings page, declared as data.
143
+ *
144
+ * Settings are data rather than a component because the host owns what a
145
+ * plugin cannot: which file an edit lands in. A page has one scope switch and
146
+ * one repository, and every field on it reports where its value came from and
147
+ * whether the selected scope may hold it. A plugin drawing its own form would
148
+ * have to reproduce all of that, and each would get it slightly different.
149
+ */
150
+ interface SettingsFieldContribution {
151
+ /** Unique within the section; also the testid suffix. */
152
+ id: string;
153
+ label: string;
154
+ kind: 'text' | 'select' | 'toggle' | 'info';
155
+ /** The config key this reads and writes, e.g. ['modes','planning','main','model']. */
156
+ keyPath: readonly string[];
157
+ /** One line of help under the field. */
158
+ detail?: string;
159
+ /** Shown while the field is unset, in place of a value. */
160
+ placeholder?: string;
161
+ /** The closed set a 'select' offers, when the plugin knows it. */
162
+ options?: readonly SettingsFieldOption[];
163
+ /**
164
+ * An option set the host supplies instead. A model picker needs the machine's
165
+ * authenticated models, which no plugin can enumerate from the browser; a
166
+ * host that cannot answer leaves the field as free text.
167
+ */
168
+ optionsFrom?: 'models';
169
+ }
170
+ interface SettingsFieldOption {
171
+ value: string;
172
+ label: string;
173
+ /** Groups entries under a heading, as providers group their models. */
174
+ group?: string;
175
+ }
176
+ /** One repository the host has admitted into repository-scoped settings. */
177
+ interface RepositorySettingsRepository {
178
+ /** Opaque hub-issued identity. Package APIs resolve this through DoomApiContext. */
179
+ id: string;
180
+ /** Last path segment, for compact pickers and headings. */
181
+ name: string;
182
+ /** Display-only absolute path. APIs must use id rather than trusting this value. */
183
+ path: string;
184
+ /** True while at least one session is open inside this repository. */
185
+ active: boolean;
186
+ }
187
+ /** Props for a package panel placed inside the host's repository control plane. */
188
+ interface RepositorySettingsPanelProps {
189
+ repository: RepositorySettingsRepository | null;
190
+ /** Same-origin request routed through the host's sealed transport when remote. */
191
+ request: (input: string, init?: RequestInit) => Promise<Response>;
192
+ /** The same transport, retrying once after the host's fresh passkey gesture. */
193
+ requestWithStepUp: (input: string, init?: RequestInit) => Promise<Response>;
194
+ }
195
+ /** One package-owned panel below the host's repository selection controls. */
196
+ interface RepositorySettingsPanelContribution {
197
+ label: string;
198
+ detail: string;
199
+ /** Sort position below the host controls; lower first, then plugin id. */
200
+ order?: number;
201
+ component: ComponentType<RepositorySettingsPanelProps>;
202
+ }
203
+ /**
204
+ * A page in cockpit settings, contributed by the package that owns the
205
+ * settings on it. The id is the URL segment (/settings/:id), so it is unique
206
+ * across plugins; a collision is an install diagnostic and the first wins.
207
+ */
208
+ interface SettingsSectionContribution {
209
+ id: string;
210
+ label: string;
211
+ detail: string;
212
+ /** Sort position in the settings menu; lower first, id breaks ties. */
213
+ order?: number;
214
+ fields: readonly SettingsFieldContribution[];
215
+ }
216
+ interface SurfaceContribution {
217
+ id: string;
218
+ component: ComponentType<WebPluginSlotProps>;
219
+ }
220
+ interface PaletteCommandContext {
221
+ sessionId: string | null;
222
+ /** Host navigation; null returns to the conversation tab. */
223
+ openTab(tabId: string | null): void;
224
+ sendSessionFrame: SessionFrameSender;
225
+ }
226
+ interface PaletteCommandContribution {
227
+ id: string;
228
+ title: string;
229
+ description?: string;
230
+ run(context: PaletteCommandContext): void;
231
+ }
232
+ /**
233
+ * One step of a Leader Space key path: the key pressed and the label the
234
+ * menu shows beside it. Keys are one lowercase letter or digit, the same
235
+ * alphabet the TUI's leader registry accepts.
236
+ */
237
+ interface LeaderKeyContribution {
238
+ key: string;
239
+ label: string;
240
+ detail?: string;
241
+ }
242
+ interface LeaderBindingBase {
243
+ id: string;
244
+ /** The SPC path, group segments first; the last segment is the key that fires. */
245
+ path: LeaderKeyContribution[];
246
+ }
247
+ /**
248
+ * A Leader Space binding, the cockpit's half of the TUI's leader contract.
249
+ *
250
+ * The session's own leader tree never reaches an RPC client, so each package
251
+ * declares here the paths its TUI documents that a browser can honor: a slash
252
+ * command line the host sends through the prompt channel (without the leading
253
+ * slash), or a client action such as opening the plugin's tab. Plugins that
254
+ * share a group prefix (SPC w) word it the same way; the first to register a
255
+ * segment names it, and a later binding on an already-bound leaf takes it
256
+ * over. Either disagreement between two plugins is an install diagnostic.
257
+ */
258
+ type LeaderBindingContribution = (LeaderBindingBase & {
259
+ command: string;
260
+ }) | (LeaderBindingBase & {
261
+ run(context: PaletteCommandContext): void;
262
+ });
263
+ /**
264
+ * A minor mode's presence in the cockpit, declared as data rather than a
265
+ * component: the host's selection bar renders the list, folding in what the
266
+ * session reports. A mode with neither signal key shows as unavailable until
267
+ * its package publishes one.
268
+ */
269
+ interface MinorModeContribution {
270
+ name: string;
271
+ /**
272
+ * The catalog mode this row drives, when the runtime registers it under a
273
+ * different id than the row shows. A package whose leader key drives one of
274
+ * several modes it owns needs this: the row must reach the same mode the
275
+ * key does, not the one that happens to share the row's label.
276
+ */
277
+ modeId?: string;
278
+ /** Leader Space key path, as the TUI documents it. */
279
+ keys: string;
280
+ /** Footer status key whose presence and content report availability and detail. */
281
+ statusKey?: string;
282
+ /** Widget key whose presence reports the mode as installed but off. */
283
+ widgetKey?: string;
284
+ /** Sort position in the selection bar list; lower first, name breaks ties. */
285
+ order?: number;
286
+ }
287
+ /**
288
+ * A selection-bar axis, declared as data: the host renders the chip and
289
+ * routes its click through the axis's slash command. The axis shows only
290
+ * while the session publishes the status key; the content is the current
291
+ * selection, and emptyLabel shows while it is published empty.
292
+ */
293
+ interface SelectionAxisContribution {
294
+ /** Chip identity: testid axis-<name> and the popover menu it claims. */
295
+ name: string;
296
+ /** Slash command the host runs when the chip is clicked. */
297
+ command: string;
298
+ /** Footer status key: absent hides the axis, content is the selection. */
299
+ statusKey: string;
300
+ /** Shown while the status is published with nothing selected. */
301
+ emptyLabel: string;
302
+ /** The status content is a comma-separated list: several selections can be active at once. */
303
+ multi?: boolean;
304
+ /** Sort position in the bar; lower first, name breaks ties. */
305
+ order?: number;
306
+ }
307
+ /** Reactive session activity owned by a plugin's session-scoped store. */
308
+ interface ActivityGroupActiveSource {
309
+ subscribe(listener: () => void): () => void;
310
+ isActive(sessionId: string | null): boolean;
311
+ }
312
+ /**
313
+ * An activity-dock group, declared as data. The host renders its frame when the
314
+ * session publishes a footer or widget signal, or whenever the contribution
315
+ * owns an active source. The latter keeps an idle group's launcher available.
316
+ * The body is the status content as a one-line summary unless some plugin fills
317
+ * the group's slot, `activity.<name>`, with a section of the same name.
318
+ */
319
+ interface ActivityGroupContribution {
320
+ name: string;
321
+ /** Leader Space key path, as the TUI documents it. */
322
+ keys: string;
323
+ /** Footer status key whose presence shows the group and content fills its summary. */
324
+ statusKey?: string;
325
+ /** Widget keys any of which shows the group without a summary. */
326
+ widgetKeys?: string[];
327
+ /**
328
+ * Widget keys that mean this session has active background work.
329
+ *
330
+ * Defaults to every widget key. Ignored when activeSource is present.
331
+ */
332
+ activeWidgetKeys?: string[];
333
+ /** Session-scoped activity state. Its presence also keeps the group visible while idle. */
334
+ activeSource?: ActivityGroupActiveSource;
335
+ /** Whether a non-empty signal represents background work. Defaults to true. */
336
+ marksBackgroundWork?: boolean;
337
+ /** The plugin tab the group's key chip opens; without one the chip is a plain label. */
338
+ tab?: string;
339
+ /**
340
+ * Drop the group entirely while its status key is present but empty, rather
341
+ * than showing a header over "idle".
342
+ *
343
+ * A session publishes a status key once and clears it by publishing nothing,
344
+ * which reaches the page as an empty string, so a group that has reported at
345
+ * all can never stop reporting. For a group whose whole content is the thing
346
+ * the session is doing (a goal, a recording, the files it changed), the
347
+ * header alone is a row that says nothing, and a dock of those buries the
348
+ * groups that do. A group that is also a way in, with a launcher or a tab,
349
+ * leaves this off: its frame is worth keeping when it is idle.
350
+ */
351
+ hideWhenEmpty?: boolean;
352
+ /** Keeps the group visible below the dock's scrolling ordinary groups. */
353
+ placement?: 'bottom';
354
+ /** Sort position in the dock; lower first, name breaks ties. */
355
+ order?: number;
356
+ }
357
+ /** Page-wide plugin state shared by its runtime, channels, and every rendered contribution. */
358
+ interface GlobalStore<T> {
359
+ readonly store: Store<T>;
360
+ /** Replaces the value; an updater returning the current value publishes nothing. */
361
+ update(updater: (current: T) => T): void;
362
+ reset(): void;
363
+ }
364
+ /**
365
+ * One session-scoped data channel: the hub pushes ChannelFrame payloads whose
366
+ * frame type equals `channel`; parse is the validation gate at the boundary
367
+ * (null rejects); drop clears the plugin's per-session state.
368
+ */
369
+ interface SessionChannelContribution<Payload = unknown> {
370
+ channel: string;
371
+ parse(input: unknown): Payload | null;
372
+ apply(sessionId: string, payload: Payload): void;
373
+ drop(sessionId: string): void;
374
+ }
375
+ /** One record per session id; a missing key means the session has reported nothing. */
376
+ type SessionRecords<T> = Partial<Record<string, T>>;
377
+ /** A channel folded straight into a session store: parse gates the wire, reduce folds one payload into the record. */
378
+ interface SessionStoreChannel<T, Payload> {
379
+ channel: string;
380
+ parse(input: unknown): Payload | null;
381
+ /** Folds one payload into the session's record; ephemeral fields (stop requests, dismissals) reconcile here. */
382
+ reduce(current: T, payload: Payload): T;
383
+ }
384
+ /**
385
+ * Per-session plugin state, the shape every plugin's store takes: one record
386
+ * per session, shared by the plugin's tab, badge, sections, and channels.
387
+ * Records are immutable values; updaters and reducers return new ones.
388
+ */
389
+ interface SessionStore<T> {
390
+ readonly store: Store<SessionRecords<T>>;
391
+ /**
392
+ * The session's record, or the one shared empty record for null and unknown
393
+ * sessions: a stable reference, so a useStore selector over it never
394
+ * re-renders on an unchanged session.
395
+ */
396
+ select(state: SessionRecords<T>, sessionId: string | null): T;
397
+ /** Replaces the session's record; an updater returning the current record publishes nothing. */
398
+ update(sessionId: string, updater: (current: T) => T): void;
399
+ drop(sessionId: string): void;
400
+ reset(): void;
401
+ /** A session channel whose apply and drop are already wired to this store. */
402
+ channel<Payload>(options: SessionStoreChannel<T, Payload>): SessionChannelContribution;
403
+ }
404
+ /**
405
+ * A tool result as Pi's tool_execution frames carry it: the content blocks
406
+ * the model sees and the structured `details` the tool attached for its own
407
+ * renderer. Both are wire JSON; the plugin that owns the tool narrows them.
408
+ */
409
+ interface ToolResultView {
410
+ content: unknown[];
411
+ details: unknown;
412
+ }
413
+ /**
414
+ * Everything a tool's timeline item receives: the actions every plugin
415
+ * component gets, plus the call and its newest result. The component owns
416
+ * the whole item, its frame, header, body, and expand state included; the
417
+ * host only wraps it in the timeline row and catches a throw. Compose it
418
+ * from the shared components package's MessageItem so it looks like every
419
+ * other item, the host's own fallback included.
420
+ */
421
+ interface ToolMessageRenderProps extends WebPluginSlotProps {
422
+ toolCallId: string;
423
+ /** The wire name, as registered with Pi (registerTool's `name`). */
424
+ toolName: string;
425
+ args: Record<string, unknown>;
426
+ /** The session's footer statuses at render time, the same picture `matches` saw. */
427
+ statuses: Readonly<Record<string, string>>;
428
+ /** The newest result: partial while the tool runs, final once it ends; null before any output. */
429
+ result: ToolResultView | null;
430
+ /** The result's text blocks joined, which is what the host's fallback item shows. */
431
+ output: string;
432
+ /** True while the tool still runs, so `result` is a partial one. */
433
+ running: boolean;
434
+ isError: boolean;
435
+ }
436
+ /** The extension UI request a running tool is blocked on, as its prompt sees it. */
437
+ interface ToolPromptDialog {
438
+ id: string;
439
+ method: 'select' | 'confirm' | 'input' | 'editor';
440
+ title: string;
441
+ message: string;
442
+ options: readonly string[];
443
+ placeholder: string;
444
+ prefill: string;
445
+ }
446
+ /**
447
+ * Everything a tool's composer prompt receives: the timeline item's props,
448
+ * plus the request the tool is waiting on and the two ways to settle it.
449
+ *
450
+ * The dialog frame is deliberately thin, because Pi's own is: a select
451
+ * carries a title and a list of labels and nothing else. A prompt that needs
452
+ * more renders it from `args`, which is the whole call the tool was made
453
+ * with, and answers with whatever its session half agreed to read back.
454
+ */
455
+ interface ToolPromptRenderProps extends ToolMessageRenderProps {
456
+ dialog: ToolPromptDialog;
457
+ /** Answers the request and unblocks the tool. Bound, so a prompt may destructure it. */
458
+ answer: (value: string) => void;
459
+ /** The reader declined; the tool sees a cancellation. */
460
+ cancel: () => void;
461
+ }
462
+ /**
463
+ * A tool's stand-in for the composer input, shown while the tool is running
464
+ * and holding an extension UI request.
465
+ *
466
+ * A question the agent is blocked on belongs where the reader is already
467
+ * looking and typing, not behind a modal covering the conversation they need
468
+ * in order to answer. While a prompt is up the host's own dialog stands down,
469
+ * so exactly one surface owns the request.
470
+ */
471
+ interface ToolPromptContribution {
472
+ /**
473
+ * True when this tool owns the open request. Without it the tool claims any
474
+ * request open while it runs, which is wrong the moment a second extension
475
+ * asks something during the same turn; a request it refuses falls back to
476
+ * the host's dialog.
477
+ */
478
+ claims?(dialog: ToolPromptDialog, args: Record<string, unknown>): boolean;
479
+ component: ComponentType<ToolPromptRenderProps>;
480
+ }
481
+ /**
482
+ * The timeline item for the tools a package registers, the web half of the
483
+ * TUI's renderCall/renderResult with Pi's renderShell 'self': one `message`
484
+ * component per claimed tool owns the whole item. One tool name belongs to
485
+ * one renderer.
486
+ */
487
+ interface ToolRendererContribution {
488
+ /** Tool names as registered with Pi (registerTool's `name`). */
489
+ tools: string[];
490
+ /** Render outside the host's tool gutter when the item is conversational content rather than implementation detail. */
491
+ timelinePresentation?: 'tool' | 'message';
492
+ /**
493
+ * Claims a tool named only at runtime (an MCP server's tools) when no
494
+ * plugin lists the name. The session's footer statuses come along so the
495
+ * plugin can read whatever its session half published, such as the server
496
+ * names; the first renderer to match, in install order, wins.
497
+ */
498
+ matches?(toolName: string, statuses: Readonly<Record<string, string>>): boolean;
499
+ message: ComponentType<ToolMessageRenderProps>;
500
+ /** Stands in for the composer input while this tool runs and holds a request. */
501
+ prompt?: ToolPromptContribution;
502
+ }
503
+ /** What a plugin's optional runtime may do through the page's hub socket. */
504
+ interface WebPluginRuntime {
505
+ sendSessionFrame: SessionFrameSender;
506
+ sendHubFrame(frame: Record<string, unknown>): void;
507
+ onHubConnected(listener: () => void): () => void;
508
+ }
509
+ interface WebPluginDefinition {
510
+ id: string;
511
+ tabs?: TabContribution[];
512
+ channels?: SessionChannelContribution[];
513
+ selectionAxes?: SelectionAxisContribution[];
514
+ minorModes?: MinorModeContribution[];
515
+ activityGroups?: ActivityGroupContribution[];
516
+ overlays?: SurfaceContribution[];
517
+ paletteCommands?: PaletteCommandContribution[];
518
+ leaderBindings?: LeaderBindingContribution[];
519
+ railSections?: SurfaceContribution[];
520
+ selectionBarItems?: SurfaceContribution[];
521
+ /** Compact controls placed in the mobile composer's action row, immediately before queue. */
522
+ composerActions?: SurfaceContribution[];
523
+ toolRenderers?: ToolRendererContribution[];
524
+ /**
525
+ * A section whose id names an activity group any plugin declares renders
526
+ * inside that group's slot, `activity.<id>`, replacing the session's
527
+ * one-line summary; any other section renders after the groups.
528
+ */
529
+ activitySections?: SurfaceContribution[];
530
+ /**
531
+ * Pages this plugin adds to cockpit settings. The host renders the fields and
532
+ * owns the scope switch, the repository picker, and every write.
533
+ */
534
+ settingsSections?: SettingsSectionContribution[];
535
+ /**
536
+ * A package-owned management panel inside the host's repository page. The
537
+ * host owns repository identity and passes only repositories it admitted.
538
+ */
539
+ repositorySettingsPanel?: RepositorySettingsPanelContribution;
540
+ /** The slots this plugin opens for others, each named '<this plugin id>.<name>'. */
541
+ slots?: SlotDeclaration[];
542
+ /** This plugin's contributions into slots other plugins (or the host) declare. */
543
+ /** This plugin's contributions into slots other plugins (or the host) declare. */
544
+ fills?: SlotFillContribution[];
545
+ /** Makes the paths this plugin tracks clickable where the host renders a message. */
546
+ fileLinks?: FileLinkSource;
547
+ /** Started after the host runtime, for page-lifetime needs such as hub frames; the return value disposes. */
548
+ start?(runtime: WebPluginRuntime): (() => void) | void;
549
+ }
550
+ //#endregion
551
+ export { ActivityGroupActiveSource, ActivityGroupContribution, FileLinkSource, GlobalStore, LeaderBindingBase, LeaderBindingContribution, LeaderKeyContribution, MinorModeContribution, PaletteCommandContext, PaletteCommandContribution, RepositorySettingsPanelContribution, RepositorySettingsPanelProps, RepositorySettingsRepository, SelectionAxisContribution, SessionChannelContribution, SessionFrameSender, SessionRecords, SessionStore, SessionStoreChannel, SettingsFieldContribution, SettingsFieldOption, SettingsSectionContribution, SlotDataFill, SlotDeclaration, SlotFillContribution, SurfaceContribution, TabContribution, ThreadViewOptions, ToolMessageRenderProps, ToolPromptContribution, ToolPromptDialog, ToolPromptRenderProps, ToolRendererContribution, ToolResultView, TransientTab, WebPluginDefinition, WebPluginRuntime, WebPluginSlotProps };
552
+ //# sourceMappingURL=webPlugin.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webPlugin.d.mts","names":[],"sources":["../../src/types/webPlugin.ts"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@agimon-ai/doompi-web-contracts",
3
+ "version": "0.0.1-alpha.11",
4
+ "description": "Web cockpit plugin contracts for DoomPi: plugin definitions, slot contributions, session data channels, and hub channel sources.",
5
+ "keywords": [
6
+ "ai",
7
+ "coding-agent",
8
+ "contracts",
9
+ "developer-tools",
10
+ "doompi",
11
+ "pi-package",
12
+ "plugin",
13
+ "typescript",
14
+ "web"
15
+ ],
16
+ "homepage": "https://agimon.ai",
17
+ "bugs": {
18
+ "url": "https://github.com/AgiFlow/doompi/issues"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Vuong Ngo",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/AgiFlow/doompi.git",
25
+ "directory": "packages/core/doompi-web-contracts"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "type": "module",
31
+ "main": "./dist/index.cjs",
32
+ "types": "./dist/index.d.mts",
33
+ "jsnext:main": "./dist/index.mjs",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.mts",
37
+ "import": "./dist/index.mjs",
38
+ "require": "./dist/index.cjs"
39
+ },
40
+ "./package.json": "./package.json",
41
+ "./testing": {
42
+ "types": "./dist/testing.d.mts",
43
+ "import": "./dist/testing.mjs",
44
+ "require": "./dist/testing.cjs"
45
+ }
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "devDependencies": {
51
+ "@tanstack/store": "0.11.1",
52
+ "@types/node": "26.4.0",
53
+ "@types/react": "19.2.18",
54
+ "@types/react-dom": "19.2.5",
55
+ "@vitest/coverage-v8": "4.1.11",
56
+ "react": "19.2.8",
57
+ "react-dom": "19.2.8",
58
+ "tsdown": "0.22.14",
59
+ "typescript": "7.0.2",
60
+ "vitest": "4.1.11"
61
+ },
62
+ "peerDependencies": {
63
+ "@tanstack/store": "0.11.1",
64
+ "react": "19.2.8",
65
+ "react-dom": "19.2.8"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "react": {
69
+ "optional": true
70
+ },
71
+ "react-dom": {
72
+ "optional": true
73
+ }
74
+ },
75
+ "engines": {
76
+ "node": ">=22.19.0"
77
+ },
78
+ "scripts": {
79
+ "build": "tsdown",
80
+ "test": "vitest --run",
81
+ "typecheck": "tsc --noEmit",
82
+ "lint": "oxlint . && oxfmt . --check",
83
+ "fixcode": "oxlint . --fix && oxfmt ."
84
+ }
85
+ }