feat(runtime): add owner tracking, filtering, and delete (#535)

Add owner_id to agent_runtime table to track who registered each runtime.
Backend: new delete endpoint with role-based permissions (owner/admin can
delete any, members only their own), list filtering by owner (?owner=me),
and agent dependency check before deletion.
Frontend: Mine/All filter toggle in runtime list, owner display in list
items and detail view, delete button with AlertDialog confirmation.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
LinYushen
2026-04-09 13:38:46 +08:00
committed by GitHub
parent b484b78cbd
commit ff27a249cc
16 changed files with 361 additions and 23 deletions

View File

@@ -11,6 +11,26 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const countAgentsByRuntime = `-- name: CountAgentsByRuntime :one
SELECT count(*) FROM agent WHERE runtime_id = $1
`
func (q *Queries) CountAgentsByRuntime(ctx context.Context, runtimeID pgtype.UUID) (int64, error) {
row := q.db.QueryRow(ctx, countAgentsByRuntime, runtimeID)
var count int64
err := row.Scan(&count)
return count, err
}
const deleteAgentRuntime = `-- name: DeleteAgentRuntime :exec
DELETE FROM agent_runtime WHERE id = $1
`
func (q *Queries) DeleteAgentRuntime(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteAgentRuntime, id)
return err
}
const failTasksForOfflineRuntimes = `-- name: FailTasksForOfflineRuntimes :many
UPDATE agent_task_queue
SET status = 'failed', completed_at = now(), error = 'runtime went offline'
@@ -50,7 +70,7 @@ func (q *Queries) FailTasksForOfflineRuntimes(ctx context.Context) ([]FailTasksF
}
const getAgentRuntime = `-- name: GetAgentRuntime :one
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at FROM agent_runtime
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id FROM agent_runtime
WHERE id = $1
`
@@ -70,12 +90,13 @@ func (q *Queries) GetAgentRuntime(ctx context.Context, id pgtype.UUID) (AgentRun
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
)
return i, err
}
const getAgentRuntimeForWorkspace = `-- name: GetAgentRuntimeForWorkspace :one
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at FROM agent_runtime
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id FROM agent_runtime
WHERE id = $1 AND workspace_id = $2
`
@@ -100,12 +121,13 @@ func (q *Queries) GetAgentRuntimeForWorkspace(ctx context.Context, arg GetAgentR
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
)
return i, err
}
const listAgentRuntimes = `-- name: ListAgentRuntimes :many
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at FROM agent_runtime
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id FROM agent_runtime
WHERE workspace_id = $1
ORDER BY created_at ASC
`
@@ -132,6 +154,52 @@ func (q *Queries) ListAgentRuntimes(ctx context.Context, workspaceID pgtype.UUID
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAgentRuntimesByOwner = `-- name: ListAgentRuntimesByOwner :many
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id FROM agent_runtime
WHERE workspace_id = $1 AND owner_id = $2
ORDER BY created_at ASC
`
type ListAgentRuntimesByOwnerParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) ListAgentRuntimesByOwner(ctx context.Context, arg ListAgentRuntimesByOwnerParams) ([]AgentRuntime, error) {
rows, err := q.db.Query(ctx, listAgentRuntimesByOwner, arg.WorkspaceID, arg.OwnerID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []AgentRuntime{}
for rows.Next() {
var i AgentRuntime
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.DaemonID,
&i.Name,
&i.RuntimeMode,
&i.Provider,
&i.Status,
&i.DeviceInfo,
&i.Metadata,
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
); err != nil {
return nil, err
}
@@ -191,7 +259,7 @@ const updateAgentRuntimeHeartbeat = `-- name: UpdateAgentRuntimeHeartbeat :one
UPDATE agent_runtime
SET status = 'online', last_seen_at = now(), updated_at = now()
WHERE id = $1
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id
`
func (q *Queries) UpdateAgentRuntimeHeartbeat(ctx context.Context, id pgtype.UUID) (AgentRuntime, error) {
@@ -210,6 +278,7 @@ func (q *Queries) UpdateAgentRuntimeHeartbeat(ctx context.Context, id pgtype.UUI
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
)
return i, err
}
@@ -224,8 +293,9 @@ INSERT INTO agent_runtime (
status,
device_info,
metadata,
owner_id,
last_seen_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, now())
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now())
ON CONFLICT (workspace_id, daemon_id, provider)
DO UPDATE SET
name = EXCLUDED.name,
@@ -233,9 +303,10 @@ DO UPDATE SET
status = EXCLUDED.status,
device_info = EXCLUDED.device_info,
metadata = EXCLUDED.metadata,
owner_id = COALESCE(EXCLUDED.owner_id, agent_runtime.owner_id),
last_seen_at = now(),
updated_at = now()
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id
`
type UpsertAgentRuntimeParams struct {
@@ -247,6 +318,7 @@ type UpsertAgentRuntimeParams struct {
Status string `json:"status"`
DeviceInfo string `json:"device_info"`
Metadata []byte `json:"metadata"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) UpsertAgentRuntime(ctx context.Context, arg UpsertAgentRuntimeParams) (AgentRuntime, error) {
@@ -259,6 +331,7 @@ func (q *Queries) UpsertAgentRuntime(ctx context.Context, arg UpsertAgentRuntime
arg.Status,
arg.DeviceInfo,
arg.Metadata,
arg.OwnerID,
)
var i AgentRuntime
err := row.Scan(
@@ -274,6 +347,7 @@ func (q *Queries) UpsertAgentRuntime(ctx context.Context, arg UpsertAgentRuntime
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
)
return i, err
}