Commit graph

2028 commits

Author SHA1 Message Date
Ramnique Singh
ac7679ec16 fix(x): fail-closed tool permissions with per-tool catalog declarations
The permission checker returned {required: false} for every non-builtin
toolId and for any builtin outside the executeCommand/file-tools switch,
so composio-execute-tool (email sends, GitHub/Jira writes), executeMcpTool,
and mcp:* attachments on user agents executed with no permission check —
even in manual mode — and never reached the auto-permission classifier.
The extension contract was inverted: a new side-effecting tool shipped
ungated unless someone remembered to extend a switch in another module.

Every builtin now declares its permission policy in the catalog itself
(required field, compile-enforced): "none", "prompt", "command-allowlist",
"file-boundary", "composio-execute", or "mcp-execute". The checker reads
the declaration and FAILS CLOSED: undeclared builtins, mcp:* attachments,
and unknown toolId families require permission. Composio and MCP requests
carry family-specific payloads (new ToolPermissionMetadata kinds, rendered
by the permission card; generic fallback for everything else). All
previously-ungated builtins keep today's behavior via explicit "none"
declarations, except addMcpServer which now prompts; a catalog test pins
the audited gated set so policy changes stay intentional.

Auto-permission flows (background tasks, live notes, channels) route the
newly gated calls through the existing classifier per the §9.3 matrix;
defer still denies without a human. No grant persistence yet — every
gated call prompts (or classifies) each time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 23:07:11 +05:30
Ramnique Singh
e22d3b4796 fix(x): make stopTurn reliable under concurrent advances on one turn
The sessions layer tracked live advances in a map keyed by bare turnId
with overwrite-on-set and unconditional delete-on-settle. A turn can
legally have several live advances at once — a running invocation plus
external-input invocations queued on the turn lock (e.g. a permission
response while an async result is pending). The overwrite made stopTurn
abort the wrong controller and await an outcome that couldn't settle;
the unconditional delete untracked a sibling advance, so a later
stopTurn missed the abort path, fell back to a cancel input, and could
reject with TurnInputError if the turn had completed meanwhile — a
user-visible stop failure. Durable state was never affected.

Track a set of advances per turn: each advance removes only its own
entry on settle, and stopTurn aborts every live controller and awaits
all outcomes. A cancel input that loses the race with a concurrent
settle now counts as a successful stop (the turn is already terminal)
instead of rejecting.

Adds four tests: concurrent invocations both aborted, an earlier settle
not untracking a later advance, the settle-race cancel treated as
success, and non-terminal rejections still surfacing. Updates
session-design.md §9.3 and §13.5 accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:46:06 +05:30
Ramnique Singh
284dc77f5e
Merge pull request #727 from rowboatlabs/fix/turn-commit-validation-gate
fix(x): validate turn event batches before they become durable
2026-07-10 22:39:31 +05:30
Ramnique Singh
21c93913f4 fix(x): validate turn event batches before they become durable
The commit ritual persisted first and reduced second, so an illegal
append (e.g. a misbehaving tool reporting progress after its terminal
result) became durable before the reducer rejected it — permanently
corrupting the turn file and, through context references, blocking
every later turn in the session. Reduce the batch against the in-memory
history first: illegal batches now reject in memory for their caller
only, the file stays legal, and a failed commit no longer poisons
this.events for subsequent appends in the same invocation.

