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
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>
The collapsed card read just "Sub-agent:" — zero information, and the
expanded view showed the task twice (the task chip plus the child
transcript's opening user message, which IS the task).
The title is now "<Humanized name>: <task>" ("London weather: Find the
current weather…"), truncated by the header with the full text on
hover; "Agent" when the model gave no name. The redundant task chip is
gone — the child transcript's first message carries the task.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The spawn-agent tool shipped with mechanics but no judgment layer —
the only guidance was the tool description. This adds the ambient
decision heuristics and closes a child-profile hole.
- Copilot system prompt gains a "Sub-Agents (parallel & heavy work)"
section (same strong/anti signal structure as Background Tasks):
the organizing principle is context hygiene — a sub-agent's reads
and fetches never enter the main conversation, only its distilled
answer. Research-shaped requests (catch-me-up, dig-into, meeting
prep) now route through sub-agents by default, with copilot as
synthesizer. Anti-signals: single quick lookups, journeys-as-answer,
anything needing mid-task user input, driving shared surfaces.
- Background-task agent gets a matching (shorter) note — children
have their own model-call budgets, so a bg-task covers more ground
per run by fanning out.
- The inline default child profile now also excludes app-navigation
and browser-control: both drive shared visible surfaces (the UI the
user is watching; the single embedded browser pane), which headless
or parallel children would corrupt, not just clutter. Still
available via explicit tools selection.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First real-world use showed the model calling spawn-agent with
{name, tools, task} and no instructions — a complete spec by any
reasonable reading — and burning a correction round-trip on the
"exactly one of agent_id or instructions" rejection (both children
then succeeded on retry).
Requiring instructions bought nothing: the task is the spec. Omitting
both agent_id and instructions now spawns a general-purpose worker
with a default headless prompt; only supplying BOTH remains an error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the agent-as-tool capability deferred in turn-runtime-design §29.2:
a `spawn-agent` builtin that runs a sub-agent in its own standalone
headless turn and returns its final answer to the parent. Multiple
spawn calls in one assistant batch run concurrently (previous commit).
- RequestedAgent is now a union: by-id (unchanged shape) | inline
{name, instructions, model?, tools?}. Inline definitions persist
verbatim in turn_created and resolve to the same immutable snapshot.
- Agent resolution splits by variant: DispatchingAgentResolver narrows
the union once; InlineAgentResolver materializes inline specs
(builtin catalog validation, headless default profile when tools are
omitted); RealAgentResolver keeps the by-id path byte-identical. The
builtin→ToolDescriptor conversion is extracted to a shared helper.
- The spawn handler (RealToolRegistry branch → runSpawnedAgent) runs
the child via HeadlessAgentRunner on the parent's model by default,
clamps the model-call budget at 20, cascades the parent's abort
signal, and records {kind:"subagent", childTurnId} as durable tool
progress — the only parent→child link; no parentTurnId is added to
the schema. Task-level failures return as conversational isError
results, never terminal.
- Depth is capped at 1: both resolvers strip spawn-agent from children
(inline always; by-id via the new subagent composition flag) and the
handler refuses child-shaped parents outright.
- Renderer: spawn-agent calls render as a SubAgentBlock — a collapsed
status card that expands to the child's live transcript
(CompactConversation over sessions:getTurn, polled at 1s while
running; standalone child turns don't reach the session bus).
- The BuiltinTools entry gives copilot (and other catalog-attached
agents) the tool automatically; its execute is the degraded legacy
path only, since the turn runtime intercepts builtin:spawn-agent.
Schema note: RequestedAgent widened under schemaVersion 1 (pre-release)
— requires wiping ~/.rowboat/storage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sync tools in one assistant batch now run via Promise.all instead of
sequentially — a tool pending on I/O no longer blocks its siblings.
Three coordinated changes keep the event-sourced runtime sound:
- executeAllowedTools is two-phase: invocation events are appended
serially in source order (durable before any side effect, deterministic
log prefix), then all sync executions run concurrently, each appending
progress/results as they land. Per-call error and cancel semantics are
unchanged (moved to executeSyncTool).
- append() commits through an internal queue: persist → reduce → stream
runs to completion per batch, so file order, in-memory order, and
stream order stay identical even while executions overlap. A failed
commit rejects only its caller; the chain survives for siblings.
- Abort-registry state is scoped per tool call (turnId:toolCallId) via a
wrapper, fixing two latent races: createForRun destroying a running
sibling's tracked processes, and cleanup tearing down the turn-wide
force-kill scope when the first tool finished.
Wire ordering is untouched: model requests already reference tool
results by the assistant message's source order, pinned by a new test.
No concurrency cap and no per-tool serialization by design; tools that
share state must tolerate racing (file edits already reject stale
writes via their search/replace precondition).
Spec §4.5/§10.5 updated. New runtime tests cover overlap (deadlock
unless concurrent), progress interleaving, sibling failure isolation,
mid-batch cancellation, and crash recovery with multiple open
invocations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Expand the curated toolkit whitelist from 26 to 67 entries, adding
well-known services that support Composio-managed OAuth2 (the only
auth scheme the app's connect flow supports): Zoom, Discord, ClickUp,
monday.com, Confluence, GitLab, Bitbucket, Supabase, Sentry, PagerDuty,
Stripe, Square, QuickBooks, Mailchimp, Google Ads/Analytics/Search
Console, Figma, Canva, YouTube, Instagram, Facebook, Box, SharePoint,
and more. Adds three new categories: design, marketing, finance.
Also fix two entries that were never connectable: 'microsoft_outlook'
and 'onedrive' do not exist in Composio's catalog (the API returns 404,
so initiateConnection always failed). The real slugs are 'outlook' and
'one_drive'.
Excluded per curation policy: Composio's own utility toolkits
(composio_search, codeinterpreter, browser_tool), no-auth toolkits
(Hacker News, OpenWeatherMap), API-key-only toolkits the app cannot
connect (Firecrawl, Tavily, Exa, PostHog, etc.), and niche/low-quality
entries.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a Caffeinate switch (Settings → Mobile channels) that uses
Electron's powerSaveBlocker ('prevent-app-suspension', equivalent to
caffeinate -i) to stop the machine from idle-sleeping so the
WhatsApp/Telegram bridge stays connected. While active, an amber
coffee icon in the titlebar shows the state and can be clicked to
turn it off; a power:caffeinateChanged push keeps the titlebar
indicator and the settings switch in sync.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Anthropic caching is opt-in, and nothing sent breakpoints: observed
sessions show 0% cache hits on Claude models (~1.27M of 1.36M sampled
input tokens billed at full rate) vs ~82% implicit hits on Gemini. Two
ephemeral breakpoints fix that: the system prompt (whose cache prefix
also covers the tool schemas — both immutable per turn by construction)
and the last message (Anthropic's incremental-conversation pattern).
Conservative simulation on the sampled traffic floors the saving at 44%
with no cross-turn reuse; realistic reuse lands 70-85%.
Applied in the model registry bridge just before streamText, gated by
provider flavor or model id (covers direct Anthropic, OpenRouter, and
the gateways — the installed OpenRouter provider reads the same
providerOptions.anthropic key). Transport-only: nothing is persisted,
message content is untouched, and non-Anthropic requests pass through
byte-identical. Verify with cachedInputTokens on model_call_completed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The resolver helper's inline policy type predated middlePaneContent and
no longer satisfied ElisionPolicy; use the exported type. Coerce message
content to string where the preview assertions call string methods.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- catalog is ranked by GitHub star count (per-repo lookup with a 10-min
cache; unauthenticated works, the publish token is used when present)
- star button on each catalog card stars/unstars the app's repo as the
signed-in user (public_repo scope covers it), optimistic with revert
and a sign-in hint when signed out
- the info panel now offers Delete for local apps (apps:delete already
existed and cleans up app-owned agents; only installed apps had a
removal button before)
The original suite covered the tool-result path well but left holes:
loadElisionPolicy had no tests at all (now: missing file, full config,
partial merge, unparseable JSON, and the all-or-nothing malformed-key
behavior — via an injectable config path so tests stay off the real
WorkDir), images/note policies had no idempotency or determinism tests
(the properties prefix caching depends on), the note floor boundary was
unpinned, resolveAgent delegation was unverified, and nothing exercised
a multi-turn reference chain or the per-resolve hot config reload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Elision reads config/context.json at compose time, so the composed
payload is no longer a pure function of the durable log — inspecting an
old turn after a config edit can show different prefix bytes than were
transmitted. Document the exception against §8.3 and make the inspect
CLI print the policy in effect so divergence is visible instead of
silent. The gold fix (recording the applied policy on the turn) is
noted for when exact-bytes replay becomes a hard requirement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TurnRepoContextResolver was still freely constructible, so a future
call site could silently lose elision. The factory now accepts an
injectable policy loader (also keeps tests off the machine's real
context.json), the raw class carries a do-not-construct note, and
factory-level tests pin both the decorator wiring and the default
policy behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real session data shows ~5k-char skill bodies are the most common
oversized historic result; the 10k default replayed them on every model
call for the life of the session. With the head preview in place the
model retains enough scent to re-load on demand, so the aggressive
default is the right trade. Still tunable via config/context.json.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bare placeholder tells the model only the tool name and size; the
first 400 characters tell it what the output actually was (a skill
guide reads very differently from a fetched page), which is what it
needs to judge whether re-running the tool is worth it. The preview is
capped at the threshold so small thresholds still shrink content, and
the transform stays a pure per-message function (byte-stable prefixes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every user message sent while a note is open carries a full snapshot of
that note in userMessageContext, so a long chat over one open note
resends N full copies on every model call. The elision decorator now
rewrites prior-turn user messages to keep the pane kind and path but
replace note content above a small floor with a placeholder pointing at
the still-readable file. The current message's snapshot is untouched,
so "summarize this" keeps working; the system prompt already tells the
model later middle-pane context overrides earlier. Config:
elideHistoricMiddlePaneContent, default on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Video-mode webcam and screen-share frames matter for the response they
were captured for; afterwards the assistant's own text carries the
takeaway and fresh frames arrive with every new call message, yet each
message's frames (~10k tokens) were resent on every subsequent model
call. The elision decorator now strips image parts from prior-turn user
messages, leaving a text part recording how many frames of each kind
were dropped. The current turn's just-captured frames are always sent
verbatim, and the policy generalizes the existing config
(elideHistoricImages, default on).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tool results from prior turns (skill loads, file reads, HTTP fetches)
dominate resent context and are rarely needed verbatim. A decorator over
the context resolver now replaces prior-turn tool results above a size
threshold with a short placeholder telling the model to re-run the tool
if it needs the output. The current turn's in-flight results are always
sent verbatim, the durable log is untouched, and elision is a pure
per-message function so resolved prefixes stay byte-stable for provider
prefix caching.
Policy lives in config/context.json (elideHistoricToolResults, default
on; elideHistoricToolResultsThresholdChars, default 10000). The inspect
CLI composes through the same decorated resolver so debug output still
matches transmitted bytes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Show a generic animated Working status whenever an active turn can be stopped, and reserve the Thinking shimmer for model reasoning_start/reasoning_end windows.
Keep human-wait state separate so permission and ask-human controls remain interactive, and add renderer state and component coverage for the new behavior.
Removes the placeholder comment added in #684 to trigger the
path-filtered 'test' check; doubles as the trigger for this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a harmless comment under apps/x/ so the path-filtered required
'test' check runs on this otherwise README-only PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>