Skip to content

Add thin Windows PowerShell launcher#128

Merged
fujibee merged 5 commits into
fujibee:mainfrom
lucianlamp:codex/windows-followup-simplify-e2e
Jun 17, 2026
Merged

Add thin Windows PowerShell launcher#128
fujibee merged 5 commits into
fujibee:mainfrom
lucianlamp:codex/windows-followup-simplify-e2e

Conversation

@lucianlamp

@lucianlamp lucianlamp commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Windows PowerShell users currently depend on session-local aliases/functions or ad hoc wrappers. This adds a thin PowerShell launcher that delegates to the existing Bash scripts, preserving agmsg's single implementation and DB access boundary while making Windows invocation reliable.

This PR also folds in the Codex sandbox README note so the Windows follow-up and README-only update land together.

What changed

  • Add scripts/dispatch.sh as the Bash-side command dispatcher for inbox, send, history, team, config, mode, join, reset, actas, and drop.
  • Keep scripts/windows/agmsg.ps1 as a thin Windows platform shim: Git Bash detection, UTF-8 setup, sqlite3 preflight, argv handoff, then dispatch.sh.
  • Remove the previous Windows runner/sqlite3-shim approach and clean up legacy shim files only when they match the agmsg marker.
  • Keep PowerShell integration under the skill tree: install.sh no longer writes a top-level ~/.agents/<cmd>.ps1; users can enable a profile function via scripts/windows/install-agmsg.ps1.
  • Add optional PowerShell profile installer docs and Windows usage docs.
  • Add Codex sandbox writable-roots guidance to README.
  • Fix install.sh --cmd <name> --update so it updates the named skill instead of the first .agmsg skill directory found.
  • Deflake the watcher self-clean install smoke by waiting for the pidfile owner to change instead of relying on a fixed short sleep.

Test plan

Local checks run on Windows:

  • git diff --check
  • bash -n install.sh uninstall.sh scripts/delivery.sh scripts/dispatch.sh scripts/send.sh scripts/inbox.sh scripts/history.sh scripts/team.sh scripts/join.sh scripts/reset.sh scripts/whoami.sh scripts/identities.sh
  • PowerShell parser check for scripts/windows/agmsg.ps1 and scripts/windows/install-agmsg.ps1
  • npm test
  • powershell -NoProfile -ExecutionPolicy Bypass -File tests/smoke_windows_powershell.ps1 -RepoRoot (Resolve-Path .).Path
  • Manual Git Bash dispatcher smoke covering explicit/env/whoami identity, multiple identity refusal, Japanese + quotes + emoji send/history, and mode off / mode turn
  • Manual install/update cleanup smoke, including no top-level ~/.agents/<cmd>.ps1 creation and cleanup of the earlier generated shortcut
  • Manual uninstall cleanup smoke
  • Real installed PowerShell E2E using a profile-managed agmsg function that points at ~/.agents/skills/agmsg/scripts/windows/agmsg.ps1: join -> send -> history -> inbox -> env identity -> whoami identity -> mode off/turn -> team -> reset

CI is green on the latest head:

  • check: pass
  • bats (ubuntu-latest): pass
  • bats (macos-latest): pass

@lucianlamp

Copy link
Copy Markdown
Contributor Author

CI is green on the latest head (6a59dcd):

  • check: pass
  • bats (ubuntu-latest): pass
  • bats (macos-latest): pass

I also ran a real native Windows PowerShell E2E after installing this branch into ~/.agents/skills/agmsg with install.sh --cmd agmsg --update and dot-sourcing ~/.agents/agmsg.ps1.

Covered flow:

join -> send -> history -> inbox -> env identity -> whoami identity -> mode off/turn -> team -> reset

The message body included Japanese text, quotes, and emoji (実機E2E 確認しました "quoted" emoji 🚀) and was preserved through history / inbox. The E2E used a temporary AGMSG_STORAGE_PATH and cleaned up the temporary team with reset.

@lucianlamp

Copy link
Copy Markdown
Contributor Author

One design note compared with #103:

