Add platforms: linux/amd64 to the depot/build-push-action invocation in
showcase_deploy.yml. Railway and GHCR serve x86 hosts, so an arm64-only
image crashes on pull. Matches the platform requirement enforced for
local docker build invocations (documented in
showcase/starters/template/README.md).
Verify Marketplace credential expects an active Azure session via azure/login, but login was gated only on published==false. When dry_run=true and version is already published, the verify step ran without a session and failed. Widening the guard to include dry_run.
The dry-run summary referenced scripts/release/vscode-extension-release.sh,
which was removed in 17c4daee3 (drop release helper script). Point
maintainers at the new CHANGELOG-edit flow instead.
Adds .github/workflows/vscode-extension-changelog-sync.yml: on PRs that
touch packages/vscode-extension/CHANGELOG.md, read the top '## X.Y.Z'
entry and bump packages/vscode-extension/package.json to match via an
auto-committed 'chore: release vX.Y.Z' on the PR branch.
Collapses the release maintainer flow to a single CHANGELOG edit.
Forks are skipped (GITHUB_TOKEN can't push to fork branches). A
guardrail rejects CHANGELOG entries older than the current package.json
version (catches history edits vs. prepends).
Lets us validate the Marketplace-OIDC and Open VSX auth chains end-to-end
without actually publishing. workflow_dispatch now accepts a dry_run input
(default true); when set, the job runs checkout, Azure login,
vsce verify-pat, and a new Open VSX /api/user token introspection, then
stops before any publish, tag, release, or Slack success notify.
Push trigger is unchanged — dry_run is only meaningful for manual runs.
- Use slackapi/slack-github-action@v2.1.0 matching 8+ existing workflows
- Switch from SLACK_WEBHOOK to SLACK_WEBHOOK_OSS_ALERTS (the secret
actually wired up in the repo, routed to #oss-alerts)
- Guard with env.SLACK_WEBHOOK_OSS_ALERTS != '' for repo-fork safety
- Add failure-path notification with failed job + per-registry outcomes
+ run URL (follows memory rule: red alerts carry actionable detail)
- Expose publisher as a step output to construct Open VSX URL cleanly
(https://open-vsx.org/extension/<publisher>/<name>) instead of inline
tr substitution
Replace tag-triggered publish with push-to-main + version-on-Marketplace
self-gate, matching CopilotKit/aimock. CI reads version from package.json,
queries vsce show for that version, and no-ops if already published. On a
new version it builds once, dual-publishes (Marketplace + Open VSX with
the existing retry wrappers and idempotent 'already exists' handling),
tags vscode-extension-vX.Y.Z, cuts a GitHub Release from the CHANGELOG
section, and posts to SLACK_WEBHOOK if configured. Path-scoped to
packages/vscode-extension/** so unrelated pushes don't trigger the job.
Wrap both registry publish steps in a bash retry helper that retries up
to 5 times with staggered backoff (10s/20s/40s/60s/90s) on transient
conditions (5xx, timeouts, connection resets, DNS). Auth and validation
errors still fail fast with no retry.
Critically, 'version already exists' is treated as idempotent success:
if attempt N-1 landed on the registry but its response was lost to a
502 after commit, attempt N sees the already-published version and
short-circuits rather than failing the job.
Motivated by Open VSX /publish returning intermittent 502 Bad Gateway
errors from Eclipse Foundation infra. Each attempt is wrapped in
::group:: markers so per-attempt logs are collapsible in the Actions
UI. Reconciliation step updated to reflect retry semantics and to
tell the operator to rerun the job (not bump the version) on exhausted
retries. RELEASING.md gets a 'Transient registry failures' section
documenting the behavior for both CI and manual publish paths.
Trigger: push of vscode-extension-v* tags.
Builds the extension once, packages a single VSIX, uploads it as a
workflow artifact, then publishes the same VSIX to the VS Code
Marketplace and Open VSX. Both registry publishes use continue-on-error
with a final reconciliation step so a partial failure is visible
instead of silently succeeding on one registry only.
Secrets VSCE_PAT and OVSX_PAT are read from the 'production'
environment so rotation and approval flow are scoped correctly.
## Summary
Three connected features land together so the CopilotKit VS Code
extension becomes a coherent debugger/preview surface:
1. **Hook Explorer** — every V1 + V2 render hook can be discovered and
previewed live with auto-generated controls, an inline `▶️ Preview
Component` CodeLens, and a sidebar that lists every captured site.
2. **AG-UI Event Inspector** — live SSE debug stream of all AG-UI
events, filterable and color-coded, in a sidebar view + editor panel.
3. **A2UI Catalog sidebar → webview** — the last native TreeView gets
replaced with a Tailwind-styled webview that matches the other two, now
with a proper **Go to source** action on components and fixtures.
## Hook Explorer
### Discovery + preview
- oxc-based scanner walks the workspace and finds every call-site of any
hook in the registry (17 across V1 + V2, render + data).
- Preview panel bundles the user's source via Rolldown (IIFE format,
React externalized, CSS collected per `@copilotkit/a2ui-renderer`
pattern), executes it in the webview with a capture-only **stub** for
`@copilotkit/react-core` (+ `/v2`), and mounts the user's component just
long enough to record each hook's config.
- Auto-generated form on the left/top drives the `render` prop's
args/parameters/state/event live. V1 parameter arrays and V2 Zod /
Standard Schema all map through a unified `FormSchema` derived at
runtime from the captured config.
- `useCopilotAction`, `useCopilotAuthenticatedAction_c`,
`useCoAgentStateRender`, `useLangGraphInterrupt`, `useRenderTool`,
`useRenderToolCall`, `useDefaultRenderTool`, `useLazyToolRenderer`,
`useRenderCustomMessages`, `useRenderActivityMessage`,
`useHumanInTheLoop`, `useInterrupt`, `useFrontendTool`, `useComponent`,
`useDefaultTool` all previewable.
- Inline `▶️ Preview Component` CodeLens above every render-hook call
site, backed by the same `copilotkit.hooks.preview` command as the
sidebar.
- Imported render components work: rolldown walks transitive imports
from the hook's `render` prop through any number of sibling files.
- Cross-file hook switches are robust: controls are reset on load,
Harness only mounts once the real HostRoot is ready, a top-level error
boundary auto-recovers when you pick a different hook.
### Why the stub approach
Bundling the real `@copilotkit/react-core` through rolldown's IIFE
output hit a `__commonJSMin` TDZ chain (`require_clipboard`,
`require_graphql`, `require_context_helpers`, …) because the
chat/runtime-client/markdown graph has circular imports. Externalizing
react-core + routing to a Proxy-backed stub that captures hook configs
avoids the whole CJS wrapping problem, shrinks the preview bundle from
~24 MB to ~1.3 KB, and keeps the preview runtime path completely
runnable without a live CopilotKit backend. Tradeoff documented in
`copilotkit-stubs.ts`.
### Weather-themed fixtures
14+ fixtures under `packages/vscode-extension/test-workspace/hooks`,
each a distinct visual scenario (forecast card, severity-palette alerts
with imported CSS, forecast strip, live radar grid, conic-gradient
precipitation gauge, air-quality badge with imported render, pollen
report with a 2-hop import graph, HITL evacuation confirm,
sunrise/sunset gradient, etc.). Used both as regression fixtures and as
the demo surface for video.
### Styling
- Tailwind-via-CDN + VS Code CSS variables for theme-aware chrome.
- User-provided CSS imports collected by rolldown and injected as a
`<style>` tag per load.
- Controls + form fields converted to Tailwind; textarea matches input
styling.
- Framed "Rendered output" card so the render prop is visually
unmistakable.
## AG-UI Event Inspector
### Runtime (`@copilotkit/runtime` + `@copilotkit/shared`)
- `DebugEventBus` — in-memory pub/sub on `BaseCopilotRuntime`, only
instantiated when `NODE_ENV != production`.
- Event tap in `createSseEventResponse` broadcasts every AG-UI event
with metadata (agentId, threadId, runId, timestamp).
- `GET /debug-events` SSE endpoint — returns 404 in production, streams
`DebugEventEnvelope` JSON to connected clients, initial `: connected`
comment flushes headers immediately.
### VSCode Extension
- `DebugStream` — Node SSE client with auto-reconnect, exponential
backoff, URL validation, error surfacing.
- `InspectorPanel` (editor panel, command `CopilotKit: Open AG-UI
Inspector`) and `InspectorViewProvider` (sidebar view) both use a shared
`DebugStream` instance — events persist when switching tabs.
- Inspector React app: `ConnectionBar`, `FilterBar`, `EventList`,
`EventDetail`.
- Color scheme: purple (lifecycle), red (errors), blue (text), orange
(tools), green (reasoning), teal (state), yellow (activity), gray
(unknown).
## A2UI Catalog → webview
- Replaces `ComponentPreviewProvider` (native TreeDataProvider) with
`CatalogListViewProvider` (WebviewViewProvider), matching the Hooks and
Inspector sidebars.
- New React webview with refresh header, component rows (name + relative
path + `auto` badge when no fixture), expandable fixtures list.
- Click a component row → preview (or toggle if it has fixtures); click
a fixture row → preview that fixture.
- Hover action buttons: `▷` preview + `</>` go-to-source on every row.
- "Go to source" opens the component file for component rows; for
fixture rows it opens the fixture file and jumps the cursor to the named
fixture key.
## Test coverage
- Runtime: DebugEventBus unit tests (8), handleDebugEvents endpoint (5),
fetch-router routes (4), integration across Express/Hono/Node/Fetch (9).
- Hooks: scanner + 16 fixture bundle-smoke test, regression guard
against `node_<builtin>` self-references, CSS collector test, stub-based
capture E2E, cross-kind controls remount, FormRenderer defensive
rendering.
- Inspector + webview: DebugStream reconnect (10), inspector components
(17), colors (9).
- Total: **178 tests** passing for the vscode-extension package; runtime
suite unchanged.
## Test plan
- [ ] `pnpm nx run copilotkit-vscode-extension:build` and `pnpm nx run
copilotkit-vscode-extension:test` both green
- [ ] F5 launches the Extension Dev Host with `test-workspace` open
- [ ] Hooks sidebar lists every fixture hook; click a row → preview
opens; `</>` button opens the source
- [ ] `▶️ Preview Component` CodeLens shows above every render hook in a
`.tsx` file; clicking it opens the preview
- [ ] Form controls drive the render live; cross-kind hook switches
(action ↔ custom-message) don't crash; a forced render-prop throw
recovers when a different hook is picked
- [ ] Imported-render fixtures (`ImportedAirQuality`,
`ImportedPollenReport`) bundle and preview correctly
- [ ] A2UI Catalog sidebar is the new webview, refresh works, `</>` on a
fixture opens the fixture file and reveals the named key
- [ ] AG-UI Inspector connects to `GET /debug-events`, filters + detail
work, events survive sidebar/panel switch, invalid URL shows red error
The dojo app was missing items under the langgraph column because
shell-dojo shipped a stale committed registry.json. The generator
only wrote to shell/, the dojo Dockerfile didn't run the generator
at build, and the CI path filter didn't rebuild the dojo when
manifest files changed.
Fix: emit from generate-registry.ts to shell, shell-dojo, and
shell-docs; add the generator step to shell-dojo's Dockerfile;
expand the deploy workflow's path filter to include packages/**
and shared/**; and refresh the committed registry/demo-content
JSON so files on disk match what the generator produces today.
The shell-dashboard app baked http://localhost:3000 into every demo and code link because NEXT_PUBLIC_SHELL_URL was never provided at build time and the source defaulted to localhost. Next.js inlines NEXT_PUBLIC_* at next build, so setting the value on Railway at runtime does nothing.
Fix: remove the silent localhost fallback, pass NEXT_PUBLIC_SHELL_URL as a Docker build arg from showcase_deploy.yml, and fail loudly if it's unset at build so this can't regress silently.
Adds a GitHub Actions workflow that curls /api/health on each of the
17 showcase starter services every 5 minutes to keep the agent's
in-memory state warm (JIT, module cache, connection pools). Railway
Pro tier doesn't sleep containers, but warm-state decays over idle.
Intentionally minimal: no Slack, no metrics, no state. Observability
is already covered by showcase_smoke-monitor.yml — this is strictly
keep-alive.
INTERIM — will be superseded by showcase-ops smoke probe cadence
once that service lands (see Notion proposal §2a).
## Summary
- Docs Sync workflow has been failing since 2026-04-21 with `fatal:
pathspec 'showcase/shell/src/content/' did not match any files` (exit
128) on the "Create PR for docs sync" step (example: run 24726678848).
- The path was moved by the shell platform restructure (#4109, #4112) —
the docs content now lives under `showcase/shell-docs/src/content/`, and
the sync marker at `showcase/shell-docs/.docs-sync-sha`. The sync script
(`showcase/scripts/sync-docs-from-main.ts`) already writes to those
locations; only the workflow's `git add` was still referencing the old
monolithic `showcase/shell/` path.
- One-line path fix.
## Test plan
- [ ] Verify yaml parses.
- [ ] Confirm next docs-sync run succeeds end-to-end (opens a sync PR
for the 11 pending review items from run 24726678848).
Regression from the 2026-04-21 incident: 18 production Railway services
were found with malformed image refs of the form
`ghcr.io/copilotkit/showcase-<slug>atest` (missing the `:` before
`latest`, so Docker treats `...atest` as the tag). Root cause was an
out-of-band MCP/manual mutation — no committed code touched those refs,
so the data has been fixed but no source-controlled guardrail exists.
Add a standalone script that queries Railway's GraphQL API for every
service in the CopilotKit Showcase project and asserts each image ref
matches the canonical shape `ghcr.io/copilotkit/<service-name>:latest`.
Wire it into showcase_deploy.yml as a pre-build job so any drift aborts
the workflow before the build matrix fans out.
On violation the script prints the service name, the current image, the
expected shape, and the reason, so the fix is obvious in the run log.
Slack classification in the notify job distinguishes a drift failure
from other pre-build failures.
Verified locally: 41 services pass against current Railway state; the
exported `validateImage` function rejects the exact `...atest`
corruption, mismatched service/image names, missing tags, wrong
registries, wrong tag values, and null sources (9/9 simulated cases).
The "Showcase: Docs Sync" workflow has been failing with
`fatal: pathspec 'showcase/shell/src/content/' did not match any files`
since the shell platform restructure (#4109, #4112) split the monolithic
`showcase/shell/` package into `showcase/shell-docs/`,
`showcase/shell-dashboard/`, and `showcase/shell-dojo/`.
The sync-docs-from-main.ts script already writes to
`showcase/shell-docs/src/content/` and maintains the sync marker at
`showcase/shell-docs/.docs-sync-sha`. Update the "Create PR for docs
sync" step to stage from the new location so the workflow can commit
synced content again.
matrix.service.timeout is already a number in the generated matrix
JSON, so wrapping it in fromJSON() was a no-op that only obscured
the expression. Drop the wrapper.
The HTTP_CODE="000" sentinel assigned immediately before the probe
loop is dead: the loop's first iteration unconditionally overwrites
HTTP_CODE before any reader runs. Remove it to avoid implying a
meaningful default where there isn't one.
The shell-family starters bundle shared demo content and tooling at
build time via showcase/scripts/bundle-demo-content.ts, which walks
showcase/shared and showcase/packages/*/manifest.yaml. A change to
any of those inputs can alter the generated bundle without touching
the package directory, so path-filter was under-reporting changes
and skipping deploys that actually needed to rebuild. Extend the
filters to shared/**, scripts/**, and packages/*/manifest.yaml so
those inputs trigger the correct downstream deploys.
- Promote SLACK_WEBHOOK_OSS_ALERTS to job-level env so the step-level
if: gate can actually see it. Step-level env: is not visible to the
same step's if:, which silently disabled every Slack post.
- Drop the service suffix from the concurrency group so two manual
dispatches for different services don't serialize unexpectedly and
push-triggered runs cancel each other deterministically.
- Rewrite the shared-module copy skip guard as an allowlist scoped to
showcase/packages/*. Starters, shell-family, aimock, and shell-dojo
don't consume shared modules, so the previous denylist missed every
new self-contained context (context=".") and polluted the repo root.
- Extend the binary-extension regex to cover .class, .jar, .pyd,
.pyc, .node, .bin, .pdb, .zip, .whl — any of which a build tool
can drop into a PR and none of which the previous regex caught.
- Add set -euo pipefail at the top of the run block. Previously
a transient wc failure (file deleted mid-diff) produced an empty
SIZE, which then failed [ "$SIZE" -gt ... ] with "integer
expression expected" on stderr while the script marched on —
silent false-green possible. Now errors fail the job loudly.
- Guard the wc -c call with an explicit numeric-regex check on
SIZE. A non-numeric or blank result now emits a ::warning:: and
skips the file rather than falling into the arithmetic
comparison and crashing under set -e (finding #17, #18).
- Add a comment to the prior-deploy Railway GraphQL query explaining
that deployments(first: 1) relies on the API default of
createdAt DESC ordering. Railway has no explicit orderBy argument
on this field; documenting the assumption means a future schema
change (or an operator reading the file cold) has something to
point at before debugging stale-deployment guard regressions
(finding #15).
- Harden both truncate_csv shell helpers (one per run block): disable
pathname globbing via set -f for the iteration so a slug
containing a glob char (*, ?, [) cannot expand against the
filesystem, and lock IFS to whitespace for deterministic
word-splitting regardless of caller env. Restore the prior glob
state on the way out so other shell blocks see no side effect
(finding #16).
## Summary
- Merges #4085 (Atai's no-docker-restructure feature port — merge-ready
variant) as the primary base.
- Renames `showcase/shell-internal/` → `showcase/shell-dashboard/` +
provisions Railway service at `dashboard.showcase.copilotkit.ai`.
- Renames `showcase/shell-dojolike/` → `showcase/shell-dojo/` + renames
Railway service in place (domain unchanged).
- Extracts MDX-docs infrastructure (`/docs`, `/[framework]`, `/ag-ui`,
`/reference` + components, libs, content) from `showcase/shell/` into
new standalone `showcase/shell-docs/` package; provisions Railway
service at `docs.showcase.copilotkit.ai`; adds 301 redirects on shell
for every moved route (including 17 per-framework slugs enumerated from
`registry.json`).
- Fixes `starter-langroid` `/api/health` 503 (sed stdout wrappers +
readiness gate); adds red-green regression guards in
`starter-consistency.test.ts`.
## DNS state
- `dashboard.showcase.copilotkit.ai` CNAME → `6u9icvje.up.railway.app` —
✅ live in Route53.
- `docs.showcase.copilotkit.ai` CNAME → `t6ge0qwv.up.railway.app` —
pending Route53 add.
## Railway services
- `showcase-shell-dashboard` (ID `4d5dfd74-be61-40b2-8564-b53b7dd4c15b`)
— provisioned, image pending first deploy.
- `showcase-shell-docs` (ID `7badfb8d-4228-414c-9145-b4026803714f`) —
provisioned, image pending first deploy.
- `showcase-shell-dojo` (ID `7ad1ece7-2228-49cd-8a78-bddf30322907`) —
renamed from shell-dojolike; already live.
## Closes / supersedes
- Supersedes #4084 (docker-restructure variant; preserved on
`jpr5/ftg-shell-ops` for future consideration, not merged here).
- True-merges #4085 via `--no-ff` — that PR auto-closes on this PR's
merge.
## Known non-blockers
- `/ag-ui/introduction` throws a server-component `onMouseEnter` runtime
error on `next start`. Same issue exists in shell today — pre-existing,
not a regression. Follow-up needed.
- `showcase/shell-dojo` lacks `package-lock.json` and `.gitignore`;
trivial follow-up for parity with siblings.
- `search-modal` retains the flat `search-index.json` shape (not the
extensible `{version:1, sources:[...]}` shape). Generator is the single
point of change if multi-shell search is wanted later.
- oxfmt has a pre-existing oscillation bug on 14 `.mdx` files with
`<Steps>` structures; sidestepped by mirroring the existing
`showcase/shell/src/content/**` ignore policy to the new
`showcase/shell-docs/src/content/**` path.
## Test plan
- [ ] CI green end-to-end (Validate Showcase, unit tests on 20/22/24,
format, oxlint, commitlint, starter-smoke).
- [ ] \`showcase_deploy.yml\` builds shell-dashboard + shell-docs images
to GHCR and Railway pulls them successfully.
- [ ] Add \`docs.showcase\` CNAME in Route53
(\`t6ge0qwv.up.railway.app\`) — without this,
docs.showcase.copilotkit.ai won't resolve and Railway won't provision
its TLS cert.
- [ ] Verify \`dashboard.showcase.copilotkit.ai/\` returns 200.
- [ ] Verify \`docs.showcase.copilotkit.ai/\` returns 200 and
\`/langgraph-python/quickstart\` renders.
- [ ] Verify \`showcase.copilotkit.ai/\` returns grid (200);
\`/docs/foo\` 301s to \`docs.showcase.copilotkit.ai/foo\`;
\`/react/foo\` 301s; \`/integrations\` and \`/matrix\` do NOT redirect.
- [ ] \`starter-langroid\` redeploy via \`showcase-deploy\` workflow:
\`/api/health\` returns 200.
The weekly Showcase pin-drift Slack alert previously read
"📉 Showcase pin-drift (weekly): FAIL=N
(baseline N) [ok]". The token "FAIL=N" read as a regression even when N
matched the ratchet baseline exactly (a stable week), generating recurring
false-alarm fatigue on #oss-alerts.
Reframe the notification text into three explicit states:
- [stable] — DRIFT=N matches baseline AND hash matches
- [REGRESSION] — DRIFT=N grew OR hash mismatch (set drifted); keeps the
🚨 emoji + run URL for actionable alerts
- [IMPROVED, ratchet me] — DRIFT=N shrank; reminder to update the baseline
Presentation-only. The ratchet logic in validate-pins.ts, the baseline file
(showcase/scripts/fail-baseline.json), and the FAIL-line hashing are
unchanged.
The main-deploy step already uses '// empty' on PRIOR_DEPLOY_ID but
the verify step extracted DEPLOY_ID / STATUS / DOMAIN without it, so
when Railway's edges array was transiently empty these became the
literal string 'null'. That breaks the prior-vs-fresh comparison and
the DOMAIN emptiness guard in inconsistent ways. Add '// empty' to
all three jq filters so empty-edge responses normalize to '' the
same way everywhere.
Two small but load-bearing hygiene fixes in the notify job's shell:
1) The matrix-legs collection pipeline used `jq -r '...' 2>/dev/null`,
swallowing any jq parse or schema errors before the surrounding
pipeline substitution could see them. If the gh-api payload shape
ever shifts (schema rev, partial response, encoding glitch), we
now want the error in the job log so an operator knows WHY notify
fell back to softer wording — the existing `-z "$pairs"` guard
still handles the empty-output path without relying on suppression.
2) The duplicated inline `truncate_csv` helper was declaring
`local ws=...` on its OWN line AFTER `local budget=...` on the
function's first line. Redeclaring `local` inside the same
function body is legal but prints 'local: not in a function'
warnings on some bash versions (3.x, noexec edge cases) and is
outright forbidden by `set -eu` hardening profiles. Collapse `ws`
into the single `local` declaration at the top and use a plain
assignment where it's computed.
The BUILD outcome classifier read:
elif [ "$BUILD" = "failure" ] || [ -n "$BUILD" ]; then
After the earlier `success` + `skipped && detect=success` branches
had claimed the obvious green cases, any non-empty BUILD string fell
through the `-n` test and was tagged FAILURE — including values like
"skipped" that reach this branch when detect-changes did NOT emit
`success` (e.g. cancelled pre-stage races or a partial matrix skip).
That means the Slack bot would occasionally cry red at runs that never
failed.
Simplify to an exact string match on "failure"; the final `else`
branch already handles the empty/unknown case by emitting OUTCOME=skip,
which is the correct disposition for indeterminate state.
The rest of the showcase workflows already pin `actions/checkout@v4`.
This one lagged on v3, which is now long-deprecated (Node 16 runner
EOL). Bringing it in line with the fleet avoids a future forced bump
when GitHub retires v3 runners entirely.
Remove every trace of the defunct test-integration-tmp service from the
showcase deploy workflow:
- drop the `test-integration-tmp` entry from the workflow_dispatch
inputs.service dropdown,
- drop the detect-changes `test_integration_tmp` output + paths-filter
entry, and
- delete the entire build-test_integration_tmp job (including its
placeholder RAILWAY_SERVICE_ID literal).
Matches the jpr5/test_integration_tmp_leak cleanup — the test harness
leak has been patched at its source (create-integration.test.ts), but
these workflow vestiges were never part of that fix and only survived
because nothing read them after the regression guard landed.
Adds the shell-docs package to the deploy matrix, paths-filter, and
dispatch options. Railway service `showcase-shell-docs` (ID
7badfb8d-4228-414c-9145-b4026803714f) has been provisioned with the
custom domain `docs.showcase.copilotkit.ai` and the Railway-generated
CNAME target `t6ge0qwv.up.railway.app`.
Env vars seeded on the Railway service: OPENAI_API_KEY,
ANTHROPIC_API_KEY, GOOGLE_API_KEY, POSTHOG_PROJECT_KEY, PORT=10000,
NEXT_PUBLIC_BASE_URL=https://docs.showcase.copilotkit.ai,
NEXT_PUBLIC_SHELL_URL=https://showcase.copilotkit.ai.
Matrix entry uses lfs:true + context:"." + explicit Dockerfile path
(mirrors shell) because the image pulls in shared/ scripts/ packages/
at build time. build_args_sha/branch are included so the build surface
embeds commit metadata, matching shell's pattern.
- Renamed showcase/shell-internal/ → showcase/shell-dashboard/ (git mv for history).
- Updated package name to @copilotkit/showcase-shell-dashboard (+ lockfile).
- Updated external references: showcase/scripts/{probe-docs,generate-status}.ts comments and showcase/README.md.
- Added dispatch + filter + ALL_SERVICES entry for shell-dashboard in .github/workflows/showcase_deploy.yml.
- Provisioned Railway service showcase-shell-dashboard (id 4d5dfd74-be61-40b2-8564-b53b7dd4c15b):
- image source ghcr.io/copilotkit/showcase-shell-dashboard:latest
- env vars OPENAI_API_KEY, ANTHROPIC_API_KEY, PORT copied from showcase-shell
- custom domain dashboard.showcase.copilotkit.ai attached (CNAME → 6u9icvje.up.railway.app)
- image will be built and pushed via showcase_deploy.yml on merge
- Add top-level 'permissions: contents: read' — the workflow performs
no repo writes, so declaring the minimum explicitly closes a
default-token hardening gap.
- Serialize publishes with concurrency: group: vscode-extension-publish,
cancel-in-progress: false. Two rapid pushes to main used to race
straight into vsce publish; the second failed noisily on duplicate
version or, worse, published out of order.
- Remove the 'Lint' step. The Nx target 'copilotkit-vscode-extension:lint'
doesn't exist and there's no 'lint' npm script either, so the step
always failed — 'continue-on-error: true' just hid it while never
actually linting anything. Re-add once a real lint target lands.
check-binaries.sh:
- Replace '[ ] && exit 1 / exit 0' with an explicit 'if [ ]; then
exit 1; fi; exit 0' block. Under 'set -e' the old form was safe
only because the trailing 'exit 0' existed; the explicit form is
robust regardless of what follows.
Critical
- scripts/hooks/check-binaries.sh: restore showcase data-file exclusions
(demo-content / search-index / starter-content >1 MB) that the inline
refactor dropped; add 'set -eu' so silent shell failures don't hide
policy violations.
- packages/vscode-extension/src/extension/preview-panel.ts: drop 'blob:'
from CSP script-src — it lets arbitrary-string JS execute via Blob URL
and defeats most of CSP's XSS protection.
- packages/vscode-extension/package.json: set private: true. The extension
ships as a .vsix via vsce, not npm, and workspace:* devDependencies
would break an accidental 'npm publish'.
Important
- runtime: forward the real agentId from handleConnectAgent into
handleSseConnect / createSseEventResponse so DebugEventBus envelopes
on /connect carry the actual agent name instead of the literal
'connect'. Updates handle-connect.ts, sse/connect.ts.
- hooks/panel.ts CSP: narrow connect-src from 'https:' to just the
Tailwind CDN. The preview path never drives a real CopilotKit runtime
— all hook calls route through the stub — so there's no legitimate
https: fetch to allow from inside bundled user code.
- extension/utils.ts getNonce(): switch to crypto.randomBytes. Math.random()
is not acceptable for a value that gates inline-script execution.
- .github/workflows/vscode-extension.yml:
* Build step uses 'nx run copilotkit-vscode-extension:build' instead
of 'pnpm run build' (targeted build with Nx caching, not full
monorepo rebuild).
* Added explicit 'Type check' step (tsc --noEmit).
* Added Lint step gated with continue-on-error until the Nx target
exists, so a missing target doesn't break the pipeline.
* Publish job now queries the Marketplace for the current published
version and skips 'vsce publish' when the local package.json
version matches — stops every docs/CI-only push to main from
failing on duplicate-version errors.
- hooks/hook-scanner.ts: bound the synchronous walk at 20 000 files so a
pathologically large workspace can't freeze the extension host; flag
kept in module-level constant with a rationale comment.
- sse-response.ts: document that debugEventBus.broadcast intentionally
runs before the stream-closed gate so debug subscribers see trailing
events even after the SSE client disconnects.
- inspector-panel.ts: subscribe to DebugStream lazily on show() rather
than in the constructor; unsubscribe on panel dispose. Avoids firing
the event callback on every envelope when no panel is open.
Suggestions
- fetch-router.ts: document 'debug-events' as a reserved route so it
can't be shadowed by an agent literally named 'debug-events'.
- activate.ts findValuePosition: add optional startOffset parameter,
document the first-occurrence limitation + the follow-up path for
per-fixture precision.
- activate-hook-explorer.ts isInsideWorkspace: fix JSDoc to reflect the
code (root itself is excluded).
I added it to the matrix in c3d105ae0 but the corresponding
examples/integrations/test-integration-tmp/ directory was never
created on this branch — the job fails on every PR run with
"No such file or directory".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Teams get pinged on every failure but never see a positive recovery
signal when these workflows flip back to passing. Mirror the per-service
transition pattern already implemented in showcase_smoke-monitor.yml.
showcase_deploy.yml (notify job):
- Restore previous run's status from actions/cache (key pattern
matches smoke-monitor: prefix-based restore-keys + run_id save key)
- Classify outcome as failure | success | skip
- failure → post existing red alert + update state
- success AND prev=failure → post recovery + clear state
- success AND prev=ok → silent (green→green, matches prior behavior)
- skip (no-changes or cancellation) → no state change, no post
starter_deployed_smoke.yml:
- Restore state before tests run
- After test step, classify via steps.playwright.outcome
- On red→green, post recovery message alongside existing failure path
- On any terminal outcome, save new state to cache
First-ever run (or cache miss): state initializes to ok so we don't
emit a false recovery on the first green run after this ships.
Recovery wording:
✅ *Showcase deploy*: recovered (was down since <iso8601>)
✅ *Starter Deployed Smoke Tests*: recovered (was down since <iso8601>)
Existing red-alert wording is unchanged.
## Summary
Two related defects in the showcase deploy pipeline let stale images sit
live on Railway while Slack stayed green. This PR fixes both.
### Defect 1 — Drift detector skipped all starter services
`.github/workflows/showcase_smoke-monitor.yml` listed only 19
**package** slugs in its `SERVICES=(...)` array (ag2, mastra,
llamaindex, ...). Zero **starter** slugs. As a result:
- GHCR `showcase-starter-<svc>` tags were never checked for drift.
- `gh workflow run showcase_deploy.yml -f service=starter-*` was never
auto-dispatched.
- Starter services could run with weeks-old images and no alert would
fire.
`showcase_deploy.yml` already supports `starter-*` dispatch names and
already calls `serviceInstanceRedeploy` for any service with a
`railway_id`, so no change is required there. The fix is extending
`SERVICES=(...)` to include all 17 starter slugs via a
sparse-checkout-driven filesystem enumeration (no more literal
duplication between workflow and `showcase/starters/`).
### Defect 2 — Silent deploy failures reported green
`.github/workflows/showcase_deploy.yml` emitted `::warning::` and exited
0 when a service never returned 200 on its health path within 360s. The
legacy justification (`# Don't fail — sleep-on-idle services take time
to wake`) no longer applies: Railway is on the Pro tier with no
sleep-on-idle, so a 6-minute failure to become healthy is a real
failure. Changed to `::error::` + `exit 1`.
## Round 2 fixes
Round 2 CR raised six findings against the original smoke-monitor +
validator changes. All fixed in this PR:
- **BLOCKING 1/2 — smoke-monitor guard.** Replaced the magic `-eq 19`
sentinel with `grep -c '^starter-'` so adds/removes to the literal
non-starter list can't silently disable the guard. Added `shopt -s
nullglob` around the `showcase/starters/*/` loop so an empty starters
tree no longer corrupts `SERVICES` with a `starter-*` literal.
- **BLOCKING 3 — GHCR stderr isolation.** Dropped `2>&1` on the `gh api
-i` call; captured stderr to a temp file and surfaced it only when `gh`
returns a non-zero RC with no HTTP status. Auth / rate-limit / network
noise can no longer splice into the HTTP header block and poison
`HTTP_STATUS` / `API_BODY` parsing.
- **BLOCKING 4 — validator tests.** Added
`showcase/scripts/__tests__/validate-workflow-starters.test.ts` (12
specs): happy path, missing-from-options-only, missing-from-matrix-only,
missing-from-both, empty starters dir (exit 3), template/ excluded,
substring-spoof (starter-ag2 vs starter-ag2-extended), missing workflow
file (exit 3). Also extended `VALIDATE_WORKFLOW_STARTERS_REPO_ROOT` to
re-home the starters dir for testability.
- **MEDIUM 1 — YAML parsing.** Replaced the fragile regex-over-YAML
options scanner with a real `yaml.parse()` + typed navigation down
`on.workflow_dispatch.inputs.service.options`. ALL_SERVICES stays
regex-scanned (embedded JSON in a bash heredoc, with `${{ ... }}`
interpolations that aren't valid JSON pre-execution), but the
surrounding step is now located via YAML.
- **MEDIUM 2 — Slack list truncation.** Replaced `cut -c1-200` with a
`truncate_csv` helper that drops whole comma-separated entries until
under budget and appends `…` when truncated. No more
`starter-claude-sdk-pyth` mid-slug corruption.
- **MEDIUM 3 — template/ exclusion cross-references.** Both the TS
validator's `EXCLUDED_DIRS` and `showcase_smoke-monitor.yml`'s `[
"$slug" = "template" ] && continue` now carry `# keep in sync with ...`
comments pointing at each other.
- **NIT 2 — entry-check simplification.** Dropped the
belt-and-suspenders `import.meta.url === \`file://${argv[1]}\`` branch;
kept only the canonical `fileURLToPath(import.meta.url)` form.
- **NIT 3 — jq pipeline collapse.** Single-pass `.jobs[]? | select |
"\(...)"` replaces the three-pass `map | map | .[]` chain in the notify
step.
## Files changed
- `.github/workflows/showcase_deploy.yml` — warning → error + exit 1 on
unhealthy deploy; `truncate_csv` replaces `cut -c1-200` (3 sites);
single-pass jq pipeline in notify step.
- `.github/workflows/showcase_smoke-monitor.yml` — filesystem-driven
`SERVICES=(...)`, starter-count guard, `nullglob` loop, stderr-isolated
`gh api` call, cross-reference comment.
- `.github/workflows/showcase_validate.yml` — wires
`validate-workflow-starters` into CI.
- `showcase/scripts/validate-workflow-starters.ts` — YAML-aware presence
checks; env-var override homes both starters dir and workflow path.
- `showcase/scripts/tsconfig.json` — scripts-local tsconfig for LSP type
resolution.
- `showcase/scripts/__tests__/validate-workflow-starters.test.ts` — 12
specs covering the full matrix of drift scenarios.
## Test plan
- [ ] Next scheduled `showcase_smoke-monitor` run includes starter
services in its drift scan.
- [ ] A deliberately-unhealthy deploy (simulate by pointing health_path
at a 404) fails the job and fires the Slack alert.
- [ ] `showcase_validate` CI job runs `validate-workflow-starters` and
`npx vitest run scripts/__tests__/validate-workflow-starters.test.ts`
green.
Two CI fixes for the showcase_validate python-unit-tests job on PR #4083:
1. Install pytest-asyncio (and pytest-mock) in the shared "minimal test
deps" step. langroid/tests/python/test_agui_adapter.py has 16 tests
annotated with @pytest.mark.asyncio; without the plugin, pytest
reports "async def functions are not natively supported" and skips
them. Locally the user's venv has it; CI did not.
2. Surgically skip the strands package on Python 3.10.
ag_ui_strands==0.1.0 declares requires-python >=3.12,<3.14, so
pip install fails on 3.10 before pytest runs. Prefer the surgical
skip over dropping 3.10 from the matrix so the typing_extensions
NotRequired fallback path in aimock_toggle.py keeps getting
exercised.
Python unit-test workflow previously installed only pytest +
typing_extensions, assuming stdlib-only tests. Tests now import runtime
deps (openai, google.genai, httpx, opentelemetry, etc.) at module load
time, causing ModuleNotFoundError at pytest collection before
conftest-based stub finders run.
Loop each showcase/packages/*/ with a tests/python/ dir, pip install the
package's requirements.txt (if present) before pytest. Re-enable pip
cache keyed on those requirements files. Matches real-runtime parity.
Refs blocker 2 on PR #4083 (fix/showcase-smoke-failures).
Depot Startup plan (unlimited minutes) for consistent runner quality
across test workflows. Bun setup action is runner-agnostic on linux.
Extends the pattern from PR #4018.
Also adds the id-token: write permission required for Depot OIDC
auth, alongside contents: read for least-privilege defaults.