16126 Commits

Author SHA1 Message Date
Tyler Slaton fb4f352032 chore: release monorepo v1.70.3 (#6960)
## Release monorepo v1.70.3

**Scope:** `monorepo` | **Bump:** `patch`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `monorepo` packages to `1.70.3`
   and generated AI-enhanced release notes.

2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
   must pass before merging. This is the review gate.

3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.

4. **When this PR is merged**, the `release / publish` workflow
automatically:
   - Builds all packages
   - Publishes the `monorepo` packages to npm at version `1.70.3`
   - Creates git tag `monorepo/v1.70.3`
   - Creates a GitHub Release with the final release notes

### Before merging

- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)

---

> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
v1.70.3
2026-09-09 01:37:03 +02:00
Ben Taylor 31d0cda168 fix(a2ui): report a generative-UI result that renders nothing (closes OSS-1048) (#6802)
A generative-UI result that does not paint reports nothing today. The
turn finishes, the input returns to idle, the network calls are all 200,
and the console is byte-identical to what it held before the request.
The only signal is a human noticing a blank space in a screenshot.

Three commits, each breaking one of those silences. Rendering behavior
is unchanged throughout — every report is a development-only
`console.warn`.

## A tool call with no renderer

`use-render-tool-call.tsx` resolves a renderer by name, then by agentId,
then by wildcard, then returns `null`. The existing comment defends that
choice well: auto-painting a default card would leak internal tool names
plus raw args and result JSON into every app's production chat. That
argument is about *painting*, and it does not cover *warning*.

The warning names the tool the agent called and lists the renderer names
that are registered. When the cause is a name that does not match, that
is the whole diagnosis.

It is deferred one task past the commit that recorded the miss, then
re-checks the registry. `useRenderTool` registers from an effect in the
component that renders the chat, and React runs child effects before
parent ones, so at effect time the resolver can see an empty registry
even though the app did register a renderer. Removing that re-check
makes two of the new tests fail on exactly that false positive.

## An A2UI surface that gets operations and paints nothing

Two reports, both in `A2UIMessageRenderer.tsx`.

**Operations arrived and no paint followed.** The renderer already waits
8s for a surface to report its first paint before dropping the loader,
so reaching that fallback is itself the signal. No new threshold was
invented. `surfaceHasRenderableContent` already knows which half is
missing, so the message says which: no `updateComponents` at all, or
`"path"`-bound components whose `updateDataModel` never carried a value.

**Operations named a surface that was never created.** `A2UIRenderer`
renders its `fallback` for an unknown surface id and that defaults to
`null` — the card is absent and the log is empty. `processMessages` is
synchronous, so a surface still missing after it was never created. This
report is also deferred and re-checked, because operations stream and a
snapshot can reach the processor before the `createSurface` that gives
it somewhere to go.

## The two surface-id resolvers disagreed

React read a top-level `operation.surfaceId` first and only then the
nested v0.9 keys. The web-components path read the nested keys only, via
`normalizeOperations`, and never looked at a top-level id at all. One
payload grouped under its own id in React and under `"default"` in the
Lit and Angular renderers.

Nested wins in both now, with a top-level id as the fallback when the
payload carries none.

Nested is the correct half of that choice, not a coin toss:
`MessageProcessor` creates the surface from the nested id. Grouping by a
top-level id files the operations against a surface `createSurface`
never made, and an unknown surface id renders the `null` fallback above.
So the old React order could *produce* the silence the second commit
teaches the renderer to report — which is what the new test asserts, by
requiring that the missing-surface warning stay quiet.

## What this does not cover

A surface that exists, holds complete components, and still draws
nothing. `onReady` fires exactly when `surfaceHasRenderableContent` is
true, so that case is invisible to the paint-fallback path by
construction. Filed as OSS-1057 with a concrete mechanism: both
renderers hard-code a root component id of `root`, and a components list
without one shimmers forever.

The other item on OSS-1048 — a turn whose only output is generative UI
recording a `tool` message with no assistant parent — is a different
repo and a real design decision. Filed as OSS-1056.

## Verification

`react-core` 1565/1565, `a2ui-renderer` 24/24, both builds clean,
lefthook green on all three commits.

Every new negative assertion was mutation-tested against the pre-change
code. Two of them are guarded twice over, and removing either guard
alone left the test green — so both had to be removed before the test
would fail, which is what confirms it is not vacuous.

Closes OSS-1048.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Operations now consistently target the correct surface, prioritizing
nested surface identifiers and falling back to top-level identifiers
when needed.
  * Improved handling of operations for surfaces that are created later.

* **Diagnostics**
* Added development-time warnings when surfaces receive operations but
render nothing.
* Added warnings for operations targeting missing surfaces or unresolved
root components, including likely causes.
* Added warnings when tool calls have no matching renderer, with
registered renderer details where available.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 18:31:15 -05:00
tylerslaton 69a940c70e chore: release monorepo v1.70.3 2026-09-08 23:22:32 +00:00
Tyler Slaton 30f67b18f1 fix(inspector): enable Learning without flags and correct status (#6957)
## Problem

Inspector Learning required both runtime debug mode and a separate
handler opt-in, while Threads did not. The Home and launcher Learning
indicators also read Memory availability, so configured Learning could
remain off. Enabled launcher toggles were purple rather than green
2026-09-09 01:17:31 +02:00
Tyler Slaton fdb6ce0714 fix(inspector): require Learning container configuration for status 2026-09-08 16:05:53 -07:00
Tyler Slaton 1b2d8eca85 fix(runtime): sync public API manifest after removing Learning flag 2026-09-08 16:03:32 -07:00
Tyler Slaton 78e498bf33 fix(inspector): derive green Learning status from its own endpoint 2026-09-08 15:52:32 -07:00
Tyler Slaton 290a8323ae fix(runtime): expose Inspector Learning without extra flags 2026-09-08 15:51:58 -07:00
Benjamin Taylor b0f349fcfb fix(a2ui): report a surface whose root component never resolves (closes OSS-1057)
Both renderers begin walking a surface at the component with id "root" and
treat an id they cannot find as not arrived yet, painting an animated
placeholder. That is right while operations stream. Once operations have
stopped it is not waiting, it is stuck — and every existing check calls it
healthy: the surface exists, processMessages does not throw, the component
type is never reached so the "Unknown component" branch cannot fire, and
surfaceHasRenderableContent says yes on the strength of components plus a
non-empty data model, so onReady fires and the never-painted report is
suppressed by its own guard. A complete, accepted payload therefore animates
a grey box forever with nothing in the console.

Reports it on the existing paint deadline, measured from the last operations
to land, so a root still missing when it expires is a root that is not
coming. The check reads the live components model rather than scanning the
operations for the id, which covers every way a root can fail to resolve —
not only a payload that never named one — and the message says which of the
two it is.

Keeps the fixed root id: A2UI v0.9 dropped v0.8's rootComponentId, and
createSurface carries only surfaceId, catalogId and theme, so a payload has
no way to declare its own entry point. Deriving one instead would pick
silently and wrongly whenever several components are unreferenced. The id
moves to a single ROOT_COMPONENT_ID constant so the three sites that
hard-coded the string, and the new report, agree by construction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 17:49:22 -05:00
Benjamin Taylor 77dc28ea2c fix(a2ui): resolve a surface id the same way in both renderer paths (refs OSS-1048)
The two paths disagreed. React read a top-level `operation.surfaceId` first and
only then the nested v0.9 keys. The web-components path read the nested keys
only, via normalizeOperations, and never looked at a top-level id at all. So
one payload grouped under its own id in React and under "default" in the Lit
and Angular renderers.

Nested wins in both now, with a top-level id as the fallback when the payload
carries none.

Nested is the correct half of that choice, not a coin toss: MessageProcessor
creates the surface from the nested id. Grouping by a top-level id instead
files the operations against a surface that createSurface never made, and an
unknown surface id renders A2UIRenderer's null fallback. That is a card that
paints nothing, which is what the previous commit taught the renderer to
report.

So the old React order could produce the silence, and the missing-surface
report is what the new test uses to prove the grouping agrees with what got
created.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 17:42:52 -05:00
Benjamin Taylor 0f7ee3501a fix(react-core): report an A2UI card that receives operations and paints nothing (refs OSS-1048)
Two ways an A2UI surface can render nothing without saying a word.

The operations arrive and no paint follows. The renderer already waits 8s for
the surface to report its first paint before dropping the loader, so reaching
that fallback is itself the signal that nothing painted. Report it there, and
use what surfaceHasRenderableContent already knows to say which half is
missing: no updateComponents at all, or bound components whose updateDataModel
never carried a value.

The operations name a surface that was never created. A2UIRenderer renders its
fallback for an unknown surface id and that defaults to null, so the card is
absent and the log is empty. processMessages is synchronous, so a surface
still missing after it was never created.

The second report is deferred a task and re-checked, because operations stream
and a snapshot can reach the processor before the createSurface that gives it
somewhere to go. Removing both the deferral and the re-check makes the
mid-stream test fail.

Neither report covers a surface that exists and holds complete components and
still draws nothing. That case needs the component catalog, which lives in
@a2ui/web_core, and it stays silent for now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 17:42:52 -05:00
Benjamin Taylor 22fa691e80 fix(react-core): warn when a tool call has no renderer instead of rendering nothing (refs OSS-1048)
A tool call that matches no registered renderer returns null. Nothing else
happens: no console output, no empty-state card, and a finished turn. The only
signal is a blank message container in the chat, which a developer has to
notice in the DOM and then guess at.

Report it in development. The warning names the tool the agent called, lists
the renderer names that are registered, and points at useRenderTool and
useDefaultRenderTool. When the cause is a name that does not match, that is
the whole diagnosis.

Rendering behavior is unchanged. Auto-painting a default card would leak tool
names and raw args into production chat, which is why the resolver returns
null, and that decision stands.

The report is deferred one task past the commit that recorded the miss, then
re-checks the registry. useRenderTool registers from an effect in the
component that renders the chat, and React runs child effects before parent
ones, so at effect time the resolver can see an empty registry even though the
app did register a renderer. Removing that re-check makes two of the new tests
fail on exactly that false positive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 17:42:52 -05:00
Ben Taylor 313a454b7e fix(react-core): wait for the user on humanInTheLoop provider tools (#6851)
## Summary

A human-in-the-loop tool declared through the `humanInTheLoop` **prop**
on `<CopilotKitProvider>` never waited for the user. The handler
resolved `undefined` the moment the agent invoked it, and the renderer
was registered unwrapped, so it never received a working `respond`. The
card appeared once at `Complete` over dead controls while the agent was
told the tool had succeeded.

This is not a regression. The placeholder handler is unchanged since the
first v2 provider commit (`a2ef51aaf3`, #2794), so this documented prop
has never waited. The reference page describes it as self-sufficient:
"Declarative human-in-the-loop tool definitions. Each becomes both a
tool handler and a tool call renderer." The `useHumanInTheLoop` hook
path is unaffected and has always worked.

## Changes

`packages/react-core/src/v2/providers/CopilotKitProvider.tsx`, matching
`useHumanInTheLoop`:

- park the tool call until the render calls `respond`, keyed by tool
call id so one call of a tool cannot resolve another
- wrap the registered renderer so it receives the full prop contract,
with `respond` live only while the status is `Executing`
- reject with `Human-in-the-loop interaction aborted` on abort, so core
records an explicit error tool result rather than an empty success
(#5554)
- keep the invoked tool's name for a wildcard (`"*"`) registration
instead of overwriting it with `"*"`

Two existing unit tests asserted the placeholder behavior (the
`console.warn`, and render identity). They now assert the waiting
contract and that the wrapper renders the caller's component.

## Testing

**New: `CopilotKitProvider.hitl.e2e.test.tsx` (4 tests).** These mount
the real pipeline — `CopilotChat` -> tool calls view -> the provider's
registered renderer — rather than calling the renderer directly, so a
mismatch between the key the handler parks under and the tool call id
the pipeline supplies cannot pass.

All four fail on `main` for the right reason (`Unable to find an element
by: [data-testid="hitl-respond"]` — the respond button never appears),
and pass with the change.

Each mechanism was mutation-checked separately:

| Mutation | Result |
| -- | -- |
| handler resolves immediately (the old placeholder) | all 4 fail |
| renderer registered unwrapped | all 4 fail |
| wildcard branch removed | only the wildcard test fails |
| park under the tool name instead of the tool call id | the 2 tests
needing a live `respond` fail |

**Suite, types, format**

```
$ node ../../node_modules/vitest/vitest.mjs run src/v2/providers/__tests__/
 Test Files  17 passed (17)
      Tests  173 passed (173)

$ tsc --noEmit
(clean)
```

`oxfmt` applied to every changed file. The full `react-core` run also
has 7 failures across `CopilotChatView.pinToSend.test.tsx` and
`use-pin-to-send.test.tsx` (`ResizeObserver is not a constructor`).
Those are pre-existing: I removed this change from the working tree and
ran those two files on clean `main`, and they fail identically.

**Live, in `examples/v2/react/demo` against a real agent and a rebuilt
dist**

| Page | Before | After |
| -- | -- | -- |
| provider prop, multi-route REST | card at `complete`, `respond`
undefined, console warned, agent said "Please pick a time slot in the
scheduler UI to confirm" over disabled buttons | parks at `executing`
with live buttons for 17s, then "Booked a call with Sam about the
Waterfall project meeting for Tue 14:00" |
| provider prop, single route | same, and worse: "…has been scheduled"
before anyone approved | parks, then reports the slot actually chosen
(Wed 09:30) |
| `useHumanInTheLoop` hook, multi-route REST | already correct |
unchanged: `inProgress` -> `executing` -> `complete` with the chosen
slot reaching the agent |

## Credit

@Tushar-Khandelwal-2004 diagnosed this path and implemented the parking
handler and the abort semantics first, in #4955. That PR predates the
wildcard-name work and the current `main`, and its review asks were
still open, so this lands the same fix rebased on `main` with the
wildcard branch and pipeline-level tests added. #4955 is now closed in
favor of this PR, with the credit staying with them.

## Related

- #4955 — the earlier implementation of this fix (closed in favor of
this PR)
- #6527 — the identical Vue defect; the two changes agree on semantics
- #4953 — the reporter hit the hook path, which does not reproduce on
`main`; this is the confirmed provider-prop half of that report

Angular already implements this path (`addHumanInTheLoop` ->
`#hitl.onResult`), so React was the last framework missing it.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Human-in-the-loop tool calls now pause until a user responds.
* Interactive renderers receive tool metadata and a callback for
submitting responses.
  * Wildcard tools display the specific tool name that was invoked.
  * Aborted tool calls now return a clear error result.

* **Bug Fixes**
* Multiple pending tool calls can be resolved independently by their
call ID.
* Interactive handlers no longer emit an unnecessary warning while
awaiting a response.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 17:39:55 -05:00
Tyler Slaton ef2f819a46 docs(learning): explain Inspector setup and platform requirements 2026-09-08 15:38:35 -07:00
Benjamin Taylor 2031643fa1 fix(react-core): wait for the user on humanInTheLoop provider tools
The `humanInTheLoop` prop on `CopilotKitProvider` registered a handler that
warned and resolved `undefined` the moment the agent invoked it, and it
registered the renderer unwrapped, so the render never received a working
`respond`. A tool declared that way jumped straight to Complete over dead
controls while the agent was told the tool had succeeded. The placeholder is
unchanged since the first v2 provider commit (a2ef51aaf3), so this documented
prop has never waited on the user. It fails the same way on both transports.

Match the `useHumanInTheLoop` semantics: park the tool call until the render
calls `respond`, expose `respond` only while the status is Executing, and
reject with 'Human-in-the-loop interaction aborted' when the run is aborted so
core records an explicit error tool result (#5554). Pending interactions are
keyed by tool call id, and a wildcard registration keeps the invoked tool's
name rather than "*".

Two existing unit tests asserted the placeholder behavior (the console warning
and render identity). They now assert the waiting contract and that the wrapper
renders the caller's component.

Mirrors the Vue fix in #6527. Related: #4953 (the provider-prop half of that
report) and #4955.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 17:31:28 -05:00
Ben Taylor 407249d4fc fix(react-core): let a custom catch-all renderer return null (#6942)
## Summary

`useDefaultRenderTool`'s `render` was typed to return
`React.ReactElement`. A caller who wants to render only *some* tool
calls therefore could not return `null` to suppress the built-in default
for the rest — the value flowed through correctly at runtime, but the
type rejected it.

This widens the public `render` return type, and the wrapper local that
carries the user's value, to `React.ReactElement | null`.

```diff
-    render?: (props: DefaultRenderProps) => React.ReactElement;
+    render?: (props: DefaultRenderProps) => React.ReactElement | null;
```

The reference page hand-writes the same signature, so it is updated to
match, with one behavior bullet describing what `null` does.

## Scope, and its relationship to #6533

#6533 already widens the same return type to `React.ReactElement | null`
in `defineToolCallRenderer.ts` and `use-render-tool.tsx`. It does
**not** touch `use-default-render-tool.tsx`, which is the remaining gap
and the whole of this PR. There is **no file overlap**, so the two can
land in either order.

A structural sweep of `react-core/src/v2` for renders still typed `=>
React.ReactElement` with no `| null` confirms this leaves nothing behind
on this surface:

```
types/defineToolCallRenderer.ts:40,48,56   <- #6533
hooks/use-render-tool.tsx:41,72,109        <- #6533
hooks/use-default-render-tool.tsx:152      <- the deliberate bridge cast, below
hooks/use-interrupt.tsx:89                 <- different surface, out of scope
components/chat/CopilotChatMessageView.tsx:418  <- different surface, out of scope
```

The `as unknown as` cast into `useRenderTool` is deliberately left in
place: `useRenderTool` still requires a `ReactElement` return on `main`
(verified again after the rebase — `use-render-tool.tsx:41`). Once #6533
lands, that cast can be tightened. The bridge comment is updated to say
so.

`DefaultToolCallRenderer`'s own return type stays `React.ReactElement` —
the built-in default always renders an element.

The Vue counterpart needs no equivalent change: its `render` already
returns `VNodeChild`, which admits `null`, and
`reference/vue/hooks/useDefaultRenderTool.mdx` already matches.

## Why the guard is a type test, not a runtime test

TypeScript types are erased, so a `null`-returning render forwards
identically before and after the widening. The runtime test passes
against un-widened source, which makes it worthless as a guard for this
change. So the real guard is `use-default-render-tool-types.test-d.ts`,
using the `expectTypeOf` + `toEqualTypeOf` convention already documented
in `v2/__tests__/headless-type-exports.test-d.ts`.

`toEqualTypeOf` is required rather than assignability: a function
returning `ReactElement` **is** assignable to one returning
`ReactElement | null`, so an assignability check would pass against the
un-widened type and assert nothing.

The `.test-d.ts` basename is outside vitest's `include` globs, so
nothing there executes; `tsc --noEmit` (`check-types`) is what reads it.
Confirmed on this base:

```
$ vitest list --filesOnly | grep -c "test-d"
0
$ grep -n include -A4 packages/react-core/vitest.config.mjs
    include: [
      "src/**/__tests__/**/*.{test,spec}.{ts,tsx}",
      "src/**/*.{test,spec}.{ts,tsx}",
    ],
$ grep include packages/react-core/tsconfig.json
  "include": ["src/**/*"],
```

The runtime test is kept as well, since it still covers prop adaptation
and forwarding.

## Testing

All numbers below were re-measured after the rebase onto `main`
(`42494df`).

**Mutation check of the type guard** — revert the widening in the
source, confirm the guard goes red:

```
########## RUN A: rebased HEAD as-is ##########
total errors: 63
--- errors in touched files ---
  none

########## RUN B: MUTATION - widening reverted in source ##########
total errors: 65
--- guard file errors (expect FAIL) ---
use-default-render-tool-types.test-d.ts(26,3): error TS2344:
  Type '((props: DefaultRenderProps) => ReactElement<...> | null) | undefined'
  does not satisfy the constraint
  '"Expected: undefined, Actual: never" | "Expected: function, Actual: never"'.
use-default-render-tool.test.tsx(150,30): error TS2322:
  Type 'Mock<({ status }: DefaultRenderProps) => null>' is not assignable to
  type '(props: DefaultRenderProps) => ReactElement<...>'.
  Type 'null' is not assignable to type 'ReactElement<...>'.
```

The guard fails when the widening is reverted, and the two new errors
are exactly the guard plus the runtime test's own use of it. Nothing
else moves.

**Typecheck** (`tsc -p packages/react-core --noEmit`) — error set
byte-identical to pristine `origin/main` in the same worktree, none in
the touched files:

```
########## RUN C: pristine origin/main baseline ##########
total errors on pristine main: 63
=== diff: pristine-main errors vs HEAD errors ===
IDENTICAL -> the change introduces no new type errors
```

The 63 are pre-existing worktree noise: `react-core` resolves
`@copilotkit/core` and `@copilotkit/shared` from a sibling checkout's
`dist`, so unrelated exports read as missing. They are present on
pristine `origin/main` in the same worktree, which is what the diff
above shows.

**Target test file:**

```
 ✓ src/v2/hooks/__tests__/use-default-render-tool.test.tsx (13 tests) 38ms
 Test Files  1 passed (1)
      Tests  13 passed (13)
```

**Broader `src/v2/hooks` + `src/v2/types`** — failure counts identical
to pristine `origin/main` in the same worktree, plus exactly the one new
passing test:

```
=== BASELINE (pristine origin/main in this worktree) ===
 Test Files  22 failed | 17 passed (39)
      Tests  9 failed | 201 passed (210)

=== WITH my change ===
 Test Files  22 failed | 17 passed (39)
      Tests  9 failed | 202 passed (211)
```

**Lint:** `oxlint packages/react-core/src/v2/hooks/` — `Found 62
warnings and 0 errors.` (all pre-existing exhaustive-deps warnings, none
in the touched files).

**Formatting:** `oxfmt --check` on the three source/test files — `All
matched files use the correct format.` `oxfmt` does not process `.mdx`,
so the reference page is out of its scope.

**Public-API manifest:** no regeneration needed —
`scripts/release/public-api/manifest.v1.json` records no type signatures
and does not mention `useDefaultRenderTool` (`grep -c ReactElement` →
`0`).

## Provenance

Extracted from #5509 (@ataibarkai), which is 3915 commits behind `main`
and being closed. That PR also changed `defineToolCallRenderer`'s schema
default from `def.name === "*" && !def.args ? z.any() : def.args` to
`def.args ?? z.any()`. **That change is deliberately not carried here**
— it alters runtime behavior for named renderers declared without `args`
(from `args: undefined` to `args: z.any()`) and deserves its own PR and
its own verification.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Custom renderers can now return `null` to suppress output when no UI
should be displayed.

* **Documentation**
* Updated `useDefaultRenderTool` guidance to describe null-return
behavior and selectively rendering tool calls.

* **Tests**
* Added coverage confirming null-render behavior and forwarded renderer
properties.
  * Added compile-time validation for supported renderer return types.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 17:27:44 -05:00
Ben Taylor ca00b1b12e fix(runtime): do not treat an unread stream as a pre-parsed body in the express bridge (#6490)
Follow-up to #6489. Same root cause — flags that report "the socket has
the bytes" being read as "the application consumed the stream" — in a
second, independent code path found while auditing for other copies of
the check. Different trigger and different file, so it ships separately.
**#6489 has since merged**, so this is the remaining half.

## Problem

`hasPreParsedBody` in
`packages/runtime/src/v2/runtime/endpoints/express-fetch-bridge.ts`
decides whether to rebuild the Fetch `Request` from `req.body` instead
of streaming the Node request:

```ts
if (req.body === undefined || req.body === null) return false;
const state = (req as any)._readableState;
return Boolean(
  req.readableEnded || req.complete || state?.ended || state?.endEmitted,
);
```

`req.complete` and the private `_readableState` flags are set by the
HTTP parser once the socket holds every byte, whether or not anything
read them — so they do not establish that a parser ran.

And `req.body` being set does not establish it either. **body-parser 1.x
(Express 4)** assigns `req.body = req.body || {}` at
[`lib/types/json.js:108`](https://github.com/expressjs/body-parser/blob/1.20.3/lib/types/json.js#L108)
— *before* its `hasBody` (111) and `shouldParse` (120) checks. So a
request it declines to parse arrives at the bridge with `req.body ===
{}` and a full, unread stream. `req.complete` satisfied the check, the
bridge rebuilt the request as `JSON.stringify({})`, and the real payload
was silently dropped.

The reachable cases are any non-JSON POST behind a global
`express.json()` — multipart uploads (audio transcription),
`text/plain`, custom content types.

On Express 5 / body-parser 2.x the declined path sets `req.body =
undefined`, so the function's first guard already returns false. This is
an Express 4 exposure specifically.

## Fix

Gate on `req.readableEnded` alone — the one flag that only becomes true
after a parser drains the stream to its end. When a parser genuinely
ran, it is true and the rebuild path is taken exactly as before; when
`req.body` is a placeholder over an unread stream, the request now falls
through to the generic streaming handler, which reads the real body.

## Testing

Reproduced on **express 4.22.2 / body-parser 1.20.6** (installed outside
the repo, since the workspace pins express 5), with a global
`express.json()` and an awaiting middleware standing in for a real app's
auth/routing layer. `current` is the predicate on `main`, `fixed` is
this change:

| request | `req.body` | `readableEnded` | `complete` | `current` |
`fixed` | what reaches the runtime today |
|---|---|---|---|---|---|---|
| `application/json` | `{"a":1}` | `true` | `true` | `true` | `true` |
`{"a":1}` — correct |
| `multipart/form-data` | `{}` | `false` | `true` | `true` | `false` |
**`{}` — payload dropped** |
| `text/plain` | `{}` | `false` | `true` | `true` | `false` | **`{}` —
payload dropped** |

Two regression tests added to `express-fetch-bridge.test.ts`. They
reproduce that state without adding an Express 4 dependency: a
middleware that assigns `req.body = req.body || {}` without reading the
stream, then an awaiting middleware so the parser sets `complete`.

Against the predicate on `main`, both fail with exactly the reported
symptom:

```
 × streams a text/plain body instead of rebuilding it from an empty req.body
 × streams a multipart body instead of rebuilding it from an empty req.body
 AssertionError: expected '{}' to be 'hello'
```

With the fix, the whole bridge suite passes — including the 14
pre-existing tests, of which `receives JSON body when express.json()
runs first` is the guard on the genuinely-pre-parsed path:

```
$ vitest run src/v2/runtime/__tests__/express-fetch-bridge.test.ts
 Test Files  1 passed (1)
      Tests  16 passed (16)
```

Full `packages/runtime` suite: `132 passed | 6 failed` — the 6 failing
files are the same ones that fail on a clean checkout of the base commit
in this worktree (`parseInspectorMetadataV1 is not a function`, a stale
cross-package `dist`), unrelated to this change. `oxfmt` clean; `oxlint`
adds no new warnings (16 before and after, all pre-existing in this
file).

*(Those counts are the original pre-rebase run, against the old base.
The rebased numbers are below and differ only because the base moved
1304 commits.)*

### Re-verified after rebase

The branch was 1304 commits behind main, so I rebased it onto
`origin/main` (`0f6c616` → `1a93586`) and re-ran everything. Neither
changed file was touched on main since the old merge-base, and the
rebase took no conflicts.

A passing test does not prove the mechanism is live, so I
mutation-checked both halves: with the old predicate restored, exactly
the new tests fail and every pre-existing test still passes.

| | tests | mutation check (old predicate restored) |
|---|---|---|
| this PR — `express-fetch-bridge.test.ts` | 16/16 pass | 2 fail:
`expected '{}' to be 'hello'`, `expected '{}' to contain 'AUDIOBYTES'` |
| #6489 on main — `request-handler.test.ts` | 5/5 pass | 2 fail:
`consumedBefore` flips `false` → `true` |

#6489's fix survived the v1 → `v1-deprecated/` tree move and is still
live at
`packages/runtime/src/v1-deprecated/lib/integrations/node-http/request-handler.ts:93`.

**No regression from the rebase.** The full `packages/runtime` suite and
`tsc --noEmit` give identical results on this branch and on a clean
`origin/main` worktree:

| | full suite | `tsc --noEmit` |
|---|---|---|
| `origin/main` | 72 failed files, 380 failed, 1472 passed | 65 errors |
| this branch | 72 failed files, 380 failed, **1474** passed | 65
errors, byte-identical set |

The +2 are the new tests. The shared failures are the stale
cross-package `@copilotkit/shared` dist in the worktree
(`parseInspectorMetadataV1`, `computeSamplingMeta`), not this change —
none of the 65 type errors is in either changed file, and CI builds
`shared` fresh and reports `check-types` green. `oxfmt` clean; `oxlint`
16 warnings and 0 errors, the same count before and after.

I also re-ran the structural search for other copies of the predicate
across the 1304 new commits. `_readableState` and `endEmitted` now
appear only inside the explanatory comments at the two fixed sites, so
there is no third copy.

## Note on the duplication

#6489 and this PR now hold the same one-line predicate in two files. I
did not extract a shared helper: the other copy lives in the v1 tree,
now at `packages/runtime/src/v1-deprecated/lib/integrations/`, and
importing it into `v2/runtime` would couple v2 to code that is on its
way out. Each site carries a comment explaining the flag semantics
instead.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Fixed Express request handling so text and multipart request bodies
are preserved when legacy body-parsing middleware assigns an empty body
without consuming the request stream.
- Prevented valid request payloads from being replaced with empty data
when the original request stream remains available.
- **Tests**
- Added coverage for legacy Express body-parser scenarios involving text
and multipart requests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 17:27:39 -05:00
Benjamin Taylor 82cd7c01f7 fix(react-core): let a custom catch-all renderer return null
`useDefaultRenderTool`'s `render` was typed to return `React.ReactElement`,
so a caller who wanted to render only some tool calls could not return
`null` to suppress the built-in default for the rest. The value already
flowed through correctly at runtime; only the type rejected it.

Widen the public `render` return type, and the wrapper local that carries
the user's value, to `React.ReactElement | null`. The `as unknown as` cast
into `useRenderTool` stays, because `useRenderTool` still requires a
`ReactElement` return on main; PR #6533 widens that hook, after which the
cast can be tightened.

Guarded by a `.test-d.ts` assertion rather than a runtime test: types are
erased, so a null-returning render forwards identically before and after
the widening and a runtime test would assert nothing.

Extracted from #5509, which is otherwise stale.

Co-authored-by: Atai Barkai <atai.barkai@gmail.com>
2026-09-08 16:39:06 -05:00
Ben Taylor 30aba6b959 docs(showcase): state the frontend-tool requirement for the last nine frameworks (closes OSS-1036) (#6874)
## Problem

`generative-ui/tool-based` is the terminal page every onboarding run
fetches, on every framework route (OSS-1034). Its `## How it works in
code` section is one `<FrameworkSetup concept="frontend-tools-setup" />`
call that resolves a per-framework snippet. Nine frameworks shipped no
snippet, so the section rendered nothing and the `.md` output emitted
`<!-- setup skipped: ... -->`. That silence meant two different things
at once: "this framework needs no agent-side wiring" and "it needs
wiring and nobody wrote it down".

Verified on the live host before the change: all nine emitted the skip
comment, at ~2.4KB per page.

One of the nine is `built-in-agent`, which is `ROOT_FRAMEWORK`. So the
page carrying the gap included the unscoped default,
`https://docs.copilotkit.ai/generative-ui/tool-based.md`.

## Solution

A snippet for each of the nine. Each verdict was read out of the pinned
adapter rather than out of the docs, which is what the ticket asked for.

**Nothing to wire on the agent** — confirmed in the adapter:

| framework | the line that decides it |
| --- | --- |
| `ag2` | `autogen/ag_ui/adapter.py` `run_stream` builds `client_tools`
from `command.incoming.tools` |
| `mastra` | `@ag-ui/mastra@1.1.0` reduces `input.tools` into
`clientTools` for `agent.stream()` |
| `strands` | `ag_ui_strands@0.2.2` calls
`sync_proxy_tools(agent.tool_registry, input_data.tools, ...)` |
| `strands-typescript` | `@ag-ui/aws-strands@0.2.3` registers a proxy
tool per forwarded tool in `toolRegistry` |

Both Strands adapters refuse to overwrite a native tool of the same
name, so each snippet says to keep the `useComponent` name distinct.

**Real wiring:**

- `agno`. Its AG-UI interface never reads `RunAgentInput.tools`. Checked
against `agno==2.6.19`: the only tool path out of
`agno/os/interfaces/agui/` is
`RunPausedEvent.tools_awaiting_external_execution`. So a component needs
a declared `@tool(external_execution=True)` stub and the `Agent` needs a
`db` to hold the paused run.
- `deepagents`. `sdk-python/copilotkit/copilotkit_lg_middleware.py`
merges `copilotkit.actions` into `request.tools` inside
`wrap_model_call`, so `middleware=[CopilotKitMiddleware()]` is
load-bearing. Its snippet lives in the docs-only tree, because
`deepagents` has no integration package.

**Mode-dependent:**

- `built-in-agent`. Config mode merges `input.tools` for you
(`packages/runtime/src/agent/index.ts`). A factory owns the model call
and forwards nothing, so it has to pass them itself. Both halves are in
the snippet, plus the collision rule: a frontend tool whose name matches
a server tool is dropped in favor of the server executor.

**In-repo evidence only:**

- `ms-agent-dotnet`, `ms-agent-harness-dotnet`. Their own
`gen-ui-tool-based` agents render charts with an empty tool list
(`tools: []` / `Tools = []`) and their instructions name the chart
tools. No .NET SDK was available here to read
`Microsoft.Agents.AI.Hosting.AGUI.AspNetCore` directly.

Six of the nine snippets also carry the "Tell the model when to call it"
step, which is not filler. `GEN_UI_TOOL_BASED_PROMPT` in
`showcase/integrations/built-in-agent/src/lib/factory/demo-prompts.ts`
records what happens without it: the model emits `value: 0` for every
point and renders a chart with no bars.

## Tests

- `REQUIREMENT_NOT_ESTABLISHED` is now empty, and the docblock says a
new framework arriving without a snippet is fixed with a snippet, not
with a name.
- The shape test pins all six auto-forwarding frameworks, plus the three
that do not fit that shape (`agno`, `deepagents`, `built-in-agent`).
- `setup-concept.test.ts` used `ag2` as its example of an unbundled
concept, so closing the last gap would have turned it red for the right
reason. It now uses a concept name that is deliberately never bundled.

## The gate never ran in CI

Second commit. No workflow runs the shell-docs vitest suite:
`test_integration-docs.yml` runs exactly one test file,
`test_unit-showcase.yml` covers harness and shell-dashboard only, and
`showcase_build_check.yml` matches `showcase/**` but only builds images.
Neither path filter listed the snippet files either. So the ratchet
added in #6777 was local-only.

Added a scoped job and widened both filters. The job runs only the two
setup-concept files on purpose: the full shell-docs suite is not green
on `main`, and gating every snippet change on unrelated failures would
be worse than no gate.

## Verification

Ran against a local `shell-docs` dev server on this branch. Expected
`setup skipped` count is 0 everywhere:

```
ag2                        skip=0 bytes=3825
agno                       skip=0 bytes=4299
deepagents                 skip=0 bytes=3786
mastra                     skip=0 bytes=3857
ms-agent-dotnet            skip=0 bytes=3706
ms-agent-harness-dotnet    skip=0 bytes=4236
strands                    skip=0 bytes=4026
strands-typescript         skip=0 bytes=4001
built-in-agent (root path) skip=0 bytes=4723
```

`vitest run` on the two setup-concept files: 12 passed. `npm run
typecheck` and `npm run lint` both exit 0.

Four tests fail in the full shell-docs suite (`llm-text.test.ts` mastra
tool-rendering, three in `angular-docs-content.test.ts`). All four fail
identically at the pre-change baseline in the same worktree, so they are
pre-existing on `main` and untouched by this branch.

## Two findings this turned up, not fixed here

1. **The `agno` showcase demo contradicts its own snippet.** Every
`useFrontendTool` tool in agno's demos has a matching
`external_execution` stub, but the `useComponent` ones do not:
`render_bar_chart`, `render_pie_chart`, `query_notes` and
`highlight_note` have no backend declaration, and
`_run_main_agent_hitl_aware` in `src/agent_server.py` cannot forward
them. Its e2e spec cannot catch this, because it asserts only that an
`svg` is visible inside an assistant message, which any icon satisfies.
2. **Four frameworks redirect the HTML page away.** For `ag2`, `agno`,
`deepagents` and `mastra`, `/<slug>/generative-ui/tool-based` 301s to
`/<slug>/generative-ui/tool-rendering`. Confirmed on the live host as
well as locally, so it predates this branch. Their `.md` output serves
the page normally, which is the path onboarding runs fetch, but a human
browsing those four never reaches it.

Closes OSS-1036.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Documentation**
- Added frontend tool setup guides for AG2, Agno, Built-in Agent,
Mastra, Microsoft Agent integrations, Strands, and Deep Agents.
- Documented tool registration, prompting, execution behavior,
persistence, and middleware configuration.

- **Tests**
- Expanded coverage for frontend tool setup documentation across
supported integrations.
- Updated setup concept tests to use a stable, intentionally unbundled
example.

- **Chores**
- Added automated workflow coverage for setup documentation and related
tooling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 16:36:44 -05:00
Ben Taylor b0c0b50826 docs(microsoft-agent-framework): document backend approval interrupts (#6922)
Closes #2770.

## Problem

Our Microsoft Agent Framework human-in-the-loop page taught exactly one
pattern: register a frontend tool whose name matches a backend tool.
That is why #2770 was filed asking for a feature we already ship, and
why two more people asked for an update on it.

Backend-raised approvals have worked for months. Mark a tool with
`approval_mode="always_require"` (Python) or wrap it in
`ApprovalRequiredAIFunction` (.NET), and the agent ends the run with an
AG-UI interrupt that `useInterrupt` renders. No name matching. Nothing
on the docs site said so.

## What changed

Split the page the way Mastra's already is, and add the missing half:

- `human-in-the-loop/index.mdx` — picks between the two patterns. It
keeps `/microsoft-agent-framework/human-in-the-loop` resolving, so the
three existing pages that link there need no edit.
- `human-in-the-loop/interrupt-flow.mdx` — new; the approval path.
- `human-in-the-loop/tool-based.mdx` — the old page, retitled, content
unchanged.
- `custom-look-and-feel/headless-ui.mdx` — one sentence pointing backend
approvals at the new page.

The new page records four details that are easy to get wrong from
reading the source. I got each of them wrong myself before running it:

- `reason` is `"tool_call"`, not `"approval"`.
- The tool name and arguments live under `metadata.agent_framework`
**only on Python**. .NET sends no such field and names the tool in
`message`.
- `cancel()` and `resolve({ approved: false })` both leave the tool
unrun, but cancel ends the run while a false approval lets the agent
keep talking.
- `renderInChat` defaults to `true`, so the UI renders nothing —
silently, no warning — unless a `<CopilotChat />` is mounted.

## Testing

Every claim on the new page was executed, not inferred.

**End to end against a real agent.** Published `@copilotkit/react-core`
over real HTTP against a real Microsoft Agent Framework agent on
released `agent-framework-ag-ui` 1.2.2, with a tool marked
`approval_mode="always_require"`, served through the real AG-UI FastAPI
endpoint. A deterministic stub chat client stands in for the model, so
no API key is needed.

```
✓ CopilotKit <-> real Microsoft Agent Framework approval (2 tests)
  ✓ renders the backend approval and, on approve, the backend runs the tool
  ✓ on cancel, the backend does not run the tool
  Tests  2 passed (2)
```

Approving produced `TOOL_CALL_RESULT ... "content":"Deleted notes.txt."`
from the backend and surfaced it in the frontend. Cancelling produced no
tool result.

**Version floors are measured, not read off a changelog.** The same test
passes on `@copilotkit/react-core` 1.61.2 and fails on 1.61.1 (`Unable
to find an element by: [data-testid="reason"]`), which is where the
1.61.2 in the requirements table comes from. `agent-framework-ag-ui`
1.2.0 ships byte-identical interrupt-conversion logic to the 1.2.2 I
ran. `AGUI.Server` 0.0.6's shipped DLL contains the `tool_call` reason
and the `Approval required for tool call` message, and ag-ui's own
verified .NET baseline shows the same wire shape.

**Mutation-checked.** Changing the backend tool's return string fails
the approve test; clicking Approve in the cancel test fails it. An
earlier version of the cancel test passed under that mutation — it
settled with a bare `setTimeout` outside `act()` — so it was rewritten
to poll with `waitFor` and require a timeout.

**Docs suite.** Generators plus the node-environment tests that cover
routing and the nav tree:

```
 Test Files  4 passed (4)
      Tests  30 passed (30)
```
(`sitemap`, `page-tree-bridge`, `llms.txt`, `llms-mdx`)

All three routes appear in the generated search index, including the
preserved index URL:

```
microsoft-agent-framework/human-in-the-loop
microsoft-agent-framework/human-in-the-loop/interrupt-flow
microsoft-agent-framework/human-in-the-loop/tool-based
```

The jsdom-environment tests could not run locally — this checkout's
`showcase/shell-docs` install predates `jsdom`, and it resolves through
symlinks to the main checkout. CI covers them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Added a Human-in-the-Loop section for Microsoft Agent Framework
integrations.
* Documented interrupt-based and tool-based workflows, including
approval, denial, cancellation, and frontend interaction guidance.
* Added details on approval-gated backend tools, interrupt payloads, and
rendering requirements.
  * Updated the tool-based guide’s title and description.
* Added the new section and page ordering to the documentation
navigation.
* Clarified how headless UI examples should render backend-tool
approvals.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 16:36:40 -05:00
Ben Taylor a278237447 docs(showcase): explain the AgentCore 401 "Missing Authentication Token" (#6920)
## What

Adds a **Troubleshooting → `HTTP 401: Missing Authentication Token`**
section to the AWS AgentCore deploy guide.

Closes #2912.

## Why

#2912 followed the AWS Strands quickstart, pointed the runtime at a
deployed AgentCore endpoint, and got:

```
Agent execution failed: Error: HTTP 401:
{"jsonrpc":"2.0","error":{"code":-32001,"message":"Missing Authentication Token"},"id":"null"}
```

That string is AWS's own response to an unsigned request or an unmatched
route, not a CopilotKit or agent error — but nothing under `docs/`
mentioned it, so it reads like a framework bug. `grep -rin "missing
authentication\|401"
showcase/shell-docs/src/content/docs/integrations/aws-strands/` returned
zero hits before this change.

The section covers the four real causes:

1. `AGENTCORE_ACCESS_TOKEN` unset on the **runtime server** — the header
interpolates to `Bearer undefined`, which AWS rejects exactly like a
missing header.
2. An expired Cognito access token.
3. `AGENTCORE_ENDPOINT_URL` that stops short of `/invocations` — an
unmatched route, which API Gateway reports as `Missing Authentication
Token` rather than a 404.
4. A browser calling AgentCore directly, which cannot do SigV4/OAuth2
signing.

## Why two files

The AgentCore guide exists as two near-duplicate copies on `main`, and
**both are live**:

| File | Live route(s) |
|---|---|
| `docs/deploy/agentcore.mdx` | `/deploy/agentcore` |
| `snippets/integrations/agentcore/index.mdx` |
`/strands/deploy-agentcore`, `/langgraph/deploy-agentcore` |

`src/lib/mdx-registry.tsx:548` resolves a bare `<Content framework="…"
/>` to `integrations/agentcore/index.mdx`, and both framework stubs use
that bare form. The section lands in both so the two copies do not drift
further. Deduplicating them is out of scope here.

## Testing

**1. Both routes are live (so both copies need the section)**

```
$ for u in /deploy/agentcore /strands/deploy-agentcore /langgraph/deploy-agentcore; do ... done
200  /deploy/agentcore
200  /strands/deploy-agentcore
200  /langgraph/deploy-agentcore
```

**2. The snippet — not the deploy page — is what renders on the
framework routes**

```
$ curl -sL https://docs.copilotkit.ai/strands/deploy-agentcore | grep -o "AGENTCORE_ACCESS_TOKEN" | wc -l
2
```

Matches `snippets/integrations/agentcore/index.mdx:178,324`. Confirms
the `<Content>` resolution traced in `mdx-registry.tsx:548`.

**3. Both files compile as MDX**

```
$ node -e "compile(matter(read(f)).content, {jsx:true})"
OK   src/content/docs/deploy/agentcore.mdx 21245 chars
OK   src/content/snippets/integrations/agentcore/index.mdx 18447 chars
```

**4. Mutation-checked that the compile check is not vacuous** — dropped
the closing `</Callout>` from the new section:

```
mutated: dropped closing </Callout>
EXPECTED FAIL: Expected a closing tag for `<Callout>` (368:1-368:80)
```

Restored, and re-confirmed clean.

**5. Content test suites — no new failures**

`showcase/shell-docs` `npm test` cannot run locally (its `pretest`
generators need `showcase/scripts` deps that are not installed; this
fails identically on unmodified `main`). Ran the content-scanning suites
directly with `vitest` and baselined them against the same worktree with
the change reverted:

| | `llm-text` + `angular-docs-content` |
|---|---|
| unmodified `origin/main` | 12 failing (claude-sdk / google-adk /
angular) |
| with this change | **same 12**, same names |

All pre-existing, all unrelated to AgentCore. Full CI is the authority
here.

## Note

The two duplicated AgentCore copies are worth collapsing into one shared
partial in a follow-up.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Added troubleshooting guidance for AWS AgentCore `HTTP 401: Missing
Authentication Token` errors.
* Documented checks for runtime credentials, expired tokens, invocation
URL formatting, and request routing.
* Clarified that credentials belong on the runtime server and requests
should pass through Copilot Runtime.
* Noted that the issue applies to deployed environments, not local
quickstarts running on `localhost:8000`.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 16:36:37 -05:00
Benjamin Taylor 1a935861a8 fix(runtime): do not treat an unread stream as a pre-parsed body in the express bridge
`hasPreParsedBody` gates on `req.body` being set, then confirms the stream is
gone via `req.readableEnded || req.complete || _readableState.ended ||
_readableState.endEmitted`. The last three are set by the Node HTTP parser once
the socket holds every byte, whether or not anything read them, so they do not
establish that a parser ran.

That matters because `req.body` being set does not establish it either.
body-parser 1.x (Express 4) assigns `req.body = req.body || {}` before its own
`hasBody`/`shouldParse` checks, so a request it declines to parse — multipart
upload, text/plain — reaches the bridge with `req.body === {}` and a full,
unread stream. `req.complete` then satisfied the check, the bridge rebuilt the
request from `{}`, and the real payload was silently dropped.

Gate on `readableEnded` alone, which only becomes true after a parser drains the
stream to its end. Verified on express 4.22.2 / body-parser 1.20.6 that a
multipart POST behind a global `express.json()` arrives with `req.body === {}`,
`readableEnded === false`, `complete === true`, and that the genuinely parsed
JSON case is unaffected.

Same root cause as #6489, which fixed the equivalent check in the node-http
request handler. Kept as a separate local predicate rather than sharing one
helper, to avoid coupling `v2/runtime` to the v1 integration tree.
2026-09-08 16:32:38 -05:00
Benjamin Taylor 0748b1a8e8 docs(showcase): explain the AgentCore 401 "Missing Authentication Token" (closes #2912)
Reporters following a framework quickstart and then pointing the runtime at a
deployed AgentCore endpoint hit `HTTP 401: Missing Authentication Token` with no
guidance anywhere in the docs. The string is AWS's own response to an unsigned
request or an unmatched route, so it reads like a CopilotKit failure when it is
not.

Add a Troubleshooting section covering the four causes: an unset
AGENTCORE_ACCESS_TOKEN interpolating to `Bearer undefined`, an expired Cognito
token, an AGENTCORE_ENDPOINT_URL that stops short of /invocations, and a browser
calling AgentCore directly instead of going through Copilot Runtime.

The AgentCore guide exists as two near-duplicate copies -- docs/deploy/agentcore.mdx
serves /deploy/agentcore, and snippets/integrations/agentcore/index.mdx is what
<Content framework="..." /> renders at /strands/deploy-agentcore and
/langgraph/deploy-agentcore. Both are live, so the section lands in both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:22:45 -05:00
Benjamin Taylor 5f8da811d9 docs(microsoft-agent-framework): document backend approval interrupts
Our Microsoft Agent Framework HITL page taught only the frontend-tool
approach, where the frontend registers a tool whose name matches a backend
tool. Backend-raised approvals have worked for months -- mark a tool with
approval_mode="always_require" (Python) or ApprovalRequiredAIFunction (.NET)
and the agent raises an AG-UI interrupt that useInterrupt renders -- but no
page said so, so readers kept reaching for name matching (issue #2770).

Split the page the way Mastra's is split and add the missing half:

- human-in-the-loop/index.mdx picks between the two patterns. It keeps the
  existing /microsoft-agent-framework/human-in-the-loop URL working, so the
  three pages linking there are untouched.
- human-in-the-loop/interrupt-flow.mdx documents the approval path.
- human-in-the-loop/tool-based.mdx is the old page, retitled.

The interrupt page records details that are easy to get wrong from reading
the code: reason is "tool_call" rather than "approval"; the tool name and
arguments sit under metadata.agent_framework only on Python, while .NET
names the tool in message; cancel() and resolve({approved:false}) both skip
the tool but end the turn differently; renderInChat defaults to true and
renders nothing, silently, with no chat mounted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:22:34 -05:00
Benjamin Taylor 2e05c9ce81 docs(showcase): put the built-in-agent name-collision rule in the right mode (refs OSS-1036)
The snippet stated the collision rule under factory mode, which implied the
runtime resolves it for you there. It does not.

Config mode is where the runtime decides: `index.ts` builds the tool set from
`convertToolsToVercelAITools(input.tools)` and then spreads the configured
tools over it, so a shared name resolves to the backend tool. The rule now
sits in that step.

In factory mode the factory owns precedence, because nothing merges the two
lists on its behalf. The showcase's own TanStack factory shows one choice,
filtering forwarded tools through `!serverToolNames.has(t.name)`, but that is
its decision rather than runtime behavior. The factory step now says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:22:29 -05:00
Benjamin Taylor b3462e01fe docs(showcase): fix the using directives in the two .NET snippets (refs OSS-1036)
Both snippets named namespaces that do not resolve.

`Microsoft.Agents.AI.Harness` is the NuGet package name, not a namespace.
`AsHarnessAgent` and `HarnessAgentOptions` come from `Microsoft.Agents.AI`,
and no `.cs` file in the repo carries a `using` for the package name.

Both snippets also needed `using Microsoft.Extensions.AI`, which is where
`AsIChatClient()` and `ChatOptions` live. Every agent file in both showcase
columns uses exactly `Microsoft.Agents.AI` plus `Microsoft.Extensions.AI`.

Also define `HarnessMaxContextWindowTokens` and `HarnessMaxOutputTokens` in the
harness snippet, which referenced them without showing a value.

No .NET SDK is available here, so this was checked against the `using` blocks
of the twelve agent files that call `AsHarnessAgent`, not by compiling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:22:29 -05:00
github-actions[bot] 700fb1496b style: auto-fix formatting 2026-09-08 16:22:29 -05:00
Benjamin Taylor d090494ec4 ci(docs): run the framework setup-concept gate in CI (refs OSS-1036)
The coverage ratchet added for OSS-1036 never ran in CI. No workflow runs the
shell-docs vitest suite: `test_integration-docs.yml` runs exactly one test
file, `test_unit-showcase.yml` covers harness and shell-dashboard only, and
`showcase_build_check.yml` matches `showcase/**` but only builds images. On top
of that, neither path filter listed the snippet files the gate reads, so a new
`frontend-tools-setup.mdx` triggered nothing.

Add a scoped job and widen both filters. The job is deliberately limited to the
two setup-concept test files, because the full shell-docs suite is not green on
main and would gate every snippet change on unrelated failures.

The harness symlink step is the same one showcase/shell-docs/Dockerfile uses:
`generate-registry.ts` imports the catalog fold out of the harness tree, which
resolves `js-yaml` by walking up from `showcase/harness/`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:22:29 -05:00
Benjamin Taylor cf060da900 docs(showcase): state the frontend-tool requirement for the last nine frameworks (closes OSS-1036)
`generative-ui/tool-based` is the terminal page every onboarding run fetches,
and its `## How it works in code` section renders a per-framework snippet.
Nine frameworks shipped no snippet, so the section rendered nothing and the
silence meant both "no agent-side wiring is needed" and "wiring is needed and
nobody wrote it down".

Each verdict was read out of the pinned adapter rather than the docs:

- ag2: `run_stream` builds `client_tools` from `incoming.tools`.
- mastra: the adapter reduces `input.tools` into `clientTools`.
- strands, strands-typescript: a proxy tool per forwarded tool is registered
  in the agent's tool registry, and a native tool of the same name wins.
- agno: its AG-UI interface never reads `RunAgentInput.tools`, so a component
  needs an `external_execution=True` stub and a `db` for the paused run.
- deepagents: `CopilotKitMiddleware` merges `copilotkit.actions` into
  `request.tools`, so the middleware is load-bearing.
- built-in-agent: config mode forwards for you, a factory does not. It is also
  the root framework, so this is the unscoped default page.

The two .NET columns rest on their own demo agents, which render charts with
an empty tool list. No .NET SDK was available to read the NuGet hosting
package.

REQUIREMENT_NOT_ESTABLISHED is now empty. The shape test pins each snippet,
and setup-concept.test.ts no longer depends on ag2 being an open gap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:22:29 -05:00
Ben Taylor 42494df607 docs(react-core): stop pointing v1 appendMessage at non-public sendMessage (#6940)
## Summary

The v1 `useCopilotChat` JSDoc tells readers to use `sendMessage` instead
of `appendMessage`. `sendMessage` is not part of the public v1 return
type, so following that advice does not compile.

`packages/react-core/src/v1-deprecated/hooks/use-copilot-chat.ts:109`
explicitly omits it:

```ts
export type UseCopilotChatReturn = Omit<
  UseCopilotChatReturnInternal,
  | "messages"
  | "sendMessage"     // <- the JSDoc points readers here
  ...
```

`sendMessage` exists only on `useCopilotChatInternal`. The public hook
returns `appendMessage`, which is the working v1 programmatic-send path.

This replaces the misdirection with the v2 migration pointer and states
plainly what `appendMessage` is for. Comment-only change — no runtime
effect.

Found while closing #4215, where a user was told by our own docs to call
a method we do not export.

## The published page does not change yet

This fixes the source of truth. The generated page cannot be refreshed
until #6939 is resolved: running the generator today would also embed
the internal v1 deprecation banner ("AI CODING AGENTS: Never copy,
suggest, or generate these v1 APIs") into 20 public reference pages. I
deliberately excluded the regenerated `.mdx` files from this PR rather
than ship that. Once #6939 lands, a regenerate publishes this wording.

## Testing

**1. `oxfmt --check` — pass**

```
$ ./node_modules/.bin/oxfmt --check packages/react-core/src/v1-deprecated/hooks/use-copilot-chat.ts
Checking formatting...
All matched files use the correct format.
Finished in 33ms on 1 files using 18 threads.
```

**2. Generator reads this file successfully (26/26)** — confirms the
JSDoc edit is picked up, and that the only thing blocking publication is
#6939, not this change:

```
$ ./node_modules/.bin/tsx scripts/docs/gen.ts
Successfully autogenerated showcase/shell-docs/src/content/reference/v1/hooks/useCopilotChat.mdx from packages/react-core/src/v1-deprecated/hooks/use-copilot-chat.ts
All reference docs processed (26/26 succeeded)
```

The regenerated page contained the new wording as expected; I then
reverted the generated files per the section above.

**3. Diff is a single comment hunk** — verified with `git diff --stat`:
`1 file changed, 4 insertions(+), 1 deletion(-)`, all inside a JSDoc
block. No exported symbol, type, or runtime line touched, so no
typecheck or test surface is affected.

**4. Claim verified against `origin/main`,** not a local branch: `git
show
origin/main:packages/react-core/src/v1-deprecated/hooks/use-copilot-chat.ts`
confirms both the misdirecting line (`:70`) and the `Omit` (`:109-121`).

## Notes

- No changeset — comment-only.
- Branch name is a leftover misnomer
(`ben1/oss-docs-generator-v1-paths`); the change is the wording fix
only.
- @ataibarkai's #6653/#6654/#6655 stack would move this file back to
`packages/react-core/src/hooks/`. If that stack lands, this one-line
change needs carrying forward into the reapply.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Documentation**
  - Clarified the deprecated `appendMessage` option in `useCopilotChat`.
  - Documented its role for programmatic sending in v1.
  - Clarified the migration path for AG-UI format users moving to v2.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 16:16:43 -05:00
Mike Ryan 380ad1220a chore(examples): update CLI starter SDK versions (#6950)
Updates all 19 first-party starter roots in the Intelligence CLI catalog
to CopilotKit 1.70.2 and CopilotKit for Angular 0.5.2, including nested
TypeScript agents and the AgentCore runtime Lambda. Aligns AG-UI
client/core/encoder/proto pins and overrides to 0.0.59, matching the SDK
releases, and regenerates npm lockfiles.

The Angular SDK requires Angular 22, so the Angular starter also updates
its Angular/TypeScript toolchain and migrates its two icons to the
compatible @lucide/angular package. Registers 1.70.2 in the
managed-entitlement compatibility checks.

Validation: clean npm installs and all 20 changed frontend/runtime
production builds pass; all three changed TypeScript agents typecheck.
Integration parity and all 159 Intelligence template contract tests
pass. Audited 144 SDK/AG-UI references across 23 manifests and verified
their lockfile pins. All 13 CI starter smoke jobs pass. Final CI: 45
successful checks and 3 expected skips on commit
9f57bced70.
2026-09-08 14:02:04 -07:00
Mike Ryan 9f57bced70 docs(examples): align Angular starter Node prerequisites 2026-09-08 13:49:08 -07:00
github-actions[bot] c2dd6cf3b8 style: auto-fix formatting 2026-09-08 20:47:09 +00:00
Mike Ryan d6ad0f8d31 fix(examples): align LangGraph starter AG-UI dependency trees 2026-09-08 13:44:34 -07:00
Mike Ryan e3b249f0f7 chore(examples): update CLI starters to current SDK releases 2026-09-08 13:43:10 -07:00
Mike Ryan 6e888d7d88 chore: release angular v0.5.2 (#6949)
## Release angular v0.5.2

**Scope:** `angular` | **Bump:** `patch`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `angular` packages to `0.5.2`
   and generated AI-enhanced release notes.

2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
   must pass before merging. This is the review gate.

3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.

4. **When this PR is merged**, the `release / publish` workflow
automatically:
   - Builds all packages
   - Publishes the `angular` packages to npm at version `0.5.2`
   - Creates git tag `angular/v0.5.2`
   - Creates a GitHub Release with the final release notes

### Before merging

- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)

---

> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
angular/v0.5.2
2026-09-08 13:29:10 -07:00
MikeRyanDev 175e9c237c chore: release angular v0.5.2 2026-09-08 20:27:11 +00:00
Mike Ryan f43ec0d30e chore: release monorepo v1.70.2 (#6947)
## Release monorepo v1.70.2

**Scope:** `monorepo` | **Bump:** `patch`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `monorepo` packages to `1.70.2`
   and generated AI-enhanced release notes.

2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
   must pass before merging. This is the review gate.

3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.

4. **When this PR is merged**, the `release / publish` workflow
automatically:
   - Builds all packages
   - Publishes the `monorepo` packages to npm at version `1.70.2`
   - Creates git tag `monorepo/v1.70.2`
   - Creates a GitHub Release with the final release notes

### Before merging

- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)

---

> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
v1.70.2
2026-09-08 13:15:06 -07:00
MikeRyanDev 16514e9424 chore: release monorepo v1.70.2 2026-09-08 20:03:39 +00:00
Guido Vizoso 6db6d39c43 feat(showcase): exec skin — conversational dashboard composition (Vantage) (#6876)
## What this adds

`exec` — **Vantage**, Cascade Industries' executive reporting desk — the
eighth skin in the reskinnable demo, and the first to demonstrate
**conversational dashboard composition**: the agent renders a metric
block *inline in the chat*, and the operator pins it onto a real
dashboard.

- **CEO / CFO dashboards** with a live exception strip, plus a **Metrics
Explorer** with four URL levers (department, period, top-N, threshold)
that round-trip through `navigateTo`.
- **A2UI blocks in the transcript.** `render_metric_block` returns
`a2ui_operations`; a new shell dispatch renders `block:`-prefixed
surfaces inline instead of taking over the canvas region. A pin control
writes the block server-side onto a dashboard.
- **A teachable publish gate.** Publishing a board pack refuses twice —
first for a countersign PIN, then `422 UNEXPLAINED_VARIANCE` — and the
narrative-code vocabulary that clears it is deliberately withheld from
every agent-readable channel, so the agent has to be taught it on stage.
- **A per-request budget-memo PDF**, seeded/forgettable Intelligence
memories, and a gated presenter reset.

Nine demo beats, one presenter reset. `docs/superpowers/` holds the
design spec and plan (local-only, gitignored).

## How it was built and reviewed

Built with a 14-wave micro-task execution (62 micro-tasks), then put
through a Tier-3 code-review loop: **four review rounds of ~30
independent Opus reviewers each** (~110 reviewer reports) and **four fix
cycles (49 agents)**, every behavioral fix landed with red-green
evidence and every new guard proved by mutation.

Bugs the loop caught that unit tests could not, among others:
- an inline block crashed the page (no `A2UIProvider` ancestor) and
every block render hijacked the app region;
- the withheld code vocabulary leaked through a zod enum error body;
- the AI SDK's default `stopWhen` ended runs after one tool call;
- schema-level value constraints hung the tool chip forever (the runtime
has no `tool-error` arm) — twice, in two different tools;
- an emptied dashboard published successfully, erasing the demo's
climactic refusal;
- the memo printed a future dateline on the 1st–4th of any month;
- untrusted-key lookups answered `500` where `404` belonged, and made
`publishPack` throw against its documented contract.

The loop was **closed deliberately, not by convergence.** A convergence
audit over rounds 2–4 showed the remaining findings were dominated by
documentation-accuracy items that each fix cycle's own new prose
generated (~85% of round 4's findings sat in code the previous cycle had
touched). Rather than run a fifth round, the final cycle fixed the
demo-facing runtime defects and added **structural levers** so the
recurring classes cannot silently recur:

- every restatement of the metric/department vocabulary is now
compiler-checked (`satisfies Record<K, true>`) — a new metric fails
typecheck at **five** sites, and a parity test compares all four shipped
schemas;
- the withheld-code sweep derives its list from the canonical type and
walks tool **parameter schemas**, not just prose (a
`z.literal("VAR-TIMING")` in a schema now goes red);
- a lint rule forbids truthiness-guarded plain-object registry lookups —
it caught a fresh instance during this very merge.

Everything the loop found and did not fix is filed as follow-up work
(see the linked issues); nothing was silently dropped.

## Since the review loop closed

A round of presenter-facing polish and two live-only bugs, all found by
looking at the running app rather than by tests:

- **The CEO dashboard is now nothing but composed blocks.** Two fixed
strips (an exception feed and an initiative RYG strip) sat above the
pinned grid while the seeded dashboard *also* pins an `exceptionList`
and an `initiativeTable` — so the page opened showing both sets of rows
twice, once in chrome the assistant cannot touch. For a demo whose claim
is "the assistant composes this page", static duplicates of the
composable blocks were the strongest argument against it. The page
readable is now derived per-kind from what is actually pinned, so
unpinning a block stops the agent claiming its rows are on screen.
- **One frame and one title per pinned block.** The shared `Tile` drew
card chrome inside a host that already draws it (two rings, two
backgrounds, two shadows), and `buildBlockOps` emitted a `Heading`
duplicating the card's own title. The heading is now emitted only when
the block is *not* pinned, so a block in the chat still names itself.
- **The initiative block no longer dwarfs the grid.** 757px of content
beside a 112px metric tile, which the row-stretch then paid for with
~600px of dead space in its neighbour. Owner moved onto the name line,
the note came off the card (still on `title`, still published verbatim
by the page readable), and the block is now 269px against 243px for its
peers.
- **Status is carried once, not three times.** The RYG cards stacked a
tone wash, a coloured left edge and a pill; on the dark theme the washes
read as a different material. Three composited contrast pairs were
*retired* rather than weakened, since the washes no longer render.
- **`Publish refused: … Burn Rate · all · 2026-08`.** The refusal
receipt resolved `metricId` through `metricLabel` and forwarded
`department` raw — and every breach the seeded demo refuses on is
company-wide, so the bare word "all" reached the room at the climax of
beat 6, two inches below an exception block reading "Company-wide". The
label map already existed in three identical copies and the receipt had
none; all four now share one module.

## Verification

`pnpm typecheck` · `pnpm test:unit` (**3,208 tests / 249 files**) ·
`pnpm lint` (0/0) · `pnpm build` — all green.

**Walked live against a real model.** All nine beats were driven
end-to-end against a running stack (self-hosted Intelligence + a real
LLM) and each one behaved:

| Beat | Evidence |
| --- | --- |
| 1 face | inline A2UI block + pin controls + one-sentence answer |
| 2 rich thread | block replays fully rendered after a hard reload |
| 3a drive the app | agent asked for the code instead of inventing one,
then filed it verbatim; ledger flipped `opex/distribution` to
`explained` |
| 3b sees my screen | named the pinned blocks in order and read the live
rows, including 3a's flip |
| 3c levers | navigated to `?department=distribution&top=10`; page
honoured both |
| 3d multimodal | genuinely parsed the PDF (quoted its figures and memo
id); filed with `source: "ingested-memo"` |
| 4 memory | chose Growth **QoQ** over YoY unprompted and named the
preference |
| 5 stored skill | replayed the month-end procedure unaided, pinning
three blocks |
| 6 teach a skill | 422 gate → recorder captured 2 steps → saved →
replay published (`200`, pack carries `narrativeIds`) |

⚠️ **Still outstanding:** the e2e spec remains `test.fixme`-gated
pending an aimock fixture, and the walkthrough above was driven
programmatically through the browser rather than by a presenter — a
human rehearsal is the remaining gate before this leaves draft.

Two known non-blockers, neither in this skin: a shared-shell
`renderActivityMessages must be a stable array` console warning from
`src/app/[skin]/layout.tsx` (affects every skin), and beat 3d's
suggestion pill not preparing the room for a two-turn beat the way beat
3a's was reworded to.

Closes CPK-8249.
2026-09-08 16:28:00 -03:00
Guido Vizoso 7f6d6008b4 fix(showcase): say "Company-wide", not "all", in the publish refusal
The demo's climactic refusal listed its breaches with the raw department
id, two inches below an exception block spelling the same scope out:

    Publish refused: …
    Burn Rate · all · 2026-08          <- the receipt
    Burn Rate · Company-wide           <- the block above it

`publishRefusalPayload` resolved `metricId` through `metricLabel` and
forwarded `department` untouched, and every breach the seeded demo
refuses on is company-wide — so the one id that reached the room was the
bare word "all", which reads like a bug rather than a scope.

The label map already existed in THREE identical copies (the catalog
renderers, the CEO dashboard, the Metrics Explorer) and the receipt had
none, so a fourth copy would have fixed this line and left the next
surface to make the same mistake. All four now share one module, which
also gives the two pages that indexed the map directly the `Object.hasOwn`
guard only the renderers had.

Nothing round-trips the field back into a query — file_variance_narrative
takes a metric and a period, no department — so the label is safe as the
only form that leaves the payload.

The new test pins "all" specifically, plus an unmapped key still falling
through as itself, since that path exists to report a query the ledger
could not answer and the raw key is what names it. Verified live: the
receipt now reads "Burn Rate · Company-wide · 2026-08".
2026-09-08 15:39:41 -03:00
Guido Vizoso 6d24c89ee7 fix(showcase): drop the note from the exec initiative card, one line per row
The note was the last thing making this block taller than its
neighbours. Printed on every row it was 757px of content beside a 112px
metric tile; restricted to off-track rows it was still 311px against
243px for the two blocks below it. A pinned dashboard card answers "what
is the state of things" — five names, five owners, five statuses — and
the prose behind any one of them is a follow-up question, not something
to read five of at a glance.

Every row is now a single 27px line and the card is 269px, level with
the rest of the grid.

THE NOTE IS UNPRINTED, NOT DELETED. It stays on the cell's `title`, and
the page readable still publishes all five verbatim off the ledger, so
"what am I looking at?" reads out every explanation exactly as before.
The test asserts the `title` survives precisely so a later cleanup
cannot drop the data path while looking like a no-op.

Verified against three mutants: reprinting the note, stripping the owner
along with it, and dropping the `title` each fail.
2026-09-08 15:39:41 -03:00
Guido Vizoso 1ea6cc58a4 fix(showcase): put the initiative owner on the name line so green rows cost one line
The previous pass moved Owner out of its own COLUMN, which fixed the
name wrapping, but it landed on a sub-line of its own — so all five
initiatives still cost two lines each, including the three green ones
whose entire entry was then two lines to say a name and "on track".

Owner now rides the name line. The sub-line is the note alone, which
only a non-green row has, so a green row is a single 27px line and the
block stops being the outlier on the dashboard.

Measured on the CEO dashboard at a 533px card: 371 → 311 tall, against
243 for the two blocks below it. It was 757 before this sequence began.
2026-09-08 15:39:40 -03:00
Guido Vizoso 6e27380718 fix(showcase): stop the exec initiative block dwarfing every card beside it
Measured on the CEO dashboard: the initiative table was 667px of content
next to a 112px metric tile, and since the grid stretches a row to its
tallest card it padded Revenue vs Plan out to 757px — ~600px of dead
space beside a six-line block.

Two causes, both width rather than data. The table split three ways by
content, so with the assistant open — the demo's normal state, leaving
the card 286px — the Initiative column got 120px while Owner and Status
took the other half for a person's name and a three-letter code, and
every initiative name wrapped. Owner now sits on the sub-line where it
costs no width, and `table-fixed` at 78% stops the widest owner name
renegotiating the split.

The note then earns its lines only when the status is not green: a green
row's prose only repeats what its pill already says, and the rows that
keep an explanation are the ones a reader has to act on. Nothing is lost
— the full note stays on the cell's `title`, and the page readable
publishes all five verbatim off the ledger. It is also clamped to two
lines, since note length is ledger data and nothing upstream bounds it.

667 → 347 of content; the row goes 757 → 436.

The renderer test that asserted every note rendered is replaced by two
that pin the rule in both directions — printed for off-track, withheld
for green, and reachable on `title` either way. Verified against three
mutants: printing every note, printing none, and dropping the `title`
each fail exactly one of them.
2026-09-08 15:39:40 -03:00
Guido Vizoso 1f6973c423 feat(showcase): leave the CEO dashboard with nothing on it but composed blocks
Two fixed strips sat above the pinned-block grid: an exception feed and
an initiative RYG strip, both read straight off the ledger and neither
agent-configurable. The seeded dashboard ALSO pins an `exceptionList`
and an `initiativeTable` block, so the page opened showing each set of
rows twice — once in chrome the assistant cannot touch, once in a block
it can. For a demo whose whole claim is "the assistant composes this
page", static duplicates of the composable blocks were the strongest
argument against it.

Both strips come off. A page header replaces them, matching the CFO
dashboard's, so the two screens still read as the same screen with a
different set of blocks on it.

The page readable is now derived per-kind from what is actually pinned:
unpin the exception block and the agent stops claiming exception rows
are on screen, rather than reading out a feed the CEO is not looking at.
`visibleExceptions` and `ceoReadableExceptions` are unchanged and keep
their tests — they were always the rows the block shows.

Two consequences worth naming. The strip's click-through into the
Metrics Explorer goes with it: the block's rows are not links, so an
exception can now only be drilled into by asking Vantage to navigate.
And three contrast pairs cited `pages/ceo-dashboard.tsx` for the
initiative pills; the pills now render only in the catalog's
InitiativeTable, so the dead sites are dropped rather than the pairs
weakened.
2026-09-08 15:39:40 -03:00
Guido Vizoso bdec0dc513 fix(showcase): line up the bottoms of two exec blocks sharing a grid row
The grid was told `items-start`, which stopped a short card stretching
but left its neighbour's extra height as a ragged gap. Dropping it
returns the row to `items-stretch`; `h-full` on the card and `flex-1` on
its body make the card actually use that height instead of leaving the
slack outside its own border. Two cards side by side now line up top and
bottom.
2026-09-08 15:39:40 -03:00
Guido Vizoso 6609d460c3 fix(showcase): let an exec initiative card carry status once, not three times
The RYG strip stacked a 40%-opacity status wash, a 4px coloured left
edge and the status pill — three signals saying one word. On the dark
theme the washes muddied the surface enough that the tinted cards read
as a different material from every other card on the page.

The pill stays as the carrier and the card keeps a hairline tinted to
match, which is still enough to scan a row by colour. Cards also widen
to 15rem and let the initiative name wrap to two lines instead of
truncating, since several names lost their last word at the old width.

theme.test.ts's contrast guard caught this immediately: three composited
pairs it checks describe text over those washes, and the washes no
longer render anywhere in exec. They are retired with a note rather than
weakened, so the guard keeps failing if a wash comes back untested.
2026-09-08 15:39:39 -03:00
Guido Vizoso fa92193883 fix(showcase): draw one frame and one title around a pinned exec block
Every block renders inside a host that already draws card chrome — the
chat bubble in inline-block-surface.tsx and the dashboard card in
dashboard-grid.tsx — and both the shared Tile and buildBlockOps were
drawing their own on top of it. A pinned block came out with two rings,
two backgrounds and two shadows, and with its title printed twice: once
in the card header beside the move/remove controls, once as the Heading
component the op builder emitted.

Tile drops the chrome and keeps only its label; buildBlockOps emits the
Heading only when the block is NOT pinned, so a block in the chat still
names itself. The initiative table's label was a bare "Initiatives",
which under a card titled "Key Initiatives" was the double title in a
second form — it now carries the count and how much of it is off track,
the way its sibling labels carry metric, department and period.

The grid gains items-start so a short card no longer stretches to the
height of the tallest one in its row.
2026-09-08 15:39:39 -03:00
Guido Vizoso 15eb3f95a1 fix(showcase): render the block and disclose the setting a kind ignores 2026-09-08 15:39:39 -03:00
Guido Vizoso 201cfcacb3 fix(showcase): send the model somewhere real when a block prop is refused 2026-09-08 15:39:39 -03:00