Adds a minimal, runnable test rig for the channels HITL path that nothing in
the repo previously exercised: `onInterrupt` + `thread.resume()`, driven by a
real LangGraph graph that suspends in its checkpointer.
Channels has two unrelated HITL models, and they suspend in different places:
thread.awaitChoice() a channel-side tool handler blocks; the agent run
stays open; the waiter is an in-memory Map
onInterrupt/resume the AGENT's graph suspends; the run ENDS; the click
starts a new run carrying the resume value
Only the first had coverage. `examples/integrations/langgraph-python` has zero
`interrupt()` calls, and `examples/slack/e2e/restart-recovery.ts` — written for
this exact path — no longer compiles (it imports `@copilotkit/slack` plus
`appComponents`/`appHitl`, none of which still exist).
What's here:
- `agent-py/` — a LangGraph agent whose `create_thing` tool calls `interrupt()`
and writes nothing, so the interrupt fires deterministically with no
Linear/Notion credentials. Served over AG-UI, so the channel dials it
directly and `runtime.ts` is not involved.
- `agent-py/probe.py` — drives the agent alone (no channel, no Slack).
- `e2e/route-b-interrupt.ts` — drives agent + channel with a `FakeAdapter`
standing in for the platform, so a failure is unambiguously channels' fault.
Asserts the resume leaves in the legacy `forwardedProps.command.resume` shape.
- `app/route-b.ts` — the Slack-facing probe, plus a boot-time preflight that
rejects an unreachable/mangled `AGENT_URL` with a specific diagnosis instead
of a bare `fetch failed` on the first message.
Two wire facts worth recording, since both fail silently:
- `on_interrupt` is not a label you choose. It's the AG-UI custom event name
LangGraph's adapter emits, and adapters default `interruptEventNames` to
exactly that. Rename either side and the graph stays suspended forever.
- the interrupt payload crosses as a JSON *string*. All four real adapters
parse it before the handler runs; `FakeAdapter` does not, so handlers that
assume an object work in Slack and break headless. Both handlers here
normalize defensively.
Nothing in `packages/` changes; this is examples-only.
The previous comment presented the effect-based ref sync as merely avoiding
the react-hooks/refs lint rule. It is a trade-off: the render reads
cardsRef.current, and an effect writes after commit, so a card mutation
landing while the PIN card is open can leave the picker one render behind.
Records why that is accepted and what fixing it properly would require.
Replays three generative-UI changes from the banking showcase (#6259) into
the reskinnable demo's banking skin:
- setCardPin keys its collapsed render on the tool result and remembers
answered calls in a module-scope map, so a resolved PIN change survives
re-render. Drops the [cards] dependency in favour of a ref, which is what
stopped the tool tearing down mid-flight.
- showCharges becomes human-in-the-loop: it asks before moving the user's
whole screen, and routes through the skin-scoped base prefix.
- Notes about reported charges are prefixed with a red alert emoji.
Replays the report chart curation from the banking showcase (#6259, #6263)
into the reskinnable demo's banking skin. The three columns become spend
breakdown, spend over time and largest charges — the last of which answers
"which line items drive the total", a question a three-team aggregate
cannot. Additions carry a policyId so their bars colour by owning team.
Ports the brand-tint treatment for explicitly-set filter selects from the
banking showcase charges page (#6259) into the reskinnable demo's banking
skin, so an agent-applied filter reads as deliberate.
Ports the seed rebalance from the banking showcase (#6259) into the
reskinnable demo's banking skin so the report's spend-share donut shows a
meaningful split rather than three near-equal slices.
Ports the ranked largest-charges chart from the banking showcase (#6263)
into the reskinnable demo's banking skin. The report-card wiring that
consumes it lands separately.
Ports the red-alert-emoji instruction from the banking showcase seeded
memories (#6259) into the reskinnable demo's banking skin, so recalled
memory drives the same note formatting.
Ports the "format prose the same way every time" instruction block from
the banking showcase agent prompt (#6259) into the reskinnable demo's
banking skin, where the prompt lives in agent.ts rather than the route.
Ports the confirm-before-navigate chat card from the banking showcase
(#6259) into the reskinnable demo's banking skin. The showCharges
human-in-the-loop wiring that consumes it lands separately.
Points this app at the stack's second seeded organization
(haus-von-haskell) instead of the one banking uses (casa-de-erlang). Org is
resolved from the authenticated cpk key, and seed.sql already provisions three
orgs for exactly this, so it is a key swap with no backend change.
Ports and orgs isolate on independent axes and the second one matters: ports are
a local convention that copying banking's .env over this one silently undoes,
whereas the org key still holds when someone does. Verified against a freshly
seeded stack — writing under one key is invisible to the other even for an
identical user id, so neither demo can read or delete the other's memories.
Also corrects an inherited claim. banking's .env.example warns that non-seeded
ids 403; measured, they do not — GET/POST /api/memories returns 200/201 for an
unseeded id and for a nonsense one, the scope being created on demand. That
mattered because DEMO_DEFAULT_USER_ID is absent from seed.sql, so the warning
implied the unpinned interactive config it recommends was broken. It is not.
This app and the sibling banking demo vendor the same durable-memory stack with
identical seeded ids and api key, so which backend a process attached to was
invisible — a `pnpm dev` pointed at the neighbour's live stack shared its memory
buckets silently, and the presenter reset could mutate the other demo's state.
Surface the target so a human notices:
- Runtime route logs the resolved Intelligence API URL once at startup when
memory is enabled ("[reskinnable-demo] Intelligence: <url> (memory enabled)").
Never logs the api key or license token.
- The destructive presenter reset now names its target: it logs a warning with
the apiUrl and the exact user ids before forgetting, and includes the resolved
apiUrl in both the success and error response bodies so the caller can see
which backend was mutated. Behaviour is otherwise unchanged (same ids forgotten,
same PRESENTER_RESET_ENABLED gate, no confirmation prompt); only observability
improves. route.test.ts updated to assert the new apiUrl field, still exact.
reskinnable-demo was cloned from examples/showcases/banking and vendors an
identical Intelligence (durable-memory) docker stack — same seeded ids
(jordan-beamson / morgan-fluxx / northwind-demo-user), same INTELLIGENCE_API_KEY
and org. Both apps identified their backend purely by address, and both pointed
at http://localhost:7050. So with banking's stack already up, a bare `pnpm dev`
here (which, unlike run-demo.sh, checks no port bind) would silently attach to
banking's backend and read/write the SAME memory buckets — worst case, the
presenter reset button forgetting the neighbour demo's memories.
Shift every published host port by +200 (705x/715x -> 725x/727x) in lockstep
across compose, env, scripts, e2e config and tests so attaching to the wrong
stack is impossible by accident:
app-api 7050 -> 7250
gateway 7053 -> 7253
postgres 7156 -> 7256
redis 7158 -> 7258
minio API 7160 -> 7260
minio console 7161 -> 7261
bundled TEI 7167 -> 7267
The native Metal TEI on :7067 is left shared ON PURPOSE: it holds no demo state
(buckets live in the now-isolated postgres/redis), the same TEI version + model
yields byte-identical embeddings, and run-demo.sh reuses it when healthy rather
than forcing a second ~20x-slower model load. A comment records why.
Creating a Node listener or an Express handler now STARTS activation of the
runtime's declared managed Channels, so `channels.ready()` becomes
await-and-observe instead of the thing you must remember to call. A declared
Channel connects because it was declared.
The failure mode this removes: forget `ready()` and you get a process that
serves HTTP, looks healthy, and is silently disconnected with zero output.
Auto-start's worst case is an activation error in the logs.
The generic Fetch handler stays LAZY — it is the serverless/edge entry point,
where isolates freeze and recycle per request and separate cold starts would
mint competing listeners for the same Channel. `createCopilotHonoHandler` stays
lazy for the same reason: it is our Next.js App Router surface in practice
(every `examples/showcases/*` route handler builds one at module scope), and its
TSDoc now says so loudly. `activateChannels: false` remains the opt-out that
opens no socket.
Consequence for host code: the shutdown-handler boundary moves earlier. Signal
handlers must be registered before the listener is CREATED, not merely before
`ready()` — otherwise a Ctrl-C during the connect window hits Node's default
handler and leaks a live gateway session. The slack and teams examples and the
docs snippets are restructured accordingly.
Also migrates the seven channel-package README quickstarts off the generic
handler (a request handler a socket-mode bot constructs and never serves) onto
the Node listener, so they inherit auto-start and agree with the docs site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Completes the previous commit, whose wiring was left out of it by mistake.
createChannel applies sanitizeAgentEventStream at the agentFactory seam, with
sanitizeAgentEvents: false to opt out; HttpAgent is re-exported from
@copilotkit/channels so the examples need no @ag-ui/client dependency; the
Slack + Teams examples and READMEs now wire a plain HttpAgent; and
SanitizingHttpAgent is deprecated (unchanged) in both adapter packages.
Also swaps a stray pair of raw control bytes in the protobuf test fixture for
escapes, so git sees the test file as text.
The report's third column was budget-usage bars: the same three team totals the
donut beside it already showed, redrawn with limits added. Two charts, one fact.
It now ranks the largest individual charges, which changes the unit of analysis
from team to transaction. A team aggregate cannot distinguish one $15,000
charge from thirty $500 ones, so this is information the donut genuinely cannot
carry. Bars stay coloured by owning team, so a row still ties back to its slice
without the two charts duplicating each other.
Budget-vs-limit is not lost: it remains the "Over policy limit" KPI above and
the "Needs a decision" rows below.
Invoice-derived line items now carry the policy id of the team they belong to,
so they colour like any other charge. Without it they fell back to a generic
swatch and a Marketing charge did not match Marketing's slice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Points the demo runner, smoke scripts and env template at the skin-scoped
routes and relocated modules, and gives the vendored Intelligence stack its own
compose project name.
That last one matters: cloned from banking, this app inherited the same project
name, so both stacks resolved to one Docker project sharing containers and
named volumes — a compose down in either destroyed the other's memory. The
smoke scripts also still posted to /agent/default/run, which no longer exists
now that agents are keyed per skin; nothing in the toolchain reads those files,
so no build or test could have caught it.
Describes the app that exists: shell versus skins, the Skin contract field by
field, the client/server boundary that keeps a skin's agent out of the browser
bundle, per-skin server identity, and the theming split. Adds a repo-local
authoring skill that walks someone through creating a skin and registering it
in both registries.
The inherited docs described a single-skin banking demo, which was actively
misleading once a second skin shipped, and carried claims that were false here
— that OGUI can only render inline in the chat (a published-SDK limitation
that does not apply on workspace packages) and that the airline skin omits
every optional contract slot when it ships nine tool labels.
Retargets the e2e suite onto skin-scoped routes and covers the app's headline
feature for the first time: the index redirect, each skin rendering its own
chrome, an unknown skin 404ing, and switching in both directions.
Those assertions check content only a mounted skin can produce. An earlier
draft matched on brand text, which the shell's skin selector renders on every
page for every skin — so it passed against a deliberately broken airline. Each
assertion here was verified by breaking the app and watching it fail.
ogui-routing is marked fixme: it clicks seven suggestion pills, six of which
were dropped from the registered set before this change, so it has been
failing on main unnoticed — nothing runs this suite in CI.
/[skin] resolves a skin from the URL and mounts it keyed by its id, so
switching fully remounts into a fresh world; / redirects to the default skin.
The runtime route registers one agent per skin and resolves end-user identity
through that skin's resolver, falling back to the default skin for app-level
routes such as the inspector's memory endpoints.
globals.css now defines only the design-token vocabulary plus a brand-neutral
default set; each skin supplies its own values through a .theme-<id> block, so
a reskin is a pure value swap and the shared chrome styles itself with
semantic utilities.
A second skin — identity, theme, app-shell, three pages, ten presentational
components, frontend tools, a2ui catalog, suggestions and an OGUI design
brief — sharing nothing with banking beyond the contract and a few primitives.
It backs its data with plain client state rather than a REST API and
contributes no identity resolver, so the contract is exercised across both
substrates. A contract that only ever hosted one kind of skin proves much
less than one that spans two.
Banking's domain types, in-memory store, seed data, REST backend, components,
gen-UI beats, a2ui catalog, OGUI sandbox, pages, tools and agent now live
entirely under src/skins/banking, and its backend is namespaced to
/api/banking/v1 rather than mounted at the app root.
Every route under it is banking domain — cards, transactions, exceptions,
policies, reports, users, dev/reset — so with a second skin sharing the app,
leaving one skin's whole backend at the root contradicts the premise that
skins are self-contained. Shared UI primitives stay in src/components/ui,
which the shell and both skins import.
Banking contributes its identity through the contract (RuntimeProviders +
useRuntimeProperties) so the CopilotKit provider owns the active member from
its first commit, and a server-safe resolver so Intelligence scopes memory to
the selected persona.
The shell owns everything independent of which skin is mounted: the Skin
contract, a provider that runs a skin's data hook, split client/server
registries, the chat surface, the a2ui/OGUI canvas region and thread
selection.
The client/server split is deliberate. A skin's agent pulls in
@copilotkit/runtime, which must never reach the browser bundle, so agents are
registered in a separate server-only registry and linked to their skin by a
shared id rather than by being part of the client contract. Skins may also
contribute a server-safe identity resolver there, which is what lets per-user
durable memory be scoped per skin instead of the runtime hard-coding one.
Registers examples/showcases/reskinnable-demo as its own pnpm workspace member
resolving @copilotkit/* via workspace:*, alongside the other showcases.
No dependency differs from the banking showcase this app started as: the
standalone prototype's recharts dep is unused (banking hand-rolls its SVG
charts), and the airline skin's lucide icons all exist in the pinned range.
Five presentation fixes to the Northwind Finance demo, all from running the
beats live.
PIN change now resolves into a card rather than the sentence "New PIN saved."
It shows the card face, brand and last4, a masked new-PIN row and an active
badge. Digits are never rendered: they are never sent to the agent, so the mask
is the honest representation.
Reopening a thread replays setCardPin with status "inProgress" and no result,
so the answered card sat on "Loading..." forever. Other human-in-the-loop tools
here (showCharges) do replay their result, so this is specific to that call.
The outcome is now remembered per tool call id for the session and consulted
ahead of the replayed status. Both this card and the charges card key their
resolved state on the RESULT rather than the status, so an answered call can
never replay with live buttons.
setCardPin also registers once via a ref instead of depending on `cards`:
useFrontendTool re-registers whenever JSON.stringify(deps) changes and
re-registration removes the tool, so the PIN write tore down the very tool that
was servicing it.
showCharges becomes human-in-the-loop. Opening a filtered list is safe, but it
replaces the whole screen, and an agent that does that unasked reads as the
agent being in charge. The confirm card names the sort and filters before the
page changes, and on arrival the Sort and Show controls carry the brand tint
whenever they are non-default, so what the agent set is what lights up.
The Q2 report shows three different chart forms (share-of-total pie, time
series, budget bars) instead of three bar charts, and the seed is rebalanced so
team shares read 42/28/30 instead of 98/2/2 while all three pending charges
still exceed their limits. This drops the income-vs-expenses chart that was
showing $0.00.
Notes about reported charges carry a leading alert emoji so they cannot be
skimmed past. The seeded procedure asks for it and the handler applies it
regardless, because a model is not a reliable emoji emitter.
Finally, prose answers get a house style. The agent was formatting the first
few bullets of a list and then lapsing into plain text, which reads as a
rendering bug rather than a style choice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## What
Bumps `ag-ui-adk` from `0.6.3` to `0.7.0` in the ADK starter templates
(`examples/integrations/adk` and `examples/integrations/adk-angular`),
and
regenerates both `uv.lock` files.
## Why
`npx create-ag-ui-app@latest` → ADK scaffolds from
`examples/integrations/adk` (via `copilotkit create -f adk`, which
resolves
`-f adk` to `copilotKitStarter("examples/integrations/adk")`).
That starter pins `ag-ui-adk==0.6.3`. A2UI generative-UI rendering for
ADK
landed in `ag-ui-adk` **0.7.0** (OSS-158, ag-ui#1955), so every ADK
project
scaffolded today ships a backend with no A2UI support at all.
## Compatibility
`ag-ui-adk` 0.7.0 requires `ag-ui-protocol>=0.1.15` (starter pins
`0.1.18` ✓),
`google-adk>=1.28.1,<3.0.0` (unpinned in the starter ✓), and pulls in
two new
transitives: `ag-ui-a2ui-toolkit>=0.0.3` and
`a2ui-agent-sdk>=0.2.4,<0.3.0`.
No manifest changes beyond the `ag-ui-adk` pin were needed.
Note on the large `uv.lock` diff: regenerating the lockfiles re-resolved
`google-adk` from `1.26.0` → `2.5.0`. The previous lock pinned
`google-adk`
below 0.7.0's new `>=1.28.1` floor, so it *had* to move; `2.5.0` is the
latest
release inside the `<3.0.0` ceiling. That major re-resolve (and its
leaner 2.x
dependency tree) accounts for the bulk of the lockfile churn. Both
starters
`uv sync` and boot (`main.py` imports cleanly) against the new tree.
## Verified
- `uv lock --check` clean on both starters
- `uv sync` resolves `ag-ui-adk 0.7.0`
- `from ag_ui_adk import get_a2ui_tool` imports (symbol does not exist
in 0.6.3)
## ⚠️ Follow-up required — this PR alone does not reach users
The `copilotkit` CLI pins the template ref at **build time**:
```js
function getTemplateRef() {
return true ? "a1c9b3147829ac358bae82df651f45a1aea2a437" : "main";
}
```
`copilotkit@4.5.0` is currently pinned to `a1c9b31`, which predates this
change. Merging this PR does **not** change what `npx
create-ag-ui-app@latest`
produces — the CLI will keep serving `ag-ui-adk==0.6.3` until a new
`copilotkit` CLI release is cut whose `getTemplateRef()` points at a
commit
containing this fix.
**A CLI release is required to ship this.**
## Out of scope
Bumping the pin gives the starter the A2UI *capability*. Whether the
scaffolded frontend registers an A2UI catalog (required for anything to
actually render) was not audited here and is left to a follow-up.
The approval loop could never pass, independent of the demo's behavior:
for (let step = 0; step < 4; step++) {
const approve = page.getByRole("button", { name: /^approve$/i });
await approve.first().click({ timeout: 30_000 }).catch(...)
}
Two compounding problems. The recall path emits at most three HITL cards
(openPolicyException -> finalizePolicyException -> approveTransaction) but the
loop demanded four, so the last iteration always waited on a card that never
arrives. And the per-click timeout (30s) equalled the whole test budget
(playwright.config.ts sets no `timeout`, so Playwright's 30s default applied),
so the first miss consumed the entire test and the `.catch()` fallback to the
other APPROVE_LABELS was unreachable dead code.
Now: poll every label for the next visible approve control, treat "no card
left" as flow completion rather than a timeout, keep a runaway guard that is
explicitly not the expected card count, and give the test a budget that can
actually hold one agent turn plus the closing server poll.
Confirmed against a local run (aimock + the docker Intelligence stack): the
agent recalls the procedure, files the EXC-BOARD-APPROVED exception, and
approves the charge, and the "Record a workflow?" card never appears. The
header's "verify on first green run" notes are updated to record what that
run settled and what it did not.
The spec still fails at its closing assertion: PUT /api/v1/transactions/t-3
{status:"approved"} keeps returning 422, i.e. store.hasApprovedException() is
false, so the over-limit gate is never lifted even though the (fixture-scripted)
agent text claims it was. That is pre-existing and out of scope here — this PR
touches no part of the approval path (lib/store, the exceptions/transactions
routes, copilot-context, components/wow are all untouched). Left failing rather
than skipped so it stays visible.
Co-Authored-By: Claude <noreply@anthropic.com>
The reference page's Parameters section covered only `agentId` and `updates`, so
both props this branch adds were undocumented.
Adds an entry for each, and notes on `agentId` that passing `runtimeAgentId`
makes it required and turns it into a name the hook registers an agent under
rather than one it retrieves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes OSS-646. Split out of OSS-641 as the unambiguous half. This PR
does **not** change when activation happens — whether the long-running
wrappers should auto-connect stays open on OSS-641.
## Why
`createCopilotRuntimeHandler` builds the `ChannelManager` but opens no
connection; activation is lazy, triggered by the first
`channels.ready()`. That is deliberate (`fbf35ac59`, OSS-473) —
Cloudflare/Next isolates freeze and recycle per request, so cold starts
would mint conflicting listeners. Two things were left inconsistent with
it:
1. `endpoints/node.ts` still documented the pre-`fbf35ac59` world — "the
same `ChannelsControl` surface the underlying fetch handler **activates
at creation time**" — and labelled the one required call as `//
Optional:`. That's the TSDoc developers and coding agents see in-editor,
and it contradicted every channel-package README. Same failure class as
OSS-634.
2. `68349bc1f` gave the fetch handler a branded overload so
`handler.channels.ready()` type-checks without `?.`, but the node
wrapper never got it — so every call site, including our own example and
all nine showcase docs pages, was written defensively.
### A live consequence, found en route
`examples/slack/app/managed.ts` never called `ready()`. It built the
runtime, mounted the listener, logged `[channel] started managed Channel
"…"`, and only ever called `stop()` — so since activation went lazy it
has connected nothing while reporting success. It was written against
exactly the creation-time model the TSDoc described. Fixed here, with a
regression assertion.
## What changed
- **Types** — `createCopilotNodeListener` gets the branded overload pair
mirroring `createCopilotRuntimeHandler`: a runtime with at least one
declared Channel yields non-optional `.channels`; `activateChannels:
false` and channel-less runtimes keep the optional shape. Adds
`NodeCopilotListenerWithChannels`; both listener types are now exported
from `@copilotkit/runtime/v2/node`.
- **Docs** — node/express/hono TSDoc corrected: creation opens no
connection, `ready()` is what activates, and it is required on a
long-running host. Same stale claim fixed in the three example comments
and `examples/slack/README.md` that repeated it.
- **Call sites** — `?.` dropped from `examples/slack`, `examples/teams`,
both READMEs, and the nine `showcase/shell-docs` channel pages.
## Deliberate scope choices, called out
- **Express/Hono keep an optional `.channels`.** Only their TSDoc is
corrected here. Their own type docs name Node as the lifecycle-owning
surface and attach `.channels` best-effort, so the branded overload is
Node-only for now; `endpoints-channels.test.ts` still uses `!` for those
two. Say the word if the overload should extend to them.
- **The non-optional shape requires a literal `channels` tuple**
(`readonly [Channel, ...Channel[]]`). A runtime built from a
dynamically-assembled `Channel[]` is unbranded and still needs `?.`. Now
stated in the node TSDoc.
- **`examples/slack/app/managed.ts` now exits nonzero if activation
fails**, where before it stayed up serving HTTP with nothing connected.
Intentional — fail loud, and it matches `index.ts`. Note that `ready()`
resolves for `setup_required`, so a declared-but-unprovisioned channel
still logs as started.
- **Signal handlers are registered before awaiting activation** in
`managed.ts`, so a Ctrl-C inside the 30s activation window still tears
the Channel down instead of hitting Node's default handler.
## Verification
- **Type contract, red → green:** the new `KeyIsRequired<typeof
listener, "channels">` assertion in `handler-channels-types.test.ts`
failed to compile before the overload (`error TS2344: Type 'false' does
not satisfy the constraint 'true'`) and passes after.
- **Example bug, red → green:** stashing only `managed.ts` fails the new
guard with `expected "vi.fn()" to be called once, but got 0 times`.
- **Strict-null proof:** `slack-example` and `teams-example` both `tsc
--noEmit` clean under `strict: true` with the `?.` removed. This matters
because the runtime package compiles with `strict: false`, so its own
type test can only probe the optionality modifier structurally.
- Runtime channel suites 54/54; slack example 63/63.
- **Coverage limit:** the `managed.ts` guard is mocked — it proves the
example *calls* `ready()` with a bound, not that a Channel connects.
Nothing in CI exercises a real gateway connect for these examples.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Realigns the inspector/memory work onto the banking demo as it shipped in
#6136 (ChatGPT-style shell, gen-UI beats, durable-memory self-learning) and
#6202 (README refresh).
All six conflicts were the same collision: this branch removes the bespoke
Glass Engine inspector, while #6136 kept and rebuilt around it.
- run-handler.ts: kept both sides (our CopilotKitCoreCatalogComponent and
main's MAX_FOLLOW_UP_DEPTH landed at the same spot).
- wrapper.tsx / layout.tsx: took main's rewritten provider tree and
right-hand icon rail, minus the Glass Engine providers, pane, and
telescope toggle. Also dropped main's `padClass` (it reserved space for
the Glass pane and referenced a now-removed `glassActive`) and
`<ProactiveNotice />` (main removed it; the import is already gone).
- memory-tab.tsx, lib/intelligence/memory.ts: confirmed the deletions.
Their only remaining importers were the bespoke inspector and the
banking-local /api/memories routes, all removed here. seed-memories.ts
is unaffected: it POSTs to INTELLIGENCE_API_URL, not the local route.
- README.md: kept our product-inspector section over main's Glass Engine
availability/activation prose, and documented the Capabilities tab.
Drive-by fixes to comment rot the migration created: user-id.ts and the
copilotkit route doc comments referenced the deleted Memory-panel proxies,
and the README pointed the presenter-reset control at the removed
telescope toggle.
Also replaces a literal NUL byte in capabilityKey() with a unicode escape.
The raw control character made tsc/grep/diff treat run-handler.ts as a
binary file, which hid this very merge's conflict markers from grep.
Behavior is unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>