Commit Graph

4309 Commits

Author SHA1 Message Date
Jordan Ritter 5cfb6ac717 test(showcase/langgraph-typescript): make watchdog tests genuinely gate
The watchdog test cluster in test_entrypoint_watchdog.py had three soundness
defects that let tests pass without exercising real behavior.

1. test_size_pid_reaped_by_in_subshell_trap asserted the literal
   trap kill $SIZE_PID string, which exists ONLY in an explanatory comment
   describing the old design. The shipped code registers
   trap _reap_watchdog_children EXIT, so the test passed even when the real
   trap registration was removed. Now asserts the shipped registration on an
   executable (comment-stripped) line and that the named handler is defined.

2. test_size_pid_reaped_on_watchdog_exit_behavioral wrote its own mock
   watchdog using the older SIZE_PID trap shape and never invoked shipped
   code, so it passed even when the shipped reaper was gutted. Now extracts
   the real _agent_descendants and _reap_watchdog_children definitions
   verbatim from entrypoint.sh and drives them with the shipped order.

3. dummy_was_killed used a single-shot dummy.poll() immediately after the
   run returned, racing the asynchronous SIGKILL delivery. Replaced with
   bounded _wait_process_exited / _assert_still_alive helpers selected by
   return code, making the killed/alive assertion deterministic.

entrypoint.sh unchanged. 22 passed; determinism confirmed by repeated runs.
2026-07-13 11:15:25 -07:00
Jordan Ritter 28ed085408 fix(showcase/langgraph-typescript): disable FileSystemPersistence disk flush
The langgraph-typescript backend's @langchain/langgraph-api FileSystemPersistence
serialises all accumulated thread/run/checkpoint state to .langgraph_api on a
3-second timer. Under the D6 probe fan-out (36 parallel probes) the dir filled
past the 200MB size-watchdog threshold in ~90s, the watchdog killed the agent,
and on rapid restart the D6 cron refilled and re-tripped it until Railway
crash-loop backoff stopped restarting the container (2026-07-13 outage, staging
and prod).

Mirror PR #5825's langgraph-python fix, which exported
LANGGRAPH_DISABLE_FILE_PERSISTENCE=true so the python inmem runtime skips its
flush-to-disk loop. The TS package has no such switch and its persistence
writers are unexported module singletons behind an exports-map wall, so ship a
node --import preload (src/agent/disable-file-persistence.mjs) that, gated on the
same env var, no-ops node:fs/promises writeFile/mkdir for .langgraph_api paths
while leaving in-memory state (the real runtime state) intact. Wire it into
npm start and export the env var in entrypoint.sh.

