agent-lookout 0.2.8 → 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/CHANGELOG.md +35 -1
- package/PRIVACY.md +39 -23
- package/README.md +7 -6
- package/SECURITY.md +12 -7
- package/dist/assets/{HistoryPanel-CQ7zovsM.js → HistoryPanel-C5-cd24R.js} +1 -1
- package/dist/assets/SessionPanel-CO5qG2uH.js +1 -0
- package/dist/assets/index-BRNSMV9A.css +1 -0
- package/dist/assets/index-dMRHt2Y8.js +9 -0
- package/dist/cli/agent-lookout.js +4 -4
- package/dist/cli/{chunk-JSBQMY3Q.js → chunk-HHZWG5HI.js} +1 -1
- package/dist/cli/{chunk-I6Q35DHC.js → chunk-ME7TBH3B.js} +19 -0
- package/dist/cli/{chunk-LZ62RW3N.js → chunk-WKRBVBCR.js} +1 -1
- package/dist/cli/{mcpServer-BMSF2TXC.js → mcpServer-7VLC6LZN.js} +6 -5
- package/dist/cli/{startCommand-ANC7NVAO.js → startCommand-FV2NXVQR.js} +657 -287
- package/dist/index.html +2 -2
- package/docs/API.md +71 -41
- package/docs/GUIDE.md +100 -64
- package/docs/INSTALL.md +5 -1
- package/package.json +1 -1
- package/dist/assets/SessionPanel-DS29Z04P.js +0 -1
- package/dist/assets/index-BWFkRW4g.js +0 -3
- package/dist/assets/index-B_1Its0R.css +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
Changes that a user of Agent Lookout would notice, newest first.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.2.10 - 2026-10-08
|
|
6
|
+
|
|
7
|
+
A Codex session's details show how many tokens its newest reply took, and the Mac app asks once whether to turn notifications on, then sends a test that says what macOS did with it.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- A Codex session's details give the token counts of its newest reply, as Codex recorded them: the input, with how much of it came from a cache, and the output, such as `182,431 in, 9,120 out`. The input is how much of the session's context is in use. Sessions read over SSH from another machine have them too. A session with none shows a dash and why. What each agent can report, in Sources, has a Tokens column: Yes for Codex, and No for Claude Code, the Antigravity CLI and status files, with the reason. `GET /api/sessions` gives the counts as `tokens`. Nothing else is kept of the line Codex writes them on, and no event, history, email, post, push, `agent-lookout mcp` answer or `agent-lookout status` holds them.
|
|
12
|
+
- The Mac app asks on the Overview, until you answer, whether to turn notifications on: "Get a notification when a session needs you?", with Turn on and Not now. Turn on turns them on and sends one test notification, so macOS asks then whether Agent Lookout may show notifications. If macOS does not show it, or has not answered yet, a note in the question's place says so and points to Settings, until you dismiss it or next open the Overview. Not now leaves them off. Once you choose, there or in Settings, it never asks again, and a browser tab never asks.
|
|
13
|
+
- In the Mac app, the Notifications card in Settings has Send a test, which shows one notification that says only that it is a test and says what macOS made of it: taken, not answered yet, or refused with macOS's reason and what to do in System Settings. Turning notifications on there sends one too. When macOS refused a test, or a notification the app showed itself while its window was closed, and has shown none since, the card says so. `GET /api/app/notifications` and `POST /api/app/notifications/test` are the app's own, and a test sends nothing off this Mac.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- In the Mac app, the Notifications card speaks of macOS and this app instead of your browser and a dashboard tab, says the app's setting is its own, so turning notifications on in a browser does not turn them on in the app, and says that macOS hides banners while the display is shared or mirrored unless that is allowed in System Settings. It no longer says notifications are on as if that meant you see them: while macOS refuses them, it says so. In a browser the card is unchanged.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Stopping Agent Lookout, or quitting the Mac app, while it was writing its history to disk could lose the last few seconds of the Events log and the charts. What that write held is now written as it stops.
|
|
22
|
+
|
|
23
|
+
## 0.2.9 - 2026-10-08
|
|
24
|
+
|
|
25
|
+
The Mac app no longer looks frozen, a Claude Code session's details show what it last said, and Night is darker and crisper everywhere.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- A Claude Code session's details show what it last said, under Last message: the text of its newest reply, up to its last 2,000 characters, as plain text, with its line breaks kept and Markdown as it was written. 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. Agent Lookout's server keeps it in memory for at most 15 seconds after the last ask, and forgets it sooner on the first poll of Claude Code that does not list the session. The page lets it go as soon as the details close or the session leaves the list. A Codex, Antigravity CLI or status-file session, or one on another machine, says it is not read. It is on by default, and `AGENT_LOOKOUT_LAST_MESSAGE=off` turns it off. `GET /api/sessions/last-message?id=<id>` gives the same answer to any program on this computer, as [PRIVACY.md](PRIVACY.md#a-sessions-last-message) says, and [docs/API.md](docs/API.md#get-apisessionslast-messageidid) has its answers.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Night is darker and crisper: a deeper ground, denser smoked glass and finer, brighter edges, on the dashboard, in the Mac app's window and on the landing page. Day is unchanged.
|
|
34
|
+
- No program Agent Lookout starts is given its own settings any more. `claude`, `tmux`, `ssh`, `osascript`, the program that opens your browser and the Mac app's check of a downloaded update were handed every `AGENT_LOOKOUT_` setting in their environment, the mail server's password, the webhook's address and the push tokens among them, as only `gh` was not. None of them used them.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- The Mac app looked frozen: a click or a scroll on most of its window, such as on List, Repos or Board, moved the window instead and never reached the page. The fields of light in the thin strip above the header were taken as part of the strip that moves the window, as Electron 44 has every child of that strip inherit it. Only the strip, the header and the rail's top cell move the window now.
|
|
39
|
+
- An Antigravity CLI session that waits for your approval stops needing you as soon as you answer in agy, not only once agy writes its next step: agy 1.3.1 logs the answer from another part of its program, with more after it, than Agent Lookout expected.
|
|
6
40
|
|
|
7
41
|
## 0.2.8 - 2026-10-07
|
|
8
42
|
|
package/PRIVACY.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Agent Lookout runs on your machine and reads a small amount of metadata about your Claude Code, Codex and Antigravity CLI sessions, and about the sessions of any agent that writes a status file for Agent Lookout to read, and which git branch each session's folder has checked out. By default Agent Lookout itself sends nothing anywhere. Email notifications are off unless you set them up. Once you do, it sends a short email when a session has waited, and, if you choose, when one finishes, fails or ends, through the mail server you name, to the address you name, and nothing else. [Email](#email) says what one holds. Webhook posts are off unless you set an address for them. Once you do, it sends the same notices as short JSON posts to that one address, such as a Slack channel's incoming webhook, and nowhere else. [Webhook](#webhook) says what one holds. Pushes to your phone, through ntfy or Pushover, are off unless you set one up. Once you do, it sends the same notices as short pushes through that service, to the one topic or the one user you name, and nowhere else. [ntfy](#ntfy) and [Pushover](#pushover) say what one holds. The Mac app, and only the Mac app, asks GitHub about once a day whether a newer version of it has been released, unless you turn that off in Settings. It sends nothing about your sessions: [Updates (Mac app only)](#updates-mac-app-only) says what it sends. The npm package, `npx agent-lookout` and the repository never check for anything. Pull requests are off unless you start Agent Lookout with `AGENT_LOOKOUT_PULL_REQUESTS=on`. Then it runs your own GitHub CLI, `gh`, which sends GitHub the owner and name of a session's repository and the name of its branch, with `gh`'s own login, which Agent Lookout never reads: [`gh`](#gh) says what it sends and when. Other machines are read only once you name them in `AGENT_LOOKOUT_REMOTES`. Then it runs your own `ssh` to each and asks the Agent Lookout running there for its sessions, and sends nothing of this computer's: [Another machine over SSH](#another-machine-over-ssh) says what is read. It does run Claude Code's own listing command, which may contact Anthropic the way Claude Code normally does. Its MCP server, `agent-lookout mcp`, runs only once you add it to an agent's app, and answers that app and nothing else. That app may pass what it learns to its model: [The MCP server](#the-mcp-server) says what an answer holds.
|
|
4
4
|
|
|
5
|
-
For a Claude Code session that is waiting for you, Agent Lookout reads the last message of its transcript, to show what the session is asking, such as the command it wants to run or the question it put to you. It
|
|
5
|
+
For a Claude Code session that is waiting for you, Agent Lookout reads the last message of its transcript, to show what the session is asking, such as the command it wants to run or the question it put to you. It keeps nothing of that line once the wait ends. It sends that line off the machine only if you turn it on for email, with `AGENT_LOOKOUT_EMAIL_ASKING=on`, for the webhook, with `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, or for a push, with `AGENT_LOOKOUT_NTFY_ASKING=on` or `AGENT_LOOKOUT_PUSHOVER_ASKING=on`, and then only in the email, post or push for that wait and its reminders. It also goes to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH. That computer shows it, and its own email, webhook and push settings decide whether it is sent on, as [Another machine over SSH](#another-machine-over-ssh) says. `AGENT_LOOKOUT_WAITING_TEXT=off` on this computer keeps it from there too. No answer of `agent-lookout mcp` holds it. [Claude Code's transcripts](#claude-codes-transcripts) says exactly what is read, and `AGENT_LOOKOUT_WAITING_TEXT=off` turns it off.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Agent Lookout also reads what a Claude Code session last said, the text of its newest reply, from the end of its transcript. This is on by default. It reads it only when it is asked for, and the dashboard asks only while that session's details are open. It keeps at most the last 2,000 characters of the reply, in memory only, for at most 15 seconds after it was last asked for, and forgets it sooner on the first poll of Claude Code that does not list the session. It never sends it anywhere on its own. The local API has no password, so any program on this computer, and on a shared Mac another user account, can ask for it too, and so can any program on another computer while that computer reads this one over SSH. `AGENT_LOOKOUT_LAST_MESSAGE=off` turns it off, and the Mac app opens no port. [A session's last message](#a-sessions-last-message) says exactly what is read.
|
|
8
|
+
|
|
9
|
+
For Codex, Agent Lookout opens Codex's session files, which hold the whole conversation. It reads them to find when each turn started and ended, and how many tokens Codex counted for the newest reply, and keeps only that and the few fields listed below. It keeps no prompt, reply, command or output.
|
|
8
10
|
|
|
9
11
|
For the Antigravity CLI, `agy`, Agent Lookout opens the transcripts agy keeps, which hold the whole conversation. It reads the end of each to find the conversation's last step, and keeps only that step's kind, its status, whether it asks for a tool and its time. It keeps no prompt, reply, command or output. It also reads the command lines of the `agy` programs that are running, which can hold a prompt given with `-p`, only to tell a session from one of agy's own commands and to find the conversation one names, and keeps nothing else of them. It reads the log each running agy program keeps 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 keeps nothing else of it, and it reads the short title agy gives each conversation. [The Antigravity CLI's files](#the-antigravity-clis-files) says exactly what is read.
|
|
10
12
|
|
|
@@ -18,7 +20,7 @@ Every 2 seconds Agent Lookout lists the folder `~/.claude/sessions/` and reads e
|
|
|
18
20
|
|
|
19
21
|
### Claude Code's transcripts
|
|
20
22
|
|
|
21
|
-
Claude Code keeps each session's conversation in a transcript, `~/.claude/projects/<folder>/<session ID>.jsonl`, where the folder is named after the session's folder. Agent Lookout reads one
|
|
23
|
+
Claude Code keeps each session's conversation in a transcript, `~/.claude/projects/<folder>/<session ID>.jsonl`, where the folder is named after the session's folder. Agent Lookout reads one while its session is waiting for you, as the registry or the `claude` command says, and when the session's last message is asked for, as [A session's last message](#a-sessions-last-message) says. It reads nothing of any transcript otherwise.
|
|
22
24
|
|
|
23
25
|
For a waiting session it looks for the file in the folder named after the session's folder, and, when it is not there, in each folder in `~/.claude/projects/`, by name, which lists those folders. It uses the session ID only once it has checked that it is an ID, and opens only an ordinary file: it does not follow the transcript file when it is a link, and it never reads a named pipe or a device. The folders above the file are followed when they are links: a folder in `~/.claude/projects/` that is a link, or `projects` itself. It reads at most the last 256 KB of the file, by position, and reads it again only when the file's size or modified time has changed. While a wait goes on it does not look for a file again, except every 10 seconds when none was found.
|
|
24
26
|
|
|
@@ -33,7 +35,19 @@ From those bytes it takes the last tool the session asked to use that has no ans
|
|
|
33
35
|
|
|
34
36
|
Everything else in those bytes, which can include your prompts, the agent's replies, the code it wrote and the output of tools, is dropped as soon as the line is made. When nothing in the end of the file fits, there is no line, and nothing is guessed.
|
|
35
37
|
|
|
36
|
-
That line is shown in the Needs you panel and in the session's details, is put in the notification of that wait that the dashboard page or the server shows on this machine, in the Mac app is in the menu that its icon in the menu bar opens, and is in the session list the API serves on this computer. It is kept in memory beside the place of the file and its size and modified time, only while the session waits, and is forgotten on the first poll after the wait ends. It is not written to disk, not kept in the event log or the history, and not put in an answer of `agent-lookout mcp`. It is put in an email, a webhook post or a push only when you turn that on, as [Email](#email), [Webhook](#webhook), [ntfy](#ntfy) and [Pushover](#pushover) say. It also goes to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH, which shows it, and whose own email, webhook and push settings decide whether it is sent on, as [Another machine over SSH](#another-machine-over-ssh) says. Set `AGENT_LOOKOUT_WAITING_TEXT=off` and no transcript is opened at all, so there is no line to send either.
|
|
38
|
+
That line is shown in the Needs you panel and in the session's details, is put in the notification of that wait that the dashboard page or the server shows on this machine, in the Mac app is in the menu that its icon in the menu bar opens, and is in the session list the API serves on this computer. It is kept in memory beside the place of the file and its size and modified time, only while the session waits, and is forgotten on the first poll after the wait ends. It is not written to disk, not kept in the event log or the history, and not put in an answer of `agent-lookout mcp`. It is put in an email, a webhook post or a push only when you turn that on, as [Email](#email), [Webhook](#webhook), [ntfy](#ntfy) and [Pushover](#pushover) say. It also goes to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH, which shows it, and whose own email, webhook and push settings decide whether it is sent on, as [Another machine over SSH](#another-machine-over-ssh) says. Set `AGENT_LOOKOUT_WAITING_TEXT=off` and no transcript is opened at all, so there is no line to send either, and no session's last message is read.
|
|
39
|
+
|
|
40
|
+
### A session's last message
|
|
41
|
+
|
|
42
|
+
This is on by default. Agent Lookout reads what a Claude Code session last said only when it is asked for, through `GET /api/sessions/last-message`. The dashboard asks only while a session's details are open. No poll reads it.
|
|
43
|
+
|
|
44
|
+
It reads it only for a session on this computer that is in the list it last made. It finds and opens the transcript as [Claude Code's transcripts](#claude-codes-transcripts) describes, by the session's ID, once it has checked that it is one, and the session's folder. The request only names the session. It does not follow the file when it is a link, never reads a named pipe or a device, and reads at most the last 256 KB, by position. It reads at most four transcripts a second, and for a second after a read it gives the same answer again without looking.
|
|
45
|
+
|
|
46
|
+
From those bytes it takes the text of the session's newest reply: the `text` parts of its last assistant message, joined with a blank line between. It leaves out the lines of the session's subagents, and never takes the model's thinking, the tools it asked to use, their output, your prompts or the lines Claude Code writes for itself. It keeps the line breaks, makes a tab two spaces and any other control character a space, takes out the characters that change the direction of text, and keeps at most the last 2,000 characters. Everything else in those bytes is dropped as soon as the text is made. When the reply is further back than those bytes, or there is none yet, there is no text, and nothing is guessed.
|
|
47
|
+
|
|
48
|
+
The text is kept in memory beside the place of the file and its size and modified time, for eight sessions at most, and only for 15 seconds after it was last asked for. It is forgotten sooner on the first poll that does not list the session. It is not written to disk, and it is never put in the session list, an event, the history, a notification, an email, a webhook post, a push or an answer of `agent-lookout mcp`. Agent Lookout never sends it anywhere on its own, and never asks another machine for one. The dashboard shows it in that session's details, holds it in the page only while they are open, and keeps none of it in the browser's storage, another tab or the address.
|
|
49
|
+
|
|
50
|
+
The local API has no password, as [Network](#network) says. So while Agent Lookout runs, any program on this computer, and on a shared Mac another user account, can ask it for up to 2,000 characters of what a listed Claude Code session last said. While another computer reads this one over SSH, any program or account on that computer can ask too, through its tunnel, as [SECURITY.md](SECURITY.md) says. A reply can quote code, paths and anything else the session wrote. A website cannot ask: the API answers only requests made to this computer, and refuses one that a browser says did not come from the dashboard's own page. Set `AGENT_LOOKOUT_LAST_MESSAGE=off` and no transcript is opened for it. `AGENT_LOOKOUT_WAITING_TEXT=off` turns it off too. The Mac app opens no port, so there only its own window can ask.
|
|
37
51
|
|
|
38
52
|
### `claude agents --json --all`
|
|
39
53
|
|
|
@@ -122,12 +136,14 @@ When the folder is there, every 2 seconds Agent Lookout:
|
|
|
122
136
|
|
|
123
137
|
- lists `sessions/` and the day folders for today and yesterday, `sessions/YYYY/MM/DD/`. While Codex has a session open whose file is in an older day folder, as a resumed session's can be, it lists every day folder: at most once every 5 seconds when Codex has newly opened such a session, and otherwise at most once every 30 seconds. Listing reads file names only.
|
|
124
138
|
- lists `thread-writer-locks/`, where Codex keeps one `<thread id>.lock` file for each session a Codex program has open. It reads the names and never opens these files.
|
|
125
|
-
- opens, read-only, each session file named `rollout-*.jsonl` in those day folders. It reads up to the first 2 MiB of a file to find its first `session_meta` line, and up to the last 8 MiB to find the last line that says a turn started or ended. After that it reads only what Codex has added, and only when the file's size or modified time has changed. A file not written to for a day, whose session no Codex program has open, is not opened. The file's modified time, which it already looks up to see whether the file has changed, is also passed to the dashboard as the time Codex last wrote to the session, to say how long a working session has been quiet. A subagent's file is one of these session files too, and its modified time counts as a write by the session that started it.
|
|
139
|
+
- opens, read-only, each session file named `rollout-*.jsonl` in those day folders. It reads up to the first 2 MiB of a file to find its first `session_meta` line, and up to the last 8 MiB to find the last line that says a turn started or ended and the newest line that counts a reply's tokens. After that it reads only what Codex has added, and only when the file's size or modified time has changed. A file not written to for a day, whose session no Codex program has open, is not opened. The file's modified time, which it already looks up to see whether the file has changed, is also passed to the dashboard as the time Codex last wrote to the session, to say how long a working session has been quiet. A subagent's file is one of these session files too, and its modified time counts as a write by the session that started it.
|
|
126
140
|
- opens `session_index.jsonl`, where Codex keeps the names you gave sessions, when its size or modified time has changed, and reads at most its last 4 MiB.
|
|
127
141
|
|
|
128
|
-
The bytes it reads are held in memory only while the file is read. A line is parsed only when it names `session_meta`, or names `event_msg` and looks like a turn line. From every other line, which includes your prompts, Codex's replies, the commands it ran and their output, Agent Lookout takes only the time the line was written.
|
|
142
|
+
The bytes it reads are held in memory only while the file is read. A line is parsed only when it names `session_meta`, or names `event_msg` and looks like a turn line or a token count line. From every other line, which includes your prompts, Codex's replies, the commands it ran and their output, Agent Lookout takes only the time the line was written.
|
|
143
|
+
|
|
144
|
+
From the `session_meta` line it keeps `id`, `timestamp`, `cwd`, `source`, `thread_source`, `parent_thread_id`, `originator` (the program that started the session, such as the Codex desktop app) and `cli_version` (the Codex version), and drops the rest, which includes Codex's instructions, its list of tools, git details and account IDs. From a turn line it keeps the kind of line, such as `task_started` or `task_complete`, its time, and whether its `turn_id` marks a turn the Codex desktop app imported from another agent. From the newest token count line, an `event_msg` of type `token_count`, it keeps three numbers of the newest reply, from `info.last_token_usage`: `input_tokens`, `cached_input_tokens` and `output_tokens`. It drops the rest of that line, which includes the session's running totals (`total_token_usage`), the model's context window and `rate_limits`, which holds your plan, your credits and how much of your usage limits you have used. From `session_index.jsonl` it keeps `id` and `thread_name`.
|
|
129
145
|
|
|
130
|
-
|
|
146
|
+
The three numbers are kept in memory only, with the rest of what it keeps of the session. They are shown in the session's details, and are in the session list the API serves on this computer, which any program on this computer can read, as it can every field there. They also go to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH. No event, history, notification, email, webhook post, push, answer of `agent-lookout mcp` or output of `agent-lookout status` holds them.
|
|
131
147
|
|
|
132
148
|
A session file or `session_index.jsonl` that is a link, a named pipe or a device is never opened. The Codex folder and the folders inside it are followed when they are links.
|
|
133
149
|
|
|
@@ -242,18 +258,18 @@ Only when `AGENT_LOOKOUT_REMOTES` names other machines, Agent Lookout reads the
|
|
|
242
258
|
|
|
243
259
|
For each machine it runs `ssh -N -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 -o ControlMaster=no -o ControlPath=none -L 127.0.0.1:<a free port>:127.0.0.1:<its port> -- <target>`, with the target you named, such as `dev@devbox.local`, after `--`, and with the port Agent Lookout listens on there, 4777 unless you give another. `ssh` is the one on your `PATH`, or in `/usr/bin`, `/opt/homebrew/bin` or `/usr/local/bin`, or the one `AGENT_LOOKOUT_SSH_BIN` names. It is started directly, by its full path, never through a shell, with nothing on its input, and given the environment Agent Lookout was started with, so it uses your `~/.ssh/config` and your ssh agent as it does in a terminal. With BatchMode it asks nothing, so Agent Lookout never sees, types or keeps a password, a passphrase or a key, and never answers a question about a host key. ssh runs no command on the other machine: it forwards one port on this computer's `127.0.0.1` to Agent Lookout's port on that machine's own `127.0.0.1`. With `ControlMaster=no` and `ControlPath=none` it makes a connection of its own every time, even when your ssh config shares connections, so it never joins an ssh you have open and never leaves one running in the background: the forward ends when that ssh ends. Agent Lookout keeps the last lines ssh writes about a failure, in memory, and shows the last of them on the machine's card in Sources, such as `Permission denied (publickey).` When ssh ends, it is started again after a wait, and when Agent Lookout stops, it ends it.
|
|
244
260
|
|
|
245
|
-
Through that port, every 2 seconds, it sends two requests and no others: `GET /api/health`, whose answer is the version of Agent Lookout there, and `GET /api/sessions`, whose answer is that machine's sessions and sources, as [docs/API.md](docs/API.md#get-apisessions) describes. The requests carry nothing of this computer's: no session, no setting, and not the `X-Agent-Lookout-Notifications` header, so they change nothing that machine's Agent Lookout believes about its own pages. Of the answer it keeps, in memory, what this computer shows of its own sessions: each session's name, folder, status, reason, the vendor's wording of a wait, its times, its app, its branch and repository name, its agent's name
|
|
261
|
+
Through that port, every 2 seconds, it sends two requests and no others: `GET /api/health`, whose answer is the version of Agent Lookout there, and `GET /api/sessions`, whose answer is that machine's sessions and sources, as [docs/API.md](docs/API.md#get-apisessions) describes. The requests carry nothing of this computer's: no session, no setting, and not the `X-Agent-Lookout-Notifications` header, so they change nothing that machine's Agent Lookout believes about its own pages. It never asks that machine what a session there last said. Of the answer it keeps, in memory, what this computer shows of its own sessions: each session's name, folder, status, reason, the vendor's wording of a wait, its times, its app, its branch and repository name, its agent's name, whether its process still runs and the token counts of its newest reply, when it has them. It drops each session's process ID, Jump, Stop and links, and any permission request that machine holds for it, with what the request would run, as the answer is read, so nothing about a session there can act on this computer. It keeps each source there by its name and its state, and what each of its agents can report. It does not take that machine's time rules, or its word on whether it is in quiet hours: this computer's own decide when a session there is stale, and when a wait there is reminded of or held back here.
|
|
246
262
|
|
|
247
263
|
That includes what a waiting session there is asking, such as `Run: npm test`, when that machine's Agent Lookout sends it, which it does unless `AGENT_LOOKOUT_WAITING_TEXT=off` is set there. With `AGENT_LOOKOUT_WAITING_TEXT=off` set on this computer, it is dropped as it arrives, and a waiting session there shows its reason alone, as one here does. Otherwise it is shown here as a waiting session's is, and in the notifications on this computer, and is kept no longer than the wait, as here. Like a session's on this computer, a session there and its events go into the history kept on this computer, without what it is asking, and into an email, a webhook post or a push as the settings for those say.
|
|
248
264
|
|
|
249
265
|
### Settings
|
|
250
266
|
|
|
251
|
-
It reads forty-
|
|
267
|
+
It reads forty-four settings from the environment: `AGENT_LOOKOUT_CLAUDE_HOME`, `AGENT_LOOKOUT_CLAUDE_BIN`, `AGENT_LOOKOUT_CLAUDE_FEED`, `AGENT_LOOKOUT_WAITING_TEXT`, `AGENT_LOOKOUT_LAST_MESSAGE`, `AGENT_LOOKOUT_CODEX_HOME`, `AGENT_LOOKOUT_ANTIGRAVITY_HOME`, `AGENT_LOOKOUT_STATUS_DIR`, `AGENT_LOOKOUT_HISTORY`, `AGENT_LOOKOUT_HISTORY_DIR`, `AGENT_LOOKOUT_SETTINGS_FILE`, `AGENT_LOOKOUT_NOTIFICATIONS`, `AGENT_LOOKOUT_TMUX`, `AGENT_LOOKOUT_TERMINAL_JUMP`, `AGENT_LOOKOUT_STOP`, `AGENT_LOOKOUT_PULL_REQUESTS`, `AGENT_LOOKOUT_ANSWER`, `AGENT_LOOKOUT_ANSWER_SOCKET`, `AGENT_LOOKOUT_ANSWER_WAIT`, `AGENT_LOOKOUT_PORT`, `AGENT_LOOKOUT_HOST`, `AGENT_LOOKOUT_REMOTES`, `AGENT_LOOKOUT_SSH_BIN`, Codex's own `CODEX_HOME`, the six email settings, `AGENT_LOOKOUT_EMAIL_TO`, `AGENT_LOOKOUT_SMTP_URL`, `AGENT_LOOKOUT_EMAIL_FROM`, `AGENT_LOOKOUT_EMAIL_AFTER`, `AGENT_LOOKOUT_EMAIL_EVENTS` and `AGENT_LOOKOUT_EMAIL_ASKING`, the four webhook settings, `AGENT_LOOKOUT_WEBHOOK_URL`, `AGENT_LOOKOUT_WEBHOOK_EVENTS`, `AGENT_LOOKOUT_WEBHOOK_AFTER` and `AGENT_LOOKOUT_WEBHOOK_ASKING`, the five ntfy settings, `AGENT_LOOKOUT_NTFY_URL`, `AGENT_LOOKOUT_NTFY_TOKEN`, `AGENT_LOOKOUT_NTFY_EVENTS`, `AGENT_LOOKOUT_NTFY_AFTER` and `AGENT_LOOKOUT_NTFY_ASKING`, and the five Pushover settings, `AGENT_LOOKOUT_PUSHOVER_TOKEN`, `AGENT_LOOKOUT_PUSHOVER_USER`, `AGENT_LOOKOUT_PUSHOVER_EVENTS`, `AGENT_LOOKOUT_PUSHOVER_AFTER` and `AGENT_LOOKOUT_PUSHOVER_ASKING`. The `agent-lookout` command reads one more, `AGENT_LOOKOUT_URL`, the address of the Agent Lookout to ask, and `agent-lookout status` reads tmux's own `TMUX`, only to tell whether tmux is running it. `AGENT_LOOKOUT_CLAUDE_HOME` replaces `~/.claude` in everything above, and `AGENT_LOOKOUT_ANTIGRAVITY_HOME` replaces `~/.gemini/antigravity-cli`. [Email](#email) says how the email settings are kept, [Webhook](#webhook) how the webhook's are, and [ntfy](#ntfy) and [Pushover](#pushover) how theirs are. No program Agent Lookout starts is given any of them: `claude`, `gh`, `tmux`, `osascript`, `ssh`, the program that opens your browser and the Mac app's check of a downloaded update get Agent Lookout's environment without every `AGENT_LOOKOUT_` setting, and `ps` gets only a search path, a language and a time zone, so the mail server's password, the webhook's address and the push tokens never reach them. The time rules and the permission rules are not in the environment: Agent Lookout keeps them in a file of its own, as [The settings file](#the-settings-file) says. The [guide](docs/GUIDE.md#settings-you-can-change) says what each setting does.
|
|
252
268
|
|
|
253
269
|
## What it never reads
|
|
254
270
|
|
|
255
271
|
- The `.key` files that sit beside the registry files in `~/.claude/sessions/`.
|
|
256
|
-
- Claude Code's transcripts, in `~/.claude/projects/` or anywhere else, beyond the end of a waiting session's own, as [Claude Code's transcripts](#claude-codes-transcripts)
|
|
272
|
+
- Claude Code's transcripts, in `~/.claude/projects/` or anywhere else, beyond the end of a waiting session's own and of a session whose last message is asked for, as [Claude Code's transcripts](#claude-codes-transcripts) and [A session's last message](#a-sessions-last-message) describe. Of what it reads there, it keeps one line about the tool the session is asking to use and, for a short while, the text of the session's newest reply, and none of your prompts, the agent's earlier replies, its thinking or the output of tools.
|
|
257
273
|
- Claude Code's settings, credentials, history and memory files.
|
|
258
274
|
- Codex's `auth.json`, `config.toml`, `history.jsonl`, its SQLite files (`*.sqlite`), its `log/` folder, `archived_sessions/` and compressed session files (`*.jsonl.zst`), and the Codex desktop app's `external_agent_session_imports.json` and `.codex-global-state.json`.
|
|
259
275
|
- In `~/.gemini`: `oauth_creds.json`, `google_accounts.json`, `settings.json`, `config/hooks.json`, `history/` and any `history.jsonl`, and in the Antigravity CLI's folder its SQLite files, `conversation_summaries.db` and `conversations/*.db` with their `-wal` and `-shm` files, which are only looked up for when they were last changed, `transcript_full.jsonl`, `cli.log` and the logs in `log/` of agy programs `ps` does not show, `jetski_state.pbtxt`, `jetbox_summaries_proto.pb`, `installation_id`, `updater/`, `presence/`, any file in `annotations/` but a conversation's `.pbtxt`, `implicit/`, `crashes/` and the other files in each conversation's folder. Nor the Antigravity desktop app's folder, `~/.gemini/antigravity`, or the Antigravity IDE's.
|
|
@@ -276,15 +292,15 @@ The `claude agents` command is Claude Code's own program, and it may contact Ant
|
|
|
276
292
|
|
|
277
293
|
The API refuses any request whose `Host` header is not `localhost`, `127.0.0.1` or `[::1]`, any request whose `Origin` header names another site, and any request the browser marks as cross-site. It sends no CORS headers. Together these stop a website you visit from reading your session names and paths through your browser.
|
|
278
294
|
|
|
279
|
-
Each request the dashboard makes carries one header of Agent Lookout's own, `X-Agent-Lookout-Notifications`, which says which events that page has notifications on for: `off`, `on`, which means a session starting to wait, or `on; events=` followed by the names of the events, such as `on; events=needs-you,finished`. It holds nothing else. It goes to Agent Lookout's own server and nowhere else. [Notifications](#notifications) says what the server does with it. The request the Jump button sends for a session in tmux, Terminal or iTerm2 carries one more, `X-Agent-Lookout-Action: jump`, which names what is asked for, and the request the Clear history button in Settings sends carries `X-Agent-Lookout-Action: clear-history`, with an empty body. The request Stop session sends carries `X-Agent-Lookout-Action: stop` and the session's ID, and the one that ends the sessions left running carries `X-Agent-Lookout-Action: clean-up`, with each session's ID and the moment its idle began. The one Allow and Deny send carries `X-Agent-Lookout-Action: answer`, with the session's ID, Agent Lookout's own ID for the request and the answer. The one the Time rules card sends carries `X-Agent-Lookout-Action: time-rules`, with the three rules, and the one the Permission rules card sends carries `X-Agent-Lookout-Action: permission-rules`, with one change to the rules. Send a test, on the ntfy and Pushover cards, sends `X-Agent-Lookout-Action: phone-test`, with `{"channel": "ntfy"}` or `{"channel": "pushover"}`. In the Mac app, the Updates card also sends `X-Agent-Lookout-Action: check-for-updates` and `install-update`, each with the body `{}`, and `update-setting`, with `{"automatic": true}` or `false`. The Menu bar card sends `menu-bar-setting`, with `{"show": true}` or `false`.
|
|
295
|
+
Each request the dashboard makes carries one header of Agent Lookout's own, `X-Agent-Lookout-Notifications`, which says which events that page has notifications on for: `off`, `on`, which means a session starting to wait, or `on; events=` followed by the names of the events, such as `on; events=needs-you,finished`. It holds nothing else. It goes to Agent Lookout's own server and nowhere else. [Notifications](#notifications) says what the server does with it. The request the Jump button sends for a session in tmux, Terminal or iTerm2 carries one more, `X-Agent-Lookout-Action: jump`, which names what is asked for, and the request the Clear history button in Settings sends carries `X-Agent-Lookout-Action: clear-history`, with an empty body. The request Stop session sends carries `X-Agent-Lookout-Action: stop` and the session's ID, and the one that ends the sessions left running carries `X-Agent-Lookout-Action: clean-up`, with each session's ID and the moment its idle began. The one Allow and Deny send carries `X-Agent-Lookout-Action: answer`, with the session's ID, Agent Lookout's own ID for the request and the answer. The one the Time rules card sends carries `X-Agent-Lookout-Action: time-rules`, with the three rules, and the one the Permission rules card sends carries `X-Agent-Lookout-Action: permission-rules`, with one change to the rules. Send a test, on the ntfy and Pushover cards, sends `X-Agent-Lookout-Action: phone-test`, with `{"channel": "ntfy"}` or `{"channel": "pushover"}`. In the Mac app, the Updates card also sends `X-Agent-Lookout-Action: check-for-updates` and `install-update`, each with the body `{}`, and `update-setting`, with `{"automatic": true}` or `false`. The Menu bar card sends `menu-bar-setting`, with `{"show": true}` or `false`. Send a test on the Notifications card, and turning notifications on there or with Turn on in the Overview's question, send `notification-test`, with `{}`.
|
|
280
296
|
|
|
281
|
-
The local server has no password. While Agent Lookout is running, another program on the same machine can request the same data, and on a shared computer so can another user account. Such a program can also send that header, and so turn the notifications the server shows on or off. It can also ask the server to select a tmux pane, or bring a tab of Terminal or iTerm2 forward, as the Jump button does, to clear the history, as the Clear history button does, to stop a Claude Code session, or end the sessions left running, as Stop and End all… do, with every check of [Stopping a session](#stopping-a-session), to answer a permission prompt Agent Lookout is holding, as Allow and Deny do, to change the time rules or the permission rules, as Settings does, which includes adding an allow rule that then answers prompts without a press, and to send a test push, as Send a test does, which counts toward the hour's 20 for that channel. A program that can do that can already type into your terminal, but on a shared computer another account could not. The Mac app opens no port, so there only its own window can ask any of this. It shares the settings file, though. A program running as you can write `~/.agent-lookout/settings.json`, or start `npx agent-lookout` beside the app and ask that copy to change a rule, and the Mac app puts the time rules and permission rules the file then holds in force at its next poll, an allow rule included. `AGENT_LOOKOUT_STOP=off` takes stopping away,
|
|
297
|
+
The local server has no password. While Agent Lookout is running, another program on the same machine can request the same data, and on a shared computer so can another user account. That includes up to 2,000 characters of what a Claude Code session last said, as [A session's last message](#a-sessions-last-message) says. Such a program can also send that header, and so turn the notifications the server shows on or off. It can also ask the server to select a tmux pane, or bring a tab of Terminal or iTerm2 forward, as the Jump button does, to clear the history, as the Clear history button does, to stop a Claude Code session, or end the sessions left running, as Stop and End all… do, with every check of [Stopping a session](#stopping-a-session), to answer a permission prompt Agent Lookout is holding, as Allow and Deny do, to change the time rules or the permission rules, as Settings does, which includes adding an allow rule that then answers prompts without a press, and to send a test push, as Send a test does, which counts toward the hour's 20 for that channel. A program that can do that can already type into your terminal, but on a shared computer another account could not. The Mac app opens no port, so there only its own window can ask any of this. It shares the settings file, though. A program running as you can write `~/.agent-lookout/settings.json`, or start `npx agent-lookout` beside the app and ask that copy to change a rule, and the Mac app puts the time rules and permission rules the file then holds in force at its next poll, an allow rule included. `AGENT_LOOKOUT_STOP=off` takes stopping away, `AGENT_LOOKOUT_ANSWER=off` takes answering away, and `AGENT_LOOKOUT_LAST_MESSAGE=off` takes last messages away.
|
|
282
298
|
|
|
283
|
-
For a session in VS Code, the Jump button opens a `vscode://` address that contains the session ID. Your operating system hands it to VS Code. For a session in tmux, the Jump button sends a request that holds the session's ID to Agent Lookout's own server, which selects the pane as described under [`tmux`](#tmux). For a session in Terminal or iTerm2 it sends the same request, and the server brings the tab forward as described under [Terminal and iTerm2](#terminal-and-iterm2). That request, the one Clear history sends, the ones Stop session and End send, the one Allow and Deny send, the ones the Time rules and Permission rules cards send and the one Send a test sends are the eight that change anything, with the Mac app's
|
|
299
|
+
For a session in VS Code, the Jump button opens a `vscode://` address that contains the session ID. Your operating system hands it to VS Code. For a session in tmux, the Jump button sends a request that holds the session's ID to Agent Lookout's own server, which selects the pane as described under [`tmux`](#tmux). For a session in Terminal or iTerm2 it sends the same request, and the server brings the tab forward as described under [Terminal and iTerm2](#terminal-and-iterm2). That request, the one Clear history sends, the ones Stop session and End send, the one Allow and Deny send, the ones the Time rules and Permission rules cards send and the one Send a test sends on the ntfy and Pushover cards are the eight that change anything, with the Mac app's five for its updates, its menu bar and its test notification. The server refuses them from any website, though not from another program on this machine, as the paragraph above says, and the Mac app answers its five only for its own window. [SECURITY.md](SECURITY.md) lists its checks.
|
|
284
300
|
|
|
285
301
|
## Notifications
|
|
286
302
|
|
|
287
|
-
Notifications are off until you turn them on in Settings, or start Agent Lookout with `AGENT_LOOKOUT_NOTIFICATIONS=on`, which turns on only the ones the server shows, and only for a session starting to wait. Pressing Turn on notifications is the only thing in Agent Lookout that asks your browser for permission to show them.
|
|
303
|
+
Notifications are off until you turn them on in Settings, or in the Mac app with Turn on in the question the Overview asks until you answer it, or start Agent Lookout with `AGENT_LOOKOUT_NOTIFICATIONS=on`, which turns on only the ones the server shows, and only for a session starting to wait. Pressing Turn on notifications is the only thing in Agent Lookout that asks your browser for permission to show them. In the Mac app, Send a test on the Notifications card, which is there while notifications are off too, and turning them on there or on the Overview, show one test notification, titled Agent Lookout, that says only that it is a test. No network request is involved.
|
|
288
304
|
|
|
289
305
|
Once they are on, Settings lists four events, each with its own switch: a session starting to wait for you, finishing, failing, and ending, which is leaving the list without having finished or failed. The first is on and the other three are off until you change them. A notification is made only for the events switched on.
|
|
290
306
|
|
|
@@ -317,7 +333,7 @@ It holds none of Claude Code's own wording and no tag. As in the page's, what a
|
|
|
317
333
|
|
|
318
334
|
### Reminders and summaries
|
|
319
335
|
|
|
320
|
-
With the [time rules](docs/GUIDE.md#time-rules) on, the page and the server make two more kinds of notification, in the same two ways and by the same switches. A reminder of a long wait has the session's name as its title, with the machine's name for a session on another machine, and as its text how long it has waited and for what, with what it is asking after it, as in "Has waited 10 minutes for permission: Run: npm test". On the page it takes the place of the wait's own, with its tag. With Remind again on, the same reminder is made again at each interval while the session still waits, with how long it has waited by then, on the page each taking the place of the last, and none once the wait is answered or the session ends. The summary made when quiet hours end has "While quiet" as its title, and as its text the names of the sessions that waited, finished, failed or ended while they held, each with the machine's name for a session on another machine, with how long each waited, as in "checkout-flow waited 25 minutes and billing-webhooks finished". It holds nothing a session was asking. The page's has the tag `agent-lookout:while-quiet`, and stays until you clear it. During quiet hours neither the page nor the server makes any notification.
|
|
336
|
+
With the [time rules](docs/GUIDE.md#time-rules) on, the page and the server make two more kinds of notification, in the same two ways and by the same switches. A reminder of a long wait has the session's name as its title, with the machine's name for a session on another machine, and as its text how long it has waited and for what, with what it is asking after it, as in "Has waited 10 minutes for permission: Run: npm test". On the page it takes the place of the wait's own, with its tag. With Remind again on, the same reminder is made again at each interval while the session still waits, with how long it has waited by then, on the page each taking the place of the last, and none once the wait is answered or the session ends. The summary made when quiet hours end has "While quiet" as its title, and as its text the names of the sessions that waited, finished, failed or ended while they held, each with the machine's name for a session on another machine, with how long each waited, as in "checkout-flow waited 25 minutes and billing-webhooks finished". It holds nothing a session was asking. The page's has the tag `agent-lookout:while-quiet`, and stays until you clear it. During quiet hours neither the page nor the server makes any notification, except the Mac app's test notification, which you ask for.
|
|
321
337
|
|
|
322
338
|
The server shows them only while notifications are on, and only for the events switched on, and it learns both from the dashboard. Each request a dashboard page makes says, in the `X-Agent-Lookout-Notifications` header, which events are on when that page's choice is on and the browser allows notifications, and `off` otherwise. The server keeps the last thing a page said, in memory, for as long as it runs, and never writes it to disk. Before any page has said anything it is off, unless `AGENT_LOOKOUT_NOTIFICATIONS=on` was set when Agent Lookout started, which turns it on for a session starting to wait. A page that says `off` after that turns it off all the same. The header is listened to only on a request that passes the checks described under [Network](#network), so a page at another address cannot set it.
|
|
323
339
|
|
|
@@ -346,7 +362,7 @@ An email is plain text. Its subject is the session's name followed by the reason
|
|
|
346
362
|
|
|
347
363
|
With the [time rules](docs/GUIDE.md#time-rules) on, two more kinds of email can be sent. A reminder of a long wait is the email of a wait with a subject that says how long it has waited, such as "checkout-flow has waited 10 minutes for permission", and a line on how often reminders come. With Remind again on, the same email goes again at each interval while the session still waits, saying how long by then, and holds nothing more than the first. The summary sent when quiet hours end has the subject "While quiet:" followed by the first few sessions' names and what happened, and in its body, one line for each session that waited, finished, failed or ended while they held, with how long it waited and from when, or when it finished, failed or ended, at most 50, and a line on where quiet hours are set. A summary holds the sessions' names alone, nothing of what they were asking and no folder.
|
|
348
364
|
|
|
349
|
-
It holds no folder path, no prompt, none of Claude Code's own wording for the wait, no link, no image, nothing that reports back when it is opened and, unless you set `AGENT_LOOKOUT_EMAIL_ASKING=on` (below), nothing of what a waiting session is asking. Its headers are the ones the mail library writes for every email: From, with the name Agent Lookout and the sender's address, To, Subject, Date, Message-ID, and three that say it is plain text in UTF-8. A session's name and its folder's name are cut to 80 characters, and anything in them that would end a line becomes a space, so nothing in a name can add a header or a recipient.
|
|
365
|
+
It holds no folder path, no prompt, none of Claude Code's own wording for the wait, no token counts, no link, no image, nothing that reports back when it is opened and, unless you set `AGENT_LOOKOUT_EMAIL_ASKING=on` (below), nothing of what a waiting session is asking. Its headers are the ones the mail library writes for every email: From, with the name Agent Lookout and the sender's address, To, Subject, Date, Message-ID, and three that say it is plain text in UTF-8. A session's name and its folder's name are cut to 80 characters, and anything in them that would end a line becomes a space, so nothing in a name can add a header or a recipient.
|
|
350
366
|
|
|
351
367
|
#### What a waiting session is asking, in an email
|
|
352
368
|
|
|
@@ -449,7 +465,7 @@ With the [time rules](docs/GUIDE.md#time-rules) on, two more kinds of post can b
|
|
|
449
465
|
|
|
450
466
|
`from` and `to` are when Agent Lookout saw the quiet hours begin and end, and `items` holds each session that waited, finished, failed or ended while they held, at most 100, with `more` saying how many were left out. A session that waited and then finished, failed or ended is one item, whose `then` says what it did and when, as `{"event": "ended", "at": "..."}`. Each item's `session` holds what a post's always does, and no item holds anything a session was asking.
|
|
451
467
|
|
|
452
|
-
It holds no folder path, no prompt, none of Claude Code's own wording for the wait, no session ID or process ID, nothing about this computer and, unless you set `AGENT_LOOKOUT_WEBHOOK_ASKING=on` (below), nothing of what a waiting session is asking. Its headers are `Content-Type: application/json`, `Content-Length`, `User-Agent: Agent Lookout/` followed by the version, `Host` and `Connection: close`. It carries no cookie and no other header.
|
|
468
|
+
It holds no folder path, no prompt, none of Claude Code's own wording for the wait, no token counts, no session ID or process ID, nothing about this computer and, unless you set `AGENT_LOOKOUT_WEBHOOK_ASKING=on` (below), nothing of what a waiting session is asking. Its headers are `Content-Type: application/json`, `Content-Length`, `User-Agent: Agent Lookout/` followed by the version, `Host` and `Connection: close`. It carries no cookie and no other header.
|
|
453
469
|
|
|
454
470
|
A session's name, its folder's name and its agent's are each cut to 80 characters, and anything in them that would end a line becomes a space. In `text`, `<`, `>` and `&` are written as `<`, `>` and `&`, which Slack shows as the characters themselves, and each `@` is followed by a space of no width, so nothing in a name can mention someone, ping a channel or make a link that shows other words than its address. The JSON is written whole by Node's own writer, so nothing in a name can add a field. Slack does show a web address written out in a name as a link to that address.
|
|
455
471
|
|
|
@@ -510,7 +526,7 @@ It holds:
|
|
|
510
526
|
|
|
511
527
|
With the [time rules](docs/GUIDE.md#time-rules) on, two more kinds of push can be sent. A reminder of a long wait has a title that says how long it has waited, such as "checkout-flow has waited 10 minutes for permission", and a message that says since when, as in "Since 14:01 · storefront · VS Code · Claude Code". With Remind again on, the same push goes again at each interval while the session still waits, saying how long by then. The summary sent when quiet hours end has the title "While quiet", and as its message the names of the first four sessions that waited, finished, failed or ended while they held, with how long each waited, and how many more there were, as in "checkout-flow waited 25 minutes and billing-webhooks finished". It holds nothing a session was asking and no folder.
|
|
512
528
|
|
|
513
|
-
It holds no folder path, no prompt, none of Claude Code's own wording for the wait, no session ID or process ID, no link, not the dashboard's address, nothing about this computer and, unless you set `AGENT_LOOKOUT_NTFY_ASKING=on` (below), nothing of what a waiting session is asking. Its headers are `Content-Type: application/json`, `Content-Length`, `User-Agent: Agent Lookout/` followed by the version, `Authorization: Bearer` followed by the token only when `AGENT_LOOKOUT_NTFY_TOKEN` is set, `Host` and `Connection: close`. It carries no cookie and no other header. A session's name, its folder's name and its agent's are each cut to 80 characters, and anything in them that would end a line becomes a space. The title is cut to 250 characters and the message to 1,024. The JSON is written whole by Node's own writer, so nothing in a name can add a field, and it is not marked as Markdown, so ntfy shows the words as they are. The ntfy apps may show a web address written out in a name as a link.
|
|
529
|
+
It holds no folder path, no prompt, none of Claude Code's own wording for the wait, no token counts, no session ID or process ID, no link, not the dashboard's address, nothing about this computer and, unless you set `AGENT_LOOKOUT_NTFY_ASKING=on` (below), nothing of what a waiting session is asking. Its headers are `Content-Type: application/json`, `Content-Length`, `User-Agent: Agent Lookout/` followed by the version, `Authorization: Bearer` followed by the token only when `AGENT_LOOKOUT_NTFY_TOKEN` is set, `Host` and `Connection: close`. It carries no cookie and no other header. A session's name, its folder's name and its agent's are each cut to 80 characters, and anything in them that would end a line becomes a space. The title is cut to 250 characters and the message to 1,024. The JSON is written whole by Node's own writer, so nothing in a name can add a field, and it is not marked as Markdown, so ntfy shows the words as they are. The ntfy apps may show a web address written out in a name as a link.
|
|
514
530
|
|
|
515
531
|
#### What a waiting session is asking, in a push
|
|
516
532
|
|
|
@@ -622,7 +638,7 @@ For each tool the agent calls, it asks Agent Lookout's own server for the list o
|
|
|
622
638
|
|
|
623
639
|
It answers the app that started it, over stdout, and nothing and nobody else. An answer is one of three:
|
|
624
640
|
|
|
625
|
-
- For each session: its ID, its name, its agent, the other machine it runs on, when it runs on one read over SSH, its status, why it waits, the name of its folder and never the rest of the path, its branch or commit, its app, when its status began, and how long its agent has written nothing. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, also the number of its branch's pull request and whether its checks are failing, pending or passing, and never its title. Never what a waiting session is asking, which
|
|
641
|
+
- For each session: its ID, its name, its agent, the other machine it runs on, when it runs on one read over SSH, its status, why it waits, the name of its folder and never the rest of the path, its branch or commit, its app, when its status began, and how long its agent has written nothing. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, also the number of its branch's pull request and whether its checks are failing, pending or passing, and never its title. Never what a waiting session is asking, or what a session last said, which are read from its transcript, and never the token counts of its newest reply.
|
|
626
642
|
- For each session that needs you: the same, with how long it has waited, and one sentence naming each of them and any agent that could not be read.
|
|
627
643
|
- For each source: its state, the sentence that says how it is read or what went wrong, which can name the folders Agent Lookout reads, such as `~/.claude/sessions`, and, for another machine, the SSH target you named, such as `dev@devbox.local`, and the last line ssh printed when it failed, and what its agent can report.
|
|
628
644
|
|
|
@@ -640,7 +656,7 @@ Nothing. Each answer is worked out from one reading of the list and is gone once
|
|
|
640
656
|
|
|
641
657
|
Agent Lookout keeps one thing about your sessions on disk: the history behind the Events log and the charts, so that they are still there after it restarts. [The history](#the-history) says what it holds, where it is, how long it is kept, how to clear it and how to turn it off. It also keeps its time rules and permission rules in a file, which holds nothing about your sessions: [The settings file](#the-settings-file). Apart from those, its own code writes no files, except the Mac app's, below.
|
|
642
658
|
|
|
643
|
-
The latest session list is held in memory only, and is gone when Agent Lookout stops. What a waiting session is asking is in that list only while it waits, and in no event or point of history, on disk or in memory. So is a permission request held for the dashboard, which is also kept in memory only while it is held. With `AGENT_LOOKOUT_EMAIL_ASKING=on`, `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, `AGENT_LOOKOUT_NTFY_ASKING=on` or `AGENT_LOOKOUT_PUSHOVER_ASKING=on`, it is also in the email, post or push for that wait from the moment it is written until it is handed over, and nowhere else. So are the tmux panes and terminal tabs it last found, the branches and repositories it last read, with pull requests on the pull requests `gh` last gave and where each branch's is looked for, and what the dashboard pages last said about notifications.
|
|
659
|
+
The latest session list is held in memory only, and is gone when Agent Lookout stops. The token counts of a session's newest reply are held in memory only, with the session, and are in no event or point of history. What a waiting session is asking is in that list only while it waits, and in no event or point of history, on disk or in memory. So is a permission request held for the dashboard, which is also kept in memory only while it is held. With `AGENT_LOOKOUT_EMAIL_ASKING=on`, `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, `AGENT_LOOKOUT_NTFY_ASKING=on` or `AGENT_LOOKOUT_PUSHOVER_ASKING=on`, it is also in the email, post or push for that wait from the moment it is written until it is handed over, and nowhere else. So are the tmux panes and terminal tabs it last found, the branches and repositories it last read, with pull requests on the pull requests `gh` last gave and where each branch's is looked for, and what the dashboard pages last said about notifications. What a Claude Code session last said is never in the session list: it is held in memory apart from it, for at most 15 seconds after it was last asked for, as [A session's last message](#a-sessions-last-message) says.
|
|
644
660
|
|
|
645
661
|
### The history
|
|
646
662
|
|
|
@@ -652,7 +668,7 @@ It writes there what it already holds in memory for the Events log and the chart
|
|
|
652
668
|
- Each history point, one for each poll: its time, and how many sessions were waiting on you, working, idle, and in all.
|
|
653
669
|
- When Agent Lookout started writing, and when the history was cleared.
|
|
654
670
|
|
|
655
|
-
Each event holds the session's name as the dashboard shows it: the name its agent gave it, from Claude Code's session file or the `claude` command's list, from Codex's `session_index.jsonl`, from the Antigravity CLI's title of the conversation or from a status file, or else the name of its folder, which for a Codex session is read from the start of its session file, and for an Antigravity CLI session from its program's log, or else, for an Antigravity CLI session, its conversation ID, the name of its folder in `brain/`. Nothing else from a transcript, from Codex's session files or from agy's transcripts and logs is written, and no folder path, branch, prompt or anything a waiting session is asking.
|
|
671
|
+
Each event holds the session's name as the dashboard shows it: the name its agent gave it, from Claude Code's session file or the `claude` command's list, from Codex's `session_index.jsonl`, from the Antigravity CLI's title of the conversation or from a status file, or else the name of its folder, which for a Codex session is read from the start of its session file, and for an Antigravity CLI session from its program's log, or else, for an Antigravity CLI session, its conversation ID, the name of its folder in `brain/`. Nothing else from a transcript, from Codex's session files or from agy's transcripts and logs is written, and no folder path, branch, prompt, token count or anything a waiting session is asking.
|
|
656
672
|
|
|
657
673
|
What it learns is written every 5 seconds, and once more as it stops. The files are JSON lines, one file for each day by the UTC clock, such as `v1-2026-10-06.jsonl`. A day that grows past 2 MB goes on in `v1-2026-10-06-2.jsonl`, and so on. Each day's file is deleted once that day ended more than 8 days ago, and the files hold 20 MB at most: before writing more than that, Agent Lookout deletes the oldest. When it starts, it reads the files back, and keeps in memory the last 1,000 events and the last six hours of points, as it does while it runs. For the Waits card it also keeps in memory, for 9 days, the events of sessions starting and stopping waiting on you, and the stretches of time it was measuring, which it works the card's totals out from. It reads only ordinary files in that folder of about 2 MB or less, does not follow a link, and passes over a line it cannot read. A file whose name says it was written by a later version of Agent Lookout, such as `v2-2026-10-06.jsonl`, is not read, written or deleted, for its age or by Clear history. A line dated more than a minute after the computer's clock was written while the clock was wrong, and is not read back.
|
|
658
674
|
|
|
@@ -680,11 +696,11 @@ The tools that run it write files of their own. None of these holds session data
|
|
|
680
696
|
|
|
681
697
|
The Mac app keeps the history in the same folder as `npx agent-lookout` and `npm start`, and four files of its own. `window-state.json`, in `~/Library/Application Support/Agent Lookout/`, holds the window's place and size and the colour of the theme it last showed. `update-state.json`, beside it, holds whether it checks for updates by itself, when it last checked, the last version it told you of and, during an install, the version being installed, as [Updates (Mac app only)](#updates-mac-app-only) says, and a newer version it downloads waits in the system's temporary folder until it is installed or the app quits. `menu-bar-state.json`, beside it, holds whether its icon is shown in the menu bar. Its log, `~/Library/Logs/Agent Lookout/main.log`, holds the errors it met and the lines `npm start` would print, such as the one that says an email setting is wrong. None of them holds a session's name, though an error in the log can name a file the app could not read. Electron, which the app is built on, keeps the page's local storage, below, and caches of its own in the first folder.
|
|
682
698
|
|
|
683
|
-
The dashboard saves seven values in your browser's local storage. Your theme choice is under the key `agent-lookout-theme`. Whether notifications are on is under the key `agent-lookout-notifications`, as `on` or `off`, and is written only when you turn them on or off. The events that send one are under the key `agent-lookout-notification-events`, as their names separated by commas, such as `needs-you,finished`, and are written only when you switch one. The apps the page has said macOS will ask about, when you first jumped to a tab of one, are under the key `agent-lookout-automation-note`, as `Terminal`, `iTerm2` or both separated by a comma. The last time the Events log was on screen is under the key `agent-lookout-last-looked`, as a number of milliseconds since 1970, and is written when the page goes out of sight or is closed. It is what the line in the Events log that marks where you left off is drawn from, and while that line still shows, the time kept is the line's own. It holds nothing about any session or event. Whether the Sessions card shows the list, the list grouped by repository or the board is under the key `agent-lookout-sessions-layout`, as `list`, `repositories` or `board`, and is written only when you choose one. The sessions left running that you hid until they change are under the key `agent-lookout-hidden-sessions`, as a list of each one's ID and the moment its idle began, at most 100, and are written only when you hide one. All seven belong to one browser at one address.
|
|
699
|
+
The dashboard saves seven values in your browser's local storage. Your theme choice is under the key `agent-lookout-theme`. Whether notifications are on is under the key `agent-lookout-notifications`, as `on` or `off`, and is written only when you turn them on or off, or press Not now in the Mac app's question on the Overview. The events that send one are under the key `agent-lookout-notification-events`, as their names separated by commas, such as `needs-you,finished`, and are written only when you switch one. The apps the page has said macOS will ask about, when you first jumped to a tab of one, are under the key `agent-lookout-automation-note`, as `Terminal`, `iTerm2` or both separated by a comma. The last time the Events log was on screen is under the key `agent-lookout-last-looked`, as a number of milliseconds since 1970, and is written when the page goes out of sight or is closed. It is what the line in the Events log that marks where you left off is drawn from, and while that line still shows, the time kept is the line's own. It holds nothing about any session or event. Whether the Sessions card shows the list, the list grouped by repository or the board is under the key `agent-lookout-sessions-layout`, as `list`, `repositories` or `board`, and is written only when you choose one. The sessions left running that you hid until they change are under the key `agent-lookout-hidden-sessions`, as a list of each one's ID and the moment its idle began, at most 100, and are written only when you hide one. All seven belong to one browser at one address.
|
|
684
700
|
|
|
685
701
|
## What is on screen
|
|
686
702
|
|
|
687
|
-
Session names, folder paths, repository names, branch names, pull request titles and what a waiting session is asking, such as a command or a file, can show what you are working on. Check a screenshot before you share it.
|
|
703
|
+
Session names, folder paths, repository names, branch names, pull request titles and what a waiting session is asking, such as a command or a file, can show what you are working on. So can what a Claude Code session last said, which its details show, and which can quote code, paths and anything else the session wrote. Check a screenshot before you share it.
|
|
688
704
|
|
|
689
705
|
Resume puts a session's folder path and its ID on the clipboard, and only when you press it. From there, anything that reads your clipboard can see them, such as a clipboard manager, and on a Mac with Handoff on, Universal Clipboard makes them available on your other Apple devices. The page never reads the clipboard.
|
|
690
706
|
|
package/README.md
CHANGED
|
@@ -106,12 +106,12 @@ Claude Code, Codex and the Antigravity CLI need no setup. Any other agent writes
|
|
|
106
106
|
|
|
107
107
|
<!-- Checked against the adapters by tests/integration/collector/adapters/adapter.test.ts -->
|
|
108
108
|
|
|
109
|
-
| Agent | Needs you | Jump | Quiet for | Stop | Answer |
|
|
110
|
-
| --------------- | --------- | ------ | --------- | ------ | ------ |
|
|
111
|
-
| Claude Code | Yes | Partly | No | Partly | Partly |
|
|
112
|
-
| Codex | No | No | Yes | No | No |
|
|
113
|
-
| Antigravity CLI | Partly | No | Yes | No | No |
|
|
114
|
-
| Status files | Partly | No | Partly | No | No |
|
|
109
|
+
| Agent | Needs you | Jump | Quiet for | Tokens | Stop | Answer |
|
|
110
|
+
| --------------- | --------- | ------ | --------- | ------ | ------ | ------ |
|
|
111
|
+
| Claude Code | Yes | Partly | No | No | Partly | Partly |
|
|
112
|
+
| Codex | No | No | Yes | Yes | No | No |
|
|
113
|
+
| Antigravity CLI | Partly | No | Yes | No | No | No |
|
|
114
|
+
| Status files | Partly | No | Partly | No | No | No |
|
|
115
115
|
|
|
116
116
|
A Codex session waiting for approval shows as working, and so does an Antigravity CLI session waiting for an answer to a question. After 5 minutes its row says how long it has been quiet. An Antigravity CLI session needs you while agy asks you to approve a tool. Antigravity CLI support is new: it is built from what agy 1.3.1 documents and its program holds, and checked against one conversation. [What each agent can report](docs/GUIDE.md#what-each-agent-can-report) gives the reason for every Partly and No.
|
|
117
117
|
|
|
@@ -231,6 +231,7 @@ Yes. The app opens no port, so `status` and `mcp` need the npx copy. Turn notifi
|
|
|
231
231
|
| See every file it reads and all it can send | [PRIVACY.md](PRIVACY.md) |
|
|
232
232
|
| Know what it guards, or report a vulnerability | [SECURITY.md](SECURITY.md) |
|
|
233
233
|
| Call its local HTTP API | [docs/API.md](docs/API.md) |
|
|
234
|
+
| Learn what each part of the code does, and why | [docs/TOUR.md](docs/TOUR.md) |
|
|
234
235
|
| See how the parts fit together | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
|
|
235
236
|
| Write tests | [tests/README.md](tests/README.md) |
|
|
236
237
|
| Install or remove the Claude Code plugin | [plugins/agent-lookout/README.md](plugins/agent-lookout/README.md) |
|
package/SECURITY.md
CHANGED
|
@@ -12,7 +12,7 @@ The project has one maintainer, so replies are best effort. Fixes land on `main`
|
|
|
12
12
|
|
|
13
13
|
## What the app handles
|
|
14
14
|
|
|
15
|
-
Agent Lookout reads metadata about the Claude Code, Codex and Antigravity CLI sessions on your machine, and, once you name them, on other machines of yours that run Agent Lookout, read over SSH: names, working directories, statuses and, for Claude Code, process IDs. For each session's folder it reads which git branch is checked out, and which repository the folder belongs to, from the repository's `.git` entry, `HEAD` and a worktree's `commondir` alone, and runs no git command. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, and only then, it also reads that repository's `config` and its remote's `refs/remotes/<remote>/HEAD`, and runs the person's own `gh`, directly and never through a shell, as `gh pr view --repo github.com/<owner>/<name> --json number,title,state,isDraft,statusCheckRollup -- <branch>`, which sends GitHub the repository's owner and name and the branch's name: [PRIVACY.md](PRIVACY.md#gh). On a Mac it also lists every process's ID, parent, terminal and program name with `ps` when a new Claude Code session appears, to find the Terminal or iTerm2 tab it runs in, and keeps only the app and the session's terminal. It serves that to the dashboard over HTTP on a loopback address. Of Claude Code's transcripts it reads only the end of a
|
|
15
|
+
Agent Lookout reads metadata about the Claude Code, Codex and Antigravity CLI sessions on your machine, and, once you name them, on other machines of yours that run Agent Lookout, read over SSH: names, working directories, statuses and, for Claude Code, process IDs. For each session's folder it reads which git branch is checked out, and which repository the folder belongs to, from the repository's `.git` entry, `HEAD` and a worktree's `commondir` alone, and runs no git command. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, and only then, it also reads that repository's `config` and its remote's `refs/remotes/<remote>/HEAD`, and runs the person's own `gh`, directly and never through a shell, as `gh pr view --repo github.com/<owner>/<name> --json number,title,state,isDraft,statusCheckRollup -- <branch>`, which sends GitHub the repository's owner and name and the branch's name: [PRIVACY.md](PRIVACY.md#gh). On a Mac it also lists every process's ID, parent, terminal and program name with `ps` when a new Claude Code session appears, to find the Terminal or iTerm2 tab it runs in, and keeps only the app and the session's terminal. It serves that to the dashboard over HTTP on a loopback address. Of Claude Code's transcripts it reads only the end of a session's own, opened without following a link, while the session waits or when `GET /api/sessions/last-message` asks what it last said. Of a waiting session's it keeps one line, what the session is asking, until the wait ends. That line goes to the dashboard and to notifications on this machine, never to an email, a webhook post or `agent-lookout mcp`. Of a last message it keeps up to the last 2,000 characters of the session's newest reply in memory, for at most 15 seconds after the last ask, and gives them only in that route's answer. Any program on this computer, and on a shared Mac another user account, can ask for them, as below. `AGENT_LOOKOUT_LAST_MESSAGE=off` stops it, and the Mac app opens no port. It does open Codex's session files, which hold whole conversations, and keeps only when turns started and ended and a few fields. It writes to no agent's files. It ends a Claude Code session's process only when the person presses Stop and confirms, and answers a Claude Code permission prompt only when the person presses Allow or Deny, or when a permission rule the person added in Settings decides it, by the routes described below, and never on its own. It also reads the folder of status files, `~/.agent-lookout/sessions` or the one `AGENT_LOOKOUT_STATUS_DIR` names, where any program can write a small JSON file to show one of its sessions, and it writes nothing there. It keeps the Events log and the counts behind its charts in its own folder, `~/.agent-lookout/history`, which it makes with mode 700, in files of mode 600 that it neither reads nor writes through a link. They hold session IDs and names, statuses and times, and never what a waiting session is asking. `AGENT_LOOKOUT_HISTORY=off` keeps them in memory only. Beside them it keeps the time rules and the permission rules set in Settings in `~/.agent-lookout/settings.json`, or the file `AGENT_LOOKOUT_SETTINGS_FILE` names, of mode 600, which holds nothing about a session and which it neither reads nor writes through a link. [PRIVACY.md](PRIVACY.md) lists everything it reads, runs and keeps.
|
|
16
16
|
|
|
17
17
|
With notifications turned on in Settings, the dashboard page hands a session's name and a fixed text, the reason it waits or Finished, Failed or Ended, to the browser's Notifications API, which passes them to the operating system. When no dashboard page is open, the local server shows the notification itself on macOS: it runs `/usr/bin/osascript` with a fixed script, and passes the name and the text to that script as arguments. Each dashboard page tells the server whether its notifications are on, in a header on its requests, and the server shows its own only while the last page to say so said they are on, or `AGENT_LOOKOUT_NOTIFICATIONS=on` was set and no page has said anything. Nothing is sent over the network for any of this. [PRIVACY.md](PRIVACY.md#notifications) has the details.
|
|
18
18
|
|
|
@@ -24,7 +24,7 @@ Pushes to a phone are off unless they are set up in the environment Agent Lookou
|
|
|
24
24
|
|
|
25
25
|
Other machines are read only when `AGENT_LOOKOUT_REMOTES` names them, each as a name and a target. A name is letters, digits and dashes, and a target is a host alias, a host name or `user@host` made of letters, digits, dots, dashes and underscores, starting with a letter, a digit or an underscore, so no space, leading dash, option such as `-oProxyCommand=`, or shell character gets in. One entry that is not, and no machine is read. For each, the collector runs the person's own `ssh`, found on `PATH`, in a fixed place or by `AGENT_LOOKOUT_SSH_BIN`, by its full path, never through a shell, with nothing on its input, as `ssh -N -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 -o ControlMaster=no -o ControlPath=none -L 127.0.0.1:<free port>:127.0.0.1:<port there> -- <target>`. The target comes after `--`, as one argument. ssh runs no command on the other machine and asks for nothing, so Agent Lookout never handles a key or a password. `ControlMaster=no` and `ControlPath=none`, given on the command line, come before the person's own ssh config, so each ssh is a connection of its own: it never joins an ssh the person has open, whose forward would outlive Agent Lookout, and never leaves one in the background through `ControlPersist`. When Agent Lookout stops, it sends each ssh SIGTERM, and SIGKILL to one that has not ended two seconds later. Through the tunnel the collector sends `GET /api/health` and `GET /api/sessions` to the Agent Lookout there and nothing else. The requests arrive on that machine's own loopback, so its `Host` and `Origin` checks pass as they do for its own page, and they carry no `Origin` and no notifications header. The page on this computer never talks to the other machine, only to its own server. The answer is read as untrusted: every field is checked and cleaned again, sessions there are given ids of their own, `remote:<name>:` and the id there, and their process ID, Jump, Stop, held permission request and links are dropped, so nothing in an answer can make this computer select a pane, bring a tab forward, open a link, stop a process or answer a permission prompt. [PRIVACY.md](PRIVACY.md#another-machine-over-ssh) says what is kept.
|
|
26
26
|
|
|
27
|
-
While a tunnel is up, its end on this computer's `127.0.0.1` is open to every program and every user account on this computer, not only to Agent Lookout, and it leads to the other machine's Agent Lookout and its whole API, not only the two routes the collector asks for. Through it, a program here can read that machine's sessions and send the routes that change something there: `POST /api/jump`, `POST /api/history/clear`, `POST /api/sessions/stop`, `POST /api/sessions/clean-up`, `POST /api/permission/answer`, `POST /api/settings/time-rules`, `POST /api/settings/permission-rules` and `POST /api/phone/test`. Their `Origin` and action-header checks stop a website, but not a program that is not a browser, which can set those headers. Before a machine is named, only someone on that machine could do this. There is also a moment, before ssh has signed in and listens on the port it was given, when that port is free: another program here could listen on it first and answer as that machine's Agent Lookout, and its sessions would be shown as that machine's, with no Jump, no Stop and no Allow or Deny. So name a machine only from a computer whose other users and programs you trust as you trust those on that machine.
|
|
27
|
+
While a tunnel is up, its end on this computer's `127.0.0.1` is open to every program and every user account on this computer, not only to Agent Lookout, and it leads to the other machine's Agent Lookout and its whole API, not only the two routes the collector asks for. Through it, a program here can read that machine's sessions, ask what each of its Claude Code sessions last said with `GET /api/sessions/last-message`, and send the routes that change something there: `POST /api/jump`, `POST /api/history/clear`, `POST /api/sessions/stop`, `POST /api/sessions/clean-up`, `POST /api/permission/answer`, `POST /api/settings/time-rules`, `POST /api/settings/permission-rules` and `POST /api/phone/test`. Their `Origin` and action-header checks stop a website, but not a program that is not a browser, which can set those headers. Before a machine is named, only someone on that machine could do this. There is also a moment, before ssh has signed in and listens on the port it was given, when that port is free: another program here could listen on it first and answer as that machine's Agent Lookout, and its sessions would be shown as that machine's, with no Jump, no Stop and no Allow or Deny. So name a machine only from a computer whose other users and programs you trust as you trust those on that machine.
|
|
28
28
|
|
|
29
29
|
Eight routes of the local API change something: `POST /api/jump`, which the Jump button of a session in tmux, Terminal or iTerm2 sends, `POST /api/history/clear`, which the Clear history button in Settings sends, `POST /api/sessions/stop` and `POST /api/sessions/clean-up`, which Stop session and the ending of the sessions left running send, `POST /api/permission/answer`, which Allow and Deny send, `POST /api/settings/time-rules`, which the Time rules card in Settings sends, `POST /api/settings/permission-rules`, which the Permission rules card in Settings sends, and `POST /api/phone/test`, which Send a test on the ntfy and Pushover cards sends. Every other route only reads. `POST /api/phone/test` sends one test push through the channel it names, which says only that it is a test and holds nothing of any session. It makes the same checks as the jump route, below, with `X-Agent-Lookout-Action: phone-test`, and takes a body of 64 bytes or less that is exactly `{"channel": "ntfy"}` or `{"channel": "pushover"}`. Nothing in it reaches the push or says where it goes: that is the channel's own setting, and a channel that is not set up is answered 409 and sent nothing. It sends one a second, one at a time, and counts toward the channel's 20 an hour, so a program on this computer that sends it again and again can spend that hour's pushes, and no more. The two that stop sessions, and the one that answers, are described after the jump route, below. `POST /api/history/clear` deletes the history files in Agent Lookout's own folder and empties the Events log and the history in memory. Its body is exactly `{}`, nothing in it names a file, and it makes the same checks as the jump route, below, with `X-Agent-Lookout-Action: clear-history`. `POST /api/settings/time-rules` puts the time rules in force and writes them to the settings file. It makes the same checks as the jump route, with `X-Agent-Lookout-Action: time-rules`, and takes a body of 2,048 bytes or less that is exactly the three rules, each with exactly its own fields and every value in range, and nothing else. Nothing in it names a file: the file is the one Agent Lookout was started with, written whole to a new file of mode 600 that then takes its place, never through a link. The rules decide only when Agent Lookout tells you of something, and which idle sessions count as stale, and so which are offered under Left running: ending one still takes a press of End and every check below. `POST /api/settings/permission-rules`, described with the route that answers, below, changes the rules that answer a permission prompt without a press. The rest of this part is about `POST /api/jump`. For a session in tmux it can change three things, all inside tmux: which pane is selected in its window, which window is selected in its tmux session, and which tmux session each attached terminal is showing. For a session in a tab of Terminal or iTerm2, on macOS, it can bring that tab forward: select it in its window, bring the window back from the Dock if it was minimised and in front of the app's others, and bring the app to the front. It sends no keys, runs nothing inside a pane or a tab, starts no tmux server and starts no app.
|
|
30
30
|
|
|
@@ -59,7 +59,7 @@ The Mac app answers a held request from its own notification of a wait, with Den
|
|
|
59
59
|
|
|
60
60
|
The permission rules answer a held request without a press. There are none until the person adds one, through `POST /api/settings/permission-rules`, which makes the checks above with `X-Agent-Lookout-Action: permission-rules` and takes a body of 1,024 bytes or less that is exactly one change: add, edit, move or remove one rule, named by its ID. The collector checks every rule itself, whatever the page checked: a tool's name of letters, digits, `_` and `-`, or `*`, a command for Bash alone, plain ASCII without `;`, `&`, `|`, `<`, `>`, parentheses, quotes, backslashes, `$`, braces or a wildcard but a final `:*`, and an allow rule that names one tool, never every tool of an MCP server, for Bash a command that does not begin with a program that runs another, such as `sudo`, `env`, `xargs` or a shell, or with one that sends requests or runs the code it is given, such as `curl`, `wget`, `nc`, `ssh`, `node`, `python` or `awk`, whatever its capitals or folder, with `.exe` or `.com` after it, or anything after its name that does not begin with a letter, such as a version, that names its subcommand where the program is a package manager or a build tool, such as `npm test` and never `npm` alone, that names no subcommand that fetches or runs code, such as `npm exec` or `docker run`, and no option through which the program runs another, such as `git -c`, and that does not name Agent Lookout's own folder, and never an edit, a plan or a question. A list in the file that holds a rule these checks refuse, such as one saved before a check was added, is no rule at all. It keeps 100 rules at most, in the settings file, written as the time rules are. A held request is put to the rules when the registry file first confirms its wait, and a deny or an allow rule answers it through `answer`, the same function a press calls, so it makes every check above: the same wait, Allow only for a request that offers Allow, and the same two fixed texts. Deny goes first, then ask, then allow. An allow rule also needs, for Bash, one plain command whose words are the rule's, word by word: no second command, substitution, variable, redirection, subshell, glob, quote, backslash, `~`, tab, space at either end, variable set before it, or letter outside ASCII, and no input but `timeout` and `run_in_background`, no subcommand that fetches or runs code before the arguments begin, such as `npm -- exec`, no option through which the program runs another, such as `go test -exec`, no word with `.agent-lookout` in it, no path, read also from the session's folder and made plain of `..`, `//` and `/./`, to the settings file, the socket or a folder above them up to `/`, and no path under `/.vol`. A session whose folder is the home folder or above gets no allow answer at all. Anything else is left to the person. A deny or an ask rule matches more loosely, since it only holds a request back: in any command of the line, the program by its name whatever its capitals or folder, its other words in order with any words between them, and from every word after a program that runs another. Each rule's answer is an event, with the tool and the rule's words, and never the command. A deny or an allow rule in Claude Code's own settings is applied before Claude Code runs the hook, so the hook, and these rules, never see a request those settle.
|
|
61
61
|
|
|
62
|
-
The Mac app answers
|
|
62
|
+
The Mac app answers eight more routes, under `/api/app/` on its own address, which no port reaches and which `npm start` and the npm package do not have: `GET /api/app/update`, `GET /api/app/menu-bar` and `GET /api/app/notifications`, and five POSTs that check GitHub for a newer version, install the one that is ready, turn the daily check on or off, show or hide the menu bar item (`POST /api/app/menu-bar/setting`, with `X-Agent-Lookout-Action: menu-bar-setting` and a body of exactly `{"show": true}` or `{"show": false}`), and show one test notification, which says only that it is a test (`POST /api/app/notifications/test`, with `X-Agent-Lookout-Action: notification-test` and a body of exactly `{}`). Each POST passes the checks above, with an `X-Agent-Lookout-Action` of its own, and a body of 256 bytes or less that is exactly what it needs. The app's only requests off the machine are the update check's and its download's, over HTTPS, with the certificate checked, to GitHub and the two hosts GitHub redirects a release's downloads to, and to no other address, a redirect's included. A download must have the size and SHA-512 the release's `latest-mac.yml` gives, and the bundle in it must be this app, by its bundle identifier, at the version offered, with no link where the bundle, its `Contents` or its `Info.plist` should be. Only Install and Restart starts the helper that replaces the app: `/bin/sh` with a fixed script, and the app's process ID and three paths as its arguments, two of them in the app's own temporary folder. Nothing from the network or from a request reaches its arguments or any command. The app is not signed with a Developer ID yet, so a download is vouched for by the release it comes from and the hash in that release, and is as safe as the project's GitHub releases are.
|
|
63
63
|
|
|
64
64
|
`agent-lookout mcp` is a Model Context Protocol server for an agent that keeps track of your other agents. The agent's app starts it and speaks to it over stdin and stdout, so it opens no port. For each tool call it reads `GET /api/sessions` from the local server, at a loopback address only, with no `Origin` and no notifications header, exactly as `agent-lookout status` does, and answers the app with what it read. Its three tools only read: none of them reaches `POST /api/jump`, `POST /api/history/clear`, `POST /api/sessions/stop`, `POST /api/sessions/clean-up`, `POST /api/permission/answer`, `POST /api/settings/time-rules`, `POST /api/settings/permission-rules` or `POST /api/phone/test`, changes a notification or changes anything else. A session's name, agent, folder and branch are text that other programs wrote, and the client is a model that takes instructions from text. So each tool's description and each answer say that this text is data, never instructions, the one-sentence summary puts each name in quotation marks, and escape sequences and control characters are taken out of every text, as `agent-lookout status` does. So are characters that show nothing, such as zero-width spaces and Unicode's tag characters, in which words can be hidden from a person but not from a model.
|
|
65
65
|
|
|
@@ -88,15 +88,20 @@ Session names and folder paths can be sensitive. The main risk is that something
|
|
|
88
88
|
- A post sent over plain HTTP to an address that is not on this machine, or to a server whose certificate was not checked.
|
|
89
89
|
- What a waiting session is asking reaching an email while `AGENT_LOOKOUT_EMAIL_ASKING` is not `on`, a post while `AGENT_LOOKOUT_WEBHOOK_ASKING` is not `on`, an email's headers, or the email or post for anything but a wait.
|
|
90
90
|
- What a waiting session is asking being written to a file, an event or the history, or reaching `GET /api/email`, `GET /api/webhook`, `GET /api/ntfy` or `GET /api/pushover`.
|
|
91
|
+
- What a session last said reaching anything but the answer of `GET /api/sessions/last-message`: the session list, an event, the history, a file, a log, a notification, an email, a post, a push, `agent-lookout mcp` or a request to another machine. Also its being kept in memory more than 15 seconds after it was last asked for, or after a poll of Claude Code that does not list its session.
|
|
92
|
+
- `GET /api/sessions/last-message` opening a file a request names, or any file but the transcript of a Claude Code session the collector lists, reading more than its last 256 KB, following a link or opening a pipe, answering a request a browser marks as anything but `same-origin`, or opening a transcript while `AGENT_LOOKOUT_LAST_MESSAGE` or `AGENT_LOOKOUT_WAITING_TEXT` is `off`.
|
|
93
|
+
- What a session last said running as script or markup in the dashboard, or becoming a link.
|
|
91
94
|
- A push sent, or a connection opened for one, while `AGENT_LOOKOUT_NTFY_URL` is unset, while either `AGENT_LOOKOUT_PUSHOVER_TOKEN` or `AGENT_LOOKOUT_PUSHOVER_USER` is unset, or while one of that channel's settings cannot be read.
|
|
92
95
|
- The ntfy topic, its token, Pushover's token or the user key reaching the dashboard, the API, an event, the history, the snapshot, a log, the console, a sentence Agent Lookout makes or a file, or going in an address rather than a header or a body.
|
|
93
96
|
- A push that holds more than PRIVACY.md lists, that goes to any address but the ntfy server named or `https://api.pushover.net/1/messages.json`, including by following a redirect, or that is sent over plain HTTP to an ntfy server that is not on this machine, or to a server whose certificate was not checked.
|
|
94
97
|
- What a waiting session is asking reaching a push while that channel's ASKING setting is not `on`, a push's title, or the push for anything but a wait and its reminders.
|
|
95
98
|
- A session's name, folder or anything else from a session that changes the structure of a push, adds a field or a header to it, or changes where it goes.
|
|
99
|
+
- Any `AGENT_LOOKOUT_` setting, such as the mail server's password, the webhook's address or a push token, reaching the environment of a program Agent Lookout starts.
|
|
96
100
|
- A test push that holds anything of a session, that goes while the hourly limit is full, or that a website, or a page served from anywhere but this machine, can make the app send.
|
|
97
101
|
- Any write to `~/.claude` or to another tool's files, and any read of the `.key` files in `~/.claude/sessions/`.
|
|
98
102
|
- Any write under the Codex folder (`~/.codex`, or the folder `CODEX_HOME` or `AGENT_LOOKOUT_CODEX_HOME` names), any open of a file in its `thread-writer-locks/`, and any read of a Codex file that PRIVACY.md does not list.
|
|
99
103
|
- Text from a Codex session file, such as a prompt, a reply or a command's output, reaching the API or the dashboard.
|
|
104
|
+
- Anything from a Codex token count line but the newest reply's three counts, such as its running totals, its rate limits, a plan or credits, reaching the API or the dashboard, and those counts reaching an event, the history, an email, a webhook post, a push, an answer of `agent-lookout mcp` or `agent-lookout status`.
|
|
100
105
|
- Any write under `~/.gemini` or the folder `AGENT_LOOKOUT_ANTIGRAVITY_HOME` names, any open of an agy database or of a file in that folder that PRIVACY.md does not list, and text from an agy transcript, an agy program's command line or its log, such as a prompt, a reply, a command's output or an address agy contacts, reaching the API or the dashboard. Only a conversation's title, its folder and the name of a tool agy asks to run may.
|
|
101
106
|
- A session name, path or other session field that runs as script or markup in the dashboard.
|
|
102
107
|
- Anything from a status file that runs as script or markup in the dashboard, or in a notification, or that is used as a link, a Jump, a command or a path to open.
|
|
@@ -116,11 +121,11 @@ Session names and folder paths can be sensitive. The main risk is that something
|
|
|
116
121
|
- A request to `POST /api/jump` that makes `osascript` run any script but the fixed one for Terminal or iTerm2, or with any argument but a terminal found for a listed session, or that makes Terminal or iTerm2 do anything but bring a tab forward: running a command, sending keys or text, or reading what a tab shows.
|
|
117
122
|
- A session name, or anything else from a session, that `osascript` reads as AppleScript or as one of its options instead of showing it as text.
|
|
118
123
|
- A Jump link that opens anything other than the intended `vscode://` address.
|
|
119
|
-
- A notification, or the browser's question about allowing them, appearing when you have not turned notifications on, in Settings or with `AGENT_LOOKOUT_NOTIFICATIONS=on`.
|
|
120
|
-
- A notification that holds anything but the session's name and the reason, or Finished, Failed or Ended, or for a reminder how long the session has waited and for what, or for the summary of quiet hours the names of the sessions and what happened to them, or that makes the browser fetch anything.
|
|
124
|
+
- A notification, or the browser's question about allowing them, appearing when you have not turned notifications on, in Settings, in the Mac app with Turn on in the Overview's question, or with `AGENT_LOOKOUT_NOTIFICATIONS=on`. The Mac app's test notification, shown when you press Send a test or turn notifications on in the app, is not one of these.
|
|
125
|
+
- A notification that holds anything but the session's name and the reason, or Finished, Failed or Ended, or for a reminder how long the session has waited and for what, or for the summary of quiet hours the names of the sessions and what happened to them, or for the Mac app's test notification anything but its fixed words, which say it is a test, or that makes the browser fetch anything.
|
|
121
126
|
- A request to `POST /api/settings/time-rules` that writes anything but the three rules to the settings file, writes to any other file, writes through a link, or changes anything else in Agent Lookout. A settings file that is read through a link, or that makes Agent Lookout read more than 64 KB, send anything anywhere, or run anything. Also a website, or a page served from anywhere but this machine, changing the time rules.
|
|
122
127
|
- A permission rule that lets through more than it says: an allow rule that answers a request its words do not name, a command line with more than one plain command, a request that offers Deny alone, a tool other than its own, or a request in another wait, an allow rule that answers one plain command whose program and words, in order, an ask or a deny rule names, or any rule that answers while `AGENT_LOOKOUT_ANSWER` is off. Also a rule's answer that writes anything but the two fixed answers, an event, history or settings answer that holds a request's command or input, a rule the collector takes that its own checks refuse, and a website, or a page served from anywhere but this machine, changing the rules.
|
|
123
|
-
- Quiet hours that let a notification, an email, a post or a push through while they hold, or a summary that holds more than PRIVACY.md lists. A test push, which the person asks for, is not held.
|
|
128
|
+
- Quiet hours that let a notification, an email, a post or a push through while they hold, or a summary that holds more than PRIVACY.md lists. A test push, or the Mac app's test notification, which the person asks for, is not held.
|
|
124
129
|
- A page at another address reading or steering the dashboard's notifications, or turning the server's own on or off.
|
|
125
130
|
- A tool of `agent-lookout mcp` that acts: that reaches `POST /api/jump`, a route that stops a session, the route that answers one or a route that changes a setting, sends the notifications header, or changes anything in Agent Lookout, in another tool or on this computer.
|
|
126
131
|
- `agent-lookout mcp` reading anything but `GET /api/sessions` from Agent Lookout at a loopback address, asking an address that is not loopback, opening a port, or making any other connection.
|
|
@@ -129,7 +134,7 @@ Session names and folder paths can be sensitive. The main risk is that something
|
|
|
129
134
|
|
|
130
135
|
## Out of scope
|
|
131
136
|
|
|
132
|
-
- Other programs on the same machine reading the local API, sending it the header that turns the server's notifications on or off, changing the time rules or the permission rules, asking it to select a tmux pane or bring a tab forward, asking it to stop a Claude Code session, with every check it makes for the Stop button, or asking it to answer a permission prompt it holds, with every check it makes for Allow and Deny. The API has no authentication, so any local process can request it, and on a shared computer that includes other user accounts. This is a known limit of this version. A program running as you could end your processes itself, but on a shared computer another account can also ask this server to stop your sessions, allow a permission prompt it holds, or add a permission rule that allows one, which it could not do itself. On such a computer, use the Mac app, which opens no port, so only its own window can ask, or set `AGENT_LOOKOUT_STOP=off`, which takes stopping away,
|
|
137
|
+
- Other programs on the same machine reading the local API, which includes up to 2,000 characters of what a listed Claude Code session last said, sending it the header that turns the server's notifications on or off, changing the time rules or the permission rules, asking it to select a tmux pane or bring a tab forward, asking it to stop a Claude Code session, with every check it makes for the Stop button, or asking it to answer a permission prompt it holds, with every check it makes for Allow and Deny. The API has no authentication, so any local process can request it, and on a shared computer that includes other user accounts. This is a known limit of this version. A program running as you could end your processes itself, but on a shared computer another account can also ask this server to stop your sessions, allow a permission prompt it holds, or add a permission rule that allows one, which it could not do itself. On such a computer, use the Mac app, which opens no port, so only its own window can ask, or set `AGENT_LOOKOUT_STOP=off`, which takes stopping away, `AGENT_LOOKOUT_ANSWER=off`, which takes answering away, and `AGENT_LOOKOUT_LAST_MESSAGE=off`, which takes last messages away. A Claude Code session is such a program too. No allow rule may begin with a program that sends requests or runs the code it is given, such as `curl`, `nc`, `node` or `python`, or answer a subcommand on its list that fetches or runs code, such as `npm exec`, an option through which a program runs another, such as `git -c`, or a command that names Agent Lookout's own folder, its settings file or socket, or a folder above them. A command an allow rule still lets it run without asking can send these requests, and add a rule: one that runs a file it can edit, such as `npm test`, `make` or a project's own script, one that installs packages, such as `npm install`, whose install scripts run, one that sets a program for another to run later, such as `git config` under `git:*`, a program not on those lists that sends requests, and one that writes the settings file by a path its words do not show, such as an archive or a patch holding full paths. Claude Code's sandbox is the fence for those, and an allow rule never lets a command leave it. GUIDE.md's Permission rules section says so.
|
|
133
138
|
- A program on this machine that names itself after Terminal or iTerm2 in the list of processes, and so gives a session under it a Jump button. Pressing it asks the real app for a tab on that session's terminal, which it does not have.
|
|
134
139
|
- A notification the server showed staying in Notification Centre after its session has moved on. The server cannot take one down, and PRIVACY.md says so.
|
|
135
140
|
- The dev server started with Vite's `--host` flag, which makes it listen on the network. `npm start` refuses to listen on anything but loopback.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as y,j as t}from"./react-C2DiJaR6.js";import{b as G,t as K,r as q,f as U,a as C,c as X,d as V,h as _,m as J,H as Q,e as Z,D as ee,g as O,s as te,i as se,j as ne,S as oe,L as ae,C as re,k as ie,l as le,n as de}from"./index-
|
|
1
|
+
import{r as y,j as t}from"./react-C2DiJaR6.js";import{b as G,t as K,r as q,f as U,a as C,c as X,d as V,h as _,m as J,H as Q,e as Z,D as ee,g as O,s as te,i as se,j as ne,S as oe,L as ae,C as re,k as ie,l as le,n as de}from"./index-dMRHt2Y8.js";import"./motion-DlYOyClL.js";import"./radix-BH0JH8UU.js";const ce={"needs-you":"text-status-needs-you-edge",working:"text-status-working",idle:"text-status-idle"},I={top:12,right:12,bottom:24,left:30},L=220,ue=76,he=104,R=1/60;function me({samples:s,start:n,end:a,tone:g,label:w,describe:l,className:c}){const p=y.useRef(null),[u,k]=y.useState(0),[h,i]=y.useState(null);y.useEffect(()=>{const e=p.current;if(!e)return;const o=new ResizeObserver(([j])=>{j&&k(Math.floor(j.contentRect.width))});return o.observe(e),()=>o.disconnect()},[]);const r=I.left,m=Math.max(r+1,u-I.right),x=I.top,d=L-I.bottom,N=a-n;let T=0;for(const e of s)e.at>=n&&e.at<=a&&e.value>T&&(T=e.value);const A=V(T),b=G(s,{start:n,end:a},{left:r,right:m,top:x,bottom:d,ceiling:A.ceiling}),W=e=>r+(e-n)/N*(m-r),S=e=>d-e/A.ceiling*(d-x),B=K(n,a,Math.max(1,Math.floor((m-r)/ue))),M=h??1,H=n+M*N,E=q(b.runs,H),$=E?l(E.value):"Not measured",v=W(H),z=(e,o)=>{const j=o.left+r,f=m-r;i(Math.min(1,Math.max(0,(e-j)/f)))},Y=e=>{const j={ArrowLeft:f=>f-R,ArrowRight:f=>f+R,PageDown:f=>f-R*10,PageUp:f=>f+R*10,Home:()=>0,End:()=>1}[e.key];j&&(e.preventDefault(),i(Math.min(1,Math.max(0,j(h??1)))))};return t.jsxs("div",{ref:p,"data-slot":"history-chart",role:"slider",tabIndex:0,"aria-label":`${w}. Arrow keys read the count at each moment.`,"aria-orientation":"horizontal","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(M*100),"aria-valuetext":`${C(H)}: ${$}`,onPointerMove:e=>z(e.clientX,e.currentTarget.getBoundingClientRect()),onPointerLeave:()=>i(null),onFocus:e=>{e.currentTarget.matches(":focus-visible")&&i(o=>o??1)},onBlur:()=>i(null),onKeyDown:Y,className:X("relative w-full cursor-crosshair rounded-row",ce[g],c),style:{height:L},children:[u>0&&b.unmeasured.map((e,o)=>t.jsx("div",{"aria-hidden":!0,"data-part":"unmeasured",className:"unmeasured-hatch absolute",style:{left:e.x,top:x,width:e.width,height:d-x}},o)),u>0&&t.jsxs("svg",{"aria-hidden":!0,width:u,height:L,className:"relative block",children:[A.ticks.map(e=>t.jsxs("g",{children:[t.jsx("line",{"data-part":"grid",x1:r,x2:m,y1:S(e),y2:S(e),stroke:e===0?"var(--rule-strong)":"var(--hairline)",strokeWidth:1,shapeRendering:"crispEdges"}),t.jsx("text",{x:r-8,y:S(e),dy:"0.32em",textAnchor:"end",className:"fill-ink-muted font-mono text-micro",children:e})]},e)),b.unmeasured.map((e,o)=>e.width>=he&&t.jsx("text",{"data-part":"unmeasured-label",x:e.x+8,y:x+14,className:"fill-ink-muted font-mono text-micro",stroke:"var(--glass-float)",strokeWidth:4,strokeLinejoin:"round",paintOrder:"stroke",children:"Not measured"},o)),b.areas.map((e,o)=>t.jsx("path",{d:e,fill:"currentColor",stroke:"none",opacity:.1},o)),b.lines.map((e,o)=>t.jsx("path",{"data-part":"line",d:e,fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},o)),B.map(e=>{const o=W(e);return o<r+16||o>m-16?null:t.jsx("text",{"data-part":"time-tick",x:o,y:L-6,textAnchor:"middle",className:"fill-ink-muted font-mono text-micro",children:U(e)},e)}),h!==null&&t.jsxs("g",{"data-part":"crosshair",children:[t.jsx("line",{x1:v,x2:v,y1:x,y2:d,stroke:"var(--ink-muted)",strokeWidth:1,shapeRendering:"crispEdges"}),E&&t.jsx("circle",{cx:v,cy:S(Math.min(E.value,A.ceiling)),r:4.5,fill:"currentColor",stroke:"var(--glass-float)",strokeWidth:2})]})]}),h!==null&&u>0&&t.jsxs("div",{"aria-hidden":!0,"data-part":"reading",className:"pointer-events-none absolute z-10 rounded-row bg-glass-float px-2.5 py-1.5 font-mono text-fact whitespace-nowrap inset-ring inset-ring-rule",style:M>.62?{top:x,right:u-v+10}:{top:x,left:v+10},children:[t.jsx("span",{className:"block text-ink-muted",children:C(H)}),t.jsx("span",{className:"block text-ink",children:$})]})]})}const xe=6e4;function pe(s,n){const[a,g]=y.useState(null),[w,l]=y.useState(null),c=s>Q,p=n?.startedAt??null,u=n?_(n).at:null;if(y.useEffect(()=>{if(!c)return;let h=!0;const i=()=>{Z(s).then(m=>{h&&(g({windowMs:s,history:m}),l(null))},()=>{h&&l(s)})};i();const r=setInterval(i,xe);return()=>{h=!1,clearInterval(r)}},[c,s,p,u]),!c)return{history:n,status:n?"ready":"loading"};const k=a?.windowMs===s&&(p===null||a.history.startedAt===p)&&(u===null||_(a.history).at===u)?a.history:null;return k?{history:J(k,n),status:"ready"}:{history:null,status:w===s?"failed":"loading"}}const P={needsYou:{title:"Needs you",about:"How many sessions were waiting on you, over time.",tone:"needs-you",chart:"Sessions that need you",describe:s=>`${s} ${s===1?"needs":"need"} you`,explainer:"A session needs you when it has stopped and cannot go on without an answer: a permission to grant, a question, or a plan to approve. The line is how many were waiting at each moment."},working:{title:"Working",about:"How many sessions were in the middle of a task, over time.",tone:"working",chart:"Sessions working",describe:s=>`${s} working`,explainer:"A session is working while it is in the middle of a task. The line is how many were busy at each moment."},idle:{title:"Idle",about:"How many sessions were ready for a new prompt, over time.",tone:"idle",chart:"Idle sessions",describe:s=>`${s} idle`,explainer:s=>`A session is idle when it has finished what it was asked and is ready for a new prompt. One left idle ${s} or more is stale, and is counted under Stale instead. The line is how many were idle at each moment.`}},D="–";function F({label:s,value:n,caption:a}){return t.jsxs("div",{"data-slot":"figure",className:"min-w-0",children:[t.jsx("p",{className:"text-body font-medium text-ink-secondary",children:s}),t.jsx("p",{className:"mt-2 text-stat font-medium tabular-nums",children:n}),t.jsx("p",{className:"mt-2 text-caption text-ink-muted",children:a})]})}function fe({metric:s,state:n,now:a}){const[g,w]=y.useState("15m"),l=O.find(d=>d.id===g)??O[0],c=P[s],{history:p,status:u}=pe(l.ms,n.history),k=a-l.ms,h=p?te(p.points,s):[];let i=null;for(const d of h)d.at<k||d.at>a||(i===null||d.value>i)&&(i=d.value);const r=h[h.length-1],m=r&&a-r.at<=se?r.value:null,x=c.tone==="needs-you"&&ne(n.snapshot?.sessions)===0?"idle":c.tone;return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-5 flex flex-wrap items-end justify-between gap-x-6 gap-y-4",children:[t.jsxs("div",{className:"flex gap-6",children:[t.jsx(F,{label:"Now",value:m===null?D:String(m),caption:m===null?"Not being measured":"At the last check"}),t.jsx("div",{className:"border-l border-rule pl-6",children:t.jsx(F,{label:"Highest",value:i===null?D:String(i),caption:i===null?"Nothing measured":`In ${l.words}`})})]}),t.jsx(oe,{label:"Time window",value:g,onValueChange:w,options:O.map(({id:d,label:N})=>({value:d,label:N}))})]}),t.jsx("div",{className:"mt-5 h-55",children:u==="ready"?t.jsx(me,{samples:h,start:k,end:a,tone:x,label:`${c.chart} over ${l.words}`,describe:c.describe}):u==="loading"?t.jsx(ae,{label:`Reading ${l.words}`,className:"h-full"}):t.jsx(re,{tone:"error",title:`${ie(l.words)} could not be read`,children:t.jsx("p",{children:"Agent Lookout did not answer when asked for it. The page asks again every minute. The 15 minute window is still there."})})}),t.jsxs("div",{"data-slot":"explainer",className:"mt-5 rounded-inner bg-fill-quiet px-4 py-3 inset-ring inset-ring-hairline",children:[t.jsx("p",{className:"text-row font-semibold text-ink",children:"What this counts"}),t.jsx("p",{className:"mt-0.5 text-body text-ink-secondary",children:typeof c.explainer=="string"?c.explainer:c.explainer(le(de(n.snapshot?.timeRules)))}),t.jsxs("p",{className:"mt-1.5 text-body text-ink-secondary",children:["Hatched time was not measured: Agent Lookout was not running, or could not read its source, or the history was cleared then. It is never drawn as zero."," ",n.history?.kept?.where==="memory"?"History covers the last six hours and starts again when Agent Lookout restarts.":"History covers the last six hours, and is kept when Agent Lookout restarts."]})]})]})}function je({metric:s,open:n,onOpenChange:a,state:g,now:w}){const l=P[s];return t.jsx(ee,{open:n,onOpenChange:a,title:l.title,description:l.about,size:"wide",children:t.jsx(fe,{metric:s,state:g,now:w})})}export{je as HistoryPanel};
|