@flamingo-stack/openframe-frontend-core 0.0.850 → 0.0.852
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/build/export-map.json +1 -1
- package/dist/{case-study-card-X2HKPKT7.cjs → case-study-card-WP5Z234Q.cjs} +5 -5
- package/dist/chat-protocol/events.d.ts +4 -1
- package/dist/chat-protocol/events.d.ts.map +1 -1
- package/dist/chat-protocol/index.cjs +90 -40
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
- package/dist/chat-protocol/nats-decoder.js +5 -1
- package/dist/chat-protocol/policy-review.d.ts +18 -0
- package/dist/chat-protocol/policy-review.d.ts.map +1 -0
- package/dist/chat-protocol/policy-review.js +55 -0
- package/dist/{chunk-BU2PSRAS.cjs → chunk-2W22LY3W.cjs} +11 -11
- package/dist/{chunk-ZDSC3GFX.cjs → chunk-3SJVF3HW.cjs} +6 -6
- package/dist/{chunk-GAYLAA3L.cjs → chunk-3ZQESOPJ.cjs} +79 -79
- package/dist/{chunk-IPGGSFR7.cjs → chunk-477P6AQH.cjs} +6 -6
- package/dist/{chunk-5CBI2ZYV.cjs → chunk-5RMBLOJ5.cjs} +12 -12
- package/dist/{chunk-36DUCHI7.cjs → chunk-6GWRPW6B.cjs} +4 -4
- package/dist/{chunk-SOSIXRQ3.cjs → chunk-AZEO7HBB.cjs} +2 -2
- package/dist/{chunk-FRG724DU.cjs → chunk-BNHTBLAG.cjs} +17 -17
- package/dist/{chunk-VRWBJL7B.cjs → chunk-CHGIACIP.cjs} +91 -91
- package/dist/{chunk-CAGNWDVJ.cjs → chunk-COJG2ATY.cjs} +45 -45
- package/dist/{chunk-XKOL5CNU.cjs → chunk-D3H6AI7O.cjs} +956 -1077
- package/dist/{chunk-ALRYVZ3E.cjs → chunk-DLTPSP5F.cjs} +2 -2
- package/dist/{chunk-3W4ASAXN.cjs → chunk-EIACEY2L.cjs} +4 -4
- package/dist/{chunk-TIVPOVIM.cjs → chunk-ETZFBB7O.cjs} +2 -2
- package/dist/{chunk-GRCOEICK.cjs → chunk-G7GF4PWA.cjs} +6 -6
- package/dist/{chunk-7ICHJVAT.cjs → chunk-GEZ3UXCA.cjs} +25 -25
- package/dist/chunk-GGUFVIJ6.cjs +28 -0
- package/dist/{chunk-ZGEWT3SF.cjs → chunk-HXURNKXI.cjs} +3 -3
- package/dist/{chunk-TX4NTH22.cjs → chunk-IC4IJJB2.cjs} +102 -102
- package/dist/{chunk-BUEEQD3G.cjs → chunk-IGVAPIEW.cjs} +2 -2
- package/dist/{chunk-IG6ROHPS.cjs → chunk-IO6NN3SL.cjs} +54 -54
- package/dist/{chunk-5PIR77V5.cjs → chunk-KHOB3TES.cjs} +4 -4
- package/dist/{chunk-JQCQCE6U.cjs → chunk-KKFDRCQF.cjs} +2 -2
- package/dist/{chunk-6AJYGXXS.cjs → chunk-KNN2GCMW.cjs} +21 -21
- package/dist/{chunk-YJZ7R3W5.cjs → chunk-KXFIYO4O.cjs} +8 -8
- package/dist/{chunk-FXLROQUT.cjs → chunk-LFEURIZN.cjs} +590 -607
- package/dist/{chunk-JWIITCVB.cjs → chunk-MFDRCTM3.cjs} +47 -47
- package/dist/{chunk-OHHDX3Z4.cjs → chunk-MXY2OGEV.cjs} +42 -42
- package/dist/{chunk-533QK4R6.cjs → chunk-NMWLXHFH.cjs} +130 -130
- package/dist/{chunk-MDLT6D46.cjs → chunk-O3EKOXFZ.cjs} +20 -20
- package/dist/{chunk-HLJXCOMA.cjs → chunk-OH5TXFRR.cjs} +4 -4
- package/dist/{chunk-SA2NNWIJ.cjs → chunk-P63CRPWI.cjs} +19 -64
- package/dist/{chunk-R5JPZ6TY.cjs → chunk-QOW2GP3V.cjs} +21 -21
- package/dist/{chunk-JBTH3O2C.cjs → chunk-SSKMGH5O.cjs} +4 -4
- package/dist/{chunk-4YBS4HIO.cjs → chunk-U767P4JU.cjs} +455 -38
- package/dist/{chunk-FNQWL4W4.cjs → chunk-UO2EJGRJ.cjs} +45 -45
- package/dist/{chunk-IHY7E4EX.cjs → chunk-USPUZO53.cjs} +4 -4
- package/dist/{chunk-IN3LA2FU.cjs → chunk-UV45EPXN.cjs} +2 -2
- package/dist/{chunk-S3QXLD4X.cjs → chunk-VCFQHPOO.cjs} +37 -3
- package/dist/{chunk-2QZUTNL3.cjs → chunk-VUHHLRAY.cjs} +92 -92
- package/dist/{chunk-QJER2ZNA.cjs → chunk-WFAZMASF.cjs} +3 -3
- package/dist/{chunk-LGYPCUHO.cjs → chunk-X3WIOMEE.cjs} +36 -36
- package/dist/{cloud-tenants-TVB3DT3J.cjs → cloud-tenants-O33HCUYB.cjs} +50 -50
- package/dist/components/case-studies/index.cjs +69 -69
- package/dist/components/chat/approval-batch-message.d.ts +5 -0
- package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
- package/dist/components/chat/approval-batch-message.js +36 -10
- package/dist/components/chat/index.cjs +45 -45
- package/dist/components/chat/policy-review.d.ts +27 -0
- package/dist/components/chat/policy-review.d.ts.map +1 -0
- package/dist/components/chat/policy-review.js +67 -0
- package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
- package/dist/components/chat/stream/message-mutations.js +4 -1
- package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
- package/dist/components/chat/tool-execution-display.js +16 -5
- package/dist/components/chat/types/message.types.d.ts +40 -2
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
- package/dist/components/chat/utils/extract-incomplete-message-state.js +2 -0
- package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.js +3 -0
- package/dist/components/chat/utils/process-historical-messages.d.ts +2 -2
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.js +7 -1
- package/dist/components/contact/index.cjs +51 -51
- package/dist/components/docs/index.cjs +54 -54
- package/dist/components/embeds/index.cjs +51 -51
- package/dist/components/faq/index.cjs +52 -52
- package/dist/components/features/approval-level.d.ts +6 -12
- package/dist/components/features/approval-level.d.ts.map +1 -1
- package/dist/components/features/approval-level.js +45 -55
- package/dist/components/features/index.cjs +55 -54
- package/dist/components/features/notifications/approval-request-notification-tile.d.ts.map +1 -1
- package/dist/components/features/notifications/approval-request-notification-tile.js +71 -48
- package/dist/components/features/notifications/types.d.ts +2 -0
- package/dist/components/features/notifications/types.d.ts.map +1 -1
- package/dist/components/features/notifications/types.js +3 -1
- package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
- package/dist/components/features/policy-configuration-panel.js +75 -64
- package/dist/components/help-center-pages/index.cjs +156 -156
- package/dist/components/icons-v2-generated/brand-logos/index.d.ts +1 -0
- package/dist/components/icons-v2-generated/brand-logos/index.d.ts.map +1 -1
- package/dist/components/icons-v2-generated/brand-logos/index.js +2 -0
- package/dist/components/icons-v2-generated/brand-logos/stripe-icon.d.ts +8 -0
- package/dist/components/icons-v2-generated/brand-logos/stripe-icon.d.ts.map +1 -0
- package/dist/components/icons-v2-generated/brand-logos/stripe-icon.js +24 -0
- package/dist/components/icons-v2-generated/icon-loaders.d.ts.map +1 -1
- package/dist/components/icons-v2-generated/icon-loaders.js +1 -0
- package/dist/components/icons-v2-generated/icon-names.js +1 -1
- package/dist/components/icons-v2-generated/index.cjs +33 -29
- package/dist/components/index.cjs +122 -123
- package/dist/components/meeting-scheduler/index.cjs +113 -112
- package/dist/components/navigation/index.cjs +54 -54
- package/dist/components/onboarding-guides/index.cjs +53 -53
- package/dist/components/product-demo/index.cjs +61 -61
- package/dist/components/related-content/index.cjs +9 -9
- package/dist/components/tickets/index.cjs +57 -57
- package/dist/components/ui/file-manager/index.cjs +8 -9
- package/dist/components/ui/index.cjs +55 -56
- package/dist/components/ui/radio-group.d.ts.map +1 -1
- package/dist/components/ui/radio-group.js +8 -2
- package/dist/components/ui/tab-selector.d.ts +7 -1
- package/dist/components/ui/tab-selector.d.ts.map +1 -1
- package/dist/components/ui/tab-selector.js +22 -3
- package/dist/{devices-NGCTSXIU.cjs → devices-2RGGMC2B.cjs} +51 -51
- package/dist/{embeddable-chat-322NDFP5.cjs → embeddable-chat-SJEEJI4N.cjs} +122 -122
- package/dist/hooks/index.cjs +9 -9
- package/dist/{icon-loaders-SUPEKMBJ.cjs → icon-loaders-CTH6E7OP.cjs} +1 -0
- package/dist/index.cjs +61 -62
- package/dist/{knowledge-75PO5DYS.cjs → knowledge-5TJIXVMD.cjs} +52 -52
- package/dist/{logs-PZESOAIG.cjs → logs-HGQOTRKZ.cjs} +57 -57
- package/dist/{policies-6XNMBFWO.cjs → policies-GWFSLVSM.cjs} +54 -54
- package/dist/{remote-session-Q2FIBCGT.cjs → remote-session-SG44CNOK.cjs} +36 -36
- package/dist/{scripts-B3JW6TPF.cjs → scripts-PGJNAZF4.cjs} +57 -57
- package/dist/{software-update-NWP542J7.cjs → software-update-KKLSYHDI.cjs} +54 -54
- package/dist/stripe-icon-YKVABTNI.cjs +7 -0
- package/dist/{tickets-board-7FLGQEHF.cjs → tickets-board-SQY2Y6QV.cjs} +39 -38
- package/dist/utils/index.cjs +32 -1
- package/package.json +1 -1
- package/src/chat-protocol/events.ts +4 -1
- package/src/chat-protocol/nats-decoder.ts +4 -0
- package/src/chat-protocol/policy-review.ts +71 -0
- package/src/components/chat/approval-batch-message.tsx +47 -10
- package/src/components/chat/policy-review.tsx +90 -0
- package/src/components/chat/stream/message-mutations.ts +4 -0
- package/src/components/chat/tool-execution-display.tsx +23 -4
- package/src/components/chat/types/message.types.ts +42 -2
- package/src/components/chat/utils/extract-incomplete-message-state.ts +2 -0
- package/src/components/chat/utils/message-segment-accumulator.ts +3 -0
- package/src/components/chat/utils/process-historical-messages.ts +14 -3
- package/src/components/features/approval-level.tsx +74 -78
- package/src/components/features/notifications/approval-request-notification-tile.tsx +75 -50
- package/src/components/features/notifications/types.ts +4 -0
- package/src/components/features/policy-configuration-panel.tsx +100 -83
- package/src/components/icons-v2/icon-colors.json +3 -0
- package/src/components/icons-v2-generated/brand-logos/index.ts +1 -0
- package/src/components/icons-v2-generated/brand-logos/stripe-icon.tsx +27 -0
- package/src/components/icons-v2-generated/icon-loaders.ts +1 -0
- package/src/components/icons-v2-generated/icon-names.ts +1 -1
- package/src/components/ui/radio-group.tsx +20 -2
- package/src/components/ui/tab-selector.tsx +26 -3
- package/src/styles/ods-colors.css +1 -0
- package/dist/chunk-N3MBQRCQ.cjs +0 -41
- /package/dist/{chunk-46547WQW.cjs → chunk-7HH4Y27U.cjs} +0 -0
|
@@ -11,6 +11,7 @@ import { StatusLine, type StatusLineTone } from '../ui/status-line';
|
|
|
11
11
|
import { CHAT_BLOCK_FRAME_CLASS } from './chat-block-frame';
|
|
12
12
|
import { ExpandChevron } from './expand-chevron';
|
|
13
13
|
import { useCollapsible } from './hooks/use-collapsible';
|
|
14
|
+
import { PolicyEscalationAlert, PolicyReviewRows, hasPolicyReviewRows, isPolicyEscalated } from './policy-review';
|
|
14
15
|
import { ArgRow, ResultBlock } from './tool-call-blocks';
|
|
15
16
|
import type { ToolExecutionDisplayProps } from './types';
|
|
16
17
|
import { COMMAND_BODY_ARG_KEYS } from './utils/tool-call-helpers';
|
|
@@ -56,6 +57,9 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
|
|
|
56
57
|
}, [message.parameters, variant]);
|
|
57
58
|
|
|
58
59
|
const hasResult = isExecuted && typeof message.result === 'string' && message.result.length > 0;
|
|
60
|
+
// Admin-only detail, like the command: the end-client card is the static row below, which draws no body.
|
|
61
|
+
const review = message.policyReview;
|
|
62
|
+
const reviewRows = hasPolicyReviewRows(review);
|
|
59
63
|
|
|
60
64
|
// A remote (MCP) read-only tool deliberately publishes NO parameters and NO
|
|
61
65
|
// result — the backend hides both, and sends a human title instead. There is
|
|
@@ -67,9 +71,15 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
|
|
|
67
71
|
// remote rather than one of ours — and guarded on there being a title to
|
|
68
72
|
// show, so a title-less row keeps the old behaviour rather than collapsing
|
|
69
73
|
// to a blank line.
|
|
70
|
-
const isConciseRemoteRead =
|
|
74
|
+
const isConciseRemoteRead =
|
|
75
|
+
!message.integratedToolType &&
|
|
76
|
+
!!previewText &&
|
|
77
|
+
argEntries.length === 0 &&
|
|
78
|
+
!hasResult &&
|
|
79
|
+
!reviewRows &&
|
|
80
|
+
!isPolicyEscalated(review);
|
|
71
81
|
const isStaticRow = isClientView || isConciseRemoteRead;
|
|
72
|
-
const hasBody = argEntries.length > 0 || hasResult || isExecuting;
|
|
82
|
+
const hasBody = argEntries.length > 0 || reviewRows || hasResult || isExecuting;
|
|
73
83
|
// A run with no arguments and no result has nothing behind a chevron either:
|
|
74
84
|
// its header stays, with no chevron and no click target, so the row never
|
|
75
85
|
// opens onto an empty box. (A run still executing has a body: its live
|
|
@@ -166,17 +176,23 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
|
|
|
166
176
|
{argEntries.map(([key, value]) => (
|
|
167
177
|
<ArgRow key={key} argKey={key} value={value} />
|
|
168
178
|
))}
|
|
179
|
+
{reviewRows && (
|
|
180
|
+
<PolicyReviewRows
|
|
181
|
+
review={review}
|
|
182
|
+
className={argEntries.length > 0 ? 'mt-[var(--spacing-system-xsf)]' : undefined}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
169
185
|
{hasResult && (
|
|
170
186
|
<ResultBlock
|
|
171
187
|
result={message.result}
|
|
172
|
-
className={argEntries.length > 0 ? 'mt-[var(--spacing-system-xsf)]' : undefined}
|
|
188
|
+
className={argEntries.length > 0 || reviewRows ? 'mt-[var(--spacing-system-xsf)]' : undefined}
|
|
173
189
|
/>
|
|
174
190
|
)}
|
|
175
191
|
{isExecuting && (
|
|
176
192
|
<div
|
|
177
193
|
className={cn(
|
|
178
194
|
'flex w-full flex-col items-start gap-[var(--spacing-system-xxs)]',
|
|
179
|
-
argEntries.length > 0 && 'mt-[var(--spacing-system-xsf)]',
|
|
195
|
+
(argEntries.length > 0 || reviewRows) && 'mt-[var(--spacing-system-xsf)]',
|
|
180
196
|
)}
|
|
181
197
|
>
|
|
182
198
|
<span className="text-ods-text-secondary">Result:</span>
|
|
@@ -187,6 +203,9 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
|
|
|
187
203
|
)}
|
|
188
204
|
</div>
|
|
189
205
|
</div>
|
|
206
|
+
|
|
207
|
+
{/* A run Mingo escalated (approved, then run) keeps saying so, outside the collapsible part. */}
|
|
208
|
+
<PolicyEscalationAlert review={review} className="border-t border-ods-border" />
|
|
190
209
|
</>
|
|
191
210
|
)}
|
|
192
211
|
</div>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Contains all message structures, segments, and content types
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { ApprovalLevel } from '../../../types/permissions';
|
|
6
7
|
import type { ChatRef } from '../chat-ref.types';
|
|
7
8
|
import type { QuickActionIconSpec } from '../quick-action-chip';
|
|
8
9
|
import type { AssistantType, AuthorType, ChatApprovalStatus, MessageOwner } from './chat.types';
|
|
@@ -55,6 +56,36 @@ export const SCROLL_ANCHOR = { TOP: 'top', BOTTOM: 'bottom' } as const;
|
|
|
55
56
|
|
|
56
57
|
export type ScrollAnchor = (typeof SCROLL_ANCHOR)[keyof typeof SCROLL_ANCHOR];
|
|
57
58
|
|
|
59
|
+
// ========== Policy Review Types ==========
|
|
60
|
+
|
|
61
|
+
/** One guardrail rule the command review matched (ai-agent `PolicyReviewRule`). */
|
|
62
|
+
export interface PolicyReviewRuleData {
|
|
63
|
+
code: string;
|
|
64
|
+
/** The rule's name when it matched; the history keeps the name it had then. */
|
|
65
|
+
label: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Why the security policy gave a tool call its approval level (ai-agent `PolicyReview`). Sent only while
|
|
70
|
+
* the semantic guardrail is on, on approval tool calls and on `EXECUTING_TOOL`. Enum values stay open
|
|
71
|
+
* strings: the backend is narrowing `decision` (`SOFT_DENY` / `HARD_DENY` become `DENY`), and a value
|
|
72
|
+
* this build does not know must not drop the review. Normalized by `chat-protocol/policy-review`.
|
|
73
|
+
*/
|
|
74
|
+
export interface PolicyReviewData {
|
|
75
|
+
/** `CLASSIFIER` (the AI review) or `REGEX`. */
|
|
76
|
+
source?: string;
|
|
77
|
+
/** `POLICY`, `FALLBACK` (no rule of the policy matched), or a deny by a matched guardrail rule. */
|
|
78
|
+
decision?: string;
|
|
79
|
+
/** The level in force for this call. */
|
|
80
|
+
approvalLevel?: ApprovalLevel;
|
|
81
|
+
/** The level the organization's policy sets; differs from `approvalLevel` when a rule raised it. */
|
|
82
|
+
policyLevel?: ApprovalLevel;
|
|
83
|
+
operationCodes: string[];
|
|
84
|
+
matchedRules: PolicyReviewRuleData[];
|
|
85
|
+
/** The review's one-line reason; absent from a regex review. */
|
|
86
|
+
reason?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
// ========== Tool Execution Types ==========
|
|
59
90
|
|
|
60
91
|
export interface ToolExecutionData {
|
|
@@ -69,6 +100,9 @@ export interface ToolExecutionData {
|
|
|
69
100
|
* the accumulator restores it onto the merged `EXECUTED_TOOL` segment.
|
|
70
101
|
*/
|
|
71
102
|
toolExplanation?: string;
|
|
103
|
+
/** The security policy's review of this call. Only sent on `EXECUTING_TOOL`, restored onto `EXECUTED_TOOL` like
|
|
104
|
+
* `toolExplanation`. */
|
|
105
|
+
policyReview?: PolicyReviewData;
|
|
72
106
|
parameters?: Record<string, unknown>;
|
|
73
107
|
result?: string;
|
|
74
108
|
success?: boolean;
|
|
@@ -100,6 +134,8 @@ export interface ExecutingToolState {
|
|
|
100
134
|
toolTitle?: string;
|
|
101
135
|
/** Mirrors {@link ToolExecutionData.toolExplanation}; absent on `EXECUTED_TOOL`. */
|
|
102
136
|
toolExplanation?: string;
|
|
137
|
+
/** Mirrors {@link ToolExecutionData.policyReview}; absent on `EXECUTED_TOOL`. */
|
|
138
|
+
policyReview?: PolicyReviewData;
|
|
103
139
|
parameters?: Record<string, unknown>;
|
|
104
140
|
}
|
|
105
141
|
|
|
@@ -211,6 +247,8 @@ export interface PendingToolCallData {
|
|
|
211
247
|
requiresApproval: boolean;
|
|
212
248
|
approvalType?: string | null;
|
|
213
249
|
toolCallArguments?: Record<string, unknown> | null;
|
|
250
|
+
/** Why the policy gave this call its approval level; absent while the semantic guardrail is off. */
|
|
251
|
+
policyReview?: PolicyReviewData;
|
|
214
252
|
}
|
|
215
253
|
|
|
216
254
|
/**
|
|
@@ -517,6 +555,8 @@ export interface ExecutingToolMessageData extends MessageDataBase {
|
|
|
517
555
|
title?: string;
|
|
518
556
|
/** Backend-issued human-readable explanation (what/why) of the tool call. */
|
|
519
557
|
toolExplanation?: string;
|
|
558
|
+
/** WIRE shape of the policy review: normalized by `normalizePolicyReview` on the way in. */
|
|
559
|
+
policyReview?: unknown;
|
|
520
560
|
parameters?: Record<string, unknown>;
|
|
521
561
|
toolExecutionRequestId?: string;
|
|
522
562
|
}
|
|
@@ -539,8 +579,8 @@ export interface ApprovalRequestMessageData extends MessageDataBase {
|
|
|
539
579
|
approvalType?: string;
|
|
540
580
|
command?: string;
|
|
541
581
|
explanation?: string;
|
|
542
|
-
/** Present when the approval is a batch of tool calls (new format). */
|
|
543
|
-
toolCalls?: PendingToolCallData
|
|
582
|
+
/** Present when the approval is a batch of tool calls (new format). WIRE shape: the review is normalized on replay. */
|
|
583
|
+
toolCalls?: Array<Omit<PendingToolCallData, 'policyReview'> & { policyReview?: unknown }>;
|
|
544
584
|
}
|
|
545
585
|
|
|
546
586
|
export interface ApprovalResultMessageData extends MessageDataBase {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Used to continue building messages across realtime connections
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { keptPolicyReview } from '../../../chat-protocol/policy-review';
|
|
6
7
|
import type { InitializeExtras } from '../stream/chat-stream-reducer';
|
|
7
8
|
import type { ProcessedMessage, MessageSegment, PendingApproval, ExecutingToolState } from '../types';
|
|
8
9
|
|
|
@@ -51,6 +52,7 @@ export function extractIncompleteMessageState(
|
|
|
51
52
|
toolFunction: segment.data.toolFunction,
|
|
52
53
|
toolTitle: segment.data.toolTitle,
|
|
53
54
|
toolExplanation: segment.data.toolExplanation,
|
|
55
|
+
...keptPolicyReview(segment.data.policyReview),
|
|
54
56
|
parameters: segment.data.parameters,
|
|
55
57
|
});
|
|
56
58
|
hasIncompleteState = true;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - Creating approval segments when results arrive
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { keptPolicyReview } from '../../../chat-protocol/policy-review';
|
|
11
12
|
import { applyApprovalStatusToSegment, nextBatchExecution, withBatchExecution } from '../stream/message-mutations';
|
|
12
13
|
import type {
|
|
13
14
|
AskOptionData,
|
|
@@ -197,6 +198,7 @@ export class MessageSegmentAccumulator {
|
|
|
197
198
|
toolFunction: toolData.toolFunction,
|
|
198
199
|
toolTitle: toolData.toolTitle,
|
|
199
200
|
toolExplanation: toolData.toolExplanation,
|
|
201
|
+
...keptPolicyReview(toolData.policyReview),
|
|
200
202
|
parameters: toolData.parameters,
|
|
201
203
|
});
|
|
202
204
|
this.segments.push(segment);
|
|
@@ -225,6 +227,7 @@ export class MessageSegmentAccumulator {
|
|
|
225
227
|
toolTitle: toolData.toolTitle ?? existingExecuting?.data.toolTitle ?? executingTool?.toolTitle,
|
|
226
228
|
toolExplanation:
|
|
227
229
|
toolData.toolExplanation ?? existingExecuting?.data.toolExplanation ?? executingTool?.toolExplanation,
|
|
230
|
+
...keptPolicyReview(toolData.policyReview, existingExecuting?.data.policyReview, executingTool?.policyReview),
|
|
228
231
|
parameters: toolData.parameters || executingTool?.parameters,
|
|
229
232
|
},
|
|
230
233
|
};
|
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
// One normalizer for ask rows, shared with the live decoder — history and the
|
|
35
35
|
// stream must agree on which options are usable.
|
|
36
36
|
import { normalizeAskOptions } from '../../../chat-protocol/nats-decoder';
|
|
37
|
+
// The same review normalizer as the live decoder, for the same reason.
|
|
38
|
+
import { policyReviewField } from '../../../chat-protocol/policy-review';
|
|
37
39
|
import {
|
|
38
40
|
attachmentsEvent,
|
|
39
41
|
mergeSourceMetadata,
|
|
@@ -57,6 +59,14 @@ import { approvalDisplaysInline } from './approval-display';
|
|
|
57
59
|
import { type MessageSegmentAccumulator, createMessageSegmentAccumulator } from './message-segment-accumulator';
|
|
58
60
|
import { getCommandText } from './tool-call-helpers';
|
|
59
61
|
|
|
62
|
+
/** A history tool call with its wire `policyReview` normalized (or dropped), as the live decoder does it. */
|
|
63
|
+
function withNormalizedReview(
|
|
64
|
+
call: Omit<PendingToolCallData, 'policyReview'> & { policyReview?: unknown },
|
|
65
|
+
): PendingToolCallData {
|
|
66
|
+
const { policyReview: raw, ...rest } = call;
|
|
67
|
+
return { ...rest, ...policyReviewField(raw) };
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
function getOwnerDisplayName(owner?: MessageOwner): string {
|
|
61
71
|
if (owner?.type === OWNER_TYPE.ADMIN && owner.user) {
|
|
62
72
|
const { firstName, lastName } = owner.user;
|
|
@@ -106,8 +116,8 @@ function pushStandaloneMessages(
|
|
|
106
116
|
* the resolver's name as `displayName`);
|
|
107
117
|
* - `approvalType` defaults to 'CLIENT' (legacy history parity;
|
|
108
118
|
* realtime defaults APPROVAL_REQUEST to 'USER');
|
|
109
|
-
* - batch `toolCalls` pass through
|
|
110
|
-
*
|
|
119
|
+
* - batch `toolCalls` pass through as persisted, except `policyReview`,
|
|
120
|
+
* normalized as the live decoder does it.
|
|
111
121
|
*
|
|
112
122
|
* Returns `null` for rows the assistant-turn path doesn't decode (SYSTEM
|
|
113
123
|
* is handled by the envelope's standalone path; user TEXT rows are handled
|
|
@@ -166,6 +176,7 @@ export function decodeHistoricalMessageData(data: MessageData): ChatStreamEvent
|
|
|
166
176
|
// EXECUTING chunk only, so a history replay that drops it renders
|
|
167
177
|
// an empty tool card even though the live stream showed one.
|
|
168
178
|
toolExplanation: typeof data.toolExplanation === 'string' ? data.toolExplanation : undefined,
|
|
179
|
+
...policyReviewField(data.policyReview),
|
|
169
180
|
parameters: data.parameters,
|
|
170
181
|
toolExecutionRequestId: data.toolExecutionRequestId,
|
|
171
182
|
},
|
|
@@ -199,7 +210,7 @@ export function decodeHistoricalMessageData(data: MessageData): ChatStreamEvent
|
|
|
199
210
|
approvalType: data.approvalType || 'CLIENT',
|
|
200
211
|
command: data.command || '',
|
|
201
212
|
explanation: data.explanation,
|
|
202
|
-
...(Array.isArray(data.toolCalls) ? { toolCalls: data.toolCalls } : {}),
|
|
213
|
+
...(Array.isArray(data.toolCalls) ? { toolCalls: data.toolCalls.map(withNormalizedReview) } : {}),
|
|
203
214
|
};
|
|
204
215
|
}
|
|
205
216
|
return null;
|
|
@@ -3,24 +3,18 @@
|
|
|
3
3
|
import type { ComponentType } from 'react';
|
|
4
4
|
import type { ApprovalLevel } from '../../types/permissions';
|
|
5
5
|
import { cn } from '../../utils/cn';
|
|
6
|
-
import { HeadsetIcon } from '../icons-v2-generated/audio-and-visual/headset-icon';
|
|
7
6
|
import { BannedIcon } from '../icons-v2-generated/security/banned-icon';
|
|
7
|
+
import { ShieldCheckIcon } from '../icons-v2-generated/security/shield-check-icon';
|
|
8
8
|
import { CheckCircleIcon } from '../icons-v2-generated/signs-and-symbols/check-circle-icon';
|
|
9
|
-
import {
|
|
9
|
+
import { UserCheckIcon } from '../icons-v2-generated/users/user-check-icon';
|
|
10
10
|
import { UnifiedSkeleton } from '../loading/unified-skeleton';
|
|
11
|
-
import { TOGGLE_GROUP_CLASS, ToggleGroup, ToggleGroupItem } from '../toggle-group';
|
|
12
11
|
import { TextHold } from '../ui/layout-hold';
|
|
13
12
|
import { useLoadingScope } from '../ui/loading-scope';
|
|
14
|
-
import {
|
|
15
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip';
|
|
13
|
+
import { TabSelector } from '../ui/tab-selector';
|
|
16
14
|
import { TouchFriendlyTooltip } from '../ui/touch-friendly-tooltip';
|
|
17
15
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
* (allow, never) side by side, then the two that ask someone (a technician,
|
|
21
|
-
* the user) side by side.
|
|
22
|
-
*/
|
|
23
|
-
export const APPROVAL_LEVELS: readonly ApprovalLevel[] = ['ALLOW', 'DENY', 'ASK_TECHNICIAN', 'ASK_USER'];
|
|
16
|
+
/** The approval levels, from the freest to the strictest, as the guardrails settings order them (Figma 4365-22670). */
|
|
17
|
+
export const APPROVAL_LEVELS: readonly ApprovalLevel[] = ['ALLOW', 'ASK_USER', 'ASK_TECHNICIAN', 'DENY'];
|
|
24
18
|
|
|
25
19
|
export interface ApprovalLevelMeta {
|
|
26
20
|
/** The level's name, as every surface writes it. */
|
|
@@ -47,31 +41,48 @@ export const APPROVAL_LEVEL_META: Record<ApprovalLevel, ApprovalLevelMeta> = {
|
|
|
47
41
|
ASK_USER: {
|
|
48
42
|
label: 'Ask User',
|
|
49
43
|
hint: 'Asks the person at the computer first.',
|
|
50
|
-
Icon:
|
|
44
|
+
Icon: UserCheckIcon,
|
|
51
45
|
toneClassName: 'text-ods-warning',
|
|
52
46
|
},
|
|
53
47
|
ASK_TECHNICIAN: {
|
|
54
48
|
label: 'Ask Technician',
|
|
55
49
|
hint: 'Asks a technician first. Nothing runs until they approve.',
|
|
56
|
-
Icon:
|
|
50
|
+
Icon: ShieldCheckIcon,
|
|
57
51
|
toneClassName: 'text-ods-warning',
|
|
58
52
|
},
|
|
59
53
|
DENY: { label: 'Restrict', hint: 'Never runs.', Icon: BannedIcon, toneClassName: 'text-ods-error' },
|
|
60
54
|
};
|
|
61
55
|
|
|
62
|
-
|
|
56
|
+
/** A read-only level's icon (Figma 1608-49531: 24px, before the name). */
|
|
57
|
+
const ICON_PX = 24;
|
|
63
58
|
|
|
64
59
|
/** The view's layout, read by the view and by its skeleton, so the skeleton is the view's size. */
|
|
65
60
|
const READ_ONLY_ROW_CLASS = 'inline-flex shrink-0 items-center gap-[var(--spacing-system-xxs)]';
|
|
66
|
-
|
|
61
|
+
/** Editable: the name after the strip on a phone, before it from `content-md` (Figma 4365-22461 / 4365-22663). */
|
|
62
|
+
const EDITABLE_ROW_CLASS =
|
|
63
|
+
'flex shrink-0 items-center gap-[var(--spacing-system-xxs)] content-md:gap-[var(--spacing-system-m)]';
|
|
64
|
+
const EDITABLE_NAME_ORDER_CLASS = 'order-last content-md:order-none';
|
|
65
|
+
/** A read-only level's icon box: 16px on a phone, 24 from `content-md` (Figma 555-39573 / 1608-49531). */
|
|
66
|
+
const READ_ONLY_ICON_BOX_CLASS = '[&>*]:!size-4 content-md:[&>*]:!size-6';
|
|
67
|
+
/** Read-only, the name is the row's value (h4, primary); editable, a caption in the level's colour beside the strip. */
|
|
68
|
+
const READ_ONLY_NAME_CLASS = 'whitespace-nowrap text-ods-text-primary text-h4';
|
|
67
69
|
const LEVEL_NAME_CLASS = 'whitespace-nowrap text-h6';
|
|
68
|
-
/** An editable level's
|
|
69
|
-
const
|
|
70
|
+
/** An editable level's icon (Figma: 20px in the segment). */
|
|
71
|
+
const EDITABLE_ICON_PX = 20;
|
|
70
72
|
/** The longest level name: the skeleton's name bar is exactly its width (the component's own words, unseen). */
|
|
71
73
|
const LONGEST_LEVEL_LABEL = APPROVAL_LEVELS.map(level => APPROVAL_LEVEL_META[level].label).reduce((a, b) =>
|
|
72
74
|
b.length > a.length ? b : a,
|
|
73
75
|
);
|
|
74
76
|
|
|
77
|
+
/** The strip's four levels: the one in force (none while loading) in the primary colour, the others secondary (Figma 4365-22670). */
|
|
78
|
+
function levelItems(inForce: ApprovalLevel | undefined) {
|
|
79
|
+
return APPROVAL_LEVELS.map(level => {
|
|
80
|
+
const { label, Icon } = APPROVAL_LEVEL_META[level];
|
|
81
|
+
const tone = level === inForce ? 'text-ods-text-primary' : 'text-ods-text-secondary';
|
|
82
|
+
return { id: level, ariaLabel: label, icon: <Icon size={EDITABLE_ICON_PX} className={tone} aria-hidden /> };
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
export interface ApprovalLevelViewProps {
|
|
76
87
|
/** The level in force. */
|
|
77
88
|
value: ApprovalLevel;
|
|
@@ -100,13 +111,11 @@ export interface ApprovalLevelViewProps {
|
|
|
100
111
|
|
|
101
112
|
/**
|
|
102
113
|
* THE way a level is shown, read-only or editable, by one prop: the level's
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* Editable, each icon has its level's name as its accessible name and a
|
|
109
|
-
* tooltip with what the level means (hover or keyboard focus; a tap selects).
|
|
114
|
+
* icon in its colour, then its name (read-only, Figma 1608-49531), or its name
|
|
115
|
+
* in its colour, then the four levels' icons in a compact `TabSelector` strip,
|
|
116
|
+
* the one in force filled (editable, Figma 4365-22670). Name, icon and colour
|
|
117
|
+
* are `APPROVAL_LEVEL_META`'s in both. Editable, each icon has its level's name
|
|
118
|
+
* as its accessible name.
|
|
110
119
|
*/
|
|
111
120
|
export function ApprovalLevelView({
|
|
112
121
|
value,
|
|
@@ -124,18 +133,16 @@ export function ApprovalLevelView({
|
|
|
124
133
|
return <ApprovalLevelSkeleton editable={canEdit} showLabel={showLabel} className={className} />;
|
|
125
134
|
}
|
|
126
135
|
const current = APPROVAL_LEVEL_META[value];
|
|
127
|
-
const name = showLabel ? <span className={cn(LEVEL_NAME_CLASS, current.toneClassName)}>{current.label}</span> : null;
|
|
128
136
|
|
|
129
137
|
if (!canEdit) {
|
|
130
138
|
const icon = (
|
|
131
|
-
<span className={cn('inline-flex shrink-0', current.toneClassName)}>
|
|
139
|
+
<span className={cn('inline-flex shrink-0', READ_ONLY_ICON_BOX_CLASS, current.toneClassName)}>
|
|
132
140
|
<current.Icon size={ICON_PX} aria-hidden />
|
|
133
141
|
{!showLabel && <span className="sr-only">{current.label}</span>}
|
|
134
142
|
</span>
|
|
135
143
|
);
|
|
136
144
|
return (
|
|
137
145
|
<span className={cn(READ_ONLY_ROW_CLASS, className)}>
|
|
138
|
-
{name}
|
|
139
146
|
{showLabel ? (
|
|
140
147
|
icon
|
|
141
148
|
) : (
|
|
@@ -143,49 +150,32 @@ export function ApprovalLevelView({
|
|
|
143
150
|
{icon}
|
|
144
151
|
</TouchFriendlyTooltip>
|
|
145
152
|
)}
|
|
153
|
+
{showLabel && <span className={READ_ONLY_NAME_CLASS}>{current.label}</span>}
|
|
146
154
|
</span>
|
|
147
155
|
);
|
|
148
156
|
}
|
|
149
157
|
|
|
158
|
+
const name = showLabel ? (
|
|
159
|
+
<span className={cn(LEVEL_NAME_CLASS, EDITABLE_NAME_ORDER_CLASS, current.toneClassName)}>{current.label}</span>
|
|
160
|
+
) : null;
|
|
161
|
+
|
|
150
162
|
return (
|
|
151
163
|
<div className={cn(EDITABLE_ROW_CLASS, className)}>
|
|
152
164
|
{name}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
variant="
|
|
157
|
-
size="
|
|
165
|
+
{/* The settings design's segmented control: one strip, the level in force filled (Figma 4365-22670). */}
|
|
166
|
+
<div role="group" aria-labelledby={labelledBy}>
|
|
167
|
+
<TabSelector
|
|
168
|
+
variant="secondary"
|
|
169
|
+
size="compact"
|
|
158
170
|
value={value}
|
|
159
171
|
disabled={disabled}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (next && next !== value) onChange?.(next as ApprovalLevel);
|
|
172
|
+
onValueChange={next => {
|
|
173
|
+
if (next !== value) onChange?.(next as ApprovalLevel);
|
|
163
174
|
}}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return (
|
|
169
|
-
// A plain tooltip (hover and keyboard focus): a tap must SELECT the level, and on
|
|
170
|
-
// touch the name beside the group says what is in force.
|
|
171
|
-
<Tooltip key={level}>
|
|
172
|
-
<TooltipTrigger asChild>
|
|
173
|
-
<ToggleGroupItem
|
|
174
|
-
value={level}
|
|
175
|
-
aria-label={label}
|
|
176
|
-
className={cn(LEVEL_BUTTON_CLASS, level === value && toneClassName)}
|
|
177
|
-
>
|
|
178
|
-
<Icon size={ICON_PX} aria-hidden />
|
|
179
|
-
</ToggleGroupItem>
|
|
180
|
-
</TooltipTrigger>
|
|
181
|
-
<TooltipContent side="top" className="max-w-xs">
|
|
182
|
-
<b>{label}</b>: {hint}
|
|
183
|
-
</TooltipContent>
|
|
184
|
-
</Tooltip>
|
|
185
|
-
);
|
|
186
|
-
})}
|
|
187
|
-
</ToggleGroup>
|
|
188
|
-
</TooltipProvider>
|
|
175
|
+
items={levelItems(value)}
|
|
176
|
+
className="w-auto"
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
189
179
|
</div>
|
|
190
180
|
);
|
|
191
181
|
}
|
|
@@ -200,35 +190,41 @@ interface ApprovalLevelSkeletonProps {
|
|
|
200
190
|
|
|
201
191
|
/**
|
|
202
192
|
* `ApprovalLevelView`'s own skeleton (a level chip, not a whole view): a level while it is read: the view's own row, the name a bar at its type step (the longest name's width), and the
|
|
203
|
-
* read-only icon's box or the editable
|
|
193
|
+
* read-only icon's box or the editable strip itself, disabled. Inert: nothing in it can be picked.
|
|
204
194
|
*/
|
|
205
195
|
function ApprovalLevelSkeleton({ editable = false, showLabel = true, className }: ApprovalLevelSkeletonProps) {
|
|
206
|
-
const name = showLabel ? (
|
|
207
|
-
<span className={LEVEL_NAME_CLASS}>
|
|
208
|
-
<TextHold>{LONGEST_LEVEL_LABEL}</TextHold>
|
|
209
|
-
</span>
|
|
210
|
-
) : null;
|
|
211
196
|
if (!editable) {
|
|
212
197
|
return (
|
|
213
198
|
<span aria-hidden className={cn(READ_ONLY_ROW_CLASS, className)}>
|
|
214
|
-
{
|
|
215
|
-
|
|
199
|
+
<span className={cn('inline-flex shrink-0', READ_ONLY_ICON_BOX_CLASS)}>
|
|
200
|
+
<UnifiedSkeleton as="span" className="inline-block shrink-0 rounded-sm" width={ICON_PX} height={ICON_PX} />
|
|
201
|
+
</span>
|
|
202
|
+
{showLabel && (
|
|
203
|
+
<span className={READ_ONLY_NAME_CLASS}>
|
|
204
|
+
<TextHold>{LONGEST_LEVEL_LABEL}</TextHold>
|
|
205
|
+
</span>
|
|
206
|
+
)}
|
|
216
207
|
</span>
|
|
217
208
|
);
|
|
218
209
|
}
|
|
210
|
+
const name = showLabel ? (
|
|
211
|
+
<span className={cn(LEVEL_NAME_CLASS, EDITABLE_NAME_ORDER_CLASS)}>
|
|
212
|
+
<TextHold>{LONGEST_LEVEL_LABEL}</TextHold>
|
|
213
|
+
</span>
|
|
214
|
+
) : null;
|
|
219
215
|
return (
|
|
220
216
|
<div aria-hidden inert className={cn(EDITABLE_ROW_CLASS, className)}>
|
|
221
217
|
{name}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
218
|
+
{/* The real strip, inert, with no level in force yet. */}
|
|
219
|
+
<TabSelector
|
|
220
|
+
variant="secondary"
|
|
221
|
+
size="compact"
|
|
222
|
+
value=""
|
|
223
|
+
disabled
|
|
224
|
+
onValueChange={() => {}}
|
|
225
|
+
items={levelItems(undefined)}
|
|
226
|
+
className="w-auto"
|
|
227
|
+
/>
|
|
232
228
|
</div>
|
|
233
229
|
);
|
|
234
230
|
}
|