Behavior preserved: runs still execute and thread state reads back from the
in-memory checkpointer within the container lifetime; only disk persistence is
removed, so the size-watchdog has nothing to fill and never trips under load.
2026-07-13 10:50:25 -07:00
Mark 6db81b8c99 Merge branch 'main' into mark/oss-451-showcase-route-wiring-guard 2026-07-09 23:05:40 -07:00
Mark 04dbc1d9b1 Merge branch 'main' into mark/oss-451-mastra-demos-404 2026-07-09 13:39:00 -07:00
Tyler Slaton 5cae8d4937 fix(docs): clean Claude generative UI snippets (#5890)
## Problem

Claude Agent SDK docs could render malformed or missing extracted
snippets across the generated Python and TypeScript integration docs.
The generative UI pages had stale duplicate regions and generic setup
leakage, while the custom look-and-feel reasoning and slots pages
referenced demo cells or regions that did not exist for every Claude
integration.

## Why

The docs pipeline treated accidental duplicate region names across files
as intentional multi-file regions, and several authored docs pages
drifted from the actual generated showcase demo IDs/regions. That left
some pages visually correct at a glance but broken when users opened
specific extracted code snippets.

## Fix

- Move the shared `bar-chart-renderer` regions to the complete
`useComponent` call and delete stale duplicate snippet files.
- Add an explicit duplicate-region guard and verifier coverage for
accidental cross-file region collisions.
- Add line-emphasis support for extracted `<Snippet>` blocks and setup
`<DemoCode>` output.
- Scope generative UI feature pages away from generic `agent-setup`
boilerplate.
- Repair the shared reasoning-messages docs to use the generated
`reasoning-default` and `reasoning-custom` demo cells.
- Add the missing Claude Python chat-slots teaching snippet regions and
keep both Claude slot snippets self-contained.
- Broad-audit both Claude integration docs locally, then targeted-audit
the repaired reasoning/slots pages in light and dark mode.
2026-07-09 09:42:56 -07:00
Maximiliano Korp d1fbd583dd docs(shell-docs): document cli import command 2026-07-09 09:02:24 -07:00
Mark 9a6c1f4753 Merge branch 'main' into mark/oss-451-mastra-demos-404 2026-07-09 07:21:28 -07:00
Tyler Slaton 66b5a58339 fix(docs): repair Claude custom look snippets 2026-07-08 21:39:49 -07:00
github-actions[bot] ae3ecc2cb7 style: auto-fix formatting 2026-07-09 03:39:16 +00:00
Tyler Slaton 0f5a916075 fix(docs): clean Claude generative UI snippets 2026-07-08 20:38:13 -07:00
Jordan Ritter 9d9056db60 fix(showcase-harness): propagate level errorDesc to aggregate signal + reorder abort short-circuit
The aggregate e2e-smoke:<slug> red signal omitted errorDesc on the normal
return path, so an abort/timeout/send-budget-exhausted red that runLevel
RETURNS (not throws) showed on the PRIMARY dashboard tick as an unclassified
content-shaped red — only the side chat:/tools: rows kept the classifier.
Thread the failing level's errorDesc (L3 precedence, L4 fallback) onto the
aggregate so the primary tick matches the side row and the launcher-phase
abort path. Does not change red/green — only carries the classifier.

Also reorder the aborted-and-empty short-circuit ABOVE the alternate-content
/ raw-byte evaluate reads: an aborted run's page is tearing down, so those
reads were swallowed against a dead page and emitted an ambiguous empty
histogram. Non-aborted runs still perform the alternate-content salvage.
2026-07-08 18:27:14 -07:00
Jordan Ritter 80bd9469c4 fix(showcase-harness): classify mid-poll abort/timeout empty as abort, not content-red
A mid-poll abort — the external ctx.abortSignal firing, or the driver's
own hard-timeout landing during the first-token poll — makes runAttempt
return empty WITHOUT throwing. The retry loop breaks and control falls to
the clean-exit path, where the level was misclassified as a generic
content red ("empty assistant response", probe.exit outcome "err", no
errorDesc). That masqueraded a teardown/abort/timeout as a CONTENT
failure on the dashboard + CVDIAG.

Add an aborted-AND-empty guard before the content-red gate that
short-circuits to the same abort classification the other paths use
(errorDesc "abort", probe.exit outcome "timeout"). Discriminator is
abortSignal.aborted, not emptiness alone: a genuinely-completed-empty
turn (not aborted) stays the content-red "empty assistant response".
2026-07-08 17:39:15 -07:00
Jordan Ritter 13a636b670 fix(showcase-harness): harmonize d4 readTurnState error handling + first-send cap + null-header re-attribution
Harmonize the three readTurnState() consumers in the d4 chat-roundtrip probe
through one guarded safeReadTurnState() wrapper so a mid-poll readTurnState()
throw is handled consistently everywhere: it means "no reliable signal" ->
degraded widen + observable telemetry, never a silent false-red (the prior
readDegraded swallow) nor a spurious level-error (the prior unguarded
readBaseline/readTurnComplete escape). A genuinely-empty degraded turn still
reds at the ceiling.

Folds completing the PR's own items:
- item-1 first-send cap: guard the in-send press against SEND_PRESS_MIN_BUDGET_MS
  so a near-hang type can't floor press to ~1ms and produce a generic
  level-error; classify distinctly as send-budget-exhausted. Only press is
  guarded (type opens the envelope), so a legitimately-small pageTimeoutMs still
  issues a healthy first send.
- item-3 null-header: suppress the finally-block fallback probe.message.send once
  a real-header boundary already fired, so a retry whose winning resend lands no
  POST no longer emits a second null-header boundary (mis-attributed
  edge_interference_signal).

Also add "abort" to the errorDesc JSDoc enumeration (zero-risk).

Red-green covered for all three behavioral items against the real
runLevel/readTurnComplete path with a faithful fake.
2026-07-08 17:09:51 -07:00
Jordan Ritter 6889fdb26a test(showcase-harness): cover D4 probe hardening follow-ups (red-green + coverage)
Tests for the #5882 bucket-(b) follow-up hardening:

- Budget-exhaustion retry guard (red-green): a near-exhausted-budget retry no
  longer attempts a doomed ~1ms-floored resend (type invoked exactly once).
- Degraded-path floor (red-green): a degraded page (sseAttachFailed, no
  completion signal) with a late-but-present token resolves GREEN instead of a
  base-floor false-red; a genuinely-empty degraded run still reds.
- Retry telemetry re-attribution (focused test): a retry-rescued GREEN turn
  records the winning attempt's edge headers on probe.message.send.
- Coverage: FIFO-cap CVDIAG_MAX_OUTSTANDING_STARTS_PER_URL eviction backstop;
  DEBUG-auto-disarm fail-closed (disarmed => no raw-byte capture);
  alternate-content / raw-byte block SKIPPED on the container-success path.
- Fake fix: makeLateTokenBrowser now mints per-page state so L3 and L4 each run
  an independent stall+retry cycle (was a shared-page singleton that leaked
  sendCount from L3 into L4, so the L4 retry path was never genuinely exercised).
2026-07-08 16:48:33 -07:00
Jordan Ritter 8c3e3aa381 fix(showcase-harness): harden D4 probe driver (budget-exhaustion, degraded-path, retry telemetry)
Follow-up to #5882 (bucket-(b) CR items). Three behavioral/telemetry fixes
plus one documentation clarification, all in d4-chat-roundtrip.ts:

- Budget-exhaustion retry guard: skip a non-completion retry resend when the
  remaining wall-clock budget is below RETRY_MIN_BUDGET_MS (750ms). A late
  resend previously floored its type/press action timeout to ~1ms, throwing a
  page-fault-shaped error that mis-classified the stall as a generic red — a
  spurious-red flap source. The stall now reds on its own terms.

- Degraded-path floor: when the SSE interceptor silently no-ops
  (sseAttachFailed), no completion signal ever arrives, so the poll could only
  fall into the never-observed branch and pin the deadline to the base floor —
  reintroducing the slow-first-token false-red #5882 targets. Consult
  sseAttachFailed to WIDEN the never-observed wait to the per-attempt ceiling so
  a late-but-present token on a degraded page is still captured.

- Retry edge-header re-attribution: on a retry-rescued turn, re-arm the
  message-POST edge-header capture (messageSendEdge / lastMessagePostResp /
  emitMessageSend latch) so probe.message.send / edge_interference_signal / the
  DEBUG raw-byte sample reflect the WINNING attempt, not the stalled first one.

- Document why lastStoppedAtMs is retained on the d4 TurnState (write-only in
  d4; part of the shared attachSseInterceptor global shape the d6 run-signal
  snapshot also mirrors) so it does not read as dead code.
2026-07-08 16:48:22 -07:00
Jordan Ritter 725f98ff79 fix(showcase-harness): wait on SSE turn-complete signal for D4 first token (#5882)
## Root cause

D4's L4 flap was a **stalled turn, not a client render race**: aimock
served content but the page never rendered it (the real 20:16:52Z
failure). The turn stream never delivered a first token to the DOM
within the fixed `textPollTimeoutMs`, so the poll read the container
empty → spurious "empty assistant response" red.

The **prior fix (`ceae0c2c9`) was inert in production**. It keyed the
turn-complete decision off the `onSseEvent` Node-side seam, which the
real launchers never wire (Playwright has no per-SSE-event signal).
`sseObserved` therefore stayed `false` on the real path and the whole
extension collapsed to the pre-fix base budget floor. Its red-green used
a fake page that invoked the seam synthetically, so the deadness was
never caught.

## The fix (3 parts, mirrors `d6-all-pills.ts`)

1. **Wire the real completion signal.** `wirePlaywrightPage.goto` now
calls `attachSseInterceptor(page)` before navigation (injectable for
tests), seeding the page-side `__hk_runsFinished` /
`__hk_copilotRunning` turn-lifecycle globals at `document_start`. A new
`readTurnState()` `E2ePage` seam reads them via `page.evaluate`; the
first-token poll keys off **that** — the same transport-level
`RUN_FINISHED` + DOM run-stop edge d6 trusts — not the dead `onSseEvent`
seam.

2. **Fast-fail genuinely-empty turns.** With a real turn-complete edge,
a turn that completes with empty assistant text reds in ~`completion +
FIRST_TOKEN_GRACE_MS` (bounded by `FIRST_TOKEN_FAST_FAIL_MS` ≈ 15s)
instead of burning the flat 60s. A completed-empty turn still reds (no
masking) and is never retried.

3. **Retry-on-non-completion.** A turn **observed in-flight** that never
signals completion within budget (stalled/dropped stream) retries once
before red. **Never-observed** (dead/no-turn) runs stop at the base
floor with no retry. Total wall-clock is bounded by `pageTimeoutMs`
(per-attempt budget split), so the retry can't blow past the ceiling.

## CR findings resolved

- `abortSignal.aborted` checked **inside** the poll loop (was only at
level entry).
- Body-scrape fallback keeps `fromAssistantContainer=false` **and** no
longer clears `cvdiagResponseEmpty` — so a fallback-salvaged red can no
longer emit `terminal_outcome=ok`.
- Red/green never gated on `cvdiag` (telemetry-only); the `onSseEvent`
seam is documented telemetry-only.
- No-turn fallback budget capped by the per-attempt ceiling (bounded by
`pageTimeoutMs`).
- Fallback `tail.length > 20` floor and `split("\n")[0]` truncation
**removed** (they false-red'd short/multiline valid answers).
- Corrected stale "not started" / dead-seam comments.

## Real-surface red-green (NO fake page)

Real chromium + real `attachSseInterceptor` + real D4 driver against a
local fixture serving an SSE `/api/copilotkit` with an injected
first-token/stream delay (aimock-style). `sseObserved-on-real-path =
(runsFinished>0 || attrPresent)` is read from the actual page-side
globals.

**RED — pre-fix (interceptor UNWIRED), late-token (token@3s):**
```
MODE: unwired  scenario: late-token
L3(chat).state: red  summary: "empty assistant response"
elapsed_ms: 1444
sseObserved-on-real-path: false      <-- dead seam proven
```

**GREEN — post-fix (interceptor WIRED), same late-token:**
```
MODE: wired  scenario: late-token
L3(chat).state: green  summary: ""
elapsed_ms: 6333
LAST readTurnState on REAL path: {"runsFinished":0,"attrPresent":true,"sawRunningTrue":true,"runningNow":true}
sseObserved-on-real-path: true       <-- interceptor fires on real path
```

**completed-empty (WIRED) — still RED, fast-fail:**
```
L3(chat).state: red  summary: "empty assistant response"
elapsed_ms: 5340 (~2.5s/level, not the 60s ceiling)
LAST readTurnState on REAL path: {"runsFinished":1,"attrPresent":true,"sawRunningTrue":true,"runningNow":false}
sseObserved-on-real-path: true
```

**recoverable-stall (WIRED) — attempt 1 never completes → retry →
GREEN:**
```
L3(chat).state: green  summary: ""
elapsed_ms: 13423   (attempt 1 polls to per-attempt ceiling observed=true/complete=false, then resends; attempt 2 renders)
sseObserved-on-real-path: true
```

## Checks

- `tsc --noEmit`: clean
- `tsc -p tsconfig.build.json` (build): clean
- vitest `d4-chat-roundtrip.test.ts`: 56/56 pass (suite rewritten to
exercise the real `readTurnState` path + retry/fast-fail;
late-token→green, completed-empty→red, recoverable-stall→green via
retry, permanent-stall→red)
- oxlint: 0 errors (pre-existing warnings only)

Kept as a **draft**; do not merge.

---

## Follow-up (b572697ab): attempt-scoped completion — the dominant a1
false-red

CR flagged that the poll's `sseDone = runsFinished >= 1` read the
page-GLOBAL monotonic counter. A **prior run on the page**
(auto-greeting / initial-mount run) leaves `runsFinished >= 1` and
`sawRunningTrue` already latched **before the user's turn starts**, so
the poll thought THIS turn had already completed, saw the still-empty
container, and spuriously **fast-failed RED**. It only passed the old
tests because the fake reset per send.

### Fixes in this commit

1. **Completion is now ATTEMPT-SCOPED.** A per-attempt BASELINE
(`runsFinished` + `runStartCount`) is captured at send time; the turn is
complete only on a **new edge past the baseline** (`runsFinished >
baseline`, or a new `runStartCount` DOM run-start), never the
page-global `>= 1`. A fresh baseline is taken before each retry resend,
so a stale prior edge can't defeat the retry.
`TurnState`/`readTurnState` now surface `runStartCount` +
`lastStoppedAtMs` (the sse-interceptor already latches them), and the
grace window is stamped from the **real finished edge** rather than the
poll's local clock (fixes the ~500ms-short grace).
2. **Retry resend bounded by remaining budget.** The retry `sendTurn()`
type/press timeouts are capped to `hardCeiling` (were flat
`pageTimeoutMs`, which let a stalled resend push poll-phase wall-clock
~2x past the ceiling).
3. **Attach-fault is observable.** A failed `attachSseInterceptor` now
emits an `onAttachFault` marker + sets `TurnState.sseAttachFailed`, so a
silent regression to the inert base-floor path is detectable.

Folded (cheap): `probe.message.send` fallback moved into `finally`
(fires on nav/send throw); external `ctx.abortSignal` aborts labeled
`"abort"` (not `driver-error`) in the aggregate; corrected
fast-fail-floor / hardCeiling / poll-deadline doc comments.

### Real-surface RED→GREEN (real Chromium + real `attachSseInterceptor`
+ local SSE fixture)

The fixture fires a PRIOR run on mount (bumps the page-global counters),
then the user turn renders a first token at 2800ms — PAST the 2s grace
window. Driven through the REAL `createE2eSmokeDriver` +
`wirePlaywrightPage` (L3/chat, the every-service level).

**PRE-FIX (`sseDone = runsFinished >= 1`):**
```
a1-priorrun      L3.state: red   "empty assistant response"  elapsed 2141ms   <-- FALSE-RED (the bug)
recoverable      L3.state: red   "empty assistant response"  elapsed 2171ms   <-- retry defeated by stale prior edge
wired-late       L3.state: green                             elapsed 3136ms   (no prior run → no false completion)
completed-empty  L3.state: red   "empty assistant response"  elapsed 2156ms
```

**POST-FIX (attempt-scoped baseline):**
```
a1-priorrun      L3.state: green                             elapsed 3285ms   <-- fixed: prior run no longer false-reds
recoverable      L3.state: green                             elapsed 5192ms   <-- retry rescues (fresh baseline per resend)
wired-late       L3.state: green                             elapsed 3150ms
completed-empty  L3.state: red   "empty assistant response"  elapsed 2707ms   <-- fast-fail (~completion+grace, not the 9s ceiling)
sseObserved-on-real-path: true (all)   sseAttachFailed: false
```

### Checks
- `tsc --noEmit`: clean · `tsc -p tsconfig.build.json` (build): clean
- vitest `d4-chat-roundtrip.test.ts`: **63/63** (added: a1-regression
L3+L4, completed-empty-one-send, L3 grace/fast-fail/retry, attach-fault
telemetry)
- oxlint: **0 errors** (4 pre-existing warnings only)

Still a **draft**; do not merge.


---

## Follow-up (a): grace-window timestamp — Node-stamp completion instant

Four reviewers flagged the same line in `runAttempt`:

```ts
completeAtMs = stoppedAtMs > 0 ? stoppedAtMs : Date.now();
```

where `stoppedAtMs` came from the page-side
`readTurnState().lastStoppedAtMs`. Two defects in one line:

1. **Stale on SSE-only completion.** When the turn is detected complete
via `sseDone = runsFinished > baseline` but no fresh DOM stop-edge fires
for THIS turn, `lastStoppedAtMs` still holds a **stale prior-run** value
→ `graceEnd = staleStop + FIRST_TOKEN_GRACE_MS` lands in the past → the
~2s grace window collapses to the base floor → a late-but-present first
token **false-REDs**.
2. **Cross-clock skew.** `lastStoppedAtMs` is stamped on the
**browser-page** `Date.now()`, but `graceEnd`/deadline math runs on the
**Node** clock → page↔Node skew mis-sizes the window.

### Fix (single-point, simplifying)
Stamp `completeAtMs = Date.now()` (**Node**) at the first poll iteration
that observes THIS turn complete; `readTurnComplete` no longer threads
`stoppedAtMs`. Both clocks are now consistent; the stamp lags the true
finished edge by at most one 500ms poll interval, well inside
`FIRST_TOKEN_GRACE_MS`. Preserved: completed-empty still fast-reds;
base-floor and `hardCeiling` caps intact; no masking. `lastStoppedAtMs`
is retained on `TurnState` (still consumed by conversation-runner /
sse-interceptor / d6).

### Tightenings (net-negative source LOC: +54/−58 = **−4**)
- Extracted the attempt-0 baseline IIFE onto the existing `readBaseline`
helper (DRY; removes a drift hazard on the completion-scoping path).
- Fixed stale comments: fallback `emitMessageSend()` now runs in the
`finally`; eviction comment (rides the `onResponse` wiring, not "always
active"); `lastStoppedAtMs` doc.

### Red-green (mandatory)

**Unit — stale-`lastStoppedAtMs` grace collapse** (`SSE-only completion
with a STALE lastStoppedAtMs …`): prior finished run +
`sseOnlyStaleStop` + prior stop aged 5s + token 700ms after completion
(inside a healthy grace window).
```
RED   (pre-fix stamping: completeAtMs = staleStop):  expected 'red' to be 'green'   → RED
GREEN (Node-stamp fix):                              64/64 pass                      → GREEN
```

**Real-surface no-regression** (real chromium + `wirePlaywrightPage` →
real `attachSseInterceptor` + local SSE fixture,
`repro_d4_a1_realsurface.mts`):
```
a1-priorrun      aggregate green · L3 green                            elapsed 3292ms   (SSE-only-late, lastStoppedAtMs stale at completion)
completed-empty  aggregate red   · L3 "empty assistant response"      elapsed 2618ms   (fast-fail, not the 9s ceiling)
recoverable      aggregate green · L3 green                            elapsed 5161ms
wired-late       aggregate green · L3 green                            elapsed 3131ms
sseObserved-on-real-path: true (all)   sseAttachFailed: false
```

### Checks (this commit)
- `tsc --noEmit`: clean · build (`tsc -p tsconfig.build.json`): clean
- vitest `d4-chat-roundtrip.test.ts`: **64/64**
- oxlint: **0 errors** (4 pre-existing warnings only)
- commit: `7bb47e64d`

Still a **draft**; do not merge.


---

## Follow-up (commit `5047f7ba9`): make the SSE-only-stale grace guard
actually bite

Three reviewers noted that the `sseOnlyStaleStop` guard above did not,
in fact, guard the Node-stamp fix. Root cause in the fake
(`makeLateTokenBrowser.readTurnState()`): the driver takes the
per-attempt baseline via `readTurnState()` **before** the first send,
and at that point `lastSendAtMs===0` made `elapsed` (~epoch ms) exceed
`completeAt`, spuriously flipping `complete=true` at the **baseline**
read. That inflated the baseline `runsFinished` to `prior+1`, so the
current turn's real finished edge never rose **past** the baseline and
the driver's `sseDone = runsFinished > baseline.runsFinished` could
never fire. The SSE-only-stale-grace path was therefore never entered —
the test passed only because the token rendered directly (path (a)), so
it did **not** guard the Node-stamp fix.

**Test-only fix:** gate the fake's `complete` on `started` (a turn has
actually been sent). The pre-send baseline read is now a **true**
baseline (`runsFinished = prior + priorSendsDone`, no current-turn
finish); the finished edge is a genuine THIS-turn transition the driver
observes via `sseDone`; and the SSE-only completion path with a stale
`lastStoppedAtMs` is genuinely exercised.

### RED-on-revert proof (the guard bites)

Temporarily reverted the production stamp back to the pre-fix form
(`completeAtMs = stoppedAtMs > 0 ? stoppedAtMs : Date.now()`,
re-threading `stoppedAtMs` through `readTurnComplete`), ran the reworked
test, then restored the Node-stamp:

```
RED  (pre-fix revert: completeAtMs = stoppedAtMs):
  × SSE-only completion with a STALE lastStoppedAtMs … → GREEN
    AssertionError: expected 'red' to be 'green'
  Test Files  1 failed (1)

GREEN (Node-stamp restored: completeAtMs = Date.now()):
  ✓ SSE-only completion with a STALE lastStoppedAtMs … → GREEN
  Test Files  1 passed (1)
```

The production revert was **not committed** — final state has the
correct Node-stamp; the production diff in this commit is
**comment-only** (no logic change).

### Comment corrections (this commit)
- Completed-empty deadline comment: previously claimed "base floor
always respected"; corrected to state that
`Math.min(Math.max(baseBudgetEnd, graceEnd), fastFailEnd,
attemptCeiling)` intentionally clamps **below** the floor (fast-fail — a
completed-empty turn must red fast, not burn the base budget).
- `FIRST_TOKEN_FAST_FAIL_MS` doc: now spells out the full `Math.min`
term rather than only the `Math.max(base, grace)` cap.

### Checks (this commit)
- `tsc --noEmit`: clean · build (`tsc -p tsconfig.build.json`): clean
- vitest `d4-chat-roundtrip.test.ts`: **64/64**
- oxlint: **0 errors** (4 pre-existing warnings only)
- diff: driver (comments) + test file only; no `repro_*`/`baseline`
staged
- commit: `5047f7ba9`

Still a **draft**; do not merge.
2026-07-08 16:26:56 -07:00
Jordan Ritter 5047f7ba9e test(showcase-harness): make D4 SSE-only-stale grace guard actually bite
The sseOnlyStaleStop guard in makeLateTokenBrowser was ineffective: the
driver reads the per-attempt baseline via readTurnState() BEFORE the
first send, and at that point lastSendAtMs===0 made elapsed (~epoch ms)
exceed completeAt, spuriously flipping complete=true at the baseline
read. That inflated the baseline runsFinished to prior+1, so the current
turn's real finished edge never rose PAST the baseline and the driver's
sseDone = runsFinished > baseline.runsFinished could never fire. The
SSE-only-stale-grace path was therefore never entered — the test passed
only because the token rendered directly, so it did NOT guard the
Node-stamp fix.

Gate the fake's complete on started (a turn has been sent) so the
pre-send baseline read is a TRUE baseline (runsFinished = prior +
priorSendsDone). The finished edge is now a genuine THIS-turn transition
the driver observes via sseDone, and the SSE-only completion path with a
stale lastStoppedAtMs is genuinely exercised.

Proof the guard now bites (temporary production revert, not committed):
- pre-fix stamp (completeAtMs = stoppedAtMs > 0 ? stoppedAtMs : now):
  test FAILS, expected 'red' to be 'green' (grace collapses).
- restored Node-stamp (completeAtMs = Date.now()): test PASSES.

Also corrects two production comments (3 reviewers flagged): the
completed-empty deadline comment claimed the base floor is always
respected, but Math.min(..., fastFailEnd, attemptCeiling) intentionally
clamps below the floor (fast-fail); and the FIRST_TOKEN_FAST_FAIL_MS doc
now spells out the full Math.min term. Comment-only, no logic change.
2026-07-08 16:18:44 -07:00
Jordan Ritter 7bb47e64df fix(showcase-harness): stamp D4 first-token grace from Node completion instant
On an SSE-only completion (turn detected complete via runsFinished>baseline
with no fresh DOM stop-edge for THIS turn), readTurnState().lastStoppedAtMs
still held a stale prior-run value, and it is stamped on the browser-page
clock while graceEnd/deadline math runs on the Node clock. Feeding it into
Node-clock arithmetic pushed graceEnd into the past and collapsed the
FIRST_TOKEN_GRACE_MS window to the base floor, false-REDing a late-but-present
first token.

Stamp completeAtMs from Date.now() (Node) at the first poll that observes
THIS turn complete; readTurnComplete no longer threads stoppedAtMs. Also DRY
the attempt-0 baseline onto the existing readBaseline helper and fix stale
comments (fallback emit is in finally; eviction rides the onResponse wiring;
lastStoppedAtMs doc). completed-empty still fast-reds; base floor and
hardCeiling caps preserved.

Adds a red-green unit test modelling an SSE-only completion with a stale
lastStoppedAtMs (grace collapses pre-fix, honored post-fix).
2026-07-08 16:02:19 -07:00
Jordan Ritter b572697ab2 fix(showcase-harness): scope D4 first-token completion per-attempt (was page-global false-red)
The first-token poll keyed turn-complete off the page-GLOBAL monotonic
`runsFinished >= 1` / latched `sawRunningTrue`. A PRIOR run on the page
(auto-greeting / initial-mount run) leaves those already satisfied when the
user's turn starts, so the poll treated THIS turn as already complete, saw the
still-empty container, and spuriously fast-failed RED — the a1 false-red.

Fix: capture a per-attempt BASELINE (`runsFinished` + `runStartCount`) at send
time and treat the turn complete only on a NEW edge past that baseline
(`runsFinished > baseline` / a new `runStartCount` DOM run-start). A fresh
baseline is taken before each retry resend, so a stale prior edge can no longer
defeat the retry. The grace window is now stamped from the REAL finished edge
(`lastStoppedAtMs`) rather than the poll's local clock (fixes the ~500ms-short
grace). `TurnState` / `readTurnState` are extended to surface `runStartCount`
and `lastStoppedAtMs` (the sse-interceptor already latches them).

Also:
- Bound the retry resend's type/press action timeouts by the remaining budget
  to `hardCeiling` (was flat `pageTimeoutMs`, letting a stalled resend push
  poll-phase wall-clock to ~2x past the ceiling).
- Surface an interceptor-attach fault (`wirePlaywrightPage.goto`) via an
  injectable `onAttachFault` marker + `TurnState.sseAttachFailed` so a silent
  regression to the inert base-floor path is detectable, not invisible.
- Move the `probe.message.send` fallback emit into the `finally` (idempotent)
  so it fires on nav/send throw paths too.
- Label external `ctx.abortSignal` aborts as `"abort"` (not `"driver-error"`)
  in the aggregate, matching the per-level classification.
- Correct the fast-fail-floor / hardCeiling / poll-deadline doc comments.

Tests: a1 regression (prior finished run + in-flight turn → not false-red) at
L3 and L4; completed-empty does exactly ONE send (retry does not fire); L3
coverage for the grace/fast-fail/retry path; attach-fault telemetry surfaces.
2026-07-08 15:45:52 -07:00
Martha Kelly Schumann 27ec110d6f Merge PR #5865 via QA Agent Pipeline
Auto-merged from Linear Needs Merge after approval and green CI.
2026-07-08 15:15:32 -07:00
Jordan Ritter 2f4a76943a fix(showcase-harness): wire real turn-complete signal for D4 first-token wait (was inert)
The prior D4 first-token fix (ceae0c2c9) was INERT in production: it keyed the
turn-complete decision off the `onSseEvent` Node-side seam, which the real
launchers never wire (Playwright has no per-SSE-event signal). `sseObserved`
therefore stayed false on the real path and the whole extension collapsed to the
pre-fix base budget floor. Its red-green used a fake page that invoked the seam
synthetically, so the deadness was never caught.

Root cause of the flap: a STALLED turn (RUN_FINISHED served by aimock but the
page never rendered it — the real 20:16:52Z failure), NOT a mere client render
race. So we need a real completion signal AND a retry for never-completed turns.

Three-part fix (mirrors d6-all-pills' production-wired signal):

1. Wire the real signal. `wirePlaywrightPage.goto` now calls
   `attachSseInterceptor(page)` before navigation (injectable for tests), seeding
   the page-side `__hk_runsFinished` / `__hk_copilotRunning` turn-lifecycle
   globals at document_start. A new `readTurnState()` E2ePage seam reads them via
   `page.evaluate`; the first-token poll keys off THAT — the same
   transport-level + DOM run-stop edge d6 trusts — not the dead onSseEvent seam.

2. Fast-fail genuinely-empty turns. With a real turn-complete edge, a turn that
   completes with empty assistant text reds in ~completion+grace (bounded by
   FIRST_TOKEN_FAST_FAIL_MS ~15s) instead of burning the flat 60s. A
   completed-empty turn still reds (no masking) and is never retried.

3. Retry-on-non-completion. A turn OBSERVED in-flight that never signals
   completion within budget (stalled/dropped stream) retries once before red.
   Never-observed (dead/no-turn) runs stop at the base floor, no retry. Total
   wall-clock is bounded by pageTimeoutMs (per-attempt budget split).

CR findings resolved: abortSignal.aborted checked inside the poll loop; the
body-scrape fallback keeps fromAssistantContainer=false AND no longer clears
cvdiagResponseEmpty, so a fallback-salvaged red can't emit terminal_outcome=ok;
red/green never gated on cvdiag (telemetry-only); the no-turn budget is capped by
the per-attempt ceiling; the fallback `tail.length>20` floor and
`split("\n")[0]` truncation removed (false-red on short/multiline answers);
stale "not started" / dead-seam comments corrected.

Real-surface red-green (real chromium + real attachSseInterceptor + real driver
against a local fixture serving SSE /api/copilotkit with injected stream delay):
- RED (pre-fix, interceptor unwired): late-token turn -> red "empty assistant
  response" in ~1.4s; sseObserved-on-real-path = FALSE (dead seam proven).
- GREEN (post-fix, interceptor wired): same late-token -> green;
  readTurnState on real path = {attrPresent:true,sawRunningTrue:true,...};
  sseObserved-on-real-path = TRUE.
- completed-empty (wired): still RED, fast-fail ~2.5s/level, runsFinished:1.
- recoverable-stall (wired): attempt 1 never completes -> retry -> green.

Unit suite (56 tests) rewritten to exercise the real readTurnState path plus the
retry/fast-fail behaviors; tsc + build + vitest all pass.
2026-07-08 15:15:28 -07:00
Martha Kelly Schumann 10ba63bd0e Merge PR #5835 via QA Agent Pipeline
Auto-merged from Linear Needs Merge after approval and green CI.
2026-07-08 15:15:08 -07:00
Martha Kelly Schumann 05443d1d63 Merge PR #5848 via QA Agent Pipeline
Auto-merged from Linear Needs Merge after approval and green CI.
2026-07-08 15:15:02 -07:00
Martha Kelly Schumann 9a47bc72a7 Merge PR #5851 via QA Agent Pipeline
Auto-merged from Linear Needs Merge after approval and green CI.
2026-07-08 15:14:57 -07:00
Martha Kelly Schumann 5f86fb0c04 Merge PR #5870 via QA Agent Pipeline
Auto-merged from Linear Needs Merge after approval and green CI.
2026-07-08 15:14:51 -07:00
Martha Kelly Schumann 5fc1a32782 Merge PR #5867 via QA Agent Pipeline
Auto-merged from Linear Needs Merge after approval and green CI.
2026-07-08 15:14:46 -07:00
Martha Kelly Schumann bc2049bdaa Merge PR #5866 via QA Agent Pipeline
Auto-merged from Linear Needs Merge after approval and green CI.
2026-07-08 15:14:41 -07:00
Mark Fogle 2dcfc25b4c ci(showcase): guard against dead-on-load demos (runtime-route wiring check)
OSS-451 shipped because nothing linked a demo page's CopilotKit runtimeUrl
to the existence of the /api route it names. The only automatic pre-merge
gate for showcase/** is a Docker build, which compiles a page that
references a non-existent route just fine (runtimeUrl is an unchecked
string) — so the page-404-on-load class was invisible.

Add a static validator (validate-runtime-routes.ts) that, for every SHIPPED
demo (a demo listed in its integration's manifest `features`), asserts its
runtimeUrl resolves to a real route dir under src/app/api. Unshipped /
experimental demos (not in `features`) and not_supported_features are
skipped, so incomplete placeholders don't fail the gate — but promoting one
into `features` immediately starts enforcing it. A baseline file can
grandfather pre-existing violations; the fleet is currently clean (0).

Wire it into a new pre-merge workflow (showcase_validate-wiring.yml) that
runs on every showcase/integrations PR alongside the build check. Add it to
branch-protection required checks to make it blocking.

Regression test proves it flags the exact OSS-451 shape (shipped demo,
missing route) while passing existing/base routes and skipping unshipped.

Verified: npm run validate-routes -> clean fleet-wide; removing the 3
OSS-451 routes -> flags exactly those 3; full showcase/scripts vitest suite
(2151 tests) green.

Refs OSS-451
2026-07-08 21:31:02 +00:00
Jordan Ritter ceae0c2c97 fix(showcase-harness): wait on SSE turn-complete signal for D4 first token
D4's L4 "tools" probe read the assistant-message container by polling
textContent for a fixed textPollTimeoutMs. On a run where the first token
rendered into the DOM slightly later than that budget — on a turn that
genuinely produced content — the poll exhausted and read the container as
empty, yielding a spurious "L4: empty assistant response" red (a client-side
first-token render race, not a real-LLM/fixture issue).

Harden the wait to key off the AG-UI SSE turn lifecycle rather than a fixed
timeout: track RUN_FINISHED/RUN_ERROR on the already-wired onSseEvent seam,
keep polling while a turn is in-flight (up to the pageTimeoutMs hard ceiling),
and after completion allow a small bounded first-token grace window for the
DOM to paint. A turn that completes with no content ever still fails, and when
no SSE stream is observed at all the poll falls back to the base budget floor
(unchanged pre-fix behavior, no hangs).

Adds red-green tests exercising the real runLevel wait path: a late-but-present
first token now passes; a genuinely-empty completed turn still fails.
2026-07-08 14:15:51 -07:00
Mark Fogle 9cbf8c7b7f fix(showcase/mastra): add missing runtime routes for a2ui-fixed-schema, declarative-gen-ui, agent-config
Three wired demos 404'd on load: their pages point <CopilotKit runtimeUrl>
at /api/copilotkit-<demo>, but those route handlers were never created in the
Mastra integration (the pages were mirrored from langgraph-python without
porting the routes). The runtime-info fetch 404'd, so the page never mounted
(runtime_info_fetch_failed).

Add the three dedicated routes, mirroring the proven copilotkit-beautiful-chat
pattern. The two A2UI demos set a2ui.injectA2UITool:false (weatherAgent already
owns generate_a2ui — avoid a double-bind) and pin defaultCatalogId to the
catalog the page registers. agent-config registers the agent id the page
requests (agent-config-demo).

Page-load fix only; full behavioral parity (dedicated Mastra agents) is OSS-381.

Verified: next build compiles all three into the route manifest; POST returns
400 (route resolves) identically to copilotkit-beautiful-chat, vs 404 for a
nonexistent route.

Refs OSS-451
2026-07-08 21:14:36 +00:00
Tyler Slaton 2255dd8cc3 test: add Claude SDK quickstart verification tooling
Add verify-shell-docs (+ unit tests), probe-shell-docs, probe-claude-quickstarts
(Playwright), and check-claude-quickstarts-runtime (extracts and runs the
documented commands/snippets) to gate the shell-docs build and quickstart
runtime. Includes CR hardening: drained server pipes, temp-dir cleanup,
SIGKILL escalation, a stack-trace-leak guard that matches SSE-escaped newlines,
and a fixed false-negative in the missing-import check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:35:00 -07:00
Tyler Slaton 77a1df2593 fix: wire Claude SDK demo integrations for the quickstart docs
Align the claude-sdk-python and claude-sdk-typescript demo integrations behind
the published quickstarts: move the @region markers used for doc snippet
extraction, add the state-streaming and weather-tool snippet files, and add
setup-doc content. Runtime alignment: the TS agent handlers consistently emit
text/event-stream; the streaming snippets emit a fresh STATE_SNAPSHOT per delta
and drop the undeclared partial-json dependency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:35:00 -07:00
Tyler Slaton 5ae2f82bb2 docs: publish Claude SDK quickstart docs
Turn on generated Shell docs for claude-sdk-python and claude-sdk-typescript:
quickstarts, framework registry data, docs links, and setup snippets.
Generalize the shared feature docs (state-streaming, HITL/interrupt,
tool-rendering, subagents, programmatic-control) to framework-neutral wording
so they read correctly across integrations. Includes review/audit fixes: the
valid claude-sonnet-4-6 model id, feature-card links pointing at pages that
exist, ms-agent-harness-dotnet docs-folder + tab-default routing, and concrete
state-streaming API names kept as neutral examples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:35:00 -07:00
Ben Taylor 3b56a10807 refactor(channels): rename @copilotkit/bot* packages to @copilotkit/channels* (OSS-438) (#5849)
## Summary

Renames the **Bots SDK → Channels SDK** (leadership decision, OSS-438).
**Names only — no behavior change.**

| old | new |
|---|---|
| `@copilotkit/bot` | `@copilotkit/channels` |
| `@copilotkit/bot-ui` | `@copilotkit/channels-ui` |
| `@copilotkit/bot-slack` | `@copilotkit/channels-slack` |
| `@copilotkit/bot-teams` | `@copilotkit/channels-teams` |
| `@copilotkit/bot-discord` | `@copilotkit/channels-discord` |
| `@copilotkit/bot-telegram` | `@copilotkit/channels-telegram` |
| `@copilotkit/bot-whatsapp` | `@copilotkit/channels-whatsapp` |

Package dirs renamed (`git mv`); versions carried over.

## What changed
- **Packages:** dir renames, `name` fields, `repository.directory`,
descriptions, and `workspace:` cross-deps rewired in lockstep.
`createBot` and other **API names are unchanged** (out of scope).
- **Release plumbing:** `release.config.json` scope keys +
`versionSource`, `ReleaseScope` union in
`scripts/release/lib/config.ts`, the
`canary`/`stable-release`/`publish-release` workflow scope dropdowns,
and `verify-release-scope-dropdowns.sh`.
- **Consumers:** `examples/slack` (Kite) + `examples/teams` — deps, the
load-bearing `jsxImportSource` pragma, build globs, imports.
- **Docs (`showcase/shell-docs`):** MDX package refs, content dirs
`docs/bots`→`docs/channels` and `reference/bot`→`reference/channels`,
nav registry, and **permanent redirects** from the old `/bots` and
`/reference/bot` URLs.

## Verification
- All 7 `@copilotkit/channels*` packages build; package test suites pass
(143+ in `channels`).
- `examples/slack` + `examples/teams` typecheck clean against the
renamed packages.
- `verify-release-scope-dropdowns.sh` green; release notification
wrapper test 30/30; `release:prepare` dry-runs for `channels` and
`channels-slack` resolve.
- `showcase/shell-docs` `frontend-options` test + full `tsc --noEmit`
clean.
- Zero stray `@copilotkit/bot` / `packages/bot` refs remain.

## ⚠️ Before merge / after merge
- **Do not squash-lose the deprecation step:** after these publish, run
`npm deprecate @copilotkit/bot@"*" "Renamed — install
@copilotkit/channels instead."` for the **5 published** old packages
(`bot`, `bot-ui`, `bot-slack`, `bot-teams`, `bot-discord`).
`bot-telegram`/`bot-whatsapp` were never published.
- New package names have **no npm version history**; the `package.json`
`version` seeds the first publish. Dry-run the OIDC publish for a
never-published scope first.
- Public-surface rename → stakeholder sign-off (kept as draft).

Refs OSS-438

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-08 14:32:47 -05:00
Jordan Ritter 4b670f4e2e docs(showcase): correct misleading watchdog/PID comments in entrypoints
The launch-site comment claimed process substitution leaves $! pointing at
the real node process. That is false and contradicts the file header: $!/
AGENT_PID is the wrapping subshell, and the real npm->node server is a
descendant reached only via the tree-kill (the reason _kill_agent_tree exists).
Rewritten in both langgraph-typescript and strands-typescript entrypoints so no
maintainer reintroduces a bare kill.

Also in langgraph-typescript: clarify that the SIZE_PID kill is a retained
belt-and-suspenders backstop to the $BASHPID PPID-walk (not dead code), and
re-anchor the startup-grace rationale to the real cause (the top-level
@langchain/langgraph-api import cost), since the prod path no longer uses
langgraph-cli dev.

Comment-only; no executable code changed.
2026-07-08 12:02:05 -07:00
Benjamin Taylor 41aed530c3 docs(channels): fix stale bot links in package docs + Channels landing page
Addresses review (tylerslaton):
- Package README/ARCHITECTURE relative links ../bot* -> ../channels* across
  discord/slack/teams/telegram/whatsapp (404'd after the dir rename)
- Channels landing page: Card hrefs /bots/{persistence,transcripts} ->
  /channels/*, and 'Bot reference' -> 'Channels reference'
- Package-noun prose (bot engine -> channel engine, bot-ui -> channels-ui,
  bot-slack approach -> channels-slack approach)

Left unchanged: runtime/third-party 'bot' prose, /api/bots/* Intelligence
wire paths, next.config /bots redirect sources.
2026-07-08 13:59:50 -05:00
Jordan Ritter 5a16b59f1e fix(showcase): guard sleep in _kill_agent_tree re-scan loop
The bounded re-scan loop's sleep 0.2 was unguarded. Under set -e on a base image
whose sleep can return non-zero (e.g. a future busybox/Alpine rebase), a failed
sleep would abort the tree-kill mid-walk — root never killed, real npm→node
server left orphaned. Add || true so the walk completes regardless of sleep's
exit status. No behavior change on the current Debian base (coreutils sleep
succeeds). Helper kept byte-identical across both entrypoints.
2026-07-08 11:56:01 -07:00
Jordan Ritter abaf72185a fix(showcase): key exit-code diagnostic off the actual reaped PID
Both langgraph-typescript and strands-typescript entrypoints inferred which
process exited via a post-hoc kill -0 if/elif after wait -n. That inference is
racy: on a near-simultaneous exit both PIDs are dead by probe time, so the first
kill -0 branch always wins and mislabels the diagnostic (naming the agent when
Next.js actually exited, attaching the wrong code to the wrong name). Use
bash's wait -n -p REAPED_PID (bash >= 5.1; node:22-slim ships 5.2) to capture
the actual reaped PID and key the message off it. Exit code (incl. 137) and the
final exit $EXIT_CODE are preserved; || EXIT_CODE=$? guard is unchanged.
2026-07-08 11:55:50 -07:00
Jordan Ritter 8d676704c7 fix(showcase/langgraph-typescript): close size-loop trap-order leak window and guard size ceiling during startup grace
Three related size-watchdog hardening changes in the langgraph entrypoint:

- Trap-order leak window: the size sub-loop was backgrounded (`( … ) &`,
  SIZE_PID=$!) BEFORE its reaping `trap … EXIT` was registered, so an outer
  SIGTERM landing in that window exited the watchdog subshell with no trap
  armed and orphaned the sub-loop (reparented to PID 1, spinning for the
  container's life). Arm the reaping trap FIRST, and reap via a $BASHPID
  PPID-walk that finds the child regardless of whether SIZE_PID is assigned
  yet — no ordering-dependent leak.

- Startup-grace size coverage: the size monitor started only AFTER the
  up-to-180s startup-grace loop, leaving the size ceiling unguarded during a
  pathological cold boot. Start it BEFORE the grace loop; safe because
  _watchdog_check_size_once already fail-closes on every not-yet-ready
  condition (agent PID not alive, PERSIST_DIR missing, non-numeric size/
  threshold), so early cycles are harmless no-ops until the dir grows.

- cleanup() comment accuracy: corrected the note claiming WATCHDOG_PID
  "forks nothing that outlives it" — it DOES fork the size sub-loop; the
  bare `kill $WATCHDOG_PID` is safe because the watchdog's own inner EXIT
  trap reaps that child, not because it forks nothing.

Proven RED->GREEN on the real entrypoint in node:22-slim: pre-fix the size
sub-loop keeps ticking after the watchdog exits (orphan) and the size monitor
spawns after the grace loop (unguarded); post-fix the sub-loop is reaped (0
ticks) and the monitor runs during grace (size-check fires within the grace
window). --check-size-once seam re-verified under/over threshold.
2026-07-08 11:36:23 -07:00
Jordan Ritter 5c71c1d047 fix(showcase): clamp _require_int to a 10-digit upper bound to stop int64 overflow disabling a guard
The numeric-config validator accepted any positive integer (`[1-9][0-9]*`),
so a 20+ digit override overflowed bash's signed-64-bit arithmetic and either
wrapped to a negative/garbage magnitude or aborted the `[ -ge ]` test with
"value too great for base" — which, suppressed to false inside the guard's
`if`, silently disabled the guard for the container's lifetime (the exact
fail-open class this validator exists to prevent).

Add a 10-digit length cap (max 9,999,999,999 — comfortably inside int64,
far above any real interval/threshold/strike knob) checked BEFORE the digit
`case`, since an all-digit 23-char value would otherwise pass validation.
A too-long value now takes the same WARN + fall-back-to-default fail-safe
path as every other bad override. Byte-identical across both entrypoints.

Proven RED->GREEN on the real entrypoints in node:22-slim: pre-fix a 23-digit
value survives validation and `$(( x * 3 ))` yields int64-wrapped garbage;
post-fix it WARNs, clamps to the default, and arithmetic is correct.
2026-07-08 11:35:59 -07:00
Jordan Ritter 966915b847 chore(showcase): replace per-PID echo|awk fork with read builtin in _agent_descendants
The /proc PPID walk forked an awk process for every entry in the process
table on every scan pass. Replace the `echo "${stat##*) }" | awk '{print $2}'`
pipeline with the `read` builtin, which word-splits the post-comm remainder
("STATE PPID PGRP …") on IFS and captures the 2nd field with no subprocess.
Byte-identical across the langgraph-typescript and strands-typescript
entrypoints. Non-behavioral; bash -n + shellcheck --severity=warning clean.
2026-07-08 11:35:31 -07:00
github-actions[bot] 1cf6eefba9 style: auto-fix formatting 2026-07-08 13:27:35 -05:00
Benjamin Taylor b394f06fdc refactor(channels): rename @copilotkit/bot* packages to @copilotkit/channels* (OSS-438)
Renames the Bots SDK to the Channels SDK. Names only — no behavior change.

- 8 packages @copilotkit/bot* -> @copilotkit/channels* (git mv dirs, names,
  workspace: cross-deps). Now includes @copilotkit/bot-intelligence ->
  @copilotkit/channels-intelligence (landed on main via #5761; unpublished, so
  renamed fresh with the family).
- release.config.json scope keys + versionSource; ReleaseScope union;
  canary/stable-release/publish-release scope dropdowns; verify script
- examples/slack (Kite) + examples/teams: deps, jsxImportSource, imports
- showcase/shell-docs: content dirs docs/bots->docs/channels and
  reference/bot->reference/channels, nav registry, redirects

createBot and other API names unchanged. Old @copilotkit/bot* to be deprecated
after the new packages publish (bot-intelligence was never published).

Re-derived onto latest main (was conflicting after #5761 landed).

Refs OSS-438
2026-07-08 13:27:35 -05:00
Jordan Ritter 7912b6e512 fix(showcase/langgraph-typescript): tree-kill agent so size-watchdog restart actually fires (#5874)
## What & why

Two showcase agent containers (**langgraph-typescript**,
**strands-typescript**) could enter a *running-but-dead* state: Railway
showed the service `● Online` while `/api/health` returned **HTTP 502**.
This took down all 36 LGT dashboard cells on staging **and** prod
(prod's `.langgraph_api` had crossed the 200 MB size-watchdog
threshold).

**Root cause:** the agent is launched via process substitution (`... &>
>(awk …) &`), so `$AGENT_PID` (`=$!`) is the **wrapper subshell**, not
the real `npm`→`node` server that holds the port. Every watchdog/cleanup
did a bare `kill -9 $AGENT_PID`, which reaped only the subshell and
**orphaned the real server** (reparented to PID 1, still bound to the
port). The watchdog's "kill agent → container restart → boot-purge"
contract therefore never fired: the frontend kept proxying to a dead
agent → 502 forever.

## Fixes (each with local red-green on the real entrypoint in
`node:22-slim`)

1. **cleanup() EXIT trap** → routes through `_kill_agent_tree` (was
orphaning the agent on every SIGTERM/redeploy).
2. **`_kill_agent_tree`** → `/proc`-based tree-kill with a bounded
re-scan (root killed last) so mid-walk forks can't escape; refuses PID ≤
1 (fail-closed).
3. **size-watchdog** hardened against non-numeric `du` and transient
errors (no silent gate-disable, no permanent loop death).
4. **strands health-watchdog** → 180 s startup-grace window (parity with
langgraph); the now-effective kill would otherwise loop a slow cold
start.
5. **`wait -n` under `set -e`** → capture exit code so the restart
diagnostic isn't dead code on the primary (137) path.
6. **structural:** one `_require_int` validator over *every*
operator-overridable numeric knob (fail-safe to default), and **every**
wrapped-PID kill (incl. `NEXTJS_PID`) routed through the guarded
tree-kill; dangerous `${AGENT_PID:-0}` sentinel removed.
7. **`_require_int`** requires a positive integer (rejects `0` and
leading-zero/octal).

## Incident status
Staging **and** prod LGT were restored immediately via redeploy
(boot-purge cleared the oversized state) — both `/api/health` → 200.
This PR stops the recurrence.

## Review
Converged through a 5-round unbiased review-fix loop (1 + 4
confirmation), zero mandatory findings at close, all load-bearing guards
independently re-verified. `bash -n` + shellcheck (`-S warning`) clean;
170/170 shell bats pass.

## Follow-up (tracked, separate PR — non-load-bearing)
`_require_int` upper-bound clamp (LOW arith-overflow, needs a 20+-digit
value); a stale `cleanup()` comment; size-guard unarmed during the
startup-grace window; SIZE_PID trap-registration micro-window;
diagnostic label on near-simultaneous exit; cosmetic log nits; startup
readiness `sleep 3`+`kill -0` probes the wrapper subshell; no dedicated
Next.js frontend watchdog.
2026-07-08 11:27:19 -07:00
Ben Taylor 9fa925bd95 feat(bot,runtime): managed bots SDK — run the bot SDK from Intelligence-delivered events (OSS-360/361) (#5761)
## Summary

Lets the `@copilotkit/bot` SDK run from **Intelligence-delivered
events** without a second programming model, and adds the runtime `bots`
declaration API. A managed event (delivered by Intelligence) runs the
*same* customer handlers, tools, context, commands, Bot UI, and agents
as local/custom adapters — the managed path is "just another
`PlatformAdapter`," fed by injected transports.

This is the **OSS / SDK slice** of the Hosted Managed Bots work. The
credentialed transports (Realtime Gateway, Connector Outbox) and the
frozen shared contracts live elsewhere (see *Out of scope*); this PR
ships the seams they plug into, fully runnable headless.

Relates to **OSS-360** (runtime bots API), **OSS-361** (run the SDK from
Intelligence events), **OSS-363** (Slack render/codec reuse).

## What's in here

- **`intelligenceAdapter()` bridge** (`@internal`, not publicly
documented) — implements `PlatformAdapter` over two injected transports:
`DeliverySource` (inbound) + `EgressSink` (outbound). Ingress →
`onTurn`/`onCommand`/`onInteraction`/`onThreadStarted`/`onReaction`; ack
on success / nack on throw (at-least-once). Egress emits generic
operations carrying `BotNode[]` IR with **deterministic ids**
(`turnId:seq`, reset per turn) so a redelivered turn reproduces the same
ids for the Connector Outbox to dedupe. Idempotency lives at egress, so
the managed path skips ingress dedup (`skipIngressDedup`) — a redelivery
re-runs rather than being dropped.
- **Runtime `bots` API** — `new CopilotRuntime({ intelligence, bots })`,
accepted by TypeScript **only when `intelligence` is configured**
(discriminated union). `createBot({ name })`; `startManagedBots()`
validates names (required, identifier-style, unique — fail-loud), builds
activation metadata, and wires each bot to its resolved transport.
- **`PlatformCodec` seam** + Slack egress codec (`slackCodec`) composing
the existing pure `renderSlackMessage`, so IR→native rendering is shared
(no Bolt/creds) instead of duplicated.
- **Backwards-compatible SDK foundations**: `bot.addAdapter()` +
optional `adapters`, deferred backend resolution at `start()` with
`stateStore`-provider precedence (+ multi-provider warning),
`bot.transcripts` throws pre-start, optional
`eventId`/`turnId`/`deliveryId` on ingress + handler context. Existing
`createBot` callers and every `PlatformAdapter` implementer are
unaffected.
- **In-memory transports + fixture tests** — the full dispatch path
(envelope in → handler runs → egress op out) runs with zero
Slack/Intelligence/network.

## Out of scope (external / separate tickets)

- **Realtime Gateway + Connector Outbox transports** — implemented in
the closed-source repo against the `DeliverySource`/`EgressSink`
interfaces shipped here.
- **Shared contracts freeze (OSS-377)** — consumed here via a minimal,
isolated placeholder (`managed/contracts.ts`, marked `TODO(OSS-377)`);
swaps in via one import change.
- **OSS-363 ingress normalization** — the egress codec is done;
extracting the pure Slack event→neutral mapping out of the Bolt listener
(so local + Intelligence ingress share it) is the remaining, higher-risk
half and is left to that ticket (`TODO(OSS-363)`).

## Testing

TDD throughout (RED→GREEN per behavior). New: managed adapter
dispatch/ack-nack/ids/run-renderer/exclusivity, all-kinds routing, name
validation + metadata + lifecycle, runtime `bots` option, Slack codec.
Full suites green: `bot` 147, `bot-slack` 256, `runtime` 1574. All
builds typecheck (`bot`/`bot-slack`/`bot-discord`/`runtime`);
oxlint/oxfmt clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-08 12:49:17 -05:00
Jordan Ritter fe1ad53c44 fix(showcase): require positive integer for numeric config knobs (reject 0 and leading-zero/octal)
The _require_int validator in the langgraph-typescript and strands-typescript
entrypoints accepted '0' and leading-zero/octal forms like '010'/'08'. Operator
typos on any numeric knob then broke a guard:
- SIZE_THRESHOLD_MB=0 kills the agent on cycle 1 (instant restart loop)
- HEALTH_STRIKE_LIMIT=0 kills on first probe miss
- SIZE_CHECK_INTERVAL=0 / HEALTH_CHECK_INTERVAL=0 busy-spin on 'while sleep 0'
- '010' is read as OCTAL (8) in arithmetic; '08'/'09' abort under set -e

Tighten the predicate to accept only a positive integer with no leading zero
([1-9][0-9]*). Invalid values keep the existing fail-safe behavior: WARN and
fall back to the documented default. Helper stays byte-identical across both
files.
2026-07-08 10:09:15 -07:00
Jordan Ritter 3d1d0a4240 fix(showcase): validate all numeric config overrides and route every wrapped-PID kill through guarded tree-kill
CLASS 1 (guard silently disabled by a bad numeric override): add a reusable
_require_int validator and run it at startup over EVERY operator-overridable
numeric knob in both entrypoints (size threshold/interval, startup grace,
health-probe interval, strike limit). A non-integer/empty override now WARNs
and falls back to the documented default instead of breaking a sleep/loop/
arithmetic test. Closes instance #3 (LANGGRAPH_SIZE_CHECK_INTERVAL='60s'
killing the size-monitor loop on its first iteration).

CLASS 2 (wrapped-PID orphan + kill-0 footgun): route the cleanup() NEXTJS_PID
kill through _kill_agent_tree (it is process-sub-wrapped like the agent, so a
bare kill orphaned the real Next.js node server holding $PORT across redeploy).
Harden _kill_agent_tree and _agent_descendants to refuse a PID that is empty,
non-numeric, 0, or 1 (fail closed), making kill -9 0 / kill -9 1 structurally
impossible. Remove the ${AGENT_PID:-0} sentinel in the --check-size-once seam;
skip with a warning when AGENT_PID is unset instead of defaulting to 0.

Shared helper code kept byte-identical between the two entrypoints.
2026-07-08 09:59:43 -07:00
Tyler Slaton 97058dc00f docs(shell-docs): update Slack and Teams agent framing (#5789)
## Summary

- Backport the website messaging from CopilotKit/website#398 into the
shell-docs Slack and Microsoft Teams frontend pages.
- Replace the stale waitlist/managed-only framing with "get early
access" copy that presents CopilotKit Enterprise Intelligence as the
self-hosted or cloud-hosted production layer around the open source Bot
SDK.
- Frame Slack and Teams as frontends for agents built on any harness or
framework, while reserving production-layer terminology for CopilotKit
Enterprise Intelligence.
- Address browser review annotations on both pages: remove filler in the
opener, avoid setup-heavy lead copy, use "open source" without a hyphen,
add the full CopilotKit Enterprise Intelligence name to the CTA titles,
and keep CTA telemetry surfaces intact.
- Update the shell-docs nav test expectation so it matches the current
root IA, where Threads lives under Build Chat UIs rather than the
generated Intelligence Platform section.

## Validation

- `npm run lint` from `showcase/shell-docs` (passes with existing
warnings)
- `npm run typecheck` from `showcase/shell-docs`
- `npm run test` from `showcase/shell-docs`
- `npm run build` from `showcase/shell-docs`

## Notes

- Hydrated Git LFS assets locally with `git lfs pull` so the shell-docs
public asset tests could read real PNG bytes.
2026-07-08 09:54:30 -07:00
Benjamin Taylor 224587101f fix(bot): move runStateStoreConformance to @copilotkit/bot/testing subpath
The package entry (@copilotkit/bot) re-exported runStateStoreConformance from
./testing/state-store-conformance, which imports vitest at module top-level.
An ESM re-export eagerly evaluates that module, so a bare
`import { createBot } from "@copilotkit/bot"` dragged vitest into every
consumer's runtime graph and threw ERR_MODULE_NOT_FOUND when vitest wasn't
installed (i.e. any production consumer).

- Drop the re-export from src/index.ts (entry is now vitest-free)
- Publish the conformance helper under the ./testing export subpath
- Declare vitest as an optional peerDependency (documents the /testing need)
- Update docs to import from @copilotkit/bot/testing

Names/behavior of the runtime API are unchanged; only the import path for the
test-only conformance helper moves.
2026-07-08 11:44:01 -05:00
Jordan Ritter ed44611263 fix(showcase): capture wait -n exit code under set -e so restart diagnostics aren't dead code
Both entrypoints run under set -e. The tail `wait -n $AGENT_PID $NEXTJS_PID`
returns non-zero on the PRIMARY designed exit path (137 = size-gate/watchdog
SIGKILL of the agent tree, or an agent crash), so set -e aborted the script AT
that line — making EXIT_CODE=$?, the entire 'which process exited with code N'
diagnostic, and the final `exit $EXIT_CODE` dead code on exactly the
interesting exits. Capture the code with `EXIT_CODE=0; wait -n ... || EXIT_CODE=$?`
so the diagnostic and explicit exit run and preserve the exact code (incl. 137);
the container-restart path is unchanged.

Same class: langgraph's LANGGRAPH_SIZE_THRESHOLD_MB was used in
`[ "$DIR_SIZE_MB" -ge "$threshold" ]` with no numericity guard, so a
non-integer operator override made the test error and silently no-op the size
gate every cycle. Validate the threshold the same way DIR_SIZE_MB already is
(numeric case guard + 'size guard inactive' WARNING, then skip safely).
2026-07-08 09:25:04 -07:00