@saadnvd1/agent-os 0.3.794 → 0.3.807

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 (167) hide show
  1. package/app/api/busy/route.ts +2 -2
  2. package/app/api/chat-views/[sessionId]/route.ts +19 -0
  3. package/app/api/chat-views/route.test.ts +79 -0
  4. package/app/api/chat-views/route.ts +8 -0
  5. package/app/api/tips/route.test.ts +56 -0
  6. package/app/api/tips/route.ts +45 -0
  7. package/app/api/updates/health/route.ts +22 -0
  8. package/app/api/updates/route.test.ts +53 -0
  9. package/app/api/updates/settings/route.ts +37 -10
  10. package/app/page.tsx +17 -14
  11. package/components/Chat/ChatPanel.tsx +74 -59
  12. package/components/Chat/NewDivider.tsx +15 -0
  13. package/components/Chat/needs-more-history.test.ts +28 -0
  14. package/components/Chat/needs-more-history.ts +14 -0
  15. package/components/Chat/session-link.ts +7 -4
  16. package/components/Chat/useFind.ts +50 -1
  17. package/components/Chat/usePosition.ts +363 -0
  18. package/components/CodeSearchDialog.tsx +90 -0
  19. package/components/CommandPalette/CommandsBody.tsx +12 -4
  20. package/components/CommandPalette/useAppCommands.ts +29 -11
  21. package/components/DevServers/ServerLogsModal.tsx +24 -21
  22. package/components/Nav/HistoryButtons.tsx +128 -0
  23. package/components/Pane/MobileTabBar.tsx +3 -0
  24. package/components/Pane/index.tsx +2 -2
  25. package/components/Recipes/TimerFields.tsx +2 -1
  26. package/components/Schedules/ScheduleForm.tsx +1 -1
  27. package/components/SessionList/useSessionSwitching.ts +3 -2
  28. package/components/Settings/SettingsDialog.tsx +1 -1
  29. package/components/Shortcuts/PendingKeys.tsx +19 -0
  30. package/components/Shortcuts/ShortcutsButton.tsx +22 -0
  31. package/components/Shortcuts/ShortcutsDialog.tsx +122 -0
  32. package/components/SidebarFooter.tsx +4 -0
  33. package/components/Terminal/hooks/useTerminalConnection.ts +4 -3
  34. package/components/Tips/TipButton.tsx +39 -0
  35. package/components/Tips/TipDialog.tsx +148 -0
  36. package/components/Tips/TipHost.tsx +14 -0
  37. package/components/Tips/TipMedia.tsx +71 -0
  38. package/components/Tips/index.ts +2 -0
  39. package/components/Updates/AutoUpdates.tsx +141 -0
  40. package/components/Updates/UpdateBanner.test.ts +39 -1
  41. package/components/Updates/UpdateBanner.tsx +31 -5
  42. package/components/Updates/UpdatesSection.tsx +24 -3
  43. package/components/views/DesktopView.tsx +7 -9
  44. package/components/views/MobileView.tsx +2 -8
  45. package/components/views/lazy.ts +2 -2
  46. package/content/tips.json +541 -0
  47. package/content/whats-new.json +56 -0
  48. package/data/chat-views/index.ts +2 -0
  49. package/data/chat-views/keys.ts +3 -0
  50. package/data/chat-views/queries.ts +34 -0
  51. package/data/sessions/pin.ts +0 -1
  52. package/data/tips/index.ts +49 -0
  53. package/data/updates/index.ts +5 -0
  54. package/hooks/useChatViewsSync.ts +12 -0
  55. package/hooks/useNavActions.ts +104 -0
  56. package/hooks/useNavKeys.ts +105 -0
  57. package/hooks/useReloadWhenUpdated.ts +5 -1
  58. package/hooks/useSessionUrl.ts +23 -5
  59. package/lib/bus/index.ts +4 -2
  60. package/lib/bus/role-alias.ts +4 -2
  61. package/lib/bus/targets.ts +2 -1
  62. package/lib/busy.ts +23 -1
  63. package/lib/chat/escape.ts +8 -4
  64. package/lib/chat-views/body.ts +41 -0
  65. package/lib/chat-views/store.ts +67 -0
  66. package/lib/chat-views/types.ts +20 -0
  67. package/lib/db/migrations/20261031T1500_chat_views.ts +30 -0
  68. package/lib/db/migrations.ts +21 -0
  69. package/lib/keybindings/focus.ts +17 -0
  70. package/lib/keybindings/keymap.ts +120 -0
  71. package/lib/keybindings/sequence.ts +64 -0
  72. package/lib/live/service.ts +1 -1
  73. package/lib/nav/history-mirror.ts +59 -0
  74. package/lib/nav/history-writer.ts +109 -0
  75. package/lib/nav/position.ts +67 -0
  76. package/lib/nav/recent.ts +34 -0
  77. package/lib/orchestrator/armed.ts +5 -0
  78. package/lib/palette/sections.ts +12 -6
  79. package/lib/recipes/timer.ts +3 -1
  80. package/lib/schedules/cron.ts +14 -5
  81. package/lib/schedules/store.ts +3 -2
  82. package/lib/security/demo.ts +2 -0
  83. package/lib/session-url.ts +11 -5
  84. package/lib/tasks/finish.ts +5 -5
  85. package/lib/tips/conditions.ts +38 -0
  86. package/lib/tips/index.ts +2 -0
  87. package/lib/tips/load.ts +26 -0
  88. package/lib/tips/media.ts +101 -0
  89. package/lib/tips/pick.ts +35 -0
  90. package/lib/tips/schema.ts +82 -0
  91. package/lib/tips/state.ts +26 -0
  92. package/lib/tips/view.ts +60 -0
  93. package/lib/updates/auto.ts +149 -0
  94. package/lib/updates/banner.ts +78 -3
  95. package/lib/updates/check.ts +64 -8
  96. package/lib/updates/history.ts +99 -0
  97. package/lib/updates/run.ts +334 -23
  98. package/lib/updates/settings.ts +66 -0
  99. package/lib/updates/view.ts +14 -3
  100. package/package.json +2 -2
  101. package/public/tips/.gitkeep +0 -0
  102. package/public/tips/chat-access-picker-dark.webp +0 -0
  103. package/public/tips/chat-access-picker.webp +0 -0
  104. package/public/tips/chat-attach-files-poster.webp +0 -0
  105. package/public/tips/chat-attach-files.mp4 +0 -0
  106. package/public/tips/chat-bookmark-dark.webp +0 -0
  107. package/public/tips/chat-bookmark.webp +0 -0
  108. package/public/tips/chat-plan-mode-dark.webp +0 -0
  109. package/public/tips/chat-plan-mode.webp +0 -0
  110. package/public/tips/chat-search-poster.webp +0 -0
  111. package/public/tips/chat-search.mp4 +0 -0
  112. package/public/tips/chat-slash-menu-poster.webp +0 -0
  113. package/public/tips/chat-slash-menu.mp4 +0 -0
  114. package/public/tips/chat-tab-suggestion-poster.webp +0 -0
  115. package/public/tips/chat-tab-suggestion.mp4 +0 -0
  116. package/public/tips/chat-terminal-handoff-poster.webp +0 -0
  117. package/public/tips/chat-terminal-handoff.mp4 +0 -0
  118. package/public/tips/chat-undo-dark.webp +0 -0
  119. package/public/tips/chat-undo.webp +0 -0
  120. package/public/tips/export-ai-dark.webp +0 -0
  121. package/public/tips/export-ai.webp +0 -0
  122. package/public/tips/net-messages-dark.webp +0 -0
  123. package/public/tips/net-messages.webp +0 -0
  124. package/public/tips/recipe-save-dark.webp +0 -0
  125. package/public/tips/recipe-save.webp +0 -0
  126. package/public/tips/schedule-first-dark.webp +0 -0
  127. package/public/tips/schedule-first.webp +0 -0
  128. package/public/tips/share-check-dark.webp +0 -0
  129. package/public/tips/share-check.webp +0 -0
  130. package/public/tips/sidebar-draft-poster.webp +0 -0
  131. package/public/tips/sidebar-draft.mp4 +0 -0
  132. package/public/tips/sidebar-groups-dark.webp +0 -0
  133. package/public/tips/sidebar-groups.webp +0 -0
  134. package/public/tips/sidebar-load-dark.webp +0 -0
  135. package/public/tips/sidebar-load.webp +0 -0
  136. package/public/tips/sidebar-snooze-dark.webp +0 -0
  137. package/public/tips/sidebar-snooze.webp +0 -0
  138. package/public/tips/tasks-plan-pieces-dark.webp +0 -0
  139. package/public/tips/tasks-plan-pieces.webp +0 -0
  140. package/public/tips/tasks-sign-off-dark.webp +0 -0
  141. package/public/tips/tasks-sign-off.webp +0 -0
  142. package/public/tips/tasks-stacks-dark.webp +0 -0
  143. package/public/tips/tasks-stacks.webp +0 -0
  144. package/scripts/autodeploy +4 -0
  145. package/scripts/autodeploy.test.ts +12 -0
  146. package/scripts/check +2 -2
  147. package/scripts/docs-links.test.ts +1 -25
  148. package/scripts/lib/commands.sh +32 -1
  149. package/scripts/lib/deps-swap.sh +8 -6
  150. package/scripts/lib/heading-slugs.ts +26 -0
  151. package/scripts/lib/update-verify.mjs +90 -0
  152. package/scripts/redeploy +31 -2
  153. package/scripts/redeploy.test.ts +94 -3
  154. package/scripts/tips-lint.test.ts +77 -0
  155. package/scripts/tips-media.ts +31 -0
  156. package/scripts/update-e2e.test.ts +215 -0
  157. package/scripts/update-recover.mjs +130 -0
  158. package/scripts/update-recover.test.ts +144 -0
  159. package/scripts/update-run.mjs +182 -40
  160. package/scripts/update-run.test.ts +208 -2
  161. package/server.ts +3 -2
  162. package/stores/chatJump.ts +2 -6
  163. package/stores/chatPositions.ts +65 -0
  164. package/stores/navHistory.ts +98 -0
  165. package/stores/shortcutsUi.ts +20 -0
  166. package/stores/tips.ts +14 -0
  167. package/components/QuickSwitcher.tsx +0 -286
