@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.js CHANGED
@@ -422,7 +422,9 @@ __export(index_exports, {
422
422
  deleteDatastoreItem: () => deleteDatastoreItem,
423
423
  deleteDatastoreItems: () => deleteDatastoreItems,
424
424
  diagnoseOutputWarning: () => diagnoseOutputWarning,
425
+ extractActionAppNames: () => extractActionAppNames,
425
426
  extractValidatedIngestionApps: () => extractValidatedIngestionApps,
427
+ extractWorkflowActionAppNames: () => extractWorkflowActionAppNames,
426
428
  extractWorkflowAppNames: () => extractWorkflowAppNames,
427
429
  fetchApps: () => fetchApps,
428
430
  fetchAppsViaApiConfig: () => fetchAppsViaApiConfig,
@@ -473,13 +475,13 @@ __export(index_exports, {
473
475
  useSyncHostBaseUrl: () => useSyncHostBaseUrl
474
476
  });
475
477
  module.exports = __toCommonJS(index_exports);
476
- var import_shuffle_mcp3 = require("./shuffle-mcp-ER33P3W2.css");
478
+ var import_shuffle_mcp3 = require("./shuffle-mcp-TWJGPBOQ.css");
477
479
  var import_react22 = __toESM(require("react"));
478
480
 
479
481
  // ShuffleMcpThemeProvider.tsx
480
482
  var import_react = __toESM(require("react"));
481
483
  var import_material = require("@mui/material");
482
- var import_shuffle_mcp = require("./shuffle-mcp-ER33P3W2.css");
484
+ var import_shuffle_mcp = require("./shuffle-mcp-TWJGPBOQ.css");
483
485
  var import_jsx_runtime = require("react/jsx-runtime");
484
486
  var readHtmlDarkClass = () => {
485
487
  if (typeof document === "undefined") return false;
@@ -1319,6 +1321,65 @@ var isIngestionApp = (appName) => {
1319
1321
  return isEmailApp(appName) || CASES_PATTERNS.some((p) => name.includes(p)) || EDR_PATTERNS.some((p) => name.includes(p)) || SIEM_PATTERNS.some((p) => name.includes(p));
1320
1322
  };
1321
1323
  var normalizeAppName = (name) => name.toLowerCase().trim().replace(/[\s_\-]+/g, "_");
1324
+ var IGNORED_WORKFLOW_APP_NAMES = /* @__PURE__ */ new Set([
1325
+ "shuffle_tools",
1326
+ "singul",
1327
+ "integration",
1328
+ "ai_agent",
1329
+ "shuffle_agent",
1330
+ "shuffle_ai"
1331
+ ]);
1332
+ var isIgnoredWorkflowAppName = (name) => IGNORED_WORKFLOW_APP_NAMES.has(normalizeAppName(name));
1333
+ var looksLikeOpaqueId = (raw) => {
1334
+ const trimmed = raw.trim();
1335
+ if (/^[a-f0-9]{16,}$/i.test(trimmed)) return true;
1336
+ 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;
1337
+ return false;
1338
+ };
1339
+ var addWorkflowAppName = (names, seen, raw) => {
1340
+ if (typeof raw !== "string" || !raw.trim()) return;
1341
+ const parts = raw.includes(",") ? raw.split(",") : [raw];
1342
+ for (const part of parts) {
1343
+ const trimmed = part.trim();
1344
+ if (!trimmed) continue;
1345
+ const key = normalizeAppName(trimmed);
1346
+ if (!key || isIgnoredWorkflowAppName(trimmed) || looksLikeOpaqueId(trimmed) || seen.has(key)) continue;
1347
+ seen.add(key);
1348
+ names.push(trimmed);
1349
+ }
1350
+ };
1351
+ var collectParameterAppNames = (input, names, seen, depth = 0) => {
1352
+ if (!input || depth > 4) return;
1353
+ if (Array.isArray(input)) {
1354
+ input.forEach((item) => collectParameterAppNames(item, names, seen, depth + 1));
1355
+ return;
1356
+ }
1357
+ if (typeof input !== "object") return;
1358
+ const obj = input;
1359
+ if (String(obj.name || "").toLowerCase() === "app_name") addWorkflowAppName(names, seen, obj.value);
1360
+ for (const [key, value] of Object.entries(obj)) {
1361
+ const lower = key.toLowerCase();
1362
+ if (lower === "app_name") addWorkflowAppName(names, seen, value);
1363
+ else if (lower === "parameters" || lower === "fields" || lower === "value") collectParameterAppNames(value, names, seen, depth + 1);
1364
+ }
1365
+ };
1366
+ function extractActionAppNames(action) {
1367
+ const names = [];
1368
+ const seen = /* @__PURE__ */ new Set();
1369
+ collectParameterAppNames(action?.parameters, names, seen);
1370
+ addWorkflowAppName(names, seen, action?.app_name);
1371
+ addWorkflowAppName(names, seen, action?.app_id);
1372
+ return names;
1373
+ }
1374
+ function extractWorkflowActionAppNames(workflow) {
1375
+ const names = [];
1376
+ const seen = /* @__PURE__ */ new Set();
1377
+ if (!workflow?.actions || !Array.isArray(workflow.actions)) return names;
1378
+ for (const action of workflow.actions) {
1379
+ for (const name of extractActionAppNames(action)) addWorkflowAppName(names, seen, name);
1380
+ }
1381
+ return names;
1382
+ }
1322
1383
  var getIngestionCategory = (appName, appCategories) => {
1323
1384
  const name = appName.toLowerCase();
1324
1385
  const categories = (appCategories || []).map((c) => c.toLowerCase());
@@ -1332,20 +1393,14 @@ var INGEST_TICKETS_WORKFLOW_NAME = "Ingest Tickets";
1332
1393
  var FORWARD_TICKETS_WORKFLOW_NAME = "Forward Tickets";
1333
1394
  function extractWorkflowAppNames(workflow) {
1334
1395
  const names = /* @__PURE__ */ new Set();
1335
- if (!workflow?.actions || !Array.isArray(workflow.actions)) return names;
1336
- for (const action of workflow.actions) {
1337
- if (action.app_name) {
1338
- names.add(normalizeAppName(action.app_name));
1339
- }
1340
- if (action.app_id) {
1341
- names.add(normalizeAppName(action.app_id));
1342
- }
1343
- if (action.parameters) {
1344
- for (const param of action.parameters) {
1345
- if (param.name === "app_name" && param.value) {
1346
- names.add(normalizeAppName(param.value));
1347
- }
1348
- }
1396
+ for (const raw of extractWorkflowActionAppNames(workflow)) {
1397
+ const parts = typeof raw === "string" && raw.includes(",") ? raw.split(",") : [raw];
1398
+ for (const part of parts) {
1399
+ const trimmed = (part || "").trim();
1400
+ if (!trimmed) continue;
1401
+ if (isIgnoredWorkflowAppName(trimmed) || looksLikeOpaqueId(trimmed)) continue;
1402
+ const key = normalizeAppName(trimmed);
1403
+ if (key) names.add(key);
1349
1404
  }
1350
1405
  }
1351
1406
  return names;
@@ -2060,6 +2115,7 @@ function AppTitleHeader({
2060
2115
  isActivated = null,
2061
2116
  activateLoading = false,
2062
2117
  onActivateToggle,
2118
+ highlightActivate = false,
2063
2119
  onAdd
2064
2120
  }) {
2065
2121
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_framer_motion.motion.div, { initial: { opacity: 0, y: 12 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.3 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -2163,7 +2219,16 @@ function AppTitleHeader({
2163
2219
  py: 0.5,
2164
2220
  minHeight: 0,
2165
2221
  flexShrink: 0,
2166
- ...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)" } }
2222
+ ...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)" } },
2223
+ ...highlightActivate ? {
2224
+ boxShadow: "0 0 0 0 hsla(var(--primary) / 0.6)",
2225
+ animation: "appTitleActivatePulse 1.1s ease-out infinite",
2226
+ "@keyframes appTitleActivatePulse": {
2227
+ "0%": { boxShadow: "0 0 0 0 hsla(var(--primary) / 0.55)" },
2228
+ "70%": { boxShadow: "0 0 0 10px hsla(var(--primary) / 0)" },
2229
+ "100%": { boxShadow: "0 0 0 0 hsla(var(--primary) / 0)" }
2230
+ }
2231
+ } : {}
2167
2232
  },
2168
2233
  children: activateLoading ? "\u2026" : isActivated ? "Deactivate" : "Activate"
2169
2234
  }
@@ -2399,6 +2464,8 @@ var AppAuthCard = ({
2399
2464
  hideDocsLink,
2400
2465
  hideUrlFields,
2401
2466
  borderless,
2467
+ compactAuthForm,
2468
+ extraFieldsSlot,
2402
2469
  globalUrl,
2403
2470
  userdata,
2404
2471
  isLoaded,
@@ -2465,10 +2532,6 @@ var AppAuthCard = ({
2465
2532
  if (!alreadyValid && newestId !== autoTestFiredRef.current) {
2466
2533
  autoTestFiredRef.current = newestId;
2467
2534
  setTimeout(() => {
2468
- toast({
2469
- title: "Auto-testing new authentication",
2470
- description: `Verifying connection for ${app.name.replace(/_/g, " ")}...`
2471
- });
2472
2535
  setTestingAuthId(newestId);
2473
2536
  onTestConnection(app.objectID, newestId);
2474
2537
  }, 500);
@@ -2530,11 +2593,17 @@ var AppAuthCard = ({
2530
2593
  executionId: authState.executionId,
2531
2594
  errorCode: authState.errorCode
2532
2595
  });
2596
+ if (autoTestFiredRef.current === testingAuthId) {
2597
+ setSelectedAuthId(ADD_NEW_AUTH);
2598
+ setUserHasSelected(true);
2599
+ setPendingFailedAuthId(testingAuthId);
2600
+ autoTestFiredRef.current = null;
2601
+ }
2533
2602
  setTestingAuthId(null);
2534
2603
  }
2535
2604
  }, [authState.status, authState.errorMessage, authState.successMessage, authState.warningMessage, testingAuthId, apiAuthEntries, wasPreValidatedPerAuth]);
2536
2605
  const isCredentialError = localTestMessages.errorCode === 401 || localTestMessages.errorCode === 403;
2537
- const showAddNewForm = selectedAuthId === ADD_NEW_AUTH;
2606
+ const showAddNewForm = compactAuthForm ? true : selectedAuthId === ADD_NEW_AUTH;
2538
2607
  const isConfigured = selectedAuth?.active === true;
2539
2608
  const isTested = selectedAuth?.validation?.valid === true;
2540
2609
  const getLastValidTimestamp = () => {
@@ -2556,6 +2625,8 @@ var AppAuthCard = ({
2556
2625
  });
2557
2626
  };
2558
2627
  const [localCredentials, setLocalCredentials] = (0, import_react7.useState)(authState.credentials || {});
2628
+ const [revealedFields, setRevealedFields] = (0, import_react7.useState)({});
2629
+ const [pendingFailedAuthId, setPendingFailedAuthId] = (0, import_react7.useState)(null);
2559
2630
  const [fieldErrors, setFieldErrors] = (0, import_react7.useState)({});
2560
2631
  const [loading, setLoading] = (0, import_react7.useState)(false);
2561
2632
  const [saving, setSaving] = (0, import_react7.useState)(false);
@@ -2631,7 +2702,8 @@ var AppAuthCard = ({
2631
2702
  for (const [k, v] of Object.entries(incoming)) {
2632
2703
  const lk = k.toLowerCase();
2633
2704
  const isUrlish = lk.includes("url") || lk.includes("endpoint") || lk.includes("host");
2634
- if (isUrlish && typeof v === "string" && v !== prev[k]) {
2705
+ const isModel = lk === "model";
2706
+ if ((isUrlish || isModel) && typeof v === "string" && v !== prev[k]) {
2635
2707
  next[k] = v;
2636
2708
  changed = true;
2637
2709
  }
@@ -2827,7 +2899,7 @@ var AppAuthCard = ({
2827
2899
  sx: {
2828
2900
  "& .MuiOutlinedInput-root": {
2829
2901
  backgroundColor: "hsl(var(--background))",
2830
- borderRadius: 2,
2902
+ borderRadius: 1,
2831
2903
  "& fieldset": { borderColor: "hsl(var(--border))" },
2832
2904
  "&:hover fieldset": { borderColor: "hsl(var(--border) / 0.8)" },
2833
2905
  "&.Mui-focused fieldset": { borderColor: "hsl(var(--primary))" }
@@ -2850,7 +2922,7 @@ var AppAuthCard = ({
2850
2922
  sx: {
2851
2923
  "& .MuiOutlinedInput-root": {
2852
2924
  backgroundColor: "hsl(var(--background))",
2853
- borderRadius: 2,
2925
+ borderRadius: 1,
2854
2926
  "& fieldset": { borderColor: "hsl(var(--border))" },
2855
2927
  "&:hover fieldset": { borderColor: "hsl(var(--border) / 0.8)" },
2856
2928
  "&.Mui-focused fieldset": { borderColor: "hsl(var(--primary))" }
@@ -2875,7 +2947,7 @@ var AppAuthCard = ({
2875
2947
  displayEmpty: true,
2876
2948
  sx: {
2877
2949
  backgroundColor: "action.hover",
2878
- borderRadius: 2,
2950
+ borderRadius: 1,
2879
2951
  color: "text.primary",
2880
2952
  "& .MuiOutlinedInput-notchedOutline": { borderColor: "divider" },
2881
2953
  "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: "text.disabled" },
@@ -2920,6 +2992,7 @@ var AppAuthCard = ({
2920
2992
  const fieldKey = param.id || param.name || `param_${index}`;
2921
2993
  const lowerName = (param.name || "").toLowerCase();
2922
2994
  const isSecretField = lowerName.includes("password") || lowerName.includes("secret") || lowerName.includes("key") || lowerName.includes("token");
2995
+ const isRevealed = !!revealedFields[fieldKey];
2923
2996
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2924
2997
  import_material4.TextField,
2925
2998
  {
@@ -2950,13 +3023,25 @@ var AppAuthCard = ({
2950
3023
  onFocus: (e) => {
2951
3024
  e.target.removeAttribute("readonly");
2952
3025
  }
2953
- }
3026
+ },
3027
+ input: isSecretField ? {
3028
+ endAdornment: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3029
+ import_material4.IconButton,
3030
+ {
3031
+ size: "small",
3032
+ onClick: () => setRevealedFields((prev) => ({ ...prev, [fieldKey]: !prev[fieldKey] })),
3033
+ "aria-label": isRevealed ? `Hide ${param.name}` : `Show ${param.name}`,
3034
+ sx: { color: "hsl(var(--muted-foreground))", "&:hover": { color: "hsl(var(--foreground))" } },
3035
+ children: isRevealed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.EyeOff, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Eye, { size: 16 })
3036
+ }
3037
+ )
3038
+ } : void 0
2954
3039
  },
2955
3040
  name: `auth-${fieldKey}-${app.objectID}-${Math.random().toString(36).slice(2, 8)}`,
2956
3041
  sx: {
2957
3042
  "& .MuiOutlinedInput-root": {
2958
3043
  backgroundColor: isSecretField ? "hsl(var(--primary) / 0.06)" : "hsl(var(--muted))",
2959
- borderRadius: 2,
3044
+ borderRadius: 1,
2960
3045
  "& fieldset": {
2961
3046
  borderColor: fieldErrors[fieldKey] ? "hsl(var(--destructive))" : isSecretField ? "hsl(var(--primary) / 0.5)" : "hsl(var(--border))",
2962
3047
  borderWidth: isSecretField ? 2 : 1
@@ -2968,7 +3053,7 @@ var AppAuthCard = ({
2968
3053
  },
2969
3054
  "& .MuiInputBase-input": {
2970
3055
  color: "hsl(var(--foreground))",
2971
- ...isSecretField && {
3056
+ ...isSecretField && !isRevealed && {
2972
3057
  WebkitTextSecurity: "disc",
2973
3058
  textSecurity: "disc",
2974
3059
  fontFamily: "text-security-disc, monospace"
@@ -3019,7 +3104,6 @@ var AppAuthCard = ({
3019
3104
  setSaveSuccess(success);
3020
3105
  if (success) {
3021
3106
  setUserHasSelected(false);
3022
- setLocalCredentials({});
3023
3107
  setFieldErrors({});
3024
3108
  }
3025
3109
  };
@@ -3227,7 +3311,7 @@ var AppAuthCard = ({
3227
3311
  borderTop: borderless ? "none" : "1px solid hsl(var(--border))"
3228
3312
  },
3229
3313
  children: [
3230
- apiAuthEntries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_material4.Box, { sx: { mb: 3 }, children: [
3314
+ !compactAuthForm && apiAuthEntries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_material4.Box, { sx: { mb: 3 }, children: [
3231
3315
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_material4.Typography, { variant: "body2", sx: { color: "hsl(var(--muted-foreground))", mb: 1 }, children: "Select authentication" }),
3232
3316
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_material4.FormControl, { fullWidth: true, size: "small", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3233
3317
  import_material4.Select,
@@ -3766,7 +3850,7 @@ var AppAuthCard = ({
3766
3850
  children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_material4.Typography, { 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." })
3767
3851
  }
3768
3852
  ),
3769
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_material4.Typography, { variant: "subtitle2", sx: { color: "hsl(var(--foreground))", fontWeight: 600, mb: 2 }, children: apiAuthEntries.length === 0 ? "Configure Authentication" : "Add New Authentication" }),
3853
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_material4.Typography, { variant: "subtitle2", sx: { color: "hsl(var(--foreground))", fontWeight: 600, mb: 2 }, children: apiAuthEntries.length === 0 ? `Configure ${app.name.replace(/_/g, " ")}` : "Add New Authentication" }),
3770
3854
  loading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_material4.Box, { sx: { display: "flex", alignItems: "center", justifyContent: "center", py: 4 }, children: [
3771
3855
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_material4.CircularProgress, { size: 32, sx: { color: "hsl(var(--primary))" } }),
3772
3856
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_material4.Typography, { sx: { ml: 2, color: "hsl(var(--muted-foreground))" }, children: "Loading configuration..." })
@@ -3802,6 +3886,7 @@ var AppAuthCard = ({
3802
3886
  ] })
3803
3887
  }
3804
3888
  ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_material4.Box, { sx: { display: "flex", flexDirection: "column", gap: 3 }, children: [
3889
+ extraFieldsSlot?.({ credentials: localCredentials, setField: handleCredentialChange }),
3805
3890
  renderOAuth2Fields(),
3806
3891
  renderParameterFields(),
3807
3892
  authState.status === "error" && authState.errorMessage && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -3856,38 +3941,97 @@ var AppAuthCard = ({
3856
3941
  children: "Failed to save authentication. Please try again."
3857
3942
  }
3858
3943
  ),
3859
- !isOAuth2 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_material4.Box, { sx: { display: "flex", justifyContent: "flex-end", pt: 1.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3860
- import_material4.Button,
3861
- {
3862
- variant: "outlined",
3863
- size: "medium",
3864
- onClick: (e) => {
3865
- e.stopPropagation();
3866
- handleSave();
3867
- },
3868
- disabled: saving || !isFormValid(),
3869
- sx: {
3870
- borderColor: isFormValid() ? "hsl(var(--primary) / 0.5)" : "hsl(var(--border))",
3871
- color: isFormValid() ? "hsl(var(--primary))" : "hsl(var(--muted-foreground))",
3872
- fontWeight: 600,
3873
- textTransform: "none",
3874
- fontSize: "0.9rem",
3875
- px: 3,
3876
- py: 1,
3877
- borderRadius: 2,
3878
- transition: "all 0.2s ease",
3879
- "&:hover": {
3880
- borderColor: "hsl(var(--primary))",
3881
- backgroundColor: "hsl(var(--primary) / 0.08)"
3944
+ !isOAuth2 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_material4.Box, { sx: { display: "flex", justifyContent: "flex-end", pt: 1.5, gap: 1, flexWrap: "wrap" }, children: [
3945
+ pendingFailedAuthId && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3946
+ import_material4.Button,
3947
+ {
3948
+ variant: "text",
3949
+ size: "medium",
3950
+ onClick: async (e) => {
3951
+ e.stopPropagation();
3952
+ const previousFailedId = pendingFailedAuthId;
3953
+ setPendingFailedAuthId(null);
3954
+ setUserHasSelected(true);
3955
+ setFieldErrors({});
3956
+ await onSaveAuth(app.objectID, localCredentials);
3957
+ let entries = apiAuthEntries;
3958
+ if (onRefreshAuth) {
3959
+ await onRefreshAuth();
3960
+ }
3961
+ entries = apiAuthEntries || [];
3962
+ const sorted = [...entries].sort((a, b) => (b.created || 0) - (a.created || 0));
3963
+ const acceptId = sorted[0]?.id || previousFailedId;
3964
+ setSelectedAuthId(acceptId);
3965
+ const stale = entries.filter((e2) => e2.id && e2.id !== acceptId);
3966
+ await Promise.all(stale.map(async (entry) => {
3967
+ try {
3968
+ await fetch(getApiUrl(`/api/v1/apps/authentication/${entry.id}`), {
3969
+ method: "DELETE",
3970
+ credentials: "include",
3971
+ headers: { ...getAuthHeader() }
3972
+ });
3973
+ } catch (err) {
3974
+ console.error("[AppAuthCard] Failed to delete stale auth on Save anyway:", err);
3975
+ }
3976
+ }));
3977
+ if (onRefreshAuth) await onRefreshAuth();
3978
+ if (onSelectAuth) onSelectAuth(app.objectID, acceptId);
3979
+ if (typeof window !== "undefined") {
3980
+ window.dispatchEvent(new CustomEvent("integrations-changed"));
3981
+ window.dispatchEvent(new CustomEvent("agent-drawer-close"));
3982
+ }
3882
3983
  },
3883
- "&.Mui-disabled": {
3884
- borderColor: "hsl(var(--border))",
3885
- color: "hsl(var(--muted-foreground))"
3886
- }
3887
- },
3888
- children: saving ? "Saving..." : "Save Authentication"
3889
- }
3890
- ) }),
3984
+ sx: {
3985
+ color: "hsl(var(--muted-foreground))",
3986
+ fontWeight: 600,
3987
+ textTransform: "none",
3988
+ fontSize: "0.85rem",
3989
+ px: 2,
3990
+ py: 1,
3991
+ borderRadius: 2,
3992
+ "&:hover": {
3993
+ color: "hsl(var(--foreground))",
3994
+ backgroundColor: "hsl(var(--muted) / 0.5)"
3995
+ }
3996
+ },
3997
+ title: "Our test isn't always authoritative \u2014 keep the credentials you saved and dismiss the failure.",
3998
+ children: "Save anyway"
3999
+ }
4000
+ ),
4001
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4002
+ import_material4.Button,
4003
+ {
4004
+ variant: "outlined",
4005
+ size: "medium",
4006
+ onClick: (e) => {
4007
+ e.stopPropagation();
4008
+ setPendingFailedAuthId(null);
4009
+ handleSave();
4010
+ },
4011
+ disabled: saving || !isFormValid(),
4012
+ sx: {
4013
+ borderColor: isFormValid() ? "hsl(var(--primary) / 0.5)" : "hsl(var(--border))",
4014
+ color: isFormValid() ? "hsl(var(--primary))" : "hsl(var(--muted-foreground))",
4015
+ fontWeight: 600,
4016
+ textTransform: "none",
4017
+ fontSize: "0.9rem",
4018
+ px: 3,
4019
+ py: 1,
4020
+ borderRadius: 2,
4021
+ transition: "all 0.2s ease",
4022
+ "&:hover": {
4023
+ borderColor: "hsl(var(--primary))",
4024
+ backgroundColor: "hsl(var(--primary) / 0.08)"
4025
+ },
4026
+ "&.Mui-disabled": {
4027
+ borderColor: "hsl(var(--border))",
4028
+ color: "hsl(var(--muted-foreground))"
4029
+ }
4030
+ },
4031
+ children: saving ? "Testing..." : pendingFailedAuthId ? "Retry Test" : "Test Authentication"
4032
+ }
4033
+ )
4034
+ ] }),
3891
4035
  isOAuth2 && (() => {
3892
4036
  const hasClientId = !!localCredentials["client_id"]?.trim();
3893
4037
  const hasClientSecret = !!localCredentials["client_secret"]?.trim();
@@ -5706,6 +5850,7 @@ function AppDetailDrawer({
5706
5850
  onAddToCanvas,
5707
5851
  isAuthenticated = true,
5708
5852
  activeOrgId,
5853
+ autoActivate = false,
5709
5854
  globalUrl,
5710
5855
  userdata,
5711
5856
  isLoaded,
@@ -5903,8 +6048,9 @@ function AppDetailDrawer({
5903
6048
  (0, import_react11.useEffect)(() => {
5904
6049
  lastValidAuthRef.current = null;
5905
6050
  }, [appName]);
5906
- const handleActivateToggle = async () => {
6051
+ const handleActivateToggle = async (opts) => {
5907
6052
  if (!appName || activateLoading) return;
6053
+ const silent = !!opts?.silent;
5908
6054
  const wasActivated = isActivated;
5909
6055
  const prevAppId = activatedAppId;
5910
6056
  setIsActivated(!wasActivated);
@@ -5918,7 +6064,7 @@ function AppDetailDrawer({
5918
6064
  });
5919
6065
  if (!res.ok) throw new Error("Deactivate failed");
5920
6066
  setActivatedAppId(null);
5921
- toast.success(`${displayName} deactivated`);
6067
+ if (!silent) toast.success(`${displayName} deactivated`);
5922
6068
  } else {
5923
6069
  const appId2 = resolvedAlgoliaId;
5924
6070
  if (!appId2) throw new Error("App ID not resolved yet");
@@ -5929,40 +6075,71 @@ function AppDetailDrawer({
5929
6075
  });
5930
6076
  if (!activateRes.ok) throw new Error(`Activate failed (${activateRes.status})`);
5931
6077
  setActivatedAppId(appId2);
5932
- toast.success(`${displayName} activated`);
6078
+ if (!silent) toast.success(`${displayName} activated`);
5933
6079
  }
5934
6080
  } catch (err) {
5935
6081
  console.error("[Activate] Error:", err);
5936
- toast.error(err?.message || "Activation failed");
6082
+ if (!silent) toast.error(err?.message || "Activation failed");
5937
6083
  setIsActivated(wasActivated);
5938
6084
  setActivatedAppId(prevAppId);
5939
6085
  } finally {
5940
6086
  setActivateLoading(false);
5941
6087
  }
5942
6088
  };
6089
+ const autoActivateFiredRef = (0, import_react11.useRef)(null);
6090
+ const [autoActivatePulse, setAutoActivatePulse] = (0, import_react11.useState)(false);
6091
+ (0, import_react11.useEffect)(() => {
6092
+ if (!open || !autoActivate || !appName) return;
6093
+ const key = `${appName}`;
6094
+ if (autoActivateFiredRef.current === key) return;
6095
+ if (isActivated !== false) return;
6096
+ if (!resolvedAlgoliaId) return;
6097
+ if (activateLoading) return;
6098
+ autoActivateFiredRef.current = key;
6099
+ setAutoActivatePulse(true);
6100
+ handleActivateToggle({ silent: true }).finally(() => {
6101
+ setTimeout(() => setAutoActivatePulse(false), 1200);
6102
+ });
6103
+ }, [open, autoActivate, appName, isActivated, resolvedAlgoliaId, activateLoading]);
6104
+ (0, import_react11.useEffect)(() => {
6105
+ if (!open) {
6106
+ autoActivateFiredRef.current = null;
6107
+ setAutoActivatePulse(false);
6108
+ }
6109
+ }, [open, appName]);
5943
6110
  const isLoadingAll = appLoading || isAuthenticated && appAuthLoading;
6111
+ const drawerWidth = `min(${width}px, 100vw)`;
5944
6112
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
5945
6113
  import_material9.Drawer,
5946
6114
  {
5947
6115
  anchor,
5948
6116
  open,
5949
6117
  onClose: handleClose,
5950
- slotProps: {
5951
- paper: {
5952
- className: scopeClassName,
5953
- sx: {
5954
- width: { xs: "100%", sm: width },
5955
- maxWidth: "100vw",
5956
- display: "flex",
5957
- flexDirection: "column",
5958
- background: "linear-gradient(180deg, hsl(var(--card)) 0%, hsl(var(--background)) 100%)",
5959
- borderLeft: anchor === "right" ? "1px solid hsl(var(--border))" : "none",
5960
- borderRight: anchor === "left" ? "1px solid hsl(var(--border))" : "none"
5961
- }
6118
+ PaperProps: {
6119
+ className: scopeClassName,
6120
+ sx: {
6121
+ width: drawerWidth,
6122
+ minWidth: drawerWidth,
6123
+ maxWidth: drawerWidth,
6124
+ flex: `0 0 ${drawerWidth}`,
6125
+ height: "100vh",
6126
+ display: "flex",
6127
+ flexDirection: "column",
6128
+ overflow: "hidden",
6129
+ background: "linear-gradient(180deg, hsl(var(--card)) 0%, hsl(var(--background)) 100%)",
6130
+ borderLeft: anchor === "right" ? "1px solid hsl(var(--border))" : "none",
6131
+ borderRight: anchor === "left" ? "1px solid hsl(var(--border))" : "none"
5962
6132
  }
5963
6133
  },
5964
6134
  sx: {
5965
- "& .MuiDrawer-paper": { boxSizing: "border-box" }
6135
+ zIndex: 9999,
6136
+ "& .MuiDrawer-paper": {
6137
+ boxSizing: "border-box",
6138
+ width: `${drawerWidth} !important`,
6139
+ minWidth: `${drawerWidth} !important`,
6140
+ maxWidth: `${drawerWidth} !important`,
6141
+ flex: `0 0 ${drawerWidth} !important`
6142
+ }
5966
6143
  },
5967
6144
  children: [
5968
6145
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
@@ -6015,7 +6192,8 @@ function AppDetailDrawer({
6015
6192
  categories: appInfo?.categories,
6016
6193
  isActivated: onAddToCanvas ? null : effectiveActivated,
6017
6194
  activateLoading,
6018
- onActivateToggle: handleActivateToggle,
6195
+ onActivateToggle: () => handleActivateToggle(),
6196
+ highlightActivate: autoActivatePulse,
6019
6197
  onAdd: onAddToCanvas && appName ? () => {
6020
6198
  onAddToCanvas({ name: appName, icon: resolvedImage || "", algoliaId: resolvedAlgoliaId });
6021
6199
  onClose();
@@ -6119,7 +6297,7 @@ function AppDetailDrawer({
6119
6297
  }
6120
6298
 
6121
6299
  // views/ShuffleMCP.tsx
6122
- var import_shuffle_mcp2 = require("./shuffle-mcp-ER33P3W2.css");
6300
+ var import_shuffle_mcp2 = require("./shuffle-mcp-TWJGPBOQ.css");
6123
6301
  init_appsCache();
6124
6302
 
6125
6303
  // components/SegmentedControl.tsx
@@ -6287,7 +6465,7 @@ function SegmentedControl({
6287
6465
  children: opt.label
6288
6466
  }
6289
6467
  ),
6290
- !isAction && typeof opt.count === "number" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6468
+ !isAction && (typeof opt.count === "number" || typeof opt.count === "string") && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6291
6469
  "span",
6292
6470
  {
6293
6471
  style: {
@@ -6786,8 +6964,8 @@ var ShuffleMCP = import_react12.default.forwardRef(({
6786
6964
  value: sourceFilter,
6787
6965
  onChange: (v) => setSourceFilter(v),
6788
6966
  options: [
6789
- { value: "all", label: "All", count: results.length + filteredPrivateApps.length, title: "All available apps \u2014 both the public catalog and your private apps." },
6790
- { value: "public", label: "Public", count: results.length, title: "Public apps from the Shuffle catalog (powered by Algolia)." },
6967
+ { 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." },
6968
+ { value: "public", label: "Public", count: results.length >= hitsPerPage ? `${results.length}+` : results.length, title: "Public apps from the Shuffle catalog (powered by Algolia)." },
6791
6969
  { 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." },
6792
6970
  { 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." }
6793
6971
  ]
@@ -6963,9 +7141,11 @@ function AppSearchDrawer({
6963
7141
  pinnedApps,
6964
7142
  highlightAppName,
6965
7143
  highlightDelayMs = 5e3,
7144
+ realtimeHighlightAppNames,
6966
7145
  multiSelect = false,
6967
7146
  selectedApps,
6968
7147
  onSelectionChange,
7148
+ autoActivate = false,
6969
7149
  globalUrl,
6970
7150
  theme,
6971
7151
  colorMode
@@ -6992,6 +7172,7 @@ function AppSearchDrawer({
6992
7172
  const t = setTimeout(() => setHighlightActive(true), highlightDelayMs);
6993
7173
  return () => clearTimeout(t);
6994
7174
  }, [open, highlightAppName, highlightDelayMs]);
7175
+ const drawerWidth = `min(${width}px, 100vw)`;
6995
7176
  const handleClose = () => {
6996
7177
  setDetailAppName(null);
6997
7178
  setDetailAppId(null);
@@ -7068,29 +7249,38 @@ function AppSearchDrawer({
7068
7249
  anchor,
7069
7250
  open: open && detailAppName === null,
7070
7251
  onClose: handleClose,
7071
- slotProps: {
7072
- paper: {
7073
- className: scopeClassName,
7074
- sx: {
7075
- // Strict, environment-independent sizing so the drawer
7076
- // looks identical on every host (Shuffle Security, /agents,
7077
- // test page, embedded library, etc.). No breakpoint variance.
7078
- width: `min(${width}px, 100vw)`,
7079
- minWidth: `min(${width}px, 100vw)`,
7080
- maxWidth: "100vw",
7081
- height: "100vh",
7082
- display: "flex",
7083
- flexDirection: "column",
7084
- overflow: "hidden",
7085
- background: "linear-gradient(180deg, hsl(var(--card)) 0%, hsl(var(--background)) 100%)",
7086
- borderLeft: anchor === "right" ? "1px solid hsl(var(--border))" : "none",
7087
- borderRight: anchor === "left" ? "1px solid hsl(var(--border))" : "none"
7088
- }
7252
+ PaperProps: {
7253
+ className: scopeClassName,
7254
+ sx: {
7255
+ // Strict, environment-independent sizing so the drawer
7256
+ // looks identical on every host (Shuffle Security, /agents,
7257
+ // test page, embedded library, etc.). No breakpoint variance.
7258
+ // NOTE: use PaperProps (not slotProps.paper) — slotProps.paper
7259
+ // was added in MUI v6 and is silently ignored on MUI v5 hosts,
7260
+ // which made the drawer collapse to content width when
7261
+ // consumed from the published library.
7262
+ width: drawerWidth,
7263
+ minWidth: drawerWidth,
7264
+ maxWidth: drawerWidth,
7265
+ flex: `0 0 ${drawerWidth}`,
7266
+ height: "100vh",
7267
+ display: "flex",
7268
+ flexDirection: "column",
7269
+ overflow: "hidden",
7270
+ background: "linear-gradient(180deg, hsl(var(--card)) 0%, hsl(var(--background)) 100%)",
7271
+ borderLeft: anchor === "right" ? "1px solid hsl(var(--border))" : "none",
7272
+ borderRight: anchor === "left" ? "1px solid hsl(var(--border))" : "none"
7089
7273
  }
7090
7274
  },
7091
7275
  sx: {
7092
7276
  zIndex: 9999,
7093
- "& .MuiDrawer-paper": { boxSizing: "border-box" }
7277
+ "& .MuiDrawer-paper": {
7278
+ boxSizing: "border-box",
7279
+ width: `${drawerWidth} !important`,
7280
+ minWidth: `${drawerWidth} !important`,
7281
+ maxWidth: `${drawerWidth} !important`,
7282
+ flex: `0 0 ${drawerWidth} !important`
7283
+ }
7094
7284
  },
7095
7285
  children: [
7096
7286
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
@@ -7122,7 +7312,7 @@ function AppSearchDrawer({
7122
7312
  ]
7123
7313
  }
7124
7314
  ),
7125
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_material11.Box, { sx: { flex: 1, boxSizing: "border-box", overflow: "hidden", p: 3, minHeight: 0, display: "flex", flexDirection: "column" }, children: [
7315
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_material11.Box, { sx: { flex: 1, width: "100%", minWidth: 0, boxSizing: "border-box", overflow: "hidden", p: 3, minHeight: 0, display: "flex", flexDirection: "column" }, children: [
7126
7316
  connectionPathApps && connectionPathApps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_material11.Box, { sx: { mb: 2.5 }, children: [
7127
7317
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_material11.Typography, { sx: { color: "hsl(var(--muted-foreground))", fontSize: "0.7rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em", mb: 1 }, children: "In this usecase" }),
7128
7318
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_material11.Box, { sx: { display: "flex", gap: 1.5, flexWrap: "wrap" }, children: connectionPathApps.map((app) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_material11.Tooltip, { title: app.name.replace(/_/g, " "), arrow: true, placement: "bottom", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
@@ -7169,6 +7359,8 @@ function AppSearchDrawer({
7169
7359
  {
7170
7360
  sx: {
7171
7361
  flex: 1,
7362
+ width: "100%",
7363
+ minWidth: 0,
7172
7364
  minHeight: 0,
7173
7365
  overflow: "hidden",
7174
7366
  "& .singul-container, & .singul-search-bar-container": {
@@ -7181,16 +7373,22 @@ function AppSearchDrawer({
7181
7373
  },
7182
7374
  "& .singul-results-container": {
7183
7375
  flex: 1,
7376
+ width: "100%",
7377
+ minWidth: 0,
7184
7378
  minHeight: 0,
7185
7379
  maxHeight: "none",
7186
7380
  overflowY: "auto"
7187
7381
  },
7188
7382
  "& .singul-results-grid": {
7383
+ width: "100% !important",
7384
+ minWidth: "0 !important",
7189
7385
  gridTemplateColumns: "repeat(2, minmax(0, 1fr)) !important",
7190
7386
  gridAutoRows: "78px !important",
7191
7387
  gap: "12px !important"
7192
7388
  },
7193
7389
  "& .singul-results-grid .singul-dropdown-item": {
7390
+ width: "100%",
7391
+ maxWidth: "100%",
7194
7392
  minWidth: 0,
7195
7393
  minHeight: "78px !important",
7196
7394
  maxHeight: "78px !important",
@@ -7224,6 +7422,41 @@ function AppSearchDrawer({
7224
7422
  "0%, 100%": { boxShadow: "0 0 0 2px hsl(var(--primary)), 0 0 12px 1px hsl(var(--primary) / 0.4)" },
7225
7423
  "50%": { boxShadow: "0 0 0 2px hsl(var(--primary)), 0 0 24px 4px hsl(var(--primary) / 0.75)" }
7226
7424
  }
7425
+ } : {},
7426
+ ...realtimeHighlightAppNames && realtimeHighlightAppNames.length > 0 ? {
7427
+ [realtimeHighlightAppNames.map((n) => `& .singul-dropdown-item[data-app-name="${n}"]`).join(", ")]: {
7428
+ position: "relative",
7429
+ zIndex: 2,
7430
+ borderColor: "hsl(var(--primary) / 0.78) !important",
7431
+ backgroundColor: "hsl(var(--primary) / 0.08) !important",
7432
+ outline: "none !important",
7433
+ boxShadow: "none !important",
7434
+ animation: "shuffleHighlightPulseStrong 1.5s ease-in-out infinite",
7435
+ transition: "border-color 120ms ease, background-color 120ms ease",
7436
+ "&::before": {
7437
+ content: '""',
7438
+ position: "absolute",
7439
+ left: "10px",
7440
+ top: "14px",
7441
+ bottom: "14px",
7442
+ width: "3px",
7443
+ borderRadius: "999px",
7444
+ backgroundColor: "hsl(var(--primary))"
7445
+ },
7446
+ "& .singul-app-info": {
7447
+ paddingLeft: "8px"
7448
+ }
7449
+ },
7450
+ "@keyframes shuffleHighlightPulseStrong": {
7451
+ "0%, 100%": {
7452
+ borderColor: "hsl(var(--primary) / 0.62)",
7453
+ backgroundColor: "hsl(var(--primary) / 0.06)"
7454
+ },
7455
+ "50%": {
7456
+ borderColor: "hsl(var(--primary) / 0.92)",
7457
+ backgroundColor: "hsl(var(--primary) / 0.12)"
7458
+ }
7459
+ }
7227
7460
  } : {}
7228
7461
  },
7229
7462
  children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_framer_motion8.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
@@ -7246,8 +7479,9 @@ function AppSearchDrawer({
7246
7479
  layout: "grid",
7247
7480
  gridColumns: 2,
7248
7481
  inline: true,
7482
+ initialQuery,
7249
7483
  initialFilterQuery: initialQuery,
7250
- hitsPerPage: 12,
7484
+ hitsPerPage: 20,
7251
7485
  showDescription: false,
7252
7486
  showCategories: true,
7253
7487
  showCheckbox: multiSelect,
@@ -7338,7 +7572,8 @@ function AppSearchDrawer({
7338
7572
  theme,
7339
7573
  colorMode,
7340
7574
  onRefresh: onClose,
7341
- onAddToCanvas
7575
+ onAddToCanvas,
7576
+ autoActivate
7342
7577
  }
7343
7578
  )
7344
7579
  ] });
@@ -7605,11 +7840,173 @@ function ShufflePipelinesBanner() {
7605
7840
  var ShufflePipelinesBanner_default = ShufflePipelinesBanner;
7606
7841
 
7607
7842
  // components/AgentUI.tsx
7608
- var import_react15 = require("react");
7843
+ var import_react15 = __toESM(require("react"));
7609
7844
  var import_lucide_react11 = require("lucide-react");
7610
7845
  var import_react_router_dom2 = require("react-router-dom");
7611
7846
  var import_material16 = require("@mui/material");
7612
7847
  var import_react_markdown4 = __toESM(require("react-markdown"));
7848
+
7849
+ // components/agentPromptSuggestions.ts
7850
+ var AGENT_PROMPT_SUGGESTIONS = [
7851
+ // — Email & comms ————————————————————————————————————————————————
7852
+ "Take care of my emails by pretending to be me and answering them",
7853
+ "Triage my inbox every hour and draft replies for anything urgent",
7854
+ "Summarise my unread emails from the last 24 hours and rank them by importance",
7855
+ "Forward any phishing-looking emails in my inbox to the security team and quarantine them",
7856
+ "Reply to scheduling requests in my inbox using my working hours and calendar availability",
7857
+ "Every morning, send me a digest of overnight emails grouped by sender and topic",
7858
+ "Auto-unsubscribe from marketing emails I have not opened in 60 days",
7859
+ "Snooze low-priority newsletters until Friday afternoon",
7860
+ "Flag and archive shipping notifications once the package is delivered",
7861
+ "Clean up my inbox by archiving anything older than 30 days that I have already read",
7862
+ // — Calendar & meetings ————————————————————————————————————————————
7863
+ "Block focus time on my calendar every weekday morning between 9 and 11",
7864
+ "Decline meetings that conflict with my focus blocks and propose alternatives",
7865
+ "Send me a daily agenda every weekday at 8am with prep notes per meeting",
7866
+ "Reschedule any meeting without an agenda 24 hours before it starts",
7867
+ "Send meeting notes and action items to all attendees after every call",
7868
+ "Find a 30-minute slot this week that works for me and the people I mention",
7869
+ "Cancel recurring meetings that I have skipped 3 times in a row",
7870
+ "Remind me 15 minutes before any meeting that I have not prepared for",
7871
+ // — Incidents & SOC operations ———————————————————————————————————
7872
+ "Triage new incidents every 15 minutes and assign severity, owner, and next steps",
7873
+ "Watch for new critical incidents and page the on-call engineer immediately",
7874
+ "Close incidents older than 30 days that have had no activity and no observables",
7875
+ "Merge duplicate incidents that share the same observable or hostname",
7876
+ "Enrich every new incident with threat intel and asset context within 2 minutes",
7877
+ "Auto-acknowledge incidents from known maintenance windows and link them to the change ticket",
7878
+ "Summarise yesterday's incidents into a daily standup brief for the SOC team",
7879
+ "Escalate any incident that breaches its SLA to the team lead",
7880
+ "Tag every incident with the MITRE ATT&CK technique that best matches its observables",
7881
+ "Watch the alert queue and group related alerts into a single incident",
7882
+ "Auto-resolve incidents whose root cause matches a previously closed incident",
7883
+ "Reopen any closed incident if the same observable shows up again within 7 days",
7884
+ "Send a weekly incident trend report every Monday morning to the security leadership",
7885
+ // — Threat intel & IOCs —————————————————————————————————————————
7886
+ "Pull new IOCs from my threat feeds every hour and push them to the firewall blocklist",
7887
+ "Check every external IP in today's incidents against VirusTotal and AbuseIPDB",
7888
+ "Sweep the environment for any host that has connected to a known bad domain in the last 7 days",
7889
+ "Subscribe to new CISA advisories and turn them into detection rules where possible",
7890
+ "Cross-reference observed file hashes against MalwareBazaar daily",
7891
+ "Watch for newly registered domains that look like our brand and report them",
7892
+ "Alert me whenever a new CVE with a known exploit affects software in our inventory",
7893
+ "Refresh the IOC datastore every 6 hours and prune entries older than 30 days",
7894
+ // — Detection engineering ———————————————————————————————————————
7895
+ "Review noisy detection rules every week and propose tuning suggestions",
7896
+ "Convert the latest Sigma rules from the public repo into our SIEM's format and stage them for review",
7897
+ "Tell me which detection rules have not fired in the last 90 days so I can retire them",
7898
+ "Audit MITRE ATT&CK coverage every Monday and flag techniques with zero detections",
7899
+ "Backtest new detection rules against the last 30 days of logs before promoting them to production",
7900
+ // — SIEM operations ———————————————————————————————————————————
7901
+ "Triage new Splunk alerts every 10 minutes and assign severity, owner, and next steps",
7902
+ "Watch Microsoft Sentinel for high-severity incidents and page the on-call analyst immediately",
7903
+ "Suppress noisy SIEM alerts that fire more than 50 times per hour and open a tuning ticket",
7904
+ "Correlate failed login spikes in the SIEM with VPN logs and flag possible brute-force attempts",
7905
+ "Hunt the SIEM every morning for sign-ins from countries we do not operate in",
7906
+ "Forward critical SIEM detections straight to Case Management and skip the alert queue",
7907
+ "Run a weekly SIEM data-source health check and alert me on any log source silent for over 1 hour",
7908
+ "Enrich every SIEM alert with asset owner, criticality, and recent vulnerabilities before triage",
7909
+ "Search the SIEM for the latest IOCs from threat intel feeds every 30 minutes",
7910
+ "Summarise top 10 noisiest SIEM rules every Friday and propose tuning candidates",
7911
+ "Detect lateral movement patterns in SIEM auth logs and open an incident with the affected hosts",
7912
+ "Watch the SIEM for any disable or delete of audit logging and treat it as a critical incident",
7913
+ // — EDR operations ————————————————————————————————————————————
7914
+ "Isolate any endpoint that triggers a critical CrowdStrike alert and notify the user's manager",
7915
+ "Kill any process on a flagged host that matches a known malware family in EDR telemetry",
7916
+ "Watch SentinelOne for ransomware behavior detections and auto-contain the host within 60 seconds",
7917
+ "Pull the process tree from EDR for every malware detection and attach it to the incident",
7918
+ "Sweep all endpoints for a given hash or filename when a new IOC arrives and report matches",
7919
+ "Detect newly created local admin accounts on any endpoint and require approval before keeping them",
7920
+ "Alert when an EDR agent goes offline for more than 1 hour and open a ticket against the asset owner",
7921
+ "Quarantine any binary flagged as suspicious by EDR and email the user with next steps",
7922
+ "Hunt EDR telemetry every night for LOLBins executed from user-writable directories",
7923
+ "Correlate EDR alerts with SIEM auth events to detect credential theft followed by execution",
7924
+ "Run a weekly EDR coverage report and list endpoints without an active agent",
7925
+ "Auto-resolve EDR alerts that match a known-good signed binary on our allowlist",
7926
+ // — Vulnerability management ————————————————————————————————————
7927
+ "Scan our asset inventory daily and open tickets for any new critical or high vulnerability",
7928
+ "Remind asset owners every Friday about open vulnerabilities older than 14 days",
7929
+ "Re-scan hosts that recently patched a critical vulnerability and close the ticket if clean",
7930
+ "Send the patch compliance report every Monday morning to IT and security leadership",
7931
+ "Correlate vulnerabilities with active exploits in the wild and re-prioritise the queue",
7932
+ // — Endpoint & host monitoring ———————————————————————————————————
7933
+ "Watch my host monitors and alert me if any agent goes offline for more than 1 hour",
7934
+ "Isolate any endpoint that triggers a critical EDR alert and notify the user's manager",
7935
+ "Kill any process on a flagged host that matches a known malware family",
7936
+ "Run an inventory of installed software on every endpoint every Sunday",
7937
+ "Detect new local admin accounts created on any endpoint and require approval",
7938
+ "Make sure full-disk encryption is on for every laptop and report exceptions weekly",
7939
+ // — Identity & access —————————————————————————————————————————
7940
+ "Review user access every quarter and flag anyone with privileges they have not used in 90 days",
7941
+ "Disable accounts that have not logged in for 60 days and notify their manager",
7942
+ "Watch for impossible travel sign-ins and force a password reset plus MFA re-enrolment",
7943
+ "Alert me whenever someone is added to an admin group and require justification within 24 hours",
7944
+ "Audit MFA enrolment weekly and email any user without a second factor",
7945
+ "Rotate service account credentials every 90 days and update the vault",
7946
+ "Detect dormant API keys older than 180 days and revoke them after notice",
7947
+ "Make sure new joiners get baseline access provisioned within 1 hour of their start date",
7948
+ "Make sure leavers have all access revoked within 15 minutes of their termination event",
7949
+ // — Cloud, infra & posture ————————————————————————————————————
7950
+ "Check our cloud accounts every hour for public S3 buckets and lock them down",
7951
+ "Detect new IAM roles with wildcard permissions and require review before they go live",
7952
+ "Watch for unencrypted databases in production and open a ticket against the owner",
7953
+ "Run a CIS benchmark scan against all production cloud accounts every Sunday",
7954
+ "Alert on any new security group that opens ports to 0.0.0.0/0",
7955
+ "Monitor cloud spend daily and warn me if any service's cost jumps more than 50% week-over-week",
7956
+ "Check certificate expiry across our domains weekly and renew anything within 30 days",
7957
+ "Watch our DNS records for unauthorised changes and revert them",
7958
+ // — Phishing & user reports ———————————————————————————————————
7959
+ "Investigate phishing reports from the abuse mailbox and respond to the reporter within 1 hour",
7960
+ "Detonate suspicious attachments in a sandbox and attach the verdict to the incident",
7961
+ "Pull URLs from reported phishing emails and submit them to URLhaus and PhishTank",
7962
+ "Send a phishing simulation to a sample of users every month and report results",
7963
+ // — Compliance, audit & reporting —————————————————————————————
7964
+ "Generate the SOC2 evidence pack at the start of every month",
7965
+ "Track changes to security policies and alert the GRC team when one is edited",
7966
+ "Send a weekly KPI report covering MTTD, MTTR, and open incident backlog",
7967
+ "Audit log retention every Sunday and confirm we still meet the 1-year requirement",
7968
+ "Generate a monthly executive summary of incidents, vulnerabilities, and detection coverage",
7969
+ // — Backups, DR & uptime ————————————————————————————————————
7970
+ "Verify nightly backups completed successfully and alert me if any failed",
7971
+ "Test restore from backup on a sample workload every month and report results",
7972
+ "Watch our public status page for incidents at our critical vendors and notify the team",
7973
+ "Ping our public endpoints every minute and open an incident if any return non-2xx for 3 minutes",
7974
+ // — Tickets, workflow & docs ——————————————————————————————————
7975
+ "Triage new Jira tickets in the security project every hour and assign by category",
7976
+ "Close tickets that have been waiting on the reporter for more than 14 days",
7977
+ "Summarise this week's closed tickets into release notes every Friday",
7978
+ "Pull action items out of meeting notes I share and create tickets for them",
7979
+ "Keep the on-call runbook in sync with the latest incident postmortems",
7980
+ "Review pull requests in our security repos and leave a summary comment within an hour",
7981
+ // — People, HR & onboarding ————————————————————————————————————
7982
+ "Send a welcome message and security training link to every new joiner on their start date",
7983
+ "Run the offboarding checklist whenever someone is marked as a leaver in HR",
7984
+ "Remind managers to complete quarterly performance reviews 1 week before the deadline",
7985
+ "Send birthday and work anniversary greetings from me on the right day",
7986
+ // — Personal / lightweight ops chores —————————————————————————
7987
+ "Order more office coffee whenever the inventory drops below 2 bags",
7988
+ "File my receipts every Friday and submit the expense report",
7989
+ "Track my open invoices and chase clients whose payment is more than 14 days overdue",
7990
+ "Send a polite follow-up to anyone I have emailed but who has not replied in 5 working days",
7991
+ "Read the security news feeds every morning and summarise the top 5 stories for me"
7992
+ ];
7993
+ var matchAgentPromptSuggestions = (input, limit = 8) => {
7994
+ const q = (input || "").trim().toLowerCase();
7995
+ if (!q) return [];
7996
+ const scored = [];
7997
+ for (const text of AGENT_PROMPT_SUGGESTIONS) {
7998
+ const lower = text.toLowerCase();
7999
+ const idx = lower.indexOf(q);
8000
+ if (idx < 0) continue;
8001
+ const score = idx === 0 ? -1e3 + text.length : idx * 10 + text.length;
8002
+ scored.push({ text, score });
8003
+ }
8004
+ scored.sort((a, b) => a.score - b.score);
8005
+ return scored.slice(0, limit).map((s) => s.text);
8006
+ };
8007
+ var DEFAULT_AGENT_PROMPT_PLACEHOLDER = "Take care of my emails by pretending to be me and answering them";
8008
+
8009
+ // components/AgentUI.tsx
7613
8010
  var import_remark_gfm4 = __toESM(require("remark-gfm"));
7614
8011
  var import_remark_breaks = __toESM(require("remark-breaks"));
7615
8012
  var import_react18_json_view2 = __toESM(require("react18-json-view"));
@@ -7755,6 +8152,73 @@ function invalidateResolvedApps() {
7755
8152
  _cache.clear();
7756
8153
  }
7757
8154
 
8155
+ // llmProviderDetect.ts
8156
+ var SHUFFLE_AI_PRESET = "Shuffle AI";
8157
+ var CUSTOM_PRESET = "Custom / self-hosted";
8158
+ var ENDPOINT_PRESETS = [
8159
+ { label: SHUFFLE_AI_PRESET, url: "" },
8160
+ { 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." },
8161
+ { 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." },
8162
+ { 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." },
8163
+ { 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." },
8164
+ { 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." },
8165
+ { 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." },
8166
+ { 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." },
8167
+ { label: "OpenRouter", url: "https://openrouter.ai/api/v1", apiKeyUrl: "https://openrouter.ai/keys", apiKeyHint: "Create a key under Keys in your OpenRouter dashboard." },
8168
+ { label: "Ollama (localhost)", url: "http://localhost:11434/v1", apiKeyHint: "Local Ollama does not require an API key \u2014 any non-empty value works." },
8169
+ { 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." },
8170
+ { label: CUSTOM_PRESET, url: "" }
8171
+ ];
8172
+ var PROVIDER_DOMAINS = {
8173
+ "Shuffle AI": "shuffler.io",
8174
+ OpenAI: "openai.com",
8175
+ Anthropic: "anthropic.com",
8176
+ "Google Gemini": "gemini.google.com",
8177
+ Mistral: "mistral.ai",
8178
+ Groq: "groq.com",
8179
+ DeepSeek: "deepseek.com",
8180
+ "Together AI": "together.ai",
8181
+ OpenRouter: "openrouter.ai",
8182
+ "Ollama (localhost)": "ollama.com",
8183
+ "LM Studio (localhost)": "lmstudio.ai"
8184
+ };
8185
+ var hostnameOf = (url) => {
8186
+ try {
8187
+ return new URL(url).hostname.replace(/^www\./, "");
8188
+ } catch {
8189
+ return null;
8190
+ }
8191
+ };
8192
+ var detectLLMProvider = (url) => {
8193
+ if (!url) return null;
8194
+ const trimmed = url.trim();
8195
+ if (!trimmed) return null;
8196
+ const exact = ENDPOINT_PRESETS.find((p) => p.url && p.url === trimmed);
8197
+ if (exact) return exact;
8198
+ const host = hostnameOf(trimmed);
8199
+ if (host) {
8200
+ const byHost = ENDPOINT_PRESETS.find((p) => {
8201
+ const presetHost = hostnameOf(p.url);
8202
+ return presetHost && presetHost === host;
8203
+ });
8204
+ if (byHost) return byHost;
8205
+ if (host === "localhost" || host === "127.0.0.1") {
8206
+ if (trimmed.includes(":11434")) return ENDPOINT_PRESETS.find((p) => p.label === "Ollama (localhost)") || null;
8207
+ if (trimmed.includes(":1234")) return ENDPOINT_PRESETS.find((p) => p.label === "LM Studio (localhost)") || null;
8208
+ }
8209
+ }
8210
+ return ENDPOINT_PRESETS.find((p) => p.label === CUSTOM_PRESET) || null;
8211
+ };
8212
+ var getProviderLogoUrl = (label, url) => {
8213
+ if (!label && !url) return "";
8214
+ let domain = label ? PROVIDER_DOMAINS[label] : void 0;
8215
+ if (!domain && url) {
8216
+ const host = hostnameOf(url);
8217
+ if (host) domain = host;
8218
+ }
8219
+ return domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=64` : "";
8220
+ };
8221
+
7758
8222
  // agentRun.ts
7759
8223
  init_api();
7760
8224
  var resolveTarget = (path, opts = {}) => {
@@ -8669,10 +9133,10 @@ var AgentRunDiagnosisBanner = ({ run, sx, onJumpToEvidence, executionId }) => {
8669
9133
  const outputWarning = !isFailed && hasOutputWarning(run);
8670
9134
  const diagnosis = outputWarning ? diagnoseOutputWarning(run) : null;
8671
9135
  if (!failureInfo && !diagnosis) return null;
8672
- const isCritical = !!failureInfo;
9136
+ const isCritical = !!failureInfo || diagnosis?.kind === "token_limit";
8673
9137
  const tone = isCritical ? "critical" : "medium";
8674
9138
  const Icon = isCritical ? import_lucide_react10.AlertTriangle : import_lucide_react10.HelpCircle;
8675
- const message = isCritical ? failureInfo.reason : `${diagnosis.title} \u2014 ${diagnosis.remediation}`;
9139
+ const message = failureInfo ? failureInfo.reason : `${diagnosis.title} \u2014 ${diagnosis.remediation}`;
8676
9140
  const firstEvidence = diagnosis?.evidence?.[0] || null;
8677
9141
  const jumpDecisionIndex = firstEvidence ? extractDecisionIndex(firstEvidence.path) : null;
8678
9142
  const canJump = !!(onJumpToEvidence && jumpDecisionIndex !== null);
@@ -9211,7 +9675,7 @@ var TimelineRow = ({
9211
9675
  ),
9212
9676
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { sx: {
9213
9677
  flex: 1,
9214
- minWidth: 0,
9678
+ minWidth: 180,
9215
9679
  fontSize: "0.85rem",
9216
9680
  color: "hsl(var(--foreground))",
9217
9681
  maxHeight: 60,
@@ -9221,7 +9685,7 @@ var TimelineRow = ({
9221
9685
  "& a": { color: "hsl(var(--primary))", textDecoration: "underline", textUnderlineOffset: "2px" },
9222
9686
  "& a:hover": { opacity: 0.85 }
9223
9687
  }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_markdown4.default, { remarkPlugins: [import_remark_gfm4.default, import_remark_breaks.default], children: normalizeMarkdown(displayLabel) }) }),
9224
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Tooltip, { title: hasTiming ? `Duration: ${dur.toFixed(2)}s \xB7 ${new Date(itemStart * 1e3).toLocaleString()}` : "No timing data", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { sx: { width: maxWidth, position: "relative", height: 10, flexShrink: 0 }, children: dur > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { sx: {
9688
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Tooltip, { title: hasTiming ? `Duration: ${dur.toFixed(2)}s \xB7 ${new Date(itemStart * 1e3).toLocaleString()}` : "No timing data", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { sx: { width: maxWidth, maxWidth, minWidth: 40, position: "relative", height: 10, flexShrink: 1, flexBasis: maxWidth }, children: dur > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { sx: {
9225
9689
  position: "absolute",
9226
9690
  left: Math.max(0, offset),
9227
9691
  width,
@@ -9547,7 +10011,7 @@ var AgentUI = ({
9547
10011
  autoLoadApps = true,
9548
10012
  title = "What do you want to do?",
9549
10013
  subtitle,
9550
- placeholder = 'Describe a task, e.g. "Get my emails for today and summarise them"',
10014
+ placeholder = DEFAULT_AGENT_PROMPT_PLACEHOLDER,
9551
10015
  defaultInput = "",
9552
10016
  autoSubmit = false,
9553
10017
  hideHeroIcon = false,
@@ -9599,6 +10063,29 @@ var AgentUI = ({
9599
10063
  const hasApiKey = !!apiKey || !!API_CONFIG.apiKey;
9600
10064
  const navigate = (0, import_react_router_dom2.useNavigate)();
9601
10065
  const [actionInput, setActionInput] = (0, import_react15.useState)(defaultInput);
10066
+ const promptAnchorRef = import_react15.default.useRef(null);
10067
+ const [suggestionsDismissed, setSuggestionsDismissed] = (0, import_react15.useState)(false);
10068
+ const [suggestionIndex, setSuggestionIndex] = (0, import_react15.useState)(-1);
10069
+ const promptSuggestions = (0, import_react15.useMemo)(
10070
+ () => matchAgentPromptSuggestions(actionInput, 8),
10071
+ [actionInput]
10072
+ );
10073
+ (0, import_react15.useEffect)(() => {
10074
+ setSuggestionIndex(-1);
10075
+ setSuggestionsDismissed(false);
10076
+ }, [actionInput]);
10077
+ const suggestionsOpen = promptSuggestions.length > 0 && !suggestionsDismissed;
10078
+ const acceptSuggestion = (0, import_react15.useCallback)((s) => {
10079
+ setActionInput(s);
10080
+ setSuggestionsDismissed(true);
10081
+ setSuggestionIndex(-1);
10082
+ requestAnimationFrame(() => {
10083
+ try {
10084
+ inputRef.current?.focus();
10085
+ } catch {
10086
+ }
10087
+ });
10088
+ }, []);
9602
10089
  const BUILTIN_DEFAULT_APPS = [
9603
10090
  { name: "http" },
9604
10091
  { name: "shuffle_tools" }
@@ -9606,6 +10093,7 @@ var AgentUI = ({
9606
10093
  const [chosenApps, setChosenApps] = (0, import_react15.useState)(apps ?? defaultApps ?? BUILTIN_DEFAULT_APPS);
9607
10094
  const [availableApps, setAvailableApps] = (0, import_react15.useState)([]);
9608
10095
  const [authAppsLoading, setAuthAppsLoading] = (0, import_react15.useState)(autoLoadApps && hasApiKey);
10096
+ const [detectedLLM, setDetectedLLM] = (0, import_react15.useState)(null);
9609
10097
  const [executionApps, setExecutionApps] = (0, import_react15.useState)([]);
9610
10098
  const [resolvedToolApps, setResolvedToolApps] = (0, import_react15.useState)({});
9611
10099
  const [appSearchOpen, setAppSearchOpen] = (0, import_react15.useState)(false);
@@ -9640,9 +10128,25 @@ var AgentUI = ({
9640
10128
  const [error, setError] = (0, import_react15.useState)(null);
9641
10129
  const [searchParams, setSearchParams] = (0, import_react_router_dom2.useSearchParams)();
9642
10130
  const initialViewParam = searchParams.get("agentView");
9643
- const [viewMode, setViewMode] = (0, import_react15.useState)(
9644
- initialViewParam === "detailed" ? "detailed" : "simple"
9645
- );
10131
+ const readStoredViewMode = () => {
10132
+ if (initialViewParam === "detailed") return "detailed";
10133
+ if (typeof window === "undefined") return "simple";
10134
+ try {
10135
+ const stored = window.localStorage.getItem("shuffle-agents-view-mode");
10136
+ if (stored === "detailed" || stored === "simple") return stored;
10137
+ } catch {
10138
+ }
10139
+ return "simple";
10140
+ };
10141
+ const [viewMode, setViewMode] = (0, import_react15.useState)(readStoredViewMode);
10142
+ (0, import_react15.useEffect)(() => {
10143
+ try {
10144
+ if (typeof window !== "undefined") {
10145
+ window.localStorage.setItem("shuffle-agents-view-mode", viewMode);
10146
+ }
10147
+ } catch {
10148
+ }
10149
+ }, [viewMode]);
9646
10150
  const [attachedImages, setAttachedImages] = (0, import_react15.useState)([]);
9647
10151
  const [nowTick, setNowTick] = (0, import_react15.useState)(() => Math.floor(Date.now() / 1e3));
9648
10152
  const [localRunStart, setLocalRunStart] = (0, import_react15.useState)(null);
@@ -9878,12 +10382,23 @@ var AgentUI = ({
9878
10382
  const list = Array.isArray(result) ? result : result?.data || [];
9879
10383
  const seen = /* @__PURE__ */ new Set();
9880
10384
  const loaded = [];
10385
+ let detectedFromEntries = null;
9881
10386
  for (const entry of list) {
9882
10387
  const app = entry?.app || entry;
9883
10388
  const name = app?.name;
9884
10389
  if (!name) continue;
9885
10390
  const valid = entry?.active || entry?.validation?.valid || entry?.hasValidAuth || app?.is_valid || app?.tested;
9886
10391
  if (valid === false) continue;
10392
+ if (!detectedFromEntries && (name.toLowerCase() === "openai" || app?.id === "5d19dd82517870c68d40cacad9b5ca91")) {
10393
+ const urlField = (entry?.fields || []).find((f) => (f?.key || "").toLowerCase() === "url");
10394
+ const urlVal = (urlField?.value || "").trim();
10395
+ if (urlVal) {
10396
+ const preset = detectLLMProvider(urlVal);
10397
+ if (preset) {
10398
+ detectedFromEntries = { label: preset.label, url: urlVal, logo: getProviderLogoUrl(preset.label, urlVal) };
10399
+ }
10400
+ }
10401
+ }
9887
10402
  const key = normalizeAgentAppName(name);
9888
10403
  if (seen.has(key)) continue;
9889
10404
  seen.add(key);
@@ -9895,6 +10410,7 @@ var AgentUI = ({
9895
10410
  }
9896
10411
  if (signal?.cancelled) return;
9897
10412
  setAvailableApps(loaded);
10413
+ setDetectedLLM(detectedFromEntries);
9898
10414
  } catch {
9899
10415
  } finally {
9900
10416
  if (!signal?.cancelled) setAuthAppsLoading(false);
@@ -10156,7 +10672,12 @@ var AgentUI = ({
10156
10672
  setSimpleSubmitAttempted(false);
10157
10673
  setError(null);
10158
10674
  setLocalRunStart(null);
10159
- setViewMode("simple");
10675
+ try {
10676
+ const stored = typeof window !== "undefined" ? window.localStorage.getItem("shuffle-agents-view-mode") : null;
10677
+ setViewMode(stored === "detailed" ? "detailed" : "simple");
10678
+ } catch {
10679
+ setViewMode("simple");
10680
+ }
10160
10681
  setShowStarter(false);
10161
10682
  userPickedStartRef.current = false;
10162
10683
  if (readUrlParams && typeof window !== "undefined") {
@@ -10592,6 +11113,34 @@ var AgentUI = ({
10592
11113
  }
10593
11114
  }, [submitOverride, actionInput, chosenApps, submitInput]);
10594
11115
  const onKeyDown = (e) => {
11116
+ if (suggestionsOpen && promptSuggestions.length > 0) {
11117
+ if (e.key === "ArrowDown") {
11118
+ e.preventDefault();
11119
+ setSuggestionIndex((i) => (i + 1) % promptSuggestions.length);
11120
+ return;
11121
+ }
11122
+ if (e.key === "ArrowUp") {
11123
+ e.preventDefault();
11124
+ setSuggestionIndex((i) => i <= 0 ? promptSuggestions.length - 1 : i - 1);
11125
+ return;
11126
+ }
11127
+ if (e.key === "Escape") {
11128
+ e.preventDefault();
11129
+ setSuggestionsDismissed(true);
11130
+ setSuggestionIndex(-1);
11131
+ return;
11132
+ }
11133
+ if (e.key === "Tab" && suggestionIndex >= 0) {
11134
+ e.preventDefault();
11135
+ acceptSuggestion(promptSuggestions[suggestionIndex]);
11136
+ return;
11137
+ }
11138
+ if (e.key === "Enter" && suggestionIndex >= 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey) {
11139
+ e.preventDefault();
11140
+ acceptSuggestion(promptSuggestions[suggestionIndex]);
11141
+ return;
11142
+ }
11143
+ }
10595
11144
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
10596
11145
  e.preventDefault();
10597
11146
  handlePrimarySubmit();
@@ -11249,7 +11798,7 @@ var AgentUI = ({
11249
11798
  mt: -1,
11250
11799
  maxWidth: 600
11251
11800
  }, children: subtitle }),
11252
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_material16.Box, { sx: {
11801
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_material16.Box, { ref: promptAnchorRef, sx: {
11253
11802
  width: "100%",
11254
11803
  display: "flex",
11255
11804
  flexDirection: "column",
@@ -11457,6 +12006,67 @@ var AgentUI = ({
11457
12006
  ) }) })
11458
12007
  ] })
11459
12008
  ] }),
12009
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
12010
+ import_material16.Popper,
12011
+ {
12012
+ open: suggestionsOpen,
12013
+ anchorEl: promptAnchorRef.current,
12014
+ placement: "bottom-start",
12015
+ style: { zIndex: 1300, width: promptAnchorRef.current?.offsetWidth },
12016
+ modifiers: [{ name: "offset", options: { offset: [0, 6] } }],
12017
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.ClickAwayListener, { onClickAway: () => setSuggestionsDismissed(true), children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
12018
+ import_material16.Paper,
12019
+ {
12020
+ elevation: 6,
12021
+ sx: {
12022
+ bgcolor: "hsl(var(--card))",
12023
+ border: "1px solid hsl(var(--border))",
12024
+ borderRadius: 2,
12025
+ overflow: "hidden",
12026
+ maxHeight: 320,
12027
+ overflowY: "auto"
12028
+ },
12029
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.MenuList, { dense: true, disablePadding: true, children: promptSuggestions.map((s, i) => {
12030
+ const q = actionInput.trim();
12031
+ const lower = s.toLowerCase();
12032
+ const idx = q ? lower.indexOf(q.toLowerCase()) : -1;
12033
+ const before = idx >= 0 ? s.slice(0, idx) : s;
12034
+ const match = idx >= 0 ? s.slice(idx, idx + q.length) : "";
12035
+ const after = idx >= 0 ? s.slice(idx + q.length) : "";
12036
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
12037
+ import_material16.MenuItem,
12038
+ {
12039
+ selected: i === suggestionIndex,
12040
+ onMouseEnter: () => setSuggestionIndex(i),
12041
+ onMouseDown: (e) => {
12042
+ e.preventDefault();
12043
+ acceptSuggestion(s);
12044
+ },
12045
+ sx: {
12046
+ fontSize: "0.88rem",
12047
+ color: "hsl(var(--foreground))",
12048
+ py: 0.75,
12049
+ px: 2,
12050
+ "&.Mui-selected, &.Mui-selected:hover": {
12051
+ bgcolor: "hsl(var(--muted) / 0.6)"
12052
+ }
12053
+ },
12054
+ children: [
12055
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react11.Search, { size: 14, style: { marginRight: 10, color: "hsl(var(--muted-foreground))", flexShrink: 0 } }),
12056
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_material16.Box, { component: "span", sx: { whiteSpace: "normal", lineHeight: 1.4 }, children: [
12057
+ before,
12058
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { component: "span", sx: { fontWeight: 700, color: "hsl(var(--foreground))" }, children: match }),
12059
+ after
12060
+ ] })
12061
+ ]
12062
+ },
12063
+ s
12064
+ );
12065
+ }) })
12066
+ }
12067
+ ) })
12068
+ }
12069
+ ),
11460
12070
  !hideAppPicker && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { sx: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_material16.Box, { sx: {
11461
12071
  display: "inline-flex",
11462
12072
  flexWrap: "wrap",
@@ -11507,8 +12117,19 @@ var AgentUI = ({
11507
12117
  "&:hover": { color: "hsl(var(--foreground))", bgcolor: "hsl(var(--muted) / 0.5)" }
11508
12118
  },
11509
12119
  children: [
11510
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react11.Settings, { size: 14 }),
11511
- "Choose LLM"
12120
+ detectedLLM?.logo ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
12121
+ import_material16.Box,
12122
+ {
12123
+ component: "img",
12124
+ src: detectedLLM.logo,
12125
+ alt: "",
12126
+ sx: { width: 14, height: 14, borderRadius: "3px", objectFit: "contain", display: "block" },
12127
+ onError: (e) => {
12128
+ e.currentTarget.style.display = "none";
12129
+ }
12130
+ }
12131
+ ) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react11.Settings, { size: 14 }),
12132
+ detectedLLM?.label || "Choose LLM"
11512
12133
  ]
11513
12134
  }
11514
12135
  ) }),
@@ -11742,7 +12363,7 @@ var AgentUI = ({
11742
12363
  AgentRunDiagnosisBanner_default,
11743
12364
  {
11744
12365
  run: execution?.results?.length ? execution : agentData,
11745
- sx: { px: 0, pb: 0, mb: 2 },
12366
+ sx: { px: 1.5, pb: 0, mb: 2 },
11746
12367
  executionId: execution?.execution_id,
11747
12368
  onJumpToEvidence: (decisionIndex) => {
11748
12369
  const dec = (agentData?.decisions || [])[decisionIndex];
@@ -11967,14 +12588,14 @@ var AgentUI = ({
11967
12588
  ] })
11968
12589
  ] });
11969
12590
  })() : !finishAnswer && isRunning ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Typography, { sx: { fontSize: "0.85rem", color: "hsl(var(--muted-foreground))" }, children: "Waiting for the agent to produce the first step." }) : !finishAnswer ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Typography, { sx: { fontSize: "0.85rem", color: "hsl(var(--muted-foreground))" }, children: "No final answer was returned. Open Detailed to inspect each step." }) : null,
11970
- !isRunning && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
12591
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
11971
12592
  import_material16.Button,
11972
12593
  {
11973
12594
  size: "small",
11974
12595
  variant: "text",
11975
12596
  onClick: () => setViewMode("detailed"),
11976
12597
  sx: { color: "hsl(var(--primary))", textTransform: "none", px: 0 },
11977
- children: "View detailed timeline \u2192"
12598
+ children: isRunning ? "View live detailed timeline \u2192" : "View detailed timeline \u2192"
11978
12599
  }
11979
12600
  ) })
11980
12601
  ] });
@@ -12255,8 +12876,6 @@ var import_jsx_runtime22 = require("react/jsx-runtime");
12255
12876
  var import_react17 = require("react");
12256
12877
  var OPENAI_APP_NAME = "OpenAI";
12257
12878
  var OPENAI_APP_ID = "5d19dd82517870c68d40cacad9b5ca91";
12258
- var SHUFFLE_AI_PRESET = "Shuffle AI";
12259
- var CUSTOM_PRESET = "Custom / self-hosted";
12260
12879
  var OPENAI_ALGOLIA_APP = {
12261
12880
  name: OPENAI_APP_NAME,
12262
12881
  description: "OpenAI-compatible LLM endpoint for agent operations",
@@ -12276,20 +12895,32 @@ var OPENAI_ALGOLIA_APP = {
12276
12895
  triggers: [],
12277
12896
  verified: true
12278
12897
  };
12279
- var ENDPOINT_PRESETS = [
12280
- { label: SHUFFLE_AI_PRESET, url: "" },
12281
- { 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." },
12282
- { 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." },
12283
- { 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." },
12284
- { 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." },
12285
- { 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." },
12286
- { 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." },
12287
- { 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." },
12288
- { label: "OpenRouter", url: "https://openrouter.ai/api/v1", apiKeyUrl: "https://openrouter.ai/keys", apiKeyHint: "Create a key under Keys in your OpenRouter dashboard." },
12289
- { label: "Ollama (localhost)", url: "http://localhost:11434/v1", apiKeyHint: "Local Ollama does not require an API key \u2014 any non-empty value works." },
12290
- { 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." },
12291
- { label: CUSTOM_PRESET, url: "" }
12292
- ];
12898
+ var CUSTOM_MODEL = "Custom\u2026";
12899
+ var PROVIDER_MODELS = {
12900
+ 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"],
12901
+ Anthropic: ["claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5", "claude-opus-4", "claude-sonnet-4", "claude-3-7-sonnet-latest"],
12902
+ "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"],
12903
+ Mistral: ["mistral-large-2026", "mistral-medium-3", "mistral-small-3.2", "codestral-2026", "magistral-medium-2026", "ministral-8b-latest"],
12904
+ 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"],
12905
+ DeepSeek: ["deepseek-v3.5", "deepseek-v3", "deepseek-r1", "deepseek-coder-v3"],
12906
+ "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"],
12907
+ 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"],
12908
+ "Ollama (localhost)": ["llama3.3", "llama3.2", "qwen3", "qwen3:32b", "deepseek-r1", "deepseek-r1:70b", "mistral-small3", "phi4", "gemma3"],
12909
+ "LM Studio (localhost)": ["llama-3.3-70b-instruct", "qwen3-32b", "deepseek-r1-distill-qwen-32b", "mistral-small-3", "phi-4", "gemma-3-27b"]
12910
+ };
12911
+ var ProviderLogo = ({ label, url }) => {
12912
+ const [errored, setErrored] = (0, import_react16.useState)(false);
12913
+ let domain = PROVIDER_DOMAINS[label];
12914
+ if (!domain && url) {
12915
+ try {
12916
+ domain = new URL(url).hostname.replace(/^www\./, "");
12917
+ } catch {
12918
+ }
12919
+ }
12920
+ const src = domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=64` : "";
12921
+ const initial = label.trim().charAt(0).toUpperCase() || "?";
12922
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Box, { 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__ */ (0, import_jsx_runtime22.jsx)("img", { src, alt: "", width: 18, height: 18, style: { display: "block" }, onError: () => setErrored(true) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Typography, { sx: { fontSize: "0.65rem", fontWeight: 600, color: "hsl(var(--muted-foreground))", lineHeight: 1 }, children: initial }) });
12923
+ };
12293
12924
  var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serverside, theme, colorMode }) => {
12294
12925
  useSyncHostBaseUrl(globalUrl);
12295
12926
  const { authStates, authenticatedApps, handleAuthChange, handleTestConnection, handleSaveAuth, refreshAuth } = useAppAuth();
@@ -12305,46 +12936,16 @@ var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serv
12305
12936
  status: "pending",
12306
12937
  credentials: {}
12307
12938
  };
12308
- const currentUrl = authState.credentials?.url || "";
12309
- const attemptedDeletionRef = (0, import_react16.useRef)(/* @__PURE__ */ new Set());
12310
- (0, import_react16.useEffect)(() => {
12311
- if (authState.status === "testing") return;
12312
- const failedEntries = openaiEntries.filter((e) => e.validation && e.validation.valid === false && e.id);
12313
- if (failedEntries.length === 0) return;
12314
- let cancelled = false;
12315
- (async () => {
12316
- let deletedAny = false;
12317
- for (const entry of failedEntries) {
12318
- const id = entry.id;
12319
- if (attemptedDeletionRef.current.has(id)) continue;
12320
- attemptedDeletionRef.current.add(id);
12321
- try {
12322
- const resp = await fetch(getApiUrl(`/api/v1/apps/authentication/${id}`), {
12323
- method: "DELETE",
12324
- credentials: "include",
12325
- headers: { ...getAuthHeader() }
12326
- });
12327
- if (resp.ok) deletedAny = true;
12328
- } catch (err) {
12329
- console.error("[LocalLLMConfig] Failed to auto-delete failed OpenAI auth:", err);
12330
- }
12331
- }
12332
- if (!cancelled && deletedAny) {
12333
- await refreshAuth();
12334
- refreshAllIntegrationStatus();
12335
- }
12336
- })();
12337
- return () => {
12338
- cancelled = true;
12339
- };
12340
- }, [openaiEntries, authState.status, refreshAuth]);
12939
+ const savedUrlFromEntry = (openaiEntries[0]?.fields || []).find((f) => (f?.key || "").toLowerCase() === "url")?.value || "";
12940
+ const currentUrl = (authState.credentials?.url || savedUrlFromEntry || "").trim();
12941
+ const [customModel, setCustomModel] = (0, import_react16.useState)("");
12942
+ const [customMode, setCustomMode] = (0, import_react16.useState)(false);
12341
12943
  const hasOpenAIEntries = openaiEntries.length > 0;
12342
12944
  const effectivePreset = (0, import_react16.useMemo)(() => {
12343
12945
  if (selectedPreset) return selectedPreset;
12344
12946
  if (!currentUrl && !hasOpenAIEntries) return SHUFFLE_AI_PRESET;
12345
12947
  if (!currentUrl) return "";
12346
- const match = ENDPOINT_PRESETS.find((p) => p.url && p.url === currentUrl);
12347
- return match ? match.label : CUSTOM_PRESET;
12948
+ return detectLLMProvider(currentUrl)?.label || CUSTOM_PRESET;
12348
12949
  }, [selectedPreset, currentUrl, hasOpenAIEntries]);
12349
12950
  const applyShuffleAI = async () => {
12350
12951
  let deletedAny = false;
@@ -12381,7 +12982,16 @@ var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serv
12381
12982
  setSelectedPreset(label);
12382
12983
  const preset = ENDPOINT_PRESETS.find((p) => p.label === label);
12383
12984
  if (!preset) return;
12384
- handleAuthChange(OPENAI_APP_ID, { ...authState.credentials, url: preset.label === CUSTOM_PRESET ? customUrl : preset.url });
12985
+ const topModel = PROVIDER_MODELS[label]?.[0] || "";
12986
+ const existingModel = authState.credentials?.model || "";
12987
+ const nextModel = topModel || existingModel;
12988
+ setCustomMode(false);
12989
+ setCustomModel("");
12990
+ handleAuthChange(OPENAI_APP_ID, {
12991
+ ...authState.credentials,
12992
+ url: preset.label === CUSTOM_PRESET ? customUrl : preset.url,
12993
+ ...nextModel ? { model: nextModel } : {}
12994
+ });
12385
12995
  };
12386
12996
  const handleCustomUrlChange = (value) => {
12387
12997
  setCustomUrl(value);
@@ -12433,14 +13043,30 @@ var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serv
12433
13043
  isOptionEqualToValue: (opt, val) => opt === val,
12434
13044
  renderOption: (props, option) => {
12435
13045
  const preset = ENDPOINT_PRESETS.find((p) => p.label === option);
12436
- return /* @__PURE__ */ (0, import_react17.createElement)("li", { ...props, key: option }, /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_material18.Box, { sx: { display: "flex", alignItems: "center", gap: 1, width: "100%", minWidth: 0 }, children: [
13046
+ return /* @__PURE__ */ (0, import_react17.createElement)("li", { ...props, key: option }, /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_material18.Box, { sx: { display: "flex", alignItems: "center", gap: 1.25, width: "100%", minWidth: 0 }, children: [
13047
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ProviderLogo, { label: option, url: preset?.url }),
12437
13048
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Typography, { sx: { fontSize: "0.85rem", color: "hsl(var(--popover-foreground))" }, children: option }),
12438
13049
  preset?.url && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Typography, { component: "span", sx: { ml: "auto", color: "hsl(var(--muted-foreground))", fontSize: "0.75rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: preset.url })
12439
13050
  ] }));
12440
13051
  },
12441
- renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.TextField, { ...params, placeholder: "Search a provider\u2026" }),
13052
+ renderInput: (params) => {
13053
+ const preset = ENDPOINT_PRESETS.find((p) => p.label === effectivePreset);
13054
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
13055
+ import_material18.TextField,
13056
+ {
13057
+ ...params,
13058
+ placeholder: "Search a provider\u2026",
13059
+ slotProps: {
13060
+ input: {
13061
+ ...params.InputProps,
13062
+ startAdornment: effectivePreset ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Box, { sx: { display: "flex", alignItems: "center", pl: 0.5, mr: 0.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ProviderLogo, { label: effectivePreset, url: preset?.url }) }) : void 0
13063
+ }
13064
+ }
13065
+ }
13066
+ );
13067
+ },
12442
13068
  slotProps: {
12443
- paper: { sx: { bgcolor: "hsl(var(--popover))", color: "hsl(var(--popover-foreground))", border: "1px solid hsl(var(--border))" } },
13069
+ paper: { sx: { bgcolor: "hsl(var(--popover))", color: "hsl(var(--popover-foreground))", border: "1px solid hsl(var(--border))", minWidth: 580 } },
12444
13070
  popper: { sx: { zIndex: 9999 } }
12445
13071
  }
12446
13072
  }
@@ -12503,6 +13129,59 @@ var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serv
12503
13129
  hideDocsLink: true,
12504
13130
  hideUrlFields: true,
12505
13131
  borderless: true,
13132
+ compactAuthForm: true,
13133
+ extraFieldsSlot: (PROVIDER_MODELS[effectivePreset]?.length ?? 0) > 0 ? ({ credentials, setField }) => {
13134
+ const liveModel = credentials.model || "";
13135
+ const presetModels = PROVIDER_MODELS[effectivePreset] || [];
13136
+ const liveIsCustom = liveModel !== "" && !presetModels.includes(liveModel);
13137
+ const showCustom = customMode || liveIsCustom;
13138
+ const liveSelectValue = showCustom ? CUSTOM_MODEL : liveModel || null;
13139
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_material18.Box, { sx: { display: "flex", flexDirection: "column", gap: 1 }, children: [
13140
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Typography, { sx: { fontSize: "0.85rem", fontWeight: 600, color: "hsl(var(--foreground))" }, children: "Model" }),
13141
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
13142
+ import_material18.Autocomplete,
13143
+ {
13144
+ size: "small",
13145
+ fullWidth: true,
13146
+ disableClearable: true,
13147
+ options: [...presetModels, CUSTOM_MODEL],
13148
+ value: liveSelectValue,
13149
+ onChange: (_e, val) => {
13150
+ if (!val) return;
13151
+ if (val === CUSTOM_MODEL) {
13152
+ setCustomMode(true);
13153
+ setField("model", customModel);
13154
+ } else {
13155
+ setCustomMode(false);
13156
+ setCustomModel("");
13157
+ setField("model", val);
13158
+ }
13159
+ },
13160
+ isOptionEqualToValue: (opt, val) => opt === val,
13161
+ slotProps: {
13162
+ paper: { sx: { bgcolor: "hsl(var(--popover))", color: "hsl(var(--popover-foreground))", border: "1px solid hsl(var(--border))" } },
13163
+ popper: { sx: { zIndex: 9999 } }
13164
+ },
13165
+ renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.TextField, { ...params, placeholder: "Select a model\u2026" })
13166
+ }
13167
+ ),
13168
+ showCustom && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
13169
+ import_material18.TextField,
13170
+ {
13171
+ size: "small",
13172
+ fullWidth: true,
13173
+ placeholder: "Enter a custom model identifier",
13174
+ value: customModel || (liveIsCustom ? liveModel : ""),
13175
+ onChange: (e) => {
13176
+ setCustomModel(e.target.value);
13177
+ setField("model", e.target.value);
13178
+ },
13179
+ helperText: "Exact model name as expected by the provider's API",
13180
+ sx: { "& .MuiFormHelperText-root": { color: "hsl(var(--muted-foreground))" } }
13181
+ }
13182
+ )
13183
+ ] });
13184
+ } : void 0,
12506
13185
  globalUrl,
12507
13186
  userdata,
12508
13187
  isLoaded,
@@ -12580,9 +13259,16 @@ var AgentRunDrawer = ({
12580
13259
  setActiveTab("localLLM");
12581
13260
  }
12582
13261
  };
13262
+ const onClose2 = () => {
13263
+ onClose();
13264
+ };
12583
13265
  window.addEventListener("agent-drawer-open", onOpen);
12584
- return () => window.removeEventListener("agent-drawer-open", onOpen);
12585
- }, []);
13266
+ window.addEventListener("agent-drawer-close", onClose2);
13267
+ return () => {
13268
+ window.removeEventListener("agent-drawer-open", onOpen);
13269
+ window.removeEventListener("agent-drawer-close", onClose2);
13270
+ };
13271
+ }, [onClose]);
12586
13272
  const effectiveLocalLLMSlot = localLLMSlot ?? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12587
13273
  LocalLLMConfig_default,
12588
13274
  {
@@ -13054,7 +13740,7 @@ var STATUS_CONFIG = {
13054
13740
  EXECUTING: { icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Loader2, { size: 16 }), color: "hsl(var(--severity-medium, 38 92% 50%))", label: "Running" },
13055
13741
  RUNNING: { icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Loader2, { size: 16 }), color: "hsl(var(--severity-medium, 38 92% 50%))", label: "Running" },
13056
13742
  WAITING: { icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Clock, { size: 16 }), color: "hsl(var(--severity-info, 217 91% 60%))", label: "Waiting" },
13057
- LIMIT_REACHED: { icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react14.AlertTriangle, { size: 16 }), color: "hsl(var(--severity-medium, 38 92% 50%))", label: "Limit reached" }
13743
+ LIMIT_REACHED: { icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react14.AlertTriangle, { size: 16 }), color: "hsl(var(--severity-critical, 0 72% 55%))", label: "Limit reached" }
13058
13744
  };
13059
13745
  var getEffectiveStatus = (run) => {
13060
13746
  const raw = (run.status || "").toUpperCase();
@@ -13067,21 +13753,126 @@ var getEffectiveStatus = (run) => {
13067
13753
  }
13068
13754
  return raw;
13069
13755
  };
13070
- var getRunIcon = (run) => {
13071
- const src = (run.execution_source || "").toLowerCase();
13072
- const arg = (run.execution_argument || "").toLowerCase();
13073
- if (src.includes("schedule") || src.includes("cron")) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Clock, { size: 18 });
13074
- if (src.includes("webhook") || src.includes("http")) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Globe, { size: 18 });
13075
- if (arg.includes("alert") || arg.includes("detect")) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Activity, { size: 18 });
13076
- if (arg.includes("report") || arg.includes("email")) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.FileText, { size: 18 });
13077
- if (arg.includes("endpoint") || arg.includes("server")) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Server, { size: 18 });
13078
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Zap, { size: 18 });
13756
+ var SOURCE_ICON_SIZE = 18;
13757
+ 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());
13758
+ var DATASTORE_KEY_RE = /\bKey:\s*(\S+)/i;
13759
+ var DATASTORE_CATEGORY_RE = /\bCategory:\s*(\S+)/i;
13760
+ var parseDatastoreTask = (arg) => {
13761
+ if (!arg) return null;
13762
+ const k = arg.match(DATASTORE_KEY_RE);
13763
+ const c = arg.match(DATASTORE_CATEGORY_RE);
13764
+ if (!k || !c) return null;
13765
+ const key = (k[1] || "").trim();
13766
+ const category = (c[1] || "").trim();
13767
+ if (!key || !category) return null;
13768
+ return { key, category };
13769
+ };
13770
+ var isAIAgentResult = (result) => String(result?.action?.app_name || "").trim().toLowerCase() === "ai agent";
13771
+ var getAIAgentResultPayload = (run) => {
13772
+ const results = Array.isArray(run.results) ? run.results : [];
13773
+ const agentResult = results.find(isAIAgentResult);
13774
+ if (!agentResult || typeof agentResult !== "object" || !("result" in agentResult)) return null;
13775
+ return tryParseJson(agentResult.result);
13776
+ };
13777
+ var getDirectAgentInput = (payload) => {
13778
+ if (!payload || typeof payload !== "object") return null;
13779
+ const obj = payload;
13780
+ if (typeof obj.original_input === "string" && obj.original_input.trim()) {
13781
+ return obj.original_input.trim();
13782
+ }
13783
+ if (typeof obj.input === "string" && obj.input.trim()) {
13784
+ return obj.input.trim();
13785
+ }
13786
+ return null;
13787
+ };
13788
+ var findDatastoreTaskInRun = (run) => {
13789
+ const agentInput = getDirectAgentInput(getAIAgentResultPayload(run));
13790
+ const agentHit = parseDatastoreTask(agentInput || void 0);
13791
+ if (agentHit) return agentHit;
13792
+ const anyRun = run;
13793
+ const candidates = [];
13794
+ if (typeof anyRun.original_input === "string") candidates.push(anyRun.original_input);
13795
+ if (typeof anyRun.input === "string") candidates.push(anyRun.input);
13796
+ if (run.execution_argument) candidates.push(run.execution_argument);
13797
+ if (run.result) candidates.push(run.result);
13798
+ if (Array.isArray(anyRun.results)) {
13799
+ try {
13800
+ candidates.push(JSON.stringify(anyRun.results));
13801
+ } catch {
13802
+ }
13803
+ }
13804
+ for (const c of candidates) {
13805
+ const hit = parseDatastoreTask(c);
13806
+ if (hit) return hit;
13807
+ }
13808
+ return null;
13809
+ };
13810
+ var classifyRunSource = (run) => {
13811
+ const raw = (run.execution_source || "").trim();
13812
+ const src = raw.toLowerCase();
13813
+ const ds = findDatastoreTaskInRun(run);
13814
+ if (ds || src.includes("datastore") || src.includes("enrichment") || src.includes("automation")) {
13815
+ return {
13816
+ kind: "datastore",
13817
+ label: "Datastore automation",
13818
+ 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).",
13819
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Database, { size: SOURCE_ICON_SIZE }),
13820
+ datastoreKey: ds?.key,
13821
+ datastoreCategory: ds?.category
13822
+ };
13823
+ }
13824
+ if (src.includes("schedule") || src.includes("cron")) {
13825
+ return {
13826
+ kind: "schedule",
13827
+ label: "Schedule",
13828
+ reason: "Started by a scheduled trigger (cron).",
13829
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Clock, { size: SOURCE_ICON_SIZE })
13830
+ };
13831
+ }
13832
+ if (src.includes("webhook") || src.includes("http")) {
13833
+ return {
13834
+ kind: "webhook",
13835
+ label: "Webhook",
13836
+ reason: "Started by an incoming webhook / HTTP trigger.",
13837
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Globe, { size: SOURCE_ICON_SIZE })
13838
+ };
13839
+ }
13840
+ if (src.includes("form")) {
13841
+ return {
13842
+ kind: "form",
13843
+ label: "Form",
13844
+ reason: "Started by a form submission.",
13845
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.FileText, { size: SOURCE_ICON_SIZE })
13846
+ };
13847
+ }
13848
+ if (raw && looksLikeExecutionId(raw)) {
13849
+ return {
13850
+ kind: "workflow",
13851
+ label: "Workflow",
13852
+ reason: "Started by another workflow as a subflow.",
13853
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Workflow, { size: SOURCE_ICON_SIZE })
13854
+ };
13855
+ }
13856
+ if (src === "" || src === "manual" || src === "default" || src === "demo") {
13857
+ return {
13858
+ kind: "manual",
13859
+ label: "Manual",
13860
+ reason: "Started manually from the Agents UI.",
13861
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Hand, { size: SOURCE_ICON_SIZE })
13862
+ };
13863
+ }
13864
+ return {
13865
+ kind: "unknown",
13866
+ label: raw || "Unknown",
13867
+ reason: raw ? `Started by "${raw}".` : "Trigger source unknown.",
13868
+ icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react13.Zap, { size: SOURCE_ICON_SIZE })
13869
+ };
13079
13870
  };
13080
13871
  var getRunIconColor = (run) => {
13081
13872
  const status = getEffectiveStatus(run);
13082
13873
  if (status === "FINISHED" || status === "SUCCESS") return "hsl(var(--severity-low, 142 71% 45%))";
13083
13874
  if (status === "FAILED" || status === "ABORTED") return "hsl(var(--severity-critical, 0 72% 55%))";
13084
- if (status === "LIMIT_REACHED") return "hsl(var(--severity-medium, 38 92% 50%))";
13875
+ if (status === "LIMIT_REACHED") return "hsl(var(--severity-critical, 0 72% 55%))";
13085
13876
  if (status === "EXECUTING" || status === "RUNNING") return "hsl(var(--severity-medium, 38 92% 50%))";
13086
13877
  return "hsl(var(--primary, 24 100% 50%))";
13087
13878
  };
@@ -13179,9 +13970,12 @@ var tryParseJson = (s) => {
13179
13970
  }
13180
13971
  };
13181
13972
  var getRunPrompt = (run) => {
13182
- const results = Array.isArray(run.results) ? run.results : null;
13973
+ const anyRun = run;
13974
+ const agentInput = getDirectAgentInput(getAIAgentResultPayload(run));
13975
+ if (agentInput) return agentInput;
13976
+ const results = Array.isArray(anyRun.results) ? anyRun.results : null;
13183
13977
  if (results) {
13184
- const agentResult = results.find((r) => r?.action?.app_name === "AI Agent");
13978
+ const agentResult = results.find(isAIAgentResult);
13185
13979
  if (agentResult?.result) {
13186
13980
  const hit = deepFindPrompt(tryParseJson(agentResult.result));
13187
13981
  if (hit) return hit;
@@ -13192,6 +13986,12 @@ var getRunPrompt = (run) => {
13192
13986
  if (hit) return hit;
13193
13987
  }
13194
13988
  }
13989
+ if (typeof anyRun.original_input === "string" && anyRun.original_input.trim()) {
13990
+ return anyRun.original_input.trim();
13991
+ }
13992
+ if (typeof anyRun.input === "string" && anyRun.input.trim()) {
13993
+ return anyRun.input.trim();
13994
+ }
13195
13995
  if (run.result) {
13196
13996
  const hit = deepFindPrompt(tryParseJson(run.result));
13197
13997
  if (hit) return hit;
@@ -13232,7 +14032,9 @@ var getRunSubtitle = (run) => {
13232
14032
  if (run.result.length < 120) return run.result;
13233
14033
  }
13234
14034
  }
13235
- return run.execution_source || "Agent execution";
14035
+ const status = getEffectiveStatus(run);
14036
+ const isRunning = status === "EXECUTING" || status === "RUNNING" || status === "WAITING";
14037
+ return run.execution_source || (isRunning ? "Agent still running\u2026" : "Agent execution");
13236
14038
  };
13237
14039
  var normalizeResultStatus = (s) => {
13238
14040
  const v = (s || "").toUpperCase();
@@ -13334,41 +14136,60 @@ var AgentRunRow = ({ run, onClick, sx, appIcons, onAppClick }) => {
13334
14136
  ...Array.isArray(sx) ? sx : sx ? [sx] : []
13335
14137
  ],
13336
14138
  children: [
13337
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13338
- import_material20.Box,
13339
- {
13340
- sx: {
13341
- width: 40,
13342
- height: 40,
13343
- borderRadius: "50%",
13344
- display: "flex",
13345
- alignItems: "center",
13346
- justifyContent: "center",
13347
- bgcolor: `${iconColor}15`,
13348
- color: iconColor,
13349
- flexShrink: 0
13350
- },
13351
- children: getRunIcon(run)
13352
- }
13353
- ),
14139
+ (() => {
14140
+ const sourceInfo = classifyRunSource(run);
14141
+ const dsLink = sourceInfo.kind === "datastore" && sourceInfo.datastoreKey && sourceInfo.datastoreCategory ? `https://shuffler.io/admin?tab=datastore&category=${encodeURIComponent(sourceInfo.datastoreCategory)}&key=${encodeURIComponent(sourceInfo.datastoreKey)}` : null;
14142
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
14143
+ import_material20.Tooltip,
14144
+ {
14145
+ title: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_material20.Box, { sx: { lineHeight: 1.4 }, children: [
14146
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material20.Box, { sx: { fontWeight: 600 }, children: sourceInfo.label }),
14147
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material20.Box, { sx: { opacity: 0.85 }, children: sourceInfo.reason })
14148
+ ] }),
14149
+ placement: "top",
14150
+ arrow: true,
14151
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
14152
+ import_material20.Box,
14153
+ {
14154
+ onClick: dsLink ? (e) => {
14155
+ e.stopPropagation();
14156
+ window.open(dsLink, "_blank", "noopener,noreferrer");
14157
+ } : void 0,
14158
+ sx: {
14159
+ width: 40,
14160
+ height: 40,
14161
+ borderRadius: "50%",
14162
+ display: "flex",
14163
+ alignItems: "center",
14164
+ justifyContent: "center",
14165
+ bgcolor: `${iconColor}15`,
14166
+ color: iconColor,
14167
+ flexShrink: 0,
14168
+ cursor: dsLink ? "pointer" : "inherit",
14169
+ transition: "background 0.15s ease",
14170
+ "&:hover": dsLink ? { bgcolor: `${iconColor}30` } : void 0
14171
+ },
14172
+ children: sourceInfo.icon
14173
+ }
14174
+ )
14175
+ }
14176
+ );
14177
+ })(),
13354
14178
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_material20.Box, { sx: { flex: 1, minWidth: 0 }, children: [
13355
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_material20.Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 0.25 }, children: [
13356
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13357
- import_material20.Typography,
13358
- {
13359
- sx: {
13360
- fontSize: "0.9rem",
13361
- fontWeight: 500,
13362
- color: "hsl(var(--foreground))",
13363
- overflow: "hidden",
13364
- textOverflow: "ellipsis",
13365
- whiteSpace: "nowrap"
13366
- },
13367
- children: getRunTitle(run)
13368
- }
13369
- ),
13370
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material20.Box, { sx: { display: "flex", alignItems: "center", color: cfg.color, flexShrink: 0 }, children: cfg.icon })
13371
- ] }),
14179
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material20.Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 0.25 }, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
14180
+ import_material20.Typography,
14181
+ {
14182
+ sx: {
14183
+ fontSize: "0.9rem",
14184
+ fontWeight: 500,
14185
+ color: "hsl(var(--foreground))",
14186
+ overflow: "hidden",
14187
+ textOverflow: "ellipsis",
14188
+ whiteSpace: "nowrap"
14189
+ },
14190
+ children: getRunTitle(run)
14191
+ }
14192
+ ) }),
13372
14193
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13373
14194
  import_material20.Typography,
13374
14195
  {
@@ -13401,17 +14222,20 @@ var AgentRunRow = ({ run, onClick, sx, appIcons, onAppClick }) => {
13401
14222
  ] })
13402
14223
  ] })
13403
14224
  ] }),
13404
- tools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
14225
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13405
14226
  import_material20.Box,
13406
14227
  {
13407
14228
  onClick: (e) => e.stopPropagation(),
13408
14229
  sx: {
13409
14230
  display: { xs: "none", sm: "flex" },
13410
14231
  alignItems: "center",
14232
+ justifyContent: "flex-end",
13411
14233
  flexShrink: 0,
13412
- ml: 1
14234
+ ml: 1,
14235
+ width: 140,
14236
+ minHeight: 28
13413
14237
  },
13414
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
14238
+ children: tools.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13415
14239
  import_material20.AvatarGroup,
13416
14240
  {
13417
14241
  max: 5,
@@ -13460,7 +14284,7 @@ var AgentRunRow = ({ run, onClick, sx, appIcons, onAppClick }) => {
13460
14284
  ) }, t.name);
13461
14285
  })
13462
14286
  }
13463
- )
14287
+ ) : null
13464
14288
  }
13465
14289
  )
13466
14290
  ]
@@ -14281,6 +15105,15 @@ var AgentsView = ({
14281
15105
  open: false,
14282
15106
  tab: "localLLM"
14283
15107
  });
15108
+ (0, import_react20.useEffect)(() => {
15109
+ if (typeof window === "undefined") return;
15110
+ const handler = (e) => {
15111
+ const detail = e.detail;
15112
+ setBuiltInDrawer({ open: true, tab: detail?.tab ?? "localLLM" });
15113
+ };
15114
+ window.addEventListener("agent-drawer-open", handler);
15115
+ return () => window.removeEventListener("agent-drawer-open", handler);
15116
+ }, []);
14284
15117
  const effectiveSchedule = (0, import_react20.useCallback)(
14285
15118
  async (info) => {
14286
15119
  if (onSchedule) return onSchedule(info);
@@ -14677,7 +15510,9 @@ var LocalLLMConfig2 = withMcpTheme(LocalLLMConfig_default, "LocalLLMConfig");
14677
15510
  deleteDatastoreItem,
14678
15511
  deleteDatastoreItems,
14679
15512
  diagnoseOutputWarning,
15513
+ extractActionAppNames,
14680
15514
  extractValidatedIngestionApps,
15515
+ extractWorkflowActionAppNames,
14681
15516
  extractWorkflowAppNames,
14682
15517
  fetchApps,
14683
15518
  fetchAppsViaApiConfig,