Commit Graph
236 Commits
Author SHA1 Message Date
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 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 5e7ec1c522 Merge pull request #744: feat(cases): clone a repository into a custom folder 2026-11-04 16:47:48 +01: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 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 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 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 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 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 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 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 1c4338e8b5 feat(discord): codeman notify, startup hint for every CLI, and file send/receive
Adds the codeman notify command, a startup hint (Claude via --append-system-prompt, other CLIs once on first idle), files saved from thread attachments into the tab folder, and --file uploads limited to the session folder.
2026-10-29 21:27:23 +08:00
Devvyn 61bf26cfdf feat(discord): two-way Discord bridge with a thread per tab
Alerts for permission prompts, questions, idle and errors post into a per-tab thread; a reply from an allowlisted user is typed into that tab. Thread creation, thread cleanup on tab delete and pinging are configurable. POST /api/sessions/:id/discord-notify lets a CLI ask for the user. Setup is in docs/discord-bridge.md.
2026-10-29 21:04:18 +08:00
Codeman maintainer c04fa97f78 chore: replace the em-dashes this release added with plain punctuation
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>
2026-10-27 09:11:19 +01:00
Codeman maintainer 5908cc51fd Merge pull request #694: feat(agent): COD-529 add bounded inspection and guarded submission
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>
2026-10-27 02:14:44 +01:00
Codeman maintainer 6612a89ebc Merge pull request #714: feat(git): a Fetch button in the Git status header, and an interval for it app-wide and per case
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>
2026-10-27 02:11:49 +01:00
JD fc0ecc6051 fix(tunnel): give the unauthenticated tunnel its own acknowledgement
The network acknowledgement that install.sh writes for every passwordless LAN install also satisfied the tunnel guard, so one click on the welcome screen's tunnel link published full terminal control to a public URL with no login and no confirm. The tunnel now needs CODEMAN_ALLOW_UNAUTHENTICATED_TUNNEL or --allow-unauthenticated-tunnel (carried by web -d and service install), and with no login the browser asks before every enable, reading a new loginConfigured field from GET /api/tunnel/status.

The guard is now decided once per request and required again where the tunnel starts, so a save that found the tunnel running can no longer restart it unguarded after a concurrent stop.

Closes #706
2026-10-26 03:13:25 -04:00
Devvyn 3411d7f35a fix(git): the fetch timeout reaches the whole tree, a tick covers every session on its folder, and the rest of the review (review)
`execFile` signals the `git` parent only, so a fetch that times out can leave its http helper
still running, and on the interval that repeats every run with nobody watching. `runFetch` now
spawns `detached`, so the fetch and the helper it spawns share one process group, and the kill
is SIGTERM to the negative pid then SIGKILL `FETCH_KILL_ESCALATION_MS` later; the escalation is
not cancelled when the promise settles, because the caller is answered at the timeout while the
group is still dying. The output cap is kept, and the clone's slot queue is not needed here.

The tick is two passes: every enabled local session is collected by folder, a folder is due when
ANY session open on it is due, the folder is probed once for the group, and the run is published
to every session on it. A single pass decided by the order the sessions were read, so [long,
short] published only the short one and the 3600 s session's row never updated again while its
folder was fetched every 30 s.

`gitNonInteractiveEnv` writes BatchMode ssh as a default, not an override: `core.sshCommand` is
read inside the same slot and the same bound as the fetch it serves, and `fetchEnvFor` keeps the
repository's answer when it has one, because that is usually the setup that works on a machine
with several ssh setups. The `accept-new` host key it can write is stated next to the BatchMode
note: a prompt is stopped, a write is not.

The write claim is scoped to the Git panel and to the timer, with cloning and worktree management
named as the writes that happen when the user asked for them, and the zh keys sit with the group
of the UI surface their English string belongs to.

