v2.19.0

Released August 19, 2026 · View on GitHub →

Custom bots you can actually trust: pin a model, grant only the capabilities you choose, and scope a bot to the projects it belongs to — enforced at runtime across CLI, Mac, VS Code and the dashboard.

Added

an exact provider/model, expose only selected Files/Terminal/Tests/Git/Web/ MCP capabilities, and limit availability to all projects, selected project names, or personal mode. Legacy prompt-only personalities remain compatible and unrestricted. CLI, ACP, VS Code, macOS, and Dashboard Agent Studio share the same Markdown contract. Versioned files fail closed when tool metadata is missing or malformed, and invalid model/scope metadata cannot activate.

through session/list_personalities, session/set_personality, and session/sync_personalities without scraping chat output.

Changed

unselected tools are "removed from this agent's runtime", which reads as a guarantee that a Git-only bot cannot see file contents. It can: git show HEAD:file is functionally cat file, and history inspection (log -p, diff, blame) cannot be separated from the content it inspects. Nor is Git read-only: git rm, git commit and git push are on the allowlist, so a Git-only bot can rewrite the repo and publish the result. Every capability now carries a description, and choosing Git without Files spells out both halves — in the builder, in /personality, in the Mac persona sheet and in the VS Code picker. The enforcement is unchanged; the promise is now true.

cloud pull atomically applies changed personality bodies and backs up every divergent local copy under ~/.codeep/backups/personalities/. Custom command pulls remain additive.

/apply --interactive picker (state + key handling + rendering) moved from the 3.3k-line App.ts monolith into components/HunkPicker.ts, following the same { State, handleKey, render } convention as Settings/Export/ Search. App.ts now owns a single state field and wires it in. The picker logic is now unit-tested in isolation (12 tests pinning the y/n/a/q/↑/↓ semantics and the fires-exactly-once onComplete contract) — previously untestable inline. First of several planned extractions (mention picker, paste dialog, autocomplete) to bring App.ts down to a manageable size.

("Paste Detected" with Add/Send/Cancel) moved to components/PasteDialog.ts in the same shape. The key handler returns { state, action } — a discriminated action union (add-to-input / send-directly / cancel / none) — so App keeps the side effects (editor insert, message submit, notification) while the decision logic is pure and unit-tested (7 tests).

autocomplete (5 state fields) moved to components/MentionPicker.ts. The load-bearing @-sigil buffer math (re-adding the @ after slicing, without which a completed path silently stops being a mention and the file never gets attached) now lives in a pure applyMentionToBuffer() covered by tests, including cursor positioning and mid-buffer replacement (9 tests).

state fields) moved to components/CommandAutocomplete.ts — same shape as MentionPicker (pure key handler + commandToBuffer() buffer math, 9 tests). With this, all four picker-style widgets live outside App.ts as testable components.

/keysync are the first commands whose semantics (env-var hard-off checks, config toggling, status facts) live in one place used by BOTH the TUI and ACP dispatch — previously two hand-maintained copies that could (and did) drift in wording and behavior. Surfaces now only render the CommandResult. The env-var invariants (CODEEP_NO_TELEMETRY, CODEEP_NO_KEY_SYNC overriding any config flag) and the server-readable-keys disclosure are pinned by 11 unit tests. Remaining ~38 shared commands migrate incrementally, same pattern.

rateLimitApi: 240/min and rateLimitCommands: 120/min — generous for a full 50-iteration agent run, but a runaway loop now stops instead of burning quota. Existing configs are untouched; tune via /settings.

Fixed

differentially — a cell whose value already matches the shadow copy is skipped — which meant a BLANK cell was never emitted at all. Column 0 of the header is blank (the wordmark starts at x = 1), so whatever the terminal happened to show there before Codeep started stayed for the whole session; the session picker and confirm prompt kept it on screen, and only resizing the window cleared it. Both overlays now invalidate the shadow on the way in and out, and the invalidation fills it with a sentinel no real cell can hold, so blanks repaint too.

reportTurnStats helper that was defined but never called: the success path kept an inline duplicate, so cloud stats kept working there, while the catch path reported nothing at all. Tokens burned by a turn you stopped with Esc, or that errored, reached no one — and gracefulShutdown no longer sends the cumulative catch-all that used to sweep them up. Both paths now go through the one helper, and the inline copy is gone. tsc stayed silent about the dead function because noUnusedLocals is off.

The fetch_url tool already blocked private/loopback/metadata IPs (169.254.169.254), but the same model-controlled URL could simply be passed to curl instead and sail through. URL arguments (including scheme-less host forms like curl 169.254.169.254/latest and hostnames that resolve privately) now go through the identical assertFetchUrlAllowed check. The guard moved to a shared utils/ssrfGuard.ts module so fetch_url and the shell path can't drift apart.

dumped process.env into the model's context — including every provider API key riding in environment variables. Run env yourself outside the agent if you need environment info.

-execdir, -ok, -okdir and tar --to-command spawn arbitrary commands as arguments, silently bypassing the command whitelist (find . -exec rm -rf / \;). Plain find/tar usage is unaffected.

was called only on the TUI's manual-chat path and checkCommandRateLimit had no production call sites at all — an autonomous agent run (up to 50 iterations, each with its own API call and shell commands) was completely unthrottled. The guards now live at the transport layer: chat() in api/index.ts and agentChat()/agentChatFallback() in utils/agentChat.ts (covering TUI, ACP sessions, sub-agents and session titles), plus execute_command in utils/toolExecution.ts. Local no-key providers (Ollama) bypass the API limiter — there's no quota to protect on localhost. The duplicate check in the TUI submit path was removed so a request isn't counted twice. Source-level regression tests in rateLimitWiring.test.ts keep the guards from being silently dropped.

Removed

case-insensitive filesystems readme.md and README.md are the same file, so "the duplicate" never existed and deleting it would have deleted the README itself. (Caught before release; restored from git.)

Downloads

Install with npm install -g codeep@2.19.0 or brew install vladoivankovic/codeep/codeep.