Skip to content

[pull] main from CopilotKit:main#50

Merged
pull[bot] merged 29 commits into
HinchK:mainfrom
CopilotKit:main
Jul 16, 2026
Merged

[pull] main from CopilotKit:main#50
pull[bot] merged 29 commits into
HinchK:mainfrom
CopilotKit:main

Conversation

@pull

@pull pull Bot commented Jul 16, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

BenTaylorDev and others added 29 commits July 15, 2026 11:41
…ity, history/files, delete (OSS-476)

The realtime-gateway transport had drifted from the HTTP transport: it built a
text-only ingress envelope that coerced commands/reactions/interactions into
empty turns, keyed conversations per-turn (breaking threaded follow-ups),
dropped the provider actor identity, and implemented none of
fetchFile/getHistory/uploadFile (so the realtime path silently ran with no
history and no file support). It also had no `delete` render kind. This brings
the realtime path to full parity with direct.

- claim-mapping: extract the claim→ingress mapping (ClaimedDelivery,
  conversationKeyFromReplyTarget, mapDeliveryToEnvelope) into a shared module
  both transports use, so they cannot drift again. Add the provider `actor` to
  the claim turn and map it to `env.user` (fixes identity on BOTH paths).
- realtime transport: build the envelope via the shared mapper — real kind
  discrimination, thread-stable conversationKey, actor→user — instead of the
  text-only inline build. Fail-closed: an unmodeled reply-target/kind is
  dropped+logged, not crashed.
- file/history: extract fetchFile/getHistory/uploadFile into a shared
  IntelligenceFileHistoryClient (HTTP-only — the gateway never relays bytes or
  history). The realtime transport gains them when configured with
  `appApiBaseUrl` + `apiKey` (threaded through the launcher); absent that, the
  methods stay undefined and the adapter degrades exactly as before.
- delete render kind: add `{ kind: "delete"; ref }` to ChannelRenderEvent
  (mirrors the frozen Intelligence contract) and route thread.delete through a
  render frame when a render sink is wired (OSS-420), like post/update/file.

Tests: shared-mapper unit tests (actor→user, kind discrimination,
conversationKey, unmodeled-adapter throw); realtime tests for non-text kind +
identity + thread-stable key and file/history capability toggling; a
delete-render-frame test. Full suite + check-types + build green.
…ort (OSS-476)

Pre-merge 7-agent CR of #5983 surfaced several realtime-transport defects (all
in channels-intelligence). Fixes:

- Poison-payload re-lease loop: an unmappable delivery (unmodeled reply-target
  adapter / unknown input.kind) with a valid lease was logged + dropped, so
  app-api re-leased the identical payload forever. It now fails NON-retryable
  (dead-letter), mirroring the HTTP path. nack() gains a `retryable` param.
- Double-terminal-signal race: realtime ack()/nack() deleted delivery state
  AFTER the wire push, so a per-turn-timeout nack could race a late dispatch ack
  and emit BOTH fail + complete_requested. Now delete-before-push (XOR).
- Concurrent dispatch: deliveries were handled fire-and-forget; now processed
  serially (parity with the HTTP runLoop) so an in-flight redelivery can't reset
  the shared per-turn seq counter or run two turns of one conversation at once.
- actor.displayName was carried through the claim mapper then dropped in
  dispatchTo (`{ id }` only) — now forwarded to handlers (OSS-476 identity).
- fetchFile enforced MAX_INBOUND_FILE_BYTES only against declared content-length;
  the actual read was unbounded. Now streams and aborts past the cap.
- getHistory returned the FULL history for limit<=0 (slice(-0)) and hydrated file
  bytes for over-returned messages it then discarded. Now caps to the most recent
  `limit` BEFORE hydrating; limit<=0 -> [].
- stream() posted an empty text frame for an empty stream; now skips the post.
- HTTP withTimeout/defaultSleep timers now unref() (parity with realtime).
- Corrected the inaccurate thread_started comment in claim-mapping.ts.

Deferred to OSS-491 (delivery-terminal-signal contract, needs app-api
coordination): an empty turn (reaction/command that posts nothing) has no valid
completion signal (acceptedThrough requires >=1) and redelivers; and the
run_error swallow on the HTTP-fallback render path.

