@flamingo-stack/openframe-frontend-core 0.0.532-snapshot.20260811155109 → 0.0.532
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 +9 -1
- package/dist/chat-protocol/decode.d.ts.map +1 -1
- package/dist/chat-protocol/events.d.ts +0 -35
- package/dist/chat-protocol/events.d.ts.map +1 -1
- package/dist/chat-protocol/index.cjs +3 -72
- package/dist/chat-protocol/index.cjs.map +1 -1
- package/dist/chat-protocol/index.d.ts +0 -1
- package/dist/chat-protocol/index.d.ts.map +1 -1
- package/dist/chat-protocol/index.js +4 -67
- package/dist/chat-protocol/index.js.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-OLOKQQAP.cjs → chunk-2MBS3YEY.cjs} +5 -5
- package/dist/{chunk-OLOKQQAP.cjs.map → chunk-2MBS3YEY.cjs.map} +1 -1
- package/dist/{chunk-DFQLUK75.cjs → chunk-2QP67D2L.cjs} +3 -3
- package/dist/{chunk-DFQLUK75.cjs.map → chunk-2QP67D2L.cjs.map} +1 -1
- package/dist/{chunk-6SMLYZLH.js → chunk-3G7FDE5E.js} +4 -4
- package/dist/{chunk-J4BE4FIR.cjs → chunk-4GL2XJ5C.cjs} +4 -4
- package/dist/{chunk-J4BE4FIR.cjs.map → chunk-4GL2XJ5C.cjs.map} +1 -1
- package/dist/{chunk-LE3BJ5H2.js → chunk-5W3RCZIK.js} +2 -2
- package/dist/{chunk-MVGL5NNJ.js → chunk-65NR333L.js} +2 -2
- package/dist/{chunk-6W3ULZ4X.js → chunk-7SGBF2I2.js} +6 -6
- package/dist/{chunk-6YXN5RHW.js → chunk-ALXLWF75.js} +2 -2
- package/dist/{chunk-5U67WTAS.js → chunk-AZBS4PZG.js} +2 -2
- package/dist/{chunk-4QKXNWH5.js → chunk-ECR37JHH.js} +2 -2
- package/dist/{chunk-ENPGPZIB.cjs → chunk-ETF2QXXU.cjs} +61 -61
- package/dist/{chunk-ENPGPZIB.cjs.map → chunk-ETF2QXXU.cjs.map} +1 -1
- package/dist/{chunk-UPHN2VRZ.cjs → chunk-GVLA2BPW.cjs} +117 -259
- package/dist/chunk-GVLA2BPW.cjs.map +1 -0
- package/dist/{chunk-4ZRHVOEB.cjs → chunk-GWFC3NE5.cjs} +31 -31
- package/dist/{chunk-4ZRHVOEB.cjs.map → chunk-GWFC3NE5.cjs.map} +1 -1
- package/dist/{chunk-GNOK5J34.cjs → chunk-HJDCDOJM.cjs} +37 -37
- package/dist/{chunk-GNOK5J34.cjs.map → chunk-HJDCDOJM.cjs.map} +1 -1
- package/dist/{chunk-JDW2IL7A.cjs → chunk-HXHA3DCI.cjs} +11 -11
- package/dist/{chunk-JDW2IL7A.cjs.map → chunk-HXHA3DCI.cjs.map} +1 -1
- package/dist/{chunk-XN4JKIJY.js → chunk-IZOGFZ7N.js} +2 -2
- package/dist/{chunk-Z3NOI7X7.cjs → chunk-JZ5XDZYD.cjs} +26 -26
- package/dist/{chunk-Z3NOI7X7.cjs.map → chunk-JZ5XDZYD.cjs.map} +1 -1
- package/dist/{chunk-YLB6XXO6.js → chunk-KYQZBQYV.js} +2 -2
- package/dist/{chunk-RKITTO5V.cjs → chunk-L6EKEYF7.cjs} +14 -14
- package/dist/{chunk-RKITTO5V.cjs.map → chunk-L6EKEYF7.cjs.map} +1 -1
- package/dist/{chunk-NAV4FRNL.cjs → chunk-LYWSHNK4.cjs} +26 -26
- package/dist/{chunk-NAV4FRNL.cjs.map → chunk-LYWSHNK4.cjs.map} +1 -1
- package/dist/{chunk-46FKXLNE.js → chunk-OAEJWP7H.js} +2 -2
- package/dist/{chunk-2MYDGLJS.cjs → chunk-QEMI34VA.cjs} +87 -87
- package/dist/{chunk-2MYDGLJS.cjs.map → chunk-QEMI34VA.cjs.map} +1 -1
- package/dist/{chunk-QZ2NAPLF.cjs → chunk-QYNLVIGQ.cjs} +9 -9
- package/dist/{chunk-QZ2NAPLF.cjs.map → chunk-QYNLVIGQ.cjs.map} +1 -1
- package/dist/{chunk-KMB66NYX.cjs → chunk-TMZS63G2.cjs} +7 -7
- package/dist/{chunk-KMB66NYX.cjs.map → chunk-TMZS63G2.cjs.map} +1 -1
- package/dist/{chunk-2TUOBFPK.js → chunk-TQ7EPKKO.js} +2 -2
- package/dist/{chunk-U555TMPC.js → chunk-UP4RA54U.js} +5 -5
- package/dist/{chunk-LIUARTOZ.js → chunk-WINT757E.js} +64 -206
- package/dist/chunk-WINT757E.js.map +1 -0
- package/dist/{chunk-2XGHEJZK.js → chunk-XBYEPSNZ.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/embeddable-chat.d.ts +0 -13
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- 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.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/types/message.types.d.ts +0 -15
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/network.types.d.ts +0 -4
- package/dist/components/chat/types/network.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +1 -7
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +0 -1
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.d.ts +3 -25
- 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/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/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +22 -22
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +132 -140
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -19
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +34 -34
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +2 -10
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +1 -9
- package/dist/index.cjs +2 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -9
- 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 -70
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +1 -108
- package/src/chat-protocol/decode.ts +153 -7
- package/src/chat-protocol/events.ts +0 -40
- package/src/chat-protocol/index.ts +0 -3
- package/src/chat-protocol/nats-decoder.ts +3 -111
- package/src/components/chat/embeddable-chat.tsx +0 -18
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +16 -7
- package/src/components/chat/stream/chat-stream-reducer.ts +3 -49
- package/src/components/chat/types/message.types.ts +0 -15
- package/src/components/chat/types/network.types.ts +0 -4
- package/src/components/chat/types/processing.types.ts +1 -12
- package/src/components/chat/utils/__tests__/process-historical-messages-approvals.test.ts +0 -92
- package/src/components/chat/utils/index.ts +0 -9
- package/src/components/chat/utils/message-segment-accumulator.ts +32 -62
- package/src/components/chat/utils/process-historical-messages.ts +6 -40
- package/dist/chat-protocol/confirm-tool.d.ts +0 -50
- package/dist/chat-protocol/confirm-tool.d.ts.map +0 -1
- package/dist/chat-protocol/leading-frames.d.ts +0 -38
- package/dist/chat-protocol/leading-frames.d.ts.map +0 -1
- package/dist/chunk-LIUARTOZ.js.map +0 -1
- package/dist/chunk-UPHN2VRZ.cjs.map +0 -1
- package/dist/components/chat/utils/approval-display.d.ts +0 -49
- package/dist/components/chat/utils/approval-display.d.ts.map +0 -1
- package/src/chat-protocol/confirm-tool.ts +0 -67
- package/src/chat-protocol/leading-frames.ts +0 -175
- package/src/components/chat/utils/approval-display.ts +0 -70
- /package/dist/{chunk-6SMLYZLH.js.map → chunk-3G7FDE5E.js.map} +0 -0
- /package/dist/{chunk-LE3BJ5H2.js.map → chunk-5W3RCZIK.js.map} +0 -0
- /package/dist/{chunk-MVGL5NNJ.js.map → chunk-65NR333L.js.map} +0 -0
- /package/dist/{chunk-6W3ULZ4X.js.map → chunk-7SGBF2I2.js.map} +0 -0
- /package/dist/{chunk-6YXN5RHW.js.map → chunk-ALXLWF75.js.map} +0 -0
- /package/dist/{chunk-5U67WTAS.js.map → chunk-AZBS4PZG.js.map} +0 -0
- /package/dist/{chunk-4QKXNWH5.js.map → chunk-ECR37JHH.js.map} +0 -0
- /package/dist/{chunk-XN4JKIJY.js.map → chunk-IZOGFZ7N.js.map} +0 -0
- /package/dist/{chunk-YLB6XXO6.js.map → chunk-KYQZBQYV.js.map} +0 -0
- /package/dist/{chunk-46FKXLNE.js.map → chunk-OAEJWP7H.js.map} +0 -0
- /package/dist/{chunk-2TUOBFPK.js.map → chunk-TQ7EPKKO.js.map} +0 -0
- /package/dist/{chunk-U555TMPC.js.map → chunk-UP4RA54U.js.map} +0 -0
- /package/dist/{chunk-2XGHEJZK.js.map → chunk-XBYEPSNZ.js.map} +0 -0
|
@@ -59,7 +59,6 @@ import type { MessageSegment } from '../types/message.types'
|
|
|
59
59
|
import { useSlashCommandRegistry, type SlashCommandSummary } from './use-slash-commands'
|
|
60
60
|
import { chatAuthedFetch } from '../utils/chat-authed-fetch'
|
|
61
61
|
import type { ScrollAnchor } from '../utils/scroll-anchor'
|
|
62
|
-
import { buildConfirmToolBody, readServerErrorMessage } from '../../../chat-protocol/confirm-tool'
|
|
63
62
|
import { createSseFrameDecoder } from '../../../chat-protocol/decode'
|
|
64
63
|
import type { ChatStreamEvent } from '../../../chat-protocol/events'
|
|
65
64
|
import {
|
|
@@ -438,11 +437,13 @@ export function useSseChatAdapter(
|
|
|
438
437
|
? endpointsRef.current.approvalToolUrl
|
|
439
438
|
: endpointsRef.current.chatStreamUrl
|
|
440
439
|
const requestBody = approvalAction
|
|
441
|
-
?
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
440
|
+
? {
|
|
441
|
+
proposal_id: approvalAction.proposalId,
|
|
442
|
+
action: approvalAction.action,
|
|
443
|
+
// Always present here — an approval can only happen inside an
|
|
444
|
+
// established conversation (the proposal turn captured the id).
|
|
445
|
+
conversationId,
|
|
446
|
+
}
|
|
446
447
|
: {
|
|
447
448
|
messages: [{ role: 'user', content: text }],
|
|
448
449
|
...(commandOverride ? { commandOverride } : {}),
|
|
@@ -475,7 +476,15 @@ export function useSseChatAdapter(
|
|
|
475
476
|
// `{error, code}`) — a bare "Chat request failed: 409" told
|
|
476
477
|
// the user nothing when e.g. a batch approval expired; the
|
|
477
478
|
// server ships real copy ("This approval expired — ...").
|
|
478
|
-
|
|
479
|
+
let serverMessage: string | null = null
|
|
480
|
+
try {
|
|
481
|
+
const errBody = (await response.json()) as { error?: unknown }
|
|
482
|
+
if (typeof errBody?.error === 'string' && errBody.error.length > 0) {
|
|
483
|
+
serverMessage = errBody.error
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
/* non-JSON error body — fall through to the generic copy */
|
|
487
|
+
}
|
|
479
488
|
throw new Error(serverMessage ?? `Chat request failed: ${response.status}`)
|
|
480
489
|
}
|
|
481
490
|
const reader = response.body?.getReader()
|
|
@@ -58,8 +58,6 @@ import {
|
|
|
58
58
|
import { getCommandText } from '../utils/tool-call-helpers'
|
|
59
59
|
import { parseScrollAnchor, type ScrollAnchor } from '../utils/scroll-anchor'
|
|
60
60
|
import { escapeThinkingTags } from '../../../chat-protocol/decode'
|
|
61
|
-
import { isGuideOrigin } from '../../../chat-protocol/events'
|
|
62
|
-
import { approvalDisplaysInline, guideApprovalOrigin } from '../utils/approval-display'
|
|
63
61
|
import { buildChatRefKey } from '../types/chat.types'
|
|
64
62
|
import type {
|
|
65
63
|
ApprovalBatchSegment,
|
|
@@ -164,15 +162,6 @@ export interface ChatReducerState {
|
|
|
164
162
|
contextWindowMaxTokens: number | null
|
|
165
163
|
} | null
|
|
166
164
|
approvalStatuses: Record<string, ChatApprovalStatus>
|
|
167
|
-
/**
|
|
168
|
-
* Hub conversation id for the Product Guide half of this dialog, learned from
|
|
169
|
-
* a guide metadata frame. The hub mints it and requires it back on every
|
|
170
|
-
* confirm-tool call, so a host resolving a guide approval card reads it from
|
|
171
|
-
* here. Null until a guide turn has streamed — an approval that arrives from
|
|
172
|
-
* history (after a reload) has no live frame to learn it from, which is why
|
|
173
|
-
* the agent should also expose it per dialog.
|
|
174
|
-
*/
|
|
175
|
-
guideConversationId: string | null
|
|
176
165
|
}
|
|
177
166
|
|
|
178
167
|
/** Escalated-approval bookkeeping entry (mirrors the legacy processor). */
|
|
@@ -531,7 +520,6 @@ export function createChatStreamReducer(
|
|
|
531
520
|
let streamingPhase: StreamingPhase = 'idle'
|
|
532
521
|
let dialogTokenUsage: DialogTokenUsage | null = null
|
|
533
522
|
let liveModel: ChatReducerState['liveModel'] = null
|
|
534
|
-
let guideConversationId: string | null = null
|
|
535
523
|
let approvalStatuses: Record<string, ChatApprovalStatus> = {
|
|
536
524
|
...(options.approvalStatuses ?? {}),
|
|
537
525
|
}
|
|
@@ -694,7 +682,6 @@ export function createChatStreamReducer(
|
|
|
694
682
|
dialogTokenUsage,
|
|
695
683
|
liveModel,
|
|
696
684
|
approvalStatuses,
|
|
697
|
-
guideConversationId,
|
|
698
685
|
}
|
|
699
686
|
}
|
|
700
687
|
return stateCache
|
|
@@ -1002,17 +989,6 @@ export function createChatStreamReducer(
|
|
|
1002
989
|
}
|
|
1003
990
|
|
|
1004
991
|
case 'metadata': {
|
|
1005
|
-
// A guide metadata event carries ONLY the hub's conversation id (the
|
|
1006
|
-
// decoder strips the rest): record it and stop. Falling through would
|
|
1007
|
-
// rebuild `liveModel` from an event with no model in it and blank the
|
|
1008
|
-
// dialog's model badge mid-answer.
|
|
1009
|
-
if (isGuideOrigin(event)) {
|
|
1010
|
-
if (typeof event.conversationId === 'string' && event.conversationId) {
|
|
1011
|
-
guideConversationId = event.conversationId
|
|
1012
|
-
invalidate()
|
|
1013
|
-
}
|
|
1014
|
-
break
|
|
1015
|
-
}
|
|
1016
992
|
// Legacy `parseChunkToAction` action shape, reconstructed for the
|
|
1017
993
|
// callback contract.
|
|
1018
994
|
emit('onMetadata', {
|
|
@@ -1125,17 +1101,11 @@ export function createChatStreamReducer(
|
|
|
1125
1101
|
const requestId = event.requestId
|
|
1126
1102
|
const approvalType = event.approvalType ?? 'USER'
|
|
1127
1103
|
const toolCalls = event.toolCalls
|
|
1128
|
-
// Where this card renders is ONE rule, shared with the SSE kernel and
|
|
1129
|
-
// the history replay (`approval-display`) — a Product Guide proposal
|
|
1130
|
-
// that renders inline live must not move on the next page load.
|
|
1131
|
-
const guideOrigin = guideApprovalOrigin(event)
|
|
1132
|
-
const displayInline = (type: string) =>
|
|
1133
|
-
approvalDisplaysInline(event, type, displayApprovalTypes)
|
|
1134
1104
|
|
|
1135
1105
|
if (toolCalls && toolCalls.length > 0) {
|
|
1136
1106
|
// ── Batch form ──
|
|
1137
1107
|
const status = (approvalStatuses[requestId] || 'pending') as ChatApprovalStatus
|
|
1138
|
-
if (!
|
|
1108
|
+
if (!displayApprovalTypes.includes(approvalType)) {
|
|
1139
1109
|
// Escalated: keep batch context locally for replay on result;
|
|
1140
1110
|
// surface a summary command via the legacy escalation callback.
|
|
1141
1111
|
const required = toolCalls.find((c) => c.requiresApproval) ?? toolCalls[0]
|
|
@@ -1157,15 +1127,7 @@ export function createChatStreamReducer(
|
|
|
1157
1127
|
}
|
|
1158
1128
|
if (batchApprovalsEnabled) {
|
|
1159
1129
|
const before = accumulator.getSegments().length
|
|
1160
|
-
const segments = accumulator.addApprovalBatch(
|
|
1161
|
-
requestId,
|
|
1162
|
-
approvalType,
|
|
1163
|
-
toolCalls,
|
|
1164
|
-
status,
|
|
1165
|
-
undefined,
|
|
1166
|
-
undefined,
|
|
1167
|
-
guideOrigin,
|
|
1168
|
-
)
|
|
1130
|
+
const segments = accumulator.addApprovalBatch(requestId, approvalType, toolCalls, status)
|
|
1169
1131
|
applyAccumulated(before, segments)
|
|
1170
1132
|
break
|
|
1171
1133
|
}
|
|
@@ -1183,8 +1145,6 @@ export function createChatStreamReducer(
|
|
|
1183
1145
|
call.toolExplanation,
|
|
1184
1146
|
approvalType,
|
|
1185
1147
|
status,
|
|
1186
|
-
undefined,
|
|
1187
|
-
guideOrigin,
|
|
1188
1148
|
)
|
|
1189
1149
|
}
|
|
1190
1150
|
applyAccumulated(before, segments)
|
|
@@ -1194,7 +1154,7 @@ export function createChatStreamReducer(
|
|
|
1194
1154
|
// ── Single form ──
|
|
1195
1155
|
const command = event.command ?? ''
|
|
1196
1156
|
const explanation = event.explanation
|
|
1197
|
-
if (
|
|
1157
|
+
if (displayApprovalTypes.includes(approvalType)) {
|
|
1198
1158
|
const status = (approvalStatuses[requestId] || 'pending') as ChatApprovalStatus
|
|
1199
1159
|
const before = accumulator.getSegments().length
|
|
1200
1160
|
const segments = accumulator.addApprovalRequest(
|
|
@@ -1203,10 +1163,6 @@ export function createChatStreamReducer(
|
|
|
1203
1163
|
explanation,
|
|
1204
1164
|
approvalType,
|
|
1205
1165
|
status,
|
|
1206
|
-
// SSE-shaped cards (a re-streamed Product Guide proposal) carry
|
|
1207
|
-
// their body as structured rows rather than prose.
|
|
1208
|
-
event.fields,
|
|
1209
|
-
guideOrigin,
|
|
1210
1166
|
)
|
|
1211
1167
|
applyAccumulated(before, segments)
|
|
1212
1168
|
} else {
|
|
@@ -1751,7 +1707,6 @@ export function createChatStreamReducer(
|
|
|
1751
1707
|
streamingPhase = 'idle'
|
|
1752
1708
|
dialogTokenUsage = null
|
|
1753
1709
|
liveModel = null
|
|
1754
|
-
guideConversationId = null
|
|
1755
1710
|
approvalStatuses = {}
|
|
1756
1711
|
accumulator.reset()
|
|
1757
1712
|
pendingEscalated.clear()
|
|
@@ -1800,7 +1755,6 @@ export function createChatStreamReducer(
|
|
|
1800
1755
|
streamingPhase = 'idle'
|
|
1801
1756
|
dialogTokenUsage = null
|
|
1802
1757
|
liveModel = null
|
|
1803
|
-
guideConversationId = null
|
|
1804
1758
|
accumulator.reset()
|
|
1805
1759
|
pendingEscalated.clear()
|
|
1806
1760
|
isInStream = false
|
|
@@ -113,12 +113,6 @@ export interface ApprovalRequestData {
|
|
|
113
113
|
requestId?: string
|
|
114
114
|
approvalRequestId?: string
|
|
115
115
|
approvalType?: string
|
|
116
|
-
/** `'guide'` when the card came from a Product Guide frame (see `GuideOrigin`
|
|
117
|
-
* in `chat-protocol/events`). Carried onto the segment because hosts route
|
|
118
|
-
* and filter approvals by where they came from: mingo lifts its OWN pending
|
|
119
|
-
* cards into a sticky footer, and a guide card must stay inline in the turn
|
|
120
|
-
* the way it does in the hub's chat. */
|
|
121
|
-
origin?: 'guide'
|
|
122
116
|
}
|
|
123
117
|
|
|
124
118
|
export interface ApprovalResultData {
|
|
@@ -195,11 +189,6 @@ export interface ApprovalBatchData {
|
|
|
195
189
|
* batch itself is approved.
|
|
196
190
|
*/
|
|
197
191
|
executions?: Record<string, ApprovalBatchExecutionState>
|
|
198
|
-
/** `'guide'` when the batch came from a Product Guide `approval_batch` frame.
|
|
199
|
-
* Same contract and the same reasons as `ApprovalRequestData.origin` — the
|
|
200
|
-
* hub groups multiple proposals into one card, and that card resolves
|
|
201
|
-
* through the hub exactly like a single one. */
|
|
202
|
-
origin?: 'guide'
|
|
203
192
|
}
|
|
204
193
|
|
|
205
194
|
/** Approve/reject handler stamped onto approval segments. MAY resolve a
|
|
@@ -345,10 +334,6 @@ export interface ThinkingMessageData extends MessageDataBase {
|
|
|
345
334
|
export interface GuideMessageData extends MessageDataBase {
|
|
346
335
|
type: 'GUIDE'
|
|
347
336
|
text?: string
|
|
348
|
-
/** Persisted Product Guide frame (GraphQL `GuideData.payload`, a JSON scalar)
|
|
349
|
-
* for rows that carry a card instead of answer text — replayed through the
|
|
350
|
-
* live path's `guideFrameEvent`. */
|
|
351
|
-
payload?: Record<string, unknown>
|
|
352
337
|
}
|
|
353
338
|
|
|
354
339
|
/** Persisted `ASK` row (GraphQL `AskData`). `text` is the intro sentence, which
|
|
@@ -61,10 +61,6 @@ export interface ChunkData {
|
|
|
61
61
|
/** On APPROVAL_RESULT chunks: display name of the user who resolved the request. */
|
|
62
62
|
resolvedByName?: string
|
|
63
63
|
toolCalls?: any[]
|
|
64
|
-
/** On GUIDE chunks: a Product Guide frame the agent re-streamed verbatim from
|
|
65
|
-
* the hub, instead of the answer text `text` carries. Decoded by
|
|
66
|
-
* `decodeNatsChunk` through the shared leading-frame table. */
|
|
67
|
-
payload?: Record<string, unknown>
|
|
68
64
|
modelName?: string
|
|
69
65
|
providerName?: string
|
|
70
66
|
provider?: string
|
|
@@ -3,12 +3,7 @@
|
|
|
3
3
|
* Contains types for message parsing, accumulation, and processing
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
7
|
-
ApprovalRequestField,
|
|
8
|
-
MessageSegment,
|
|
9
|
-
PendingToolCallData,
|
|
10
|
-
ExecutingToolState,
|
|
11
|
-
} from './message.types'
|
|
6
|
+
import type { MessageSegment, PendingToolCallData, ExecutingToolState } from './message.types'
|
|
12
7
|
import type { ChatApprovalStatus, AssistantType } from './chat.types'
|
|
13
8
|
import type { ChunkData } from './network.types'
|
|
14
9
|
|
|
@@ -23,12 +18,6 @@ export interface PendingApproval {
|
|
|
23
18
|
command: string
|
|
24
19
|
explanation?: string
|
|
25
20
|
approvalType: string
|
|
26
|
-
/** Structured label/value rows — a Product Guide card is almost entirely
|
|
27
|
-
* these, so a path that forgets them renders a bare title. */
|
|
28
|
-
fields?: ApprovalRequestField[]
|
|
29
|
-
/** Where the card came from; `'guide'` keeps it inline and routes its
|
|
30
|
-
* buttons to the hub (see `ApprovalRequestData.origin`). */
|
|
31
|
-
origin?: 'guide'
|
|
32
21
|
}
|
|
33
22
|
|
|
34
23
|
export interface AccumulatorState {
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
import { describe, it, expect } from 'vitest'
|
|
12
12
|
import { processHistoricalMessagesWithErrors } from '../process-historical-messages'
|
|
13
|
-
import { decodeNatsChunk } from '../../../../chat-protocol/nats-decoder'
|
|
14
13
|
import { createChatStreamReducer } from '../../stream/chat-stream-reducer'
|
|
15
14
|
import type { HistoricalMessage, MessageSegment } from '../../types'
|
|
16
15
|
|
|
@@ -157,94 +156,3 @@ describe('displayApprovalTypes — history vs realtime default divergence', () =
|
|
|
157
156
|
expect(live.escalated).toHaveLength(0)
|
|
158
157
|
})
|
|
159
158
|
})
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* A Product Guide card must survive a reload UNCHANGED.
|
|
163
|
-
*
|
|
164
|
-
* It arrives as a hub frame the agent re-streams into the dialog, so it is
|
|
165
|
-
* typed by TOOL and resolves through the hub's confirm route — the tier gate
|
|
166
|
-
* these tests are about simply does not apply to it. When the rule lived only
|
|
167
|
-
* in the realtime kernel, the same card rendered inline while streaming and
|
|
168
|
-
* then, on the next page load, was tier-gated: lifted out of its turn into the
|
|
169
|
-
* consumer's sticky pending-approvals envelope, stripped of the `fields` that
|
|
170
|
-
* are its entire body, and stripped of the `origin` that routes its buttons —
|
|
171
|
-
* so clicking Approve hit the agent's endpoint and 404'd.
|
|
172
|
-
*/
|
|
173
|
-
describe('Product Guide approvals — history and realtime agree', () => {
|
|
174
|
-
const guideProposalMessage = (id: string): HistoricalMessage => ({
|
|
175
|
-
id,
|
|
176
|
-
createdAt: '2026-07-17T12:00:00Z',
|
|
177
|
-
owner: { type: 'ASSISTANT' },
|
|
178
|
-
messageData: [
|
|
179
|
-
{
|
|
180
|
-
type: 'GUIDE',
|
|
181
|
-
payload: {
|
|
182
|
-
kind: 'approval_request',
|
|
183
|
-
proposalId: 'prop-1',
|
|
184
|
-
toolName: 'create_ticket',
|
|
185
|
-
title: 'Open a support ticket',
|
|
186
|
-
fields: [{ label: 'Subject', value: 'Agent will not enroll' }],
|
|
187
|
-
},
|
|
188
|
-
} as any,
|
|
189
|
-
],
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
// The narrowest possible tier list: it excludes the guide's tool name, so a
|
|
193
|
-
// card that still renders proves the gate was bypassed, not merely satisfied.
|
|
194
|
-
const NARROW_TIERS = ['CLIENT']
|
|
195
|
-
|
|
196
|
-
it('renders inline on reload despite a tier list that excludes it', () => {
|
|
197
|
-
const { messages, escalatedApprovals } = processHistoricalMessagesWithErrors(
|
|
198
|
-
[guideProposalMessage('m1')],
|
|
199
|
-
{ displayApprovalTypes: NARROW_TIERS },
|
|
200
|
-
)
|
|
201
|
-
const rendered = messages.flatMap((m) => approvalSegments(m.content))
|
|
202
|
-
expect(rendered).toHaveLength(1)
|
|
203
|
-
expect(escalatedApprovals.size).toBe(0)
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
it('keeps the card in its own turn, not in the sticky pending-approvals envelope', () => {
|
|
207
|
-
const { messages } = processHistoricalMessagesWithErrors([guideProposalMessage('m1')], {
|
|
208
|
-
displayApprovalTypes: NARROW_TIERS,
|
|
209
|
-
})
|
|
210
|
-
expect(messages.some((m) => String(m.id).startsWith('pending-approvals-'))).toBe(false)
|
|
211
|
-
const turn = messages.find((m) => m.id === 'm1')
|
|
212
|
-
expect(approvalSegments(turn?.content)).toHaveLength(1)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('carries the same fields and origin as the live stream', () => {
|
|
216
|
-
/** The single card's `data`, narrowed off the segment union. */
|
|
217
|
-
const cardData = (segments: MessageSegment[]) => {
|
|
218
|
-
const card = segments.find((s) => s.type === 'approval_request')
|
|
219
|
-
return card?.type === 'approval_request' ? card.data : undefined
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const { messages } = processHistoricalMessagesWithErrors([guideProposalMessage('m1')], {
|
|
223
|
-
displayApprovalTypes: NARROW_TIERS,
|
|
224
|
-
})
|
|
225
|
-
const replayed = cardData(messages.flatMap((m) => approvalSegments(m.content)))
|
|
226
|
-
|
|
227
|
-
const live = createChatStreamReducer({ transport: 'nats', displayApprovalTypes: NARROW_TIERS })
|
|
228
|
-
live.apply({ type: 'turn-start' })
|
|
229
|
-
live.apply(
|
|
230
|
-
decodeNatsChunk({
|
|
231
|
-
type: 'GUIDE',
|
|
232
|
-
payload: {
|
|
233
|
-
kind: 'approval_request',
|
|
234
|
-
proposalId: 'prop-1',
|
|
235
|
-
toolName: 'create_ticket',
|
|
236
|
-
title: 'Open a support ticket',
|
|
237
|
-
fields: [{ label: 'Subject', value: 'Agent will not enroll' }],
|
|
238
|
-
},
|
|
239
|
-
})!,
|
|
240
|
-
)
|
|
241
|
-
const streamed = cardData(live.state.messages.flatMap((m) => approvalSegments(m.segments)))
|
|
242
|
-
|
|
243
|
-
// Same card either way — down to the rows that ARE its body, and the marker
|
|
244
|
-
// that decides which backend its buttons talk to.
|
|
245
|
-
expect(replayed).toEqual(streamed)
|
|
246
|
-
expect(replayed?.origin).toBe('guide')
|
|
247
|
-
expect(replayed?.approvalType).toBe('create_ticket')
|
|
248
|
-
expect(replayed?.fields).toEqual([{ label: 'Subject', value: 'Agent will not enroll' }])
|
|
249
|
-
})
|
|
250
|
-
})
|
|
@@ -14,15 +14,6 @@ export {
|
|
|
14
14
|
type AccumulatorCallbacks,
|
|
15
15
|
} from './message-segment-accumulator'
|
|
16
16
|
|
|
17
|
-
// Where an approval card renders — one rule for both live kernels, the history
|
|
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'
|
|
25
|
-
|
|
26
17
|
// Historical message processing
|
|
27
18
|
export {
|
|
28
19
|
processHistoricalMessages,
|
|
@@ -12,7 +12,6 @@ import type {
|
|
|
12
12
|
AskOptionData,
|
|
13
13
|
MessageSegment,
|
|
14
14
|
ToolExecutionSegment,
|
|
15
|
-
ApprovalRequestField,
|
|
16
15
|
ApprovalRequestSegment,
|
|
17
16
|
ApprovalBatchSegment,
|
|
18
17
|
ApprovalBatchExecutionState,
|
|
@@ -325,55 +324,23 @@ export class MessageSegmentAccumulator {
|
|
|
325
324
|
command: string,
|
|
326
325
|
explanation: string | undefined,
|
|
327
326
|
approvalType: string,
|
|
328
|
-
status: ChatApprovalStatus = 'pending'
|
|
329
|
-
/** Structured label/value rows. The card prefers them over `explanation`
|
|
330
|
-
* (see `ApprovalRequestData.fields`). Optional because the agent's own
|
|
331
|
-
* approvals carry prose; a Product Guide card is almost entirely fields —
|
|
332
|
-
* dropping them here left it as a bare title. */
|
|
333
|
-
fields?: ApprovalRequestField[],
|
|
334
|
-
/** Where the card came from; `'guide'` keeps it inline (see
|
|
335
|
-
* `ApprovalRequestData.origin`). */
|
|
336
|
-
origin?: 'guide'
|
|
327
|
+
status: ChatApprovalStatus = 'pending'
|
|
337
328
|
): MessageSegment[] {
|
|
338
|
-
|
|
339
|
-
this.buildApprovalRequestSegment(
|
|
340
|
-
requestId,
|
|
341
|
-
{ command, explanation, approvalType, fields, origin },
|
|
342
|
-
status,
|
|
343
|
-
),
|
|
344
|
-
)
|
|
345
|
-
return this.getSegments()
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Build one approval-request segment — THE constructor for this segment type.
|
|
350
|
-
*
|
|
351
|
-
* Three paths produce these: `addApprovalRequest` (live stream and replay),
|
|
352
|
-
* `flushPendingApprovals` (tracked-but-unresolved after a history replay) and
|
|
353
|
-
* `processApprovalResult` (a result arriving for a tracked request). They used
|
|
354
|
-
* to hand-write the object each, which is how `fields` and `origin` reached
|
|
355
|
-
* the card down one path and not the others — the same proposal rendering as a
|
|
356
|
-
* bare title, or losing the marker that routes its buttons to the hub.
|
|
357
|
-
*/
|
|
358
|
-
private buildApprovalRequestSegment(
|
|
359
|
-
requestId: string,
|
|
360
|
-
approval: PendingApproval,
|
|
361
|
-
status: ChatApprovalStatus,
|
|
362
|
-
): ApprovalRequestSegment {
|
|
363
|
-
return {
|
|
329
|
+
const segment: ApprovalRequestSegment = {
|
|
364
330
|
type: 'approval_request',
|
|
365
331
|
data: {
|
|
366
|
-
command
|
|
367
|
-
explanation
|
|
332
|
+
command,
|
|
333
|
+
explanation,
|
|
368
334
|
requestId,
|
|
369
|
-
approvalType
|
|
370
|
-
...(approval.fields && approval.fields.length > 0 ? { fields: approval.fields } : {}),
|
|
371
|
-
...(approval.origin ? { origin: approval.origin } : {}),
|
|
335
|
+
approvalType,
|
|
372
336
|
},
|
|
373
337
|
status,
|
|
374
338
|
onApprove: this.callbacks.onApprove,
|
|
375
339
|
onReject: this.callbacks.onReject,
|
|
376
340
|
}
|
|
341
|
+
|
|
342
|
+
this.segments.push(segment)
|
|
343
|
+
return this.getSegments()
|
|
377
344
|
}
|
|
378
345
|
|
|
379
346
|
/**
|
|
@@ -396,9 +363,6 @@ export class MessageSegmentAccumulator {
|
|
|
396
363
|
status: ChatApprovalStatus = 'pending',
|
|
397
364
|
executions?: Record<string, ApprovalBatchExecutionState>,
|
|
398
365
|
resolvedByName?: string | null,
|
|
399
|
-
/** Where the batch came from; `'guide'` keeps it inline and routes it to
|
|
400
|
-
* the hub, exactly as for a single card (see `ApprovalBatchData.origin`). */
|
|
401
|
-
origin?: 'guide',
|
|
402
366
|
): MessageSegment[] {
|
|
403
367
|
const existingIndex = this.segments.findIndex(
|
|
404
368
|
(s): s is ApprovalBatchSegment =>
|
|
@@ -408,9 +372,6 @@ export class MessageSegmentAccumulator {
|
|
|
408
372
|
if (existingIndex !== -1) {
|
|
409
373
|
const existing = this.segments[existingIndex] as ApprovalBatchSegment
|
|
410
374
|
const mergedExecutions = executions ?? existing.data.executions
|
|
411
|
-
// An upsert must not strip the marker: the flip to `approved` comes from
|
|
412
|
-
// a later event that carries no origin of its own.
|
|
413
|
-
const mergedOrigin = origin ?? existing.data.origin
|
|
414
375
|
this.segments[existingIndex] = {
|
|
415
376
|
...existing,
|
|
416
377
|
data: {
|
|
@@ -418,7 +379,6 @@ export class MessageSegmentAccumulator {
|
|
|
418
379
|
approvalType,
|
|
419
380
|
toolCalls,
|
|
420
381
|
...(mergedExecutions ? { executions: mergedExecutions } : {}),
|
|
421
|
-
...(mergedOrigin ? { origin: mergedOrigin } : {}),
|
|
422
382
|
},
|
|
423
383
|
status,
|
|
424
384
|
resolvedByName: resolvedByName ?? existing.resolvedByName,
|
|
@@ -435,7 +395,6 @@ export class MessageSegmentAccumulator {
|
|
|
435
395
|
approvalType,
|
|
436
396
|
toolCalls,
|
|
437
397
|
...(executions ? { executions } : {}),
|
|
438
|
-
...(origin ? { origin } : {}),
|
|
439
398
|
},
|
|
440
399
|
status,
|
|
441
400
|
resolvedByName,
|
|
@@ -458,17 +417,20 @@ export class MessageSegmentAccumulator {
|
|
|
458
417
|
): { segment: ApprovalRequestSegment; pendingData: PendingApproval | null } | null {
|
|
459
418
|
const pendingApproval = this.pendingApprovals.get(requestId)
|
|
460
419
|
const status: ChatApprovalStatus = approved ? 'approved' : 'rejected'
|
|
461
|
-
|
|
462
|
-
const segment =
|
|
463
|
-
|
|
464
|
-
{
|
|
465
|
-
...pendingApproval,
|
|
420
|
+
|
|
421
|
+
const segment: ApprovalRequestSegment = {
|
|
422
|
+
type: 'approval_request',
|
|
423
|
+
data: {
|
|
466
424
|
command: pendingApproval?.command || '',
|
|
425
|
+
explanation: pendingApproval?.explanation,
|
|
426
|
+
requestId,
|
|
467
427
|
approvalType: pendingApproval?.approvalType || approvalType,
|
|
468
428
|
},
|
|
469
429
|
status,
|
|
470
|
-
|
|
471
|
-
|
|
430
|
+
onApprove: this.callbacks.onApprove,
|
|
431
|
+
onReject: this.callbacks.onReject,
|
|
432
|
+
}
|
|
433
|
+
|
|
472
434
|
this.segments.push(segment)
|
|
473
435
|
|
|
474
436
|
if (pendingApproval) {
|
|
@@ -576,11 +538,22 @@ export class MessageSegmentAccumulator {
|
|
|
576
538
|
*/
|
|
577
539
|
flushPendingApprovals(): ApprovalRequestSegment[] {
|
|
578
540
|
const segments: ApprovalRequestSegment[] = []
|
|
579
|
-
|
|
541
|
+
|
|
580
542
|
this.pendingApprovals.forEach((approval, requestId) => {
|
|
581
|
-
segments.push(
|
|
543
|
+
segments.push({
|
|
544
|
+
type: 'approval_request',
|
|
545
|
+
data: {
|
|
546
|
+
command: approval.command,
|
|
547
|
+
explanation: approval.explanation,
|
|
548
|
+
requestId,
|
|
549
|
+
approvalType: approval.approvalType,
|
|
550
|
+
},
|
|
551
|
+
status: 'pending',
|
|
552
|
+
onApprove: this.callbacks.onApprove,
|
|
553
|
+
onReject: this.callbacks.onReject,
|
|
554
|
+
})
|
|
582
555
|
})
|
|
583
|
-
|
|
556
|
+
|
|
584
557
|
return segments
|
|
585
558
|
}
|
|
586
559
|
|
|
@@ -654,8 +627,6 @@ export class MessageSegmentAccumulator {
|
|
|
654
627
|
data.explanation,
|
|
655
628
|
data.approvalType || '',
|
|
656
629
|
status,
|
|
657
|
-
data.fields,
|
|
658
|
-
data.origin,
|
|
659
630
|
)
|
|
660
631
|
break
|
|
661
632
|
}
|
|
@@ -668,7 +639,6 @@ export class MessageSegmentAccumulator {
|
|
|
668
639
|
status,
|
|
669
640
|
data.executions,
|
|
670
641
|
resolvedByName,
|
|
671
|
-
data.origin,
|
|
672
642
|
)
|
|
673
643
|
break
|
|
674
644
|
}
|
|
@@ -40,8 +40,7 @@ import {
|
|
|
40
40
|
import { ESCALATION_STATE, escalationResolvedStatus, type ChatStreamEvent } from '../../../chat-protocol/events'
|
|
41
41
|
// One normalizer for ask rows, shared with the live decoder — history and the
|
|
42
42
|
// stream must agree on which options are usable.
|
|
43
|
-
import {
|
|
44
|
-
import { approvalDisplaysInline, guideApprovalOrigin } from './approval-display'
|
|
43
|
+
import { normalizeAskOptions } from '../../../chat-protocol/nats-decoder'
|
|
45
44
|
import { MessageSegmentAccumulator, createMessageSegmentAccumulator } from './message-segment-accumulator'
|
|
46
45
|
import { applyApprovalStatusToSegment } from '../stream/message-mutations'
|
|
47
46
|
import { getCommandText } from './tool-call-helpers'
|
|
@@ -116,21 +115,11 @@ export function decodeHistoricalMessageData(data: MessageData): ChatStreamEvent
|
|
|
116
115
|
}
|
|
117
116
|
return null
|
|
118
117
|
|
|
119
|
-
|
|
120
|
-
// (`text`) and a Product Guide frame the agent re-streamed (`payload`,
|
|
121
|
-
// persisted so a card survives a reload). Decoded through the SAME
|
|
122
|
-
// `guideFrameEvent` the live path uses — a second mapping here would let
|
|
123
|
-
// history and realtime disagree about the same bytes.
|
|
124
|
-
case MESSAGE_TYPE.GUIDE: {
|
|
118
|
+
case MESSAGE_TYPE.GUIDE:
|
|
125
119
|
if ('text' in data && data.text) {
|
|
126
120
|
return { type: 'guide-delta', text: data.text }
|
|
127
121
|
}
|
|
128
|
-
const payload = 'payload' in data ? data.payload : undefined
|
|
129
|
-
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
130
|
-
return guideFrameEvent(payload as Record<string, unknown>)
|
|
131
|
-
}
|
|
132
122
|
return null
|
|
133
|
-
}
|
|
134
123
|
|
|
135
124
|
// Same completeness gate as the live decoder (`decodeNatsChunk`): a
|
|
136
125
|
// persisted row without a question or without options is not a card the
|
|
@@ -372,24 +361,12 @@ function applyHistoryEvent(
|
|
|
372
361
|
const approvalType = event.approvalType || 'CLIENT'
|
|
373
362
|
const toolCalls = event.toolCalls as PendingToolCallData[] | undefined
|
|
374
363
|
const isBatch = !!toolCalls && toolCalls.length > 0
|
|
375
|
-
// Same rule the live kernels use — a card must not change where it
|
|
376
|
-
// renders (or which backend its buttons hit) just because the page was
|
|
377
|
-
// reloaded and it came back through history instead of the stream.
|
|
378
|
-
const guideOrigin = guideApprovalOrigin(event)
|
|
379
364
|
|
|
380
|
-
if (
|
|
365
|
+
if (!displayApprovalTypes || displayApprovalTypes.includes(approvalType)) {
|
|
381
366
|
if (isBatch) {
|
|
382
367
|
const status = (approvalStatuses[event.requestId] as ChatApprovalStatus) || 'pending'
|
|
383
368
|
if (batchApprovalsEnabled) {
|
|
384
|
-
accumulator.addApprovalBatch(
|
|
385
|
-
event.requestId,
|
|
386
|
-
approvalType,
|
|
387
|
-
toolCalls!,
|
|
388
|
-
status,
|
|
389
|
-
undefined,
|
|
390
|
-
undefined,
|
|
391
|
-
guideOrigin,
|
|
392
|
-
)
|
|
369
|
+
accumulator.addApprovalBatch(event.requestId, approvalType, toolCalls!, status)
|
|
393
370
|
} else {
|
|
394
371
|
// Flag OFF — unfold batch into N legacy approval cards (same id).
|
|
395
372
|
for (const call of toolCalls!) {
|
|
@@ -400,8 +377,6 @@ function applyHistoryEvent(
|
|
|
400
377
|
call.toolExplanation,
|
|
401
378
|
approvalType,
|
|
402
379
|
status,
|
|
403
|
-
undefined,
|
|
404
|
-
guideOrigin,
|
|
405
380
|
)
|
|
406
381
|
}
|
|
407
382
|
}
|
|
@@ -414,28 +389,19 @@ function applyHistoryEvent(
|
|
|
414
389
|
// sticky card on every history re-process. Mirror the batch path
|
|
415
390
|
// and honor `approvalStatuses`.
|
|
416
391
|
const resolvedStatus = approvalStatuses[event.requestId] as ChatApprovalStatus | undefined
|
|
417
|
-
|
|
418
|
-
// A guide card is added inline even while pending. The tracked path
|
|
419
|
-
// below ends in `flushPendingApprovals`, whose segments the consumer
|
|
420
|
-
// lifts into a sticky footer — that is the treatment for the
|
|
421
|
-
// consumer's OWN approvals, and it is not how the hub's chat renders
|
|
422
|
-
// a proposal (nor where its preamble expects it).
|
|
423
|
-
if (guideOrigin || isResolved) {
|
|
392
|
+
if (resolvedStatus === 'approved' || resolvedStatus === 'rejected') {
|
|
424
393
|
accumulator.addApprovalRequest(
|
|
425
394
|
event.requestId,
|
|
426
395
|
event.command || '',
|
|
427
396
|
event.explanation,
|
|
428
397
|
approvalType,
|
|
429
|
-
resolvedStatus
|
|
430
|
-
event.fields,
|
|
431
|
-
guideOrigin,
|
|
398
|
+
resolvedStatus,
|
|
432
399
|
)
|
|
433
400
|
} else {
|
|
434
401
|
accumulator.trackApprovalRequest(event.requestId, {
|
|
435
402
|
command: event.command || '',
|
|
436
403
|
explanation: event.explanation,
|
|
437
404
|
approvalType,
|
|
438
|
-
fields: event.fields,
|
|
439
405
|
})
|
|
440
406
|
}
|
|
441
407
|
}
|