Commit Graph
1917 Commits
Author SHA1 Message Date
RandalixandClaude Opus 5.5 c4ba14235f feat(edit-history): read remote (SSH) claude sessions, never waking the host
The edit history panel now covers remote claude sessions. The sync stays
path-based; its deps get the session and server.ts routes them to the local
reader or to two new ssh reads in remote-files.ts:

- remoteLocateClaudeTranscript: one round trip; the conversation the remote
  pane's claude writes now (~/.claude/sessions/<pid>.json via the pane's
  process tree, since remote hosts send no hooks), else claudeSessionId,
  else the session id, newest $HOME/.claude/projects/*/<id>.jsonl.
- remoteReadClaudeEditSources: one round trip per read pass, with the local
  reader's semantics (offset clamp, block end at offset+len, new files read
  their tail, shared main-first budget, onlyRequested, 8 KiB meta cap).

So /clear catch-up, "Load older", cursors and the store LRU work remotely
unchanged. Reads use buildRemoteFileCommand + runWithRemoteSshLimit only:
a sleeping host is a connect timeout -> unavailable, stored entries kept.

Tests: both scripts run on a real sh against a temp HOME; the read is
compared byte for byte with readClaudeEditSources on 60 seeded random
transcripts. A transitive import guard pins that the edit-history read path
reaches neither remote-wake.ts nor session-routes.ts. Browser: a remote
session lists images without thumbnails, a click previews. Docs updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-05 11:19:46 +01:00
Codeman maintainer 9eac5ecf01 test: wait for the first read before starting the other edit history sessions
The gate belonged to whichever read arrived first, and sessions load their stores
independently, so under CI timing another session took it, the loop never reached
release() and the test timed out (master, CI run 41930703100). The code cannot block
one session on another: sync() only waits on its own slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 00:21:12 +01:00
Codeman maintainer 4960bee779 chore: one changeset for the release, and no new em-dashes
Consolidates the four contributor changesets, and rewords the em-dashes this release
added in comments and docs. The five in test/settings-save-failure-ui.test.ts stay:
they assert existing toast text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:31:48 +01:00
Codeman maintainer 7803d5616a fix: probe bounded before a realpath in clone and New folder (release review, #516)
Clone ran an unbounded realpath over every Docker case workspace, and the mkdir
route inherited the picker resolver's realpathSync, which freezes the whole server
on a mount that stopped answering. The picker's browse route has the same call and
is left as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:29:08 +01:00
Codeman maintainer 345c901e81 fix: refuse an unlaunchable model id before quick-start asks to evict a model (#752)
The dedicated route got this order at merge and quick-start kept the old one, so
a user could agree to a swap only to get the 400. It also runs on the pure build
now, so a refused id writes no config dir to clean up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:29:08 +01:00
Codeman maintainer e68afdd2ac fix: bound the Discord table conversion input and keep received files off the watcher (release review, #741)
A table costs its widest cell times its rows, so one wide cell in an agent's last
message built a 202 MB string on the event loop although only 8 posts can ever go
out. Only what can be posted is converted now. A file a person sent from Discord
also fired attachment:detected as if the agent had produced it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:29:08 +01:00
Codeman maintainer 4276b53a8f fix: keep credential files out of the file routes and Discord uploads (release review)
The Discord bot token was readable through the attachment routes, because
discord.json was not on the sensitive list, and so were webhook.json,
custom-model-hosts.json and the generated custom-model configs. Config backups
were the wider gap: a dated copy of users.json or settings.json matched none of
the name-anchored patterns. All are blocked now.

`codeman notify --file` relied on folder confinement alone, which holds nothing
back when a tab's folder is the home directory, and it would also post a
workspace .env. It now applies the attachment blocklist to the real path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:28:52 +01:00
Codeman maintainer f36bea86c7 Merge pull request #693: feat: edit history panel for local claude sessions (files edited, diffs, generated images) 2026-11-04 17:04:46 +01:00
Codeman maintainer 9d04a4fb02 feat: drop the typed first-message hint from the Discord bridge (#741)
Owner decision. Typing the note into a brand-new non-Claude tab spent a model
turn per tab and, for a spawned worker or a scheduled run, landed with the first
real prompt. Claude keeps its hint through the system prompt, and every other CLI
is told nothing. The setting goes with the path it gated: sessionIdle, the fresh
set, isHintableSession and the session:idle wiring. sendInput loses `fromUser`,
which was only ever false for the hint, and the deps test pins that a reply is a
person's own input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:03:14 +01:00
Codeman maintainer 7c8d440928 Merge pull request #741: feat(discord): two-way Discord bridge with a thread per tab 2026-11-04 16:59:16 +01:00
Codeman maintainer 91d157cae4 fix: name the pane directory field paneCwd and apply the review items for #740
`cwd` beside the fixed `workingDir` reads ambiguously on a public API surface, and
`paneCwd` says where the value comes from, like `paneExit`. Also pin the field on
the payload the browser really receives (getSessionStateWithRespawn), and give the
pane-directory text its own architecture-invariants paragraph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:59:16 +01:00
Codeman maintainer 4f50af6a9f Merge pull request #740: feat(terminal): resolve relative path links against the pane's current directory 2026-11-04 16:57:30 +01:00
Codeman maintainerandDDT dc6f8d27e8 fix: keep a Bash( row out of the parenthesised path detector (#735)
A Bash( row holds a command, so its parentheses do not bound one spaced path:
`Bash(/tmp/a.out input.txt)` became a single link to a file that cannot exist and
lost master's `input.txt` link. The path tools keep linking a spaced name whole,
and a hard-wrapped Bash( row gets no spaced lookup either. Test rows from #734.

Co-Authored-By: DDT <141296842+Hugo-DDT@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:57:10 +01:00
Codeman maintainer 76e9f364f9 Merge pull request #735: fix(links): preserve wrapped configuration filenames 2026-11-04 16:55:44 +01:00
Codeman maintainer 8c317d84c8 fix: apply the review items for #755 at merge
Say that settings.yaml is shared only until the session's first settings write
(dsh saves by rename, which replaces the link), warn when a session's own DSH_HOME
does not answer and the server's home is linked instead, test
guardSessionConfigDirHome, and document the linkFromRealHome exception and what a
custom-model dsh session does not carry over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:55:44 +01:00
Codeman maintainer 4c29aedbc5 Merge pull request #755: feat(custom-model): DeepSeek Harness injection through a per-session patch 2026-11-04 16:54:32 +01:00
Codeman maintainer 6232dffe03 fix: apply the review items for #752 and #757 at merge
The launch-id check is pure, so it now runs before the context warning and the
swap confirmation: nobody agrees to evict a model only to get the 400. Also the
search-box and in-use comments, the model-plain-colon wording, and the wiki's
order of the picker's marks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:53:26 +01:00
Codeman maintainer f7dfe091c5 Merge pull request #757: feat(custom-model): mark the model a session is using in the endpoint model picker 2026-11-04 16:51:56 +01:00
Codeman maintainer 3bcb55ce96 Merge pull request #752: fix(custom-model): context limits, Codex model override, context discovery and picker filter 2026-11-04 16:51:56 +01:00
Codeman maintainer 7b8e295647 fix: retire the OpenCode Console breach notice
A new install has no seen list, so every new OpenCode user was still warned about
an October incident that is long over. The mechanism stays for the next notice;
an empty list is now a valid file, and the validators keep running over whatever
it holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:51:56 +01:00
Codeman maintainer 0dbfc8dc2d fix: apply the review items for #744 at merge
Classify OpenSSL and LibreSSL's SSL_connect errors as unreachable again (`_` is a
word character, so \bssl\b missed them and the user read the temp directory as the
reason), point a cases-folder refusal at Clone Repo instead of Create New, and move
the mechanism and history out of CLAUDE.md into architecture-invariants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:50:44 +01:00
Codeman maintainer d9d6fad698 fix: cap the first tab on every pass, before a selection widens it (#748)
selectSession() toggles .active at once while _syncTabStripCorner() only runs in
the debounced render, so a cap set after measuring left a newly selected first
tab wider than the room and dropped the first row below the toolbar until then.
max-width changes nothing on a narrower tab, so the cap is simply always on, and
the clear-before-measure and finish-the-animation loops go. The no-usable-room
test uses 8 tabs: at 40 the scroll fallback fires first and it passed at any
threshold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:48:57 +01:00
Codeman maintainer 5e7ec1c522 Merge pull request #744: feat(cases): clone a repository into a custom folder 2026-11-04 16:47:48 +01:00
Codeman maintainer 1efe1d40c3 Merge pull request #745: feat(picker): New folder button in the path picker, on for Link Existing 2026-11-04 16:47:48 +01:00
Codeman maintainer 9d6f84f446 Merge pull request #748: fix(tabs): keep the L-shaped strip whichever tab is selected 2026-11-04 16:47:48 +01:00
Codeman maintainer 58f7dbc7ad Merge pull request #736: fix(settings): stop a failed settings write from saying it was saved 2026-11-04 16:47:48 +01:00
Codeman maintainer 7ed660ff11 Merge pull request #737: fix(mobile): show a press on the phone toolbar and accessory keys 2026-11-04 16:47:48 +01:00
Codeman maintainer 457dd54825 Merge pull request #738: test(docker): give the Update-Codeman.sh smoke harness a non-root appdata owner 2026-11-04 16:47:48 +01:00
RandalixandClaude Opus 5.5 109fd98038 fix(edit-history): colons in file:// paths, yield on forward reads, no store for unsupported
Follow-up from an own review pass of de436576:
- PATH_TOKEN_RE branch 1 stops at the next `file://` instead of at `:`,
  so `file:///tmp/plot-12:30.png` is found again; still linear.
- Forward reads yield between files like "Load older"; a store removed
  meanwhile ends the read and its printed paths are never probed.
- POST /edits/older and GET /edits/:entryId no longer load a store for
  remote, Docker or non-claude sessions (shared unsupportedList()).
- Time budgets widened (1 s per line, 3 s per chunk; ~3 s / 6.6 s before
  the fix) so the throttled suite does not flake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:50:21 +01:00
RandalixandClaude Opus 5.5 de43657676 fix(edit-history): bounded image probe, linear path scan (review of #693)
- probeLocalPaths: every agent-printed candidate goes through
  probePathKind() first (no pastCap, bulk scan), sequentially; only a path
  that answered 'file' gets realpath + stat. A path on a mount that stopped
  answering no longer hangs the sync, a queued sync or remove().
- PATH_TOKEN_RE: branch 4 starts only at a token boundary (`:` excluded so
  URLs stay ignored), branch 1 stops at `:`. Linear in the line length
  (30,000 `#` took ~3 s before).
- Load older yields to the event loop between the files it parses.
- GET /edits answers unsupported without loading a store.
- CLAUDE.md paragraph trimmed to the rules; probe and regex rules added to
  architecture-invariants#edit-history-panel.

Tests: stall test through the real sync and probe with a stat that never
settles; time-budget tests with 30,000-character inputs and a 139-result
base64 chunk; route test asserts no store load for unsupported sessions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:46:23 +01:00
Devvyn 4aeab0d9ce fix(custom-model): keep the in-use picker honest about down endpoints and exited panes
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
2026-11-02 19:15:37 +08:00
Devvyn e1c792f491 fix(custom-model): ignore a remembered model id the endpoint no longer lists
Round-four review, split fix 1: a stale 'last used' id kept the promotion truthy, so no row was tagged and the 'Nothing is marked' line stayed hidden. Derive it from promoted && models.includes(promoted).
2026-11-02 19:05:22 +08:00
Devvyn 1f0823c582 fix(custom-model): show which model a session is using when an endpoint cannot report it
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.
2026-11-02 19:05:22 +08:00
Devvyn 884e67d07e test(custom-model): pin the codex one-shot launch model on quick-start
Round-four review smaller item: one getCustomModelForPersist().launchModel expectation, like the pi/grok/omp tests beside it, pins #750 on the one-shot path.
2026-11-02 17:04:12 +08:00
Devvyn 2cc5f3f9e6 fix(custom-model): keep the picker search box unfocused on touch devices
Round-four review item 4: focusing the search input on every picker open raises the OS keyboard over the list on phones; guard it the way readmymind-ui.js does.
2026-11-02 17:04:01 +08:00
Devvyn 181e419644 fix(custom-model): check the forced launch id before any config dir write
Round-four review item 3: the swap route wrote the new config.toml into the very dir a live codex session uses as CODEX_HOME before checking the pattern, and its refusal then deleted that dir. buildCustomModelInjection is pure, so the check runs on its result and a refused id never touches the filesystem. Pinned by a two-apply codex route test.
2026-11-02 17:04:01 +08:00
Devvyn d980d70173 test(custom-model): pin the codex context lines 2026-11-02 16:54:56 +08:00
Devvyn fa2b81484b fix(custom-model): guard, document and test the session-supplied dsh home
Second review round on PR #755: skip non-absolute DSH_HOME candidates so a relative home can never mkdir under the server cwd; probe a session-supplied home with probePathKind in both async routes before the synchronous apply; test the session env-override lookup (seventh arg) incl. the self-link fall-through and the relative-home skip; reject '.' and '..' as realHomeFallback and enforce linkFromRealHomeDirs subset in the schema; fix the stale apply JSDoc and the two doc lines lagging the verified dsh run; state the shared/isolated split plainly.
2026-11-02 15:50:45 +08:00
Devvyn 1464332e91 fix(custom-model): create missing dsh home dirs before linking, rebuild restore bookkeeping, dedicated custom key var, schema guards for the real-home recipe
- linkFromRealHomeDirs marks directory entries: missing dirs are created (0700) instead of leaving a dangling link that failed every prompt with ENOENT; missing file targets are skipped
- _restoreCustomModel rebuilds the bookkeeping from the CURRENT recipe (configDir + env-key union) so a selection saved under the old env recipe can still be cleared after upgrade; drop the dead session-home recovery lookup
- deepseek's patch now references CODEMAN_CUSTOM_MODEL_API_KEY, so an in-session /model switch back to the official route cannot carry the custom key to api.deepseek.com (privilegedEnvKeys updated)
- schema: realHomeFallback must be one relative segment; linkFromRealHome requires it and must not collide with fileName
- docs: maintainer-verified real-dsh run replaces the outstanding-verification wording; shares-vs-isolates note; plan table row refreshed
2026-11-02 14:06:56 +08:00
Devvyn c646613c56 refactor(custom-model): move the real-home link into the registry recipe
- linkFromRealHome/realHomeFallback on the configDir recipe; the apply consumer is generic and names no CLI
- lstat guard so boot-recovery re-applies survive a dangling link; symlink is best-effort
- resolve the real home from the session's own dirEnvVar override before the server's, threaded through both routes and boot recovery (Session.envOverrideValue)
- deepseek links profiles/sessions/settings.yaml; boot-recovery test covers the session lookup
2026-11-01 21:42:02 +08:00
Devvyn aece02da87 feat(custom-model): DeepSeek configDir injection via a per-session patch
Replace the deepseek env redirect with a deepseek-patch-json recipe: a per-session DSH_HOME holding a generated cordis.patch.yml that declares a codeman-custom openai-completions provider over the installed profile. Ported from PR #752 commits 941df574 and 4bb1bfe9, minus the core context-limit changes.
2026-11-01 21:29:31 +08:00
Devvyn 1b8ac40baf fix(custom-model): core review fixes for context-length discovery and injection
- share one context-length resolution (discovered figure, endpoint default fallback) across the routes and boot recovery, with a recovery test
- record listed per-slot context figures with a slot count of 1 and force the slot-count fallback once per rediscovery
- open up the codex model launch token for qwen3-style ids (model-plain-colon), pinned by a spawn-golden case
- pass the full discovered window to opencode as limit.context
- translate the picker search strings and document the discovery precedence
2026-11-01 21:26:54 +08:00
Devvyn dbd1b6e587 refactor(custom-model): keep the DeepSeek patch rewrite on its own branch 2026-11-01 21:20:40 +08:00
Devvyn a97fa0de17 feat(custom-model): discover context length from /v1/models and add a default context length
Fixes #754
2026-11-01 21:08:52 +08:00
Devvyn 5582ec523b fix(custom-model): fix picker spacing and the no-matches line 2026-11-01 21:06:39 +08:00
Devvyn ed67596387 fix(custom-model): pass context limits and selected model to CLIs, and add model picker filter
- opencode: write limit.context/output from the discovered context length (#749)
- codex: override the native launch model when a custom endpoint is selected (#750), and set model_context_window
- model picker: add a search/filter box with a no-matches line
2026-11-01 21:06:38 +08:00
timkjrandClaude Opus 5.5 6a23fecb1c fix: keep the L-shaped tab strip whichever tab is selected
The wrapped header strip fell back to the plain wrap whenever its first
tab was wider than the room beside the toolbar. A selected tab is wider
(its gear and close show), so selecting the first tab flipped the whole
strip back: the toolbar took its own column again and, with long names,
every tab took a line of its own. Selecting any other tab brought the
full-width rows back.

The first tab is now trimmed to the room instead (tab-corner-fit, a
max-width from --tab-corner-first-max, its name ellipsized). Only a
toolbar that leaves less than TAB_CORNER_MIN_FIRST_TAB (120px) beside
it keeps the plain wrap, which does not depend on the selection.

Two measuring details make the trim hold:
- the room comes from the rounded-up float width, less the tab's 2px
  margin and a pixel for subpixel layout (a tab at the exact width still
  wrapped under the float);
- the first tab is measured with its trim cleared and its gear/close
  width transitions finished, or a grown room reads the last trim as the
  width and a selection change is judged by the width the tab is leaving.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:41:26 -05:00
Devvyn 7a3629e890 fix(picker): do not offer New folder inside a cases directory
A folder made in a cases directory is already a case (no scaffold), which Link
Existing then refuses by name and which deleting would take the linked folder
with. The route refuses a cases directory as the parent (the shared one and the
caller's own), a leading tilde in the name, and maps a vanished parent to 404.
Docs now say every name it accepts is one Link Existing accepts, and the wiki
describes the button.
2026-10-31 21:12:46 +08:00
Devvyn 41186f5ac3 fix(cases): never let a path decide whether a refused clone empties the folder
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).
2026-10-31 20:29:26 +08:00
Devvyn 869633ae6d fix(picker): make New folder names Link Existing can use, and tighten the route
The name rule now matches safePathSchema (letters, digits, space and _ - . ~,
no .., no leading dot, at most 255 bytes) so every folder it makes can be
linked. A file as the parent is a 400, errno text is no longer returned,
showHidden is forwarded like browse, and Create is disabled while the request
runs. Adds multi-user scoping tests.
2026-10-31 19:44:02 +08:00