@seed-ship/mcp-ui-solid 5.0.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +160 -6
  3. package/dist/components/ChatPrompt.cjs +71 -53
  4. package/dist/components/ChatPrompt.cjs.map +1 -1
  5. package/dist/components/ChatPrompt.d.ts +37 -2
  6. package/dist/components/ChatPrompt.d.ts.map +1 -1
  7. package/dist/components/ChatPrompt.js +72 -54
  8. package/dist/components/ChatPrompt.js.map +1 -1
  9. package/dist/components/FeedbackInline.cjs +57 -0
  10. package/dist/components/FeedbackInline.cjs.map +1 -0
  11. package/dist/components/FeedbackInline.d.ts +71 -0
  12. package/dist/components/FeedbackInline.d.ts.map +1 -0
  13. package/dist/components/FeedbackInline.js +57 -0
  14. package/dist/components/FeedbackInline.js.map +1 -0
  15. package/dist/index.cjs +9 -0
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +8 -2
  18. package/dist/index.d.ts +8 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +11 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/services/chat-bus.cjs +71 -0
  23. package/dist/services/chat-bus.cjs.map +1 -1
  24. package/dist/services/chat-bus.d.ts +31 -1
  25. package/dist/services/chat-bus.d.ts.map +1 -1
  26. package/dist/services/chat-bus.js +71 -0
  27. package/dist/services/chat-bus.js.map +1 -1
  28. package/dist/services/chat-prompt-controller.cjs +83 -0
  29. package/dist/services/chat-prompt-controller.cjs.map +1 -0
  30. package/dist/services/chat-prompt-controller.d.ts +93 -0
  31. package/dist/services/chat-prompt-controller.d.ts.map +1 -0
  32. package/dist/services/chat-prompt-controller.js +83 -0
  33. package/dist/services/chat-prompt-controller.js.map +1 -0
  34. package/dist/stores/scratchpad-store.cjs +105 -77
  35. package/dist/stores/scratchpad-store.cjs.map +1 -1
  36. package/dist/stores/scratchpad-store.d.ts +88 -19
  37. package/dist/stores/scratchpad-store.d.ts.map +1 -1
  38. package/dist/stores/scratchpad-store.js +105 -77
  39. package/dist/stores/scratchpad-store.js.map +1 -1
  40. package/dist/types/chat-bus.d.ts +164 -22
  41. package/dist/types/chat-bus.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/components/ChatPrompt.test.tsx +122 -0
  44. package/src/components/ChatPrompt.tsx +70 -15
  45. package/src/components/FeedbackInline.test.tsx +117 -0
  46. package/src/components/FeedbackInline.tsx +143 -0
  47. package/src/index.ts +24 -1
  48. package/src/services/chat-bus.test.ts +154 -2
  49. package/src/services/chat-bus.ts +115 -0
  50. package/src/services/chat-prompt-controller.test.ts +144 -0
  51. package/src/services/chat-prompt-controller.ts +214 -0
  52. package/src/stores/scratchpad-store.test.tsx +140 -0
  53. package/src/stores/scratchpad-store.tsx +244 -0
  54. package/src/types/chat-bus.ts +166 -22
  55. package/tsconfig.tsbuildinfo +1 -1
  56. package/src/stores/scratchpad-store.ts +0 -126
@@ -1,23 +1,101 @@
1
1
  /**
2
- * Scratchpad Store — singleton reactive state for HITL scratchpad
3
- * v3.0.3: Eliminates ChatBus relay chain race condition
2
+ * Scratchpad Store — reactive state for HITL scratchpad
4
3
  *
5
4
  * @experimental
6
5
  *
7
- * Parser calls dispatchScratchpad() store updates ScratchpadPanel reads reactively.
8
- * Zero bus, zero relay, zero race condition.
6
+ * **v5.2.0 :** the store is now a factory (`createScratchpadStore()`) with a
7
+ * module-level singleton kept as default. Two consumption modes :
9
8
  *
10
- * **Known limitation (v4.3.9):** This store is a module-level singleton, not
11
- * a context-scoped factory. Two `ScratchpadPanel` instances in the same app
12
- * will share the same state. Multi-panel scenarios (e.g. chat + admin dashboard
13
- * both showing scratchpads simultaneously) are unsupported. Host apps that need
14
- * isolated scratchpads should not reuse this store wait for v4.4.0 which
15
- * will expose `createScratchpadStore()` factory for per-panel instances.
9
+ * 1. **Singleton mode (default, zero-config)** `dispatchScratchpad(event)` +
10
+ * `useScratchpadState()` read/write the module singleton. This is the v4.x
11
+ * path and keeps working unchanged.
12
+ *
13
+ * 2. **Multi-instance mode** wrap a subtree in `<ScratchpadStoreProvider>`
14
+ * (it creates a scoped `ScratchpadStoreHandle` internally, or use your own
15
+ * via the `store` prop). `useScratchpadState()` auto-detects the context
16
+ * and reads from it; `ScratchpadPanel` mounted inside the provider reads
17
+ * the scoped store. Non-reactive callers (SSE parsers) should pass the
18
+ * handle explicitly — do NOT try to reach context from a non-reactive
19
+ * scope.
16
20
  */
21
+ import { type ParentComponent } from 'solid-js';
17
22
  import type { ScratchpadState, ScratchpadEvent } from '../types/chat-bus';
