Commit Graph

2830 Commits

Author SHA1 Message Date
BenTaylorDev bf1bb98765 chore: release angular v0.4.0 2026-08-28 15:03:28 +00:00
Alem Tuzlak 8469e72b30 feat(web-inspector): copy stored threads into Playground from Threads (#6642)
Inspector Threads now has **Try from here**. One click copies a stored
thread into a Playground scratch session. The stored thread does not
change.

If the copy fails, Inspector stays on Threads and keeps the current
Playground scratch. Example tour threads and locked Threads do not show
the button.

## What does this PR do?

Adds **Try from here** on a real stored thread in Inspector Threads. One
click copies messages and thread state into a Playground scratch
session. The stored thread does not change.

If the copy fails, Inspector stays on Threads and keeps the current
Playground scratch. Example tour threads and locked Threads do not show
the button.

## Related PRs and Issues

- Linear: OSS-873
- Playground base: https://github.com/CopilotKit/CopilotKit/pull/6580
(merged)

## 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

## Testing

**Commands run**

1. Rebased `feat/oss-873-try-from-here` onto `origin/main` and resolved
6 conflict files.
2. `npx nx run @copilotkit/web-inspector:test` — 626 tests passed (after
the stale-result guard).
3. `npx nx run @copilotkit/web-inspector:check-types` — passed.

**Manual test**

1. Open Inspector on localhost with Intelligence on, so a real stored
thread exists.
2. Open that thread. Confirm **Try from here** is in the thread header.
3. Click **Try from here**. Confirm Inspector opens Playground with the
copied messages and the stored thread is unchanged.
4. Open an example tour thread. Confirm **Try from here** is not shown.
5. Force a copy failure (disconnect runtime). Confirm Inspector stays on
Threads and the prior Playground scratch is unchanged.

**How this PR makes testing easy**

- `packages/web-inspector/src/__tests__/inspector-navigation.spec.ts`
covers the button, copy path, failure path, and a stale click that must
not overwrite Playground.
- `packages/web-inspector/src/lib/__tests__/telemetry.test.ts` covers
`oss.inspector.threads_try_from_here_clicked`.

## Risk / rollback

Risk is limited to Inspector Threads and Playground. A revert of this PR
removes the button and the new telemetry event. No runtime protocol
change.

## Public API change

New Inspector telemetry export and event name:

**Before**

```ts
trackThreadsTabClicked(props);
```

**After**

```ts
trackThreadsTabClicked(props);
trackThreadsTryFromHereClicked({ ...props, outcome: "success" });
```

`CpkThreadInspector` also emits a `tryFromHere` custom event when the
user clicks the button.
2026-08-28 16:10:06 +02:00
Alem Tuzlak 5a191c8e36 fix(web-inspector): put Try from here next to Expand all
Move the button onto the messages toolbar, on the right of Expand all and Collapse all, with a top-right arrow.
2026-08-28 15:57:18 +02:00
Alem Tuzlak ec146f6721 fix(web-inspector): drop stale Try from here results 2026-08-28 15:01:06 +02:00
Alem Tuzlak d6812e41c8 fix(core): report the runtime connection status from the last actual contact (#6706)
Closes OSS-904.

## The problem

`CopilotKitCore.runtimeConnectionStatus` was set only by the `/info`
handshake, which runs **once on connect**. If the runtime became
unreachable after that, the status stayed `connected` indefinitely —
measured two independent ways against `examples/v2/react/demo` and
recorded in the ticket.

The failure was not lost, it was filed in the wrong drawer: it arrived
as `agent_run_failed`, indistinguishable from an agent bug. So
everything downstream inherited the wrong answer — System Health
reported healthy, the launcher error signal could not fire for the most
common real symptom ("it worked a minute ago"), and a customer `onError`
handler written to separate wiring problems from agent problems got the
wrong classification.

## What this changes

The status now reports **the outcome of the last actual contact with the
runtime**.

A failed runtime request — or silence past a per-request watchdog —
triggers **one** bounded confirmation request. If nothing answers, the
status moves to `error` and the failure is emitted through the existing
wiring error code, so customers already handling startup wiring failures
pick up the mid-session case without changing a line. A subsequent
successful request re-syncs and clears it.

Crucially, **the conversation survives**. The transition does not
discard runtime knowledge, so the agent backing an open chat is the same
instance, its messages stay on screen, and submitting stays possible —
which matters because submitting is what restores the status.

No polling, no heartbeat, no retry loop. Every timer is bound to one
request and dies with it.

## Decisions worth knowing when reviewing

- **Reactive in both directions.** A heartbeat would put permanent
background traffic into every embedding application; a retry loop mostly
races a user who is about to retry anyway. The cost is stated rather
than hidden: while nothing is happening, nothing is detected.
- **Status change is separated from discarding knowledge.** The only
pre-existing code that set `error` also cleared `remoteAgents`. That is
right at startup and destructive mid-session, because conversation state
lives on the agent instance. Four sites now hold this invariant up
together; each carries a comment saying so.
- **The trigger is deliberately permissive, and the check is the
arbiter.** A request that received a successful response never triggers
a check; user cancellation never does; everything else may. Defining the
trigger precisely would mean maintaining a status-code list that is
complete only for the deployment topologies someone thought of.
- **Silence counts.** A server can refuse (fails fast) or hang (accepts
and never answers). A stopped dev server refuses; a container
mid-rollout, a half-switched deploy and a dropped tunnel hang. Only
bounding the check does not help, because no check starts — hence the
per-request watchdog. It observes only and never cancels the request.
- **The rule is stated by destination, not by call site**, so a runtime
route added later inherits the behaviour. Excluded: the Intelligence
realtime endpoint (a different service — reporting its outage as
"runtime unreachable" would be a false diagnosis), endpoints belonging
to the customer, and the stop request.
- **Recovery may prune, under two conditions**: the runtime must have
reported at least one agent, and the agent must carry no conversation
state. An empty list is the signature of a runtime that has not finished
registering.
- **"Answered but refused" keeps the error status and gets a different
message.** An expired token means the app cannot work, so red is right;
telling the reader "unreachable" would send them to check ports and
containers.

## Deliberately not delivered

- Detecting an outage, or a recovery, while the application is idle.
- Recovery by opening the Threads view: every binding withholds thread
requests until the status is already connected, so nothing is sent while
it is red. The thread plumbing still earns its place for *detection*.
- A signal for the Intelligence realtime endpoint failing while the
runtime is healthy — a real gap, and its own ticket.
- Memory and suggestion routes adopting the instrumented fetch.
- A new status value or a new error code.

## Costs this introduces

`error` now means two things — "never connected, no agents" and "lost
mid-session, agents intact". Documented on the enum. And because the
status can now change mid-session at all, an outage costs some churn
that did not exist before: the memory list and the Inspector's thread
list are cleared and refetched, and where the chat owns its run-activity
store it is stopped and restarted. All of it is paid on a user-caused
transition, never while idle.

## Testing

Four independent reviewers audited an earlier revision of this branch;
the ten defects they reproduced are fixed and each is pinned by a test
that was red first. A mutation audit of 110 mutants killed 90; the
surviving holes were closed in the round after.

The connection-health suites carry 72 tests. Request counting is a
first-class assertion throughout, because several decisions are
*absences* — no polling, no retry loop, one check per burst, no traffic
while red — and an absence is only testable by counting. Those tests use
fake timers advancing ten minutes; that boundary is documented where it
lives, since anything slower is invisible to them.

Verified by hand in a browser with the runtime running as its own
process, so the page outlives it: a refusing runtime, a hanging runtime,
recovery, an agent added during an outage, an agent deleted during an
outage. `performance.timeOrigin` was checked throughout to prove the
page never reloaded and the result was not an artefact of a fresh
handshake.

## Follow-ups this leaves behind

Three of these deserve their own ticket. None blocks this PR; all three
are consequences of where its scope was drawn, and they are listed here
so the boundary is explicit rather than implied.

### 1. A signal for the Intelligence realtime endpoint

In Intelligence mode the browser gets its chat events from a **second
service** at its own address; the runtime is only asked for the
credentials. If that service fails while the runtime is healthy, this
change correctly reports the runtime as reachable — and the user
experiences exactly the silence this ticket exists to remove.

It is excluded here on purpose: folding it into the runtime status would
report "runtime unreachable" about a healthy runtime, and a false
diagnosis costs more debugging time than no signal. It needs its own
signal, which is a presentation decision as much as a detection one.

### 2. Memory routes onto the instrumented fetch

The memory store still builds with the global fetch, so its
runtime-bound requests are invisible to connection health. Two costs: a
genuine failure there is a signal we discard, and a success there cannot
restore the status.

The asymmetry is what makes this worth fixing rather than leaving:
memory is the surface most disrupted by a status transition (its list is
cleared and refetched) and currently the one least able to contribute.
The change itself is small — that module already takes its request
function as an injected dependency.

### 3. Consumers should key on what they need, not on the status value

Several consumers treat "status is not connected" as "discard
everything": the memory list, the Inspector's thread list, and the
chat's run-activity store. That was harmless while the status could not
change after page load. It can now, so every outage costs churn that did
not exist before.

This is the same mistake this PR fixes three times *inside* core — a
guard bound to a state instead of to the thing it protects. The
principle was applied internally and not to these consumers. That makes
the churn listed under "Costs" above **deferred rather than inherent**,
and it is the largest of the three follow-ups: three consumers in three
packages, each with its own risk, which is why it was kept out of this
PR.

### Two smaller items

- The launcher error signal on `main` carries a comment stating the
limitation this change removes ("a runtime that dies after the page
loaded … raises nothing … closing that gap means a re-probe in the
core"). It becomes false when this lands and should be corrected then.
- `packages/web-inspector/src/styles/generated.css` is build output
under version control and re-dirties the tree on every build. Unrelated
to this PR, but the Tailwind source glob scans test files, so any prose
comment containing a utility word (`fixed`, `hidden`, `visible`,
`block`) silently changes the committed CSS. Narrowing the glob would
remove the class of problem.

Full specification, including the interview decisions and every
revision: `OSS-904-PRD.md`.
2026-08-28 14:46:15 +02:00
github-actions[bot] c71dcfbb12 style: auto-fix formatting 2026-08-28 14:24:39 +02:00
Alem Tuzlak 95285be33b feat(web-inspector): copy stored threads into Playground from Threads 2026-08-28 14:24:05 +02:00
Alem Tuzlak 1cb76928ad Turn the Home Intelligence card into an install path (#6740)
## Why

The Inspector said what Intelligence *is* and linked out to a signup
page. Of ~1,655 Inspector opens in 90 days, **under 100 clicked any
CTA**. This replaces the feature list with an argument, and the outbound
link with an install that happens in the editor the developer is already
in.

This is the unfinished half of OSS-867, whose body asks for exactly
this: *"If a capability requires Intelligence, detail why and include a
video demonstrating that feature working end-to-end."*

## What changed

**A four-slide argument, paired to the picture beside it.** Each slide
carries two sentences and the visual they describe: your users' threads
→ the pattern inside them → the skill file → what it compounds into. An
earlier draft sold Threads in prose while animating Learning; bound
together they read as one chain, and `meeting-scheduling/SKILL.md`
recurs through all four so the closing diagram is checkable rather than
decorative.

Condensed from the six-phase animation on the Intelligence home page —
not screen-recorded. A ported version is themeable, stays sharp, and
costs no asset weight; the original also runs 21.4s and opens on the
agent booking the wrong meeting, a poor first frame for a card arguing
for the product.

**A copy-prompt button instead of a link out.** It hands the CLI's own
onboarding prompt to a coding agent. Every previous Intelligence CTA
opened a new tab into a signup form, which is where developers drop out.

It carries the CLI's `onboarding_run_id`, so
`oss.inspector.home_prompt_copied` can be joined to
`cli.onboarding.completed` on the Intelligence side. `home_cta_clicked`
only ever proved that someone clicked a link — this is the first event
that can show whether an install followed.

**Section anatomy mirrors System Health** (header band, rule, content),
so the action sits in the same top-right slot the status pill and renew
link already use, and the panel keeps one section shape throughout.

## Correctness of the claims

The copy was checked against the product's own surfaces, and two claims
did not survive:

- **Skills are not applied at run time.** Candidates land at
`pending_review`, a human approves, and the published set is pulled down
with `copilotkit skills download`. Nothing reads published skills during
a run. The slide says approve → pull in → the next run starts from what
worked.
- **Insights were missing**, and with them the evidence link that makes
Learning credible: every Insight cites the Threads and messages behind
it.

Also: *Rich Threads* is the product's name for the durable ones, and the
distinction is the whole sale next to a Threads tab full of local ones
that die on reload. "Your users" means the app's end users — which is
what the platform means too (`identifyUser` resolves one user per
request; a thread carries `end_user_id`, renamed from `user_id` because
the old name *"caused repeated misdiagnosis"*).

## Behaviour worth reviewing

- The story advances **only while Home is visible and the document is
not hidden**. A debugging tool should not hold a repeating timer behind
a closed panel.
- Slide motion is horizontal and derived from each slide's index
relative to the active one, so clicking a tab backwards animates
backwards with no stored direction to fall out of sync.
- Copied state **expires after 4s** so the button invites a second
press; a failed copy **does not**, because that state is the only place
the prompt is selectable by hand.
- Three modes, not two: a lapsed plan keeps the renew link and never
sees an install prompt.
- The rotating copy is hidden from assistive tech (it would announce
four times a loop); one stable sentence is exposed in its place and is
test-covered so it cannot quietly rot.

## Deliberate omissions

**No third-party coding-agent logos on the button**, unlike the
Intelligence app. That app is a private hosted surface; this package
ships inside other people's sites, and vendoring Anthropic's and
OpenAI's marks is not a call to make quietly. The helper line names the
agents in text.

Deferred and worth discussing separately: ordering the Home sections by
state (health first when something is broken, Intelligence first when
nothing is), and putting the same button in the locked Learning and
Threads tabs, where intent is highest.

## Verification

617 tests pass, `check-types` clean, oxlint 0 errors. Verified live in
both themes: all four slides, uniform 16px padding on every slide, card
height stable across slides, copy success **and** failure paths, and the
header band unchanged at 76px when the copied hint appears. The reset
behaviour is mutation-checked — the file records which mutation each
test does and does not catch.
2026-08-28 14:14:39 +02:00
Alem Tuzlak 5686a0669e Merge branch 'main' into lukas/oss-904-runtime-connection-status 2026-08-28 14:06:40 +02:00
Alem Tuzlak 1dfc5cdafa refactor(core): remove OSS-904 design comments 2026-08-28 13:27:37 +02:00
Alem Tuzlak a7191e2a12 fix(core): bound recovery /info hang and tighten OSS-904 comments 2026-08-28 13:08:44 +02:00
Alem Tuzlak b8b35b736c fix(packages): declare the MIT SPDX license on five published packages (#6511)
Five packages publish to npm with no `license` field, so registry
metadata and automated license scanners report them as **Unknown**:

```
@copilotkit/agentcore-runner  published=1.68.1  license=<NONE>
@copilotkit/core              published=1.68.1  license=<NONE>
@copilotkit/sqlite-runner     published=1.68.1  license=<NONE>
@copilotkit/voice             published=1.68.1  license=<NONE>
@copilotkit/web-inspector     published=1.68.1  license=<NONE>
```

The repo is MIT (see `LICENSE`) and every other published
`@copilotkit/*` package already declares it — these five were simply
missed. This adds `"license": "MIT"` to each, positioned before
`"repository"` to match the sibling packages.

## Why

Reported downstream in #2860, where a corporate procurement scan refused
packages whose license it could not resolve. That class of scanner reads
the `license` field from registry metadata; a `LICENSE` file in the repo
is not enough, and these packages ship no `LICENSE` file either.

**Correcting the record on that issue while I am here:** the `@ag-ui/*`
packages named in the original report are *not* affected. Every version
the reporter’s scanner flagged already carries `"license": "MIT"`:

```
@ag-ui/client@0.0.42     MIT
@ag-ui/core@0.0.37       MIT
@ag-ui/core@0.0.42       MIT
@ag-ui/encoder@0.0.42    MIT
@ag-ui/langgraph@0.0.20  MIT
@ag-ui/proto@0.0.42      MIT
```

`@ag-ui/core` has declared MIT since at least 0.0.35. An earlier triage
note on #2860 attributed the failure to a missing SPDX field upstream;
that was wrong, and why their scanner reported `Unknown` for `@ag-ui/*`
is still unexplained. This PR fixes the part that is genuinely defective
on our side.

## Testing

Metadata-only; no source, build, or runtime change.

- Confirmed the five missing fields against the live registry with `npm
view <pkg> license` (output above), and confirmed the other published
`@copilotkit/*` packages (`runtime`, `react-core`, `react-ui`, `shared`,
`sdk-js`, `angular`, `channels`, `channels-core`) already report `MIT`.
- Enumerated every non-private `packages/*/package.json` on
`origin/main` to confirm these five are the complete set missing the
field.
- Each edited file re-parsed with `json.load` and reports `MIT`.
- The `sync-lockfile` pre-commit hook resolved all 71 workspace projects
against the edited manifests without error.

Placement matches `packages/shared/package.json`, where `"license"`
immediately precedes `"repository"`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-28 12:32:22 +02:00
Lukas Moschitz 669132d731 fix(web-inspector): stop lit's part marker from failing the usage-footer test
The assertion searched footer.outerHTML for "241" to prove the unclamped
thread count never reaches the user. outerHTML also carries lit's part
markers, which lit builds as `lit$` + nine digits from Math.random(),
regenerated per process. Roughly one process in a hundred rolls a marker
containing those digits and fails the assertion with no relation to what the
footer rendered — this run drew lit$924125892$.

Comments are stripped before the check. Visible text and attributes still
count, so a genuine leak in an aria-label is caught exactly as before, and the
stripping is asserted so it cannot silently stop working.

Not introduced here: the same dice roll could hit any change to this package.
The other outerHTML assertions in the suite compare two strings from the same
process and share a marker, so they were never exposed.
2026-08-28 11:44:42 +02:00
Lukas Moschitz 1efdf37a40 feat(web-inspector): report which story step a developer opens by hand (refs OSS-867)
The rail's four tabs reported nothing. Adds
oss.inspector.home_story_beat_selected, carrying the step as a property rather
than one event per label: the labels are expected to move as the story is
iterated, and per-label events would retire with them. beat_index rides along
so a reorder can be judged against where people actually click.

Only a press reports. The story also advances on its own every few seconds,
and reporting that would emit one event per idle developer per beat and bury
the handful of real interactions under a metronome — asserted, not assumed.
2026-08-28 10:36:59 +02:00
copilotkit-qa-bot[bot] ed315ac592 fix: preserve structured system messages when exposing state 2026-08-27 15:12:55 -07:00
copilotkit-qa-bot[bot] d385b9290e test: assert structured system message content safely 2026-08-27 15:05:45 -07:00
copilotkit-qa-bot[bot] f17d0509db fix: preserve system prompt text when exposing state 2026-08-27 15:05:09 -07:00
copilotkit-qa-bot[bot] 2f1c4c9332 fix: preserve sibling middleware state for exposure 2026-08-27 14:49:56 -07:00
MikeRyanDev 8617f5b76b chore: release monorepo v1.69.3 2026-08-27 16:47:48 +00:00
Lukas Moschitz 9e29de156d fix(angular): stop the core mock from hiding the Inspector's exports
The spec replaced @copilotkit/core wholesale with a two-export factory. That
held until the Inspector started mounting in these tests — it is enabled by
default in browser frameworks now, and its connectedCallback calls
isInspectorThreadBridgeEnabled, one of seventeen value exports it imports from
core. A missing one throws an uncaught exception, so the run fails while all
49 test files still report passing, which is a confusing way to find out.

The factory now spreads the real module and overrides only CopilotKitCore and
the connection-status enum, which is what these tests actually drive. Listing
the seventeen would have postponed the next occurrence rather than removed it.

Surfaced by the web-inspector work on this branch: angular only runs when
affected, and it becomes affected the moment web-inspector changes — so the
first PR to touch web-inspector after the default-on change was going to hit
this regardless of what it changed.
2026-08-27 16:58:39 +02:00
Lukas Moschitz d6b01a47c4 chore(web-inspector): regenerate the stylesheet against current main
The checked-in artifact was produced mid-rebase, so its Tailwind token block
reflects the utilities in use at that point rather than on today's main. A
fresh build:css on the rebased tree differs; this is that output, so the
committed file matches what the build produces.
2026-08-27 16:16:39 +02:00
Lukas Moschitz f011e236cf fix(web-inspector): name the second route instead of promising an explainer (refs OSS-867)
"What Intelligence does" promised an explanation and pointed at
intelligence.copilotkit.ai — the product and signup page. Mis-promising a
destination is a poor trade at the moment the card is asking to be trusted,
and the label was doing no work besides.

The two actions are two routes to one outcome: let the coding agent wire it
up, or go and do it in the browser. The secondary now says so — "Set it up
yourself" — which is honest about where it lands and quietly argues for the
primary, because "yourself" implies the other route is not.
2026-08-27 16:11:32 +02:00
Lukas Moschitz 0e0f49b21c feat(web-inspector): let the copied prompt expire so the button invites a second press (refs OSS-867)
The confirmation used to stand for the rest of the session. The reasoning was
that a developer leaves for their editor and comes back and should still find
the instruction — but whoever comes back has already pasted. The likelier
reason to return is to copy again, and a button wearing a checkmark and
"Prompt copied" reads as spent even though it still works.

Copied now reverts after 4s: button label and secondary line both return, so
the "What Intelligence does" link comes back too. Longer than the 2s the
Threads setup prompt uses, because this state carries an instruction to read
and not just an acknowledgement.

A failed copy does not expire. That state is the only place the prompt text is
selectable by hand, and pulling it away mid-selection would be worse than the
clipboard failing.

Three tests, mutation-checked: disabling the reset kills the two copied-state
tests. The failed-state test is blunter and only fails when both safeguards go
(the scheduling condition and the timer's own state re-check) — recorded in the
file so nobody reads more into it than it proves.
2026-08-27 16:11:32 +02:00
Lukas Moschitz 869c8714a7 fix(web-inspector): stop the copied-prompt hint from growing the header (refs OSS-867)
The instruction was a second row under the button, which pushed the action
column past the band's 76px min-height and shoved the whole story down at the
moment the developer had just acted — the worst possible time for the layout
to move.

It now takes over the secondary slot instead of adding to it. Before the press
the useful aside is "what is this"; after it, "where to put it". Both are
single lines in the same flex slot, so the swap cannot change the height:
measured 76px and an unmoved story in both states.

Dropping the line was the alternative, but it is the one instruction that
cannot go: a prompt on the clipboard with no idea what to do with it converts
nobody. It is also vendor-neutral now — naming three editors was brittle and
the prompt identifies the agent itself.
2026-08-27 16:11:31 +02:00
Lukas Moschitz 83a626f5cd fix(web-inspector): sharpen the Intelligence slide copy (refs OSS-867)
- "Your users had thousands" carried a count a developer wiring this up
  locally does not have yet. "Your users have all the others" holds on day one
  and at scale. Whose users: the app's end users, which is what the platform
  means too — `identifyUser` resolves one user per request from the app, and a
  thread carries `end_user_id`, a column renamed away from `user_id` because
  the old name "caused repeated misdiagnosis" against control-plane users.
- The Skills line defended instead of selling. "Nothing reaches your agent
  until you approve it" answers a fear the reader has not voiced and plants
  the worry it deflects. Same fact as ownership: a SKILL.md that is yours to
  review, edit and ship.
- The last tab is "Intelligence", not "Better agents". The other three are
  the parts; this one is the whole, so the rail reads Threads · Learning ·
  Skills · Intelligence. Beat id renamed to match, selectors included.
2026-08-27 16:11:31 +02:00
Lukas Moschitz e5d406dea4 fix(web-inspector): make the Intelligence pitch match what the product does (refs OSS-867)
Checked the four slides against the product's own copy instead of against
intuition, and two claims did not survive.

- "Skills apply it — without you writing another prompt" said the platform
  applies skills at run time. It does not: candidates land at pending_review,
  a human approves, and the published set is pulled down with
  `copilotkit skills download`. Nothing reads published skills during a run.
  The slide now says approve, pull in, and the next run starts from what
  worked — which is also the stronger pitch, because a developer does not
  want a platform silently changing how their agent behaves.
- Insights were missing entirely, and with them the evidence link that makes
  Learning credible: every Insight cites the Threads and messages behind it.
  Learning's own onboarding leads with "46 evidence refs" for that reason.

Also: "Rich Threads" is the product's name for the durable ones, and the
distinction is the whole sale next to a Threads tab full of local ones that
die on reload. A skill is a directory holding a SKILL.md, so the card shows
`meeting-scheduling/SKILL.md` and is marked Pending review.

- The rail's last step was "Reuse", which named neither a product surface nor
  an outcome. It is "Better agents" — the promise, in the customer's words.
- Secondary link before the primary button: with the filled button in the
  middle it read as a block wedged between the heading and the link instead
  of the one thing to press.
- The heading gets the brand mark and its band a wash along the brand's hue
  path, reusing the account strip's existing device. Not gradient text: at
  18px it renders muddy, costs contrast, and is the most over-used premium
  tell going.
2026-08-27 16:11:30 +02:00
Lukas Moschitz d50e3d7106 feat(web-inspector): turn the Home Intelligence card into an install path (refs OSS-867)
The Inspector said what Intelligence is and linked out to a signup page. Of
~1,655 opens in 90 days, under 100 clicked any CTA. This replaces the feature
list with an argument, and the outbound link with an install that happens in
the editor the developer is already in.

- Copy: the heading names the product; a four-slide argument runs underneath,
  each slide pairing two sentences with the picture beside it (threads →
  the pattern in them → the skill file → it applies itself). An earlier draft
  sold Threads in prose while animating Learning; bound together they read as
  one chain.
- Copy prompt: hands the CLI's own onboarding prompt to a coding agent instead
  of opening a signup form. Carries the CLI's onboarding_run_id, so
  oss.inspector.home_prompt_copied can finally be joined to
  cli.onboarding.completed — home_cta_clicked only ever proved a click.
  A refused clipboard reveals the prompt instead of swallowing it.
- Three modes, not two: a lapsed plan keeps the renew link and never sees an
  install prompt.
- Section anatomy mirrors System Health (header band, rule, content), so the
  action sits in the same top-right slot the status pill and renew link use.
- The story only advances while Home is visible and the document is not
  hidden; a debugging tool should not hold a repeating timer behind a closed
  panel.

No third-party coding-agent logos on the button, unlike the Intelligence app:
this package ships inside other people's sites, and vendoring those marks is
not a call to make quietly. The helper line names the agents in text.
2026-08-27 16:11:28 +02:00
Alem Tuzlak ed28f3a908 fix(angular): export inspector development-mode token from public API 2026-08-27 13:01:01 +02:00
Alem Tuzlak 2dec983ed8 fix(angular): restore web-inspector workspace dependency 2026-08-27 12:15:03 +02:00
Alem Tuzlak 42d3c92fbd chore: merge origin/main into tyler/default-browser-inspector 2026-08-27 12:09:27 +02:00
Alem Tuzlak 0e700fa3b9 docs(react-core): correct Inspector debug-mode skill examples 2026-08-27 12:00:55 +02:00
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
Lukas Moschitz c69ef433b1 fix(core): let recovery prune an agent nothing is standing on
A recovery re-sync merged: it added and updated what the runtime reported
and never removed anything. That was safe but too blunt — an agent the
developer genuinely deleted stayed visible until a page reload, so the
application kept offering something that no longer existed.

The harm was never removal; it was removing the agent a live conversation
hangs on. Recovery now reconciles instead of merging, dropping an agent
only when both conditions hold: the runtime reported at least one agent
(an empty list is a runtime that has not finished registering, and
nothing it says is worth acting on), and the agent carries no
conversation state — no messages, and no thread bound to it by a
binding. Both are decidable inside the core, the second off the agent
instance, so nothing here depends on the UI layer.

Note that `threadId` is always populated: `AbstractAgent`'s constructor
generates one when none is supplied, and the run pipeline never writes
it. So presence says nothing, and the registry records the value it
minted each remote agent with in order to recognise the value having
moved — which is what a binding resolving a thread actually looks like.

Non-recovery re-syncs are unchanged: a deliberate runtime url, transport,
header or credentials change and the initial handshake all still replace
the set outright, conversation state included.

This is only safe because `subscribeToAgent` now leaves a live
subscription alone when handed the same instance. Pruning changes the
set, so recovery announces a change, so the state manager re-subscribes
per agent — and recovery fires while the run whose response proved the
runtime was back is typically still streaming. Covered by a test that
prunes across an in-flight run; it fails if that guard is removed.
2026-08-26 13:25:13 +02:00
Lukas Moschitz c1e6e72812 refactor(core): name the watchdog's flag after what it records
It records whether the silence report caused a confirmation check, not whether
the timer fired, and the two are now deliberately different. One honest name
beats a doc comment explaining that the name is not quite right.
2026-08-26 13:25:13 +02:00
Lukas Moschitz 4f432848f5 docs(oss-904): correct the claims about thread requests and the error state
Five comments justified routing thread requests through the instrumented fetch
by saying it lets opening a view restore the status after an outage. It does
not: every binding withholds its thread requests until the status is already
connected, so while it is red nothing is sent. The justification is DETECTION
only, which is what the CopilotChat site already said correctly.

Also:

- Documents both meanings of the Error state and the invariant behind them —
  the status reports the last actual contact with the runtime — on the
  connection-status reference page, which described only the startup meaning.
- Renames RUNTIME_PROBE_TIMEOUT_MS to ɵRUNTIME_PROBE_TIMEOUT_MS. core/index.ts
  re-exports agent-registry wholesale, so a constant whose own doc says
  "exported for tests" was public API of @copilotkit/core.
- Guards the Inspector's read of ɵruntimeFetch the way it guards its four other
  internal core accessors. A newer Inspector against an older pinned core was
  handing the thread store `undefined`, which breaks the Threads view outright
  rather than merely losing detection through it.
- Corrects the stop-request comment, which claimed to be the only runtime
  destination off the seam; the suggestion route's stateless path, the memory
  store and /inspector-metadata are too, just not by design.
- Corrects OSS-904-VERIFY.md, which said scenario 4 had real traffic to work
  with and left it off the not-covered list.
2026-08-26 13:25:13 +02:00
Lukas Moschitz dd1639726d test(core): close six mutations the suite let through
Each of these passed the whole core suite with the production code
deliberately broken; each new test was verified by re-applying the mutation.

- The watchdog becoming a repeating interval: nothing kept the clock running
  past the first fire, so a fresh probe every ten seconds for as long as one
  request hangs went unnoticed. Pinned by count and by getTimerCount.
- Deleting the "only probe while Connected" guard: a developer pressing Send
  three times at a red indicator bought three extra probes and three duplicate
  wiring errors.
- Rerouting the stop request onto the seam, which would make pressing Stop
  against a dead runtime turn the status red.
- Putting the agent-level auto-detect /info back on the global fetch. Both new
  suites pinned "rest", so the product default was never exercised.
- "A success on a non-critical request still counts", documented and asserted
  nowhere.
- isAbortError's string branch was unreachable from either call site, so it is
  removed rather than tested; suggestion-engine's own isAbortError already
  answers false for a string.

Also records where the absence tests' ten-minute virtual window stops proving
anything, and why the getTimerCount assertion is the period-independent one.
2026-08-26 13:25:13 +02:00
Lukas Moschitz 7c86d57fad fix(core): announce recovery's agent set, and protect knowledge not a status
Two fixes that the same test sequence exercises.

Recovery skipped onAgentsChanged for an unchanged agent set, to avoid core
re-subscribing the state manager and revoking the in-flight run's
subscription. That was the wrong lever: when an agent HAD been added — the
usual reason for restarting a runtime — the notification fired anyway and the
recovering run's state was lost, while an unchanged-set check that stopped
working would silently swallow a genuinely new agent. The subscription hazard
is now fixed at its source in state-manager, so recovery announces its set
like every other connection attempt.

hasLiveRuntimeKnowledgeToProtect required the status to be exactly Error, but
recovery deliberately passes through Connecting, so a setRuntimeUrl or
setRuntimeTransport landing in that window wiped the agents, closed the
submission gate and stranded the application red. The condition is now stated
in terms of the thing being protected: remote agents exist, and contact is not
currently established.

Also rewrites the two diagnosis assertions, which were pinned on a status code
interpolated into both branches and a phrase matching neither, so both halves
passed against the wrong branch.
2026-08-26 13:25:13 +02:00