@alivault/pico 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{index-CW1T9dk-.js → index-DBVxXDtw.js} +4 -4
  3. package/.output/public/assets/routes-yUxD2q-l.js +2194 -0
  4. package/.output/public/assets/styles-CUVz7jV9.css +2 -0
  5. package/.output/public/screenshots/pico-ios-dark.png +0 -0
  6. package/.output/public/screenshots/pico-ios-light.png +0 -0
  7. package/.output/public/screenshots/pico-macos-dark.jpg +0 -0
  8. package/.output/public/screenshots/pico-macos-light.jpg +0 -0
  9. package/.output/server/_chunks/ssr-renderer.mjs +3 -3
  10. package/.output/server/_libs/crossws.mjs +2929 -0
  11. package/.output/server/_libs/h3+rou3+srvx.mjs +7 -1
  12. package/.output/server/_libs/unctx.mjs +1 -1
  13. package/.output/server/_routes/api/terminal/[id]/ws.mjs +8874 -0
  14. package/.output/server/_ssr/{router-l__gf5Y7.mjs → router-CN4JMm2A.mjs} +1074 -316
  15. package/.output/server/_ssr/{routes-DySm_W8N.mjs → routes-BsQ26pbY.mjs} +738 -189
  16. package/.output/server/_ssr/{routes-CuWg5WTI.mjs → routes-DbjuMLFI.mjs} +19 -10
  17. package/.output/server/_ssr/ssr.mjs +3 -3
  18. package/.output/server/_ssr/start-jIczsXlR.mjs +4 -0
  19. package/.output/server/{_tanstack-start-manifest_v-CVoKKKc_.mjs → _tanstack-start-manifest_v-Bs4g_j1_.mjs} +7 -4
  20. package/.output/server/index.mjs +621 -542
  21. package/.output/server/node_modules/node-pty/build/Makefile +2 -2
  22. package/.output/server/node_modules/node-pty/build/pty.target.mk +14 -14
  23. package/README.md +68 -6
  24. package/bin/pico.mjs +103 -1
  25. package/package.json +4 -4
  26. package/public/screenshots/pico-ios-dark.png +0 -0
  27. package/public/screenshots/pico-ios-light.png +0 -0
  28. package/public/screenshots/pico-macos-dark.jpg +0 -0
  29. package/public/screenshots/pico-macos-light.jpg +0 -0
  30. package/.output/public/assets/routes-2LboeUx4.js +0 -2194
  31. package/.output/public/assets/styles-B1ITYh-r.css +0 -2
  32. package/.output/server/_ssr/start-Captdpmb.mjs +0 -4
@@ -2,7 +2,7 @@ import { i as __toESM } from "../_runtime.mjs";
2
2
  import { $ as require_react, A as TabsTab, B as Input, C as MenuPositioner, D as MenuGroupLabel, E as MenuItem, F as DialogPopup, H as useRender, I as DialogDescription$1, L as DialogClose$1, M as DialogTitle$1, N as DialogRoot, O as MenuGroup, P as DialogPortal$1, Q as require_jsx_runtime, R as DialogBackdrop, S as MenuRoot, T as MenuPopup, V as Button, Y as mergeProps, _ as AccordionItem$1, a as ContextMenuRoot, b as MenuTrigger, c as CollapsibleRoot, d as PopoverPortal, f as PopoverTrigger$1, g as AccordionHeader, h as AccordionTrigger$1, i as ContextMenuTrigger$1, j as TabsRoot, k as TabsList$1, l as PopoverPopup, m as AccordionPanel, n as ToggleGroup, o as CollapsiblePanel, p as PopoverRoot, r as Toggle, s as CollapsibleTrigger$1, t as AlertDialogRoot, u as PopoverPositioner, v as AccordionRoot, w as MenuPortal, x as MenuSubmenuRoot, y as MenuSubmenuTrigger, z as Separator } from "../_libs/@base-ui/react+[...].mjs";
3
3
  import { t as cva } from "../_libs/class-variance-authority+clsx.mjs";
4
4
  import { A as Folder, B as ChevronsUpDown, C as ListStart, D as GitPullRequestArrow, E as ImagePlus, F as EllipsisVertical, G as Check, I as Download, J as ArrowUpRight, K as ArrowUp, L as Copy, M as FileDiff, N as ExternalLink, O as GitBranch, P as Ellipsis, R as Command, S as Maximize2, U as ChevronRight, V as ChevronsDownUp, W as ChevronDown, X as ArrowLeft, Z as ArrowDown, _ as PanelLeftClose, a as TriangleAlert, b as Minus, c as Square, d as Settings2, f as Search, g as PanelLeftOpen, h as PanelLeft, i as Undo2, j as FolderPlus, k as GitCommitHorizontal, l as SquareTerminal, m as PanelRight, n as WandSparkles, o as Trash2, p as Plus, q as ArrowUpToLine, r as Upload, s as Tag, t as X, u as SquarePen, w as ListEnd, x as Minimize2, y as OctagonX } from "../_libs/lucide-react.mjs";
5
- import { $ as normalizeSessionSelectionKeys, A as THEME_STORAGE_KEY, At as themeFamilyDescription, B as buildItemsFromSync, C as SESSION_DONE_SOUND_ENABLED_STORAGE_KEY, Ct as rememberStoredPromptDraft, D as TERMINAL_PANEL_OPEN_STORAGE_KEY, Dt as sameContextUsage, E as TERMINAL_PANEL_HEIGHT_STORAGE_KEY, Et as safeLocalStorageSetItem, F as TooltipTrigger, Ft as thinkingSummaryText, G as exploreShellCommandNameFromTool, I as VIEWER_CONTEXT_STORAGE_KEY, It as toolArgsAreIncompleteJsonObject, L as appliedThemeClass, Lt as toolCategoryFromTool, M as Tooltip$1, Mt as themeFamilyKeywords, N as TooltipContent, Nt as themeFamilyLabel, O as THEME_COLOR_MODE_STORAGE_KEY, Ot as sessionListEntryKey, Pt as themeFamilySupportsColorMode, Q as normalizePromptImage, R as appliedThemeClassColorMode, Rt as toolCouldBecomeExploreFromTool, S as SESSION_DONE_DESKTOP_NOTIFICATIONS_ENABLED_STORAGE_KEY, St as readStoredThemeColorMode, T as Spinner, Tt as safeLocalStorageGetItem, U as createContextId, V as cn, W as createInitialSessionState, X as getSessionTitle, Y as flattenTree, _ as RIGHT_SIDEBAR_HISTORY_HEIGHT_STORAGE_KEY, _t as readStoredSessionDoneSoundEnabled, a as COLLAPSED_DIRECTORIES_STORAGE_KEY, at as promptDraftKeyMatchesOwner, b as RIGHT_SIDEBAR_WIDTHS_STORAGE_KEY, bt as readStoredTerminalTabs, c as Kbd, ct as readStoredCenterMessages, d as PICO_DIFF_SHIKI_THEMES, dt as readStoredHideToolBlocks, et as normalizeStoredDirectoryList, f as PICO_SHIKI_VARIABLE_DEFAULTS, ft as readStoredPinnedSessionKeys, g as RIGHT_SIDEBAR_FILE_TREE_WIDTH_STORAGE_KEY, gt as readStoredSessionDoneDesktopNotificationsEnabled, h as RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY, ht as readStoredRightSidebarOpen, i as CENTER_MESSAGES_STORAGE_KEY, it as promptDraftKey, j as TitleTooltip, jt as themeFamilyFixedMode, k as THEME_FAMILIES, kt as themeColorModeLabel, l as KeyboardShortcutContent, lt as readStoredCollapsedDirectories, m as RECENT_DIRECTORIES_STORAGE_KEY, mt as readStoredRecentDirectories, n as APPLIED_THEME_STORAGE_KEY, nt as normalizeThemeFamily, o as DRAFT_DIRECTORY_STORAGE_KEY, ot as rawShellCommandTextFromTool, p as PINNED_SESSIONS_STORAGE_KEY, pt as readStoredPromptDraft, r as AUTO_SCROLL_ENABLED_STORAGE_KEY, rt as previewUrlForImage, s as HIDE_TOOL_BLOCKS_STORAGE_KEY, st as readStoredAutoScrollEnabled, tt as normalizeThemeColorMode, ut as readStoredDraftDirectory, v as RIGHT_SIDEBAR_HISTORY_TAB_STORAGE_KEY, vt as readStoredSidebarDirectories, w as SIDEBAR_DIRECTORIES_STORAGE_KEY, wt as rememberStoredTerminalTabs, x as Route, xt as readStoredTheme, y as RIGHT_SIDEBAR_OPEN_STORAGE_KEY, yt as readStoredTerminalPanelOpen } from "./routes-CuWg5WTI.mjs";
5
+ import { $ as normalizeSessionSelectionKeys, A as THEME_STORAGE_KEY, At as themeFamilyDescription, B as buildItemsFromSync, C as SESSION_DONE_SOUND_ENABLED_STORAGE_KEY, Ct as rememberStoredPromptDraft, D as TERMINAL_PANEL_OPEN_STORAGE_KEY, Dt as sameContextUsage, E as TERMINAL_PANEL_HEIGHT_STORAGE_KEY, Et as safeLocalStorageSetItem, F as TooltipTrigger, Ft as thinkingSummaryText, G as exploreShellCommandNameFromTool, I as VIEWER_CONTEXT_STORAGE_KEY, It as toolArgsAreIncompleteJsonObject, L as appliedThemeClass, Lt as toolCategoryFromTool, M as Tooltip$1, Mt as themeFamilyKeywords, N as TooltipContent, Nt as themeFamilyLabel, O as THEME_COLOR_MODE_STORAGE_KEY, Ot as sessionListEntryKey, Pt as themeFamilySupportsColorMode, Q as normalizePromptImage, R as appliedThemeClassColorMode, Rt as toolCouldBecomeExploreFromTool, S as SESSION_DONE_DESKTOP_NOTIFICATIONS_ENABLED_STORAGE_KEY, St as readStoredThemeColorMode, T as Spinner, Tt as safeLocalStorageGetItem, U as createContextId, V as cn, W as createInitialSessionState, X as getSessionTitle, Y as flattenTree, _ as RIGHT_SIDEBAR_HISTORY_HEIGHT_STORAGE_KEY, _t as readStoredSessionDoneSoundEnabled, a as COLLAPSED_DIRECTORIES_STORAGE_KEY, at as promptDraftKeyMatchesOwner, b as RIGHT_SIDEBAR_WIDTHS_STORAGE_KEY, bt as readStoredTerminalTabs, c as Kbd, ct as readStoredCenterMessages, d as PICO_DIFF_SHIKI_THEMES, dt as readStoredHideToolBlocks, et as normalizeStoredDirectoryList, f as PICO_SHIKI_VARIABLE_DEFAULTS, ft as readStoredPinnedSessionKeys, g as RIGHT_SIDEBAR_FILE_TREE_WIDTH_STORAGE_KEY, gt as readStoredSessionDoneDesktopNotificationsEnabled, h as RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY, ht as readStoredRightSidebarOpen, i as CENTER_MESSAGES_STORAGE_KEY, it as promptDraftKey, j as TitleTooltip, jt as themeFamilyFixedMode, k as THEME_FAMILIES, kt as themeColorModeLabel, l as KeyboardShortcutContent, lt as readStoredCollapsedDirectories, m as RECENT_DIRECTORIES_STORAGE_KEY, mt as readStoredRecentDirectories, n as APPLIED_THEME_STORAGE_KEY, nt as normalizeThemeFamily, o as DRAFT_DIRECTORY_STORAGE_KEY, ot as rawShellCommandTextFromTool, p as PINNED_SESSIONS_STORAGE_KEY, pt as readStoredPromptDraft, r as AUTO_SCROLL_ENABLED_STORAGE_KEY, rt as previewUrlForImage, s as HIDE_TOOL_BLOCKS_STORAGE_KEY, st as readStoredAutoScrollEnabled, tt as normalizeThemeColorMode, ut as readStoredDraftDirectory, v as RIGHT_SIDEBAR_HISTORY_TAB_STORAGE_KEY, vt as readStoredSidebarDirectories, w as SIDEBAR_DIRECTORIES_STORAGE_KEY, wt as rememberStoredTerminalTabs, x as Route, xt as readStoredTheme, y as RIGHT_SIDEBAR_OPEN_STORAGE_KEY, yt as readStoredTerminalPanelOpen } from "./routes-DbjuMLFI.mjs";
6
6
  import { i as useQuery, n as useIsMutating, o as useQueryClient, r as useIsFetching, t as useMutation } from "../_libs/tanstack__react-query.mjs";