23
+ export interface ScratchpadStoreHandle {
24
+ /** Mutate the store from an SSE/parser callback. */
25
+ dispatch: (event: ScratchpadEvent) => void;
26
+ /** Reactive accessor for the current scratchpad state (null when closed). */
27
+ state: () => ScratchpadState | null;
28
+ /** Reactive accessor for the pinned flag. */
29
+ pinned: () => boolean;
30
+ /** Close the scratchpad (equivalent to dispatching an action='close'). */
31
+ close: () => void;
32
+ }
33
+ /**
34
+ * Create an isolated scratchpad store instance.
35
+ *
36
+ * Use this when you need two or more scratchpads live at the same time
37
+ * (e.g. chat scratchpad + admin dashboard scratchpad). Pair with
38
+ * `<ScratchpadStoreProvider store={...}>` to scope a SolidJS subtree.
39
+ *
40
+ * @experimental
41
+ * @since v5.2.0
42
+ */
43
+ export declare function createScratchpadStore(): ScratchpadStoreHandle;
44
+ /**
45
+ * Function for the PARSER/STORE — mutates the **module-level singleton**
46
+ * scratchpad state. Use this when you only need one scratchpad at a time
47
+ * (single-instance consumer, the v4.x pattern).
48
+ *
49
+ * For multi-instance scenarios, prefer `createScratchpadStore()` and pass the
50
+ * handle around explicitly.
51
+ *
52
+ * @example
53
+ * // In your SSE parser callback — ONE LINE
54
+ * onScratchpad: (data) => dispatchScratchpad(data as ScratchpadEvent)
55
+ */
56
+ export declare function dispatchScratchpad(event: ScratchpadEvent): void;
57
+ /**
58
+ * Context for a scoped scratchpad store. Populated by
59
+ * `<ScratchpadStoreProvider>`. Read by `useScratchpadState()` with automatic
60
+ * fallback to the module-level singleton when the context is absent.
61
+ *
62
+ * @experimental
63
+ * @since v5.2.0
64
+ */
65
+ export declare const ScratchpadStoreContext: import("solid-js").Context<ScratchpadStoreHandle | undefined>;
66
+ /**
67
+ * Provide a scoped `ScratchpadStoreHandle` to a SolidJS subtree. Children
68
+ * reading via `useScratchpadState()` or rendering a `<ScratchpadPanel>` will
69
+ * bind to this store instead of the module singleton.
70
+ *
71
+ * If no `store` prop is passed, a fresh store is created for the provider's
72
+ * lifetime. Pass `store` explicitly when you need the handle outside the
73
+ * tree (e.g. in an SSE parser that lives at the app root).
74
+ *
75
+ * @experimental
76
+ * @since v5.2.0
77
+ *
78
+ * @example
79
+ * const chatStore = createScratchpadStore()
80
+ * const adminStore = createScratchpadStore()
81
+ *
82
+ * <ScratchpadStoreProvider store={chatStore}>
83
+ * <ChatInterface />
84
+ * </ScratchpadStoreProvider>
85
+ * <ScratchpadStoreProvider store={adminStore}>
86
+ * <AdminDashboard />
87
+ * </ScratchpadStoreProvider>
88
+ */
89
+ export declare const ScratchpadStoreProvider: ParentComponent<{
90
+ store?: ScratchpadStoreHandle;
91
+ }>;
18
92
  /**
19
93
  * Hook for the COMPONENT — reads the scratchpad state reactively.
20
94
  *
95
+ * **v5.2.0 :** if called inside a `<ScratchpadStoreProvider>`, reads the
96
+ * scoped handle; otherwise falls back to the module singleton. Old v4.x
97
+ * consumers keep working unchanged.
98
+ *
21
99
  * @example
22
100
  * const { state, pinned, close } = useScratchpadState()
23
101
  * <Show when={state()}>
@@ -29,13 +107,4 @@ export declare function useScratchpadState(): {
29
107
  pinned: () => boolean;
30
108
  close: () => void;
31
109
  };
32
- /**
33
- * Function for the PARSER/STORE — mutates the scratchpad state.
34
- * Called from the SSE callback, no bus needed.
35
- *
36
- * @example
37
- * // In your SSE parser callback — ONE LINE
38
- * onScratchpad: (data) => dispatchScratchpad(data as ScratchpadEvent)
39
- */
40
- export declare function dispatchScratchpad(event: ScratchpadEvent): void;
41
110
  //# sourceMappingURL=scratchpad-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scratchpad-store.d.ts","sourceRoot":"","sources":["../../src/stores/scratchpad-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAqB,MAAM,mBAAmB,CAAA;AAO5F;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB;;;;EAMjC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CA2E/D"}
