The scrub and #4084 touched the same surface: #4084 re-added an `open:`
generative_ui profile listing `open-gen-ui`/`open-gen-ui-advanced`, and
re-added both features to `constrained-explicit.allowed`. Extending the
branch's scrub to both re-additions keeps the semantic consistent with
the schema (which already dropped `open` from the approaches enum).
- `showcase/shared/constraints.yaml`: drop `open-gen-ui` +
`open-gen-ui-advanced` from `constrained-explicit.allowed`; drop main's
re-added `open:` profile entirely.
- `showcase/packages/langgraph-python/manifest.yaml`: drop the now-orphan
`open-gen-ui` + `open-gen-ui-advanced` feature and demo entries
(validator confirmed they had no allowed approach left).
- Regenerated `showcase/shell/src/data/registry.json` + sibling
`shell-docs`/`shell-dojo` registries and `constraints.json` via
`pnpm --dir showcase/scripts generate-registry`. All 17 integrations
validate.
`feature-registry.json` intentionally still defines both features — the
original scrub commits (2b996c54d, 27f886e59) left it untouched, so the
demo source files on disk also stay. Follow-up deletion if desired is
out of scope for this merge.
- entrypoint.template.sh: cleanup trap + final kill now tolerate no-match
(|| true) so trap doesn't abort shell when one of the three PIDs has
already exited. Package-level entrypoint.sh already had this shape.
- generate-starters.ts: claude-sdk-typescript tsc now uses --rootDir .
so emitted artifact is /app/dist/agent/index.js (matches entrypoint
expectation + runner-stage COPY). tsc was inferring agent/ as rootDir
and stripping the prefix.
- generate-starters.ts: drop server.mjs from langgraph-typescript starter
output. The starter uses 'npx @langchain/langgraph-cli dev' and never
invokes server.mjs; the file imports @langchain/langgraph-api/server
which is not in the starter's extraDependencies (resolution relied on
transitive hoist via @langchain/langgraph-cli). The file stays in the
PACKAGE where prod mode does use it.
- generate-starters.ts: assert agentDir non-empty / non-absolute before
Dockerfile substitution so the 'rm -f {{AGENT_DIR}}/package*.json' line
can never degrade into 'rm -f /package*.json' across /app.
Add showcase/starters/template/README.md covering the multi-stage build
convention (builder + runtime split), the >=40% image-size reduction
target, the linux/amd64 platform requirement for local builds, and the
dockerfile_hygiene probe rules (>=2 FROM stages, no dev-dep installs in
the runtime stage).
Expand the header comment on showcase/scripts/generate-starters.ts to
document the >=40% size target, the getAgentBuildSteps/getAgentBuildCopy
handoff in more detail, and the linux/amd64 platform pin in the deploy
workflow.
Add per-slug Dockerfile emitters that pair with the new
AGENT_BUILD_STEPS / AGENT_BUILD_COPY tokens in Dockerfile.typescript:
- getAgentBuildSteps(fw): runs in the builder stage, after `npm run build`.
Emits `npx tsc` for claude-sdk-typescript (compiles agent/index.ts →
/app/dist/agent/index.js with flags that match the sibling package
Dockerfile), and `npx mastra build --dir src/mastra` for mastra
(bundles the server into .mastra/output/index.mjs). Returns "" for
every other slug so their Dockerfile cache stays unchanged.
- getAgentBuildCopy(fw): runs in the runner stage, after the agent-code
COPY. Moves /app/dist (claude-sdk-ts) or /app/.mastra (mastra) from
the frontend stage into the runner.
- getEntrypointBlock() prod-mode updates: mastra now boots via
`node /app/.mastra/output/index.mjs` (not `npx mastra dev`) and
claude-sdk-typescript via `node /app/dist/agent/index.js` (not
`npx tsx agent/index.ts`). Cold start is a straight `node`
invocation on Railway — mirrors PR #4132's fix for langgraph-ts.
- Wire AGENT_BUILD_STEPS / AGENT_BUILD_COPY into the `vars` map in
generateStarterImpl so the template substitution picks up the new
tokens, and export the two helpers so the test suite can guard them.
Also refresh the generator header comment to describe the multi-stage
shape (builder toolchain vs. minimal runtime) and the prod-mode emitter
wiring.
Tests:
- Replace the legacy `mastra dev` / `npx tsx` entrypoint expectations
with prod-mode assertions (`node /app/.mastra/output/index.mjs`,
`node /app/dist/agent/index.js`), plus not-to-contain guards so a
future refactor can't accidentally re-enable the tsx/dev path.
- Add a dedicated describe block for getAgentBuildSteps /
getAgentBuildCopy covering the two opted-in slots, the ""
fallthrough for langgraph-typescript (which has its own server.mjs
migration path), and the "" fallthrough for every Python slug
(Python prod-mode is shared-template, not per-slug).
Full `vitest run` in showcase/scripts is green (1085 tests).
Starter Dockerfile regeneration is deferred to a follow-up commit
block once Task 1's template wiring lands.
The all-dirs-missing guard added in c91c7c567 fataled the shell Docker
build, which intentionally does not COPY shell-docs/src/content/. The
shell only needs the static-pages stub so its header search modal has
something to render (links resolve across to docs.showcase.copilotkit.ai).
Downgrade the fatal to a loud warn + emit the 5-entry static-pages stub.
A misconfigured full build is still visible in logs.
Regression from PR #4127 (8e6991cea) on top of c91c7c567 / debfa6600.
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.
Two more starters are restart-looping with the same pattern #4123 fixed
for langgraph-*: the 90s (3-strike) watchdog budget is shorter than the
cold-start path on a fresh Railway container, so the agent gets killed
before it ever reports healthy, and the loop never breaks.
Railway evidence:
- claude-sdk-typescript (package, :8000): restart-looping since 04-20
16:54 UTC. Runs compiled `node /app/agent_server.js` which spins up
the full @anthropic-ai/claude-agent-sdk. Package entrypoint is hand-
written (not generator-emitted), so add the same grace block inline
matching #4123's shape.
- mastra (starter, :8123/api): restart-looping since 04-20 18:18 UTC.
Runs `mastra dev` on :8123 alongside Next.js on ${PORT:-10000}.
`mastra dev` performs a tsx build + Mastra server boot on first
request — legitimate supervised process, grace is the right fix.
Package-level mastra has no watchdog (the entire package IS the Next.js
app; no separate agent to watchdog) — PR #4116's classification of
"N/A" was correct for the package. The starter has a separate Mastra
dev server, so the generator-emitted watchdog is legitimate there.
Per-framework grace (not universal) preserves the #4123 design: uvicorn
and express agents are responsive within the 2-3s sleep before
AGENT_HEALTH_CHECK, so adding grace elsewhere would only delay
legitimate restart on true hangs.
This commit covers the generator mapping + the hand-written package
entrypoint. The regenerated starter entrypoints land in the next commit.
## Incident
`langgraph-typescript` is in a restart loop on Railway as of **04-20
17:05 UTC** (deployment `58bbebe8-7a94-4f99-b6e4-ffcbb4eb78b9`),
returning 502 to production traffic.
## Root cause
PR #4116 generalized the silent-hang watchdog from `crewai-crews` to
every showcase starter: poll agent health every 30s, kill after 3
consecutive failures (~90s). That's fine for uvicorn/express agents
(responsive in <5s), but `langgraph-cli dev` does a heavy cold-start:
- Studio browser IPC handshake (`createIpcServer`)
- `@langchain/langgraph-api` JIT spawn (`spawnServer`)
- Graph compile
On cold Railway containers this routinely exceeds 90s — the watchdog was
killing the process before `/ok` ever became reachable, producing a
kill-loop every ~90s.
## Phase 1 findings
Verified the health path is correct — not a path bug:
- `@langchain/langgraph-cli@1.1.17/dist/cli/up.mjs:27` uses `/ok` as its
own probe.
- `@langchain/langgraph-api@1.1.17/dist/api/meta.mjs:64` registers
`api.get("/ok", ...)`.
- `langgraph_cli` (Python) serves `/ok` from the same api surface.
The regression is purely a **timing** issue: 90s strike budget is
insufficient for langgraph cold-start on Railway.
## Fix — Option B (startup grace)
Added per-framework `getWatchdogGraceSeconds()` in the generator:
- `langgraph-*` starters: **180s grace**
- All other starters: **0s grace** (matches pre-#4116 behavior)
The grace loop waits up to 180s for the first healthy `/ok` probe before
arming the strike counter:
- First success → fall through immediately and arm the counter.
- 180s elapsed without success → arm the counter anyway (steady-state
watchdog then handles true hangs on the normal 90s schedule).
- Agent dies during grace → exit grace loop, `wait -n` in main shell
handles it.
**Not a revert of #4116.** The silent-hang vulnerability class remains
covered (still kills after 90s of steady-state failures); the grace only
defers the first strike.
## Per-starter changes
| Starter | Grace | Behavioral change |
|---|---|---|
| langgraph-typescript | 180s | **Fixes restart loop** |
| langgraph-python | 180s | Preemptive |
| langgraph-fastapi | 180s | Preemptive |
| crewai-crews, ag2, agno, claude-sdk-*, google-adk, llamaindex,
langroid, mastra, ms-agent-*, pydantic-ai, spring-ai, strands | 0s |
None (comment-only diff) |
## Files touched
- `showcase/scripts/generate-starters.ts` — new
`getWatchdogGraceSeconds()` + grace block in `getWatchdogBlock()`
- `showcase/starters/*/entrypoint.sh` — regenerated (17 files; grace
block for langgraph-*, comment-only for the other 14)
- `showcase/packages/langgraph-typescript/entrypoint.sh` — hand-edited
grace block (this package uses its own hand-edited entrypoint, not the
template)
- `showcase/packages/langgraph-fastapi/entrypoint.sh` — same
- `showcase/packages/langgraph-python/entrypoint.sh` — same
## Validation
- `bash -n` passes on all 19 edited `entrypoint.sh` files
- 1079/1079 `showcase-scripts` vitest pass (`pnpm --filter
@copilotkit/showcase-scripts test`)
## References
- Regression source: #4116
- Railway deployment: `58bbebe8-7a94-4f99-b6e4-ffcbb4eb78b9`
- Incident window: 04-20 17:05 UTC — ongoing at PR creation
## Test plan
- [ ] CI green
- [ ] Deploy to Railway — watch first boot log for `[watchdog] Startup
grace: waiting up to 180s...` followed by `[watchdog] Agent healthy
after Ns — arming strike counter`
- [ ] Confirm no restart loop on `langgraph-typescript`
PR #4116 introduced a generalized silent-hang watchdog that polls the
agent health endpoint every 30s and kills the agent after 3 consecutive
failures (~90s). This worked for fast-starting agents (uvicorn,
express) but regressed langgraph-typescript on Railway: `langgraph-cli
dev` does a heavy cold-start (Studio IPC setup + @langchain/langgraph-api
JIT spawn + graph compile) that routinely exceeds 90s on a fresh
container, so the watchdog was killing the process before /ok ever
became reachable — producing the 04-20 17:05 UTC restart loop on
deployment 58bbebe8-7a94-4f99-b6e4-ffcbb4eb78b9.
Phase 1 verification:
- `@langchain/langgraph-cli@1.1.17/dist/cli/up.mjs:27` uses /ok as
its own health probe, and `@langchain/langgraph-api@1.1.17/dist/
api/meta.mjs:64` registers `api.get("/ok", ...)` — so the watchdog
path is correct. The regression is purely timing.
- Railway logs show no successful /ok probe before kill-loop starts.
Fix (Option B — startup grace): add a per-framework startup-grace
window in getWatchdogGraceSeconds(). For langgraph-* starters, the
watchdog now waits up to 180s for the first healthy /ok probe before
arming the strike counter. If /ok comes up sooner, fall through
immediately. If 180s elapses without success, arm the counter anyway —
the steady-state watchdog will then handle a true hang on the normal
90s schedule.
Non-langgraph starters (crewai-crews, ag2, agno, etc.) have grace=0 and
behave exactly as before PR #4116 — the 2–3s `sleep` before the health
check is sufficient for uvicorn-based agents.
Files changed:
- showcase/scripts/generate-starters.ts — new getWatchdogGraceSeconds()
+ grace block in getWatchdogBlock()
- showcase/starters/*/entrypoint.sh — regenerated (grace block for
langgraph-*, comment-only for all others)
- showcase/packages/langgraph-typescript/entrypoint.sh — hand-edited
grace block (this package uses its own hand-edited entrypoint, not
the template)
- showcase/packages/langgraph-fastapi/entrypoint.sh — same
- showcase/packages/langgraph-python/entrypoint.sh — same
Not a revert of #4116: the silent-hang vulnerability class remains
covered (the watchdog still kills after 90s of steady-state failures;
the grace only defers the first strike).
Validated:
- bash -n on all 19 edited entrypoint.sh files
- 1079/1079 showcase-scripts vitest pass
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).
Generalize the watchdog shape proven in showcase/packages/crewai-crews/entrypoint.sh
(PRs #4114 + #4115) so that every Bucket-B starter gets:
- PYTHONUNBUFFERED=1 export (harmless for non-Python frameworks)
- A backgrounded watchdog subshell that polls the agent health endpoint
every 30s and kills the agent after 3 consecutive failures, letting
wait -n + container runtime handle the restart through the normal path.
- python -u on uvicorn / langgraph_cli invocations (Python frameworks).
- awk ... fflush() log prefixing (replaces the previous sed pipe; keeps $!
pointing at the real agent process).
Per-framework agent health paths:
* FastAPI / uvicorn agents -> /health
* langgraph-python, langgraph-fastapi, langgraph-typescript -> /ok
* claude-sdk-typescript, ms-agent-dotnet, spring-ai -> /health
* mastra -> /api
Spring Boot starter also gets a 60s /health startup probe (replacing the
blind sleep 5) because JVM warmup + context refresh can exceed 30s.
Root-cause fix for the 04-21 silent-hang incident on the crewai-crews
Railway deploy. Three tightly-coupled changes:
1. Bump ag-ui-crewai pin from `>=0.1.4,<0.1.6` to `>=0.2.0,<0.3.0`.
0.1.5 had three defects that wedged the agent: unguarded
`source.state.messages` access, an orphan `asyncio.create_task` with
no cancel ref, and a sync `completion()` call that pinned the event
loop. All three are fixed in ag-ui PR #1550, released as 0.2.0 on
2026-04-18. Our upper ceiling was blocking the fix.
2. Remove the pre-bind LLM crash hardening shim in `agent_server.py`.
The shim monkey-patched `crewai.cli.crew_chat.generate_*_description_with_ai`
to static strings so that `ChatWithCrewFlow.__init__` — which
ag-ui-crewai <= 0.1.5 invoked at endpoint-registration time, BEFORE
uvicorn bound its port — could not crash the process before the HTTP
server was listening. 0.2.0 defers `ChatWithCrewFlow` construction to
first request via a module-scoped `_cached_flow` + `asyncio.Lock`
inside `add_crewai_crew_fastapi_endpoint`. Any LLM hiccup now
surfaces as a 5xx on the first request instead of a startup crash,
which is what the shim was reaching for. The shim is dead code on
0.2.0 and has been removed (with `logging` import dropped as it was
only used by the shim).
3. Add `python -u` to the uvicorn invocation in `entrypoint.sh` as a
belt-and-suspenders complement to the existing `PYTHONUNBUFFERED=1`
export. The env var can in principle be un-exported by a child;
`-u` forces unbuffered stdout/stderr at the interpreter level and
is not overridable by user code. Combined with `awk '{...; fflush()}'`
in the pipe (already in place), this guarantees uvicorn request
lines reach Railway's log stream line-at-a-time. During the 04-21
incident Railway saw only ~15 log lines over 9h of uptime because
of buffering through a previous `sed` formulation.
Also updates `showcase/scripts/fail-baseline.json`'s `validatePinsFailHash`
to match the new `ag-ui-crewai` spec string. Pin-drift FAIL count is
unchanged (110); the hash changed only because the `ag-ui-crewai` line
in the FAIL set went from `>=0.1.4,<0.1.6` to `>=0.2.0,<0.3.0`.
Verified locally:
- `pip install -r requirements.txt` resolves `ag-ui-crewai-0.2.0` cleanly.
- `python -u -m uvicorn agent_server:app` starts; `/health` returns
200 `{"status":"ok"}`; request lines appear in real-time logs.
- `pytest tests/python/` — 94/94 pass.
- `pnpm -C showcase/scripts test` (vitest) — 1079/1079 pass.
- `validate-pins.ts` — count=110 matches baseline; hash updated.
Upstream refs:
- crewAI issue: https://github.com/crewAIInc/crewAI/issues/5510
- ag-ui PR #1550: https://github.com/ag-ui-protocol/ag-ui/pull/1550
Intentionally NOT in this PR:
- `showcase/starters/crewai-crews/` parity backport (the starter still
carries the 0.1.5 pin and the shim).
- The 14-starter watchdog generalisation.
Both belong to the silent-hang vulnerability-class work tracked
separately.
Correctness and portability fixes in the demo-content bundler:
- Track contributor snippets per-file so edits to multiple files in
one commit all get attributed, not just the last one walked.
- Extend endLine when the same file is seen again rather than
dropping the earlier slice — previously a later, smaller region
overwrote a larger one.
- Warn when the watch flag is set on Linux without the recursive
fs.watch support matrix, so the user sees why nothing is firing
instead of assuming silent success.
- Normalize path separators for Windows so bundle manifests use
POSIX paths regardless of the host OS.
- bundle-demo-content: reject highlight: paths that resolve outside
the package root. The bundle output is committed to the repo and
consumed by both shells at build time, so a malicious or
mistake-riddled manifest could otherwise smuggle arbitrary
filesystem contents (../../secrets, absolute paths) into
demo-content.json. Resolve relative to pkgRoot and throw on
escape (finding #19).
- generate-registry: add a runtime guard that manifest.slug is a
non-empty string before path.join(PACKAGES_DIR, manifest.slug).
Schema validation upstream already enforces this, but a
silently-undefined slug fed to path.join yields
"<packages-dir>/undefined" and would produce an empty docs_links
without surfacing any error. Fail loudly instead (finding #20).
If shell-docs/src/content/{reference,ag-ui,docs} didn't exist the
script silently produced a tiny index with no warning — operators
only found out by noticing Cmd-K search returning nothing.
Warn per missing directory and exit non-zero if ALL scan roots are
missing (that means we're running outside a prepared tree, e.g.
shell-docs didn't emit into the expected layout).
The watch loop logged '[watch] bundle failed' once and then fell
silent — repeat failures looked like success, and recoveries were
invisible (no news = assumed fine). Track the last error in module
scope so we distinguish first-failure from repeat-failure, and log an
explicit 'bundle recovered' note when the next green run clears the
state. Makes dev-mode transitions visible instead of silent.
A malformed docs-links.json was only console.warn'd and treated as
empty — build ran green while the override file silently rotted.
Accept an errors accumulator and push parse failures into it so
main()'s non-zero exit path fires. Missing file and stale-shape
tolerance are unchanged (both are legitimate states).
Without a timeout on the inner fetch, a hung upstream would stall the
entire docs-probe run indefinitely (Node's fetch has no default
timeout). Add a 10s AbortController. The bare catch also left 'error'
states opaque — log URL + error kind so operators can tell an
abort from DNS from TLS.
The bare catch returned 'unknown' with no trace, making it impossible
to distinguish DNS failures from aborts from TLS errors in the unknown
bucket. Log the URL plus a kind:code pair so spikes of 'unknown' are
actually diagnosable.
mockE2E/mockSmoke/mockQA were called unconditionally from main(), so
production dashboards shipped seeded test data in columns that aren't
wired to real CI yet. Gate each behind GENERATE_STATUS_MOCK_*
env vars (default off → emit null / 'unknown'). Print a prominent
banner to stderr naming which columns are mock whenever any gate is
on, so operators can't discover stub data by surprise.
The cross-starter consistency test had two AGENT_URL matchers:
function re8000() {
return new RegExp(AGENT_URL_LOCALHOST_8000_RE.source, flags);
}
function re8123() {
return new RegExp(
AGENT_URL_LOCALHOST_8000_RE.source.replace(/8000\\b/, "8123\\b"),
flags,
);
}
The 8123 variant does regex-source string munging — brittle: the
replacement target `8000\\b` has to stay in lockstep with the
shared regex's literal source, and a future port format change would
snap in a subtle way (the test could keep passing while matching
subtly wrong content, or start failing for reasons unrelated to the
starter it's guarding).
Replace the `.source.replace` hack with an exported factory
`makeAgentUrlLocalhostPortRE(port)` in `generate-starters.ts` and
have both `re8000`/`re8123` use it. The existing
`AGENT_URL_LOCALHOST_8000_RE` constant is preserved (now delegating to
the factory) so any external import stays happy.
Bonus: the factory validates port bounds so a typo can't silently
produce a pattern that never matches.
Two orthogonal bugs in the demo-content bundler:
1) README regions were silently dropped. `collectDemoFiles` pulls the
demo-dir root README out into the `readme` field rather than
appending to `files`, but it STILL wrote any `@region[...]`
slices it found inside the README into `perFileRegions` under the
README's bundled path. The region-collation loop at write time
iterated `fileOrder = files.map(f => f.filename)`, which by
construction never contains the README path — so those regions
went nowhere. Walk any `perFileRegions` keys not in fileOrder
after the main pass, so README regions still land in the public
map. Preserves the stable file-order rule (README contributions
come after source files, alphabetical tiebreak).
2) Binary-like files were being passed through
`fs.readFileSync(abs, "utf-8")` and then stringified into the
bundle. A PNG or font file typically produces a mess of
replacement characters in `demo-content.json` — waste at best,
visible corruption at worst when the Code tab ever tried to
render them. Extend `SKIP_EXTENSIONS` with the usual binary
suspects (images, fonts, archives, media, PDFs) so the walker
drops them at scan time.
The canonical schema in `scripts/generate-registry.ts` + per-package
`docs-links.json` calls the shell path `shell_docs_path`. probe-docs
was still reading ONLY the legacy `shell_docs_url` key, so any
future cleanup of `shared/feature-registry.json` to the canonical
key would silently turn every shell-docs status into 'missing'.
Prefer `shell_docs_path`, fall back to `shell_docs_url` so older
registry snapshots still contribute. Emit a one-shot dev-mode warning
when we see only the legacy key, so a lingering registry stays visible
without spamming CI logs.
showcase/scripts/ now ships a package-lock.json (committed here for the
first time), so the Docker builder can use 'npm ci' for deterministic
installs instead of 'npm install'. Update the comment to match actual
state and copy the lockfile into the scripts stage.
- Extend .oxfmtrc.json ignore to cover shell-docs/src/content
(mirrors existing shell/src/content ignore, which was not updated
when shell-ops-v2 moved MDX content into the new shell-docs package)
- Format showcase/scripts/generate-registry.ts and probe-docs.ts
MDX docs moved from shell to shell-docs, but several generated artifacts
are still consumed by both shells:
- registry.json: shell uses it (home grid, integrations, matrix,
middleware, layout); shell-docs uses it (docs renderer framework lookup)
- demo-content.json: shell uses it (integrations/[slug]/[demo]); shell-docs
uses it (<Snippet> in docs renderer)
- search-index.json: shell-docs consumes it for the docs search modal;
shell also keeps a copy so its header search still works — links 301
across to docs.showcase.copilotkit.ai.
Updated scripts:
- generate-registry.ts: dual-emits registry.json to both shells
(constraints.json stays shell-only — integration-explorer is shell)
- bundle-demo-content.ts: dual-emits demo-content.json
- generate-search-index.ts: scans from shell-docs/src/content (where MDX
now lives), writes to both shells' data dirs
- probe-docs.ts: scans shell-docs/src/content/docs (content source moved),
still writes docs-status.json under shell/ for the dashboard
- sync-docs-from-main.ts: target path updated to shell-docs/src/content
Tests in __tests__/ reference shell/src/data paths; dual-emit keeps
those stable so existing afterEach-restore hooks continue to work.
- 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
Production showcase-starter-langroid returns ``{"status":"degraded","agent":"down"}`` HTTP 503
at ``/api/health`` — the exact path the showcase-deploy workflow's ``ALL_SERVICES.health_path``
verify step asserts, so the next CI redeploy of this starter would fail verification.
Root cause: two compounding bugs in ``showcase/starters/langroid/entrypoint.sh``.
1. Process-substitution log prefixers suppressed stdout. Both backgrounded
services were wrapped with ``> >(sed 's/^/[agent] /') 2>&1 &`` /
``> >(sed 's/^/[nextjs] /') 2>&1 &``. In Railway's V2 runtime this shape
reliably produced ZERO ``[agent]``/``[nextjs]`` log lines (confirmed against
several weeks of Railway logs for this service) and correlated with
agent-unreachable 503s at ``/api/health``. The package entrypoint
(``showcase/packages/langroid/entrypoint.sh``) uses the plain-``&`` pattern
with no wrapper, stays green on the same Railway runtime, and shows full
uvicorn ``INFO:`` startup logs including ``127.0.0.1:... "GET /health
HTTP/1.1" 200 OK`` from Next.js's fetch. Match the working package pattern.
2. Cold-start race amplified by ``sleepApplication=true``. Railway sleeps
idle services. On wake, Next.js is ready in <1s but Python + langroid
imports take 10-20s. Without a readiness gate, Next.js answered the first
post-wake ``/api/health`` probe with ``agent:"down"`` before uvicorn had
bound port 8123. The deploy workflow's verify step then saw the 503 and
reported the deploy as unhealthy. Add a 30s readiness probe that curls
``http://127.0.0.1:8123/health`` in a loop before starting Next.js. The
IPv4 literal (``127.0.0.1`` not ``localhost``) is load-bearing — Node
22+'s fetch resolves ``localhost`` to IPv6 ``::1`` first, and uvicorn
binds IPv4 only; the readiness probe must not false-negative on resolver
semantics alone.
Also export ``PYTHONUNBUFFERED=1`` so Python import-time tracebacks (e.g.
langroid module-load failures) reach the container log immediately instead
of sitting in userspace buffers until process exit closes them off.
Verification:
- Built langroid starter image locally from this branch. ``/api/health``
returns 200 with ``{"status":"ok","agent":"ok"}``. Full uvicorn INFO
logs visible including ``127.0.0.1:xxxxx - "GET /health HTTP/1.1"
200 OK`` confirming Next.js -> agent path works.
- Readiness probe fires before Next.js launch: ``[entrypoint] Agent
/health ready after 3s``.
- New regression guards in ``starter-consistency.test.ts`` cover all four
invariants (no sed wrapping around uvicorn, no sed wrapping around next
start, ``PYTHONUNBUFFERED=1`` export, readiness probe on 127.0.0.1:8123
before ``next start``). Verified red→green (reverted entrypoint -> 4
fails; restored -> 4 pass). Full showcase/scripts suite: 1079/1079 pass.
The auditableDemos filter (introduced for cli-start's informational
`command:` entry) was applied to the spec/qa SHOULD checks but not
the missing-demo-dir MUST check. Result: cli-start in langgraph-python's
manifest triggered a false missing-dir failure even though it has no
on-disk folder by design.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clarify the positioning of the In-App Human in the Loop cell. The old
label was ambiguous with the in-chat variant; the new label surfaces
the core technical story at-a-glance: this is the async useFrontendTool
pattern where the approval UI pops up OUTSIDE the chat surface.
Also register the cell in the langgraph-python manifest (features list
+ demos entry pointing to the new files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "Declarative Generative UI (A2UI — Custom Catalog)" single-file
prototype variant was a testing-only companion to the canonical
declarative-gen-ui 3-file BYOC pattern. Drop it everywhere:
- Delete src/app/demos/declarative-gen-ui-hardcoded/ cell directory
- Delete src/agents/a2ui_dynamic_hardcoded.py
- Remove from langgraph-python manifest.yaml (features + demos) and
langgraph.json graphs
- Remove from docs-links.json, route.ts (agent entry + a2ui agents
list), shared/feature-registry.json, shared/constraints.yaml
- Drop the Callout promoting it from a2ui/dynamic-schema.mdx
- Bump expected langgraph-python count 32 → 31 in generate-registry
test; regenerate all bundles
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-Built: Chat was redundant with Pre-Built: CopilotChat — both rows
referred to the same CopilotChat surface with slightly different
framing. Drop prebuilt-chat everywhere:
- Delete src/app/demos/prebuilt-chat/ cell directory
- Remove from langgraph-python manifest.yaml features + demos
- Remove from shared/feature-registry.json features + chat-ui allowlist
- Remove from shared/constraints.yaml constrained-explicit
- Remove from src/app/api/copilotkit/route.ts neutral-fallthrough list
- Update expected counts in bundle-demo-content + generate-registry
tests (langgraph now exposes 32 features, down from 33)
- Fix stale src/agents/main.py test expectation to
src/agents/agentic_chat.py (main.py was split into a neutral assistant
by an earlier Phase-1 fix commit)
- Regenerate shell/src/data/{registry,constraints,demo-content,
docs-status}.json
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports probe-docs.ts from 4084. For each entry in shared/feature-registry.json
it HEADs the feature's og_docs_url and checks whether a matching .mdx exists
under shell/src/content/docs/<shell_docs_url>, then writes the results to
shell/src/data/docs-status.json. shell-internal's DocsRow falls back to this
probed state for (integration, feature) cells that don't have a per-column
docs-links.json override. Exposes the script as `probe-docs` in
showcase/scripts/package.json.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports the loadDocsLinks() helper from 4084 into generate-registry.ts. After
schema validation, each integration gets a docs_links field merged in from its
sibling packages/<slug>/docs-links.json (best-effort: missing file or stale
shell_docs_url shape is tolerated). The shell-internal DocsRow reads this via
integration.docs_links.features[<id>] to prefer curated per-column overrides
over the per-feature defaults.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add prebuilt-chat feature to the registry and constrained-explicit
allowlist so the 4085 manifest's intentional prebuilt-chat wiring
validates cleanly (4084 omits it; 4085 committed the cell earlier)
- Bump expected langgraph-python feature count in generate-registry
test from 32 to 33 to reflect the new row
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three interlocking fixes to generate-starters.ts that together restore
langgraph-fastapi and langgraph-typescript /api/health to "ok" after
#4099 shipped an entrypointOverride feature that accidentally reverted
earlier fixes and left gaps for the TS starter.
1. Resolution-aware python import rewrite for langgraph starters.
langgraph_cli loads agent modules standalone (not as packages), so the
generator rewrites `from .X import ...` to absolute form. The previous
flat rewrite produced `from <agentDir>.X import ...`, and the
subsequent subdir-aware variant used the file's OWN dir as the
package. Both assume sibling targets sit alongside the importing file.
langgraph-fastapi violates that: agent.py lives at
`src/agents/src/agent.py` but the `tools/` it imports lives at
`src/agents/tools/` -- one level up, not inside `src/`. Either prior
rewrite produced `from src.agents.src.tools import ...`, a path that
doesn't exist, and langgraph_cli crashed on module import with
`ModuleNotFoundError: No module named 'src.agents.src.tools'` before
ever binding 8123.
Walk UP from the file's own dir toward agentDest and rebase the
absolute import on the shallowest directory that actually contains
`<firstSeg>/` or `<firstSeg>.py`. Correct both for co-located imports
(`tools/get_weather.py` importing `.types`) and for sibling-directory
imports (`src/agent.py` importing `.tools` from `../tools`).
2. Merge langgraph-typescript agent runtime deps into root package.json.
The TS Dockerfile deliberately deletes `agent/package.json` to
collapse the ESM package boundary between the Next.js frontend and
the agent subtree. Without merging the agent's runtime deps up into
the root package.json, the langgraph-cli's runtime import of
graph.ts fails with `Cannot find module '@langchain/openai'` and
the agent never binds 8123.
Add `extraDependencies` to the langgraph-typescript framework def
with @langchain/core, @langchain/langgraph,
@langchain/langgraph-checkpoint, @langchain/langgraph-cli,
@langchain/openai, and @copilotkit/sdk-js.
3. Restore AGENT_LOG_PREFIX process-substitution helper.
#4099 inadvertently replaced `cmd &> >(awk … fflush …)` with
`cmd 2>&1 | sed …` across every getEntrypointBlock branch. After a
pipeline `$!` points at `sed`, not the agent, so `kill -0 $AGENT_PID`
and `wait -n $AGENT_PID` monitor the wrong process and mask real
crashes; `sed` also line-buffers so crash output can be lost.
Restore the helper so the committed starter entrypoint.sh files and
regenerated output stay consistent.
generate-starters.ts:
- Add entrypointOverride?: boolean to FrameworkDef so slugs whose boot sequence
diverges from the shared template (e.g. langroid's provider-aware credential
guard) can preserve their committed entrypoint.sh through regeneration.
- Snapshot the override from STARTERS_DIR/<slug>/entrypoint.sh (canonical
committed path), NOT outDir — so --check mode (which passes a temp outDir)
doesn't false-flag the override as drift.
- Force mode: 0o755 on the restored file regardless of source mode so editors
that strip the executable bit can't silently ship a broken starter.
- Throw (not warn) when an entrypointOverride slug is missing the override
file, when declared extraFiles / agent_server.py / Dockerfile are missing,
and when PIN_OVERRIDES references a stale dep. These are repo-integrity
failures — silent-warn-and-continue ships broken starters.
- Strip unused `import sys` after sys.path.insert removal (pair with the
existing osUsed detection).
Opt langroid in via entrypointOverride: true.
Also adds a regression test in __tests__/generate-starters.test.ts that
calls generateStarterToDir against a tmp dir and asserts the emitted
entrypoint.sh byte-equals the committed langroid starter entrypoint,
plus a negative test that a non-override slug does NOT preserve its file.
Port the 4084 simplification of the status generator: remove the
MOCK_VARIANTS seeding table + mockVariantTest / mockVariantQA /
mockVariantHealth helpers + Variant type. Informational demos (no
route) are skipped from health probing. The status JSON no longer
carries per-demo `variants[]` — the shell-internal dashboard renders
one row per demo without variant expansion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port the 4084 scripts-layer enhancements so 4085's showcase toolchain
matches the new feature shape:
- lib/manifest.ts: ManifestDemo gains optional `command` field; parser
accepts + validates it (non-empty string, frozen).
- bundle-demo-content.ts: inline `@region[name]` / `@endregion[name]`
comment-marker extraction; informational-only demos (no route, e.g.
cli-start) are skipped; markers stripped from bundled content;
regions: { file, startLine, endLine, code, language } emitted per
demo. External-highlight-file merging (4085-specific) preserved, so
backend agents under src/agents/*.py still flow into the bundle.
- validate-parity.ts: accepts demos at BOTH demos/<cell>/ (4084 layout)
and src/app/demos/<cell>/ (4085 layout); informational demos
(command field) are excluded from the parity audit.
- tests: bundle-demo-content.test.ts expectedDemos updated for the
shared-state rename; generate-registry.test.ts feature count 25→32;
validate-parity.test.ts missing-demo-dir message updated to match
the new dual-location wording.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Regenerate agno starter to pick up agno>=2.5.17 (from #4095)
- Ratchet validate-pins fail-baseline hash to match new FAIL set
(count unchanged at 110; hash rotates because agno Dojo/showcase
pair now reflects the SDK upgrade)