Tests: poison non-retryable, single-terminal XOR, serial dispatch, displayName
forwarding, getHistory limit<=0 + cap. channels-intelligence 169 tests + check-types green.
…giene (OSS-476)

From the 7-agent CR confirmation round:

- RealtimeGatewayTransport.stop() now halts intake (a `stopped` guard in
  handleDeliveryAvailable — the session exposes no `off` to detach the
  DELIVERY_AVAILABLE listener) and DRAINS the in-flight delivery (awaits the
  serial `processing` chain) before clearing state, so a turn settling at stop
  time still sends its terminal signal instead of silently no-oping and
  redelivering. Mirrors HttpDeliverySource.stop().
- Realtime nack() truncates the reason to 500 chars (parity with HTTP).
- getMessages drops empty content parts before join(" ") so a read_thread
  transcript isn't corrupted with doubled/leading/trailing spaces (a test had
  enshrined "part one  part two").
- Removed dead imports (buildContentParts, AgentContentPart, ChannelFileRef)
  left in http-transports.ts after the file/history extraction.

Deferred (delivery-contract / design; need coordination — folded into OSS-491):
timeout-nack can redeliver a still-running turnId (overlap); realtime push()
no-state fallback vs HTTP fail-loud (documented intentional — parity Q); the
empty-turn completion signal.

Tests: stop() drains in-flight + ignores post-stop deliveries; getMessages
assertion corrected. channels-intelligence 170 tests + check-types green.
…OSS-476)

Trivial items from the CR confirmation rounds (no behavior change to production
paths):

- intelligence-adapter.test.ts: `delete source.getHistory` was inert (getHistory
  is a prototype method; delete only removes own props), so the "no getHistory"
  branch was never actually exercised. Shadow with an own `undefined` instead so
  the `source?.getHistory?.()` short-circuit is genuinely tested.
- in-memory-transports.ts: mirror the production `limit <= 0 -> []` guard in
  InMemoryDeliverySource.getHistory (was `slice(-limit)` → returns ALL for 0).
- realtime ack(): log the empty-turn (no accepted frames) drop so the OSS-491
  redelivery pile-up is diagnosable (every other drop path here logs).
- http-transports.ts: remove two orphaned JSDoc comments dangling over
  ClaimResponse (leftovers from the file/history extraction).
- intelligence-adapter.ts: correct an inaccurate op-id comment (mintOp is the
  ${turnId}:${seq} source; the render path keys on ${turnId}:${slot}:${seq}).

channels-intelligence 170 tests + check-types green.
…tivation + map user name (OSS-476)

- [P1] History/files were absent on the NORMAL managed path: defaultActivateChannel
  never forwarded the app-api HTTP URL, so the transport (which installs
  fetchFile/getHistory/uploadFile only when appApiBaseUrl is set) ran without
  them for Channels started by the CopilotRuntime handler — only manual
  low-level launcher callers got file/history. Thread intelligence.ɵgetApiUrl()
  through ChannelActivationConfig.apiUrl → defaultActivateChannel →
  startChannelsOverRealtimeGateway({ appApiBaseUrl }). The launcher + transport
  already accepted it.
- [P2] Managed turns exposed the provider profile under a non-public `displayName`
  field, leaving PlatformUser.name undefined. Map env.user.displayName -> name
  (parity with the direct Slack adapter, which populates `name`).

Tests: deriver returns apiUrl; defaultActivateChannel forwards appApiBaseUrl to
the launcher opts; onMessage sees message.user.name. channel-activation-config +
channels-intelligence (170) green; runtime build type-checks. (channel-manager.test
executes in CI — local vitest hits the known optional-peer-dep resolution flake.)
…r RED+GREEN harness)

Faithful node:22-slim repro: fd1 through the same awk process-substitution as entrypoint.sh, a Railway-capped drain reader, a uvicorn+CVDIAG-shaped flood, and the static no-log /api/health as victim. RED wedges (200->502, CPU->0, heartbeat frozen); the FIXED lane stays 200 throughout. run.sh asserts the outcome (exit 3/4/5 on a false result, proven). watchdog.sh runs the entrypoint public-guard loop verbatim and needle-anchors it against entrypoint.sh.
…gate (default ON)

