Commit feb7ffb
chore(app-onboard): merge main into the epic branch (#2447)
* feat(mcp): upgrade both MCP servers to the 2026-07-28 revision (#2290)
* feat(server): migrate the MCP server to the v2 SDK with dual-era 2026-07-28 support (#2272)
* feat(server): migrate the MCP server to the v2 SDK with dual-era 2026-07-28 support
Replace @hono/mcp and the server-side @modelcontextprotocol/sdk 1.x with @modelcontextprotocol/server and @modelcontextprotocol/hono 2.0, serving both the legacy initialize handshake and the modern 2026-07-28 protocol per request via a stateless WebStandardStreamableHTTPServerTransport. v2 derives tools/list JSON schemas only from Zod v4, so the local browser and filesystem tool schemas move to the zod/v4 subpath of the installed zod 3.25; the Klavis connector schemas are bridged through fromJsonSchema until the Strata client migration. Drop the logging capability and the SetLevel handler, both removed in 2026-07-28. The Strata MCP client stays on the 1.x SDK for a follow-up change.
Refs #2168
* fix(ci): call the real tabs tool in the MCP integration test
The test invoked a non-existent tool name and passed only because the 1.x server returned a lenient error result for unknown tools. The v2 server correctly rejects unknown tools (its sibling test asserts the throw), so call the registered tabs tool with a valid action instead.
* refactor(server): migrate the Strata MCP client to the v2 client SDK (#2274)
Move the Klavis Strata MCP client and the remaining Klavis type imports from the 1.x @modelcontextprotocol/sdk to @modelcontextprotocol/client 2.0, so apps/server no longer depends on the legacy SDK at runtime. The SDK stays as a devDependency for the integration and end-to-end tests that simulate a legacy client against the v2 server. No behavior change; the connector schema bridge is unchanged and can be cleaned up in a follow-up.
Refs #2168
* chore(claw-server): upgrade rmcp to 3.x with a legacy protocol version pin (#2275)
* chore(claw-server): upgrade rmcp to 3.x with a legacy protocol version pin
Bump rmcp from 2.1 to 3.x. rmcp 3.0 changes the ServerHandler call_tool return type to the MRTR-aware CallToolResponse, so wrap the existing CallToolResult with .into() in the production and reference handlers and the serving test. rmcp 3.x advertises the modern 2026-07-28 protocol by default, but the claw-server's sessionless state model for that revision is not in place yet, so override supported_protocol_versions() on the production handler to pin to legacy versions (2025-11-25 and earlier) and keep this upgrade behavior-neutral. The pin is lifted together with the handle-based session model. All crates compile, cargo test passes, clippy and fmt clean.
Refs #2168
* test(claw-server): prove the legacy protocol pin rejects the modern revision
Add a serving-level test that initializes against a legacy-pinned handler with protocolVersion 2026-07-28 and asserts the server negotiates down to a legacy revision rather than agreeing to the modern one. This exercises the same supported_protocol_versions() override the claw-server uses and confirms rmcp consults it during initialize negotiation.
* feat(claw-server): serve the modern MCP revision with a session handle (#2278)
* feat(claw-server): serve the modern MCP revision with a session handle
Advertise the 2026-07-28 revision alongside the legacy revisions. rmcp serves 2026-07-28 clients statelessly, so the per-agent tab-group session, previously keyed on the mcp-session-id header that revision removes, is now carried as a server-minted handle passed as an ordinary `session` tool argument: minted as a UUID when absent, returned in structuredContent, and looked up on later calls so the agent keeps its tab group. Legacy clients and stdio keep the existing session model unchanged, and idle sweeping reaps handle sessions independent of transport close.
* test(claw-server): expect the injected session arg in the name_session golden schema
* fix(claw-server): only reuse live server-minted session handles
Resolve a modern session by reusing a handle only when it maps to a live session; any absent or unrecognized handle now mints a fresh server-generated handle instead of being minted under the caller-supplied value. A caller can no longer choose or seed a session id, so it cannot land on another agent's ownership by presenting a chosen id, and because every mint uses a fresh unique id, two concurrent calls can no longer both mint under the same id and orphan one session. Continuity flows through the returned handle.
* feat(server): give /mcp clients a server-minted session identity handle (#2289)
* feat(server): give /mcp clients a server-minted session identity handle
Expose an optional session string argument on each /mcp browser tool. The server mints a UUID when the caller omits it and returns it in the result's structuredContent so an agent can thread it back on later calls to identify its own session; a supplied handle is echoed. The handle is stripped before the tool runs, so tool logic (including the one strict-schema tool) and the internal agent's shared tool schemas stay unchanged. It is gated behind an opt-in registration flag so only the /mcp surface carries it, and it is attributed in the per-tool execution metric. Identity only: no isolation, no per-session state, no change to browser behavior.
* fix(server): attribute the /mcp session handle on the per-call log
The session handle was added to the aggregated `tool_executed` metric event, whose rollup keeps only tool name, source, and success, so the handle was discarded. Move the attribution to the per-call log context instead, which is the right home for a per-session value and avoids the unbounded cardinality of a UUID in an aggregated metric. The returned handle in structuredContent is unchanged.
* fix(server): serve the 2026-07-28 revision on /mcp and harden the endpoint (#2298)
* fix(server): deliver the /mcp tool abort signal from the request context
The browser tool wrapper read the abort signal from a top-level `extra.signal`, which does not exist on the v2 SDK's tool handler context, so it was always undefined and cancellation never reached the tool over /mcp. Read it from `extra.mcpReq.signal` where the SDK actually places it. The internal AI-SDK agent path threads its own signal and is unaffected. Adds a test that a pre-aborted signal delivered via mcpReq.signal makes a waiting tool abort.
* feat(server): reject browser-originated /mcp requests via Sec-Fetch-Site
The /mcp endpoint is intentionally reachable across the LAN, so a loopback bind is not an option. Browsers always send a Sec-Fetch-Site header while native MCP clients and the internal ACP client never do, so rejecting requests that carry it filters browser-originated attacks (DNS rebinding, CSRF) without restricting the bind address. Scoped to /mcp so browser-facing routes are unaffected, and mirrors the claw-server's request hygiene.
* fix(server): serve the modern 2026-07-28 revision on the TS /mcp endpoint
The TS agent server advertised only legacy protocol versions and rejected 2026-07-28: the McpServer was built without supportedProtocolVersions (so it never registered server/discover), and the route dispatched through the bare streamable-HTTP transport, which does not run the modern request machinery.
Advertise the modern revision alongside the legacy list on the McpServer, and split the route: legacy (2025-era) requests keep the existing hand-wired transport with enableJsonResponse so the internal ACP client still gets single-JSON responses, while modern requests go through createMcpHandler, which serves server/discover and the stateless 2026-07-28 dispatch. Both eras are exercised end to end by new tests: legacy initialize negotiates 2025-11-25 as JSON, modern server/discover advertises 2026-07-28, and a modern tool call succeeds.
* feat(mcp-manager): add read-only tool catalogue endpoint for settings UI (#2410)
The settings Available Tools list previously ran a full MCP handshake against /mcp from the browser. With the browser-request guard on /mcp, that path is no longer reachable from the extension.
Add a GET /mcp-manager/tools endpoint that returns the same tool set (browser tools plus Klavis connector tools) as name and description pairs, and point the settings section at it through a react-query hook mirroring the existing agents hook. The MCP hardening is left unchanged.
* fix(klavis): pass connector tool JSON schemas through unchanged (#2412)
* fix(klavis): pass connector tool JSON schemas through unchanged
Strata connector tools (Linear and others) advertised an empty input schema, so every parameterized call was rejected with both "required" and "additional properties" errors and the agent could never pass arguments.
Their remote JSON schema was routed through zod-from-json-schema and then zod-to-json-schema, which under zod v3 silently drops every property. Pass the remote JSON schema straight through instead: fromJsonSchema for the MCP server path and the ai jsonSchema() helper for the AI SDK tool set. The hand-built connector_mcp_servers schema is unchanged. Removes the now-unused per-session schema map.
* chore(server): drop unused zod-from-json-schema dependency
* fix(claw-app): drop the audit timeline risk labels and recolor the rows (#2416)
The audit timeline tagged act/evaluate/run/download rows as "High risk", which
is inaccurate, and highlighted them in warning-amber. Remove the label and switch
the row emphasis to the theme accent (blue); errors keep their red highlight. The
same tool set still auto-expands on load, now named for what it is (notable
actions) rather than risk.
* chore(release): update extension alpha feeds to 0.2.17.0
Automated release snapshot update.
* chore: bump browserclaw extension version to 0.2.17.0 (#2421)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(claw-server): install the neo skill under browseros-neo and migrate legacy dirs (#2428)
* fix(claw-server): install the neo skill under browseros-neo and migrate legacy dirs
The managed skill's SKILL.md declares name: browseros-neo but was installed into a
directory named browserclaw, so agents that require name == parent directory reject
it (e.g. Claude Code: name "browseros-neo" does not match parent directory
"browserclaw").
Install the skill under browseros-neo so the directory matches the frontmatter, and
add a one-time, idempotent migration that removes the legacy browserclaw directories
(only ones whose ownership marker proves BrowserOS installed them) across every
harness and purges their manifest records, so the reconcile replants them under the
new name. User-authored browserclaw directories are left untouched. The migration
runs on every managed-skill reconcile (startup and connect), so existing users heal
without any action. Marker internals (managed_by) stay stable for recognition.
* fix(harness): keep legacy skill records for directories that remain
Purge a legacy manifest record only once its directory is actually gone. If removal
or marker inspection fails the directory stays on disk, so keeping its record leaves
the manifest consistent and lets the next reconcile retry, instead of orphaning it.
* fix(chat): surface the full error in a copyable block (#2434)
* fix(chat): surface the full scrubbed upstream error in a copyable block
When a turn fails, the classifier only forwarded the short constructed message
into the error envelope's details and capped it at 500 characters, so the
gateway's real reason (OpenRouter-style gateways hide it in the response body /
metadata.raw) never reached the user. Errors the AI SDK could not classify
carried no details at all.
Forward the full upstream response body (or structured data.raw), pretty-printed
as JSON, into details on every path, redacted and bounded at a larger cap so the
whole body survives to be copied. The card now renders that detail always
expanded under a Full error heading with a copy control that copies the entire
error even when the block clips it on screen, while the classified title and
message stay on top. The block is omitted when the detail only repeats the
message, so a clean one-line error is not shown twice.
* fix(chat): broaden secret scrubbing for the full upstream error body
Forwarding the whole response body means more credential shapes can appear than
the four key patterns caught. Redact value-shaped secrets on the raw text so it
works whether or not the body parses as an object: JWTs, PEM private keys,
Google/Slack/GitLab/GitHub tokens, URL userinfo credentials (host kept), and
sensitive JSON string values matched by key name. Skewed toward over-redaction
since a missed token is copied into a bug report.
* feat(agents): custom ACP agent support (#2433)
* feat(server): add custom ACP agent data model + migration
Widen the agent type enum to include 'custom', add a nullable custom_config
JSON column to acp_agents (named migration 0007) plus the bootstrap DDL and
migration-history mirror, and round-trip a validated CustomAcpAgentConfig
through the agent definition and store (with a new update method).
* feat(server): launch custom ACP agents from a stored command
Add a quote-aware command splitter and a launcher branch that runs a custom
agent's full command line as given (no bundled-bun wrapping). Make the agent
policy data-driven for custom agents: a per-agent registry id, stored env,
system-prompt append, and full-access modes. Skip the full-access mode step
when none are configured, and read the reasoning-effort key from config.
* feat(server): route custom agent chats through the ACP path
Include 'custom' in the conversation target-type enum and dispatch custom
chat requests to processAcpMessage alongside claude and codex.
* feat(server): validate custom agent config and add an update endpoint
Extend POST /agents to accept and cross-validate customConfig (required for
custom agents, rejected for built-ins), and add PUT /agents/:agentId so a
custom agent's command can be edited. Refresh running sessions on update so
the next turn re-spawns with the new command.
* feat(server): probe a custom agent by explicit command
Accept an optional command/env/cwd on POST /acpx/probe (command required for
type 'custom') so the settings UI can validate a not-yet-saved agent and
enumerate its models before the user commits.
* test(server): cover custom ACP agent config, launch, routes, and probe
Add a command-splitter suite and custom-agent cases across the store (config
round-trip + update through the real migration), agent routes (create
cross-validation + PUT), launcher (run-as-given argv, env injection, no
bundled-bun wrapping), policy (per-agent registry id, stored env/prompt/modes,
empty full-access), and probe (command required, forwarded).
* feat(app): custom agent data layer (config type, update + probe hooks)
Add customConfig to the app AcpAgent type, extend the create payload, add
useUpdateAcpAgent (PUT), and a useProbeCustomAgent mutation that probes a
not-yet-saved custom command for the Test-connection flow.
* feat(app): custom ACP agent dialog with Test connection + popular agents
Add the add/edit custom-agent dialog (name, full command line, working dir,
Test-connection probe, advanced env/modes/prompt/icon), a nested Popular ACP
agents helper backed by a verified registry (opencode, hermes, openclaw, pi;
antigravity as docs-only), and the dashed 'Custom ACP agent' template tile.
* feat(app): surface custom agents in settings and chat
Wire the custom-agent dialog into the AI settings pane (template tile + edit
from the agents list), render a custom agent's stored icon and command in its
card, and give custom agents a distinct icon/label fallback in the adapter
helpers and the chat provider selector.
* test(app): cover custom agent form helpers and card rendering
Add unit tests for the env/CSV parsing and config builder, and card cases for
the custom-agent command, icon, and edit button.
* test(server): use a neutral path in the command-splitter fixture
* fix(ci): expect the custom-agent migration in the bootstrap schema test
* fix(server): keep Windows backslash paths intact when splitting commands
Treat backslashes as literal in the custom-agent command splitter so Windows
paths like C:\Users\me\agent.exe survive; use quotes for arguments with
spaces. Previously every backslash was consumed as an escape, launching a
nonexistent executable path on Windows.
* feat(app): real brand logos and code-block commands in popular agents
Render svgl brand marks for opencode (currentColor), OpenClaw, and Antigravity
in neutral tiles, with monograms for pi and Hermes (no svgl logo). Show each
suggested command as a proper code chip so it reads as code, not description.
* fix(server): resolve custom agents against the login-shell PATH
A GUI-launched server inherits a minimal PATH that omits shell-profile
additions (homebrew, nvm/fnm, ~/.local/bin), so a custom agent's binary could
be unfindable even when it works in a terminal. Ask the user's login shell for
its PATH once (cached, timeout-guarded) and prepend it for custom command
spawns. Windows inherits the full registry PATH already, so it is unaffected.
* feat(app): real brand logos across built-in and custom agents
Consolidate agent brand marks (Claude, Codex, opencode, OpenClaw, Antigravity,
pi) into a shared module reusing the project's svg components, and use them in
the provider templates, agent cards, and the Popular-agents picker. Picking a
popular agent now carries its logo onto the saved agent. Removed the manual
Icon text field. Hermes keeps a monogram until its logo asset is added.
* feat(app): agent brand logos in the chat selector, Hermes icon, opencode color
Render brand marks in the newtab + sidepanel provider selector by threading a
brand key through the chat targets. Add the Hermes icon (object-contain, from
the provided asset) and give opencode its own dark/white ink instead of the
tile accent color.
* fix(app): brand logos in the selected-agent chip, input avatar, and header
The newtab search-bar pill and input avatar and the sidepanel header rendered a
generic Bot for the selected agent. Resolve the agent's brand mark from its
brand key in all three, falling back to Bot when none.
* fix(app): make the sidepanel agent selector look clickable
The header agent selector read as a static title once it showed a brand logo.
Add a border, an active (foreground) name, and a rotating chevron caret so it
reads as a dropdown, matching the newtab pill.
* fix(mcp): reject unknown arguments on every browser tool (#2432)
* fix(mcp): reject unknown arguments on every browser tool
Only tabs, windows, and history rejected unknown fields. The other 14
tools dropped them during deserialization and ran with defaults, so a
misspelled argument produced a successful but quietly wrong result:
screenshot {page: 1, fullpage: true} -> viewport shot, no error
grep {page, pattern, maxResults: 5} -> default 50 matches, no error
This is the failure mode behind #2161, and the only reason that one
surfaced is that tabs happens to be strict, so the retired hidden input
raised "unknown field" instead of being ignored.
Add deny_unknown_fields to the 14 permissive Rust arg structs and
.strict() to the matching zod schemas, which also closes a parity gap
where tabs and windows were strict in Rust but permissive in TypeScript.
The schema pipeline already handles this: normalize_schema_value rewrites
additionalProperties: false into {"not": {}}, so the generated schemas
stay free of boolean nodes.
Fixes #2431
* fix(mcp): reject unknown fields on nested tool arguments too
Top-level strictness did not propagate into nested argument objects, so a
misspelled nested key was still silently dropped and defaulted. screenshot
size {width, heigth} accepted 'heigth', defaulted height to 768, and returned
a successful but wrong capture. Add deny_unknown_fields / .strict() to the
nested input objects (screenshot size, act fill fields) in both the Rust and
TypeScript implementations, and cover a nested unknown key in the regression
test.
* test(mcp): walk nested schemas in the Rust strictness check
The Rust regression test only inspected the top-level additionalProperties,
so the nested structs fixed in the previous commit were guarded on the
TypeScript side but not the Rust side. Walk the whole generated schema
instead, so any object node that still accepts unknown properties fails
and the message names the offending path:
screenshot accepts unknown arguments at $.properties.size
Verified by removing deny_unknown_fields from ScreenshotSize alone.
---------
Co-authored-by: DaniAkash <DaniAkash@users.noreply.github.com>
* perf(rust): drop dependency debug info from the dev profile (#2440)
A clean dev build of the workspace produces a 3.0G target directory, and
every git worktree pays it in full. A strip probe on the largest dependency
rlib put debug info at 81% of the artifact: 224M drops to 42M.
Dependencies are not stepped into during normal work, so their DWARF is
pure disk cost. Turning it off for `*` leaves workspace crates untouched,
and line-tables-only on those keeps panics and backtraces resolving to
file and line while dropping variable and type DWARF.
Measured on a clean build, same commit, same machine:
target/ 3.0G -> 1.8G (-40%)
target/debug/deps 2.1G -> 1.1G
build time 57.33s -> 51.00s
The build is faster because there is less debug info to write. Object
files carrying DWARF drop from 9964 (1349M) to 1461 (121M).
Smaller artifacts also shrink the Swatinem/rust-cache entry in the test
workflow, which eases pressure on the per-repo Actions cache limit.
`cargo clippy --workspace --all-targets -- -D warnings` passes clean.
* fix(analytics): surface unrecognized MCP clients instead of dropping them (#2439)
* fix(analytics): surface unrecognized MCP clients instead of dropping them
Every client whose name was not in the ~19-entry allowlist was collapsed to a
single opaque "unrecognized-client" bucket, discarding the real name before it
reached analytics. That hid the largest slice of real users behind one label.
Split the unrecognized path into privacy-safe buckets: a blank name becomes
unrecognized-empty; a name carrying structural PII markers (email, path, URL,
host:port) or an opaque over-long blob becomes unrecognized-redacted; and a
safe-shaped name surfaces its own slug so real long-tail clients (roo-code,
librechat, 5ire, ...) become visible and can be allowlisted. The PII gate runs
on the original raw, not the slug, so stripped fragments cannot leak.
* refactor(analytics): record unlisted client names directly, drop the marker gate
An unlisted client now records its own slug; only a blank name is reported as
unrecognized-empty. Removes the PII-marker and length gates: clientInfo.name is
a client-chosen implementation identifier, so recording it verbatim is the
intended behaviour.
* ci: resolve the bun version from package.json in every workflow (#2441)
* ci: resolve the bun version from package.json in every workflow
Nine setup-bun steps had no version pinned, so they installed whatever
Bun was newest at run time: the whole of Code Quality (biome, typecheck,
claw-api-codegen, claw-api-contract, fallow), both Tests jobs, Audit, and
the Turbo cache warmer. Those are the workflows that gate merges, so a
Bun release could change install layout, resolution, or test behaviour
with no commit and no warning. It also let Tests pass on a Bun the
release workflows never use.
The remaining six hardcoded "1.3.6" in YAML, a third place to keep in
sync with what the repo already declares.
All fifteen now read the version from the existing source of truth:
packageManager: "bun@1.3.6"
setup-bun's bun-version-file accepts package.json and prefers
packageManager, falling back to engines.bun. Both are present and agree,
so behaviour for the six release workflows is unchanged. Bumping Bun for
the entire repo is now a one-line change.
Every workflow was parsed to confirm valid YAML and that each setup-bun
step resolves from the file with no leftover literal: 15 steps, 0
problems.
* test(release): assert the release build pins bun via the version file
The release extensions workflow test asserted the literal
`bun-version: "1.3.6"` in the build job. The guard's intent is that the
release build pins Bun rather than floating, and that still holds; the
mechanism is now `bun-version-file` reading packageManager from
package.json.
Point the assertion at the new form so the guard keeps its meaning
instead of pinning a string that no longer appears.
* fix(ci): keep the macos builder on a literal bun pin
The macOS build job runs on the self-hosted builder against the
persistent tree at BROWSEROS_REPO_PATH and has no actions/checkout, so
GITHUB_WORKSPACE never holds the source. setup-bun resolves
bun-version-file from GITHUB_WORKSPACE, finds nothing, and falls back to
latest, which would have left source-mode macOS releases on a floating
toolchain.
Restore the literal pin for this one job and record why it cannot read
the version file. A job-level audit confirms it is the only one of the
fifteen setup-bun steps without a checkout ahead of it.
* feat(analytics): let a session declare its task category (#2443)
* feat(analytics): let a session declare its task category
name_session gains an optional category argument from a fixed enum. The
free-form name stays local and drives the tab group title exactly as before;
only the enum category is emitted, on a new agent_session_task_declared event
(task_category + client_name), so we can aggregate what kind of work sessions
do without any free-form text leaving the machine. An unrecognized category is
coerced to "other" so the declaration still counts; a non-string is dropped.
The prompt and skill now nudge the agent to pass a category.
* fix(analytics): declare task category at most once per session; fix catalog boundary test
Guard the task-declared emit with a per-session once-flag so a later
name_session rename does not emit a second agent_session_task_declared, which
would overcount the category mix and the declaration rate. Also register the
new wire name in the analytics single-source boundary test and its catalog
count.
* fix(ci): update name_session route test for the category argument
* refactor(server): replace custom compaction with AI SDK guided pruning (#2445)
* refactor(server): replace custom compaction with AI SDK guided pruning
Collapse the four-stage compaction pipeline (prune, tool-output reduction,
LLM summarization, sliding-window fallback) into the shape the AI SDK
compaction guide recommends: one trigger threshold, one prune pass, one
deterministic prune-only fallback.
The sliding-window fallback is replaced by pruneMessages hard mode at all
four of its former call sites, which cannot orphan a tool call from its
result. LLM summarization and the split-turn dual-summary path are removed.
Token estimation stays image-aware rather than adopting the guide's
JSON.stringify heuristic, which would read a single base64 screenshot as
roughly 250K tokens.
Fixes three latent issues along the way: the chars-per-token constant was 3
in the estimator and 4 in the inflation guard, the fallback targeted a
looser budget than the trigger so a compaction could succeed and re-fire
immediately, and a zero or negative client-supplied context window produced
a threshold every request exceeded.
* fix(server): stop compaction returning a transcript over the budget
Pruning has no lever against plain user and assistant text, so once the
floor pass has cleared every tool exchange a prose-heavy transcript came
back untouched and went to the provider over the model's limit. Verified at
a 200K window: 212,003 tokens in, 212,003 tokens out against a 180,000
threshold.
Adds a message-drop floor that keeps the first message and the longest
recent suffix that fits. It runs only after every tool call has already been
pruned, so unlike a general sliding window it cannot separate a tool call
from its result: there are no pairs left to break. A single message larger
than the window still cannot be shrunk, which is now surfaced on the log
line rather than left as a bare provider error.
Also tightens the context window guard from > 0 to >= 1. A fractional value
below one passed the check and then floored to zero, producing a zero-token
threshold that every step exceeded.
The test that should have caught the first bug asserted only that the
message count did not grow, which passes when nothing changes at all. It now
asserts the transcript lands under the threshold, at three window sizes.
* perf(rust): share cargo intermediates across checkouts (#2446)
* perf(rust): share cargo intermediates across checkouts
Every checkout compiles its own copy of the dependency graph. Anyone
keeping more than one clone or worktree open pays that in full each time,
around 1.6G apiece.
build-dir moves only the intermediate artifacts out of the checkout, and
it supports path templating, so {cargo-cache-home} resolves to CARGO_HOME
and one shared location covers every checkout on a machine. Nothing
absolute or machine specific is committed.
target-dir was the obvious alternative and does not work here: it has no
templating, cargo expands neither ~ nor $HOME, so a committed value could
only be relative to the checkout. That would limit sharing to sibling
directories, and because it also moves the final artifacts it would break
the three places the BrowserClaw release locates a built binary.
Final artifacts still land in <checkout>/target, so nothing that resolves
a build output by path changes.
Measured across two checkouts of the same branch:
cold build 52.36s target 227M shared 1.6G
second checkout 16.14s target 227M shared 2.1G
A release build against a warm shared directory still produces
target/release/browseros-claw-server-rs.
rust-cache saves only workspace target dirs plus the registry and git
caches, and never reads a build dir setting, so the shared directory is
named to it explicitly. Without that, CI would recompile the dependency
graph on every run.
* ci(rust): warm the rust cache on main and drop it fortnightly
Three related gaps around the shared cargo build directory.
The Rust cache was never warm for a new pull request. Tests run only on
pull_request, so rust-cache saved under a PR branch's scope, and branches
cannot read each other's caches. This is the same problem the Turbo warm
run already solves, and Rust was simply never covered. It matters more
now that the intermediates live in a cache-directories entry: without a
warm run, every PR recompiles the dependency graph.
Warming alone would not have worked. rust-cache builds its key from
GITHUB_JOB unless shared-key is set, and the existing keys show it:
v0-rust-test-Linux-x64-<hash>-<hash>
A warm job under any other name would have written a cache nothing else
could read. Both steps now pin the same shared-key, workspaces,
cache-directories and toolchain, since the toolchain hashes into the key
too.
The new warm job mirrors what the Rust suites compile, test binaries and
clippy's separate artifacts, and deliberately omits -D warnings because
it exists to populate a cache rather than to gate on lints.
Finally, rust-cache prunes only workspace target dirs and never extra
cache-directories, so the shared build directory is cached wholesale and
grows without bound. It is already the larger part of the problem:
v0-rust 25 entries 6.97 GB
all caches 262 entries 10.35 GB against a 10 GB allowance
Being over the allowance means LRU eviction is already discarding other
caches. Dropping the Rust entries on the 1st and 15th keeps that bounded,
matched on the prefix so nothing else is touched, and the warm workflow
is dispatched straight after so no branch waits for the next merge.
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Athul Nambiar <108534940+athul-22@users.noreply.github.com>1 parent 9852f96 commit feb7ffb
164 files changed
Lines changed: 5476 additions & 3537 deletions
File tree
- .cargo
- .github/workflows
- packages/browseros-agent
- apps
- app
- assets
- components
- agents
- chat
- modules
- agents
- chat
- screens
- agent-command
- ai-settings
- mcp-settings
- sidepanel/index
- claw-app
- components/audit
- claw-server-rust
- src
- analytics
- api/mcp
- services
- sessions
- tests
- server
- src
- agent
- compaction
- api
- middleware
- routes
- services
- acpx-probe
- klavis
- mcp
- lib
- agents
- acp
- host-acp
- storage
- conversations
- db
- migrations
- meta
- schema
- tools/filesystem
- tests
- agent
- api
- routes
- services
- klavis
- mcp
- lib
- agents
- acp
- host-acp
- db
- tools/browser
- crates
- browseros-mcp
- src
- tools
- tests
- harness-integrations/src/skills
- packages
- browser-mcp
- src
- tools
- resources/skills/browserclaw
- scripts/release
- updates/extensions
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
22 | 24 | | |
23 | 25 | | |
24 | 26 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
237 | 237 | | |
238 | 238 | | |
239 | 239 | | |
240 | | - | |
| 240 | + | |
241 | 241 | | |
242 | 242 | | |
243 | 243 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
| 30 | + | |
29 | 31 | | |
30 | 32 | | |
31 | 33 | | |
| |||
52 | 54 | | |
53 | 55 | | |
54 | 56 | | |
| 57 | + | |
| 58 | + | |
55 | 59 | | |
56 | 60 | | |
57 | 61 | | |
| |||
89 | 93 | | |
90 | 94 | | |
91 | 95 | | |
| 96 | + | |
| 97 | + | |
92 | 98 | | |
93 | 99 | | |
94 | 100 | | |
| |||
121 | 127 | | |
122 | 128 | | |
123 | 129 | | |
| 130 | + | |
| 131 | + | |
124 | 132 | | |
125 | 133 | | |
126 | 134 | | |
| |||
160 | 168 | | |
161 | 169 | | |
162 | 170 | | |
| 171 | + | |
| 172 | + | |
163 | 173 | | |
164 | 174 | | |
165 | 175 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
226 | 226 | | |
227 | 227 | | |
228 | 228 | | |
229 | | - | |
| 229 | + | |
230 | 230 | | |
231 | 231 | | |
232 | 232 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
29 | | - | |
| 29 | + | |
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
376 | 376 | | |
377 | 377 | | |
378 | 378 | | |
379 | | - | |
| 379 | + | |
380 | 380 | | |
381 | 381 | | |
382 | 382 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
266 | 266 | | |
267 | 267 | | |
268 | 268 | | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
269 | 274 | | |
270 | 275 | | |
271 | 276 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
270 | | - | |
| 270 | + | |
271 | 271 | | |
272 | 272 | | |
273 | 273 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
0 commit comments