[pull] main from CopilotKit:main#296
Merged
Merged
Conversation
Three classes of legacy URLs were 404'ing because shell-docs (with BIA as the soft-default framework) doesn't serve them at the root surface the old docs did: 1. BIA-canonical pages — /server-tools, /mcp-servers, /model-selection, /advanced-configuration, /agent-app-context live only under /built-in-agent/ now. Internal sidebar clicks already framework-scope via SidebarLink; external traffic (marketing, blog posts, bookmarks) was 404'ing. 2. Moved root pages — /mcp-apps (moved to /generative-ui/), /copilot-runtime, /custom-agent (moved to /backend/), /deep-agents (renamed to /deepagents), /multi-agent-flows (LangGraph-only), /custom-look-and-feel folder index, /generative-ui/specs/* (specs subgroup retired), plus assorted misc (/what-is-copilotkit, /getting-started/quickstart-chatbot, /telemetry, /migration-guides/*, /reference/hooks/useCoAgent). 3. Legacy /integrations/<fw>/* prefix — R15/R17 already handled the built-in-agent variant; this extends the same pattern to every other framework, mirroring the existing /docs/integrations/* coverage. All redirect destinations verified to return 200 against a local dev build; existing tests still pass.
## What does this PR do? Fixes #4995 in the Python SDK by making `LangGraphAGUIAgent.dict_repr()` build its metadata directly instead of calling `super().dict_repr()`. ### Problem description `CopilotKitRemoteEndpoint.info()` crashed with `AttributeError: 'super' object has no attribute 'dict_repr'` whenever a registered agent was a `LangGraphAGUIAgent`. That made the runtime info endpoint unusable for LangGraph CoAgent setups. ### Root cause `LangGraphAGUIAgent` inherits from AG-UI's `LangGraphAgent`, and that upstream base class does not implement `dict_repr()`. The existing implementation called `super().dict_repr()`, so Python resolved to `object` and raised `AttributeError`. ### Fix - Replace the `super().dict_repr()` call with an explicit metadata dict built from `self.name` and `self.description` - Preserve the existing CopilotKit-specific `type: "langgraph_agui"` field - Add regression coverage for both `dict_repr()` and `CopilotKitRemoteEndpoint.info()` ## Related PRs and Issues - Fixes #4995 ## Testing - [x] `python3 -m pytest sdk-python/tests/test_agui_agent.py -q` - [x] `python3 -m pytest sdk-python/tests/test_agui_agent.py sdk-python/tests/test_agui_context_serializable.py sdk-python/tests/test_emit_filtering.py -q` - [x] `python3 -m pytest sdk-python/tests -q` - [x] Manual verification that `CopilotKitRemoteEndpoint.info()` returns serialized LangGraph AGUI agent metadata without raising ## Checklist - [x] I have read the [Contribution Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md) - [ ] If the PR changes or adds functionality, I have updated the relevant documentation - [x] "Allow edits by maintainers" is checked (lets us help iterate on your PR directly — faster turnaround for everyone)
…ver (#5079) Three classes of legacy URLs were 404'ing because shell-docs (with BIA as the soft-default framework) doesn't serve them at the root surface the old docs did: 1. BIA-canonical pages — /server-tools, /mcp-servers, /model-selection, /advanced-configuration, /agent-app-context live only under /built-in-agent/ now. Internal sidebar clicks already framework-scope via SidebarLink; external traffic (marketing, blog posts, bookmarks) was 404'ing. 2. Moved root pages — /mcp-apps (moved to /generative-ui/), /copilot-runtime, /custom-agent (moved to /backend/), /deep-agents (renamed to /deepagents), /multi-agent-flows (LangGraph-only), /custom-look-and-feel folder index, /generative-ui/specs/* (specs subgroup retired), plus assorted misc (/what-is-copilotkit, /getting-started/quickstart-chatbot, /telemetry, /migration-guides/*, /reference/hooks/useCoAgent). 3. Legacy /integrations/<fw>/* prefix — R15/R17 already handled the built-in-agent variant; this extends the same pattern to every other framework, mirroring the existing /docs/integrations/* coverage. All redirect destinations verified to return 200 against a local dev build; existing tests still pass.
…File content
Fumadocs's Tab component applies escapeValue() internally to the value
prop. Our DocsTab wrapper was also calling escapeValue() before passing
to FumadocsTab, causing multi-word tab values to be escaped twice.
For "JSON Configuration File" (3 words, 2 spaces):
1st escape (our wrapper): "json-configuration file" (1 space left)
2nd escape (Fumadocs Tab): "json-configuration-file" (fully hyphenated)
The trigger value uses only ONE escapeValue call:
escapeValue("JSON Configuration File") = "json-configuration file"
Trigger "json-configuration file" != content "json-configuration-file"
so Radix sets data-state="inactive" on the content panel, which is
then hidden by data-[state=inactive]:hidden.
Fix: remove escapeValue() from our Tab wrapper. The Tabs defaultValue
still needs pre-escaping because FumadocsTabs accepts it as-is (no
internal escape); only the individual Tab has internal escaping.
Single-word and two-word tabs (HTTP, Application Settings) were
unaffected because one escapeValue pass already produces a hyphen-only
string that is idempotent under a second pass. Same bug also affected
"stdio Transport (Local)" in the Windsurf section.
Single-file Ruby (stdlib only) with 9 subcommands for Showcase Railway operations: snapshot, restore, rollback, rollback-commit, promote, pin, env-diff, resolve-digest, lint-prod. - Production protection: --yes + typed 'production' confirmation (--non-interactive skips the prompt but still requires --yes). - Uniform exit codes: 0 clean, 1 drift/findings, 2 error. - GraphQL via backboard.railway.app with serviceInstanceDeployV2. - GHCR digest resolution via Docker-Content-Digest header. - Promote prechecks: service-set parity, critical env-key parity, custom-domain audit; REFUSE on parity miss, WARN on domain drift. - Minitest suite (stdlib) covers CLI parsing, snapshot YAML roundtrip, GHCR digest decision tree, and production prompt. - CI workflow showcase_lint_prod.yml runs lint-prod + tests on every PR that touches showcase/.
Audit of staging + production PocketBase rules turned up two open holes: - users.createRule = "" allowed any anonymous client to POST to /api/collections/users/records and create a real account. The dashboard exposes no signup UX — operators authenticate via the superuser credentials in PbAuthPrompt — so create should be admin-only. - baseline.updateRule = "" (production only) allowed any anonymous client to PATCH baseline rows, including silently flipping status cells and overwriting the updated_by/updated_at audit fields. The dashboard's baseline edit flow already gates writes behind PbAuthPrompt, so locking updateRule to admin-only keeps the existing operator workflow intact while removing the open hole. Both changes were applied via the PocketBase admin API to staging first (verified dashboard still renders and live status SSE still flows), then to production (same verification, plus 403 confirmations on the anonymous signup + anonymous baseline PATCH requests that previously returned 200). All other collection rules already had the right shape: status, status_history, probe_runs, baseline retain listRule/viewRule = "" because the dashboard reads them unauthenticated via the PocketBase JS SDK; create/update/delete rules stayed null because the harness writes with the superuser JWT.
…ent (#5081) ## Problem On [docs.copilotkit.ai/built-in-agent/build-with-agents](https://docs.copilotkit.ai/built-in-agent/build-with-agents), **"JSON Configuration File"** shows a blank panel. <img width="789" height="99" alt="image" src="https://github.com/user-attachments/assets/1f8d8c4a-c5fb-4d59-9d3e-785352a3bf5c" /> ## Why Our `Tab` wrapper called `escapeValue()` before passing to Fumadocs's `FumadocsTab` — which also calls it internally. For 3-word labels, the double-escape produces different values for the trigger vs the content panel, so Radix hides the content. ``` "JSON Configuration File" → trigger (1 pass): json-configuration file ← space → content (2 passes): json-configuration-file ← hyphen (mismatch → hidden) ``` ## Fix Remove `escapeValue()` from the `Tab` wrapper — Fumadocs already handles it. The `Tabs` `defaultValue` still needs pre-escaping since `FumadocsTabs` doesn't apply it internally.
Add --exit-zero flag to bin/railway lint-prod that makes the command exit 0 even when digest-pinning findings exist. Findings still print to stdout so they remain visible in the CI step log. Wire the showcase_lint_prod.yml workflow to pass --exit-zero so the job cannot block PRs while we soak the check against real production state. Once we have confidence the findings are clean, remove --exit-zero from the workflow step to flip lint-prod to enforcing. Updates README to document the advisory-mode behavior and the path to flipping the check to enforcing.
…cky PR comment)
Make the lint-prod audit result legible without having to click into the
workflow logs. Two surfaces, both rendered from the same JSON payload:
1. `$GITHUB_STEP_SUMMARY` — structured markdown block at the top of every
workflow run page. Shows on every event (push, pull_request,
workflow_dispatch).
2. Sticky PR comment — one comment per PR, keyed by the HTML marker
`<!-- lint-prod-sticky-comment -->`. Re-runs update the same comment via
`gh api -X PATCH` instead of creating duplicates. Plain `gh` CLI only,
no third-party action.
Both surfaces show: one-line status, a table of the unpinned services only
(not all 27), and a Pacific-time run timestamp with the finding count.
To support the renderer, add `--format json` to `lint-prod`:
{services:[{name,source,status}], findings:N, timestamp:"ISO8601"}
The workflow consumes this shape and also writes `findings` to
`$GITHUB_OUTPUT` so downstream jobs (future Slack alert) can compare runs.
Idempotent: re-running the workflow finds the existing comment by marker
and PATCHes it — never duplicates.
…errors The lint-prod step crashes today on a pre-existing snapshot bug (GraphQL schema query for a "domains" field that no longer exists on Project). The `--exit-zero` flag only suppresses exit-on-findings — it doesn't catch fatal Ruby errors during snapshot building, so an error short- circuits the whole job and the visibility surfaces never render. Make the workflow truly advisory: - Capture lint-prod stderr + exit code instead of failing the step. - If lint-prod errors (or produces no JSON), synthesize a minimal payload with an "error" field so the renderer has something to chew on. - Renderer detects the error field and emits an "audit unavailable" block with the captured error inside a <details> fold instead of leaving the step summary / sticky comment blank. Snapshot bug itself is out of scope for this change; tracked separately.
The tool was written from a spec but never exercised against live Railway,
so several queries reference fields that do not exist in the public
schema. Live runs (including the CI lint-prod step) failed with errors
like `Cannot query field "domains" on type "Project"`. Unit tests passed
because they only covered parsing and IO, not the GraphQL shape.
Verified the live schema via introspection (2026-05) and corrected
every mismatch:
- Project has no `domains` field. The previous `customDomains: domains
{ customDomains { ... } }` block on the Project selection is gone.
Custom domains now come from `serviceInstance.domains.customDomains`
for the env we are inspecting.
- Service has no `serviceInstances` field. We can no longer enumerate
per-env instances by nesting under Service. The new flow is:
1. SERVICES_LIST_QUERY -> list services in the project
2. SERVICE_INSTANCE_QUERY -> per (service, env), fetch source,
startCommand, latestDeployment, domains
3. ENVIRONMENT_VARIABLES_QUERY -> all variables in the env, then
group keys by Variable.serviceId for per-service env_keys
- `serviceInstanceDeployV2` does not accept an `image` argument; its
signature is (commitSha, environmentId, serviceId). To pin a service
to a specific image we now use
serviceInstanceUpdate(input: { source: { image } })
followed by serviceInstanceRedeploy. RestoreCommand exposes a
pin_and_redeploy class method that PromoteCommand and PinCommand
reuse.
- `deploymentRollback` returns scalar Boolean, so the previous
`deploymentRollback(id: $id) { id }` was invalid GraphQL — selection
sets are not allowed on scalars. Dropped the selection set.
- Auth.token now reads `user.accessToken` from ~/.railway/config.json
first. The legacy `user.token` field is a short CLI session token
(4 chars on a fresh login) that does not authenticate against the
public GraphQL API and was producing silent "Not Authorized" errors.
Added spec/test_snapshot_graphql.rb with a FakeGQL that returns realistic
shapes, plus regression guards that fail the build if anyone reintroduces
`Project.domains`, `Service.serviceInstances`, `serviceInstanceDeployV2`
with an image arg, or a selection set on `deploymentRollback`.
Smoke-tested live (read-only) against the showcase project:
- lint-prod: "OK: all production services digest-pinned." (27 services)
- snapshot --env staging: full YAML with images, startCommands, env_keys
- env-diff staging production: 32 drift findings (expected, staging is
not digest-pinned)
- resolve-digest ghcr.io/copilotkit/showcase-aimock:latest: digest
returned successfully
No mutating subcommands (restore, rollback, promote, pin) were run live.
…prod Fixes zizmor findings on .github/workflows/showcase_lint_prod.yml: - unpinned-uses: pin actions/checkout to the repo-canonical SHA (v4) - unpinned-uses: pin ruby/setup-ruby to v1.310.0 SHA - artipacked: set persist-credentials: false on the checkout step Matches the rest of the repo, which SHA-pins every uses: with a trailing # vX.Y.Z comment for Dependabot to keep current.
…tes (#5083) ## Summary Audit of staging + production PocketBase collection rules turned up two open holes. Both are now closed in both environments and captured as forward/backward migrations under `showcase/pocketbase/pb_migrations/`. ## Findings ### users.createRule = "" (both envs) Any anonymous client could POST to `/api/collections/users/records` and create a real account. The dashboard exposes no signup UX — operators authenticate via the superuser credentials in `PbAuthPrompt` — so create should be admin-only. Verified on prod with curl: ``` POST /api/collections/users/records {anon body} -> 200 (BEFORE) POST /api/collections/users/records {anon body} -> 403 (AFTER) ``` ### baseline.updateRule = "" (prod only — staging has no baseline collection) Any anonymous client could PATCH baseline rows, silently flipping status cells (`works` ↔ `impossible`) and rewriting `updated_by` / `updated_at`. The dashboard's baseline edit flow already gates writes behind `PbAuthPrompt`, so locking `updateRule` to admin-only keeps the existing operator workflow intact. Verified on prod with curl: ``` PATCH /api/collections/baseline/records/<id> {} -> 200 (BEFORE) PATCH /api/collections/baseline/records/<id> {} -> 403 (AFTER) ``` ## Rule matrix (after this change) | Collection | list | view | create | update | delete | Notes | |-----------------|------------------|------------------|--------|--------------------|--------------------|-------| | users | `id=@req.auth.id`| `id=@req.auth.id`| null | `id=@req.auth.id` | `id=@req.auth.id` | self-edit only; admin-only signup | | status | "" | "" | null | null | null | dashboard reads anon, harness writes as admin | | status_history | "" | "" | null | null | null | same | | probe_runs | "" | "" | null | null | null | same | | baseline (prod) | "" | "" | null | null *(was "")* | null | dashboard reads anon, edit gated by PbAuthPrompt | | alert_state | `auth.id != ""` | `auth.id != ""` | null | null | null | already locked | The only fields changed by this PR are `users.createRule` (both envs) and `baseline.updateRule` (prod). Everything else was already correct. ## Procedure followed 1. Read superuser credentials from Railway via direct GraphQL (`variables` query) on the `pocketbase` service in both environments. 2. Authed against `/api/collections/_superusers/auth-with-password` in both envs and captured JWTs. 3. Enumerated `/api/collections?perPage=200` and built the before/after matrix. 4. Confirmed anonymous user signup succeeded against prod (HTTP 200) and that a probe user was created; deleted it via the admin API. 5. Confirmed anonymous baseline PATCH succeeded against prod (HTTP 200). 6. Applied the rule changes via `PATCH /api/collections/<id>` against **staging first**: - `users.createRule` → `null` - Re-fetched all rules, confirmed. - Verified anonymous signup now 403, anonymous status read still 200, dashboard at `dashboard.showcase.staging.copilotkit.ai` still HTTP 200 with a populated HTML payload. 7. Applied the same change set to **production**: - `users.createRule` → `null` - `baseline.updateRule` → `null` - Verified anonymous signup 403, anonymous baseline PATCH 403, anonymous status + baseline READ both 200, dashboard at `dashboard.showcase.copilotkit.ai` still HTTP 200 with a populated HTML payload. ## Test plan - [x] Staging dashboard renders after change (verified) - [x] Prod dashboard renders after change (verified) - [x] Anonymous user signup returns 403 (verified both envs) - [x] Anonymous baseline update returns 403 (verified prod) - [x] Anonymous reads of status/baseline still return 200 (verified prod) - [ ] On a fresh PocketBase instance, applying these migrations from scratch leaves the final rule shape identical to the audit (the migrations key off `findCollectionByNameOrId` so they only mutate the two fields, leaving everything else untouched).
## Summary Adds `showcase/bin/railway` — a single-file Ruby tool (stdlib only, no Bundler/Gemfile) that exposes 9 subcommands for Showcase Railway operations: | Subcommand | Purpose | |---|---| | `snapshot` | Capture an env's services + config into a YAML snapshot. | | `restore` | Restore an env to a snapshot (force-redeploy each service). | | `rollback` | Roll a single service back one deploy (`--to DEPLOYMENT_ID` for specific). | | `rollback-commit` | Restore an env to the snapshot committed at a given git SHA. | | `promote` | Promote staging digests to production with prechecks. | | `pin` | Pin a service to a specific image digest. | | `env-diff` | Diff two envs; exits 1 on drift. | | `resolve-digest` | Resolve an image tag to its `sha256:` digest via GHCR. | | `lint-prod` | CI gate: warn if any prod service is not digest-pinned. Supports `--exit-zero` (advisory mode) and `--format json` (machine-readable output). | Spec: https://www.notion.so/36d3aa38185281df97e4cfe11dad7d47 (companion to the main rollback spec) ## Design highlights - **Stdlib only** — `net/http`, `json`, `yaml`, `optparse`. No gem deps, no Gemfile, no Bundler. - **Auth** — reads `RAILWAY_TOKEN` env var first, falls back to `~/.railway/config.json`. Never invokes `railway login`/`railway logout`/`op`. - **GraphQL** — direct calls to `https://backboard.railway.app/graphql/v2` using `serviceInstanceDeployV2` for force-redeploy. - **GHCR digest resolution** — uses OCI Distribution Spec manifest HEAD + `Docker-Content-Digest` header. Anonymous token for public packages. - **Snapshot YAML schema** — versioned (`version: 1`). Records env-var KEYS only, never values, so snapshots are safe to commit. - **Production protection** — every mutating subcommand requires `--yes` AND a typed `production` confirmation phrase on stdin. `--non-interactive` skips the prompt but still requires `--yes`. No way to mutate prod without explicit acknowledgement. - **Uniform exit codes** — 0 clean, 1 drift/findings/refused, 2 error. ## Promote precheck classes Per the spec: - **MOVE**: image digests; startCommand (only with `--include-startcommand`); auto-update-disable. - **VERIFY-REFUSE**: service-set parity, critical env-key parity (RAILWAY_TOKEN, GHCR_TOKEN, SHARED_SECRET, OPS_TRIGGER_TOKEN, POCKETBASE_SUPERUSER_*, GITHUB_APP_PRIVATE_KEY, OPENAI/ANTHROPIC/GOOGLE keys). - **WARN**: missing/extra custom domains (expected: showcase/dashboard/dojo/docs/hooks.copilotkit.ai for prod, .staging.copilotkit.ai for staging). - **IGNORE**: env-scoped URLs, volumes. ## Tests Minitest suite (stdlib) at `showcase/bin/spec/`: - `test_cli_parsing.rb` — argv parsing for each subcommand, dispatcher behavior, env aliases, `lint-prod --format` parsing. - `test_snapshot_roundtrip.rb` — YAML write/read, schema version validation, `find_service` helper. - `test_ghcr_digest.rb` — image-ref parsing across all shapes, digest resolution decision tree, 404 → nil, 5xx → raise. - `test_production_protection.rb` — staging bypasses, prod-without-yes aborts, prod+yes+non-interactive proceeds, typed-phrase prompt accept/reject. 27 runs, 70 assertions, 0 failures. ```sh ruby showcase/bin/spec/all_tests.rb ``` ## CI integration New workflow `.github/workflows/showcase_lint_prod.yml` runs on every PR that touches `showcase/**`: 1. `bin/railway lint-prod --exit-zero --format json` — checks that every prod service is digest-pinned. (Soft-skips with a warning if `RAILWAY_TOKEN` secret is unset.) 2. `ruby showcase/bin/spec/all_tests.rb` — runs the test suite. ### lint-prod is advisory during initial soak The lint-prod step currently passes `--exit-zero`, which makes the command exit 0 even when findings exist. The workflow is also resilient to snapshot/GraphQL errors: if `lint-prod` itself crashes for any reason, the workflow renders an "audit unavailable" block instead of failing the PR. Findings still print to the job log, so we can see drift, but they will not block PRs while we soak the check against real production state. **Plan to flip to enforcing:** 1. Merge this PR; let the advisory job run on every showcase PR for a few rounds. 2. Confirm the findings list stays clean (or fix any genuine drift we surface). 3. Remove `--exit-zero` (and the error-tolerant capture) from the workflow step in a follow-up one-liner PR to turn lint-prod into a hard CI gate. This avoids the failure mode where a brand-new check immediately blocks unrelated PRs because of pre-existing prod state we haven't audited yet. ### Visibility surfaces Every run renders the audit result in two places so people don't have to click into the job logs: 1. **`$GITHUB_STEP_SUMMARY`** — a structured markdown block at the top of every workflow run page. Shows on every event (`pull_request`, `push`, `workflow_dispatch`). Contains: one-line status, table of unpinned services (only — `pinned` services are not enumerated), Pacific-time run timestamp, finding count. 2. **Sticky PR comment** — on `pull_request` events, the workflow posts (or updates) a single comment per PR. The comment is keyed by the HTML marker `<!-- lint-prod-sticky-comment -->` so re-runs PATCH the same comment instead of creating duplicates. Plain `gh` CLI only — no third-party action. The workflow also writes the findings count to `$GITHUB_OUTPUT`, so a future Slack-alert step can compare against prior runs. If `lint-prod` itself fails (e.g. a snapshot/GraphQL error), both surfaces render an "audit unavailable" block with the captured error inside a `<details>` fold, rather than going blank. ## Test plan - [ ] CI passes (`Showcase: lint-prod (digest pinning)` job) - [ ] `showcase/bin/railway --help` lists all 9 subcommands - [ ] `showcase/bin/railway <sub> --help` works for every subcommand - [ ] Local: `RAILWAY_TOKEN=... showcase/bin/railway snapshot --env staging --dry-run` produces valid YAML - [ ] Local: `RAILWAY_TOKEN=... showcase/bin/railway env-diff staging production` produces a drift report - [ ] Local: `RAILWAY_TOKEN=... showcase/bin/railway lint-prod` returns 0 (or 1 if prod drift exists — informational) - [ ] Local: `RAILWAY_TOKEN=... showcase/bin/railway lint-prod --format json` emits valid JSON with `services`, `findings`, `timestamp` - [ ] CI run shows the audit block in the step summary - [ ] PR has a single sticky comment that updates (not duplicates) on re-runs
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 : )