On codex-cli 0.162.0, with 0.163.0 released, every launch first shows the
"Update available" prompt, and choosing Skip leads to a variant of the
workspace trust prompt: "2. Back to Agent Command Center" over the key-hint
row "enter continue · esc back". The pattern only accepted cancel,
interrupt, quit, answer or all as the closing word, so a fresh git repo
launched from Codeman sat on that trust prompt reading idle, with no red
alert and no blocked signal (measured live on a throwaway instance).
Adding "back" is safe because the hint row must still START with one of
the anchored hints ("enter continue" here): the popups the user opens at
the keyboard (/model, /permissions, /review, Implement this plan?) end in
"esc back" too, but their row starts with "enter select", so they stay
unmatched, as every existing negative fixture confirms. The new fixture is
the live capture with the scratch path replaced.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The README (both languages) still said only claude and deepseek sessions
emit `blocked` and that codex accepts only idle, working and exit, and the
skill's Flow 5 called `blocked` claude-mode only. Codex now raises it from
its screen-read dialogs; the recipe says to watch it with
`until=blocked,exit` (codex has no `stop`) and to answer in the terminal.
The plugin mirror is regenerated with scripts/sync-plugin.mjs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`GET .../wait` answers `blocked` at once for a codex pane showing a dialog,
because a screen-read dialog is a level the server is still reading. The
duplicate branch of `POST .../input` (a tagged retry after a dropped
connection, answered from current state) still used the plain
`currentSignalFor()`, so a retry with `wait: "blocked"` on a pane the first
delivery had already blocked waited out its whole timeout for an edge that
had passed. It now uses `currentSignalForWait()` like the GET route; a
caller that asked only for `idle` still gets `idle`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
While a codex dialog was up, the session re-read its screen every 2 s with
a synchronous `tmux capture-pane`, for as long as the dialog stayed up. That
read costs about 8 ms of blocked event loop (measured 2026-10-14), and an
approval can sit unanswered overnight: 1,800 execs an hour per waiting
session, measured as 300 in 10 quiet minutes in the unit test.
A dialog leaves the screen only through a repaint, and tmux sends every
repaint to the session's client as PTY output. So while the last reading
saw the dialog and nothing has been drawn since, it is still up and the
read is skipped; a safety-net read still runs every 30 s
(BLOCKED_QUIET_RECHECK_MS). The clear keeps its 2 to 4 s latency, because
the answer itself is output. Never skipped once a reading has missed the
dialog: tmux updates the pane before it sends the repaint, and the
confirming read must not wait for output that may already have arrived.
The session-state tests now change the fake screen the way a real pane
changes (with output), and two new tests pin the skip and the confirming
read (the second fails with the guard removed).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Document what the two previous commits changed for users and for whoever
adds the field to another CLI:
- docs/cli-registry.md: the `blockedLine`/`blockedLines` fields, how codex's
pattern was measured, and the three rules any declaring CLI keeps (end on
chrome only the CLI draws, alert only; raise on one reading, clear on two;
hooks win).
- docs/architecture-invariants.md: a "Screen-read blocked dialogs (codex)"
entry, plus the wait-signal, idle-detection, watching and Approvals Inbox
entries it changes.
- docs/api-reference.md, docs/extending-codeman.md and the wiki (Agent CLIs,
Notifications and Approvals, Hooks, HTTP API, Driving Codeman From An
Agent): `blocked` now fires for codex, `stop` still does not, codex
approvals are alert-only cards answered in the terminal.
- CLAUDE.md: `stop` and `blocked` are no longer one rule, the config-regex
list gains `blockedLine`, and a short pointer that a codex dialog match is
only ever an alert.
- The codeman skill's signal table and verbs note, mirrored into
plugins/codeman by scripts/sync-plugin.mjs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A codex dialog the session now reads off its screen gets exactly what a
Claude `permission_prompt` hook gets, so every surface that already handles
that event needs no second path: `web/screen-blocked.ts` turns the session's
`blockedChanged` edge into the `blocked` wait signal, an Approvals Inbox
item, the `hook:permission_prompt` broadcast (with `source: 'screen'`), web
push and webhooks, and a run-summary row. That lights the red "needs you"
tab alert and desktop notification, the phone overview's NEEDS YOU row and
the TUI. When the dialog leaves the screen the item it opened is resolved,
and `approval:resolved` clears every client.
It is an ALERT and never a keystroke, following #610's shape for DeepSeek:
the item is `answerInTerminal`, push carries no `approvalId` (so no
Approve/Deny), and the answer route refuses it. The dialog was read from a
screen the agent writes most of, so a forged match must cost a false alert
and nothing more. The message every surface shows is built from the CLI's
registry label, never from the dialog's text. The item gets no `capture`
callback, only its frame as context, because the inbox's option parser and
staleness sweep are shaped for Claude's dialogs and the session owns this
item's lifetime. A CLI whose hooks report blocked gets nothing from the
screen, so declaring both could never alert twice.
The answer route now checks `answerInTerminal` BEFORE its hooks gate and
names the CLI: a codex session has no hooks, and "Session mode cannot have
pending approvals" would be false about the item the caller holds.
Wait signals: `blocked` is available where hooks report it OR a CLI declares
`blockedLine` (`blockedSignalAvailable()`); `stop` stays hooks-only, since
nothing on a screen says a turn ended, so `until=stop` on codex is still a
400 (now suggesting `idle, blocked or exit`). A screen-read dialog is a
level the server keeps reading, so a `GET .../wait` that asks for `blocked`
while one is up answers at once; a caller that asked for `idle` alone keeps
getting `idle`. The MCP and `codeman agent` help text say so.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Codex is the one CLI Codeman launches with its approvals on by default, and
it declares `hooks: 'none'`, so nothing tells Codeman it stopped to ask. A
codex session parked on "Would you like to run the following command?" read
as plainly idle, and one parked on a question read as WORKING, because the
question's own key hints end in `esc to interrupt`, codex's working line.
This adds a generic, optional registry field,
`capabilities.workDetect.blockedLine` (plus `blockedLines`, the window), and
the session state that reads it:
- `isBlockedScreen()` (session-activity.ts, pure) joins the last
`blockedLines` non-blank rows of a capture and tests the pattern once, so a
pattern can demand several markers and anchor the dialog's footer on the
end of the window.
- `Session` reads it off the same capture the idle confirmation already
takes, emits `blockedChanged`, and exposes `screenBlocked`. It raises on
one reading, and while a dialog is up re-reads the screen every 2 s,
because answering resumes the turn and a busy pane would not reach the
settle path again until the turn ended. It clears only after two readable
captures in a row without the dialog, so a capture taken mid-repaint cannot
clear and re-raise it; an unreadable capture changes nothing. A blocked
screen wins over the working line in the same capture.
- The pattern goes through `compileVersionRegex()` in both the schema refine
and at use (lazily cached), like every other config regex.
Codex's pattern was measured on a live codex-cli 0.162.0 pane on 2026-10-14
(`-a on-request -s read-only`, widths 200 down to 36): it needs a `› N.`
option row and then the dialog's key-hint row as the last row, allowing for
that row to wrap below about 60 columns. Command, edit and network-escalation
approvals, single and multi-question dialogs and the startup trust prompt
match; the idle and working views, menus the user opened (`/model`,
`/permissions`, `/review`), the composer holding the dialogs' words and a
transcript quoting them verbatim do not. `Implement this plan?` is left out
on purpose: its footer is the same as `/model`'s and the turn has ended
there. The captures are kept as fixtures (trailing whitespace trimmed, the
scratch path replaced).
Nothing consumes `blockedChanged` yet; the next commit raises the alert.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only GIFs that a public page embeds stay in docs/images/: the five README
GIFs plus subagent-demo-20260724.gif, which the published "mission control"
article hotlinks from master. Seven GIFs nothing embeds leave the tree
(their masters are kept in the maintainer's local media library).
.gitignore now blocks *.mp4/*.webm/*.mov/*.m4v/*.mkv/*.avi/*.gif outside
docs/images/*.gif (and the promo branch's Remotion inputs), so a video can
only be committed with an explicit `git add -f`.
docs/media-assets.md records the rule: what is tracked and where it is
embedded, website media (pr/website/assets, deployed separately), release
clips on the design-assets branch, and the local library layout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The installer's three up-front questions, the :443 fallback, the
optional tailnet rename, command-line flags and install.sh status; the
installer's closing QR note in the Tailscale tip; Diagnostics in
Settings, the bottom-bar git status and creating a case in a custom
folder; docker load in the Docker export bullet; and the installer
note on the loopback default. The Chinese README now carries the same
content as the English one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mirrors the English README update: tile grid ranking, saved layouts,
tile animations, wheel scrolling in tiles, Detach Tiles and gesture
moves; rail search, tab groups and Focus; HTML pages, the folder grid
and clickable ~/ and relative paths; .codeman-uploads and the phone
Attach button; OSC 52, the prompt-cache readout, MCP server sync with
Copilot CLI, Settings Apply and notification display times; remote SSH
scrollback; the sandboxed HTML page view in Security; Ctrl+W and the
tile shortcuts in the shortcut table.
Adds what the Chinese README never had: the `codeman agent` and
`codeman mcp` sections, the agent skill for every CLI (install table,
--mode, the Codeman Agent Skill setting, the skill GIF), and the
refreshed dashboard tour and tab-states images.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tile grid (session ranking, saved layouts, tile animations, wheel
scrolling in tiles, Detach Tiles, gesture moves), rail search, tab
groups and Focus, HTML pages and the folder grid, clickable ~/ and
relative paths, .codeman-uploads and the phone Attach button with
videos, OSC 52, the prompt-cache readout, MCP server sync with Copilot
CLI, Settings Apply, notification display times and remote SSH
scrollback.
Also corrects three stale claims: HTML now renders in the sandboxed
page view, Ctrl+W has not closed a session since 1.40.0, and the tile
shortcuts were missing from the table.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The root CLAUDE.md had grown to 202 KB, all of it loaded into every
session. This lands the split drafted on 2026-10-10 (it sat uncommitted
in the shared checkout through two releases), redone on top of today's
master so nothing that landed since is lost:
- src/web/CLAUDE.md: the frontend invariants (every src/web/public/ UI
subsystem, the module notes, the XLSX preview limits, the z-index
stack). Claude Code loads it when a file under src/web/ is read; the
root file tells sessions to Read it before editing src/web/public/.
Its link targets are relative to the file so they resolve on GitHub.
- docs/releasing.md: the COM procedure (now nine steps, with the MCP
registry publish), behind a pointer in the root COM section. The
Version line stays in the root file.
- Root CLAUDE.md (130 KB): everything else, plus the split's trims: the
Core Files table, the reference-only rows of the Additional Commands
table, Data Flow and the respawn presets, whose rules already live in
the root's other paragraphs (the "Read before touching a subsystem"
line keeps the table's doc pointers).
Every other paragraph of master's CLAUDE.md lands in exactly one of the
three files, checked line by line; the paragraphs master changed since
the draft (tile grid, OSC 52, prompt uploads, file-path links and the
HTML view, markdown preview, z-index, MCP server, agent CLI and others)
are in their current form. Both new files join CLAUDE.md in
.prettierignore. Four code comments and docs/architecture-invariants.md
now point at src/web/CLAUDE.md for the moved sections.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`codeman mcp` is listed in the official MCP registry as
io.github.Ark0N/codeman (first published for 1.41.1). The registry does
not follow npm, so without a step in the COM procedure the listing stays
on the version it was last published with. The step also records the
two traps hit on the first publish: the Snap Store mcp-publisher (1.1.0)
rejects the current server.json schema with a false "deprecated schema
detected", and older publishers save live login tokens into the current
directory as .mcpregistry_* files.
The announcement step becomes step 9.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 800px GIF rendered the header stats strip (CPU, MEM, 5H/7D) at ~5px
text height. The 1600px source shown at width=800 stays sharp on high-DPI
screens. New filename so GitHub's image cache serves the new file.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
One consolidated patch changeset for the 24 PRs in this release (the PRs'
own changesets folded in), CHANGELOG, package and plugin manifest versions,
and the CLAUDE.md version line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
One stored grid per browser shared by every Codeman window, no cross-profile
notice (BroadcastChannel is per profile), and the user-activation window that
blocks a pop-out from a drag-out held too long.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
_dockForeignTile posted close-request only when this window knew the session
was popped out. A pop-out opened from another dashboard window was not in this
window's detachedSessions, so it stayed open beside the newly docked tile and
two views sized one PTY. The request now goes out for every docked id;
dashboards ignore it and only that session's pop-out reacts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
_onTileAdoptedElsewhere acted only on a tear-off, a pending tear-off or a drag
that just ended. A header drag carries the tile id even when this window has
Detach Tiles off (no tear-off), so another window could dock the tile and its
claim could arrive before this drag ended; the tile then stayed here as well.
The header drag (_draggedTileId) now counts as dragging too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There is one stored grid per browser (codeman:tile-grid), and the window that
docked the tile has just written it. When this window then hears the claim and
drops the tile, removeTile (or, for its last tile, closeTileGrid with
keepStored: false) overwrote or forgot that stored grid. The removal now runs
under _tilePersistHold, and the last-tile close keeps the stored grid.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#604 makes `~/` links clickable and passes them through unchanged, since only
the server knows the session host's home. The html-view mint read `~/x.html`
as workspace-relative and answered 404, so the preview fell back to the
source view. It now expands `~/` against this host's home, as the attachment
route does, and refuses it in a Docker case (that `~` is the container's)
with the same message as the attachment and folder routes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pins the merge resolution: `~/`, relative (`./`, bare) and hard-wrapped
links to .md/.markdown and .html/.htm open the file preview (rendered
markdown, HTML page) exactly like rooted ones, while other text such as a
relative .json keeps the log viewer. Fails if the terminal's activate path
drops rendersAsPage().
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts resolved against #604, #606 and #601:
- constants.js: the rooted link pattern takes html-md's `markdown` and
`html?` with #606's m4v/ogv; #604's relative pattern gets the same list
(link-provider-regex pins the two equal); #604's findFilePathLinks /
folder helpers and html-md's rendersAsPage() are both kept, and
window.CodemanFilePaths exports all of them.
- terminal-ui.js: #604's resolved `target` (relative, ~/ and folder links)
now also goes through rendersAsPage(), so every link kind routes .html to
the page preview; .md routes there through FILE_PREVIEW_EXTENSIONS.
- CLAUDE.md, api-reference, architecture-invariants: both sides' text kept.
- test/folder-grid-ui.test.ts (#604) stubs rendersAsPage() like the other
panels-ui harnesses.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
format:check failed on src/web/server.ts and src/web/public/approvals-ui.js;
both assignments fit on one line at printWidth 120.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Three comments said `altPath` is never written, but run() makes the alt file
the write target when the primary is absent. The behaviour is fine (a JSONC
file with comments fails the parse guard and is never written); the
comments now say so, so nobody loosens the path rule on that belief.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mcpTable took any record at `mcp.servers` as OpenCode's V2 container.
OpenCode itself does so only when the value is not a V1 server (`type`
local or remote). So a server named "servers" copied from another CLI was
written as V1, re-parsed as a container, failed the read-back guard and
failed the whole OpenCode target, and an existing V1 "servers" stopped
being read. The V2 entry now also wins a name collision, as in OpenCode;
sync never writes a name already defined, so the read-back is unaffected.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
_sendBracketedPaste wrapped the text as is, so clipboard text carrying the
end marker (a pastejacking page can write one) closed the bracket early and
the rest was typed as keys, its CR running a command before review. The
paste markers inside the text are now dropped first; this also hardens the
Compose path, which shares the helper.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#606 added m4v and ogv to the rooted file-path link pattern for the new video
uploads; #604's relative path pattern copies that extension list, and its
test pins the two equal. The relative pattern now carries them too, so a
relative `clip.m4v` links like `/home/u/clip.m4v`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ZH_CN already had `Attach: '附加'` (the tile grid's and remote discovery's
Attach buttons). #606 added a second `Attach: '添加附件'` later in the same
object literal, which won and turned every Attach into "add attachment".
The duplicate is removed; 附加 covers the phone toolbar button too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ?kind=video branch called req.file(), which pipes the request into
busboy, then answered 400 (unsupported container) or 403 (upload dir not a
regular directory) without reading part.file. The server then stopped
reading the body, and a browser fetch, which reads the response only after
its upload is sent, hung on "Uploading 1/1 file" for an Android 3gp or an
.mkv. Both early answers now consume the part first, as the mislabeled
container path already did. (Reviewer-verified in Chromium: hang before,
400 in about 0.2 s after; inject and a Node client do not reproduce the hang.)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts resolved against #605 (and #602's test mock):
- CLAUDE.md Prompt uploads: #606's rewrite is kept and #605's bounded
workspace probe sentence follows the remote (SSH) refusal.
- test/routes/session-routes.test.ts: the remote-pane-history and
bounded-path-probe mocks are kept beside #606's _resetPasteRateBuckets
import, and both PRs' paste-image tests are kept.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflict resolved in test/routes/session-routes.test.ts: #602's
remote-pane-history mock and #605's bounded-path-probe mock are both kept.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The row said the path is "on the machine hosting Codeman" for every session.
A remote SSH session's workingDir is the path on the remote host, and a
Docker session's CLI runs inside the container the host folder is mounted
into. openSessionOptions now sets the description from session.remote /
session.docker (the id it targets was added with the layout fix).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The row was a plain flex `.set-row` with a non-shrinking actions column, so
the <code> took its full max-content width: the label column collapsed to
0px, the path overlapped the description and the Copy button left the row
(off-screen on a phone). It now uses the stacked `set-row-block` layout the
Session color row already uses, and the path shrinks and wraps.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#591 rebuilt the committed gesture bundle in a worktree whose node_modules sat
in a sibling directory, so its esbuild source comment named that local path.
Nothing in the PR touches the gesture source; the bundle goes back to master's
bytes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
xterm.js still parses OSC 52 introduced by C1 OSC (\x9d) and ended by C1 ST
(\x9c). OSC52_STRIP_RE matched only the 7-bit forms, so a direct-PTY session
that emitted the C1 form re-copied old text on every replay.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When navigator.clipboard is missing (plain HTTP, install.sh's LAN option) or
refuses the write (an unfocused window), _copyText falls back to a hidden
textarea whose select() takes focus, and removing it left focus on <body>:
the next keystrokes after Claude Code's /copy went nowhere. The handler now
restores the element focused before the copy, as Auto Copy does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#582 made the web-tab actions menu reachable on the flat rail (ContextMenu or
Shift+F10 on a role=tab row), but closeTabGroupMenu only looked for a tree
item or a Focus shortcut, so Escape dropped focus to <body>. A `.session-tab`
trigger now counts too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#528's browser followParent operation rebuilt the target ref as
{ kind, id } to drop its manual placement, which also dropped #582's
`focus` flag. The browser PUTs whole layouts, so the pin was erased on the
next save. The operation now copies the ref and removes only `placement`.
#528's row-menu test also expects #582's 'Add to Focus' entry now.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the follow setting off (the default), pinCreatedChild marked each new
child manual where normalization put it: right after the parent's FOLLOWING
block, which no longer holds the siblings pinned before it. A fan-out
therefore showed parent, w3, w2, w1 in the layout and the global
sessionOrder. The pinned child now steps past the siblings (and their
descendants) already after the parent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>