fix: make execution names unique, move approval bar above input
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled

- Execution entity name now includes UUID suffix: 'pct_create on host:strong (abc12345)'
  so the (type,name) UNIQUE constraint doesn't block subsequent executions for the
  same target+action. Dedup now uses JOIN + LIKE prefix match to find only
  pending_approval executions.

- Move persistent approval bar from top of messages area to just above the
  chat input box (bottom-fixed position, above the textarea form).
This commit is contained in:
2026-07-09 23:42:49 +02:00
parent 9376dc7d89
commit a567930466
2 changed files with 43 additions and 47 deletions

View File

@@ -284,18 +284,17 @@ func newServer(pool *db.Pool, agentID uuid.UUID) *mcp.Server {
// Deduplicate: if a pending execution already exists for the same
// target+action, return the existing one instead of creating a
// duplicate. Prevents the LLM from re-requesting the same gated
// action in a tool-calling loop. Uses the entities type+name
// UNIQUE constraint as the dedup key (one execution per
// action:target pair).
// action in a tool-calling loop. Only blocks when a pending
// execution exists; completed/failed ones don't block.
if action == "systemctl" || action == "apt_upgrade" || action == "pct_create" {
execName := action + " on " + targetSlug
var existingID, existingStatus string
execNamePrefix := action + " on " + targetSlug
var existingID string
err := pool.QueryRow(ctx, `
SELECT e.id::text, COALESCE(ex.status,'') FROM entities e
LEFT JOIN executions ex ON ex.entity_id = e.id
WHERE e.type = 'execution' AND e.name = $1
ORDER BY e.created_at DESC LIMIT 1`, execName).Scan(&existingID, &existingStatus)
if err == nil && existingID != "" && existingStatus != "completed" && existingStatus != "failed" {
SELECT e.id::text FROM entities e
JOIN executions ex ON ex.entity_id = e.id
WHERE e.type = 'execution' AND e.name LIKE $1 AND ex.status = 'pending_approval'
ORDER BY e.created_at DESC LIMIT 1`, execNamePrefix+"%").Scan(&existingID)
if err == nil && existingID != "" {
return textResult(fmt.Sprintf("%s on %s is already queued for approval — execution %s. Wait for operator approval. Do not re-request.",
action, targetSlug, existingID)), nil
}
@@ -304,12 +303,10 @@ func newServer(pool *db.Pool, agentID uuid.UUID) *mcp.Server {
id, _ := uuid.NewV7()
correlationID := uuid.New().String()
// Write execution record. If the (type,name) UNIQUE constraint
// fires (dedup race), the INSERT silently does nothing and the
// existing record wins.
execName := action + " on " + targetSlug + " (" + id.String()[:8] + ")"
execSlug := "exec:" + targetSlug + ":" + id.String()[:8]
_, err := pool.Exec(ctx, `INSERT INTO entities (id, slug, type, name, attributes) VALUES ($1, $2, 'execution', $3, '{}') ON CONFLICT (type, name) DO NOTHING`,
id, execSlug, action+" on "+targetSlug)
_, err := pool.Exec(ctx, `INSERT INTO entities (id, slug, type, name, attributes) VALUES ($1, $2, 'execution', $3, '{}')`,
id, execSlug, execName)
if err != nil {
return textResult(fmt.Sprintf("error: failed to create execution: %v", err)), nil
}