Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c7e8b45
🪶 refactor: Polish Event Subagent Activity (#15152)
danny-avila Aug 24, 2026
092bc58
📭 fix: Detect Agent List Pages in `useHasData` (#15156)
jomspk Aug 24, 2026
8773b36
🎽 fix: Commit Subagent Roster Selections to Form State (#15154)
danny-avila Aug 24, 2026
c52ba4e
fix: restore provider typing against the Agents SDK declarations (#15…
danny-avila Aug 24, 2026
b6e3cf4
🕯️ fix: Decay Violation Scores With a Configurable TTL (#15153)
danny-avila Aug 24, 2026
6a7da61
🥸 chore: Resolve Agents SDK Path Aliases That Masked Backend Types (#…
danny-avila Aug 24, 2026
18cc471
chore: bump agents sdk to v3.7.0 (#15163)
danny-avila Aug 24, 2026
f10fcd7
🎰 ci: Vote on the Full Mock Suite to End Phantom Spec Trials (#15162)
danny-avila Aug 24, 2026
bf1e13b
🥁 fix: Compare TOTP Codes in Constant Time (#15157)
pacocartones Aug 24, 2026
6f3d303
📍 ci: Pin the Votes Ledger Results JSON to an Absolute Path (#15166)
danny-avila Aug 24, 2026
5a87006
⚡ perf: Build the Memory Message Copy Only When Something Reads It (#…
danny-avila Aug 24, 2026
e0d5e11
⏱️ feat: Show Elapsed Time Under the Streaming Response (#15167)
danny-avila Aug 24, 2026
a997275
🧾 feat: Persist Authoritative Subagent Control Receipts (#15168)
danny-avila Aug 24, 2026
f9c051f
🪶 feat: Support Non-Persistent Controlled Themes (#15170)
danny-avila Aug 24, 2026
3df046f
🎓 ci: Graduated E2E Spec Skipping, Wired Dark Until Armed (#15172)
danny-avila Aug 24, 2026
649e681
🖼️ refactor: Consolidate Provider Icons Into a Single Registry (#15148)
danny-avila Aug 24, 2026
afcf2e8
📦 chore: bump `@librechat/agents@latest` to v3.7.1 (#15176)
danny-avila Aug 24, 2026
d641c39
🧳 fix: Port Subagent Control Receipt Writes to DocumentDB-Safe Operat…
danny-avila Aug 25, 2026
69e7c73
🎛️ feat: Expose Authoritative Subagent Controls (#15169)
danny-avila Aug 25, 2026
6988ff5
✂️ fix: Unclip the Share Dialog's Public Role Menu (#15177)
danny-avila Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,9 @@ BAN_VIOLATIONS=true
BAN_DURATION=1000 * 60 * 60 * 2
BAN_INTERVAL=20

# Violation scores expire after this long (in ms) without new violations; 0 = never expire
VIOLATION_SCORE_TTL=1000 * 60 * 60

LOGIN_VIOLATION_SCORE=1
REGISTRATION_VIOLATION_SCORE=1
CONCURRENT_VIOLATION_SCORE=1
Expand Down
129 changes: 55 additions & 74 deletions .github/workflows/codegraph-e2e-votes.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
# Codegraph e2e VOTES — observe-only, post-merge, time-boxed.
#
# Playwright never runs on pushes to dev, so evidence for the e2e skip election would
# otherwise wait on rare organic PR spec failures. This workflow runs EXACTLY the skippable
# tier the merged PR's selection computed — every merge becomes a direct trial of "would
# skipping these specs have missed a failure". A green run is a confirmation vote; a failing
# spec here is a tier-miss vote counted AGAINST enabling skipping. The shadow evaluator on
# the codegraph droplet harvests these runs and attributes them back to the merged PR.
# otherwise wait on rare organic PR spec failures. This workflow runs the FULL mock suite on
# every merge: each run is one graduation trial for every spec it executes, and doubles as
# the post-merge safety net the jest workflows already have via their dev-push triggers.
#
# It cannot fail the branch: the tier lookup exits 0 on every path and the test step is
# continue-on-error. The newest merge cancels older vote runs. The whole campaign switches
# off by setting repo variable CODEGRAPH_E2E_VOTES=off once the election passes.
# It previously ran only the merged PR's skippable tier, passing the tier as CLI path
# filters. playwright.config.mock.ts scopes discovery to testDir specs/mock/, so tier
# entries outside that directory matched nothing — and the covered-list log line still
# claimed them, minting graduation trials for specs that never executed (run 32701691037:
# a11y/keys/messages in the covered list, zero of their tests run). The covered list below
# is therefore derived from the run's EXECUTED results — discovery is not enough either,
# since env-gated suites self-skip under this job's default env — and the run takes no
# path filters at all.
#
# It cannot fail the branch: the test step is continue-on-error. The newest merge cancels
# older vote runs. The whole campaign switches off by setting repo variable
# CODEGRAPH_E2E_VOTES=off once the election passes.
name: Codegraph E2E Votes

on:
Expand All @@ -20,6 +27,7 @@ on:
- '**'
- '!**.md'
- '!.github/workflows/**'
- '.github/workflows/codegraph-e2e-votes.yml'

permissions:
contents: read
Expand All @@ -34,67 +42,23 @@ env:

jobs:
vote:
name: vote (skippable tier)
name: vote (full suite)
if: vars.CODEGRAPH_E2E_VOTES != 'off'
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 45
env:
CI: 'true'
E2E_CHROMIUM_CHANNEL: chrome
E2E_STREAM_STORE: memory
steps:
- uses: actions/checkout@v5

- name: Ask codegraph for this merge's skippable tier
id: tiers
env:
URL: ${{ secrets.CODEGRAPH_URL }}
TOKEN: ${{ secrets.CODEGRAPH_TOKEN }}
GH_TOKEN: ${{ github.token }}
run: |
set +e
N=0
if [ -n "$URL" ] && [ -n "$TOKEN" ]; then
gh api "repos/${{ github.repository }}/commits/${{ github.sha }}" \
--jq '[.files[] | {path: .filename, status: .status}]' > files.json 2>/dev/null
if [ -s files.json ]; then
jq -c '{files: .}' files.json > body.json
RESP=$(curl -sS -m 45 -H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' --data-binary @body.json "$URL/v1/select")
# fail_open reflects the JEST floors (root config, lockfile, stale graph); the
# e2e tiers come from the testid bridge and are valid whenever they computed at
# all. The old fail-open skip silently excused exactly the big backend merges
# whose trials matter most (LibreChat#14957's merge produced no vote because
# api/package.json tripped the jest floor). Tiers present => vote.
if ! echo "$RESP" | jq -e '.e2e.skippable' >/dev/null 2>&1; then
echo "codegraph unavailable or no tiers; skipping"
else
echo "$RESP" | jq -r '.e2e.skippable[]' | sed 's|^e2e/||' > skippable.txt
N=$(wc -l < skippable.txt | tr -d ' ')
fi
else
echo "could not read merge commit files; skipping"
fi
else
echo "no codegraph config; skipping"
fi
echo "codegraph-votes: running $N skippable specs"
# The exact list, one log line: the shadow's per-spec graduation ledger counts a clean
# trial for every spec a green vote run covered, and until this line existed it had to
# approximate coverage from the decision event's tier (drift: the tier is recomputed
# here at the merge commit against a possibly newer graph head).
if [ "$N" != "0" ]; then echo "codegraph-votes-specs: $(tr '\n' ' ' < skippable.txt)"; fi
echo "count=$N" >> "$GITHUB_OUTPUT"
exit 0

- name: Use Node.js 24.16.0
if: steps.tiers.outputs.count != '0'
uses: actions/setup-node@v5
with:
node-version: '24.16.0'

- name: Restore node_modules cache
if: steps.tiers.outputs.count != '0'
id: cache-node-modules
uses: actions/cache@v5
with:
Expand All @@ -109,100 +73,92 @@ jobs:
key: node-modules-e2e-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }}

- name: Install dependencies
if: steps.tiers.outputs.count != '0' && steps.cache-node-modules.outputs.cache-hit != 'true'
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci

- name: Restore data-provider build cache
if: steps.tiers.outputs.count != '0'
id: cache-data-provider
uses: actions/cache@v5
with:
path: packages/data-provider/dist
key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}

- name: Build data-provider
if: steps.tiers.outputs.count != '0' && steps.cache-data-provider.outputs.cache-hit != 'true'
if: steps.cache-data-provider.outputs.cache-hit != 'true'
run: npm run build:data-provider

- name: Restore data-schemas build cache
if: steps.tiers.outputs.count != '0'
id: cache-data-schemas
uses: actions/cache@v5
with:
path: packages/data-schemas/dist
key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}

- name: Build data-schemas
if: steps.tiers.outputs.count != '0' && steps.cache-data-schemas.outputs.cache-hit != 'true'
if: steps.cache-data-schemas.outputs.cache-hit != 'true'
run: npm run build:data-schemas

- name: Restore api build cache
if: steps.tiers.outputs.count != '0'
id: cache-api
uses: actions/cache@v5
with:
path: packages/api/dist
key: build-api-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }}

- name: Build api
if: steps.tiers.outputs.count != '0' && steps.cache-api.outputs.cache-hit != 'true'
if: steps.cache-api.outputs.cache-hit != 'true'
run: npm run build:api

- name: Restore client-package build cache
if: steps.tiers.outputs.count != '0'
id: cache-client-package
uses: actions/cache@v5
with:
path: packages/client/dist
key: build-client-package-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}

- name: Build client-package
if: steps.tiers.outputs.count != '0' && steps.cache-client-package.outputs.cache-hit != 'true'
if: steps.cache-client-package.outputs.cache-hit != 'true'
run: npm run build:client-package

- name: Restore client app build cache
if: steps.tiers.outputs.count != '0'
id: cache-client-app
uses: actions/cache@v5
with:
path: client/dist
key: build-client-app-e2e-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'client/src/**', 'client/public/**', 'client/scripts/post-build.cjs', 'client/index.html', 'client/package.json', 'client/vite.config.*', 'client/tsconfig*.json', 'client/tailwind.config.*', 'client/postcss.config.*', 'packages/client/src/**', 'packages/client/tailwind.preset.cjs', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}

- name: Build client app
if: steps.tiers.outputs.count != '0' && steps.cache-client-app.outputs.cache-hit != 'true'
if: steps.cache-client-app.outputs.cache-hit != 'true'
run: npm run build:client

- name: Verify Chrome is present
if: steps.tiers.outputs.count != '0'
run: google-chrome --version

# ffmpeg for retry video — see the note in playwright-mock.yml.
- name: Resolve Playwright version
id: playwright-version
if: steps.tiers.outputs.count != '0'
run: |
version=$(node -p "require('./package-lock.json').packages['node_modules/playwright-core'].version")
echo "version=${version}" >> "$GITHUB_OUTPUT"

- name: Restore Playwright ffmpeg cache
id: cache-ffmpeg
if: steps.tiers.outputs.count != '0'
uses: actions/cache/restore@v5
with:
path: ~/.cache/ms-playwright
key: playwright-ffmpeg-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}

- name: Install Playwright ffmpeg (best effort)
id: install-ffmpeg
if: steps.tiers.outputs.count != '0' && steps.cache-ffmpeg.outputs.cache-hit != 'true'
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
timeout-minutes: 3
continue-on-error: true
run: |
timeout -k 10 60 npx playwright install ffmpeg
.github/scripts/verify-playwright-ffmpeg.sh

- name: Save Playwright ffmpeg cache
if: steps.tiers.outputs.count != '0' && steps.install-ffmpeg.outcome == 'success'
if: steps.install-ffmpeg.outcome == 'success'
continue-on-error: true
uses: actions/cache/save@v5
with:
Expand All @@ -211,15 +167,40 @@ jobs:

# Optional fonts only — see the note in playwright-mock.yml.
- name: Install optional Playwright font dependencies (best effort)
if: steps.tiers.outputs.count != '0'
timeout-minutes: 4
continue-on-error: true
run: .github/scripts/install-playwright-fonts.sh

- name: Vote — run the skippable tier (cannot fail the branch)
if: steps.tiers.outputs.count != '0'
- name: Vote — run the full mock suite (cannot fail the branch)
continue-on-error: true
run: npx playwright test --config=e2e/playwright.config.mock.ts $(tr '\n' ' ' < skippable.txt)
env:
# Absolute on purpose: Playwright resolves a relative PLAYWRIGHT_JSON_OUTPUT_NAME
# against the CONFIG directory (e2e/), not the working directory — the first live run
# wrote e2e/pw-results.json while the ledger looked in the repo root and logged zero
# trials (fail-safe, but a silent no-op).
PLAYWRIGHT_JSON_OUTPUT_NAME: ${{ github.workspace }}/pw-results.json
run: npx playwright test --config=e2e/playwright.config.mock.ts --reporter=line,json

- name: Ledger — log the specs that actually executed
run: |
set +e
# The shadow's per-spec graduation ledger counts a clean trial for every spec a green
# run covered, so the covered list must come from EXECUTED tests, not from discovery:
# env-gated suites (mcp-tool-list-changed needs E2E_MCP_LIST_CHANGED, enforced-model-
# specs needs E2E_MODEL_SPECS_ENFORCE) are discovered by --list yet skip every test
# under this job's default env — counting them as covered would mint phantom trials,
# the exact bug this workflow revision exists to kill (Codex P1 on #15162). A spec is
# covered iff at least one of its tests reached a non-skipped outcome.
if jq -e '.suites' "$GITHUB_WORKSPACE/pw-results.json" >/dev/null 2>&1; then
jq -r '[.suites[] | recurse(.suites[]?) | .specs[]? | select([.tests[]?.status] | any(. != "skipped")) | .file] | unique | .[]' "$GITHUB_WORKSPACE/pw-results.json" \
| sed 's|^|specs/mock/|' > covered.txt
N=$(wc -l < covered.txt | tr -d ' ')
echo "codegraph-votes: running $N specs (executed, full suite)"
if [ "$N" != "0" ]; then echo "codegraph-votes-specs: $(tr '\n' ' ' < covered.txt)"; fi
else
echo "codegraph-votes: no results json — run crashed before reporting; no trials logged"
fi
exit 0

- name: Done
if: always()
Expand Down
47 changes: 46 additions & 1 deletion .github/workflows/playwright-mock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
decided: ${{ steps.sel.outputs.decided }}
e2e_include: ${{ steps.sel.outputs.e2e_include }}
mcp_run: ${{ steps.sel.outputs.mcp_run }}
e2e_skip: ${{ steps.sel.outputs.e2e_skip }}
steps:
- name: Select matrix lanes, fail open on any doubt
id: sel
Expand All @@ -61,6 +62,7 @@ jobs:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
CHANGED: ${{ github.event.pull_request.changed_files }}
E2E_SKIP_ARMED: ${{ vars.CODEGRAPH_E2E_SKIP }}
FULL_INCLUDE: '{"include":[{"name":"memory, shard 1/3","stream_store":"memory","redis_image":"","suite":"full","shard":"1/3","artifact":"memory-1-of-3"},{"name":"memory, shard 2/3","stream_store":"memory","redis_image":"","suite":"full","shard":"2/3","artifact":"memory-2-of-3"},{"name":"memory, shard 3/3","stream_store":"memory","redis_image":"","suite":"full","shard":"3/3","artifact":"memory-3-of-3"},{"name":"redis transport","stream_store":"redis","redis_image":"redis:7-alpine","suite":"transport","shard":"","artifact":"redis-transport"}]}'
run: |
set +e
Expand Down Expand Up @@ -115,6 +117,25 @@ jobs:
exit 0
fi
echo "e2e_include=$INCLUDE" >> "$GITHUB_OUTPUT"
# Graduated per-spec skips are DARK until the operator arms repo variable
# CODEGRAPH_E2E_SKIP=on (the election switch — flipped only when the pre-registered
# resume condition holds). Even then, act only on a well-typed list from a non-fail-open
# decision: every entry must be a pool spec path, or nothing is skipped. The server
# already intersects with this PR's skippable tier and applies the streak bars
# (2x where history-coupled); see codegraph-poc service/graduate.ts.
SKIP=""
if [ "$E2E_SKIP_ARMED" = "on" ]; then
if echo "$RESP" | jq -e '(.e2e.fail_open != true) and (.e2e.graduated | type == "array" and all(.[]?; type == "string" and test("^e2e/specs/mock/[A-Za-z0-9._/-]+\\.spec\\.ts$") and (contains("..") | not)))' >/dev/null 2>&1; then
SKIP=$(echo "$RESP" | jq -r '[.e2e.graduated[] | sub("^e2e/"; "")] | join(" ")')
else
note "_graduated list absent or malformed; no specs skipped_"
fi
fi
echo "e2e_skip=$SKIP" >> "$GITHUB_OUTPUT"
if [ -n "$SKIP" ]; then
note "| graduated spec skips | $(echo "$SKIP" | wc -w | tr -d ' ') (armed) |"
echo "codegraph-e2e-graduated-skips: $SKIP"
fi
echo "codegraph-select: redis_transport=$REDIS mcp_tool_list_changed=$MCP matrix_entries=$(echo "$INCLUDE" | jq '.include | length')"
if [ "$MCP_SKIP" = 1 ]; then
echo "mcp_run=false" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -305,9 +326,33 @@ jobs:

- name: Run full mock-LLM Tier-1 e2e
if: matrix.suite == 'full'
run: npx playwright test --config=e2e/playwright.config.mock.ts --shard=${{ matrix.shard }}
env:
CI: 'true'
E2E_SKIP: ${{ needs.codegraph_select.outputs.e2e_skip }}
run: |
set +e
# Graduated-spec skipping (dark until repo var CODEGRAPH_E2E_SKIP=on upstream): subtract
# the earned skips from a run list derived from the tree itself, so an unknown or stale
# name in the skip list simply matches nothing. If subtraction would drop everything —
# or drops nothing — run the full shard exactly as before. Skipped specs still execute
# post-merge in every full-suite vote run, which is the net that catches a wrong skip.
RUN_ARGS=""
if [ -n "$E2E_SKIP" ]; then
KEEP=""; DROP=0
for spec in $(git ls-files 'e2e/specs/mock/*.spec.ts' 'e2e/specs/mock/**/*.spec.ts' | sed 's|^e2e/||' | sort -u); do
case "$spec" in *" "*) KEEP="$KEEP $spec"; continue;; esac
case " $E2E_SKIP " in
*" $spec "*) DROP=$((DROP+1));;
*) KEEP="$KEEP $spec";;
esac
done
if [ "$DROP" -gt 0 ] && [ -n "$KEEP" ]; then
RUN_ARGS="$KEEP"
echo "codegraph-e2e-skip: dropped $DROP graduated specs from this shard's pool"
fi
fi
set -e
npx playwright test --config=e2e/playwright.config.mock.ts --shard=${{ matrix.shard }} $RUN_ARGS

- name: Run Redis stream transport e2e
if: matrix.suite == 'transport'
Expand Down
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@azure/storage-blob": "^12.30.0",
"@google/genai": "^2.8.0",
"@keyv/redis": "5.1.6",
"@librechat/agents": "^3.6.16",
"@librechat/agents": "^3.7.1",
"@librechat/api": "*",
"@librechat/data-schemas": "*",
"@microsoft/microsoft-graph-client": "^3.0.7",
Expand Down
Loading
Loading