Files
goclaw/internal/gateway/methods/exec_approval.go
T
viettranx 9115169c03 feat: expand audit logging via pub/sub event pattern
Replace direct ActivityStore injection with event-driven audit system.
Handlers emit audit events via msgBus.Broadcast(), a single subscriber
with buffered channel persists to activity_logs table.

Coverage expanded from 3 agent CRUD actions to ~65 audit points across
all HTTP handlers and WebSocket RPC methods including agents, providers,
skills, MCP servers, cron, sessions, teams, pairing, and more.
2026-03-12 18:34:56 +07:00

129 lines
3.8 KiB
Go

package methods
import (
"context"
"encoding/json"
"github.com/nextlevelbuilder/goclaw/internal/bus"
"github.com/nextlevelbuilder/goclaw/internal/gateway"
"github.com/nextlevelbuilder/goclaw/internal/i18n"
"github.com/nextlevelbuilder/goclaw/internal/store"
"github.com/nextlevelbuilder/goclaw/internal/tools"
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
)
// ExecApprovalMethods handles exec.approval.list, exec.approval.approve, exec.approval.deny.
type ExecApprovalMethods struct {
manager *tools.ExecApprovalManager
eventBus bus.EventPublisher
}
func NewExecApprovalMethods(manager *tools.ExecApprovalManager, eventBus bus.EventPublisher) *ExecApprovalMethods {
return &ExecApprovalMethods{manager: manager, eventBus: eventBus}
}
func (m *ExecApprovalMethods) Register(router *gateway.MethodRouter) {
router.Register(protocol.MethodApprovalsList, m.handleList)
router.Register(protocol.MethodApprovalsApprove, m.handleApprove)
router.Register(protocol.MethodApprovalsDeny, m.handleDeny)
}
func (m *ExecApprovalMethods) handleList(_ context.Context, client *gateway.Client, req *protocol.RequestFrame) {
if m.manager == nil {
client.SendResponse(protocol.NewOKResponse(req.ID, map[string]any{
"pending": []any{},
}))
return
}
pending := m.manager.ListPending()
type pendingInfo struct {
ID string `json:"id"`
Command string `json:"command"`
AgentID string `json:"agentId"`
CreatedAt int64 `json:"createdAt"`
}
items := make([]pendingInfo, 0, len(pending))
for _, pa := range pending {
items = append(items, pendingInfo{
ID: pa.ID,
Command: pa.Command,
AgentID: pa.AgentID,
CreatedAt: pa.CreatedAt.UnixMilli(),
})
}
client.SendResponse(protocol.NewOKResponse(req.ID, map[string]any{
"pending": items,
}))
}
func (m *ExecApprovalMethods) handleApprove(ctx context.Context, client *gateway.Client, req *protocol.RequestFrame) {
locale := store.LocaleFromContext(ctx)
if m.manager == nil {
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgExecApprovalDisabled)))
return
}
var params struct {
ID string `json:"id"`
Always bool `json:"always"` // true = allow-always, false = allow-once
}
if req.Params != nil {
json.Unmarshal(req.Params, &params)
}
if params.ID == "" {
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgRequired, "id")))
return
}
decision := tools.ApprovalAllowOnce
if params.Always {
decision = tools.ApprovalAllowAlways
}
if err := m.manager.Resolve(params.ID, decision); err != nil {
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrNotFound, err.Error()))
return
}
client.SendResponse(protocol.NewOKResponse(req.ID, map[string]any{
"resolved": true,
"decision": string(decision),
}))
emitAudit(m.eventBus, client, "exec.approved", "exec", params.ID)
}
func (m *ExecApprovalMethods) handleDeny(ctx context.Context, client *gateway.Client, req *protocol.RequestFrame) {
locale := store.LocaleFromContext(ctx)
if m.manager == nil {
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgExecApprovalDisabled)))
return
}
var params struct {
ID string `json:"id"`
}
if req.Params != nil {
json.Unmarshal(req.Params, &params)
}
if params.ID == "" {
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgRequired, "id")))
return
}
if err := m.manager.Resolve(params.ID, tools.ApprovalDeny); err != nil {
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrNotFound, err.Error()))
return
}
client.SendResponse(protocol.NewOKResponse(req.ID, map[string]any{
"resolved": true,
"decision": "deny",
}))
emitAudit(m.eventBus, client, "exec.denied", "exec", params.ID)
}