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:
@@ -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 } : {}),
|
||||
|
||||
Reference in New Issue
Block a user