@@ -0,0 +1,20 @@
1
+ import { proxy } from "valtio";
2
+
3
+ // The shortcuts panel, and the first key of a sequence that is waiting for
4
+ // its second (shown as a chip so the reader sees it was heard).
5
+ export const shortcutsUi = proxy<{ open: boolean; pending: string | null }>({
6
+ open: false,
7
+ pending: null,
8
+ });
9
+
10
+ export const shortcutsUiActions = {
11
+ open: () => {
12
+ shortcutsUi.open = true;
13
+ },
14
+ setOpen: (open: boolean) => {
15
+ shortcutsUi.open = open;
16
+ },
17
+ setPending: (key: string | null) => {
18
+ shortcutsUi.pending = key;
19
+ },
20
+ };
package/stores/tips.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { proxy } from "valtio";
2
+
3
+ // Whether the Tip of the Day dialog is open, so the sidebar icon can open
4
+ // the one dialog mounted near the top of the app.
5
+ export const tipsStore = proxy({ open: false });
6
+
7
+ export const tipsActions = {
8
+ open: () => {
9
+ tipsStore.open = true;
10
+ },
11
+ close: () => {
12
+ tipsStore.open = false;
13
+ },
14
+ };
@@ -1,286 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useEffect, useCallback, useRef } from "react";
4
- import {
5
- Dialog,
6
- DialogContent,
7
- DialogHeader,
8
- DialogTitle,
9
- } from "@/components/ui/dialog";
10
- import { Input } from "@/components/ui/input";
11
- import { cn } from "@/lib/utils";
12
- import { Terminal, GitBranch, Clock, Check } from "lucide-react";
13
- import type { Session } from "@/lib/db";
14
- import { CodeSearchResults } from "@/components/CodeSearch/CodeSearchResults";
15
- import { useRipgrepAvailable } from "@/data/code-search";
16
-
17
- interface QuickSwitcherProps {
18
- sessions: Session[];
19
- open: boolean;
20
- onOpenChange: (open: boolean) => void;
21
- onSelectSession: (sessionId: string) => void;
22
- onSelectFile?: (file: string, line: number) => void;
23
- currentSessionId?: string;
24
- activeSessionWorkingDir?: string;
25
- }
26
-
27
- /**
28
- * Quick session switcher with search
29
- * Triggered by Cmd+K or button tap
30
- */
31
- export function QuickSwitcher({
32
- sessions,
33
- open,
34
- onOpenChange,
35
- onSelectSession,
36
- onSelectFile,
37
- currentSessionId,
38
- activeSessionWorkingDir,
39
- }: QuickSwitcherProps) {
40
- const [chosenMode, setMode] = useState<"sessions" | "code">("sessions");
41
- const [query, setQueryState] = useState("");
42
- const [selectedIndex, setSelectedIndex] = useState(0);
43
- const inputRef = useRef<HTMLInputElement>(null);
44
-
45
- // Check if ripgrep is available
46
- const { data: ripgrepAvailable } = useRipgrepAvailable();
47
-
48
- // Code search needs ripgrep
49
- const mode = ripgrepAvailable === false ? "sessions" : chosenMode;
50
-
51
- const setQuery = (next: string) => {
52
- setQueryState(next);
53
- setSelectedIndex(0);
54
- };
55
-
56
- // Reset state when dialog opens
57
- const [wasOpen, setWasOpen] = useState(open);
58
- if (open !== wasOpen) {
59
- setWasOpen(open);
60
- if (open) {
61
- setMode("sessions");
62
- setQueryState("");
63
- setSelectedIndex(0);
64
- }
65
- }
66
-
67
- // Filter sessions based on search query
68
- const filteredSessions = sessions.filter((session) => {
69
- if (!query) return true;
70
- const q = query.toLowerCase();
71
- return (
72
- session.name?.toLowerCase().includes(q) ||
73
- session.working_directory?.toLowerCase().includes(q) ||
74
- session.agent_type?.toLowerCase().includes(q)
75
- );
76
- });
77
-
78
- useEffect(() => {
79
- if (!open) return;
80
- const t = setTimeout(() => inputRef.current?.focus(), 50);
81
- return () => clearTimeout(t);
82
- }, [open]);
83
-
84
- // Handle keyboard navigation
85
- const handleKeyDown = useCallback(
86
- (e: React.KeyboardEvent) => {
87
- switch (e.key) {
88
- case "ArrowDown":
89
- e.preventDefault();
90
- setSelectedIndex((prev) =>
91
- Math.min(prev + 1, filteredSessions.length - 1)
92
- );
93
- break;
94
- case "ArrowUp":
95
- e.preventDefault();
96
- setSelectedIndex((prev) => Math.max(prev - 1, 0));
97
- break;
98
- case "Enter":
99
- e.preventDefault();
100
- if (filteredSessions[selectedIndex]) {
101
- onSelectSession(filteredSessions[selectedIndex].id);
102
- onOpenChange(false);
103
- }
104
- break;
105
- case "Escape":
106
- e.preventDefault();
107
- onOpenChange(false);
108
- break;
109
- }
110
- },
111
- [filteredSessions, selectedIndex, onSelectSession, onOpenChange]
112
- );
113
-
114
- // Format relative time
115
- const formatTime = (dateStr: string | null) => {
116
- if (!dateStr) return "";
117
- const now = new Date();
118
- const date = new Date(dateStr);
119
- const diff = now.getTime() - date.getTime();
120
- const mins = Math.floor(diff / 60000);
121
- if (mins < 1) return "just now";
122
- if (mins < 60) return `${mins}m ago`;
123
- const hours = Math.floor(mins / 60);
124
- if (hours < 24) return `${hours}h ago`;
125
- return `${Math.floor(hours / 24)}d ago`;
126
- };
127
-
128
- // Handle file selection from code search
129
- const handleSelectFile = useCallback(
130
- (file: string, line: number) => {
131
- onOpenChange(false);
132
- onSelectFile?.(file, line);
133
- },
134
- [onOpenChange, onSelectFile]
135
- );
136
-
137
- return (
138
- <Dialog open={open} onOpenChange={onOpenChange}>
139
- <DialogContent
140
- showCloseButton={false}
141
- className="gap-0 overflow-hidden p-0 sm:max-w-md"
142
- >
143
- <DialogHeader className="sr-only">
144
- <DialogTitle>Switch Session / Search Code</DialogTitle>
145
- </DialogHeader>
146
-
147
- {/* Mode Toggle - only show if ripgrep is available */}
148
- {ripgrepAvailable === true && (
149
- <div className="border-border flex gap-2 border-b p-2">
150
- <button
151
- onClick={() => setMode("sessions")}
152
- className={cn(
153
- "rounded-full px-3 py-1 text-sm transition-colors",
154
- mode === "sessions"
155
- ? "bg-primary text-primary-foreground"
156
- : "hover:bg-accent"
157
- )}
158
- >
159
- Sessions
160
- </button>
161
- <button
162
- onClick={() => setMode("code")}
163
- className={cn(
164
- "rounded-full px-3 py-1 text-sm transition-colors",
165
- mode === "code"
166
- ? "bg-primary text-primary-foreground"
167
- : "hover:bg-accent"
168
- )}
169
- >
170
- Code Search
171
- </button>
172
- </div>
173
- )}
174
-
175
- {/* Search Input */}
176
- <div className="border-border border-b p-3">
177
- <Input
178
- ref={inputRef}
179
- placeholder={
180
- mode === "sessions" || !ripgrepAvailable
181
- ? "Search sessions..."
182
- : "Search code (min 3 chars)..."
183
- }
184
- value={query}
185
- onChange={(e) => setQuery(e.target.value)}
186
- onKeyDown={mode === "sessions" ? handleKeyDown : undefined}
187
- className="h-10"
188
- />
189
- </div>
190
-
191
- {/* Content */}
192
- <div className="max-h-[300px] overflow-y-auto py-2">
193
- {mode === "sessions" ? (
194
- filteredSessions.length === 0 ? (
195
- <div className="text-muted-foreground px-4 py-8 text-center text-sm">
196
- No sessions found
197
- </div>
198
- ) : (
199
- filteredSessions.map((session, index) => {
200
- const isCurrent = session.id === currentSessionId;
201
- return (
202
- <button
203
- key={session.id}
204
- onClick={() => {
205
- onSelectSession(session.id);
206
- onOpenChange(false);
207
- }}
208
- className={cn(
209
- "flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors",
210
- index === selectedIndex
211
- ? "bg-accent"
212
- : "hover:bg-accent/50",
213
- isCurrent && "bg-primary/10"
214
- )}
215
- >
216
- {/* Icon */}
217
- <div
218
- className={cn(
219
- "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md",
220
- session.worktree_path
221
- ? "bg-purple-500/20 text-purple-400"
222
- : "bg-emerald-500/20 text-emerald-400"
223
- )}
224
- >
225
- {session.worktree_path ? (
226
- <GitBranch className="h-4 w-4" />
227
- ) : (
228
- <Terminal className="h-4 w-4" />
229
- )}
230
- </div>
231
-
232
- {/* Content */}
233
- <div className="min-w-0 flex-1">
234
- <div className="flex items-center gap-2">
235
- <span className="truncate font-medium">
236
- {session.name || "Unnamed Session"}
237
- </span>
238
- {isCurrent && (
239
- <Check className="text-primary h-3.5 w-3.5 flex-shrink-0" />
240
- )}
241
- </div>
242
- <div className="text-muted-foreground flex items-center gap-2 text-xs">
243
- <span className="truncate">
244
- {session.working_directory?.split("/").pop() || "~"}
245
- </span>
246
- <span>•</span>
247
- <span className="capitalize">
248
- {session.agent_type || "claude"}
249
- </span>
250
- </div>
251
- </div>
252
-
253
- {/* Time */}
254
- <div className="text-muted-foreground flex flex-shrink-0 items-center gap-1 text-xs">
255
- <Clock className="h-3 w-3" />
256
- <span>{formatTime(session.updated_at)}</span>
257
- </div>
258
- </button>
259
- );
260
- })
261
- )
262
- ) : (
263
- <CodeSearchResults
264
- workingDirectory={activeSessionWorkingDir || "~"}
265
- query={query}
266
- onSelectFile={handleSelectFile}
267
- />
268
- )}
269
- </div>
270
-
271
- {/* Footer Hint */}
272
- <div className="border-border text-muted-foreground flex items-center gap-4 border-t px-4 py-2 text-xs">
273
- <span>
274
- <kbd className="bg-muted rounded px-1.5 py-0.5">↑↓</kbd> navigate
275
- </span>
276
- <span>
277
- <kbd className="bg-muted rounded px-1.5 py-0.5">↵</kbd> select
278
- </span>
279
- <span>
280
- <kbd className="bg-muted rounded px-1.5 py-0.5">esc</kbd> close
281
- </span>
282
- </div>
283
- </DialogContent>
284
- </Dialog>
285
- );
286
- }