@agent-native/core 0.171.1 → 0.172.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +4 -5
  3. package/corpus/templates/calendar/actions/create-event.ts +11 -3
  4. package/corpus/templates/calendar/app/components/calendar/AddCalendarDialog.tsx +56 -51
  5. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +3 -0
  6. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +1 -1
  7. package/corpus/templates/calendar/app/components/calendar/PeopleSearchDialog.tsx +54 -51
  8. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +1 -1
  9. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -1
  10. package/corpus/templates/calendar/app/hooks/use-people.ts +23 -13
  11. package/corpus/templates/calendar/app/lib/event-layout.ts +21 -6
  12. package/corpus/templates/calendar/app/pages/CalendarView.tsx +3 -0
  13. package/corpus/templates/calendar/changelog/2026-08-24-overlapping-events-use-space-better.md +5 -0
  14. package/corpus/templates/calendar/server/lib/google-api.ts +9 -0
  15. package/corpus/templates/calendar/server/lib/google-calendar.ts +59 -2
  16. package/corpus/templates/calendar/server/lib/people-search.ts +43 -28
  17. package/corpus/templates/calendar/shared/api.ts +2 -0
  18. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +3 -3
  19. package/corpus/templates/mail/app/root.tsx +29 -0
  20. package/dist/agent/actions/set-tool-approval-policy.d.ts +8 -0
  21. package/dist/agent/actions/set-tool-approval-policy.js +26 -0
  22. package/dist/agent/harness/migrations.js +8 -0
  23. package/dist/agent/harness/runner.js +114 -33
  24. package/dist/agent/harness/store.d.ts +9 -0
  25. package/dist/agent/harness/store.js +87 -23
  26. package/dist/agent/production-agent.d.ts +3 -0
  27. package/dist/agent/production-agent.js +23 -1
  28. package/dist/agent/run-manager.js +40 -9
  29. package/dist/agent/tool-approval-migrations.d.ts +21 -0
  30. package/dist/agent/tool-approval-migrations.js +32 -0
  31. package/dist/agent/tool-approval-store.d.ts +11 -0
  32. package/dist/agent/tool-approval-store.js +76 -1
  33. package/dist/cli/code-agent-executor.js +20 -3
  34. package/dist/client/AgentPanel.d.ts +4 -1
  35. package/dist/client/AgentPanel.js +11 -3
  36. package/dist/client/AssistantChat.d.ts +5 -5
  37. package/dist/client/AssistantChat.js +24 -11
  38. package/dist/client/DefaultSpinner.d.ts +3 -1
  39. package/dist/client/DefaultSpinner.js +2 -2
  40. package/dist/client/chat/tool-call-display.d.ts +5 -5
  41. package/dist/client/chat/tool-call-display.js +27 -11
  42. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  43. package/dist/client/chat-first/apps-rail.js +11 -8
  44. package/dist/client/chat-first/copy.js +1 -0
  45. package/dist/client/chat-first/types.d.ts +1 -0
  46. package/dist/client/resources/McpIntegrationDialog.js +5 -5
  47. package/dist/client/resources/mcp-integration-catalog.js +1 -1
  48. package/dist/coding-tools/index.d.ts +31 -0
  49. package/dist/coding-tools/index.js +139 -3
  50. package/dist/collab/struct-routes.d.ts +1 -1
  51. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  52. package/dist/localization/core-messages/ar-SA.js +3 -0
  53. package/dist/localization/core-messages/de-DE.js +3 -0
  54. package/dist/localization/core-messages/en-US.d.ts +3 -0
  55. package/dist/localization/core-messages/en-US.js +3 -0
  56. package/dist/localization/core-messages/es-ES.js +3 -0
  57. package/dist/localization/core-messages/fr-FR.js +3 -0
  58. package/dist/localization/core-messages/hi-IN.js +3 -0
  59. package/dist/localization/core-messages/ja-JP.js +3 -0
  60. package/dist/localization/core-messages/ko-KR.js +3 -0
  61. package/dist/localization/core-messages/pt-BR.js +3 -0
  62. package/dist/localization/core-messages/zh-CN.js +3 -0
  63. package/dist/localization/core-messages/zh-TW.js +3 -0
  64. package/dist/localization/core-messages.d.ts +3 -0
  65. package/dist/localization/default-messages.d.ts +1 -0
  66. package/dist/localization/default-messages.js +7 -6
  67. package/dist/notifications/routes.d.ts +1 -1
  68. package/dist/progress/routes.d.ts +1 -1
  69. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  70. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  71. package/dist/server/action-discovery.js +5 -0
  72. package/dist/server/agent-chat/prompt-resources.d.ts +9 -0
  73. package/dist/server/agent-chat/prompt-resources.js +12 -2
  74. package/dist/server/realtime-token.d.ts +1 -1
  75. package/dist/server/release-schema.js +4 -1
  76. package/dist/triggers/dispatcher.d.ts +24 -0
  77. package/dist/triggers/dispatcher.js +76 -18
  78. package/dist/vite/action-types-plugin.js +4 -0
  79. package/package.json +2 -1
