mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-12 04:12:09 +00:00
9115169c03
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.
129 lines
3.8 KiB
Go
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, ¶ms)
|
|
}
|
|
|
|
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, ¶ms)
|
|
}
|
|
|
|
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)
|
|
}
|