Files
kandev/apps/backend/internal/office/repository/sqlite/runtime.go
T
2026-05-16 20:07:57 +01:00

167 lines
5.1 KiB
Go

package sqlite
import (
"context"
"database/sql"
"time"
)
// RuntimeState holds the DB-persisted runtime state for an agent instance.
type RuntimeState struct {
AgentID string `db:"agent_id"`
Status string `db:"status"`
PauseReason string `db:"pause_reason"`
LastRunFinishedAt *time.Time `db:"last_run_finished_at"`
UpdatedAt time.Time `db:"updated_at"`
}
// GetAgentRuntime returns the runtime state for a single agent.
// Returns nil, nil if the agent has no runtime row.
func (r *Repository) GetAgentRuntime(ctx context.Context, agentID string) (*RuntimeState, error) {
var state RuntimeState
err := r.ro.QueryRowxContext(ctx, r.ro.Rebind(
`SELECT * FROM office_agent_runtime WHERE agent_id = ?`), agentID).StructScan(&state)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return &state, nil
}
// UpsertAgentRuntime creates or updates the runtime state for an agent.
func (r *Repository) UpsertAgentRuntime(ctx context.Context, agentID, status, pauseReason string) error {
now := time.Now().UTC()
_, err := r.db.ExecContext(ctx, r.db.Rebind(`
INSERT INTO office_agent_runtime (agent_id, status, pause_reason, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(agent_id) DO UPDATE SET
status = excluded.status,
pause_reason = excluded.pause_reason,
updated_at = excluded.updated_at
`), agentID, status, pauseReason, now)
return err
}
// DeleteAgentRuntime removes the runtime row for an agent.
func (r *Repository) DeleteAgentRuntime(ctx context.Context, agentID string) error {
_, err := r.db.ExecContext(ctx, r.db.Rebind(
`DELETE FROM office_agent_runtime WHERE agent_id = ?`), agentID)
return err
}
// ListAgentRuntimes returns all runtime rows keyed by agent ID.
func (r *Repository) ListAgentRuntimes(ctx context.Context) (map[string]*RuntimeState, error) {
var rows []*RuntimeState
err := r.ro.SelectContext(ctx, &rows,
`SELECT * FROM office_agent_runtime`)
if err != nil {
return nil, err
}
result := make(map[string]*RuntimeState, len(rows))
for _, row := range rows {
result[row.AgentID] = row
}
return result, nil
}
// UpdateRuntimeLastRunFinished records the time an agent's run finished.
func (r *Repository) UpdateRuntimeLastRunFinished(ctx context.Context, agentID string, finishedAt time.Time) error {
now := time.Now().UTC()
_, err := r.db.ExecContext(ctx, r.db.Rebind(`
UPDATE office_agent_runtime
SET last_run_finished_at = ?, updated_at = ?
WHERE agent_id = ?
`), finishedAt, now, agentID)
return err
}
// ListDistinctTriggerRoutineIDs returns the set of routine IDs that have triggers in the DB.
func (r *Repository) ListDistinctTriggerRoutineIDs(ctx context.Context) ([]string, error) {
var ids []string
err := r.ro.SelectContext(ctx, &ids,
`SELECT DISTINCT routine_id FROM office_routine_triggers`)
if err != nil {
return nil, err
}
return ids, nil
}
// DeleteTriggersByRoutineID removes all triggers for a routine.
func (r *Repository) DeleteTriggersByRoutineID(ctx context.Context, routineID string) error {
_, err := r.db.ExecContext(ctx, r.db.Rebind(
`DELETE FROM office_routine_triggers WHERE routine_id = ?`), routineID)
return err
}
// DeleteRunsByRoutineID removes all runs for a routine.
func (r *Repository) DeleteRunsByRoutineID(ctx context.Context, routineID string) error {
_, err := r.db.ExecContext(ctx, r.db.Rebind(
`DELETE FROM office_routine_runs WHERE routine_id = ?`), routineID)
return err
}
// DeleteBudgetPoliciesForRemovedScopes removes budget policies whose scope_id
// is no longer in the provided set of valid IDs.
func (r *Repository) DeleteBudgetPoliciesForRemovedScopes(
ctx context.Context, scopeType string, validIDs []string,
) (int64, error) {
if len(validIDs) == 0 {
res, err := r.db.ExecContext(ctx, r.db.Rebind(
`DELETE FROM office_budget_policies WHERE scope_type = ?`), scopeType)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
// Build placeholders for the IN clause.
query := `DELETE FROM office_budget_policies WHERE scope_type = ? AND scope_id NOT IN (`
args := []interface{}{scopeType}
for i, id := range validIDs {
if i > 0 {
query += ","
}
query += "?"
args = append(args, id)
}
query += ")"
res, err := r.db.ExecContext(ctx, r.db.Rebind(query), args...)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
// DeleteChannelsForRemovedAgents removes channels whose agent_profile_id
// is no longer in the provided set of valid IDs.
func (r *Repository) DeleteChannelsForRemovedAgents(
ctx context.Context, validAgentIDs []string,
) (int64, error) {
if len(validAgentIDs) == 0 {
res, err := r.db.ExecContext(ctx,
`DELETE FROM office_channels`)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
query := `DELETE FROM office_channels WHERE agent_profile_id NOT IN (`
args := make([]interface{}, 0, len(validAgentIDs))
for i, id := range validAgentIDs {
if i > 0 {
query += ","
}
query += "?"
args = append(args, id)
}
query += ")"
res, err := r.db.ExecContext(ctx, r.db.Rebind(query), args...)
if err != nil {
return 0, err
}
return res.RowsAffected()
}