@usecrow/ui 0.1.50 → 0.1.51

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/index.d.cts CHANGED
@@ -201,6 +201,10 @@ interface WidgetConfigResponse {
201
201
  label: string;
202
202
  message: string;
203
203
  }>;
204
+ /** Per-tool consent settings from dashboard (shield icon) */
205
+ toolConsentSettings?: Record<string, {
206
+ requires_consent: boolean;
207
+ }>;
204
208
  }
205
209
 
206
210
  /**
@@ -649,6 +653,10 @@ interface UseChatOptions {
649
653
  welcomeMessage?: string;
650
654
  /** AI model to use for this chat (defaults to DEFAULT_MODEL) */
651
655
  selectedModel?: string;
656
+ /** Per-tool consent settings — when a tool has requires_consent, show Allow/Deny before executing */
657
+ toolConsentSettings?: Record<string, {
658
+ requires_consent: boolean;
659
+ }>;
652
660
  onVerificationStatus?: (isVerified: boolean) => void;
653
661
  onConversationId?: (id: string) => void;
654
662
  onWorkflowEvent?: (event: WorkflowEvent) => void;
@@ -656,7 +664,7 @@ interface UseChatOptions {
656
664
  onToolResult?: (toolName: string, result: Record<string, unknown>) => void;
657
665
  onRestoredConversation?: (conversationId: string) => void;
658
666
  }
659
- declare function useChat({ productId, apiUrl, persistAnonymousConversations, welcomeMessage, selectedModel: initialSelectedModel, onVerificationStatus, onConversationId, onWorkflowEvent, onToolCall, onToolResult, onRestoredConversation, }: UseChatOptions): {
667
+ declare function useChat({ productId, apiUrl, persistAnonymousConversations, welcomeMessage, selectedModel: initialSelectedModel, toolConsentSettings, onVerificationStatus, onConversationId, onWorkflowEvent, onToolCall, onToolResult, onRestoredConversation, }: UseChatOptions): {
660
668
  messages: Message[];
661
669
  isLoading: boolean;
662
670
  activeToolCalls: ToolCall[];
@@ -781,6 +789,10 @@ interface UseWidgetStylesResult {
781
789
  label: string;
782
790
  message: string;
783
791
  }>;
792
+ /** Per-tool consent settings from dashboard */
793
+ toolConsentSettings: Record<string, {
794
+ requires_consent: boolean;
795
+ }>;
784
796
  /** Refetch styles from API */
785
797
  refetch: () => Promise<void>;
786
798
  }
@@ -822,6 +834,10 @@ interface UseCopilotStylesResult {
822
834
  welcomeMessage: string | undefined;
823
835
  /** AI model configured for this product */
824
836
  selectedModel: string | undefined;
837
+ /** Per-tool consent settings from dashboard */
838
+ toolConsentSettings: Record<string, {
839
+ requires_consent: boolean;
840
+ }>;
825
841
  /** Refetch styles from API */
826
842
  refetch: () => Promise<void>;
827
843
  }
package/dist/index.d.ts CHANGED
@@ -201,6 +201,10 @@ interface WidgetConfigResponse {
201
201
  label: string;
202
202
  message: string;
203
203
  }>;
204
+ /** Per-tool consent settings from dashboard (shield icon) */
205
+ toolConsentSettings?: Record<string, {
206
+ requires_consent: boolean;
207
+ }>;
204
208
  }
205
209
 
206
210
  /**
@@ -649,6 +653,10 @@ interface UseChatOptions {
649
653
  welcomeMessage?: string;
650
654
  /** AI model to use for this chat (defaults to DEFAULT_MODEL) */
651
655
  selectedModel?: string;
656
+ /** Per-tool consent settings — when a tool has requires_consent, show Allow/Deny before executing */
657
+ toolConsentSettings?: Record<string, {
658
+ requires_consent: boolean;
659
+ }>;
652
660
  onVerificationStatus?: (isVerified: boolean) => void;
653
661
  onConversationId?: (id: string) => void;
654
662
  onWorkflowEvent?: (event: WorkflowEvent) => void;
@@ -656,7 +664,7 @@ interface UseChatOptions {
656
664
  onToolResult?: (toolName: string, result: Record<string, unknown>) => void;
657
665
  onRestoredConversation?: (conversationId: string) => void;
658
666
  }
