Commit Graph
3643 Commits
Author SHA1 Message Date
Codeman maintainer 94912241bc feat(desktop): sign-macos.sh signs, notarizes and staples the app and its DMG
The release DMG is signed with the owner's Developer ID and notarized, so a
downloaded copy opens after macOS's usual confirmation instead of stopping at
Gatekeeper. The work is a script of its own, so CI and a Mac run it the same way:

  SIGN_IDENTITY=... NOTARY_PROFILE=<profile> \
    packages/desktop/scripts/sign-macos.sh <Codeman.app> <out-dir>

It re-signs the app with the hardened runtime, a secure timestamp and
macos/entitlements.plist, checks the signature is a Developer ID one with the
runtime flag, has notarytool notarize it and staples the ticket, builds the DMG
around the stapled app, signs it, notarizes and staples it too, and has spctl
assess both. Codeman.dmg and Codeman.dmg.sha256 reach <out-dir> only after every
check passed; a failed run removes an older pair there rather than leave one to
upload by mistake. A submission Apple does not accept stops the script with its
id and Apple's log. The notary login is either a notarytool keychain profile
(NOTARY_PROFILE, NOTARY_KEYCHAIN) or APPLE_ID, APPLE_APP_PASSWORD and
APPLE_TEAM_ID; `sign-macos.sh --check` tests that without doing anything.

The DMG layout (the app next to an /Applications link, UDZO, volume "Codeman")
moves to make-dmg.sh, which both scripts call, and gains a retry: hosted macOS
runners now and then fail hdiutil create with "Resource busy". bundle-macos.sh
keeps its SIGN_IDENTITY path as it was; its new --notarize checks the
environment before building, then hands the app to sign-macos.sh.
packages/desktop/README.md says the release DMG is notarized and how to do
the same by hand, and drops the CI build and notarization from its ideas list.

All three scripts are bash 3.2 clean, and shellcheck reports nothing. With the
macOS tools stubbed, sign-macos.sh was run under bash 3.2 through a full
signing, a notarization rejected for the app and for the DMG, two hdiutil
failures then a pass, three failures, a relative out-dir and every missing
setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 03:57:29 +01:00
Codeman maintainer 1e262b04d1 chore(release): take release.yml and docs/releasing.md from master as they are
This branch starts from feat/desktop-app (#763), which predates master's MCP
registry job and the release-notes body in release.yml and their pages in
docs/releasing.md. Both files are master's text verbatim here, so the Mac app
commit after this one changes them by additions only, and a rebase onto master
drops this commit as empty.

release.yml now names .github/workflows/publish-mcp.yml and
scripts/generate-release-notes.mjs, which exist on master but not on this
branch. The Release workflow runs only on pushes to master, so that matters
nowhere else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 03:57:11 +01:00
Codeman maintainer 92797c2619 test(desktop): unit tests for main.rs's decisions and a browser test for the connect page
main.rs was the only source file without tests, and it held three decisions
with real failure modes:

- Which login a connect goes out with. The choice was a closure body inside
  the probe worker; it is now choose_login, tested for every row: typed wins,
  then the Keychain, then the local Codeman's own login, which is used only
  when no saved password was expected and only for the same username.
- What the Keychain keeps afterwards (update_keychain): saved or deleted by
  Remember, never a copy of the local server's login, never a password the
  server wants changed, and the old user's item deleted before the new save.
  The in-memory Keychain store moves out of credentials' test module so other
  test modules can use it.
- When a commit in the main window means the server page is loaded.

The connect page had only substring checks in the repository; the checks that
drove it lived in a scratch script. They are now
test/desktop-connect-page.browser.test.ts: Chromium, the page's real CSP, a
stubbed native side, five ordered walks through every state of both tabs
(first run, logins, failures, redirects, Start, the install wait, the SSH
install, layout, double clicks, keys, a starting server, a WebKit too old).
Listed in BROWSER_TEST_GLOBS, so the nightly browser suite runs it and
`npm test` does not.

Also pins the install one-liner the page shows to the one Copy copies.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 03:54:49 +01:00
Codeman maintainer a42ce9c79f fix(desktop): a starting server, windows that open where expected, and the review's smaller findings
Found by the three-part review of this branch. Each was checked against the
source first; every decision that can run without a window has a unit test.

This Mac:
- A Codeman that runs but listens nowhere yet (the first seconds after login,
  a restart) was reported as "already running ... it listens at process N,
  which this app cannot open", and Start was refused for it. It is now its own
  state: the tab says "starting", looks again every second, and opens Codeman
  once it is up when a connect to this Mac had just been refused. The state
  table is a pure function with a test (setup::local_state).
- A server the app starts runs in the home folder, like one started by launchd
  or in a terminal. It ran in the app's own directory (`/` after a Finder
  launch), which Codeman then used as the default for sessions.
