Skip to content

refactor(angular): signals-based reasoning collapse - #2

Open
rainerhahnekamp wants to merge 7 commits into
feat/ng-a2ui-expfrom
refactor/angular/reasoning-message-collapse-signals
Open

refactor(angular): signals-based reasoning collapse#2
rainerhahnekamp wants to merge 7 commits into
feat/ng-a2ui-expfrom
refactor/angular/reasoning-message-collapse-signals

Conversation

@rainerhahnekamp

Copy link
Copy Markdown
Collaborator

Summary

Replace effect()-driven collapse/expand behavior in CopilotChatReasoningMessage with derived signals/computed state.

Why

Avoid propagating UI state via effect(); keep toggle behavior deterministic while streaming.

Tests

  • pnpm -C packages/angular test -- -t "renders streaming reasoning messages"

Made with Cursor

msari-ipe-ext-1 and others added 7 commits June 5, 2026 12:40
Adds angular-developer and angular-new-app skills via `npx skills add`
from https://github.com/angular/skills into .agents/skills/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply the common Angular CLI schematics to code which has been added
by Soverius.
We can do a follow-up for the high amount of effects, but we have to be
careful. We need tests first to do a refactoring for that.
Revert open-generative-ui-tool-renderer and drop empty imports arrays.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@rainerhahnekamp
rainerhahnekamp force-pushed the feat/ng-a2ui-exp branch 2 times, most recently from d0f3161 to 015701f Compare June 10, 2026 20:48
rainerhahnekamp pushed a commit that referenced this pull request Jun 15, 2026
…-side parity (SU7-F1/F2/F3)

SU7-F1 — backend host pattern hardening:
- F1.1  Reject bare trailing ?/# in the backend host pattern
- F1.2  Strip internal tab/CR/LF from the backend host pattern
- F1.3  Warn when ignoring an empty-string local backend override
- F1.4  Reject empty-userinfo @ in the backend host pattern authority
- F1.5  Keep __proto__ keys as data in local-backend maps
- F1.6  Commit the local-backends memo key only after the value computes
- F1.7  Trim local backend overrides before validation and name the real
        rejection
- F1.8  Honest FATAL when the pattern host is a stray scheme fragment
- F1.9  Canonicalize the pattern authority for parity with the override
        path
- F1.10 Acknowledge the staging-to-prod fail-open in the pattern fallback
- F1.11 Harden backend-url/local-backends-env test hygiene

SU7-F2 — runtime-config & client-config edge cases:
- F2.1  Branch POSTHOG_HOST rejection reasons (scheme/degenerate/parse-
        failure) instead of the catch-all mislabel
- F2.2  Reject loopback BASE_URL/DOCS_HOST in production instead of the
        silent http:// prepend
- F2.3  Key the DOCS_HOST fallback once-guard on (mode, shellHost, value)
        and mode-prefix all value-only guard keys
- F2.4  Reject a present-but-empty posthogKey in the client config reader
- F2.5  Drop the trailing slash from SSR_PLACEHOLDER_URL for structural
        parity with server values
- F2.6  Attribute the DOCS_HOST slash-strip to readDocsHost itself
- F2.7  Normalize trailing-dot FQDN spellings in the docs self-host loop
        guard (both compare sides)
- F2.8  Harden console spies to capture all log args; pin the full all-env
        config shape; converge SSR simulation on vi.stubGlobal

SU7-F3 — script-side parity, table classification & test isolation:
- F3 #1  Handle a missing reference integration per the error contract
- F3 #2  Port the runtime backend-host-pattern normalization into the
         generator — scheme/trailing-slash strip, degenerate fallback,
         NEXT_PUBLIC fallback
- F3 #3  Treat non-mapping manifest parses (empty/null/scalar/array YAML)
         as validation errors, not TypeErrors
- F3 #4  Label a missing/unreadable constraints.yaml per the stderr+exit(1)
         error contract
- F3 #5  Align atomic-write tmp naming with the test harness straggler-
         sweep convention; guard main() on direct invocation
- F3 #6  Correct the determineCellStatus unshipped docstring; replace
         stale hardcoded cell counts with formulas
- F3 CopilotKit#7  Isolate the pattern suite on a per-suite tmpdir harness; snapshot
         the generator's full write set
- F3 CopilotKit#8  Classify discarded duplicate wildcards as duplicates — hoist the
         owner check above the destination warns
- F3 CopilotKit#9  Reject a root ("/") EXACT seo-redirect source — homepage-hijack
         twin of the root-wildcard guard
- F3 CopilotKit#10 Reject seo-redirect entries with non-printable-ASCII source/
         destination — close the silent-dead-entry class
