agent-lookout 0.2.0 → 0.2.1

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 CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  Changes that a user of Agent Lookout would notice, newest first.
4
4
 
5
+ ## 0.2.1 - 2026-10-06
6
+
7
+ The first version with a Mac app to download: its [release on GitHub](https://github.com/Olanetsoft/agent-lookout/releases/tag/v0.2.1) has a disk image for Apple silicon and one for Intel.
8
+
9
+ ### Added
10
+
11
+ - A Mac app for Apple silicon and for Intel, to download from the release or build from the repository with `npm run dist:mac`. It shows the dashboard in its own window, runs Agent Lookout inside itself with no port opened, keeps running in the Dock when the window is closed, shows on its Dock icon how many sessions need you, and shows notifications under its own name. It is not signed yet, so macOS asks once before it first opens: [Desktop app](docs/GUIDE.md#desktop-app) has the steps.
12
+ - The Mac app can update itself from the project's releases on GitHub. About once a day while it runs, it asks GitHub whether a newer version is out, sending nothing about your sessions, and a switch under Updates in Settings turns that off. Check for Updates…, in the Agent Lookout menu and in Settings, checks at once. A newer version is downloaded and checked against the size and SHA-512 its release gives, and installed only when you press Install and Restart, which quits the app and opens the new version. It updates itself from Applications or any folder it can change, and run from its disk image, another disk or a read-only copy it says how to move it instead. The npm package and the repository never check. [Updates](docs/GUIDE.md#updates) has the details.
13
+
5
14
  ## 0.2.0 - 2026-10-06
6
15
 
7
16
  The first version on npm. It shows Claude Code and Codex sessions, and those of any agent that writes a status file. The one thing it changes on the machine is which tmux pane, or which tab of Terminal or iTerm2, is in front, when you press Jump.
package/PRIVACY.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Privacy
2
2
 
3
- Agent Lookout runs on your machine and reads a small amount of metadata about your Claude Code and Codex 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. 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.
3
+ Agent Lookout runs on your machine and reads a small amount of metadata about your Claude Code and Codex 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. 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. 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
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 reads nothing of a transcript while the session is not waiting, keeps nothing of it once the wait ends, and never sends it off the machine: no email, webhook post or 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
 
@@ -160,11 +160,11 @@ It reads twenty settings from the environment: `AGENT_LOOKOUT_CLAUDE_HOME`, `AGE
160
160
  - In a git repository, anything but the `.git` file, `HEAD` and a worktree's `commondir` described under [Git repositories](#git-repositories).
161
161
  - The files of any other application.
162
162
 
163
- It runs no program but the `claude` binary, `ps`, `tmux`, on macOS `osascript`, to show a notification and to bring a tab of Terminal or iTerm2 forward, and, only for `--open`, `open` or `xdg-open`. It runs no git command. It never writes to `~/.claude`, to the Codex folder, to the folder of status files, to any git repository or to any agent tool's files, and it never sends input to a session. It never makes, renames or deletes a status file. The one thing it changes outside itself is which tmux pane, window and session are selected, or which tab of Terminal or iTerm2 is in front, and only when you press Jump.
163
+ It runs no program but the `claude` binary, `ps`, `tmux`, on macOS `osascript`, to show a notification and to bring a tab of Terminal or iTerm2 forward, and, only for `--open`, `open` or `xdg-open`. The Mac app also runs `ditto` and `plutil` to unpack and check a newer version of itself, and, only when you press Install and Restart, a short `sh` script that puts it in place, as [Updates (Mac app only)](#updates-mac-app-only) describes. It runs no git command. It never writes to `~/.claude`, to the Codex folder, to the folder of status files, to any git repository or to any agent tool's files, and it never sends input to a session. It never makes, renames or deletes a status file. The one thing it changes outside itself is which tmux pane, window and session are selected, or which tab of Terminal or iTerm2 is in front, and only when you press Jump.
164
164
 
165
165
  ## Network
166
166
 
167
- With email and the webhook off, which is the default, the only network traffic Agent Lookout's own code makes is between its own server and the dashboard in your browser, or the `agent-lookout status` and `agent-lookout mcp` commands, on the same machine. With email set up, it also connects to the mail server you named, once for each email, as [Email](#email) describes. With a webhook address set, it also connects to that address, once for each post, as [Webhook](#webhook) describes. One more connection never leaves the machine either: before `npm start` or `agent-lookout` begins listening, it connects once to its own address and port to see whether another program already answers there, and sends nothing over that connection. There is no telemetry, no analytics, no crash reporting and no update check, and there is no account. Fonts and scripts are bundled, so the page loads nothing from the internet.
167
+ With email and the webhook off, which is the default, the only network traffic Agent Lookout's own code makes is between its own server and the dashboard in your browser, or the `agent-lookout status` and `agent-lookout mcp` commands, on the same machine. With email set up, it also connects to the mail server you named, once for each email, as [Email](#email) describes. With a webhook address set, it also connects to that address, once for each post, as [Webhook](#webhook) describes. One more connection never leaves the machine either: before `npm start` or `agent-lookout` begins listening, it connects once to its own address and port to see whether another program already answers there, and sends nothing over that connection. There is no telemetry, no analytics, no crash reporting and no account. There is no update check either, except in the Mac app, which asks GitHub about once a day whether a newer version of it is out, as [Updates (Mac app only)](#updates-mac-app-only) describes. Fonts and scripts are bundled, so the page loads nothing from the internet.
168
168
 
169
169
  Started with `npx agent-lookout`, npm itself contacts its registry: to download the package the first time, and each time after to ask whether a newer version is out, as it does for any package it runs. That is npm's own traffic, not Agent Lookout's. Installed with `npm install -g agent-lookout` and started as `agent-lookout`, npm is not involved.
170
170
 
@@ -315,6 +315,36 @@ Through `GET /api/webhook`, the dashboard learns whether the webhook is on, the
315
315
 
316
316
  Once a post has been sent, Agent Lookout has no hold on it. The service at the address, such as Slack or Discord, keeps the message, and whatever it records about it, for as long as its own settings and terms say, and shows it to everyone who can read that channel. Agent Lookout cannot recall or delete it.
317
317
 
318
+ ## Updates (Mac app only)
319
+
320
+ The Mac app checks whether a newer version of it has been released. Nothing else does: the npm package, `npx agent-lookout`, `npm start`, `npm run dev` and a development run of the app, `npm run dev:desktop`, never check by themselves.
321
+
322
+ ### What it asks for, and from where
323
+
324
+ It asks GitHub, over HTTPS, for one small file, `latest-mac.yml`, from the latest release of the project, at `https://github.com/Olanetsoft/agent-lookout/releases/latest/download/latest-mac.yml`. The file gives the latest version's number, and the name, size and SHA-512 of each of its files. GitHub answers with a redirect to its own file host, `release-assets.githubusercontent.com`, or `objects.githubusercontent.com`, and the app follows a redirect only to one of those three hosts, only over HTTPS. The file may be no larger than 64 KB.
325
+
326
+ Only when that version is newer than the one running, and is not a prerelease, does it ask for one more file: the zip of the new version for your kind of Mac, from that version's release, about 120 MB, from the same hosts. It checks the zip's size and SHA-512 against `latest-mac.yml` as it downloads, unpacks it with `/usr/bin/ditto`, and checks with the bundle's `Info.plist`, turned into text by `/usr/bin/plutil` when it needs to be, that it is Agent Lookout and the version offered. A file that does not match is deleted.
327
+
328
+ ### What a request holds
329
+
330
+ Each request holds the address above and a `User-Agent` header, `Agent-Lookout/` followed by the version running, such as `Agent-Lookout/0.2.1`, and an `Accept` header. It holds no cookie, no account, no session data, no name, path or count of sessions, and nothing else about this computer. As with any web request, GitHub sees the network address the request comes from, and it may keep what it records of requests under its own terms. The requests are made by the app's main process with Node's own HTTPS client, with the certificate checked. The page in the app's window cannot make any request off the machine.
331
+
332
+ ### How often
333
+
334
+ A short while after the app starts, if a day has passed since the last check that had an answer, and then once a day while it runs. A check that had no answer, because the Mac was offline for instance, is tried again after six hours. Check for Updates…, in the Agent Lookout menu and in Settings, checks at once, whether or not the automatic check is on.
335
+
336
+ ### Turning it off
337
+
338
+ Settings has an Updates card, in the Mac app only, with the switch Check for updates automatically. Turned off, the app asks GitHub nothing until you press Check for Updates…. It is on until you turn it off.
339
+
340
+ ### Installing
341
+
342
+ Nothing is installed until you press Install and Restart, in Settings, or in the dialog Check for Updates… shows when the window is closed. The app then starts a short script, `/bin/sh` with a script that never changes, and quits. The script waits for the app to quit, moves the old app into the update's temporary folder, moves the new one into its place, and opens it, using `/bin/kill`, `/bin/sleep`, `/bin/mv` and `/usr/bin/open`. It is given the app's process ID and three paths: where the app is, and two in the temporary folder the app made itself. Nothing from the network reaches its arguments. If the new version cannot be moved in, the old one is put back and opened, and says under Updates in Settings that the version could not be installed. The app does this only from a folder it can change, such as Applications, and not when macOS runs it from where it was downloaded, or when it runs from its disk image or another disk under `/Volumes/`: then it says so, and links to the release page.
343
+
344
+ ### What is kept
345
+
346
+ `update-state.json`, in `~/Library/Application Support/Agent Lookout/`, holds whether the automatic check is on, when a check last had an answer, the last version a notification was shown for, so each version is told of once, and, from Install and Restart until the app opens again, the version being installed, so the copy that opens knows whether that worked. The download is in a folder of the app's own in the system's temporary folder, and is deleted when the app quits without installing it, or when it does not match. After an install, the old app stays in that folder, and macOS clears it there in time.
347
+
318
348
  ## The MCP server
319
349
 
320
350
  `agent-lookout mcp` does nothing until you add it to an agent's app, such as Claude Code, as the [guide](docs/GUIDE.md#for-your-agents) shows. The app then starts it, and speaks to it over its stdin and stdout. It opens no port.
@@ -343,7 +373,7 @@ Nothing. Each answer is worked out from one reading of the list and is gone once
343
373
 
344
374
  ## Storage
345
375
 
346
- Agent Lookout stores no session data on disk, and its own code writes no files. The latest session list, the last 1,000 events and the last six hours of history are held in memory and are gone when Agent Lookout stops. What a waiting session is asking is in the latest session list only while it waits, and in no event or point of history. So are the tmux panes and terminal tabs it last found, the branches and repositories it last read, and what the dashboard pages last said about notifications.
376
+ Agent Lookout stores no session data on disk, and its own code writes no files, except the Mac app's, below. The latest session list, the last 1,000 events and the last six hours of history are held in memory and are gone when Agent Lookout stops. What a waiting session is asking is in the latest session list only while it waits, and in no event or point of history. So are the tmux panes and terminal tabs it last found, the branches and repositories it last read, and what the dashboard pages last said about notifications.
347
377
 
348
378
  With notifications on, each notification holds a session's name, and for a wait what the session is asking, and the operating system keeps it in its notification list, as does the browser for one it made. [Notifications](#notifications) says what it holds and how long it stays. With email set up, each email holds a session's name and its folder's name, and the mail server and the mailbox keep it, as [Email](#email) says. With a webhook set up, each post holds the same, and the service it went to keeps it, as [Webhook](#webhook) says.
349
379
 
@@ -355,6 +385,8 @@ The tools that run it write files of their own. None of these holds session data
355
385
  - `npm run dev` runs through Vite, which keeps pre-bundled copies of the dependencies in `node_modules/.vite/`.
356
386
  - `npm run build` writes the built dashboard to `dist/` and the type checker's records to `node_modules/.tmp/`. `npm run build:package` also writes the bundled command to `dist/cli/`.
357
387
 
388
+ The Mac app keeps three 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. 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.
389
+
358
390
  The dashboard saves six 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. All six belong to one browser at one address.
359
391
 
360
392
  ## What is on screen
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Agent Lookout shows the AI agent sessions on your Mac or Linux computer in one browser page, and which need you.
4
4
 
5
- It finds Claude Code and Codex sessions with no setup, and the sessions of any other agent that writes a small status file. It only watches: it never starts, stops or answers a session. By default Agent Lookout itself sends nothing anywhere. Start it with `npx agent-lookout`.
5
+ It finds Claude Code and Codex sessions with no setup, and the sessions of any other agent that writes a small status file. It only watches: it never starts, stops or answers a session. By default Agent Lookout itself sends nothing anywhere. Start it with `npx agent-lookout`, or download the [Mac app](#mac-app).
6
6
 
7
7
  [Install](#install) · [Guide](docs/GUIDE.md) · [Privacy](#privacy) · [Roadmap](https://github.com/Olanetsoft/agent-lookout/milestones) · [Website](https://agent-lookout.vercel.app)
8
8
 
@@ -42,6 +42,16 @@ Agent Lookout is developed and tested on macOS. On Linux, CI runs every test on
42
42
 
43
43
  ## Install
44
44
 
45
+ ### Mac app
46
+
47
+ Download the disk image for your Mac from the [latest release](https://github.com/Olanetsoft/agent-lookout/releases/latest): the one that ends in `mac-arm64.dmg` for Apple silicon, or `mac-x64.dmg` for Intel. Open it and drag Agent Lookout to Applications. The app needs no Node.js.
48
+
49
+ It is not signed with an Apple Developer ID yet, so the first time you open it, macOS says “Agent Lookout” Not Opened. Press Done, then open System Settings › Privacy & Security and press Open Anyway. [Desktop app](docs/GUIDE.md#desktop-app) has the steps.
50
+
51
+ Kept in Applications, it updates itself: about once a day it asks GitHub whether a newer version is out, and installs it when you press Install and Restart. Settings turns the daily check off. The app opens no port, so `agent-lookout status` and `agent-lookout mcp` cannot reach it. For those, start it with npx as well, and turn notifications on in only one of the two, or you get each one twice.
52
+
53
+ ### With npx
54
+
45
55
  1. Check what you have.
46
56
 
47
57
  ```sh
@@ -78,7 +88,7 @@ Agent Lookout is developed and tested on macOS. On Linux, CI runs every test on
78
88
  curl -s http://127.0.0.1:4777/api/health
79
89
  ```
80
90
 
81
- It prints `{"ok":true,"version":"0.2.0"}`, or a later version number.
91
+ It prints `{"ok":true,"version":"0.2.1"}`, or a later version number.
82
92
 
83
93
  4. Open <http://127.0.0.1:4777>. Your sessions appear within a few seconds. If none do, see [No sessions appear](docs/GUIDE.md#no-sessions-appear). If you used `npm run dev` before, turn notifications on again here: the browser keeps them for each address.
84
94
 
@@ -182,7 +192,7 @@ AGENT_LOOKOUT_CLAUDE_FEED=off npx agent-lookout
182
192
 
183
193
  ## Privacy
184
194
 
185
- By default Agent Lookout itself sends nothing anywhere: no telemetry, no analytics, no crash reports, no update check and no account. Its fonts and scripts are bundled, so the page loads nothing from the internet. It stores no session data on disk: what it knows is held in memory and is gone when it stops. [PRIVACY.md](PRIVACY.md) lists everything it reads, runs and can send.
195
+ By default Agent Lookout itself sends nothing anywhere: no telemetry, no analytics, no crash reports and no account. The one exception is the Mac app, which asks GitHub about once a day whether a newer version of it is out and sends nothing about your sessions: Settings turns that off, [Updates (Mac app only)](PRIVACY.md#updates-mac-app-only) says what it sends, and `npx agent-lookout` and the repository never check. Its fonts and scripts are bundled, so the page loads nothing from the internet. It stores no session data on disk: what it knows is held in memory and is gone when it stops. [PRIVACY.md](PRIVACY.md) lists everything it reads, runs and can send.
186
196
 
187
197
  - Email and the webhook are off until you set them up. Then each goes only to the one address you name, and email goes through the mail server you name. A message holds the session's name, what happened and when, how long a wait has lasted, and the names of its folder, app and agent. It holds no path and no prompt.
188
198
  - Started with `npx agent-lookout`, npm contacts its registry: to download the package the first time, and each time after to ask whether a newer version is out. That is npm's own traffic, not Agent Lookout's. Installed with `npm install -g agent-lookout` and started as `agent-lookout`, npm is not involved.
@@ -200,7 +210,7 @@ It listens on a loopback address only, `127.0.0.1`, or `::1` if you set `AGENT_L
200
210
  - It shows only sessions on this computer. Cloud sessions, Codex cloud tasks and chats in a browser tab do not appear.
201
211
  - Claude Code's session files and Codex's files are not documented by their makers. An update to either can make Agent Lookout show less, or nothing, until it is updated. [docs/adapters/codex.md](docs/adapters/codex.md#what-breaks-when-codex-changes) lists what breaks when Codex changes.
202
212
  - The Events log, the charts and the Timeline start empty each time Agent Lookout starts.
203
- - There is no Mac app yet. It is planned, with support for more agents, in the [milestones](https://github.com/Olanetsoft/agent-lookout/milestones).
213
+ - The Mac app is not signed with an Apple Developer ID yet, so the first time it is opened, macOS does not open it until you allow it in Privacy & Security. There is no app for Linux. Support for more agents is planned in the [milestones](https://github.com/Olanetsoft/agent-lookout/milestones).
204
214
 
205
215
  [What it does not do yet](docs/GUIDE.md#what-it-does-not-do-yet) has the details.
206
216
 
package/SECURITY.md CHANGED
@@ -8,7 +8,7 @@ Say what you found, how to reproduce it and what someone could do with it. Leave
8
8
 
9
9
  If that page does not open, or shows no form, reach the maintainer, Idris Olubisi, through the links on the [Olanetsoft GitHub profile](https://github.com/Olanetsoft). In that first message, say only that you have a security report for Agent Lookout. The maintainer will agree a private way to send the details.
10
10
 
11
- The project has one maintainer, so replies are best effort. There are no tagged releases yet, so fixes land on `main`.
11
+ The project has one maintainer, so replies are best effort. Fixes land on `main` and go out in the next release, whose version only changes its last number, as in 0.2.1 to 0.2.2. Use the latest release.
12
12
 
13
13
  ## What the app handles
14
14
 
@@ -36,6 +36,8 @@ On top of the checks every route makes, the route answers only a request that:
36
36
 
37
37
  It makes one jump a second, and one at a time. `AGENT_LOOKOUT_TMUX=off` stops Agent Lookout running tmux, and the route then finds no pane for any session. `AGENT_LOOKOUT_TERMINAL_JUMP=off` stops it looking for tabs, and the route then brings no tab forward. [PRIVACY.md](PRIVACY.md#tmux) lists each command, and [Terminal and iTerm2](PRIVACY.md#terminal-and-iterm2) the script.
38
38
 
39
+ The Mac app answers four 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`, and three POSTs that check GitHub for a newer version, install the one that is ready, and turn the daily check on or off. 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.
40
+
39
41
  `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`, 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.
40
42
 
41
43
  The local API has no token, and the MCP server adds none, though one was suggested for it. The server opens no port of its own, and the API it reads is the same loopback API that the dashboard and `agent-lookout status` read without one. Websites and other computers are already kept away from that API by the `Host` and `Origin` checks and by its listening on loopback alone. Against websites and other computers a token would add nothing to those checks, and programs running as you could read a token as easily as the API. It could keep out other accounts on a shared computer, the known limit listed below, and is left for later. An API that can be reached some other way, such as from another computer, would need one too, and Agent Lookout has none. [docs/API.md](docs/API.md) describes every route and its checks.
@@ -68,6 +70,7 @@ Session names and folder paths can be sensitive. The main risk is that something
68
70
  - Agent Lookout reading any file in or through a git repository but a `.git` file, a `HEAD` and a worktree's `commondir`, as PRIVACY.md describes, writing anything in a repository, or running a git command.
69
71
  - Anything from the list of processes that `ps` prints, other than the name of the app a session runs in, reaching the dashboard, the API, a log or a file.
70
72
  - A way to make the app run anything other than the `claude` binary it found, `ps`, the `tmux` binary it found and `/usr/bin/osascript`, or to pass any of them arguments they should not get.
73
+ - In the Mac app, a request, a release or anything from the network that makes it run anything but `/usr/bin/ditto`, `/usr/bin/plutil` and its fixed update helper, passes them arguments it did not make itself, installs a download whose size or SHA-512 is not the one the release gives or whose bundle is not Agent Lookout at the version offered, installs anything before Install and Restart is pressed, or makes a request to any host but GitHub's three for a release.
71
74
  - A website, or a page served from anywhere but this machine, making the app select a tmux pane or bring a tab of Terminal or iTerm2 forward.
72
75
  - A request to `POST /api/jump` that makes tmux do anything but select a pane found for a listed session, its window and its tmux session: running a command, sending keys, or reaching any other pane.
73
76
  - A session's name, a tmux session's name, or anything else from a session or from tmux, that `tmux` reads as a command, a target or an option.
@@ -1 +1 @@
1
- import{r as w,j as t}from"./react-C2DiJaR6.js";import{b as Y,t as G,r as K,f as q,a as _,c as U,d as X,m as V,H as J,e as Q,D as Z,g as M,s as ee,h as te,i as se,S as ne,L as ae,C as oe,j as re}from"./index-BEOekdx1.js";import"./motion-BAhdRuCw.js";import"./radix-27o0jeIu.js";const ie={"needs-you":"text-status-needs-you-edge",working:"text-status-working",idle:"text-status-idle"},T={top:12,right:12,bottom:24,left:30},I=220,le=76,de=104,L=1/60;function ce({samples:s,start:a,end:o,tone:g,label:k,describe:i,className:h}){const p=w.useRef(null),[c,y]=w.useState(0),[u,l]=w.useState(null);w.useEffect(()=>{const e=p.current;if(!e)return;const n=new ResizeObserver(([j])=>{j&&y(Math.floor(j.contentRect.width))});return n.observe(e),()=>n.disconnect()},[]);const r=T.left,m=Math.max(r+1,c-T.right),x=T.top,d=I-T.bottom,N=o-a;let O=0;for(const e of s)e.at>=a&&e.at<=o&&e.value>O&&(O=e.value);const A=X(O),b=Y(s,{start:a,end:o},{left:r,right:m,top:x,bottom:d,ceiling:A.ceiling}),W=e=>r+(e-a)/N*(m-r),S=e=>d-e/A.ceiling*(d-x),P=G(a,o,Math.max(1,Math.floor((m-r)/le))),R=u??1,H=a+R*N,E=K(b.runs,H),C=E?i(E.value):"Not measured",v=W(H),B=(e,n)=>{const j=n.left+r,f=m-r;l(Math.min(1,Math.max(0,(e-j)/f)))},z=e=>{const j={ArrowLeft:f=>f-L,ArrowRight:f=>f+L,PageDown:f=>f-L*10,PageUp:f=>f+L*10,Home:()=>0,End:()=>1}[e.key];j&&(e.preventDefault(),l(Math.min(1,Math.max(0,j(u??1)))))};return t.jsxs("div",{ref:p,"data-slot":"history-chart",role:"slider",tabIndex:0,"aria-label":`${k}. Arrow keys read the count at each moment.`,"aria-orientation":"horizontal","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(R*100),"aria-valuetext":`${_(H)}: ${C}`,onPointerMove:e=>B(e.clientX,e.currentTarget.getBoundingClientRect()),onPointerLeave:()=>l(null),onFocus:e=>{e.currentTarget.matches(":focus-visible")&&l(n=>n??1)},onBlur:()=>l(null),onKeyDown:z,className:U("relative w-full cursor-crosshair rounded-row",ie[g],h),style:{height:I},children:[c>0&&b.unmeasured.map((e,n)=>t.jsx("div",{"aria-hidden":!0,"data-part":"unmeasured",className:"unmeasured-hatch absolute",style:{left:e.x,top:x,width:e.width,height:d-x}},n)),c>0&&t.jsxs("svg",{"aria-hidden":!0,width:c,height:I,className:"relative block",children:[A.ticks.map(e=>t.jsxs("g",{children:[t.jsx("line",{"data-part":"grid",x1:r,x2:m,y1:S(e),y2:S(e),stroke:e===0?"var(--rule-strong)":"var(--hairline)",strokeWidth:1,shapeRendering:"crispEdges"}),t.jsx("text",{x:r-8,y:S(e),dy:"0.32em",textAnchor:"end",className:"fill-ink-muted font-mono text-micro",children:e})]},e)),b.unmeasured.map((e,n)=>e.width>=de&&t.jsx("text",{"data-part":"unmeasured-label",x:e.x+8,y:x+14,className:"fill-ink-muted font-mono text-micro",stroke:"var(--glass-float)",strokeWidth:4,strokeLinejoin:"round",paintOrder:"stroke",children:"Not measured"},n)),b.areas.map((e,n)=>t.jsx("path",{d:e,fill:"currentColor",stroke:"none",opacity:.1},n)),b.lines.map((e,n)=>t.jsx("path",{"data-part":"line",d:e,fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},n)),P.map(e=>{const n=W(e);return n<r+16||n>m-16?null:t.jsx("text",{"data-part":"time-tick",x:n,y:I-6,textAnchor:"middle",className:"fill-ink-muted font-mono text-micro",children:q(e)},e)}),u!==null&&t.jsxs("g",{"data-part":"crosshair",children:[t.jsx("line",{x1:v,x2:v,y1:x,y2:d,stroke:"var(--ink-muted)",strokeWidth:1,shapeRendering:"crispEdges"}),E&&t.jsx("circle",{cx:v,cy:S(Math.min(E.value,A.ceiling)),r:4.5,fill:"currentColor",stroke:"var(--glass-float)",strokeWidth:2})]})]}),u!==null&&c>0&&t.jsxs("div",{"aria-hidden":!0,"data-part":"reading",className:"pointer-events-none absolute z-10 rounded-row bg-glass-float px-2.5 py-1.5 font-mono text-fact whitespace-nowrap inset-ring inset-ring-rule",style:R>.62?{top:x,right:c-v+10}:{top:x,left:v+10},children:[t.jsx("span",{className:"block text-ink-muted",children:_(H)}),t.jsx("span",{className:"block text-ink",children:C})]})]})}const ue=6e4;function he(s,a){const[o,g]=w.useState(null),[k,i]=w.useState(null),h=s>J,p=a?.startedAt??null;if(w.useEffect(()=>{if(!h)return;let y=!0;const u=()=>{Q(s).then(r=>{y&&(g({windowMs:s,history:r}),i(null))},()=>{y&&i(s)})};u();const l=setInterval(u,ue);return()=>{y=!1,clearInterval(l)}},[h,s,p]),!h)return{history:a,status:a?"ready":"loading"};const c=o?.windowMs===s&&(p===null||o.history.startedAt===p)?o.history:null;return c?{history:V(c,a),status:"ready"}:{history:null,status:k===s?"failed":"loading"}}const F={needsYou:{title:"Needs you",about:"How many sessions were waiting on you, over time.",tone:"needs-you",chart:"Sessions that need you",describe:s=>`${s} ${s===1?"needs":"need"} you`,explainer:"A session needs you when it has stopped and cannot go on without an answer: a permission to grant, a question, or a plan to approve. The line is how many were waiting at each moment."},working:{title:"Working",about:"How many sessions were in the middle of a task, over time.",tone:"working",chart:"Sessions working",describe:s=>`${s} working`,explainer:"A session is working while it is in the middle of a task. The line is how many were busy at each moment."},idle:{title:"Idle",about:"How many sessions were ready for a new prompt, over time.",tone:"idle",chart:"Idle sessions",describe:s=>`${s} idle`,explainer:"A session is idle when it has finished what it was asked and is ready for a new prompt. One left idle a day or more is stale, and is counted under Stale instead. The line is how many were idle at each moment."}},$="–";function D({label:s,value:a,caption:o}){return t.jsxs("div",{"data-slot":"figure",className:"min-w-0",children:[t.jsx("p",{className:"text-body font-medium text-ink-secondary",children:s}),t.jsx("p",{className:"mt-2 text-stat font-medium tabular-nums",children:a}),t.jsx("p",{className:"mt-2 text-caption text-ink-muted",children:o})]})}function me({metric:s,state:a,now:o}){const[g,k]=w.useState("15m"),i=M.find(d=>d.id===g)??M[0],h=F[s],{history:p,status:c}=he(i.ms,a.history),y=o-i.ms,u=p?ee(p.points,s):[];let l=null;for(const d of u)d.at<y||d.at>o||(l===null||d.value>l)&&(l=d.value);const r=u[u.length-1],m=r&&o-r.at<=te?r.value:null,x=h.tone==="needs-you"&&se(a.snapshot?.sessions)===0?"idle":h.tone;return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-5 flex flex-wrap items-end justify-between gap-x-6 gap-y-4",children:[t.jsxs("div",{className:"flex gap-6",children:[t.jsx(D,{label:"Now",value:m===null?$:String(m),caption:m===null?"Not being measured":"At the last check"}),t.jsx("div",{className:"border-l border-rule pl-6",children:t.jsx(D,{label:"Highest",value:l===null?$:String(l),caption:l===null?"Nothing measured":`In ${i.words}`})})]}),t.jsx(ne,{label:"Time window",value:g,onValueChange:k,options:M.map(({id:d,label:N})=>({value:d,label:N}))})]}),t.jsx("div",{className:"mt-5 h-55",children:c==="ready"?t.jsx(ce,{samples:u,start:y,end:o,tone:x,label:`${h.chart} over ${i.words}`,describe:h.describe}):c==="loading"?t.jsx(ae,{label:`Reading ${i.words}`,className:"h-full"}):t.jsx(oe,{tone:"error",title:`${re(i.words)} could not be read`,children:t.jsx("p",{children:"Agent Lookout did not answer when asked for it. The page asks again every minute. The 15 minute window is still there."})})}),t.jsxs("div",{"data-slot":"explainer",className:"mt-5 rounded-inner bg-fill-quiet px-4 py-3 inset-ring inset-ring-hairline",children:[t.jsx("p",{className:"text-row font-semibold text-ink",children:"What this counts"}),t.jsx("p",{className:"mt-0.5 text-body text-ink-secondary",children:h.explainer}),t.jsx("p",{className:"mt-1.5 text-body text-ink-secondary",children:"Hatched time was not measured: Agent Lookout was not running, or could not read its source. It is never drawn as zero. History covers the last six hours and starts again when Agent Lookout restarts."})]})]})}function ye({metric:s,open:a,onOpenChange:o,state:g,now:k}){const i=F[s];return t.jsx(Z,{open:a,onOpenChange:o,title:i.title,description:i.about,size:"wide",children:t.jsx(me,{metric:s,state:g,now:k})})}export{ye as HistoryPanel};
1
+ import{r as w,j as t}from"./react-C2DiJaR6.js";import{b as Y,t as G,r as K,f as q,a as _,c as U,d as X,m as V,H as J,e as Q,D as Z,g as M,s as ee,h as te,i as se,S as ne,L as ae,C as oe,j as re}from"./index-DRcy1hT5.js";import"./motion-BAhdRuCw.js";import"./radix-27o0jeIu.js";const ie={"needs-you":"text-status-needs-you-edge",working:"text-status-working",idle:"text-status-idle"},T={top:12,right:12,bottom:24,left:30},I=220,le=76,de=104,L=1/60;function ce({samples:s,start:a,end:o,tone:g,label:k,describe:i,className:h}){const p=w.useRef(null),[c,y]=w.useState(0),[u,l]=w.useState(null);w.useEffect(()=>{const e=p.current;if(!e)return;const n=new ResizeObserver(([j])=>{j&&y(Math.floor(j.contentRect.width))});return n.observe(e),()=>n.disconnect()},[]);const r=T.left,m=Math.max(r+1,c-T.right),x=T.top,d=I-T.bottom,N=o-a;let O=0;for(const e of s)e.at>=a&&e.at<=o&&e.value>O&&(O=e.value);const A=X(O),b=Y(s,{start:a,end:o},{left:r,right:m,top:x,bottom:d,ceiling:A.ceiling}),W=e=>r+(e-a)/N*(m-r),S=e=>d-e/A.ceiling*(d-x),P=G(a,o,Math.max(1,Math.floor((m-r)/le))),R=u??1,H=a+R*N,E=K(b.runs,H),C=E?i(E.value):"Not measured",v=W(H),B=(e,n)=>{const j=n.left+r,f=m-r;l(Math.min(1,Math.max(0,(e-j)/f)))},z=e=>{const j={ArrowLeft:f=>f-L,ArrowRight:f=>f+L,PageDown:f=>f-L*10,PageUp:f=>f+L*10,Home:()=>0,End:()=>1}[e.key];j&&(e.preventDefault(),l(Math.min(1,Math.max(0,j(u??1)))))};return t.jsxs("div",{ref:p,"data-slot":"history-chart",role:"slider",tabIndex:0,"aria-label":`${k}. Arrow keys read the count at each moment.`,"aria-orientation":"horizontal","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(R*100),"aria-valuetext":`${_(H)}: ${C}`,onPointerMove:e=>B(e.clientX,e.currentTarget.getBoundingClientRect()),onPointerLeave:()=>l(null),onFocus:e=>{e.currentTarget.matches(":focus-visible")&&l(n=>n??1)},onBlur:()=>l(null),onKeyDown:z,className:U("relative w-full cursor-crosshair rounded-row",ie[g],h),style:{height:I},children:[c>0&&b.unmeasured.map((e,n)=>t.jsx("div",{"aria-hidden":!0,"data-part":"unmeasured",className:"unmeasured-hatch absolute",style:{left:e.x,top:x,width:e.width,height:d-x}},n)),c>0&&t.jsxs("svg",{"aria-hidden":!0,width:c,height:I,className:"relative block",children:[A.ticks.map(e=>t.jsxs("g",{children:[t.jsx("line",{"data-part":"grid",x1:r,x2:m,y1:S(e),y2:S(e),stroke:e===0?"var(--rule-strong)":"var(--hairline)",strokeWidth:1,shapeRendering:"crispEdges"}),t.jsx("text",{x:r-8,y:S(e),dy:"0.32em",textAnchor:"end",className:"fill-ink-muted font-mono text-micro",children:e})]},e)),b.unmeasured.map((e,n)=>e.width>=de&&t.jsx("text",{"data-part":"unmeasured-label",x:e.x+8,y:x+14,className:"fill-ink-muted font-mono text-micro",stroke:"var(--glass-float)",strokeWidth:4,strokeLinejoin:"round",paintOrder:"stroke",children:"Not measured"},n)),b.areas.map((e,n)=>t.jsx("path",{d:e,fill:"currentColor",stroke:"none",opacity:.1},n)),b.lines.map((e,n)=>t.jsx("path",{"data-part":"line",d:e,fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},n)),P.map(e=>{const n=W(e);return n<r+16||n>m-16?null:t.jsx("text",{"data-part":"time-tick",x:n,y:I-6,textAnchor:"middle",className:"fill-ink-muted font-mono text-micro",children:q(e)},e)}),u!==null&&t.jsxs("g",{"data-part":"crosshair",children:[t.jsx("line",{x1:v,x2:v,y1:x,y2:d,stroke:"var(--ink-muted)",strokeWidth:1,shapeRendering:"crispEdges"}),E&&t.jsx("circle",{cx:v,cy:S(Math.min(E.value,A.ceiling)),r:4.5,fill:"currentColor",stroke:"var(--glass-float)",strokeWidth:2})]})]}),u!==null&&c>0&&t.jsxs("div",{"aria-hidden":!0,"data-part":"reading",className:"pointer-events-none absolute z-10 rounded-row bg-glass-float px-2.5 py-1.5 font-mono text-fact whitespace-nowrap inset-ring inset-ring-rule",style:R>.62?{top:x,right:c-v+10}:{top:x,left:v+10},children:[t.jsx("span",{className:"block text-ink-muted",children:_(H)}),t.jsx("span",{className:"block text-ink",children:C})]})]})}const ue=6e4;function he(s,a){const[o,g]=w.useState(null),[k,i]=w.useState(null),h=s>J,p=a?.startedAt??null;if(w.useEffect(()=>{if(!h)return;let y=!0;const u=()=>{Q(s).then(r=>{y&&(g({windowMs:s,history:r}),i(null))},()=>{y&&i(s)})};u();const l=setInterval(u,ue);return()=>{y=!1,clearInterval(l)}},[h,s,p]),!h)return{history:a,status:a?"ready":"loading"};const c=o?.windowMs===s&&(p===null||o.history.startedAt===p)?o.history:null;return c?{history:V(c,a),status:"ready"}:{history:null,status:k===s?"failed":"loading"}}const F={needsYou:{title:"Needs you",about:"How many sessions were waiting on you, over time.",tone:"needs-you",chart:"Sessions that need you",describe:s=>`${s} ${s===1?"needs":"need"} you`,explainer:"A session needs you when it has stopped and cannot go on without an answer: a permission to grant, a question, or a plan to approve. The line is how many were waiting at each moment."},working:{title:"Working",about:"How many sessions were in the middle of a task, over time.",tone:"working",chart:"Sessions working",describe:s=>`${s} working`,explainer:"A session is working while it is in the middle of a task. The line is how many were busy at each moment."},idle:{title:"Idle",about:"How many sessions were ready for a new prompt, over time.",tone:"idle",chart:"Idle sessions",describe:s=>`${s} idle`,explainer:"A session is idle when it has finished what it was asked and is ready for a new prompt. One left idle a day or more is stale, and is counted under Stale instead. The line is how many were idle at each moment."}},$="–";function D({label:s,value:a,caption:o}){return t.jsxs("div",{"data-slot":"figure",className:"min-w-0",children:[t.jsx("p",{className:"text-body font-medium text-ink-secondary",children:s}),t.jsx("p",{className:"mt-2 text-stat font-medium tabular-nums",children:a}),t.jsx("p",{className:"mt-2 text-caption text-ink-muted",children:o})]})}function me({metric:s,state:a,now:o}){const[g,k]=w.useState("15m"),i=M.find(d=>d.id===g)??M[0],h=F[s],{history:p,status:c}=he(i.ms,a.history),y=o-i.ms,u=p?ee(p.points,s):[];let l=null;for(const d of u)d.at<y||d.at>o||(l===null||d.value>l)&&(l=d.value);const r=u[u.length-1],m=r&&o-r.at<=te?r.value:null,x=h.tone==="needs-you"&&se(a.snapshot?.sessions)===0?"idle":h.tone;return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-5 flex flex-wrap items-end justify-between gap-x-6 gap-y-4",children:[t.jsxs("div",{className:"flex gap-6",children:[t.jsx(D,{label:"Now",value:m===null?$:String(m),caption:m===null?"Not being measured":"At the last check"}),t.jsx("div",{className:"border-l border-rule pl-6",children:t.jsx(D,{label:"Highest",value:l===null?$:String(l),caption:l===null?"Nothing measured":`In ${i.words}`})})]}),t.jsx(ne,{label:"Time window",value:g,onValueChange:k,options:M.map(({id:d,label:N})=>({value:d,label:N}))})]}),t.jsx("div",{className:"mt-5 h-55",children:c==="ready"?t.jsx(ce,{samples:u,start:y,end:o,tone:x,label:`${h.chart} over ${i.words}`,describe:h.describe}):c==="loading"?t.jsx(ae,{label:`Reading ${i.words}`,className:"h-full"}):t.jsx(oe,{tone:"error",title:`${re(i.words)} could not be read`,children:t.jsx("p",{children:"Agent Lookout did not answer when asked for it. The page asks again every minute. The 15 minute window is still there."})})}),t.jsxs("div",{"data-slot":"explainer",className:"mt-5 rounded-inner bg-fill-quiet px-4 py-3 inset-ring inset-ring-hairline",children:[t.jsx("p",{className:"text-row font-semibold text-ink",children:"What this counts"}),t.jsx("p",{className:"mt-0.5 text-body text-ink-secondary",children:h.explainer}),t.jsx("p",{className:"mt-1.5 text-body text-ink-secondary",children:"Hatched time was not measured: Agent Lookout was not running, or could not read its source. It is never drawn as zero. History covers the last six hours and starts again when Agent Lookout restarts."})]})]})}function ye({metric:s,open:a,onOpenChange:o,state:g,now:k}){const i=F[s];return t.jsx(Z,{open:a,onOpenChange:o,title:i.title,description:i.about,size:"wide",children:t.jsx(me,{metric:s,state:g,now:k})})}export{ye as HistoryPanel};
@@ -1 +1 @@
1
- import{r as y,j as e}from"./react-C2DiJaR6.js";import{w as F,u as A,J as $,c as E,D as L,k as M,l as O,M as v,n as q,o as D,p as C,F as I,q as h,v as P,x as k,O as R,f as g,T as W,y as B,z as J,A as K,B as _,E as z,G,I as H,K as w,N as S,P as Q,Q as U,L as V,R as X}from"./index-BEOekdx1.js";import"./motion-BAhdRuCw.js";import"./radix-27o0jeIu.js";const T=[];function Y(s,n){const t=CSS.escape(s),a=[...document.querySelectorAll(`:is([data-slot="session-row"], [data-slot="board-card"], [data-slot="hero-session"])[data-session="${t}"] a[data-part="name"]`)];return n!==null&&a.includes(n)?n:a.find(l=>!l.closest('[data-slot="hero-session"]'))??a[0]??null}function Z({session:s,gone:n,asOf:t,now:a}){const l=s.status==="finished"||s.status==="failed",r=B(s),o=r?"stale":n&&s.status==="needs-you"?"answered":s.status,c=s.statusSince,i=c!==null?Math.max(0,t-c):null,m=J(s,t),d=s.status==="needs-you",p=d?K(s):null,j=d&&!n&&s.waitingText?.trim()||null,u=c!==null?`${l?"at":r?"idle since":"since"} ${k(c,a)}`:"time not reported",x=[d?_(s):null,u,m&&z(m)].filter(f=>typeof f=="string");return e.jsxs(h,{label:"Status",note:j?e.jsxs(e.Fragment,{children:[e.jsx(Q,{"data-part":"asking",lines:2,className:"wrap-anywhere",children:j}),p&&e.jsx("span",{className:"block",children:p})]}):p??void 0,children:[e.jsxs("span",{className:"inline-flex items-center gap-2",children:[e.jsx(G,{kind:o}),e.jsx("span",{"data-part":"status",children:r?"Stale":H[s.status]}),i!==null&&e.jsxs("span",{"data-part":"duration",className:"tabular-nums",children:[e.jsx("span",{className:"sr-only",children:l?` ${w(i)} ago`:` for ${w(i)}`}),e.jsxs("span",{"aria-hidden":!0,children:[S(i),l&&" ago"]})]})]}),e.jsx("span",{"data-part":"since",className:"block text-caption font-normal text-ink-secondary tabular-nums",children:x.join(", ")})]})}function ee({session:s,state:n,now:t,asOf:a}){const l=y.useMemo(()=>U({sessions:n.snapshot?.sessions??T,events:n.events,history:n.history,now:t,asOf:a,eventsFull:n.events.length>=v}),[n.snapshot,n.events,n.history,t,a]);if(!l)return e.jsxs(h,{label:"Waits",children:["Not known",e.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:"The history could not be read"})]});const r=l.sessions.find(i=>i.id===s.id),o=l.period.today?"today":`since ${g(l.period.from)}`;let c="None";if(r){const i=`${r.startKnown?"":"at least "}${S(r.ms)}`;c=r.times===1?`Once, ${i}`:`${r.times} times, ${i} in all`}return e.jsxs(h,{label:"Waits",children:[e.jsx("span",{"data-part":"waits",className:"tabular-nums",children:c}),e.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:o})]})}function N({title:s,aside:n,children:t,...a}){return e.jsxs("section",{...a,"aria-label":s,className:"mt-6",children:[e.jsxs("div",{className:"flex items-baseline justify-between gap-4 border-b border-hairline pb-2",children:[e.jsx("h3",{className:"text-body font-semibold text-ink",children:s}),n&&e.jsx("p",{className:"text-caption text-ink-muted",children:n})]}),t]})}function se({session:s,gone:n,state:t,now:a,asOf:l}){const r=t.snapshot?.sources??[],o=t.snapshot?.sessions??T,c=O(s.surface),i=t.events.length>=v,m=y.useMemo(()=>q(t.events.filter(u=>u.sessionId===s.id),o),[t.events,o,s.id]),d=D(t.events,t.history,i),p=y.useMemo(()=>t.history?C({sessions:o,events:t.events,history:t.history,now:a,asOf:l,eventsFull:i}):null,[o,t.events,t.history,a,l,i]),j=p?.rows.find(u=>u.id===s.id);return e.jsxs(e.Fragment,{children:[n&&e.jsx("p",{"data-part":"gone",className:"mt-4 text-body text-ink-secondary",children:"This session has left the list. Here is what was last known of it."}),e.jsxs(I,{className:"mt-3",children:[e.jsx(Z,{session:s,gone:n,asOf:l,now:a}),e.jsx(h,{label:"Agent",children:P(s,r)}),c!==null&&e.jsx(h,{label:"App",children:c}),e.jsx(h,{label:"Folder",mono:s.cwd!==null,children:s.cwd!==null?e.jsx("span",{"data-part":"folder",className:"wrap-anywhere select-text",children:s.cwd.split("/").map((u,x,f)=>e.jsxs(y.Fragment,{children:[u,x<f.length-1&&e.jsxs(e.Fragment,{children:["/",e.jsx("wbr",{})]})]},x))}):"Not known"}),s.git?.repository!==void 0&&e.jsx(h,{label:"Repository",children:e.jsx("span",{"data-part":"repository",className:"wrap-anywhere",children:s.git.repository.name})}),s.git?.branch!==void 0?e.jsx(h,{label:"Branch",children:e.jsx("span",{"data-part":"branch",className:"wrap-anywhere",children:s.git.branch})}):s.git?.commit!==void 0&&e.jsx(h,{label:"Commit",mono:!0,children:s.git.commit}),e.jsx(h,{label:"Started",children:e.jsx("span",{className:"tabular-nums",children:s.startedAt!==null?k(s.startedAt,a):"Not reported"})}),s.pid!==void 0&&e.jsx(h,{label:"Process",mono:!0,note:s.alive===!1?"The process has ended.":void 0,children:s.pid}),e.jsx(ee,{session:s,state:t,now:a,asOf:l})]}),e.jsx(N,{"data-part":"events",title:"Events",aside:d?`since ${g(d.at)}`:void 0,children:m.length>0?e.jsx(R,{entries:m,now:a,className:"pt-0.5"}):e.jsxs("p",{className:"py-2.5 text-body text-ink-secondary",children:["Nothing has happened to this session"," ",d?`since ${g(d.at)}`:"yet","."]})}),e.jsx(N,{"data-part":"timeline",title:"Timeline",aside:"last hour",children:p&&j?e.jsx(W,{timeline:p,rows:[j],named:!1,label:"Its last hour",className:"pt-3"}):e.jsx("p",{className:"py-2.5 text-body text-ink-secondary",children:p?"It did not run in the last hour.":"The last hour could not be read. The page asks again every two seconds."})})]})}function te({sessionId:s,open:n,onClose:t,state:a,now:l}){const r=a.snapshot?.sessions.find(b=>b.id===s)??null,[o,c]=y.useState(null);r!==null&&r!==o?.session?c({session:r,at:a.lastOkAt??l}):r===null&&o?.session.waitingText!==void 0&&c({...o,session:F(o.session)});const i=r??o?.session??null,m=r===null&&o!==null,d=A(i??{id:s,source:"status-files",surface:"unknown",links:{}}),p=m&&o?o.at:a.phase==="stalled"&&a.lastOkAt!==null?a.lastOkAt:l,j=d.outcome!==null||d.asking!==null;let u,x,f="wide";return i?(u=i.name,x=e.jsxs(e.Fragment,{children:[!m&&e.jsx("div",{className:E("flex flex-wrap items-center gap-x-2 gap-y-1",j&&"mt-3"),children:e.jsx($,{session:i,jump:d})}),e.jsx(se,{session:i,gone:m,state:a,now:l,asOf:p})]})):a.snapshot===null?(u="Session",x=e.jsx(V,{label:"Reading sessions",className:"justify-start px-0 pt-5 pb-1"})):(u="No such session",f="sm",x=e.jsxs("div",{"data-part":"missing",children:[e.jsx("p",{className:"mt-2 text-body text-ink-secondary",children:"Agent Lookout is not watching a session at this address. It may have ended before this page was opened."}),e.jsx(X,{className:"mt-4",onClick:t,children:"Go to the Overview"})]})),e.jsx(L,{open:n,onOpenChange:b=>{b||t()},title:u,size:f,focusTitle:!0,returnFocus:b=>Y(s,b)??document.querySelector("main"),actions:i&&!m&&e.jsx(M,{session:i,jump:d,variant:i.status==="needs-you"?"needs-you":"quiet"}),children:x})}function re({sessionId:s,onClose:n,state:t,now:a}){const[l,r]=y.useState(s);return s!==null&&s!==l&&r(s),l===null?null:e.jsx(te,{sessionId:l,open:s!==null,onClose:n,state:t,now:a},l)}export{re as SessionPanel};
1
+ import{r as y,j as e}from"./react-C2DiJaR6.js";import{w as F,u as A,J as $,c as E,D as L,k as M,l as O,M as v,n as q,o as D,p as C,F as I,q as h,v as P,x as k,O as R,f as g,T as W,y as B,z as J,A as K,B as _,E as z,G,I as H,K as w,N as S,P as Q,Q as U,L as V,R as X}from"./index-DRcy1hT5.js";import"./motion-BAhdRuCw.js";import"./radix-27o0jeIu.js";const T=[];function Y(s,n){const t=CSS.escape(s),a=[...document.querySelectorAll(`:is([data-slot="session-row"], [data-slot="board-card"], [data-slot="hero-session"])[data-session="${t}"] a[data-part="name"]`)];return n!==null&&a.includes(n)?n:a.find(l=>!l.closest('[data-slot="hero-session"]'))??a[0]??null}function Z({session:s,gone:n,asOf:t,now:a}){const l=s.status==="finished"||s.status==="failed",r=B(s),o=r?"stale":n&&s.status==="needs-you"?"answered":s.status,c=s.statusSince,i=c!==null?Math.max(0,t-c):null,m=J(s,t),d=s.status==="needs-you",p=d?K(s):null,j=d&&!n&&s.waitingText?.trim()||null,u=c!==null?`${l?"at":r?"idle since":"since"} ${k(c,a)}`:"time not reported",x=[d?_(s):null,u,m&&z(m)].filter(f=>typeof f=="string");return e.jsxs(h,{label:"Status",note:j?e.jsxs(e.Fragment,{children:[e.jsx(Q,{"data-part":"asking",lines:2,className:"wrap-anywhere",children:j}),p&&e.jsx("span",{className:"block",children:p})]}):p??void 0,children:[e.jsxs("span",{className:"inline-flex items-center gap-2",children:[e.jsx(G,{kind:o}),e.jsx("span",{"data-part":"status",children:r?"Stale":H[s.status]}),i!==null&&e.jsxs("span",{"data-part":"duration",className:"tabular-nums",children:[e.jsx("span",{className:"sr-only",children:l?` ${w(i)} ago`:` for ${w(i)}`}),e.jsxs("span",{"aria-hidden":!0,children:[S(i),l&&" ago"]})]})]}),e.jsx("span",{"data-part":"since",className:"block text-caption font-normal text-ink-secondary tabular-nums",children:x.join(", ")})]})}function ee({session:s,state:n,now:t,asOf:a}){const l=y.useMemo(()=>U({sessions:n.snapshot?.sessions??T,events:n.events,history:n.history,now:t,asOf:a,eventsFull:n.events.length>=v}),[n.snapshot,n.events,n.history,t,a]);if(!l)return e.jsxs(h,{label:"Waits",children:["Not known",e.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:"The history could not be read"})]});const r=l.sessions.find(i=>i.id===s.id),o=l.period.today?"today":`since ${g(l.period.from)}`;let c="None";if(r){const i=`${r.startKnown?"":"at least "}${S(r.ms)}`;c=r.times===1?`Once, ${i}`:`${r.times} times, ${i} in all`}return e.jsxs(h,{label:"Waits",children:[e.jsx("span",{"data-part":"waits",className:"tabular-nums",children:c}),e.jsx("span",{className:"block text-caption font-normal text-ink-secondary",children:o})]})}function N({title:s,aside:n,children:t,...a}){return e.jsxs("section",{...a,"aria-label":s,className:"mt-6",children:[e.jsxs("div",{className:"flex items-baseline justify-between gap-4 border-b border-hairline pb-2",children:[e.jsx("h3",{className:"text-body font-semibold text-ink",children:s}),n&&e.jsx("p",{className:"text-caption text-ink-muted",children:n})]}),t]})}function se({session:s,gone:n,state:t,now:a,asOf:l}){const r=t.snapshot?.sources??[],o=t.snapshot?.sessions??T,c=O(s.surface),i=t.events.length>=v,m=y.useMemo(()=>q(t.events.filter(u=>u.sessionId===s.id),o),[t.events,o,s.id]),d=D(t.events,t.history,i),p=y.useMemo(()=>t.history?C({sessions:o,events:t.events,history:t.history,now:a,asOf:l,eventsFull:i}):null,[o,t.events,t.history,a,l,i]),j=p?.rows.find(u=>u.id===s.id);return e.jsxs(e.Fragment,{children:[n&&e.jsx("p",{"data-part":"gone",className:"mt-4 text-body text-ink-secondary",children:"This session has left the list. Here is what was last known of it."}),e.jsxs(I,{className:"mt-3",children:[e.jsx(Z,{session:s,gone:n,asOf:l,now:a}),e.jsx(h,{label:"Agent",children:P(s,r)}),c!==null&&e.jsx(h,{label:"App",children:c}),e.jsx(h,{label:"Folder",mono:s.cwd!==null,children:s.cwd!==null?e.jsx("span",{"data-part":"folder",className:"wrap-anywhere select-text",children:s.cwd.split("/").map((u,x,f)=>e.jsxs(y.Fragment,{children:[u,x<f.length-1&&e.jsxs(e.Fragment,{children:["/",e.jsx("wbr",{})]})]},x))}):"Not known"}),s.git?.repository!==void 0&&e.jsx(h,{label:"Repository",children:e.jsx("span",{"data-part":"repository",className:"wrap-anywhere",children:s.git.repository.name})}),s.git?.branch!==void 0?e.jsx(h,{label:"Branch",children:e.jsx("span",{"data-part":"branch",className:"wrap-anywhere",children:s.git.branch})}):s.git?.commit!==void 0&&e.jsx(h,{label:"Commit",mono:!0,children:s.git.commit}),e.jsx(h,{label:"Started",children:e.jsx("span",{className:"tabular-nums",children:s.startedAt!==null?k(s.startedAt,a):"Not reported"})}),s.pid!==void 0&&e.jsx(h,{label:"Process",mono:!0,note:s.alive===!1?"The process has ended.":void 0,children:s.pid}),e.jsx(ee,{session:s,state:t,now:a,asOf:l})]}),e.jsx(N,{"data-part":"events",title:"Events",aside:d?`since ${g(d.at)}`:void 0,children:m.length>0?e.jsx(R,{entries:m,now:a,className:"pt-0.5"}):e.jsxs("p",{className:"py-2.5 text-body text-ink-secondary",children:["Nothing has happened to this session"," ",d?`since ${g(d.at)}`:"yet","."]})}),e.jsx(N,{"data-part":"timeline",title:"Timeline",aside:"last hour",children:p&&j?e.jsx(W,{timeline:p,rows:[j],named:!1,label:"Its last hour",className:"pt-3"}):e.jsx("p",{className:"py-2.5 text-body text-ink-secondary",children:p?"It did not run in the last hour.":"The last hour could not be read. The page asks again every two seconds."})})]})}function te({sessionId:s,open:n,onClose:t,state:a,now:l}){const r=a.snapshot?.sessions.find(b=>b.id===s)??null,[o,c]=y.useState(null);r!==null&&r!==o?.session?c({session:r,at:a.lastOkAt??l}):r===null&&o?.session.waitingText!==void 0&&c({...o,session:F(o.session)});const i=r??o?.session??null,m=r===null&&o!==null,d=A(i??{id:s,source:"status-files",surface:"unknown",links:{}}),p=m&&o?o.at:a.phase==="stalled"&&a.lastOkAt!==null?a.lastOkAt:l,j=d.outcome!==null||d.asking!==null;let u,x,f="wide";return i?(u=i.name,x=e.jsxs(e.Fragment,{children:[!m&&e.jsx("div",{className:E("flex flex-wrap items-center gap-x-2 gap-y-1",j&&"mt-3"),children:e.jsx($,{session:i,jump:d})}),e.jsx(se,{session:i,gone:m,state:a,now:l,asOf:p})]})):a.snapshot===null?(u="Session",x=e.jsx(V,{label:"Reading sessions",className:"justify-start px-0 pt-5 pb-1"})):(u="No such session",f="sm",x=e.jsxs("div",{"data-part":"missing",children:[e.jsx("p",{className:"mt-2 text-body text-ink-secondary",children:"Agent Lookout is not watching a session at this address. It may have ended before this page was opened."}),e.jsx(X,{className:"mt-4",onClick:t,children:"Go to the Overview"})]})),e.jsx(L,{open:n,onOpenChange:b=>{b||t()},title:u,size:f,focusTitle:!0,returnFocus:b=>Y(s,b)??document.querySelector("main"),actions:i&&!m&&e.jsx(M,{session:i,jump:d,variant:i.status==="needs-you"?"needs-you":"quiet"}),children:x})}function re({sessionId:s,onClose:n,state:t,now:a}){const[l,r]=y.useState(s);return s!==null&&s!==l&&r(s),l===null?null:e.jsx(te,{sessionId:l,open:s!==null,onClose:n,state:t,now:a},l)}export{re as SessionPanel};