Adds a test that fires a stashed reportProgress after the tool's result
is durable (while a sibling keeps the invocation alive) and asserts the
late append rejects, nothing illegal reaches the file/stream/bus, and
the turn completes and stays re-advanceable. Updates the design doc
(§4.5, §24) to the reduce → persist → stream order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:32:33 +05:30
Ramnique Singh
a1f03d0a69
Merge pull request #726 from rowboatlabs/runtime-refactor
fix(x): close out the runtime review — findings 4-10
2026-07-10 21:07:27 +05:30
PRAKHAR PANDEY
37c1847413
Merge pull request #724 from rowboatlabs/fix/ollama-mlx-bad-request
fix: surface Ollama's real error message instead of bare HTTP statusText
2026-07-10 20:32:01 +05:30
Ramnique Singh
d7c19a9a38 docs(x): fix pointers the reorg docs pass got wrong or missed
The Phase B docs pass introduced two errors in VIDEO_MODE.md's prompt
catalog (composer still at the pre-move agents/ path; frame-context
pointed at legacy/engine.ts when convertFromMessages lives in
runtime/assembly/message-encoding.ts) and missed ANALYTICS.md entirely
(copilot_chat emit point still cited agents/runtime.ts:1313, file_parse
still cited the deleted builtin-tools.ts monolith).
turn-runtime-design.md referenced the repo-root AGENTS.md, which is not
committed — the carve-out is now stated inline. Also drops a leftover
debug comment in skills/index.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:57:03 +05:30
Ramnique Singh
1f0cdbd366 refactor(x): single live homes for getErrorDetails and isPathInside
getErrorDetails was the last live import out of runtime/legacy/ that
isn't part of the code-mode / mini-apps carve-out — four knowledge
pipelines pulled it from legacy/utils.ts, blocking 'legacy deletes as
a unit'. Their agents run via the headless runner now, so the
RunFailedError unwrap branch was dead for them; the live version in
application/lib/errors.ts is the plain Error->message idiom, and the
legacy copy (with the RunFailedError coupling) is deleted as
consumerless.

isPathInside backs path-grant permission decisions, and byte-identical
copies lived in filesystem/files.ts, assembly/permission-metadata.ts,
and legacy/repo.ts — a place for a future divergence to become a
permission bypass in exactly one caller. files.ts now exports the one
live copy; permission-metadata imports it (it already imports files.ts,
so no new edge). legacy/repo.ts deliberately keeps its frozen private
copy: the quarantine must not import live modules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:54:43 +05:30
Ramnique Singh
7fdfa32c7e fix(x): copilot prompt's Composio section uses the shared connection check
getComposioToolsPrompt still called the raw composio client's
isConfigured; everything else (skill catalog availability, the other
prompt connection blocks) goes through assembly/connections.ts, whose
check wraps in try/catch -> false. On an errored config read the two
could diverge: the prompt advertises Composio tools while the catalog
hides the composio skill the model would need to use them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:51:23 +05:30
Ramnique Singh
3b6c30fac9 fix(x): blank tool paths error instead of resolving to the process cwd
tools/paths.ts' lenient expandHome passed blank input through, and
both call sites feed the result to path.resolve — so a model calling
code_agent_run with cwd: '' spawned the coding agent in the Electron
process cwd, and resolveCodeProject('') could register that cwd (a
real, existing directory) as a code project.