- Server detection accepts any entry word that `web` follows, so a dev server
  under a folder named codeman is seen and never gets a second server beside
  it.
- A login shell is polled from 5 ms up instead of every 100 ms, and the port
  after a launchd start every 100 ms instead of 500: both were pure waiting.
- The last line of stdout is no longer glued to the first line of stderr.

Addresses and policy:
- A tailnet name typed with a port gets http://, not https://. With a port it
  is Codeman's own listener; https there ended in a TLS error with no hint.
- within_base refuses `..%2F` and the other encoded ways out of the base path,
  so a sibling app behind a decoding proxy never sees the login.
- A status answer whose body stalls is a timeout, not "something else answers
  here".
- The folder pop-out pattern matches the two routes the server has.
- A malformed `window` in desktop.json no longer costs the server list.

Windows:
- Pop-outs step off the window in use. tao centres a window with no position,
  so every pop-out opened exactly on top of the one before.
- A window opened by a script no longer brings the app to the front: wry
  activated the application for it, whatever the window itself was told.
- The first-run window sits between the menu bar and the Dock. Centred in the
  whole display, its bottom was under the Dock on a 900 pt high one.
- The window frame is saved once after a move or resize ends, not once a
  second during the drag.

Also makes the connect page independent of TextEncoder and scrollIntoView, so
it runs in jsdom as well as in a web view.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 03:40:53 +01:00
Codeman maintainer 116db3ba66 fix(desktop): a connect page that holds still, answers a press and never strands the user
Layout:
- The header no longer moves. The page was centred with align-items, so every
  change of the card's height moved everything (up to 150px between states),
  and a page taller than its window lost its top above the scroll origin,
  where no scrolling reaches it. The top margin is now fixed, the card grows
  downward and has a minimum height, and the page scrolls from its top. A
  short window gets a compact header.
- The status line sits right under the action it reports on, above the
  "not installed there yet?" section, takes no room while empty, and is
  scrolled into view in a small window. It was below the fold there.
- Looking at this Mac again keeps what was found on screen (the pane used to
  collapse to "Looking..." and grow back).

Feedback and keys:
- Buttons answer hover and press, every control shows keyboard focus, the
  primary button and the footer meet WCAG AA contrast, "Opening Codeman" keeps
  its spinner while the server page loads.
- Return opens or starts the Codeman on this Mac, Esc cancels a connect.
  Install always takes a click.
- The This Mac result is a live region, Forget buttons are named after their
  server, a long unbroken token wraps inside the card, the status text can be
  selected.

Correctness:
- The second click of a double click acts only on the control that took the
  first. It could connect to the server that slid under the pointer after a
  Forget, or flip "Connect automatically".
- An address, username or password too long for the app is refused with a
  reason. The app drops such a message without an answer, which left the page
  on "Connecting..." (the limits are pinned to policy.rs by a test).
- A connect answer goes to the tab that asked, a failed SSH install to its
  tab, and the login block no longer claims a saved password for another
  server.
- An automatic connect to the Codeman on this Mac no longer looks at this Mac
  first; a failed connect, Cancel or an error asks for that look.
- A login over plain http:// to another machine says that it is unencrypted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 03:26:57 +01:00
Codeman maintainer 01d3379b74 fix(desktop): recover a crashed page, name every dialog's frame, and four smaller review fixes
The findings of the 2026-11-05 review of this branch, each with a unit test
where the code can be tested without a window:

- A window whose web content process ended (a WebKit crash, or macOS taking
  the process back under memory pressure) stayed blank: wry answers WebKit's
  notice itself, which turns WebKit's own reload off. The window now loads its
  page again. A second end within 30 seconds sends the main window back to the
  connect page and closes a pop-out, so it cannot loop (policy::recovery).
- A JavaScript dialog from a frame nothing names (about:srcdoc, a data: or
  blob: URL under an opaque origin) showed the page's text as its bold first
  line. The first line is now always the app's own, the "embedded" flag is set
  when the origin names no host (it was the wrong way round), and every
  sub-frame counts as embedded, a trusted web tab on Codeman's origin included.
- The connect page lost the text of an error that needs the login fields: the
  tab switch cleared the status it had just set.
