Commit d0addd9
feat: add mcodex launcher statusline (#500)
* feat: add mcodex launcher statusline
* Improve mcodex tmux scrollback
* Route wheel scroll to tmux history
* fix(mcodex): close review findings on the launcher + statusline
Security:
- scripts/mcodex: validate MCODEX_MONITOR_INTERVAL (and MCODEX_TMUX_HISTORY_LIMIT)
as numeric before they are interpolated into the `watch -n <n> ...` / tmux
option strings that tmux hands to a shell. A value like "5; rm -rf ~" or
"$(cmd)" was previously executed verbatim; it is now rejected and replaced with
the safe default. Closes the command-injection findings on lines 53/68.
Correctness:
- scripts/codex.js maybeRefreshQuotaCacheInBackground: the status-refresh child's
close/error handlers called the Atomics.wait-backed removeDirectoryWithRetry,
blocking the PARENT event loop up to ~200ms on a transient Windows lock while
Codex was running. Added removeDirectoryBestEffortAsync (fsPromises.rm with
internal retry, no blocking sleep) and use it in those callbacks.
- scripts/codex.js formatStatusPath: the `~` abbreviation used a hardcoded "/"
separator, so on Windows (backslash paths from resolvePath) the cwd was never
abbreviated. Use path.sep for the boundary check and normalize the remainder to
forward slashes for a stable status line.
mcodex polish:
- quote_args no longer emits a trailing space (avoids an empty trailing token in
the interpolated tmux command).
- the tmux new-session command is built in a variable from printf %q-escaped args
so arguments with embedded quotes/spaces are passed safely.
- --monitor no longer forwards unused "$@"; configure_tmux_scrollback redirects
stderr too for consistent silence.
Docs / known-race notes:
- documented the bounded TOCTOU in the stale-lock recovery (a slow-but-alive
refresh owner can be evicted, briefly yielding two idempotent refresh children)
and the detached-child caveat (close/error cleanup may not fire if the parent
exits first; 10-minute stale-lock recovery reclaims the lock).
Tests:
- test/documentation.test.ts: add `mcodex` to the canonical bin surface the PR
introduced (the assertion was left stale by the PR and was failing).
- test/mcodex-launcher.test.ts (new): regression for the interval-injection
guard — valid integer/fractional pass through; injection payloads and shell
substitutions collapse to the safe default; asserts the shipped script still
contains the numeric guard. Skips where bash is unavailable.
typecheck + lint clean; full suite 4067 passed.
* fix(mcodex): status line reads the per-project account pool
P1 (Greptile): the forwarded status line resolved accounts via the global
`resolveAccountsPath` (~/.codex/multi-auth/openai-codex-accounts.json), so inside
a project with a per-project pool it either showed no status line (global pool
empty) or showed the wrong account/quota — silently contradicting the account
Codex actually routes through.
Fix: add `resolveStatusAccountsDir`, which mirrors the runtime's own account
scoping (lib/runtime/account-scope.ts) by reusing the built dist helpers (never
re-deriving project keys from raw paths, per AGENTS.md):
- perProjectAccounts enabled + Codex CLI sync OFF + cwd resolves to a project
root → per-project pool via getProjectGlobalConfigDir(
resolveProjectStorageIdentityRoot(projectRoot));
- otherwise (no project, sync on, config off, or dist unavailable) → global.
Only the ACCOUNTS pool is per-project; quota-cache.json and
runtime-observability.json stay global (lib: getCodexMultiAuthDir), so those
reads are unchanged. `maybePrintForwardStatusLine` is now async to resolve the
dir before formatting; any failure falls back to the global dir so the launcher
never breaks.
Note: the per-project pool is grounded in the dist config dir, which itself
honors CODEX_MULTI_AUTH_DIR / CODEX_HOME — so no special-casing of those envs
here, or the status line would diverge from where the runtime writes.
test/mcodex-statusline-scope.test.ts: end-to-end regressions over the real
wrapper + dist build — per-project pool is shown inside a project, global pool
outside one, and global when Codex CLI sync is enabled (matching account-scope).
typecheck + lint clean; full suite 4070 passed.
* fix(mcodex): gate monitor modes on `watch` + assert bin publishing
Follow-up review findings:
- scripts/mcodex (Major): `watch` was invoked unconditionally in run_monitor and
the two tmux live-account panes, with no equivalent of the existing `tmux`
guard. Added require_watch; --monitor now fails fast with an actionable error
and propagates a non-zero exit (exit $? instead of exit 0), and each live pane
is gated on `&& require_watch` so a missing `watch` skips the pane instead of
spawning a broken one (Codex still launches).
- test/documentation.test.ts (Minor): the bin assertion locked the new `mcodex`
entry but nothing verified package.json files[] publishes the shim, so npm
could ship a bin pointing at a missing script while the test passed. Now every
declared bin target is asserted present in files[].
Tests: test/mcodex-launcher.test.ts gains a missing-`watch` runtime guard case
(drives require_watch with `watch` shadowed absent → non-zero + clear error) and
a static check that all three watch sites are wired through require_watch.
typecheck + lint clean; full suite 4072 passed.
---------
Co-authored-by: Neil Daquioag <devzeian@gmail.com>1 parent d930ac8 commit d0addd9
6 files changed
Lines changed: 933 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
114 | 114 | | |
115 | 115 | | |
116 | 116 | | |
| 117 | + | |
117 | 118 | | |
118 | 119 | | |
119 | 120 | | |
| |||
128 | 129 | | |
129 | 130 | | |
130 | 131 | | |
| 132 | + | |
131 | 133 | | |
132 | 134 | | |
133 | 135 | | |
| |||
0 commit comments