mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 02:15:57 +00:00
feat(desktop): add channel management, paired devices, and multiple fixes
Channel Management:
- Add Channels settings tab with full CRUD for Telegram/Discord (Lite: max 1 each)
- Channel detail panel with tabs: General, Credentials, Managers
- Advanced settings dialog (network, limits, streaming, behavior, access control)
- Schema-driven field renderer with Combobox selects, Switch toggles
- Paired Devices section: approve/deny pending, revoke paired, WS event auto-refresh
- Pairing notification badge in sidebar footer with pending count
- i18n support (en/vi/zh) for all channel strings
Bug Fixes:
- Fix Vietnamese slug generation (NFD normalize + đ/Đ handling) in lib/slug.ts
- Make agent key field editable instead of read-only
- Fix trace detail input/output: use MarkdownRenderer with copy button instead of forced-dark CodePreview
- Fix API error parsing to handle both {error: "string"} and {error: {message}} formats
- Add Accept-Language header to desktop API client for i18n error messages
- Wrap raw err.Error() with i18n messages in HTTP handlers (agents, channel_instances)
- Increase SQLite MaxOpenConns from 2 to 4 to reduce SQLITE_BUSY contention
- Add retryOnBusy wrapper for context file seeding writes
- Update EditionCompareModal: channels moved from "false" to "1 Telegram + 1 Discord"
This commit is contained in:
1 parent
2bc0f5f14a
commit
6ea9b4d762
40 files changed
+2346
-64
No files matched your search
@@ -4,12 +4,32 @@ import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
)
|
||||
|
||||
// retryOnBusy retries fn up to 3 times on SQLITE_BUSY errors with 500ms delay.
|
||||
func retryOnBusy(fn func() error) error {
|
||||
for attempt := 0; attempt < 3; attempt++ {
|
||||
err := fn()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !strings.Contains(err.Error(), "SQLITE_BUSY") && !strings.Contains(err.Error(), "database is locked") {
|
||||
return err
|
||||
}
|
||||
if attempt < 2 {
|
||||
slog.Warn("bootstrap: retrying after SQLITE_BUSY", "attempt", attempt+1)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
return nil // unreachable, but satisfies compiler
|
||||
}
|
||||
|
||||
// SeedToStore seeds embedded templates into agent_context_files (agent-level).
|
||||
// Used for predefined agents only — open agents get per-user files via SeedUserFiles.
|
||||
// Only writes files that don't already have content.
|
||||
@@ -55,7 +75,7 @@ func SeedToStore(ctx context.Context, agentStore store.AgentStore, agentID uuid.
|
||||
continue
|
||||
}
|
||||
|
||||
if err := agentStore.SetAgentContextFile(ctx, agentID, name, string(content)); err != nil {
|
||||
if err := retryOnBusy(func() error { return agentStore.SetAgentContextFile(ctx, agentID, name, string(content)) }); err != nil {
|
||||
return seeded, err
|
||||
}
|
||||
seeded = append(seeded, name)
|
||||
@@ -66,7 +86,7 @@ func SeedToStore(ctx context.Context, agentStore store.AgentStore, agentID uuid.
|
||||
if !hasContent[UserPredefinedFile] {
|
||||
content, err := templateFS.ReadFile(filepath.Join("templates", UserPredefinedFile))
|
||||
if err == nil {
|
||||
if err := agentStore.SetAgentContextFile(ctx, agentID, UserPredefinedFile, string(content)); err != nil {
|
||||
if err := retryOnBusy(func() error { return agentStore.SetAgentContextFile(ctx, agentID, UserPredefinedFile, string(content)) }); err != nil {
|
||||
return seeded, err
|
||||
}
|
||||
seeded = append(seeded, UserPredefinedFile)
|
||||
@@ -167,7 +187,7 @@ func SeedUserFiles(ctx context.Context, agentStore store.AgentStore, agentID uui
|
||||
// This propagates wizard/dashboard-configured owner profile to the first user.
|
||||
if agentType == store.AgentTypePredefined && name == UserFile {
|
||||
if agentContent, ok := agentLevelFiles[name]; ok {
|
||||
if err := agentStore.SetUserContextFile(ctx, agentID, userID, name, agentContent); err != nil {
|
||||
if err := retryOnBusy(func() error { return agentStore.SetUserContextFile(ctx, agentID, userID, name, agentContent) }); err != nil {
|
||||
return seeded, err
|
||||
}
|
||||
seeded = append(seeded, name)
|
||||
@@ -188,7 +208,7 @@ func SeedUserFiles(ctx context.Context, agentStore store.AgentStore, agentID uui
|
||||
continue
|
||||
}
|
||||
|
||||
if err := agentStore.SetUserContextFile(ctx, agentID, userID, name, string(content)); err != nil {
|
||||
if err := retryOnBusy(func() error { return agentStore.SetUserContextFile(ctx, agentID, userID, name, string(content)) }); err != nil {
|
||||
return seeded, err
|
||||
}
|
||||
seeded = append(seeded, name)
|
||||
|
||||
Reference in new issue
Block a user