Review round on PR #757. The route turns EVERY upstream failure into
isLlamaSwap: false, so the old line silently promised an unreachable
endpoint was merely mute, and the live-session check missed a null pid
(the PTY exited, the same test keep-warm applies).
- Require pid !== null in _getCustomModelInUseBySession, pinned by a
'process exited' case in the marks-nothing table
- Reword #customModelPickUnknown to claim only that nothing was
reported, true for a down endpoint too; drop the 'stays quiet for a
failed probe' promise from changeset, docs and comments
- architecture-invariants: add the In-use tier, both pin fences
(customModel not displayModel; stopped/null-pid/paneExit never
count) and the unknown-line rule
- Mirror the wording through i18n (key + zh-CN), index.html fixture,
styles.css comment, and the run-menu tests
Only llama-swap answers what is loaded, so behind a gateway such as Bifrost
nothing was marked and the picker fell back to discovery order apart from the
one CLI that had launched there before. A live session whose own injected
endpoint is that host now promotes its model as "In use by a session", under
its own tag and below llama-swap's "Currently loaded" answer, reading only
what Codeman injected rather than terminal text, and never a stopped session.
When genuinely nothing is known the picker says so instead of leaving an
unmarked wall, and stays quiet for an endpoint that merely failed to answer.
Consolidate this release's notes into one changeset (Thanks, the tab
strip, the fixes, the fixes applied while landing) and drop #730's own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The corner grip and Expand size the window correctly, but the document
kept a fixed 960px reading column from before the resize feature
existed, so text stopped reflowing once the window grew past it.
Pinned by new rule 13 in file-preview-window.test.ts.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Conflict in config/test-suites.ts resolved by keeping both sides: #689's
header-divider and #692's header-host-sensors browser tests are both
listed in BROWSER_TEST_GLOBS.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflict in docs/architecture-invariants.md resolved by keeping both
sides: the hollow-pane paragraph carries #681's unloaded-scrollback
sentence, #698's Codex-menu rewording and #699's notch rewording (a
word-level three-way merge; the three edits touch disjoint sentences).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
changeset: say Codex does not track the mouse "measured on 0.162.1 and 0.164.0", since 0.163.0 fullscreen was measured tracking it
docs/architecture-invariants.md: list page-keys-dialog-open and page-keys-menu-open among the [scroll] decisions
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Clamp the stored toolbar width to MIN_WIDTH in apply() and step the arrow keys from the width on screen, so a drag to the window edge or a held ArrowRight no longer stores a negative width or leaves an ArrowLeft dead zone (browser test added)
Drop Escape as a reset key on the handle: the global capture-phase Escape handler already closes every panel on it (static pin added)
Say in the changeset, the wiki and the densityFor comment that the percentages go at the narrowest step in the Compact style only, and drop Escape from the changeset
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only an admin's rename touches the server-wide settings.json, which is now
written before linked-cases.json with the registry restored on failure. The
Resume list follows a renamed folder, the owner's default session group is
carried over, remote sessions are never listed or stopped, errorCode maps
EXISTS and BUSY, and the unmigrated items are documented.
Conflicts in CLAUDE.md, README.md, docs/api-reference.md and
src/web/server.ts were additions beside #662's Copilot quota read and
#666's keep-warm sections; both sides kept.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Squash of PR #658. Discovery records each llama.cpp model's parallel slot
count (from --parallel/-np, or /props total_slots when the launch command
states none), and a Claude launch warns when the context split across
those slots leaves less than 40K tokens per request. An explicit
--parallel beside a /props context records a slot count of 1, so a later
round overwrites an earlier count, and a non-2xx /props is remembered per
launch command while a network error is retried.
Merge-time fix by the maintainer: the architecture-invariants sentence on
the explicit --parallel case now says it records a slot count of 1.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Squash of PR #662. The header plan-usage chip gains a Copilot row: the
calendar-month premium-request quota, with the request counts and the
reset in the tooltip, read every 10 minutes from GitHub's
copilot_internal/user endpoint with the Copilot CLI's own sign-in token.
It rides showPlanUsageLimits and is on by default while the CLI is
installed and signed in; any failure leaves the chip without the row.
Merge-time fixes by the maintainer: the gates (setting, then token, then
the CLI probe) live in readCopilotPlanUsageIfEnabled() so a host with no
Copilot sign-in never runs the login-shell probe each tick, with a test
that Plan Usage Limits off means no request; the X-GitHub-Api-Version
header the Copilot CLI itself does not send is dropped; and CLAUDE.md,
architecture-invariants, README and copilot-integration describe the read.
The hooks-config import in server.ts was merged with #666's.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
Settings -> System -> Config backups gains a Compare view: a backup against the
running config, a backup against the one before it, or any two backups. Each
differing file lists its changed keys as before/after, plus files that appeared
or vanished. GET /api/config-backups/diff?from=&to= serves it (`to` defaults to
`current`; admin only in multi-user mode).
Credential values are never returned: .env, users.json and push-keys.json report
names only, and any other key that looks like a token, secret, password,
credential or key is reported as changed without its value. A non-JSON file
reports only that it differs, a file is cut at 200 changes, and a self-refreshed
discovery timestamp does not count as a difference.
The files that hold a user's customization (settings.json, clis.json, custom-model-hosts.json,
webhook.json, intents.json, linked-cases.json, push-keys.json, users.json, .env) are plain JSON
in the data dir, rewritten by the server, the Settings UI and the CLI, with no copy to go back to.
A snapshot is taken at startup and whenever the tracked files' content changes (checked every
five minutes, and only written when a digest of the file set differs from the latest snapshot's),
into `<data dir>/backups/config` by default or any absolute folder. Retention keeps the newest
configBackupKeepCount (1-500) and, when configBackupKeepDays > 0, drops older ones; it only ever
deletes folders it made (name pattern and a parseable manifest) and never the newest.
GET/POST /api/config-backups list and back up now; POST .../:id/restore verifies every file against
the manifest's SHA-256, takes a pre-restore snapshot, then replaces files atomically with mode
0600, writing only allowlisted names. Snapshots are 0700 folders of 0600 files and all routes are
admin only in multi-user mode. Settings -> System gets the controls and a Restore list.
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>
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.
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.
Copilot (`copilot`, npm @github/copilot) as a stock CLI entry with the same launch options as the other agent CLIs: model (--model), resume (--resume), continue (--continue) and allow-all (--yolo, clamped off for non-granted multi-user owners). Adds a resolver and GET /api/copilot/status probe, the agent-image package, Run-menu/mobile/logo entries, skill docs and tests.
Review round: the footer hint is drawn only at rest, so nothing arms the idle check while the Run this command list is up. The stock.ts comment, wiki, changeset and test now say a pending approval shows as working until answered, and the test pins busy during the confirmation. Adds the antigravity copy of the reattached-pane settle test, and updates the promptGlyph comment and the fixture header.
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.