Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fe21448
Endpoints: add your own OpenAI-compatible servers from Settings
claude Oct 12, 2026
20f210a
Config: the theme follows the desktop by default
claude Oct 12, 2026
4bfe941
Search: find words in the agent's answers and show where they matched
claude Oct 12, 2026
6fec659
Palette: fuzzy search, jump to conversations, recent commands
claude Oct 12, 2026
6a91a7e
Settings: an Agent page for limits, recovery, subagents, checkpoints …
claude Oct 12, 2026
4f1cffb
Docs: the retry example matches the default of three retries
claude Oct 12, 2026
5cbcf41
Transcript: find in conversation (Ctrl+F)
claude Oct 12, 2026
d9d98f0
Appearance: text size, live preview and every theme from the palette
claude Oct 12, 2026
b82127c
Transcript: message times, full tool output with Copy, conversation t…
claude Oct 12, 2026
a8661f2
Composer: Enter picks the highlighted / command
claude Oct 12, 2026
dc69cd4
Automations: Delete asks in the app's own dialog
claude Oct 12, 2026
a778bba
Toasts: wait while pointed at or focused
claude Oct 12, 2026
be83ae9
Conversation: Latest activity jumps when motion is reduced
claude Oct 12, 2026
bb781b2
Drawer: the panel tabs are a tab list
claude Oct 12, 2026
a42eed2
Screen readers: announce milestones, not every update
claude Oct 12, 2026
7bcc0dd
Appearance spec: find palette entries as the combobox's options
claude Oct 12, 2026
5743442
Changelog: 2.2.0 (unreleased)
claude Oct 12, 2026
4af4726
Tests: follow the theme default and the drawer's tab list
claude Oct 12, 2026
e6f862a
Workspace spec: let a smooth scroll land before scrolling back up
claude Oct 12, 2026
20a3ddb
Endpoints: list only the models added in Settings
claude Oct 12, 2026
e11dfcb
Control: count a socket dated ahead of the clock as age zero
claude Oct 12, 2026
7bb969f
Spending: Stop at a limit always ends the task as cancelled
claude Oct 12, 2026
79927ff
Subagents: check saved settings by name, type and range
claude Oct 12, 2026
524834d
Control tests: wait until dropped listeners really refuse
claude Oct 12, 2026
c031fa8
Settings › Agent: show and save the profile's values, note the project's
claude Oct 12, 2026
c4b1d30
Text size: a browser using remote access keeps its own
claude Oct 12, 2026
26d6b74
Worktrees: wait briefly for a lock held only by a forking process
claude Oct 12, 2026
4865776
Process test: wait until the detached child has left the group
claude Oct 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,96 @@
# Changelog

## 2.2.0 (unreleased)

