@saadnvd1/agent-os 0.3.794 → 0.3.799
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/busy/route.ts +2 -2
- package/app/api/tips/route.test.ts +56 -0
- package/app/api/tips/route.ts +45 -0
- package/app/api/updates/health/route.ts +22 -0
- package/app/api/updates/route.test.ts +53 -0
- package/app/api/updates/settings/route.ts +37 -10
- package/components/Chat/ChatPanel.tsx +28 -0
- package/components/Chat/needs-more-history.test.ts +28 -0
- package/components/Chat/needs-more-history.ts +14 -0
- package/components/Recipes/TimerFields.tsx +2 -1
- package/components/Schedules/ScheduleForm.tsx +1 -1
- package/components/Settings/SettingsDialog.tsx +1 -1
- package/components/SidebarFooter.tsx +2 -0
- package/components/Tips/TipButton.tsx +39 -0
- package/components/Tips/TipDialog.tsx +148 -0
- package/components/Tips/TipHost.tsx +14 -0
- package/components/Tips/TipMedia.tsx +71 -0
- package/components/Tips/index.ts +2 -0
- package/components/Updates/AutoUpdates.tsx +141 -0
- package/components/Updates/UpdateBanner.test.ts +39 -1
- package/components/Updates/UpdateBanner.tsx +31 -5
- package/components/Updates/UpdatesSection.tsx +24 -3
- package/content/tips.json +456 -0
- package/content/whats-new.json +24 -0
- package/data/tips/index.ts +49 -0
- package/data/updates/index.ts +5 -0
- package/hooks/useReloadWhenUpdated.ts +5 -1
- package/lib/busy.ts +23 -1
- package/lib/db/migrations.ts +21 -0
- package/lib/recipes/timer.ts +3 -1
- package/lib/schedules/cron.ts +14 -5
- package/lib/schedules/store.ts +3 -2
- package/lib/security/demo.ts +2 -0
- package/lib/tasks/finish.ts +5 -5
- package/lib/tips/conditions.ts +38 -0
- package/lib/tips/index.ts +2 -0
- package/lib/tips/load.ts +26 -0
- package/lib/tips/media.ts +101 -0
- package/lib/tips/pick.ts +35 -0
- package/lib/tips/schema.ts +82 -0
- package/lib/tips/state.ts +26 -0
- package/lib/tips/view.ts +60 -0
- package/lib/updates/auto.ts +149 -0
- package/lib/updates/banner.ts +78 -3
- package/lib/updates/check.ts +64 -8
- package/lib/updates/history.ts +99 -0
- package/lib/updates/run.ts +334 -23
- package/lib/updates/settings.ts +66 -0
- package/lib/updates/view.ts +14 -3
- package/package.json +2 -2
- package/public/tips/.gitkeep +0 -0
- package/public/tips/chat-slash-menu-poster.webp +0 -0
- package/public/tips/chat-slash-menu.mp4 +0 -0
- package/scripts/autodeploy +4 -0
- package/scripts/autodeploy.test.ts +12 -0
- package/scripts/docs-links.test.ts +1 -25
- package/scripts/lib/commands.sh +32 -1
- package/scripts/lib/deps-swap.sh +8 -6
- package/scripts/lib/heading-slugs.ts +26 -0
- package/scripts/lib/update-verify.mjs +90 -0
- package/scripts/redeploy +31 -2
- package/scripts/redeploy.test.ts +94 -3
- package/scripts/tips-lint.test.ts +77 -0
- package/scripts/tips-media.ts +31 -0
- package/scripts/update-e2e.test.ts +215 -0
- package/scripts/update-recover.mjs +130 -0
- package/scripts/update-recover.test.ts +144 -0
- package/scripts/update-run.mjs +182 -40
- package/scripts/update-run.test.ts +208 -2
- package/server.ts +3 -2
- package/stores/tips.ts +14 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
{
|
|
2
|
+
"v": 1,
|
|
3
|
+
"tips": [
|
|
4
|
+
{
|
|
5
|
+
"id": "chat-enter-sends",
|
|
6
|
+
"title": "Enter sends, Shift+Enter adds a line",
|
|
7
|
+
"body": "Cmd/Ctrl+Enter always sends. On a phone, Enter is a new line.",
|
|
8
|
+
"guide": {
|
|
9
|
+
"file": "chat"
|
|
10
|
+
},
|
|
11
|
+
"added": "2026-10-31"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "chat-plan-mode",
|
|
15
|
+
"title": "Plan before anything changes",
|
|
16
|
+
"body": "Turn on Plan under the composer, or press Shift+Tab, and the agent reads and plans without editing. It stays on for that session.",
|
|
17
|
+
"guide": {
|
|
18
|
+
"file": "chat"
|
|
19
|
+
},
|
|
20
|
+
"added": "2026-10-31",
|
|
21
|
+
"mediaBrief": {
|
|
22
|
+
"kind": "crop",
|
|
23
|
+
"shot": "Composer bottom bar with Plan switched on, the agent's reply a plan checklist above it."
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "chat-attach-files",
|
|
28
|
+
"title": "Attach PDFs, spreadsheets and documents",
|
|
29
|
+
"body": "The paperclip, a paste or a drag takes up to 8 files a message, 25 MB each. Big photos are shrunk for you.",
|
|
30
|
+
"guide": {
|
|
31
|
+
"file": "chat"
|
|
32
|
+
},
|
|
33
|
+
"added": "2026-10-31",
|
|
34
|
+
"mediaBrief": {
|
|
35
|
+
"kind": "clip",
|
|
36
|
+
"shot": "A PDF dragged onto the composer; its chip appears; one more pasted; send."
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "chat-slash-menu",
|
|
41
|
+
"title": "Type / for commands and skills",
|
|
42
|
+
"body": "/ in the composer lists every command and skill the agent knows, filtered as you type.",
|
|
43
|
+
"guide": {
|
|
44
|
+
"file": "chat"
|
|
45
|
+
},
|
|
46
|
+
"added": "2026-10-31",
|
|
47
|
+
"media": {
|
|
48
|
+
"kind": "video",
|
|
49
|
+
"src": "/tips/chat-slash-menu.mp4",
|
|
50
|
+
"poster": "/tips/chat-slash-menu-poster.webp",
|
|
51
|
+
"alt": "Typing a slash in the composer opens a menu of commands and skills that narrows as you type.",
|
|
52
|
+
"width": 768,
|
|
53
|
+
"height": 480,
|
|
54
|
+
"durationMs": 6367
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "chat-tab-suggestion",
|
|
59
|
+
"title": "Tab takes the suggested reply",
|
|
60
|
+
"body": "After the first turn, a guess at your next message shows in the empty composer. Tab or → takes it; typing sets it aside.",
|
|
61
|
+
"guide": {
|
|
62
|
+
"file": "chat"
|
|
63
|
+
},
|
|
64
|
+
"added": "2026-10-31",
|
|
65
|
+
"mediaBrief": {
|
|
66
|
+
"kind": "clip",
|
|
67
|
+
"shot": "Empty composer shows a grey suggested reply; Tab fills it in."
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "chat-access-picker",
|
|
72
|
+
"title": "Choose what the agent may do",
|
|
73
|
+
"body": "The access picker under the composer has Ask first, Accept edits and Full access. Ask first shows an approval card for anything not already allowed.",
|
|
74
|
+
"guide": {
|
|
75
|
+
"file": "chat"
|
|
76
|
+
},
|
|
77
|
+
"added": "2026-10-31",
|
|
78
|
+
"mediaBrief": {
|
|
79
|
+
"kind": "crop",
|
|
80
|
+
"shot": "The access picker open, showing Ask first, Accept edits, Full access."
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "chat-undo",
|
|
85
|
+
"title": "Undo a message and its edits",
|
|
86
|
+
"body": "Undo on a message puts back the files the agent edited since and returns the message to the composer. Shell command changes aren't tracked.",
|
|
87
|
+
"guide": {
|
|
88
|
+
"file": "chat"
|
|
89
|
+
},
|
|
90
|
+
"added": "2026-10-31",
|
|
91
|
+
"mediaBrief": {
|
|
92
|
+
"kind": "crop",
|
|
93
|
+
"shot": "A user message with its Undo action visible on hover."
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "chat-terminal-handoff",
|
|
98
|
+
"title": "Hand a chat to the terminal",
|
|
99
|
+
"body": "The Chat / Terminal switch in the tab bar resumes the same conversation in the agent's own terminal, and back.",
|
|
100
|
+
"guide": {
|
|
101
|
+
"file": "chat"
|
|
102
|
+
},
|
|
103
|
+
"added": "2026-10-31",
|
|
104
|
+
"mediaBrief": {
|
|
105
|
+
"kind": "clip",
|
|
106
|
+
"shot": "Click Terminal in the Chat / Terminal switch; the same conversation resumes in the terminal."
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"id": "chat-bookmark",
|
|
111
|
+
"title": "Star a message to find it later",
|
|
112
|
+
"body": "Star a message, a step or a whole turn. The star in the chat header lists them and jumps to one.",
|
|
113
|
+
"guide": {
|
|
114
|
+
"file": "chat"
|
|
115
|
+
},
|
|
116
|
+
"when": ["no-bookmarks"],
|
|
117
|
+
"added": "2026-10-31",
|
|
118
|
+
"mediaBrief": {
|
|
119
|
+
"kind": "crop",
|
|
120
|
+
"shot": "Chat header's star list open with three bookmarks."
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"id": "chat-search",
|
|
125
|
+
"title": "⌘K searches every conversation",
|
|
126
|
+
"body": "Type in ⌘K for matching chats, or Tab for a full search of what you and the agents said.",
|
|
127
|
+
"guide": {
|
|
128
|
+
"file": "chat"
|
|
129
|
+
},
|
|
130
|
+
"added": "2026-10-31",
|
|
131
|
+
"mediaBrief": {
|
|
132
|
+
"kind": "clip",
|
|
133
|
+
"shot": "⌘K, type two words, matching chats appear, Tab switches to the full search."
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "sidebar-draft",
|
|
138
|
+
"title": "⌥N opens a draft",
|
|
139
|
+
"body": "A draft in the project you're in. Nothing starts until you send, and unsent drafts wait under Drafts.",
|
|
140
|
+
"guide": {
|
|
141
|
+
"file": "sessions",
|
|
142
|
+
"anchor": "new-sessions"
|
|
143
|
+
},
|
|
144
|
+
"added": "2026-10-31",
|
|
145
|
+
"mediaBrief": {
|
|
146
|
+
"kind": "clip",
|
|
147
|
+
"shot": "⌥N opens a draft with project, agent and worktree chips; type a line."
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "sidebar-open-pr",
|
|
152
|
+
"title": "Make a session a task",
|
|
153
|
+
"body": "Tick Open a PR when done on a draft and it runs in its own worktree and ends in a pull request.",
|
|
154
|
+
"guide": {
|
|
155
|
+
"file": "sessions",
|
|
156
|
+
"anchor": "new-sessions"
|
|
157
|
+
},
|
|
158
|
+
"added": "2026-10-31"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "sidebar-pin",
|
|
162
|
+
"title": "Pin a session",
|
|
163
|
+
"body": "Pin in a row's ⋯ menu keeps it on the Pinned shelf, and a pinned session never settles on its own.",
|
|
164
|
+
"guide": {
|
|
165
|
+
"file": "sessions",
|
|
166
|
+
"anchor": "the-sidebar"
|
|
167
|
+
},
|
|
168
|
+
"added": "2026-10-31"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "sidebar-snooze",
|
|
172
|
+
"title": "Snooze until tomorrow",
|
|
173
|
+
"body": "Snooze in a row's ⋯ menu hides it until 9 AM tomorrow or next Monday. It wakes early if it needs you.",
|
|
174
|
+
"guide": {
|
|
175
|
+
"file": "sessions",
|
|
176
|
+
"anchor": "the-sidebar"
|
|
177
|
+
},
|
|
178
|
+
"added": "2026-10-31",
|
|
179
|
+
"mediaBrief": {
|
|
180
|
+
"kind": "crop",
|
|
181
|
+
"shot": "A row's ⋯ menu open on Snooze with Tomorrow and Next week."
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"id": "sidebar-groups",
|
|
186
|
+
"title": "Put sessions in a group",
|
|
187
|
+
"body": "Move to group → New group… in a row's ⋯ menu gives them their own shelf. Select moves many at once.",
|
|
188
|
+
"guide": {
|
|
189
|
+
"file": "sessions",
|
|
190
|
+
"anchor": "groups"
|
|
191
|
+
},
|
|
192
|
+
"when": ["no-groups"],
|
|
193
|
+
"added": "2026-10-31",
|
|
194
|
+
"mediaBrief": {
|
|
195
|
+
"kind": "screenshot",
|
|
196
|
+
"shot": "Sidebar with a named group shelf holding three sessions."
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"id": "sidebar-load",
|
|
201
|
+
"title": "The dot that shows machine load",
|
|
202
|
+
"body": "The dot in the sidebar header turns amber or red when the machine is busy. Tap it to see which sessions use the most CPU.",
|
|
203
|
+
"guide": {
|
|
204
|
+
"file": "sessions",
|
|
205
|
+
"anchor": "machine-load"
|
|
206
|
+
},
|
|
207
|
+
"added": "2026-10-31",
|
|
208
|
+
"mediaBrief": {
|
|
209
|
+
"kind": "crop",
|
|
210
|
+
"shot": "The machine load popover: the dot amber, the top CPU sessions listed."
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"id": "tasks-sign-off",
|
|
215
|
+
"title": "Sign off and merge from the app",
|
|
216
|
+
"body": "Sign off & merge merges a task's PR once CI is green, then removes its worktree and branches.",
|
|
217
|
+
"guide": {
|
|
218
|
+
"file": "tasks"
|
|
219
|
+
},
|
|
220
|
+
"added": "2026-10-31",
|
|
221
|
+
"mediaBrief": {
|
|
222
|
+
"kind": "crop",
|
|
223
|
+
"shot": "A task in review with CI green and the Sign off & merge button."
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"id": "tasks-queue",
|
|
228
|
+
"title": "Cap how many tasks run at once",
|
|
229
|
+
"body": "Settings → Workspaces sets a running limit. Extra tasks wait under Queued and start in order.",
|
|
230
|
+
"guide": {
|
|
231
|
+
"file": "tasks"
|
|
232
|
+
},
|
|
233
|
+
"added": "2026-10-31"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"id": "tasks-pause",
|
|
237
|
+
"title": "Pause a task for a while",
|
|
238
|
+
"body": "A running task's row menu pauses it for an hour, until tomorrow, or until you resume it.",
|
|
239
|
+
"guide": {
|
|
240
|
+
"file": "tasks"
|
|
241
|
+
},
|
|
242
|
+
"added": "2026-10-31"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"id": "tasks-plan-pieces",
|
|
246
|
+
"title": "Approve a plan piece by piece",
|
|
247
|
+
"body": "A plan waiting on you lists its pieces, each with Merge into ① or Drop, and Approve with note… for the whole plan.",
|
|
248
|
+
"guide": {
|
|
249
|
+
"file": "tasks",
|
|
250
|
+
"anchor": "plans-in-pieces"
|
|
251
|
+
},
|
|
252
|
+
"added": "2026-10-31",
|
|
253
|
+
"mediaBrief": {
|
|
254
|
+
"kind": "screenshot",
|
|
255
|
+
"shot": "A plan awaiting approval with two pieces, Merge into ① and Drop on the second."
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"id": "tasks-linked-machines",
|
|
260
|
+
"title": "Run tasks on another machine",
|
|
261
|
+
"body": "Link a second machine and queued tasks start there when this one is busy. Linking more than one machine with Connect needs Pro.",
|
|
262
|
+
"guide": {
|
|
263
|
+
"file": "install",
|
|
264
|
+
"anchor": "with-agentos-connect-any-network-free-for-one-machine-pro-to-link-several"
|
|
265
|
+
},
|
|
266
|
+
"added": "2026-10-31"
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"id": "tasks-stacks",
|
|
270
|
+
"title": "Run a board as a stack",
|
|
271
|
+
"body": "In a project linked to a board, Run as stack starts the cards as stacked PRs, and Land merges them in order.",
|
|
272
|
+
"guide": {
|
|
273
|
+
"file": "tasks",
|
|
274
|
+
"anchor": "stacks"
|
|
275
|
+
},
|
|
276
|
+
"added": "2026-10-31",
|
|
277
|
+
"mediaBrief": {
|
|
278
|
+
"kind": "screenshot",
|
|
279
|
+
"shot": "A stack of three tasks with their PR states and Land."
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"id": "schedule-first",
|
|
284
|
+
"title": "Start work on a timer",
|
|
285
|
+
"body": "Schedules in the sidebar's ⋯ menu runs a prompt every day, every few hours, or once.",
|
|
286
|
+
"guide": {
|
|
287
|
+
"file": "schedules",
|
|
288
|
+
"anchor": "schedules"
|
|
289
|
+
},
|
|
290
|
+
"when": ["no-schedules"],
|
|
291
|
+
"added": "2026-10-31",
|
|
292
|
+
"mediaBrief": {
|
|
293
|
+
"kind": "screenshot",
|
|
294
|
+
"shot": "The Schedules form filled in: daily at 9 AM, a prompt, a project."
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"id": "schedule-check-in",
|
|
299
|
+
"title": "Check in on a long session",
|
|
300
|
+
"body": "Schedule check-ins in a session's ⋯ menu sends it a message on a timer.",
|
|
301
|
+
"guide": {
|
|
302
|
+
"file": "schedules",
|
|
303
|
+
"anchor": "schedules"
|
|
304
|
+
},
|
|
305
|
+
"added": "2026-10-31"
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
"id": "recipe-save",
|
|
309
|
+
"title": "Save a job you'll do again",
|
|
310
|
+
"body": "Save as recipe on a finished session or task fills in a recipe from what it did. Each run starts fresh.",
|
|
311
|
+
"guide": {
|
|
312
|
+
"file": "schedules",
|
|
313
|
+
"anchor": "recipes"
|
|
314
|
+
},
|
|
315
|
+
"when": ["no-recipes"],
|
|
316
|
+
"added": "2026-10-31",
|
|
317
|
+
"mediaBrief": {
|
|
318
|
+
"kind": "crop",
|
|
319
|
+
"shot": "The recipe form prefilled from a finished session."
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"id": "net-peers",
|
|
324
|
+
"title": "Sessions can talk to each other",
|
|
325
|
+
"body": "Agents run aos peers to see other sessions and aos send to message one.",
|
|
326
|
+
"guide": {
|
|
327
|
+
"file": "agent-network"
|
|
328
|
+
},
|
|
329
|
+
"added": "2026-10-31"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
"id": "net-await",
|
|
333
|
+
"title": "Wait for a reply without polling",
|
|
334
|
+
"body": "aos send <session> --await ends the agent's turn and wakes it with the answer.",
|
|
335
|
+
"guide": {
|
|
336
|
+
"file": "agent-network",
|
|
337
|
+
"anchor": "waiting-on-a-peer"
|
|
338
|
+
},
|
|
339
|
+
"added": "2026-10-31"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"id": "net-messages",
|
|
343
|
+
"title": "Read what your agents say to each other",
|
|
344
|
+
"body": "Messages shows every conversation between sessions, and you can join any of them.",
|
|
345
|
+
"guide": {
|
|
346
|
+
"file": "agent-network"
|
|
347
|
+
},
|
|
348
|
+
"added": "2026-10-31",
|
|
349
|
+
"mediaBrief": {
|
|
350
|
+
"kind": "screenshot",
|
|
351
|
+
"shot": "Messages with two sessions talking, one waiting on a reply."
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"id": "phone-pair",
|
|
356
|
+
"title": "Use AgentOS from your phone",
|
|
357
|
+
"body": "Settings → Devices & access → Add a device shows a code to scan. Each code works once.",
|
|
358
|
+
"guide": {
|
|
359
|
+
"file": "phone",
|
|
360
|
+
"anchor": "pair-the-phone"
|
|
361
|
+
},
|
|
362
|
+
"when": ["no-paired-device"],
|
|
363
|
+
"added": "2026-10-31"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"id": "phone-connect",
|
|
367
|
+
"title": "Reach this machine from anywhere",
|
|
368
|
+
"body": "Connect gives it a private address that works off your home network, free for one machine.",
|
|
369
|
+
"guide": {
|
|
370
|
+
"file": "phone",
|
|
371
|
+
"anchor": "from-anywhere-with-connect"
|
|
372
|
+
},
|
|
373
|
+
"when": ["connect-not-linked"],
|
|
374
|
+
"added": "2026-10-31"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"id": "phone-push",
|
|
378
|
+
"title": "Get pushes when an agent needs you",
|
|
379
|
+
"body": "Settings → Notifications → Your phone sends a test push to check it works.",
|
|
380
|
+
"guide": {
|
|
381
|
+
"file": "phone",
|
|
382
|
+
"anchor": "notifications"
|
|
383
|
+
},
|
|
384
|
+
"when": ["no-push-device"],
|
|
385
|
+
"added": "2026-10-31"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"id": "share-check",
|
|
389
|
+
"title": "Shares are masked before upload",
|
|
390
|
+
"body": "The Share dialog shows exactly what goes up, with secrets, addresses and emails masked. Nothing leaves until Create link.",
|
|
391
|
+
"guide": {
|
|
392
|
+
"file": "sharing",
|
|
393
|
+
"anchor": "check-it-first"
|
|
394
|
+
},
|
|
395
|
+
"when": ["no-shares"],
|
|
396
|
+
"added": "2026-10-31",
|
|
397
|
+
"mediaBrief": {
|
|
398
|
+
"kind": "screenshot",
|
|
399
|
+
"shot": "The Share dialog preview with masked values highlighted, before Create link."
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"id": "share-expiry",
|
|
404
|
+
"title": "Links can expire",
|
|
405
|
+
"body": "Create link lasts 1, 7 or 30 days, or never. Revoke one in Settings → Shared links.",
|
|
406
|
+
"guide": {
|
|
407
|
+
"file": "sharing",
|
|
408
|
+
"anchor": "create-the-link"
|
|
409
|
+
},
|
|
410
|
+
"added": "2026-10-31"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
"id": "export-ai",
|
|
414
|
+
"title": "Hand a chat to another AI",
|
|
415
|
+
"body": "Export for AI… in the ⋯ menu saves the chat as Markdown, JSON or HTML, with secrets masked.",
|
|
416
|
+
"guide": {
|
|
417
|
+
"file": "export"
|
|
418
|
+
},
|
|
419
|
+
"added": "2026-10-31",
|
|
420
|
+
"mediaBrief": {
|
|
421
|
+
"kind": "crop",
|
|
422
|
+
"shot": "The Export for AI sheet: format, Compact selected, the size estimate."
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"id": "export-compact",
|
|
427
|
+
"title": "Compact export for long chats",
|
|
428
|
+
"body": "Compact drops thinking and trims tool output, and the sheet warns when a file is too big for most models.",
|
|
429
|
+
"guide": {
|
|
430
|
+
"file": "export",
|
|
431
|
+
"anchor": "what-you-choose"
|
|
432
|
+
},
|
|
433
|
+
"added": "2026-10-31"
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"id": "update-when-idle",
|
|
437
|
+
"title": "Update without interrupting work",
|
|
438
|
+
"body": "Update when idle in the update banner waits until nothing is working.",
|
|
439
|
+
"guide": {
|
|
440
|
+
"file": "install",
|
|
441
|
+
"anchor": "update-from-the-app"
|
|
442
|
+
},
|
|
443
|
+
"added": "2026-10-31"
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"id": "tips-whats-new",
|
|
447
|
+
"title": "See what changed in each update",
|
|
448
|
+
"body": "The sparkle next to the lightbulb lists every change. A dot on it means something new since you last looked.",
|
|
449
|
+
"guide": {
|
|
450
|
+
"file": "tips",
|
|
451
|
+
"anchor": "whats-new"
|
|
452
|
+
},
|
|
453
|
+
"added": "2026-10-31"
|
|
454
|
+
}
|
|
455
|
+
]
|
|
456
|
+
}
|
package/content/whats-new.json
CHANGED
|
@@ -1793,6 +1793,30 @@
|
|
|
1793
1793
|
"area": "Chat",
|
|
1794
1794
|
"text": "GitHub Copilot chats can now ask you questions with clickable answer cards, like Claude chats do.",
|
|
1795
1795
|
"url": "https://github.com/saadnvd1/agent-os/pull/530"
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
"id": 531,
|
|
1799
|
+
"date": "2026-10-31",
|
|
1800
|
+
"kind": "new",
|
|
1801
|
+
"area": "Updates",
|
|
1802
|
+
"text": "AgentOS now keeps itself up to date once nothing is working, and goes back to the previous version on its own if an update doesn't start cleanly.",
|
|
1803
|
+
"url": "https://github.com/saadnvd1/agent-os/pull/531"
|
|
1804
|
+
},
|
|
1805
|
+
{
|
|
1806
|
+
"id": 532,
|
|
1807
|
+
"date": "2026-10-31",
|
|
1808
|
+
"kind": "new",
|
|
1809
|
+
"area": "AgentOS",
|
|
1810
|
+
"text": "A lightbulb in the sidebar shows one short tip a day about a feature you may have missed, and you can turn tips off in Settings.",
|
|
1811
|
+
"url": "https://github.com/saadnvd1/agent-os/pull/532"
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
"id": 533,
|
|
1815
|
+
"date": "2026-10-31",
|
|
1816
|
+
"kind": "fixed",
|
|
1817
|
+
"area": "Chat",
|
|
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
|
+
"url": "https://github.com/saadnvd1/agent-os/pull/533"
|
|
1796
1820
|
}
|
|
1797
1821
|
]
|
|
1798
1822
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import type { TipsView } from "@/lib/tips/schema";
|
|
3
|
+
|
|
4
|
+
export const tipsKeys = { all: ["tips"] as const };
|
|
5
|
+
|
|
6
|
+
// The browser's own date: the tip changes at the user's midnight.
|
|
7
|
+
const localDay = () => new Date().toLocaleDateString("sv");
|
|
8
|
+
|
|
9
|
+
async function send(init?: { body: object }): Promise<TipsView> {
|
|
10
|
+
const res = await fetch(
|
|
11
|
+
init ? "/api/tips" : `/api/tips?day=${localDay()}`,
|
|
12
|
+
init && {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: { "Content-Type": "application/json" },
|
|
15
|
+
body: JSON.stringify(init.body),
|
|
16
|
+
}
|
|
17
|
+
).catch(() => {
|
|
18
|
+
throw new Error("Couldn't reach AgentOS. Try again.");
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) throw new Error(`Tips failed (${res.status})`);
|
|
21
|
+
return res.json();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The tips file only changes with an update; the day can change under an open tab.
|
|
25
|
+
export const useTips = () =>
|
|
26
|
+
useQuery({
|
|
27
|
+
queryKey: tipsKeys.all,
|
|
28
|
+
queryFn: () => send(),
|
|
29
|
+
staleTime: 10 * 60_000,
|
|
30
|
+
refetchOnWindowFocus: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function useTipsMutation<V>(body: (v: V) => object) {
|
|
34
|
+
const queryClient = useQueryClient();
|
|
35
|
+
return useMutation({
|
|
36
|
+
mutationFn: (v: V) => send({ body: body(v) }),
|
|
37
|
+
onSuccess: (view) => queryClient.setQueryData(tipsKeys.all, view),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const useMarkTipSeen = () =>
|
|
42
|
+
useTipsMutation<string>((id) => ({
|
|
43
|
+
action: "seen",
|
|
44
|
+
ids: [id],
|
|
45
|
+
day: localDay(),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
export const useSetTipsOff = () =>
|
|
49
|
+
useTipsMutation<boolean>((off) => ({ action: "off", off }));
|
package/data/updates/index.ts
CHANGED
|
@@ -61,3 +61,8 @@ export const useUpdateBannerSetting = () =>
|
|
|
61
61
|
useUpdateMutation<boolean>((banner) =>
|
|
62
62
|
send("PUT", "/api/updates/settings", { banner })
|
|
63
63
|
);
|
|
64
|
+
|
|
65
|
+
export const useAutoUpdateSetting = () =>
|
|
66
|
+
useUpdateMutation<{ auto?: boolean; idleMinutes?: number }>((patch) =>
|
|
67
|
+
send("PUT", "/api/updates/settings", patch)
|
|
68
|
+
);
|
|
@@ -13,7 +13,11 @@ export function useReloadWhenUpdated(view: UpdateView | undefined) {
|
|
|
13
13
|
useEffect(() => {
|
|
14
14
|
if (!run) return;
|
|
15
15
|
if (isActivePhase(run.phase)) watched.current = run.id;
|
|
16
|
-
|
|
16
|
+
// A rollback restarted the server onto the old build: reload onto it too.
|
|
17
|
+
else if (
|
|
18
|
+
(run.phase === "done" || run.phase === "rolled_back") &&
|
|
19
|
+
watched.current === run.id
|
|
20
|
+
)
|
|
17
21
|
window.location.reload();
|
|
18
22
|
}, [run]);
|
|
19
23
|
}
|
package/lib/busy.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { db } from "./db";
|
|
|
2
2
|
import { runningStarts } from "./simulator";
|
|
3
3
|
|
|
4
4
|
// What a restart would cut off and have to start over: reviews, task
|
|
5
|
-
// setups
|
|
5
|
+
// setups, simulator starts, merges and stack landings run in the server. (Chat turns run in their own workers and
|
|
6
6
|
// outlive it.) Older than half an hour is stuck, not in flight.
|
|
7
7
|
export function inFlight(): {
|
|
8
8
|
reviews: number;
|
|
9
9
|
setups: number;
|
|
10
10
|
simulatorStarts: number;
|
|
11
|
+
merges: number;
|
|
12
|
+
landings: number;
|
|
11
13
|
} {
|
|
12
14
|
const count = (sql: string) => (db.prepare(sql).get() as { n: number }).n;
|
|
13
15
|
return {
|
|
@@ -22,5 +24,25 @@ export function inFlight(): {
|
|
|
22
24
|
AND created_at > datetime('now', '-30 minutes')`
|
|
23
25
|
),
|
|
24
26
|
simulatorStarts: runningStarts(),
|
|
27
|
+
merges: mergesInFlight(),
|
|
28
|
+
landings: count(
|
|
29
|
+
`SELECT COUNT(*) AS n FROM stacks WHERE status = 'landing'`
|
|
30
|
+
),
|
|
25
31
|
};
|
|
26
32
|
}
|
|
33
|
+
|
|
34
|
+
// Between `gh pr merge` and the tidying after it (lib/tasks/finish.ts keeps
|
|
35
|
+
// both on globalThis, so every bundle sees the same ones).
|
|
36
|
+
function mergesInFlight(): number {
|
|
37
|
+
const g = globalThis as unknown as {
|
|
38
|
+
__agentosSigningOff?: Set<string>;
|
|
39
|
+
__agentosAfterMerge?: Map<string, unknown>;
|
|
40
|
+
};
|
|
41
|
+
return new Set([
|
|
42
|
+
...(g.__agentosSigningOff ?? []),
|
|
43
|
+
...(g.__agentosAfterMerge?.keys() ?? []),
|
|
44
|
+
]).size;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const inFlightTotal = (w: ReturnType<typeof inFlight>): number =>
|
|
48
|
+
w.reviews + w.setups + w.simulatorStarts + w.merges + w.landings;
|
package/lib/db/migrations.ts
CHANGED
|
@@ -26,6 +26,11 @@ export type { Migration } from "./migration-types";
|
|
|
26
26
|
// typecheck, lint and test) and is not committed. A duplicate id or key, or a
|
|
27
27
|
// file whose name does not match its id, throws when this module loads.
|
|
28
28
|
// lib/db/migrations.test.ts enforces the rest.
|
|
29
|
+
//
|
|
30
|
+
// A migration must leave the previous release runnable: an automatic update
|
|
31
|
+
// that isn't healthy rolls the code back but not the database (chat workers
|
|
32
|
+
// keep writing through the restart, so a restore would drop their work). Add
|
|
33
|
+
// columns and tables; don't rename or drop what the last release reads.
|
|
29
34
|
|
|
30
35
|
export const migrationKey = (m: Migration): string => m.key ?? String(m.id);
|
|
31
36
|
const migrationName = (m: Migration): string =>
|
|
@@ -93,6 +98,22 @@ function compareToUpTo(m: Migration, upTo: number | string): number {
|
|
|
93
98
|
// All migrations in order - never modify existing ones, only add new
|
|
94
99
|
export const migrations: Migration[] = buildRegistry(migrationFiles);
|
|
95
100
|
|
|
101
|
+
/** How many of this build's migrations the database has; the update health check reads it. */
|
|
102
|
+
export function migrationStatus(db: Database.Database): {
|
|
103
|
+
known: number;
|
|
104
|
+
applied: number;
|
|
105
|
+
} {
|
|
106
|
+
const rows = db.prepare(`SELECT id, key FROM _migrations`).all() as {
|
|
107
|
+
id: number;
|
|
108
|
+
key: string | null;
|
|
109
|
+
}[];
|
|
110
|
+
const have = new Set(rows.map((r) => r.key ?? String(r.id)));
|
|
111
|
+
return {
|
|
112
|
+
known: migrations.length,
|
|
113
|
+
applied: migrations.filter((m) => have.has(migrationKey(m))).length,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
96
117
|
// `upTo`: stop after this legacy id or timestamp key, inclusive (tests that
|
|
97
118
|
// start from an older database).
|
|
98
119
|
export function runMigrations(
|
package/lib/recipes/timer.ts
CHANGED
|
@@ -67,7 +67,9 @@ export function setTimer(
|
|
|
67
67
|
if (!isTimezone(timezone)) throw new Error(`Unknown time zone "${timezone}"`);
|
|
68
68
|
const bad = cronError(cron);
|
|
69
69
|
if (bad) throw new Error(bad);
|
|
70
|
-
if (
|
|
70
|
+
if (
|
|
71
|
+
minGapMinutes(cron, timezone, Date.now(), MIN_GAP_MINUTES) < MIN_GAP_MINUTES
|
|
72
|
+
)
|
|
71
73
|
throw new Error("A recipe runs at most once an hour");
|
|
72
74
|
const enabled = input.enabled ?? true;
|
|
73
75
|
if (!existing) {
|
package/lib/schedules/cron.ts
CHANGED
|
@@ -261,16 +261,25 @@ export const MIN_GAP_MINUTES = 60;
|
|
|
261
261
|
// A message to a session, at most this often.
|
|
262
262
|
export const MESSAGE_MIN_GAP_MINUTES = 10;
|
|
263
263
|
|
|
264
|
-
// The shortest gap between runs over the next few weeks of them.
|
|
264
|
+
// The shortest gap between runs over the next few weeks of them. With
|
|
265
|
+
// `stopBelow`, it returns as soon as a gap under it turns up: "every minute"
|
|
266
|
+
// then costs two searches, not two hundred.
|
|
265
267
|
export function minGapMinutes(
|
|
266
268
|
cron: string,
|
|
267
269
|
timezone: string,
|
|
268
|
-
from = Date.now()
|
|
270
|
+
from = Date.now(),
|
|
271
|
+
stopBelow = 0
|
|
269
272
|
): number {
|
|
270
|
-
const
|
|
273
|
+
const parsed = parseCron(cron);
|
|
271
274
|
let min = Infinity;
|
|
272
|
-
|
|
273
|
-
|
|
275
|
+
let prev = nextRun(parsed, from, timezone);
|
|
276
|
+
for (let i = 1; i < 200 && prev !== null; i++) {
|
|
277
|
+
const next = nextRun(parsed, prev, timezone);
|
|
278
|
+
if (next === null) break;
|
|
279
|
+
min = Math.min(min, (next - prev) / 60_000);
|
|
280
|
+
if (min < stopBelow) break;
|
|
281
|
+
prev = next;
|
|
282
|
+
}
|
|
274
283
|
return min;
|
|
275
284
|
}
|
|
276
285
|
|