3119 Commits

Author SHA1 Message Date
Benjamin Taylor 04684614ca test(web-inspector): stop the gesture tests racing the beat on real timers
The two real-timer tests in the launcher's error-signal suite each asserted a
pre-beat state 200ms after breaking the connection. The beat is 400ms
(ERROR_GESTURE_MS.beat), so both assertions had a 200ms margin against a wall
clock on a shared runner. When the runner is loaded the 200ms sleep overshoots
400ms, the beat has already fired, and the assertion reports the next phase.

Both claims are already pinned deterministically on the fake clock, where every
phase boundary of the gesture is asserted. The real-timer tests exist only to
show the beat and the gesture run to their end on their own, so the racy
preconditions are removed rather than retimed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 14:46:25 -05:00
MikeRyanDev 6053e4e262 chore: release monorepo v1.69.1 2026-08-25 18:50:37 +00:00
Alem Tuzlak 7509b379e6 fix(web-inspector): remove extra space under the sidebar plan card
The sidebar footer had 18px extra padding under the plan card.

The card now uses the same 12px inset as the rest of the sidebar.
2026-08-25 18:36:29 +02:00
Alem Tuzlak 72f037744f fix(web-inspector): match HUD arrow and drop the SW overlay
The last mix made the HUD arrow a bit too light.

An empty 28px SW resize handle sat on the collapsed sidebar toggle.

In dark mode that handle painted as a black box.

The south and west edges still resize the floating window.

The SE grip is unchanged.
2026-08-25 18:29:55 +02:00
Alem Tuzlak 841aea0614 style(web-inspector): lighten the HUD arrow to match the glass card 2026-08-25 18:12:10 +02:00
Alem Tuzlak f7efb5dc7b style(web-inspector): keep the HUD glass fill and match the arrow to it 2026-08-25 18:08:44 +02:00
Alem Tuzlak 3d5d42c1a0 style(web-inspector): match the HUD arrow fill to the card 2026-08-25 18:04:34 +02:00
Aswin Kumar 042a098ae6 Merge branch 'main' into fix/angular-hitl-result-envelope 2026-08-25 20:30:59 +05:30
Lukas Moschitz cd711b369c style(web-inspector): one hairline on the launcher, a lens for the dot
Review asked for fewer borders and boxes, and for the dot's heavy border to
become a subtle shadow.

The launcher had two concentric lilac hairlines: the border, and a second
ring 1px outside it as a box-shadow, which also hardcoded the lilac instead
of reading the edge token. The outer one goes. What replaces it is a
one-pixel light edge along the top, which keeps the face from reading flat
without drawing a frame.

The border itself stays, and it is not decoration: against a dark host page
the face measures 1.10:1 (GitHub dark) and 1.04:1 (Tailwind slate-900), so
without it the launcher has no outline there at all.

`backdrop-blur-md` goes too. It sat behind a 95%-opaque fill and bought
almost nothing, while mounting a permanent blur layer over a customer's
page. It did have one real side effect -- promoting the launcher to its own
compositing layer -- and without that the hover scale re-rasterises the mark
every frame and it visibly jitters. `will-change: transform` asks for the
layer directly, and the jitter is gone.

The dot loses its opaque 1.5px collar. That collar was 21% of the dot's
footprint, and because the dot's centre sits *on* the rim, its outer half
painted a hard dark crescent onto the host page rather than onto the
launcher -- which is what read as "heavy". A hairline plus a soft drop
separates it just as well. The fill becomes a lens lit from the upper left,
both stops derived from the signal colour so a new tone needs no new values.

