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>
Overlapping turns on the same conversation now run concurrently by default
so multi-user Slack threads get parallel replies. Singleton agents are
isolated via clone() per run; store.concurrency serial/drop remain opt-in.
Allow a failed/uncertain terminal after effect or complete-terminal push
failures; seal only after a successful terminal apply. Leave Phoenix child
channels on failed join, re-arm delivery handlers on restart, replay
onStateChange health, skip empty Teams stream deltas, and align docs/tests.
Close packet path after permanent push/ack failures so a later effect
cannot mint a new effectId on the same seq. Refresh owner generation on
join_token reconnect, add reconnect backoff, require claimed on claim
assert, reject unknown turn kinds, skip empty Slack stream deltas, and
surface missing file-client attachments instead of dropping them.
Always release the product thread lock after a Channel canonical run.
Align connectTimeoutMs docs, projectId validation, ops error guidance,
and test fixtures with the delivery ID contract.
Note: local lefthook skipped (no node_modules in this worktree); CI will
validate. CR findings addressed from PR #6249 review.
## The race, with evidence
Three PRs merged within 34 seconds on 2026-07-26. `Showcase: Build &
Push` has **no concurrency group** (deliberately), so all three ran
simultaneously and raced to push the same `:latest` tags.
| run | commit | PR | start → end |
|---|---|---|---|
| `30190815370` | `7b28934387` | #6162 | 06:18:05 → 06:29:44 |
| `30190823203` | `59f275eedc` | #6161 | 06:18:21 → 06:29:44 |
| `30190831480` | `db75a04837` | #6158 | 06:18:39 → **06:29:13** ←
newest, finished FIRST |
The newest commit finished first, so the older builds overwrote its
`:latest`. Per-service job completion times — older beating newer on
every shared slot:
| service | newer (`db75a04837`) | older (`59f275eedc`) | older won by |
|---|---|---|---|
| `shell-dashboard` | 06:25:33 | 06:25:34 | +1s |
| `showcase-harness` | 06:27:51 | 06:27:55 | +4s |
| `shell` | 06:27:17 | 06:27:28 | +11s |
| `shell-dojo` | 06:25:10 | 06:25:26 | +16s |
**All three runs reported `success`.** Staging served pre-#6158 code
while CI, the redeploy gate and deploy verification all looked clean.
Same failure class as #6171: a success that doesn't mean what it says.
## What I verified in YAML vs took on trust
Verified by reading the files / querying the API:
- **Tagging** — `showcase_build.yml` pushed `:latest` **and** `:${{
github.sha }}` in one `depot/build-push-action` step, in both the
`build` and `build-starters` matrices. **A per-commit sha tag already
existed**; confirmed in GHCR (`showcase-shell-dashboard` has digests
tagged `db75a04837…`, `59f275eedc…`, `d28384a2eb…`).
- **Nothing serialized the pushes.** No concurrency group; confirmed the
header comment states this is intentional.
- **Deploy consumes `:latest`** — `verify-railway-image-refs.ts` is the
SSOT assertion: staging is `ghcr.io/copilotkit/<repo>:latest` (mutable),
**prod is `ghcr.io/copilotkit/<repo>@sha256:<digest>` (already immutably
pinned)**. So this race is **staging-only**; prod was never exposed.
- **`Showcase: Verify Deploy` structurally cannot catch it.** It is a
health probe; it asserts no digest or commit provenance anywhere. Its
#6171 per-commit concurrency key is about *which run verifies*, not
*what image is running*. A stale-but-healthy service passes.
- **The racing runs build DISJOINT service sets** (see below) — I pulled
the actual job lists.
Taken on trust: nothing material. The issue description matched the API
on every point I checked.
## Why NOT a concurrency group
`detect-changes` builds a **per-push, path-filtered** matrix, so
concurrent runs build overlapping but **non-identical** service sets:
- `7b28934387` → ag2, agno, built-in-agent, claude-sdk-python,
claude-sdk-typescript, crewai-crews, langgraph-fastapi,
langgraph-python, langroid, llamaindex, mastra, pydantic-ai, shell-docs,
spring-ai, strands (**15**)
- `db75a04837` → crewai-crews, llamaindex, shell, shell-dashboard,
shell-docs, shell-dojo, showcase-harness (**7**)
`cancel-in-progress: true` would have cancelled the `7b28934387` run and
the ~10 services **only it builds would never have shipped** — trading a
stale-image bug for a never-shipped bug. Per [GitHub's
docs](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#concurrency),
`cancel-in-progress: false` is no better: *"Any previously pending job
or workflow in the concurrency group will be canceled"* — with three
rapid merges the **middle** commit's build is dropped outright. GitHub
also does not guarantee FIFO ordering of queued runs.
Concurrent runs here are **not redundant**, so they must not be
cancelled.
**Re the #6171 interaction:** this design does **not** use
`cancel-in-progress`, so no build is ever superseded-and-cancelled and
the cancelled-slot notifier is never tripped by this change. That
interaction stays theoretical — deliberately.
## The design
Make the one shared mutable resource monotonic instead of serializing
the fleet.
1. The build step pushes **only** the immutable `:<sha>` tag, plus an
`org.opencontainers.image.revision` label.
2. A guard resolves the commit behind the current `:latest`, asks GitHub
`compare/<theirs>...<ours>`, and advances `:latest` (registry-side
retag, no pull) **unless ours is `behind`** — i.e. `:latest` already
holds a descendant and moving it would roll staging back.
**Fails open by design.** No `:latest` yet, unlabelled legacy image,
unreachable API, diverged history → advance. A stuck `:latest` is the
very failure being fixed, so it declines only on *positive proof* of
regression.
**Placement:** in `redeploy-staging` / `redeploy-staging-starters`,
immediately before the Railway pull that consumes `:latest` — not per
build slot. Those jobs already have Node (build slots do **not**, so
per-slot would mean an unpinned `npx tsx` fetch on ~50 parallel
runners), and deciding right before the pull makes the window as narrow
as possible. The image list is the **same matrix ∩ build-success
intersection** that decides what gets redeployed, so a failed build can
never move a tag.
Also adds `showcase_build.yml` to `showcase_validate.yml`'s trigger
paths — the new test asserts against that file's live text, and without
the path a PR re-adding `:latest` would never run the test that catches
it.
## Tradeoffs / what stays open
- **Residual sub-second TOCTOU.** GHCR has no compare-and-swap on tags,
so two runs reading `:latest` simultaneously could still both advance.
This narrows the window from the whole build (~10 min) to inspect→retag.
Fully closing it means retiring the mutable staging tag and pinning
staging to digests the way prod already is — a change to the Railway
image-ref SSOT contract, not a workflow change. **Recommended
follow-up.**
- **The guard is inert for one build per image.** Today's `:latest`
images carry no labels (verified: `showcase-shell-dashboard:latest` has
no `Labels` at all), so the first post-merge build fails open and
advances unconditionally — same as today. Protection starts from the
second build of each image.
- Failure to retag exits non-zero, redding the redeploy job and stopping
the deploy. That is intended: redeploying against a tag that did not
move is exactly the silent false green being fixed.
## Proof
**Red/green on the live YAML.** `advance-latest-tag.test.ts` parses the
real `showcase_build.yml` (extending #6171's `redeploy-guard.test.ts`
pattern). Reverting the workflow to its pre-fix state: **14 failed / 23
passed**. With the fix: **37 passed**. Full `showcase/scripts` suite:
**2382 passed, 73 files**.
**The load-bearing predicate, verified live against the real incident
commits:**
```
compare/db75a04837...59f275eedc => behind (older run arriving late → DECLINE)
compare/59f275eedc...db75a04837 => ahead (newer run → advance)
compare/db75a04837...db75a04837 => identical
```
**Label reading, verified against a real multi-platform registry image**
— `docker buildx imagetools inspect ghcr.io/astral-sh/uv:latest` piped
through `extractRevisionLabel()` returns
`3010295ae7ff572de459987ad70db315a62ecd61`, matching `jq` exactly. The
platform-keyed shape is handled.
**Shell/jq transforms** exercised directly, including the empty-CSV edge
case (empty → empty, step skipped by its `if:`).
**Lint:** `actionlint` finding counts byte-identical to the pre-change
baseline (no new findings; the 11 pre-existing are unrelated). `zizmor
--min-severity low` with the repo config: **no findings**.
**Typecheck:** both new files are in `showcase/scripts/tsconfig.json`'s
include set and produce **zero** errors. Worth stating plainly: `nx
run-many -t check-types` **does not reach `showcase/scripts`** — the
project isn't in the nx graph and has no `check-types` target (there are
9 pre-existing type errors in sibling files, which is how I confirmed
it). So the typecheck above is mine, not CI's. The *tests* are gated:
`showcase_validate.yml` runs bare `pnpm exec vitest run` in
`showcase/scripts`, which auto-discovers the new file.
### What I could NOT prove
**I did not construct a real concurrent race on scratch branches.**
Doing it faithfully needs two builds pushing the same GHCR repo with
controlled finish ordering, which means merging to `main` — the only
branch the build workflow triggers on. No run IDs for a live race
demonstration; I am not implying one.
Unproven until this runs on main: that `docker buildx imagetools create`
retags cleanly under the runner's GHCR credentials, and that `npx tsx`
behaves in the redeploy jobs (it is already the established invocation
there — `redeploy-env.ts` — so this is low risk, not zero).
## Normal single-merge builds are unaffected
No concurrency group is added, so nothing queues or cancels. A lone
merge finds `:latest` at its own parent → `ahead` → advances, exactly as
before. Cost is one `imagetools inspect` + one `gh api` + one
registry-side retag per built service, in a job that already exists — no
extra job, no extra checkout, no change to build parallelism.
---
Branched from `db75a04837`; #6156/#6159 landed after, so this will need
main merged in before it goes green.
Probable conflict with the concurrent `git lfs pull` work in
`showcase_validate.yml` — my edit there is only the top-level `on:
paths:` list, so it should merge cleanly, but flagging it.
## Summary
- Make Slack and Microsoft Teams the production-ready Channels choices
in the top provider picker, with framework-aware routes under
`/slack/...` and `/teams/...`.
- Put ten task-oriented guides inside every provider/framework journey
and remove the standalone Channels overview from navigation.
- Restore the global Channels SDK reference at `/reference/channels`,
including 34 current core, UI, state, transcript, and direct-adapter
entries.
- Add the complete CopilotKit Intelligence setup walkthrough, product
screenshot, and a stable architecture-diagram slot that Mike's final
artwork can replace in place.
- Qualify Discord and WhatsApp correctly: their direct adapters already
ship, while managed Intelligence support is coming soon.
## Why
Developers should choose their chat provider and agent framework first,
then stay in that context while they build and operate the integration.
The previous structure mixed provider guides, an extra overview layer,
and stale provider-specific reference pages, making it hard to find the
supported path or understand which behavior was managed versus
developer-operated.
This update keeps the guide journey provider-specific while returning
API material to the normal global Reference surface. It also documents
operational boundaries that matter in production instead of adding pages
for their own sake.
## How
- Reuse provider-aware MDX across Slack, Teams, and all 19 public
agent-framework integrations; the built-in agent keeps the shorter route
without a framework segment.
- Organize the sidebar into Getting started, Build, Production, and API
reference with guides for Intelligence, tools, rich and interactive
messages, commands and reactions, files, state, persistence,
transcripts, and operations.
- Pin the verified `@copilotkit/channels@0.4.0` and
`@copilotkit/runtime@1.64.1` pair and align the copy with current SDK
source plus live Intelligence behavior.
- Document managed capabilities and provider-specific realities,
including active/standby runtimes, optional hosted endpoint defaults,
output-free turn finalization, Slack manifest scopes, Teams attachment
shapes and consent, delivery acceptance semantics, and durable state
requirements.
- Preserve useful direct-adapter discoverability for Slack, Teams,
Discord, Telegram, and WhatsApp without restoring obsolete symbol pages.
- Add one-hop redirects for retired routes and cover navigation,
framework selection, raw-doc URLs, search, reference discovery, and
sitemap output.
Validation:
- Full docs suite: 51 files / 348 tests
- Typecheck
- Lint with no errors (existing baseline warnings only)
- Production build: 222 static pages
- Live HTTP checks: Slack Intelligence, Teams + Mastra files, Slack rich
messages, and direct-adapter reference all return 200
- Independent read-only correctness passes against the current Channels
SDK, Runtime, Intelligence, and every public framework setup
Linear:
https://linear.app/copilotkit/issue/OSS-615/channels-sdk-documentation-audit
Two built-in-agent demos were broken against a real model while their D5/D6
rows stayed green, because aimock exercises neither failure. Both root causes
were confirmed against the live OpenAI API.
gen-ui-agent stopped mid-plan on every run. `@tanstack/ai`'s `chat()` applies
`maxIterations(5)` when no `agentLoopStrategy` is passed, and nothing errors
when the budget runs out — the run just ends. GEN_UI_AGENT_PROMPT scripts 7
`set_steps` calls (1 initial + in_progress/completed per step x 3) plus a
closing message, so the walk died two calls short with the last step pinned at
`pending` and no narration. Reproduced with the real model and the real prompt:
default budget -> 5 calls, "completed, completed, pending", no message
maxIterations(25) -> 7 calls, all completed, 333-char summary
Every demo factory now passes the shared DEMO_AGENT_LOOP_STRATEGY (25 —
several times the longest scripted walk, still bounded). The two non-streaming
tool-free `chat()` calls keep the default: they have no loop to exhaust.
declarative-json-render rendered nothing at all: RUN_STARTED -> RUN_FINISHED,
no events, no console error, no banner. `text.format: { type: "json_object" }`
has a server-side precondition that the word "json" appear in the request
`input`, but the adapter sends `systemPrompts` as `instructions` and only
`messages` as `input` — so with the JSON directive living solely in
SYSTEM_PROMPT the API rejected every run with
400 Response input messages must contain the word 'json' in some form to
use 'text.format' of type 'json_object'. (param: input)
The directive now rides in `messages` (as `user`, since TanStackChatMessage
admits no `system` role and the runtime hoists system messages into
systemPrompts — the half that is not input). json_object enforcement is kept;
verified live that the run then streams a complete, JSON.parse-able spec.
That 400 was invisible because the hand-rolled converters forward a whitelist
of chunk types and dropped RUN_ERROR — every one except reasoning-factory. The
`type: "tanstack"` factories were fine (the runtime's converter rethrows), so
the four `type: "custom"` converters now call the shared throwOnRunError.
Also: the gen-ui-agent progress card announced "All N steps complete" whenever
the RUN ended, ignoring the step data, so a truncated run read as a UI glitch
instead of an agent that stopped early. The wording is now derived from the
steps (`describeProgress`, extracted pure so it is testable without a DOM) and
a stalled run says so.
Both gotchas recorded in showcase/GOTCHAS.md.
## What does this PR do?
Fixes a fleet-wide false red on `mcp-apps` D5/D6: red on all 18
integrations that support the feature since `first_failure_at`
2026-07-28 23:03Z, while the demos rendered correctly by hand.
**Root cause.** The `completeOnMount` gate added in d70d48a561 named the
`mcp-app-iframe` testid, which only Angular's `copilot-mcp-apps-widget`
declares. `react-core` and `vue` build the sandbox iframe imperatively
(`document.createElement("iframe")`) with no testid, so the settle gate
could never be satisfied — every React/Vue integration timed the turn
out at 30s with `reason=surface-missing` and never reached
`assertIframePresent`, whose `iframe[sandbox]` fallback would have
passed. `crewai-crews` and `langroid` stayed green only because they
skip the feature (`errorClass: "skipped-incapable"`).
Verified against live staging (`showcase-built-in-agent-staging`, after
clicking the "Sketch a system diagram" pill):
```
oldGate_testIdOnly: 0 <- what the probe waited for
newGate_cascade: 1 <- the surface that was there all along
allIframes: [{ sandbox: "allow-scripts allow-same-origin allow-forms", testid: null, hasSrcdoc: true }]
```
**Fixed on both sides of the contract:**
1. **Product** — `react-core` and `vue` `MCPAppsActivityRenderer` now
set `data-testid="mcp-app-iframe"` and `title="Interactive MCP
application"` on the host-created iframe, matching Angular. Pinned by a
new test in each package, so dropping the attribute fails in the package
that owns it rather than silently reddening the fleet a day later.
2. **Harness** — `completeOnMount` accepts CSS `selectors` alongside
`testIds`, so the probe settles on the cascade its own module doc and
assertion already declare:
```ts
completeOnMount: { selectors: ['[data-testid="mcp-app-iframe"],
iframe[sandbox]'] }
```
`querySelectorAll` unions comma-separated branches, so one entry
expresses "any conforming form of this surface" while the
conjunctive-across-entries and `minNewMounts` delta semantics stay
exactly as before; `testIds: ["x"]` is now sugar for `selectors:
['[data-testid="x"]']`, so every other probe is untouched. This half
matters on its own: the integrations pin
`@copilotkit/react-core@1.61.2`, so a testid-only fix would leave 18
cells red until a release plus a fleet redeploy.
Also: a `completeOnMount` spec naming no surface now throws instead of
burning the whole turn budget and reporting a misleading
`surface-missing`. The trap is recorded in `showcase/GOTCHAS.md` next to
the sibling `copilot-assistant-message` testid gotcha.
## Verification
- harness `conversation-runner.test.ts` + `d5-mcp-apps.test.ts` —
101/101, including new red-green pairs: a `selectors` cascade greens, a
never-mounting cascade still reds `surface-missing`, a leftover-only
surface still reds, an empty spec fails loud
- `@copilotkit/vue` `MCPAppsActivityRenderer.test.ts` — 6/6
- `@copilotkit/react-core` `MCPAppsActivityRenderer.e2e.test.tsx` —
17/17
- `oxfmt` clean, `check-types` clean on both packages
- Pre-existing and unrelated (fail identically on an untouched worktree
on Windows): harness `typecheck` wants the gitignored generated
`showcase/shell/src/data/frontend-catalog.json`; 6 `src/probes` tests
assert POSIX path separators
## Related PRs and Issues
- Regressed by #6212-era commit d70d48a561 ("test(showcase): add
deterministic Angular parity audit")
## Checklist
- [x] I have read the Contribution Guide
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation (`showcase/GOTCHAS.md`)
- [x] "Allow edits by maintainers" is checked
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The x-pathname middleware added in this branch activates layout.tsx's
request-time readFileSync(cwd/manifest.yaml) in generateMetadata. Without
the manifest in the runner stage, every /demos page 500s with ENOENT once
middleware sets the header. Mirrors langgraph-python's Dockerfile and
satisfies scripts/__tests__/runtime-manifest-copy.test.ts.
The mcp-apps POST catch block returned the raw error message and stack
to the client and logged nothing server-side, leaking internal paths and
library versions to anyone who could hit /api/copilotkit-mcp-apps. Mirror
the sibling copilotkit/route.ts hardening: log full details server-side
under a randomUUID correlation id and return only the id plus a generic
message with status 500.
mcp-apps is aimock-green but live-degraded: gpt-4o-mini emits malformed Excalidraw
create_view elements JSON (stray trailing }) -> MCP JSON.parse fails -> black
canvas live. gpt-5.4 fixes it live but reds aimock D6 because the TS MCP-Apps
middleware doesn't act on aimock's REPLAYED responses-path create_view tool call
(never mounts the iframe; fixture is byte-identical to LGP, LGP's Python runtime
mounts it — so TS-runtime-specific, not a fixture issue). Kept gpt-4o-mini to hold
D6 green; the real fix is a TS MCP-Apps-middleware/@copilotkit runtime change +
then gpt-5.4. Flagged as follow-up.
The @ag-ui/langgraph converter collapses EVERY attachment (image and document)
into a LangChain image_url data-URL, so a PDF reached rewritePart as an
image_url part and hit neither the image nor document branch -> passed through
unchanged -> sent to gpt-4o as an image -> OpenAI 400 'Invalid MIME type. Only
image types are supported.' (The existing document-branch pdf-parse code was
dead — the part never arrives as 'document'.)
Add an image_url branch to rewritePart that routes on the data-URL MIME (mirrors
langgraph-python's multimodal_agent): image/* passes through unchanged; any
non-image (application/pdf) is flattened to text via the existing pdf-parse path
instead of being forwarded as an image. Source-only, no dep/model change.
multimodal D6 still green (PNG path unchanged); PDF upload verified live (text
extracted, no 400).
Root cause across nearly every ChatClientAgent: system prompts were passed as
`description:` (agent metadata) instead of `instructions:` (the actual system
message). ChatClientAgent(instructions, name, description, …) therefore ran
with null instructions, so models ignored tool guidance and BYOC JSON demos
emitted prose.
Fixes reported staging failures:
- shared-state-read-write pills: instructions now reach the model + stronger set_notes guidance
- declarative-hashbrown / declarative-json-render: instructions + ChatResponseFormat.Json (LGP parity)
- declarative-gen-ui: catalog-specific design prompt (no DashboardCard), stronger outer agent
- shared-state-streaming: feature was demo-only in the manifest → shell "Backend fixture unavailable"; added to features
Verified: unit suite 79/79 in Docker SDK 9.
The `completeOnMount` gate added in d70d48a561 named the `mcp-app-iframe`
testid, which only Angular's `copilot-mcp-apps-widget` declared. react-core
and vue build the sandbox iframe imperatively with no testid, so every
React/Vue integration timed the turn out at 30s with
`reason=surface-missing` and never reached `assertIframePresent` — whose
`iframe[sandbox]` fallback would have passed. D5 + D6 `mcp-apps` went red on
all 18 integrations that support the feature (first_failure_at 2026-07-28
23:03Z) while the demos rendered correctly by hand.
Fixed on both sides of the contract:
- react-core and vue now set `data-testid="mcp-app-iframe"` and a `title` on
the host-created iframe, matching Angular. Pinned by a test in each package.
- `completeOnMount` accepts CSS `selectors` alongside `testIds`, so the probe
settles on the same cascade its module doc and assertion already use
(`[data-testid="mcp-app-iframe"], iframe[sandbox]`). A comma-joined entry is
one conjunctive surface whose branches `querySelectorAll` unions, so the
delta/`minNewMounts` semantics are unchanged and `testIds` is now sugar for
the equivalent selector. This half greens the fleet on the next sweep
without waiting for a package release, since the integrations pin
@copilotkit/react-core 1.61.2.
A spec naming no surface now throws instead of burning the turn budget and
reporting a misleading `surface-missing`.
Verified against live staging: after clicking the pill, the old gate matched
0 elements and the cascade matched 1 (the sandboxed iframe was there all
along). Also recorded in showcase/GOTCHAS.md.
Staging click-through on ms-agent-dotnet / ms-agent-harness-dotnet hit
several GOTCHAS #8 defects: aimock D6 was green while live LLMs failed.
A2UI (beautiful-chat sales dashboard, declarative-gen-ui pills):
- Force the page-registered catalogId (models invent "sales_dashboard").
- Sanitize/normalize flat components; salvage type-as-key nests; drop
entries missing id/component (SummaryCard without id, charts without type).
- Strengthen secondary design prompts with the flat catalog contract.
Shared state + subagents side panels:
- Tool invocation drops AsyncLocal set by SetActiveThread, so writes landed
in the global slot while snapshots keyed by AgentSession/AgentThread.
Mirror writes and fall back on read (same pattern as D5ParityAgents).
- Wire the dead TryBuildDeterministicReply path for the "Remember something"
pill so notes update without relying on the model calling set_notes.
Open generative UI advanced + beautiful-chat calculator:
- Prompt for clickable keypad (not form/submit) and notifyHost ping wiring.
Verified: ms-agent-dotnet unit suite 79/79 green in Docker SDK 9; harness
agent builds clean and cvdiag tests 5/5.