- **Your own model servers.** **Settings › Accounts › Your own endpoints**
connects Ollama, LM Studio, a llama.cpp or vLLM server, xAI, OpenAI or any
OpenAI-compatible API. **Find models** lists what the server offers; the
models you tick appear in the picker (servers on this computer under *On
this computer*, others under *API keys*, off in offline mode) and run with
ShadowCode's own agent. **Test** and **Remove** sit beside each one. A typed
key is saved on this computer only when you add models, under a name chosen
for the service (`api.deepseek.com` uses `DEEPSEEK_API_KEY`), so two services
never overwrite each other's key; a remote device never makes the desktop
send a saved key to an address it typed. New routes `POST
/api/providers/models`, `POST /api/models/endpoints` and `POST
/api/models/remove`; `GET /api/picker` lists these rows with `custom: true`.
- **Settings › Agent.** Steps per task, the token budget and autonomy profile,
tool time limit and output size, history shortening and summaries, provider
retries, fix attempts, the stuck check, subagent limits, checkpoints and the
commands that count as checks can be changed without editing `config.yaml`.
Each setting shows what it does, its range and default, and has a Reset;
out-of-range values are caught before saving and a refused save shows the
engine's reason. Only the settings you changed are saved, so a project's own
agent settings are never copied into your profile.
- **Find in conversation.** `Ctrl+F` (or *Find in conversation* in the command
palette) searches the open conversation's prompts, answers and notes, older
saved messages included, and shows *3 of 12*. `Enter` / `Shift+Enter` and
`F3` / `Shift+F3` step through matches, opening older messages as needed;
matches on screen are highlighted (the row is outlined where the webview
cannot highlight text). A file editor and the terminal keep their own
`Ctrl+F`.
- **Search finds words in answers.** Task search in the sidebar and the Tasks
tab also matches the agent's answers, and a result whose title doesn't show
the words shows the message or answer that matched, with the words marked.
`GET /api/sessions?q=` rows carry `match: {field, snippet, start, end}`.
- **A smarter command palette.** `Ctrl+K` matches letters in order, so *rev ch*
finds **Review changes**; word starts rank first and matched letters are
bold. Typing also lists matching **Conversations** and **Projects** to open,
and with nothing typed **Recent** shows the last five commands you ran.
Screen readers follow the highlighted entry; `Page Up`/`Page Down` and
`Home`/`End` move through the list.
- **Text size.** `Ctrl+=` (or `Ctrl++`), `Ctrl+-` and `Ctrl+0` zoom the whole
window from 80% to 175%, with the new size shown briefly; it is saved as
`ui.zoom` and the window reopens at that size. The desktop zooms its webview;
in a browser over remote access the terminal and editor still put the
pointer on the right text. A phone or browser using remote access keeps its
own size in its own storage, starting at 100%, and never changes the
desktop's. **Settings › Appearance** gains a Text size
control, and theme and text size now change as you pick them: leaving
without saving puts back what you had. The palette offers *Theme: same as the
system*, *light* and *dark* (the old toggle could never get back to System)
and *Zoom in*, *Zoom out* and *Reset zoom*.
- **Message times.** Prompts and answers show when they were sent, quietly at
their edge ("14:32", "Yesterday 14:32", or the date), with the full date and
time on hover; a quick reply keeps its time hidden until hovered. Times come
from the conversation's record.
- **All of a tool's output.** Long output in the activity timeline and in hook
cards offers **Show all (N lines)** / **Show less** and **Copy**, which copies
all of it; very long output shows its last 2,000 lines and says so.
- **Composer `/` menu.** `Enter` picks the highlighted command, as `Tab` and
the `@` menu do, and the next `Enter` sends it. Commands whose name or
description contains what you typed follow the name matches.
- **Notifications wait while you read them.** A notification stays while the
pointer is on it or focus is inside it, then finishes its remaining time.
- **Screen readers hear milestones, not every update.** Only the status bar's
state is announced, and the conversation is a named log that isn't read out
while it streams. One announcement says when an answer finished, a task
failed or stopped, or an action needs approval; approval cards are named for
their action.
- The page title names the open conversation, with a dot while a task runs,
so a browser tab on a paired device says what it shows.
- Deleting an automation asks in the app's own dialog. The drawer's tabs are a
tab list (arrow keys, Home/End). *Latest activity* jumps instead of gliding
when the system asks for reduced motion.
- **The theme follows the desktop by default.** `ui.theme` now defaults to
`system` in the engine and `config.example.yaml`, as the window and
onboarding already assumed; a profile that never chose a theme no longer
opens light on a dark desktop. Saved choices are unchanged.
- `docs/NATIVE_ROUTING.md` no longer points at a *Custom model…* control that
does not exist.
- **Subagent settings are checked when saved.** A `subagents` value that is
misspelled, the wrong type or out of range (for example `max_parallel: 20`)
is refused with the allowed range, instead of being clamped or ignored
without a word. A `config.yaml` saved earlier still loads as before.
- **Settings › Agent shows what it saves.** It showed the open project's own
`agent` values (from `.shadow/config/config.yaml`) as if they were the
profile's, while saving to the profile. It now shows and compares against
the profile's values and notes under a setting when the project uses
another, for example "This project uses 200.". `GET`/`PUT /api/config`
return `agent_profile` and `agent_project` for this.
- **Stop at a spending limit** always ends the task as *cancelled*; it could
end as *failed* when the answer reached the task before the engine did.

## 2.1.0: ARM64, a signed APT repository and a safer installer

- **Signed APT repository.** Each release's authenticated `.deb` is indexed in
Expand Down
6 changes: 5 additions & 1 deletion config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ sandbox:
- .deno

# Project checkpoints around shell commands and subscription turns (Rewind).
# Settings › Agent edits these.
checkpoints:
shell: true
vendor: true
Expand All @@ -82,6 +83,7 @@ spending:
logging:
level: info

# ShadowCode's own agent (Settings › Agent edits these).
agent:
max_steps: 64 # model turns per task, 1-1000
tool_timeout_sec: 60 # 1-3600
Expand Down Expand Up @@ -157,6 +159,7 @@ hooks:
approved: [] # {workspace, path, hash}

# Exact checks a task may run to verify its work (shell policy still applies).
# Settings › Agent › Checks edits the list.
verification:
commands: []

Expand All @@ -167,7 +170,7 @@ guardian:
allow_prepare_patch: false

ui:
theme: light # system | light | dark
theme: system # system (follow the desktop) | light | dark
notify: true # desktop notifications at all
notify_approval: true # a task asks for approval
notify_failed: true # a task failed
Expand All @@ -176,6 +179,7 @@ ui:
notify_sound: false # ask the notification server to play its sound