- Start for http://localhost:PORT missed the LaunchAgent (the compare was
  exact against 127.0.0.1) and ran a second, unsupervised server on its port.
- The path picker's preview Open link (api/filesystem/preview) did nothing: it
  was the one page Codeman opens in a window that the allowlist lacked.
- The port of a running install was lost whenever the Terminal script that
  wraps the installer came first in ps.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 03:20:01 +01:00
Codeman maintainer 8ba5134174 feat(desktop): native macOS app that runs, installs or connects to Codeman
Adds packages/desktop, a small native app (Rust: tao, wry/WKWebView, muda,
objc2) that opens Codeman in its own window. The universal app is about 5 MB
(3.2 MB as a DMG) and runs on macOS 11.3 or later.

- Connect screen with two tabs. This Mac finds the local Codeman and offers
  Open, Start or Install. Start uses launchd for a LaunchAgent install and
  codeman web -d otherwise. It never starts a second server while one runs,
  because two servers on one data dir share live sessions. Another machine
  connects by address (recent servers, Keychain logins) and can install
  Codeman there over SSH.
- Installs run Codeman's install.sh in Terminal from a self-deleting
  .command script. The installer needs a terminal for sudo, Homebrew and
  ssh. A local install uses --local --service and the first free port from
  3000, and the app opens Codeman once it answers.
- The local Codeman's login is read from its LaunchAgent or
  ~/.codeman/.env. It is sent only to 127.0.0.1 on that port, only while
  the user's own Codeman process holds it, and is never saved.
- Native JS dialogs (alert, confirm, prompt) and HTTP Basic challenge
  answers, both scoped to Codeman's own pages, so the page keeps working
  after a server restart. App cookies are cleared before every connect.
- Pop-outs through window.CodemanHost, with an allowlist (webview/ never
  opens a window), external links in the default browser, downloads to
  ~/Downloads, real full screen and window state that is remembered.
- The connect page refuses a WebKit older than Safari 16.4, which Codeman's
  terminal needs.

Codeman side: viewSubagentTranscript shows a toast when the pop-up is
refused instead of failing, with zh-CN strings and a test that pins them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 00:36:49 +01:00
Codeman maintainer 2aa6016d33 chore: version packages
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
codeman@1.48.0
2026-11-05 00:04:01 +01:00
Codeman maintainer 1921a47aa8 docs: say exactly which routes refuse the Discord token file
The release note and the security doc read as if no file view could return it. The
attachment, folder and download routes refuse it; file-raw serves a session's own
workspace without the blocklist, so a session rooted at the home directory can
still read it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:28:32 +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 eb3b08d58b docs: fix text the release left stale (release review)
The startup hint is no longer typed, a Discord reply is one of the input paths that
set fromUser, edit-history is a state file, POST /api/filesystem/mkdir is in the API
reference, and the store's header says which routes really refuse it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:29:09 +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 126d967ba3 docs: put the remove() comment back on remove() (#693)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:04:46 +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 2526fdb0d8 style: format the DSH_HOME warning
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:55:58 +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 927c9a461e docs: place the New folder note after the picker overview (#745)
It sat between the "two places" list and the paragraph starting "It browses",
so that "It" read as New folder, and it said the button is not offered inside
the cases folder when the button shows and the route refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:49:58 +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 823013226d fix: record the settings baseline when a tunnel enable is abandoned (#736)
Both _abandonTunnelEnable() returns come after saveAppSettingsToStorage() wrote
the new values to this device but before _unsavedSettingsBaseline was set, so
after cancelling the no-password prompt the next Save sent no
showPlanUsageLimits flip and did not reload for a gesture-control change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:48:25 +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
Codeman maintainer de2bfb69c3 Merge pull request #742: test(tui): check the TUI pid option is unset in a way tmux 3.2a agrees with 2026-11-04 16:47:48 +01:00
Codeman maintainer 6670bc64b6 docs: add the Featured in Awesome AI Agents badge
Codeman was added to the Coding Agents section of NipunaRanasinghe/awesome-ai-agents (their PR #245, merged 2026-11-04).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 15:34:46 +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 4623ee0d12 fix(custom-model): keep the in-use changeset at mode 100644 like its neighbours 2026-11-02 19:05:22 +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 b177885dd7 docs(custom-model): say a PUT clears defaultContextLength and a learned figure wins
Round-four review smaller items: api-reference now states the opposite-of-apiKey behaviour for an omitted defaultContextLength, and the wiki sentence follows the code, modelContextLengths[id] ?? defaultContextLength: any stored figure beats the default.
2026-11-02 17:04:12 +08:00