Commit Graph
2763 Commits
Author SHA1 Message Date
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
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 5c8a557390 refactor(custom-model): one exported MAX_DISCOVERED_CONTEXT_TOKENS for routes and schema
Round-four review smaller item: the discovery comment claimed a shared ceiling while schemas.ts restated the literal twice; the constant now lives in schemas.ts (routes already import from it, no cycle) and both bounds reference it.
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 82d251d52b docs(custom-model): align the docs and comments with the core behaviour 2026-11-02 16:54:57 +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
Codeman maintainer 87c656283d feat(codex): let Codex sessions drive other sessions through the codeman skill
Measured on 1.47.0 with the same "spawn a shell worker, echo, read, delete"
task: DeepSeek Harness and OpenCode already pass end to end; Codex 0.162
failed three ways, fixed here.

- Start Codex 0.162+ with --no-daemon (new noDaemonFlag gate, fail-closed).
  The shared app-server daemon stopped every launch on an "incompatible
  feature settings" dialog when another version's daemon was running, and
  ran commands with another session's CODEMAN_* environment.
- Evaluate registry version gates against the gated CLI's own version
  (resolveSessionCliVersion) instead of claude's for every entry.
- Add the opt-in synced setting App Settings > Codex > Allow network access
  (codexNetworkAccess, default off): --config
  sandbox_workspace_write.network_access=true, filled as a launch default
  and clamped off for non-granted owners like the bypass switch.
- Skill: carry on with the curl path when `codeman` is not on PATH, re-run a
  sandbox-blocked call once through the CLI's approval path, name the new
  setting. Wiki and registry docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 06:21:38 +01: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
