From 631f957cd9327659ed56964e81a8136472eac2ef Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 7 May 2026 10:37:16 +0800 Subject: [PATCH] feat(acp): sqlc queries (agent_kinds + sessions + events) + generated code --- internal/acp/queries/agent_kinds.sql | 50 ++++ internal/acp/queries/events.sql | 17 ++ internal/acp/queries/sessions.sql | 67 ++++++ internal/acp/sqlc/agent_kinds.sql.go | 263 ++++++++++++++++++++ internal/acp/sqlc/db.go | 32 +++ internal/acp/sqlc/events.sql.go | 112 +++++++++ internal/acp/sqlc/models.go | 296 +++++++++++++++++++++++ internal/acp/sqlc/sessions.sql.go | 347 +++++++++++++++++++++++++++ 8 files changed, 1184 insertions(+) create mode 100644 internal/acp/queries/agent_kinds.sql create mode 100644 internal/acp/queries/events.sql create mode 100644 internal/acp/queries/sessions.sql create mode 100644 internal/acp/sqlc/agent_kinds.sql.go create mode 100644 internal/acp/sqlc/db.go create mode 100644 internal/acp/sqlc/events.sql.go create mode 100644 internal/acp/sqlc/models.go create mode 100644 internal/acp/sqlc/sessions.sql.go diff --git a/internal/acp/queries/agent_kinds.sql b/internal/acp/queries/agent_kinds.sql new file mode 100644 index 0000000..ed46b7f --- /dev/null +++ b/internal/acp/queries/agent_kinds.sql @@ -0,0 +1,50 @@ +-- name: CreateAgentKind :one +INSERT INTO acp_agent_kinds ( + id, name, display_name, description, binary_path, args, encrypted_env, enabled, created_by +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +RETURNING id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at; + +-- name: GetAgentKindByID :one +SELECT id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +FROM acp_agent_kinds +WHERE id = $1; + +-- name: GetAgentKindByName :one +SELECT id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +FROM acp_agent_kinds +WHERE name = $1; + +-- name: ListAgentKinds :many +SELECT id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +FROM acp_agent_kinds +ORDER BY created_at DESC; + +-- name: ListEnabledAgentKinds :many +SELECT id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +FROM acp_agent_kinds +WHERE enabled = TRUE +ORDER BY name ASC; + +-- name: UpdateAgentKind :one +UPDATE acp_agent_kinds +SET display_name = $2, + description = $3, + binary_path = $4, + args = $5, + encrypted_env = $6, + enabled = $7, + updated_at = now() +WHERE id = $1 +RETURNING id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at; + +-- name: DeleteAgentKind :exec +DELETE FROM acp_agent_kinds WHERE id = $1; + +-- name: CountAgentKindUsage :one +SELECT COUNT(*) FROM acp_sessions WHERE agent_kind_id = $1; diff --git a/internal/acp/queries/events.sql b/internal/acp/queries/events.sql new file mode 100644 index 0000000..5ec9b4e --- /dev/null +++ b/internal/acp/queries/events.sql @@ -0,0 +1,17 @@ +-- name: InsertEvent :one +INSERT INTO acp_events ( + session_id, direction, rpc_kind, method, payload, payload_size, truncated +) VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id, session_id, direction, rpc_kind, method, payload, + payload_size, truncated, created_at; + +-- name: ListEventsSince :many +SELECT id, session_id, direction, rpc_kind, method, payload, + payload_size, truncated, created_at +FROM acp_events +WHERE session_id = $1 AND id > $2 +ORDER BY id ASC +LIMIT $3; + +-- name: PurgeEventsBefore :execrows +DELETE FROM acp_events WHERE created_at < $1; diff --git a/internal/acp/queries/sessions.sql b/internal/acp/queries/sessions.sql new file mode 100644 index 0000000..19c998e --- /dev/null +++ b/internal/acp/queries/sessions.sql @@ -0,0 +1,67 @@ +-- name: InsertSession :one +INSERT INTO acp_sessions ( + id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, branch, cwd_path, is_main_worktree, status +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +RETURNING id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, agent_session_id, + branch, cwd_path, is_main_worktree, status, + pid, exit_code, last_error, started_at, ended_at; + +-- name: GetSessionByID :one +SELECT id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, agent_session_id, + branch, cwd_path, is_main_worktree, status, + pid, exit_code, last_error, started_at, ended_at +FROM acp_sessions +WHERE id = $1; + +-- name: ListSessionsByUser :many +SELECT id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, agent_session_id, + branch, cwd_path, is_main_worktree, status, + pid, exit_code, last_error, started_at, ended_at +FROM acp_sessions +WHERE user_id = $1 +ORDER BY started_at DESC; + +-- name: ListAllSessions :many +SELECT id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, agent_session_id, + branch, cwd_path, is_main_worktree, status, + pid, exit_code, last_error, started_at, ended_at +FROM acp_sessions +ORDER BY started_at DESC; + +-- name: UpdateSessionRunning :exec +UPDATE acp_sessions +SET status = 'running', agent_session_id = $2, pid = $3 +WHERE id = $1; + +-- name: UpdateSessionFinished :exec +UPDATE acp_sessions +SET status = $2, exit_code = $3, last_error = $4, ended_at = now() +WHERE id = $1; + +-- name: CountActiveSessions :one +SELECT COUNT(*) FROM acp_sessions WHERE status IN ('starting', 'running'); + +-- name: CountActiveSessionsByUser :one +SELECT COUNT(*) FROM acp_sessions +WHERE user_id = $1 AND status IN ('starting', 'running'); + +-- name: ResetStuckSessionsOnRestart :many +UPDATE acp_sessions +SET status = 'crashed', + last_error = 'process_aborted_on_restart', + ended_at = now() +WHERE status IN ('starting', 'running') +RETURNING id, workspace_id, user_id, branch, agent_kind_id, is_main_worktree; + +-- name: ReleaseMainWorktree :execrows +UPDATE workspaces SET active_main_session_id = NULL +WHERE id = $1 AND active_main_session_id = $2; + +-- name: AcquireMainWorktree :execrows +UPDATE workspaces SET active_main_session_id = $1 +WHERE id = $2 AND active_main_session_id IS NULL; diff --git a/internal/acp/sqlc/agent_kinds.sql.go b/internal/acp/sqlc/agent_kinds.sql.go new file mode 100644 index 0000000..845e3e8 --- /dev/null +++ b/internal/acp/sqlc/agent_kinds.sql.go @@ -0,0 +1,263 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: agent_kinds.sql + +package acpsqlc + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const countAgentKindUsage = `-- name: CountAgentKindUsage :one +SELECT COUNT(*) FROM acp_sessions WHERE agent_kind_id = $1 +` + +func (q *Queries) CountAgentKindUsage(ctx context.Context, agentKindID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countAgentKindUsage, agentKindID) + var count int64 + err := row.Scan(&count) + return count, err +} + +const createAgentKind = `-- name: CreateAgentKind :one +INSERT INTO acp_agent_kinds ( + id, name, display_name, description, binary_path, args, encrypted_env, enabled, created_by +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +RETURNING id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +` + +type CreateAgentKindParams struct { + ID pgtype.UUID `json:"id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + BinaryPath string `json:"binary_path"` + Args []string `json:"args"` + EncryptedEnv []byte `json:"encrypted_env"` + Enabled bool `json:"enabled"` + CreatedBy pgtype.UUID `json:"created_by"` +} + +func (q *Queries) CreateAgentKind(ctx context.Context, arg CreateAgentKindParams) (AcpAgentKind, error) { + row := q.db.QueryRow(ctx, createAgentKind, + arg.ID, + arg.Name, + arg.DisplayName, + arg.Description, + arg.BinaryPath, + arg.Args, + arg.EncryptedEnv, + arg.Enabled, + arg.CreatedBy, + ) + var i AcpAgentKind + err := row.Scan( + &i.ID, + &i.Name, + &i.DisplayName, + &i.Description, + &i.BinaryPath, + &i.Args, + &i.EncryptedEnv, + &i.Enabled, + &i.CreatedBy, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const deleteAgentKind = `-- name: DeleteAgentKind :exec +DELETE FROM acp_agent_kinds WHERE id = $1 +` + +func (q *Queries) DeleteAgentKind(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, deleteAgentKind, id) + return err +} + +const getAgentKindByID = `-- name: GetAgentKindByID :one +SELECT id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +FROM acp_agent_kinds +WHERE id = $1 +` + +func (q *Queries) GetAgentKindByID(ctx context.Context, id pgtype.UUID) (AcpAgentKind, error) { + row := q.db.QueryRow(ctx, getAgentKindByID, id) + var i AcpAgentKind + err := row.Scan( + &i.ID, + &i.Name, + &i.DisplayName, + &i.Description, + &i.BinaryPath, + &i.Args, + &i.EncryptedEnv, + &i.Enabled, + &i.CreatedBy, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getAgentKindByName = `-- name: GetAgentKindByName :one +SELECT id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +FROM acp_agent_kinds +WHERE name = $1 +` + +func (q *Queries) GetAgentKindByName(ctx context.Context, name string) (AcpAgentKind, error) { + row := q.db.QueryRow(ctx, getAgentKindByName, name) + var i AcpAgentKind + err := row.Scan( + &i.ID, + &i.Name, + &i.DisplayName, + &i.Description, + &i.BinaryPath, + &i.Args, + &i.EncryptedEnv, + &i.Enabled, + &i.CreatedBy, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const listAgentKinds = `-- name: ListAgentKinds :many +SELECT id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +FROM acp_agent_kinds +ORDER BY created_at DESC +` + +func (q *Queries) ListAgentKinds(ctx context.Context) ([]AcpAgentKind, error) { + rows, err := q.db.Query(ctx, listAgentKinds) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AcpAgentKind + for rows.Next() { + var i AcpAgentKind + if err := rows.Scan( + &i.ID, + &i.Name, + &i.DisplayName, + &i.Description, + &i.BinaryPath, + &i.Args, + &i.EncryptedEnv, + &i.Enabled, + &i.CreatedBy, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listEnabledAgentKinds = `-- name: ListEnabledAgentKinds :many +SELECT id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +FROM acp_agent_kinds +WHERE enabled = TRUE +ORDER BY name ASC +` + +func (q *Queries) ListEnabledAgentKinds(ctx context.Context) ([]AcpAgentKind, error) { + rows, err := q.db.Query(ctx, listEnabledAgentKinds) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AcpAgentKind + for rows.Next() { + var i AcpAgentKind + if err := rows.Scan( + &i.ID, + &i.Name, + &i.DisplayName, + &i.Description, + &i.BinaryPath, + &i.Args, + &i.EncryptedEnv, + &i.Enabled, + &i.CreatedBy, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateAgentKind = `-- name: UpdateAgentKind :one +UPDATE acp_agent_kinds +SET display_name = $2, + description = $3, + binary_path = $4, + args = $5, + encrypted_env = $6, + enabled = $7, + updated_at = now() +WHERE id = $1 +RETURNING id, name, display_name, description, binary_path, args, encrypted_env, + enabled, created_by, created_at, updated_at +` + +type UpdateAgentKindParams struct { + ID pgtype.UUID `json:"id"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + BinaryPath string `json:"binary_path"` + Args []string `json:"args"` + EncryptedEnv []byte `json:"encrypted_env"` + Enabled bool `json:"enabled"` +} + +func (q *Queries) UpdateAgentKind(ctx context.Context, arg UpdateAgentKindParams) (AcpAgentKind, error) { + row := q.db.QueryRow(ctx, updateAgentKind, + arg.ID, + arg.DisplayName, + arg.Description, + arg.BinaryPath, + arg.Args, + arg.EncryptedEnv, + arg.Enabled, + ) + var i AcpAgentKind + err := row.Scan( + &i.ID, + &i.Name, + &i.DisplayName, + &i.Description, + &i.BinaryPath, + &i.Args, + &i.EncryptedEnv, + &i.Enabled, + &i.CreatedBy, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/internal/acp/sqlc/db.go b/internal/acp/sqlc/db.go new file mode 100644 index 0000000..d2dd965 --- /dev/null +++ b/internal/acp/sqlc/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package acpsqlc + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/acp/sqlc/events.sql.go b/internal/acp/sqlc/events.sql.go new file mode 100644 index 0000000..736d856 --- /dev/null +++ b/internal/acp/sqlc/events.sql.go @@ -0,0 +1,112 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: events.sql + +package acpsqlc + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const insertEvent = `-- name: InsertEvent :one +INSERT INTO acp_events ( + session_id, direction, rpc_kind, method, payload, payload_size, truncated +) VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id, session_id, direction, rpc_kind, method, payload, + payload_size, truncated, created_at +` + +type InsertEventParams struct { + SessionID pgtype.UUID `json:"session_id"` + Direction string `json:"direction"` + RpcKind string `json:"rpc_kind"` + Method *string `json:"method"` + Payload []byte `json:"payload"` + PayloadSize int32 `json:"payload_size"` + Truncated bool `json:"truncated"` +} + +func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) (AcpEvent, error) { + row := q.db.QueryRow(ctx, insertEvent, + arg.SessionID, + arg.Direction, + arg.RpcKind, + arg.Method, + arg.Payload, + arg.PayloadSize, + arg.Truncated, + ) + var i AcpEvent + err := row.Scan( + &i.ID, + &i.SessionID, + &i.Direction, + &i.RpcKind, + &i.Method, + &i.Payload, + &i.PayloadSize, + &i.Truncated, + &i.CreatedAt, + ) + return i, err +} + +const listEventsSince = `-- name: ListEventsSince :many +SELECT id, session_id, direction, rpc_kind, method, payload, + payload_size, truncated, created_at +FROM acp_events +WHERE session_id = $1 AND id > $2 +ORDER BY id ASC +LIMIT $3 +` + +type ListEventsSinceParams struct { + SessionID pgtype.UUID `json:"session_id"` + ID int64 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) ListEventsSince(ctx context.Context, arg ListEventsSinceParams) ([]AcpEvent, error) { + rows, err := q.db.Query(ctx, listEventsSince, arg.SessionID, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AcpEvent + for rows.Next() { + var i AcpEvent + if err := rows.Scan( + &i.ID, + &i.SessionID, + &i.Direction, + &i.RpcKind, + &i.Method, + &i.Payload, + &i.PayloadSize, + &i.Truncated, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const purgeEventsBefore = `-- name: PurgeEventsBefore :execrows +DELETE FROM acp_events WHERE created_at < $1 +` + +func (q *Queries) PurgeEventsBefore(ctx context.Context, createdAt pgtype.Timestamptz) (int64, error) { + result, err := q.db.Exec(ctx, purgeEventsBefore, createdAt) + if err != nil { + return 0, err + } + return result.RowsAffected(), nil +} diff --git a/internal/acp/sqlc/models.go b/internal/acp/sqlc/models.go new file mode 100644 index 0000000..4e17083 --- /dev/null +++ b/internal/acp/sqlc/models.go @@ -0,0 +1,296 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package acpsqlc + +import ( + "net/netip" + + "github.com/jackc/pgx/v5/pgtype" +) + +type AcpAgentKind struct { + ID pgtype.UUID `json:"id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + BinaryPath string `json:"binary_path"` + Args []string `json:"args"` + EncryptedEnv []byte `json:"encrypted_env"` + Enabled bool `json:"enabled"` + CreatedBy pgtype.UUID `json:"created_by"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type AcpEvent struct { + ID int64 `json:"id"` + SessionID pgtype.UUID `json:"session_id"` + Direction string `json:"direction"` + RpcKind string `json:"rpc_kind"` + Method *string `json:"method"` + Payload []byte `json:"payload"` + PayloadSize int32 `json:"payload_size"` + Truncated bool `json:"truncated"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +type AcpSession struct { + ID pgtype.UUID `json:"id"` + WorkspaceID pgtype.UUID `json:"workspace_id"` + ProjectID pgtype.UUID `json:"project_id"` + AgentKindID pgtype.UUID `json:"agent_kind_id"` + UserID pgtype.UUID `json:"user_id"` + IssueID pgtype.UUID `json:"issue_id"` + RequirementID pgtype.UUID `json:"requirement_id"` + AgentSessionID *string `json:"agent_session_id"` + Branch string `json:"branch"` + CwdPath string `json:"cwd_path"` + IsMainWorktree bool `json:"is_main_worktree"` + Status string `json:"status"` + Pid *int32 `json:"pid"` + ExitCode *int32 `json:"exit_code"` + LastError *string `json:"last_error"` + StartedAt pgtype.Timestamptz `json:"started_at"` + EndedAt pgtype.Timestamptz `json:"ended_at"` +} + +type AuditLog struct { + ID int64 `json:"id"` + UserID pgtype.UUID `json:"user_id"` + Action string `json:"action"` + TargetType *string `json:"target_type"` + TargetID *string `json:"target_id"` + Ip *netip.Addr `json:"ip"` + Metadata []byte `json:"metadata"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +type Conversation struct { + ID pgtype.UUID `json:"id"` + UserID pgtype.UUID `json:"user_id"` + ProjectID pgtype.UUID `json:"project_id"` + WorkspaceID pgtype.UUID `json:"workspace_id"` + IssueID pgtype.UUID `json:"issue_id"` + ModelID pgtype.UUID `json:"model_id"` + SystemPrompt string `json:"system_prompt"` + Title *string `json:"title"` + TitleGeneratedAt pgtype.Timestamptz `json:"title_generated_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + DeletedAt pgtype.Timestamptz `json:"deleted_at"` +} + +type GitCredential struct { + ID pgtype.UUID `json:"id"` + WorkspaceID pgtype.UUID `json:"workspace_id"` + Kind string `json:"kind"` + Username *string `json:"username"` + EncryptedSecret []byte `json:"encrypted_secret"` + Fingerprint *string `json:"fingerprint"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type Issue struct { + ID pgtype.UUID `json:"id"` + ProjectID pgtype.UUID `json:"project_id"` + RequirementID pgtype.UUID `json:"requirement_id"` + Number int32 `json:"number"` + Title string `json:"title"` + Description string `json:"description"` + Status string `json:"status"` + AssigneeID pgtype.UUID `json:"assignee_id"` + CreatedBy pgtype.UUID `json:"created_by"` + ClosedAt pgtype.Timestamptz `json:"closed_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + WorkspaceID pgtype.UUID `json:"workspace_id"` +} + +type Job struct { + ID pgtype.UUID `json:"id"` + Type string `json:"type"` + Payload []byte `json:"payload"` + Status string `json:"status"` + ScheduledAt pgtype.Timestamptz `json:"scheduled_at"` + Attempts int32 `json:"attempts"` + MaxAttempts int32 `json:"max_attempts"` + LeasedAt pgtype.Timestamptz `json:"leased_at"` + LeasedBy *string `json:"leased_by"` + LastError *string `json:"last_error"` + CompletedAt pgtype.Timestamptz `json:"completed_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type LlmEndpoint struct { + ID pgtype.UUID `json:"id"` + Provider string `json:"provider"` + DisplayName string `json:"display_name"` + BaseUrl string `json:"base_url"` + ApiKeyEncrypted []byte `json:"api_key_encrypted"` + CreatedBy pgtype.UUID `json:"created_by"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type LlmModel struct { + ID pgtype.UUID `json:"id"` + EndpointID pgtype.UUID `json:"endpoint_id"` + ModelID string `json:"model_id"` + DisplayName string `json:"display_name"` + Capabilities []byte `json:"capabilities"` + ContextWindow int32 `json:"context_window"` + MaxOutputTokens int32 `json:"max_output_tokens"` + PromptPricePerMillionUsd pgtype.Numeric `json:"prompt_price_per_million_usd"` + CompletionPricePerMillionUsd pgtype.Numeric `json:"completion_price_per_million_usd"` + ThinkingPricePerMillionUsd pgtype.Numeric `json:"thinking_price_per_million_usd"` + IsTitleGenerator bool `json:"is_title_generator"` + Enabled bool `json:"enabled"` + SortOrder int32 `json:"sort_order"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type LlmUsage struct { + ID int64 `json:"id"` + ConversationID pgtype.UUID `json:"conversation_id"` + MessageID int64 `json:"message_id"` + UserID pgtype.UUID `json:"user_id"` + EndpointID pgtype.UUID `json:"endpoint_id"` + ModelID pgtype.UUID `json:"model_id"` + PromptTokens int32 `json:"prompt_tokens"` + CompletionTokens int32 `json:"completion_tokens"` + ThinkingTokens int32 `json:"thinking_tokens"` + CostUsd pgtype.Numeric `json:"cost_usd"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +type Message struct { + ID int64 `json:"id"` + ConversationID pgtype.UUID `json:"conversation_id"` + Role string `json:"role"` + Content string `json:"content"` + Thinking *string `json:"thinking"` + ToolCalls []byte `json:"tool_calls"` + Status string `json:"status"` + ErrorMessage *string `json:"error_message"` + PromptTokens *int32 `json:"prompt_tokens"` + CompletionTokens *int32 `json:"completion_tokens"` + ThinkingTokens *int32 `json:"thinking_tokens"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type MessageAttachment struct { + ID pgtype.UUID `json:"id"` + UserID pgtype.UUID `json:"user_id"` + MessageID *int64 `json:"message_id"` + Filename string `json:"filename"` + MimeType string `json:"mime_type"` + SizeBytes int64 `json:"size_bytes"` + Sha256 string `json:"sha256"` + StoragePath string `json:"storage_path"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +type Notification struct { + ID pgtype.UUID `json:"id"` + UserID pgtype.UUID `json:"user_id"` + Topic string `json:"topic"` + Severity string `json:"severity"` + Title string `json:"title"` + Body string `json:"body"` + Link *string `json:"link"` + Metadata []byte `json:"metadata"` + ReadAt pgtype.Timestamptz `json:"read_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +type Project struct { + ID pgtype.UUID `json:"id"` + Slug string `json:"slug"` + Name string `json:"name"` + Description string `json:"description"` + Visibility string `json:"visibility"` + OwnerID pgtype.UUID `json:"owner_id"` + ArchivedAt pgtype.Timestamptz `json:"archived_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type PromptTemplate struct { + ID pgtype.UUID `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Scope string `json:"scope"` + OwnerID pgtype.UUID `json:"owner_id"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type Requirement struct { + ID pgtype.UUID `json:"id"` + ProjectID pgtype.UUID `json:"project_id"` + Number int32 `json:"number"` + Title string `json:"title"` + Description string `json:"description"` + Phase string `json:"phase"` + Status string `json:"status"` + OwnerID pgtype.UUID `json:"owner_id"` + ClosedAt pgtype.Timestamptz `json:"closed_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + WorkspaceID pgtype.UUID `json:"workspace_id"` +} + +type User struct { + ID pgtype.UUID `json:"id"` + Email string `json:"email"` + PasswordHash string `json:"password_hash"` + DisplayName string `json:"display_name"` + IsAdmin bool `json:"is_admin"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type UserSession struct { + ID pgtype.UUID `json:"id"` + UserID pgtype.UUID `json:"user_id"` + TokenHash []byte `json:"token_hash"` + ExpiresAt pgtype.Timestamptz `json:"expires_at"` + LastSeenAt pgtype.Timestamptz `json:"last_seen_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +type Workspace struct { + ID pgtype.UUID `json:"id"` + ProjectID pgtype.UUID `json:"project_id"` + Slug string `json:"slug"` + Name string `json:"name"` + Description string `json:"description"` + GitRemoteUrl string `json:"git_remote_url"` + DefaultBranch string `json:"default_branch"` + MainPath string `json:"main_path"` + SyncStatus string `json:"sync_status"` + LastSyncedAt pgtype.Timestamptz `json:"last_synced_at"` + LastSyncError *string `json:"last_sync_error"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + ActiveMainSessionID pgtype.UUID `json:"active_main_session_id"` +} + +type WorkspaceWorktree struct { + ID pgtype.UUID `json:"id"` + WorkspaceID pgtype.UUID `json:"workspace_id"` + Branch string `json:"branch"` + Path string `json:"path"` + Status string `json:"status"` + ActiveHolder *string `json:"active_holder"` + AcquiredAt pgtype.Timestamptz `json:"acquired_at"` + LastUsedAt pgtype.Timestamptz `json:"last_used_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + PruneWarningAt pgtype.Timestamptz `json:"prune_warning_at"` +} diff --git a/internal/acp/sqlc/sessions.sql.go b/internal/acp/sqlc/sessions.sql.go new file mode 100644 index 0000000..9322bec --- /dev/null +++ b/internal/acp/sqlc/sessions.sql.go @@ -0,0 +1,347 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: sessions.sql + +package acpsqlc + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const acquireMainWorktree = `-- name: AcquireMainWorktree :execrows +UPDATE workspaces SET active_main_session_id = $1 +WHERE id = $2 AND active_main_session_id IS NULL +` + +type AcquireMainWorktreeParams struct { + ActiveMainSessionID pgtype.UUID `json:"active_main_session_id"` + ID pgtype.UUID `json:"id"` +} + +func (q *Queries) AcquireMainWorktree(ctx context.Context, arg AcquireMainWorktreeParams) (int64, error) { + result, err := q.db.Exec(ctx, acquireMainWorktree, arg.ActiveMainSessionID, arg.ID) + if err != nil { + return 0, err + } + return result.RowsAffected(), nil +} + +const countActiveSessions = `-- name: CountActiveSessions :one +SELECT COUNT(*) FROM acp_sessions WHERE status IN ('starting', 'running') +` + +func (q *Queries) CountActiveSessions(ctx context.Context) (int64, error) { + row := q.db.QueryRow(ctx, countActiveSessions) + var count int64 + err := row.Scan(&count) + return count, err +} + +const countActiveSessionsByUser = `-- name: CountActiveSessionsByUser :one +SELECT COUNT(*) FROM acp_sessions +WHERE user_id = $1 AND status IN ('starting', 'running') +` + +func (q *Queries) CountActiveSessionsByUser(ctx context.Context, userID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countActiveSessionsByUser, userID) + var count int64 + err := row.Scan(&count) + return count, err +} + +const getSessionByID = `-- name: GetSessionByID :one +SELECT id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, agent_session_id, + branch, cwd_path, is_main_worktree, status, + pid, exit_code, last_error, started_at, ended_at +FROM acp_sessions +WHERE id = $1 +` + +func (q *Queries) GetSessionByID(ctx context.Context, id pgtype.UUID) (AcpSession, error) { + row := q.db.QueryRow(ctx, getSessionByID, id) + var i AcpSession + err := row.Scan( + &i.ID, + &i.WorkspaceID, + &i.ProjectID, + &i.AgentKindID, + &i.UserID, + &i.IssueID, + &i.RequirementID, + &i.AgentSessionID, + &i.Branch, + &i.CwdPath, + &i.IsMainWorktree, + &i.Status, + &i.Pid, + &i.ExitCode, + &i.LastError, + &i.StartedAt, + &i.EndedAt, + ) + return i, err +} + +const insertSession = `-- name: InsertSession :one +INSERT INTO acp_sessions ( + id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, branch, cwd_path, is_main_worktree, status +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +RETURNING id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, agent_session_id, + branch, cwd_path, is_main_worktree, status, + pid, exit_code, last_error, started_at, ended_at +` + +type InsertSessionParams struct { + ID pgtype.UUID `json:"id"` + WorkspaceID pgtype.UUID `json:"workspace_id"` + ProjectID pgtype.UUID `json:"project_id"` + AgentKindID pgtype.UUID `json:"agent_kind_id"` + UserID pgtype.UUID `json:"user_id"` + IssueID pgtype.UUID `json:"issue_id"` + RequirementID pgtype.UUID `json:"requirement_id"` + Branch string `json:"branch"` + CwdPath string `json:"cwd_path"` + IsMainWorktree bool `json:"is_main_worktree"` + Status string `json:"status"` +} + +func (q *Queries) InsertSession(ctx context.Context, arg InsertSessionParams) (AcpSession, error) { + row := q.db.QueryRow(ctx, insertSession, + arg.ID, + arg.WorkspaceID, + arg.ProjectID, + arg.AgentKindID, + arg.UserID, + arg.IssueID, + arg.RequirementID, + arg.Branch, + arg.CwdPath, + arg.IsMainWorktree, + arg.Status, + ) + var i AcpSession + err := row.Scan( + &i.ID, + &i.WorkspaceID, + &i.ProjectID, + &i.AgentKindID, + &i.UserID, + &i.IssueID, + &i.RequirementID, + &i.AgentSessionID, + &i.Branch, + &i.CwdPath, + &i.IsMainWorktree, + &i.Status, + &i.Pid, + &i.ExitCode, + &i.LastError, + &i.StartedAt, + &i.EndedAt, + ) + return i, err +} + +const listAllSessions = `-- name: ListAllSessions :many +SELECT id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, agent_session_id, + branch, cwd_path, is_main_worktree, status, + pid, exit_code, last_error, started_at, ended_at +FROM acp_sessions +ORDER BY started_at DESC +` + +func (q *Queries) ListAllSessions(ctx context.Context) ([]AcpSession, error) { + rows, err := q.db.Query(ctx, listAllSessions) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AcpSession + for rows.Next() { + var i AcpSession + if err := rows.Scan( + &i.ID, + &i.WorkspaceID, + &i.ProjectID, + &i.AgentKindID, + &i.UserID, + &i.IssueID, + &i.RequirementID, + &i.AgentSessionID, + &i.Branch, + &i.CwdPath, + &i.IsMainWorktree, + &i.Status, + &i.Pid, + &i.ExitCode, + &i.LastError, + &i.StartedAt, + &i.EndedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listSessionsByUser = `-- name: ListSessionsByUser :many +SELECT id, workspace_id, project_id, agent_kind_id, user_id, + issue_id, requirement_id, agent_session_id, + branch, cwd_path, is_main_worktree, status, + pid, exit_code, last_error, started_at, ended_at +FROM acp_sessions +WHERE user_id = $1 +ORDER BY started_at DESC +` + +func (q *Queries) ListSessionsByUser(ctx context.Context, userID pgtype.UUID) ([]AcpSession, error) { + rows, err := q.db.Query(ctx, listSessionsByUser, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AcpSession + for rows.Next() { + var i AcpSession + if err := rows.Scan( + &i.ID, + &i.WorkspaceID, + &i.ProjectID, + &i.AgentKindID, + &i.UserID, + &i.IssueID, + &i.RequirementID, + &i.AgentSessionID, + &i.Branch, + &i.CwdPath, + &i.IsMainWorktree, + &i.Status, + &i.Pid, + &i.ExitCode, + &i.LastError, + &i.StartedAt, + &i.EndedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const releaseMainWorktree = `-- name: ReleaseMainWorktree :execrows +UPDATE workspaces SET active_main_session_id = NULL +WHERE id = $1 AND active_main_session_id = $2 +` + +type ReleaseMainWorktreeParams struct { + ID pgtype.UUID `json:"id"` + ActiveMainSessionID pgtype.UUID `json:"active_main_session_id"` +} + +func (q *Queries) ReleaseMainWorktree(ctx context.Context, arg ReleaseMainWorktreeParams) (int64, error) { + result, err := q.db.Exec(ctx, releaseMainWorktree, arg.ID, arg.ActiveMainSessionID) + if err != nil { + return 0, err + } + return result.RowsAffected(), nil +} + +const resetStuckSessionsOnRestart = `-- name: ResetStuckSessionsOnRestart :many +UPDATE acp_sessions +SET status = 'crashed', + last_error = 'process_aborted_on_restart', + ended_at = now() +WHERE status IN ('starting', 'running') +RETURNING id, workspace_id, user_id, branch, agent_kind_id, is_main_worktree +` + +type ResetStuckSessionsOnRestartRow struct { + ID pgtype.UUID `json:"id"` + WorkspaceID pgtype.UUID `json:"workspace_id"` + UserID pgtype.UUID `json:"user_id"` + Branch string `json:"branch"` + AgentKindID pgtype.UUID `json:"agent_kind_id"` + IsMainWorktree bool `json:"is_main_worktree"` +} + +func (q *Queries) ResetStuckSessionsOnRestart(ctx context.Context) ([]ResetStuckSessionsOnRestartRow, error) { + rows, err := q.db.Query(ctx, resetStuckSessionsOnRestart) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ResetStuckSessionsOnRestartRow + for rows.Next() { + var i ResetStuckSessionsOnRestartRow + if err := rows.Scan( + &i.ID, + &i.WorkspaceID, + &i.UserID, + &i.Branch, + &i.AgentKindID, + &i.IsMainWorktree, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateSessionFinished = `-- name: UpdateSessionFinished :exec +UPDATE acp_sessions +SET status = $2, exit_code = $3, last_error = $4, ended_at = now() +WHERE id = $1 +` + +type UpdateSessionFinishedParams struct { + ID pgtype.UUID `json:"id"` + Status string `json:"status"` + ExitCode *int32 `json:"exit_code"` + LastError *string `json:"last_error"` +} + +func (q *Queries) UpdateSessionFinished(ctx context.Context, arg UpdateSessionFinishedParams) error { + _, err := q.db.Exec(ctx, updateSessionFinished, + arg.ID, + arg.Status, + arg.ExitCode, + arg.LastError, + ) + return err +} + +const updateSessionRunning = `-- name: UpdateSessionRunning :exec +UPDATE acp_sessions +SET status = 'running', agent_session_id = $2, pid = $3 +WHERE id = $1 +` + +type UpdateSessionRunningParams struct { + ID pgtype.UUID `json:"id"` + AgentSessionID *string `json:"agent_session_id"` + Pid *int32 `json:"pid"` +} + +func (q *Queries) UpdateSessionRunning(ctx context.Context, arg UpdateSessionRunningParams) error { + _, err := q.db.Exec(ctx, updateSessionRunning, arg.ID, arg.AgentSessionID, arg.Pid) + return err +}