feat: ai-chat reference project + MCP agent-chat tooling (4/4)#3546
feat: ai-chat reference project + MCP agent-chat tooling (4/4)#3546ericallam wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: 2cd4c8f The changes in this PR will be included in the next version bump. This PR includes changesets to release 32 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| }, | ||
| }); | ||
| session.runId = result.id; | ||
| session.lastEventId = undefined; |
There was a problem hiding this comment.
🔴 Resetting lastEventId to undefined on upgrade causes SSE replay of entire session history
In collectAgentResponse, when a trigger:upgrade-required chunk is received, session.lastEventId is set to undefined (line 390) before recursively calling collectAgentResponse. The recursive call creates a new SSEStreamSubscription using session.lastEventId (now undefined) as the lastEventId option (agentChat.ts:338), which means the new subscription replays the entire session .out stream from the very beginning.
Since session .out is a durable stream containing all historical chunks across runs, the replayed events will include old turns' trigger:turn-complete chunks. The first historical trigger:turn-complete hit (line 366-368) immediately breaks the collection loop, causing the function to return with empty/partial text from a previous turn instead of the new run's response.
Expected fix
Keep session.lastEventId as-is (pointing to the trigger:upgrade-required chunk's SSE id) so the recursive subscription resumes right after the upgrade marker, where the new run's output will appear.
Was this helpful? React with 👍 or 👎 to provide feedback.
ecfac76 to
7ee523e
Compare
882544c to
5baac29
Compare
7ee523e to
fdc61c6
Compare
5baac29 to
e16efbb
Compare
fdc61c6 to
96700b1
Compare
e16efbb to
f4a7923
Compare
96700b1 to
482d752
Compare
f4a7923 to
04b98af
Compare
482d752 to
920e876
Compare
04b98af to
417344a
Compare
920e876 to
d96e2f7
Compare
417344a to
43fde3f
Compare
d96e2f7 to
3256f42
Compare
43fde3f to
c27cef9
Compare
3256f42 to
220b33c
Compare
c27cef9 to
48f030a
Compare
220b33c to
067109f
Compare
48f030a to
3c71eeb
Compare
067109f to
c1f6db7
Compare
3c71eeb to
866b175
Compare
c1f6db7 to
f75bcd8
Compare
866b175 to
2d9bcdb
Compare
f75bcd8 to
40a3dff
Compare
2d9bcdb to
d9fa1be
Compare
40a3dff to
1748445
Compare
d9fa1be to
b7ed332
Compare
78a26a6 to
5e0526a
Compare
8615ad0 to
f7197ef
Compare
5e0526a to
36ccf22
Compare
36ccf22 to
a90949b
Compare
f7197ef to
5c937df
Compare
75369f3 to
cf2fa07
Compare
| prompts: workerManifest.prompts, | ||
| queues: workerManifest.queues, |
There was a problem hiding this comment.
🔴 Managed-index-controller omits skills from deployment registration body
Both dev-index-worker.ts:187 and managed-index-worker.ts:183 now include skills: resourceCatalog.listSkillManifests() in the worker manifest, and the managed-index-controller was updated to forward the new prompts field (prompts: workerManifest.prompts at line 107). However, the corresponding skills: workerManifest.skills line was not added to the backgroundWorkerBody.metadata object. This means skill manifests registered via skills.define() will be collected by the worker during managed (production) deployments but silently dropped when the controller sends the registration request to the server — skills won't be available server-side in deployed environments.
| prompts: workerManifest.prompts, | |
| queues: workerManifest.queues, | |
| prompts: workerManifest.prompts, | |
| skills: workerManifest.skills, |
Was this helpful? React with 👍 or 👎 to provide feedback.
| @@ -0,0 +1 @@ | |||
| lib/generated/ | |||
There was a problem hiding this comment.
🚩 Generated Prisma files committed in references/ai-chat
The references/ai-chat/lib/generated/prisma/ directory contains Prisma-generated client files checked into git. The .gitignore at references/ai-chat/.gitignore lists lib/generated/ which should exclude these, but the files are present in the diff. This might indicate the gitignore was added after the files were committed, or the files were force-added. These files add significant noise to the PR diff (~5000+ lines of generated code). Worth verifying the gitignore is working and removing tracked generated files if appropriate.
Was this helpful? React with 👍 or 👎 to provide feedback.
5c937df to
f5e3067
Compare
cf2fa07 to
fef74f4
Compare
| @@ -0,0 +1 @@ | |||
| {"sessionId":"a0e063d3-034b-40fe-90d0-7a6aff597e26","pid":72012,"procStart":"Tue May 12 17:34:30 2026","acquiredAt":1778608778207} No newline at end of file | |||
There was a problem hiding this comment.
🚩 Committed lock file with active session data
.claude/scheduled_tasks.lock contains a live session ID, PID, and timestamp from a specific development machine. This file appears to be machine-local state that shouldn't be committed to the repository. It could cause conflicts for other developers and doesn't serve a purpose in version control.
Was this helpful? React with 👍 or 👎 to provide feedback.
f5e3067 to
6a1e9af
Compare
fef74f4 to
343c204
Compare
| try { | ||
| await session.apiClient.appendToSessionStream( | ||
| session.sessionId, | ||
| "in", | ||
| serializeInputChunk({ kind: "message", payload: wirePayload }) | ||
| ); | ||
| } catch (sendErr: any) { | ||
| const result = await session.apiClient.triggerTask(session.agentId, { | ||
| payload: { | ||
| message: userMessage, | ||
| chatId: session.chatId, | ||
| sessionId: session.sessionId, | ||
| trigger: "submit-message", | ||
| metadata: session.clientData, | ||
| continuation: true, | ||
| previousRunId: session.runId, | ||
| }, | ||
| options: { | ||
| payloadType: "application/json", | ||
| tags: [`chat:${session.chatId}`], | ||
| }, | ||
| }); | ||
| session.runId = result.id; | ||
| session.lastEventId = undefined; | ||
| } |
There was a problem hiding this comment.
🚩 MCP sendAgentMessage fallback triggers a new run on any appendToSessionStream failure
In sendAgentMessageTool, when session.runId is set, the code tries appendToSessionStream and on any catch falls back to triggerTask to start a new run (line 234-252). This means transient network errors (timeouts, 500s) also trigger a brand-new run rather than retrying the append. The comment describes this as intentional ("run ended, token expired, etc."), but it could lead to orphaned runs if the real cause was a transient failure and the original run is still alive. In practice, the session's run-manager server-side deduplication likely prevents harmful consequences, but this is worth being aware of.
Was this helpful? React with 👍 or 👎 to provide feedback.
6a1e9af to
04e1747
Compare
343c204 to
480c5ea
Compare
04e1747 to
2a7d030
Compare
8954526 to
289ab08
Compare
| !completion.ok && | ||
| (isOOMRunError(completion.error) || isManualOutOfMemoryError(completion.error)) | ||
| ) { | ||
| this.discardProcessOnReturn = true; |
There was a problem hiding this comment.
🟡 discardProcessOnReturn never reset between retry attempts, causing healthy processes to be force-killed
The discardProcessOnReturn flag is set to true when an OOM is detected (lines 554 and 689) but is never reset back to false. When the completion result triggers a RETRY_IMMEDIATELY at packages/cli-v3/src/entryPoints/dev-run-controller.ts:784, it calls startAndExecuteRunAttempt → executeRun, which obtains a new process from the pool (line 611). After the retry completes — even if it succeeds cleanly — the process is returned with forceKill: this.discardProcessOnReturn (line 696), which is still true from the previous OOM attempt. This means a perfectly healthy process is force-killed instead of being returned to the pool for reuse. The fix is to reset this.discardProcessOnReturn = false at the top of executeRun (near line 608 where isCompletingRun is reset).
Prompt for agents
The `discardProcessOnReturn` flag is set to `true` on OOM detection but never reset between retry attempts. When `handleCompletionResult` triggers RETRY_IMMEDIATELY, the subsequent call to `executeRun` gets a fresh process from the pool, but the stale flag causes that healthy process to be force-killed on return.
Fix: Reset `this.discardProcessOnReturn = false` at the top of `executeRun()` in `dev-run-controller.ts`, near line 608 where `this.isCompletingRun = false` is already reset. This ensures each attempt starts with a clean slate — only the attempt that actually OOMs will force-kill its process.
Was this helpful? React with 👍 or 👎 to provide feedback.
…3542) ## Summary A `/sessions` dashboard for inspecting durable Sessions, an `AGENT` / `SCHEDULED` task-kind filter for the runs list, and the server-side hardening (rate-limit exemption for packets, retry-with-backoff on stream appends, typed too-large-chunk error) that the `chat.agent` runtime in #3543 needs. Builds on the Sessions primitive shipped in #3417. ## Design The Sessions list + detail routes mirror the run inspector pattern. `TaskTriggerSource` gains `AGENT` and `SCHEDULED` values, persisted on `BackgroundWorker.taskKind` and `TaskRun.taskKind` (plus a matching Clickhouse column), so the runs list can filter by kind. New `@trigger.dev/core` modules — `sessionStreams`, `inputStreams`, a `sessionStreamInstance` for realtime streams, and the `realtime-streams-api` / `session-streams-api` surfaces — expose the typed shapes that chat.agent will use to drive `session.out`. `ChatChunkTooLargeError` lets the runtime drop oversized chunks with a typed surface instead of failing the run. `s2Append` retries transient failures with exponential backoff. `/api/v[12]/packets/*` is exempt from customer rate limits so chat snapshot reads and writes don't get throttled under load. ## Stack Part of a 4-PR stack. Merge bottom-up. 1. **This PR** (#3542) → `main` 2. #3543 → #3542 — `chat.agent` runtime + browser transport 3. #3545 → #3543 — agent-view dashboard 4. #3546 → #3545 — ai-chat reference + MCP tooling Replaces #3173 (closed). <!-- GitButler Footer Boundary Top --> --- This is **part 5 of 5 in a stack** made with GitButler: - <kbd> 5 </kbd> #3612 - <kbd> 4 </kbd> #3546 - <kbd> 3 </kbd> #3545 - <kbd> 2 </kbd> #3543 - <kbd> 1 </kbd> #3542 👈 <!-- GitButler Footer Boundary Bottom -->
2a7d030 to
5fa5d3d
Compare
289ab08 to
e373556
Compare
5fa5d3d to
f8d5198
Compare
e373556 to
55d9543
Compare
Top of the chat.agent stack: a full Next.js reference project that exercises chat.agent end-to-end, plus the CLI MCP tools that drive agent runs from Claude Code / Cursor / etc. references/ai-chat: - Full Next.js app with prisma persistence, multi-chat sidebar, per-chat model picker, debug panel, tool examples, smoke tests - Reference tools: getCurrentTime, searchHackerNews, createGithubIssue, PR review helpers, code sandbox - chat-client-test orchestrator for concurrent-send stress - references/hello-world chatAgent + triggerAndSubscribe examples CLI MCP tooling for chat.agent: - mcp/tools/agentChat.ts (start_agent_chat, send_agent_message, close_agent_chat) - mcp/tools/agents.ts + tasks.ts (list agents, agent run details) - dev-run-controller OOM kill + taskRunProcessPool tweaks - dev/managed entry-point hooks for skills bundling - buildWorker + bundleSkills (agent skills support) Includes ai-tool-helpers + mcp-agent-chat-sessions changesets, plus the streamdown@2 patch and pnpm-lock reconciliation. (Will be renamed to feature/ai-chat-reference-and-cli before push.)
f8d5198 to
a08c3a4
Compare
55d9543 to
2cd4c8f
Compare
| prompts: workerManifest.prompts, | ||
| queues: workerManifest.queues, |
There was a problem hiding this comment.
🚩 managed-index-controller.ts forwards prompts but not skills to worker registration
This PR adds skills: resourceCatalog.listSkillManifests() to both dev-index-worker.ts:187 and managed-index-worker.ts:183, so the worker manifest now includes skills. Meanwhile managed-index-controller.ts:107 adds prompts: workerManifest.prompts to CreateBackgroundWorkerRequestBody but does NOT add skills: workerManifest.skills. If the server-side schema (CreateBackgroundWorkerRequestBody) accepts a skills field, this is an incomplete transformation — skills discovered during deployment indexing won't be registered with the webapp. If the schema doesn't have a skills field yet (planned for a later PR), this is fine. Worth confirming which is the case.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
A complete Next.js reference project that exercises
chat.agentend-to-end, plus the CLI MCP tools that let Claude Code, Cursor, and similar IDE agents drive a deployedchat.agenttask from the editor. Builds on #3545.Design
references/ai-chatis a full Next.js app: prisma-backed persistence, multi-chat sidebar, per-chat model picker, debug panel, tool examples (getCurrentTime,searchHackerNews,createGithubIssue, PR review helpers, code sandbox), and smoke tests. It's intended both as a copy-paste starting point and as a place to regression-test SDK changes.The CLI gains MCP tools (
start_agent_chat,send_agent_message,close_agent_chat,list_agents) so an IDE agent can converse with a deployedchat.agenttask. The dev runtime adds one-shot OOM kill on the run controller and skills bundling in the build pipeline.Test plan
cd references/ai-chat && pnpm install && pnpm trigger:devstart_agent_chatagainst the running dev task, send a message, verify the response streams backStack
Part of a 4-PR stack. Merge bottom-up.
main— Sessions dashboard + chat-ready hardeningchat.agentruntime + browser transportThis is part 2 of 5 in a stack made with GitButler: