100 KiB
aicodeman
1.1.15
Patch Changes
-
Security: harden all frontend inline
onclick/ondblclickhandlers against a stored-XSS double-context bug.Many inline handlers interpolated values as
'${escapeHtml(value)}'— a JavaScript string literal sitting inside an HTML attribute. The browser HTML-decodes the attribute value before parsing the handler source, soescapeHtml's'reverts to a literal'and a quote-bearing id/name/path/URL breaks out of the JS string into executable code.escapeHtmlalone is insufficient for this JS-string-within-HTML-attribute context.All affected handlers now use
escapeHtml(JSON.stringify(value)):JSON.stringifyJS-encodes and quote-wraps the value, thenescapeHtmlhandles the HTML-attribute layer, so the value round-trips as a single inert string argument.- ultracode run/agent cards and minimized-tab badges (
ultracode-panel.js,ultracode-windows.js) — PR #132. - Session tabs (click/rename/gear/detach/close), notifications, subagent windows + dropdowns, the agents/tools/log-viewer/image-popup panels, mux-session monitor rows, and case-management buttons (
app.js,notification-manager.js,subagent-windows.js,panels-ui.js,session-ui.js). - Two non-
escapeHtmlvariants of the same class: a pre-escaped mux-session id inpanels-ui.js(selectSession/killMuxSession) and a fully raw, unescapedphase.idinorchestrator-panel.js(orchestratorSkipPhase/orchestratorRetryPhase).
The most realistic exploitation vector was file paths in the project-insights log-viewer link, since filenames can legally contain a single quote. Purely numeric interpolations and developer-literal handler strings were left unchanged.
- ultracode run/agent cards and minimized-tab badges (
1.1.14
Patch Changes
- Ultracode (Workflow-tool) floating windows — agent transcripts in-page, and minimize-to-tab.
- Agent transcripts open in-page, connected, instead of a detached browser popup. Clicking an agent card (in a run window or the dock panel) now opens the agent's live transcript as its own draggable floating window, tied by a connector line to its parent run window (falling back to the run's session tab if that window has since closed) — the same line idiom the run windows use. Re-clicking a card focuses the existing window; closing it removes the window and its line. (Previously this spawned a separate
window.openbrowser popup.) - The window "−" button now minimizes into the originating session tab, mirroring the subagent-window idiom. The window genie-animates into its tab and is tracked there; the tab shows an
ULTRAbadge whose hover/click dropdown lists each minimized item (🧬 run windows, 📄 agent transcripts). Click an item to restore its floating window, or dismiss it with ×. A run minimized while still active keeps tracking in the background and its badge auto-clears shortly after the run finishes. Both run windows and agent-transcript windows minimize into the same merged badge. - Removed the old collapse-to-header behavior that the "−" button previously triggered (now superseded by minimize-to-tab).
- Agent transcripts open in-page, connected, instead of a detached browser popup. Clicking an agent card (in a run window or the dock panel) now opens the agent's live transcript as its own draggable floating window, tied by a connector line to its parent run window (falling back to the run's session tab if that window has since closed) — the same line idiom the run windows use. Re-clicking a card focuses the existing window; closing it removes the window and its line. (Previously this spawned a separate
1.1.13
Patch Changes
- Keep the
/compactbutton in the extended (full) mobile keyboard accessory bar; only the simple bar drops it. (1.1.12 had removed it from both.)
1.1.12
Patch Changes
- Remove the
/compactbutton from the mobile keyboard accessory bar. It had been reintroduced in 1.1.10; this removes the button from both the simple and full accessory-bar layouts (the underlying command handler is left in place as inert plumbing).
1.1.11
Patch Changes
-
Ultracode (Workflow-tool) run visualization — much better live tracking.
While a run is in flight, the watcher previously showed empty agent slots ("agent N", 0 tokens, raw
wf_…id as the title) because the detailed completion JSON only lands when the run finishes. The live path now enriches in-flight runs directly from the on-disk transcript tree:- Real per-agent stats mid-run — tokens and tool-call counts are parsed from each
agent-<id>.jsonltranscript (tool counts match the final accounting exactly; token totals land within ~1% of the completion value), with model and a prompt preview. All mtime-cached (transcripts, journal, and script meta) so idle polls do no extra reads. - Readable window/run title — workflow name, summary, and phases are derived from the persisted
workflows/scripts/<name>-<runId>.jsinstead of showing the raw run id. - Agent status colors — done agents show green, working agents show yellow (this also fixes the run/agent status badges, which referenced undefined
--success/--warningCSS variables and were rendering with no color). - Connector line — the floating-window → session-tab line now uses the session-tab accent blue (was purple).
- Click a run to open its floating window — clicking a workflow in the dock panel opens (or focuses) its floating window with the connector line, in addition to the auto-popped windows.
- Agents are ordered by journal launch order; concurrent run-detail fetches are de-duplicated.
- Real per-agent stats mid-run — tokens and tool-call counts are parsed from each
1.1.10
Patch Changes
-
Mobile CJK input, iPad keyboard accessory bar, and terminal touch interaction fixes (PRs #130, #131).
Mobile / CJK (#130):
- Restore reliable real-time CJK (e.g. Pinyin) composition in the always-visible textarea, and refocus input when the terminal is tapped.
- Stop clearing the textarea during
compositionstart— some IMEs include existing text in the composition region, and clearing it mid-composition corrupted input. - iPad-specific fixes:
#cjkInputpositioning, paste-dialog placement, and duplicated voice-dictation output. - Split CJK keyboard positioning by device size (phones vs iPad use different keyboard offsets).
- iPad accessory-bar styling/positioning: moved the accessory-bar and paste-overlay base styles out of the
max-width:1023px-gated mobile stylesheet so iPad landscape (≥1024px) renders them correctly. - Raise the toolbar stacking context while the case-settings popover is open so the popover is no longer hidden behind the toolbar.
- Restore the
/compactbutton to the keyboard accessory bar (with double-tap confirmation, like/clear); the paste dialog now submits pasted text on "Send".
Terminal touch + forced redraw (#131):
- Enable terminal touch interaction on all touch devices and show the stop button on touch devices.
- Add an 8px tap threshold so micro-drift is treated as a tap, not a scroll, fixing cases where a tap failed to register.
- Tap-to-position the cursor via a synthesized mouse report, gated on the live mouse-tracking mode so it never triggers local text selection when tracking is off; let SGR mouse reports through to the PTY even while the CJK input field owns focus.
- Suppress the cursor/momentum side effects of a sub-threshold tap so a jittery tap no longer both positions the cursor and starts a momentum fling.
- New opt-in, per-device "Redraw Terminal" header button (
showRedrawButton, default off) that forces an xterm redraw via a resize jitter to clear occasional rendering glitches; the resize path now accepts aforceflag (threaded through the session, HTTP, and WebSocket resize routes) that guarantees a SIGWINCH/redraw at the current device's size without bypassing multi-client resize arbitration.
1.1.9
Patch Changes
- Two welcome-screen tunnel changes:
- UI (Daylight Blue skin): the Cloudflare Tunnel button is now purple (was orange/yellow), keeping the three welcome buttons visually distinct — Claude blue, Tunnel purple, OpenCode green.
- Enable a tunnel without
CODEMAN_PASSWORD, with a warning. Previously enabling the Cloudflare tunnel with no password set was hard-refused unless you setCODEMAN_ALLOW_UNAUTHENTICATED_NETWORK=1. Now you can opt in straight from the browser: clicking the tunnel toggle without a password pops a security confirm dialog ("publishes this machine to a public URL with no login — effectively remote code execution; set CODEMAN_PASSWORD instead"), and only on confirm does it enable, sending an explicit per-requestacknowledgeUnauthTunnel:true. The server logs a loud warning whenever a passwordless public tunnel starts. curl/API/CLI callers are unchanged — still refused unless they set a password, set the env var, or passacknowledgeUnauthTunnel:true— so nothing gets exposed accidentally. The acknowledgment is an action field and is never persisted to settings.json.
1.1.8
Patch Changes
- UI (Daylight Blue skin): give the welcome-screen action buttons distinct colors instead of all reading blue. Run Claude Code keeps the blue accent, Cloudflare Tunnel now uses Cloudflare's brand orange, and Run OpenCode uses an emerald green — so the three are visually distinguishable at a glance. Scoped to the default
daylight-blueskin only (daylight-green and OG are unchanged), with matching hover/active states and dark ink for contrast. Verified in a real browser: the three buttons compute to blue / orange / green gradients on the welcome overlay.
1.1.7
Patch Changes
-
Fix: terminal scroll-up (scrollback) intermittently breaking for Claude sessions — most visible on iPhone, where you suddenly "can't scroll up the Claude console."
Root cause: Claude Code periodically emits alternate-screen switches (
\x1b[?1049h/\x1b[?47h/\x1b[?1047h), scrollback-erase (\x1b[3J), and mouse-tracking enables — typically when it draws a full-screen UI (pickers/dialogs, the boot welcome). xterm.js obeys these by moving to the scrollback-less alternate buffer (or wiping saved lines / hijacking the wheel), so the conversation history becomes unreachable until Claude returns to its normal view. Codeman already stripped these sequences so history stays scrollable, but the strip was gated to Codex mode only — Claude (and the equivalent buffer-replay path) let them through.The strip is now shared via a single
isAltScreenStripMode(mode)predicate (codex || claude) applied at BOTH sites that were Codex-only: the live PTY stream (Session._handleTerminalOutput, including the split-across-chunks carry reassembly) and the/terminalbuffer replay used on tab-switch/reconnect.shellis deliberately excluded so full-screen TUIs run from a shell (vim/less/htop) keep their alternate screen;opencodeis also unchanged.Verified end-to-end on an isolated instance against a real Claude session: the replayed buffer and live stream now carry zero alt-screen/scrollback-erase/mouse sequences, the terminal stays in the normal buffer with scrollback intact, and touch swipe-up scrolls correctly. Covered by new unit tests (
test/claude-scrollback-strip.test.ts); the existing Codex strip tests are unchanged.
1.1.6
Patch Changes
-
Fix: ultracode floating run windows now pop on a fresh device/browser that loads while a run is already active.
ultracodeFloatingWindowssyncs from the server (it's a non-display setting), but on a first-time device the SSEgetLightStaterun snapshot can seed the run list BEFORE the async settings load resolves — so the floating-window gate readfalseat that instant and skipped any already-active run, leaving the window un-popped until the next ~10s watcher tick. The app now re-runssyncAllUltracodeFloatingWindows()once server settings finish loading (in theloadAppSettingsFromServer().then()callback), so an in-flight run pops its window immediately. Idempotent: open windows are left as-is, and if the setting is off any premature windows are torn down. Verified end-to-end against a real in-flight run on an isolated instance — a pristine browser (empty localStorage) seeds the setting from the server and pops the active run's window ~0.4s after first paint.Also corrected a stale
@fileoverviewcomment inultracode-windows.jsthat claimed the floating windows are gated onshowUltracodeAgents; they are gated on the dedicatedultracodeFloatingWindowstoggle (only the docked "Ultracode Agents" panel usesshowUltracodeAgents).
1.1.5
Patch Changes
-
Fix: the Ultracode Agents panel's (×) Close button now fully hides the panel.
closeUltracodeAgentsPanel()only removed theopenclass, which drops the bottom-docked drawer to its collapsed peek state (the 36px header strip stays visible) rather than closing it — so clicking (×) looked like it did nothing. It now also adds thehiddenclass (display:none), mirroringcloseSubagentsPanel(). It deliberately does NOT flip theshowUltracodeAgentssetting (that also gates the run watcher and floating windows); the header launcher button reopens the panel. Verified in a real browser: after (×) the panel computesdisplay:none.
1.1.4
Patch Changes
-
Fix: ultracode floating run windows (and the live dock panel) now appear DURING an in-flight Workflow/ultracode run, not only after it finishes.
The Workflow runtime writes the run-state file
…/workflows/wf_<id>.jsononly at completion (always a terminal status); while a run is live, its only on-disk state is the sibling…/subagents/workflows/wf_<id>/transcript tree.workflow-run-watcherpreviously scanned only the completion file, so it never observed a run until it was already terminal — and the floating-window auto-pop is gated on an ACTIVE run, so it never fired for a live run (the feature was effectively dead for in-flight runs).The watcher now ALSO scans the
subagents/workflows/wf_<id>/transcript tree and synthesizes a minimal ACTIVE run (statusrunning, agent slots keyed by theiragentIdso the agent-card → live-transcript click still works,lastActivityAtfrom the newest agent/journal mtime, per-agent done/running derived from the run journal'sresultevents) when no completion file exists yet. When the run finishes, the realwf_<id>.jsonsupersedes the synthesized record (same runId), restoring full phase/token detail and the normal finish → 8s-grace auto-close flow. The watcher stays standalone (it never imports subagent-watcher). Verified end-to-end against a real in-flight run; adds unit coverage for live synthesis, agentId preservation, journal-derived state, empty-dir skipping, and completion-file precedence.
1.1.3
Patch Changes
- Ultracode floating run windows + a dedicated toggle to control them.
- New: floating ultracode run windows. When enabled, each active ultracode / Workflow run pops a small draggable window (like the file browser) connected by a glowing line to its originating session tab — the same connector-line idiom as subagent windows. The tab is resolved by matching the run's
sessionUuidto a session'sclaudeSessionId. The window mirrors the live agent grid (phases, per-agent model / tokens burned / tool calls / state), auto-closes a few seconds after its run finishes, and remembers windows you explicitly dismiss so they don't re-pop. These windows are additional to the existing docked "Ultracode Agents" master-detail panel, which is unchanged. - New setting "Ultracode Floating Windows" (App Settings → Display), default OFF, independent of the "Ultracode Agents" panel toggle. Either toggle now starts the server-side workflow-run watcher (at boot and on live settings change), so the floating windows work even with the docked panel off.
- Internals: new frontend module
ultracode-windows.js(load order 15.5); ultracode connector lines are appended into the shared#connectionLinesSVG within the existing batched read/write reflow pass insubagent-windows.js; newultracodeFloatingWindowsapp-settings key inschemas.ts; watcher gating inserver.ts+system-routes.tsnow ORs both ultracode toggles. - Docs:
CLAUDE.mdbrought up to date for the 1.1.2 ultracode/workflow-run subsystem (Agents / Frontend / Types / Config inventories, JS load order, a Key Patterns entry) and the new floating-windows feature.
- New: floating ultracode run windows. When enabled, each active ultracode / Workflow run pops a small draggable window (like the file browser) connected by a glowing line to its originating session tab — the same connector-line idiom as subagent windows. The tab is resolved by matching the run's
1.1.2
Patch Changes
- Ultracode/Workflow run visualization + subagent discovery fixes.
- Ultracode / Workflow run visualization (new, opt-in): App Settings → Display → "Ultracode Agents" (
showUltracodeAgents, default OFF) adds a master-detail tab that shows ultracode / Workflow-tool runs like Claude Code's "working agents" view — the LEFT pane lists runs and their phases (selectable tasks), the RIGHT pane shows each run's agents with model, live state, tokens burned, and tool calls. Clicking an agent opens its live transcript. Backed by a new standalone workflow-run watcher that reads the per-run state JSON (stripping the heavy embedded script/result/logs so payloads stay small), exposesGET /api/workflowsandGET /api/workflows/:runId, and broadcastsworkflow:run_discovered/updated/removedSSE events. The header launcher and panel stay hidden until the setting is enabled (the setting is synced across devices, not per-device). - Subagent tracking discovery fix: restored subagent tracking after Claude Code changed the on-disk format from
agent-*.jsonltoagent-*.meta.json(background agents were showing 0). Also discovers workflow-nested subagents undersubagents/workflows/<wf>/and hardens the meta→transcript upgrade path so an agent re-points to its.jsonltranscript once it appears. - File viewer: opens audio, SVG, and other binary files the same way the attachments viewer does.
- Tooling: hardened the real-overview screenshot capture script and documented the
deviceScaleFactor/ static-cache gotchas.
- Ultracode / Workflow run visualization (new, opt-in): App Settings → Display → "Ultracode Agents" (
1.1.1
Patch Changes
- Six reviewed contributor PRs (all adversarially reviewed and fixed before merge):
- Markdown sanitizer hardened against mutation-XSS (#126). The denylist
_sanitizeHtmlis replaced with vendored DOMPurify 3.4.8 (authentic, byte-matched to the official dist) wired via a newsanitize-html.jsallowlist, with a fail-closed escape fallback. The curated allowlist is genuinely enforced (noUSE_PROFILESoverride) so non-markdown tags and svg/math/style/script/event-handler/javascript:vectors are stripped while legitimate markdown survives. - Hook-event secret now required unconditionally (#127). The
/api/hook-event+/api/status-telemetrylocalhost bypass requires the per-instance hook secret whether or not a managed tunnel is running, closing the own-loopback-reverse-proxy gap. A self-heal refreshes pre-secret hook configs in existing cases on spawn so password-protected installs don't silently 401 their hooks. No-password loopback installs are unaffected. codeman doctordependency checker (#125). Newdoctor/check-depscommand probes Node, the agent CLIs, tmux, and document converters per environment (linux/darwin/win32/wsl), with grouped or--jsonoutput and a non-zero exit when a required tool is missing. Requires Node 22+, reportspdftoppm(used for PDF/Office thumbnails), and validates--category.- macOS Option / physical-key session shortcuts (#129). Tab switching matches physical key codes (
e.code) so Option+1–9 works on macOS layouts that remap Option, plus Option/Alt+[/]for previous/next session — without leaking escape sequences into the focused terminal. - Desktop session tabs auto-wrap to a second row on overflow (#128) instead of horizontal scrolling (off when the manual two-row layout is pinned; mobile/tablet unchanged), re-evaluated on window resize.
- CJK input textarea hidden on the welcome screen (#123) so it no longer floats over the welcome overlay, and re-shown on session entry; vertical centering fixed.
- Markdown sanitizer hardened against mutation-XSS (#126). The denylist
1.1.0
Minor Changes
-
Plan Usage Limits chip (new). A header chip now shows your live Claude plan usage — the 5-hour and weekly windows as a percentage — parsed from Claude Code's statusLine telemetry (CLI v2.1.80+). It's opt-in via App Settings → Display → "Plan Usage Limits" (default OFF). The toggle is per-device: turn it on at your desk without it appearing on your phone. Telemetry collection is decoupled from display, so one device's preference never affects another's, and the last-known value replays instantly on reconnect. Distinct from auto-resume (which reacts to the limit message) — this proactively shows the live %.
Attachments. New attachment history drawer to browse files referenced by a session (COD-39), plus document previews and thumbnails on attachment cards (COD-38). The header Attachments button is now opt-in (default OFF) via App Settings → Display → "Attachments Button", per-device like the Response Viewer button.
Settings & models. Added Opus 4.6 options to the Claude Model picker. Removed the legacy Token Count / Show Cost header toggles and moved Plan Usage Limits to the top of the Display settings. Slimmed the Skin picker control to match its row.
Mobile & header polish. Restored the response-viewer (eye) button on phones; kept the phone header minimal (settings gear + lifecycle log stay in the toolbar). Added two regression guards so header controls can't silently leak onto the mobile header again — a CI-runnable static policy check plus a real-browser E2E test.
1.0.0
Major Changes
-
Codeman 1.0.0 🎉
The first stable release of Codeman — and it comes with a fresh new look.
New: theme skins. Codeman now ships a built-in skin switcher (App Settings → Display → Appearance):
- OG Codeman — the original look, preserved exactly.
- Daylight Green — a fresh emerald-on-slate theme.
- Daylight Blue — bright sky-blue on lifted slate (the new default).
Skins apply instantly, persist per device (with a pre-paint script so there's no flash on load), and re-theme any open terminals live. The system is built on
html[data-skin]design tokens and self-hosted Manrope (UI) + JetBrains Mono (terminal) fonts — no external CDN, CSP-safe.1.0.0 milestone. This marks the start of the stable 1.x line: the CLI, documented environment variables, and the
{ success, data }HTTP/SSE API envelope follow semantic versioning (seedocs/versioning-policy.md).Thank you to everyone who helped build Codeman. This release is dedicated to all of our contributors for their work on the project: Ark0N, Aamer Akhter (@aakhter), Tenggan Zhang (@TeigenZhang), zhouyuan / @sunnyzhouy, jaypark, Marco Migozzi, Skúli Arnlaugsson, Aaron Fields, Loïc Sculier, and Noah Waldner (@noahwaldner). 💙
0.9.14
Patch Changes
-
Security hardening for the tunnel exposure path, Codex terminal rendering fixes, and a mobile modal fix.
Security (PR #115, COD-54/COD-55):
/api/hook-eventlocalhost bypass is now gated while the managed Cloudflare tunnel is running: tunneled traffic arrives with a loopback source IP, so the bypass additionally requires a per-instance shared secret (X-Codeman-Hook-Secret, 256-bit,~/.codeman/hook-secret, mode 0600). Locally generated hook commands read the secret file at execution time via$CODEMAN_HOOK_SECRET_FILE(exported into every managed session's environment), so the value never lands on command lines or in case configs, and running sessions pick up a new secret without respawn. Failed presentations rate-limit in a dedicated per-IP bucket so misfiring legacy hooks can never lock out the Basic-Auth login path. With no tunnel running, behavior is unchanged.- Enabling the Cloudflare tunnel now refuses with 403 when no
CODEMAN_PASSWORDis set (a public tunnel URL with no auth is effectively public RCE), unlessCODEMAN_ALLOW_UNAUTHENTICATED_NETWORK=1explicitly acknowledges the exposure. The settings UI surfaces the refusal as an error toast and reverts the toggle.
Codex rendering (PRs #116, #117):
- Alt-screen toggles (
?47/?1047/?1049), scrollback-erase (CSI 3 J), and mouse-tracking enables (?1000–?1007) are stripped from the Codex byte stream (live + replay), so conversation history survives tab switches and the scroll wheel scrolls the viewport instead of being hijacked. Sequences split across PTY chunk boundaries are reassembled via a small carry before stripping, so a split?1049hcan no longer trap xterm in the scrollback-less alt buffer. - Smaller 32KB first-frame write budget for Codex sessions keeps dense synchronized redraws from stalling the renderer; a 1.5s grace window after a manual scroll-up suppresses sticky-scroll so high-frequency
• Working (Ns)status ticks no longer snap the viewport back to the bottom while reading earlier output.
Mobile: session-options modal raised above the fixed mobile/tablet header (z-index 1300 vs 1200) so the close button is reachable on phones; Respawn tab controls regrouped.
Docs: security-architecture.md updated for the secret-gated hook bypass (including the external-proxy caveat) and the tunnel password guard; README documents auto-resume on usage limit.
0.9.13
Patch Changes
-
Auto-resume on usage limit ("token pause" control) plus a set of mobile-view fixes for regressions introduced in 0.9.8.
Auto-resume on usage limit — new opt-in checkbox at the top of the session Respawn tab (off by default). When Claude stops because a usage limit was reached, Codeman parses the reset time from the limit message, waits until the limit lifts (plus a 2-minute safety buffer), then dismisses the rate-limit dialog (Esc) and sends "continue" so the session picks its work back up automatically. All Claude Code message formats from 1.0.x through 2.1.x are recognized ("5-hour limit reached ∙ resets 8pm", "Limit reached · resets 1pm (America/Chicago) · /upgrade…", "You've hit your weekly limit · resets Mon 12:00am", weekly date forms, and the raw API
usage limit reached|<epoch>form). Still-limited responses re-arm the scheduler (5-minute retry loop); a pending schedule persists across Codeman restarts and re-arms on boot; respawn cycles are blocked while a limit pause is active so the cycle's/clearcannot wipe the paused conversation. New endpointPOST /api/sessions/:id/auto-resume; new SSE eventssession:limitPauseScheduled,session:limitResume,session:limitResumeCancelled; toast/notification on pause and resume, plus a live "resumes at HH:MM" status line in the modal. The Respawn tab layout was also tidied: compact single-row Update/Kickstart prompt fields and a merged options row.Mobile fixes (0.9.8 regressions):
- Activity-based resize arbitration — a desktop sizing claim now only blocks a phone's resize while that desktop has actually typed within the last 90 seconds. Previously any connected desktop tab (even one abandoned hours ago) silently discarded the phone's resize with no fallback, leaving the phone rendering a desktop-width stream in a narrow terminal: mid-word wraps, tmux dot-fill rows, overdrawn garbled text, and misplaced keyboard echo. Now an idle desktop yields the pane to the phone, and the next desktop keystroke automatically restores the desktop layout ("whoever is actively using the session wins"). Phones also re-send their dimensions every 30 seconds (visible tab only, skipped while the virtual keyboard is open) so attaching under a momentarily-active desktop self-corrects.
- Keyboard accessory bar and toolbar restored on iOS — the lift offset is measured against the layout viewport (
window.innerHeight) again instead of the keyboard-shrunken app element; on iOS the offset computed to 0, leaving both bars hidden behind the OS keyboard with a dead black gap above it. - Removed the mobile header utility ("three dots") toggle — the header-utilities tray stays collapsed on small viewports.
0.9.12
Patch Changes
-
Documentation refresh — README catches up with the Codex run mode, plus a CLAUDE.md correction.
README (en + zh-CN): Codex is now listed as a third supported AI coding CLI everywhere the docs previously said "Claude Code or OpenCode": the install requirement in Quick Start (now "any combination works", linking to the official Codex CLI docs), the Windows/WSL setup note, the renamed Multi-CLI feature bullet (env-prefix gating now reads
CLAUDE_CODE_*vsOPENCODE_*vsCODEX_*), the Zod schema-validation security bullet, and the architecture mermaid diagram. The header tagline was also finalized to "Claude Code • OpenCode • Codex — One Dashboard • Any Device" in both languages.CLAUDE.md: fixed a stale "Local packages" line that claimed the xterm-zerolag-input local-echo overlay had a copy embedded in
app.js— it is single-source inpackages/xterm-zerolag-input/, bundled to the gitignored vendor file, and only consumed byapp.js, matching the existing single-source gotcha.
0.9.11
Patch Changes
-
Fix a terminal freeze on hover (catastrophic regex backtracking) and a CSP violation that disabled the terminal's anti-throttling worker.
Tab-freezing hover bug: the terminal link provider's
cmdPattern(which turnstail -f /path-style text into clickable links) used an empty-matchable, unbounded arg group —(?:[^\s\/]*\s+)*— that backtracks exponentially on real Claude output, e.g. wrappedgit commit -m "$(cat <<'EOF'heredoc lines or aligned table rows. Hovering the mouse over such a line hung the page's main thread for minutes ("page unresponsive"). The pattern now uses non-empty tokens with bounded repetition (linear time); all intended command+path link forms still match. Newtest/link-provider-regex.test.tsextracts the shipped patterns from source and pins linear-time behavior on the killer line shapes.Blob worker CSP fix:
worker-src 'self' blob:is now always present in the CSP (previously only withCODEMAN_GESTURE=1). The terminal's_safeYieldanti-throttling tick worker is created from a Blob URL and was silently blocked on every install, logging a CSP violation on each page load and disabling the worker leg of the render-yield fallback chain.
0.9.10
Patch Changes
-
Self-update now restarts automatically on headless Macs supervised by a system LaunchDaemon.
New
launchd-daemonsupervisor kind: when Codeman runs under a bootstrapped, KeepAlive system-level LaunchDaemon (/Library/LaunchDaemons/com.codeman.web.plist— the right setup for headless Macs, where LaunchAgents never start because there is no GUI login), the updater no longer ends with "Update staged — restart Codeman to apply". It restarts rootlessly: the update script kills the server PID (passed via--server-pid) and launchd respawns it on the freshly builtdist/. Detection is conservative — the daemon must be bootstrapped in the system domain AND haveKeepAliveenabled.Also fixed: a lingering "restart Codeman to apply" status. After a manual restart of a staged update, boot reconciliation now flips
completed-needs-manual-restarttocompletedonce the running version matches the staged target, so the Updates tab stops showing the stale instruction.
0.9.9
Patch Changes
-
Codex (OpenAI CLI) run mode, Claude Model picker, and response-viewer button now opt-in.
Codex (OpenAI CLI) run mode (#114): new
codexsession mode alongside Claude Code and OpenCode. Sessions launch the Codex CLI via tmux with secrets injected throughtmux setenv(OPENAI_API_KEY/CODEX_API_KEY/CODEX_HOME— never on the command line). Supports--model,resume <id>, and--dangerously-bypass-approvals-and-sandboxvia thecodexConfigpayload or the new App Settings → Codex CLI tab (codexDangerouslyBypassApprovals). Availability surfaced atGET /api/codex/statuswith an install hint when the binary is missing. Frontend gets a "Run CX" run-mode option; Respawn/Ralph options stay Claude-only (session options open on the Summary tab for external-CLI sessions).CODEX_*env prefix added to the env-override allowlist.Claude Model picker: App Settings → Claude CLI gains a "Claude Model" select (
claudeModelsetting) that pins the model for new Claude sessions via the case's.claude/settings.local.json— e.g. Fable 5 (1M context), Fable 5, Opus (1M), Opus, Sonnet, Haiku. It takes precedence over the legacy 1M Opus Context toggle. Fable 5 also added to the orchestrator default/phase model dropdowns.Response-viewer (eye) header button is now hidden by default — existing users who relied on it can re-enable it under App Settings → Display → Response Viewer (
showResponseViewer, per-device setting). A new Display toggle controls its visibility.Also: tests made immune to a set
CODEMAN_GESTUREenv var; CLAUDE.md documents the Codex run mode and the eye-button toggle.
0.9.8
Patch Changes
-
Stable HTTP contract, terminal pane-buffer rework, mobile/touch fixes, and fresh-install default cleanups.
API / v1 readiness (PR #113)
- Stable HTTP contract: uniform
{success, data}/{success: false, error, errorCode}response envelope across all ~134 handlers, correct HTTP status codes, and a versioned/api/v1/*alias of/api/* - Post-merge adversarial audit closed 9 contract gaps (envelope/status-code stragglers), incl.
loadQuickStartCasesdouble-unwrap - Node.js floor raised to >=22;
codemanbin alias installed alongsideaicodeman - Security hardening: SSRF guard on the push endpoint, tmux session-name validation, documented tail-file roots
- Governance: SECURITY.md and a SemVer versioning policy (docs/versioning-policy.md)
- CI now runs the full unit/integration suite (vitest.ci.config.ts) plus a frontend JS syntax gate
Terminal (PR #112)
- tmux pane-buffer primitives and session/render reliability fixes for the terminal pipeline, with re-review findings addressed
Mobile / touch (PR #111)
- Terminal and layout fixes for touch devices: desktop focus handling, WS resize-claim wiring, CJK setting, ESC passthrough
- New: Esc button in the simple (default) keyboard accessory bar, next to paste — sends a real ESC to the session
Defaults & UI
- Monitor panel is now disabled by default on fresh installs (desktop previously slid it open at startup; mobile was already off). Opt in via App Settings -> Show Monitor
- Fixed the session-tab task badge silently failing to open the Monitor panel when it was hidden by the setting (long-broken on mobile)
- Local echo defaults audited and confirmed per-device: off on desktop, on for touch devices, never server-synced
- Stable HTTP contract: uniform
0.9.7
Patch Changes
- Fix installer failure on corrupt puppeteer cache + add Simplified Chinese README.
- Installer / self-update reliability: The universal installer (
install.sh) and the in-app self-updater (scripts/self-update.sh) now setPUPPETEER_SKIP_DOWNLOAD=1beforenpm install.puppeteeris a devDependency used only byscripts/browser-comparison.mjs; its ~150MBchrome-headless-shelldownload is never needed to build or run Codeman. Previously, a partially-downloaded browser cache (folder present, executable missing) made puppeteer refuse to re-download and abortnpm install, which failed the entire install/update — most visibly on macOS (mac_arm). The download is now skipped on both paths; callers can still opt back in withPUPPETEER_SKIP_DOWNLOAD=0. - Docs: Added a Simplified Chinese translation of the README (
README.zh-CN.md) with an English/中文 language switcher inREADME.md. Refreshed the README and documented the v0.9.5 security hardening (Host-header/DNS-rebinding guard, cross-site Origin/CSRF guard, anti-CSWSH WebSocket validation).
- Installer / self-update reliability: The universal installer (
0.9.6
Patch Changes
-
Self-updater: show live progress during the slow steps so an update no longer looks frozen.
- The detached update runner (
scripts/self-update.sh) now emits a heartbeat every few seconds duringnpm installandnpm run build, refreshing the update status with the latest output line (full output is still written to the update log). - App Settings → Updates now shows the live status message plus a ticking elapsed-time counter during non-terminal phases, instead of only a static phase label.
This takes effect when updating from a build that includes it — the detached runner script and the polling UI are both the from-version's copies.
- The detached update runner (
0.9.5
Patch Changes
-
Security hardening from the 2026-06-09 adversarial review — close the remote-exploit paths that affected the default (loopback + no-password) configuration. Full report:
docs/reports/security-review-2026-06-09.md.- Anti-DNS-rebinding Host allowlist (always on). A new request guard rejects requests whose
Hostis a custom domain rebound to a loopback/LAN address — previously a website the operator merely visited could DNS-rebind to127.0.0.1and drive the entire API (arbitrary command execution, since sessions run--dangerously-skip-permissions). The allowlist acceptslocalhost, any bare IP literal, the bind host,*.ts.net/*.trycloudflare.com/*.cfargotunnel.com, the active managed tunnel, and anything in the newCODEMAN_ALLOWED_HOSTSenv var (comma-separated;hostor leading-dot.suffix). - Cross-site (CSRF) Origin guard on all state-changing requests. Forged cross-site requests are rejected; a missing
Originis allowed socurl/CLI automation and Claude Code hooks keep working. This closes the previously CSRF-triggerable self-update, session create/input, and settings/tunnel-toggle endpoints. text/plainbody parser no longer JSON-parses every request body (which let a cross-site "simple request" submit JSON with no CORS preflight). The crash-diagnostics beacon now parses its own body.- WebSocket terminal upgrade now validates
Origin/Host(blocks cross-site WebSocket hijacking that could inject keystrokes into a running agent). - Stored-XSS fix: AI-/transcript-derived fields (tool name, tool detail, tool id, hook text) in the subagent activity panel are now HTML-escaped.
Operational note: if you front Codeman with a custom reverse-proxy domain, allow it via
CODEMAN_ALLOWED_HOSTS=host,.suffix. SettingCODEMAN_PASSWORDalso fully mitigates these via the existing auth hook. - Anti-DNS-rebinding Host allowlist (always on). A new request guard rejects requests whose
0.9.4
Patch Changes
-
In-app self-updater, plus the SSE-registry and security-doc changes since 0.9.3.
New: update Codeman from the web UI (App Settings → Updates). A "Check for updates" button asks the server to query GitHub for the latest tagged release (falling back to
git ls-remote) and shows its release notes; "Update now" then runs the fullgit checkout <tag>→npm install→npm run build→ restart cycle and streams live progress that survives the service restart (the browser polls a status file across the connection drop).- Channel: latest tagged release (e.g.
codeman@0.9.4), not bleeding-edge master. - Dirty working trees are auto-stashed (
git stash, left for you togit stash pop) instead of discarded. - Cross-platform restart, detected from the running process: systemd (
systemctl --user restart codeman-web) on Linux, launchd (launchctl kickstart) on macOS, or a printed manual command otherwise. - Survives its own restart: the updater runs detached in a transient
systemd-run --user --scope(Linux) orsetsidsession (macOS), so the restart it triggers cannot kill the build mid-flight. - Safety: build failure rolls back to the pre-update commit (never restarts into a half-built
dist/); the pre-restart status marker is reconciled on boot with an update-id + freshness guard so a normal reboot is not misreported as a completed update; concurrent updates are rejected (409); the runner script is staged outside the repo sogit checkoutcannot corrupt it mid-run; release tags are strictly validated before reaching the shell;CODEMAN_DISABLE_SELF_UPDATE=1disables the feature; non-git (npm-global) installs are detected and pointed atnpm i -g aicodeman@latest. - New endpoints:
GET /api/system/update/check,POST /api/system/update,GET /api/system/update/status.
Also in this release:
- Sync the frontend
SSE_EVENTSregistry (constants.js) with the backendsse-events.tsso every broadcast event has a matching frontend entry. - Expand
docs/security-architecture.mdwith the trust model, CSP detail, and a source-file map.
- Channel: latest tagged release (e.g.
0.9.3
Patch Changes
- Installer security notice + clarify gesture control stays opt-in and default-off.
- Installer:
install.shnow prints the network-security notice as the final block of both the fresh install (one-linecurl … | bash) and the update flow, so it stays visible to the user: Codeman binds127.0.0.1by default (no password needed), and the safe ways to reach it remotely (tailscale serve/ tunnel, or--host 0.0.0.0+CODEMAN_PASSWORD), noting a non-loopback bind without a password still starts but warns loudly. - Gesture control is disabled by default and is enabled only by the per-user toggle at App Settings → Display → Input → Gesture Control (
gestureControlEnabled, defaultfalse). SettingCODEMAN_GESTURE=1on the server only makes the feature available (CSP widening + same-origin/gesture/assets); it does not turn the overlay on. There is no default-on path — the bundle is injected only when a user explicitly enables the setting.
- Installer:
0.9.2
Patch Changes
-
Vendor the gesture-control source into the repo for in-tree development.
The hand-tracking overlay's source (previously the standalone
Ark0N/codeman-gesture-controlrepo) now lives atpackages/gesture-control/as thecodeman-gesture-controlworkspace package: the transport-agnostic gesture core (src/gesture/*— MediaPipe GestureRecognizer → One-Euro-filtered cursor → pinch state machine), the Codeman consumer entry (src/codeman/entry.ts, maps grab/drag/drop onto real session tabs + toolbar buttons), and a standalone vite playground for iterating on gesture feel.- New
npm run build:gesture(scripts/build-gesture-bundle.mjs) esbuild-bundlesentry.tsinto the servedsrc/web/public/gesture/gesture-codeman.js;scripts/build.mjsnow reruns it on every production build so the served bundle always reflects current source. The MediaPipe wasm + model stay runtime-loaded from same-origin/gesture/(unchanged). - Added
@mediapipe/tasks-vision@0.10.21as the package dependency (kept in sync withfetch-gesture-assets.mjs). The playground uses vite 7 (no known advisories).
No change to the shipped app behavior — gesture control remains opt-in (
CODEMAN_GESTURE=1+ the App Settings → Input toggle). This release just makes the overlay developable inside the Codeman repo. - New
0.9.1
Patch Changes
- Multi-monitor & settings UX fixes.
- Multi-monitor button (remote servers): the "span displays" button spawns
scripts/span-codeman.shserver-side, so on a non-macOS Codeman server it can't open a window on your machine. The non-macOS API error now explains this and points to running the script locally on your Mac with the remote server URL; the script header documents the same remote-client workflow. - App Settings modal: stop the modal overflowing horizontally on narrow viewports.
- systemd: sync the
codeman-web.servicetemplate with the deployed unit.
- Multi-monitor button (remote servers): the "span displays" button spawns
0.9.0
Minor Changes
-
Security hardening release: network-bind policy, auth lockout recovery, download/SVG hardening, dependency & supply-chain fixes, tmux launch reliability, and a full security-architecture doc.
Network binding (COD-29, #107):
- The web server now defaults to binding
127.0.0.1(loopback) instead of0.0.0.0, so a fresh install is reachable only from the same machine and needs no password. New--host/-H/CODEMAN_HOSTflag to choose the bind host. - Binding a non-loopback host without
CODEMAN_PASSWORDno longer refuses to start — it starts and prints a loud warning with the three ways to secure it (setCODEMAN_PASSWORD, bind loopback + an authenticated tunnel /tailscale serve, or acknowledge with--allow-unauthenticated-network/CODEMAN_ALLOW_UNAUTHENTICATED_NETWORK=1). This keeps Codeman "just working" for new users while making remote exposure a guided, explicit choice. Host classification lives in the newsrc/web/network-auth-policy.ts(handles127.0.0.0/8,::1,::ffff:127.*, bracketed IPv6). - A post-install security note now explains the loopback default and how to expose safely.
Authentication (COD-29, #107):
- Auth lockout now recovers gracefully: the per-IP rate-limit (
429) check runs after the cookie/credential checks, so a valid session cookie or correct password is never locked out by a prior attacker's failures from the same IP (important behind a shared-IP tunnel). Wrong credentials are still counted and still hit the limit, and aRetry-Afterheader is returned.
Downloads & content-type hardening (COD-29, #107):
- New session-scoped
POST /api/downloadroute: realpath-bounded to the session working dir, a sensitive-path blocklist (/etc/shadow,~/.ssh/,.env,*credentials*, …),isFile()+ 50 MB cap, forcedattachment. - Workspace
.svgfiles are served asapplication/octet-stream+attachment+nosniff(closes a stored-XSS-via-SVG vector);nosniffnow applies to allfile-rawresponses.
Dependencies & supply chain (COD-28, #106):
- Bumped security-sensitive deps to patched versions (
@fastify/static9,fastify5.8,uuid14,vitest4.1, …) and addedoverridesfor patched transitives (picomatch,basic-ftp,fast-uri,flatted);npm auditgoes from 7 advisories to 0. - New
npm run check:public-assets(scripts/check-public-assets.mjs): scanssrc/web/public/**for literal NUL bytes and runsnode --checkon every.jsfile, plus a Prettier pass on maintained files. Removed literal NUL placeholders fromapp.js. Addedtest/dependency-security.test.tsandtest/frontend-public-tooling.test.ts.
tmux launch reliability (COD-31, #110):
- New tmux sessions and respawns launch from a stable
/tmpandcdinto the workspace inside the pane, avoidingnew-sessioncrashes when a FUSE/rclone-mounted workspace has a transient mount blip at launch. Thecd "<dir>" && <cmd>form is fail-safe (the CLI never runs in/tmp) and the path is validated + double-quoted.
Test stability (COD-30, #108):
- Cleared leaked auth env in the Vitest setup, corrected stale route status-code / SSE-lifecycle expectations to match shipped behavior, updated the mobile keyboard accessory expectations, and measured DOMContentLoaded via browser navigation timing. Also fixed the
WebServertitle tests for the newhostconstructor arg + asyncrenderIndexHtml.
Docs:
- New
docs/security-architecture.mddocumenting the full model (network binding, auth pipeline, the tunnelreq.ipcaveat, file-serving hardening, supply-chain, multi-instance isolation, security headers, and recommended secure setups). CLAUDE.md updated accordingly.
- The web server now defaults to binding
0.8.2
Patch Changes
- Session detach/undock, opt-in gesture-control overlay, multi-monitor spanning, new App-Settings toggles, and asset cache-busting.
- Session detach/undock + instance isolation (#103): Detach a session into its own solo (popup) window from the tab strip. Adds multi-instance isolation primitives in
src/config/instance.ts(getDataDir()/dataPath()/DEFAULT_TMUX_SOCKET) keyed offCODEMAN_INSTANCE, so a beta can run side-by-side with prod without discovering/attaching to prod's live tmux sessions or clobbering itsstate.json.CODEMAN_INSTANCEdefaults to the production layout (~/.codeman,-L codeman, port 3000), so master installs are unaffected. Addsscripts/run-beta.sh(CODEMAN_INSTANCE=beta+CODEMAN_PORT=5000). The legacy~/.claudemanmigration is now scoped to the default instance only. Hardened detach edge cases. Tests:test/config/instance.test.ts. - Gesture-control overlay (Phase 5, opt-in via
CODEMAN_GESTURE=1): Camera hand-tracking overlay (self-hosted MediaPipe — wasm + model fetched at install/build viascripts/fetch-gesture-assets.mjsrather than committed).CODEMAN_GESTURE=1makes the feature available (CSP widening +/gesture/assets +window.__codemanGestureAvailable); the per-user Gesture Control (beta) toggle (App Settings → Display → Input, default OFF) is the actual on/off and reloads the page to inject/remove the bundle. Dashboard-only (not solo popups). Labeled "(beta)" (#109). - Multi-monitor button: Header button (opt-in via App Settings → Display → Header Displays) that POSTs
/api/system/span-displaysto spawnscripts/span-codeman.sh— a maximized browser--appwindow sized to the union of all displays, so the gesture layer's floating panels can drag across the physical monitor seam. Tests:test/routes/system-span-displays.test.ts. - New App-Settings toggles (#105): Gesture control and the multi-monitor button are both opt-in (default OFF), with live show/hide on save.
- Asset cache-busting:
renderIndexHtmlappends?v=<mtime>to every same-origin.js/.cssreference;index.htmlis servedno-cache, so a normal reload picks up edited modules/styles without a hard refresh. Tests:test/render-index-html.test.ts. - Gesture Control toggle placement: the toggle now lives inside the existing Input settings section (alongside Local Echo / CJK Input / Extended Keyboard Bar) instead of a duplicate "Input" section; only the toggle itself is hidden when
CODEMAN_GESTURE=1is unset, leaving the rest of the section intact. - Service env:
scripts/codeman-web.servicenow setsCODEMAN_GESTURE=1so the gesture feature is available on the local install (still gated behind the default-OFF per-user toggle). - Docs: CLAUDE.md updated for the orchestrator loop, multi-monitor/span-displays, cache-busting, gesture/multi-monitor toggles, and structural-count fixes.
- Session detach/undock + instance isolation (#103): Detach a session into its own solo (popup) window from the tab strip. Adds multi-instance isolation primitives in
0.8.1
Patch Changes
-
Thinking Effort now flows as a soft default the user can override in-session (PR #104, by @TeigenZhang).
Previously Codeman carried the effort setting as the
CLAUDE_CODE_EFFORT_LEVELenv var, which Claude Code treats as a hard override — it locked effort for the whole session and rejected in-session/effortswitching (including switching toultracode). Effort is now injected at spawn time as a CLI soft default that/effortcan still change freely in either direction:- Regular levels (
low/medium/high/xhigh/max) are passed viaclaude --effort <level>(the settingseffortLevelkey silently dropsmax, so the flag is used instead). ultracode(xhigh effort + standing dynamic-workflow orchestration) is passed viaclaude --settings '{"ultracode":true}', since the--effortflag rejects it.
Details:
- New
effortfield on the create-session, quick-start, and Ralph-loop request schemas; threaded throughSession._effortto both spawn paths (tmuxbuildSpawnCommandand direct-PTYbuildInteractiveArgs), persisted inSessionState.effort, and restored on reboot recovery. buildEffortCliArgs()is the single, allowlist-validated source for both carriers (injection-safe).- Settings UI adds an "Ultracode (multi-agent workflows)" option to the Thinking Effort dropdown; the frontend no longer emits
CLAUDE_CODE_EFFORT_LEVEL. - Legacy migration: sessions persisted with the old env var are auto-migrated into the new
effortfield, and the stale tmux env var is unset so respawned panes are no longer locked. - Adds
test/effort-injection.test.ts(13 cases) covering carrier mapping, injection guards, args building, and constructor migration.
- Regular levels (
0.8.0
Minor Changes
- Event-loop responsiveness fix, mobile image upload, response-viewer polish, and a mobile-UI trim.
- fix: avoid event-loop stalls from synchronous tmux/ps calls (#100): The session manager ran
execSyncfor tmux mouse-mode toggles,list-panes, andps/pgrepresource-stat queries on the main thread. Under multi-session / many-pane load these blocking spawns froze Node's single event loop, stalling SSE broadcasts and PTY I/O (the ":3000 briefly unreachable, process never restarts" class of incident). Converted those calls to asyncexecAsyncand updated all callers toawait. Added a lightweightutils/event-loop-monitor.tsthat samples loop-delay and logs when a stall threshold is exceeded, started on web-server boot and stopped on shutdown — so future regressions leave a timestamped, quantified log line instead of vanishing silently. - feat(web): mobile image upload to active session via paste dialog (#101): The mobile keyboard-accessory paste dialog now attaches images, not just text — via a native picker (
accept=image/*→ camera / photo library / files) plus best-effort capture of images pasted into the textarea. Both paths reuse the existing_uploadAndInsertImages()→POST /api/sessions/:id/paste-imagepipeline. Images are re-encoded client-side before upload (PNG→PNG to preserve transparency, everything else→JPEG, animated GIFs passed through untouched) so the bytes always match their declared extension — fixing the Android/MIUI case where a WebP/HEIF mislabeled asimage/jpegpassed the extension allowlist but failed the server's magic-byte check. The server logs a precise diagnostic on any remaining magic-byte mismatch. - feat(web): response-viewer transcript fallback + code-block rendering (#102): A substantial response-viewer styling overhaul — proportional prose font (monospace kept for code), refined heading/code/blockquote/list styling, readable max content width, and a smoother slide-in animation; the
.rv-textrules now also apply to.response-viewer-bodyso transcript-missing fallback content gets the same typography. Plus a_renderMarkdownnull-safety fix (text→src = text || ''). - feat(web): remove /compact button from the mobile keyboard accessory bar: Dropped
/compactfrom both the simple and extended accessory-bar layouts and the associated action handling./clearretains its double-tap confirmation. Verified on a touch-emulated viewport that neither layout renders a compact action.
- fix: avoid event-loop stalls from synchronous tmux/ps calls (#100): The session manager ran
0.7.1
Patch Changes
-
fix(respawn): auto-accept now fires on plan approvals after
Worked for Xline, and on AskUserQuestion menusTwo related blockers in the respawn controller's auto-accept path:
- Modern Claude Code emits
✻ Worked for Xm Ysimmediately before a plan-approval menu._detectCompletionMessage()cancelled the auto-accept timer andcanAutoAccept()then rejected oncompletionMessageTime !== null, so plan approvals never auto-accepted — the 10 s completion-confirm timer instead started a respawn cycle while the menu sat unanswered. - The same logic in
signalElicitation()set a hard flag that blocked auto-accept whenever Claude Code fired theelicitation_dialoghook, contradicting the in-UI hint ("Auto-accept presses Enter for plan approvals and default question options"). AskUserQuestion menus were therefore never auto-accepted either.
Fix:
_detectCompletionMessage()no longer cancels the auto-accept timer; the auto-accept pre-filter is now the authoritative "is there a numbered selection menu?" gate.canAutoAccept()and the AI-plan-check callback both accept'watching'AND'confirming_idle'states (covers the single-PTY-burst case whereWorked forand the menu arrive together —_detectCompletionMessagereturns early before the substantial-output check can demote state back to watching).sendAutoAcceptEnter()self-transitions back to'watching'before sending Enter.signalElicitation()is now an affirmative hint that primes the auto-accept timer instead of blocking. Still gated onconfig.autoAcceptPromptsAND state ∈ {watching,confirming_idle} — never fires Enter when respawn is off or auto-accept is disabled.- AI plan-check prompt broadened to recognize AskUserQuestion / elicitation menus as valid for auto-accept (the verdict name
PLAN_MODEis preserved for compatibility but now means "auto-accept this selection menu"). - Removed the now-unused
elicitationDetectedfield and its assignments.
Two new regression tests cover both the separate-PTY-chunk and single-PTY-chunk cases; the previously misleading "should NOT send Enter when completion message was detected" test was renamed and re-scoped to clarify it tests the no-menu path (which still correctly rejects via the pre-filter).
docs(web): correct
sendPendingCtrlLcomment — removed the stale "called by foo/bar" note from the dead-call-graph helper after #99. - Modern Claude Code emits
0.7.0
Minor Changes
- Response viewer & terminal-stability improvements, plus test/error-handling hardening.
- Copy button on code blocks (#98): Every fenced code block in the response viewer now has a one-click copy button pinned to its top-right, outside the
<pre>scroll container so it stays put during horizontal scroll. ASCII diagrams keep their line-wrap toggle alongside it. Copy prefers the async Clipboard API and falls back to a hidden-textarea +execCommandpath, so it works over plain HTTP (tunnel) too, with a brief ✓/✕ feedback state. - Fix: stop auto-sending Ctrl+L from session-selection paths (#99): A fast page refresh or SSE reconnect could fire two programmatic Ctrl+L (
\x0c) sends within Claude Code 2.x's "clear conversation" confirmation window, silently wiping the active conversation. Removed the automatic Ctrl+L sends fromselectSession(),restoreTerminalSize(), and the deadsendPendingCtrlL()path; redraws now rely on resize/SIGWINCH. User-initiated Ctrl+L still works. Trade-off: an occasional transient stale Ink frame right after refresh that self-heals on the next keypress — far preferable to silent data loss. - Test & error-handling hardening (#97): Repaired route-test harness error rendering via a dedicated
route-error-handler.ts, and stopped the AI idle/plan checkers from spawning real processes during tests.
- Copy button on code blocks (#98): Every fenced code block in the response viewer now has a one-click copy button pinned to its top-right, outside the
0.6.12
Patch Changes
- Fix new-session crash after a tmux upgrade and isolate Codeman sessions on a dedicated tmux socket.
- Pane file-descriptor limit: raise
ulimit -Snbefore launching the CLI (in both the spawn and respawn paths) so the newer tmux + macOS launchd combination — which hands panes a low softnofilelimit (256) that recent Claude Code refuses to start under — no longer kills every freshly spawned session on startup. - Single-socket isolation: all Codeman-owned tmux sessions now live on a dedicated socket (
tmux -L codeman, overridable viaCODEMAN_TMUX_SOCKET), fully separated from the user's default tmux server. The socket name is validated and shell-escaped at every call site. - Drop the drift-prone per-session
tmuxSocketfield: session reconciliation collapses to a singlelist-panesquery against the one socket, eliminating live sessions being wrongly marked dead ("session not found") and duplicate "Restored:" tabs. Stale per-session socket tags and duplicate records are cleaned from disk on load (dedup bymuxName, keeping the real entry overrestored-placeholders). - Route remaining bare-
tmuxcall sites through the socket: the window-size query on re-attach (previously fell back to 120×40 and lost scrollback) and the send-key route (Shift+Enter / Ctrl+Enter newline). - SSH chooser scripts (
tmux-manager.sh,tmux-chooser.sh) route every tmux call through the dedicated socket.
- Pane file-descriptor limit: raise
0.6.11
Patch Changes
- Resume Conversation: fixes and folder drill-down.
- fix(history):
decodeProjectKey()now uses longest-join-first backtracking with on-disk validation, so sibling directories sharing a prefix (e.g.diary/vsdiary-app/) resolve to the correct path. Previously the greedy shortest-match decoder picked the shorter name and bailed, surfacing$HOMEin the Resume Conversation list and resuming into the wrong folder. Greedy decode is kept as a fallback so history for deleted projects still resolves. (#92) - fix(tabs): Drop the client-side resurrection of ended-session tabs. The old code cached open tabs in
localStorageand rebuilt them as grayed-out stubs whenever the server no longer knew them, which left phantom tabs after closing a session on another device. The server is now the single source of truth; legacylocalStoragekeys are purged on init. Net -44 / +6 lines. (#93) - feat(history): New "View all in this folder" drill-down on Resume Conversation.
GET /api/history/sessionsacceptsprojectKey(validated against^[A-Za-z0-9_-]+$before any filesystem access),offset, andlimit; single-folder mode bypasses the 50-cap and returns{ sessions, total }. Frontend adds a modal listing 20 sessions per page with a "Show more" pagination button. Modal items omit their own "View all" button to prevent recursive entry points. (#94)
- fix(history):
0.6.10
Patch Changes
-
Security: paste-image endpoint hardening (#90)
Addresses seven findings from the dismissed review of #84. Most exposed in tunneled deployments where
CODEMAN_PASSWORDis set but the server is reachable beyond localhost.- CSRF protection on
POST /api/sessions/:id/paste-image. RequiresOrigin/Refererto matchreq.host; non-browser clients (noOriginand noReferer) must sendX-Codeman-CSRF. Defeats cross-origin<form enctype="multipart/form-data">submits that would otherwise plant arbitrary bytes into the victim's.claude-images/while their session cookie is live. - Magic-byte validation on uploaded images. Sniffs the first 12 bytes against PNG/JPEG/GIF/WebP/BMP signatures and rejects 415 on mismatch. Polyglot HTML-or-SVG-with-image-MIME no longer round-trips through the endpoint.
- Symlink-safe writes on
.claude-images/.lstatbefore the write, non-recursivemkdir,O_EXCL|O_NOFOLLOWon file open. Anode_modulespostinstall (or the agent itself) planting.claude-images -> ~/.ssh/no longer redirects pastes outsideworkingDir. - Multipart parser swap to
@fastify/multipartwithlimits: { fileSize: 10MB, files: 1, fields: 4 }. Replaces a hand-rolled boundary scanner that matched the literal boundary anywhere in the body, hard-coded\r\n(silently corrupting LF-only clients), and had no part-count cap. - Rate limit + GC: token-bucket (30/min per IP+session) and hourly GC of
paste-*files older than 7 days from each live session's.claude-images/. Newpaste-image-gc.tsstarted/stopped fromWebServer.start/stop. - Collision-free filenames:
paste-${Date.now()}-${randomBytes(4)}${ext}. Two tabs pasting in the same millisecond no longer silently last-write-wins. - Bracketed-paste preservation: text-only paste in
image-input.jsnow goes throughterminal.paste(text)instead ofsendInput(text), so xterm preservesCSI 200~ ... CSI 201~markers — Claude Code uses them as part of its prompt-injection defenses.
Fix: duplicate multipart parser conflict
Removed a duplicate multipart content-type parser left behind after the swap above. The duplicate registration conflicted with
@fastify/multipart's own parser; uploads now flow through the plugin exclusively.WebGL renderer auto-fallback hardening (#91)
Follow-ups on the longtask auto-fallback shipped in #83.
PerformanceObserveris now disconnected ononContextLossas well as on the trip path. Previously the observer outlived its disposed addon after a context loss, holding a closure reference over every longtask the page emitted.- Thresholds (
200ms / 3 longtasks / 30s window / 5s grace / 7d sticky-disable) are hoisted toWEBGL_FALLBACKinconstants.js. No more inline literals. - New
evaluateWebGLLongTaskTrip()pure helper splits the rolling-window arithmetic from thePerformanceObservercallback so the trip math is unit-testable. Newtest/webgl-fallback.test.ts(9 tests, port 3166): trip inside window, no-trip when spread, sub-threshold filtering, stale-entry pruning, cumulative counting across batches, observer-dispose idempotency.
CI: server boot smoke test
GitHub Actions now boots the server as a final step after typecheck/lint/format. Catches production-only ESM/CJS regressions that
tsxmasks in dev.Docs
CLAUDE.mdfrontend-module table updated to includeimage-input.js(overlooked when #84 landed). - CSRF protection on
0.6.9
Patch Changes
- Terminal renderer hardening, SSE bandwidth cut, image paste, and a security tightening on the new live filter:
- Multi-primitive yield for write pacing (#85): replaces six raw
requestAnimationFramecallsites in the xterm.js write pipeline with a yielding helper that racesrequestAnimationFrame,setTimeout(50), and a tick Worker. Keeps the terminal responsive when the tab is backgrounded or occluded — Chrome's intensive-throttling no longer stalls long writes. - WebGL longtask auto-fallback (#83): a
PerformanceObserverwatches for ≥200ms WebGL frames; three within a 30s window disposes the WebGL addon and falls back to the canvas renderer. Decision is persisted in localStorage for 7 days, and?webgl=forceclears it. - Per-client live SSE subscription filter (#86): each connected client gets a stable UUID and can narrow its terminal stream to one session via
POST /api/events/subscribe— no EventSource reconnect on tab switches. Cuts SSE bandwidth roughly N× when N sessions are open. Lifecycle/metadata events (session:*,case:*,ralph:*,hook:*) now broadcast to every client so sidebars stay in sync. - Image paste and drag-and-drop into the terminal (#84):
Ctrl+Vand dropped images upload toPOST /api/sessions/:id/paste-image, save under${workingDir}/.claude-images/paste-${ts}.${ext}and type the path into the terminal. Hard 10MB cap, server-generated filename (no traversal),.svgdeliberately excluded from the allowlist to avoid a same-origin XSS path throughfile-raw. - SSE clientId validation: the per-client identifier introduced in #86 is now constrained to
[A-Za-z0-9_-]{8,64}at both ingress points. Without this, an authenticated attacker could send another tab's clientId to silently evict it from broadcasts, mutate any clientId's session filter to blackhole the victim's terminal stream, or growsseClientsByIdunboundedly via long IDs. The subscribe payload is also capped at 64 session entries of ≤128 chars each.
- Multi-primitive yield for write pacing (#85): replaces six raw
0.6.8
Patch Changes
- Finish the hostname-aware notification plumbing started in 0.6.7 and lock down the recent UI/runtime fixes with regression tests.
- Browser Notification API (OS-level desktop pop-ups, layer 3 of the 5-layer notification system) now uses
${originalTitle}: ${title}instead of the hardcodedCodeman:literal — so multi-host users running Codeman on laptop / dev box / NAS seecodeman:<host>: <event>consistently across tab title, tab-flash, Web Push, and OS notifications. - Inline session rename hardened against three corner cases: IME composition commits (Chinese pinyin Enter no longer ships half-composed text as the session name), mid-rename SSE deletion (orphaned
<input>no longer 404s on blur), and double-fire on stuck settle-once flag (closure-localsettledboolean replaces the boolean instance flag). - Test coverage backfilled for two prior shipped fixes:
<title>codeman:<host></title>server-side templating (#82): 8 tests covering defaultos.hostname(),--title-hostnameoverride, HTML-escape against<script>-style breakout, ampersand non-double-encoding, and template-tail byte-identical invariance.- tmux size-query helper (#80): 15 tests covering the browser-resize-between-attaches happy path, the query-then-die race, zero/negative/empty/non-numeric output fallbacks, and argv-form/timeout assertions that lock down the no-shell-interpolation guarantee. Inline 14-line query block extracted into a named
queryTmuxWindowSize()export insession.tsso the test surface is a pure function.
- Regression coverage added for
stripInkRedrawBloatroute helper. - CLAUDE.md and README.md updated to document dual-CLI env-prefix discipline (
CLAUDE_CODE_*vsOPENCODE_*), thexterm-zerolag-inputpublished-package side-effect of overlay edits, and the unified hostname prefix across tab title / tab-flash / OS notifications.
- Browser Notification API (OS-level desktop pop-ups, layer 3 of the 5-layer notification system) now uses
0.6.7
Patch Changes
-
- fix(client): preserve inline rename input across tab re-renders (#81) — Right-click → rename on a session tab no longer loses keystrokes when SSE traffic from sibling sessions triggers a tab re-render. Adds an
_inlineRenameActiveguard at the top ofrenderSessionTabs()and_fullRenameSessionTabs()so the in-progress input isn't destroyed mid-typing. Also fixes a latent double-fire offinishRename(blur + Enter could both invoke it). Drive-by: safer DOM child clearing in place ofinnerHTML = ''. - feat: hostname-aware window title (#82) — The browser tab title is now
codeman:<hostname>instead of the bareCodemanliteral, so users running Codeman on multiple hosts (laptop, dev box, NAS) can tell at a glance which tab points at which backend. New--title-hostname <name>CLI flag overrides the detectedos.hostname()when it's noisy or you want a cosmetic name. The title is templated into the served HTML on first byte (with narrow HTML escaping), so it's correct from the first paint and works without JavaScript. Title-flash logic now respects the per-host title. - perf: larger terminal tail on tab switch —
TERMINAL_TAIL_SIZEraised from 128KB to 1MB. When switching back to a busy session tab you now get ~8× more scrollback restored immediately. - fix: preserve response text in Ink redraw stripping —
stripInkRedrawBloat()rewritten from a first-VPA approach to cluster-based detection. The previous algorithm assumed all VPA escapes after the first one belonged to a single redraw region and discarded everything in between, which silently lost 100KB+ of legitimate Claude response text once a render had occurred. The new approach groups VPAs into clusters separated by ≥8KB gaps and only collapses clusters spanning ≥32KB, so streamed response content between redraw bursts is preserved. - docs:
CLAUDE.mdAdditional Commands gains the--title-hostnamerow;README.mdgets a "Hostname-Aware Window Title" subsection under Multi-Session Dashboard.
- fix(client): preserve inline rename input across tab re-renders (#81) — Right-click → rename on a session tab no longer loses keystrokes when SSE traffic from sibling sessions triggers a tab re-render. Adds an
0.6.6
Patch Changes
-
Terminal scrollback significantly increased — both the xterm.js viewport and the tmux backing buffer were bottlenecking how far back you could scroll. Three changes:
DEFAULT_SCROLLBACKraised from 20000 → 50000 lines (xterm.js, main terminal). The previous bump from 5000 only helped users with empty localStorage; existing users were stuck on whatever value they first picked up. The loader now treatsDEFAULT_SCROLLBACKas a floor — if your stored value is below the new minimum, you're raised to it automatically.- Subagent / teammate terminals (
panels-ui.js) were stuck at 5000; now use the sameDEFAULT_SCROLLBACKconstant (50000). - New tmux sessions now run with
history-limit 50000(tmux defaults to 2000). This matters for hard-reload / re-attach — without it, only the last ~2000 lines survive the round-trip back into a fresh xterm.
Tmux flicker on session re-attach fixed (PR #80 by @aakhter): the PTY now queries the existing tmux window size via
tmux display -pbefore spawning, instead of hardcoding 120x40. Previously, every re-attach forced tmux to resize down to 120x40, causing a visible flicker and one frame of scrollback loss. The-x 120 -y 40flag was also dropped fromtmux new-sessionso the initial size matches the first attaching client. UsesexecFileSync(not shell) for safety and falls back to 120x40 on any error.Docs: CLAUDE.md now documents two recurring foot-guns — the
xterm-zerolag-inputoverlay code is duplicated betweenpackages/xterm-zerolag-input/src/and inline insidesrc/web/public/app.js, so any overlay change must touch both; and the COM workflow explicitly includes a post-pushgh run watchstep to confirm CI before considering the release done.
0.6.5
Patch Changes
-
Mobile fix
- Android virtual keyboard: space character was silently dropped on touch devices using GBoard / SwiftKey / similar IMEs. Root cause: the input-event handler in
terminal-ui.jstreated any whitespace-only textarea value as proof that xterm had already processed the input. A lone space (' '.trim() === '') tripped this guard, so the space was consumed but never forwarded. Now skips only when the textarea is truly empty (or whitespace from a non-space key). Reported and diagnosed by @coolk8 in #79.
Docs
CLAUDE.md: added Zod.optional()-vs-nullgotcha (recurring trap from 0.6.3 / 0.6.4 incidents) and a more visible warning against running barenpm test(kills the host tmux session).docs/local-echo-overlay-plan.md: marked SHIPPED, corrected xterm version reference (v5.3.0 →@xterm/xterm^6.0.0).
- Android virtual keyboard: space character was silently dropped on touch devices using GBoard / SwiftKey / similar IMEs. Root cause: the input-event handler in
0.6.4
Patch Changes
- Fix "Failed to enable respawn: Invalid request body" error when selecting infinity duration (∞) in the respawn modal. Frontend was sending
durationMinutes: null, which Zod's.optional()schema rejected (it acceptsundefinedonly). The body now omits the field when no duration is selected.
0.6.3
Patch Changes
- Fix
- Allowlist
opusContext1mEnabledinSettingsUpdateSchema. Without this entry, the strict schema rejectedPUT /api/settings {"opusContext1mEnabled":...}withINVALID_INPUT, so the toggle's value never persisted across reloads. The frontend was already reading and writing this key (settings-ui.js:336/1137,session-ui.js:340), so saves were silently failing — users never noticed because the load path falls back tofalseon missing keys, hiding the bug. (#78)
- Allowlist
0.6.2
Patch Changes
-
Mobile UX
- Resume Conversation list (welcome page) reworked for narrow screens: 2-line title clamp so more of the first prompt is visible; case-aware subtitle that renders
#caseName(or#caseName/sub) whenworkingDirmatches a known case, otherwise falls back to the directory basename; inline⋯toggle that expands a detail panel with full prompt, full path, timestamp, size, and short session id;/Users/<user>/now collapses to~/alongside/home/<user>/. (#77) - Response viewer: ASCII diagram wrap toggle, dedicated mobile code-block layout, and chrome-stripping fallback when the model wraps its reply in extra markup. (#75)
- Mobile keyboard accessory bar no longer triggers vertical scroll. (#72)
Sessions & settings
- New
thinkingEffortsetting on session creation, withxhighoption and/effort maxmobile shortcut. (#73) thinkingEffortis now allowlisted inSettingsUpdateSchemaso it round-trips through PATCH /api/settings.envOverrides(CLAUDE_CODE_*/OPENCODE_*) are now passed to Claude via tmux env exports at spawn time instead of being written to<case>/.claude/settings.local.json. Eliminates UI/disk drift; the value lives onSession._envOverrides, is exported bytmux-manager.buildEnvExports(), and is persisted inSessionState.envOverrides. (#74)
Fixes
- Eye icon (active-session indicator) now follows
/clearto the new Claude conversation instead of getting stuck on the previous transcript. (#76) tmux-manager.reconcileSessionsnow uses|as the field separator, fixing parsing when session names contain other delimiters. (#71)
Docs
- CLAUDE.md: added
npm run knipto the dead-code sweep table and aCommon Gotchasentry documenting theenvOverrides→ tmux export flow.
- Resume Conversation list (welcome page) reworked for narrow screens: 2-line title clamp so more of the first prompt is visible; case-aware subtitle that renders
0.6.1
Patch Changes
- Internal cleanup and release hygiene:
- Dead-code sweep via knip: added
knip.jsonfor dead-code detection and ran a full sweep — removed unused test files, unused scripts, and narrowed internal module exports to the minimum surface area actually consumed. - Lockfile drift prevention:
version-packagesnow runsnpm install --package-lock-onlyand verifies the lockfile is in sync viascripts/check-lockfile-sync.mjs; CI runs the same check on every push/PR so version drift fails the build instead of reaching production. Resolves thepackage-lock.json/package.jsonversion mismatch that shipped in 0.6.0. - Docs tightening: archived 22 completed plan docs from
docs/, corrected file/handler counts inCLAUDE.md, documented the lockfile step in the COM workflow, and removed footer redundancy.
- Dead-code sweep via knip: added
0.6.0
Minor Changes
- Community contributions from @aakhter:
- feat (#66): Tab reorder shortcuts —
Ctrl+Shift+{andCtrl+Shift+}move the active session tab left/right, matching WezTerm convention. Order persists across reloads viasaveSessionOrder(). - feat (#67): Active tab visibility + Alt+N badges — active tab now has a bright green border with color-matched glow, and the first 9 tabs display number badges hinting at the
Alt+Nswitch shortcut. Badges update on reorder/rerender. - feat (#68): Clipboard API — new
POST /api/clipboardaccepting{text}broadcasts aclipboard:writeSSE event; connected browsers attemptnavigator.clipboard.writeText()with a manual-copy modal fallback when the page isn't focused. Auth-protected via the standard middleware. Useful for pushing snippets from remote sessions to the user's local clipboard. - fix (#65): Android Shift+key double character — pressing
Shift+Aon attached Android keyboards no longer produces "AA". Tracks xterm-handled keydown timestamps and skips the orphaned-input listener for 50ms after a real keydown, while still catching Gboard symbol-keyboard inputs (keyCode 229).
- feat (#66): Tab reorder shortcuts —
0.5.13
Patch Changes
- Fix "Case path not found" error in Quick Start when
~/codeman-cases/does not exist (issue #64). Two bugs insession-ui.js:runClaude()auto-create readcreateCaseData.case, butPOST /api/casesreturns{ success, data: { case } }— corrected tocreateCaseData.data.case.runShell()had no auto-create logic and would immediately throw on a missing case directory — now mirrorsrunClaude()'s create-on-demand flow.
0.5.12
Patch Changes
- Fix quick-start to resolve linked cases before codeman-cases fallback.
/api/quick-startwas always resolvingcaseNameagainstCASES_DIR, ignoring entries in~/.codeman/linked-cases.json. Sessions started via quick-start now correctly honour linked external project directories, consistent with regular case routes.
0.5.11
Patch Changes
- Community contributions and security hardening:
- Mobile response viewer: native-scroll panel for reading full Claude responses with markdown rendering via marked.js (PR #62)
- PWA support: service worker caching, web app manifest, and Android home screen install (PR #59)
- Named Cloudflare tunnel support (PR #58)
- Markdown rendering for response viewer with HTML sanitization (XSS prevention) — strips dangerous elements, event handlers, and javascript: URIs
- Service worker switched from stale-while-revalidate to network-first caching so deploys take effect immediately
- Content-Disposition filename sanitization to prevent header injection in file downloads
- Expose session.muxName public getter, replace unsafe
as anycast in session-routes - Static import for execFile in session-routes
- Keyboard shortcut updates: Alt+1-9 tab switching, Shift+Enter newline
- Repo restructure for cleaner GitHub landing page
- Mobile logo, expandable history, session resume fixes
0.5.10
Patch Changes
-
fix: allow bracket characters in model validation regex so models like opus[1m] (1M context window) are accepted instead of silently dropped. Quote the model flag value in tmux spawn commands to prevent bash glob expansion of bracket patterns.
docs: update macOS launchd instructions to use
launchctl bootstrapinstead of deprecatedload. Clean up README install and service sections.
0.5.9
Patch Changes
-
Mobile keyboard accessory bar: add configurable "Extended Keyboard Bar" setting (Settings > Display > Input) that toggles between simple mode (up/down arrows, /init, /clear, /compact, paste, dismiss) and extended mode (adds left/right arrows, Tab, Shift+Tab, Ctrl+O, Alt+Enter, Esc). Default is simple mode. Setting is device-specific (not synced to server).
Restyle dismiss button: muted steel-blue tone, fills remaining bar space via flex, larger tap target. Arrow buttons now blue.
Fix paste overlay visibility on mobile: dialog repositioned to top of screen (15vh from top) so the virtual keyboard doesn't cover it. Textarea enlarged for better usability.
(Also includes all v0.5.8 changes: case reorder/delete, XSS sanitization, auto-attach PTY on restart, mobile keyboard buttons, macOS installer fixes, terminal flicker fix, state store collision fix.)
0.5.8
Patch Changes
-
Case management: add Manage tab with reorder (up/down arrows) and delete for cases; linked cases are unlinked (folder preserved), CASES_DIR cases are permanently deleted. New endpoints: DELETE /api/cases/:name, PUT /api/cases/order. SSE events: case:deleted, case:order-changed.
Security: sanitize case names from filesystem with /^[a-zA-Z0-9_-]+$/ regex before returning from GET /api/cases to prevent XSS via maliciously-named directories reaching frontend inline onclick handlers.
Auto-attach PTY: server now calls startInteractive() for recovered tmux sessions during startup so all sessions resume capturing output immediately after deploy, instead of waiting for client selection. Frontend auto-attach condition relaxed from (pid===null && status==='idle') to (pid===null && !_ended).
Mobile keyboard accessory: add Shift+Tab, Tab, Esc, Alt+Enter, Left/Right arrow, and Ctrl+O buttons.
Terminal: fix flicker regression by moving viewport clear inside dimension guard.
State store: fix temp file collisions on concurrent writes.
macOS: fix installer failures when piped via curl | bash, add HTML cache support, launchd service template, and trust dialog handling.
Housekeeping: remove accidentally committed dist/state-store.js build artifact.
0.5.7
Patch Changes
- feat: support "Default (CLI default)" option for model selection. Adds a new empty-value option to the model dropdown that defers to the CLI's own default model instead of forcing a specific model. Ensures empty defaultModel values are treated as undefined when passed to session creation and Ralph loop start, preventing empty strings from being sent as model flags.
0.5.6
Patch Changes
- fix: default new sessions to opus[1m] (1M context window) instead of plain opus (200k context)
0.5.5
Patch Changes
- Add 1M Opus context quick setting — per-case and global toggle that writes
model: "opus[1m]"to.claude/settings.local.jsonwhen creating new sessions. Fix mobile layout: banners (respawn, timer, orchestrator) between header and main content now visible by switching from margin-top on.mainto padding-top on.app. Add tablet-optimized respawn banner styles and mobile phone banner refinements.
0.5.4
Patch Changes
- Fix terminal flicker regression — re-add server-side DEC 2026 synchronized output wrapping around batched terminal data. Ink spinner frames (cursor-up + redraw cycles) do not emit their own DEC 2026 markers, so without the server wrapper each partial cursor update rendered individually causing visible flicker. Also: extract SSE stream management, session listener wiring, and respawn event wiring from server.ts into dedicated modules; deduplicate error message extraction across 7 files with shared getErrorMessage() helper; update SSE event count in CLAUDE.md (106 → 117).
0.5.3
Patch Changes
- Readability refactor across 12 core files, extracting ~35 helper methods to reduce duplication:
- state-store: extract serializeState(), split assembleStateJson() into focused sub-methods
- session: extract _resetBuffers() (3x dedup), _clearAllTimers() (10 timer cleanups), _handleJsonMessage()
- ralph-tracker: extract completeAllTodos() (4x dedup), emitValidationWarning(), named similarity constants
- subagent-watcher: extract markSubagentAsCompleted(), extractFirstTextContent(), emitToolResult(), findOldestInactiveAgent()
- respawn-controller: extract recoveryResetToWatching(), canAutoAccept(), formatRemainingSeconds(), validatePositiveTimeout()
- tmux-manager: replace 15 path.includes() with UNSAFE_PATH_CHARS regex, extract buildEnvExports/buildPathExport/_configureOpenCode helpers
- session-auto-ops: extract executeWhenIdle() shared retry helper, convert to options object, add validateThreshold()
- app.js: add _clearTimer() (11 call sites), _isStaleSelect(), keyboard shortcut lookup table, _cleanupPreviousSession(), _resetAllAppState()
- route-helpers: add readJsonConfig() (5 inline patterns replaced), validateSessionFilePath() (2 duplicated blocks replaced)
0.5.2
Patch Changes
-
Make buffer size limits configurable via CODEMAN_* environment variables (MAX_TERMINAL_BUFFER, TRIM_TERMINAL_TO, MAX_TEXT_OUTPUT, TRIM_TEXT_TO, MAX_MESSAGES), falling back to existing defaults. Allows users with fewer sessions or more RAM to tune buffer sizes without patching source.
Fix duplicate terminal output on tab switch to busy sessions by clearing the terminal before writing the new buffer.
Fix stale Ink CUP frames after tab switch by sending Ctrl+L to force a clean redraw.
Fix mobile CJK input handling: resolve textarea positioning, terminal flicker during composition, and layout overflow on small screens. Improve CJK composition lifecycle with better event handling and fallback flush timers.
0.5.1
Patch Changes
- refactor: codebase cleanup — extract route helpers, eliminate boilerplate, optimize hot paths
- Add
parseBody()helper to route-helpers.ts: validates request body against Zod schema with structured 400 error on failure, replacing 37 identical safeParse + error-check blocks across 10 route files - Add
persistAndBroadcastSession()helper: combines persist + SessionUpdated broadcast into one call, replacing 5 repeated 2-line pairs - Migrate session-routes.ts to use
findSessionOrFail()consistently (17 inline session lookups replaced) andparseBody()(12 patterns) - Migrate ralph-routes.ts to use
findSessionOrFail()(9 lookups) andparseBody()(4 patterns) - Migrate 8 remaining route files to use
parseBody()(21 patterns total) - Fix O(n log n) eviction in bash-tool-parser.ts: replace
Array.from().sort()[0]with O(n) min-scan for oldest active tool - Extract
_debouncedCall()utility in frontend: replaces 4 manual debounce patterns (7 lines each → 1 line) in app.js, panels-ui.js, ralph-panel.js - Net reduction: 208 lines removed across 16 files
- Add
0.5.0
Minor Changes
- Visual redesign with glass morphism, refined colors, and polished UI. Optimize history endpoint with buffer reuse and line iterator. Fix Ink frame search window (4KB→64KB) to prevent partial frames. Fix stale terminal data on tab switch via chunkedTerminalWrite cancellation. Improve history prompt extraction with expanded command filtering and tail scan fallback. Align case select group height to match dropdown. Fix no-control-regex lint error for ANSI strip pattern. Add browser-testing-guide to CLAUDE.md references.
0.4.7
Patch Changes
- feat: improve session navigability in history and monitor panel (closes #45)
- History items now show the first user prompt as the title with the project path as a subtitle, making it much easier to distinguish sessions from the same project
- The
/api/history/sessionsendpoint extracts the first user message from each transcript JSONL, stripping system-injected XML tags and command artifacts, truncating to 120 chars - Monitor panel session rows are now clickable — clicking navigates directly to that session's tab via
selectSession(); Kill button retains independent behavior viastopPropagation() - Updated CLAUDE.md architecture tables to reflect Orchestrator Loop additions (14 route modules, 15 type files, orchestrator domain files, orchestrator-panel.js frontend module)
- fix: stop subagent monitor windows from auto-opening on discovery
- feat: add Orchestrator Loop with phased plan execution, live progress during plan generation, and toolbar button (hidden until fully tested)
- fix: patch 3 production bugs found during deep audit
- fix: restore mobile terminal scrollback using JS scrollLines() instead of broken native scroll
0.4.6
Patch Changes
- Fix mobile keyboard scroll and layout issues:
- Prevent iOS Safari from scrolling the page when typing with the keyboard open (position:fixed on .app + window.scroll reset)
- Eliminate dead space between terminal and keyboard accessory bar by removing redundant CSS padding, tightening JS padding constant, and adding row quantization gap compensation
- Fix toolbar overlapping terminal content when keyboard is hidden by adding proper padding-bottom to .main, including iOS Safari bottom bar offset
- Strip Ink spinner bloat from terminal buffer before tailing
- Fix resolveCasePath priority order and suppress JSON parse warnings
0.4.5
Patch Changes
- Fix mobile keyboard toolbar positioning on iOS Safari: toolbar (Run/Stop/Run Shell) was hidden behind the accessory bar when virtual keyboard was active due to overlapping CSS positions. Remove the aggressive safety check in
updateLayoutForKeyboard()that incorrectly dismissed keyboard state when iOS scrolled the visual viewport during typing. Add Safari-bar CSS offset to accessory bar so it properly stacks above the toolbar. Remove the double-counted Safari-bar offset when keyboard is visible since the JS transform already covers the full distance.
0.4.4
Patch Changes
-
fix: mobile keyboard hides terminal content on iPhone
Fixed a bug where opening the virtual keyboard on iPhone left zero visible terminal space. Two independent mechanisms were both accounting for the keyboard height:
MobileDetection.updateAppHeight()shrunk--app-heightto the visual viewport height, whileKeyboardHandler.updateLayoutForKeyboard()added a largepaddingBottom. These double-counted, leaving negative space for the terminal (user saw accessory bar + toolbar but no terminal content).Fix:
updateAppHeight()now skips when the keyboard is visible, andhandleViewportResize()restores--app-heightto the pre-keyboard value on first detection (since MobileDetection's listener fires before KeyboardHandler's). On keyboard close,--app-heightis re-synced to the current visual viewport.
0.4.3
Patch Changes
- Refactor case routes: extract readLinkedCases() and resolveCasePath() helpers to eliminate 6x duplicated linked-cases.json path construction and 5x duplicated file read/parse logic. Replace O(n) .some() duplicate check with O(1) Set.has() in case listing. Un-export unused isError() type guard. Standardize reply.status() to reply.code() in system routes. Update CLAUDE.md frontend module listing and SSE event count.
0.4.2
Patch Changes
- Extract monolithic app.js (~12.5K lines) into 6 focused domain modules that extend CodemanApp.prototype via Object.assign: terminal-ui.js (terminal setup, rendering pipeline, controls), respawn-ui.js (respawn banner, countdown, presets, run summary), ralph-panel.js (Ralph state panel, fix_plan, plan versioning), settings-ui.js (app settings, visibility, web push, tunnel/QR, help), panels-ui.js (subagent panel, teams, insights, file browser, log viewer), session-ui.js (quick start, session options, case settings). Fix critical deferred script init ordering bug: wrap CodemanApp instantiation in DOMContentLoaded so all defer'd mixin modules execute their Object.assign before the constructor runs. Guard missing cleanupWizardDragging() call in subagent-windows.js. Update build.mjs to minify/hash all new modules.
0.4.1
Patch Changes
- Performance optimizations: V8 compile cache for 10-20% faster cold starts, lazy-load WebGL addon (244KB saved on mobile), preload hints for critical scripts, batch tmux reconciliation (N subprocess calls → 1). Also: WebSocket session lifecycle fixes, CJK IME input support, CI upgrade to Node 24/actions v6, install.sh fork support, and CLAUDE.md/README documentation refresh.
0.4.0
Minor Changes
-
Add CJK IME input textarea for xterm.js terminal (env toggle INPUT_CJK_FORM=ON). Always-visible textarea below terminal handles native browser IME composition, forwarding completed text to PTY on Enter. Supports arrow keys, Ctrl combos, backspace passthrough, and Escape to clear.
Add fork installation support to install.sh with CODEMAN_REPO_URL and CODEMAN_BRANCH env vars, allowing custom repository and branch for git clone/update operations. README updated with fork installation instructions.
Fix WebSocket session lifecycle: close WS connections when session exits (prevents orphaned listeners and stale writes to dead PTY), add readyState guard in onTerminal to stop buffering after socket closes, simplify heartbeat by removing redundant alive flag.
Add WebSocket reconnection with exponential backoff (1s-10s) on unexpected close, skipping server rejection codes (4004/4008/4009). Falls back gracefully to SSE+POST during reconnection.
Clear CJK textarea on session switch to prevent sending stale text to wrong session.
0.3.12
Patch Changes
- Add WebSocket terminal I/O with server-side DEC 2026 synchronized update markers. Replaces per-keystroke HTTP POST + SSE terminal output with a single bidirectional WebSocket connection for dramatically lower input latency. Server-side 8ms micro-batching with 16KB flush threshold groups rapid PTY events into single WS frames wrapped in DEC 2026 markers for flicker-free atomic rendering. Includes 30s ping/pong heartbeat with 10s timeout for stale connection detection through tunnels. Existing SSE + HTTP POST paths remain fully functional as transparent fallback. Resize messages validated to match HTTP route bounds (cols 1-500, rows 1-200, integers only). 16 automated route tests added for WS endpoint. Also patches 5 dependency vulnerabilities (basic-ftp, fastify, minimatch, serialize-javascript).
0.3.11
Patch Changes
-
Session Resume & History
- Add
resumeSessionIdsupport for conversation resume after reboot - Add history session resume UI and API with route shell sessions routing fix
- Improve session resume reliability and persist user settings across refresh
- Correct
claudeSessionIdfor resumed sessions
Terminal & Frontend
- Upgrade xterm.js 5.3 → 6.0 with native DEC 2026 synchronized output
- Increase terminal scrollback from 5,000 to 20,000 lines
- Reduce default font size and persist tab state across refresh
- Resolve terminal resize scrollback ghost renders
- Hide subagent monitor panel by default
Installer
- Auto-detect existing install and run update instead of fresh install
- Auto-restart codeman-web service after update if running
- Show restart command when codeman-web is not a systemd service
- Fix one-liner restart command for background processes
Codebase Quality
- Remove dead code, consolidate imports, extract constants
- Repair 15 pre-existing subagent-watcher test failures
- Clean up DEC sync dead code
- Add
0.3.10
Patch Changes
-
- feat: upgrade xterm.js from 5.3 to 6.0 with native DEC 2026 synchronized output support
- feat: add history session resume UI and API — resume Claude conversations after reboot
- feat: add resumeSessionId support for conversation resume across session restarts
- feat: persist active tabs across page refresh
- feat: improve session resume reliability and persist user settings
- perf: increase terminal scrollback from 5,000 to 20,000 lines
- fix: resolve terminal resize scrollback ghost renders
- fix: route shell sessions to correct endpoint on tab click
- fix: correct claudeSessionId for resumed sessions (use original Claude conversation ID)
- fix: increase default desktop font size from 12 to 14
- refactor: extract shared _fetchHistorySessions() method to eliminate duplication
- refactor: remove dead DEC 2026 sync code (extractSyncSegments, DEC_SYNC_START/END constants)
0.3.9
Patch Changes
-
Add content-hash cache busting for static assets — build step now renames JS/CSS files with MD5 content hashes (e.g. app.js → app.94b71235.js) and rewrites index.html references. HTML served with Cache-Control: no-cache so browsers always revalidate and pick up new hashed filenames after deploys. Hashed assets keep immutable 1-year cache. Eliminates the need for manual hard refresh (Ctrl+Shift+R) after deployments.
Refactor path traversal validation into shared validatePathWithinBase() helper in route-helpers.ts, replacing 6 duplicate inline checks across case-routes, plan-routes, and session-routes.
Deduplicate stripAnsi in bash-tool-parser.ts — use shared utility from utils/index.ts instead of private method.
0.3.8
Patch Changes
- Add tunnel status indicator with control panel — green pulsing dot in header when Cloudflare tunnel is active, dropdown with URL, remote clients, auth sessions, and start/stop/QR/revoke controls
0.3.7
Patch Changes
- Operation Lightspeed: 5 parallel performance optimizations — multi-layer backpressure to prevent terminal write freezes, TERMINAL_TAIL_SIZE constant with client-drop recovery, tab switching SSE gating, and local echo improvements
- Codebase cleanup: remove dead code (unused token validation exports, PlanPhase alias), add execPattern() regex helper to eliminate repetitive .lastIndex resets, centralize 11 magic number constants into config files, fix CLAUDE.md inaccuracies, and add 316 new tests for utilities, respawn helpers, and system-routes
0.3.6
Patch Changes
- Re-enable WebGL renderer with 48KB/frame flush cap protection against GPU stalls
0.3.5
Patch Changes
- Fix Chrome "page unresponsive" crashes caused by xterm.js WebGL renderer GPU stalls during heavy terminal output. Disable WebGL by default (canvas renderer used instead), gate SSE terminal writes during tab switches, and add crash diagnostics with server-side breadcrumb collection.
0.3.4
Patch Changes
- Fix Chrome tab freeze from flicker filter buffer accumulation during active sessions, and fix shell mode feedback delay by excluding shell sessions from cursor-up filter
0.3.3
Patch Changes
- fix: eliminate WebGL re-render flicker during tab switch by keeping renderer active instead of toggling it off/on around large buffer writes
0.3.2
Patch Changes
- Make file browser panel draggable by its header
0.3.1
Patch Changes
- LLM context optimization and performance improvements: compress CLAUDE.md 21%, MEMORY.md 61%; SSE broadcast early return, cached tunnel state, cache invalidation fix, ralph todo cleanup timer; frontend SSE listener leak fix, short ID caching, subagent window handle cleanup; 100% @fileoverview coverage
0.3.0
Minor Changes
- QR code authentication for tunnel access, 7-phase codebase refactor (route extraction, type domain modules, frontend module split, config consolidation, managed timers, test infrastructure), overlay rendering fixes, and security hardening
0.2.9
Patch Changes
- System-level performance optimizations (Phase 4): stream parent transcripts instead of full reads, consolidate subagent file watchers from 500 to ~50 using directory-level inotify, incremental state persistence with per-session JSON caching, and replace team watcher polling with chokidar fs events
0.2.8
Patch Changes
- Remove 159 lines of dead code: unused interfaces, functions, config constants, legacy no-op timer, and stale barrel re-exports
0.2.7
Patch Changes
- Fix race condition in StateStore where dirty flag was overwritten after async write, silently discarding mutations
- Fix PlanOrchestrator session leak by adding session.stop() in finally blocks and centralizing cleanup
- Fix symlink path traversal in file-content and file-raw endpoints by adding realpathSync validation
- Fix PTY exit handler to clean up sessionListenerRefs, transcriptWatchers, runSummaryTrackers, and terminal batching state
- Fix sendInput() fire-and-forget by propagating runPrompt errors to task queue via taskError event
- Fix Ralph Loop tick() race condition by running checkTimeouts/assignTasks sequentially with per-iteration error handling
- Fix shell injection in hook scripts by piping HOOK_DATA via printf to curl stdin instead of inline embedding
- Narrow tail-file allowlist to remove ~/.cache and ~/.local/share paths that exposed credentials
- Fix stored XSS in quick-start dropdown by escaping case names with escapeHtml()
0.2.6
Patch Changes
- Disable tunnel auto-start on boot; tunnel now only starts when user clicks the UI toggle
0.2.5
Patch Changes
- Fix 3 minor memory leaks: clear respawn timers in stop(), clean up persistDebounceTimers on session cleanup, reset _parentNameCache on SSE reconnect
0.2.4
Patch Changes
- Fix tunnel button not working: settings PUT was rejected by strict Zod validation when sending full settings blob; now sends only
{tunnelEnabled}. Added polling fallback for tunnel status in case SSE events are missed.
0.2.3
Patch Changes
- Fix tunnel button stuck on "Connecting..." when tunnel is already running on the server
0.2.2
Patch Changes
- Update CLAUDE.md app.js line count references
0.2.1
Patch Changes
- Integrate @changesets/cli for automated releases with changelogs, GitHub Releases, and npm publishing
0.2.0
Minor Changes
- Initial public release with changesets-based versioning