This follow-up intentionally simplifies the native-Windows approach from the previous PR. Instead of keeping a Windows runner script plus a sqlite3 compatibility shim, the current version keeps Windows-specific code to a thin PowerShell platform shim:

  • detect Git Bash
  • set UTF-8-oriented process settings
  • preflight sqlite3 from Git Bash
  • preserve argv across the PowerShell -> Git Bash boundary
  • delegate into the Bash-side scripts/dispatch.sh

All command semantics and DB access stay on the Bash side (dispatch.sh -> existing scripts/*.sh). The Git Bash installer also no longer writes a top-level ~/.agents/<cmd>.ps1; PowerShell integration is enabled through the profile installer under the skill tree (scripts/windows/install-agmsg.ps1).

So compared with #103, this PR removes the runner/shim layer and makes the Windows path smaller: PowerShell is only the launcher, Bash remains the implementation.

@fujibee

fujibee commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Reviewed — this looks good. The design is clean: keeping all command semantics in dispatch.sh so the PowerShell side is a thin platform shim (Git Bash detection, UTF-8, sqlite3 preflight, then delegate) preserves agmsg's single-implementation boundary, and the base64 + file argv handoff is the right way to get spaces / quotes / Japanese / emoji across the PowerShell→Bash boundary without multi-layer shell re-parsing. The marker-guarded cleanup of the legacy shim/shortcut (only removing files that match the agmsg marker) and the --cmd <name> --update fix (targeting the named skill instead of the first .agmsg dir) are both correct improvements. The #124 deflake bundled in is a nice extra.

CI is green (check + bats ubuntu/macos), and the manual Windows E2E in the test plan is thorough. Approving.

Two non-blocking notes for follow-up:

  1. Second command surface. dispatch.sh now routes commands in parallel with the per-agent skill templates (cmd.*.md). Adding a future command means updating both, so they can drift. Not a blocker — a reasonable cost for giving Windows/shell users a real CLI — but worth keeping in mind, or eventually having the templates reference dispatch where practical.

  2. PowerShell side isn't covered by CI. scripts/windows/agmsg.ps1 and tests/smoke_windows_powershell.ps1 only run via the manual E2E here — there's no windows-latest runner yet (CI: add Windows (windows-latest) coverage for the native Windows helpers #125). The Bash/dispatch side is CI-covered; landing CI: add Windows (windows-latest) coverage for the native Windows helpers #125 would lock the PowerShell path in too so it can't regress silently.

Thanks for the thorough test plan.

@fujibee fujibee merged commit 9c48d0f into fujibee:main Jun 17, 2026
3 checks passed
@fujibee fujibee mentioned this pull request Jun 17, 2026
fujibee added a commit that referenced this pull request Jun 18, 2026
Build the full Codex pseudo-monitor on top of the re-arm fix, ported clean onto
current main (despawn + instance-id already landed).

New scripts:
- codex-shim.sh: PATH-front entrypoint that routes interactive codex / codex
  resume launches in monitor-mode projects through codex-monitor.sh; everything
  else (--version, exec, non-monitor projects) passes through to real Codex.
- codex-monitor.sh: starts/reuses the shared app-server, launches the
  out-of-sandbox bridge launcher, then execs the TUI on the same unix socket.
- codex-bridge-launcher.sh: polls the request file and starts codex-bridge.js
  outside the Codex sandbox (a hook-launched bridge cannot connect to the
  socket from inside the sandbox).
- codex-shim-install.sh: installs/removes the ~/.agents/bin/codex shim.

Wiring grafted onto main's versions (preserving #129/#132/#128):
- session-start.sh: codex branch writes a request file (launcher mode) or
  nohup-launches the bridge. Resolves the thread id via CODEX_THREAD_ID, then
  falls back to the newest rollout whose session_meta cwd matches the project —
  fresh / "codex exec" sessions never export CODEX_THREAD_ID, so without this
  the bridge only auto-launched on the interactive resume path (launch-gap #41).
- delivery.sh: "set monitor" for codex installs the shim + prints guidance;
  rejects "both".
- install.sh: configure_codex_sandbox adds db/teams/run to Codex
  sandbox_workspace_write.writable_roots.

Tests: codex shim, session-start codex (incl. rollout fallback), delivery
monitor/both codex, install writable_roots. Full suite 314 green.
fujibee added a commit that referenced this pull request Jun 18, 2026
… + fresh-session launch (#41) (#148)

* feat(codex-bridge): re-arm watch-once without depending on turn/completed (#41)

Vertical slice of the Codex app-server bridge, rebuilt clean on current main
(despawn + instance-id already landed). Carries the working WebSocket-over-UDS
transport and watch-once oracle, and fixes the delivery bug.

Root cause: the bridge re-armed watch-once only from onTurnCompleted
(turn/completed | turn/failed). The real Codex app-server does not reliably
deliver those, so after the first wake the bridge started a turn and never
re-armed — it handled one message then slept. The fake app-server in tests
sends turn/completed explicitly, hiding it.

Fix: route turn teardown through a single onTurnEnded() reachable from
turn/completed, thread/status idle, OR a turn watchdog (--turn-timeout, default
60s). Detection re-arms when the turn ends, so a missing completion event no
longer parks the bridge. The stale max_id guard is retained.

Tests: two regressions where the fake never sends turn/completed — one driven
by the watchdog, one by an idle status — asserting a second wakeup occurs.
Full codex-bridge + watch-once suites green (12 + 6).

* feat(codex-bridge): wire up the Codex monitor stack on main (#41)

Build the full Codex pseudo-monitor on top of the re-arm fix, ported clean onto
current main (despawn + instance-id already landed).

New scripts:
- codex-shim.sh: PATH-front entrypoint that routes interactive codex / codex
  resume launches in monitor-mode projects through codex-monitor.sh; everything
  else (--version, exec, non-monitor projects) passes through to real Codex.
- codex-monitor.sh: starts/reuses the shared app-server, launches the
  out-of-sandbox bridge launcher, then execs the TUI on the same unix socket.
- codex-bridge-launcher.sh: polls the request file and starts codex-bridge.js
  outside the Codex sandbox (a hook-launched bridge cannot connect to the
  socket from inside the sandbox).
- codex-shim-install.sh: installs/removes the ~/.agents/bin/codex shim.

Wiring grafted onto main's versions (preserving #129/#132/#128):
- session-start.sh: codex branch writes a request file (launcher mode) or
  nohup-launches the bridge. Resolves the thread id via CODEX_THREAD_ID, then
  falls back to the newest rollout whose session_meta cwd matches the project —
  fresh / "codex exec" sessions never export CODEX_THREAD_ID, so without this
  the bridge only auto-launched on the interactive resume path (launch-gap #41).
- delivery.sh: "set monitor" for codex installs the shim + prints guidance;
  rejects "both".
- install.sh: configure_codex_sandbox adds db/teams/run to Codex
  sandbox_workspace_write.writable_roots.

Tests: codex shim, session-start codex (incl. rollout fallback), delivery
monitor/both codex, install writable_roots. Full suite 314 green.

* docs(codex-bridge): document the Codex monitor beta (#41)

- templates/cmd.codex.md: offer monitor as a delivery mode for codex (the
  app-server bridge beta), default to it on first join, and accept "mode
  monitor"; keep rejecting "both".
- README.md: describe the Codex monitor beta in the Codex section and the
  intro, pointing at docs/codex-monitor-beta.md for setup and internals.

* test(codex): sandbox HOME in setup_test_env so the suite never touches real state (#41)

Running the bats suite was clobbering the developer's real shim: a couple of
tests install the codex shim (delivery set monitor codex -> codex-shim-install
writes $HOME/.agents/bin/codex) and configure $HOME/.codex/config.toml, but
setup_test_env did not sandbox HOME. The shim ended up pointing at the test's
temp scripts dir, which dangled once the temp dir was torn down. CI never caught
it (clean containers have no real ~/.agents to break).

Sandbox HOME under the per-test temp skill dir in setup_test_env. bats runs each
test in its own subshell, so the export is scoped per test and needs no restore.
test_install.bats keeps its own FAKE_HOME and is unaffected.

* feat(codex): flag missing PATH / Node when enabling monitor mode (#41)

The two silent first-run failures for the Codex monitor beta were a shim that
isn't on PATH and a missing Node — in both cases the bridge just never starts.
`delivery set monitor codex` now surfaces them at enable time:

- If ~/.agents/bin is not on PATH, print a loud WARNING that the shim won't
  intercept `codex` and the bridge will not engage (this is the #1 cause of
  "I turned monitor on and nothing happens").
- If Node is missing, print a WARNING that the bridge needs Node. Presence only,
  no version gate: the bridge uses old/stable APIs (one optional-chaining call,
  Node 14+) and any Node new enough to run Codex runs it. AGMSG_CODEX_NODE
  overrides the checked binary (and makes the path testable).

Tests for both warnings.

* docs(codex-bridge): fix the flow diagram — hook writes a request file, launcher starts the bridge (#41)

The diagram and prose claimed the SessionStart hook starts codex-bridge.js
directly. That is the broken in-sandbox path (socket connect EPERM). Correct it:
the hook resolves the thread (CODEX_THREAD_ID or newest matching rollout) and
writes a request file; codex-monitor.sh's out-of-sandbox launcher reads it and
starts the bridge, which connects over WebSocket-over-UDS. Add the re-arm loop
and turn serialization.

* feat(codex): emphasize the monitor beta and clean up on mode off (#41)

Make the experimental nature and the PATH cost of the Codex monitor beta hard to
miss, and make turning it off actually tear things down:

- cmd.codex.md: move monitor to the LAST delivery option (3, "BETA, advanced"),
  default to turn, and spell out that it installs a PATH shim that intercepts
  the `codex` command — opt in only if you understand PATH precedence.
- README + docs/codex-monitor-beta.md: prominent warning blocks (changes how
  Codex starts, ~/.agents/bin must be first on PATH, known rough edges #149/#150).
- delivery.sh: `set off codex` now stops the project's bridge process(es) and
  removes their run files (the manual counterpart to the not-yet-wired auto
  teardown, #149), and tells the user the shim is global — how to remove it and
  restore PATH if no other project uses monitor. Leaves the shared app-server
  and shim in place. Test included.

* feat(codex): tell users to restart Codex for monitor to take effect (#151)

Enabling mode monitor only launches the bridge on the next Codex SessionStart, so
an already-running session (or an off->on toggle) stays unmonitored until restart.
delivery.sh, the cmd.codex.md prompt guidance, and docs now say so explicitly.
The actual in-session (re)launch is tracked in #151.

* docs(codex): clarify bridge starts on first turn, not at launch

Codex fires the SessionStart hook on the session's first turn (first
message), not the moment the TUI opens, so the monitor bridge is absent
until the user interacts once after a restart. Update the delivery.sh
enable message, the cmd.codex.md prompts, and the beta doc to say
"restart and send your first message". Note the launcher/request-file
redundancy review (#153) in the bridge mechanics.

* feat(codex): lower monitor poll interval default from 5s to 2s

The watch-once poll interval governs how fast an idle, armed bridge
detects a new unread message. Drop the default from 5s to 2s for
snappier delivery. Safe against double-injection: watch-once is one-shot
(exits on first detection), re-arm only happens after the turn ends, and
the stale-max_id guard backstops any re-detection — none of which the
interval affects. Update both the bridge default and the watch-once.sh
standalone fallback to keep them in sync.

* fix(codex): deliver deferred wake on turn end; de-dup Codex writable_roots

Two blocking issues from review:

1) codex-bridge.js: a wake observed while the resumed thread was still active
   (SessionStart fires on the first user turn) was deferred by tryStartTurn(),
   but onTurnEnded() re-armed instead of delivering it. The next watch-once
   re-observed the same unread max_id and the stale-wake guard stopped the
   bridge with exit 1 before the message was delivered. onTurnEnded() now
   delivers a pending wake via tryStartTurn() rather than re-arming.

2) install.sh: Codex writable_roots was configured by TWO copies — a legacy
   inline block (db/teams only, old closing-bracket awk) and configure_codex_
   sandbox() (db/teams/run). On a fresh install both ran, and the inline copy's
   empty-array handling emitted a leading comma, which combined with the
   function's insert to produce invalid TOML (`["run", , "db", "teams"]`).
   Removed the legacy inline duplicate and rewrote the function to insert after
   the opening '[' — valid for empty/single-line/multiline arrays.

Adds regression tests for both (the bridge test fails without the fix).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants