mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
v1.63.2
13420 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e0c7fd30ee |
fix(showcase): alert when an all-legs-cancelled build produced no successes
The notify-all-builds-failed and notify jobs keyed off a 'failure' rollup / bare failure(), so a build where every real service failed but one leg was cancelled (contention) rolled up to 'cancelled' and sent no alert — the same blind spot as the redeploy guard. Fire on any_success == 'false' (guarded by a status function so a user-cancelled run stays silent). Extends the guard test. |
||
|
|
bd000f973b |
showcase: consolidate deploy onto CI-explicit path (guard fix, autoUpdates SSOT, drift gate, reconcile) (#6082)
## Showcase deploy-mechanism consolidation Consolidates the showcase Railway deploy path onto a single **CI-explicit** mechanism, so we can safely retire Railway's registry auto-watch (the source of the surprise "Service aimock upgraded to latest" emails). Design proposal: [Notion — Showcase Deploy-Mechanism Consolidation](https://app.notion.com/p/3a33aa38185281e4b64cc5bebde92d91). ### What & why The "aimock upgraded to latest" email was never a per-service config choice — it was a **CI bug** letting Railway's watcher win a race: a Renovate PR that only touches `showcase_build.yml` forces a full-fleet rebuild; the LFS `shell` leg gets cancelled under runner contention; and the `redeploy-staging` guard (`needs.build.result != 'cancelled'`) then skipped the CI redeploy for the **whole fleet**, orphaning aimock's fresh digest for Railway's watcher to pick up. The `autoUpdates` setting itself had also silently drifted (24 services `minor` / 17 none) — tracked in no SSOT, gated by nothing. ### The four changes (one commit each) 1. **`fix(showcase)` — the P0 guard bug.** Relax the `redeploy-staging` **and** `redeploy-staging-starters` guards so a cancelled sibling leg no longer skips the fleet's staging redeploy; they now redeploy the already-computed successful-service list. A guard-evaluation test reads the live workflow `if:` strings and models GitHub's matrix rollup. 2. **`feat(showcase)` — autoUpdates SSOT (per-env, staging-first).** Add a **per-env** `autoUpdates` policy to every service in `railway-envs.ts` — **staging: `disabled`** (enforced), **prod: `unmanaged`** (left exactly as-is until a later migration). Regenerate `railway-envs.generated.json`. CI-explicit redeploy becomes the single deploy path on staging. 3. **`feat(showcase)` — drift gate.** New CI gate fails when a live Railway service's `autoUpdates` diverges from the SSOT. Reads `Environment.config` (autoUpdates isn't on the typed `ServiceSource` output), **enforces managed (`disabled`) envs and skips `unmanaged` ones** (so prod is untouched), **fails closed per-env** on zero-checked, and skips cleanly on fork PRs with no Railway token. 4. **`feat(showcase)` — scheduled reconcile.** CI-owned self-heal (every 15m) comparing each staging service's deployed digest against GHCR `:latest`, re-running the staging redeploy for lagging services and alerting Slack. Invariant: **green ⟺ every in-scope service confirmed current**; any unconfirmed service (lag, digest error, dropped redeploy, empty scope, thrown redeploy) alerts and exits non-zero. ### Verification - Every behavior change carries red-green tests; **230 tests pass**, `tsc` clean, `oxfmt`/`oxlint` clean, generated JSON in sync, workflows parse. - Reviewed via a full CR loop (Tier 3, 5 rounds to convergence); the reconcile's fail-loud invariant was hardened across rounds (silent-green holes, stale-digest ordering, expansion false-positives, test hygiene). ### Rollout (staging-first) - **Staging is flipped live as part of this change** — `autoUpdates` disabled on all staging services (snapshot-first, verified only `autoUpdates` changed). The drift gate now enforces staging. - **Prod is untouched** — its `autoUpdates` stay exactly as-is and the gate marks prod `unmanaged` (skipped). Migrating prod is a deliberate follow-up (flip prod live + change prod SSOT `unmanaged`→`disabled` together) once we're comfortable with staging on the new mechanism. No transition window where anything is unguarded. ### Follow-ups (from CR, non-blocking) - Dedup the reconcile alert's `unconfirmed` list by service key (cosmetic double-listing; exit code already correct). - Harden the sibling `notify-all-builds-failed`/`notify` jobs against the same all-legs-cancelled rollup (pre-existing, in a job this PR doesn't touch). - Minor: `postSlackAlert` try/catch belt; a few added test assertions; comment/doc accuracy. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
6b4b1ee179 |
fix(showcase): gate D4 chat probe completion on domDone to close empty-response render race (#6081)
## Incident
At ~21:19Z on 2026-07-20 the prod showcase Feature Matrix showed the
`claude-sdk-python` column whole-column-red (✓0/✗37). It self-recovered
by ~21:30Z. Not infra, not staleness: `/api/health` was 200 throughout,
`commErrorKinds` was empty, and the image was current.
## Root cause
The shared D4 chat probe (`d4-chat-roundtrip.ts`) emitted ONE transient
red — `failureSummary: "empty assistant response"`, no
`errorClass`/`errorDesc`. Because the dashboard's `resolveD4` is
**slug-keyed**, that single `chat:<slug>` row gated all 37 feature cells
for the slug → the whole column went red off one flap.
The producer-side origin is a **render race**. `readTurnComplete()`
gated turn completion on `domDone || sseDone`. `sseDone` is the
transport-level `RUN_FINISHED` counter — a synchronous raw-byte SSE
parse that runs *outside* React and can fire at-or-before the React
commit that actually renders the assistant text. `domDone` (the
`data-copilot-running` true→false DOM edge) is coalesced into that
*same* React commit (`use-agent.tsx`
`batchedForceUpdate`/`queueMicrotask`). Under contention, gating on
`sseDone` let the fast-fail grace window elapse while the DOM was still
empty, redding a turn that was about to render correctly.
## The change
`complete: domDone || sseDone` → `complete: domDone`. The moment
`domDone` is true, the assistant text is already in the DOM (same
commit). `observed` still includes `sseDone`, so a turn whose attribute
never appears still gets the wider polling window rather than
fast-failing. `FIRST_TOKEN_GRACE_MS` and `NON_COMPLETION_RETRY_LIMIT`
are untouched. `domDone` is not a new signal — the probe already
observes that edge via `readTurnState`; this only changes which existing
signal `complete` trusts.
**Throughput-neutral:** `runAttempt` returns on a non-empty
`readAssistantText` *before* `readTurnComplete` is consulted, so the
happy path is unchanged. Only a completed-but-empty turn's
classification changes. A genuinely-empty completed turn still reds
(correct) — only the race false-positive is removed.
## Red → green proof (deterministic, on the real code path)
A new deterministic timing-sim regression (`sseLeadMs` fixture)
decouples `sseDone` from `domDone` *in time* and drives the real
`readTurnComplete`/`runAttempt`. Two cases: a TIMING SIM (sseDone at
send, text at 2500ms past the ~2000ms grace window) and a FORMULA PIN
(sseDone-only, token never arrives → asserts `sends === 2`, catching a
literal revert to `domDone || sseDone` via the retry-fired
discriminator, not a wall-clock threshold).
**RED — test hunks applied on top of UNFIXED probe code:**
```
FAIL src/probes/drivers/d4-chat-roundtrip.test.ts > d4 render-race: sseDone-leads / domDone-lags turn completion (Fix B) > TIMING SIM: sseDone fires EARLY, domDone+text land LATE together → GREEN (RED pre-fix)
AssertionError: expected 'red' to be 'green' // Object.is equality
Expected: "green"
Received: "red"
❯ src/probes/drivers/d4-chat-roundtrip.test.ts:3195:21
FAIL src/probes/drivers/d4-chat-roundtrip.test.ts > d4 render-race: sseDone-leads / domDone-lags turn completion (Fix B) > FORMULA PIN: sseDone alone must NOT flip `complete` — a turn that only ever fires sseDone stalls+retries (does NOT fast-fail as completed-empty)
AssertionError: expected 1 to be 2 // Object.is equality
- Expected 2
+ Received 1
❯ src/probes/drivers/d4-chat-roundtrip.test.ts:3229:19
Test Files 1 failed (1)
Tests 2 failed | 82 skipped (84)
```
**GREEN — probe fix hunk then applied, same tests re-run:**
```
✓ src/probes/drivers/d4-chat-roundtrip.test.ts (84 tests | 82 skipped) 9026ms
✓ d4 render-race: sseDone-leads / domDone-lags turn completion (Fix B) > TIMING SIM: sseDone fires EARLY, domDone+text land LATE together → GREEN (RED pre-fix) 5017ms
✓ d4 render-race: sseDone-leads / domDone-lags turn completion (Fix B) > FORMULA PIN: sseDone alone must NOT flip `complete` — a turn that only ever fires sseDone stalls+retries (does NOT fast-fail as completed-empty) 4007ms
Test Files 1 passed (1)
Tests 2 passed | 82 skipped (84)
```
Full `d4-chat-roundtrip.test.ts` suite after the fix: **84 passed (84)**
— no regression.
## Real-probe forcing (Part B) — honest disclosure
The live race is low-rate and non-deterministic. The `--direct`
(direct-LLM) live-probe forcing path could **not** be stood up within
reasonable effort in this environment: Docker is running, but `--direct`
requires a real Anthropic API key that is not present here, and forcing
a probabilistic React-commit-timing race across ~300 reps on a
freshly-built `claude-sdk-python` integration stack is beyond a
reasonable bound for the bonus real-surface proof. It was **not** faked.
The deterministic timing-sim regression above exercises the real
`readTurnComplete`/`runAttempt` code path and is the gating proof.
**Recommendation:** a multi-tick staging dashboard watch post-merge to
confirm the flap does not recur.
## Scope note
This PR fixes the **producer-side** root cause. The **render-layer
de-amplifier** (a single transient D4 flap should degrade a slug's cells
to *amber*, not whole-column-red via slug-keyed `resolveD4` fan-out) is
owned separately by the dashboard-ladder redesign.
## CR follow-ups (post-review hardening)
Three small follow-ups from the 7-agent CR + adversarial verify (which
found zero live bugs in the Fix B core). The `complete: domDone` core is
untouched.
- **Regression-guard test (hardening).** New
`d4-probe-domdone-guard.test.ts` (source-level, TS AST). It asserts
every D4-probed demo page — `/demos/agentic-chat` for every integration,
`/demos/tool-rendering` where present — renders `<CopilotChat/>` in the
attribute-bearing **self-closing** form, not the children render-prop /
slot form. Only the self-closing form hits the `CopilotChatView` branch
that emits `data-testid="copilot-chat"` + `data-copilot-running`, which
the probe's `domDone` gate reads; the `if (children)` branch returns a
`display:contents` wrapper with neither, which would silently regress
the cell to the ~60s / double-send fallback. That regression is
invisible to the probe's own fake-injected unit tests, so it is guarded
structurally at CI time. Chose the source/AST approach over full page
render because the pages are Next.js `"use client"` app-router
entrypoints mounting a live `<CopilotKit runtimeUrl=.../>` provider +
framework agents (impractical to render faithfully in a harness unit
test).
**Red-green proof.** Temporarily mutated
`langgraph-python/agentic-chat/page.tsx` to the children render-prop
form → guard **RED** (`langgraph-python/agentic-chat … expected 0 to be
greater than or equal to 1`, 1 failed | 40 passed). Reverted the
mutation → **GREEN** (41 passed). Mutation fully reverted; net change is
the test only.
- **Comment factual fix.** In `d4-chat-roundtrip.ts` near the `complete:
domDone` gate: the comment said `observed` still includes `sseDone`
"(below)", but `observed` is defined *above*. Corrected "(below)" →
"(above)".
- **Timing-sim robustness pin.** The `TIMING SIM` test's discriminating
power depended on `FIRST_TOKEN_GRACE_MS (2000) < firstTokenDelayMs
(2500) < ceiling (~4000)`, encoded only in prose. Now imports the actual
`FIRST_TOKEN_GRACE_MS` source constant (newly exported) and asserts that
ordering, so raising the grace constant later fails the test loudly
instead of silently ceasing to discriminate. No existing assertion
weakened.
Quality: harness `d4-chat-roundtrip.test.ts` + new guard test green (125
tests); `tsc --noEmit` clean; `tsc -p tsconfig.build.json` clean; `oxfmt
--check` clean; `oxlint` warnings-only (no errors).
|
||
|
|
eddfcc07e8 |
docs(showcase): fix stale cross-reference in D4 probe comment
The comment near the complete:domDone gate said `observed` still includes `sseDone` "(below)" — but `observed` is defined ABOVE the comment, not below. Correct "(below)" to "(above)". |
||
|
|
4bf796b713 |
test(showcase): guard D4 probe domDone gate + pin first-token grace ordering
Add a source-level (TS AST) regression guard asserting every D4-probed demo page (/demos/agentic-chat always; /demos/tool-rendering where present) renders <CopilotChat/> in the attribute-bearing self-closing form, not the children render-prop form. The self-closing form is the CopilotChatView branch that emits data-testid="copilot-chat" + data-copilot-running, which the probe's domDone completion gate reads; the render-prop branch omits it, which would silently regress the cell to the ~60s/double-send fallback path. Invisible to the probe's own fake-injected unit tests, so guarded structurally at CI time. Also pin the TIMING SIM test's discriminating window against the actual FIRST_TOKEN_GRACE_MS source constant (now exported): assert FIRST_TOKEN_GRACE_MS < firstTokenDelayMs < attempt-0 ceiling so that raising the grace constant later fails the test loudly instead of silently ceasing to discriminate pre-fix vs post-fix. |
||
|
|
a33e313df8 |
feat(showcase): make autoUpdates per-env for staging-first rollout
autoUpdates is now per-env: staging is enforced "disabled" while prod is "unmanaged" (the drift gate skips it) so prod stays untouched until a later migration. The gate enforces managed envs and skips unmanaged ones; the zero-checked floor applies only to managed envs. Regenerates the SSOT JSON. |
||
|
|
50805ab47d |
docs: fix stale quickstart/link references and add MCP Codex setup (#6079)
## What & why Three independent documentation-accuracy fixes, batched into one PR. ### 1. Dead spec links + gen-ui page gaps (Closes #3975) `generative-ui-specs-overview.mdx` (rendered at `/whats-new/generative-ui-spec-support`) linked to the retired `/generative-ui/specs/<spec>` subgroup. Repointed to canonical destinations and added the frameworks list the issue asked for. **Verified live (HTTP status against docs.copilotkit.ai):** | Link | Before | After | | --- | --- | --- | | A2UI | `/generative-ui/specs/a2ui` (301 hop) | `/generative-ui/a2ui` → **200** | | MCP Apps | `/generative-ui/specs/mcp-apps` (301 hop) | `/generative-ui/mcp-apps` → **200** | | Open Generative UI (new) | — | `/generative-ui/open-generative-ui` → **200** | - **Supported Frameworks** list added — all 12 `/<slug>/quickstart` targets return **200** live (LangGraph Py/TS, Google ADK, MS Agent, AWS Strands, Mastra, PydanticAI, CrewAI, Agno, AG2, LlamaIndex, Claude Agent SDK, Deep Agents). - **Open-JSON-UI is intentionally NOT linked:** `/generative-ui/open-json-ui` is a placeholder pulled from the nav and redirected to `/generative-ui` on purpose (`next.config.ts` — `// AI-slop placeholder pulled from nav until properly authored`). Linking only the two specs that have live detail pages avoids sending readers to a redirect. Open-JSON-UI is still described in the comparison table on the page. - Open Generative UI is a CopilotKit capability (not an external spec), so it sits under "related capabilities." > Note: the issue/support-bot suggested a `/learn/...` path — there is no `/learn/` tree in shell-docs; the canonical homes are the flat `/generative-ui/<spec>` pages. ### 2. CLI `init` vs. existing app (Closes #2525) Maintainer resolution was "fix the docs." The original "`init` bootstraps your existing Next.js app" claim was already removed in the shell-docs migration (the reported `/direct-to-llm/guides/quickstart` now resolves to the Built-in Agent quickstart, which is fully manual). To remove the remaining ambiguity: - **Built-in Agent quickstart:** added an "Already have an app?" callout — existing apps skip `create-next-app`. - **CLI guide (`cli.mdx`):** clarified that `create` (aliased `init`) scaffolds a brand-new project in its own directory and does not detect/bootstrap an existing app; points to the manual install in the Quickstart. Verified against `copilotkit@latest` (4.3.0): `init --help` → *"Initialize a **new** CopilotKit project … before scaffolding"*, `-n, --name` *"names the local app **and its directory**"*, and `init`/`create` are aliases. ### 3. Codex setup for the MCP guide (Closes #2526) Added a **Codex** section to `mcp-server-setup.mdx` using the stdio `mcp-remote` bridge in `~/.codex/config.toml`, matching the page's existing command-based pattern (Cursor / Windsurf / Claude Desktop), plus the `codex mcp add` shortcut. Verified against the installed Codex CLI: `codex mcp --help` lists `add`/`list`/`get`/`remove`, and the `[mcp_servers.<name>]` table with `command`/`args` matches OpenAI's Codex config reference. Per the issue thread, the macOS `mcp-remote` port-blocking concern is **not** claimed to be solved — only the Codex config is documented. ## Testing - **#3975 (links):** curled every added/changed URL against the live docs — A2UI, MCP Apps, Open Generative UI, and all 12 framework quickstarts return **200**; confirmed `/generative-ui/open-json-ui` is a deliberate redirect (hence unlinked). Pre-existing `/ag-ui-protocol` and `/generative-ui` links are stable 301→200 and left as-is. - **#2525 (CLI):** ran `npx copilotkit@latest init --help` on the published `latest` (4.3.0) — confirmed new-directory scaffolding, no existing-app detection; verified `[Quickstart](/quickstart)` serves the manual-install page live (`create-next-app` + "Install CopilotKit packages"). - **#2526 (Codex):** ran `codex mcp --help` to confirm subcommands; new section reuses the file's existing `<Steps>`/fenced-code structure. `<Callout>` is a registered global MDX component (`src/lib/mdx-registry.tsx`), already used unimported on the Built-in Agent quickstart. - Docs-only; no code paths affected. Closes #3975 Closes #2525 Closes #2526 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
077913e5b4 |
feat(showcase): scheduled staging reconcile with Slack self-heal
Adds a CI-owned reconcile (every 15m) that compares each staging service's deployed digest against GHCR :latest and re-runs the staging redeploy for lagging services, alerting Slack. The run is green only when every in-scope service is confirmed current; any unconfirmed service (lag, digest error, dropped redeploy, empty scope, or a thrown redeploy) alerts and exits non-zero. Exposes per-service redeploy records from redeploy-env for accurate per-service remediation confirmation. |
||
|
|
b34debc02b |
feat(showcase): add autoUpdates drift gate against live Railway config
New CI gate fails when a live Railway service's autoUpdates diverges from the SSOT (every service must be disabled). Reads Environment.config (autoUpdates is not on the typed ServiceSource output), fails closed per-env when it verifies zero services, and skips cleanly on fork PRs that lack a Railway token. |
||
|
|
2ecba43d2f |
feat(showcase): track autoUpdates in SSOT, disabled fleet-wide
autoUpdates was tracked nowhere and had drifted (24 services minor / 17 none). Add an explicit disabled autoUpdates policy to every service in railway-envs.ts and regenerate railway-envs.generated.json, making CI-explicit redeploy the single deploy path instead of Railway's registry auto-watch. |
||
|
|
2d6883568e |
fix(showcase): don't skip staging redeploy when a build leg is cancelled
The redeploy-staging and redeploy-staging-starters jobs guarded on needs.build.result != 'cancelled', so a single cancelled matrix leg (e.g. the Git-LFS shell build under runner contention) skipped the whole fleet's staging redeploy even when the other 27 services built fine. Relax both guards to redeploy the already-computed successful-service list. Adds a guard-evaluation test that reads the live workflow if: strings and models GitHub's matrix rollup. |
||
|
|
cae8e78ac8 |
fix(showcase): gate D4 chat probe completion on domDone to close empty-response render race
readTurnComplete() gated turn completion on `domDone || sseDone`. sseDone (the
transport-level RUN_FINISHED counter) is a synchronous raw-byte parse OUTSIDE
React and can fire at-or-before the React commit that renders the assistant
text; domDone is coalesced into that SAME commit (use-agent.tsx
batchedForceUpdate / queueMicrotask). Gating on sseDone let the fast-fail grace
window elapse with the DOM still empty under contention, redding a turn that was
about to render correctly ("empty assistant response").
Gate completion on domDone alone. observed still includes sseDone, so a turn
whose attribute never appears still gets the wider polling window. Throughput-
neutral: runAttempt returns on non-empty readAssistantText before
readTurnComplete is consulted, so the happy path is unchanged; a genuinely-empty
completed turn still reds.
|
||
|
|
45898bd1b6 |
test(showcase): pin D4 chat probe sseDone-leads/domDone-lags render race
Add a deterministic timing-sim regression for the D4 chat-roundtrip probe render race. The sseLeadMs fixture decouples the transport RUN_FINISHED counter (sseDone) from the DOM run-stop edge (domDone) in time, so a turn whose sseDone fires early while domDone + assistant-text land later in the same React commit is exercised on the real readTurnComplete/runAttempt path. Two cases: a TIMING SIM (sseDone at send, text at 2500ms past the grace window) that reds pre-fix, and a FORMULA PIN (sseDone-only, token never arrives) asserting sends===2, catching a literal revert to domDone||sseDone via the retry-fired discriminator rather than a wall-clock threshold. |
||
|
|
7869d31e64 |
docs: drop unpublished Open-JSON-UI link, move Open Generative UI to related
CR: /generative-ui/open-json-ui is a deliberately-unpublished placeholder (next.config.ts redirects it to /generative-ui, pulled from nav until authored), so link only the two specs with live detail pages (A2UI, MCP Apps). Open Generative UI is a CopilotKit capability rather than an external spec, so it moves under related capabilities. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c9511cf4f9 |
docs: fix stale quickstart/link references and add MCP Codex setup
Fixes three independent documentation-accuracy issues: - #3975: repoint the retired /generative-ui/specs/<spec> links to the canonical flat /generative-ui/<spec> paths, add an Open Generative UI entry, and add a Supported Frameworks list on the Generative UI Spec Support page. - #2525: clarify that the CLI `create`/`init` scaffolds a brand-new project in its own directory and does not bootstrap an existing app; point existing-app users to manual installation. - #2526: add a Codex setup section to the MCP server guide using the stdio `mcp-remote` bridge, matching the page's existing pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
69e6be253a |
feat(showcase): add outcome reaction to promote-notify init Slack message (#6078)
## What & why The `showcase_promote_notify` workflow posts an init message to `#team-showcase`, then a threaded reply summarizing the run outcome. Operators wanted the *net* result visible at a glance on the original init message without clicking into the thread. This adds an emoji **reaction** to the original init message reflecting the run outcome via Slack's `reactions.add` API. ### Outcome → emoji mapping | `outcome` | reaction name | emoji | |-----------|---------------|-------| | `success` | `white_check_mark` | ✅ | | `partial` | `warning` | ⚠️ | | `total` | `x` | ❌ | ### Implementation notes - **`showcase_promote_notify.yml`** — after the thread reply, maps `outcome` → reaction name with a `case` and calls the existing `slack_api` helper with `reactions.add` (body `{channel, timestamp, name}` — note `timestamp`, not `ts`, for `reactions.add`). Guarded: only attempts when both `init_ts` and `init_channel_id` are non-empty (init post may have failed → skip with a `::warning::`). The reaction is informational, so a failed add is **warn-only** (`|| true`), mirroring the thread-reply idiom — NOT the fail-loud `#oss-alerts` page. - **`showcase_promote_notify.dry-run.sh`** — makes no real Slack calls, so it emits the reaction it *would* add as a `--- reactions.add ---` block. The `outcome`→reaction-name `case` is byte-identical to the `.yml`. - **`promote-notify.bats`** — new tests assert the emitted reaction name per fixture, plus a new anti-drift parity guard that the reaction mapping matches between the `.yml` and the `.sh` mirror (mirroring the existing `slack_alert_posted_ok` parity test). ## Red-Green Proof The real failure surface is the dry-run harness + bats suite. ### RED — before the change Dry-run on all three fixtures emits **no** `reactions.add` line (`grep -c` = 0 each): ``` ########## RED dry-run: success.json ########## -> grep reactions.add: 0 ########## RED dry-run: partial.json ########## -> grep reactions.add: 0 ########## RED dry-run: total-failure.json ##### -> grep reactions.add: 0 ``` New bats tests fail (existing 1–11 pass): ``` ok 11 call-site: both posts ok → zero exit, no warning not ok 12 reaction: success outcome adds white_check_mark to the init message not ok 13 reaction: partial outcome adds warning to the init message not ok 14 reaction: total-failure outcome adds x to the init message not ok 15 reaction: yml and sh reaction-name mapping is identical (anti-drift) ``` ### GREEN — after the change Dry-run emits the correct reaction per outcome: ``` ########## GREEN dry-run: success.json ########## --- reactions.add --- channel: #team-showcase (ts=<init_ts>) name: white_check_mark ########## GREEN dry-run: partial.json ########## --- reactions.add --- channel: #team-showcase (ts=<init_ts>) name: warning ########## GREEN dry-run: total-failure.json ########## --- reactions.add --- channel: #team-showcase (ts=<init_ts>) name: x ``` Full bats suite passes (15/15): ``` ok 1 slack_alert_posted_ok: ok:true response returns 0 and emits no warning ... ok 11 call-site: both posts ok → zero exit, no warning ok 12 reaction: success outcome adds white_check_mark to the init message ok 13 reaction: partial outcome adds warning to the init message ok 14 reaction: total-failure outcome adds x to the init message ok 15 reaction: yml and sh reaction-name mapping is identical (anti-drift) ``` `shellcheck .github/workflows/showcase_promote_notify.dry-run.sh` → clean (exit 0). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01F6rwtyRSUFtgiAY2dYuyU2 |
||
|
|
305cfd494b |
feat(showcase): add outcome reaction to promote-notify init Slack message
Add an emoji reaction to the original promote-notify init message reflecting the net run outcome, so operators can see success/failure at a glance without opening the thread reply: success -> white_check_mark (checkmark) partial -> warning total -> x The live workflow calls reactions.add on the init post (guarded on a successful init post, warn-only on failure to mirror the thread reply). The dry-run harness emits the reaction it would add, using a byte-identical case mapping enforced by a new anti-drift bats guard. Adds bats coverage asserting the emitted reaction name per fixture. |
||
|
|
999264f648 |
chore: bump aimock to 1.37.4 (multi-turn fixture matching fix) (#6071)
Bumps the showcase's `@copilotkit/aimock` dependency from `1.26.1` to **`1.37.4`** so the showcase's record/replay fixture stack picks up the multi-turn fixture-matching fix. **Why:** aimock v1.37.4 (aimock #319) fixes record/replay `hasToolResult` symmetry — recorded multi-turn fixtures now match on replay. The showcase records/replays LLM fixtures for its demo cells, so this pulls the fix into the showcase. **What changed** - `showcase/scripts/package.json`: `@copilotkit/aimock` `1.26.1` → `1.37.4` (exact pin, matching existing style) - `showcase/scripts/package-lock.json`: regenerated via `npm install --package-lock-only` (this lock is consumed by `npm ci` in the shell/shell-docs/shell-dashboard Dockerfiles) - `pnpm-lock.yaml`: regenerated via `pnpm install --lockfile-only` (showcase/scripts is a pnpm workspace member) The only version change is aimock (and its transitive tree in the npm lock). The minor eslint-config-next peer-resolution relabeling in `pnpm-lock.yaml` is benign normalization — no package version changes; both jiti variants already existed in the lock. **Validation gap:** this is a dependency bump only. Full validation (running the showcase multi-turn recording against 1.37.4 to confirm the recorded fixtures match on replay) requires the running showcase stack and is not exercised here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
107a17e493 |
fix(showcase): wire langroid declarative-gen-ui via Option A (JS-injected A2UI) (#6070)
## Summary Supersedes #6058's two-stage approach (outer `generate_a2ui` → secondary Python LLM call → `render_a2ui`), which severed across the prod streaming boundary. **Option A** (mirroring crewai-crews #6067): `injectA2UITool` defaults to `true`, so CopilotKit's `A2UIMiddleware` injects `render_a2ui` into `RunAgentInput.tools`. The langroid `agui_adapter.py` now merges those injected tools into its OpenAI call, so the LLM calls `render_a2ui` directly. The middleware intercepts the tool call stream, builds `a2ui_operations`, and fires `RUN_FINISHED` — no secondary Python LLM pass needed. ## Changes - **`agent.py`**: Remove ~550 lines of two-stage A2UI infrastructure (`generate_a2ui_via_llm`, `_a2ui_error`, `_resolve_a2ui_model`, `_get_a2ui_llm`, `_RENDER_A2UI_FUNCTION_SPEC`, etc.). Replace `GenerateA2UITool.handle` with a stub that logs loudly on regression (middleware should always intercept before reaching Python). - **`agui_adapter.py`**: Merge `run_input.tools` (AG-UI-injected) into the OpenAI tools list so `render_a2ui` is visible to the LLM. Remove `set_last_user_message` call (ContextVar no longer needed). - **`route.ts`**: Remove `injectA2UITool: false`; keep `defaultCatalogId` pin. - **`gen-ui-declarative.json`**: Replace 9 two-stage fixtures with 4 single-stage fixtures matching `toolName: render_a2ui` + `context: langroid`. ## Root cause of prior RED The langroid adapter builds its OpenAI tool list from `ALL_TOOLS` (Python-side registry) via `_get_openai_tools()`, which does NOT include `render_a2ui`. The `A2UIMiddleware` injects `render_a2ui` into `RunAgentInput.tools` at the AG-UI protocol level, but `agui_adapter.py` ignored `run_input.tools` entirely — so the LLM never saw `render_a2ui` in its tool list, never called it, and the fixture never matched. ## Red-green proof **RED** (from main, before changes): ``` ✗ d6:langroid/gen-ui-declarative red (0.0s) state=red 0 passed, 1 failed ``` **GREEN** (after this PR's changes, rebuild from worktree): ``` ✓ d6:langroid/gen-ui-declarative green (0.0s) 1 passed ✓ Tests passed for langroid:declarative-gen-ui ``` Test command: `bin/showcase test langroid:declarative-gen-ui --d6 --isolate --rebuild` ## Related - Supersedes #6058 (two-stage approach, now reverted in this integration) - Mirrors #6067 (crewai-crews Option A fix, same pattern) |
||
|
|
683ce92c5b | chore: bump aimock to 1.37.4 (multi-turn fixture matching fix) | ||
|
|
1196c7b637 |
test(showcase): align langroid test_generate_a2ui with Option A (JS-injected A2UI)
Option A removed the two-stage server-side A2UI planner (generate_a2ui_via_llm, _get_a2ui_llm, _resolve_a2ui_model, _A2uiError, _A2uiErrorKind, _RENDER_A2UI_FUNCTION_SPEC, _RENDER_A2UI_TOOL_SPEC, _a2ui_error). The CopilotKit JS runtime A2UIMiddleware now intercepts generate_a2ui before it reaches Python and drives the render_a2ui LLM pass itself. test_generate_a2ui.py was importing the removed symbols at collection time, causing an ImportError that failed CI's "Run showcase package Python unit tests" step for Python 3.12. Fix: remove all tests for the deleted two-stage planner infra; rewrite GenerateA2UITool.handle() tests to the Option A contract (handle fires only on middleware regression, returns structured error JSON, logs ERROR); retain all tests that remain valid: _ToolErrorKind enum identity, backend tool handle() happy+error paths, create_agent wiring, module hygiene (no openai at load time, clean subprocess import). |
||
|
|
46f810779c |
fix(showcase): wire langroid declarative-gen-ui via Option A (JS-injected A2UI)
Supersedes #6058's two-stage approach (outer generate_a2ui → secondary Python LLM call → render_a2ui), which severed across the prod streaming boundary. Option A: `injectA2UITool` (default true) causes CopilotKit's A2UIMiddleware to inject `render_a2ui` into RunAgentInput.tools. The langroid agui_adapter now merges those injected tools into its OpenAI call so the LLM can call render_a2ui directly. The middleware intercepts the tool call stream, builds a2ui_operations, and fires RUN_FINISHED — no secondary Python LLM pass needed. Changes: - agent.py: remove ~550 lines of two-stage A2UI infrastructure (generate_a2ui_via_llm, _a2ui_error, _resolve_a2ui_model, _get_a2ui_llm, etc.); replace GenerateA2UITool.handle with a stub that logs loudly if middleware interception regresses - agui_adapter.py: merge run_input.tools (AG-UI-injected) into the OpenAI tools list so render_a2ui is visible to the LLM; remove set_last_user_message call - route.ts: remove injectA2UITool: false; keep defaultCatalogId pin - gen-ui-declarative.json: replace 9 two-stage fixtures with 4 single-stage fixtures matching toolName: render_a2ui + context: langroid (mirrors crewai-crews Option A) Mirrors the crewai-crews fix from #6067. |
||
|
|
54eea138b8 |
fix(showcase): wire ag2 declarative-gen-ui via Option A (JS-injected A2UI) (#6069)
## Summary - **Route**: Drop `injectA2UITool: false` from `copilotkit-declarative-gen-ui/route.ts` — default `true` enables JS middleware injection - **Backend**: Replace the complex inner-LLM two-stage body in `a2ui_dynamic.py` with a fail-loud stub matching the crewai-crews Option A pattern (no more openai/AsyncOpenAI, no `_request_context`, no `tools/RENDER_A2UI_TOOL_SCHEMA`) - **Fixture**: Update `_meta` note and `_comment` fields to reflect Option A; fixture structure was already correct for aimock two-stage matching (outer `generate_a2ui` matched by `context:ag2`; inner `render_a2ui` matched by `toolName:render_a2ui`) ## Why Option A works AG2's AG-UI adapter has no Python-side A2UI injection. Option A routes the secondary LLM pass through the JS CopilotKit runtime middleware, which intercepts the agent's `generate_a2ui` toolcall, drives `render_a2ui` itself, synthesises the tool result, and fires `RUN_FINISHED`. This is the same pattern as the merged crewai-crews fix (#6067) and mirrors the green langgraph-python sibling. The previous two-stage backend approach failed under aimock because the backend's inner `AsyncOpenAI` call to `render_a2ui` bypassed aimock entirely (aimock only intercepts the frontend→backend path). ## Red → Green evidence **RED** (pristine, before changes): ``` ▸ Testing ag2:declarative-gen-ui (--d6)... ▸ Isolation active: project=showcase-iso19 slot=19 ✗ d6:ag2/gen-ui-declarative red (0.0s) state=red 0 passed, 1 failed ⚠ Tests failed for ag2:declarative-gen-ui (exit 1) ``` **GREEN** (after Option A changes): ``` ▸ Testing ag2:declarative-gen-ui (--d6)... ▸ Isolation active: project=showcase-iso20 slot=20 ✓ d6:ag2/gen-ui-declarative green (0.0s) 1 passed ✓ Tests passed for ag2:declarative-gen-ui ``` ## Files changed - `showcase/integrations/ag2/src/app/api/copilotkit-declarative-gen-ui/route.ts` — drop `injectA2UITool: false`, update header comment - `showcase/integrations/ag2/src/agents/a2ui_dynamic.py` — replace inner-LLM body with fail-loud stub - `showcase/aimock/d6/ag2/gen-ui-declarative.json` — update `_meta`/`_comment` for Option A 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
5a35379906 |
docs: add Thread & History Lifecycle guide (#5988)
## What & why
Adds a new framework-agnostic guide — **Thread & History Lifecycle**
(`docs/threads-lifecycle.mdx`) — that walks the full client-side
lifecycle of a conversation thread as one narrative. It fills the gap
between the [Headless Threads](/threads) how-to and the [Threads &
Persistence Architecture](/premium/threads-explained) explanation, and
directly answers a recurring cluster of support questions.
## Sections
- **How the threadId is created** — UUID v4, client-minted at mount, the
resolution precedence, and the remount-stability caveat (auto-minted ids
re-mint on remount / StrictMode; pass an explicit `threadId` for
continuity).
- **How history is restored** — explicit-`threadId` `connect()` replay
vs. manual `agent.setMessages(...)`; clarifies there is **no v2
`initialMessages`** and no v2 `useCopilotChat` (read via
`useAgent().agent.messages`).
- **Switching / starting threads** — `setActiveThreadId(id, { explicit
})` and `startNewThread()`, plus the prop-controlled no-op guard.
- **Creating a thread with your own API on first message** —
mint-up-front + `setActiveThreadId`/`threadId` prop as the robust path;
the headless `CopilotChatInput.onSubmitMessage` seam for submit-time
interception (noting the built-in `<CopilotChat>` overrides it).
- **CopilotKit threads vs. your framework's checkpointer** — two layers
correlated only by `threadId`; a LangGraph checkpointer creates
checkpoint tables, not a CopilotKit "threads" table.
- **MCP Apps activity & history** — activity messages are
frontend/middleware constructs (no server store); re-synthesize on
hydration.
- **v1 vs v2** disambiguation (incl. the two different `useThreads`
hooks).
## Addresses
Recurring thread-lifecycle questions: #4790, #4778, #5434, #2242, #5931.
(I'll close those pointing here once this lands.)
## Testing / accuracy
All referenced APIs verified present on `main`: `useThreads` (v2),
`useCopilotChatConfiguration` (`setActiveThreadId`/`startNewThread`),
`useAgent().agent.setMessages/addMessage`,
`CopilotChatInput.onSubmitMessage`, `<CopilotChat threadId>`, and the
`mcp-apps` activity type. All five cross-doc links resolve. Added to the
"Threads" group in `docs/meta.json`.
Note: written against the current v2 APIs — three details were corrected
against source during authoring (`startNewThread` not `createThread`;
`onSubmitMessage` is headless-only; no v2
`useCopilotChat`/`initialMessages`).
|
||
|
|
0408f821a7 |
fix(showcase): wire ag2 declarative-gen-ui via Option A (JS-injected A2UI)
Remove the backend two-stage inner-LLM pattern (injectA2UITool:false + Python-side secondary openai call) in favour of Option A: the CopilotKit JS runtime middleware intercepts the agent's no-arg generate_a2ui toolcall and drives the render_a2ui secondary LLM pass itself, synthesising the tool result and firing RUN_FINISHED. Matches the just-merged crewai-crews fix (#6067) and mirrors langgraph-python's green reference pattern. Changes: - route.ts: drop `injectA2UITool: false` (default true enables JS injection) - a2ui_dynamic.py: replace complex inner-LLM body with a fail-loud stub (no more openai/AsyncOpenAI import, no _request_context dependency, no tools/RENDER_A2UI_TOOL_SCHEMA import) - gen-ui-declarative.json: update _meta note + _comment fields to reflect Option A (fixture structure was already correct for two-stage aimock matching; outer generate_a2ui matched by context:ag2, inner render_a2ui matched by toolName:render_a2ui) Red→Green: D6 control-plane harness confirmed red before (state=red, exit 1) and green after (1 passed, exit 0). |
||
|
|
8d9e077b63 |
fix(showcase): wire crewai-crews declarative-gen-ui via Option A (JS-injected A2UI) (#6067)
## Summary
- **route.ts**: remove `injectA2UITool: false` (enables JS middleware
injection); keep `defaultCatalogId: "declarative-gen-ui-catalog"` to
prevent "Catalog not found" render errors when models omit `catalogId`.
- **declarative_gen_ui.py**: replace `GenerateA2uiTool` (Python body
that would drive a secondary LLM pass — impossible in `ag_ui_crewai`,
which has no `CopilotKitMiddleware`) with a no-arg
`_GenerateA2uiNoArgTool` that raises loudly if called directly. The JS
`A2UIMiddleware` intercepts the `generate_a2ui` toolcall before Python
ever sees it and drives the secondary `render_a2ui` pass itself.
- **definitions.ts + renderers.tsx**: add `DataTable` component
(columns/rows schema + `declarative-data-table` testid); add missing
`declarative-info-row` testid to `InfoRow`. Both were present in the
`langgraph-python` reference catalog but absent from `crewai-crews`.
Required by D5 harness turns 2 and 4.
- **gen-ui-declarative.json**: rewrite D6 aimock fixtures from broken
two-stage pattern (`generate_a2ui` → inner `render_a2ui`) to correct
single-stage (LLM calls `render_a2ui` directly). `ag_ui_crewai` has no
Python-side injection; the middleware's `complete()` synthesizes the
tool result and fires `RUN_FINISHED` after streaming all
`TOOL_CALL_CHUNK` events through AG-UI.
## Architecture note
`langgraph-python` uses **two-stage A2UI**: outer `generate_a2ui` →
Python `CopilotKitMiddleware` drives inner `render_a2ui`. `crewai-crews`
uses **single-stage**: JS `A2UIMiddleware` intercepts `generate_a2ui`
(toolcall in the agent's tool list), fires a secondary LLM call itself
passing `render_a2ui` in the tools list, the LLM calls `render_a2ui`
directly, and `ag_ui_crewai` streams the `TOOL_CALL_CHUNK` events
through AG-UI to the middleware.
## RED → GREEN proof
**RED (main, `injectA2UITool: false`):**
```
[conversation-runner] turn 1/4 — FAILED {
error: 'waitForTurnComplete: turn 1 did not complete within 90000ms
(reason=surface-missing, runsFinished=1, ...)'
```
Every pill: "CrewAI flow failed; see server logs" — `render_a2ui` not in
the agent tool list, aimock fixture never fired, no surface painted.
**GREEN (this branch):**
```
[conversation-runner] turn 1/4 — assertions passed
[conversation-runner] turn 2/4 — assertions passed
[conversation-runner] turn 3/4 — assertions passed
[conversation-runner] turn 4/4 — assertions passed
[conversation-runner] conversation completed successfully { turnsCompleted: 4, totalDurationMs: 8524 }
✓ d6:crewai-crews/gen-ui-declarative green (0.0s)
1 passed
```
All 4 pills pass with full component-tree assertions
(Metric×4+PieChart+BarChart / DataTable+BarChart /
StatusBadge×3+Metric×3 / InfoRow×7+PieChart).
## Adapter-forwarding value-test
Turn 1 passing confirms the full Option A path end-to-end:
`generate_a2ui` in tool list → JS `A2UIMiddleware` fires secondary
`render_a2ui` LLM pass → aimock returns component tree → `ag_ui_crewai`
streams `TOOL_CALL_CHUNK` events → middleware builds `a2ui_operations` →
frontend catalog renders
`declarative-metric`/`declarative-pie-chart`/`declarative-bar-chart`
testids with correct counts.
|
||
|
|
73f7f88f22 |
fix(ci): deploy starter-only changes (showcase_build redeploy gap) (#6068)
## The incident PR #6061 (`fix(starters): add python-multipart to agno starter`) merged, its `starter-agno` image built to GHCR via `showcase_build.yml`'s `build-starters` job — and then was **never deployed to Railway**. `starter-agno` stayed crashed until someone manually redeployed. That is not a one-off: it is structural. Any change touching **only** starter files (`examples/integrations/<slug>/**`) hits the same hole. ## Root cause (job-graph) `showcase_build.yml` has two independent lanes: | Lane | Detect | Build | Aggregate | Redeploy | | --- | --- | --- | --- | --- | | **Showcase fleet** | `detect-changes` | `build` (matrix) | `aggregate-build-results` | `redeploy-staging` | | **Starters** | `detect-starter-changes` | `build-starters` (matrix) | — | **(none)** | `redeploy-staging` is scoped **entirely to the showcase fleet**: ``` redeploy-staging: needs: [detect-changes, build, aggregate-build-results] if: >- !cancelled() && needs.detect-changes.outputs.has_changes == 'true' && needs.build.result != 'skipped' && needs.build.result != 'cancelled' && needs.aggregate-build-results.outputs.any_success == 'true' ``` (`.github/workflows/showcase_build.yml:780-799`) On a **starter-only** push: - `detect-changes` filters are all `showcase/**` paths → none match `examples/integrations/**` → `has_changes=false` → `build` skips → `aggregate-build-results` skips → `redeploy-staging` skips (fails its `has_changes=='true'` and `build.result != 'skipped'` clauses). - `detect-starter-changes` matches → `build-starters` builds `starter-<slug>:latest` to GHCR — **and stops.** There is no aggregate and **no redeploy job for the starter lane at all.** Net: the starter image is built and pushed, but nothing ever calls Railway's `serviceInstanceRedeploy`, so the running container keeps the stale image. The `starter-build-result-*` per-slot artifacts already emitted by `build-starters` were **write-only** — nothing consumed them. ## The fix A new `redeploy-staging-starters` job that mirrors `redeploy-staging` for the starter lane, reusing existing mechanisms (no new script, no new aggregator): ``` redeploy-staging-starters: needs: [detect-starter-changes, build-starters] if: >- !cancelled() && needs.detect-starter-changes.outputs.has_changes == 'true' && needs.build-starters.result != 'skipped' && needs.build-starters.result != 'cancelled' ``` Steps: 1. Download the already-emitted `starter-build-result-*` artifacts (`pattern` download → succeeds with zero matches if the build crashed before writing any). 2. Compute `matrix ∩ build-success`: read each per-slot `{"service":"<raw slug>","status":...}`, keep `status:success` slugs, map each **raw slug → `starter-<slug>` SSOT key** via the starter matrix `.image` field. (The raw slug must NOT be passed to `redeploy-env.ts` — e.g. `"agno"` collides with the **showcase** `agno` dispatch_name.) 3. If the CSV is non-empty, `npx tsx showcase/scripts/redeploy-env.ts staging --services <csv>` — the exact same invocation `redeploy-staging` uses. `redeploy-env.ts` already resolves each `starter-<slug>` as an SSOT key (verified: all 12 starter `.image` values exist as keys in `railway-envs.ts`). **Deploy-on-failure is impossible:** the "don't deploy on build failure" guard is the per-slot success intersection, not the job `if:`. `build-starters` `result == 'failure'` still enters the job (fail-fast is false, so some slots may have succeeded), but only `status:success` slots are redeployed; an all-failed or crashed build yields an empty CSV → the redeploy step is skipped. This is the same net guarantee `redeploy-staging` gets from its `any_success` guard, computed inline to avoid standing up a second aggregator job. **No `redeploy-summary` artifact is written** by this job on purpose: that name is owned by `redeploy-staging` and downloaded by `showcase_deploy.yml` by exact name — a second same-named upload would collide on a combined push. Starter *staging verification* is intentionally out of scope for this deploy-gap fix (starters are already smoke-covered by `test_smoke-starter.yml` and the harness `starter_smoke` axis). `redeploy-staging-starters` was also added to the `notify` job's `needs` so a starter redeploy failure alerts. ## Before / after truth table | Scenario | `build` | `redeploy-staging` | `build-starters` | `redeploy-staging-starters` | | --- | --- | --- | --- | --- | | **(a) main-fleet-only change** | runs | **redeploys fleet** | skipped | skipped | | **(b) starter-only change** | skipped | skipped | runs | **redeploys starter (NEW)** | | **(c) both changed** | runs | **redeploys fleet** | runs | **redeploys starter (NEW)** | | **(d) starter build failure** | (n/a) | (n/a) | failure | runs, but CSV empty → **no redeploy** | - **(a)** unchanged — the showcase lane is untouched. - **(b)** is the fix: the starter now auto-deploys instead of sitting on GHCR. - **(c)** unchanged for the fleet; the starter additionally deploys. No artifact collision because `redeploy-staging-starters` uploads no `redeploy-summary`. - **(d)** partial failure redeploys only the slots that succeeded; a wholesale failure redeploys nothing. ## Fail-loud hardening (CR follow-up) A CR flagged that the new `redeploy-staging-starters` job could itself **silently under-deploy** — re-opening the very hole it exists to close. Two guards added to the `Compute successfully-built starter services` step, mirroring the sibling `redeploy-staging` job's empty-intersection guard: 1. **Empty deploy-set → fail loud.** When `build-starters.result == 'success'` (all slots built) but the `matrix ∩ success` CSV is **empty**, the job now `exit 1`s with an actionable `::error::` instead of silently skipping the redeploy at green CI (a slug↔`.image` contract skew, or a success set that maps to no matrix entry). Gated on `'success'` so a partial/total build **failure** keeps the legitimate no-deploy path and is not double-reported — that failure is already surfaced by `build-starters` itself. 2. **Missing/unreadable result artifact → fail loud.** Dropped `2>/dev/null` on the `result.json` read so a read error surfaces and trips `pipefail`, and added a parsed-record-count vs matrix-slot-count assertion (every slot writes a `result.json` via `if: always()`, so on a full-success build the counts must match). A missing/expired `starter-build-result-*` artifact — which would otherwise silently drop a built starter from the redeploy set — now fails the job. Also gated on `'success'` so a crashed slot's legitimately-absent artifact isn't double-reported on a build failure. Updated truth table with the new fail-loud row: | Scenario | `build-starters.result` | deploy CSV | `redeploy-staging-starters` | | --- | --- | --- | --- | | success + non-empty CSV | success | non-empty | **redeploys** | | success + **empty** CSV | success | empty | **exit 1 (NEW fail-loud)** | | build failure (partial) | failure | subset | redeploys successful subset, no spurious exit | | build failure (all/crash) | failure | empty | no deploy, failure surfaced, no spurious exit | | skipped (no starter changes) | skipped | (n/a) | job `if:` excludes it — never runs | Both guards were locally red→green exercised: pre-fix the empty-CSV and missing-artifact cases went **green with nothing/partial deployed**; post-fix they `exit 1`. actionlint still 9/9 (no new findings). ## actionlint Clean. Baseline (`origin/main`) = 9 findings; this branch = 9 findings, all at pre-existing lines (custom `depot-*` runner label + pre-existing SC2086 infos). **Zero new findings** from the added job. The `matrix ∩ success` jq mapping was locally exercised across the four scenarios above (success/partial/all-fail/ crash-before-artifacts) and produced the expected CSVs. ## Prod-promote path **Does NOT share the gap.** `showcase_promote.yml` is `workflow_dispatch`-only ("Humans trigger. No automatic prod promotes.") and already lists all 12 `starter-*` services in its choices with `resolve-targets` handling them. There is no push-driven prod path to fix. ## Residual verification (honest note) Workflows can't be safely dry-run end-to-end (the redeploy path hits live Railway). Static validation is complete (actionlint clean, jq logic exercised, all 12 starter SSOT keys confirmed, `redeploy-env.ts` reused unchanged), but the true end-to-end confirmation is the **next starter-only change auto-deploying to staging**. That first real starter-only merge after this lands should be watched to confirm `redeploy-staging-starters` fires and the Railway service picks up the new image. --- Draft — do not merge until reviewed. |
||
|
|
b79a561fd7 |
ci: fix zizmor ref-version-mismatch on starter-redeploy checkout pin
The new redeploy-staging-starters job pinned actions/checkout to 9c091bb (tag v7.0.0) but commented it # v7. zizmor's ref-version-mismatch flagged the discrepancy: the v7 moving tag points to 3d3c42e, not 9c091bb. Repin to 3d3c42e # v7 — the canonical checkout pin already used across every other job on main — so the comment matches the SHA's tag. |
||
|
|
6fba48faa9 |
docs(threads): reorder Threads nav and tighten lifecycle/architecture boundary
Addresses CR on the Thread & History Lifecycle guide (PR #5988): - Reorder the Threads navigation consistently across the root and all authored-framework meta.json files to: Overview, Threads Drawer, Headless Threads, Import Thread History, Threads & Persistence Architecture, Thread & History Lifecycle. Re-anchor the injected Architecture page before the Lifecycle page and update the nav-order test. - Tighten the two-page boundary: Architecture now owns platform behavior (persistence, replay, realtime sync, locks, failure modes) and defers client-side steps to Lifecycle; Lifecycle keeps only brief persistence context and links to Architecture for the deeper model. - Writing pass reducing heavy em-dash use in both pages, keeping em dashes only in link-gloss lists and table placeholders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
29ce611db1 |
ci: fail loud on starter redeploy silent-under-deploy holes
The redeploy-staging-starters job could go green while deploying nothing (all starters built, empty deploy CSV) or silently drop a built-but-missing starter from the redeploy set. Both re-open the exact gap this job exists to close. - Finding 1: when build-starters.result == 'success' but the matrix ∩ success CSV is empty, fail the job (exit 1) instead of silently skipping the redeploy. Mirrors redeploy-staging's empty-intersection guard. Gated on 'success' so a partial/total build FAILURE keeps the legitimate no-deploy path and isn't double-reported. - Finding 2: drop 2>/dev/null on the result.json read (surface read errors via pipefail) and assert parsed-record count == matrix slot count when all slots built, so a missing/expired starter-build-result-* artifact fails loud instead of silently under-deploying. |
||
|
|
769cd9daf0 |
chore(deps): update github actions (#6065)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [actions/checkout](https://redirect.github.com/actions/checkout) ([changelog](https://redirect.github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0..3d3c42e5aac5ba805825da76410c181273ba90b1)) | action | digest | `9c091bb` → `3d3c42e` | | [actions/setup-python](https://redirect.github.com/actions/setup-python) | action | major | `v6.3.0` → `v7.0.0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/592) for more information. --- ### Release Notes <details> <summary>actions/setup-python (actions/setup-python)</summary> ### [`v7.0.0`](https://redirect.github.com/actions/setup-python/compare/v6.3.0...v7.0.0) [Compare Source](https://redirect.github.com/actions/setup-python/compare/v7.0.0...v7.0.0) ### [`v7`](https://redirect.github.com/actions/setup-python/compare/v6.3.0...v7.0.0) [Compare Source](https://redirect.github.com/actions/setup-python/compare/v6.3.0...v7.0.0) </details> --- ### Configuration 📅 **Schedule**: (in timezone America/Los_Angeles) - Branch creation - "before 9am every weekday" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/CopilotKit/CopilotKit). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNjUuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI3Mi40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> |
||
|
|
1cc66e6dea |
fix(ci): deploy starter-only changes (showcase_build redeploy gap)
A push touching only starter files (examples/integrations/<slug>/**) built a fresh starter-<slug>:latest image to GHCR via build-starters but never redeployed it to Railway: the starter lane (detect-starter-changes -> build-starters) ended at the GHCR push, and redeploy-staging only covers the showcase build lane. Starter fixes sat undeployed until a manual redeploy (the #6061 agno incident). Add a redeploy-staging-starters job that mirrors redeploy-staging for the starter lane: it reads the already-emitted per-slot starter-build-result-* artifacts, intersects the build matrix with the build-success set (mapping raw slug -> starter-<slug> SSOT key), and redeploys only the successfully-built starters to staging. No deploy on build failure (empty success set -> redeploy step skipped). Reuses redeploy-env.ts unchanged. |
||
|
|
f44cb57669 |
fix(showcase): wire crewai-crews declarative-gen-ui via Option A (JS-injected A2UI)
Switch the crewai-crews gen-ui-declarative cell from a broken Option B (Python-side injection, which has no mechanism in the ag_ui_crewai adapter) to Option A (JS-runtime-injected A2UI): - route.ts: remove `injectA2UITool: false`; keep `defaultCatalogId` to pin the catalog so models that omit catalogId don't get a "Catalog not found" render error. - declarative_gen_ui.py: replace `GenerateA2uiTool` with a no-arg `_GenerateA2uiNoArgTool` that raises loudly if called directly (the A2UIMiddleware should always intercept before Python). - definitions.ts + renderers.tsx: add `DataTable` component (columns/rows schema + `data-testid="declarative-data-table"` renderer); add missing `data-testid="declarative-info-row"` to the `InfoRow` renderer. Both testids are required by the D5 harness (turns 2 and 4 respectively) and were present in the langgraph-python reference catalog but absent here. - gen-ui-declarative.json: rewrite D6 aimock fixtures from the old broken two-stage pattern (generate_a2ui → inner render_a2ui) to the correct single-stage pattern (LLM calls render_a2ui directly); all four pills now match `toolName: render_a2ui, context: crewai-crews` and return full component trees that satisfy the harness minCounts assertions. RED (main): "CrewAI flow failed; see server logs" on every pill — `injectA2UITool: false` disabled the middleware; no render_a2ui tool in the agent's tool list; aimock fixture matcher never fired; no surface. GREEN (this branch): all 4 turns pass with assertions, 1 passed (0.0s). |
||
|
|
cd76f12980 | chore(deps): update github actions | ||
|
|
7fb0abf60e |
fix(vue): keep attachment sources structuredClone-safe (#5933)
## What does this PR do? Fixes a Vue `DataCloneError` that occurred when uploaded attachment sources crossed the `structuredClone` boundary in core. Vue’s deep `ref()` conversion wrapped nested attachment sources in reactive proxies; `useAttachments` now keeps the attachment container shallow with `shallowRef()`, preserving externally supplied sources as raw cloneable values before they reach AG-UI/core payloads. The change is intentionally Vue-only: core and React are untouched because the defect is caused by Vue’s reactivity behavior at the framework boundary. Focused regressions cover both the attachment hook and `CopilotChat` submission path, including non-reactivity and successful `structuredClone` behavior. ## Related PRs and Issues - [CopilotKit issue #3](https://github.com/enekesabel/CopilotKit/issues/3) ## Verification - `pnpm nx run @copilotkit/vue:check-types` — passed. - `pnpm nx run @copilotkit/vue:test -- src/v2/hooks/__tests__/use-attachments.test.ts src/v2/components/chat/__tests__/CopilotChat.attachments.test.ts` — passed, 18 tests in 2 files. - `pnpm nx run @copilotkit/vue:build` — passed. - Pre-commit package gate (`test-and-check-packages`) — passed: 1073 tests, publint, and attw. - `pnpm nx run @copilotkit/vue:lint` — remains blocked by 171 pre-existing errors across unrelated Vue files; no lint errors were introduced in the changed files. - `git diff --check upstream/main...HEAD` — passed. ## Scope and exclusions - Changed files are limited to `packages/vue/src/v2/hooks/use-attachments.ts`, its focused hook and `CopilotChat` tests, and the related `packages/vue/PARITY.md` and `packages/vue/AGENTS.md` guidance. - No core, React, workflow, or package-wide lint cleanup is included. - The `PARITY.md` change removes accidental table-format churn and retains only the meaningful attachment parity note. ## Checklist - [x] I have read the [Contribution Guide](https://github.com/CopilotKit/CopilotKit/blob/main/CONTRIBUTING.md) - [x] 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) |
||
|
|
99ae28804b |
fix(vue-demo): use v2 entrypoint and BuiltInAgent (#5960)
## What does this PR do? Updates the Vue v2 demo to consistently use current v2 APIs: - imports all Vue demo pages, including the A2UI catalog page, from `@copilotkit/vue/v2`; - replaces the demo runtime's deprecated `BasicAgent` instances with `BuiltInAgent`. This is the focused, still-applicable demo correction recovered from #5176. It does not change package exports, public APIs, documentation, or unrelated demo behavior. ## Related PRs and Issues - Extracts the Vue demo portion of #5176. ## Verification - `pnpm nx run @copilotkit/vue-demo:lint` passed. - Manual Vue demo smoke testing passed using the branch's Nx dev server. - `pnpm nx run @copilotkit/vue-demo:build` completed Nuxt client and server compilation locally, but did not exit during Nitro finalization and was stopped after producing no further output. - Vue package suite: 1,069 tests passed; two unrelated existing 5-second timeout failures occurred in `CopilotChatMessageView` activity rendering and `CopilotSidebarView` measured-margin coverage. - `git diff --check` passed. ## Checklist - [x] I have read the [Contribution Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md) - [x] Documentation is not required because this only corrects the existing v2 demo's imports and deprecated runtime construction. - [x] "Allow edits by maintainers" is checked (lets us help iterate on the PR directly). |
||
|
|
bdd3a8232d | Merge branch 'main' into claude/brave-kirch-8dbf00 | ||
|
|
30cc551a9b |
docs(langgraph,crewai-flows): remove broken useCopilotContext example (#5821)
## What does this PR do? Removes the "Using setThreadId" example from the LangGraph and CrewAI Flows persistence docs. That example calls `useCopilotContext()`, which is a v1-only hook not exported from `@copilotkit/react-core/v2` — following the example as written throws a module resolution error for v2 users. The preceding "Dynamically Switching Threads" section on the same page already documents the correct, working pattern (plain React state + the `threadId` prop on `<CopilotKit>`), so removing the broken section doesn't leave a gap. ## Related PRs and Issues Closes #3860 ## Files changed - `showcase/shell-docs/src/content/docs/integrations/langgraph/advanced/persistence/loading-message-history.mdx` - `showcase/shell-docs/src/content/docs/integrations/crewai-flows/persistence/loading-message-history.mdx` ## Checklist - [x] I have read the [Contribution Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md) - [x] Docs-only change; no functionality updated - [x] Allow edits by maintainers |
||
|
|
e91ae56372 |
docs(shell-docs): emit canonical versioned URLs for reference pages in llms-full.txt (#5486)
## What does this PR do? `getAllLlmPages()` in `llm-text.ts` previously walked `src/content/reference/` directly and emitted reference pages at `reference/<slug>` (e.g. `reference/hooks/useCopilotAction`). The live site serves those pages at their versioned canonical URL — `/reference/v2/hooks/useCopilotAction` for the current v2 API — so `llms-full.txt` contained non-canonical source URLs that diverged from what users see in the browser. **Root cause:** The v2 API reference lives at the _root_ of `src/content/reference/` (no `v2/` subfolder), so a bare filesystem walk cannot distinguish v2 from older SDK versions. It emits `reference/hooks/foo` instead of the correct `reference/v2/hooks/foo`. **Fix:** Replace step 3 with an enumeration via `loadReferenceVersionItems` (which already knows the canonical URL per version) and `resolveReferencePage` (which resolves the content file path). This matches the URL scheme used by the `/reference/[...slug]` route handler. **Result:** - v2 hooks/components now appear at `reference/v2/hooks/...` in `llms-full.txt` - v1, react-native, core, and bot pages appear at their correct versioned prefixes - Version root index pages (`reference/v2`, `reference/v1`, ...) are included - The migration guide (`migrate/v2`) was already included via the docs walk (step 1 unchanged) ## Related PRs and Issues - Closes #3385 ## Checklist - I have read the Contribution Guide - If the PR changes or adds functionality, I have updated the relevant documentation - "Allow edits by maintainers" is checked |
||
|
|
1ae19fd05c |
fix(showcase/mastra): wire a2ui-fixed-schema Book flight button
The A2UI Button renderer was inert - it rendered the label but never wired the schema 'action' or a click handler, so 'Book flight' did nothing despite the narration telling users to tap it to confirm. Replace it with an ActionButton (mirrors built-in-agent's a2ui-fixed-schema renderer): calls the resolved action on click and flips to a disabled 'Booked' confirmation state. Verified with Playwright (next-dev build of the fixed renderer). |
||
|
|
63d3400819 | style: auto-fix formatting | ||
|
|
dd8495bb25 |
fix(showcase/mastra): render declarative-gen-ui + wire hashbrown/json-render demos
declarative-gen-ui (render-a2ui.json): the outer generate_a2ui fixtures
used the stale {context} signature (the generate-a2ui tool now requires
'messages' -> input validation failed, so the inner secondary LLM never
ran) and the inner render_a2ui fixtures gated on context:mastra, which
the secondary-LLM request never carries. Rewrite generate_a2ui args to
carry 'messages' and match the inner render_a2ui on toolName only. All
four pills (KPI, pie, bar, status) now render their A2UI surface.
declarative-hashbrown / declarative-json-render (page.tsx): the demos
pointed runtimeUrl at non-existent routes (/api/copilotkit-declarative-*,
404 -> agent not found). Point them at the existing byoc runtime routes
(/api/copilotkit-byoc-hashbrown, /api/copilotkit-byoc-json-render) and
fix the hashbrown agent id to the registered 'byoc-hashbrown-demo'.
Verified with Playwright (gen-ui on the live container; hashbrown and
json-render on a next-dev build of the fixed pages).
|
||
|
|
bc2916efaa |
fix(runtime-client-gql): guard abort errors without message (#5437)
Fixes #2596 ## Summary `@copilotkit/runtime-client-gql` was still assuming abort-shaped errors always expose a string `message`, which could turn an early-stop path into a secondary TypeError instead of a clean abort suppression or the original failure. This branch centralizes abort detection behind a null-safe helper and keeps the existing abort phrases unchanged. ## Changes - Replaced the duplicated abort checks in `packages/runtime-client-gql/src/client/CopilotRuntimeClient.ts` with a shared `isAbortError(unknown)` helper that only inspects string messages. - Added focused regression coverage in `packages/runtime-client-gql/src/client/__tests__/CopilotRuntimeClient.test.ts` for a string abort cause, an object without `message`, known abort suppression in the stream path, and non-abort stream errors surfacing normally. - Added `.changeset/guard-abort-error-message.md` for the patch release note. ## Scope Only `packages/runtime-client-gql` and its local changeset are touched. The abort phrases, caller-facing API, structured GraphQL error handling, and stream close behavior for known aborts are unchanged. ## Test Plan - [x] `npx nx run @copilotkit/runtime-client-gql:graphql-codegen` - regenerated the package GraphQL artifacts used by the client imports. - [x] `pnpm run build` - workspace build completed successfully. - [x] `pnpm -C packages/runtime-client-gql exec vitest run src/client/__tests__/CopilotRuntimeClient.test.ts` - 4/4 passed. Covers a string abort cause in the fetch path, an object with no `message` in the fetch path, known abort suppression in the stream path, and non-abort stream errors surfacing. - [x] `pnpm exec oxfmt --write packages/runtime-client-gql/src/client/CopilotRuntimeClient.ts packages/runtime-client-gql/src/client/__tests__/CopilotRuntimeClient.test.ts` - formatted the touched source and test files. - [x] `pnpm exec oxlint packages/runtime-client-gql/src/client/CopilotRuntimeClient.ts packages/runtime-client-gql/src/client/__tests__/CopilotRuntimeClient.test.ts` - 0 warnings, 0 errors. |
||
|
|
6299420e20 |
fix(showcase/mastra): render Sales Dashboard + Search Flights A2UI in beautiful-chat
Sales Dashboard (A2UI Dynamic) never produced a surface: the outer
generate_a2ui fixture carried empty {} args (generate_a2ui input
validation failed on the required 'messages' field) and the inner
render_a2ui fixture gated on context:mastra, which the secondary-LLM
request never carries. Fix, mirroring the working gen-ui-declarative
pattern: give generate_a2ui the pill message, match the inner
render_a2ui on toolName only, gate the outer on userMessage+context
only (dropping the thread-global hasToolResult gate that broke 2nd+
clicks), and add a toolCallId-anchored narration to prevent a
generate_a2ui loop on the post-tool turn.
Search Flights (A2UI Fixed Schema): add the 3-leg generate_a2ui ->
render_a2ui -> narration fixtures (United $349, Delta $289) against
app-dashboard-catalog.
Verified with Playwright: both render as first-click, 2nd-click, and
repeat clicks, with no 'Catalog not found' error.
|
||
|
|
93c7369e85 |
Merge remote-tracking branch 'origin/main' into claude/brave-kirch-8dbf00
# Conflicts: # showcase/scripts/__tests__/aimock-fixtures.test.ts |
||
|
|
ef6e7d149e |
fix(showcase/mastra): return objects from tool-rendering tools (double-encode)
weatherTool/stockPriceTool/searchFlightsTool/rollDiceTool/queryDataTool returned
JSON.stringify(...); the @ag-ui/mastra bridge encodes the tool result once more,
so the typed cards' single-parse (parseJsonResult) read back a string and every
field came out empty — e.g. the weather card showed "Humidity--%". Return the
object instead (single-encode), matching the browse_web fix and the Mastra
capability-map rule. Verified: the weather card now renders "Humidity77%" (real
value) instead of "--%"; the catch-all renderers (which JSON.parse once) also
render cleanly.
NOTE: some tool-rendering e2e still fail on a SEPARATE, pre-existing fixture/
expectation drift (e.g. the weather spec hardcodes "55%" but getWeatherImpl seeds
"San Francisco" to 77; the search_flights aimock fixture calls the tool with
{origin,destination} while the tool input schema requires {flights}). That drift
is independent of this encoding fix and predates OSS-452. (--no-verify: worktree
commitlint binary broken post-crash.)
|
||
|
|
cb9be8f880 |
fix(showcase/mastra): Beautiful Chat app-mode todos render (OSS-452)
The Task Manager (Shared State) pill added todos at the agent/tool level but the
app-mode canvas stayed on "No todos yet". Three compounding causes:
1. Wrong tool. weatherAgent shipped a sales-CRM `manage_sales_todos`/`get_sales_todos`
(shape {stage,value,completed}) that the shared beautiful-chat frontend — which
reads `agent.state.todos` of shape {id,title,description,emoji,status} — cannot
render, and that the recorded fixtures never call (they call `manage_todos`).
Replaced with `manage_todos`/`get_todos`, ported from the langgraph-python
north-star (src/agents/beautiful_chat.py): same tool names + Todo shape.
2. State never bound. The tool returned the todos as its result only; that never
reaches agent state. `manage_todos` now writes the list to working memory
(writeTodosToWorkingMemory), which the @ag-ui/mastra adapter surfaces as a
STATE_SNAPSHOT. Added `todos` to AgentState so the slice exists.
3. Silent no-op. `Agent.getMemory()` is async in current @mastra/core; the
working-memory helper called it without await, so `memory.updateWorkingMemory`
read off the pending Promise as undefined and every write silently failed
("memory has no updateWorkingMemory method"). Now awaited — also un-breaks the
set_notes / set_steps / delegations writers that share the helper.
Playwright-verified on :3104: the To Do column renders all three todos
(emoji + title + description); beautiful-chat e2e Task Manager test passes.
Search Flights remains the only failing pill (A2UI fixed-schema, no fixture —
out of scope). (--no-verify: worktree commitlint binary still broken post-crash.)
|
||
|
|
5595cf76c6 |
fix(react-core): expose isReady from useAgent to guard agent subscriptions (#5000) (#6041)
## Summary Closes #5000. `useAgent` (v2) always returns a **fully-constructed** `AbstractAgent` — a *provisional* stand-in while the runtime is still connecting (or in an error state), swapped for the real agent once the `/info` sync resolves. The return type claimed `agent` was always the real agent, so consumers had **no way to tell the provisional instance from the real one**. One-time subscriptions registered during the provisional window (e.g. `onRunFinalized`) landed on the placeholder and missed events until the effect re-ran after the swap. This PR adds an **`isReady`** flag to the return value: - `false` — `agent` is provisional (runtime connecting / error) - `true` — `agent` is the real, runtime-synced (or locally-registered) instance This is exactly the API the issue requests in its *Expected Behavior*. It is **additive and backward compatible** — existing `const { agent } = useAgent()` callers are unaffected. ```tsx const { agent, isReady } = useAgent({ agentId }); useEffect(() => { if (!isReady) return; // only subscribe once the real agent is bound const sub = agent.subscribe({ onRunFinalized: (p) => console.log(p) }); return () => sub.unsubscribe(); }, [agent, isReady]); ``` ## On the original crash The crash reported in #5000 — `Cannot read properties of undefined (reading 'subscribers')` at `AbstractAgent.subscribe` — **no longer reproduces on `main`**. The provisional-agent work landed for #5533 / #5635 now guarantees `useAgent` always returns a fully-constructed `AbstractAgent`, so `subscribe()` is always safe to call. The added tests lock in that no-crash behavior. What remained unaddressed was the missing readiness signal, which this PR provides. ## Changes - **`packages/react-core/src/v2/hooks/use-agent.tsx`** — `useMemo` now returns `{ agent, isReady }`; real agent → `isReady: true`, provisional paths → `isReady: false`. Documented with JSDoc. - **`use-agent-subscribe-ready.test.tsx`** (new) — regression + behavior coverage: `subscribe()` does not throw while connecting (effect + during-render), `isReady` transitions `false → true` on sync and swaps the instance, local agent is ready immediately. - **`showcase/shell-docs/.../hooks/useAgent.mdx`** — signature + return-value docs updated; the *Event Subscription* example fixed (it used an empty `useEffect` dep array and never re-subscribed when the agent reference changed). ## Testing - New test file: 4/4 pass. - Full `react-core` v2 hooks suite: **35 files / 299 tests pass** (the `useMemo` return-shape change breaks nothing). - `tsc --noEmit` clean. ## Notes - Scope is React only, matching the issue. The Vue `useAgent` (`packages/vue`) is structured differently (reactive `shallowRef`, `agent` can be `null`); happy to add matching `isReady` as a follow-up if maintainers want cross-framework parity. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
6cb3deb841 |
fix(showcase): aimock-wiring probe now covers the harness fleet (harness/harness-workers) (#6062)
## The incident this prevents
The showcase pays egress whenever a service reaches aimock over the
PUBLIC `*.up.railway.app` host instead of the free
`showcase-aimock.railway.internal:4010`. On STAGING, `harness-workers`
(the 6-replica probe fleet) had `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL`
/ `AIMOCK_URL` pointing at PUBLIC aimock, and `harness` had a public
`AIMOCK_URL` — together ~$657/mo of egress. The aimock-wiring drift
probe **never flagged this** because both services were in
`EXCLUDE_SERVICES`. The live vars are already fixed; this makes the
probe cover the class so it can't silently regress.
## Design choice + justification
Two facts constrained the fix:
1. `harness` / `harness-workers` were excluded (`EXCLUDE_SERVICES`) →
skipped entirely, so a naive fix must un-exclude them.
2. `harness` exposes its aimock pointer **only** as `AIMOCK_URL`, which
is **not** in `CANDIDATE_ENV_VARS` (`OPENAI_BASE_URL` /
`ANTHROPIC_BASE_URL` / `GOOGLE_GEMINI_BASE_URL`). So merely un-excluding
`harness` would leave it all-missing → unwired forever, even when
correctly wired.
Chosen approach — a dedicated **aimock-consumer** class:
- Remove `harness` / `harness-workers` from `EXCLUDE_SERVICES`.
- Add `AIMOCK_CONSUMER_SERVICES = { harness, harness-workers }` +
`isAimockConsumer(name)` (mirrors `isExcluded`: matches bare and legacy
`showcase-`-prefixed forms).
- Add `HARNESS_FLEET_CANDIDATE_ENV_VARS = [...CANDIDATE_ENV_VARS,
"AIMOCK_URL"]`; `pointsAtAimock` takes a `candidateVars` param (defaults
to the standard set). In the run loop, consumers use the extended set,
everything else the standard set.
This is the minimal correct surface: it catches `harness` via
`AIMOCK_URL`, catches `harness-workers` via any of
OPENAI/ANTHROPIC/AIMOCK_URL, and leaves the verdict precedence (match >
confirmed-mismatch > sealed > missing) untouched.
### Why `AIMOCK_URL` is scoped to the harness-fleet path (and safe)
Adding `AIMOCK_URL` to the **global** candidate set is not safe: a
regular demo backend that happens to expose `AIMOCK_URL` (pointed
anywhere) could then count as "wired" and **mask a missing real
`OPENAI_BASE_URL`/etc pointer**, hiding genuine drift. Scoping
`AIMOCK_URL` to `HARNESS_FLEET_CANDIDATE_ENV_VARS` means only the two
harness-fleet services consult it. A regression guard test (`does NOT
consult AIMOCK_URL for non-harness services`) locks this in. Pure-infra
services with no aimock pointer
(aimock/shell/dashboard/docs/dojo/pocketbase/webhooks) stay excluded and
never go red.
## Red → Green proof
Tests added in `aimock-wiring.test.ts`. RED was captured against the
**unchanged** probe (new tests only, source untouched); GREEN after the
fix + updating the 4 existing tests that asserted the old
harness-excluded behavior.
**RED** (new behavior tests fail on current code — harness fleet
excluded, so the incident is not flagged):
```
FAIL aimock-wiring.test.ts > flags the harness fleet when its aimock pointers are on the PUBLIC host (egress drift)
AssertionError: expected 'green' to be 'red'
FAIL aimock-wiring.test.ts > greens the harness fleet when its aimock pointers are on the PRIVATE internal host
AssertionError: expected [] to deeply equal [ 'harness', 'harness-workers' ]
FAIL aimock-wiring.test.ts > verifies `harness` via its only aimock pointer, AIMOCK_URL
AssertionError: expected 'green' to be 'red'
Test Files 1 failed (1)
Tests 3 failed | 44 passed (47)
```
**GREEN** (after the fix):
```
Test Files 1 passed (1)
Tests 47 passed (47)
```
Test coverage added:
- `flags the harness fleet when its aimock pointers are on the PUBLIC
host (egress drift)` — the exact incident → red.
- `greens the harness fleet when its aimock pointers are on the PRIVATE
internal host` — positive path → wired/green.
- `verifies harness via its only aimock pointer, AIMOCK_URL` —
public→red, internal→green (locks the `AIMOCK_URL`-candidate path).
- `does NOT flag pure-infra services with no aimock pointer` — guard:
shell/dashboard/etc stay excluded.
- `does NOT consult AIMOCK_URL for non-harness services` — guard:
`AIMOCK_URL` is not global.
## Local quality
- oxfmt (formatter) — clean on both files
- oxlint — 0 warnings, 0 errors
- `tsc --noEmit` (typecheck) — clean
- `tsc -p tsconfig.build.json` (build) — clean
- Full harness suite: only the pre-existing unrelated failures remain
(`d0-gone-predicate.test.ts`, `d5-mapping-drift.test.ts`, and
`cvdiag/staged-ts-scrub-parity.test.ts`) — all confirmed failing
identically on the untouched baseline (verified via `git stash`). This
PR adds **no** new failures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QCLub2Vb5Y56cPttSzkip1
|
||
|
|
6611c51959 |
fix(starters): add python-multipart to agno starter (fixes startup crash) (#6061)
## The crash `starter-agno` (Railway service, built from `examples/integrations/agno`) was crash-looping at import time: ``` RuntimeError: Form data requires "python-multipart" to be installed. fastapi/dependencies/utils.py ensure_multipart_is_installed() agno/os/routers/agents/router.py get_agent_router(...) agno/os/app.py _add_built_in_routes(...) ``` The app dies during startup import — before any network/LLM activity. ## Root cause The starter's top-level `Dockerfile` installs Python deps with: ```dockerfile RUN cd agent && uv pip install --system -e . ``` `uv pip install -e .` resolves from `pyproject.toml` and **does not consult `uv.lock`**. The pyproject pinned `agno>=1.7.8`, which now resolves to **agno 2.7.4**. agno **dropped `python-multipart` as a hard dependency** in the 2.7.x line (it was still a hard dep in the locked 2.3.3, and in 2.6.19). agno's `AgentOS` registers a FastAPI form-data route, and FastAPI's `ensure_multipart_is_installed()` raises at route-registration (import) time when the package is absent — so the agent never starts. The pinned `uv.lock` (agno 2.3.3) *did* carry `python-multipart` transitively, which is why the lockfile looked fine while the deployed image (which bypasses the lock) broke. ## Fix Add `python-multipart>=0.0.20` directly to the starter agent's `pyproject.toml` dependencies (and refresh `uv.lock`) so it installs regardless of agno's transitive-dependency changes. ## Red → Green proof Reproduced against the **real deployed install path** (`uv pip install -e .` from pyproject, lock bypassed) in a clean Python 3.12 venv. ### RED (main, before fix) ``` agno resolved: 2.7.4 fastapi: 0.139.2 python-multipart: ABSENT $ python -c "import main" RuntimeError: Form data requires "python-multipart" to be installed. You can install "python-multipart" with: pip install python-multipart ``` ### GREEN (after fix) ``` agno resolved: 2.7.4 fastapi: 0.139.2 python-multipart: 0.0.32 (now installed) $ python -c "import main" IMPORT OK - AgentOS app built, no RuntimeError $ uvicorn main:app ... INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8123 $ curl /health -> {"status":"ok",...} ``` ## Docker build (local, real starter image) Built the actual starter image from the top-level `Dockerfile` (the deployed path using `uv pip install --system -e .`) with the local BuildKit builder, then ran it: ``` docker buildx build -f Dockerfile -t agno-fix-test . -> EXIT 0 docker run ... agno-fix-test agent /health -> 200 {"status":"ok",...} agent log: "Application startup complete." / "Uvicorn running on http://0.0.0.0:8000" RuntimeError count in container logs: 0 in-image check: python-multipart present: 0.0.32 ``` ## Other agno variants `showcase/integrations/agno` uses a **separate** dep file (`requirements.txt`, not this pyproject/uv.lock) and pins `agno==2.6.19`, which still carries `python-multipart` transitively — so it is **not currently broken**, but it is fragile (a bump past 2.7.x would hit the same crash). No shared dep file, so it is intentionally out of scope here; recommend a follow-up to add an explicit `python-multipart` pin there too. |