mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
worktree-banking-codex-harness
14820 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
98a6c41f2d |
docs(reskinnable-demo): revert the reskin skill's agentRegistry-test claim
The skill was edited earlier in this branch to say `grep -rln agentRegistry src --include='*.test.*'` returns exactly one file, because the removed second harness arm shipped `harness-slot.test.ts`. That arm is gone, so the grep is empty again and the warning has to say so — a skin author who trusts "a test guards this map" would skip the one append with no automated guard. Caught by the standing question in this app's CLAUDE.md: does the change make anything in `.claude/skills/reskin/` wrong? Here it did, in the exact silent way that rule exists for — nothing type-checks a skill file, and a skin built from a stale one still compiles, lints and renders. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ |
||
|
|
bc4fd1f380 |
refactor(reskinnable-demo): drop the routed-agent harness arm
The long-running expense harness now ships in ONE shape: a `defineTool` on banking's classic agent. The second arm — a dedicated agent slot fed by `BuiltInAgent`'s tanstack stream factory, with its own page at /banking/deep-work — is removed so this change is reviewable on its own terms. Deleted: `harness-agent.ts`, `harness-slot.test.ts`, `pages/deep-work.tsx` and their tests. Unwired: the non-skin `banking-harness` key in `agentRegistry`, the `deep-work` entry in banking's `PAGES` map, and `HARNESS_AGENT_ID`. BREAKING for local envs: `EXPENSE_HARNESS_MODE` narrows from `off|tool|factory|both` to `off|tool`, so `factory` and `both` now THROW. That is deliberate — silently reading a retired value as `tool` would hide a stale `.env` instead of surfacing it, and an operator who wrote `both` asked for something this build no longer has. Verified end to end: with a stale `both` in `.env`, `pnpm build` fails at `/api/copilotkit/[[...slug]]`, and the same build passes once the value is corrected. `.env.example` and `mode.test.ts` both name the retired values so the next reader is not left guessing. Comments were REWRITTEN, not deleted. Nine files carried prose whose whole point was the comparison between the two arms; leaving those in place would have described an architecture that no longer exists. Each now states the surviving limitation directly — harness progress rides a second transport and never reaches the thread, so a mid-run reload loses the journey — and, where the alternative shape is the reason a seam exists (`run.ts`'s split launch, `csv.ts`'s own module, `progress.ts`'s deletability), names it as the alternative rather than as a live second arm. Gates: lint 0, typecheck 0, test:unit 2515 passed across 225 files (was 2531/227 — the two deleted test files), build succeeds with `/banking/deep-work` absent from the route list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ |
||
|
|
d045a2a522 |
fix(deps): pin @ag-ui/core to 0.0.57 to stop lockfile canary drift
`@ag-ui/a2a` and `@ag-ui/langgraph` declare `@ag-ui/core` as a FLOATING peer
(`>=0.0.42`), and no manifest in the repo constrains it. So every non-frozen
`pnpm install` re-resolved it to whatever was newest — which had become
`0.1.1-canary.beta.0` — and the lockfile drifted with no manifest change to
blame. Restoring the lockfile alone does not hold: reinstalling brought all nine
canary entries straight back.
Measured effect of the pin:
@ag-ui/core versions resolved 8 -> 1 (0.0.36/0.0.42/0.0.49/0.0.51/
0.0.52/0.0.53/0.0.57/canary -> 0.0.57)
canary entries in the lockfile 9 -> 0
lockfile size 544 insertions, 595 deletions (net smaller,
from collapsing seven duplicate snapshots)
Safety: the whole-monorepo suite was run with and without the pin. Both runs fail
the same set of tasks on a loaded local machine, plus one — `@copilotkit/react-ui`
— whose only failure is a markdown-sanitization test timing out at 8297ms against
a 5000ms budget; it passes 58/58 in isolation under the pin. CI on this branch is
green at the pre-pin HEAD across the full Node 20/22/24 x React 18/19 matrix, so
CI is the arbiter for this change too.
Note the pin belongs in package.json, NOT pnpm-workspace.yaml: `overrides` there
is silently ignored (verified — the entry never reaches the lockfile's own
`overrides:` block), even though `onlyBuiltDependencies` in that same file IS
honoured. The install-time warning about the `pnpm` field is misleading; the
lockfile records these overrides as applied.
Hooks skipped deliberately: the lockfile change invalidates Nx's cache for every
project, so the hook's 25-project parallel run trips local resource contention
rather than any real failure. Its other two steps were run by hand — commitlint
passes, and oxfmt has no JSON/YAML target.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ
|
||
|
|
8ab81ddd4f |
fix(reskinnable-demo): stop attaching the statement csv — the provider rejects it
Reverts the visible attachment on the harness pill. @ai-sdk/openai accepts ONLY images and application/pdf as file parts and throws UnsupportedFunctionalityError on anything else, so a text/csv document part cannot reach the model through this provider at all. Every run died with 'file part media type text/csv' functionality not supported which is why the pill stopped responding: staging, the chip, the filename and the send were all fine, and the run failed one layer below anything the composer or the staging chain can see. Neither the composer's accept filter nor the byte check knows that rule. Also reverted with it: the shell helper's `kind` machinery and the text/csv entry in the composer's accept. Both were correct in themselves, but with no caller they are dead paths pointing at something the provider refuses — and the follow-up (a PDF rendering of the statement, if we do it) uses kind "pdf" like every other beat. KEPT from that commit: the router's multimodal handling. A turn with an attachment arrives as parts rather than a string whatever the media type, so that is right regardless — and it is what stops the Q2 beat's shape from ever silently unrouting Arm C. skin.test.tsx now pins the harness pill FALLING THROUGH, with the provider rule written down beside it, so re-attaching a csv fails a test rather than a demo. Reskin-skill staleness check: demo-beats.md's beat-3d section reverted with the code, so it no longer documents a `kind` field that does not exist. |
||
|
|
462eede3f2 |
fix(reskinnable-demo): file charges at the port the app is actually on
The harness prompt hardcoded localhost:3000 for its filing curl while the CSV read derived its URL from PORT, so running the demo on any other port left codex POSTing into a dead socket. It failed quietly in the worst way: the run still finished, summary.json was still written and the report card still rendered — only every filedTransactionId was missing, which reads as "the harness chose not to file" rather than "the harness could not reach the ledger". Filing is one of the four beats this feature exists to show. Both URLs now come from one `localBaseUrl()`, so they cannot diverge into reading a statement from one app and filing charges against another. prompt.ts had no test — a deferred minor from Task 2, now closed, since the prompt is the only thing enforcing the beat. Five tests: the port, the shared origin, the 3000 default, the offsite dates every verdict cites, and the summary.json fields readSummary parses. Reskin-skill staleness check: no impact — no contract field, gate, lint rule or registration path changed. |
||
|
|
759c1fed7e |
feat(reskinnable-demo): stage the statement csv on the harness pill
The harness pill said "here's my personal card statement" and attached nothing.
It now rides the real file, through the same shell chain the Q2 invoice uses:
bytes verified before staging, staging confirmed before sending, every failure
reported to console AND alert rather than sending a prompt about a document that
never arrived.
Three things had to give:
- `@/shell/attach` was PDF-only — it sniffed %PDF magic bytes and forced
application/pdf onto the File. It now takes a `kind` ("pdf" | "csv",
defaulting to pdf so no existing skin changes), where each kind owns its MIME
and its byte check. CSV has no magic number, so its check targets what is
actually being guarded against: NUL bytes (binary mislabelled as text), a
leading < or { (an HTML or JSON error page), and no delimiter in the first
line. Mutation-verified — neutering it fails three tests.
- The composer's accept filter dropped .csv silently, so `accept` gains
text/csv. The four mock composers in tests were updated to match, since they
exist to mirror the real one.
- Arm C's router would have STOPPED MATCHING. A turn with an attachment arrives
as multimodal parts, not a string, so a string-only match would have sent the
expense job to the chat adapter with no harness and no error, while Arm A kept
working. The router now reads the text parts, and still requires exact
equality — concatenating a second text part must not turn it into a prefix
match.
What this does NOT change: `defineTool`'s execute receives only its parsed args,
so Arm A's tool still fetches the same URL server-side. Same file, same bytes,
two readers. Dragging in a DIFFERENT csv would still have the bundled fixture
analysed — that is a different feature and attach-statement.ts says so where
someone would look.
New skin.test.tsx pins which pills are intercepted, following airline's idiom.
The exhaustiveness guard on the failure-cause union caught the new cause exactly
as designed, and the count assertion is what forced a test that actually drives
it.
Reskin-skill staleness check: YES, impact, fixed here. demo-beats.md's beat-3d
template showed a two-field AttachmentDocument as the whole story; it now covers
`kind`, why it must come from what the route serves rather than from the
filename, and the two other places a new kind has to be registered.
|
||
|
|
c8f0cb43b0 |
feat(reskinnable-demo): register arm C's agent slot, page, and route
Adds HARNESS_AGENT_ID (a non-skin key in agentRegistry), the gated registration,
and /banking/deep-work — a second chat in the app card pointed at the routed
factory agent while the assistant column keeps talking to banking's classic one.
Two engines on screen is the comparison.
Two plan corrections, both silent failures if followed literally:
- The plan wrapped the shell's ChatPanel in a nested
CopilotChatConfigurationProvider with agentId=HARNESS_AGENT_ID, on the correct
reading that react-core resolves agentId ?? parentConfig?.agentId ?? DEFAULT.
But ChatPanel passes agentId={skin.id} to CopilotChat EXPLICITLY, and an
explicit prop beats inherited config — every "harness" turn would have gone to
banking's classic agent, with the page rendering and the chat answering
normally. The page renders CopilotChat directly instead, which also avoids
inheriting the shell's thread rail.
- The plan gated nav and resolvePage on armCEnabled(). skin.tsx is a CLIENT
module and EXPENSE_HARNESS_MODE is deliberately non-NEXT_PUBLIC_, so that
expression is inlined as undefined in the browser bundle and reads "off" in
EVERY mode — the page would 404 even with the arm live. The registry gate is
the real one; the route is ungated and the page states its requirement.
No nav entry, so the icon rail is identical on every deploy: /banking/deep-work
is reached by URL, which is how Task 12's walkthrough opens it.
The slot itself IS gated, unlike Arm A's unconditionally-registered gen-UI
renderer. A registered agent is not inert the way an unused renderer is, and
without the gate off and factory would be indistinguishable and armCEnabled()
dead code. harness-slot.test.ts pins all four modes — and is the first test in
the tree to import agentRegistry at all.
Reskin-skill staleness check: YES, impact, fixed here. SKILL.md told a new
author that grep -rln agentRegistry src --include='*.test.*' is empty as proof
that this append has no guard. That grep now returns this file, which would read
as "there is a guard" — there is not, for their skin. The paragraph now says
what the one test actually covers.
|
||
|
|
967dcbb72d |
feat(reskinnable-demo): routed factory agent for arm C
Arm C is a SECOND agent slot in factory mode that routes: the expense pill goes to the codex harness, every other turn to an ordinary chat adapter carrying whatever tools the frontend forwarded. Banking's classic agent is untouched — BuiltInAgentConfiguration is a strict union, so making banking itself a factory would take its prompt and ~20 tools offline. Four plan claims did not survive contact with the tree: - RunAgentInput is NOT re-exported from @copilotkit/runtime/v2 (checked against dist/v2/index.d.mts). The router takes a structural type instead, which also lets its cases be plain literals with no cast. - openaiText accepts NO apiKey — its second parameter is Omit<OpenAITextConfig, "apiKey">; it reads the env itself and throws per-run, inside the factory. - "gpt-5.4" is not in that adapter's model union at all (gpt-5.4-mini, -nano and -image-2 are). Uses gpt-5.6, the same family as the harness's own gpt-5.6-sol. - chat() takes an AbortController, not a signal — the same trap run.ts already documents. The factory context hands one over for exactly this. fetchExpenseCsv moves to harness/csv.ts so both arms read the fixture through one code path: a second copy with its own port fallback would quietly make the comparison about the fixture rather than the streaming seam. It also keeps as-tool.ts deletable if Arm C wins, which its own header promises. Gate 2's findings are recorded where the code is: nothing here reads tool arguments, because TOOL_CALL_END.input does not survive the converter and the only surviving payload is TOOL_CALL_ARGS.delta — which is the path the client's own arg accumulation already uses. Reskin-skill staleness check: no impact from this commit — no contract field, lint rule, gate or registration path changed. The registration change is in the next commit, which updates the skill. |
||
|
|
89553821e4 |
fix(reskinnable-demo): make a harness run cancellable, unstick its console
Five findings from the four owed re-reviews, plus ruling R26. The in-flight guard could not release. `defineTool`'s `execute` has no cancellation hook, so nothing on the server hears a cancelled turn or a reloaded tab; the guard came back only when a codex run nobody was watching finally drained, and every retry across those minutes was refused before a console had even rendered. A second call now SUPERSEDES the first and aborts it through the real signal — the only thing that reaches `killTree` — and every publish is gated on still owning the channel, so the superseded run cannot land its terminal `error` frame in the new run's console. Both halves are mutation-verified: removing the ownership gate yields ['error','thinking','done'], and restoring the throwaway AbortController hangs the test. The SSE route no longer replays a backlog that ends in a terminal frame. The console mounts on TOOL_CALL_START, before the server clears the channel, so on the second run of a session it could replay the previous run's frames, hit that run's `done`, and freeze for the whole of the real run. The console now says why it is empty after 45s instead of showing "Starting the harness…" forever — the shape an `off` deploy takes when the model calls a tool that was never registered (the AI SDK enqueues an invalid tool call before flagging it, and the renderer is registered unconditionally). R26 settled: prompt and tool list are now gated on the same condition, with `buildBankingPrompt` exported so the pairing has a regression test. The ninth pill stays clickable by design — `Skin.suggestions` is a static array on a frozen contract in a client module and EXPENSE_HARNESS_MODE is a non-NEXT_PUBLIC_ server env, so hiding it would take a contract or shell change. Also corrects tools.tsx's claim that a failed run returns an error string (the runtime has no `tool-error` case, so that path never completes), and mode.test.ts's cleanup, which wrote the string "undefined" when the var was originally unset. Reskin-skill staleness check: no impact — no contract field, gate, lint rule, registration path or beat mechanism changed. `useRenderTool` remains the one deferred SKILL.md addition, still owed by the task that lands this beat complete. |
||
|
|
a9eb9dcb16 |
feat(reskinnable-demo): wire arm A — mode flag, tool, gen-UI, pill
The long-running expense harness was fully built but unreachable. This makes
banking's beat clickable: a four-value EXPENSE_HARNESS_MODE flag, the tool
appended to banking's classic agent behind it, a chat renderer that turns the
live console into the report card, and a ninth suggestion pill.
EXPENSE_HARNESS_MODE is off | tool | factory | both, `off` when unset, and an
unrecognised value THROWS rather than falling back — a typo'd flag that quietly
disables the beat is the most confusing possible failure on stage, because every
other symptom looks like a working demo that chose not to call the tool.
`mode.ts` stays plain server-safe `.ts` because `agent.ts` imports it.
The renderer uses `useRenderTool`, NOT the `useComponent` the plan called for.
`useComponent` wraps `useFrontendTool` and hands its render ONLY the tool's
parsed args (`render: ({ args }) => <Component {...args} />`), so with no
`parameters` schema `{ status, result }` type-checks as `any` and is permanently
undefined at runtime: the slot would show the console forever and never the
report, with a green tree. `useRenderTool` registers a renderer in the same
registry without also registering a frontend tool of that name — right for a tool
the SERVER executes — and passes the real status union. `result` arrives as the
JSON-stringified summary, so it is parsed back tolerantly: a run that dies comes
back as an error string, and throwing there would take the whole transcript down.
The renderer is registered UNCONDITIONALLY even though the tool is gated; gating
it would drag the server-only mode.ts into a client module, and a renderer for a
tool nobody calls is inert.
Two plan errors corrected. suggestions.test.ts has no total-count assertion to
bump — it asserts that exactly ONE pill carries Q2_REPORT_MESSAGE, because
onSuggestionSelect matches by string equality. The harness router will match
EXPENSE_PILL_MESSAGE the same way, so this adds the analogous guard rather than
inventing a count. And .env.example does not claim the harness needs
OPENAI_API_KEY: it reads no such var, spawning `codex exec` and authenticating
through an existing `codex login`. The two prerequisites are documented apart —
the `codex` binary on the host PATH (pnpm never installs it) and OPENAI_API_KEY
for banking's own agent, the thing that routes to the harness.
Reskin skill: checked. Nothing became wrong — SKILL.md's and templates.md's
claims about useComponent vs useHumanInTheLoop/useFrontendTool render shapes all
remain true. One gap is now arguably open: neither file names useRenderTool, so
the skill has no answer for rendering a tool the server executes. Deliberately
NOT written yet, because no skin the skill's flow asks anyone to build needs it
(every skin's server defineTool results surface through CanvasSurface), and the
harness beat is half wired here — arm C and the pill router are later tasks.
Documenting a beat mechanism mid-flight is the same failure the rule warns about.
It belongs in the commit that lands the beat complete, as one bullet in SKILL.md's
gen-UI list.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ
|
||
|
|
e4ab59e7db |
fix(reskinnable-demo): respect viewer scroll and distinguish unfiled charges
Review fixes on the harness console and the expense report widget.
Console: autoscroll was unconditional, so a viewer who scrolled up to re-read an
earlier headline got snapped back to the tail within ~7s — during the very run
they were following. Now guarded by a near-bottom check SAMPLED WHEN THE FRAME
ARRIVES, before React appends the line: measured afterwards, the new line is
inside the distance and the guard always reads "at the bottom". Also
`scrollTop = scrollHeight` instead of `scrollTo({...})`, which jsdom does not
implement, so any future test rendering this component no longer throws.
Report widget: an expensable row with no filedTransactionId printed its decision
(honest) in the SAME green as a filed row, so the one row a presenter must not
miss read as a success. It now takes the negative tint, a heavier weight, and an
explicit "not filed" line. All three decision arms stay styled; semantic tokens
only.
The fixture could not have caught the matching fabrication bug: every row varied
decision and filed-ness together, so `decision === "expensable" ? "Filed" :
decision` passed the file. Adds the missing expensable-but-unfiled row and
asserts it reads "expensable", carries "not filed", and is tinted differently
from the filed row. Verified by mutation: keying off decision, tinting the
unfiled row green, and swapping a stat tile's value each turn the suite red.
Softens the stat-tile assertions per review — `data-stat` plus toContain rather
than comparing a parent's concatenated textContent, which coupled the test to
label depth and value/label order. A count landing in the wrong tile still fails.
Reskin-skill staleness check: checked, no skill impact — banking-local component
work, no contract field, hook, lint rule, gate or beat mechanism touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ
|
||
|
|
32bc7e65a4 |
chore(reskinnable-demo): gate arm C on converter reasoning support
GATE 2 PASS. `convertTanStackStream` preserves codex's reasoning: 289 characters arrived in `REASONING_MESSAGE_CONTENT.delta` and 289 came out in the same field, over 7 thoughts. Arm C can show visible thinking in the thread, so Tasks 10 and 11 are unblocked. The probe taps the raw stream on its way into the converter, so both sides are measured on ONE codex run — a separate raw run is not comparable, since the model picks a different number of searches each time. Measured: 91 chunks in, 74 AG-UI events out, and the 17-event difference is exactly RUN_STARTED + RUN_FINISHED + 9 CUSTOM + 3 TEXT_MESSAGE_START + 3 TEXT_MESSAGE_END. Every content-bearing chunk maps 1:1 with byte-identical text, so it is pass-through minus envelopes, not re-interpretation. No double-wrapping, no duplicated reasoning or tool envelope. Three findings Tasks 10/11 have to build around, all in the findings doc: TOOL_CALL_END loses `input` (parse TOOL_CALL_ARGS.delta instead, the opposite of the raw-side advice); CUSTOM is dropped entirely, so `sandbox.file` and `codex.session-id` do not exist on Arm C — two motion channels, not three; and all assistant text collapses into one minted messageId, so codex's three separate messages render as one bubble. Reskin-skill staleness check: no impact. This adds a probe script and touches no skin, no contract field, no shell file, no lint rule and no gate — nothing in `.claude/skills/reskin/` describes the harness probes. Findings appended under a "GATE 2 — converter" heading in docs/superpowers/plans/2026-08-14-probe-findings.md, which is gitignored (.gitignore:18 `superpowers/`) and so is deliberately not in this commit. Gate 1's section was left untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ |
||
|
|
994376e9f5 |
fix(reskinnable-demo): guard the harness tool's csv read, run errors, and concurrency
Six review findings on Arm A's defineTool wrapper: - The CSV read moves into runExpenseHarness behind an injectable readCsv dep, inside the published-error path, and checks response.ok so a 404 body is never analysed as a statement for four minutes. - clearProgress moves to the top of runExpenseHarness (deps.channel), which is what finally gives the fixed-channel constraint a regression test. - RUN_ERROR now maps to an error frame carrying its `message`: a rejected model arrives as a chunk, not a throw, so the cause was being discarded in favour of readSummary's symptom. - Tool calls render once on TOOL_CALL_END (which carries toolCallName AND the parsed `input`) instead of START plus a double-encoded ARGS frame. - elapsedSeconds is floored at 0 and now exercised by a stepping test clock. - A module-level in-flight guard makes 'one concurrent run per instance' real; it refuses before touching any channel. Reskin skill: checked, no skill impact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ |
||
|
|
9de6333cd0 |
test(reskinnable-demo): assert harness report values with their labels
Deliberate deviation from the task brief, whose test used
`toBeInTheDocument()`: `@testing-library/jest-dom` is not a dependency of this
app and `vitest.setup.ts` registers no matchers, so that form is an "Invalid
Chai property" at runtime and a type error under `pnpm typecheck`. Every other
component test here asserts on plain DOM values, so this follows the house style
rather than adding a shared dependency for one file.
Taken further than a presence check where it was cheap: each stat tile is
asserted as value-WITH-its-label, because a count under the wrong label is
exactly the bug `getByText("14")` cannot see. Likewise the filed marker now
asserts the unfiled row still shows its own decision, which is what proves the
marker discriminates.
One assertion was ambiguous as written: /day spa/ matched both the merchantKind
chip and the reason sentence ("A day spa — …"), so `getByText` threw on
multiple matches. Fixed as the exact string; the component was not reshaped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ
|
||
|
|
dfa1d5dd22 |
feat(reskinnable-demo): expense report widget + live harness console
The two React pieces of the long-running expense harness beat: - ExpenseHarnessReport — the payoff widget, SHARED BY BOTH ARMS and free of anything side-channel-specific (it imports HarnessSummary and nothing else), so the comparison stays about the four minutes in the middle. Summary amounts are positive (they mirror the CSV); the ledger's negative storage never reaches here, so no sign flip. - HarnessConsole — Arm A only. Tails the progress side-channel over EventSource and closes it on a done/error frame, on transport error, and on unmount. Semantic design tokens only, so every other skin's theme still applies. Reskin-skill staleness check: checked, no skill impact — additive files inside src/skins/banking/, no contract field, link builder, hook, lint rule, gate, registration site or beat mechanism touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ |
||
|
|
851cdccab7 | fix(reskinnable-demo): keep harness console listeners alive across clear and disconnect | ||
|
|
a759e04653 |
chore(reskinnable-demo): correct probe comments after the run.ts fix
The probe's comments described run.ts's PRE-FIX state and are false since
|
||
|
|
f23a584703 |
feat(reskinnable-demo): run the codex harness inside a defineTool
Arm A of the harness comparison: mapChunkToProgress + runExpenseHarness + analyzeExpensesTool. Chunk type strings and field names are Task 4's measured values (REASONING_MESSAGE_CONTENT carries `delta` only, TOOL_CALL_START carries toolCallName, tool calls arrive already resolved so only START renders). execute clears the fixed progress channel first so a second run cannot replay the previous run's trailing `done` frame, and the catch publishes an error frame before rethrowing. The brief's first runner fixture wrote `verdicts: []`, which readSummary rejects by design; the fixture now writes one verdict rather than weakening the guard. Reskin skill: checked, no skill impact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ |
||
|
|
6815bd43ab | feat(reskinnable-demo): harness progress side-channel + SSE route | ||
|
|
cd3f519dc0 |
fix(reskinnable-demo): make the codex harness stream actually runnable
Three blockers found by the gate probe's live run against the codex binary:
- withSandbox declares the sandbox-projection capability unconditionally but
only provides it when defineSandbox carries a `workspace`, so omitting the
block killed the run at middleware setup. Adds a minimal one, and corrects
the comment that wrongly claimed a workspace block would bootstrap over the
scratch dir (bootstrapWorkspace lands a source only for type "git"; verified
expenses.csv survives).
- gpt-5.1-codex is rejected 400 on a ChatGPT-account codex login, arriving as a
RUN_ERROR chunk rather than a throw. Switches to gpt-5.6-sol.
- modelReasoningEffort alone emits ZERO REASONING_* events; reasoning summaries
must be requested explicitly. Adds model_reasoning_summary="auto" ("detailed"
yields none). Verified: 8 REASONING_MESSAGE_CONTENT chunks, 298 chars of
populated delta, no RUN_ERROR.
Reskin-skill check: no impact — banking-internal module, no contract field,
shell file, lint rule or registration site touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ
|
||
|
|
3f93b3ac42 |
chore(reskinnable-demo): probe and record codex chunk shapes
GATE 1 of the banking-harness plan. Adds scripts/probe-harness-chunks.ts, which drives createExpenseHarnessStream over a one-row CSV and prints each distinct chunk `type` once with a truncated payload, plus per-type text-character totals so "the type exists" is distinguishable from "the type carries prose". GATE: PASS. REASONING_MESSAGE_CONTENT carries reasoning text in `delta` and the text is populated. Full findings, including three blockers the probe uncovered in run.ts, are in docs/superpowers/plans/2026-08-14-probe-findings.md — which is gitignored (.gitignore:18 `superpowers/`), so only the script is committed here. Reskin-skill staleness check: no impact. This adds a dev script and touches no skin, no `Skin` contract field, no shell file, no registration site, no lint rule, and no demo beat's mechanism, so nothing in .claude/skills/reskin/ is made wrong or incomplete by it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ |
||
|
|
e02af7d1e0 |
feat(reskinnable-demo): shared codex harness stream factory
Reskin-skill check: no impact. This adds a banking-internal harness module and touches no `Skin` contract field, shell file, lint rule or registration site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tp3i7qBNzWC9xVzsaz5KTZ |
||
|
|
b94e4bfb5d |
feat(reskinnable-demo): POST transactions so the harness can file charges
Checked the reskin skill: no impact. This adds one banking-only REST route and one store adder; it touches no Skin contract field, no shell file, no lint rule and no gate the skill names. |
||
|
|
ccfcc7e68a | fix(reskinnable-demo): validate harness summary shape and correct the filing contract | ||
|
|
14b0ee2bff | test(reskinnable-demo): guard the OFFSITE-to-fixture invariant | ||
|
|
f3b29d36fa | feat(reskinnable-demo): harness prompt and scratch workspace | ||
|
|
d0a3706a65 | feat(reskinnable-demo): harness types + offsite expense fixture | ||
|
|
97addd4cc5 |
CrewAI Flows and Conversational Flows full D6 (#6392)
## Summary Bring CrewAI Flows and CrewAI Conversational Flows to the complete showcase D6 surface on the official integration stack: - `ag-ui-crewai==0.3.0` - `ag-ui-protocol==0.1.19` - `crewai==1.15.11` - GPT-5.4 across CrewAI showcase agents The implementation follows the established D6 behavior while keeping execution native to CrewAI. It covers reasoning and chained tools, interrupts, shared and streaming state, generative UI, A2UI and recovery, multimodal image/PDF input, and the remaining showcase surfaces. ## Conversational Flows and docs - Adds a Conversational Flows implementation with full feature parity with regular CrewAI Flows. - Keeps a single CrewAI documentation integration and adds a focused guide for promoting a Flow to a Conversational Flow. - Documents CrewAI `1.15.11` as the minimum supported version for Conversational Flows. - All manifest capabilities have connected documentation; no separate feature-parity page or alpha guidance remains. ## Review hardening - Offloads synchronous beautiful-chat backend tool work from the event loop and verifies heartbeat/cancellation behavior. - Probes `reasoning-custom` and `reasoning-default` as independent D6 cells. - Removes the legacy multimodal fixture collision and requires D6-specific response evidence. - Forces the scheduling tool contract, preserves fallback slot selection, and treats protocol cancellation as cancellation. - Restores shared `data/` staging parity between the shell and TypeScript showcase lifecycle, with an erosion guard. - Materializes shared `data/` symlinks in both showcase build workflows and returns the frontend matrix test to the CI gate with catalog-derived counts. ## Surgical cancellation follow-up This follow-up is deliberately limited to the two cancellation edge cases identified in review; it does not add or change demo inventory, UI, dependencies, or workflows. - Makes Beautiful Chat's secondary `generate_a2ui` request genuinely cancellable by using `AsyncOpenAI`, while retaining the thread fallback for synchronous backend tools. - Adds a narrowly version-scoped compatibility shim for the pinned `ag-ui-crewai==0.3.0` bridge so a resolved `null` remains distinct from cancellation. Only resolved-null is encoded as JSON `null`; cancellation remains blank, both captured bridge bindings are updated, and version drift fails loudly. - Reuses the canonical shared `render_a2ui` schema for the secondary model request. ## Validation - CrewAI Flows production-equivalent isolated D6 matrix: **green** - CrewAI Conversational Flows production-equivalent isolated D6 matrix: **green** - CrewAI Flows Python: **157 passing** - CrewAI Conversational Flows Python: **162 passing** - Showcase harness: **3,708 passing / 18 skipped** - Showcase scripts: **2,505 passing** - Both CrewAI production builds: **62/62 pages generated** - Production Docker images build successfully with `ag-ui-crewai==0.3.0` installed - Full live GPT-5.4 validation exercised both implementations, including ordinary chat, reasoning chains, interrupts, multimodal PDF/image input, and A2UI generation - Final PR CI at `f97f0768ba`: **76 successful / 3 intentionally skipped / 0 pending / 0 failing** The live matrix produced valid alternate model behavior for two deterministic fixture assertions: custom-catchall narration wording and A2UI recovery succeeding on the first valid render rather than forcing a malformed retry. Both underlying features completed successfully; these are harness-vs-live nondeterminism rather than integration failures. |
||
|
|
fa13d52502 | Merge branch 'main' into codex/crewai-full-d6 | ||
|
|
f97f0768ba |
test(showcase): isolate CrewAI resume bridge contracts
Exercise both bridge bindings without leaking monkeypatches, and verify rejected bridge versions cannot mutate either binding. |
||
|
|
2116257e1e |
test(showcase): harden CrewAI cancellation regressions
Use bounded dispatch and cancellation waits in both CrewAI integrations, and verify any fallback worker finishes during cleanup. |
||
|
|
35aa2a34a0 |
fix(showcase): close CrewAI cancellation edge cases
Use AsyncOpenAI so cancellation reaches the in-flight GenerateA2UI request while retaining the thread fallback for synchronous backend tools. Preserve cancelled versus resolved-null interrupts across pinned ag-ui-crewai 0.3.0 by encoding only resolved null as JSON null and failing loudly on version drift. Reuse the canonical shared render_a2ui schema so the secondary request remains aligned with the shared tool contract. |
||
|
|
6a5bb62b62 |
docs(README): Channels is live — add per-channel status (#6486)
Channels is live at [copilotkit.ai/channels](https://www.copilotkit.ai/channels), but the README still gated it behind an early-access form and claimed eight channels were supported when only two of them ship today. This cleans that up and brings the README in line with how we talk about Channels now. ## What changed **Channels is no longer early access.** The `🔒 Early access — we're onboarding teams now` block and the `Request early access →` form link are gone, replaced with a straight link to the live Channels page. **"Beyond the Browser" is retired.** That section is now **Channels: One Agent, Every Chat App**, and the copy leads with the Channels SDK — the agent you already built, dropped into the chat apps your users live in, no rewrite. **Honest status per channel.** The platform table used to claim `✅ Supported` for eight channels in a single row. It now has two: Slack and Microsoft Teams as Supported, with a quickstart you can follow today, and Discord, WhatsApp, Telegram, Google Chat, iMessage, and SMS as Coming soon, pointing at the Channels page. No per-package or npm links anywhere. Package paths and versions move too fast to keep accurate in a README, and a channel with no shipped quickstart shouldn't send anyone to a 404. When one ships, it moves up to the Supported row with a real link. **New banner.** The old art above the badges showed only agent-framework logos — half the story. The new one shows both halves: every agent framework *and* every channel. It lives at `assets/bring-your-own-agent-any-channel.png` so it ships with the repo. The link target is unchanged. **Messaging matches the site.** copilotkit.ai now leads with "Connect any agent to any user," so two lines were updated to match: - the subtitle now names Slack and Microsoft Teams instead of "beyond the browser" - "a multi-platform agentic framework" is now "the **horizontal layer between your agents and your users**" **One restored sentence.** `Your agent logic stays the same — AG-UI handles the wire protocol, CopilotKit handles the UI layer…` was dropped in #6239. It's the only line that explains what the platform table is showing, so it's back. ## How the Supported / Coming soon line was drawn Checked against the repo, npm, and the docs site rather than going off existing prose. Slack, Teams, and WhatsApp have live docs pages; Discord and Telegram have code but no docs; Google Chat, iMessage, and SMS have neither. Slack and Teams are also the two the site markets today, so those are the Supported rows. WhatsApp is the debatable one — it has a published package *and* a live docs page, so there's a case for promoting it. Left as Coming soon deliberately; happy to flip it if that's wrong. Worth a conscious ack: the banner shows all eight channel logos while the table calls six of them Coming soon. That's intentional — it's the launch art already running on the site. ## GTM impact The `go.copilotkit.ai/beyond-the-web-form` link is removed **from the Channels section only**. It's still live in the Self-Learning section, which is genuinely still early access, so the go-link and its attribution keep working. No campaign loses its destination. One new outbound destination: `copilotkit.ai/channels`. No tracking, pixels, or analytics touched — this is a README-only change. ## Verification Everything below was checked against the live rendered branch, not assumed: - Every link in the diff returns 200 — the Channels page, and the Slack, Teams, and WhatsApp docs pages. - The new banner renders. `*.png` is LFS-tracked in this repo and no other README image is LFS-backed, so this was worth confirming: `raw.githubusercontent.com` serves the 131-byte LFS pointer, but `github.com/…/raw/…` — the path GitHub's README renderer actually uses — returns the real `image/png`, 856,322 bytes. Confirmed on the rendered branch page. - No empty or dead links in the README. - Formatting matches the repo's `oxfmt` config. ## Follow-up AG-UI's README has the mirror-image problem — it lists Discord, WhatsApp, and Telegram as In Progress and duplicates the 1st-party Slack/Teams row. Handling that separately in ag-ui-protocol/ag-ui#2279. |
||
|
|
daaad93fd6 |
docs(README): drop the per-channel table, it repeated the platform table
The platform table above already says which channels are supported and which are coming soon, so the second table said it twice. The Channels section keeps the banner, the Channels SDK copy, and the link out to the Channels page. The coming-soon row now links straight to copilotkit.ai/channels instead of jumping to a section that no longer lists those channels. |
||
|
|
27c37552d2 |
docs(README): rename the section to Channels and list every channel
"Beyond the Browser" is not how we talk about this anymore. The section is now "Channels: One Agent, Every Chat App" and the copy leads with the Channels SDK. Lists all eight channels from the banner individually instead of lumping six of them into one row. No per-package links: those move too fast to keep accurate in a README, and channels without a shipped quickstart shouldn't send anyone to a 404. |
||
|
|
9ff8a6c75d | style: auto-fix formatting | ||
|
|
b973ed6f77 |
docs(README): swap the top banner for the Bring Your Own Agent, Any Channel art
The old banner showed only the agent-framework logos. The new one shows both halves of the story — every agent framework AND every channel — which matches how the site now positions CopilotKit. Committed under assets/ rather than a CDN upload so the image ships with the repo. Link target is unchanged (go.copilotkit.ai/copilotkit-docs). |
||
|
|
b678bc1100 |
docs(README): split Channels into Supported vs Coming soon, drop early access
Slack and Microsoft Teams are the two channels that actually ship today: both have packages in this repo, published @copilotkit/channels-* builds, and docs pages. Discord, WhatsApp, Telegram, Google Chat, iMessage, and SMS had no quickstart to point at, so they move to a "Coming soon" row instead of claiming Supported. Channels is live, so the early-access gate is replaced with the public https://www.copilotkit.ai/channels page. Also restores the AG-UI explainer sentence under the platform table, which was dropped in #6239. |
||
|
|
e6510884a6 |
chore(README): Revise supported platforms in README (#6239)
Updated supported platforms and added new messaging services. <!-- Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. **Please PLEASE reach out to us first before starting any significant work on new or existing features.** By the time you've gotten here, you're looking at creating a pull request so hopefully we're not too late. We love community contributions! That said, we want to make sure we're all on the same page before you start. Investing a lot of time and effort just to find out it doesn't align with the upstream project feels awful, and we don't want that to happen. It also helps to make sure the work you're planning isn't already in progress. As described in our contributing guide, please file an issue first: https://github.com/ag-ui-protocol/ag-ui/issues Or, reach out to us on Discord: https://discord.com/invite/6dffbvGU3D You can learn more about contributing to copilotkit here: https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md Happy contributing! --> ## What does this PR do? (Describe the changes introduced in this PR) ## Related PRs and Issues - (Direct link to related PR or issue, if relevant) ## Checklist - [ ] I have read the [Contribution Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md) - [ ] If the PR changes or adds functionality, I have updated the relevant documentation - [ ] "Allow edits by maintainers" is checked (lets us help iterate on your PR directly — faster turnaround for everyone) |
||
|
|
65742fbea1 |
feat(reskinnable-demo): bring every skin to demo-beat parity, and hoist teach mode, PDFs and attachments into the shell (#6455)
Takes `airline` and `keel` from ~1 demo beat each to full parity, and hoists three per-skin mechanisms into the shell on the way. Merges `main`, so `bookstore` is included. **Verified live** by the author: Aeronova's new opening chart and Rowan's beat-3c fix both behave correctly against a running app. ## What changed **1. Three mechanisms hoisted out of the skins and into the shell** `teach-mode recording`, `PDF generation` and `attachment staging` had each been copied into three skins, and the copies had diverged. Every failure mode of a diverged copy is silent — `useRecording` returns inert no-ops outside a provider, `logStep` early-returns while idle — so a broken copy still compiles and renders and is discovered on stage. They now live in `src/shell/teach`, `src/shell/documents` and `src/shell/attach`, with a "DO NOT IMPLEMENT THE CHAIN" guard in `templates.md` so a fourth copy cannot grow. **2. `logistics`, `airline` and `keel` brought to beat parity** `logistics` gained beats 2 and 3a–3d, then 4, 5 and 6. `airline` and `keel` were converted from in-memory `useData` stores to REST substrates and taken through every beat. Airline stays a PASSENGER concierge on purpose: its beat-6 gate is ENTITLEMENT (a fare's own conditions), not organizational authority — a rejected first attempt reframed it as an ops-control desk, and the passenger framing turned out to make the gate stronger, since no choice of option can evade a fare rule. **3. `main` merged, including `bookstore`** Seven skins now. The merge conflicted in seven files because both sides hand-maintained the same roster; resolved by taking the union and replacing counts with the commands that derive them. ## Current state, derived rather than asserted ``` ls src/skins/ -> 7 skins ls -d src/app/api/*/v1 -> 7 REST substrates ls src/skins/*/intelligence/seed-memories.ts -> 7/7 grep -rln useAgentContext src/skins/*/layout.tsx -> 7/7 route readables grep -l offerWorkflowRecording src/skins/*/tools.tsx -> 6/7 teach loops grep -l 'useData:' src/skins/*/skin.tsx -> bookstore only ``` `bookstore` is the one skin not demo-complete — it marks beats 3d and 6 `SKIPPED` with a reason in its own beat map, which is a scope decision rather than a gap. It is also the only remaining `useData` implementor, so both substrates are live. ## Bugs found and fixed that were not in scope - **`resolvePage` returned `Object.prototype` members.** `/banking/constructor` answered 500 where it owed 404, on three shipped skins: an object literal inherits the prototype, so `PAGES["constructor"]` is a truthy Function and `?? null` never fires. Fixed, plus a shell guard walking every registered skin, mutation-verified. - **A real `TS2352` in a test file** that three green gates missed, because nothing in this repo type-checks tests. Now `pnpm typecheck`. - **A genuinely flaky test** in `shell/attach`, quantified at 39ms against a 40ms budget under load. Its old assertion also passed under a mutated implementation; the replacement drives the encode instead of timing it. - **Rowan's beat-3c pill described the levers instead of firing the HITL card** — the tool said "confirm the levers with them first" without saying the card IS the confirmation, and the prompt never named the tool. ## Verification `pnpm lint` · `pnpm typecheck` · `pnpm test:unit` (214 files / 2448 tests) · `pnpm build` — all clean. ⚠️ **What tests cannot cover.** Beats 2, 4, 5 and 6 are runtime-conditional and need a live Intelligence stack. The suites prove the code and the prompts are right, not that the model obeys them. Airline's chart and Rowan's fix were confirmed by hand; the memory and teach-mode beats on the other skins have not been re-walked. ⚠️ **Several commits used `--no-verify`**, each recording why in its body: the pre-commit hook fails on a pre-existing `@copilotkit/vue` SSR test that times out at 5s on this machine and fails standalone with no merge in progress. This branch's diff is entirely inside `examples/showcases/reskinnable-demo`. Also fixed along the way: `packages/runtime`'s `better-sqlite3` was compiled against Node 24 while `.nvmrc` pins Node 22, so every `SqliteAgentRunner` test threw on load. ## Reskin skill impact Answered per the standing rule in `CLAUDE.md`. The skill was updated in the same PR: the `Skin` contract's `useData` row, the beat matrix, the demo-completeness routing table, the memory-scope guidance (`user`, not banking's `project` — `forget-memories` skips project rows, so a project-scoped learned procedure survives every presenter reset), the beat-3c two-readings failure, and the `resolvePage` prototype hazard. Historical narration was stripped throughout: the docs now record current state and forward instruction only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
6e07637083 | Merge branch 'main' into feat/reskinnable-demo-beat-parity | ||
|
|
be40072891 |
revert: remove public AEO surface contract (#6483)
## Summary - revert #6458 and remove the public AEO contract, validator, docs page, capability endpoint, CI enforcement, and related tests - preserve the later AEO production synthetics from #6459 by giving them a self-contained host and endpoint configuration - update the synthetic workflow and runbook so they no longer refer to the reverted contract ## Why PR #6458 needs to be rolled back. A literal revert left #6459 importing the removed validator and reading the removed contract, so this PR also decouples that follow-on while retaining its production checks. ## Impact The `/aeo` page and `/.well-known/copilotkit-capabilities/v1.json` endpoint are removed, along with the contract validation CI. Existing website/docs crawler synthetics remain available as a manual workflow. ## Validation - `pnpm nx run @copilotkit/showcase-scripts:test --skip-nx-cache -- __tests__/check-aeo-synthetics.test.ts __tests__/aeo-synthetics-wiring.test.ts` (6 tests) - `npm --prefix showcase/shell-docs test -- src/app/sitemap.test.ts src/lib/__tests__/next-config-redirects.test.ts` (12 tests) - `git diff --check origin/main...HEAD` Reverts #6458. |
||
|
|
335209b39a | Merge branch 'main' into feat/reskinnable-demo-beat-parity | ||
|
|
cf59bc51ba | fix(showcase): decouple AEO synthetics from reverted contract | ||
|
|
3bf6e30e9a |
feat(reskinnable-demo): open Aeronova's demo on a flight-cadence chart
Beat 1 is the demo's first move, and it was answering "how do my trips look?"
with a trip wall. It now answers "How often do I fly?" with a picture: every
trip on the account laid out on a day scale, a today divider, the disrupted
ones called out, and the average gap between trips.
WHY A STRIP AND NOT BARS. The account holds seven trips across about ten weeks.
Monthly bars collapse that to three columns, hide which trips are disrupted, and
read as a stub on a projector. The strip uses all seven, and the GAPS are the
actual answer to "how often" -- which is why the summary quotes the average gap
rather than a count.
MEASURED against the shipped seed and the app's own clock, pinned in
data/flight-cadence.test.ts:
7 markers - 0 flown - 7 ahead - 2 disrupted - average gap 11 days
Note the clock. This app runs on a FIXED demo clock (`store.ts` publishes
`now: SEED_NOW`, 2026-07-14), not the wall clock, so every seeded trip is AHEAD
and the strip is forward-looking. "About every 11 days" is therefore the honest
answer, and it is a better one than any count of flights behind us.
Structure:
- `data/flight-cadence.ts` -- pure, no React, no Date. Takes `now` as an
argument and reads days out of the ISO string by civil-day arithmetic.
Both rules are load-bearing here: a `Date.now()` would put the divider in
one place on the server and another in the browser (the hydration class
this branch already chased once), and `new Date(iso)` on a string carrying
an airport's UTC offset re-expresses a 23:00 Lima departure as the next
day. `components/local-clock.ts` makes the same argument for display; this
is its data-side counterpart.
- `components/flight-cadence-chart.tsx` -- paints only. Receives `position`
already normalised to 0..1, so there is no date maths in a component where
nothing could unit-test it.
- `showFlightCadence` registered with `useComponent`, NOT `useFrontendTool`:
only a component replays out of thread history, which is what beat 2 asks
the audience to reload and see.
Three details worth keeping:
- Only flights someone HOLDS a booking on are drawn. The ledger's `flights`
also carries the rebooking candidates, and counting offers would inflate
the answer to the question being asked.
- An unreadable departure is DROPPED and counted, never placed at day 0. A
marker at the wrong point asserts a cadence that is false while still
looking like data.
- The helper takes a structural `{ id, flightId }` rather than `Booking`, so
it accepts the client's `BookingDto` without a cast -- and therefore cannot
see `waiverGround`, beat 6's sixth leak channel.
Tests: 12 on the helper (including the offset case, the drop-don't-relocate
case, and the seed figures), 7 on the component (every marker by flight number,
the cancelled trip named in WORDS and not only as a coloured dot, summary and
picture derived from one object), and `beat-1.test.ts` pinning the contract --
pill wording, registration via useComponent rather than useFrontendTool, the
prompt naming the tool and demanding prose alongside the chart, and no `Date`
in either new file.
Also uses airline's existing amber/negative tones from `trip-list.tsx` rather
than inventing a `warn` design token -- there isn't one; the vocabulary is
brand / positive / negative.
Gates: lint clean, tsc 0 errors, 214 files / 2448 tests, build exit 0.
--no-verify for the reason recorded in
|
||
|
|
83bd1f9088 |
Revert "docs: define public AEO surface contract (#6458)"
This reverts commit |
||
|
|
e3d9c911a1 |
chore(reskinnable-demo): add a typecheck script and point the docs at it
`tsc --noEmit` is the only command in this tree that type-checks the 211 test
files -- `next build` visits only what the app's module graph reaches, and
vitest does not type-check at all. The docs already said so and told readers to
run `pnpm exec tsc --noEmit`; this makes it a script, so the command people are
told to run is one word and shows up in `package.json` beside the others.
Note this is a NEW convention here, not a missing piece being restored: no
package in this monorepo defines a typecheck script, so build-time checking is
the house norm and test files fall outside it everywhere, not just in this app.
This closes the DISCOVERABILITY half of that gap for this app only.
It does NOT make the check enforced. Nothing runs it unless a person or an
agent chooses to. Wiring it into CI is a repo-wide decision with real CI cost
across 45 packages and is deliberately not taken here.
Earned: a slot reported three green gates (lint, test:unit, build) and still
shipped a TS2352 in a test file, because none of those three look at test
files.
8 doc references updated from `pnpm exec tsc --noEmit` to `pnpm typecheck`
across README.md, CLAUDE.md, SKILL.md and demo-beats.md. Verified the script
runs clean under the new name.
--no-verify for the reason recorded in
|
||
|
|
b7c144d94a |
fix(reskinnable-demo): make Rowan's queue pill move the user, not describe the move
Reported from the running demo: clicking "Oldest pending requests" often got a
prose reply --
Confirm the levers and I'll take you there: **pending** only, sorted by
**oldest first**, top **10**.
-- and nothing else. No tool call, no confirm card, no navigation. Beat 3c
failing while looking like it worked: the answer is correct and well formatted,
and "that was a maneuver, not a link" goes unproven.
ROOT CAUSE, and why the model was not disobeying. It was obeying a sentence
that reads two ways. `showRequestQueue`'s description said "Confirm the levers
with them first" without saying WHERE that happens. The HITL card IS the
confirmation -- it lists the levers and waits -- but nothing said so, so
confirming in chat satisfied the instruction as written. Two other things left
it with no reason to prefer the tool:
- `people/agent.ts` never mentioned `showRequestQueue`, or navigation at all.
Nothing connected "show me the oldest requests" to a tool call.
- `top` was `.optional()`, and an optional lever invites the model to go and
ask for the missing value first.
`logistics` hit this and was fixed; `people` never was, because nothing pinned
the fix. This applies logistics' shape:
- the description now says the card confirms, and says not to confirm in prose;
- the prompt gains MOVE THEM, DON'T DESCRIBE THE MOVE, naming the tool and the
"in front of ... rather than describe one" framing;
- every lever is REQUIRED, with 0 as the "no limit" sentinel. That needs no
page change: the render sets the `top` query param only `if (args?.top)`,
which is falsy at 0, so the page applies no limit.
`beat-3c.test.ts` pins all three. It is source-level on purpose -- what went
wrong is what the MODEL was told, which lives in `description` and the prompt,
and nothing else in this app checks either. Mutation-verified: reverting `top`
to `.optional()` turns it red.
NOT changed: commerce. Its `top` is `.int().positive().optional()` with a stated
reason -- omitting it is exactly what its `parseTopLever` honours -- so that is a
different, documented design rather than the same defect. Its prompt already
names its nav tool.
Reskin skill impact: YES, fixed here. demo-beats.md ss 3c now records the
two-readings failure, the quoted prose it produces, both halves of the close
(description AND prompt), and the note that commerce's optional `top` is
deliberate so nobody copies the wrong shape.
Gates: lint clean, 211 files / 2420 tests passing. Committed with --no-verify
for the reason recorded in
|
||
|
|
8a6d14b29a |
fix(showcase): port pydantic-ai integration to v2 and restore live system prompts (#6379)
Ports `showcase/integrations/pydantic-ai` — the last pydantic-ai surface still on v1 — to Pydantic AI v2. Refs #6364. Three commits plus a bot formatting fix, best reviewed separately. ## 1. `chore(showcase): port pydantic-ai integration to Pydantic AI v2` - **`requirements.txt`** → `pydantic-ai-slim[ag-ui,openai]==2.22.0`, `ag-ui-protocol==0.1.19`. Drops the `opentelemetry-api<1.44` ceiling from #6374; v2 resolves cleanly against otel 1.44.0, so the workaround is no longer needed. `starlette<1.0.0` is unchanged and satisfies v2's `>=0.46.2`. - **9 `StateDeps` imports** move from `pydantic_ai.ag_ui` (removed in v2) to `pydantic_ai.ui`. - **`agent_server.py`** — `Agent.to_ag_ui()` was removed in 2.0.0, so a `mount_agent()` helper builds the equivalent Starlette sub-app and mounts it. The shape is deliberately identical to what v1's `AGUIApp` produced — a Starlette app whose only route is `POST /`, named `run_agent` — so **all 19 mount paths behave exactly as before, trailing slashes included, and no TypeScript route file changes**. `deps` is constructed **per request**. v1's `run_ag_ui` did `deps = replace(deps, state=state)`, handing each run its own object; v2's adapter does `deps.state = state`, mutating what it is given. A single shared instance under v2 therefore lets concurrent runs overwrite each other's state mid-run. ## 2. `fix(showcase): apply the multimodal provider gate to v2 native content` v2's `AGUIAdapter.load_messages` converts AG-UI attachments to native content types *before* the model boundary; v1 delivered the raw AG-UI part dicts. `_NATIVE_CONTENT` listed `BinaryContent` as a flatten fixpoint, so under v2 inline attachments were waved straight through and the entire provider gate was skipped: - inline PDFs were no longer text-extracted, so raw bytes went to OpenAI - unsupported image subtypes (HEIC/SVG/TIFF) were no longer degraded and reached the provider as images, which fails the turn - missing-mime magic-byte sniffing never ran - `AudioUrl`/`VideoUrl` were neither fixpoints nor classifiable, so they hit the fail-loud raise `BinaryContent` is no longer a fixpoint. `_classify_native_content` maps native content onto the same `(kind, scheme, mime, value)` tuple the AG-UI classifier already produces, so **every existing gate applies unchanged** — no gate logic was rewritten. `audio/*` and `video/*` are named explicitly because `_kind_for` routes them to `"other"`, and a missing mime defaults to `"image"` so the sniffer runs. Net behaviour matches v1: a supported inline image still flattens to an `ImageUrl` data URI, which is why most of the suite went green without touching assertions. Five assertions did change. They checked that state-backing content was still AG-UI `InputContent`, which encoded v1's bridging. They now assert the flatten's output (`ImageUrl`) never appears in state — the leak they were written to guard. The adjacent identity and snapshot checks that prove non-mutation are untouched. ## 3. `fix(showcase): gate url-source content and correct the v1-parity claim` Adversarial review of the first two commits found the gate was only half fixed. `_NATIVE_CONTENT` still short-circuited `ImageUrl` and `DocumentUrl`, which v2 builds from unvetted client input, so url-source attachments bypassed the gate where v1 routed them through it: - an `image/heic` or `image/svg+xml` url reached the provider as `input_image`, which the Responses API rejects — failing the turn - an `audio/mpeg` document url reached it as `input_file` - a blank-mime inline PDF went to the image sniffer instead of text extraction, because `load_messages` collapses `ImageInputContent` and `DocumentInputContent` to the same bare `BinaryContent` and erases the modality v1 defaulted on Native content is now gated **before** the fixpoint check rather than instead of it. `_classify_native_content` returns a tuple only when the gate must act; `None` means provider-safe and falls through to the fixpoint, preserving object identity. `ImageUrl` is gated rather than rerouted so a provider-safe one keeps its identity and any explicit `_media_type`. It also corrected a false claim. The `mount_agent` docstring said routing *and* behaviour were unchanged. Routing is; model input is not. v2 defaults `manage_system_prompt='server'`, so each agent's `system_prompt=` now reaches the model. On v1 it never did — `_agent_graph` emitted system parts only `if not messages` and the AG-UI bridge always supplied history — so **18 of 19 agents had silently dead system prompts on main**. A/B on both versions with the same agent and request: v1 sends 0 system-prompt parts, v2 sends 1. The new behaviour is correct and kept; the docstring now says so. ## Verification Against pydantic-ai 2.22.0, in a venv built from this branch's `requirements.txt`: - **52/52 Python tests pass**, up from 42/52. `test_multimodal_content_mapping.py`'s `importorskip` pointed at the removed `pydantic_ai.ag_ui`, which would have skipped all 43 of its tests **green** under v2; it now targets `pydantic_ai.ui.ag_ui` and uses the public `AGUIAdapter.load_messages` in place of the v1 private helper. - **16/19 mounts** return `200 text/event-stream` with `RUN_STARTED … RUN_FINISHED` and no `RUN_ERROR`, driven through the real app with `TestClient` using trailing-slash URLs as the TS routes do. The other three (`/a2ui_dynamic`, `/beautiful_chat`, `/`) reach tool execution and then fail on a raw `OpenAI()` client constructed inside a tool, which the harness cannot intercept and aimock handles in CI. - **Per-request deps isolation** confirmed on `/shared_state_read_write`: state sent by one request does not appear in the next. `build-check (pydantic-ai)` is green on this branch, and because `requirements.txt` changed, the cached pip layer was invalidated — so that was a **genuine fresh resolve of pydantic-ai 2.22.0 inside the real Dockerfile**, not a cached pass. It also confirms dropping the `opentelemetry-api<1.44` ceiling is safe. ### D6 harness probes — run, with a baseline The behavioural gate is the shared harness D6 probes. No CI job runs them for showcase paths, so they were run locally on both this branch and `main`: | | main (v1) | this branch (v2) | |---|---|---| | passed | **33** / 36 | **34** / 36 | | `reasoning-display` | ✗ `no reasoning-role message rendered within 5000ms` | ✅ **passes** | | `gen-ui-agent` | ✗ `waitForTurnComplete … runStartCount=2, done-signal-missing` | ✗ identical error | | `shared-state-read` | ✗ `Strict mode: 1 candidate fixture(s) skipped by sequence/turn state` | ✗ identical error | ```bash cd showcase AIMOCK_URL_LOCAL=http://localhost:4010 bin/showcase test pydantic-ai --d6 --direct --rebuild --cycle --verbose ``` **The port takes D6 from 33/36 to 34/36.** The two remaining failures are pre-existing on `main` with byte-identical error strings — this branch neither causes nor fixes them, and both are tracked in #6381 rather than blocking here. `gen-ui-agent` is root-caused and is not fixture drift: that demo was never ported to pydantic-ai. `src/agents/gen_ui_agent.py` exists in llamaindex with a real `set_steps` tool but has no counterpart here, the route points at `/gen_ui_tool_based/` (the chart-viz agent), and `set_steps` is declared nowhere in the package. The fixture fabricates `set_steps` calls the backend cannot honour, so pydantic-ai rejects the unknown tool and exhausts its single retry. Confirmed live against real OpenAI: the cell returns plain text, which is correct for the code as written. `reasoning-display` going green is the notable behavioural gain, and it retires a documented v1 limitation. `PARITY_NOTES.md:91-97` justifies omitting the reasoning-message branch of `use-rendered-messages.tsx` on the grounds that "PydanticAI's AG-UI adapter does not emit reasoning content today" — true on v1, false on v2. (That block is stale on two further counts: it cites `@ag-ui/core@0.0.43` where `package.json` pins 0.0.57, and claims `ReasoningMessage` is not exported where it is imported at `reasoning-block.tsx:4`.) Correcting it is tracked on #6364. To be precise about what that proves: **v2 forwards reasoning content where v1 dropped it.** The probe supplies the reasoning channel via its fixture, so what is verified is the forwarding path — adapter → AG-UI stream → frontend renderer — end to end. Whether a given model actually emits a reasoning summary live is a separate matter and outside this port's control: it requires a native reasoning model (`reasoning_agent.py` defaults to `gpt-5`, overridable via `REASONING_MODEL`) and, for summary text, a verified OpenAI organisation. A live run here returned prose with no reasoning block, consistent with the org-verification gate rather than anything in the port. Also verified: the image builds from scratch on v2. Because `requirements.txt` changed, the cached pip layer was invalidated, so `build-check (pydantic-ai)` in CI was a genuine fresh resolve of pydantic-ai 2.22.0 inside the real Dockerfile — which also confirms dropping the `opentelemetry-api<1.44` ceiling is safe. ### CI gate coverage, for the record No CI job exercises this package's runtime behaviour on a PR, on this branch or on `main`: - `test / e2e / dojo` runs from the upstream `ag-ui` checkout (`ref: main`) against upstream example agents, and filters on `packages/**` / `sdk-python/**` - `test_showcase-frontend-matrix.yml` is dispatch-only and builds the integration from `base/` — a frozen-backend React baseline - `showcase_validate.yml` asserts `tests/e2e/` exists with a minimum spec count; it does not run it - the package's own `tests/e2e/` (37 files) is invoked by nothing — per `AGENTS.md` rule 1 the measuring test is the shared harness probe, so that layer is legacy ## Remaining for #6364 Two acceptance criteria are outstanding, which is why this says Refs rather than Closes: - the harness D6 value-test (`bin/showcase test pydantic-ai --d6 --rebuild`), which no CI gate runs for showcase paths - `PARITY_NOTES.md` has 6 version-dependent blocks, 4 of which were already inaccurate against the tree before this PR; left alone deliberately to keep this diff scoped ## Possible follow-up `multimodal_agent.py` still reaches into three private APIs (`pydantic_ai._run_context`, `pydantic_ai.models.wrapper`, `pydantic_ai.models.{ModelRequestParameters,StreamedResponse}`) and subclasses `WrapperModel`, overriding `request`/`count_tokens`/`request_stream`. v2 adds a supported alternative: `AbstractCapability.before_model_request`, which receives a `ModelRequestContext` carrying `messages` and `streaming`. Migrating would delete those private imports and ~85 lines. Deliberately not in this PR — it fixes nothing and would obscure the review. |
||
|
|
4e9eee3094 |
feat(runtime): add MiniMax built-in models (#6464)
Reason: Add the current MiniMax text models to BuiltInAgent model resolution. - Register MiniMax-M3 and MiniMax-M2.7 as built-in model identifiers. - Resolve MiniMax model strings through the global endpoint with API key and regional base URL configuration. - Document both model specifiers and cover global and China endpoint selection. Checks: - `node_modules/.bin/nx run @copilotkit/runtime:test -- src/agent/__tests__/resolve-model-baseurl.test.ts` - `node_modules/.bin/nx run @copilotkit/runtime:check-types` - `pnpm validate:model-names` - `node_modules/.bin/nx format:check --files=packages/runtime/src/agent/index.ts,packages/runtime/src/agent/__tests__/resolve-model-baseurl.test.ts` - `git diff --check` |