plan-approve-once policy + cooler empty states + remove graph header
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user