7
7
  import { t as useHotkeys } from "../_libs/tanstack__react-hotkeys.mjs";
8
8
  import { a as matchesKeyboardEvent, i as batch, o as formatForDisplay, r as createStore } from "../_libs/@tanstack/hotkeys+[...].mjs";
@@ -22,7 +22,7 @@ import { t as remarkGfm } from "../_libs/remark-gfm.mjs";
22
22
  import { a as getBuiltInSpriteSheet, i as getBuiltInFileIconColor, n as FileTree, r as createFileTreeIconResolver, t as useFileTree } from "../_libs/@pierre/trees+[...].mjs";
23
23
  import { a as closestCenter, c as useDraggable, d as useSensors, f as CSS$1, i as PointerSensor, n as DragOverlay, t as DndContext, u as useSensor } from "../_libs/@dnd-kit/core+[...].mjs";
24
24
  import { a as verticalListSortingStrategy, i as useSortable, n as arrayMove, r as horizontalListSortingStrategy, t as SortableContext } from "../_libs/dnd-kit__sortable.mjs";
25
- //#region node_modules/.nitro/vite/services/ssr/assets/routes-DySm_W8N.js
25
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-BsQ26pbY.js
26
26
  var import_react = /* @__PURE__ */ __toESM(require_react());
27
27
  var import_jsx_runtime = require_jsx_runtime();
28
28
  var MOBILE_BREAKPOINT = 768;
@@ -1098,7 +1098,7 @@ function directoryDialogHasExactMatch(directoryPaths, normalizedQuery) {
1098
1098
  return normalizedPath === normalizedQuery || normalizedDisplayPath === normalizedQuery;
1099
1099
  });
1100
1100
  }
1101
- function splitDisplayPath$1(value) {
1101
+ function splitDisplayPath(value) {
1102
1102
  if (!value) return {
1103
1103
  leading: "",
1104
1104
  trailing: ""
@@ -1121,7 +1121,7 @@ function splitDisplayPath$1(value) {
1121
1121
  }
1122
1122
  function DirectoryPathLabel$1({ path, prefix }) {
1123
1123
  const displayPath = formatDirectoryDisplayPath(path);
1124
- const { leading, trailing } = splitDisplayPath$1(displayPath);
1124
+ const { leading, trailing } = splitDisplayPath(displayPath);
1125
1125
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1126
1126
  className: "flex min-w-0 items-center text-foreground",
1127
1127
  children: [
@@ -2429,7 +2429,7 @@ function formatShortcutLabel(hotkey) {
2429
2429
  function matchesShortcutEvent(event, hotkey) {
2430
2430
  return matchesKeyboardEvent(event, hotkey);
2431
2431
  }
2432
- function tildePath$1(value) {
2432
+ function tildePath(value) {
2433
2433
  return value.replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^\/home\/[^/]+(?=\/|$)/, "~");
2434
2434
  }
2435
2435
  function normalizeDirectories(directories) {
@@ -2647,6 +2647,8 @@ function AppShellSessionsDialog(props) {
2647
2647
  }
2648
2648
  function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, currentDirectory, knownDirectories, directorySessionsByPath, sessionStatusByKey, activeSessionId, activeSessionPath, onSelectSession, onRenameSession, onDeleteSession, onError }) {
2649
2649
  const isMobile = useIsMobile();
2650
+ const [isGeneratingName, setIsGeneratingName] = import_react.useState(false);
2651
+ const [generatedNamePath, setGeneratedNamePath] = import_react.useState("");
2650
2652
  const [state, dispatch] = import_react.useReducer(sessionsDialogReducer, initialSessionsDialogState);
2651
2653
  const { query, scope, stage, selectedSessionKey, renameValue, localSessionNames, loadedSessionsByPath, loadingByPath } = state;
2652
2654
  const normalizedCurrentDirectory = currentDirectory.trim();
@@ -2675,6 +2677,7 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2675
2677
  })));
2676
2678
  const sortedSessionItems = flatSessions.toSorted((left, right) => sessionActivityTime(right.entry) - sessionActivityTime(left.entry));
2677
2679
  const selectedSession = selectedSessionKey ? flatSessions.find((session) => session.key === selectedSessionKey)?.entry : void 0;
2680
+ const canGenerateSelectedSessionName = Boolean(selectedSession?.path && !selectedSession.name?.trim() && generatedNamePath !== selectedSession.path);
2678
2681
  const loading = visibleGroups.some((group) => group.loading);
2679
2682
  const browseInputRef = import_react.useRef(null);
2680
2683
  const deletePanelRef = import_react.useRef(null);
@@ -2755,6 +2758,7 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2755
2758
  };
2756
2759
  const renameSelectedSession = () => {
2757
2760
  if (!selectedSession?.path) return;
2761
+ setGeneratedNamePath("");
2758
2762
  dispatch({
2759
2763
  type: "renameRequested",
2760
2764
  value: selectedSession.title
@@ -2771,6 +2775,28 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2771
2775
  name: nextName
2772
2776
  });
2773
2777
  };
2778
+ const generateSelectedSessionName = async () => {
2779
+ const targetPath = selectedSession?.path;
2780
+ if (!targetPath || isGeneratingName || !canGenerateSelectedSessionName) return;
2781
+ setIsGeneratingName(true);
2782
+ try {
2783
+ const response = await fetchJson(buildRequestUrl("/api/session/name", { contextId: viewerContextId }), {
2784
+ method: "POST",
2785
+ headers: { "content-type": "application/json" },
2786
+ body: JSON.stringify({ path: targetPath })
2787
+ });
2788
+ dispatch({
2789
+ type: "renameValueChanged",
2790
+ value: response.name
2791
+ });
2792
+ setGeneratedNamePath(targetPath);
2793
+ if (response.source === "heuristic" && response.reason) toast.info("Using heuristic session name", { description: response.reason });
2794
+ } catch (error) {
2795
+ onError?.(error);
2796
+ } finally {
2797
+ setIsGeneratingName(false);
2798
+ }
2799
+ };
2774
2800
  const deleteSelectedSession = () => {
2775
2801
  if (!selectedSession?.path) return;
2776
2802
  dispatch({ type: "deleteRequested" });
@@ -2855,7 +2881,7 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2855
2881
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CommandList, {
2856
2882
  className: "max-h-none min-h-0 flex-1 md:max-h-[min(70vh,32rem)]",
2857
2883
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandEmpty, { children: loading ? "Loading sessions…" : "No sessions found." }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CommandGroup, {
2858
- heading: `${scope === "all" ? "All sessions" : `Current directory: ${tildePath$1(currentDirectories[0] || "")}`}${loading ? " · loading" : ""}`,
2884
+ heading: `${scope === "all" ? "All sessions" : `Current directory: ${tildePath(currentDirectories[0] || "")}`}${loading ? " · loading" : ""}`,
2859
2885
  children: [loading && sortedSessionItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CommandItem, {
2860
2886
  value: "loading:sessions",
2861
2887
  disabled: true,
@@ -2891,7 +2917,7 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2891
2917
  children: entry.title
2892
2918
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2893
2919
  className: "line-clamp-1 text-xs text-muted-foreground",
2894
- children: tildePath$1(entry.cwd || directory)
2920
+ children: tildePath(entry.cwd || directory)
2895
2921
  })]
2896
2922
  }),
2897
2923
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -2941,25 +2967,37 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2941
2967
  children: [
2942
2968
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2943
2969
  className: "flex items-center gap-2 border-b border-border/70 px-3 py-2",
2944
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
2945
- type: "button",
2946
- variant: "ghost",
2947
- size: "icon-sm",
2948
- onClick: () => {
2949
- dispatch({ type: "browseRequested" });
2950
- },
2951
- "aria-label": "Back to sessions",
2952
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, {})
2953
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2954
- className: "min-w-0 flex-1",
2955
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2956
- className: "truncate text-sm font-medium",
2957
- children: "Rename session"
2958
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2959
- className: "truncate text-xs text-muted-foreground",
2960
- children: selectedSession?.title || "Selected session"
2961
- })]
2962
- })]
2970
+ children: [
2971
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
2972
+ type: "button",
2973
+ variant: "ghost",
2974
+ size: "icon-sm",
2975
+ onClick: () => {
2976
+ dispatch({ type: "browseRequested" });
2977
+ },
2978
+ "aria-label": "Back to sessions",
2979
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, {})
2980
+ }),
2981
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2982
+ className: "min-w-0 flex-1",
2983
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2984
+ className: "truncate text-sm font-medium",
2985
+ children: "Rename session"
2986
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2987
+ className: "truncate text-xs text-muted-foreground",
2988
+ children: selectedSession?.title || "Selected session"
2989
+ })]
2990
+ }),
2991
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
2992
+ type: "button",
2993
+ variant: "ghost",
2994
+ disabled: isGeneratingName || !canGenerateSelectedSessionName,
2995
+ onClick: () => {
2996
+ generateSelectedSessionName();
2997
+ },
2998
+ children: [isGeneratingName ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WandSparkles, {}), "Generate"]
2999
+ })
3000
+ ]
2963
3001
  }),
2964
3002
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2965
3003
  className: "flex min-h-0 flex-1 items-center p-3",
@@ -2978,6 +3016,12 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2978
3016
  dispatch({ type: "browseRequested" });
2979
3017
  return;
2980
3018
  }
3019
+ if (matchesShortcutEvent(event.nativeEvent, "Control+G")) {
3020
+ event.preventDefault();
3021
+ event.stopPropagation();
3022
+ generateSelectedSessionName();
3023
+ return;
3024
+ }
2981
3025
  if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
2982
3026
  event.preventDefault();
2983
3027
  event.stopPropagation();
@@ -2989,13 +3033,20 @@ function useAppShellSessionsDialogView({ open, onOpenChange, viewerContextId, cu
2989
3033
  }),
2990
3034
  isMobile ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2991
3035
  className: "hidden flex-wrap items-center gap-x-3 gap-y-1 border-t border-border/70 px-3 py-2 text-xs text-muted-foreground md:flex",
2992
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2993
- className: "inline-flex items-center gap-1",
2994
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: "Enter" }), " Save"]
2995
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2996
- className: "inline-flex items-center gap-1",
2997
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: "Esc" }), " Back"]
2998
- })]
3036
+ children: [
3037
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3038
+ className: "inline-flex items-center gap-1",
3039
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: formatShortcutLabel("Control+G") }), " Generate"]
3040
+ }),
3041
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3042
+ className: "inline-flex items-center gap-1",
3043
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: "Enter" }), " Save"]
3044
+ }),
3045
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3046
+ className: "inline-flex items-center gap-1",
3047
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd$1, { children: "Esc" }), " Back"]
3048
+ })
3049
+ ]
2999
3050
  })
3000
3051
  ]
3001
3052
  });
@@ -7578,7 +7629,7 @@ function GitCommitDialogController({ viewerContextId, cwd, openStateRef, ref })
7578
7629
  function FooterKbd({ children }) {
7579
7630
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children });
7580
7631
  }