1
+ {"version":3,"file":"scratchpad-store.d.ts","sourceRoot":"","sources":["../../src/stores/scratchpad-store.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAA6B,KAAK,eAAe,EAAY,MAAM,UAAU,CAAA;AAEpF,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAqB,MAAM,mBAAmB,CAAA;AAI5F,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IAC1C,6EAA6E;IAC7E,KAAK,EAAE,MAAM,eAAe,GAAG,IAAI,CAAA;IACnC,6CAA6C;IAC7C,MAAM,EAAE,MAAM,OAAO,CAAA;IACrB,0EAA0E;IAC1E,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAID;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,IAAI,qBAAqB,CAoG7D;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAE/D;AAID;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,+DAA8D,CAAA;AAEjG;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,uBAAuB,EAAE,eAAe,CAAC;IACpD,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAC9B,CAKA,CAAA;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,IAAI;IACpC,KAAK,EAAE,MAAM,eAAe,GAAG,IAAI,CAAA;IACnC,MAAM,EAAE,MAAM,OAAO,CAAA;IACrB,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB,CAQA"}
@@ -1,89 +1,117 @@
1
+ import { createComponent } from "solid-js/web";
2
+ import { createContext, useContext } from "solid-js";
1
3
  import { createStore, produce } from "solid-js/store";
2
- const [scratchpadStore, setScratchpadStore] = createStore({ current: null, pinned: false });
3
- function useScratchpadState() {
4
+ function createScratchpadStore() {
5
+ const [scratchpadStore, setScratchpadStore] = createStore({
6
+ current: null,
7
+ pinned: false
8
+ });
9
+ const dispatch = (event) => {
10
+ var _a, _b;
11
+ if (event.action === "create") {
12
+ console.info(`%c[MCP-UI] dispatchScratchpad%c create id=${event.id} sections=${((_a = event.sections) == null ? void 0 : _a.length) || 0} status=${event.status || "loading"}${event.pinned ? " pinned" : ""}`, "color: #10b981; font-weight: bold", "color: inherit");
13
+ setScratchpadStore({
14
+ current: {
15
+ id: event.id,
16
+ title: event.title || "",
17
+ sections: event.sections || [],
18
+ filters: event.filters || {},
19
+ preview: event.preview,
20
+ agentMessages: event.agentMessages || [],
21
+ status: event.status || "loading",
22
+ previewEndpoint: event.previewEndpoint,
23
+ previewDebounce: event.previewDebounce,
24
+ previewMethod: event.previewMethod,
25
+ previewHeaders: event.previewHeaders,
26
+ turn: event.turn,
27
+ totalTurns: event.totalTurns,
28
+ turnHistory: event.turnHistory
29
+ },
30
+ pinned: event.pinned || false
31
+ });
32
+ } else if (event.action === "update") {
33
+ console.info(`%c[MCP-UI] dispatchScratchpad%c update id=${event.id} sectionMode=${event.sectionMode || "replace"} sections=${((_b = event.sections) == null ? void 0 : _b.length) || 0} status=${event.status || "-"}`, "color: #3b82f6; font-weight: bold", "color: inherit");
34
+ setScratchpadStore(produce((s) => {
35
+ var _a2;
36
+ if (!s.current || s.current.id !== event.id) {
37
+ console.warn(`[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${((_a2 = s.current) == null ? void 0 : _a2.id) || "null"}. Ignoring.`);
38
+ return;
39
+ }
40
+ if (event.sections) {
41
+ const mode = event.sectionMode || "replace";
42
+ if (mode === "replace") {
43
+ s.current.sections = event.sections;
44
+ } else if (mode === "append") {
45
+ s.current.sections = [...s.current.sections, ...event.sections];
46
+ } else if (mode === "upsert") {
47
+ let matchCount = 0;
48
+ for (const incoming of event.sections) {
49
+ const idx = s.current.sections.findIndex((sec) => sec.id === incoming.id);
50
+ if (idx >= 0) {
51
+ s.current.sections[idx] = incoming;
52
+ matchCount++;
53
+ } else {
54
+ s.current.sections.push(incoming);
55
+ }
56
+ }
57
+ if (matchCount === 0 && event.sections.length > 0) {
58
+ console.warn(`[MCP-UI] dispatchScratchpad: sectionMode='upsert' but no IDs matched. Incoming: [${event.sections.map((s2) => s2.id).join(", ")}] Existing: [${s.current.sections.map((s2) => s2.id).join(", ")}]. All appended.`);
59
+ }
60
+ }
61
+ }
62
+ if (event.agentMessages) s.current.agentMessages = event.agentMessages;
63
+ if (event.status) s.current.status = event.status;
64
+ if (event.filters) s.current.filters = event.filters;
65
+ if (event.preview) s.current.preview = event.preview;
66
+ if (event.pinned != null) s.pinned = event.pinned;
67
+ if (event.turnHistory) s.current.turnHistory = event.turnHistory;
68
+ if (event.turn != null) s.current.turn = event.turn;
69
+ }));
70
+ } else if (event.action === "close") {
71
+ console.info(`%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`, "color: #6b7280; font-weight: bold", "color: inherit");
72
+ setScratchpadStore({
73
+ current: null,
74
+ pinned: false
75
+ });
76
+ }
77
+ };
4
78
  return {
79
+ dispatch,
5
80
  state: () => scratchpadStore.current,
6
81
  pinned: () => scratchpadStore.pinned,
7
- close: () => setScratchpadStore({ current: null, pinned: false })
82
+ close: () => setScratchpadStore({
83
+ current: null,
84
+ pinned: false
85
+ })
8
86
  };
9
87
  }
88
+ const defaultStore = createScratchpadStore();
10
89
  function dispatchScratchpad(event) {
11
- var _a, _b;
12
- if (event.action === "create") {
13
- console.info(
14
- `%c[MCP-UI] dispatchScratchpad%c create id=${event.id} sections=${((_a = event.sections) == null ? void 0 : _a.length) || 0} status=${event.status || "loading"}${event.pinned ? " pinned" : ""}`,
15
- "color: #10b981; font-weight: bold",
16
- "color: inherit"
17
- );
18
- setScratchpadStore({
19
- current: {
20
- id: event.id,
21
- title: event.title || "",
22
- sections: event.sections || [],
23
- filters: event.filters || {},
24
- preview: event.preview,
25
- agentMessages: event.agentMessages || [],
26
- status: event.status || "loading",
27
- previewEndpoint: event.previewEndpoint,
28
- previewDebounce: event.previewDebounce,
29
- previewMethod: event.previewMethod,
30
- previewHeaders: event.previewHeaders,
31
- turn: event.turn,
32
- totalTurns: event.totalTurns,
33
- turnHistory: event.turnHistory
34
- },
35
- pinned: event.pinned || false
36
- });
37
- } else if (event.action === "update") {
38
- console.info(
39
- `%c[MCP-UI] dispatchScratchpad%c update id=${event.id} sectionMode=${event.sectionMode || "replace"} sections=${((_b = event.sections) == null ? void 0 : _b.length) || 0} status=${event.status || "-"}`,
40
- "color: #3b82f6; font-weight: bold",
41
- "color: inherit"
42
- );
43
- setScratchpadStore(produce((s) => {
44
- var _a2;
45
- if (!s.current || s.current.id !== event.id) {
46
- console.warn(`[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${((_a2 = s.current) == null ? void 0 : _a2.id) || "null"}. Ignoring.`);
47
- return;
48
- }
49
- if (event.sections) {
50
- const mode = event.sectionMode || "replace";
51
- if (mode === "replace") {
52
- s.current.sections = event.sections;
53
- } else if (mode === "append") {
54
- s.current.sections = [...s.current.sections, ...event.sections];
55
- } else if (mode === "upsert") {
56
- let matchCount = 0;
57
- for (const incoming of event.sections) {
58
- const idx = s.current.sections.findIndex((sec) => sec.id === incoming.id);
59
- if (idx >= 0) {
60
- s.current.sections[idx] = incoming;
61
- matchCount++;
62
- } else {
63
- s.current.sections.push(incoming);
64
- }
65
- }
66
- if (matchCount === 0 && event.sections.length > 0) {
67
- console.warn(
68
- `[MCP-UI] dispatchScratchpad: sectionMode='upsert' but no IDs matched. Incoming: [${event.sections.map((s2) => s2.id).join(", ")}] Existing: [${s.current.sections.map((s2) => s2.id).join(", ")}]. All appended.`
69
- );
70
- }
71
- }
72
- }
73
- if (event.agentMessages) s.current.agentMessages = event.agentMessages;
74
- if (event.status) s.current.status = event.status;
75
- if (event.filters) s.current.filters = event.filters;
76
- if (event.preview) s.current.preview = event.preview;
77
- if (event.pinned != null) s.pinned = event.pinned;
78
- if (event.turnHistory) s.current.turnHistory = event.turnHistory;
79
- if (event.turn != null) s.current.turn = event.turn;
80
- }));
81
- } else if (event.action === "close") {
82
- console.info(`%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`, "color: #6b7280; font-weight: bold", "color: inherit");
83
- setScratchpadStore({ current: null, pinned: false });
84
- }
90
+ defaultStore.dispatch(event);
91
+ }
92
+ const ScratchpadStoreContext = createContext(void 0);
93
+ const ScratchpadStoreProvider = (props) => {
94
+ const store = props.store ?? createScratchpadStore();
95
+ return createComponent(ScratchpadStoreContext.Provider, {
96
+ value: store,
97
+ get children() {
98
+ return props.children;
99
+ }
100
+ });
101
+ };
102
+ function useScratchpadState() {
103
+ const scoped = useContext(ScratchpadStoreContext);
104
+ const handle = scoped ?? defaultStore;
105
+ return {
106
+ state: handle.state,
107
+ pinned: handle.pinned,
108
+ close: handle.close
109
+ };
85
110
  }
86
111
  export {
112
+ ScratchpadStoreContext,
113
+ ScratchpadStoreProvider,
114
+ createScratchpadStore,
87
115
  dispatchScratchpad,
88
116
  useScratchpadState
89
117
  };
@@ -1 +1 @@
1
- {"version":3,"file":"scratchpad-store.js","sources":["../../src/stores/scratchpad-store.ts"],"sourcesContent":["/**\n * Scratchpad Store — singleton reactive state for HITL scratchpad\n * v3.0.3: Eliminates ChatBus relay chain race condition\n *\n * @experimental\n *\n * Parser calls dispatchScratchpad() → store updates → ScratchpadPanel reads reactively.\n * Zero bus, zero relay, zero race condition.\n *\n * **Known limitation (v4.3.9):** This store is a module-level singleton, not\n * a context-scoped factory. Two `ScratchpadPanel` instances in the same app\n * will share the same state. Multi-panel scenarios (e.g. chat + admin dashboard\n * both showing scratchpads simultaneously) are unsupported. Host apps that need\n * isolated scratchpads should not reuse this store — wait for v4.4.0 which\n * will expose `createScratchpadStore()` factory for per-panel instances.\n */\n\nimport { createStore, produce } from 'solid-js/store'\nimport type { ScratchpadState, ScratchpadEvent, ScratchpadSection } from '../types/chat-bus'\n\nconst [scratchpadStore, setScratchpadStore] = createStore<{\n current: ScratchpadState | null\n pinned: boolean\n}>({ current: null, pinned: false })\n\n/**\n * Hook for the COMPONENT — reads the scratchpad state reactively.\n *\n * @example\n * const { state, pinned, close } = useScratchpadState()\n * <Show when={state()}>\n * <ScratchpadPanel state={state()!} pinned={pinned()} onClose={close} />\n * </Show>\n */\nexport function useScratchpadState() {\n return {\n state: () => scratchpadStore.current,\n pinned: () => scratchpadStore.pinned,\n close: () => setScratchpadStore({ current: null, pinned: false }),\n }\n}\n\n/**\n * Function for the PARSER/STORE — mutates the scratchpad state.\n * Called from the SSE callback, no bus needed.\n *\n * @example\n * // In your SSE parser callback — ONE LINE\n * onScratchpad: (data) => dispatchScratchpad(data as ScratchpadEvent)\n */\nexport function dispatchScratchpad(event: ScratchpadEvent): void {\n // DX1: lifecycle logging\n if (event.action === 'create') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c create id=${event.id} sections=${event.sections?.length || 0} status=${event.status || 'loading'}${event.pinned ? ' pinned' : ''}`,\n 'color: #10b981; font-weight: bold', 'color: inherit'\n )\n setScratchpadStore({\n current: {\n id: event.id,\n title: event.title || '',\n sections: event.sections || [],\n filters: event.filters || {},\n preview: event.preview,\n agentMessages: event.agentMessages || [],\n status: event.status || 'loading',\n previewEndpoint: (event as any).previewEndpoint,\n previewDebounce: (event as any).previewDebounce,\n previewMethod: (event as any).previewMethod,\n previewHeaders: (event as any).previewHeaders,\n turn: (event as any).turn,\n totalTurns: (event as any).totalTurns,\n turnHistory: (event as any).turnHistory,\n },\n pinned: event.pinned || false,\n })\n } else if (event.action === 'update') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c update id=${event.id} sectionMode=${event.sectionMode || 'replace'} sections=${event.sections?.length || 0} status=${event.status || '-'}`,\n 'color: #3b82f6; font-weight: bold', 'color: inherit'\n )\n setScratchpadStore(produce((s) => {\n if (!s.current || s.current.id !== event.id) {\n console.warn(`[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${s.current?.id || 'null'}. Ignoring.`)\n return\n }\n\n if (event.sections) {\n const mode = event.sectionMode || 'replace'\n if (mode === 'replace') {\n s.current.sections = event.sections\n } else if (mode === 'append') {\n s.current.sections = [...s.current.sections, ...event.sections]\n } else if (mode === 'upsert') {\n let matchCount = 0\n for (const incoming of event.sections) {\n const idx = s.current.sections.findIndex((sec: ScratchpadSection) => sec.id === incoming.id)\n if (idx >= 0) {\n s.current.sections[idx] = incoming\n matchCount++\n } else {\n s.current.sections.push(incoming)\n }\n }\n if (matchCount === 0 && event.sections.length > 0) {\n console.warn(\n `[MCP-UI] dispatchScratchpad: sectionMode='upsert' but no IDs matched. ` +\n `Incoming: [${event.sections.map((s: ScratchpadSection) => s.id).join(', ')}] ` +\n `Existing: [${s.current.sections.map((s: ScratchpadSection) => s.id).join(', ')}]. All appended.`\n )\n }\n }\n }\n if (event.agentMessages) s.current.agentMessages = event.agentMessages\n if (event.status) s.current.status = event.status\n if (event.filters) s.current.filters = event.filters\n if (event.preview) s.current.preview = event.preview\n if (event.pinned != null) s.pinned = event.pinned\n if ((event as any).turnHistory) s.current.turnHistory = (event as any).turnHistory\n if ((event as any).turn != null) s.current.turn = (event as any).turn\n }))\n } else if (event.action === 'close') {\n console.info(`%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`, 'color: #6b7280; font-weight: bold', 'color: inherit')\n setScratchpadStore({ current: null, pinned: false })\n }\n}\n"],"names":["_a","s"],"mappings":"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"}
1
+ {"version":3,"file":"scratchpad-store.js","sources":["../../src/stores/scratchpad-store.tsx"],"sourcesContent":["/**\n * Scratchpad Store — reactive state for HITL scratchpad\n *\n * @experimental\n *\n * **v5.2.0 :** the store is now a factory (`createScratchpadStore()`) with a\n * module-level singleton kept as default. Two consumption modes :\n *\n * 1. **Singleton mode (default, zero-config)** — `dispatchScratchpad(event)` +\n * `useScratchpadState()` read/write the module singleton. This is the v4.x\n * path and keeps working unchanged.\n *\n * 2. **Multi-instance mode** — wrap a subtree in `<ScratchpadStoreProvider>`\n * (it creates a scoped `ScratchpadStoreHandle` internally, or use your own\n * via the `store` prop). `useScratchpadState()` auto-detects the context\n * and reads from it; `ScratchpadPanel` mounted inside the provider reads\n * the scoped store. Non-reactive callers (SSE parsers) should pass the\n * handle explicitly — do NOT try to reach context from a non-reactive\n * scope.\n */\n\nimport { createContext, useContext, type ParentComponent, type JSX } from 'solid-js'\nimport { createStore, produce } from 'solid-js/store'\nimport type { ScratchpadState, ScratchpadEvent, ScratchpadSection } from '../types/chat-bus'\n\n// ─── Handle shape ─────────────────────────────────────────────\n\nexport interface ScratchpadStoreHandle {\n /** Mutate the store from an SSE/parser callback. */\n dispatch: (event: ScratchpadEvent) => void\n /** Reactive accessor for the current scratchpad state (null when closed). */\n state: () => ScratchpadState | null\n /** Reactive accessor for the pinned flag. */\n pinned: () => boolean\n /** Close the scratchpad (equivalent to dispatching an action='close'). */\n close: () => void\n}\n\n// ─── Factory ──────────────────────────────────────────────────\n\n/**\n * Create an isolated scratchpad store instance.\n *\n * Use this when you need two or more scratchpads live at the same time\n * (e.g. chat scratchpad + admin dashboard scratchpad). Pair with\n * `<ScratchpadStoreProvider store={...}>` to scope a SolidJS subtree.\n *\n * @experimental\n * @since v5.2.0\n */\nexport function createScratchpadStore(): ScratchpadStoreHandle {\n const [scratchpadStore, setScratchpadStore] = createStore<{\n current: ScratchpadState | null\n pinned: boolean\n }>({ current: null, pinned: false })\n\n const dispatch = (event: ScratchpadEvent): void => {\n if (event.action === 'create') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c create id=${event.id} sections=${event.sections?.length || 0} status=${event.status || 'loading'}${event.pinned ? ' pinned' : ''}`,\n 'color: #10b981; font-weight: bold',\n 'color: inherit'\n )\n setScratchpadStore({\n current: {\n id: event.id,\n title: event.title || '',\n sections: event.sections || [],\n filters: event.filters || {},\n preview: event.preview,\n agentMessages: event.agentMessages || [],\n status: event.status || 'loading',\n previewEndpoint: (event as any).previewEndpoint,\n previewDebounce: (event as any).previewDebounce,\n previewMethod: (event as any).previewMethod,\n previewHeaders: (event as any).previewHeaders,\n turn: (event as any).turn,\n totalTurns: (event as any).totalTurns,\n turnHistory: (event as any).turnHistory,\n },\n pinned: event.pinned || false,\n })\n } else if (event.action === 'update') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c update id=${event.id} sectionMode=${event.sectionMode || 'replace'} sections=${event.sections?.length || 0} status=${event.status || '-'}`,\n 'color: #3b82f6; font-weight: bold',\n 'color: inherit'\n )\n setScratchpadStore(\n produce((s) => {\n if (!s.current || s.current.id !== event.id) {\n console.warn(\n `[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${s.current?.id || 'null'}. Ignoring.`\n )\n return\n }\n\n if (event.sections) {\n const mode = event.sectionMode || 'replace'\n if (mode === 'replace') {\n s.current.sections = event.sections\n } else if (mode === 'append') {\n s.current.sections = [...s.current.sections, ...event.sections]\n } else if (mode === 'upsert') {\n let matchCount = 0\n for (const incoming of event.sections) {\n const idx = s.current.sections.findIndex(\n (sec: ScratchpadSection) => sec.id === incoming.id\n )\n if (idx >= 0) {\n s.current.sections[idx] = incoming\n matchCount++\n } else {\n s.current.sections.push(incoming)\n }\n }\n if (matchCount === 0 && event.sections.length > 0) {\n console.warn(\n `[MCP-UI] dispatchScratchpad: sectionMode='upsert' but no IDs matched. ` +\n `Incoming: [${event.sections.map((s: ScratchpadSection) => s.id).join(', ')}] ` +\n `Existing: [${s.current.sections.map((s: ScratchpadSection) => s.id).join(', ')}]. All appended.`\n )\n }\n }\n }\n if (event.agentMessages) s.current.agentMessages = event.agentMessages\n if (event.status) s.current.status = event.status\n if (event.filters) s.current.filters = event.filters\n if (event.preview) s.current.preview = event.preview\n if (event.pinned != null) s.pinned = event.pinned\n if ((event as any).turnHistory) s.current.turnHistory = (event as any).turnHistory\n if ((event as any).turn != null) s.current.turn = (event as any).turn\n })\n )\n } else if (event.action === 'close') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`,\n 'color: #6b7280; font-weight: bold',\n 'color: inherit'\n )\n setScratchpadStore({ current: null, pinned: false })\n }\n }\n\n return {\n dispatch,\n state: () => scratchpadStore.current,\n pinned: () => scratchpadStore.pinned,\n close: () => setScratchpadStore({ current: null, pinned: false }),\n }\n}\n\n// ─── Module-level singleton (v4.x-compatible default) ─────────\n\nconst defaultStore: ScratchpadStoreHandle = createScratchpadStore()\n\n/**\n * Function for the PARSER/STORE — mutates the **module-level singleton**\n * scratchpad state. Use this when you only need one scratchpad at a time\n * (single-instance consumer, the v4.x pattern).\n *\n * For multi-instance scenarios, prefer `createScratchpadStore()` and pass the\n * handle around explicitly.\n *\n * @example\n * // In your SSE parser callback — ONE LINE\n * onScratchpad: (data) => dispatchScratchpad(data as ScratchpadEvent)\n */\nexport function dispatchScratchpad(event: ScratchpadEvent): void {\n defaultStore.dispatch(event)\n}\n\n// ─── Context (v5.2.0) ─────────────────────────────────────────\n\n/**\n * Context for a scoped scratchpad store. Populated by\n * `<ScratchpadStoreProvider>`. Read by `useScratchpadState()` with automatic\n * fallback to the module-level singleton when the context is absent.\n *\n * @experimental\n * @since v5.2.0\n */\nexport const ScratchpadStoreContext = createContext<ScratchpadStoreHandle | undefined>(undefined)\n\n/**\n * Provide a scoped `ScratchpadStoreHandle` to a SolidJS subtree. Children\n * reading via `useScratchpadState()` or rendering a `<ScratchpadPanel>` will\n * bind to this store instead of the module singleton.\n *\n * If no `store` prop is passed, a fresh store is created for the provider's\n * lifetime. Pass `store` explicitly when you need the handle outside the\n * tree (e.g. in an SSE parser that lives at the app root).\n *\n * @experimental\n * @since v5.2.0\n *\n * @example\n * const chatStore = createScratchpadStore()\n * const adminStore = createScratchpadStore()\n *\n * <ScratchpadStoreProvider store={chatStore}>\n * <ChatInterface />\n * </ScratchpadStoreProvider>\n * <ScratchpadStoreProvider store={adminStore}>\n * <AdminDashboard />\n * </ScratchpadStoreProvider>\n */\nexport const ScratchpadStoreProvider: ParentComponent<{\n store?: ScratchpadStoreHandle\n}> = (props): JSX.Element => {\n const store = props.store ?? createScratchpadStore()\n return (\n <ScratchpadStoreContext.Provider value={store}>{props.children}</ScratchpadStoreContext.Provider>\n )\n}\n\n// ─── Reactive hook (context-aware) ────────────────────────────\n\n/**\n * Hook for the COMPONENT — reads the scratchpad state reactively.\n *\n * **v5.2.0 :** if called inside a `<ScratchpadStoreProvider>`, reads the\n * scoped handle; otherwise falls back to the module singleton. Old v4.x\n * consumers keep working unchanged.\n *\n * @example\n * const { state, pinned, close } = useScratchpadState()\n * <Show when={state()}>\n * <ScratchpadPanel state={state()!} pinned={pinned()} onClose={close} />\n * </Show>\n */\nexport function useScratchpadState(): {\n state: () => ScratchpadState | null\n pinned: () => boolean\n close: () => void\n} {\n const scoped = useContext(ScratchpadStoreContext)\n const handle = scoped ?? defaultStore\n return {\n state: handle.state,\n pinned: handle.pinned,\n close: handle.close,\n }\n}\n"],"names":["createScratchpadStore","scratchpadStore","setScratchpadStore","createStore","current","pinned","dispatch","event","action","console","info","id","sections","length","status","title","filters","preview","agentMessages","previewEndpoint","previewDebounce","previewMethod","previewHeaders","turn","totalTurns","turnHistory","sectionMode","produce","s","warn","mode","matchCount","incoming","idx","findIndex","sec","push","map","join","state","close","defaultStore","dispatchScratchpad","ScratchpadStoreContext","createContext","undefined","ScratchpadStoreProvider","props","store","_$createComponent","Provider","value","children","useScratchpadState","scoped","useContext","handle"],"mappings":"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"}
@@ -5,6 +5,7 @@
5
5
  * @experimental — These types may change without major bump until stabilized in v2.5.0.
6
6
  * See CHANGELOG for breaking changes on experimental types.
7
7
  */
8
+ import type { JSX } from 'solid-js';
8
9
  import type { UIComponent, UILayout } from './index';
9
10
  /**
10
11
  * @experimental
@@ -58,6 +59,9 @@ export interface ChatEvents {
58
59
  onClarificationNeeded: (event: ChatEventBase & {
59
60
  clarification: ClarificationEvent;
60
61
  }) => void;
62
+ onElicitation: (event: ChatEventBase & {
63
+ elicitation: ElicitationEvent;
64
+ }) => void;
61
65
  onAgentSwitch: (event: ChatEventBase & {
62
66
  agent: AgentContext;
63
67
  }) => void;
@@ -103,15 +107,44 @@ export interface ChatCommands {
103
107
  /**
104
108
  * Show a ChatPrompt (choice, confirm, form) above the input (C4).
105
109
  *
106
- * **Known limitation (v4.3.9):** Not re-entrant. If called while another
107
- * prompt is already active, the previous prompt's Promise will never resolve
108
- * (memory leak). Host apps must queue prompts or dismiss the previous one
109
- * manually before showing a new one. Fix planned for v4.4.0 (auto-reject
110
- * previous prompt or FIFO queue).
110
+ * **No default handler in v5.0.0 / v5.1.0.** `showChatPrompt` is a command
111
+ * *name*, not a default implementation mcp-ui ships `ChatPrompt` (the
112
+ * presentation component) and the bus (event/command plumbing), but the
113
+ * handler that threads a Promise resolver through the SolidJS lifecycle is
114
+ * the consumer's responsibility. Every host app calls
115
+ * `bus.commands.handle('showChatPrompt', (config, signal?) => { ... })`.
116
+ *
117
+ * ### Implementer contract
118
+ *
119
+ * A conforming handler MUST:
120
+ *
121
+ * 1. Return a `Promise<ChatPromptResponse>`.
122
+ * 2. Resolve the Promise from the `ChatPrompt` component's `onSubmit`
123
+ * (explicit answer) or from `onDismiss` (dismissed flag true).
124
+ * 3. If a `signal` is provided:
125
+ * - If `signal.aborted` is already `true`, reject with
126
+ * `new DOMException('Prompt aborted', 'AbortError')` synchronously
127
+ * (or via `Promise.reject`) and do NOT show the UI.
128
+ * - Otherwise, register `signal.addEventListener('abort', () =>
129
+ * reject(new DOMException('Prompt aborted', 'AbortError')))` and
130
+ * clean up the listener on resolve/dismiss.
131
+ * 4. Enforce re-entrance policy — if a previous prompt is still active
132
+ * when a new one arrives, the recommended behavior is auto-reject the
133
+ * previous Promise with a custom error (e.g. `PromptReplacedError`).
134
+ * Alternatives: FIFO queue, or throw synchronously.
135
+ *
136
+ * The `DOMException('AbortError')` shape is the Web Platform convention
137
+ * (matches `fetch()`, `Response.body.cancel()`, `WritableStream.abort()`).
138
+ * Consumers branching on the error can do
139
+ * `catch (err) { if (err.name === 'AbortError') return; throw err }`.
140
+ *
141
+ * ### Planned primitive (v5.2.0)
111
142
  *
112
- * **AbortSignal limitation (v4.3.9):** The `signal` argument is currently
113
- * unused `ChatPrompt` does not listen to aborts. Host apps must wire
114
- * abort Promise rejection themselves. Fix planned for v4.4.0.
143
+ * A `createChatPromptController(setActivePrompt)` helper will centralise
144
+ * the resolver lifecycle + abort + re-entrance logic once, so consumers
145
+ * can write `bus.commands.handle('showChatPrompt', ctrl.handle)` instead
146
+ * of threading a `let chatPromptResolver` closure by hand. Design doc:
147
+ * `docs/2026/r&d/mcpui-v5.1.0-consensus.md`.
115
148
  */
116
149
  showChatPrompt: (config: ChatPromptConfig, signal?: AbortSignal) => Promise<ChatPromptResponse>;
117
150
  /** Dismiss the active ChatPrompt */
@@ -184,21 +217,94 @@ export interface ChatPromptConfig {
184
217
  /** Type-specific configuration */
185
218
  config: ChoicePromptConfig | ConfirmPromptConfig | FormPromptConfig;
186
219
  }
187
- export interface ChoicePromptConfig {
188
- options: Array<{
189
- value: string;
190
- label: string;
191
- icon?: string;
192
- description?: string;
193
- /**
194
- * Free-form metadata (confidence, source, tags, ...).
195
- * Opaque to default renderer — use a custom ChoiceBody wrapper to display it.
196
- * Preserved through showChatPrompt → ChatPromptResponse roundtrip.
197
- * @since v4.3.9
198
- */
199
- metadata?: Record<string, unknown>;
200
- }>;
220
+ /**
221
+ * A single choice option. The generic `TMeta` parameter flows through the
222
+ * whole `ChoicePromptConfig<TMeta>` shape so consumers can strongly-type
223
+ * their metadata in `optionRenderer` without casting.
224
+ *
225
+ * @since v4.3.9 (metadata), v5.1.0 (generic TMeta + optionRenderer typing)
226
+ */
227
+ export interface ChoiceOption<TMeta = Record<string, unknown>> {
228
+ value: string;
229
+ label: string;
230
+ icon?: string;
231
+ description?: string;
232
+ /**
233
+ * Free-form metadata (confidence, source, tags, ...).
234
+ * Opaque to the default renderer — use `optionRenderer` to display it.
235
+ * Preserved through `showChatPrompt → ChatPromptResponse` roundtrip.
236
+ * @since v4.3.9
237
+ */
238
+ metadata?: TMeta;
239
+ }
240
+ export interface ChoicePromptConfig<TMeta = Record<string, unknown>> {
241
+ options: Array<ChoiceOption<TMeta>>;
201
242
  layout?: 'horizontal' | 'vertical' | 'grid';
243
+ /**
244
+ * Optional render prop for custom option bodies (badges, confidence
245
+ * indicators, rich layouts). Replaces the default `label + icon +
246
+ * description` body. mcp-ui still wraps the returned JSX in a `<button>`
247
+ * with the `onClick` handler, keyboard support, and focus styles — only
248
+ * the *content* of the button is yours.
249
+ *
250
+ * @param option The full `ChoiceOption` including strongly-typed `metadata`.
251
+ * @param index Zero-based position in the `options` array.
252
+ *
253
+ * @example
254
+ * ```tsx
255
+ * interface ConfBadgeMeta { confidence: number; source: string }
256
+ *
257
+ * bus.commands.exec('showChatPrompt', {
258
+ * type: 'choice',
259
+ * title: 'Pick an intent',
260
+ * config: {
261
+ * layout: 'vertical',
262
+ * options: [
263
+ * { value: 'a', label: 'Immobilier', metadata: { confidence: 0.9, source: 'llm' } },
264
+ * { value: 'b', label: 'Santé', metadata: { confidence: 0.4, source: 'llm' } },
265
+ * ],
266
+ * optionRenderer: (opt: ChoiceOption<ConfBadgeMeta>) => (
267
+ * <div>
268
+ * {opt.label}
269
+ * <span class="ml-2 text-xs">
270
+ * ({Math.round((opt.metadata?.confidence ?? 0) * 100)}%)
271
+ * </span>
272
+ * </div>
273
+ * ),
274
+ * },
275
+ * } as ChatPromptConfig)
276
+ * ```
277
+ *
278
+ * ### ⚠️ Accessibility
279
+ * Do NOT return `<button>`, `<a href>`, or other interactive elements from
280
+ * `optionRenderer`. mcp-ui already wraps the content in a `<button>`, and
281
+ * nested interactive elements break screen-reader semantics, keyboard
282
+ * focus order, and click-through behaviour.
283
+ *
284
+ * ### ⚠️ Stale closures
285
+ * `optionRenderer` is called once per option per render. If you capture
286
+ * SolidJS signals inside the closure, wrap the access in a thunk so the
287
+ * framework tracks the dependency correctly. Don't destructure signal
288
+ * values into locals outside reactive scopes.
289
+ *
290
+ * @since v5.1.0
291
+ */
292
+ optionRenderer?: (option: ChoiceOption<TMeta>, index: number) => JSX.Element;
293
+ /**
294
+ * Custom Tailwind classes appended to each option button (after mcp-ui's
295
+ * defaults). Escape hatch for colour/border/radius tweaks that don't
296
+ * warrant a full `optionRenderer`.
297
+ *
298
+ * @since v5.1.0
299
+ */
300
+ buttonClass?: string;
301
+ /**
302
+ * Custom Tailwind classes appended to the options container (the
303
+ * flex/grid wrapper that lays out the buttons).
304
+ *
305
+ * @since v5.1.0
306
+ */
307
+ containerClass?: string;
202
308
  }
203
309
  export interface ConfirmPromptConfig {
204
310
  message?: string;
@@ -445,6 +551,42 @@ export interface ToolCallEvent {
445
551
  results?: unknown;
446
552
  duration_ms?: number;
447
553
  }
554
+ /**
555
+ * MCP `elicitation/create` request payload — server asks the client to
556
+ * collect input from the user according to a JSON Schema.
557
+ *
558
+ * Derived from MCP spec 2025-06-18. See
559
+ * `elicitationToPromptConfig()` in `services/chat-bus.ts` for the
560
+ * helper that converts this to a `ChatPromptConfig`.
561
+ *
562
+ * @experimental
563
+ * @since v5.2.0
564
+ */
565
+ export interface ElicitationEvent {
566
+ /** Question / instruction to present to the user. */
567
+ message: string;
568
+ /** JSON Schema describing the expected response shape. Object with primitive properties only. */
569
+ requestedSchema: ElicitationRequestedSchema;
570
+ }
571
+ export interface ElicitationRequestedSchema {
572
+ type: 'object';
573
+ properties: Record<string, ElicitationPropertySchema>;
574
+ required?: string[];
575
+ }
576
+ export interface ElicitationPropertySchema {
577
+ type: 'string' | 'number' | 'integer' | 'boolean';
578
+ title?: string;
579
+ description?: string;
580
+ /** Enum of allowed values (strings or numbers). */
581
+ enum?: Array<string | number>;
582
+ /** Parallel array with display labels for each enum entry. */
583
+ enumNames?: string[];
584
+ default?: unknown;
585
+ minimum?: number;
586
+ maximum?: number;
587
+ /** String format hint — date, date-time, email, uri. */
588
+ format?: 'date' | 'date-time' | 'email' | 'uri';
589
+ }
448
590
  export interface ClarificationEvent {
449
591
  /** The question to ask the user */
450
592
  question: string;