Make delivery and integration failures explicit
Persist reminder presentations and retry state, atomically complete collapsed deliveries, fall back across away reaches, and block permanent failures visibly (V-715, V-678). Fail closed when enabled integrations lack credentials and keep remote arms explicitly dark (V-691). Give mavweb one sanitized, request-correlated error contract (V-689). Owner explicitly requested direct commits to master.
This commit is contained in:
+21
-16
@@ -3,8 +3,8 @@ package main
|
||||
import (
|
||||
"cmp"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -32,13 +32,13 @@ var toolsTmpl = parsePage("tools", toolsHTML, template.FuncMap{
|
||||
// shell-word parsing; the box owner controls this input, quote a wrapper script
|
||||
// if an arg needs spaces).
|
||||
func handleTools(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI, session *webauthn.PasskeySession, requireStepUp bool) {
|
||||
if !requireCore(w, core, "tools") {
|
||||
if !requireCore(w, r, core, "tools") {
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
var msg string
|
||||
if r.Method == http.MethodPost {
|
||||
if !stepUpGate(w, session, requireStepUp) {
|
||||
if !stepUpGate(w, r, session, requireStepUp) {
|
||||
return
|
||||
}
|
||||
action := r.FormValue("action")
|
||||
@@ -49,54 +49,59 @@ func handleTools(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI, sessi
|
||||
cmd := strings.Fields(r.FormValue("cmd"))
|
||||
destructive := r.FormValue("destructive") != ""
|
||||
if name == "" || len(cmd) == 0 {
|
||||
http.Error(w, "name and cmd required", http.StatusBadRequest)
|
||||
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
|
||||
"name and cmd required", nil)
|
||||
return
|
||||
}
|
||||
if err := core.EnableTool(ctx, name, cmd, destructive, scope, time.Now()); err != nil {
|
||||
log.Printf("tools: enable %q: %v", name, err)
|
||||
http.Error(w, "enable failed: "+err.Error(), http.StatusBadGateway)
|
||||
writeProblem(w, r, http.StatusBadGateway, problemToolsChange,
|
||||
"enable failed", fmt.Errorf("enable tool %q: %w", name, err))
|
||||
return
|
||||
}
|
||||
msg = "enabled " + name
|
||||
case "disable":
|
||||
if name == "" {
|
||||
http.Error(w, "name required", http.StatusBadRequest)
|
||||
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
|
||||
"name required", nil)
|
||||
return
|
||||
}
|
||||
if err := core.DisableTool(ctx, name); err != nil {
|
||||
log.Printf("tools: disable %q: %v", name, err)
|
||||
http.Error(w, "disable failed: "+err.Error(), http.StatusBadGateway)
|
||||
writeProblem(w, r, http.StatusBadGateway, problemToolsChange,
|
||||
"disable failed", fmt.Errorf("disable tool %q: %w", name, err))
|
||||
return
|
||||
}
|
||||
msg = "disabled " + name
|
||||
case "dismiss":
|
||||
if name == "" {
|
||||
http.Error(w, "name required", http.StatusBadRequest)
|
||||
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
|
||||
"name required", nil)
|
||||
return
|
||||
}
|
||||
if err := core.DeleteTool(ctx, name); err != nil {
|
||||
log.Printf("tools: dismiss %q: %v", name, err)
|
||||
http.Error(w, "dismiss failed: "+err.Error(), http.StatusBadGateway)
|
||||
writeProblem(w, r, http.StatusBadGateway, problemToolsChange,
|
||||
"dismiss failed", fmt.Errorf("dismiss tool %q: %w", name, err))
|
||||
return
|
||||
}
|
||||
msg = "dismissed " + name
|
||||
default:
|
||||
http.Error(w, "unknown action", http.StatusBadRequest)
|
||||
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
|
||||
"unknown action", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
proposed, err1 := core.ListTools(ctx, "proposed")
|
||||
enabled, err2 := core.ListTools(ctx, "enabled")
|
||||
if err := cmp.Or(err1, err2); err != nil {
|
||||
log.Printf("tools: %v", err)
|
||||
http.Error(w, "core read failed", http.StatusBadGateway)
|
||||
writeProblem(w, r, http.StatusBadGateway, problemCoreReadFailed,
|
||||
"core read failed", fmt.Errorf("list tools: %w", err))
|
||||
return
|
||||
}
|
||||
// MCP is off by default and an older core may not know the method at all,
|
||||
// so a failure here renders an empty section rather than breaking the page.
|
||||
servers, err := core.MCPServers(ctx)
|
||||
if err != nil {
|
||||
log.Printf("tools: mcp servers: %v", err)
|
||||
logProblem(r, http.StatusOK, problemCoreReadFailed,
|
||||
"MCP server status unavailable", fmt.Errorf("read MCP server status: %w", err))
|
||||
servers = nil
|
||||
}
|
||||
// Enabled rows are shown grouped by capability domain (Vikunja #452). A
|
||||
|
||||
Reference in New Issue
Block a user