@flamingo-stack/openframe-frontend-core 0.0.579 → 0.0.580-1958.5358.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.
- package/dist/chat-protocol/decode.d.ts.map +1 -1
- package/dist/chat-protocol/events.d.ts +1 -47
- package/dist/chat-protocol/events.d.ts.map +1 -1
- package/dist/chat-protocol/index.cjs +0 -51
- package/dist/chat-protocol/index.cjs.map +1 -1
- package/dist/chat-protocol/index.js +1 -48
- package/dist/chat-protocol/index.js.map +1 -1
- package/dist/chat-protocol/leading-frames.d.ts +3 -9
- package/dist/chat-protocol/leading-frames.d.ts.map +1 -1
- package/dist/chat-protocol/nats-decoder.d.ts +0 -41
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
- package/dist/{chunk-OGZAFM6M.cjs → chunk-26NL6WZU.cjs} +5 -5
- package/dist/{chunk-OGZAFM6M.cjs.map → chunk-26NL6WZU.cjs.map} +1 -1
- package/dist/{chunk-QCP2KV7P.js → chunk-4SYELUXP.js} +3 -3
- package/dist/{chunk-4HGRXFQP.cjs → chunk-6PLACYFA.cjs} +3 -3
- package/dist/{chunk-4HGRXFQP.cjs.map → chunk-6PLACYFA.cjs.map} +1 -1
- package/dist/{chunk-FEVLXRTC.cjs → chunk-7LGVUXWE.cjs} +344 -282
- package/dist/chunk-7LGVUXWE.cjs.map +1 -0
- package/dist/{chunk-AKPLEWMH.js → chunk-A4BZ2CP4.js} +2 -2
- package/dist/{chunk-HHJVIHVR.cjs → chunk-COSV5FC2.cjs} +69 -69
- package/dist/{chunk-HHJVIHVR.cjs.map → chunk-COSV5FC2.cjs.map} +1 -1
- package/dist/{chunk-4WSKSNXL.js → chunk-CX62MVCV.js} +6 -6
- package/dist/{chunk-O2FTVZOD.js → chunk-DZAPMQ6U.js} +3 -3
- package/dist/{chunk-YW4WBNYV.cjs → chunk-EXUKW6GT.cjs} +24 -24
- package/dist/{chunk-YW4WBNYV.cjs.map → chunk-EXUKW6GT.cjs.map} +1 -1
- package/dist/{chunk-PWOYEUIQ.js → chunk-FN4PUTHZ.js} +5 -5
- package/dist/{chunk-L2CF2QZW.js → chunk-JDP5BGVO.js} +5 -5
- package/dist/{chunk-5Y6TPFW7.js → chunk-JFVT6XRN.js} +5 -5
- package/dist/{chunk-BCJQ7CJE.cjs → chunk-JHLZTU7R.cjs} +9 -9
- package/dist/{chunk-BCJQ7CJE.cjs.map → chunk-JHLZTU7R.cjs.map} +1 -1
- package/dist/{chunk-D44S2QLI.js → chunk-OSIIXSYG.js} +3 -3
- package/dist/{chunk-DB3TV4CW.js → chunk-PGWXVNYS.js} +3 -3
- package/dist/{chunk-H54EPP3P.cjs → chunk-PP3LCLWI.cjs} +4 -4
- package/dist/{chunk-H54EPP3P.cjs.map → chunk-PP3LCLWI.cjs.map} +1 -1
- package/dist/{chunk-M5GWCMYE.cjs → chunk-QIDD3LTS.cjs} +32 -32
- package/dist/{chunk-M5GWCMYE.cjs.map → chunk-QIDD3LTS.cjs.map} +1 -1
- package/dist/{chunk-3AX3ZEY3.js → chunk-QZSNH7XU.js} +1317 -1480
- package/dist/chunk-QZSNH7XU.js.map +1 -0
- package/dist/{chunk-37XJYOAY.cjs → chunk-RF65N4QA.cjs} +10 -10
- package/dist/{chunk-37XJYOAY.cjs.map → chunk-RF65N4QA.cjs.map} +1 -1
- package/dist/{chunk-36TJYCF3.cjs → chunk-S5BS2CZA.cjs} +21 -21
- package/dist/{chunk-36TJYCF3.cjs.map → chunk-S5BS2CZA.cjs.map} +1 -1
- package/dist/{chunk-QGJYWLF4.js → chunk-SN6SL4HR.js} +2 -2
- package/dist/{chunk-4FJSN2XW.cjs → chunk-TDM5FQZG.cjs} +10 -10
- package/dist/{chunk-4FJSN2XW.cjs.map → chunk-TDM5FQZG.cjs.map} +1 -1
- package/dist/{chunk-HGA74X35.js → chunk-TXNAZKWW.js} +3 -3
- package/dist/{chunk-IFYQ7DHV.cjs → chunk-UNVW4BVF.cjs} +43 -43
- package/dist/{chunk-IFYQ7DHV.cjs.map → chunk-UNVW4BVF.cjs.map} +1 -1
- package/dist/{chunk-CP5EYMNL.js → chunk-V5RWTIRS.js} +202 -140
- package/dist/{chunk-CP5EYMNL.js.map → chunk-V5RWTIRS.js.map} +1 -1
- package/dist/{chunk-RKGOVTXZ.cjs → chunk-VYQNM75R.cjs} +167 -330
- package/dist/chunk-VYQNM75R.cjs.map +1 -0
- package/dist/{chunk-PMENIZXI.js → chunk-WEUWTEQL.js} +4 -4
- package/dist/{chunk-H4BPHGU5.js → chunk-WW435OOF.js} +2 -2
- package/dist/{chunk-4LZ7B5H5.cjs → chunk-YB75SLEH.cjs} +3 -3
- package/dist/{chunk-4LZ7B5H5.cjs.map → chunk-YB75SLEH.cjs.map} +1 -1
- package/dist/{chunk-A6C5HOD7.cjs → chunk-YLYU6DDT.cjs} +15 -15
- package/dist/{chunk-A6C5HOD7.cjs.map → chunk-YLYU6DDT.cjs.map} +1 -1
- package/dist/{chunk-BW4WDUSI.js → chunk-ZNCYSM6R.js} +2 -2
- package/dist/{chunk-NPVDKOGD.cjs → chunk-ZQ2RBAXO.cjs} +22 -22
- package/dist/{chunk-NPVDKOGD.cjs.map → chunk-ZQ2RBAXO.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/chat-message-enhanced.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 +1 -9
- package/dist/components/chat/stream/chat-stream-reducer.d.ts +0 -9
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
- package/dist/components/chat/stream/delta-batcher.d.ts +3 -3
- package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -1
- package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +0 -6
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/chat/types/message.types.d.ts +2 -30
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/network.types.d.ts +1 -3
- package/dist/components/chat/types/network.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +1 -4
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/utils/approval-display.d.ts +8 -36
- package/dist/components/chat/utils/approval-display.d.ts.map +1 -1
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +1 -1
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.d.ts +4 -16
- package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/chat/utils/resolve-fetched-card-href.d.ts +1 -1
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/features/push-button-selector.d.ts +29 -1
- package/dist/components/features/push-button-selector.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +121 -129
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +13 -21
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +21 -21
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/index.cjs +3 -11
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +2 -10
- package/dist/index.cjs +7 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -14
- package/dist/index.js.map +1 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +0 -80
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +1 -118
- package/src/chat-protocol/decode.ts +3 -4
- package/src/chat-protocol/events.ts +0 -51
- package/src/chat-protocol/leading-frames.ts +3 -9
- package/src/chat-protocol/nats-decoder.ts +1 -110
- package/src/components/chat/__tests__/chat-pending-turn.test.tsx +0 -19
- package/src/components/chat/chat-message-enhanced.tsx +1 -13
- package/src/components/chat/index.ts +0 -1
- package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +0 -90
- package/src/components/chat/stream/chat-dialog-store.ts +1 -1
- package/src/components/chat/stream/chat-stream-reducer.ts +11 -49
- package/src/components/chat/stream/delta-batcher.ts +4 -9
- package/src/components/chat/stream/message-mutations.ts +0 -2
- package/src/components/chat/types/component.types.ts +0 -9
- package/src/components/chat/types/message.types.ts +0 -32
- package/src/components/chat/types/network.types.ts +1 -3
- package/src/components/chat/types/processing.types.ts +1 -4
- package/src/components/chat/utils/__tests__/process-historical-messages-approvals.test.ts +0 -93
- package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +1 -78
- package/src/components/chat/utils/approval-display.ts +8 -53
- package/src/components/chat/utils/extract-incomplete-message-state.ts +0 -1
- package/src/components/chat/utils/index.ts +1 -6
- package/src/components/chat/utils/message-segment-accumulator.ts +3 -37
- package/src/components/chat/utils/process-historical-messages.ts +7 -44
- package/src/components/chat/utils/resolve-fetched-card-href.ts +1 -1
- package/src/components/features/.push-button-selector.md +1 -1
- package/src/components/features/push-button-selector.tsx +130 -6
- package/src/stories/ChatMessageEnhanced.stories.tsx +3 -7
- package/dist/chunk-3AX3ZEY3.js.map +0 -1
- package/dist/chunk-FEVLXRTC.cjs.map +0 -1
- package/dist/chunk-RKGOVTXZ.cjs.map +0 -1
- package/dist/components/chat/guide-display.d.ts +0 -14
- package/dist/components/chat/guide-display.d.ts.map +0 -1
- package/src/components/chat/guide-display.tsx +0 -42
- /package/dist/{chunk-QCP2KV7P.js.map → chunk-4SYELUXP.js.map} +0 -0
- /package/dist/{chunk-AKPLEWMH.js.map → chunk-A4BZ2CP4.js.map} +0 -0
- /package/dist/{chunk-4WSKSNXL.js.map → chunk-CX62MVCV.js.map} +0 -0
- /package/dist/{chunk-O2FTVZOD.js.map → chunk-DZAPMQ6U.js.map} +0 -0
- /package/dist/{chunk-PWOYEUIQ.js.map → chunk-FN4PUTHZ.js.map} +0 -0
- /package/dist/{chunk-L2CF2QZW.js.map → chunk-JDP5BGVO.js.map} +0 -0
- /package/dist/{chunk-5Y6TPFW7.js.map → chunk-JFVT6XRN.js.map} +0 -0
- /package/dist/{chunk-D44S2QLI.js.map → chunk-OSIIXSYG.js.map} +0 -0
- /package/dist/{chunk-DB3TV4CW.js.map → chunk-PGWXVNYS.js.map} +0 -0
- /package/dist/{chunk-QGJYWLF4.js.map → chunk-SN6SL4HR.js.map} +0 -0
- /package/dist/{chunk-HGA74X35.js.map → chunk-TXNAZKWW.js.map} +0 -0
- /package/dist/{chunk-PMENIZXI.js.map → chunk-WEUWTEQL.js.map} +0 -0
- /package/dist/{chunk-H4BPHGU5.js.map → chunk-WW435OOF.js.map} +0 -0
- /package/dist/{chunk-BW4WDUSI.js.map → chunk-ZNCYSM6R.js.map} +0 -0
|
@@ -207,7 +207,7 @@ const EMPTY_STATE: ChatReducerState = Object.freeze({
|
|
|
207
207
|
dialogTokenUsage: null,
|
|
208
208
|
liveModel: null,
|
|
209
209
|
approvalStatuses: Object.freeze({}) as ChatReducerState['approvalStatuses'],
|
|
210
|
-
})
|
|
210
|
+
});
|
|
211
211
|
|
|
212
212
|
/** Shared frozen empty list for a key with no send in flight. */
|
|
213
213
|
const EMPTY_PENDING_ECHOES: readonly PendingEcho[] = Object.freeze([]);
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
*/
|
|
52
52
|
|
|
53
53
|
import { escapeThinkingTags } from '../../../chat-protocol/decode';
|
|
54
|
-
import { errorDetailsMessage
|
|
54
|
+
import { errorDetailsMessage } from '../../../chat-protocol/events';
|
|
55
55
|
import {
|
|
56
56
|
CHAT_OWNER_ADMIN,
|
|
57
57
|
type ChatStreamEvent,
|
|
@@ -77,7 +77,7 @@ import type {
|
|
|
77
77
|
UnifiedChatMessage,
|
|
78
78
|
UnifiedUsageBreakdown,
|
|
79
79
|
} from '../types/unified-chat-state.types';
|
|
80
|
-
import { approvalDisplaysInline
|
|
80
|
+
import { approvalDisplaysInline } from '../utils/approval-display';
|
|
81
81
|
import {
|
|
82
82
|
type MessageSegmentAccumulator,
|
|
83
83
|
createMessageSegmentAccumulator,
|
|
@@ -161,15 +161,6 @@ export interface ChatReducerState {
|
|
|
161
161
|
contextWindowMaxTokens: number | null;
|
|
162
162
|
} | null;
|
|
163
163
|
approvalStatuses: Record<string, ChatApprovalStatus>;
|
|
164
|
-
/**
|
|
165
|
-
* Hub conversation id for the Product Guide half of this dialog, learned from
|
|
166
|
-
* a guide metadata frame. The hub mints it and requires it back on every
|
|
167
|
-
* confirm-tool call, so a host resolving a guide approval card reads it from
|
|
168
|
-
* here. Null until a guide turn has streamed — an approval that arrives from
|
|
169
|
-
* history (after a reload) has no live frame to learn it from, which is why
|
|
170
|
-
* the agent should also expose it per dialog.
|
|
171
|
-
*/
|
|
172
|
-
guideConversationId: string | null;
|
|
173
164
|
}
|
|
174
165
|
|
|
175
166
|
/** Escalated-approval bookkeeping entry (mirrors the legacy processor). */
|
|
@@ -539,7 +530,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
539
530
|
let streamingPhase: StreamingPhase = 'idle';
|
|
540
531
|
let dialogTokenUsage: DialogTokenUsage | null = null;
|
|
541
532
|
let liveModel: ChatReducerState['liveModel'] = null;
|
|
542
|
-
let guideConversationId: string | null = null;
|
|
543
533
|
let approvalStatuses: Record<string, ChatApprovalStatus> = {
|
|
544
534
|
...(options.approvalStatuses ?? {}),
|
|
545
535
|
};
|
|
@@ -701,7 +691,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
701
691
|
dialogTokenUsage,
|
|
702
692
|
liveModel,
|
|
703
693
|
approvalStatuses,
|
|
704
|
-
guideConversationId,
|
|
705
694
|
};
|
|
706
695
|
}
|
|
707
696
|
return stateCache;
|
|
@@ -996,17 +985,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
996
985
|
}
|
|
997
986
|
|
|
998
987
|
case 'metadata': {
|
|
999
|
-
// A guide metadata event carries ONLY the hub's conversation id (the
|
|
1000
|
-
// decoder strips the rest): record it and stop. Falling through would
|
|
1001
|
-
// rebuild `liveModel` from an event with no model in it and blank the
|
|
1002
|
-
// dialog's model badge mid-answer.
|
|
1003
|
-
if (isGuideOrigin(event)) {
|
|
1004
|
-
if (typeof event.conversationId === 'string' && event.conversationId) {
|
|
1005
|
-
guideConversationId = event.conversationId;
|
|
1006
|
-
invalidate();
|
|
1007
|
-
}
|
|
1008
|
-
break;
|
|
1009
|
-
}
|
|
1010
988
|
// Legacy `parseChunkToAction` action shape, reconstructed for the
|
|
1011
989
|
// callback contract.
|
|
1012
990
|
emit('onMetadata', {
|
|
@@ -1025,21 +1003,13 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
1025
1003
|
break;
|
|
1026
1004
|
}
|
|
1027
1005
|
|
|
1028
|
-
//
|
|
1029
|
-
//
|
|
1030
|
-
//
|
|
1031
|
-
// but rides the identical path so a guide body streams, coalesces and
|
|
1032
|
-
// continues post-MESSAGE_END exactly like answer text.
|
|
1006
|
+
// Both APPEND-ONLY body streams share one shape: accumulate the verbatim
|
|
1007
|
+
// slice into the trailing segment of the matching kind (the accumulator
|
|
1008
|
+
// coalesces), then emit.
|
|
1033
1009
|
case 'text-delta':
|
|
1034
|
-
case 'thinking-delta':
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
const segments =
|
|
1038
|
-
kind === 'text'
|
|
1039
|
-
? accumulator.appendText(event.text)
|
|
1040
|
-
: kind === 'thinking'
|
|
1041
|
-
? accumulator.appendThinking(event.text)
|
|
1042
|
-
: accumulator.appendGuide(event.text);
|
|
1010
|
+
case 'thinking-delta': {
|
|
1011
|
+
const kind = event.type === 'text-delta' ? 'text' : 'thinking';
|
|
1012
|
+
const segments = kind === 'text' ? accumulator.appendText(event.text) : accumulator.appendThinking(event.text);
|
|
1043
1013
|
// Append-mode only for *true* post-stream continuation (after a
|
|
1044
1014
|
// MESSAGE_END we actually saw). Cold-start chunks (no prior
|
|
1045
1015
|
// MESSAGE_START) emit cumulative segments so the consumer can spawn
|
|
@@ -1115,10 +1085,8 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
1115
1085
|
const approvalType = event.approvalType ?? 'USER';
|
|
1116
1086
|
const toolCalls = event.toolCalls;
|
|
1117
1087
|
// Where this card renders is ONE rule, shared with the SSE kernel and
|
|
1118
|
-
// the history replay (`approval-display`)
|
|
1119
|
-
|
|
1120
|
-
const guideOrigin = guideApprovalOrigin(event);
|
|
1121
|
-
const displayInline = (type: string) => approvalDisplaysInline(event, type, displayApprovalTypes);
|
|
1088
|
+
// the history replay (`approval-display`).
|
|
1089
|
+
const displayInline = (type: string) => approvalDisplaysInline(type, displayApprovalTypes);
|
|
1122
1090
|
|
|
1123
1091
|
if (toolCalls && toolCalls.length > 0) {
|
|
1124
1092
|
// ── Batch form ──
|
|
@@ -1150,7 +1118,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
1150
1118
|
status,
|
|
1151
1119
|
undefined,
|
|
1152
1120
|
undefined,
|
|
1153
|
-
guideOrigin,
|
|
1154
1121
|
);
|
|
1155
1122
|
applyAccumulated(before, segments);
|
|
1156
1123
|
break;
|
|
@@ -1170,7 +1137,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
1170
1137
|
approvalType,
|
|
1171
1138
|
status,
|
|
1172
1139
|
undefined,
|
|
1173
|
-
guideOrigin,
|
|
1174
1140
|
);
|
|
1175
1141
|
}
|
|
1176
1142
|
applyAccumulated(before, segments);
|
|
@@ -1189,10 +1155,8 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
1189
1155
|
explanation,
|
|
1190
1156
|
approvalType,
|
|
1191
1157
|
status,
|
|
1192
|
-
// SSE-shaped cards
|
|
1193
|
-
// their body as structured rows rather than prose.
|
|
1158
|
+
// SSE-shaped cards carry their body as structured rows rather than prose.
|
|
1194
1159
|
event.fields,
|
|
1195
|
-
guideOrigin,
|
|
1196
1160
|
);
|
|
1197
1161
|
applyAccumulated(before, segments);
|
|
1198
1162
|
} else {
|
|
@@ -1704,7 +1668,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
1704
1668
|
streamingPhase = 'idle';
|
|
1705
1669
|
dialogTokenUsage = null;
|
|
1706
1670
|
liveModel = null;
|
|
1707
|
-
guideConversationId = null;
|
|
1708
1671
|
approvalStatuses = {};
|
|
1709
1672
|
accumulator.reset();
|
|
1710
1673
|
pendingEscalated.clear();
|
|
@@ -1752,7 +1715,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
|
|
|
1752
1715
|
streamingPhase = 'idle';
|
|
1753
1716
|
dialogTokenUsage = null;
|
|
1754
1717
|
liveModel = null;
|
|
1755
|
-
guideConversationId = null;
|
|
1756
1718
|
accumulator.reset();
|
|
1757
1719
|
pendingEscalated.clear();
|
|
1758
1720
|
isInStream = false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* createDeltaBatcher — framework-free, append-only batching of the
|
|
3
|
-
* `text-delta` / `thinking-delta`
|
|
3
|
+
* `text-delta` / `thinking-delta` events.
|
|
4
4
|
*
|
|
5
5
|
* WHY THIS IS SHARED (and not a hook-private detail): every host that drives a
|
|
6
6
|
* `ChatStreamReducer` from a raw transport needs the SAME batching, and a
|
|
@@ -23,19 +23,14 @@
|
|
|
23
23
|
* deltas never land on the wrong dialog/side.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import type {
|
|
27
|
-
ChatStreamEvent,
|
|
28
|
-
GuideDeltaEvent,
|
|
29
|
-
TextDeltaEvent,
|
|
30
|
-
ThinkingDeltaEvent,
|
|
31
|
-
} from '../../../chat-protocol/events';
|
|
26
|
+
import type { ChatStreamEvent, TextDeltaEvent, ThinkingDeltaEvent } from '../../../chat-protocol/events';
|
|
32
27
|
|
|
33
28
|
export const DELTA_FLUSH_FALLBACK_MS = 50;
|
|
34
29
|
|
|
35
|
-
export type DeltaEvent = TextDeltaEvent | ThinkingDeltaEvent
|
|
30
|
+
export type DeltaEvent = TextDeltaEvent | ThinkingDeltaEvent;
|
|
36
31
|
|
|
37
32
|
export function isDeltaEvent(event: ChatStreamEvent): event is DeltaEvent {
|
|
38
|
-
return event.type === 'text-delta' || event.type === 'thinking-delta'
|
|
33
|
+
return event.type === 'text-delta' || event.type === 'thinking-delta';
|
|
39
34
|
}
|
|
40
35
|
|
|
41
36
|
export interface CreateDeltaBatcherOptions<K> {
|
|
@@ -126,8 +126,6 @@ export function appendToTrailingAssistant(
|
|
|
126
126
|
merged[merged.length - 1] = { type: 'text', text: tail.text + seg.text };
|
|
127
127
|
} else if (seg.type === 'thinking' && tail?.type === 'thinking') {
|
|
128
128
|
merged[merged.length - 1] = { type: 'thinking', text: tail.text + seg.text };
|
|
129
|
-
} else if (seg.type === 'guide' && tail?.type === 'guide') {
|
|
130
|
-
merged[merged.length - 1] = { type: 'guide', text: tail.text + seg.text };
|
|
131
129
|
} else if (key) {
|
|
132
130
|
// Block deltas must be IDEMPOTENT: an emit can be seen twice (live plus
|
|
133
131
|
// the JetStream catch-up replay over hydrated history), so upsert on the
|
|
@@ -578,15 +578,6 @@ export interface ThinkingDisplayProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
578
578
|
isStreaming?: boolean;
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
-
// ========== Guide Display Props ==========
|
|
582
|
-
|
|
583
|
-
export interface GuideDisplayProps extends HTMLAttributes<HTMLDivElement> {
|
|
584
|
-
/** Rendered guide body. `ChatMessageEnhanced` passes the SAME markdown
|
|
585
|
-
* output a text segment gets, so `[card://]` cards and mention chips
|
|
586
|
-
* behave identically inside a guide. */
|
|
587
|
-
children?: ReactNode;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
581
|
// ========== Ask Display Props ==========
|
|
591
582
|
|
|
592
583
|
export interface AskDisplayProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'> {
|
|
@@ -10,7 +10,6 @@ import type { AssistantType, AuthorType, ChatApprovalStatus, MessageOwner } from
|
|
|
10
10
|
export const MESSAGE_TYPE = {
|
|
11
11
|
TEXT: 'TEXT',
|
|
12
12
|
THINKING: 'THINKING',
|
|
13
|
-
GUIDE: 'GUIDE',
|
|
14
13
|
ASK: 'ASK',
|
|
15
14
|
EXECUTING_TOOL: 'EXECUTING_TOOL',
|
|
16
15
|
EXECUTED_TOOL: 'EXECUTED_TOOL',
|
|
@@ -114,12 +113,6 @@ export interface ApprovalRequestData {
|
|
|
114
113
|
requestId?: string;
|
|
115
114
|
approvalRequestId?: string;
|
|
116
115
|
approvalType?: string;
|
|
117
|
-
/** `'guide'` when the card came from a Product Guide frame (see `GuideOrigin`
|
|
118
|
-
* in `chat-protocol/events`). Carried onto the segment because hosts route
|
|
119
|
-
* and filter approvals by where they came from: mingo lifts its OWN pending
|
|
120
|
-
* cards into a sticky footer, and a guide card must stay inline in the turn
|
|
121
|
-
* the way it does in the hub's chat. */
|
|
122
|
-
origin?: 'guide';
|
|
123
116
|
}
|
|
124
117
|
|
|
125
118
|
export interface ApprovalResultData {
|
|
@@ -227,11 +220,6 @@ export interface ApprovalBatchData {
|
|
|
227
220
|
* batch itself is approved.
|
|
228
221
|
*/
|
|
229
222
|
executions?: Record<string, ApprovalBatchExecutionState>;
|
|
230
|
-
/** `'guide'` when the batch came from a Product Guide `approval_batch` frame.
|
|
231
|
-
* Same contract and the same reasons as `ApprovalRequestData.origin` — the
|
|
232
|
-
* hub groups multiple proposals into one card, and that card resolves
|
|
233
|
-
* through the hub exactly like a single one. */
|
|
234
|
-
origin?: 'guide';
|
|
235
223
|
}
|
|
236
224
|
|
|
237
225
|
/** Approve/reject handler stamped onto approval segments. MAY resolve a
|
|
@@ -254,15 +242,6 @@ export type ThinkingSegment = {
|
|
|
254
242
|
text: string;
|
|
255
243
|
};
|
|
256
244
|
|
|
257
|
-
/** Guide answer body — the assistant's how-to/documentation reply, rendered as
|
|
258
|
-
* a titled "OpenFrame Guide" card instead of a bare paragraph. `text` is
|
|
259
|
-
* markdown, streamed in fragments like a `text` segment and coalesced by the
|
|
260
|
-
* accumulator. */
|
|
261
|
-
export type GuideSegment = {
|
|
262
|
-
type: 'guide';
|
|
263
|
-
text: string;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
245
|
/** One reading the assistant offers in an `ask` card. `label` is BOTH the row's
|
|
267
246
|
* headline and the exact text sent back when the row is picked — the backend's
|
|
268
247
|
* guide classifier resolves the user's next message against the labels it
|
|
@@ -380,7 +359,6 @@ export type ContextCompactionSegment = {
|
|
|
380
359
|
export type MessageSegment =
|
|
381
360
|
| TextSegment
|
|
382
361
|
| ThinkingSegment
|
|
383
|
-
| GuideSegment
|
|
384
362
|
| AskSegment
|
|
385
363
|
| ToolExecutionSegment
|
|
386
364
|
| ApprovalRequestSegment
|
|
@@ -413,15 +391,6 @@ export interface ThinkingMessageData extends MessageDataBase {
|
|
|
413
391
|
text?: string;
|
|
414
392
|
}
|
|
415
393
|
|
|
416
|
-
export interface GuideMessageData extends MessageDataBase {
|
|
417
|
-
type: 'GUIDE';
|
|
418
|
-
text?: string;
|
|
419
|
-
/** Persisted Product Guide frame (GraphQL `GuideData.payload`, a JSON scalar)
|
|
420
|
-
* for rows that carry a card instead of answer text — replayed through the
|
|
421
|
-
* live path's `guideFrameEvent`. */
|
|
422
|
-
payload?: Record<string, unknown>;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
394
|
/** Persisted `ASK` row (GraphQL `AskData`). `text` is the intro sentence, which
|
|
426
395
|
* history replays as a text segment ahead of the card — same split the live
|
|
427
396
|
* `ASK` chunk carries. */
|
|
@@ -563,7 +532,6 @@ export interface ContextCompactionEndMessageData extends MessageDataBase {
|
|
|
563
532
|
export type MessageData =
|
|
564
533
|
| TextMessageData
|
|
565
534
|
| ThinkingMessageData
|
|
566
|
-
| GuideMessageData
|
|
567
535
|
| AskMessageData
|
|
568
536
|
| ExecutingToolMessageData
|
|
569
537
|
| ExecutedToolMessageData
|
|
@@ -63,9 +63,7 @@ export interface ChunkData {
|
|
|
63
63
|
/** Batch-approval rows. Coerced by `normalizeToolCalls` in the NATS decoder
|
|
64
64
|
* — the wire shape is per-backend, so entries stay opaque here. */
|
|
65
65
|
toolCalls?: unknown[];
|
|
66
|
-
/**
|
|
67
|
-
* the hub, instead of the answer text `text` carries. Decoded by
|
|
68
|
-
* `decodeNatsChunk` through the shared leading-frame table. */
|
|
66
|
+
/** Opaque per-chunk payload some backends attach beside `text`. */
|
|
69
67
|
payload?: Record<string, unknown>;
|
|
70
68
|
modelName?: string;
|
|
71
69
|
providerName?: string;
|
|
@@ -18,12 +18,9 @@ export interface PendingApproval {
|
|
|
18
18
|
command: string;
|
|
19
19
|
explanation?: string;
|
|
20
20
|
approvalType: string;
|
|
21
|
-
/** Structured label/value rows — a
|
|
21
|
+
/** Structured label/value rows — a card can be almost entirely
|
|
22
22
|
* these, so a path that forgets them renders a bare title. */
|
|
23
23
|
fields?: ApprovalRequestField[];
|
|
24
|
-
/** Where the card came from; `'guide'` keeps it inline and routes its
|
|
25
|
-
* buttons to the hub (see `ApprovalRequestData.origin`). */
|
|
26
|
-
origin?: 'guide';
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export interface AccumulatorState {
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { describe, it, expect } from 'vitest';
|
|
12
|
-
import { decodeNatsChunk } from '../../../../chat-protocol/nats-decoder';
|
|
13
12
|
import { createChatStreamReducer } from '../../stream/chat-stream-reducer';
|
|
14
13
|
import type { HistoricalMessage, MessageSegment } from '../../types';
|
|
15
14
|
import { processHistoricalMessagesWithErrors } from '../process-historical-messages';
|
|
@@ -151,95 +150,3 @@ describe('displayApprovalTypes — history vs realtime default divergence', () =
|
|
|
151
150
|
expect(live.escalated).toHaveLength(0);
|
|
152
151
|
});
|
|
153
152
|
});
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* A Product Guide card must survive a reload UNCHANGED.
|
|
157
|
-
*
|
|
158
|
-
* It arrives as a hub frame the agent re-streams into the dialog, so it is
|
|
159
|
-
* typed by TOOL and resolves through the hub's confirm route — the tier gate
|
|
160
|
-
* these tests are about simply does not apply to it. When the rule lived only
|
|
161
|
-
* in the realtime kernel, the same card rendered inline while streaming and
|
|
162
|
-
* then, on the next page load, was tier-gated: lifted out of its turn into the
|
|
163
|
-
* consumer's sticky pending-approvals envelope, stripped of the `fields` that
|
|
164
|
-
* are its entire body, and stripped of the `origin` that routes its buttons —
|
|
165
|
-
* so clicking Approve hit the agent's endpoint and 404'd.
|
|
166
|
-
*/
|
|
167
|
-
describe('Product Guide approvals — history and realtime agree', () => {
|
|
168
|
-
const guideProposalMessage = (id: string): HistoricalMessage => ({
|
|
169
|
-
id,
|
|
170
|
-
createdAt: '2026-07-17T12:00:00Z',
|
|
171
|
-
owner: { type: 'ASSISTANT' },
|
|
172
|
-
messageData: [
|
|
173
|
-
{
|
|
174
|
-
type: 'GUIDE',
|
|
175
|
-
payload: {
|
|
176
|
-
kind: 'approval_request',
|
|
177
|
-
proposalId: 'prop-1',
|
|
178
|
-
toolName: 'create_ticket',
|
|
179
|
-
title: 'Open a support ticket',
|
|
180
|
-
fields: [{ label: 'Subject', value: 'Agent will not enroll' }],
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
],
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// The narrowest possible tier list: it excludes the guide's tool name, so a
|
|
187
|
-
// card that still renders proves the gate was bypassed, not merely satisfied.
|
|
188
|
-
const NARROW_TIERS = ['CLIENT'];
|
|
189
|
-
|
|
190
|
-
it('renders inline on reload despite a tier list that excludes it', () => {
|
|
191
|
-
const { messages, escalatedApprovals } = processHistoricalMessagesWithErrors([guideProposalMessage('m1')], {
|
|
192
|
-
displayApprovalTypes: NARROW_TIERS,
|
|
193
|
-
});
|
|
194
|
-
const rendered = messages.flatMap(m => approvalSegments(m.content));
|
|
195
|
-
expect(rendered).toHaveLength(1);
|
|
196
|
-
expect(escalatedApprovals.size).toBe(0);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('keeps the card in its own turn, not in the sticky pending-approvals envelope', () => {
|
|
200
|
-
const { messages } = processHistoricalMessagesWithErrors([guideProposalMessage('m1')], {
|
|
201
|
-
displayApprovalTypes: NARROW_TIERS,
|
|
202
|
-
});
|
|
203
|
-
expect(messages.some(m => String(m.id).startsWith('pending-approvals-'))).toBe(false);
|
|
204
|
-
const turn = messages.find(m => m.id === 'm1');
|
|
205
|
-
expect(approvalSegments(turn?.content)).toHaveLength(1);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('carries the same fields and origin as the live stream', () => {
|
|
209
|
-
/** The single card's `data`, narrowed off the segment union. */
|
|
210
|
-
const cardData = (segments: MessageSegment[]) => {
|
|
211
|
-
const card = segments.find(s => s.type === 'approval_request');
|
|
212
|
-
return card?.type === 'approval_request' ? card.data : undefined;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const { messages } = processHistoricalMessagesWithErrors([guideProposalMessage('m1')], {
|
|
216
|
-
displayApprovalTypes: NARROW_TIERS,
|
|
217
|
-
});
|
|
218
|
-
const replayed = cardData(messages.flatMap(m => approvalSegments(m.content)));
|
|
219
|
-
|
|
220
|
-
const live = createChatStreamReducer({ transport: 'nats', displayApprovalTypes: NARROW_TIERS });
|
|
221
|
-
live.apply({ type: 'turn-start' });
|
|
222
|
-
const guideChunk = decodeNatsChunk({
|
|
223
|
-
type: 'GUIDE',
|
|
224
|
-
payload: {
|
|
225
|
-
kind: 'approval_request',
|
|
226
|
-
proposalId: 'prop-1',
|
|
227
|
-
toolName: 'create_ticket',
|
|
228
|
-
title: 'Open a support ticket',
|
|
229
|
-
fields: [{ label: 'Subject', value: 'Agent will not enroll' }],
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
// A decoder that declines this frame is the failure the test exists to
|
|
233
|
-
// catch — say so rather than applying `undefined` to the reducer.
|
|
234
|
-
if (!guideChunk) throw new Error('decodeNatsChunk did not decode the GUIDE approval_request frame');
|
|
235
|
-
live.apply(guideChunk);
|
|
236
|
-
const streamed = cardData(live.state.messages.flatMap(m => approvalSegments(m.segments)));
|
|
237
|
-
|
|
238
|
-
// Same card either way — down to the rows that ARE its body, and the marker
|
|
239
|
-
// that decides which backend its buttons talk to.
|
|
240
|
-
expect(replayed).toEqual(streamed);
|
|
241
|
-
expect(replayed?.origin).toBe('guide');
|
|
242
|
-
expect(replayed?.approvalType).toBe('create_ticket');
|
|
243
|
-
expect(replayed?.fields).toEqual([{ label: 'Subject', value: 'Agent will not enroll' }]);
|
|
244
|
-
});
|
|
245
|
-
});
|
|
@@ -318,86 +318,9 @@ describe('extractErrorMessages / processHistoricalMessagesWithErrors — golden'
|
|
|
318
318
|
});
|
|
319
319
|
});
|
|
320
320
|
|
|
321
|
-
// ─── GUIDE rows (#1583) ────────────────────────────────────────────────────
|
|
322
|
-
//
|
|
323
|
-
// The realtime path decodes a `GUIDE` chunk into a `guide-delta` event; the
|
|
324
|
-
// history path must decode the persisted `GUIDE` row into the SAME event so a
|
|
325
|
-
// reloaded dialog renders the guide card instead of silently dropping it.
|
|
326
|
-
// Explicit assertions rather than a corpus snapshot — the point is the exact
|
|
327
|
-
// coalescing shape, not the whole-dialog record.
|
|
328
|
-
|
|
329
|
-
describe('processHistoricalMessages — GUIDE rows', () => {
|
|
330
|
-
const guideDialog: HistoricalMessage[] = [
|
|
331
|
-
{
|
|
332
|
-
id: 'u1',
|
|
333
|
-
chatType: 'CLIENT_CHAT',
|
|
334
|
-
createdAt: '2026-07-19T09:00:00Z',
|
|
335
|
-
owner: { type: 'CLIENT', userId: 'user-42' },
|
|
336
|
-
messageData: [{ type: 'TEXT', text: 'how do I enroll a device?' }],
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
id: 'a1',
|
|
340
|
-
chatType: 'CLIENT_CHAT',
|
|
341
|
-
createdAt: '2026-07-19T09:00:05Z',
|
|
342
|
-
owner: { type: 'ASSISTANT', model: 'claude-sonnet-x' },
|
|
343
|
-
messageData: [
|
|
344
|
-
{ type: 'GUIDE', text: '## Enroll a device\n' },
|
|
345
|
-
{ type: 'GUIDE', text: '1. Open Settings' },
|
|
346
|
-
],
|
|
347
|
-
},
|
|
348
|
-
];
|
|
349
|
-
|
|
350
|
-
it('replays consecutive GUIDE rows into ONE coalesced guide segment', () => {
|
|
351
|
-
const { messages } = processHistoricalMessages(guideDialog, {
|
|
352
|
-
assistantName: 'Mingo AI',
|
|
353
|
-
assistantType: 'mingo',
|
|
354
|
-
});
|
|
355
|
-
expect(messages).toHaveLength(2);
|
|
356
|
-
expect(messages[1]).toMatchObject({
|
|
357
|
-
role: 'assistant',
|
|
358
|
-
content: [{ type: 'guide', text: '## Enroll a device\n1. Open Settings' }],
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it('keeps GUIDE and TEXT as separate segments in wire order', () => {
|
|
363
|
-
const mixed: HistoricalMessage[] = [
|
|
364
|
-
{
|
|
365
|
-
id: 'a1',
|
|
366
|
-
chatType: 'CLIENT_CHAT',
|
|
367
|
-
createdAt: '2026-07-19T09:00:05Z',
|
|
368
|
-
owner: { type: 'ASSISTANT' },
|
|
369
|
-
messageData: [
|
|
370
|
-
{ type: 'TEXT', text: 'Here is the walkthrough.' },
|
|
371
|
-
{ type: 'GUIDE', text: '## Steps' },
|
|
372
|
-
{ type: 'TEXT', text: 'Anything else?' },
|
|
373
|
-
],
|
|
374
|
-
},
|
|
375
|
-
];
|
|
376
|
-
const { messages } = processHistoricalMessages(mixed);
|
|
377
|
-
expect(messages[0].content).toEqual([
|
|
378
|
-
{ type: 'text', text: 'Here is the walkthrough.' },
|
|
379
|
-
{ type: 'guide', text: '## Steps' },
|
|
380
|
-
{ type: 'text', text: 'Anything else?' },
|
|
381
|
-
]);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it('a GUIDE row with no text decodes to nothing (no empty card)', () => {
|
|
385
|
-
const empty: HistoricalMessage[] = [
|
|
386
|
-
{
|
|
387
|
-
id: 'a1',
|
|
388
|
-
chatType: 'CLIENT_CHAT',
|
|
389
|
-
createdAt: '2026-07-19T09:00:05Z',
|
|
390
|
-
owner: { type: 'ASSISTANT' },
|
|
391
|
-
messageData: [{ type: 'GUIDE' }],
|
|
392
|
-
},
|
|
393
|
-
];
|
|
394
|
-
expect(processHistoricalMessages(empty).messages).toEqual([]);
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
|
|
398
321
|
// ─── ASK rows ──────────────────────────────────────────────────────────────
|
|
399
322
|
//
|
|
400
|
-
//
|
|
323
|
+
// History must replay a persisted `ASK` row into
|
|
401
324
|
// the SAME shape the live `ASK` chunk produces — the intro sentence as answer
|
|
402
325
|
// text, then the card — or a reloaded dialog loses the clarification the user
|
|
403
326
|
// was about to answer.
|
|
@@ -4,65 +4,20 @@
|
|
|
4
4
|
*
|
|
5
5
|
* There are three such paths and they must agree: the NATS kernel (live chunks),
|
|
6
6
|
* the SSE kernel, and the history replay that rebuilds a dialog after a reload.
|
|
7
|
-
* When the rule lived only in the live kernel, a
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* wrong backend once it moved.
|
|
11
|
-
*
|
|
12
|
-
* The rule itself: a card is gated on approval TIER (`displayApprovalTypes`,
|
|
13
|
-
* the agent's own privilege model) UNLESS it came from the Product Guide, whose
|
|
14
|
-
* proposals are typed by TOOL and are resolved through the hub's confirm route.
|
|
15
|
-
* A hub proposal has no tier to gate on, so tier-gating one means escalating it
|
|
16
|
-
* to a human who has no way to act on it.
|
|
7
|
+
* When the rule lived only in the live kernel, a card could render inline while
|
|
8
|
+
* streaming and then move into the escalation treatment on the next page load —
|
|
9
|
+
* same card, two behaviors.
|
|
17
10
|
*/
|
|
18
11
|
|
|
19
|
-
import type { GuideOrigin } from '../../../chat-protocol/events';
|
|
20
|
-
import { GUIDE_ORIGIN, isGuideOrigin } from '../../../chat-protocol/events';
|
|
21
|
-
import type { MessageSegment } from '../types/message.types';
|
|
22
|
-
|
|
23
|
-
/** Anything carrying the optional `origin` marker: a stream event, or the
|
|
24
|
-
* `data` of a rendered segment. */
|
|
25
|
-
interface OriginBearing {
|
|
26
|
-
origin?: GuideOrigin | string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** True when this approval was minted by the Product Guide (the hub) rather
|
|
30
|
-
* than by the agent's own tool-approval tiering. */
|
|
31
|
-
export function isGuideApproval(source: OriginBearing | null | undefined): boolean {
|
|
32
|
-
return isGuideOrigin(source);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** The marker to stamp onto a built segment, or undefined for agent approvals.
|
|
36
|
-
* Keeps `origin` out of segment data unless it means something. */
|
|
37
|
-
export function guideApprovalOrigin(source: OriginBearing | null | undefined): GuideOrigin | undefined {
|
|
38
|
-
return isGuideApproval(source) ? GUIDE_ORIGIN : undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
12
|
/**
|
|
42
13
|
* Does this approval render inline in its turn (true), or take the consumer's
|
|
43
14
|
* escalation treatment (false)?
|
|
44
15
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
16
|
+
* A card is gated on approval TIER (`displayApprovalTypes`, the agent's own
|
|
17
|
+
* privilege model). `undefined` means "no tier gate configured" and every
|
|
18
|
+
* approval displays — the pre-existing history-replay default, preserved here
|
|
19
|
+
* so both kernels can share one predicate.
|
|
48
20
|
*/
|
|
49
|
-
export function approvalDisplaysInline(
|
|
50
|
-
source: OriginBearing | null | undefined,
|
|
51
|
-
approvalType: string,
|
|
52
|
-
displayApprovalTypes?: readonly string[],
|
|
53
|
-
): boolean {
|
|
54
|
-
if (isGuideApproval(source)) return true;
|
|
21
|
+
export function approvalDisplaysInline(approvalType: string, displayApprovalTypes?: readonly string[]): boolean {
|
|
55
22
|
return !displayApprovalTypes || displayApprovalTypes.includes(approvalType);
|
|
56
23
|
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* True for a rendered segment that holds a Product Guide card (single or batch).
|
|
60
|
-
*
|
|
61
|
-
* Consumers use it to keep such a card in the message flow, where the hub's own
|
|
62
|
-
* chat puts it, instead of lifting it into their sticky pending-approvals
|
|
63
|
-
* footer — and to route its buttons to the hub rather than to the agent.
|
|
64
|
-
*/
|
|
65
|
-
export function isGuideApprovalSegment(segment: MessageSegment): boolean {
|
|
66
|
-
if (segment.type !== 'approval_request' && segment.type !== 'approval_batch') return false;
|
|
67
|
-
return isGuideApproval(segment.data);
|
|
68
|
-
}
|
|
@@ -16,12 +16,7 @@ export {
|
|
|
16
16
|
|
|
17
17
|
// Where an approval card renders — one rule for both live kernels, the history
|
|
18
18
|
// replay, and hosts that sort cards between their own footer and the flow.
|
|
19
|
-
export {
|
|
20
|
-
isGuideApproval,
|
|
21
|
-
guideApprovalOrigin,
|
|
22
|
-
approvalDisplaysInline,
|
|
23
|
-
isGuideApprovalSegment,
|
|
24
|
-
} from './approval-display';
|
|
19
|
+
export { approvalDisplaysInline } from './approval-display';
|
|
25
20
|
|
|
26
21
|
// Historical message processing
|
|
27
22
|
export {
|