7581
- function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueChange, onRenameSession }) {
7632
+ function RenameSessionDialog({ open, onOpenChange, renameValue, generatingName, canGenerateName, onRenameValueChange, onGenerateSessionName, onRenameSession }) {
7582
7633
  const isMobile = useIsMobile();
7583
7634
  const renameInputRef = import_react.useRef(null);
7584
7635
  import_react.useEffect(() => {
@@ -7595,13 +7646,22 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7595
7646
  className: "flex min-h-0 flex-1 flex-col",
7596
7647
  children: [
7597
7648
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
7598
- className: "border-b border-border/70 px-3 py-2",
7599
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7600
- className: "truncate text-sm font-medium",
7601
- children: "Rename session"
7602
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7603
- className: "truncate text-xs text-muted-foreground",
7604
- children: "Update the display name shown in the sidebar."
7649
+ className: "flex items-center gap-2 border-b border-border/70 px-3 py-2",
7650
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
7651
+ className: "min-w-0 flex-1",
7652
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7653
+ className: "truncate text-sm font-medium",
7654
+ children: "Rename session"
7655
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7656
+ className: "truncate text-xs text-muted-foreground",
7657
+ children: "Update the display name shown in the sidebar."
7658
+ })]
7659
+ }), isMobile ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
7660
+ type: "button",
7661
+ variant: "ghost",
7662
+ disabled: generatingName || !canGenerateName,
7663
+ onClick: onGenerateSessionName,
7664
+ children: [generatingName ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WandSparkles, {}), "Generate"]
7605
7665
  })]
7606
7666
  }),
7607
7667
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -7617,6 +7677,12 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7617
7677
  onOpenChange(false);
7618
7678
  return;
7619
7679
  }
7680
+ if (event.ctrlKey && event.key.toLowerCase() === "g") {
7681
+ event.preventDefault();
7682
+ event.stopPropagation();
7683
+ onGenerateSessionName();
7684
+ return;
7685
+ }
7620
7686
  if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
7621
7687
  event.preventDefault();
7622
7688
  event.stopPropagation();
@@ -7628,13 +7694,24 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7628
7694
  }),
7629
7695
  isMobile ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
7630
7696
  className: "hidden flex-wrap items-center gap-x-3 gap-y-1 border-t border-border/70 px-3 py-2 text-xs text-muted-foreground md:flex",
7631
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7632
- className: "inline-flex items-center gap-1",
7633
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Enter" }), " Save"]
7634
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7635
- className: "inline-flex items-center gap-1",
7636
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Esc" }), " Cancel"]
7637
- })]
7697
+ children: [
7698
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7699
+ className: "inline-flex items-center gap-1",
7700
+ children: [
7701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Ctrl" }),
7702
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "G" }),
7703
+ " Generate"
7704
+ ]
7705
+ }),
7706
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7707
+ className: "inline-flex items-center gap-1",
7708
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Enter" }), " Save"]
7709
+ }),
7710
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
7711
+ className: "inline-flex items-center gap-1",
7712
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FooterKbd, { children: "Esc" }), " Cancel"]
7713
+ })
7714
+ ]
7638
7715
  })
7639
7716
  ]
7640
7717
  });
@@ -7652,14 +7729,23 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7652
7729
  }),
7653
7730
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DrawerFooter, {
7654
7731
  className: "border-t border-border/70",
7655
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7656
- variant: "outline",
7657
- onClick: () => onOpenChange(false),
7658
- children: "Cancel"
7659
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7660
- onClick: onRenameSession,
7661
- children: "Save"
7662
- })]
7732
+ children: [
7733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
7734
+ variant: "outline",
7735
+ disabled: generatingName || !canGenerateName,
7736
+ onClick: onGenerateSessionName,
7737
+ children: [generatingName ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WandSparkles, {}), "Generate"]
7738
+ }),
7739
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7740
+ variant: "outline",
7741
+ onClick: () => onOpenChange(false),
7742
+ children: "Cancel"
7743
+ }),
7744
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7745
+ onClick: onRenameSession,
7746
+ children: "Save"
7747
+ })
7748
+ ]
7663
7749
  })
7664
7750
  ]
7665
7751
  })
@@ -7674,9 +7760,11 @@ function RenameSessionDialog({ open, onOpenChange, renameValue, onRenameValueCha
7674
7760
  children: body
7675
7761
  });
7676
7762
  }
7677
- function RenameSessionDialogController({ ref, openStateRef, onRenameSession }) {
7763
+ function RenameSessionDialogController({ ref, openStateRef, viewerContextId, onRenameSession }) {
7678
7764
  const [open, setOpen] = import_react.useState(false);
7679
7765
  const [renameValue, setRenameValue] = import_react.useState("");
7766
+ const [isGeneratingName, setIsGeneratingName] = import_react.useState(false);
7767
+ const [canGenerateName, setCanGenerateName] = import_react.useState(false);
7680
7768
  const targetPathRef = import_react.useRef("");
7681
7769
  const openRef = import_react.useRef(open);
7682
7770
  const setOpenState = (nextOpen) => {
@@ -7689,8 +7777,27 @@ function RenameSessionDialogController({ ref, openStateRef, onRenameSession }) {
7689
7777
  if (!nextPath) return;
7690
7778
  targetPathRef.current = nextPath;
7691
7779
  setRenameValue(target.title);
7780
+ setCanGenerateName(!target.hasGeneratedName);
7692
7781
  setOpenState(true);
7693
7782
  };
7783
+ const generateSessionName = async () => {
7784
+ if (!viewerContextId || !targetPathRef.current || isGeneratingName || !canGenerateName) return;
7785
+ setIsGeneratingName(true);
7786
+ try {
7787
+ const response = await fetchJson(buildRequestUrl("/api/session/name", { contextId: viewerContextId }), {
7788
+ method: "POST",
7789
+ headers: { "content-type": "application/json" },
7790
+ body: JSON.stringify({ path: targetPathRef.current })
7791
+ });
7792
+ setRenameValue(response.name);
7793
+ setCanGenerateName(false);
7794
+ if (response.source === "heuristic" && response.reason) toast.info("Using heuristic session name", { description: response.reason });
7795
+ } catch (error) {
7796
+ toast.error(error instanceof Error ? error.message : "Failed to generate session name");
7797
+ } finally {
7798
+ setIsGeneratingName(false);
7799
+ }
7800
+ };
7694
7801
  const submitRename = async () => {
7695
7802
  if (await onRenameSession(targetPathRef.current, renameValue) === false) return;
7696
7803
  setOpenState(false);
@@ -7701,7 +7808,8 @@ function RenameSessionDialogController({ ref, openStateRef, onRenameSession }) {
7701
7808
  if (!entry.path) return;
7702
7809
  openTarget({
7703
7810
  path: entry.path,
7704
- title: entry.title || ""
7811
+ title: entry.title || "",
7812
+ hasGeneratedName: Boolean(entry.name?.trim())
7705
7813
  });
7706
7814
  },
7707
7815
  close: () => {
@@ -7715,7 +7823,12 @@ function RenameSessionDialogController({ ref, openStateRef, onRenameSession }) {
7715
7823
  setOpenState(nextOpen);
7716
7824
  },
7717
7825
  renameValue,
7826
+ generatingName: isGeneratingName,
7827
+ canGenerateName: canGenerateName && Boolean(viewerContextId && targetPathRef.current),
7718
7828
  onRenameValueChange: setRenameValue,
7829
+ onGenerateSessionName: () => {
7830
+ generateSessionName();
7831
+ },
7719
7832
  onRenameSession: () => {
7720
7833
  submitRename();
7721
7834
  }
@@ -7819,19 +7932,22 @@ function DeleteSessionsDialog({ open, onOpenChange, title, description, targets,
7819
7932
  })
7820
7933
  ]
7821
7934
  });
7822
- if (isMobile) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Drawer$1, {
7935
+ if (isMobile) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
7823
7936
  open,
7824
7937
  onOpenChange,
7825
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DrawerContent, {
7826
- className: "max-h-[90svh] overflow-hidden",
7938
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
7939
+ className: "max-h-[90svh] overflow-hidden p-0",
7827
7940
  children: [
7828
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DrawerHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DrawerTitle, { children: title }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DrawerDescription, { children: description })] }),
7941
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, {
7942
+ className: "sr-only",
7943
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: title }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, { children: description })]
7944
+ }),
7829
7945
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
7830
- className: "flex min-h-0 flex-1 flex-col overflow-hidden px-4 pb-4",
7946
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden",
7831
7947
  children: body
7832
7948
  }),
7833
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DrawerFooter, {
7834
- className: "border-t border-border/70",
7949
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, {
7950
+ className: "flex-col-reverse gap-2 border-t border-border/70 p-3 sm:flex-row",
7835
7951
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
7836
7952
  variant: "outline",
7837
7953
  onClick: () => onOpenChange(false),
@@ -8370,10 +8486,11 @@ var AppShellMoveSessionDirectoryDialogHost = import_react.memo(function AppShell
8370
8486
  })
8371
8487
  });
8372
8488
  });
8373
- var AppShellRenameSessionDialogHost = import_react.memo(function AppShellRenameSessionDialogHost({ renameDialogRef, renameOpenRef, renameSessionPath }) {
8489
+ var AppShellRenameSessionDialogHost = import_react.memo(function AppShellRenameSessionDialogHost({ renameDialogRef, renameOpenRef, renameSessionPath, viewerContextId }) {
8374
8490
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RenameSessionDialogController, {
8375
8491
  ref: renameDialogRef,
8376
8492
  openStateRef: renameOpenRef,
8493
+ viewerContextId,
8377
8494
  onRenameSession: renameSessionPath
8378
8495
  });
8379
8496
  });
@@ -8515,7 +8632,8 @@ var AppShellFloatingControllers = import_react.memo(function AppShellFloatingCon
8515
8632
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellRenameSessionDialogHost, {
8516
8633
  renameDialogRef,
8517
8634
  renameOpenRef,
8518
- renameSessionPath
8635
+ renameSessionPath,
8636
+ viewerContextId
8519
8637
  }),
8520
8638
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellDeleteSessionsDialogHost, {
8521
8639
  deleteDialogRef,
@@ -8861,7 +8979,7 @@ function pendingMessageEditReducer(state, action) {
8861
8979
  };
8862
8980
  }
8863
8981
  }
8864
- var ComposerPendingMessages = import_react.memo(function ComposerPendingMessages({ currentPendingMessages, onEditPendingMessage, onRemovePendingMessage, onReorderPending }) {
8982
+ var ComposerPendingMessages = import_react.memo(function ComposerPendingMessages({ currentPendingMessages, canStartQueue, onEditPendingMessage, onRemovePendingMessage, onReorderPending, onStartPendingQueue }) {
8865
8983
  const [editState, dispatchEditState] = import_react.useReducer(pendingMessageEditReducer, EMPTY_PENDING_MESSAGE_EDIT_STATE);
8866
8984
  const [openSections, setOpenSections] = import_react.useState([]);
8867
8985
  const { editingPendingId, editText, editError } = editState;
@@ -8900,19 +9018,26 @@ var ComposerPendingMessages = import_react.memo(function ComposerPendingMessages
8900
9018
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionItem, {
8901
9019
  value: "pending-prompts",
8902
9020
  className: "border-0",
8903
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionTrigger, {
8904
- className: "min-h-10 items-center gap-3 px-3 py-2 hover:no-underline",
8905
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
8906
- className: "flex min-w-0 items-center gap-2 text-sm font-medium",
8907
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8908
- className: "truncate",
8909
- children: "Queue"
8910
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
8911
- variant: "outline",
8912
- className: "shrink-0",
8913
- children: currentPendingMessages.length
8914
- })]
8915
- })
9021
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
9022
+ className: "flex min-h-10 items-center gap-2 px-3 py-2",
9023
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionTrigger, {
9024
+ className: "min-h-0 flex-1 p-0 hover:no-underline",
9025
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
9026
+ className: "flex min-w-0 items-center gap-2 text-sm font-medium",
9027
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
9028
+ className: "truncate",
9029
+ children: "Queue"
9030
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
9031
+ variant: "outline",
9032
+ className: "shrink-0",
9033
+ children: currentPendingMessages.length
9034
+ })]
9035
+ })
9036
+ }), canStartQueue ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
9037
+ size: "sm",
9038
+ onClick: onStartPendingQueue,
9039
+ children: "Start"
9040
+ }) : null]
8916
9041
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionContent, {
8917
9042
  className: "p-0",
8918
9043
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -10713,7 +10838,7 @@ function assistantBlockKey(block) {
10713
10838
  case "text": return `text:${block.text}`;
10714
10839
  case "thinking": return `thinking:${block.text}`;
10715
10840
  case "tool": return `tool:${block.callId || block.name || "tool"}:${toolArgsKey(block.args)}`;
10716
- case "compaction": return `compaction:${block.tokensBefore}:${block.summary}`;
10841
+ case "compaction": return `compaction:${block.tokensBefore}:${block.estimatedTokensAfter ?? ""}:${block.summary}`;
10717
10842
  default: return "block";
10718
10843
  }
10719
10844
  }
