agent-lookout 0.2.9 → 0.2.10

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/API.md CHANGED
@@ -146,6 +146,7 @@ Sessions come in this order: those that need you first, longest wait first, then
146
146
  | `startedAt` | When it started, or `null` |
147
147
  | `statusSince` | When its status last changed, or `null`. It does not move while a wait goes on |
148
148
  | `lastWriteAt` | When its agent last wrote the file it is read from, for Codex, the Antigravity CLI and status files |
149
+ | `tokens` | For Codex sessions, and sessions on another machine whose Agent Lookout sent them: `{ input, cached, output }`, the token counts its agent recorded for the session's newest reply. `input` is the whole prompt, cached tokens included, and is above 0. `cached` is the part of it read from a cache, left out when not recorded. `output` includes reasoning. Left out when there are none that could be right, never 0. No event, no point of history, no email, post or push, no answer of `agent-lookout mcp` and no output of `agent-lookout status` holds them |
149
150
  | `pid`, `alive` | Its process and whether that still runs, when known |
150
151
  | `links.open` | A `vscode://` address that opens it, for a session in VS Code |
151
152
  | `jump` | `{ kind: "tmux", place }` or `{ kind: "terminal", app, place }` when `POST /api/jump` can take you to it |
@@ -162,12 +163,12 @@ Sessions come in this order: those that need you first, longest wait first, then
162
163
  | `advice` | One plain sentence saying what you can do about a problem, when the collector knows |
163
164
  | `watching` | `[{ label, value }]`: what it reads and runs, and how often |
164
165
  | `basis` | Which way the sessions were read, for a source that has more than one |
165
- | `capabilities` | What its agent can report, and whether its sessions can be stopped and answered: for each of nine things, `{ level: "yes" }`, or `{ level: "no" }` or `"partly"` with a `reason` |
166
+ | `capabilities` | What its agent can report, and whether its sessions can be stopped and answered: for each of ten things, `{ level: "yes" }`, or `{ level: "no" }` or `"partly"` with a `reason` |
166
167
  | `checkedAt` | When it was last read |
167
168
  | `machine` | For another machine: its name, which its sessions carry as their `machine` |
168
169
  | `agents` | For another machine: `[{ label, capabilities }]`, what each agent found there can report, as last read from it, with `jump`, `stop` and `answer` always `no` |
169
170
 