Tests: the process-group test proves the tree by group membership, because the helper is a
grandchild (`git fetch` runs `git remote-http`, which spawns `git-remote-http`) and because the
group is what the negative pid reaches, and it remembers each process once found instead of
re-scanning a group it had already emptied. The non-zero-exit test changed premises: `git fetch
--quiet` in a repository with no remote exits 0 on git 2.47.3, so resolving empty stdout there was
correct, and the case that has to reject is a remote pointing at a path that is not a repository.
The join test asserts after the run and does not await the request before the held run is
released, which would wait on the run the test is holding.
2026-10-25 22:31:25 +08:00
Devvyn ea4318947b fix(git): the interval fetches one run per folder, and the rest of the review (review)
The scheduler is the part that runs with nobody watching, so it is decided per folder
rather than per session: sessions sharing a folder are the same clone and share one run
that both see, `inFlight`/`lastRunAt` are keyed by the resolved folder, the schedule is
stamped when a folder is picked instead of when its run starts, and a tick is skipped
while the previous one is still going. The click route joins a run already going for the
same folder rather than starting a second fetch in one clone.

The tick keeps ONE bound for the git processes of the whole run and hands it to every
folder, and bounds its folders with a bound of its own: the same semaphore cannot serve
both levels, because a folder holding a slot would then wait for a slot its own fetch
needs. Both bounds are `FETCH_CONCURRENCY`, so the ceiling is unchanged. The bound claim
in CLAUDE.md, the `Limiter` jsdoc and docs/api-reference.md now describes it as it is.

Before anything is fetched the folder is probed with the bounded path probe, so a case on
a mount that stopped answering is skipped and never touched.

The rest of the review: the hooks are named as they really are (`reference-transaction`,
`pre-auto-gc` through the maintenance a fetch triggers, config commands such as
`credential.helper`) and `FETCH_CONFIG_ARGS` turns maintenance and gc off for an
unattended run; the claim that an agent in the same repository is not disturbed is
replaced by the ref-lock collision it actually is; the click route publishes its own run
as `trigger: 'click'`; `autoSaveGitFetch` uses `_apiJson` so a 400 shows the error toast
and applies the returned settings; and the two wording fixes.

Tests: the scheduler pins one run per folder, the two bounds, the probe before anything
runs, and that closing the session holding the override keeps the folder while another
session is still open on it; the route test pins the click publish and the join, asserting
the join before the held run is released — releasing first lets the run finish and clears
the lane, so the joiner would be entitled to its own fetch and the test would pass for the
wrong reason.
2026-10-25 20:04:01 +08:00
Devvyn 9432d9f9e2 feat(git): a Fetch button in the Git status header, and an interval for it app-wide and per case
Fetch is the only git command Codeman runs that writes, and the only one that runs a
repository's own fetch-post hook, so it stays behind a click and off by default. The Git
status window's header row gains Fetch; App Settings -> Bottom bar gains the switch and
the interval, synced because the scheduler acts on them, and Session Options -> Git
overrides both per case. The scheduler reads settings fresh each tick, times the interval
from the start of a run, never overlaps runs, and re-checks the Docker and remote rules
every tick before any git runs. Nothing but remote-tracking refs moves: HEAD, the index
and the working tree are proven unchanged against real git while origin/main advances.

Tests: src/git-fetch.ts and the scheduler, the per-case settings route, the synced/display
setting split, the two new i18n rows, and the Fetch block of the Git status browser suite.
2026-10-25 17:23:55 +08:00
Aamer Akhter c78635a3d3 feat(agent): make coordination routes opt-in and address review
Gate inspect, stage and submit behind the synced agentCoordinationEnabled
setting (default OFF, App Settings > Cross-session features, zh-CN
strings). While off, all three routes answer 403 FORBIDDEN naming the
setting, checked before session lookup; the CLI verbs print it verbatim.

Remember the attributed Codex rollout per session and re-verify only its
head on later calls, rescanning only date directories not older than it
for a /new conflict. The first walk reads 1 KiB prefixes first, so a
store of 2,100+ unrelated rollouts still attributes.

Answer unexpected inspection errors with 422 OPERATION_FAILED and log
the session id and error code only.

Read the guarded composer glyph and footer from the CLI registry: codex
workDetect.promptGlyph and a new workDetect.composerFooterLine
capability, compiled through compileVersionRegex. Codex footer effort
words are one shared list with modelDetect.screenLine.

