Releases: Ark0N/Codeman
codeman@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 (
codeman@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
codeman@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
codeman@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
codeman@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
codeman@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):
codeman@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
codeman@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
codeman@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
codeman@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