Shared cvdiag_bootstrap: gate the per-LLM-call breadcrumb capture handler and the emit_cvdiag stdout write behind CVDIAG_LOG_STDOUT (default ON so every other integration is byte-for-byte unchanged; opt-out per service). Enqueue to the non-blocking PocketBase sink BEFORE the stdout write so a wedged fd1 cannot cost the durable breadcrumb. No sampling; full fidelity to PB. Red-green unit tests incl. a hostile-stdout durability test.
… alerting watchdog (claude-sdk-python)

Self-activate CVDIAG_LOG_STDOUT=0 when CVDIAG_PB_URL is wired (safe: only silences stdout when PB is receiving; explicit override preserved). Extend the watchdog to poll the public $PORT /api/health and, on sustained failure, POST a loud #oss-alerts Slack alert BEFORE kill-restart; add the same alert to the agent-\:8000 branch (no silent recovery). Add uvicorn --no-access-log to cut the access-log flood. Together these keep the shared log stream under the 500/sec cap so the pipe never backs up.
…ity, history/files, delete (#5983)

## What & why

The Realtime Gateway transport had drifted from the HTTP polling
transport, so managed channels running over the gateway behaved
differently from direct. This brings the realtime path to full parity.

Before, the realtime transport:
- built a **text-only** ingress envelope —
commands/reactions/interactions were coerced into empty turns;
- keyed conversations **per-turn** (`conversationKey = turn.id`), so
threaded follow-ups didn't share agent/session state;
- **dropped the provider actor** — `env.user` was never populated (on
*both* transports, in fact);
- implemented **none** of `fetchFile`/`getHistory`/`uploadFile`, so the
realtime path silently ran with no history and no file support;
- had **no `delete` render kind**, so `thread.delete` couldn't render
over the gateway.

## Changes

- **`claim-mapping.ts` (new, shared):** extract the claim→ingress
mapping — `ClaimedDelivery`, `conversationKeyFromReplyTarget`,
`mapDeliveryToEnvelope` — into one module both transports import, so
they can't drift again (drift *was* the bug). Adds the provider `actor`
to the claim turn and maps it to `env.user` — fixing identity on
**both** paths.
- **realtime transport:** builds the envelope via the shared mapper
(real kind discrimination, thread-stable `conversationKey`, actor→user)
instead of the inline text-only build. Fail-closed: an unmodeled
reply-target/kind is dropped+logged, not crashed.
- **`intelligence-file-history.ts` (new, shared):** extract
`fetchFile`/`getHistory`/`uploadFile` into
`IntelligenceFileHistoryClient`. These are **HTTP-only** — the gateway
relays the render-event stream but never file bytes or history. The
realtime transport gains them when configured with `appApiBaseUrl` +
`apiKey` (threaded through the launcher); without those, the methods
stay `undefined` and the adapter degrades exactly as before.
- **`delete` render kind:** add `{ kind: "delete"; ref }` to
`ChannelRenderEvent` (mirrors the frozen Intelligence contract) and
route `thread.delete` through a render frame when a render sink is
wired, like post/update/file.

## Tests

- `claim-mapping.test.ts`: actor→user, kind discrimination
(command/reaction/interaction/text), thread-stable conversationKey
(slack/teams), unmodeled-adapter throw.
- `render-events.test.ts`: realtime non-text kind + identity +
thread-stable key; file/history capability toggling on config; a
delete-render-frame test.
- Full suite (160) + `check-types` + `build` green.

## Companion

This is the CopilotKit SDK half of the managed-transport parity work;
the Intelligence-side half (gateway `render_event/1` validator for
file/delete, DB CHECK, Connector Outbox `chat.delete`, actor on the
claim) landed separately. Together they close the direct-vs-managed
parity matrix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary

- Extract the platform-neutral Channels foundation into
`@copilotkit/channels-core`.
- Make `@copilotkit/channels` the batteries-included consumer entry
point, with adapter and UI subpaths.
- Release the umbrella, core, UI, and all six adapters as one shared
`channels` version scope.
- Verify the packed consumer contract and migrate the Slack and Teams
examples to the umbrella.

## Why

Consumers should be able to install one Channels package without making
the runtime or selective integrations depend on every platform SDK.
Shipping the complete Channels family together prevents adapter/core
version drift and makes the umbrella's exact dependency set release as a
compatible unit.

## How

- Move shared bot/runtime primitives into `channels-core` and invert
adapter/runtime dependencies.
- Add exact workspace dependencies and export subpaths from the umbrella
package.
- Consolidate the existing release configuration and all three release
workflow selectors into one shared `channels` scope containing all nine
packages.
- Use `@copilotkit/channels` as the version source: the next minor
release resolves to `0.2.0` and bumps every Channels package together.
- Publish scoped packages in dependency order for both stable and canary
releases: UI, core, adapters, then the umbrella.
- For a stable Channels release, publish the UI/core/adapters first, run
the registry-backed packed-consumer verifier against those newly
published exact versions, then publish the umbrella.
- Generate Channels release notes from `channels/v*` tags rather than
the monorepo `v*` tags.
- Verify builds, type checks, tests, package artifacts, examples, and
release workflow scope synchronization locally.

### First stable release sequence

1. Bootstrap the currently unpublished `@copilotkit/channels-core`
package on npm and configure npm trusted publishing for every Channels
package against this repository's `release / publish` workflow and `npm`
environment.
2. Create and merge the `channels` minor release PR. The stable workflow
publishes the Channels family in the staged order above and validates
the packed umbrella from the registry before the umbrella is released.
3. Create and merge a subsequent `monorepo` release PR so the published
`@copilotkit/runtime` switches from the historical umbrella dependency
to `@copilotkit/channels-core`.
## Release channels v0.2.0

**Scope:** `channels` | **Bump:** `minor`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `channels` packages to `0.2.0`
   and generated AI-enhanced release notes.

2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
   must pass before merging. This is the review gate.

3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.

4. **When this PR is merged**, the `release / publish` workflow
automatically:
   - Builds all packages
   - Publishes the `channels` packages to npm at version `0.2.0`
   - Creates git tag `channels/v0.2.0`
   - Creates a GitHub Release with the final release notes

### Before merging

- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)

