@flamingo-stack/openframe-frontend-core 0.0.578 → 0.0.579-1952.5335.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/dist/chat-protocol/decode.d.ts.map +1 -1
  2. package/dist/chat-protocol/events.d.ts +1 -47
  3. package/dist/chat-protocol/events.d.ts.map +1 -1
  4. package/dist/chat-protocol/index.cjs +0 -51
  5. package/dist/chat-protocol/index.cjs.map +1 -1
  6. package/dist/chat-protocol/index.js +1 -48
  7. package/dist/chat-protocol/index.js.map +1 -1
  8. package/dist/chat-protocol/leading-frames.d.ts +3 -9
  9. package/dist/chat-protocol/leading-frames.d.ts.map +1 -1
  10. package/dist/chat-protocol/nats-decoder.d.ts +0 -41
  11. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
  12. package/dist/{chunk-IBSOYTGZ.js → chunk-2ML5QEDH.js} +3 -3
  13. package/dist/{chunk-NPNYQSCG.cjs → chunk-3B42TFEE.cjs} +21 -21
  14. package/dist/{chunk-NPNYQSCG.cjs.map → chunk-3B42TFEE.cjs.map} +1 -1
  15. package/dist/{chunk-POB6GNIR.cjs → chunk-3XFMM34I.cjs} +43 -43
  16. package/dist/{chunk-POB6GNIR.cjs.map → chunk-3XFMM34I.cjs.map} +1 -1
  17. package/dist/{chunk-R7OJZCE6.cjs → chunk-4C3MS2G6.cjs} +69 -69
  18. package/dist/{chunk-R7OJZCE6.cjs.map → chunk-4C3MS2G6.cjs.map} +1 -1
  19. package/dist/{chunk-G3AJXLKH.cjs → chunk-653A7FPP.cjs} +22 -22
  20. package/dist/{chunk-G3AJXLKH.cjs.map → chunk-653A7FPP.cjs.map} +1 -1
  21. package/dist/{chunk-KPE277X2.js → chunk-6QDW4CDG.js} +2 -2
  22. package/dist/{chunk-E5QAU63Y.cjs → chunk-7WKFLOC7.cjs} +9 -9
  23. package/dist/{chunk-E5QAU63Y.cjs.map → chunk-7WKFLOC7.cjs.map} +1 -1
  24. package/dist/{chunk-ABWA3W3M.cjs → chunk-AAHKPPGQ.cjs} +32 -32
  25. package/dist/{chunk-ABWA3W3M.cjs.map → chunk-AAHKPPGQ.cjs.map} +1 -1
  26. package/dist/{chunk-JJH4H4CJ.js → chunk-AT4WAFJO.js} +5 -5
  27. package/dist/{chunk-5IAGT5LB.js → chunk-C4KL7OQI.js} +2 -2
  28. package/dist/{chunk-SETRICAM.cjs → chunk-C7Q2PVHD.cjs} +4 -4
  29. package/dist/{chunk-SETRICAM.cjs.map → chunk-C7Q2PVHD.cjs.map} +1 -1
  30. package/dist/{chunk-AJ67T7LI.js → chunk-CGVPUA4X.js} +3 -3
  31. package/dist/{chunk-BFJBE4PQ.js → chunk-CSBNQJSI.js} +4 -4
  32. package/dist/{chunk-LPFA56WY.js → chunk-DBP7S75Q.js} +1317 -1480
  33. package/dist/chunk-DBP7S75Q.js.map +1 -0
  34. package/dist/{chunk-KQBYQBQN.cjs → chunk-DGB2FLFH.cjs} +10 -10
  35. package/dist/{chunk-KQBYQBQN.cjs.map → chunk-DGB2FLFH.cjs.map} +1 -1
  36. package/dist/{chunk-GOEBT72V.cjs → chunk-EVTRRDIH.cjs} +15 -15
  37. package/dist/{chunk-GOEBT72V.cjs.map → chunk-EVTRRDIH.cjs.map} +1 -1
  38. package/dist/{chunk-UQPA2XT7.cjs → chunk-GP2QLZIN.cjs} +24 -24
  39. package/dist/{chunk-UQPA2XT7.cjs.map → chunk-GP2QLZIN.cjs.map} +1 -1
  40. package/dist/{chunk-KLG3MGRP.js → chunk-KXLJT5U5.js} +3 -3
  41. package/dist/{chunk-SNZZ6Q6G.js → chunk-L66XBQM6.js} +2 -2
  42. package/dist/{chunk-SHJXC7A4.js → chunk-NWAUHBYH.js} +3 -3
  43. package/dist/{chunk-YOYHJQX3.cjs → chunk-OCSAB6NT.cjs} +5 -5
  44. package/dist/{chunk-YOYHJQX3.cjs.map → chunk-OCSAB6NT.cjs.map} +1 -1
  45. package/dist/{chunk-5NG2P2KI.js → chunk-PFTEG6L7.js} +5 -5
  46. package/dist/{chunk-BLPGMX2L.cjs → chunk-PITZMTGE.cjs} +159 -159
  47. package/dist/{chunk-BLPGMX2L.cjs.map → chunk-PITZMTGE.cjs.map} +1 -1
  48. package/dist/{chunk-ZBVMPVJF.js → chunk-QOPQ6BCA.js} +3 -3
  49. package/dist/{chunk-IWK2NFVR.js → chunk-RXZWUJEB.js} +2 -2
  50. package/dist/{chunk-OIJWSYKP.cjs → chunk-SY5HCZZO.cjs} +3 -3
  51. package/dist/{chunk-OIJWSYKP.cjs.map → chunk-SY5HCZZO.cjs.map} +1 -1
  52. package/dist/{chunk-ESXUEMPA.js → chunk-TYJITV3H.js} +2 -2
  53. package/dist/{chunk-M7FNT5AC.cjs → chunk-VLRXLTR4.cjs} +167 -330
  54. package/dist/chunk-VLRXLTR4.cjs.map +1 -0
  55. package/dist/{chunk-WUZQYZXI.cjs → chunk-WHSSI3S7.cjs} +3 -3
  56. package/dist/{chunk-WUZQYZXI.cjs.map → chunk-WHSSI3S7.cjs.map} +1 -1
  57. package/dist/{chunk-LEUJNBWU.js → chunk-XJ45MHWI.js} +6 -6
  58. package/dist/{chunk-NJDL7F7K.cjs → chunk-YYEVFC3K.cjs} +10 -10
  59. package/dist/{chunk-NJDL7F7K.cjs.map → chunk-YYEVFC3K.cjs.map} +1 -1
  60. package/dist/{chunk-QHGARH6P.js → chunk-ZCEQLHU3.js} +5 -5
  61. package/dist/components/case-studies/index.cjs +9 -9
  62. package/dist/components/case-studies/index.js +3 -3
  63. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  64. package/dist/components/chat/index.cjs +2 -10
  65. package/dist/components/chat/index.cjs.map +1 -1
  66. package/dist/components/chat/index.d.ts +0 -1
  67. package/dist/components/chat/index.d.ts.map +1 -1
  68. package/dist/components/chat/index.js +1 -9
  69. package/dist/components/chat/stream/chat-stream-reducer.d.ts +0 -9
  70. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
  71. package/dist/components/chat/stream/delta-batcher.d.ts +3 -3
  72. package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -1
  73. package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
  74. package/dist/components/chat/types/component.types.d.ts +0 -6
  75. package/dist/components/chat/types/component.types.d.ts.map +1 -1
  76. package/dist/components/chat/types/message.types.d.ts +2 -30
  77. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  78. package/dist/components/chat/types/network.types.d.ts +1 -3
  79. package/dist/components/chat/types/network.types.d.ts.map +1 -1
  80. package/dist/components/chat/types/processing.types.d.ts +1 -4
  81. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  82. package/dist/components/chat/utils/approval-display.d.ts +8 -36
  83. package/dist/components/chat/utils/approval-display.d.ts.map +1 -1
  84. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  85. package/dist/components/chat/utils/index.d.ts +1 -1
  86. package/dist/components/chat/utils/index.d.ts.map +1 -1
  87. package/dist/components/chat/utils/message-segment-accumulator.d.ts +4 -16
  88. package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
  89. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  90. package/dist/components/chat/utils/resolve-fetched-card-href.d.ts +1 -1
  91. package/dist/components/contact/index.cjs +4 -4
  92. package/dist/components/contact/index.js +3 -3
  93. package/dist/components/docs/index.cjs +7 -7
  94. package/dist/components/docs/index.js +6 -6
  95. package/dist/components/embeds/index.cjs +4 -4
  96. package/dist/components/embeds/index.js +3 -3
  97. package/dist/components/faq/index.cjs +4 -4
  98. package/dist/components/faq/index.js +3 -3
  99. package/dist/components/features/index.cjs +3 -3
  100. package/dist/components/features/index.js +2 -2
  101. package/dist/components/help-center-pages/index.cjs +23 -23
  102. package/dist/components/help-center-pages/index.js +14 -14
  103. package/dist/components/index.cjs +121 -129
  104. package/dist/components/index.cjs.map +1 -1
  105. package/dist/components/index.js +13 -21
  106. package/dist/components/index.js.map +1 -1
  107. package/dist/components/meeting-scheduler/index.cjs +21 -21
  108. package/dist/components/meeting-scheduler/index.js +2 -2
  109. package/dist/components/navigation/index.cjs +6 -6
  110. package/dist/components/navigation/index.js +5 -5
  111. package/dist/components/onboarding-guides/index.cjs +6 -6
  112. package/dist/components/onboarding-guides/index.js +5 -5
  113. package/dist/components/tickets/index.cjs +7 -7
  114. package/dist/components/tickets/index.js +6 -6
  115. package/dist/components/ui/index.cjs +3 -11
  116. package/dist/components/ui/index.cjs.map +1 -1
  117. package/dist/components/ui/index.js +2 -10
  118. package/dist/index.cjs +7 -15
  119. package/dist/index.cjs.map +1 -1
  120. package/dist/index.js +6 -14
  121. package/dist/index.js.map +1 -1
  122. package/dist/schemas/meeting-booking-schema.d.ts +2 -2
  123. package/dist/utils/index.cjs.map +1 -1
  124. package/dist/utils/index.js.map +1 -1
  125. package/package.json +1 -1
  126. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +0 -80
  127. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +1 -118
  128. package/src/chat-protocol/decode.ts +3 -4
  129. package/src/chat-protocol/events.ts +0 -51
  130. package/src/chat-protocol/leading-frames.ts +3 -9
  131. package/src/chat-protocol/nats-decoder.ts +1 -110
  132. package/src/components/chat/__tests__/chat-pending-turn.test.tsx +0 -19
  133. package/src/components/chat/chat-message-enhanced.tsx +1 -13
  134. package/src/components/chat/index.ts +0 -1
  135. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +0 -90
  136. package/src/components/chat/stream/chat-dialog-store.ts +1 -1
  137. package/src/components/chat/stream/chat-stream-reducer.ts +11 -49
  138. package/src/components/chat/stream/delta-batcher.ts +4 -9
  139. package/src/components/chat/stream/message-mutations.ts +0 -2
  140. package/src/components/chat/types/component.types.ts +0 -9
  141. package/src/components/chat/types/message.types.ts +0 -32
  142. package/src/components/chat/types/network.types.ts +1 -3
  143. package/src/components/chat/types/processing.types.ts +1 -4
  144. package/src/components/chat/utils/__tests__/process-historical-messages-approvals.test.ts +0 -93
  145. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +1 -78
  146. package/src/components/chat/utils/approval-display.ts +8 -53
  147. package/src/components/chat/utils/extract-incomplete-message-state.ts +0 -1
  148. package/src/components/chat/utils/index.ts +1 -6
  149. package/src/components/chat/utils/message-segment-accumulator.ts +3 -37
  150. package/src/components/chat/utils/process-historical-messages.ts +7 -44
  151. package/src/components/chat/utils/resolve-fetched-card-href.ts +1 -1
  152. package/src/stories/ChatMessageEnhanced.stories.tsx +3 -7
  153. package/dist/chunk-LPFA56WY.js.map +0 -1
  154. package/dist/chunk-M7FNT5AC.cjs.map +0 -1
  155. package/dist/components/chat/guide-display.d.ts +0 -14
  156. package/dist/components/chat/guide-display.d.ts.map +0 -1
  157. package/src/components/chat/guide-display.tsx +0 -42
  158. /package/dist/{chunk-IBSOYTGZ.js.map → chunk-2ML5QEDH.js.map} +0 -0
  159. /package/dist/{chunk-KPE277X2.js.map → chunk-6QDW4CDG.js.map} +0 -0
  160. /package/dist/{chunk-JJH4H4CJ.js.map → chunk-AT4WAFJO.js.map} +0 -0
  161. /package/dist/{chunk-5IAGT5LB.js.map → chunk-C4KL7OQI.js.map} +0 -0
  162. /package/dist/{chunk-AJ67T7LI.js.map → chunk-CGVPUA4X.js.map} +0 -0
  163. /package/dist/{chunk-BFJBE4PQ.js.map → chunk-CSBNQJSI.js.map} +0 -0
  164. /package/dist/{chunk-KLG3MGRP.js.map → chunk-KXLJT5U5.js.map} +0 -0
  165. /package/dist/{chunk-SNZZ6Q6G.js.map → chunk-L66XBQM6.js.map} +0 -0
  166. /package/dist/{chunk-SHJXC7A4.js.map → chunk-NWAUHBYH.js.map} +0 -0
  167. /package/dist/{chunk-5NG2P2KI.js.map → chunk-PFTEG6L7.js.map} +0 -0
  168. /package/dist/{chunk-ZBVMPVJF.js.map → chunk-QOPQ6BCA.js.map} +0 -0
  169. /package/dist/{chunk-IWK2NFVR.js.map → chunk-RXZWUJEB.js.map} +0 -0
  170. /package/dist/{chunk-ESXUEMPA.js.map → chunk-TYJITV3H.js.map} +0 -0
  171. /package/dist/{chunk-LEUJNBWU.js.map → chunk-XJ45MHWI.js.map} +0 -0
  172. /package/dist/{chunk-QHGARH6P.js.map → chunk-ZCEQLHU3.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.578",
3
+ "version": "0.0.579-1952.5335.1",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -254,86 +254,6 @@ exports[`decodeNatsChunk — golden corpus > maps the full recorded corpus to no
254
254
  },
255
255
  "type": "tool-execution",
256
256
  },
257
- "guide": {
258
- "text": "## Enroll a device
259
- 1. Open Settings",
260
- "type": "guide-delta",
261
- },
262
- "guide_empty_string": {
263
- "text": "",
264
- "type": "guide-delta",
265
- },
266
- "guide_frame_approval_batch": {
267
- "approvalType": "chat",
268
- "origin": "guide",
269
- "requestId": "batch:prop-1",
270
- "status": "pending",
271
- "toolCalls": [
272
- {
273
- "requiresApproval": true,
274
- "toolCallArguments": null,
275
- "toolExecutionRequestId": "prop-1",
276
- "toolName": "create_ticket",
277
- "toolTitle": "Open a ticket",
278
- },
279
- ],
280
- "type": "approval-request",
281
- },
282
- "guide_frame_approval_request": {
283
- "approvalType": "create_ticket",
284
- "command": "Open a support ticket",
285
- "fields": [
286
- {
287
- "label": "Subject",
288
- "value": "Agent will not enroll",
289
- },
290
- ],
291
- "origin": "guide",
292
- "requestId": "prop-1",
293
- "status": "pending",
294
- "type": "approval-request",
295
- },
296
- "guide_frame_decision_resolved": {
297
- "ok": true,
298
- "origin": "guide",
299
- "requestId": "prop-1",
300
- "status": "approved",
301
- "type": "approval-resolved",
302
- "willAutoContinue": false,
303
- },
304
- "guide_frame_metadata": {
305
- "conversationId": "conv-1",
306
- "origin": "guide",
307
- "type": "metadata",
308
- },
309
- "guide_frame_payload_array": null,
310
- "guide_frame_payload_empty": null,
311
- "guide_frame_payload_not_object": null,
312
- "guide_frame_routing": null,
313
- "guide_frame_status_thinking": null,
314
- "guide_frame_text_leading": {
315
- "text": "I'll look that up in the docs.",
316
- "type": "guide-delta",
317
- },
318
- "guide_frame_text_wins": {
319
- "text": "body",
320
- "type": "guide-delta",
321
- },
322
- "guide_frame_thinking": {
323
- "text": "The user asked about <policies> setup",
324
- "type": "thinking-delta",
325
- },
326
- "guide_frame_tool_error": {
327
- "title": "Could not reach HubSpot",
328
- "type": "error",
329
- },
330
- "guide_frame_usage_start": null,
331
- "guide_frame_with_seq": {
332
- "seq": 77,
333
- "text": "Checking the docs…",
334
- "type": "guide-delta",
335
- },
336
- "guide_missing_text": null,
337
257
  "message_end": {
338
258
  "type": "turn-end",
339
259
  },
@@ -11,8 +11,7 @@
11
11
  */
12
12
 
13
13
  import { describe, it, expect } from 'vitest';
14
- import type { ChatStreamEvent } from '../events';
15
- import { decodeNatsChunk, guideEventForNats } from '../nats-decoder';
14
+ import { decodeNatsChunk } from '../nats-decoder';
16
15
 
17
16
  /** Recorded corpus of realistic NATS chunk shapes, keyed by scenario name. */
18
17
  const CORPUS: Record<string, unknown> = {
@@ -26,73 +25,6 @@ const CORPUS: Record<string, unknown> = {
26
25
  thinking: { type: 'THINKING', text: 'Considering options…' },
27
26
  thinking_missing_text: { type: 'THINKING' },
28
27
 
29
- guide: { type: 'GUIDE', text: '## Enroll a device\n1. Open Settings' },
30
- guide_empty_string: { type: 'GUIDE', text: '' },
31
- guide_missing_text: { type: 'GUIDE' },
32
-
33
- // GUIDE frames — hub frames the agent re-streams verbatim inside the same
34
- // chunk type, under `payload`. They cross over by DEFAULT; only the dialog's
35
- // own accounting and phase stop at the seam. See `guideEventForNats`.
36
- guide_frame_text_leading: {
37
- type: 'GUIDE',
38
- payload: { kind: 'text-leading', text: "I'll look that up in the docs." },
39
- },
40
- guide_frame_thinking: {
41
- type: 'GUIDE',
42
- payload: { kind: 'thinking-delta', text: 'The user asked about <policies> setup' },
43
- },
44
- guide_frame_tool_error: {
45
- type: 'GUIDE',
46
- payload: { kind: 'tool_error', message: 'Could not reach HubSpot' },
47
- },
48
- // Dropped: would blank the model badge / relabel the turn with the hub's model.
49
- guide_frame_routing: {
50
- type: 'GUIDE',
51
- payload: { kind: 'routing', routedComplexity: 'deep', routedModel: 'claude-x', routedThinkingBudget: 8000 },
52
- },
53
- guide_frame_metadata: {
54
- type: 'GUIDE',
55
- payload: { model: 'claude-x', modelLabel: 'Guide model', conversationId: 'conv-1' },
56
- },
57
- // Dropped: the dialog owns its own phase and token accounting.
58
- guide_frame_status_thinking: { type: 'GUIDE', payload: { status: 'thinking' } },
59
- guide_frame_usage_start: { type: 'GUIDE', payload: { kind: 'usage', stage: 'start', input_tokens: 12 } },
60
- // Crosses over stamped `origin: 'guide'` — the marker that keeps the card
61
- // inline and sends its buttons to the hub's confirm route.
62
- guide_frame_approval_request: {
63
- type: 'GUIDE',
64
- payload: {
65
- kind: 'approval_request',
66
- proposalId: 'prop-1',
67
- toolName: 'create_ticket',
68
- title: 'Open a support ticket',
69
- fields: [{ label: 'Subject', value: 'Agent will not enroll' }],
70
- },
71
- },
72
- guide_frame_approval_batch: {
73
- type: 'GUIDE',
74
- payload: {
75
- kind: 'approval_batch',
76
- batchId: 'batch:prop-1',
77
- proposals: [{ proposalId: 'prop-1', toolName: 'create_ticket', title: 'Open a ticket' }],
78
- },
79
- },
80
- guide_frame_decision_resolved: {
81
- type: 'GUIDE',
82
- payload: { kind: 'decision_resolved', proposalId: 'prop-1', ok: true, action: 'approved', willAutoContinue: false },
83
- },
84
- // Malformed payloads degrade to a no-op, like every other chunk shape.
85
- guide_frame_payload_not_object: { type: 'GUIDE', payload: 'text-leading' },
86
- guide_frame_payload_array: { type: 'GUIDE', payload: [{ kind: 'text-leading', text: 'hi' }] },
87
- guide_frame_payload_empty: { type: 'GUIDE', payload: {} },
88
- // `text` wins when both are present — the body is what the agent persists.
89
- guide_frame_text_wins: { type: 'GUIDE', text: 'body', payload: { kind: 'thinking-delta', text: 'ignored' } },
90
- guide_frame_with_seq: {
91
- type: 'GUIDE',
92
- streamSeq: 77,
93
- payload: { kind: 'text-leading', text: 'Checking the docs…' },
94
- },
95
-
96
28
  // ASK — the guide-routing clarification card. `text` is the intro sentence
97
29
  // riding the same chunk; a card without a question or without usable options
98
30
  // is dropped (nothing the user could answer).
@@ -387,55 +319,6 @@ describe('decodeNatsChunk — golden corpus', () => {
387
319
  });
388
320
  });
389
321
 
390
- describe('guideEventForNats — the two kernels reconciled', () => {
391
- // The property that matters for maintenance: `leading-frames` is the ONE
392
- // place a hub frame kind is taught to the client, so an event kind that
393
- // reaches this adapter must cross over WITHOUT an edit here. These use event
394
- // types the frame table cannot produce today on purpose — they stand in for
395
- // whatever the hub ships next.
396
- it('passes an unfamiliar event through unchanged', () => {
397
- const ask: ChatStreamEvent = {
398
- type: 'ask',
399
- question: 'Which workspace?',
400
- options: [{ label: 'Acme' }],
401
- };
402
- expect(guideEventForNats(ask)).toEqual(ask);
403
- });
404
-
405
- it('stamps origin on the events that carry it, so the card routes to the hub', () => {
406
- expect(
407
- guideEventForNats({ type: 'approval-request', requestId: 'prop-1', approvalType: 'create_ticket' }),
408
- ).toMatchObject({ approvalType: 'create_ticket', origin: 'guide' });
409
- expect(guideEventForNats({ type: 'approval-resolved', requestId: 'prop-1', status: 'approved' })).toMatchObject({
410
- origin: 'guide',
411
- });
412
- });
413
-
414
- it('stops the events the agent owns for this dialog', () => {
415
- expect(guideEventForNats({ type: 'usage', stage: 'start', input_tokens: 10 })).toBeNull();
416
- expect(guideEventForNats({ type: 'status', phase: 'thinking' })).toBeNull();
417
- expect(
418
- guideEventForNats({
419
- type: 'token-usage',
420
- inputTokensSize: 1,
421
- outputTokensSize: 2,
422
- totalTokensSize: 3,
423
- contextSize: 4,
424
- }),
425
- ).toBeNull();
426
- expect(guideEventForNats({ type: 'dialog-closed' })).toBeNull();
427
- });
428
-
429
- it('keeps metadata only for the hub conversation id every confirm must quote back', () => {
430
- expect(guideEventForNats({ type: 'metadata', conversationId: 'conv-1', modelName: 'hub-model' })).toEqual({
431
- type: 'metadata',
432
- conversationId: 'conv-1',
433
- origin: 'guide',
434
- });
435
- expect(guideEventForNats({ type: 'metadata', modelName: 'hub-model' })).toBeNull();
436
- });
437
- });
438
-
439
322
  describe('decodeNatsChunk — seq envelope', () => {
440
323
  it('lifts a numeric JetStream `streamSeq` into `seq`, and omits it otherwise (snapshot)', () => {
441
324
  expect({
@@ -36,10 +36,9 @@
36
36
  import type { ChatStreamEvent } from './events';
37
37
  import type { UsageTelemetry } from './frames';
38
38
  import { FRAME_TERMINATOR, END_OF_LEADING, TRAILER_SENTINEL } from './frames';
39
- // The frame table is SHARED with the NATS decoder — guide answers re-stream the
40
- // hub's frames verbatim inside `GUIDE` chunks — so it lives in its own module.
41
- // `frameNum` comes from there too: the trailing usage frame's token counts must
42
- // pass the SAME typeof gate the leading `usage:start` frame's already do.
39
+ // The frame table lives in its own module so it is reusable outside this
40
+ // decoder. `frameNum` comes from there too: the trailing usage frame's token
41
+ // counts must pass the SAME typeof gate the leading `usage:start` frame's do.
43
42
  import { mapLeadingFrame, frameNum } from './leading-frames';
44
43
 
45
44
  export interface SseFrameDecoder {
@@ -54,18 +54,6 @@ export interface ThinkingDeltaEvent extends ChatStreamEventBase {
54
54
  text: string;
55
55
  }
56
56
 
57
- /** Guide-body delta (NATS `GUIDE` chunk) — the assistant's how-to /
58
- * documentation answer, rendered as a titled "OpenFrame Guide" card rather
59
- * than bare prose. Same APPEND-ONLY contract as `text-delta` /
60
- * `thinking-delta`: each event carries the next verbatim slice and consumers
61
- * coalesce into the trailing `guide` segment. NATS-only today — the SSE
62
- * frame grammar has no guide frame — but it lives in the shared union
63
- * because the reducer is transport-agnostic. */
64
- export interface GuideDeltaEvent extends ChatStreamEventBase {
65
- type: 'guide-delta';
66
- text: string;
67
- }
68
-
69
57
  /** Clarification card (NATS `ASK` chunk) — the assistant asking which reading
70
58
  * of an ambiguous question to answer. NOT a delta: the chunk carries the
71
59
  * finished card, so consumers push it whole instead of coalescing. `text` is
@@ -134,35 +122,6 @@ export interface ApprovalRequestEvent extends ChatStreamEventBase {
134
122
  fields?: ApprovalRequestField[];
135
123
  toolCalls?: ApprovalToolCall[];
136
124
  status?: 'pending';
137
- /** Set when the card came from a Product Guide frame — see {@link GuideOrigin}. */
138
- origin?: GuideOrigin;
139
- }
140
-
141
- /**
142
- * Marks an event whose payload is a Product Guide frame, whatever transport
143
- * carried it. It exists because ONE stream can now mix both worlds: the agent
144
- * re-streams the hub's frames into a NATS dialog, so a card typed the hub's way
145
- * (`approvalType` = the tool name, resolved through the hub's confirm route)
146
- * travels beside cards typed the agent's way (`approvalType` = an approval TIER
147
- * routed to human escalation).
148
- *
149
- * Consumers read it to keep the guide half behaving exactly as it does in the
150
- * hub's own chat — NOT to give it special treatment. Without it the NATS kernel
151
- * would have to guess from `approvalType`, and every tool the hub adds would
152
- * silently fall into the escalation path.
153
- */
154
- export type GuideOrigin = 'guide';
155
-
156
- /** The only value of {@link GuideOrigin}. Lives beside the type, and beside the
157
- * predicate below, because both decoders and every consumer that branches on
158
- * provenance must compare against the same token — a bare `'guide'` literal
159
- * typo silently disables the branch instead of failing to compile. */
160
- export const GUIDE_ORIGIN: GuideOrigin = 'guide';
161
-
162
- /** True for anything stamped as coming from the Product Guide — a stream event
163
- * or the `data` of a segment built from one. */
164
- export function isGuideOrigin(source: { origin?: GuideOrigin | string } | null | undefined): boolean {
165
- return source?.origin === GUIDE_ORIGIN;
166
125
  }
167
126
 
168
127
  /** An approval request was resolved (SSE `decision_resolved` frame /
@@ -178,8 +137,6 @@ export interface ApprovalResolvedEvent extends ChatStreamEventBase {
178
137
  receiptText?: string;
179
138
  result?: DecisionResolvedFrame['result'];
180
139
  willAutoContinue?: boolean;
181
- /** Set when the resolution came from a Product Guide frame — see {@link GuideOrigin}. */
182
- origin?: GuideOrigin;
183
140
  }
184
141
 
185
142
  /** The client is offered a handoff of this ticket to a human technician
@@ -281,13 +238,6 @@ export interface ChatMetadataEvent extends ChatStreamEventBase {
281
238
  routedModel?: string;
282
239
  routedThinkingBudget: number | null;
283
240
  };
284
- /**
285
- * Set when the metadata came from a Product Guide frame — see
286
- * {@link GuideOrigin}. Such an event carries ONLY `conversationId`: it exists
287
- * to record the hub's conversation id (every confirm-tool call must quote it
288
- * back), NOT to describe the dialog's model, which stays the agent's.
289
- */
290
- origin?: GuideOrigin;
291
241
  }
292
242
 
293
243
  /** SSE usage frames — raw wire keys (snake_case) preserved. */
@@ -402,7 +352,6 @@ export type ChatStreamEvent =
402
352
  | TurnEndEvent
403
353
  | TextDeltaEvent
404
354
  | ThinkingDeltaEvent
405
- | GuideDeltaEvent
406
355
  | AskEvent
407
356
  | StatusEvent
408
357
  | ToolExecutionEvent
@@ -2,15 +2,9 @@
2
2
  * Leading-frame → `ChatStreamEvent` table — the ONE mapping both transports read.
3
3
  *
4
4
  * The SSE decoder (`./decode.ts`) reaches it while walking the hub's byte
5
- * framing. The NATS decoder (`./nats-decoder.ts`) reaches it for `GUIDE` chunks
6
- * carrying a `payload`: the saas-ai-agent re-streams the Product Guide's hub
7
- * frames VERBATIM into the Mingo chunk stream (`GuideStreamingService`), so the
8
- * bytes a guide answer is made of are the same frames on both transports — only
9
- * the envelope differs. Decoding them twice would drift the moment the hub adds
10
- * a frame kind, which is why this table lives outside either decoder.
11
- *
12
- * The NATS side does NOT take every event this table can produce; see
13
- * `guideFrameEvent` in `./nats-decoder.ts` for which ones cross over and why.
5
+ * framing. It lives outside that decoder so a second transport can reuse the
6
+ * table rather than re-implement it and drift the moment the hub adds a frame
7
+ * kind.
14
8
  *
15
9
  * Server-safe: no React, no browser APIs.
16
10
  */
@@ -18,9 +18,8 @@
18
18
 
19
19
  import { MESSAGE_TYPE } from '../components/chat/types/message.types';
20
20
  import type { AskOptionData } from '../components/chat/types/message.types';
21
- import { ESCALATION_STATE, GUIDE_ORIGIN, escalationResolvedStatus } from './events';
21
+ import { ESCALATION_STATE, escalationResolvedStatus } from './events';
22
22
  import type { ApprovalToolCall, ChatStreamEvent } from './events';
23
- import { escapeThinkingTags, mapLeadingFrame } from './leading-frames';
24
23
 
25
24
  /** Minimal structural view of a NATS chunk (see `ChunkData` in
26
25
  * `src/components/chat/types/network.types.ts`). */
@@ -48,11 +47,6 @@ function numOr(value: unknown, fallback = 0): number {
48
47
  return typeof value === 'number' ? value : fallback;
49
48
  }
50
49
 
51
- // `GUIDE_ORIGIN` moved to `./events`, beside the `GuideOrigin` type it is the
52
- // only value of — the SSE half reads it too, so it never belonged to the NATS
53
- // decoder. Re-exported here because that is the import path consumers know.
54
- export { GUIDE_ORIGIN } from './events';
55
-
56
50
  /** Coerce the wire's `toolCalls[]` into the batch-approval shape, dropping
57
51
  * non-object entries and defaulting every field. */
58
52
  function normalizeToolCalls(raw: unknown): ApprovalToolCall[] {
@@ -83,94 +77,6 @@ export function normalizeAskOptions(raw: unknown): AskOptionData[] {
83
77
  .filter(option => option.label.length > 0);
84
78
  }
85
79
 
86
- /**
87
- * Events whose meaning belongs to the AGENT's dialog rather than to the guide
88
- * turn embedded inside it. The hub emits its own copies of these, and the Mingo
89
- * dialog already has authoritative ones: letting the hub's through would
90
- * double-count the dialog's tokens or fight the agent's own phase chunks.
91
- *
92
- * Note this is a DENYLIST, and that is the point. `./leading-frames` is the ONE
93
- * place a hub frame kind is taught to the client; whatever it learns to emit —
94
- * including kinds that do not exist yet — must reach a Mingo dialog without a
95
- * second edit here. An allowlist would silently swallow every card the hub adds
96
- * next, and the guide half would quietly lag the guide chat.
97
- */
98
- const AGENT_OWNED_EVENTS: ReadonlySet<ChatStreamEvent['type']> = new Set([
99
- 'usage',
100
- 'token-usage',
101
- 'status',
102
- 'compaction',
103
- 'participant',
104
- 'dialog-closed',
105
- ]);
106
-
107
- /** Event types that declare `origin` (see `GuideOrigin`). Data rather than a
108
- * branch per type, so a future hub card opts in by adding the field to its
109
- * interface and its name here — the pass-through below needs no edit. */
110
- const ORIGIN_BEARING_EVENTS: ReadonlySet<ChatStreamEvent['type']> = new Set(['approval-request', 'approval-resolved']);
111
-
112
- /**
113
- * Adapt ONE Product Guide event to the NATS kernel.
114
- *
115
- * The guide half of a Mingo dialog is the hub's own stream, so it is decoded by
116
- * the shared table in `./leading-frames` and then passed through here — the ONE
117
- * place that reconciles the two kernels. Both entry points use it: frames
118
- * re-streamed by the agent inside `GUIDE` chunks, and the SSE response of the
119
- * hub's confirm-tool route, which a host replays into the same dialog.
120
- *
121
- * Pass-through is the DEFAULT; only these rules alter an event, and each exists
122
- * because the two kernels genuinely disagree:
123
- *
124
- * - `text-delta` becomes `guide-delta`: the body of a guide turn belongs
125
- * inside the "OpenFrame Guide" card, and a `text` segment would strand part
126
- * of the same answer outside it.
127
- * - thinking is escaped HERE. The SSE kernel escapes `<` on the way in, the
128
- * NATS kernel does not, and the guide's thinking is full of XML-ish tokens
129
- * that would otherwise render as markup.
130
- * - `metadata` survives ONLY to carry `conversationId`, which the hub mints
131
- * and every confirm-tool call must quote back. Everything else on that
132
- * frame (the hub's model, routing) is dropped: `applyNats` rebuilds the
133
- * dialog's live model from a metadata event, so letting it through would
134
- * relabel a Mingo turn with the hub's model, and a routing frame — which
135
- * carries no model at all — would blank the badge mid-answer.
136
- * - dialog-level events stop here (`AGENT_OWNED_EVENTS`).
137
- *
138
- * Everything else crosses over as the hub typed it, gaining only `origin`.
139
- * `approvalType` in particular stays the TOOL NAME, exactly as in the hub's own
140
- * chat: the NATS kernel gates approvals on approval TIER and escalates the rest,
141
- * which is an agent-side concept a hub proposal has no tier for. Rewriting the
142
- * tool name into a fake tier to slip past that gate would make the guide half of
143
- * the stream diverge from the guide chat itself.
144
- */
145
- export function guideEventForNats(event: ChatStreamEvent): ChatStreamEvent | null {
146
- switch (event.type) {
147
- case 'text-delta':
148
- return { type: 'guide-delta', text: event.text };
149
- case 'thinking-delta':
150
- return { type: 'thinking-delta', text: escapeThinkingTags(event.text) };
151
- case 'metadata':
152
- return typeof event.conversationId === 'string' && event.conversationId
153
- ? { type: 'metadata', conversationId: event.conversationId, origin: GUIDE_ORIGIN }
154
- : null;
155
- default:
156
- if (AGENT_OWNED_EVENTS.has(event.type)) return null;
157
- return ORIGIN_BEARING_EVENTS.has(event.type) ? ({ ...event, origin: GUIDE_ORIGIN } as ChatStreamEvent) : event;
158
- }
159
- }
160
-
161
- /**
162
- * Decode one re-streamed Product Guide frame (a `GUIDE` chunk's `payload`) into
163
- * a NATS event. The frame grammar is decoded by the shared table, never by a
164
- * second copy here; the kernel reconciliation is `guideEventForNats`.
165
- */
166
- export function guideFrameEvent(frame: Record<string, unknown>): ChatStreamEvent | null {
167
- const events: ChatStreamEvent[] = [];
168
- // Every branch of the table pushes at most one event, so the first is the one.
169
- mapLeadingFrame(frame, events);
170
- const event = events[0];
171
- return event ? guideEventForNats(event) : null;
172
- }
173
-
174
80
  /**
175
81
  * Parse one raw NATS chunk into a normalized event. Returns `null` for
176
82
  * unknown/malformed chunks — an unrecognized `type`, a missing required
@@ -219,21 +125,6 @@ export function decodeNatsChunk(chunk: unknown): ChatStreamEvent | null {
219
125
  }
220
126
  return null;
221
127
 
222
- // Two shapes share this chunk type. `text` is the answer body the agent
223
- // streams (and persists). `payload` is a Product Guide frame re-streamed
224
- // verbatim from the hub — decoded through the shared frame table, with the
225
- // narrowing `guideFrameEvent` documents.
226
- case MESSAGE_TYPE.GUIDE: {
227
- if (typeof data.text === 'string') {
228
- return { type: 'guide-delta', text: data.text, ...seq };
229
- }
230
- const frame = data.payload;
231
- if (!frame || typeof frame !== 'object' || Array.isArray(frame)) return null;
232
- const event = guideFrameEvent(frame as Record<string, unknown>);
233
- if (!event) return null;
234
- return { ...event, ...seq };
235
- }
236
-
237
128
  // An ask card is only an ask card with something to pick: a question and at
238
129
  // least one option. Anything less is dropped rather than rendered as an
239
130
  // empty card the user can't answer — the backend already falls back to a
@@ -203,25 +203,6 @@ describe('a thinking turn has exactly ONE pending affordance', () => {
203
203
  expect(names()).toEqual(['Michael Assraf', 'Mingo']);
204
204
  });
205
205
 
206
- it('renders the assistant row when it carries ONLY a guide segment', () => {
207
- // A `guide` body is the whole visible answer for a how-to question. If
208
- // the empty-pending-turn predicate ever counted it as "nothing visible"
209
- // (e.g. by widening its text-only clause), a guide-only turn would render
210
- // NOTHING until the turn ended — the footer loader would be the sole
211
- // affordance while a fully-formed guide card sat in state.
212
- const withGuide: Message[] = [
213
- pending[0],
214
- {
215
- id: 'a1',
216
- role: 'assistant',
217
- name: 'Mingo',
218
- content: [{ type: 'guide', text: '## Enroll a device' }],
219
- },
220
- ];
221
- render(<ChatMessageList dialogId="d" messages={withGuide} isTyping />);
222
- expect(names()).toEqual(['Michael Assraf', 'Mingo']);
223
- });
224
-
225
206
  it('renders a completed empty-ish assistant row when NOT typing', () => {
226
207
  render(<ChatMessageList dialogId="d" messages={pending} />);
227
208
  expect(names()).toEqual(['Michael Assraf', 'Mingo']);
@@ -16,7 +16,6 @@ import { ContextCompactionDisplay } from './context-compaction-display';
16
16
  import { BlockCard, type BlockCardProps } from './entity-cards/block-card';
17
17
  import { ErrorMessageDisplay } from './error-message-display';
18
18
  import { EscalationOfferMessage } from './escalation-offer-message';
19
- import { GuideDisplay } from './guide-display';
20
19
  import { remarkCardLinks } from './remark-card-links';
21
20
  import { remarkMentionChips } from './remark-mention-chips';
22
21
  import { remarkStripCitations } from './remark-strip-citations';
@@ -226,12 +225,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
226
225
  // duplicates so two siblings never collide on the same React key.
227
226
  const emittedBlockKeys = new Set<string>();
228
227
  segments.forEach((segment, segIdx) => {
229
- // Both markdown-bearing segment types take part: a `guide` body is
230
- // authored by the same LLM and carries the same `[card://]` markers,
231
- // so skipping it here left its markers with no `inlineByKey` entry and
232
- // no hoisted card — the `<a card://>` override silently degraded them
233
- // to a bare title / id.
234
- if (segment.type !== 'text' && segment.type !== 'guide') return;
228
+ if (segment.type !== 'text') return;
235
229
  const text = segment.text;
236
230
  const parts: SegmentPart[] = [];
237
231
  let cursor = 0;
@@ -608,12 +602,6 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
608
602
  {renderSegmentBody(index, segment.text, segmentIsStreaming)}
609
603
  </div>
610
604
  );
611
- } else if (segment.type === 'guide') {
612
- return (
613
- <GuideDisplay key={index}>
614
- {renderSegmentBody(index, segment.text, segmentIsStreaming)}
615
- </GuideDisplay>
616
- );
617
605
  } else if (segment.type === 'ask') {
618
606
  // Only the run's head draws — the tail segments are pages of
619
607
  // the card already rendered above them.
@@ -10,7 +10,6 @@ export * from './ai-assistant-info';
10
10
  export * from './context-compaction-display';
11
11
  export * from './expand-chevron';
12
12
  export * from './thinking-display';
13
- export * from './guide-display';
14
13
  export * from './ask-display';
15
14
  export * from './error-message-display';
16
15
  export * from './chat-container';
@@ -886,96 +886,6 @@ describe('createChatStreamReducer — mergeApprovalStatuses precedence', () => {
886
886
  });
887
887
  });
