[pull] main from danny-avila:main - #572
Merged
Merged
Conversation
* feat: add generic agent trigger dispatch seam * refactor: harden trigger dispatch contract * fix: annotate envelope depth alias * style: sort trigger envelope imports * fix: reject unknown trigger dispatch modes * fix: reject unknown trigger envelope versions * refactor: validate complete trigger envelopes * feat: add agent trigger execution host * fix: enforce trigger delivery contracts * fix: harden trigger admission path * fix: finish trigger cancellation handling * fix: retry strict steer rollout gaps * fix: retry paused trigger steers * fix: parallelize trigger admission setup
* feat: add generic agent trigger dispatch seam * refactor: harden trigger dispatch contract * fix: annotate envelope depth alias * style: sort trigger envelope imports * fix: reject unknown trigger dispatch modes * fix: reject unknown trigger envelope versions * refactor: validate complete trigger envelopes
Co-authored-by: danny-avila <110412045+danny-avila@users.noreply.github.com>
* feat: wire trusted agent trigger dispatch * feat: add durable agent trigger delivery * fix: annotate trigger envelope byte limit * test: isolate trigger startup in server specs * fix: fence trigger delivery during account deletion * test: isolate trigger service in user controller specs * fix: close trigger deletion admission race * fix: harden account deletion fences * fix: close durable trigger review gaps * fix: require offline stale-fence recovery * fix: type trigger lane sequence ids * fix: fence admin user deletion triggers * fix: make trigger deletion recovery durable * fix: harden offline user deletion * fix: serialize trigger lane publication * style: sort trigger delivery imports * fix: recover orphaned trigger publications * fix: preserve trigger recovery ordering * fix: fence trigger publication during purge * fix: defer remote trigger deletion fences * fix: close durable delivery cleanup races * fix: drain CLI generation owners before deletion
* 🧩 refactor: Resolve Tool-Card State Once (AI-1810) Each tool card derived its state several times over — the visible label from one expression, the `aria-live` announcement from another, the icon and shimmer from a third, and since #14906 the follow-scroll from a fourth. Nothing tied them together; they agreed only because each was written to agree. Thirteen of the seventeen review findings on #14873 were instances of one derivation being updated and another left behind, and #14892 added more. `resolveToolCallPhase` is now the single source: one function encoding the precedence rules, each of which a specific review finding established, returning `running | completed | cancelled | failed`. Everything the card shows reads that value. `ProgressText` takes `phase` in place of the `error` + `errorSuffix` pair, which encoded three terminal states in two booleans — `error` meant cancelled, a present `errorSuffix` meant failed — and made every consumer reconstruct the distinction. That shape is precisely what let a duration render beside "failed" (Codex round 1 on #14892). Two things fell out once the state had one home, both dead code rather than deletions of behaviour: - `progress` left `ProgressText` entirely; the phase already carries everything it was used to decide. - The `useProgress` mask went with it. Passing 1 in still matters — it stops the 200ms interval — but masking the output no longer does, because the phase treats an explicit close as terminal outright. The "both halves are load-bearing" subtlety is now one half. Scope: the nine cards that render the shared `ProgressText`. The three with bespoke layouts (`WebSearch`, `SubagentCall`, `OpenAIImageGen`) still resolve their own state and are the natural follow-up — they can adopt the resolver without adopting the component. Refactor-only. 4891/4891 client tests pass unchanged, including the suites that encode the cancelled/failed precedence in both directions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5 * 🐛 fix: Infer Cancellation From Reported Progress, Not The Animation `useProgress` holds below 1 for ~200ms after a call reports completion: it emits the previous value, then `0.99`, then `1` on a timeout. The resolver read that animated value for its cancellation inference, so a successful call whose submission ended inside that window rendered — and announced — as "Cancelled". The input is now split. `reportedProgress` is what the stream said and drives the inference; `displayProgress` is the animated value and drives `running` vs `completed`, so the label and shimmer still follow the animation rather than snapping. This restores `ToolCall` and `RetrievalCall`, whose previous predicates used `initialProgress` and were immune, and additionally fixes `useToolCallState`, which inferred from `rawProgress` and therefore carried the bug already — every card the hook backs was exposed to it before this PR. Three tests cover the window: a reported-complete call mid-settle is `running`, a genuinely unfinished one is still `cancelled`, and the card settles to `completed` without a cancelled frame in between. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5 * 🧹 chore: Drop Unused Phase Predicates; Correct A Stale Comment `isFailedPhase` and `isRunningPhase` had no callers — every consumer compares the phase directly, which reads better than a wrapper. An unused abstraction is the thing this PR argues against, so it should not ship one. The comment above the hook's resolver call still described "the raw progress the legacy heuristic was written against", which stopped being true when the input split into reported and display progress. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5 --------- Co-authored-by: Claude <noreply@anthropic.com>
A batched `ask_user_question` interrupt rendered every question stacked in one scrolling form, which reads as a wall on mobile and desktop alike. Show one question per step instead, with clickable progress dots, Back/Next, and Submit only on the last step. The batch contract is untouched: one interrupt, one answer map, Submit still gated on every question having an answer, Skip still declines the whole batch from any step. Single-question batches render exactly as before.
* ci: codegraph test-selection probe (observe-only) Asks the codegraph service which test files and matrix jobs the PR needs and writes the decision to the job summary. Gates nothing — every path exits 0, forks without secrets no-op. Companion to the shadow-mode evaluation: the decision CI would act on, made visible next to the runs it would have replaced. * chore: remove GitNexus CI and deployment configs Superseded by the codegraph service: the index workflow spent ~45min per invocation building an artifact the PR flow never served, while the replacement indexes incrementally in ~1.4s per commit server-side. Removes the four workflows (index, deploy, cleanup-pr, pr-command) and the .do/gitnexus deployment bundle. No remaining references. * ci: render playwright spec tiers in the codegraph probe summary
* feat: Add graph subagent integration * style: Sort response usage test imports * fix: Preserve lazy graph runtime context * fix: Use isolated graph input helper * test: Align graph integration fixtures * fix: Preserve lazy graph runtime capabilities * fix: Bound lazy graph metadata preload * fix: Harden lazy graph resolution lifecycle * fix: Coalesce lazy graph member resolution * fix: Snapshot initialized graph members only * fix: Preserve lazy agent runtime context * fix: Preserve batched lazy context preparation * fix: Preserve graph member capability bounds * fix: reconcile graph subagents with execution profiles * style: align graph subagent types with formatter
* ✨ feat: Custom Request Headers For Langfuse
Self-hosted Langfuse behind an authenticating proxy or gateway could not
be reached: every outbound Langfuse request hardcoded `Authorization` and
nothing else. Adds `langfuse.headers`, mirroring `endpoints.custom`
headers, and applies it to all four request surfaces — trace/media export
(via the agents run config), feedback scores, central project-identity
lookup, and admin credential verification.
Values resolve through the same pipeline as endpoint headers, so
`${ENV_VAR}` interpolation and header-safe encoding come along.
`extractEnvVariable` continues to refuse infrastructure secrets, so a
config cannot exfiltrate `MONGO_URI` through a header. A header whose
variable is unset is dropped with a one-time warning rather than sent as
a literal `${...}`, which a gateway would read as a wrong credential
instead of a missing one.
Headers merge beneath LibreChat's own `Authorization` on the REST
surfaces, matching `mergeHeaders`, so a custom header can never displace
the Langfuse credential.
These are deployment-level and documented as such: trace export batches
spans from every user through a single exporter, so unlike endpoint
headers they cannot carry per-user placeholders.
The central project-id cache key now includes the headers, so the
header-less module warm-up cannot record a proxy rejection against the
entry the request path later reads.
* 🔒 fix: Keep Langfuse Headers Out Of Stored Overrides
The generic admin config API accepts any field path inside an allowed
section, so `langfuse.headers` could be written through it. Unlike
`langfuse.secretKey`, headers are a map rather than one scalar path, so
the config secret registry cannot encrypt them at rest or mask them on
read — an admin-written map would sit in Mongo in plaintext and come
back in plaintext, widening exposure of what are gateway credentials.
Rejects them on both the dotted-patch and object-upsert routes, the same
way process-backed MCP servers are held to librechat.yaml. This is what
makes "deployment-level" true rather than merely documented.
* 🐛 fix: Wire Config Middleware And Header Collisions For Langfuse
Two codex review findings.
P1 — `api/server/routes/admin/langfuse.js` never mounted
`configMiddleware`, so `req.config` was undefined in production and
credential verification silently ran without the deployment's proxy
headers: exactly the deployments this feature targets could not save a
connection. The handler unit tests injected `config` into their mock
requests, so they stayed green. Mounts the middleware after the access
checks (unauthorized callers still short-circuit first) and adds
route-level tests that assert the handler actually receives a resolved
config — the composition root, not the component.
P2 — spreading custom headers under `Authorization` only replaced an
exact-case collision. A configured `authorization` survived alongside
the managed `Authorization` and fetch appends rather than replaces,
sending both credentials in one combined value. All four request sites
now use `mergeHeaders`, which already merges case-insensitively with the
override winning; tests cover the lower- and upper-case variants.
* 🔒 fix: Mask Langfuse Headers On Read And Harden Value Handling
Three codex round-2 findings.
P1 — the write guard blocked storing `langfuse.headers` in Mongo but did
nothing for the read path: `GET /api/admin/config/base` serves the
resolved AppConfig through `redactConfigSecrets`, which only knows
registered scalar secrets, so a yaml-configured gateway credential was
returned in full to any admin with Langfuse read access. Adds a
secret-map registry that masks values while keeping key names, so an
admin can still see which headers a deployment sets. Masking is safe
precisely because these are yaml-only — a masked read cannot be
round-tripped back over the real values. A malformed non-object value at
that path is dropped rather than serialized.
P2 — `mergeHeaders` indexes one spelling per lowercase name, so a config
holding both `authorization` and `AUTHORIZATION` had only one displaced;
the survivor was then appended by `Headers` into a combined value. Case
variants are now collapsed at resolution, before any consumer sees them.
P2 — `resolveHeaders` encodes only values it substitutes a user field
into, and no user is supplied here, so a literal or interpolated
character above U+00FF reached `Headers` unencoded and threw. Final
values now go through `encodeHeaderValue`; Latin-1 still passes verbatim.
* 🔒 fix: Keep Langfuse Header Credentials Out Of Logs And Validate Names
Three codex round-3 findings, plus a documented boundary for the fourth.
P1 — `loadCustomConfig` logs the parsed config at startup (`printConfig`
defaults true), so a literal gateway credential in `langfuse.headers` was
copied into application logs on every boot, undoing the masking the admin
read path had just gained. The printed copy now goes through
`redactConfigSecretMaps`, reusing the same registry. Scoped to map-valued
secrets so scalar-secret log behavior is unchanged; the live config keeps
its real values.
P2 — a nonempty but invalid field name (` X-Token`, `X Proxy Token`)
passed the emptiness check and then threw in the `Headers` constructor,
which would break export, verification, lookup, and feedback for the whole
deployment rather than that one header. Names are trimmed and validated
against the RFC 7230 token grammar, and dropped with a warning otherwise.
P2 — unresolved `${VAR}` detection tested the *resolved* value, so a
credential legitimately containing `${...}` was mistaken for a failed
substitution and dropped. Detection now inspects the configured text and
checks the referenced variables directly, which also drops references to
denylisted infrastructure secrets instead of forwarding them verbatim.
The fourth (fanout gateway forwards only `Authorization`, so a tenant
Langfuse behind its own proxy is not covered) is a real limitation in a
separate component. Documented on the schema field and in the example
config rather than left implied.
* 🔒 fix: Scope Langfuse Headers To Configured Origins
Three codex round-4 findings.
P1 — one header map was attached to every destination a run resolves to.
Under fanout that means a credential meant for an internal gateway was
also sent to the central destination, typically Langfuse Cloud: an
unrelated third-party origin. Headers are now attached only when the
destination's origin is one the deployment explicitly configured (a
self-hosted base URL, the fanout collector, or a tenant destination set
by env). The built-in `*.cloud.langfuse.com` defaults are excluded
precisely because nobody pointed at them. For trace export this also
means attaching after the export branch settles on a `baseUrl` rather
than before, since which destination wins depends on the branch.
P2 — `encodeHeaderValue` only encodes above U+00FF, so a newline, CR, or
NUL passed through and threw in `Headers`, breaking every request rather
than the one header. Values are trimmed (the common trailing-newline
case) then validated against the legal field-value bytes; an embedded
CRLF is a request-splitting attempt and is dropped, not stripped.
P2 — the write guard matched only the exact `headers` property, so
`{ langfuse: { "headers.X-Token": "..." } }` and root-level dotted
variants slipped through into the Mixed overrides document, where the
nested-map redactor never walks them and a later read returns them in
plaintext. All dotted spellings are now rejected.
* 🔒 fix: Bind Langfuse Headers To One Configured Origin
Four codex round-5 findings.
P1 — the round-4 allowlist still authorized every configured origin, so a
deployment with both a collector and an explicit central host sent the
same credential to both. `langfuse.headers` is one map with no way to say
which endpoint it authenticates to, so it is only unambiguous when the
deployment configures exactly one Langfuse origin. Iterating on which
origins to guess was the wrong axis; headers are now sent only when there
is a single configured origin and the destination is it, with a warning
when several make the intent unresolvable. That covers the self-hosted
case this feature exists for; multi-destination deployments need
per-destination headers the schema cannot yet express.
P1 — `fetch` defaults to following redirects, and Node strips
`Authorization` across origins but keeps arbitrary headers, so a redirect
off an allowed origin would hand the gateway credential to a host that
passed no check. Requests carrying custom headers now refuse redirects;
requests without them keep the default, so nothing changes for existing
deployments.
P2 — `extractEnvVariable`'s whole-string branch is anchored and greedy, so
`${CLIENT_ID}:${CLIENT_SECRET}` parsed as one variable name and the raw
template was sent as the credential. References are expanded here now, so
only literal values reach that path.
P2 — a valid token name is not necessarily usable: `Transfer-Encoding`
makes `fetch` throw and a fixed `Content-Length` misdescribes the body of
every other request sharing the map. Request-framing names are dropped.
* 🐛 fix: Expand Langfuse Header References Exactly Once
Codex round 6 (P2). After expanding `${VAR}` references myself I still
handed the result to `resolveHeaders`, which runs `extractEnvVariable`
over it again — so a credential containing `${PATH}`, or any other name
that happens to be set, was silently rewritten on export, verification,
lookup, and feedback. The round-3 test only used an *unset* embedded
name, which the second pass leaves alone, so it could not catch this.
Resolution no longer round-trips through `resolveHeaders`. The only part
still wanted from it was stripping `{{...}}` user placeholders, which is
now applied directly; expansion, encoding, and validation were already
local. Adds a test whose embedded variable is set, which fails against
the previous pipeline.
* 🐛 fix: Process Langfuse Header Templates Before Substitution
Codex round 7 (P2), the mirror of round 6. Having stopped re-expanding
the resolved credential, the placeholder strip was still running over it:
a token containing `{{LIBRECHAT_USER_ID}}` had that span deleted and
`abc{{...}}ghi` went out as `abcghi`.
Establishes the invariant the last two rounds were circling. Every
template operation — placeholder strip, unresolved-reference check,
expansion — now runs on the operator's configured text, and the
credential is substituted last and never touched again. Gateway
credentials are arbitrary strings, so none of their bytes are syntax.
Also moves the unresolved-reference check after the strip, so it no
longer reports a variable inside a `{{...}}` span that the strip removes.
* fix: make the shared chat language dropdown scrollable and use available height The language dropdown in the shared chat settings dialog could not be scrolled with the wheel and was capped at 256px, so most of the language list was unreachable. Radix wraps the dialog overlay in RemoveScroll with its shards limited to DialogContent, so wheel events over a popover portaled to document.body were cancelled. That same portal placement also left the popover inside Radix's aria-hidden treatment, hiding the whole option list from assistive technology. Render the popover inside the dialog and let that dialog's content overflow so the popover is not clipped by it. Drop the hardcoded max-height so the popover uses the available height reported by the positioner. This also restores flipping, because the positioner can now see that the natural height overflows and place the popover above the trigger when there is more room there. Remove declarations that never took effect: max-h-[80vh] and overflow-y-auto on the popover, both shadowed by .popover-ui later in the same stylesheet, and the --anchor-max-height and --anchor-max-width custom properties, which nothing reads. Move the theme and language selectors into their own directory so the public share page no longer imports through the Nav settings tabs. * chore: drop the redundant nested winston entry from the lockfile packages/data-schemas declares winston as a peer dependency of ^3.17.0, which the root winston 3.19.0 already satisfies, so npm deduped the nested 3.17.0 copy. * refactor: give Dropdown separate wrapper, trigger and popover class props className was spread onto three elements at once: the positioning wrapper, the trigger button and the popover. A caller styling the trigger silently restyled the popover as well, and because className was merged after sizeClasses it also beat the popover's own sizing. LangfuseConnection asked for a popover the width of its anchor and got a full width one instead. className now applies to the wrapper only, triggerClassName styles the trigger and sizeClasses continues to style the popover. Call sites that relied on the old spread pass the class to the part that needs it, so the rendered result is unchanged apart from the LangfuseConnection width. Also add portalElement so a caller can render the popover into a specific container rather than document.body. * fix: align the packaged popover radius with the app stylesheet .popover-ui is declared both in the component's own stylesheet and in the app's, and the two had drifted: the packaged copy used a 1rem radius while the app used 0.7rem. The app copy wins inside LibreChat, so consumers of @librechat/client saw a different corner radius from the app itself. * fix: keep the shared chat settings dialog scrollable The dialog content was made overflow visible so the language popover would not be clipped, which meant the dialog itself could no longer scroll. If it ever grew past the viewport its content would have been unreachable. Move the scroll onto an inner region and portal the popover into the dialog content, outside that region. The popover still sits inside DialogContent, so it stays within the scroll lock shard and out of the aria-hidden subtree, while the rows above it can scroll on their own. * style: format the locales README Applies the repository Prettier style, which the file did not satisfy. Formatting only, no content changes. * chore: remove the unused DropdownNoState component The file defined a HeadlessUI based dropdown that nothing imported. It was absent from the package barrels and from the generated type declarations, so it was never part of the published API and no consumer can be relying on it. It carried the same defect the Ariakit Dropdown just had, spreading className onto the wrapper, the trigger and the popover, so deleting it is preferable to fixing code that never runs. * fix: declare the dependencies packages/client imports InputNumber imports the ValueType type from @rc-component/mini-decimal and the generated declarations re-export that import, but the package never declared it. It resolved only because npm hoists it as a transitive dependency of rc-input-number, so a consumer on a strict or nested layout would fail to resolve the type. Declare it as a peer alongside the other externals, using the same range rc-input-number asks for. The theme test requires tailwindcss directly, so add it to devDependencies rather than relying on hoisting there too. Also mark the ValueType import as a type import, matching the convention used elsewhere. * style: group the ValueType import with the package imports Type-only imports belong before local imports, as in Avatar.tsx.
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 : )