agent-lookout 0.2.8 → 0.2.9

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/dist/index.html CHANGED
@@ -28,11 +28,11 @@
28
28
  document.documentElement.setAttribute("data-theme", theme);
29
29
  })();
30
30
  </script>
31
- <script type="module" crossorigin src="/assets/index-BWFkRW4g.js"></script>
31
+ <script type="module" crossorigin src="/assets/index-DlDz48ci.js"></script>
32
32
  <link rel="modulepreload" crossorigin href="/assets/react-C2DiJaR6.js">
33
33
  <link rel="modulepreload" crossorigin href="/assets/motion-DlYOyClL.js">
34
34
  <link rel="modulepreload" crossorigin href="/assets/radix-BH0JH8UU.js">
35
- <link rel="stylesheet" crossorigin href="/assets/index-B_1Its0R.css">
35
+ <link rel="stylesheet" crossorigin href="/assets/index-BRNSMV9A.css">
36
36
  </head>
37
37
  <body>
38
38
  <div id="root"></div>
package/docs/API.md CHANGED
@@ -31,37 +31,38 @@ curl -s http://127.0.0.1:4777/api/sessions
31
31
 
32
32
  Every answer has `Content-Type: application/json; charset=utf-8`, `Cache-Control: no-store`, `X-Content-Type-Options: nosniff` and `Cross-Origin-Resource-Policy: same-origin`. Every answer that is not a 200 has the body `{ "error": "<one sentence>" }`.
33
33
 
34
- | Status | When |
35
- | ------ | ------------------------------------------------------------------------------------------------------- |
36
- | 400 | The address, `since` or `windowMs` could not be read |
37
- | 403 | The `Host`, the `Origin` or `Sec-Fetch-Site` above |
38
- | 404 | There is no route at that path |
39
- | 405 | Any method but `GET`, with `Allow: GET`. The eight routes that act take only `POST`, with `Allow: POST` |
40
- | 500 | Something went wrong that the server did not expect |
34
+ | Status | When |
35
+ | ------ | ------------------------------------------------------------------------------------------------------------- |
36
+ | 400 | The address, `since`, `windowMs` or the `id` of `GET /api/sessions/last-message` could not be read |
37
+ | 403 | The `Host`, the `Origin` or `Sec-Fetch-Site` above |
38
+ | 404 | There is no route at that path, or no session listed with the `id` `GET /api/sessions/last-message` was given |
39
+ | 405 | Any method but `GET`, with `Allow: GET`. The eight routes that act take only `POST`, with `Allow: POST` |
40
+ | 500 | Something went wrong that the server did not expect |
41
41
 
42
42
  ## Routes
43
43
 
44
- | Route | Answers |
45
- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
- | `GET /api/health` | `{ ok: true, version }` |
47
- | `GET /api/sessions` | The latest snapshot: `{ generatedAt, sources, sessions, answering, timeRules, quiet }` |
48
- | `GET /api/events?since=<epoch ms>` | `{ events }`, newest first, at most 200 |
49
- | `GET /api/history?windowMs=<ms>` | `{ points, startedAt, since, kept, restarts }`, one point for each poll, oldest first |
50
- | `GET /api/email` | `{ on, to, events, afterMs, asking, problem, last, limitedUntil }` |
51
- | `GET /api/webhook` | `{ on, host, events, afterMs, asking, problem, last, limitedUntil }` |
52
- | `GET /api/ntfy` | `{ on, host, tokenSet, events, afterMs, asking, problem, last, limitedUntil }` |
53
- | `GET /api/pushover` | `{ on, events, afterMs, asking, problem, last, limitedUntil }` |
54
- | `GET /api/pull-requests` | `{ on, problem, gh, last }`: whether each branch's pull request is shown |
55
- | `GET /api/waits` | `{ at, today, sevenDays, since, where }`: how long sessions waited on you |
56
- | `GET /api/settings` | `{ timeRules, file, problem, permissionRules, permissionRulesProblem, ruleAnswers, ruleAnswersSince }`: the time rules, the permission rules, the file they are kept in, and what the rules answered |
57
- | `POST /api/jump` | `{ ok: true, kind, place }`, with `app` for a terminal tab. One of eight routes that act |
58
- | `POST /api/history/clear` | `{ ok: true, clearedAt }`. A route that acts |
59
- | `POST /api/sessions/stop` | `{ ok: true }` once the session's process has ended. A route that acts |
60
- | `POST /api/sessions/clean-up` | `{ results }`, what became of each session left running. A route that acts |
61
- | `POST /api/permission/answer` | `{ ok: true, decision }` once a permission prompt was answered. A route that acts |
62
- | `POST /api/settings/time-rules` | `{ ok: true, timeRules }` once the time rules are saved. A route that acts |
63
- | `POST /api/settings/permission-rules` | `{ ok: true, permissionRules }` once one change to the permission rules is saved. A route that acts |
64
- | `POST /api/phone/test` | `{ ok: true, channel, sentAt }` once a test push went through ntfy or Pushover. A route that acts |
44
+ | Route | Answers |
45
+ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | `GET /api/health` | `{ ok: true, version }` |
47
+ | `GET /api/sessions` | The latest snapshot: `{ generatedAt, sources, sessions, answering, timeRules, quiet }` |
48
+ | `GET /api/sessions/last-message?id=<id>` | `{ message: { text, cut } }`, what one session last said, or `{ message: null, reason }` |
49
+ | `GET /api/events?since=<epoch ms>` | `{ events }`, newest first, at most 200 |
50
+ | `GET /api/history?windowMs=<ms>` | `{ points, startedAt, since, kept, restarts }`, one point for each poll, oldest first |
51
+ | `GET /api/email` | `{ on, to, events, afterMs, asking, problem, last, limitedUntil }` |
52
+ | `GET /api/webhook` | `{ on, host, events, afterMs, asking, problem, last, limitedUntil }` |
53
+ | `GET /api/ntfy` | `{ on, host, tokenSet, events, afterMs, asking, problem, last, limitedUntil }` |
54
+ | `GET /api/pushover` | `{ on, events, afterMs, asking, problem, last, limitedUntil }` |
55
+ | `GET /api/pull-requests` | `{ on, problem, gh, last }`: whether each branch's pull request is shown |
56
+ | `GET /api/waits` | `{ at, today, sevenDays, since, where }`: how long sessions waited on you |
57
+ | `GET /api/settings` | `{ timeRules, file, problem, permissionRules, permissionRulesProblem, ruleAnswers, ruleAnswersSince }`: the time rules, the permission rules, the file they are kept in, and what the rules answered |
58
+ | `POST /api/jump` | `{ ok: true, kind, place }`, with `app` for a terminal tab. One of eight routes that act |
59
+ | `POST /api/history/clear` | `{ ok: true, clearedAt }`. A route that acts |
60
+ | `POST /api/sessions/stop` | `{ ok: true }` once the session's process has ended. A route that acts |
61
+ | `POST /api/sessions/clean-up` | `{ results }`, what became of each session left running. A route that acts |
62
+ | `POST /api/permission/answer` | `{ ok: true, decision }` once a permission prompt was answered. A route that acts |
63
+ | `POST /api/settings/time-rules` | `{ ok: true, timeRules }` once the time rules are saved. A route that acts |
64
+ | `POST /api/settings/permission-rules` | `{ ok: true, permissionRules }` once one change to the permission rules is saved. A route that acts |
65
+ | `POST /api/phone/test` | `{ ok: true, channel, sentAt }` once a test push went through ntfy or Pushover. A route that acts |
65
66
 
