The input ended on a whitespace run, so the old regex returned a different
string and the test failed on the result before it reached the time bound.
Keep the run inside a line ('a<run>b\nc'): the old regex and the split now
return the same 'a<run>b c', and only the elapsed time tells them apart
(about 1.8 s against well under a millisecond).
Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
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>
"\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>
/\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>
- 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>
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>
- 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>
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>
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>
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>
`node_modules/` with a trailing slash matches directories only, so a
worktree that links node_modules to a shared install shows the link as
untracked and a plain `git add` commits it. Two PR heads did (#658,
#662), which also broke checking them out next to a real install.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The nightly browser suite failed in run-mode-menu-scroll: the short-menu case
opened the stock menu and asserted it needs no scrollbar on a 390x640 phone.
The GitHub Copilot entry made the stock CLI list eleven rows, 562px of content
against a 538px cap, so the menu now scrolls there, which is the cap-and-scroll
behaviour the CSS is written for (and what the other two cases verify). Every
further CLI would have broken the same assumption again.
The case now hides the whole CLI block (the container, since its buttons are
rebuilt from the registry) and also checks the "not stretched" half of its name:
the border-box height must stay under max-height. clientHeight would not do,
since it leaves out the border and a menu stretched to its cap reads just under it.
Visible side effect worth knowing: on a phone about 640px tall the stock Run
dropdown now scrolls by roughly one row. At 900px it fits (564px) unscrolled.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
- 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>
- 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>
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>
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>
The pure select() and plan() loaded from the shipped constants.js, the
shipped JSON itself (unique ids, https links, real dates, every cli a
stock registry id, a zh-CN title wherever there is an English one), and
the wiring in a JSDOM page: fresh install and same-version reload get the
card, an existing install's update toast carries the notice, the update
toast without a notice matches master's DOM byte for byte, seen only on a
user action, storage blocked shows nothing, base path, zh-CN, and the
card's CSS contract (theme tokens, motion, phone width).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
docs/api-reference.md (the signal notes and the idle/working rows), and the
wiki's Notifications-And-Approvals, Keeping-Agents-Running and Troubleshooting
pages, plus docs/grok-integration.md, still called Antigravity's working and
idle status unreliable. It now declares a working line and prompt marker like
Codex, OpenCode, Gemini, Pi and OMP, so only Grok stays on generic cues. The
Troubleshooting entry for a session stuck showing busy also says that an
Antigravity session waiting on a shell-command approval shows busy until it is
answered, and the API reference says the same of its idle signal.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
_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>
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>
Its prototype-only app had no _pendingDeliveries and only passed because the
removed try/catch swallowed the read. The real constructor always creates the
Map.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The parked-viewport test had no wrapped rows and the termination test runs at
baseY 0, so nothing pinned the walk of a multi-row composer through absolute
lines off the bottom. The new case fails on the pre-fix terminal-ui.js
('link' instead of 'history') and passes with the fix.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
- 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>
The suffix is part of the pill's text line, which the Tiles style hides (it
shows WS live) and phones hide too, so the page promised a suffix some users
never see.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The timer test only checked that a handle existed, and the other hold tests
re-rendered by hand, so deleting the re-render from the timer callback left
every test green. A new test waits out the hold for real and asserts the
suffix appears and the timer is spent with no further call.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
GET /api/sessions/:id/last-response reads a copilot pane through
capturePaneText, since a repainting full-screen TUI streams cursor moves
rather than lines, and nothing pinned that at the route: had the branch
stopped capturing, copilot would quietly have been segmented off the
fused stream again. Three app.inject cases with a stubbed mux: the
captured screen is what gets segmented (the stream text never shows),
a null capture falls back to the buffer, and a line-streaming mode
(opencode) never asks for the screen. The first two fail with the
capture switched off.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The reverse classifier check read only the one includes(ext) list, so a
second branch such as `if (ext === 'rtf') return 'document';` passed every
test; the method may now return 'document' from exactly one place. The
accept= checks kept only .ext tokens, so adding text/* to a picker passed;
the non-extension tokens must be exactly image/* and video/*. Both verified
by mutation (each fails the suite).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
The reflow around the new UTF-8 sentence left one 120-character line; the
paragraph is wrapped at about 90 columns like the rest of the page.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pins the user-facing fix end to end: POST /api/cases with a path two levels
down returns 200, lands in the linked-cases registry and is scaffolded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The one message read "That folder is in the cases folder (...), where it is
already listed as a case" for the cases folder itself too, which is not in
itself and is never listed, and "already listed" is not true for a direct
child that does not exist yet. Two messages now, both still pointing at plain
Create New.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>