@@ -74,6 +74,12 @@ const AgentTerminal = lazy(() => import("./terminal/index.js").then((m) => ({ de
74
74
  const AGENT_PANEL_PREPARE_EVENT = "agent-panel:prepare";
75
75
  const AGENT_PANEL_SET_MODE_EVENT = "agent-panel:set-mode";
76
76
  const AGENT_PANEL_OPEN_SETTINGS_EVENT = "agent-panel:open-settings";
77
+ export function shouldHandleAgentSidebarToggle(event, toggleScopeId) {
78
+ const detail = event.detail;
79
+ if (!detail || detail.scopeId === undefined)
80
+ return true;
81
+ return typeof detail.scopeId === "string" && detail.scopeId === toggleScopeId;
82
+ }
77
83
  function postPerAppChatSidebarStateToEmbeddedFrames(open) {
78
84
  const message = buildAppChatSidebarStateMessage(open);
79
85
  for (const frame of document.querySelectorAll("iframe")) {
@@ -1423,7 +1429,7 @@ export function AgentChatSurface({ mode = "panel", className, defaultMode = "cha
1423
1429
  * Wraps app content with a toggleable agent sidebar.
1424
1430
  * Use AgentToggleButton in your header to open/close it.
1425
1431
  */
1426
- export function AgentSidebar({ children, enabled = true, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, restoreActiveThread = true, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, isolateHistoryByScope = false, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
1432
+ export function AgentSidebar({ children, enabled = true, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, restoreActiveThread = true, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, toggleScopeId, isolateHistoryByScope = false, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
1427
1433
  const staticHostedHarnessEnabled = isHostedHarnessConfigured(injectedAgentNativeConfig().harness);
1428
1434
  const hostedHarnessQuery = useActionQuery("get-hosted-harness-config", undefined, { enabled: staticHostedHarnessEnabled });
1429
1435
  const hostedHarnessStatus = hostedHarnessQuery.data;
@@ -1729,7 +1735,9 @@ export function AgentSidebar({ children, enabled = true, emptyStateText = "How c
1729
1735
  };
1730
1736
  }, [setOpenPersisted]);
1731
1737
  useEffect(() => {
1732
- const toggleHandler = () => {
1738
+ const toggleHandler = (event) => {
1739
+ if (!shouldHandleAgentSidebarToggle(event, toggleScopeId))
1740
+ return;
1733
1741
  if (isPerAppChatHosted) {
1734
1742
  requestPerAppChatCommand("toggle");
1735
1743
  return;
@@ -1774,7 +1782,7 @@ export function AgentSidebar({ children, enabled = true, emptyStateText = "How c
1774
1782
  window.removeEventListener("agent-panel:open", openHandler);
1775
1783
  window.removeEventListener("agent-panel:close", closeHandler);
1776
1784
  };
1777
- }, [setOpenPersisted, frameCodeMode, isPerAppChatHosted]);
1785
+ }, [setOpenPersisted, frameCodeMode, isPerAppChatHosted, toggleScopeId]);
1778
1786
  // Listen for sidebar mode commands from the frame parent.
1779
1787
  // When frame is in "code" mode, hide the app sidebar.
1780
1788
  // When frame is in "app" mode, show the app sidebar, sync width and panel mode.
@@ -438,14 +438,14 @@ export interface AssistantChatProps {
438
438
  externalStreaming?: boolean;
439
439
  /**
440
440
  * Optional host hooks for the inline `needsApproval` affordance beyond the
441
- * built-in Approve. Additive: omit entirely to keep today's default
442
- * behavior (Deny is local-only, no "Always allow" button). Code sessions
443
- * pass these through to the same `host.controlRun` commands their
444
- * standalone approval banner already uses (see CodeAgentsApp).
441
+ * built-in Approve and action-type policy. Code sessions pass their
442
+ * exact-command callback through for the standalone banner, but suppress the
443
+ * shared action-type menu (see CodeAgentsApp).
445
444
  */
446
445
  approvalActions?: {
447
446
  onDeny?: (approvalKey: string) => void;
448
- onAlwaysAllow?: (approvalKey: string) => void;
447
+ onAlwaysAllow?: (approvalKey: string, toolName: string) => void | Promise<void>;
448
+ alwaysAllowScope?: "action" | "exact-command";
449
449
  };
450
450
  }
451
451
  export declare function shouldShowAssistantChatModelSelector(showModelSelector: boolean | undefined): boolean;
@@ -38,6 +38,7 @@ import { addMcpConnectionCompleteListener, consumeMcpConnectionResume, } from ".
38
38
  import { McpConnectionSuggestion } from "./resources/McpConnectionSuggestion.js";
39
39
  import { claimRunStream, createRunStreamToken, ownsRunStream, releaseRunStream, } from "./run-stream-ownership.js";
40
40
  import { AgentAutoContinueSignal, readSSEStreamRaw, settleInterruptedToolCalls, } from "./sse-event-processor.js";
41
+ import { callAction } from "./use-action.js";
41
42
  import { useAgentEngineConfigured } from "./use-agent-engine-configured.js";
42
43
  import { appendChatThreadScopeParams, } from "./use-chat-threads.js";
43
44
  import { useDevMode } from "./use-dev-mode.js";
@@ -4268,24 +4269,36 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4268
4269
  // tool call, re-issue the turn carrying the call's approval key so the server
4269
4270
  // gate lets that specific call run. Reuses the same append path as recovery /
4270
4271
  // queued messages (no hand-written fetch).
4272
+ const approveToolCall = useCallback((approvalKey) => {
4273
+ void addToQueue("Approved. Go ahead and run the requested action.", // i18n-ignore -- stable hidden agent instruction, not UI copy.
4274
+ undefined, undefined, undefined, undefined, "queued", undefined, false, false, false, true, // hideUserMessage: this is a protocol continuation, not a new prompt
4275
+ undefined, [approvalKey]);
4276
+ }, [addToQueue]);
4277
+ const alwaysAllowToolCall = useCallback((approvalKey, toolName) => {
4278
+ if (approvalActions?.onAlwaysAllow) {
4279
+ return approvalActions.onAlwaysAllow(approvalKey, toolName);
4280
+ }
4281
+ return callAction("set-tool-approval-policy", {
4282
+ toolName,
4283
+ enabled: true,
4284
+ }).then(() => approveToolCall(approvalKey));
4285
+ }, [approvalActions, approveToolCall]);
4286
+ const showActionTypeApprovalPolicy = approvalActions?.alwaysAllowScope !== "exact-command";
4271
4287
  const approvalCtx = useMemo(() => ({
4272
4288
  getApprovalResolution,
4273
4289
  onApprovalResolved: recordApprovalResolution,
4274
- onApprove: (approvalKey) => {
4275
- void addToQueue("Approved. Go ahead and run the requested action.", // i18n-ignore -- stable hidden agent instruction, not UI copy.
4276
- undefined, undefined, undefined, undefined, "queued", undefined, false, false, false, true, // hideUserMessage: this is a protocol continuation, not a new prompt
4277
- undefined, [approvalKey]);
4278
- },
4279
- ...(approvalActions?.onDeny ? { onDeny: approvalActions.onDeny } : {}),
4280
- ...(approvalActions?.onAlwaysAllow
4281
- ? { onAlwaysAllow: approvalActions.onAlwaysAllow }
4290
+ onApprove: approveToolCall,
4291
+ ...(showActionTypeApprovalPolicy
4292
+ ? { onAlwaysAllow: alwaysAllowToolCall }
4282
4293
  : {}),
4294
+ ...(approvalActions?.onDeny ? { onDeny: approvalActions.onDeny } : {}),
4283
4295
  }), [
4284
- addToQueue,
4285
- execMode,
4286
- getApprovalResolution,
4296
+ alwaysAllowToolCall,
4287
4297
  approvalActions,
4298
+ approveToolCall,
4299
+ getApprovalResolution,
4288
4300
  recordApprovalResolution,
4301
+ showActionTypeApprovalPolicy,
4289
4302
  ]);
4290
4303
  return (_jsx(SuppressInlineOpenAppContext.Provider, { value: suppressInlineOpenApp, children: _jsx(CheckpointContext.Provider, { value: checkpointCtx, children: _jsx(MessageActionsContext.Provider, { value: messageActionsCtx, children: _jsx(ApprovalContext.Provider, { value: approvalCtx, children: _jsx(ChatRunDurationContext.Provider, { value: lastChatRunDurationMs, children: _jsx(ServerRunActiveContext.Provider, { value: serverRunActive, children: _jsx(ChatRunningRunIdContext.Provider, { value: activeChatRunId, children: _jsx(ChatRunningTurnIdContext.Provider, { value: activeChatTurnId, children: _jsx(ChatRunningContext.Provider
4291
4304
  // Keep the current message in a non-complete state while
@@ -1 +1,3 @@
1
- export declare function DefaultSpinner(): import("react").JSX.Element;
1
+ export declare function DefaultSpinner({ ariaLabel, }: {
2
+ ariaLabel?: string;
3
+ }): import("react").JSX.Element;
@@ -23,7 +23,7 @@ function isDevelopmentBuild() {
23
23
  return false;
24
24
  return viteEnv?.DEV === true;
25
25
  }
26
- export function DefaultSpinner() {
26
+ export function DefaultSpinner({ ariaLabel = "Loading", }) {
27
27
  const [showStallHint, setShowStallHint] = useState(false);
28
28
  useEffect(() => {
29
29
  setShowStallHint(isDevelopmentBuild());
@@ -36,7 +36,7 @@ export function DefaultSpinner() {
36
36
  gap: 16,
37
37
  height: "100vh",
38
38
  width: "100%",
39
- }, children: [_jsx("svg", { role: "status", "aria-label": "Loading", width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "an-spin 1s linear infinite", opacity: 0.7 }, children: _jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }), showStallHint && (_jsx("p", { className: "an-stall-hint", children: "Still loading. A first run compiles dependencies and can take a minute \u2014 if it does not finish, check the terminal running the dev server for errors." })), _jsx("style", { children: `
39
+ }, children: [_jsx("svg", { role: "status", "aria-label": ariaLabel, width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "an-spin 1s linear infinite", opacity: 0.7 }, children: _jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }), showStallHint && (_jsx("p", { className: "an-stall-hint", children: "Still loading. A first run compiles dependencies and can take a minute \u2014 if it does not finish, check the terminal running the dev server for errors." })), _jsx("style", { children: `
40
40
  @keyframes an-spin { to { transform: rotate(360deg) } }
41
41
  ${showStallHint
42
42
  ? `
@@ -23,8 +23,8 @@ export declare function ToolCallStackMotion({ children, className, }: {
23
23
  * re-issues the turn approving a specific paused tool call (opt-in
24
24
  * `needsApproval` actions). When null, the Approve button is not rendered.
25
25
  * Deny defaults to local-only (the action stays un-run) unless `onDeny` is
26
- * provided, and "Always allow" only renders when `onAlwaysAllow` is provided
27
- * — both are additive so existing action-approval consumers are unaffected.
26
+ * provided. The chevron menu persists an action-type policy before approving
27
+ * the current call.
28
28
  */
29
29
  export type ApprovalResolution = "approved" | "denied";
30
30
  export type ApprovalContextValue = {
@@ -51,10 +51,10 @@ export type ApprovalContextValue = {
51
51
  */
52
52
  onDeny?: (approvalKey: string) => void;
53
53
  /**
54
- * Optional host hook that persists this exact call so future occurrences
55
- * skip the approval gate. When absent, no "Always allow" button renders.
54
+ * Optional host hook that persists this action type and resolves the current
55
+ * call. The default AssistantChat implementation uses the shared policy.
56
56
  */
57
- onAlwaysAllow?: (approvalKey: string) => void;
57
+ onAlwaysAllow?: (approvalKey: string, toolName: string) => void | Promise<void>;
58
58
  };
59
59
  export declare const ApprovalContext: React.Context<ApprovalContextValue | null>;
60
60
  /** Pending human-in-the-loop gate still waiting for Approve/Deny. */
@@ -1,9 +1,10 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { IconLoader2, IconAlertTriangle, IconCircleX, IconCheck, IconChevronRight, IconCopy, IconCode, IconBrandSlack, IconTerminal2, IconDatabase, IconSearch, IconFileCode, IconShieldCheck, IconX, } from "@tabler/icons-react";
2
+ import { IconLoader2, IconAlertTriangle, IconCircleX, IconCheck, IconChevronDown, IconChevronRight, IconCopy, IconCode, IconBrandSlack, IconTerminal2, IconDatabase, IconSearch, IconFileCode, IconShieldCheck, IconX, } from "@tabler/icons-react";
3
3
  import React, { useState, useEffect, useCallback, useLayoutEffect, useRef, } from "react";
4
4
  import { formatAgentChatContextItemsForPrompt } from "../agent-chat.js";
5
5
  import { AgentTaskCard } from "../AgentTaskCard.js";
6
6
  import { writeClipboardText } from "../clipboard.js";
7
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../components/ui/dropdown-menu.js";
7
8
  import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
8
9
  import { ConnectBuilderCard } from "../ConnectBuilderCard.js";
9
10
  import { FileStorageSetupCard } from "../FileStorageSetupCard.js";
@@ -251,6 +252,8 @@ function ApprovalAffordance({ toolName, toolCallId, approval, }) {
251
252
  const t = useT();
252
253
  const ctx = React.useContext(ApprovalContext);
253
254
  const [localResolution, setLocalResolution] = useState(null);
255
+ const [isAlwaysAllowing, setIsAlwaysAllowing] = useState(false);
256
+ const [alwaysAllowFailed, setAlwaysAllowFailed] = useState(false);
254
257
  const retainedResolution = ctx?.getApprovalResolution?.(approval.approvalKey, toolCallId, approval.askId) ?? null;
255
258
  const resolution = retainedResolution ??
256
259
  localResolution ??
@@ -266,19 +269,32 @@ function ApprovalAffordance({ toolName, toolCallId, approval, }) {
266
269
  if (resolution === "denied") {
267
270
  return (_jsx("div", { className: "mt-1.5 text-xs text-muted-foreground", children: t("agentChat.approval.denied", { tool: toolName }) }));
268
271
  }
269
- return (_jsxs("div", { className: "mt-1.5 flex min-w-0 flex-wrap items-center gap-2 rounded-md border border-border bg-muted/40 px-2.5 py-1.5", children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 text-xs text-muted-foreground", children: t("agentChat.approval.question", { tool: toolName }) }), ctx && (_jsxs("button", { type: "button", onClick: () => {
270
- setLocalResolution("approved");
271
- ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId, approval.askId);
272
- ctx.onApprove(approval.approvalKey);
273
- }, className: cn("inline-flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), t("agentChat.approval.approve")] })), ctx?.onAlwaysAllow && (_jsxs("button", { type: "button", onClick: () => {
274
- setLocalResolution("approved");
275
- ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId, approval.askId);
276
- ctx.onAlwaysAllow?.(approval.approvalKey);
277
- }, title: t("agentChat.approval.alwaysAllowHint"), className: cn("inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5" }), t("agentChat.approval.alwaysAllow")] })), _jsxs("button", { type: "button", onClick: () => {
272
+ const handleAlwaysAllow = async () => {
273
+ if (!ctx?.onAlwaysAllow || isAlwaysAllowing)
274
+ return;
275
+ setIsAlwaysAllowing(true);
276
+ setAlwaysAllowFailed(false);
277
+ try {
278
+ await ctx.onAlwaysAllow(approval.approvalKey, toolName);
279
+ setLocalResolution("approved");
280
+ ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId, approval.askId);
281
+ }
282
+ catch {
283
+ setAlwaysAllowFailed(true);
284
+ }
285
+ finally {
286
+ setIsAlwaysAllowing(false);
287
+ }
288
+ };
289
+ return (_jsxs("div", { className: "mt-1.5 flex min-w-0 flex-wrap items-center gap-2 rounded-md border border-border bg-muted/40 px-2.5 py-1.5", children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 text-xs text-muted-foreground", children: t("agentChat.approval.question", { tool: toolName }) }), ctx && (_jsxs("div", { className: "inline-flex shrink-0 items-stretch", children: [_jsxs("button", { type: "button", disabled: isAlwaysAllowing, onClick: () => {
290
+ setLocalResolution("approved");
291
+ ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId, approval.askId);
292
+ ctx.onApprove(approval.approvalKey);
293
+ }, className: cn("inline-flex shrink-0 items-center gap-1 px-2.5 py-1 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", ctx.onAlwaysAllow ? "rounded-s-md rounded-e-none" : "rounded-md", "disabled:pointer-events-none disabled:opacity-50"), children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), t("agentChat.approval.approve")] }), ctx.onAlwaysAllow && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { type: "button", disabled: isAlwaysAllowing, "aria-label": t("agentChat.approval.moreOptions"), title: t("agentChat.approval.moreOptions"), className: cn("inline-flex w-7 shrink-0 items-center justify-center rounded-s-none rounded-e-md border-s border-background/25 bg-foreground text-background transition-colors hover:bg-foreground/90", "disabled:pointer-events-none disabled:opacity-50"), children: _jsx(IconChevronDown, { className: "h-3.5 w-3.5" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: _jsxs(DropdownMenuItem, { onSelect: () => void handleAlwaysAllow(), title: t("agentChat.approval.alwaysAllowActionHint"), children: [_jsx(IconShieldCheck, { className: "h-4 w-4" }), t("agentChat.approval.alwaysAllowAction")] }) })] }))] })), _jsxs("button", { type: "button", disabled: isAlwaysAllowing, onClick: () => {
278
294
  setLocalResolution("denied");
279
295
  ctx?.onApprovalResolved?.(approval.approvalKey, "denied", toolCallId, approval.askId);
280
296
  ctx?.onDeny?.(approval.approvalKey);
281
- }, className: cn("inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconX, { className: "h-3.5 w-3.5" }), t("agentChat.approval.deny")] })] }));
297
+ }, className: cn("inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted", "disabled:pointer-events-none disabled:opacity-50"), children: [_jsx(IconX, { className: "h-3.5 w-3.5" }), t("agentChat.approval.deny")] }), alwaysAllowFailed && (_jsx("span", { role: "alert", className: "basis-full text-xs text-destructive", children: t("agentChat.common.saveFailed") }))] }));
282
298
  }
283
299
  // ─── ToolCallDisplay ──────────────────────────────────────────────────────────
284
300
  export function ToolCallDisplay({ toolName, toolCallId, argsText, args, result, mcpApp, chatUI, isRunning, outcome, structuredMeta, activity, approval, repeatCount, isLatestRunning = isRunning, isActiveTail, }) {
@@ -1,3 +1,3 @@
1
1
  import type { ChatFirstAppRailProps } from "./types.js";
2
2
  export declare const CHAT_FIRST_APP_RAIL_SHOW_ALL_STORAGE_KEY = "agent-native:chat-first-app-rail-show-all:v1";
3
- export declare const ChatFirstAppsRail: import("react").MemoExoticComponent<({ apps, defaultAppIds, activeAppId, loading, error, collapsed, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onRemoveApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy, }: ChatFirstAppRailProps) => import("react").JSX.Element>;
3
+ export declare const ChatFirstAppsRail: import("react").MemoExoticComponent<({ apps, defaultAppIds, activeAppId, loading, error, collapsed, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onReloadApp, onRemoveApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy, }: ChatFirstAppRailProps) => import("react").JSX.Element>;
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, Skeleton, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@agent-native/toolkit/ui";
3
- import { IconApps, IconChevronDown, IconChevronUp, IconPin, IconPlus, IconTrash, } from "@tabler/icons-react";
3
+ import { IconApps, IconChevronDown, IconChevronUp, IconPin, IconPlus, IconRefresh, IconTrash, } from "@tabler/icons-react";
4
4
  import { memo, useEffect, useMemo, useState } from "react";
5
5
  import { CHAT_FIRST_DEFAULT_APP_IDS, orderChatFirstAppIds, readChatFirstAppLayout, writeChatFirstAppLayout, } from "../chat-first.js";
6
6
  import { cn } from "../utils.js";
@@ -33,7 +33,10 @@ function ChatFirstRailAppIcon({ app, activeAppId, renderIcon, }) {
33
33
  const isInactive = activeAppId !== undefined && !isActive;
34
34
  return (_jsx("span", { "data-chat-first-app-icon": true, className: cn("transition-[filter]", isInactive && "grayscale"), children: renderIcon(app, { isActive, isInactive }) }));
35
35
  }
36
- function AppRows({ apps, defaultAppIds, activeAppId, layout, onDragStart, onDrop, onDragEnd, onOpenApp, onRemoveApp, onTogglePinned, onMove, renderIcon, copy, }) {
36
+ function AppContextMenuContent({ app, copy, index, onMove, onReloadApp, onRemoveApp, onTogglePinned, pinned, total, }) {
37
+ return (_jsxs(ContextMenuContent, { children: [_jsxs(ContextMenuItem, { onSelect: () => onTogglePinned(app.id), children: [_jsx(IconPin, { size: 14, "aria-hidden": "true" }), pinned ? copy("removePinned") : copy("pinTop")] }), onReloadApp ? (_jsxs(ContextMenuItem, { onSelect: () => onReloadApp(app), children: [_jsx(IconRefresh, { size: 14, "aria-hidden": "true" }), copy("reloadApp")] })) : null, _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { disabled: index === 0, onSelect: () => onMove(app.id, -1), children: [_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" }), copy("moveUp")] }), _jsxs(ContextMenuItem, { disabled: index === total - 1, onSelect: () => onMove(app.id, 1), children: [_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" }), copy("moveDown")] }), onRemoveApp ? (_jsxs(_Fragment, { children: [_jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { onSelect: () => onRemoveApp(app), className: "text-destructive focus:text-destructive", children: [_jsx(IconTrash, { size: 14, "aria-hidden": "true" }), copy("removeApp")] })] })) : null] }));
38
+ }
39
+ function AppRows({ apps, defaultAppIds, activeAppId, layout, onDragStart, onDrop, onDragEnd, onOpenApp, onReloadApp, onRemoveApp, onTogglePinned, onMove, renderIcon, copy, }) {
37
40
  const orderedIds = orderChatFirstAppIds(apps.map((app) => app.id), layout, defaultAppIds);
38
41
  const appsById = new Map(apps.map((app) => [app.id, app]));
39
42
  const orderedApps = orderedIds
@@ -61,10 +64,10 @@ function AppRows({ apps, defaultAppIds, activeAppId, layout, onDragStart, onDrop
61
64
  }
62
65
  }, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown", "aria-label": copy("openApp", { name: app.name }), children: [_jsx(ChatFirstRailAppIcon, { app: app, activeAppId: activeAppId, renderIcon: renderIcon }), _jsx("span", { className: "truncate", children: app.name })] }), _jsx("button", { type: "button", className: cn("flex size-6 shrink-0 items-center justify-center rounded text-sidebar-foreground/45 opacity-0 hover:bg-sidebar-accent hover:text-sidebar-foreground group-hover:opacity-100 focus-visible:opacity-100", pinned && "text-sidebar-foreground/70 opacity-100"), "aria-label": copy(pinned ? "unpinApp" : "pinApp", {
63
66
  name: app.name,
64
- }), "aria-pressed": pinned, onClick: () => onTogglePinned(app.id), children: _jsx(IconPin, { size: 13, strokeWidth: pinned ? 2.2 : 1.6, "aria-hidden": "true" }) }), _jsx("span", { className: "sr-only", children: `${index + 1} of ${orderedApps.length}` })] }) }), _jsxs(ContextMenuContent, { children: [_jsxs(ContextMenuItem, { onSelect: () => onTogglePinned(app.id), children: [_jsx(IconPin, { size: 14, "aria-hidden": "true" }), pinned ? copy("removePinned") : copy("pinTop")] }), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { disabled: index === 0, onSelect: () => onMove(app.id, -1), children: [_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" }), copy("moveUp")] }), _jsxs(ContextMenuItem, { disabled: index === orderedApps.length - 1, onSelect: () => onMove(app.id, 1), children: [_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" }), copy("moveDown")] }), onRemoveApp ? (_jsxs(_Fragment, { children: [_jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { onSelect: () => onRemoveApp(app), className: "text-destructive focus:text-destructive", children: [_jsx(IconTrash, { size: 14, "aria-hidden": "true" }), copy("removeApp")] })] })) : null] })] }, app.id));
67
+ }), "aria-pressed": pinned, onClick: () => onTogglePinned(app.id), children: _jsx(IconPin, { size: 13, strokeWidth: pinned ? 2.2 : 1.6, "aria-hidden": "true" }) }), _jsx("span", { className: "sr-only", children: `${index + 1} of ${orderedApps.length}` })] }) }), _jsx(AppContextMenuContent, { app: app, copy: copy, index: index, onMove: onMove, onReloadApp: onReloadApp, onRemoveApp: onRemoveApp, onTogglePinned: onTogglePinned, pinned: pinned, total: orderedApps.length })] }, app.id));
65
68
  }) }));
66
69
  }
67
- export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, defaultAppIds, activeAppId, loading = false, error, collapsed = false, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onRemoveApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy = defaultChatFirstCopy, }) {
70
+ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, defaultAppIds, activeAppId, loading = false, error, collapsed = false, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onReloadApp, onRemoveApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy = defaultChatFirstCopy, }) {
68
71
  const [localLayout, setLocalLayout] = useState(() => readChatFirstAppLayout());
69
72
  const layout = controlledLayout ?? localLayout;
70
73
  const [draggedAppId, setDraggedAppId] = useState(null);
@@ -135,9 +138,9 @@ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, default
135
138
  if (collapsed) {
136
139
  return (_jsx(TooltipProvider, { children: _jsxs("section", { "data-chat-first-apps-rail": true, className: "flex flex-col items-center gap-1 px-1.5 pt-2", "aria-label": copy("workspaceApps"), children: [loading && apps.length === 0
137
140
  ? [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "size-9 rounded-md" }, index)))
138
- : visibleApps.map((app) => (_jsxs(Tooltip, { delayDuration: 0, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-chat-first-app": true, "data-app-id": app.id, className: cn("flex size-9 items-center justify-center rounded-md", activeAppId === app.id
139
- ? "bg-sidebar-accent text-sidebar-accent-foreground"
140
- : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), children: _jsx(ChatFirstRailAppIcon, { app: app, activeAppId: activeAppId, renderIcon: renderIcon }) }) }), _jsx(TooltipContent, { side: "right", children: app.name })] }, app.id))), onOpenAllApps ? (_jsxs(Tooltip, { delayDuration: 0, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) }) }), _jsx(TooltipContent, { side: "right", children: copy("allApps") })] })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }) }));
141
+ : visibleApps.map((app) => (_jsxs(ContextMenu, { children: [_jsxs(Tooltip, { delayDuration: 0, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(ContextMenuTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-chat-first-app": true, "data-app-id": app.id, className: cn("flex size-9 items-center justify-center rounded-md", activeAppId === app.id
142
+ ? "bg-sidebar-accent text-sidebar-accent-foreground"
143
+ : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), children: _jsx(ChatFirstRailAppIcon, { app: app, activeAppId: activeAppId, renderIcon: renderIcon }) }) }) }), _jsx(TooltipContent, { side: "right", children: app.name })] }), _jsx(AppContextMenuContent, { app: app, copy: copy, index: orderedApps.indexOf(app), onMove: moveApp, onReloadApp: onReloadApp, onRemoveApp: onRemoveApp, onTogglePinned: togglePinned, pinned: layout.pinnedIds.includes(app.id), total: orderedApps.length })] }, app.id))), onOpenAllApps ? (_jsxs(Tooltip, { delayDuration: 0, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) }) }), _jsx(TooltipContent, { side: "right", children: copy("allApps") })] })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }) }));
141
144
  }
142
- return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-2 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-2", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-2", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, defaultAppIds: defaultAppIds, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onRemoveApp: onRemoveApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-2 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
145
+ return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-2 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-2", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-2", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, defaultAppIds: defaultAppIds, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onReloadApp: onReloadApp, onRemoveApp: onRemoveApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-2 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
143
146
  });
@@ -14,6 +14,7 @@ const DEFAULT_COPY = {
14
14
  dismiss: "Dismiss",
15
15
  unpinApp: "Unpin this app",
16
16
  pinApp: "Pin this app",
17
+ reloadApp: "Reload app",
17
18
  removePinned: "Remove from pinned apps",
18
19
  pinTop: "Pin app to the top",
19
20
  moveUp: "Move up",
@@ -56,6 +56,7 @@ export interface ChatFirstAppRailProps {
56
56
  onLayoutError?: (reason: "unavailable" | "write-failed") => void;
57
57
  onRetry?: () => void;
58
58
  onOpenApp: (app: ChatFirstAppItem) => void;
59
+ onReloadApp?: (app: ChatFirstAppItem) => void;
59
60
  onRemoveApp?: (app: ChatFirstAppItem) => void;
60
61
  onOpenAllApps?: () => void;
61
62
  onCreateApp?: () => void;
@@ -242,7 +242,7 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
242
242
  });
243
243
  };
244
244
  const quickConnect = (integration) => {
245
- if (hasOrg) {
245
+ if (hasOrg && supportsMcpIntegrationOrganizationScope(integration)) {
246
246
  setSelected(integration);
247
247
  setMode("choice");
248
248
  return;
@@ -271,7 +271,7 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
271
271
  const selectCatalogConnection = (integration) => {
272
272
  if (!mcpServersQuery.isSuccess)
273
273
  return;
274
- if (hasOrg) {
274
+ if (hasOrg && supportsMcpIntegrationOrganizationScope(integration)) {
275
275
  setSelected(integration);
276
276
  setMode("choice");
277
277
  return;
@@ -329,7 +329,7 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
329
329
  if (quickConnectAttemptedRef.current === attemptKey)
330
330
  return;
331
331
  quickConnectAttemptedRef.current = attemptKey;
332
- if (hasOrg) {
332
+ if (hasOrg && supportsMcpIntegrationOrganizationScope(integration)) {
333
333
  setSelected(integration);
334
334
  setMode("choice");
335
335
  return;
@@ -507,7 +507,7 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
507
507
  : t("mcpIntegrations.presetAuthDescription")
508
508
  : t("mcpIntegrations.customDescription") })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-7 py-7 sm:px-10", children: _jsxs("div", { className: "mx-auto max-w-2xl space-y-3", children: [renderScopeSelector(), selected?.setupNoteKey && !selectedRequiresSetup ? (_jsx("div", { className: "rounded-md bg-muted/40 px-3 py-2 text-[11px] leading-relaxed text-muted-foreground", children: t(selected.setupNoteKey) })) : null, selectedRequiresSetup && selected && (_jsxs("div", { className: "mx-auto grid w-full max-w-xl gap-4 py-8", children: [_jsxs("div", { children: [_jsx("p", { className: "text-base font-semibold tracking-[-0.02em] text-foreground", children: t("mcpIntegrations.providerSetupRequired") }), _jsx("p", { className: "mt-1 text-sm leading-6 text-muted-foreground", children: t("mcpIntegrations.providerSetupDescription", {
509
509
  name: selected.name,
510
- }) })] }), selected.setupNoteKey ? (_jsx("p", { className: "text-sm leading-6 text-muted-foreground", children: t(selected.setupNoteKey) })) : null, selected.docsUrl ? (_jsxs("a", { href: selected.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex w-fit items-center gap-1 text-sm font-medium text-foreground underline underline-offset-4 hover:text-muted-foreground", children: [t("mcpIntegrations.viewSetup"), _jsx(IconExternalLink, { className: "size-3.5" })] })) : null] })), !selected && (_jsxs("div", { className: "flex items-center justify-between gap-3 rounded-md bg-muted/30 px-3 py-2 text-[11px]", children: [_jsx("span", { className: "text-muted-foreground", children: customAuthMode === "oauth"
510
+ }) })] }), selected.setupNoteKey ? (_jsx("p", { className: "text-sm leading-6 text-muted-foreground", children: t(selected.setupNoteKey) })) : null, _jsxs("div", { className: "flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: t("mcpIntegrations.personalConnection") }), _jsx("p", { className: "text-[11px] leading-relaxed text-muted-foreground", children: t("mcpIntegrations.personalDescription") })] }), _jsx("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: t("mcpIntegrations.personal") })] }), selected.docsUrl ? (_jsxs("a", { href: selected.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex w-fit items-center gap-1 text-sm font-medium text-foreground underline underline-offset-4 hover:text-muted-foreground", children: [t("mcpIntegrations.viewSetup"), _jsx(IconExternalLink, { className: "size-3.5" })] })) : null] })), !selected && (_jsxs("div", { className: "flex items-center justify-between gap-3 rounded-md bg-muted/30 px-3 py-2 text-[11px]", children: [_jsx("span", { className: "text-muted-foreground", children: customAuthMode === "oauth"
511
511
  ? t(
512
512
  /* i18n-key-ignore */
513
513
  "mcpIntegrations.customOAuthDefault", { defaultValue: "Sign in with OAuth" })
@@ -541,5 +541,5 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
541
541
  }, placeholder: selected?.headerPlaceholder ??
542
542
  t("mcpIntegrations.headersPlaceholder") })] })), selected?.docsUrl && (_jsxs("a", { href: selected.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground underline hover:text-foreground", children: [t("mcpIntegrations.openSetupDocs"), _jsx(IconExternalLink, { className: "h-3 w-3" })] }))] })), testResult && (_jsxs("div", { className: cn("flex items-start gap-1 rounded-md px-3 py-2 text-[11px] leading-snug", testResult.ok
543
543
  ? "bg-green-500/5 text-green-600 dark:text-green-400"
544
- : "bg-red-500/5 text-red-600 dark:text-red-400"), children: [testResult.ok && (_jsx(IconCheck, { className: "mt-0.5 h-3 w-3 shrink-0" })), _jsx("span", { className: "min-w-0 break-words", children: testResult.message })] })), error && (_jsx("div", { className: "break-words rounded-md bg-red-500/5 px-3 py-2 text-[11px] leading-snug text-red-600 dark:text-red-400", children: error }))] }) }), _jsxs("div", { className: "flex shrink-0 items-center justify-between gap-2 border-t border-border px-7 py-4", children: [!selectedRequiresSetup && (_jsx("button", { type: "button", onClick: runTest, disabled: !url.trim() || busy, className: "rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent disabled:pointer-events-none disabled:opacity-40", children: t("mcpIntegrations.test") })), !selected && customAuthMode === "oauth" ? (_jsxs("button", { type: "button", onClick: connectCustomWithOAuth, disabled: !name.trim() || !url.trim() || busy, "aria-busy": busy, className: "rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent disabled:pointer-events-none disabled:opacity-40", children: [busy && (_jsx(IconLoader2, { className: "me-1.5 inline h-3.5 w-3.5 animate-spin" })), t("mcpIntegrations.connectWithOAuth")] })) : null, selectedRequiresSetup ? (_jsxs("div", { className: "ms-auto flex items-center gap-2", children: [selected?.docsUrl ? (_jsxs("a", { href: selected.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent", children: [t("mcpIntegrations.viewSetup"), _jsx(IconExternalLink, { className: "h-3 w-3" })] })) : null, selected?.authMode === "oauth" && (_jsxs("button", { type: "button", onClick: () => connectWithOAuth(selected), disabled: busy, "aria-busy": busy, className: "inline-flex min-w-[132px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40", children: [busy && (_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" })), t("mcpIntegrations.continueToConnect")] }))] })) : selected?.authMode === "oauth" ? (_jsxs("button", { type: "button", onClick: () => connectWithOAuth(selected), disabled: !name.trim() || !url.trim() || busy, "aria-busy": busy, className: "inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40", children: [busy && _jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), t("mcpIntegrations.connectWithOAuth")] })) : !selected && customAuthMode === "oauth" ? null : (_jsxs("button", { type: "button", onClick: submitForm, disabled: !name.trim() || !url.trim() || busy, className: "inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40", children: [busy && _jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), t("mcpIntegrations.connect")] }))] })] }))] }) }));
544
+ : "bg-red-500/5 text-red-600 dark:text-red-400"), children: [testResult.ok && (_jsx(IconCheck, { className: "mt-0.5 h-3 w-3 shrink-0" })), _jsx("span", { className: "min-w-0 break-words", children: testResult.message })] })), error && (_jsx("div", { className: "break-words rounded-md bg-red-500/5 px-3 py-2 text-[11px] leading-snug text-red-600 dark:text-red-400", children: error }))] }) }), _jsxs("div", { className: "flex shrink-0 items-center justify-between gap-2 border-t border-border px-7 py-4", children: [!selectedRequiresSetup && (_jsx("button", { type: "button", onClick: runTest, disabled: !url.trim() || busy, className: "rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent disabled:pointer-events-none disabled:opacity-40", children: t("mcpIntegrations.test") })), !selected && customAuthMode === "oauth" ? (_jsxs("button", { type: "button", onClick: connectCustomWithOAuth, disabled: !name.trim() || !url.trim() || busy, "aria-busy": busy, className: "rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent disabled:pointer-events-none disabled:opacity-40", children: [busy && (_jsx(IconLoader2, { className: "me-1.5 inline h-3.5 w-3.5 animate-spin" })), t("mcpIntegrations.connectWithOAuth")] })) : null, selectedRequiresSetup ? (_jsx("div", { className: "ms-auto flex items-center gap-2", children: selected?.authMode === "oauth" && (_jsxs("button", { type: "button", onClick: () => connectWithOAuth(selected), disabled: busy, "aria-busy": busy, className: "inline-flex min-w-[132px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40", children: [busy && (_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" })), t("mcpIntegrations.continueToConnect")] })) })) : selected?.authMode === "oauth" ? (_jsxs("button", { type: "button", onClick: () => connectWithOAuth(selected), disabled: !name.trim() || !url.trim() || busy, "aria-busy": busy, className: "inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40", children: [busy && _jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), t("mcpIntegrations.connectWithOAuth")] })) : !selected && customAuthMode === "oauth" ? null : (_jsxs("button", { type: "button", onClick: submitForm, disabled: !name.trim() || !url.trim() || busy, className: "inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40", children: [busy && _jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), t("mcpIntegrations.connect")] }))] })] }))] }) }));
545
545
  }
@@ -272,7 +272,7 @@ export const DEFAULT_MCP_INTEGRATIONS = [
272
272
  },
273
273
  {
274
274
  id: "atlassian",
275
- name: "Atlassian",
275
+ name: "Jira",
276
276
  provider: "atlassian",
277
277
  description: "Read and write Jira issues and Confluence content.",
278
278
  descriptionKey: "mcpIntegrations.catalog.atlassian.description",
@@ -5,6 +5,12 @@ export interface CodingCommandResult {
5
5
  stderr: string;
6
6
  timedOut: boolean;
7
7
  durationMs?: number;
8
+ /**
9
+ * The command exited but something it started outlived it and still holds the
10
+ * output pipe, so the captured output stops here rather than at end-of-stream.
11
+ * Distinct from a clean finish — the caller is not looking at the whole story.
12
+ */
13
+ outputStillOpen?: boolean;
8
14
  }
9
15
  /**
10
16
  * Structured metadata emitted on tool_start / tool_done events so the UI can
@@ -103,4 +109,29 @@ export declare function truncateCodingOutput(value: string, max: number): string
103
109
  * signal (error messages, test results, etc.).
104
110
  */
105
111
  export declare function truncateBashOutput(value: string, headChars?: number, tailChars?: number): string;
112
+ /**
113
+ * Strip shell quoting so a policy regex sees the word the shell will actually
114
+ * run. Bash removes quotes before the command word exists, so `git 'checkout'`
115
+ * and `gi''t checkout` both execute `git checkout` while matching no rule
116
+ * written against the literal text — every denylist entry is otherwise one pair
117
+ * of quotes away from being bypassed.
118
+ *
119
+ * `unanalyzable` reports constructs whose executed text this pass cannot
120
+ * recover, so no rule matched against the canonical form can be trusted:
121
+ * `$'…'` ANSI-C escapes (`$'\x67it'` → `git`) and command substitution
122
+ * (`$(printf git) $(printf checkout) main` runs the forbidden operation while
123
+ * the string contains neither token). Callers must escalate rather than clear a
124
+ * command when this is set. Substitution inside single quotes is literal, so it
125
+ * does not set the flag.
126
+ *
127
+ * ponytail: plain parameter expansion (`$VAR`, `${VAR}`) can also build a token
128
+ * at runtime and is NOT flagged — doing so would make ordinary `cd $TMPDIR`
129
+ * commands require approval. Closing that needs a real shell parser, which is
130
+ * the upgrade path if this boundary ever has to hold against a determined
131
+ * attacker rather than a misbehaving model.
132
+ */
133
+ export declare function canonicalizeShellCommand(command: string): {
134
+ canonical: string;
135
+ unanalyzable: boolean;
136
+ };
106
137
  export declare function isReadOnlyShellCommand(command: string): boolean;