The 'lenient variant' turns out to be filesystem/files.ts'
expandHomePath minus exactly this guard, so this deletes tools/
paths.ts and exports the filesystem one instead of keeping a third
variant. A blank path now surfaces as a clear tool error ('Path is
required') the model can react to. Also drops the stray
resolveCodeProject comment the extraction left in paths.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:47:15 +05:30
Ramnique Singh
05545ba283 fix(x): gate session skill carry-forward on the skillCarryForward trait
withActiveSkills injected carried-forward skills into every non-inline
agent request, while the resolver only honors them for agents with the
skillCarryForward trait (real-agent-resolver). The mismatch persisted
an ever-growing activeSkills list into the requested composition of
every turn for agents that would never use it.

The gate now lives in withActiveSkills, mirroring the resolver. That
required a structural fix: sessions cannot import assembly/registry.js
(its agent builders transitively reach di/container, which imports
sessions — the module cycle broke SessionsImpl registration under
test). Traits move to assembly/traits.ts, a zero-import leaf, with
registry re-exporting them so existing assembly-level importers are
unchanged. Regression test pins the non-trait agent case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:44:56 +05:30
Ramnique Singh
b6c2905af7 fix(x): notify-user derives its use-case fallback from the turn record
The ALS-miss fallback still imported fetchRun from the legacy runs
store, but ctx.runId has been a turn id since the runs->turns
migration — the lookup always threw and fell through, so a
background-task notification delivered outside its starting ALS
context (crash recovery, resume paths) lost its background_task
gating and fired as a plain notification.

The durable equivalent lives on the turn itself: turn_created's
agent.resolved.agentId, and only the background-task runner starts
'background-task-agent' turns. Also removes one of the last live
imports into runtime/legacy/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:40:44 +05:30
Ramnique Singh
599b7d13fb
Merge pull request #720 from rowboatlabs/runtime-refactor
refactor(x): the runtime moves under one roof — core/src/runtime/
2026-07-10 16:36:04 +05:30
Ramnique Singh
6a8e67878d ci(x): typecheck test files — the gap vitest and the build tsconfigs both miss
Test files were typechecked by nothing: tsconfig.build.json excludes
them (correctly — they must not land in dist), and vitest transforms
with esbuild, which strips types without checking them. That gap is
where the review's two tsc breaks and the fixture drift accumulated.

Adds 'typecheck' scripts per package (dev tsconfigs, which include
src/**/*.test.ts; renderer reuses its existing tsc -b), a root
orchestrator mirroring the test script, and a CI step in the vitest
job. All three packages are currently clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:30:26 +05:30
Ramnique Singh
02912ba9ef fix(x): clear remaining tsc drift in runtime test fixtures
Same class as the two review findings — type errors only tsc sees
(tests run untyped under vitest; the build tsconfig excludes them):

- headless.test.ts read .agent.overrides without narrowing the
  RequestedAgent union; the assertions now compare the whole agent
  object, which is also stricter
- real-agent-resolver.test.ts's byte-identity fixture predates the
  videoMode/coachMode flags (explicit false = the schema defaults,
  so the composed bytes are unchanged)
- real-tool-registry.test.ts's permission asks predate isRead being
  required (options: [] was from the older ask shape)

npx tsc --noEmit -p tsconfig.json is now clean for the whole package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:30:26 +05:30
Ramnique Singh
05eb94cd3a fix(x): FakeTurnEventBus satisfies ITurnEventBus
The interface grew subscribe/subscribeAll when the IPC bridge moved
to resolving ITurnEventBus, but the runtime-test fake still only had
publish — a TS2739 invisible to vitest and the build tsconfig. The
fake now declares 'implements ITurnEventBus' so the next interface
change fails loudly here instead of silently drifting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:30:26 +05:30
Ramnique Singh
de84b2e9ce fix(x): real-permission-checker test types against permission-metadata, not the legacy engine
The Phase A extraction moved getToolPermissionMetadata to
assembly/permission-metadata.ts but this test's type-only import was
mechanically retargeted to legacy/engine.js during the tree move,
where the symbol is imported rather than exported — a TS2459 that
vitest and the build tsconfig never see, and one of the last
live->legacy edges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:30:26 +05:30
Ramnique Singh
24fb0d38e5 fix(x): skill catalog advertises the post-reorg path; legacy path stays an alias
The bundled-skill CATALOG_PREFIX still pointed at the deleted
src/application/assistant/skills tree, so the system prompt and the
loadSkill example advertised paths that no longer exist — while the
real post-reorg path (src/runtime/assembly/skills) was NOT a
resolvable alias, so a model referencing the actual source tree got
'Skill not found'.

The new path is now canonical in the catalog and tool description;
the old prefix is kept as a loadSkill alias because agent snapshots
baked before the reorg still carry it. Regression test pins both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:30:26 +05:30
Ramnique Singh
a3d2ddf34c docs(x): design docs and prompt catalogs follow the runtime/ move
Path pass over turn-runtime-design.md, session-design.md,
VIDEO_MODE.md, and LIVE_NOTE.md: every source pointer updated to the
runtime/ layout, the turn doc's suggested-module-layout section (§28)
refreshed to the real tree (it predated the event hub, bridges, and
composer), and the session doc's §14 layout fixed including its old
§11/§14 contradiction about where the headless runner lives.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:30:26 +05:30
Ramnique Singh
5f4788cf56 refactor(x): dissolve the tools support grab-bag into its owners
domains/support.ts bundled five unrelated concerns. Each moves to its
owner: the catalog schema → tools/types.ts (every domain's typing);
lenient ~-expansion → tools/paths.ts (documented as distinct from
filesystem's throwing variant); the bg-task input schemas and
resolveCodeProject → domains/background-tasks.ts; the code-run
coalescer → domains/code.ts (catalog re-export retargeted); parser
plumbing → domains/parsing.ts, now module-private.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:30:26 +05:30
Ramnique Singh
e808d6d4f0 refactor(x): the runtime moves under one roof — core/src/runtime/
Everything "model runtime" now lives under core/src/runtime/, whose
five children are the architecture: turns/ (the engine + bridges),
sessions/, assembly/ (what an agent is: registry, composer, workspace
context, message encoding, permission metadata, headless runners,
spawn-agent, copilot/ definition, capabilities/, skills/ — no longer
buried at application/assistant), tools/ (catalog + domains + exec
plumbing + descriptors, which leave turns/bridges and fix the upward
lib→bridges edge), and legacy/ (the quarantined runs engine, with
agents/runtime.ts finally honestly named engine.ts).

Pure moves via git mv (history follows) with mechanical import
rewrites; no durable schema, wire format, or behavior changes — the
golden prompt snapshots and the catalog key-order pin pass unchanged.
di/ stays top-level (whole-app composition root); application/ shrinks
to browser-control/browser-skills/notification and true cross-cutting
lib utils.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:30:26 +05:30
gagan
25a465880d
Merge pull request #723 from rowboatlabs/feature/apps-copilot-bridge
Apps: generic copilot bridge — answer from app data, surface the app
2026-07-10 15:39:47 +05:30
gagan
5ffe6c26a2
Merge pull request #725 from rowboatlabs/feat/chat-history-polish
Chat history redesign, session-store search fix, chat header cleanup
2026-07-10 15:32:09 +05:30
Gagan
269734a3d5 feat(x): chat history redesign, session-store search, header cleanup
- Chat history: shared tab design (heading, bg, centered column, card
  table), hover kebab + context menu with rename/delete/open-in-new-tab
- Search: chat search now targets the session store (titles from the
  sessions index, content grepped from turn logs) — results open again
  and new chats are searchable; segmented scope control, scope-aware
  placeholder and empty states, Tab flips scope, keyboard-hint footer
- Sidebar pins: stale ids of deleted chats no longer eat pin slots
- Chat header: session token-usage as a ghost bar-chart button with
  tooltip that opens stats directly; options menu (download log) moved
  into the shared ChatHeader so full-screen chats get it too; caffeinate
  indicator renders nothing while off (was a 32px hole)
- Turn usage dots: borderless, aligned with message text edge
- Workspace: breadcrumb baseline alignment
2026-07-10 15:16:28 +05:30
Gagan
5df26e6fca feat(apps): run an app's own agents when its data/config.json changes
Apps store user settings in data/config.json (e.g. pr-dashboard's
tracked repo) and rely on their bundled agents to turn config into
data. Without this, changing a setting meant staring at an empty app
until the next cron tick. The apps-server watcher now debounces
config.json writes and fires a one-shot manual run of the app's owned
tasks (app--<slug>--*). Generic for any app; dynamic import keeps the
server decoupled from the bg-task runner at load time.
2026-07-10 15:15:24 +05:30
Prakhar Pandey
6fc5d5ec11 fix: surface Ollama's real error message instead of bare HTTP statusText
Ollama returns errors as {"error":"<string>"}, but ollama-ai-provider-v2's
error parser expects OpenAI-style {"error":{"message":...}}. The schema
mismatch makes the AI SDK fall back to the HTTP statusText, so users only
ever saw "Bad Request"/"Not Found" while Ollama's actual reason (model not
found, no tool support, ...) was swallowed -- which is why reports like
#696 were impossible to debug.

makeOllamaThinkFetch now rewraps failed responses' plain-string error
bodies into the nested shape the provider parses, preserving status,
statusText and headers (content-length dropped so the runtime recomputes
it). Successful responses, already-nested errors and non-JSON bodies pass
through untouched. The wrapper's two fetch call sites were merged into one
so both the /api/chat path and the passthrough get the same error handling
without a duplicate-request hazard.

Refs #696

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 14:30:13 +05:30
Gagan
7d38dc84a2 feat(apps): copilot answers from installed apps and surfaces them
Generic app-copilot bridge (no app-specific wiring anywhere):

- app-read-data builtin: read (or list) an installed app's data/ files —
  the agent-maintained JSON the app renders. Same path confinement as
  app-set-data; 50k char cap with truncation flag; parsed JSON returned
  when possible. Attached via the app-navigation skill.
- read-view view=apps: installed apps with name/description/dataFiles/
  agentSlugs, navigating the user's screen to the Apps view (same
  show-while-telling contract as email/bg-tasks). Renderer routes the
  new view value.
- copilot instructions embed the installed-apps list (name +
  description per app) with routing guidance: prefer an app's fresh
  data over external calls, answer from it, open-app to surface it.
  The apps:list handler fingerprints the app set and invalidates the
  instructions cache when it changes (installs, deletes, copilot-
  created folders all flow through that poll).
- app-navigation skill: apps section + a worked example framing the
  pattern generically (match apps by their own descriptions, never a
  hardcoded topic map).

Verified against real data: app-read-data returns pr-dashboard's live
10-PR data.json, escape paths rejected, key-order golden test updated
and passing.
2026-07-10 14:23:03 +05:30
Ramnique Singh
214cae7190
Merge pull request #721 from rowboatlabs/subagent-effort
Add reasoning effort for spawned agents
2026-07-10 12:01:54 +05:30
Ramnique Singh
36ce35a058 Add reasoning effort for spawned agents 2026-07-10 11:59:11 +05:30
Ramnique Singh
7765bec96e
Merge pull request #719 from rowboatlabs/runtime-refactor
refactor(x): decouple the new runtime from the legacy engine (reorg phase A)
2026-07-10 11:43:35 +05:30
Ramnique Singh
fdce639839 refactor(x): decouple the new runtime from the legacy engine file
Findings from a fresh post-refactor review. The turn-runtime bridges
imported convertFromMessages and getToolPermissionMetadata from
agents/runtime.ts — the legacy engine file — making it undeletable;
both move to neutral modules (agents/message-encoding.ts,
agents/permission-metadata.ts) that both engines now share. The
abort registry, used by the live tool registry, moves out of the
legacy runs/ cluster into turns/. Dead surface goes: the zero-consumer
CopilotInstructions, skillCatalog, RunLogger, and MappedToolCall
exports are deleted, mapAgentTool/StreamStepMessageBuilder become
module-private, and the mid-file import scar from the composer
extraction is hoisted.

Three review fixes ride along: ITurnEventBus gains
subscribe/subscribeAll so consumers stop resolving the concrete hub;
skill carry-forward becomes its own registry trait instead of
overloading workspaceContext; and connection checks (slack/composio/
code-mode/google) collapse into one shared connections.ts consumed by
both skill availability and the copilot prompt blocks — plus stale
doc pointers (VIDEO_MODE.md to moved prompt text, CLAUDE.md's dangling
AGENTS.md reference).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:39:39 +05:30
Ramnique Singh
6375704d66
Merge pull request #717 from rowboatlabs/reasoning-effort
Per-turn reasoning effort control
2026-07-10 11:38:25 +05:30
Ramnique Singh
7ab7143372 fix(x): round-trip signed reasoning via message-level providerOptions
Thorough-effort Anthropic runs through the gateway failed on the tool
loop: Bedrock rejected the echoed thinking block ("Invalid `signature`
in `thinking` block"). OpenRouter streams reasoning_details as
per-delta FRAGMENTS on reasoning events — the part-level capture kept
only the last fragment (unsigned) — while the fully accumulated,
signed array rides the finish event's providerMetadata, and OpenRouter
gives message-level reasoning_details precedence on read-back.

The bridge now attaches finish-step providerMetadata to the assistant
message as message-level providerOptions (restoring parity with the
legacy StreamStepMessageBuilder, which always did this);
convertFromMessages already echoes it. The authoritative signed array
wins over the per-part fragments, which providers then ignore.

Also persist provider failure detail on turn_failed: errorMessage()
appends the API error's status code and responseBody (bounded to 2KB)
— "Failed after 3 attempts" alone was undebuggable from the turn log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:34:16 +05:30
Ramnique Singh
3b447d07b9 refactor(x): models.dev — single startup writer, cache-only consumers
Previously catalog readers (listOnboardingModels, getChatModelIds)
fetched models.dev inline on a 24h TTL, while the reasoning-capability
paths read the cache only — so a signed-in install could run forever
with no cache and the effort chip never appeared, and catalog calls
could stall on a slow third-party fetch.

Now main calls startModelsDevRefresh() once at boot: refresh if the
cache is missing or older than 24h, then every 24h while running, with
a 10s fetch timeout, errors logged and swallowed (existing cache stays
in use). Every consumer reads the on-disk cache only. Catalog-shaped
readers (models:list, gateway annotation) await the warm-up's first
attempt so a fresh install's first list sees the fetched data; the
turn-start capability gate never waits. A missing cache is an empty
catalog, not an error — chat is unaffected either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:38:33 +05:30
Ramnique Singh
bea16c6513
Merge pull request #718 from rowboatlabs/readme2
Update readme
2026-07-10 10:36:15 +05:30
Ramnique Singh
885d4bc5cf fix(x): join gateway model ids to models.dev across id dialects
The reasoning-capability join failed for most gateway models, hiding
the effort chip for everything except the Gemini default. Two id
dialects broke it: OpenRouter-style ids spell versions with dots
("anthropic/claude-opus-4.8") where models.dev uses dashes
("claude-opus-4-8"), and the rowboat gateway serves OpenAI models with
no vendor prefix at all ("gpt-5.4").

The lookup is now a pure index (buildReasoningIndex/lookupReasoningFlag,
unit-tested) that normalizes ids case-insensitively with dots folded to
dashes, keyed both vendor-qualified and bare; bare ids that clash across
vendors with different flags are dropped rather than guessed. Verified
against the live gateway list: all 11 models now resolve.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:20:56 +05:30
Arjun
ffb1a84870 fix brain image 2026-07-10 10:16:50 +05:30
Ramnique Singh
15d4a497f0 feat(x): reasoning-effort control in the chat composer
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>
2026-07-10 10:11:25 +05:30
Ramnique Singh
1ffe29db9d feat(x): thread the models.dev reasoning flag to the renderer
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>
2026-07-10 10:11:25 +05:30
Ramnique Singh
125d39ee0b feat(x): per-turn reasoning effort — core plumbing
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>
2026-07-10 10:11:25 +05:30
Ramnique Singh
091f3e3acd fix(x): preserve block-level provider metadata in the turns bridge
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>
2026-07-10 10:11:25 +05:30
Arjun
380449565e fix brain image 2026-07-10 10:09:47 +05:30
Arjun
9b0b2f8888 add gif 2026-07-10 10:04:37 +05:30
Ramnique Singh
7a0e729703
Merge pull request #716 from rowboatlabs/runtime-refactor
refactor(x): split the builtin-tool monolith into domain modules
2026-07-10 10:01:58 +05:30
Arjun
bbd12b9937 fix readme 2026-07-10 10:00:10 +05:30
Ramnique Singh
38e63b9609 refactor(x): split the builtin-tool monolith into domain modules
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>
2026-07-10 09:47:53 +05:30
Ramnique Singh
78c1309b6f
Merge pull request #715 from rowboatlabs/runtime-refactor
refactor(x): capability rules move from convention to compiler
2026-07-10 08:55:28 +05:30
Ramnique Singh
ca077faa95 refactor(x): shared lazyResolve for the no-static-DI-edge pattern
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>
2026-07-10 08:50:39 +05:30
Ramnique Singh
8dd5a2b49f refactor(x): skills declare their own availability; excludeIds dies
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>
2026-07-10 08:48:24 +05:30
Ramnique Singh
efeaae1e19 refactor(x): workspace-context loading gets one trait-gated chokepoint
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>
2026-07-10 08:45:58 +05:30