170
- The [guide](GUIDE.md#what-each-agent-can-report) has the table `capabilities` holds.
171
+ The [guide](GUIDE.md#what-each-agent-can-report) has the table `capabilities` holds. An agent on another machine whose Agent Lookout sends no `tokens` has `tokens` as `no`.
171
172
 
172
173
  Another machine that `AGENT_LOOKOUT_REMOTES` names is a source of its own, after this computer's three. Its `state` is `ok` while its sessions are read, `searching` while ssh signs in the first time, for 10 seconds at most, and `unavailable`, with the reason in `detail` and what to do in `advice`, for anything else: still connecting after those 10 seconds, ssh not found, the machine turned ssh away, the connection dropped, no Agent Lookout answering there, or an answer that cannot be read. A machine that is switched off is no fault of this computer, so it is not `error`. Its `watching` gives the ssh target, the command, the routes asked for, how often, the version there and the state of each source there, the first 20. When `AGENT_LOOKOUT_REMOTES` is set but cannot be read, one source stands for the machines instead: `remote:`, with no name after it, labelled `Other machines`, `not-set-up`, with what is wrong in `detail` and how to correct it in `advice`, and no sessions. Its sessions are listed with this computer's, in the same order, and counted in the same way. [Another machine over SSH](GUIDE.md#another-machine-over-ssh) has the rest.
173
174
 
@@ -635,18 +636,20 @@ The plugin's hook reaches Agent Lookout another way, not through this API: a Uni
635
636
 
636
637
  ## In the Mac app only
637
638
 
638
- The Mac app serves the dashboard and this API from its own address, `agent-lookout://app/`, with no port, and answers six more routes there, for its updates and its menu bar item. `npm start`, `npx agent-lookout` and `npm run dev` do not have them: there, each answers 404.
639
+ The Mac app serves the dashboard and this API from its own address, `agent-lookout://app/`, with no port, and answers eight more routes there, for its updates, its menu bar item and its own notifications. `npm start`, `npx agent-lookout` and `npm run dev` do not have them: there, a GET answers 404 and a POST 405.
639
640
 
640
- | Route | Body | Does |
641
- | -------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
642
- | `GET /api/app/update` | | Where updates stand: the version, the switch, the last check, what was found and how far a download has got |
643
- | `POST /api/app/update/check` | `{}` | Checks GitHub now, and answers once it has |
644
- | `POST /api/app/update/install` | `{}` | Installs the version that is ready, then quits and opens it. 409 when none is ready, or it cannot be installed where the app runs |
645
- | `POST /api/app/update/setting` | `{ "automatic": true }` or `false` | Turns the daily check on or off |
646
- | `GET /api/app/menu-bar` | | Whether the menu bar item is shown: `{ "show": true }` or `false` |
647
- | `POST /api/app/menu-bar/setting` | `{ "show": true }` or `false` | Shows the menu bar item or takes it away |
641
+ | Route | Body | Does |
642
+ | ---------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
643
+ | `GET /api/app/update` | | Where updates stand: the version, the switch, the last check, what was found and how far a download has got |
644
+ | `POST /api/app/update/check` | `{}` | Checks GitHub now, and answers once it has |
645
+ | `POST /api/app/update/install` | `{}` | Installs the version that is ready, then quits and opens it. 409 when none is ready, or it cannot be installed where the app runs |
646
+ | `POST /api/app/update/setting` | `{ "automatic": true }` or `false` | Turns the daily check on or off |
647
+ | `GET /api/app/menu-bar` | | Whether the menu bar item is shown: `{ "show": true }` or `false` |
648
+ | `POST /api/app/menu-bar/setting` | `{ "show": true }` or `false` | Shows the menu bar item or takes it away |
649
+ | `GET /api/app/notifications` | | The reason macOS gave the last time it would not show a notification the app showed itself, a test or one while its window was closed: `{ "lastRefusal": "…" }`, with `null` when there is none |
650
+ | `POST /api/app/notifications/test` | `{}` | Shows one test notification and answers with what macOS made of it |
648
651
 
649
- Each POST passes the checks `POST /api/jump` does, with its own `X-Agent-Lookout-Action` and a body that is exactly what the table says. For updates, the action is `check-for-updates`, `install-update` or `update-setting`, the body is 256 bytes or less, and each answers with the status `GET /api/app/update` gives, `AppUpdateStatus` in `src/core/appUpdate.ts`. For the menu bar item, the action is `menu-bar-setting`, the body is 64 bytes or less, and it answers with what `GET /api/app/menu-bar` gives, `MenuBarStatus` in `src/core/appMenuBar.ts`. That says `false` while the item could not be put in the menu bar, even with the switch on. Nothing in a request reaches a command or a path.
652
+ Each POST passes the checks `POST /api/jump` does, with its own `X-Agent-Lookout-Action` and a body that is exactly what the table says. For updates, the action is `check-for-updates`, `install-update` or `update-setting`, the body is 256 bytes or less, and each answers with the status `GET /api/app/update` gives, `AppUpdateStatus` in `src/core/appUpdate.ts`. For the menu bar item, the action is `menu-bar-setting`, the body is 64 bytes or less, and it answers with what `GET /api/app/menu-bar` gives, `MenuBarStatus` in `src/core/appMenuBar.ts`. That says `false` while the item could not be put in the menu bar, even with the switch on. For the test notification, the action is `notification-test`, the body is 64 bytes or less, and it answers with `NotificationTestOutcome` in `src/core/notices/appNotifications.ts`: `{ "outcome": "shown" }` once macOS took it, `{ "outcome": "refused", "reason": "…" }` with macOS's reason, `{ "outcome": "unsupported" }`, or `{ "outcome": "no-answer" }` when macOS said nothing within 5 seconds, as while it asks whether Agent Lookout may show notifications. A refusal is kept until a notification is shown, and `GET /api/app/notifications` gives it. The test notification says only that it is a test. Nothing in a request reaches a command, a path or a notification.
650
653
 
651
654
  ## The notifications header
652
655
 
package/docs/GUIDE.md CHANGED
@@ -101,6 +101,7 @@ The details show everything Agent Lookout knows about the session:
101
101
  - its [repository](#repositories), and its [branch](#branches) or the commit when no branch is checked out
102
102
  - with pull requests on, its branch's [pull request](#pull-requests): its number and title, a link that opens it on github.com in your browser, whether it is open, a draft, merged or closed, and how many of its checks are failing, pending and passing
103
103
  - when it started and its process ID, when its agent reports them
104
+ - the token counts of its newest reply, as in `182,431 in, 9,120 out`, and under them how much of the input came from a cache. In is the whole prompt the reply was given, cached tokens included, which is how much of the session's context is in use. Out is what the reply wrote, its reasoning included. Agent Lookout reads them only for Codex sessions so far. A session with none has a dash and, under it, why: its agent's reason in [What each agent can report](#what-each-agent-can-report), that its machine sent none, or that none are recorded yet
104
105
  - how many times it waited for you and how long in all, over the same time as the bars in the Needs you panel
105
106
  - what a Claude Code session last said, under Last message
106
107
  - its own events, newest first, as the Events log shows them
@@ -218,14 +219,14 @@ Each [other machine](#another-machine-over-ssh) named in `AGENT_LOOKOUT_REMOTES`
218
219
 
219
220
  #### What each agent can report
220
221
 
221
- Under the cards, What each agent can report has a row for each agent and a column for each thing Agent Lookout can show of its sessions, and for [Stop](#stop-a-session) and [Answer](#answer-a-permission-prompt), what it can do to one, with Yes, No or Partly in each. Point at No or Partly, or move to it with Tab, to read why. In a narrow window each agent has a block of its own, with the reason under each No and Partly. A No means Agent Lookout cannot show it for that agent's sessions, so not seeing it is not good news. The [branch](#branches) is not in the table, because it is read the same way for every agent.
222
+ Under the cards, What each agent can report has a row for each agent and a column for each thing Agent Lookout can show of its sessions, and for [Stop](#stop-a-session) and [Answer](#answer-a-permission-prompt), what it can do to one, with Yes, No or Partly in each. Point at No or Partly, or move to it with Tab, to read why. In a narrow window each agent has a block of its own, with the reason under each No and Partly. A No means Agent Lookout cannot show it for that agent's sessions, so not seeing it is not good news. Tokens is whether a session's [details](#a-sessions-details) can give the token counts of its newest reply. The [branch](#branches) is not in the table, because it is read the same way for every agent.
222
223
 
223
- | Agent | Working and idle | Needs you | Finished | Failed | Names | Jump | Quiet for | Stop | Answer |
224
- | --------------- | ---------------- | --------- | -------- | ------ | ------ | ------ | --------- | ------ | ------ |
225
- | Claude Code | Yes | Yes | Partly | Partly | Yes | Partly | No | Partly | Partly |
226
- | Codex | Yes | No | Partly | No | Partly | No | Yes | No | No |
227
- | Antigravity CLI | Yes | Partly | Partly | Partly | Partly | No | Yes | No | No |
228
- | Status files | Partly | Partly | Partly | Partly | Partly | No | Partly | No | No |
224
+ | Agent | Working and idle | Needs you | Finished | Failed | Names | Jump | Quiet for | Tokens | Stop | Answer |
225
+ | --------------- | ---------------- | --------- | -------- | ------ | ------ | ------ | --------- | ------ | ------ | ------ |
226
+ | Claude Code | Yes | Yes | Partly | Partly | Yes | Partly | No | No | Partly | Partly |
227
+ | Codex | Yes | No | Partly | No | Partly | No | Yes | Yes | No | No |
228
+ | Antigravity CLI | Yes | Partly | Partly | Partly | Partly | No | Yes | No | No | No |
229
+ | Status files | Partly | Partly | Partly | Partly | Partly | No | Partly | No | No | No |
229
230
 
230
231
  Why, for each No and Partly:
231
232
 
@@ -233,6 +234,7 @@ Why, for each No and Partly:
233
234
  - Claude Code, Failed: Only background jobs. Any other session leaves the list without saying how it ended.
234
235
  - Claude Code, Jump: In VS Code, in tmux, and in a tab of Terminal or iTerm2 on a Mac. Not in the desktop app or another terminal.
235
236
  - Claude Code, Quiet for: The file Agent Lookout reads is not rewritten as a session works.
237
+ - Claude Code, Tokens: Agent Lookout does not read the token counts in its transcripts yet.
236
238
  - Claude Code, Stop: In a terminal, in VS Code and for background jobs. Not in the desktop app.
237
239
  - Claude Code, Answer: With the Agent Lookout plugin, by Allow, Deny or a permission rule. Allow only if all is shown: never edits, plans or questions.
238
240
  - Codex, Needs you: Codex does not record approval waits, so a session waiting for you shows as working.
@@ -247,6 +249,7 @@ Why, for each No and Partly:
247
249
  - Antigravity CLI, Failed: Only when agy records an error of its own or a failed reply. A tool that fails shows as working.
248
250
  - Antigravity CLI, Names: The title agy gives a conversation once it has one, and the folder its program's log names. Until then, its conversation ID.
249
251
  - Antigravity CLI, Jump: The transcripts name no terminal, and agy documents no link that opens a conversation.
252
+ - Antigravity CLI, Tokens: Agent Lookout does not read token counts from agy's transcripts yet.
250
253
  - Antigravity CLI, Stop: No agy program is tied to one conversation surely enough for Agent Lookout to confirm it and stop it.
251
254
  - Antigravity CLI, Answer: Its approval prompt takes an answer only in the terminal agy runs in.
252
255
  - Status files, Working and idle: If the agent writes working and idle.
@@ -256,14 +259,15 @@ Why, for each No and Partly:
256
259
  - Status files, Names: If the agent writes a name. Otherwise the folder's or the file's name is used.
257
260
  - Status files, Jump: Nothing in a status file is used to reach a session.
258
261
  - Status files, Quiet for: If the agent writes its file again as it works.
262
+ - Status files, Tokens: A status file has no field for token counts.
259
263
  - Status files, Stop: Any program can write a status file, so nothing in one is used to stop a session.
260
264
  - Status files, Answer: A status file only says a session waits, and holds nothing to answer it through, by hand or by a permission rule.
261
265
 
262
- Each [other machine](#another-machine-over-ssh) adds a row for each agent found there, such as Claude Code on devbox, with what that machine's Agent Lookout says its agent can report, and No for Jump, Stop and Answer, which act on this computer only.
266
+ Each [other machine](#another-machine-over-ssh) adds a row for each agent found there, such as Claude Code on devbox, with what that machine's Agent Lookout says its agent can report, and No for Jump, Stop and Answer, which act on this computer only. Under an older Agent Lookout there, which sends nothing for Tokens, each agent has No for Tokens.
263
267
 
264
268
  ### Settings
265
269
 
266
- Settings has twelve 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. [Time rules](#time-rules) reminds you of a long wait, sets how long a session is idle before it is stale, and holds everything back during quiet hours. 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, Webhook whether it posts to a webhook, such as a Slack channel's, and for what, and [ntfy](#ntfy) and [Pushover](#pushover) whether it pushes to your phone through either, and for what, with Send a test to try it. Pull requests says whether each session's [pull request](#pull-requests) is shown, and whether `gh` can be asked for them. All five are set up when you start it, not here. Permission prompts says whether Allow and Deny are offered, and whether the [plugin](#install-the-plugin)'s requests reach Agent Lookout, and [Permission rules](#permission-rules), under it, keeps the rules that answer a permission prompt for you, and lists what they answered. This copy shows the version you are running and where Agent Lookout sends your data: nowhere, or only in the emails, webhook posts and pushes you set up, and with pull requests on, the names of repositories and branches to GitHub, through `gh`, unless `gh` was not found or is not signed in, when nothing goes to GitHub.
270
+ Settings has twelve 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, and in the Mac app it sends a test to see what macOS answers. [Time rules](#time-rules) reminds you of a long wait, sets how long a session is idle before it is stale, and holds everything back during quiet hours. 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, Webhook whether it posts to a webhook, such as a Slack channel's, and for what, and [ntfy](#ntfy) and [Pushover](#pushover) whether it pushes to your phone through either, and for what, with Send a test to try it. Pull requests says whether each session's [pull request](#pull-requests) is shown, and whether `gh` can be asked for them. All five are set up when you start it, not here. Permission prompts says whether Allow and Deny are offered, and whether the [plugin](#install-the-plugin)'s requests reach Agent Lookout, and [Permission rules](#permission-rules), under it, keeps the rules that answer a permission prompt for you, and lists what they answered. This copy shows the version you are running and where Agent Lookout sends your data: nowhere, or only in the emails, webhook posts and pushes you set up, and with pull requests on, the names of repositories and branches to GitHub, through `gh`, unless `gh` was not found or is not signed in, when nothing goes to GitHub.
267
271
 
268
272
  #### Notifications
269
273
 
@@ -322,6 +326,32 @@ A notification shows the session's name outside the dashboard: over other apps,
322
326
 
323
327
  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.
324
328
 
329
+ ##### Notifications in the Mac app
330
+
331
+ In the [Mac app](#desktop-app), notifications are off until you turn them on, as in a browser. The app's setting is its own. Its page keeps the choice apart from any browser's, so turning notifications on in a browser does not turn them on in the app, and the other way round.
332
+
333
+ Until you choose, the Overview asks: "Get a notification when a session needs you?" Turn on turns them on and sends one test notification, so that if macOS has not asked before, it asks then whether Agent Lookout may show notifications. Allow it. Not now leaves them off. Either way it does not ask again, and the Notifications card in Settings turns them on or off at any time.
334
+
335
+ While the test is on its way, the question says "Sending a test…". If macOS shows it, the question goes. If it does not, a note in the question's place says "macOS did not show the test notification", and Send a test in Settings, under Notifications, says why and what to do. If macOS has not answered yet, as while it asks whether to allow Agent Lookout, the note says so: allow it, and send another test from Settings. If Agent Lookout does not answer, the note says "The test was not sent": send another test from Settings. Notifications stay on in each case. Press Dismiss to take the note away. It is also gone the next time you open the Overview.
336
+
337
+ In the app the card speaks of macOS and this app, not of a browser and a tab. The app always lets its page show notifications, whatever macOS decides, so the page cannot tell from that whether you will see one. A test can. Send a test, under the button, shows one notification that says only that it is a test, and the card says what macOS made of it. Turn on notifications sends one too.
338
+
339
+ | What the card says | What it means |
340
+ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
341
+ | macOS took the test at 14:02. | macOS took the notification. It can still hide the banner, as while the display is shared or mirrored |
342
+ | macOS has not answered yet. | macOS said nothing within 5 seconds, as while it asks whether Agent Lookout may show notifications. Answer it, then send a test again |
343
+ | macOS did not allow it | macOS refused it, and the note gives its reason. Open System Settings, choose Notifications, then Agent Lookout, and turn on Allow notifications. Then send a test again |
344
+ | Agent Lookout could not show a notification | This Mac gives the app no way to show one, or Electron, which the app is built on, could not make or show it. Quit Agent Lookout, open it again, and send another test |
345
+ | The test was not sent | The app did not answer. Try again in a moment |
346
+
347
+ If macOS refused a test, or a notification the app showed itself while its window was closed, since the app started, and has shown none of those since, the card says so under "macOS did not show the last notification", and while notifications are on it says "Notifications are on in this app, but macOS is not showing them." It says so when you open Settings, and again while a later test is on its way or when the app does not answer one. A test macOS refuses there counts, and one it shows clears it. A test macOS does not answer, or that Agent Lookout could not show, changes nothing. The notifications the page shows while the window is open are not counted, since the app does not hear what macOS made of them. To check those, press Send a test.
348
+
349
+ macOS hides banners while the display is shared or mirrored, unless Allow notifications when mirroring or sharing the display is on, in System Settings under Notifications.
350
+
351
+ A copy started with `npm run dev:desktop` runs as Electron, and macOS may refuse its notifications outright. To try them, build the app with `npm run dist:mac` and install it, as [Build it yourself](INSTALL.md#build-it-yourself) says.
352
+
353
+ A test notification shows on this Mac and is sent nowhere.
354
+
325
355
  #### Time rules
326
356
 
327
357
  The Time rules card has three rules, each with an Off and On switch, and all three are off until you turn them on. Agent Lookout keeps them in `~/.agent-lookout/settings.json`, so they hold with no dashboard tab open and after a restart. A change takes effect at once. The card shows what Agent Lookout saved: if a change could not be saved, a note says why and the switch stays as it was. A field is read when you leave it or press Enter, and Escape puts back what it held.
@@ -332,7 +362,7 @@ Under it, Remind again while it waits sends the reminder again while the session
332
362
 
333
363
  Mark idle sessions stale sets how long a session is idle before it is stale, in hours or days, from an hour to 30 days: 2 days unless you change them. A stale session is counted under Stale rather than Idle, its idle line in the Timeline turns to dots where it became stale, and a Claude Code session among them whose process still runs is listed under [Left running](#sessions-left-running), where it can be ended. With the rule off, a session is stale after a day.
334
364
 
335
- Quiet hours hold back every notification, email, post and push between two times on your computer's clock: 22:00 to 08:00 unless you change them. When the end comes before the start, they run past midnight, and the days you tick are the days they begin on: with Monday to Friday ticked, Friday night is quiet until Saturday morning, and Saturday night is not. With no day ticked they never begin. When they end, a session still waiting is notified as usual, or reminded of if it was told of before they began and its reminder came due in them, and everything else held comes as one summary on each channel, such as "While quiet: checkout-flow waited 25 minutes, docs-site waited 1 hour 5 minutes over 2 waits and billing-webhooks finished": one notification headed While quiet, which stays until you clear it, one email, one webhook post and one push each through ntfy and Pushover. A session that waited and then finished, failed or ended is one item, as in "checkout-flow waited 25 minutes then ended". A wait whose reminders came due in quiet hours and that was answered before they ended is one item too, however many came due. Each summary holds what its channel would have sent, so a wait answered before the email's delay was up is in no email. Turn on Leave answered waits out of the summary and it names only the sessions that finished, failed or ended. With nothing to say, there is no summary. Turning quiet hours off while they hold ends them at once. While they hold, a line under the times says so: "Quiet now, until 08:00. Notifications, emails, posts and pushes are held." In the Mac app, the notice of a new version waits for them to end too.
365
+ Quiet hours hold back every notification, email, post and push between two times on your computer's clock: 22:00 to 08:00 unless you change them. A test, which you ask for, is not held. When the end comes before the start, they run past midnight, and the days you tick are the days they begin on: with Monday to Friday ticked, Friday night is quiet until Saturday morning, and Saturday night is not. With no day ticked they never begin. When they end, a session still waiting is notified as usual, or reminded of if it was told of before they began and its reminder came due in them, and everything else held comes as one summary on each channel, such as "While quiet: checkout-flow waited 25 minutes, docs-site waited 1 hour 5 minutes over 2 waits and billing-webhooks finished": one notification headed While quiet, which stays until you clear it, one email, one webhook post and one push each through ntfy and Pushover. A session that waited and then finished, failed or ended is one item, as in "checkout-flow waited 25 minutes then ended". A wait whose reminders came due in quiet hours and that was answered before they ended is one item too, however many came due. Each summary holds what its channel would have sent, so a wait answered before the email's delay was up is in no email. Turn on Leave answered waits out of the summary and it names only the sessions that finished, failed or ended. With nothing to say, there is no summary. Turning quiet hours off while they hold ends them at once. While they hold, a line under the times says so: "Quiet now, until 08:00. Notifications, emails, posts and pushes are held." In the Mac app, the notice of a new version waits for them to end too.
336
366
 
337
367
  The rules apply on top of the switches under Notifications, and of the settings for [email](#email), the [webhook](#webhook), [ntfy](#ntfy) and [Pushover](#pushover), which still choose what goes where. Quiet hours go by the clock and time zone of the computer Agent Lookout runs on, and a dashboard tab follows what Agent Lookout says, so a tab in another time zone holds and sums up at the same moments. A dashboard tab opened during quiet hours sums up what happened after it opened. With no tab open, Agent Lookout shows its own summary on a Mac, and it covers all of the quiet hours. The rules set here cover the sessions of [another machine](#another-machine-over-ssh) too, as they cover this computer's.
338
368
 
@@ -771,7 +801,7 @@ A request waits for an answer for 5 minutes, or `AGENT_LOOKOUT_ANSWER_WAIT` seco
771
801
 
772
802
  The [Mac app](#desktop-app) offers Deny and Allow outside its window too: on its own notification of a wait, and in its [menu bar](#menu-bar). A press there is answered as a press on the dashboard is, with every check it makes, and the Events log says the request was allowed or denied from Agent Lookout. As on the page, neither takes a press in the first second what it answers is shown, and Allow is offered only for what is shown there whole, as it is written, and never for an edit, a new file, a plan or a question.
773
803
 
774
- - **The notification.** These come only while [notifications](#notifications) are on in Settings, with Needs you switched on: the notification of a wait, and of a reminder of one while Remind me of a long wait is on, in [Time rules](#time-rules). While Agent Lookout holds the request of a session it tells you of, the notification has Deny, and Allow too for one shell command with no other input, on one line of at most 40 characters, with only plain spaces, no tab, no two spaces in a row and no space at either end, when the notification can hold all of it with the session's name: its text is then the whole command, under "Asks to run", or, for a reminder, under how long it has waited, "Has waited 40 minutes. Asks to run". For anything else it has Deny alone, and its text says why, or to open Agent Lookout to see it whole, then, on a line of its own, the start of what it asks: its first line, ending in "…" when more follows or it is not shown as written. macOS shows the buttons as it shows any notification's, which depends on the style chosen for Agent Lookout in System Settings, under Notifications. Click the notification itself to open the session's details. Once a button is pressed, the notification is taken down, and a press that sent nothing is followed by a notification that says why: click that one to answer in the session's details. The app shows these only while its window is closed, or the page in it is not checking the sessions: with the window open, the page shows its own notifications, which have no buttons.
804
+ - **The notification.** These come only while [notifications](#notifications) are on in the app's own Settings, with Needs you switched on: the notification of a wait, and of a reminder of one while Remind me of a long wait is on, in [Time rules](#time-rules). While Agent Lookout holds the request of a session it tells you of, the notification has Deny, and Allow too for one shell command with no other input, on one line of at most 40 characters, with only plain spaces, no tab, no two spaces in a row and no space at either end, when the notification can hold all of it with the session's name: its text is then the whole command, under "Asks to run", or, for a reminder, under how long it has waited, "Has waited 40 minutes. Asks to run". For anything else it has Deny alone, and its text says why, or to open Agent Lookout to see it whole, then, on a line of its own, the start of what it asks: its first line, ending in "…" when more follows or it is not shown as written. macOS shows the buttons as it shows any notification's, which depends on the style chosen for Agent Lookout in System Settings, under Notifications. Click the notification itself to open the session's details. Once a button is pressed, the notification is taken down, and a press that sent nothing is followed by a notification that says why: click that one to answer in the session's details. The app shows these only while its window is closed, or the page in it is not checking the sessions: with the window open, the page shows its own notifications, which have no buttons.
775
805
  - **The menu bar.** A session whose request is held opens a submenu instead of its details: Open Details, then what it asks, each line of the command on a line of its own, then, after a separator, each other input as its name and value, then Deny and Allow. Allow is there only when every line, and the line over them, is at most 80 characters, with only plain spaces, no tab, no two spaces in a row and no space at either end, in 20 lines at most, no input's name has a `:` in it, and the menu draws each line as it is written, which rules out a line with `...` or `(&)` in it. Otherwise Deny is there alone, with a line that says why. What a press came to is the menu's second line for a minute, and a press that sent nothing opens the menu again to say why.
776
806
 
777
807
  Nothing is answered while the Mac is locked, should macOS let a notification's buttons be pressed on the lock screen. With `AGENT_LOOKOUT_ANSWER=off`, or when Agent Lookout cannot listen for the plugin, neither has Deny or Allow, and the dashboard in a browser and `npx agent-lookout` have neither of these.
@@ -1467,8 +1497,8 @@ To install it, download it from the [latest release](https://github.com/Olanetso
1467
1497
  It behaves as a Mac app does:
1468
1498
 
1469
1499
  - One copy runs at a time. Opening it again brings its window forward.
1470
- - 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.
1471
- - With its window open or closed, its [notifications](#notifications) come from Agent Lookout, not from Script Editor, and clicking one opens the window. A wait's notification that the app shows itself, with the window closed, or a reminder of one, opens the session's details, and can have Deny and Allow: [In the Mac app](#in-the-mac-app) says when.
1500
+ - Closing the window leaves it running, still watching your sessions and, once notifications are on in the app, showing them. Click its icon in the Dock to open the window again. Cmd+Q quits it.
1501
+ - With its window open or closed, its [notifications](#notifications) come from Agent Lookout, not from Script Editor, and clicking one opens the window. They are off until you turn them on, and the app's setting is apart from a browser's: until you choose, the Overview asks whether to turn them on. [Notifications in the Mac app](#notifications-in-the-mac-app) says more. A wait's notification that the app shows itself, with the window closed, or a reminder of one, opens the session's details, and can have Deny and Allow: [In the Mac app](#in-the-mac-app) says when.
1472
1502
  - Its icon in the Dock shows how many sessions need you, with the window open or closed, and no number when none do.
1473
1503
  - Its icon in the menu bar shows the same count, and lists those sessions when you click it. Choose one to open its details, or, while Agent Lookout holds its permission request, to answer it. [Menu bar](#menu-bar) says more.
1474
1504
  - Settings… in the Agent Lookout menu, or Cmd+comma, opens the Settings view. The Help menu opens this guide.
@@ -1563,6 +1593,8 @@ Use `http://localhost:5173` if you started it with `npm run dev`. It prints the
1563
1593
 
1564
1594
  ### No notification appears
1565
1595
 
1596
+ In the Mac app, open Settings in the app itself, since its setting is apart from a browser's, and press Send a test under Notifications. The card says whether macOS shows them, and [Notifications in the Mac app](#notifications-in-the-mac-app) what to do when it does not. In a browser:
1597
+
1566
1598
  1. Open Settings in the same browser, at the same address, where you turned notifications on. If the card says "Notifications are off.", press Turn on notifications. If it shows Notifications are blocked, allow notifications for this address in the browser's site settings first. Check that the event you expected is switched on in the list under the button.
1567
1599
  2. If the card says "Notifications are on.", your browser is allowed to show them, and macOS may be holding them back. The card reads the browser's permission and cannot see the system's. Open System Settings, then Notifications, choose your browser and check that Allow notifications is on.
1568
1600
  3. Check Focus. While a Focus such as Do Not Disturb is on, notifications go to Notification Centre without appearing on screen.
@@ -1633,7 +1665,7 @@ The program serving the page has stopped. The page keeps the last thing it saw,
1633
1665
 
1634
1666
  For Claude Code, Agent Lookout reads the small file Claude Code keeps for each running session in `~/.claude/sessions/`, every 2 seconds, whether the session runs in a terminal, in VS Code or in the desktop app. That starts no program and uses no network. When it starts, and every 30 seconds after that, it also runs `claude agents --json --all`, the command Claude Code [documents](https://code.claude.com/docs/en/agent-view) for listing its sessions. That answer decides which sessions exist, and it adds background jobs that have finished or failed. If the command cannot be found or fails, Agent Lookout uses the files alone. While a session is waiting for you, Agent Lookout also reads the end of its transcript, in `~/.claude/projects/`, to say what it is asking, and forgets it when the wait ends. It also reads that end when a session's last message is asked for, as [PRIVACY.md](../PRIVACY.md#a-sessions-last-message) says. Claude Code does not document its transcripts, so a new version can change them, and then the reason shows alone.
1635
1667
 
1636
- For Codex it runs nothing. Every 2 seconds it reads what Codex has added to the session files under `~/.codex/sessions/` and finds the last line that says a turn started or ended: a session with a turn under way is working, and one whose last turn ended is idle. A session that no Codex program has open is finished. Those files hold your conversations with Codex. Agent Lookout keeps only when each turn started and ended and a few details, such as the session's folder, and when each file was last changed, which says how long a working session has been [quiet](#quiet-for). Codex documents none of these files, so a new Codex version can change them.
1668
+ For Codex it runs nothing. Every 2 seconds it reads what Codex has added to the session files under `~/.codex/sessions/` and finds the last line that says a turn started or ended, and the newest line that counts a reply's tokens: a session with a turn under way is working, and one whose last turn ended is idle. A session that no Codex program has open is finished. Those files hold your conversations with Codex. Agent Lookout keeps only when each turn started and ended, the token counts of the newest reply and a few details, such as the session's folder, and when each file was last changed, which says how long a working session has been [quiet](#quiet-for). Codex documents none of these files, so a new Codex version can change them.
1637
1669
 
1638
1670
  For the Antigravity CLI, `agy`, it runs no agy program either. Every 2 seconds it looks at the names of the folders in `~/.gemini/antigravity-cli/brain` and `~/.gemini/antigravity-cli/conversations` and when each conversation's files were last changed, and reads the end of the transcript agy keeps for each conversation written in the last day, `brain/<conversation ID>/.system_generated/logs/transcript.jsonl`, for its last step: a prompt, a tool or a reply that asks for a tool is working, a reply that asks for none is idle, and an error agy reports itself is failed. To tell an open conversation from one that has ended, it asks `ps`, at most every 10 seconds, which agy programs are running and when each started, and reads the command lines of those programs alone. A conversation no agy program running could have open is finished. It reads the log each running agy program keeps in `~/.gemini/antigravity-cli/log`, only for the lines that say which folder it works in, which conversation it has open and whether it waits for your approval of a tool, and the title agy gives each conversation, in `annotations/`. Those transcripts and logs hold your conversations with agy and what agy does. Agent Lookout keeps only each last step's kind, status and time, and those few lines' facts. agy documents the transcript's format for its own agents, and not its log, so a new agy version can change either. This was built from agy 1.3.1's documentation and program, and checked against one conversation.
1639
1671
 
package/docs/INSTALL.md CHANGED
@@ -110,7 +110,11 @@ The app is not yet signed with an Apple Developer ID, so the first time a downlo
110
110
  2. Open System Settings › Privacy & Security.
111
111
  3. Under Security, next to the line that says Agent Lookout was blocked, press Open Anyway, and confirm with your password or Touch ID. The button is there for about an hour after macOS stopped the app. If it has gone, open the app again, press Done, and go back to Privacy & Security.
112
112
 
113
- macOS remembers the choice. [Desktop app](GUIDE.md#desktop-app) says how the app behaves, [Menu bar](GUIDE.md#menu-bar) what its icon in the menu bar lists, and [Updates](GUIDE.md#updates) how it checks for a newer version and installs it.
113
+ macOS remembers the choice.
114
+
115
+ Notifications are off until you turn them on in the app, and the app's setting is apart from a browser's. The first time it opens, the Overview asks "Get a notification when a session needs you?" Press Turn on, and allow Agent Lookout to show notifications if macOS asks. You can also turn them on later under Notifications in the app's Settings, where Send a test says whether macOS shows them. [Notifications in the Mac app](GUIDE.md#notifications-in-the-mac-app) has more.
116
+
117
+ [Desktop app](GUIDE.md#desktop-app) says how the app behaves, [Menu bar](GUIDE.md#menu-bar) what its icon in the menu bar lists, and [Updates](GUIDE.md#updates) how it checks for a newer version and installs it.
114
118
 
115
119
  ### Build it yourself
116
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-lookout",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
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",
@@ -1 +0,0 @@
1
- import{r as o,j as t}from"./react-C2DiJaR6.js";import{L as O,c as $,o as M,F as j,T as D,p as H,q as J,u as K,w as z,v as U,x as Y,y as G,J as V,z as X,A as Q,R as Z,D as ee,B as te,E as q,G as se,I as ne,n as ae,K as re,M as _,N as le,O as oe,P as ie,Q as ce,U as T,V as W,W as ue,X as de,f as A,Y as fe,Z as me,_ as he,$ as pe,a0 as xe,a1 as ge,a2 as je,a3 as be,a4 as we,a5 as ye,a6 as F,a7 as C,a8 as ke,a9 as ve}from"./index-DlDz48ci.js";import"./motion-DlYOyClL.js";import"./radix-BH0JH8UU.js";function Ne(e){const a=o.useRef(null),[s,n]=o.useState(!1);return o.useLayoutEffect(()=>{const r=a.current;if(!r)return;const d=()=>n(r.scrollHeight>r.clientHeight);d();const l=new ResizeObserver(d);l.observe(r);for(const c of r.children)l.observe(c);return()=>l.disconnect()},[e]),[a,s]}const Se="Last messages are not read from another machine.",Ee="Its last message could not be read. The page asks again every two seconds.",Ae="The start of a longer message is left out.";function S({children:e}){return t.jsx("p",{"data-part":"last-message-state",className:"py-2.5 text-body text-ink-secondary",children:e})}function Le(e,a,s){switch(e){case"off":return a===void 0?"Last messages are off.":t.jsxs(t.Fragment,{children:["Last messages are off:"," ",t.jsx("code",{"data-slot":"fact",className:"font-mono text-fact",children:t.jsx(M,{children:a})})," ","is off."]});case"not-read":return`Agent Lookout does not read what ${s} sessions say.`;case"not-found":return"Its transcript was not found.";case"unreadable":return"Its transcript could not be read.";case"nothing-yet":return"It has not said anything yet.";case"too-far-back":return"Its last message is further back than the end of its transcript that Agent Lookout reads."}}function Re({text:e,cut:a}){const[s,n]=Ne(e);return t.jsxs("div",{className:"pt-2.5",children:[a&&t.jsx("p",{"data-part":"last-message-cut",className:"mb-1.5 text-caption text-ink-muted",children:Ae}),t.jsx("div",{ref:s,"data-part":"last-message-scroll","data-overflows":n||void 0,role:n?"group":void 0,tabIndex:n?0:void 0,"aria-label":n?"Its last message":void 0,className:$("thin-scroll max-h-60 overflow-y-auto",n&&"focus-visible:-outline-offset-2"),children:t.jsx("p",{"data-part":"last-message-text",className:"text-body whitespace-pre-wrap text-ink wrap-anywhere select-text",children:e})})]})}function Te({reading:e,remote:a,agent:s}){if(a)return t.jsx(S,{children:Se});if(e.status==="idle")return null;if(e.status==="loading")return t.jsx(O,{label:"Reading its last message",className:"justify-start px-0 py-2.5"});if(e.status==="failed"||e.answer===null)return t.jsx(S,{children:Ee});const{answer:n}=e;return n.message!==null?t.jsx(Re,{text:n.message.text,cut:n.message.cut}):t.jsx(S,{children:Le(n.reason,n.setting,s)})}function Fe({pullRequest:e}){return t.jsxs(j,{label:"Pull request",children:[t.jsxs(D,{"data-part":"pull-request",href:e.url,target:"_blank",rel:"noreferrer noopener",lines:2,tooltip:`#${e.number} ${e.title}`,className:"wrap-anywhere underline decoration-rule-strong underline-offset-2 transition-colors duration-120 hover:decoration-ink",children:[t.jsxs("span",{className:"tabular-nums",children:["#",e.number]})," ",e.title]}),t.jsx("span",{"data-part":"pull-request-state",className:"block text-caption font-normal text-ink-secondary tabular-nums",children:H(e)})]})}function Oe(e,a={}){const{open:s=!0,onStopped:n,request:r=J}=a,[d,l]=o.useState({kind:"idle"}),[c,i]=o.useState(s);s!==c&&(i(s),!s&&!(d.kind==="confirming"&&d.busy)&&l({kind:"idle"}));const p=o.useRef(s);o.useEffect(()=>{p.current=s},[s]);const u=o.useId(),[m]=o.useState(()=>({stop:`${u}-stop`,cancel:`${u}-cancel`,outcome:`${u}-outcome`})),f=o.useRef(null),h=o.useRef(!1),g=o.useRef(!0);o.useEffect(()=>(g.current=!0,()=>{g.current=!1}),[]),o.useEffect(()=>{const w=f.current;f.current=null,w!==null&&document.getElementById(m[w])?.focus()},[d,m]);const x=o.useCallback(()=>{h.current||(f.current="cancel",l({kind:"confirming",busy:!1}))},[]),y=o.useCallback(()=>{h.current||(f.current="stop",l({kind:"idle"}))},[]),b=o.useCallback(()=>{h.current||(h.current=!0,l({kind:"confirming",busy:!0}),r(e).then(w=>{if(h.current=!1,!!g.current){if(w==="stopped"&&n?.(),!p.current){l({kind:"idle"});return}f.current="outcome",l({kind:"answered",outcome:w,at:Date.now()})}}))},[e,r,n]);return{step:d,ask:x,cancel:y,confirm:b,ids:m}}const $e={status:"idle",answer:null};function Me(e){return e.message===null&&(e.reason==="off"||e.reason==="not-read")}function De(e,a){const{active:s,hidden:n,beat:r}=a,[d,l]=o.useState(null);d!==null&&(!s||d.sessionId!==e)&&l(null);const c=o.useRef(void 0),i=o.useRef(!1),p=o.useRef(!1),u=o.useRef(0),m=o.useRef(e),f=o.useRef(!1);if(o.useEffect(()=>(f.current=!0,()=>{f.current=!1}),[]),o.useEffect(()=>{if((!s||m.current!==e)&&(m.current=e,c.current=void 0,i.current=!1,p.current=!1,u.current+=1),!s||n||p.current||i.current||c.current===r)return;c.current=r,i.current=!0;const g=u.current;K(e).then(x=>{g===u.current&&(i.current=!1,!(!f.current||x==="busy")&&(Me(x)&&(p.current=!0),l({sessionId:e,answer:x,failed:!1})))},()=>{g===u.current&&(i.current=!1,f.current&&l(x=>x?.sessionId===e&&x.answer!==null?x:{sessionId:e,answer:null,failed:!0}))})},[e,s,n,r]),!s)return $e;const h=d?.sessionId===e?d:null;return h?.answer?{status:"ready",answer:h.answer}:h?.failed?{status:"failed",answer:null}:{status:"loading",answer:null}}const qe="Read over SSH. Jump, Stop, Allow and Deny work on this computer only, so use that machine to go to it, stop it or answer it.",I=[];function _e(e,a){const s=CSS.escape(e),n=[...document.querySelectorAll(`:is([data-slot="session-row"], [data-slot="board-card"], [data-slot="hero-session"])[data-session="${s}"] a[data-part="name"]`)];return a!==null&&n.includes(a)?a:n.find(r=>!r.closest('[data-slot="hero-session"]'))??n[0]??null}function We({session:e,gone:a,asOf:s,now:n}){const r=e.status==="finished"||e.status==="failed",d=me(e),l=he(e),c=d?"stale":l||a&&e.status==="needs-you"?"answered":e.status,i=e.statusSince,p=i!==null&&!l?Math.max(0,s-i):null,u=pe(e,s),m=q(e),f=m?xe(e):null,h=m&&!a&&!e.ask&&e.waitingText?.trim()||null,g=i!==null?`${r?"at":d?"idle since":l?"asked for permission at":"since"} ${W(i,n)}`:"time not reported",x=[m?ge(e):null,g,u&&je(u)].filter(y=>typeof y=="string");return t.jsxs(j,{label:"Status",note:h?t.jsxs(t.Fragment,{children:[t.jsx(D,{"data-part":"asking",lines:2,className:"wrap-anywhere",children:h}),f&&t.jsx("span",{className:"block",children:f})]}):f??void 0,children:[t.jsxs("span",{className:"inline-flex items-center gap-2",children:[t.jsx(be,{kind:c}),t.jsx("span",{"data-part":"status",children:d?"Stale":l?we:ye[e.status]}),p!==null&&t.jsxs("span",{"data-part":"duration",className:"tabular-nums",children:[t.jsx("span",{className:"sr-only",children:r?` ${F(p)} ago`:` for ${F(p)}`}),t.jsxs("span",{"aria-hidden":!0,children:[C(p),r&&" ago"]})]})]}),t.jsx("span",{"data-part":"since",className:"block text-caption font-normal text-ink-secondary tabular-nums",children:x.join(", ")})]})}function Ce({session:e,state:a,now:s,asOf:n}){const r=o.useMemo(()=>ke({sessions:a.snapshot?.sessions??I,events:a.events,history:a.history,now:s,asOf:n,eventsFull:a.events.length>=_}),[a.snapshot,a.events,a.history,s,n]);if(!r)return t.jsxs(j,{label:"Waits",children:["Not known",t.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:"The history could not be read"})]});const d=r.sessions.find(i=>i.id===e.id),l=r.period.today?"today":`since ${A(r.period.from)}`;let c="None";if(d){const i=`${d.startKnown?"":"at least "}${C(d.ms)}`;c=d.times===1?`Once, ${i}`:`${d.times} times, ${i} in all`}return t.jsxs(j,{label:"Waits",children:[t.jsx("span",{"data-part":"waits",className:"tabular-nums",children:c}),t.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:l})]})}function E({title:e,aside:a,children:s,...n}){return t.jsxs("section",{...n,"aria-label":e,className:"mt-6",children:[t.jsxs("div",{className:"flex items-baseline justify-between gap-4 border-b border-hairline pb-2",children:[t.jsx("h3",{className:"text-body font-semibold text-ink",children:e}),a&&t.jsx("p",{className:"text-caption text-ink-muted",children:a})]}),s]})}function Ie({session:e,gone:a,state:s,now:n,asOf:r,lastMessage:d}){const l=s.snapshot?.sources??[],c=s.snapshot?.sessions??I,i=ae(s.snapshot?.timeRules),p=re(e.surface),u=s.events.length>=_,m=o.useMemo(()=>le(s.events.filter(x=>x.sessionId===e.id),c),[s.events,c,e.id]),f=oe(s.events,s.history,u),h=o.useMemo(()=>s.history?ie({sessions:c,events:s.events,history:s.history,now:n,asOf:r,eventsFull:u,staleAfterMs:i}):null,[c,s.events,s.history,n,r,u,i]),g=h?.rows.find(x=>x.id===e.id);return t.jsxs(t.Fragment,{children:[a&&t.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."}),t.jsxs(ce,{className:"mt-3",children:[t.jsx(We,{session:e,gone:a,asOf:r,now:n}),t.jsx(j,{label:"Agent",children:T(e,l)}),e.machine!==void 0&&t.jsx(j,{label:"Machine",note:qe,children:t.jsx("span",{"data-part":"machine",children:e.machine})}),p!==null&&t.jsx(j,{label:"App",children:p}),t.jsx(j,{label:"Folder",mono:e.cwd!==null,children:e.cwd!==null?t.jsx(M,{"data-part":"folder",className:"select-text",children:e.cwd}):"Not known"}),e.git?.repository!==void 0&&t.jsx(j,{label:"Repository",children:t.jsx("span",{"data-part":"repository",className:"wrap-anywhere",children:e.git.repository.name})}),e.git?.branch!==void 0?t.jsx(j,{label:"Branch",children:t.jsx("span",{"data-part":"branch",className:"wrap-anywhere",children:e.git.branch})}):e.git?.commit!==void 0&&t.jsx(j,{label:"Commit",mono:!0,children:e.git.commit}),e.git?.pullRequest!==void 0&&t.jsx(Fe,{pullRequest:e.git.pullRequest}),t.jsx(j,{label:"Started",children:t.jsx("span",{className:"tabular-nums",children:e.startedAt!==null?W(e.startedAt,n):"Not reported"})}),e.pid!==void 0&&t.jsx(j,{label:"Process",mono:!0,note:e.alive===!1?"The process has ended.":!a&&e.source==="claude-code"&&e.surface==="desktop"?ue:void 0,children:e.pid}),t.jsx(Ce,{session:e,state:s,now:n,asOf:r})]}),!a&&t.jsx(E,{"data-part":"last-message",title:"Last message",children:t.jsx(Te,{reading:d,remote:e.machine!==void 0,agent:T(e,l)})}),t.jsx(E,{"data-part":"events",title:"Events",aside:f?`since ${A(f.at)}`:void 0,children:m.length>0?t.jsx(de,{entries:m,now:n,className:"pt-0.5"}):t.jsxs("p",{className:"py-2.5 text-body text-ink-secondary",children:["Nothing has happened to this session"," ",f?`since ${A(f.at)}`:"yet","."]})}),t.jsx(E,{"data-part":"timeline",title:"Timeline",aside:"last hour",children:h&&g?t.jsx(fe,{timeline:h,rows:[g],named:!1,label:"Its last hour",className:"pt-3"}):t.jsx("p",{className:"py-2.5 text-body text-ink-secondary",children:h?"It did not run in the last hour.":"The last hour could not be read. The page asks again every two seconds."})})]})}function Pe({sessionId:e,open:a,onClose:s,state:n,now:r,onStopped:d,onAnswered:l}){const c=n.snapshot?.sessions.find(k=>k.id===e)??null,[i,p]=o.useState(null);c!==null&&c!==i?.session?p({session:c,at:n.lastOkAt??r}):c===null&&(i?.session.waitingText!==void 0||i?.session.ask!==void 0)&&p({...i,session:z(i.session)});const u=c??i?.session??null,m=c===null&&i!==null,f=U(),h=De(e,{active:a&&c!==null&&c.machine===void 0,hidden:f,beat:n.lastOkAt}),g=Y(u??{id:e,source:"status-files",surface:"unknown",links:{}}),x=m&&i?i.at:n.phase==="stalled"&&n.lastOkAt!==null?n.lastOkAt:r,y=g.outcome!==null||g.asking!==null,b=Oe(e,{open:a,onStopped:d}),w=b.step.kind==="answered"&&c?.alive===!0&&(n.snapshot?.generatedAt??0)>b.step.at,P=b.step.kind==="answered"&&b.step.outcome==="stopped"&&!w,L=G(u??{id:e,source:"status-files",status:"unknown",cwd:null},P?!0:void 0),B=b.step.kind==="answered"||b.step.kind==="confirming"&&(b.step.busy||!m);let v,N,R="wide";return u?(v=u.name,N=t.jsxs(t.Fragment,{children:[!m&&t.jsx("div",{className:$("flex flex-wrap items-center gap-x-2 gap-y-1",y&&"mt-3"),children:t.jsx(V,{session:u,jump:g})}),B&&t.jsx(X,{session:u,stop:b,className:"mt-3"}),!m&&t.jsx(Q,{session:u,onAnswered:l,className:"mt-3"}),t.jsx(Z,{resume:L,className:"mt-3"}),t.jsx(Ie,{session:u,gone:m,state:n,now:r,asOf:x,lastMessage:h})]})):n.snapshot===null?(v="Session",N=t.jsx(O,{label:"Reading sessions",className:"justify-start px-0 pt-5 pb-1"})):(v="No such session",R="sm",N=t.jsxs("div",{"data-part":"missing",children:[t.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."}),t.jsx(ve,{className:"mt-4",onClick:s,children:"Go to the Overview"})]})),t.jsx(ee,{open:a,onOpenChange:k=>{k||s()},title:v,size:R,focusTitle:!0,returnFocus:k=>_e(e,k)??document.querySelector("main"),actions:u&&t.jsxs(t.Fragment,{children:[!m&&t.jsxs(t.Fragment,{children:[t.jsx(te,{session:u,jump:g,variant:q(u)?"needs-you":"quiet"}),t.jsx(se,{session:u,stop:b})]}),t.jsx(ne,{session:u,resume:L,tooltip:!1})]}),children:N})}function ze({sessionId:e,onClose:a,state:s,now:n,onStopped:r,onAnswered:d}){const[l,c]=o.useState(e);return e!==null&&e!==l&&c(e),l===null?null:t.jsx(Pe,{sessionId:l,open:e!==null,onClose:a,state:s,now:n,onStopped:r,onAnswered:d},l)}export{ze as SessionPanel};