fix(sdk,cli): server-to-agent chat preload — trigger: "preload" + messages: [] in basePayload

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
This commit is contained in:
Eric Allam
2026-04-29 15:54:52 +01:00
parent 9fc1870f7d
commit 2639259652
2 changed files with 28 additions and 52 deletions
+21 -52
View File
@@ -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"),
},
],
@@ -513,6 +513,13 @@ export class AgentChat<TAgent = unknown> {
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 } : {}),