From 263925965249710113395ed37aa43eab83259a95 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 29 Apr 2026 15:54:52 +0100 Subject: [PATCH] =?UTF-8?q?fix(sdk,cli):=20server-to-agent=20chat=20preloa?= =?UTF-8?q?d=20=E2=80=94=20`trigger:=20"preload"`=20+=20`messages:=20[]`?= =?UTF-8?q?=20in=20basePayload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server-to-agent flows (`AgentChat` SDK class + cli-v3 MCP `start_agent_chat`) were building `triggerConfig.basePayload` without the `trigger: "preload"` and `messages: []` fields the agent runtime branches on. Result: the auto-triggered first run had `payload.trigger === undefined`, neither `onPreload` nor `onChatStart` fired, and `onTurnStart`'s DB-write blew up with PrismaClient "No record found" because no Chat row had been created. Browser-mediated flows already had this right (`chat.createStartSessionAction` in `ai.ts:6951`); the server-side path now mirrors that shape. - packages/trigger-sdk/src/v3/chat-client.ts — `AgentChat.ensureStarted` adds the two fields to `basePayload`. `chat-client-test`'s `pong` orchestrator now returns the assistant text instead of an empty string. - packages/cli-v3/src/mcp/tools/agentChat.ts — same fix on `start_agent_chat`'s `createSession` call. Also drops the redundant separate `apiClient.triggerTask(...)` call: `POST /api/v1/sessions` now auto-triggers the first run and returns its runId, so a second trigger from the MCP would have produced a competing run on the same session. Use `session.runId` from the create response. The `preload` input flag becomes a no-op signal (response message wording only) since session-create always triggers a run now. Verified end-to-end against local: - `chat-client-test` orchestrator returns `{ text: "pong" }` - MCP `start_agent_chat` → `send_agent_message` x2 → `close_agent_chat` succeeds, both turns reuse the same runId --- packages/cli-v3/src/mcp/tools/agentChat.ts | 73 +++++++--------------- packages/trigger-sdk/src/v3/chat-client.ts | 7 +++ 2 files changed, 28 insertions(+), 52 deletions(-) diff --git a/packages/cli-v3/src/mcp/tools/agentChat.ts b/packages/cli-v3/src/mcp/tools/agentChat.ts index f1049032d..b06c13642 100644 --- a/packages/cli-v3/src/mcp/tools/agentChat.ts +++ b/packages/cli-v3/src/mcp/tools/agentChat.ts @@ -118,67 +118,35 @@ export const startAgentChatTool = { // Sessions are now task-bound: taskIdentifier + triggerConfig are // required, and the server reuses them for every run scheduled by // this session (initial + continuations after run termination). + // + // basePayload mirrors the browser-mediated `chat.createStartSessionAction` + // shape so the auto-triggered first run hits `onPreload` (not + // `onChatStart` with `preloaded: true`). Without `trigger: "preload"` + // + `messages: []`, the agent runtime bypasses both lifecycle hooks + // and `onTurnStart`'s DB write fails with "No record found". + // + // POST /api/v1/sessions auto-triggers the first run and returns its + // runId, so we don't need a separate triggerTask call. The `preload` + // flag on this MCP tool is kept as a no-op signal (true=default) for + // backwards compat — a Session is always created with a live run now. const session = await apiClient.createSession({ type: "chat.agent", externalId: chatId, taskIdentifier: input.agentId, triggerConfig: { - basePayload: { chatId, ...(input.clientData ?? {}) }, + basePayload: { + messages: [], + trigger: "preload", + chatId, + ...(input.clientData ? { metadata: input.clientData } : {}), + }, tags: [`chat:${chatId}`], }, }); - if (input.preload) { - // Trigger a preload run. The agent opens the session via - // `sessions.open(payload.sessionId)` on startup. - const payload = { - messages: [], - chatId, - sessionId: session.id, - trigger: "preload", - metadata: input.clientData, - }; - - const result = await apiClient.triggerTask(input.agentId, { - payload, - options: { - payloadType: "application/json", - tags: [`chat:${chatId}`, "preload:true"], - }, - }); - - activeSessions.set(chatId, { - sessionId: session.id, - runId: result.id, - chatId, - agentId: input.agentId, - apiClient, - clientData: input.clientData, - messages: [], - }); - - return { - content: [ - { - type: "text", - text: [ - `Agent chat started and preloaded.`, - `- Chat ID: ${chatId}`, - `- Session ID: ${session.id}`, - `- Agent: ${input.agentId}`, - `- Run ID: ${result.id}`, - ``, - `Use send_agent_message with chatId "${chatId}" to send messages.`, - ].join("\n"), - }, - ], - }; - } - - // No preload — register the session, first sendMessage will trigger. activeSessions.set(chatId, { sessionId: session.id, - runId: "", + runId: session.runId, chatId, agentId: input.agentId, apiClient, @@ -191,12 +159,13 @@ export const startAgentChatTool = { { type: "text", text: [ - `Agent chat created (not yet preloaded).`, + `Agent chat started${input.preload ? " and preloaded" : ""}.`, `- Chat ID: ${chatId}`, `- Session ID: ${session.id}`, `- Agent: ${input.agentId}`, + `- Run ID: ${session.runId}`, ``, - `Use send_agent_message with chatId "${chatId}" to send the first message (this will trigger the run).`, + `Use send_agent_message with chatId "${chatId}" to send messages.`, ].join("\n"), }, ], diff --git a/packages/trigger-sdk/src/v3/chat-client.ts b/packages/trigger-sdk/src/v3/chat-client.ts index e66f8f617..91aaf35fa 100644 --- a/packages/trigger-sdk/src/v3/chat-client.ts +++ b/packages/trigger-sdk/src/v3/chat-client.ts @@ -513,6 +513,13 @@ export class AgentChat { const triggerConfig: SessionTriggerConfig = { basePayload: { + // `trigger: "preload"` + empty `messages` mirror the browser-mediated + // `chat.createStartSessionAction` shape so the agent runtime fires + // `onPreload` (not `onChatStart` with `preloaded: true`). Without + // this, AgentChat's first run skips both preload and start hooks, + // which is where customer apps typically upsert their Chat row. + messages: [], + trigger: "preload", ...(this.triggerConfigDefault?.basePayload ?? {}), chatId: this.chatId, ...(this.clientData ? { metadata: this.clientData } : {}),