## 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)
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.
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.
## 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)
## 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.
## 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)
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.
- 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.
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.
## 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.
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.
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.
## 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
The previous guide documented an older API (createTeamsAgentBot, a local "Teams
DevTools" bridge) that shipped through copilotkitnext. Rewrite it for the new
createBot + teams() PlatformAdapter, mirroring the Slack guide: M365 Agents
Playground quickstart, interactive Adaptive Cards, a human-approval gate,
splitting the bot from its agent over AG-UI, and Azure sideloading into real
Teams. Also refresh the frontend picker summary (Playground, not DevTools).
schema.json regenerated from the merged canonical schema.ts (failure_classifier
probe.exit additions UNION backend request.ingress/sse.first_byte/llm.call.*
boundaries + test_id adoption). Per-integration staged schema.ts copies
re-derived via 'showcase cvdiag-stage-ts' so codegen --check and stage --check
are both in sync. No hand-merge of generated artifacts.
## What
A new Cookbook recipe — **Build an agentic app on Angular + Google ADK**
— covering the non-obvious production gotchas when you wire an Angular
frontend to a Google ADK agent over AG-UI, with optional CopilotKit
Intelligence threads and memory. Written in the existing cookbook house
style (symptom → cause → fix callouts), and it links out to the Angular
and ADK quickstarts rather than re-teaching setup.
## Why
The Angular + ADK combination has a handful of correctness issues that
only surface in a real, multi-user, governed app, and they aren't
covered by the per-piece quickstarts:
- One agent, one store (a second composer must not create its own store)
- Scope the user via the **run body**, not an HTTP header (a header lags
by one in-session switch)
- Never reconfigure the runtime mid-submit (it recreates the agent store
and drops the message)
- Governance is server-side; the per-request allow-list rides the run
body
- Choose a capable model, and degrade gracefully when the Intelligence
platform is absent
The recipe is **model-flexible**: it frames ADK as running Gemini by
default but supporting any model ADK supports, consistent with the ADK
quickstart.
## Changes
- **New** `cookbook/angular-adk-agentic-app.mdx` — the recipe
- `cookbook/meta.json` — register in nav
- `cookbook/index.mdx` — add the index card (uses
`/logos/google-adk.svg`)
- `src/lib/sidebar-icon.tsx` — add `custom/google-adk` sidebar icon
entry
- `frontends/angular.mdx` — bidirectional cross-link into the recipe
- `src/lib/__tests__/docs-render.test.ts` — update the cookbook nav test
for the new page
## Test plan
Run from `showcase/shell-docs`:
- `npm run typecheck` — passes
- `npm run lint` — passes (only pre-existing warnings; none in changed
files)
- `npm run test` — the cookbook-nav test passes. Two
`public-assets.test.ts` cases fail **only locally** because git-LFS PNGs
are unmaterialized in a fresh worktree (the tests assert assets are not
LFS pointer stubs); they are unrelated to this change and pass in CI.
- `npm run build` — passes; all 211 static pages generate, including
`/cookbook` and `/cookbook/[...slug]`.
Verified in the browser at `localhost:3003`: recipe renders with correct
callout styling, code highlighting, populated TOC, sidebar entry, and
index card.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<img width="1026" height="586" alt="image"
src="https://github.com/user-attachments/assets/4c8d61ef-d74b-40fa-9e54-807750daed24"
/>
Two PR checks were red on the new integration:
- check-config-files: add strands-typescript/next.config.ts to the build-
config allowlist.
- Validate Showcase: update the new-integration guard pins that intentionally
trip when an integration is added — BORN_IN_SHOWCASE 6→7, calculator
_from-feature-parity count 18→19, catalog cross-join 874→920 / total_cells
855→900 / docs_only 19→20 (46 features × 20 integrations), and the aimock
substring-shadow ceiling 132→133 (+1 from the strands-typescript calculator
fixture).
Also drop the premature deploy wiring: strands-typescript is removed from
showcase_build.yml (matrix + path filter + ALL_SERVICES) because it has no
Railway service yet (deployed: false) and the railway-envs SSOT test requires
a real service entry. It re-enters the deploy pipeline when the Railway
service is provisioned (external setup per INTEGRATION-CHECKLIST).
Bring the TypeScript AWS Strands integration to parity with the Python
strands sibling now that the @ag-ui/aws-strands TS adapter is confirmed to
support the same feature surface (per its examples/server):
- Restore A2UI: the declarative-gen-ui + a2ui-fixed-schema demos, their
routes, qa, specs, the @copilotkit/a2ui-renderer dep, beautiful-chat's
A2UI catalog, and the manifest entries (generative_ui / features / demos /
a2ui_pattern). manifest now matches strands-python feature-for-feature.
- Header forwarding: attach `x-aimock-context: strands-typescript` as a
static defaultHeader on the OpenAI client (model-factory + sub-agent
client) — the TS analog of the Python integration's _header_forwarding
shim — so aimock matches this integration's fixtures.
- aimock fixtures: add d6/strands-typescript + d4/strands-typescript
(ported from the Python sibling, context retargeted).
- playwright.config: X-AIMock-Context → strands-typescript.
Note: the raw tests/e2e Playwright suite is flaky and not a CI merge gate
(demo e2e / `/eval` D5 are comment-triggered, not required) — it fails the
same specs for strands-python too. The auto-gates (build, validate-
constraints, oxlint/oxfmt, unit) are green.
Replace the placeholder logo with the official Agent Development Kit mark
from google/adk-python (assets/agent-development-kit.png), committed as an
LFS PNG like the other recipe logos (Daytona, Arcade). Point the card and
sidebar at /logos/google-adk.png and revert the unused google-adk.svg back
to its original state.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
public/logos/google-adk.svg was a placeholder (a grey tile with the text
"Go"), like the other letter-stub SVGs in that directory. Replace it with
the real ADK glyph, reusing the vector paths from the `AdkIcon` component
(src/components/icons/framework-icons.tsx) on a light tile so it renders
on both light and dark surfaces. Only the new cookbook recipe references
this file, so no other page is affected. Monochrome for now; design can
recolor to official ADK colors at approval.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ADK adapter (ag_ui_adk) builds session state from `dict(input.state)`
plus `input.context` (under `_ag_ui_context`); it does not mirror
`forwarded_props` into session state (it only reads it for the
`injectA2UITool` flag). So a user id sent via CopilotKit `properties`
(-> forwardedProps) never reaches `tool_context.state`, and the documented
scoping silently failed — the exact bug class the section warns about.
Carry the user id as agent context via `connectAgentContext` (or shared
agent state) instead, and read it from `tool_context.state["_ag_ui_context"]`
(or directly from state). Reconcile the contradictory forwardedProps/state
lines and fix the coding-agent prompt to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a cookbook recipe covering the non-obvious production gotchas when
wiring an Angular frontend to a Google ADK agent over AG-UI, with optional
CopilotKit Intelligence threads and memory: one agent store, run-body user
scoping (not a header), never reconfiguring the runtime mid-submit,
server-side governance, model selection, and graceful platform degradation.
- New recipe at cookbook/angular-adk-agentic-app.mdx
- Register in cookbook nav (meta.json) and add an index card
- Add a custom/google-adk sidebar icon entry
- Cross-link from frontends/angular
- Update the cookbook nav test for the new page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What
Adds **cvdiag** — a permanent, always-available observability subsystem
for the showcase, built to diagnose the red↔green cell flap on the
staging dashboard and to make that diagnosis a dashboard query rather
than a multi-day forensic hunt in the future.
Captures the full request path with `X-Test-Id` correlation across
**probe → backend → aimock → edge**, across every integration
(TypeScript, Python, Java/spring-ai, .NET):
- Per-language backend emitters (canonical + staged/compile-linked
mirrors), all sharing one schema (`schema.json`, closed-world
`additionalProperties:false`).
- CREATE-only writes to two new PocketBase collections: `cvdiag_events`
and `cvdiag_raw_byte_samples` (additive migrations — no existing data
touched).
- An 8-class flap classifier mapping to the observed failure signatures
(`sse-missing` / `text-unstable` / `dom-missing`).
- DEBUG-tier raw-byte capture (secret-scrubbed) and HMAC-guarded A/B
edge-interference detection.
## Why
The runId flap-fix (`cdc1e90e`, 2026-06-09) did **not** fully resolve
the flap — it was still observed 2026-06-19. cvdiag exists so the
*remaining* cause is observed live with full correlation instead of
inferred.
## Safety / enablement
- **Inert by default.** With `CVDIAG_BACKEND_EMITTER` unset the
subsystem performs zero host mutation (no logging-config changes, no
threads/tasks, no stdout) — verified by
`test_cvdiag_inert_when_disabled`. **To accumulate data, set
`CVDIAG_BACKEND_EMITTER=1` on the showcase services.**
- All per-language scrubbers match the canonical `scrubSecrets`
(sk-/base64url, Bearer, colon-less URL userinfo, size-guard) — verified
with real toolchains (vitest / mvn / dotnet).
- Merged latest `main` (only conflict: a clean `.csproj` include union).
## Verification
- harness `tsc --noEmit` ✓ · `src/cvdiag` vitest 251/251 ✓ ·
`cvdiag-stage-ts --check` in-sync ✓
- Java MessageScrubber 17/17 (mvn) ✓ · .NET CvdiagBackend 5/5 (dotnet
sdk:9.0) ✓ · Python emitters 93/93 (3.12) ✓
🤖 Generated with [Claude Code](https://claude.com/claude-code)