Commit Graph

10404 Commits

Author SHA1 Message Date
Jordan Ritter c82e37851d style: auto-fix formatting 2026-06-03 12:06:10 -07:00
Jordan Ritter 93f54beddc feat(react-core): serialize CopilotChat sends per agent via a chained queue
Replace the bare `await copilotkit.runAgent()` send path with a per-agent
chained queue. enqueueSend captures the agent at enqueue time, chains off the
prior send for that agent (swallowing rejections so the chain never breaks),
adds the user message, then holds the slot on
copilotkit.runAgentCompletion(agent) until the run genuinely completes. Both
onSubmitInput and handleSelectSuggestion route through enqueueSend. Keying by
agent instance means switching agents starts a fresh, independent chain.

This prevents the gen-ui / event-timing races where a second send begins (adds
its message, kicks a new run) while the prior run's event pipeline is still in
flight. Because the queue gates on completion, by the time a later run reaches
`await agent.detachActiveRun()` the prior run has fully settled, so detach is a
clean no-op.

Adds MockRunLifecycleAgent + Deferred test infra (controllable start/completion
gates, failBeforeStart, call-order recording) — the Subject-based
MockStepwiseAgent cannot reproduce these races — and an E2E suite covering all
four races plus the abort-releases-queue case.
2026-06-03 12:05:01 -07:00
Jordan Ritter 7633e333c5 feat(core): expose run-completion promise synchronously from runAgent
Split RunHandler.runAgent into a synchronous public wrapper plus a private
_runAgentInner so the inner run promise can be captured BEFORE any await. For
top-level runs (not recursive tool follow-ups), register a normalized
completion promise in a per-agent WeakMap, exposed via runCompletion(agent) and
surfaced on CopilotKitCore as runAgentCompletion(agent).

The promise resolves (never rejects) once the run settles either way, including
a pre-RUN_STARTED rejection. This closes the structural seam in
intelligence-agent.ts, where activeRunCompletionPromise is only assigned AFTER
await onInitialize — too late for a caller to gate on. A synchronous handle lets
the CopilotChat send queue hold a slot until the prior run genuinely finishes.
2026-06-03 12:04:51 -07:00
github-actions[bot] 0c5a2d37df style: auto-fix formatting 2026-06-03 11:44:06 -07:00
Jordan Ritter 9296a2a2e5 feat(ci): tag starter-smoke Slack alerts with their source repo
The starter smoke-test Slack alert had no indication of where it came
from, which is ambiguous when the same workflow runs across multiple
repos (e.g. the public CopilotKit/CopilotKit repo vs the internal
testybara fork). Prepend a `[ci:<owner/repo>]` tag derived from
github.repository so triage is unambiguous about the source. These CI
alerts test example source and carry no staging/production dimension,
so [ci] is the meaningful source axis. Existing message format is
otherwise preserved.
2026-06-03 11:44:06 -07:00
Jordan Ritter 2b626109c3 feat(showcase/harness): label every Slack alert with its source environment
Prefix every harness-dispatched alert with a `[staging]`/`[production]`/
`[unknown]` source-env tag so operators triaging a red probe know which
deploy environment is affected. The label is derived in the orchestrator
from SHOWCASE_ENV ?? RAILWAY_ENVIRONMENT_NAME ?? "unknown" and applied at
the single renderer chokepoint (covering per-key, cron, and on-error
dispatch) plus the aggregation flush path that bypasses the renderer, via
a shared sourceEnvPrefix helper so the two paths never drift. A missing
env var surfaces as a visible [unknown] rather than a silent un-prefixed
alert.
2026-06-03 11:44:06 -07:00
github-actions[bot] 283b1f8473 style: auto-fix formatting 2026-06-03 11:30:58 -07:00
Jordan Ritter 6d2f5885d2 test(showcase/harness): cover browser-pool crash-recovery, self-heal, and serve-retry paths
- STAGING-OUTAGE regressions: degraded alarm fires (not silent) when the
  set empties from a relaunch storm; self-heal re-inits a fresh set once the
  kernel relaxes; a waiter queued during the dead window is served by
  self-heal; a transient relaunch EAGAIN is retried and the entry survives
  (no eviction, no alarm).
- Bounded serveNextWaiter transient re-drive + FIX#7 dead-vs-alive gate
  propagation to the serve path.
- orchestrator: degraded/recovered signal wiring covered.
- BUG3 (orphan-by-recycle waiter drain) adapted to the crash-recovery
  acquire path: an acquire whose in-flight open is orphaned re-enqueues as a
  waiter; with cap=1 the freed slot goes to the other waiter, so the orphaned
  acquire settles via its own (now bounded) timeout. The invariant it
  verifies (freed capacity immediately serves the queued waiter) is unchanged.
2026-06-03 11:30:58 -07:00
Jordan Ritter 2b3e18bb78 fix(showcase/harness): raise container PID/thread ulimit for the chromium pool
Lift the soft nproc limit to the hard ceiling (`ulimit -u $(ulimit -Hu)`)
before exec'ing the orchestrator so the legitimate 40-context chromium
workload (several hundred OS threads at steady state) has ample thread
headroom instead of running near the default ~1024 soft ceiling, where
`chromium.launch()` tripped `pthread_create: Resource temporarily
unavailable`. `exec` keeps node as PID 1 for correct signal handling; the
`|| true` fallback keeps boot resilient when the runtime forbids raising
the soft limit (the cgroup pids limit then remains the dominant control).
2026-06-03 11:30:58 -07:00
Jordan Ritter 098909992e fix(showcase/harness): harden browser-pool crash recovery, self-heal, and bounded serve retry
Make the long-lived chromium pool survive a pthread/PID-ceiling
(`pthread_create: Resource temporarily unavailable`, errno 11) thread-
exhaustion storm instead of draining to an empty, permanently-wedged set.

