git's refusal quotes the destination, so a folder named openssl or rustls
classified as HOST_UNREACHABLE and the cleanup emptied it. The cleanup now
tests git's own text, DESTINATION_EXISTS is matched first, and ssl/tls are
matched as words. Also: a test for the Docker refusal on the default branch,
the route JSDoc and constant comment, and the folder copy (empty, or new).
A direct clone whose git-slot wait timed out came back with no spawnError, so
the cleanup emptied a folder git never touched. GitRun now says whether git
started and the cleanup requires it. Cloning into a folder at or inside a
Docker case workspace is refused, as git status, fetch and worktrees do, since
git would run the new repository's hooks and filters on the host.
A direct clone into an empty folder emptied the folder after any failure, which
deleted a concurrent attempt's finished clone when git had written nothing.
Clones into one folder are now mutually exclusive in-process, the cleanup is
skipped when git refused the folder, never ran, or the mount is unresponsive,
and a name linked while the clone ran is a 409 that keeps the folder.
Documents path for POST /api/cases/clone.
The custom folder was treated as a parent, so the repository landed in <folder>/<case name>. The chosen folder is now the clone destination: an empty existing folder is cloned into directly (CloneOptions.intoEmptyDir, no temporary sibling, never removed on failure), a new folder is created, and a folder with files is refused.
Clone Repo could only clone under ~/codeman-cases. POST /api/cases/clone now takes an optional path (parent folder plus case name) and the Clone tab gets a Clone into a custom folder option with a Browse button, mirroring Create in a custom folder. The path is judged by prepareNewCasePath, the clone is registered as a linked case, the option is admin-only in multi-user mode, and an existing target folder is refused.
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>
#726 attaches the keyCode 229 recovery only when (pointer: coarse)
matches, which keeps a desktop dead key from arriving twice. Chromium
reports the PRIMARY pointer as fine once a mouse, a trackpad cover or a
stylus is attached, so on such an Android device the module was off
while the text still came from the soft keyboard, and the #441 dropped
last character and the #541 autocorrect duplication came back.
The gate is now a coarse pointer OR an Android user agent. Android ran
the recovery unconditionally before #726, so this only keeps that; a
desktop (fine pointer, not Android) and a touchscreen laptop stay off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setRemoteWorkingDir() records the path for authority only, and today
nothing else reads it for a remote session (loadFixPlanFromDisk's only
callers are setWorkingDir and the local watcher). The watcher now
remembers the remote mode and refuses both the local read and the local
watch for it, so a future caller cannot import a same-named local twin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ctrl+V's hand-built bracketed paste (#726) and the phone composer's
dropped CSI 200~/201~ markers from the text in ONE pass, so a clipboard
carrying ESC[20 ESC[201~ 1~ came out of it with an ESC[201~ that ended
the frame early: everything after it was typed as keys and each CR ran.
Both now strip until nothing matches. The composer's copy predates this
release; it is the same one-liner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#722 stopped handing a remote session's workingDir to the Ralph tracker,
which also left isFileAuthoritative false for remote sessions only. That
flag is what keeps the output-based completion guessers off, and it is
true for every local session with a working directory, so a remote loop
could end, and mark every todo done, on a prose line such as "All tasks
completed" while a local one cannot.
setRemoteWorkingDir() records the path without reading or watching the
local disk, so the fix plan stays authoritative as for a local session;
the file still reaches the tracker only through fix-plan/read, over ssh.
The test pins both, and fails on the previous head.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the L-shaped strip to the Tab layouts section of
docs/architecture-invariants.md, the mechanism file src/web/CLAUDE.md
links to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Set --rv-content-max: none on the File Viewer's rendered document, so
ASCII-diagram blocks (capped by that variable with a 720px fallback the
Response Viewer sets) follow the window like the rest of the document,
and pin it with a third assertion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export isPathWithinRoot from file-routes.ts and use it for the remote
@fix_plan.md confinement, so the two checks cannot drift; rewrap the
readRemoteFixPlan JSDoc and say NOT_FOUND; list fix-plan/read and the two
write refusals in docs/remote-sessions.md and architecture-invariants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
toggleSessionImageWatcher checks the response, so any refusal puts the
switch back and toasts an error instead of a success; architecture-
invariants says the image watcher never watches a remote workingDir.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Note in docs/remote-sessions.md that the @fix_plan.md auto-load does not
run for remote sessions, and rewrap the test's header comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the @fileoverview the repo carries on every module: why the parser
exists, why it is POST + /api/ only, and that it owns the root '*'
content-type slot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hand the bracketed frame to the target terminal with input() so onData
scrolls to the bottom, clears the selection, and delivers the bytes.
The paste trap no longer writes the frame itself.
Fixes#716
Co-authored-by: DDT <1786035110@qq.com>
Path links and in-document links carry a placeholder href (#, #fragment) that the click delegate resolves itself, but a middle click fires auxclick, never click, so the browser followed the placeholder and opened the dashboard root in a new tab. A middle click on those links now does what a click does; real URLs keep the browser's own middle click.
The Approvals Inbox strings added a second `Idle: '空闲'` to the ZH_CN
table, beside the original in the status words. Same value, so nothing
changed on screen, but every build warned:
[WARNING] Duplicate key "Idle" in object literal [duplicate-object-key]
A remote-SSH session's workingDir is a path on the remote host, but the
Ralph file routes used local fs on it: fix-plan/read imported a same-named
LOCAL @fix_plan.md (or 404'd), and fix-plan/write and ralph-prompt/write
wrote their files into the local twin of the remote folder. Same root
cause as the Ralph @fix_plan.md watcher fix.
- fix-plan/read reads the file on the remote host through
src/remote-files.ts (the File Viewer's ssh path): one probe of the file
and the workspace, the File Viewer's confinement (a file resolving
outside the workspace is treated as missing), then a capped read (1 MB).
A missing remote file is the same NOT_FOUND as a missing local one; a
non-file, an oversized file or an unreachable host is a failed read,
never a local fallback.
- fix-plan/write and ralph-prompt/write refuse a remote session
(INVALID_INPUT): remote writes are out of scope by design, as for the
File Viewer's remote edit. The Ralph panel already shows the error.
A remote-SSH session's workingDir is a path on the remote host, and the
image watcher watched it on the local disk: a folder that is not there,
or a same-named local folder whose images then popped up as the remote
session's. Same root cause as the Ralph @fix_plan.md watcher fix.
Three places start a watch, and all three now skip a remote session:
setupSessionListeners(), the app-wide toggle in PUT /api/settings (which
re-watches every enabled session), and POST /api/sessions/:id/image-watcher,
which refuses to turn it on (INVALID_INPUT, like the Git fetch settings
route) and still lets it be turned off. The Session Options row is hidden
for a remote session, the way the Git tab is, since the client's
_apiPost() does not throw on a refused request.
KeyCode 229 recovery forwarded a composed character that xterm had
already delivered, so a desktop dead-key backtick arrived twice.
create() now attaches only when the primary pointer is coarse.
Ctrl+V text paste went through terminal.paste(), which drops the
CSI 200~/201~ markers after a replay clears xterm's DECSET 2004
mirror. Plain text is now a hand-built bracketed frame on the
session input path.
Fixes#716
Co-authored-by: DDT <1786035110@qq.com>
CODEMAN_ALLOW_UNAUTHENTICATED_NETWORK and the new
CODEMAN_ALLOW_UNAUTHENTICATED_TUNNEL (#718), with their CLI flags, join
the documented environment variables the versioning policy covers, from
1.46.0, the release that gave each its own meaning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Handle untyped zero-byte POST streams at the shared API parser boundary.
Keep request.body undefined and preserve existing typed parsers, 404
fallbacks, authentication and webview proxy stream handling.
Reject nonempty untyped streams on the first byte, and cover parser
boundaries, raw HTTP framing and real WebServer routing with regressions.
The merged PRs (mostly #714's git fetch) added about 120 em-dashes in
comments, docs, test names and three settings strings. Each one is now a
comma, colon, semicolon or parentheses. The English settings strings in
index.html and their i18n.js keys change together, and the zh-CN value
for the fetch interval uses full-width parentheses instead of a double
dash. The one left is the regex in constants.js that matches a dash
typed as a word, which is behaviour, not punctuation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up for the L-shaped header strip:
- _syncTabStripCorner() keeps the plain wrap when the plain strip is
already past --tab-strip-max-height, checked before .tabs-corner goes
on: the float scrolled with the tabs while the toolbar stayed put, so a
scrolled strip put tabs under #headerRight, and a re-render of a
scrolled two-rows strip sized the float from the scrolled frame. The
"- scrollTop" in contentTop is gone (always 0 now).
- A strip just under its max-height that passes it once the float is in
falls back to the plain wrap as well.
- test/tab-strip-corner.browser.test.ts (real Chromium, real header and
styles.css), listed in BROWSER_TEST_GLOBS.
- src/web/CLAUDE.md Tab layouts entry describes the L-shaped classic strip.
Replaces the six contributor changesets (#694, #707, #708, #709, #718,
#713) with one consolidated minor changeset covering all 14 PRs, with the
Thanks section first and the release-review fixes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
terminalLinkLine() cut the URL's first row at its cell to stitch the URL, so a
path in a later column of that row was never scanned. The cut-off columns now
follow the stitched rows as a segment of their own, mapped back to their cells.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The self-gate now says the skill reads saved Codex conversations offline outside a session; the staging
reference says only tagged input gets 409 during a guarded submit (the rest is held), and the API
reference notes a managed write past a full hold queue falls back to a direct write. Also: missing
reference rows, agent --help naming inspect/stage/submit, the tunnel opt-in in Remote Access, MCP wording.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
spacedFilePathOnLine() refused only a FIRST word ending in `$`, so `/mnt/c/My Docs$ ls notes.md`
and `/mnt/c/Users/me/OneDrive - Contoso/proj$ code notes.md` linked the whole line. Any earlier
word ending in `$` now refuses the candidate, and both lines link what master links.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
readDockerCases() reads an unreadable or half-written docker-cases.json as [], so a fetch (which runs
repository hooks, and runs unattended on the interval) could run inside a Docker workspace. The fetch
paths now read it through a strict reader: the Fetch button answers a failed run (state 'error') and the
scheduler skips the tick, undoes its picks and logs once. The status and diff routes are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
settingsWrites() now requires every writeFile to be a settings.json temp file that a rename put in
place, with one rename per write, and the concurrent-stop harness stores a direct write to
settings.json at once. A stray fs.writeFile(SETTINGS_PATH) in the PUT handler passed all 34 tests
before and now fails 20 of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The settings readers for git fetch and keep-warm no longer repeat V8's JSON.parse message, which
quotes the text around the fault and could print voiceSettings.apiKey. The 15 s scheduler tick now
logs an unreadable settings.json once, and again only after a successful read in between. Stale
comments that said settings.json is rewritten in place now say writes are atomic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved by keeping both sides:
- CLAUDE.md (Agent CLI): #694's opening (an HTTP client, the experimental
agent-session-routes behind agentCoordinationEnabled) with #708's clause
on the directory spawn and its two refusals.
- docs/api-reference.md (codeman agent): #708's spawn paragraph and --json
shape, with #694's inspect/stage/submit in the verb list and the guarded
endpoints named before "adds no route for the other verbs".
- src/cli-agent.ts (@fileoverview): #708's quick-start / directory spawn
routes and #694's experimental routes in one endpoint list; the two
node:fs/promises imports (#708's realpath+stat, #694's open+stat) merged
into one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in CLAUDE.md (Adding Features, App setting) resolved by keeping
both sides: #714's sentence that a setting the server acts on must be
synced, after the `.strict()` clause, and #719's mutateSettingsFile() rule
at the end of the bullet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>