// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: chat.sql package db import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const createChatMessage = `-- name: CreateChatMessage :one INSERT INTO chat_message (chat_session_id, role, content, task_id, failure_reason, elapsed_ms) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, chat_session_id, role, content, task_id, created_at, failure_reason, elapsed_ms ` type CreateChatMessageParams struct { ChatSessionID pgtype.UUID `json:"chat_session_id"` Role string `json:"role"` Content string `json:"content"` TaskID pgtype.UUID `json:"task_id"` FailureReason pgtype.Text `json:"failure_reason"` ElapsedMs pgtype.Int8 `json:"elapsed_ms"` } func (q *Queries) CreateChatMessage(ctx context.Context, arg CreateChatMessageParams) (ChatMessage, error) { row := q.db.QueryRow(ctx, createChatMessage, arg.ChatSessionID, arg.Role, arg.Content, arg.TaskID, arg.FailureReason, arg.ElapsedMs, ) var i ChatMessage err := row.Scan( &i.ID, &i.ChatSessionID, &i.Role, &i.Content, &i.TaskID, &i.CreatedAt, &i.FailureReason, &i.ElapsedMs, ) return i, err } const createChatSession = `-- name: CreateChatSession :one INSERT INTO chat_session (workspace_id, agent_id, creator_id, title, runtime_id) VALUES ($1, $2, $3, $4, (SELECT runtime_id FROM agent WHERE id = $2)) RETURNING id, workspace_id, agent_id, creator_id, title, session_id, work_dir, status, created_at, updated_at, unread_since, runtime_id ` type CreateChatSessionParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` AgentID pgtype.UUID `json:"agent_id"` CreatorID pgtype.UUID `json:"creator_id"` Title string `json:"title"` } func (q *Queries) CreateChatSession(ctx context.Context, arg CreateChatSessionParams) (ChatSession, error) { row := q.db.QueryRow(ctx, createChatSession, arg.WorkspaceID, arg.AgentID, arg.CreatorID, arg.Title, ) var i ChatSession err := row.Scan( &i.ID, &i.WorkspaceID, &i.AgentID, &i.CreatorID, &i.Title, &i.SessionID, &i.WorkDir, &i.Status, &i.CreatedAt, &i.UpdatedAt, &i.UnreadSince, &i.RuntimeID, ) return i, err } const createChatTask = `-- name: CreateChatTask :one INSERT INTO agent_task_queue (agent_id, runtime_id, issue_id, status, priority, chat_session_id, initiator_user_id) VALUES ($1, $2, NULL, 'queued', $3, $4, $5) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id ` type CreateChatTaskParams struct { AgentID pgtype.UUID `json:"agent_id"` RuntimeID pgtype.UUID `json:"runtime_id"` Priority int32 `json:"priority"` ChatSessionID pgtype.UUID `json:"chat_session_id"` InitiatorUserID pgtype.UUID `json:"initiator_user_id"` } func (q *Queries) CreateChatTask(ctx context.Context, arg CreateChatTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, createChatTask, arg.AgentID, arg.RuntimeID, arg.Priority, arg.ChatSessionID, arg.InitiatorUserID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, ) return i, err } const deleteChatSession = `-- name: DeleteChatSession :exec DELETE FROM chat_session WHERE id = $1 AND workspace_id = $2 ` type DeleteChatSessionParams struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` } // Hard delete. chat_message rows cascade via FK ON DELETE CASCADE; the // chat_session_id on agent_task_queue is set NULL by FK so completed/failed // task history survives the session being removed. Callers MUST run inside // the same transaction that holds LockChatSessionForDelete and that has // already cancelled any in-flight tasks (see CancelAgentTasksByChatSession) // so the daemon does not keep running work whose result has nowhere to // land. workspace_id in the WHERE clause is a SQL-layer tenant guard; see // DeleteIssue. func (q *Queries) DeleteChatSession(ctx context.Context, arg DeleteChatSessionParams) error { _, err := q.db.Exec(ctx, deleteChatSession, arg.ID, arg.WorkspaceID) return err } const getChatMessage = `-- name: GetChatMessage :one SELECT id, chat_session_id, role, content, task_id, created_at, failure_reason, elapsed_ms FROM chat_message WHERE id = $1 ` func (q *Queries) GetChatMessage(ctx context.Context, id pgtype.UUID) (ChatMessage, error) { row := q.db.QueryRow(ctx, getChatMessage, id) var i ChatMessage err := row.Scan( &i.ID, &i.ChatSessionID, &i.Role, &i.Content, &i.TaskID, &i.CreatedAt, &i.FailureReason, &i.ElapsedMs, ) return i, err } const getChatSession = `-- name: GetChatSession :one SELECT id, workspace_id, agent_id, creator_id, title, session_id, work_dir, status, created_at, updated_at, unread_since, runtime_id FROM chat_session WHERE id = $1 ` func (q *Queries) GetChatSession(ctx context.Context, id pgtype.UUID) (ChatSession, error) { row := q.db.QueryRow(ctx, getChatSession, id) var i ChatSession err := row.Scan( &i.ID, &i.WorkspaceID, &i.AgentID, &i.CreatorID, &i.Title, &i.SessionID, &i.WorkDir, &i.Status, &i.CreatedAt, &i.UpdatedAt, &i.UnreadSince, &i.RuntimeID, ) return i, err } const getChatSessionInWorkspace = `-- name: GetChatSessionInWorkspace :one SELECT id, workspace_id, agent_id, creator_id, title, session_id, work_dir, status, created_at, updated_at, unread_since, runtime_id FROM chat_session WHERE id = $1 AND workspace_id = $2 ` type GetChatSessionInWorkspaceParams struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` } func (q *Queries) GetChatSessionInWorkspace(ctx context.Context, arg GetChatSessionInWorkspaceParams) (ChatSession, error) { row := q.db.QueryRow(ctx, getChatSessionInWorkspace, arg.ID, arg.WorkspaceID) var i ChatSession err := row.Scan( &i.ID, &i.WorkspaceID, &i.AgentID, &i.CreatorID, &i.Title, &i.SessionID, &i.WorkDir, &i.Status, &i.CreatedAt, &i.UpdatedAt, &i.UnreadSince, &i.RuntimeID, ) return i, err } const getLastChatTaskSession = `-- name: GetLastChatTaskSession :one SELECT session_id, work_dir, runtime_id FROM agent_task_queue WHERE chat_session_id = $1 AND ( status = 'completed' OR ( status = 'failed' AND COALESCE(failure_reason, '') NOT IN ('iteration_limit', 'agent_fallback_message', 'api_invalid_request', 'codex_semantic_inactivity') AND NOT (COALESCE(error, '') ILIKE '%400%' AND COALESCE(error, '') ILIKE '%invalid_request_error%') ) ) AND session_id IS NOT NULL ORDER BY completed_at DESC LIMIT 1 ` type GetLastChatTaskSessionRow struct { SessionID pgtype.Text `json:"session_id"` WorkDir pgtype.Text `json:"work_dir"` RuntimeID pgtype.UUID `json:"runtime_id"` } // Returns the most recent task in this chat session that managed to record a // session_id. Includes both completed and failed tasks: even a failed task // may have established a real agent session before failing, and we'd rather // resume there than start over and lose conversation memory. Used as a // fallback when chat_session.session_id is NULL. Resume-unsafe failures are // excluded because replaying those sessions deterministically reproduces the // same terminal state. func (q *Queries) GetLastChatTaskSession(ctx context.Context, chatSessionID pgtype.UUID) (GetLastChatTaskSessionRow, error) { row := q.db.QueryRow(ctx, getLastChatTaskSession, chatSessionID) var i GetLastChatTaskSessionRow err := row.Scan(&i.SessionID, &i.WorkDir, &i.RuntimeID) return i, err } const getMostRecentUserChatMessage = `-- name: GetMostRecentUserChatMessage :one SELECT id, chat_session_id, role, content, task_id, created_at, failure_reason, elapsed_ms FROM chat_message WHERE chat_session_id = $1 AND role = 'user' ORDER BY created_at DESC LIMIT 1 ` // Returns the most recent role='user' message in a session. Used by the // Lark `/issue` command parser: when the user types `/issue` with no // title, the spec falls back to "use the previous user message as the // title". Bot replies (role='assistant') are excluded — only human // input qualifies as a fallback title source. func (q *Queries) GetMostRecentUserChatMessage(ctx context.Context, chatSessionID pgtype.UUID) (ChatMessage, error) { row := q.db.QueryRow(ctx, getMostRecentUserChatMessage, chatSessionID) var i ChatMessage err := row.Scan( &i.ID, &i.ChatSessionID, &i.Role, &i.Content, &i.TaskID, &i.CreatedAt, &i.FailureReason, &i.ElapsedMs, ) return i, err } const getPendingChatTask = `-- name: GetPendingChatTask :one SELECT id, status, created_at FROM agent_task_queue WHERE chat_session_id = $1 AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') ORDER BY created_at DESC LIMIT 1 ` type GetPendingChatTaskRow struct { ID pgtype.UUID `json:"id"` Status string `json:"status"` CreatedAt pgtype.Timestamptz `json:"created_at"` } // Returns the most recent in-flight task for a chat session, if any. // Used by the frontend to recover pending state after refresh / reopen. // created_at is the anchor for the chat StatusPill timer (it computes // elapsed = now - task.created_at), so the pill survives refresh / reopen // without "resetting to 0s". func (q *Queries) GetPendingChatTask(ctx context.Context, chatSessionID pgtype.UUID) (GetPendingChatTaskRow, error) { row := q.db.QueryRow(ctx, getPendingChatTask, chatSessionID) var i GetPendingChatTaskRow err := row.Scan(&i.ID, &i.Status, &i.CreatedAt) return i, err } const listAllChatSessionsByCreator = `-- name: ListAllChatSessionsByCreator :many SELECT cs.id, cs.workspace_id, cs.agent_id, cs.creator_id, cs.title, cs.session_id, cs.work_dir, cs.status, cs.created_at, cs.updated_at, cs.unread_since, cs.runtime_id, (cs.unread_since IS NOT NULL)::bool AS has_unread FROM chat_session cs WHERE cs.workspace_id = $1 AND cs.creator_id = $2 ORDER BY cs.updated_at DESC ` type ListAllChatSessionsByCreatorParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` CreatorID pgtype.UUID `json:"creator_id"` } type ListAllChatSessionsByCreatorRow struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` AgentID pgtype.UUID `json:"agent_id"` CreatorID pgtype.UUID `json:"creator_id"` Title string `json:"title"` SessionID pgtype.Text `json:"session_id"` WorkDir pgtype.Text `json:"work_dir"` Status string `json:"status"` CreatedAt pgtype.Timestamptz `json:"created_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` UnreadSince pgtype.Timestamptz `json:"unread_since"` RuntimeID pgtype.UUID `json:"runtime_id"` HasUnread bool `json:"has_unread"` } func (q *Queries) ListAllChatSessionsByCreator(ctx context.Context, arg ListAllChatSessionsByCreatorParams) ([]ListAllChatSessionsByCreatorRow, error) { rows, err := q.db.Query(ctx, listAllChatSessionsByCreator, arg.WorkspaceID, arg.CreatorID) if err != nil { return nil, err } defer rows.Close() items := []ListAllChatSessionsByCreatorRow{} for rows.Next() { var i ListAllChatSessionsByCreatorRow if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.AgentID, &i.CreatorID, &i.Title, &i.SessionID, &i.WorkDir, &i.Status, &i.CreatedAt, &i.UpdatedAt, &i.UnreadSince, &i.RuntimeID, &i.HasUnread, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listChatMessages = `-- name: ListChatMessages :many SELECT id, chat_session_id, role, content, task_id, created_at, failure_reason, elapsed_ms FROM chat_message WHERE chat_session_id = $1 ORDER BY created_at ASC ` func (q *Queries) ListChatMessages(ctx context.Context, chatSessionID pgtype.UUID) ([]ChatMessage, error) { rows, err := q.db.Query(ctx, listChatMessages, chatSessionID) if err != nil { return nil, err } defer rows.Close() items := []ChatMessage{} for rows.Next() { var i ChatMessage if err := rows.Scan( &i.ID, &i.ChatSessionID, &i.Role, &i.Content, &i.TaskID, &i.CreatedAt, &i.FailureReason, &i.ElapsedMs, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listChatMessagesPage = `-- name: ListChatMessagesPage :many SELECT id, chat_session_id, role, content, task_id, created_at, failure_reason, elapsed_ms FROM chat_message WHERE chat_session_id = $1 AND ( $3::timestamptz IS NULL OR (created_at, id) < ($3::timestamptz, $4::uuid) ) ORDER BY created_at DESC, id DESC LIMIT $2 ` type ListChatMessagesPageParams struct { ChatSessionID pgtype.UUID `json:"chat_session_id"` Limit int32 `json:"limit"` BeforeCreatedAt pgtype.Timestamptz `json:"before_created_at"` BeforeID pgtype.UUID `json:"before_id"` } func (q *Queries) ListChatMessagesPage(ctx context.Context, arg ListChatMessagesPageParams) ([]ChatMessage, error) { rows, err := q.db.Query(ctx, listChatMessagesPage, arg.ChatSessionID, arg.Limit, arg.BeforeCreatedAt, arg.BeforeID, ) if err != nil { return nil, err } defer rows.Close() items := []ChatMessage{} for rows.Next() { var i ChatMessage if err := rows.Scan( &i.ID, &i.ChatSessionID, &i.Role, &i.Content, &i.TaskID, &i.CreatedAt, &i.FailureReason, &i.ElapsedMs, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listChatSessionsByCreator = `-- name: ListChatSessionsByCreator :many SELECT cs.id, cs.workspace_id, cs.agent_id, cs.creator_id, cs.title, cs.session_id, cs.work_dir, cs.status, cs.created_at, cs.updated_at, cs.unread_since, cs.runtime_id, (cs.unread_since IS NOT NULL)::bool AS has_unread FROM chat_session cs WHERE cs.workspace_id = $1 AND cs.creator_id = $2 AND cs.status = 'active' ORDER BY cs.updated_at DESC ` type ListChatSessionsByCreatorParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` CreatorID pgtype.UUID `json:"creator_id"` } type ListChatSessionsByCreatorRow struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` AgentID pgtype.UUID `json:"agent_id"` CreatorID pgtype.UUID `json:"creator_id"` Title string `json:"title"` SessionID pgtype.Text `json:"session_id"` WorkDir pgtype.Text `json:"work_dir"` Status string `json:"status"` CreatedAt pgtype.Timestamptz `json:"created_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` UnreadSince pgtype.Timestamptz `json:"unread_since"` RuntimeID pgtype.UUID `json:"runtime_id"` HasUnread bool `json:"has_unread"` } // Returns active sessions with a boolean unread flag. Unread is strictly // per-session: either the user has uncleared assistant replies in this // session or they don't. Counting messages would be misleading. func (q *Queries) ListChatSessionsByCreator(ctx context.Context, arg ListChatSessionsByCreatorParams) ([]ListChatSessionsByCreatorRow, error) { rows, err := q.db.Query(ctx, listChatSessionsByCreator, arg.WorkspaceID, arg.CreatorID) if err != nil { return nil, err } defer rows.Close() items := []ListChatSessionsByCreatorRow{} for rows.Next() { var i ListChatSessionsByCreatorRow if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.AgentID, &i.CreatorID, &i.Title, &i.SessionID, &i.WorkDir, &i.Status, &i.CreatedAt, &i.UpdatedAt, &i.UnreadSince, &i.RuntimeID, &i.HasUnread, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPendingChatTasksByCreator = `-- name: ListPendingChatTasksByCreator :many SELECT atq.id AS task_id, atq.status, atq.chat_session_id FROM agent_task_queue atq JOIN chat_session cs ON cs.id = atq.chat_session_id WHERE cs.workspace_id = $1 AND cs.creator_id = $2 AND atq.status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') ORDER BY atq.created_at DESC ` type ListPendingChatTasksByCreatorParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` CreatorID pgtype.UUID `json:"creator_id"` } type ListPendingChatTasksByCreatorRow struct { TaskID pgtype.UUID `json:"task_id"` Status string `json:"status"` ChatSessionID pgtype.UUID `json:"chat_session_id"` } // Aggregate view of all in-flight chat tasks owned by a given creator in a // workspace. Drives the FAB's "running" indicator when the chat window is // closed and no single session's query is active. func (q *Queries) ListPendingChatTasksByCreator(ctx context.Context, arg ListPendingChatTasksByCreatorParams) ([]ListPendingChatTasksByCreatorRow, error) { rows, err := q.db.Query(ctx, listPendingChatTasksByCreator, arg.WorkspaceID, arg.CreatorID) if err != nil { return nil, err } defer rows.Close() items := []ListPendingChatTasksByCreatorRow{} for rows.Next() { var i ListPendingChatTasksByCreatorRow if err := rows.Scan(&i.TaskID, &i.Status, &i.ChatSessionID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const lockChatSessionForDelete = `-- name: LockChatSessionForDelete :one SELECT id FROM chat_session WHERE id = $1 FOR UPDATE ` // Acquires an exclusive (FOR UPDATE) row lock on chat_session(id). Used by // the delete path so that a concurrent SendChatMessage cannot enqueue a new // agent_task_queue row referencing this session between our cancel and // delete steps. The FK from agent_task_queue.chat_session_id takes a // KEY SHARE lock on the parent row during INSERT validation, which // conflicts with FOR UPDATE — concurrent inserts block here and then fail // their FK check after we commit the delete. func (q *Queries) LockChatSessionForDelete(ctx context.Context, id pgtype.UUID) (pgtype.UUID, error) { row := q.db.QueryRow(ctx, lockChatSessionForDelete, id) var id_2 pgtype.UUID err := row.Scan(&id_2) return id_2, err } const markChatSessionRead = `-- name: MarkChatSessionRead :exec UPDATE chat_session SET unread_since = NULL WHERE id = $1 ` // Clears unread_since, dropping the session's unread count to 0. func (q *Queries) MarkChatSessionRead(ctx context.Context, id pgtype.UUID) error { _, err := q.db.Exec(ctx, markChatSessionRead, id) return err } const setUnreadSinceIfNull = `-- name: SetUnreadSinceIfNull :exec UPDATE chat_session SET unread_since = now() WHERE id = $1 AND unread_since IS NULL ` // Atomically stamps the first unread assistant message's arrival time. // No-op if the session is already in "has unread" state — keeps the earliest // unread boundary stable across multiple incoming replies. func (q *Queries) SetUnreadSinceIfNull(ctx context.Context, id pgtype.UUID) error { _, err := q.db.Exec(ctx, setUnreadSinceIfNull, id) return err } const touchChatSession = `-- name: TouchChatSession :exec UPDATE chat_session SET updated_at = now() WHERE id = $1 ` func (q *Queries) TouchChatSession(ctx context.Context, id pgtype.UUID) error { _, err := q.db.Exec(ctx, touchChatSession, id) return err } const updateChatSessionSession = `-- name: UpdateChatSessionSession :exec UPDATE chat_session SET session_id = COALESCE($1, session_id), work_dir = COALESCE($2, work_dir), runtime_id = COALESCE($3, runtime_id), updated_at = now() WHERE id = $4 ` type UpdateChatSessionSessionParams struct { SessionID pgtype.Text `json:"session_id"` WorkDir pgtype.Text `json:"work_dir"` RuntimeID pgtype.UUID `json:"runtime_id"` ID pgtype.UUID `json:"id"` } // Updates the resume pointer for a chat session. Empty/NULL inputs are // ignored via COALESCE so a task that completes without a session_id (e.g. // the agent crashed before establishing one) cannot wipe out a previously // recorded resume pointer. This makes the chat memory robust against // intermittent agent failures. func (q *Queries) UpdateChatSessionSession(ctx context.Context, arg UpdateChatSessionSessionParams) error { _, err := q.db.Exec(ctx, updateChatSessionSession, arg.SessionID, arg.WorkDir, arg.RuntimeID, arg.ID, ) return err } const updateChatSessionTitle = `-- name: UpdateChatSessionTitle :one UPDATE chat_session SET title = $2, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, agent_id, creator_id, title, session_id, work_dir, status, created_at, updated_at, unread_since, runtime_id ` type UpdateChatSessionTitleParams struct { ID pgtype.UUID `json:"id"` Title string `json:"title"` } func (q *Queries) UpdateChatSessionTitle(ctx context.Context, arg UpdateChatSessionTitleParams) (ChatSession, error) { row := q.db.QueryRow(ctx, updateChatSessionTitle, arg.ID, arg.Title) var i ChatSession err := row.Scan( &i.ID, &i.WorkspaceID, &i.AgentID, &i.CreatorID, &i.Title, &i.SessionID, &i.WorkDir, &i.Status, &i.CreatedAt, &i.UpdatedAt, &i.UnreadSince, &i.RuntimeID, ) return i, err }