888
888
 
889
- // ─── GUIDE segments (#1583 threaded through the unified reader) ─────────────
890
- //
891
- // A `GUIDE` chunk is an APPEND-ONLY body stream like TEXT/THINKING, but it
892
- // renders as a titled "OpenFrame Guide" card. It must therefore coalesce the
893
- // same way (extend the trailing `guide` segment, never push a new one per
894
- // chunk) and satisfy the same reducer invariants — seq idempotency,
895
- // referential stability, and post-MESSAGE_END append mode.
896
-
897
- describe('createChatStreamReducer — guide segments', () => {
898
- it('coalesces consecutive guide deltas into ONE trailing guide segment', () => {
899
- const r = createChatStreamReducer({ transport: 'nats' });
900
- r.apply({ type: 'turn-start', seq: 1 });
901
- r.apply({ type: 'guide-delta', text: '## Enroll a device\n', seq: 2 });
902
- r.apply({ type: 'guide-delta', text: '1. Open Settings', seq: 3 });
903
- const last = r.state.messages[r.state.messages.length - 1];
904
- expect(last.segments).toEqual([{ type: 'guide', text: '## Enroll a device\n1. Open Settings' }]);
905
- });
906
-
907
- it('starts a NEW guide segment when a non-guide segment interrupts', () => {
908
- const r = createChatStreamReducer({ transport: 'nats' });
909
- r.apply({ type: 'turn-start', seq: 1 });
910
- r.apply({ type: 'guide-delta', text: 'step one', seq: 2 });
911
- r.apply({ type: 'text-delta', text: 'aside', seq: 3 });
912
- r.apply({ type: 'guide-delta', text: 'step two', seq: 4 });
913
- const last = r.state.messages[r.state.messages.length - 1];
914
- expect(last.segments).toEqual([
915
- { type: 'guide', text: 'step one' },
916
- { type: 'text', text: 'aside' },
917
- { type: 'guide', text: 'step two' },
918
- ]);
919
- });
920
-
921
- it('drops a redelivered guide delta via the shared seq gate (no double text)', () => {
922
- const r = createChatStreamReducer({ transport: 'nats' });
923
- r.apply({ type: 'turn-start', seq: 1 });
924
- r.apply({ type: 'guide-delta', text: 'a', seq: 2 });
925
- r.apply({ type: 'guide-delta', text: 'b', seq: 3 });
926
- r.apply({ type: 'guide-delta', text: 'b', seq: 3 });
927
- r.apply({ type: 'guide-delta', text: 'a', seq: 2 });
928
- const last = r.state.messages[r.state.messages.length - 1];
929
- expect(last.segments).toEqual([{ type: 'guide', text: 'ab' }]);
930
- });
931
-
932
- it('reclones ONLY the touched message — earlier rows keep identity', () => {
933
- const r = createChatStreamReducer({ transport: 'nats' });
934
- r.apply({ type: 'participant', kind: 'message-request', text: 'how do I enroll?', seq: 1 });
935
- r.apply({ type: 'turn-start', seq: 2 });
936
- r.apply({ type: 'guide-delta', text: 'first', seq: 3 });
937
- const userRow = r.state.messages[0];
938
- r.apply({ type: 'guide-delta', text: ' more', seq: 4 });
939
- expect(r.state.messages[0]).toBe(userRow);
940
- });
941
-
942
- it('post-MESSAGE_END continuation emits a DELTA guide segment with append:true', () => {
943
- const effects: Array<{ name: string; args: unknown[] }> = [];
944
- const r = createChatStreamReducer({
945
- transport: 'nats',
946
- onEffect: e => effects.push(e),
947
- });
948
- r.apply({ type: 'turn-start', seq: 1 });
949
- r.apply({ type: 'guide-delta', text: 'in-stream ', seq: 2 });
950
- r.apply({ type: 'turn-end', seq: 3 });
951
- r.apply({ type: 'guide-delta', text: 'post-end', seq: 4 });
952
-
953
- const appendEmits = effects.filter(
954
- e => e.name === 'onSegmentsUpdate' && (e.args[1] as { append?: boolean } | undefined)?.append === true,
955
- );
956
- expect(appendEmits).toHaveLength(1);
957
- expect(appendEmits[0].args[0]).toEqual([{ type: 'guide', text: 'post-end' }]);
958
- // …and the bubble carries ONE coalesced guide segment, not two.
959
- const last = r.state.messages[r.state.messages.length - 1];
960
- expect(last.segments).toEqual([{ type: 'guide', text: 'in-stream post-end' }]);
961
- });
962
- });
963
-
964
- /**
965
- * `setMessages` is a RAW setter and must not be used to hydrate history.
966
- *
967
- * It assigns the thread and nothing else — in particular it does not mark the
968
- * reducer resumed, which is what makes a later chunk with no preceding
969
- * `turn-start` take the APPEND branch. Hydrating with it leaves the reducer
970
- * looking cold, so the first such chunk goes down the cumulative path and
971
- * REPLACES the restored bubble's segments. When that bubble holds a pending
972
- * approval card, the control the agent is blocked on disappears with it and
973
- * there is no way back.
974
- *
975
- * `initializeWithState` derives `resumed` from the restored thread, which is
976
- * why every hydration path must go through it (the SSE adapter always did; the
977
- * NATS adapter used `setMessages` and hit exactly this).
978
- */
979
889
  describe('createChatStreamReducer — history hydration must declare resumed', () => {
980
890
  const hydrated = () => [
981
891
  {