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>
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
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
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
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>
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>
The smoke-starter CI job builds the starter in Docker, where the Dockerfiles copy
only package.json before running npm install; .npmrc arrived later with the rest of
the source, so the install ran without legacy-peer-deps and failed with ERESOLVE.
A local npm install passed precisely because it was not in Docker.
Verified by building docker/Dockerfile.app --target builder locally: npm install and
npm run build both succeed.
Verification skipped on commit: the pre-commit hook runs the full repo suite, which
is unrelated to two Dockerfile COPY lines.
Every starter's .env.example documents INTELLIGENCE_API_URL and
INTELLIGENCE_GATEWAY_WS_URL under its Threads-enablement block, with a
localhost/local-ws value. Uncommenting (or, in llamaindex/mcp-apps,
leaving as shipped) that block is the documented way to enable Threads,
but it silently points the channel host at a local Intelligence that is
usually not running — so `ready()` burns its 30s timeout and the host
exits 1 with no indication why.
Adds a short comment next to those two vars in each of the 14 starters
that ship an .env.example (mastra has none tracked in this branch) noting
they're for a self-hosted or local Intelligence deployment only and
should be left unset for managed Intelligence. No values changed, nothing
uncommented.
Only langgraph-python's README described channel-host.mts even though
all 15 starters ship it, its tsconfig.channel.json, and its channel /
typecheck:channel scripts. Adds the equivalent "Running a managed
Channel" section to the other 14 (adk, agno, claude-sdk-python,
claude-sdk-typescript, crewai-flows, ms-agent-framework-dotnet,
ms-agent-framework-python, pydantic-ai, strands-python, langgraph-js,
llamaindex, mastra, mcp-apps, a2a-middleware), adapted to each file's
existing heading level and tone, and lists channel / typecheck:channel
alongside their other scripts (mastra has no typecheck:channel — see the
prior commit).
Each section states that INTELLIGENCE_API_KEY and a declared Channel in
.copilotkit/channels.json are required, that INTELLIGENCE_CHANNEL_NAME
disambiguates multiple declared Channels, and is explicit that starting
the host does not prove the provider app is installed or reachable.
channel-host.mts imports src/agent.ts, which transitively pulls in
src/mastra/** — and that tree has a pre-existing "Type 'Memory' is not
assignable to type 'MastraMemory'" error unrelated to the Channel host.
TypeScript follows imports regardless of tsconfig `include`, so no
scoping fix exists; a typecheck script that can never pass just trains
people to ignore it.
Removes typecheck:channel from mastra/package.json only — the channel
script and tsconfig.channel.json (still used for module resolution) are
unchanged — and leaves a comment in tsconfig.channel.json explaining why
this starter alone has no such script. Every other starter keeps
typecheck:channel.
ready() resolves once every declared Channel reaches `online` or
`setup_required` — the latter means the Channel is declared but has no
managed provider attached yet, a normal waiting state rather than a
failure. The host used to log an unconditional "holding managed Channel"
success line in both cases, so a developer who has not finished
installing the provider app would see success, message the bot, and get
silence with no diagnostic.
The host now calls status() after ready() and reports the truth per
Channel: "is online" when actually online, or "declared but no managed
provider is attached yet" (pointing at `copilotkit channels status`)
when only setup_required. A ready() rejection still exits non-zero,
unchanged.
Propagated to all 15 starters (byte-identical apart from the
mcp-apps/a2a-middleware import line, as before), and updated the
langgraph-python README section describing the new log output.
The scaffolded host now ships in the starter, so the skill no longer needs to
teach creating it -- only customising it (tools, context, commands, and
per-provider extras like defaultSlackTools/defaultSlackContext). Hand-wiring
guidance stays, clearly marked, for projects that did not come from a starter.
The langgraph-python README documents the new `npm run channel` script: what
holding a managed Channel proves (the runtime activated and the gateway
accepted it) and what it doesn't (the provider app is installed, invited, or
reachable).
Note: skills/copilotkit-channels/SKILL.md did not exist on this branch or on
main -- it lives on a separate, unmerged branch. Seeded it from that branch's
content before rescoping; that branch's version will need manual
reconciliation with this one whichever merges to main second.