mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
v1.66.3
14363 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b3c7d57406 |
fix(telemetry): commit the reconciled canonical in docs fragment PRs too
Revalidation against current main (the branch was 1345 commits behind): - docs workflow ran `pnpm reconcile` but `add-paths` listed only the fragment, so its PR would land a fresh fragment beside a stale telemetry-events.json and fail the registry's telemetry-reconcile staleness gate — the exact failure the runtime workflow was already fixed for. Verified against the registry's shipped emitters: every automated fragment PR there (website.corp, Intelligence surfaces) carries telemetry-events.json alongside its fragment. - Refresh the action pins to the SHAs main now uses everywhere (checkout v7, setup-node v7.0.0, pnpm/action-setup v6.0.10). - Narrow the docs trigger to code under shell-docs/src, excluding src/content (1000+ MDX/JSON prose files that cannot hold a posthog.capture call site) so prose edits stop firing a full install. - Note in the zizmor justification why setup-node v7's new package-manager-cache auto-path still leaves this workflow cacheless (it engages only for npm-declared repos; this one declares pnpm). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5855496103 |
fix(telemetry): reconcile + commit canonical in fragment PRs
A fragment-only PR fails oss-path-to-production's telemetry-reconcile gate (it recomputes telemetry-events.json and fails on staleness). After emitting each fragment, install the registry's deps and run pnpm reconcile, then include telemetry-events.json in the PR alongside the fragment — matching the Intelligence CLI + surface-emitter pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c785dcea64 |
fix(telemetry): use TELEMETRY_REGISTRY_APP_* (dedicated registry App), not DEVOPS_BOT
The cross-repo fragment PRs must be authored by the dedicated telemetry-registry GitHub App that's installed on oss-path-to-production (the same App the Intelligence CLI release workflow uses), not CopilotKit's DEVOPS_BOT release bot. Switch both workflows to app-id/private-key from secrets.TELEMETRY_REGISTRY_APP_ID / TELEMETRY_REGISTRY_APP_PRIVATE_KEY and gate the mint on the App ID env var. These secrets must be added to the CopilotKit repo (they currently live only on Intelligence). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6e702c906b |
feat(telemetry): emit telemetry-registry fragments for runtime + docs surfaces
Adds scripts/telemetry/ (emit-fragment.ts + extract.ts) and two CI workflows that generate CopilotKit's telemetry-registry fragments and open path-limited PRs into CopilotKit/oss-path-to-production: - runtime (bespoke catalog): reads the AnalyticsEvents type map for event names + properties, scans capture() sites for call_sites, fails loud if the v1/v2 catalogs diverge. Triggered on stable monorepo release. - docs (callee mode): extracts inline posthog.capture literals from showcase/shell-docs (drops $-reserved events). Triggered on push to main touching showcase/shell-docs/**. Both are content-gated: the fragment is left untouched (and no PR opened) when the event set is unchanged, so releases/edits don't churn the registry. Cross- repo token follows the least-privilege recipe (no owner, bare repositories, contents+PR write); mint gated on a job-level env var. zizmor clean (one justified cache-poisoning suppression). 13 unit tests; tsc + oxlint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
55aaad21a6 |
test(react-core): add React 18 + 19 unit-test CI matrix (#6060)
## Summary
Adds a `react-version` matrix axis (**18**, **19**) to the unit-test
workflow so react-core, react-ui, and a2ui-renderer are exercised across
the full **supported peer range** (`^18 || ^19`), not just the
repo-default React 19.
This is a **reconstruction of the durable parts of #4221**
(@tylerslaton) onto current `main`. That PR went stale (~5,000 commits
behind, conflicting) and never landed. Rather than rebase it, this
rebuilds its design fresh — and deliberately **scopes to the supported
React range**: React 17 is dropped, because it is no longer a supported
peer version and carried ~80% of the original PR's complexity
(polyfills, `use-sync-external-store` source shims, `jsx-runtime`
aliases, a legacy `renderHook` fallback).
## What surfaced
Dropping R17 and validating R18 revealed a **latent React 18
incompatibility on current `main`**: the `window = {}` test pattern
crashes React 18's concurrent renderer with `"Should not already be
working."` mid-commit, which then corrupts the scheduler for the rest of
the file — **22 failures across 5 files** under React 18. (React 19
happens to tolerate the empty-window swap, so it was invisible until
now.)
The original PR fixed this but mislabeled it R17-only; it's actually
needed for R18, a *supported* version. So the matrix earned its keep on
day one.
Replacing `window = {}` with `stubWindowLocation()` is the load-bearing
fix — it resolves the crash cascade. Separately, **two** tests differ
under R18 purely in *render scheduling*, and are handled by narrow
version gates:
| Test | React 18 behavior | Why it's not a bug |
|---|---|---|
| `renderCustomMessages` → "executes multiple renderers in order" |
`executionOrder` is `["first", "first"]` | Renderer double-invoke.
`second` still never runs, which is the actual contract. |
| `use-human-in-the-loop` → `statusHistory` | `inProgress → executing →
inProgress → complete` | Transient backwards transition from extra
effect runs. Start, end, and the set of observed statuses are all still
correct. |
**No assertion tolerates a different state value.** An earlier revision
of this PR also relaxed the three-turn state-snapshot assertion to
accept `Turn: 2` on R18; @tylerslaton correctly flagged that as an
observable-behavior difference rather than a scheduling artifact.
Re-verified against a real 18.3.1 install — the strict `Turn: 3`
assertion passes **25/25** consecutive runs — so that gate was
unnecessary and has been removed (`a227f46a8`). The two gates above were
re-tested the same way and both genuinely reproduce.
## Changes
| File | What |
|---|---|
| `.github/workflows/test_unit.yml` | `react-version: ["18","19"]` axis.
R19 installs frozen; R18 overrides the root `pnpm.overrides` React
version and installs unfrozen. Adds a guard verifying the installed
React matches the matrix leg, and suffixes `NX_CI_EXECUTION_ID` with the
React version. Layered on top of the existing nx-affected selection
logic. |
| `test-helpers/stub-window-location.ts` *(new)* | Clears
`window.location` (so the localhost auto-open-inspector heuristic skips)
while keeping the real jsdom window — the safe replacement for `window =
{}`. |
| `use-agent-error-state`, `CopilotKitProvider.onError`,
`CopilotKitProvider.test` | Swap `window = {}` for
`stubWindowLocation()`. |
| `use-human-in-the-loop.e2e`, `renderCustomMessages.e2e` | Two
React-version-gated assertions, both **render-scheduling only** (see
table above). State assertions stay strict on every leg. |
No dependency or lockfile changes. None of the R17-only machinery from
#4221.
## CI cost
Full runs go from 3 legs (node 20/22/24) to **6** (node × react). On
PRs, nx-affected still scopes what actually builds/tests; the full 6×
only hits `workflow_dispatch` or when `test_unit.yml` itself changes (so
this PR runs all 6). This is the honest price of adding R18 coverage.
## Testing
Run locally in a worktree via the exact install-override logic the
workflow uses — `react`/`react-dom` → 18.3.1,
`@types/react`/`@types/react-dom` → `^18`, `@testing-library/react` →
`^14.3.1`, `streamdown>react` → 18.3.1, then `pnpm install
--no-frozen-lockfile`. Installed versions confirmed by resolving from
`packages/react-core` (18.3.1 / 19.2.3, `@testing-library/react` 14.3.1
on the R18 leg).
| Check | Result |
|---|---|
| react-core full suite @ React 18.3.1 | **117 files, 1433/1433
passing** ✓ |
| react-core full suite @ React 19.2.3 | **117 files, 1433/1433
passing** ✓ |
| Strict `Turn: 3` state-snapshot assertion @ R18, ×25 runs | **25 pass
/ 0 fail** — gate removed as unnecessary |
| `executionOrder` gate reverted to strict @ R18 | **fails**
(`['first','first']`) — gate justified |
| HITL `statusHistory` gate reverted to strict @ R18 | **fails** (extra
`inProgress`) — gate justified |
| `oxlint` (project-aware) | **0 warnings, 0 errors** — unchanged from
`main` |
| `oxfmt --check` | clean |
| Workflow YAML parse + lefthook commit hooks (lint-fix, package tests,
commitlint) | green |
Before the `window` fix, the R18 leg was **22 failing across 5 files**;
it is now fully green.
Credit to @tylerslaton for the original design in #4221, and for
catching the over-relaxed state assertion in review.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
7e502dd4a4 |
fix(react-core): preserve logical run identity across HITL resolve (#6296)
## Summary
Preserve the logical run ID when a legacy `useCopilotAction({
renderAndWaitForResponse })` frontend tool resolves and
`processAgentResult` starts its recursive follow-up.
The run handler binds each internal continuation handoff to the exact
follow-up invocation, cancels it when setup fails or no run starts, and
keeps the handoff out of the public `CopilotKitCore.runAgent` contract.
The public regression drives the legacy hook through its
`useHumanInTheLoop` and `useFrontendTool` path, renders the approval
control, resolves it, and verifies both agent calls use the same
generated ID.
Closes https://github.com/CopilotKit/CopilotKit/issues/3456
## Changes
- Preserve the originating ID across recursive frontend-tool follow-up
runs
- Keep the existing legacy HITL registration and response behavior
unchanged
- Add core follow-up coverage and a public `useCopilotAction` regression
- Retain the existing standard/legacy interrupt and StateManager
coverage from the earlier fix
## Test plan
- [x] `pnpm -C packages/react-core exec vitest run
src/hooks/__tests__/use-copilot-action.e2e.test.tsx`
- [x] `pnpm -C packages/core exec vitest run
src/__tests__/core-follow-up.test.ts`
- [x] `pnpm -C packages/react-core exec vitest run
src/v2/hooks/__tests__/use-interrupt.test.tsx`
- [x] `pnpm -C packages/core exec vitest run
src/__tests__/state-manager.test.ts`, 39 tests passed
- [x] `pnpm -C packages/react-core exec vitest run`, 123 files and 1475
tests passed
- [x] `pnpm -C packages/core exec vitest run`, 58 files and 625 tests
passed
- [x] `pnpm -C packages/core run check-types`
- [x] `pnpm -C packages/react-core run check-types`
- [x] `pnpm exec oxfmt --check` on all eight changed source/test files
- [x] `pnpm exec oxlint` on all eight changed source/test files, 5
pre-existing warnings and 0 errors
|
||
|
|
d4bb53940d |
fix(banking): collapse HITL approval buttons on the tool result (#6401)
Approve/Deny buttons could reappear on a card the user had **already answered**. The clearest case is the teach arc: after "Open policy exception" is approved and the chain moves on, that first card comes back showing live buttons, while the two later cards correctly read "Response submitted." ## Root cause `"Response submitted."` was purely local `useState`. Nothing tied it to the tool call, so any remount lost it — and the earliest card in a multi-step chain has its subtree replaced when the run syncs. That's why card 1 regresses and cards 2 and 3 don't. The visual glitch is the mild part. The buttons are live: clicking them again fires a duplicate write against an already-settled call. ## Fix `ApprovalButtons` takes a `resolved` prop carrying the durable signal from the tool call itself, OR-ed with local state: - local state collapses the buttons instantly on click, without waiting for the round trip - `resolved` survives remounts and thread reloads Same result-over-status rule already applied to the PIN and charges cards in #6259. ## Scope Wires the 3 call sites that had no guard. The 3 that already collapse on `result` are unchanged — there TypeScript correctly rejects `status === "complete"` as unreachable, which is a useful signal that the guard is already in place. The identical bug existed in `CopilotKit/fde` `reskinnable-demo`, where 10 of 13 call sites were unguarded. Fixed there separately (`58d400b` on that repo's main). ## Verification `tsc --noEmit` and `oxlint` clean. Not yet exercised in a browser: reproducing it means running the teach arc through to the AWS climax. The change is mechanical and type-checked, but flagging that the runtime behaviour is reasoned rather than observed, since on this demo green checks have not always meant the surface is right. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
e1b1ebf3c6 |
fix(banking): collapse HITL approval buttons on the tool result
Approve/Deny buttons could reappear on a card the user had already answered. The clearest case is the teach arc: after "Open policy exception" is approved and the chain moves on, that first card came back showing live buttons while the later cards read "Response submitted." "Response submitted." was purely local useState, so nothing tied it to the tool call. Any remount lost it, and the earliest card in a multi-step chain has its subtree replaced when the run syncs. Clicking the resurrected buttons would fire a duplicate write against an already-settled call. ApprovalButtons now takes a `resolved` prop carrying the durable signal from the tool call itself, OR-ed with the local state: local collapses the buttons immediately on click, `resolved` survives remounts and thread reloads. This is the same result-over-status rule already applied to the PIN and charges cards. Wires the 3 call sites that had no guard. The 3 that already collapse on `result` are unchanged — there TypeScript correctly rejects the status comparison as unreachable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
87a33ee6b8 |
fix(runtime): bound the in-memory agent runner to prevent OOM (#5837)
## Problem
The OSS/SSE runtime path — `CopilotRuntime`/`CopilotSseRuntime` built
**without** an Intelligence backend — uses `InMemoryAgentRunner`, which
kept all thread history in a process-global `Map` that nothing ever
bounded. A production Node.js deployment (`--max-old-space-size=4096`)
hit a fatal V8 OOM after ~173 threads / ~15.5k runs. Three unbounded
growth vectors: thread count, runs per thread, and per-run
`ReplaySubject(Infinity)` buffers.
## Fix
Make the in-memory runner **safe by default**, zero-config and
dependency-free.
- **`ɵBoundedThreadStore`** owns the `Map`, LRU ordering, approximate
byte accounting, and eviction. `InMemoryAgentRunner` keeps all streaming
logic and delegates storage.
- **Three bounds, whichever trips first:** `maxThreads` (**1000**, LRU),
`maxRunsPerThread` (**100**, FIFO; `Infinity`/`0` disables), `maxBytes`
(**512 MiB**, the primary guard).
- **Never evicts** a running thread, one still finalizing after
`stop()`, or the thread currently being created/appended — it accepts
temporary overage instead.
- **Thread-level `messagesSnapshot` and `createdAt`**, decoupled from
`historicRuns`, so run-cap eviction can neither drop the message history
nor drift a thread's reported creation time.
- **Subject-buffer release:** `ReplaySubject(Infinity)` is retained only
while a run is active, released on completion once events are in
`historicRuns`.
- **Both eviction paths log**, through one warn-once-per-store latch:
whole-thread eviction *and* per-thread run-cap trimming.
- **Limits validation:** `ɵnormalizeLimits` rejects negative / `NaN` /
non-integer / `-Infinity` values at construction and at `setLimits`,
clamping to defaults with a warning. Previously `maxRunsPerThread: -1`
crashed with a `TypeError` inside a fire-and-forget path.
- **Partial limit updates preserve siblings** — tuning one bound no
longer silently resets the others process-wide.
## API
Additive and non-breaking. Bounds ride on the existing options bag
introduced by `onConcurrentRun`:
```ts
export interface InMemoryAgentRunnerOptions extends InMemoryLimits {
onConcurrentRun?: "throw" | "supersede";
}
new InMemoryAgentRunner(); // unchanged, safe defaults
new InMemoryAgentRunner({ onConcurrentRun: "supersede" }); // unchanged
new InMemoryAgentRunner({ maxThreads: 200, maxBytes: 128 * 1024 ** 2 });
```
Note the differing scopes, documented on both surfaces: the limits
reconfigure the **process-global** store; `onConcurrentRun` is
**per-runner**. Passing only `onConcurrentRun` leaves limits untouched.
## Interaction with `onConcurrentRun: "supersede"`
Rebased onto `main` after supersede landed, which surfaced a bug neither
change had alone. A superseded run's teardown was not isolated from the
run that replaced it: the new run cleared the shared `stopRequested`, so
the superseded run finalized as a synthetic `RUN_ERROR`, and the
`prevSubject` bridge forwarded that terminal event — plus a replayed
`RUN_STARTED` — into the **live** run's subject. `connect()` consumers
replayed a duplicate `RUN_STARTED` followed by a dangling `RUN_ERROR`.
Fixed by capturing per-run finalize intent (`RunFinalizeControl`),
widening the concurrency gate to `isRunning || stopRequested`, removing
the leaking bridge, and identity-guarding the subject release. Both
teardown paths were extracted into one shared `finalizeRun` helper so
they cannot diverge again.
## Docs
Neither constructor option was documented before this PR.
- `backend/agent-runner.mdx` — the bounds and their defaults, the
**eviction model stated once** (whole-thread eviction removes the thread
from `GET /threads`; run-cap trimming keeps it, preserving `createdAt`
and the message snapshot; the warning is once-per-store, reset by
`clearThreads()`), the `onConcurrentRun` modes, and durability options
including the first-party `SqliteAgentRunner`.
- `troubleshooting/common-issues.mdx` — growing runtime memory and the
eviction warning, cross-linked both ways.
- Runtime skill references refreshed — they still described a removed
`globalThis`-keyed store with HMR backup — and their source citations
re-anchored on stable symbols rather than line numbers.
## Tests
**1939 passing, 0 skipped** across 133 files (`@copilotkit/runtime`);
`check-types`, `build`, `publint`, and `attw` all clean.
- `ɵBoundedThreadStore` units: thread-LRU, run-cap FIFO + opt-out,
byte-ceiling eviction, never-evict-running, never-evict-finalizing (both
count and byte paths), snapshot dedup, `createdAt` stability past the
run cap, warn-once, `clear()` reset.
- Limits validation: every field × every invalid shape, plus proof the
`0`/`Infinity` sentinels still disable the run cap.
- Supersede/stop: teardown isolation with **negative** assertions (no
phantom `RUN_ERROR`, exactly one `RUN_STARTED` for a `connect()`
subscriber on a superseded thread), plus `stop()`'s reachable rollback
branches.
- **OOM guard:** drives a sustained workload across the thread-count,
runs-per-thread, and byte bounds and asserts eviction through the
runner's public surface. It replaces an earlier heap-plateau test that
was both `--expose-gc`-gated (so it never ran in normal CI) and
non-discriminating (its workload crossed no bound, so it passed with
bounding removed). The replacement was verified to fail when
`enforceRunCap` is neutered.
## Caveats & follow-ups
1. **Non-durable by design.** Eviction is history loss, same as a
restart. The durable paths are `SqliteAgentRunner` (single instance) or
the Intelligence runner.
2. **Store stays process-global.** Pre-existing cross-endpoint bleed and
process-wide `clearThreads()` are unchanged; instance-scoping is a
follow-up needing maintainer sign-off.
3. **Multiple runners with differing limits → last-constructed wins**,
with a one-time warning. Partial updates now preserve unspecified
bounds.
4. **Byte accounting is approximate** (serialized-length estimate), not
exact heap bytes, and under-counts non-serializable payloads.
5. **Behavioral change to a default path.** History that previously
persisted until OOM now evicts past the limits — shipped as a `fix`
(crash → bounded), observable at scale. Run-cap trimming now also logs
once per store.
6. **`maxBytes` is a cross-thread ceiling.** It evicts other LRU threads
and never trims the just-appended thread, so a single hot thread is
bounded by `maxRunsPerThread` only — which is why the docs steer away
from disabling that cap.
7. **Dedup weakens past the run cap.** `historicMessageIds` is rebuilt
from `historicRuns`, so a thread beyond `maxRunsPerThread` can
re-present already-seen messages. Documented with its escape hatch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
ab71913932 | Merge branch 'main' into fix/in-memory-runner-bounding | ||
|
|
77d99f6bd5 |
docs(reskinnable-demo): teach the reskin skill what the demo must prove (#6400)
## What does this PR do? Reworks the repo-local `reskin` skill so it teaches **what a skin's demo has to prove**, not just how to wire the `Skin` contract — based on David's walkthrough of the banking demo (the "beats" and the why behind each one), plus the layout facts from #6394. Docs only. No code changes. ## Why The skill documented the contract thoroughly and the demo not at all. I tested that gap before writing anything: gave a fresh agent the **old** skill and asked it to plan a new BI skin. It returned a technically sound plan — correct contract usage, server-emitted a2ui, deps arrays, OGUI sandbox, dark mode — that missed most of the beats, and substituted its own thesis (RBAC governance) for the demo's. | Beat | Old skill | Reworked skill | | --- | --- | --- | | 1. Lead with generative UI | ✅ | ✅ | | 2. Rich thread survives reload | ❌ absent | ✅ explicit step; renders keyed off `result` | | 3a. Drive the app, secret withheld | ⚠️ reframed as RBAC | ✅ confidential figure never enters the transcript | | 3b. "What's on my screen?" | ❌ **zero readables planned** | ✅ route + per-page readables, asked on two pages | | 3c. Navigate via real levers | ⚠️ plain `navigateTo` | ✅ HITL confirm → query params → highlighted controls | | 3d. Multimodal → durable artifact | ⚠️ no durability | ✅ artifact outlives thread deletion (demonstrated) | | 4. Long-term memory recall | ⚠️ one line | ✅ seeded preference + recall-first + names what it recalled | | 5. Stored-procedure replay | ❌ absent | ✅ seeded procedure, 3 visible writes, distractors | | 6. Teach a new procedure | ❌ absent | ✅ symptom-only gate, decoys, record → save → replay | | Presenter Reset | ❌ | ✅ | | Pills covering the flow | 6, partial | 8, one per beat | Same prompt, same conditions, only the skill changed. The telling detail is 3b: `useAgentContext` was already in the skill's contract table, so the agent *knew* the field existed — it just had no reason to prioritise it. Contract docs say what is available; only choreography says what is required. ## What changed **`.claude/skills/reskin/demo-beats.md`** (new) — nine beats, each framed by *what the audience must conclude*, then what the skin needs, then banking's implementation with file:line. Plus seeding rules, the presentation requirements (a pill per beat so the presenter never types, a visible affordance on every mutation, pretty markdown prose, Reset, the chat-placement framing), the vertical shortlist with BI called out as highest-stakes, the quality bar, and a "which skin to copy for what" routing table. The beats are a **strong default that an explicit instruction overrides**, expressed as a beat map with one row per beat that records deliberate skips. Omission failures respond to a slot you have to fill in, not to exhortation. **`SKILL.md`** — beat table and override rule up front; step 0 is the beat map, before code. Two new silent-failure rules found by tracing banking: - Renders must key off the tool `result`, not `status`, or they go blank on thread replay — exactly when beat 2 is being demonstrated. Only banking does this today. - Beat 3b needs a route readable plus per-page on-screen readables. All four skins register readables; only banking registers those, which is why the beat is impossible in the other three. Verification now ends with a 10-point demo walkthrough, since a green build proves the wiring and nothing about the demo. **`templates.md`** — pills-per-beat template; `seed-memories.ts` against the real `POST /api/memories` shape (long-term memory is a seeded file, not emergent — including why beat 6's procedure must never be seeded, and why a seeded procedure must run with no confirmation gate, which previously left an unresolved tool call that failed the next message); a prompt-clause table mapping clauses to beats; replay-safe render; route and per-page readables. **`CLAUDE.md`** — claimed four skins in the intro and then documented two. Now covers all four across both substrates with a beat-coverage matrix, and corrects three false claims: - `identifyUser` was attributed to banking alone; logistics and keel contribute one too. - An unsupported claim that a `Panel`'s `id` becomes its emitted `data-testid` — the cited header comment says no such thing and the installed package has no such behaviour. Replaced with what it actually documents. - "two implementations" in Reference. Also notes keel's parameterized routes (the only skin with them) and the shell-mounted inspector that replaced the glass engine. ## Notes for reviewers - Verified against source rather than assumed: the entire `Skin` contract field table in both docs was already accurate and is unchanged; so were the Commands section, all panel-size/breakpoint/radius numbers, the `.d.cts` filename the skill cites, and `defaultSkinId`. There were no glass-engine references left to remove. - Two follow-ups surfaced but deliberately not included here: `banking/components/wow/proactive-notice.tsx` is an `animate-ping` bell toast that is mounted nowhere, and logistics/keel already ship the full per-user identity plumbing with no memory prompts, tools or seed file — the cheapest beat-4 upgrades available if we want existing skins pulled up to the bar. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
b93b0951bc |
docs(reskinnable-demo): teach the reskin skill what the demo must prove
The skill documented how to wire the `Skin` contract and nothing about what the resulting demo has to demonstrate. Tested by asking a fresh agent to plan a new BI skin with only the old skill: it produced a technically sound plan that missed most of the banking demo's beats — no rich-thread step, zero `useAgentContext` readables, no stored-procedure or teach-mode arc — and substituted its own thesis (RBAC governance) for the demo's. Contract documentation says what is available; only choreography says what is required. Adds demo-beats.md: the nine beats framed by what the audience must conclude, each with banking's implementation cited, plus the presentation requirements (a pill per beat so the presenter never types, a visible affordance on every mutation, pretty prose, Reset, the chat-placement framing), the domain shortlist and the quality bar. The beats are a strong default an explicit instruction can override, expressed as a beat map with a row per beat that records deliberate skips — omission failures respond to a slot you must fill, not to exhortation. Re-running the same planning task with the reworked skill covers all nine. Also folds in what tracing banking turned up, all of it silent-failure class: renders must key off the tool `result` and not `status` or they go blank on thread replay, exactly when "reload and the chart is still there" is being shown; "what's on my screen?" needs a route readable plus per-page on-screen readables, which is why that beat is impossible in the three other skins; and long-term memory is a seeded file, not emergent, so seed-memories.ts gets a template — including why beat 6's procedure must never be seeded and why a seeded procedure must run without a confirmation gate. Corrects CLAUDE.md, which claimed four skins and then documented two. Now covers all four across both substrates with a beat-coverage matrix, fixes the `identifyUser` attribution (logistics and keel contribute one too), drops an unsupported claim that a Panel's `id` becomes its `data-testid`, notes keel's parameterized routes, and acknowledges the shell-mounted inspector that replaced the glass engine. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
20f2ff8f4c |
fix(showcase): move Mastra MCP Apps agent to gpt-5.4
Owner preference for the 5.x line. Recorded honestly: this reduces the
empty-diagram failure but does not remove it.
Measured against the real Excalidraw MCP server (same system prompt, real
tool schema, via the Responses API the AI SDK actually uses):
gpt-4o-mini create_view OK 3, isError 5
gpt-5.4 create_view OK 7, isError 3
gpt-4.1 create_view OK 8, isError 0
gpt-5.5 create_view OK 10, isError 0
JSON validity of the `elements` argument:
gpt-4o-mini 7 invalid of 12
gpt-5.4 7 invalid of 28, plus two runs whose tool call came back
garbled with unrelated spam text
gpt-5.4 + a hardened prompt 2 invalid of 16 (prompting does not fix it)
gpt-4.1 0 invalid of 12
gpt-5.5 0 invalid of 16
So roughly 30% of diagrams still render as an empty iframe on gpt-5.4. Closing
that gap needs a follow-up, most likely validating or repairing the `elements`
string before the MCP call rather than relying on the model to hand-escape
nested JSON correctly.
|
||
|
|
fed66e046f | Merge branch 'main' into ran/pni-121-mastra-tool-rendering-results-delivered-out-of-sequence | ||
|
|
73fe341c3d | Merge branch 'main' into claude/competent-chatelet-7305ee | ||
|
|
b1aebc706d |
fix(mastra-showcase): match gold model — beautifulChatAgent on gpt-5.4
Gold `beautiful_chat.py` uses `ChatOpenAI(model="gpt-5.4")`; align the mastra beautifulChatAgent to the same model (was gpt-4o). Verified live: dashboard turn stays [query_data, generate_a2ui] (no standalone chart over-call), full surface renders (3 metrics + pie + bar). |
||
|
|
5d5546d3fc |
Assert finish() is present in the regression test
finish is optional on the renderer type, so calling it unguarded fails tsc even though vitest is happy. Matches the non-null assertions the rest of the file uses for subscriber callbacks. |
||
|
|
2aa6f35edf |
fix(showcase/mastra): align tool-rendering agent to gold's model
PNI-121's reported symptom was "the weather appears after 'Find flights from
SFO to JFK'". Two separate things produced that screenshot; the blank flight
rows were the previous commit. This is the other one.
On gpt-4o this agent opened the flights turn by restating the PREVIOUS turn's
weather ("The weather in San Francisco is currently 20C with heavy rain.")
before narrating the flights, which reads exactly like a tool result arriving
a turn late. Nothing was actually late: only one weather card exists and it
stays in turn 1, and the flights turn's card holds the flights turn's data.
The system prompt is already byte-identical to gold tool_rendering_agent.py,
so the model was the remaining divergence - gold runs gpt-5.4.
Verified live (real LLM, ticket's exact click order - "Weather in SF" then
"Find flights"): the stale weather sentence is gone and the narration now
matches gold's shape ("SFO -> JFK options: United UA231 08:15-16:45 $348; ..."
against gold's "Flights SFO -> JFK: United UA231 08:15-16:45 $348; ...").
Weather, stock and d20 pills re-checked on the same rig.
No effect on CI: aimock fixtures never match on model, and the d20 pill's
5-roll chain is fixture-scripted (7/14/3/19/20), so the e2e sequence is
unchanged. Live, gold rolls the d20 once too - so this moves the demo toward
gold rather than away from it.
|
||
|
|
a04c708e5d | Merge branch 'main' into claude/framework-d6-integration-validate-7be45e | ||
|
|
20201bcd51 |
fix(mastra-showcase): drop the recorded dashboard over-call from the aimock fixture
recorded.json baked in a real 2026-05-15 gpt-4o session that, for the beautiful- chat Sales Dashboard pill, called standalone pieChart+barChart at turnIndex 2 AFTER already calling generate_a2ui — so under aimock the dashboard painted loose charts next to the A2UI surface (the same over-call the live fix removed via parallelToolCalls:false + steering). Replace that turnIndex-2 leg's pieChart+barChart tool calls with narration so the aimock chain is query_data -> generate_a2ui -> narration, matching the fixed live behavior. The now-redundant turnIndex-3 narration leg is folded in. |
||
|
|
753b1445eb |
fix(mastra-showcase): stop beautiful-chat dashboard from over-calling standalone charts + align flights fixture
Issue: asking for the Sales Dashboard (esp. after a prior turn) made the model call the standalone pieChart + barChart frontend tools AND generate_a2ui, so loose charts painted next to the dashboard. - beautifulChatAgent: mirror gold `beautiful_chat.py` `parallel_tool_calls=False` (defaultOptions.providerOptions.openai.parallelToolCalls) + sharpen the steering so a dashboard / "using A2UI" request calls generate_a2ui ONLY (it draws the charts inside the surface), while a single-chart request still uses the standalone pieChart/barChart tool. Verified live: dashboard turn now calls [query_data, generate_a2ui] only, single + after-flights. - aimock beautiful-chat flights fixture: model the fixed-schema `search_flights` path (returns the A2UI FlightCard envelope directly) instead of the old generate_a2ui -> render_a2ui chain, so the fixture matches the live behavior and the e2e spec's stated intent (United $349 / Delta $289). |
||
|
|
da3dba5704 |
fix(channels-slack): clear the status when a tool call follows the first reply
An agent that streams text, calls a tool, then streams more text leaves Slack's
native "is thinking…" indicator running indefinitely after the answer has
already been posted.
`postedReply` latches on the first posted reply and makes `clearStatus` a
one-shot. But `onToolCallStartEvent` and `onToolCallEndEvent` both write the
status again *after* that latch closes, and from then on nothing clears it:
`onFirstReply` early-returns, and the backstops in `finalizeTurnStream` and
`finish` are skipped precisely because a reply was posted. Slack eventually
expires the stale status, which is why it presents as a slow hang rather than
a bug.
The latch really tracks "the status is already cleared for what is on screen",
not "a reply has been posted", so `setStatus` now resets it whenever a
non-empty status is written. Both existing backstops then fire exactly when
they should, and the normal streamed-text path still skips the redundant clear.
Reproduces on any agent that narrates before a tool call — emitted stream is
text → TOOL_CALL_* → text — and is independent of `showToolStatus`: with it off
both tool events set the generic status, with it on START sets
"is using `tool`…". Either way the write lands after the latch.
Covered by a regression test driving that exact sequence, verified failing
without the change ("expected 'is thinking…' to be ''").
|
||
|
|
36f05f17b9 |
fix(showcase): render Mastra MCP Apps diagrams reliably on live
The MCP Apps cell intermittently rendered an empty iframe (an empty box or a thin band) on a live endpoint while passing under aimock. Root cause is the model, not the renderer. Excalidraw's `create_view` declares `elements` as `type: "string"` holding a JSON array, so the model must emit double-encoded, hand-escaped JSON. gpt-4o-mini frequently appends a stray `}` just past the closing `]`. The MCP server then rejects the call with "Invalid JSON in elements: Unexpected non-whitespace character after JSON", returns isError, and there is no diagram to draw, so the iframe paints empty. aimock never catches this because it replays a recorded, valid payload. Measured against the real Excalidraw MCP server using this agent's exact system prompt and the real tool schema: gpt-4o-mini create_view OK 3, isError 5 gpt-4.1 create_view OK 8, isError 0 And on JSON validity alone (n=12 unless noted): gpt-4o-mini 8 invalid gpt-4o-mini + hardened prompt 7 invalid (prompting does not fix it) gpt-4.1-mini 3 invalid, and it bloats output gpt-4.1 1 invalid of 24, output stays compact gpt-4.1 is already used by five other agents in this file, so this keeps the integration consistent. The cell's aimock fixture does not key on the model, so d6 replay is unaffected. |
||
|
|
33fca30cad |
fix(showcase/mastra): return gold-shaped search_flights result so flight rows render
On a live endpoint the tool-rendering flight card rendered every row blank
("United ? -> ? --") while the model's narration below it carried the real
times and prices. The result was delivered in full; the card just never
matched it.
search_flights emitted Mastra-flavored keys (flightNumber / departureTime /
arrivalTime / price) but FlightListCard - in both this integration and gold
langgraph-python, byte-identical - reads { airline, flight, depart, arrive,
price_usd }, which is exactly what gold's tool_rendering_agent.py returns.
Only `airline` overlapped, so the rest fell back to placeholders.
Return the gold result shape directly. The legacy caller-supplied `flights`
passthrough is untouched, and the only consumers of this tool are the three
tool-rendering-style agents, all of which drive gold-shaped cards.
Verified on a live real-LLM endpoint (no aimock): reproduced the blank rows
before the change, then confirmed all three rows render
"United UA231 08:15 -> 16:45 $348" (plus Delta and JetBlue) after it, across
tool-rendering, tool-rendering-custom-catchall and
tool-rendering-reasoning-chain, and across a two-turn weather + flights
conversation.
The existing e2e only asserted origin/destination and a row count, so blank
rows passed. It now asserts the result's depart/arrive/price and rejects the
"? -> ?" placeholder.
Fixes PNI-121
|
||
|
|
8df39dd9bb | style: auto-fix formatting | ||
|
|
d9bf3efcf1 |
fix(mastra-showcase): render beautiful-chat flights as fixed-schema A2UI (langgraph-python parity)
The "Search Flights (A2UI Fixed Schema)" pill narrated flight results as plain
text instead of rendering FlightCards. Root cause: mastra's beautiful-chat
reused the shared `searchFlightsTool` (returns plain `{ flights }`, which the
tool-rendering cells render via their own frontend FlightListCard), so nothing
produced an A2UI surface and the model just described the data.
langgraph-python's beautiful_chat.py wires a DEDICATED fixed-schema
`search_flights` whose tool RESULT is a complete `a2ui_operations` envelope (a
flat Row of literal FlightCards on `app-dashboard-catalog`, surface
`flight-search-results`). Mirror it:
- Add `searchFlightsA2uiTool` returning that envelope (buildFlightCardComponents
mirrors `_build_flight_components` — inline values, no structural-template
children).
- Add a dedicated `beautifulChatAgent` (query_data, todos, generate_a2ui, the
fixed search_flights, + the flight/dashboard steering prompt) and point the
beautiful-chat route at it, so the fixed-schema flights and steering don't
leak into the shared weatherAgent / tool-rendering cells.
Verified live (real LLM, dedicated agent): the flights pill paints two
FlightCard surfaces (catalogId app-dashboard-catalog, surface
flight-search-results), and the Sales Dashboard dynamic surface still renders in
full (metrics + pie + bar) via the grounded generate_a2ui.
|
||
|
|
b30527ca8a |
fix(showcase-mastra): render interactive open-gen-ui on a live endpoint (#6389)
## Problem (PNI-119)
`open-gen-ui-advanced` (and its minimal sibling) did **not** render
working generative UI on a **live** endpoint, though both passed under
aimock.
Both cells reused the shared `weatherAgent` (`instructions: "You are a
helpful assistant."`, gpt-4o). On a live LLM that emitted **static HTML
with no JS wiring** — the calculator's buttons rendered but did nothing
(staging repro: `7 + 8 =` left the display on `0`; captured SSE
`generateSandboxedUi` args contained only
`initialHeight/placeholderMessages/css/html`, no
`<script>`/`jsFunctions`/`jsExpressions`).
aimock hid the divergence: its fixtures match on
`userMessage`/`hasToolResult` and replay a fully-wired UI regardless of
the agent.
## Root cause
Two factors, one dominant:
1. **No dedicated agent.** Gold `langgraph-python` uses purpose-built
`open_gen_ui_agent.py` / `open_gen_ui_advanced_agent.py` whose system
prompt mandates a single interactive `generateSandboxedUi` call (iframe
restrictions, wire host functions via `Websandbox.connection.remote.*`,
visible result element). Mastra reused a generic agent.
2. **Context is a read-channel on Mastra.** The provider delivers the
design-skill + sandbox-function descriptors via `copilotkit.addContext`
→ `RunAgentInput.context`. The `@ag-ui/mastra` bridge maps that to
`requestContext.get("ag-ui").context` (a tool read-channel) and does
**not** inject it into the LLM prompt (unlike langgraph's
`CopilotKitMiddleware`). So even the tool description alone was not
enough for gpt-4o.
## Fix (surgical, showcase-only)
- `src/mastra/agents/index.ts` — add dedicated `openGenUiAgent` /
`openGenUiAdvancedAgent` porting gold's system prompts, with **dynamic
instructions** that read `requestContext.get("ag-ui").context` and fold
the design-skill + sandbox descriptors into the prompt (surfacing the
read-channel the Mastra-idiomatic way, matching what gold's agent sees).
- `src/mastra/index.ts` — register both.
- `src/app/api/copilotkit-ogui/route.ts` — point `getLocalAgent` at the
new agents.
No published-package or bridge change.
## Verification (live)
Local `next dev` against **real OpenAI** (aimock bypassed). All three
advanced pills render interactive, host-wired UI; host round-trips
confirmed in console:
- **Calculator** → `evaluateExpression 7*8 = 56`
- **Ping the host** → `notifyHost: Ping!` (+ "Host notified
successfully." in-iframe)
- **Inline expression evaluator** → `evaluateExpression 5 + 3 * 2 = 11`
**aimock unaffected** — matcher keys
(`userMessage`/`hasToolResult`/`context`) don't touch the prompt or
agent, so D6 replay still passes. Changed files typecheck clean.
## Follow-up (out of scope)
Live surfaced a benign `@copilotkit/runtime` OGUI-middleware warning: an
empty/null `jsFunctions` yields a JSON-patch `add /jsFunctions` with no
`value` (`OPERATION_VALUE_REQUIRED`, patch dropped). Tracked separately
(needs a runtime release).
|
||
|
|
9c3c0c0148 |
feat(reskinnable-demo): improve general layout and UI (#6394)
## What does this PR do? Reworks the reskinnable demo's layout from four flush, edge-to-edge columns into a shadcn-inset-style frame: the assistant column and the skin's app each float as a rounded card, separated by a resizable gutter. **Layout** - Two panels with simple bounds — assistant `min 250px / default 600px / max 50%`, app takes the remainder. Capping the assistant as a *share* means the app needs no floor of its own and the mobile breakpoint stays a genuine 768px. - Drag to resize, swap which side the assistant docks on, or hide it entirely. Side and open state persist, shell-global, so switching skins never rearranges the workspace. - Card radius is a fixed 12px in px, deliberately *not* `--radius` — the frame is shell chrome and must read identically in every skin. Card colours stay themed, so a reskin still restyles it. **Chat** - `CopilotSidebar` → inline `CopilotChat`, which is what lets the cluster live in a panel. That deletes a whole geometry contract (`--nw-chat-width`, `--nw-rail-offset`, `data-nw-chat-open`) plus ~110 lines of CSS that existed only to keep a floating selector off a fixed panel. - The thread rail is a fixed-width element, not a nested panel — it sits beside the conversation and hides via a container query when the card is dragged narrow. **Skin selector** - Now a dropdown, so its footprint stays flat as skins are added (the pill row took three rows at four skins). Shell controls — switcher, swap sides, hide — are grouped in the card; the chat header keeps only conversation actions. **Type** - Base scale set to 15px at the root, so it reaches all four skins with no per-skin edits. ## Notes for reviewers - `react-resizable-panels` is pinned to **4.x**, whose API is renamed from the 2.x/3.x used by six sibling examples (`Group`/`orientation`/`Separator`/`useDefaultLayout`). Only 4.x accepts pixel size constraints. See the header comment in `src/components/ui/resizable.tsx` before touching it. - The chat CSS re-key landed as a deliberate bracket — duplicate onto `.nw-chat`, make the structural change, delete the originals — because its failure mode is silent: a lint-clean, test-clean build with subtly wrong chat typography. A guard test asserts first parity, then absence. - Adds `e2e/inset-layout.spec.ts` (9 tests). Weighted toward what actually broke during development — every bug here was a sizing behaviour invisible to jsdom. - The reskin skill taught two now-retired patterns (publishing `--nw-nav-inset-*`, rooting layouts at `h-screen`); both are corrected, so authoring a new skin no longer produces dead code and a broken root height. **Known limitation:** the sub-768px overlay branch is not verified. Mobile was already broken before this work and is explicitly out of scope; the e2e suite runs at 1280px. ## Checklist - [x] I have read the Contribution Guide - [x] If the PR changes or adds functionality, I have updated the relevant documentation - [x] "Allow edits by maintainers" is checked |
||
|
|
5d3671b245 |
fix(runtime): skip value-less activity patch for null open-gen-ui params
When the LLM emits jsFunctions/css as null (frequent on open-gen-ui-advanced),
setParam coerces the value to undefined and emitParamDelta produced a JSON
Patch `{op:"add", path}` with no `value` property. fast-json-patch rejects
that client-side with OPERATION_VALUE_REQUIRED and drops the whole activity
patch (console warning, benign but noisy and fragile).
Guard emitParamDelta to skip emitting when the value is undefined. Empty
arrays and completion markers still emit. Adds a regression test feeding a
null jsFunctions and asserting no value-less patch is produced.
|
||
|
|
1c53a8c60b | Merge branch 'main' into feat/reskinnable-demo-ui | ||
|
|
7aab1bead2 |
docs(reskinnable-demo): document the inset frame and retire the old chrome
The reskin skill was the urgent part: it is the sanctioned path for adding a skin, and it still taught two patterns the frame retired. It told authors to publish --nw-nav-inset-left/right from a useEffect so the floating selector could dodge their nav -- both the variables and that selector are gone -- and to root the layout at h-screen overflow-hidden, which overflows the app card by the frame's padding. Following it produced a skin with dead code and a broken root height. The nav-inset step is removed with a short "Retired" note so it is not reintroduced by copying an older skin, and the root guidance now explains that the CARD is the bound. Adds a "The inset frame" section to CLAUDE.md covering what a reader needs before touching src/shell/layout: the two-panel model and its bounds, why the thread rail is a fixed element rather than a nested panel, the v4 API rename and the id-becomes-data-testid behaviour, where the shell controls live, the fixed 12px card radius, and the h-full requirement for skin layouts. Also corrects the composition chain, the shell's ownership list, and the four-skins-not-two count across CLAUDE.md, README.md and DESIGN.md, plus the skill's verification step, which told authors to look for their skin in a bottom-left floating pill. |
||
|
|
c70db12a43 |
test(reskinnable-demo): cover the inset layout end to end
Nine new tests over what only a real browser can see: panel order per docked side, resize bounds in both directions, persistence across a reload and across a skin switch, the rail collapse/reopen cycle, the hide/launcher cycle, that the selector renders options only while open, that every skin's chrome fits its card without scrolling the document, and that the chat header shows the active skin's assistant name. Weighted toward what actually broke. Every bug this layout shipped was a sizing or resize behaviour invisible to jsdom, which has no layout engine: a rail collapsed to zero width, a rail that could not be reopened, an assistant column that could be widened but never narrowed, and a header showing the framework default. All four passed a green unit suite. Each assertion was mutation-checked rather than assumed -- widening the assistant cap to 95% fails only the resize test, hardcoding the header title fails only the header test. Navigation goes through a gotoSkin helper that waits for the frame; the shell is client-rendered, so reading geometry straight after goto returns an empty panel set. Updates smoke.spec.ts for the dropdown: switching opens the menu first, and the "no other skin's chrome leaked" guards get STRICTER -- from exactly-one selector pill to zero occurrences, since a closed menu renders no other brand. Retires the CopilotSidebar launcher from the fixme'd specs. a2ui-canvas clicked copilot-chat-toggle, which no longer exists -- inert only because the spec never runs, so it would have failed the moment anyone re-enabled it. memory-learning keeps its guarded open-chat block deliberately: the count check makes it a no-op today, and that spec needs the docker memory stack so it cannot be run here to verify a behavioural edit. |
||
|
|
0c8d8db275 |
feat(reskinnable-demo): mount the frame, free the skins, retire the floating selector
Composes ShellFrame in the per-skin layout, passing the skin's Layout as its app slot and the shared ChatPanel as its chat slot, replacing the sibling Layout + ChatPanel + FloatingSelector trio. Each skin's chrome drops its viewport height for h-full: it now fills the app card, which the frame has already inset by its own padding, so a viewport-height root overflowed the card by exactly that much. Logistics keeps overflow-hidden -- its nav stays pinned only while the container is bounded, which is now the card. Banking keeps its second, unrelated canvas-clearing effect and therefore its useEffect import. The four --nw-nav-inset-* publishing effects go with the floating selector they fed. Each skin published the width of its own edge-nav so a floating pill could compute a safe band and dodge it; the selector now occupies a slot in the assistant column and overlaps nothing, so the whole mechanism is unnecessary and its component is deleted. This is one commit because the halves are not independently correct: without the skin edits the cards overflow, and deleting the selector before its import is removed would not build. |
||
|
|
58efc192e4 |
feat(reskinnable-demo): render the chat inline inside a resizable card
Replaces CopilotSidebar with an inline CopilotChat so the cluster can live in a panel. CopilotSidebar was a fixed <aside> that pushed document.body's margin and faked two columns by being handed the width of both, insetting its own contents past the rail while the rail painted into the freed strip as a separately-fixed sibling. That deletes an entire geometry contract: --nw-chat-width, --nw-rail-offset and data-nw-chat-open existed only to describe a fixed panel's footprint to the rest of the page. Also gone: the force-open-on-mount ref dance, the header slot cast (CopilotChat has no header slot -- that is a modal concern, so the header is now an ordinary sibling), and the rail's own fixed positioning and translate animation. The thread rail is a FIXED-WIDTH element, not a panel. v4's collapse API fought that three ways: a collapsed 0 written to storage was restored forever, expand() restores the "most recent size" which after collapsing to 0 is 0, and resize() is ignored while collapsed. Conditionally rendering it makes isInboxOpen the single source of truth with no imperative sync. The header reads the SKIN for its title, not the chat configuration. As CopilotSidebar's header slot it rendered inside the chat's own provider so our labels reached it; as a sibling it reads the wrapper's, whose default "CopilotKit Chat" is non-null and won the ?? chain -- every skin's header showed that instead of its assistant name. The header is skin chrome now, and holds only conversation actions. |
||
|
|
06f973823e |
feat(reskinnable-demo): add the inset shell frame
src/shell/layout/ owns the app's outer geometry: a padded region holding the assistant column (selector card above chat card) and the skin's app card, separated by a resizable gutter. The model is deliberately just "one bounded panel, one that takes the remainder" -- assistant min 250px / default 600px / max 50%, app gets what is left. An earlier version nested the thread rail as a resizable panel INSIDE the assistant column, which made its floor a compound of rail + conversation and forced a derived breakpoint, a switching collapsed floor and an app floor to compensate. Capping the assistant as a SHARE rather than a pixel count is what removes the need for an app floor and lets the mobile breakpoint stay a genuine 768px instead of being derived from panel arithmetic. - selector-card: the skin switcher as a dropdown, so its footprint stays flat as skins are added, plus the shell controls -- swap sides and hide. All three are shell concerns, which is why they are here and not in the chat header. useSkinThemeReconcile stays on the card root: it reads the computed --nw-dark-capable from inside the skin's theme root, and losing it would let a light-only skin render dark chat chrome. - layout-preferences: side and open state, shell-global so switching skins never rearranges the workspace. Read through useSyncExternalStore because this repo treats react-hooks/set-state-in-effect as an error, with write-through persistence so only a deliberate choice is stored. The hook returns inert defaults outside its provider rather than throwing. - use-is-desktop: matchMedia through the same external-store pattern. - panel-sizes: the three numbers, in one place. |
||
|
|
a36ab28019 |
refactor(reskinnable-demo): re-scope the chat CSS and set the base type scale
Three changes to the shell stylesheet. Re-scopes chat typography from the SDK's [data-copilot-sidebar] attribute to a .nw-chat wrapper. That attribute came from CopilotSidebar, which the inset frame replaces with an inline chat, so ~23 rule blocks styling assistant markdown would have silently stopped matching -- a lint-clean, test-clean build with subtly wrong chat text. The migration ran as a bracket: duplicate onto .nw-chat, land the structural change, then delete the originals, with a guard test asserting first the parity and then the absence. Typography is provably unchanged -- a probe mounting markdown-shaped content with the library's prose class reported 140 identical computed values across 14 selectors before and after. Adds the frame's own rules: .nw-panel-card (a FIXED 12px radius, deliberately not reading --radius, because the frame is shell chrome and must read identically in every skin) and the chat cluster's fixed-width rail with the container query that hides it when the card is dragged narrow. Sets the root font size to 15px. The root, not body: every rem in the shell and all four skins resolves against it, so this is the one lever that rescales type everywhere without per-skin edits. Spacing scales with it by design; anything that must not move is written in px. The chat prose moves 0.9375rem -> 1rem, since that value existed to hit 15px under a 16px root and would otherwise sit below the new default. Deletes the ~120-line floating-selector dock and the unused .brand-text-gradient. |
||
|
|
6c7e7b7df4 |
feat(reskinnable-demo): add the react-resizable-panels v4 wrapper
Pins ^4.12.2 and wraps its Group/Panel/Separator API behind two styled handles: an 8px gutter for the gap between cards and a 1px hairline. 4.x rather than the 2.x/3.x used by six sibling examples because only 4.x accepts PIXEL size constraints, and the layout's bounds are pixels. Three v4 behaviours the consumers depend on, each with a test: - The API is RENAMED from 2.x/3.x: PanelGroup->Group, direction->orientation, PanelResizeHandle->Separator, autoSaveId->useDefaultLayout. Most material online, including shadcn's Resizable block, targets the old names and will not compile. - data-testid is DERIVED from a Panel's id and overwrites any passed in, so a panel's id is its query handle. Getting this wrong makes every [data-testid$='-panel'] query silently match nothing. - Separator refuses flex-grow/flex-shrink overrides, so shrink-0 would be dead. Adds a jsdom ResizeObserver stub, without which every test rendering a Group throws. Note for anyone adding a dependency here: declare it in package.json and run 'pnpm install --lockfile-only' from the REPO ROOT. This example pins pnpm 10.10.0 while the root pins 10.33.4, so 'pnpm add' from this directory makes corepack switch versions and the older pnpm re-resolves the entire workspace lockfile. |
||
|
|
ced993447f |
fix(showcase): copy patches/ into harness build context
Root package.json pins patchedDependencies (eventsource@3.0.7 -> patches/eventsource@3.0.7.patch, added in #6334). The harness Dockerfile selectively copies root manifests but omitted patches/, so the frozen install hashes the patch file, ENOENTs, and exits 254. Broke the showcase-harness build-check for any PR rebased onto post-#6334 main. Copy patches/ before the install. Verified: image builds clean locally. |
||
|
|
aa15946c2a |
fix(mastra-showcase): ground beautiful-chat A2UI dynamic render from request context
The dynamic `generate_a2ui` tool grounded its inner `render_a2ui` subagent from
the tool's `contextEntries` arg, which the outer model always sends empty
(captured live: `{"messages":[…],"contextEntries":[]}`). On a live LLM the inner
render then ran with an EMPTY system prompt: ungrounded, it emitted
invalid/misnamed components (or none), so the surface never resolved against the
catalog. Result: the Beautiful Chat A2UI dynamic surface (Sales Dashboard,
flights) rendered no UI, with a render error that varied run to run. aimock hid
it: the recorded fixture returns a valid envelope regardless of the empty
context.
Read the catalog schema + A2UI generation guidelines the `@ag-ui/mastra` bridge
already forwards onto Mastra's request context
(`requestContext.get("ag-ui").context`) and ground the render there instead of
trusting the model-supplied arg. Mirrors `readAgUiContext` in `@ag-ui/mastra`'s
`getA2UITools`. Preserves per-demo catalogId (the grounded model emits it, and
`buildA2uiOperationsFromToolCall` uses `args.catalogId`), so the same shared
weatherAgent serves both beautiful-chat and declarative-gen-ui. Falls back to
the arg when no request context is present.
|
||
|
|
528ebfa351 |
fix(showcase): un-quarantine MAF Python multimodal (parity with langgraph)
multimodal was wrongly quarantined based on a local-only failure: the sample.png/sample.pdf demo assets are Git LFS pointers, and without git-lfs on PATH the attachment send fails before the run starts (runStartCount=0). langgraph-python multimodal fails locally for the exact same reason, yet declares the feature supported. Verified the MAF agent actually works: with the real assets in place the D6 cell is green (2 turns, assertions passed). Both production deploys serve the real 10KB PNG (LFS pulled), so multimodal is green in prod for MAF just like langgraph. not_supported_features now matches langgraph exactly: [gen-ui-interrupt, interrupt-headless]. |
||
|
|
47c56c8f9c |
fix(showcase): MAF Python manifest + exact pins for clean validate
- Remove multimodal from features (already in not_supported_features);
the duplicate made generate-registry reject the manifest, blocking D6
from starting.
- Pin agent-framework-{ag-ui,openai,core} to exact published latest
(1.0.1 / 1.12.0 / 1.13.0) so validate-pins classifies them as exact
framework deps; drop unused langchain-openai/langchain-core.
- Ratchet validate-pins baseline down 31 -> 27 (the 4 non-exact framework
fails are now exact); ms-agent-python's only remaining FAIL is the
shared-frontend openai ^5.9.0, identical to every other integration.
|
||
|
|
a71768d3c4 |
chore(showcase): MAF Python deps floor to explicit agent-framework 1.0+ latest
Replace main's ugly beta floors (ag-ui 1.0.0b251117 / openai 1.0.0rc6) with clean 1.0+ ranges. Resolves to latest (ag-ui 1.0.1, core 1.13.0, openai 1.12.0); D6 36/36 green on that. |
||
|
|
02f079180d |
fix(showcase): MAF Python frontend-tools-async + gen-ui-open green; quarantine multimodal
- frontend-tools-async: drop the stray broad 'project planning' fixture entry that
substring-shadowed 'Find my notes about project planning' -> run-loop. Green.
- gen-ui-open + gen-ui-open-advanced: remove 6 gen-ui-open-owned strays from
gen-ui-tool-based.json ('3D axis visualization', 'Inline expression evaluator',
'render an open gen-ui element', 'continue the advanced gen-ui flow') that
collided (same context, different responses) with gen-ui-open.json. Green;
gen-ui-tool-based unaffected.
- multimodal: quarantine. On main + latest deps the browser gets 200 from the
runtime but never starts a run (runStartCount=0) — a shared multimodal
frontend/runtime issue (identical frontend to langgraph; agent works via direct
POST; my #5985 fix doesn't change it). Genuine gap, needs shared-layer work.
Full D6 now 36/36 green on official latest (ag-ui 1.0.1, core 1.13.0, openai 1.12.0).
|
||
|
|
b56bb182d2 |
feat(showcase): MAF Python shared-state + reasoning-chain green on main (latest deps)
- shared-state-streaming: replace main's stale 'counter' fixture (drifted from langgraph canonical) with the write_document document demo (6-entry poem/email/ quantum + chunkSize) + SharedStateStreamingFrameworkAgent seed subclass; frontend already matches langgraph. Un-quarantine + add to features. Green (3/3 turns). - tool-rendering-reasoning-chain: un-quarantine + add to features. Green on core 1.13.0 / openai 1.12.0 (latest) via aimock encrypted_content (CopilotKit/aimock#342, which fixes the Responses reasoning multi-tool regression) + the store:False agent. CI-green depends on aimock#342 releasing. - Drop reasoning-default-render / agentic-chat-reasoning from not_supported (no D6 probe featureType — not real cells). |
||
|
|
9de40f4e10 |
fix(showcase): MAF Python custom-catchall green on main base
Port the #5985 fix onto main: ported langgraph's full 18-entry custom-catchall fixture (main's 4-entry set left SF/flights/d20/chain pills leaking to the default-catchall fixture) + _ToolRenderingFrameworkAgent dropping the divergent end-of-run MESSAGES_SNAPSHOT so the narration (not the tool card) is the terminal bubble. Verified green via per-cell --direct. First cell of the #5985->main re-integration. |
||
|
|
c2cb1c9e60 |
fix(showcase-mastra): render interactive open-gen-ui on a live endpoint
Both open-gen-ui cells reused the shared weatherAgent
("You are a helpful assistant.", gpt-4o). On a live LLM that produced
static HTML with no JS wiring - buttons rendered but were dead. aimock
hid it: fixtures match on userMessage/hasToolResult and replay a
fully-wired UI regardless of the agent.
Gold langgraph-python uses dedicated agents whose system prompt mandates
a single interactive generateSandboxedUi call and reads the design-skill
+ sandbox-function descriptors from copilotkit context. On Mastra,
RunAgentInput.context is a read-channel (requestContext.get("ag-ui")),
not injected into the prompt, so that guidance never reached the model.
Add dedicated openGenUiAgent / openGenUiAdvancedAgent porting gold's
prompts, with dynamic instructions that fold the ag-ui context into the
system prompt, and point the ogui route at them.
Verified live (real OpenAI, aimock bypassed) - all three advanced pills
render interactive, host-wired UI with confirmed round-trips:
evaluateExpression 7*8=56, notifyHost, evaluateExpression 5+3*2=11.
aimock matcher keys are untouched, so replay is unaffected.
|
||
|
|
2f24038c0d |
chore(skills): sync in-memory runner reference skills and mirror
Updates the runtime agent-runner skill references to match the bounded in-memory runner: correct the InMemoryAgentRunner store as a process-global singleton, document its bounds and onConcurrentRun concurrency handling, note that dedup weakens past the run cap, fix rotted in-memory.ts citations onto stable symbols, and correct the multi-instance SqliteAgentRunner scaling guidance. The generated skills/ mirror is regenerated in lockstep so source and mirror stay in sync. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
c91376452a |
docs(runtime): document in-memory runner bounds, concurrency, and durability
Documents the now-bounded in-memory runner for users: the maxThreads / maxRunsPerThread / maxBytes limits and their defaults, the precise eviction model (LRU threads, per-thread run-cap, cross-thread byte ceiling enforced at run completion), and the onConcurrentRun throw/supersede option. Clarifies that the store is a process-global singleton shared by every runner, that dedup weakens past the run cap, and points at the first-party SqliteAgentRunner for durable or multi-instance deployments. Adds a troubleshooting entry for the in-memory eviction warning. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
8f56cc9457 |
test(runtime): cover bounded store, eviction, and run teardown isolation
Adds a dedicated bounded-thread-store suite and extends the in-memory runner suite to lock in the behaviors introduced with the bounded, teardown-isolated runner: - LRU thread eviction, per-thread run-cap FIFO trimming, and byte-ceiling eviction (including that a live/running or stop-requested thread is never evicted, and that a just-appended thread pushes OTHER threads out rather than self-evicting). - InMemoryLimits validation/normalization: invalid bounds clamp to defaults instead of crashing enforceRunCap, and the 0/Infinity disable sentinels are preserved. - Thread-level createdAt and message-snapshot decoupling survive run-cap eviction and interleaved empty-snapshot runs. - stop() guards and the supersede path: an aborted run finalizes as a clean RUN_FINISHED against its own captured intent, a superseded run cannot clobber its replacement's state or history, and an immediate abort-throw that emitted nothing creates no phantom historic run. Also restores the shared store's default limits after tests that reconfigure the process-global store so suites stay isolated, and updates a handle-run comment for the GLOBAL_STORE -> shared store rename. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
e93119ae20 |
fix(runtime): isolate a superseded or stopped in-memory run's teardown
A run's async teardown used to read the shared, mutable store.stopRequested to decide whether to finalize as a clean RUN_FINISHED or a synthetic RUN_ERROR. Under supersede (or a stop() immediately followed by a new run()), a later run resets that shared flag, so the earlier run's fire-and-forget finalization could be mislabelled — an intentionally stopped run finalized as an error, or vice versa — and could push history under, or clobber the state of, the newer run that now owns the thread. Fix by capturing a per-run RunFinalizeControl when the run starts. stop() and a superseding run() flip THAT run's captured control (not just the store flag), and the run's teardown reads its own captured intent, so a later run resetting store state can never change how an earlier run finalizes. The teardown itself is unified into a single finalizeRun helper shared by the success and error paths (they were near-identical and must stay symmetric) and made ownership-aware: - It only pushes history / resets shared store state when this run still owns the thread (store.currentRunId still equals this run's id), so a superseded run cannot corrupt the successor's history or state. - The error path additionally requires at least one real (pre-finalize) event, reviving a guard that had gone dead: an immediate throw that emitted nothing must not create a phantom historic run holding only the synthetic terminal. - On completion it releases the run's infinite ReplaySubject buffer via an identity guard (store.subject === nextSubject), reclaiming the duplicate buffer on the owning path while leaving a live successor's subject untouched. The concurrency branch now also triggers on store.stopRequested, not just isRunning: stop() flips isRunning off the instant it aborts but the run keeps finalizing, and a run() slipping through that window went entirely unhandled. The previous-subject bridge is removed: forwarding a dying superseded run's subject would replay its RUN_STARTED and push its terminal into the live run's stream, an invalid AG-UI sequence — a superseded run must stay isolated to its own subscribers. Co-Authored-By: Claude <noreply@anthropic.com> |