mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-29 04:20:21 +00:00
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
75 lines
2.8 KiB
Go
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)
|
|
}
|
|
}
|