- Crash-recovery relaunch backpressure: a transient EAGAIN on relaunch is
  retried with bounded linear backoff before the entry is evicted, so a
  thread-exhaustion window that relaxes within seconds recovers in place
  rather than splicing the entry out of the set.
- Self-heal + degraded/recovered alarm: when the set empties mid-life the
  pool fires an `onDegraded` red alarm (previously only emitted on init()
  failure — mid-life death was silent) and kicks a background self-heal
  loop that relaunches a fresh set the moment a launch succeeds, firing
  `onRecovered`. No manual redeploy required.
- Bounded serveNextWaiter transient re-drive: a persistently-transient
  newContext() on a still-connected browser no longer hot-loops the event
  loop; it self-reschedules up to a ceiling then leaves the waiter queued
  for a later release/recovery handoff (mirrors acquire()'s retry-once
  semantics).
- Accounting hardening: generation-token guard on in-flight opens across a
  recycle, clamped servedContexts rollback on orphan-close, deferred-recycle
  re-check on non-release teardown paths, and waiter-drain on orphan-by-
  recycle rollback so freed capacity is served immediately.
- orchestrator wires the pool's onDegraded/onRecovered hooks to the shared
  `system:browser-pool-degraded` red/green capacity-loss signal.

Fixes the 2026-06-03 staging incident: the browser pool died from thread
exhaustion, the relaunch storm emptied the set, and the harness wedged with
no alarm -> 626 D0-red cells until a manual redeploy.
2026-06-03 11:30:58 -07:00
Sam Julien 74e95684c8 docs: route all documentation authoring to shell-docs (#5184)
## Problem

The top-level `docs/` app is retired, but nothing in the repo said so,
and contributors (and agents) kept editing it. Two parallel docs trees
plus a one-directional legacy sync script made it ambiguous where
documentation should be authored:

- `docs/content/docs/` — the old Fumadocs app, no longer publishing
- `showcase/shell-docs/src/content/docs/` — the live source for
docs.copilotkit.ai

Two instruction surfaces actively pointed the wrong way: `CLAUDE.md`
said nothing about docs at all, and `.claude/docs/hooks.md` told
contributors to "add a docs page under `/docs`" (the retired location).

## Change

Establish one canonical rule and reduce the other surfaces to pointers:

- **`.claude/docs/documentation.md`** (new) — source of truth.
CopilotKit docs are authored in `showcase/shell-docs/src/content/`
(`docs/`, `reference/`, `snippets/`, `framework-overviews/`); the
top-level `docs/` folder is retired; AG-UI protocol docs are authored
upstream in `ag-ui-protocol/ag-ui` (publishing to docs.ag-ui.com) and
mirrored into `content/ag-ui/`.
- **`CLAUDE.md`** — adds an Essentials hard-rule and a Reference link.
- **`docs/README.md`** — replaces boilerplate with a retired/STOP
banner; legacy README retained under a `<details>`.
- **`.claude/docs/hooks.md`** — fixes the stale `/docs` pointer and
clarifies that a hook's API reference page lives in
`reference/hooks/<hookName>.mdx`, where v2 reference navigation is
generated automatically from frontmatter (no `meta.json`); conceptual
guide pages under `docs/` still use `meta.json`.
- **`CONTRIBUTING.md`** — adds a two-domain documentation section for
human contributors.

## Notes

- Two docs domains: **CopilotKit docs** → shell-docs; **AG-UI protocol
docs** → upstream `ag-ui-protocol/ag-ui`, then synced into the in-repo
mirror.
- Instructions-only change; no enforcement hook or sync-process change.
- Markdown only; no package code touched.
2026-06-03 11:03:32 -07:00
github-actions[bot] 43c448b5d4 style: auto-fix formatting 2026-06-03 17:06:19 +00:00
Sam Julien 69673f2ea7 docs: route all documentation authoring to shell-docs
The top-level docs/ app is retired but nothing said so, and two
instruction surfaces still pointed contributors there. Establish a
single canonical rule and reduce the other surfaces to pointers.

- Add .claude/docs/documentation.md as the source of truth: CopilotKit
  docs are authored in showcase/shell-docs/src/content/; the top-level
  docs/ folder is retired; AG-UI protocol docs are authored upstream in
  ag-ui-protocol/ag-ui and mirrored here.
- CLAUDE.md: add an Essentials rule and a Reference link.
- docs/README.md: replace boilerplate with a retired/STOP banner.
- .claude/docs/hooks.md: fix the stale /docs pointer; document that a
  hook's API reference page lives in reference/hooks/ and that v2
  reference nav is generated from frontmatter (no meta.json).
- CONTRIBUTING.md: add a two-domain documentation section.
2026-06-03 10:04:56 -07:00
Ben Taylor 6c834f83d8 fix(runtime): Properly derive thread names from agents when generating them (fix ENT-732) (#5156)
## Summary
- Select generated thread titles only from assistant text messages
returned by the naming run.
- Reject JSON-shaped title output unless it parses to an object with a
string title.
- Add Runtime regression coverage for tool-result suffixes and invalid
JSON title payloads.

## Validation
- pnpm nx run @copilotkit/runtime:test --
src/v2/runtime/__tests__/thread-names.test.ts
src/v2/runtime/__tests__/handle-run.test.ts (passed, 56 tests)
- pnpm nx run @copilotkit/runtime:build (passed)
- Pre-commit package checks passed
- pnpm nx run @copilotkit/runtime:check-types (blocked in dependency
task @copilotkit/shared:check-types: missing
LicenseContextValue/LicenseMode exports from
@copilotkit/license-verifier and telemetry index error)
- NODE_OPTIONS=--max-old-space-size=8192 pnpm nx run
@copilotkit/runtime:check-types --excludeTaskDependencies (failed: tsc
heap out of memory near 8 GB)
2026-06-03 09:05:59 -05:00
Jordan Ritter dfc72f1a7b fix(showcase/harness): harden browser-pool non-release teardown paths (#5174)
## Summary

Follow-up to #5173 (bucket-(d)) closing three **pre-existing**
browser-pool concurrency defects on the non-release teardown paths. The
browser-pool is a context-pool over a fixed set of long-lived Chromium
processes; these defects biased the hygiene-recycle cadence and could
strand waiters / leak deferred recycles.

## Fixes (each red-green proven)

1. **`serveNextWaiter` orphan-close leaked `servedContexts`.** A waiter
timing out mid-`openContextOn` cleaned up the reservation/context but
never decremented `servedContexts` (which `openContextOn` had already
`++`'d) → every orphaned-by-timeout serve permanently inflated the count
→ premature hygiene recycles. Fix: decrement `servedContexts` in the
orphan-close block.
2. **Deferred `recyclePending` honored only on `release()`.** A recycle
deferred because `pendingOpens > 0` set `recyclePending`, but the
non-release teardown paths (orphan-by-recycle rollback, orphan-close)
returned the entry to idle without re-checking it → the deferred recycle
was dropped and the browser exceeded `recycleAfter` indefinitely. Fix:
shared `maybeFireDeferredRecycle(entry)` helper called on both
non-release teardown paths.
3. **`openContextOn` rollback didn't drain waiters.** The
orphan-by-recycle rollback freed a reservation but never
`scheduleServeNextWaiter()` → queued waiters could stall with free
capacity until an unrelated release. Fix: `scheduleServeNextWaiter()`
after the rollback.

## Verification
- Red-green for all three (reproduced each bug, then green).
- Full harness vitest: **1702–1705 passed**; browser-pool suite
**27/27** (mutation-tested — reverting fix #3 fails its guard). `tsc
--noEmit` exit 0.
- Public API + `BROWSER_POOL_MAX_CONTEXTS` default untouched.

## Reviewed
7-agent unbiased CR (cr-loop): the three fixes confirmed sound; one
ordering concern on the new code investigated and **refuted**
(sole-browser relaunch-failure rejects the waiter rather than stranding
it).

## Known further hardening (separate effort — NOT in this PR)
The CR surfaced additional **pre-existing** browser-pool reliability
bugs that warrant a dedicated hardening pass, independently flagged by
multiple reviewers:
- `shutdown()` vs in-flight `openContextOn` → leaked context (no
`isShutdown` re-check post-`newContext`); recycles added to
`inFlightRecycles` after shutdown's snapshot not awaited.
- crash-reason `recycleBrowser` abandons live contexts without
`.close()` — leaks on the non-dead `acquire`-retry path.
- `acquire` retry treats a transient `newContext` failure as a full
crash → recycles the whole browser, tearing down unrelated live contexts
(correlated flakiness).
- `launchChain` launch gate has no timeout → a single hung
`chromium.launch()` permanently deadlocks all relaunches.
- `parseInt` env parsing silently accepts trailing garbage
(`MAX_CONTEXTS=24x` → 24); no warning.
- context-close failures swallowed via bare `.catch(() => {})`
(inconsistent with `closeBrowser`'s logged path).
- relaunch-failure eviction only rejects waiters when the pool is fully
empty (doesn't redistribute onto surviving browsers); `pickLeastLoaded`
tie-break concentrates load on browser 0.
2026-06-02 20:56:33 -07:00
github-actions[bot] e032c514de style: auto-fix formatting 2026-06-03 02:43:19 +00:00
Jordan Ritter 57ff0d332f fix(showcase/harness): close three browser-pool non-release teardown concurrency defects
Bug 1: serveNextWaiter orphan-close (timed-out waiter mid-open) now mirrors
openContextOn's servedContexts++ with a decrement, so an orphaned serve no
longer permanently inflates servedContexts and biases the hygiene recycle to
fire early.

Bug 2: a hygiene recycle deferred via the release-path shouldRecycle&&hadWaiter
guard is now re-checked on the NON-release teardown paths (openContextOn
orphan-by-recycle rollback and serveNextWaiter orphan-close) via a shared
maybeFireDeferredRecycle helper, so the deferred recycle still fires when the
entry's last activity ends without a release() — previously it was dropped and
the browser exceeded recycleAfter indefinitely.

Bug 3: openContextOn's orphan-by-recycle rollback now calls
scheduleServeNextWaiter() so freed capacity immediately drains queued waiters
instead of stalling them until the next unrelated release/recycle handoff.

Adds three red-green regression tests (BUG1/BUG2/BUG3) to the browser-pool
suite. Public API and MAX_CONTEXTS default unchanged.
2026-06-02 19:32:26 -07:00
Jordan Ritter 90894901a2 fix(showcase): regression campaign — tool-rendering/calc fixtures, dashboard links, deploy guard, D6 ladder-gating, browser-pool cap (#5173)
## Fixes

- **Gate LGP tool-rendering AAPL + Find-flights fixtures on `toolName`**
(not `hasToolResult`): the AAPL tool-rendering and Find-flights
first-leg fixtures now key off `toolName` so the right tool renders.
Local proof: tool-rendering AAPL + Find-flights run **local D6 green**.
- **Restore sandboxed-UI `jsFunctions` in `gen-ui-open-advanced`
fixtures** (agno, crewai-crews, langgraph-fastapi, langgraph-python,
mastra): the sandboxed Calculator/Ping `jsFunctions` were missing, so
the calculator never computed. Local proof: calc **`=` → 4
browser-verified**.
- **Resolve dashboard links to the real shell host via server-threaded
`shellUrl`**: the dashboard tree was entirely `"use client"`, so
`getRuntimeConfig()` returned the `ssr-placeholder.invalid` SSR sentinel
and baked dead hrefs into every Demo/Code link. `page.tsx` is now a
server component that reads the real host server-side and threads it
into the client `DashboardPage`. Local proof: **SSR links click → real
demo, verified**.
- **Fail `verify-deploy` on env-unset config sentinel + robust config
extractor**: when `SHELL_URL` is unset the server config returns the
`about:blank#shell-url-missing` sentinel; the deploy guard now fails
loud on it rather than shipping dead links, with a hardened config
extractor. Local proof: **deploy-guard red-green**.
- **Gate Coverage D6 badge + stats by the depth ladder; gated indicator
only on genuine lower-rung failure**: D6 is the top of the verification
ladder, so a green D6 claim is only valid when the ladder through D5 is
intact. New `d6Effective` collapses to gated (`—`) when a lower rung
genuinely fails (never on no-data), keeping the badge, stat, regression
flag, and chip in agreement. Local proof: **D6-gating full-suite 790
green incl dashboard-color-matrix 54/54**.
- **Raise browser-pool default `MAX_CONTEXTS` to 40 + correct pool
docs**: contexts (not chromium processes) are the scaling knob since the
PID ceiling of 1000 is the binding constraint; D6 peak 32 + D5 peak 8 =
40. Probe cadence/docs corrected to match. Local proof: **pool
MAX_CONTEXTS=40 locally proven, 50 PIDs ≪ 1000**.

## CR

Converged via 4 unbiased 7-agent cr-loop rounds + 2 fix rounds.

## Known follow-ups (not in this PR)

- **(d) browser-pool concurrency hardening** — `servedContexts`
inflation on `serveNextWaiter` orphan-close, `recyclePending` deferral
on non-release teardown, and waiter-drain on `openContextOn` rollback.
These are pre-existing pool internals; separate PR.
- **(b/c) minor cosmetic / naming items** — `DocsRow` unused `shellUrl`
prop; `computeColumnTallyDetail` labels a D6-absent amber as `"e2e"`;
the agno `gen-ui-open-advanced` `_meta` note is misleading but is the
SOLE source of agno Calculator/Ping fixtures (do NOT delete); `API=d3`
vs `d2` naming; `resolveD3` has no effective stale row (pre-existing);
`e2e-deep.yml` stale primary-key comment.
- **react-core consecutive-interrupt run-state fix** — a SEPARATE
pending branch; the `gen-ui-interrupt` cell needs it.
2026-06-02 19:26:06 -07:00
github-actions[bot] 33d7eef4e4 style: auto-fix formatting 2026-06-03 00:58:59 +00:00
Jordan Ritter 07d230ae9c fix(showcase): raise browser-pool MAX_CONTEXTS default to 40 + correct pool docs 2026-06-02 17:57:48 -07:00
Jordan Ritter 9125ec5b81 fix(showcase): gate Coverage D6 badge + stats by depth ladder; gated indicator only on genuine lower-rung failure 2026-06-02 17:57:48 -07:00
Jordan Ritter 64461e1704 fix(showcase): fail verify-deploy on env-unset config sentinel + robust config extractor 2026-06-02 17:57:48 -07:00
Jordan Ritter 7d097feabc fix(showcase): resolve dashboard links to real shell host via server-threaded shellUrl 2026-06-02 17:57:47 -07:00
Jordan Ritter b6b35bd406 fix(showcase): restore sandboxed-UI jsFunctions in gen-ui-open-advanced fixtures 2026-06-02 17:57:47 -07:00
Jordan Ritter 3ca0a26f11 fix(showcase): gate LGP tool-rendering AAPL + Find-flights fixtures on toolName 2026-06-02 17:57:47 -07:00
Jordan Ritter a835b41e3e fix(showcase): bump @ag-ui/langgraph to 0.0.36 in langgraph integrations (#5171)
## Summary

Carries [ag-ui PR
#1784](https://github.com/ag-ui-protocol/ag-ui/pull/1784)
("fix(langgraph): skip regeneration check when `command.resume` is set")
into the three langgraph showcase stacks, greening the
`gen-ui-interrupt` D6 cell.

- ag-ui `@ag-ui/langgraph` 0.0.35 (and earlier) incorrectly ran the
regenerate path on a **resumed** run, tripping the regeneration trap and
breaking gen-ui-interrupt. `0.0.36` adds the `command.resume` guard so a
resume skips the regeneration check.
- Pins `@ag-ui/langgraph` `0.0.36` via npm `overrides` (it is a
**transitive** dep of `@copilotkit/runtime`) in `langgraph-python`,
`langgraph-typescript`, and `langgraph-fastapi`.
- Regenerates each integration's `package-lock.json` (python/fastapi
regenerated with `--legacy-peer-deps`, matching their Dockerfile `npm ci
--legacy-peer-deps`).

## Files changed

-
`showcase/integrations/langgraph-typescript/{package.json,package-lock.json}`
-
`showcase/integrations/langgraph-python/{package.json,package-lock.json}`
-
`showcase/integrations/langgraph-fastapi/{package.json,package-lock.json}`

Lockfiles flip `@ag-ui/langgraph` 0.0.34 → 0.0.36 and pull in 0.0.36's
new transitive dep `@ag-ui/a2ui-toolkit@0.0.1-alpha.3`.

## Verification

- `@ag-ui/langgraph@0.0.36` is published to npm `latest`; its
`dist/index.js` contains the `!command?.resume` regeneration guard from
#1784.
- All three regenerated lockfiles resolve
`node_modules/@ag-ui/langgraph` to `0.0.36`.

## Test plan

- [ ] CI green
- [ ] gen-ui-interrupt D6 cell green for langgraph-python,
langgraph-typescript, langgraph-fastapi after showcase rebuild + re-run
2026-06-02 16:54:50 -07:00
Jordan Ritter 72156dbb83 fix(showcase): bump @ag-ui/langgraph to 0.0.36 in langgraph integrations
Carries ag-ui PR #1784 (skip regeneration check when command.resume is
set) into the three langgraph showcase stacks. ag-ui 0.0.35 incorrectly
ran the regenerate path on a resumed run, breaking the gen-ui-interrupt
D6 cell. 0.0.36 adds the command.resume guard.

Pins @ag-ui/langgraph 0.0.36 via npm overrides (transitive dep of
@copilotkit/runtime) in langgraph-python, langgraph-typescript, and
langgraph-fastapi, and regenerates each per-integration package-lock.json.
2026-06-02 15:17:41 -07:00
Tyler Slaton f878892761 docs(shell-docs): recommend v2 CopilotKit provider import (#5163)
## Problem

Shell-docs had conflicting v2 guidance around the provider import path.
Some migration/reference/quickstart pages either recommended
`CopilotKitProvider` or kept `CopilotKit` examples on the root
`@copilotkit/react-core` package even though v2 docs should import the
`CopilotKit` component from `@copilotkit/react-core/v2`.

## Why

The correct recommendation is the `CopilotKit` component name, imported
from the v2 entrypoint. Leaving root-package imports in v2-facing docs
makes the migration and reference guidance contradict the v2 package
layout.

## Fix

- Recommend `CopilotKit` from `@copilotkit/react-core/v2`, not
`CopilotKitProvider`.
- Update v2 migration, reference, and quickstart examples to use the v2
provider/style entrypoints.
- Leave root `@copilotkit/react-core` imports only in v1 docs and
explicit migration “Before” examples.
- Add regression coverage for stale provider/style package paths.
- Fix the shell-docs SignupLink SSR test typing exposed by typecheck.

Closes #5153
2026-06-02 15:17:09 -07:00
Jordan Ritter fa5a84cdba Fix OPS_BASE_URL so dashboard Ops tab resolves the harness probe endpoint (#5168)
## Summary

The showcase dashboard's Ops tab fetches `/api/ops/*` as a same-origin
path, which the Route Handler at
`shell-dashboard/src/app/api/ops/[...path]/route.ts` forwards at request
time to `${OPS_BASE_URL}/api/*` on the showcase-harness HTTP origin (the
service that serves `/api/probes`).

In the local compose stack, `OPS_BASE_URL` was set to
`http://localhost:3200` — the dashboard's own host. The proxy therefore
looped back into the dashboard instead of reaching the harness, so the
probe-trigger endpoint failed (self-referential 500/503) and the Ops
live-probe grid could not resolve.

This points `OPS_BASE_URL` at the harness origin over the compose
network: `http://showcase-harness:8080`. The harness `Dockerfile`
EXPOSEs `8080` and `orchestrator.ts` binds `PORT ?? 8080`, so the
dashboard reaches `/api/probes` by container name on the internal port.
This mirrors staging, where the dashboard's `OPS_BASE_URL` likewise
points at the harness origin rather than at itself.

Scope: a single build-arg value in `showcase/docker-compose.local.yml`
(plus an updated explanatory comment). `OPS_BASE_URL` is read at request
time by the Route Handler, so this only seeds the runtime default — no
build-time resolution required.

## Test plan

- [ ] Dashboard Ops tab loads without a 500/503 from the ops proxy
- [ ] Probe-trigger endpoint (`/api/ops/probes` POST) returns 2xx,
forwarded to the harness `/api/probes`
- [ ] Ops live-probe grid renders harness data (harness running on the
compose network as `showcase-harness`)
2026-06-02 14:29:36 -07:00
Jordan Ritter b4a396c831 Env-gate LOCAL_SERVICES_JSON injection in harness service discovery (#5167)
## Summary

Adds a permanent, env-gated injection seam to the showcase harness
service discovery
(`showcase/harness/src/probes/discovery/railway-services.ts`). When
`LOCAL_SERVICES_JSON` is set, the harness (especially the
d6-all-pills-e2e driver) runs against **LOCAL** backend services instead
of performing Railway discovery — enabling apples-to-apples LOCAL D6
verification without Railway credentials.

- **Zero behavior change when unset/empty.** An unset or empty
`LOCAL_SERVICES_JSON` takes the byte-identical Railway discovery path —
the seam is fully transparent in the default configuration.
- **`demos` plumbed end-to-end (load-bearing).** The injected service
records carry `demos` all the way through. This matters: empty `demos`
would short-circuit the D6 driver into a false 15ms zero-cell "green,"
masking real failures. Plumbing `demos` end-to-end is what makes the
LOCAL path a faithful stand-in for Railway discovery.
- **Enables apples-to-apples LOCAL D6 verification** — run the full pill
suite against local services with the same code path shape as staging.

## Notes

- 8 new tests covering the `LOCAL_SERVICES_JSON injection` path (66
tests total in `railway-services.test.ts`, all passing).
- Env-gated: no Railway credentials required when services are injected.
- The injection seam executes the real discovery code (not mocked).

## Test plan

- [ ] `tsc --noEmit` (typecheck) exits 0
- [ ] `railway-services.test.ts` passes (66 tests, incl. all 8
`LOCAL_SERVICES_JSON injection` tests)
- [ ] Injection seam executes against the real code path (verified via
`discovery.railway-services.local-injection` log emission, not a mock)
- [ ] Unset/empty `LOCAL_SERVICES_JSON` produces a byte-identical
Railway discovery path (zero behavior change)
- [ ] `demos` plumbed end-to-end through injected service records
(guards against false zero-cell D6 green)
2026-06-02 14:29:31 -07:00
Jordan Ritter 6cc06209c9 Resolve dashboard D6 per-cell via enum keys + correct depth/D6 stats (#5170)
## Summary
- **Fix per-cell D6 resolution**: the dashboard's `resolveD6` now reads
per-cell ENUM keys (`d6:<slug>/<featureType>` via `CATALOG_TO_D5_KEY`
fan-out) instead of the integration aggregate — D6 cells render real
per-cell green/red instead of all-gray.
- **Depth + D6 surfaced by default**: `DEFAULT_OVERLAYS = [links,
health, depth]` (the per-cell D6 badge rides on the Health layer; the
depth chip folds D6).
- **Correct the aggregate stats bar** (`page-stats.ts` extraction): D6
cells were dropped from the depth distribution (`dist["d6"]++` → `NaN`,
masked by an `as` cast); gray/no-data cells were counted as green;
`d6Stats` swallowed amber into gray. Now renders the reachable buckets
**D0/D3/D4/D5/D6** (dropped permanently-zero D1/D2), tracks `noData` and
`degraded` distinctly, and validates `parity_tier` fail-loud.
- **Test coverage**: new table-driven cell color/rollup matrix
(`dashboard-color-matrix.test.tsx`) + `page-stats.test.ts` +
order-independence/comment hardening.
- **CR fixes**: effective (stale-downgraded) `.row` from cell-model
resolvers, `WORST_STATE_RANK` rename, composed-cell memo keys,
overlay-types re-export dedup, `setTab` persistence + `window.location`
test stub, `page.tsx` shared-`now` threading + 60s staleness re-render.

## Verification
- **776 tests pass / 1 skip**, `tsc --noEmit` clean, Next.js production
build OK.
- **Local visual proof**: seeded enum D6 rows (30✓/8✗/0 gray) into a
local PocketBase, rebuilt this branch, screenshotted bare `#matrix` —
per-cell D6 green/red renders, and the depth distribution shows `D6:30
D5:8 D4:0 D3:0 D0:588`.
- **7-agent code review converged** over 3 confirmation cycles.

## Notes
- The **#5152 `d6-all-pills` driver must stay on enum keys** (matches
this dashboard's `resolveD6` + the `d5-mapping-drift` test). Do not
revert it to raw catalog keys.
- **Follow-up (separate PR)**: stats-bar single-source-of-truth
hardening (`computeD6Stats`→`buildCellModel`; reconcile
`resolveD6Row`/`resolveD5Row` to the effective row; thread `connection`
into stats for SSE-offline; docs-only stats exclusion); delete
deprecated `composed-cell.tsx` + `deriveDepth`; `useOverlays` mount-hash
deep-link clobber; Notion visualization-doc sync to per-cell
`resolveD6`.

## Test plan
- [ ] CI green
- [ ] After staging deploy, dashboard renders per-cell D6 green/red (not
all-gray) for LangGraph-Python
2026-06-02 13:55:16 -07:00
github-actions[bot] a8f1544286 style: auto-fix formatting 2026-06-02 20:51:16 +00:00
Jordan Ritter 7b0e663b22 fix(showcase): render reachable depth buckets (D0..D6) in stats bar, drop dead D1/D2
The depth-distribution row showed permanent-zero D2/D1 rows while the computed
D0 bucket (wired-but-unverified cells) was never rendered, so wired cells
vanished from the row and it never summed to the "Wired" count.
buildCellModel().achievedDepth is typed 0|3|4|5|6 and can never be 1 or 2.

- Remove unreachable d1/d2 from the DepthDistribution type, from
  computeDepthDistribution's init, and from the rendered levels array.
- Add a D0 row to the rendered levels so wired-unverified cells are visible
  and the distribution sums to the wired-cell count (D6,D5,D4,D3,D0).
- Reconcile section wrapper keys: use each section's stable key instead of the
  array index so overlay-toggle reconciliation is correct.
- Document that health/depth/d6 rollups need no dedup: catalogData.cells is
  one row per (integration, feature) grid cell (verified: 0 duplicate pairs),
  so these per-cell signals are counted exactly once.
2026-06-02 13:30:35 -07:00
Jordan Ritter 13d7fa5ee5 fix(showcase): correct aggregate-stats divergence from per-cell model
Extract the AdaptiveStatsBar aggregate computations out of page.tsx into a
unit-testable pure module (src/lib/page-stats.ts), mirroring the
computeColumnTally pattern, and fix a cluster of correctness bugs:

1. D6 cells were dropped from the depth distribution: DepthDistribution
   lacked a `d6` key and the `\`d${depth}\` as keyof` cast produced
   `dist["d6"]++ === NaN`. Add `d6` to the type, render a D6 row in
   DepthDistributionSection, and replace the cast with an exhaustive
   Record<0|3|4|5|6, keyof DepthDistribution> map the compiler checks.
2. d6Stats folded amber (stale/degraded D6) into gray. Count degraded
   distinctly and surface it in D6Section.
3. healthStats counted gray (no-data) cells as green, contradicting the
   "stats bar matches the matrix" invariant. Track no-data separately and
   render it in HealthSection.
4. isSupported is correctly hardcoded true for wired-cell stats: a wired
   catalog cell can never be in not_supported_features (generate-registry
   resolves those to status "unsupported" before "wired"), so stats and
   renderCell cannot diverge. Comment updated to state the invariant.
5. parity_tier was indexed via an unchecked cast (unknown tier →
   `undefined++ === NaN`). Validate against the known tier set and skip +
   log loud on unknown.

The matrix render path (renderCell/buildCellModel) is untouched.
2026-06-02 13:19:43 -07:00
github-actions[bot] 8d071aa8a0 style: auto-fix formatting 2026-06-02 13:11:43 -07:00
Jordan Ritter 036ecf546a feat(showcase): single-service promote dropdown + unattended hardened promote
Convert the promote workflow's `service` input from a freeform string
(default "all", the accidental-fleet-promote footgun) to a generated
`type: choice` dropdown whose first/default option is a rejected sentinel
so a blind "Run workflow" aborts instead of promoting.

resolve-targets: reject the sentinel, fail loud on ambiguous matches
(no silent head -n1), independently re-filter probe.prod, and reject
--digest combined with `all`. promote: run unattended in CI
(--yes --non-interactive) — the manual dispatch + service selection is
the human authorization. notify: empty-webhook guard + fallback warning,
neutral state for sentinel-abort and any cancellation, and surface
resolve-targets.result in the failure alert for triage.
2026-06-02 13:11:43 -07:00
Jordan Ritter a7499569d7 feat(showcase): self-maintaining promote service dropdown generator
Add showcase/scripts/sync-promote-service-options.ts: generates the
promote workflow's service `choice` options from the SSOT
(railway-envs.ts), spliced between BEGIN/END markers in
showcase_promote.yml. Fail-loud throughout — every emitted token must
resolve to exactly one service under the resolve-step predicate
(name|dispatchName match AND probe.prod), tokens are YAML-safe, args are
strict (a typo'd flag cannot trigger a destructive write), and markers
are validated before any rewrite.

Wire it into a lefthook pre-commit hook (regenerate + restage; set -e so
a failed regen blocks the commit) and an advisory (never-failing) drift
check in showcase_validate.yml. Vitest coverage for ordering, exclusion,
collision/ambiguity guards, marker errors, exit codes, idempotency, and
the import-side-effect guard.
2026-06-02 13:11:43 -07:00
Jordan Ritter 377b56cb19 test(showcase): harden D6 rollup tests — order-independent worst-state, comment precision
Add a D5 any-fail fan-out case where the red sub-key is NOT first (greens
then a trailing red) and assert the rollup still resolves red, proving the
worst-state fold is order-independent. Also tighten three imprecise/misleading
comments: clarify the absent-tools D4 worst-state skip, annotate the d6:lgp
aggregate as a distractor not consulted by per-cell resolveD6, and drop the
inapplicable amber-path clause from the aggregate-only gray case.
2026-06-02 13:06:01 -07:00
Jordan Ritter 07abb37ea4 fix(showcase): thread shared now + periodic staleness re-render in dashboard page 2026-06-02 13:05:59 -07:00
Jordan Ritter 952209977e test(showcase): harden useOverlays mocks and cover baseline + selectProbe
Stub window.location explicitly via Object.defineProperty (instead of the
global) so the hook's window.location.hash read-path is genuinely exercised
and robust even if window !== globalThis. Add coverage for setTab persistence,
the #baseline parse resolving to the baseline tab, and selectProbe leaving the
tab and hash consistent (ops probe drilldown).
2026-06-02 13:05:58 -07:00
Jordan Ritter b6f865adce fix(showcase): persist overlays in setTab for storage symmetry
setTab wrote the URL hash but, unlike toggle/updateOverlays, never called
saveToStorage, so a tab switch dropped overlay persistence asymmetrically.
Persist the current overlay set in setTab so the user's overlay selection
survives tab switches and reloads. Covered by a red-green test in the
useOverlays suite.
2026-06-02 13:05:58 -07:00
Jordan Ritter e6da6ba3ed refactor(showcase): re-export overlay/preset definitions from overlay-types
OverlayToggleBar redefined ALL_OVERLAYS, PRESETS, and OverlayPreset as local
literals duplicating src/lib/overlay-types.ts (drift hazard if an overlay or
preset is added to one but not the other). Replace the local definitions with
re-exports from overlay-types so there is a single source of truth. Behavior
identical; existing component-level imports keep working via the re-export.
2026-06-02 13:05:58 -07:00
Jordan Ritter e408489313 fix(showcase): correct cell renderer memo keys and d6 overlay blank matrix
ComposedCell.arePropsEqual watched the wrong liveStatus keys: it omitted
agent/chat/tools (which its only consumer, deriveDepth, reads for D2/D4) and
watched an unused smoke key. Watch exactly deriveDepth's reads and fix the
stale "keep in sync with resolveCell" comment.

UnifiedCell rendered a blank cell for a {d6}-only overlay set because d6 is
consumed only by AdaptiveStatsBar and produced no per-cell content. Treat d6
as content-bearing: surface the depth chip + health row (which renders the
per-cell D6 badge). Default overlay set (links/health/depth) is unchanged.
2026-06-02 13:05:56 -07:00
Jordan Ritter 7f5c3ac16e fix(showcase): correct live-status/depth-utils CR findings
- Rename module-private D5_STATE_RANK to WORST_STATE_RANK (used by both
  resolveD5Row and resolveD6Row) and move the misplaced resolveD5Row doc
  block onto resolveD5Row.
- Add a test asserting every CATALOG_TO_D5_KEY mapping value is free of the
  ':' / '/' key delimiters (keyFor's guard did not cover mapping values).
- mergeRowsToMap: fire the collision warning only on genuine state
  divergence (rowsAreNoop content compare) instead of reference inequality,
  eliminating noisy false warnings.
- isD6Green JSDoc: note the caller only invokes it after D5 is green
  (contiguous ladder), so D6 can never be credited over a broken D5.
- computeMaxPossible: treat stub like unshipped (maxPossible=0) so a
  not-yet-wired stub cell no longer false-positives isRegression.
2026-06-02 13:05:55 -07:00
Jordan Ritter 461621af0e fix(showcase): return effective (downgraded) row from cell-model resolvers
resolveD4/D5/D6 returned the RAW status row in `.row` while `.status` was
derived from the stale-downgraded effective state, so a stale-green fold
reported `.row.state === "green"` but `.status === "amber"`. Store the
effective (downgraded) row instead so `.row.state` agrees with `.status`,
mirroring the invariant in live-status.ts `buildBadge`. Also replace
resolveD4's `worstState!`/`winner!` non-null assertions with a guard like
resolveD5/D6, and pin the resolveD3 producer-invariant (D1/D2 gate is
enforced upstream; buildCellModel never reads health:/agent: rows) with a
characterization test and comment.
2026-06-02 13:05:54 -07:00
Jordan Ritter 19a1f750de fix(showcase): point dashboard OPS_BASE_URL at harness, not itself 2026-06-02 12:37:58 -07:00
Jordan Ritter c32a877a91 test(showcase): add dashboard cell color/rollup matrix 2026-06-02 12:37:55 -07:00
Jordan Ritter a510aa08d6 feat(showcase): enable Depth (and D6) overlays by default 2026-06-02 12:37:55 -07:00
Jordan Ritter acf2e135a2 feat(showcase/harness): env-gate LOCAL_SERVICES_JSON injection seam in railway-services discovery
Make LOCAL_SERVICES_JSON a permanent, env-gated feature of the railway-services
discovery source so the harness (especially the d6-all-pills-e2e probe driver)
can run against LOCAL backends instead of querying Railway.

When LOCAL_SERVICES_JSON is set, enumerate() builds the IDENTICAL
RailwayServiceInfo[] shape from the injected static list and returns it without
consulting Railway creds — only the service URLs differ (local container
hostnames vs Railway public domains). The same namePrefix/nameExcludes filter is
applied, shape is recomputed from the name via classifyShape (single source of
truth), and malformed JSON throws DiscoverySourceSchemaError (same taxonomy as a
Railway shape failure). When the var is unset OR empty, the Railway discovery
path is byte-identical to before.

Critically, the injected demos array is plumbed end-to-end
(demos: svc.demos ?? []) so the d6-all-pills driver's
demosToFeatureTypes(input.demos) produces a real feature matrix; an empty demos
would short-circuit the driver to a zero-cell false-green.
2026-06-02 12:18:10 -07:00
Tyler Slaton 4c6ecbb550 docs: simplify cookbook navigation (#5166)
## Problem

The cookbook sidebar reused the docs framework picker and showed extra
grouping chrome even though Daytona is the only cookbook page.

## Why

The cookbook routes render through the shared docs page view, which
always injected the framework selector into the sidebar and included the
cookbook overview/section header from `meta.json`.

## Fix

- Let cookbook pages explicitly suppress the shared sidebar selector.
- Redirect `/cookbook` to `/cookbook/daytona`.
- Keep the cookbook sidebar to the single Daytona page link.

Validation:
- `npm run lint` in `showcase/shell-docs` passes with existing warnings.
- `npm run build` in `showcase/shell-docs` passes with existing
Next/Turbopack warnings.
- Repo pre-commit package checks passed on both commits.
- `npm run typecheck` currently fails on existing `SignupLink` test prop
errors unrelated to this change.
2026-06-02 11:36:24 -07:00