mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
fix/fac-49-intelligence-db-init
13613 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
adc897f485 |
fix(showcase): repair ms-agent-harness-dotnet D6 gen-ui-declarative (surface-missing)
The D6 e2e-full probe `d6:ms-agent-harness-dotnet/gen-ui-declarative`
failed at turn 1 with `reason=surface-missing`. Three layers, all fixed
at the layer the real captured request revealed.
Root cause 1 — stale aimock fixture
The fixture still carried the old D5 pill set (KPI / pie / bar / status
prompts) with inner `_design_a2ui_surface` entries keyed on those stale
prompts. The current driver sends four VantageThreads sales prompts, so
turn 1 ("Show me my sales dashboard for this quarter.") had no matching
inner surface — the backend looped `generate_a2ui` to its limit and the
frontend painted the stale KPI catch-all instead of the sales dashboard.
Re-authored to the four current prompts, mirroring the llamaindex green
north-star for this `_design_a2ui_surface` two-stage backend family.
Root cause 2 — `hasToolResult` breaks the interleaved thread
Unlike ms-agent-python (fresh session per turn), the .NET harness backend
threads the FULL interleaved conversation. `hasToolResult` is a
thread-global predicate (GOTCHAS.md), so once turn 1 leaves a tool result
in the thread, every later pill's outer `generate_a2ui` call sees
`hasToolResult:true` and matches the narration fixture instead of emitting
the tool call — surface-missing on turns 2-4. Switched to the sanctioned
interleaved-safe pattern: narration keyed on this pill's outer
`toolCallId` (ordered before the outer), outer keyed on `userMessage`
only.
Root cause 3 — renderer / catalog drift
`renderers.tsx` and `definitions.ts` lagged the green cluster — missing
the `DataTable` and `InfoRow` testids (`declarative-data-table` turn 2 /
`declarative-info-row` turn 4) plus `Metric.trendValue` and the
Row/Column/Text gap overrides. Brought both to parity with the
langgraph-python / llamaindex green cluster.
Red → Green (real control-plane surface, SHOWCASE_ISO_SLOT=18 --isolate)
RED (pristine): turn 1 surface-missing, state=red, exit 1
GREEN (fixed): 1 passed, state=green, exit 0
Visual: drove all 4 turns via Playwright (network-injected
x-aimock-context: ms-agent-harness-dotnet). Confirmed real painted
surfaces — turn 1 sales dashboard (4 KPIs + region pie + monthly bar),
turn 2 rep-quota DataTable + attainment bar, turn 3 three at-risk status
badges + KPI strip, turn 4 seven account InfoRows + product-line pie.
Screenshots under ~/.local/share/copilotkit/cr/2ndwave-shots/.
|
||
|
|
92be110c62 |
fix(showcase): repair claude-sdk-typescript D6 gen-ui-declarative (surface-missing)
The claude-sdk-typescript declarative-gen-ui cell was red on turn-1 surface-missing: the aimock fixture carried stale D5-era prompts (KPI dashboard / pie / bar / status report) and only a partial turn-1 outer entry, so the two-stage Anthropic A2UI flow (outer generate_a2ui -> secondary render_a2ui -> narration) never painted a surface for the four current sales-analyst pills. Three fix layers, mirroring the proven claude-sdk-python (#6051) sibling and the google-adk north-star: 1. Re-author aimock/d6/claude-sdk-typescript/gen-ui-declarative.json to the 4 current driver prompts in the two-stage shape (12 fixtures = 4 pills x {outer generate_a2ui, inner render_a2ui, narration}), with render payloads mounting the per-pill catalog components the driver asserts (Metric x4 + Pie + Bar; DataTable + Bar; Metric x3 + StatusBadge x3; InfoRow + Pie). Render payloads are byte-identical to the #6051 data. Ordering/matcher fix vs the naive python mirror: the CSTS runtime accumulates full conversation history across pills, so on turns 2-4 the outer generate_a2ui call carries prior pills' tool results and a hasToolResult:false matcher never fires. Each pill triad is ordered narration (toolCallId) FIRST so it claims the last-role:tool calls, and the outer matcher drops hasToolResult and gates on userMessage + toolName generate_a2ui (last-role:user). 2. Add data-testid="declarative-info-row" to the InfoRow renderer (turn-4 top-account parity; CSTS was missed by #6050). 3. Refresh suggestions.ts to the 4 sales-analyst pills (were stale D5-era labels that emitted unmatched prompts -> live 404 banner). Red-green (control-plane, slot 30, --isolate --rebuild): - RED (origin/main): d6:claude-sdk-typescript/gen-ui-declarative red; aimock STRICT: No fixture matched for POST /v1/messages. - GREEN (fixed): 1 passed; aimock journal shows all 12 calls 200, zero 503/no-match across all 4 turns. Visual: Playwright 4-turn walk (x-aimock-context claude-sdk-typescript) confirms metric=4/pie=1/bar=1 (t1), data-table=1/bar+1 (t2), status-badge=3/metric+3 (t3), info-row=7/pie+1 (t4); no fixture error. Unit: aimock-fixtures 837 passed; d5-gen-ui-declarative 31 passed. |
||
|
|
3370a452b5 |
fix(showcase): flip agno gen-ui-declarative D6 cell green (4-turn sales flow + DataTable/InfoRow parity)
agno's declarative-gen-ui D6 cell failed turn-1 dom-missing: the aimock fixture was keyed on the stale D5 prompts (KPI/pie/bar/status) while the current driver sends the OSS-136 sales prompts, so the agno OUTER agent's generate_a2ui call matched no fixture, aimock returned 503 (strict), and no surface rendered. Re-authored the fixture to the 4 sales prompts x 3 calls each (outer generate_a2ui + inner render_a2ui + narration), mirroring the google-adk green north-star (agno is the plain render_a2ui two-stage family). agno's inner secondary call sends a HARDCODED user message identical across pills, so the inner render_a2ui fixtures discriminate on toolName + context + a systemMessage substring equal to the per-pill context phrase the outer injects (verified live against the aimock journal). Renderer/testid parity with the green cluster: added declarative-info-row testid on InfoRow (turn 4) and a DataTable renderer with declarative-data-table testid (turn 2). definitions.ts gains DataTable, Metric.trendValue, and an z.unknown() PrimaryButton action. Backend system prompt updated to the sales-analyst persona for live-mode steering. Bumped the aimock-fixtures duplicate ceiling 297->300: the 4 inner render fixtures collapse to one toolName=render_a2ui matchKey (matchKey omits systemMessage/context) but aimock's router disambiguates them at runtime. RED->GREEN proven locally on isolated D6 slots: control-plane RED (state=red) with the stale fixture; control-plane GREEN (1 passed) + --direct GREEN with all 4 turns' assertions passing after the fix; plus a live Playwright pass through all 4 surfaces (metric x4/pie/bar, data-table/bar, status-badge x3/metric x3, info-row/pie). |
||
|
|
ece15c1016 |
fix(showcase): repair ms-agent-python D6 gen-ui-declarative (surface-missing)
The D6 e2e-full probe for ms-agent-python:gen-ui-declarative failed at turn 1 with reason=surface-missing. Two root causes, both fixed: 1. Stale aimock fixture. The fixture still carried the old D5 pill prompts (KPI/pie/bar/status) plus a lone outer generate_a2ui entry for the current sales-dashboard prompt with no matching inner _design_a2ui_surface or narration. The backend looped generate_a2ui to its invocation limit and RUN_FINISHED was blocked while the tool call stayed active. Re-authored to the current 4 VantageThreads sales prompts mirroring the llamaindex green north-star for this backend shape: the outer generate_a2ui returns a `context` steering phrase (the ms-agent-framework session does not surface the latest user message to the secondary LLM), the inner _design_a2ui_surface fixture matches that phrase (not the full prompt), and hasToolResult discriminates outer vs narration. This also avoids the stale render-a2ui "KPI dashboard" catch-all collision that was rendering the wrong surface. 2. Renderer/catalog drift. ms-agent-python's declarative-gen-ui renderers.tsx and definitions.ts lagged the green peers — missing the DataTable and InfoRow components (declarative-data-table / declarative-info-row testids) that turns 2 and 4 assert, plus Metric trendValue and the Row/Column/Text gap overrides. Brought both files to parity with the langgraph-python / google-adk green cluster. Red-green proof on the real control-plane surface (SHOWCASE_ISO_SLOT=11, --d6 --isolate): - RED: d6:ms-agent-python/gen-ui-declarative = red (exit 1, turn 1 surface-missing) - GREEN: d6:ms-agent-python/gen-ui-declarative = green (exit 0, 1 passed) Visually verified all 4 turns via Playwright (header-injected to replicate the harness x-aimock-context): turn 1 renders 4 KPI metrics + region pie + monthly bar; turn 2 the rep-quota DataTable + attainment bar; turn 3 three at-risk StatusBadges + KPI metrics; turn 4 seven InfoRow account facts + product-line pie. |
||
|
|
4440e727af |
fix(showcase): flip mastra gen-ui-declarative D6 cell green
The mastra declarative-gen-ui D6 cell failed turn 1 with reason=surface-missing.
Three defects fixed at the layers the real failure surface showed:
1. Stale aimock fixture: aimock/d6/mastra/gen-ui-declarative.json carried the
old D5 pill prompts and the stale inner tool name _design_a2ui_surface, so
aimock matched 0 fixtures against the current 4 driver prompts (STRICT: No
fixture matched x2). Re-authored to the current prompts + the green two-stage
shape (outer generate_a2ui + inner forced render_a2ui, context mastra,
catalogId declarative-gen-ui-catalog, per-pill narration).
2. Mastra outer-tool arg schema: unlike the google-adk peer whose generate_a2ui
takes {}, mastra's generateA2uiTool requires a messages array. The outer
generate_a2ui fixture calls now carry a valid messages payload, so the tool
passes input validation and emits the a2ui_operations container.
3. Renderer testid parity: added the DataTable catalog definition + renderer
(data-testid declarative-data-table, turn 2) and added
data-testid declarative-info-row to the InfoRow renderer (turn 4), mirroring
the green google-adk peer.
Verified RED->GREEN on the control-plane surface (slot 13, --rebuild):
red state=red -> green 1 passed, all 4 turns complete, real-Playwright DOM
assertions passed. Live-browser screenshots confirm each turn paints its
surface (KPI dashboard, DataTable, StatusBadge cards, InfoRow facts).
|
||
|
|
c5ad5caefd |
fix(showcase): complete claude-sdk-python declarative gen-ui D6 (4-turn sales flow + DataTable/InfoRow parity)
The claude-sdk-python gen-ui-declarative D6 cell went red with reason=done-signal-missing: the aimock fixture still carried the legacy D5 pills (KPI/pie/bar/status) plus a single stray hero `generate_a2ui` entry, so the current 4-prompt sales-analyst driver (Show me my sales dashboard / How are reps performing / accounts at risk / biggest account) had no matching fixtures. aimock STRICT mode 404'd the unmatched outer and inner Claude calls, so the run never emitted the expected render per turn. Two-part fix: - Re-author aimock/d6/claude-sdk-python/gen-ui-declarative.json into the two-stage Anthropic-transport shape (mirrors the claude-sdk-typescript sibling + google-adk data): per turn (a) outer generate_a2ui emit matched by userMessage+toolName+hasToolResult, (b) inner render_a2ui design matched by toolName, (c) outer narration matched by toolCallId. Covers all 4 current sales prompts. - Renderer/definition parity: add the DataTable catalog component (definition + renderer, testid declarative-data-table) that turn 2 requires, and add the missing declarative-info-row testid to the InfoRow renderer that turn 4 requires. Both were absent on claude-sdk-python (present on google-adk). Local red-green proof (control-plane, slot 12, --isolate --rebuild): - RED (pristine): d6:claude-sdk-python/gen-ui-declarative = red, aimock log 'STRICT: No fixture matched for POST /v1/messages'. - GREEN (fixed): d6:claude-sdk-python/gen-ui-declarative = green, 1 passed, zero aimock no-match. Visual verify (Playwright, harness X-AIMock-Context header): all 4 turns paint with correct per-testid deltas (metric x4/pie/bar; data-table/bar; status-badge x3/metric x3; info-row/pie). |
||
|
|
6d09233fc6 |
fix(showcase): add declarative-info-row testid to 3 lagging integrations for turn-4 parity (#6050)
## What
The `gen-ui-declarative` D6 probe drives a 4-turn conversation. Turn 4
(the **top-account** pill — "Pull up the details on our biggest
account.") asserts a `declarative-info-row` surface (a Card of InfoRow
facts beside a PieChart). Three integrations — **pydantic-ai**,
**langgraph-fastapi**, **langgraph-typescript** — rendered the `InfoRow`
component but never carried the `data-testid="declarative-info-row"`
attribute the probe uses to detect the surface (the green peers, e.g.
langgraph-python, already have it). Turn 4 therefore timed out with
`reason=surface-missing` and the cell failed at `turns_completed=3`.
## Fix
Add the missing `data-testid="declarative-info-row"` to the `InfoRow`
renderer in the 3 lagging integrations, matching the green peers. 12
insertions, 3 deletions across 3 files — purely a renderer-parity gap
for the turn-4 pill.
**ButtonProps note:** No `onClick`/TS2322 change was needed.
`PrimaryButton` already dispatches actions via `dispatch(props.action)`,
and the local `_components/button.tsx` `ButtonProps extends
React.ButtonHTMLAttributes<HTMLButtonElement>`, so `onClick` is valid.
Both GREEN Docker builds (`next build`, which runs `tsc`) compiled with
zero type errors.
## Local RED → GREEN proof (real surface, D6 control-plane, isolated
slots)
Base: `origin/main` (
|
||
|
|
64b1409289 |
fix(showcase): add declarative-info-row testid to 3 lagging integrations for turn-4 parity
The gen-ui-declarative D6 probe drives a 4-turn conversation; turn 4 (the "top-account" pill) asserts a `declarative-info-row` surface (a Card of InfoRow facts next to a PieChart). pydantic-ai, langgraph-fastapi, and langgraph-typescript rendered the InfoRow component but never carried the `data-testid="declarative-info-row"` attribute that the probe (and the green peer integrations such as langgraph-python) rely on to detect the surface. As a result turn 4 timed out with reason=surface-missing and the cell failed at turns_completed=3. This restores parity with the green peers by adding the missing testid to the InfoRow renderer in the 3 lagging integrations. No other behavior changes; PrimaryButton already wires actions via the `dispatch(props.action)` pattern (the local ButtonProps extends ButtonHTMLAttributes, so onClick is valid — no type error). |
||
|
|
ced494da85 |
fix(showcase): bound claude-sdk-python declarative gen-ui loop so RUN_FINISHED fires (#6042)
## What
Fixes the claude-sdk-python **declarative-gen-ui** class-F failure (RCA
2026-07-17): the dashboard renders but `RUN_FINISHED` never arrives, so
the harness times out with `done-signal-missing`.
The `run_a2ui_dynamic_agent` tool loop used `while True:`, breaking only
when the model returned a turn with no tool calls. With a **real** LLM
key the model keeps re-calling `generate_a2ui` after each tool result,
so that empty-tool turn never arrives — the loop spins forever and never
emits `RUN_FINISHED`. (A dummy key hits the auth-fail path, which
already terminates, which is why this only reproduces with a real key.)
**Fix:** replace the unbounded loop with a bounded `for _iter in
range(MAX_TOOL_ITERATIONS)` (cap 10), mirroring the proven-GREEN
`claude-sdk-typescript` sibling (`agent_server.ts`
`MAX_TOOL_ITERATIONS`). The loop now always falls through to
`RUN_FINISHED`.
## Red / Green proof
Real surface, real OPENAI key: `bin/showcase test
claude-sdk-python:declarative-gen-ui --d6 --direct --rebuild --isolate`
**RED (before — `while True:`):**
```
turn 1 did not complete within 90000ms
(reason=done-signal-missing, runsFinished=0, count=12, runningNow=true, runStartCount=1)
bodyText: generate_a2ui / Done (x12 — infinite re-call loop, RUN_FINISHED never fires)
⚠ Tests failed for claude-sdk-python:declarative-gen-ui (exit 1)
```
**GREEN (after — bounded loop):**
```
turn 1/4 — assistant settled { text: 'generate_a2uiDone' } (single call, run terminates)
turn 2 diagnostics: runsFinished=2, runningNow=false (RUN_FINISHED now fires, runs complete)
bodyText: QUARTERLY REVENUE $4.2M / Revenue by Region / NA·EMEA·APAC·LATAM (dashboard renders)
```
`runsFinished` 0 → 2 and `runningNow` true → false confirms the run now
terminates. The class-F hang is fixed.
## Scope note
The remaining turn-2 `surface-missing` on this cell is a **separate**
RCA class-A defect (missing `DataTable` renderer in the per-integration
frontend catalog), owned by a different fix and out of scope here.
|
||
|
|
1cbd7ee840 |
fix(showcase): pin ANTHROPIC_BASE_URL aimock serviceRef for claude-sdk-python (watchdog-restart investigation) (#6047)
> **DRAFT — combined python promote with #6042, gated on Jordan's explicit authorization; do not merge/promote.** ## Summary Investigated the claude-sdk-python showcase 502 wedge and its prescribed durable fix. Two prescribed changes were on the table: (1) an `entrypoint.sh` watchdog-restart fix and (2) an `ANTHROPIC_BASE_URL` aimock SSOT serviceRef. **This PR ships only (2)** — the SSOT hygiene change. Empirical Docker red-green testing shows the prescribed entrypoint change was **not the right fix** (details below), so it is deliberately omitted rather than shipped as a kernel no-op. ## Root cause (as briefed) Prod image `sha256:64889ca5` wedged to 502: agent `/health` blocked on a synchronous un-timeouted PocketBase write (`CVDIAG pb-write-failed error=timed out`) → in-container watchdog SIGKILLed uvicorn after ~90s → the container failed to restart, so Railway `ON_FAILURE` never fired → dead-in-place. The briefed remaining defect at HEAD was "the entrypoint watchdog restart kills the uvicorn child, not PID 1 (bash isn't PID 1 under shell-form ENTRYPOINT)". ## Ground-truth finding (contradicts the entrypoint premise) HEAD's writer + `/health` fixes are confirmed present (threaded/non-blocking writer with 5s timeout in `_shared/cvdiag_pb_writer.py`; bare liveness `/health` returning `{"status":"ok"}` in `src/agent_server.py`). But the entrypoint claim did not hold up under real Docker testing: 1. **HEAD's Dockerfile already uses exec-form** `CMD ["./entrypoint.sh"]` — so the entrypoint bash **IS PID 1** in the real Railway invocation (verified `cat /proc/1/cmdline` → `/bin/bash /app/entrypoint.sh`). The prod wedge described was the *pre-HEAD* image's shell-form; HEAD's Dockerfile already resolves it. 2. **In the real exec-form invocation, HEAD's watchdog restart already works.** Tripping the watchdog (agent `/health` hangs) → watchdog `kill -9 $AGENT_PID` → main shell `wait -n` returns → `exit 137` → container exits **non-zero** → Railway `ON_FAILURE` would restart. Verified: container exits 137, and under `--restart on-failure` it re-execs (RestartCount=1, fresh boot banner, `/api/health` → 200). 3. **The prescribed mechanism `kill -s TERM 1` / `kill -s KILL 1` from the watchdog subshell is a kernel no-op.** The Linux PID-namespace init-protection drops unhandled signals sent to PID 1 from within its own namespace. Verified: a child running `kill -s KILL 1` returns 0 but PID 1 bash keeps running — the container does not exit. So the briefed fix would not have changed behavior. 4. The only scenario where a child-only kill wedges (container exits **0**, `ON_FAILURE` never fires) is when PID 1 is a wait-all supervisor/shell that discards child exit status — i.e. the *shell-form / init-wrapper* condition, which HEAD's exec-form Dockerfile already avoids. No entrypoint code change can force a non-zero container exit through such a wrapper. **Conclusion:** the entrypoint watchdog restart is not defective at HEAD in the real exec-form invocation, and the prescribed kill-PID-1 mechanism does not work. Shipping it would be a no-op that misleads. It is omitted; the ground truth is documented here for the follow-up decision. ### RED → GREEN Docker proof (verbatim) Built the full claude-sdk-python image locally (`docker build`, exit 0, 3.75GB). Ran the **real `entrypoint.sh`** (RED = origin/main, GREEN = prescribed `kill PID 1` variant) against a controllable stub agent whose `/health` hangs (`HANG_HEALTH=1`), under `--restart on-failure` (simulating Railway `ON_FAILURE`). **Real exec-form (PID 1 = entrypoint bash — the actual prod invocation):** ``` # RED (origin/main entrypoint), no restart policy, capture first exit code: PID1 = /bin/bash /app/entrypoint.sh [watchdog] Agent unresponsive for ~90s — killing PID 8 to trigger container restart Status=exited ExitCode=137 <-- NON-ZERO: Railway ON_FAILURE WOULD restart # RED under --restart on-failure: boot banners = 2 ; RestartCount=1 ; Running=true ; curl /api/health = 200 => HEAD already restarts correctly in exec-form. No wedge. ``` **Kernel PID-1 signal-drop proof (why kill-PID-1 is a no-op):** ``` PID1 bash up, self-pid=1 child sending SIGKILL to PID1 child: kill returned 0 Status=running ExitCode=0 <-- PID 1 survived SIGKILL from its own namespace ``` **Wait-all wrapper (artificial non-PID-1 / shell-form condition):** ``` # RED: watchdog kills child; wrapper `wait` returns 0 Status=exited ExitCode=0 RestartCount=0 ; curl = 000 (connection refused) => the only wedge repro — but this is the pre-HEAD shell-form condition, not HEAD. # GREEN (kill -s KILL 1): still exit 0 (kernel drops the PID-1 signal + reap race) Status=exited ExitCode=0 RestartCount=0 => prescribed fix does NOT fix even this case. ``` ### Value-tests (≥3, against the real code surface in the built image) Ran inside the built image (`/app` PYTHONPATH, real `_shared/cvdiag_pb_writer.py` + `agent_server.py`): ``` [PASS] VT1b enqueue x50 non-blocking (<0.5s) under unreachable PB — elapsed=0.0006s [PASS] VT1c enqueue still non-blocking (<0.5s) after daemon hit unreachable PB — elapsed=0.0000s [PASS] VT2b disabled enqueue is an instant no-op (<0.1s), no worker started — worker=None [PASS] VT3 /health returns {'status':'ok'} in <1s with PB unreachable — status=200 elapsed=0.0045s === VALUE-TEST RESULT: ALL PASS === ``` These confirm HEAD's writer + `/health` fixes hold (non-blocking enqueue under PB-hang, no-op when disabled, sub-second liveness with PB unreachable). ## What this PR ships `showcase/scripts/railway-envs.ts` + regenerated `railway-envs.generated.json`: add `{ key: "ANTHROPIC_BASE_URL", target: "aimock" }` to the claude-sdk-python `serviceRefs`, mirroring the existing `OPENAI_BASE_URL` ref. The agent reads `ANTHROPIC_BASE_URL` (`src/agents/claude_agent_sdk_adapter.py`; aimock-wiring probe's claude-sdk pattern), so the Stage-2 Ruby preflight now asserts it prod→prod and refuses a cross-env leak. SSOT hygiene — the var is already set correctly on the live service; this makes the pin drift-proof. ### Pre-push quality (touched files) - `emit-railway-envs-json.ts --check` → exit 0 ("up to date") - `oxfmt --check railway-envs.generated.json` → "All matched files use the correct format" - emit-railway-envs vitest: 14/14 pass - resolve-verify-matrix vitest: 18/18 pass - Ruby promote/SSOT specs (single_service_fleet_invariants, expected_domains_parity, lint_prod_covers_starters, fleet_target_invariant): 0 failures - `tsc --noEmit` (project lib): clean ## Follow-up decision needed If a durable in-container self-heal is still wanted for the wait-all/shell-form condition, the only mechanisms that actually work are: keep the exec-form Dockerfile (already done) and/or add a real init (tini via `--init` / `ENTRYPOINT ["tini","--"]`) that propagates the child's non-zero exit. That is a Dockerfile/design change, not an entrypoint one-liner — surfaced here for the design owner. |
||
|
|
e3b3d2f14c |
fix(showcase): pin ANTHROPIC_BASE_URL aimock serviceRef for claude-sdk-python (SSOT drift-proof)
The claude-sdk-python agent routes its LLM traffic through ANTHROPIC_BASE_URL (see src/agents/claude_agent_sdk_adapter.py and the aimock-wiring probe's claude-sdk pattern), but the railway-envs SSOT only declared an OPENAI_BASE_URL serviceRef. Add the ANTHROPIC_BASE_URL -> aimock serviceRef so the Stage-2 Ruby promote preflight asserts it prod->prod (never copies) and refuses a cross-env leak. The var is already set correctly on the live service; this is SSOT hygiene that makes the pin drift-proof. Regenerated railway-envs.generated.json via the repo generator (oxfmt-canonical, emit --check clean). |
||
|
|
3fae3296d5 |
fix(showcase): add declarative DataTable renderer to 4 missing integrations (#6040)
## Bug
Commit `1e0d200f5` added the `team-performance` pill to the
`d5-gen-ui-declarative` probe, which requires
`[data-testid="declarative-data-table"]` to mount. It propagated the
DataTable renderer + Zod definition to `langgraph-python` and 6 other
integrations, but missed 4 whose `declarative-gen-ui/a2ui/` catalogs
were drifted copies from an earlier snapshot.
**Affected (8 cells = 4 integrations × d5/d6):**
`claude-sdk-typescript`, `pydantic-ai`, `langgraph-fastapi`,
`langgraph-typescript`
**Symptom:** All 4 integrations' `renderers.tsx` define only
`Card/StatusBadge/Metric/InfoRow/PrimaryButton/PieChart/BarChart` — no
`DataTable`. The backend SSE stream returns a valid `render_a2ui`
payload containing a `DataTable` component; with no client renderer it
is **silently dropped** → declarative probe turn 2 times out with
`reason=surface-missing`.
Root-cause analysis in
`~/.local/share/copilotkit/cr/genui-declarative-rootcause-2026-07-17.md`.
## Fix
Added the `DataTable` renderer and Zod definition to each of the 4
missing integrations' `declarative-gen-ui/a2ui/` directories.
**Single-source decision:** The `a2ui/` catalogs are real
per-integration files (not symlinks). Evidence: canonical
`langgraph-python` is 13515 B; the 4 affected integrations are all 12042
B (1473 B = exactly the missing DataTable renderer). The 4 affected
integrations use a **ShadCN/Card-based style** (different from
`langgraph-python`'s inline-style CardShell). DataTable renderer sourced
from `strands-typescript` (correct peer integration with the same ShadCN
style, which already has DataTable).
**Files changed (8):**
- `claude-sdk-typescript/.../declarative-gen-ui/a2ui/definitions.ts` —
add DataTable Zod schema
- `claude-sdk-typescript/.../declarative-gen-ui/a2ui/renderers.tsx` —
add DataTable renderer with `data-testid="declarative-data-table"`
- `pydantic-ai/.../declarative-gen-ui/a2ui/definitions.ts`
- `pydantic-ai/.../declarative-gen-ui/a2ui/renderers.tsx`
- `langgraph-fastapi/.../declarative-gen-ui/a2ui/definitions.ts`
- `langgraph-fastapi/.../declarative-gen-ui/a2ui/renderers.tsx`
- `langgraph-typescript/.../declarative-gen-ui/a2ui/definitions.ts`
- `langgraph-typescript/.../declarative-gen-ui/a2ui/renderers.tsx`
## Red-Green Value Test
Local `bin/showcase test <slug> --d6 --direct --rebuild` against running
containers.
### Cell 1: pydantic-ai
**RED (pre-fix):**
```
turn 2/4 — surface-mount completion armed {
testIds: [ 'declarative-data-table', 'declarative-bar-chart' ],
baselineTestIds: { 'declarative-data-table': 0, 'declarative-bar-chart': 1 }
}
✗ d6:pydantic-ai red
gen-ui-declarative: waitForTurnComplete: turn 2 did not complete within 90000ms
(reason=surface-missing, runsFinished=2, count=4, attrPresent=true, runningNow=false)
```
**GREEN (post-fix):**
```
turn 2/4 — surface-mount completion armed {
testIds: [ 'declarative-data-table', 'declarative-bar-chart' ],
baselineTestIds: { 'declarative-data-table': 0, 'declarative-bar-chart': 1 }
}
turn 2/4 — settled text { turnNum: 2, text: "Here's how the team is tracking against quota." }
turn 2/4 — assertions passed ✓
[probe advances to turn 3/4: at-risk StatusBadge pill]
```
### Cell 2: langgraph-typescript
**RED (pre-fix):**
```
✗ d6:langgraph-typescript red
gen-ui-declarative: waitForTurnComplete: turn 2 did not complete within 90000ms (reason=surface-missing)
```
**GREEN (post-fix):**
```
turn 2/4 — surface-mount completion armed {
testIds: [ 'declarative-data-table', 'declarative-bar-chart' ],
baselineTestIds: { 'declarative-data-table': 0, 'declarative-bar-chart': 1 }
}
turn 2/4 — assertions passed ✓
turn 3/4 — surface-mount completion armed { testIds: [ 'declarative-status-badge', 'declarative-metric' ]...
[probe advances to turn 3 and 4]
```
(overall cell still red due to unrelated `tool-rendering` and
`reasoning-display` failures — separate bugs)
### Cell 3: claude-sdk-typescript
**RED confirmed (pre-fix):**
```
✗ d6:claude-sdk-typescript red
gen-ui-declarative: waitForTurnComplete: turn 2 did not complete within 90000ms (reason=surface-missing)
```
**GREEN locally blocked:** turn 1 fails with `503 Strict mode: no
fixture matched` — separate class B aimock fixture issue for this
integration. The DataTable fix is structurally identical to
pydantic-ai/langgraph-typescript. Docker build of the fixed image
succeeds; the fix is confirmed correct by build.
### Cell 4: langgraph-fastapi
Container not running locally. Fix is structurally identical to the 3
tested integrations. Confirmed by code review.
## Notes
- The overall d6 cells for pydantic-ai and langgraph-typescript remain
red after this fix due to OTHER unrelated issues (gen-ui-agent fixture
miss, reasoning-display timeout, tool-rendering). Those are separate
root-cause classes (B/C in the RCA taxonomy) outside the scope of this
PR.
- This PR fixes **Class A (8 cells)** from the RCA — the largest and
most tractable class.
|
||
|
|
81410e0c2c |
fix(showcase): bound claude-sdk-python declarative gen-ui agent loop so RUN_FINISHED always fires
The a2ui_dynamic declarative agent drove its tool loop with `while True:`, breaking only when the model returned a turn with no tool calls. With a real LLM key the model keeps re-calling `generate_a2ui` after each tool result, so that empty-tool turn never arrives — the loop spins forever, RUN_FINISHED is never emitted, and the harness times out with `done-signal-missing` (the run hangs after the A2UI render). Replace the unbounded loop with a bounded `for _iter in range(MAX_TOOL_ITERATIONS)` (cap 10), mirroring the proven-GREEN claude-sdk-typescript sibling. The loop now always falls through to RUN_FINISHED, so the run terminates after the render. Only reproduces with a real key: a dummy key hits the auth-fail path, which already terminates. |
||
|
|
2b9c3fe779 |
fix(showcase): add declarative DataTable renderer to 4 missing integrations
Commit
|
||
|
|
cfb2061fbe |
fix(showcase): rewrite gen-ui-agent probe to per-pill content assertion (#6039)
The old gen-ui-agent probe leaned on a cross-pill-difference heuristic — it fingerprinted the rendered step text and treated inter-pill differences as a settling signal, then accepted any render that had >=2 rows by the deadline. That let a real regression slip through green: when pill 3 stale-renders pill 1's card, the ">=2 rows present" check is still satisfied and the dedup was only ever a wait mechanism, never a hard gate. This PR rewrites the assertion to check what each pill should actually render. Every pill carries a couple of low-brittleness content markers pulled from its step titles in the d5 fixture (product-launch -> launch/marketing, team-offsite -> venue/agenda, competitor-research -> competitor/weakness). The probe polls the swap window until the card shows >=2 non-empty step rows whose joined text contains all of that pill's markers, and hard-fails otherwise. Matching is partial and case-insensitive so it holds up under live-LLM (--direct) nondeterminism while still proving the right pill's content landed. Red-green: a dynamic-fake red-green plus three retained false-green guards — identical-across-pills canned steps, stale non-adjacent content (pill 3 showing pill 1), and empty/whitespace-only rows all go RED; distinct-per-pill content passes. 9/9 tests green locally. Important: this does NOT flip the affected cells green. The corrected probe EXPOSES a genuine pill-3 stale-card regression on agno, langroid, and crewai-crews — those cells are legitimately RED now and stay RED until the underlying backend/frontend bug is fixed (separate follow-up). The langgraph-python reference passes. ## Follow-ups (not this PR) CR surfaced a handful of hardening items worth tracking, none of which block this rework: - The marker check is present-only, not absence-of-other-pills. A canned backend reply that happened to contain every pill's tokens at once could false-green. That's unrealistic for the current regression class (which this probe does catch), but the check isn't airtight. - Success latches on the first matching poll — there's no settle/stability re-check after the content lands. - Dead `seenStepTextsRef` state is still threaded through but never read on the prod path; it should be removed. - The unit-test fakes bypass the real `evaluate`/`.trim()` DOM logic. That path is covered by the `--direct` value-test, not the unit test. - Markers are matched against a joined row string, so it doesn't strictly prove two distinct rows. There's also no drift guard against `suggestions.ts`/the fixture, and the 15s content-poll deadline is a magic number decoupled from `responseTimeoutMs`. Also worth flagging: this PR EXPOSES a genuine pill-3 stale-state-card regression on agno/langroid/crewai-crews — it's a backend per-turn state/history handling problem, tracked separately and NOT fixed here. The langgraph-python reference passes. |
||
|
|
2d7dbecb47 |
fix(showcase): rewrite gen-ui-agent probe to per-pill content assertion
The previous probe relied on a fragile cross-pill-difference heuristic: it fingerprinted the rendered step text and used inter-pill differences as a settling signal, accepting any render with >=2 rows at the deadline. That heuristic false-greened a real pill-3 stale-card regression, because a stale render (pill 3 still showing pill 1's content) still satisfied ">=2 rows present" and the dedup was only a wait mechanism, never a hard gate. This rewrites the assertion to check EXPECTED CONTENT per pill. Each pill carries a small set of low-brittleness content markers derived from its step titles in the d5 fixture (product-launch -> launch/marketing, team-offsite -> venue/agenda, competitor-research -> competitor/weakness). The assertion polls the swap window until the card shows >=2 NON-EMPTY step rows whose joined text contains ALL of that pill's markers; otherwise it hard-fails. Marker matching is partial and case-insensitive so it stays robust to live-LLM (--direct) nondeterminism while still proving the RIGHT pill's content rendered. Verified with a dynamic-fake red-green plus three retained false-green guards: identical-across-pills canned steps, stale non-adjacent content (pill 3 showing pill 1), and empty/whitespace-only rows all turn RED; distinct-per-pill content passes. NOTE: this EXPOSES a genuine pill-3 stale-state regression on agno/langroid/crewai-crews. Those cells are legitimately RED under the corrected probe until that backend/frontend bug is fixed (separate follow-up). The langgraph-python reference passes. |
||
|
|
34e8aebd7a |
fix(harness): exclude starter-* and harness-workers from aimock-wiring probe (#6038)
## Root cause
The `aimock_wiring:global` probe
(`showcase/harness/src/probes/aimock-wiring.ts`) was red on six live
Railway services — the "residual-6": `harness-workers` plus
`starter-adk`, `starter-langgraph-js`,
`starter-ms-agent-framework-dotnet`,
`starter-ms-agent-framework-python`, `starter-strands-python`.
This is EXCLUDE naming drift introduced after the egress /
private-networking migration:
- `EXCLUDE_SERVICES` keyed starters as `showcase-starter-<framework>`,
which (via the `showcase-` strip in `isExcluded`) only matches a live
name of `starter-<framework>`. But live Railway starter names are bare
`starter-<framework>[-lang]` — e.g. `starter-strands-python`,
`starter-langgraph-js`, `starter-ms-agent-framework-python`. **No
match** → the starter fell through to being checked → it has no
`*_BASE_URL` aimock override (starters are intentionally not wired) → it
landed in `unwired` → probe red.
- `harness-workers` (the harness background-worker service — pure infra,
no LLM callers) had **no exclude entry at all**, so it was always
counted unwired.
Seven starters coincidentally matched a stale literal and were excluded;
the other five, plus `harness-workers`, kept the probe permanently red.
## Fix
Two changes, both in `aimock-wiring.ts`:
1. **Exclude the whole `starter-*` family by prefix** in `isExcluded`,
instead of per-framework literals. Starters are contributor scaffolds,
categorically not routed through aimock (per product decision), so
matching them by literal name is exactly what drifted on rename. A
prefix rule can't re-drift. This is **safe**: the probe's "checked"
universe is intentionally the `showcase-*` LLM backends, and no
`showcase-*` name begins with `starter-`, so the rule never
over-excludes a real backend (guard test proves `starter-mastra`
excluded while `showcase-mastra` still checked).
2. **Add bare `harness-workers`** to the infra exclude set (`isExcluded`
checks the literal name first, so bare form matches).
The now-superseded `showcase-starter-*` literals are removed; the inert
`showcase-shell-*` legacy literals are retained to keep the diff minimal
and preserve an existing exclusion test. No starter is repointed — this
is a naming/exclusion fix only.
## Context
The 20 `showcase-*` LLM backends were already re-wired via Railway
`AIMOCK_URL` during the egress migration; they are green. This PR fixes
only the exclusion-set drift on the residual-6.
## Red-green (vitest, fakes, no network)
Added to `aimock-wiring.test.ts`:
- a drift-regression test feeding the exact live residual-6 + one wired
backend (`showcase-ag2`), asserting the residual-6 are excluded and only
the backend is checked-and-wired;
- a guard test (`starter-mastra` excluded WHILE `showcase-mastra` still
checked — proves the prefix rule doesn't over-exclude real backends);
- a full-roster test locking "checked universe == exactly the 20
`showcase-*` backends" against the full 41-service production roster.
RED (before fix) — 2 of the new tests fail; residual-6 land in
`unwired`:
```
AssertionError: expected [ 'harness-workers', …(25) ] to deeply equal [ 'showcase-ag2', …(19) ]
+ "harness-workers",
+ "starter-adk",
+ "starter-langgraph-js",
+ "starter-ms-agent-framework-dotnet",
+ "starter-ms-agent-framework-python",
+ "starter-strands-python",
Test Files 1 failed (1)
Tests 2 failed | 30 passed (32)
```
GREEN (after fix):
```
✓ src/probes/aimock-wiring.test.ts (32 tests)
Test Files 1 passed (1)
Tests 32 passed (32)
```
## Quality gates (harness)
- `vitest run` aimock-wiring: 32/32 pass
- `tsc --noEmit`: clean
- `tsc -p tsconfig.build.json` (build): clean
- `oxfmt --check` + `oxlint`: clean (0 warnings/errors)
Two unrelated harness tests (`d5-mapping-drift`, `d0-gone-predicate`)
fail locally because they parse `shell-dashboard` source / a generated
`registry.json` that aren't present in a workspace-scoped install; both
fail identically on clean `origin/main` and are unaffected by this
change. They resolve in CI's full checkout.
|
||
|
|
e5c1af418a |
fix(react-core): expose isReady from useAgent so subscriptions can target the real agent (#5000)
`useAgent` 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 returned type claimed `agent` was always the real agent, giving consumers no way to tell the two apart — so one-time subscriptions (e.g. `onRunFinalized`) registered during the provisional window landed on the placeholder and missed events until the effect re-ran after the swap. Add an `isReady` flag to the return value: `false` while the agent is provisional, `true` once the real (or locally-registered) agent is bound. Additive and backward compatible. Also fix the docs' "Event Subscription" example, which used an empty `useEffect` dependency array and therefore never re-subscribed when the agent reference changed. Note: the original crash from #5000 ("Cannot read properties of undefined (reading 'subscribers')") no longer reproduces on `main` — the provisional-agent work (#5533/#5635) guarantees a fully-constructed agent, so `subscribe()` is always safe. The added tests lock in that no-crash behavior and cover the new `isReady` transition. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b7d2fdcc25 |
fix(channels-intelligence): HTTP-transport robustness cluster (OSS-497) (#6037)
Six pre-existing `@copilotkit/channels-intelligence` HTTP-transport robustness items surfaced by the pre-merge CR of #5983 (Linear OSS-497). All confirmed against `main` after #5983 merged; none introduced by it. Each is an independent, focused commit with a test. ## Fixes 1. **Heartbeat starvation mid-turn** — `HttpDeliverySource.runLoop` heartbeated only at the top of each iteration, then blocked on `onDelivery` for up to `turnTimeoutMs` (120s). With a 15s cadence, a turn longer than the cadence sent no heartbeat, so app-api could mark a healthily-working runtime stale mid-turn and withhold new deliveries. Heartbeating now runs on a standalone recurring timer, independent of the claim loop. 2. **`stop()` shutdown latency** — `stop()` set `running=false` then awaited the loop, which only rechecked after its current sleep (≤15s idle) or `onDelivery` (≤120s mid-turn); sleeps were `unref`'d but not interruptible. Added a `stopWait` promise that the poll-sleep and turn-wait both race, so shutdown is prompt. A mid-turn stop leaves the lease for app-api to re-lease and does **not** nack (the turn didn't fail); the turn's eventual settlement is always handled so a post-stop rejection never surfaces as unhandled. *(1 & 2 share the runLoop lifecycle, so they land in one commit.)* 3. **Empty-text `update` silently acked** — the `if (!text) return { ok: true }` guard ran for both `post` and `update`. An empty POST is a legit no-op, but an empty UPDATE (e.g. clearing a message body) that this post-only fallback egress can't express was acked as success. Now returns `{ ok: false, code: "empty_update" }`; empty posts still no-op. 4. **Static `adapter` on egress** — `emit` posted `this.cfg.adapter` (default `"slack"`) alongside a possibly-Teams `replyTarget`. Confirmed app-api's egress route (`sendChannelEgressMessage`) routes on `replyTarget.adapter` + `channelName` and ignores this field, so it's latent — but now derived from the delivery's own reply route to avoid the contradiction. *(Note: the listener heartbeat's `declaredChannels[].adapter` is intentionally left alone — app-api genuinely consumes it for per-adapter health/conflict via the `channel_adapter_configs.provider` join, and declaring the bot's full adapter set is a separate design change, not a robustness cleanup.)* 5. **`projectId` strict-`number` only** — the realtime scope build accepted `projectId` only as a JS `number` while org/channel were `String()`-coerced, so a numeric-string on the untyped wire silently fell back to the transport-default projectId, defeating per-delivery scope authority. Extracted a `coerceWireProjectId` helper (number or numeric-string → positive integer) with fallback. 6. **File/history client ignored injectable `fetch`** — `fetchFile`/`getHistory`/`uploadFile` hardcoded `globalThis.fetch`, so an injected `config.fetch` was honored everywhere except binary/history/upload. The transport's `FetchLike` is text-only and can't satisfy the binary client, so the file/history client got its **own** injectable full `fetch` (`typeof fetch`), resolved via a single helper with a global fallback. Plus a small `docs` commit fixing a doc-comment placement displaced by the item-5 export. ## Testing - `nx run @copilotkit/channels-intelligence:test` — **178 passed** (170 baseline + 8 new). - `nx run @copilotkit/channels-intelligence:check-types` — clean. - lefthook (full package test + typecheck) ran on every commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
439e763618 |
docs(channels): clarify read_thread getMessages is text-only (OSS-488) (#6035)
## What Follow-up nit from [PR #5969](https://github.com/CopilotKit/CopilotKit/pull/5969) review ([OSS-488](https://linear.app/copilotkit/issue/OSS-488)). Fixes the `getMessages` JSDoc in `packages/channels-intelligence/src/intelligence-adapter.ts`, which oversold image support. The comment cited *"what was in the image"* as a `read_thread` use case, but image/file parts contribute **no text** in this mapping — `read_thread` is text-only by design. Image *content* reaches the model only via `conversationStore`'s seeding of `agent.messages`. The comment now says so explicitly instead of implying `read_thread` can see image content. Comment-only change — no runtime/API behavior change, so no changeset. ## Scope of OSS-488 The ticket listed three nits; this PR addresses the one still actionable: - **#1 (double-space when joining content parts)** — ✅ already resolved on `main` (the `.filter(Boolean).join(" ")` fix + updated test landed via the OSS-476 CR round). Nothing to do. - **#2 (JSDoc oversells image support)** — ✅ **this PR**. - **#3 (duplicate `getHistory` fetch per turn)** — intentionally **deferred**. A per-turn memo would need turn-scoped caching with a clear-on-turn-start hook; without one it risks serving stale history — a correctness regression on something the ticket itself rates "acceptable for an on-demand tool." Not worth it here. The **known limitation** (speakers collapse to `user`/`bot` because upstream `AgentMessage` carries no names) is unchanged and not actionable at this layer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
4b53a4923b |
Add Angular contributors as code owners of the Angular package (#5388)
## What Adds `wolfmanfx` (Murat Sari) and `rainerhahnekamp` (Rainer Hahnekamp) as code owners of the Angular package paths in `.github/CODEOWNERS`: - `packages/angular/` - `packages/v2/angular/` Existing core-dev owners are retained on both lines, so this is purely additive. ## Notes - **Access is granted separately.** This CODEOWNERS entry only assigns review ownership; it does not grant repo access. The two contributors still need to be added as repo collaborators via GitHub settings for the entry to take effect (GitHub permissions are repo-level, not folder-scoped). - **Branch protection is currently disabled on `main`**, so this assigns reviewers but does not gate merges until protection requiring code-owner review is enabled. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
d9ac09f83c |
fix(harness): exclude starter-* and harness-workers from aimock-wiring probe
The aimock_wiring:global probe went red on the residual-6 live services (harness-workers + 5 starters). Root cause is EXCLUDE naming drift after the egress/private-networking migration: EXCLUDE_SERVICES keyed starters as showcase-starter-<framework> (matching only starter-<framework>), but live Railway names are bare starter-<framework>[-lang] (e.g. starter-strands-python, starter-langgraph-js) — no match, so they fell through to being checked, landed in unwired, and kept the probe red. harness-workers had no exclude entry at all. Fix: exclude the whole starter-* family by prefix in isExcluded (starters are contributor scaffolds, categorically not wired through aimock; safe because no showcase-* backend name starts with starter-, so it never over-excludes a real backend), and add bare harness-workers to the infra exclude set. Superseded showcase-starter-* literals removed; inert showcase-shell-* legacy literals retained to keep the diff minimal. The 20 showcase-* LLM backends were already re-wired via Railway AIMOCK_URL; this is a naming/exclusion fix only (no starter is repointed). |
||
|
|
54989f31f5 |
chore(codeowners): drop nonexistent packages/v2/angular path
The repo uses a flat package layout; there is no packages/v2/ directory, so that CODEOWNERS entry never matches and fails path validation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
15a8ddb0f7 |
fix(channels-intelligence): address adversarial CR findings (OSS-497)
Follow-up to the OSS-497 robustness cluster after a 3-agent adversarial review.
- Empty-text update: reclassify from a hard {ok:false} failure to a logged
no-op. The failure path threw up the run and nack'd the WHOLE turn retryably,
re-running valid work to dead-letter for a condition a retry can't fix (an
agent clearing a message body the post-only fallback can't express). Now it is
skipped with a distinct warning log — surfaced, not silent, and not turn-fatal.
- Injectable fetch: plumb it through the transports. Both HttpDeliverySource and
RealtimeGatewayTransport now accept a binary-capable /
and forward it to IntelligenceFileHistoryClient, so a consumer injecting a
fetch has it honored on the download/history/upload paths (previously the
client-level injectable was reachable only by constructing the client directly).
- coerceWireProjectId: use Number.isSafeInteger so a numeric string beyond
MAX_SAFE_INTEGER is rejected (was silently coerced to a lossy integer).
- Malformed-present projectId: log before falling back to the transport scope
(an absent projectId still falls back quietly), mirroring the loud drops in
the same function instead of silently masking a wire-corruption signal.
- Restart safety: guard the recurring heartbeat with a run generation so a
stop->start while a heartbeat POST is in flight can't leave two reschedule
chains doubling the heartbeat rate; bump the generation on stop().
- Remove now-dead lastHeartbeatAt state (only reader was the removed in-loop
heartbeat check).
Tests: empty-update now asserts log + no-op; added transport fileFetch
wire-through, adapter-from-route fallback, no-unhandled-rejection after a
mid-turn stop, heartbeat-stops-after-stop, elapsed-time bounds on the stop()
promptness tests, and coerceWireProjectId edge cases (leading zeros, > safe int).
nx test (green) + check-types (clean).
|
||
|
|
81562f5b24 |
docs(channels-intelligence): restore assertValidChannelRealtimeScope doc placement
The coerceWireProjectId export (added for OSS-497) was inserted directly above assertValidChannelRealtimeScope, displacing that function's doc comment onto the helper. Reorder so each function carries its own docblock. |
||
|
|
0d412f3bb8 |
fix(channels-intelligence): honor an injectable fetch in the file/history client
IntelligenceFileHistoryConfig's fetchFile/getHistory/uploadFile hardcoded
globalThis.fetch, so a consumer (or test) injecting a fetch had it honored
everywhere EXCEPT the binary download, history, and upload paths. The transport's
FetchLike is text-only ({ ok, status, text() }) and cannot satisfy the binary
client (.body/.arrayBuffer()/.headers/.json()), so add the client's OWN
injectable full `fetch` (typeof fetch) on its config, resolved via a single
resolveFetch() helper that falls back to the global fetch. Each caller keeps its
existing degrade/throw contract for the no-fetch runtime.
Surfaced by the pre-merge CR of #5983 (OSS-497).
|
||
|
|
5ebbbd1e2f |
fix(ci): stop canary dispatches from false-paging the release notifier (#6036)
## What broke Run [29605894890](https://github.com/CopilotKit/CopilotKit/actions/runs/29605894890) — a `workflow_dispatch` canary on branch `canary/mme-subagents-29605883024-1` — fired a red `🔴 CopilotKit release notifier failed — a release alert may have been swallowed` page to #engr. No release was actually missed (`npm latest` was `1.63.1`, published fine). It was a false page. ## Why it false-paged The canary build died at `pnpm install --frozen-lockfile` (`ERR_PNPM_OUTDATED_LOCKFILE` — `@ag-ui/client` lockfile `0.0.57` vs manifest `0.0.58` on the source branch). That flipped the `notify` job to `failure()`, which triggered its self-watchdog "best-effort Slack" post. That self-alert is gated on release intent (`npm_intended || py_intended`), and the `npm_intended` computation treated **any** `workflow_dispatch` as stable-release intent: ``` NPM_INTENDED="${{ (github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/publish/'))) && 'true' || 'false' }}" ``` But `canary.yml` orchestrates `publish-release.yml` with `-f mode=prerelease` on a short-lived `canary/*` ref (see `canary.yml` line ~196). A prerelease/canary is explicitly *not* a stable release: it pushes no tag, cuts no GH Release, and posts nothing to Slack (the notification builder returns `should_post=false` for it). So a failed canary build had no business self-paging. ## The fix **Fix 1 (this PR):** Narrow the `npm_intended` dispatch arm with `inputs.mode != 'prerelease'`, so a canary dispatch yields `npm_intended=false` and never self-pages, while a stable dispatch (default mode + the main-only retry escape hatch) and a merged `release/publish/*` PR still page correctly on genuine failure. Using `!= 'prerelease'` (rather than `== 'stable'`) matches the existing `mode != 'prerelease'` guards on the publish/tag steps and correctly treats an empty/default mode as stable. before: ``` NPM_INTENDED="${{ (github.event_name == 'workflow_dispatch' || (...)) && 'true' || 'false' }}" ``` after: ``` NPM_INTENDED="${{ ((github.event_name == 'workflow_dispatch' && inputs.mode != 'prerelease') || (...)) && 'true' || 'false' }}" ``` **Fix 2 (follow-up, not in this PR — deliberately):** The stale-lockfile source. `pnpm install --frozen-lockfile` *already is* the lockfile-consistency check and it failed loudly and correctly — adding another pre-check to this workflow would be redundant. The lockfile drift lives on the `canary/mme-subagents-*` source branch, produced by the `mme-subagents` agent-orchestration automation (it bumped a manifest without regenerating `pnpm-lock.yaml`). The right place to fix that is that generator, which is out of scope for this workflow file. Filing as a follow-up rather than over-engineering a guard here. ## Red-green proof The failure surface is the intent-gate bash expression. Extracted the exact `npm_intended` boolean (pre- and post-fix) into isolated scripts and ran the real canary inputs. **RED (pre-fix logic):** ``` $ EVENT_NAME=workflow_dispatch INPUT_MODE=prerelease PR_HEAD_REF="canary/mme-subagents-29605883024-1" bash gate_pre.sh npm_intended=true # BUG: canary treated as stable-release intent → self-pages ``` **GREEN (post-fix logic):** ``` $ EVENT_NAME=workflow_dispatch INPUT_MODE=prerelease PR_HEAD_REF="canary/mme-subagents-29605883024-1" bash gate_post.sh npm_intended=false # canary no longer self-pages # no regression on genuine-release paths: $ EVENT_NAME=workflow_dispatch INPUT_MODE=stable # stable dispatch (retry hatch) npm_intended=true $ EVENT_NAME=workflow_dispatch INPUT_MODE="" # stable dispatch, default mode npm_intended=true $ EVENT_NAME=pull_request PR_MERGED=true PR_HEAD_REF="release/publish/monorepo/v1.63.1" npm_intended=true $ EVENT_NAME=pull_request PR_MERGED=true PR_HEAD_REF="feature/some-branch" # non-release merge npm_intended=false ``` ## Lint `actionlint .github/workflows/publish-release.yml` → exit 0 (clean). This matches the repo's `Lint Release Workflows` CI job (actionlint, `level=error`, same file in scope). |
||
|
|
ba38ee0e5f |
fix(channels-intelligence): heartbeat during long turns and make stop() prompt
Two coupled HttpDeliverySource runLoop lifecycle robustness fixes: Heartbeat starvation: the loop heartbeated only at the top of each iteration, then blocked on onDelivery for up to turnTimeoutMs (120s). With a 15s cadence, a turn longer than the cadence sent no heartbeat for its whole duration, so app-api could expire the activation and mark a healthily-working runtime stale mid-turn, withholding new deliveries. Move heartbeating to a standalone recurring timer that fires on cadence regardless of what the loop is doing (rescheduling only after each heartbeat settles, so no overlap). Shutdown latency: stop() set running=false then awaited the loop, but the loop only rechecked after its current sleep (up to cadence, idle) or onDelivery (up to turnTimeoutMs, mid-turn) — the sleeps were unref'd but not interruptible, so shutdown could take up to 15s idle or 120s mid-turn. Add a stopWait promise that stop() resolves; the poll-sleep and the turn-wait both race it, so shutdown is prompt. A mid-turn stop leaves the lease for app-api to re-lease and does NOT nack (the turn didn't fail); the turn's eventual settlement is always handled so a post-stop rejection never surfaces as unhandled. Surfaced by the pre-merge CR of #5983 (OSS-497). |
||
|
|
787264fa8f |
fix(channels-intelligence): accept a numeric-string projectId in the realtime scope build
RealtimeGatewayTransport.toIngressEnvelope built the per-delivery scope with
`typeof delivery.projectId === 'number' ? delivery.projectId : this.scope.projectId`,
while organizationId/channelId were String()-coerced. The delivery.available
payload is untyped JSON, so a numeric-string projectId ('9' on the wire) failed
the strict check and silently substituted the transport-default projectId —
defeating the per-delivery scope authority and routing the render-accept under
the wrong project.
Extract a `coerceWireProjectId` helper (number or numeric-string -> positive
integer, else undefined) and use it with a fallback to the transport default.
Surfaced by the pre-merge CR of #5983 (OSS-497).
|
||
|
|
46517eecd9 |
fix(channels-intelligence): derive egress adapter from the reply route
HttpEgressSink.emit posted the static `this.cfg.adapter` (default "slack") alongside a possibly-Teams `replyTarget`, so a Teams delivery through the fallback egress carried a contradictory `adapter: "slack"`. app-api's egress route (sendChannelEgressMessage) routes on `replyTarget.adapter` + channelName and ignores this top-level field, so this is latent — but one provider-agnostic runtime serves every adapter its bot has attached, so posting the config default is misleading. Derive the posted adapter from the delivery's own reply route, falling back to the config adapter when the route carries none. Note: the listener heartbeat's declaredChannels[].adapter is NOT changed here — app-api genuinely consumes it (the channel_adapter_configs.provider join) for per-adapter health/conflict reporting, and the runtime only knows its single configured adapter; declaring the bot's full adapter set is a separate design change, not this robustness cleanup. Surfaced by the pre-merge CR of #5983 (OSS-497). |
||
|
|
af68620640 |
fix(ci): stop canary dispatches from false-paging the release notifier
The notify job's intent gate treated ANY workflow_dispatch as stable-release intent (npm_intended=true), including prerelease/canary dispatches. canary.yml orchestrates publish-release.yml with mode=prerelease on a short-lived canary/* ref, so a canary whose build fails (e.g. a stale pnpm-lock.yaml on the source branch tripping pnpm install --frozen-lockfile) flipped the notify job to failure() and fired the self-watchdog's best-effort Slack post — a red 'a release alert may have been swallowed' page for a release that was never going to publish. Narrow the npm_intended dispatch arm with 'inputs.mode != prerelease' so a canary yields npm_intended=false and never self-pages, while a stable dispatch (default mode, and the main-only retry escape hatch) and a merged release/publish/* PR still page correctly on genuine failure. This mirrors the existing 'mode != prerelease' guards on the publish/tag steps and the builder's should_post=false suppression for canaries. |
||
|
|
060cebf304 |
fix(channels-intelligence): fail loud on empty-text egress update
HttpEgressSink.emit ran the `if (!text) return { ok: true }` no-op guard for
both post and update ops. An empty POST is a legitimate no-op (nothing to say),
but an empty UPDATE — a real intent such as clearing a message body that this
post-only fallback egress cannot express — was acked as success while nothing
was sent, inconsistent with the module's fail-loud posture. Return
`{ ok: false, code: 'empty_update' }` for that case; empty posts still no-op.
Surfaced by the pre-merge CR of #5983 (OSS-497).
|
||
|
|
0b69bb4b4d |
docs(channels): clarify read_thread getMessages is text-only (OSS-488)
The getMessages JSDoc cited "what was in the image" as a use case, but image/file parts contribute no text in this mapping — read_thread is text-only. Image content reaches the model only via conversationStore's seeding of agent.messages. Tweak the comment so it no longer implies read_thread can see image content. |
||
|
|
a6768cf1e6 |
docs: add Thread & History Lifecycle to docs-render expected nav order
The new Threads page shifts the expected navigation order; update the expected array in docs-render.test.ts to include "Thread & History Lifecycle" after "Overview" so the shell-docs suite passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fa319dffe5 |
docs: address CR on threads-lifecycle guide (agentId prop, per-framework nav, persistence caveat)
Addresses samjulien's review on #5988: - Use `agentId` (not `agent`) in both <CopilotChat> snippets — `agent` is not a v2 prop. - Add threads-lifecycle to every authored-framework Threads nav group (built-in-agent, mastra, crewai-flows, llamaindex, agno, ag2, pydantic-ai, microsoft-agent-framework, deepagents). Extracted the page body into a shared snippet and made the root + per-framework pages thin wrappers, matching the existing threads/headless-threads/threads-import single-source convention. - Qualify the "Run" glance statement: server-side persistence/replay depends on a configured store (Enterprise Intelligence or a persisting AgentRunner); a runtime with no persistence layer keeps nothing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
06f331b9a2 |
docs(channels): add Channels SDK documentation
Documents the channels SDK across Slack, Teams, WhatsApp, Telegram, and Discord. |
||
|
|
4278cec65a |
merge main; resolve nav conflict + repoint links to restructured threads docs
- meta.json: keep main's Threads nav group and slot `threads-lifecycle` in after the `threads` overview. - threads-lifecycle.mdx: main split the old `/threads` page into a `threads` overview + a `headless-threads` how-to; repoint the two "Headless Threads" links from `/threads` to `/headless-threads`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e72e9b8f9c |
chore: release channels v0.2.1 (#6025)
## Release channels v0.2.1 **Scope:** `channels` | **Bump:** `patch` --- ### How this release process works 1. **This PR was created automatically** by the "release / create-pr" workflow. It bumped the `channels` packages to `0.2.1` and generated AI-enhanced release notes. 2. **CI runs on this PR** — the full test suite (unit tests, lint, type checks, build) must pass before merging. This is the review gate. 3. **Review the release notes** in `release-notes.md` in this PR. If a Notion draft was created, you can edit the release notes there before merging. 4. **When this PR is merged**, the `release / publish` workflow automatically: - Builds all packages - Publishes the `channels` packages to npm at version `0.2.1` - Creates git tag `channels/v0.2.1` - Creates a GitHub Release with the final release notes ### Before merging - [ ] CI is green (tests, lint, types, build) - [ ] Version bumps look correct - [ ] Release notes are accurate (edit in Notion if a draft was created) --- > **Do not merge until CI is fully green.** The full test suite runs automatically on this PR.channels/v0.2.1 |
||
|
|
21b9a7138b | chore: release channels v0.2.1 | ||
|
|
c51b84a349 |
fix(showcase): offload sync LLM calls off showcase agent event loops (claude-sdk-python, ag2, llamaindex) (#5997)
## What The `claude-sdk-python` showcase agent `:8000` wedges under D6/LLM load: two **synchronous** `anthropic.Anthropic().messages.create()` calls run directly on the uvicorn asyncio event loop, freezing it for the full LLM round-trip so `/health` stops answering. The watchdog counts 3 consecutive failures (~90s) and kill-restarts the container, dropping active sessions. This is the pre-existing root cause behind the #oss-alerts restart noise that #5987's alerting surfaced (it was never a #5987 regression). ## Root cause (sync-in-async) All in `integrations/claude-sdk-python/`: - **`src/agents/agent.py`** — `_execute_tool`'s `generate_a2ui` branch builds a sync `anthropic.Anthropic()` and calls `client.messages.create()` synchronously. `_execute_tool` is a sync callback invoked on the loop from **two** async callers: `run_agent`'s agentic loop, and the Claude-Agent-SDK MCP tool handler in `claude_agent_sdk_adapter.py`. - **`src/agents/a2ui_dynamic.py`** — `_generate_a2ui`, same sync pattern, invoked on the loop from the `run_a2ui_dynamic_agent` generator. ## Fix — approach (a): `await asyncio.to_thread(...)` at the call sites Chosen over approach (b) (`AsyncAnthropic` + `async def`) because it is the **lowest blast radius**: the sync functions and the shared `ExecuteTool` callback type stay unchanged, and wrapping at the call sites fixes the **whole** tool-dispatch path uniformly (any current or future sync tool in the dispatcher), not just `generate_a2ui`. Approach (b) would only fix `generate_a2ui` unless `_execute_tool` were made fully async — which ripples into the `ExecuteTool` type and both call sites anyway. Every occurrence fixed (file:line): - `src/agents/agent.py:~1355` — `run_agent` call site → `await asyncio.to_thread(_execute_tool, ...)` - `src/agents/claude_agent_sdk_adapter.py:~152` — MCP `sdk_tool_handler` → `await asyncio.to_thread(execute_tool, ...)` - `src/agents/a2ui_dynamic.py:~287` — secondary call site → `await asyncio.to_thread(_generate_a2ui, ...)` Whole-integration grep for the pattern (`anthropic.Anthropic(`, sync `.messages.create`, `OpenAI(`, `time.sleep`, blocking I/O): only these two sites existed. Every other agent in the integration already uses `AsyncAnthropic`. ## Showcase parity verdict `a2ui_dynamic.py` is **per-integration**, not shared. Each framework (langgraph-python, llamaindex, ms-agent-python, pydantic-ai, ag2, strands, agno, …) ships its own copy that "mirrors" langgraph-python but uses that framework's own client. Only claude-sdk-python's copy used the sync `anthropic.Anthropic()` pattern, so **blast radius is claude-sdk-python only** — no other integration has this wedge. The `tools` symlink (→ `showcase/shared/python/tools`) was **not** touched (iron-rule: edit shared source only, never symlink copies; here no shared change was needed). ## entrypoint.sh alert scoping - `:8000` agent watchdog branch: **removed** the Slack POST, **kept** the `kill -9 $AGENT_PID` self-heal. It now self-heals silently (root cause fixed). - Public `$PORT` `/api/health` branch: Slack POST to `$SLACK_WEBHOOK_OSS_ALERTS` **intact** — the public Next.js wedge still pages. ## Red → green proof Faithful harness at `showcase/tests/repro/async-wedge/`: a real `anthropic.Anthropic` sync client (real httpx transport) pointed via `ANTHROPIC_BASE_URL`/`base_url` at a controllable slow local Anthropic-compatible endpoint (`slow_anthropic.py`, `SLOW_SECONDS` latency, serves both `messages.create` JSON and `messages.stream` SSE). 5× concurrent `POST /generate`; poll `/health` 1/s for 10s; assert & exit non-zero on a false result. **Minimal replica (`server.py`) — the load-bearing construct:** ``` REPLICA RED (FIXED=0, sync client on loop): ASSERT_SUMMARY is_fixed=0 ok=6 wedge=4 → PASS RED REPLICA GREEN (FIXED=1, asyncio.to_thread): ASSERT_SUMMARY is_fixed=1 ok=10 wedge=0 → PASS GREEN ``` **Real production code (`prod_server.py`):** ``` PROD GENERATOR GREEN (real run_a2ui_dynamic_agent, fix in source): ASSERT_SUMMARY expect=green ok=10 wedge=0 → PASS GREEN (secondary sync _generate_a2ui fired 5x via to_thread; /health stayed 200) MUTATION GUARD RED (real _generate_a2ui, sync-on-loop): ASSERT_SUMMARY expect=red ok=5 wedge=5 → PASS RED (proves the harness fails on the bug) PROD DIRECT GREEN (real _generate_a2ui via to_thread): ASSERT_SUMMARY expect=green ok=10 wedge=0 → PASS GREEN ``` The mutation guard exercises the **real** production `_generate_a2ui` sync-on-loop and confirms it wedges (`wedge=5`), while the `to_thread` path stays fast-200 (`wedge=0`) — the harness is not vacuously green. ## Regression check `pytest tests/python/` → **6 passed**. `ruff format --check` clean on all 3 edited files (no new lint; the pre-existing origin/main ruff findings are untouched, out of scope). `bash -n` clean on entrypoint.sh and all repro scripts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- ## CR round 2 — repro hardening Addressed the one mandatory CR finding (M1) plus two optional items on the `showcase/tests/repro/async-wedge/` harness. **Repro-harness only — no production source touched** (`src/agents/a2ui_dynamic.py` unchanged). **M1 (mandatory) — MODE=generator false-green closed.** The GREEN lane previously asserted only `WEDGE==0`. If the mock's SSE were mis-parsed, the `generate_a2ui` tool_use dropped, or the generator early-exited, `_generate_a2ui` (the bug site) would never run, the loop would never park, and `WEDGE==0` would pass trivially — proving nothing. Fix: `prod_server.py` now wraps the real `a2ui_dynamic._generate_a2ui` with a counter (`tool_dispatch_fired`), exposed via `/stats` and the `/generate` response. `run_prod.sh` reads it after the poll window and the GREEN lane now **fails (exit 6)** unless `tool_dispatch_fired >= 1`. The counter tracks genuine execution regardless of call shape (`asyncio.to_thread` offload or sync-on-loop). **O1** — `slow_anthropic.py` now uses `await asyncio.sleep(SLOW_SECONDS)` instead of blocking `time.sleep`, so the mock's own loop stays free under concurrency. **O2** — `run.sh` adds a 0.5s gap after firing load, before the first `/health` poll, so poll 1 isn't wasted on a pre-block fast-200. Also fixed a latent hang: `run_prod.sh` ended with a bare `wait` that blocked forever on the long-lived uvicorn server jobs, so the summary/assertion never printed. It now tracks and reaps only the load-curl PIDs (bash-3.2 / `set -u` safe). ### Red-green proof (harness assertion) RED (false-green injected via `REPRO_DROP_TOOL_USE=1` — tool_use dropped, generator drains but never dispatches; env-only, no source mutation): ``` ASSERT_SUMMARY expect=green ok=10 wedge=0 tool_dispatch_fired=0 FAIL GREEN: 0 wedges but tool_dispatch_fired=0 — _generate_a2ui never ran; WEDGE==0 is a false green (bug site never exercised) EXIT=6 ``` Old assertion (`WEDGE==0` only) would have PASSED here; hardened assertion correctly FAILS. GREEN (normal operation — tool dispatch fires): ``` ASSERT_SUMMARY expect=green ok=10 wedge=0 tool_dispatch_fired=9 PASS GREEN: 0 wedges AND tool_dispatch_fired=9 (>=1) — real production code exercised the bug site and kept /health fast-200 under load EXIT=0 ``` ### No regression — deterministic MODE=direct mutation guard ``` MODE=direct EXPECT=red -> ASSERT_SUMMARY expect=red ok=8 wedge=2 tool_dispatch_fired=3 -> PASS RED (exit 0) MODE=direct EXPECT=green -> ASSERT_SUMMARY expect=green ok=10 wedge=0 tool_dispatch_fired=3 -> PASS GREEN (exit 0) ``` --- ## Fold-in: ag2 + llamaindex (fleet-wide sweep) A CR flagged the same sync-LLM-on-the-event-loop wedge in two more integrations, and a fleet-wide sweep of every `integrations/*/src/**` Python file for a sync LLM `.create()` running **directly inside an `async def`** on the uvicorn loop found a THIRD previously-missed site. All three are now fixed with the same lowest-blast-radius approach (extract the blocking round-trip into a sync `_generate_a2ui`, and `await asyncio.to_thread(...)` from the async wrapper): - `integrations/ag2/src/agents/beautiful_chat.py` — `async def generate_a2ui` (sync `openai.OpenAI().chat.completions.create`) - `integrations/llamaindex/src/agents/a2ui_dynamic.py` — `async def generate_a2ui` (sync `OpenAI().chat.completions.create`) - `integrations/llamaindex/src/agents/agent.py` — `async def generate_a2ui` (sync `OpenAI().chat.completions.create`) — **found by the sweep, not in the original report** ### Fleet-sweep result (what was NOT touched, and why) - **ag2 `agent.py` + `a2ui_dynamic.py`**: already non-blocking (`await _async_openai_client.chat.completions.create`, i.e. `AsyncOpenAI`). Confirmed, not re-touched. - **agno, ms-agent-python, pydantic-ai, strands, crewai** sync `.create` sites: all inside plain `def` framework tools (`@tool`-style), which the frameworks dispatch in their own worker/executor context — never bare-`await`ed on the loop. No wedge. - **`.ts` voice routes / `agent_server.ts`**: TypeScript/Node, not the uvicorn asyncio loop. - **`llama_index.llms.openai.OpenAI(model=…)`** objects: framework-managed async LLMs, distinct from the raw `openai` SDK client at the wedge sites. So the wedge exists at exactly these three `async def generate_a2ui` sites (plus the two claude-sdk-python sites fixed above). All three edited files are per-integration REAL files (not symlinks to `showcase/shared/`) — verified per the iron rule. `ag2`/`llamaindex` `entrypoint.sh` were intentionally **not** touched — the Slack-alert scoping was specific to the claude-sdk-python incident. ### Red → green proof (real production code, deterministic) New OpenAI-SDK repro harness (dev-only, under `showcase/tests/repro/async-wedge/`, sibling of the anthropic one): a real `openai` SDK client (real httpx transport) pointed via `OPENAI_BASE_URL` at a controllable slow local OpenAI-compatible endpoint (`slow_openai.py`). `run_prod_openai.sh` drives the **real production `_generate_a2ui`** for each `TARGET`, 5× concurrent `POST /generate`, poll `/health` 1/s ×10, with the `tool_dispatch_fired >= 1` anti-false-green guard. ``` ag2-beautiful-chat RED: ASSERT_SUMMARY expect=red ok=5 wedge=5 tool_dispatch_fired=5 -> PASS RED ag2-beautiful-chat GREEN: ASSERT_SUMMARY expect=green ok=10 wedge=0 tool_dispatch_fired=5 -> PASS GREEN llamaindex-agent RED: ASSERT_SUMMARY expect=red ok=5 wedge=5 tool_dispatch_fired=5 -> PASS RED llamaindex-agent GREEN: ASSERT_SUMMARY expect=green ok=10 wedge=0 tool_dispatch_fired=5 -> PASS GREEN llamaindex-a2ui RED: ASSERT_SUMMARY expect=red ok=5 wedge=5 tool_dispatch_fired=5 -> PASS RED llamaindex-a2ui GREEN: ASSERT_SUMMARY expect=green ok=10 wedge=0 tool_dispatch_fired=5 -> PASS GREEN ``` Source-level cross-check driving the **real on-disk `async generate_a2ui` wrapper** directly: 5 concurrent 3s calls complete in ~3.1s (parallel offload threads, not serialized ~15s) while a bare asyncio heartbeat keeps ticking (64 ticks through the load window) — loop stays free. Negative control (ag2 wrapper temporarily reverted to sync-on-loop): 15.11s serialized, heartbeat starved to 4 ticks — wedge reproduced; source restored. Existing claude-sdk-python lanes re-run green (no regression). |
||
|
|
8aa763aa15 | style: auto-fix formatting | ||
|
|
bf3f327f8c |
fix(showcase): offload sync LLM calls in ag2 + llamaindex a2ui generators
A fleet-wide sweep for sync LLM .create() calls running directly inside an async def on the uvicorn event loop found three more wedge sites (same class as the claude-sdk-python fix in this PR): - integrations/ag2/src/agents/beautiful_chat.py - integrations/llamaindex/src/agents/a2ui_dynamic.py - integrations/llamaindex/src/agents/agent.py (missed by the original report) Each extracts the blocking secondary-LLM round-trip into a sync _generate_a2ui helper and offloads it via await asyncio.to_thread(...) from the async generate_a2ui wrapper (lowest blast radius; sync body unchanged). ag2's other agents already use AsyncOpenAI; all other sync .create sites are inside plain def framework tools dispatched off-loop by their frameworks, so they do not wedge. entrypoint.sh alert-scoping left untouched (claude-sdk-python-specific). Adds a dev-only OpenAI-SDK repro harness (slow_openai.py, prod_server_openai.py, run_prod_openai.sh) that drives the REAL production _generate_a2ui via a slow local OpenAI-compatible endpoint, with a tool_dispatch_fired>=1 anti-false-green guard. RED (sync-on-loop) -> GREEN (to_thread) verified for all three sites. |
||
|
|
e49a902741 |
fix(release): make packed Channels umbrella verify hermetic (#6022)
## Problem Every **monorepo** release PR is blocked by its own not-yet-published version. The `test / unit` → **`unit (20.x)` → "Verify packed Channels umbrella contract"** step (`pnpm run verify:channels-umbrella`, local mode) fails with: ``` ERR_PNPM_NO_MATCHING_VERSION No matching version found for @copilotkit/core@^1.63.1 This error happened while installing the dependencies of @copilotkit/channels@0.2.0 ``` ### Root cause `verify:channels-umbrella` (local mode) packs the Channels family into tarballs and installs a throwaway consumer, pinning **only the family** to those local tarballs via `pnpm.overrides`. But the family depends on monorepo-versioned packages — `@copilotkit/core`, `@copilotkit/shared` — via the `workspace:` protocol. `pnpm pack` rewrites `workspace:^` to the workspace's *current* version (e.g. `^1.63.1`). On a release PR that's the freshly-bumped version, which isn't on npm until the release publishes. Since those packages aren't in the override set, the consumer install falls through to the public registry and can't find them → deadlock: the gate can't go green until the very version it's bumping to is published, and publishing only happens after merge. This is why `unit (20.x)` was red on **#5992 (v1.63.0)** and **#6019 (v1.63.1)** — both merged through the red manually. ## Fix In local mode, also `pnpm pack` the family's `workspace:` monorepo siblings and pin them as consumer overrides, so the install is fully hermetic and never races the registry against our own in-flight release. Siblings are discovered **transitively via the `workspace:` protocol**, so the list never drifts as the family's internal dependencies change (today it resolves to exactly `@copilotkit/core` + `@copilotkit/shared`; `@copilotkit/shared`'s `license-verifier ~0.5.0` is correctly excluded since it's a normal published range, not `workspace:`). The registry-backed mode (`--registry`, used at publish time in `publish-release.yml`) is unchanged. ## Testing - Bumped `packages/shared` + `packages/core` to an unpublished `1.63.99` to simulate a release PR: - **Unmodified script** → `ERR_PNPM_NO_MATCHING_VERSION` for `@copilotkit/core@^1.63.99` (reproduces the CI failure). - **Fixed script** → `OK: local Channels snapshot is exact, compatible, singly resolved, and TSX-consumable.` - `npx vitest run --config scripts/release/vitest.config.mts` → **109/109 pass**. - Sibling discovery verified to return exactly `[@copilotkit/core, @copilotkit/shared]`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
d221c03f02 |
fix(release): make packed Channels umbrella verify hermetic
The local `verify:channels-umbrella` check packs the Channels family and installs a throwaway consumer, overriding only the family tarballs. The family depends on monorepo-versioned packages (`@copilotkit/core`, `@copilotkit/shared`) via the `workspace:` protocol, which `pnpm pack` rewrites to the workspace's current version. On a release PR that version is the freshly-bumped one that isn't on the registry until the release publishes, so the consumer install fell through to npm and failed with ERR_PNPM_NO_MATCHING_VERSION — the unit(20.x) red that blocked every monorepo release PR (e.g. #5992, #6019) on its own not-yet-published version. Pack those workspace siblings locally too and pin them as consumer overrides, so local mode never races the registry against our own in-flight release. Siblings are discovered transitively via the `workspace:` protocol so the list never drifts as the family's internal dependencies change. Verified by bumping shared+core to an unpublished version: the old script fails with ERR_PNPM_NO_MATCHING_VERSION, the fixed script reports OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0c9d639b13 |
chore: release monorepo v1.63.1 (#6019)
## Release monorepo v1.63.1 **Scope:** `monorepo` | **Bump:** `patch` --- ### How this release process works 1. **This PR was created automatically** by the "release / create-pr" workflow. It bumped the `monorepo` packages to `1.63.1` and generated AI-enhanced release notes. 2. **CI runs on this PR** — the full test suite (unit tests, lint, type checks, build) must pass before merging. This is the review gate. 3. **Review the release notes** in `release-notes.md` in this PR. If a Notion draft was created, you can edit the release notes there before merging. 4. **When this PR is merged**, the `release / publish` workflow automatically: - Builds all packages - Publishes the `monorepo` packages to npm at version `1.63.1` - Creates git tag `monorepo/v1.63.1` - Creates a GitHub Release with the final release notes ### Before merging - [ ] CI is green (tests, lint, types, build) - [ ] Version bumps look correct - [ ] Release notes are accurate (edit in Notion if a draft was created) --- > **Do not merge until CI is fully green.** The full test suite runs automatically on this PR.v1.63.1 |
||
|
|
a7459f4fb2 | chore: release monorepo v1.63.1 | ||
|
|
2296aa5b89 |
feat(channels): canonical cross-platform reaction normalization (Teams & WhatsApp) (#6011)
## What Canonical cross-platform reaction normalization for **Teams** and **WhatsApp**, plus the managed (Intelligence) delivery path. `onReaction` handlers now receive one canonical `emoji` value regardless of provider — a 🔄 arrives as `refresh` whether it came from Teams (`1f504_refresh`), Slack (`arrows_counterclockwise`), Discord/Telegram/WhatsApp (unicode), or the managed Intelligence path. ## Why Teams sends emoji reactions as `<unicode-codepoint>_<name>` codes (e.g. `1f504_refresh`) plus classic bare names (`like`/`heart`/…), and WhatsApp wasn't a normalized platform — so consumers had to match provider-specific tokens. Normalization already lived centrally in `channels-core` for Slack/Discord/Telegram; this extends it to every adapter we ship. ## Changes - **channels-ui/emoji**: `EmojiPlatform` → `slack | discord | telegram | teams | whatsapp`; new canonical entries (`refresh`, `laugh`, `surprised`, `sad`, `angry`); Teams normalization (codepoint parse + classic-name map; an out-of-range codepoint degrades to passthrough, never throws); WhatsApp on the unicode path. - **channels-core**: `teams`+`whatsapp` added to `EMOJI_PLATFORMS`; `IncomingReaction` gains an optional source `platform`; `onReaction` normalizes by `evt.platform ?? adapter.platform`. - **channels-intelligence**: the managed reaction dispatch forwards the delivery's source `platform`, so managed-path reactions normalize too. ## Tests channels-ui (25), channels-core (156), channels-intelligence (170) — all green; the three packages build clean. Covers Teams codepoint + classic + malformed(out-of-range) + unknown-passthrough, WhatsApp unicode, and managed-path source-platform normalization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
90720cd188 |
chore(deps): update github actions (#6002)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [actions/setup-java](https://redirect.github.com/actions/setup-java) | action | minor | `v5.5.0` → `v5.6.0` | | [ruby/setup-ruby](https://redirect.github.com/ruby/setup-ruby) | action | minor | `v1.318.0` → `v1.319.0` | | [slackapi/slack-github-action](https://redirect.github.com/slackapi/slack-github-action) | action | major | `v3.0.5` → `v4.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-java (actions/setup-java)</summary> ### [`v5.6.0`](https://redirect.github.com/actions/setup-java/compare/v5.5.0...v5.6.0) [Compare Source](https://redirect.github.com/actions/setup-java/compare/v5.5.0...v5.6.0) </details> <details> <summary>ruby/setup-ruby (ruby/setup-ruby)</summary> ### [`v1.319.0`](https://redirect.github.com/ruby/setup-ruby/compare/v1.318.0...v1.319.0) [Compare Source](https://redirect.github.com/ruby/setup-ruby/compare/v1.318.0...v1.319.0) </details> <details> <summary>slackapi/slack-github-action (slackapi/slack-github-action)</summary> ### [`v4.0.0`](https://redirect.github.com/slackapi/slack-github-action/compare/v4.0.0...v4.0.0) [Compare Source](https://redirect.github.com/slackapi/slack-github-action/compare/v4.0.0...v4.0.0) ### [`v4.0`](https://redirect.github.com/slackapi/slack-github-action/compare/v4.0.0...v4.0.0) [Compare Source](https://redirect.github.com/slackapi/slack-github-action/compare/v4.0.0...v4.0.0) ### [`v4`](https://redirect.github.com/slackapi/slack-github-action/compare/v3.0.5...v4.0.0) [Compare Source](https://redirect.github.com/slackapi/slack-github-action/compare/v3.0.5...v4.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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNTkuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI2NS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> |
||
|
|
1110f18e30 |
fix(release): exempt first-party publishes from the release-age gate
The packed Channels umbrella (and the runtime-package verify) install first-party packages straight from the registry, so a just-published @copilotkit/* version fails pnpm's 24h minimumReleaseAge check — this is what reddened `unit (20.x)` on channels PRs. The enumerated exclude lists drift every time an internal dependency is added (main already had to bolt on @copilotkit/channels-core, then #6012 added @copilotkit/core + shared). Wildcard the @copilotkit/* scope (org-owned, same publish pipeline — no outsider can inject a package, and it stops the drift). Keep @ag-ui enumerated rather than wildcarded: it's a separate upstream org, so we don't extend immediate-install trust to its entire scope. Third-party deps stay fully gated. Applied to both the generated consumer workspace and the repo-root .npmrc. Verified: `pnpm run verify:channels-umbrella` installs the packed umbrella with no ERR_PNPM_NO_MATURE_MATCHING_VERSION; umbrella unit tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
052dc0948e |
fix(channels): address CR — case-insensitive Teams hex + test/comment hygiene
- emoji: match Teams codepoint hex case-insensitively (providers may send upper-case) + document single-codepoint-only parsing; cover 1F504_refresh. - emoji: note the dormant outbound Teams asymmetry in toPlatformEmoji (unicode vs `<codepoint>_<name>`) as a TODO for when outbound reactions land. - channels-slack: split the tool-only status-clear test so one mirrors the real event order (RUN_FINISHED before finish()) and one isolates the finish() backstop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |