mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 01:16:45 +08:00
* fix(runtimes): preserve custom profile base runtime identity Co-authored-by: multica-agent <[email protected]> * fix(runtimes): pass both url params to the profile identity test TestUpdateRuntimeProfile_RejectsIdentityChange nested two withURLParam calls. That helper installs a fresh chi route context each time and does not copy what is already there, so the outer call for profileId dropped id: the handler answered 400 "workspace_id is required" and never reached the immutability check the test exists to cover. Use withURLParams, which carries existing params forward and is what the rest of this file already uses. Co-authored-by: multica-agent <[email protected]> * fix(runtimes): name a runtime target the same way on every surface The base-runtime picker spelled Oh-My-Pi with an inline ternary, so it was the only place that used the product name. The catalog row, both detail headers, the base-runtime detail row and the locked chip in the form kept rendering the stored id under `capitalize` — a user picked "Oh-My-Pi" and the next screen, and the saved profile, said "Omp". Move the name into runtimeTypeLabel next to the target whitelist and read it from every surface. profileRuntimeType replaces the repeated runtime_type ?? protocol_family fallback. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> Co-authored-by: Bohan-J <[email protected]>
588 lines
19 KiB
Go
588 lines
19 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: runtime_profile.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const createRuntimeProfile = `-- name: CreateRuntimeProfile :one
|
|
|
|
INSERT INTO runtime_profile (
|
|
workspace_id,
|
|
display_name,
|
|
protocol_family,
|
|
command_name,
|
|
description,
|
|
fixed_args,
|
|
visibility,
|
|
created_by,
|
|
enabled,
|
|
runtime_type
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
RETURNING id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at, runtime_type
|
|
`
|
|
|
|
type CreateRuntimeProfileParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
DisplayName string `json:"display_name"`
|
|
ProtocolFamily string `json:"protocol_family"`
|
|
CommandName string `json:"command_name"`
|
|
Description pgtype.Text `json:"description"`
|
|
FixedArgs []byte `json:"fixed_args"`
|
|
Visibility string `json:"visibility"`
|
|
CreatedBy pgtype.UUID `json:"created_by"`
|
|
Enabled bool `json:"enabled"`
|
|
RuntimeType string `json:"runtime_type"`
|
|
}
|
|
|
|
// Custom Runtime profiles (MUL-3284). Workspace-level definitions of a custom
|
|
// runtime; see migration 120 for the table. Relational integrity (workspace,
|
|
// created_by) is enforced in the application layer — there are no DB FKs.
|
|
func (q *Queries) CreateRuntimeProfile(ctx context.Context, arg CreateRuntimeProfileParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, createRuntimeProfile,
|
|
arg.WorkspaceID,
|
|
arg.DisplayName,
|
|
arg.ProtocolFamily,
|
|
arg.CommandName,
|
|
arg.Description,
|
|
arg.FixedArgs,
|
|
arg.Visibility,
|
|
arg.CreatedBy,
|
|
arg.Enabled,
|
|
arg.RuntimeType,
|
|
)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RuntimeType,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const deleteAgentRuntimesByProfile = `-- name: DeleteAgentRuntimesByProfile :many
|
|
DELETE FROM agent_runtime
|
|
WHERE profile_id = $1 AND workspace_id = $2
|
|
RETURNING id, workspace_id, owner_id, daemon_id, provider
|
|
`
|
|
|
|
type DeleteAgentRuntimesByProfileParams struct {
|
|
ProfileID pgtype.UUID `json:"profile_id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
type DeleteAgentRuntimesByProfileRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
OwnerID pgtype.UUID `json:"owner_id"`
|
|
DaemonID pgtype.Text `json:"daemon_id"`
|
|
Provider string `json:"provider"`
|
|
}
|
|
|
|
// Application-layer cascade: migration 120 dropped the DB ON DELETE CASCADE, so
|
|
// the profile-delete path must remove the profile's registered runtime
|
|
// instances itself. Returns the deleted rows so the caller can broadcast /
|
|
// audit. Runs inside the same transaction as DeleteRuntimeProfile.
|
|
func (q *Queries) DeleteAgentRuntimesByProfile(ctx context.Context, arg DeleteAgentRuntimesByProfileParams) ([]DeleteAgentRuntimesByProfileRow, error) {
|
|
rows, err := q.db.Query(ctx, deleteAgentRuntimesByProfile, arg.ProfileID, arg.WorkspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []DeleteAgentRuntimesByProfileRow{}
|
|
for rows.Next() {
|
|
var i DeleteAgentRuntimesByProfileRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.OwnerID,
|
|
&i.DaemonID,
|
|
&i.Provider,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const deleteRuntimeProfile = `-- name: DeleteRuntimeProfile :exec
|
|
DELETE FROM runtime_profile
|
|
WHERE id = $1 AND workspace_id = $2
|
|
`
|
|
|
|
type DeleteRuntimeProfileParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
func (q *Queries) DeleteRuntimeProfile(ctx context.Context, arg DeleteRuntimeProfileParams) error {
|
|
_, err := q.db.Exec(ctx, deleteRuntimeProfile, arg.ID, arg.WorkspaceID)
|
|
return err
|
|
}
|
|
|
|
const getRuntimeProfile = `-- name: GetRuntimeProfile :one
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at, runtime_type FROM runtime_profile
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetRuntimeProfile(ctx context.Context, id pgtype.UUID) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, getRuntimeProfile, id)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RuntimeType,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getRuntimeProfileForWorkspace = `-- name: GetRuntimeProfileForWorkspace :one
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at, runtime_type FROM runtime_profile
|
|
WHERE id = $1 AND workspace_id = $2
|
|
`
|
|
|
|
type GetRuntimeProfileForWorkspaceParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
func (q *Queries) GetRuntimeProfileForWorkspace(ctx context.Context, arg GetRuntimeProfileForWorkspaceParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, getRuntimeProfileForWorkspace, arg.ID, arg.WorkspaceID)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RuntimeType,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listActiveAgentsByProfile = `-- name: ListActiveAgentsByProfile :many
|
|
WITH blockers AS (
|
|
SELECT
|
|
a.id,
|
|
a.name,
|
|
a.kind,
|
|
a.system_key,
|
|
ar.id AS runtime_id,
|
|
ar.name AS runtime_name,
|
|
ar.custom_name AS runtime_custom_name,
|
|
ar.status AS runtime_status,
|
|
CASE
|
|
WHEN a.system_key IS NULL OR btrim(a.system_key) = '' THEN 'user'
|
|
WHEN btrim(a.system_key) = 'mika' THEN 'mika'
|
|
-- starts_with, not LIKE: '_' is a single-character wildcard in
|
|
-- LIKE, so 'agent_builder:%' also matches 'agent-builder:x' and
|
|
-- 'agentXbuilder:x'. Those would be classified here as carriers
|
|
-- and by the Go side as other_system, and the refusal would send
|
|
-- the user to an Agent Builder session that does not exist.
|
|
WHEN starts_with(btrim(a.system_key), 'agent_builder:') THEN 'agent_builder'
|
|
ELSE 'other_system'
|
|
END AS blocker_class
|
|
FROM agent a
|
|
JOIN agent_runtime ar ON ar.id = a.runtime_id
|
|
WHERE ar.profile_id = $1 AND ar.workspace_id = $2 AND a.archived_at IS NULL
|
|
)
|
|
SELECT
|
|
id,
|
|
name,
|
|
kind,
|
|
system_key,
|
|
runtime_id,
|
|
runtime_name,
|
|
runtime_custom_name,
|
|
runtime_status,
|
|
blocker_class,
|
|
count(*) OVER () AS total_count,
|
|
count(*) FILTER (WHERE blocker_class = 'user') OVER () AS user_count,
|
|
count(*) FILTER (WHERE blocker_class = 'mika') OVER () AS mika_count,
|
|
count(*) FILTER (WHERE blocker_class = 'agent_builder') OVER () AS agent_builder_count,
|
|
count(*) FILTER (WHERE blocker_class = 'other_system') OVER () AS other_system_count
|
|
FROM blockers
|
|
ORDER BY runtime_name ASC, name ASC
|
|
LIMIT $3::int
|
|
`
|
|
|
|
type ListActiveAgentsByProfileParams struct {
|
|
ProfileID pgtype.UUID `json:"profile_id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
MaxRows int32 `json:"max_rows"`
|
|
}
|
|
|
|
type ListActiveAgentsByProfileRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
Kind string `json:"kind"`
|
|
SystemKey pgtype.Text `json:"system_key"`
|
|
RuntimeID pgtype.UUID `json:"runtime_id"`
|
|
RuntimeName string `json:"runtime_name"`
|
|
RuntimeCustomName pgtype.Text `json:"runtime_custom_name"`
|
|
RuntimeStatus string `json:"runtime_status"`
|
|
BlockerClass string `json:"blocker_class"`
|
|
TotalCount int64 `json:"total_count"`
|
|
UserCount int64 `json:"user_count"`
|
|
MikaCount int64 `json:"mika_count"`
|
|
AgentBuilderCount int64 `json:"agent_builder_count"`
|
|
OtherSystemCount int64 `json:"other_system_count"`
|
|
}
|
|
|
|
// Active (non-archived) agents bound to any runtime instance of this profile.
|
|
// The profile-delete path uses this to refuse deletion (409) while agents
|
|
// still depend on it, mirroring the runtime-delete guard.
|
|
//
|
|
// It returns the rows rather than a count because the refusal has to name
|
|
// them: a profile spans every machine that registered it, so the agents
|
|
// blocking the delete are routinely bound to a different machine than the one
|
|
// the user was trying to clean up, and a bare count gives them no way to tell
|
|
// (GH #8456). Carrying the runtime is what lets the message say which machine.
|
|
//
|
|
// Deliberately not filtered by kind: it defines when deletion is refused, and
|
|
// narrowing it to user agents here would let a profile with a bound builder
|
|
// carrier through, whereupon TeardownRuntime would hard-delete that carrier.
|
|
//
|
|
// system_key rides along because it, not kind, decides what the user can
|
|
// actually do about a blocker. Mika is kind='user' with system_key='mika' and
|
|
// can be neither archived nor moved; a builder carrier is kind='system' and is
|
|
// released by its Builder session, not from the agent list.
|
|
//
|
|
// Bounded on purpose. The caller only needs to know that blockers exist, name a
|
|
// few, and report how many there are — it never needs every row. This runs
|
|
// inside the delete transaction while profile, runtime and agent rows are
|
|
// locked, and the response is buffered whole before it is written, so an
|
|
// unbounded read here would make both the time under lock and the response body
|
|
// grow with the number of agents a profile has accumulated across machines.
|
|
//
|
|
// The per-class counts are what let the refusal stay correct while bounded.
|
|
// Rows are ordered by machine and name, so which blockers land inside the LIMIT
|
|
// is arbitrary with respect to class: twenty ordinary agents can push the one
|
|
// Mika to position 21. A message whose advice came from the visible rows would
|
|
// then tell the user to archive all of them — the unactionable instruction this
|
|
// whole change removes, just deferred. So the recovery paths are chosen from
|
|
// these counts and only the names come from the rows.
|
|
//
|
|
// Every count is a window function over the pre-LIMIT result, so they stay
|
|
// exact no matter how small max_rows is. blocker_class is emitted per row as
|
|
// well, giving the classification one definition that the Go classifier is
|
|
// pinned against in tests.
|
|
func (q *Queries) ListActiveAgentsByProfile(ctx context.Context, arg ListActiveAgentsByProfileParams) ([]ListActiveAgentsByProfileRow, error) {
|
|
rows, err := q.db.Query(ctx, listActiveAgentsByProfile, arg.ProfileID, arg.WorkspaceID, arg.MaxRows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListActiveAgentsByProfileRow{}
|
|
for rows.Next() {
|
|
var i ListActiveAgentsByProfileRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Kind,
|
|
&i.SystemKey,
|
|
&i.RuntimeID,
|
|
&i.RuntimeName,
|
|
&i.RuntimeCustomName,
|
|
&i.RuntimeStatus,
|
|
&i.BlockerClass,
|
|
&i.TotalCount,
|
|
&i.UserCount,
|
|
&i.MikaCount,
|
|
&i.AgentBuilderCount,
|
|
&i.OtherSystemCount,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listAgentRuntimeIDsByProfile = `-- name: ListAgentRuntimeIDsByProfile :many
|
|
SELECT id FROM agent_runtime
|
|
WHERE profile_id = $1 AND workspace_id = $2
|
|
ORDER BY id
|
|
FOR UPDATE
|
|
`
|
|
|
|
type ListAgentRuntimeIDsByProfileParams struct {
|
|
ProfileID pgtype.UUID `json:"profile_id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// Enumerates the runtime instance rows registered against a profile. The
|
|
// profile-delete cascade walks these so it can run the same archived-agent /
|
|
// archived-squad / autopilot teardown the runtime-delete path uses before
|
|
// removing each runtime row — agent.runtime_id is ON DELETE RESTRICT, so a
|
|
// bare delete would 500 whenever an archived agent still references the row.
|
|
func (q *Queries) ListAgentRuntimeIDsByProfile(ctx context.Context, arg ListAgentRuntimeIDsByProfileParams) ([]pgtype.UUID, error) {
|
|
rows, err := q.db.Query(ctx, listAgentRuntimeIDsByProfile, arg.ProfileID, arg.WorkspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []pgtype.UUID{}
|
|
for rows.Next() {
|
|
var id pgtype.UUID
|
|
if err := rows.Scan(&id); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, id)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listEnabledRuntimeProfilesForWorkspace = `-- name: ListEnabledRuntimeProfilesForWorkspace :many
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at, runtime_type FROM runtime_profile
|
|
WHERE workspace_id = $1 AND enabled = true
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
// Daemon-facing list: only enabled profiles are candidates for a daemon to
|
|
// resolve on PATH and register. Ordered for stable output.
|
|
func (q *Queries) ListEnabledRuntimeProfilesForWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]RuntimeProfile, error) {
|
|
rows, err := q.db.Query(ctx, listEnabledRuntimeProfilesForWorkspace, workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []RuntimeProfile{}
|
|
for rows.Next() {
|
|
var i RuntimeProfile
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RuntimeType,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listRuntimeProfiles = `-- name: ListRuntimeProfiles :many
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at, runtime_type FROM runtime_profile
|
|
WHERE workspace_id = $1
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
func (q *Queries) ListRuntimeProfiles(ctx context.Context, workspaceID pgtype.UUID) ([]RuntimeProfile, error) {
|
|
rows, err := q.db.Query(ctx, listRuntimeProfiles, workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []RuntimeProfile{}
|
|
for rows.Next() {
|
|
var i RuntimeProfile
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RuntimeType,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const lockRuntimeProfileForDelete = `-- name: LockRuntimeProfileForDelete :one
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at, runtime_type FROM runtime_profile
|
|
WHERE id = $1 AND workspace_id = $2
|
|
FOR UPDATE
|
|
`
|
|
|
|
type LockRuntimeProfileForDeleteParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// See LockRuntimeProfileForRegistration. The stronger lock prevents a daemon
|
|
// from registering another instance between the delete plan and commit.
|
|
func (q *Queries) LockRuntimeProfileForDelete(ctx context.Context, arg LockRuntimeProfileForDeleteParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, lockRuntimeProfileForDelete, arg.ID, arg.WorkspaceID)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RuntimeType,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const lockRuntimeProfileForRegistration = `-- name: LockRuntimeProfileForRegistration :one
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at, runtime_type FROM runtime_profile
|
|
WHERE id = $1 AND workspace_id = $2
|
|
FOR KEY SHARE
|
|
`
|
|
|
|
type LockRuntimeProfileForRegistrationParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// Serializes daemon registration with profile deletion. Registration holds a
|
|
// KEY SHARE lock until its runtime row is committed; profile deletion takes an
|
|
// UPDATE lock, then locks the profile's runtime rows. Whichever starts first
|
|
// wins, so deletion cannot miss a runtime inserted from a stale profile read.
|
|
func (q *Queries) LockRuntimeProfileForRegistration(ctx context.Context, arg LockRuntimeProfileForRegistrationParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, lockRuntimeProfileForRegistration, arg.ID, arg.WorkspaceID)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RuntimeType,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateRuntimeProfile = `-- name: UpdateRuntimeProfile :one
|
|
UPDATE runtime_profile
|
|
SET display_name = COALESCE($1, display_name),
|
|
command_name = COALESCE($2, command_name),
|
|
description = COALESCE($3, description),
|
|
fixed_args = COALESCE($4, fixed_args),
|
|
visibility = COALESCE($5, visibility),
|
|
enabled = COALESCE($6, enabled),
|
|
updated_at = now()
|
|
WHERE id = $7 AND workspace_id = $8
|
|
RETURNING id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at, runtime_type
|
|
`
|
|
|
|
type UpdateRuntimeProfileParams struct {
|
|
DisplayName pgtype.Text `json:"display_name"`
|
|
CommandName pgtype.Text `json:"command_name"`
|
|
Description pgtype.Text `json:"description"`
|
|
FixedArgs []byte `json:"fixed_args"`
|
|
Visibility pgtype.Text `json:"visibility"`
|
|
Enabled pgtype.Bool `json:"enabled"`
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// Partial update via COALESCE: NULL args leave the column unchanged. The
|
|
// protocol_family is intentionally NOT updatable — changing the underlying
|
|
// backend of an existing profile would silently repoint every agent bound to
|
|
// it onto a different protocol; callers create a new profile instead.
|
|
func (q *Queries) UpdateRuntimeProfile(ctx context.Context, arg UpdateRuntimeProfileParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, updateRuntimeProfile,
|
|
arg.DisplayName,
|
|
arg.CommandName,
|
|
arg.Description,
|
|
arg.FixedArgs,
|
|
arg.Visibility,
|
|
arg.Enabled,
|
|
arg.ID,
|
|
arg.WorkspaceID,
|
|
)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RuntimeType,
|
|
)
|
|
return i, err
|
|
}
|