- F3 CopilotKit#11 Strip trailing slashes in normalizePosthogHost before the scheme
         test
- F3 CopilotKit#12 Message-filter the empty-slug-set error count; pin the single
         matcher entry
rainerhahnekamp pushed a commit that referenced this pull request Jun 15, 2026
… not only an array nested value; dashboard mirror updated in lockstep (drift test pins byte-identity) (CR G2 #2)
rainerhahnekamp pushed a commit that referenced this pull request Jun 15, 2026
…cer-emitted degraded reads 'degraded', not 'stale' (CF6-G5 #2)
rainerhahnekamp pushed a commit that referenced this pull request Jun 15, 2026
… dereferencing probeKey (CF7 #2)

A null/undefined/primitive enumerator element threw a TypeError out of the
tick body, rejecting the tick promise and violating the 'tick never
rejects' invariant. Non-object elements are now dropped loudly and counted
in the existing invalid-spec accounting.
rainerhahnekamp pushed a commit that referenced this pull request Jun 15, 2026
…gray, never green (CF7-F3 #2)

The D1-D4 gate fires only on d3.exists/d4.exists, so a cell with ONLY
green D5/D6 rows (no e2e/chat/tools rows at all) slipped past it and
rendered a green chip + green d6Effective at achievedDepth=0/
ceilingDepth=0 — a false top-of-ladder claim contradicting the
strictness doctrine (PRESENT-but-null D4 grays; D5-no-data grays the
ladder). A wholly absent D3/D4 family now collapses to the gray
"unverified" chip (same shape as the d4NoData collapse) with red-D5/D6
dominance preserved, and d6Effective stays blocked (null).

cell-model.ts:847-852 (gate) / :905-921 (d6Effective).

One existing fixture (amber pass-through under reclaimed-pending) built
its amber from an absent-D3/D4 map; it now carries green e2e/chat rows
so the chip is genuinely amber through an intact ladder — the test's
never-mask assertion is unchanged.
@rainerhahnekamp
rainerhahnekamp force-pushed the feat/ng-a2ui-exp branch 2 times, most recently from 3064fb9 to cd3a94f Compare June 17, 2026 15:57
wolfmanfx pushed a commit that referenced this pull request Jul 10, 2026
…rwarded inbound headers (CopilotKit#5782)

## Problem

When a self-hosted v2 `CopilotRuntime` is configured with a server-side
agent (an `@ag-ui/client` `HttpAgent` with static `headers` for
service-to-service auth), the runtime forwards inbound
`authorization`/`x-*` request headers onto the agent's outgoing call
**and lets them override the headers the server configured** — silently
breaking service-to-service auth to a secured backend (e.g. a private
Cloud Run agent behind IAM).

`Fixes CopilotKit#5712`

## Root cause


`packages/runtime/src/v2/runtime/handlers/shared/agent-utils.ts:125-128`
merged forwarded inbound headers **last**, so they won on collision:

```ts
agent.headers = {
  ...agent.headers,                      // server-configured
  ...extractForwardableHeaders(request), // inbound — overrode the above
};
```

There are actually **two** failure modes:

1. **Same-case collision** — inbound `authorization` overwrites a server
`authorization` (last-write-wins).
2. **Case-mismatch collision** — `extractForwardableHeaders` lowercases
inbound keys (`authorization`), while the server typically configures
canonical casing (`Authorization`). A plain spread treats those as
*distinct* keys and emits **both** — which undici downstream comma-joins
into a single invalid `"Bearer A, Bearer B"` ("multiple JWTs") value.
Flipping the spread order alone does **not** fix this case.

## Fix

In `agent-utils.ts`, make server-configured `agent.headers`
authoritative on collision, matched **case-insensitively**: drop any
forwarded inbound header whose name (case-insensitively) is already set
on the agent, and let non-colliding inbound headers pass through
unchanged. This preserves the existing forward-for-auth behavior for
headers the server does *not* set, while guaranteeing a server-set token
is never overridden or duplicated.

The merge logic lives in a shared
`mergeForwardableHeaders(serverHeaders, request)` helper in
`packages/runtime/src/v2/runtime/handlers/header-utils.ts` so the
precedence semantics are defined in exactly one place.

### Scope note

This is the conservative precedence + case-insensitive-dedup fix (the
issue's suggested fix #1). I did **not** tighten the default allowlist
to drop hop-by-hop/platform `x-*` headers (`x-serverless-*`,
`x-forwarded-*`, …) or add an opt-out — those alter existing forwarding
behavior and are worth a separate, deliberate change. The precedence fix
alone resolves the reported breakage (the server-set token now wins
regardless of what the platform injects on a colliding header name).

A documented workaround already exists for users on released versions:
pass a custom `fetch` to the `HttpAgent` that builds outgoing headers
from scratch (it runs after `configureAgentForRequest` and survives the
per-request `agent.clone()`).

## Red-green proof (the real fix — `/run` path)

The load-bearing assertion: there must be exactly **one** authorization
header carrying the **server** value.

### RED (fix stashed, against unmodified `agent-utils.ts`)

```
 ❯ src/v2/runtime/__tests__/agent-header-precedence.test.ts (2 tests | 1 failed)
   × configureAgentForRequest — header precedence (CopilotKit#5712) > server-configured agent headers win over a colliding inbound header
AssertionError: expected [ 'Authorization', 'authorization' ] to have a length of 1 but got 2
     81|     expect(authKeys).toHaveLength(1);
 Test Files  1 failed (1)
      Tests  1 failed | 1 passed (2)
```

The pre-existing `agent-utils-header-forwarding.test.ts` also failed,
because it explicitly encoded the buggy behavior
(`expect(...["x-aimock-context"]).toBe("new-context")` — inbound
winning):

```
 FAIL  src/v2/runtime/__tests__/agent-utils-header-forwarding.test.ts > ... > request forwardable headers override matching pre-existing agent headers
AssertionError: expected 'old-context' to be 'new-context'
```

### GREEN (fix applied)

```
 ✓ src/v2/runtime/__tests__/agent-header-precedence.test.ts (2 tests) 2ms
 ✓ src/v2/runtime/__tests__/agent-utils-header-forwarding.test.ts (8 tests) 3ms
 Test Files  2 passed (2)
      Tests  10 passed (10)
```

The colliding test (`agent-utils-header-forwarding.test.ts`) was updated
from asserting the old bug to asserting corrected precedence + a new
case-insensitive-dedup guard. The non-colliding-forward test is retained
unchanged as a regression guard.

## Quality gates

```
NX  Successfully ran target check-types for project @copilotkit/runtime
NX  Successfully ran target test for project @copilotkit/runtime  — Test Files 113 passed (113), Tests 1576 passed (1576)
```

---

## `/connect`-path change — forward-looking plumbing, inert today

The original issue and a prior eval flagged the same forwarding pattern
at `handlers/sse/connect.ts`. To keep the two paths' merge semantics
consistent, the `/connect` path now builds the same server-wins merged
headers (via the shared `mergeForwardableHeaders` helper) and passes
them into `runner.connect()`.

**This is not an active auth fix, and it is not red-green-proven as one
— because there is no live bug to fix on the connect path today.** No
shipped runner consumes the `headers` field of
`AgentRunnerConnectRequest`: the in-memory, intelligence, telemetry, and
sqlite runners all destructure only `threadId` from the connect request
and ignore `headers` entirely. Connect is a thread replay/reconnect, not
a fresh outgoing agent call. So whatever headers we pass into
`runner.connect()` are dropped on the floor by every runner that ships.

What this change actually does:

- Threads the per-request agent clone through `handle-connect.ts →
handleSseConnect` so the connect path *has access to* the
server-configured `agent.headers` (it previously did not).
- Passes `mergeForwardableHeaders(agent?.headers, request)` into
`runner.connect()` — the correct, server-wins argument **shape** for a
future outbound-connecting runner that *would* consume connect-path
headers.
- Rewrites the comments/JSDoc on this path to say this plainly, rather
than implying an active auth fix. It also documents that the
connect-site `cloneAgentForRequest` call is the sole `agentId`-existence
guard (the intelligence branch never re-validates the id), and documents
`cloneAgentForRequest`'s `AbstractAgent | Response` (404) dual-return
contract that both callers depend on.

The real outbound header forwarding — the thing that fixes CopilotKit#5712 — is
the `/run` path's `agent.headers` mutation described above. The connect
change is staged plumbing so that if/when a runner starts honoring
connect-path headers, it inherits the same server-wins precedence
without a second fix.

### Tests on the `/connect` path

The connect tests assert the *merge shape* that reaches
`runner.connect()` (server value wins on collision, exactly one
`authorization` key, non-colliding `x-*` still forwards) and that the
agent-undefined case (no server `agent.headers`) degrades to forwarding
allowlisted inbound headers only and does not crash. These verify the
argument we construct is correctly shaped — not that any shipped runner
consumes it.

## Files

- `packages/runtime/src/v2/runtime/handlers/header-utils.ts` — shared
`mergeForwardableHeaders` helper (case-insensitive, server-wins).
- `packages/runtime/src/v2/runtime/handlers/shared/agent-utils.ts` —
`/run` path uses the helper so server headers win on collision (**the
real fix**).
- `packages/runtime/src/v2/runtime/handlers/sse/connect.ts` — `/connect`
path uses the helper; forward-looking plumbing, inert until a runner
consumes connect-path headers.
- `packages/runtime/src/v2/runtime/handlers/handle-connect.ts` — threads
the per-request agent clone into `handleSseConnect`.
-
`packages/runtime/src/v2/runtime/__tests__/agent-header-precedence.test.ts`
— `/run` regression test exercising the real `configureAgentForRequest`
surface with a real `HttpAgent`.
-
`packages/runtime/src/v2/runtime/__tests__/agent-utils-header-forwarding.test.ts`
— updated the test that encoded the old (buggy) precedence; added a
case-mismatch dedup guard.
-
`packages/runtime/src/v2/runtime/handlers/sse/__tests__/sse-connect-agent-id.test.ts`
— connect-path merge-shape + agent-undefined coverage.

### Notes

- A documented `@ag-ui/client` `HttpAgent` `fetch` workaround already
exists for attaching service-to-service auth the runtime can't override
(see the issue). This change makes the workaround unnecessary for the
`/run` precedence case.
- Conservative scope: this is the **precedence flip on `/run`** plus
forward-looking connect plumbing. Tightening the default allowlist
(dropping hop-by-hop / platform `x-serverless-*`, `x-forwarded-*`,
`x-cloud-trace-context`, …) and an opt-out switch — issue suggestions
#2/#3 — are intentionally left as a follow-up to keep the
security-policy change minimal.
wolfmanfx pushed a commit that referenced this pull request Jul 20, 2026
…d failures (CopilotKit#5956)

## Two silent-failure gaps in the showcase build/deploy/notify pipeline

These are **pre-existing** silent-failure holes surfaced in code review
(not
caused by any recent PR). This PR fixes the two load-bearing ones.

### 1. Green-but-zero-redeploy (silent "we thought we shipped but
didn't")

The `redeploy-staging` job computes the redeploy set as the intersection
of the
build matrix and the build-success set. This job **only runs when
`aggregate-build-results.outputs.any_success == 'true'`** (job-level
`if:`
guard). So if that intersection comes back **EMPTY**, it does NOT mean
"nothing
to deploy" — it means at least one slot built successfully yet none of
those
successes maps back to a matrix `dispatch_name`. That's a
`dispatch_name`↔
`service` contract skew (the aggregator's `service` values and the
matrix's
`dispatch_name` values drifted apart).

The old code emitted `services=` (empty) and exited 0 → the build went
**GREEN
while redeploying NOTHING**, silently.

**Fix:** on an empty intersection in this any_success-guaranteed step,
fail loud
(`::error::` + `exit 1`) with a diagnostic naming both sides of the
skew.
The legitimate "nothing changed / nothing succeeded" no-op paths are
guarded at
the **job level** (`has_changes=='true' && any_success=='true'`), so the
fixed
step never runs there — no false-red.

### 2. Starter build failures had no alert surface (invisible failures)

The `notify` job's `needs` (and its `if: failure()`) omitted
`detect-starter-changes` and `build-starters`, and `build-starters`
wrote no
per-slot build-result artifact. So a **failed starter image build
produced NO
Slack alert and NO PR comment** — it shipped silently.

**Fix:**
- Added `detect-starter-changes` + `build-starters` to `notify.needs` so
`if: failure()` sees a starter build failure → Slack alert + PR comment.
- Gave `build-starters` a per-slot build-result artifact **mirroring the
main
  `build` matrix** (same `{service,status}` shape, `cancelled→skipped`
  normalization, `if: always()`, `if-no-files-found: error`), using a
  **distinct `starter-build-result-*` prefix** so it never matches the
aggregator's `build-result-*` download pattern (starters must not
pollute the
  showcase redeploy set keyed by `dispatch_name`).

### Red / Green

**Finding #1** — extracted the step's shell/jq logic and drove it with
synthetic
inputs:

RED (pre-fix), any_success=true + empty intersection:
```
No services in matrix ∩ success-set — skipping redeploy.
Computed services CSV (matrix ∩ build-success):
EXIT=0        # $GITHUB_OUTPUT: services=   -> silent pass, redeploys NOTHING
```
GREEN (post-fix), same inputs:
```
::error::Build succeeded (any_success=true) but matrix ∩ success-set is EMPTY — dispatch_name/service contract skew; nothing would be redeployed.
Successful build service values: ["shell-RENAMED","mastra-RENAMED"]
Scheduled matrix dispatch_name values: ["shell","mastra"]
EXIT=1        # fails loud
```
No-regression: non-empty intersection → `EXIT=0 ; services=shell`. The
nothing-changed/nothing-succeeded paths are skipped at the job level
(never
reach the step) → no false-red.

**Finding #2** — modeled `if: failure()` (fires iff any `needs` job
result is
`failure`):
```
BEFORE (starters NOT in needs), starter=failure -> notify fires = False  (INVISIBLE, the bug)
AFTER  (starters IN needs),     starter=failure -> notify fires = True   (FIXED)
AFTER no-regression, starters=skipped, all green -> notify fires = False (quiet)
```

### Validation
- `python3 yaml.safe_load` parses OK.
- `actionlint`: only pre-existing findings remain (matrix jq SC2086 +
the known
`depot-ubuntu-24.04-4` runner-label warning); no new errors in edited
regions.
- `yamllint`: only pre-existing line-length/document-start/truthy
warnings.

### Scope
Touches **only** `.github/workflows/showcase_build.yml`, and only these
two
concerns. Does NOT touch the `shell_dashboard` paths-filter region (PR
CopilotKit#5955's
domain), nor the other backlog debt (false-root-cause comment,
double-alert,
check-lockfile guard). Self-contained; not stacked on CopilotKit#5955.
wolfmanfx pushed a commit that referenced this pull request Jul 20, 2026
CopilotKit#6035)

## What

Follow-up nit from [PR
CopilotKit#5969](CopilotKit#5969) review
([OSS-488](https://linear.app/copilotkit/issue/OSS-488)). Fixes the
`getMessages` JSDoc in
`packages/channels-intelligence/src/intelligence-adapter.ts`, which
oversold image support.

The comment cited *"what was in the image"* as a `read_thread` use case,
but image/file parts contribute **no text** in this mapping —
`read_thread` is text-only by design. Image *content* reaches the model
only via `conversationStore`'s seeding of `agent.messages`. The comment
now says so explicitly instead of implying `read_thread` can see image
content.

Comment-only change — no runtime/API behavior change, so no changeset.

## Scope of OSS-488

The ticket listed three nits; this PR addresses the one still
actionable:

- **#1 (double-space when joining content parts)** — ✅ already resolved
on `main` (the `.filter(Boolean).join(" ")` fix + updated test landed
via the OSS-476 CR round). Nothing to do.
- **#2 (JSDoc oversells image support)** — ✅ **this PR**.
- **#3 (duplicate `getHistory` fetch per turn)** — intentionally
**deferred**. A per-turn memo would need turn-scoped caching with a
clear-on-turn-start hook; without one it risks serving stale history — a
correctness regression on something the ticket itself rates "acceptable
for an on-demand tool." Not worth it here.

The **known limitation** (speakers collapse to `user`/`bot` because
upstream `AgentMessage` carries no names) is unchanged and not
actionable at this layer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
wolfmanfx pushed a commit that referenced this pull request Aug 6, 2026
Follows the maintainer's Correction #2 on issue 6363. An exact version in a
docs install command is the same rot as the starlette==0.45.3 pin it replaced:
it goes stale silently and nobody re-resolves prose. The 2.22.0 the docs shipped
was already a version behind current the day it was written.

- docs install lines use pydantic-ai-slim[ag-ui,openai]>=2,<3, which constrains
  the dep the pages actually care about and fails loudly at the v3 boundary
- ag-ui-protocol drops out of the docs lines entirely; no doc snippet imports
  ag_ui, so naming it there was the transitive-dep noise the correction is about
- starlette>=0.46.2 stays, because the v2 snippets import Starlette directly.
  A floor with no ceiling cannot force a downgrade, so it does not recreate the
  silent backtrack
- examples/showcases/pydantic-ai-todos moves to a range in pyproject.toml and
  relocks; the uv.lock is what reproduces
- examples/canvas/pydantic-ai keeps exact pins: it has no lockfile, so
  requirements.txt is its only reproducibility artifact

Smoke-tested the open question from the issue: starlette 1.x works on
pydantic-ai v2. All 8 doc pages pass on 2.23.0 + starlette 1.3.1 and on
2.23.0 + starlette 0.52.1, so Jordan's <1.0 guard can be dropped rather
than raised.
wolfmanfx pushed a commit that referenced this pull request Aug 14, 2026
…wave slots could not reach

Comment-only, no behaviour change. Each of these sat OUTSIDE the boundary of the
slot whose work falsified it, so each described the tree as it was mid-migration.
All verified against the current tree before editing.

src/shell/skin-contract.ts
- `RuntimeProviders`: "airline needs neither, so omits it". Airline omits
  `RuntimeProviders` and DOES supply `useRuntimeProperties` — the two are
  SEPARABLE, and airline is now the worked example: one account holder, no
  switcher, so its hook reads no context and returns a frozen module constant. Do
  not mount an empty provider for symmetry.
- `useData`: "Omit when a skin has no shell-managed data (banking …)". Every skin
  omits it now; documented with the grep, plus WHY the field is kept (the shape is
  legitimate, it just has no worked example left).
- `CanvasSurface`: the "omit if the skin has no report canvas" branch is currently
  unexercised — every shipped skin has one.

src/shell/agent-registry.ts
- The logistics entry said it ships "neither `intelligence/seed-memories.ts` nor
  `intelligence/forget-memories.ts`" and is "identity plumbing only — do NOT read
  it as a durable-memory demo". It ships both (`ls src/skins/*/intelligence/`), and
  its `dev/reset` sweeps and re-seeds through them. Corrected, with the same
  properties-forwarding caveat the other five entries carry, and a note that
  "expensive half built, cheap half skipped" was its state for two releases.

src/skins/keel/data/types.ts
- The `THE REST SUBSTRATE` banner said the two substrates are "deliberately not
  merged yet", that `useKeelData` "holds runs in `useState` and ticks them on a
  900 ms interval", and that "the pages still read it through `useSkinData`". All
  three are false: one substrate, one clock, `useSkinData` returns undefined.
  Rewritten to name the server-settled read (`settle-runs.ts`, called by both
  `GET /ledger` and `GET /runs/[runId]`) and to record WHY the deleted client
  ticker was a defect rather than a design choice — it was a second clock that
  painted progress the server never heard of, which the next re-read after any
  write silently rewound.
- The `KeelData` interface header claimed to be "the interface every page,
  component, and tool codes against". It is not referenced by any code at all
  (`grep -rn KeelData src` returns only comments). Marked HISTORICAL, with a
  do-not-add-a-consumer note. NOT deleted: it is a doc pass, several comments
  across the skin describe the migration in terms of this shape, and removing an
  exported type is a code change for a separate commit. Flagged as a follow-up.

src/skins/keel/data/store.ts
- "it does not advance them on a timer, because the ticker lives in `useKeelData`
  on the client. Whichever slot migrates that hook has to decide where the ticker
  ends up" — decided: the server is the only clock now.

src/skins/keel/data/beat-map.md
- Header: "Keel today is `useKeelData`, an in-memory `useState` store, and it hits
  about one beat." Marked BUILT and reframed as the design record.
- Risk #2 (the two-substrates/ticker question, correctly called "the biggest single
  risk in the migration") marked RESOLVED, with the answer (move the clock, do not
  relocate the ticker) and the generalised lesson.

src/skins/airline/data/beat-map.md
- Header: the tools/prompt/pages/pills were "later slots". All landed.
- § "What this slot did NOT build": every row of the deferral table has shipped.
  Kept as the retrofit record — which is the most useful thing about it — with a
  third column saying where each landed, and the three flagged traps marked
  resolved (including "the reset route says memoryBeats: unarmed on purpose",
  which was removed in exactly the change that added the seed module, as
  instructed).

src/skins/airline/data/fare-waiver-codes.ts
- "⚠️ THE LINT GUARD DOES NOT COVER THIS SKIN YET." It does: both
  `src/skins/airline/tools.tsx` and `agent.ts` are in `withheldGateVocabulary`'s
  `files` glob. Also dropped its "COUNT the selectors" instruction (that count has
  rotted twice) in favour of the resolved-selector table in
  `skins-config.test.ts`, and spelled out that a green lint still leaves the three
  prose channels AND `waiverGround` — which matches no `*_CODES` pattern, so the
  rule cannot see it — as hand-review items.

src/skins/airline/tools.test.ts
- Header said `statusKeyedTerminalRender` "covers logistics only; airline's glob
  entry is a later slot's, so until it lands this file is the whole guard" and that
  `withheldGateVocabulary`'s glob "does not list airline yet either". Both globs
  list airline now. Also fixed "Three defect classes" over a list of five.

src/skins/keel/skin.tsx
- "exactly as it does for the four other REST-backed skins" → every skin; nothing
  sets `useData`.

src/proxy.ts
- "matching how the other three skins behave" → numeral-free. This was one of the
  two known-stale instances named in `skin-roster-docs.test.ts`'s header; that
  header is updated in the app-docs commit, and the remaining one
  (`e2e/inset-layout.spec.ts`'s hardcoded four-skin loop) is deliberately left —
  fixing it means adding assertions against skins the spec has never visited, which
  is a coverage change rather than a prose fix.

docs/teach-mode/README.md
- It correctly refuses to write the roster into prose, but its verified-by-role
  paragraph named only banking/commerce/logistics/people and its "so copy commerce
  or logistics" line named the only two skins with pinned replay behaviour. The
  `offerWorkflowRecording` grep now returns every registered skin, and
  `ls src/skins/*/teach-mode-directives.ts` — added as the mechanical
  discriminator for role #3 — returns four. Also: "logistics and commerce both"
  skip project-scoped rows in `forget-memories.ts` is now every skin but banking,
  replaced with the grep that proves it.

Does this make anything in .claude/skills/reskin/ stale? No — the reverse. The
preceding commit updated the skill for exactly these facts, and these comments were
brought into line with it. Checked: `grep -rn "useKeelData\|use-data\|in-memory"
.claude/skills/reskin/` names no path or symbol that no longer exists.

Verified: pnpm lint, pnpm exec tsc --noEmit, pnpm test:unit (197 files / 2227
tests), pnpm build — all green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
wolfmanfx pushed a commit that referenced this pull request Aug 19, 2026
…angGraph docs gaps (CopilotKit#6520)

Bundles the CopilotKit-side work from **OSS-769**, **OSS-767**
(partial), and the unshipped remainder of **OSS-609**.

## OSS-769 — `useCoAgent().nodeName` never updates

`useAgentNodeName` tracked the current node in a ref and returned
`nodeNameRef.current`. Mutating a ref schedules no render, so a
component reading `useCoAgent().nodeName` kept showing whichever node
was current at its last render and never updated on its own — it only
appeared to work when something unrelated happened to re-render it.

Backing the value with state fixes it. Five lines, no API change.

## OSS-767 (partial) — silent content-part drops (CopilotKit#1748)

`normalizeMessageContent` handles only `text` and `binary` parts;
anything else — the `{"type": "image", ...}` case from the report — maps
to `""` and is filtered out with no signal, so an agent emitting
structured content sees its output vanish silently.

This makes the drop visible, once per unrecognised part type so
streaming doesn't flood the log. Deliberately **not** the schema change:
carrying structured assistant content needs an `AssistantMessage`
decision upstream in `ag-ui`, which stays open on OSS-767.

## OSS-609 — the five docs gaps that never shipped

Gap #2 shipped in CopilotKit#6403; gaps 1, 3, 4, 5 and 6 did not.

| Gap | Where | Closes |
| --- | --- | --- |
| AWS Lambda self-hosting | `docs/deploy/aws-lambda.mdx` | CopilotKit#1151 |
| Per-user thread authorization | `docs/auth.mdx` (new section) | CopilotKit#2241
|
| Thread persistence without the platform |
`docs/threads-self-managed.mdx` | CopilotKit#6090 |
| DIY guardrails / DLP | `docs/integrations/langgraph/guardrails.mdx` |
CopilotKit#3414 |
| When you need an MCP App | `docs/agentic-protocols/mcp.mdx` (new
section) | CopilotKit#5991 |

The Lambda guide leads with the constraint that actually bites —
streaming is opt-in on every front door, so a chat runtime deployed with
the defaults appears to hang for the whole run and then dumps the reply
at once. It documents the Function URL + `RESPONSE_STREAM` path as the
default, and API Gateway REST + `responseTransferMode: STREAM` for
anyone who needs a REST API in front.

`threads-self-managed` follows the existing shared-snippet pattern with
per-framework wrappers, because two nav contracts require it: every
authored framework must publish the page, and every React destination
must map to an Angular one (`ANGULAR_DOC_REDIRECTS`). All nine wrappers
were confirmed necessary by deleting one and watching the suite fail.

## Review corrections

Two blockers from @MikeRyanDev, both verified against primary sources
before changing anything.

**API Gateway REST APIs can stream**
([`91b3e632d5`](CopilotKit@91b3e632d5)).
The guide was built on the pre-November-2025 limitation and claimed no
API Gateway type supports response streaming, steering readers to a
buffered `serverless-http` setup. REST gained it via
`responseTransferMode: STREAM`, which also lifts the 10 MB cap and
29-second timeout. REST and HTTP are now split; REST is documented as a
streaming front door (payload-format-1.0 event adapter, `AWS_PROXY`
integration on the `2021-11-15/.../response-streaming-invocations` URI,
CLI/CDK/SAM config), and the buffered fallback is scoped to HTTP APIs
and ALB, which still have no streaming path. Added the constraints that
actually matter for chat: the 30-second idle timeout on edge-optimized
endpoints (5 min Regional), and the console Test tab always buffering so
a working config looks broken.

**`identifyUser` is the platform's thread-scoping binding**
([`91b3e632d5`](CopilotKit@91b3e632d5)).
The section told every reader to build an ownership table and enforce it
in `onBeforeHandler`. On the Intelligence path the runtime already
resolves `identifyUser(request)` server-side and carries that id to the
platform; `listThreads` is scoped by user *and* filtered by `agentId`,
so the "every user of one project sees that project's threads" claim was
wrong. `identifyUser` is now documented as the binding, and the DIY
pattern is scoped to SSE runtimes, custom stores, and the local
in-memory runner.

**Follow-up correction — two routes are genuinely unscoped**
([`240672ff56`](CopilotKit@240672ff56)).
My rewrite then over-claimed. `handleGetThreadEvents` and
`handleGetThreadState` resolve the caller and discard it, and the
platform client takes no `userId` on either method
(`client.ts:1113`/`1135`) — unlike `getThreadMessages` at `1063`. Both
hit project-authenticated `_inspect` endpoints, so any caller
`identifyUser` accepts can read the event log and agent state of **any
thread in the project** given its id. The blanket guarantee is replaced
by a per-route table marking those two explicitly unscoped, plus an
`onBeforeHandler` guard narrowed to them.

That is a live gap in shipped runtime code, not a docs error, and it is
tracked as **OSS-851** — a platform-side `_inspect` change plus matching
runtime/client work and tests, out of scope for a docs PR. The interim
callout in `auth.mdx` comes out when OSS-851 lands.

## Not in this PR

**OSS-772** and **OSS-773** are already merged in
`oss-path-to-production` (CopilotKit#237, CopilotKit#238). Both are telemetry-sink changes
with no CopilotKit-side component. OSS-773's remaining half — re-keying
runtime `distinct_id` from email to the Clerk subject — is recorded on
the ticket as an open decision, not a task.

## Testing

**OSS-769.** New `use-agent-nodename.test.tsx`, 5 tests. Against
unmodified `origin/main`, **4 of 5 fail**:

```
× re-renders consumers on every node transition
× reports 'end' when a run errors
× resets to 'start' when a new run begins
✓ unsubscribes on unmount
× carries the agent, thread, and current node
  Tests  4 failed | 1 passed (5)
```

With the fix: `Tests 5 passed (5)`. These assert only re-render
behaviour under normal `act()` flushing — no manufactured intra-batch
window.

**Full react-core suite:** `Tests 7 failed | 1496 passed (1503)`. All 7
failures are **pre-existing** `ResizeObserver is not a constructor`
under jsdom, confined to `CopilotChatView.pinToSend` and
`use-pin-to-send` — neither of which this PR touches.

**Typecheck:** `packages/react-core` → `tsc --noEmit` exit 0, no output.

**OSS-767:** 3 new tests covering the warn, warn-once-per-type, and
no-warn-for-supported-types. `src/graphql/message-conversion/` → `Tests
125 passed (125)`.

**Docs:** `showcase/shell-docs` → `Tests 1 failed | 373 passed (374)`.
The single failure (`channels-docs > publishes the Channels overview
only through provider navigation`) is **pre-existing**; baselining with
all changes stashed reproduces it and nothing else. Re-run unchanged
after both review-correction commits.

**Review corrections.** The AWS rewrite was checked against the AWS
sources rather than written from memory — the REST streaming
announcement, `configuration-response-streaming`,
`response-transfer-mode` (endpoint-type idle timeouts, unsupported
buffered-only features), `response-streaming-lambda-configure`
(CLI/OpenAPI shapes), the CFN `Integration` reference, and the CDK
`ResponseTransferMode` enum. Two details corrected in passing:
`InvokeWithResponseStream` authorizes against plain
`lambda:InvokeFunction` (no new grant, contrary to what the streaming
URI suggests), and ALB still has no Lambda streaming path.

The auth corrections were verified by reading the handlers and the
platform client, not the tests — `resolve-intelligence-user.ts`,
`intelligence/threads.ts` (every `resolveIntelligenceUser` call site),
and `intelligence-platform/client.ts`. The existing tests assert the
`threadId`-only call shape, so they pass under the defect and could not
have surfaced it. Also corrected: there is no `threads/delete` route —
delete is `DELETE` on `threads/update` (`fetch-handler.ts:606`).

Both edited pages MDX-compile clean, and all inbound
`#thread-authorization` anchors still resolve after the h3→h4 demotions.

Two nav tests broke during this work and are fixed rather than papered
over — adding a page to the Rich Threads group violated the
cross-framework ordering contract and the React→Angular parity contract:

```
src/lib/__tests__/docs-render.test.ts
src/lib/__tests__/angular-docs-content.test.ts
  Test Files  2 passed (2)
       Tests  33 passed (33)
```

All 15 internal links in the new pages resolve against the content tree.

Closes CopilotKit#1151, CopilotKit#2241, CopilotKit#3414, CopilotKit#5991, CopilotKit#6090
Refs CopilotKit#1748, OSS-851
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants