Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 0.6.1 - 2026-07-18

- Fixed a macOS restart loop after an unexpected shutdown when an authenticated Windows relay requested a snapshot before the Codex Micro signals were ready.
- The macOS watcher no longer launches Codex while the app is closed, waits for an unbridged process to stabilize, and uses a generation-independent recovery cooldown to prevent PID-to-PID restart loops.
- Added safe relay error handling, rate-limited offline diagnostics, and a dedicated macOS watcher stderr log.
- Fixed macOS Codex updates exposing avatar-overlay renderer targets before the real main window, which could stop relay snapshots and leave an agent key stuck in `working`.
- Fixed remote agent commands for nested `local:client-new-thread:` task identities being rejected by the relay validator.
- Read the live agent-source setting directly from Codex instead of falling back to Recently updated after app updates.
- Added mode-aware combined agent slots for pinned, recent, priority, and individual Codex Micro assignments while preserving native single-host behavior.
- Interleaved pinned Windows and Mac tasks fairly, de-duplicated mirrored task identities, and routed each key to its real owner.
- Defined individual-assignment conflicts: the Stream Deck computer wins a doubly assigned slot, while the other host fills empty slots; duplicate tasks appear only once.
- Normalized additional native `thinking`, `complete`, `completed`, and `done` status names for stable animations and colors.
- Added diagnostics when the two Codex apps use different agent-source modes.
- Fixed explicit release-audit paths and added regression coverage for private runtime-state rejection.
- Kept CDP evaluation promises alive in the renderer to prevent intermittent `Promise was collected` failures on remote agent presses.
- Added a content-free local session-presence catalog so cloud/SSH mirrors are attributed to the computer that owns the rollout even when Codex omits that task from the owner's six native Micro slots.
- Keeps freshly completed owner sessions visible at unread/error priority instead of dropping them behind idle tasks.
- Bounds and validates relay presence catalogs and clears a derived completion state after that task is opened from the deck.

## 0.6.0 - 2026-07-18

- Added the local macOS Codex Micro launcher and persistent LaunchAgent watcher.
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Other Stream Deck models may work, but the included layout and physical-device t
4. In **Codex Settings > Codex Micro**, choose the agent source, action assignments, joystick actions, and encoder behavior.
5. Build the two Stream Deck pages below.

