[pull] main from danny-avila:main - #217
Merged
Merged
Conversation
`updateAgent` returned early when the resulting state matched the newest `versions` entry, so `findOneAndUpdate` never ran and the caller's update was discarded behind a 200 response. Suppressing a redundant version entry is correct; suppressing the write is not. The document is regularly not equal to its newest version entry: `$push`/`$pull`/`$addToSet` updates snapshot the pre-update state (as `addAgentResourceFile` does on every file attach), `skipVersioning` writes snapshot nothing, and `removeAgentResourceFiles` bypasses `updateAgent` altogether. Any update that moved the document back onto that entry's content was then dropped, leaving the drifted state in place. Keep the version entry suppressed, apply the write, and still report the unchanged `versions` count as `version` so callers keep their existing "no new version" signal. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* 💬 style: Right-Align In-Flight Steer Bubbles to the Message UI The chat surface reads as message bubbles now — user turns on the right, assistant turns on the left — but the in-flight steer bubbles anchored above the composer were still left-aligned, so a steer sat on the opposite side from the words the user had just sent, then jumped across on `on_steer_applied` when the persisted `SteerPart` landed in-thread on the right. Align the overlay with the user turn it belongs to: - The bubble stack right-aligns and is constrained to the message column (`max-w-3xl`), so the in-flight bubble sits where its applied twin lands instead of ~52px further right (the composer runs wider than the message column at `xl`). - The bubble adopts the same theme-token geometry as `SteerPart` and every user turn (`rounded-theme-surface rounded-br-theme-control`, `px-theme-normal`), replacing the raw `rounded-3xl`/`pl-3 pr-4`. It keeps its outline: an in-flight steer is still provisional. - The controls flank the bubble — overflow menu outboard-left, send-now arrow outboard-right — so neither reads as belonging to the other. DOM order matches visual order, so focus order stays coherent. Also drops the thin `bg-border-medium` divider that bound the arrow to its message: with the arrow now outboard on the far side of the bubble it has nothing to separate, and `EscalateNowButton` no longer needs its fragment. * 💬 style: Center the Steer Controls on the Bubble's First Line The flanking controls read as neither top-aligned nor centered, because their resting position was an accident of `sticky top-2`: the topmost rail trips the sticky inset at rest and is shoved 8px below the row top, while every rail below it clears the inset and stays at the top. So the controls sat 3.8px above the bubble's centre — and stacked steers did not even agree with each other. Give each rail a `py-3` band that reproduces the bubble's own first line (its `py-2.5`, its 1px border, and half the gap between the 24px control and the taller text line box), and pad the overlay evenly so the topmost rail already clears the sticky inset instead of being displaced by it. A 24px control now centres on the first line: measured at 722.0 against the text's 721.8, versus 718.4 before. Beside a one-line steer that reads as centred; on a tall one it aligns to the opening line rather than drifting to the middle, and sticky still carries it while the stack scrolls.
…on (#14815) `MCPServerInspector` overwrote the operator's `serverInstructions` declaration with the text fetched from the server. That made a YAML server's cached entry differ from its own raw config on an admin-configurable field, so `isUnmodifiedYamlServer` misclassified it as admin-modified and re-inspected it on the first user-scoped resolve. The second inspection produced a config with a newer `updatedAt`, which: - flipped `getServerConnectionStatus` to `disconnected` permanently, since the healthy app connection was then measured against the newer timestamp; and - made `isAppServerConfig` reject the effective config, gating off the app connection so `GET /api/mcp/tools` returned zero tools and cached nothing. Fetched instructions now land on a separate `resolvedInstructions` field, matching how every other inspector-derived value is stored, so the declaration survives inspection and the guard compares like with like. Bumps `REGISTRY_STORAGE_SCHEMA_VERSION` so Redis-backed deployments rewrite entries whose `serverInstructions` still holds fetched text. Fixes #14798
A Streamable HTTP server allows one standalone `GET` SSE stream per session and
releases its mapping from the response stream's cancel callback. That callback
never runs when the connection dies at a proxy rather than at the client, so the
server keeps holding a stream nobody is reading while the client knows its stream
is gone. Every reconnect carrying that session id then gets a 409:
SSE stream disconnected: TypeError: terminated
Transport error (may require manual intervention):
Streamable HTTP error: Failed to open SSE stream: Conflict
Transport error (may require manual intervention):
Maximum reconnection attempts (2) exceeded.
Nothing there requires manual intervention. The connection recovers on its own in
a few seconds, because the rebuild the first 409 escalates to sends the
spec-mandated `DELETE`, which drops the server's session along with the stream it
leaked. Two things made a self-healing event read as a fatal one.
`extractSSEErrorMessage` classified status by scanning the message text for
digits, but `StreamableHTTPError` and `SseError` carry the status on `code` and
their messages do not always repeat it. "Failed to open SSE stream: Conflict"
has no digits at all, so a 409 never reached the status branch and fell through
to the terminal `isTransient: false` — the same verdict as a DNS typo. A 5xx
arriving on `code` alone had the same blind spot. The status is now read from
`code` when it is in HTTP range, with the message scan kept as a fallback, and
409 joins 5xx as transient: the stale session it reports is cleared by the
rebuild, with nothing for an operator to do.
The second is volume. Each SDK retry fires `onerror` twice — once with the raw
throw out of `_startOrAuthSse`, once with the `Failed to reconnect SSE stream`
wrapper. Only the wrapper matched the existing suppression, so every doomed retry
logged at error level, and the retries are doomed by construction: nothing about
the same session id can stop conflicting. The first conflict now escalates for
rebuild and the rest are logged as the echo they are, along with the SDK's
out-of-retries announcement when a rebuild is already underway. The non-conflict
path for that announcement is untouched, so an exhausted budget still falls
through to our reconnection everywhere else.
`extractSSEErrorMessage` moves to `errors.ts` alongside `isOAuthAuthenticationError`.
It had no test: `MCPConnection.test.ts` held a hand-copied clone marked "keep in
sync with the actual implementation", so 66 assertions were exercising the copy.
The clone is deleted and the suite now imports the real function, which it turns
out had not drifted.
`MCPConnectionSseConflict.test.ts` drives a real client transport against a real
in-process `StreamableHTTPServerTransport` reproducing the sequence above: the
stream opens, its socket is destroyed underneath the client, and every later
`GET` on that session id conflicts while a rebuilt session gets a healthy stream.
* 📎 fix: Alias Shell Script MIME Variants to `application/x-sh` Chrome on Linux reports `.sh` files as `application/x-shellscript` (freedesktop shared-mime-info) and libmagic reports `text/x-shellscript`. Neither string appears anywhere in the source, so uploads were rejected even though `application/x-sh` is in the default allowlist and `codeTypeMapping` maps `sh` to it — `inferMimeType` only consults the extension map when the client sends no type at all, so a non-empty browser value passed straight through to the allowlist check. Alias both variants to the canonical `application/x-sh`, matching the existing treatment of `text/x-markdown` and `application/x-zip-compressed`. Also attach `statusCode`/`body` to multer file-filter rejections. Without them the error misses the `isCustomError` branch in `ErrorController` and falls through to a bare `500 An unknown error occurred.`, so the rejection reason was logged server-side but never reached the client. The upload hook already surfaces `error.response.data.message`, so a rejected file now explains itself instead of showing a generic upload failure. * 🔁 refactor: Move Upload Error Contract Into `packages/api` Addresses codex P1 on #14817. The producer of the `statusCode`/`body` pair now sits beside its consumer: `isCustomError` and `ErrorController` are already in `packages/api/src/middleware/error.ts`, and `CustomError` is already in `packages/api/src/types/error.ts` — only the construction of that pair was stranded in legacy JS. `createCustomError` is exported from the same module as the guard that recognizes it, and `multer.js` is back to a thin caller. Also pins the `.sh` back-compat claim with tests: configs from the documented workarounds (`application/x-sh` per #4660/#5689/#6297, and the broad patterns from #14804) still accept a `.sh` upload after the alias rewrites the type. A negative control confirms the endpoint config is genuinely in play rather than falling back to the default allowlist.
* ⚡ feat: Add Gemini 3.7 Flash Support Adds first-class support for Google's Gemini 3.7 Flash (`gemini-3.7-flash`) for both the Gemini API (AI Studio) and Google Cloud Gemini Enterprise Agent Platform, following the Gemini 3.6 Flash integration (#14369). - Context window (1,048,576) in googleModels; API + cache pricing in tx.ts. - Model dropdown (config.ts) and GOOGLE_MODELS examples for both integrations. - Register the model in the Flash-family handler so it inherits the existing strip of deprecated sampling params (temperature/topP/topK), rejected penalty params, and thinkingBudget, and defaults to `medium` thinking. - Generalize that handler's enumerated table from a [id, level] tuple to a rule object, so a model can also declare thinking levels it rejects. Gemini 3.7 Flash errors on `minimal` (which the Google endpoint offers in its thinkingLevel slider), so an explicit `minimal` is substituted with the nearest supported level, `low`. Explicit low/medium/high pass through unchanged. - Apply Google's introductory pricing ($0.75 in / $3.75 out / $0.075 cached, per 1M) to Gemini 3.7 Flash and correct Gemini 3.6 Flash to the same rates. Both revert to $1.50 / $7.50 / $0.15 on 2027-01-01; noted at both call sites. Resolves #14802 Ref: https://ai.google.dev/gemini-api/docs/models/gemini-3.7-flash Ref: https://ai.google.dev/gemini-api/docs/pricing * 📝 docs: Match the House Style for Promotional Rate Comments Align the Gemini 3.6/3.7 Flash introductory-pricing notes with the existing Sonnet 5 convention in the same file: one comment per group, naming the models and the exact values to restore, so the manual follow-up is unambiguous. No rate changes. * ⬆️ chore: Bump `@librechat/agents` to 3.4.7 for Gemini 3.7 Flash Prefill Unblocks this PR. `NO_PREFILL_GEMINI_MODELS` is model-enumerated in the agents SDK, so 3.4.6 does not know `gemini-3.7-flash` forbids a trailing `model`-role turn — editing an assistant reply and resubmitting would reach Google as a prefill and return HTTP 400 on a model this PR adds to the default list. 3.4.7 (danny-avila/agents#412, released via #413) adds it. Verified the published tarball: `3.4.6...3.4.7` touches only `dist/{cjs,esm}/llm/google/utils/common.*` — the prefill array and its comment. `dist/types` is byte-identical, so there is no API surface change. Raises the declared range in both workspaces alongside the lock. `^3.4.6` already permitted 3.4.7, but the fix is required rather than merely compatible, so the floor should say so.
… Searching It (#14820) `@modelcontextprotocol/sdk@1.30.0` is a small maintenance release on the 1.x line (upstream's active line is now the 2.0.0 scoped packages). The range was already `^1.29.0`, so only the lockfile pinned the old version; the manifests move too so the floor matches what we test against. Nothing in it is breaking. The four changed type declarations are additive — optional `maxBufferSize` on `StdioServerParameters`, an optional third constructor argument on `StdioServerTransport`, optional options on `ReadBuffer`, optional `keepAliveMs` on the server transport — and the only manifest change is `@hono/node-server` widening to `^1.19.9 || ^2.0.5`. No new dependencies. Two behavior changes are worth knowing about even though neither is an API break. `ReadBuffer` now caps a single stdio message at 10 MB (previously unbounded) and errors the transport instead of growing, which is reachable through `StdioClientTransport` if a stdio server returns a very large single result; it takes `maxBufferSize` if that ever needs raising. And Content-Type handling switched from substring search to parsed media types, client and server. Most of the release is Streamable HTTP server hardening we do not run — a 15s SSE keep-alive, `X-Accel-Buffering: no` on SSE responses, guards so a stale stream's cancel cannot tear down its successor, and `_closed` checks so a transport closing mid-request stops registering streams into swept maps. None of it changes how we behave as a client. In particular it does not address the stale-stream 409 in #14816: that keep-alive runs in whichever server we connect to, not here. The same substring-vs-parse mistake the SDK corrected exists in our streamable HTTP response guard, which classified a response as SSE with `contentType.includes('text/event-stream')`. A `Content-Type` naming the SSE type in a parameter — `text/plain; boundary=text/event-stream` — is not an event stream, but matched. The guard then took `canEmitFallbackSSEError`, so an oversized body was answered with a synthetic SSE error frame the caller reads as a well-formed response body, rather than the throw a non-SSE response gets. The check now compares the parsed media type, via a `mediaTypeEssence` helper added to the header utils where `mergeHeaders` already lives. Verified against 1.30.0 rather than assuming: the package was staged into the worktree's own `node_modules` so it shadowed the shared install, and `packages/api` `src/mcp` ran green on it — same four pre-existing red suites as on 1.29.0 (`MCPReinitRecovery` plus three Redis `cache_integration` suites that need a live Redis), no new failures.
…#14821) `activity-phases` asserted the parent `summary` was visible immediately after `sendMessage` resolved. A parent phase only exists once the turn completes, the phase closes, and its summary round-trips to the phase-label model — so that assertion raced the entire pipeline and only survived on Playwright's retries. It shows as `1 flaky` on the memory lane of a green dev run, and fails all three attempts on slower hardware. Gate the DOM on the durable projection instead. The test already fetched /api/messages twice; the first fetch now also waits for the persisted phase part before any DOM assertion runs, so the client is only asked about a phase the server has already written. Also drops the duplicate fetch. The two poll blocks queried the same endpoint for the same message and both asserted `finalTextIndex === activity_end_index`; the removed copy left `liveAssistant`, `livePhase` and `liveFinalTextIndex` shadowing their durable equivalents. No coverage removed — every assertion is preserved, reordered to follow the dependency chain: persisted shape, then DOM, then label-model requests, then the reload round-trip.
* 🚀 chore: Prepare v0.8.8-rc1 release * 📚 docs: Complete v0.8.8-rc1 operator references * 📚 docs: Mark stateful sessions experimental * 📚 docs: Clarify background code capability * 📚 docs: Refresh v0.8.8-rc1 operator guidance * 📚 docs: Highlight v0.8.8-rc1 features in README * 📦 chore: Bump publishable packages again * 📚 docs: Add streaming question progress * 📦 chore: Bump publishable packages again * 📚 docs: Refresh v0.8.8-rc1 release highlights * 📦 chore: Bump publishable packages again * 📚 docs: Refresh v0.8.8-rc1 release guidance * 📦 chore: Bump publishable packages again * 📚 docs: Highlight batched Agent questions * 📦 chore: Bump publishable packages again * 📦 chore: Bump publishable packages again * 📦 chore: Bump publishable packages again * 📦 chore: Refresh v0.8.8-rc1 package versions * 📦 chore: Refresh v0.8.8-rc1 package versions * 📦 chore: Refresh v0.8.8-rc1 package versions * 📄 docs: Note PowerPoint template support * 📦 chore: Refresh v0.8.8-rc1 package versions * 📄 docs: Note latest provider and file support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )