Files
goclaw/cmd/gateway_callbacks.go
T
viettranx 6595c0185a refactor: merge file writers into config permissions + agent hard delete
Migration 000023:
- Migrate group_file_writers data into agent_config_permissions (config_type='file_writer')
- Add FK CASCADE/SET NULL on 16 tables for agent hard delete
- Clean up soft-deleted zombie agents
- ALTER scope VARCHAR(100) → VARCHAR(255)
- Partial unique index on agent_key (allows reuse after delete)

Store layer:
- Remove GroupWriterCache, GroupFileWriterData, 5 AgentStore methods
- Add CheckFileWriterPermission (fail-open), ListFileWriters (cached)
- Add scope filter to ConfigPermissionStore.List()

Tools: Replace GroupWriterCache with ConfigPermissionStore in read_file,
write_file, edit, cron, context_file_interceptor

Agent loop: buildGroupWriterPrompt uses ListFileWriters + metadata parsing

Channels: Telegram commands rewritten to use configPermStore.Grant/Revoke/List
HTTP API: Writer endpoints rewired to configPermStore (same response shapes)

Hard delete: agents.go DELETE FROM instead of soft delete
2026-03-19 13:35:57 +07:00

75 lines
2.8 KiB
Go

package cmd
import (
"context"
"encoding/json"
"log/slog"
"strings"
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/agent"
"github.com/nextlevelbuilder/goclaw/internal/bootstrap"
"github.com/nextlevelbuilder/goclaw/internal/store"
"github.com/nextlevelbuilder/goclaw/internal/tools"
)
// buildEnsureUserFiles creates the per-user file seeding callback.
// Seeds per-user context files on first chat (new user profile).
func buildEnsureUserFiles(as store.AgentStore, configPermStore store.ConfigPermissionStore) agent.EnsureUserFilesFunc {
return func(ctx context.Context, agentID uuid.UUID, userID, agentType, workspace, channel string) (string, error) {
isNew, effectiveWs, err := as.GetOrCreateUserProfile(ctx, agentID, userID, workspace, channel)
if err != nil {
return effectiveWs, err
}
if !isNew {
return effectiveWs, nil // already profiled = already seeded
}
// Auto-add first group member as a file writer (bootstrap the allowlist).
if configPermStore != nil && (strings.HasPrefix(userID, "group:") || strings.HasPrefix(userID, "guild:")) {
senderID := store.SenderIDFromContext(ctx)
if senderID != "" {
parts := strings.SplitN(senderID, "|", 2)
numericID := parts[0]
senderUsername := ""
if len(parts) > 1 {
senderUsername = parts[1]
}
meta, _ := json.Marshal(map[string]string{"displayName": "", "username": senderUsername})
if addErr := configPermStore.Grant(ctx, &store.ConfigPermission{
AgentID: agentID,
Scope: userID,
ConfigType: "file_writer",
UserID: numericID,
Permission: "allow",
Metadata: meta,
}); addErr != nil {
slog.Warn("failed to auto-add group file writer", "error", addErr, "sender", numericID, "group", userID)
}
// No bus broadcast needed — Grant already invalidates cache
}
}
_, err = bootstrap.SeedUserFiles(ctx, as, agentID, userID, agentType)
return effectiveWs, err
}
}
// buildBootstrapCleanup creates a callback that removes BOOTSTRAP.md for a user.
// Used as a safety net after enough conversation turns, in case the LLM
// didn't clear BOOTSTRAP.md itself. Idempotent — no-op if already cleared.
func buildBootstrapCleanup(as store.AgentStore) agent.BootstrapCleanupFunc {
return func(ctx context.Context, agentID uuid.UUID, userID string) error {
return as.DeleteUserContextFile(ctx, agentID, userID, bootstrap.BootstrapFile)
}
}
// buildContextFileLoader creates the per-request context file loader callback.
// Delegates to the ContextFileInterceptor for type-aware routing.
func buildContextFileLoader(intc *tools.ContextFileInterceptor) agent.ContextFileLoaderFunc {
return func(ctx context.Context, agentID uuid.UUID, userID, agentType string) []bootstrap.ContextFile {
return intc.LoadContextFiles(ctx, agentID, userID, agentType)
}
}