🛶 chore: Bring Canary Up to Date With Dev - #16361
lia-by-librechat[bot] wants to merge 41 commits into
Conversation
Co-authored-by: Lia <lia@librechat.ai>
* 🚡 chore: Bump Agents SDK to v3.9.3 * chore: Lock Agents SDK 3.9.3 release --------- Co-authored-by: Lia <lia@librechat.ai>
Move the mirror out of the merge job and into a job that declares the legacy-mirror environment, so the token that can write to the legacy namespace is reachable only from workflows running on the branches that environment allows. A failed mirror can no longer fail a publish either: the job carries continue-on-error.
* 🚉 fix: Keep Queue Controls with the Starting Turn * 🚉 fix: Stabilize the Pending-Turn Portal Callback --------- Co-authored-by: Lia <lia@librechat.ai>
…16267) * 🛝 refactor: Route Packaged Chat Completion Frames Through a Writer * style: Match Chat Completion Contract Test Import Order * fix: Avoid ES2022 Array Methods in Protocol Contract Test --------- Co-authored-by: Lia <lia@librechat.ai>
Co-authored-by: Lia <lia@librechat.ai>
Co-authored-by: Lia <lia@librechat.ai>
* fix: constrain agent selector to 480px and 10 unsearched agents The Agents panel selector dropdown now caps at 480px tall with the option list scrolling inside, and lists at most 10 agents while the search field is empty; typing lifts the cap so search still reaches every agent. ControlCombobox gains optional popoverMaxHeight and unsearchedLimit props, so every other selector keeps its current fixed 300px list. * fix: make the agent selector cap configurable, viewport-safe, and selection-preserving The unsearched list cap now reads interface.agentSelectorLimit from librechat.yaml (default 10), the popover cap yields to Ariakit's available height so a short viewport never pushes options offscreen, and a selected agent ranking past the cut keeps the last slot instead of disappearing until searched for. * fix: forward agentSelectorLimit through the interface loader and guard it at the client loadDefaultInterface whitelists interface fields, so the new knob never reached /api/config without explicit forwarding; and values that bypass interfaceSchema (raw yaml reads, principal-scoped admin overrides) are normalized back into 1..100 at the client so a bad override cannot empty the selector.
* fix: accept GitHub App bot commit identities * test: retain friendly Lia author name with bot email * docs(api): align Git bot identity OpenAPI schema
Update package repository and bugs URLs, GitHub links in docs, templates and comments, and image references (compose files, deployed-update, Helm values and renovate hints) from danny-avila to LibreChat-AI / librechat-ai. Covers the rag-api repository and its images, which moved to the organization. Sponsor links and the sealed star-history chart are unchanged.
* 🏚️ feat: Recover Coding Chats With Missing Workspaces * fix: Revalidate Workspace Recovery and Preserve V1 Usage --------- Co-authored-by: Lia <lia@librechat.ai>
Co-authored-by: Lia <lia@librechat.ai>
Co-authored-by: Lia <lia@librechat.ai>
* 🐤 docs: Clarify Maintainer-Directed Canary Pull Requests * 🐤 docs: Link Issues on Dev and Canary PRs --------- Co-authored-by: Lia <lia@librechat.ai>
…6298) * 🦺 fix: Skip Redundant MCP OAuth Probes and Teardown Cancellations * 🧪 test: Complete OAuth Discovery Token Method Fixture --------- Co-authored-by: Lia <lia@librechat.ai>
* 🪁 fix: Keep Workspace Discovery Reactive * fix: Observe Agent Endpoints Without Recoil --------- Co-authored-by: Lia <lia@librechat.ai>
* 🌫️ feat: Add Bounded PII Text Transformation * ⚡ perf: Reuse Bounded Placeholder Scan * fix: Respect PII Transformation Session Boundaries --------- Co-authored-by: Lia <lia@librechat.ai>
* fix: Surface Persistent Memory Before First Save * fix: Distinguish Unreadable Memory From Empty Memory --------- Co-authored-by: Lia <lia@librechat.ai>
* 🔕 perf: Back Off Idle Durable Agent Recovery Polls * 🎛️ style: Format Queued-Turn Recovery Test * fix: Preserve Durable Recovery Wakeups and Discovery Invariants --------- Co-authored-by: Lia <lia@librechat.ai>
* 🥞 fix: Keep Tool Glyphs Behind Approval Reviews * fix: Keep Chat Header Above Composer Approval Reviews --------- Co-authored-by: Lia <lia@librechat.ai>
…16308) * 🦉 fix: Quiet Idle Subagent Discovery While Preserving Child Wakeups * 🐣 fix: Wake Saved Parent Index During First-Run Events --------- Co-authored-by: Lia <lia@librechat.ai>
Tokenize budgeting inputs in bounded chunks instead of charging a token per UTF-8 byte above 4 KiB. Subdivide long whitespace/non-whitespace runs to bound BPE merge work and preserve surrogate pairs. Keep strict provider-accounting counts and unavailable-tokenizer fallbacks separate. Cover both encodings, SDK slice composition, programmatic tool prompts, summarization, real overflow, cache reuse, and failure recovery. Related to #16278. Co-authored-by: Lia <lia@librechat.ai>
Co-authored-by: Lia <lia@librechat.ai>
The e2e fake model reported input tokens for the chat messages alone, because the SDK hands a test override model the pruned messages without the systemRunnable pipe. Once #16301 gave every memory-enabled chat a system prompt, the calibrated context snapshot put used tokens below the instructions and the gauge dropped its Messages row. Count input over the complete prompt, as a real provider bills it.
…16339) * 🐢 fix: Back Off Waiting Completion Wake-ups and Deliver Them When Ready * fix: Signal every readiness path and make the completion wait cap configurable * fix: Mark held completion deliveries instead of pipeline expedite, and announce store-won approval expiry * fix: Scope settle expedites to the resumed conversation and close the remaining signal gaps * fix: Announce a won approval expiry once and release subagent wake-up registrations * 🧪 ci: Count System Instructions in Mock Model Usage (#16350) The e2e fake model reported input tokens for the chat messages alone, because the SDK hands a test override model the pruned messages without the systemRunnable pipe. Once #16301 gave every memory-enabled chat a system prompt, the calibrated context snapshot put used tokens below the instructions and the gauge dropped its Messages row. Count input over the complete prompt, as a real provider bills it. --------- Co-authored-by: Lia <lia@librechat.ai>
* 🛍️ fix: Discover Managed Agents in Marketplace Search * 🛍️ fix: Reject Invalid Agent List Permissions Before Bypass * 🛍️ fix: Scope Managed Agent Discovery and Avatar Refresh * 🛍️ fix: Bound Managed Avatar Cache to Signed URL Lifetime * 🛍️ fix: Invalidate Tenant Avatar Cache After Upload --------- Co-authored-by: Lia <lia@librechat.ai>
* 📣 fix: Surface MCP Credential Failures During Tool Loading * fix: address MCP credential propagation review --------- Co-authored-by: Lia <lia@librechat.ai>
…Results (#16345) * 🐢 fix: Back Off Waiting Completion Wake-ups and Deliver Them When Ready * fix: Signal every readiness path and make the completion wait cap configurable * 🧷 fix: Wait for Long Dispatch Turns Before Giving Up Background Code Results * fix: Mark held completion deliveries instead of pipeline expedite, and announce store-won approval expiry * fix: Keep code tasks protected and wait without a fixed cutoff * fix: Scope settle expedites to the resumed conversation and close the remaining signal gaps * style: Format the settlement waiter * fix: Announce a won approval expiry once and release subagent wake-up registrations * fix: Outlast terminal persistence before giving up an unanchored code result * fix: Anchor code results the moment their dispatch turn settles * fix: Scope harvest epoch pinning to the owning stream --------- Co-authored-by: Lia <lia@librechat.ai>
* 📮 fix: Count Undelivered Background Results as Outstanding * fix: Reconcile delivered tasks, discard exactly, and count pending subagent results * fix: Settle manually claimed remote deliveries and keep pending controls while wake-ups drain * fix: Report dead-lettered deliveries as failed and read subagent wake-ups from the durable store * fix: List restored dead letters and retire deliveries on local durable claims
* 🚥 fix: Pause Rejected Steer Recoveries * 🚥 fix: Preserve Held Recovery Boundaries and Refused Copy Feedback --------- Co-authored-by: Lia <lia@librechat.ai>
* 🍱 fix: Carry Coding Approval Mode With Queued Turns * fix: Address Queued Approval Policy and Protocol Review Findings * fix: Preserve Queued Turn Retry and Error Contracts --------- Co-authored-by: Lia <lia@librechat.ai>
… Without Step IDs (#16353) * 🧷 fix: Anchor Background Results Onto Tool Calls a Resumed Turn Saved Without Step IDs * 🧷 fix: Preserve Background Result Fallback Ownership --------- Co-authored-by: Lia <lia@librechat.ai>
* 📦 chore: bump `@librechat/agents` SDK to v3.9.5 * 🐛 fix: Preserve Fading Tier State With Agents SDK v3.9.5 * 📦 chore: bump `@librechat/agents` SDK to v3.9.6 --------- Co-authored-by: Lia <lia@librechat.ai>
* 🐙 ci: Spread Playwright Coverage Across Isolated Shards * ci: Allow Manual Validation of the PR Shard Profile --------- Co-authored-by: Lia <lia@librechat.ai>
* 📋 feat: View and Stop Background Tasks From the Chat Header * 🔧 fix: Restore background task CI and review safeguards * test: Mock background task routes in conversation suites * fix: Reconcile Task Panel Identity and Lifecycle * fix: Expire Cached Terminal Tasks Across Both Kinds * fix: Preserve Conversation Task Discovery Across Endpoint Changes * test: Provide Query Context to Presentation Harness --------- Co-authored-by: Lia <lia@librechat.ai>
* feat: Attach or leave a workspace on a saved chat
A saved chat that recorded running without an attached environment could
never take one: `without_attached` is sealed for the life of the chat, and
the only transition was a move between attached environments. Switching an
existing chat to a coding agent therefore ended in a composer that could
send but never reach files or commands. A chat sealed to a machine that went
offline had the opposite dead end: Send stayed disabled with nothing on
offer but a reconnect hint, and the "Move the chat to continue without it"
copy promised a request the server rejected as `invalid`.
One transition mechanism now covers all three directions. `from` still
repeats the persisted decision, so a stale view cannot replace a decision it
has not seen, and only a decision the chat actually recorded can move:
- attach, from a recorded `without_attached` onto the environments its
agents use, with each target workspace revalidated on its worker;
- detach, an empty `to`, which clears the selections without polling the
machine the chat is leaving;
- move, unchanged, including its refusal to switch the workspace of an
environment the decision already covers.
The composer keeps the control visible while a chat runs without a
workspace, so that state is named and reversible, and offers "Continue
without workspace" whenever a sealed machine stops being usable. History is
untouched and no file is copied.
A send now also records the decision it was submitted under on its own
conversation. The server seals that pair for the rest of the chat, so a chat
that became saved mid-run held none while the stored one was already sealed:
the composer re-derived an agent default the seal refuses and reported
"Choose workspace" with Send disabled until the page was reloaded.
* 🎨 style: Format the Workspace Transition Hook Tests
* fix: Never seal a partial workspace decision, and gate the new directions
Two review findings on the transition mechanism.
A transition replaces the sealed decision whole, and the next turn resolves
every environment or none. The move path could not truncate before, because it
was offered only in `choose` state, which `aggregateState` reaches only when no
environment is unavailable, unsupported or missing: every environment was then
either carried over or selectable. Widening the gate to `!canSubmit` so an
unreachable machine could be left behind broke that invariant, and an
environment nothing can cover would have been dropped from a decision the
composer then sealed, blocking the next send and needing a second transition to
escape. A nonempty transition now requires every environment to be retained or
selectable; when one cannot be, only leaving attached execution is offered,
which is the escape.
The move protocol version becomes 2. Attach and detach travel on the same
owner-authorized endpoint as the move, so a client built against this change
would otherwise offer both to a deployment that only implements moves, where an
attach comes back `locked` and an empty target set `invalid`. The client already
compares the advertised number against the constant, so the bump is the gate.
`conversationMoves.enabled` still decides whether an owner may replace a sealed
decision at all.
* fix: Keep v1 moves working, and take the conversation updater from its owner
Two review findings on the previous head.
Bumping the move protocol wholesale made the client's exact-version check treat
a replica still advertising 1 as supporting nothing, so an attached chat whose
agent moved to another machine lost the relocation that replica already served
-- the previous head's own compatibility test pinned that regression. The
protocol is now two constants: version 1 is the move between attached
environments, version 2 adds attaching an environment to a chat that recorded
running without one and leaving attached execution behind. The server
advertises the highest it implements and the client keeps every lower
capability, so a v1 replica keeps its move while attach and detach wait for the
version that serves them. Where an environment cannot be covered and leaving is
not served either, no transition is offered and the composer reports the state.
Recording the submitted decision had made this hook a second Recoil writer of
the conversation atom, widening the surface that migration has to convert. The
host that owns the atom already holds its setter, so the hook receives it
instead of reaching through `~/store`.
* fix: Advertise attach and detach beside the move instead of above it
Raising the move protocol to 2 kept a client and server of the same build in
agreement, but not the two builds a rollout actually runs at once. The deployed
client compares the advertised number for equality against its own constant of
1, so a server advertising 2 tells every client already loaded in a browser that
moves are unsupported, and an attached chat whose agent changed machines loses
its relocation until that tab reloads.
The move version therefore stays 1, exactly as before this branch, and the two
new directions are advertised on their own field:
codeEnvironmentMoveVersion: 1 // move between attached environments
codeEnvironmentTransitionVersion: 2 // attach an environment, or leave one
Both come from `conversationMoves.enabled`, since all three are one owner
replacing one sealed decision. A client that predates this capability reads only
the move version and keeps working; a client that has it offers attach and
detach only where the second field appears, so it never sends a replica an
attach it refuses as `locked` or an empty target set it calls `invalid`.
* fix: Hold a send while a workspace decision is being replaced
Confirming an attach left Send enabled. The composer disables only the
workspace menu while the request is in flight, so a turn submitted right after
the confirmation started under the decision being replaced: it ran without the
workspace its owner had just chosen, and the replacement still landed, because
the stored decision the compare-and-swap expects was unchanged. The chat then
reported an environment its last turn never used, and nothing said so.
The server's own check has the same shape. It reads the conversation's
generation state, then revalidates each target workspace over the network, then
swaps; a turn that starts during that round trip is invisible to the check that
already ran. It now re-reads the generation state immediately before the swap,
leaving only the instant the compare-and-swap itself covers, which closes the
window for every client rather than just this one.
On the client the submission resolver refuses while a replacement is pending,
so the same signal disables the Send button and makes a programmatic send
return false. The workspace control stays reachable, so the state a chat is in
remains visible while it settles.
* fix: Fence Workspace Transitions and Reconcile Rejected Choices
Advance a private conversation revision after publishing an active generation and before reading its authoritative workspace decision. Transition CAS writes compare that revision, covering admission during the final idle-check window without expiring locks. Preserve tenant middleware and exclude private counter writes from chat activity and indexing.
Default existing undecided chats to no workspace, reconcile failed submissions from the server, scope pending guards to the affected chat, compare selection tuples without delimiter collisions, and opt attach/detach into deployment policy independently of existing moves. Retain legacy selection-only choices through first-send events.
* fix: Retain Failed Workspace Recovery and Skip Disabled Fences
Keep unconfirmed code-environment decisions in conversation-scoped Jotai state after authoritative reads fail, mutation observers reset, or navigation unmounts them. Expose a retry action and release submission only after a matching successful read. Ignore stale completions and reject mismatched conversation responses.
Reuse the owner-scoped conversation already loaded by each ingress when effective conversation moves are disabled. Retain the admitted revision fence for enabled moves, including move-only deployments. Cover failure/retry lifecycle and both policy branches at the helper and ingress levels.
* fix: Reuse the Atomic Admitted Snapshot Through Recovery
Return the admission revision and persisted decision from one Mongo findOneAndUpdate. Skip content indexing per internal query while retaining tenant middleware, private projection, and timestamps. Carry the returned snapshot through web and API initialization instead of validating against a stale pre-admission selection.
Keep first-send workspace choices in both the recovery preset and conversation template, and reconcile using the generated conversation ID from error or message data rather than requiring a saved submission snapshot. Cover single-query storage, indexing isolation, moved-machine validation, and first-send error recovery.
* fix: Reconcile Cancelled Turns and Keep Detach Reachable
Reconcile the submitted code decision before the local cancellation path finalizes streamed content or recovers a minimal submission. A cancel that beats the server rejection must not leave the attempted choice sealed locally; navigation must not update a different conversation.
Offer the authorized detach-only transition when a sealed conversation no longer has any attached agents, while ordinary chat stays sendable. Keep the selector visible for that transition and retain existing policy gates. Cover cancellation branches, no-attached-agent states, managed-only agents and the detach action.
* fix: Recover Workspace Transitions Across Navigation
* test: Verify Saved Workspace Transitions Against Current Dev
* test: Assert Explicit No-Workspace State in Native Journey
* test: Await Browser Settlement Before Switching Native Agents
* test: Settle URL Agent Selection Before Native Chat
* test: Wait for Worker Lease Expiry Before Offline Detach
---------
Co-authored-by: Lia <lia@librechat.ai>
Co-authored-by: Danny Avila <danny@librechat.ai>
|
Review handoff for head |
|
Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures. |
|
Review handoff for head |
|
Review handoff for head |
|
Review handoff for head |
|
Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures. |
|
CI note for head |
Summary
canaryis an integration branch with early PRs targeting it, butdevhas advanced independently. This PR mergesdevintocanarywithout rebasing or force-pushing either published branch. It retains the existing canary commits and seven open canary-targeted PRs while importing the 37 dev-only commits present at7b2362d7a7c6.Merge with GitHub's “Create a merge commit.” Do not squash or rebase this PR. Its head contains the two-parent
dev-into-canarymerge. Squashing would copy the files but not makedevan ancestor ofcanary, defeating the reason for this sync. Ifdevadvances before this lands, merge the newerdevinto this PR branch first.How it works
The 19 overlapping files reconcile behavior rather than choosing a wholesale side: manager-scoped agent lists keep per-page S3 refresh without moving sort/cursor boundaries; MCP loading keeps canary's app capability policy while adopting dev's credential-error handling; background result settlement retains canary's bounded App BSON admissions and dev's stepless tool-call fallback. The frontend keeps canary's theme roles while incorporating dev's workspace attach/detach and queued-turn recovery behavior. Dev's six-shard Playwright matrix keeps canary's registered-email-change runs and MCP Apps required gate.
Type of change
Testing
Tested environments/configuration: Isolated worktree from
origin/canary, merged withorigin/dev; Node 24; MongoDB 8.2.1 viamongodb-memory-serverwith--nounixsocketfor the read-only sandbox.Automated tests:
packages/data-provider,packages/data-schemas,packages/api,packages/client.packages/data-provider,packages/data-schemas(tsc --noEmit).packages/data-schemas/src/methods/message.spec.ts(focused App admission and tool-call settlement, 72 passed).api/server/controllers/agents/v1.spec.js(focused S3 avatar/pagination, 21 passed).node --test .github/scripts/retarget-prs.test.mjs .github/scripts/playwright-matrix.test.mjs(15 passed).tsc --noEmitpassed locally forpackages/data-providerandpackages/data-schemas. The local checks inpackages/api,packages/client, andclientuse a mixed-version dependency overlay and emit unrelated diagnostics; GitHub CI clean-install typechecks pass for all five workspaces at headb9b82d75cf5e.b9b82d75cf5e. Lighthouse remains red: median LCP 4,534.104 ms against 4,500 ms. The previous head passed at 4,402.524 ms; only class formatting and a suppression-ledger removal changed since then, so treat this as a timing investigation, not a green check. Other Playwright shards are still running. Chrome is unavailable in this worktree; no local Lighthouse or browser run.Screenshots / recordings
Not captured for this branch-maintenance merge. User-visible changes come from their original feature PRs rather than a new UI feature in the sync; affected chat components and matrix behavior have focused checks above and CI coverage below.