Commit Graph

10887 Commits

Author SHA1 Message Date
Tyler Slaton ec239b15f7 Add v1 reference selector and content 2026-05-28 13:25:39 -07:00
Jordan Ritter 9756697854 feat(showcase): bin/railway Ruby tooling for Railway ops (#5082)
## 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
2026-05-28 11:27:45 -07:00
Jordan Ritter 2775ac1bb7 fix(showcase/pocketbase): lock anonymous user signup and baseline writes (#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).
2026-05-28 11:27:41 -07:00
Jordan Ritter 5cac38c74e ci(showcase): pin actions and disable credential persistence in lint-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.
2026-05-28 11:13:04 -07:00
Jordan Ritter 9def341da4 fix(showcase): align bin/railway GraphQL with Railway public schema
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.
2026-05-28 11:05:25 -07:00
Jordan Ritter 79ef098c9b ci: re-trigger lint-prod to verify sticky-comment idempotency 2026-05-28 10:54:33 -07:00
Jordan Ritter 0d8277c1e4 fix(showcase): make lint-prod workflow resilient to snapshot/GraphQL 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.
2026-05-28 10:53:35 -07:00
github-actions[bot] d425a0d8e3 style: auto-fix formatting 2026-05-28 17:52:35 +00:00
Jordan Ritter d4edc96908 feat(showcase): add lint-prod visibility surfaces (step summary + sticky 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.
2026-05-28 10:51:16 -07:00
github-actions[bot] 63f5f15f79 style: auto-fix formatting 2026-05-28 17:47:34 +00:00
Jordan Ritter 15303cd406 chore(showcase): make lint-prod CI step advisory during initial soak
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.
2026-05-28 10:46:39 -07:00
Tyler Slaton ff7e921653 Merge branch 'main' into tyler/pdx-199-error-anchor-drift 2026-05-28 10:43:38 -07:00
Austin Merrick 4fbcb4c1cd fix(shell-docs): fix Tab double-escaping that hid multi-word tab content (#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.
2026-05-28 10:38:44 -07:00
Jordan Ritter 83ec206d80 fix(showcase/pocketbase): lock anonymous user signup and baseline writes
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.
2026-05-28 10:25:18 -07:00
github-actions[bot] 8abe6c0c08 style: auto-fix formatting 2026-05-28 17:24:42 +00:00
Jordan Ritter 56e85dfd80 feat(showcase): add bin/railway Ruby tooling for Railway ops
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/.
2026-05-28 10:23:24 -07:00
Austin Merrick 4bc7427f2a fix(shell-docs): fix Tab double-escaping that hid JSON Configuration 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.
2026-05-28 09:07:46 -07:00
Tyler Slaton 9b22849b2d fix(showcase): restore authored shell-docs sidebar (#5080) 2026-05-28 08:23:54 -07:00
Tyler Slaton c70e5ed3bd Merge branch 'main' into codex/restore-authored-shell-docs-sidebar 2026-05-28 08:23:08 -07:00
Tyler Slaton 4e21ab1954 fix(showcase): restore authored shell-docs sidebar 2026-05-28 08:19:56 -07:00
Sam Julien 8eba35e0f8 fix(shell-docs): redirect legacy/external URLs that 404 post-BIA cutover (#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.
2026-05-28 08:11:31 -07:00
Ran Shemtov f0fa6d6c00 fix(python): serialize AGUI agent metadata safely (#5036)
## 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)
2026-05-28 17:07:42 +02:00
Ran Shemtov f954a2e15b Merge branch 'main' into wt-4995-29229-6154 2026-05-28 17:06:59 +02:00
Sam Julien 8966d76b80 fix(shell-docs): redirect legacy/external URLs that 404 post-BIA cutover
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.
2026-05-28 15:05:41 +00:00
Anmol Baranwal ca114288e0 chore: add multimodal and dynamic flow details to readme 2026-05-28 19:51:11 +05:30
Anmol Baranwal 0ef47d90ad feat(showcase): add a2ui pdf analyst demo 2026-05-28 18:21:47 +05:30
Ran Shemtov b2febea228 docs(aws-strands): add TypeScript code samples (#5033)
## What does this PR do?

Add complimentary TypeScript examples now that the TypeScript Strands
integration (`@ag-ui/aws-strands`) has been published.

## Related PRs and Issues

- https://github.com/ag-ui-protocol/ag-ui/pull/1681

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] 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)
2026-05-28 11:26:55 +02:00
Ran Shemtov ede4cd19c2 Merge branch 'main' into docs/aws-strands-typescript-tabs 2026-05-28 10:08:56 +02:00
Jordan Ritter ce87d91c07 fix(ci): merge prerelease.yml into publish-release.yml (single OIDC entry point) (#5071)
## Why

npm OIDC trusted publishers match on the **caller's** `workflow_ref`
claim, not the callee's `job_workflow_ref`. The PR-A / PR-B
reusable-workflow architecture was structurally incompatible with that
matching rule: any prerelease publish dispatched via a separate
`prerelease.yml` caller would present a `workflow_ref` that npm's
trusted-publisher records (registered against `publish-release.yml`)
would reject.

The canonical fix used by Storybook, Nx, and Vite is a single-workflow
pattern: one entry-point workflow that branches internally on a `mode`
input. That's what this PR implements.

## What

- **Deletes** `.github/workflows/prerelease.yml`.
- **Refactors** `.github/workflows/publish-release.yml` to handle both
`stable` and `prerelease` modes via `workflow_dispatch` inputs (`mode`,
`suffix`, `dry-run`).
- Adds a `Bump prerelease versions` step in the build job (gated on
`mode == 'prerelease'`) that validates the user-supplied suffix against
`[a-zA-Z0-9._-]+` and falls back to the script's timestamp default when
the suffix input is empty.
- Mode-aware publish script selection (`prerelease.ts` vs
`publish-release.ts`) via env var on the publish step.
- Mode-aware gating on tag creation, GitHub Release creation, Notion
notification, and the release-summary step — all skipped for
prereleases.
- Adds a `Verify publish step emitted version` step that aborts if the
publish script didn't emit a `version` output (would otherwise produce
blank-version summaries and malformed tags/releases).

## Evidence

A dry-run dispatched against this workflow file completed all build +
publish stages successfully (publish step skipped via `dry-run=true`,
summary rendered correctly). The verification canary run `26544606752`
failed against pre-PR-C `main`, confirming that the PR-A/PR-B
architecture cannot pass OIDC and that PR-C is required.

## Code Review

- **Round 1:** 7-agent CR returned 5 consensus Bucket B findings (suffix
validation, version-emission verification, mode-aware tag/release gating
wording, comment cleanups). All applied.
- **Round 2:** 7-agent CR confirmation returned **zero Bucket A
findings**. Two trivial additive nits applied in the final commit: `set
-euo pipefail` on the new verification step for consistency with every
other shell step in the file, and a leading comment on the `Bump
prerelease versions` step explaining the validation logic.
2026-05-27 21:49:32 -07:00
Mark Fogle db2a76f55d docs(cookbook): defer nav placement, leave Cookbook section orphaned
Remove the root and learn meta.json entries that placed the Cookbook in the
docs sidebar / Learn footer, deferring the nav-placement decision (top-level
tab vs sidebar group) to a follow-up. The recipe pages still route at
/cookbook and /cookbook/daytona (Fumadocs discovers MDX from the filesystem,
confirmed via .source generation), so Daytona-side deeplinks continue to
work. Nothing in our docs nav points at the section until the decision lands.

Also drops the stale '[Cookbook: State Machine]' Learn entry entirely (it had
been temporarily repointed to /cookbook in the previous commit).

OSS-222
2026-05-28 04:19:18 +00:00
Jordan Ritter 2fc27b3b3d style: oxfmt auto-fix 2026-05-27 21:12:15 -07:00
Jordan Ritter 0d3067f396 fix(ci): add set -euo pipefail + comment from CR round 2 2026-05-27 21:07:27 -07:00
Jordan Ritter a8d29776ce fix(ci): apply CR round 1 fixes (mode consistency, suffix validation, token via env, main-branch guard, prerelease version verify) 2026-05-27 21:03:25 -07:00
Jordan Ritter 195e769b7e fix(ci): delete prerelease.yml, superseded by publish-release.yml
Prerelease canary publishing is now an input mode on publish-release.yml's
workflow_dispatch. The dedicated prerelease.yml workflow is no longer
reachable and its workflow_call delegation never worked (npm matches the
OIDC token's caller `workflow_ref`, which was always prerelease.yml —
unregistered with any package's trust record).

Canaries are now dispatched via:

    gh workflow run publish-release.yml \
      -f scope=monorepo -f mode=prerelease -f suffix=<name>

The `bump-prerelease.ts` and `prerelease.ts` scripts remain unchanged; they
are invoked by publish-release.yml when `mode=prerelease`.
2026-05-27 20:54:42 -07:00
Jordan Ritter 5e319f7607 feat(ci): support prerelease mode via workflow_dispatch in publish-release.yml
Folds prerelease.yml's canary publishing back into publish-release.yml, which
is the workflow registered as npm trusted publisher for all 15 @copilotkit/*
monorepo packages and @copilotkitnext/angular. Since npm matches on the OIDC
token's `workflow_ref` claim (the caller), the canary must dispatch from THIS
workflow file — not from a separate workflow that delegates via workflow_call.

Changes:
  - Add `mode` (stable|prerelease) and `suffix` inputs to workflow_dispatch.
    `mode` selects the publish script and gates post-publish tag/release steps;
    `suffix` is forwarded to bump-prerelease.ts when mode=prerelease.
  - Add a conditional `Bump prerelease versions` step in the build job that
    runs `scripts/release/bump-prerelease.ts` before `Build packages` whenever
    `inputs.mode == 'prerelease'`. The bumped versions flow through the
    workspace artifact to the publish job exactly as in the deleted
    prerelease.yml.
  - Switch the publish step's `PUBLISH_SCRIPT` env var to be mode-driven:
    `prerelease.ts` for canaries, `publish-release.ts` for stable. The old
    `inputs.publish-script` plumbing was removed in the prior commit along
    with the workflow_call inputs schema.
  - Simplify the publish-job `meta` step now that workflow_call is gone: mode
    defaults to `stable` unless workflow_dispatch passes `prerelease`.
  - Rewrite the top-of-file comment to document both modes and the OIDC trust
    binding, replacing the old "MANUAL RETRIGGER" wording which only covered
    the stable retrigger path.

All post-publish gating (`steps.meta.outputs.mode != 'prerelease'` on the tag,
release, and stable-summary steps; `mode == 'prerelease'` on the prerelease
summary) was already in place from the prior PR-A architecture and is left
intact. The `Verify publish step emitted version` step keeps its prerelease
bypass.

Canary invocation after this lands:
  gh workflow run publish-release.yml \
    -f scope=monorepo -f mode=prerelease -f suffix=<name>
2026-05-27 20:54:31 -07:00
Jordan Ritter 8a00793ba5 fix(ci): remove dead workflow_call trigger from publish-release.yml
The workflow_call trigger was added in the PR-A/B architecture so prerelease.yml
could invoke this workflow as a reusable workflow. That architecture was wrong:
npm's trusted-publisher matching uses the OIDC token's `workflow_ref` claim,
which is the CALLER workflow (prerelease.yml), not the callee's
`job_workflow_ref` (publish-release.yml). Since prerelease.yml has no trust
record, every canary attempt failed at the npm publish step with ENEEDAUTH.

This commit removes the dead code:
  - workflow_call: trigger block (inputs schema + secrets block)
  - the `github.event_name != 'workflow_call'` guard on the build job
  - the `needs.build.result == 'skipped' && github.event_name == 'workflow_call'`
    branch on the publish job's `if:` (simplified to plain success check)

The follow-up commit folds prerelease support back into this workflow as a
`workflow_dispatch` mode, so the canary path executes from the workflow with
the trust record.
2026-05-27 20:53:32 -07:00
Mark Fogle 2a29103bae feat(skills): add copilotkit-daytona Agent Skill
Add an installable Agent Skill that wires a Daytona-backed runCode server
tool into an existing CopilotKit Built-in Agent app. Mirrors the structure
of skills/copilotkit-setup (SKILL.md + assets + references + eval.yaml +
sources.md + a workspace fixture).

References point at Daytona's maintained sources (llms.txt, dashboard
limits, the official daytona skill, and the Daytona MCP server) rather than
duplicating a hand-copied SDK surface that would rot. The runCode tool
itself was validated live against published @copilotkit/runtime@1.58.0
with Python and JavaScript snippets executing in real sandboxes.

The recipe in docs/content/docs/cookbook/daytona.mdx now also points at
this skill alongside the inline coding-agent prompt.

OSS-222
2026-05-28 03:49:43 +00:00
Mark Fogle eb13dbe770 docs(cookbook): add Daytona recipe + new Cookbook docs section
Add a top-level Cookbook section to the docs (landing page + meta.json wiring)
and the first recipe: wire CopilotKit's Built-in Agent up with a runCode server
tool that executes code in an isolated Daytona sandbox. The tool takes a
language (python/typescript/javascript, default python) and returns stdout +
exit code; recipe code validated end to end against published
@copilotkit/runtime@1.58.0 with both Python and JavaScript snippets round-
tripping through a real sandbox.

Also fixes a stale '../direct-to-llm/cookbook/state-machine' link in
learn/meta.json by repointing it at the new /cookbook section.

OSS-222
2026-05-28 03:41:34 +00:00
cogwirrel 24b981de16 docs(aws-strands): add Python/TypeScript tabs to code examples
The @ag-ui/aws-strands TypeScript adapter ships alongside the Python
ag_ui_strands package but the docs only showed Python snippets. Add a
TypeScript tab next to every Python snippet (Python default, `persist`
on the tab group so a reader's choice sticks across pages) covering:

- quickstart: project init, install, agent file, run command
- frontend-tools: @tool stub + createStrandsApp server
- shared-state (read + write): StrandsAgentConfig.stateContextBuilder
- generative-ui tool-rendering: backend tool definition
- generative-ui state-rendering: ToolBehavior.stateFromArgs

Also applied to the parallel showcase/shell-docs tree. Non-code pages
(deploy-agentcore, copilot-runtime, inspector, etc.) remain untouched
since they either re-export shared snippets or have no framework code.
2026-05-28 01:03:02 +00:00
Tyler Slaton 31fae47d93 Merge branch 'main' into tyler/pdx-199-error-anchor-drift 2026-05-27 16:13:58 -07:00
Tyler Slaton c425a2a8a8 fix(docs): hide unsupported shared-state frameworks (#5067) 2026-05-27 16:13:43 -07:00
Tyler Slaton 55cc95595c Merge branch 'main' into tyler/pdx-199-error-anchor-drift 2026-05-27 16:05:18 -07:00
Tyler Slaton fb4c065aae Merge branch 'main' into tyler/pdx-156-shared-state-excludes 2026-05-27 16:05:10 -07:00
Tyler Slaton 8cb04a63c9 fix: resolve recursive snippet imports (#5062)
## Summary
- Inline MDX helpers imported from `@/snippets/...` recursively by
import target
- Remove the `ComponentExamples` SNIPPET_MAP workaround
- Add focused regression coverage for snippet helper imports and runtime
component imports

## Tests
- `npm test` in `showcase/shell-docs`
- `npm run typecheck` in `showcase/shell-docs`
- `npm run lint` in `showcase/shell-docs` (passes with existing
warnings)
- `npm run build` in `showcase/shell-docs`
- pre-commit: root package tests and package checks via lefthook
2026-05-27 16:05:01 -07:00
Tyler Slaton acfc673ed7 Merge branch 'main' into tyler/pdx-208-recursive-snippet-imports 2026-05-27 15:58:36 -07:00
Tyler Slaton 574666a3bb Merge branch 'main' into tyler/pdx-199-error-anchor-drift 2026-05-27 15:58:02 -07:00
Jordan Ritter 17340d6b2a fix(ci): convert prerelease.yml into a thin caller of publish-release.yml (#5068)
## Why

Follow-up to #5063. Together they unblock customer canary publishes that
have been failing with `npm ENEEDAUTH` since 2026-05-15.

#5063 parametrized `publish-release.yml` to support `workflow_call`.
This PR converts `prerelease.yml`'s publish path into a thin caller of
that reusable workflow. Because npm OIDC's `job_workflow_ref` claim
resolves to the callee in a reusable-workflow invocation, the existing
trust record on `publish-release.yml` covers both stable AND canary
publishes — no second trusted-publisher record needed (npm only allows
one per package).

## What changes

- `prerelease.yml`'s `build` job is preserved verbatim (checkout,
install, `bump-prerelease.ts --suffix`, build, test, upload `workspace`
artifact).
- The old `publish` job is replaced with `uses:
./.github/workflows/publish-release.yml` with `mode: prerelease`,
`publish-script: prerelease.ts`, `scope`, `dry-run`.
- The caller's `publish` job grants explicit `permissions: { contents:
write, id-token: write, actions: read }` — REQUIRED for the callee's npm
OIDC mint (per GitHub Actions, caller's per-job permissions cap the
callee's declared permissions).
- The caller's `workflow_dispatch.inputs.dry_run` (underscore, preserved
for backwards compat) maps to the callee's `dry-run` (hyphen) at the
`with:` boundary.
- No `secrets:` block — Notion not needed for prereleases; callee
declares `NOTION_API_KEY` optional and gates it on `mode == 'stable'`.

## Verification plan

1. Dispatch `prerelease.yml` with `scope: monorepo`, `suffix:
test-oidc-refactor`, `dry_run: true` → confirms plumbing without an
actual publish.
2. Dispatch again with `dry_run: false` → first OIDC-handshake-verifying
real canary.
3. `npm view @copilotkit/runtime@1.58.0-canary.test-oidc-refactor --json
| jq '.dist.attestations'` → confirm provenance attestation references
`publish-release.yml`.

## Test plan

- [ ] CI green.
- [ ] Post-merge: dispatch dry-run + real canary per Verification plan
above.
- [ ] Confirm provenance attestations land via `npm view`.

Supersedes #5066 (closed when #5063's base branch was deleted).
2026-05-27 15:47:30 -07:00
Tyler Slaton 38b2bd4fc6 fix(docs): resolve PDX-208 merge conflict 2026-05-27 15:46:30 -07:00
Tyler Slaton f285056ba7 fix(docs): resolve PDX-156 merge conflict 2026-05-27 15:43:58 -07:00
Tyler Slaton 438307ddfe fix: remove stale smoke monitor refs (#5060)
## Summary
- remove stale smoke monitor workflow reference from showcase validate
push paths
- update keep-alive comment so it no longer points at the deleted
workflow

## Tests
- rg "showcase_smoke-monitor|smoke-monitor" .github/workflows
- ruby -e 'require "yaml"; ARGV.each { |f| YAML.load_file(f); puts "ok
#{f}" }' .github/workflows/showcase_keep-alive.yml
.github/workflows/showcase_validate.yml
- git diff --check -- .github/workflows/showcase_keep-alive.yml
.github/workflows/showcase_validate.yml
- pre-commit hook: lint-fix, test-and-check-packages, commitlint passed
on retry
2026-05-27 15:41:51 -07:00