337 Commits
Author SHA1 Message Date
Codeman maintainer 8e6abf4bfd Merge pull request #592: feat(cli-registry): GitHub Copilot CLI as a run mode
GitHub Copilot CLI (`copilot`) becomes a run mode: Run menu, welcome
screen, cron and CLI management once it is installed, tmux-backed like
the other agent CLIs, working/idle and the model read off its footer,
launch options through `copilotConfig` (model, resume, continue, the
multi-user-clamped allowAll), Custom Model Endpoints via Copilot's BYOK
variables, MCP sync of its mcp-config.json, a status probe, and the
`codeman agent` verbs (spawn waits for its composer, read returns its
last answer).

The plan-usage quota row that earlier rounds carried was split out on
review and is not part of this merge.

Merge-time commits on top: the wider COPILOT_* clamp and the zh-CN
README rows (maintainer), the Docker sign-in docs, divider rows inside
an answer kept by `agent read`, route tests for the screen-read
last-response branch, and doc drift (Past Sessions, wheelForward JSDoc,
COPILOT_/OMP_ in the env prefix lists).

Verified before merge on Copilot CLI 1.0.98: injected keystrokes,
bracketed paste and Enter reach the TUI in tmux (github/copilot-cli#4180
does not reproduce), a shell tool call completes inside tmux (#4223 does
not reproduce), and a session driven through this branch answered over
send-and-wait.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:17:06 +02:00
Codeman maintainer d55267a07c Merge pull request #648: fix(antigravity): show the working indicator while an agy turn runs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:16:43 +02:00
Codeman maintainer d2f6021132 Merge pull request #646: feat(sessions): launch every Claude session with --name set to its tab name
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:16:41 +02:00
Codeman maintainer 0adce48f1a fix(#651): name _admitPromptAttachments() as the admission entry point in the docs
CLAUDE.md (Prompt uploads) and architecture-invariants still named
_promptAttachKind() as the filter every surface reads. That stays true, but
the surfaces now admit through _admitPromptAttachments(), which is what says
how many files a pick skipped. A new surface that filtered on the classifier
directly would drop files silently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:05:38 +02:00
Devvyn 76479fdc96 feat(sessions): make pinning every tab name an opt-in setting
Add pinCliSessionName (synced, default OFF), re-read at each spawn and respawn.
Off keeps the #457 behaviour; on pins every non-blank tab name as --name.
Update the docs, JSDoc and changeset that stated the old rule.
2026-10-18 13:58:09 +08:00
Devvyn 740474ebca fix(copilot): Free plan usage, tall answers in agent read, page-key fallback, review nits
Review of 8659ba25..be987fbb:

- Usage: a Free account answers premium_interactions {entitlement: 0, remaining: 0,
  percent_remaining: 0, unlimited: false, has_quota: false}, which read as 100% used. The parser now
  returns null when has_quota is false or the entitlement is not positive, with that payload as a
  fixture. Free meters chat (200) and completions (2000) too, so the comment no longer says they
  are unlimited on every plan.
- agent read: rows were skipped until a `❯ <prompt>` row, so an answer taller than the pane
  (prompt scrolled off the top) read as "". With no prompt row AND no banner / trust dialog on
  screen, the rows above the composer are now the tail of the last answer. Text typed in the
  composer (a prompt-shaped row between the two rules) is not a prompt row.
- Page keys: measured on 1.0.95 in tmux that Copilot pages on PageUp/PageDown (three PageUp moved
  lines 109-140 to 010-042, PageDown returned) as well as on SGR wheel reports, so copilot joins
  claude and opencode in _localScrollbackIsHollow(); the stale-tracking-flag fallback the comment
  and docs describe is now true, for the main pane and a TerminalTile (same function), with tests.
- X-GitHub-Api-Version is 2022-11-28, a version GitHub supports (verified against the live
  endpoint); readPlanUsageTelemetryEnabled() is checked before isCopilotAvailable() so a switched-off
  chip skips the resolver probe too.
- Palette label for Copilot in panels-ui.js; stale comments in types/session.ts and stock.ts.
- Docs: Past Sessions continues with --continue (resumeSessionId is for API callers), nothing sets
  continueSession on a respawn, and only a tab name the user set is passed as --name; the usage
  wording for Free; the wheel and mouse-strip paragraphs in CLAUDE.md and
  architecture-invariants; the changeset describes the whole feature.
2026-10-18 00:47:54 +08:00
Devvyn 4a47ead824 fix(antigravity): show the working indicator while an agy turn runs
agy animates an 8-dot braille spinner (⣯ ⡿ ⣻ ⣾ ⣷ ⣟ ⢿ ⣽) with a label one row above
the composer, none of which is in SPINNER_PATTERN, and the entry declared no
workDetect, so no frame ever marked a tab working.

Declare the working line from the live pane and the footer hint `? for shortcuts`
as the idle-arming mark. The composer's `>` cannot be the glyph: the echoed
prompt starts with it too. Tests use verbatim captures from agy 1.3.3.
2026-10-18 00:45:52 +08:00
Codeman maintainer 5483811753 Merge pull request #625 from Ark0N/feat/git-worktrees
feat(worktrees): create, open and remove git worktrees of a case

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 02:00:32 +02:00
Codeman maintainer c76600315e Merge pull request #624 from Ark0N/feat/codex-blocked-detection
feat(codex): raise codex approval, question and trust dialogs as BLOCKED

Conflict resolved against #634 in src/session.ts _probePaneWorking():
#624's assignment (a dialog on screen is never work) is kept, and #634's
trust-dialog verdict block (log the hold once, remember the reading)
follows it, before _readWatching().

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 02:00:29 +02:00
Codeman maintainer 9aa3083280 Merge pull request #634 from JDProfresh/fix/idle-not-on-trust-dialog
fix(idle): hold idle while the workspace trust dialog is on screen

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:59:55 +02:00
Codeman maintainer eec2895dd7 Merge pull request #635 from JDProfresh/fix/trust-dialog-deferred-read
fix(trust-dialog): re-read a throttled trust-dialog scan instead of dropping it

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:59:55 +02:00
Codeman maintainer d080004436 Merge pull request #617 from opticon454/feat/custom-keybindings
feat(shortcuts): rebindable previous/next session, duplicate-chord refusal, rebinds stay out of the PTY

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:59:51 +02:00
Codeman maintainer 8084cae0fe Merge pull request #628 from Randalix/fix/codex-submit-paste
fix(tmux): submit text as a bracketed paste so Codex does not swallow Enter

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:59:46 +02:00
Codeman maintainer bea9865cff fix(#625): never remove a worktree that is, or holds, a Docker case workspace
The Docker rule was applied to the CASE folder and its common git dir
only. A Docker case can be linked to any host folder, including a managed
worktree (`<repo>/.claude/worktrees/<name>`), which is a natural setup for
running one of the parallel agents in a container. Removing that worktree
from the local case's dialog, or through Close Session's "Kill & remove
worktree", ran `git worktree remove`, which runs a git status INSIDE the
tree (container-written files, checkout filters on this machine) and then
deletes the whole tree, ignored files included, while the container still
mounts it. Reproduced on an isolated instance: DELETE answered 200 and the
Docker case's workspace was gone.

That contradicts the invariant the PR itself documents in CLAUDE.md ("git
never runs ... at or inside a Docker workspace"). removeCaseWorktree now
refuses (400) a present worktree that is, sits inside, or holds a Docker
case workspace, before any git runs; the DELETE route passes the Docker
roots it already reads for the case check. A worktree whose folder is gone
is unaffected (git only drops its record there). Unit and route tests
cover both directions and an unrelated Docker workspace; docs updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:36:45 +02:00
JD 2a5f5b30be fix(idle): hold idle while the workspace trust dialog is on screen
The dialog's own prompt glyph armed idle confirmation and the quiet dialog passed it, so a fresh case reported idle about 4 s in. An agent that waited for idle and then prompted typed into the dialog, and its Enter confirmed No, exit and killed the pane.
2026-10-14 22:16:49 -04:00
JD 892c2fc4df fix(trust-dialog): re-read a throttled trust-dialog scan instead of dropping it
The scan must schedule its own next read rather than wait for PTY output, but a call inside the throttle was simply dropped. The dialog's own paint is often the last output a launching pane produces and lands inside that throttle, so the session sat on the dialog with no keystroke ever sent. A stopped session no longer scans, since the kill's own output kept it answering a dialog in a dead pane.
2026-10-14 22:03:15 -04:00
RandalixandClaude Opus 5.5 7fd76f06c3 fix(tmux): type a single-character submit so a dialog answer selects
Claude Code's and Codex's selection dialogs ignore a bracketed paste, so
after the previous commit a pasted `3` + Enter confirmed the highlighted
option instead of option 3. sendInput() now types a single character with
`send-keys -l` as master did (submitsAsKeys()). One character cannot form
the burst Codex reads as a paste, so the Codex fix is unaffected.

The trailing-`;` escape moves into escapeTmuxTrailingSemicolon() and now
also covers every `send-keys -l` call in sendInput() and
sendInputToPane(); tmux 3.5a types `x;` for `send-keys -l 'x\;'`.

Docs: the @fileoverview lines and orchestrator-loop-architecture.md still
described `send-keys -l`; CLAUDE.md, architecture-invariants.md and
cron-guide.md name the single-character exception.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 21:23:58 +02:00
RandalixandClaude Opus 5.5 344edc942b fix(tmux): submit text as a bracketed paste so Codex does not swallow Enter
sendInput() typed the text of a text+Enter pair with `send-keys -l` and
sent Enter 50 ms later. Codex detects an unmarked paste from a fast
character burst and folds an Enter inside that window into the paste as a
newline, so the prompt stays in the composer unsubmitted. Measured with
codex-cli 0.162.1 in a throwaway tmux pane: 1505 chars stuck 3/3 as
`[Pasted Content 1506 chars]`, a 98-char line stuck 1/4. SubmitVerifier
re-presses Enter for the short case, but it cannot match the long one: the
composer shows the placeholder instead of the prompt's head.

The text now goes in via set-buffer + `paste-buffer -p -d` (markers only
when the pane requested bracketed paste), followed by the separate Enter as
before. Same pane, same texts: 4/4 and 3/3 submitted. Each send gets its
own named buffer; a paste that fails (pane or server gone) deletes it,
since tmux never trims named buffers on its own.

Also escapes a trailing `;` in the text, which tmux treats as a command
separator (`send-keys -l 'x;'` typed `x`, a lone `;` typed nothing).

sendInputToPane() has no callers in src/ and is left as it was.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 10:20:01 +02:00
Codeman maintainer 1c1658d039 fix(codex): skip the blocked re-read while the pane is quiet
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>
2026-10-14 05:53:58 +02:00
Codeman maintainer 28cbbad591 fix(worktrees): the injected agent skill no longer blocks removal
With Codeman Agent Skill on (agentSkillEnabled), every session create
writes the skill into the folder the session runs in, so a session in a
worktree leaves an untracked .claude/skills/codeman/ (claude) or
.agents/skills/codeman/ (codex, gemini and the rest) behind. git then
refuses `git worktree remove` for that worktree forever, and an agent
running `git add -A` there would commit Codeman's skill into its branch
(reproduced: the worktree's status showed "?? .agents/" and
"?? .claude/").

A create now excludes <skills dir>/codeman/ next to the hooks file, for
every skills folder the registry names (capabilities.agentSkillDir,
read at call time by the route and passed in as data, so no CLI id is
branched on). Like the other lines it is only added when git does not
already ignore the path, and only to the repository's local
info/exclude.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 05:37:48 +02:00
Codeman maintainer 64fea4ff84 fix(worktrees): a worktree whose folder was deleted can still be removed
When a managed worktree's folder is deleted by hand (a user in a file
manager, an agent's rm -rf), git keeps its record and lists it as
prunable. Codeman then had no way out: the dialog showed the row as
"missing" with no action, DELETE answered "No worktree named ...", and
the name could never be used again (the branch still existed, and with
another branch git refused "a missing but already registered
worktree"). Reproduced on git 2.43; git's own `git worktree remove` on
the missing path drops the record with exit 0.

Removal (only removal, never a session start) now accepts a worktree
whose folder is gone when git lists it at exactly the managed path, as
prunable or locked, and no component on the way is a symlink or a file.
The live-session and lock refusals still apply. The dialog offers
Remove... (not Open) on such a row, with a note saying what it does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 05:34:49 +02:00
Codeman maintainer 9b3030f9a1 docs(codex): blocked dialogs read off the screen
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>
2026-10-14 05:19:14 +02:00
Codeman maintainer 3952963bad docs(worktrees): API reference, wiki page and the invariants behind them
Documents the worktree endpoints and quick-start's worktree field in
docs/api-reference.md, adds a Git Worktrees page to the wiki (sidebar,
Home index, a pointer from Core Concepts where sessions sharing one
case folder is explained), and records the rules that keep the feature
safe in docs/architecture-invariants.md with a short CLAUDE.md pointer:
only the managed .claude/worktrees path is ever touched, git never runs
over a Docker workspace, removal is never forced and branch delete is
-d only, and sessions start through the existing create paths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 05:16:10 +02:00
JD 244d9a49f1 feat(uploads): prompt uploads take documents
The Attach button, the Compose and paste-dialog pickers, terminal drop and the
clipboard now take documents beside images and videos: the office formats
pdf/docx/pptx/xlsx/zip and the text family txt/md/csv/tsv/json/log/yaml/yml/xml.

A document is admitted by its name's extension (the MIME a phone attaches to a
.md is anyone's guess), buffered under the same 50MB image cap, and judged on
its bytes by the same fail-closed sniffer: a magic-number signature for the
binary formats, UTF-8 with no NUL byte for text. It is saved under a sanitized
stem of its name, so Q3 report.pdf is findable as paste-<ts>-<hex>-Q3-report.pdf.

sh/py/js/html are refused by name with a clear 400. That is a scope choice, not
a security property: file-raw already serves non-media text inert, so this keeps
the server from storing what it would serve or an agent could run, and html and
svg are the ones deliberately kept out. One client classifier (_promptAttachKind)
now returns image, video or document, and the server's dispatch mirrors it: an
image MIME wins whatever the name says, a document extension takes the document
branch, and a foreign extension with no image MIME is refused only after the
image sniff fails, so nothing the image path accepted by its bytes is lost.
2026-10-13 20:31:39 -04:00
Codeman maintainer 29f76e7ec3 docs: split CLAUDE.md into root, src/web/CLAUDE.md and docs/releasing.md
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>
2026-10-13 22:43:45 +02:00
Devvyn 3776bbd431 fix(shortcuts): address review on rebinding (gate holes, re-enable/Reset duplicates, labels, tests)
- isUserBoundShortcutEvent skips toggleSessionSidebar (it has its own gate) and an
  entry re-captured onto its own default, so ^L still reaches the CLI.
- Re-enabling a shortcut and Reset run the duplicate check and refuse when the
  chord was taken meanwhile; Ctrl/Shift+Enter and Ctrl/Cmd+V are reserved with
  Alt+1-9, and the Alt-digit check no longer excludes Cmd.
- Alt+[ / Alt+] keep their Alt/Option+[ labels via displayBindings.
- Behavioural tests (vm harness) replace the source-string test.
- Docs: wiki Rebinding / Why-chords-behave-oddly, architecture-invariants note.
- Small: _shortcutActions indentation, stale Alt-block comment, typeof check
  instead of Object.hasOwn, full-width parentheses in zh-CN.
2026-10-14 02:47:58 +08:00
Codeman maintainer 91750d4bd5 docs(tiles): Detach Tiles known limits (#584 merge-time fix)
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>
2026-10-13 18:59:56 +02:00
Codeman maintainer 9ad43fbd88 Merge pull request #584: feat(tiles): move tiles by hand, and detach a tile into its own window 2026-10-13 18:57:50 +02:00
Codeman maintainer 0996f99fbb Merge branch 'rel1411/html-md': render HTML files as pages (#547) and open .md from the terminal in the rendered viewer
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>
2026-10-13 18:55:02 +02:00
Codeman maintainer 6c7d63d45e Merge pull request #606: feat(mobile): phone Attach button and camera-roll video uploads
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>
2026-10-13 18:21:52 +02:00
Codeman maintainer 6bbfc44054 Merge pull request #605: fix(uploads): probe the workspace bounded before a prompt upload
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>
2026-10-13 18:20:21 +02:00
Codeman maintainer a38eab49cd Merge pull request #528: feat(tabs): place new, child and web tabs into groups
Conflict resolved in docs/api-reference.md: #607's prompt cache section and
#528's tabGroupId section are both kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:10:37 +02:00
Codeman maintainer 90034d07d7 Merge pull request #604: feat(preview): open ~/, relative and hard-wrapped paths; folder grid 2026-10-13 18:08:12 +02:00
Codeman maintainer 62227145dc Merge pull request #607: feat(telemetry): read out Claude Code's prompt cache state per session 2026-10-13 18:07:15 +02:00
Codeman maintainer 0e11012470 Merge pull request #527: fix(terminal): page a hollow Codex transcript with PageUp/PageDown from the first wheel event
Conflict resolved in terminal-tile.js's @dependency header: #601's
SHELL_LOAD_BASE_QUERY/SHELL_LOAD_QUERY and #527's pageKeysForGesture and
app.tabAlerts are all listed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:06:24 +02:00
Codeman maintainer 5b8313988f fix(terminal): no paneHistoryLines for a remote session (#583/#602 integration)
A remote session's local pane hosts the remote tmux client in the alternate
screen, so its history_size is always 0. #583 read that as "nothing to load"
and hid the partial-history notice, but with #602 a full=1 load reads the real
scrollback from the remote tmux. The route now leaves the field out for remote
sessions, so their notice keeps the byte-based wording and its Load full
history button.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:05:30 +02:00
Codeman maintainer f9f00bdb5e Merge pull request #602: fix(remote): load a remote session's scrollback from the remote tmux
A full-history load of a remote SSH session reads the scrollback from the
remote tmux in one bounded ssh round trip and splices it above the local
frame. A transport failure (ssh 255, timeout, unparseable output) starts a
30 s per-host back-off; a missing remote session is per-session only.

Conflict resolved in session-routes.ts: #602's remote scrollback read runs
before #601's capture helper and its alternate-screen fallback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:01:15 +02:00
Codeman maintainer 78ed6bf004 docs(terminal): only a full=1 pass without alt=recording is never mux-visible (#601 merge-time fix)
The geometry-retry comment in selectSession and the #435 invariant said the
route never answers full=1 with mux-visible; a shell load's alt=recording on
an alternate-screen pane now is. The conclusion holds (a useFullHistory pass
never sends it), so only the stated reason changes. Also lists
SHELL_LOAD_BASE_QUERY in terminal-tile.js's @dependency header.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:59:20 +02:00
Codeman maintainer 36f8afdaba Merge pull request #601: fix(terminal): load a shell from tmux's rendered history, not the byte recording
Conflicts resolved against #583 and #588:
- One history-depth field: #583's `historyLines` / `capturedHistoryLines`
  (reported as `paneHistoryLines`) is kept, and #601's lines cut now reads it
  instead of a second `capturedHistorySize`. #601's `alternateOn` /
  `capturedAlternateOn` joins it, and the one display-message format string
  ends `#{history_size} #{alternate_on}`.
- selectSession keeps #588's full=1 switch for panes with no scrollback, gated
  off remote and Docker sessions, and #601's SHELL_LOAD_QUERY for shells.
- computeHistoryTruncationNotice names a line count when paneHistoryLines is
  known (#583) and otherwise uses #601's remainder wording.
- docs: api-reference lists both historyLinesCut and paneHistoryLines; the
  notice invariant keys on history_size while the shell's automatic load is
  the one place alternate_on decides.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:59:07 +02:00
Codeman maintainer 1855d9423b fix(terminal): remote and Docker tabs keep the byte tail on a switch (#588 merge-time fix)
A remote (ssh) or Docker session's local pane hosts a nested tmux client in
the alternate screen, so it always reports paneHistoryLines 0 while its byte
tail carries the real scrolled-off output. The new full=1 switch therefore
replaced their scrollback with the visible frame on every revisit. Those
sessions now keep the tail; the pinned test string and the invariants note
say so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:49:43 +02:00
Codeman maintainer 7ba1e51c51 Merge pull request #588: perf(terminal): tab switch to a fullscreen Claude tab loads only its current screen
Conflict resolved in config/test-suites.ts: both new browser test globs kept.
2026-10-13 17:49:16 +02:00
Codeman maintainer f2d5af2b4c Merge pull request #583: fix(terminal): partial-history notice only when there is history to load 2026-10-13 17:47:37 +02:00
Codeman maintainer 50a1b58d2b Merge pull request #613: Give the codeman agent skill to every agent CLI, not just Claude Code 2026-10-13 17:42:58 +02:00
Codeman maintainer ba5649bcd4 feat(html-view): open network access for rendered pages (owner decision)
Owner decision 2026-10-13 (#547): a rendered .html page behaves like a
file opened from disk in a browser. It may load scripts, styles, fonts,
images and media from any host and make fetch, XHR, WebSocket, beacon
and form requests to any host.

The /html-view Content-Security-Policy is therefore the `sandbox`
directive alone (HTML_VIEW_CSP, now exported and documented): the same
sandbox flags as before, never `allow-same-origin`, and no fetch
directive. It replaces Codeman's own `default-src 'self'` policy on these
responses. Everything that confines a page stays: the opaque origin (no
Codeman cookies or document, API writes with `Origin: null` refused, API
reads not readable cross-origin), and the capability serving only the
page's own folder under every path, symlink, dot, extension and
confinement rule.

A new test builds the route behind registerSecurityHeaders, as in
production, and pins the exact header: one `sandbox` directive with the
current flags, no `allow-same-origin`, no `-src` directive. The security
architecture, architecture invariants, API reference, CLAUDE.md and the
Working With Files wiki page now say what a page can do (reach the
network, read its own folder) and cannot do (Codeman's origin, cookies,
API, other folders), and that this is the owner's decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:39:36 +02:00
Codeman maintainer 79b9181932 feat(file-preview): open markdown clicked in the terminal in the rendered viewer
An in-workspace .md path clicked in the terminal went to the log viewer
(tail -f, a "streaming" header over raw text), because md was kept out
of FILE_PREVIEW_EXTENSIONS on purpose; every other surface already
rendered it. Owner decision 2026-10-13: markdown is a document to read,
so md and markdown join FILE_PREVIEW_EXTENSIONS and a click or tap opens
the file preview, rendered by default with the MD pill for source. The
main pane, every grid tile and the split's Pane B share the link
provider, so all of them route the same way. Other in-workspace text
(.log, .txt, .json, code) keeps the log viewer and its live follow.

FILE_PATH_LINK_PATTERN now also links `.markdown` and `.htm`, which
were not clickable at all.

Tests: file-preview-markdown.test.ts now asserts md and markdown ARE
routed to the preview (and plain text is not); terminal-touch-tap pins a
tapped .md, .markdown and .html opening the preview while .log, .txt and
.json open the log viewer, and a tile-registered provider routing the
same way against the tile's own session. Docs: CLAUDE.md, the
architecture invariants and the wiki (Working With Files, Mobile Guide).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:19:02 +02:00
Codeman maintainer fa23f8be2a feat(html-view): answer Range requests through the shared sendFileBody()
A rendered page's own <video> or <audio> could not seek, because the
route streamed a plain 200 without Accept-Ranges. sendFileBody() and
localFileSource() are now exported from file-routes.ts and the serving
route uses them, so it advertises Accept-Ranges and answers a Range with
206 + Content-Range (or 416), exactly like file-raw, the attachment raw
route and /api/download. The body sender hijacks the reply and copies
every header set before it (the sandbox CSP, CORS, no-store), which the
new test pins on a 206.

Media types stay in ASSET_TYPES: with Range support they work, and they
are served under the same sandbox CSP as everything else.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:11:50 +02:00
Codeman maintainer 69aa5724e7 fix(html-view): scope the hidden-directory rule below the workspace root
The mint refused an HTML file whose absolute path had any dot segment, so
a workspace that itself lives under a hidden folder (~/.local/src/app)
could never render one of its own pages. For a file inside the workspace
the rule now covers the path below the workspace root, which is all the
page's tree can reach; outside the workspace it still covers the whole
path. A hidden directory below the workspace root is still refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:09:28 +02:00
Codeman maintainer 876dbca074 fix(html-view): re-check workspace confinement on every served request
The serving route checked the blocklist per request but not
attachmentConfineToWorkspace, while the attachment serve path
(file-routes.ts) re-checks it every time. A capability minted for a
folder outside the workspace before an admin turned confinement on kept
serving that folder for the rest of its TTL.

The capability record now says whether its folder was admitted from
outside the workspace, and the serving route refuses such a record while
confinement is on. The flag is part of the reuse key, so an
out-of-workspace mint never picks up a capability recorded as
in-workspace for the same folder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:09:00 +02:00
Codeman maintainer fe50ad0be3 fix(html-view): apply the dot and extension rules to the resolved path
The serving route checked hidden segments and the asset allowlist on the
requested name only; after realpath it checked containment and the
blocklist. A symlink `cfg.json -> .git/config` inside the page's folder
was therefore served as 200 application/json. The dot rule now also runs
on the segments of relative(rootDir, resolvedPath), and the extension
allowlist on the resolved file name, which also types the response.

Both realpath calls on the request path (the mint of an out-of-workspace
file and the serving route) are now `await fs.realpath` instead of
realpathSync, so a slow mount stalls one request, not the event loop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:08:13 +02:00
Codeman maintainer ded7911d1e fix(html-view): refuse /tmp, /var/tmp and directories above a broad root
os.tmpdir() returns $TMPDIR when it is set, and macOS sets it in every
terminal session, so with TMPDIR set /tmp, /private/tmp and /var/tmp were
never treated as broad and /tmp/report.html got a capability over all of
/tmp. The broad-root list now names /tmp and /var/tmp literally (through
canonical(), so macOS gets /private/tmp), refuses any directory that is an
ancestor of a broad root (/home, /Users, /private), and treats a drive's
mount point (/mnt/d on WSL, /media/x, /Volumes/x) as broad as well.

The new test sets TMPDIR elsewhere and still gets /tmp refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:07:36 +02:00