# Subagents: children a task starts with `spawn_agent` (native loop only).
# Settings › Agent edits these.
subagents:
enabled: true
max_parallel: 4 # children of one task running at once, 1-8
Expand Down
49 changes: 47 additions & 2 deletions docs/API_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,10 @@ need a Unix build. ShadowCode 1.x ships for Linux, where all of them exist.
| `POST` | `/api/mcp/servers/delete` | stable | allowed | [Extensions](#extensions) |
| `POST` | `/api/memory` | stable | allowed | [Slash commands and memory](#slash-commands-and-memory) |
| `GET` | `/api/models` | stable | allowed | [Accounts and models](#accounts-and-models) |
| `POST` | `/api/models/endpoints` | stable | allowed | [Accounts and models](#accounts-and-models) |
| `GET` | `/api/models/outcomes` | experimental | allowed | [Accounts and models](#accounts-and-models) |
| `POST` | `/api/models/register` | stable | allowed | [Accounts and models](#accounts-and-models) |
| `POST` | `/api/models/remove` | stable | allowed | [Accounts and models](#accounts-and-models) |
| `POST` | `/api/models/select` | stable | allowed | [Accounts and models](#accounts-and-models) |
| `POST` | `/api/models/test` | stable | allowed | [Accounts and models](#accounts-and-models) |
| `GET` | `/api/onboarding` | stable | allowed | [Settings and health](#settings-and-health) |
Expand All @@ -493,6 +495,7 @@ need a Unix build. ShadowCode 1.x ships for Linux, where all of them exist.
| `POST` | `/api/projects/trust` | stable | allowed | [Conversations](#conversations) |
| `GET` | `/api/providers` | experimental | allowed | [Accounts and models](#accounts-and-models) |
| `GET` | `/api/providers/detect` | experimental | allowed | [Accounts and models](#accounts-and-models) |
| `POST` | `/api/providers/models` | stable | allowed | [Accounts and models](#accounts-and-models) |
| `GET` | `/api/remote` | stable | refused | [Remote access](#remote-access) |
| `PUT` | `/api/remote` | stable | refused | [Remote access](#remote-access) |
| `POST` | `/api/remote/devices/revoke` | stable | refused | [Remote access](#remote-access) |
Expand Down Expand Up @@ -644,7 +647,12 @@ SessionRow = Session + {compare_id, compare_lane, worktree_task, worktree_source

- `GET /api/sessions?q=&workspace=&limit=&include_compare=&include_subagents=`
→ `{sessions: SessionRow[]}`, newest `updated_at` first. `q` matches the
title, folder or any task prompt. `limit` default 100, max 10 000.
title, folder, any task prompt or any task's answer (`summary`), ignoring
the case of ASCII letters. With `q`, a row whose title does not contain it
carries `match: {field: "prompt"|"answer", snippet, start, end}`: an
excerpt (about 40 characters before and 80 after, on one line, `…` where
cut) of the newest prompt or answer that matched, with the match at
character offsets `start`–`end`. `limit` default 100, max 10 000.
Compare lanes and subagent conversations are hidden unless
`include_compare=true|1` / `include_subagents=true|1`. `workspace=<project>`
also lists conversations running in that project's worktree tasks.
Expand Down Expand Up @@ -1907,7 +1915,10 @@ See [SUBAGENTS.md](SUBAGENTS.md).
- `GET /api/config` → the effective configuration for the selected project
plus derived, read-only fields:
`permissions.vendor_notes: {native, codex, claude, cursor, grok, antigravity, network}`
and `network.offline`. Sections: `model`, `permissions`, `agent`, `ui`,
and `network.offline`, plus `agent_profile` (the profile's own `agent`
values, which `PUT` saves) and `agent_project` (the `agent` keys a trusted
project's `.shadow/config/config.yaml` changes, with the values it uses;
`{}` when none). `PUT` returns both too. Sections: `model`, `permissions`, `agent`, `ui`,
`onboarding`, `routing`, `mcp`, `hooks`, `verification`,
`trusted_workspaces`, `guardian`, `cli_agents`, `local_engine`, `network`,
`sandbox`, `checkpoints`, `limits`, `updates`, and, when saved, `code_intel`
Expand Down Expand Up @@ -2309,6 +2320,40 @@ Row = {id: "cli:<vendor>"|"openrouter"|"local", kind: "subscription"|"api_key"|"
`running` from detection. `GET /api/providers/detect?refresh=1`
(experimental) → `{providers}`: local servers found (cached 30 s).

### Your own endpoints

OpenAI-compatible servers the user adds (Ollama, LM Studio, a llama.cpp or
vLLM server, xAI, OpenAI or any compatible API). `provider` is one of
`ollama`, `local` (LM Studio), `llamacpp`, `vllm`, `grok` or
`openai_compatible`. An `endpoint` given as a bare host gains `/v1`; one
with a user name, password, query or fragment is refused. `api_key_env`
defaults to the preset's key name, and for `openai_compatible` on a named
host to `<DOMAIN>_API_KEY` (`api.deepseek.com` → `DEEPSEEK_API_KEY`;
`api.openai.com` → `OPENAI_API_KEY`).

- `POST /api/providers/models {provider, endpoint?, api_key?, api_key_env?}`
→ `{ok, provider, label, endpoint, api_key_env, key_saved, local, models:
[{id, name, size_bytes, context_limit, capabilities, detail}]}`: the models
the server lists (`/api/tags` for Ollama, otherwise `GET {endpoint}/models`;
10 s, at most 2 MB). Nothing is saved. The typed `api_key` is sent as a
bearer token; without one the saved key is used, except from a remote
device. Offline, only servers on this computer are reached.
- `POST /api/models/endpoints {provider, endpoint?, models: [name], api_key?,
api_key_env?, context_limit?, vision?}` → `{ok, models: [ModelConfig]}`:
registers each model (id `model:<hash>` of provider, endpoint and name) and
saves a typed `api_key` under `api_key_env`. At most 100 models per call.
- `POST /api/models/remove {id}` → `{ok, removed}`: removes a model added for
an endpoint (and older registry rows for the same server and model). The
`model:` default in `config.yaml` and other registry rows are refused.
Saved keys are kept.

`GET /api/picker` lists these models with `custom: true` and their
`endpoint`. A model on this computer (loopback) is a `local` row named
`<model> · This computer`; any other is an `api` row (`inference: "cloud"`,
billed by the provider, `unavailable` offline). A row whose key was saved
and is now missing is `sign_in`. Tasks on them run ShadowCode's own agent
loop.

### Local models

`GET /api/local-models` → `LocalCatalog`:
Expand Down
4 changes: 2 additions & 2 deletions docs/NATIVE_ROUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ private secret storage; they are not included in task routing events.

Use registry IDs for API configuration. A bare model name is accepted only when
it resolves unambiguously, unless it is already an exact saved ID. Add a model
through **Custom model…** or configure it in Settings before assigning it to a
route. The native service also supports `POST /api/models/register` through IPC
through **Settings › Accounts › Your own endpoints** (any OpenAI-compatible
server) or `shadowcode models --use` before assigning it to a route. The native service also supports `POST /api/models/register` through IPC
for integrations that need to register a model without changing the default.

## Changes and failures
Expand Down
6 changes: 4 additions & 2 deletions docs/SUBAGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ conversation. It can start one, or several at once with `tasks`.
vendor CLI subagent's process.
- **Limits.** At most 4 subagents run at the same time, and one task can start
16 in total. Subagents cannot start their own subagents. A subagent stops
after its step limit (the definition's `max_turns`, or 24).
after its step limit (the definition's `max_turns`, or 24). These are the
defaults; change them in **Settings › Agent › Subagents**.
- **Models.** A subagent uses, in order: the model the call names, its
definition's `model`, its role's model (`explore`, `plan`, `review`, and
write agents as `implement`), or the parent task's model. Any picker id
Expand Down Expand Up @@ -279,7 +280,8 @@ the agent; you can still run it with `/skill <name>`.

## Settings

`config.yaml`:
**Settings › Agent › Subagents** changes the `subagents` group, with each
setting's range and default. By hand, in `config.yaml`:

```yaml
subagents:
Expand Down
6 changes: 3 additions & 3 deletions docs/SUBSCRIPTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,9 @@ never uses it once a turn has started or when images are attached.
changed although the agent didn't report editing them (a command it ran,
or another program). Changes you make outside ShadowCode's editor during
the turn can't be told apart from the agent's and are rewound with them.
The vendor's own conversation isn't told about a rewind. Set
`checkpoints.vendor: false` to turn this off (the editor then waits for
the turn to end).
The vendor's own conversation isn't told about a rewind. To turn this off,
clear **Before subscription turns** in **Settings › Agent › Checkpoints**
(`checkpoints.vendor: false`); the editor then waits for the turn to end.
- **Usage.** Tokens and cost the CLI reports are counted once per model call,
and also when the turn fails, hits the plan limit or is stopped.
- **Switching providers.** A move between providers hands over at most 12,000
Expand Down
Loading
Loading