Commit Graph

15424 Commits

Author SHA1 Message Date
Ran Shem Tov 6862508eb2 Merge remote-tracking branch 'origin/main' into codex/crewai-full-d6
# Conflicts:
#	showcase/harness/Dockerfile
#	showcase/scripts/fail-baseline.json
2026-08-07 17:53:55 +03:00
Ran Shem Tov 61eed4a4ad fix(showcase): harden CrewAI D6 parity on a3 2026-08-07 17:50:13 +03:00
Ran Shem Tov 255f791d81 test(showcase): support live D6 fixture recording 2026-08-07 17:49:22 +03:00
Alem Tuzlak 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
2026-08-07 16:31:02 +02:00
Alem Tuzlak 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
2026-08-07 16:29:19 +02:00
Maxim 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)
2026-08-07 13:45:14 +02:00
renovate[bot] 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==-->
2026-08-07 11:32:52 +00:00
Maxim e79376b11b Merge branch 'main' into feat/reskinnable-demo-lock-skin 2026-08-07 13:32:13 +02:00
renovate[bot] f4c959e3fe chore(deps): update reviewdog/action-actionlint action to v1.73.1 2026-08-07 10:11:21 +00:00
Maxim 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>
2026-08-07 10:48:30 +02:00
Mark 2e0627fc08 fix(shell-docs): drop the redirect-only link rewrites from this PR
Nine /integrations/<fw>/* links were rewritten to their canonical URLs.
Checking production, they were never broken — seo-redirects.ts 301s that
whole retired surface, so a reader always landed on the right page. That
made them cosmetic, and cosmetic changes do not belong in a PR whose
value is the defects around them.

Removing them takes this from 22 files to 15, all of which are things a
reader actually hits. The canonicalization is still worth doing; the
checker now reports it as its own advisory (legacy-redirect-links) so it
is tracked rather than lost.
2026-08-07 02:23:09 +00:00
Tyler Slaton 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
2026-08-06 18:25:59 -07:00
tylerslaton b40602e698 chore: release monorepo v1.66.4 2026-08-07 01:25:14 +00:00
Mark 6457e87378 fix(shell-docs): repair 24 dead links, missing imports, and stale demo ids
Twenty-four defects a reader would hit, each verified against the tree
rather than pattern-matched. Found while root-causing PDX-313.

Links written as filesystem paths instead of served URLs (9). The docs
router serves integration pages at /<framework>/<slug>; the
/integrations/ prefix is the content directory, not a route. All nine
corrected URLs were confirmed to resolve.

Tutorial cross-links to directories with no index.mdx (4). loadDoc
resolves <slug>.mdx or <slug>/index.mdx, so /tutorials/ai-todo-app was a
404 — the page is at /overview.

Snippet components used with props but never imported (7). These fall
through to stubWithPartial in the global mdx-registry, which drops props
"on the floor" by design, so framework="pydantic-ai" never reached the
partial and the shared snippet rendered untailored. Importing the
partial restores prop flow. The mastra and ag2 siblings were already
correct; the seven broken ones are all in authored trees.

Dead YouTubeVideo imports (2). The component is provided globally by
mdx-registry.tsx and four other pages render it with no import at all;
these two imported a module that has never existed in the repo.

Stale byoc-* demo ids (2). Renamed to declarative-* in 70e2fb13c8
(2026-05-10, "rename byoc-* slugs to declarative-*"); the docs were
never updated. Only the three registry ID references per page change
here — snippet_cell, InlineDemo, IntegrationGrid.

Left alone deliberately: the runtimeUrl/agent code samples on the
hashbrown and json-render pages. The API routes were renamed to
copilotkit-declarative-*, but the agent ids were not renamed
consistently — declarative-hashbrown's demo uses
agent="declarative-hashbrown-demo" while declarative-json-render's still
exports AGENT_ID = "byoc_json_render". Guessing would ship a broken
copy-paste sample, so that needs an owner.
2026-08-07 01:17:53 +00:00
Tyler Slaton 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
2026-08-06 17:34:05 -07:00
tylerslaton cfc5cfe727 chore: release monorepo v1.66.3 2026-08-07 00:31:47 +00:00
Mark 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.
2026-08-06 16:51:36 -07:00
Mark 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`.
2026-08-06 21:47:23 +00:00
Mark 3cf128ec8f docs(showcase): correct pydantic-ai v2 API refs and gen-ui-agent comments
PARITY_NOTES.md and qa/beautiful-chat.md described `agent.to_ag_ui()`,
which v2 removes. Replaced with the AG-UI adapter / `mount_agent()`
wording this branch introduces.

Also flags the PARITY_NOTES "Skipped demos" section as stale rather than
silently leaving it: mcp-apps, hitl-in-chat and hitl-in-chat-booking all
ship, and the reasoning/interrupt reasons no longer match manifest.yaml
(which is the authority). Full rewrite tracked in OSS-777.

The gen-ui-agent comments asserted a `src/agents/gen_ui_agent.py` and a
`set_steps` tool that exist nowhere in this package. The cell has no route
override, so it proxies to the root sales agent and its D6 probe is red on
main (GH #6381). The comments now describe that, instead of an
intended-but-unbuilt contract.

Adds the missing `shared-state-read` entry to manifest.yaml `demos:` — it
was declared under `features:` with no route or highlight. Mirrors
langgraph-python's entry, which likewise omits an agent file because the
cell runs on the neutral default agent.
2026-08-06 21:42:05 +00:00
Maxim 43df6e5afd Merge remote-tracking branch 'origin/main' into feat/reskinnable-demo-lock-skin 2026-08-06 22:10:21 +02:00
Maxim 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>
2026-08-06 21:31:56 +02:00
Maxim 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>
2026-08-06 21:22:39 +02:00
Maxim 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>
2026-08-06 21:08:24 +02:00
Maxim 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>
2026-08-06 21:08:24 +02:00
Maxim 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>
2026-08-06 19:18:26 +02:00
Maxim 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>
2026-08-06 19:18:26 +02:00
Mark dc3681b106 Merge branch 'main' into chore/showcase-pydantic-ai-v2 2026-08-06 10:11:29 -07:00
Ben Taylor 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)
2026-08-06 11:43:45 -05:00
Ben Taylor 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)
2026-08-06 11:42:44 -05:00
Tyler Slaton 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
2026-08-06 09:28:26 -07:00
tylerslaton 289ae4a539 chore: release channels v0.8.0 2026-08-06 09:26:34 -07:00
renovate[bot] 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==-->
2026-08-06 16:25:32 +00:00
Maxim 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>
2026-08-06 17:48:44 +02:00
Maxim c1d3a0a22c test(reskinnable-demo): state skin-path guard detector (ii)'s name gate honestly
The header claimed every detector matches the SHAPE of the defect, but
detector (ii) (builderResultConcat) is name-gated to the two sanctioned
builder-result names skinHref/keelHref — so a renamed builder slips the
// bug through. That is intrinsic, not a bug: a lexical guard cannot tell
`const base = skinHref()` from `const base = apiUrl.replace(...)`
(banking/intelligence, legitimately concatenated) without the callee name.

Keep the name gate (deliberate precision/recall trade-off — those two are
the only href builders the reskin skill teaches) and correct the header to
state the actual guarantee and its known blind spot. Encode the blind spot
in an executable test so a renamed builder staying uncaught is a reviewed
decision, not a silent regression.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 17:48:43 +02:00
Maxim 5ff990a891 docs(reskinnable-demo): correct README skin count from two to four
The intro said the app 'ships two of them' and listed only banking and
airline, contradicting line 56 ('banking, airline, logistics, keel'),
CLAUDE.md, and src/shell/registry.ts. Corrected the count to four, added
logistics and keel to the list with their substrates, rewrote the
substrate-agnostic paragraph to name all four honestly (banking + logistics
REST-backed, airline + keel in-memory; keel the only one with parameterized
routes), and fixed 'the richer of the two' to 'the richest of the four'.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 17:48:43 +02:00
Maxim 3368370b6c fix(reskinnable-demo): make the URL drift guard cover the invariant, not a spelling list
The URL-contract drift guard enumerated known spellings of a mistake
(literal ids and the exact `${skin.id}`/`${skinId}` interpolations) and
so reported green while blind to the shape that actually shipped:
`router.push(`${base}/charges`)` with `base = skinHref()`, which returns
`/` under a LOCK_SKIN deploy and ships `//charges`. A guard that lists
spellings cannot cover the space.

Rewrite the guard to match the SHAPE of the defect via three detectors
over one invariant (no in-skin link may carry a skin prefix or yield `//`):
- (i)   interpolated id at the START of a quoted path, ANY holder whose
        expression ends in id/Id (`/${id}`, `/${s.id}`, `/${activeSkin.id}`),
        not just the literal `skin.id`/`skinId`;
- (ii)  concatenation onto a value BOUND from a builder call
        (`const base = skinHref()` → `${base}/x`, `${base}${x}`). Gating on
        the builder BINDING is what spares the legitimate REST bases
        `const base = apiUrl.replace(...)` (banking/intelligence) and
        `const BASE = "/api/logistics/v1"` (logistics/actions), and keel's
        inline `${keelHref(...)}#${id}` deep links (not bound vars);
- (iii) literal skin prefix (kept).

Correct the docstring/behaviour mismatch: the check matches ANY skin id,
which is STRICTER than "its OWN prefix". Kept the stricter rule (a comment
explains why: cross-skin nav is the shell switcher's job, out of scope by
living outside src/skins/; inside a skin any sibling prefix is just as
broken under a lock) rather than narrowing to the owning id.

Fix the live bug the hardened guard exposed in banking/tools.tsx: two
`base = skinHref()` concatenations (`${base}/charges` and
`${base}${page}`) now route through skinHref(), which strips leading
slashes and re-joins cleanly under both lock states.

The self-test now asserts every previously-MISSED shape is caught and the
two REST-base forms are not; the guard was also proven to fire end-to-end
by injecting a real literal-prefix and a real `${base}/x` violation into
skin sources (each failed naming its file), then reverting.

Call-Site Enumeration (Procedure 2 step 8): swept all `src/skins/**` for
in-skin link construction. Builder-result vars: `base` (banking/tools.tsx,
banking/layout.tsx), `href` (airline/keel/logistics layout.tsx). Only
banking/tools.tsx concatenated onto one (2 sites, both fixed);
banking/layout.tsx and the `href` vars use the value bare. No literal-id or
start-interpolation offenders exist. Legitimate non-lock bases confirmed
untouched: banking/intelligence `${base}/api/memories`, logistics/actions
`${BASE}/...`, and banking/actions `/api/banking/v1/.../${id}/...`.

pnpm lint, pnpm test:unit (330 tests), and pnpm build all pass.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 17:30:01 +02:00
Maxim a719246bb1 fix(reskinnable-demo): stop banking tools emitting protocol-relative // hrefs under LOCK_SKIN
BankingTools composed navigation URLs by concatenating onto the no-arg
result of the skin href builder (`const base = skinHref()`). `useSkinHref`
returns "/" — not "" — for the skin index under a lock (the empty string
is not a usable href), so on a LOCK_SKIN deploy:

  - `${base}${page.toLowerCase()}` for page "/team" -> "//team"
  - `${base}/charges` (and the ?qs variant) -> "//charges"

Both are protocol-relative URLs: the browser reads "//team" as
"https://team/" and navigates off-site. Unlocked they were correct
(/banking/team, /banking/charges) which is why this never surfaced there.

Fix: route both through the `skinHref(path)` builder, which strips a
leading slash and yields /banking/team|/team and /banking/charges|/charges
with no "//". The two compositions are extracted into a pure module
(src/skins/banking/nav-target.ts: navTarget, chargesTarget) so they can be
unit-tested without rendering the whole tools tree, which needs the full
CopilotKit/auth/recording provider stack. Query-string behaviour at the
charges site and the "/"+"/cards" -> skin-index special case are preserved.

Red-green verified: reverting the helpers to the `${base}...` concat form
turns the two locked-deploy tests RED (asserting "//team"/"//charges"),
restoring them GREEN.

Call-site enumeration (Procedure 2 step 8):
- Local `base` in BankingTools (removed): had two code users — the
  navigateToPageAndPerform target and the showCharges push. Both now call
  the helpers; grep shows no remaining code reference (only comments).
  Assumption removed cleanly.
- navTarget / chargesTarget (added): referenced only from tools.tsx
  (navigateToPageAndPerform, showCharges) and nav-target.test.tsx. New
  symbols, no external assumptions.
- SkinHref type (added): local to nav-target.ts; mirrors useSkinHref's
  public return type `(path?: string) => string`. Holds.
- useSkinHref / skinHref (unchanged signature): still called as
  skinHref(page.toLowerCase()) and skinHref("charges"); all other call
  sites across skins (keel keelHref(path), airline/logistics
  skinHref(route.segment), banking/layout.tsx base-as-index-href) are
  unaffected — none concatenated onto the no-arg result, so their
  assumptions still hold.
- The other `${base}` matches in banking/intelligence/{seed,forget}-memories.ts
  are an unrelated API base URL, not the skin href builder.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 17:29:08 +02:00
Maxim fcdf512a91 docs(reskinnable-demo): correct the false LOCK_SKIN 'defence in depth' claim on /
Under a lock, src/proxy.ts rewrites / to /<locked> in place before src/app/
page.tsx renders, so the page is unreachable on a locked deploy (verified: a
locked server answers GET / with 200 and no redirect). The old comments framed
its lockedSkinId() read as 'defence in depth' and claimed that without it a
locked / would 404 — both premised on the page running under a lock, which it
never does.

Behaviour is already correct and unchanged: redirect(`/${lockedSkinId() ??
defaultSkinId}`). The read is a proxy-INDEPENDENT backup (were / to reach this
page with the proxy absent, it targets the locked skin's real route /<locked>,
which renders — not defaultSkinId which would 404, and not / which would loop).
It is not the double-prefix trap: /<locked> is only re-rewritten to
/<locked>/<locked> when the proxy is present, and then this page never runs.

Rewrite the page.tsx header, the CLAUDE.md routing bullet, and the page.test.ts
locked-case comment to state this precisely. Left the isSkinLockedOut 'defence
in depth' bullet: verified accurate — it correctly 404s a non-locked skin if the
layout is ever reached directly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 17:29:08 +02:00
Maxim a5791f072a test(reskinnable-demo): make locked-skin guards positive, not vacuous
The headline "no in-app link carries the skin prefix" test asserted an
empty match set (`a[href^="/banking"]` -> []), which passes green if the
nav never renders at all. Add a positive precondition — nonzero in-app
anchors and the known banking nav targets (/, /dashboard, /charges,
/team) present — before asserting the prefix is absent, and also assert
no rendered href is protocol-relative (`//host`), the other way the
skin-href builder breaks. Make the metadata description assertion a strict
null-safe exact match on the locked skin's tagline, mirroring toHaveTitle,
instead of a negative-only `.not.toContain`.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 17:29:07 +02:00
Maxim 1da2001fec fix(reskinnable-demo): parameterize the unlocked webServer probe and correct its comments
The unlocked webServer hardcoded its readiness probe as http://localhost:3000/banking,
restating both the port and the default skin id while the baseURL hardcoded the port
separately. A divergence in the port or defaultSkinId would silently mismatch the probe
and fail before any spec runs. Parameterize the unlocked side like the locked side:
derive the port from UNLOCKED_PORT (also passed as PORT to the dev server) and the skin
from the real defaultSkinId (imported from the deliberately import-free skins-config).

Also correct the webServer rationale: Playwright starts webServer entries in parallel,
so aimock has no ordering guarantee over the dev servers. The invariant holds because
the runtime reads OPENAI_BASE_URL per request, not at boot. Update the count to three
servers, and extend the reuseExistingServer warning to cover the locked port too.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 17:29:07 +02:00
Maxim 151ce3505b fix(reskinnable-demo): anchor proxy matcher to segment boundaries and exclude _next dev endpoints
The LOCK_SKIN proxy matcher had two boundary defects:

1. It excluded only `_next/static` and `_next/image`, not `_next` generally.
   Extension-less framework paths therefore got rewritten under a lock:
   `/_next/webpack-hmr`, `/_next/dev/on-demand-entries-ping`, and
   `/__nextjs_original-stack-frame` all MATCHED and would rewrite to
   `/<locked>/_next/...`. That breaks HMR and the error overlay — and
   next.config.mjs states this demo is PRESENTED from `next dev`, so that is
   the feature's real usage, not an edge case.

2. `api` and `_next` were PREFIX matches, not SEGMENT matches. A future
   top-level route like `/apiary` or `/api-keys` would silently skip the
   rewrite and 404 only on locked deploys.

Fix: anchor `api` and `_next` to a segment boundary (`(?:/|$)`) and exclude
`_next` plus the `__nextjs`-prefixed dev endpoints wholesale. Dotted paths
(public assets, favicon.ico) stay excluded. The `api` exclusion is NOT
weakened — `/api/copilotkit` carries the agent SSE stream and must never
enter the proxy; bare `/api` and all `/api/*` remain excluded.

Also corrected the matcher comment: it overstated the old pattern's coverage
("Next's own asset routes") and cited keel run ids as kebab-case `r-1` when
the real ids are `RUN-1041`-style (src/skins/keel/data/seed.ts). The dot-free
property the comment relies on still holds — doc ids are kebab-case
(`phi-access-contractor`), run ids are `RUN-1041` — so the conclusion stands;
only the stated evidence is fixed.

Tests: added boundary near-miss cases to src/proxy.test.ts — `/_next/webpack-hmr`,
`/_next/dev/on-demand-entries-ping`, `/__nextjs_original-stack-frame` (excluded)
and `/apiary`, `/api-keys` (matched — app routes, not the API) plus bare `/api`
(excluded). Red-green verified: the five differentiating cases FAIL against the
old matcher and pass after the fix.

Call-site enumeration (Procedure 2 step 8): `config.matcher` and `proxy` are
exported from src/proxy.ts. Next.js loads this file by convention (Next 16's
rename of middleware.ts) and reads `config.matcher` to decide which paths
invoke `proxy` — a framework consumer, not app code. The only in-repo importer
is src/proxy.test.ts (imports both `config` and `proxy`). No other module
references either symbol, so the behavior change is contained to the framework
routing hook and its test.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 17:29:06 +02:00
Maxim 494dc4f773 test(reskinnable-demo): cover the LOCK_SKIN deploy shape
LOCK_SKIN's headline behaviour had ZERO automated coverage. Every existing spec
pins the gate off (`LOCK_SKIN: ""` in the webServer env), so the locked shape was
verified only by hand. That gap matters more now that the lock rewrites the whole
URL space rather than just picking a redirect target: the client and server
halves (useSkinHref and proxy.ts) must agree, and if they do not the feature
half-works SILENTLY — pages still resolve, the tenant prefix just reappears in
the address bar. Nothing fails; the demo stops being what it claims to be.

Two guards, cheapest first.

`src/shell/skin-path.drift.test.ts` — a lexical guard that no file under
src/skins/** hardcodes its own route prefix. Deliberately static, not a render
test: the violation type-checks, lints, renders AND navigates correctly, so
there is nothing for a behavioural test to catch short of reading the href. The
shell's skin SWITCHER is the one legitimate hardcoded prefix (it targets a
DIFFERENT skin and only renders unlocked) and sits outside src/skins/, so it is
out of scope by construction rather than by exemption list. Verified the guard
actually fires by reintroducing airline's old `/${skin.id}/${route.segment}` and
confirming it failed naming that file.

`e2e/locked-skin.spec.ts` + a `locked` Playwright project — the real check, in a
browser against a genuinely locked server. The lock is a boot-time server env, so
the two deploy shapes are two processes; hence a second webServer rather than a
fixture. 12 tests: served at `/` with no redirect, branded metadata, static
badge, NO link carrying the prefix, click-through keeping the URL clean, deep
page cold-loading, the other skins 404ing, and the SSE + public-asset paths
staying un-rewritten.

Supporting config, each item load-bearing:

- `next.config.mjs` gains an env-driven `distDir`. Two `next dev` processes
  corrupt each other's output through a shared `.next`.
- `eslint.config.mjs` ignores `.next-locked/**`. ESLint does not read
  .gitignore, so without it one e2e run made `pnpm lint` report 23,706 problems
  in generated output.
- `tsconfig.json` pre-lists the `.next-locked` type globs so the locked server
  has nothing to append to a tracked file.
- The unlocked project repeats `ogui-routing.spec.ts` in its OWN testIgnore. A
  project-level testIgnore REPLACES the config-level one rather than adding to
  it, so introducing projects silently re-admitted those 7 specs — caught by
  checking the per-project test counts against the pre-change baseline, not by
  the run passing.

Suite goes 17 -> 29 tests: the same 17 unlocked (baseline preserved exactly) plus
12 locked. Unit tests 326 -> 330. `pnpm lint` clean, `pnpm build` clean.

Also verified airline and logistics locked in a browser — both were changed by
the parent commit (href construction AND active-state derivation) and neither had
been exercised. Nav is prefix-free and aria-current tracks correctly in both.

KNOWN CHURN, documented at the env block: Next rewrites the tracked
`next-env.d.ts` to reference whichever dist dir booted last, so a full e2e run
leaves it pointing at `.next-locked`. Discard that hunk before committing; any
build restores it.

Reskin skill: verification gains the two steps that would have caught a new
skin's violation — run `pnpm test:unit` for the drift guard, then run the skin
under `LOCK_SKIN=<id>` and open `/`.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 16:46:36 +02:00
Ran Shem Tov e7cc29bfc0 docs(showcase): consolidate conversational flows under CrewAI 2026-08-06 16:40:01 +03:00
Maxim 853156e7f8 docs(reskinnable-demo): teach the reskin skill the URL contract
The layout template handed every new skin the two patterns the LOCK_SKIN
root-serving change just removed: a hardcoded `/${skin.id}/${segment}` href and
a `pathname.split("/").slice(2)` segment derivation.

Both fail SILENTLY on a locked deploy, which is what makes them worth a skill
edit rather than just a fixed template. The hardcoded href still resolves — it
merely puts `/banking` back in the address bar on the first nav click, undoing
the single-tenant illusion the lock exists to create. The fixed slice eats the
first real segment when there is no prefix to skip, so every locked page reports
itself as the index and the wrong nav entry lights up.

Template now uses `useSkinHref` / `useSkinSegments`, and compares segments rather
than `pathname === href` for the active entry. SKILL.md gains a "URL contract"
section stating the rule, the two failure modes, the per-skin wrapper pattern
(`src/skins/keel/href.ts`), and the one legitimate exception — a link to a
DIFFERENT skin, which must keep the prefix and only ever renders unlocked.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 15:34:12 +02:00
Maxim c554f4e5da feat(reskinnable-demo): serve the locked skin AT the root, not under its prefix
LOCK_SKIN made `/` REDIRECT to `/<id>`, so a single-tenant deploy still showed
the substrate's tenant segment in the address bar — on the front door and on
every link after it. A customer opening the Meridian deploy landed on
`/logistics`. The lock removed the OTHER skins; it never removed the prefix.

Now the prefix leaves the URL space entirely: `LOCK_SKIN=banking` serves the
cards view at `/`, the dashboard at `/dashboard`, the team page at `/team`.
Nothing redirects. Unlocked behaviour is unchanged in every respect.

Two halves, and they must agree:

- `src/proxy.ts` rewrites the prefix-free space onto the `/[skin]` route tree
  (`/cards` -> `/banking/cards`). `proxy.ts` (Next 16's rename of
  `middleware.ts`) and NOT a `next.config` rewrite, because `rewrites()` is
  serialised into routes-manifest.json at BUILD time and would bake the lock
  into the artifact — forfeiting the one-build-serves-both-hosts invariant this
  feature was built around. Proxy files always run on the Node server, so
  LOCK_SKIN stays a per-request read.
- `useSkinHref` (`src/shell/skin-path.ts`) makes every in-skin link prefix-free
  under a lock. Without it the rewrite alone is useless: the first nav click
  would put `/banking` straight back in the address bar.

Because the rewrite TARGET keeps the `[skin]` segment, `params` is untouched —
keel's `useParams<{ skin, rest }>` pages needed no change. That is why this is a
proxy rewrite rather than a collapse of `[skin]/[[...rest]]` into a root
catch-all, which would have broken them.

`useSkinSegments` replaces three copies of `pathname.split("/").slice(2)`. It
strips a LEADING skin id instead of slicing a fixed offset, so it is correct
whether or not the pathname carries the prefix — the fixed slice ate the first
real segment on every locked page, highlighting the wrong nav entry.

The SSE stream is safe by construction: the matcher excludes `api`, so
`/api/copilotkit` never enters the proxy. That was the stated reason the
original change avoided a request-time hook; the documented matcher answers it.

Under a lock the locked skin's OWN prefix (`/banking`) now 404s, consistent with
the existing "a disowned skin is as absent as /nope" semantics.

Verified on ONE build artifact served three ways (locked banking, locked keel,
unlocked), in a real browser rather than only in tests — SSR alone cannot see
these hrefs, since the skin tree is entirely client-rendered:

  locked banking  /  -> cards view, title "Northwind Finance", nav hrefs
                       /, /dashboard, /charges, /team; click -> URL stays
                       /dashboard with aria-current on the right entry;
                       /banking, /airline, /nope -> 404 page
  locked keel     /knowledge/phi-access-policy -> doc reader renders all six
                       sections (useParams resolved through the rewrite);
                       zero /keel-prefixed hrefs in the DOM
  unlocked        / -> 307 /banking; hrefs prefixed; switcher present;
                       all four skins 200

`pnpm lint` clean · `pnpm test:unit` 53 files / 326 tests (+25) · `pnpm build`
clean, zero static routes, proxy registered.

Known, pre-existing: an unknown path under a lock renders the 404 PAGE but
returns HTTP 200. This is not caused by the rewrite — on the unlocked build
`/banking/nope` is already 200, because `notFound()` raised from the client PAGE
component (resolvePage -> null) cannot change a status Next has already
committed, whereas `notFound()` from the layout can. The lock only makes the
page-level path the one unknown URLs take.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 15:31:07 +02:00
Ran Shem Tov 0b6129141c docs(showcase): document CrewAI CF version floor 2026-08-06 15:45:27 +03:00
renovate[bot] 732987da9f chore(deps): update dorny/paths-filter action to v4.0.3 2026-08-06 12:37:56 +00:00
Murat Sari 53cf7e6575 fix(angular): height measurement cleanup (#6080)
Fixes two layout bugs in the Angular chat view:

1. **Floating-input height stuck at `0`.** Measurement ran once in
`ngAfterViewInit`,
but on the welcome screen the input overlay isn't in the DOM yet —
retries
expired and the height never recovered. After the first message, the
list
rendered under the floating input and the scroll-to-bottom button
overlapped it.
2. **Scroll wrapper sized to the viewport** (`h-[calc(100vh-9rem)]`),
which
   overshoots when the chat is embedded in a smaller panel.
2026-08-06 14:36:49 +02:00
Ran Shem Tov 5136097aa0 feat(showcase): add CrewAI conversational flows 2026-08-06 15:33:10 +03:00
Murat Sari df8599c8a3 Merge branch 'CopilotKit:main' into feat/height-measurement-cleanup 2026-08-06 13:11:23 +02:00