66
67
  Times are milliseconds since 1970, and lengths of time are milliseconds.
67
68
 
@@ -200,6 +201,32 @@ With `AGENT_LOOKOUT_PULL_REQUESTS=on`, a session on a branch that has a pull req
200
201
  It is what `gh` last said, asked at most once every 2 minutes for each repository and branch, so it can be up to about 2 minutes old. [Pull requests](GUIDE.md#pull-requests) in the guide says when it is asked for.
201
202
  `answering` says whether permission prompts can be answered from the dashboard: `{ state, plugin, holdMs, problem }`. `state` is `on` while Agent Lookout listens for the plugin's requests, `off` with `AGENT_LOOKOUT_ANSWER=off`, and `unavailable` when it could not listen or does not, as on Windows, which has no Unix socket for the plugin's hook, with `problem` saying why in one sentence. `plugin` is `seen` once a request has arrived since the last permission prompt it missed, `missed` when a Claude Code session waited for permission and no request arrived, as for a session without the plugin, and `unknown` before either. `holdMs` is how long a request is held.
202
203
 
204
+ ### `GET /api/sessions/last-message?id=<id>`
205
+
206
+ What one session on this computer last said, read from the end of its transcript when it is asked for. The dashboard asks for it only while that session's details are open. Only Claude Code sessions are read for now. It only reads.
207
+
208
+ ```json
209
+ { "message": { "text": "The tests pass now.\n\nI changed two files in src/app.", "cut": false } }
210
+ ```
211
+
212
+ | Field | Holds |
213
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
214
+ | `message.text` | The text of the session's newest reply, its parts joined with a blank line between. Line breaks are kept, a tab is two spaces, any other control character is a space, and the characters that change the direction of text are taken out. At most its last 2,000 characters. Plain text, with Markdown left as it was written |
215
+ | `message.cut` | `true` when the start of a longer message is left out |
216
+
217
+ When there is nothing to show, `message` is `null` and `reason` says why:
218
+
219
+ | `reason` | When |
220
+ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
221
+ | `off` | `AGENT_LOOKOUT_LAST_MESSAGE=off`, or `AGENT_LOOKOUT_WAITING_TEXT=off` for a Claude Code session. `setting` names the one that is off |
222
+ | `not-read` | The session is not a Claude Code session on this computer: a Codex, Antigravity CLI or status-file session, or one on another machine |
223
+ | `not-found` | Its transcript was not found. A link or a pipe where it should be does not count, and a background job known only by its job ID has none to look for |
224
+ | `unreadable` | Its transcript could not be read, as when a link or a pipe has taken its place since it was found |
225
+ | `nothing-yet` | The whole transcript was read, and the session has said nothing yet |
226
+ | `too-far-back` | Its last message is further back than the last 256 KB of the transcript, which is all that is read |
227
+
228
+ The query is one `id` of 1 to 300 characters, a session's `id` from `GET /api/sessions` written with `encodeURIComponent`, and nothing else, or the answer is 400. A request a browser marks with any `Sec-Fetch-Site` but `same-origin` gets 403, so a page at another port of this computer, or a link opened from anywhere, reads nothing. A session that is not listed gets 404. At most four transcripts are read in a second, for every session together, and above that the answer is 429 with `Retry-After: 1`. An answer is given again from memory for a second. With `AGENT_LOOKOUT_LAST_MESSAGE=off` every `id` is answered `off`, and nothing is looked up. [A session's last message](../PRIVACY.md#a-sessions-last-message) in PRIVACY.md says what is read and kept.
229
+
203
230
  ### `GET /api/events?since=<epoch ms>`
204
231
 
205
232
  `{ "events": [...] }`: what changed, newest first, at most 200, and with `since` only those after it. Each event is `{ id, at, sessionId, sessionName, kind, from, to, severity }`, where `kind` is `appeared`, `status-changed`, `ended`, `stopped` or `answered`, `from` and `to` are statuses, and `severity` is `advisory`, `warning` for a change to `needs-you` or `critical` for a change to `failed`. A `stopped` event says that Agent Lookout stopped the session, when you pressed Stop or ended the sessions left running: it has `from`, the status the session had, no `to`, and `by: "agent-lookout"`. Its leaving the list is an `ended` event of its own. An `answered` event says that Agent Lookout answered a permission prompt of the session's, when you pressed Allow or Deny, or by a permission rule: it has `from`, `by: "agent-lookout"` and `decision`, `allow` or `deny`, and nothing of what was asked. One a rule gave also has `tool`, the tool's name as Claude Code gave it, and `rule`, `{ tool, command }`, the rule as you wrote it, with `command` only for a Bash rule that names one; its decision is the event's. Neither holds the command the session asked to run. A reader should pass over a `kind` it does not know: later versions may add others. The collector keeps the last 1,000 in memory, and with history kept on disk it reads them back when it starts again.
@@ -629,7 +656,7 @@ A program that only reads should not send it. `agent-lookout status` and `agent-
629
656
 
630
657
  ## For this computer only
631
658
 
632
- The API has no token or password. The `Host` and `Origin` rules keep websites, and other computers on the network, away from it: it listens on loopback, and a browser cannot be made to read it from another site. What they do not stop is another program on this computer, which can read it as `curl` does, and on a shared computer that includes other user accounts. Such a program can also send the headers a route that acts asks for, since no browser is involved, so another account can ask the server to stop your Claude Code sessions, which it could not do itself. [SECURITY.md](../SECURITY.md) lists that as a known limit. The Mac app opens no port, and `AGENT_LOOKOUT_STOP=off` takes stopping away.
659
+ The API has no token or password. The `Host` and `Origin` rules keep websites, and other computers on the network, away from it: it listens on loopback, and a browser cannot be made to read it from another site. What they do not stop is another program on this computer, which can read it as `curl` does, and on a shared computer that includes other user accounts. Such a program can also send the headers a route that acts asks for, since no browser is involved, so another account can ask the server to stop your Claude Code sessions, which it could not do itself. It can also read up to 2,000 characters of what a listed Claude Code session last said, from `GET /api/sessions/last-message`. [SECURITY.md](../SECURITY.md) lists that as a known limit. The Mac app opens no port, `AGENT_LOOKOUT_STOP=off` takes stopping away, and `AGENT_LOOKOUT_LAST_MESSAGE=off` takes last messages away.
633
660
 
634
661
  A token would not keep out programs running as you, which could read it as easily as the API. It could keep out other accounts on a shared computer, stopping your sessions included, the known limit above, and is left for a later version. The MCP server changes none of this: it opens no port of its own, speaks to the app that started it over stdin and stdout, and reads the same loopback API the dashboard and `agent-lookout status` read without one. A token is also for an API that can be reached some other way, such as from another computer, and Agent Lookout has none.
635
662
 
package/docs/GUIDE.md CHANGED
@@ -102,9 +102,12 @@ The details show everything Agent Lookout knows about the session:
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
104
  - 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
+ - what a Claude Code session last said, under Last message
105
106
  - its own events, newest first, as the Events log shows them
106
107
  - its row of the Timeline over the last hour
107
108
 
109
+ Last message shows the text of the session's newest reply, up to its last 2,000 characters, as plain text. Its line breaks are kept, and Markdown, such as `**` or a backtick, shows as it was written. When the start of a longer reply is left out, a line above it says so. A long reply scrolls inside its own block, which you can reach with Tab and scroll with the arrow keys. The page asks for it only while the details are open and the page is in sight, and again every two seconds while they stay open, until an answer says it is off or not read. Agent Lookout reads it from the end of the session's transcript only when it is asked for. When there is nothing to show, one line says why: the setting that turned it off, that its transcript was not found or could not be read, that the session has said nothing yet, that its last message is further back than the end of the transcript Agent Lookout reads, or, when asking for it failed, that the page asks again. For a Codex, Antigravity CLI or status-file session it says that Agent Lookout does not read what those sessions say, and for a session on [another machine](#another-machine-over-ssh) that last messages are not read from there. A session that has left the list shows none. `AGENT_LOOKOUT_LAST_MESSAGE=off` turns it off, and [PRIVACY.md](../PRIVACY.md#a-sessions-last-message) says what is read and kept.
110
+
108
111
  A session with a Jump button has it at the top of its details, and it works as it does in the list. It is the amber one while the session needs you. A session Agent Lookout can stop has a [Stop](#stop-a-session) button beside it. A session that can be [resumed](#resume-a-session) has Resume there instead, with the command it copies at the top of the details. A session with a permission prompt Agent Lookout holds shows it at the top of its details, with [Allow and Deny](#answer-a-permission-prompt).
109
112
 
110
113
  Each session's details have an address of their own, such as `#overview/session/claude-code:` followed by the session's ID, so a bookmark or a reload opens them again, and the browser's Back button closes them. `Esc` or the close button closes them too, and puts focus back on the session's name. If the session leaves the list while its details are open, they say so and keep what was last known of it. An address for a session Agent Lookout is not watching says so, with a button back to the Overview.
@@ -124,7 +127,7 @@ It is a measurement, not a status. The session is still listed as working, with
124
127
  | Status files | When the status file was last written. An agent that wants this should write its file now and then while it works |
125
128
  | Claude Code | None, so nothing is shown. The file Agent Lookout reads for a Claude Code session is not rewritten as the session works: it can stay hours old while the session is busy |
126
129
 
127
- For Claude Code, the time its file was written would make a session busy on one long task look quiet for as long as the task lasts. The transcripts would say when it last did anything, and Agent Lookout reads one only while its session is waiting, for what it is asking. For every agent, Agent Lookout reads only when the file was last changed, from the file system, and nothing more of the file than it already reads.
130
+ For Claude Code, the time its file was written would make a session busy on one long task look quiet for as long as the task lasts. The transcripts would say when it last did anything, and Agent Lookout reads one only while its session is waiting, for what it is asking, and when its last message is asked for. For every agent, Agent Lookout reads only when the file was last changed, from the file system, and nothing more of the file than it already reads.
128
131
 
129
132
  #### Branches
130
133
 
@@ -199,11 +202,11 @@ Waits counts only the time Agent Lookout was running, from the [history](#histor
199
202
 
200
203
  ### Sources
201
204
 
202
- ![The whole Sources view in the Night theme. Cards for Claude Code, Codex and status files, each marked Watching, list what Agent Lookout reads and runs and how often, how many sessions it found and when it last checked. Under them, the card for devbox, another machine, says Connected and gives the ssh target, the ssh command, the two routes it asks for, the version of Agent Lookout there and what each source there is doing. Last, What each agent can report gives Yes, No or Partly for each agent, Claude Code on devbox among them, and each thing it can show. Beside the cards, About sources says what a source is.](images/sources-night.png)
205
+ ![The whole Sources view in the Night theme. Cards for Claude Code, Codex and status files, each marked Watching, list what Agent Lookout reads and runs and how often, how many sessions it found and when it last checked. Between Codex and status files, the Antigravity CLI card says Not found, as there is no ~/.gemini/antigravity-cli folder, and gives the conversations folder it would read, the ps command it would run every 10 seconds at most, and 0 sessions. Under them, the card for devbox, another machine, says Connected and gives the ssh target, the ssh command, the two routes it asks for, the version of Agent Lookout there and what each source there is doing. Last, What each agent can report gives Yes, No or Partly for each agent, the Antigravity CLI and Claude Code on devbox among them, and each thing it can show. Beside the cards, About sources says what a source is.](images/sources-night.png)
203
206
 
204
207
  Sources has a card for Claude Code, one for Codex, one for the Antigravity CLI and one for status files, and under them a table of [what each agent can report](#what-each-agent-can-report). Each card says whether the agent was found: Watching, Searching, Not found or Not working. Under that, a short note says how its sessions are being read right now, then rows give what Agent Lookout reads and runs.
205
208
 
206
- For Claude Code the rows give the folder of session files it reads, normally `~/.claude/sessions`, how often it reads that folder, the Claude Code command it runs to list sessions, how often it runs it, or "not run", and Transcript read: the last message of a waiting session, or "Off" with `AGENT_LOOKOUT_WAITING_TEXT=off`. For Codex they give the folder where Codex saves its sessions, normally `~/.codex/sessions`, how often it reads them, the folder where Codex marks the sessions it has open, normally `~/.codex/thread-writer-locks`, and the file where Codex keeps the names you give sessions, normally `~/.codex/session_index.jsonl`. For the Antigravity CLI they give the folder where agy keeps its conversations, normally `~/.gemini/antigravity-cli/brain`, how often it reads them, the `ps` command that tells which agy programs are running, and how often it runs it. Each of these cards ends with Sessions found and Last checked.
209
+ For Claude Code the rows give the folder of session files it reads, normally `~/.claude/sessions`, how often it reads that folder, the Claude Code command it runs to list sessions, how often it runs it, or "not run", and Transcript read: the last message of a waiting session, and of one whose details are open, or of a waiting session alone with `AGENT_LOOKOUT_LAST_MESSAGE=off`, or "Off" with `AGENT_LOOKOUT_WAITING_TEXT=off`. For Codex they give the folder where Codex saves its sessions, normally `~/.codex/sessions`, how often it reads them, the folder where Codex marks the sessions it has open, normally `~/.codex/thread-writer-locks`, and the file where Codex keeps the names you give sessions, normally `~/.codex/session_index.jsonl`. For the Antigravity CLI they give the folder where agy keeps its conversations, normally `~/.gemini/antigravity-cli/brain`, how often it reads them, the `ps` command that tells which agy programs are running, and how often it runs it. Each of these cards ends with Sessions found and Last checked.
207
210
 
208
211
  The notes on Codex's card and the Antigravity CLI's also give the limits of what their files can show, which are described under [What it does not do yet](#what-it-does-not-do-yet).
209
212
 
@@ -1391,52 +1394,53 @@ Put a setting in front of the command that starts Agent Lookout:
1391
1394
  AGENT_LOOKOUT_CLAUDE_FEED=off npx agent-lookout
1392
1395
  ```
1393
1396
 
1394
- | Setting | What it does |
1395
- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1396
- | `AGENT_LOOKOUT_PORT` | The port `npx agent-lookout`, `agent-lookout` and `npm start` listen on. The default is 4777. |
1397
- | `AGENT_LOOKOUT_HOST` | The address `npx agent-lookout`, `agent-lookout` and `npm start` listen on: `127.0.0.1`, which is the default, `localhost` or `::1`. Anything else is refused, so other computers cannot reach it. |
1398
- | `AGENT_LOOKOUT_CLAUDE_BIN` | The full path of the `claude` program. When set, it is the only place Agent Lookout looks. |
1399
- | `AGENT_LOOKOUT_CLAUDE_HOME` | A folder to read in place of `~/.claude`. When set, the `claude` command is not run unless `AGENT_LOOKOUT_CLAUDE_BIN` is set too. |
1400
- | `AGENT_LOOKOUT_CLAUDE_FEED` | Set to `off` and Agent Lookout never runs the `claude` command. Sessions come from the session files alone, and finished or failed background jobs are not listed. |
1401
- | `AGENT_LOOKOUT_WAITING_TEXT` | Set to `off` and Agent Lookout never opens a Claude Code transcript. A waiting session then shows its reason alone, without what it is asking, and so does one on [another machine](#another-machine-over-ssh). |
1402
- | `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. |
1403
- | `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. |
1404
- | `AGENT_LOOKOUT_ANTIGRAVITY_HOME` | A folder to read in place of the Antigravity CLI's, `~/.gemini/antigravity-cli`. A folder with no `brain` folder in it shows no Antigravity CLI sessions. |
1405
- | `AGENT_LOOKOUT_STATUS_DIR` | A folder of [status files](#your-own-agents) to read in place of `~/.agent-lookout/sessions`. |
1406
- | `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). |
1407
- | `AGENT_LOOKOUT_HISTORY_DIR` | A folder to keep the [history](#history) in, in place of `~/.agent-lookout/history`. |
1408
- | `AGENT_LOOKOUT_SETTINGS_FILE` | A file to keep the [time rules](#time-rules) and the [permission rules](#permission-rules) in, in place of `~/.agent-lookout/settings.json`. |
1409
- | `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. |
1410
- | `AGENT_LOOKOUT_TMUX` | Set to `off` and Agent Lookout never runs `tmux`. Sessions in tmux are still listed, without a Jump button. |
1411
- | `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. |
1412
- | `AGENT_LOOKOUT_STOP` | Set to `off` and Agent Lookout never stops a session: no session has a Stop button, Left running has no End all…, and the server answers no request to stop one. |
1413
- | `AGENT_LOOKOUT_ANSWER` | Set to `off` and Agent Lookout never answers a permission prompt: it opens no socket for the plugin, no session shows Allow or Deny, no [permission rule](#permission-rules) answers one, and the server answers no request to answer one. |
1414
- | `AGENT_LOOKOUT_ANSWER_SOCKET` | The socket the [plugin](#install-the-plugin) sends permission requests to, in place of `~/.agent-lookout/answer.sock`. Set the same in the environment Claude Code starts with, so its hook finds it. With `AGENT_LOOKOUT_CLAUDE_HOME` set, nothing is answered unless this is set too. |
1415
- | `AGENT_LOOKOUT_ANSWER_WAIT` | How many seconds a permission request waits for Allow or Deny before the prompt in the session is left to decide, from 5 to 540. The default is 300. |
1416
- | `AGENT_LOOKOUT_EMAIL_TO` | The one address emails go to. With `AGENT_LOOKOUT_SMTP_URL` set too, it turns [email](#email) on. |
1417
- | `AGENT_LOOKOUT_SMTP_URL` | The mail server emails go through, with the user name and password: `smtps://name:password@server:port`. |
1418
- | `AGENT_LOOKOUT_EMAIL_FROM` | The address emails come from. The default is the address they go to. |
1419
- | `AGENT_LOOKOUT_EMAIL_AFTER` | How many seconds a wait lasts before it is emailed, from 0 to 86400. The default is 60. |
1420
- | `AGENT_LOOKOUT_EMAIL_EVENTS` | What is emailed: `needs-you`, `finished`, `failed` and `ended`, any of them, separated by commas. The default is `needs-you`. |
1421
- | `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). |
1422
- | `AGENT_LOOKOUT_WEBHOOK_URL` | The one address [webhook](#webhook) posts go to, beginning with `https://`. It turns the webhook on. |
1423
- | `AGENT_LOOKOUT_WEBHOOK_EVENTS` | What is posted, as for `AGENT_LOOKOUT_EMAIL_EVENTS`. The default is `needs-you`. |
1424
- | `AGENT_LOOKOUT_WEBHOOK_AFTER` | How many seconds a wait lasts before it is posted, from 0 to 86400. The default is 60. |
1425
- | `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). |
1426
- | `AGENT_LOOKOUT_NTFY_URL` | The address of the one [ntfy](#ntfy) topic pushes go to, beginning with `https://`. It turns ntfy on. Keep it secret: anyone who has it can read the pushes. |
1427
- | `AGENT_LOOKOUT_NTFY_TOKEN` | An access token for that topic, sent as `Authorization: Bearer`. Left out, none is sent. |
1428
- | `AGENT_LOOKOUT_NTFY_EVENTS` | What is pushed through ntfy, as for `AGENT_LOOKOUT_EMAIL_EVENTS`. The default is `needs-you`. |
1429
- | `AGENT_LOOKOUT_NTFY_AFTER` | How many seconds a wait lasts before it is pushed through ntfy, from 0 to 86400. The default is 60. |
1430
- | `AGENT_LOOKOUT_NTFY_ASKING` | Set to `on` and the push for a wait says what the session is asking. The default is `off`. |
1431
- | `AGENT_LOOKOUT_PUSHOVER_TOKEN` | The API token of your [Pushover](#pushover) application. With `AGENT_LOOKOUT_PUSHOVER_USER` set too, it turns Pushover on. |
1432
- | `AGENT_LOOKOUT_PUSHOVER_USER` | Your Pushover user key, or a group key, which the pushes go to. |
1433
- | `AGENT_LOOKOUT_PUSHOVER_EVENTS` | What is pushed through Pushover, as for `AGENT_LOOKOUT_EMAIL_EVENTS`. The default is `needs-you`. |
1434
- | `AGENT_LOOKOUT_PUSHOVER_AFTER` | How many seconds a wait lasts before it is pushed through Pushover, from 0 to 86400. The default is 60. |
1435
- | `AGENT_LOOKOUT_PUSHOVER_ASKING` | Set to `on` and the push for a wait says what the session is asking. The default is `off`. |
1436
- | `AGENT_LOOKOUT_PULL_REQUESTS` | Set to `on` and Agent Lookout asks your own `gh` for each session's [pull request](#pull-requests) on github.com and its checks, and shows them. The default is `off`. With it on, the names of the repository and the branch go to GitHub, through `gh`, with `gh`'s own login. Any value but `on` or `off` leaves it off, and the card and the terminal name the setting |
1437
- | `AGENT_LOOKOUT_REMOTES` | Other machines to read over SSH, as `name=target`, separated by commas, such as `devbox=dev@devbox.local,gpu=gpu-vm:4800`. Unset, none is read. See [Another machine over SSH](#another-machine-over-ssh). |
1438
- | `AGENT_LOOKOUT_SSH_BIN` | The full path of the `ssh` program used to reach those machines. When set, it is the only place Agent Lookout looks. |
1439
- | `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. |
1397
+ | Setting | What it does |
1398
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1399
+ | `AGENT_LOOKOUT_PORT` | The port `npx agent-lookout`, `agent-lookout` and `npm start` listen on. The default is 4777. |
1400
+ | `AGENT_LOOKOUT_HOST` | The address `npx agent-lookout`, `agent-lookout` and `npm start` listen on: `127.0.0.1`, which is the default, `localhost` or `::1`. Anything else is refused, so other computers cannot reach it. |
1401
+ | `AGENT_LOOKOUT_CLAUDE_BIN` | The full path of the `claude` program. When set, it is the only place Agent Lookout looks. |
1402
+ | `AGENT_LOOKOUT_CLAUDE_HOME` | A folder to read in place of `~/.claude`. When set, the `claude` command is not run unless `AGENT_LOOKOUT_CLAUDE_BIN` is set too. |
1403
+ | `AGENT_LOOKOUT_CLAUDE_FEED` | Set to `off` and Agent Lookout never runs the `claude` command. Sessions come from the session files alone, and finished or failed background jobs are not listed. |
1404
+ | `AGENT_LOOKOUT_WAITING_TEXT` | Set to `off` and Agent Lookout never opens a Claude Code transcript. A waiting session then shows its reason alone, without what it is asking, and so does one on [another machine](#another-machine-over-ssh). No session's last message is read either. |
1405
+ | `AGENT_LOOKOUT_LAST_MESSAGE` | Set to `off` and Agent Lookout never reads what a Claude Code session last said. It is on by default: Agent Lookout reads it from the end of the session's transcript only when it is asked for, which the dashboard does while that session's details are open. Any program on this computer can ask for it too, as [PRIVACY.md](../PRIVACY.md#a-sessions-last-message) says. |
1406
+ | `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. |
1407
+ | `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. |
1408
+ | `AGENT_LOOKOUT_ANTIGRAVITY_HOME` | A folder to read in place of the Antigravity CLI's, `~/.gemini/antigravity-cli`. A folder with no `brain` folder in it shows no Antigravity CLI sessions. |
1409
+ | `AGENT_LOOKOUT_STATUS_DIR` | A folder of [status files](#your-own-agents) to read in place of `~/.agent-lookout/sessions`. |
1410
+ | `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). |
1411
+ | `AGENT_LOOKOUT_HISTORY_DIR` | A folder to keep the [history](#history) in, in place of `~/.agent-lookout/history`. |
1412
+ | `AGENT_LOOKOUT_SETTINGS_FILE` | A file to keep the [time rules](#time-rules) and the [permission rules](#permission-rules) in, in place of `~/.agent-lookout/settings.json`. |
1413
+ | `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. |
1414
+ | `AGENT_LOOKOUT_TMUX` | Set to `off` and Agent Lookout never runs `tmux`. Sessions in tmux are still listed, without a Jump button. |
1415
+ | `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. |
1416
+ | `AGENT_LOOKOUT_STOP` | Set to `off` and Agent Lookout never stops a session: no session has a Stop button, Left running has no End all…, and the server answers no request to stop one. |
1417
+ | `AGENT_LOOKOUT_ANSWER` | Set to `off` and Agent Lookout never answers a permission prompt: it opens no socket for the plugin, no session shows Allow or Deny, no [permission rule](#permission-rules) answers one, and the server answers no request to answer one. |
1418
+ | `AGENT_LOOKOUT_ANSWER_SOCKET` | The socket the [plugin](#install-the-plugin) sends permission requests to, in place of `~/.agent-lookout/answer.sock`. Set the same in the environment Claude Code starts with, so its hook finds it. With `AGENT_LOOKOUT_CLAUDE_HOME` set, nothing is answered unless this is set too. |
1419
+ | `AGENT_LOOKOUT_ANSWER_WAIT` | How many seconds a permission request waits for Allow or Deny before the prompt in the session is left to decide, from 5 to 540. The default is 300. |
1420
+ | `AGENT_LOOKOUT_EMAIL_TO` | The one address emails go to. With `AGENT_LOOKOUT_SMTP_URL` set too, it turns [email](#email) on. |
1421
+ | `AGENT_LOOKOUT_SMTP_URL` | The mail server emails go through, with the user name and password: `smtps://name:password@server:port`. |
1422
+ | `AGENT_LOOKOUT_EMAIL_FROM` | The address emails come from. The default is the address they go to. |
1423
+ | `AGENT_LOOKOUT_EMAIL_AFTER` | How many seconds a wait lasts before it is emailed, from 0 to 86400. The default is 60. |
1424
+ | `AGENT_LOOKOUT_EMAIL_EVENTS` | What is emailed: `needs-you`, `finished`, `failed` and `ended`, any of them, separated by commas. The default is `needs-you`. |
1425
+ | `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). |
1426
+ | `AGENT_LOOKOUT_WEBHOOK_URL` | The one address [webhook](#webhook) posts go to, beginning with `https://`. It turns the webhook on. |
1427
+ | `AGENT_LOOKOUT_WEBHOOK_EVENTS` | What is posted, as for `AGENT_LOOKOUT_EMAIL_EVENTS`. The default is `needs-you`. |
1428
+ | `AGENT_LOOKOUT_WEBHOOK_AFTER` | How many seconds a wait lasts before it is posted, from 0 to 86400. The default is 60. |
1429
+ | `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). |
1430
+ | `AGENT_LOOKOUT_NTFY_URL` | The address of the one [ntfy](#ntfy) topic pushes go to, beginning with `https://`. It turns ntfy on. Keep it secret: anyone who has it can read the pushes. |
1431
+ | `AGENT_LOOKOUT_NTFY_TOKEN` | An access token for that topic, sent as `Authorization: Bearer`. Left out, none is sent. |
1432
+ | `AGENT_LOOKOUT_NTFY_EVENTS` | What is pushed through ntfy, as for `AGENT_LOOKOUT_EMAIL_EVENTS`. The default is `needs-you`. |
1433
+ | `AGENT_LOOKOUT_NTFY_AFTER` | How many seconds a wait lasts before it is pushed through ntfy, from 0 to 86400. The default is 60. |
1434
+ | `AGENT_LOOKOUT_NTFY_ASKING` | Set to `on` and the push for a wait says what the session is asking. The default is `off`. |
1435
+ | `AGENT_LOOKOUT_PUSHOVER_TOKEN` | The API token of your [Pushover](#pushover) application. With `AGENT_LOOKOUT_PUSHOVER_USER` set too, it turns Pushover on. |
1436
+ | `AGENT_LOOKOUT_PUSHOVER_USER` | Your Pushover user key, or a group key, which the pushes go to. |
1437
+ | `AGENT_LOOKOUT_PUSHOVER_EVENTS` | What is pushed through Pushover, as for `AGENT_LOOKOUT_EMAIL_EVENTS`. The default is `needs-you`. |
1438
+ | `AGENT_LOOKOUT_PUSHOVER_AFTER` | How many seconds a wait lasts before it is pushed through Pushover, from 0 to 86400. The default is 60. |
1439
+ | `AGENT_LOOKOUT_PUSHOVER_ASKING` | Set to `on` and the push for a wait says what the session is asking. The default is `off`. |
1440
+ | `AGENT_LOOKOUT_PULL_REQUESTS` | Set to `on` and Agent Lookout asks your own `gh` for each session's [pull request](#pull-requests) on github.com and its checks, and shows them. The default is `off`. With it on, the names of the repository and the branch go to GitHub, through `gh`, with `gh`'s own login. Any value but `on` or `off` leaves it off, and the card and the terminal name the setting |
1441
+ | `AGENT_LOOKOUT_REMOTES` | Other machines to read over SSH, as `name=target`, separated by commas, such as `devbox=dev@devbox.local,gpu=gpu-vm:4800`. Unset, none is read. See [Another machine over SSH](#another-machine-over-ssh). |
1442
+ | `AGENT_LOOKOUT_SSH_BIN` | The full path of the `ssh` program used to reach those machines. When set, it is the only place Agent Lookout looks. |
1443
+ | `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. |
1440
1444
 
1441
1445
  To see the empty screen, set `AGENT_LOOKOUT_CLAUDE_HOME`, `AGENT_LOOKOUT_CODEX_HOME`, `AGENT_LOOKOUT_ANTIGRAVITY_HOME`, `AGENT_LOOKOUT_STATUS_DIR` and `AGENT_LOOKOUT_HISTORY_DIR` to an empty folder. With only the first set, Codex and Antigravity CLI sessions and sessions from status files still appear.
1442
1446
 
@@ -1627,7 +1631,7 @@ The program serving the page has stopped. The page keeps the last thing it saw,
1627
1631
 
1628
1632
  ## How it finds sessions
1629
1633
 
1630
- 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. Claude Code does not document its transcripts, so a new version can change them, and then the reason shows alone.
1634
+ 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.
1631
1635
 
1632
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.
1633
1637
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-lookout",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
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{j as e,r as m}from"./react-C2DiJaR6.js";import{F as j,T as $,p as L,o as W,w as C,u as P,q as _,J as B,c as I,v as J,A as H,R as K,D as Y,x as z,y as F,z as G,B as U,n as V,E as X,M as R,G as Q,I as Z,K as ee,N as te,O as se,P as ne,Q as O,U as ae,V as re,f as S,W as le,X as ie,Y as oe,Z as ce,_ as ue,$ as de,a0 as pe,a1 as me,a2 as he,a3 as xe,a4 as T,a5 as M,a6 as fe,L as je,a7 as be}from"./index-BWFkRW4g.js";import"./motion-DlYOyClL.js";import"./radix-BH0JH8UU.js";function ge({pullRequest:t}){return e.jsxs(j,{label:"Pull request",children:[e.jsxs($,{"data-part":"pull-request",href:t.url,target:"_blank",rel:"noreferrer noopener",lines:2,tooltip:`#${t.number} ${t.title}`,className:"wrap-anywhere underline decoration-rule-strong underline-offset-2 transition-colors duration-120 hover:decoration-ink",children:[e.jsxs("span",{className:"tabular-nums",children:["#",t.number]})," ",t.title]}),e.jsx("span",{"data-part":"pull-request-state",className:"block text-caption font-normal text-ink-secondary tabular-nums",children:L(t)})]})}function ke(t,a={}){const{open:s=!0,onStopped:n,request:l=W}=a,[c,r]=m.useState({kind:"idle"}),[u,i]=m.useState(s);s!==u&&(i(s),!s&&!(c.kind==="confirming"&&c.busy)&&r({kind:"idle"}));const h=m.useRef(s);m.useEffect(()=>{h.current=s},[s]);const o=m.useId(),[d]=m.useState(()=>({stop:`${o}-stop`,cancel:`${o}-cancel`,outcome:`${o}-outcome`})),p=m.useRef(null),x=m.useRef(!1),b=m.useRef(!0);m.useEffect(()=>(b.current=!0,()=>{b.current=!1}),[]),m.useEffect(()=>{const g=p.current;p.current=null,g!==null&&document.getElementById(d[g])?.focus()},[c,d]);const f=m.useCallback(()=>{x.current||(p.current="cancel",r({kind:"confirming",busy:!1}))},[]),k=m.useCallback(()=>{x.current||(p.current="stop",r({kind:"idle"}))},[]),v=m.useCallback(()=>{x.current||(x.current=!0,r({kind:"confirming",busy:!0}),l(t).then(g=>{if(x.current=!1,!!b.current){if(g==="stopped"&&n?.(),!h.current){r({kind:"idle"});return}p.current="outcome",r({kind:"answered",outcome:g,at:Date.now()})}}))},[t,l,n]);return{step:c,ask:f,cancel:k,confirm:v,ids:d}}const ye="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.",q=[];function we(t,a){const s=CSS.escape(t),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(l=>!l.closest('[data-slot="hero-session"]'))??n[0]??null}function Ne({session:t,gone:a,asOf:s,now:n}){const l=t.status==="finished"||t.status==="failed",c=ie(t),r=oe(t),u=c?"stale":r||a&&t.status==="needs-you"?"answered":t.status,i=t.statusSince,h=i!==null&&!r?Math.max(0,s-i):null,o=ce(t,s),d=F(t),p=d?ue(t):null,x=d&&!a&&!t.ask&&t.waitingText?.trim()||null,b=i!==null?`${l?"at":c?"idle since":r?"asked for permission at":"since"} ${O(i,n)}`:"time not reported",f=[d?de(t):null,b,o&&pe(o)].filter(k=>typeof k=="string");return e.jsxs(j,{label:"Status",note:x?e.jsxs(e.Fragment,{children:[e.jsx($,{"data-part":"asking",lines:2,className:"wrap-anywhere",children:x}),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(me,{kind:u}),e.jsx("span",{"data-part":"status",children:c?"Stale":r?he:xe[t.status]}),h!==null&&e.jsxs("span",{"data-part":"duration",className:"tabular-nums",children:[e.jsx("span",{className:"sr-only",children:l?` ${T(h)} ago`:` for ${T(h)}`}),e.jsxs("span",{"aria-hidden":!0,children:[M(h),l&&" ago"]})]})]}),e.jsx("span",{"data-part":"since",className:"block text-caption font-normal text-ink-secondary tabular-nums",children:f.join(", ")})]})}function ve({session:t,state:a,now:s,asOf:n}){const l=m.useMemo(()=>fe({sessions:a.snapshot?.sessions??q,events:a.events,history:a.history,now:s,asOf:n,eventsFull:a.events.length>=R}),[a.snapshot,a.events,a.history,s,n]);if(!l)return e.jsxs(j,{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 c=l.sessions.find(i=>i.id===t.id),r=l.period.today?"today":`since ${S(l.period.from)}`;let u="None";if(c){const i=`${c.startKnown?"":"at least "}${M(c.ms)}`;u=c.times===1?`Once, ${i}`:`${c.times} times, ${i} in all`}return e.jsxs(j,{label:"Waits",children:[e.jsx("span",{"data-part":"waits",className:"tabular-nums",children:u}),e.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:r})]})}function E({title:t,aside:a,children:s,...n}){return e.jsxs("section",{...n,"aria-label":t,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:t}),a&&e.jsx("p",{className:"text-caption text-ink-muted",children:a})]}),s]})}function Se({session:t,gone:a,state:s,now:n,asOf:l}){const c=s.snapshot?.sources??[],r=s.snapshot?.sessions??q,u=V(s.snapshot?.timeRules),i=X(t.surface),h=s.events.length>=R,o=m.useMemo(()=>Q(s.events.filter(b=>b.sessionId===t.id),r),[s.events,r,t.id]),d=Z(s.events,s.history,h),p=m.useMemo(()=>s.history?ee({sessions:r,events:s.events,history:s.history,now:n,asOf:l,eventsFull:h,staleAfterMs:u}):null,[r,s.events,s.history,n,l,h,u]),x=p?.rows.find(b=>b.id===t.id);return e.jsxs(e.Fragment,{children:[a&&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(te,{className:"mt-3",children:[e.jsx(Ne,{session:t,gone:a,asOf:l,now:n}),e.jsx(j,{label:"Agent",children:se(t,c)}),t.machine!==void 0&&e.jsx(j,{label:"Machine",note:ye,children:e.jsx("span",{"data-part":"machine",children:t.machine})}),i!==null&&e.jsx(j,{label:"App",children:i}),e.jsx(j,{label:"Folder",mono:t.cwd!==null,children:t.cwd!==null?e.jsx(ne,{"data-part":"folder",className:"select-text",children:t.cwd}):"Not known"}),t.git?.repository!==void 0&&e.jsx(j,{label:"Repository",children:e.jsx("span",{"data-part":"repository",className:"wrap-anywhere",children:t.git.repository.name})}),t.git?.branch!==void 0?e.jsx(j,{label:"Branch",children:e.jsx("span",{"data-part":"branch",className:"wrap-anywhere",children:t.git.branch})}):t.git?.commit!==void 0&&e.jsx(j,{label:"Commit",mono:!0,children:t.git.commit}),t.git?.pullRequest!==void 0&&e.jsx(ge,{pullRequest:t.git.pullRequest}),e.jsx(j,{label:"Started",children:e.jsx("span",{className:"tabular-nums",children:t.startedAt!==null?O(t.startedAt,n):"Not reported"})}),t.pid!==void 0&&e.jsx(j,{label:"Process",mono:!0,note:t.alive===!1?"The process has ended.":!a&&t.source==="claude-code"&&t.surface==="desktop"?ae:void 0,children:t.pid}),e.jsx(ve,{session:t,state:s,now:n,asOf:l})]}),e.jsx(E,{"data-part":"events",title:"Events",aside:d?`since ${S(d.at)}`:void 0,children:o.length>0?e.jsx(re,{entries:o,now:n,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 ${S(d.at)}`:"yet","."]})}),e.jsx(E,{"data-part":"timeline",title:"Timeline",aside:"last hour",children:p&&x?e.jsx(le,{timeline:p,rows:[x],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 Ae({sessionId:t,open:a,onClose:s,state:n,now:l,onStopped:c,onAnswered:r}){const u=n.snapshot?.sessions.find(y=>y.id===t)??null,[i,h]=m.useState(null);u!==null&&u!==i?.session?h({session:u,at:n.lastOkAt??l}):u===null&&(i?.session.waitingText!==void 0||i?.session.ask!==void 0)&&h({...i,session:C(i.session)});const o=u??i?.session??null,d=u===null&&i!==null,p=P(o??{id:t,source:"status-files",surface:"unknown",links:{}}),x=d&&i?i.at:n.phase==="stalled"&&n.lastOkAt!==null?n.lastOkAt:l,b=p.outcome!==null||p.asking!==null,f=ke(t,{open:a,onStopped:c}),k=f.step.kind==="answered"&&u?.alive===!0&&(n.snapshot?.generatedAt??0)>f.step.at,v=f.step.kind==="answered"&&f.step.outcome==="stopped"&&!k,g=_(o??{id:t,source:"status-files",status:"unknown",cwd:null},v?!0:void 0),D=f.step.kind==="answered"||f.step.kind==="confirming"&&(f.step.busy||!d);let w,N,A="wide";return o?(w=o.name,N=e.jsxs(e.Fragment,{children:[!d&&e.jsx("div",{className:I("flex flex-wrap items-center gap-x-2 gap-y-1",b&&"mt-3"),children:e.jsx(B,{session:o,jump:p})}),D&&e.jsx(J,{session:o,stop:f,className:"mt-3"}),!d&&e.jsx(H,{session:o,onAnswered:r,className:"mt-3"}),e.jsx(K,{resume:g,className:"mt-3"}),e.jsx(Se,{session:o,gone:d,state:n,now:l,asOf:x})]})):n.snapshot===null?(w="Session",N=e.jsx(je,{label:"Reading sessions",className:"justify-start px-0 pt-5 pb-1"})):(w="No such session",A="sm",N=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(be,{className:"mt-4",onClick:s,children:"Go to the Overview"})]})),e.jsx(Y,{open:a,onOpenChange:y=>{y||s()},title:w,size:A,focusTitle:!0,returnFocus:y=>we(t,y)??document.querySelector("main"),actions:o&&e.jsxs(e.Fragment,{children:[!d&&e.jsxs(e.Fragment,{children:[e.jsx(z,{session:o,jump:p,variant:F(o)?"needs-you":"quiet"}),e.jsx(G,{session:o,stop:f})]}),e.jsx(U,{session:o,resume:g,tooltip:!1})]}),children:N})}function Re({sessionId:t,onClose:a,state:s,now:n,onStopped:l,onAnswered:c}){const[r,u]=m.useState(t);return t!==null&&t!==r&&u(t),r===null?null:e.jsx(Ae,{sessionId:r,open:t!==null,onClose:a,state:s,now:n,onStopped:l,onAnswered:c},r)}export{Re as SessionPanel};