@@ -11015,7 +11140,7 @@ function sameAssistantBlock(left, right) {
11015
11140
  return left.callId === right.callId && left.name === right.name && left.category === right.category && left.output === right.output && left.isError === right.isError && left.running === right.running && sameUnknownValue(left.args, right.args) && sameUnknownValue(left.details, right.details);
11016
11141
  case "compaction":
11017
11142
  if (right.type !== "compaction") return false;
11018
- return left.summary === right.summary && left.tokensBefore === right.tokensBefore;
11143
+ return left.summary === right.summary && left.tokensBefore === right.tokensBefore && left.estimatedTokensAfter === right.estimatedTokensAfter;
11019
11144
  default: return false;
11020
11145
  }
11021
11146
  }
@@ -11352,7 +11477,8 @@ function useAssistantToolGroupBodyBlocks(store, keys, enabled) {
11352
11477
  }
11353
11478
  function compactionTriggerText(block) {
11354
11479
  if (block.type !== "compaction") return "Compaction";
11355
- return block.tokensBefore > 0 ? `Compaction: Compacted from ${block.tokensBefore.toLocaleString()} tokens` : "Compaction";
11480
+ if (block.tokensBefore <= 0) return "Compaction";
11481
+ return block.estimatedTokensAfter && block.estimatedTokensAfter > 0 ? `Compaction: ${block.tokensBefore.toLocaleString()} → ~${block.estimatedTokensAfter.toLocaleString()} tokens` : `Compaction: Compacted from ${block.tokensBefore.toLocaleString()} tokens`;
11356
11482
  }
