mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
feat/rn-streaming-tool-render
14474 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4a77c031c2 | Merge branch 'main' into feat/rn-streaming-tool-render | ||
|
|
7a474dabee |
fix(react-core): invalidate messages memo for activity object content (#6325)
## Summary - `CopilotChat` fingerprints messages for `useMemo` with `contentKey = 0` for any non-string / non-array content. - `ACTIVITY_SNAPSHOT` updates often keep the same `messageId` and only replace object `content` (progress / generative UI). That left activity renderers stuck on the first frame until some other list change forced a refresh. - Include `JSON.stringify(m.content)` for object content in the fingerprint. Multimodal attachments remain on the array-length branch, so this does not reintroduce base64 serialization for user uploads. - Add a regression test that replaces the same activity `messageId` and asserts the UI updates. ## Test plan - [x] `nx run @copilotkit/react-core:test` (or the package's activity e2e suite) - [x] Manual: stream repeated `ACTIVITY_SNAPSHOT` with the same `messageId` + `replace: true` and confirm custom activity renderers update each frame Made with [Cursor](https://cursor.com) |
||
|
|
eba55e7e43 | Merge branch 'main' into feat/rn-streaming-tool-render | ||
|
|
35502a5d07 | Merge branch 'main' into fix/react-core-activity-contentkey-memo | ||
|
|
2328062960 |
docs(channels): clarify the custom channel runner path (#6437)
Small docs clarification for the Channels SDK. - **Channels overview** (`/channels`): adds a short note under the architecture diagram that you can build your own channel runner on the open-source SDK, with no CopilotKit Intelligence dependency — a supported path where the team owns state, persistence, concurrency, locking, retries, and race-condition handling. Intelligence remains the managed runner; analytics, learning, and governance come in addition. - **Self-hosting section**: Enterprise Intelligence can be fully self-hosted today; onboarding guides are still to come, and we're happy to help in the meantime. - **Package READMEs** (`channels`, `channels-core`, and the five adapters): removes the "there is no standalone / DIY runner" phrasing, which contradicted the above, in favor of the same managed-vs-custom framing. Intentionally does **not** add any implementation guidance for custom runners (no state-store interface details, no persistence examples). Follow-up (not in this PR): swap in the updated architecture diagram assets (light + dark) that show the "Build Your Own Channel Runner" box alongside Enterprise Intelligence. Refs [FAC-155](https://linear.app/copilotkit/issue/FAC-155/channels-docs-lack-a-clear-supported-path-without-intelligence) 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
47a4a84896 |
docs(channels): re-export architecture diagram at 4000px
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e705704889 |
docs(channels): update architecture diagram to the runner-emphasis version
New export from Figma: two runner paths (managed Intelligence runner or build your own), durable-data emphasis, Any Agent framework list, and channel platforms with the +4 more row. Used for both themes until a dark export exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e06b762b3e |
docs(channels): explain the durable-data dividing line
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
af78563beb |
docs(channels): self-hosting note covers the Channels SDK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
271614573d |
docs(channels): drop the channels-core pointer from adapter READMEs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6e0e1de049 |
docs(channels): state lifecycle positively across READMEs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
049bd2bdcc |
docs(channels-core): drop the phantom-negation phrasing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a0007c21fe |
docs(channels): polish the runner note and self-hosting copy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
adbdf65263 |
docs(channels): state the free plan plainly
Drop the defensive parentheticals around Intelligence pricing; say "available on a free plan" and move on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cb4439b241 |
docs(channels): clarify the custom channel runner path
Note in the Channels overview and package READMEs that building your own channel runner on the open-source SDK primitives is a supported path with no CopilotKit Intelligence dependency; teams choosing it own their state, persistence, concurrency, locking, retries, and race-condition handling. Intelligence remains the managed runner, with analytics, learning, and governance in addition. Also updates the production self-hosting note: Enterprise Intelligence can be fully self-hosted today, onboarding guides are still to come. Refs FAC-155 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b32b5539cc |
feat: add Inspector navigation, usage, and locked Threads (refs ENT-1173) (#6275)
## What does this PR do? Adds the CopilotKit consumer side of ENT-1173 across Shared, Runtime, Core, Web Inspector, and the existing Shell Docs pages. - Defines and parses optional trusted Inspector metadata for identity, plan, license, action, usage, and expiry. Runtime proxies it through a private, failure-isolated route, and Core refreshes it without changing connection state. - Groups Inspector navigation into Threads, Agents, and Learning. Threads renders finite, unlimited, unknown, overage, and expiring usage states plus matching trusted plan or license actions. - Keeps explicit `threadEndpoints` as the only authority for Thread requests. Locked or absent capability states make no list, subscription, detail, message, event, or state calls. - Keeps the zero-thread video, three example Threads, detail tabs, and guided tour in empty and locked states. General Intelligence remains the default onboarding path; only trusted `team_self_hosted` metadata uses self-hosted onboarding. - Gives an active license with missing Runtime routes a short **Finish setting up Rich Threads** state. Users can copy a safe coding-agent prompt or open the public Runtime setup guide. The same copy control appears in that guide, and raw Markdown/LLM views include the full prompt. - Keeps finite usage green below 90%, orange from 90% to the limit, and red at or above the limit. At 90%, a trusted plan action changes from **Manage Your Plan** to a purple **Upgrade Your Plan** without changing its trusted URL, action kind, or telemetry contract. - Adds a deterministic 33-state loopback lab for CopilotKit developers. It has no production route or export, is absent from public docs and package metadata, and is excluded from the npm tarball. `Expiring Soon` is display-only; this PR does not enable the thread culler. Managed Enterprise receives no manage-plan action, and Team Self-Hosted receives no hosted plan action. Optional metadata and the additive expiry field remain compatible across mixed producer, Runtime, Core, and Inspector versions. A small Channels test-only change updates fetch mocks for current TypeScript types. It changes no Slack or Teams docs or runtime behavior. ## Related PRs and issues - Refs [ENT-1173](https://linear.app/copilotkit/issue/ENT-1173/ship-plg-ready-inspector-navigation-metadata-and-locked-threads) - Producer: [CopilotKit/Intelligence#696](https://github.com/CopilotKit/Intelligence/pull/696) ## Validation - `@copilotkit/web-inspector`: 20 files and 372 tests passed; typecheck and production build passed. - Shell Docs: 57 files and 383 tests passed; lint, typecheck, and production build passed. The build generated all 222 static pages. - Browser checks cover the copy-prompt flow, unchanged white **Manage Your Plan**, purple **Upgrade Your Plan**, orange 4,500/5,000 usage, and red 5,000/5,000 usage. - Independent review found no Critical or Important issues. - The broader Runtime, React Native, Channels, package-quality, compatibility, and Node-version checks from the prior pushed head remain green. ## Checklist - [x] I have read the [Contribution Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md) - [x] I updated the relevant documentation - [ ] "Allow edits by maintainers" is checked |
||
|
|
a60cc771e4 |
feat(reskinnable-demo): add the people skin (Rowan), a demo-complete People Ops desk (#6432)
|
||
|
|
db9b9205b0 | Merge branch 'main' into feat/reskinnable-demo-people-skin | ||
|
|
8d25b6bf07 |
fix(reskinnable-demo): resolve thread-list identity from the query-string agentId (#6431)
## The bug `agentIdFromUrl` in `src/app/api/copilotkit/[[...slug]]/route.ts` only read the target agent from the URL **path** (`/agent/:agentId/run`). Thread routes carry it in the **query string** instead (`/threads?agentId=<id>`), so every thread-list request looked agentId-less and fell through to `defaultSkinId`'s `identifyUser` — banking's. That split identity for every non-default skin: | path | resolves via | result | | --- | --- | --- | | `POST /agent/people/run` | `people`'s resolver ✅ | thread created under `rowan-demo-user` | | `GET /threads?agentId=people` | **banking's** resolver ❌ | asks for `northwind-demo-user`, gets `[]` | ## Why it was hard to see Nothing errors. The thread rail just reads *"No conversations yet"* forever and reopening a conversation after a reload is impossible — which reads to a viewer as "this product doesn't persist threads", the exact opposite of what the demo exists to prove. **Banking was immune only because it IS `defaultSkinId`.** ## The fix Fall back to `?agentId=` from the query string when the path carries no `/agent/<id>` segment. ## Verification Against a local Intelligence stack, thread counts from `GET /api/copilotkit/threads?agentId=<id>`: | skin | before | after | | --- | --- | --- | | people | 0 | 11 | | airline | 0 | 1 | | logistics | 0 | 3 | | banking | 7 | 7 (unchanged) | Confirmed the backend held those threads all along — they were being listed under the wrong end-user id. Skins with no `identifyUser` (airline) still fall through to `genericIdentity()` via the existing `if (!resolve)` guard, so this widens correct resolution without adding a failure mode. `pnpm build`, `pnpm lint`, `pnpm test:unit` (335/335) green. ## Skill-staleness check Per the app's standing rule: **checked, no skill impact.** This is shell-internal identity plumbing; `.claude/skills/reskin/` documents the `identifyUser` contract, which is unchanged — a skin still contributes the same resolver in the same place. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
37988e90b6 | Merge branch 'main' into fix/reskinnable-demo-thread-agent-id | ||
|
|
3d4de1cedd |
feat(reskinnable-demo): add the people skin (Rowan), a demo-complete People Ops desk
Rowan is a People Operations command center and the second skin built against
the full nine-beat bar in `.claude/skills/reskin/demo-beats.md` (banking was the
first). Pages: Roster (index), Compensation, Requests, Onboarding.
REST-backed like banking and logistics: `/api/people/v1/*` serves one `ledger`
snapshot read plus the write paths, a generated `offer-letter` PDF, and a
presenter-gated `dev/reset`. Components read the ledger through the skin's own
`usePeopleLedger()` context, so `useData` is omitted. That context is mounted in
`RuntimeProviders` rather than `Providers`, which lets the single fetch also feed
`useRuntimeProperties`.
The signature element is the band ladder: one rail per level, each normalized to
its OWN band, so "halfway up L3" and "halfway up L7" line up at the same height
and become comparable. Anyone outside their band is drawn outside the rail, in
the negative colour, always labelled.
Beats, all walked in a browser against a live Intelligence stack:
1 face showCompBands renders the ladder + a two-sentence answer
2 rich thread gen-UI replays intact on reopen after a hard reload
3a drive the app setBaseSalary — the figure is typed into a chat card and
goes straight to REST; it appears nowhere in the transcript
3b sees screen route readable + per-page on-screen readables on all four
pages; Roster and Requests give different, correct answers
3c levers HITL confirm naming the levers, then
?status=pending&sort=aging_desc&top=10 with the Status, Sort
and Show controls visibly tinted, "TOP 10 OF 11"
3d multimodal an offer-letter PDF rides the pill, and the filed packet
survives deleting the thread and reloading
4 memory seeded preference recalled AND named in the component's
`note` slot
5 stored skill one vague sentence fires three visible writes in order, no
confirmations, amid four distractor tools
6 teach a skill 422 OUT_OF_BAND (symptom only) -> decline -> record the
demonstration -> save -> apply unaided to a DIFFERENT person
in a fresh thread
Notes for reviewers:
- The beat-6 gate is deliberately discriminating. Decoy exception codes file and
finalize successfully and still do not lift it, and an unknown code is refused
without enumerating the catalogue — so "the agent filed an exception" is not
the same as "the agent cleared the gate". Two out-of-band comp requests are
seeded so the case taught on stage and the unaided replay are different people.
- Seed dates are relative offsets materialized at store init, not absolute ISO
strings, so request aging and the generated offer letter stay coherent years
from now and a Reset genuinely re-freshens the queue.
- Memories are seeded and saved at `user` scope, not `project`. Verified against
the running stack: a project-scoped row is returned for EVERY user id in the
instance, so with several skins sharing one backend it is not a per-skin
boundary. For the same reason this skin's `forgetAllMemories` skips
project-scoped rows rather than deleting data it does not own, and `dev/reset`
reports the skipped count.
- `temperature` is not set. gpt-5.4 rejects it and the value is discarded, so
carrying it alongside a comment claiming determinism would be misleading.
- Beat 2 additionally requires the thread-list identity fix sent separately; the
skin merges and runs fine without it, it just cannot demo thread reopen.
Docs updated for the fifth skin per the app's standing skill-staleness rule:
CLAUDE.md (skin list, substrate split, beat matrix), README.md,
docs/teach-mode/README.md (teach-mode is now per-skin, not banking-only), and
`.claude/skills/reskin/{SKILL,demo-beats,templates}.md` — including six
"only banking does this" claims that are no longer true.
Verified: pnpm build, pnpm lint, pnpm test:unit (335/335) on this base.
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
d7ab8ce2a8 |
fix(reskinnable-demo): resolve thread-list identity from the query-string agentId
`agentIdFromUrl` only read the target agent from the URL PATH (`/agent/:agentId/run`). Thread routes carry it in the QUERY STRING instead (`/threads?agentId=<id>`), so every thread-list request looked agentId-less and fell through to `defaultSkinId`'s `identifyUser` — banking's. The result was a split identity for every non-default skin: runs created threads under the skin's own end-user id (the run path resolves correctly), while the list asked for banking's id and got an empty array back. The thread rail read "No conversations yet" forever and reopening a conversation after a reload was impossible. Nothing errored, which is what made it hard to see — and it reads to a viewer as "this product doesn't persist threads", the opposite of what the demo exists to show. Banking was immune only because it IS `defaultSkinId`. Verified against a local Intelligence stack; thread counts returned by `GET /api/copilotkit/threads?agentId=<id>` before → after: people 0 → 11 airline 0 → 1 logistics 0 → 3 banking 7 → 7 (unchanged; it was already resolving correctly) Skins with no `identifyUser` (airline) still fall through to `genericIdentity()` via the existing guard, so this widens correct resolution without introducing a new failure mode. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
ea3e3fbfa6 |
fix(react-ui): let sidebar children fill the viewport height (#6410)
Fixes #261 (open since March 2024). Supersedes #4622 — @ashish4143 diagnosed the same wrappers and is credited as co-author on the commit. ## The bug `CopilotSidebar` wraps consumer content in two divs: - `.copilotKitSidebarContentWrapper` (`Sidebar.tsx`) — only sets `overflow`, `margin-right`, `transition` - `.copilotKitModalChildrenWrapper` (`Modal.tsx`) — **has no CSS rule anywhere in the repo** Both are auto-height blocks, so a child's `height: 100%` has no definite containing block to resolve against and collapses to content height. ## The fix An opt-in `fullHeightChildren` prop on `CopilotSidebar` that adds a modifier class to the content wrapper. Two deliberate choices, both from the review on #4622: - **Opt-in, not default.** The content wrapper wraps the *entire* consumer app. Making it a fixed-height flex column for everyone would reflow apps that never asked for it. - **A viewport unit, not `height: 100%`.** `100%` only resolves if every ancestor (`html`/`body`/`#root`) also declares a height — react-ui neither sets that nor can guarantee it, so `100%` would silently no-op in a stock Next.js app. `min-height: 0` on the children wrapper clears the flex-item `min-height: auto` floor so tall content scrolls inside the child rather than stretching the wrapper past the viewport. ```tsx <CopilotSidebar fullHeightChildren> <div style={{ height: "100%" }}>...</div> </CopilotSidebar> ``` ## Testing **Unit** — `packages/react-ui/src/css/sidebar-full-height.test.ts` (4 tests), in the repo's existing CSS-contract style. Guards both halves: the escape hatch's rules, and that the default wrapper stays auto-height. Also asserts the height is *not* `100%`, since that's the regression that would make the whole feature a silent no-op. ``` ✓ src/css/sidebar-full-height.test.ts (4 tests) Test Files 9 passed (9) Tests 58 passed (58) # full react-ui suite ``` `npx tsc --noEmit` → exit 0. `oxlint` on changed files → 0 warnings, 0 errors. **Live in Chrome** — the acceptance criterion from the #4622 review: a stock app where **nothing** declares a height on `html`/`body`/`#root`, loading the real built `dist/index.css` (not the source CSS), standards mode, 762px viewport. DOM per `Sidebar.tsx:92` + `Modal.tsx:143`. | case | child `height:100%` measures | |---|---| | default (no opt-in) | **17px** — collapsed, i.e. behavior unchanged for existing consumers | | `fullHeightChildren` | **762px** — exactly the viewport | | `fullHeightChildren`, content 3000px tall | **762px**, scrolls inside the child (`min-height: 0` holds) | Also confirmed on the opt-in path: `.copilotKitSidebar` stays `position: fixed`, and the expanded push-aside `margin-right` is still `448px` (28rem), so the sidebar's own layout is untouched. **Docs** — `CopilotSidebar.mdx` is auto-generated from `Sidebar.tsx`; regenerated via `scripts/docs/gen.ts` and committed only the new `fullHeightChildren` entry (the generator also surfaces unrelated pre-existing drift in other reference pages, left out of this PR). ## Not covered The issue mentions a "works in Safari, not Chrome" symptom. I verified in Chromium only — the mechanism above is spec behavior rather than a Chrome quirk, but I haven't measured WebKit. |
||
|
|
e010786d5c |
docs(langgraph): replace broken self-hosted auth snippets with a working pattern (#6403)
Fixes #5961 (OSS-609). ## The bug Both LangGraph auth pages told self-hosted readers to wrap their graph in `CopilotKitRemoteEndpoint`. That path is retired and fails two ways against the stack the reporter used (`copilotkit==0.1.94`, `ag-ui-langgraph==0.0.4x`, Python 3.12): ``` import LangGraphAgent -> ImportError: cannot import name 'LangGraphAgent' from 'copilotkit' execute_agent -> AgentExecutionException: Agent 'sample_agent' failed to execute: 'LangGraphAGUIAgent' object has no attribute 'execute' ``` (Reproduced locally against the `langgraph-fastapi` example's venv — output above is verbatim.) ## The fix `showcase/shell-docs/src/content/docs/auth.mdx` (Self-hosted tab of the `auth_pattern: langgraph` section) and `showcase/shell-docs/src/content/docs/integrations/langgraph/auth.mdx` now document the supported pattern: **serve the AG-UI endpoint yourself**, validate in a FastAPI dependency (401 before the graph runs), and bake the resolved user into a **per-request** `LangGraphAGUIAgent(config={"configurable": {"auth_user": user}})` so nodes read an already-verified identity off `RunnableConfig` — no raw token in the graph, no shared agent carrying another request's identity. The gate-only variant (`FastAPI(dependencies=[Depends(current_user)])` + `add_langgraph_fastapi_endpoint`) is documented for readers who only want unauthenticated traffic rejected. Two adjacent bugs on the same pages, fixed here because they break the same walkthrough: - **The frontend channel was wrong.** The pages said to pass `properties={{ authorization: userToken }}` and claimed it "is forwarded as a Bearer token". Nothing in `packages/` converts properties into headers — `properties` reach the agent as AG-UI `forwardedProps` (run payload data). The v2 runtime *does* forward the inbound `authorization` header (and custom `x-*`) onto the agent call, so `headers={{ Authorization: ... }}` is the channel that actually works, for both Platform and self-hosted. - **The Platform user key was wrong.** `config["configuration"]["langgraph_auth_user"]` → `config["configurable"]["langgraph_auth_user"]` (matches `langgraph/pregel/main.py` and `langgraph_api/worker.py`). ## Testing **1. Doc snippets extracted verbatim from the MDX and executed** (a script pulls the `main.py` + node code blocks out of each page, stubs only `validate_your_token`, and drives them with `TestClient`; run under the `examples/integrations/langgraph-fastapi` venv — `copilotkit 0.1.94`, `ag-ui-langgraph 0.0.41`, Python 3.12): ``` # docs/auth.mdx PASS no header -> 401 {"detail":"Missing bearer token"} PASS bad token -> 401 {"detail":"Invalid token"} PASS valid token -> 200 PASS no RUN_ERROR PASS node read user_123 off RunnableConfig PASS node read role 'member' PASS run completed MESSAGES_SNAPSHOT: [{"id": "None", "role": "assistant", "content": "hello user_123"}] ALL DOC-SNIPPET CHECKS PASSED # docs/integrations/langgraph/auth.mdx — same script, same 7 checks ALL DOC-SNIPPET CHECKS PASSED ``` **2. Gate-only variant** (`FastAPI(dependencies=[Depends(current_user)])` + stock `add_langgraph_fastapi_endpoint`): ``` no token -> 401 {"detail":"Missing bearer token"} valid token -> 200 True True PASS gate-only pattern (401 without token, run proceeds with token; identity NOT injected) ``` The trailing `True True` is `RUN_FINISHED` present **and** the node seeing `nobody` — i.e. the gate works but no identity lands on the config, exactly as the docs now say. **3. Header forwarding actually reaches a LangGraph deployment** — the claim behind the new `headers` guidance. Pointed a real `@ag-ui/langgraph` `LangGraphAgent` at a local stub server, set `agent.headers` the way `configureAgentForRequest` does, and recorded what arrived: ``` [ { "url": "/assistants/search", "auth": "Bearer end-user-token", "apiKey": "server-side-key" } ] PASS: authorization forwarded to the deployment ``` Both the end-user token and the server-side key arrive, which is why the "server-configured headers win on collision" note is accurate. Runtime-side breadth is already covered by `packages/runtime/src/v2/runtime/__tests__/agent-utils-header-forwarding.test.ts` ("authorization header IS forwarded"). **4. Docs render checks** — both pages compile as MDX (`@mdx-js/mdx` `compile()`), and every `python` block on both pages parses (`ast.parse`), including the ones I didn't touch. ## Follow-up **The DIY endpoint is deliberate but temporary.** `add_langgraph_fastapi_endpoint` exposes no per-request seam (no `dependencies` passthrough, no config/agent factory), and `endpoint.py` is byte-identical in 0.0.41 and 0.0.42 — so owning the route is currently the only way to get a verified identity onto `config["configurable"]`. Adding that seam upstream in `ag-ui-protocol/ag-ui` is tracked as **OSS-760**; when it lands, both pages collapse back to the helper form and the DIY route stays only as an escape hatch. The broader "document request-scoped auth for `add_langgraph_fastapi_endpoint`" ask in #3177 is now substantively answered by these pages; leaving that issue open pending a maintainer's call on whether it wants an SDK-level hook rather than the DIY endpoint. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
6f640f7eb1 |
fix(showcase/ms-agent-dotnet): surface shared-state-read-write chat replies (#6233)
## Summary `shared-state-read-write` pills showed **no chat responses** on staging. ### Cause #6227 wired deterministic replies for the suggestion pills, but those updates were emitted as: ```csharp new AgentRunResponseUpdate { Contents = [new TextContent(...)] } ``` without `Role = ChatRole.Assistant`. AG-UI's .NET adapter only turns assistant-role text into `TEXT_MESSAGE_*` events, so the frontend dropped every pill reply. Notes snapshots could still land; chat looked dead. ### Fix - Set `Role = ChatRole.Assistant` on deterministic text updates - Prefer `message.Text` when resolving the latest user message - Broaden pill matching for greet / weekend / remember-something copy ## Test plan - [x] `dotnet build` ms-agent-dotnet agent - [ ] Staging after deploy: Greet / Remember something / Plan a weekend all show assistant text; Remember something updates the notes panel |
||
|
|
d5d2e73a53 |
fix(showcase/ms-agent-dotnet): ground declarative-gen-ui charts in sales data (#6232)
## Summary `declarative-gen-ui` on staging painted surfaces but charts showed **No data available** and tables were empty. ### Cause With `injectA2UITool: false`, the secondary design LLM does **not** receive frontend App Context (`useSalesAnalystContext` / sales-context.ts). It only got a thin design prompt, so it omitted or emptied `PieChart`/`BarChart` `data` arrays and `DataTable` rows. ### Fix - Embed the Vantage Threads Q2 dataset + composition rules into `DeclarativeGenUiDesignSystemPrompt` - Add concrete non-empty PieChart / BarChart / DataTable examples - Coerce string chart values to numbers - Tighten outer agent: one short sentence, no prose dashboards ## Test plan - [x] GenerateA2ui unit tests 12/12 - [ ] Staging after deploy: all four declarative-gen-ui pills show populated charts/tables from the Q2 dataset |
||
|
|
6181fd24c5 |
feat(reskinnable-demo): LOCK_SKIN serves one skin at the root (#6405)
`LOCK_SKIN=<skin id>` turns the four-skin demo shell into a
**single-tenant product deploy**. Unset — the default — behaviour is
byte-identical to before.
```
LOCK_SKIN=logistics # the skin is SERVED AT /, and the /logistics prefix
# leaves the URL space: /, /lanes, /inventory
# /logistics itself -> 404, as do banking|airline|keel
# switcher -> static badge; tab reads "Meridian"
LOCK_SKIN= # unset: all four reachable under /<id>, switcher present
LOCK_SKIN=bankng # throws at boot, naming the typo and listing valid ids
```
The point is what the deploy *admits to being*. The selector card used
to announce "this is a reskinnable demo with four tenants" — and so did
every URL. A locked deploy says "this is Meridian": in the routing, the
chrome, the page metadata, **and the address bar**.
## Design notes for review
- **Served at `/`, not redirected to `/logistics`.** A redirect still
puts the substrate's tenant id in front of a customer, on the front door
and on every link after it. `src/proxy.ts` REWRITES the prefix-free
space onto the `/[skin]` route tree instead, so the segment never
appears.
- **`proxy.ts`, not a `next.config` rewrite.** `rewrites()` is
serialised into `routes-manifest.json` at BUILD time, which would bake
the lock into the artifact. Proxy files (Next 16's rename of
`middleware.ts`) always run on the Node.js server, so `LOCK_SKIN` stays
a per-request read and ONE BUILD SERVES BOTH HOSTS.
- **The SSE stream is safe by construction.** The matcher excludes `api`
at a segment boundary, so `/api/copilotkit` never enters the proxy.
`proxy.test.ts` asserts it directly, plus a live-server check in the
locked e2e.
- **Links are the other half of the contract.** `useSkinHref`
(`src/shell/skin-path.ts`) makes every in-skin href prefix-free under a
lock. The rewrite alone is useless: a hardcoded `` `/${skin.id}/cards`
`` still RESOLVES, it just puts the prefix back in the address bar on
the first nav click.
- **`useSkinHref` drops the prefix for the LOCKED skin, not for any
lock** (`locked === skinId`). Otherwise `useSkinHref("airline")` under
`LOCK_SKIN=banking` would silently ignore the id it was handed and
return a banking URL.
- **`params` is untouched, which is why this is a rewrite.** The rewrite
target keeps the `[skin]` segment, so keel's `useParams<{ skin, rest }>`
pages needed no change. Collapsing `[skin]/[[...rest]]` into a root
catch-all — the obvious alternative — would have broken them.
- **`useSkinSegments` replaces three copies of
`pathname.split("/").slice(2)`.** It strips a LEADING skin id rather
than slicing a fixed offset, so it is correct whether or not the
pathname carries the prefix, and does not depend on resolving whether
`usePathname()` reports the browser URL or the matched route under a
rewrite.
- **The URL contract is enforced by an ESLint AST rule**, not by
scanning source as text. See "What the review changed" below — this
replaced a regex scanner that drifted out of true three rounds running.
- **Non-`NEXT_PUBLIC_` env**, read server-side and threaded to client
chrome through a small context, so one build serves every deployment
shape. **`force-dynamic` on both env-reading entry points** — reading
`process.env` is not a dynamic API, so `/` would otherwise be
prerendered with the build-time skin baked in. **SSR metadata** via
`generateMetadata`, because a client effect cannot brand what crawlers
and unfurlers read.
- **A disabled dropdown was rejected** for the locked state — it implies
a choice that doesn't exist. The switcher's own
`router.push(\`/${skin.id}\`)` deliberately KEEPS the prefix: it renders
only when unlocked, and switching skins is the one case where the
segment is meaningful.
## What the review changed
A 5-round review loop (7+ unbiased agents per round, ~60 agent-reviews
total) found and fixed the following. Round 1 found four production
defects; rounds 2–5 found **zero** — every later finding was in the
review's own scaffolding or in docs the fix cycles themselves wrote.
**Production defects (all round 1):**
| Defect | Impact |
|---|---|
| `` `${base}/charges` `` and `` `${base}${page}` `` in
`banking/tools.tsx` | `skinHref()` returns `/` under a lock, so these
emitted `//charges` — a **protocol-relative URL that navigates
off-site** to `https://charges/`. Both were `router.push` calls,
invisible to any rendered-href check. Now routed through a pure,
unit-tested `nav-target.ts`. |
| Proxy matcher excluded `_next/static` + `_next/image`, not `_next` |
`/_next/webpack-hmr` and the error-overlay endpoint WERE rewritten under
a lock, breaking HMR and the overlay in `next dev` — which
`next.config.mjs:22` states is how this demo is presented. |
| `api`/`_next` matched by prefix, not segment | A future `/apiary` or
`/api-keys` route would silently skip the rewrite and 404 only on locked
deploys. |
| The locked e2e's headline guard passed vacuously |
`expect(hrefs).toEqual([])` succeeds when zero links render. Now has a
positive precondition plus a `//` assertion. |
**Scaffolding and docs, rounds 2–5:** the drift guard was rewritten from
a regex text-scanner to an ESLint AST rule after it produced a mandatory
finding three rounds running (it caught 1 of 5 spellings, missed the bug
that actually shipped, over-claimed its coverage, and was evadable via a
`$` in a variable name); the AST rule was then narrowed twice, first to
navigation contexts and then to navigation *objects*, after it
false-positived on dates (`` `${m}/${d}` ``), `String.replace`,
`Object.assign` and `Array.push`. Docs fixes: a false "defence in depth"
claim on `/`, the README's skin count, keel's brand name, and the reskin
skill's file list.
**Deliberately not fixed here** — 13 real findings that fail all three
subject-scope tests, routed to follow-up PRs under four subject handles:
Intelligence dev-env credential/org consistency (`.env.example` key
contradicts `docker-compose.yml`, silently emptying memory scope), HITL
replay-safety across banking and keel (including an approval card that
offers a non-approver no escape, hanging the interrupt), reskin template
correctness (a stray space in the `theme.css` selector yields invalid
CSS), and assorted pre-existing nits. Full list in the review ledger.
## Deliberately out of scope
- **Does not pin dark/light** — separate axis (theme toggle + per-skin
`--nw-dark-capable`).
- **Does not hide the inspector.** Locked-`banking`-with-inspector is
the intended FDE configuration.
- **Not a security boundary.** All four agents stay registered
server-side, so another skin's agent endpoint remains reachable under a
lock. `.env.example` says so explicitly.
## Verification
`pnpm lint` clean · `pnpm test:unit` 54 files / 335 tests · `pnpm build`
clean (the type-check gate) · zero static routes, `ƒ Proxy (Middleware)`
registered · locked e2e 12/12.
**One build artifact, served three ways, driven in a REAL BROWSER.** Raw
SSR HTML cannot substitute: the skin tree is entirely client-rendered,
so the server response contains no nav links at all. The hrefs — the
thing most likely to be wrong — only exist after hydration.
| Same build served… | `<title>` | `/` renders | nav hrefs | other
routes |
|---|---|---|---|---|
| `LOCK_SKIN=banking` | `Northwind Finance` | cards view **at `/`** |
`/`, `/dashboard`, `/charges`, `/team` | `/banking`, `/airline`, `/nope`
→ 404 page |
| `LOCK_SKIN=keel` | `Keel` | Desk **at `/`** | zero `/keel`-prefixed
hrefs in the DOM | `/knowledge/phi-access-policy` → doc reader, all 6
sections |
| unlocked | `CopilotKit Reskinnable Demo` | 307 → `/banking` |
`/banking`, `/banking/dashboard`, … | all four 200; switcher present |
Clicking a nav entry under a lock keeps the URL prefix-free with
`aria-current` on the correct entry. `GET /api/copilotkit/info` returns
200 under both locks and `public/sample-invoice-q2.pdf` still serves.
Airline and logistics were browser-verified locked as well.
**The e2e suite now has two projects**, because the lock is a boot-time
server env and the two deploy shapes are therefore two processes:
`unlocked` (port 3000) runs 17 tests, `locked` (port 3100,
`LOCK_SKIN=banking`, its own `.next-locked` dist dir) runs 12. Target
one with `--project=locked`.
## Known, pre-existing
**An unknown path under a lock renders the 404 PAGE but returns HTTP
200.** Not caused by the rewrite — on the unlocked build `/banking/nope`
is already 200, because `notFound()` raised from a client PAGE component
cannot change a status Next has already committed, whereas `notFound()`
from a layout can. The lock only changes which of those two paths an
unknown URL takes.
**The lint rule is sound, not complete** — deliberately, and documented
in the config. A URL assembled into a variable before `router.push(u)`,
or built with string concatenation, is not caught. Completeness would
require flagging shapes indistinguishable from legitimate code, and a
guard that misfires gets disabled.
**`next-env.d.ts` churns on e2e runs.** Next rewrites it to reference
whichever dist dir booted last, so a full run leaves it pointing at
`.next-locked`. Discard that hunk before committing; any build restores
it. Documented at the env block in `playwright.config.ts`.
`e2e/memory-learning.spec.ts` fails in this environment and reproduces
identically at base `77d99f6` — it exercises license-gated durable
memory and needs the Docker Intelligence stack. Not this branch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
dcddb18e78 |
chore(deps): update reviewdog/action-actionlint action to v1.73.1 (#6429)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [reviewdog/action-actionlint](https://redirect.github.com/reviewdog/action-actionlint) | action | patch | `v1.73.0` → `v1.73.1` | --- ### Release Notes <details> <summary>reviewdog/action-actionlint (reviewdog/action-actionlint)</summary> ### [`v1.73.1`](https://redirect.github.com/reviewdog/action-actionlint/compare/v1.73.0...v1.73.1) [Compare Source](https://redirect.github.com/reviewdog/action-actionlint/compare/v1.73.0...v1.73.1) </details> --- ### Configuration 📅 **Schedule**: (in timezone America/Los_Angeles) - Branch creation - "before 9am every weekday" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/CopilotKit/CopilotKit). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMi4wIiwidXBkYXRlZEluVmVyIjoiNDQuMTIuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> |
||
|
|
e79376b11b | Merge branch 'main' into feat/reskinnable-demo-lock-skin | ||
|
|
f4c959e3fe | chore(deps): update reviewdog/action-actionlint action to v1.73.1 | ||
|
|
d6f285baec |
docs(reskinnable-demo): require a skill-staleness check on every code change
The reskin skill is the only instruction a new skin's author reads, and it goes
stale SILENTLY: nothing type-checks it, no test imports it, and a skin built from
a stale template still compiles, lints and renders. There is no mechanism that
notices — only a person who thought to look.
This adds one standing question to every change to existing code: does it make
anything in `.claude/skills/reskin/` wrong, incomplete or misleading? Answered in
the PR body or commit message; "checked, no skill impact" is a fine answer. The
unanswered question is the failure, not a considered no.
Grounded in three real misses from the LOCK_SKIN root-serving change in this same
PR, all caught late and none by tooling:
- templates.md handed every new skin the two patterns that change had just removed
(a hardcoded `/${skin.id}/…` href, a fixed `pathname.split("/").slice(2)`). Both
fail silently under a lock — the page renders, the URL is just wrong.
- SKILL.md's verification steps pointed at `pnpm test:unit` and a drift test the
same PR deleted. Caught by a reviewer, not by a gate.
- The skill's authoring half was updated and its verification half was not; the gap
survived until it was asked about directly.
Includes a trigger table (contract change, required/forbidden call, a gate a skin
must pass, registration/routing/boundary, beat mechanism, brand or id, deleted or
renamed referenced file) so it is a lookup rather than a judgement call, and a
~2-minute grep check.
Skill-staleness check for THIS change: no impact. It is a process rule for people
editing the app, not guidance for people authoring a skin; no contract, gate,
command or path the skill references is altered.
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
66fef88b24 |
chore: release monorepo v1.66.4 (#6426)
## Release monorepo v1.66.4 **Scope:** `monorepo` | **Bump:** `patch` --- ### How this release process works 1. **This PR was created automatically** by the "release / create-pr" workflow. It bumped the `monorepo` packages to `1.66.4` 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 `monorepo` packages to npm at version `1.66.4` - Creates git tag `monorepo/v1.66.4` - 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.v1.66.4 |
||
|
|
b40602e698 | chore: release monorepo v1.66.4 | ||
|
|
5ef81c1d5b |
chore: release monorepo v1.66.3 (#6424)
## Release monorepo v1.66.3 **Scope:** `monorepo` | **Bump:** `patch` --- ### How this release process works 1. **This PR was created automatically** by the "release / create-pr" workflow. It bumped the `monorepo` packages to `1.66.3` 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 `monorepo` packages to npm at version `1.66.3` - Creates git tag `monorepo/v1.66.3` - 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.v1.66.3 |
||
|
|
cfc5cfe727 | chore: release monorepo v1.66.3 | ||
|
|
6c6ec28da6 |
docs(pydantic-ai): remove duplicate quickstart, fix dead links and commands (#6421)
Mechanical documentation repairs for the Pydantic AI integration, found while auditing its docs. **No content rewrites** — every change here is a dead link, a wrong command, or a duplicate file, and each was verified against the tree. ## Changes | Fix | Evidence | |---|---| | Delete `shell-docs/.../pydantic-ai/quickstart/` (`pydantic-ai.mdx` + `meta.json`) | `seo-redirects.ts` rule **F6** already routes `/pydantic-ai/quickstart/pydantic-ai` → `/pydantic-ai/quickstart`; adk has the identical **F7** rule and no such directory. pydantic-ai was the **only** framework of 17 still carrying a `quickstart/` subdir alongside the canonical `quickstart.mdx`. | | `human-in-the-loop/agent.mdx` — quickstart link | Pointed at the redirected legacy path; now points at `/pydantic-ai/quickstart` directly. | | `human-in-the-loop/agent.mdx` — starter link | `examples/coagents-starter-pydantic-ai` does not exist. Now `examples/integrations/pydantic-ai`. | | `docs-links.json` — `subagents.shell_docs_path` | Was `/multi-agent/subagents`; there is no `multi-agent/` directory. Real page is `/multi-agent-flows`, which the entry's own `og_docs_url` already pointed at. | | `headless-simple/chat.tsx` — console tag | Said `[langgraph-python:headless-simple]` inside the pydantic-ai package. This sits inside an `@region` block, so it is pulled into the docs as a snippet. | | `pydantic-ai-todos/README.md` — troubleshooting command | `uv run src/main.py`; that file does not exist in this tree (entrypoint is `agent/main.py`, which `scripts/run-agent.sh` gets right). | | `pydantic-ai-todos/README.md` — Python floor | Said 3.12+; `agent/pyproject.toml` declares `requires-python = ">=3.13"`. A 3.12 user hits a `uv sync` resolver error. | | `canvas/pydantic-ai/README.md` — prerequisites | Said Python 3.8+, but `agent/agent.py:100` uses a PEP 604 union (`str \| None`), which requires 3.10+ at runtime. Aligned to the sibling tree pinning the same `pydantic-ai-slim==2.22.0`. Node floor aligned to the two sibling READMEs. | ## Deliberately not included - **The `human-in-the-loop.mdx` / `human-in-the-loop/index.mdx` route collision.** Both resolve to `/pydantic-ai/human-in-the-loop`, and pydantic-ai is the only framework with both. Resolving it means choosing which page survives — the flat file has the correct `pydantic-ai` demo embed, the directory matches the house structure. That is a content decision, tracked in OSS-777 along with the related `meta.json` nav omission. - **11 other integrations carry the same `[langgraph-python:headless-simple]` console tag.** Left for the fleet sweep rather than fixed piecemeal here. - Two candidate findings were **dropped after verification**: `/pydantic-ai/generative-ui` is not a dead link (no framework has a `generative-ui/index.mdx` — it is the house pattern), and `uv run main.py` in `docs/setup/channels-agent-setup.mdx` is correct (the quickstart genuinely produces a `main.py` in a uv project). ## Related - OSS-777 — the remaining pydantic-ai documentation drift (PARITY_NOTES rewrite, `qa/*.md` sweep, per-demo READMEs teaching LangGraph APIs) - #6379 — carries the v2-specific doc corrections - #6381 — the D6 probe failures with the same root cause ## Verification Static: `docs-links.json` re-parsed and its new target confirmed to exist; deleted paths confirmed unreferenced except by the F6 redirect that supersedes them; every replacement path confirmed present on disk. No Docker in the audit environment, so the docs site was not built — worth a preview check on the nav after the `quickstart/` deletion. |
||
|
|
0c10d8c882 |
docs(pydantic-ai): remove duplicate quickstart, fix dead links and commands
Mechanical repairs found while auditing the pydantic-ai docs. Each was verified against the tree; nothing here is a content rewrite. - Delete `quickstart/pydantic-ai.mdx` + its `meta.json`. `seo-redirects.ts` already routes `/pydantic-ai/quickstart/pydantic-ai` -> `/pydantic-ai/quickstart` (rule F6), and adk got the same treatment (F7). pydantic-ai was the only framework still carrying a `quickstart/` subdirectory alongside the canonical `quickstart.mdx`. - `human-in-the-loop/agent.mdx`: link to the canonical quickstart directly instead of the redirected legacy path, and point the starter link at `examples/integrations/pydantic-ai` — `examples/coagents-starter-pydantic-ai` does not exist. - `docs-links.json`: `subagents.shell_docs_path` was `/multi-agent/subagents`, which has no page. The real page is `/multi-agent-flows`, which the entry's own `og_docs_url` already pointed at. - `headless-simple/chat.tsx`: the console tag said `langgraph-python` inside the pydantic-ai package. This sits in an `@region` block, so it is pulled into docs as a snippet. 11 other integrations carry the same copy-paste; they are left for the fleet sweep. - `examples/showcases/pydantic-ai-todos/README.md`: `uv run src/main.py` -> `uv run main.py` (there is no `src/main.py` in that tree), and the stated Python floor now matches `agent/pyproject.toml` (`>=3.13`). - `examples/canvas/pydantic-ai/README.md`: Python 3.8+ was unrunnable — `agent/agent.py` uses PEP 604 unions. Aligned to the sibling tree that pins the same `pydantic-ai-slim==2.22.0`. |
||
|
|
43df6e5afd | Merge remote-tracking branch 'origin/main' into feat/reskinnable-demo-lock-skin | ||
|
|
964f7c784c |
docs(reskinnable-demo): name demo-beats.md in the README's reskin-skill pointer
The README described the reskin skill as "(SKILL.md + templates.md)". The skill
has THREE canonical files — demo-beats.md is the read-first one, and both
SKILL.md and CLAUDE.md say so ("Write the beat map before you write code"). A
reader following the README alone never learns it exists, and a skin authored
without mapping its beats first has to be rebuilt, because the beats decide the
tools, pages and pills.
Surfaced by the post-convergence promotion audit, which proposed it as
PROMOTE_TO_A on the grounds that this PR introduced demo-beats.md and thereby
made the README claim newly wrong. That premise is FALSE and was refuted before
acting: demo-beats.md is absent from this PR's diff (only SKILL.md and
templates.md are modified) and already exists at the merge-base, and README:75-76
falls between this PR's hunks. The omission predates this branch.
Fixed anyway rather than escalated: the gap is real, the correction is one
sentence, and Procedure 3 sanctions "or fix it" as a resolution. Recorded as a
refuted-premise doc fix, NOT a promotion-driven reopen — the loop stays
converged.
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
8955701ff2 |
fix(reskinnable-demo): scope nav-target lint selectors to navigation objects
The NAV_TARGET_ANCESTORS selectors matched by method name only (.push/.replace/.assign on any object), so String.prototype.replace, Object.assign, and Array.prototype.push with slash-containing templates false-positived as broken in-skin navigation. Pin each call form to its object (router.push/replace, location.assign, window.location.assign); leave the JSX href and location.href assignment ancestors unchanged. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
0549bbcd61 |
fix(reskinnable-demo): scope the //-concat lint guard to navigation targets
The interpolationThenSlash selector fired on the bare AST shape "interpolation
then a quasi opening with /", which is identical to an ordinary date
`${month}/${day}` or ratio `${used}/${total} used`. Any future skin component
formatting a date or fraction would have been blocked with a link error that
makes no sense for that code (verified by probe).
Narrow the selector to fire only when the template is an actual navigation
target: router.push/replace, location.assign, location.href, or a JSX href
attribute (ESLint ancestry). Literal-prefix guards (literalSkinPrefix,
templateLeadingPrefix) are unchanged — they never false-positived and cover the
prefix shapes regardless of use site.
Residual limitation documented plainly in the config, SKILL.md, and CLAUDE.md: a
URL assembled into a variable first and then passed to router.push(u) is not
caught by an ancestry-scoped selector.
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
384f5f0c23 |
docs(reskinnable-demo): name keel's real brand in the skin lists
The README and CLAUDE.md skin bullets put "Harbor Point Health" in the brand slot for keel, but keel's brand is "Keel" (Harbor Point Health is the tagline's healthcare org). The three sibling bullets quote their real brands (Northwind Finance, Meridian, Aeronova); keel now matches, with Harbor Point Health kept as the org descriptor. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
9148fa2e54 |
fix(reskinnable-demo): enforce LOCK_SKIN URL contract via ESLint AST, retire the regex scanner
The URL-contract drift guard scanned skin source as raw text with regexes — a
re-implementation of a fragment of a JS parser that produced a mandatory review
finding three rounds running, each a different hole (missed spellings, a header
out of sync with its detectors, an unescaped `$` var name spliced into `new
RegExp`, and comment-stripping that both false-tripped on a trailing example
path and over-stripped inside strings).
Replace it with `no-restricted-syntax` selectors in eslint.config.mjs, scoped to
`src/skins/**`:
- (i) literal skin-id prefix — `"/banking/cards"`, `` `/keel/runs/${id}` ``
- (ii) interpolation immediately followed by `/` — `` `${base}/charges` `` (the
`//` that shipped); scoped OFF for the REST/data layer (`actions.ts`,
`intelligence/**`) whose `` `${apiBase}/…` `` targets a server URL the
lock never rewrites
- (iii) leading-slash interpolation — `` `/${skin.id}/…` ``
Each selector names useSkinHref / the skin's own helper and points at
src/shell/skin-path.ts. The AST rule ignores comments/prose and is immune to a
`$` in a variable name. Skin tests are exempt (they assert unlocked, prefixed
hrefs by design).
Delete src/shell/skin-path.drift.test.ts — one mechanism, not two. Point the
reskin skill (verification step 7 + URL-contract section) and CLAUDE.md at
`pnpm lint` and the ESLint rule instead of `pnpm test:unit` and the drift test.
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
8311d4d412 |
fix(reskinnable-demo): drop the URL prefix for the LOCKED skin, not any lock
`useSkinHref(skinId)` computed its base as `locked ? "" : `/${skinId}``,
testing whether ANY skin is locked rather than whether the CALLER's skin is
the locked one. Under `LOCK_SKIN=banking`, `useSkinHref("airline")("trips")`
returned `/trips` — a banking URL — silently discarding the `skinId` argument
and pointing the caller at the wrong app. Correct only by an invariant held
OUTSIDE the function (the locked deploy 404s every non-locked skin before it
mounts, and the one cross-skin link bypasses this hook).
Make it correct by construction: `locked === skinId ? "" : `/${skinId}``.
The prefix is dropped only for the skin that is actually locked.
Call-site enumeration (Procedure 2 step 8) — every `useSkinHref(` /
`useKeelHref(` caller and why the change is behaviour-preserving for it. In
every case the caller passes its OWN skin id, and a skin's layout/pages/tools
only render when that skin is active; under a lock the only skin that mounts
IS the locked one, so `skinId === locked` there and `locked === skinId`
reduces to the old `locked` truthiness. Equivalent everywhere:
src/skins/keel/href.ts:25 useSkinHref(KEEL_ID="keel") — wrapped by
useKeelHref(); consumed by keel/tools.tsx, layout.tsx, run-timeline,
approval-card, playbook-card, pages/{knowledge,desk,document,playbooks,
runs}. All render only under the keel skin ⇒ passes "keel"; under a lock
that lock is "keel". Unchanged.
src/skins/banking/tools.tsx:111 useSkinHref(skin.id="banking"). Banking-
only render. Unchanged.
src/skins/banking/layout.tsx:126 useSkinHref(skin.id="banking"). Banking-
only render. Unchanged.
src/skins/airline/layout.tsx:30 useSkinHref(skin.id="airline"). Airline-
only render. Unchanged.
src/skins/logistics/layout.tsx:25 useSkinHref(skin.id="logistics").
Logistics-only render. Unchanged.
Non-callers, for completeness:
src/shell/layout/selector-card.tsx the sole cross-skin link; deliberately
bypasses this hook and builds `/${skin.id}` directly (line 126). Never
exercised the buggy branch — unaffected.
src/skins/banking/nav-target.test.tsx:14,19 probes with skinId="banking"
under lock null or "banking"; `locked === "banking"` matches old `locked`.
Unchanged.
Test: added a covering case in skin-path.test.tsx asserting that under
`LOCK_SKIN=banking`, `useSkinHref("airline")("trips")` still returns the
PREFIXED `/airline/trips`. Verified red against the old one-line impl
(returned `/trips`), green after. Doc comment restated: the prefix is dropped
for the locked skin specifically, not "under a lock" for any skin.
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
291cd32832 |
chore: stop changeset files from reappearing in PRs (#6406)
## What does this PR do? Community PRs keep arriving with `.changeset/*.md` files even though the repo migrated off Changesets to conventional-commit-driven releases. `.changeset/` has now been deleted from `main` twice (`5afa55f067` on 2026-06-16, `1e5ba689e0` on 2026-07-29) and **five open PRs carry changeset files today** (#6287, #6289, #6290, #6292, #6346). Three mechanisms keep feeding it: 1. **Stale forks.** `rodboev/CopilotKit`'s default branch still contains 10 of the pre-cleanup `.changeset/*.md` debris files. Three of the five open PRs come from that fork — the contributor's agent opens the repo, sees a directory full of changesets, and adds one more. (No `config.json`, and `@changesets/cli` isn't installed anywhere, so these are hand-written by agents, not CLI output.) 2. **Merging stale PRs re-seeds `main`.** The two files Tyler removed in `1e5ba689e0` arrived via 2026-06-10-authored branches (#2910, #5360) merged on 2026-07-25 — they sat on `main` for four days, and anyone who forked in that window inherited the directory. His hunch in that commit message was right. 3. **Convention inference, uncontradicted.** #6346 is from a branch in this repo, where `.changeset/` does *not* exist, and it still has one. The repo reads as a Changesets repo: pnpm workspace monorepo, per-package `CHANGELOG.md` in Changesets' exact `### Patch Changes` output format, `chore: release monorepo vX.Y.Z` release PRs. Nothing in `CONTRIBUTING.md`, the PR template, `AGENTS.md`, `CLAUDE.md`, or `.claude/docs/` said otherwise, so the guess was well-supported. This PR closes all three off: - **`CONTRIBUTING.md`** — new "Changelogs and releases — do not add a changeset" section: we did use Changesets, `scripts/release/` now builds changelogs from commit subjects, `.changeset/*.md` is inert, write a good conventional commit subject instead, and leave versions/changelogs to maintainers. Includes a note to rebase old forks. - **`AGENTS.md` / `CLAUDE.md`** — the same rule as an Essentials bullet. This is the highest-leverage change: the contributors doing this are coding agents, and agents load these files automatically while mostly not reading `CONTRIBUTING.md`. - **`static / check binaries`** — fail the PR on added `.changeset/*` files, so this stops depending on review catching it (which is what failed in July and restarted the loop). Added to the existing forbidden-files gate rather than a new workflow: it already runs on every PR to `main`, is fork-safe (`contents: read`, no secrets), and has exactly this `git diff --name-only origin/BASE...HEAD` + `VIOLATIONS` shape. Filters on `--diff-filter=AM` so a PR that *deletes* stale changesets still passes. - **`.oxfmtrc.json`** — drop the ignore entry for `.github/actions/changesets-action/src/run.ts`, a path that hasn't existed for a long time. It was the last grep-visible "we use changesets" signal in a root config file. ## Related PRs and Issues - Follows up `1e5ba689e0` ("fix: remove all changesets"), whose commit message asked for exactly this: a durable record of the decision that future agents can find. - Open PRs that would be caught by the new gate: #6287, #6289, #6290, #6292, #6346. ## Testing Docs + CI-config change, so verification focused on the guard. `actionlint` was run on the workflow, then the step body was extracted with `yq` and executed against real branches. **Lint / parse:** ``` $ actionlint .github/workflows/static_check-binaries.yml actionlint: clean $ python3 -c "import json; json.load(open('.oxfmtrc.json'))" # oxfmtrc still valid JSON oxfmtrc JSON OK ``` **True positive** — real head of #6292, via `yq '.jobs.check-binaries.steps[1].run'` piped to bash with `BASE_REF=main`: ``` ::error::Changeset files detected in PR: .changeset/enable-mcp-apps-tool-filters.md This repo no longer uses Changesets — releases are driven by conventional commit subjects (see scripts/release/). Nothing reads .changeset/*.md. Delete these files and describe the change in your commit subject instead. See the 'Changelogs and releases' section of CONTRIBUTING.md. This PR contains files that should not be committed (see the errors above). Please remove them and update your .gitignore if needed. exit=1 ``` **True negative** — same script on this branch, which has five changed files and no changesets: ``` $ git diff --name-only origin/main...HEAD .github/workflows/static_check-binaries.yml .oxfmtrc.json AGENTS.md CLAUDE.md CONTRIBUTING.md $ BASE_REF=main bash step.sh No binary artifacts or oversized files detected. exit=0 ``` **Delete-safety** — a commit that *removes* changesets must not be punished. Using the real cleanup commit (`8806f668d1...1e5ba689e0`): ``` unfiltered: with --diff-filter=AM (what the gate uses): .changeset/coalesce-...md (empty) .changeset/fix-parallel-...md ``` Not verified locally: the gate firing in real GitHub Actions — that needs this PR's own CI run (the `static / check binaries` check on this PR exercises the true-negative path). 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
c6d59c529f |
feat(telemetry): emit telemetry-registry fragments for runtime + docs surfaces (#5891)
## What Adds the CopilotKit side of the [telemetry event registry](https://github.com/CopilotKit/oss-path-to-production/blob/main/docs/telemetry-registry-publish-roadmap.md): tooling + CI that generate this repo's registry **fragments** and open path-limited PRs into `CopilotKit/oss-path-to-production`, where the reconciler folds them into `telemetry-events.json`. Two surfaces, two mechanisms (per the surface-owns-its-extractor design): | Surface | Events | Extraction | Trigger | |---|---|---|---| | **runtime** | 5 `oss.runtime.*` | **bespoke catalog** — reads the `AnalyticsEvents` type map (names + properties), scans `capture()` sites for `call_sites`; **fails loud if the v1/v2 catalogs diverge** | stable **monorepo** release (`on: release`, tag `vX.Y.Z`) | | **docs** (`showcase/shell-docs`) | 11 | **callee mode** — inline `posthog.capture("name", {…})` literals; drops `$`-reserved events | push to `main` touching `showcase/shell-docs/src/**` (excluding `src/content`) | ## Key properties - **Content-gated.** The emitter leaves the target fragment byte-for-byte untouched when the extracted event set is unchanged, so a PR opens **only when telemetry actually changes** — no per-release / per-commit churn. - **Reconciled canonical in every PR.** Both workflows run the registry's `pnpm reconcile` and commit `telemetry-events.json` alongside the fragment, matching the registry's shipped emitters — a fragment-only PR fails its `telemetry-reconcile` staleness gate. - **Least-privilege cross-repo token.** No explicit `owner` (defaults to the app installation's org) + bare `repositories: oss-path-to-production` + `contents`/`pull-requests` write only; mint gated on a job-level env var (GitHub rejects `secrets.*` in `if:`). - **zizmor clean** at CI's `--min-severity low` (one `cache-poisoning` suppression, justified in `.github/zizmor.yml`: the workflow configures no cache and publishes a PR, not build artifacts). ## Files - `scripts/telemetry/extract.ts` — pure extraction (callee scan + catalog reader), deterministic output. - `scripts/telemetry/emit-fragment.ts` — CLI: `--surface runtime|docs --out <path>`, assembles + content-gates the fragment. - `scripts/__tests__/telemetry-fragment.test.ts` — 13 unit tests (fixtures) + a loose real-catalog drift smoke test. - `.github/workflows/telemetry-{runtime,docs}-fragment.yml` — the two CI jobs. ## Testing Rebased onto `main` (`55aaad21a6`) and revalidated end-to-end on 2026-08-05 — the branch had fallen 1345 commits behind. **Unit / static** - `vitest run scripts/__tests__/telemetry-fragment.test.ts` → **13/13 passed**. - `tsc --noEmit --strict --esModuleInterop` over both scripts → **clean** (`scripts/` has no tsconfig, so this is the ad-hoc invocation). - `oxlint scripts/telemetry` → **0 warnings, 0 errors**; `oxfmt --check` → **all files correctly formatted**. - `zizmor --min-severity low --config .github/zizmor.yml .github/workflows` (CI's exact invocation) → **No findings to report** (32 ignored, 233 suppressed). **Runtime surface — mechanism proven against the live rebased tree** ``` $ tsx scripts/telemetry/emit-fragment.ts --surface runtime --out /tmp/CopilotKit.runtime.json runtime: wrote 5 events → /tmp/CopilotKit.runtime.json (released_in runtime@1.66.2) ``` Diffed event-for-event against the registry's committed `CopilotKit.runtime.json`: **semantically identical** (same 5 events, same `call_sites`, same `properties_seen`) — the only difference is ordering, since the emitter sorts alphabetically and the hand-seeded fragment is in catalog-declaration order. Confirmed the reorder is a no-op at the canonical level (see below), so the first automated run opens one reordering PR with an empty `telemetry-events.json` diff and is quiet thereafter. Also confirmed the catalog is still complete on current `main`: the only `oss.*` event literals anywhere under `packages/runtime/src` + `packages/shared/src` are the 5 catalog entries (43/22/12/9/9 occurrences), so no untyped event is being silently dropped. Both v1 and v2 catalogs remain byte-identical, so the divergence guard passes. **Docs surface** ``` $ tsx scripts/telemetry/emit-fragment.ts --surface docs --out /tmp/CopilotKit.docs.json docs: wrote 11 events → /tmp/CopilotKit.docs.json (released_in shell-docs@5855496103) ``` 11 events (up from 7 when this PR was authored — the docs site grew): `cli_command_copied`, `docs_conversion_clicked`, `docs_conversion_copied`, `docs.framework_selected`, `docs.frontend_selected`, `docs.journey_continued`, `hero_command_copied`, `markdown_copied`, `open_in_llm_clicked`, `talk_to_us_clicked`, `try_for_free_clicked`. `$pageview` correctly dropped. **End-to-end against the real registry** Dropped both emitted fragments into a clean `oss-path-to-production@main` worktree and ran its own `pnpm reconcile`: - Both fragments **validate against `fragment.schema.json`** (ajv, via the reconciler's loader). - Reconcile succeeded; `telemetry-events.json` grew by 216 lines with 11 new `"surface": "docs"` observations. - **Zero `oss.runtime.*` entries changed** — confirming the runtime fragment's reordering has no canonical effect. ## Fixed during revalidation - **`add-paths` bug in the docs workflow (would have failed on first run).** It ran `pnpm reconcile` but listed only the fragment in `add-paths`, so its PR would have landed a fresh fragment beside a stale `telemetry-events.json` and tripped 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` #232/#220, Intelligence surfaces #228) carries `telemetry-events.json` alongside its fragment. - **Stale action pins.** Refreshed to the SHAs `main` now uses everywhere: `actions/checkout` v7, `actions/setup-node` v7.0.0, `pnpm/action-setup` v6.0.10. - **Over-broad docs trigger.** Narrowed from `showcase/shell-docs/**` to the code under `src/**`, excluding `src/content/**` — 1012 MDX + 140 JSON prose files with zero `.ts`/`.tsx`, none of which can hold a `posthog.capture` call site. Prose edits no longer fire a full monorepo install. - **zizmor justification accuracy.** `setup-node` v7 adds a `package-manager-cache` input defaulting to `true`; per its `action.yml` it engages only when `package.json` declares **npm**, and this repo declares pnpm — so the workflow is still cacheless and the suppression still holds. Noted inline. ## Prerequisite — now satisfied The registry App secrets (`TELEMETRY_REGISTRY_APP_ID`, `TELEMETRY_REGISTRY_APP_PRIVATE_KEY`) are configured on this repo (added 2026-07-09), and `app/copilotkit-telemetry-bot` is demonstrably installed on `oss-path-to-production` — it has been opening fragment PRs there from other surfaces (#232, #228, #220). No further setup needed. ## Not in this PR - The registry-side seed of the **docs** surface. The docs fragment first appears via this workflow's initial run, which now also carries the reconciled canonical, so it lands green. - The **web-inspector** surface, hand-seeded in the registry since this PR was authored, remains manual. Automating it is a follow-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
00969e323e |
chore: release channels v0.8.0 (#6419)
## Release channels v0.8.0 **Scope:** `channels` | **Bump:** `minor` --- ### How this release process works 1. **This PR was created automatically** by the "release / create-pr" workflow. It bumped the `channels` packages to `0.8.0` 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 `channels` packages to npm at version `0.8.0` - Creates git tag `channels/v0.8.0` - 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.channels/v0.8.0 |
||
|
|
289ae4a539 | chore: release channels v0.8.0 | ||
|
|
df6be1876c |
chore(deps): update dorny/paths-filter action to v4.0.3 (#6393)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [dorny/paths-filter](https://redirect.github.com/dorny/paths-filter) | action | patch | `v4.0.2` → `v4.0.3` | --- ### Release Notes <details> <summary>dorny/paths-filter (dorny/paths-filter)</summary> ### [`v4.0.3`](https://redirect.github.com/dorny/paths-filter/blob/HEAD/CHANGELOG.md#v403) [Compare Source](https://redirect.github.com/dorny/paths-filter/compare/v4.0.2...v4.0.3) - [Document safe handling of file list outputs in workflows](https://redirect.github.com/dorny/paths-filter/pull/326) - [Escape multi-line filenames in list-files shell and csv output](https://redirect.github.com/advisories/GHSA-7hc6-8hq5-9q2m) - [Add 'some-with-excludes' predicate quantifier](https://redirect.github.com/dorny/paths-filter/pull/322) - [Add contents permission to PR example](https://redirect.github.com/dorny/paths-filter/pull/248) - [Scope base-ignored warning to API path](https://redirect.github.com/dorny/paths-filter/pull/319) - [Update outputs in readme to account for the 'every' predicate-quantifier](https://redirect.github.com/dorny/paths-filter/pull/247) </details> --- ### Configuration 📅 **Schedule**: (in timezone America/Los_Angeles) - Branch creation - "before 9am every weekday" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/CopilotKit/CopilotKit). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMi4wIiwidXBkYXRlZEluVmVyIjoiNDQuMTIuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> |
||
|
|
703283c84a |
test(reskinnable-demo): decouple LOCK_SKIN nav guard from admin-gated /team
The vacuity precondition in locked-skin.spec.ts required the banking nav to render /, /dashboard, /charges AND /team. But /team is admin-gated in the banking layout (rendered only when currentUser.role === MemberRole.Admin), and the default user is team[0] from the seed (Alex Morgan, Admin). That silently coupled the LOCK_SKIN prefix guard to seed order and the default user's role — a reorder or role flip would fail the suite on an assertion unrelated to LOCK_SKIN. Require only the role-independent targets (/, /dashboard, /charges) as the vacuity guard, and document why /team must not be re-added. The /team route stays covered role-independently by the cold deep-page load test. Co-Authored-By: Claude <noreply@anthropic.com> |