@shuffleio/shuffle-mcps 0.0.31 → 0.0.33

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.mjs CHANGED
@@ -20,13 +20,13 @@ import {
20
20
  } from "./chunk-B4JFC2JU.mjs";
21
21
 
22
22
  // index.ts
23
- import "./shuffle-mcp-ER33P3W2.css";
23
+ import "./shuffle-mcp-TWJGPBOQ.css";
24
24
  import React5 from "react";
25
25
 
26
26
  // ShuffleMcpThemeProvider.tsx
27
27
  import React from "react";
28
28
  import { ThemeProvider, createTheme, useTheme as useMuiTheme } from "@mui/material";
29
- import "./shuffle-mcp-ER33P3W2.css";
29
+ import "./shuffle-mcp-TWJGPBOQ.css";
30
30
  import { jsx, jsxs } from "react/jsx-runtime";
31
31
  var readHtmlDarkClass = () => {
32
32
  if (typeof document === "undefined") return false;
@@ -879,6 +879,65 @@ var isIngestionApp = (appName) => {
879
879
  return isEmailApp(appName) || CASES_PATTERNS.some((p) => name.includes(p)) || EDR_PATTERNS.some((p) => name.includes(p)) || SIEM_PATTERNS.some((p) => name.includes(p));
880
880
  };
881
881
  var normalizeAppName = (name) => name.toLowerCase().trim().replace(/[\s_\-]+/g, "_");
882
+ var IGNORED_WORKFLOW_APP_NAMES = /* @__PURE__ */ new Set([
883
+ "shuffle_tools",
884
+ "singul",
885
+ "integration",
886
+ "ai_agent",
887
+ "shuffle_agent",
888
+ "shuffle_ai"
889
+ ]);
890
+ var isIgnoredWorkflowAppName = (name) => IGNORED_WORKFLOW_APP_NAMES.has(normalizeAppName(name));
891
+ var looksLikeOpaqueId = (raw) => {
892
+ const trimmed = raw.trim();
893
+ if (/^[a-f0-9]{16,}$/i.test(trimmed)) return true;
894
+ if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(trimmed)) return true;
895
+ return false;
896
+ };
897
+ var addWorkflowAppName = (names, seen, raw) => {
898
+ if (typeof raw !== "string" || !raw.trim()) return;
899
+ const parts = raw.includes(",") ? raw.split(",") : [raw];
900
+ for (const part of parts) {
901
+ const trimmed = part.trim();
902
+ if (!trimmed) continue;
903
+ const key = normalizeAppName(trimmed);
904
+ if (!key || isIgnoredWorkflowAppName(trimmed) || looksLikeOpaqueId(trimmed) || seen.has(key)) continue;
905
+ seen.add(key);
906
+ names.push(trimmed);
907
+ }
908
+ };
909
+ var collectParameterAppNames = (input, names, seen, depth = 0) => {
910
+ if (!input || depth > 4) return;
911
+ if (Array.isArray(input)) {
912
+ input.forEach((item) => collectParameterAppNames(item, names, seen, depth + 1));
913
+ return;
914
+ }
915
+ if (typeof input !== "object") return;
916
+ const obj = input;
917
+ if (String(obj.name || "").toLowerCase() === "app_name") addWorkflowAppName(names, seen, obj.value);
918
+ for (const [key, value] of Object.entries(obj)) {
919
+ const lower = key.toLowerCase();
920
+ if (lower === "app_name") addWorkflowAppName(names, seen, value);
921
+ else if (lower === "parameters" || lower === "fields" || lower === "value") collectParameterAppNames(value, names, seen, depth + 1);
922
+ }
923
+ };
924
+ function extractActionAppNames(action) {
925
+ const names = [];
926
+ const seen = /* @__PURE__ */ new Set();
927
+ collectParameterAppNames(action?.parameters, names, seen);
928
+ addWorkflowAppName(names, seen, action?.app_name);
929
+ addWorkflowAppName(names, seen, action?.app_id);
930
+ return names;
931
+ }
932
+ function extractWorkflowActionAppNames(workflow) {
933
+ const names = [];
934
+ const seen = /* @__PURE__ */ new Set();
935
+ if (!workflow?.actions || !Array.isArray(workflow.actions)) return names;
936
+ for (const action of workflow.actions) {
937
+ for (const name of extractActionAppNames(action)) addWorkflowAppName(names, seen, name);
938
+ }
939
+ return names;
940
+ }
882
941
  var getIngestionCategory = (appName, appCategories) => {
883
942
  const name = appName.toLowerCase();
884
943
  const categories = (appCategories || []).map((c) => c.toLowerCase());
@@ -892,20 +951,14 @@ var INGEST_TICKETS_WORKFLOW_NAME = "Ingest Tickets";
892
951
  var FORWARD_TICKETS_WORKFLOW_NAME = "Forward Tickets";
893
952
  function extractWorkflowAppNames(workflow) {
894
953
  const names = /* @__PURE__ */ new Set();
895
- if (!workflow?.actions || !Array.isArray(workflow.actions)) return names;
896
- for (const action of workflow.actions) {
897
- if (action.app_name) {
898
- names.add(normalizeAppName(action.app_name));
899
- }
900
- if (action.app_id) {
901
- names.add(normalizeAppName(action.app_id));
902
- }
903
- if (action.parameters) {
904
- for (const param of action.parameters) {
905
- if (param.name === "app_name" && param.value) {
906
- names.add(normalizeAppName(param.value));
907
- }
908
- }
954
+ for (const raw of extractWorkflowActionAppNames(workflow)) {
955
+ const parts = typeof raw === "string" && raw.includes(",") ? raw.split(",") : [raw];
956
+ for (const part of parts) {
957
+ const trimmed = (part || "").trim();
958
+ if (!trimmed) continue;
959
+ if (isIgnoredWorkflowAppName(trimmed) || looksLikeOpaqueId(trimmed)) continue;
960
+ const key = normalizeAppName(trimmed);
961
+ if (key) names.add(key);
909
962
  }
910
963
  }
911
964
  return names;
@@ -1619,6 +1672,7 @@ function AppTitleHeader({
1619
1672
  isActivated = null,
1620
1673
  activateLoading = false,
1621
1674
  onActivateToggle,
1675
+ highlightActivate = false,
1622
1676
  onAdd
1623
1677
  }) {
1624
1678
  return /* @__PURE__ */ jsx4(motion.div, { initial: { opacity: 0, y: 12 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.3 }, children: /* @__PURE__ */ jsxs3(
@@ -1722,7 +1776,16 @@ function AppTitleHeader({
1722
1776
  py: 0.5,
1723
1777
  minHeight: 0,
1724
1778
  flexShrink: 0,
1725
- ...isActivated ? { color: "hsl(var(--muted-foreground))", borderColor: "hsl(var(--border))", "&:hover": { borderColor: "hsl(var(--destructive))", color: "hsl(var(--destructive))", bgcolor: "hsla(var(--destructive) / 0.08)" } } : { bgcolor: "hsl(var(--primary))", "&:hover": { bgcolor: "hsl(var(--primary) / 0.85)" } }
1779
+ ...isActivated ? { color: "hsl(var(--muted-foreground))", borderColor: "hsl(var(--border))", "&:hover": { borderColor: "hsl(var(--destructive))", color: "hsl(var(--destructive))", bgcolor: "hsla(var(--destructive) / 0.08)" } } : { bgcolor: "hsl(var(--primary))", "&:hover": { bgcolor: "hsl(var(--primary) / 0.85)" } },
1780
+ ...highlightActivate ? {
1781
+ boxShadow: "0 0 0 0 hsla(var(--primary) / 0.6)",
1782
+ animation: "appTitleActivatePulse 1.1s ease-out infinite",
1783
+ "@keyframes appTitleActivatePulse": {
1784
+ "0%": { boxShadow: "0 0 0 0 hsla(var(--primary) / 0.55)" },
1785
+ "70%": { boxShadow: "0 0 0 10px hsla(var(--primary) / 0)" },
1786
+ "100%": { boxShadow: "0 0 0 0 hsla(var(--primary) / 0)" }
1787
+ }
1788
+ } : {}
1726
1789
  },
1727
1790
  children: activateLoading ? "\u2026" : isActivated ? "Deactivate" : "Activate"
1728
1791
  }
@@ -1745,7 +1808,9 @@ import {
1745
1808
  Pencil as EditIcon,
1746
1809
  ChevronDown as ExpandMoreIcon,
1747
1810
  Lock as LockIcon,
1748
- BookOpen as MenuBookIcon
1811
+ BookOpen as MenuBookIcon,
1812
+ Eye as EyeIcon,
1813
+ EyeOff as EyeOffIcon
1749
1814
  } from "lucide-react";
1750
1815
  import {
1751
1816
  Box as Box3,
@@ -1986,6 +2051,8 @@ var AppAuthCard = ({
1986
2051
  hideDocsLink,
1987
2052
  hideUrlFields,
1988
2053
  borderless,
2054
+ compactAuthForm,
2055
+ extraFieldsSlot,
1989
2056
  globalUrl,
1990
2057
  userdata,
1991
2058
  isLoaded,
@@ -2052,10 +2119,6 @@ var AppAuthCard = ({
2052
2119
  if (!alreadyValid && newestId !== autoTestFiredRef.current) {
2053
2120
  autoTestFiredRef.current = newestId;
2054
2121
  setTimeout(() => {
2055
- toast({
2056
- title: "Auto-testing new authentication",
2057
- description: `Verifying connection for ${app.name.replace(/_/g, " ")}...`
2058
- });
2059
2122
  setTestingAuthId(newestId);
2060
2123
  onTestConnection(app.objectID, newestId);
2061
2124
  }, 500);
@@ -2117,11 +2180,17 @@ var AppAuthCard = ({
2117
2180
  executionId: authState.executionId,
2118
2181
  errorCode: authState.errorCode
2119
2182
  });
2183
+ if (autoTestFiredRef.current === testingAuthId) {
2184
+ setSelectedAuthId(ADD_NEW_AUTH);
2185
+ setUserHasSelected(true);
2186
+ setPendingFailedAuthId(testingAuthId);
2187
+ autoTestFiredRef.current = null;
2188
+ }
2120
2189
  setTestingAuthId(null);
2121
2190
  }
2122
2191
  }, [authState.status, authState.errorMessage, authState.successMessage, authState.warningMessage, testingAuthId, apiAuthEntries, wasPreValidatedPerAuth]);
2123
2192
  const isCredentialError = localTestMessages.errorCode === 401 || localTestMessages.errorCode === 403;
2124
- const showAddNewForm = selectedAuthId === ADD_NEW_AUTH;
2193
+ const showAddNewForm = compactAuthForm ? true : selectedAuthId === ADD_NEW_AUTH;
2125
2194
  const isConfigured = selectedAuth?.active === true;
2126
2195
  const isTested = selectedAuth?.validation?.valid === true;
2127
2196
  const getLastValidTimestamp = () => {
@@ -2143,6 +2212,8 @@ var AppAuthCard = ({
2143
2212
  });
2144
2213
  };
2145
2214
  const [localCredentials, setLocalCredentials] = useState5(authState.credentials || {});
2215
+ const [revealedFields, setRevealedFields] = useState5({});
2216
+ const [pendingFailedAuthId, setPendingFailedAuthId] = useState5(null);
2146
2217
  const [fieldErrors, setFieldErrors] = useState5({});
2147
2218
  const [loading, setLoading] = useState5(false);
2148
2219
  const [saving, setSaving] = useState5(false);
@@ -2218,7 +2289,8 @@ var AppAuthCard = ({
2218
2289
  for (const [k, v] of Object.entries(incoming)) {
2219
2290
  const lk = k.toLowerCase();
2220
2291
  const isUrlish = lk.includes("url") || lk.includes("endpoint") || lk.includes("host");
2221
- if (isUrlish && typeof v === "string" && v !== prev[k]) {
2292
+ const isModel = lk === "model";
2293
+ if ((isUrlish || isModel) && typeof v === "string" && v !== prev[k]) {
2222
2294
  next[k] = v;
2223
2295
  changed = true;
2224
2296
  }
@@ -2414,7 +2486,7 @@ var AppAuthCard = ({
2414
2486
  sx: {
2415
2487
  "& .MuiOutlinedInput-root": {
2416
2488
  backgroundColor: "hsl(var(--background))",
2417
- borderRadius: 2,
2489
+ borderRadius: 1,
2418
2490
  "& fieldset": { borderColor: "hsl(var(--border))" },
2419
2491
  "&:hover fieldset": { borderColor: "hsl(var(--border) / 0.8)" },
2420
2492
  "&.Mui-focused fieldset": { borderColor: "hsl(var(--primary))" }
@@ -2437,7 +2509,7 @@ var AppAuthCard = ({
2437
2509
  sx: {
2438
2510
  "& .MuiOutlinedInput-root": {
2439
2511
  backgroundColor: "hsl(var(--background))",
2440
- borderRadius: 2,
2512
+ borderRadius: 1,
2441
2513
  "& fieldset": { borderColor: "hsl(var(--border))" },
2442
2514
  "&:hover fieldset": { borderColor: "hsl(var(--border) / 0.8)" },
2443
2515
  "&.Mui-focused fieldset": { borderColor: "hsl(var(--primary))" }
@@ -2462,7 +2534,7 @@ var AppAuthCard = ({
2462
2534
  displayEmpty: true,
2463
2535
  sx: {
2464
2536
  backgroundColor: "action.hover",
2465
- borderRadius: 2,
2537
+ borderRadius: 1,
2466
2538
  color: "text.primary",
2467
2539
  "& .MuiOutlinedInput-notchedOutline": { borderColor: "divider" },
2468
2540
  "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: "text.disabled" },
@@ -2507,6 +2579,7 @@ var AppAuthCard = ({
2507
2579
  const fieldKey = param.id || param.name || `param_${index}`;
2508
2580
  const lowerName = (param.name || "").toLowerCase();
2509
2581
  const isSecretField = lowerName.includes("password") || lowerName.includes("secret") || lowerName.includes("key") || lowerName.includes("token");
2582
+ const isRevealed = !!revealedFields[fieldKey];
2510
2583
  return /* @__PURE__ */ jsx6(
2511
2584
  TextField,
2512
2585
  {
@@ -2537,13 +2610,25 @@ var AppAuthCard = ({
2537
2610
  onFocus: (e) => {
2538
2611
  e.target.removeAttribute("readonly");
2539
2612
  }
2540
- }
2613
+ },
2614
+ input: isSecretField ? {
2615
+ endAdornment: /* @__PURE__ */ jsx6(
2616
+ IconButton2,
2617
+ {
2618
+ size: "small",
2619
+ onClick: () => setRevealedFields((prev) => ({ ...prev, [fieldKey]: !prev[fieldKey] })),
2620
+ "aria-label": isRevealed ? `Hide ${param.name}` : `Show ${param.name}`,
2621
+ sx: { color: "hsl(var(--muted-foreground))", "&:hover": { color: "hsl(var(--foreground))" } },
2622
+ children: isRevealed ? /* @__PURE__ */ jsx6(EyeOffIcon, { size: 16 }) : /* @__PURE__ */ jsx6(EyeIcon, { size: 16 })
2623
+ }
2624
+ )
2625
+ } : void 0
2541
2626
  },
2542
2627
  name: `auth-${fieldKey}-${app.objectID}-${Math.random().toString(36).slice(2, 8)}`,
2543
2628
  sx: {
2544
2629
  "& .MuiOutlinedInput-root": {
2545
2630
  backgroundColor: isSecretField ? "hsl(var(--primary) / 0.06)" : "hsl(var(--muted))",
2546
- borderRadius: 2,
2631
+ borderRadius: 1,
2547
2632
  "& fieldset": {
2548
2633
  borderColor: fieldErrors[fieldKey] ? "hsl(var(--destructive))" : isSecretField ? "hsl(var(--primary) / 0.5)" : "hsl(var(--border))",
2549
2634
  borderWidth: isSecretField ? 2 : 1
@@ -2555,7 +2640,7 @@ var AppAuthCard = ({
2555
2640
  },
2556
2641
  "& .MuiInputBase-input": {
2557
2642
  color: "hsl(var(--foreground))",
2558
- ...isSecretField && {
2643
+ ...isSecretField && !isRevealed && {
2559
2644
  WebkitTextSecurity: "disc",
2560
2645
  textSecurity: "disc",
2561
2646
  fontFamily: "text-security-disc, monospace"
@@ -2606,7 +2691,6 @@ var AppAuthCard = ({
2606
2691
  setSaveSuccess(success);
2607
2692
  if (success) {
2608
2693
  setUserHasSelected(false);
2609
- setLocalCredentials({});
2610
2694
  setFieldErrors({});
2611
2695
  }
2612
2696
  };
@@ -2814,7 +2898,7 @@ var AppAuthCard = ({
2814
2898
  borderTop: borderless ? "none" : "1px solid hsl(var(--border))"
2815
2899
  },
2816
2900
  children: [
2817
- apiAuthEntries.length > 0 && /* @__PURE__ */ jsxs4(Box3, { sx: { mb: 3 }, children: [
2901
+ !compactAuthForm && apiAuthEntries.length > 0 && /* @__PURE__ */ jsxs4(Box3, { sx: { mb: 3 }, children: [
2818
2902
  /* @__PURE__ */ jsx6(Typography3, { variant: "body2", sx: { color: "hsl(var(--muted-foreground))", mb: 1 }, children: "Select authentication" }),
2819
2903
  /* @__PURE__ */ jsx6(FormControl, { fullWidth: true, size: "small", children: /* @__PURE__ */ jsxs4(
2820
2904
  Select,
@@ -3353,7 +3437,7 @@ var AppAuthCard = ({
3353
3437
  children: /* @__PURE__ */ jsx6(Typography3, { sx: { fontSize: "0.875rem" }, children: localTestMessages.errorCode === 401 ? "Previous credentials were invalid or expired. Please enter new credentials." : "Access was denied with the previous credentials. Please re-authenticate with valid permissions." })
3354
3438
  }
3355
3439
  ),
3356
- /* @__PURE__ */ jsx6(Typography3, { variant: "subtitle2", sx: { color: "hsl(var(--foreground))", fontWeight: 600, mb: 2 }, children: apiAuthEntries.length === 0 ? "Configure Authentication" : "Add New Authentication" }),
3440
+ /* @__PURE__ */ jsx6(Typography3, { variant: "subtitle2", sx: { color: "hsl(var(--foreground))", fontWeight: 600, mb: 2 }, children: apiAuthEntries.length === 0 ? `Configure ${app.name.replace(/_/g, " ")}` : "Add New Authentication" }),
3357
3441
  loading ? /* @__PURE__ */ jsxs4(Box3, { sx: { display: "flex", alignItems: "center", justifyContent: "center", py: 4 }, children: [
3358
3442
  /* @__PURE__ */ jsx6(CircularProgress2, { size: 32, sx: { color: "hsl(var(--primary))" } }),
3359
3443
  /* @__PURE__ */ jsx6(Typography3, { sx: { ml: 2, color: "hsl(var(--muted-foreground))" }, children: "Loading configuration..." })
@@ -3389,6 +3473,7 @@ var AppAuthCard = ({
3389
3473
  ] })
3390
3474
  }
3391
3475
  ) : /* @__PURE__ */ jsxs4(Box3, { sx: { display: "flex", flexDirection: "column", gap: 3 }, children: [
3476
+ extraFieldsSlot?.({ credentials: localCredentials, setField: handleCredentialChange }),
3392
3477
  renderOAuth2Fields(),
3393
3478
  renderParameterFields(),
3394
3479
  authState.status === "error" && authState.errorMessage && /* @__PURE__ */ jsx6(
@@ -3443,38 +3528,97 @@ var AppAuthCard = ({
3443
3528
  children: "Failed to save authentication. Please try again."
3444
3529
  }
3445
3530
  ),
3446
- !isOAuth2 && /* @__PURE__ */ jsx6(Box3, { sx: { display: "flex", justifyContent: "flex-end", pt: 1.5 }, children: /* @__PURE__ */ jsx6(
3447
- Button2,
3448
- {
3449
- variant: "outlined",
3450
- size: "medium",
3451
- onClick: (e) => {
3452
- e.stopPropagation();
3453
- handleSave();
3454
- },
3455
- disabled: saving || !isFormValid(),
3456
- sx: {
3457
- borderColor: isFormValid() ? "hsl(var(--primary) / 0.5)" : "hsl(var(--border))",
3458
- color: isFormValid() ? "hsl(var(--primary))" : "hsl(var(--muted-foreground))",
3459
- fontWeight: 600,
3460
- textTransform: "none",
3461
- fontSize: "0.9rem",
3462
- px: 3,
3463
- py: 1,
3464
- borderRadius: 2,
3465
- transition: "all 0.2s ease",
3466
- "&:hover": {
3467
- borderColor: "hsl(var(--primary))",
3468
- backgroundColor: "hsl(var(--primary) / 0.08)"
3531
+ !isOAuth2 && /* @__PURE__ */ jsxs4(Box3, { sx: { display: "flex", justifyContent: "flex-end", pt: 1.5, gap: 1, flexWrap: "wrap" }, children: [
3532
+ pendingFailedAuthId && /* @__PURE__ */ jsx6(
3533
+ Button2,
3534
+ {
3535
+ variant: "text",
3536
+ size: "medium",
3537
+ onClick: async (e) => {
3538
+ e.stopPropagation();
3539
+ const previousFailedId = pendingFailedAuthId;
3540
+ setPendingFailedAuthId(null);
3541
+ setUserHasSelected(true);
3542
+ setFieldErrors({});
3543
+ await onSaveAuth(app.objectID, localCredentials);
3544
+ let entries = apiAuthEntries;
3545
+ if (onRefreshAuth) {
3546
+ await onRefreshAuth();
3547
+ }
3548
+ entries = apiAuthEntries || [];
3549
+ const sorted = [...entries].sort((a, b) => (b.created || 0) - (a.created || 0));
3550
+ const acceptId = sorted[0]?.id || previousFailedId;
3551
+ setSelectedAuthId(acceptId);
3552
+ const stale = entries.filter((e2) => e2.id && e2.id !== acceptId);
3553
+ await Promise.all(stale.map(async (entry) => {
3554
+ try {
3555
+ await fetch(getApiUrl(`/api/v1/apps/authentication/${entry.id}`), {
3556
+ method: "DELETE",
3557
+ credentials: "include",
3558
+ headers: { ...getAuthHeader() }
3559
+ });
3560
+ } catch (err) {
3561
+ console.error("[AppAuthCard] Failed to delete stale auth on Save anyway:", err);
3562
+ }
3563
+ }));
3564
+ if (onRefreshAuth) await onRefreshAuth();
3565
+ if (onSelectAuth) onSelectAuth(app.objectID, acceptId);
3566
+ if (typeof window !== "undefined") {
3567
+ window.dispatchEvent(new CustomEvent("integrations-changed"));
3568
+ window.dispatchEvent(new CustomEvent("agent-drawer-close"));
3569
+ }
3469
3570
  },
3470
- "&.Mui-disabled": {
3471
- borderColor: "hsl(var(--border))",
3472
- color: "hsl(var(--muted-foreground))"
3473
- }
3474
- },
3475
- children: saving ? "Saving..." : "Save Authentication"
3476
- }
3477
- ) }),
3571
+ sx: {
3572
+ color: "hsl(var(--muted-foreground))",
3573
+ fontWeight: 600,
3574
+ textTransform: "none",
3575
+ fontSize: "0.85rem",
3576
+ px: 2,
3577
+ py: 1,
3578
+ borderRadius: 2,
3579
+ "&:hover": {
3580
+ color: "hsl(var(--foreground))",
3581
+ backgroundColor: "hsl(var(--muted) / 0.5)"
3582
+ }
3583
+ },
3584
+ title: "Our test isn't always authoritative \u2014 keep the credentials you saved and dismiss the failure.",
3585
+ children: "Save anyway"
3586
+ }
3587
+ ),
3588
+ /* @__PURE__ */ jsx6(
3589
+ Button2,
3590
+ {
3591
+ variant: "outlined",
3592
+ size: "medium",
3593
+ onClick: (e) => {
3594
+ e.stopPropagation();
3595
+ setPendingFailedAuthId(null);
3596
+ handleSave();
3597
+ },
3598
+ disabled: saving || !isFormValid(),
3599
+ sx: {
3600
+ borderColor: isFormValid() ? "hsl(var(--primary) / 0.5)" : "hsl(var(--border))",
3601
+ color: isFormValid() ? "hsl(var(--primary))" : "hsl(var(--muted-foreground))",
3602
+ fontWeight: 600,
3603
+ textTransform: "none",
3604
+ fontSize: "0.9rem",
3605
+ px: 3,
3606
+ py: 1,
3607
+ borderRadius: 2,
3608
+ transition: "all 0.2s ease",
3609
+ "&:hover": {
3610
+ borderColor: "hsl(var(--primary))",
3611
+ backgroundColor: "hsl(var(--primary) / 0.08)"
3612
+ },
3613
+ "&.Mui-disabled": {
3614
+ borderColor: "hsl(var(--border))",
3615
+ color: "hsl(var(--muted-foreground))"
3616
+ }
3617
+ },
3618
+ children: saving ? "Testing..." : pendingFailedAuthId ? "Retry Test" : "Test Authentication"
3619
+ }
3620
+ )
3621
+ ] }),
3478
3622
  isOAuth2 && (() => {
3479
3623
  const hasClientId = !!localCredentials["client_id"]?.trim();
3480
3624
  const hasClientSecret = !!localCredentials["client_secret"]?.trim();
@@ -5320,6 +5464,7 @@ function AppDetailDrawer({
5320
5464
  onAddToCanvas,
5321
5465
  isAuthenticated = true,
5322
5466
  activeOrgId,
5467
+ autoActivate = false,
5323
5468
  globalUrl,
5324
5469
  userdata,
5325
5470
  isLoaded,
@@ -5517,8 +5662,9 @@ function AppDetailDrawer({
5517
5662
  useEffect7(() => {
5518
5663
  lastValidAuthRef.current = null;
5519
5664
  }, [appName]);
5520
- const handleActivateToggle = async () => {
5665
+ const handleActivateToggle = async (opts) => {
5521
5666
  if (!appName || activateLoading) return;
5667
+ const silent = !!opts?.silent;
5522
5668
  const wasActivated = isActivated;
5523
5669
  const prevAppId = activatedAppId;
5524
5670
  setIsActivated(!wasActivated);
@@ -5532,7 +5678,7 @@ function AppDetailDrawer({
5532
5678
  });
5533
5679
  if (!res.ok) throw new Error("Deactivate failed");
5534
5680
  setActivatedAppId(null);
5535
- toast.success(`${displayName} deactivated`);
5681
+ if (!silent) toast.success(`${displayName} deactivated`);
5536
5682
  } else {
5537
5683
  const appId2 = resolvedAlgoliaId;
5538
5684
  if (!appId2) throw new Error("App ID not resolved yet");
@@ -5543,40 +5689,71 @@ function AppDetailDrawer({
5543
5689
  });
5544
5690
  if (!activateRes.ok) throw new Error(`Activate failed (${activateRes.status})`);
5545
5691
  setActivatedAppId(appId2);
5546
- toast.success(`${displayName} activated`);
5692
+ if (!silent) toast.success(`${displayName} activated`);
5547
5693
  }
5548
5694
  } catch (err) {
5549
5695
  console.error("[Activate] Error:", err);
5550
- toast.error(err?.message || "Activation failed");
5696
+ if (!silent) toast.error(err?.message || "Activation failed");
5551
5697
  setIsActivated(wasActivated);
5552
5698
  setActivatedAppId(prevAppId);
5553
5699
  } finally {
5554
5700
  setActivateLoading(false);
5555
5701
  }
5556
5702
  };
5703
+ const autoActivateFiredRef = useRef5(null);
5704
+ const [autoActivatePulse, setAutoActivatePulse] = useState8(false);
5705
+ useEffect7(() => {
5706
+ if (!open || !autoActivate || !appName) return;
5707
+ const key = `${appName}`;
5708
+ if (autoActivateFiredRef.current === key) return;
5709
+ if (isActivated !== false) return;
5710
+ if (!resolvedAlgoliaId) return;
5711
+ if (activateLoading) return;
5712
+ autoActivateFiredRef.current = key;
5713
+ setAutoActivatePulse(true);
5714
+ handleActivateToggle({ silent: true }).finally(() => {
5715
+ setTimeout(() => setAutoActivatePulse(false), 1200);
5716
+ });
5717
+ }, [open, autoActivate, appName, isActivated, resolvedAlgoliaId, activateLoading]);
5718
+ useEffect7(() => {
5719
+ if (!open) {
5720
+ autoActivateFiredRef.current = null;
5721
+ setAutoActivatePulse(false);
5722
+ }
5723
+ }, [open, appName]);
5557
5724
  const isLoadingAll = appLoading || isAuthenticated && appAuthLoading;
5725
+ const drawerWidth = `min(${width}px, 100vw)`;
5558
5726
  return /* @__PURE__ */ jsxs9(
5559
5727
  Drawer,
5560
5728
  {
5561
5729
  anchor,
5562
5730
  open,
5563
5731
  onClose: handleClose,
5564
- slotProps: {
5565
- paper: {
5566
- className: scopeClassName,
5567
- sx: {
5568
- width: { xs: "100%", sm: width },
5569
- maxWidth: "100vw",
5570
- display: "flex",
5571
- flexDirection: "column",
5572
- background: "linear-gradient(180deg, hsl(var(--card)) 0%, hsl(var(--background)) 100%)",
5573
- borderLeft: anchor === "right" ? "1px solid hsl(var(--border))" : "none",
5574
- borderRight: anchor === "left" ? "1px solid hsl(var(--border))" : "none"
5575
- }
5732
+ PaperProps: {
5733
+ className: scopeClassName,
5734
+ sx: {
5735
+ width: drawerWidth,
5736
+ minWidth: drawerWidth,
5737
+ maxWidth: drawerWidth,
5738
+ flex: `0 0 ${drawerWidth}`,
5739
+ height: "100vh",
5740
+ display: "flex",
5741
+ flexDirection: "column",
5742
+ overflow: "hidden",
5743
+ background: "linear-gradient(180deg, hsl(var(--card)) 0%, hsl(var(--background)) 100%)",
5744
+ borderLeft: anchor === "right" ? "1px solid hsl(var(--border))" : "none",
5745
+ borderRight: anchor === "left" ? "1px solid hsl(var(--border))" : "none"
5576
5746
  }
5577
5747
  },
5578
5748
  sx: {
5579
- "& .MuiDrawer-paper": { boxSizing: "border-box" }
5749
+ zIndex: 9999,
5750
+ "& .MuiDrawer-paper": {
5751
+ boxSizing: "border-box",
5752
+ width: `${drawerWidth} !important`,
5753
+ minWidth: `${drawerWidth} !important`,
5754
+ maxWidth: `${drawerWidth} !important`,
5755
+ flex: `0 0 ${drawerWidth} !important`
5756
+ }
5580
5757
  },
5581
5758
  children: [
5582
5759
  /* @__PURE__ */ jsxs9(
@@ -5629,7 +5806,8 @@ function AppDetailDrawer({
5629
5806
  categories: appInfo?.categories,
5630
5807
  isActivated: onAddToCanvas ? null : effectiveActivated,
5631
5808
  activateLoading,
5632
- onActivateToggle: handleActivateToggle,
5809
+ onActivateToggle: () => handleActivateToggle(),
5810
+ highlightActivate: autoActivatePulse,
5633
5811
  onAdd: onAddToCanvas && appName ? () => {
5634
5812
  onAddToCanvas({ name: appName, icon: resolvedImage || "", algoliaId: resolvedAlgoliaId });
5635
5813
  onClose();
@@ -5733,7 +5911,7 @@ function AppDetailDrawer({
5733
5911
  }
5734
5912
 
5735
5913
  // views/ShuffleMCP.tsx
5736
- import "./shuffle-mcp-ER33P3W2.css";
5914
+ import "./shuffle-mcp-TWJGPBOQ.css";
5737
5915
 
5738
5916
  // components/SegmentedControl.tsx
5739
5917
  import * as React2 from "react";
@@ -5900,7 +6078,7 @@ function SegmentedControl({
5900
6078
  children: opt.label
5901
6079
  }
5902
6080
  ),
5903
- !isAction && typeof opt.count === "number" && /* @__PURE__ */ jsx13(
6081
+ !isAction && (typeof opt.count === "number" || typeof opt.count === "string") && /* @__PURE__ */ jsx13(
5904
6082
  "span",
5905
6083
  {
5906
6084
  style: {
@@ -6399,8 +6577,8 @@ var ShuffleMCP = React3.forwardRef(({
6399
6577
  value: sourceFilter,
6400
6578
  onChange: (v) => setSourceFilter(v),
6401
6579
  options: [
6402
- { value: "all", label: "All", count: results.length + filteredPrivateApps.length, title: "All available apps \u2014 both the public catalog and your private apps." },
6403
- { value: "public", label: "Public", count: results.length, title: "Public apps from the Shuffle catalog (powered by Algolia)." },
6580
+ { value: "all", label: "All", count: results.length >= hitsPerPage ? `${results.length + filteredPrivateApps.length}+` : results.length + filteredPrivateApps.length, title: "All available apps \u2014 both the public catalog and your private apps." },
6581
+ { value: "public", label: "Public", count: results.length >= hitsPerPage ? `${results.length}+` : results.length, title: "Public apps from the Shuffle catalog (powered by Algolia)." },
6404
6582
  { value: "private", label: "Private", count: filteredPrivateApps.length, title: "Private apps are apps you have activated in your organization, or your own custom apps \u2014 not just from the public Algolia catalog." },
6405
6583
  { value: "authenticated", label: "Authenticated", count: [...filteredPrivateApps, ...results].filter((a, i, arr) => arr.findIndex((x) => x.name?.toLowerCase() === a.name?.toLowerCase()) === i).filter(isAppConfigured).length, title: "Apps you have authenticated and that are ready to use." }
6406
6584
  ]
@@ -6577,9 +6755,11 @@ function AppSearchDrawer({
6577
6755
  pinnedApps,
6578
6756
  highlightAppName,
6579
6757
  highlightDelayMs = 5e3,
6758
+ realtimeHighlightAppNames,
6580
6759
  multiSelect = false,
6581
6760
  selectedApps,
6582
6761
  onSelectionChange,
6762
+ autoActivate = false,
6583
6763
  globalUrl,
6584
6764
  theme,
6585
6765
  colorMode
@@ -6606,6 +6786,7 @@ function AppSearchDrawer({
6606
6786
  const t = setTimeout(() => setHighlightActive(true), highlightDelayMs);
6607
6787
  return () => clearTimeout(t);
6608
6788
  }, [open, highlightAppName, highlightDelayMs]);
6789
+ const drawerWidth = `min(${width}px, 100vw)`;
6609
6790
  const handleClose = () => {
6610
6791
  setDetailAppName(null);
6611
6792
  setDetailAppId(null);
@@ -6682,29 +6863,38 @@ function AppSearchDrawer({
6682
6863
  anchor,
6683
6864
  open: open && detailAppName === null,
6684
6865
  onClose: handleClose,
6685
- slotProps: {
6686
- paper: {
6687
- className: scopeClassName,
6688
- sx: {
6689
- // Strict, environment-independent sizing so the drawer
6690
- // looks identical on every host (Shuffle Security, /agents,
6691
- // test page, embedded library, etc.). No breakpoint variance.
6692
- width: `min(${width}px, 100vw)`,
6693
- minWidth: `min(${width}px, 100vw)`,
6694
- maxWidth: "100vw",
6695
- height: "100vh",
6696
- display: "flex",
6697
- flexDirection: "column",
6698
- overflow: "hidden",
6699
- background: "linear-gradient(180deg, hsl(var(--card)) 0%, hsl(var(--background)) 100%)",
6700
- borderLeft: anchor === "right" ? "1px solid hsl(var(--border))" : "none",
6701
- borderRight: anchor === "left" ? "1px solid hsl(var(--border))" : "none"
6702
- }
6866
+ PaperProps: {
6867
+ className: scopeClassName,
6868
+ sx: {
6869
+ // Strict, environment-independent sizing so the drawer
6870
+ // looks identical on every host (Shuffle Security, /agents,
6871
+ // test page, embedded library, etc.). No breakpoint variance.
6872
+ // NOTE: use PaperProps (not slotProps.paper) — slotProps.paper
6873
+ // was added in MUI v6 and is silently ignored on MUI v5 hosts,
6874
+ // which made the drawer collapse to content width when
6875
+ // consumed from the published library.
6876
+ width: drawerWidth,
6877
+ minWidth: drawerWidth,
6878
+ maxWidth: drawerWidth,
6879
+ flex: `0 0 ${drawerWidth}`,
6880
+ height: "100vh",
6881
+ display: "flex",
6882
+ flexDirection: "column",
6883
+ overflow: "hidden",
6884
+ background: "linear-gradient(180deg, hsl(var(--card)) 0%, hsl(var(--background)) 100%)",
6885
+ borderLeft: anchor === "right" ? "1px solid hsl(var(--border))" : "none",
6886
+ borderRight: anchor === "left" ? "1px solid hsl(var(--border))" : "none"
6703
6887
  }
6704
6888
  },
6705
6889
  sx: {
6706
6890
  zIndex: 9999,
6707
- "& .MuiDrawer-paper": { boxSizing: "border-box" }
6891
+ "& .MuiDrawer-paper": {
6892
+ boxSizing: "border-box",
6893
+ width: `${drawerWidth} !important`,
6894
+ minWidth: `${drawerWidth} !important`,
6895
+ maxWidth: `${drawerWidth} !important`,
6896
+ flex: `0 0 ${drawerWidth} !important`
6897
+ }
6708
6898
  },
6709
6899
  children: [
6710
6900
  /* @__PURE__ */ jsxs12(
@@ -6736,7 +6926,7 @@ function AppSearchDrawer({
6736
6926
  ]
6737
6927
  }
6738
6928
  ),
6739
- /* @__PURE__ */ jsxs12(Box9, { sx: { flex: 1, boxSizing: "border-box", overflow: "hidden", p: 3, minHeight: 0, display: "flex", flexDirection: "column" }, children: [
6929
+ /* @__PURE__ */ jsxs12(Box9, { sx: { flex: 1, width: "100%", minWidth: 0, boxSizing: "border-box", overflow: "hidden", p: 3, minHeight: 0, display: "flex", flexDirection: "column" }, children: [
6740
6930
  connectionPathApps && connectionPathApps.length > 0 && /* @__PURE__ */ jsxs12(Box9, { sx: { mb: 2.5 }, children: [
6741
6931
  /* @__PURE__ */ jsx15(Typography9, { sx: { color: "hsl(var(--muted-foreground))", fontSize: "0.7rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em", mb: 1 }, children: "In this usecase" }),
6742
6932
  /* @__PURE__ */ jsx15(Box9, { sx: { display: "flex", gap: 1.5, flexWrap: "wrap" }, children: connectionPathApps.map((app) => /* @__PURE__ */ jsx15(Tooltip5, { title: app.name.replace(/_/g, " "), arrow: true, placement: "bottom", children: /* @__PURE__ */ jsxs12(
@@ -6783,6 +6973,8 @@ function AppSearchDrawer({
6783
6973
  {
6784
6974
  sx: {
6785
6975
  flex: 1,
6976
+ width: "100%",
6977
+ minWidth: 0,
6786
6978
  minHeight: 0,
6787
6979
  overflow: "hidden",
6788
6980
  "& .singul-container, & .singul-search-bar-container": {
@@ -6795,16 +6987,22 @@ function AppSearchDrawer({
6795
6987
  },
6796
6988
  "& .singul-results-container": {
6797
6989
  flex: 1,
6990
+ width: "100%",
6991
+ minWidth: 0,
6798
6992
  minHeight: 0,
6799
6993
  maxHeight: "none",
6800
6994
  overflowY: "auto"
6801
6995
  },
6802
6996
  "& .singul-results-grid": {
6997
+ width: "100% !important",
6998
+ minWidth: "0 !important",
6803
6999
  gridTemplateColumns: "repeat(2, minmax(0, 1fr)) !important",
6804
7000
  gridAutoRows: "78px !important",
6805
7001
  gap: "12px !important"
6806
7002
  },
6807
7003
  "& .singul-results-grid .singul-dropdown-item": {
7004
+ width: "100%",
7005
+ maxWidth: "100%",
6808
7006
  minWidth: 0,
6809
7007
  minHeight: "78px !important",
6810
7008
  maxHeight: "78px !important",
@@ -6838,6 +7036,41 @@ function AppSearchDrawer({
6838
7036
  "0%, 100%": { boxShadow: "0 0 0 2px hsl(var(--primary)), 0 0 12px 1px hsl(var(--primary) / 0.4)" },
6839
7037
  "50%": { boxShadow: "0 0 0 2px hsl(var(--primary)), 0 0 24px 4px hsl(var(--primary) / 0.75)" }
6840
7038
  }
7039
+ } : {},
7040
+ ...realtimeHighlightAppNames && realtimeHighlightAppNames.length > 0 ? {
7041
+ [realtimeHighlightAppNames.map((n) => `& .singul-dropdown-item[data-app-name="${n}"]`).join(", ")]: {
7042
+ position: "relative",
7043
+ zIndex: 2,
7044
+ borderColor: "hsl(var(--primary) / 0.78) !important",
7045
+ backgroundColor: "hsl(var(--primary) / 0.08) !important",
7046
+ outline: "none !important",
7047
+ boxShadow: "none !important",
7048
+ animation: "shuffleHighlightPulseStrong 1.5s ease-in-out infinite",
7049
+ transition: "border-color 120ms ease, background-color 120ms ease",
7050
+ "&::before": {
7051
+ content: '""',
7052
+ position: "absolute",
7053
+ left: "10px",
7054
+ top: "14px",
7055
+ bottom: "14px",
7056
+ width: "3px",
7057
+ borderRadius: "999px",
7058
+ backgroundColor: "hsl(var(--primary))"
7059
+ },
7060
+ "& .singul-app-info": {
7061
+ paddingLeft: "8px"
7062
+ }
7063
+ },
7064
+ "@keyframes shuffleHighlightPulseStrong": {
7065
+ "0%, 100%": {
7066
+ borderColor: "hsl(var(--primary) / 0.62)",
7067
+ backgroundColor: "hsl(var(--primary) / 0.06)"
7068
+ },
7069
+ "50%": {
7070
+ borderColor: "hsl(var(--primary) / 0.92)",
7071
+ backgroundColor: "hsl(var(--primary) / 0.12)"
7072
+ }
7073
+ }
6841
7074
  } : {}
6842
7075
  },
6843
7076
  children: /* @__PURE__ */ jsx15(AnimatePresence2, { mode: "wait", children: /* @__PURE__ */ jsx15(
@@ -6860,8 +7093,9 @@ function AppSearchDrawer({
6860
7093
  layout: "grid",
6861
7094
  gridColumns: 2,
6862
7095
  inline: true,
7096
+ initialQuery,
6863
7097
  initialFilterQuery: initialQuery,
6864
- hitsPerPage: 12,
7098
+ hitsPerPage: 20,
6865
7099
  showDescription: false,
6866
7100
  showCategories: true,
6867
7101
  showCheckbox: multiSelect,
@@ -6952,7 +7186,8 @@ function AppSearchDrawer({
6952
7186
  theme,
6953
7187
  colorMode,
6954
7188
  onRefresh: onClose,
6955
- onAddToCanvas
7189
+ onAddToCanvas,
7190
+ autoActivate
6956
7191
  }
6957
7192
  )
6958
7193
  ] });
@@ -7219,7 +7454,7 @@ function ShufflePipelinesBanner() {
7219
7454
  var ShufflePipelinesBanner_default = ShufflePipelinesBanner;
7220
7455
 
7221
7456
  // components/AgentUI.tsx
7222
- import { useCallback as useCallback8, useEffect as useEffect11, useMemo as useMemo6, useRef as useRef8, useState as useState12 } from "react";
7457
+ import React4, { useCallback as useCallback8, useEffect as useEffect11, useMemo as useMemo6, useRef as useRef8, useState as useState12 } from "react";
7223
7458
  import {
7224
7459
  Plus as AddIcon3,
7225
7460
  Paperclip as AttachFileIcon,
@@ -7240,7 +7475,8 @@ import {
7240
7475
  CircleStop as StopCircleIcon,
7241
7476
  ThumbsDown as ThumbDownIcon,
7242
7477
  ThumbsUp as ThumbUpIcon,
7243
- AlertTriangle as WarningIcon
7478
+ AlertTriangle as WarningIcon,
7479
+ Search as SearchIcon
7244
7480
  } from "lucide-react";
7245
7481
  import { useNavigate, useSearchParams } from "react-router-dom";
7246
7482
  import {
@@ -7250,16 +7486,182 @@ import {
7250
7486
  Button as Button8,
7251
7487
  Chip as Chip7,
7252
7488
  CircularProgress as CircularProgress4,
7489
+ ClickAwayListener,
7253
7490
  IconButton as IconButton8,
7254
7491
  InputBase as InputBase2,
7255
7492
  MenuItem as MenuItem2,
7493
+ MenuList,
7494
+ Paper,
7256
7495
  Popover,
7496
+ Popper,
7257
7497
  Select as Select2,
7258
7498
  TextField as TextField5,
7259
7499
  Tooltip as Tooltip9,
7260
7500
  Typography as Typography13
7261
7501
  } from "@mui/material";
7262
7502
  import Markdown from "react-markdown";
7503
+
7504
+ // components/agentPromptSuggestions.ts
7505
+ var AGENT_PROMPT_SUGGESTIONS = [
7506
+ // — Email & comms ————————————————————————————————————————————————
7507
+ "Take care of my emails by pretending to be me and answering them",
7508
+ "Triage my inbox every hour and draft replies for anything urgent",
7509
+ "Summarise my unread emails from the last 24 hours and rank them by importance",
7510
+ "Forward any phishing-looking emails in my inbox to the security team and quarantine them",
7511
+ "Reply to scheduling requests in my inbox using my working hours and calendar availability",
7512
+ "Every morning, send me a digest of overnight emails grouped by sender and topic",
7513
+ "Auto-unsubscribe from marketing emails I have not opened in 60 days",
7514
+ "Snooze low-priority newsletters until Friday afternoon",
7515
+ "Flag and archive shipping notifications once the package is delivered",
7516
+ "Clean up my inbox by archiving anything older than 30 days that I have already read",
7517
+ // — Calendar & meetings ————————————————————————————————————————————
7518
+ "Block focus time on my calendar every weekday morning between 9 and 11",
7519
+ "Decline meetings that conflict with my focus blocks and propose alternatives",
7520
+ "Send me a daily agenda every weekday at 8am with prep notes per meeting",
7521
+ "Reschedule any meeting without an agenda 24 hours before it starts",
7522
+ "Send meeting notes and action items to all attendees after every call",
7523
+ "Find a 30-minute slot this week that works for me and the people I mention",
7524
+ "Cancel recurring meetings that I have skipped 3 times in a row",
7525
+ "Remind me 15 minutes before any meeting that I have not prepared for",
7526
+ // — Incidents & SOC operations ———————————————————————————————————
7527
+ "Triage new incidents every 15 minutes and assign severity, owner, and next steps",
7528
+ "Watch for new critical incidents and page the on-call engineer immediately",
7529
+ "Close incidents older than 30 days that have had no activity and no observables",
7530
+ "Merge duplicate incidents that share the same observable or hostname",
7531
+ "Enrich every new incident with threat intel and asset context within 2 minutes",
7532
+ "Auto-acknowledge incidents from known maintenance windows and link them to the change ticket",
7533
+ "Summarise yesterday's incidents into a daily standup brief for the SOC team",
7534
+ "Escalate any incident that breaches its SLA to the team lead",
7535
+ "Tag every incident with the MITRE ATT&CK technique that best matches its observables",
7536
+ "Watch the alert queue and group related alerts into a single incident",
7537
+ "Auto-resolve incidents whose root cause matches a previously closed incident",
7538
+ "Reopen any closed incident if the same observable shows up again within 7 days",
7539
+ "Send a weekly incident trend report every Monday morning to the security leadership",
7540
+ // — Threat intel & IOCs —————————————————————————————————————————
7541
+ "Pull new IOCs from my threat feeds every hour and push them to the firewall blocklist",
7542
+ "Check every external IP in today's incidents against VirusTotal and AbuseIPDB",
7543
+ "Sweep the environment for any host that has connected to a known bad domain in the last 7 days",
7544
+ "Subscribe to new CISA advisories and turn them into detection rules where possible",
7545
+ "Cross-reference observed file hashes against MalwareBazaar daily",
7546
+ "Watch for newly registered domains that look like our brand and report them",
7547
+ "Alert me whenever a new CVE with a known exploit affects software in our inventory",
7548
+ "Refresh the IOC datastore every 6 hours and prune entries older than 30 days",
7549
+ // — Detection engineering ———————————————————————————————————————
7550
+ "Review noisy detection rules every week and propose tuning suggestions",
7551
+ "Convert the latest Sigma rules from the public repo into our SIEM's format and stage them for review",
7552
+ "Tell me which detection rules have not fired in the last 90 days so I can retire them",
7553
+ "Audit MITRE ATT&CK coverage every Monday and flag techniques with zero detections",
7554
+ "Backtest new detection rules against the last 30 days of logs before promoting them to production",
7555
+ // — SIEM operations ———————————————————————————————————————————
7556
+ "Triage new Splunk alerts every 10 minutes and assign severity, owner, and next steps",
7557
+ "Watch Microsoft Sentinel for high-severity incidents and page the on-call analyst immediately",
7558
+ "Suppress noisy SIEM alerts that fire more than 50 times per hour and open a tuning ticket",
7559
+ "Correlate failed login spikes in the SIEM with VPN logs and flag possible brute-force attempts",
7560
+ "Hunt the SIEM every morning for sign-ins from countries we do not operate in",
7561
+ "Forward critical SIEM detections straight to Case Management and skip the alert queue",
7562
+ "Run a weekly SIEM data-source health check and alert me on any log source silent for over 1 hour",
7563
+ "Enrich every SIEM alert with asset owner, criticality, and recent vulnerabilities before triage",
7564
+ "Search the SIEM for the latest IOCs from threat intel feeds every 30 minutes",
7565
+ "Summarise top 10 noisiest SIEM rules every Friday and propose tuning candidates",
7566
+ "Detect lateral movement patterns in SIEM auth logs and open an incident with the affected hosts",
7567
+ "Watch the SIEM for any disable or delete of audit logging and treat it as a critical incident",
7568
+ // — EDR operations ————————————————————————————————————————————
7569
+ "Isolate any endpoint that triggers a critical CrowdStrike alert and notify the user's manager",
7570
+ "Kill any process on a flagged host that matches a known malware family in EDR telemetry",
7571
+ "Watch SentinelOne for ransomware behavior detections and auto-contain the host within 60 seconds",
7572
+ "Pull the process tree from EDR for every malware detection and attach it to the incident",
7573
+ "Sweep all endpoints for a given hash or filename when a new IOC arrives and report matches",
7574
+ "Detect newly created local admin accounts on any endpoint and require approval before keeping them",
7575
+ "Alert when an EDR agent goes offline for more than 1 hour and open a ticket against the asset owner",
7576
+ "Quarantine any binary flagged as suspicious by EDR and email the user with next steps",
7577
+ "Hunt EDR telemetry every night for LOLBins executed from user-writable directories",
7578
+ "Correlate EDR alerts with SIEM auth events to detect credential theft followed by execution",
7579
+ "Run a weekly EDR coverage report and list endpoints without an active agent",
7580
+ "Auto-resolve EDR alerts that match a known-good signed binary on our allowlist",
7581
+ // — Vulnerability management ————————————————————————————————————
7582
+ "Scan our asset inventory daily and open tickets for any new critical or high vulnerability",
7583
+ "Remind asset owners every Friday about open vulnerabilities older than 14 days",
7584
+ "Re-scan hosts that recently patched a critical vulnerability and close the ticket if clean",
7585
+ "Send the patch compliance report every Monday morning to IT and security leadership",
7586
+ "Correlate vulnerabilities with active exploits in the wild and re-prioritise the queue",
7587
+ // — Endpoint & host monitoring ———————————————————————————————————
7588
+ "Watch my host monitors and alert me if any agent goes offline for more than 1 hour",
7589
+ "Isolate any endpoint that triggers a critical EDR alert and notify the user's manager",
7590
+ "Kill any process on a flagged host that matches a known malware family",
7591
+ "Run an inventory of installed software on every endpoint every Sunday",
7592
+ "Detect new local admin accounts created on any endpoint and require approval",
7593
+ "Make sure full-disk encryption is on for every laptop and report exceptions weekly",
7594
+ // — Identity & access —————————————————————————————————————————
7595
+ "Review user access every quarter and flag anyone with privileges they have not used in 90 days",
7596
+ "Disable accounts that have not logged in for 60 days and notify their manager",
7597
+ "Watch for impossible travel sign-ins and force a password reset plus MFA re-enrolment",
7598
+ "Alert me whenever someone is added to an admin group and require justification within 24 hours",
7599
+ "Audit MFA enrolment weekly and email any user without a second factor",
7600
+ "Rotate service account credentials every 90 days and update the vault",
7601
+ "Detect dormant API keys older than 180 days and revoke them after notice",
7602
+ "Make sure new joiners get baseline access provisioned within 1 hour of their start date",
7603
+ "Make sure leavers have all access revoked within 15 minutes of their termination event",
7604
+ // — Cloud, infra & posture ————————————————————————————————————
7605
+ "Check our cloud accounts every hour for public S3 buckets and lock them down",
7606
+ "Detect new IAM roles with wildcard permissions and require review before they go live",
7607
+ "Watch for unencrypted databases in production and open a ticket against the owner",
7608
+ "Run a CIS benchmark scan against all production cloud accounts every Sunday",
7609
+ "Alert on any new security group that opens ports to 0.0.0.0/0",
7610
+ "Monitor cloud spend daily and warn me if any service's cost jumps more than 50% week-over-week",
7611
+ "Check certificate expiry across our domains weekly and renew anything within 30 days",
7612
+ "Watch our DNS records for unauthorised changes and revert them",
7613
+ // — Phishing & user reports ———————————————————————————————————
7614
+ "Investigate phishing reports from the abuse mailbox and respond to the reporter within 1 hour",
7615
+ "Detonate suspicious attachments in a sandbox and attach the verdict to the incident",
7616
+ "Pull URLs from reported phishing emails and submit them to URLhaus and PhishTank",
7617
+ "Send a phishing simulation to a sample of users every month and report results",
7618
+ // — Compliance, audit & reporting —————————————————————————————
7619
+ "Generate the SOC2 evidence pack at the start of every month",
7620
+ "Track changes to security policies and alert the GRC team when one is edited",
7621
+ "Send a weekly KPI report covering MTTD, MTTR, and open incident backlog",
7622
+ "Audit log retention every Sunday and confirm we still meet the 1-year requirement",
7623
+ "Generate a monthly executive summary of incidents, vulnerabilities, and detection coverage",
7624
+ // — Backups, DR & uptime ————————————————————————————————————
7625
+ "Verify nightly backups completed successfully and alert me if any failed",
7626
+ "Test restore from backup on a sample workload every month and report results",
7627
+ "Watch our public status page for incidents at our critical vendors and notify the team",
7628
+ "Ping our public endpoints every minute and open an incident if any return non-2xx for 3 minutes",
7629
+ // — Tickets, workflow & docs ——————————————————————————————————
7630
+ "Triage new Jira tickets in the security project every hour and assign by category",
7631
+ "Close tickets that have been waiting on the reporter for more than 14 days",
7632
+ "Summarise this week's closed tickets into release notes every Friday",
7633
+ "Pull action items out of meeting notes I share and create tickets for them",
7634
+ "Keep the on-call runbook in sync with the latest incident postmortems",
7635
+ "Review pull requests in our security repos and leave a summary comment within an hour",
7636
+ // — People, HR & onboarding ————————————————————————————————————
7637
+ "Send a welcome message and security training link to every new joiner on their start date",
7638
+ "Run the offboarding checklist whenever someone is marked as a leaver in HR",
7639
+ "Remind managers to complete quarterly performance reviews 1 week before the deadline",
7640
+ "Send birthday and work anniversary greetings from me on the right day",
7641
+ // — Personal / lightweight ops chores —————————————————————————
7642
+ "Order more office coffee whenever the inventory drops below 2 bags",
7643
+ "File my receipts every Friday and submit the expense report",
7644
+ "Track my open invoices and chase clients whose payment is more than 14 days overdue",
7645
+ "Send a polite follow-up to anyone I have emailed but who has not replied in 5 working days",
7646
+ "Read the security news feeds every morning and summarise the top 5 stories for me"
7647
+ ];
7648
+ var matchAgentPromptSuggestions = (input, limit = 8) => {
7649
+ const q = (input || "").trim().toLowerCase();
7650
+ if (!q) return [];
7651
+ const scored = [];
7652
+ for (const text of AGENT_PROMPT_SUGGESTIONS) {
7653
+ const lower = text.toLowerCase();
7654
+ const idx = lower.indexOf(q);
7655
+ if (idx < 0) continue;
7656
+ const score = idx === 0 ? -1e3 + text.length : idx * 10 + text.length;
7657
+ scored.push({ text, score });
7658
+ }
7659
+ scored.sort((a, b) => a.score - b.score);
7660
+ return scored.slice(0, limit).map((s) => s.text);
7661
+ };
7662
+ var DEFAULT_AGENT_PROMPT_PLACEHOLDER = "Take care of my emails by pretending to be me and answering them";
7663
+
7664
+ // components/AgentUI.tsx
7263
7665
  import remarkGfm4 from "remark-gfm";
7264
7666
  import remarkBreaks from "remark-breaks";
7265
7667
  import JsonView2 from "react18-json-view";
@@ -7402,6 +7804,73 @@ function invalidateResolvedApps() {
7402
7804
  _cache.clear();
7403
7805
  }
7404
7806
 
7807
+ // llmProviderDetect.ts
7808
+ var SHUFFLE_AI_PRESET = "Shuffle AI";
7809
+ var CUSTOM_PRESET = "Custom / self-hosted";
7810
+ var ENDPOINT_PRESETS = [
7811
+ { label: SHUFFLE_AI_PRESET, url: "" },
7812
+ { label: "OpenAI", url: "https://api.openai.com/v1", apiKeyUrl: "https://platform.openai.com/api-keys", apiKeyHint: "Create a key under API keys in the OpenAI platform dashboard." },
7813
+ { label: "Anthropic", url: "https://api.anthropic.com/v1/", apiKeyUrl: "https://console.anthropic.com/settings/keys", apiKeyHint: "Generate a key under Settings \u2192 API Keys in the Anthropic Console." },
7814
+ { label: "Google Gemini", url: "https://generativelanguage.googleapis.com/v1beta/openai/", apiKeyUrl: "https://aistudio.google.com/app/apikey", apiKeyHint: "Create a key in Google AI Studio under Get API key." },
7815
+ { label: "Mistral", url: "https://api.mistral.ai/v1", apiKeyUrl: "https://console.mistral.ai/api-keys/", apiKeyHint: "Create a key under API Keys in the Mistral Console." },
7816
+ { label: "Groq", url: "https://api.groq.com/openai/v1", apiKeyUrl: "https://console.groq.com/keys", apiKeyHint: "Create a key under API Keys in the Groq Console." },
7817
+ { label: "DeepSeek", url: "https://api.deepseek.com/v1", apiKeyUrl: "https://platform.deepseek.com/api_keys", apiKeyHint: "Create a key under API Keys in the DeepSeek platform." },
7818
+ { label: "Together AI", url: "https://api.together.xyz/v1", apiKeyUrl: "https://api.together.ai/settings/api-keys", apiKeyHint: "Create a key under Settings \u2192 API Keys in Together AI." },
7819
+ { label: "OpenRouter", url: "https://openrouter.ai/api/v1", apiKeyUrl: "https://openrouter.ai/keys", apiKeyHint: "Create a key under Keys in your OpenRouter dashboard." },
7820
+ { label: "Ollama (localhost)", url: "http://localhost:11434/v1", apiKeyHint: "Local Ollama does not require an API key \u2014 any non-empty value works." },
7821
+ { label: "LM Studio (localhost)", url: "http://localhost:1234/v1", apiKeyHint: "Local LM Studio does not require an API key \u2014 any non-empty value works." },
7822
+ { label: CUSTOM_PRESET, url: "" }
7823
+ ];
7824
+ var PROVIDER_DOMAINS = {
7825
+ "Shuffle AI": "shuffler.io",
7826
+ OpenAI: "openai.com",
7827
+ Anthropic: "anthropic.com",
7828
+ "Google Gemini": "gemini.google.com",
7829
+ Mistral: "mistral.ai",
7830
+ Groq: "groq.com",
7831
+ DeepSeek: "deepseek.com",
7832
+ "Together AI": "together.ai",
7833
+ OpenRouter: "openrouter.ai",
7834
+ "Ollama (localhost)": "ollama.com",
7835
+ "LM Studio (localhost)": "lmstudio.ai"
7836
+ };
7837
+ var hostnameOf = (url) => {
7838
+ try {
7839
+ return new URL(url).hostname.replace(/^www\./, "");
7840
+ } catch {
7841
+ return null;
7842
+ }
7843
+ };
7844
+ var detectLLMProvider = (url) => {
7845
+ if (!url) return null;
7846
+ const trimmed = url.trim();
7847
+ if (!trimmed) return null;
7848
+ const exact = ENDPOINT_PRESETS.find((p) => p.url && p.url === trimmed);
7849
+ if (exact) return exact;
7850
+ const host = hostnameOf(trimmed);
7851
+ if (host) {
7852
+ const byHost = ENDPOINT_PRESETS.find((p) => {
7853
+ const presetHost = hostnameOf(p.url);
7854
+ return presetHost && presetHost === host;
7855
+ });
7856
+ if (byHost) return byHost;
7857
+ if (host === "localhost" || host === "127.0.0.1") {
7858
+ if (trimmed.includes(":11434")) return ENDPOINT_PRESETS.find((p) => p.label === "Ollama (localhost)") || null;
7859
+ if (trimmed.includes(":1234")) return ENDPOINT_PRESETS.find((p) => p.label === "LM Studio (localhost)") || null;
7860
+ }
7861
+ }
7862
+ return ENDPOINT_PRESETS.find((p) => p.label === CUSTOM_PRESET) || null;
7863
+ };
7864
+ var getProviderLogoUrl = (label, url) => {
7865
+ if (!label && !url) return "";
7866
+ let domain = label ? PROVIDER_DOMAINS[label] : void 0;
7867
+ if (!domain && url) {
7868
+ const host = hostnameOf(url);
7869
+ if (host) domain = host;
7870
+ }
7871
+ return domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=64` : "";
7872
+ };
7873
+
7405
7874
  // agentRun.ts
7406
7875
  var resolveTarget = (path, opts = {}) => {
7407
7876
  const url = opts.apiBaseUrl ? `${opts.apiBaseUrl.replace(/\/+$/, "")}${path}` : getApiUrl(path);
@@ -8315,10 +8784,10 @@ var AgentRunDiagnosisBanner = ({ run, sx, onJumpToEvidence, executionId }) => {
8315
8784
  const outputWarning = !isFailed && hasOutputWarning(run);
8316
8785
  const diagnosis = outputWarning ? diagnoseOutputWarning(run) : null;
8317
8786
  if (!failureInfo && !diagnosis) return null;
8318
- const isCritical = !!failureInfo;
8787
+ const isCritical = !!failureInfo || diagnosis?.kind === "token_limit";
8319
8788
  const tone = isCritical ? "critical" : "medium";
8320
8789
  const Icon = isCritical ? AlertTriangle : HelpCircle;
8321
- const message = isCritical ? failureInfo.reason : `${diagnosis.title} \u2014 ${diagnosis.remediation}`;
8790
+ const message = failureInfo ? failureInfo.reason : `${diagnosis.title} \u2014 ${diagnosis.remediation}`;
8322
8791
  const firstEvidence = diagnosis?.evidence?.[0] || null;
8323
8792
  const jumpDecisionIndex = firstEvidence ? extractDecisionIndex(firstEvidence.path) : null;
8324
8793
  const canJump = !!(onJumpToEvidence && jumpDecisionIndex !== null);
@@ -8857,7 +9326,7 @@ var TimelineRow = ({
8857
9326
  ),
8858
9327
  /* @__PURE__ */ jsx20(Box14, { sx: {
8859
9328
  flex: 1,
8860
- minWidth: 0,
9329
+ minWidth: 180,
8861
9330
  fontSize: "0.85rem",
8862
9331
  color: "hsl(var(--foreground))",
8863
9332
  maxHeight: 60,
@@ -8867,7 +9336,7 @@ var TimelineRow = ({
8867
9336
  "& a": { color: "hsl(var(--primary))", textDecoration: "underline", textUnderlineOffset: "2px" },
8868
9337
  "& a:hover": { opacity: 0.85 }
8869
9338
  }, children: /* @__PURE__ */ jsx20(Markdown, { remarkPlugins: [remarkGfm4, remarkBreaks], children: normalizeMarkdown(displayLabel) }) }),
8870
- /* @__PURE__ */ jsx20(Tooltip9, { title: hasTiming ? `Duration: ${dur.toFixed(2)}s \xB7 ${new Date(itemStart * 1e3).toLocaleString()}` : "No timing data", children: /* @__PURE__ */ jsx20(Box14, { sx: { width: maxWidth, position: "relative", height: 10, flexShrink: 0 }, children: dur > 0 && /* @__PURE__ */ jsx20(Box14, { sx: {
9339
+ /* @__PURE__ */ jsx20(Tooltip9, { title: hasTiming ? `Duration: ${dur.toFixed(2)}s \xB7 ${new Date(itemStart * 1e3).toLocaleString()}` : "No timing data", children: /* @__PURE__ */ jsx20(Box14, { sx: { width: maxWidth, maxWidth, minWidth: 40, position: "relative", height: 10, flexShrink: 1, flexBasis: maxWidth }, children: dur > 0 && /* @__PURE__ */ jsx20(Box14, { sx: {
8871
9340
  position: "absolute",
8872
9341
  left: Math.max(0, offset),
8873
9342
  width,
@@ -9193,7 +9662,7 @@ var AgentUI = ({
9193
9662
  autoLoadApps = true,
9194
9663
  title = "What do you want to do?",
9195
9664
  subtitle,
9196
- placeholder = 'Describe a task, e.g. "Get my emails for today and summarise them"',
9665
+ placeholder = DEFAULT_AGENT_PROMPT_PLACEHOLDER,
9197
9666
  defaultInput = "",
9198
9667
  autoSubmit = false,
9199
9668
  hideHeroIcon = false,
@@ -9245,6 +9714,29 @@ var AgentUI = ({
9245
9714
  const hasApiKey = !!apiKey || !!API_CONFIG.apiKey;
9246
9715
  const navigate = useNavigate();
9247
9716
  const [actionInput, setActionInput] = useState12(defaultInput);
9717
+ const promptAnchorRef = React4.useRef(null);
9718
+ const [suggestionsDismissed, setSuggestionsDismissed] = useState12(false);
9719
+ const [suggestionIndex, setSuggestionIndex] = useState12(-1);
9720
+ const promptSuggestions = useMemo6(
9721
+ () => matchAgentPromptSuggestions(actionInput, 8),
9722
+ [actionInput]
9723
+ );
9724
+ useEffect11(() => {
9725
+ setSuggestionIndex(-1);
9726
+ setSuggestionsDismissed(false);
9727
+ }, [actionInput]);
9728
+ const suggestionsOpen = promptSuggestions.length > 0 && !suggestionsDismissed;
9729
+ const acceptSuggestion = useCallback8((s) => {
9730
+ setActionInput(s);
9731
+ setSuggestionsDismissed(true);
9732
+ setSuggestionIndex(-1);
9733
+ requestAnimationFrame(() => {
9734
+ try {
9735
+ inputRef.current?.focus();
9736
+ } catch {
9737
+ }
9738
+ });
9739
+ }, []);
9248
9740
  const BUILTIN_DEFAULT_APPS = [
9249
9741
  { name: "http" },
9250
9742
  { name: "shuffle_tools" }
@@ -9252,6 +9744,7 @@ var AgentUI = ({
9252
9744
  const [chosenApps, setChosenApps] = useState12(apps ?? defaultApps ?? BUILTIN_DEFAULT_APPS);
9253
9745
  const [availableApps, setAvailableApps] = useState12([]);
9254
9746
  const [authAppsLoading, setAuthAppsLoading] = useState12(autoLoadApps && hasApiKey);
9747
+ const [detectedLLM, setDetectedLLM] = useState12(null);
9255
9748
  const [executionApps, setExecutionApps] = useState12([]);
9256
9749
  const [resolvedToolApps, setResolvedToolApps] = useState12({});
9257
9750
  const [appSearchOpen, setAppSearchOpen] = useState12(false);
@@ -9286,9 +9779,25 @@ var AgentUI = ({
9286
9779
  const [error, setError] = useState12(null);
9287
9780
  const [searchParams, setSearchParams] = useSearchParams();
9288
9781
  const initialViewParam = searchParams.get("agentView");
9289
- const [viewMode, setViewMode] = useState12(
9290
- initialViewParam === "detailed" ? "detailed" : "simple"
9291
- );
9782
+ const readStoredViewMode = () => {
9783
+ if (initialViewParam === "detailed") return "detailed";
9784
+ if (typeof window === "undefined") return "simple";
9785
+ try {
9786
+ const stored = window.localStorage.getItem("shuffle-agents-view-mode");
9787
+ if (stored === "detailed" || stored === "simple") return stored;
9788
+ } catch {
9789
+ }
9790
+ return "simple";
9791
+ };
9792
+ const [viewMode, setViewMode] = useState12(readStoredViewMode);
9793
+ useEffect11(() => {
9794
+ try {
9795
+ if (typeof window !== "undefined") {
9796
+ window.localStorage.setItem("shuffle-agents-view-mode", viewMode);
9797
+ }
9798
+ } catch {
9799
+ }
9800
+ }, [viewMode]);
9292
9801
  const [attachedImages, setAttachedImages] = useState12([]);
9293
9802
  const [nowTick, setNowTick] = useState12(() => Math.floor(Date.now() / 1e3));
9294
9803
  const [localRunStart, setLocalRunStart] = useState12(null);
@@ -9524,12 +10033,23 @@ var AgentUI = ({
9524
10033
  const list = Array.isArray(result) ? result : result?.data || [];
9525
10034
  const seen = /* @__PURE__ */ new Set();
9526
10035
  const loaded = [];
10036
+ let detectedFromEntries = null;
9527
10037
  for (const entry of list) {
9528
10038
  const app = entry?.app || entry;
9529
10039
  const name = app?.name;
9530
10040
  if (!name) continue;
9531
10041
  const valid = entry?.active || entry?.validation?.valid || entry?.hasValidAuth || app?.is_valid || app?.tested;
9532
10042
  if (valid === false) continue;
10043
+ if (!detectedFromEntries && (name.toLowerCase() === "openai" || app?.id === "5d19dd82517870c68d40cacad9b5ca91")) {
10044
+ const urlField = (entry?.fields || []).find((f) => (f?.key || "").toLowerCase() === "url");
10045
+ const urlVal = (urlField?.value || "").trim();
10046
+ if (urlVal) {
10047
+ const preset = detectLLMProvider(urlVal);
10048
+ if (preset) {
10049
+ detectedFromEntries = { label: preset.label, url: urlVal, logo: getProviderLogoUrl(preset.label, urlVal) };
10050
+ }
10051
+ }
10052
+ }
9533
10053
  const key = normalizeAgentAppName(name);
9534
10054
  if (seen.has(key)) continue;
9535
10055
  seen.add(key);
@@ -9541,6 +10061,7 @@ var AgentUI = ({
9541
10061
  }
9542
10062
  if (signal?.cancelled) return;
9543
10063
  setAvailableApps(loaded);
10064
+ setDetectedLLM(detectedFromEntries);
9544
10065
  } catch {
9545
10066
  } finally {
9546
10067
  if (!signal?.cancelled) setAuthAppsLoading(false);
@@ -9802,7 +10323,12 @@ var AgentUI = ({
9802
10323
  setSimpleSubmitAttempted(false);
9803
10324
  setError(null);
9804
10325
  setLocalRunStart(null);
9805
- setViewMode("simple");
10326
+ try {
10327
+ const stored = typeof window !== "undefined" ? window.localStorage.getItem("shuffle-agents-view-mode") : null;
10328
+ setViewMode(stored === "detailed" ? "detailed" : "simple");
10329
+ } catch {
10330
+ setViewMode("simple");
10331
+ }
9806
10332
  setShowStarter(false);
9807
10333
  userPickedStartRef.current = false;
9808
10334
  if (readUrlParams && typeof window !== "undefined") {
@@ -10238,6 +10764,34 @@ var AgentUI = ({
10238
10764
  }
10239
10765
  }, [submitOverride, actionInput, chosenApps, submitInput]);
10240
10766
  const onKeyDown = (e) => {
10767
+ if (suggestionsOpen && promptSuggestions.length > 0) {
10768
+ if (e.key === "ArrowDown") {
10769
+ e.preventDefault();
10770
+ setSuggestionIndex((i) => (i + 1) % promptSuggestions.length);
10771
+ return;
10772
+ }
10773
+ if (e.key === "ArrowUp") {
10774
+ e.preventDefault();
10775
+ setSuggestionIndex((i) => i <= 0 ? promptSuggestions.length - 1 : i - 1);
10776
+ return;
10777
+ }
10778
+ if (e.key === "Escape") {
10779
+ e.preventDefault();
10780
+ setSuggestionsDismissed(true);
10781
+ setSuggestionIndex(-1);
10782
+ return;
10783
+ }
10784
+ if (e.key === "Tab" && suggestionIndex >= 0) {
10785
+ e.preventDefault();
10786
+ acceptSuggestion(promptSuggestions[suggestionIndex]);
10787
+ return;
10788
+ }
10789
+ if (e.key === "Enter" && suggestionIndex >= 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey) {
10790
+ e.preventDefault();
10791
+ acceptSuggestion(promptSuggestions[suggestionIndex]);
10792
+ return;
10793
+ }
10794
+ }
10241
10795
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
10242
10796
  e.preventDefault();
10243
10797
  handlePrimarySubmit();
@@ -10895,7 +11449,7 @@ var AgentUI = ({
10895
11449
  mt: -1,
10896
11450
  maxWidth: 600
10897
11451
  }, children: subtitle }),
10898
- /* @__PURE__ */ jsxs17(Box14, { sx: {
11452
+ /* @__PURE__ */ jsxs17(Box14, { ref: promptAnchorRef, sx: {
10899
11453
  width: "100%",
10900
11454
  display: "flex",
10901
11455
  flexDirection: "column",
@@ -11103,6 +11657,67 @@ var AgentUI = ({
11103
11657
  ) }) })
11104
11658
  ] })
11105
11659
  ] }),
11660
+ /* @__PURE__ */ jsx20(
11661
+ Popper,
11662
+ {
11663
+ open: suggestionsOpen,
11664
+ anchorEl: promptAnchorRef.current,
11665
+ placement: "bottom-start",
11666
+ style: { zIndex: 1300, width: promptAnchorRef.current?.offsetWidth },
11667
+ modifiers: [{ name: "offset", options: { offset: [0, 6] } }],
11668
+ children: /* @__PURE__ */ jsx20(ClickAwayListener, { onClickAway: () => setSuggestionsDismissed(true), children: /* @__PURE__ */ jsx20(
11669
+ Paper,
11670
+ {
11671
+ elevation: 6,
11672
+ sx: {
11673
+ bgcolor: "hsl(var(--card))",
11674
+ border: "1px solid hsl(var(--border))",
11675
+ borderRadius: 2,
11676
+ overflow: "hidden",
11677
+ maxHeight: 320,
11678
+ overflowY: "auto"
11679
+ },
11680
+ children: /* @__PURE__ */ jsx20(MenuList, { dense: true, disablePadding: true, children: promptSuggestions.map((s, i) => {
11681
+ const q = actionInput.trim();
11682
+ const lower = s.toLowerCase();
11683
+ const idx = q ? lower.indexOf(q.toLowerCase()) : -1;
11684
+ const before = idx >= 0 ? s.slice(0, idx) : s;
11685
+ const match = idx >= 0 ? s.slice(idx, idx + q.length) : "";
11686
+ const after = idx >= 0 ? s.slice(idx + q.length) : "";
11687
+ return /* @__PURE__ */ jsxs17(
11688
+ MenuItem2,
11689
+ {
11690
+ selected: i === suggestionIndex,
11691
+ onMouseEnter: () => setSuggestionIndex(i),
11692
+ onMouseDown: (e) => {
11693
+ e.preventDefault();
11694
+ acceptSuggestion(s);
11695
+ },
11696
+ sx: {
11697
+ fontSize: "0.88rem",
11698
+ color: "hsl(var(--foreground))",
11699
+ py: 0.75,
11700
+ px: 2,
11701
+ "&.Mui-selected, &.Mui-selected:hover": {
11702
+ bgcolor: "hsl(var(--muted) / 0.6)"
11703
+ }
11704
+ },
11705
+ children: [
11706
+ /* @__PURE__ */ jsx20(SearchIcon, { size: 14, style: { marginRight: 10, color: "hsl(var(--muted-foreground))", flexShrink: 0 } }),
11707
+ /* @__PURE__ */ jsxs17(Box14, { component: "span", sx: { whiteSpace: "normal", lineHeight: 1.4 }, children: [
11708
+ before,
11709
+ /* @__PURE__ */ jsx20(Box14, { component: "span", sx: { fontWeight: 700, color: "hsl(var(--foreground))" }, children: match }),
11710
+ after
11711
+ ] })
11712
+ ]
11713
+ },
11714
+ s
11715
+ );
11716
+ }) })
11717
+ }
11718
+ ) })
11719
+ }
11720
+ ),
11106
11721
  !hideAppPicker && /* @__PURE__ */ jsx20(Box14, { sx: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxs17(Box14, { sx: {
11107
11722
  display: "inline-flex",
11108
11723
  flexWrap: "wrap",
@@ -11153,8 +11768,19 @@ var AgentUI = ({
11153
11768
  "&:hover": { color: "hsl(var(--foreground))", bgcolor: "hsl(var(--muted) / 0.5)" }
11154
11769
  },
11155
11770
  children: [
11156
- /* @__PURE__ */ jsx20(SettingsIcon, { size: 14 }),
11157
- "Choose LLM"
11771
+ detectedLLM?.logo ? /* @__PURE__ */ jsx20(
11772
+ Box14,
11773
+ {
11774
+ component: "img",
11775
+ src: detectedLLM.logo,
11776
+ alt: "",
11777
+ sx: { width: 14, height: 14, borderRadius: "3px", objectFit: "contain", display: "block" },
11778
+ onError: (e) => {
11779
+ e.currentTarget.style.display = "none";
11780
+ }
11781
+ }
11782
+ ) : /* @__PURE__ */ jsx20(SettingsIcon, { size: 14 }),
11783
+ detectedLLM?.label || "Choose LLM"
11158
11784
  ]
11159
11785
  }
11160
11786
  ) }),
@@ -11388,7 +12014,7 @@ var AgentUI = ({
11388
12014
  AgentRunDiagnosisBanner_default,
11389
12015
  {
11390
12016
  run: execution?.results?.length ? execution : agentData,
11391
- sx: { px: 0, pb: 0, mb: 2 },
12017
+ sx: { px: 1.5, pb: 0, mb: 2 },
11392
12018
  executionId: execution?.execution_id,
11393
12019
  onJumpToEvidence: (decisionIndex) => {
11394
12020
  const dec = (agentData?.decisions || [])[decisionIndex];
@@ -11613,14 +12239,14 @@ var AgentUI = ({
11613
12239
  ] })
11614
12240
  ] });
11615
12241
  })() : !finishAnswer && isRunning ? /* @__PURE__ */ jsx20(Typography13, { sx: { fontSize: "0.85rem", color: "hsl(var(--muted-foreground))" }, children: "Waiting for the agent to produce the first step." }) : !finishAnswer ? /* @__PURE__ */ jsx20(Typography13, { sx: { fontSize: "0.85rem", color: "hsl(var(--muted-foreground))" }, children: "No final answer was returned. Open Detailed to inspect each step." }) : null,
11616
- !isRunning && /* @__PURE__ */ jsx20(Box14, { children: /* @__PURE__ */ jsx20(
12242
+ /* @__PURE__ */ jsx20(Box14, { children: /* @__PURE__ */ jsx20(
11617
12243
  Button8,
11618
12244
  {
11619
12245
  size: "small",
11620
12246
  variant: "text",
11621
12247
  onClick: () => setViewMode("detailed"),
11622
12248
  sx: { color: "hsl(var(--primary))", textTransform: "none", px: 0 },
11623
- children: "View detailed timeline \u2192"
12249
+ children: isRunning ? "View live detailed timeline \u2192" : "View detailed timeline \u2192"
11624
12250
  }
11625
12251
  ) })
11626
12252
  ] });
@@ -11825,7 +12451,7 @@ import {
11825
12451
  } from "lucide-react";
11826
12452
 
11827
12453
  // components/LocalLLMConfig.tsx
11828
- import { useEffect as useEffect12, useMemo as useMemo7, useRef as useRef9, useState as useState13 } from "react";
12454
+ import { useEffect as useEffect12, useMemo as useMemo7, useState as useState13 } from "react";
11829
12455
  import {
11830
12456
  Autocomplete as Autocomplete3,
11831
12457
  Box as Box16,
@@ -11924,8 +12550,6 @@ import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-r
11924
12550
  import { createElement as createElement2 } from "react";
11925
12551
  var OPENAI_APP_NAME = "OpenAI";
11926
12552
  var OPENAI_APP_ID = "5d19dd82517870c68d40cacad9b5ca91";
11927
- var SHUFFLE_AI_PRESET = "Shuffle AI";
11928
- var CUSTOM_PRESET = "Custom / self-hosted";
11929
12553
  var OPENAI_ALGOLIA_APP = {
11930
12554
  name: OPENAI_APP_NAME,
11931
12555
  description: "OpenAI-compatible LLM endpoint for agent operations",
@@ -11945,20 +12569,32 @@ var OPENAI_ALGOLIA_APP = {
11945
12569
  triggers: [],
11946
12570
  verified: true
11947
12571
  };
11948
- var ENDPOINT_PRESETS = [
11949
- { label: SHUFFLE_AI_PRESET, url: "" },
11950
- { label: "OpenAI", url: "https://api.openai.com/v1", apiKeyUrl: "https://platform.openai.com/api-keys", apiKeyHint: "Create a key under API keys in the OpenAI platform dashboard." },
11951
- { label: "Anthropic", url: "https://api.anthropic.com/v1/", apiKeyUrl: "https://console.anthropic.com/settings/keys", apiKeyHint: "Generate a key under Settings \u2192 API Keys in the Anthropic Console." },
11952
- { label: "Google Gemini", url: "https://generativelanguage.googleapis.com/v1beta/openai/", apiKeyUrl: "https://aistudio.google.com/app/apikey", apiKeyHint: "Create a key in Google AI Studio under Get API key." },
11953
- { label: "Mistral", url: "https://api.mistral.ai/v1", apiKeyUrl: "https://console.mistral.ai/api-keys/", apiKeyHint: "Create a key under API Keys in the Mistral Console." },
11954
- { label: "Groq", url: "https://api.groq.com/openai/v1", apiKeyUrl: "https://console.groq.com/keys", apiKeyHint: "Create a key under API Keys in the Groq Console." },
11955
- { label: "DeepSeek", url: "https://api.deepseek.com/v1", apiKeyUrl: "https://platform.deepseek.com/api_keys", apiKeyHint: "Create a key under API Keys in the DeepSeek platform." },
11956
- { label: "Together AI", url: "https://api.together.xyz/v1", apiKeyUrl: "https://api.together.ai/settings/api-keys", apiKeyHint: "Create a key under Settings \u2192 API Keys in Together AI." },
11957
- { label: "OpenRouter", url: "https://openrouter.ai/api/v1", apiKeyUrl: "https://openrouter.ai/keys", apiKeyHint: "Create a key under Keys in your OpenRouter dashboard." },
11958
- { label: "Ollama (localhost)", url: "http://localhost:11434/v1", apiKeyHint: "Local Ollama does not require an API key \u2014 any non-empty value works." },
11959
- { label: "LM Studio (localhost)", url: "http://localhost:1234/v1", apiKeyHint: "Local LM Studio does not require an API key \u2014 any non-empty value works." },
11960
- { label: CUSTOM_PRESET, url: "" }
11961
- ];
12572
+ var CUSTOM_MODEL = "Custom\u2026";
12573
+ var PROVIDER_MODELS = {
12574
+ OpenAI: ["gpt-5.5", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-5.2", "gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "o4-mini"],
12575
+ Anthropic: ["claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5", "claude-opus-4", "claude-sonnet-4", "claude-3-7-sonnet-latest"],
12576
+ "Google Gemini": ["gemini-3-pro-preview", "gemini-3-flash-preview", "gemini-3.1-pro-preview", "gemini-3.1-flash-lite-preview", "gemini-3.5-flash", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"],
12577
+ Mistral: ["mistral-large-2026", "mistral-medium-3", "mistral-small-3.2", "codestral-2026", "magistral-medium-2026", "ministral-8b-latest"],
12578
+ Groq: ["llama-4-maverick-17b-128e", "llama-4-scout-17b-16e", "llama-3.3-70b-versatile", "deepseek-r1-distill-llama-70b", "qwen-3-32b", "kimi-k2-instruct"],
12579
+ DeepSeek: ["deepseek-v3.5", "deepseek-v3", "deepseek-r1", "deepseek-coder-v3"],
12580
+ "Together AI": ["meta-llama/Llama-4-Maverick-17B-128E-Instruct", "meta-llama/Llama-4-Scout-17B-16E-Instruct", "deepseek-ai/DeepSeek-V3", "deepseek-ai/DeepSeek-R1", "Qwen/Qwen3-235B-A22B"],
12581
+ OpenRouter: ["openai/gpt-5.5", "openai/gpt-5.4", "anthropic/claude-opus-4.5", "anthropic/claude-sonnet-4.5", "google/gemini-3-pro-preview", "google/gemini-3-flash-preview", "meta-llama/llama-4-maverick", "deepseek/deepseek-v3.5", "x-ai/grok-4"],
12582
+ "Ollama (localhost)": ["llama3.3", "llama3.2", "qwen3", "qwen3:32b", "deepseek-r1", "deepseek-r1:70b", "mistral-small3", "phi4", "gemma3"],
12583
+ "LM Studio (localhost)": ["llama-3.3-70b-instruct", "qwen3-32b", "deepseek-r1-distill-qwen-32b", "mistral-small-3", "phi-4", "gemma-3-27b"]
12584
+ };
12585
+ var ProviderLogo = ({ label, url }) => {
12586
+ const [errored, setErrored] = useState13(false);
12587
+ let domain = PROVIDER_DOMAINS[label];
12588
+ if (!domain && url) {
12589
+ try {
12590
+ domain = new URL(url).hostname.replace(/^www\./, "");
12591
+ } catch {
12592
+ }
12593
+ }
12594
+ const src = domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=64` : "";
12595
+ const initial = label.trim().charAt(0).toUpperCase() || "?";
12596
+ return /* @__PURE__ */ jsx22(Box16, { sx: { width: 18, height: 18, borderRadius: "4px", display: "flex", alignItems: "center", justifyContent: "center", bgcolor: "hsl(var(--muted))", overflow: "hidden", flexShrink: 0 }, children: src && !errored ? /* @__PURE__ */ jsx22("img", { src, alt: "", width: 18, height: 18, style: { display: "block" }, onError: () => setErrored(true) }) : /* @__PURE__ */ jsx22(Typography15, { sx: { fontSize: "0.65rem", fontWeight: 600, color: "hsl(var(--muted-foreground))", lineHeight: 1 }, children: initial }) });
12597
+ };
11962
12598
  var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serverside, theme, colorMode }) => {
11963
12599
  useSyncHostBaseUrl(globalUrl);
11964
12600
  const { authStates, authenticatedApps, handleAuthChange, handleTestConnection, handleSaveAuth, refreshAuth } = useAppAuth();
@@ -11974,46 +12610,16 @@ var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serv
11974
12610
  status: "pending",
11975
12611
  credentials: {}
11976
12612
  };
11977
- const currentUrl = authState.credentials?.url || "";
11978
- const attemptedDeletionRef = useRef9(/* @__PURE__ */ new Set());
11979
- useEffect12(() => {
11980
- if (authState.status === "testing") return;
11981
- const failedEntries = openaiEntries.filter((e) => e.validation && e.validation.valid === false && e.id);
11982
- if (failedEntries.length === 0) return;
11983
- let cancelled = false;
11984
- (async () => {
11985
- let deletedAny = false;
11986
- for (const entry of failedEntries) {
11987
- const id = entry.id;
11988
- if (attemptedDeletionRef.current.has(id)) continue;
11989
- attemptedDeletionRef.current.add(id);
11990
- try {
11991
- const resp = await fetch(getApiUrl(`/api/v1/apps/authentication/${id}`), {
11992
- method: "DELETE",
11993
- credentials: "include",
11994
- headers: { ...getAuthHeader() }
11995
- });
11996
- if (resp.ok) deletedAny = true;
11997
- } catch (err) {
11998
- console.error("[LocalLLMConfig] Failed to auto-delete failed OpenAI auth:", err);
11999
- }
12000
- }
12001
- if (!cancelled && deletedAny) {
12002
- await refreshAuth();
12003
- refreshAllIntegrationStatus();
12004
- }
12005
- })();
12006
- return () => {
12007
- cancelled = true;
12008
- };
12009
- }, [openaiEntries, authState.status, refreshAuth]);
12613
+ const savedUrlFromEntry = (openaiEntries[0]?.fields || []).find((f) => (f?.key || "").toLowerCase() === "url")?.value || "";
12614
+ const currentUrl = (authState.credentials?.url || savedUrlFromEntry || "").trim();
12615
+ const [customModel, setCustomModel] = useState13("");
12616
+ const [customMode, setCustomMode] = useState13(false);
12010
12617
  const hasOpenAIEntries = openaiEntries.length > 0;
12011
12618
  const effectivePreset = useMemo7(() => {
12012
12619
  if (selectedPreset) return selectedPreset;
12013
12620
  if (!currentUrl && !hasOpenAIEntries) return SHUFFLE_AI_PRESET;
12014
12621
  if (!currentUrl) return "";
12015
- const match = ENDPOINT_PRESETS.find((p) => p.url && p.url === currentUrl);
12016
- return match ? match.label : CUSTOM_PRESET;
12622
+ return detectLLMProvider(currentUrl)?.label || CUSTOM_PRESET;
12017
12623
  }, [selectedPreset, currentUrl, hasOpenAIEntries]);
12018
12624
  const applyShuffleAI = async () => {
12019
12625
  let deletedAny = false;
@@ -12050,7 +12656,16 @@ var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serv
12050
12656
  setSelectedPreset(label);
12051
12657
  const preset = ENDPOINT_PRESETS.find((p) => p.label === label);
12052
12658
  if (!preset) return;
12053
- handleAuthChange(OPENAI_APP_ID, { ...authState.credentials, url: preset.label === CUSTOM_PRESET ? customUrl : preset.url });
12659
+ const topModel = PROVIDER_MODELS[label]?.[0] || "";
12660
+ const existingModel = authState.credentials?.model || "";
12661
+ const nextModel = topModel || existingModel;
12662
+ setCustomMode(false);
12663
+ setCustomModel("");
12664
+ handleAuthChange(OPENAI_APP_ID, {
12665
+ ...authState.credentials,
12666
+ url: preset.label === CUSTOM_PRESET ? customUrl : preset.url,
12667
+ ...nextModel ? { model: nextModel } : {}
12668
+ });
12054
12669
  };
12055
12670
  const handleCustomUrlChange = (value) => {
12056
12671
  setCustomUrl(value);
@@ -12102,14 +12717,30 @@ var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serv
12102
12717
  isOptionEqualToValue: (opt, val) => opt === val,
12103
12718
  renderOption: (props, option) => {
12104
12719
  const preset = ENDPOINT_PRESETS.find((p) => p.label === option);
12105
- return /* @__PURE__ */ createElement2("li", { ...props, key: option }, /* @__PURE__ */ jsxs19(Box16, { sx: { display: "flex", alignItems: "center", gap: 1, width: "100%", minWidth: 0 }, children: [
12720
+ return /* @__PURE__ */ createElement2("li", { ...props, key: option }, /* @__PURE__ */ jsxs19(Box16, { sx: { display: "flex", alignItems: "center", gap: 1.25, width: "100%", minWidth: 0 }, children: [
12721
+ /* @__PURE__ */ jsx22(ProviderLogo, { label: option, url: preset?.url }),
12106
12722
  /* @__PURE__ */ jsx22(Typography15, { sx: { fontSize: "0.85rem", color: "hsl(var(--popover-foreground))" }, children: option }),
12107
12723
  preset?.url && /* @__PURE__ */ jsx22(Typography15, { component: "span", sx: { ml: "auto", color: "hsl(var(--muted-foreground))", fontSize: "0.75rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: preset.url })
12108
12724
  ] }));
12109
12725
  },
12110
- renderInput: (params) => /* @__PURE__ */ jsx22(TextField6, { ...params, placeholder: "Search a provider\u2026" }),
12726
+ renderInput: (params) => {
12727
+ const preset = ENDPOINT_PRESETS.find((p) => p.label === effectivePreset);
12728
+ return /* @__PURE__ */ jsx22(
12729
+ TextField6,
12730
+ {
12731
+ ...params,
12732
+ placeholder: "Search a provider\u2026",
12733
+ slotProps: {
12734
+ input: {
12735
+ ...params.InputProps,
12736
+ startAdornment: effectivePreset ? /* @__PURE__ */ jsx22(Box16, { sx: { display: "flex", alignItems: "center", pl: 0.5, mr: 0.5 }, children: /* @__PURE__ */ jsx22(ProviderLogo, { label: effectivePreset, url: preset?.url }) }) : void 0
12737
+ }
12738
+ }
12739
+ }
12740
+ );
12741
+ },
12111
12742
  slotProps: {
12112
- paper: { sx: { bgcolor: "hsl(var(--popover))", color: "hsl(var(--popover-foreground))", border: "1px solid hsl(var(--border))" } },
12743
+ paper: { sx: { bgcolor: "hsl(var(--popover))", color: "hsl(var(--popover-foreground))", border: "1px solid hsl(var(--border))", minWidth: 580 } },
12113
12744
  popper: { sx: { zIndex: 9999 } }
12114
12745
  }
12115
12746
  }
@@ -12172,6 +12803,59 @@ var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serv
12172
12803
  hideDocsLink: true,
12173
12804
  hideUrlFields: true,
12174
12805
  borderless: true,
12806
+ compactAuthForm: true,
12807
+ extraFieldsSlot: (PROVIDER_MODELS[effectivePreset]?.length ?? 0) > 0 ? ({ credentials, setField }) => {
12808
+ const liveModel = credentials.model || "";
12809
+ const presetModels = PROVIDER_MODELS[effectivePreset] || [];
12810
+ const liveIsCustom = liveModel !== "" && !presetModels.includes(liveModel);
12811
+ const showCustom = customMode || liveIsCustom;
12812
+ const liveSelectValue = showCustom ? CUSTOM_MODEL : liveModel || null;
12813
+ return /* @__PURE__ */ jsxs19(Box16, { sx: { display: "flex", flexDirection: "column", gap: 1 }, children: [
12814
+ /* @__PURE__ */ jsx22(Typography15, { sx: { fontSize: "0.85rem", fontWeight: 600, color: "hsl(var(--foreground))" }, children: "Model" }),
12815
+ /* @__PURE__ */ jsx22(
12816
+ Autocomplete3,
12817
+ {
12818
+ size: "small",
12819
+ fullWidth: true,
12820
+ disableClearable: true,
12821
+ options: [...presetModels, CUSTOM_MODEL],
12822
+ value: liveSelectValue,
12823
+ onChange: (_e, val) => {
12824
+ if (!val) return;
12825
+ if (val === CUSTOM_MODEL) {
12826
+ setCustomMode(true);
12827
+ setField("model", customModel);
12828
+ } else {
12829
+ setCustomMode(false);
12830
+ setCustomModel("");
12831
+ setField("model", val);
12832
+ }
12833
+ },
12834
+ isOptionEqualToValue: (opt, val) => opt === val,
12835
+ slotProps: {
12836
+ paper: { sx: { bgcolor: "hsl(var(--popover))", color: "hsl(var(--popover-foreground))", border: "1px solid hsl(var(--border))" } },
12837
+ popper: { sx: { zIndex: 9999 } }
12838
+ },
12839
+ renderInput: (params) => /* @__PURE__ */ jsx22(TextField6, { ...params, placeholder: "Select a model\u2026" })
12840
+ }
12841
+ ),
12842
+ showCustom && /* @__PURE__ */ jsx22(
12843
+ TextField6,
12844
+ {
12845
+ size: "small",
12846
+ fullWidth: true,
12847
+ placeholder: "Enter a custom model identifier",
12848
+ value: customModel || (liveIsCustom ? liveModel : ""),
12849
+ onChange: (e) => {
12850
+ setCustomModel(e.target.value);
12851
+ setField("model", e.target.value);
12852
+ },
12853
+ helperText: "Exact model name as expected by the provider's API",
12854
+ sx: { "& .MuiFormHelperText-root": { color: "hsl(var(--muted-foreground))" } }
12855
+ }
12856
+ )
12857
+ ] });
12858
+ } : void 0,
12175
12859
  globalUrl,
12176
12860
  userdata,
12177
12861
  isLoaded,
@@ -12249,9 +12933,16 @@ var AgentRunDrawer = ({
12249
12933
  setActiveTab("localLLM");
12250
12934
  }
12251
12935
  };
12936
+ const onClose2 = () => {
12937
+ onClose();
12938
+ };
12252
12939
  window.addEventListener("agent-drawer-open", onOpen);
12253
- return () => window.removeEventListener("agent-drawer-open", onOpen);
12254
- }, []);
12940
+ window.addEventListener("agent-drawer-close", onClose2);
12941
+ return () => {
12942
+ window.removeEventListener("agent-drawer-open", onOpen);
12943
+ window.removeEventListener("agent-drawer-close", onClose2);
12944
+ };
12945
+ }, [onClose]);
12255
12946
  const effectiveLocalLLMSlot = localLLMSlot ?? /* @__PURE__ */ jsx23(
12256
12947
  LocalLLMConfig_default,
12257
12948
  {
@@ -12444,14 +13135,16 @@ import {
12444
13135
  AlertCircle,
12445
13136
  CheckCircle,
12446
13137
  Clock,
13138
+ Database,
12447
13139
  FileText,
12448
13140
  GitBranch,
12449
13141
  Globe,
13142
+ Hand,
12450
13143
  Loader2,
12451
- Server as Server2,
13144
+ Workflow as WorkflowIcon,
12452
13145
  XCircle,
12453
13146
  Zap,
12454
- Search as SearchIcon
13147
+ Search as SearchIcon2
12455
13148
  } from "lucide-react";
12456
13149
 
12457
13150
  // agentActivity.ts
@@ -12750,7 +13443,7 @@ var STATUS_CONFIG = {
12750
13443
  EXECUTING: { icon: /* @__PURE__ */ jsx24(Loader2, { size: 16 }), color: "hsl(var(--severity-medium, 38 92% 50%))", label: "Running" },
12751
13444
  RUNNING: { icon: /* @__PURE__ */ jsx24(Loader2, { size: 16 }), color: "hsl(var(--severity-medium, 38 92% 50%))", label: "Running" },
12752
13445
  WAITING: { icon: /* @__PURE__ */ jsx24(Clock, { size: 16 }), color: "hsl(var(--severity-info, 217 91% 60%))", label: "Waiting" },
12753
- LIMIT_REACHED: { icon: /* @__PURE__ */ jsx24(AlertTriangle2, { size: 16 }), color: "hsl(var(--severity-medium, 38 92% 50%))", label: "Limit reached" }
13446
+ LIMIT_REACHED: { icon: /* @__PURE__ */ jsx24(AlertTriangle2, { size: 16 }), color: "hsl(var(--severity-critical, 0 72% 55%))", label: "Limit reached" }
12754
13447
  };
12755
13448
  var getEffectiveStatus = (run) => {
12756
13449
  const raw = (run.status || "").toUpperCase();
@@ -12763,21 +13456,126 @@ var getEffectiveStatus = (run) => {
12763
13456
  }
12764
13457
  return raw;
12765
13458
  };
12766
- var getRunIcon = (run) => {
12767
- const src = (run.execution_source || "").toLowerCase();
12768
- const arg = (run.execution_argument || "").toLowerCase();
12769
- if (src.includes("schedule") || src.includes("cron")) return /* @__PURE__ */ jsx24(Clock, { size: 18 });
12770
- if (src.includes("webhook") || src.includes("http")) return /* @__PURE__ */ jsx24(Globe, { size: 18 });
12771
- if (arg.includes("alert") || arg.includes("detect")) return /* @__PURE__ */ jsx24(Activity, { size: 18 });
12772
- if (arg.includes("report") || arg.includes("email")) return /* @__PURE__ */ jsx24(FileText, { size: 18 });
12773
- if (arg.includes("endpoint") || arg.includes("server")) return /* @__PURE__ */ jsx24(Server2, { size: 18 });
12774
- return /* @__PURE__ */ jsx24(Zap, { size: 18 });
13459
+ var SOURCE_ICON_SIZE = 18;
13460
+ var looksLikeExecutionId = (s) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s.trim());
13461
+ var DATASTORE_KEY_RE = /\bKey:\s*(\S+)/i;
13462
+ var DATASTORE_CATEGORY_RE = /\bCategory:\s*(\S+)/i;
13463
+ var parseDatastoreTask = (arg) => {
13464
+ if (!arg) return null;
13465
+ const k = arg.match(DATASTORE_KEY_RE);
13466
+ const c = arg.match(DATASTORE_CATEGORY_RE);
13467
+ if (!k || !c) return null;
13468
+ const key = (k[1] || "").trim();
13469
+ const category = (c[1] || "").trim();
13470
+ if (!key || !category) return null;
13471
+ return { key, category };
13472
+ };
13473
+ var isAIAgentResult = (result) => String(result?.action?.app_name || "").trim().toLowerCase() === "ai agent";
13474
+ var getAIAgentResultPayload = (run) => {
13475
+ const results = Array.isArray(run.results) ? run.results : [];
13476
+ const agentResult = results.find(isAIAgentResult);
13477
+ if (!agentResult || typeof agentResult !== "object" || !("result" in agentResult)) return null;
13478
+ return tryParseJson(agentResult.result);
13479
+ };
13480
+ var getDirectAgentInput = (payload) => {
13481
+ if (!payload || typeof payload !== "object") return null;
13482
+ const obj = payload;
13483
+ if (typeof obj.original_input === "string" && obj.original_input.trim()) {
13484
+ return obj.original_input.trim();
13485
+ }
13486
+ if (typeof obj.input === "string" && obj.input.trim()) {
13487
+ return obj.input.trim();
13488
+ }
13489
+ return null;
13490
+ };
13491
+ var findDatastoreTaskInRun = (run) => {
13492
+ const agentInput = getDirectAgentInput(getAIAgentResultPayload(run));
13493
+ const agentHit = parseDatastoreTask(agentInput || void 0);
13494
+ if (agentHit) return agentHit;
13495
+ const anyRun = run;
13496
+ const candidates = [];
13497
+ if (typeof anyRun.original_input === "string") candidates.push(anyRun.original_input);
13498
+ if (typeof anyRun.input === "string") candidates.push(anyRun.input);
13499
+ if (run.execution_argument) candidates.push(run.execution_argument);
13500
+ if (run.result) candidates.push(run.result);
13501
+ if (Array.isArray(anyRun.results)) {
13502
+ try {
13503
+ candidates.push(JSON.stringify(anyRun.results));
13504
+ } catch {
13505
+ }
13506
+ }
13507
+ for (const c of candidates) {
13508
+ const hit = parseDatastoreTask(c);
13509
+ if (hit) return hit;
13510
+ }
13511
+ return null;
13512
+ };
13513
+ var classifyRunSource = (run) => {
13514
+ const raw = (run.execution_source || "").trim();
13515
+ const src = raw.toLowerCase();
13516
+ const ds = findDatastoreTaskInRun(run);
13517
+ if (ds || src.includes("datastore") || src.includes("enrichment") || src.includes("automation")) {
13518
+ return {
13519
+ kind: "datastore",
13520
+ label: "Datastore automation",
13521
+ reason: ds ? `Started by datastore automation (category "${ds.category}", key "${ds.key}"). Click to open in Datastore.` : "Started by a datastore automation (e.g. enrichment trigger).",
13522
+ icon: /* @__PURE__ */ jsx24(Database, { size: SOURCE_ICON_SIZE }),
13523
+ datastoreKey: ds?.key,
13524
+ datastoreCategory: ds?.category
13525
+ };
13526
+ }
13527
+ if (src.includes("schedule") || src.includes("cron")) {
13528
+ return {
13529
+ kind: "schedule",
13530
+ label: "Schedule",
13531
+ reason: "Started by a scheduled trigger (cron).",
13532
+ icon: /* @__PURE__ */ jsx24(Clock, { size: SOURCE_ICON_SIZE })
13533
+ };
13534
+ }
13535
+ if (src.includes("webhook") || src.includes("http")) {
13536
+ return {
13537
+ kind: "webhook",
13538
+ label: "Webhook",
13539
+ reason: "Started by an incoming webhook / HTTP trigger.",
13540
+ icon: /* @__PURE__ */ jsx24(Globe, { size: SOURCE_ICON_SIZE })
13541
+ };
13542
+ }
13543
+ if (src.includes("form")) {
13544
+ return {
13545
+ kind: "form",
13546
+ label: "Form",
13547
+ reason: "Started by a form submission.",
13548
+ icon: /* @__PURE__ */ jsx24(FileText, { size: SOURCE_ICON_SIZE })
13549
+ };
13550
+ }
13551
+ if (raw && looksLikeExecutionId(raw)) {
13552
+ return {
13553
+ kind: "workflow",
13554
+ label: "Workflow",
13555
+ reason: "Started by another workflow as a subflow.",
13556
+ icon: /* @__PURE__ */ jsx24(WorkflowIcon, { size: SOURCE_ICON_SIZE })
13557
+ };
13558
+ }
13559
+ if (src === "" || src === "manual" || src === "default" || src === "demo") {
13560
+ return {
13561
+ kind: "manual",
13562
+ label: "Manual",
13563
+ reason: "Started manually from the Agents UI.",
13564
+ icon: /* @__PURE__ */ jsx24(Hand, { size: SOURCE_ICON_SIZE })
13565
+ };
13566
+ }
13567
+ return {
13568
+ kind: "unknown",
13569
+ label: raw || "Unknown",
13570
+ reason: raw ? `Started by "${raw}".` : "Trigger source unknown.",
13571
+ icon: /* @__PURE__ */ jsx24(Zap, { size: SOURCE_ICON_SIZE })
13572
+ };
12775
13573
  };
12776
13574
  var getRunIconColor = (run) => {
12777
13575
  const status = getEffectiveStatus(run);
12778
13576
  if (status === "FINISHED" || status === "SUCCESS") return "hsl(var(--severity-low, 142 71% 45%))";
12779
13577
  if (status === "FAILED" || status === "ABORTED") return "hsl(var(--severity-critical, 0 72% 55%))";
12780
- if (status === "LIMIT_REACHED") return "hsl(var(--severity-medium, 38 92% 50%))";
13578
+ if (status === "LIMIT_REACHED") return "hsl(var(--severity-critical, 0 72% 55%))";
12781
13579
  if (status === "EXECUTING" || status === "RUNNING") return "hsl(var(--severity-medium, 38 92% 50%))";
12782
13580
  return "hsl(var(--primary, 24 100% 50%))";
12783
13581
  };
@@ -12875,9 +13673,12 @@ var tryParseJson = (s) => {
12875
13673
  }
12876
13674
  };
12877
13675
  var getRunPrompt = (run) => {
12878
- const results = Array.isArray(run.results) ? run.results : null;
13676
+ const anyRun = run;
13677
+ const agentInput = getDirectAgentInput(getAIAgentResultPayload(run));
13678
+ if (agentInput) return agentInput;
13679
+ const results = Array.isArray(anyRun.results) ? anyRun.results : null;
12879
13680
  if (results) {
12880
- const agentResult = results.find((r) => r?.action?.app_name === "AI Agent");
13681
+ const agentResult = results.find(isAIAgentResult);
12881
13682
  if (agentResult?.result) {
12882
13683
  const hit = deepFindPrompt(tryParseJson(agentResult.result));
12883
13684
  if (hit) return hit;
@@ -12888,6 +13689,12 @@ var getRunPrompt = (run) => {
12888
13689
  if (hit) return hit;
12889
13690
  }
12890
13691
  }
13692
+ if (typeof anyRun.original_input === "string" && anyRun.original_input.trim()) {
13693
+ return anyRun.original_input.trim();
13694
+ }
13695
+ if (typeof anyRun.input === "string" && anyRun.input.trim()) {
13696
+ return anyRun.input.trim();
13697
+ }
12891
13698
  if (run.result) {
12892
13699
  const hit = deepFindPrompt(tryParseJson(run.result));
12893
13700
  if (hit) return hit;
@@ -12928,7 +13735,9 @@ var getRunSubtitle = (run) => {
12928
13735
  if (run.result.length < 120) return run.result;
12929
13736
  }
12930
13737
  }
12931
- return run.execution_source || "Agent execution";
13738
+ const status = getEffectiveStatus(run);
13739
+ const isRunning = status === "EXECUTING" || status === "RUNNING" || status === "WAITING";
13740
+ return run.execution_source || (isRunning ? "Agent still running\u2026" : "Agent execution");
12932
13741
  };
12933
13742
  var normalizeResultStatus = (s) => {
12934
13743
  const v = (s || "").toUpperCase();
@@ -13030,41 +13839,60 @@ var AgentRunRow = ({ run, onClick, sx, appIcons, onAppClick }) => {
13030
13839
  ...Array.isArray(sx) ? sx : sx ? [sx] : []
13031
13840
  ],
13032
13841
  children: [
13033
- /* @__PURE__ */ jsx24(
13034
- Box18,
13035
- {
13036
- sx: {
13037
- width: 40,
13038
- height: 40,
13039
- borderRadius: "50%",
13040
- display: "flex",
13041
- alignItems: "center",
13042
- justifyContent: "center",
13043
- bgcolor: `${iconColor}15`,
13044
- color: iconColor,
13045
- flexShrink: 0
13046
- },
13047
- children: getRunIcon(run)
13048
- }
13049
- ),
13842
+ (() => {
13843
+ const sourceInfo = classifyRunSource(run);
13844
+ const dsLink = sourceInfo.kind === "datastore" && sourceInfo.datastoreKey && sourceInfo.datastoreCategory ? `https://shuffler.io/admin?tab=datastore&category=${encodeURIComponent(sourceInfo.datastoreCategory)}&key=${encodeURIComponent(sourceInfo.datastoreKey)}` : null;
13845
+ return /* @__PURE__ */ jsx24(
13846
+ Tooltip12,
13847
+ {
13848
+ title: /* @__PURE__ */ jsxs21(Box18, { sx: { lineHeight: 1.4 }, children: [
13849
+ /* @__PURE__ */ jsx24(Box18, { sx: { fontWeight: 600 }, children: sourceInfo.label }),
13850
+ /* @__PURE__ */ jsx24(Box18, { sx: { opacity: 0.85 }, children: sourceInfo.reason })
13851
+ ] }),
13852
+ placement: "top",
13853
+ arrow: true,
13854
+ children: /* @__PURE__ */ jsx24(
13855
+ Box18,
13856
+ {
13857
+ onClick: dsLink ? (e) => {
13858
+ e.stopPropagation();
13859
+ window.open(dsLink, "_blank", "noopener,noreferrer");
13860
+ } : void 0,
13861
+ sx: {
13862
+ width: 40,
13863
+ height: 40,
13864
+ borderRadius: "50%",
13865
+ display: "flex",
13866
+ alignItems: "center",
13867
+ justifyContent: "center",
13868
+ bgcolor: `${iconColor}15`,
13869
+ color: iconColor,
13870
+ flexShrink: 0,
13871
+ cursor: dsLink ? "pointer" : "inherit",
13872
+ transition: "background 0.15s ease",
13873
+ "&:hover": dsLink ? { bgcolor: `${iconColor}30` } : void 0
13874
+ },
13875
+ children: sourceInfo.icon
13876
+ }
13877
+ )
13878
+ }
13879
+ );
13880
+ })(),
13050
13881
  /* @__PURE__ */ jsxs21(Box18, { sx: { flex: 1, minWidth: 0 }, children: [
13051
- /* @__PURE__ */ jsxs21(Box18, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 0.25 }, children: [
13052
- /* @__PURE__ */ jsx24(
13053
- Typography17,
13054
- {
13055
- sx: {
13056
- fontSize: "0.9rem",
13057
- fontWeight: 500,
13058
- color: "hsl(var(--foreground))",
13059
- overflow: "hidden",
13060
- textOverflow: "ellipsis",
13061
- whiteSpace: "nowrap"
13062
- },
13063
- children: getRunTitle(run)
13064
- }
13065
- ),
13066
- /* @__PURE__ */ jsx24(Box18, { sx: { display: "flex", alignItems: "center", color: cfg.color, flexShrink: 0 }, children: cfg.icon })
13067
- ] }),
13882
+ /* @__PURE__ */ jsx24(Box18, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 0.25 }, children: /* @__PURE__ */ jsx24(
13883
+ Typography17,
13884
+ {
13885
+ sx: {
13886
+ fontSize: "0.9rem",
13887
+ fontWeight: 500,
13888
+ color: "hsl(var(--foreground))",
13889
+ overflow: "hidden",
13890
+ textOverflow: "ellipsis",
13891
+ whiteSpace: "nowrap"
13892
+ },
13893
+ children: getRunTitle(run)
13894
+ }
13895
+ ) }),
13068
13896
  /* @__PURE__ */ jsx24(
13069
13897
  Typography17,
13070
13898
  {
@@ -13097,17 +13925,20 @@ var AgentRunRow = ({ run, onClick, sx, appIcons, onAppClick }) => {
13097
13925
  ] })
13098
13926
  ] })
13099
13927
  ] }),
13100
- tools.length > 0 && /* @__PURE__ */ jsx24(
13928
+ /* @__PURE__ */ jsx24(
13101
13929
  Box18,
13102
13930
  {
13103
13931
  onClick: (e) => e.stopPropagation(),
13104
13932
  sx: {
13105
13933
  display: { xs: "none", sm: "flex" },
13106
13934
  alignItems: "center",
13935
+ justifyContent: "flex-end",
13107
13936
  flexShrink: 0,
13108
- ml: 1
13937
+ ml: 1,
13938
+ width: 140,
13939
+ minHeight: 28
13109
13940
  },
13110
- children: /* @__PURE__ */ jsx24(
13941
+ children: tools.length > 0 ? /* @__PURE__ */ jsx24(
13111
13942
  AvatarGroup2,
13112
13943
  {
13113
13944
  max: 5,
@@ -13156,7 +13987,7 @@ var AgentRunRow = ({ run, onClick, sx, appIcons, onAppClick }) => {
13156
13987
  ) }, t.name);
13157
13988
  })
13158
13989
  }
13159
- )
13990
+ ) : null
13160
13991
  }
13161
13992
  )
13162
13993
  ]
@@ -13499,7 +14330,7 @@ var AgentActivityList = ({
13499
14330
  onChange: (e) => updateSearchQuery(e.target.value),
13500
14331
  slotProps: {
13501
14332
  input: {
13502
- startAdornment: /* @__PURE__ */ jsx24(InputAdornment, { position: "start", children: /* @__PURE__ */ jsx24(SearchIcon, { size: 18, color: "hsl(var(--muted-foreground))" }) })
14333
+ startAdornment: /* @__PURE__ */ jsx24(InputAdornment, { position: "start", children: /* @__PURE__ */ jsx24(SearchIcon2, { size: 18, color: "hsl(var(--muted-foreground))" }) })
13503
14334
  }
13504
14335
  },
13505
14336
  sx: {
@@ -13979,6 +14810,15 @@ var AgentsView = ({
13979
14810
  open: false,
13980
14811
  tab: "localLLM"
13981
14812
  });
14813
+ useEffect15(() => {
14814
+ if (typeof window === "undefined") return;
14815
+ const handler = (e) => {
14816
+ const detail = e.detail;
14817
+ setBuiltInDrawer({ open: true, tab: detail?.tab ?? "localLLM" });
14818
+ };
14819
+ window.addEventListener("agent-drawer-open", handler);
14820
+ return () => window.removeEventListener("agent-drawer-open", handler);
14821
+ }, []);
13982
14822
  const effectiveSchedule = useCallback10(
13983
14823
  async (info) => {
13984
14824
  if (onSchedule) return onSchedule(info);
@@ -14370,7 +15210,9 @@ export {
14370
15210
  deleteDatastoreItem,
14371
15211
  deleteDatastoreItems,
14372
15212
  diagnoseOutputWarning,
15213
+ extractActionAppNames,
14373
15214
  extractValidatedIngestionApps,
15215
+ extractWorkflowActionAppNames,
14374
15216
  extractWorkflowAppNames,
14375
15217
  fetchApps,
14376
15218
  fetchAppsViaApiConfig,