Fix the wiki API reference link, frame the versioning policy entry as a
case of the experimental rule, join the CLAUDE.md Agent CLI paragraph,
and update the changeset, skill reference and architecture notes.
2026-10-24 22:54:17 -04:00
Aamer Akhter a24b0ec7bb feat(agent): add bounded inspection and guarded submission 2026-10-24 22:37:06 -04:00
Codeman maintainer e739b2e9d4 Merge pull request #690: feat(agent): per-session agent inbox (discussion #558)
Conflict in src/web/routes/session-routes.ts (the import block) resolved
by keeping both sides: #680's claudeProjectDirName import stays, and the
fastify import takes #690's form without FastifyReply, since #690 moved
abortOnClientHangUp (its only user here) into route-helpers.ts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 15:29:15 +02:00
Codeman maintainer 9ba24a02df fix: apply the review items for #690 at merge
Boot prune keeps the inboxes of sessions on the reboot-restore offer (their records are already pruned and a rebuild reuses the id), with a case in test/agent-inbox-server-wiring.test.ts.
docs/wiki: post, inbox and ack in the codeman agent command block of Driving-Codeman-From-An-Agent, and "Keep agent inbox across restarts" in Settings-Reference.
codeman agent inbox prints message text through terminalSafeText (ANSI stripped, other C0, DEL and C1 controls shown as \xHH); --json stays as stored.
Shutdown flushes the inbox snapshot again after app.close(), for a post that lands after the first flush.
POST /api/cleanup-state drops the inboxes of the records it removes.
InboxPostSchema derives its fence from MAX_MESSAGE_BYTES instead of the literal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 14:15:49 +02:00
RandalixandClaude Opus 5.5 a1a33cf050 fix(agent-inbox): review round 1
- codeman agent post: accept text that starts with "-" (markdown from a
  file), --json and -h stay options.
- DELETE /api/sessions/:id on a persisted-only row drops the inbox a detach
  kept.
- inbox --wait: wait.ended is exit 3 only when the session list no longer has
  the session; a restart or detach ending the wait is exit 2 (wait again).
- A read whose client hung up during ?wait= marks nothing seen.
- InboxPostSchema bound is 4x the byte cap, so every oversize message gets
  the store's "post a file path" answer.
- Store: restore sorts oldest first; the TTL sweep checks every message,
  not only the first.
- Docs: admins can act as any session of the receiver's owner (now tested
  for admin and non-admin); a peek with ?wait= does not block; exit codes.
