Commit Graph

11932 Commits

Author SHA1 Message Date
Tyler Slaton 1acb3c85de ci: notify engineering when release PR is created 2026-06-23 14:44:10 -07:00
Tyler Slaton 56615da485 ci: reduce release delay, fix flakey test, use devops bot token (#5644)
## Summary
- Mint a GitHub App token for the stable release workflow and reuse it
for PR creation and follow-up API calls
- Disable lefthook during automation commits so release PR generation
does not depend on local developer hooks
- Relax the CopilotChat perf regression test to assert correctness
without a hard 5s wall-clock check

## Testing
- Unit/UI test updated to allow longer async rendering while still
verifying 100 messages render successfully
- Not run (not requested)
2026-06-23 13:56:33 -07:00
Tyler Slaton 83b21df925 Stabilize release PR token and chat perf test 2026-06-23 13:51:35 -07:00
Mark f7cddc7651 fix(examples): route stack analyzer through end node (#5634)
## What does this PR do?

Routes the Gemini canvas stack analyzer through its registered `end`
node instead of directly to LangGraph's `END` sentinel.

This keeps the workflow wiring consistent with
`workflow.set_finish_point("end")` and ensures the cleanup/final state
emission in `end_node` is reachable.

## Related PRs and Issues

- Fixes #5605

## Tests

- `python3.12 -m py_compile examples/canvas/gemini/agent/stack_agent.py`
- `python3.12 - <<'PY' ... PY` (AST check that
`workflow.add_edge("analyze", "end")` exists and
`workflow.add_edge("analyze", END)` does not)
- `. /tmp/oss-pr-pipeline/langgraph-venv/bin/activate && python - <<'PY'
... PY` (LangGraph topology reproduction asserts `analyze -> end ->
__end__` and that `end_node` runs)
- `. /tmp/oss-pr-pipeline/langgraph-venv/bin/activate && python - <<'PY'
... PY` (imports `stack_agent.py` with CopilotKit/Gemini stubs and
asserts compiled `stack_analysis_graph` edges include `analyze -> end`
and not `analyze -> __end__`)
- `git diff --check`

## 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 (not applicable: example graph wiring bug fix)
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
2026-06-23 13:41:29 -07:00
Sam Julien c45f3ce63a fix: gate thread requests on runtime capabilities (#4971)
## Root cause

`useThreads` and the web inspector treated a connected runtime as if
thread list/inspect endpoints were always available. In non-Intelligence
setups, that could trigger `/threads` and inspector requests against
runtimes that did not expose compatible thread endpoints, producing
noisy benign 404s.

## Behavior change

- Adds `threadEndpoints` capability metadata to runtime info and carries
it through shared/core types.
- Reports thread list/inspect support only for multi-route runtimes
backed by Intelligence or the local `InMemoryAgentRunner`; single-route
runtime info reports thread endpoints as unavailable.
- Gates `useThreads` so it does not fetch when the runtime does not
advertise thread list support, and returns an explicit
unavailable-endpoints error instead.
- Gates web inspector owned thread stores and thread detail fetches on
advertised list/inspect support.

## Docs

- Clarifies managed Intelligence thread metadata/history versus
self-managed runner persistence.
- Documents that `AgentRunner`, `SqliteAgentRunner`, or a custom runner
can persist and replay chat history, but does not automatically provide
the managed `useThreads` list/mutation/realtime contract unless
compatible REST thread endpoints are exposed.
- Updates inspector/event-inspector messaging to avoid implying thread
browsing is Intelligence-only while still distinguishing durable managed
history from local in-memory development support.

## Verification

Publish-pass checks:

- `gh --version`
- `gh auth status`
- `git diff --check`
- `git diff --cached --check`
- Commit hook ran `lint-fix` with warnings only, then package checks via
Nx:
  - `nx run-many -t test --projects=packages/**`
  - `nx run-many -t publint,attw --projects=packages/**`

Implementation-agent reported checks:

- web-inspector tests passed
- core tests passed
- react-core tests passed
- runtime tests passed
- sqlite-runner tests passed
- `git diff --check` passed

Known limitation:

- Full `run-many check-types` was attempted by the implementation agent
and failed due pre-existing dependency/typecheck issues and runtime OOM,
not this FAC-1 change.
2026-06-23 12:50:00 -07:00
Mike Ryan 3e4c515252 fix: preserve legacy thread endpoint behavior 2026-06-23 12:10:01 -07:00
Mike Ryan 3452657416 fix: update thread capability test fixtures 2026-06-23 11:36:58 -07:00
Mike Ryan 0187ec250a fix: address thread capability review feedback 2026-06-23 11:33:48 -07:00
Mike Ryan d906171c26 fix: honor thread endpoint capabilities 2026-06-23 11:33:10 -07:00
Mike Ryan db09796809 fix: gate thread endpoints by runtime capability 2026-06-23 11:32:30 -07:00
Jordan Ritter d9ae055612 fix(showcase): wire promote-notify renderer + 2-line success Slack message (#5639)
## Summary

The three-variant promote Slack renderer (`showcase_promote_notify.yml`,
merged in #5240-era work) was built and reviewed but **never wired in**
— nothing dispatched it, so the showcase promote workflow kept posting
its old inline two-state notify (the misleading `❌ Showcase Promote
Failed [all 39 services]` even when most promoted). This PR completes
the wiring and applies the agreed success-message layout.

- **`promote-fleet.sh`**: emit a base64 `results_b64` payload
(schema_version=1, `succeeded[]` + `failed[]` with
`service/exit/category`) to `$GITHUB_OUTPUT`, alongside the existing
`succeeded_csv`/`staging_drift`.
- **`showcase_promote.yml`**: promote job exports `results_b64`;
**removed** the inline two-state notify and replaced it with a job that
enriches the blob with run context and dispatches
`showcase_promote_notify.yml` via `gh workflow run` (authed with the
devops-bot App token, mirroring `canary.yml`). Exactly one Slack message
per run.
- **`showcase_promote_notify.yml`** + **dry-run harness**: success
message reshaped to the agreed 2 lines — `✅ *Showcase Promoted to Prod*
— N ✓ · <run|View run>` then `Services: <promoted csv>`. Partial (⚠️)
and total (❌) variants unchanged.

## Why it matters

Today's `service=all` promote (25 promoted, 14 legitimately refused)
posted a blanket "Failed" listing all 39 services. With this wired, that
same run renders a **partial** (`⚠️ N ✓ · M ✗` + per-service Failed
bullets, cross-posted to #oss-alerts) — promoted vs refused is finally
visible, and a clean run shows the 2-line success.

## Verification

- Dry-run harness rendered for **all three variants**
(success/partial/total) — success is the new 2-line form; partial/total
byte-identical to the agreed format (regression-guarded).
- bats: promote-fleet 33/33 (incl. 3 new tests asserting the
`results_b64` schema), resolve-promote-targets 17/17,
verify-prod-display 6/6.
- shellcheck clean; actionlint no new findings.

## Test plan
- [x] dry-run harness: 3 variants render correctly
- [x] bats suites green
- [ ] CI green

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-23 11:24:20 -07:00
Austin Merrick e0dfe56940 fix(web-inspector): forward core headers on owned thread store requests (#5596)
## Problem

Fixes #5581.

When `enableInspector={true}` and `useThreads()` is **not** mounted, the
inspector creates its own thread store per agent
(`ensureOwnedThreadStore`). That store initialized its context with
empty headers:

```ts
store.setContext({
  runtimeUrl: core.runtimeUrl,
  headers: {}, // ← ignores headers configured on <CopilotKit>
  agentId,
});
```

So the inspector's `/threads` requests omitted the headers configured on
`<CopilotKit>` (e.g. `X-CSRF`, `Authorization`). In environments that
enforce CSRF/auth checks this returns **HTTP 403**; in lax local envs it
200s but still sends no headers. This is the inspector-side counterpart
to the `useThreads()` fix in #5300.

## Solution

1. Source the headers from `core.headers` when the owned store's context
is created.
2. Add an `onHeadersChanged` subscriber that re-applies headers to all
owned stores when the host updates them at runtime (e.g. a refreshed
auth/CSRF token via `core.setHeaders`). This mirrors `useThreads()`,
which re-dispatches the context whenever `core.headers` change, so the
owned stores' requests stay authorized.

Headers are spread (`{ ...core.headers }`) to match the existing pattern
in `use-threads.tsx` and produce a fresh mutable object. Stores
registered by `useThreads()` are untouched — only inspector-owned stores
are affected.

## Testing

Added two regression tests in
`packages/web-inspector/src/__tests__/web-inspector.spec.ts` (stub
`globalThis.fetch`, drive the owned store via the agents-changed path):

- the owned store's `/threads` request carries `core.headers`;
- an `onHeadersChanged` update re-applies the new headers on the next
request.

`pnpm --filter @copilotkit/web-inspector test` → 34 passed. Verified the
first test fails when the fix is reverted. Lint (oxlint) and formatting
(oxfmt) clean on the changed files.
2026-06-23 11:13:45 -07:00
Jordan Ritter 09968de331 fix(showcase): success promote Slack message — 2-line title+View-run, list promoted services 2026-06-23 10:58:51 -07:00
Jordan Ritter 951c20e329 fix(showcase): wire promote-notify renderer — emit results JSON + dispatch three-variant Slack message
promote-fleet.sh now emits a base64 results JSON (schema_version=1) with
both succeeded[] and failed[] alongside the existing succeeded_csv /
staging_drift outputs. showcase_promote.yml's notify job replaces the old
inline two-state (success/failure) notifier — which dumped the full
requested CSV and mislabeled any partial promote as a blanket Failed — with
a single dispatch of the previously-orphaned showcase_promote_notify.yml
renderer (success / partial / total). The dispatch enriches promote-fleet's
results blob with run context (6-hex run_id, trigger=workflow, operator,
pre_staging) and authenticates via the devops-bot App token (actions:write),
mirroring canary.yml — the default GITHUB_TOKEN cannot start new workflow runs.

promote-fleet has no failure taxonomy, so each failed entry uses the default
category "promote-failed".
2026-06-23 10:49:17 -07:00
Nathan 🔶 Tarbert 518c12e984 fix: reject pending human-in-the-loop promise on run abort (#5554) (#5633)
## Summary

Fixes #5554

When a backend agent calls a frontend tool via
`renderAndWaitForResponse` (the `useHumanInTheLoop` hook) and the run is
aborted (`stopAgent`/`abortRun`) while the form is still pending, the
handler promise was settled only by `respond()` — so on abort it either
hung forever or silently resolved to an empty string. The backend
received an empty `tool_call_result` (no error), which downstream agent
logic interpreted as "no input" — a silent state corruption.

## Root cause

`useHumanInTheLoop`
(`packages/react-core/src/v2/hooks/use-human-in-the-loop.tsx`) created
its handler promise capturing only `resolve`, and ignored the
`AbortSignal` that the core `RunHandler` already passes to tool handlers
(`packages/core/src/core/run-handler.ts`). An aborted run therefore
never settled the pending promise; an `undefined` result is stringified
to `""` (run-handler.ts) → silent empty tool result.

## Fix

Honor the existing `AbortSignal` in the HITL handler:
- If the signal is already aborted when the handler runs, reject
immediately.
- Otherwise attach a one-shot `abort` listener that rejects the pending
promise with an explicit `Error("Human-in-the-loop interaction
aborted")`.
- `respond()` detaches the listener before resolving, so a normal
response is unchanged and abort cannot fire after a normal resolve.

Core's existing catch path converts the rejection into an explicit error
tool result instead of a silent `""`. Scoped to the one hook; no
protocol change, no new timeout API, and **unmount is deliberately not
touched** (to avoid regressing reconnect/remount-resume).

## Tests added


`packages/react-core/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx`
— drives a HITL tool to the executing state, aborts the run without
calling `respond()`, and asserts an explicit non-empty error surfaces
(via `onToolExecutionEnd`) rather than a silent empty result. Fails
before the fix (handler hangs), passes after.

## Checklist

- [x] Failing test written and confirmed failing before the fix
- [x] Fix applied, test passes
- [x] Full `@copilotkit/react-core` suite passes (1291 passed;
reconnect/remount tests green)
- [x] Build succeeds (`nx build @copilotkit/react-core`)
- [x] Formatter passes
2026-06-23 13:20:19 -04:00
Alem Tuzlak 7393df22ff feat(bot): pluggable StateStore persistence + cross-platform transcripts (A+C) (#5613)
## Summary

Adds the **pluggable persistence layer** for the bot packages (spec
slices **A + C**): a single `StateStore` interface with
`memory`/`redis`/`postgres` backends, the existing in-memory action
store folded onto it (durable actions across restarts for free),
turn-locking + inbound-event dedup + thread subscriptions/state, and
**cross-platform per-user transcripts** (`bot.transcripts`). These are
the open-source primitives a future managed `IntelligenceStore` slots
into -- that backend is intentionally **out of scope** here.

Surface parity (reactions / ephemeral / modals -- slice **D**) and
concurrency strategies + serverless webhook ingress (slice **B**) are
**deferred** to follow-on PRs.

## What's included

**A -- `StateStore` foundation**
- `StateStore` interface: grouped `kv` / `list` / `lock` / `dedup` /
`queue` primitives (`packages/bot`).
- Backends, each its own package, all passing a shared conformance
suite: `MemoryStore` (in `@copilotkit/bot`),
**`@copilotkit/bot-store-redis`** (Lua-scripted lock/queue atomicity),
**`@copilotkit/bot-store-postgres`** (row-based TTL lock, `SKIP LOCKED`
dequeue).
- `ActionStore`/`InMemoryActionStore` reimplemented over `state.kv`
(`kvActionStore`) and marked `@deprecated`; `ConversationStore` gains a
`StateStore`-backed helper.
- **All persistence config lives under one `store` object** --
`createBot({ store: { adapter, state, identity, transcripts,
onLockConflict, lockTtl, dedupTtl } })` (default backend `MemoryStore`;
legacy root `actionStore` still honored). `store.state` is an optional
**Standard Schema** that makes `thread.state()` / `thread.setState()`
**typesafe** (inferred type + runtime validation on write).

```ts
const bot = createBot({
  adapters: [slack(/*...*/)],
  agent,
  store: {
    adapter: createRedisStore({ url: process.env.REDIS_URL }),
    state: z.object({ step: z.enum(["ask_name", "done"]) }), // -> thread.state()/setState() typed
    identity: ({ author }) => author.email ?? null,
    transcripts: { retention: "30d", maxPerUser: 200 },
    onLockConflict: "drop",
    lockTtl: 60_000,
    dedupTtl: 300_000,
  },
});
```
- **Durable actions** fall out of the fold -- the minted `ck:` action id
is the `kv` key, so a click after a restart re-renders from the
persisted snapshot when a durable backend is configured.
- Consumers wired: action snapshots, thread subscriptions + per-thread
state (`kv`), per-conversation **turn lock** with `onLockConflict:
"drop" | "force"` (`lock`), **inbound-event idempotency** by platform
event id (`dedup`). `queue` ships fully implemented + conformance-tested
but unconsumed (its consumer is the deferred concurrency work).

**C -- Cross-platform transcripts**
- `bot.transcripts` (`append` / `list` / `delete`) over `state.list`,
keyed by a developer-chosen `identity` resolver (paired-or-throw with
`transcripts` config), `retention` + `maxPerUser`, GDPR delete. Each
entry records its `platform` + `ts`, so an agent can reference "you said
on WhatsApp X" across surfaces.
- **`thread.runAgent({ transcript: true })`** auto-bridges the
transcript into a run: injects prior cross-platform history
(platform-labeled) as agent context, appends the current user turn, and
captures the streamed assistant reply back into the transcript. Manual
`append`/`list` remain available for full control.

**Surface plumbing**
- `eventId?` on the three ingress types + Slack adapter wiring
(Events-envelope `event_id` -> `client_msg_id` -> `channel:ts`;
interaction/command ids) so the dedup guard engages on Slack.

## Testing
- Shared `runStateStoreConformance` suite (kv/list/lock/dedup/queue
contract incl. stale-token fencing, TTL expiry, queue eviction,
**mixed-ttl whole-list expiry**, **kv/lock keyspace isolation**) runs
against all three backends. **Redis and Postgres validated live (16/16
each)** against local containers; MemoryStore runs in CI.
- Focused unit tests for `kvActionStore`, `Transcripts`,
`createStateBackedConversationStore`, thread subscribe/state, the
`createBot` orchestration (lock-release-on-throw, `onLockConflict`
callback, identity throws/null, dedup+lock ordering, dedup fail-open),
and Slack `eventId` derivation.

## Code review
Reviewed by a multi-agent pass (correctness / silent-failures / tests /
types / comments / backends / packaging) and **fixed to zero**. Notable
fixes: a Postgres data-retention bug (non-TTL append leaving immortal
rows -- now whole-list expiry), a Redis kv/lock keyspace collision
(added `lock:` infix), fail-open-but-now-fail-**loud** logging on
identity/dedup failures, eager `retention` validation, and required
`ThreadDeps.state`.

## Follow-ups (not in this PR)
- **CI: provision Redis + Postgres services and set `TEST_REDIS_URL` /
`TEST_POSTGRES_URL`** so the backend conformance suites run in CI
(currently `describe.skip` without them -- validated locally here).
- Discord/Telegram `eventId` wiring (Slack done; `// TODO` left in
place).
- Slice **B** (concurrency strategies, serverless webhook ingress) and
slice **D** (reactions/ephemeral/modals).
- `IntelligenceStore` managed backend (the product layer).

## Notes
- Dedup records on **receipt** (drops duplicate deliveries within the
TTL window); a handler that throws still leaves its event marked seen
(no retry-recovery) -- intentional, documented inline.
- **Squash-merge friendly** -- intra-branch commits are individual green
slices from a gated wave execution (one commit, `d73732cf14`, absorbed a
few wave-3 files via the auto-staging pre-commit hook).
- Pre-existing failures unrelated to this branch: strict `check-types`
in `src/threads.ts` / `src/utils/phoenix-observable.ts` /
`@copilotkit/core`; eslint warnings on Slack `chat.postMessage` and the
`pg` named export.

 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-23 18:58:01 +02:00
Mark bbe3b2a31a Merge branch 'main' into fix/issue-5554-hitl-abort-signal 2026-06-23 09:52:29 -07:00
Jordan Ritter 7fd44cce01 feat(showcase): whitelist promote parity policy — drop env-key-set-diff, add advisory tier (#5630)
## Summary

Implements the [Showcase Prod↔Staging Comparison
Policy](https://app.notion.com/p/3883aa38185281c4a564d09ad9613729). The
showcase `promote` preflight blocked on differences that are
intrinsically environment-specific and carry no functional risk — most
visibly a `NODE_ENV` env-key difference that helped trip the
`shell-docs` promote closed. This moves the env-key comparison from an
implicit **blacklist** (flag every key difference) to a **whitelist**
posture.

Changes in `showcase/bin/railway` (PromoteCommand):
- **Drop the env-var key-set-diff WARN** in `check_p6_parity` —
environment-specific keys (`NODE_ENV`, `CVDIAG_*`,
`SHOWCASE_BACKEND_HOST_PATTERN`, `BROWSER_POOL_*`) no longer block a
promote.
- **Add a non-blocking ADVISORY disposition** and demote region /
replicas / restartPolicy / missing-expected-prod-domains /
concurrency-key divergence from WARN → ADVISORY (reported, never
blocks). No REFUSE was demoted.
- `check_critical_env_key_parity` is **unchanged** — it stays
staging-gated (`(CRITICAL_ENV_KEYS & staging_keys) - prod_keys`): it
flags a critical key that staging carries but prod is missing (a real
divergence) and tolerates infra/operator tokens (`RAILWAY_TOKEN`,
`GHCR_TOKEN`, `SHARED_SECRET`, ...) that no application container holds.

## Verification

**Red-green (unit):** new spec coverage in `test_promote_p6.rb` (failing
pre-change, passing post-change): prod-only env key (the `NODE_ENV`
case) no longer blocks; region/replicas divergence is ADVISORY;
staging-has/prod-missing critical key REFUSEs while absent-from-both
does not. Full ruby suite: **172 runs / 0 failures**.

**Empirical live fleet diff (read-only, staging↔prod, before=main vs
after=this branch):**
- **0 regressions.** No service went clean→block; every still-blocked
service has the same pre-existing reason both sides (GHCR `:latest`
unresolvable for starters / transient staging-probe blips).
- **Net +5 clean / −5 block** — `aimock`, `harness`, `shell`,
`showcase-langgraph-python`, `showcase-langgraph-typescript` were
blocked solely by the removed env-key-set WARN and now promote cleanly.
- **shell-docs (`docs`): clean preflight** (rc=2) — the original gating
is gone.
- env-key-set WARN count across the fleet: 5 → 0.

## Test plan
- [x] `ruby showcase/bin/spec/all_tests.rb` (172/0)
- [x] 7-agent CR + confirmation round to zero bucket-(a); Procedure 3
promotion audit: 0 PROMOTE_TO_A
- [x] live fleet preflight diff: 0 regressions, shell-docs clean
- [ ] CI green

## Follow-up (separate PR — pre-existing, surfaced during review)
GHCR-P1 preflight test hardening; and pre-existing `bin/railway` latent
debt: `replicate_env_keys` mutates prod before the gate (latent, empty
set today) + prefix-less findings dropped by the tier filter; P2
in-flight TOCTOU guard vs P1 image_tag fallback;
`@staging_running_digests` memo-not-reset; `check_service_refs`
substring host-match; `assert_prod_specific_keys` no-op contract;
`imageDigest.empty?` on non-String; stale GHCR comment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-23 09:44:52 -07:00
Alem Tuzlak 5f2d7102d5 docs(bot): StateStore, persistence, and transcripts reference + guides 2026-06-23 18:38:47 +02:00
Alem Tuzlak 5ecdee36b8 feat(bot): pluggable StateStore persistence + cross-platform transcripts
Adds a durable persistence layer for @copilotkit/bot, replacing the
in-memory-only ActionStore with a pluggable StateStore.

- StateStore interface (kv/list/lock/dedup/queue) with a shared
  conformance suite; MemoryStore default plus @copilotkit/bot-store-redis
  and @copilotkit/bot-store-postgres backends.
- createBot({ store }): typed per-thread state via Standard Schema,
  action snapshots persisted through the store, per-conversation turn
  lock (onLockConflict drop|force), and inbound-event dedup keyed on a
  stable eventId. ActionStore is kept as a deprecated alias.
- Cross-platform transcripts (bot.transcripts + identity resolver) with
  age-bounded retention (prune on append + filter on read), and
  runAgent({ transcript: true }) to auto-inject history and capture the
  reply.
- createBot({ components }) re-registers components so durable actions
  re-fire after a restart; restart-durability demo in examples/slack.
- Dedup is marked seen only after the turn lock is acquired, so a turn
  dropped on lock-conflict does not burn its eventId (no lost retries).
- Release lockstep: bot-store-redis/postgres version with bot + bot-ui.
2026-06-23 18:33:38 +02:00
cyphercodes beb5485c0d fix(examples): route stack analyzer through end node 2026-06-23 18:25:52 +03:00
Nathan 🔶 Tarbert 6e14deeca9 Merge branch 'main' into fix/issue-5554-hitl-abort-signal 2026-06-23 10:48:43 -04:00
Jordan Ritter 43c5220398 feat(showcase): whitelist promote parity policy — drop env-key-set-diff, add advisory tier
Drop the env-var key-set-diff WARN in check_p6_parity (environment-specific keys
like NODE_ENV/CVDIAG_*/SHOWCASE_BACKEND_HOST_PATTERN/BROWSER_POOL_* no longer
block a promote) and introduce a non-blocking ADVISORY disposition demoting
region/replicas/restartPolicy, missing-expected-prod-domains, and concurrency-key
divergence from WARN. check_critical_env_key_parity stays staging-gated
((CRITICAL_ENV_KEYS & staging_keys) - prod_keys): it flags a critical key staging
carries but prod is missing, and tolerates infra/operator tokens absent from both.

Adds red-green spec coverage in test_promote_p6.rb. No source symbols renamed/removed.
2026-06-23 01:56:19 -07:00
Ran Shemtov 4170b4abe3 feat(showcase): add strands-typescript integration with base demos (#5575) 2026-06-23 10:48:15 +02:00
Ran Shemtov 429daec68a Merge branch 'main' into claude/trusting-babbage-f4d48a 2026-06-23 10:47:19 +02:00
Ran Shemtov 15bbc4ce86 fix(react-ui): sanitize raw HTML in Markdown renderer to prevent XSS (#5629)
## Summary

While auditing `@copilotkit/react-ui`, we found that the legacy chat
Markdown renderer (`packages/react-ui/src/components/chat/Markdown.tsx`)
runs `react-markdown` with `rehype-raw` and **no HTML sanitizer**.
`rehype-raw` re-enables parsing of raw HTML embedded in Markdown, and
this renderer is the default path for every assistant message
(`AssistantMessage.tsx`). Because assistant/agent output is
attacker-influenceable (prompt injection via RAG/retrieved docs,
tool/web-fetch results, or a compromised agent), raw HTML could reach
the DOM in the host app's origin — a cross-site scripting sink (CWE-79).

Note: the v2 `react-core` path already renders via sanitizing
Streamdown; only this legacy `react-ui` renderer was exposed.

## Fix

Add `rehype-sanitize` to the rehype plugin chain, positioned as the
**terminal pass**:

```
[rehypeRaw, ...(rehypePlugins ?? []), rehypeSanitize]
```

Sanitize runs **last**, after any consumer-supplied `rehypePlugins`, so
it cannot be bypassed by a downstream plugin re-introducing raw HTML.
The default render path (no consumer plugins) is unchanged in behavior.
Adds `rehype-sanitize` as a dependency and `react-dom` (caret range) as
a devDependency for the SSR-based regression test.

## Red → green

A regression test installs a malicious consumer `rehypePlugins` entry
that injects a raw `<script>` node into the HAST:
- **Before** (sanitize not terminal): payload survived to output —
`…safe content</div><script>alert("pwned")</script>` — bypass
reproduced.
- **After** (sanitize terminal): payload stripped; test passes.

The suite also covers `<script>/<style>/<base>/<form>/<iframe>`, event
handlers, and `javascript:` URLs, and asserts legitimate Markdown/GFM
features still render.

## Risk

**Medium — CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N (4.8).**
`react-ui` is a `"use client"` boundary, so the "arbitrary `<script>`
under SSR/RSC" tier is non-default (host-misconfiguration-dependent); on
the client path React already neutralizes
`onerror`/`javascript:`/`<script>`. The realistic residual set this
closes is `<base href>` (relative-URL/redirect hijack), `<style>`/CSS
injection, DOM-clobbering, and `<form action>`. All `react-ui` 1.x
consumers on the default chat are exposed today.

## Functionality retention

Full react-ui suite green (**53/53**). Verified preserved: `language-*`
code classNames (syntax highlighting), GFM tables/strikethrough, the
streaming cursor, and links. The default GitHub sanitize schema sufficed
— no custom schema needed.

## Follow-ups (out of scope here)

- `remarkMath` is enabled with no `rehype-katex`, so `$$…$$` renders as
a code-block widget rather than math — add KaTeX (+ schema) or drop
`remarkMath`.
- `code` renderer robustness: it ignores react-markdown's `inline` prop
and `String(children)` on multi-node arrays yields `"[object Object]"`.
- Custom components spread `{...props}` (leaking `node`), and
`{...components}` lets callers override the hardened `<a>`.
- Add CSP guidance to docs (`base-uri 'self'`, `form-action 'self'`,
`script-src`) as defense-in-depth.

## Test plan

- [x] `pnpm -F @copilotkit/react-ui test` — 53/53
- [x] `nx run @copilotkit/react-ui:build`
- [x] red→green on the consumer-plugin injection test
2026-06-23 09:42:00 +02:00
Ran Shemtov 47fa480a55 Merge branch 'main' into fix/react-ui-markdown-xss-sanitize 2026-06-23 09:41:44 +02:00
Ran Shemtov e0d6eeaebe Merge branch 'main' into claude/trusting-babbage-f4d48a 2026-06-23 09:21:35 +02:00
Ran Shemtov 2fae1e798e fix(core): preserve runtime agent instance across re-connection (#5628) 2026-06-23 09:02:15 +02:00
Jordan Ritter 76c21b90e4 fix(react-ui): sanitize raw HTML in Markdown renderer to prevent XSS
The legacy Markdown renderer enabled rehype-raw with no HTML sanitizer,
so raw HTML embedded in assistant/model output reached the DOM (CWE-79).

Add rehype-sanitize as the terminal rehype pass so it runs after any
consumer-supplied rehypePlugins and cannot be bypassed. Add a regression
test covering the dangerous-HTML vectors (script/style/base/form/iframe,
event handlers, javascript: URLs) and the consumer-plugin injection path,
and assert legitimate Markdown/GFM features still render. Pin react-dom to
a caret range for the SSR-based test.
2026-06-22 23:54:58 -07:00
Jordan Ritter b9311f94b9 fix(core): preserve runtime agent instance across re-connection
`updateRuntimeConnection` unconditionally rebuilt the `remoteAgents` map
with a fresh `ProxiedCopilotRuntimeAgent` for every id on each connect,
discarding the already-registered live instance along with its
accumulated `messages`, `threadId`, and subscriptions. A re-connection
(an `/info` re-settle, or a header/config/transport change) therefore
swapped the live instance for an empty one. Downstream the `use-agent`
memo keys on the instance identity returned by `getAgent(id)`, so the
swap unmounted an already-rendered conversation — the source of the
showcase auth `dom-missing` flap.

Reuse the existing instance for ids still advertised by the runtime
(re-applying only registry-owned headers/credentials in place); mint a
new proxy only for genuinely-new ids; drop ids no longer present. The
disconnect/no-runtime and error paths still clear `remoteAgents`.
2026-06-22 23:16:35 -07:00
Jordan Ritter f1b2595dcd feat(showcase): on-demand prod-vs-staging reconcile tool (reconcile-prod CLI + manual workflow) (#5623)
## Summary

An **on-demand** tool to answer "is prod caught up with staging right
now?". The showcase deploy model is **staging = mutable `:latest`**
(continuously rebuilt) and **prod = immutable `@sha256:`** (advances
only on an explicit promote), so a prod column can sit **behind** a
green staging.

**There is no scheduled drift alert — by design.** Prod lagging staging
is **often intentional**: changes are batched and promoted deliberately,
so a recurring "N columns stale" alert would be pure noise. This tool is
therefore manual-only: a maintainer runs it when they want to check, and
it tells them the current state.

- **`bin/railway reconcile-prod`** — for every prod-eligible
(`probe.prod == true`) service, compares the **prod serving digest**
(the `@sha256:` from
`SnapshotCommand.build_snapshot(PRODUCTION_ENV_ID)`) against the
**staging running digest** (reuses
`PromoteCommand#staging_running_digest`, the same source the promote pin
uses). Classifies each:
  - `green` — prod == staging (in sync)
- `stale` — prod != staging **and** staging is resolvable (prod is
behind a green staging)
- `gray` — staging running digest not resolvable, or the service has no
prod snapshot entry yet — informational, **not** stale
- Prints a readable per-service table + summary; **exits nonzero iff any
service is stale**; `--json` for machine output. **Read-only — no
promotes/mutations.**
- **`showcase/scripts/reconcile-prod-gate.sh`** — wrapper mirroring
`lint-prod-gate.sh`: surfaces the table into `$GITHUB_STEP_SUMMARY`,
optionally captures `--json` to `RECONCILE_JSON`, and propagates the
exit-code verdict (never swallows a non-zero).
- **`.github/workflows/showcase_reconcile.yml`** — **`workflow_dispatch`
only** (no cron). Regenerates the SSOT JSON (`EMIT_SKIP_OXFMT=1`, same
as the promote workflow's resolve/promote jobs), runs the gate with the
Railway/GHCR auth env, renders the reconcile table to the **GH step
summary**, and uploads the `--json` as a `reconcile-json` artifact. **No
Slack.** The run exits nonzero on a stale column so a manual run visibly
flags drift. `timeout-minutes: 10`.
- **Tests** — Ruby minitest (`test_reconcile_prod.rb`: classification +
exit-code + `--json` shape + dispatcher registration) and a bats gate
test (`reconcile-prod-gate.bats`). Wired the gate script into the
`showcase_validate.yml` shellcheck list.

### What changed from the original scheduled-alert design

The first cut of this PR shipped a daily cron + auto-post to #oss-alerts
on any stale column. Per owner feedback, that was reshaped to on-demand
only: the `schedule:` trigger and the Slack-on-stale step were removed
(intentional/deliberate staleness is not a bug, so an unsolicited
recurring alert is noise). The CLI command, the gate wrapper, and all
tests are unchanged.

## Gates

- `ruby showcase/bin/spec/test_reconcile_prod.rb` → **9 runs, 20
assertions, 0 failures**
- `bats showcase/scripts/__tests__/reconcile-prod-gate.bats` → **6 ok**
- `shellcheck -s bash showcase/scripts/reconcile-prod-gate.sh` →
**clean**
- `actionlint .github/workflows/showcase_reconcile.yml` → **clean** (the
pre-existing `depot-ubuntu-24.04-4` custom-runner-label warning is on
`showcase_validate.yml`, predates this PR — my only change there is one
line in the shellcheck list)

## Test plan

- [ ] CI green (Ruby suite, bats suite, actionlint/shellcheck,
commitlint)
- [ ] Optional: read-only `workflow_dispatch` run of
`showcase_reconcile.yml` to confirm it runs against live prod/staging
(safe — no mutations)
2026-06-22 23:08:17 -07:00
Jordan Ritter 9cb9260dca fix(showcase): preserve reconcile gate stderr diagnostics, skip blank artifact 2026-06-22 23:00:08 -07:00
Tyler Slaton b77d2710aa feat(bot-whatsapp): WhatsApp Cloud API platform adapter + example + docs (#5449)
## What

Adds **`@copilotkit/bot-whatsapp`** — a WhatsApp Business **Cloud API**
`PlatformAdapter` for the platform-agnostic `@copilotkit/bot` engine —
plus a runnable **`examples/whatsapp`** app and docs. This brings
WhatsApp to the bots ecosystem alongside the existing Slack support,
reusing the engine, the `@copilotkit/bot-ui` IR, and the pluggable
`ActionStore` untouched.

## How it works

- **Ingress:** the adapter owns its own HTTP server — GET verification
handshake (`hub.challenge`) + POST intake validated by
`X-Hub-Signature-256` HMAC (timing-safe), acked `200` immediately then
processed async.
- **No streaming:** WhatsApp messages are immutable, so the run renderer
**buffers** text and sends once on `TEXT_MESSAGE_END`
(`supportsStreaming: false`; `update()` posts fresh, `delete()` no-ops).
- **Interactive mapping:** text/section → text; ≤3 buttons →
reply-button message; `Select` or 4–10 actions → list message; >10 →
numbered-text fallback. A control's `value` round-trips by encoding it
into the reply id (`ck:…::<json>`), since WhatsApp replies carry no
value field; oversized encodings fail loud rather than corrupt silently.
- **Memory:** WhatsApp exposes no readable history, so a pluggable
**`HistoryStore`** (default `InMemoryHistoryStore`) holds it and replays
it into `agent.messages` each turn (fresh threadId per turn, mirroring
`bot-slack`). Swap in a durable backend to persist across restarts.
- **Commands:** leading-keyword matching (`commandPrefix`, default `/`);
the command text is injected via the engine's `runAgent({ prompt })`
path (not persisted at ingress).
- **Inbound media** → AG-UI multimodal content parts; **HITL** via
interactive replies.

## Example

`examples/whatsapp` mirrors `examples/slack`: a CopilotKit
`BuiltInAgent` over MCP (Linear + Notion), with `issue_list`, an
interactive `show_incident`, and a `confirm_write` HITL gate.

## Tests & verification

- 62 unit tests across the package (render mapping, markdown→WhatsApp,
signature verification incl. wrong-but-equal-length, interaction
decode/round-trip, buffered renderer, webhook listener/server, stores,
media, adapter).
- `build` ✅, package `check-types` ✅, `publint`/`attw` (ESM-only) ✅,
example `check-types` ✅. Full `nx run-many -t test
--projects=packages/**` passes.
- Two rounds of code review (APPROVE) — fixed slash-command history
double-append and silent value-truncation; minors (HMAC over raw bytes,
conversationKey invariant, offset-correct Blob, unused-dep pruning,
added tests).

## Docs

Package `README.md` + `ARCHITECTURE.md`, example setup guide (Meta app +
webhook + tunnel), and a `shell-docs` WhatsApp guide page (registered in
`meta.json` + early-access gate).

## Notes / out of scope (v1)

- No template-send path for messaging outside WhatsApp's 24-hour
customer-service window (documented limitation).
- Pre-existing, unrelated `@copilotkit/core` `phoenix-observable.ts`
typecheck error exists on the branch base (missing `@types/phoenix`) —
not introduced here.
2026-06-22 20:34:54 -07:00
Jordan Ritter d10aa858ec feat(harness): complete cvdiag observability on the d5/d6 probe path (#5625)
## What
Completes the cvdiag flap-observability instrument so **d5/d6/e2e probe
runs are readable from `cvdiag_events`** — previously only the d4 driver
emitted probe-layer rows, leaving the d5/d6 path (where the dashboard
flaps live) a blind spot.

- Extract `CvdiagProbeSession` (+ event shapes, constants,
`turnCompleteReason`) from `d4-chat-roundtrip.ts` into shared
`cvdiag/probe-session.ts`; d4 re-imports (behavior-preserving).
- Wire `CvdiagProbeSession` into `d6-all-pills.ts::runFeature`: emit
`probe.start/navigate.complete/message.send/firstToken` + an
exactly-once guarded `probe.exit` carrying `terminal_outcome` +
`failure_classifier`, and close the probe↔backend `test_id` join
(X-Test-Id already injected).
- Thread `cvdiagPbWriter` through `orchestrator.ts` (fleet worker) +
`cli/runner.ts` (`--live`) so probe events persist to `cvdiag_events`.

## Why
The d5/d6 probe path emitted **zero `probe.exit` rows** (verified: 2
days of cvdiag retention = 100% `d4-`, zero d5), so a flapping d5 cell
(e.g. `dom-missing` reds) could not be diagnosed from staging data. This
closes that gap.

## Verification
- Local red-green on the real surface (`langgraph-typescript --d5`):
`probe.exit` rows **0 → 38** (terminal_outcome + failure_classifier
populated).
- Full harness suite: **3167/3167 passing**; tsc/oxlint/oxfmt/build
clean.

## Code review (2-round cr-loop, 7 agents/round, converged + Procedure 3
clean)
- **A1** — `messageSend` char-count ran unguarded in the probe path; a
non-string input could throw and red a green probe. Fixed: computation
moved inside the `if (cvdiag)` guard + non-string coerced. (red-green)
- **A2** — `parseFailureClassifier` used a stale hardcoded 4-member
allow-list omitting `selector-mismatch`; reconciled both membership
checks to the canonical `FAILURE_CLASSIFIER_SET` (derived from
`CVDIAG_FAILURE_CLASSIFIERS`). (red-green)

## Follow-ups (not in this PR)
- `--headed` CLI launcher lacks the `goto`-wrap that installs the SSE
interceptor, so cvdiag (newly wired into `--headed`) mislabels
`failure_classifier` there; the supported fleet/`--direct` path is
correct. (dev-path quality)
- d6 feature-concurrency: 4 concurrent features share `test_id`
`d6-<slug>-<runId>`; per-feature reads work via `demo=featureType`, but
the §5 sequence_num join + replay buffer would benefit from per-feature
keying.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-22 17:14:32 -07:00
Jordan Ritter 6ef7762448 fix(showcase): standalone promote services (no deps, never gated) — flag docs (#5626)
## Problem

A `shell-docs` promote (live run
[27990607860](https://github.com/CopilotKit/CopilotKit/actions/runs/27990607860))
failed even though docs itself was healthy. Requesting `docs` expands
the promote **dependency closure** to include the always-on Tier-1
verification set:

```
CLOSURE_PLAN: 0:pocketbase,1:dashboard,1:harness,2:docs
```

`harness` then tripped a **P6 env-divergence WARN-refusal**
(`only-in-staging BROWSER_POOL_*`, `only-in-prod NODE_ENV`) and the tier
barrier gated `docs` as **NOT-ATTEMPTED**. docs has no runtime
dependency on the control plane, so it should never have been coupled to
harness.

## Fix

Introduce a declarative **`standalone`** service class — a leaf that
**neither depends on anything nor gates on anything** — and flag `docs`:

- **`computePromoteClosure` (railway-envs.ts)** and the
**resolve-promote-targets jq mirror** skip the Tier-1 union when the
requested set is *entirely* standalone, so a standalone request promotes
**only itself** (a mixed / `all` request still forces Tier-1 for the
rest).
- Standalone closure members are emitted with an **`s:` plan marker**;
**promote-fleet.sh** promotes `s:` services **ungated** — always
attempted (never NOT-ATTEMPTED on an unrelated failure), and their own
failure fails the run **without** gating the tiers.
- The tier-gating model is unchanged for every non-standalone service.

Net effect: `service=shell-docs` now resolves to a closure of just
`docs` (no harness), and docs is never gated in an `all` run.

## Red → Green (real surfaces)

Against `origin/main` (pre-fix) the new tests fail with the exact bug;
after the fix they pass:

| suite | RED (origin/main) | GREEN (this PR) |
|---|---|---|
| `railway-envs.test.ts` (computePromoteClosure) | 4 fail — `docs`
closure pulls in `harness`/`dashboard`; `.standalone` undefined | 103
pass |
| `resolve-promote-targets.bats` | 4 fail —
`closure_csv=harness,dashboard,docs`;
`closure_plan=1:harness,1:dashboard,2:docs` | 17 pass |
| `promote-fleet.bats` | 3 fail — `CLOSURE_PLAN token 's:svc-docs' has
an unknown tier 's'` | 30 pass |

Other gates: `shellcheck --severity=warning` clean (both scripts);
`emit-railway-envs-json.ts --check` → up to date; `tsc -p
scripts/tsconfig.json` clean for the changed files.

## Note

Prod `docs` was unblocked out-of-band via a direct `bin/railway promote
docs` (single-service, no closure) while this lands; the #team-showcase
success notification was posted manually. This PR fixes the *workflow*
path so the manual route is no longer needed.
2026-06-22 17:13:35 -07:00
Jordan Ritter 4d953e5ed2 fix(showcase): standalone promote services (no deps, never gated) — flag docs
A `shell-docs` promote expanded its dependency closure to include the
always-on Tier-1 verification set (harness, dashboard, …); harness then
tripped a P6 env-divergence WARN-refusal and the tier barrier gated docs
as NOT-ATTEMPTED. docs has no runtime dependency on the control plane.

Introduce a declarative `standalone` service class: a leaf that neither
depends on anything nor gates on anything. computePromoteClosure (TS) and
the resolve-promote-targets jq both skip the Tier-1 union when the request
is entirely standalone, so a standalone request promotes ONLY itself; the
fleet runner promotes `s:`-marked services ungated (never NOT-ATTEMPTED on
an unrelated failure, never gating a tier). Flag `docs` standalone.

Red-green: railway-envs.test.ts (4), resolve-promote-targets.bats (5),
promote-fleet.bats (3) fail on origin/main (docs closure pulls harness;
`s:` is an unknown tier) and pass after the fix.
2026-06-22 17:05:25 -07:00
Jordan Ritter a618bdce35 feat(harness): emit cvdiag probe-session boundaries from the d5/d6 probe path
- open a per-feature CvdiagProbeSession for each d5/d6 pill probe
- emit exactly-once probe.exit and failure_classifier per session
- join probe-session output to its run via the X-Test-Id header
- thread cvdiagPbWriter through the orchestrator and CLI runner

Call-Site Enumeration: FAILURE_CLASSIFIER_SET is exported from
cvdiag/probe-session and consumed by d6-all-pills (classifier validation
against the canonical set). The export has no other call sites; any future
classifier addition must update the canonical set in probe-session and the
validation in d6-all-pills together.
2026-06-22 16:42:43 -07:00
Jordan Ritter 6929ad1c33 refactor(harness): extract CvdiagProbeSession into shared cvdiag/probe-session
Behavior-preserving extraction of the CvdiagProbeSession lifecycle from the
d4 chat-roundtrip driver into a shared cvdiag/probe-session module, so the
d5/d6 probe path can reuse the same session boundaries. d4-chat-roundtrip
now imports the extracted session instead of defining it inline.
2026-06-22 16:42:31 -07:00
Sam Julien b9b08381ba docs: backport Threads guide to authored integrations (#5620)
## Summary

- Backports the generated/root Threads guide content into the shared
authored Threads snippet.
- Adds the CLI “Choose your starting point” path, manual path, thread
lock options, Enterprise Intelligence CTA, and corrected next-step links
to authored Threads docs.
- Standardizes authored integration Threads pages to explicitly import
the shared snippet with `components={props.components}` so authored
routes stay aligned.

## Authored routes covered

- AG2
- Agno
- AWS Strands
- Built-in Agent
- CrewAI Flows
- LangGraph
- LlamaIndex
- Mastra
- Microsoft Agent Framework
- PydanticAI

## Validation

- `npm run pretypecheck` in `showcase/shell-docs`
- `npm run lint` in `showcase/shell-docs` (passes with existing
warnings)
- `npm run test` in `showcase/shell-docs`
- `npm run typecheck` in `showcase/shell-docs`
- `npm run build` in `showcase/shell-docs` (passes with existing
Next/Turbopack warnings)
- `git diff --check`
- Manual MDX link sweep for changed docs links (`/premium/self-hosting`,
`/premium/threads-explained`, `/reference/hooks/useThreads`, and
`http://localhost:3000`)

## Formatter note

- `pnpm run check-format` currently fails on unrelated existing files
under `examples/showcases/arcade-tools/*`,
`examples/v2/react/demo/tsconfig.json`, `migrations.json`, and
`nx.json`.
- Scoped `oxfmt --check` does not treat the changed MDX files as target
files, so there is no formatter-owned MDX change to apply here.
2026-06-22 15:47:07 -07:00
Sam Julien 2fe292e4c7 docs(shell-docs): clarify tailored content selectors (#5621)
## Summary
- restyle TailoredContent selectors as native segmented path buttons
- add a selected check indicator plus hover/focus affordances
- add a rendering test for button semantics and selected state

## Verification
- pnpm exec oxfmt --check
showcase/shell-docs/src/components/react/tailored-content.tsx
showcase/shell-docs/src/components/react/__tests__/tailored-content.test.tsx
- npm run lint (showcase/shell-docs; existing warnings only)
- npm run typecheck (showcase/shell-docs)
- npm run test (showcase/shell-docs)
- npm run build (showcase/shell-docs; existing Turbopack NFT trace
warning)

## Notes
- Repo-wide pnpm check-format currently fails on unrelated pre-existing
files outside this PR; the touched files pass scoped oxfmt check.
2026-06-22 15:46:53 -07:00
Jordan Ritter 2610b87bcb perf(showcase): within-tier parallel fan-out for promote-fleet so service=all fits the timeout (#5622)
## Summary

A full `service=all` promote ran **fully serially** and exceeded the
promote job's 20-minute `timeout-minutes`, getting cancelled mid-fleet
(silent partial promotion). This adds **within-tier parallel fan-out**
so `all` completes in budget.

## Changes

- **`promote-fleet.sh`** — within a tier, `promote_one` is backgrounded
up to `PROMOTE_FANOUT` (default 5) via a **bash-3.2-safe** PID-array
bounded launcher (plain `wait <pid>`, no `wait -n`/`declare -n`).
Per-service results go to temp files (`$WORK/<svc>.rc/.drift/.log`) and
`reap_tier` repatriates them into `succeeded[]`/`failed[]`/`drift[]`
(subshell-safe — backgrounded children can't mutate parent arrays).
**Tier boundaries are hard barriers** (all PIDs drained before the next
tier); cross-tier stays serial. A present-but-empty/non-numeric `.rc`
(crash/disk-full mid-write) is treated as a clean failure, not parsed as
garbage.
- **`showcase_promote.yml`** — wires `CLOSURE_PLAN` into the promote
step (was flat `SERVICES_CSV`) so the fan-out is tier-aware; bumps the
promote job `timeout-minutes` **20 → 35**.
- **`promote-fleet.bats`** — new fan-out tests with a **deterministic
rendezvous-barrier** concurrency proof (no wall-clock sleep race;
RED-on-serial preserved via timeout), tier-barrier boundary-inclusive
(`>=`), and the empty-`.rc`-as-failure case.
- `SC2317,SC2329` shellcheck disable for the trap-only `cleanup()`
(ubuntu-24.04 CI ships shellcheck 0.9.0).

## Test plan

- [x] bats **27/27** (file) / **108/108** (dir) green
- [x] shellcheck clean on local 0.11.0 **and** pinned CI 0.9.0
- [x] actionlint clean on the workflow
- [x] within-tier concurrency ≤ cap and reaches cap; tier barrier holds;
one failure doesn't abort its tier + run exits nonzero
2026-06-22 15:45:19 -07:00
Nathan 🔶 Tarbert 7d644f22fa fix: reject pending human-in-the-loop promise on run abort (#5554) 2026-06-22 18:04:50 -04:00
Jordan Ritter c2c19c0853 perf(showcase): within-tier parallel fan-out for promote-fleet so service=all fits the timeout
promote-fleet.sh fans out promote_one within a tier up to PROMOTE_FANOUT
(default 5) via a bash-3.2-safe PID-array bounded launcher (plain `wait`, no
`wait -n`/`declare -n`); per-service results to temp files + reap_tier
repatriates (subshell-safe); tier boundaries are hard barriers (cross-tier
serial); showcase_promote.yml wires CLOSURE_PLAN + bumps timeout-minutes 20->35;
adds bats fan-out tests (deterministic rendezvous-barrier concurrency proof;
present-but-empty .rc treated as failed; tier-barrier boundary-inclusive `>=`);
SC2317/SC2329 shellcheck disable for ubuntu-24.04 0.9.0.
2026-06-22 14:50:16 -07:00
Jordan Ritter cd3844da65 refactor(showcase): make prod-vs-staging reconcile on-demand only (no cron, no Slack)
Prod sitting behind staging is often intentional (changes are batched and
promoted deliberately), so a recurring drift alert is noise. Reshape the
reconcile workflow to manual-only:

- Remove the daily `schedule:` cron trigger — leave only `workflow_dispatch`.
- Remove the auto-Slack-on-stale step (and its SLACK_WEBHOOK env / stale_line
  output derivation) — no unsolicited #oss-alerts post on mere staleness.
- A manual run surfaces the reconcile table to the GH step summary, keeps the
  cheap `--json` capture as an uploaded artifact, and still exits nonzero on a
  stale column so a manual run visibly flags drift.
- De-noise the gate script + bats comments that referenced the removed
  scheduled/Slack behavior.

The on-demand CLI (`bin/railway reconcile-prod`), the gate wrapper, and the
Ruby + bats tests are unchanged.
2026-06-22 14:20:02 -07:00
Jordan Ritter 90bcd66b09 feat(showcase): detect prod columns stale vs green staging (reconcile-prod drift gate)
Lever 1 of the promote-reliability hardening plan. The showcase deploy
model is staging=mutable :latest (continuously rebuilt), prod=immutable
@sha256: (advances only on explicit promote), so a prod column can
silently fall BEHIND a green staging — drift today is only noticed by
eyeballing a dead column. This adds proactive, automatic detection.

- bin/railway reconcile-prod: for every prod-eligible (probe.prod==true)
  service, compares the prod SERVING digest (LintProd snapshot path) vs
  the staging RUNNING digest (reuses PromoteCommand#staging_running_digest).
  Classifies green/stale/gray, prints a table + summary, exits 1 iff any
  stale. --json for machine output. Read-only: no promotes/mutations.
- scripts/reconcile-prod-gate.sh: wrapper mirroring lint-prod-gate.sh —
  surfaces the table to the GH step summary, captures JSON for the Slack
  builder, propagates the exit-code verdict.
- .github/workflows/showcase_reconcile.yml: daily cron + workflow_dispatch;
  runs the gate; on stale services posts the stale-column list to
  #oss-alerts (SLACK_WEBHOOK_OSS_ALERTS) via the fromJSON('"\n"') idiom.
- Tests: Ruby minitest (classification + exit-code, RED-anchored on a
  drift-blind classifier) and a bats gate test. Wired the gate script
  into the showcase_validate.yml shellcheck list.

Post-promote convergence verification is deferred to a fast-follow.
2026-06-22 14:14:18 -07:00
Sam Julien 3910d24aab docs(shell-docs): clarify tailored content selectors 2026-06-22 13:52:31 -07:00
Sam Julien fae187b5db docs: backport threads guide to authored integrations 2026-06-22 13:46:32 -07:00
Sam Julien f8df3abf75 docs: correct Enterprise Intelligence docs links (#5617)
## Summary
- Rename the overview capability from "Cloud-hosted web app" to
"Cloud-hosted Intelligence features".
- Remove the redirecting multi-conversation tutorial link from thread
docs, shared thread snippets, and useThreads references.

## Validation
- npm run test (showcase/shell-docs)
- npm run lint (showcase/shell-docs; exits 0 with pre-existing warnings)
- npm run typecheck (showcase/shell-docs)
- npm run build (showcase/shell-docs)
- Manual link sweep for edited MDX confirmed no remaining
/tutorials/multi-conversation-chat links
2026-06-22 13:24:30 -07:00
Mark 33a2fbc167 fix(react-ui): add stable test ids to chat input (#4519)
## What does this PR do?

Adds stable test IDs to the CopilotChat input area so automated tests
can reliably target the default chat UI.

Changes included:

- Adds `data-testid="copilot-chat-input"` to the chat textarea.
- Adds standard `data-testid` values to the send and stop button.
- Keeps the existing `data-test-id` values for backwards compatibility.

This is a small non-breaking testability improvement for the React UI
package.

## Related PRs and Issues

Related to #4215

## 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-06-22 12:25:04 -07:00