Commit Graph
2313 Commits
Author SHA1 Message Date
Codeman maintainer b67018b318 Merge pull request #670: fix(input): keep the line breaks of a multi-line mux prompt, pasted as one block
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:23:17 +02:00
6eaaa960ff fix(tmux): keep browser keys from switching a tab onto another session (#669)
Squash of PR #669. Browser terminals could be moved onto another session
by tmux's prefix keys: Ctrl+B then ), (, s or w switched the tab's tmux
client to a different session while the tab kept the original's name.
Codeman's tmux socket now keeps only Ctrl+B Ctrl+B (send-prefix) in its
prefix table, and codeman tui and scripts/tmux-manager.sh get F1 as their
way out instead of Ctrl+B d.

Merge-time fixes by the maintainer: on an empty prefix table the
unbind-key that fails no longer skips binding send-prefix; a key tmux lists
escaped (prefix C-\ shows as C-\\) compares equal, so the reset does not rerun on every create;
and reconcileSessions() reduces the table at boot when the socket has live
sessions, so sessions that survive an upgrade are covered too.

Co-authored-by: christianhaberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:23:17 +02:00
Codeman maintainer 620e7728ec Merge pull request #667: fix(mux): give a discovered tmux session its pane's directory, not the server cwd
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:23:09 +02:00
Codeman maintainer 6c97d63499 docs: give the composer frame's mux-fallback rule its current reason
Merge-time fix for #670.

TmuxManager.sendInput() now keeps line feeds and strips only `\r`. The
rule that the composer's bracketed-paste frame must never take the mux
fallback still holds, because the frame maps its line breaks to `\r`:
on tmux the lines weld into one, pasted inside a second pair of markers,
and get submitted. docs/architecture-invariants.md and src/web/CLAUDE.md
now say that instead of "strips every \r and \n" and "pastes the
frame's own markers inside a second paste".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:12:03 +02:00
Codeman maintainer 30a60f7bb5 Merge pull request #673: fix(mobile): keep the keyboard up when an accessory key is pressed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:02:18 +02:00
Codeman maintainer 28a7c9bdf5 Merge pull request #668: feat(limits): make the global session cap configurable with CODEMAN_MAX_SESSIONS
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:02:18 +02:00
JD d56b90b7c6 fix(mobile): keep the keyboard up when an accessory key is pressed
An accessory key that acts on the terminal (arrows, Tab, Esc, Ctrl, scroll, clear input) took focus on press, so every tap blurred the terminal and the click handler had to focus it again: a blur/focus pair per tap that started a keyboard dismissal and shifted the toolbar each time. Such a key now cancels the mousedown default instead, the same pattern as the Attach input, so the terminal never loses focus on the press; the click-time refocus stays as the fallback. A first tap on clear/compact only arms the confirm and is treated the same way.
2026-10-21 21:40:23 -04:00
9a00c2feaa fix(tmux): drop a leading blank line made of spaces, not only line breaks
`"  \nfix it\r"` still pasted a blank first line, because the leading strip
only matched `\n`. /^\s*\n/ drops everything up to the last leading line
break, mirroring the trimEnd() at the other end, and the first real line
keeps its indentation. The test row fails with the old pattern.

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 23:31:06 +02:00
c3fa6d8923 docs(limits): state when the 462 ceiling holds; skill docs follow CODEMAN_MAX_SESSIONS
The tab layout's 512 refs also count pinned sessions kept as stopped records, so
the ceiling holds at the default MAX_WEBVIEWS and with no pinned closed sessions.
SKILL.md, verbs.md (both copies) and the cron guide no longer give a fixed 50/25.

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 10:18:23 +02:00
f17aa047d0 docs(limits): say 1 to 462 in the MAX_CONCURRENT_SESSIONS doc comment
Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 09:02:15 +02:00
e58c1a2f19 fix(limits): lower the session ceiling to 462 so the layout budget holds
An owner's tab layout holds at most MAX_TAB_REFS (512) refs, counting saved web
tabs (up to MAX_WEBVIEWS, 50 by default) as well as sessions. With a ceiling of
500, session creation could still fail with HTTP 500 on the layout limit before
the capacity message. 512 - 50 = 462; a test pins the ceiling plus MAX_WEBVIEWS
inside MAX_TAB_REFS so the two limits cannot drift apart again.

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:59:29 +02:00
Codeman maintainer 2b9aa055cd fix(webview): relay the request Content-Length so length-only dashboards get the body
The proxy hands undici the request body as a stream and dropped the browser's
content-length, assuming undici recomputes it. For a stream it cannot: undici
frames it as Transfer-Encoding: chunked. A dashboard that reads only
Content-Length (Python's http.server, many small Go/C servers) then saw an
empty body on every POST through a web tab. Found on a WoL dashboard whose
Shut down posted {"confirm":true} and was refused for a missing flag on every
click through Codeman, while curl straight at it worked.

buildUpstreamRequestHeaders now puts the browser's content-length back when a
body goes upstream (relayBodyLength), it is a plain number, and no
transfer-encoding contradicts it. GET/HEAD never carry it. undici 6.28 frames
the stream with that length and fails cleanly on a mismatch. Side effect:
a DELETE/OPTIONS with content-length 0 now goes upstream with neither header
nor body instead of an empty chunked body.

test/webview-proxy-body-framing.test.ts pins it end to end against a
Content-Length-only upstream; it fails on the old code with
"expected 'chunked' to be null".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 08:34:14 +02:00
74dcf9a59d docs: the submit check cannot reliably recognise a multi-line paste
It does match one Claude shows inline when the first line has 24+
non-space characters; it misses a short first line and the
[Pasted text #N +k lines] placeholder. Say "cannot reliably" instead of
"cannot".

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:03:55 +02:00
cf9511cb57 fix(tmux): drop leading line breaks from sendInput text
"\nfix it\r" pasted an empty first line. Leading line breaks are now
dropped like trailing ones. Document what useMux does with a line feed,
and that a pane without bracketed paste receives each pasted line break
as CR, so a multi-line write without \r runs every line but the last
there, where the old line-stripping typed the joined text and ran nothing.

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:03:54 +02:00
3b747774f1 fix(single-line): join lines by split and trim instead of a backtracking regex
/\s*[\r\n]+\s*/g backtracks over every start position of a long
whitespace run, so 50,000 spaces took over a second. Split on line breaks,
trim each line, drop blank ones and join with a space: linear, and a
leading or trailing blank line no longer leaves a space behind. A prompt
without a line break is still returned unchanged. session-auto-ops now
imports toSingleLine from the utils barrel like respawn-controller.

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:03:53 +02:00
ced88b3310 fix(mux): treat the launch directory as no answer for a discovered pane
Every pane starts in TMUX_LAUNCH_CWD (/tmp) and reaches its workspace with a
cd inside the pane command, but a remote or docker pane never does that cd,
so tmux reports /tmp for it. A rediscovered record carries no remote/docker
metadata and looks local, so adopting that directory would root the session
at /tmp: hooks written into /tmp/.claude, the Files panel at /tmp, and a
respawn running claude in a directory any local account can write to.

reconcileSessions() now skips a pane whose directory is the launch directory,
in both spellings a pane can report it: the literal one and its realpath
(/private/tmp on macOS, where a process cwd comes back resolved). The set is
computed once at module load. Such a session falls back to process.cwd(),
as before this series.

Also stop trimming the list-panes output: the directory is the last field,
so a trim cut a trailing space off the last row's path; parsePaneRows()
already skips empty lines. And the comments that list what a discovered
record knows for certain now include the directory, when tmux reported a
usable one.

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:02:05 +02:00
b073035bdc fix(limits): clamp CODEMAN_MAX_SESSIONS to 500 and document it
Review follow-up. Other bounds assume a small session count: an owner's
tab layout holds at most MAX_TAB_REFS (512) refs and PUT
/api/session-order accepts at most 500 ids, so with
CODEMAN_MAX_SESSIONS=600 the 513th session creation answered HTTP 500
instead of the capacity message. The value is now clamped to a named
MAX_CONCURRENT_SESSIONS_CEILING of 500; any digit string above it
(including one past the safe-integer range) resolves to 500 instead of
falling back to 50. The resolver reads process.env directly, the unused
env parameter is gone.

Docs: a CODEMAN_MAX_SESSIONS row in the Settings Reference environment
table, the env name next to the cap in the cron guide and the
architecture invariants, and both copies of the skill's endpoints
reference now give the per-user cap as half the global cap (25 by
default). The changeset says "1 to 500". The test drops the
`as unknown as string` casts and adds '1000', '501' and an
out-of-range value, each refusing at 500.

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:01:24 +02:00
Claudia[bot]andClaude Opus 5.5 225fdbf505 docs: align the cron, teammate-pane and TUI comments with the paste
- cron resolvePrompt(): give the reason the rest of the cron docs give (the
  submit check cannot recognise a multi-line paste; in paste mode the newlines
  reach the CLI as keys) instead of the old one about writeViaMux fusing lines.
- sendInputToPane(): it types with send-keys -l and strips line breaks, so it
  no longer uses "the same approach as sendInput()".
- TUI client: newlines become spaces (the docstring said stripped), and the
  test name no longer cites "multi-line breaks Ink".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 05:06:43 +02:00
Claudia[bot]andClaude Opus 5.5 cf999c1296 fix(auto-compact): send a multi-line focus prompt as one line
Auto-compact writes `/compact <prompt>\r` through writeViaMux when the token
count crosses the threshold, unattended. AutoCompactSchema.prompt accepts line
breaks, so a prompt set through the API can hold several lines. The mux used to
strip them and run the lines together; it now pastes multi-line text as one
block, and the check that re-presses a stranded Enter cannot recognise that
paste in the composer. That is the case respawn's update and kickstart prompts
were already joined for.

The join moves out of respawn-controller into a shared toSingleLine()
(src/utils/single-line.ts), used by both respawn steps and by auto-compact.
Behaviour for respawn is unchanged. The respawn test file becomes
test/unattended-single-line-prompt.test.ts and gains an auto-compact block that
feeds a real Session a token count over its threshold and reads what reaches
writeViaMux.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 05:06:42 +02:00
Claudia[bot]andClaude Opus 5.5 3392a69290 docs: tighten the comments and skill text around multi-line input
- sendInput's docstring said all text without `\r` is typed; that now holds
  for single-line text only.
- The skill's big-input section still listed "the newline stripping" among
  the hazards a workspace file avoids; it is the unconfirmed multi-line
  paste now. Rewrapped, mirrored in plugins/codeman.
- The mobile composer's hand-back comment said the prompt transport strips
  newlines. The hand-back writes to the terminal prompt, whose text reaches
  the PTY as typed input, where a newline is a key; the behaviour (a
  multi-line draft stays parked) is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 04:53:57 +02:00
Claudia[bot]andClaude Opus 5.5 f06635512a fix(respawn): send a multi-line update or kickstart prompt as one line
The update and kickstart prompts are textareas in the respawn settings and
the schema accepts line breaks, so a saved config can hold several lines.
Both steps went out through writeViaMux, which used to strip every `\n` and
run the lines together. Since the mux now pastes multi-line text as one
block, a multi-line respawn prompt would have gone out as a multi-line paste,
and the check that re-presses a stranded Enter cannot recognise that paste in
the composer. Respawn runs unattended, the same situation for which cron
keeps rejecting multi-line prompts.

singleLineRespawnPrompt() joins the lines with a space before the write; a
single-line prompt is passed through unchanged. Respawn joins rather than
rejects, so a multi-line config that was accepted before is still sent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 04:53:56 +02:00
Claudia[bot]andClaude Opus 5.5 fe10cad83f docs: finish dropping the stripped-newline rule from the input docs
The first commit left the old rule standing in places: the gotchas list in
extending-codeman.md still said newlines were stripped and that
"echo A\necho B\r" ran `echo Aecho B`, and so did both READMEs and the
skill's big-input section. Those now describe the paste.

Newlines are only kept on the mux path, so the docs that describe the paste
say "with useMux": without it a multi-line body is not a plain prompt and is
written straight into the pane.

Cron keeps rejecting multi-line prompts, but its docs and the code comments
in cron-service.ts, schemas.ts and respawn-controller.ts gave a reason this
change removes (writeViaMux joining lines, a newline submitting in Ink). The
reason that remains is the submit check, which cannot recognise a
multi-line paste in the composer, and for cron's paste mode the newlines
reaching the CLI as keys.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 04:11:28 +02:00
Claudia[bot]andClaude Opus 5.5 e3de354461 fix(input): keep the line breaks of a multi-line mux prompt, pasted as one block
TmuxManager.sendInput() removed every `\n` before the text reached tmux, so a
multi-line prompt sent with `useMux` (or a multi-line respawn prompt) arrived as
one line with its lines run together: `"echo A\necho B\r"` ran `echo Aecho B`.

The text half of a submit has gone in as one bracketed paste
(buildPasteTextCommands) since the Codex paste-burst fix, and inside the paste
markers Claude Code and Codex insert a pasted line break into the composer
instead of submitting. So the `\n` is now kept (`\r\n` loses only its `\r`) and
the existing paste carries the lines; the Enter stays a separate tmux call.
Multi-line text without an Enter is pasted too rather than typed with
`send-keys -l`, where every line break would reach the pane as a key.
Single-line input is unchanged.

The test-mode gate moves from the top of sendInput() into inputRunner(), as
readPaneRows() does, so a test subclass can record the commands sendInput()
would run instead of pinning the branch shapes on the source.

SubmitVerifier still compares the composer's last glyph line with the prompt
minus its newlines, so it does not reliably recognise a multi-line paste and
may not re-press an Enter that Claude Code ignored. `agent send` and the TUI
therefore keep refusing or joining multi-line input; the docs that described
the stripping now describe the paste and that limit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 03:51:31 +02:00
Claudia[bot]andClaude Opus 5.5 3bd7e81233 fix(mux): give a discovered tmux session its pane's directory, not the server cwd
reconcileSessions() adopts codeman-* sessions it finds on the socket but has
no record of (a lost or stale mux-sessions.json) as "Restored: ..." entries,
and set their workingDir to process.cwd(). Every such session then looked like
it lived wherever the server was started, and a later respawn of it cd'd
there instead of into its project.

tmux already knows the answer as #{pane_current_path}. It is appended as the
last field of PANE_LIST_FORMAT, so a directory containing the separator stays
whole, and parsePaneRows() reads it as PaneRow.currentPath. No extra tmux call:
the same list-panes read that yields the pid now yields the directory.

The path is set by whoever owns the pane, so it is admitted only if it passes
isValidPath(), the check createSession() and respawnPane() already apply to a
workingDir; otherwise, and when tmux reported none, process.cwd() stays the
fallback. A hand-split session takes the first pane tmux lists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 03:48:00 +02:00
Claudia[bot]andClaude Opus 5.5 04eb1ed158 feat(limits): make the global session cap configurable with CODEMAN_MAX_SESSIONS
The cap of 50 concurrent sessions was a hard-coded constant, so a machine
with the memory for more (or a small box that wants fewer) had to patch the
source. CODEMAN_MAX_SESSIONS now sets it at startup. The default stays 50,
and anything that is not a positive integer (empty, 0, negative, fractional,
1e3, out of safe-integer range) falls back to it, so a typo never locks
session creation.

Every site keeps reading MAX_CONCURRENT_SESSIONS, so the session routes,
cron launches, the scheduled-run loop and the multi-user per-user cap (half
the global cap unless CODEMAN_MAX_SESSIONS_PER_USER is set) all follow the
variable. The test goes through sessionCapacityState and
sessionCapacityMessage, the check every session-creating route uses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 03:46:55 +02:00
Codeman maintainer e63bb65b36 chore: version packages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 10:03:55 +02:00
Codeman maintainer f744c503c5 Merge branch 'relnext/security-notices': security notices, starting with the OpenCode Console breach
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 10:03:02 +02:00
Codeman maintainer 613fc86d70 fix(ui): touch-sized notice buttons on any coarse pointer, no mid-word break in a Chinese title
A phone in landscape is wider than 599px, so the 44px targets now follow (pointer: coarse) instead of the width. A balanced title broke the zh-CN text inside 入侵; :lang(zh) keeps CJK runs whole and breaks at the title's spaces and colon.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 10:02:49 +02:00
Codeman maintainer 82c34fba99 fix(ui): notice buttons act once, links send no referrer, solo windows defer
- A closing notice card stays in the DOM for its 200 ms fade, and its
  link and "See what's new" buttons still worked there: a double click
  opened the notice twice. close() now makes the card inert and every
  button handler does nothing once it is closing.
- Notice links (and the combined card's "See what's new") open with
  noopener,noreferrer. noopener alone still sent this instance's origin,
  which can be a tailnet name or a tunnel URL, as the Referer. The plain
  update toast is unchanged.
- The https rule is checked again at the button
  (CodemanSecurityNotices.urlOk): a caller handing showSecurityNotice or
  showUpdateNotice an unchecked URL gets no link button.
- A detached session window (/session/:id) is rendered without
  __codemanCliAvailable, so it could never select the OpenCode notice,
  yet it took the update toast and recorded the version, and the
  dashboard then showed the notice as a separate card. While any notice
  is pending, a solo window now shows and records nothing and leaves
  both to the dashboard; with none pending it shows the plain toast as
  before.
- The wiring tests pin the page's own clock (JSDOM gives the window its
  own Date). On the real clock 18 of them would have failed on every push
  from 2027-01-01, when the fixture notice expires. New tests pin the
  once-only close directly (the old double-click comment claimed more
  than it checked), the single open, the sink check, the solo windows,
  the scroll cap and the light-skin ink.
- CLAUDE.md now says the public .json files are served no-cache, the
  exception to the year-long immutable cache.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 09:30:22 +02:00
Codeman maintainer ee6b115023 fix(ui): keep the security notice card reachable and readable everywhere
- The card is capped to the room below the toast stack's top and
  scrolls inside itself. The stack is fixed and cannot scroll, so on a
  phone in landscape the update toast's buttons sat below the screen
  with only the × left, which records the notice unread. The accent bar
  becomes a background layer, which stays put while the content scrolls.
- Small amber text and the primary button use --notice-ink, darkened a
  step on the four light skins, where --yellow measured 3.9 to 4.4:1 for
  the 10.6px eyebrow (now 5.1 or better, the button label 5.7 or better).
- The update toast that carries a notice frames itself in the accent
  colour and leaves the amber to the warning section inside it.
- The × uses --text-dim (2.4:1 on og before, 5.5 now).
- Phones: every button fills its row (a wrapped Dismiss no longer sits
  stray at the left) and is 44px tall, and the × gets a 44px box.
- The title balances its lines, and the card matches the plain toast's
  420px so a toast stacked under it lines up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 09:30:13 +02:00
Codeman maintainer 5e405bbbb9 fix(ui): shorten the OpenCode notice to what OpenCode said, live from 17 October
The message ran 66 words over five sentences, which pushed the update
toast's buttons off a 360x640 phone. It also repeated the card's own
eyebrow ("Security notice for OpenCode users:"), placed the breached
"legacy system" inside the Console (no source says so), and closed with
an unsourced line about the CLI not being affected. The new text (55
words) says what OpenCode said, including its own reassurance that a
user who got no email was not affected, which matters because the card
goes to every opencode user while about 1.4% of Console users were hit.
The zh-CN text follows it and uses 撤销 for revoking keys.

published moves to 2026-10-17, so the notice is already live in every
timezone when the release lands: a browser still on the 17th would
otherwise take the plain update toast and get the notice later as a
separate card. docs/releasing.md says both things for the next notice.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 09:30:13 +02:00
Codeman maintainer 54d0699abd docs: how security notices work, and how to add and retire one
src/web/CLAUDE.md gets the load-bearing rules (hand-maintained file,
strictly-true availability, one surface per notice per load, seen only
on a user action, no CLI-id branching, theme tokens only), and
docs/releasing.md a section on adding a notice, its fields, and letting
`expires` retire it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:59:39 +02:00
Codeman maintainer 50bae2d371 feat(ui): security notices, shown only to users of the affected CLI
A hand-maintained src/web/public/security-notices.json carries warnings
about things outside Codeman that its users should act on. The first:
OpenCode's report of unauthorized access to its legacy Console, which
may have exposed provider API keys saved there.

- Pure core in constants.js (CodemanSecurityNotices): select() keeps the
  notices that apply now (inclusive local-day dates, not yet seen, https
  links, malformed entries skipped), and one naming a CLI only when
  window.__codemanCliAvailable[cli] === true. Unknown means not
  installed: a warning about software you do not have is noise. The CLI
  id is data in the JSON; no code branches on it. plan() decides the
  surface, so a load shows each notice once.
- An existing install on a new version gets the notice INSIDE the
  release's update toast: the highlights as before, a warning section,
  and the notice's link next to "See what's new". With no notice the
  update toast is the plain one it always was.
- Everyone else (a fresh install, a same-version reload) gets its own
  card in the toast stack: shield, "Security notice" eyebrow, title,
  message, the link, Dismiss and the ×; amber-to-red accent bar, theme
  tokens only (dark and light skins), full width on phones, an
  opacity/transform entrance and none under prefers-reduced-motion,
  role=alert.
- A notice is recorded as seen only when the user closes it, dismisses it
  or follows its link, never when it appears. Storage blocked shows
  nothing. The notice's own text is textContent and carries its own
  zh-CN, so i18n.js leaves it alone; the card's own copy translates.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:59:39 +02:00
Codeman maintainer ada2309721 fix(web): serve the public JSON data files no-cache, not immutable
The static plugin gave every file under public/ a year of `immutable`,
including the JSON the page fetches at runtime: whats-new.json (rewritten
every release), the new security-notices.json (edited by hand) and
manifest.json. None of them is content-hashed or `?v=`-busted, so a
browser could keep last release's copy, and a security notice added in a
later release might never reach it. A `.json` file now revalidates
(`no-cache`); every other static asset keeps its immutable year, and a
route that set its own Cache-Control still wins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:59:39 +02:00
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 9fc7781442 Merge pull request #647: fix(web): hold the WS pill's queued-bytes suffix until a backlog persists
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:16:43 +02:00
Codeman maintainer 7504a2bc8b Merge pull request #567: fix(cases): only refuse the cases folder and its direct children for a custom-folder case
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:16:42 +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 66a6319af9 Merge pull request #644: feat(links): make .diff and .patch paths clickable
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:16:41 +02:00
Codeman maintainer 7edea53841 Merge pull request #653: test(uploads): pin the browser's copies of the document list to ALLOWED_DOCUMENT_EXTS
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	CLAUDE.md
2026-10-18 08:16:40 +02:00
Codeman maintainer 5481fd599b Merge pull request #651: fix(uploads): say how many files a mixed pick, drop or paste skipped
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	CLAUDE.md
2026-10-18 08:16:10 +02:00
Codeman maintainer 3c16eccd47 Merge pull request #652: fix(uploads): say a refused text upload is not UTF-8 instead of a type mismatch
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:15:52 +02:00
Codeman maintainer 9dfbb29a64 fix(#648): anchor antigravity's working line to the start of a row
_probePaneWorking() tests the pattern against the whole pane, so an answer
that quoted the spinner row (an agent explaining this entry: it matches
`⣯  Generating...`) kept the session busy for as long as the text was on
screen. Anchored like gemini's braille branch; every agy capture draws the
spinner at column 0. The prose test now covers the quoted row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:13:34 +02:00
Codeman maintainer 14a9e66547 Merge origin/master into #644: keep diff|patch beside #621's tsv and zip
Master's #621 added tsv and zip to both file-link extension groups in
constants.js while this branch added diff|patch to the same lines. The
resolution keeps both, in master's order with diff|patch after sql where the
PR put it; the rooted and relative groups stay identical, and every upload
document extension plus .diff/.patch still links in both patterns.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:12:35 +02:00
Codeman maintainer 8870cfc7dc fix(#646): translate the Pin tab names row and list it in the Settings reference
The new App Settings row had no zh-CN entry, so it read in English in a
Chinese UI, and docs/wiki/Settings-Reference.md did not list it. Adds the
label and description to i18n.js, a row in the Agents & CLIs table, and a
JSDOM test that runs the real translator over the real index.html.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:10:17 +02:00
Codeman maintainer 44f87de9a7 fix(#592): doc drift around Copilot's resume, wheelForward and the env prefixes
- docs/cli-registry.md said Session history resume goes through
  copilotConfig.resumeSessionId / continueSession. Past Sessions sends only
  `continueSession: true` (terminal-ui.js, the modeConfigKey table);
  resumeSessionId is for API callers.
- The wheelForward JSDoc in cli-registry/types.ts read as if the value had
  effect. Nothing reads it (it is in the DECLARED-FOR-LATER list); the
  browser gate is _shouldForwardWheelToApp in terminal-ui.js with its own
  per-CLI rules, which the comment now says.
- The env-prefix lists in the CLAUDE.md Security table and the
  allowedEnvPrefixes() comment in schemas.ts stopped at DEEPSEEK_, missing
  both OMP_ and the new COPILOT_; the Multi-CLI prefix rule missed OMP_.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:09:39 +02:00
Codeman maintainer 67ca0954db fix(#647): read the pending queue once per indicator update
The try/catch around _pendingBytes() could not keep _updateConnectionIndicator()
from throwing, because _computeConnectionDescriptor() read the queue again,
unguarded, right after; its test only passed because it stubbed the compute
out. The update now reads the queue once and hands the totals to the compute
(which still reads a fresh queue when called on its own), so the hot input
path walks the queue once per keystroke and ACK instead of twice. The stubbed
test is gone: the COD-133 combinatorial test already pins never-throw.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:08:46 +02:00
Codeman maintainer d8850d1b50 fix(#653): export ALLOWED_DOCUMENT_EXTS as ReadonlySet<string>
Now that the set is exported for the parity test, type it read-only like
VIDEO_ATTACHMENT_EXTENSIONS, so an importer cannot add to the live upload
allowlist.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 08:08:24 +02:00
Codeman maintainer d170ad2295 fix(#592): a rule inside a Copilot answer stays part of it in agent read
The pane segmenter ended the response block at every full-width rule, so
an answer that draws one (a markdown `---`, a passage quoted between two
rules) read as only its last section: `Summary / ──── / Details` came
back as "Details", and the existing test pinned that by expecting the
quoted passage without its "Here it is:" lead-in.

Inside an open response a rule is now held and written into the text as
`---` once more of the same answer follows, so a rule that only separates
the answer from the footer never trails it. The composer's own two rules
(the last rule/❯/rule triple) are still boundaries. The old test now
expects both parts, and a new one pins a Summary/rule/Details answer as
one block; both fail without the change.

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