- Tests: partial PUT /api/settings keeps persistence on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 12:45:48 +02:00
RandalixandClaude Opus 5.5 c6dfaa2cac feat(agent): per-session agent inbox (discussion #558)
A coordinator can leave text for a worker without typing into its pane.

- Store (src/web/agent-inbox.ts): read until ack by id, seen only for the
  receiver, 16 KiB/message, 512 KiB + 200 messages per inbox, full = 409
  CONFLICT (never drops the oldest), 12 h TTL from each message's createdAt.
- Routes (routes/inbox-routes.ts): POST/GET /api/sessions/:id/inbox
  (?wait= parks while nothing is unseen), POST .../inbox/ack (receiver
  only, 403 otherwise), GET /api/agent-inbox/summary (owner-scoped counts,
  not in toState()). Caller = resolveParentSessionId().
- Inbox waiters are the wait registry's third kind: same caps, ended by
  cancelAll/cancelEverything.
- Opt-in snapshot (agentInboxPersistEnabled, synced, default off):
  agent-inbox.json via dataPath(), 0600 tmp+rename, off deletes it, boot
  prune only after a successful restore. Dropped on delete, kept on detach.
- CLI: codeman agent post | inbox [--wait] | ack, INBOX column in ls.
- waitCapacityResponse/abortOnClientHangUp moved to route-helpers.ts
  unchanged.
- Docs: api-reference, README, CLAUDE.md, architecture-invariants, skill +
  plugin mirror.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 12:45:48 +02:00
Devvyn ab0bc0aecd feat(cases): rename a case and its folder, with a lock and live-session handling 2026-10-23 04:12:37 +08:00
Codeman maintainer 463a083a23 Merge pull request #676: feat(tabs): per-case default session group
Conflict in CLAUDE.md's State Files line resolved by keeping #661's new
config-backup entries and this PR's "case default groups" in state.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:54:54 +02:00
Codeman maintainer 3b6818449e Merge pull request #661: feat(config): timestamped config backups with a configurable folder and retention
Conflicts in CLAUDE.md, README.md, docs/api-reference.md and
src/web/server.ts were additions beside #662's Copilot quota read and
#666's keep-warm sections; both sides kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:54:40 +02:00
DevvynandClaude Opus 5.5 33d697afe3 fix(custom-model): warn when parallel llama.cpp slots leave too little context per request (#658)
Squash of PR #658. Discovery records each llama.cpp model's parallel slot
count (from --parallel/-np, or /props total_slots when the launch command
states none), and a Claude launch warns when the context split across
those slots leaves less than 40K tokens per request. An explicit
--parallel beside a /props context records a slot count of 1, so a later
round overwrites an earlier count, and a non-2xx /props is remembered per
launch command while a network error is retried.

Merge-time fix by the maintainer: the architecture-invariants sentence on
the explicit --parallel case now says it records a slot count of 1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:32:24 +02:00
Codeman maintainer f80f107445 Merge pull request #666: feat(sessions): keep an idle session's prompt cache warm with a guarded keep-alive turn
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:31:08 +02:00
Codeman maintainer b67018b318 Merge pull request #670: fix(input): keep the line breaks of a multi-line mux prompt, pasted as one block
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:23:17 +02:00
Aamer Akhter 74c980413a feat: per-case default session group
Case settings gains a "Default session group" selector (Ungrouped plus every
named tab group of the owner). A new session of the case lands in that group
in the same versioned layout write that registers it, so it is drawn in place
with no Ungrouped-to-group jump.

- Stored per owner and case in state.json (caseDefaultGroups), as the group's
  stable id or null for Ungrouped; read and written only through
  TabLayoutService under the owner lock.
- Precedence in sessionCreated: explicit tabGroupId, then the parent
  placement, then the case default, else Ungrouped. A deleted or foreign id
  never fails a spawn and is forgotten only after the creating write lands.
- POST /api/quick-start (local, linked, remote, Docker) and
  /api/ralph-loop/start pass their case; POST /api/sessions infers it
  lexically from workingDir. The create response carries tabLayout when a
  case default placed the session.
- GET/PUT /api/cases/:name/default-group; deleting a case (or an admin
  deleting a user's case folder) forgets the value.
- The selector mirrors desktop and phone, reverts on a refused save, and is
  redrawn from every adopted tab layout; zh-CN strings added.
2026-10-22 03:42:42 -04:00
e58c1a2f19 fix(limits): lower the session ceiling to 462 so the layout budget holds
An owner's tab layout holds at most MAX_TAB_REFS (512) refs, counting saved web
tabs (up to MAX_WEBVIEWS, 50 by default) as well as sessions. With a ceiling of
500, session creation could still fail with HTTP 500 on the layout limit before
the capacity message. 512 - 50 = 462; a test pins the ceiling plus MAX_WEBVIEWS
inside MAX_TAB_REFS so the two limits cannot drift apart again.

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:59:29 +02:00
74dcf9a59d docs: the submit check cannot reliably recognise a multi-line paste
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>
2026-10-21 08:03:55 +02:00
b073035bdc fix(limits): clamp CODEMAN_MAX_SESSIONS to 500 and document it
Review follow-up. Other bounds assume a small session count: an owner's
tab layout holds at most MAX_TAB_REFS (512) refs and PUT
/api/session-order accepts at most 500 ids, so with
CODEMAN_MAX_SESSIONS=600 the 513th session creation answered HTTP 500
instead of the capacity message. The value is now clamped to a named
MAX_CONCURRENT_SESSIONS_CEILING of 500; any digit string above it
(including one past the safe-integer range) resolves to 500 instead of
falling back to 50. The resolver reads process.env directly, the unused
env parameter is gone.

Docs: a CODEMAN_MAX_SESSIONS row in the Settings Reference environment
table, the env name next to the cap in the cron guide and the
architecture invariants, and both copies of the skill's endpoints
reference now give the per-user cap as half the global cap (25 by
default). The changeset says "1 to 500". The test drops the
`as unknown as string` casts and adds '1000', '501' and an
out-of-range value, each refusing at 500.

Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:01:24 +02:00
Claudia[bot]andClaude Opus 5.5 fe10cad83f docs: finish dropping the stripped-newline rule from the input docs
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>
2026-10-21 04:11:28 +02:00
Claudia[bot]andClaude Opus 5.5 04eb1ed158 feat(limits): make the global session cap configurable with CODEMAN_MAX_SESSIONS
The cap of 50 concurrent sessions was a hard-coded constant, so a machine
with the memory for more (or a small box that wants fewer) had to patch the
source. CODEMAN_MAX_SESSIONS now sets it at startup. The default stays 50,
and anything that is not a positive integer (empty, 0, negative, fractional,
1e3, out of safe-integer range) falls back to it, so a typo never locks
session creation.

Every site keeps reading MAX_CONCURRENT_SESSIONS, so the session routes,
cron launches, the scheduled-run loop and the multi-user per-user cap (half
the global cap unless CODEMAN_MAX_SESSIONS_PER_USER is set) all follow the
variable. The test goes through sessionCapacityState and
sessionCapacityMessage, the check every session-creating route uses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 03:46:55 +02:00
Devvyn a0b9570457 fix(config-backup): age a snapshot from its successor, ignore activity-only rewrites, keep at least 2
Review round 3 on #661: retention aged each snapshot by its own createdAt, so the first change after a quiet month deleted the last good copy; it now ages from when the next snapshot was made. users.json lastLoginAt, settings.json lastUsedCase and intents.json recentPrompts/updatedAt are volatile keys for the change check. The keep count has a minimum of 2. Wiki restore paragraph aligned with RESTART_REQUIRED.
2026-10-20 18:01:43 +08:00
Codeman maintainer f2230d5d3d feat(config): timestamped config backups with a configurable folder and retention
The files that hold a user's customization (settings.json, clis.json, custom-model-hosts.json,
webhook.json, intents.json, linked-cases.json, push-keys.json, users.json, .env) are plain JSON
in the data dir, rewritten by the server, the Settings UI and the CLI, with no copy to go back to.

A snapshot is taken at startup and whenever the tracked files' content changes (checked every
five minutes, and only written when a digest of the file set differs from the latest snapshot's),
into `<data dir>/backups/config` by default or any absolute folder. Retention keeps the newest
configBackupKeepCount (1-500) and, when configBackupKeepDays > 0, drops older ones; it only ever
deletes folders it made (name pattern and a parseable manifest) and never the newest.

GET/POST /api/config-backups list and back up now; POST .../:id/restore verifies every file against
the manifest's SHA-256, takes a pre-restore snapshot, then replaces files atomically with mode
0600, writing only allowlisted names. Snapshots are 0700 folders of 0600 files and all routes are
admin only in multi-user mode. Settings -> System gets the controls and a Restore list.
2026-10-20 18:01:42 +08:00
JD 5db6c633d3 fix(sessions): leave a pending idle alert to the user through a keep-alive turn
Only a human spends an idle alert, but the keep-alive turn resolved an unseen one three ways: its working transition, its stop hook, and its own idle prompt replacing the item with a pre-acknowledged one. While the turn runs, a pending idle item is now left exactly as found, the page's stop handler returns on the keep-alive reason before clearing hooks or raising Response Complete, and session:working/session:idle carry keepAlive so the stuck-detection timer neither restarts nor re-arms.

The window that marks the turn closes on its own idle prompt or any other submit instead of a fixed three minutes, which a slow turn's idle prompt (sent a minute after the turn ends) could outlast. The service keeps the default and cap in memory, read through a strict reader so a mid-write read is retried rather than kept, and the cap is clamped before the strict settings save.
2026-10-19 01:42:00 -04:00
JD 71a51df2e2 feat(sessions): keep an idle session's prompt cache warm with a guarded keep-alive turn
Claude Code lets a subscription's one-hour prompt cache expire after an hour idle, and the first turn back re-writes the whole conversation at the cache-write rate; nearly every gap over an hour goes cold, while the saving is concentrated in gaps of one to four hours. Codeman now sends one short keep-alive turn five minutes before the expiry Claude Code reports on its statusline, so the next real prompt still reads the conversation from cache, and stops two hours (configurable) after the last real prompt.

Codeman owns the idle policy for these sessions, as agreed in Discussion #554: a launch with keep-warm on also carries idleCompaction: false in its one --settings JSON, so Claude Code's own idle compaction cannot replace the conversation with a summary a minute before the nudge. It is decided once per launch (keep-warm on, the statusLine exporter injected, Claude Code 2.1.290 or newer, an unknown version fails closed like --name), recorded on the mux record, and picked up by any relaunch; a launch with keep-warm off is byte-identical. A session switched on after launch is kept warm best-effort and the readout says so.

Every guard lives in one pure rulebook: the opt-in (an agent-spawned session does not inherit the app default), a launch that reports cache state, a warm 1h cache with enough tokens at stake and no compaction this gap, a live pane, no usage-limit pause, no respawn, Ralph, task or waiting agent, idle, no open dialog, an empty composer and no recent keystroke. The nudge is a submit and moves lastSubmitAt, which the response viewer's conversation correlation needs; the cap counts from its own persisted lastRealPromptAt instead. Read My Mind drops the exact nudge text, and the turn's stop hook and idle prompt open pre-acknowledged so nothing alerts. The last cache report and the clock survive a restart, so the timer re-arms.
2026-10-18 15:19:08 -04:00
Codeman maintainer 8e6abf4bfd Merge pull request #592: feat(cli-registry): GitHub Copilot CLI as a run mode
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>
2026-10-18 08:17:06 +02:00
Codeman maintainer 44f87de9a7 fix(#592): doc drift around Copilot's resume, wheelForward and the env prefixes
- 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>
2026-10-18 08:09:39 +02:00
Devvyn 76479fdc96 feat(sessions): make pinning every tab name an opt-in setting
Add pinCliSessionName (synced, default OFF), re-read at each spawn and respawn.
Off keeps the #457 behaviour; on pins every non-blank tab name as --name.
Update the docs, JSDoc and changeset that stated the old rule.
2026-10-18 13:58:09 +08:00
Devvyn 6cca5465a9 fix(copilot): a model value must start with a letter or digit
Like resumeSessionId, model follows a flag, so --model --yolo made --yolo its own flag and bypassed the allowAll clamp. New model-plain token pattern, the same rule in the create schema, and golden and schema tests.
2026-10-18 00:47:54 +08:00
Devvyn 5b77c271b0 docs(copilot): document GitHub Copilot CLI in full
- New docs/copilot-integration.md in the shape of the other CLI guides: install and identity
  probe, authentication (keychain vs plain-text config.json, COPILOT_GITHUB_TOKEN), what Codeman
  wires up (Run button --yolo and its clamp, model, resume, continue, --name rule), BYOK custom
  endpoints, terminal behaviour (drag selection, wheel and touch), the codeman agent verbs
  (ready mark, read), plan usage, Docker, Remote SSH, what is deliberately not wired, known gaps.
- Agent-CLIs wiki: the eleven-modes table, a GitHub Copilot CLI section, the env prefix row, the
  feature-comparison rows and the status endpoint list.
- README, Home, FAQ, Core Concepts, Docker Cases, Remote SSH, Contributing, The Dashboard and
  Custom Model Endpoints: mode lists and counts; CLAUDE.md: stack line, SessionMode, prefixes,
  resolver list, external-CLI notes and the plan-usage source; api-reference MCP target wording;
  the env allowlist error message names COPILOT_*.
2026-10-18 00:47:53 +08:00
Devvyn dc87ef724c fix(cli-registry): address review on GitHub Copilot run mode
- Drop GH_TOKEN / GITHUB_TOKEN from env.allowedKeys. The allowlist is one global list with no
  mode context, so they were settable on every session. COPILOT_GITHUB_TOKEN is already admitted
  by the COPILOT_ prefix.
- Declare capabilities.mcpConfig (copilot-json, COPILOT_HOME relocation) so Settings does not
  list Copilot as unsupported while the sync-only target also lists it. The sync-only row is
  skipped while the run mode is enabled.
- Require a leading alphanumeric on the resume id (path-segment in the registry, matching regex in
  the schema): --resume's value is optional, so `--resume --yolo` made --yolo its own flag and
  bypassed the allowAll clamp. Golden cases added; "ids only" wording corrected.
- Remove the unread overlays.credStore line and document signing in inside a Docker container.
- Add the zh-CN "Run GitHub Copilot" string, pin the new declarations in tests and update the
  MCP registry expectations.
2026-10-18 00:47:47 +08:00
Devvyn c5de8383e7 feat(cli-registry): GitHub Copilot CLI as a run mode
Copilot (`copilot`, npm @github/copilot) as a stock CLI entry with the same launch options as the other agent CLIs: model (--model), resume (--resume), continue (--continue) and allow-all (--yolo, clamped off for non-granted multi-user owners). Adds a resolver and GET /api/copilot/status probe, the agent-image package, Run-menu/mobile/logo entries, skill docs and tests.
2026-10-18 00:47:46 +08:00