agent-lookout 0.2.1 → 0.2.3

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/docs/GUIDE.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ## Requirements
4
4
 
5
5
  - macOS or Linux. Agent Lookout is developed and tested on macOS. On Linux it has been checked in CI by starting it with `npm start`, and not yet by a person on a Linux desktop. [On Linux](#on-linux) says what differs there. Windows is untested.
6
- - Node.js 20.19 or newer. On Node 22 it needs 22.12 or newer. The [Mac app](#desktop-app) needs no Node.js.
6
+ - Node.js 22.12 or newer. The [Mac app](#desktop-app) needs no Node.js.
7
7
  - Claude Code, Codex or both. Neither needs any setup. Any other agent can appear too, by writing a status file: see [Your own agents](#your-own-agents).
8
8
  - For Claude Code, a version that has the `claude agents` command. `claude agents --help` should print `Usage: claude agents`. Without that command Agent Lookout still reads the session files, but cannot list background jobs that have finished or failed.
9
9
  - For Codex, version 0.155 or later, so that Agent Lookout can tell a session that has ended from one that is idle.
@@ -20,7 +20,7 @@ The header over each view says how many sessions are being watched and, in a wid
20
20
 
21
21
  ### Overview
22
22
 
23
- The Overview has five parts. The Needs you panel and the Last hour chart share the top row, the Sessions list and the Events log share the next, and the Timeline runs under both. In a narrower window they are one column in that order. The Sessions list can also be shown as a [board](#board), and any session, in it or in the Needs you panel, can be opened to see its [details](#a-sessions-details).
23
+ The Overview has six parts. The Needs you panel and the Last hour chart share the top row, the Sessions list and the Events log share the next, and the Timeline runs under both, with Waits under it. In a narrower window they are one column in that order. The Sessions list can also be shown as a [board](#board), and any session, in it or in the Needs you panel, can be opened to see its [details](#a-sessions-details).
24
24
 
25
25
  #### Needs you
26
26
 
@@ -161,13 +161,19 @@ Repos then lists both sessions under `storefront`, each with its own folder and
161
161
 
162
162
  #### Events
163
163
 
164
- The Events log records each session appearing, changing status and ending, newest first. When a wait ends, it says how long the wait lasted if it saw the wait begin. When Agent Lookout measured nothing for a while in the last hour, such as while the computer was asleep, a row says when watching resumed and how long was not measured. While the log still holds everything since Agent Lookout started, it ends with Started watching.
164
+ The Events log records each session appearing, changing status and ending, newest first. When a wait ends, it says how long the wait lasted if it saw the wait begin. When Agent Lookout measured nothing for a while in the last hour, such as while the computer was asleep or Agent Lookout was stopped, a row says when watching resumed and how long was not measured. The log is kept on this computer with the charts' counts, so a restart does not empty it: see [History](#history). While the log still holds everything since Agent Lookout started, it ends with Started watching, and its heading says since when, with the day when that was not today. Once the history has been cleared, it ends with History cleared instead.
165
165
 
166
166
  When you come back to the tab after it was in the background or minimised, or open the dashboard again, a line in the log marks where you left off, such as New since 14:02:37, and the log's heading says how many events arrived since, such as 3 new. The events above the line are the new ones. The line and the count go once the line has been in view for 10 seconds, or when you open Sources or Settings. If more arrived than the log shows at once, scroll down the log to the line. With the tab in front all the time there is never a line. Events that arrive while the line is showing join the new ones above it. A wait among the new events has its usual mark, and nothing else changes colour. The one thing kept for this is the time you left off, in your browser.
167
167
 
168
168
  #### Timeline
169
169
 
170
- The Timeline draws each session's status over the last hour, one row for each session. Hatched stretches are time Agent Lookout did not measure, such as the time before it started. The legend at the top of the card names each mark.
170
+ The Timeline draws each session's status over the last hour, one row for each session. Hatched stretches are time Agent Lookout did not measure, such as the time before it started or while it was stopped. The legend at the top of the card names each mark.
171
+
172
+ #### Waits
173
+
174
+ Waits says how long sessions waited on you today and over the last 7 days, which are today and the six days before it, so you can see whether waiting on you is getting better or worse, and which sessions wait the longest. Each figure gives how many waits it took. By day has a bar for each of the 7 days, oldest first, with how long sessions waited that day and how much of it Agent Lookout measured. A day it did not measure at all is hatched, with a dash. Longest waits lists the five sessions that waited longest, with how many times each waited and whether it is waiting now, today or over the 7 days, by the switch beside it.
175
+
176
+ Waits counts only the time Agent Lookout was running, from the [history](#history) it keeps, and the line under the figures says how much of today and of the 7 days that was. Time it was stopped, the computer was asleep, or a source did not answer is left out, and a wait that went on across it counts for the time either side and is still one wait. A wait already under way when Agent Lookout started counts from when it started, so it can be shorter here than in the Needs you panel, which shows how long the session has waited in all. When two sessions wait at once, each one's wait counts, so two waiting for a minute is two minutes. With `AGENT_LOOKOUT_HISTORY=off` it covers only the time since Agent Lookout started, and says so. Only Claude Code sessions and sessions from a status file can be seen waiting, so a Codex session never counts. A wait that is still open is amber at the end of today's bar, as in the Last hour chart, and goes on second by second. The page asks for the totals every 30 seconds, and at once when a wait opens or ends or you come back to the page, and they are worked out on this computer and sent nowhere.
171
177
 
172
178
  ### Sources
173
179
 
@@ -214,7 +220,7 @@ Why, for each No and Partly:
214
220
 
215
221
  ### Settings
216
222
 
217
- Settings has five cards, and in the Mac app a sixth, [Updates](#updates). Theme chooses Night, which is the default, Day, or System, which follows your computer's setting. Notifications turns notifications on and off, for the dashboard page and for Agent Lookout itself, and chooses what sends one. Email says whether Agent Lookout emails you, and for what, and Webhook whether it posts to a webhook, such as a Slack channel's, and for what. Both are set up when you start it, not here. This copy shows the version you are running and where Agent Lookout sends your data: nowhere, or only in the emails and webhook posts you set up.
223
+ Settings has six cards, and in the Mac app two more, [Menu bar](#menu-bar) and [Updates](#updates). Theme chooses Night, which is the default, Day, or System, which follows your computer's setting. Notifications turns notifications on and off, for the dashboard page and for Agent Lookout itself, and chooses what sends one. History says where the Events log and the charts are kept, how much they hold and since when, and clears them. Email says whether Agent Lookout emails you, and for what, and Webhook whether it posts to a webhook, such as a Slack channel's, and for what. Both are set up when you start it, not here. This copy shows the version you are running and where Agent Lookout sends your data: nowhere, or only in the emails and webhook posts you set up.
218
224
 
219
225
  #### Notifications
220
226
 
@@ -273,6 +279,20 @@ A notification shows the session's name outside the dashboard: over other apps,
273
279
 
274
280
  The browser gives its permission to the address, not to Agent Lookout. Another program you later serve at the same address, such as another project's dev server on `localhost:5173`, can show notifications without asking. To take the permission back, remove it for that address in the browser's site settings. `npx agent-lookout` and `npm start` serve Agent Lookout at `127.0.0.1:4777`, an address other tools are less likely to use. [PRIVACY.md](../PRIVACY.md#notifications) says what a notification holds and where it is kept.
275
281
 
282
+ #### History
283
+
284
+ The Events log and the counts behind the charts are written to `~/.agent-lookout/history` every 5 seconds, and once more as Agent Lookout stops, so they are still there when it starts again. The time it was not running shows as time not measured, as the time the computer was asleep does: a Watching resumed row in the Events log, and hatching in the Timeline and the Last hour chart. Every restart gets that row, however short the stop and however long ago the history before it ends.
285
+
286
+ A session that changed status or ended while Agent Lookout was stopped is found by the first poll after it starts again, and the Events log and the charts record the change at that poll, as they do when the computer wakes. A session the history holds no event of, because nothing about it changed while Agent Lookout watched, is shown with its present status only from the restart, unless its agent says when that status began.
287
+
288
+ The History card says that history is kept on this computer for 8 days, and gives the folder, how much the files hold out of 20 MB, and how far back they go. Each day's history is deleted once that day ended more than 8 days ago, and when the files would hold more than 20 MB the oldest is deleted first, so they never hold more. They hold the events and counts the dashboard shows, and no folder path, prompt or anything a waiting session is asking. [PRIVACY.md](../PRIVACY.md#the-history) has the details.
289
+
290
+ Clear history asks first, in the card: Clear history again to go ahead, or Cancel. It deletes the files and empties the Events log and the charts, which then start with History cleared. The card says when it was cleared. Files written by a later version of Agent Lookout are left alone.
291
+
292
+ `AGENT_LOOKOUT_HISTORY_DIR` keeps the files in another folder. With `AGENT_LOOKOUT_HISTORY=off` nothing is written: the card says history is kept in memory only and has no button, and the Events log and the charts start empty each time Agent Lookout starts.
293
+
294
+ When two copies run at once, such as the Mac app and `npx agent-lookout`, one of them writes the history. The other shows what had been kept when it started and keeps what it sees in memory. Its card says another copy is writing the history, and whether that copy reads sessions from other folders, and has no button. It takes over when the first one stops. To try Agent Lookout on empty folders without writing to your history, set `AGENT_LOOKOUT_HISTORY_DIR` to an empty folder too.
295
+
276
296
  #### Email
277
297
 
278
298
  Agent Lookout can also email you when a session has waited for a while, for when you are away from the computer, and, if you choose, when a session finishes, fails or ends. It is off unless you set it up. With it off, which is the default, Agent Lookout sends no email and opens no connection to a mail server.
@@ -288,7 +308,7 @@ Two settings turn email on:
288
308
  | `AGENT_LOOKOUT_EMAIL_TO` | The one address emails go to |
289
309
  | `AGENT_LOOKOUT_SMTP_URL` | The mail server, as `smtps://name:password@server:port`, or `smtp://` |
290
310
 
291
- Three more can be left out. `AGENT_LOOKOUT_EMAIL_FROM` is the address emails come from, which is the address they go to unless you set it. If emails go to an address other than the one you sign in to the mail server with, set `AGENT_LOOKOUT_EMAIL_FROM` to the address you sign in with. Most providers refuse to send from any other. `AGENT_LOOKOUT_EMAIL_AFTER` is how many seconds a wait lasts before it is emailed: 60 unless you set it, and 0 for at once. `AGENT_LOOKOUT_EMAIL_EVENTS` is what is emailed: one or more of `needs-you`, `finished`, `failed` and `ended`, the four events described under [Notifications](#notifications), separated by commas, such as `needs-you,finished`. Unless you set it, it is `needs-you`, a wait alone. The switches in Settings choose notifications only, not emails, because the page cannot change how Agent Lookout was started.
311
+ Four more can be left out. `AGENT_LOOKOUT_EMAIL_FROM` is the address emails come from, which is the address they go to unless you set it. If emails go to an address other than the one you sign in to the mail server with, set `AGENT_LOOKOUT_EMAIL_FROM` to the address you sign in with. Most providers refuse to send from any other. `AGENT_LOOKOUT_EMAIL_AFTER` is how many seconds a wait lasts before it is emailed: 60 unless you set it, and 0 for at once. `AGENT_LOOKOUT_EMAIL_EVENTS` is what is emailed: one or more of `needs-you`, `finished`, `failed` and `ended`, the four events described under [Notifications](#notifications), separated by commas, such as `needs-you,finished`. Unless you set it, it is `needs-you`, a wait alone. `AGENT_LOOKOUT_EMAIL_ASKING`, `on` or `off`, is whether the email for a wait says what the session is asking, and is `off` unless you set it: [What a waiting session is asking](#what-a-waiting-session-is-asking-by-email) says what that adds. The switches in Settings choose notifications only, not emails, because the page cannot change how Agent Lookout was started.
292
312
 
293
313
  In `AGENT_LOOKOUT_SMTP_URL`, write any `@`, `:`, `/`, `?`, `#` or `%` in the user name or the password as `%40`, `%3A`, `%2F`, `%3F`, `%23` or `%25`. A user name that is an email address is the usual case: `name@example.com` is written `name%40example.com`.
294
314
 
@@ -328,11 +348,38 @@ Then start Agent Lookout with them, brackets included:
328
348
 
329
349
  The brackets keep the settings to this one run, so the terminal does not keep them afterwards, and no program you start from it later is handed the password. In a clone, use `npm start` or `npm run dev` in place of `npx agent-lookout`, inside the same brackets.
330
350
 
331
- To check that it worked, open Settings. The Email card says where emails go and after how long, with most of the address hidden, such as "Emails go to Y…@gmail.com after a wait of 1 minute." With other events set, it names each one, such as "Emails go to Y…@gmail.com when a session has waited 1 minute or finishes." Once an email has been tried, the line under it says when the last one was sent, such as "Last sent at 14:02.", or, in a note headed "The last email could not be sent", why it could not be, such as "The mail server did not accept the user name and password." If a setting cannot be read, email stays off: the card says "Email is off.", and a note headed "Email is not set up correctly" names the setting, and the terminal you started Agent Lookout in prints one line that says the same. Neither ever shows the password.
351
+ To check that it worked, open Settings. The Email card says where emails go and after how long, with most of the address hidden, such as "Emails go to Y…@gmail.com after a wait of 1 minute." With other events set, it names each one, such as "Emails go to Y…@gmail.com when a session has waited 1 minute or finishes." Under that, a line says whether the email for a wait says what the session is asking, as [below](#what-a-waiting-session-is-asking-by-email) describes. Once an email has been tried, the line under those says when the last one was sent, such as "Last sent at 14:02.", or, in a note headed "The last email could not be sent", why it could not be, such as "The mail server did not accept the user name and password." If a setting cannot be read, email stays off: the card says "Email is off.", and a note headed "Email is not set up correctly" names the setting, and the terminal you started Agent Lookout in prints one line that says the same. Neither ever shows the password.
332
352
 
333
353
  The card can say where emails go before any has been tried, so a wrong password or port shows only once a wait has lasted the delay. To try it at once, start Agent Lookout with `AGENT_LOOKOUT_EMAIL_AFTER=0` as well, and let a session ask for permission. The card then says whether the email went. Start it again without that setting afterwards.
334
354
 
335
- An email's subject names the session and what happened: "checkout-flow is waiting for permission", "checkout-flow asked you a question" or "checkout-flow is waiting for you", and "billing-webhooks finished", "billing-webhooks failed" or "billing-webhooks ended". Its text says how long the session has waited and since when, or when Agent Lookout saw it finish, fail or end, then the name of its project folder, its app and its agent, leaving out any that is not known, and how to stop these emails. It holds no path, no prompt and no link. [PRIVACY.md](../PRIVACY.md#email) lists all it holds.
355
+ An email's subject names the session and what happened: "checkout-flow is waiting for permission", "checkout-flow asked you a question" or "checkout-flow is waiting for you", and "billing-webhooks finished", "billing-webhooks failed" or "billing-webhooks ended". Its text says how long the session has waited and since when, or when Agent Lookout saw it finish, fail or end, then the name of its project folder, its app and its agent, leaving out any that is not known, and how to stop these emails. It holds no path, no prompt, no link and, unless you set `AGENT_LOOKOUT_EMAIL_ASKING=on` (below), nothing of what a waiting session is asking. [PRIVACY.md](../PRIVACY.md#email) lists all it holds.
356
+
357
+ ##### What a waiting session is asking, by email
358
+
359
+ An email leaves out what a waiting session is asking, the line under its reason in the Needs you panel, such as `Run: npm test`, because that line can hold a command, a web address or a file's full path, and an email leaves this computer. If that line is what tells you whether to go back now, start Agent Lookout with `AGENT_LOOKOUT_EMAIL_ASKING=on` as well:
360
+
361
+ ```sh
362
+ (source ~/.agent-lookout-email && AGENT_LOOKOUT_EMAIL_ASKING=on npx agent-lookout)
363
+ ```
364
+
365
+ The email for a wait then has one more line, before the folder's name:
366
+
367
+ ```text
368
+ checkout-flow is waiting for permission.
369
+
370
+ It has waited 1 minute, since 14:02.
371
+
372
+ Asking: Run: npm test
373
+ Folder: storefront
374
+ App: VS Code
375
+ Agent: Claude Code
376
+
377
+ Sent by Agent Lookout on your computer. To stop these emails, start it again without AGENT_LOOKOUT_EMAIL_TO.
378
+ ```
379
+
380
+ It is the line the dashboard shows, cleaned and cut the same way: one line of at most 200 characters. A command in it can name any path, a server or a password typed on the command line, a web address can show as a link in your mail app, and a file outside the session's folder is named by its full path, which can name your home folder. Your mail server and your mailbox keep it, as they keep the rest of the email. It is never in the subject, and never in an email for a session that finished, failed or ended. Only a Claude Code session has one, and only when its transcript says, so other waits send the same email as before. It is taken from the sessions at the moment the email is sent, and Agent Lookout keeps nothing of it afterwards. With `AGENT_LOOKOUT_WAITING_TEXT=off` no transcript is read, so there is nothing to add.
381
+
382
+ The Email card then says "Emails for a wait say what the session is asking." under where emails go, and "Emails leave out what a waiting session is asking." while the setting is off or left out. Any value but `on` or `off` turns email off, and the card and the terminal name the setting, as for any other setting that cannot be read. To leave the line out again, start Agent Lookout without `AGENT_LOOKOUT_EMAIL_ASKING`. The webhook has a setting of its own for this, so the line can come to your mailbox and stay out of a channel others read.
336
383
 
337
384
  - One email goes for a wait that has lasted the delay and is still open. A wait you answer before then sends nothing.
338
385
  - Each wait sends one email at most. A session you answer that waits again later sends another.
@@ -356,7 +403,7 @@ It is set up with a setting in the environment when you start Agent Lookout, not
356
403
  | --------------------------- | ------------------------------------------------------ |
357
404
  | `AGENT_LOOKOUT_WEBHOOK_URL` | The one address posts go to, beginning with `https://` |
358
405
 
359
- Two more can be left out. `AGENT_LOOKOUT_WEBHOOK_EVENTS` is what is posted, written as for email: one or more of `needs-you`, `finished`, `failed` and `ended`, separated by commas, and `needs-you` unless you set it. `AGENT_LOOKOUT_WEBHOOK_AFTER` is how many seconds a wait lasts before it is posted: 60 unless you set it, and 0 for at once. A finish, a failure or an end is posted at once. These are separate from the email settings, so email and the webhook can send different events.
406
+ Three more can be left out. `AGENT_LOOKOUT_WEBHOOK_EVENTS` is what is posted, written as for email: one or more of `needs-you`, `finished`, `failed` and `ended`, separated by commas, and `needs-you` unless you set it. `AGENT_LOOKOUT_WEBHOOK_AFTER` is how many seconds a wait lasts before it is posted: 60 unless you set it, and 0 for at once. A finish, a failure or an end is posted at once. `AGENT_LOOKOUT_WEBHOOK_ASKING`, `on` or `off`, is whether the post for a wait says what the session is asking, and is `off` unless you set it: [What a waiting session is asking](#what-a-waiting-session-is-asking-in-a-post) says what that adds. These are separate from the email settings, so email and the webhook can send different events, and one can say what a session is asking while the other does not.
360
407
 
361
408
  The address is a secret. Anyone who has it can post to the channel, so keep it out of screenshots, issues and shared files. Agent Lookout never shows it: Settings shows its host alone, and that in full, so with a service that puts its secret in the host, keep Settings out of screenshots too.
362
409
 
@@ -388,9 +435,15 @@ For Slack:
388
435
 
389
436
  For Discord, create a webhook in the channel's settings, under Integrations, copy its address and add `/slack` to the end of it: Discord then takes the same posts.
390
437
 
391
- To check that it worked, open Settings. The Webhook card says where posts go and when, naming only the host, such as "Posts go to hooks.slack.com after a wait of 1 minute." Once a post has been tried, the line under it says when the last one went, such as "Last posted at 14:02.", or, in a note headed "The last post failed", why it did not, such as "The address refused the post (status 403)." If a setting cannot be read, the webhook stays off: the card says "The webhook is off.", and a note headed "The webhook is not set up correctly" names the setting, and the terminal you started Agent Lookout in prints one line that says the same. Neither ever shows the address. To try it at once, start Agent Lookout with `AGENT_LOOKOUT_WEBHOOK_AFTER=0` as well, `(source ~/.agent-lookout-webhook && AGENT_LOOKOUT_WEBHOOK_AFTER=0 npx agent-lookout)`, and let a session ask for permission. The card then says whether the post went. Start it again without that setting afterwards, or every wait is posted as soon as it begins.
438
+ To check that it worked, open Settings. The Webhook card says where posts go and when, naming only the host, such as "Posts go to hooks.slack.com after a wait of 1 minute." Under that, a line says whether the post for a wait says what the session is asking, as [below](#what-a-waiting-session-is-asking-in-a-post) describes. Once a post has been tried, the line under those says when the last one went, such as "Last posted at 14:02.", or, in a note headed "The last post failed", why it did not, such as "The address refused the post (status 403)." If a setting cannot be read, the webhook stays off: the card says "The webhook is off.", and a note headed "The webhook is not set up correctly" names the setting, and the terminal you started Agent Lookout in prints one line that says the same. Neither ever shows the address. To try it at once, start Agent Lookout with `AGENT_LOOKOUT_WEBHOOK_AFTER=0` as well, `(source ~/.agent-lookout-webhook && AGENT_LOOKOUT_WEBHOOK_AFTER=0 npx agent-lookout)`, and let a session ask for permission. The card then says whether the post went. Start it again without that setting afterwards, or every wait is posted as soon as it begins.
392
439
 
393
- A post shows in the channel as one line, such as "checkout-flow is waiting for permission (4m 12s, storefront, VS Code, Claude Code)" or "billing-webhooks finished (billing-webhooks, Terminal, Claude Code)": the session's name, what happened, and in brackets how long it has waited, its project folder's name, its app and its agent, leaving out any that is not known. The post also carries the same in fields of its own, for a program to read. It holds no path and no prompt, and a link only where a session's name has a web address written out in it. [PRIVACY.md](../PRIVACY.md#webhook) lists all it holds.
440
+ A post shows in the channel as one line, such as "checkout-flow is waiting for permission (4m 12s, storefront, VS Code, Claude Code)" or "billing-webhooks finished (billing-webhooks, Terminal, Claude Code)": the session's name, what happened, and in brackets how long it has waited, its project folder's name, its app and its agent, leaving out any that is not known. The post also carries the same in fields of its own, for a program to read. It holds no path, no prompt, a link only where a session's name has a web address written out in it and, unless you set `AGENT_LOOKOUT_WEBHOOK_ASKING=on` (below), nothing of what a waiting session is asking. [PRIVACY.md](../PRIVACY.md#webhook) lists all it holds.
441
+
442
+ ##### What a waiting session is asking, in a post
443
+
444
+ A post leaves out what a waiting session is asking, such as `Run: npm test`, for the reasons an email does. Start Agent Lookout with `AGENT_LOOKOUT_WEBHOOK_ASKING=on` as well, and the post for a wait says it after the reason, as the notification of a wait does: "checkout-flow is waiting for permission: Run: npm test (4m 12s, storefront, VS Code, Claude Code)". It is in a field of its own too, `asking`, for a program to read. It is the line the dashboard shows, cleaned and cut the same way, so it can hold a command, which can name any path, a server or a password typed on the command line, a web address, which Slack shows as a link, or a file's full path when the file is outside the session's folder. Everyone who can read the channel sees it, and the service keeps it. It cannot mention anyone or ping a channel, as a session's name cannot. It is never in a post for a session that finished, failed or ended, and only a Claude Code session has one. It is taken from the sessions at the moment the post is sent, and Agent Lookout keeps nothing of it afterwards.
445
+
446
+ The Webhook card then says "Posts for a wait say what the session is asking." under where posts go, and "Posts leave out what a waiting session is asking." while the setting is off or left out. Any value but `on` or `off` turns the webhook off, and the card and the terminal name the setting. To leave the line out again, start Agent Lookout without `AGENT_LOOKOUT_WEBHOOK_ASKING`.
394
447
 
395
448
  Posts follow the rules emails do. One goes for a wait that has lasted the delay and is still open, and one at most for each wait. A finish, a failure or an end goes as soon as it is seen. Nothing is sent for what was already true when Agent Lookout starts. At most 20 posts are tried in any hour, counted apart from emails. A post that could not be sent is not tried again, and a redirect is never followed.
396
449
 
@@ -817,7 +870,7 @@ Codex sessions and sessions from status files have no Jump button. Nor do Claude
817
870
 
818
871
  Email and a webhook are the two ways it can tell you of a session away from this computer, and each sends to one address. A post is one line of text, with no buttons, and nothing can be answered from it. An email or a post that could not be sent is not tried again. The events are chosen when Agent Lookout starts, with `AGENT_LOOKOUT_EMAIL_EVENTS` and `AGENT_LOOKOUT_WEBHOOK_EVENTS`, and not in Settings.
819
872
 
820
- The Events log, the charts and the Timeline are kept in memory. They start empty each time Agent Lookout starts.
873
+ The history is kept for 8 days, but the charts show at most the last six hours of it, the Events log its newest 200 events, and Waits the last 7 days.
821
874
 
822
875
  A repository reached through a symbolic link by one session and by its real path from another is two groups of one name. A session's branch shows in the Sessions list, on its board card, in the Needs you panel, in the search and in the answers of `agent-lookout mcp`, and nowhere else: not in the Timeline, the Events log, a notification, an email, a webhook post or `agent-lookout status`. A repository in your home folder itself, as some people keep their settings in, is not looked in, so a session in a folder under it that is in no other repository shows no branch.
823
876
 
@@ -842,6 +895,8 @@ AGENT_LOOKOUT_CLAUDE_FEED=off npx agent-lookout
842
895
  | `AGENT_LOOKOUT_CODEX_HOME` | A folder to read in place of the Codex folder. A folder with no `sessions` folder in it shows no Codex sessions. |
843
896
  | `CODEX_HOME` | Codex's own setting for where it keeps its files. When it is set, Agent Lookout reads that folder too, unless `AGENT_LOOKOUT_CODEX_HOME` is set. |
844
897
  | `AGENT_LOOKOUT_STATUS_DIR` | A folder of [status files](#your-own-agents) to read in place of `~/.agent-lookout/sessions`. |
898
+ | `AGENT_LOOKOUT_HISTORY` | Set to `off` and Agent Lookout keeps the Events log and the charts in memory only and writes nothing to disk, so they start empty each time it starts. See [History](#history). |
899
+ | `AGENT_LOOKOUT_HISTORY_DIR` | A folder to keep the [history](#history) in, in place of `~/.agent-lookout/history`. |
845
900
  | `AGENT_LOOKOUT_NOTIFICATIONS` | Set to `on` and, on a Mac, Agent Lookout shows notifications of waits itself from the moment it starts. A dashboard page that has notifications off turns them off again. Anywhere else it prints a line saying it shows none itself. |
846
901
  | `AGENT_LOOKOUT_TMUX` | Set to `off` and Agent Lookout never runs `tmux`. Sessions in tmux are still listed, without a Jump button. |
847
902
  | `AGENT_LOOKOUT_TERMINAL_JUMP` | Set to `off` and Agent Lookout never looks for, or brings forward, a tab of Terminal or iTerm2. Sessions there are still listed, without a Jump button. |
@@ -850,14 +905,16 @@ AGENT_LOOKOUT_CLAUDE_FEED=off npx agent-lookout
850
905
  | `AGENT_LOOKOUT_EMAIL_FROM` | The address emails come from. The default is the address they go to. |
851
906
  | `AGENT_LOOKOUT_EMAIL_AFTER` | How many seconds a wait lasts before it is emailed, from 0 to 86400. The default is 60. |
852
907
  | `AGENT_LOOKOUT_EMAIL_EVENTS` | What is emailed: `needs-you`, `finished`, `failed` and `ended`, any of them, separated by commas. The default is `needs-you`. |
908
+ | `AGENT_LOOKOUT_EMAIL_ASKING` | Set to `on` and the email for a wait says what the session is asking, such as `Run: npm test`, which can hold a command, a web address or a file's full path. The default is `off`. See [Email](#what-a-waiting-session-is-asking-by-email). |
853
909
  | `AGENT_LOOKOUT_WEBHOOK_URL` | The one address [webhook](#webhook) posts go to, beginning with `https://`. It turns the webhook on. |
854
910
  | `AGENT_LOOKOUT_WEBHOOK_EVENTS` | What is posted, as for `AGENT_LOOKOUT_EMAIL_EVENTS`. The default is `needs-you`. |
855
911
  | `AGENT_LOOKOUT_WEBHOOK_AFTER` | How many seconds a wait lasts before it is posted, from 0 to 86400. The default is 60. |
912
+ | `AGENT_LOOKOUT_WEBHOOK_ASKING` | Set to `on` and the post for a wait says what the session is asking, as `AGENT_LOOKOUT_EMAIL_ASKING` does for email. The default is `off`. See [Webhook](#what-a-waiting-session-is-asking-in-a-post). |
856
913
  | `AGENT_LOOKOUT_URL` | The address `agent-lookout status` and `agent-lookout mcp` ask, such as `http://127.0.0.1:4778`, in place of `http://127.0.0.1:4777` and then `http://localhost:5173`. Only an address on this computer is accepted. Set it where the command runs, not where Agent Lookout starts. |
857
914
 
858
- To see the empty screen, set `AGENT_LOOKOUT_CLAUDE_HOME`, `AGENT_LOOKOUT_CODEX_HOME` and `AGENT_LOOKOUT_STATUS_DIR` to an empty folder. With only the first set, Codex sessions and sessions from status files still appear.
915
+ To see the empty screen, set `AGENT_LOOKOUT_CLAUDE_HOME`, `AGENT_LOOKOUT_CODEX_HOME`, `AGENT_LOOKOUT_STATUS_DIR` and `AGENT_LOOKOUT_HISTORY_DIR` to an empty folder. With only the first set, Codex sessions and sessions from status files still appear.
859
916
 
860
- The Claude Code, transcript, Codex, status file, notification, tmux, terminal tab, email and webhook settings work with `npx agent-lookout` and `agent-lookout`, and in a clone with `npm start` and `npm run dev`. The port and address settings apply to `npx agent-lookout`, `agent-lookout` and `npm start`, and `AGENT_LOOKOUT_URL` to `agent-lookout status` and `agent-lookout mcp` only. To choose the port for `npm run dev`, pass it after `--`:
917
+ The Claude Code, transcript, Codex, status file, history, notification, tmux, terminal tab, email and webhook settings work with `npx agent-lookout` and `agent-lookout`, and in a clone with `npm start` and `npm run dev`. The port and address settings apply to `npx agent-lookout`, `agent-lookout` and `npm start`, and `AGENT_LOOKOUT_URL` to `agent-lookout status` and `agent-lookout mcp` only. To choose the port for `npm run dev`, pass it after `--`:
861
918
 
862
919
  ```sh
863
920
  npm run dev -- --port 5180
@@ -926,11 +983,23 @@ It behaves as a Mac app does:
926
983
  - Closing the window leaves it running, still watching your sessions and showing notifications. Click its icon in the Dock to open the window again. Cmd+Q quits it.
927
984
  - With its window open or closed, its [notifications](#notifications) come from Agent Lookout, not from Script Editor, and clicking one opens the window.
928
985
  - Its icon in the Dock shows how many sessions need you, with the window open or closed, and no number when none do.
986
+ - Its icon in the menu bar shows the same count, and lists those sessions when you click it. Choose one to open its details. [Menu bar](#menu-bar) says more.
929
987
  - Settings… in the Agent Lookout menu, or Cmd+comma, opens the Settings view. The Help menu opens this guide.
930
988
  - Right-click selected text to copy it, or in the search field to cut, copy and paste.
931
989
  - It opens where you left it, at the size you left it.
990
+ - It keeps its [history](#history) in the same folder as `npx agent-lookout`, so the Events log and the charts carry over between the two.
932
991
  - Check for Updates…, in the Agent Lookout menu under About, checks for a newer version at once. [Updates](#updates) says how.
933
992
 
993
+ ### Menu bar
994
+
995
+ The app puts the Agent Lookout mark in the menu bar, in the menu bar's own colour, light or dark. While one or more sessions need you, the small circle above its horizon, the lamp, is filled and the number of them is beside it, with the window open or closed. While none do, it is the mark alone, with the lamp hollow.
996
+
997
+ Click it to list the sessions that need you, the longest wait first, each with how long it has waited, as in `checkout-flow · 4m 12s`. Two sessions with the same name have what tells them apart in brackets, their agent, project, branch or app, as in `checkout-flow (Codex) · 4m 12s`. Under each name are the reason and what the session is asking, when that is known, cut to one line: rest the pointer on a session to read all of it. macOS shows the line under a name from macOS 14.4 on; before that, resting the pointer is the way to read it. Up to 10 sessions are listed, and a line under them counts the rest. Choose a session to open its [details](#a-sessions-details) in the window, which opens if it was closed. With nothing waiting, the menu says Nothing needs you, and before Agent Lookout has read any agent, Looking for agents…. It ends with Open Agent Lookout, Check for Updates…, Settings… and Quit Agent Lookout.
998
+
999
+ The times are those of the moment the pointer comes over the icon, or at most a minute old when you open the menu from the keyboard, and while it is open it stays as it is. A session that starts or stops waiting meanwhile shows the next time you open it, and the count beside the icon follows at once.
1000
+
1001
+ To take the icon out of the menu bar, open Settings and, under Menu bar, set Show in menu bar to Off. Set it to On to put it back. The app remembers the choice, and the count on the Dock icon stays either way. If macOS does not let the app put its icon there, the switch reads Off and the app's log, `~/Library/Logs/Agent Lookout/main.log`, says why.
1002
+
934
1003
  ### Updates
935
1004
 
936
1005
  The app can update itself from the project's [releases on GitHub](https://github.com/Olanetsoft/agent-lookout/releases). About once a day while it runs, it asks GitHub whether a newer version is out. It sends nothing about your sessions: GitHub sees the app's version number and your IP address, as with any web request. [Updates (Mac app only)](../PRIVACY.md#updates-mac-app-only) has the details. To turn it off, open Settings and, under Updates, set Check for updates automatically to Off. Check for Updates…, in the Agent Lookout menu and in Settings, checks at once either way. `npx agent-lookout` and the repository never check for anything.
@@ -953,6 +1022,16 @@ To work on the app, `npm run dev:desktop` builds it and opens it from `dist-elec
953
1022
 
954
1023
  ## When something goes wrong
955
1024
 
1025
+ ### It says it needs a newer Node.js
1026
+
1027
+ `npx agent-lookout` stops at once with one line, such as:
1028
+
1029
+ ```text
1030
+ Agent Lookout needs Node.js 22.12 or newer, and this is Node.js 20.19.4.
1031
+ ```
1032
+
1033
+ The `node` your terminal runs is older than that, and npm may also have printed `EBADENGINE Unsupported engine` as it installed the package. Install Node.js 22.12 or newer, from [nodejs.org](https://nodejs.org) or with a version manager, as `nvm install 22` does with nvm. Check that `node --version` prints `v22.12.0` or later, then start it again. `agent-lookout` says the same and exits with 1, and `agent-lookout status` exits with 2, so a script never takes it for a session that needs you. The [Mac app](#desktop-app) needs no Node.js.
1034
+
956
1035
  ### The port is in use
957
1036
 
958
1037
  `npx agent-lookout` stops with `Port 4777 is already in use`. Choose another port:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-lookout",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Shows the AI agent sessions on your computer in one browser page, and which need you.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -40,7 +40,7 @@
40
40
  "SECURITY.md"
41
41
  ],
42
42
  "engines": {
43
- "node": "^20.19.0 || >=22.12.0"
43
+ "node": ">=22.12.0"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public"
@@ -1 +0,0 @@
1
- import{r as w,j as t}from"./react-C2DiJaR6.js";import{b as Y,t as G,r as K,f as q,a as _,c as U,d as X,m as V,H as J,e as Q,D as Z,g as M,s as ee,h as te,i as se,S as ne,L as ae,C as oe,j as re}from"./index-DRcy1hT5.js";import"./motion-BAhdRuCw.js";import"./radix-27o0jeIu.js";const ie={"needs-you":"text-status-needs-you-edge",working:"text-status-working",idle:"text-status-idle"},T={top:12,right:12,bottom:24,left:30},I=220,le=76,de=104,L=1/60;function ce({samples:s,start:a,end:o,tone:g,label:k,describe:i,className:h}){const p=w.useRef(null),[c,y]=w.useState(0),[u,l]=w.useState(null);w.useEffect(()=>{const e=p.current;if(!e)return;const n=new ResizeObserver(([j])=>{j&&y(Math.floor(j.contentRect.width))});return n.observe(e),()=>n.disconnect()},[]);const r=T.left,m=Math.max(r+1,c-T.right),x=T.top,d=I-T.bottom,N=o-a;let O=0;for(const e of s)e.at>=a&&e.at<=o&&e.value>O&&(O=e.value);const A=X(O),b=Y(s,{start:a,end:o},{left:r,right:m,top:x,bottom:d,ceiling:A.ceiling}),W=e=>r+(e-a)/N*(m-r),S=e=>d-e/A.ceiling*(d-x),P=G(a,o,Math.max(1,Math.floor((m-r)/le))),R=u??1,H=a+R*N,E=K(b.runs,H),C=E?i(E.value):"Not measured",v=W(H),B=(e,n)=>{const j=n.left+r,f=m-r;l(Math.min(1,Math.max(0,(e-j)/f)))},z=e=>{const j={ArrowLeft:f=>f-L,ArrowRight:f=>f+L,PageDown:f=>f-L*10,PageUp:f=>f+L*10,Home:()=>0,End:()=>1}[e.key];j&&(e.preventDefault(),l(Math.min(1,Math.max(0,j(u??1)))))};return t.jsxs("div",{ref:p,"data-slot":"history-chart",role:"slider",tabIndex:0,"aria-label":`${k}. Arrow keys read the count at each moment.`,"aria-orientation":"horizontal","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(R*100),"aria-valuetext":`${_(H)}: ${C}`,onPointerMove:e=>B(e.clientX,e.currentTarget.getBoundingClientRect()),onPointerLeave:()=>l(null),onFocus:e=>{e.currentTarget.matches(":focus-visible")&&l(n=>n??1)},onBlur:()=>l(null),onKeyDown:z,className:U("relative w-full cursor-crosshair rounded-row",ie[g],h),style:{height:I},children:[c>0&&b.unmeasured.map((e,n)=>t.jsx("div",{"aria-hidden":!0,"data-part":"unmeasured",className:"unmeasured-hatch absolute",style:{left:e.x,top:x,width:e.width,height:d-x}},n)),c>0&&t.jsxs("svg",{"aria-hidden":!0,width:c,height:I,className:"relative block",children:[A.ticks.map(e=>t.jsxs("g",{children:[t.jsx("line",{"data-part":"grid",x1:r,x2:m,y1:S(e),y2:S(e),stroke:e===0?"var(--rule-strong)":"var(--hairline)",strokeWidth:1,shapeRendering:"crispEdges"}),t.jsx("text",{x:r-8,y:S(e),dy:"0.32em",textAnchor:"end",className:"fill-ink-muted font-mono text-micro",children:e})]},e)),b.unmeasured.map((e,n)=>e.width>=de&&t.jsx("text",{"data-part":"unmeasured-label",x:e.x+8,y:x+14,className:"fill-ink-muted font-mono text-micro",stroke:"var(--glass-float)",strokeWidth:4,strokeLinejoin:"round",paintOrder:"stroke",children:"Not measured"},n)),b.areas.map((e,n)=>t.jsx("path",{d:e,fill:"currentColor",stroke:"none",opacity:.1},n)),b.lines.map((e,n)=>t.jsx("path",{"data-part":"line",d:e,fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},n)),P.map(e=>{const n=W(e);return n<r+16||n>m-16?null:t.jsx("text",{"data-part":"time-tick",x:n,y:I-6,textAnchor:"middle",className:"fill-ink-muted font-mono text-micro",children:q(e)},e)}),u!==null&&t.jsxs("g",{"data-part":"crosshair",children:[t.jsx("line",{x1:v,x2:v,y1:x,y2:d,stroke:"var(--ink-muted)",strokeWidth:1,shapeRendering:"crispEdges"}),E&&t.jsx("circle",{cx:v,cy:S(Math.min(E.value,A.ceiling)),r:4.5,fill:"currentColor",stroke:"var(--glass-float)",strokeWidth:2})]})]}),u!==null&&c>0&&t.jsxs("div",{"aria-hidden":!0,"data-part":"reading",className:"pointer-events-none absolute z-10 rounded-row bg-glass-float px-2.5 py-1.5 font-mono text-fact whitespace-nowrap inset-ring inset-ring-rule",style:R>.62?{top:x,right:c-v+10}:{top:x,left:v+10},children:[t.jsx("span",{className:"block text-ink-muted",children:_(H)}),t.jsx("span",{className:"block text-ink",children:C})]})]})}const ue=6e4;function he(s,a){const[o,g]=w.useState(null),[k,i]=w.useState(null),h=s>J,p=a?.startedAt??null;if(w.useEffect(()=>{if(!h)return;let y=!0;const u=()=>{Q(s).then(r=>{y&&(g({windowMs:s,history:r}),i(null))},()=>{y&&i(s)})};u();const l=setInterval(u,ue);return()=>{y=!1,clearInterval(l)}},[h,s,p]),!h)return{history:a,status:a?"ready":"loading"};const c=o?.windowMs===s&&(p===null||o.history.startedAt===p)?o.history:null;return c?{history:V(c,a),status:"ready"}:{history:null,status:k===s?"failed":"loading"}}const F={needsYou:{title:"Needs you",about:"How many sessions were waiting on you, over time.",tone:"needs-you",chart:"Sessions that need you",describe:s=>`${s} ${s===1?"needs":"need"} you`,explainer:"A session needs you when it has stopped and cannot go on without an answer: a permission to grant, a question, or a plan to approve. The line is how many were waiting at each moment."},working:{title:"Working",about:"How many sessions were in the middle of a task, over time.",tone:"working",chart:"Sessions working",describe:s=>`${s} working`,explainer:"A session is working while it is in the middle of a task. The line is how many were busy at each moment."},idle:{title:"Idle",about:"How many sessions were ready for a new prompt, over time.",tone:"idle",chart:"Idle sessions",describe:s=>`${s} idle`,explainer:"A session is idle when it has finished what it was asked and is ready for a new prompt. One left idle a day or more is stale, and is counted under Stale instead. The line is how many were idle at each moment."}},$="–";function D({label:s,value:a,caption:o}){return t.jsxs("div",{"data-slot":"figure",className:"min-w-0",children:[t.jsx("p",{className:"text-body font-medium text-ink-secondary",children:s}),t.jsx("p",{className:"mt-2 text-stat font-medium tabular-nums",children:a}),t.jsx("p",{className:"mt-2 text-caption text-ink-muted",children:o})]})}function me({metric:s,state:a,now:o}){const[g,k]=w.useState("15m"),i=M.find(d=>d.id===g)??M[0],h=F[s],{history:p,status:c}=he(i.ms,a.history),y=o-i.ms,u=p?ee(p.points,s):[];let l=null;for(const d of u)d.at<y||d.at>o||(l===null||d.value>l)&&(l=d.value);const r=u[u.length-1],m=r&&o-r.at<=te?r.value:null,x=h.tone==="needs-you"&&se(a.snapshot?.sessions)===0?"idle":h.tone;return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-5 flex flex-wrap items-end justify-between gap-x-6 gap-y-4",children:[t.jsxs("div",{className:"flex gap-6",children:[t.jsx(D,{label:"Now",value:m===null?$:String(m),caption:m===null?"Not being measured":"At the last check"}),t.jsx("div",{className:"border-l border-rule pl-6",children:t.jsx(D,{label:"Highest",value:l===null?$:String(l),caption:l===null?"Nothing measured":`In ${i.words}`})})]}),t.jsx(ne,{label:"Time window",value:g,onValueChange:k,options:M.map(({id:d,label:N})=>({value:d,label:N}))})]}),t.jsx("div",{className:"mt-5 h-55",children:c==="ready"?t.jsx(ce,{samples:u,start:y,end:o,tone:x,label:`${h.chart} over ${i.words}`,describe:h.describe}):c==="loading"?t.jsx(ae,{label:`Reading ${i.words}`,className:"h-full"}):t.jsx(oe,{tone:"error",title:`${re(i.words)} could not be read`,children:t.jsx("p",{children:"Agent Lookout did not answer when asked for it. The page asks again every minute. The 15 minute window is still there."})})}),t.jsxs("div",{"data-slot":"explainer",className:"mt-5 rounded-inner bg-fill-quiet px-4 py-3 inset-ring inset-ring-hairline",children:[t.jsx("p",{className:"text-row font-semibold text-ink",children:"What this counts"}),t.jsx("p",{className:"mt-0.5 text-body text-ink-secondary",children:h.explainer}),t.jsx("p",{className:"mt-1.5 text-body text-ink-secondary",children:"Hatched time was not measured: Agent Lookout was not running, or could not read its source. It is never drawn as zero. History covers the last six hours and starts again when Agent Lookout restarts."})]})]})}function ye({metric:s,open:a,onOpenChange:o,state:g,now:k}){const i=F[s];return t.jsx(Z,{open:a,onOpenChange:o,title:i.title,description:i.about,size:"wide",children:t.jsx(me,{metric:s,state:g,now:k})})}export{ye as HistoryPanel};
@@ -1 +0,0 @@
1
- import{r as y,j as e}from"./react-C2DiJaR6.js";import{w as F,u as A,J as $,c as E,D as L,k as M,l as O,M as v,n as q,o as D,p as C,F as I,q as h,v as P,x as k,O as R,f as g,T as W,y as B,z as J,A as K,B as _,E as z,G,I as H,K as w,N as S,P as Q,Q as U,L as V,R as X}from"./index-DRcy1hT5.js";import"./motion-BAhdRuCw.js";import"./radix-27o0jeIu.js";const T=[];function Y(s,n){const t=CSS.escape(s),a=[...document.querySelectorAll(`:is([data-slot="session-row"], [data-slot="board-card"], [data-slot="hero-session"])[data-session="${t}"] a[data-part="name"]`)];return n!==null&&a.includes(n)?n:a.find(l=>!l.closest('[data-slot="hero-session"]'))??a[0]??null}function Z({session:s,gone:n,asOf:t,now:a}){const l=s.status==="finished"||s.status==="failed",r=B(s),o=r?"stale":n&&s.status==="needs-you"?"answered":s.status,c=s.statusSince,i=c!==null?Math.max(0,t-c):null,m=J(s,t),d=s.status==="needs-you",p=d?K(s):null,j=d&&!n&&s.waitingText?.trim()||null,u=c!==null?`${l?"at":r?"idle since":"since"} ${k(c,a)}`:"time not reported",x=[d?_(s):null,u,m&&z(m)].filter(f=>typeof f=="string");return e.jsxs(h,{label:"Status",note:j?e.jsxs(e.Fragment,{children:[e.jsx(Q,{"data-part":"asking",lines:2,className:"wrap-anywhere",children:j}),p&&e.jsx("span",{className:"block",children:p})]}):p??void 0,children:[e.jsxs("span",{className:"inline-flex items-center gap-2",children:[e.jsx(G,{kind:o}),e.jsx("span",{"data-part":"status",children:r?"Stale":H[s.status]}),i!==null&&e.jsxs("span",{"data-part":"duration",className:"tabular-nums",children:[e.jsx("span",{className:"sr-only",children:l?` ${w(i)} ago`:` for ${w(i)}`}),e.jsxs("span",{"aria-hidden":!0,children:[S(i),l&&" ago"]})]})]}),e.jsx("span",{"data-part":"since",className:"block text-caption font-normal text-ink-secondary tabular-nums",children:x.join(", ")})]})}function ee({session:s,state:n,now:t,asOf:a}){const l=y.useMemo(()=>U({sessions:n.snapshot?.sessions??T,events:n.events,history:n.history,now:t,asOf:a,eventsFull:n.events.length>=v}),[n.snapshot,n.events,n.history,t,a]);if(!l)return e.jsxs(h,{label:"Waits",children:["Not known",e.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:"The history could not be read"})]});const r=l.sessions.find(i=>i.id===s.id),o=l.period.today?"today":`since ${g(l.period.from)}`;let c="None";if(r){const i=`${r.startKnown?"":"at least "}${S(r.ms)}`;c=r.times===1?`Once, ${i}`:`${r.times} times, ${i} in all`}return e.jsxs(h,{label:"Waits",children:[e.jsx("span",{"data-part":"waits",className:"tabular-nums",children:c}),e.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:o})]})}function N({title:s,aside:n,children:t,...a}){return e.jsxs("section",{...a,"aria-label":s,className:"mt-6",children:[e.jsxs("div",{className:"flex items-baseline justify-between gap-4 border-b border-hairline pb-2",children:[e.jsx("h3",{className:"text-body font-semibold text-ink",children:s}),n&&e.jsx("p",{className:"text-caption text-ink-muted",children:n})]}),t]})}function se({session:s,gone:n,state:t,now:a,asOf:l}){const r=t.snapshot?.sources??[],o=t.snapshot?.sessions??T,c=O(s.surface),i=t.events.length>=v,m=y.useMemo(()=>q(t.events.filter(u=>u.sessionId===s.id),o),[t.events,o,s.id]),d=D(t.events,t.history,i),p=y.useMemo(()=>t.history?C({sessions:o,events:t.events,history:t.history,now:a,asOf:l,eventsFull:i}):null,[o,t.events,t.history,a,l,i]),j=p?.rows.find(u=>u.id===s.id);return e.jsxs(e.Fragment,{children:[n&&e.jsx("p",{"data-part":"gone",className:"mt-4 text-body text-ink-secondary",children:"This session has left the list. Here is what was last known of it."}),e.jsxs(I,{className:"mt-3",children:[e.jsx(Z,{session:s,gone:n,asOf:l,now:a}),e.jsx(h,{label:"Agent",children:P(s,r)}),c!==null&&e.jsx(h,{label:"App",children:c}),e.jsx(h,{label:"Folder",mono:s.cwd!==null,children:s.cwd!==null?e.jsx("span",{"data-part":"folder",className:"wrap-anywhere select-text",children:s.cwd.split("/").map((u,x,f)=>e.jsxs(y.Fragment,{children:[u,x<f.length-1&&e.jsxs(e.Fragment,{children:["/",e.jsx("wbr",{})]})]},x))}):"Not known"}),s.git?.repository!==void 0&&e.jsx(h,{label:"Repository",children:e.jsx("span",{"data-part":"repository",className:"wrap-anywhere",children:s.git.repository.name})}),s.git?.branch!==void 0?e.jsx(h,{label:"Branch",children:e.jsx("span",{"data-part":"branch",className:"wrap-anywhere",children:s.git.branch})}):s.git?.commit!==void 0&&e.jsx(h,{label:"Commit",mono:!0,children:s.git.commit}),e.jsx(h,{label:"Started",children:e.jsx("span",{className:"tabular-nums",children:s.startedAt!==null?k(s.startedAt,a):"Not reported"})}),s.pid!==void 0&&e.jsx(h,{label:"Process",mono:!0,note:s.alive===!1?"The process has ended.":void 0,children:s.pid}),e.jsx(ee,{session:s,state:t,now:a,asOf:l})]}),e.jsx(N,{"data-part":"events",title:"Events",aside:d?`since ${g(d.at)}`:void 0,children:m.length>0?e.jsx(R,{entries:m,now:a,className:"pt-0.5"}):e.jsxs("p",{className:"py-2.5 text-body text-ink-secondary",children:["Nothing has happened to this session"," ",d?`since ${g(d.at)}`:"yet","."]})}),e.jsx(N,{"data-part":"timeline",title:"Timeline",aside:"last hour",children:p&&j?e.jsx(W,{timeline:p,rows:[j],named:!1,label:"Its last hour",className:"pt-3"}):e.jsx("p",{className:"py-2.5 text-body text-ink-secondary",children:p?"It did not run in the last hour.":"The last hour could not be read. The page asks again every two seconds."})})]})}function te({sessionId:s,open:n,onClose:t,state:a,now:l}){const r=a.snapshot?.sessions.find(b=>b.id===s)??null,[o,c]=y.useState(null);r!==null&&r!==o?.session?c({session:r,at:a.lastOkAt??l}):r===null&&o?.session.waitingText!==void 0&&c({...o,session:F(o.session)});const i=r??o?.session??null,m=r===null&&o!==null,d=A(i??{id:s,source:"status-files",surface:"unknown",links:{}}),p=m&&o?o.at:a.phase==="stalled"&&a.lastOkAt!==null?a.lastOkAt:l,j=d.outcome!==null||d.asking!==null;let u,x,f="wide";return i?(u=i.name,x=e.jsxs(e.Fragment,{children:[!m&&e.jsx("div",{className:E("flex flex-wrap items-center gap-x-2 gap-y-1",j&&"mt-3"),children:e.jsx($,{session:i,jump:d})}),e.jsx(se,{session:i,gone:m,state:a,now:l,asOf:p})]})):a.snapshot===null?(u="Session",x=e.jsx(V,{label:"Reading sessions",className:"justify-start px-0 pt-5 pb-1"})):(u="No such session",f="sm",x=e.jsxs("div",{"data-part":"missing",children:[e.jsx("p",{className:"mt-2 text-body text-ink-secondary",children:"Agent Lookout is not watching a session at this address. It may have ended before this page was opened."}),e.jsx(X,{className:"mt-4",onClick:t,children:"Go to the Overview"})]})),e.jsx(L,{open:n,onOpenChange:b=>{b||t()},title:u,size:f,focusTitle:!0,returnFocus:b=>Y(s,b)??document.querySelector("main"),actions:i&&!m&&e.jsx(M,{session:i,jump:d,variant:i.status==="needs-you"?"needs-you":"quiet"}),children:x})}function re({sessionId:s,onClose:n,state:t,now:a}){const[l,r]=y.useState(s);return s!==null&&s!==l&&r(s),l===null?null:e.jsx(te,{sessionId:l,open:s!==null,onClose:n,state:t,now:a},l)}export{re as SessionPanel};