Commit Graph

2767 Commits

Author SHA1 Message Date
Ben Taylor d02f7e699e docs(react-core): state the blast radius of key-remount and the provisional agent (refs OSS-979) (#6710)
## Why

An Intelligence integration lost a request-to-row correlation map
partway through a user interaction — no error, no warning. It surfaced
as "our response routing is flaky". OSS-979 filed it as
`CopilotKitProvider` remounting its children.

The provider does nothing of the kind. It renders `{children}`
unconditionally at `CopilotKitProvider.tsx:952` — unkeyed, no early
return, and there is no `Suspense` boundary anywhere in `v2`. Nothing in
the SDK silently re-points the active thread either; every mutation path
(`setActiveThreadId`, `startNewThread`, the drawer row click, the
inspector override) is caller-driven.

The remount was app-side, and it was app-side because this skill told it
to be:

- `references/threads.md:98` teaches `useThreads()` → select →
`<CopilotChat key={activeId}>`, and that recipe is only reachable once
Intelligence is wired.
- `references/switching-agents.md:123` teaches "`key={activeAgent}`
forces remount so thread state doesn't leak" without saying what else
that discards.
- `examples/showcases/reskinnable-demo/src/app/[skin]/layout.tsx:223`
models `<SubagentActivityProvider key={threadId}>` above `{children}`,
commented "Remounting is deliberate".

Follow all three and you key a layout-level provider on a thread id that
changes asynchronously after mount. Everything below it dies
mid-interaction.

Two properties made it invisible:

- Durable threads exist only in Intelligence mode, so with a plain SSE
runtime `useThreads` returns nothing, the selected thread never changes,
and the remount never fires. It appears the moment Intelligence is
wired.
- Whether state survives depends on whether the user acted before the
thread list resolved.

## What changed

Docs only — no library change. Both traps now carry their blast radius,
in the four places an agent actually reads:

| File | Change |
|---|---|
| `SKILL.md` | Two invariants in the load-once section, so they land
before any reference is opened |
| `references/threads.md` | New HIGH entry on keying above app state;
note that `activeId` in the switcher recipe settles asynchronously |
| `references/switching-agents.md` | Existing HIGH entry now states the
blast radius and cross-links the threads trap |
| `references/switching-agents-recipes.md` | Key rule amended — keep it
on `<CopilotChat>`, nowhere higher |
| `references/agent-access.md` | The second route to the same symptom:
`useAgent` swaps a provisional stand-in for the real agent when `/info`
resolves, so an effect keyed on `agent` re-runs once, mid-interaction.
Adds an `isReady` pattern and a HIGH entry |

`isReady` appeared in **zero** shipped skills before this — it was
documented only in `showcase/shell-docs/.../useAgent.mdx` and in JSDoc.
Same shape as OSS-888, where the root cause was the shipped skill rather
than the library.

Also corrects a factual error: the skill claimed `useAgent` returns `{
agent }` only. It returns `{ agent, isReady }`.

The 10-file diff is 5 source files under `packages/react-core/skills/`
plus their 5 mirrors under `skills/`, regenerated with `pnpm
sync:plugin-skills`.

## Verification

- `pnpm check:plugin-skills` — mirror in sync
- `pnpm exec vitest run scripts/__tests__/sync-plugin-skills.test.ts` —
12 passed
- `oxfmt --check` — clean over both skill trees
- Full pre-commit suite green, including `test-and-check-packages`
(`test`, `publint`, `attw` across 2 projects and 20 dependent tasks)

## Not in scope

Whether the run's app keyed on `threadId` or on `agent` is not
settleable from the repo — its source is not in any checkout, and there
is no `2026-08-25` strands run report under
`tools/one-prompt-development/evaluation/runs` on any branch. Both
variants produce the reported symptom and this covers both, so a
first-hand repro is a separate task. The `reskinnable-demo` layout is
left as-is deliberately: it is a legitimate use of the pattern, and it
is now the worked example the guidance warns about.

Scoping detail in the OSS-979 comment.

refs OSS-979

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-26 14:38:23 -05:00
Ben Taylor 61a67e716b fix(runtime): keep thread naming task after transcript (#6722)
## Summary

- place the thread-title task after the embedded conversation transcript
- explicitly tell the reused agent not to answer the conversation
- add a regression test that locks the prompt ordering

## Why

LangGraph starter agents can interpret the final embedded `user:` line
as the active request when the transcript comes last. They answer the
conversation instead of returning title JSON, causing retries and
eventual `Untitled` thread names.

## Validation

- reproduced with the latest LangGraph starter and
`@copilotkit/runtime@1.69.2`
- original prompt: 0/12 direct calls returned title JSON; 4/4 targeted
threads fell back to `Untitled`
- reordered prompt: 12/12 direct calls returned title JSON; 4/4 targeted
threads received generated titles
- runtime thread-name unit suite: 28/28 passing
- pre-commit affected package checks passing under the repository Node
22 toolchain
2026-08-26 13:44:47 -05:00
Maximiliano Korp eeb01fc33f fix(runtime): keep thread naming task after transcript 2026-08-26 11:28:30 -07:00
copilotkit-qa-bot[bot] ad8f3f0a1b Merge remote-tracking branch 'origin/main' into codex/fac-122-predict-state-tool-argument 2026-08-26 11:00:48 -07:00
copilotkit-qa-bot[bot] b04b47c932 fix(react-core): preserve predictive state updates 2026-08-26 10:44:39 -07:00
Tyler Slaton d44178a8f0 fix(web-inspector): match Playground composer surface 2026-08-26 19:31:36 +02:00
Tyler Slaton 76f3e9fff4 fix(web-inspector): repair Inspector verification baseline 2026-08-26 19:23:38 +02:00
Tyler Slaton edbbdbdf80 fix(web-inspector): restore Playground surface styling 2026-08-26 19:22:45 +02:00
copilotkit-qa-bot[bot] 6d6f59ee77 Merge main into FAC-122 predictive state fix
# Conflicts:
#	packages/react-core/src/v1-deprecated/components/__tests__/CopilotListeners.predictState.test.ts
2026-08-26 10:14:07 -07:00
Alem Tuzlak 5a188d6783 fix(web-inspector): animate launcher hover scale and color 2026-08-26 09:25:41 -07:00
Alem Tuzlak 93861b428d fix(web-inspector): polish inspector chrome, threads, and dark mode 2026-08-26 09:25:41 -07:00
Tyler Slaton e8a5e00e8b feat(web-inspector): preview launcher HUD on load 2026-08-26 17:41:32 +02:00
Benjamin Taylor 6669b3a487 docs(react-core): state the blast radius of key-remount and the provisional agent (refs OSS-979)
An Intelligence integration lost a request-to-row correlation map partway
through a user interaction, with no error and no warning. It surfaced as
"our response routing is flaky". OSS-979 filed it as CopilotKitProvider
remounting its children.

The provider does nothing of the kind. It renders `{children}`
unconditionally, unkeyed, with no early return and no Suspense boundary
anywhere in v2. The remount was app-side, and it was app-side because this
skill told it to be:

* `references/threads.md` teaches `useThreads()` -> select ->
  `<CopilotChat key={activeId}>`, and that recipe is only reachable once
  Intelligence is wired.
* `references/switching-agents.md` teaches "`key={activeAgent}` forces
  remount so thread state doesn't leak" without saying what else that
  discards.
* `examples/showcases/reskinnable-demo/src/app/[skin]/layout.tsx:223`
  models `<SubagentActivityProvider key={threadId}>` above `{children}`,
  commented "Remounting is deliberate".

Follow all three and you key a layout-level provider on a thread id that
changes asynchronously after mount. Everything below it dies
mid-interaction.

Two properties made it invisible. Durable threads exist only in
Intelligence mode, so in OSS-only development the selected thread never
changes and the remount never fires. And whether state survives depends on
whether the user acted before the thread list resolved.

Both traps now carry their blast radius, in the four places an agent
actually reads:

* `SKILL.md` -- two invariants in the load-once section, so they land
  before any reference is opened.
* `references/threads.md` -- a HIGH entry on keying above app state, plus a
  note that `activeId` in the switcher recipe settles asynchronously.
* `references/switching-agents.md` and `switching-agents-recipes.md` --
  keep the `key` on `<CopilotChat>`, never on a wrapper or a layout
  provider.
* `references/agent-access.md` -- the second route to the same symptom.
  `useAgent` swaps a provisional stand-in for the real agent when `/info`
  resolves, so an effect keyed on `agent` re-runs once, mid-interaction.
  Adds an `isReady` pattern and a HIGH entry. `isReady` appeared in zero
  shipped skills before this; it was documented only in shell-docs and in
  JSDoc.

Also corrects a factual error: the skill claimed `useAgent` returns
`{ agent }` only. It returns `{ agent, isReady }`.

No library change. The provider behaves correctly; the guidance did not
describe what it costs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:58:28 -05:00
David McKay b4145f42fc fix(runtime): stop reporting every connect failure as a 404 (closes OSS-971)
The Intelligence connect handler classified a handful of platform rejections and
flattened everything else into HTTP 404 "Connect plan not available", writing the
real cause only to server-side stderr.

That made every unrecognised failure look like a missing thread. A 500 from
app-api, a socket timeout, a connection reset and a bug in our own code all
produced the same misleading answer, and the only way to find out what actually
happened was to read the runtime container's logs.

It cost a customer a day. Their Redis filled, app-api returned 500 on a
join-code write, and their engineer saw a 404 naming a "connect plan" that had
nothing to do with the failure. The platform was up and the request was
retryable; the reported status said neither.

Now:

  - a status we already special-case (400, 401, 403, 404, 409) still reports as
    a rejection with its message, unchanged;
  - any other status from the platform passes through as itself, so a 503 stays
    a 503 and the caller knows to retry; and
  - an error carrying no status never reached the platform, so it reports 502
    rather than 404, which says "the thing behind me is unreachable" instead of
    asserting the thread does not exist.

Every branch now returns the underlying message rather than burying it in a log
line the caller cannot see.

The SSE run path in handlers/shared/sse-response.ts has the same shape and is not
addressed here: it returns 200 and text/event-stream before the run starts, so a
later throw closes the stream with no events and no error frame. That needs its
own change to the streaming contract.
2026-08-26 07:55:31 -07:00
Alem Tuzlak fd7f2fa683 Merge origin/main into alem/hud-arrow-color 2026-08-26 11:36:50 +02:00
Tyler Slaton a6751df682 fix(web-inspector): theme launcher HUD with inspector 2026-08-26 03:40:30 +02:00
Tyler Slaton d2e0c35cae fix(web-inspector): align launcher HUD shell styling 2026-08-26 03:38:32 +02:00
Tyler Slaton d2a6eee07a fix(web-inspector): align launcher HUD availability 2026-08-26 03:29:38 +02:00
tylerslaton 9629e930d1 chore: release monorepo v1.69.2 2026-08-26 00:18:42 +00:00
Tyler Slaton b3b339f544 Revert "feat(web-inspector): add Event Snippets and save-as-snippet (#6649)"
This reverts commit ba4260ad66, reversing
changes made to 47c5510b49.
2026-08-26 02:11:19 +02:00
Tyler Slaton dc916484ab fix(inspector): clarify local action availability
fix(inspector): group local developer actions

fix(inspector): refine local tools menu

fix(inspector): use direct local action
2026-08-26 02:11:19 +02:00
Mike Ryan b3c3cb0d7b test(web-inspector): stop the gesture tests racing the beat on real timers (#6693)
## Problem

`packages/web-inspector/src/__tests__/launcher-error-signal.spec.ts` is
failing intermittently on `test / unit`, across unrelated branches and
on `main`. It is currently red on the **v1.69.1 release PR**.

```
FAIL  src/__tests__/launcher-error-signal.spec.ts >
      the whole gesture completes on its own and leaves the resting state behind
AssertionError: expected 'opening' to be 'closed'
```

| When | Branch | Shard | Run |
|---|---|---|---|
| 19:29Z | `release/publish/monorepo/v1.69.1` | Node 24 / React 18 |
[32886136479](https://github.com/CopilotKit/CopilotKit/actions/runs/32886136479)
|
| 14:09Z | `ben1/oss-924-agui-core-058` (#6687) | Node 24 / React 19 |
[32857130830](https://github.com/CopilotKit/CopilotKit/actions/runs/32857130830)
|
| 12:41Z | `lukas/oss-903-presentation-…` | Node 20 / React 18 |
[32848616256](https://github.com/CopilotKit/CopilotKit/actions/runs/32848616256)
|

Neither #6687 (an `@ag-ui/core` version bump) nor the presentation
branch touches gesture timing, and no shard fails consistently — it
follows runner load, not code.

## Cause

Both real-timer tests in this suite asserted a *pre-beat* state 200ms
after breaking the connection:

```ts
const context = await setup({ realTimers: true });
await context.breakConnection();
await context.advance(200);
expect(pillPhase(context.inspector)).toBe("closed");   // ← races the beat
```

Under `realTimers`, `advance(ms)` is a literal `setTimeout(resolve, ms)`
(spec L656–664). The pill's `closed → opening` transition fires at
`ERROR_GESTURE_MS.beat = 400` (`index.ts` L330–339, scheduled at
L18998). So the assertion had a **200ms margin against a 400ms boundary
on a wall clock**. On a loaded runner the 200ms sleep overshoots 400ms,
the beat has already fired, and the phase reads `opening`.

The comment directly above the test already says phase boundaries are
asserted on the fake clock "because real timers would make this suite
slow and flaky" — and then this was a phase-boundary assertion on real
timers.

The same 200-vs-400 race sat in the adjacent test (`pulsing` is true
only for the beat's 400ms), so both are fixed here.

## Fix

Remove the two racy preconditions. Both claims are already pinned
deterministically on the fake clock at spec L411–427, which asserts
`pillPhase === "closed"` **and** `pulsing === true` right after arming,
then walks every phase boundary. These real-timer tests exist only to
show the beat and the gesture run to their end on their own — which the
loops and their closing assertions still prove.

This also matches the idiom the sibling `launcher-signal.spec.ts`
already uses (L667–676): assert at t≈0, then poll for the end.

## Testing

**1. Reproduced the CI failure locally.** Injected a 250ms stall before
the assertion on the unmodified test, simulating a loaded runner (200ms
sleep + 250ms ≈ 450ms > the 400ms beat):

```
FAIL  src/__tests__/launcher-error-signal.spec.ts > the whole gesture completes on its own …
AssertionError: expected 'opening' to be 'closed' // Object.is equality
Expected: "closed"
Received: "opening"
 ❯ src/__tests__/launcher-error-signal.spec.ts:2666:40
```

Byte-for-byte the CI assertion.

**2. The fix survives that same simulation.** With a 700ms stall (well
past the beat) injected into both tests:

```
 ✓ the beat ends and leaves the resting dot behind  750ms
 ✓ the whole gesture completes on its own and leaves the resting state behind  3435ms
 Tests  2 passed | 88 skipped (90)
```

**3. Mutation-checked that the remaining assertions still have teeth.**
Three separate breaks to `src/index.ts`, each caught:

| Mutation | Result |
|---|---|
| Gesture opens but never closes (drop the `closing` phase +
`endGesture`) | `FAIL … AssertionError: expected <span …> to be null` |
| Pill never opens at all (`openPill` returns early) | `FAIL …
AssertionError: expected false to be true` (`sawOpenPill`) |
| Beat never ends (`beat: 400` → `999_999`) | `FAIL … AssertionError:
expected true to be false` (`pulsing`) |

Source restored afterwards; `git status` confirms this PR touches only
the spec file.

**4. Full `@copilotkit/web-inspector` suite:**

```
 Test Files  29 passed (29)
      Tests  625 passed (625)
```

**5. Pre-commit gate** (`test-and-check-packages`: test, publint, attw
across 5 projects + 22 dependencies, incl. `@copilotkit/react-core`,
`@copilotkit/angular`, `@copilotkit/runtime`) passed on the committed
tree.

## Note

`main` also has a second, unrelated flake I did not touch here —
`CopilotChatToolRerenders.e2e.test.tsx > should not re-render a
completed tool call when subsequent text is streamed` (`expected 4 to be
3`), which reddened `main` at `0943c519` ([run
32750372113](https://github.com/CopilotKit/CopilotKit/actions/runs/32750372113)),
a runtime `.d.ts` change that touches no react-core chat code. Different
mechanism, worth its own issue.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-25 14:22:03 -07:00
Benjamin Taylor b8283ef4e1 refactor(scripts): match dead hosts case-insensitively, carry each reason with its host
DNS is case-insensitive, so a capitalized host in prose would have slipped the
literal match. Env var names stay case-sensitive — `ignoreCase` is opt-in per
rule. The per-host reason moves onto the constant so adding a third host cannot
silently inherit the wrong message.

Also restores the TSDoc's original framing of what an override is for
("non-production or future self-hosted"), matching the runtime skill's wording
rather than diverging from it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 15:45:27 -05:00
Benjamin Taylor b057744684 fix(docs): stop shipping stale Intelligence config claims, and gate the dead hosts (refs OSS-961)
The packaged runtime skill up to v1.62.2 prescribed `api.copilotkit.ai` /
`realtime.copilotkit.ai`. The first host is a CNAME onto the legacy Copilot
Cloud ALB, where no listener rule matches it, so every request gets the ALB
default action: a 404 with an empty body. The second has no DNS record at all.
A reader who followed that page converted a working OSS install into a 502.

The hosts themselves were corrected in v1.64.0, but two shipped surfaces still
carried stale claims about the same step, and nothing stopped the hosts from
coming back a third time:

- The debug skill said Intelligence "requires ... `apiUrl`, `wsUrl`, `apiKey`,
  `tenantId`". Three errors in one line: `apiUrl`/`wsUrl` have been optional
  with managed defaults since v1.64.0, and `tenantId` has never existed on
  `CopilotKitIntelligenceConfig` — the API key carries the project (its token
  format is `cpk-{projectId}_...`) and the platform resolves the organization
  server-side, so there is no org or tenant field for a caller to pass.
- `CopilotKitIntelligence`'s own TSDoc showed only `*.internal` placeholders,
  so the class's hover docs never named the pair that actually serves prod.

`validate-intelligence-env-names` — already the unfiltered guard for this same
config surface (OSS-881) — now also fails on either dead host. The
channels-intelligence realtime test is allowlisted: it needs a hostname that
genuinely does not resolve, since `getaddrinfo ENOTFOUND` is the condition
under test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 15:35:55 -05:00
Tyler Slaton 79c02f0f02 style(web-inspector): fewer layers on the launcher, a lens for its dot (#6688)
## What does this PR do?

Design review on the launcher and its notification dot asked for three
things: a milder face than solid black, fewer borders and background
layers, and a subtle shadow in place of the dot's heavy border. This is
all three, plus the removal of six utilities that never had any effect.

Everything here was compared side by side at production size, on a light
*and* a dark host page, before it was chosen. Two of my own first
proposals were dropped after measuring them, both described below.

**Two concerns, three commits.** `4fa38d91f` and `cd711b369` are the
launcher itself — the package change, 2 files. `295f75495` gives the
react-router lab a dark mode, because a dark host page is what this
change has to be judged against and the lab could not produce one. If
you would rather review those separately, say so and I will split them.

## The face

`#181C1F` at 95%, which review asked for.

Worth recording so it does not come up again: the near-black the review
saw was `#010507`, 20.5:1 against white. What shipped yesterday was
already `#1C1F24` at 16.5:1, so this value is a hair *darker* than the
one it replaces (17.2:1) and the difference between them is a ΔE of 2.3,
at the floor of what an eye can separate. It settles the question rather
than changing the look.

## Fewer layers

Six Tailwind utilities on the launcher set properties the unlayered
`css` block sets again — `bg-slate-950/95`, `border-white/20`, `ring-1`,
`ring-white/10` and the two hover variants. Unlayered declarations beat
layered ones regardless of specificity or source order, so none of them
has ever had any effect. Each was the package's only use, so the
checked-in stylesheet drops 980 bytes.

Of the *visible* layers, two went:

**The outer hairline.** The launcher carried two concentric lilac rings:
the border, and a second one 1px outside it as a box-shadow. The outer
one also hardcoded the lilac rather than reading `--cpk-launcher-edge`,
so it silently could not follow the token. It is replaced by a one-pixel
light edge along the top, which is what keeps the face from reading flat
without drawing a frame.

**`backdrop-blur-md`.** It sat behind a 95%-opaque fill and bought close
to nothing, while mounting a permanent blur compositing layer over a
customer's page.

**The border stays, and this is the finding that changed my mind.** I
first proposed removing it too. Against a dark host page the face
measures 1.10:1 (GitHub dark), 1.04:1 (Tailwind slate-900) and 1.22:1
(black) — indistinguishable from the page. The border is the only thing
that gives the launcher an outline there. It is not decoration.

## The dot

The collar was `1.5px`, opaque, zero blur, and 21% of the dot's
footprint. 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 are derived
from `--cpk-launcher-signal`, so a new tone needs no new values;
verified for the rose error tone and the violet announcement tone.

**Dropped after looking at it:** a coloured glow around the dot. It was
the obvious reading of "more premium", but the launcher already pulses
in that same colour when a failure is new, and a permanent glow competes
with the thing that is supposed to draw the eye.

**Also dropped:** tinting the border in the signal colour, which was
suggested in review. On a dark page the border is the entire silhouette,
so tinting it recolours the whole launcher for a state that can persist
for hours.

## One non-obvious consequence

Removing the blur removed a side effect nobody had written down:
`backdrop-filter` promotes the element to its own compositing layer.
Without a layer, the hover `scale(1.05)` re-rasterises the mark every
frame and it visibly jitters — geometrically nothing moves, the mark's
centre holds to three decimals, but the vector is re-rendered at
fractional offsets. `will-change: transform` asks for the layer directly
and the jitter is gone. Confirmed by eye on the running demo before this
was chosen.

## Tests

`packages/web-inspector` stays at **28 files / 611 tests**, all passing.

No new tests. The colour tests here are deliberately token-shaped rather
than value-shaped — they assert the custom property and the *sharing* of
one face and one edge between the launcher and its pill, never a hex —
so face and edge values are free to move and this change is exactly the
kind they were written to allow. The one test that constrains it, `"the
pill and the launcher share one surface and one edge"`, still passes.

What is genuinely unguarded, and was before this PR too: the dot's
collar width, the double hairline, and the Tailwind class list.
Asserting rendered geometry would need a browser test runner, which this
package does not have — jsdom computes no layout.

## How to see it

`pnpm --filter react-router-example dev`, then `http://localhost:5173`.
The launcher is top right; `Break runtime` arms the error tone and
`Break run` the announcement one. Hover it to check the mark no longer
jitters.

One thing worth knowing while reviewing: the launcher anchors top-right
and is `position: fixed` on an element mounted directly under `<body>`,
so on this page it sits over the lab's toolbar. Drag it to the lower
right and it is out of the way.

## The lab's dark mode

A dark host page is where the launcher's border earns its place, and the
lab had no way to produce one, so reviewing this change was not possible
without it.

It follows `examples/v2/react/demo` rather than inventing anything: the
host owns a `theme` state, and `CopilotChat` gets `className="dark"` —
which is what makes the package swap its own variable set. The colours
are the demo's by another route; it writes the oklch literals
CopilotKit's variables use, and those are Tailwind's neutral steps
(`neutral-950` is `oklch(0.145 0 0)`, `neutral-50` is `oklch(0.985 0
0)`, `neutral-800` is `oklch(0.269 0 0)`). Measured identical on the
running lab.

`@custom-variant dark (&:is(.dark *))` is needed in the lab's stylesheet
because Tailwind v4 points `dark:` at `prefers-color-scheme` by default,
so the toggle would have lost to the OS. Same declaration the package
uses for its own sheet.

Two details that are decisions rather than oversights. The **error
banner keeps a rose tint** in dark mode instead of going neutral,
because an error banner that looks like every other surface is not an
error banner. And the **toolbar buttons keep a visible on/off contrast**
— active inverts to a light face, inactive sits on `neutral-800` —
because the lab's whole purpose is knowing which failure is currently
armed.

My first attempt stripped every background instead of theming, and that
is worth recording because it looked plausible: the chat bubble, the
send button, the button states and the banner all collapsed into one
flat grey. The chat paints its own surfaces and has to be told what
theme it is in, not undressed.

## A separate bug found on the way

`CopilotKitProvider` documents `inspectorDefaultAnchor` — *"Default
anchor corner for the inspector button and window"* — and it has no
effect. `defaultAnchor` is typed on the React wrapper and forwarded to
the element, but the string `defaultAnchor` does not occur anywhere in
`packages/web-inspector`, so it lands as `defaultanchor="[object
Object]"` and is ignored. The corner stays hardcoded `{ horizontal:
"right", vertical: "top" }` in two places.

Not fixed here, to keep this PR to one concern. It is worth fixing: any
host with a top navigation bar hits exactly this, finds exactly that
prop, and it does nothing.

## Related PRs and Issues

- Follows #6646
2026-08-25 13:27:06 -07:00
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
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
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