Considered and dropped: tinting the border in the signal colour. On a dark
page the border is the whole silhouette, so tinting it recolours the entire
launcher for a state that can persist for hours -- and the launcher already
pulses in that colour when the failure is new.
2026-08-25 16:42:54 +02:00
github-actions[bot] 3667885fd1 style: auto-fix formatting 2026-08-25 14:08:09 +00:00
Alem Tuzlak 6870c4a926 test(react-core): give the 100-message chat render a 20s test timeout 2026-08-25 16:06:37 +02:00
Aswin Kumar 8eb55a118e Merge branch 'main' into fix/angular-hitl-result-envelope 2026-08-25 19:25:45 +05:30
Alem Tuzlak 3d23675968 test(web-inspector): type HUD helpers as Node so check-types passes 2026-08-25 15:52:03 +02:00
Lukas Moschitz 4fa38d91f2 refactor(web-inspector): drop the launcher's dead chrome and soften its face
Six Tailwind utilities on the launcher set properties the unlayered `css`
block sets again, and unlayered declarations beat layered ones regardless of
specificity -- so `bg-slate-950/95`, `border-white/20`, `ring-1`,
`ring-white/10` and the two hover variants have never had any effect. Each
was the package's only use, so the checked-in stylesheet drops 939 bytes.
No visual change: verified the three rules are gone from the generated sheet
and the 611 tests still pass.

Kept deliberately: plain `border` (the hand CSS sets only `border-color`),
`rounded-full` (nothing else sets the radius) and the focus-visible trio
(the hand CSS sets only `outline-color`).

