Adds a Brain chip next to the model picker (Auto / Fast / Balanced /
Thorough) in both the full-screen and side-pane composer. Selection is
per-tab, next-turn intent like the model picker (same ref pattern), but
unlike model it is never frozen on a run — it applies turn by turn,
riding sendConfig.reasoningEffort into turn_created.config.
The chip renders only when the effective model (run-frozen, picked, or
app default) is known-reasoning per the models:list capability flag,
and a stale selection resets when switching to a non-reasoning model.
Turns that ran at a non-auto effort show the level beside the existing
token-usage affordance, read retroactively from the persisted turn
config; reasoning tokens were already displayed there.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Widens the models.dev schema to keep the per-model `reasoning` boolean
and carries it through normalizeModels → ProviderSummary → models:list,
so the composer can gate the reasoning-effort control on actual model
capability. Gateway model lists (bare "vendor/model" ids from the
server) are annotated from the models.dev cache in one batched,
cache-only read; unknown models keep the flag absent, which the UI
treats as "hide the control".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a canonical reasoning-effort ladder (low|medium|high, absent =
auto/provider default) that travels: send config → turn_created.config →
every model call's persisted parameters (§8.3) → provider-specific
options at invoke time. Behavior-neutral: nothing sends an effort yet,
and auto produces byte-identical requests to today.
- shared: ReasoningEffort enum (single source, reused by models.json
provider config), TurnCreated.config.reasoningEffort (additive
optional on a non-strict object — old builds strip it, no
schemaVersion bump), sessions:sendMessage IPC schema.
- turns: CreateTurnInput/SendMessageConfig/HeadlessAgentOptions carry
the value; runModelStep stamps it on each call's parameters so every
step durably records what it ran with.
- bridge: maps canonical effort to provider options, transport-only
like prompt caching — OpenAI reasoningEffort, Anthropic thinking
budgets (raising maxOutputTokens to the budget floor, never lowering
an explicit value), Gemini thinkingLevel/thinkingBudget by
generation, OpenRouter/rowboat reasoning.effort. Capability-gated
via a cache-only models.dev lookup (never blocks a turn on the
network); unknown support fails closed on strict flavors, while
OpenRouter-shaped flavors map permissively since OpenRouter drops
the field for non-reasoning models. Explicit persisted
providerOptions win over the mapping. Ollama keeps its existing
provider-level think rewrite; openai-compatible endpoints get
nothing (no safe universal parameter).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bridge dropped providerMetadata from content-block stream events,
so reasoning parts persisted as bare {type, text}. Anthropic thinking
signatures (which arrive on a reasoning-delta with an empty text delta),
Gemini thoughtSignatures (on text-end / reasoning-end / tool-call), and
OpenAI encrypted reasoning were all lost — breaking multi-step tool
turns whenever extended thinking is enabled, since providers require
signed blocks to be echoed back verbatim within the tool loop.
Now every -start event opens a new part (distinct blocks keep distinct
signatures) and metadata from each event of a block is merged opaquely
onto that part's providerOptions, which convertFromMessages already
echoes verbatim. The bridge never interprets the contents; each
provider reads back only its own keys. Empty blocks with no metadata
are dropped, matching the previous lazy-creation behavior. finish-step
metadata stays top-level as before.
Schemas already allowed providerOptions on all part types, so persisted
files remain readable in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 2,091-line BuiltinTools object literal becomes 15 domain modules
(files, parsing, mcp, shell, code, browser, app, web, memory, composio,
models, live-note, background-tasks, notifications, agent-analysis)
plus a shared support module; the root builtin-tools.ts keeps the same
public path and exports and shrinks to loadSkill + spawn-agent (catalog
infrastructure) and the ordered merge.
Catalog key order is provider-payload bytes (tool declarations sit in
the cached prompt prefix), and the historical order interleaves domains
— so the spread order preserves it verbatim, with code/app/web each
contributing two fragments at their original positions. Entries were
moved by script, not retyped. Two proofs pin the order: a
HISTORICAL_KEY_ORDER test on the merged catalog, and a pre/post runtime
key dump compared during the split (byte-identical). Adding a tool now
means editing its domain module; adding a domain means one fragment in
the ordered merge list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four modules hand-rolled the same lazy container-import-and-resolve
dance (agent registry, spawn-agent, background-task runner, notifier),
each restating the rationale. di/lazy-resolve.ts holds the pattern and
the reasoning once; all four sites migrate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Catalog membership for connection-gated skills (composio-integration,
code-with-agents, slack) was decided by hand-rolled excludeIds forks
inside buildCopilotInstructions — a third place that had to know which
skill depends on which connection. Now each entry declares an
availability() check (repos resolved via the new lazyResolve helper, so
the skills module keeps no static DI edge; failures read as
unavailable, the historical default), buildAvailableSkillCatalog
evaluates them concurrently for the system prompt, and the excludeIds
mechanism is deleted. Availability gates catalog VISIBILITY only —
loadSkill still resolves explicitly-requested ids, exactly matching the
old behavior. Tests cover the pure filter and pin that gated ids remain
resolvable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The workspaceContext trait was consulted separately at every assembly
site before loading agent notes / work dir — a convention each caller
had to re-remember, where a forgotten check either leaks the user's
agent-memory into non-copilot prompts or silently omits it for the
copilot, and neither fails loudly. The loaders move out of the legacy
runtime file into agents/workspace-context.ts, and
loadWorkspaceContext() applies the trait gate INSIDE: both engines now
call it (the resolver keeps its loader test-seams by injecting them
through), so a future assembly site structurally cannot skip the gate.
Tests pin the gate: non-workspace agents get nulls even when the
loaders would yield values.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The app-toggled mode flags were hand-mirrored in three places (the
resolver's CompositionOverrides zod schema, CapabilityContext, and
ComposeSystemInstructionsInput) and copied field-by-field with '?? false'
defaults — so a mode missed at one site compiled clean and silently
never composed. Now capabilities/types.ts declares one zod ModeFlags
schema with defaults: the wire shape (all-optional, what turn files
carry), the resolver's concrete parse output, and the composition
context all derive from it; CompositionOverrides just extends it with
the resolver-only keys, the composer takes {instructions, notes,
workDir} & ModeFlags with a rest-spread (no copy list), and the legacy
call site passes explicit false for the modes it never composes.
Adding a mode = one schema key + one record; anything less is a
compile/parse error. New resolver test pins historical-composition
compatibility (sparse, null-heavy, unknown-key, and garbage
compositions all compose exactly as before). Golden snapshots
unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The design rule "app/always capabilities are never model-loadable" was
held only by two arrays happening to be separate. Now it is structural:
CapabilityDefinition is a union — ModelCapability (requires catalog
title/summary and lazy content, cannot carry a fragment) vs
EagerCapability (requires promptFragment, never enters the catalog) —
and DiskSkill is typed as ModelCapability, making the disk trust
boundary a compile-time fact instead of a comment. The kitchen is
fenced along with the menu: catalog building and skill-tool attachment
go through pure, fenced helpers (buildCatalogFromEntries /
toolNamesFromEntries), and the boundary test now feeds a real eager
capability through a mixed list and asserts both the catalog hides it
and the tool path refuses its tools. Golden snapshots unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The side-pane copilot renders messages through its own markup in
chat-sidebar.tsx, which never got the hover copy button added to the
full-screen chat renderer. Maximizing the pane stays on the same code
path, so it was missing there too.
- Hover kebab menu on sidebar chat rows with Pin / Rename / Delete
- Rename: inline edit persisted via the existing sessions:setTitle;
custom titles survive since auto-titling only fills empty titles
- Pin: up to 3 chats float to the top of the list (localStorage)
- Delete: confirm dialog, sessions:delete, closes any open tab
- Dialog shell: sectioned sidebar nav (Configure / App), larger header
without divider, more padding
- Connections & Mobile: real brand logos (Google, Fireflies, Slack,
WhatsApp, Telegram), no icon tiles, no separator lines
- Models: provider logos on the your-own-providers cards
- Code Mode: Anthropic/OpenAI marks for Claude Code and Codex
- Help: GitHub and Discord logos, tile-free rows
- Dialog overlay: frosted backdrop blur
- Chat empty state: smaller type scale in the side-pane copilot,
compact prop on connections card
- Background tasks: stop button sized to match the Updating pill,
wraps under it in narrow panes, aligned with the state toggle
- Sidebar: Code above Meetings
- Brain: page + graph background matches other tabs, darker light-mode
borders for Base rows and Files containers, bolder section labels
- Top-align icons with titles on two-line nav rows; consistent ink
color for Apps/Background agents/Workspaces (sublabels stay muted)
- Quick actions: labeled New chat button, secondary actions as
compact ghost icons
Findings from an independent review of the branch:
- loadAgent's plain-object lookup traversed Object.prototype, so a
user-defined agent named "constructor"/"toString" threw a TypeError
instead of falling through to the agents repo as it did before the
registry (Object.hasOwn now guards both lookups; regression test
pins the fallthrough without depending on the environment).
- agentFromRaw now uses the same parseFrontmatter helper FSAgentsRepo
uses, so builtin and user agents can never drift on one file format.
- The fused workspace-context capability splits into agent-notes and
work-directory records — the composer's own separator joining makes
the output byte-identical, so the fusion bought nothing.
- Mode records drop their write-only title/summary (optional on the
base record, still required on skills where the catalog renders
them), the composer body is reindented and iterates a hoisted
PROMPT_CAPABILITIES list, and the two single-consumer re-export
shims in the legacy runtime file are deleted (consumers retargeted).
Golden snapshots unchanged throughout: composed prompts remain
byte-identical. Deferred to the follow-up PR with availability wiring:
discriminated-union typing on activation + fencing resolveSkill, and
unifying the three mirrored composition shapes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The agent-notes and work-directory prompt blocks move out of the
composer into WORKSPACE_CONTEXT_CAPABILITY (activation: 'always'),
whose fragment is pure over the resolved inputs — the resolver still
loads notes/work-dir only for agents with the workspaceContext trait
and passes null otherwise, so trait gating is unchanged. The composer
now iterates one capability list (workspace + modes) in fixed order;
golden snapshots pass unchanged, byte-identical output. The composer's
own body is down to: base instructions + hidden-user-context + the
capability loop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The six mode blocks (voice input, video, coach, voice output
summary/full, search, code mode) move out of the composer's if-chain
into capability records (capabilities/modes.ts), each owning its
fragment text as a pure function of the composition context — the
code-mode fragment keeps its chip/cwd parameterization. The composer
now iterates MODE_CAPABILITIES in declared order, which is the fixed
total order that keeps composed prompts byte-stable; the 13 golden
snapshot tests pass unchanged, proving byte-identical output. Fragment
text was extracted from the if-chain programmatically (not retyped) so
the bytes could not drift. Adding a mode is now one record in one
file instead of a flag threaded through the resolver plus a concat
site in the composer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
capabilities/types.ts introduces the assembly unit: id/title/summary +
lazy guidance + owned tools (what skills already were), plus an
activation axis ('model' | 'app' | 'always') and an eager
promptFragment(ctx) for app/always entries — pure over a persisted
composition context so composed prompts stay byte-identical.
SkillDefinition is now CapabilityDefinition & {content}: bundled skills
are unchanged model-activated entries, the loadSkill catalog filters to
that subset, and disk skills remain structurally limited to it (eager
fragments and app activation are bundled-only powers — a disk file must
never gain a standing system-prompt injection channel).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
composeSystemInstructions (+ the hidden-user-context block) moves
verbatim to agents/compose-instructions.ts, owned by the assembly
layer; the legacy engine and old import paths keep working via
re-export. New golden-bytes snapshot tests pin the composed output for
a 12-case composition matrix plus the block ordering — the safety net
for folding the mode blocks into capability records: prefix caching
and snapshot inheritance require byte-identical prompts, so any
restructuring must keep these snapshots green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Built-in agent identity moves to agents/registry.ts: a table of
definitions with builders, an alias (rowboatx → copilot) expressed as
shared entries, and declared traits. One prompt-file loader replaces
the five copy-pasted frontmatter-parsing blocks, and the stringly
"copilot" || "rowboatx" comparisons in the resolver and the legacy
runtime become hasWorkspaceContext(agentId) trait lookups. loadAgent
keeps its signature and its legacy import path via re-export; the
user-agents repo fallback resolves the container lazily so the
registry adds no static edge into the DI graph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Chat empty state: icon-free heading, single suggestion list with
leading icons and hover arrows, aligned to the composer column
- User message bubbles: rounded-2xl with flattened top-right corner,
hover-only copy button under the bubble's bottom-right
- Sidebar: hairline gap between menu items so adjacent active/hover
highlights don't merge
Quoted-chain detection only matched Gmail/Apple markup (.gmail_quote,
.gmail_attr, blockquote[type=cite]). Outlook emits none of those, so
hasQuote stayed false, no "•••" toggle rendered, and the whole quoted
history was drawn inline — each message in a long thread re-rendering
every message before it.
Outlook desktop marks the boundary with a border-top:solid #E1E1E1 div
that holds only the From:/Sent:/Subject: header; the quoted body trails
it as siblings. Hiding just the matched element is therefore not enough,
so mark the divider and everything after it, climbing ancestors. Require
the header block before treating a border-top div as a boundary —
signatures and marketing footers draw the same rule, and wrongly hiding a
signature is worse than wrongly showing a quote. Measured against a
558-message local corpus: 7/7 real quotes caught, 0 false positives.
Also fixes a pre-existing bug: a forward is quoted top to bottom, so
hiding its quote left a blank body behind a toggle nobody would press.
When nothing visible remains, show everything and drop the toggle.
The plain-text path had the same Gmail-only hole, and the fix already
existed in the file — isReplyQuoteBoundary handled Outlook headers but
was wired only to the composer. It now backs display too.
Quote tagging moves into the srcDoc build, so quotes are hidden on first
paint and the measured iframe height is the collapsed height.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The follower's join protocol was already unit-tested, but the hooks
grew stateful behavior of their own: useTurn's reset-on-turnId-change
vs keep-while-disabled, the snapshotFailed signal and feed-event
recovery, and useAgentRunTranscript's legacy runs:fetch fallback and
loading/error derivation. Tests drive the real turn-feed singleton
through a stubbed window.ipc preload surface.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completes the turn-spine unification. Chat's SessionChatStore now
consumes turns:events like every other surface, joining live turns by
file offset (drop covered, append contiguous, refetch on gap) instead
of blind-appending session-bus events. Text/reasoning deltas cross IPC
only for turns a window subscribed to (turns:subscribe/unsubscribe, a
per-webContents registry that also survives window teardown), so
headless pipeline chatter never reaches windows that aren't watching.
The channels bridge settles turns off the turn event bus instead of
filtering the session broadcast. With no turn-event consumers left,
SessionsImpl stops forwarding entirely (it drains its execution streams
and keeps outcome/index handling) and sessions:events shrinks to
index-changed entries — one channel for turn events, one for session
metadata.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Background-task and live-note transcript views move from fetch-once to
the shared useAgentRunTranscript hook (useTurn over the turns:events
spine, with a one-shot legacy runs:fetch fallback for pre-migration run
ids), so an in-flight run's transcript now streams live. The headless
runner drains its execution stream — delivery rides the turn event bus,
and an unconsumed HotStream buffered every durable event until settle.
Deletes the caller-less runHeadlessTurn duplicate (HeadlessAgentRunner
is the implementation; session-design.md now says so) and the orphaned
web-search skill file that was never registered.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TurnRuntime now publishes every turn's events (durable ones tagged with
their 1-based file offset, deltas without) to an injected TurnEventHub,
regardless of who started the turn. Main forwards durable events to all
windows on one turns:events channel; the renderer's new useTurn(turnId)
hook joins live turns gap/duplicate-free via the offset protocol. The
sub-agent card drops its 1s polling for push updates, and the nine
per-channel window fan-out loops collapse into one broadcastToWindows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Capability guidance now co-locates with the tools that skills own:
- Register the previously orphaned slack skill in the catalog (excluded
when Slack isn't connected, like composio-integration) and shrink the
prompt's Slack block to a routing line + followed-channels hint — the
agent-slack command patterns already live in the skill body.
- Compress the per-capability routing directives (meeting-prep,
create-presentations, doc-collab, app-navigation, background-task,
apps, browser-control, notify-user) to when-to-load signals; the
how-to detail loads with each skill.
- Slim the knowledge-graph access examples to the essential patterns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Skills now own their tools. The copilot attaches only a small hardcoded
base set (~16 tools instead of all ~42); loading a skill via loadSkill
attaches its declared tools as native tool definitions from the very
next model step, and they stay attached for the rest of the session.
- tools_extended: new durable turn event (the one sanctioned exception
to per-turn tool immutability — explicit, replayable, never silent).
Reducer tracks extensions per model-call index; effectiveTools()
composes base + extensions for both the live loop and inspect.
- Runtime: sync tool results may carry metadata.toolAdditions; dedupe
and the durable append run atomically on the serialized commit chain
(safe under concurrent sync tools). Crash recovery rebuilds the
extended toolset from the log.
- Skills declare tools (SkillDefinition.tools; SKILL.md tools:/
allowed-tools frontmatter); catalog entries list them; loadSkill
returns them via a reserved $toolAdditions key the registry lifts
into result metadata. builtin-tools skill = escape hatch, derived as
"every non-base builtin" at module init.
- Sessions derive composition.activeSkills from the previous turn's
request + its tools_extended events; the resolver attaches active
skills' tools on top of the base set (stable order, so snapshot
inheritance keeps working).
- Legacy code-mode path keeps working on the base set (which includes
code_agent_run/launch-code-task) and strips $toolAdditions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>