Two CR comments addressed:
- Rename the local destructure of forwardedProps.auth.copilotkitIntelligence
from 'cki' to 'cpki' so it matches the project-wide abbreviation already
used in metadata fields (cpki_event_id, cpki_event_seq, etc).
- Replace the inline 'X-Cpki-User-Id' string literal with the existing
INTELLIGENCE_USER_ID_HEADER constant exported from intelligence-platform/client.
Applies to the runtime auto-attach in agent/index.ts and to the three
test sites in intelligence-mcp-helper.test.ts so the user-side and
runtime-side stay in sync.
Move the per-request Intelligence MCP bag from
forwardedProps.copilotkitIntelligence to
forwardedProps.auth.copilotkitIntelligence so the Intelligence-side
redaction policy strips it. The 'auth' namespace is the convention for
credentials; persistence sinks (Postgres, Redis, S3) and FE replay
paths in apps/realtime-gateway already strip everything under it.
Updates:
- Emitter (handlers/intelligence/run.ts): merge the bag into a single
forwardedProps.auth object alongside any upstream auth keys, and
only emit the auth namespace when there is something to put in it.
- Reader (agent/index.ts): read from forwardedProps.auth.copilotkitIntelligence
instead of forwardedProps.copilotkitIntelligence.
- Tests (intelligence-mcp-helper.test.ts): three fixtures rewritten
to the nested shape.
Four cases at the agent layer (BuiltInAgent reading forwardedProps):
* attaches when `copilotkitIntelligence` carries all three strings
(userId, apiKey, mcpUrl) — outbound headers carry Authorization +
X-Cpki-User-Id.
* does NOT attach when the bag is absent (no Intelligence wiring on
this run).
* does NOT attach when the bag is partial (e.g. mcpUrl missing).
* does NOT attach when the user has already configured an MCP server
pointing at the same URL — explicit user config wins, with the
user's headers and resolver hitting the wire.
Runtime side (intelligence/run.ts): when `runtime.intelligence.mcpServer`
is enabled, build a `copilotkitIntelligence` bag carrying the resolved
user-id, project apiKey, and the platform MCP URL, and pass it through
on `RunAgentInput.forwardedProps` to the agent. Skipped when the flag
is off — runs that don't go through this Intelligence path simply
don't see the bag.
Agent side (BuiltInAgent's run code): if `forwardedProps.copilotkitIntelligence`
contains all three string values AND the user's static `config.mcpServers`
doesn't already include the same URL, append a per-request
`MCPClientConfigHTTP`. Its `options.fetch` closes over apiKey + userId
and stamps `Authorization: Bearer <apiKey>` and `X-Cpki-User-Id:
<userId>` on every outbound MCP call. The custom fetch is the MCP
TypeScript SDK's documented extension point for per-request header
injection — no extra wrapper class, no separate framework concept.
The agent class is otherwise untouched: no new fields, no per-request
side channels, no typed reference to `CopilotKitIntelligence`. Other
agents that don't read `forwardedProps.copilotkitIntelligence` ignore
the bag.
Pulls in the AI SDK's stable `createMCPClient` export (rename from
`experimental_createMCPClient`) — the experimental name was deprecated;
`mcp-clients.test.ts`'s mock setup follows.
Adds `mcpServer?: boolean` to `CopilotKitIntelligenceConfig` (default
`false`). When true, the runtime emits the per-request bag the agent
needs to attach the platform's MCP server.
Internal accessors `ɵisMcpServerEnabled()` and `ɵgetApiKey()` round
out the existing `ɵgetApiUrl()`. Used by the runtime layer in the
forthcoming auto-attach commit; not part of the public surface.
Convenience re-exports so consumers wiring custom MCP clients (via
`mcpClients`) or custom transports don't need to add `@ai-sdk/mcp`
to their dependencies just to type the values.
Replaces the 501 stubs in handleGetThreadEvents and handleGetThreadState
with real delegation to CopilotKitIntelligence. Adds two new client
methods (`getThreadEvents`, `getThreadState`) on intelligence-platform/
client.ts that hit the new `/api/_inspect/threads/:id/{events,state}`
routes shipped in Intelligence PR #144 (CPK-7453). Auth flows through
the existing `resolveIntelligenceUser` path; threadId scoping happens
server-side via the API key's org/project resolution.
Wire shapes match the in-memory branch so the inspector consumes both
runtimes identically:
- events: `{ events }` (platform-internal `decodeErrorRowIds` and
`truncated` flags are stripped at the runtime boundary)
- state: `{ state }` where the platform's discriminated `ThreadStateResult`
flattens to the snapshot value for `kind: "snapshot"` and to `null` for
both `no-snapshot` and `snapshot-decode-error`
Replaces the 501 regression-protection tests with real delegation tests
that mock the platform methods, assert call args, and exercise the
no-snapshot / decode-error / throw paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new test areas covering surfaces this PR introduced or relies on:
- fetch-router: matchRoute tests for `/threads/:id/events`,
`/threads/:id/state`, and `/threads/clear` (with and without URL
encoding). Critically pins that "/threads/clear" resolves to
`threads/clear` and does NOT fall through to the more permissive
`threads/update` arm with threadId="clear" — the explicit guard in
the router exists for this reason.
- fetch-handler validation: 405 enforcement for POST/PATCH/DELETE on
the read-only `/threads/:id/events` and `/threads/:id/state`
endpoints, with `Allow: GET` header. Complementary positive case
asserts GET is NOT a 405.
- handle-run: end-to-end test that handle-run.ts:40
(`agent.agentId = agentId`) propagates the registry key onto historic
runs. Without this stamp, InMemoryAgentRunner falls back to "default"
and the agentId filter on `GET /threads?agentId=...` breaks for the
local-dev fallback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Type quality:
- intelligence/threads.ts handleGetThreadMessages: switch on the Message
discriminant (role) and read narrowed fields directly. Removes
`as Record<string, unknown>` laundering and chained `as` casts on
toolCalls/function/arguments. AssistantMessage's toolCalls always have
`function: { name, arguments }`, so the prior fallbacks (`tc.name`,
`tc.args`) were dead branches.
- in-memory.ts getThreadState: import StateSnapshotEvent and use it
instead of `(event as { snapshot?: unknown }).snapshot`.
Comment / API alignment:
- intelligence/threads.ts handleClearThreads JSDoc no longer claims the
inspector calls this; the actual caller is the demo button.
- in-memory.ts clearThreads JSDoc updated to match.
- in-memory.ts getThreadEvents JSDoc no longer references a SQLite
runner that does not exist; just describes the compaction logic.
- web-inspector lint fix: rename unused `changed` parameter to
`_changed` per oxc no-unused-vars.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- thread-store-registry: makeStore now attaches a __testId via
intersection so callers can distinguish stubs at a glance during
debugging instead of relying on identity-by-allocation alone.
- thread-store-registry subscriber-isolation test: assert the
diagnostic content ("Subscriber onThreadStoreRegistered error") and
Error argument, not just that some error was logged.
- handle-threads identifyUser-throws test: assert
"Error identifying intelligence user" with an Error argument, since
the throw originates inside resolveIntelligenceUser which logs and
returns 500 before subscribeToThreads is reached.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add the missing protected connect() override returning EMPTY so the
mock matches TestAgent and ThrowingAgent. Without it, a clone() that
ever exercised connect() would fall through to AbstractAgent.connect()
and may try to open a real transport in tests.
- clone() now forwards this.agentId directly instead of coercing
undefined to "". The constructor accepts string | undefined to make
this type-safe — coercion would silently turn "no agent id" into
"empty agent id", a different state per AgentConfig.agentId?: string.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
handleClearThreads is intentionally synchronous, but neither test
asserted the return type. Add `expect(response).not.toBeInstanceOf(Promise)`
on both branches so a regression that starts awaiting I/O updates the
synchronous call sites.
The handleGetThreadEvents/handleGetThreadState 501 tests asserted the
status code but not that intelligence stayed untouched. Stub spies for
both `listThreads` and a hypothetical `getThreadEvents`/`getThreadState`,
then assert neither was called — so a regression that drops the early
return and falls through to platform calls fails this test even after
the response code changes.
The handleSubscribeToThreads 500 test created an `errorSpy` to silence
console output but never asserted on it. Add `expect(errorSpy).toHaveBeenCalled()`
so a regression that quietly drops the diagnostic log is caught.
The InMemoryAgentRunner getThreadEvents test claimed the synthetic
terminal event was present but only asserted on TEXT_MESSAGE_*. Add an
explicit assertion on `RUN_ERROR` with `code: "INCOMPLETE_STREAM"` so
finalizeRunEvents' contract is locked in — a regression that stops
appending the synthetic event would leave the inspector showing an
in-progress thread forever.
Declare `onNewMessage` on `MessagePopulatingTestAgent.runAgent`'s options
type to match the runner's call site and `TestAgent` above. Without it,
a regression that starts depending on `onNewMessage` here would compile
cleanly even though the mock would silently drop the call.
handle-threads.test.ts:
- handleGetThreadMessages intelligence-path test now asserts the response
body verbatim, so a regression that swaps in a stub body is caught.
- 422-no-intelligence test issues a real DELETE request for the delete
path instead of cloning a POST request.
- handleClearThreads block carries a comment explaining why the handler
is intentionally synchronous (no I/O on either branch).
- The identifyUser-throws test now silences console.error for the
duration of the assertion, matching the pattern already used by the
subscribe-throws test.
in-memory-runner.test.ts:
- ThrowingAgent test asserts RUN_ERROR is the last emitted event and
that no RUN_FINISHED is emitted, locking in terminal-event semantics.
- getThreadEvents test asserts the full TEXT_MESSAGE triple is present
in the persisted event log, and the comment now reflects the real
finalizeRunEvents behaviour (it appends a synthetic terminal event
when the agent does not emit one, so terminal events ARE persisted).
- getThreadState multi-run test gains a cross-thread isolation
assertion: a snapshot on a different thread must not bleed into the
original thread's state.
- Bumped the inter-thread sort delay from 5ms to 20ms to absorb timer
jitter on slow CI runners.
- Removed four redundant `agent.agentId = "test-agent"` reassignments
(the constructor already sets it via super({ agentId })).
- Aligned MessagePopulatingTestAgent.runAgent with TestAgent: `onEvent`
is now required so the runner contract is exercised consistently.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TanStack's chat() engine runs a multi-turn agent loop: after the model
returns tool calls, it tries to execute them via processToolCalls().
Frontend-only tools (like render_pie_chart) are unknown to TanStack, so
executeToolCalls() treats them as errors and buildToolResultChunks()
re-emits TOOL_CALL_END without a preceding TOOL_CALL_START. The ag-ui
verify middleware rejects this duplicate.
Fix: track a runFinished flag in convertTanStackStream and discard all
events after the first RUN_FINISHED, which marks the boundary between
the streaming pass and TanStack's internal tool execution loop.
Also adds built-in-agent to docker-compose.local.yml and local-ports.json.
Picks up ag-ui-protocol/ag-ui#1578 — `import * as jsonpatch from
"fast-json-patch"` produced an empty namespace under Node native ESM
because fast-json-patch@3.x populates exports via Object.assign, which
the CJS→ESM named-export detector cannot see. Result: every STATE_DELTA
and ACTIVITY_DELTA event threw "applyPatch is not a function", and
LangGraph generative UI streams floods the console with the failure on
each patch.
0.0.53 switches to a default import so the emitted bundle works under
both ESM and CJS consumers. Bumped @ag-ui/core and @ag-ui/encoder in
lockstep since they share the release.
## Summary
- Explicitly defaults `streamSubgraphs: true` in `LangGraphAgent.run()`
forwardedProps so subagent streaming works with `@ag-ui/langgraph`
0.0.31+, which changed the default from `true` to `undefined`
- Uses nullish coalescing (`??`) so explicit user overrides (including
`false`) are preserved
- Fixes 6 showcase integrations failing E2E probes on the `subagents`
feature
## Why
`@ag-ui/langgraph` 0.0.31 removed the `?? true` fallback on
`streamSubgraphs` in `handleStreamEvents()`. The CopilotKit runtime
never explicitly set this prop, so subgraph event forwarding became
silently disabled. This caused all subagent-dependent demos to stop
working.
A previous v1 fix (commit `21e12afca`) handled this in the now-deleted
`agui-action.ts`, but the logic was lost during the v2 migration.
## Additional context
Investigation identified a second issue: `@ag-ui/langgraph` 0.0.34
includes state snapshot fixes needed for shared-state-read/write
features, but 0.0.34 is not yet published on npm (0.0.31 is latest). The
ag-ui team needs to cut a release. Full analysis: [Notion
write-up](https://app.notion.com/p/3513aa381852812a9ecef5fbf1e71739)
## Test plan
- [x] Runtime tests pass (1412/1412)
- [x] 7-agent CR converged Round 1 (0 findings)
- [ ] Verify showcase E2E probes for subagent features turn green after
merge
Express 4 does not support the {*splat} wildcard syntax introduced in
Express 5. Replace string-based wildcard patterns with RegExp routes
that work across both major versions.
The @ag-ui/langgraph 0.0.31+ changed the default for streamSubgraphs
from true to undefined, breaking subagent streaming. This enriches the
run() input's forwardedProps with streamSubgraphs: true as a default,
while preserving any explicit user override via the nullish coalescing
operator.
Needed for turnIndex/sequenceIndex fixture matching in D5 multi-turn
conversations. Updated in both packages/runtime (devDep) and
showcase/scripts (dep).
Express 4.20+ uses path-to-regexp v8 which requires named wildcard
params. Bare * is no longer valid — use {*splat} syntax instead.
Updated production code (express.ts) and test (express-fetch-bridge).
Restores express >=4.20.0 override now that code is compatible.