659
- declare function useChat({ productId, apiUrl, persistAnonymousConversations, welcomeMessage, selectedModel: initialSelectedModel, onVerificationStatus, onConversationId, onWorkflowEvent, onToolCall, onToolResult, onRestoredConversation, }: UseChatOptions): {
667
+ declare function useChat({ productId, apiUrl, persistAnonymousConversations, welcomeMessage, selectedModel: initialSelectedModel, toolConsentSettings, onVerificationStatus, onConversationId, onWorkflowEvent, onToolCall, onToolResult, onRestoredConversation, }: UseChatOptions): {
660
668
  messages: Message[];
661
669
  isLoading: boolean;
662
670
  activeToolCalls: ToolCall[];
@@ -781,6 +789,10 @@ interface UseWidgetStylesResult {
781
789
  label: string;
782
790
  message: string;
783
791
  }>;
792
+ /** Per-tool consent settings from dashboard */
793
+ toolConsentSettings: Record<string, {
794
+ requires_consent: boolean;
795
+ }>;
784
796
  /** Refetch styles from API */
785
797
  refetch: () => Promise<void>;
786
798
  }
@@ -822,6 +834,10 @@ interface UseCopilotStylesResult {
822
834
  welcomeMessage: string | undefined;
823
835
  /** AI model configured for this product */
824
836
  selectedModel: string | undefined;
837
+ /** Per-tool consent settings from dashboard */
838
+ toolConsentSettings: Record<string, {
839
+ requires_consent: boolean;
840
+ }>;
825
841
  /** Refetch styles from API */
826
842
  refetch: () => Promise<void>;
827
843
  }
package/dist/index.js CHANGED
@@ -44,6 +44,7 @@ function useChat({
44
44
  persistAnonymousConversations,
45
45
  welcomeMessage,
46
46
  selectedModel: initialSelectedModel,
47
+ toolConsentSettings,
47
48
  onVerificationStatus,
48
49
  onConversationId,
49
50
  onWorkflowEvent,
@@ -68,6 +69,8 @@ function useChat({
68
69
  const abortControllerRef = useRef(null);
69
70
  const hasCheckedPersistRef = useRef(false);
70
71
  const streamingToolCallsRef = useRef([]);
72
+ const toolConsentSettingsRef = useRef(toolConsentSettings);
73
+ toolConsentSettingsRef.current = toolConsentSettings;
71
74
  useEffect(() => {
72
75
  if (initialSelectedModel) {
73
76
  setSelectedModel((prev) => prev !== initialSelectedModel ? initialSelectedModel : prev);
@@ -319,26 +322,43 @@ function useChat({
319
322
  }
320
323
  break;
321
324
  case "client_tool_call":
322
- onToolCall?.({
323
- type: "start",
324
- toolName: parsed.tool_name,
325
- arguments: parsed.arguments
326
- });
327
- const clientToolCall = {
328
- id: parsed.tool_call_id || `tool-${Date.now()}`,
329
- name: parsed.tool_name,
330
- displayName: parsed.display_name || void 0,
331
- arguments: parsed.arguments || {},
332
- status: "executing",
333
- timestamp: /* @__PURE__ */ new Date()
334
- };
335
- streamingToolCallsRef.current = [...streamingToolCallsRef.current, clientToolCall];
336
- setActiveToolCalls((prev) => [...prev, clientToolCall]);
337
- pendingClientTools.push({
338
- toolName: parsed.tool_name,
339
- toolCallId: parsed.tool_call_id,
340
- arguments: parsed.arguments
341
- });
325
+ {
326
+ const needsConsent = toolConsentSettingsRef.current?.[parsed.tool_name]?.requires_consent === true;
327
+ onToolCall?.({
328
+ type: "start",
329
+ toolName: parsed.tool_name,
330
+ arguments: parsed.arguments
331
+ });
332
+ if (needsConsent) {
333
+ const consentClientTc = {
334
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
335
+ name: parsed.tool_name,
336
+ displayName: parsed.display_name || void 0,
337
+ arguments: parsed.arguments || {},
338
+ status: "awaiting_consent",
339
+ requiresConsent: true,
340
+ timestamp: /* @__PURE__ */ new Date()
341
+ };
342
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, consentClientTc];
343
+ setActiveToolCalls((prev) => [...prev, consentClientTc]);
344
+ } else {
345
+ const clientToolCall = {
346
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
347
+ name: parsed.tool_name,
348
+ displayName: parsed.display_name || void 0,
349
+ arguments: parsed.arguments || {},
350
+ status: "executing",
351
+ timestamp: /* @__PURE__ */ new Date()
352
+ };
353
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, clientToolCall];
354
+ setActiveToolCalls((prev) => [...prev, clientToolCall]);
355
+ pendingClientTools.push({
356
+ toolName: parsed.tool_name,
357
+ toolCallId: parsed.tool_call_id,
358
+ arguments: parsed.arguments
359
+ });
360
+ }
361
+ }
342
362
  break;
343
363
  case "tool_consent_required":
344
364
  onToolCall?.({
@@ -720,21 +740,36 @@ function useChat({
720
740
  break;
721
741
  case "client_tool_call":
722
742
  {
723
- const toolCallEntry = {
724
- id: parsed.tool_call_id || `tool-${Date.now()}`,
725
- name: parsed.tool_name,
726
- displayName: parsed.display_name || void 0,
727
- arguments: parsed.arguments || {},
728
- status: "executing",
729
- timestamp: /* @__PURE__ */ new Date()
730
- };
731
- streamingToolCallsRef.current = [...streamingToolCallsRef.current, toolCallEntry];
732
- setActiveToolCalls((prev) => [...prev, toolCallEntry]);
733
- pendingClientTools.push({
734
- toolName: parsed.tool_name,
735
- toolCallId: parsed.tool_call_id,
736
- arguments: parsed.arguments
737
- });
743
+ const needsConsent2 = toolConsentSettingsRef.current?.[parsed.tool_name]?.requires_consent === true;
744
+ if (needsConsent2) {
745
+ const consentEntry2 = {
746
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
747
+ name: parsed.tool_name,
748
+ displayName: parsed.display_name || void 0,
749
+ arguments: parsed.arguments || {},
750
+ status: "awaiting_consent",
751
+ requiresConsent: true,
752
+ timestamp: /* @__PURE__ */ new Date()
753
+ };
754
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, consentEntry2];
755
+ setActiveToolCalls((prev) => [...prev, consentEntry2]);
756
+ } else {
757
+ const toolCallEntry = {
758
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
759
+ name: parsed.tool_name,
760
+ displayName: parsed.display_name || void 0,
761
+ arguments: parsed.arguments || {},
762
+ status: "executing",
763
+ timestamp: /* @__PURE__ */ new Date()
764
+ };
765
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, toolCallEntry];
766
+ setActiveToolCalls((prev) => [...prev, toolCallEntry]);
767
+ pendingClientTools.push({
768
+ toolName: parsed.tool_name,
769
+ toolCallId: parsed.tool_call_id,
770
+ arguments: parsed.arguments
771
+ });
772
+ }
738
773
  }
739
774
  break;
740
775
  case "tool_consent_required":
@@ -1501,6 +1536,9 @@ function useWidgetStyles({
1501
1536
  const [initialSuggestions, setInitialSuggestions] = useState(
1502
1537
  styleCache.get(key)?.initialSuggestions || []
1503
1538
  );
1539
+ const [toolConsentSettings, setToolConsentSettings] = useState(
1540
+ styleCache.get(key)?.toolConsentSettings || {}
1541
+ );
1504
1542
  const [agentName, setAgentName] = useState(
1505
1543
  styleCache.get(key)?.agentName || "Assistant"
1506
1544
  );
@@ -1541,6 +1579,7 @@ function useWidgetStyles({
1541
1579
  setWelcomeMessage(config.welcomeMessage ?? void 0);
1542
1580
  setSelectedModel(config.model ?? void 0);
1543
1581
  setInitialSuggestions(config.initialSuggestions || []);
1582
+ setToolConsentSettings(config.toolConsentSettings || {});
1544
1583
  } catch (err) {
1545
1584
  console.error("[CrowWidget] Failed to fetch styles:", err);
1546
1585
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -1579,6 +1618,7 @@ function useWidgetStyles({
1579
1618
  welcomeMessage,
1580
1619
  selectedModel,
1581
1620
  initialSuggestions,
1621
+ toolConsentSettings,
1582
1622
  refetch: fetchStyles
1583
1623
  };
1584
1624
  }
@@ -1614,6 +1654,9 @@ function useCopilotStyles({
1614
1654
  const [selectedModel, setSelectedModel] = useState(
1615
1655
  styleCache.get(key)?.model ?? void 0
1616
1656
  );
1657
+ const [toolConsentSettings, setToolConsentSettings] = useState(
1658
+ styleCache.get(key)?.toolConsentSettings || {}
1659
+ );
1617
1660
  const hasFetchedRef = useRef(false);
1618
1661
  const fetchStyles = async () => {
1619
1662
  if (skip) return;
@@ -1630,6 +1673,7 @@ function useCopilotStyles({
1630
1673
  setPersistAnonymousConversations(config.persistAnonymousConversations ?? true);
1631
1674
  setWelcomeMessage(config.welcomeMessage ?? void 0);
1632
1675
  setSelectedModel(config.model ?? void 0);
1676
+ setToolConsentSettings(config.toolConsentSettings || {});
1633
1677
  } catch (err) {
1634
1678
  console.error("[CrowCopilot] Failed to fetch styles:", err);
1635
1679
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -1667,6 +1711,7 @@ function useCopilotStyles({
1667
1711
  persistAnonymousConversations,
1668
1712
  welcomeMessage,
1669
1713
  selectedModel,
1714
+ toolConsentSettings,
1670
1715
  refetch: fetchStyles
1671
1716
  };
1672
1717
  }
@@ -3322,7 +3367,8 @@ function CrowWidget({
3322
3367
  persistAnonymousConversations,
3323
3368
  welcomeMessage: welcomeMessageFromAPI,
3324
3369
  selectedModel: selectedModelFromAPI,
3325
- initialSuggestions
3370
+ initialSuggestions,
3371
+ toolConsentSettings
3326
3372
  } = useWidgetStyles({
3327
3373
  productId,
3328
3374
  apiUrl,
@@ -3365,6 +3411,7 @@ function CrowWidget({
3365
3411
  persistAnonymousConversations,
3366
3412
  welcomeMessage,
3367
3413
  selectedModel,
3414
+ toolConsentSettings,
3368
3415
  onVerificationStatus: (isVerified) => {
3369
3416
  setIsVerifiedUser(isVerified);
3370
3417
  },
@@ -3667,14 +3714,47 @@ function CrowWidget({
3667
3714
  const handleToolConsent = async (toolCallId, approved) => {
3668
3715
  const toolCall = chat.activeToolCalls.find((tc) => tc.id === toolCallId) || chat.messages.flatMap((m) => m.toolCalls || []).find((tc) => tc.id === toolCallId);
3669
3716
  if (!toolCall) return;
3717
+ const isClientSide = !toolCall.serverSideExecution;
3670
3718
  if (approved) {
3671
3719
  chat.updateToolCallStatus(toolCallId, "executing");
3672
- if (submitToolResultRef.current) {
3673
- await submitToolResultRef.current(
3674
- toolCallId,
3675
- toolCall.name,
3676
- { consent_approved: true, tool_arguments: toolCall.arguments || {} }
3677
- );
3720
+ if (isClientSide) {
3721
+ try {
3722
+ const result = await executeClientToolRef.current?.(
3723
+ toolCall.name,
3724
+ toolCall.arguments || {}
3725
+ );
3726
+ const resultObj = result;
3727
+ const dataObj = resultObj?.data;
3728
+ const wasUserCancelled = dataObj?.declined === true || typeof resultObj?.error === "string" && resultObj.error.includes("cancelled by user") || typeof resultObj?.error === "string" && resultObj.error.includes("declined");
3729
+ if (wasUserCancelled) {
3730
+ console.log("[Crow Widget] Tool was cancelled by user after consent");
3731
+ return;
3732
+ }
3733
+ if (result && submitToolResultRef.current) {
3734
+ await submitToolResultRef.current(
3735
+ toolCallId,
3736
+ toolCall.name,
3737
+ result
3738
+ );
3739
+ }
3740
+ } catch (e) {
3741
+ console.error("[Crow Widget] Tool error after consent:", e);
3742
+ if (submitToolResultRef.current) {
3743
+ await submitToolResultRef.current(
3744
+ toolCallId,
3745
+ toolCall.name,
3746
+ { success: false, error: String(e) }
3747
+ );
3748
+ }
3749
+ }
3750
+ } else {
3751
+ if (submitToolResultRef.current) {
3752
+ await submitToolResultRef.current(
3753
+ toolCallId,
3754
+ toolCall.name,
3755
+ { consent_approved: true, tool_arguments: toolCall.arguments || {} }
3756
+ );
3757
+ }
3678
3758
  }
3679
3759
  } else {
3680
3760
  chat.updateToolCallStatus(toolCallId, "denied");
@@ -4103,7 +4183,8 @@ function CrowCopilot({
4103
4183
  pageNavigationRoutes,
4104
4184
  persistAnonymousConversations,
4105
4185
  welcomeMessage: welcomeMessageFromAPI,
4106
- selectedModel
4186
+ selectedModel,
4187
+ toolConsentSettings
4107
4188
  } = useCopilotStyles({
4108
4189
  productId,
4109
4190
  apiUrl,
@@ -4294,6 +4375,7 @@ function CrowCopilot({
4294
4375
  persistAnonymousConversations,
4295
4376
  welcomeMessage,
4296
4377
  selectedModel,
4378
+ toolConsentSettings,
4297
4379
  onVerificationStatus: (isVerified) => {
4298
4380
  setIsVerifiedUser(isVerified);
4299
4381
  },
@@ -4492,14 +4574,54 @@ function CrowCopilot({
4492
4574
  const handleToolConsent = async (toolCallId, approved) => {
4493
4575
  const toolCall = chat.activeToolCalls.find((tc) => tc.id === toolCallId) || chat.messages.flatMap((m) => m.toolCalls || []).find((tc) => tc.id === toolCallId);
4494
4576
  if (!toolCall) return;
4577
+ const isClientSide = !toolCall.serverSideExecution;
4495
4578
  if (approved) {
4496
4579
  chat.updateToolCallStatus(toolCallId, "executing");
4497
- if (submitToolResultRef.current) {
4498
- await submitToolResultRef.current(
4499
- toolCallId,
4500
- toolCall.name,
4501
- { consent_approved: true, tool_arguments: toolCall.arguments || {} }
4502
- );
4580
+ if (isClientSide) {
4581
+ try {
4582
+ const result = await executeClientToolRef.current?.(
4583
+ toolCall.name,
4584
+ toolCall.arguments || {}
4585
+ );
4586
+ const resultObj = result;
4587
+ const dataObj = resultObj?.data;
4588
+ const wasUserCancelled = dataObj?.declined === true || typeof resultObj?.error === "string" && resultObj.error.includes("cancelled by user") || typeof resultObj?.error === "string" && resultObj.error.includes("declined");
4589
+ if (wasUserCancelled) {
4590
+ console.log("[Crow Copilot] Tool was cancelled by user after consent");
4591
+ if (submitToolResultRef.current) {
4592
+ await submitToolResultRef.current(
4593
+ toolCallId,
4594
+ toolCall.name,
4595
+ { success: false, cancelled: true, error: "Action was cancelled by the user." }
4596
+ );
4597
+ }
4598
+ return;
4599
+ }
4600
+ if (result && submitToolResultRef.current) {
4601
+ await submitToolResultRef.current(
4602
+ toolCallId,
4603
+ toolCall.name,
4604
+ result
4605
+ );
4606
+ }
4607
+ } catch (e) {
4608
+ console.error("[Crow Copilot] Tool error after consent:", e);
4609
+ if (submitToolResultRef.current) {
4610
+ await submitToolResultRef.current(
4611
+ toolCallId,
4612
+ toolCall.name,
4613
+ { success: false, error: String(e) }
4614
+ );
4615
+ }
4616
+ }
4617
+ } else {
4618
+ if (submitToolResultRef.current) {
4619
+ await submitToolResultRef.current(
4620
+ toolCallId,
4621
+ toolCall.name,
4622
+ { consent_approved: true, tool_arguments: toolCall.arguments || {} }
4623
+ );
4624
+ }
4503
4625
  }
4504
4626
  } else {
4505
4627
  chat.updateToolCallStatus(toolCallId, "denied");