The face moves to `#181C1F` at 95%, which review asked for. It is a hair
darker than the `#1C1F24` this replaces -- 17.2:1 against white rather than
16.5:1, a ΔE of 2.3, at the floor of what an eye can separate -- so this
settles the question rather than changing the look.
2026-08-25 15:49:55 +02:00
github-actions[bot] ec8ad0c33a style: auto-fix formatting 2026-08-25 13:30:54 +00:00
Alem Tuzlak 7dd47cc2dd Merge branch 'main' into alem/oss-903-inspector-bubble-emanations 2026-08-25 15:28:55 +02:00
Tyler Slaton c61f0f32ed feat(web-inspector): surface failures on the launcher and open the Inspector on them (#6646) 2026-08-25 06:28:17 -07:00
Ben Taylor d85b0b5db9 fix(runtime): reject runner passed alongside intelligence (closes OSS-933) (#6670)
## Problem

`runner` and `intelligence` are mutually exclusive by construction, but
the exclusivity was enforced in only one direction and only for object
literals.

`CopilotIntelligenceRuntime` hardcodes `new
IntelligenceAgentRunner(...)` into its `super()` call
(`runtime.ts:582`), and `runner?` is declared only on
`CopilotSseRuntimeOptions` (`runtime.ts:239`). The type system catches a
`runner:` key on an Intelligence-shaped **object literal** via
excess-property checking — but that is the only barrier. A JS caller, an
`as any`, or a non-literal options object routes through
`CopilotRuntimeShim`'s `hasIntelligenceOptions()` dispatch into the
Intelligence constructor and has `runner` **silently dropped**, with no
diagnostic.

The mirror case is already guarded: `CopilotSseRuntime` throws on
`channels`, and the comment there states the exact reasoning that
applies here — "the type forbids it, but a JS / `as any` caller ...
would otherwise land here and have `channels` silently dropped — fail
loud instead." The Intelligence constructor validates `identifyUser`,
`channels`, `memory`, and `ɵlearning`. Same file, same pattern, one case
missing.

### It also made a shipped skill lie

`packages/runtime/skills/runtime/SKILL.md:87` asserted:

> Passing both `runner` and `intelligence` to `CopilotRuntime` is
rejected at construction.

It was not. And that contradicted the skill's own reference page,
`references/agent-runners.md`, which correctly described the silent
drop. Two files in the same shipped skill said opposite things about the
same behaviour.

## Change

- **Guard** (`runtime.ts:512`) — `CopilotIntelligenceRuntime` now throws
when `runner` is present, mirroring the `channels` guard in
`CopilotSseRuntime`. The message names the exclusivity and points out
that an in-memory/SQLite runner is unnecessary in Intelligence mode,
where durability is managed by the service.
- **`SKILL.md:87` unchanged** — the guard makes it accurate.
- **`references/agent-runners.md` updated** — this is *not* optional.
That page was the accurate one before this change; adding the guard
makes its "the auto-wired Intelligence runner wins regardless of what
you pass" false. Leaving it would fix SKILL.md's lie by creating the
same lie in the reference — rotating the contradiction rather than
resolving it. Its stale `:149-173,285-294` source citation is corrected
to the real line numbers too. Root `skills/` mirror regenerated via
`pnpm sync:plugin-skills`.

## Behavior notes

- Explicit `runner: undefined` still constructs. This matches how the
sibling `identifyUser` / `channels` / `memory` guards treat `undefined`,
and avoids breaking callers that spread an options object.
- The v1 deprecated compat path is unaffected:
`copilot-runtime.ts:492-509` already omits `runner` from its
Intelligence branch, so nothing routes a `runner` into this constructor
from v1.

## Tests

Two tests in `channels-option.test.ts`, alongside the existing `sse
runtime rejects channels` mirror:

- `intelligence runtime rejects a caller-supplied runner` — written
first and confirmed **red** against the unpatched constructor
(`AssertionError: expected [Function] to throw an error`), green after.
- `intelligence runtime tolerates an explicitly undefined runner` — pins
the undefined-tolerance above so the guard cannot over-throw.

## Verification

| Gate | Result |
|---|---|
| `nx test @copilotkit/runtime` | 144 files, **2080 passed, 0 failed** |
| `nx check-types @copilotkit/runtime` | Successfully ran (+22 deps) |
| `oxlint` (changed files) | 0 warnings, 0 errors |
| `oxfmt --check` | no issues in changed files |
| lefthook pre-commit + commit-msg | all green |

Closes OSS-933.
2026-08-25 08:22:06 -05:00
Alem Tuzlak 3a3df106b5 fix(web-inspector): show Learning on or off in the HUD 2026-08-25 14:53:30 +02:00
Lukas Moschitz 23e3287fa1 test(web-inspector): stop the privacy check tripping over a random id
"no telemetry payload anywhere carries the failure message" serialises the
whole property bag and asserts it does not contain "503". Two of those
properties are the anonymous distinct id -- random hex -- so a three-digit
numeric needle lands inside one roughly once in a few hundred runs. It just
failed a CI job that way, which reads like a privacy breach and is not one.

The ids are excluded by name and asserted to still be strings, rather than
weakening the needles, which are the point of the test. Proven both ways:
with an id seeded to contain "503" the test passes with this change and
fails without it, and it still goes red when a real message is attached to
the payload.
2026-08-25 14:49:17 +02:00
Alem Tuzlak 7d8a3b611e fix(web-inspector): ease HUD details, land Intelligence on Home, add Learning 2026-08-25 14:49:07 +02:00
Alem Tuzlak 303ff4a00e fix(web-inspector): make the whole HUD row open its view 2026-08-25 14:43:07 +02:00
Lukas Moschitz 3ba4a8fbda fix(web-inspector): let the event-error guard narrow a plain string
`refocusEventErrorLanding` reads the subject back out of the card's
`data-cpk-event-error` attribute, where the DOM can only offer
`string | undefined`, and handed that to a guard typed for
`LauncherSignalKey`. Narrowing untrusted input is what the guard is for, so
it takes a string; every caller that already holds a key still satisfies it.

Tests do not typecheck, so the suite stayed green and only `check-types`
saw it -- which failed three CI jobs on the same one line.
2026-08-25 14:37:07 +02:00
Alem Tuzlak afa5e29959 feat(web-inspector): show a hover HUD on the closed launcher 2026-08-25 14:29:51 +02:00
Alem Tuzlak 5391c4886b feat(web-inspector): add view thread in your app (#6562)
## What does this PR do?

Lets a developer open a saved Inspector thread in the live official
chat.

- New header action: **View in your app**
- Official React and Vue chat switch to that thread
- A pinned `threadId` does not block the switch
- **Stop viewing** or an app thread change restores the previous thread
- Example threads have no action
- Production builds hide the action
- Same agent only. No matching official chat shows an error in the
Inspector

Core owns a two-way EventClient bridge
(`@tanstack/devtools-event-client`). The root import is a no-op in
production.

Docs: Inspector guide, section **View a thread in your app**.

## Related PRs and Issues

-
https://linear.app/copilotkit/issue/OSS-871/new-features-add-a-new-view-thread-in-your-app-feature

## Checklist

- [x] I have read the Contribution Guide
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] Allow edits by maintainers is checked
2026-08-25 13:52:52 +02:00
Tyler Slaton d7ac976636 fix(web-inspector): preserve independent error signals 2026-08-25 13:46:59 +02:00
Lukas Moschitz cf062dd375 chore(web-inspector): regenerate the stylesheet artifact after the merge 2026-08-25 12:10:52 +02:00
Lukas Moschitz 306ddaa5df Merge remote-tracking branch 'origin/main' into lukas/oss-903-presentation-wire-errors-notifications-to-emanate-from-and
# Conflicts:
#	packages/web-inspector/src/lib/__tests__/telemetry.test.ts
#	packages/web-inspector/src/styles/generated.css
2026-08-25 12:09:52 +02:00
Tyler Slaton 2bb60b67c5 style: apply repository formatting 2026-08-25 11:36:12 +02:00
Alem Tuzlak 8c12aba792 chore: remove merge-only import formatting 2026-08-25 11:28:41 +02:00
Lukas Moschitz aeac35d168 fix(web-inspector): land an error only on arrival, not on every visit
`handleMenuSelect` applied the event error's landing on every click on Agent
or AG-UI Events. A landing selects the failed agent, clears the event
filters and re-expands the failed row -- help for a reader who came
*because* of that error, vandalism for one who did not.

`lastEventError` outlives being read on purpose, so the how-to-fix card
survives while it is being read. Together that meant: one run error, and
for the rest of the session every return to AG-UI Events silently wiped the
reader's own search filter and re-expanded a row from hours ago. It also
undid the `all-agents` restore eight lines above it, whose comment says it
exists "so the Events tab isn't silently filtered to one agent".

The three arrivals that really are landings keep it -- pressing the
launcher, pressing the card, and an error arriving while its view is
already open. The new test fails with the old call in place, on the value
the reader can see: expected '' to be 'bookFlight'.
2026-08-25 11:26:46 +02:00
Tyler Slaton b53fa8f1ea refactor(inspector): consolidate framework integration 2026-08-25 11:25:08 +02:00
Tyler Slaton 0748627929 docs(inspector): remove obsolete configuration guidance 2026-08-25 11:25:08 +02:00
Tyler Slaton 39d5598f0e fix(inspector): simplify framework lifecycle mounts 2026-08-25 11:25:07 +02:00
Tyler Slaton 240ea8a575 test(angular): verify inspector configuration before append 2026-08-25 11:25:06 +02:00
Tyler Slaton 32827246c0 fix(react-core): defer inspector until after hydration 2026-08-25 11:25:06 +02:00
Tyler Slaton a5e072301a fix(inspector): restrict defaults to development builds 2026-08-25 11:25:06 +02:00
Tyler Slaton 3c44aa4640 fix(react-core): assign inspector core explicitly 2026-08-25 11:25:05 +02:00
Tyler Slaton fd4d06b089 refactor: simplify default inspector integration 2026-08-25 11:25:05 +02:00
Tyler Slaton 4691946a8f feat: enable the Inspector by default in browser frameworks 2026-08-25 11:25:04 +02:00
Totoro-qaq 0b85f08842 fix(react-core): render every v1 tool call 2026-08-25 16:53:39 +08:00
Alem Tuzlak ba4260ad66 feat(web-inspector): add Event Snippets and save-as-snippet (#6649)
Open Inspector Event Snippets on localhost. You can compile, save, and
replay AG-UI events in chat. Chat shows a bookmark icon next to a tool
call, an A2UI block, or generative UI. Click the icon to save that turn
as a snippet.

## What does this PR do?

This PR adds the Inspector Event Snippets pane.

You can:

- Compile a snippet from a recipe (tool-call, reasoning, text, activity,
raw)
- Save snippets in origin-scoped localStorage
(`cpk:inspector:event-snippets`)
- Import and export snippets from the pane header
- Replay a snippet into live chat through Inspector-only Core inject

Each Run remints `messageId`, `parentMessageId`, `toolCallId`, and
`runId`. The second Run of the same snippet is a new turn.

On localhost, chat shows a bookmark icon beside a tool call, A2UI block,
or generative UI. The icon is absolutely positioned. It hangs to the
right when there is room. Otherwise it hangs to the left. The card stays
full chat width.

The React demo adds `sayHello`, `getTime`, `addNumbers`, and a **Call 3
tools** suggestion.

## Related PRs and Issues

- Linear
[OSS-874](https://linear.app/copilotkit/issue/OSS-874/new-features-also-allow-users-to-emit-specific-events-from-the)

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)

## Testing

### Commands run

1. Lefthook pre-commit ran `nx` targets `test`, `publint`, and `attw`
for 27 affected projects. All passed.
2. I did not run `pnpm test:pr` (full repo). Lefthook ran the affected
package matrix only.

### Manual test

1. Run `pnpm demo:react` from the repo root.
2. Open http://localhost:3000
3. Open Inspector and select Event Snippets.
4. In chat, click **Call 3 tools**. Confirm three tool cards at full
chat width, with the bookmark hanging outside the card.
5. Click a bookmark, then click Run twice. Chat shows a second turn with
new IDs.

### How this PR makes testing easy

- `packages/web-inspector/src/lib/__tests__/event-snippets.test.ts`
- `packages/core/src/__tests__/inspect-inject.test.ts` (covers two
injects)
- React demo: `examples/v2/react/demo/src/app/page.tsx`

## Linked issues

Linear
[OSS-874](https://linear.app/copilotkit/issue/OSS-874/new-features-also-allow-users-to-emit-specific-events-from-the)

## Risk / rollback

- If ID remint is wrong, a second Run can no-op or duplicate a turn.
- The save icon shows on localhost Inspector (or when `showDevConsole`
is `true`).
- Rollback: revert this PR.

## Public API change

**Before**

Angular has no Inspector service.

```ts
// no CopilotInspector export from @copilotkit/angular
```

**After**

```ts
import { CopilotInspector } from "@copilotkit/angular";

const inspector = inject(CopilotInspector);
inspector.openInspector({
  messageId: "msg-1",
  menu: "event-snippets",
});
```

React and Vue apps that already mount Inspector on localhost need no new
caller code. Chat wires the bookmark through Inspector context.

`@copilotkit/core` exports `ɵinjectInspectorEvents` for Inspector only.
App code must not call it. There is no public Core emit API.
2026-08-25 09:39:37 +02:00
Alem Tuzlak 328ee3cb58 Merge origin/main into feat/OSS-871-view-thread-in-app 2026-08-24 19:58:19 +02:00
Alem Tuzlak 98968b49c2 fix(web-inspector): address review on event snippets
- Raw recipe: the Events JSON input now calls requestUpdate, so Run and
  Save stop being permanently disabled.
- Tool args recovery: one depth scan replaces the parse-every-prefix loop.
  Truncated args from a streaming tool call now fail at once, not after
  seconds of blocked main thread.
- Chat bookmark: hidden while the tool arguments are incomplete, so a
  partial payload cannot be captured.
- saveEventSnippet: React, Vue, and Angular wrap the body, so a compile or
  storage failure is reported instead of becoming an unhandled rejection.
- Vue and Angular now gate the in-chat affordances on a dev build plus
  localhost, the same as React. showDevConsole: true on a staging URL no
  longer puts a bookmark into a production chat.
2026-08-24 19:10:06 +02:00
Mike Ryan db88826432 chore: rename Enterprise Intelligence product copy 2026-08-24 09:38:15 -07:00
Ben Taylor 0943c5196e fix(runtime): make published declarations resolvable for consumers (#6674)
Closes the remaining half of OSS-899.

PR #6644 removed the `require("reflect-metadata")` banner from the
published
`.d.cts` files, taking a bare strict-mode `import { CopilotRuntime }`
from 81
errors to 10. This takes it to **1**, and that one is not ours.

## Result

Measured by overlaying the built `dist` onto a real consumer install of
1.69.0
and typechecking a bare import under `strict` + `skipLibCheck: false`:

| entry point | before | after |
| -- | --: | --: |
| `@copilotkit/runtime` | 10 | **1** |
| `/v2` | 1 | **1** |
| `/v2/express` | 1 | **1** |
| `/v2/hono` | 1 | **1** |
| `/v2/node` | 1 | **1** |
| `/langgraph` | 7 | **6** |

The single remaining error on every entry point is the same one, and it
is in a
third-party package — see "Not fixed here" below.

## The gate

`scripts/validate-dts-imports.ts` is the companion to
`validate-dts-ambient.ts`.
That one checks the *shape* of published declarations; this one checks
what they
*reach for*, against a single invariant:

> Every module a published `.d.ts` imports must be resolvable by someone
who
> installed this package and nothing else.

It flags devDependencies, optional peers, dependencies whose types live
in a
devDependency `@types/*`, relative imports of JS-only bundler chunks,
and an
explicit ban on `graphql-yoga`. It started red on 18 violations and is
now clean
on all 175 declaration files. Wired into the existing `check-dts`
target, so it
runs in `check:packages` and `static_quality.yml`.

It caught two problems the typecheck probe missed, because they happened
to
hoist in that particular install: `@whatwg-node/server` and
`@langchain/langgraph-sdk`.

## The fixes

- **`graphql-yoga` (5× TS2416)** — `GraphQLContext` was
`YogaInitialContext & {...}`.
That one reference pulled the Yoga barrel into every consumer program,
and with
it `lru-cache@10`, whose `implements Map` clause is what actually
errors.
Declared locally instead; structurally identical, so a real Yoga context
still
satisfies it. Nothing in this package serves GraphQL any more — every v1
  integration entry point delegates to the v2 Hono endpoint.
- **`@copilotkit/channels` (1× TS7016)** — a devDependency and a pure
re-export of
`channels-core`, so tsdown inlined its prebuilt declarations plus a
rolldown
helper chunk that ships JavaScript only. Types now come from
`channels-core`,
  which is a real dependency and stays external.
- **`cors` / `express` (1× TS7016)** — public declarations use their
types, but both
  `@types` packages were devDependencies. Promoted to dependencies.
- **`@anthropic-ai/sdk` / `groq-sdk` (2× TS2307)** — optional peers
named in exported
signatures. Params and getters now use `SdkClientLike`, a structural
type a real
client satisfies; the SDK types stay at the two call sites that need
them, inside
  function bodies where they are never emitted.
- **`@langchain/langgraph-sdk`** — was deep-importing
`dist/types.messages`, an
internal file of an optional peer. Now derived from the base class
signature in
  `@ag-ui/langgraph`, a real dependency.
- **`@whatwg-node/server`** — an `export type {}` workaround for
microsoft/TypeScript#42873 whose own comment said it was waiting on
TypeScript
  5.5. We are on 5.9.2 and it typechecks without it.

## Not fixed here

**1× TS2694** — `@copilotkit/license-verifier@0.5.0` references zod's
`core`
namespace, which only exists in zod v4; this package pins `zod ^3.23.3`.
It is an
external package (0.5.0 is the latest published), so the fix is either a
license-verifier release or migrating this package to zod v4. Both are
larger than
this PR and deserve their own ticket.

Also unchanged: the 6 remaining `/langgraph` errors, all inside
`@ag-ui/langgraph`,
`@langchain/core`, and `@langchain/langgraph-sdk`. The `TS2416` on `run`
is present
identically in pristine 1.69.0 — it is downstream of
`@ag-ui/langgraph`'s own
unresolvable deep imports, not something this branch introduced.

## Reviewer note

The `SdkClientLike` swap narrows `adapter.anthropic` and `adapter.groq`
for anyone
reaching past `baseURL` and `apiKey`. Both adapters are v1-deprecated,
and the
alternative is a type error for every consumer who does not install
those optional
peers — but it is a public-surface change and worth a second opinion.

## Verification

- `nx test runtime` — 2078 tests, 144 files, all passing
- `nx build runtime` + `npm run check-dts` — both validators clean on
175 files
- `tsc --noEmit` on the package — clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-24 11:21:34 -05:00
Ben Taylor 105ac3cfb9 fix(core): keep exactly one tool result per tool call across message snapshots (#6294)
# fix(core): keep exactly one tool result per tool call across message
snapshots

## Summary

When an agent emits a MESSAGES_SNAPSHOT, AG-UI merges it by message id
and can drop a tool message that TOOL_CALL_RESULT created. The next turn
then sends an assistant tool call without its paired result, which
providers reject.

This PR records observed tool results for the current input and repairs
history through AG-UI's returned-messages mutation channel. It keeps one
tool message per toolCallId and composes with current main's first-seen
message provenance.

## Root cause

@ag-ui/client applies events against its own cloned messages array.
TOOL_CALL_RESULT creates a tool message and inserts it after its
assistant owner. A later MESSAGES_SNAPSHOT is a replace-by-id merge, so
a missing tool entry can remove that result. packages/core had no record
of the result event, so there was nothing to restore it from.

## What changed

- StateManager records one ToolCallResultEvent per toolCallId on the
current RunAgentInput. Events keep flowing through AG-UI's normal path.
- At RUN_FINISHED, RUN_ERROR, and onRunFailed, reconciliation returns a
fresh message array only when a repair is needed. AG-UI applies it
through its normal mutation chain.
- Pending results are released at each finished server-run boundary and
at finalization, so a later server run under one input cannot resurrect
a result removed by its snapshot.
- The "Forwarded to client" sentinel classifier now lives in one
internal module used by StateManager and run-handler.ts.

## The reconciliation rule

toolCallId is the decisive identity:

- No assistant owner for the call: do nothing.
- A real tool message already exists for the call under any message id:
keep one result and remove duplicate same-call representations.
- Only placeholders exist: promote one to the canonical result and drop
the rest.
- Nothing exists: insert the result after its assistant owner and its
contiguous tool messages.

## On the LangGraph duplicate

LangGraph can represent one result with different streamed and
checkpoint message ids. The regression fixture keeps the streamed result
before the snapshot and places both representations in the snapshot. The
final history and next-turn input contain one result for that
toolCallId.

Two tool messages for one toolCallId are one malformed history class.
Keying reconciliation by toolCallId makes that duplicate unrepresentable
while preserving normal event delivery.

## What this does not do

- No direct agent.messages mutation, setMessages from a subscriber, or
stopPropagation. AG-UI remains the owner of message application,
ordering, and publication.
- Reconciliation does not infer or rewrite run identity. Current main's
event-derived run identity and first-seen snapshot provenance remain
intact.
- No message-to-run association is performed inside reconciliation.
- No public API change, export, version bump, or changeset.

## Relationship to #3884

Related to #3884, but not marked as closing it. The event sequence in
that issue has no MESSAGES_SNAPSHOT, and it already produces a correct
turn-2 history on current main. Snapshot-dropped results are a real bug
worth fixing independently, while the reporter's case still needs a raw
event trace.

## Test plan

All cases drive CopilotKitCore.runAgent() against real AbstractAgent
subclasses.

- Two-turn reproduction: a snapshot omits the result, and the next turn
receives exactly one result.
- LangGraph shape: differing message ids under one toolCallId produce
one surviving tool message in real event-before-snapshot order.
- Repeated server runs under one input do not resurrect a result removed
by a later snapshot.
- Terminal mutation, normal result propagation, duplicate results,
placeholders, ownerless results, ordering, RUN_ERROR, local failure, and
run ownership remain covered.
- The focused core tests pass 32/32. Core typecheck, build, formatting,
lint, and diff checks pass. CI checkboxes remain for GitHub.
2026-08-24 11:19:31 -05:00
Lukas Moschitz aa91d059ef test(web-inspector): cover the Learning error source, and quiet its lint
The `memory` latch shipped with no test at all, and the one test that
mentions it still explained the old behaviour: "the Memory store is never
even subscribed from here". That is no longer true -- opening the view
does subscribe it -- so the test passed while teaching the wrong thing.
The comment now says what actually holds: without an Intelligence context
the store never fetches, so there is nothing to fail.

Three tests: a refused load names itself and lands on Learning, a refused
load arms nothing while the view has never been opened, and a resolved one
clears itself. The middle one is the important one -- it is the constraint
that makes `memory` an unread event rather than a state the launcher could
mirror from a cold start.

The harness needed two things. `intelligence` is now settable, because the
view is a locked teaser without it and the latch is unreachable. And the
memory store stub replaces its state rather than mutating it: the store's
selectors memoize on the state they were handed, so an in-place edit
reports the stale value forever and looks exactly like a signal that never
fires.

Also hoists the selector-escape pattern so `no-control-regex` can be
suppressed on it with a reason -- the control range is the point, since an
attribute selector escapes those too. Lint returns to its 5359 baseline.
2026-08-24 18:16:29 +02:00
Lukas Moschitz 1d1a319b1f docs(web-inspector): record why the wiring latch arms without a window
Arming used to wait 2000ms so a blip produced neither dot nor beat. The
window is gone and that is deliberate, but nothing said so, and the tests
now assert immediate arming as the contract -- so the next reader would
find no trace of the trade-off, only the behaviour.

What the note holds: `threads` can flap and plays a whole gesture for a
blip, bounded by the single pending-beat slot to one gesture per gesture
length; `connection` cannot flap on its own because nothing retries the
handshake; and the `connecting` branch already covers the retry case if a
fix for the mid-session gap adds polling. Mirrored on OSS-904, which is
where such a fix would land.
2026-08-24 18:05:28 +02:00
Benjamin Taylor cbe6a81ff4 fix(runtime): keep optional peer SDK types off the published surface
@anthropic-ai/sdk and groq-sdk are optional peers, so a consumer who never uses
those adapters does not install them -- yet both were named in exported
signatures, which is a TS2307 on a bare `import { CopilotRuntime }`. The
LangGraph agent had a third case, and a worse one: it deep-imported
@langchain/langgraph-sdk/dist/types.messages, an internal file of an optional
peer.

The adapter params and getters now use SdkClientLike, a structural type a real
Anthropic or Groq instance satisfies. The SDK types stay for the two call sites
that need them, inside function bodies where they are never emitted. The
LangGraph message type is derived from the base class signature in
@ag-ui/langgraph, which is a real dependency.

Narrows adapter.anthropic and adapter.groq for anyone reaching past baseURL and
apiKey. Both adapters are v1-deprecated and the alternative is a type error for
every consumer.
2026-08-24 10:39:37 -05:00