Devvyn b549a66aa3 fix(cases): skip the cleanup when git never ran, and refuse Docker workspaces
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.
2026-10-31 19:13:56 +08:00
Devvyn d5e4fe0f87 feat(picker): New folder button in the path picker, on for Link Existing
The picker could only choose an existing folder. POST /api/filesystem/mkdir
creates one folder inside a folder the picker may browse (same roots,
ownership and blocked-tree rules as browse), and PathPicker gains an opt-in
allowNewFolder that adds a New folder button and selects the result.
2026-10-31 18:26:00 +08:00
Devvyn 086249a386 fix(cases): never delete another clone's files after a failed direct clone
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.
2026-10-31 17:57:51 +08:00
Devvyn 68bd7e4d15 fix(cases): clone into the chosen folder itself, not a subfolder of it
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.
2026-10-31 16:43:11 +08:00
Devvyn 0bd15ddc84 feat(cases): clone a repository into a custom folder
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.
2026-10-31 16:19:20 +08:00
Devvyn c5d0e4b8b4 fix(discord): bound the table conversion, claim a message before posting it, list every CLI without a turn end 2026-10-30 22:28:13 +08:00
Devvyn 021641911e fix(discord): make the table separator test linear, record a posted message only after it posts, say which CLIs raise a turn end 2026-10-30 21:28:35 +08:00
Devvyn e5d82eb044 feat(discord): post a Markdown table as an aligned code block 2026-10-30 19:58:44 +08:00
Devvyn 093f242662 feat(discord): post a finished turn as the CLI's whole last message, stale thread cleanup, types moved to src/types 2026-10-30 18:26:35 +08:00
Devvyn d6c7d30bd4 fix(discord): tidy a thread made in the old channel, keep the two pending sets in step, settle the hint when the settings are off 2026-10-30 16:42:33 +08:00
Devvyn 97c698b249 fix(discord): ignore gateway frames after stop, hold the typed hint while a choice is pending, name the Discord group in the settings reference 2026-10-30 15:42:48 +08:00
Devvyn 9f18b78857 fix(discord): replace a gateway that gave up when the same config is saved again 2026-10-30 14:27:36 +08:00
Devvyn d044e49635 fix(discord): show the real reason a thread fails, keep the gateway across a save, refund the rate limit for a refused file 2026-10-30 12:58:03 +08:00
Devvyn a698729fb6 fix(discord): keep the thread of a discarded rebuild, close one made for a closed tab, unique tmp names, wider prune keep set 2026-10-30 11:43:09 +08:00
Devvyn b68792f988 fix(discord): say when the agent has exited, keep replies in order, settle a stalled response, prune dead tabs
- a reply to a tab whose agent has exited is answered in the thread, with no tick
- inbound messages run on one chain per thread, so a reply with a file cannot be overtaken
- postJson settles when a response stalls after its headers
- the thread map is pruned against the live sessions once after boot
2026-10-30 10:33:34 +08:00
Devvyn 72a59de96c docs(discord): write down the lone-digit rule, which installs have codeman, and tidy comments
- lone digit rule in CLAUDE.md, invariants, user docs, wiki, API reference and code comments
- say which installs have the codeman command
- reload() comment back above reload(), merge the sendInput comments, typedHint in the hint comment
- drop the stray dot after --urgent in the startup hint
- config-backups wiki: name the discord.json token, not the URL
- comma in the CLAUDE.md auto-named list
2026-10-30 09:12:41 +08:00
Devvyn 212989b349 fix(discord): digit replies without Enter, start the gateway after restore, hide other users' session ids, fix docs
- a lone digit reply is typed without Enter while the tab has a pending permission or question
- the gateway starts after restoreMuxSessions (hold and release), so a reply during restore is not told the tab is gone
- discord-notify resolves an id prefix only among sessions the caller may see
- restore docs/architecture-invariants.md from base and add only the Discord section
- docs: self-ignoring folder, no logging, Notifications group, Send test, local-tab-only Claude hint, typedHint wording
2026-10-30 07:58:33 +08:00
Devvyn dc22519adb fix(discord): discard a thread created for a replaced parent channel, test the notify timeout, document the bridge invariants
- a thread creation that finishes after the parent channel changed is dropped, and a finished creation only clears its own pending entry
- postJson takes an optional timeout, pinned by a test against a server that never answers
- drop the flat discord-bridge.json fallback
- add a Discord bridge section to architecture-invariants
2026-10-30 06:42:17 +08:00
Devvyn 1ab54b9e9b fix(discord): report a refused post as a failure, bound the file probe, drop threads of a previous channel
- codeman notify exits 1 when the route answers 200 with data.ok false
- notify request times out at 30 s instead of hanging
- fileProblem probes the file with the bounded probe before realpath
- the thread map records its parent channel; a new channel starts afresh
- threadFor returns its failure instead of sharing this.last
2026-10-30 04:58:17 +08:00
Devvyn 0db9b55961 fix(discord): rate limit discord-notify, bound the folder probe, stop sendTest swapping the live config
- discord-notify: per-session token bucket (6 a minute), 429 RATE_LIMITED, plain line from codeman notify
- fileProblem probes the working folder with the bounded probe before realpath
- sendTest uses its own DiscordApi and leaves this.cfg alone
- drop the resumed-tab check from the typed hint (only Claude's field was read)
- first sessionRenamed per session records the name without a PATCH
- boot goes through ensureLoaded, so one gateway starts
- a thread created without an id reports the failure
2026-10-30 03:47:25 +08:00
Devvyn 6d4357502b feat(discord): make the typed first-message hint a separate opt-in, off by default
Claude keeps the silent system-prompt hint (startupHint, on). Other CLIs are sent the typed message only with typedHint, which costs a model turn per tab.
2026-10-30 01:38:37 +08:00
Devvyn e95d38a944 fix(discord): load the thread map once, so alerts that race at startup keep their threads 2026-10-29 23:04:20 +08:00
Devvyn eb0553a645 fix(discord): address review on #741
Builds the alert tail only for a post that goes out; separates deduped from disabled and never dedupes a deliberate notify; types the startup hint with fromUser false and skips resumed, remote and Docker tabs; clears per-tab maps and closes the bridge on stop; acts only on a real delete, not a detach; serialises thread-map writes; reloads the bridge after a discord.json restore; probes and self-ignores .codeman-discord; validates the message id; reacts when a reply is rate limited; documents what leaves the machine.
2026-10-29 23:03:26 +08:00
Devvyn 902d4491e5 feat(discord): testable codeman notify, short-id route, wiki page, docs and glue tests
Moves the notify command into src/cli-notify.ts with tests, lets discord-notify take the 8-character id a Docker case exports, adds the wiki page and index entries, rewrites the stale changeset and security notes, and pins the WebServer wiring.
2026-10-29 21:46:06 +08:00
Devvyn dcf51b1715 fix(discord): post the rendered screen, not the raw stream, as the alert tail
A full-screen UI repaints in place, so the raw output stream read as garbage. Read capture-pane instead and drop rules, blanks and escape codes.
2026-10-29 21:31:37 +08:00