Two new suites, each written so it would have failed against the previous code
rather than merely describing the new behaviour:
- derived-spend: policies[].spent equals the sum of approved charges, excludes
pending/flagged (so the over-limit check cannot double-count the charge it is
gating), and agrees between policies() and findPolicy(). Plus the properties
the demo depends on — at least three distinct months (the condition whose
absence made the trend chart fabricate), AWS $15,000 still deriving
over-limit for the teach-mode pill, exactly one Delta charge, and a team and
category on every ledger row.
- charges-data: parseSort and parseTop reject the values that previously slipped
through as valid, and toChargeRow's over-limit projection only applies to
pending charges.
Confirmed red against the old code: parseSort("banana") returned "banana",
parseTop("-5") returned -5, the seed spanned two months, no row carried a team,
and spent was a stored field.
The four existing fixtures move from ExpenseRole to PolicyType for policy
`type`, following the team/policy split.
The regex that decides whether a transaction note gets a red-alert prefix was
anchored on the left only, so `report` matched inside "reporter" and "quarterly
report" and `disput` inside "disputation". A note reading "attached to the
quarterly report" was served a fraud marker.
Anchored on both sides and switched from stems to whole words, verified against
the four phrasings that previously false-positived.
Four defects in the report's charts, all reported by review and all
reproduced in the running app before fixing.
- SpendingTrendChart substituted a hard-coded [3200, 4100, 3600, 5200, 4800,
6400] Jan-Jun series whenever fewer than three months were present. Intended
as an empty state, it was the DEFAULT path: the seeded ledger spanned two
months, so the report's "Spend over time" always showed six invented numbers
— roughly 20x smaller than the total printed directly above them — under a
card whose own contract says every number comes from the live ledger. It now
charts whatever months exist, with a real empty state at zero.
- SpendBreakdownChart's docstring said the report must use SpendByTeamBars
instead, "because an attached invoice can push one team to ~96% and a donut
cannot survive that", while the report rendered the donut anyway. The warning
was real but its cause was the thin ledger, not the chart: against the old
$137,000 base a $900,000 invoice took one slice to 89%. Against the real
~$533,000 base the same invoice reaches 73%, and 89% would need ~$2.8M.
Robust because the data is real, not because a floor was added to the arc.
- augmentForReport built its synthetic transactions behind an `as Transaction`
cast that was hiding a real hole: `policyId` came from an `?.id` lookup, so it
could be undefined where the field is a required string. The cast is gone and
the compiler checks it. Additions also now resolve their model-authored team
to a policy envelope through `policyForTeam` rather than comparing a team name
to a policy name, with one "Unattributed" segment for unmappable names.
- A comment inside TopChargesChart claimed document-sourced charges "have no
policyId" — three lines above the code that gives them one.
The donut column is relabelled "Spend by policy", which is what it reads.
The skin held three disagreeing answers to "what did we spend": a 45-row
Charges fixture ($632,806), a 4-row seeded ledger ($30,089 across two
months), and static policy totals ($137,000). Each surface read a different
one, so they drifted silently — and because the ledger spanned only two
months, the report's trend chart fell back to a hard-coded series and showed
invented figures under a card that promises live numbers.
Now there is one ledger. The 45 charges live in seed.json as real
transactions across Apr/May/Jun, and the Charges page reads them over REST
like every other surface.
- Splits team from policy. A charge belongs to one of seven org teams; a
policy is one of three budget envelopes (Technology / Go-to-Market / G&A)
and several teams share one. These were a single `ExpenseRole` enum, which
is why the two axes read as one thing and why covering every team meant
either a seven-slice donut or discarding real charges. `ExpenseRole` still
types a member's own team; `PolicyType` types the envelopes, joined by
`policyForTeam`.
- Derives `policies[].spent` from approved charges on every read, so it can
no longer disagree with the charts. It also now MOVES: approving a charge
previously left `spent` untouched, so the budget never reflected the
approval and the over-limit gate kept comparing against a stale figure.
- Makes over-limit derived-only. A charge no longer stores "over-limit"; the
Charges table resolves the badge through `withOverLimit`, the same rule the
report uses, so the two cannot disagree.
- Validates the `?sort=` and `?top=` params. `?sort=banana` used to be cast
straight to a SortKey and lit the control's "active" tint while the table
silently sorted by the default; `?top=-5` reached `slice(0, -5)` and dropped
the LAST five rows, inverting top-N.
The scripted demo is unchanged by construction: the four demo-load-bearing
transactions survive byte-identical, over-limit is still exactly three charges
totalling $30,000, AWS $15,000 still derives over-limit for the teach-mode
pill, and Delta Airlines is still the only Delta charge (the fixture's near
-duplicate "Delta Air Lines" became United Airlines).
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.
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.
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>
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.
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.
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
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
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
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
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
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
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
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>
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.
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>
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>
`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
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.
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.
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.
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.
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
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
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
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