---

> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
## Problem

The Channels v0.2.0 release publishes its dependency packages, then
fails before publishing the umbrella package because pnpm rejects the
freshly published dependencies under the 24-hour minimum release age
policy.

## Why

The registry-backed verifier installs the packed umbrella in an isolated
temporary consumer without carrying a Channels-family release-age
exemption into that consumer.

## Fix

Generate a pnpm workspace config for the isolated consumer that exempts
every Channels package while retaining the maturity gate for unrelated
dependencies. Add regression coverage for the complete family. Verified
with all 109 release-script tests, the registry-backed Channels
verifier, explicit lint/typecheck/format checks, and the full Nx package
build.
## Release monorepo v1.63.0

**Scope:** `monorepo` | **Bump:** `minor`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `monorepo` packages to `1.63.0`
   and generated AI-enhanced release notes.

2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
   must pass before merging. This is the review gate.

3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.

4. **When this PR is merged**, the `release / publish` workflow
automatically:
   - Builds all packages
   - Publishes the `monorepo` packages to npm at version `1.63.0`
   - Creates git tag `monorepo/v1.63.0`
   - Creates a GitHub Release with the final release notes

### Before merging

- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)

---

> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
…oop wedge (#5987)

## What happened

The `claude-sdk-python` showcase column went fully red on **staging**
(37 cells, `BE ✗` cascading) while prod and the TypeScript sibling
stayed green on the same image. It wasn't 37 bugs — it was one wedged
replica.

**Root cause:** a D6 fan-out pushed the container's combined log volume
past Railway's ~500 logs/sec drain cap. `entrypoint.sh` pipes both
processes' stdout through an `awk` process-substitution, so Next.js's
`fd1` is a **synchronous pipe**; when Railway stopped draining, the pipe
filled and Next.js's next `console.log` blocked in `write(2)`,
**freezing the event loop**. Even the static `/api/health` went 502,
CPU→0, memory flat, process alive — so `restartPolicyType: ON_FAILURE`
never fired and `numReplicas: 1` meant one wedge reds the whole column.
Load-triggered, not a code/env diff.

Full analysis (root cause + mitigation trade-offs): Notion → *Showcase
stdout-backpressure wedge* under Plans / Proposals.

## The fix (two coordinated MUSTs)

Showcase's promise is preserved throughout — real D6 traffic, live page,
and full CVDIAG diagnostics all intact. Nothing is sampled or dropped.

- **MUST-1 — take the flood off stdout, losslessly.** Route CVDIAG's
per-LLM-call breadcrumb off stdout behind a new `CVDIAG_LOG_STDOUT` gate
(**default ON**, so every other integration is byte-for-byte unchanged);
the non-blocking PocketBase sink still receives every envelope at full
fidelity — which is the path `cvdiag classify` and the dashboard already
read from. `emit_cvdiag` now enqueues to PB **before** the stdout write
so a wedged fd1 can't cost the durable breadcrumb. `entrypoint.sh`
self-activates `CVDIAG_LOG_STDOUT=0` only when `CVDIAG_PB_URL` is wired
(safe: never silences stdout when PB isn't receiving). Plus uvicorn
`--no-access-log` to drop the access-log noise.
- **MUST-2 — detect and recover the hang, loudly.** Extend the watchdog
to poll the public `$PORT /api/health` and, on sustained failure, POST a
`#oss-alerts` Slack alert **before** kill-restarting — and add the same
alert to the agent-`:8000` branch. No silent recovery: every wedge
pages.

## Red → green proof

A docker `node:22-slim` repro (`showcase/tests/repro/stdout-wedge/`)
reproduces the real topology (same `awk` pipe, Railway-capped drain
reader, uvicorn+CVDIAG-shaped flood, static no-log `/api/health`
victim):

- **RED** (unmitigated): `/api/health` 200 → 502/timeout the instant the
flood crosses the cap; heartbeat frozen; CPU parked at 0. `200=6 /
WEDGE=11`.
- **GREEN** (flood cut below cap): health stays fast-200 across the
whole window; heartbeat advances. `200=16 / WEDGE=0`.
- `run.sh` asserts the outcome and **exits non-zero on a false result**
(a deliberately-forced false-GREEN exits 5, was exit 0 pre-fix).
- MUST-2: the actual `entrypoint.sh` watchdog loop, run verbatim against
a genuinely wedged port, detects → POSTs the captured alert → `kill -9`s
the real Next.js PID. `watchdog.sh` needle-anchors the public
probe/kill/alert against `entrypoint.sh` (mutating the probe fails the
test).
- Unit: `test_cvdiag_log_stdout_gate.py` — 5/5 incl. a hostile-stdout
durability test (RED: enqueue starved; GREEN: enqueue preserved). Full
`_shared` suite 19 passed / 2 skipped.

## Review

Tier-3 cr-loop (shared source + deploy config + kill path): 7-agent
review → adversarial per-finding verification → 5 mandatory fixes (all
red-green'd, isolated worktrees) → 7-agent confirmation round converged
to **zero mandatory findings** → bucket-(c) promotion audit
`PROMOTE_TO_A: 0`.

## ⚠️ Not merge-ready yet — draft on purpose

- [ ] **Staging branch-deploy validation** — local can't exercise the
one runtime unknown: that the container actually reaches
`showcase-pocketbase.railway.internal:8090` and lands a `cvdiag_events`
row. Must confirm on a staging deploy of this branch before merge.
- [ ] **Railway env wiring (out-of-band, human-gated):** set on
`showcase-claude-sdk-python` (staging + prod) —
`CVDIAG_BACKEND_EMITTER=1`,
`CVDIAG_PB_URL=http://showcase-pocketbase.railway.internal:8090`,
`CVDIAG_WRITER_KEY` (op:// in DevOps `showcase`),
`SLACK_WEBHOOK_OSS_ALERTS`. Without these MUST-1/MUST-2 stay inert
(safe: default is current behavior).
- [ ] Green CI.

## Follow-up (non-blocking, fail-safe)

Repro-harness polish (none false-GREEN-capable): GREEN heartbeat
assertion is timing-fragile under a raised start-delay (false-RED only);
`run.sh` local lane doesn't forward all tunables; `watchdog.sh`
webhook-assert race + helper-death flake; reader has no close handler on
the local lane. Pre-existing (bucket c): `OPENAI_API_KEY` warning is
mislabeled; Next.js has no readiness gate; `_SETUP_DONE` degrade-latch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@pull pull Bot locked and limited conversation to collaborators Jul 16, 2026
@pull pull Bot added the ⤵️ pull label Jul 16, 2026
@pull pull Bot merged commit 7abcd21 into HinchK:main Jul 16, 2026
1 of 3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants