@saadnvd1/agent-os 0.3.799 → 0.3.810
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/api/chat-views/[sessionId]/route.ts +19 -0
- package/app/api/chat-views/route.test.ts +79 -0
- package/app/api/chat-views/route.ts +8 -0
- package/app/page.tsx +17 -14
- package/components/Chat/ChatPanel.tsx +46 -59
- package/components/Chat/NewDivider.tsx +15 -0
- package/components/Chat/session-link.ts +7 -4
- package/components/Chat/useFind.ts +50 -1
- package/components/Chat/usePosition.ts +363 -0
- package/components/CodeSearchDialog.tsx +90 -0
- package/components/CommandPalette/CommandsBody.tsx +12 -4
- package/components/CommandPalette/useAppCommands.ts +29 -11
- package/components/DevServers/ServerLogsModal.tsx +24 -21
- package/components/Media/MediaViewer.tsx +111 -0
- package/components/Media/viewer.test.ts +26 -0
- package/components/Media/viewer.ts +23 -0
- package/components/Nav/HistoryButtons.tsx +128 -0
- package/components/Pane/MobileTabBar.tsx +3 -0
- package/components/Pane/index.tsx +2 -2
- package/components/SessionList/useSessionSwitching.ts +3 -2
- package/components/Shortcuts/PendingKeys.tsx +19 -0
- package/components/Shortcuts/ShortcutsButton.tsx +22 -0
- package/components/Shortcuts/ShortcutsDialog.tsx +122 -0
- package/components/SidebarFooter.tsx +2 -0
- package/components/Terminal/hooks/useTerminalConnection.ts +4 -3
- package/components/Tips/TipDialog.tsx +1 -1
- package/components/Tips/TipMedia.tsx +38 -3
- package/components/Updates/{UpdateBanner.test.ts → UpdateToast.test.ts} +158 -8
- package/components/Updates/{UpdateBanner.tsx → UpdateToast.tsx} +119 -89
- package/components/Updates/index.ts +1 -1
- package/components/views/DesktopView.tsx +9 -11
- package/components/views/MobileView.tsx +4 -10
- package/components/views/lazy.ts +2 -2
- package/content/tips.json +146 -61
- package/content/whats-new.json +48 -0
- package/data/chat-views/index.ts +2 -0
- package/data/chat-views/keys.ts +3 -0
- package/data/chat-views/queries.ts +34 -0
- package/data/sessions/pin.ts +0 -1
- package/hooks/useChatViewsSync.ts +12 -0
- package/hooks/useNavActions.ts +104 -0
- package/hooks/useNavKeys.ts +105 -0
- package/hooks/useSessionUrl.ts +23 -5
- package/lib/bus/index.ts +4 -2
- package/lib/bus/role-alias.ts +4 -2
- package/lib/bus/targets.ts +2 -1
- package/lib/chat/escape.ts +8 -4
- package/lib/chat-views/body.ts +41 -0
- package/lib/chat-views/store.ts +67 -0
- package/lib/chat-views/types.ts +20 -0
- package/lib/db/migrations/20261031T1500_chat_views.ts +30 -0
- package/lib/keybindings/focus.ts +17 -0
- package/lib/keybindings/keymap.ts +120 -0
- package/lib/keybindings/sequence.ts +64 -0
- package/lib/live/service.ts +1 -1
- package/lib/nav/history-mirror.ts +59 -0
- package/lib/nav/history-writer.ts +109 -0
- package/lib/nav/position.ts +67 -0
- package/lib/nav/recent.ts +34 -0
- package/lib/orchestrator/armed.ts +5 -0
- package/lib/palette/sections.ts +12 -6
- package/lib/session-url.ts +11 -5
- package/package.json +1 -1
- package/public/tips/chat-access-picker-dark.webp +0 -0
- package/public/tips/chat-access-picker.webp +0 -0
- package/public/tips/chat-attach-files-poster.webp +0 -0
- package/public/tips/chat-attach-files.mp4 +0 -0
- package/public/tips/chat-bookmark-dark.webp +0 -0
- package/public/tips/chat-bookmark.webp +0 -0
- package/public/tips/chat-plan-mode-dark.webp +0 -0
- package/public/tips/chat-plan-mode.webp +0 -0
- package/public/tips/chat-search-poster.webp +0 -0
- package/public/tips/chat-search.mp4 +0 -0
- package/public/tips/chat-tab-suggestion-poster.webp +0 -0
- package/public/tips/chat-tab-suggestion.mp4 +0 -0
- package/public/tips/chat-terminal-handoff-poster.webp +0 -0
- package/public/tips/chat-terminal-handoff.mp4 +0 -0
- package/public/tips/chat-undo-dark.webp +0 -0
- package/public/tips/chat-undo.webp +0 -0
- package/public/tips/export-ai-dark.webp +0 -0
- package/public/tips/export-ai.webp +0 -0
- package/public/tips/net-messages-dark.webp +0 -0
- package/public/tips/net-messages.webp +0 -0
- package/public/tips/recipe-save-dark.webp +0 -0
- package/public/tips/recipe-save.webp +0 -0
- package/public/tips/schedule-first-dark.webp +0 -0
- package/public/tips/schedule-first.webp +0 -0
- package/public/tips/share-check-dark.webp +0 -0
- package/public/tips/share-check.webp +0 -0
- package/public/tips/sidebar-draft-poster.webp +0 -0
- package/public/tips/sidebar-draft.mp4 +0 -0
- package/public/tips/sidebar-groups-dark.webp +0 -0
- package/public/tips/sidebar-groups.webp +0 -0
- package/public/tips/sidebar-load-dark.webp +0 -0
- package/public/tips/sidebar-load.webp +0 -0
- package/public/tips/sidebar-snooze-dark.webp +0 -0
- package/public/tips/sidebar-snooze.webp +0 -0
- package/public/tips/tasks-plan-pieces-dark.webp +0 -0
- package/public/tips/tasks-plan-pieces.webp +0 -0
- package/public/tips/tasks-sign-off-dark.webp +0 -0
- package/public/tips/tasks-sign-off.webp +0 -0
- package/public/tips/tasks-stacks-dark.webp +0 -0
- package/public/tips/tasks-stacks.webp +0 -0
- package/scripts/check +2 -2
- package/stores/chatJump.ts +2 -6
- package/stores/chatPositions.ts +65 -0
- package/stores/navHistory.ts +98 -0
- package/stores/shortcutsUi.ts +20 -0
- package/components/QuickSwitcher.tsx +0 -286
package/content/tips.json
CHANGED
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
"file": "chat"
|
|
19
19
|
},
|
|
20
20
|
"added": "2026-10-31",
|
|
21
|
-
"
|
|
22
|
-
"kind": "
|
|
23
|
-
"
|
|
21
|
+
"media": {
|
|
22
|
+
"kind": "image",
|
|
23
|
+
"src": "/tips/chat-plan-mode.webp",
|
|
24
|
+
"srcDark": "/tips/chat-plan-mode-dark.webp",
|
|
25
|
+
"alt": "The chat composer with Plan switched on, the agent's plan shown above it.",
|
|
26
|
+
"width": 768,
|
|
27
|
+
"height": 481
|
|
24
28
|
}
|
|
25
29
|
},
|
|
26
30
|
{
|
|
@@ -31,9 +35,14 @@
|
|
|
31
35
|
"file": "chat"
|
|
32
36
|
},
|
|
33
37
|
"added": "2026-10-31",
|
|
34
|
-
"
|
|
35
|
-
"kind": "
|
|
36
|
-
"
|
|
38
|
+
"media": {
|
|
39
|
+
"kind": "video",
|
|
40
|
+
"src": "/tips/chat-attach-files.mp4",
|
|
41
|
+
"poster": "/tips/chat-attach-files-poster.webp",
|
|
42
|
+
"alt": "Two files, a PDF and a CSV, attached to the composer as chips before sending.",
|
|
43
|
+
"width": 768,
|
|
44
|
+
"height": 480,
|
|
45
|
+
"durationMs": 7300
|
|
37
46
|
}
|
|
38
47
|
},
|
|
39
48
|
{
|
|
@@ -48,7 +57,7 @@
|
|
|
48
57
|
"kind": "video",
|
|
49
58
|
"src": "/tips/chat-slash-menu.mp4",
|
|
50
59
|
"poster": "/tips/chat-slash-menu-poster.webp",
|
|
51
|
-
"alt": "Typing a slash in the composer opens a
|
|
60
|
+
"alt": "Typing a slash in the composer opens a filtered list of commands and skills.",
|
|
52
61
|
"width": 768,
|
|
53
62
|
"height": 480,
|
|
54
63
|
"durationMs": 6367
|
|
@@ -62,9 +71,14 @@
|
|
|
62
71
|
"file": "chat"
|
|
63
72
|
},
|
|
64
73
|
"added": "2026-10-31",
|
|
65
|
-
"
|
|
66
|
-
"kind": "
|
|
67
|
-
"
|
|
74
|
+
"media": {
|
|
75
|
+
"kind": "video",
|
|
76
|
+
"src": "/tips/chat-tab-suggestion.mp4",
|
|
77
|
+
"poster": "/tips/chat-tab-suggestion-poster.webp",
|
|
78
|
+
"alt": "A grey suggested next message appears in the composer and Tab accepts it.",
|
|
79
|
+
"width": 768,
|
|
80
|
+
"height": 480,
|
|
81
|
+
"durationMs": 5233
|
|
68
82
|
}
|
|
69
83
|
},
|
|
70
84
|
{
|
|
@@ -75,9 +89,13 @@
|
|
|
75
89
|
"file": "chat"
|
|
76
90
|
},
|
|
77
91
|
"added": "2026-10-31",
|
|
78
|
-
"
|
|
79
|
-
"kind": "
|
|
80
|
-
"
|
|
92
|
+
"media": {
|
|
93
|
+
"kind": "image",
|
|
94
|
+
"src": "/tips/chat-access-picker.webp",
|
|
95
|
+
"srcDark": "/tips/chat-access-picker-dark.webp",
|
|
96
|
+
"alt": "The access picker open in the composer, listing Ask first, Accept edits and Full access.",
|
|
97
|
+
"width": 768,
|
|
98
|
+
"height": 480
|
|
81
99
|
}
|
|
82
100
|
},
|
|
83
101
|
{
|
|
@@ -88,9 +106,13 @@
|
|
|
88
106
|
"file": "chat"
|
|
89
107
|
},
|
|
90
108
|
"added": "2026-10-31",
|
|
91
|
-
"
|
|
92
|
-
"kind": "
|
|
93
|
-
"
|
|
109
|
+
"media": {
|
|
110
|
+
"kind": "image",
|
|
111
|
+
"src": "/tips/chat-undo.webp",
|
|
112
|
+
"srcDark": "/tips/chat-undo-dark.webp",
|
|
113
|
+
"alt": "The undo button on a user message in a chat transcript.",
|
|
114
|
+
"width": 768,
|
|
115
|
+
"height": 480
|
|
94
116
|
}
|
|
95
117
|
},
|
|
96
118
|
{
|
|
@@ -101,9 +123,14 @@
|
|
|
101
123
|
"file": "chat"
|
|
102
124
|
},
|
|
103
125
|
"added": "2026-10-31",
|
|
104
|
-
"
|
|
105
|
-
"kind": "
|
|
106
|
-
"
|
|
126
|
+
"media": {
|
|
127
|
+
"kind": "video",
|
|
128
|
+
"src": "/tips/chat-terminal-handoff.mp4",
|
|
129
|
+
"poster": "/tips/chat-terminal-handoff-poster.webp",
|
|
130
|
+
"alt": "The Chat and Terminal switch flipping a session from the chat view to its terminal.",
|
|
131
|
+
"width": 768,
|
|
132
|
+
"height": 480,
|
|
133
|
+
"durationMs": 5967
|
|
107
134
|
}
|
|
108
135
|
},
|
|
109
136
|
{
|
|
@@ -115,9 +142,13 @@
|
|
|
115
142
|
},
|
|
116
143
|
"when": ["no-bookmarks"],
|
|
117
144
|
"added": "2026-10-31",
|
|
118
|
-
"
|
|
119
|
-
"kind": "
|
|
120
|
-
"
|
|
145
|
+
"media": {
|
|
146
|
+
"kind": "image",
|
|
147
|
+
"src": "/tips/chat-bookmark.webp",
|
|
148
|
+
"srcDark": "/tips/chat-bookmark-dark.webp",
|
|
149
|
+
"alt": "A bookmarked message with the star filled in a chat transcript.",
|
|
150
|
+
"width": 768,
|
|
151
|
+
"height": 481
|
|
121
152
|
}
|
|
122
153
|
},
|
|
123
154
|
{
|
|
@@ -128,9 +159,14 @@
|
|
|
128
159
|
"file": "chat"
|
|
129
160
|
},
|
|
130
161
|
"added": "2026-10-31",
|
|
131
|
-
"
|
|
132
|
-
"kind": "
|
|
133
|
-
"
|
|
162
|
+
"media": {
|
|
163
|
+
"kind": "video",
|
|
164
|
+
"src": "/tips/chat-search.mp4",
|
|
165
|
+
"poster": "/tips/chat-search-poster.webp",
|
|
166
|
+
"alt": "The command palette searching conversations for a phrase and listing matching sessions.",
|
|
167
|
+
"width": 768,
|
|
168
|
+
"height": 480,
|
|
169
|
+
"durationMs": 8000
|
|
134
170
|
}
|
|
135
171
|
},
|
|
136
172
|
{
|
|
@@ -142,9 +178,14 @@
|
|
|
142
178
|
"anchor": "new-sessions"
|
|
143
179
|
},
|
|
144
180
|
"added": "2026-10-31",
|
|
145
|
-
"
|
|
146
|
-
"kind": "
|
|
147
|
-
"
|
|
181
|
+
"media": {
|
|
182
|
+
"kind": "video",
|
|
183
|
+
"src": "/tips/sidebar-draft.mp4",
|
|
184
|
+
"poster": "/tips/sidebar-draft-poster.webp",
|
|
185
|
+
"alt": "A new draft session opened from the New menu with a prompt typed into its composer.",
|
|
186
|
+
"width": 768,
|
|
187
|
+
"height": 480,
|
|
188
|
+
"durationMs": 7667
|
|
148
189
|
}
|
|
149
190
|
},
|
|
150
191
|
{
|
|
@@ -176,9 +217,13 @@
|
|
|
176
217
|
"anchor": "the-sidebar"
|
|
177
218
|
},
|
|
178
219
|
"added": "2026-10-31",
|
|
179
|
-
"
|
|
180
|
-
"kind": "
|
|
181
|
-
"
|
|
220
|
+
"media": {
|
|
221
|
+
"kind": "image",
|
|
222
|
+
"src": "/tips/sidebar-snooze.webp",
|
|
223
|
+
"srcDark": "/tips/sidebar-snooze-dark.webp",
|
|
224
|
+
"alt": "A session row's menu open with the snooze options in the sidebar.",
|
|
225
|
+
"width": 768,
|
|
226
|
+
"height": 480
|
|
182
227
|
}
|
|
183
228
|
},
|
|
184
229
|
{
|
|
@@ -191,9 +236,13 @@
|
|
|
191
236
|
},
|
|
192
237
|
"when": ["no-groups"],
|
|
193
238
|
"added": "2026-10-31",
|
|
194
|
-
"
|
|
195
|
-
"kind": "
|
|
196
|
-
"
|
|
239
|
+
"media": {
|
|
240
|
+
"kind": "image",
|
|
241
|
+
"src": "/tips/sidebar-groups.webp",
|
|
242
|
+
"srcDark": "/tips/sidebar-groups-dark.webp",
|
|
243
|
+
"alt": "The sidebar with sessions grouped under a named group header.",
|
|
244
|
+
"width": 768,
|
|
245
|
+
"height": 480
|
|
197
246
|
}
|
|
198
247
|
},
|
|
199
248
|
{
|
|
@@ -205,9 +254,13 @@
|
|
|
205
254
|
"anchor": "machine-load"
|
|
206
255
|
},
|
|
207
256
|
"added": "2026-10-31",
|
|
208
|
-
"
|
|
209
|
-
"kind": "
|
|
210
|
-
"
|
|
257
|
+
"media": {
|
|
258
|
+
"kind": "image",
|
|
259
|
+
"src": "/tips/sidebar-load.webp",
|
|
260
|
+
"srcDark": "/tips/sidebar-load-dark.webp",
|
|
261
|
+
"alt": "The sidebar load indicator showing which sessions use the most CPU and memory.",
|
|
262
|
+
"width": 768,
|
|
263
|
+
"height": 480
|
|
211
264
|
}
|
|
212
265
|
},
|
|
213
266
|
{
|
|
@@ -218,9 +271,13 @@
|
|
|
218
271
|
"file": "tasks"
|
|
219
272
|
},
|
|
220
273
|
"added": "2026-10-31",
|
|
221
|
-
"
|
|
222
|
-
"kind": "
|
|
223
|
-
"
|
|
274
|
+
"media": {
|
|
275
|
+
"kind": "image",
|
|
276
|
+
"src": "/tips/tasks-sign-off.webp",
|
|
277
|
+
"srcDark": "/tips/tasks-sign-off-dark.webp",
|
|
278
|
+
"alt": "A task in review with checks passing and a Sign off action.",
|
|
279
|
+
"width": 768,
|
|
280
|
+
"height": 480
|
|
224
281
|
}
|
|
225
282
|
},
|
|
226
283
|
{
|
|
@@ -250,9 +307,13 @@
|
|
|
250
307
|
"anchor": "plans-in-pieces"
|
|
251
308
|
},
|
|
252
309
|
"added": "2026-10-31",
|
|
253
|
-
"
|
|
254
|
-
"kind": "
|
|
255
|
-
"
|
|
310
|
+
"media": {
|
|
311
|
+
"kind": "image",
|
|
312
|
+
"src": "/tips/tasks-plan-pieces.webp",
|
|
313
|
+
"srcDark": "/tips/tasks-plan-pieces-dark.webp",
|
|
314
|
+
"alt": "A task plan split into pieces awaiting approval.",
|
|
315
|
+
"width": 768,
|
|
316
|
+
"height": 480
|
|
256
317
|
}
|
|
257
318
|
},
|
|
258
319
|
{
|
|
@@ -274,9 +335,13 @@
|
|
|
274
335
|
"anchor": "stacks"
|
|
275
336
|
},
|
|
276
337
|
"added": "2026-10-31",
|
|
277
|
-
"
|
|
278
|
-
"kind": "
|
|
279
|
-
"
|
|
338
|
+
"media": {
|
|
339
|
+
"kind": "image",
|
|
340
|
+
"src": "/tips/tasks-stacks.webp",
|
|
341
|
+
"srcDark": "/tips/tasks-stacks-dark.webp",
|
|
342
|
+
"alt": "A stack of pull requests listed in order under one stack.",
|
|
343
|
+
"width": 768,
|
|
344
|
+
"height": 480
|
|
280
345
|
}
|
|
281
346
|
},
|
|
282
347
|
{
|
|
@@ -289,9 +354,13 @@
|
|
|
289
354
|
},
|
|
290
355
|
"when": ["no-schedules"],
|
|
291
356
|
"added": "2026-10-31",
|
|
292
|
-
"
|
|
293
|
-
"kind": "
|
|
294
|
-
"
|
|
357
|
+
"media": {
|
|
358
|
+
"kind": "image",
|
|
359
|
+
"src": "/tips/schedule-first.webp",
|
|
360
|
+
"srcDark": "/tips/schedule-first-dark.webp",
|
|
361
|
+
"alt": "The schedule form with a prompt, a repeat interval and a time zone.",
|
|
362
|
+
"width": 768,
|
|
363
|
+
"height": 480
|
|
295
364
|
}
|
|
296
365
|
},
|
|
297
366
|
{
|
|
@@ -314,9 +383,13 @@
|
|
|
314
383
|
},
|
|
315
384
|
"when": ["no-recipes"],
|
|
316
385
|
"added": "2026-10-31",
|
|
317
|
-
"
|
|
318
|
-
"kind": "
|
|
319
|
-
"
|
|
386
|
+
"media": {
|
|
387
|
+
"kind": "image",
|
|
388
|
+
"src": "/tips/recipe-save.webp",
|
|
389
|
+
"srcDark": "/tips/recipe-save-dark.webp",
|
|
390
|
+
"alt": "The menu action that saves a conversation as a recipe.",
|
|
391
|
+
"width": 768,
|
|
392
|
+
"height": 480
|
|
320
393
|
}
|
|
321
394
|
},
|
|
322
395
|
{
|
|
@@ -346,9 +419,13 @@
|
|
|
346
419
|
"file": "agent-network"
|
|
347
420
|
},
|
|
348
421
|
"added": "2026-10-31",
|
|
349
|
-
"
|
|
350
|
-
"kind": "
|
|
351
|
-
"
|
|
422
|
+
"media": {
|
|
423
|
+
"kind": "image",
|
|
424
|
+
"src": "/tips/net-messages.webp",
|
|
425
|
+
"srcDark": "/tips/net-messages-dark.webp",
|
|
426
|
+
"alt": "A session sending and receiving messages from another session.",
|
|
427
|
+
"width": 768,
|
|
428
|
+
"height": 480
|
|
352
429
|
}
|
|
353
430
|
},
|
|
354
431
|
{
|
|
@@ -394,9 +471,13 @@
|
|
|
394
471
|
},
|
|
395
472
|
"when": ["no-shares"],
|
|
396
473
|
"added": "2026-10-31",
|
|
397
|
-
"
|
|
398
|
-
"kind": "
|
|
399
|
-
"
|
|
474
|
+
"media": {
|
|
475
|
+
"kind": "image",
|
|
476
|
+
"src": "/tips/share-check.webp",
|
|
477
|
+
"srcDark": "/tips/share-check-dark.webp",
|
|
478
|
+
"alt": "The share dialog preview showing secrets, addresses and emails masked.",
|
|
479
|
+
"width": 768,
|
|
480
|
+
"height": 481
|
|
400
481
|
}
|
|
401
482
|
},
|
|
402
483
|
{
|
|
@@ -417,9 +498,13 @@
|
|
|
417
498
|
"file": "export"
|
|
418
499
|
},
|
|
419
500
|
"added": "2026-10-31",
|
|
420
|
-
"
|
|
421
|
-
"kind": "
|
|
422
|
-
"
|
|
501
|
+
"media": {
|
|
502
|
+
"kind": "image",
|
|
503
|
+
"src": "/tips/export-ai.webp",
|
|
504
|
+
"srcDark": "/tips/export-ai-dark.webp",
|
|
505
|
+
"alt": "The export menu with an option to copy the conversation for another AI.",
|
|
506
|
+
"width": 768,
|
|
507
|
+
"height": 481
|
|
423
508
|
}
|
|
424
509
|
},
|
|
425
510
|
{
|
package/content/whats-new.json
CHANGED
|
@@ -1817,6 +1817,54 @@
|
|
|
1817
1817
|
"area": "Chat",
|
|
1818
1818
|
"text": "Chats now fill the screen with earlier messages on open instead of showing a nearly empty view with a \"Show earlier messages\" button.",
|
|
1819
1819
|
"url": "https://github.com/saadnvd1/agent-os/pull/533"
|
|
1820
|
+
},
|
|
1821
|
+
{
|
|
1822
|
+
"id": 536,
|
|
1823
|
+
"date": "2026-10-31",
|
|
1824
|
+
"kind": "new",
|
|
1825
|
+
"area": "AgentOS",
|
|
1826
|
+
"text": "Tip of the Day now shows pictures and short clips that demonstrate each tip.",
|
|
1827
|
+
"url": "https://github.com/saadnvd1/agent-os/pull/536"
|
|
1828
|
+
},
|
|
1829
|
+
{
|
|
1830
|
+
"id": 537,
|
|
1831
|
+
"date": "2026-10-31",
|
|
1832
|
+
"kind": "new",
|
|
1833
|
+
"area": "AgentOS",
|
|
1834
|
+
"text": "Chats now reopen where you left off, with back and forward buttons, a quick chat switcher and a keyboard shortcuts list.",
|
|
1835
|
+
"url": "https://github.com/saadnvd1/agent-os/pull/537"
|
|
1836
|
+
},
|
|
1837
|
+
{
|
|
1838
|
+
"id": 539,
|
|
1839
|
+
"date": "2026-10-31",
|
|
1840
|
+
"kind": "fixed",
|
|
1841
|
+
"area": "AgentOS",
|
|
1842
|
+
"text": "Holding Shift with a letter no longer triggers the shortcut meant for that letter on its own.",
|
|
1843
|
+
"url": "https://github.com/saadnvd1/agent-os/pull/539"
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
"id": 541,
|
|
1847
|
+
"date": "2026-10-31",
|
|
1848
|
+
"kind": "new",
|
|
1849
|
+
"area": "Chat",
|
|
1850
|
+
"text": "Pressing Esc now takes you out of the message box when there is nothing else for it to do.",
|
|
1851
|
+
"url": "https://github.com/saadnvd1/agent-os/pull/541"
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
"id": 542,
|
|
1855
|
+
"date": "2026-10-31",
|
|
1856
|
+
"kind": "improved",
|
|
1857
|
+
"area": "AgentOS",
|
|
1858
|
+
"text": "Tip of the Day images are larger on desktop, and you can tap one to see it full screen.",
|
|
1859
|
+
"url": "https://github.com/saadnvd1/agent-os/pull/542"
|
|
1860
|
+
},
|
|
1861
|
+
{
|
|
1862
|
+
"id": 543,
|
|
1863
|
+
"date": "2026-10-31",
|
|
1864
|
+
"kind": "improved",
|
|
1865
|
+
"area": "Updates",
|
|
1866
|
+
"text": "Update notices now appear as a small pop-up instead of a bar across the top of the app.",
|
|
1867
|
+
"url": "https://github.com/saadnvd1/agent-os/pull/543"
|
|
1820
1868
|
}
|
|
1821
1869
|
]
|
|
1822
1870
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import type { ChatView, ChatViewInput } from "@/lib/chat-views/types";
|
|
3
|
+
import { chatViewKeys } from "./keys";
|
|
4
|
+
|
|
5
|
+
export type { ChatView } from "@/lib/chat-views/types";
|
|
6
|
+
|
|
7
|
+
/** Where each chat was last read and when, newest first. Another device's
|
|
8
|
+
* reading shows up when this tab is focused again. */
|
|
9
|
+
export function useChatViews() {
|
|
10
|
+
return useQuery({
|
|
11
|
+
queryKey: chatViewKeys.all,
|
|
12
|
+
queryFn: async (): Promise<ChatView[]> => {
|
|
13
|
+
const res = await fetch("/api/chat-views");
|
|
14
|
+
if (!res.ok) throw new Error("Failed to fetch chat views");
|
|
15
|
+
return (await res.json()).views;
|
|
16
|
+
},
|
|
17
|
+
staleTime: 30_000,
|
|
18
|
+
refetchOnWindowFocus: true,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Saves where the reader is, or (null) that they just opened the chat.
|
|
23
|
+
* keepalive lets it finish while the page unloads. */
|
|
24
|
+
export function saveChatView(
|
|
25
|
+
sessionId: string,
|
|
26
|
+
input: (Omit<ChatViewInput, "advance"> & { advance?: boolean }) | null
|
|
27
|
+
): void {
|
|
28
|
+
void fetch(`/api/chat-views/${encodeURIComponent(sessionId)}`, {
|
|
29
|
+
method: "PUT",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
body: JSON.stringify(input ?? { viewed: true }),
|
|
32
|
+
keepalive: true,
|
|
33
|
+
}).catch(() => {});
|
|
34
|
+
}
|
package/data/sessions/pin.ts
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useChatViews } from "@/data/chat-views";
|
|
3
|
+
import { chatPositionsActions } from "@/stores/chatPositions";
|
|
4
|
+
|
|
5
|
+
/** Hands the server's reading positions to the page; chats wait for them to open where they were left. */
|
|
6
|
+
export function useChatViewsSync(): void {
|
|
7
|
+
const { data, isError } = useChatViews();
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (data) chatPositionsActions.hydrate(data);
|
|
10
|
+
else if (isError) chatPositionsActions.giveUp();
|
|
11
|
+
}, [data, isError]);
|
|
12
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Keyboard, MessageSquare, Repeat } from "lucide-react";
|
|
4
|
+
import type { Session } from "@/lib/db";
|
|
5
|
+
import { chordHint } from "@/lib/keybindings/chord";
|
|
6
|
+
import { keysOf } from "@/lib/keybindings/keymap";
|
|
7
|
+
import { previousChatId } from "@/lib/nav/recent";
|
|
8
|
+
import { useNavKeys, useSingleKeys } from "@/hooks/useNavKeys";
|
|
9
|
+
import { usePaletteCommands } from "@/hooks/usePaletteCommands";
|
|
10
|
+
import { bookmarksUiActions } from "@/stores/bookmarksUi";
|
|
11
|
+
import { chatOpened } from "@/stores/chatPositions";
|
|
12
|
+
import { newDraft } from "@/stores/drafts";
|
|
13
|
+
import { inboxUiActions } from "@/stores/inboxUi";
|
|
14
|
+
import { paletteActions, paletteUi } from "@/stores/palette";
|
|
15
|
+
import { settingsUiActions } from "@/stores/settingsUi";
|
|
16
|
+
import { shortcutsUiActions } from "@/stores/shortcutsUi";
|
|
17
|
+
|
|
18
|
+
/** The chats ⌘P, O C and the like switch between, as the palette's Sessions tab. */
|
|
19
|
+
function openSwitcher() {
|
|
20
|
+
if (paletteUi.open && paletteUi.tab === "sessions")
|
|
21
|
+
return paletteActions.setOpen(false);
|
|
22
|
+
paletteActions.open();
|
|
23
|
+
paletteActions.setTab("sessions");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* What each shortcut in keymap.ts does, and the palette commands that go
|
|
28
|
+
* with them. Opening a chat goes through `select`, so history, position and
|
|
29
|
+
* the address follow as they do for a click.
|
|
30
|
+
*/
|
|
31
|
+
export function useNavActions({
|
|
32
|
+
sessions,
|
|
33
|
+
activeSessionId,
|
|
34
|
+
select,
|
|
35
|
+
}: {
|
|
36
|
+
sessions: Session[];
|
|
37
|
+
activeSessionId: string | null | undefined;
|
|
38
|
+
select: (session: Session) => void;
|
|
39
|
+
}) {
|
|
40
|
+
const [singles, setSingles] = useSingleKeys();
|
|
41
|
+
const previous = () => {
|
|
42
|
+
const open = sessions.filter((s) => !s.archived_at);
|
|
43
|
+
const id = previousChatId(
|
|
44
|
+
open.map((s) => s.id),
|
|
45
|
+
chatOpened,
|
|
46
|
+
activeSessionId
|
|
47
|
+
);
|
|
48
|
+
const session = open.find((s) => s.id === id);
|
|
49
|
+
if (session) select(session);
|
|
50
|
+
};
|
|
51
|
+
const actions = {
|
|
52
|
+
"palette.open": () =>
|
|
53
|
+
paletteUi.open ? paletteActions.setOpen(false) : paletteActions.open(),
|
|
54
|
+
"switcher.open": openSwitcher,
|
|
55
|
+
"chat.open": openSwitcher,
|
|
56
|
+
"chat.previous": previous,
|
|
57
|
+
"chat.new": () => newDraft({ kind: "current" }),
|
|
58
|
+
"go.inbox": () => inboxUiActions.open(),
|
|
59
|
+
"go.messages": () => inboxUiActions.open("messages"),
|
|
60
|
+
"go.bookmarks": () => bookmarksUiActions.open(activeSessionId ?? null),
|
|
61
|
+
"go.settings": () => settingsUiActions.open(),
|
|
62
|
+
"help.open": shortcutsUiActions.open,
|
|
63
|
+
};
|
|
64
|
+
useNavKeys(actions);
|
|
65
|
+
usePaletteCommands("nav", [
|
|
66
|
+
{
|
|
67
|
+
id: "nav.switch",
|
|
68
|
+
title: "Switch chat",
|
|
69
|
+
group: "Go to",
|
|
70
|
+
hint: chordHint(keysOf("switcher.open")),
|
|
71
|
+
keywords: ["recent", "open chat", "session", "switcher"],
|
|
72
|
+
icon: MessageSquare,
|
|
73
|
+
run: openSwitcher,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "nav.previous",
|
|
77
|
+
title: "Previous chat",
|
|
78
|
+
group: "Go to",
|
|
79
|
+
hint: "G P",
|
|
80
|
+
keywords: ["flip", "alternate", "last", "back"],
|
|
81
|
+
icon: Repeat,
|
|
82
|
+
run: previous,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "nav.shortcuts",
|
|
86
|
+
title: "Keyboard shortcuts",
|
|
87
|
+
group: "Go to",
|
|
88
|
+
hint: "?",
|
|
89
|
+
keywords: ["keys", "help", "hotkeys"],
|
|
90
|
+
icon: Keyboard,
|
|
91
|
+
run: shortcutsUiActions.open,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "nav.single-keys",
|
|
95
|
+
title: singles
|
|
96
|
+
? "Turn off single-key shortcuts"
|
|
97
|
+
: "Turn on single-key shortcuts",
|
|
98
|
+
group: "Settings",
|
|
99
|
+
keywords: ["keyboard", "letters", "vim", "linear"],
|
|
100
|
+
icon: Keyboard,
|
|
101
|
+
run: () => setSingles(!singles),
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { useKeybindings } from "@/hooks/useKeybindings";
|
|
5
|
+
import { useStoredFlag } from "@/hooks/useStoredFlag";
|
|
6
|
+
import { isFreeTarget } from "@/lib/keybindings/focus";
|
|
7
|
+
import { KEYMAP, isSequence, keysOf } from "@/lib/keybindings/keymap";
|
|
8
|
+
import {
|
|
9
|
+
SEQUENCE_MS,
|
|
10
|
+
SequenceMatcher,
|
|
11
|
+
plainKey,
|
|
12
|
+
} from "@/lib/keybindings/sequence";
|
|
13
|
+
import { shortcutsUiActions } from "@/stores/shortcutsUi";
|
|
14
|
+
|
|
15
|
+
export const SINGLE_KEYS_FLAG = "agentos-single-key-shortcuts";
|
|
16
|
+
|
|
17
|
+
/** The single-key shortcuts' on/off switch, kept per browser. */
|
|
18
|
+
export const useSingleKeys = () => useStoredFlag(SINGLE_KEYS_FLAG, true);
|
|
19
|
+
|
|
20
|
+
export type NavActions = Record<string, () => void>;
|
|
21
|
+
|
|
22
|
+
const SEQUENCES = Object.fromEntries(
|
|
23
|
+
KEYMAP.filter((k) => k.single && isSequence(k.keys)).map((k) => [
|
|
24
|
+
k.keys,
|
|
25
|
+
k.id,
|
|
26
|
+
])
|
|
27
|
+
);
|
|
28
|
+
const BARE = Object.fromEntries(
|
|
29
|
+
KEYMAP.filter((k) => k.single && !isSequence(k.keys)).map((k) => [
|
|
30
|
+
k.keys,
|
|
31
|
+
k.id,
|
|
32
|
+
])
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const CHORD_IDS = ["switcher.open", "palette.open"];
|
|
36
|
+
|
|
37
|
+
const dialogOpen = () =>
|
|
38
|
+
!!document.querySelector('[role="dialog"][data-state="open"]');
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The app's keys by id (see keymap.ts): chords through the registry, bare
|
|
42
|
+
* keys and two-key sequences here, only while nothing is being typed into
|
|
43
|
+
* and the single-key switch is on.
|
|
44
|
+
*/
|
|
45
|
+
export function useNavKeys(actions: NavActions) {
|
|
46
|
+
const latest = useRef(actions);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
latest.current = actions;
|
|
49
|
+
});
|
|
50
|
+
const run = (id: string) => latest.current[id]?.();
|
|
51
|
+
const [singles] = useSingleKeys();
|
|
52
|
+
const singlesOn = useRef(singles);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
singlesOn.current = singles;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// The sidebar's ⌘↑/⌘↓ are bound by useSessionSwitching, from the same table.
|
|
58
|
+
useKeybindings(
|
|
59
|
+
"nav",
|
|
60
|
+
CHORD_IDS.map((id) => ({ id, keys: keysOf(id), run: () => run(id) }))
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const matcher = new SequenceMatcher(SEQUENCES);
|
|
65
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
66
|
+
const clear = () => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
matcher.reset();
|
|
69
|
+
shortcutsUiActions.setPending(null);
|
|
70
|
+
};
|
|
71
|
+
const onKey = (e: KeyboardEvent) => {
|
|
72
|
+
if (!singlesOn.current) return clear();
|
|
73
|
+
if (e.metaKey || e.ctrlKey || e.altKey || e.isComposing) return clear();
|
|
74
|
+
if (!isFreeTarget(e.target) || dialogOpen()) return clear();
|
|
75
|
+
if (e.key === "Escape") return clear();
|
|
76
|
+
if (e.key === "Shift") return;
|
|
77
|
+
const bare = plainKey(e.key, e.shiftKey);
|
|
78
|
+
if (bare === null) return clear();
|
|
79
|
+
const waiting = matcher.pending() !== null;
|
|
80
|
+
const step = matcher.feed(bare);
|
|
81
|
+
if (step.kind === "match") {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
clear();
|
|
84
|
+
run(step.id);
|
|
85
|
+
} else if (step.kind === "pending") {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
shortcutsUiActions.setPending(step.first);
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
timer = setTimeout(clear, SEQUENCE_MS);
|
|
90
|
+
} else {
|
|
91
|
+
clear();
|
|
92
|
+
const id = BARE[bare.toLowerCase()];
|
|
93
|
+
if (id && !waiting && !e.repeat) {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
run(id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
window.addEventListener("keydown", onKey, true);
|
|
100
|
+
return () => {
|
|
101
|
+
window.removeEventListener("keydown", onKey, true);
|
|
102
|
+
clear();
|
|
103
|
+
};
|
|
104
|
+
}, []);
|
|
105
|
+
}
|