In Windows + Mac mode, choose the same agent-source mode in both Codex apps when you want both native Pinned lists or both sets of Individual assignments to contribute. Pinned tasks are interleaved fairly. For Individual assignments, the Stream Deck computer wins when both apps assign different tasks to one button, while the other computer fills empty slots. Mirrored copies of the same task are shown only once. See [Multi-host behavior](docs/MULTI_HOST.md#agent-source-modes).

## Recommended 15-key layout

This is the actual polished two-page layout used for the MK.2. It keeps the six live agents on the main page and puts lower-frequency navigation/reasoning controls on page 2.
Expand Down Expand Up @@ -123,10 +125,10 @@ Do not use the launcher while running untrusted local software. See [SECURITY.md

## Compatibility

Release 0.6.0 was locally validated against:
The current build was locally validated against:

- Codex for Windows `26.715.3651.0`
- Codex for macOS `26.715.31251` (build `5538`)
- Codex for Windows `26.715.4045.0`
- Codex for macOS `26.715.31925`
- Stream Deck `7.4.2.22730`
- Windows `10.0.26220.0`
- Node.js `24.13.0`
Expand All @@ -136,7 +138,7 @@ The Windows physical-device path and the Windows+Mac relay were exercised on the

## Troubleshooting

Start with [Troubleshooting](docs/TROUBLESHOOTING.md). The important rule is: restart only the Stream Deck plugin/app for plugin updates. Do not restart Codex unless the launcher explicitly says an unbridged Codex generation needs one recovery restart and you choose to proceed.
Start with [Troubleshooting](docs/TROUBLESHOOTING.md). The important rule is: restart only the Stream Deck plugin/app for plugin updates. The macOS watcher never launches a closed Codex app; after a manual app start it permits at most one guarded recovery restart and opens a global cooldown before any later recovery.

## Build and release validation

Expand Down
27 changes: 19 additions & 8 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ bundle metadata, reads `CFBundleExecutable`, and launches the app bundle through
LaunchServices with the same loopback-only debugging arguments. The per-user
LaunchAgent watcher stores a main-process generation (PID, start time, and
executable path), reuses healthy bridges, and performs at most one graceful
recovery restart for a later unbridged generation. The generation and recovery
policy is persisted atomically and guarded by a PID-directory lock.
recovery restart for a later stable unbridged generation. It never launches a
closed Codex app. A generation-independent cooldown prevents a failed recovery
from becoming a PID-to-PID restart loop, even if LaunchServices immediately
creates another process. The generation and recovery policy is persisted
atomically and guarded by a PID-directory lock. LaunchAgent stderr is retained
separately from the bounded watcher log for post-crash diagnosis.

Both platforms persist a stable `hostId`, `hostName`, and platform identifier.
The relay uses that identity; the CDP port is never a relay endpoint.
Expand All @@ -62,16 +66,22 @@ and routes agent presses by stable `(hostId, threadKey)` identity. Other control
target the host selected by the Windows/Mac toggle.

Host ownership is resolved from exact local rollout filenames, not from a
renderer's mirrored recent list. This distinguishes a Mac desktop task mirrored
through Windows remote SSH from a genuinely Windows-owned task. File contents,
prompts, responses, and project names are never read. The relay never reads or
proxies the remote CLI app-server stream.
renderer's mirrored recent list. This distinguishes a task's owning desktop
from a stale cloud or remote-SSH mirror. A bounded rollout tail is searched only
for structural activity/completion event tags; prompts, responses, project
names, and other content are neither parsed nor relayed. The relay never reads
or proxies the remote CLI app-server stream.

The relay protocol has no arbitrary-evaluation, filesystem, shell, or raw-CDP
operation. Payloads are capped at 64 KiB, authentication is required before a
snapshot or command is accepted, and command results use request IDs with
bounded timeouts.

An authenticated client may remain connected while the Mac app or its native
Micro signals are unavailable. Snapshot failures are caught and rate-limited;
they do not terminate the relay server or watcher. Normal snapshots resume
automatically when the local bridge becomes ready.

### Rendering

Agent keys are original deterministic SVGs generated in memory from task title and state. The status palette is:
Expand Down Expand Up @@ -108,8 +118,9 @@ In single-host mode Codex Deck has no server, API key, analytics endpoint, or
update service. Runtime data stays between Stream Deck, the local plugin
process, and the local Codex renderer. Optional multi-host mode adds one
user-configured Mac listener reachable through SSH or inside the encrypted
tailnet; titles, task IDs, states, ownership metadata, and typed commands pass
between the paired machines and nowhere else.
tailnet; titles, task IDs, states, a bounded catalog of recent local task UUIDs
and modification times, ownership metadata, and typed commands pass between the
paired machines and nowhere else.

## Compatibility boundary

Expand Down
4 changes: 3 additions & 1 deletion docs/MACOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ chmod +x start-codex-deck.sh "Start Codex Deck.command"
./start-codex-deck.sh install
```

`install` copies the watcher runtime into Application Support and installs a per-user LaunchAgent. It does not restart a normal Codex session already open during first installation. A later unbridged Codex generation may receive one graceful recovery restart; the same generation is never restarted repeatedly.
`install` copies the watcher runtime into Application Support and installs a per-user LaunchAgent. It does not restart a normal Codex session already open during first installation and never launches Codex while the app is closed. After you open Codex normally, a later unbridged process must remain stable before it may receive one graceful recovery restart. A global cooldown blocks further automatic recovery across replacement process IDs, preventing restart loops after crashes, power loss, or incomplete app startup.

Update by extracting the new launcher and running `install` again. The stable host identity, optional relay configuration, and user-owned icons are preserved.

Expand All @@ -56,6 +56,7 @@ Update by extracting the new launcher and running `install` again. The stable ho
host.json
watcher-state.json
watcher.log, watcher.log.1 ...
watcher.stderr.log # LaunchAgent/runtime failures
icons/ # optional user-owned SVG copies

~/Library/LaunchAgents/com.simeo.codex-deck.watcher.plist
Expand All @@ -68,6 +69,7 @@ State writes are atomic, a PID-directory lock prevents duplicate watchers, and l
```zsh
./start-codex-deck.sh dry-run
tail -n 100 "$HOME/Library/Application Support/CodexDeck/watcher.log"
tail -n 100 "$HOME/Library/Application Support/CodexDeck/watcher.stderr.log"
launchctl print "gui/$(id -u)/com.simeo.codex-deck.watcher"
plutil -lint "$HOME/Library/LaunchAgents/com.simeo.codex-deck.watcher.plist"
```
Expand Down
23 changes: 19 additions & 4 deletions docs/MULTI_HOST.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Stream Deck -> Windows plugin -> local Windows Codex
\-> authenticated relay -> local Mac Codex
```

The relay never exposes Chrome DevTools. It forwards only typed Codex Deck commands and six native agent snapshots.
The relay never exposes Chrome DevTools. It forwards only typed Codex Deck commands, six native agent snapshots, and a bounded content-free local task-presence catalog.

## Before pairing

Expand Down Expand Up @@ -40,6 +40,8 @@ On Windows, run the matching configurator and omit `-Token` so the secret is ent

The persistent Windows watcher maintains this dedicated tunnel after sign-in and reconnects it after network interruptions. It does not adopt or depend on Codex desktop's remote-CLI SSH process.

The Mac relay can remain authenticated while the Mac Codex app is closed or still loading. Snapshot failures are contained and rate-limited; they do not crash the watcher. Once the native Micro bridge is ready, snapshots resume over the existing connection.

Restart only the Stream Deck plugin or Stream Deck app after pairing. Do not restart Codex.

## Pair with Tailscale
Expand All @@ -60,17 +62,30 @@ Enter the token in the hidden prompt. The relay accepts loopback, Tailscale IPv4

## Stream Deck behavior

- The six agent keys are one globally ordered Windows+Mac list.
- The six agent keys form one mode-aware Windows+Mac list controlled by the normal Codex Micro agent-source setting.
- Each visible tile receives a small `W` or `M` badge and routes to its owning desktop.
- Add **Windows / Mac Target** to page 2. It switches action slots, joystick, encoder, reasoning, standalone keycaps, and New Task between computers.
- Agent keys ignore the selected target because each task already knows its owner.
- The selected target survives plugin and relay restarts. If Mac is selected while offline, the key visibly fails instead of silently executing on Windows.

### Agent-source modes

Single-host Windows and macOS setups preserve Codex's six native slots exactly. In multi-host mode, the Codex app on the computer running the Stream Deck plugin is the controller for the combined list:

- **Pinned:** interleaves the pinned order from Windows and Mac slot by slot, removes duplicates, and routes each task to its owning desktop. With six unique tasks available this gives both hosts up to three keys instead of allowing one full list to hide the other.
- **Recently updated:** globally orders actual Windows and Mac activity.
- **Priority:** ranks approval/questions first, then unread/errors, active work, and idle tasks.
- **Individual assignments:** preserves the controller's slot positions. If both apps assign different tasks to one physical button, the controller assignment wins. When the controller slot is empty and the remote Codex app is also set to Individual assignments, its assignment from the same slot is used. The same task assigned through both apps is shown only once and routed to its rollout owner.

For a true combined Pinned or Individual list, select that mode in both Codex apps. If the modes differ, the controller mode still determines the list, but only a remote host using the matching mode can contribute its own pinned order or individual assignments. Codex Deck writes a warning to the plugin log when this happens.

This makes manual mixed layouts possible without another settings application. Assign a synced task directly in the controller Codex app when it is available there. For a Mac-only task that is not selectable on Windows, leave that Windows slot empty and assign the task to the same slot in the Mac Codex Micro settings. Changes are picked up automatically by the next native snapshot.

### Ownership and SSH mirrors

Codex's built-in remote-SSH feature can mirror a Mac-backed task into the Windows renderer. Codex Deck does not confuse that CLI connection with the Mac desktop app. In multi-host mode it compares exact local rollout **filenames** on both hosts to find the owning desktop; it never reads rollout contents, prompts, responses, or project names.
Codex's built-in remote-SSH feature can mirror a task into the other renderer. Codex Deck does not confuse that CLI connection with the owning desktop. In multi-host mode it compares exact local rollout **filenames** on both hosts and checks only bounded tail text for structural `agent_reasoning`, `function_call`, `turn_context`, and `task_complete` event tags. Prompt text, responses, project names, and other rollout content are neither parsed nor sent through the relay.

For the same cloud task visible on both hosts, live status and selection are merged while commands route to the rollout owner. Ownership is host-generic and contains no hard-coded task IDs or project names.
The relay catalog contains only recent task UUIDs, modification times, and derived `working`, `complete`, or `idle` state. For the same cloud task visible on both hosts, live status and selection are merged while commands route to the rollout owner—even when Codex temporarily omits the task from that owner's six native Micro slots. Ownership is host-generic and contains no hard-coded task IDs or project names.

### Ordering boundary

Expand Down
13 changes: 12 additions & 1 deletion docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ tail -n 100 "$HOME/Library/Application Support/CodexDeck/watcher.log"

Do not replace, re-sign, or edit the Codex app bundle. If `start` says an existing normal session needs a restart, it waits for your explicit `yes`.

The installed watcher never launches Codex while it is closed. After you open Codex manually, one controlled recovery restart can occur if the new process lacks the loopback bridge. A global cooldown prevents further automatic restarts across replacement PIDs.

If an older watcher is repeatedly relaunching Codex after a crash or empty battery, stop only that watcher first:

```zsh
launchctl bootout "gui/$(id -u)/com.simeo.codex-deck.watcher"
```

Then install the launcher from the newest release. This command does not start, stop, or modify Codex itself.

## Agent keys say Bridge offline

- Confirm Codex was started through the launcher.
Expand All @@ -45,7 +55,7 @@ The native handler was unavailable or the action is not valid in the current com

## Agent assignments are unexpected

Codex Deck does not choose the six native tasks. Open **Codex Settings > Codex Micro > Agent keys** and select pinned, recently updated, priority, or custom assignments. In multi-host mode, both native six-slot lists are de-duplicated and globally ordered; mirrored tasks route to the host owning the exact local rollout filename.
Codex Deck does not choose the six native tasks. Open **Codex Settings > Codex Micro > Agent keys** and select pinned, recently updated, priority, or custom assignments. For combined Pinned or Individual assignments, select the same mode in both Codex apps. Pinned tasks are interleaved between hosts; in Individual mode the Stream Deck computer wins a conflicting slot and the remote host fills empty slots. Both lists are de-duplicated, and mirrored tasks route to the host owning the exact local rollout filename.

## Local command icon does not appear

Expand All @@ -63,6 +73,7 @@ Restart Stream Deck. Elgato notes that plugins can fail to appear when the Strea
- First confirm both local bridges work independently.
- SSH mode: confirm the Windows watcher is installed and the SSH alias works outside Codex's remote-CLI connection.
- Inspect `%LOCALAPPDATA%\CodexDeck\watcher.log` for the dedicated relay tunnel state.
- On macOS, inspect both `watcher.log` and `watcher.stderr.log` under `~/Library/Application Support/CodexDeck/`.
- Confirm the Windows relay URL is `ws://127.0.0.1:<port>` for SSH, or an explicit Tailscale address.
- Restart only the Stream Deck plugin/app after configuration. Do not restart Codex.
- Run `Configure-CodexDeckRelay.ps1 -Disable` to return cleanly to Windows-only mode.
Expand Down
Loading
Loading