@seed-ship/mcp-ui-solid 4.3.8 → 4.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +63 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/services/chat-bus.cjs +22 -0
- package/dist/services/chat-bus.cjs.map +1 -1
- package/dist/services/chat-bus.d.ts +16 -1
- package/dist/services/chat-bus.d.ts.map +1 -1
- package/dist/services/chat-bus.js +22 -0
- package/dist/services/chat-bus.js.map +1 -1
- package/dist/stores/scratchpad-store.cjs.map +1 -1
- package/dist/stores/scratchpad-store.d.ts +7 -0
- package/dist/stores/scratchpad-store.d.ts.map +1 -1
- package/dist/stores/scratchpad-store.js.map +1 -1
- package/dist/testing/index.cjs +19 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.ts +35 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +19 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types/chat-bus.d.ts +58 -14
- package/dist/types/chat-bus.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +7 -1
- package/src/services/chat-bus.test.ts +80 -2
- package/src/services/chat-bus.ts +42 -0
- package/src/stores/scratchpad-store.ts +7 -0
- package/src/testing/index.test.ts +66 -0
- package/src/testing/index.ts +50 -0
- package/src/types/chat-bus.ts +58 -11
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,65 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.3.9] - 2026-04-14
|
|
9
|
+
|
|
10
|
+
### Added — Sprint 52 multi-agent primitives
|
|
11
|
+
|
|
12
|
+
#### Type additions (all non-breaking)
|
|
13
|
+
- **`ChoicePromptConfig.options[].metadata?: Record<string, unknown>`** (G1) — free-form metadata on prompt choices (confidence, source, tags...). Opaque to the default renderer, preserved through `showChatPrompt → ChatPromptResponse` roundtrip. Use a custom ChoiceBody wrapper to display it.
|
|
14
|
+
- **`ClarificationEvent.options[].metadata?: Record<string, unknown>`** (G3) — same extension point on clarification events. Legacy `file_id?: number` deprecated in JSDoc (removal in v5.0.0).
|
|
15
|
+
- **`ClarificationEvent.type?: string`** (G3) — free-form type tag for host routing (e.g. `'intent_disambiguate'`, `'file_select'`).
|
|
16
|
+
|
|
17
|
+
#### New helper: `clarificationToPromptConfig()` (G11)
|
|
18
|
+
- Universal bridge converting `ClarificationEvent` → `ChatPromptConfig` for any MCP-UI consumer
|
|
19
|
+
- Transparent `file_id` legacy migration into `metadata.file_id`
|
|
20
|
+
- Explicit `metadata.file_id` takes precedence over legacy field
|
|
21
|
+
- Agnostic — zero Deposium-specific concepts
|
|
22
|
+
- Exported from `@seed-ship/mcp-ui-solid`
|
|
23
|
+
|
|
24
|
+
#### New testing entry point: `createMockChatBus()` (G6)
|
|
25
|
+
- New sub-module `src/testing/` with `createMockChatBus({ promptResponses, onShowChatPrompt })`
|
|
26
|
+
- Pre-programs `showChatPrompt` responses in FIFO order for flow tests
|
|
27
|
+
- Spy hook on `onShowChatPrompt` for assertions
|
|
28
|
+
- Throws helpful error when the queue is exhausted
|
|
29
|
+
|
|
30
|
+
### Removed — dead code (G7)
|
|
31
|
+
- **BREAKING (theoretical, never implemented)**: `ChatPromptConfig.type = 'select'` removed from the union type. `SelectPromptConfig` interface removed + export dropped from `src/index.ts`. The `'select'` variant was declared in v4.0 but `ChatPrompt.tsx` never had a rendering branch — it was dead code. Use `'form'` with a single `select` field, or `'choice'` for visual picks.
|
|
32
|
+
|
|
33
|
+
### Documented — known limitations (G5, G8, G9, G10)
|
|
34
|
+
- **`ChatPromptResponse.dismissed`** (G5) — full semantics in JSDoc: X icon/Cancel → `true`, explicit click/submit → `undefined`, AbortSignal → Promise rejection (host responsibility until v4.4.0).
|
|
35
|
+
- **Scratchpad store is a singleton** (G8) — two `ScratchpadPanel` instances share state. Documented as known limitation. Factory `createScratchpadStore()` planned for v4.4.0.
|
|
36
|
+
- **`showChatPrompt` is not re-entrant** (G9) — calling it while another prompt is active leaks the previous Promise. Documented in JSDoc. Host apps must queue/dismiss manually. Auto-reject planned for v4.4.0.
|
|
37
|
+
- **`correlationId` is host-propagated** (G10) — README recipe. mcp-ui does not auto-forward the ID; host SSE parsers must thread it into subsequent event emissions.
|
|
38
|
+
|
|
39
|
+
### Documented — integration recipes (G2)
|
|
40
|
+
- **Bridging external clarification events** — new README section showing the `onClarificationNeeded → clarificationToPromptConfig → showChatPrompt` flow, with metadata preservation and opaque type tags.
|
|
41
|
+
|
|
42
|
+
### Documentation catch-up
|
|
43
|
+
- Backfilled `CHANGELOG.md` entries for 4.3.6, 4.3.7, 4.3.8 (previously missing).
|
|
44
|
+
|
|
45
|
+
## [4.3.8] - 2026-04-11
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- **Search term highlighting** — matched query terms are wrapped in `<mark>` tags across all visible cells. `bg-yellow-200` in light mode, `bg-[#222F49]` in dark mode. New `highlightQuery` helper skips HTML tag content to preserve markup.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- **Fullscreen phantom scrollbar** — removed `h-full` from the table wrapper in expanded mode so it shrinks to content. No more empty space or unnecessary scrollbar when rows don't fill the viewport.
|
|
52
|
+
|
|
53
|
+
## [4.3.7] - 2026-04-11
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
- **Prev/Next pagination is now the default** — replaced the progressive "show more" mode with unified Prev/Next navigation for consistency between chat and fullscreen views.
|
|
57
|
+
- **Page size selector in fullscreen** — dropdown with 10 / 30 / 60 / 100 / All options.
|
|
58
|
+
- **Fullscreen table fills the viewport** via `calc(100vh - 180px)`.
|
|
59
|
+
- **Header contrast** — thead background bumped from `bg-gray-50` to `bg-gray-100` for better visibility in chat view.
|
|
60
|
+
|
|
61
|
+
## [4.3.6] - 2026-04-11
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
- **Opaque sticky header** — changed from `bg-gray-900/50` (translucent) to `bg-gray-900` (opaque) so the header remains readable over chat bubbles behind it.
|
|
65
|
+
- **Compact search input** — `max-w-xs min-w-[200px]` instead of `w-full` so the filter field doesn't span the entire table width.
|
|
66
|
+
|
|
8
67
|
## [4.3.5] - 2026-04-11
|
|
9
68
|
|
|
10
69
|
### Fixed — Sticky Table Header on Scroll
|
package/README.md
CHANGED
|
@@ -347,6 +347,65 @@ function ChatInterface() {
|
|
|
347
347
|
|
|
348
348
|
See [Chat Bus documentation](https://github.com/theseedship/mcp-ui#chat-bus--agent-interactions-experimental) for the full event/command reference.
|
|
349
349
|
|
|
350
|
+
### Bridging external clarification events (v4.3.9)
|
|
351
|
+
|
|
352
|
+
When your MCP server emits a clarification event via SSE (e.g. a `_pause`
|
|
353
|
+
frame asking the user to disambiguate intent), convert it to a `ChatPrompt`
|
|
354
|
+
using the universal `clarificationToPromptConfig` helper — no app-specific
|
|
355
|
+
glue required:
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
import { clarificationToPromptConfig } from '@seed-ship/mcp-ui-solid'
|
|
359
|
+
|
|
360
|
+
// In your SSE parser, when you decode a clarification frame:
|
|
361
|
+
bus.events.emit('onClarificationNeeded', {
|
|
362
|
+
streamKey: 'main',
|
|
363
|
+
clarification: {
|
|
364
|
+
question: 'Which space do you mean?',
|
|
365
|
+
options: [
|
|
366
|
+
{ value: 'sp-1', label: 'Immobilier', metadata: { confidence: 0.9 } },
|
|
367
|
+
{ value: 'sp-2', label: 'Santé', metadata: { confidence: 0.6 } },
|
|
368
|
+
],
|
|
369
|
+
type: 'intent_disambiguate', // opaque tag for host routing
|
|
370
|
+
},
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
// Wire the event to a prompt:
|
|
374
|
+
bus.events.on('onClarificationNeeded', async ({ clarification }) => {
|
|
375
|
+
const response = await bus.commands.exec(
|
|
376
|
+
'showChatPrompt',
|
|
377
|
+
clarificationToPromptConfig(clarification)
|
|
378
|
+
)
|
|
379
|
+
// POST response.value to your /api/agent-resume endpoint, etc.
|
|
380
|
+
})
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Legacy `option.file_id` is automatically migrated into `metadata.file_id`.
|
|
384
|
+
Arbitrary `metadata` (confidence scores, source tags, ...) flows through
|
|
385
|
+
unchanged and can be rendered by a custom `ChoiceBody` wrapper.
|
|
386
|
+
|
|
387
|
+
### ChatPromptResponse — dismissed / aborted / answered (v4.3.9)
|
|
388
|
+
|
|
389
|
+
Every `ChatPrompt` resolves to one of three outcomes:
|
|
390
|
+
|
|
391
|
+
| Outcome | How | `response.dismissed` | Promise |
|
|
392
|
+
|---------|-----|----------------------|---------|
|
|
393
|
+
| Explicit answer | Click a choice / submit a form | `undefined` | resolves |
|
|
394
|
+
| Dismissed | Click the X icon, click Cancel (confirm type) | `true` | resolves |
|
|
395
|
+
| Aborted | Host app rejects the Promise via `AbortSignal` | *(never resolves)* | rejects |
|
|
396
|
+
|
|
397
|
+
> **v4.3.9 limitation:** `ChatPrompt` does not listen to `AbortSignal` yet.
|
|
398
|
+
> Host apps are responsible for wiring `signal.addEventListener('abort', ...)`
|
|
399
|
+
> to `Promise.reject`. A built-in helper lands in v4.4.0.
|
|
400
|
+
|
|
401
|
+
### correlationId — host-propagated (v4.3.9)
|
|
402
|
+
|
|
403
|
+
`ChatEventBase.correlationId` is opaque to mcp-ui. When an agent calls
|
|
404
|
+
`sendPrompt('...')` the returned string is a correlation ID — the host app's
|
|
405
|
+
SSE parser must forward this value into every subsequent event emission
|
|
406
|
+
(`onToken`, `onStreamEnd`, etc.) so agents can match responses back to their
|
|
407
|
+
original prompts. mcp-ui does not auto-propagate it across the bus.
|
|
408
|
+
|
|
350
409
|
## ScratchpadPanel — HITL/AITL Shared Workspace (`@experimental`)
|
|
351
410
|
|
|
352
411
|
A shared workspace where agent and human collaborate in real-time. 18 section types:
|
|
@@ -438,8 +497,12 @@ import {
|
|
|
438
497
|
ChatBusProvider, useChatBus,
|
|
439
498
|
dispatchScratchpad, useScratchpadState,
|
|
440
499
|
createChatBus, createEventEmitter, createCommandHandler,
|
|
500
|
+
clarificationToPromptConfig, // v4.3.9 — universal ClarificationEvent → ChatPromptConfig bridge
|
|
441
501
|
} from '@seed-ship/mcp-ui-solid'
|
|
442
502
|
|
|
503
|
+
// Testing utilities (v4.3.9)
|
|
504
|
+
import { createMockChatBus } from '@seed-ship/mcp-ui-solid'
|
|
505
|
+
|
|
443
506
|
// Validation + Security
|
|
444
507
|
import {
|
|
445
508
|
validateComponent, validateLayout,
|
package/dist/index.cjs
CHANGED
|
@@ -52,6 +52,7 @@ const validation = require("./services/validation.cjs");
|
|
|
52
52
|
const componentRegistry = require("./services/component-registry.cjs");
|
|
53
53
|
const chatBus = require("./services/chat-bus.cjs");
|
|
54
54
|
const dataValidator = require("./services/data-validator.cjs");
|
|
55
|
+
const index = require("./testing/index.cjs");
|
|
55
56
|
exports.UIResourceRenderer = UIResourceRenderer.UIResourceRenderer;
|
|
56
57
|
exports.StreamingUIRenderer = StreamingUIRenderer.StreamingUIRenderer;
|
|
57
58
|
exports.GenerativeUIErrorBoundary = GenerativeUIErrorBoundary.GenerativeUIErrorBoundary;
|
|
@@ -108,9 +109,11 @@ exports.validateComponent = validation.validateComponent;
|
|
|
108
109
|
exports.validateIframeDomain = validation.validateIframeDomain;
|
|
109
110
|
exports.validateLayout = validation.validateLayout;
|
|
110
111
|
exports.ComponentRegistry = componentRegistry.ComponentRegistry;
|
|
112
|
+
exports.clarificationToPromptConfig = chatBus.clarificationToPromptConfig;
|
|
111
113
|
exports.createChatBus = chatBus.createChatBus;
|
|
112
114
|
exports.createCommandHandler = chatBus.createCommandHandler;
|
|
113
115
|
exports.createEventEmitter = chatBus.createEventEmitter;
|
|
114
116
|
exports.mergeScratchpadSections = chatBus.mergeScratchpadSections;
|
|
115
117
|
exports.validateAgainstSource = dataValidator.validateAgainstSource;
|
|
118
|
+
exports.createMockChatBus = index.createMockChatBus;
|
|
116
119
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.d.cts
CHANGED
|
@@ -72,5 +72,8 @@ export type { AutocompleteContextValue, AutocompleteProviderProps, } from './con
|
|
|
72
72
|
export { createGroqPlugin, createSupabasePlugin, createRestPlugin, } from './plugins';
|
|
73
73
|
export type { UIComponent, UILayout, GridPosition, ComponentType, RendererError, ChartComponentParams, TableComponentParams, MetricComponentParams, TextComponentParams, ActionComponentParams, GridComponentParams, FormFieldOption, FormFieldType, FormFieldParams, FormComponentParams, ShowWhenOperator, ShowWhenCondition, ActionRequestBase, ActionResultBase, ActionLifecycleCallbacks, ModalSize, ModalComponentParams, ActionGroupLayout, ActionGroupGap, ActionGroupParams, GalleryImage, ImageGalleryParams, VideoComponentParams, CodeComponentParams, MapMarker, MapComponentParams, MapPopupConfig, MapGeoJSONStyle, MapLayer, MapPMTilesConfig, IframePolicy, ValidationOptions, ResizeConstraints, DragDropConfig, DragEventData, DraggableGridItemProps as DraggableGridItemPropsType, AutocompleteResultType, AutocompleteOption, AutocompleteResult, AutocompleteContext, AutocompletePlugin, GroqPluginConfig, SupabasePluginConfig, DuckDBPluginConfig, RestPluginConfig, FieldAutocompleteConfig, AutocompleteProviderConfig, } from './types';
|
|
74
74
|
export { validateComponent, validateLayout, validateIframeDomain, getIframeSandbox, DEFAULT_RESOURCE_LIMITS, DEFAULT_IFRAME_DOMAINS, TRUSTED_IFRAME_DOMAINS, ComponentRegistry, createEventEmitter, createCommandHandler, createChatBus, mergeScratchpadSections, validateAgainstSource, } from './services';
|
|
75
|
-
export
|
|
75
|
+
export { clarificationToPromptConfig } from './services/chat-bus';
|
|
76
|
+
export { createMockChatBus } from './testing';
|
|
77
|
+
export type { MockChatBusOptions } from './testing';
|
|
78
|
+
export type { ChatEventBase, ChatEvents, ChatCommands, ChatBus, ChatEventEmitter, ChatCommandHandler, EventSubscribeOptions, ChatPromptConfig, ChatPromptResponse, ChoicePromptConfig, ConfirmPromptConfig, FormPromptConfig, SuggestionItem, AgentContext, BriefingEvent, BriefingSection, ScratchpadState, ScratchpadSection, ScratchpadEvent, StreamDoneMetadata, ChatError, Citation, ToolCallEvent, ClarificationEvent, DataValidation, LLMNumber, HallucinatedNumber, DataValidationOptions, VerifiedTextContent, DataPreviewColumn, DataPreviewContent, MapSectionContent, AgentCardContent, SplitStepperContent, AgentHandoffContent, BriefingDiffContent, } from './types/chat-bus';
|
|
76
79
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -72,5 +72,8 @@ export type { AutocompleteContextValue, AutocompleteProviderProps, } from './con
|
|
|
72
72
|
export { createGroqPlugin, createSupabasePlugin, createRestPlugin, } from './plugins';
|
|
73
73
|
export type { UIComponent, UILayout, GridPosition, ComponentType, RendererError, ChartComponentParams, TableComponentParams, MetricComponentParams, TextComponentParams, ActionComponentParams, GridComponentParams, FormFieldOption, FormFieldType, FormFieldParams, FormComponentParams, ShowWhenOperator, ShowWhenCondition, ActionRequestBase, ActionResultBase, ActionLifecycleCallbacks, ModalSize, ModalComponentParams, ActionGroupLayout, ActionGroupGap, ActionGroupParams, GalleryImage, ImageGalleryParams, VideoComponentParams, CodeComponentParams, MapMarker, MapComponentParams, MapPopupConfig, MapGeoJSONStyle, MapLayer, MapPMTilesConfig, IframePolicy, ValidationOptions, ResizeConstraints, DragDropConfig, DragEventData, DraggableGridItemProps as DraggableGridItemPropsType, AutocompleteResultType, AutocompleteOption, AutocompleteResult, AutocompleteContext, AutocompletePlugin, GroqPluginConfig, SupabasePluginConfig, DuckDBPluginConfig, RestPluginConfig, FieldAutocompleteConfig, AutocompleteProviderConfig, } from './types';
|
|
74
74
|
export { validateComponent, validateLayout, validateIframeDomain, getIframeSandbox, DEFAULT_RESOURCE_LIMITS, DEFAULT_IFRAME_DOMAINS, TRUSTED_IFRAME_DOMAINS, ComponentRegistry, createEventEmitter, createCommandHandler, createChatBus, mergeScratchpadSections, validateAgainstSource, } from './services';
|
|
75
|
-
export
|
|
75
|
+
export { clarificationToPromptConfig } from './services/chat-bus';
|
|
76
|
+
export { createMockChatBus } from './testing';
|
|
77
|
+
export type { MockChatBusOptions } from './testing';
|
|
78
|
+
export type { ChatEventBase, ChatEvents, ChatCommands, ChatBus, ChatEventEmitter, ChatCommandHandler, EventSubscribeOptions, ChatPromptConfig, ChatPromptResponse, ChoicePromptConfig, ConfirmPromptConfig, FormPromptConfig, SuggestionItem, AgentContext, BriefingEvent, BriefingSection, ScratchpadState, ScratchpadSection, ScratchpadEvent, StreamDoneMetadata, ChatError, Citation, ToolCallEvent, ClarificationEvent, DataValidation, LLMNumber, HallucinatedNumber, DataValidationOptions, VerifiedTextContent, DataPreviewColumn, DataPreviewContent, MapSectionContent, AgentCardContent, SplitStepperContent, AgentHandoffContent, BriefingDiffContent, } from './types/chat-bus';
|
|
76
79
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -48,8 +48,9 @@ import { createSupabasePlugin } from "./plugins/supabase.js";
|
|
|
48
48
|
import { createRestPlugin } from "./plugins/rest.js";
|
|
49
49
|
import { DEFAULT_IFRAME_DOMAINS, DEFAULT_RESOURCE_LIMITS, TRUSTED_IFRAME_DOMAINS, getIframeSandbox, validateComponent, validateIframeDomain, validateLayout } from "./services/validation.js";
|
|
50
50
|
import { ComponentRegistry } from "./services/component-registry.js";
|
|
51
|
-
import { createChatBus, createCommandHandler, createEventEmitter, mergeScratchpadSections } from "./services/chat-bus.js";
|
|
51
|
+
import { clarificationToPromptConfig, createChatBus, createCommandHandler, createEventEmitter, mergeScratchpadSections } from "./services/chat-bus.js";
|
|
52
52
|
import { validateAgainstSource } from "./services/data-validator.js";
|
|
53
|
+
import { createMockChatBus } from "./testing/index.js";
|
|
53
54
|
export {
|
|
54
55
|
AgentCard,
|
|
55
56
|
AgentHandoff,
|
|
@@ -80,10 +81,12 @@ export {
|
|
|
80
81
|
TRUSTED_IFRAME_DOMAINS,
|
|
81
82
|
UIResourceRenderer,
|
|
82
83
|
VerifiedText,
|
|
84
|
+
clarificationToPromptConfig,
|
|
83
85
|
createChatBus,
|
|
84
86
|
createCommandHandler,
|
|
85
87
|
createEventEmitter,
|
|
86
88
|
createGroqPlugin,
|
|
89
|
+
createMockChatBus,
|
|
87
90
|
createRestPlugin,
|
|
88
91
|
createSupabasePlugin,
|
|
89
92
|
dispatchScratchpad,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -134,6 +134,28 @@ function mergeScratchpadSections(existing, incoming, mode = "replace") {
|
|
|
134
134
|
return incoming;
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
+
function clarificationToPromptConfig(event) {
|
|
138
|
+
return {
|
|
139
|
+
type: "choice",
|
|
140
|
+
title: event.question,
|
|
141
|
+
config: {
|
|
142
|
+
options: event.options.map((opt) => {
|
|
143
|
+
const merged = { ...opt.metadata ?? {} };
|
|
144
|
+
if (opt.file_id !== void 0 && merged.file_id === void 0) {
|
|
145
|
+
merged.file_id = opt.file_id;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
value: opt.value,
|
|
149
|
+
label: opt.label,
|
|
150
|
+
// Only include metadata if non-empty (keeps payloads clean)
|
|
151
|
+
...Object.keys(merged).length > 0 ? { metadata: merged } : {}
|
|
152
|
+
};
|
|
153
|
+
}),
|
|
154
|
+
layout: "vertical"
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
exports.clarificationToPromptConfig = clarificationToPromptConfig;
|
|
137
159
|
exports.createChatBus = createChatBus;
|
|
138
160
|
exports.createCommandHandler = createCommandHandler;
|
|
139
161
|
exports.createEventEmitter = createEventEmitter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-bus.cjs","sources":["../../src/services/chat-bus.ts"],"sourcesContent":["/**\n * Chat Bus — Event Emitter + Command Handler\n * v2.4.0: Core primitives for the chat event/command bus\n *\n * @experimental — This API may change without major bump until v2.5.0.\n */\n\nimport type {\n ChatEvents,\n ChatCommands,\n ChatEventEmitter,\n ChatCommandHandler,\n ChatBus,\n EventSubscribeOptions,\n ScratchpadSection,\n} from '../types/chat-bus'\n\n// ─── Event Emitter ───────────────────────────────────────────\n\ninterface Listener<F extends (...args: any[]) => any> {\n handler: F\n options?: EventSubscribeOptions\n throttledHandler?: F\n}\n\n/**\n * Create a typed event emitter with throttle and streamKey filtering support.\n *\n * @experimental\n *\n * @example\n * const emitter = createEventEmitter<ChatEvents>()\n * const unsub = emitter.on('onToken', (event) => console.log(event.token), { throttle: 100 })\n * emitter.emit('onToken', { streamKey: 'abc', token: 'hello' })\n * unsub()\n */\nexport function createEventEmitter(): ChatEventEmitter {\n const listeners = new Map<string, Set<Listener<any>>>()\n\n interface ThrottledFn<F> {\n fn: F\n cancel: () => void\n }\n\n function createThrottled<F extends (...args: any[]) => void>(fn: F, ms: number): ThrottledFn<F> {\n let lastCall = 0\n let timer: ReturnType<typeof setTimeout> | null = null\n let lastArgs: any[] | null = null\n let cancelled = false\n\n const throttled = ((...args: any[]) => {\n if (cancelled) return\n lastArgs = args\n const now = Date.now()\n const remaining = ms - (now - lastCall)\n\n if (remaining <= 0) {\n if (timer) { clearTimeout(timer); timer = null }\n lastCall = now\n fn(...args)\n } else if (!timer) {\n timer = setTimeout(() => {\n lastCall = Date.now()\n timer = null\n if (lastArgs && !cancelled) {\n try { fn(...lastArgs) } catch (err) { console.error('[ChatBus] Error in throttled handler:', err) }\n }\n }, remaining)\n }\n }) as F\n\n return {\n fn: throttled,\n cancel: () => { cancelled = true; if (timer) { clearTimeout(timer); timer = null } },\n }\n }\n\n return {\n on(event, handler, options) {\n if (!listeners.has(event as string)) {\n listeners.set(event as string, new Set())\n }\n\n const listener: Listener<typeof handler> = { handler, options }\n\n // Apply throttle if requested\n let throttleHandle: ThrottledFn<typeof handler> | null = null\n if (options?.throttle && options.throttle > 0) {\n throttleHandle = createThrottled(handler, options.throttle)\n listener.throttledHandler = throttleHandle.fn\n }\n\n listeners.get(event as string)!.add(listener)\n\n // Return unsubscribe function — cancels pending throttle timers\n return () => {\n throttleHandle?.cancel()\n listeners.get(event as string)?.delete(listener)\n }\n },\n\n emit(event, ...args) {\n const set = listeners.get(event as string)\n if (!set) return\n\n for (const listener of set) {\n // StreamKey filtering: skip if listener wants a specific streamKey\n // For most events args[0] has streamKey; for onCustomEvent args[1] has it\n if (listener.options?.streamKey) {\n let streamKeyArg: unknown\n for (const arg of args) {\n if (arg && typeof arg === 'object' && 'streamKey' in (arg as any)) {\n streamKeyArg = (arg as any).streamKey\n break\n }\n }\n if (streamKeyArg !== undefined && streamKeyArg !== listener.options.streamKey) continue\n }\n\n const fn = listener.throttledHandler || listener.handler\n try {\n fn(...args)\n } catch (err) {\n console.error(`[ChatBus] Error in ${event as string} handler:`, err)\n }\n }\n },\n\n clear() {\n listeners.clear()\n },\n } as ChatEventEmitter\n}\n\n// ─── Command Handler ─────────────────────────────────────────\n\n/**\n * Create a typed command handler. The host app registers handlers,\n * agents execute commands.\n *\n * @experimental\n *\n * @example\n * const commands = createCommandHandler<ChatCommands>()\n * commands.handle('injectPrompt', (text) => setInputValue(text))\n * commands.exec('injectPrompt', 'Hello world')\n */\nexport function createCommandHandler(): ChatCommandHandler {\n const handlers = new Map<string, (...args: any[]) => any>()\n\n return {\n handle(command, handler) {\n handlers.set(command as string, handler)\n },\n\n exec(command, ...args) {\n const handler = handlers.get(command as string)\n if (!handler) {\n console.warn(`[ChatBus] No handler registered for command: ${command as string}`)\n return undefined as any\n }\n return handler(...args)\n },\n } as ChatCommandHandler\n}\n\n// ─── Chat Bus Factory ────────────────────────────────────────\n\n/**\n * Create a complete ChatBus with events + commands.\n *\n * @experimental\n *\n * @example\n * const bus = createChatBus()\n * bus.events.on('onStreamEnd', (event) => { ... })\n * bus.commands.handle('sendPrompt', (text) => { ... })\n */\nexport function createChatBus(): ChatBus {\n return {\n events: createEventEmitter(),\n commands: createCommandHandler(),\n }\n}\n\n// ─── Scratchpad Section Merge Helper ─────────────────────────\n\n/**\n * Merge sections from a ScratchpadEvent into existing state sections.\n * Handles replace/append/upsert modes.\n *\n * @example\n * const newSections = mergeScratchpadSections(\n * currentState.sections,\n * event.sections,\n * event.sectionMode\n * )\n */\nexport function mergeScratchpadSections(\n existing: ScratchpadSection[],\n incoming: ScratchpadSection[] | undefined,\n mode: 'replace' | 'append' | 'upsert' = 'replace'\n): ScratchpadSection[] {\n if (!incoming) return existing\n\n switch (mode) {\n case 'append':\n return [...existing, ...incoming]\n\n case 'upsert': {\n const result = [...existing]\n for (const section of incoming) {\n const idx = result.findIndex((s) => s.id === section.id)\n if (idx >= 0) {\n result[idx] = section\n } else {\n result.push(section)\n }\n }\n return result\n }\n\n case 'replace':\n default:\n return incoming\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-bus.cjs","sources":["../../src/services/chat-bus.ts"],"sourcesContent":["/**\n * Chat Bus — Event Emitter + Command Handler\n * v2.4.0: Core primitives for the chat event/command bus\n *\n * @experimental — This API may change without major bump until v2.5.0.\n */\n\nimport type {\n ChatEvents,\n ChatCommands,\n ChatEventEmitter,\n ChatCommandHandler,\n ChatBus,\n EventSubscribeOptions,\n ScratchpadSection,\n ClarificationEvent,\n ChatPromptConfig,\n} from '../types/chat-bus'\n\n// ─── Event Emitter ───────────────────────────────────────────\n\ninterface Listener<F extends (...args: any[]) => any> {\n handler: F\n options?: EventSubscribeOptions\n throttledHandler?: F\n}\n\n/**\n * Create a typed event emitter with throttle and streamKey filtering support.\n *\n * @experimental\n *\n * @example\n * const emitter = createEventEmitter<ChatEvents>()\n * const unsub = emitter.on('onToken', (event) => console.log(event.token), { throttle: 100 })\n * emitter.emit('onToken', { streamKey: 'abc', token: 'hello' })\n * unsub()\n */\nexport function createEventEmitter(): ChatEventEmitter {\n const listeners = new Map<string, Set<Listener<any>>>()\n\n interface ThrottledFn<F> {\n fn: F\n cancel: () => void\n }\n\n function createThrottled<F extends (...args: any[]) => void>(fn: F, ms: number): ThrottledFn<F> {\n let lastCall = 0\n let timer: ReturnType<typeof setTimeout> | null = null\n let lastArgs: any[] | null = null\n let cancelled = false\n\n const throttled = ((...args: any[]) => {\n if (cancelled) return\n lastArgs = args\n const now = Date.now()\n const remaining = ms - (now - lastCall)\n\n if (remaining <= 0) {\n if (timer) { clearTimeout(timer); timer = null }\n lastCall = now\n fn(...args)\n } else if (!timer) {\n timer = setTimeout(() => {\n lastCall = Date.now()\n timer = null\n if (lastArgs && !cancelled) {\n try { fn(...lastArgs) } catch (err) { console.error('[ChatBus] Error in throttled handler:', err) }\n }\n }, remaining)\n }\n }) as F\n\n return {\n fn: throttled,\n cancel: () => { cancelled = true; if (timer) { clearTimeout(timer); timer = null } },\n }\n }\n\n return {\n on(event, handler, options) {\n if (!listeners.has(event as string)) {\n listeners.set(event as string, new Set())\n }\n\n const listener: Listener<typeof handler> = { handler, options }\n\n // Apply throttle if requested\n let throttleHandle: ThrottledFn<typeof handler> | null = null\n if (options?.throttle && options.throttle > 0) {\n throttleHandle = createThrottled(handler, options.throttle)\n listener.throttledHandler = throttleHandle.fn\n }\n\n listeners.get(event as string)!.add(listener)\n\n // Return unsubscribe function — cancels pending throttle timers\n return () => {\n throttleHandle?.cancel()\n listeners.get(event as string)?.delete(listener)\n }\n },\n\n emit(event, ...args) {\n const set = listeners.get(event as string)\n if (!set) return\n\n for (const listener of set) {\n // StreamKey filtering: skip if listener wants a specific streamKey\n // For most events args[0] has streamKey; for onCustomEvent args[1] has it\n if (listener.options?.streamKey) {\n let streamKeyArg: unknown\n for (const arg of args) {\n if (arg && typeof arg === 'object' && 'streamKey' in (arg as any)) {\n streamKeyArg = (arg as any).streamKey\n break\n }\n }\n if (streamKeyArg !== undefined && streamKeyArg !== listener.options.streamKey) continue\n }\n\n const fn = listener.throttledHandler || listener.handler\n try {\n fn(...args)\n } catch (err) {\n console.error(`[ChatBus] Error in ${event as string} handler:`, err)\n }\n }\n },\n\n clear() {\n listeners.clear()\n },\n } as ChatEventEmitter\n}\n\n// ─── Command Handler ─────────────────────────────────────────\n\n/**\n * Create a typed command handler. The host app registers handlers,\n * agents execute commands.\n *\n * @experimental\n *\n * @example\n * const commands = createCommandHandler<ChatCommands>()\n * commands.handle('injectPrompt', (text) => setInputValue(text))\n * commands.exec('injectPrompt', 'Hello world')\n */\nexport function createCommandHandler(): ChatCommandHandler {\n const handlers = new Map<string, (...args: any[]) => any>()\n\n return {\n handle(command, handler) {\n handlers.set(command as string, handler)\n },\n\n exec(command, ...args) {\n const handler = handlers.get(command as string)\n if (!handler) {\n console.warn(`[ChatBus] No handler registered for command: ${command as string}`)\n return undefined as any\n }\n return handler(...args)\n },\n } as ChatCommandHandler\n}\n\n// ─── Chat Bus Factory ────────────────────────────────────────\n\n/**\n * Create a complete ChatBus with events + commands.\n *\n * @experimental\n *\n * @example\n * const bus = createChatBus()\n * bus.events.on('onStreamEnd', (event) => { ... })\n * bus.commands.handle('sendPrompt', (text) => { ... })\n */\nexport function createChatBus(): ChatBus {\n return {\n events: createEventEmitter(),\n commands: createCommandHandler(),\n }\n}\n\n// ─── Scratchpad Section Merge Helper ─────────────────────────\n\n/**\n * Merge sections from a ScratchpadEvent into existing state sections.\n * Handles replace/append/upsert modes.\n *\n * @example\n * const newSections = mergeScratchpadSections(\n * currentState.sections,\n * event.sections,\n * event.sectionMode\n * )\n */\nexport function mergeScratchpadSections(\n existing: ScratchpadSection[],\n incoming: ScratchpadSection[] | undefined,\n mode: 'replace' | 'append' | 'upsert' = 'replace'\n): ScratchpadSection[] {\n if (!incoming) return existing\n\n switch (mode) {\n case 'append':\n return [...existing, ...incoming]\n\n case 'upsert': {\n const result = [...existing]\n for (const section of incoming) {\n const idx = result.findIndex((s) => s.id === section.id)\n if (idx >= 0) {\n result[idx] = section\n } else {\n result.push(section)\n }\n }\n return result\n }\n\n case 'replace':\n default:\n return incoming\n }\n}\n\n// ─── Clarification → Prompt Helper (v4.3.9) ──────────────────\n\n/**\n * Convert a ClarificationEvent into a ChatPromptConfig.\n * Universal bridge for apps receiving clarification events via SSE.\n *\n * Legacy `file_id` (deprecated) is transparently moved into `metadata.file_id`\n * so consumers don't need to handle both shapes.\n *\n * @experimental\n * @since v4.3.9\n * @example\n * bus.events.on('onClarificationNeeded', ({ clarification }) => {\n * bus.commands.exec('showChatPrompt', clarificationToPromptConfig(clarification))\n * })\n */\nexport function clarificationToPromptConfig(\n event: ClarificationEvent\n): ChatPromptConfig {\n return {\n type: 'choice',\n title: event.question,\n config: {\n options: event.options.map((opt) => {\n const merged: Record<string, unknown> = { ...(opt.metadata ?? {}) }\n if (opt.file_id !== undefined && merged.file_id === undefined) {\n merged.file_id = opt.file_id\n }\n return {\n value: opt.value,\n label: opt.label,\n // Only include metadata if non-empty (keeps payloads clean)\n ...(Object.keys(merged).length > 0 ? { metadata: merged } : {}),\n }\n }),\n layout: 'vertical',\n },\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @experimental — This API may change without major bump until v2.5.0.
|
|
6
6
|
*/
|
|
7
|
-
import type { ChatEventEmitter, ChatCommandHandler, ChatBus, ScratchpadSection } from '../types/chat-bus';
|
|
7
|
+
import type { ChatEventEmitter, ChatCommandHandler, ChatBus, ScratchpadSection, ClarificationEvent, ChatPromptConfig } from '../types/chat-bus';
|
|
8
8
|
/**
|
|
9
9
|
* Create a typed event emitter with throttle and streamKey filtering support.
|
|
10
10
|
*
|
|
@@ -52,4 +52,19 @@ export declare function createChatBus(): ChatBus;
|
|
|
52
52
|
* )
|
|
53
53
|
*/
|
|
54
54
|
export declare function mergeScratchpadSections(existing: ScratchpadSection[], incoming: ScratchpadSection[] | undefined, mode?: 'replace' | 'append' | 'upsert'): ScratchpadSection[];
|
|
55
|
+
/**
|
|
56
|
+
* Convert a ClarificationEvent into a ChatPromptConfig.
|
|
57
|
+
* Universal bridge for apps receiving clarification events via SSE.
|
|
58
|
+
*
|
|
59
|
+
* Legacy `file_id` (deprecated) is transparently moved into `metadata.file_id`
|
|
60
|
+
* so consumers don't need to handle both shapes.
|
|
61
|
+
*
|
|
62
|
+
* @experimental
|
|
63
|
+
* @since v4.3.9
|
|
64
|
+
* @example
|
|
65
|
+
* bus.events.on('onClarificationNeeded', ({ clarification }) => {
|
|
66
|
+
* bus.commands.exec('showChatPrompt', clarificationToPromptConfig(clarification))
|
|
67
|
+
* })
|
|
68
|
+
*/
|
|
69
|
+
export declare function clarificationToPromptConfig(event: ClarificationEvent): ChatPromptConfig;
|
|
55
70
|
//# sourceMappingURL=chat-bus.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-bus.d.ts","sourceRoot":"","sources":["../../src/services/chat-bus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAGV,gBAAgB,EAChB,kBAAkB,EAClB,OAAO,EAEP,iBAAiB,EAClB,MAAM,mBAAmB,CAAA;AAU1B;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,IAAI,gBAAgB,CAgGrD;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,IAAI,kBAAkB,CAiBzD;AAID;;;;;;;;;GASG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAKvC;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,QAAQ,EAAE,iBAAiB,EAAE,GAAG,SAAS,EACzC,IAAI,GAAE,SAAS,GAAG,QAAQ,GAAG,QAAoB,GAChD,iBAAiB,EAAE,CAwBrB"}
|
|
1
|
+
{"version":3,"file":"chat-bus.d.ts","sourceRoot":"","sources":["../../src/services/chat-bus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAGV,gBAAgB,EAChB,kBAAkB,EAClB,OAAO,EAEP,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,mBAAmB,CAAA;AAU1B;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,IAAI,gBAAgB,CAgGrD;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,IAAI,kBAAkB,CAiBzD;AAID;;;;;;;;;GASG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAKvC;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,QAAQ,EAAE,iBAAiB,EAAE,GAAG,SAAS,EACzC,IAAI,GAAE,SAAS,GAAG,QAAQ,GAAG,QAAoB,GAChD,iBAAiB,EAAE,CAwBrB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,kBAAkB,GACxB,gBAAgB,CAoBlB"}
|
|
@@ -132,7 +132,29 @@ function mergeScratchpadSections(existing, incoming, mode = "replace") {
|
|
|
132
132
|
return incoming;
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
+
function clarificationToPromptConfig(event) {
|
|
136
|
+
return {
|
|
137
|
+
type: "choice",
|
|
138
|
+
title: event.question,
|
|
139
|
+
config: {
|
|
140
|
+
options: event.options.map((opt) => {
|
|
141
|
+
const merged = { ...opt.metadata ?? {} };
|
|
142
|
+
if (opt.file_id !== void 0 && merged.file_id === void 0) {
|
|
143
|
+
merged.file_id = opt.file_id;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
value: opt.value,
|
|
147
|
+
label: opt.label,
|
|
148
|
+
// Only include metadata if non-empty (keeps payloads clean)
|
|
149
|
+
...Object.keys(merged).length > 0 ? { metadata: merged } : {}
|
|
150
|
+
};
|
|
151
|
+
}),
|
|
152
|
+
layout: "vertical"
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
135
156
|
export {
|
|
157
|
+
clarificationToPromptConfig,
|
|
136
158
|
createChatBus,
|
|
137
159
|
createCommandHandler,
|
|
138
160
|
createEventEmitter,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-bus.js","sources":["../../src/services/chat-bus.ts"],"sourcesContent":["/**\n * Chat Bus — Event Emitter + Command Handler\n * v2.4.0: Core primitives for the chat event/command bus\n *\n * @experimental — This API may change without major bump until v2.5.0.\n */\n\nimport type {\n ChatEvents,\n ChatCommands,\n ChatEventEmitter,\n ChatCommandHandler,\n ChatBus,\n EventSubscribeOptions,\n ScratchpadSection,\n} from '../types/chat-bus'\n\n// ─── Event Emitter ───────────────────────────────────────────\n\ninterface Listener<F extends (...args: any[]) => any> {\n handler: F\n options?: EventSubscribeOptions\n throttledHandler?: F\n}\n\n/**\n * Create a typed event emitter with throttle and streamKey filtering support.\n *\n * @experimental\n *\n * @example\n * const emitter = createEventEmitter<ChatEvents>()\n * const unsub = emitter.on('onToken', (event) => console.log(event.token), { throttle: 100 })\n * emitter.emit('onToken', { streamKey: 'abc', token: 'hello' })\n * unsub()\n */\nexport function createEventEmitter(): ChatEventEmitter {\n const listeners = new Map<string, Set<Listener<any>>>()\n\n interface ThrottledFn<F> {\n fn: F\n cancel: () => void\n }\n\n function createThrottled<F extends (...args: any[]) => void>(fn: F, ms: number): ThrottledFn<F> {\n let lastCall = 0\n let timer: ReturnType<typeof setTimeout> | null = null\n let lastArgs: any[] | null = null\n let cancelled = false\n\n const throttled = ((...args: any[]) => {\n if (cancelled) return\n lastArgs = args\n const now = Date.now()\n const remaining = ms - (now - lastCall)\n\n if (remaining <= 0) {\n if (timer) { clearTimeout(timer); timer = null }\n lastCall = now\n fn(...args)\n } else if (!timer) {\n timer = setTimeout(() => {\n lastCall = Date.now()\n timer = null\n if (lastArgs && !cancelled) {\n try { fn(...lastArgs) } catch (err) { console.error('[ChatBus] Error in throttled handler:', err) }\n }\n }, remaining)\n }\n }) as F\n\n return {\n fn: throttled,\n cancel: () => { cancelled = true; if (timer) { clearTimeout(timer); timer = null } },\n }\n }\n\n return {\n on(event, handler, options) {\n if (!listeners.has(event as string)) {\n listeners.set(event as string, new Set())\n }\n\n const listener: Listener<typeof handler> = { handler, options }\n\n // Apply throttle if requested\n let throttleHandle: ThrottledFn<typeof handler> | null = null\n if (options?.throttle && options.throttle > 0) {\n throttleHandle = createThrottled(handler, options.throttle)\n listener.throttledHandler = throttleHandle.fn\n }\n\n listeners.get(event as string)!.add(listener)\n\n // Return unsubscribe function — cancels pending throttle timers\n return () => {\n throttleHandle?.cancel()\n listeners.get(event as string)?.delete(listener)\n }\n },\n\n emit(event, ...args) {\n const set = listeners.get(event as string)\n if (!set) return\n\n for (const listener of set) {\n // StreamKey filtering: skip if listener wants a specific streamKey\n // For most events args[0] has streamKey; for onCustomEvent args[1] has it\n if (listener.options?.streamKey) {\n let streamKeyArg: unknown\n for (const arg of args) {\n if (arg && typeof arg === 'object' && 'streamKey' in (arg as any)) {\n streamKeyArg = (arg as any).streamKey\n break\n }\n }\n if (streamKeyArg !== undefined && streamKeyArg !== listener.options.streamKey) continue\n }\n\n const fn = listener.throttledHandler || listener.handler\n try {\n fn(...args)\n } catch (err) {\n console.error(`[ChatBus] Error in ${event as string} handler:`, err)\n }\n }\n },\n\n clear() {\n listeners.clear()\n },\n } as ChatEventEmitter\n}\n\n// ─── Command Handler ─────────────────────────────────────────\n\n/**\n * Create a typed command handler. The host app registers handlers,\n * agents execute commands.\n *\n * @experimental\n *\n * @example\n * const commands = createCommandHandler<ChatCommands>()\n * commands.handle('injectPrompt', (text) => setInputValue(text))\n * commands.exec('injectPrompt', 'Hello world')\n */\nexport function createCommandHandler(): ChatCommandHandler {\n const handlers = new Map<string, (...args: any[]) => any>()\n\n return {\n handle(command, handler) {\n handlers.set(command as string, handler)\n },\n\n exec(command, ...args) {\n const handler = handlers.get(command as string)\n if (!handler) {\n console.warn(`[ChatBus] No handler registered for command: ${command as string}`)\n return undefined as any\n }\n return handler(...args)\n },\n } as ChatCommandHandler\n}\n\n// ─── Chat Bus Factory ────────────────────────────────────────\n\n/**\n * Create a complete ChatBus with events + commands.\n *\n * @experimental\n *\n * @example\n * const bus = createChatBus()\n * bus.events.on('onStreamEnd', (event) => { ... })\n * bus.commands.handle('sendPrompt', (text) => { ... })\n */\nexport function createChatBus(): ChatBus {\n return {\n events: createEventEmitter(),\n commands: createCommandHandler(),\n }\n}\n\n// ─── Scratchpad Section Merge Helper ─────────────────────────\n\n/**\n * Merge sections from a ScratchpadEvent into existing state sections.\n * Handles replace/append/upsert modes.\n *\n * @example\n * const newSections = mergeScratchpadSections(\n * currentState.sections,\n * event.sections,\n * event.sectionMode\n * )\n */\nexport function mergeScratchpadSections(\n existing: ScratchpadSection[],\n incoming: ScratchpadSection[] | undefined,\n mode: 'replace' | 'append' | 'upsert' = 'replace'\n): ScratchpadSection[] {\n if (!incoming) return existing\n\n switch (mode) {\n case 'append':\n return [...existing, ...incoming]\n\n case 'upsert': {\n const result = [...existing]\n for (const section of incoming) {\n const idx = result.findIndex((s) => s.id === section.id)\n if (idx >= 0) {\n result[idx] = section\n } else {\n result.push(section)\n }\n }\n return result\n }\n\n case 'replace':\n default:\n return incoming\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-bus.js","sources":["../../src/services/chat-bus.ts"],"sourcesContent":["/**\n * Chat Bus — Event Emitter + Command Handler\n * v2.4.0: Core primitives for the chat event/command bus\n *\n * @experimental — This API may change without major bump until v2.5.0.\n */\n\nimport type {\n ChatEvents,\n ChatCommands,\n ChatEventEmitter,\n ChatCommandHandler,\n ChatBus,\n EventSubscribeOptions,\n ScratchpadSection,\n ClarificationEvent,\n ChatPromptConfig,\n} from '../types/chat-bus'\n\n// ─── Event Emitter ───────────────────────────────────────────\n\ninterface Listener<F extends (...args: any[]) => any> {\n handler: F\n options?: EventSubscribeOptions\n throttledHandler?: F\n}\n\n/**\n * Create a typed event emitter with throttle and streamKey filtering support.\n *\n * @experimental\n *\n * @example\n * const emitter = createEventEmitter<ChatEvents>()\n * const unsub = emitter.on('onToken', (event) => console.log(event.token), { throttle: 100 })\n * emitter.emit('onToken', { streamKey: 'abc', token: 'hello' })\n * unsub()\n */\nexport function createEventEmitter(): ChatEventEmitter {\n const listeners = new Map<string, Set<Listener<any>>>()\n\n interface ThrottledFn<F> {\n fn: F\n cancel: () => void\n }\n\n function createThrottled<F extends (...args: any[]) => void>(fn: F, ms: number): ThrottledFn<F> {\n let lastCall = 0\n let timer: ReturnType<typeof setTimeout> | null = null\n let lastArgs: any[] | null = null\n let cancelled = false\n\n const throttled = ((...args: any[]) => {\n if (cancelled) return\n lastArgs = args\n const now = Date.now()\n const remaining = ms - (now - lastCall)\n\n if (remaining <= 0) {\n if (timer) { clearTimeout(timer); timer = null }\n lastCall = now\n fn(...args)\n } else if (!timer) {\n timer = setTimeout(() => {\n lastCall = Date.now()\n timer = null\n if (lastArgs && !cancelled) {\n try { fn(...lastArgs) } catch (err) { console.error('[ChatBus] Error in throttled handler:', err) }\n }\n }, remaining)\n }\n }) as F\n\n return {\n fn: throttled,\n cancel: () => { cancelled = true; if (timer) { clearTimeout(timer); timer = null } },\n }\n }\n\n return {\n on(event, handler, options) {\n if (!listeners.has(event as string)) {\n listeners.set(event as string, new Set())\n }\n\n const listener: Listener<typeof handler> = { handler, options }\n\n // Apply throttle if requested\n let throttleHandle: ThrottledFn<typeof handler> | null = null\n if (options?.throttle && options.throttle > 0) {\n throttleHandle = createThrottled(handler, options.throttle)\n listener.throttledHandler = throttleHandle.fn\n }\n\n listeners.get(event as string)!.add(listener)\n\n // Return unsubscribe function — cancels pending throttle timers\n return () => {\n throttleHandle?.cancel()\n listeners.get(event as string)?.delete(listener)\n }\n },\n\n emit(event, ...args) {\n const set = listeners.get(event as string)\n if (!set) return\n\n for (const listener of set) {\n // StreamKey filtering: skip if listener wants a specific streamKey\n // For most events args[0] has streamKey; for onCustomEvent args[1] has it\n if (listener.options?.streamKey) {\n let streamKeyArg: unknown\n for (const arg of args) {\n if (arg && typeof arg === 'object' && 'streamKey' in (arg as any)) {\n streamKeyArg = (arg as any).streamKey\n break\n }\n }\n if (streamKeyArg !== undefined && streamKeyArg !== listener.options.streamKey) continue\n }\n\n const fn = listener.throttledHandler || listener.handler\n try {\n fn(...args)\n } catch (err) {\n console.error(`[ChatBus] Error in ${event as string} handler:`, err)\n }\n }\n },\n\n clear() {\n listeners.clear()\n },\n } as ChatEventEmitter\n}\n\n// ─── Command Handler ─────────────────────────────────────────\n\n/**\n * Create a typed command handler. The host app registers handlers,\n * agents execute commands.\n *\n * @experimental\n *\n * @example\n * const commands = createCommandHandler<ChatCommands>()\n * commands.handle('injectPrompt', (text) => setInputValue(text))\n * commands.exec('injectPrompt', 'Hello world')\n */\nexport function createCommandHandler(): ChatCommandHandler {\n const handlers = new Map<string, (...args: any[]) => any>()\n\n return {\n handle(command, handler) {\n handlers.set(command as string, handler)\n },\n\n exec(command, ...args) {\n const handler = handlers.get(command as string)\n if (!handler) {\n console.warn(`[ChatBus] No handler registered for command: ${command as string}`)\n return undefined as any\n }\n return handler(...args)\n },\n } as ChatCommandHandler\n}\n\n// ─── Chat Bus Factory ────────────────────────────────────────\n\n/**\n * Create a complete ChatBus with events + commands.\n *\n * @experimental\n *\n * @example\n * const bus = createChatBus()\n * bus.events.on('onStreamEnd', (event) => { ... })\n * bus.commands.handle('sendPrompt', (text) => { ... })\n */\nexport function createChatBus(): ChatBus {\n return {\n events: createEventEmitter(),\n commands: createCommandHandler(),\n }\n}\n\n// ─── Scratchpad Section Merge Helper ─────────────────────────\n\n/**\n * Merge sections from a ScratchpadEvent into existing state sections.\n * Handles replace/append/upsert modes.\n *\n * @example\n * const newSections = mergeScratchpadSections(\n * currentState.sections,\n * event.sections,\n * event.sectionMode\n * )\n */\nexport function mergeScratchpadSections(\n existing: ScratchpadSection[],\n incoming: ScratchpadSection[] | undefined,\n mode: 'replace' | 'append' | 'upsert' = 'replace'\n): ScratchpadSection[] {\n if (!incoming) return existing\n\n switch (mode) {\n case 'append':\n return [...existing, ...incoming]\n\n case 'upsert': {\n const result = [...existing]\n for (const section of incoming) {\n const idx = result.findIndex((s) => s.id === section.id)\n if (idx >= 0) {\n result[idx] = section\n } else {\n result.push(section)\n }\n }\n return result\n }\n\n case 'replace':\n default:\n return incoming\n }\n}\n\n// ─── Clarification → Prompt Helper (v4.3.9) ──────────────────\n\n/**\n * Convert a ClarificationEvent into a ChatPromptConfig.\n * Universal bridge for apps receiving clarification events via SSE.\n *\n * Legacy `file_id` (deprecated) is transparently moved into `metadata.file_id`\n * so consumers don't need to handle both shapes.\n *\n * @experimental\n * @since v4.3.9\n * @example\n * bus.events.on('onClarificationNeeded', ({ clarification }) => {\n * bus.commands.exec('showChatPrompt', clarificationToPromptConfig(clarification))\n * })\n */\nexport function clarificationToPromptConfig(\n event: ClarificationEvent\n): ChatPromptConfig {\n return {\n type: 'choice',\n title: event.question,\n config: {\n options: event.options.map((opt) => {\n const merged: Record<string, unknown> = { ...(opt.metadata ?? {}) }\n if (opt.file_id !== undefined && merged.file_id === undefined) {\n merged.file_id = opt.file_id\n }\n return {\n value: opt.value,\n label: opt.label,\n // Only include metadata if non-empty (keeps payloads clean)\n ...(Object.keys(merged).length > 0 ? { metadata: merged } : {}),\n }\n }),\n layout: 'vertical',\n },\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scratchpad-store.cjs","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\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":["createStore","produce","_a","s"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"scratchpad-store.cjs","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":["createStore","produce","_a","s"],"mappings":"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"}
|
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Parser calls dispatchScratchpad() → store updates → ScratchpadPanel reads reactively.
|
|
8
8
|
* Zero bus, zero relay, zero race condition.
|
|
9
|
+
*
|
|
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
16
|
*/
|
|
10
17
|
import type { ScratchpadState, ScratchpadEvent } from '../types/chat-bus';
|
|
11
18
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scratchpad-store.d.ts","sourceRoot":"","sources":["../../src/stores/scratchpad-store.ts"],"names":[],"mappings":"AAAA
|
|
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 +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\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":";
|
|
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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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const chatBus = require("../services/chat-bus.cjs");
|
|
4
|
+
function createMockChatBus(options = {}) {
|
|
5
|
+
const bus = chatBus.createChatBus();
|
|
6
|
+
const queue = [...options.promptResponses ?? []];
|
|
7
|
+
bus.commands.handle("showChatPrompt", async (config) => {
|
|
8
|
+
var _a;
|
|
9
|
+
(_a = options.onShowChatPrompt) == null ? void 0 : _a.call(options, config);
|
|
10
|
+
const response = queue.shift();
|
|
11
|
+
if (!response) {
|
|
12
|
+
throw new Error("createMockChatBus: no more pre-programmed responses");
|
|
13
|
+
}
|
|
14
|
+
return response;
|
|
15
|
+
});
|
|
16
|
+
return bus;
|
|
17
|
+
}
|
|
18
|
+
exports.createMockChatBus = createMockChatBus;
|
|
19
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../src/testing/index.ts"],"sourcesContent":["/**\n * Testing utilities for mcp-ui consumers.\n *\n * @experimental\n * @since v4.3.9\n */\n\nimport { createChatBus } from '../services/chat-bus'\nimport type { ChatBus, ChatPromptConfig, ChatPromptResponse } from '../types/chat-bus'\n\nexport interface MockChatBusOptions {\n /** Pre-programmed responses for showChatPrompt, consumed in FIFO order */\n promptResponses?: ChatPromptResponse[]\n /** Called when showChatPrompt is invoked (spy hook) */\n onShowChatPrompt?: (config: ChatPromptConfig) => void\n}\n\n/**\n * Create a ChatBus pre-wired with test fixtures.\n *\n * Pre-programmed `ChatPromptResponse`s are returned in FIFO order when the\n * agent calls `showChatPrompt`. Useful for testing flows that depend on\n * user choices without rendering any UI.\n *\n * @example\n * const bus = createMockChatBus({\n * promptResponses: [\n * { type: 'choice', value: 'yes', label: 'Yes' },\n * ],\n * })\n * const response = await bus.commands.exec('showChatPrompt', {\n * type: 'choice',\n * title: 'Proceed?',\n * config: { options: [{ value: 'yes', label: 'Yes' }] },\n * })\n * expect(response.value).toBe('yes')\n */\nexport function createMockChatBus(options: MockChatBusOptions = {}): ChatBus {\n const bus = createChatBus()\n const queue = [...(options.promptResponses ?? [])]\n bus.commands.handle('showChatPrompt', async (config) => {\n options.onShowChatPrompt?.(config)\n const response = queue.shift()\n if (!response) {\n throw new Error('createMockChatBus: no more pre-programmed responses')\n }\n return response\n })\n return bus\n}\n"],"names":["createChatBus"],"mappings":";;;AAqCO,SAAS,kBAAkB,UAA8B,IAAa;AAC3E,QAAM,MAAMA,QAAAA,cAAA;AACZ,QAAM,QAAQ,CAAC,GAAI,QAAQ,mBAAmB,CAAA,CAAG;AACjD,MAAI,SAAS,OAAO,kBAAkB,OAAO,WAAW;;AACtD,kBAAQ,qBAAR,iCAA2B;AAC3B,UAAM,WAAW,MAAM,MAAA;AACvB,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,qDAAqD;AAAA,IACvE;AACA,WAAO;AAAA,EACT,CAAC;AACD,SAAO;AACT;;"}
|