plan-approve-once policy + cooler empty states + remove graph header
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

Backend:
- proposePlan sets plan window in autonomy_settings (nomos:plan:<session>)
- run handler checks plan window — auto-executes config_mutation commands
  within plan without per-action approval
- planWindowActive function in server.go
- Plan window cleaned up on completeTask (already covered by LIKE '%:' || )

Frontend:
- Removed 'Session graph' header bar
- Cooler empty states: Plan shows animated dots + 'Awaiting plan…',
  Activity shows pulsing dots + 'Waiting for activity…'
This commit is contained in:
2026-07-14 13:04:51 +02:00
parent 2ed169d239
commit b423cf4dea
6 changed files with 59 additions and 11 deletions

View File

@@ -1313,6 +1313,26 @@ func classifyAndGate(ctx context.Context, pool *db.Pool, agentID, targetID uuid.
return textResult(fmt.Sprintf("run on %s (read_only, auto): %s", targetSlug, out))
}
// Plan window: if a plan was proposed (and possibly approved), this
// command is part of an in-flight plan. The plan IS the approval —
// config_mutation commands within an active plan auto-run without
// per-action approval. Created by propose_plan, checked by planWindowActive.
if riskClass == policy.RiskConfigMutation && sessionID != "" && planWindowActive(ctx, pool, sessionID) {
host, user, wrap, rerr := resolveExecTarget(ctx, pool, targetSlug)
if rerr != nil {
pool.Exec(ctx, `UPDATE executions SET status='failed', result=$2::jsonb WHERE entity_id=$1`, id, jsonErr("%s", rerr.Error()))
return textResult(fmt.Sprintf("resolve target: %v", rerr))
}
out, xerr := sshExec(ctx, host, user, wrap(command))
if xerr != nil {
pool.Exec(ctx, `UPDATE executions SET status='failed', result=$2::jsonb WHERE entity_id=$1`, id, jsonErr("%s: %s", xerr.Error(), out))
return textResult(fmt.Sprintf("run on %s: ERROR %v\n%s", targetSlug, xerr, out))
}
pool.Exec(ctx, `UPDATE executions SET status='completed', result=$2::jsonb WHERE entity_id=$1`, id, jsonOut(out))
slog.Info("mcp: run auto-executed via plan window", "target", targetSlug, "execution_id", id)
return textResult(fmt.Sprintf("run on %s (config_mutation, auto via plan): %s", targetSlug, out))
}
// Assent window: if the operator recently approved a plan in this
// agent's chat session, config_mutation commands auto-run without
// re-approval. This is the "approve the plan, carry it out" path — the
@@ -1408,6 +1428,21 @@ func executeApprovedViaAPI(ctx context.Context, execID uuid.UUID, targetSlug, ac
}
}
// planWindowActive reports whether a plan has been proposed (or approved)
// for this session. Created by propose_plan, the window allows config_mutation
// commands to auto-execute without per-action approval — the plan IS the
// approval. Plan-approve-once policy (2026-07-14).
func planWindowActive(ctx context.Context, pool *db.Pool, sessionID string) bool {
if sessionID == "" {
return false
}
var val string
err := pool.QueryRow(ctx,
"SELECT value FROM autonomy_settings WHERE key = $1",
"nomos:plan:"+sessionID).Scan(&val)
return err == nil && (val == "proposed" || val == "active")
}
// assentWindowActive checks whether the operator has recently approved a plan
// in THIS TASK's chat session. The agent sets an
// assent_window.agent:<uuid>.session:<id> key in autonomy_settings with an