Commit Graph

1378 Commits

Author SHA1 Message Date
Mark b4041622b0 feat(examples): LangGraph interrupt HITL probe for channels
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.
2026-08-03 08:53:30 +00:00
Mike Ryan fdbd05d164 fix(channels): use provider actors for private replies 2026-08-01 09:19:13 -07:00
Mike Ryan 561bf19fa6 feat(channels): add explicit identity and memory grants 2026-08-01 09:19:13 -07:00
github-actions[bot] 04b445a04d style: auto-fix formatting 2026-07-31 18:58:10 +00:00
Maxim a40427a6f3 docs(reskinnable-demo): state the cardsRef sync trade-off honestly
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.
2026-07-31 20:55:38 +02:00
Maxim 08e10deb59 Merge branch 'blitz/banking-genui-replay/report' into blitz/reskinnable-demo-port/integration 2026-07-31 20:51:24 +02:00
Maxim 52461637ff feat(reskinnable-demo): port PIN, charges-confirm and note polish to the banking skin
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.
2026-07-31 20:49:05 +02:00
Maxim 3a2ea211a0 feat(reskinnable-demo): curate the banking report charts to the final trio
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.
2026-07-31 20:46:45 +02:00
Maxim e5b54d81b8 Merge branch 'blitz/banking-genui-replay/seed-policy' into blitz/reskinnable-demo-port/integration 2026-07-31 20:41:01 +02:00
Maxim 83d3bfa31a Merge branch 'blitz/banking-genui-replay/seed-memo' into blitz/reskinnable-demo-port/integration 2026-07-31 20:41:00 +02:00
Maxim 5af07bf489 Merge branch 'blitz/banking-genui-replay/charges-tint' into blitz/reskinnable-demo-port/integration 2026-07-31 20:41:00 +02:00
Maxim 109583caf4 Merge branch 'blitz/banking-genui-replay/prompt' into blitz/reskinnable-demo-port/integration 2026-07-31 20:40:59 +02:00
Maxim 37cf227d8b Merge branch 'blitz/banking-genui-replay/top-charges' into blitz/reskinnable-demo-port/integration 2026-07-31 20:40:59 +02:00
Maxim d9d50139e7 Merge branch 'blitz/banking-genui-replay/pin-card' into blitz/reskinnable-demo-port/integration 2026-07-31 20:40:59 +02:00
Maxim a6c5c93238 feat(reskinnable-demo): tint the charges Sort and Show selects when set
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.
2026-07-31 20:39:59 +02:00
Maxim ec00c42c50 feat(reskinnable-demo): rebalance seeded policy limits in the banking skin
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.
2026-07-31 20:38:50 +02:00
Maxim 9eef856d29 feat(reskinnable-demo): add TopChargesChart to the banking skin
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.
2026-07-31 20:37:21 +02:00
Maxim 4b4b937599 feat(reskinnable-demo): seed the alert-emoji note convention in the banking skin
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.
2026-07-31 20:37:05 +02:00
Maxim a83afc6db8 feat(reskinnable-demo): add prose house-style rules to the banking prompt
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.
2026-07-31 20:36:29 +02:00
Maxim 92756f4a41 feat(reskinnable-demo): add PinChangedCard to the banking skin
Ports the resolved-state PIN card from the banking showcase (#6259) into
the reskinnable demo's banking skin. The consumer wiring lands separately.
2026-07-31 20:36:27 +02:00
Maxim c8fd81a4fe feat(reskinnable-demo): add NavigateConfirmCard to the banking skin
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.
2026-07-31 20:36:02 +02:00
Maxim 916f4b7f24 Merge remote-tracking branch 'origin/main' into blitz/reskinnable-demo-port/integration 2026-07-31 20:31:11 +02:00
Maxim acd8d0715a feat(reskinnable-demo): isolate memory by organization as well as port
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.
2026-07-31 19:04:05 +02:00
Maxim 3453b228e2 feat(reskinnable-demo): make the resolved Intelligence backend explicit
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.
2026-07-31 17:04:26 +02:00
Maxim 9fe2714d24 fix(reskinnable-demo): shift Intelligence stack host ports by +200 to isolate from banking
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.
2026-07-31 17:04:16 +02:00
Benjamin Taylor 4d74bdc5c3 feat(runtime): auto-start managed Channels on long-running hosts (refs OSS-641)
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>
2026-07-30 18:37:37 -05:00
David McKay 8aee7df4b0 Merge branch 'main' into demo/banking-report-largest-charges 2026-07-30 13:36:44 -07:00
Benjamin Taylor 76662f31b1 feat(channels): apply the event sanitizer by default and drop it from the examples
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.
2026-07-30 15:16:30 -05:00
David McKay de2bc6efc0 feat(banking): rank largest charges instead of repeating the team split
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>
2026-07-30 13:11:26 -07:00
Maxim 4b8b6572d9 chore(reskinnable-demo): retarget the demo tooling
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.
2026-07-30 21:49:02 +02:00
Maxim 1387f32dee docs(reskinnable-demo): document the architecture and add the reskin skill
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.
2026-07-30 21:49:02 +02:00
Maxim 12b1ac6b0d test(reskinnable-demo): cover reskinning and retarget the suite
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.
2026-07-30 21:49:02 +02:00
Maxim 4ad47517aa feat(reskinnable-demo): route by skin
/[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.
2026-07-30 21:49:01 +02:00
Maxim 788783bb58 feat(reskinnable-demo): add the airline skin
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.
2026-07-30 21:49:01 +02:00
Maxim 00f95834e5 feat(reskinnable-demo): make banking a self-contained skin
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.
2026-07-30 21:49:01 +02:00
Maxim 651f7dd7aa feat(reskinnable-demo): add the skin-agnostic shell and Skin contract
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.
2026-07-30 21:49:01 +02:00
Maxim 3790bbb431 feat(reskinnable-demo): add the app to the workspace
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.
2026-07-30 21:49:00 +02:00
David McKay 269d49a2c0 feat(banking): generative-UI polish for PIN, charges, report and notes
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>
2026-07-30 11:12:00 -07:00
Mark e816a93151 chore(examples): bump ADK starters to ag-ui-adk==0.7.0 for A2UI support (#6216)
## 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.
2026-07-30 06:44:34 -07:00
Tyler Slaton 2f095fd8c9 fix(channels): preserve managed Slack tool history 2026-07-29 18:15:42 -04:00
Maxim 1c4676c687 Merge remote-tracking branch 'origin/main' into blitz/glass-inspector/integration 2026-07-29 20:19:10 +02:00
David McKay ce0576d7d1 Merge branch 'main' into fix/useagent-threadid 2026-07-29 06:50:17 -07:00
Maxim 1e575233e6 test(banking): make the memory-recall e2e approval loop actually completable
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>
2026-07-29 15:27:08 +02:00
Markus Ecker 4cbca9ebe5 docs(react-core): document useAgent threadId and runtimeAgentId
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>
2026-07-29 11:27:57 +02:00
Mark 6294579fbc chore(examples): bump ADK starters to ag-ui-adk==0.7.0 for A2UI support 2026-07-29 00:25:04 +00:00
copilotkit-qa-bot a0cfc60c1b Address FAC-49 review feedback 2026-07-28 14:40:24 -07:00
copilotkit-qa-bot 7749d9b29c QA Factory Coding update for FAC-49 2026-07-28 14:39:00 -07:00
Ben Taylor 8a8e52d9e9 fix(runtime): non-optional listener.channels and honest lifecycle docs (OSS-646) (#6207)
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)
2026-07-28 14:11:53 -05:00
Maxim 0850c5e522 Merge remote-tracking branch 'origin/main' into blitz/glass-inspector/integration
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>
2026-07-28 21:10:21 +02:00
Benjamin Taylor b2e825050f fix(examples): match teams teardown semantics to slack (guarded handler, nonzero on failure) (OSS-646) 2026-07-28 13:34:50 -05:00