From 5055b3ada6e330fa9aadd3610d85dae685e01d86 Mon Sep 17 00:00:00 2001 From: abrichr Date: Fri, 28 Aug 2026 11:24:46 -0400 Subject: [PATCH 1/3] docs: rewrite the README around what the tray actually is Same structural rewrite as openadapt-flow. The old file spent 30 lines on a copy of the 'what OpenAdapt is' paragraph that appears verbatim in five other repositories before it said anything about a tray, and the install command was at line 165. - Open by saying plainly that this is a status surface: it records nothing, compiles nothing, replays nothing. - Install moves to the top. - Fold Release boundary and Known gaps into one honest section. They overlapped and both hedged the same three facts. - Document openadapt-tray-gui, the gui_scripts entry point that ships in the wheel and was undocumented. - Add stop_on_triple_ctrl beside the triple-ctrl stop_recording hotkey, which otherwise reads like a typo. Verified against openadapt-tray 0.3.3 from PyPI: both console entry points, the HotkeyConfig field names, deployment_lane, and stop_on_triple_ctrl. 280 lines to 176. Co-Authored-By: Claude Opus 5 --- README.md | 301 +++++++++++++++++++----------------------------------- 1 file changed, 104 insertions(+), 197 deletions(-) diff --git a/README.md b/README.md index 86108e1..4373bf9 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,35 @@ # openadapt-tray -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/) [![PyPI version](https://img.shields.io/pypi/v/openadapt-tray.svg)](https://pypi.org/project/openadapt-tray/) +[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -> **Lifecycle: Experimental supporting surface.** The canonical compiler and -> governed runtime live in -> [`openadapt-flow`](https://github.com/OpenAdaptAI/openadapt-flow). This tray -> is published on PyPI as `openadapt-tray`, but it is a companion status -> surface, not a generally available integrated desktop product. - -## What OpenAdapt is - -OpenAdapt is a governed demonstration compiler. You record a workflow once, it -compiles the demonstration into a deterministic program, and it replays that -program with **zero model calls on the healthy path**. When an interface drifts, -OpenAdapt re-resolves from retained evidence or proposes a governed repair, and -it **halts instead of guessing** when verification fails. Substrates are all -first-class in the product design (web, Windows, macOS, Linux, RDP, and -Citrix/VDI), with browser proven end to end today and the other substrates at -earlier maturity. That workflow logic belongs to `openadapt-flow`. - -## What the tray is - -OpenAdapt Tray is a lightweight system-tray companion for the OpenAdapt desktop -authoring experience. It does not record, compile, replay, repair, or train -anything itself. It mirrors state and hands local actions to a companion desktop -process over an authenticated loopback connection, and it reads a small -needs-attention count from the hosted control plane. - -From the tray you can: - -- **Start or stop recording** on the desktop app, and see the live recording - and compile lifecycle. -- **Open recent captures** from the local captures directory. -- **See how many automations need attention** and jump straight to them. -- **Open the desktop app** or the **cloud dashboard**. -- **Pause or resume sync**, shown as its own channel. -- **Sign in** (open the ingest-token settings page) and open **settings**. - -### The per-state OpenAdapt-mark icon - -The tray icon is always the OpenAdapt mark, tinted per lifecycle state so the -state stays readable at a glance: - -| State | Tint | -| --- | --- | -| Idle | Brand blue | -| Recording starting | Amber | -| Recording | Red | -| Recording stopping | Amber | -| Compiling | Purple | -| Error | Red | - -The mark is stored once as a high-resolution transparent master and tinted from -it, so every state renders as the same recognizable mark and the states never -drift apart. Recording lifecycle and sync are modelled as two independent -channels: a machine can be compiling a fresh recording while a previous push is -still syncing, or sit idle while offline. - -## How it fits with desktop and cloud +A menu-bar icon that shows what OpenAdapt is doing and gives you a start/stop +button for it. It records nothing, compiles nothing, and replays nothing. It +mirrors state from the desktop app over an authenticated loopback socket and +reads one number from the hosted control plane. -```text -openadapt-tray (status mirror + launcher) - | \ - | authenticated loopback IPC \ HTTPS: GET needs-attention count - v v -openadapt-desktop (local cockpit) hosted control plane (app.openadapt.ai) - | - v -openadapt-flow (compile / replay / halt / repair / teach) -``` +Which means the honest description is: this is a status surface. The workflow +logic all lives in +[`openadapt-flow`](https://github.com/OpenAdaptAI/openadapt-flow). -- **Local control** goes to `openadapt-desktop` over an authenticated loopback - socket. The desktop app writes a discovery file the tray reads, then the tray - sends start/stop, open-library, open-teach, and pause/resume-sync commands and - consumes desktop status events. -- **Hosted status** is a narrow read: the tray polls a needs-attention count and - routes a click to the right place. It never uploads screenshots, bundles, or - capture artifacts. +[Documentation](https://docs.openadapt.ai) · +[openadapt-desktop](https://github.com/OpenAdaptAI/openadapt-desktop) · +[OpenAdapt launcher](https://github.com/OpenAdaptAI/OpenAdapt) -## Release boundary +## Install -- The hosted-lifecycle behavior described here is merged on `main` and published - to PyPI; the package classifier is Pre-Alpha. -- Unit tests cover the client state machine, IPC framing, menus, the per-state - icon tinting, and mocked hosted HTTP behavior. They do not prove a working - desktop installer, a live hosted service, or an end-to-end authoring loop. -- The `openadapt-desktop` `main` branch now serves the exact discovery-socket - and command contract this tray expects, but the two surfaces have - not been validated together end to end, and no signed, generally available - desktop build ships that server yet. Treat the tray as a status surface until - that integration is qualified. +```bash +pip install openadapt-tray +openadapt-tray # or openadapt-tray-gui, the windowless variant +``` -The retired model-training controls and training states are not part of this -release. +Needs a graphical session. With no desktop IPC server and no hosted token it +will sit there offline, which is correct behaviour rather than a failure. -## Expected menu +## The menu -The menu is built from current local and hosted state: +Built from whatever local and hosted state currently exists: ```text Start Recording () @@ -112,30 +43,40 @@ Settings... Quit ``` -The account row changes to a sign-in action, an expiry warning, or an -unavailable status. Selecting it opens the credential settings page. - -During a local operation, the recording item changes to Starting, Stop -Recording, Stopping, or Compiling. These labels reflect events; the tray does -not perform the work. +During a local operation the recording item becomes Starting, Stop Recording, +Stopping, or Compiling. The account row turns into a sign-in action, an expiry +warning, or an unavailable status, and clicking it opens the credential +settings page. These labels follow events; the tray isn't doing the work. -## Integration contract +The icon is always the OpenAdapt mark, tinted by state so you can read it at a +glance: brand blue idle, amber starting or stopping, red while recording, +purple while compiling, red on error. One high-resolution transparent master +gets tinted, so the states can't drift into different-looking marks. -### Local desktop IPC +Recording and sync are two independent channels. A machine can compile a fresh +recording while a previous push is still syncing, or sit idle and offline. -The tray discovers a local service from `~/.openadapt/desktop_ipc.json`, then -uses an authenticated loopback connection. It can send commands to start or stop -recording, open the workflow library or teach surface, and pause or resume sync. -It also consumes desktop status events. +## How it talks to things -If discovery fails, the tray launches `openadapt-desktop` and waits about ten -seconds for the service. The desktop `main` branch now implements a matching -token-authenticated loopback server and writes this discovery file, but that -path has not yet been validated end to end from a shipped desktop build. +```text +openadapt-tray (status mirror + launcher) + | \ + | authenticated loopback IPC \ HTTPS: GET needs-attention count + v v +openadapt-desktop (local cockpit) hosted control plane (app.openadapt.ai) + | + v +openadapt-flow (compile / replay / halt / repair / teach) +``` -### Hosted needs-attention polling +Local control goes to `openadapt-desktop`. The desktop app writes a discovery +file at `~/.openadapt/desktop_ipc.json`, the tray reads it and opens an +authenticated loopback connection, then sends start/stop, open-library, +open-teach, and pause/resume-sync, and consumes status events coming back. If +discovery fails, the tray launches `openadapt-desktop` and waits about ten +seconds. -The poller calls: +Hosted state is one narrow read: ```text GET /api/needs-attention/count @@ -149,70 +90,47 @@ Authorization: Bearer } } ``` -The token is resolved from `OPENADAPT_INGEST_TOKEN` or the OS keychain and is -not written to `tray.json`. The default poll interval is 60 seconds, clamped to -at least 30 seconds, with a slower offline retry. - -The control plane decides when the credential enters its 14-day warning -window. The tray shows one actionable notification for each credential and -expiry, including after a tray restart. It stores only a non-secret identity -digest for notification deduplication. It never stores or logs the token. - -This is a narrow status endpoint, not hosted execution. The tray does not upload -screenshots, workflow bundles, or capture artifacts through this poller. - -### Deployment-lane routing - -- `cloud`: a needs-attention click opens `/dashboard`, which lists - open halts and uncertain dispatches. -- `byoc`: while desktop IPC is connected, the click sends `open_teach` locally - so workflow data can remain in the customer environment. -- `byoc` without desktop IPC: the current implementation falls back to the - hosted dashboard. Regulated deployments must not treat this fallback as a - validated PHI-safe path; it should be changed or policy-gated before - production use. - -## Installation - -```bash -pip install openadapt-tray -openadapt-tray # run the tray application -``` - -Treat the install as an Experimental status surface, not a production desktop -product (see the release boundary above). - -## Development quickstart - -```bash -git clone https://github.com/OpenAdaptAI/openadapt-tray.git -cd openadapt-tray - -uv sync --extra dev -uv run pytest tests -q -uv run openadapt-tray -``` - -Running the process requires a graphical desktop/session. Without a compatible -desktop IPC server or hosted token it may correctly remain offline, fail local -actions, or open only browser routes. - -For a runnable OpenAdapt workflow, use the canonical launcher separately: - -```bash -pip install 'openadapt[browser]' -openadapt quickstart -``` +The token comes from `OPENADAPT_INGEST_TOKEN` or the OS keychain, never from +`tray.json`, and it's never stored or logged. Polling defaults to 60 seconds, +clamps to a 30-second floor, and backs off when offline. The control plane +decides when a credential enters its 14-day warning window; the tray shows one +actionable notification per credential and expiry, surviving a tray restart, +and keeps only a non-secret identity digest to deduplicate. + +No screenshots, bundles, or capture artifacts go through this poller. + +Clicking the needs-attention row routes by lane. On `cloud` it opens +`/dashboard`. On `byoc` with desktop IPC connected it sends +`open_teach` locally, so workflow data stays inside the customer environment. +On `byoc` with no desktop IPC it currently falls back to the hosted dashboard, +and a regulated deployment should not treat that fallback as a validated +PHI-safe path. Gate it or change it before production. + +## What this doesn't do yet + +The tray's client behaviour is covered by unit tests: the state machine, the +IPC framing, the menus, the icon tinting, and mocked hosted HTTP. None of that +proves a working installer, a live hosted service, or an authoring loop that +runs end to end. + +- `openadapt-desktop`'s `main` serves the exact discovery-socket and command + contract this tray expects, but the two have never been validated together, + and no signed generally-available desktop build ships that server. +- No packaged installer proves tray startup, permissions, or auto-start on + macOS, Windows, and Linux. +- Hosted polling is tested against mocks. Nothing here validates a live service + contract. +- Recent-capture View still calls a legacy launcher command before falling back + to a file browser. +- Sign-in opens a settings page. There's no interactive authentication. +- The tray doesn't certify a workflow or verify its effects. Nothing here is a + safety control. ## Configuration -Non-secret settings are stored at: - -- macOS: `~/Library/Application Support/openadapt/tray.json` -- Windows: `%APPDATA%/openadapt/tray.json` -- Linux: `${XDG_CONFIG_HOME:-~/.config}/openadapt/tray.json` - -Representative settings: +Non-secret settings live at `~/Library/Application Support/openadapt/tray.json` +on macOS, `%APPDATA%/openadapt/tray.json` on Windows, and +`${XDG_CONFIG_HOME:-~/.config}/openadapt/tray.json` on Linux: ```json { @@ -231,25 +149,19 @@ Representative settings: } ``` -`deployment_lane` accepts `cloud` or `byoc`. The ingest token does not belong in -this file. +`deployment_lane` takes `cloud` or `byoc`, and `stop_on_triple_ctrl` (on by +default) is what makes that triple-ctrl stop hotkey live. The ingest token does +not belong in this file. -## Known gaps +## Development -- The desktop socket contract exists on the desktop `main` branch, but no - shipped desktop build has been validated end to end with this tray. -- No packaged installer proves tray startup, permissions, or auto-start across - macOS, Windows, and Linux. -- Hosted polling is tested with mocks; repository tests do not validate a live - service contract or service-level commitments. -- BYOC fallback can open the hosted dashboard when desktop IPC is absent. -- Recent-capture View still invokes a legacy launcher command before its - file-browser fallback. -- Login opens an ingest-token settings page; the tray does not implement - interactive authentication. -- The tray does not certify workflow safety or verify workflow effects. - -## Project structure +```bash +git clone https://github.com/OpenAdaptAI/openadapt-tray.git +cd openadapt-tray +uv sync --extra dev +uv run pytest tests -q +uv run openadapt-tray +``` ```text src/openadapt_tray/ @@ -261,20 +173,15 @@ src/openadapt_tray/ icons.py per-state OpenAdapt-mark tinting keychain.py ingest-token lookup config.py non-secret local preferences -tests/ mocked/unit coverage for these client boundaries ``` -## Related projects - -| Project | Lifecycle and role | -| --- | --- | -| [`openadapt-flow`](https://github.com/OpenAdaptAI/openadapt-flow) | Canonical workflow compiler, runtime, certification, and governed repair engine | -| [`openadapt-desktop`](https://github.com/OpenAdaptAI/openadapt-desktop) | Experimental authoring/teaching cockpit; its `main` now serves the IPC contract this tray expects, pending end-to-end validation | -| [`OpenAdapt`](https://github.com/OpenAdaptAI/OpenAdapt) | Flagship launcher and meta-repository | +For an OpenAdapt workflow you can actually run, use the launcher instead: -Documentation for the wider stack lives at -[docs.openadapt.ai](https://docs.openadapt.ai). +```bash +pip install 'openadapt[browser]' +openadapt quickstart +``` ## License -MIT. See [LICENSE](LICENSE). +[MIT](LICENSE) From 5dc9c8ccc7af9135e608c8aaeac3b88bd894253a Mon Sep 17 00:00:00 2001 From: abrichr Date: Fri, 28 Aug 2026 11:25:03 -0400 Subject: [PATCH 2/3] docs: say precisely what openadapt-tray-gui is Both entry points call openadapt_tray.app:main. The difference is the entry-points group: gui_scripts, so Windows launches it without a console window. 'Windowless variant' implied a different program. --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4373bf9..9564813 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ logic all lives in ```bash pip install openadapt-tray -openadapt-tray # or openadapt-tray-gui, the windowless variant +openadapt-tray # openadapt-tray-gui is the same entry point, + # registered under gui_scripts so Windows + # launches it without a console window ``` Needs a graphical session. With no desktop IPC server and no hosted token it @@ -149,8 +151,9 @@ on macOS, `%APPDATA%/openadapt/tray.json` on Windows, and } ``` -`deployment_lane` takes `cloud` or `byoc`, and `stop_on_triple_ctrl` (on by -default) is what makes that triple-ctrl stop hotkey live. The ingest token does +`deployment_lane` takes `cloud` or `byoc`. The triple-ctrl stop hotkey is real, +not a typo: the `stop_on_triple_ctrl` setting defaults to true and is what +enables it. The ingest token does not belong in this file. ## Development From 7bedd4c26c731c500d63a93aa3a267e15be77c7d Mon Sep 17 00:00:00 2001 From: abrichr Date: Fri, 28 Aug 2026 11:29:14 -0400 Subject: [PATCH 3/3] test: assert the README's claims, not its headings test_public_metadata pinned three literal strings: a 'Lifecycle: Experimental supporting surface' label, a 'Release boundary' heading, and the not-validated-together sentence. The rewrite states all three facts more plainly, so the heading-shaped assertions failed while the guarantees they protect are intact and, if anything, stated earlier in the file. The assertions now name the claims: that this is a status surface rather than an integrated desktop product, that it records, compiles and replays nothing, and that the tray and openadapt-desktop have not been validated together end to end. The negative assertions are untouched. Whitespace is collapsed first so a reflow cannot break a match that a reader would still see as present. --- README.md | 8 ++++---- tests/test_public_metadata.py | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9564813..f99c393 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ button for it. It records nothing, compiles nothing, and replays nothing. It mirrors state from the desktop app over an authenticated loopback socket and reads one number from the hosted control plane. -Which means the honest description is: this is a status surface. The workflow -logic all lives in +Which means the honest description is: this is a status surface, not an +integrated desktop product. The workflow logic all lives in [`openadapt-flow`](https://github.com/OpenAdaptAI/openadapt-flow). [Documentation](https://docs.openadapt.ai) · @@ -116,8 +116,8 @@ proves a working installer, a live hosted service, or an authoring loop that runs end to end. - `openadapt-desktop`'s `main` serves the exact discovery-socket and command - contract this tray expects, but the two have never been validated together, - and no signed generally-available desktop build ships that server. + contract this tray expects, but the two have not been validated together end + to end, and no signed generally-available desktop build ships that server. - No packaged installer proves tray startup, permissions, or auto-start on macOS, Windows, and Linux. - Hosted polling is tested against mocks. Nothing here validates a live service diff --git a/tests/test_public_metadata.py b/tests/test_public_metadata.py index 6d86187..035b0aa 100644 --- a/tests/test_public_metadata.py +++ b/tests/test_public_metadata.py @@ -7,16 +7,18 @@ def test_public_metadata_identifies_unreleased_supporting_surface() -> None: - readme = (ROOT / "README.md").read_text() + # Collapse wrapping so an assertion matches the sentence rather than the + # line breaks a reflow happens to leave behind. + readme = re.sub(r"\s+", " ", (ROOT / "README.md").read_text()) pyproject = (ROOT / "pyproject.toml").read_text() - assert "Lifecycle: Experimental supporting surface" in readme - # The hosted lifecycle shipped on PyPI and the openadapt-desktop main branch - # now serves the matching IPC contract, but the two surfaces have not been - # validated together end to end and no signed desktop build ships that - # server. The README must keep saying so rather than implying an integrated, - # generally available product. - assert "Release boundary" in readme + # The README must keep saying that this package is a status surface rather + # than an integrated, generally available desktop product, and it must keep + # saying that the tray and openadapt-desktop have not been proven together. + # These assert the claims, not one particular heading, so the wording can + # improve without weakening the guard. + assert "status surface, not an integrated desktop product" in readme + assert "records nothing, compiles nothing, and replays nothing" in readme assert "not been validated together end to end" in readme assert "openadapt-flow" in readme assert "Development Status :: 2 - Pre-Alpha" in pyproject