mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
fix/showcase-msagentpython-genui
13191 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3fb24ef403 | fix(shell-docs): address docs review edge cases | ||
|
|
6618d92f72 |
ci(test-unit): scope build+test to nx affected packages
Run `test / unit` over only the packages affected since the base instead
of building + testing every package 3× across the Node 20/22/24 matrix on
every PR.
- fetch-depth: 0 so affected has a merge-base to diff against.
- Derive NX_BASE/NX_HEAD: PR → merge-base with the base branch tip; push →
github.event.before with a HEAD~1 fallback.
- Select packages via `nx show projects --affected --projects='packages/**'`
fed to run-many (the `nx affected` run form ignores --projects and pulls
in downstream examples/storybook — hence the show-projects → run-many split).
- workflow_dispatch still runs all packages (manual/full run).
- Editing this workflow can't surface as an affected package, so a change to
test_unit.yml in the range now forces a full all-packages run — this keeps
the build/test path exercised on the PR that changes it.
- GitHub context passed via env: (not inline ${{ }}) to satisfy zizmor;
NX_VERBOSE_LOGGING forced off for the JSON-parsing step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
88588223af |
fix(react-core): add dark mode styling for default tool rendering (#5455)
## Summary - Update the default tool-call renderer to use dark-theme-aware classes for the card, header, status badge, and detail panels - Add coverage for the dark theme styling path in unit tests - Add a Storybook example that demonstrates the default tool renderer on a dark CopilotKit surface ## Testing - Added unit tests for the renderer’s dark-theme class output and expanded details state - Not run (not requested) |
||
|
|
06cee3f727 |
fix(vue): adjust pin-to-send spacer in both directions to keep the user message pinned (#5423)
## What does this PR do? Ports the `usePinToSend` spacer fix from #5386 to the Vue package. `packages/vue/src/v2/hooks/use-pin-to-send.ts` is a documented 1:1 parity port of the React hook and retained the shrink-only `ResizeObserver`: when content below the anchored user message loses height (suggestions swapping in, input container resizing after streaming), the spacer could not grow back, so total scrollable height dropped and the pinned message shifted — the same defect class reported in #5355. - Lets the spacer adjust in both directions so total scrollable space below the pinned bubble stays constant. - Mirrors the test change from #5386 1:1 in the Vue suite (red/green: the updated assertion fails on the shrink-only implementation, passes with the fix). - Updates the `PARITY.md` row and hook JSDoc that documented the old shrink-only semantics. Vue has no scroll-to-bottom button, so the scroll-listener half of #5386 does not apply here. ## Related PRs and Issues - Follow-up to #5386 - Relates to #5355 (Vue side of the same spacer defect) ## Tests - `nx test @copilotkit/vue` — 1001 passed (94 files), including the mirrored `adjusts spacer as content height changes to keep the user message pinned` case - `nx build @copilotkit/vue` — green (dts build) - `oxfmt --check` clean on touched files - Pre-existing on main, untouched by this diff: 176 package-wide lint errors (identical count on clean main) and `@copilotkit/core:check-types` failures 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
06112f4c36 |
refactor(showcase): rename Wired → BE (Agent) across dashboard taxonomies (#5498)
## Summary Renames the display label \"Wired\" to **\"BE (Agent)\"** across both taxonomies in the showcase dashboard, unifying the two overloaded meanings (live-probe runtime health + catalog integration build state) under one user-visible name. Mirrors the shape of #5473's `E2E (Demo)` → `UI (Frontend)` rename — same atomic, label-only refactor. ## Why The word \"Wired\" was overloaded across the dashboard: - The **live-probe L1 row** in `level-strip` rendered \"Wired\" for runtime backend health (the agent is up and responding). - The **catalog integration-status** column in `stats-bar`/`adaptive-stats-bar`/`coverage-bar`/`filter-chips` rendered \"Wired\" for build state (the integration is built and shipped). Both refer to the same underlying thing — the backend agent integration — at different lifecycle stages. Calling both **\"BE (Agent)\"** makes the dashboard internally consistent and aligns with the parallel \"UI (Frontend)\" label introduced in #5473. ## Scope - Visible label flip across `level-strip.tsx`, `packages-section.tsx` legend (incl. `W=Wired` → `B=BE (Agent)` mnemonic), `stats-bar.tsx`, `adaptive-stats-bar.tsx`, `coverage-bar.tsx`, `filter-chips.tsx`. - Internal counter renames: `pctWired`/`totalWired` → `pctBeAgent`/`totalBeAgent` (`coverage-bar.tsx`, `cells-view.tsx`, `parity-view.tsx`). - Test assertion + comment updates in `level-strip.test.tsx`, `packages-section.test.tsx`. - Slugifier fix in `stats-bar.tsx` testid generator — `label.toLowerCase()` on `\"BE (Agent)\"` previously produced `stat-be (agent)` (spaces + parens). Now slugifies to `stat-be-agent`. Incidentally also fixes the pre-existing `stat-max depth` → `stat-max-depth`. Surfaced and fixed during cr-loop Round 1. ## Stable contracts preserved (no behavior change) - PocketBase row keys `agent:<slug>` - `LiveDimension` union value `\"agent\"` - `Status` enum string values `\"wired\" | \"stub\" | \"unshipped\" | \"unsupported\"` (persisted catalog data; only display labels flip) - Filter chip `id: \"wired\"` (filter state key joining to `cell-matrix.tsx`) - `catalog.metadata.wired` data field reads - Harness driver `kind`/emission contracts - `data-testid=\"coverage-segment-wired\"` ## Commits (4) - \`ef40bd7c\` live-probe agent dimension Wired → BE (Agent) - \`bfebac65\` catalog status Wired display → BE (Agent) - \`f316d055\` rename internal Wired counters to BeAgent - \`80b526d6\` slugify stats-bar testids (CR fix) ## Verification - typecheck (\`tsc --noEmit\`): clean - lint (next build inline): clean - tests (\`vitest run\`): 1089 pass / 1 skip / 0 fail across 63 files - build (\`next build\`): clean - oxfmt: clean - NUL-byte scan: empty - cr-loop converged at Round 2 with Procedure 3 audit returning 0 PROMOTE_TO_A items ## Follow-up backlog Round 1 + Round 2 surfaced ~30 pre-existing structural findings in adjacent code (not subject-scope for this rename). Grouped into 8 follow-up PR subjects: fail-loud sweep, catalog view derivation extraction, derivePackageDepth gate consistency, parity tier counting hardening, coverage-bar tooltip/rounding, cells-view stats useMemo, test hardening, Tailwind opacity-on-CSS-var. None block this rename. |
||
|
|
a76b2eff69 |
docs: apply LangGraph link fixes to shell-docs (canonical source)
The top-level docs/ folder is retired; the live docs are sourced from showcase/shell-docs. Move the same three link fixes there and revert the no-op edits to docs/: - configurable.mdx: graph-api -> use-graph-api (#add-runtime-configuration) - configurable.mdx: drop malformed %23 double-anchor on the schema link - step-2-langgraph-agent.mdx: studio.langchain.com -> docs.langchain.com/.../studio Closes #3190. |
||
|
|
38e987f7e4 | fix(docs): validate showcase docs model names | ||
|
|
0df97a830b |
fix(examples): bump integrations to @copilotkit 1.60.1 for @ag-ui fetch fix (#5457)
## Summary
Fixes the integration examples whose chat never responds and logs this
on **every agent run**:
```
TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
(agent_run_failed_event){ source: onRunFailed, agentId: default }
```
The fix is a dependency bump that lands every integration demo on
`@copilotkit/* 1.60.1` + `@ag-ui/client 0.0.57`.
## Root cause
`@ag-ui/client@0.0.56` (and `0.0.55`/`0.0.53`) `HttpAgent` stores the
global `fetch` **unbound** and later calls it as a method:
```ts
// buggy
this.fetch = config.fetch ?? fetch;
run(input) { return runHttpRequest(() => this.fetch(this.url, this.requestInit(input))); }
```
`this.fetch(...)` invokes native `fetch` with the agent instance as the
receiver instead of `window`. Native `fetch` is brand-checked and throws
`Illegal invocation`. It fires on the client run path
(`ProxiedCopilotRuntimeAgent.run` → `#runViaHttp`) in **both dev and
prod** — not environment-specific.
Fixed upstream in `@ag-ui/client@0.0.57`:
```ts
this.fetch = config.fetch ?? ((url, requestInit) => fetch(url, requestInit));
```
`@copilotkit/* 1.60.1` (published) bumped its pin to
`@ag-ui/client@0.0.57`. Examples still on `1.60.0` ship the buggy client
— which is why deployments fail.
## What changed (all `examples/integrations/*`)
- **`@copilotkit/* 1.60.0 → 1.60.1`** across every integration demo. The
`_parity` manifest requires all demos to track the north-star
(`langgraph-python`) `@copilotkit` version, so they move in lockstep — a
partial bump fails `parity-check`.
- **Starter-fleet apps force a single `@ag-ui` tree via `overrides`**
(`@ag-ui/client|core|encoder|proto`). Bumped that single-tree pin
**`0.0.53`/`0.0.55` → `0.0.57`** so they actually carry the fetch fix
instead of a `1.60.1 ↔ 0.0.55` skew. Adapter packages (`@ag-ui/crewai`,
`@ag-ui/mastra`, `@ag-ui/llamaindex`, `@ag-ui/a2a`, middlewares) are
intentionally left at their own versions.
- Lockfiles regenerated; all resolve `@ag-ui/client 0.0.57`.
## Verification
- [x] Reproduced `Illegal invocation` locally on `1.60.0` (dev + prod
build)
- [x] Verified `langgraph-python` on `1.60.1`: agent run completes, todo
created, **0 console errors**
- [x] All lockfiles resolve `@ag-ui/client 0.0.57`
- [x] `pnpm parity:check` passes locally (0 errors)
- [ ] CI `parity-check` + per-integration `smoke-starter` green
- [ ] Redeploy affected examples (e.g. Render) and confirm chat works
|
||
|
|
80b526d68b |
refactor(showcase): slugify stats-bar testids to handle multi-word labels
The testid generator in stats-bar.tsx used `label.toLowerCase()` directly,
producing fragile selectors with spaces and parens for compound labels.
After this PR's rename of "Wired" to "BE (Agent)", the testid became
`stat-be (agent)` — a CSS-hostile selector. A pre-existing
`stat-max depth` (from "Max Depth") had the same problem but predated
this PR.
Fix: replace with a proper slugifier:
label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")
Resulting testids:
"BE (Agent)" -> stat-be-agent (was stat-be (agent), broken)
"Max Depth" -> stat-max-depth (was stat-max depth, pre-existing fix)
"Stub" -> stat-stub (unchanged)
"Unshipped" -> stat-unshipped (unchanged)
"Unsupported" -> stat-unsupported (unchanged)
"Regressions" -> stat-regressions (unchanged)
"Failures" -> stat-failures (unchanged)
Call-site enumeration receipt (mandatory):
rg 'stat-wired|stat-be|stat-max|stat-stub|stat-unshipped|stat-unsupported|stat-regressions|stat-failures' showcase/shell-dashboard/
-> no matches (zero hardcoded references anywhere)
rg 'data-testid=.stat-|"stat-|`stat-' showcase/shell-dashboard/
-> only one hit: stats-bar.tsx:29 (the generator itself)
rg 'stat-' showcase/shell-dashboard/tests/
-> no matches (no Playwright/visual tests depend on these testids)
No test updates required.
Verification:
npm run typecheck -> clean
npm test -> 1088 pass / 1 pre-existing unrelated failure
(useLiveStatus.test.tsx R5 F5.2 — does not
reference stats-bar, exists on PR HEAD)
npm run build -> clean (Next.js lint inline; no lint script)
NUL byte sweep -> empty
|
||
|
|
d88245199f | docs: testing that previews are up (#5431) | ||
|
|
9491b89340 |
fix(showcase): disjoint catchall userMessages + content-asserting probes (supersedes #5465)
Closed PR #5465 introduced cross-fixture leakage by stripping the toolName discriminator on shared {userMessage, context} keys: with aimock's alphabetical first-match-wins ordering, tool-rendering-custom-catchall.json sorts before tool-rendering-default-catchall.json, so default-catchall page requests were served the custom file's content. The probe was structurally blind because it asserted only DOM testids (copilot-tool-render + data-tool-name=get_weather) — both fixtures emit get_weather, so the testid signal passed regardless of which fixture won. Real LGP-gold pattern is disjoint userMessages between default and custom catchall fixtures (NOT shared keys discriminated by toolName). This PR ports the LGP-gold pattern to the other 17 integrations and adds page-text content assertions to both d5 catchall probes so this class of regression can't recur silently: - Probe prompts disjoint between default-catchall and custom-catchall - Fixture userMessages updated to match the new disjoint prompts - Page-text content assertions in both d5 catchall probes (default negatively asserts the custom-catchall leak phrase; custom positively asserts it) Local gold-standard red-green proof captured: - Step A: live staging Playwright baseline (RED-OF-RECORD) - Step B: local control-plane on origin/main reproduces structural fragility (probes pass at testid level, custom fixture wins on default's userMessage path) - Step C: local control-plane on this branch — both catchall probes GREEN with the new content-asserting assertions tool-rendering-default-catchall: pass=true (5443ms) tool-rendering-custom-catchall: pass=true (8956ms) cross-tool signature passed LGP (langgraph-python) was already disjoint; it remains untouched. |
||
|
|
f9bcf11295 |
fix(examples): sync langgraph-js agent lockfile to @langchain/core 1.1.49
Keeps package-lock.json in sync with the core bump pulled in from main (#5489) so the agent lockfile is not stale after the rebase. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
a144323d35 |
fix(examples): regenerate integration lockfiles for npm ci
The previous lockfiles were written with `npm install --package-lock-only`, which left out optional transitive deps (e.g. @emnapi/wasi-threads). The Docker smoke-starter build runs strict `npm ci`, which then failed with "package.json and package-lock.json not in sync" (crewai-crews, 9s fail). Regenerated each lockfile with a full `npm install --ignore-scripts` so the tree is complete. Verified `npm ci` now succeeds (crewai-crews, mastra) and all resolve @ag-ui/client 0.0.57. |
||
|
|
cef4fab9d7 |
fix(examples): bump remaining integrations to 1.60.1 + @ag-ui 0.0.57
Follow-up to the @copilotkit 1.60.1 bump so the integration-demo parity check passes. The north-star (langgraph-python) is now on 1.60.1, and the _parity manifest requires every examples/integrations/* demo to track the same @copilotkit version — so all integrations must move together. - @copilotkit/* 1.60.0 -> 1.60.1 across the remaining integrations. - Starter-fleet apps force a single @ag-ui tree via `overrides` (@ag-ui/client|core|encoder|proto). Bump that single-tree pin 0.0.53/0.0.55 -> 0.0.57 so they actually carry the HttpAgent fetch fix (Illegal invocation) instead of a 1.60.1<->0.0.55 skew. Adapter packages (@ag-ui/crewai, @ag-ui/mastra, @ag-ui/llamaindex, @ag-ui/a2a, middlewares) are left untouched. - Lockfiles regenerated; all resolve @ag-ui/client 0.0.57. Per-integration smoke-starter CI validates each starter against 0.0.57. |
||
|
|
0363017503 |
fix(examples): bump @copilotkit to 1.60.1 for @ag-ui/client fetch fix
The chat never responds and the browser console shows "TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation" on every agent run (onRunFailed, agentId: default). Root cause is in @ag-ui/client@0.0.56's HttpAgent: it stores the global fetch unbound (`this.fetch = config.fetch ?? fetch`) and later invokes it as `this.fetch(...)`, so native fetch runs with the agent instance as its receiver instead of `window`. Native fetch is brand-checked and throws "Illegal invocation". It surfaces in both dev and prod. Fixed upstream in @ag-ui/client@0.0.57 (`config.fetch ?? ((url, init) => fetch(url, init))`), which shipped in @copilotkit/* 1.60.1. Bumping these examples 1.60.0 -> 1.60.1 pulls 0.0.57 transitively; lockfiles regenerated. Scope: only the examples that take @ag-ui transitively from react-core are bumped here. Other integration examples force-pin @ag-ui via `overrides` to 0.0.53/0.0.55, where 1.60.1 would create a version skew; those need a per-integration bump and are intentionally left out of this change. |
||
|
|
e9f1f21ddb |
fix(examples): bump langgraph-js agent @langchain/core to 1.1.49 (v6 uuid export) (#5489)
## Summary The `smoke-starter (langgraph-js)` CI job is failing on `main` (and on every open PR, e.g. #5457). The agent container crashes on startup: ``` SyntaxError: The requested module '@langchain/core/utils/uuid' does not provide an export named 'v6' ``` ## Root cause - `examples/integrations/langgraph-js/docker/Dockerfile.agent` copies only `package.json` (not the lockfile) and runs `npm install`, so transitive deps resolve fresh at build time. - `@langchain/langgraph@1.3.0` → `@langchain/langgraph-checkpoint@^1.0.2`. - Checkpoint `1.1.1` (published 2026-06-12) started importing `v6` and raised its peer to `@langchain/core@^1.1.48`. - The agent pinned `@langchain/core@1.1.44`, which predates the `v6` export → import crash. Verified that `@langchain/core@1.1.44` does not export `v6` from `utils/uuid` while `1.1.49` does, and a clean `npm install` with core `1.1.49` dedupes to checkpoint `1.1.1` + core `1.1.49` and imports `v6` successfully. ## Change Bump `@langchain/core` `1.1.44` → `1.1.49` in the langgraph-js agent. The npm `package-lock.json` is intentionally left untouched — no CI job consumes it (the Docker agent build runs `npm install` against `package.json`, per the Dockerfile's own "Mirrors the user experience" comment). ## Test plan - [ ] `smoke-starter (langgraph-js)` passes on this PR |
||
|
|
f316d055fb |
refactor(showcase): rename internal Wired counters to BeAgent
Renames the internal display-counter variables that fed the stats-bar / coverage-bar "Wired" labels to match the new "BE (Agent)" taxonomy: totalWired → totalBeAgent in cells-view.tsx and parity-view.tsx, pctWired → pctBeAgent in coverage-bar.tsx. These are local render-time aggregates, not part of any persisted contract. The status enum literal "wired" (the .filter((c) => c.status === "wired") guard, the coverage-segment-wired test ID, and the wiredByCategory Map's local name) is intentionally preserved — those all key directly off the persisted catalog Status enum and changing them would expand scope into the catalog contract. |
||
|
|
bfebac6565 |
refactor(showcase): relabel catalog status Wired display → BE (Agent)
Unifies the catalog integration status display label with the same
"BE (Agent)" taxonomy as the live-probe agent dimension. The
underlying Status enum literal ("wired"), the catalog.metadata.wired
data field, and the filter chip id ("wired" → cell-matrix.tsx:311
status === "wired" filter key) are all PRESERVED — they are
persisted catalog data + filter state contracts that must not move.
Only the user-facing display label on stats-bar, adaptive-stats-bar,
and the filter chip flips.
This collapses the two distinct dashboard "Wired" surfaces (the L1
live-probe dimension and the per-cell build-state count) under one
unified label, matching the user-confirmed Path B.
|
||
|
|
ef40bd7cfb |
refactor(showcase): relabel live-probe agent dimension Wired → BE (Agent)
Unifies the L1 "agent" live-probe display label with the taxonomy convention established by #5473 (UI (Frontend), E2E, CV, D6 — layer descriptor in parentheses). The dimension name stays "agent" in code (PocketBase row keys agent:<slug>, LiveDimension union, keyFor lookups are all unchanged stable contracts) — only the visible label changes. Updates the level-strip L1 badge label and the packages-section L1-L4 header legend (W → B, "Wired" → "BE (Agent)") so the level-strip's ToneChip first-letter abbreviation matches the legend key. Test assertions covering the rendered letter, the legend text, and the degraded-tone test's local variable follow suit. |
||
|
|
5d67341881 | docs(react-ui): clarify all modalities in attachments prop examples | ||
|
|
c82e3b40e2 |
fix(examples): bump langgraph-js agent @langchain/core to 1.1.49 for v6 uuid export
The langgraph-js smoke-starter job crashes on agent startup with "'@langchain/core/utils/uuid' does not provide an export named 'v6'". @langchain/langgraph@1.3.0 depends on @langchain/langgraph-checkpoint@^1.0.2. Checkpoint 1.1.1 (published 2026-06-12) imports v6 and raised its peer to @langchain/core@^1.1.48. The agent pinned core 1.1.44, which predates the v6 export. The Docker agent build runs `npm install` against package.json (no lockfile), so it floats to checkpoint 1.1.1 against the too-old core. Bumping core to 1.1.49 restores the v6 export and satisfies the peer range. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d305dc6ef2 |
fix(examples): regenerate standalone slack lockfile for @copilotkit/bot* 0.0.2 (#5481)
## What Regenerates the **standalone** `examples/slack/pnpm-lock.yaml` so its `@copilotkit/bot*` deps match `package.json` (`~0.0.2`). ## Why (the actual Railway failure) `examples/slack` commits its own standalone `pnpm-lock.yaml`, which Railway frozen-installs when building with `rootDirectory=/examples/slack`. PR #5478 fixed the **root** workspace lockfile but not this standalone one, so it still recorded `@copilotkit/bot*` at `~0.0.1` while `package.json` requires `~0.0.2`: ``` ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with <ROOT>/package.json specifiers in the lockfile ({"@copilotkit/bot":"~0.0.1",...}) don't match specs in package.json ({"@copilotkit/bot":"~0.0.2",...}) ``` ## How Regenerated with the repo's pinned pnpm (10.33.4, via corepack) in standalone mode — matching how the file was originally produced and how Railway builds it: ``` pnpm -C examples/slack install --ignore-workspace --lockfile-only ``` Result: importer specifiers are now `~0.0.2` resolving to the published `0.0.2`; the `@ai-sdk/mcp: 1.0.21` override is preserved. > Note: regenerating with pnpm 11 dropped the `@ai-sdk/mcp` override (pnpm 11 ignores `package.json` `pnpm.overrides` in `--ignore-workspace` mode), so this was generated with the pinned 10.33.4 specifically. ## Verified `CI=true pnpm -C examples/slack install --ignore-workspace --frozen-lockfile` → **"Lockfile is up to date"** (exit 0). Full pre-commit suite + commitlint green. Only `examples/slack/pnpm-lock.yaml` changed (+103/−32). |
||
|
|
7c884b931b |
fix(examples): regenerate standalone slack lockfile for @copilotkit/bot* 0.0.2
examples/slack ships a standalone pnpm-lock.yaml (used by Railway when building with rootDirectory=/examples/slack). PR #5478 fixed the root workspace lockfile but not this one, so it still pinned @copilotkit/bot* at ~0.0.1 while package.json now requires ~0.0.2 — pnpm install --frozen-lockfile failed with ERR_PNPM_OUTDATED_LOCKFILE on deploy. Regenerate it with the repo's pinned pnpm (10.33.4) via pnpm -C examples/slack install --ignore-workspace --lockfile-only: importer specifiers are now ~0.0.2 resolving to the published 0.0.2, the @ai-sdk/mcp override is preserved. Verified: CI=true frozen install reports "Lockfile is up to date" (exit 0). |
||
|
|
eea1c861c5 |
docs(examples): note monorepo deploy watch-paths for slack example (#5479)
## What A small docs touch to `examples/slack/README.md` that (a) documents the monorepo-deploy watch-path gotcha and (b) **touches a watched path so Railway redeploys the slack services off latest `main`**. ## Why PR #5478 fixed the example's lockfile so a standalone deploy resolves the published `@copilotkit/bot*` `0.0.2` — but that fix only changed **repo-root** files (`pnpm-lock.yaml`, `package.json`, `.npmrc`). The `kite-slack-bot` / `runtime` Railway services watch `examples/slack/**`, so they reported "watched paths not modified" and never picked up the fix. This commit changes a file under `examples/slack/**`, so merging it triggers a rebuild of latest `main` (which carries #5478's fix). The added note also tells future deployers to include the repo-root `pnpm-lock.yaml` / `package.json` in their Railway watch paths so this doesn't recur. Docs-only; no code change. |
||
|
|
d6d487ba47 |
docs(examples): note monorepo deploy watch-paths for slack example
Document that the slack example consumes the published @copilotkit/bot* packages and that deploys (e.g. Railway) must include the repo-root pnpm-lock.yaml / package.json in their watch paths, so a dependency bump triggers a rebuild and a frozen install doesn't fail on an out-of-date lockfile. Touches examples/slack/** to trigger the Railway redeploy of the current main (which now carries the deployable-lockfile fix). |
||
|
|
a7688dd35e |
fix(examples): make slack example lockfile deployable (drop workspace override) (#5478)
## What Fixes the Railway deploy of `examples/slack` failing with `ERR_PNPM_OUTDATED_LOCKFILE`. ## Why it broke PR #5476 bumped the example's `@copilotkit/bot*` deps to `~0.0.2` but the lockfile didn't change — because the root `pnpm.overrides` (`@copilotkit/bot* → workspace:*`, added for local dev) pinned those deps to **workspace links** for every importer. So the committed lockfile recorded the example's bot deps as `~0.0.1` / `link:../../packages/bot`, which: - frozen-install validates fine **inside** the monorepo (the override masks it), but - a **standalone Railway deploy** frozen-installs only `examples/slack`, sees `package.json ~0.0.2` vs lockfile `~0.0.1`, and can't resolve `link:` paths → crash. ## The fix Now that `bot`/`bot-slack`/`bot-ui` are published at `0.0.2`: - **Drop the three `@copilotkit/bot*` overrides** from root `package.json`. Workspace packages still link each other via `workspace:~`; only the **example** switches to consuming the published versions — the correct model for a deployable demo. - **Regenerate `pnpm-lock.yaml`** — the example's importer now records `specifier: ~0.0.2` resolving to the **registry** `0.0.2` (not a `link:`), so a standalone frozen install resolves cleanly. - **Add `@copilotkit/bot*` to `minimum-release-age-exclude`** in `.npmrc` (matching the existing `@ag-ui/*` entries) so the freshly-published `0.0.2` resolves past the 24h supply-chain gate. ## Verified - `pnpm install --frozen-lockfile` → "Lockfile is up to date"; the registry `0.0.2` packages download cleanly. - Example importer: `specifier: ~0.0.2`, `version: 0.0.2(...)` (registry, no `link:`). - Full pre-commit suite + commitlint green. ## Trade-off Local monorepo dev of the example now consumes the **published** `0.0.2` rather than live worktree source. To iterate on the bot packages against the example locally, temporarily set the example's bot deps to `workspace:*` (or re-add the overrides) — don't commit that. |
||
|
|
761ae8caec |
fix(examples): make slack example lockfile deployable (drop workspace override)
The root pnpm.overrides pinned @copilotkit/bot* to workspace:* for every importer, so the committed lockfile resolved the slack example's bot deps to workspace links. A standalone deploy (Railway) frozen-installs only the example and can't resolve those, failing with ERR_PNPM_OUTDATED_LOCKFILE (lockfile specifiers ~0.0.1 vs package.json ~0.0.2, and link: refs that don't exist outside the monorepo). Now that bot/bot-slack/bot-ui are published at 0.0.2, drop the overrides so the example resolves the published ~0.0.2 from the registry, and regenerate the lockfile (importer specifiers now ~0.0.2, versions resolve to registry 0.0.2 — deployable). Add @copilotkit/bot* to minimum-release-age-exclude (matching the @ag-ui/* entries) so the freshly published 0.0.2 resolves past the 24h gate. Workspace packages still link each other via workspace:~; only the example switches to published versions (the correct model for a deployable demo). |
||
|
|
5f1d771cbe |
chore(examples): bump slack example to @copilotkit/bot* 0.0.2 (#5476)
## What Bumps the `examples/slack` dependency ranges for the bot packages from `~0.0.1` to `~0.0.2`, now that `@copilotkit/bot`, `@copilotkit/bot-slack`, and `@copilotkit/bot-ui` are all published at **0.0.2** (the agent-native assistant pane + native streaming release). ```diff - "@copilotkit/bot": "~0.0.1", - "@copilotkit/bot-slack": "~0.0.1", - "@copilotkit/bot-ui": "~0.0.1", + "@copilotkit/bot": "~0.0.2", + "@copilotkit/bot-slack": "~0.0.2", + "@copilotkit/bot-ui": "~0.0.2", ``` ## Why A standalone/deployed install of the example (outside the monorepo) resolves these from npm. At `~0.0.1` it would pull `bot-slack@0.0.2` but could still resolve `bot`/`bot-ui@0.0.1`, which lack the engine surface the pane code calls at runtime (`sink.onThreadStarted`, `thread.setSuggestedPrompts/setTitle`, the new capability flags) — so the assistant pane would crash. Pinning all three to `~0.0.2` keeps the trio aligned. ## Notes - **Lockfile unchanged**: local monorepo installs already resolve these to the workspace copies via the root `pnpm.overrides` (`@copilotkit/bot* → workspace:*`), so this range bump only affects deployed/standalone installs. - No code changes — `package.json` only. |
||
|
|
ba159ce8e1 |
chore(examples): bump slack example to @copilotkit/bot* 0.0.2
@copilotkit/bot, @copilotkit/bot-slack, and @copilotkit/bot-ui are published at 0.0.2 (agent-native assistant pane + native streaming). Point the slack example's dependency ranges at ~0.0.2 so a deployed/standalone install pulls the new packages. Local monorepo installs already use the workspace copies via root pnpm.overrides, so the lockfile is unchanged. |
||
|
|
7ed4a44447 |
chore: release bot v0.0.2 (#5475)
## Release bot v0.0.2 **Scope:** `bot` | **Bump:** `patch` --- ### How this release process works 1. **This PR was created automatically** by the "release / create-pr" workflow. It bumped the `bot` packages to `0.0.2` and generated AI-enhanced release notes. 2. **CI runs on this PR** — the full test suite (unit tests, lint, type checks, build) must pass before merging. This is the review gate. 3. **Review the release notes** in `release-notes.md` in this PR. If a Notion draft was created, you can edit the release notes there before merging. 4. **When this PR is merged**, the `release / publish` workflow automatically: - Builds all packages - Publishes the `bot` packages to npm at version `0.0.2` - Creates git tag `bot/v0.0.2` - Creates a GitHub Release with the final release notes ### Before merging - [ ] CI is green (tests, lint, types, build) - [ ] Version bumps look correct - [ ] Release notes are accurate (edit in Notion if a draft was created) --- > **Do not merge until CI is fully green.** The full test suite runs automatically on this PR.bot/v0.0.2 |
||
|
|
035e7cf9c9 | chore: release bot v0.0.2 | ||
|
|
72203fbcba |
chore: release bot-slack v0.0.2 (#5466)
## Release bot-slack v0.0.2 **Scope:** `bot-slack` | **Bump:** `patch` --- ### How this release process works 1. **This PR was created automatically** by the "release / create-pr" workflow. It bumped the `bot-slack` packages to `0.0.2` and generated AI-enhanced release notes. 2. **CI runs on this PR** — the full test suite (unit tests, lint, type checks, build) must pass before merging. This is the review gate. 3. **Review the release notes** in `release-notes.md` in this PR. If a Notion draft was created, you can edit the release notes there before merging. 4. **When this PR is merged**, the `release / publish` workflow automatically: - Builds all packages - Publishes the `bot-slack` packages to npm at version `0.0.2` - Creates git tag `bot-slack/v0.0.2` - Creates a GitHub Release with the final release notes ### Before merging - [ ] CI is green (tests, lint, types, build) - [ ] Version bumps look correct - [ ] Release notes are accurate (edit in Notion if a draft was created) --- > **Do not merge until CI is fully green.** The full test suite runs automatically on this PR.bot-slack/v0.0.2 |
||
|
|
e941049d3c |
fix(harness): cascade fallback to whole-bubble-minus-toolbar for tool-only responses (Class B) (#5474)
## Summary Adds a **Class B cascade fallback** in `readCascadeStateLast` so the harness settle gate can resolve text for **single-bubble tool-only responses** (recharts SVG, gen-UI cards, A2UI render-only output). These bubbles have ALL scoped text selectors empty but carry substantial rendered content in non-cascade children. The current cascade-pollution guard correctly returns `null` for arbitrary-index reads (`readCascadeState` / `findAssistantBubbleAt`) — but for the LAST bubble, by `RUN_FINISHED` the content is mounted and stable, so we can safely fall back to `bubble.textContent` MINUS the assistant-toolbar's textContent (the suffix the toolbar contributes). This is the residual fix on top of PR #5472 (Class A: multi-bubble last-bubble-has-prose) and #5473 (probe taxonomy cleanup). The ~200-red plateau on PocketBase after #5472 landed is consistent with Class B affecting the long tail of tool-render-only demos (ms-agent-python:beautiful-chat-*, plus other tool-render-only Class-B-shaped demos). ## Class B failure mode (live-confirmed) Probed against `https://showcase-ms-agent-python-staging.up.railway.app/demos/beautiful-chat` with the prompt _"Show me a bar chart of monthly sales for Q1 2026."_: - `count = 1` (single canonical bubble) - `bubble.querySelector('[data-message-content]')` -> empty string - `bubble.querySelector('.cpk:prose')` -> empty string - `bubble.querySelector('.prose')` -> empty string - `bubble.textContent` -> **351 chars** of real content (chart title, axis labels, animation styles, query metadata) - Current `readCascadeStateLast` returns `text=null` -> settle gate spins on `text-unstable` until timeout (RED). ## Red-green proof (verbatim from /tmp/cr/class-b-red-green.mjs) The probe runs TWO in-browser readers against the same DOM snapshot — the OLD (current production) and the NEW (with fallback). Run live against staging, **before any code changes** in this PR: ``` [probe] navigating to https://showcase-ms-agent-python-staging.up.railway.app/demos/beautiful-chat [probe] locating chat input [probe] sending prompt: Show me a bar chart of monthly sales for Q1 2026. [probe] waiting 20000ms for response to render [probe] result: { "count": 1, "oldText": null, "newText": "query_dataquery:\"monthly sales for Q1 2026\"\n @keyframes barSlideIn {\n from { transform: translateY(40px); opacity: 0; }\n 20% { opacity: 1; }\n to { transform: translateY(0); opacity: 1; }\n }\n Monthly Sales for Q1 2026Breakdown of income generated each month in Q1 2026.JanuaryFebruaryMarch0255075100January", "oldLen": null, "newLen": 351, "newPreview": "query_dataquery:\"monthly sales for Q1 2026\"\n @keyframes barSlideIn {\n from { transform: translateY(40px); opacity: 0; }\n 20% { opacity: 1; }\n to { transform: translat" } [probe] verdict: { "red": true, "green": true, "count": 1 } [probe] RED-GREEN confirmed: old=null, new=non-empty. ``` - **RED**: `oldText = null`, `oldLen = null` — matches current production behavior; settle gate cannot resolve text. - **GREEN**: `newText` is 351 chars of stable rendered content; settle gate can lock in `text-stable`. ## The fix In `showcase/harness/src/probes/helpers/assistant-message-count.ts`, inside `readCascadeStateLast`'s per-tier loop — AFTER the existing scoped-selector cascade exhausts without a non-empty match and BEFORE returning `{count, text: null}`: 1. Read `bubble.textContent` (whole bubble). 2. Read `bubble.querySelector('[data-testid="copilot-assistant-toolbar"]').textContent` if present. 3. If the toolbar's text is a **trailing suffix** of the whole-bubble text (which it canonically is — toolbar is a leaf sibling of the prose div), strip it. 4. If what remains is non-empty after trim -> return `{count, text: <trimmed>}`. 5. Otherwise (no toolbar, no content, or only-toolbar text) -> return `{count, text: null}` to keep polling. The same fallback is NOT applied to `findAssistantBubbleAt` / `readCascadeState` because those address arbitrary indices and intermediate bubbles in multi-step responses can transiently carry empty scoped text while the NEXT bubble is mounting. Reading whole-bubble at intermediate indices would re-introduce the cross-bubble text flap PR #5462 was designed to prevent. The LAST bubble in a multi-step turn is the agent's terminal output, mounted by `RUN_FINISHED`. ## Why the fallback is safe (toolbar-suffix invariant) In the canonical CopilotKit assistant bubble (`CopilotChatAssistantMessage.tsx`), the toolbar is the **last child** of the bubble, mounted as a leaf sibling of the prose/markdown wrapper. Its `textContent` is therefore the trailing slice of `bubble.textContent`. Stripping it by suffix-match yields the message content. When the suffix-match fails (defensive — e.g. the DOM shape changes), we return `bubble.textContent` unchanged rather than mangle it. When the toolbar isn't present (headless / custom-composer), we return whole text. When even whole-text is empty, we return `null` (don't lock the settle gate on a placeholder). ## Tests - `assistant-message-count.test.ts`: 9 new test cases under a new `describe("readCascadeStateLast")` block. Pins: - non-empty scoped text path unchanged (no fallback when scoped works) - Class B fallback: scoped empty + whole-bubble non-empty + toolbar suffix -> strip suffix, return content - Class B fallback works without a toolbar (returns whole text) - defensive: toolbar text NOT a suffix -> return whole text unchanged - everything empty -> return `text:null` (settle gate keeps polling) - only-toolbar text -> return `text:null` after strip - multi-bubble: reads the LAST bubble, not the first - no tier matches -> `{count:0, text:null}` - `evaluate()` throws -> swallowed -> `{count:0, text:null}` - Full harness suite: **2769 / 2769 passing across 129 files** (`pnpm exec vitest run` in `showcase/harness`). - TypeScript: `pnpm exec tsc --noEmit` clean. - Lint: `oxlint` 0 warnings, 0 errors. - Format: `oxfmt --check` clean. ## Out-of-scope (untouched) - `conversation-runner.ts` (the settle-gate logic stays — the cascade does the right thing now). - `sse-interceptor.ts`, `init-scripts.ts`, `d6-all-pills.ts`. - `findAssistantBubbleAt` / `readCascadeState` (intentionally retain the pollution guard — they address arbitrary indices). - `resolveBubbleTextFromSelectors` (pure sibling of `findAssistantBubbleAt`, intentionally retains pollution-guard semantics). ## Verification plan after merge 1. Watch CI green on the PR. 2. Promote `showcase-ms-agent-python` and other tool-render-only services. 3. Observe PocketBase D6 red counts: expect the ~200-red plateau to drop further toward baseline. 4. If any demo regresses, revert `readCascadeStateLast` alone. ## Files changed - `showcase/harness/src/probes/helpers/assistant-message-count.ts` (production fix) - `showcase/harness/src/probes/helpers/assistant-message-count.test.ts` (9 new tests) |
||
|
|
72520de43d | fix(harness): cascade last-bubble whole-bubble-minus-toolbar fallback for tool-only responses | ||
|
|
f7779f90bf |
refactor(showcase): probe taxonomy cleanup — drop Smoke, rename E2E (Demo) → UI (Frontend) (#5473)
## Summary
The smoke probe was the same HTTP contract as `/health` on the same
service (200-OK JSON body), so every tick paid two HTTP calls for the
same liveness signal. This PR:
- Drops the `/smoke` GET probe and the `smoke:<slug>` primary
`ProbeResult`. The driver now emits `health:<slug>` as the primary and
`agent:<slug>` via writer side-emit (half the per-tick cost).
- Renames the dashboard's D3 / e2e row label from `E2E (Demo)` to `UI
(Frontend)` (and the short cell badge from `E2E` to `UI`) — the row was
already about "the demo page renders in a browser", not E2E in the
traditional sense.
- Drops the `Smoke` row from the cell drilldown (CellState.smoke field
is retained for back-compat so historical rows still parse).
- Adds an explicit `Health` row to the legend (was implicit).
The driver's registry `kind` stays `"smoke"` so existing YAML configs +
orchestrator family wiring keep routing to this driver — the emission
key (`health:<slug>`) is the taxonomy contract that matters. The
underlying probe key (`e2e:<slug>/<feature>`) is preserved on PocketBase
so historical rows render correctly during the rename window.
## RED proof (BEFORE)
`smoke:<slug>` primary emission in `liveness.ts` (origin/main):
```
18: * 1. `smoke:<slug>` — the RETURN VALUE of `run()`. The invoker runs
56: * `smoke:<slug>`/`health:<slug>`/`agent:<slug>` triple the dashboard
241: input.mode === "discovery" ? `smoke:${slug}` : input.key;
```
`E2E (Demo)` / `Smoke` strings in dashboard render code (origin/main):
```
cell-drilldown.tsx:38: * Trip)" (chat+tools round-trip), D3/e2e = "E2E (Demo)" (the demo page loads
cell-drilldown.tsx:49: { key: "e2e", label: "E2E (Demo)" },
cell-drilldown.tsx:52: { key: "smoke", label: "Smoke" },
cell-pieces.tsx:382: name="E2E"
unified-cell.tsx:231: <TestBadge name="E2E" level={model.d3} />
adaptive-legend.tsx:59: E2E (Demo): demo page loads and round-trips in a browser
```
## GREEN proof (AFTER)
Primary emission rewritten — driver returns `health:<slug>` and no
`smoke:<slug>` ProbeResult is ever emitted:
```
33: * to `/smoke` and emitted a `smoke:<slug>` primary result, but that probe
230: // `kind: "smoke"` is the registry/family identifier the orchestrator
236: kind: "smoke", // registry-key back-compat; no smoke ProbeResult is emitted
367: if (input.mode === "discovery") return `health:${slug}`;
368: if (input.key.startsWith("smoke:")) {
369: return `health:${input.key.slice("smoke:".length)}`;
```
New labels in dashboard:
```
cell-drilldown.tsx:60: { key: "e2e", label: "UI (Frontend)" },
cell-pieces.tsx:382: name="UI"
unified-cell.tsx:231: <TestBadge name="UI" level={model.d3} />
adaptive-legend.tsx:65: UI (Frontend): demo page renders in browser (Playwright)
```
Confirmation that the old labels are gone from render code:
```
$ grep -rn 'E2E (Demo)\|"Smoke"' cell-drilldown.tsx cell-pieces.tsx unified-cell.tsx adaptive-legend.tsx
(no matches — labels removed)
```
## Test results
- `pnpm exec vitest run` in `showcase/shell-dashboard`: **63 files, 1089
tests passed, 1 skipped**
- `pnpm exec vitest run` in `showcase/harness`: **129 files, 2760 tests
passed**
- `pnpm exec tsc --noEmit` in both: clean
Liveness driver tests include a regression guard (`never emits a
smoke:<slug> key`) so the smoke contract cannot be re-introduced
silently.
## Test plan
- [x] Dashboard vitest green (1089/1089)
- [x] Harness vitest green (2760/2760)
- [x] tsc --noEmit green in both packages
- [x] Visual cross-check: drilldown renders 6 rows (no Smoke), e2e row
labelled `UI (Frontend)`; cell strip shows `UI` badge in place of `E2E`;
legend shows explicit `Health` row + `UI (Frontend)` D3 line
|
||
|
|
53f021afe8 |
refactor(showcase): probe taxonomy cleanup — drop Smoke, E2E (Demo) → UI (Frontend)
The smoke probe was the same HTTP contract as /health on the same service (200-OK JSON body), so every tick paid two HTTP calls for the same liveness signal. Drop the /smoke GET + the smoke:<slug> primary ProbeResult; the driver now emits health:<slug> as the primary and agent:<slug> via writer side-emit (half the per-tick cost). The driver's registry kind stays "smoke" so existing YAML configs and orchestrator family wiring keep routing to this driver — the emission key (health:<slug>) is the taxonomy contract that matters. Dashboard: - Drilldown: D3/e2e row labelled "UI (Frontend)" (was "E2E (Demo)"); Smoke row dropped (CellState.smoke field retained for back-compat). - Cell badges: short label "UI" (was "E2E") in cell-pieces + unified-cell. - Legend: D3 = "UI (Frontend): demo page renders in browser (Playwright)" plus an explicit Health row at the top. Tests updated to assert new labels: - cell-drilldown.test.tsx: 6 dimensions (no Smoke); UI (Frontend) label. - cell-drilldown.lazy-signal.test.tsx: drilldown-badge-ui--frontend- testid. - cell-pieces.test.tsx + .signal-degrade.test.tsx: badge name "UI". - unified-cell.test.tsx: mock-badge-UI. - overlay-selector-integration.test.tsx: "UI" in place of "E2E". - dashboard-color-matrix.test.tsx: badge: "UI" case names. - liveness.test.ts: two-call contract (health + agent), regression guard asserting no smoke:<slug> ever emitted. Underlying probe key (e2e:<slug>/<feature>) preserved on PocketBase so historical rows render correctly during the rename window. |
||
|
|
665dd5aae6 |
fix(harness): waitForTurnComplete reads LAST bubble (multi-step agent regression from #5462) (#5472)
## Summary **URGENT hotfix.** Staging is widely red for D5/D6 multi-step demos. Root cause confirmed via live Playwright DOM inspection on staging (langgraph-typescript : beautiful-chat : pie chart prompt). PR #5462's defect-2 fix uses `bubbleIndex = turnIndex - 1`, which assumes one assistant bubble per turn. Multi-step agents (LangGraph, Mastra, CrewAI, llama-index, ag2, etc.) emit 2-3+ bubbles per turn — tool-call bubble + tool-render bubble + final-text bubble. The strict-index read lands on an intermediate **tool-call** bubble whose scoped-text selectors (`.cpk:prose`, `[data-message-content]`, `.prose`, `p`) are **empty** (tool-call content lives in a sibling card div outside the scoped cascade). The cascade-pollution guard correctly returns `null` → the text-stable conjunct never converges → `reason=text-unstable` timeout across virtually every multi-step demo (beautiful-chat-*, gen-ui-*, shared-state-*, agentic-chat on most backends). ## Live RED-GREEN proof (staging) Standalone Playwright probe against `showcase-langgraph-typescript-staging.up.railway.app/demos/beautiful-chat`, prompt = `"pie chart of revenue distribution by category from the sample sales data"`, captured BOTH the OLD-logic read (`bubbleIndex = turnIndex - 1 = 0`) AND the NEW-logic read (`bubbleIndex = count - 1 = 2`) in ONE atomic page evaluate after the turn settled: ``` TOTAL BUBBLE COUNT FOR TURN 1: 3 (true multi-step — tool-call + tool-render + final-text) FINAL TIER: [data-testid="copilot-assistant-message"] OLD LOGIC (bubbleIndex = turnIndex - 1 = 0): count = 3 text = null reason = all scoped selectors empty ← would time out: reason=text-unstable NEW LOGIC (bubbleIndex = count - 1 = 2): count = 3 text = "Here is the pie chart showing the revenue distribution by category from the sample sales data." textLen = 94 ← gate settles cleanly ``` RED ≠ GREEN; new logic returns 94 chars of stable final-text where old logic returns `null`. The bug and the fix are proven on real staging DOM. ### Live DOM evidence LangGraph TS beautiful-chat, prompt = "pie chart of revenue distribution by category…": ``` count = 3 bubbles in [data-testid="copilot-assistant-message"] bubble[0]: tool-call "query_data" — `.cpk:prose` exists but EMPTY (content in sibling <div class="my-1.5">) bubble[1]: pie-chart card — `.cpk:prose` exists but EMPTY (content in sibling styled card div) bubble[2]: final text "Here is the pie chart..." — `.cpk:prose` has 94 chars + toolbar mounted ``` The old pre-#5462 runner read `list[last]` (always the last bubble — always bubble[2] above). The PR moved to `turnIndex - 1`, which is bubble[0] (the tool-call) for any multi-step turn → broken. ## Fix Read the LAST bubble in the matched cascade tier (`count - 1`) instead of a fixed strict index. Defect-2 protection (don't read a leftover bubble from a prior turn) is preserved via an explicit **pre-submit baselineCount sentinel snapshot** in the runner — the gate now requires `count > baselineCount` rather than `count > turnIndex - 1`. This correctly rejects stale prior-turn bubbles **without assuming 1 bubble = 1 turn**. ### Changes - `assistant-message-count.ts`: add `readCascadeStateLast(page)` — same atomic single-evaluate contract as `readCascadeState`, but resolves the bubble index internally as `count - 1`. - `conversation-runner.ts`: - Snapshot `baselineCount = countAssistantMessages(page)` BEFORE `sendTurnMessage()` each turn. - Pass through to `waitForTurnComplete` via new `baselineCount` option (defaults to `turnIndex - 1` to preserve unit-test fake compatibility). - `waitForTurnComplete` reads via `readCascadeStateLast` and gates on `domOk = count > baselineCount`. - Cold-start retry re-snapshots `baselineCount` after `page.reload()`. - Returned `bubbleIndex` in the success ctx = `count - 1` (matches the bubble whose text settled the gate). - `dom-missing` reason classifier updated to `countFinal <= baselineCount`. ## Why this doesn't regress defect-2 The original defect-2 (un-turn-scoped bubble selection) leaked a later turn's bubble into THIS turn's assertions because `readLastAssistantText` read `list[last]` GLOBALLY with no per-turn baseline. The new fix re-introduces the "read last" semantics but adds the **per-turn pre-submit baseline count snapshot** — the gate only advances once a NEW bubble has appeared (`count > baselineCount`), so we cannot read a leftover from a prior turn, and the "last" we return is the last NEW bubble (which for multi-step turns is the final-text bubble whose scoped text actually settles). ## Test plan - [x] **Live RED-GREEN staging probe** (see above) — confirms OLD logic = null and NEW logic = 94-char final-text on a real 3-bubble multi-step turn. - [x] All 72 `conversation-runner.test.ts` + `assistant-message-count.test.ts` unit tests pass (default `baselineCount = turnIndex - 1` keeps existing count-progression scripts working). - [x] Full harness vitest suite: 2758/2758 green across 129 test files. - [x] `tsc --noEmit` clean on `showcase/harness`. - [x] `oxfmt --check` clean on changed files. - [x] `oxlint` clean on changed files. - [ ] Staging D5/D6 sweep after merge confirms multi-step demos (beautiful-chat, gen-ui, shared-state, langgraph/mastra/crewai agentic-chat) recover from `text-unstable` red. ## Scope Strictly the two files above. No unrelated changes. No refactors. |
||
|
|
88e77f1403 |
fix(showcase): allow harness to boot locally without SHARED_SECRET (unblock local verify) (#5471)
## Problem PR #5458 (`c81b361f1` — *fix(showcase/harness): register /webhooks/deploy on CP path + fail-loud on missing SHARED_SECRET*) added a fail-loud gate to `loadWebhookSecrets` in `showcase/harness/src/orchestrator.ts` that refuses harness boot in any deployable mode (`NODE_ENV !== "test"`) without `SHARED_SECRET` (or `SHARED_SECRET_PREV`) set, because `POST /webhooks/deploy` is only registered when `webhookSecrets.length > 0` (gate at `showcase/harness/src/http/server.ts:119`). The local `showcase/docker-compose.local.yml` harness services inherit `NODE_ENV=production` from the image and do not set `SHARED_SECRET` (and should not — that secret only matters for the Showcase: Verify Deploy webhook flow on Railway). As a result, the harness crash-loops on every local `bin/showcase test --d5/--d6` run: ``` FATAL-CONFIG: SHARED_SECRET (or SHARED_SECRET_PREV) is required — refusing to boot in any deployable mode … Set SHARED_SECRET (or SHARED_SECRET_PREV) in the env, or set NODE_ENV=test / HARNESS_ALLOW_NO_SECRET=1 for local dev. Current NODE_ENV=production. ``` …which blocks **all** local D5/D6 verify. ## Fix Add `HARNESS_ALLOW_NO_SECRET=1` (the documented local-dev escape hatch — explicitly named in the FATAL-CONFIG message itself, and the dedicated env var checked by `loadWebhookSecrets` at `orchestrator.ts:190`) to both harness services in `showcase/docker-compose.local.yml`: - `harness-control-plane` - `harness-pool-worker` Inline comments explain the rationale and pin the relevant source locations (the gate at `src/http/server.ts:119` and `loadWebhookSecrets` in `src/orchestrator.ts`). Diff: 17 lines added (comments + 2 env vars), no other files touched. ## Prod impact: NONE Railway sets `SHARED_SECRET` explicitly via env on every harness service, so `loadWebhookSecrets` sees a real secret, registers `POST /webhooks/deploy`, and **never reads `HARNESS_ALLOW_NO_SECRET`**. The escape hatch only takes effect when both secrets are absent. This change only affects the local docker-compose stack. ## Local verification Brought up the slot-1 isolated stack with the fix applied via `SHOWCASE_ISO_SLOT=3 ./bin/showcase test langgraph-typescript --d6 --verbose --isolate` and observed clean boot: ``` showcase-iso1-harness Up 12 seconds (healthy) showcase-iso1-langgraph-typescript Up 12 seconds (healthy) showcase-iso1-harness-pool-worker-1 Up 12 seconds (healthy) showcase-iso1-dashboard Up 13 seconds (healthy) showcase-iso1-aimock Up 18 seconds (healthy) showcase-iso1-pocketbase Up 18 seconds (healthy) ``` Harness boot logs confirm: - `fleet.role-selected` + `fleet.control-plane.started` + `showcase-harness.fleet.control-plane.boot` (port 8080) — control-plane up - `webhook auth disabled — neither SHARED_SECRET nor SHARED_SECRET_PREV is set` with `escapeHatch:true` at **warn** level — the expected log produced by `loadWebhookSecrets` when the escape hatch fires - `worker.registered` + `fleet.worker.boot` — worker self-registered - `worker.claimed jobId=… probeKey=d6:langgraph-typescript` — real D6 job picked up and run end-to-end NO `FATAL-CONFIG`, NO restart loop. (The D6 cell may still be RED on this branch — that is a pre-existing issue, unrelated to this infra fix. The point of this PR is "harness boots and runs a job at all locally", which it now does.) ## References - PR #5458 (`c81b361f1`): the fail-loud gate this PR re-enables an escape for - `showcase/harness/src/orchestrator.ts` `loadWebhookSecrets` (~line 180-220): the predicate that reads `HARNESS_ALLOW_NO_SECRET` - `showcase/harness/src/http/server.ts:119`: the route-registration gate |
||
|
|
d145bb1708 |
fix(harness): waitForTurnComplete reads LAST bubble in matched tier
PR #5462's defect-2 fix assumed 1 bubble per turn (bubbleIndex = turnIndex - 1). Multi-step agents (LangGraph, Mastra, CrewAI) emit 2-3+ bubbles per turn — tool-call + tool-render + final-text — so the strict-index read lands on an intermediate tool-call bubble whose scoped-text selectors (`.cpk:prose`, `[data-message-content]`, `p`) are EMPTY (its content lives in a sibling card div outside the scoped cascade). The cascade-pollution guard returns null forever and the text-stable conjunct times out with `reason=text-unstable` across virtually every multi-step demo (beautiful-chat-*, gen-ui-*, shared-state-*, agentic-chat on LangGraph/Mastra/CrewAI/etc.). Live DOM evidence from staging (langgraph-typescript : beautiful-chat : pie chart prompt): count = 3 bubbles bubble[0]: tool-call "query_data" — `.cpk:prose` empty bubble[1]: pie chart card — `.cpk:prose` empty bubble[2]: final text "Here is the pie chart..." — 90 chars Fix: read the LAST bubble in the matched cascade tier (count - 1) instead of a fixed strict index. Defect-2 protection (don't read a leftover bubble from a prior turn) is preserved via an explicit pre-submit `baselineCount` sentinel snapshot in the runner; the gate now requires `count > baselineCount` rather than `count > turnIndex - 1`, which correctly rejects stale bubbles without assuming 1 bubble = 1 turn. Changes: - assistant-message-count.ts: add `readCascadeStateLast(page)` — same atomic single-evaluate contract as `readCascadeState` but resolves the bubble index internally as `count - 1`. - conversation-runner.ts: snapshot `baselineCount` before sendTurnMessage; pass through to `waitForTurnComplete` via new `baselineCount` option (defaults to `turnIndex - 1` for unit- test fakes); `waitForTurnComplete` reads via `readCascadeStateLast` and gates on `count > baselineCount`. Cold-start retry re-snapshots `baselineCount` after page.reload. Test plan: - All 72 conversation-runner + assistant-message-count unit tests pass (default baselineCount preserves count-progression scripts). - Full harness suite 2758/2758 green, tsc clean, oxfmt/oxlint clean on changed files. |
||
|
|
48ef506abf |
fix(showcase): allow harness to boot locally without SHARED_SECRET
PR #5458 (
|
||
|
|
7cc579a58d |
chore: drop dead .changeset/ debris and workflow path filters (#5470)
## Summary The repo migrated off `@changesets/*` to conventional-commit-driven releases. This chore PR removes the leftover debris: - **11 stale `.changeset/*.md` files** (the entire `.changeset/` directory) - **4 dead `paths:` filter lines** in two e2e workflows ## Migration context Releases are driven by `scripts/release/prepare-release.ts` / `scripts/release/lib/changes.ts::getChangesSummary`, which reads `git log <lastTag>..HEAD` commit subjects. It never touches `.changeset/`. Verification — none of the changesets tooling is wired up anymore: - No `.changeset/config.json` - No `@changesets/*` in any `package.json` (root or workspace) - No npm scripts reference `changeset` The 11 `.changeset/*.md` files describe changes that have either already shipped (via commit subjects in prior releases) or will ship in the next release (via the current commit subjects in the `v1.60.1..main` window). The files are inert with respect to releases — most are 2+ months old; the newest (`slack-agent-native-apis.md`) was added recently by a contributor who didn't know about the migration. ## What's removed `.changeset/` (11 files): - `angular-21-install-and-types.md` - `angular-disable-license-watermark.md` - `bump-license-verifier-ent-251.md` - `debug-mode.md` - `empty-mails-applaud.md` - `ent-314-thread-connect-ux.md` - `ent-658-generated-thread-tool-roundtrip.md` - `five-avocados-visit.md` - `fix-thread-switch-state-reset.md` - `little-pears-tell.md` - `slack-agent-native-apis.md` Workflow `paths:` filters (4 lines across 2 files): - `.github/workflows/test_e2e-dojo.yml` — `push.paths`, `pull_request.paths`, and the `dorny/paths-filter` `ts:` filter - `.github/workflows/test_e2e-legacy-v1.yml` — `push.paths` Nothing to fire on after the directory is gone. ## Test plan - [ ] CI green on this PR |
||
|
|
5afa55f067 |
chore: drop dead .changeset/ debris and workflow path filters
The repo migrated off @changesets/* to conventional-commit-driven releases. scripts/release/lib/changes.ts::getChangesSummary reads `git log <lastTag>..HEAD` commit subjects and never touches .changeset/. No .changeset/config.json, no @changesets/* in any package.json, no npm scripts reference it. The 11 .changeset/*.md files describe changes that have either already shipped (via commit subjects in prior releases) or will ship in the next release (via the current commit subjects in the v1.60.1..main window) — the .changeset/ files are inert. Also removes the dead workflow `paths:` filters in test_e2e-dojo.yml and test_e2e-legacy-v1.yml that re-fired e2e on .changeset/ changes — nothing to fire on after the directory is gone. |
||
|
|
f8b14f1d6a |
fix(react-core): await runAgent in useInterrupt::resolve and propagate to demo-local hooks (#5461)
## Summary
Convert `useInterrupt::resolve` and the demo-local
`useHeadlessInterrupt::resolve` callbacks from fire-and-forget into
`async` + `return await copilotkit.runAgent(...)`, so callers receive a
Promise that settles when the resume run settles.
## Mechanism
Pre-fix: `resolve(...)` called `copilotkit.runAgent({...})` without
`await` and without `return` — the arrow returned `undefined`. The
showcase harness DOM-settle check on the assistant confirmation bubble
timed out because consumers had no handle to sequence against the resume
run's settle.
Post-fix:
- Framework `useInterrupt::resolve` is `async`, `return await`s
`runAgent`, wraps in try/catch + `setPendingEvent(null)` +
`console.error` + rethrow on rejection.
- `onRunFailed` now also `setPendingEvent(null)` — symmetric with
`onRunStartedEvent`, prevents stuck popups on run failure.
- `InterruptHandlerProps.resolve` / `InterruptRenderProps.resolve` typed
`() => Promise<RunAgentResult>` (was `() => void`).
- The same fix-pattern applied to 13 demo-local `useHeadlessInterrupt`
implementations across integrations: ag2, agno, claude-sdk-typescript,
crewai-crews, langgraph-{fastapi,python,typescript}, langroid,
llamaindex, mastra, pydantic-ai, spring-ai, strands.
## Verification
- **Unit:** new tests `resolve returns a Promise that settles when
runAgent settles (RESUME-PATH)` + `resolve rejects when runAgent
rejects, logs the failure, and clears pending (RESUME-PATH-REJECT)`.
Red-then-green on baseline. 22/22 react-core hook tests passing.
- **Backplane (end-to-end):** showcase control plane on langgraph-python
via `./bin/showcase test langgraph-python:gen-ui-interrupt --d5 --direct
--verbose` (GREEN, 70.1s, was RED-RESUME-PATH baseline) and
`:interrupt-headless --d5 --direct --verbose` (GREEN, 7.7s, was
RED-RESUME-PATH baseline). Built Docker artifact + aimock D6 fixture
replay — staging-equivalent path.
## CR
4 review rounds × 7 unbiased agents = 28 agent-runs. 2 Procedure 3
audits (3 promotions in round 1 → Fix C + D; 0 promotions in round 2).
Convergence achieved per agent-confirmed `bucket (a) empty` + Procedure
3 zero `PROMOTE_TO_A`.
## Out of scope (deferred follow-ups)
- **No manifest unquarantine.** A prior version of this branch flipped
`interrupt-headless` and `gen-ui-interrupt` from
`not_supported_features` to `features` across 12 manifests, but Phase 4
backplane validation across 8 PATCHED integrations × 2 demos surfaced
(a) `langgraph-fastapi:gen-ui-interrupt` still RED-RESUME-PATH because
the framework patch doesn't propagate from workspace to the
integration's Docker image without a published `@copilotkit/react-core`
release, and (b) a separate popup-mount RED-RUNTIME-OTHER cluster across
6 integrations that's pre-existing rot unrelated to this PR. Manifest
unquarantine + `@copilotkit/react-core` version bump are deferred to a
follow-up `copilotkit-release` PR after this fix lands.
- **No version bump.** Release follows in a separate PR.
- **Bucket (c) follow-up backlog** (audit verdicts: 11 STAY_IN_C):
- JSON.parse guarding in showcase demos (pre-existing, all 13)
- Type-design quirks in `InterruptHandlerProps.result` (non-nullable in
type, nullable in runtime)
- `renderInChat` dynamic toggle leak (pre-existing edge case)
- JSDoc clarity ("symmetric with onRunFailed" wording, `@typeParam
TRenderInChat`, `event.value` "any" → "unknown")
- StrictMode test coverage gap
- 13× duplicate `useHeadlessInterrupt` → bucket (d): consolidate into a
shared module in a future PR
## Files changed
16 files, +486/-154:
- `packages/react-core/src/v2/hooks/use-interrupt.tsx`
- `packages/react-core/src/v2/hooks/__tests__/use-interrupt.test.tsx`
- `packages/react-core/src/v2/types/interrupt.ts`
- 13×
`showcase/integrations/<integ>/src/app/demos/interrupt-headless/page.tsx`
## Test plan
- [x] react-core unit tests (22/22 passing including new RESUME-PATH +
RESUME-PATH-REJECT regression tests)
- [x] Backplane D5 probe on langgraph-python (gen-ui-interrupt +
interrupt-headless GREEN end-to-end via the showcase control plane)
- [ ] CI green on this PR
|
||
|
|
4564e6205d | chore: release bot-slack v0.0.2 | ||
|
|
431d5baae0 |
feat(bot-slack): agent-native Slack APIs — assistant pane + native streaming (default-on) (#5447)
## What
Activates Slack's agent-grade APIs as the **default** experience for
`@copilotkit/bot-slack`, with zero config and safe degradation.
Implements the Notion spec *"Slack Agent APIs in bot-slack — assistant
pane + native streaming by default"* (Rev 2).
### Assistant pane ("Agents & AI Apps")
- Opening the pane greets the user + shows tappable prompt chips; each
pane conversation is its own thread (replies stay in-thread, instead of
leaking to a merged flat DM).
- While the agent runs, native composer status
(`assistant.threads.setStatus`: "is thinking…", "is using `tool`…")
replaces placeholder/`🔧` messages.
- Pane threads are auto-titled from the first message.
- Customize via the new `assistant` option; `assistant: false` disables
pane handling. Apps without the Agents toggle behave exactly as before
(pane machinery dormant).
### Native streaming
- Replies stream via `chat.startStream` / `appendStream` / `stopStream`
(raw markdown — real tables / fenced code render natively) wherever a
thread exists.
- Flat DMs and workspaces without the streaming API fall back to the
legacy `chat.update` transport **automatically** — the first
`startStream` failure marks the workspace legacy and replays via legacy.
Opting in can never break a bot. `streaming: "legacy"` forces the old
transport.
### Portable engine surface (`@copilotkit/bot`)
- `bot.onThreadStarted` lifecycle handler + `IncomingThreadStart` sink
event.
- Capability-gated `thread.setSuggestedPrompts` / `thread.setTitle` (the
shipped `postFile` gating pattern).
- Two `SurfaceCapabilities` flags + optional `PlatformAdapter` methods.
All degrade gracefully on surfaces without support.
## Files
- **New:** `bot-slack/src/assistant.ts` (Bolt `Assistant` middleware ⇄
engine sink), `bot-slack/src/native-stream.ts` (`NativeMessageStream`,
same `append/finish` contract as `MessageStream`, legacy fallback).
- **Engine:** `platform-adapter.ts`, `thread.ts`, `create-bot.ts`,
`index.ts` (+ `bot-ui` `Thread` type).
- **Adapter:** `adapter.ts` (options/capabilities/stream
branch/methods), `slack-listener.ts` (one-line no-double-delivery
guard), `event-renderer.ts` (pane status mode + native text transport).
- **Example/docs:** `examples/slack` manifest (`assistant_view` + scope
+ events) & dev-ex, package READMEs/ARCHITECTURE, `slack.mdx`.
- **Hooks:** `chore(hooks)` makes the `lint-fix` script
double-quote-free (it was breaking `sh -c "…"` on Windows/lefthook
2.1.1).
## Tests / verification (CI-runnable)
- New unit tests: `native-stream` (cadence, 12k continuation, fence
re-open, first- **and** continuation-`startStream` fallback),
`assistant` (defaults-before-onThreadStarted ordering, thread-scoped
turn, auto-title), listener no-double-delivery, renderer pane-status
mode, engine capability gating.
- Verified locally: `@copilotkit/bot` 31 tests, `@copilotkit/bot-slack`
199 tests, `bot-ui` tests; `tsc` clean (bot + bot-slack); build clean;
oxlint/oxfmt clean; publint/attw pass.
## ⚠️ E2E not covered here
The §8 end-to-end flows need a **real Slack workspace** with the Agents
toggle (open pane → chips → streamed reply w/ live status; channel
mention → native markdown; kill `startStream` → legacy fallback;
non-agent app → shipped behavior). `examples/slack` is the vehicle.
Three items remain to confirm against a live workspace (spec §7 spikes):
the `isAssistantThread` runtime detection, the exact `assistant_view`
manifest acceptance, and `stopStream` finalize tolerance.
Spec:
https://app.notion.com/p/copilotkit/Slack-Agent-APIs-in-bot-slack-assistant-pane-native-streaming-by-default-spec-37b3aa38185281f5948dc6a665064d04
|
||
|
|
c88d687456 |
fix(showcase/harness): bubble-race elimination — 4 defects + atomic readCascadeState + cold-start retry + SSE counter (#5462)
## Summary
Fixes 4 bubble-race defects in the CopilotKit showcase e2e harness's
conversation runner that caused flaky turn-completion detection on
slow-streaming and cold-start integrations.
**Defects fixed (RED → GREEN):**
- **Defect 1** (fast-replay): turn settled on count, not text/SSE →
false-positive settle
- **Defect 2** (multi-turn flicker): un-turn-scoped bubble selection via
`list[last]` → cross-turn leak
- **Defect 3** (cascade blindness): diagnostic cascade picked wrong tier
→ wrong bubble text
- **Defect 4** (boot-time baseline staleness): pre-paint/stale bubble at
boot poisoned turn-1 settle
**Core changes:**
- `waitForTurnComplete` 3-conjunct primitive (SSE counter + DOM-at-index
+ text-quiet for settleMs)
- Atomic `readCascadeState` helper — single `page.evaluate` returns
`{count, text}` from one cascade tier (prevents within-poll cross-tier
inconsistency)
- Probe contract: `assertions(page, ctx: {bubbleIndex, text})` —
turn-scoped bubble retrieval (replaces `list[last]`)
- SSE interceptor — `__hk_runsFinished` counter via CDP + page-side
fetch wrap; idempotent attach/detach + handle-cache invalidation on stop
+ `framenavigated` reset (cold-start retry compatible)
- Cold-start retry — single bounded retry on first-attempt banner;
reload + re-resolve chat input + settleMs floor honoring shared turn
deadline
- Banner fast-fail — baseline banner snapshot + differs-from-baseline
2-poll debounce; in-poll `BannerVisibleError` translates to
`AssistantErroredError`
- Pre-paint placeholder env adapter for defect-4 repro infrastructure
**Defect repro tests:** real-browser tests in
`test/integration/bubble-race-repro-defect-{1,2,3,4}.test.ts` plus
mechanism-GREEN tests and `wait-for-turn-complete.test.ts` (3-conjunct
classification matrix), `probe-contract.test.ts` (ctx bridge),
`sse-counter.test.ts` (counter increment + framenavigated reset +
handle-cache invalidation).
## Test plan
- [x] Full harness vitest: 2742/2742 passing
- [x] Defect-1, defect-2, defect-4 RED tests verified GREEN after fix
- [x] Mechanism-GREEN tests pin internal contracts (cascade pollution
guard, SSE counter atomicity, init-script navigation re-entry, etc.)
- [x] cr-loop converged across 10 rounds (25→6→6→6→4→2→0 bucket-(a)
findings) + Procedure 3 audit clean
- [x] `oxfmt --check` clean across all 31 changed TS files
- [x] `tsc -p tsconfig.build.json` clean
- [ ] CI green on this PR
- [ ] Manual smoke: `bin/showcase test --d5
langgraph-python:agentic-chat` post-merge
## Follow-up debt (deferred from cr-loop; not subject-scope)
Detected by cr-loop reviewers but out of this PR's scope:
- `d6-all-pills.ts` deploy-churn NSF aggregation — features in
`notSupportedFeatures` lose `incapable` classification during
deploy-churn skip
- `d6-all-pills.ts` `joinAimockJournal` slug fallback under D6
concurrency — can pick wrong-feature entry when aimock doesn't echo
`x-diag-run-id`
- `d6-all-pills.ts` `BrowserDisconnectedError` sentinel is constructed
but never matched in `runFeature` catch — gets bucketed as
`driver-error`
- `d5-tool-rendering-default-catchall.ts` Path B narration
false-positive (substring matches in assistant prose)
- HITL registry side-effect tests use manual fallback registration that
masks real registration failures
- `installPrePaintFromEnv` and `attachSseInterceptor` are re-registered
on every `page.goto` — accumulate init scripts; harmless today via
in-script idempotency guards
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
1ef6a0960b |
test(harness): update d5-* + d6-all-pills unit tests to new probe + cascade contract
- d5-* unit tests: update page fakes / assertion-call shapes to pass
the ctx bridge ({bubbleIndex, text}) instead of querying the live
bubble list
- d6-all-pills.test.ts: update page-fake dispatch + cascade-state
expectations to match the atomic readCascadeState return shape
({count, text} or null) — picks up the cross-tier consistency
guarantee in the unit layer
|
||
|
|
cd2af0f9fd |
test(harness): conversation-runner unit coverage — banner debounce, cold-start retry, skipFill, error-banner shapes
Comprehensive unit coverage for: - preFill + fillAndVerifySend retry behavior - baseline banner snapshot + differs-from-baseline debounce matrix (stable banner = no fail; changed banner = fast-fail after 2 polls) - cold-start retry cases (first-attempt banner → reload + retry; retry-then-banner → translatedErr; retry exhausts deadline) - skipFill / skipSend short-circuit paths - ErrorBannerReadResult union shape variants (present/absent/unknown) |
||
|
|
3d49d4c38f |
test(harness): bubble-race integration tests + defect reproductions
- bubble-race-repro.ts: shared driver harness for repro tests
- bubble-race-repro-defect-{1,2,3,4}.test.ts: real-browser RED tests
that fail without the fix and pass with it
- Defect 1: fast-replay false-positive settle on count
- Defect 2: multi-turn flicker via list[last]
- Defect 3: cascade-tier blindness
- Defect 4: boot-time baseline staleness / pre-paint placeholder
- bubble-race-mechanisms.test.ts: GREEN tests pinning internal
contracts (cascade-pollution guard, atomic readCascadeState,
init-script idempotency)
- wait-for-turn-complete.test.ts: 3-conjunct classification matrix
- probe-contract.test.ts: ctx-bridge shape
- sse-counter.test.ts: counter increment + framenavigated reset +
handle-cache invalidation
|
||
|
|
cf7efbda95 |
fix(harness): d6 driver — installPrePaintFromEnv + attachSseInterceptor wiring + helper-based captureDiagnostics
- d6-all-pills.ts: wire installPrePaintFromEnv and attachSseInterceptor PRE-goto in both defaultLauncher and pooled launcher paths, so first-paint state is deterministic and the SSE counter is armed before any page navigation - captureDiagnostics rewired through the Node-side helper (readCascadeState) — single page.evaluate per call, no within-snapshot tier drift - Consume BUBBLE_RACE_MESSAGES override for defect-4 repro flows |