11357
11483
  var ANSI_16_COLORS = [
11358
11484
  "#3f3f46",
@@ -12720,7 +12846,7 @@ function getClipboardImageFiles(data) {
12720
12846
  if (itemFiles.length > 0) return itemFiles;
12721
12847
  return Array.from(data.files).filter((file) => file.type.startsWith("image/"));
12722
12848
  }
12723
- function ComposerPanel({ activeSessionId, currentPendingMessages, composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, centerMessages, contextUsageStore, displaySettingsStore, sessionStore, isSubmitting, isStreaming, awaitingFirstTurn, disabled = false, flush = false, topContent, viewerContextId, fileInputRef, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onEditPendingMessage, onRemovePendingMessage, onReorderPending, onRunBuiltinSlashCommand, onSelectModel, onSelectThinkingLevel, requestPathCompletions, requestFileCompletions, ref }) {
12849
+ function ComposerPanel({ activeSessionId, currentPendingMessages, composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, canStartPendingQueue, centerMessages, contextUsageStore, displaySettingsStore, sessionStore, isSubmitting, isCompacting, isStreaming, awaitingFirstTurn, disabled = false, flush = false, topContent, viewerContextId, fileInputRef, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onEditPendingMessage, onRemovePendingMessage, onReorderPending, onStartPendingQueue, onRunBuiltinSlashCommand, onSelectModel, onSelectThinkingLevel, requestPathCompletions, requestFileCompletions, ref }) {
12724
12850
  const promptRef = import_react.useRef(null);
12725
12851
  const [pickerState, dispatchPickerState] = import_react.useReducer(composerPickerReducer, INITIAL_COMPOSER_PICKER_STATE);
12726
12852
  const { modelPickerOpen, thinkingPickerOpen, modelQuery } = pickerState;
@@ -12747,9 +12873,11 @@ function ComposerPanel({ activeSessionId, currentPendingMessages, composerDiffLi
12747
12873
  topContent,
12748
12874
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComposerPendingMessages, {
12749
12875
  currentPendingMessages,
12876
+ canStartQueue: canStartPendingQueue,
12750
12877
  onEditPendingMessage,
12751
12878
  onRemovePendingMessage,
12752
- onReorderPending
12879
+ onReorderPending,
12880
+ onStartPendingQueue
12753
12881
  }),
12754
12882
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12755
12883
  className: "overflow-visible rounded-[18px] border bg-card",
@@ -12760,6 +12888,7 @@ function ComposerPanel({ activeSessionId, currentPendingMessages, composerDiffLi
12760
12888
  composerSkill,
12761
12889
  composerSyncNonce,
12762
12890
  isSubmitting,
12891
+ isCompacting,
12763
12892
  isStreaming,
12764
12893
  awaitingFirstTurn,
12765
12894
  disabled,
@@ -12843,7 +12972,7 @@ var ComposerAttachments = import_react.memo(function ComposerAttachments({ image
12843
12972
  }, promptImageKey(image)))
12844
12973
  });
12845
12974
  });
12846
- var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, isSubmitting, isStreaming, awaitingFirstTurn, disabled = false, fileInputRef, promptRef, displaySettingsStore, sessionStore, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onRunBuiltinSlashCommand, requestPathCompletions, requestFileCompletions }) {
12975
+ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, isSubmitting, isCompacting, isStreaming, awaitingFirstTurn, disabled = false, fileInputRef, promptRef, displaySettingsStore, sessionStore, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onRunBuiltinSlashCommand, requestPathCompletions, requestFileCompletions }) {
12847
12976
  const slashCommands = useComposerSlashCommands({
12848
12977
  displaySettingsStore,
12849
12978
  sessionStore
@@ -12931,7 +13060,7 @@ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ com
12931
13060
  promptRef
12932
13061
  ]);
12933
13062
  const hasSubmittableContent = hasDraftText || composerDiffLineComments.length > 0 || composerImages.length > 0;
12934
- const responseInFlight = isStreaming || awaitingFirstTurn || isSubmitting;
13063
+ const responseInFlight = isStreaming || isCompacting || awaitingFirstTurn || isSubmitting;
12935
13064
  const acceptFollowUps = responseInFlight;
12936
13065
  const setAssistPointerSelectionSuppressedValue = (next) => {
12937
13066
  assistPointerSelectionSuppressedRef.current = next;
@@ -13564,6 +13693,10 @@ var AppShellComposerController = import_react.memo(function AppShellComposerCont
13564
13693
  if (actionsRef.current.reorderPendingDraftFollowUp(pendingId, direction)) return;
13565
13694
  actionsRef.current.reorderPending(pendingId, direction);
13566
13695
  });
13696
+ const onStartPendingQueue = useStableEvent(() => {
13697
+ if (snapshotRef.current.disabled) return;
13698
+ actionsRef.current.startPendingQueue();
13699
+ });
13567
13700
  const onRunBuiltinSlashCommand = useStableEvent((name, args) => {
13568
13701
  if (snapshotRef.current.disabled) return;
13569
13702
  actionsRef.current.runBuiltinSlashCommand(name, args);
@@ -13614,11 +13747,13 @@ var AppShellComposerController = import_react.memo(function AppShellComposerCont
13614
13747
  composerText: snapshot.composerText,
13615
13748
  composerSkill: snapshot.composerSkill,
13616
13749
  composerSyncNonce: snapshot.composerSyncNonce,
13750
+ canStartPendingQueue: snapshot.canStartPendingQueue,
13617
13751
  centerMessages,
13618
13752
  contextUsageStore,
13619
13753
  displaySettingsStore,
13620
13754
  sessionStore,
13621
13755
  isSubmitting: snapshot.isSubmitting,
13756
+ isCompacting: snapshot.isCompacting,
13622
13757
  isStreaming: snapshot.isStreaming,
13623
13758
  awaitingFirstTurn: snapshot.awaitingFirstTurn,
13624
13759
  disabled: snapshot.disabled,
@@ -13635,6 +13770,7 @@ var AppShellComposerController = import_react.memo(function AppShellComposerCont
13635
13770
  onEditPendingMessage,
13636
13771
  onRemovePendingMessage,
13637
13772
  onReorderPending,
13773
+ onStartPendingQueue,
13638
13774
  onRunBuiltinSlashCommand,
13639
13775
  onSelectModel,
13640
13776
  onSelectThinkingLevel,
@@ -20472,6 +20608,11 @@ function errorMessage(error) {
20472
20608
  if (typeof error === "string" && error.trim()) return error;
20473
20609
  return "Unknown terminal error";
20474
20610
  }
20611
+ function buildTerminalWebSocketUrl(path, options) {
20612
+ const url = new URL(buildRequestUrl(path, options));
20613
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
20614
+ return url.toString();
20615
+ }
20475
20616
  function exitedStatus(event) {
20476
20617
  return typeof event.signal === "number" && event.signal > 0 ? {
20477
20618
  type: "exited",
@@ -20556,14 +20697,22 @@ function TerminalTabPane({ active, cwd, onStatusChange, onTerminalIdChange, sele
20556
20697
  const terminalElement = terminalElementRef.current;
20557
20698
  if (!terminalElement) return;
20558
20699
  let disposed = false;
20559
- let eventSource = null;
20700
+ let websocket = null;
20560
20701
  let inputDisposable = null;
20561
20702
  let fitAddon = null;
20562
20703
  let resizeObserver = null;
20563
20704
  let inputFlushTimer = null;
20705
+ let pingTimer = null;
20706
+ let reconnectTimer = null;
20564
20707
  let resizeFrame = null;
20565
20708
  let inputBuffer = "";
20566
20709
  let lastResizeKey = "";
20710
+ let lastOutputSeq = 0;
20711
+ let nextInputSeq = 1;
20712
+ let reconnectAttempt = 0;
20713
+ let terminalExited = false;
20714
+ let terminalTransportReady = false;
20715
+ const pendingInputs = /* @__PURE__ */ new Map();
20567
20716
  const requestOptions = sessionId ? {
20568
20717
  contextId: viewerContextId,
20569
20718
  sessionId
@@ -20580,18 +20729,36 @@ function TerminalTabPane({ active, cwd, onStatusChange, onTerminalIdChange, sele
20580
20729
  method: "POST"
20581
20730
  });
20582
20731
  };
20732
+ const sendTerminalMessage = (message) => {
20733
+ if (!websocket || websocket.readyState !== WebSocket.OPEN) return false;
20734
+ try {
20735
+ websocket.send(JSON.stringify(message));
20736
+ return true;
20737
+ } catch (error) {
20738
+ publishStatus({
20739
+ type: "error",
20740
+ message: errorMessage(error)
20741
+ });
20742
+ return false;
20743
+ }
20744
+ };
20745
+ const sendPendingInputs = () => {
20746
+ if (!terminalTransportReady) return;
20747
+ for (const [inputSeq, data] of [...pendingInputs.entries()].sort(([left], [right]) => left - right)) if (!sendTerminalMessage({
20748
+ type: "input",
20749
+ data,
20750
+ inputSeq
20751
+ })) return;
20752
+ };
20583
20753
  const flushInput = () => {
20584
20754
  inputFlushTimer = null;
20585
- const id = terminalIdRef.current;
20586
- if (!id || !inputBuffer) return;
20755
+ if (!terminalIdRef.current || !inputBuffer || !terminalTransportReady) return;
20587
20756
  const data = inputBuffer;
20757
+ const inputSeq = nextInputSeq;
20758
+ nextInputSeq += 1;
20588
20759
  inputBuffer = "";
20589
- postJson(`/api/terminal/${encodeURIComponent(id)}/input`, { data }).catch((error) => {
20590
- if (!disposed) publishStatus({
20591
- type: "error",
20592
- message: errorMessage(error)
20593
- });
20594
- });
20760
+ pendingInputs.set(inputSeq, data);
20761
+ sendPendingInputs();
20595
20762
  };
20596
20763
  const scheduleInputFlush = () => {
20597
20764
  if (inputFlushTimer !== null) return;
@@ -20605,15 +20772,15 @@ function TerminalTabPane({ active, cwd, onStatusChange, onTerminalIdChange, sele
20605
20772
  } catch {
20606
20773
  return;
20607
20774
  }
20608
- const id = terminalIdRef.current;
20609
- if (!id) return;
20775
+ if (!terminalIdRef.current || !terminalTransportReady) return;
20610
20776
  const resizeKey = `${terminal.cols}x${terminal.rows}`;
20611
20777
  if (resizeKey === lastResizeKey) return;
20612
- lastResizeKey = resizeKey;
20613
- postJson(`/api/terminal/${encodeURIComponent(id)}/resize`, {
20778
+ if (!sendTerminalMessage({
20779
+ type: "resize",
20614
20780
  cols: terminal.cols,
20615
20781
  rows: terminal.rows
20616
- }).catch(() => {});
20782
+ })) return;
20783
+ lastResizeKey = resizeKey;
20617
20784
  };
20618
20785
  const scheduleFitAndResize = () => {
20619
20786
  if (resizeFrame !== null) return;
@@ -20623,6 +20790,116 @@ function TerminalTabPane({ active, cwd, onStatusChange, onTerminalIdChange, sele
20623
20790
  });
20624
20791
  };
20625
20792
  fitAndResizeRef.current = scheduleFitAndResize;
20793
+ const clearPingTimer = () => {
20794
+ if (pingTimer === null) return;
20795
+ window.clearInterval(pingTimer);
20796
+ pingTimer = null;
20797
+ };
20798
+ const startPingTimer = () => {
20799
+ clearPingTimer();
20800
+ pingTimer = window.setInterval(() => {
20801
+ sendTerminalMessage({ type: "ping" });
20802
+ }, 2e4);
20803
+ };
20804
+ const scheduleWebSocketReconnect = () => {
20805
+ if (disposed || terminalExited || reconnectTimer !== null) return;
20806
+ terminalTransportReady = false;
20807
+ clearPingTimer();
20808
+ reconnectAttempt += 1;
20809
+ const delay = Math.min(1e4, 500 * 2 ** (reconnectAttempt - 1));
20810
+ reconnectTimer = window.setTimeout(() => {
20811
+ reconnectTimer = null;
20812
+ const id = terminalIdRef.current;
20813
+ if (!id || disposed || terminalExited) return;
20814
+ publishStatus({ type: "connecting" });
20815
+ connectTerminalWebSocket(id);
20816
+ }, delay);
20817
+ };
20818
+ const handleTerminalEvent = (event) => {
20819
+ const terminal = terminalRef.current;
20820
+ if (!terminal) return;
20821
+ if (event.type === "output") {
20822
+ if (typeof event.seq === "number") {
20823
+ if (event.seq <= lastOutputSeq) return;
20824
+ lastOutputSeq = event.seq;
20825
+ }
20826
+ terminal.write(event.data);
20827
+ return;
20828
+ }
20829
+ if (event.type === "reset") {
20830
+ lastOutputSeq = Math.max(0, event.firstSeq - 1);
20831
+ terminal.clear();
20832
+ terminal.writeln("Terminal output replay gap; showing retained output.");
20833
+ return;
20834
+ }
20835
+ if (event.type === "ready") {
20836
+ terminalTransportReady = true;
20837
+ reconnectAttempt = 0;
20838
+ nextInputSeq = Math.max(nextInputSeq, event.nextInputSeq ?? 1);
20839
+ publishStatus({
20840
+ type: "connected",
20841
+ shell: event.shell
20842
+ });
20843
+ startPingTimer();
20844
+ sendPendingInputs();
20845
+ flushInput();
20846
+ lastResizeKey = "";
20847
+ scheduleFitAndResize();
20848
+ return;
20849
+ }
20850
+ if (event.type === "input_ack") {
20851
+ pendingInputs.delete(event.inputSeq);
20852
+ return;
20853
+ }
20854
+ if (event.type === "pong") return;
20855
+ if (event.type === "exit") {
20856
+ terminalExited = true;
20857
+ terminalTransportReady = false;
20858
+ clearPingTimer();
20859
+ publishStatus(exitedStatus(event));
20860
+ return;
20861
+ }
20862
+ if (event.type === "error") publishStatus({
20863
+ type: "error",
20864
+ message: event.error
20865
+ });
20866
+ };
20867
+ function connectTerminalWebSocket(id) {
20868
+ if (websocket) {
20869
+ websocket.onclose = null;
20870
+ websocket.onerror = null;
20871
+ websocket.onmessage = null;
20872
+ websocket.close();
20873
+ }
20874
+ terminalTransportReady = false;
20875
+ const nextWebSocket = new WebSocket(buildTerminalWebSocketUrl(`/api/terminal/${encodeURIComponent(id)}/ws`, {
20876
+ ...requestOptions,
20877
+ searchParams: { lastSeq: lastOutputSeq || void 0 }
20878
+ }));
20879
+ websocket = nextWebSocket;
20880
+ nextWebSocket.onmessage = (message) => {
20881
+ if (disposed || websocket !== nextWebSocket) return;
20882
+ try {
20883
+ handleTerminalEvent(JSON.parse(message.data));
20884
+ } catch (error) {
20885
+ publishStatus({
20886
+ type: "error",
20887
+ message: errorMessage(error)
20888
+ });
20889
+ }
20890
+ };
20891
+ nextWebSocket.onclose = () => {
20892
+ if (websocket !== nextWebSocket) return;
20893
+ websocket = null;
20894
+ terminalTransportReady = false;
20895
+ scheduleWebSocketReconnect();
20896
+ };
20897
+ nextWebSocket.onerror = () => {
20898
+ if (websocket !== nextWebSocket) return;
20899
+ terminalTransportReady = false;
20900
+ nextWebSocket.close();
20901
+ };
20902
+ }
20626
20903
  const startTerminal = async () => {
20627
20904
  try {
20628
20905
  publishStatus({ type: "connecting" });
@@ -20658,45 +20935,7 @@ function TerminalTabPane({ active, cwd, onStatusChange, onTerminalIdChange, sele
20658
20935
  if (disposed) return;
20659
20936
  terminalIdRef.current = terminalResponse.id;
20660
20937
  onTerminalIdChangeRef.current(tabId, terminalResponse.id);
20661
- publishStatus({
20662
- type: "connected",
20663
- shell: terminalResponse.shell
20664
- });
20665
- eventSource = new EventSource(buildRequestUrl(`/api/terminal/${encodeURIComponent(terminalResponse.id)}/events`, requestOptions));
20666
- eventSource.onmessage = (message) => {
20667
- if (disposed) return;
20668
- const event = JSON.parse(message.data);
20669
- if (event.type === "output") {
20670
- terminal.write(event.data);
20671
- return;
20672
- }
20673
- if (event.type === "ready") {
20674
- publishStatus({
20675
- type: "connected",
20676
- shell: event.shell
20677
- });
20678
- return;
20679
- }
20680
- if (event.type === "exit") {
20681
- publishStatus(exitedStatus(event));
20682
- return;
20683
- }
20684
- if (event.type === "error") publishStatus({
20685
- type: "error",
20686
- message: event.error
20687
- });
20688
- };
20689
- eventSource.onerror = () => {
20690
- if (!disposed && terminalIdRef.current) setStatus((current) => {
20691
- if (current.type === "connected") return current;
20692
- const nextStatus = {
20693
- type: "error",
20694
- message: "Terminal connection interrupted."
20695
- };
20696
- onStatusChangeRef.current(tabId, nextStatus);
20697
- return nextStatus;
20698
- });
20699
- };
20938
+ connectTerminalWebSocket(terminalResponse.id);
20700
20939
  resizeObserver = new ResizeObserver(scheduleFitAndResize);
20701
20940
  resizeObserver.observe(terminalElement);
20702
20941
  scheduleFitAndResize();
@@ -20715,9 +20954,16 @@ function TerminalTabPane({ active, cwd, onStatusChange, onTerminalIdChange, sele
20715
20954
  disposed = true;
20716
20955
  if (inputFlushTimer !== null) window.clearTimeout(inputFlushTimer);
20717
20956
  if (resizeFrame !== null) window.cancelAnimationFrame(resizeFrame);
20957
+ if (reconnectTimer !== null) window.clearTimeout(reconnectTimer);
20958
+ clearPingTimer();
20718
20959
  flushInput();
20719
20960
  resizeObserver?.disconnect();
20720
- eventSource?.close();
20961
+ if (websocket) {
20962
+ websocket.onclose = null;
20963
+ websocket.onerror = null;
20964
+ websocket.onmessage = null;
20965
+ websocket.close();
20966
+ }
20721
20967
  inputDisposable?.dispose();
20722
20968
  terminalRef.current?.dispose();
20723
20969
  terminalRef.current = null;
@@ -22021,45 +22267,10 @@ function useDelayedTrue(value, delayMs) {
22021
22267
  }, [delayMs, value]);
22022
22268
  return delayedValue;
22023
22269
  }
22024
- function tildePath(value) {
22025
- return value.replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^\/home\/[^/]+(?=\/|$)/, "~");
22026
- }
22027
- function splitDisplayPath(value) {
22028
- if (!value) return {
22029
- leading: "",
22030
- trailing: ""
22031
- };
22032
- const trimmed = value.replace(/[\\/]+$/, "");
22033
- if (!trimmed) return {
22034
- leading: "",
22035
- trailing: value
22036
- };
22037
- const suffix = value.slice(trimmed.length);
22038
- const separatorIndex = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
22039
- if (separatorIndex < 0 || separatorIndex === trimmed.length - 1) return {
22040
- leading: "",
22041
- trailing: `${trimmed}${suffix}`
22042
- };
22043
- return {
22044
- leading: trimmed.slice(0, separatorIndex + 1),
22045
- trailing: `${trimmed.slice(separatorIndex + 1)}${suffix}`
22046
- };
22047
- }
22048
22270
  function DirectoryPathLabel({ path }) {
22049
- const displayPath = tildePath(path);
22050
- const { leading, trailing } = splitDisplayPath(displayPath);
22051
22271
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
22052
- className: "block min-w-0 text-sm text-sidebar-foreground",
22053
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
22054
- className: "flex min-w-0 items-center",
22055
- children: [leading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
22056
- className: "truncate text-sidebar-foreground/60",
22057
- children: leading
22058
- }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
22059
- className: "shrink-0 font-medium",
22060
- children: trailing || displayPath
22061
- })]
22062
- })
22272
+ className: "block min-w-0 truncate text-sm font-medium text-sidebar-foreground",
22273
+ children: formatFolderName$1(path) || path
22063
22274
  });
22064
22275
  }
22065
22276
  var EMPTY_DIRECTORY_SESSION_KEYS = [];
@@ -22299,7 +22510,7 @@ function SidebarSessionItem({ entryKey, directorySessionsStore, activeSessionSto
22299
22510
  const timestamp = entry.lastMessageAt || entry.modified || entry.lastUserMessageAt;
22300
22511
  const hasTimestamp = isValidSidebarTimestamp(timestamp);
22301
22512
  const lastMessagePreview = entry.lastMessagePreview?.trim() || "";
22302
- const pinnedDirectoryLabel = isPinned ? formatDisplayPath$1(entry.cwd?.trim() || entry.path?.trim() || "") : "";
22513
+ const pinnedDirectoryLabel = isPinned ? formatFolderName$1(entry.cwd?.trim() || entry.path?.trim() || "") : "";
22303
22514
  const exactTimestamp = hasTimestamp && timestamp ? new Date(timestamp).toLocaleString() : void 0;
22304
22515
  const showUnread = Boolean(entry.unread) && !entry.streaming;
22305
22516
  const isOptimisticSession = Boolean(entry.optimistic || entry.id?.startsWith("optimistic:") && !entry.path);
@@ -23893,12 +24104,13 @@ function sameComposerPendingMessages(left, right) {
23893
24104
  return true;
23894
24105
  }
23895
24106
  function sameAppShellComposerSnapshot(left, right) {
23896
- return left.activeSessionId === right.activeSessionId && left.awaitingFirstTurn === right.awaitingFirstTurn && left.centerMessages === right.centerMessages && sameComposerDiffLineComments(left.composerDiffLineComments, right.composerDiffLineComments) && left.composerSkill === right.composerSkill && left.composerSyncNonce === right.composerSyncNonce && left.composerText === right.composerText && left.disabled === right.disabled && left.isStreaming === right.isStreaming && left.isSubmitting === right.isSubmitting && left.viewerContextId === right.viewerContextId && sameComposerPromptImages(left.composerImages, right.composerImages) && sameComposerPendingMessages(left.currentPendingMessages, right.currentPendingMessages);
24107
+ return left.activeSessionId === right.activeSessionId && left.awaitingFirstTurn === right.awaitingFirstTurn && left.canStartPendingQueue === right.canStartPendingQueue && left.centerMessages === right.centerMessages && sameComposerDiffLineComments(left.composerDiffLineComments, right.composerDiffLineComments) && left.composerSkill === right.composerSkill && left.composerSyncNonce === right.composerSyncNonce && left.composerText === right.composerText && left.disabled === right.disabled && left.isCompacting === right.isCompacting && left.isStreaming === right.isStreaming && left.isSubmitting === right.isSubmitting && left.viewerContextId === right.viewerContextId && sameComposerPromptImages(left.composerImages, right.composerImages) && sameComposerPendingMessages(left.currentPendingMessages, right.currentPendingMessages);
23897
24108
  }
23898
24109
  function createInitialAppShellComposerSnapshot(viewerContextId) {
23899
24110
  return {
23900
24111
  activeSessionId: void 0,
23901
24112
  awaitingFirstTurn: false,
24113
+ canStartPendingQueue: false,
23902
24114
  centerMessages: false,
23903
24115
  composerDiffLineComments: EMPTY_COMPOSER_DIFF_LINE_COMMENTS,
23904
24116
  composerImages: EMPTY_COMPOSER_IMAGES,
@@ -23907,6 +24119,7 @@ function createInitialAppShellComposerSnapshot(viewerContextId) {
23907
24119
  composerText: "",
23908
24120
  currentPendingMessages: EMPTY_COMPOSER_PENDING_MESSAGES,
23909
24121
  disabled: false,
24122
+ isCompacting: false,
23910
24123
  isStreaming: false,
23911
24124
  isSubmitting: false,
23912
24125
  viewerContextId
@@ -23969,6 +24182,143 @@ function createPicoLatestThrottler(options) {
23969
24182
  cancel: () => throttler.cancel()
23970
24183
  };
23971
24184
  }
24185
+ var OFFLINE_PROMPT_DB_NAME = "pico-offline-prompts";
24186
+ var OFFLINE_PROMPT_DB_VERSION = 1;
24187
+ var OFFLINE_PROMPT_STORE_NAME = "prompts";
24188
+ var OFFLINE_PROMPT_FALLBACK_STORAGE_KEY = "pico-offline-prompts";
24189
+ var dbPromise;
24190
+ function hasIndexedDb() {
24191
+ return typeof indexedDB !== "undefined";
24192
+ }
24193
+ function openOfflinePromptDb() {
24194
+ if (!hasIndexedDb()) return Promise.resolve(null);
24195
+ dbPromise ??= new Promise((resolve) => {
24196
+ const request = indexedDB.open(OFFLINE_PROMPT_DB_NAME, OFFLINE_PROMPT_DB_VERSION);
24197
+ request.onupgradeneeded = () => {
24198
+ const db = request.result;
24199
+ if (!db.objectStoreNames.contains(OFFLINE_PROMPT_STORE_NAME)) db.createObjectStore(OFFLINE_PROMPT_STORE_NAME, { keyPath: "id" });
24200
+ };
24201
+ request.onerror = () => resolve(null);
24202
+ request.onsuccess = () => resolve(request.result);
24203
+ });
24204
+ return dbPromise;
24205
+ }
24206
+ function fallbackItems() {
24207
+ try {
24208
+ const raw = window.localStorage.getItem(OFFLINE_PROMPT_FALLBACK_STORAGE_KEY);
24209
+ if (!raw) return [];
24210
+ const parsed = JSON.parse(raw);
24211
+ return Array.isArray(parsed) ? normalizeItems(parsed) : [];
24212
+ } catch {
24213
+ return [];
24214
+ }
24215
+ }
24216
+ function rememberFallbackItems(items) {
24217
+ try {
24218
+ window.localStorage.setItem(OFFLINE_PROMPT_FALLBACK_STORAGE_KEY, JSON.stringify(items));
24219
+ } catch {}
24220
+ }
24221
+ function normalizeImage(value) {
24222
+ if (!value || typeof value !== "object") return null;
24223
+ const rawImage = value;
24224
+ if (typeof rawImage.mimeType !== "string") return null;
24225
+ if (typeof rawImage.data !== "string") return null;
24226
+ return {
24227
+ type: "image",
24228
+ mimeType: rawImage.mimeType,
24229
+ data: rawImage.data
24230
+ };
24231
+ }
24232
+ function normalizeItem(value) {
24233
+ if (!value || typeof value !== "object") return null;
24234
+ const rawItem = value;
24235
+ if (typeof rawItem.id !== "string" || !rawItem.id) return null;
24236
+ if (typeof rawItem.contextId !== "string" || !rawItem.contextId) return null;
24237
+ if (typeof rawItem.message !== "string") return null;
24238
+ if (typeof rawItem.clientRequestId !== "string") return null;
24239
+ const images = Array.isArray(rawItem.images) ? rawItem.images.flatMap((image) => {
24240
+ const normalizedImage = normalizeImage(image);
24241
+ return normalizedImage ? [normalizedImage] : [];
24242
+ }) : [];
24243
+ return {
24244
+ id: rawItem.id,
24245
+ contextId: rawItem.contextId,
24246
+ createdAt: typeof rawItem.createdAt === "number" && Number.isFinite(rawItem.createdAt) ? rawItem.createdAt : Date.now(),
24247
+ sessionId: typeof rawItem.sessionId === "string" ? rawItem.sessionId : void 0,
24248
+ targetSessionKey: typeof rawItem.targetSessionKey === "string" ? rawItem.targetSessionKey : void 0,
24249
+ message: rawItem.message,
24250
+ images,
24251
+ streamingBehavior: rawItem.streamingBehavior === "followUp" || rawItem.streamingBehavior === "steer" ? rawItem.streamingBehavior : void 0,
24252
+ pendingId: typeof rawItem.pendingId === "string" ? rawItem.pendingId : void 0,
24253
+ clientRequestId: rawItem.clientRequestId,
24254
+ thinkingLevel: typeof rawItem.thinkingLevel === "string" ? rawItem.thinkingLevel : void 0,
24255
+ draftOwnerKey: typeof rawItem.draftOwnerKey === "string" ? rawItem.draftOwnerKey : void 0,
24256
+ draftCwd: typeof rawItem.draftCwd === "string" ? rawItem.draftCwd : void 0
24257
+ };
24258
+ }
24259
+ function normalizeItems(values) {
24260
+ return values.flatMap((value) => {
24261
+ const normalizedItem = normalizeItem(value);
24262
+ return normalizedItem ? [normalizedItem] : [];
24263
+ }).sort((left, right) => left.createdAt - right.createdAt);
24264
+ }
24265
+ async function enqueueOfflinePrompt(item) {
24266
+ const db = await openOfflinePromptDb();
24267
+ if (!db) {
24268
+ const items = fallbackItems().filter((entry) => entry.id !== item.id);
24269
+ items.push(item);
24270
+ rememberFallbackItems(normalizeItems(items));
24271
+ return;
24272
+ }
24273
+ await new Promise((resolve, reject) => {
24274
+ const transaction = db.transaction(OFFLINE_PROMPT_STORE_NAME, "readwrite");
24275
+ const request = transaction.objectStore(OFFLINE_PROMPT_STORE_NAME).put(item);
24276
+ request.onerror = () => reject(request.error);
24277
+ transaction.oncomplete = () => resolve();
24278
+ transaction.onerror = () => reject(transaction.error);
24279
+ });
24280
+ }
24281
+ async function readOfflinePrompts(contextId) {
24282
+ const db = await openOfflinePromptDb();
24283
+ if (!db) {
24284
+ const items = fallbackItems();
24285
+ return contextId ? items.filter((item) => item.contextId === contextId) : items;
24286
+ }
24287
+ const items = await new Promise((resolve, reject) => {
24288
+ const transaction = db.transaction(OFFLINE_PROMPT_STORE_NAME, "readonly");
24289
+ const request = transaction.objectStore(OFFLINE_PROMPT_STORE_NAME).getAll();
24290
+ request.onerror = () => reject(request.error);
24291
+ request.onsuccess = () => resolve(normalizeItems(request.result));
24292
+ transaction.onerror = () => reject(transaction.error);
24293
+ });
24294
+ return contextId ? items.filter((item) => item.contextId === contextId) : items;
24295
+ }
24296
+ async function removeOfflinePrompt(id) {
24297
+ const db = await openOfflinePromptDb();
24298
+ if (!db) {
24299
+ rememberFallbackItems(fallbackItems().filter((item) => item.id !== id));
24300
+ return;
24301
+ }
24302
+ await new Promise((resolve, reject) => {
24303
+ const transaction = db.transaction(OFFLINE_PROMPT_STORE_NAME, "readwrite");
24304
+ const request = transaction.objectStore(OFFLINE_PROMPT_STORE_NAME).delete(id);
24305
+ request.onerror = () => reject(request.error);
24306
+ transaction.oncomplete = () => resolve();
24307
+ transaction.onerror = () => reject(transaction.error);
24308
+ });
24309
+ }
24310
+ function pendingMessagesFromResponse(response) {
24311
+ if (!("ok" in response) || !response.ok) return null;
24312
+ return response.pendingMessages.map((message) => ({
24313
+ pendingId: message.pendingId,
24314
+ text: message.text,
24315
+ images: message.images.flatMap((image) => {
24316
+ const normalizedImage = normalizePromptImage(image);
24317
+ return normalizedImage ? [normalizedImage] : [];
24318
+ }),
24319
+ streamingBehavior: message.streamingBehavior === "steer" ? "steer" : "followUp"
24320
+ }));
24321
+ }
23972
24322
  function samePromptMutationSessionSnapshot(left, right) {
23973
24323
  return left.assistantOutputSignature === right.assistantOutputSignature && left.cwd === right.cwd && left.draft === right.draft && left.sessionFile === right.sessionFile && left.sessionId === right.sessionId && left.sessionKey === right.sessionKey && left.streaming === right.streaming;
23974
24324
  }
@@ -24008,6 +24358,15 @@ function createLocalPendingId() {
24008
24358
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `optimistic:${crypto.randomUUID()}`;
24009
24359
  return `optimistic:${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
24010
24360
  }
24361
+ function createClientPromptRequestId() {
24362
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `prompt:${crypto.randomUUID()}`;
24363
+ return `prompt:${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
24364
+ }
24365
+ function isOfflinePromptSubmissionError(error) {
24366
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return true;
24367
+ const message = error instanceof Error ? error.message.toLowerCase() : "";
24368
+ return message === "failed to fetch" || message === "load failed" || message.includes("networkerror") || message.includes("network error");
24369
+ }
24011
24370
  function buildCurrentSessionRequestUrl(path, { contextId, sessionState, fallbackSessionId, targetSessionKey }) {
24012
24371
  if (targetSessionKey) return buildRequestUrl(path, {
24013
24372
  contextId,
@@ -24021,6 +24380,25 @@ function buildCurrentSessionRequestUrl(path, { contextId, sessionState, fallback
24021
24380
  searchParams: shouldUseSessionKey ? { sessionKey } : void 0
24022
24381
  });
24023
24382
  }
24383
+ function buildOfflinePromptRequestUrl(item) {
24384
+ return buildRequestUrl("/api/prompt", {
24385
+ contextId: item.contextId,
24386
+ sessionId: item.targetSessionKey ? void 0 : item.sessionId,
24387
+ searchParams: item.targetSessionKey ? { sessionKey: item.targetSessionKey } : void 0
24388
+ });
24389
+ }
24390
+ function buildOfflinePromptBody(item) {
24391
+ return {
24392
+ message: item.message,
24393
+ images: item.images,
24394
+ streamingBehavior: item.streamingBehavior,
24395
+ pendingId: item.pendingId,
24396
+ clientRequestId: item.clientRequestId,
24397
+ thinkingLevel: item.thinkingLevel,
24398
+ draftOwnerKey: item.draftOwnerKey,
24399
+ draftCwd: item.draftCwd
24400
+ };
24401
+ }
24024
24402
  function isPendingPromptNotFoundError(error) {
24025
24403
  return error instanceof Error && error.message.toLowerCase().includes("pending prompt not found");
24026
24404
  }
@@ -24334,7 +24712,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24334
24712
  updateOptimisticSidebarSessionPreview
24335
24713
  ]);
24336
24714
  const promptMutation = useMutation({
24337
- mutationFn: async ({ message, images, streamingBehavior, pendingId, thinkingLevel, targetSessionKey, draftTarget }) => {
24715
+ mutationFn: async ({ message, images, streamingBehavior, pendingId, clientRequestId, thinkingLevel, targetSessionKey, draftTarget }) => {
24338
24716
  if (!viewerContextId) throw new Error("Viewer context unavailable");
24339
24717
  return await fetchJson(buildCurrentSessionRequestUrl("/api/prompt", {
24340
24718
  contextId: viewerContextId,
@@ -24349,6 +24727,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24349
24727
  images,
24350
24728
  streamingBehavior,
24351
24729
  pendingId,
24730
+ clientRequestId,
24352
24731
  thinkingLevel,
24353
24732
  draftOwnerKey: draftTarget?.ownerKey,
24354
24733
  draftCwd: draftTarget?.cwd
@@ -24362,6 +24741,58 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24362
24741
  });
24363
24742
  }
24364
24743
  });
24744
+ const offlinePromptFlushInFlightRef = import_react.useRef(false);
24745
+ const offlinePromptFailureToastIdsRef = import_react.useRef(/* @__PURE__ */ new Set());
24746
+ const flushOfflinePromptQueue = import_react.useCallback(async () => {
24747
+ if (!viewerContextId || offlinePromptFlushInFlightRef.current) return;
24748
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return;
24749
+ offlinePromptFlushInFlightRef.current = true;
24750
+ try {
24751
+ const queuedPrompts = await readOfflinePrompts(viewerContextId);
24752
+ if (queuedPrompts.length === 0) return;
24753
+ let flushedCount = 0;
24754
+ for (const item of queuedPrompts) try {
24755
+ await fetchJson(buildOfflinePromptRequestUrl(item), {
24756
+ method: "POST",
24757
+ headers: { "content-type": "application/json" },
24758
+ body: JSON.stringify(buildOfflinePromptBody(item))
24759
+ });
24760
+ await removeOfflinePrompt(item.id);
24761
+ offlinePromptFailureToastIdsRef.current.delete(item.id);
24762
+ flushedCount += 1;
24763
+ } catch (error) {
24764
+ if (isOfflinePromptSubmissionError(error)) break;
24765
+ if (!offlinePromptFailureToastIdsRef.current.has(item.id)) {
24766
+ offlinePromptFailureToastIdsRef.current.add(item.id);
24767
+ toast.error(error instanceof Error ? `Failed to send queued prompt: ${error.message}` : "Failed to send queued prompt");
24768
+ }
24769
+ break;
24770
+ }
24771
+ if (flushedCount > 0) {
24772
+ toast.success(flushedCount === 1 ? "Sent queued prompt" : `Sent ${flushedCount.toLocaleString()} queued prompts`);
24773
+ await queryClient.invalidateQueries({ refetchType: "active" });
24774
+ }
24775
+ } finally {
24776
+ offlinePromptFlushInFlightRef.current = false;
24777
+ }
24778
+ }, [queryClient, viewerContextId]);
24779
+ import_react.useEffect(() => {
24780
+ if (!viewerContextId) return;
24781
+ const flush = () => {
24782
+ flushOfflinePromptQueue();
24783
+ };
24784
+ window.addEventListener("online", flush);
24785
+ window.addEventListener("focus", flush);
24786
+ document.addEventListener("visibilitychange", flush);
24787
+ const timer = window.setInterval(flush, 3e4);
24788
+ flush();
24789
+ return () => {
24790
+ window.removeEventListener("online", flush);
24791
+ window.removeEventListener("focus", flush);
24792
+ document.removeEventListener("visibilitychange", flush);
24793
+ window.clearInterval(timer);
24794
+ };
24795
+ }, [flushOfflinePromptQueue, viewerContextId]);
24365
24796
  const submitPrompt = import_react.useCallback(async (streamingBehavior, options) => {
24366
24797
  if (!viewerContextId) return false;
24367
24798
  if (draftSessionLoadingOwnerKeyRef.current) return queuePendingDraftPrompt(streamingBehavior);
@@ -24388,7 +24819,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24388
24819
  }
24389
24820
  return queuePendingDraftPrompt(streamingBehavior, currentDraftOwnerKey);
24390
24821
  }
24391
- const treatAsQueuedPrompt = options?.forceFirstPrompt ? false : Boolean(currentSessionState.streaming || awaitingFirstTurnRef.current);
24822
+ const treatAsQueuedPrompt = options?.forceFirstPrompt ? false : Boolean(currentSessionState.streaming || currentSessionState.compacting || awaitingFirstTurnRef.current);
24392
24823
  const normalizedStreamingBehavior = treatAsQueuedPrompt ? normalizeQueuedStreamingBehavior(streamingBehavior) : void 0;
24393
24824
  const submittedImages = composerImagesRef.current.map((image) => ({ ...image }));
24394
24825
  const sidebarPreview = promptSidebarPreview(message, submittedImages);
@@ -24414,6 +24845,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24414
24845
  images: submittedImages.map((image) => ({ ...image })),
24415
24846
  streamingBehavior: normalizedStreamingBehavior
24416
24847
  });
24848
+ const clientRequestId = createClientPromptRequestId();
24417
24849
  setIsSubmitting(true);
24418
24850
  replaceComposerDraft("", void 0, { forceSync: true });
24419
24851
  setComposerDiffLineComments([]);
@@ -24425,6 +24857,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24425
24857
  images: submittedImages,
24426
24858
  streamingBehavior: normalizedStreamingBehavior,
24427
24859
  pendingId: queuedPendingId,
24860
+ clientRequestId,
24428
24861
  thinkingLevel: currentSessionState.thinkingLevel,
24429
24862
  targetSessionKey,
24430
24863
  draftTarget: targetDraft
@@ -24433,6 +24866,39 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24433
24866
  if (targetSessionKey && pendingNewSessionPromptTargetRef.current?.sessionKey === targetSessionKey) pendingNewSessionPromptTargetRef.current = null;
24434
24867
  return true;
24435
24868
  } catch (error) {
24869
+ const queueSessionKey = targetSessionKey || (currentSessionState.sessionKey && !currentSessionState.sessionKey.startsWith("optimistic:") && (currentSessionState.draft || !currentSessionState.sessionId) ? currentSessionState.sessionKey : void 0);
24870
+ const queueSessionId = queueSessionKey ? void 0 : currentSessionState.sessionId ?? activeSessionId;
24871
+ if (isOfflinePromptSubmissionError(error) && (queueSessionKey || queueSessionId)) {
24872
+ await enqueueOfflinePrompt({
24873
+ id: clientRequestId,
24874
+ contextId: viewerContextId,
24875
+ createdAt: Date.now(),
24876
+ sessionId: queueSessionId,
24877
+ targetSessionKey: queueSessionKey,
24878
+ message,
24879
+ images: submittedImages.map((image) => ({
24880
+ type: "image",
24881
+ mimeType: image.mimeType,
24882
+ data: image.data
24883
+ })),
24884
+ streamingBehavior: normalizedStreamingBehavior,
24885
+ pendingId: queuedPendingId,
24886
+ clientRequestId,
24887
+ thinkingLevel: currentSessionState.thinkingLevel,
24888
+ draftOwnerKey: targetDraft?.ownerKey,
24889
+ draftCwd: targetDraft?.cwd
24890
+ });
24891
+ if (queuedPendingId) removeOptimisticPendingMessage(queuedPendingId);
24892
+ removeOptimisticUserMessage(optimisticId);
24893
+ removeOptimisticSidebarSession(optimisticSidebarSessionId);
24894
+ if (!treatAsQueuedPrompt) {
24895
+ awaitingFirstTurnAssistantOutputSignatureRef.current = null;
24896
+ awaitingFirstTurnRef.current = false;
24897
+ setAwaitingFirstTurn(false);
24898
+ }
24899
+ toast.info("Prompt queued locally and will send when reconnected");
24900
+ return true;
24901
+ }
24436
24902
  if (queuedPendingId) removeOptimisticPendingMessage(queuedPendingId);
24437
24903
  removeOptimisticUserMessage(optimisticId);
24438
24904
  removeOptimisticSidebarSession(optimisticSidebarSessionId);
@@ -24455,6 +24921,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24455
24921
  setIsSubmitting(false);
24456
24922
  }
24457
24923
  }, [
24924
+ activeSessionId,
24458
24925
  addOptimisticPendingMessage,
24459
24926
  addOptimisticSidebarSession,
24460
24927
  addOptimisticUserMessage,
@@ -24499,6 +24966,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24499
24966
  images: followUp.images,
24500
24967
  streamingBehavior: followUp.streamingBehavior,
24501
24968
  pendingId: followUp.optimisticId,
24969
+ clientRequestId: createClientPromptRequestId(),
24502
24970
  thinkingLevel: sessionStateRef.current.thinkingLevel,
24503
24971
  targetSessionKey,
24504
24972
  draftTarget
@@ -24676,6 +25144,41 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24676
25144
  updatePendingMessages,
24677
25145
  viewerContextId
24678
25146
  ]);
25147
+ const startPendingQueueMutation = useMutation({
25148
+ mutationFn: async () => {
25149
+ if (!viewerContextId) throw new Error("Viewer context unavailable");
25150
+ return await fetchJson(buildCurrentSessionRequestUrl("/api/pending-messages/start", {
25151
+ contextId: viewerContextId,
25152
+ sessionState: sessionStateRef.current,
25153
+ fallbackSessionId: activeSessionId
25154
+ }), { method: "POST" });
25155
+ },
25156
+ onSuccess: async () => {
25157
+ await queryClient.invalidateQueries({
25158
+ queryKey: picoQueryKeys.sessionTree(viewerContextId, picoSessionScopeKey(sessionStateRef.current)),
25159
+ refetchType: "active"
25160
+ });
25161
+ }
25162
+ });
25163
+ const startPendingQueue = import_react.useCallback(async () => {
25164
+ if (!viewerContextId) return;
25165
+ if (pendingMessagesRef.current.length === 0) return;
25166
+ setIsSubmitting(true);
25167
+ try {
25168
+ const nextPendingMessages = pendingMessagesFromResponse(await startPendingQueueMutation.mutateAsync());
25169
+ if (nextPendingMessages) updatePendingMessages(() => nextPendingMessages);
25170
+ } catch (error) {
25171
+ toast.error(error instanceof Error ? error.message : "Failed to start queue");
25172
+ } finally {
25173
+ setIsSubmitting(false);
25174
+ }
25175
+ }, [
25176
+ pendingMessagesRef,
25177
+ setIsSubmitting,
25178
+ startPendingQueueMutation,
25179
+ updatePendingMessages,
25180
+ viewerContextId
25181
+ ]);
24679
25182
  const removePendingMessageMutation = useMutation({
24680
25183
  mutationFn: async (pendingId) => {
24681
25184
  if (!viewerContextId) throw new Error("Viewer context unavailable");
@@ -24737,6 +25240,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
24737
25240
  updatePendingMessages,
24738
25241
  viewerContextId
24739
25242
  ]),
25243
+ startPendingQueue,
24740
25244
  submitPrompt
24741
25245
  };
24742
25246
  }
@@ -25338,7 +25842,7 @@ function useAppShellSessionMutations({ viewerContextId, activeSessionId, session
25338
25842
  });
25339
25843
  return {
25340
25844
  cycleThinkingLevel,
25341
- deleteSessions: import_react.useCallback(async (targets) => {
25845
+ deleteSessions: import_react.useCallback((targets) => {
25342
25846
  if (!viewerContextId || targets.length === 0) return false;
25343
25847
  const orderedTargets = [...targets.filter((target) => target.path && target.path !== sessionStateRef.current.sessionFile), ...targets.filter((target) => target.path && target.path === sessionStateRef.current.sessionFile)];
25344
25848
  const orderedPaths = [];
@@ -25407,6 +25911,26 @@ function useAppShellSessionMutations({ viewerContextId, activeSessionId, session
25407
25911
  }
25408
25912
  var RESUME_RECONNECT_AFTER_MS = 3e4;
25409
25913
  var COMPACT_WORKING_LABEL = "Compacting context...";
25914
+ var EVENT_SOURCE_LAST_ID_STORAGE_PREFIX = "pico-events-last-id:";
25915
+ function eventsLastIdStorageKey(viewerContextId) {
25916
+ return `${EVENT_SOURCE_LAST_ID_STORAGE_PREFIX}${viewerContextId}`;
25917
+ }
25918
+ function readStoredEventsLastId(viewerContextId) {
25919
+ try {
25920
+ return window.sessionStorage.getItem(eventsLastIdStorageKey(viewerContextId));
25921
+ } catch {
25922
+ return null;
25923
+ }
25924
+ }
25925
+ function rememberStoredEventsLastId(viewerContextId, lastEventId) {
25926
+ try {
25927
+ window.sessionStorage.setItem(eventsLastIdStorageKey(viewerContextId), lastEventId);
25928
+ } catch {}
25929
+ }
25930
+ function normalizeEventSourceLastId(value) {
25931
+ const normalized = typeof value === "string" ? value.trim() : "";
25932
+ return /^\d+$/.test(normalized) ? normalized : void 0;
25933
+ }
25410
25934
  function normalizePendingMessages(payload) {
25411
25935
  if (!isStateSyncEvent(payload)) return;
25412
25936
  if (!Array.isArray(payload.pendingUserMessages)) return;
@@ -25420,6 +25944,9 @@ function normalizePendingMessages(payload) {
25420
25944
  streamingBehavior: message.streamingBehavior === "steer" ? "steer" : "followUp"
25421
25945
  }));
25422
25946
  }
25947
+ function autoSessionNamingErrorDescription(payload) {
25948
+ return payload.refinementReason || payload.heuristicReason || payload.promptPreview || "The server could not generate a session name.";
25949
+ }
25423
25950
  function sameStringArray(left = [], right = []) {
25424
25951
  if (left.length !== right.length) return false;
25425
25952
  for (let index = 0; index < left.length; index += 1) if (left[index] !== right[index]) return false;
@@ -25507,7 +26034,7 @@ function sameSessionsEvent(left, right) {
25507
26034
  function visibleAssistantBlockKey(block, options) {
25508
26035
  switch (block.type) {
25509
26036
  case "text": return block.text.trim() ? `text:${block.text}` : "";
25510
- case "compaction": return `compaction:${block.tokensBefore}:${block.summary}`;
26037
+ case "compaction": return `compaction:${block.tokensBefore}:${block.estimatedTokensAfter ?? ""}:${block.summary}`;
25511
26038
  case "thinking": return options.hideThinking ? "" : `thinking:${block.summaryLabel || ""}:${block.text}`;
25512
26039
  case "tool": return options.hideToolBlocks ? "" : `tool:${block.callId || ""}:${block.name || ""}:${JSON.stringify(block.args ?? null)}:${block.output}:${JSON.stringify(block.details ?? null)}:${block.isError ? "1" : "0"}:${block.running ? "1" : "0"}`;
25513
26040
  default: return "";
@@ -25584,6 +26111,7 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
25584
26111
  const currentSessionIdRef = import_react.useRef(sessionId);
25585
26112
  const draftSessionLoadingOwnerKeyRef = import_react.useRef(draftSessionLoadingOwnerKey);
25586
26113
  const currentSourceRef = import_react.useRef(null);
26114
+ const eventsLastIdRef = import_react.useRef(void 0);
25587
26115
  const hasReceivedStateSyncRef = import_react.useRef(false);
25588
26116
  const [hasReceivedStateSync, setHasReceivedStateSync] = import_react.useState(false);
25589
26117
  const backgroundedAtRef = import_react.useRef(null);
@@ -25595,6 +26123,9 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
25595
26123
  import_react.useEffect(() => {
25596
26124
  draftSessionLoadingOwnerKeyRef.current = draftSessionLoadingOwnerKey;
25597
26125
  }, [draftSessionLoadingOwnerKey]);
26126
+ import_react.useEffect(() => {
26127
+ eventsLastIdRef.current = normalizeEventSourceLastId(readStoredEventsLastId(viewerContextId));
26128
+ }, [viewerContextId]);
25598
26129
  import_react.useEffect(() => {
25599
26130
  if (currentSourceRef.current) return;
25600
26131
  initialEventsSessionIdRef.current = sessionId;
@@ -25710,6 +26241,11 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
25710
26241
  ]);
25711
26242
  const handleEventSourceMessage = import_react.useCallback((source, gitInvalidationBatcher, event) => {
25712
26243
  if (currentSourceRef.current !== source) return;
26244
+ const lastEventId = normalizeEventSourceLastId(event.lastEventId);
26245
+ if (lastEventId) {
26246
+ eventsLastIdRef.current = lastEventId;
26247
+ rememberStoredEventsLastId(viewerContextId, lastEventId);
26248
+ }
25713
26249
  const payload = JSON.parse(event.data);
25714
26250
  if (isStateSyncEvent(payload)) {
25715
26251
  const activationRevision = payload.activationRevision;
@@ -25808,6 +26344,11 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
25808
26344
  });
25809
26345
  return;
25810
26346
  }
26347
+ if (payload.type === "auto_session_naming_error") {
26348
+ console.warn("[pico] auto session naming failed", payload);
26349
+ toast.warning("Could not name session", { description: autoSessionNamingErrorDescription(payload) });
26350
+ return;
26351
+ }
25811
26352
  if (payload.type === "request_error") {
25812
26353
  toast.error(payload.error || "Request failed");
25813
26354
  return;
@@ -25848,7 +26389,8 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
25848
26389
  setSessionDoneEvents,
25849
26390
  setSessionState,
25850
26391
  setSessionsEvent,
25851
- setWorkingState
26392
+ setWorkingState,
26393
+ viewerContextId
25852
26394
  ]);
25853
26395
  import_react.useEffect(() => {
25854
26396
  if (!viewerContextId) return;
@@ -25900,7 +26442,10 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
25900
26442
  const source = new EventSource(buildRequestUrl("/events", {
25901
26443
  contextId: viewerContextId,
25902
26444
  sessionId: initialEventsSessionIdRef.current,
25903
- searchParams: { sidebarDirectory: bootstrapSidebarDirectories }
26445
+ searchParams: {
26446
+ lastEventId: eventsLastIdRef.current,
26447
+ sidebarDirectory: bootstrapSidebarDirectories
26448
+ }
25904
26449
  }));
25905
26450
  currentSourceRef.current = source;
25906
26451
  source.onopen = () => {
@@ -27132,7 +27677,8 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27132
27677
  if (!currentState.sessionFile) return;
27133
27678
  renameDialogRef.current?.open({
27134
27679
  path: currentState.sessionFile,
27135
- title: currentState.sessionName || getCurrentSessionTitleFromState(currentState)
27680
+ title: currentState.sessionName || getCurrentSessionTitleFromState(currentState),
27681
+ hasGeneratedName: Boolean(currentState.sessionName?.trim())
27136
27682
  });
27137
27683
  };
27138
27684
  const openRenameDialogForEntry = (entry) => {
@@ -27525,7 +28071,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27525
28071
  const pendingDraftPrompt = pendingDraftPromptStore.state;
27526
28072
  const pendingDraftFollowUps = pendingDraftFollowUpsStore.state;
27527
28073
  const pendingMessages = pendingMessagesStore.state;
27528
- const { abortSession, addDirectoryPath, createSession: requestCreateSession, editPendingMessage, removePendingMessage, reorderPending, submitPrompt } = useAppShellPromptMutations({
28074
+ const { abortSession, addDirectoryPath, createSession: requestCreateSession, editPendingMessage, removePendingMessage, reorderPending, startPendingQueue, submitPrompt } = useAppShellPromptMutations({
27529
28075
  viewerContextId,
27530
28076
  activeSessionId,
27531
28077
  defaultNewSessionDirectory,
@@ -27964,6 +28510,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27964
28510
  const composerSnapshot = {
27965
28511
  activeSessionId,
27966
28512
  awaitingFirstTurn: composerDisabled ? false : awaitingFirstTurn,
28513
+ canStartPendingQueue: !composerDisabled && pendingMessages.length > 0 && !sessionStateRef.current.streaming && !sessionStateRef.current.compacting && !awaitingFirstTurn && !isSubmitting,
27967
28514
  centerMessages: displaySettingsRef.current.centerMessages,
27968
28515
  composerDiffLineComments: displayedDiffLineComments,
27969
28516
  composerImages: displayedComposerImages,
@@ -27972,6 +28519,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27972
28519
  composerText: displayedComposerText,
27973
28520
  currentPendingMessages: displayedPendingMessages,
27974
28521
  disabled: composerDisabled,
28522
+ isCompacting: composerDisabled ? false : sessionStateRef.current.compacting,
27975
28523
  isStreaming: composerDisabled ? false : sessionStateRef.current.streaming,
27976
28524
  isSubmitting: composerDisabled ? false : isSubmitting,
27977
28525
  viewerContextId
@@ -27994,6 +28542,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
27994
28542
  removePendingMessage,
27995
28543
  reorderPending,
27996
28544
  reorderPendingDraftFollowUp,
28545
+ startPendingQueue,
27997
28546
  runBuiltinSlashCommand,
27998
28547
  setModel,
27999
28548
  setThinkingLevel,