InMemoryAgentRunner gains an opt-in { onConcurrentRun: "throw" | "supersede" }
(default "throw", so existing consumers are unchanged). In "supersede" mode a
new run for an already-running thread aborts the prior run (agent.abortRun(),
mirroring stop()) instead of throwing "Thread already running" — so a fast
follow-up turn, or one after a dropped/aborted run, cleanly replaces the
previous one.
Superseding overlaps two runs on the module-global per-thread store, so all
four finalization sites (both resets and both historicRuns.push) are guarded on
store.currentRunId === request.input.runId and stamp the run's own id. A
superseded run therefore drops its partial events rather than resetting or
mislabeling the new run's state/history.
Used by the Intelligence-hosted (managed) Slack listener. (OSS-417)
## What does this PR do?
This PR fixes build failures on Windows by replacing Unix-only shell
commands (`rm -rf`, `cp`, `mkdir -p`) in `package.json` scripts with
cross-platform Node.js `fs` built-in commands.
This follows the project's existing codebase pattern for cross-platform
operations, as seen in `packages/react-ui/package.json` (line 45).
### 🛠️ Changes:
- **`packages/runtime`**: Replaced `rm -rf` in `generate-graphql-schema`
with `fs.rmSync`.
- **`packages/vue`**: Replaced `cp` in `build:types` and `rm -rf` in
`clean` with `fs.cpSync` and `fs.rmSync`.
- **`packages/angular`**: Replaced `mkdir -p` and `cp` in `build:css`
with `fs.mkdirSync` and `fs.cpSync`.
- **`examples/v1/next-openai`, `next-pages-router`, `state-machine`**:
Replaced `rm -rf` clean commands with a single Node.js loop that deletes
`.turbo`, `node_modules`, `dist`, and `.next`.
All modified packages now build successfully on Windows.
## Related PRs and Issues
- Closes#5601
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [ ] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
CopilotIntelligenceRuntimeLike.bots became required with the managed-bots
runtime work (OSS-360); the get-runtime-info test factory still omitted it,
failing check-types. Add the missing field.
Empty/whitespace-only allow/deny/denyPrefixes entries are trimmed and
dropped before the allowlist-mode decision, so allow: [""] stays in
denylist mode rather than switching on exclusive allowlist mode. Also
document that allowlist mode bypasses the built-in default denylist, so
integrators must not allow-list protected/platform headers unintentionally.
`shouldForwardHeader` returned early on `policy.allow` and silently ignored
the integrator's `deny`/`denyPrefixes`, so a header listed in BOTH `allow`
and `deny` still forwarded — a footgun on a security feature.
Rework the predicate so the integrator's own `deny`/`denyPrefixes` (exact,
case-insensitive, and prefix) always strip, including in allowlist mode:
`allow` selects the candidate set, `deny` subtracts from it. The built-in
default denylist is unchanged and still applies only in denylist mode (an
explicit `allow` is a deliberate opt-in), so only the integrator's OWN deny
subtracts from an allowlist.
Also harden `resolveForwardHeadersPolicy`: trim and drop empty/whitespace-only
entries from `deny`/`denyPrefixes`/`allow`. A stray `denyPrefixes:[""]` made
`startsWith("")` true for every header (silently denying ALL forwarding), and
`allow:[""]`/`allow:[" "]` seeded the exclusive allowlist with an entry that
could never match — both integrator typos that now can't silently break
forwarding. Entries are lowercased consistently with existing handling.
- agent-utils: assert configureAgentForRequest does not mutate the shared
registered agent; only the per-request clone carries merged inbound headers
(guards against a cross-request bearer-token leak, #5712).
- header-utils: direct mergeForwardableHeaders unit test for server Authorization
vs inbound lowercase authorization with a different value — exactly one
authorization-family key survives carrying the server value (#5712).
- header-utils: shouldForwardHeader boundary tests for the bare 'x' name and the
empty-string name under both denylist and allowlist policies.
The published CopilotRuntimeLike interface (v2 export surface) added
forwardHeadersPolicy as a REQUIRED field, which breaks any external
implementor of the interface — inconsistent with the minor-release
classification. The /run (agent-utils) and /connect (sse/connect) read
sites dereferenced runtime.forwardHeadersPolicy with no coalesce, so a
policy-less object crashed with "Cannot read properties of undefined
(reading 'allow')".
Make the field optional on the interface and coalesce both read sites to
the default resolved policy (resolveForwardHeadersPolicy(undefined),
default-on denylist) when absent. Concrete runtimes (BaseCopilotRuntime)
still always resolve and set it, so behavior is identical for all real
runtimes; the interface is now non-breaking and crash-proof.
Adds a red-green test driving configureAgentForRequest with a runtime
whose forwardHeadersPolicy is undefined: asserts no throw and that the
default denylist applies (x-forwarded-for dropped, custom x-* and
authorization forwarded).
- header-utils.test.ts: new coverage for the default denylist (exact names +
prefix families, case-insensitive), custom x-* still forwarding, config
overrides (useDefaultDenylist:false, deny, denyPrefixes, allow allowlist mode),
and the breadth/precedence interaction. Migrate the inverting assertions
(x-request-id / X-Forwarded-For now stripped; extract result drops x-request-id)
and add the new required policy arg to all call sites.
- agent-utils-header-forwarding.test.ts + sse-connect-agent-id.test.ts: /run and
/connect integration coverage — denylisted infra/platform headers dropped,
custom x-* + authorization still forward, and a runtime-supplied forwardHeaders
policy is actually applied (plumb-through). Swap denylisted filler headers for
non-denylisted custom headers in the precedence regression tests.
- handle-run / handle-connect / intelligence-run-telemetry / get-runtime-info:
add the resolved forwardHeadersPolicy to mock runtimes that route through the
header merge so they satisfy the now-required policy.
Tighten which inbound HTTP headers the v2 runtime forwards onto the outgoing
agent call. The old `authorization` + `x-*` allowlist leaked infrastructure,
proxy, and platform headers (x-forwarded-*, x-real-ip, x-amzn-trace-id,
x-vercel-*, and the Copilot Cloud platform key x-copilotcloud-public-api-key)
to arbitrary configured agent URLs (#5712, breadth half).
- header-utils.ts: add DEFAULT_DENY_HEADER_NAMES + DEFAULT_DENY_HEADER_PREFIXES
constants and a policy-aware shouldForwardHeader; thread ResolvedForwardHeadersPolicy
through extractForwardableHeaders and mergeForwardableHeaders. Add the public
ForwardHeadersConfig and resolveForwardHeadersPolicy (useDefaultDenylist defaults
to true; deny/denyPrefixes extend the default; allow switches to allowlist mode).
Server-wins precedence and server-self case-dedup are unchanged.
- runtime.ts: add forwardHeaders?: ForwardHeadersConfig to BaseCopilotRuntimeOptions,
resolve it once in the constructor into forwardHeadersPolicy (mirroring the
debug -> ResolvedDebugConfig resolve-once), expose it on CopilotRuntimeLike /
BaseCopilotRuntime, and add a passthrough getter on the CopilotRuntime shim.
- Apply the resolved policy at both call sites: /run (configureAgentForRequest)
and /connect (handleSseConnect), so the two paths can never diverge.
Default-on in a minor with { useDefaultDenylist: false } as the documented opt-out.
When an agent is configured with both case-variants of the same header
in agent.headers (e.g. Authorization and authorization), the prior
{ ...base } spread kept both keys — the exact undici comma-join hazard
the function guards against for inbound collisions. Collapse server-self
case-collisions to a single first-occurrence-wins entry; server-wins-over
-inbound and case-insensitive inbound suppression are unchanged.
Cover the #5712 header-precedence behavior across both paths:
- agent-header-precedence.test.ts: server-configured agent.headers win
over forwarded inbound headers on collision (case-insensitive), with
single-key uniqueness assertions for both authorization and the x-*
family (exactly one surviving key carrying the SERVER value).
- agent-utils-header-forwarding.test.ts: the /run path merges via
mergeForwardableHeaders so server values are authoritative and inbound
headers fill only unset keys.
- sse/__tests__/sse-connect-agent-id.test.ts: the /connect path applies
the same merge, plus the agent-undefined case (no server agent.headers)
degrades to forwarding allowlisted inbound headers only and does not
crash.
The /connect path now builds the same server-wins merged headers as the
/run path and passes them into runner.connect(). This does NOT fix
connect-path auth: no shipped runner consumes the headers field of
AgentRunnerConnectRequest today. The in-memory, intelligence, telemetry,
and sqlite runners all read only threadId from the connect request and
ignore headers entirely. The real outbound header forwarding lives on the
/run path, where agent.headers is mutated before the agent runs.
Passing merged headers here is the correct argument shape for a future
outbound-connecting runner, and keeps the connect path's merge semantics
consistent with /run. The comments and JSDoc are rewritten to state this
plainly rather than implying an active auth fix: the connect-site
cloneAgentForRequest call is documented as the sole agentId-existence
guard (the intelligence branch never re-validates the id), and
cloneAgentForRequest's AbstractAgent | Response (404) dual-return contract
that both callers depend on is now documented.
When a request hits the /run path, inbound headers are forwarded to the
agent. Previously, forwarded inbound headers could clobber the
server-configured agent.headers on a key collision, letting a client
override server-set values (e.g. authorization). This is the #5712 bug.
Introduce mergeForwardableHeaders (header-utils.ts): a case-insensitive
merge where server-configured agent.headers always win on collision,
regardless of header-name casing. agent-utils.ts now uses this helper on
the /run path so server-configured values are authoritative and inbound
headers only fill keys the server did not set.
Fixes#5712
Expose new CopilotRuntime({ intelligence, bots }) -- the Mode B entry point
for managed bots:
- bots is accepted only on the Intelligence runtime variant (bots?: undefined
on the SSE variant), so TypeScript rejects bots without intelligence
- CopilotIntelligenceRuntime stores the declared bots; the facade exposes them
via the existing isIntelligenceRuntime getter pattern
- @copilotkit/bot is imported type-only (it is pure-ESM; a value import would
break this package's CJS output). Name validation + transport wiring happen
in startManagedBots (called by the managed-listener bootstrap), not here.
Adds a type-only @copilotkit/bot workspace dependency.
ɵsubscribeToMemories mirrored ɵsubscribeToThreads and sent userId in the
body, but the platform's memory routes resolve the app user from the
x-cpki-user-id header (like listMemories/createMemory), not the body — so
POST /api/memories/subscribe returned 401 AUTH_UNAUTHENTICATED. Send the
user via the header instead; the body is now empty.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The memory store client POSTs /memories/subscribe to mint realtime join
credentials, but the runtime router had no such route — the path fell
through to /memories/:id (memories/mutate), so a POST returned 405 Method
Not Allowed. Threads had the full chain (route → handleSubscribeToThreads →
platform); memory was missing the runtime proxy between the client and the
platform's POST /api/memories/subscribe.
Add `memories/subscribe` to the route union, match it before /memories/:id
(and exclude "subscribe" from the :id rule, mirroring threads), add
handleSubscribeToMemories returning { joinToken, joinCode } (memory delivers
the join code here, unlike threads where it rides the thread-list response),
and ɵsubscribeToMemories on the platform client.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Memory handlers now forward client-actionable 4xx platform statuses
verbatim (404 not-found, 409 conflict, 422 unprocessable) so a useMemories
consumer can distinguish those from a server error, and map a platform 5xx
(or malformed status) to 502 rather than collapsing everything to 500 — the
runtime is healthy, its dependency failed. This also avoids a Response
RangeError on an out-of-range status.
parseMemoryBody and the platform client's createMemory/updateMemory now
treat scope as optional, deferring the default to the platform.
Adds POST /memories (create), PATCH /memories/:id (supersede), and
DELETE /memories/:id (retire) to the intelligence runtime, mirroring the GET
read route: CopilotKitIntelligence.{createMemory,updateMemory,removeMemory}
proxy the platform's /api/memories with the identifyUser-resolved user via the
x-cpki-user-id header. Completes the REST surface the client memory store's
addMemory/updateMemory/removeMemory call.
memories/list fell through to the POST-only default in validateHttpMethod,
so GET /memories returned 405. Add it to the GET-allowed group alongside
threads/list.
Adds a runtime-native memory read endpoint mirroring /threads: route
(memories/list) → handleListMemories → CopilotKitIntelligence.listMemories,
which proxies the platform's GET /api/memories with the project API key and
the user resolved via identifyUser (scoped through the x-cpki-user-id header,
never a client-supplied id). The client memory store's {runtimeUrl}/memories
fetch now resolves out of the box for any intelligence runtime — no per-app
BFF code. Read-only for now (list); mutations to follow.
Reverts 2cad274. The real fix for models filling optional tool params is to
use OpenAI's Chat Completions API (the Responses API fills every declared
optional); preserving format/pattern in convertJsonSchemaToZodSchema was
unnecessary (OpenAI ignores format) and broadened behavior for every tool.
convertJsonSchemaToZodSchema dropped every string constraint except enum,
so a tool param like { type: "string", format: "uuid" } reached the model
as a bare optional string. Models then fill such optionals with "" instead
of omitting them, and the loosened model-side validation accepts "" — only
for a stricter downstream (e.g. an MCP server) to reject it.
Carry format (uuid/email/url/date-time), pattern, and minLength/maxLength
through to the generated Zod schema so the model sees the field's real
shape and the model-side validation matches the server's.
Repairs TypeScript check-types across the monorepo and adds a CI gate so
regressions are caught going forward:
- core: bundler module resolution and strict-mode fixes
- sdk-js: bundler module resolution; keep codegen, formatter, packaging working
- react-core: fixes across components, hooks, and tests
- react-native: restore catch binding referenced by TypeError cause
- runtime: repair check-types and bound AI SDK schema inference
- web-inspector: nodenext import extensions, export Anchor
- remaining packages and node example: assorted check-types repairs
- deps: add missing type-only devDependencies
- license context driven from /info licenseStatus
- ci: run check-types in the static quality workflow
Squashed from 12 commits for a single, easily-revertable change.
## Release monorepo v1.61.1
**Scope:** `monorepo` | **Bump:** `patch`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.61.1`
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.61.1`
- Creates git tag `monorepo/v1.61.1`
- 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.
Addresses PR review feedback:
- Resolve the license token once (option ?? COPILOTKIT_LICENSE_TOKEN) into a
protected readonly field on BaseCopilotRuntime, and have
CopilotIntelligenceRuntime's licenseChecker reuse it. Collapses the duplicated
resolution and structurally enforces that telemetry attribution and feature
gating can never disagree, instead of relying on a "keep in sync" comment.
- Add an integration test for the env-var-only path (no licenseToken option) —
the exact self-hosted scenario this PR targets — proving the env-resolved
token reaches lambdaClient.send through a real request. Kept in its own file
so the process-wide telemetry singleton (last-write-wins) can't false-pass it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds genuine end-to-end coverage beyond the SSE-via-Express case:
- SSE via the Hono adapter
- SSE via the framework-agnostic fetch handler (what node + custom adapters wrap)
- Intelligence mode end-to-end (real CopilotIntelligenceRuntime, WS runner stubbed)
Each constructs a real runtime (so the base-class setLicenseToken runs), drives a
real request through the adapter, and asserts the token reaches lambdaClient.send
on oss.runtime.copilot_request_created.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Only CopilotIntelligenceRuntime called telemetry.setLicenseToken in its
constructor; BaseCopilotRuntime and CopilotSseRuntime did not. As a result,
self-hosted SSE users got anonymous runtime telemetry (no telemetry_id) even
with a license token configured — and those events were additionally throttled
to the 5% anonymous sample rate, leaving runtime telemetry_id stuck at ~1%.
Hoist the licenseToken resolution (option ?? COPILOTKIT_LICENSE_TOKEN env
fallback) and telemetry.setLicenseToken call into BaseCopilotRuntime so SSE and
Intelligence runtimes attribute telemetry identically. Remove the now-redundant
duplicate from CopilotIntelligenceRuntime (its licenseChecker stays).
Tests cover every construction path into the endpoints:
- runtime-license-telemetry.test.ts: SSE/Intelligence direct + CopilotRuntime
shim (both delegates) x {explicit option, env fallback, none}; asserts the
token is set exactly once (guards against a double-set after the hoist).
- sse-license-telemetry.integration.test.ts: end-to-end proof the token rides
to lambdaClient.send through a real Express endpoint request.
- copilot-runtime-license-telemetry.test.ts: regression guard for the v1
CopilotRuntime path (already worked, previously untested).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>