1442 Commits

Author SHA1 Message Date
Mark cdd605c672 docs(pydantic-ai): port integration docs and demos to Pydantic AI v2 (#6367)
This pull request was posted by Claude Code using claude-opus-5 on
behalf of David. David has not reviewed this diff line by line.

Closes https://github.com/CopilotKit/CopilotKit/issues/6363

`Agent.to_ag_ui()`, `AGUIApp` and the whole `pydantic_ai.ag_ui` module
were removed in Pydantic AI v2. The docs installed pydantic-ai unpinned,
so following the quickstart today gets 2.22.0 and fails twice: first at
resolution (`starlette==0.45.3` conflicts with the `>=0.46.2` the
`ag-ui` extra requires), then at `AttributeError`.

## What changed

**8 doc pages** under
`showcase/shell-docs/src/content/docs/integrations/pydantic-ai/`
(`quickstart.mdx`, `quickstart/pydantic-ai.mdx`,
`human-in-the-loop.mdx`, `human-in-the-loop/agent.mdx`,
`generative-ui/tool-rendering.mdx`, and the three `shared-state/`
pages):

- the agent is served from a Starlette route via
`AGUIAdapter.dispatch_request(request, agent=agent)`
- `StateDeps` imports move from `pydantic_ai.ag_ui` to `pydantic_ai.ui`
- install commands exact-pin `pydantic-ai-slim[ag-ui,openai]==2.22.0`
and `ag-ui-protocol==0.1.19`, matching the starter fleet, plus
`starlette>=0.46.2` since the snippets import Starlette directly

**Per-request deps.** Every stateful snippet builds `StateDeps` inside
the request handler:

```python
async def run_agent(request: Request) -> Response:
    return await AGUIAdapter.dispatch_request(
        request, agent=agent, deps=StateDeps(AgentState())
    )
```

`dispatch_request` validates the client's state into `deps.state`
(`pydantic_ai/ui/_adapter.py`, `run_stream_native`), so a module-level
instance shared across requests lets concurrent runs clobber each other.
The old `to_ag_ui(deps=...)` snippets all did this.

**`examples/canvas/pydantic-ai`** — `requirements.txt` pinned,
`agent/agent.py` ported, README corrected.

**`examples/showcases/pydantic-ai-todos`** — `pyproject.toml` pinned and
`uv.lock` regenerated (it was still resolving 1.0.10), `agent/main.py`
ported, `src/agent.py` and `src/tools.py` imports moved, README and
`src/app/api/copilotkit/route.ts` comments corrected.

**`skills/copilotkit-integrations`** — beyond the issue's file list:
`SKILL.md`, `sources.md` and `references/integrations/pydantic-ai.md`
also taught `to_ag_ui()`. Same rot, same fix.

## Verified by execution

The reason these docs rotted is that nothing runs them, so everything
below was actually run, not read.

- Both install commands were run verbatim in throwaway environments. `uv
add 'pydantic-ai-slim[ag-ui,openai]==2.22.0' 'ag-ui-protocol==0.1.19'
'starlette>=0.46.2' uvicorn` and the `pip install` equivalent both
resolve, landing pydantic-ai-slim 2.22.0, ag-ui-protocol 0.1.19,
starlette 1.3.1.
- Every ```python fence on the 8 doc pages was extracted, `exec`'d, and
driven with a real `RunAgentInput` POST through
`starlette.testclient.TestClient` with the model overridden to
`TestModel`. All 8 return 200 `text/event-stream` with a `RUN_STARTED`
... `RUN_FINISHED` sequence and no `RUN_ERROR`.
- The canvas agent was installed from its `requirements.txt` and driven
the same way: 200, SSE, `RUN_STARTED` ... `TOOL_CALL_*` ...
`STATE_SNAPSHOT` ... `RUN_FINISHED`.
- The todos agent was installed with `uv sync --frozen` from the
regenerated lock and driven the same way. Two sequential requests, one
seeding a todo and one sending empty state, each saw only their own
state, confirming the per-request deps actually isolate.

Not executed: the Next.js frontends and the docs site build (no
`node_modules` in this checkout). The TypeScript edits are comment-only.

## Deliberately out of scope

`showcase/integrations/pydantic-ai` is left on its v1 fleet pin. It is
418 files, 19 mounts and 190 e2e specs, and CopilotKit said they will
take it as https://github.com/CopilotKit/CopilotKit/issues/6364. The
dojo and the docs therefore diverge until that lands.

The CI guard from the issue's last acceptance criterion is not built
here. A proposal for it is posted on
https://github.com/CopilotKit/CopilotKit/issues/6363 for the team to
own.

Two pre-existing malformed code fences were fixed in passing, because
leaving them meant the ported snippets still would not run:
`quickstart/pydantic-ai.mdx` and
`shared-state/predictive-state-updates.mdx` each had TypeScript embedded
inside an unterminated ```python fence. The TypeScript now sits in its
own fence.

Overlaps with https://github.com/CopilotKit/CopilotKit/pull/6355, which
ports `examples/integrations/pydantic-ai`. No file overlap.
2026-08-04 12:58:07 -07:00
David Sanchez 22108c0948 docs(pydantic-ai): constrain the direct dep, not the transitive one
Follows the maintainer's Correction #2 on issue 6363. An exact version in a
docs install command is the same rot as the starlette==0.45.3 pin it replaced:
it goes stale silently and nobody re-resolves prose. The 2.22.0 the docs shipped
was already a version behind current the day it was written.

- docs install lines use pydantic-ai-slim[ag-ui,openai]>=2,<3, which constrains
  the dep the pages actually care about and fails loudly at the v3 boundary
- ag-ui-protocol drops out of the docs lines entirely; no doc snippet imports
  ag_ui, so naming it there was the transitive-dep noise the correction is about
- starlette>=0.46.2 stays, because the v2 snippets import Starlette directly.
  A floor with no ceiling cannot force a downgrade, so it does not recreate the
  silent backtrack
- examples/showcases/pydantic-ai-todos moves to a range in pyproject.toml and
  relocks; the uv.lock is what reproduces
- examples/canvas/pydantic-ai keeps exact pins: it has no lockfile, so
  requirements.txt is its only reproducibility artifact

Smoke-tested the open question from the issue: starlette 1.x works on
pydantic-ai v2. All 8 doc pages pass on 2.23.0 + starlette 1.3.1 and on
2.23.0 + starlette 0.52.1, so Jordan's <1.0 guard can be dropped rather
than raised.
2026-08-04 12:08:20 -05:00
Mark 14a294f2d8 Port the Pydantic AI example to Pydantic AI v2 (#6355)
This pull request was posted by Claude Code using claude-opus-5 on
behalf of David. David has not reviewed this diff.

`examples/integrations/pydantic-ai` only runs on Pydantic AI **v1**.
`Agent.to_ag_ui()`, `AGUIApp`, and the `pydantic_ai.ag_ui` shim were all
removed in v2 (pydantic/pydantic-ai#5464, announced in
pydantic/pydantic-ai#5345). The example pins `pydantic-ai-slim==1.0.18`,
so anyone installing it against current Pydantic AI (2.22.0) fails at
import.

This ports it to v2.

## Changes

1. `agent/pyproject.toml` — `pydantic-ai-slim[ag-ui,openai]>=2.0.0`,
`ag-ui-protocol>=0.1.19`
2. `agent/src/agent.py` — `StateDeps` moved from `pydantic_ai.ag_ui` to
`pydantic_ai.ui`
3. `agent/src/main.py` — serve via `AGUIAdapter.dispatch_request` on a
Starlette route
4. `agent/uv.lock` — relocked (resolves `pydantic-ai-slim` 2.22.0,
`ag-ui-protocol` 0.1.19)

## One fix beyond the mechanical port

The old wiring built the app once around a single shared `StateDeps`
instance. `dispatch_request` mutates `deps.state` with the state the
client sends, so one shared instance lets state leak between threads,
channels and users — which matters more for Channels than it did for a
single browser tab. Each request now gets its own `replace(deps)` copy,
matching the pattern in [Pydantic AI's own AG-UI
examples](https://github.com/pydantic/pydantic-ai/blob/main/examples/pydantic_ai_examples/ag_ui/api/shared_state.py).

<details><summary>Verified end to end</summary>

`uv sync` + a request through the actual ASGI app (model overridden with
`TestModel` so no API call), with a Channels-shaped payload (`threadId`,
`state`, `forwardedProps`):

```
health: 200 {'status': 'ok'}
POST / -> 200 text/event-stream; charset=utf-8
event types: ['RUN_STARTED', 'TOOL_CALL_START', 'TOOL_CALL_END', 'TOOL_CALL_START', 'TOOL_CALL_ARGS',
 'TOOL_CALL_END', ..., 'TOOL_CALL_RESULT', 'STATE_SNAPSHOT', 'TOOL_CALL_RESULT', 'STATE_SNAPSHOT',
 'TEXT_MESSAGE_START', 'TEXT_MESSAGE_CONTENT', ..., 'TEXT_MESSAGE_END', 'RUN_FINISHED']
threadId echoed: slack-C123-thread-1
tool call: get_proverbs
tool call: add_proverbs
tool call: set_proverbs
tool call: get_weather
```

The emitted event set is exactly what `channels-slack`'s `RunRenderer`
subscribes to (`RUN_*`, `TEXT_MESSAGE_*`, `TOOL_CALL_*`), plus
`STATE_SNAPSHOT` which it ignores.

</details>

<details><summary>Note on <code>ag-ui-protocol</code>: 0.1.19, not
0.1.18</summary>

Not required by this port, but worth pinning forward: typed multimodal
input content (`ImageInputContent` &c.) landed in 0.1.15 and the
interrupt lifecycle in 0.1.19. We found that a Pydantic AI install below
0.1.15 rejects an inbound image attachment with a 422 rather than
skipping it — so a Channels gateway forwarding a Slack image needs the
newer floor. We're tracking that on our side.

</details>
2026-08-04 09:02:10 -07:00
David Sanchez d19bd0f81c docs(pydantic-ai): port integration docs and demos to Pydantic AI v2
Agent.to_ag_ui(), AGUIApp and the pydantic_ai.ag_ui module were removed in
Pydantic AI v2. The docs installed pydantic-ai unpinned, so anyone following
the quickstart got 2.22.0 and failed first at dependency resolution
(starlette==0.45.3 conflicts with the >=0.46.2 the ag-ui extra needs) and then
at AttributeError.

- 8 doc pages under showcase/shell-docs .../integrations/pydantic-ai serve the
  agent from a Starlette route via AGUIAdapter.dispatch_request
- StateDeps moves from pydantic_ai.ag_ui to pydantic_ai.ui
- stateful snippets build StateDeps per request; dispatch_request writes the
  client's state into deps.state, so a shared instance leaks state between users
- install commands exact-pin pydantic-ai-slim==2.22.0 and ag-ui-protocol==0.1.19
- examples/canvas/pydantic-ai and examples/showcases/pydantic-ai-todos ported
  and pinned, todos relocked
- skills/copilotkit-integrations reference updated to the same shape

showcase/integrations/pydantic-ai is deliberately untouched; it is tracked
separately.
2026-08-04 10:51:17 -05:00
David Sanchez 4be25161b1 fix(examples): pin pydantic-ai starter deps and build deps per request
Address review on CopilotKit/CopilotKit#6355:

- Pin `pydantic-ai-slim[ag-ui,openai]==2.22.0` and `ag-ui-protocol==0.1.19`,
  matching the starter fleet standard. Open-ended floors would pull a
  breaking major on release, and `docker/Dockerfile.agent:16` runs a bare
  `uv sync` that would silently re-resolve forward.
- Mirror the specifiers in `uv.lock`'s `requires-dist` so `uv sync --frozen`
  at `Dockerfile:42` keeps working. Resolved versions and hashes are
  unchanged; `uv lock` is a no-op.
- Construct `StateDeps(ProverbsState())` per request instead of
  `dataclasses.replace()`. `replace()` is a shallow copy, so the new deps
  point at the same state object; it is only safe today because `StateDeps`
  has one field that the adapter rebinds before every run. Any deps class
  with a second mutable field would silently share it.
2026-08-04 10:31:07 -05:00
Maxim 0b1b37e0af Merge origin/main into feat/reskinnable-demo-keel-skin
Resolves the append-only registry conflict this PR's description predicted:
#6302 (logistics / Meridian) landed first and took the same two lines, so
keel takes the trivial conflict. Both edits are additive, so the resolution
is keep-both in both registries — logistics first (it landed first), keel
second.

Why the merge was necessary rather than optional: GitHub does not create
pull_request workflow runs for a PR whose mergeable state is CONFLICTING,
because it cannot compute the merge ref those events run against. The push
of the review fixes therefore produced ZERO CI runs — verified over 12
minutes of polling the Actions API by head SHA, and corroborated by Actions
being healthy repo-wide at the same time. Not a timing artifact, and not a
path filter: test_e2e-legacy-v1 and auto_merge_showcases both trigger on
pull_request with paths: examples/**, and synchronize is a default activity
type.

Scope of the drift: main is 127 commits ahead of the merge-base, but within
reskinnable-demo it added only logistics' own files (48 skin + 17 API route
+ 2 reskin-skill docs) plus +7 append-only lines across exactly the two
registry files. No globals.css change, no skin-contract.ts change, no
skins-config.ts change — so the shared token vocabulary and the frozen Skin
contract are untouched, and keel's isolation claim still holds against a
four-skin registry.

All four skins are now registered under the same id in both registries
(banking, airline, logistics, keel), which is the invariant the two-registry
split exists to maintain.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-04 13:52:24 +02:00
Maxim c01b637bdc fix(keel): harden report surface, catalog, and remaining chrome
Remaining fixes across the ops-report builder, the a2ui canvas surface,
the catalog, agent wiring, suggestions, and the knowledge/playbooks pages:
correct the report surface output and its catalog registration so the
canvas renders the intended report, and tighten the surrounding chrome so
these entry points behave consistently. Tests cover the report builder and
canvas surface.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-04 04:46:21 +02:00
Maxim 28adff5dbf fix(keel): anchor seeded runs to a per-call now
Seed data used a fixed or module-load timestamp, so seeded run timelines
drifted out of a plausible window as time passed and could not be
reproduced deterministically in tests. This anchors every seeded run
relative to a `now` passed in at seed time, keeping the demo's timeline
coherent whenever it is generated and making the seed testable. A test
pins the relative anchoring.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-04 04:46:13 +02:00
Maxim 5bbe483073 fix(keel): pin date/time formatting and fix in-chat interactivity
Dates and times were formatted with the runtime's ambient locale and
timezone, so server and client rendered different strings and React
hydration mismatched; several call sites also built ad-hoc formatters that
drifted from one another. This centralizes formatting behind pinned
locale/timezone formatters — one per format — so output is stable across
environments. It also fixes the ChatSurface pointer-events boundary so
interactive controls rendered inside the chat transcript (playbook,
approval, and run cards) actually receive clicks. Tests cover the pinned
formatters.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-04 04:46:05 +02:00
Maxim 43cce12c53 fix(keel): guard prototype access on untrusted lookup keys
URL route segments and client-forwarded runtime properties were used
directly as object keys, so a crafted value like "__proto__" or
"constructor" could resolve to an inherited Object.prototype member
instead of a real entry — misrouting a page or silently matching a bogus
user identity. This constrains both lookups to own, real keys so untrusted
input can no longer reach the prototype chain. Tests cover the malicious
key cases for page resolution and user identification.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-04 04:45:55 +02:00
Maxim 576b3c4df8 fix(keel): make retrieval deterministic and citations grounded
Search results depended on the runtime locale for tie-breaking, synonym
expansion chained transitively into unrelated terms, and phrase matching
hit partial tokens, so the same query could return different or wrong
results across environments. Citations could also duplicate and fail to
land on their source document. This pins result ordering to a
locale-independent comparison, bounds synonym expansion to direct
(non-transitive) matches, matches phrases on whole-token boundaries,
deduplicates citations, and makes a citation click land on the correct
document. Tests lock in the deterministic ordering and citation targeting.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-04 04:45:46 +02:00
Maxim 3eb7d33e4f fix(keel): make engine mutations honest and single-path
The data engine could report success for mutations that changed nothing
and exposed multiple divergent write paths, so callers could not trust a
returned status or reason about a gate from one place. This routes every
mutation through one shared commit path, returns values that reflect what
actually happened, and tightens the gates so a no-op or a rejected change
can no longer be misrepresented as applied. Types and the seed-backed data
hook are updated to match, with tests covering the corrected return
contract and persona-scoped access.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-04 04:45:36 +02:00
Maxim a8ab77d05e docs(reskinnable-demo): correct reskin skill's layout, theme, tools, and canvas guidance
The reskin authoring skill taught several patterns that ship a broken skin when
followed literally. Corrected against the debugged logistics skin (the frozen
contract wins on conflict):

- layout: h-screen overflow-hidden (not min-h-screen) + h-full aside, so the nav
  stays pinned and <main> scrolls inside it
- layout: publish --nw-nav-inset-left/right with cleanup, so the floating skin
  selector never docks on the nav and the inset does not leak between skins
- layout: document the meta-utility strip (Reset/ThemeToggle/Help) as
  skin-authored chrome, with the reset-route gating coupling
- theme: document --nw-dark-capable as the dark-mode opt-in + a .dark .theme-<id>
  example
- tools: every useComponent/useFrontendTool/useHumanInTheLoop registration must
  close with a deps array, or the closure captures empty pre-fetch data forever
- tools: a parameterized useComponent render receives the schema output directly,
  not { args }
- a2ui: a CanvasSurface must be fed by a server defineTool, never a client
  useFrontendTool
- contract: nav is display-only; resolvePage is the sole segment validator
- fix airline useData contradiction; NPE-safe no-data tools shape; skin.tsx
  dangling-import note

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 02:25:01 +02:00
Maxim 675a5d8568 fix(reskinnable-demo): pin the logistics nav to the viewport
The shell root used `min-h-screen`, which is a MINIMUM: on a page taller
than the viewport the container grew with the content, so the whole
document scrolled and the sidebar scrolled away with it. It also left
`<main>`'s own `overflow-y-auto` inert, because an unbounded parent gives
it no height to overflow against.

Switch the root to `h-screen overflow-hidden` and give the aside `h-full`,
mirroring banking's layout. The shell is now exactly one viewport tall, the
nav stays pinned, and `<main>` scrolls internally.

Measured on /logistics/lanes at a 700px viewport: document scrollHeight
700 (page no longer scrolls), aside top 0 / bottom 700, main scrollHeight
1472 vs clientHeight 700, and the aside stays at top 0 after scrolling
main by 700px.
2026-08-04 01:56:16 +02:00
Maxim deffa6c3d6 feat(reskinnable-demo): add meta-utility strip and dark mode to logistics skin
Port banking's sidebar-footer utility controls into Meridian: a Reset
(presenter-gated), the shared ThemeToggle, and a copilot Help shortcut, placed
directly above the existing "On duty" planner switcher. Give the skin a warm-
graphite dark palette (--nw-dark-capable + a .dark .theme-logistics block) so
the theme toggle is a live control. Port useAskCopilot into the skin (no cross-
skin import) and widen the reset endpoint's gate to allow presenter/booth
deployments (presenterResetEnabled OR non-production).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:16 +02:00
Maxim cf16befb11 fix(reskinnable-demo): re-register logistics gen-UI and HITL tools when data loads
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:16 +02:00
Maxim e2bd6cec43 fix(reskinnable-demo): server-side brief tool, decision records, honest greeting
Applies the final whole-branch review wave for the logistics skin.

- renderBrief: move from a client useFrontendTool to a server defineTool on the
  BuiltInAgent (mirrors banking's render_report). A client frontend-tool result
  never yields an in-stream TOOL_CALL_RESULT, so the a2ui middleware never
  emitted the a2ui-surface activity and the brief canvas stayed blank. Tool name
  kept exactly "renderBrief". build-brief-ops + catalog/definitions confirmed
  server-safe (plain Zod, no React/.tsx), so agent.ts stays server-safe.
- createDecisionRecord: implement the missing tool (globally registered) to log
  a decision NOT executed through commitMitigation, wiring the previously-dead
  fileDecision + POST /decisions path. Harden the route: require/resolve
  plannerId, derive decidedBy/role server-side (never from body), 404 unknown
  shipmentId, clamp costUsd. Client forwards plannerId only. Prompt + toolLabels
  updated. Adds a decisions route test (planner-derived identity vs body decoys,
  404, 400).
- greeting: drop the wrong "six lanes / three shipments" figures (seed has 10
  lanes, 4 exception shipments); quantitative claims bind to data, not prose.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:15 +02:00
Maxim 8fef990752 feat(reskinnable-demo): register the logistics skin in both registries
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:15 +02:00
Maxim 74f187db5d feat(reskinnable-demo): add logistics agent, suggestions, and OGUI design brief
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:15 +02:00
Maxim 5db4703b94 feat(reskinnable-demo): register logistics gen-UI, HITL, and brief tools
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:15 +02:00
Maxim 7413152a4a feat(reskinnable-demo): add logistics provider stack and per-planner identity
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:14 +02:00
Maxim 60ad3d353a feat(reskinnable-demo): expose logistics data to OGUI sandbox via projected DTOs 2026-08-04 01:56:14 +02:00
Maxim 244763f93a feat(reskinnable-demo): add logistics a2ui decision-brief canvas surface
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:14 +02:00
Maxim 34b4703341 feat(reskinnable-demo): add deterministic decision-brief op builder
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:13 +02:00
Maxim 6c19d49e31 feat(reskinnable-demo): add logistics a2ui catalog bound to live ledger data
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:13 +02:00
Maxim 16937b460d feat(reskinnable-demo): add logistics pages 2026-08-04 01:56:13 +02:00
Maxim d176d3b5ba fix(reskinnable-demo): give TradeoffTable an empty state
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:13 +02:00
Maxim 28b49eefb2 feat(reskinnable-demo): add logistics presentational components
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:12 +02:00
Maxim bc51e57e3f feat(reskinnable-demo): add logistics REST client hook with revalidation bus 2026-08-04 01:56:12 +02:00
Maxim 563665e03a feat(reskinnable-demo): add logistics identity, theme, nav, and layout chrome
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:12 +02:00
Maxim e25ff08c76 fix(reskinnable-demo): allow-list PATCH fields so pricing inputs cannot bypass the authority gate
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:12 +02:00
Maxim bcdaa51f55 feat(reskinnable-demo): add logistics REST routes with server-enforced authority gate
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:11 +02:00
Maxim b540b5db27 fix(reskinnable-demo): add same-destination alternate lanes so reroute is offerable
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:11 +02:00
Maxim dfe1ef259d feat(reskinnable-demo): add logistics role-based authority gate 2026-08-04 01:56:11 +02:00
Maxim c670247de6 feat(reskinnable-demo): compute logistics mitigation options 2026-08-04 01:56:10 +02:00
Maxim 8c7f983125 feat(reskinnable-demo): add logistics escalation code catalogue 2026-08-04 01:56:10 +02:00
Maxim 7e70670fc7 feat(reskinnable-demo): add logistics domain types, seed, and store
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 01:56:10 +02:00
David Sanchez a55e499f1a Port the Pydantic AI example to Pydantic AI v2
`Agent.to_ag_ui()`, `AGUIApp`, and the `pydantic_ai.ag_ui` shim were removed in
Pydantic AI v2, so the example only ran on v1.

- pin `pydantic-ai-slim[ag-ui,openai]>=2.0.0` and `ag-ui-protocol>=0.1.19`
- import `StateDeps` from `pydantic_ai.ui`
- serve via `AGUIAdapter.dispatch_request` on a Starlette route
- give each request its own `deps` copy so state no longer leaks between threads
2026-08-03 17:08:07 -05:00
Mark 36f2972150 fix(a2ui-renderer): bump @a2ui/web_core to 0.10.4 for the openUrl XSS advisory
GHSA-72qq-p3r5-f7wq (CVSS 9.3). web_core <= 0.10.1 passed an agent-supplied
`openUrl` argument straight to `window.open()` with no scheme allowlist, so a
Button whose `functionCall` named a `javascript:` URI executed arbitrary script
in the host origin when a user clicked it. The Basic Catalog is the default, so
no non-default configuration was required to be exposed.

We pinned 0.9.0 exactly, as a runtime dependency of two published packages
(@copilotkit/a2ui-renderer, @copilotkit/vue) and transitively of
@copilotkit/react-core and @copilotkit/angular, so downstream users could not
upgrade out of it on their own. 0.10.4 keeps the ./v0_9 and
./v0_9/basic_catalog entrypoints we import; the only symbol dropped from v0_9
is FrameworkSignal, which we never referenced.

Add regression tests over both renderers that reach the sink independently
(React and Lit). They assert that javascript: and data: URIs never reach
window.open, that https URLs still open with noopener,noreferrer, and that a
blocked scheme leaves the surface mounted rather than escaping into the click
handler. Verified they fail against 0.9.0 and pass against 0.10.4.
2026-08-03 18:43:00 +00:00
Benjamin Taylor b9ea51e819 docs(channels): name the short-scoped Slack token as its own silent failure
Slack installs an app when it creates one from a manifest, and that install
grants two scopes: channels:history and chat:write. The manifest's declared
scopes reach the app's configuration but not the grant, which is what Slack's
"you've changed the permission scopes" banner reports. One Reinstall to
Workspace raises the grant to the full set. Measured against a real workspace.

A token copied before that reinstall passes every check we have. auth.test
succeeds, so attaching stores it and reports the adapter healthy. chat:write is
present, so the bot can post. app_mentions:read is absent, so Slack never
delivers app_mention and no handler ever runs — an online, structurally deaf
Channel.

The channels skill already documents an "online but silent" failure caused by a
version disagreement, which logs a rejected delivery. This one logs nothing at
all, because Slack never sends anything to reject, so it gets its own section
next to it and the verify checklist now says "reinstalled" rather than
"installed". Intelligence refuses a short token at paste time now, so the
section also says to read that error as this problem caught early.

examples/slack said "Install to Workspace → copy the xoxb- bot token", which is
both the wrong button label and the wrong order. Its manifest declares even more
scopes than the managed one, so the gap there is larger.
2026-08-03 10:37:00 -05:00
Benjamin Taylor 23671e09f1 chore(examples): move the starters onto channels 0.6.1
0.6.1 carries one change: createChannel's clone check now warns instead of
throwing when `clone()` drops subclass state (#6322). On 0.6.0 a starter
hosting a Channel through @ag-ui/langgraph refuses every turn, because
LangGraphAgent's clone() leaves `emittedToolCallStartIds` and
`eventsStreamActive` behind -- both per-run scratch that is re-initialized
before anything reads it, so dropping them was never the problem. The
starters are the surface where that failure is user-visible, so they should
not sit on the release that has it.

No @copilotkit/* bump rides along, and none is needed. The fix lives entirely
in @copilotkit/channels-core, and every path to it is a caret range:
runtime@1.65.0 asks for channels-core ^0.6.0, and channels-intelligence@0.6.0
(which runtime does pin exactly) asks for ^0.6.0 as well. Both resolve onto
the same 0.6.1, so the runtime's channel path picks up the fix without a new
runtime release. Verified from the regenerated locks rather than assumed:
each of the 15 resolves exactly one channels-core, at 0.6.1, with no second
copy nested under runtime.

Lockfiles were regenerated with --package-lock-only; the diffs contain
@copilotkit/channels* lines and nothing else, so no unrelated dependency
floated forward in the process.
2026-08-03 09:19:47 -05:00
Benjamin Taylor 475002e49d chore(examples): move the starters off the canary onto stable
The canary pin existed for one reason: createChannel's identifyUser was absent
from stable, and the pin carried a note that it must not reach users as-is.
Stable has caught up -- @copilotkit/* 1.65.0 and @copilotkit/channels 0.6.0 --
so the workaround goes.

This is not only hygiene. The runtime validates each delivery with an exact
field set, so a client and a server that disagree fail in BOTH directions: a
client expecting a field the server omits, and equally a client receiving one it
does not expect. Now that every Intelligence environment sends the prepared
turn's messageRef, pinning back to an older stable would break exactly as hard
as staying on a canary would have before. 0.6.0 expects it, which is what makes
it the correct pin rather than merely a newer one.

Verified before committing: channels-intelligence@0.6.0 requires messageRef on a
text turn, channels-core@0.6.0 carries identifyUser, and channels@0.6.0 pins its
subpackages exactly rather than by range, so there is no internal skew. The
reference starter installs, typechecks its channel host, and builds. Its one
remaining tsc error is a pre-existing recharts type mismatch, untouched here.

langgraph-fastapi is included: it does not ship a host, but this branch pinned it
to the canary, so it cannot be left there.
2026-08-02 20:05:55 -05:00
Maxim 731c23d595 feat(reskinnable-demo): wire and register the keel skin
The agent (grounding rule first and most emphatic, temperature 0 for
deterministic routing, two server tools), the OGUI design brief, the
suggestion pills, tools.tsx, and skin.tsx -- registered in both
registries under the id 'keel'. defaultSkinId is unchanged.

tools.tsx is the join point where corpus, run engine, and chat components
meet. Two details are load-bearing: agent-context readables memoize on
summaryKey (never on runs) so the 900ms ticker cannot thrash the agent's
context, and showSources takes only (docId, sectionId) pairs and resolves
the ref/heading/snippet from the real corpus client-side -- so a citation
the model invents fails to resolve and is dropped rather than rendering
as a convincing fake.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-03 01:44:47 +02:00
Maxim b3404d490b feat(reskinnable-demo): add keel chat components and canvas surface
The five in-chat cards plus the a2ui catalog, ops-report builder, canvas
surface, and OGUI sandbox functions.

Every interactive card carries pointer-events-auto: CopilotKit paints
useComponent renders with pointer-events:none, so without it the cards
render perfectly and are completely unclickable.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-03 01:44:46 +02:00
Maxim 4e58f25b76 feat(reskinnable-demo): add keel chrome, identity, and pages
App-shell chrome with the persona switcher (which gates what is
approvable), the RuntimeProviders/useRuntimeProperties/identifyUser triad
for per-persona memory scoping, and the six pages. Two routes are
parameterized -- knowledge/<docId> and runs/<runId> -- so resolvePage
destructures the segment array rather than using a flat lookup. An
unknown id renders a not-found body, never a 404: the route is
structurally valid and a citation deep-link must not break on a rename.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-03 01:44:46 +02:00
Maxim 8eb9fcbb2b feat(reskinnable-demo): add keel playbooks, run engine, and ticker
Four playbooks whose every step carries a policyRef into the corpus, four
seeded runs, and a pure reducer driving them: steps advance on a 900ms
ticker and halt at approval gates keyed to the current persona's role.

seed.test.ts asserts all 23 policyRefs resolve to a real doc + section.
That invariant is what fuses the knowledge and process substrates -- a
dangling ref means an approval card cites a document that does not exist.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-03 01:44:45 +02:00
Maxim 95095bea54 feat(reskinnable-demo): add keel knowledge corpus and deterministic retrieval
Nine Harbor Point Health policy documents across three spaces, and a
pure lexical scorer over them (heading 3 / title 2 / body 1, stopword +
synonym expansion, total-order tie-break). Server-safe: imported by both
the agent's search tool and the client Knowledge pages. Determinism is a
requirement, not a nicety -- the same question returns the same citations
every run, which is what makes the demo reproducible.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-03 01:44:45 +02:00
Maxim e3efa79d0a feat(reskinnable-demo): add keel skin contracts, theme, and shared atoms
The frozen interfaces every other part of the keel skin compiles against:
knowledge + process types, the four demo personas, the role context, the
two shared presentational atoms, plus brand identity, nav, and the
.theme-keel token block (deep pine on warm neutrals, tightest radius in
the app, --brand-violet carrying the amber awaiting-approval accent).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkMi54kqfNwAoUB3M3CBp3
2026-08-03 01:44:44 +02:00
Benjamin Taylor e1f88ebc12 refactor(examples): split the Channel out of the host, drop its HTTP server
Addresses review feedback that channel-host.mts is doing too much.

Two changes, both scoped to the starters:

1. Channel construction moves to a new `channels.mts` beside `agent.ts` —
   name resolution, `createChannel`, and the `onMessage` handler. That is
   also the file to edit to customise a Channel (commands, reactions,
   onMention), which previously meant editing the host.

   The per-framework agent import moves with it, so `channel-host.mts` is now
   byte-identical in all 15 starters rather than 13 + 2.

2. The host no longer stands up an HTTP server. Its comment claimed the
   server was what "keeps the lifecycle-owning process alive"; that is false.
   An open undici WebSocket holds the event loop on its own — verified with a
   standalone repro where a process with no HTTP server and no timers of its
   own stayed up indefinitely on a single WebSocket connection. The server was
   therefore serving a second, uncalled copy of the runtime API on port 8300
   for no reason.

   With the server gone, `createCopilotNodeListener` was the wrong factory —
   it builds a request listener purely for its activation side effect. The
   host now uses `createCopilotRuntimeHandler` + `ready()`, which is the
   documented long-running-host pattern (see fetch-handler.ts). This also
   drops `node:http`, `basePath`, and the CHANNEL_PORT env var.

Behaviour is unchanged: same Channel, same agent, same status reporting, and
the same non-zero exit on activation failure.

Verified: 14/14 starters with a `typecheck:channel` script pass; mastra has no
such script by design (166dc94691) and its pre-existing Mastra `Memory` type
error is byte-identical before and after. `npm run channel` exercised on both
failure paths — missing channels.json, and missing INTELLIGENCE_API_KEY with a
name supplied — confirming the new `./channels.mjs` specifier resolves under
tsx as well as tsc. `parity:check` output identical to the pre-change baseline.

Refs #6315

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 12:41:46 -05:00
Benjamin Taylor b13fa63cb5 docs(examples): call them Channels, not managed Channels
The starter READMEs, channel-host.mts headers, and the host's own log
lines described the feature as a "managed Channel". Managed is an
implementation detail of how Intelligence attaches the provider edge,
not part of the name — the product surface is just a Channel.

Renames every occurrence across the 15 starters. Section headings become
"Running a Channel", the host header becomes "Channel host", and the
setup_required log reads "no provider is attached yet".

"managed Intelligence" in the .env.example comments is left alone: that
one distinguishes hosted Intelligence from a self-hosted deployment and
is unrelated to Channel naming.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 12:17:13 -05:00