@flamingo-stack/openframe-frontend-core 0.0.492-1506.3956.1 → 0.0.492
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/dist/{chunk-KX3K2EH4.cjs → chunk-2XAAFAQT.cjs} +11 -11
- package/dist/{chunk-KX3K2EH4.cjs.map → chunk-2XAAFAQT.cjs.map} +1 -1
- package/dist/{chunk-JHEIOXXZ.js → chunk-4AWRPTKE.js} +5 -6
- package/dist/chunk-4AWRPTKE.js.map +1 -0
- package/dist/{chunk-PTXZB2KW.js → chunk-4J3CB4KO.js} +2 -2
- package/dist/{chunk-7N7YZCHR.cjs → chunk-4MLOMMDX.cjs} +5519 -7555
- package/dist/chunk-4MLOMMDX.cjs.map +1 -0
- package/dist/{chunk-MEMRQTOI.js → chunk-56J7RLMM.js} +10434 -12470
- package/dist/chunk-56J7RLMM.js.map +1 -0
- package/dist/{chunk-MEIGDK7M.cjs → chunk-5ICPQAUI.cjs} +60 -60
- package/dist/chunk-5ICPQAUI.cjs.map +1 -0
- package/dist/{chunk-BEEPU44B.js → chunk-5RHJVPL7.js} +10 -10
- package/dist/chunk-5RHJVPL7.js.map +1 -0
- package/dist/{chunk-XOFE2RT4.js → chunk-BTIRYGDT.js} +4 -4
- package/dist/{chunk-JSEJ5MH5.cjs → chunk-DY6AJNVK.cjs} +5 -5
- package/dist/{chunk-JSEJ5MH5.cjs.map → chunk-DY6AJNVK.cjs.map} +1 -1
- package/dist/{chunk-PLB6CQSO.js → chunk-FG5ALORQ.js} +2 -2
- package/dist/{chunk-QG2EBIH5.cjs → chunk-J2J36PEN.cjs} +31 -31
- package/dist/{chunk-QG2EBIH5.cjs.map → chunk-J2J36PEN.cjs.map} +1 -1
- package/dist/{chunk-A6FPYAUD.cjs → chunk-LIRXKEOE.cjs} +15 -15
- package/dist/{chunk-A6FPYAUD.cjs.map → chunk-LIRXKEOE.cjs.map} +1 -1
- package/dist/{chunk-MSL5NE3N.cjs → chunk-LVDI7R7T.cjs} +37 -37
- package/dist/{chunk-MSL5NE3N.cjs.map → chunk-LVDI7R7T.cjs.map} +1 -1
- package/dist/{chunk-MPUQFRJG.cjs → chunk-M4SCYPCR.cjs} +7 -7
- package/dist/{chunk-MPUQFRJG.cjs.map → chunk-M4SCYPCR.cjs.map} +1 -1
- package/dist/{chunk-QDGB2DKY.cjs → chunk-O6S6Q52W.cjs} +26 -26
- package/dist/{chunk-QDGB2DKY.cjs.map → chunk-O6S6Q52W.cjs.map} +1 -1
- package/dist/{chunk-W56JYDSC.cjs → chunk-OFFYASUY.cjs} +27 -28
- package/dist/chunk-OFFYASUY.cjs.map +1 -0
- package/dist/{chunk-NCAN6VUF.js → chunk-P3ITKTUE.js} +2 -2
- package/dist/{chunk-M6FEGPQJ.cjs → chunk-PI5BMKV3.cjs} +62 -62
- package/dist/{chunk-M6FEGPQJ.cjs.map → chunk-PI5BMKV3.cjs.map} +1 -1
- package/dist/{chunk-34TDA22K.js → chunk-QHRNQTW5.js} +2 -2
- package/dist/{chunk-OL3MJKUI.js → chunk-SZWMJIH3.js} +2 -2
- package/dist/{chunk-MZRIRQYV.js → chunk-U5LU56JE.js} +2 -2
- package/dist/{chunk-C65NCIMV.cjs → chunk-VML66FJQ.cjs} +9 -9
- package/dist/{chunk-C65NCIMV.cjs.map → chunk-VML66FJQ.cjs.map} +1 -1
- package/dist/{chunk-3LKB722U.js → chunk-WKWZ7LVC.js} +2 -2
- package/dist/{chunk-J3VBVR4W.js → chunk-WPFLV4QJ.js} +4 -4
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +1 -0
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +48 -15
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +6 -0
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +57 -10
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -10
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +0 -1
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +13 -21
- package/dist/components/chat/types/api.types.d.ts +81 -1
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +93 -2
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/utils/chunk-parser.d.ts +25 -0
- package/dist/components/chat/utils/chunk-parser.d.ts.map +1 -0
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +6 -34
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +3 -2
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts +11 -59
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/floating-walkthrough-video.d.ts.map +1 -1
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +130 -172
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +21 -63
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/navigation/navigation-sidebar-item.d.ts +23 -1
- package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
- package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/data-table/data-table-column-filter.d.ts.map +1 -1
- package/dist/components/ui/data-table/data-table-header.d.ts.map +1 -1
- package/dist/components/ui/data-table/data-table-skeleton.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +2 -44
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +2 -1
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +13 -55
- package/dist/components/ui/rich-markdown-renderer.d.ts +34 -0
- package/dist/components/ui/rich-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/simple-markdown-renderer.d.ts +73 -0
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/tab-navigation.d.ts.map +1 -1
- package/dist/index.cjs +2 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +13 -71
- package/dist/utils/index.cjs +40 -169
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +41 -162
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
- package/package.json +1 -7
- package/src/components/chat/__tests__/chat-message-list.test.tsx +5 -293
- package/src/components/chat/chat-message-enhanced.tsx +20 -122
- package/src/components/chat/chat-message-list.tsx +109 -358
- package/src/components/chat/embeddable-chat.tsx +0 -6
- package/src/components/chat/hooks/.index.md +33 -30
- package/src/components/chat/hooks/.use-nats-chat-adapter.md +55 -36
- package/src/components/chat/hooks/.use-realtime-chunk-processor.md +77 -0
- package/src/components/chat/hooks/index.ts +1 -0
- package/src/components/chat/hooks/use-chat.ts +0 -8
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +825 -243
- package/src/components/chat/hooks/use-realtime-chunk-processor.ts +489 -0
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +684 -380
- package/src/components/chat/index.ts +0 -5
- package/src/components/chat/thinking-display.tsx +1 -1
- package/src/components/chat/types/.api.types.md +49 -39
- package/src/components/chat/types/.processing.types.md +58 -29
- package/src/components/chat/types/api.types.ts +73 -6
- package/src/components/chat/types/processing.types.ts +53 -11
- package/src/components/chat/utils/.chunk-parser.md +62 -0
- package/src/components/chat/utils/.index.md +40 -31
- package/src/components/chat/utils/chunk-parser.ts +262 -0
- package/src/components/chat/utils/extract-incomplete-message-state.ts +5 -63
- package/src/components/chat/utils/index.ts +8 -5
- package/src/components/chat/utils/process-historical-messages.ts +386 -358
- package/src/components/features/floating-walkthrough-video.tsx +3 -1
- package/src/components/navigation/navigation-sidebar-item.tsx +80 -30
- package/src/components/navigation/navigation-sidebar.tsx +106 -46
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +4 -22
- package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
- package/src/components/shared/product-release/release-detail-page.tsx +4 -19
- package/src/components/ui/.simple-markdown-renderer.md +52 -0
- package/src/components/ui/__tests__/tab-navigation.test.tsx +159 -0
- package/src/components/ui/data-table/data-table-column-filter.tsx +7 -1
- package/src/components/ui/data-table/data-table-header.tsx +4 -1
- package/src/components/ui/data-table/data-table-skeleton.tsx +7 -0
- package/src/components/ui/index.ts +2 -1
- package/src/components/ui/release-changelog-section.tsx +1 -1
- package/src/components/ui/rich-markdown-renderer.tsx +1223 -0
- package/src/components/ui/simple-markdown-renderer.tsx +964 -0
- package/src/components/ui/tab-navigation.tsx +370 -171
- package/src/components/ui/ticket-info-section.tsx +1 -1
- package/src/utils/index.ts +0 -1
- package/src/utils/markdown-section-extractor.ts +56 -41
- package/dist/chat-protocol/decode.d.ts +0 -58
- package/dist/chat-protocol/decode.d.ts.map +0 -1
- package/dist/chat-protocol/encode.d.ts +0 -44
- package/dist/chat-protocol/encode.d.ts.map +0 -1
- package/dist/chat-protocol/env-flag.d.ts +0 -35
- package/dist/chat-protocol/env-flag.d.ts.map +0 -1
- package/dist/chat-protocol/events.d.ts +0 -207
- package/dist/chat-protocol/events.d.ts.map +0 -1
- package/dist/chat-protocol/frames.d.ts +0 -213
- package/dist/chat-protocol/frames.d.ts.map +0 -1
- package/dist/chat-protocol/index.cjs +0 -500
- package/dist/chat-protocol/index.cjs.map +0 -1
- package/dist/chat-protocol/index.d.ts +0 -18
- package/dist/chat-protocol/index.d.ts.map +0 -1
- package/dist/chat-protocol/index.js +0 -484
- package/dist/chat-protocol/index.js.map +0 -1
- package/dist/chat-protocol/ip-normalize.d.ts +0 -44
- package/dist/chat-protocol/ip-normalize.d.ts.map +0 -1
- package/dist/chat-protocol/nats-decoder.d.ts +0 -26
- package/dist/chat-protocol/nats-decoder.d.ts.map +0 -1
- package/dist/chunk-7N7YZCHR.cjs.map +0 -1
- package/dist/chunk-BEEPU44B.js.map +0 -1
- package/dist/chunk-JHEIOXXZ.js.map +0 -1
- package/dist/chunk-MEIGDK7M.cjs.map +0 -1
- package/dist/chunk-MEMRQTOI.js.map +0 -1
- package/dist/chunk-W56JYDSC.cjs.map +0 -1
- package/dist/components/chat/stream/chat-dialog-store.d.ts +0 -184
- package/dist/components/chat/stream/chat-dialog-store.d.ts.map +0 -1
- package/dist/components/chat/stream/chat-stream-reducer.d.ts +0 -367
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +0 -1
- package/dist/components/chat/stream/delta-batcher.d.ts +0 -51
- package/dist/components/chat/stream/delta-batcher.d.ts.map +0 -1
- package/dist/components/chat/stream/index.d.ts +0 -17
- package/dist/components/chat/stream/index.d.ts.map +0 -1
- package/dist/components/chat/stream/message-mutations.d.ts +0 -77
- package/dist/components/chat/stream/message-mutations.d.ts.map +0 -1
- package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +0 -22
- package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +0 -1
- package/dist/components/ui/markdown/base-components.d.ts +0 -51
- package/dist/components/ui/markdown/base-components.d.ts.map +0 -1
- package/dist/components/ui/markdown/engine.d.ts +0 -99
- package/dist/components/ui/markdown/engine.d.ts.map +0 -1
- package/dist/components/ui/markdown/heading-ids.d.ts +0 -65
- package/dist/components/ui/markdown/heading-ids.d.ts.map +0 -1
- package/dist/components/ui/markdown/index.d.ts +0 -18
- package/dist/components/ui/markdown/index.d.ts.map +0 -1
- package/dist/components/ui/markdown/mermaid-diagram.d.ts +0 -79
- package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +0 -1
- package/dist/components/ui/markdown/rich/embed-overrides.d.ts +0 -14
- package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +0 -1
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +0 -40
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +0 -1
- package/dist/components/ui/markdown/rich/shortcodes.d.ts +0 -8
- package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +0 -1
- package/dist/components/ui/markdown/sanitize.d.ts +0 -153
- package/dist/components/ui/markdown/sanitize.d.ts.map +0 -1
- package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +0 -23
- package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +0 -1
- package/dist/components/ui/markdown/streaming.d.ts +0 -78
- package/dist/components/ui/markdown/streaming.d.ts.map +0 -1
- package/dist/components/ui/markdown/text-size.d.ts +0 -34
- package/dist/components/ui/markdown/text-size.d.ts.map +0 -1
- package/dist/utils/markdown-fences.d.ts +0 -42
- package/dist/utils/markdown-fences.d.ts.map +0 -1
- package/dist/utils/markdown-heading-id.d.ts +0 -85
- package/dist/utils/markdown-heading-id.d.ts.map +0 -1
- package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +0 -317
- package/src/chat-protocol/__tests__/chat-protocol.test.ts +0 -516
- package/src/chat-protocol/__tests__/env-flag.test.ts +0 -52
- package/src/chat-protocol/__tests__/ip-normalize.test.ts +0 -136
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +0 -232
- package/src/chat-protocol/decode.ts +0 -278
- package/src/chat-protocol/encode.ts +0 -71
- package/src/chat-protocol/env-flag.ts +0 -39
- package/src/chat-protocol/events.ts +0 -245
- package/src/chat-protocol/frames.ts +0 -245
- package/src/chat-protocol/index.ts +0 -21
- package/src/chat-protocol/ip-normalize.ts +0 -146
- package/src/chat-protocol/nats-decoder.ts +0 -258
- package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +0 -207
- package/src/components/chat/__tests__/chat-pending-turn.test.tsx +0 -230
- package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +0 -37
- package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
- package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +0 -438
- package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +0 -318
- package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +0 -940
- package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +0 -756
- package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +0 -460
- package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +0 -973
- package/src/components/chat/stream/__tests__/delta-batcher.test.ts +0 -255
- package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +0 -98
- package/src/components/chat/stream/chat-dialog-store.ts +0 -536
- package/src/components/chat/stream/chat-stream-reducer.ts +0 -1808
- package/src/components/chat/stream/delta-batcher.ts +0 -168
- package/src/components/chat/stream/index.ts +0 -55
- package/src/components/chat/stream/message-mutations.ts +0 -340
- package/src/components/chat/stream/use-chat-stream-reducer.ts +0 -126
- package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +0 -420
- package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +0 -605
- package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +0 -394
- package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +0 -270
- package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +0 -2420
- package/src/components/ui/__tests__/markdown-parity.test.tsx +0 -2528
- package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +0 -115
- package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +0 -95
- package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +0 -309
- package/src/components/ui/markdown/__tests__/streaming.test.tsx +0 -388
- package/src/components/ui/markdown/base-components.tsx +0 -360
- package/src/components/ui/markdown/engine.tsx +0 -315
- package/src/components/ui/markdown/heading-ids.ts +0 -239
- package/src/components/ui/markdown/index.ts +0 -49
- package/src/components/ui/markdown/mermaid-diagram.tsx +0 -291
- package/src/components/ui/markdown/rich/embed-overrides.tsx +0 -199
- package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +0 -184
- package/src/components/ui/markdown/rich/shortcodes.ts +0 -170
- package/src/components/ui/markdown/sanitize.ts +0 -3499
- package/src/components/ui/markdown/simple-markdown-renderer.tsx +0 -28
- package/src/components/ui/markdown/streaming.ts +0 -362
- package/src/components/ui/markdown/text-size.ts +0 -106
- package/src/utils/markdown-fences.ts +0 -127
- package/src/utils/markdown-heading-id.ts +0 -348
- /package/dist/{chunk-PTXZB2KW.js.map → chunk-4J3CB4KO.js.map} +0 -0
- /package/dist/{chunk-XOFE2RT4.js.map → chunk-BTIRYGDT.js.map} +0 -0
- /package/dist/{chunk-PLB6CQSO.js.map → chunk-FG5ALORQ.js.map} +0 -0
- /package/dist/{chunk-NCAN6VUF.js.map → chunk-P3ITKTUE.js.map} +0 -0
- /package/dist/{chunk-34TDA22K.js.map → chunk-QHRNQTW5.js.map} +0 -0
- /package/dist/{chunk-OL3MJKUI.js.map → chunk-SZWMJIH3.js.map} +0 -0
- /package/dist/{chunk-MZRIRQYV.js.map → chunk-U5LU56JE.js.map} +0 -0
- /package/dist/{chunk-3LKB722U.js.map → chunk-WKWZ7LVC.js.map} +0 -0
- /package/dist/{chunk-J3VBVR4W.js.map → chunk-WPFLV4QJ.js.map} +0 -0
|
@@ -1,28 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Utility for processing historical messages from GraphQL/API
|
|
3
|
-
* display-ready format
|
|
4
|
-
*
|
|
5
|
-
* Phase 3 of the chat unification reduced this file to DECODE + ENVELOPE:
|
|
6
|
-
*
|
|
7
|
-
* - `decodeHistoricalMessageData` maps one persisted `MessageData` item
|
|
8
|
-
* into the shared `ChatStreamEvent` vocabulary (historical items share
|
|
9
|
-
* the MESSAGE_TYPE vocabulary with NATS chunks but are NOT full chunk
|
|
10
|
-
* envelopes — e.g. compaction summaries ride in `summary`, not `text`,
|
|
11
|
-
* and APPROVAL_RESULT rows carry `resolvedByName` directly);
|
|
12
|
-
* - `applyHistoryEvent` replays each decoded event into the shared
|
|
13
|
-
* per-turn kernel (`MessageSegmentAccumulator` — the same kernel the
|
|
14
|
-
* master `createChatStreamReducer` instantiates) with the
|
|
15
|
-
* history-specific approval semantics (display-all default, pending
|
|
16
|
-
* tracking + `flushPendingApprovals`, `approvalStatuses` overrides);
|
|
17
|
-
* - the ENVELOPE (user/assistant flush-grouping, OWNER_TYPE author /
|
|
18
|
-
* display-name / avatar resolution, standalone SYSTEM handling,
|
|
19
|
-
* per-message contextItems mapping, per-turn streamSeq MAX) lives ONCE
|
|
20
|
-
* in `processHistory` — the previous near-duplicate second copy was
|
|
21
|
-
* deleted (`processHistoricalMessagesWithErrors` is an alias; the two
|
|
22
|
-
* snapshots were byte-identical).
|
|
23
|
-
*
|
|
24
|
-
* Adapters then feed the processed rows into the reducer via
|
|
25
|
-
* `initializeWithState` (see `useNatsChatAdapter.loadDialogHistory`).
|
|
2
|
+
* Utility for processing historical messages from GraphQL/API
|
|
3
|
+
* into display-ready format
|
|
26
4
|
*/
|
|
27
5
|
|
|
28
6
|
import {
|
|
@@ -37,7 +15,6 @@ import {
|
|
|
37
15
|
type MessageData,
|
|
38
16
|
type MessageOwner,
|
|
39
17
|
} from '../types'
|
|
40
|
-
import type { ChatStreamEvent } from '../../../chat-protocol/events'
|
|
41
18
|
import { MessageSegmentAccumulator, createMessageSegmentAccumulator } from './message-segment-accumulator'
|
|
42
19
|
import { getCommandText } from './tool-call-helpers'
|
|
43
20
|
|
|
@@ -76,67 +53,255 @@ function pushStandaloneMessages(
|
|
|
76
53
|
})
|
|
77
54
|
}
|
|
78
55
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Result type for historical message processing
|
|
58
|
+
*/
|
|
59
|
+
export interface ProcessHistoricalMessagesResult {
|
|
60
|
+
messages: ProcessedMessage[]
|
|
61
|
+
escalatedApprovals: Map<
|
|
62
|
+
string,
|
|
63
|
+
{ command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
|
|
64
|
+
>
|
|
65
|
+
}
|
|
82
66
|
|
|
83
67
|
/**
|
|
84
|
-
*
|
|
85
|
-
* Thin shim over the NATS chunk decoder's core mapping — the differences
|
|
86
|
-
* are exactly the persisted-row divergences from the realtime envelope:
|
|
87
|
-
*
|
|
88
|
-
* - compaction summaries arrive in `summary` (realtime: `text`);
|
|
89
|
-
* - APPROVAL_RESULT rows carry `resolvedByName` (realtime chunks carry
|
|
90
|
-
* the resolver's name as `displayName`);
|
|
91
|
-
* - `approvalType` defaults to 'CLIENT' (legacy history parity;
|
|
92
|
-
* realtime defaults APPROVAL_REQUEST to 'USER');
|
|
93
|
-
* - batch `toolCalls` pass through verbatim (already the persisted
|
|
94
|
-
* `PendingToolCallData` shape — no normalization pass).
|
|
95
|
-
*
|
|
96
|
-
* Returns `null` for rows the assistant-turn path doesn't decode (SYSTEM
|
|
97
|
-
* is handled by the envelope's standalone path; user TEXT rows are handled
|
|
98
|
-
* by the envelope's user path).
|
|
68
|
+
* Process an array of historical messages into display-ready format
|
|
99
69
|
*/
|
|
100
|
-
export function
|
|
70
|
+
export function processHistoricalMessages(
|
|
71
|
+
messages: HistoricalMessage[],
|
|
72
|
+
options: MessageProcessingOptions = {}
|
|
73
|
+
): ProcessHistoricalMessagesResult {
|
|
74
|
+
const {
|
|
75
|
+
assistantName = 'Fae',
|
|
76
|
+
assistantType = 'fae',
|
|
77
|
+
assistantAvatar,
|
|
78
|
+
onApprove,
|
|
79
|
+
onReject,
|
|
80
|
+
chatTypeFilter,
|
|
81
|
+
approvalStatuses = {},
|
|
82
|
+
// An omitted option means "display every approval type" — the original
|
|
83
|
+
// history semantics. Deliberately NOT defaulted to the realtime
|
|
84
|
+
// processor's ['CLIENT']: consumers that pass a wider list to their
|
|
85
|
+
// realtime processor but omit it on the history path would silently lose
|
|
86
|
+
// pending non-CLIENT approval cards on every reload/reconnect refetch
|
|
87
|
+
// (they also ignore `escalatedApprovals`). Realtime/history parity is
|
|
88
|
+
// opt-in: pass the same explicit list to both.
|
|
89
|
+
displayApprovalTypes,
|
|
90
|
+
batchApprovalsEnabled,
|
|
91
|
+
} = options
|
|
92
|
+
|
|
93
|
+
const processedMessages: ProcessedMessage[] = []
|
|
94
|
+
const accumulator = createMessageSegmentAccumulator({ onApprove, onReject })
|
|
95
|
+
const escalatedApprovals = new Map<
|
|
96
|
+
string,
|
|
97
|
+
{ command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
|
|
98
|
+
>()
|
|
99
|
+
|
|
100
|
+
let currentAssistantId: string | null = null
|
|
101
|
+
let currentAssistantTimestamp: Date | null = null
|
|
102
|
+
let lastAssistantId: string | null = null
|
|
103
|
+
// MAX persisted seq across the rows grouped into the current assistant turn
|
|
104
|
+
// — carried onto the flushed message's streamSeq for per-role merge coverage.
|
|
105
|
+
let currentAssistantStreamSeq: number | undefined
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Flush the current assistant message to processedMessages.
|
|
109
|
+
* Uses the LAST message ID in the group for stable React keys across page boundaries.
|
|
110
|
+
*/
|
|
111
|
+
const flushAssistantMessage = () => {
|
|
112
|
+
const idToUse = lastAssistantId || currentAssistantId
|
|
113
|
+
if (idToUse && accumulator.hasContent()) {
|
|
114
|
+
processedMessages.push({
|
|
115
|
+
id: idToUse,
|
|
116
|
+
role: 'assistant',
|
|
117
|
+
content: accumulator.getSegments(),
|
|
118
|
+
name: assistantName,
|
|
119
|
+
assistantType,
|
|
120
|
+
authorType: assistantType as AuthorType,
|
|
121
|
+
timestamp: currentAssistantTimestamp || new Date(),
|
|
122
|
+
avatar: assistantAvatar,
|
|
123
|
+
...(currentAssistantStreamSeq !== undefined ? { streamSeq: currentAssistantStreamSeq } : {}),
|
|
124
|
+
})
|
|
125
|
+
accumulator.resetSegments()
|
|
126
|
+
}
|
|
127
|
+
// Reset grouping identity + seq UNCONDITIONALLY — even on an EMPTY flush (an
|
|
128
|
+
// assistant turn whose only data was a filtered/escalated approval renders
|
|
129
|
+
// nothing, so `hasContent()` is false and nothing is pushed). Left inside
|
|
130
|
+
// the push-block, a stale id/timestamp and (worse) a stale
|
|
131
|
+
// `currentAssistantStreamSeq` bleed into the NEXT assistant turn: `!currentAssistantId`
|
|
132
|
+
// stays false so it keeps the old id/timestamp, and `Math.max` inflates its
|
|
133
|
+
// streamSeq — which then over-covers synthetics in the history merge.
|
|
134
|
+
currentAssistantId = null
|
|
135
|
+
currentAssistantTimestamp = null
|
|
136
|
+
lastAssistantId = null
|
|
137
|
+
currentAssistantStreamSeq = undefined
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
messages.forEach((msg, index) => {
|
|
141
|
+
// Filter by chat type if specified
|
|
142
|
+
if (chatTypeFilter && msg.chatType !== chatTypeFilter) return
|
|
143
|
+
|
|
144
|
+
const messageDataArray = Array.isArray(msg.messageData)
|
|
145
|
+
? msg.messageData
|
|
146
|
+
: msg.messageData
|
|
147
|
+
? [msg.messageData]
|
|
148
|
+
: []
|
|
149
|
+
|
|
150
|
+
const hasStandaloneData = messageDataArray.some((data) =>
|
|
151
|
+
data.type === MESSAGE_TYPE.SYSTEM
|
|
152
|
+
)
|
|
153
|
+
if (hasStandaloneData) {
|
|
154
|
+
flushAssistantMessage()
|
|
155
|
+
pushStandaloneMessages(processedMessages, msg, messageDataArray)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const isUserMessage =
|
|
160
|
+
msg.owner?.type === OWNER_TYPE.CLIENT || msg.owner?.type === OWNER_TYPE.ADMIN
|
|
161
|
+
|
|
162
|
+
if (isUserMessage) {
|
|
163
|
+
flushAssistantMessage()
|
|
164
|
+
|
|
165
|
+
const userAuthorType: AuthorType = msg.owner?.type === OWNER_TYPE.ADMIN ? 'admin' : 'user'
|
|
166
|
+
messageDataArray.forEach((data) => {
|
|
167
|
+
if (data.type === MESSAGE_TYPE.TEXT && 'text' in data && data.text) {
|
|
168
|
+
// `TextData.contextItems` (server: `[{ type, id }]`) — the entity
|
|
169
|
+
// context the user attached to this message. Surface it so the bubble
|
|
170
|
+
// renders its chip strip from history (no label on the wire → fall
|
|
171
|
+
// back to the id, matching the realtime path).
|
|
172
|
+
const rawContext = (data as { contextItems?: Array<{ type?: unknown; id?: unknown }> }).contextItems
|
|
173
|
+
const contextItems = Array.isArray(rawContext)
|
|
174
|
+
? rawContext
|
|
175
|
+
.filter((c) => typeof c?.type === 'string' && typeof c?.id === 'string')
|
|
176
|
+
.map((c) => ({ type: c.type as string, id: c.id as string, label: c.id as string }))
|
|
177
|
+
: undefined
|
|
178
|
+
processedMessages.push({
|
|
179
|
+
id: msg.id,
|
|
180
|
+
role: 'user',
|
|
181
|
+
content: data.text,
|
|
182
|
+
name: getOwnerDisplayName(msg.owner),
|
|
183
|
+
avatar: getOwnerAvatar(msg.owner),
|
|
184
|
+
authorType: userAuthorType,
|
|
185
|
+
timestamp: new Date(msg.createdAt),
|
|
186
|
+
...(contextItems && contextItems.length > 0 ? { contextItems } : {}),
|
|
187
|
+
...(typeof msg.lastChunkStreamSeq === 'number' ? { streamSeq: msg.lastChunkStreamSeq } : {}),
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
} else {
|
|
192
|
+
if (!currentAssistantId) {
|
|
193
|
+
currentAssistantId = msg.id
|
|
194
|
+
currentAssistantTimestamp = new Date(msg.createdAt)
|
|
195
|
+
}
|
|
196
|
+
lastAssistantId = msg.id
|
|
197
|
+
if (typeof msg.lastChunkStreamSeq === 'number') {
|
|
198
|
+
currentAssistantStreamSeq =
|
|
199
|
+
currentAssistantStreamSeq === undefined
|
|
200
|
+
? msg.lastChunkStreamSeq
|
|
201
|
+
: Math.max(currentAssistantStreamSeq, msg.lastChunkStreamSeq)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
messageDataArray.forEach((data) => {
|
|
205
|
+
processMessageData(
|
|
206
|
+
data,
|
|
207
|
+
accumulator,
|
|
208
|
+
approvalStatuses,
|
|
209
|
+
{ displayApprovalTypes, batchApprovalsEnabled },
|
|
210
|
+
escalatedApprovals,
|
|
211
|
+
)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
// Check if we should flush (next message is from user or last message)
|
|
215
|
+
const nextMsg = messages[index + 1]
|
|
216
|
+
const isLastMessage = index === messages.length - 1
|
|
217
|
+
const nextIsFromUser =
|
|
218
|
+
nextMsg &&
|
|
219
|
+
(nextMsg.owner?.type === OWNER_TYPE.CLIENT || nextMsg.owner?.type === OWNER_TYPE.ADMIN)
|
|
220
|
+
|
|
221
|
+
if (isLastMessage || nextIsFromUser) {
|
|
222
|
+
flushAssistantMessage()
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
flushAssistantMessage()
|
|
228
|
+
|
|
229
|
+
const pendingApprovalSegments = accumulator.flushPendingApprovals()
|
|
230
|
+
if (pendingApprovalSegments.length > 0) {
|
|
231
|
+
processedMessages.push({
|
|
232
|
+
id: `pending-approvals-${Date.now()}`,
|
|
233
|
+
role: 'assistant',
|
|
234
|
+
content: pendingApprovalSegments,
|
|
235
|
+
name: assistantName,
|
|
236
|
+
assistantType,
|
|
237
|
+
timestamp: new Date(),
|
|
238
|
+
avatar: assistantAvatar,
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
messages: processedMessages,
|
|
244
|
+
escalatedApprovals: escalatedApprovals
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Process a single message data item into segments
|
|
250
|
+
*/
|
|
251
|
+
function processMessageData(
|
|
252
|
+
data: MessageData,
|
|
253
|
+
accumulator: MessageSegmentAccumulator,
|
|
254
|
+
approvalStatuses: Record<string, string>,
|
|
255
|
+
options: MessageProcessingOptions = {},
|
|
256
|
+
escalatedApprovals?: Map<
|
|
257
|
+
string,
|
|
258
|
+
{ command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
|
|
259
|
+
>
|
|
260
|
+
): void {
|
|
261
|
+
// batchApprovalsEnabled is owned by the consumer (oss-tenant chat client /
|
|
262
|
+
// openframe-frontend tickets). Defaults to ON so consumers that haven't
|
|
263
|
+
// wired the flag yet get the batch UI; pass `false` to force legacy.
|
|
264
|
+
const { displayApprovalTypes, batchApprovalsEnabled = true } = options
|
|
101
265
|
switch (data.type) {
|
|
102
266
|
case MESSAGE_TYPE.TEXT:
|
|
103
267
|
if ('text' in data && data.text) {
|
|
104
|
-
|
|
268
|
+
accumulator.appendText(data.text)
|
|
105
269
|
}
|
|
106
|
-
|
|
270
|
+
break
|
|
107
271
|
|
|
108
272
|
case MESSAGE_TYPE.THINKING:
|
|
109
273
|
if ('text' in data && data.text) {
|
|
110
|
-
|
|
274
|
+
accumulator.appendThinking(data.text)
|
|
111
275
|
}
|
|
112
|
-
|
|
276
|
+
break
|
|
113
277
|
|
|
114
278
|
case MESSAGE_TYPE.GUIDE:
|
|
115
279
|
if ('text' in data && data.text) {
|
|
116
|
-
|
|
280
|
+
accumulator.appendGuide(data.text)
|
|
117
281
|
}
|
|
118
|
-
|
|
282
|
+
break
|
|
119
283
|
|
|
120
284
|
case MESSAGE_TYPE.EXECUTING_TOOL:
|
|
121
285
|
if ('integratedToolType' in data) {
|
|
122
|
-
|
|
123
|
-
type: '
|
|
286
|
+
accumulator.addToolExecution({
|
|
287
|
+
type: 'tool_execution',
|
|
124
288
|
data: {
|
|
125
289
|
type: 'EXECUTING_TOOL',
|
|
126
290
|
integratedToolType: data.integratedToolType || '',
|
|
127
291
|
toolFunction: data.toolFunction || '',
|
|
128
292
|
toolTitle: typeof data.title === 'string' ? data.title : undefined,
|
|
293
|
+
toolExplanation: typeof data.toolExplanation === 'string' ? data.toolExplanation : undefined,
|
|
129
294
|
parameters: data.parameters,
|
|
130
295
|
toolExecutionRequestId: data.toolExecutionRequestId,
|
|
131
296
|
},
|
|
132
|
-
}
|
|
297
|
+
})
|
|
133
298
|
}
|
|
134
|
-
|
|
299
|
+
break
|
|
135
300
|
|
|
136
301
|
case MESSAGE_TYPE.EXECUTED_TOOL:
|
|
137
302
|
if ('integratedToolType' in data) {
|
|
138
|
-
|
|
139
|
-
type: '
|
|
303
|
+
accumulator.addToolExecution({
|
|
304
|
+
type: 'tool_execution',
|
|
140
305
|
data: {
|
|
141
306
|
type: 'EXECUTED_TOOL',
|
|
142
307
|
integratedToolType: data.integratedToolType || '',
|
|
@@ -147,298 +312,218 @@ export function decodeHistoricalMessageData(data: MessageData): ChatStreamEvent
|
|
|
147
312
|
success: data.success,
|
|
148
313
|
toolExecutionRequestId: data.toolExecutionRequestId,
|
|
149
314
|
},
|
|
150
|
-
}
|
|
315
|
+
})
|
|
151
316
|
}
|
|
152
|
-
|
|
317
|
+
break
|
|
153
318
|
|
|
154
319
|
case MESSAGE_TYPE.APPROVAL_REQUEST:
|
|
155
320
|
if ('approvalRequestId' in data && data.approvalRequestId) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
321
|
+
const approvalType = data.approvalType || 'CLIENT'
|
|
322
|
+
const toolCalls: PendingToolCallData[] | undefined = Array.isArray(data.toolCalls)
|
|
323
|
+
? data.toolCalls
|
|
324
|
+
: undefined
|
|
325
|
+
const isBatch = !!toolCalls && toolCalls.length > 0
|
|
326
|
+
|
|
327
|
+
if (!displayApprovalTypes || displayApprovalTypes.includes(approvalType)) {
|
|
328
|
+
if (isBatch) {
|
|
329
|
+
const status = (approvalStatuses[data.approvalRequestId] as ChatApprovalStatus) || 'pending'
|
|
330
|
+
if (batchApprovalsEnabled) {
|
|
331
|
+
accumulator.addApprovalBatch(data.approvalRequestId, approvalType, toolCalls!, status)
|
|
332
|
+
} else {
|
|
333
|
+
// Flag OFF — unfold batch into N legacy approval cards (same id).
|
|
334
|
+
for (const call of toolCalls!) {
|
|
335
|
+
if (!call.requiresApproval) continue
|
|
336
|
+
accumulator.addApprovalRequest(
|
|
337
|
+
data.approvalRequestId,
|
|
338
|
+
getCommandText(call),
|
|
339
|
+
call.toolExplanation,
|
|
340
|
+
approvalType,
|
|
341
|
+
status,
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
// The resolution may already be known to the consumer (realtime
|
|
347
|
+
// flipped it and fed it back via `approvalStatuses`) while the
|
|
348
|
+
// matching APPROVAL_RESULT row is absent from the fetched history
|
|
349
|
+
// pages. Without this, the single-approval path tracks it as
|
|
350
|
+
// pending and `flushPendingApprovals()` resurrects it as a stale
|
|
351
|
+
// sticky card on every history re-process. Mirror the batch path
|
|
352
|
+
// and honor `approvalStatuses`.
|
|
353
|
+
const resolvedStatus = approvalStatuses[data.approvalRequestId] as ChatApprovalStatus | undefined
|
|
354
|
+
if (resolvedStatus === 'approved' || resolvedStatus === 'rejected') {
|
|
355
|
+
accumulator.addApprovalRequest(
|
|
356
|
+
data.approvalRequestId,
|
|
357
|
+
data.command || '',
|
|
358
|
+
data.explanation,
|
|
359
|
+
approvalType,
|
|
360
|
+
resolvedStatus,
|
|
361
|
+
)
|
|
362
|
+
} else {
|
|
363
|
+
accumulator.trackApprovalRequest(data.approvalRequestId, {
|
|
364
|
+
command: data.command || '',
|
|
365
|
+
explanation: data.explanation,
|
|
366
|
+
approvalType,
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
escalatedApprovals?.set(data.approvalRequestId, {
|
|
372
|
+
command: data.command || '',
|
|
373
|
+
explanation: data.explanation,
|
|
374
|
+
approvalType,
|
|
375
|
+
...(isBatch ? { toolCalls } : {}),
|
|
376
|
+
})
|
|
163
377
|
}
|
|
164
378
|
}
|
|
165
|
-
|
|
379
|
+
break
|
|
166
380
|
|
|
167
381
|
case MESSAGE_TYPE.APPROVAL_RESULT:
|
|
168
382
|
if ('approvalRequestId' in data && data.approvalRequestId) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
approvalType: data.approvalType,
|
|
174
|
-
resolvedByName: 'resolvedByName' in data ? data.resolvedByName : undefined,
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return null
|
|
178
|
-
|
|
179
|
-
case MESSAGE_TYPE.ERROR:
|
|
180
|
-
if ('error' in data) {
|
|
181
|
-
return {
|
|
182
|
-
type: 'error',
|
|
183
|
-
title: data.error || 'An error occurred',
|
|
184
|
-
details: 'details' in data ? data.details : undefined,
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return null
|
|
188
|
-
|
|
189
|
-
case MESSAGE_TYPE.CONTEXT_COMPACTION_START:
|
|
190
|
-
return { type: 'compaction', phase: 'start' }
|
|
191
|
-
|
|
192
|
-
case MESSAGE_TYPE.CONTEXT_COMPACTION_END:
|
|
193
|
-
return {
|
|
194
|
-
type: 'compaction',
|
|
195
|
-
phase: 'end',
|
|
196
|
-
summary: 'summary' in data && typeof data.summary === 'string' ? data.summary : undefined,
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
case MESSAGE_TYPE.SYSTEM:
|
|
200
|
-
if ('text' in data && data.text) {
|
|
201
|
-
return { type: 'participant', kind: 'system', text: data.text }
|
|
202
|
-
}
|
|
203
|
-
return null
|
|
204
|
-
|
|
205
|
-
default:
|
|
206
|
-
// Unknown message type — ignore.
|
|
207
|
-
return null
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// =============================================================================
|
|
212
|
-
// Replay — decoded event → per-turn kernel (history approval semantics)
|
|
213
|
-
// =============================================================================
|
|
214
|
-
|
|
215
|
-
type EscalatedApprovals = Map<
|
|
216
|
-
string,
|
|
217
|
-
{ command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
|
|
218
|
-
>
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Replay one decoded event into the shared per-turn segment kernel with the
|
|
222
|
-
* HISTORY approval semantics (an omitted `displayApprovalTypes` means
|
|
223
|
-
* "display every approval type" — the original history behavior; the
|
|
224
|
-
* realtime reducer defaults to `['CLIENT']`).
|
|
225
|
-
*/
|
|
226
|
-
function applyHistoryEvent(
|
|
227
|
-
event: ChatStreamEvent,
|
|
228
|
-
accumulator: MessageSegmentAccumulator,
|
|
229
|
-
approvalStatuses: Record<string, string>,
|
|
230
|
-
options: MessageProcessingOptions,
|
|
231
|
-
escalatedApprovals?: EscalatedApprovals,
|
|
232
|
-
): void {
|
|
233
|
-
// batchApprovalsEnabled is owned by the consumer (oss-tenant chat client /
|
|
234
|
-
// openframe-frontend tickets). Defaults to ON so consumers that haven't
|
|
235
|
-
// wired the flag yet get the batch UI; pass `false` to force legacy.
|
|
236
|
-
const { displayApprovalTypes, batchApprovalsEnabled = true } = options
|
|
237
|
-
|
|
238
|
-
switch (event.type) {
|
|
239
|
-
case 'text-delta':
|
|
240
|
-
accumulator.appendText(event.text)
|
|
241
|
-
break
|
|
242
|
-
|
|
243
|
-
case 'thinking-delta':
|
|
244
|
-
accumulator.appendThinking(event.text)
|
|
245
|
-
break
|
|
246
|
-
|
|
247
|
-
case 'guide-delta':
|
|
248
|
-
accumulator.appendGuide(event.text)
|
|
249
|
-
break
|
|
250
|
-
|
|
251
|
-
case 'tool-execution':
|
|
252
|
-
accumulator.addToolExecution({ type: 'tool_execution', data: event.data })
|
|
253
|
-
break
|
|
383
|
+
const existingStatus = approvalStatuses[data.approvalRequestId] as ChatApprovalStatus | undefined
|
|
384
|
+
const status: ChatApprovalStatus = existingStatus || (data.approved ? 'approved' : 'rejected')
|
|
385
|
+
const resolvedByName = 'resolvedByName' in data ? data.resolvedByName : undefined
|
|
386
|
+
const escalatedData = escalatedApprovals?.get(data.approvalRequestId)
|
|
254
387
|
|
|
255
|
-
|
|
256
|
-
const approvalType = event.approvalType || 'CLIENT'
|
|
257
|
-
const toolCalls = event.toolCalls as PendingToolCallData[] | undefined
|
|
258
|
-
const isBatch = !!toolCalls && toolCalls.length > 0
|
|
259
|
-
|
|
260
|
-
if (!displayApprovalTypes || displayApprovalTypes.includes(approvalType)) {
|
|
261
|
-
if (isBatch) {
|
|
262
|
-
const status = (approvalStatuses[event.requestId] as ChatApprovalStatus) || 'pending'
|
|
388
|
+
if (escalatedData?.toolCalls && escalatedData.toolCalls.length > 0) {
|
|
263
389
|
if (batchApprovalsEnabled) {
|
|
264
|
-
accumulator.addApprovalBatch(
|
|
390
|
+
accumulator.addApprovalBatch(
|
|
391
|
+
data.approvalRequestId,
|
|
392
|
+
escalatedData.approvalType,
|
|
393
|
+
escalatedData.toolCalls,
|
|
394
|
+
status,
|
|
395
|
+
undefined,
|
|
396
|
+
resolvedByName,
|
|
397
|
+
)
|
|
265
398
|
} else {
|
|
266
|
-
|
|
267
|
-
for (const call of toolCalls!) {
|
|
399
|
+
for (const call of escalatedData.toolCalls) {
|
|
268
400
|
if (!call.requiresApproval) continue
|
|
269
401
|
accumulator.addApprovalRequest(
|
|
270
|
-
|
|
402
|
+
data.approvalRequestId,
|
|
271
403
|
getCommandText(call),
|
|
272
404
|
call.toolExplanation,
|
|
273
|
-
approvalType,
|
|
405
|
+
escalatedData.approvalType,
|
|
274
406
|
status,
|
|
275
407
|
)
|
|
276
408
|
}
|
|
277
409
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// flipped it and fed it back via `approvalStatuses`) while the
|
|
281
|
-
// matching APPROVAL_RESULT row is absent from the fetched history
|
|
282
|
-
// pages. Without this, the single-approval path tracks it as
|
|
283
|
-
// pending and `flushPendingApprovals()` resurrects it as a stale
|
|
284
|
-
// sticky card on every history re-process. Mirror the batch path
|
|
285
|
-
// and honor `approvalStatuses`.
|
|
286
|
-
const resolvedStatus = approvalStatuses[event.requestId] as ChatApprovalStatus | undefined
|
|
287
|
-
if (resolvedStatus === 'approved' || resolvedStatus === 'rejected') {
|
|
288
|
-
accumulator.addApprovalRequest(
|
|
289
|
-
event.requestId,
|
|
290
|
-
event.command || '',
|
|
291
|
-
event.explanation,
|
|
292
|
-
approvalType,
|
|
293
|
-
resolvedStatus,
|
|
294
|
-
)
|
|
295
|
-
} else {
|
|
296
|
-
accumulator.trackApprovalRequest(event.requestId, {
|
|
297
|
-
command: event.command || '',
|
|
298
|
-
explanation: event.explanation,
|
|
299
|
-
approvalType,
|
|
300
|
-
})
|
|
301
|
-
}
|
|
410
|
+
escalatedApprovals?.delete(data.approvalRequestId)
|
|
411
|
+
break
|
|
302
412
|
}
|
|
303
|
-
} else {
|
|
304
|
-
escalatedApprovals?.set(event.requestId, {
|
|
305
|
-
command: event.command || '',
|
|
306
|
-
explanation: event.explanation,
|
|
307
|
-
approvalType,
|
|
308
|
-
...(isBatch ? { toolCalls } : {}),
|
|
309
|
-
})
|
|
310
|
-
}
|
|
311
|
-
break
|
|
312
|
-
}
|
|
313
413
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (escalatedData?.toolCalls && escalatedData.toolCalls.length > 0) {
|
|
323
|
-
if (batchApprovalsEnabled) {
|
|
324
|
-
accumulator.addApprovalBatch(
|
|
325
|
-
requestId,
|
|
326
|
-
escalatedData.approvalType,
|
|
327
|
-
escalatedData.toolCalls,
|
|
328
|
-
status,
|
|
329
|
-
undefined,
|
|
330
|
-
resolvedByName,
|
|
331
|
-
)
|
|
332
|
-
} else {
|
|
333
|
-
for (const call of escalatedData.toolCalls) {
|
|
334
|
-
if (!call.requiresApproval) continue
|
|
335
|
-
accumulator.addApprovalRequest(
|
|
336
|
-
requestId,
|
|
337
|
-
getCommandText(call),
|
|
338
|
-
call.toolExplanation,
|
|
339
|
-
escalatedData.approvalType,
|
|
340
|
-
status,
|
|
341
|
-
)
|
|
342
|
-
}
|
|
414
|
+
if (escalatedData) {
|
|
415
|
+
accumulator.trackApprovalRequest(data.approvalRequestId, {
|
|
416
|
+
command: escalatedData.command,
|
|
417
|
+
explanation: escalatedData.explanation,
|
|
418
|
+
approvalType: escalatedData.approvalType,
|
|
419
|
+
})
|
|
420
|
+
escalatedApprovals?.delete(data.approvalRequestId)
|
|
343
421
|
}
|
|
344
|
-
escalatedApprovals?.delete(requestId)
|
|
345
|
-
break
|
|
346
|
-
}
|
|
347
422
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
423
|
+
// If a segment with this id is already present (batch or legacy), just flip its status.
|
|
424
|
+
// updateApprovalStatus matches both `approval_batch` and `approval_request` segments.
|
|
425
|
+
const before = accumulator.getSegments()
|
|
426
|
+
const after = accumulator.updateApprovalStatus(data.approvalRequestId, status, resolvedByName)
|
|
427
|
+
const updatedExisting = before.some((s, i) => after[i] !== s)
|
|
428
|
+
if (updatedExisting) break
|
|
429
|
+
|
|
430
|
+
accumulator.processApprovalResult(
|
|
431
|
+
data.approvalRequestId,
|
|
432
|
+
status === 'approved',
|
|
433
|
+
data.approvalType || 'USER'
|
|
434
|
+
)
|
|
355
435
|
}
|
|
356
|
-
|
|
357
|
-
// If a segment with this id is already present (batch or legacy), just
|
|
358
|
-
// flip its status. updateApprovalStatus matches both `approval_batch`
|
|
359
|
-
// and `approval_request` segments.
|
|
360
|
-
const before = accumulator.getSegments()
|
|
361
|
-
const after = accumulator.updateApprovalStatus(requestId, status, resolvedByName)
|
|
362
|
-
const updatedExisting = before.some((s, i) => after[i] !== s)
|
|
363
|
-
if (updatedExisting) break
|
|
364
|
-
|
|
365
|
-
accumulator.processApprovalResult(
|
|
366
|
-
requestId,
|
|
367
|
-
status === 'approved',
|
|
368
|
-
event.approvalType || 'USER',
|
|
369
|
-
)
|
|
370
436
|
break
|
|
371
|
-
}
|
|
372
437
|
|
|
373
|
-
case
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
438
|
+
case MESSAGE_TYPE.ERROR:
|
|
439
|
+
if ('error' in data) {
|
|
440
|
+
let message: string | undefined
|
|
441
|
+
if ('details' in data && data?.details) {
|
|
442
|
+
try {
|
|
443
|
+
message = JSON.parse(data.details)?.error?.message
|
|
444
|
+
} catch {
|
|
445
|
+
message = data.details
|
|
446
|
+
}
|
|
380
447
|
}
|
|
448
|
+
accumulator.addError(
|
|
449
|
+
data.error || 'An error occurred',
|
|
450
|
+
message
|
|
451
|
+
)
|
|
381
452
|
}
|
|
382
|
-
accumulator.addError(event.title, message)
|
|
383
453
|
break
|
|
384
|
-
}
|
|
385
454
|
|
|
386
|
-
case
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
455
|
+
case MESSAGE_TYPE.CONTEXT_COMPACTION_START:
|
|
456
|
+
accumulator.addContextCompaction()
|
|
457
|
+
break
|
|
458
|
+
|
|
459
|
+
case MESSAGE_TYPE.CONTEXT_COMPACTION_END: {
|
|
460
|
+
const summary = 'summary' in data && typeof data.summary === 'string' ? data.summary : undefined
|
|
461
|
+
accumulator.completeContextCompaction(summary)
|
|
392
462
|
break
|
|
463
|
+
}
|
|
393
464
|
|
|
394
465
|
default:
|
|
395
|
-
//
|
|
396
|
-
// realtime-only vocabulary that never appears in persisted rows.
|
|
466
|
+
// Unknown message type - ignore
|
|
397
467
|
break
|
|
398
468
|
}
|
|
399
469
|
}
|
|
400
470
|
|
|
401
|
-
// =============================================================================
|
|
402
|
-
// Envelope — ONE implementation (previous duplicate deleted)
|
|
403
|
-
// =============================================================================
|
|
404
|
-
|
|
405
471
|
/**
|
|
406
|
-
*
|
|
472
|
+
* Extract error messages from historical messages
|
|
473
|
+
* Returns a separate array of error messages that should be displayed
|
|
407
474
|
*/
|
|
408
|
-
export
|
|
409
|
-
messages:
|
|
410
|
-
|
|
475
|
+
export function extractErrorMessages(
|
|
476
|
+
messages: HistoricalMessage[],
|
|
477
|
+
options: MessageProcessingOptions = {}
|
|
478
|
+
): ProcessedMessage[] {
|
|
479
|
+
const { assistantName = 'Fae', assistantType = 'fae', assistantAvatar, chatTypeFilter } = options
|
|
480
|
+
|
|
481
|
+
const errorMessages: ProcessedMessage[] = []
|
|
482
|
+
|
|
483
|
+
messages.forEach((msg) => {
|
|
484
|
+
if (chatTypeFilter && msg.chatType !== chatTypeFilter) return
|
|
485
|
+
|
|
486
|
+
const messageDataArray = Array.isArray(msg.messageData)
|
|
487
|
+
? msg.messageData
|
|
488
|
+
: msg.messageData
|
|
489
|
+
? [msg.messageData]
|
|
490
|
+
: []
|
|
491
|
+
|
|
492
|
+
messageDataArray.forEach((data) => {
|
|
493
|
+
if (data.type === MESSAGE_TYPE.ERROR) {
|
|
494
|
+
errorMessages.push({
|
|
495
|
+
id: `${msg.id}-error`,
|
|
496
|
+
role: 'error',
|
|
497
|
+
content: 'error' in data && data.error ? data.error : 'An error occurred',
|
|
498
|
+
name: assistantName,
|
|
499
|
+
assistantType,
|
|
500
|
+
timestamp: new Date(msg.createdAt),
|
|
501
|
+
avatar: assistantAvatar,
|
|
502
|
+
})
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
return errorMessages
|
|
411
508
|
}
|
|
412
509
|
|
|
413
510
|
/**
|
|
414
|
-
* Process
|
|
511
|
+
* Process messages and include error messages in the correct order
|
|
415
512
|
*/
|
|
416
|
-
export function
|
|
513
|
+
export function processHistoricalMessagesWithErrors(
|
|
417
514
|
messages: HistoricalMessage[],
|
|
418
|
-
options: MessageProcessingOptions = {}
|
|
515
|
+
options: MessageProcessingOptions = {}
|
|
419
516
|
): ProcessHistoricalMessagesResult {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
assistantAvatar,
|
|
424
|
-
onApprove,
|
|
425
|
-
onReject,
|
|
426
|
-
chatTypeFilter,
|
|
427
|
-
approvalStatuses = {},
|
|
428
|
-
// An omitted option means "display every approval type" — the original
|
|
429
|
-
// history semantics. Deliberately NOT defaulted to the realtime
|
|
430
|
-
// reducer's ['CLIENT']: consumers that pass a wider list to their
|
|
431
|
-
// realtime path but omit it on the history path would silently lose
|
|
432
|
-
// pending non-CLIENT approval cards on every reload/reconnect refetch
|
|
433
|
-
// (they also ignore `escalatedApprovals`). Realtime/history parity is
|
|
434
|
-
// opt-in: pass the same explicit list to both.
|
|
435
|
-
displayApprovalTypes,
|
|
436
|
-
batchApprovalsEnabled,
|
|
437
|
-
} = options
|
|
517
|
+
// displayApprovalTypes omitted = display every approval type (original
|
|
518
|
+
// history semantics — see the matching note in processHistoricalMessages).
|
|
519
|
+
const { chatTypeFilter, assistantName = 'Fae', assistantType = 'fae', assistantAvatar, onApprove, onReject, approvalStatuses = {}, displayApprovalTypes, batchApprovalsEnabled } = options
|
|
438
520
|
|
|
439
521
|
const processedMessages: ProcessedMessage[] = []
|
|
440
522
|
const accumulator = createMessageSegmentAccumulator({ onApprove, onReject })
|
|
441
|
-
const escalatedApprovals
|
|
523
|
+
const escalatedApprovals = new Map<
|
|
524
|
+
string,
|
|
525
|
+
{ command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
|
|
526
|
+
>()
|
|
442
527
|
|
|
443
528
|
let currentAssistantId: string | null = null
|
|
444
529
|
let currentAssistantTimestamp: Date | null = null
|
|
@@ -447,10 +532,6 @@ export function processHistoricalMessages(
|
|
|
447
532
|
// — carried onto the flushed message's streamSeq for per-role merge coverage.
|
|
448
533
|
let currentAssistantStreamSeq: number | undefined
|
|
449
534
|
|
|
450
|
-
/**
|
|
451
|
-
* Flush the current assistant message to processedMessages.
|
|
452
|
-
* Uses the LAST message ID in the group for stable React keys across page boundaries.
|
|
453
|
-
*/
|
|
454
535
|
const flushAssistantMessage = () => {
|
|
455
536
|
const idToUse = lastAssistantId || currentAssistantId
|
|
456
537
|
if (idToUse && accumulator.hasContent()) {
|
|
@@ -481,7 +562,6 @@ export function processHistoricalMessages(
|
|
|
481
562
|
}
|
|
482
563
|
|
|
483
564
|
messages.forEach((msg, index) => {
|
|
484
|
-
// Filter by chat type if specified
|
|
485
565
|
if (chatTypeFilter && msg.chatType !== chatTypeFilter) return
|
|
486
566
|
|
|
487
567
|
const messageDataArray = Array.isArray(msg.messageData)
|
|
@@ -545,10 +625,8 @@ export function processHistoricalMessages(
|
|
|
545
625
|
}
|
|
546
626
|
|
|
547
627
|
messageDataArray.forEach((data) => {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
applyHistoryEvent(
|
|
551
|
-
event,
|
|
628
|
+
processMessageData(
|
|
629
|
+
data,
|
|
552
630
|
accumulator,
|
|
553
631
|
approvalStatuses,
|
|
554
632
|
{ displayApprovalTypes, batchApprovalsEnabled },
|
|
@@ -556,7 +634,6 @@ export function processHistoricalMessages(
|
|
|
556
634
|
)
|
|
557
635
|
})
|
|
558
636
|
|
|
559
|
-
// Check if we should flush (next message is from user or last message)
|
|
560
637
|
const nextMsg = messages[index + 1]
|
|
561
638
|
const isLastMessage = index === messages.length - 1
|
|
562
639
|
const nextIsFromUser =
|
|
@@ -589,52 +666,3 @@ export function processHistoricalMessages(
|
|
|
589
666
|
escalatedApprovals: escalatedApprovals
|
|
590
667
|
}
|
|
591
668
|
}
|
|
592
|
-
|
|
593
|
-
/**
|
|
594
|
-
* Extract error messages from historical messages
|
|
595
|
-
* Returns a separate array of error messages that should be displayed
|
|
596
|
-
*/
|
|
597
|
-
export function extractErrorMessages(
|
|
598
|
-
messages: HistoricalMessage[],
|
|
599
|
-
options: MessageProcessingOptions = {}
|
|
600
|
-
): ProcessedMessage[] {
|
|
601
|
-
const { assistantName = 'Fae', assistantType = 'fae', assistantAvatar, chatTypeFilter } = options
|
|
602
|
-
|
|
603
|
-
const errorMessages: ProcessedMessage[] = []
|
|
604
|
-
|
|
605
|
-
messages.forEach((msg) => {
|
|
606
|
-
if (chatTypeFilter && msg.chatType !== chatTypeFilter) return
|
|
607
|
-
|
|
608
|
-
const messageDataArray = Array.isArray(msg.messageData)
|
|
609
|
-
? msg.messageData
|
|
610
|
-
: msg.messageData
|
|
611
|
-
? [msg.messageData]
|
|
612
|
-
: []
|
|
613
|
-
|
|
614
|
-
messageDataArray.forEach((data) => {
|
|
615
|
-
if (data.type === MESSAGE_TYPE.ERROR) {
|
|
616
|
-
errorMessages.push({
|
|
617
|
-
id: `${msg.id}-error`,
|
|
618
|
-
role: 'error',
|
|
619
|
-
content: 'error' in data && data.error ? data.error : 'An error occurred',
|
|
620
|
-
name: assistantName,
|
|
621
|
-
assistantType,
|
|
622
|
-
timestamp: new Date(msg.createdAt),
|
|
623
|
-
avatar: assistantAvatar,
|
|
624
|
-
})
|
|
625
|
-
}
|
|
626
|
-
})
|
|
627
|
-
})
|
|
628
|
-
|
|
629
|
-
return errorMessages
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Process messages and include error messages in the correct order.
|
|
634
|
-
*
|
|
635
|
-
* HISTORICAL ALIAS: this was a byte-identical second copy of
|
|
636
|
-
* `processHistoricalMessages` (both goldens snapshot identical output on the
|
|
637
|
-
* same corpus). The duplicate envelope was deleted in Phase 3 — kept as an
|
|
638
|
-
* alias for the established import sites.
|
|
639
|
-
*/
|
|
640
|
-
export const processHistoricalMessagesWithErrors = processHistoricalMessages
|